Files
n8n-docs/main.py

120 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import requests
import json
import urllib.parse
from typing import Optional
import re
import unicodedata
import os
def define_env(env):
no_template = os.environ.get('NO_TEMPLATE', False)
# Define the character map and locales as in the original JavaScript version
CHAR_MAP = {"$":"dollar","%":"percent","&":"and","<":"less",">":"greater","|":"or","¢":"cent","£":"pound","¤":"currency","¥":"yen","©":"(c)","ª":"a","®":"(r)","º":"o","À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","Æ":"AE","Ç":"C","È":"E","É":"E","Ê":"E","Ë":"E","Ì":"I","Í":"I","Î":"I","Ï":"I","Ð":"D","Ñ":"N","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","Ù":"U","Ú":"U","Û":"U","Ü":"U","Ý":"Y","Þ":"TH","ß":"ss","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","æ":"ae","ç":"c","è":"e","é":"e","ê":"e","ë":"e","ì":"i","í":"i","î":"i","ï":"i","ð":"d","ñ":"n","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","ù":"u","ú":"u","û":"u","ü":"u","ý":"y","þ":"th","ÿ":"y","Ā":"A","ā":"a","Ă":"A","ă":"a","Ą":"A","ą":"a","Ć":"C","ć":"c","Č":"C","č":"c","Ď":"D","ď":"d","Đ":"DJ","đ":"dj","Ē":"E","ē":"e","Ė":"E","ė":"e","Ę":"e","ę":"e","Ě":"E","ě":"e","Ğ":"G","ğ":"g","Ģ":"G","ģ":"g","Ĩ":"I","ĩ":"i","Ī":"i","ī":"i","Į":"I","į":"i","İ":"I","ı":"i","Ķ":"k","ķ":"k","Ļ":"L","ļ":"l","Ľ":"L","ľ":"l","Ł":"L","ł":"l","Ń":"N","ń":"n","Ņ":"N","ņ":"n","Ň":"N","ň":"n","Ō":"O","ō":"o","Ő":"O","ő":"o","Œ":"OE","œ":"oe","Ŕ":"R","ŕ":"r","Ř":"R","ř":"r","Ś":"S","ś":"s","Ş":"S","ş":"s","Š":"S","š":"s","Ţ":"T","ţ":"t","Ť":"T","ť":"t","Ũ":"U","ũ":"u","Ū":"u","ū":"u","Ů":"U","ů":"u","Ű":"U","ű":"u","Ų":"U","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","ź":"z","Ż":"Z","ż":"z","Ž":"Z","ž":"z","Ə":"E","ƒ":"f","Ơ":"O","ơ":"o","Ư":"U","ư":"u","Lj":"LJ","lj":"lj","Nj":"NJ","nj":"nj","Ș":"S","ș":"s","Ț":"T","ț":"t","ə":"e","˚":"o","Ά":"A","Έ":"E","Ή":"H","Ί":"I","Ό":"O","Ύ":"Y","Ώ":"W","ΐ":"i","Α":"A","Β":"B","Γ":"G","Δ":"D","Ε":"E","Ζ":"Z","Η":"H","Θ":"8","Ι":"I","Κ":"K","Λ":"L","Μ":"M","Ν":"N","Ξ":"3","Ο":"O","Π":"P","Ρ":"R","Σ":"S","Τ":"T","Υ":"Y","Φ":"F","Χ":"X","Ψ":"PS","Ω":"W","Ϊ":"I","Ϋ":"Y","ά":"a","έ":"e","ή":"h","ί":"i","ΰ":"y","α":"a","β":"b","γ":"g","δ":"d","ε":"e","ζ":"z","η":"h","θ":"8","ι":"i","κ":"k","λ":"l","μ":"m","ν":"n","ξ":"3","ο":"o","π":"p","ρ":"r","ς":"s","σ":"s","τ":"t","υ":"y","φ":"f","χ":"x","ψ":"ps","ω":"w","ϊ":"i","ϋ":"y","ό":"o","ύ":"y","ώ":"w","Ё":"Yo","Ђ":"DJ","Є":"Ye","І":"I","Ї":"Yi","Ј":"J","Љ":"LJ","Њ":"NJ","Ћ":"C","Џ":"DZ","А":"A","Б":"B","В":"V","Г":"G","Д":"D","Е":"E","Ж":"Zh","З":"Z","И":"I","Й":"J","К":"K","Л":"L","М":"M","Н":"N","О":"O","П":"P","Р":"R","С":"S","Т":"T","У":"U","Ф":"F","Х":"H","Ц":"C","Ч":"Ch","Ш":"Sh","Щ":"Sh","Ъ":"U","Ы":"Y","Ь":"","Э":"E","Ю":"Yu","Я":"Ya","а":"a","б":"b","в":"v","г":"g","д":"d","е":"e","ж":"zh","з":"z","и":"i","й":"j","к":"k","л":"l","м":"m","н":"n","о":"o","п":"p","р":"r","с":"s","т":"t","у":"u","ф":"f","х":"h","ц":"c","ч":"ch","ш":"sh","щ":"sh","ъ":"u","ы":"y","ь":"","э":"e","ю":"yu","я":"ya","ё":"yo","ђ":"dj","є":"ye","і":"i","ї":"yi","ј":"j","љ":"lj","њ":"nj","ћ":"c","ѝ":"u","џ":"dz","Ґ":"G","ґ":"g","Ғ":"GH","ғ":"gh","Қ":"KH","қ":"kh","Ң":"NG","ң":"ng","Ү":"UE","ү":"ue","Ұ":"U","ұ":"u","Һ":"H","һ":"h","Ә":"AE","ә":"ae","Ө":"OE","ө":"oe","Ա":"A","Բ":"B","Գ":"G","Դ":"D","Ե":"E","Զ":"Z","Է":"E\'","Ը":"Y\'","Թ":"T\'","Ժ":"JH","Ի":"I","Լ":"L","Խ":"X","Ծ":"C\'","Կ":"K","Հ":"H","Ձ":"D\'","Ղ":"GH","Ճ":"TW","Մ":"M","Յ":"Y","Ն":"N","Շ":"SH","Չ":"CH","Պ":"P","Ջ":"J","Ռ":"R\'","Ս":"S","Վ":"V","Տ":"T","Ր":"R","Ց":"C","Փ":"P\'","Ք":"Q\'","Օ":"O\'\'","Ֆ":"F","և":"EV","ء":"a","آ":"aa","أ":"a","ؤ":"u","إ":"i","ئ":"e","ا":"a","ب":"b","ة":"h","ت":"t","ث":"th","ج":"j","ح":"h","خ":"kh","د":"d","ذ":"th","ر":"r","ز":"z","س":"s","ش":"sh","ص":"s","ض":"dh","ط":"t","ظ":"z","ع":"a","غ":"gh","ف":"f","ق":"q","ك":"k","ل":"l","م":"m","ن":"n","ه":"h","و":"w","ى":"a","ي":"y","ً":"an","ٌ":"on","ٍ":"en","َ":"a","ُ":"u","ِ":"e","ْ":"","٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9","پ":"p","چ":"ch","ژ":"zh","ک":"k","گ":"g","ی":"y","۰":"0","۱":"1","۲":"2","۳":"3","۴":"4","۵":"5","۶":"6","۷":"7","۸":"8","۹":"9","฿":"baht","":"a","":"b","":"g","":"d","":"e","":"v","":"z","":"t","":"i","":"k","":"l","":"m","":"n","":"o","":"p","":"zh","":"r","":"s","":"t","":"u","":"f","":"k","":"gh","":"q","":"sh","":"ch","":"ts","":"dz","":"ts","":"ch","":"kh","":"j","":"h","":"S","":"s","":"W","":"w","":"W","":"w","":"W","":"w","":"SS","":"A","":"a","":"A","":"a","":"A","":"a","":"A","":"a","":"A","":"a","":"A","":"a","":"A","":"a","":"A","":"a","":"A","":"a","":"A","":"a","":"A","":"a","":"A","":"a","":"E","":"e","":"E","":"e","":"E","":"e","":"E","ế":"e","":"E","":"e","":"E","":"e","":"E","":"e","":"E","":"e","":"I","":"i","":"I","":"i","":"O","":"o","":"O","":"o","":"O","":"o","":"O","":"o","":"O","":"o","":"O","":"o","":"O","":"o","":"O","":"o","":"O","":"o","":"O","":"o","":"O","":"o","":"O","":"o","":"U","":"u","":"U","":"u","":"U","":"u","":"U","":"u","":"U","":"u","":"U","":"u","":"U","":"u","":"Y","":"y","":"Y","":"y","":"Y","":"y","":"Y","":"y","":"-","":"\'","":"\'","":"\\\"","":"\\\"","":"\\\"","":"+","":"*","":"...","":"ecu","":"cruzeiro","":"french franc","":"lira","":"mill","":"naira","":"peseta","":"rupee","":"won","":"new shequel","":"dong","":"euro","":"kip","":"tugrik","":"drachma","":"penny","":"peso","":"guarani","":"austral","":"hryvnia","":"cedi","":"kazakhstani tenge","":"indian rupee","":"turkish lira","":"russian ruble","":"bitcoin","":"sm","":"tm","":"d","":"delta","":"sum","":"infinity","":"love","":"yuan","":"yen","":"rial","":"laa","":"laa","":"lai","":"la"}
def custom_slugify(string):
if not isinstance(string, str):
raise ValueError("slugify: string argument expected")
# Process the string
slug = ''.join(
CHAR_MAP.get(ch, ch).replace('-', ' ')
for ch in unicodedata.normalize('NFKC', string)
)
# Remove not allowed characters and apply strict filtering
slug = re.sub(r'[^\w\s$*_+~.()\'"!\-:@]+', '', slug)
slug = re.sub(r'[^A-Za-z0-9\s]', '', slug).strip()
# Replace spaces with hyphen and convert to lowercase
slug = re.sub(r'\s+', '-', slug).lower()
return slug
@env.macro
def templatesWidget(title: str, slug: str, toLoad: int = 3) -> str:
if no_template:
return '<div class="n8n-templates-widget"><p>Template widget placeholder.</p></div>'
node_for_template = 'email+imap' if title == 'Email Trigger (IMAP)' else title.replace(' ', '+')
try:
response = requests.get(f'https://api.n8n.io/api/templates/search?rows={toLoad}&search={node_for_template}&page=1&sort=views:desc')
response.raise_for_status()
data = response.json()
except requests.RequestException as e:
return f'<span class="n8n-templates-widget-more"><a href="https://n8n.io/integrations/{slug}/" target="_blank">Browse {title} integration templates</a>, or <a href="https://n8n.io/workflows/" target="_blank">search all templates</a></span>'
workflows = data.get("workflows", [])[:toLoad]
if len(workflows) < 3:
return f'<span class="n8n-templates-widget-more"><a href="https://n8n.io/integrations/{slug}/" target="_blank">Browse {title} integration templates</a>, or <a href="https://n8n.io/workflows/" target="_blank">search all templates</a></span>'
def get_workflow_details(workflow: dict) -> Optional[dict]:
try:
return {
"title": workflow["name"],
"user": workflow["user"].get("name", "n8n Community"),
"url": f'https://n8n.io/workflows/{workflow["id"]}-{custom_slugify(workflow["name"])}/',
}
except KeyError:
return None
workflow_details = [get_workflow_details(workflow) for workflow in workflows]
if any(detail is None for detail in workflow_details):
return f'<span class="n8n-templates-widget-more"><a href="https://n8n.io/integrations/{slug}/" target="_blank">Browse all {title} integration templates</a>, or <a href="https://n8n.io/workflows/" target="_blank">search all templates</a></span>'
template_html = ''.join(
f'<div class="n8n-templates-widget-template">'
f'<strong>{detail["title"]}</strong>'
f'<p class="n8n-templates-name">by {detail["user"]}</p>'
f'<a class="n8n-templates-link" href="{detail["url"]}" target="_blank">View template details</a>'
f'</div>'
for detail in workflow_details
)
return (
f'<div class="n8n-templates-widget">'
f'{template_html}'
f'<span class="n8n-templates-widget-more">'
f'<a href="https://n8n.io/integrations/{slug}/" target="_blank">Browse {title} integration templates</a>, or '
f'<a href="https://n8n.io/workflows/" target="_blank">search all templates</a>'
f'</span></div>'
)
@env.macro
def workflowDemo(workflow_json):
if no_template:
return "<div class='n8n-workflow-preview'><p>Workflow preview placeholder.</p></div>"
parsed_workflow_url = urllib.parse.urlparse(workflow_json)
if parsed_workflow_url.scheme in ["https", "http"]:
r = requests.get(url = workflow_json)
wf_data = r.json()
template_url = f'https://n8n.io/workflows/{wf_data["id"]}-{custom_slugify(wf_data["name"])}/'
workflow_json = {
"nodes": wf_data['workflow']['nodes'],
"connections": wf_data['workflow']['connections']
}
workflow_message = "View template details"
elif parsed_workflow_url.scheme == "file":
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
request_path = parsed_workflow_url.path
file_path = f'{BASE_DIR}/docs/_workflows{request_path}'
with open(file_path, 'r') as file:
wf_data = json.load(file)
template_url = f'/_workflows{request_path}'
workflow_json = {
"nodes": wf_data['nodes'],
"connections": wf_data['connections']
}
workflow_message = "View workflow file"
else:
raise ValueError("Workflow JSON must include a URL scheme")
encoded_workflow_json = urllib.parse.quote(json.dumps(workflow_json))
return f"<div class='n8n-workflow-preview'><n8n-demo hidecanvaserrors='true' clicktointeract='true' frame='false' collapseformobile='false' workflow='{encoded_workflow_json}'></n8n-demo><a href='{template_url}' target='_blank'>{workflow_message}</a></div>"