feat(flows):allineamento filtri
This commit is contained in:
23
credential_stubs/mRqzxhSboGscolqI.json
Normal file
23
credential_stubs/mRqzxhSboGscolqI.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL",
|
||||
"type": "postgres",
|
||||
"data": {
|
||||
"host": "",
|
||||
"port": 5432,
|
||||
"database": "",
|
||||
"user": "",
|
||||
"password": "",
|
||||
"ssl": true,
|
||||
"sshTunnel": false,
|
||||
"sslRejectUnauthorized": false,
|
||||
"rejectUnauthorized": false
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/u0JCseXGnDG5hS9F.json
Normal file
16
credential_stubs/u0JCseXGnDG5hS9F.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "u0JCseXGnDG5hS9F",
|
||||
"name": "Home Assistant API",
|
||||
"type": "httpHeaderAuth",
|
||||
"data": {
|
||||
"name": "",
|
||||
"value": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
48
tags.json
48
tags.json
@@ -15,6 +15,18 @@
|
||||
{
|
||||
"id": "EWDBc4QSoIeOq7Q3",
|
||||
"name": "Documents"
|
||||
},
|
||||
{
|
||||
"id": "LYocedE5I1N5tEyg",
|
||||
"name": "Behaviour"
|
||||
},
|
||||
{
|
||||
"id": "Ah5bIXYGdnykmxGo",
|
||||
"name": "Media"
|
||||
},
|
||||
{
|
||||
"id": "Y2kqORepGSATiLQH",
|
||||
"name": "Memory"
|
||||
}
|
||||
],
|
||||
"mappings": [
|
||||
@@ -30,10 +42,30 @@
|
||||
"workflowId": "cOVKGH8x5PD0NZzA",
|
||||
"tagId": "lohEBeVbwLjFCYHK"
|
||||
},
|
||||
{
|
||||
"workflowId": "o3uM1xDLTAKw4D6E",
|
||||
"tagId": "Ah5bIXYGdnykmxGo"
|
||||
},
|
||||
{
|
||||
"workflowId": "o3uM1xDLTAKw4D6E",
|
||||
"tagId": "Y2kqORepGSATiLQH"
|
||||
},
|
||||
{
|
||||
"workflowId": "K07e4PPANXDkmQsr",
|
||||
"tagId": "Y2kqORepGSATiLQH"
|
||||
},
|
||||
{
|
||||
"workflowId": "K07e4PPANXDkmQsr",
|
||||
"tagId": "Ah5bIXYGdnykmxGo"
|
||||
},
|
||||
{
|
||||
"workflowId": "1lIKvVJQIcva30YM",
|
||||
"tagId": "lohEBeVbwLjFCYHK"
|
||||
},
|
||||
{
|
||||
"workflowId": "1lIKvVJQIcva30YM",
|
||||
"tagId": "M3JFTtiZ0sxHInGG"
|
||||
},
|
||||
{
|
||||
"workflowId": "GBPFFq8rmbdFrNn9",
|
||||
"tagId": "M3JFTtiZ0sxHInGG"
|
||||
@@ -41,6 +73,22 @@
|
||||
{
|
||||
"workflowId": "GBPFFq8rmbdFrNn9",
|
||||
"tagId": "EWDBc4QSoIeOq7Q3"
|
||||
},
|
||||
{
|
||||
"workflowId": "qtvB3r0cgejyCxUp",
|
||||
"tagId": "0Cy9mOsSRGn0D1mL"
|
||||
},
|
||||
{
|
||||
"workflowId": "qtvB3r0cgejyCxUp",
|
||||
"tagId": "M3JFTtiZ0sxHInGG"
|
||||
},
|
||||
{
|
||||
"workflowId": "JJ6B3w8i1bL7Q0rr",
|
||||
"tagId": "M3JFTtiZ0sxHInGG"
|
||||
},
|
||||
{
|
||||
"workflowId": "JJ6B3w8i1bL7Q0rr",
|
||||
"tagId": "lohEBeVbwLjFCYHK"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -31,8 +31,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
-1600,
|
||||
48
|
||||
-1568,
|
||||
144
|
||||
],
|
||||
"webhookId": "9959425d-bdbb-4597-ad72-77ec25fcf49c",
|
||||
"credentials": {
|
||||
@@ -54,25 +54,25 @@
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-1376,
|
||||
48
|
||||
144
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"limit": 20,
|
||||
"filters": {
|
||||
"readStatus": "unread",
|
||||
"q": "-label:Processed"
|
||||
},
|
||||
"limit": 20
|
||||
"q": "-label:Processed newer_than:1d",
|
||||
"readStatus": "unread"
|
||||
}
|
||||
},
|
||||
"id": "b73f8915-1173-43a8-9f78-0c49fa471b7d",
|
||||
"name": "Gmail - Fetch ultime 3h",
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
608,
|
||||
160
|
||||
-1008,
|
||||
304
|
||||
],
|
||||
"webhookId": "efc020f5-87c1-4009-8bc2-82ad371c0dde",
|
||||
"credentials": {
|
||||
@@ -92,8 +92,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
-944,
|
||||
48
|
||||
-736,
|
||||
-32
|
||||
],
|
||||
"webhookId": "078abbbb-a75c-4507-8c0a-23196b1fef45",
|
||||
"credentials": {
|
||||
@@ -114,13 +114,12 @@
|
||||
"type": "n8n-nodes-base.aggregate",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-720,
|
||||
48
|
||||
-512,
|
||||
-32
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const item = $input.first();\nconst emailsRaw = item.json.emails || [];\nconst emails = emailsRaw.map(e => e.json || e);\n\nif (emails.length === 0) {\n return [{ json: { prompt: 'NESSUNA_EMAIL', emailCount: 0, emailMeta: [] } }];\n}\n\nconst emailMeta = emails.map(e => ({ id: e.id, threadId: e.threadId || e.id }));\n\nconst threads = {};\nemails.forEach(e => {\n const tid = e.threadId || e.id;\n if (!threads[tid]) threads[tid] = [];\n threads[tid].push(e.id);\n});\n\nconst emailList = emails.map((e, i) => {\n const from = e.From || e.from || 'Mittente sconosciuto';\n const subject = e.Subject || e.subject || '(nessun oggetto)';\n const date = e.Date || e.date || '';\n const body = (e.text || e.snippet || '').substring(0, 600);\n // Gmail node restituisce payload.mimeType='multipart/mixed' quando ci sono allegati\n const hasMixedPayload = e.payload && e.payload.mimeType === 'multipart/mixed';\n const hasAtt = (hasMixedPayload || (e.attachments && e.attachments.length > 0)) ? 'Sì (PDF probabile)' : 'No';\n const threadCount = (threads[e.threadId || e.id] || []).length;\n const threadNote = threadCount > 1 ? ` [THREAD: ${threadCount} msg]` : '';\n return `--- EMAIL ${i + 1}${threadNote} ---\nID: ${e.id}\nThreadID: ${e.threadId || e.id}\nDa: ${from}\nOggetto: ${subject}\nData: ${date}\nAllegati: ${hasAtt}\n${body}`;\n}).join('\\n\\n');\n\nconst today = new Date().toLocaleDateString('it-IT', {\n weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'\n});\n\nconst prompt = `Sei l'assistente personale di Martin. Analizza ${emails.length} email non lette.\n\nLABEL GMAIL (nomi ESATTI, rispetta maiuscole e &):\nLavoro/Comunicazioni, Lavoro/Cedolino, Lavoro/Contratto\nCondominio/Comunicazioni, Condominio/Spese\nInternet/Bollette, Internet/Comunicazioni\nLuce&Gas/Bollette, Luce&Gas/Comunicazioni\nMarketing, Prenotazioni\nRicevute/Trasporti, Ricevute/Acquisti, Ricevute/Abbonamenti\n\nREGOLE CLASSIFICAZIONE:\n- action \"trash\" solo per Marketing/newsletter → verranno eliminate automaticamente\n- action \"keep\" per tutto il resto → etichettate e incluse nel report\n- EMAIL con stesso ThreadID = stessa conversazione: riassumile INSIEME nel report\n- Non usare label non presenti nella lista sopra\n\nPAPERLESS ACTION (campo aggiuntivo, guarda il campo Allegati):\n- paperless_action \"pdf_allegato\" → email con allegato PDF che potrebbe valere la pena archiviare (Allegati: Sì) — bollette, ricevute, contratti, cedolini, comunicazioni ufficiali, etc.\n- paperless_action null → email senza allegati PDF rilevanti, o allegati irrilevanti (immagini, firma, etc.)\n\nREPORT (daily_report) - OBBLIGATORIO essere narrativo:\n- Scrivi come un assistente personale che racconta a Martin cosa c'è nella sua inbox\n- NON fare semplice elenco mittente/oggetto: racconta cosa è successo\n- Raggruppa thread correlati in un unico punto\n- Estrai dettagli concreti: importi €, date, numeri ordine, azioni richieste\n- Segnala esplicitamente se Martin deve fare qualcosa\n- Ordine: urgente/da rispondere, poi info utili, poi ricevute, poi eliminati\n- Usa *grassetto* e emoji Telegram. Max 3000 caratteri.\n\nRispondi SOLO JSON valido:\n{\n \"emails\": [\n {\"id\": \"ID_ESATTO\", \"action\": \"trash\", \"labels\": [\"Marketing\"], \"summary\": \"\", \"paperless_action\": null},\n {\"id\": \"ID_ESATTO\", \"action\": \"keep\", \"labels\": [\"Luce&Gas/Bollette\"], \"summary\": \"E.ON €87,50 scad. 28/03\", \"paperless_action\": \"pdf_allegato\"},\n {\"id\": \"ID_ESATTO\", \"action\": \"keep\", \"labels\": [\"Internet/Bollette\"], \"summary\": \"Avviso bolletta Fastweb disponibile\", \"paperless_action\": null}\n ],\n \"daily_report\": \"[REPORT NARRATIVO]\"\n}\n\n${emailList}`;\n\nreturn [{ json: { prompt, emailCount: emails.length, emailMeta } }];"
|
||||
},
|
||||
"id": "f6b052dd-48da-41dd-8c68-f58c8b9f8c1d",
|
||||
@@ -128,13 +127,12 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-496,
|
||||
48
|
||||
-288,
|
||||
-32
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const raw = $input.first().json;\n// Formato OpenAI: choices[0].message.content\nconst rawText = (raw.choices && raw.choices[0] && raw.choices[0].message)\n ? raw.choices[0].message.content\n : (raw.text || raw.output || '');\n\nconst cleaned = rawText\n .replace(/<thinking>[\\s\\S]*?<\\/thinking>/gi, '')\n .replace(/```json\\n?/g, '')\n .replace(/```\\n?/g, '')\n .trim();\n\nlet parsed;\ntry {\n parsed = JSON.parse(cleaned);\n} catch (e) {\n const match = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (match) {\n try { parsed = JSON.parse(match[0]); }\n catch (e2) { throw new Error('Parse error: ' + rawText.substring(0, 300)); }\n } else {\n throw new Error('No JSON found: ' + rawText.substring(0, 300));\n }\n}\n\nif (!parsed.emails || !Array.isArray(parsed.emails)) {\n throw new Error('Missing emails array: ' + JSON.stringify(parsed).substring(0, 200));\n}\n\nconst emailMeta = $('Costruisci Prompt').first().json.emailMeta || [];\nconst metaMap = {};\nemailMeta.forEach(m => { metaMap[m.id] = m.threadId; });\n\nparsed.emails = parsed.emails.map(e => ({\n ...e,\n threadId: metaMap[e.id] || e.id\n}));\n\nreturn [{ json: parsed }];"
|
||||
},
|
||||
"id": "cac1efac-2d3e-482c-a529-22bdd2136575",
|
||||
@@ -142,8 +140,8 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-64,
|
||||
48
|
||||
432,
|
||||
-32
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -159,8 +157,8 @@
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
160,
|
||||
-96
|
||||
704,
|
||||
-192
|
||||
],
|
||||
"webhookId": "d42335fd-4748-4406-b0dd-d8d7f4d0b027",
|
||||
"credentials": {
|
||||
@@ -180,8 +178,8 @@
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
160,
|
||||
48
|
||||
704,
|
||||
96
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -200,8 +198,8 @@
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
384,
|
||||
48
|
||||
944,
|
||||
96
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -214,8 +212,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
608,
|
||||
-96
|
||||
1200,
|
||||
-80
|
||||
],
|
||||
"webhookId": "abc3390b-9a83-4a40-bf36-d52a921a25b0",
|
||||
"credentials": {
|
||||
@@ -236,8 +234,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
832,
|
||||
-96
|
||||
1488,
|
||||
-80
|
||||
],
|
||||
"webhookId": "e0919d48-248a-4191-b8ca-6f9ff619bf16",
|
||||
"credentials": {
|
||||
@@ -249,7 +247,6 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const items = $input.all();\nif (items.length === 0) return [];\n\n// Costruisce mappa nome → ID dalle label Gmail\nconst labelsData = $('Gmail - Leggi tutte le label').all();\nconst labelMap = {};\nlabelsData.forEach(item => {\n const name = item.json.name;\n const id = item.json.id;\n if (name && id) labelMap[name] = id;\n});\n\n// Processa ogni email nella branch \"keep\"\nconst result = [];\nfor (const item of items) {\n const email = item.json;\n const requestedLabels = Array.isArray(email.labels) ? email.labels : [];\n const resolvedLabelIds = requestedLabels\n .map(name => labelMap[name])\n .filter(id => !!id);\n\n // Skip se nessuna label trovata (evita errore Gmail 'No label add or removes')\n if (resolvedLabelIds.length === 0) continue;\n\n result.push({\n json: {\n id: String(email.id || ''),\n threadId: String(email.threadId || email.id || ''),\n action: String(email.action || 'keep'),\n labels: requestedLabels,\n summary: String(email.summary || ''),\n resolvedLabelIds: resolvedLabelIds\n }\n });\n}\n\nreturn result;"
|
||||
},
|
||||
"id": "99e0b420-3d9a-4ece-80ed-27dab248b018",
|
||||
@@ -257,8 +254,8 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
608,
|
||||
192
|
||||
1504,
|
||||
368
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -272,8 +269,8 @@
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
832,
|
||||
192
|
||||
1696,
|
||||
368
|
||||
],
|
||||
"webhookId": "debadb11-9b5c-44a6-9ad0-7f1a6598b14a",
|
||||
"credentials": {
|
||||
@@ -284,36 +281,24 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"path": "gmail-digest-test",
|
||||
"options": {}
|
||||
},
|
||||
"id": "test-webhook-trigger",
|
||||
"name": "🧪 Test Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-1824,
|
||||
200
|
||||
-112
|
||||
],
|
||||
"parameters": {
|
||||
"path": "gmail-digest-test",
|
||||
"responseMode": "onReceived",
|
||||
"options": {}
|
||||
},
|
||||
"webhookId": "gmail-digest-test-001"
|
||||
},
|
||||
{
|
||||
"id": "copilot-gpt41-node",
|
||||
"name": "GPT-4.1 - Classifica Email",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-336,
|
||||
48
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.githubcopilot.com/chat/completions",
|
||||
"sendBody": true,
|
||||
"options": {},
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
@@ -335,29 +320,36 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"authentication": "none",
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "application/json",
|
||||
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\": $('Costruisci Prompt').first().json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":8192}) }}"
|
||||
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\": $('Costruisci Prompt').first().json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":8192}) }}",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {}
|
||||
},
|
||||
{
|
||||
"id": "copilot-token-refresh",
|
||||
"name": "Ottieni Token Copilot",
|
||||
"id": "copilot-gpt41-node",
|
||||
"name": "GPT-4.1 - Classifica Email",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-496,
|
||||
48
|
||||
],
|
||||
192,
|
||||
-32
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://api.github.com/copilot_internal/v2/token",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"id": "copilot-token-refresh",
|
||||
"name": "Ottieni Token Copilot",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-64,
|
||||
-32
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "vBwUxlzKrX3oDHyN",
|
||||
@@ -366,14 +358,6 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "if-paperless",
|
||||
"name": "Ha azione Paperless?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
384,
|
||||
250
|
||||
],
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
@@ -393,18 +377,19 @@
|
||||
"rightValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "if-bolletta-or-fastweb",
|
||||
"name": "Ha PDF allegato?",
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-paperless",
|
||||
"name": "Ha azione Paperless?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
608,
|
||||
350
|
||||
],
|
||||
1200,
|
||||
272
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {},
|
||||
@@ -420,32 +405,32 @@
|
||||
"rightValue": "pdf_allegato"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-bolletta-or-fastweb",
|
||||
"name": "Ha PDF allegato?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1488,
|
||||
112
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Leggi il messaggio Gmail completo per trovare gli allegati PDF\n// Il nodo Gmail - Leggi contenuto ha già il payload completo\nconst emails = $input.all();\nconst results = [];\nfor (const emailItem of emails) {\n const email = emailItem.json;\n if (email.paperless_action !== 'pdf_allegato') continue;\n // Trova allegati PDF in payload.parts (ricorsivo)\n function findPDFs(parts, found) {\n if (!parts) return;\n for (const p of parts) {\n if (p.parts) findPDFs(p.parts, found);\n if (p.body && p.body.attachmentId) {\n const isPDF = (p.mimeType||'').includes('pdf') || (p.filename||'').toLowerCase().endsWith('.pdf');\n if (isPDF) found.push({ attachment_id: p.body.attachmentId, filename: p.filename || 'documento.pdf' });\n }\n }\n }\n const pdfs = [];\n findPDFs((email.payload && email.payload.parts) || [], pdfs);\n for (const pdf of pdfs) {\n results.push({ json: {\n email_id: email.id,\n attachment_id: pdf.attachment_id,\n filename: pdf.filename,\n hint: (email.Subject || email.subject || '') + ' da ' + (email.From || email.from || ''),\n from: email.From || email.from || '',\n }});\n }\n}\nreturn results;"
|
||||
},
|
||||
"id": "http-trigger-bolletta",
|
||||
"name": "Estrai Allegati PDF",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
832,
|
||||
260
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "// Leggi il messaggio Gmail completo per trovare gli allegati PDF\n// Il nodo Gmail - Leggi contenuto ha già il payload completo\nconst emails = $input.all();\nconst results = [];\nfor (const emailItem of emails) {\n const email = emailItem.json;\n if (email.paperless_action !== 'pdf_allegato') continue;\n // Trova allegati PDF in payload.parts (ricorsivo)\n function findPDFs(parts, found) {\n if (!parts) return;\n for (const p of parts) {\n if (p.parts) findPDFs(p.parts, found);\n if (p.body && p.body.attachmentId) {\n const isPDF = (p.mimeType||'').includes('pdf') || (p.filename||'').toLowerCase().endsWith('.pdf');\n if (isPDF) found.push({ attachment_id: p.body.attachmentId, filename: p.filename || 'documento.pdf' });\n }\n }\n }\n const pdfs = [];\n findPDFs((email.payload && email.payload.parts) || [], pdfs);\n for (const pdf of pdfs) {\n results.push({ json: {\n email_id: email.id,\n attachment_id: pdf.attachment_id,\n filename: pdf.filename,\n hint: (email.Subject || email.subject || '') + ' da ' + (email.From || email.from || ''),\n from: email.From || email.from || '',\n }});\n }\n}\nreturn results;"
|
||||
}
|
||||
1824,
|
||||
-16
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "if-is-test",
|
||||
"name": "È un test?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
384,
|
||||
260
|
||||
],
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {},
|
||||
@@ -460,26 +445,36 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-is-test",
|
||||
"name": "È un test?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-1184,
|
||||
144
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"limit": 20,
|
||||
"filters": {
|
||||
"q": "-label:Processed newer_than:1d",
|
||||
"readStatus": "unread"
|
||||
}
|
||||
},
|
||||
"id": "gmail-fetch-all-unread",
|
||||
"name": "Gmail - Fetch tutte non lette",
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
608,
|
||||
360
|
||||
-992,
|
||||
-32
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"filters": {
|
||||
"readStatus": "unread",
|
||||
"q": "-label:Processed"
|
||||
},
|
||||
"limit": 20
|
||||
},
|
||||
"webhookId": "94c9342a-4f54-42f1-9f8f-4a165468ba33",
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "qvOikS6IF0H5khr8",
|
||||
@@ -488,14 +483,6 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_callcore",
|
||||
"name": "Chiama Core Upload",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2200,
|
||||
200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://orchestrator.mt-home.uk/webhook/paperless-upload",
|
||||
@@ -503,6 +490,79 @@
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ email_id: $json.email_id, attachment_id: $json.attachment_id, filename: $json.filename, hint: $json.hint, from: $json.from }) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "n_callcore",
|
||||
"name": "Chiama Core Upload",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2160,
|
||||
128
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "n_estrai_fatti",
|
||||
"name": "🧠 Estrai Fatti",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1900,
|
||||
400
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "const parsed = $input.first().json;\nconst copilotToken = $('Ottieni Token Copilot').first().json.token;\n\n// Only non-trash emails with a summary\nconst keepEmails = (parsed.emails || []).filter(e => e.action !== 'trash' && e.summary?.trim());\nif (keepEmails.length === 0) return [{ json: { _skip: true } }];\n\nconst emailList = keepEmails.map((e, i) =>\n `${i+1}. [labels: ${(e.labels||[]).join(',')}] [threadId: ${e.threadId}]\\nRiassunto: ${e.summary}`\n).join('\\n\\n');\n\nconst gptRes = await this.helpers.httpRequest({\n method: 'POST',\n url: 'https://api.githubcopilot.com/chat/completions',\n headers: {\n 'Authorization': `Bearer ${copilotToken}`,\n 'editor-version': 'vscode/1.95.3',\n 'Copilot-Integration-Id': 'vscode-chat',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({\n model: 'gpt-4.1',\n messages: [\n { role: 'system', content: 'Sei un assistente che estrae fatti strutturati da email per il sistema di memoria di Pompeo, l\\'AI personale di Martin Tahiraj. Rispondi SEMPRE con JSON valido.' },\n { role: 'user', content: `Estrai un fatto strutturato per ogni email che Martin ha ricevuto oggi.\n\nEMAIL (indice. [label] [threadId]):\n${emailList}\n\nRegole TTL:\n- Prenotazioni, viaggi, eventi imminenti → ttl_days: 14\n- Bollette → ttl_days: 45\n- Lavoro, condominio, documenti ufficiali → ttl_days: 90\n- Abbonamenti, acquisti → ttl_days: 30\n- Default → ttl_days: 30\n\nRispondi SOLO JSON:\n{\n \"facts\": [\n {\n \"index\": 1,\n \"thread_id\": \"threadId_esatto\",\n \"category\": \"finance|work|home|personal|travel|subscription|health\",\n \"subject\": \"fatto conciso max 120 caratteri\",\n \"detail\": {\"from\": \"mittente\", \"labels\": [\"label1\"]},\n \"action_required\": false,\n \"action_text\": \"\",\n \"pompeo_note\": \"inner monologue breve: perché questo fatto è rilevante per capire Martin o la sua vita\",\n \"entity_refs\": {\"people\": [], \"places\": [], \"products\": [], \"amounts\": []},\n \"ttl_days\": 30\n }\n ]\n}` }\n ],\n temperature: 0.2,\n response_format: { type: 'json_object' }\n })\n});\n\nconst content = gptRes.choices?.[0]?.message?.content || '{}';\nconst { facts = [] } = JSON.parse(content);\n\nconst now = new Date();\nconst results = facts.map(f => {\n const email = keepEmails[f.index - 1] || keepEmails.find(e => e.threadId === f.thread_id);\n if (!f.thread_id && !email?.threadId) return null;\n const expiresAt = (f.ttl_days > 0)\n ? new Date(now.getTime() + f.ttl_days * 86400000).toISOString()\n : null;\n const detail = JSON.stringify({ ...(f.detail || {}), labels: email?.labels || [] });\n return { json: {\n thread_id: f.thread_id || email?.threadId,\n category: f.category || 'personal',\n subject: (f.subject || email?.summary || '').substring(0, 200).replace(/'/g, \"''\"),\n detail,\n action_required: f.action_required ? true : false,\n action_text: (f.action_text || '').replace(/'/g, \"''\"),\n pompeo_note: (f.pompeo_note || '').replace(/'/g, \"''\"),\n entity_refs: JSON.stringify(f.entity_refs || {}),\n expires_at: expiresAt\n }};\n}).filter(Boolean);\n\nreturn results.length > 0 ? results : [{ json: { _skip: true } }];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_if_skip",
|
||||
"name": "🔀 Ha Fatti?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2120,
|
||||
400
|
||||
],
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "cond_skip",
|
||||
"leftValue": "={{ $json._skip }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "notEquals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_upsert_memoria",
|
||||
"name": "💾 Upsert Memoria",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.5,
|
||||
"position": [
|
||||
2340,
|
||||
400
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO memory_facts\n (user_id, source, source_ref, category, subject, detail, action_required, action_text, pompeo_note, entity_refs, expires_at)\nVALUES (\n 'martin',\n 'email',\n '{{ $json.thread_id }}',\n '{{ $json.category }}',\n '{{ $json.subject }}',\n '{{ $json.detail }}',\n {{ $json.action_required }},\n '{{ $json.action_text }}',\n '{{ $json.pompeo_note }}',\n '{{ $json.entity_refs }}'::jsonb,\n {{ $json.expires_at ? \"'\" + $json.expires_at + \"'::timestamptz\" : \"NULL\" }}\n)\nON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL DO UPDATE SET\n subject = EXCLUDED.subject,\n detail = EXCLUDED.detail,\n action_required = EXCLUDED.action_required,\n action_text = EXCLUDED.action_text,\n pompeo_note = EXCLUDED.pompeo_note,\n entity_refs = EXCLUDED.entity_refs,\n expires_at = EXCLUDED.expires_at",
|
||||
"options": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -647,6 +707,11 @@
|
||||
"node": "Dividi Email",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "🧠 Estrai Fatti",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -759,6 +824,29 @@
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🧠 Estrai Fatti": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔀 Ha Fatti?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔀 Ha Fatti?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "💾 Upsert Memoria",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -767,7 +855,7 @@
|
||||
"callerPolicy": "workflowsFromSameOwner"
|
||||
},
|
||||
"triggerCount": 2,
|
||||
"versionId": "55dea25a-a72c-4c4f-8126-cc6dae55e15a",
|
||||
"versionId": "ba68ff52-7646-45ef-b999-af915df23a00",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
|
||||
464
workflows/4ZIEGck9n4l5qaDt.json
Normal file
464
workflows/4ZIEGck9n4l5qaDt.json
Normal file
@@ -0,0 +1,464 @@
|
||||
{
|
||||
"id": "4ZIEGck9n4l5qaDt",
|
||||
"name": "📅 Pompeo — Calendar Agent [Schedule]",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "5162caf6661f4aa8",
|
||||
"name": "⏰ Schedule",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
0,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "cronExpression",
|
||||
"expression": "*/30 * * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "63a2b1a9ba7a4cf2",
|
||||
"name": "📅 Imposta Range",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
260,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "const now = new Date();\nconst start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0).toISOString();\nconst end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 7, 23, 59, 59).toISOString();\nreturn [{json: {start, end}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "89ee9f58cd2f4d4d",
|
||||
"name": "🔑 Ottieni Token Copilot",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"position": [
|
||||
520,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"url": "https://api.github.com/copilot_internal/v2/token",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "vBwUxlzKrX3oDHyN",
|
||||
"name": "GitHub Copilot OAuth Token"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ffb3d86cf2a54659",
|
||||
"name": "📋 Prepara Calendari",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
780,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const start = $('📅 Imposta Range').first().json.start;\nconst end = $('📅 Imposta Range').first().json.end;\nconst HA = 'http://10.30.20.100:8123';\n\n// Note: calendar.films and calendar.serie_tv removed — handled by Media Agent (direct Radarr/Sonarr API)\nconst calendars = [\n {entity_id:'calendar.calendar', name:'Lavoro', category:'work', user_id:'martin'},\n {entity_id:'calendar.famiglia', name:'Famiglia', category:'personal',user_id:'martin'},\n {entity_id:'calendar.spazzatura', name:'Spazzatura', category:'chores', user_id:'martin'},\n {entity_id:'calendar.pulizie', name:'Pulizie', category:'chores', user_id:'martin'},\n {entity_id:'calendar.formula_1', name:'Formula 1', category:'leisure', user_id:'martin'},\n {entity_id:'calendar.lm_wec_fia_world_endurance_championship', name:'WEC', category:'leisure', user_id:'martin'},\n {entity_id:'calendar.inter_calendar', name:'Inter', category:'leisure', user_id:'martin'},\n {entity_id:'calendar.birthdays', name:'Compleanni', category:'social', user_id:'martin'},\n {entity_id:'calendar.varie', name:'Varie', category:'misc', user_id:'martin'},\n {entity_id:'calendar.festivita_in_italia', name:'Festività Italia',category:'holiday', user_id:'shared'}\n];\n\nreturn calendars.map(cal => ({json: {...cal, start, end, ha_base_url: HA}}));"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7985f2d26aa64e69",
|
||||
"name": "📡 HA - Scarica Calendario",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"position": [
|
||||
1040,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"url": "={{ $json.ha_base_url + '/api/calendars/' + $json.entity_id + '?start=' + $json.start + '&end=' + $json.end }}",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "u0JCseXGnDG5hS9F",
|
||||
"name": "Home Assistant API"
|
||||
}
|
||||
},
|
||||
"onError": "continueRegularOutput"
|
||||
},
|
||||
{
|
||||
"id": "9ed15cad33234465",
|
||||
"name": "🏷️ Estrai ed Etichetta",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1300,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const results = [];\nconst allCals = $('📋 Prepara Calendari').all();\n\nfor (let i = 0; i < $input.all().length; i++) {\n const httpItem = $input.all()[i];\n const pairedIdx = (typeof httpItem.pairedItem?.item === 'number') ? httpItem.pairedItem.item : i;\n const cal = allCals[pairedIdx]?.json ?? {};\n\n const events = Array.isArray(httpItem.json) ? httpItem.json : [];\n\n for (const ev of events) {\n const startVal = ev.start?.dateTime || ev.start?.date || null;\n const endVal = ev.end?.dateTime || ev.end?.date || null;\n const uid = ev.uid ||\n `${cal.entity_id}_${(ev.summary||'').replace(/\\s+/g,'_')}_${startVal}`;\n\n results.push({json: {\n uid,\n summary: ev.summary || '',\n description: ev.description || null,\n location: ev.location || null,\n start_dt: startVal,\n end_dt: endVal,\n all_day: !ev.start?.dateTime,\n calendar_name: cal.name || 'Unknown',\n calendar_entity: cal.entity_id || '',\n calendar_category:cal.category || 'misc',\n user_id: cal.user_id || 'martin'\n }});\n }\n}\n\nreturn results.length > 0 ? results : [{json:{skip:true}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "9a2b47a3ce774ca1",
|
||||
"name": "📝 Prepara Prompt",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1560,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const allItems = $input.all().map(i => i.json).filter(e => !e.skip);\nif (!allItems.length) return [{json:{skip:true, events:[], prompt:null, event_count:0}}];\n\n// Dedup by uid\nconst seen = new Set();\nconst events = allItems.filter(e => {\n if (!e.uid || seen.has(e.uid)) return false;\n seen.add(e.uid); return true;\n});\n\nconst fmt = dt => dt ? dt.substring(0,16).replace('T',' ') : '';\n\nconst eventList = events.map((e, idx) =>\n `${idx+1}. [${e.calendar_name}] ${e.summary}` +\n (e.all_day ? ' | Tutto il giorno' : ` | ${fmt(e.start_dt)} → ${fmt(e.end_dt)}`) +\n (e.location ? ` | 📍 ${e.location}` : '') +\n (e.description ? ` | ${e.description.substring(0,80)}` : '') +\n ` | uid: ${e.uid}`\n).join('\\n');\n\nconst prompt =\n`Sei Pompeo, l'assistente AI di Martin Tahiraj. Analizza questi eventi calendario (prossimi 7 giorni) e classificali.\n\nEVENTI:\n${eventList}\n\nRispondi SOLO con JSON valido nel formato:\n{\n \"events\": [\n {\n \"uid\": \"<uid originale>\",\n \"category\": \"work|personal|chores|leisure|social|holiday|misc\",\n \"action_required\": false,\n \"action_text\": null,\n \"do_not_disturb\": false,\n \"priority\": \"high|medium|low\",\n \"behavioral_context\": \"es: work_meeting|sport_event|home_chores|birthday\",\n \"home_presence_expected\": true,\n \"pompeo_note\": \"Nota breve in italiano per il briefing\"\n }\n ]\n}`;\n\nreturn [{json:{events, prompt, event_count: events.length}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5589a6ce6f1a4f81",
|
||||
"name": "🤖 GPT-4.1 - Analizza Calendari",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4,
|
||||
"position": [
|
||||
1820,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.githubcopilot.com/chat/completions",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "={{ 'Bearer ' + $('🔑 Ottieni Token Copilot').first().json.token }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "Copilot-Integration-Id",
|
||||
"value": "vscode-chat"
|
||||
},
|
||||
{
|
||||
"name": "Editor-Version",
|
||||
"value": "vscode/1.85.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "application/json",
|
||||
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Sei Pompeo, l'assistente AI di Martin. Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\":$json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":2048}) }}",
|
||||
"options": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "89505d194e014900",
|
||||
"name": "📋 Parse Risposta GPT",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2080,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "const raw = $input.first().json;\nconst content = (raw.choices || [])[0]?.message?.content || '{}';\nlet parsed;\ntry { parsed = JSON.parse(content); }\ncatch(e) { throw new Error('GPT non JSON: ' + content.substring(0,300)); }\n\nconst gptEvents = parsed.events || [];\nconst originalEvents = $('📝 Prepara Prompt').first().json.events || [];\n\nif (!originalEvents.length) return [{json:{skip:true}}];\n\nreturn originalEvents.map((orig, i) => {\n const enriched = gptEvents.find(e => e.uid === orig.uid) || gptEvents[i] || {};\n return {json:{\n ...orig,\n category: enriched.category || orig.calendar_category || 'misc',\n action_required: enriched.action_required || false,\n action_text: enriched.action_text || null,\n do_not_disturb: enriched.do_not_disturb || false,\n priority: enriched.priority || 'low',\n behavioral_context: enriched.behavioral_context || null,\n home_presence_expected: enriched.home_presence_expected ?? null,\n pompeo_note: enriched.pompeo_note || orig.summary\n }};\n});"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5bb69072497546d3",
|
||||
"name": "💾 Postgres - Salva Evento",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2860,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO memory_facts (user_id, source, source_ref, category, subject, detail, action_required, action_text, expires_at)\nVALUES (\n '{{ $json.user_id }}',\n 'calendar',\n '{{ $json.uid }}',\n '{{ $json.category }}',\n '{{ $json.summary }}',\n '{{ JSON.stringify({start:$json.start_dt,end:$json.end_dt,all_day:$json.all_day,location:$json.location,calendar:$json.calendar_name}) }}'::jsonb,\n {{ $json.action_required ? 'true' : 'false' }},\n '{{ $json.action_text || \"\" }}',\n '{{ $json.expires_at }}'::timestamptz\n)\nON CONFLICT ON CONSTRAINT memory_facts_dedup_idx DO UPDATE SET\n subject = EXCLUDED.subject,\n detail = EXCLUDED.detail,\n action_required = EXCLUDED.action_required,\n action_text = EXCLUDED.action_text,\n expires_at = EXCLUDED.expires_at",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "53f04d2895cf40b2",
|
||||
"name": "📦 Aggrega Risultati",
|
||||
"type": "n8n-nodes-base.aggregate",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
3120,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"aggregate": "aggregateAllItemData",
|
||||
"destinationFieldName": "events"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5573e6e8d378427e",
|
||||
"name": "✍️ Prepara Messaggio",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
3380,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "const events = ($json.events || []).map(i => i.json || i).filter(e => !e.skip);\nif (!events.length) return [{json:{message:'📅 *Briefing Calendario*\\n\\nNessun evento nei prossimi 7 giorni.'}}];\n\nconst emojis = {work:'🗂',personal:'👨👩👧',chores:'🧹',leisure:'🏎',social:'🎂',holiday:'🎉',misc:'📌'};\nconst byCal = {};\nfor (const e of events) {\n const k = e.calendar_name || 'Varie';\n if (!byCal[k]) byCal[k] = {cat: e.calendar_category, evs:[]};\n byCal[k].evs.push(e);\n}\n\nconst today = new Date().toLocaleDateString('it-IT',{weekday:'long',day:'numeric',month:'long'});\nlet msg = `📅 *Briefing Calendario — Prossimi 7 giorni*\\n_(${today})_\\n\\n`;\n\nfor (const [cal, {cat, evs}] of Object.entries(byCal)) {\n msg += `${emojis[cat]||'📌'} *${cal}*\\n`;\n for (const e of evs) {\n const dt = e.all_day ? '' : (e.start_dt ? ' `' + e.start_dt.substring(11,16) + '`' : '');\n const flag = e.action_required ? ' ⚡' : '';\n msg += `• ${e.summary}${dt}${flag}\\n`;\n }\n msg += '\\n';\n}\nmsg += `_${events.length} eventi — generato da Pompeo_`;\nreturn [{json:{message:msg}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "420db8250bf9492f",
|
||||
"name": "📱 Telegram - Briefing Giornaliero",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
3640,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
"operation": "sendMessage",
|
||||
"chatId": "-4814221197",
|
||||
"text": "={{ $json.message }}",
|
||||
"additionalFields": {
|
||||
"parse_mode": "Markdown"
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "uTXHLqcCJxbOvqN3",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "7fd81a1892184b61",
|
||||
"name": "🗑️ Cleanup Cancellati",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2340,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "DELETE FROM memory_facts\nWHERE source = 'calendar'\n AND source_ref IS NOT NULL\n AND (detail->>'start')::timestamptz >= '{{ $('📅 Imposta Range').first().json.start }}'::timestamptz\n AND (detail->>'start')::timestamptz < '{{ $('📅 Imposta Range').first().json.end }}'::timestamptz\n AND source_ref NOT IN ({{ $('📋 Parse Risposta GPT').all().filter(i => i.json.uid).map(i => \"'\" + String(i.json.uid).replace(/'/g,\"''\") + \"'\").join(',') || \"'__no_uid__'\" }})",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "f8922396b255475a",
|
||||
"name": "🔀 Riemetti Eventi",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2600,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "return $('📋 Parse Risposta GPT').all().filter(i => !i.json.skip);"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"⏰ Schedule": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📅 Imposta Range",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📅 Imposta Range": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔑 Ottieni Token Copilot",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔑 Ottieni Token Copilot": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📋 Prepara Calendari",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📋 Prepara Calendari": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📡 HA - Scarica Calendario",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📡 HA - Scarica Calendario": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🏷️ Estrai ed Etichetta",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🏷️ Estrai ed Etichetta": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📝 Prepara Prompt",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📝 Prepara Prompt": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🤖 GPT-4.1 - Analizza Calendari",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🤖 GPT-4.1 - Analizza Calendari": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📋 Parse Risposta GPT",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📋 Parse Risposta GPT": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🗑️ Cleanup Cancellati",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"💾 Postgres - Salva Evento": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📦 Aggrega Risultati",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📦 Aggrega Risultati": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "✍️ Prepara Messaggio",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"✍️ Prepara Messaggio": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📱 Telegram - Briefing Giornaliero",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🗑️ Cleanup Cancellati": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔀 Riemetti Eventi",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔀 Riemetti Eventi": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "💾 Postgres - Salva Evento",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "5b03aaed-af76-4d6a-bff0-5a2949c24dfe",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": false
|
||||
}
|
||||
260
workflows/AyrKWvboPldzZPsM.json
Normal file
260
workflows/AyrKWvboPldzZPsM.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"id": "AyrKWvboPldzZPsM",
|
||||
"name": "🎬 Pompeo — Jellyfin Playback [Webhook]",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "wh_jellyfin",
|
||||
"name": "Webhook Jellyfin",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"webhookId": "jellyfin-playback",
|
||||
"position": [
|
||||
0,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "jellyfin-playback",
|
||||
"responseMode": "onReceived",
|
||||
"options": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "code_norm",
|
||||
"name": "🔀 Normalizza Evento",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
280,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "// Parse body: il plugin Jellyfin invia il payload come stringa JSON dentro il body\nconst rawBody = $json.body || $json;\nconst body = typeof rawBody === 'string' ? JSON.parse(rawBody) : rawBody;\n\nconst notifType = body.NotificationType || body.Event || '';\nconst isStart = /PlaybackStart|Play$/i.test(notifType);\nconst isStop = /PlaybackStop|PlaybackPause|Pause|Stop|Scrobble/i.test(notifType);\nif (!isStart && !isStop) return [];\n\n// Whitelist utenti\nconst ALLOWED = ['42369255a7c64917a28fc26d4c7f8265', 'martin'];\nconst jellyUser = String(body.UserId || body.User || '');\nif (jellyUser && !ALLOWED.some(u => jellyUser.toLowerCase().includes(u.toLowerCase()))) return [];\n\n// Campi Jellyfin reali (plugin v1)\nconst series = body.SeriesName || null;\nconst season = body.SeasonNumber || null;\nconst ep = body.EpisodeNumber || null;\nconst item = body.Name || body.ItemName || 'Sconosciuto';\nconst device = body.DeviceName || body.Client || 'Unknown';\nconst sid = body.SessionId || body.DeviceId || null;\nconst itemType = (body.ItemType || '').toLowerCase();\n\nconst subject = series\n ? `${series} S${String(season||0).padStart(2,'0')}E${String(ep||0).padStart(2,'0')} - ${item}`\n : item;\n\nreturn [{json:{\n is_start: isStart,\n is_stop: isStop,\n subject,\n item_name: item,\n series_name: series,\n item_type: itemType,\n device,\n session_id: sid,\n user_id: 'martin',\n jellyfin_user_id: jellyUser,\n ts: new Date().toISOString()\n}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sw_startstop",
|
||||
"name": "🔀 Start o Stop?",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3,
|
||||
"position": [
|
||||
560,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "rules",
|
||||
"rules": {
|
||||
"values": [
|
||||
{
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.is_start }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "start"
|
||||
},
|
||||
{
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "loose"
|
||||
},
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.is_stop }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "stop"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pg_start",
|
||||
"name": "💾 PG - Inizia Sessione",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
840,
|
||||
180
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO behavioral_context (user_id,event_type,start_at,do_not_disturb,home_presence_expected,notes) VALUES ('{{ $json.user_id }}','watching_media',now(),true,true,'{{ JSON.stringify({item:$json.subject,device:$json.device,item_type:$json.item_type,session_id:$json.session_id}) }}'::jsonb)",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pg_msg_start",
|
||||
"name": "💬 PG - Msg Start",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1120,
|
||||
180
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO agent_messages (agent,priority,event_type,user_id,subject,detail,expires_at) VALUES ('media','low','behavioral_observation','{{ $(\"🔀 Normalizza Evento\").item.json.user_id }}','▶️ {{ $(\"🔀 Normalizza Evento\").item.json.subject }} ({{ $(\"🔀 Normalizza Evento\").item.json.device }})','{{ JSON.stringify({event:\"watching_media_start\",item_type:$(\"🔀 Normalizza Evento\").item.json.item_type,device:$(\"🔀 Normalizza Evento\").item.json.device,ts:$(\"🔀 Normalizza Evento\").item.json.ts}) }}'::jsonb,now()+interval '4 hours')",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pg_stop",
|
||||
"name": "💾 PG - Chiudi Sessione",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
840,
|
||||
420
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "UPDATE behavioral_context SET end_at=now(),do_not_disturb=false WHERE id=(SELECT id FROM behavioral_context WHERE user_id='{{ $json.user_id }}' AND event_type='watching_media' AND end_at IS NULL ORDER BY start_at DESC LIMIT 1)",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pg_msg_stop",
|
||||
"name": "💬 PG - Msg Stop",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1120,
|
||||
420
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO agent_messages (agent,priority,event_type,user_id,subject,detail,expires_at) VALUES ('media','low','behavioral_observation','{{ $(\"🔀 Normalizza Evento\").item.json.user_id }}','⏹️ {{ $(\"🔀 Normalizza Evento\").item.json.subject }} ({{ $(\"🔀 Normalizza Evento\").item.json.device }})','{{ JSON.stringify({event:\"watching_media_stop\",device:$(\"🔀 Normalizza Evento\").item.json.device,ts:$(\"🔀 Normalizza Evento\").item.json.ts}) }}'::jsonb,now()+interval '1 hour')",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook Jellyfin": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔀 Normalizza Evento",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔀 Normalizza Evento": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔀 Start o Stop?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔀 Start o Stop?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "💾 PG - Inizia Sessione",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "💾 PG - Chiudi Sessione",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"💾 PG - Inizia Sessione": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "💬 PG - Msg Start",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"💾 PG - Chiudi Sessione": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "💬 PG - Msg Stop",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "fe285311-5816-456e-bdf1-b7f85fcc1f7c",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": false
|
||||
}
|
||||
@@ -51,7 +51,7 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-1952,
|
||||
-1696,
|
||||
0
|
||||
]
|
||||
},
|
||||
@@ -66,7 +66,7 @@
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
-1696,
|
||||
-1440,
|
||||
0
|
||||
],
|
||||
"webhookId": "ccbdaca8-c592-4c22-88a3-0e18b8272a26",
|
||||
@@ -110,8 +110,8 @@
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-1696,
|
||||
464
|
||||
-1456,
|
||||
512
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -411,8 +411,8 @@
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
2032,
|
||||
288
|
||||
2048,
|
||||
-16
|
||||
],
|
||||
"webhookId": "60fefda0-215b-49e8-86ae-15ef4b03d89f",
|
||||
"credentials": {
|
||||
@@ -431,8 +431,8 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2032,
|
||||
-64
|
||||
2048,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -469,8 +469,8 @@
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2288,
|
||||
-64
|
||||
2304,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
@@ -488,8 +488,8 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2512,
|
||||
-64
|
||||
2528,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -501,8 +501,8 @@
|
||||
"type": "n8n-nodes-base.wait",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
2736,
|
||||
-64
|
||||
2752,
|
||||
304
|
||||
],
|
||||
"webhookId": "wait-pl-multi"
|
||||
},
|
||||
@@ -527,8 +527,8 @@
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2960,
|
||||
-64
|
||||
2976,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
@@ -546,8 +546,8 @@
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
3184,
|
||||
-64
|
||||
3200,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -566,8 +566,8 @@
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
3392,
|
||||
-64
|
||||
3408,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
@@ -589,8 +589,8 @@
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
3616,
|
||||
-64
|
||||
3632,
|
||||
304
|
||||
],
|
||||
"webhookId": "3be53ead-f628-4ec4-99d4-f378224c57ed",
|
||||
"credentials": {
|
||||
@@ -611,8 +611,8 @@
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
3840,
|
||||
-64
|
||||
3856,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -626,9 +626,43 @@
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
4064,
|
||||
-64
|
||||
4080,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "n_memoria_code",
|
||||
"name": "🧠 Salva in Memoria",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
3200,
|
||||
400
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "// ─── 🧠 Salva in Memoria: memory_facts + Qdrant knowledge ────────────────────\n// Eseguito in parallelo dopo Paperless - Patch Metadati\n// Non bloccante: errori vengono loggati ma non interrompono il flusso\n\nconst OLLAMA_URL = 'http://ollama.ai.svc.cluster.local:11434';\nconst QDRANT_URL = 'http://qdrant.persistence.svc.cluster.local:6333';\nconst QDRANT_KEY = '__Montecarlo00!';\nconst COLLECTION = 'knowledge';\n\nconst parsed = $('Parse Risposta GPT').first().json;\nconst docData = $('Estrai Document ID').first().json;\nconst ocrText = ($('FileWizard - Stato OCR').first().json.result_preview || '').substring(0, 3000);\nconst normIn = $('Normalizza Input').first().json;\n\nconst docId = docData.doc_id;\nconst title = parsed.title || docData.title || normIn.filename || 'Documento';\nconst sourceRef = `paperless-${docId}`;\n\n// TTL by doc type\nconst dtype = (parsed.document_type_name || '').toLowerCase();\nlet ttlDays = 365;\nif (dtype.includes('bolletta') || dtype.includes('fattura')) ttlDays = 180;\nelse if (dtype.includes('cedolino')) ttlDays = 730;\nelse if (dtype.includes('ricevuta')) ttlDays = 90;\nconst expiresAt = new Date(Date.now() + ttlDays * 86400000).toISOString();\n\n// Text to embed: title + OCR excerpt\nconst embedText = `${title}\\n\\n${ocrText}`.substring(0, 4000);\n\n// ── 1. Embedding via Ollama ────────────────────────────────────────────────\nlet embedding = null;\ntry {\n const embedRes = await this.helpers.httpRequest({\n method: 'POST',\n url: `${OLLAMA_URL}/api/embed`,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ model: 'nomic-embed-text', input: embedText })\n });\n embedding = embedRes.embeddings?.[0] || null;\n} catch(e) {\n console.error('Ollama embed error:', e.message);\n}\n\n// ── 2. Qdrant upsert ───────────────────────────────────────────────────────\nlet qdrantId = null;\nif (embedding && embedding.length === 768) {\n const { v4: uuidv4 } = require('uuid');\n qdrantId = uuidv4();\n try {\n await this.helpers.httpRequest({\n method: 'PUT',\n url: `${QDRANT_URL}/collections/${COLLECTION}/points`,\n headers: { 'content-type': 'application/json', 'api-key': QDRANT_KEY },\n body: JSON.stringify({ points: [{\n id: qdrantId,\n vector: embedding,\n payload: {\n user_id: 'martin',\n source: 'paperless',\n source_ref: sourceRef,\n doc_id: docId,\n title,\n category: 'finance',\n doc_type: parsed.document_type_name || '',\n correspondent: parsed.correspondent_name || '',\n created_date: parsed.created_date || '',\n tags: Array.isArray(parsed.tag_ids) ? parsed.tag_ids : [],\n date: new Date().toISOString(),\n action_required: false\n }\n }]})\n });\n } catch(e) {\n console.error('Qdrant upsert error:', e.message);\n qdrantId = null;\n }\n}\n\n// ── 3. Prepare for Postgres node ──────────────────────────────────────────\nconst pompeoNote = `Documento \"${title}\" archiviato su Paperless (doc_id=${docId}). ` +\n `Tipo: ${parsed.document_type_name || 'N/D'}. ` +\n `Corrispondente: ${parsed.correspondent_name || 'N/D'}. ` +\n `Fonte: ${normIn._source === 'email' ? 'email da ' + (normIn.from || '?') : 'Telegram'}.`;\n\nconst detail = {\n paperless_doc_id: docId,\n doc_type: parsed.document_type_name || '',\n correspondent: parsed.correspondent_name || '',\n created_date: parsed.created_date || '',\n tag_ids: Array.isArray(parsed.tag_ids) ? parsed.tag_ids : [],\n source: normIn._source,\n is_duplicate: docData.is_duplicate || false,\n qdrant_embedded: !!qdrantId\n};\n\nreturn [{ json: {\n source_ref: sourceRef,\n category: 'finance',\n subject: title.substring(0, 200).replace(/'/g, \"''\"),\n detail: JSON.stringify(detail),\n action_required: false,\n action_text: '',\n pompeo_note: pompeoNote.substring(0, 500).replace(/'/g, \"''\"),\n entity_refs: JSON.stringify({ people: [], places: [], products: [], amounts: [] }),\n expires_at: expiresAt,\n qdrant_id: qdrantId\n}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_pg_memoria",
|
||||
"name": "💾 Upsert Memoria",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.5,
|
||||
"position": [
|
||||
3420,
|
||||
400
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO memory_facts\n (user_id, source, source_ref, category, subject, detail, action_required, action_text, pompeo_note, entity_refs, expires_at)\nVALUES (\n 'martin',\n 'email',\n '{{ $json.thread_id }}',\n '{{ $json.category }}',\n '{{ $json.subject }}',\n '{{ $json.detail }}',\n {{ $json.action_required }},\n '{{ $json.action_text }}',\n '{{ $json.pompeo_note }}',\n '{{ $json.entity_refs }}'::jsonb,\n {{ $json.expires_at ? \"'\" + $json.expires_at + \"'::timestamptz\" : \"NULL\" }}\n)\nON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL DO UPDATE SET\n subject = EXCLUDED.subject,\n detail = EXCLUDED.detail,\n action_required = EXCLUDED.action_required,\n action_text = EXCLUDED.action_text,\n pompeo_note = EXCLUDED.pompeo_note,\n entity_refs = EXCLUDED.entity_refs,\n expires_at = EXCLUDED.expires_at",
|
||||
"options": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
@@ -926,6 +960,11 @@
|
||||
"node": "Telegram - Conferma Upload",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "🧠 Salva in Memoria",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -951,6 +990,17 @@
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🧠 Salva in Memoria": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "💾 Upsert Memoria",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -959,7 +1009,7 @@
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 2,
|
||||
"versionId": "5865a3df-1ea4-4983-a60e-cbc8997b89f8",
|
||||
"versionId": "f0e2579d-f423-4465-a144-9f03572ba94e",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
|
||||
135
workflows/JJ6B3w8i1bL7Q0rr.json
Normal file
135
workflows/JJ6B3w8i1bL7Q0rr.json
Normal file
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"id": "JJ6B3w8i1bL7Q0rr",
|
||||
"name": "💬 Pompeo - Alexa Voice Interface [Webhook]",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "pompeo-alexa",
|
||||
"responseMode": "lastNode",
|
||||
"options": {}
|
||||
},
|
||||
"id": "n_wh",
|
||||
"name": "Alexa Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
208,
|
||||
304
|
||||
],
|
||||
"webhookId": "pompeo-alexa"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT subject, category, pompeo_note FROM memory_facts WHERE user_id='martin' AND (expires_at IS NULL OR expires_at > now()) ORDER BY created_at DESC LIMIT 15",
|
||||
"options": {}
|
||||
},
|
||||
"id": "n_memory",
|
||||
"name": "Leggi Memoria",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.5,
|
||||
"position": [
|
||||
432,
|
||||
304
|
||||
],
|
||||
"alwaysOutputData": true,
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.github.com/copilot_internal/v2/token",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer ghu_WetWTL0IOKvvUPwZk6RGZ2qBnPd5Nz47qPXM"
|
||||
},
|
||||
{
|
||||
"name": "editor-version",
|
||||
"value": "vscode/1.95.3"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "n_token",
|
||||
"name": "Ottieni Token Copilot",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
640,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const webhookItem = $('Alexa Webhook').first().json;\nconst headers = webhookItem.headers || {};\nif ((headers['x-n8n-webhook-secret'] || '') !== 'cXvt0LZK9koIqgzcJZgV2qb4ymlEwpa7') {\n return [{ json: { tts_response: 'Accesso non autorizzato.' } }];\n}\nconst body = webhookItem.body || webhookItem;\nconst requestType = body.request?.type || 'Unknown';\nconst intent = body.request?.intent?.name || '';\nconst slots = body.request?.intent?.slots || {};\nconst query = Object.values(slots).map(s => s?.value).filter(Boolean).join(' ') || '';\n\nif (requestType === 'LaunchRequest') return [{ json: { tts_response: 'Ciao Martin! Sono Pompeo, il tuo assistente. Dimmi pure!' } }];\nif (requestType === 'SessionEndedRequest') return [{ json: { tts_response: '' } }];\nif (['AMAZON.StopIntent','AMAZON.CancelIntent'].includes(intent)) return [{ json: { tts_response: 'A presto Martin!' } }];\nif (intent === 'AMAZON.HelpIntent') return [{ json: { tts_response: 'Puoi chiedermi qualsiasi cosa. Cosa vuoi sapere?' } }];\n\nconst memoryFacts = $('Leggi Memoria').all().map(i => i.json).filter(f => f.subject);\nconst copilotToken = $('Ottieni Token Copilot').first().json.token;\nconst today = new Date().toLocaleDateString('it-IT', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\nconst memCtx = memoryFacts.length > 0\n ? memoryFacts.map(f => `- [${f.category}] ${f.subject}${f.pompeo_note ? ' -> ' + f.pompeo_note : ''}`).join('\\n')\n : '(nessun fatto recente)';\n\nconst gptResp = await this.helpers.httpRequest({\n method: 'POST', url: 'https://api.githubcopilot.com/chat/completions',\n headers: { 'Authorization': `Bearer ${copilotToken}`, 'Content-Type': 'application/json',\n 'editor-version': 'vscode/1.95.3', 'Copilot-Integration-Id': 'vscode-chat' },\n body: JSON.stringify({ model: 'gpt-4.1', messages: [\n { role: 'system', content: `Sei Pompeo, l'assistente AI personale di Martin Tahiraj. Rispondi via Amazon Echo (testo letto ad alta voce). Regole: italiano, max 2-3 frasi concise, niente markdown o emoji, tono amichevole. Oggi: ${today}\\nContesto:\\n${memCtx}` },\n { role: 'user', content: query || 'dimmi qualcosa su di me' }\n ], temperature: 0.7, max_tokens: 200 })\n});\nconst content = gptResp.choices?.[0]?.message?.content || 'Non ho capito, puoi ripetere?';\nreturn [{ json: { tts_response: content.replace(/\\*\\*(.*?)\\*\\*/g,'$1').replace(/\\*(.*?)\\*/g,'$1').replace(/#{1,6}\\s/g,'').trim() } }];"
|
||||
},
|
||||
"id": "n_master",
|
||||
"name": "Pompeo Core",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
864,
|
||||
304
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Alexa Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Leggi Memoria",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Leggi Memoria": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Ottieni Token Copilot",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Ottieni Token Copilot": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Pompeo Core",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "ea3ed67a-87ab-4289-8f53-c7e083e08087",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": false
|
||||
}
|
||||
337
workflows/K07e4PPANXDkmQsr.json
Normal file
337
workflows/K07e4PPANXDkmQsr.json
Normal file
@@ -0,0 +1,337 @@
|
||||
{
|
||||
"id": "K07e4PPANXDkmQsr",
|
||||
"name": "🎞️ Pompeo — Jellyfin Watch History [Schedule]",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "b76a6172-d497-42a5-b286-56263e0de523",
|
||||
"name": "⏰ Cron",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
0,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "http://jellyfin.media.svc.cluster.local:8096/Users/42369255a7c64917a28fc26d4c7f8265/Items?Recursive=true&IncludeItemTypes=Movie,Episode&Fields=Genres,UserData,SeriesName,ParentIndexNumber&SortBy=DatePlayed&SortOrder=Descending&Limit=100",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "MediaBrowser Token=\"d153606c1ca54574a20d2b40fcf1b02e\""
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "6bfda9a6-3b1f-46cb-ac05-0c19b9720e30",
|
||||
"name": "🎞️ HTTP Jellyfin",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
224,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const response = $input.first().json;\nconst allItems = response.Items || [];\n\nconst now = new Date();\nconst ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);\n\nconst filtered = allItems\n .filter(d => d.UserData && d.UserData.PlayCount > 0)\n .filter(d => {\n if (!d.UserData.LastPlayedDate) return false;\n return new Date(d.UserData.LastPlayedDate) >= ninetyDaysAgo;\n })\n .map(d => ({\n title: d.Type === 'Episode'\n ? (d.SeriesName || '') + ' S' + String(d.ParentIndexNumber || '')\n : (d.Name || ''),\n type: d.Type === 'Episode' ? 'episode' : 'movie',\n genres: d.Genres || [],\n play_count: d.UserData.PlayCount,\n played_pct: d.UserData.PlayedPercentage || 0,\n last_played: d.UserData.LastPlayedDate,\n is_favorite: d.UserData.IsFavorite || false\n }));\n\nlet watch_summary = 'Contenuti visti di recente:\\n';\nfor (const item of filtered) {\n const dateStr = item.last_played\n ? new Date(item.last_played).toLocaleDateString('it-IT')\n : 'N/A';\n watch_summary += '- ' + item.title + ' (' + item.type + ', ' + dateStr + ') - ' + item.play_count + 'x\\n';\n}\n\nreturn [{ json: { items: filtered, watch_summary } }];"
|
||||
},
|
||||
"id": "11e8a16f-4b77-4d1e-987b-3b269d577f08",
|
||||
"name": "🔀 Filtra Visti",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
448,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"number": [
|
||||
{
|
||||
"value1": "={{ $json.items.length }}",
|
||||
"operation": "larger"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "19de4739-efe2-495e-969a-fbb01cbd7eeb",
|
||||
"name": "❓ Ha Visti?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
672,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "d0a1c6e7-06eb-4d74-ac8b-c3c18abbcdbb",
|
||||
"name": "⛔ Stop",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
880,
|
||||
480
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.github.com/copilot_internal/v2/token",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"id": "52f1351c-594b-4e33-8c50-5318b7de67ff",
|
||||
"name": "🔑 Token Copilot",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
880,
|
||||
192
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "vBwUxlzKrX3oDHyN",
|
||||
"name": "GitHub Copilot OAuth Token"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const data = $input.first().json;\nconst watch_summary = data.watch_summary;\n\nconst prompt = 'Analizza questi contenuti guardati di recente da Martin e restituisci un JSON:\\n' +\n '{\\n' +\n ' \"recent_favorites\": [\"titoli\", \"più\", \"visti\"],\\n' +\n ' \"preferred_genres\": [\"generi\", \"prevalenti\"],\\n' +\n ' \"watch_patterns\": \"descrizione in italiano dei pattern di visione\",\\n' +\n ' \"completion_rate\": \"alta|media|bassa (basato su played_pct medio)\",\\n' +\n ' \"notes\": \"osservazioni utili per l\\'assistente personale\"\\n' +\n '}\\n\\n' +\n 'Cronologia:\\n' + watch_summary;\n\nreturn [{ json: { prompt } }];"
|
||||
},
|
||||
"id": "5d4d889c-bda7-4f7e-8873-277f2b8727d5",
|
||||
"name": "📝 Build Prompt",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1104,
|
||||
192
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.githubcopilot.com/chat/completions",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "={{ 'Bearer ' + $('🔑 Token Copilot').first().json.token }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "Copilot-Integration-Id",
|
||||
"value": "vscode-chat"
|
||||
},
|
||||
{
|
||||
"name": "Editor-Version",
|
||||
"value": "vscode/1.85.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "application/json",
|
||||
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\": $('📝 Build Prompt').first().json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":1024}) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "5135bca5-5f8b-4464-83d1-495ec9855a4a",
|
||||
"name": "🤖 GPT-4.1",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1328,
|
||||
192
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const content = $input.first().json.choices[0].message.content;\nreturn [{ json: JSON.parse(content) }];"
|
||||
},
|
||||
"id": "8dd4c239-79c2-47de-b898-f0f3d5b00e40",
|
||||
"name": "🔍 Parse",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1552,
|
||||
192
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO memory_facts (user_id, source, category, subject, detail, expires_at, source_ref)\nVALUES (\n 'martin',\n 'jellyfin',\n 'watch_history',\n 'Cronologia Visione Recente',\n '{{ $json.detail_json }}'::jsonb,\n NOW() + INTERVAL '30 days',\n 'watch_history_summary'\n)\nON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL\nDO UPDATE SET\n detail = EXCLUDED.detail,\n expires_at = EXCLUDED.expires_at,\n created_at = NOW();",
|
||||
"options": {}
|
||||
},
|
||||
"id": "93ca06bb-5a19-4931-a023-e701b367ce58",
|
||||
"name": "💾 PG — Upsert Cronologia",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1904,
|
||||
192
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const parsed = $input.first().json;\nconst detailJson = JSON.stringify(parsed).split(\"'\").join(\"''\");\nreturn [{ json: { detail_json: detailJson } }];"
|
||||
},
|
||||
"id": "599a48c0-dc77-4699-9060-ebccec1cc92c",
|
||||
"name": "🔧 Prepara Detail B2",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1728,
|
||||
192
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"⏰ Cron": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🎞️ HTTP Jellyfin",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🎞️ HTTP Jellyfin": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔀 Filtra Visti",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔀 Filtra Visti": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "❓ Ha Visti?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"❓ Ha Visti?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔑 Token Copilot",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "⛔ Stop",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔑 Token Copilot": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📝 Build Prompt",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📝 Build Prompt": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🤖 GPT-4.1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🤖 GPT-4.1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔍 Parse",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔍 Parse": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔧 Prepara Detail B2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔧 Prepara Detail B2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "💾 PG — Upsert Cronologia",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "bbcf28d7-652d-4aa9-b2a0-74d5ec29b56c",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": false
|
||||
}
|
||||
857
workflows/ZX5rLSETg6Xcymps.json
Normal file
857
workflows/ZX5rLSETg6Xcymps.json
Normal file
@@ -0,0 +1,857 @@
|
||||
{
|
||||
"id": "ZX5rLSETg6Xcymps",
|
||||
"name": "📄 Paperless — Upload Documento [Telegram]",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "n01",
|
||||
"name": "Telegram Trigger",
|
||||
"type": "n8n-nodes-base.telegramTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-2000,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"updates": [
|
||||
"message"
|
||||
],
|
||||
"additionalFields": {}
|
||||
},
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "uTXHLqcCJxbOvqN3",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
},
|
||||
"webhookId": "tg-documento-v3"
|
||||
},
|
||||
{
|
||||
"id": "n02",
|
||||
"name": "Check Caption",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-1750,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const msg = $input.first().json.message;\nconst doc = msg.document;\nif (!doc) return [];\nconst caption = (msg.caption || '').trim();\nif (!caption.toLowerCase().startsWith('documento')) return [];\nreturn [{ json: {\n file_id: doc.file_id,\n filename: doc.file_name || 'documento.pdf',\n mime_type: doc.mime_type || 'application/pdf',\n caption,\n chat_id: String(msg.chat.id),\n}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n03new",
|
||||
"name": "Telegram - Scarica File",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
-1500,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"resource": "file",
|
||||
"operation": "get",
|
||||
"fileId": "={{ $json.file_id }}",
|
||||
"download": true,
|
||||
"binaryProperty": "attachment",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "uTXHLqcCJxbOvqN3",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n05",
|
||||
"name": "FileWizard - Avvia OCR",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-1000,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://filewizard.home.svc.cluster.local:8000/ocr-pdf",
|
||||
"sendBody": true,
|
||||
"contentType": "multipart-form-data",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"parameterType": "formBinaryData",
|
||||
"name": "file",
|
||||
"inputDataFieldName": "data"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n06",
|
||||
"name": "⏳ Attendi OCR",
|
||||
"type": "n8n-nodes-base.wait",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-750,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"amount": 25,
|
||||
"unit": "seconds"
|
||||
},
|
||||
"webhookId": "wait-ocr-tg"
|
||||
},
|
||||
{
|
||||
"id": "n07",
|
||||
"name": "FileWizard - Stato OCR",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-500,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "=http://filewizard.home.svc.cluster.local:8000/job/{{ $json.job_id }}",
|
||||
"options": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n08",
|
||||
"name": "Paperless - Corrispondenti",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-250,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/correspondents/?page_size=50",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n09",
|
||||
"name": "Paperless - Tipi Doc",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/document_types/?page_size=50",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n10",
|
||||
"name": "Paperless - Tag",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
250,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/tags/?page_size=50",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n11",
|
||||
"name": "Paperless - Percorsi",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
500,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/storage_paths/?page_size=50",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n12",
|
||||
"name": "Build Prompt",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
750,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const fi = $('Check Caption').first().json;\nconst ocr = ($('FileWizard - Stato OCR').first().json.result_preview || '').substring(0, 2500);\nconst C = ($('Paperless - Corrispondenti').first().json.results || []).map(c => '[' + c.id + '] ' + c.name + ' (' + (c.document_count||0) + ' doc)').join('\\n');\nconst D = ($('Paperless - Tipi Doc').first().json.results || []).map(d => '[' + d.id + '] ' + d.name).join('\\n');\nconst T = ($('Paperless - Tag').first().json.results || []).map(t => '[' + t.id + '] ' + t.name).join('\\n');\nconst P = ($('Paperless - Percorsi').first().json.results || []).map(s => '[' + s.id + '] ' + s.name).join('\\n');\nconst prompt = `Sei l'assistente di Martin che gestisce Paperless-NGX.\nHa inviato un PDF via Telegram con caption: \"${fi.caption}\".\nNome file: ${fi.filename}\n\nTESTO ESTRATTO DAL PDF (OCR):\n${ocr}\n\nCORRISPONDENTI DISPONIBILI:\n${C}\n\nTIPI DOCUMENTO:\n${D}\n\nTAG:\n${T}\n\nPERCORSI:\n${P}\n\nISTRUZIONI:\n1. Se il documento non corrisponde a nessun tipo/tag esistente: skip:true con motivazione\n2. Altrimenti inferisci i metadati dal testo OCR (importo, periodo, data emissione...)\n3. Titolo descrittivo con dettagli concreti (es: 'E.ON Bolletta Energia Mar2026 87.50EUR')\n4. created_date = data emissione dal documento (non oggi)\n\nRispondi SOLO JSON valido:\n{\\\"skip\\\":false,\\\"skip_reason\\\":\\\"\\\",\\\"correspondent_id\\\":44,\\\"document_type_id\\\":2,\\\"tag_ids\\\":[3],\\\"storage_path_id\\\":2,\\\"title\\\":\\\"...\\\",\\\"created_date\\\":\\\"2026-03-01\\\",\\\"confidence_note\\\":\\\"...\\\"}`;\nreturn [{ json: { prompt, filename: fi.filename, caption: fi.caption }}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n13",
|
||||
"name": "Ottieni Token Copilot",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1000,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://api.github.com/copilot_internal/v2/token",
|
||||
"options": {},
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth"
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "vBwUxlzKrX3oDHyN",
|
||||
"name": "GitHub Copilot OAuth Token"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n14",
|
||||
"name": "GPT-4.1 - Inferisci Metadati",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1250,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.githubcopilot.com/chat/completions",
|
||||
"authentication": "none",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "={{ 'Bearer ' + $('Ottieni Token Copilot').first().json.token }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "Copilot-Integration-Id",
|
||||
"value": "vscode-chat"
|
||||
},
|
||||
{
|
||||
"name": "Editor-Version",
|
||||
"value": "vscode/1.85.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "application/json",
|
||||
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\": $('Build Prompt').first().json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":1024}) }}",
|
||||
"options": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n15",
|
||||
"name": "Parse Risposta GPT",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1500,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const raw = $input.first().json;\nconst content = (raw.choices || [])[0]?.message?.content || '{}';\nlet meta;\ntry { meta = JSON.parse(content); } catch(e) { throw new Error('GPT non JSON: ' + content.substring(0,300)); }\nconst fi = $('Check Caption').first().json;\nreturn [{ json: { ...meta, filename: fi.filename, caption: fi.caption }}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n16",
|
||||
"name": "Skip?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1750,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {},
|
||||
"combinator": "and",
|
||||
"conditions": [
|
||||
{
|
||||
"id": "s1",
|
||||
"leftValue": "={{ $json.skip }}",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true"
|
||||
},
|
||||
"rightValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n17",
|
||||
"name": "Telegram - Documento Non Riconosciuto",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
1980,
|
||||
140
|
||||
],
|
||||
"parameters": {
|
||||
"chatId": "-4814221197",
|
||||
"text": "={{ '⚠️ *Documento non archiviato*\\n\\n' + $json.skip_reason + '\\n\\nCaricalo manualmente su Paperless se necessario.' }}",
|
||||
"additionalFields": {
|
||||
"parse_mode": "Markdown"
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "uTXHLqcCJxbOvqN3",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n18",
|
||||
"name": "Prepara Upload",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1980,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const meta = $('Parse Risposta GPT').first().json;\nconst dl = $('Telegram - Scarica File').first();\nconst filename = meta.filename || 'documento.pdf';\nconst titleFromFilename = filename.replace(/\\.[^/.]+$/, '');\nlet tag_ids = meta.tag_ids;\nif (typeof tag_ids === 'string') { try { tag_ids = JSON.parse(tag_ids); } catch(e) { tag_ids = []; } }\nconst created_date = meta.created_date && meta.created_date !== 'None'\n ? String(meta.created_date).substring(0, 10)\n : new Date().toISOString().substring(0, 10);\nreturn [{ json: {\n title: meta.title || titleFromFilename,\n upload_title: titleFromFilename,\n filename,\n correspondent_id: meta.correspondent_id != null ? Number(meta.correspondent_id) : null,\n document_type_id: meta.document_type_id != null ? Number(meta.document_type_id) : null,\n storage_path_id: meta.storage_path_id != null ? Number(meta.storage_path_id) : null,\n tag_ids: Array.isArray(tag_ids) ? tag_ids.map(Number).filter(n => !isNaN(n)) : [],\n created_date,\n}, binary: dl.binary }];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n19",
|
||||
"name": "Paperless - Upload Documento",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2220,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://docs.mt-home.uk/api/documents/post_document/",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"sendBody": true,
|
||||
"contentType": "multipart-form-data",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"parameterType": "formBinaryData",
|
||||
"name": "document",
|
||||
"inputDataFieldName": "data"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"value": "={{ $json.upload_title }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n20",
|
||||
"name": "Estrai Task ID",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2440,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const raw = $input.first().json;\nconst task_id = (raw.data || '').replace(/\"/g, '').trim();\nconst meta = $('Prepara Upload').first().json;\nlet tag_ids = meta.tag_ids;\nif (typeof tag_ids === 'string') { try { tag_ids = JSON.parse(tag_ids); } catch(e) { tag_ids = []; } }\nreturn [{ json: { task_id, ...meta, tag_ids } }];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n21",
|
||||
"name": "⏳ Attendi Paperless",
|
||||
"type": "n8n-nodes-base.wait",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
2660,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"amount": 20,
|
||||
"unit": "seconds"
|
||||
},
|
||||
"webhookId": "wait-pl-documento"
|
||||
},
|
||||
{
|
||||
"id": "n22",
|
||||
"name": "Paperless - Stato Task",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2880,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/tasks/",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "task_id",
|
||||
"value": "={{ $json.task_id }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n23",
|
||||
"name": "Estrai Document ID",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
3100,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const tasks = $input.first().json;\nconst task = Array.isArray(tasks) ? tasks[0] : tasks;\nif (!task) throw new Error('Nessun task');\nconst isDup = task.status === 'FAILURE' && String(task.result || '').includes('duplicate');\nif (!isDup && task.status !== 'SUCCESS') throw new Error('Task ' + task.status + ': ' + (task.result || ''));\nconst doc_id = parseInt(String(task.related_document || '').replace(/\\D/g, ''), 10);\nif (!doc_id) throw new Error('Doc ID non trovato: ' + JSON.stringify(task));\nconst meta = $('Prepara Upload').first().json;\nlet tag_ids = meta.tag_ids;\nif (typeof tag_ids === 'string') { try { tag_ids = JSON.parse(tag_ids); } catch(e) { tag_ids = []; } }\nreturn [{ json: {\n doc_id, is_duplicate: isDup, title: meta.title,\n correspondent_id: meta.correspondent_id != null ? Number(meta.correspondent_id) : null,\n document_type_id: meta.document_type_id != null ? Number(meta.document_type_id) : null,\n storage_path_id: meta.storage_path_id != null ? Number(meta.storage_path_id) : null,\n tag_ids: Array.isArray(tag_ids) ? tag_ids.map(Number).filter(n => !isNaN(n)) : [],\n created_date: meta.created_date ? String(meta.created_date).substring(0, 10) : null,\n} }];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n24",
|
||||
"name": "Paperless - Patch Metadati",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
3320,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"method": "PATCH",
|
||||
"url": "=https://docs.mt-home.uk/api/documents/{{ $json.doc_id }}/",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify(Object.fromEntries(Object.entries({correspondent:$json.correspondent_id,document_type:$json.document_type_id,storage_path:$json.storage_path_id,tags:$json.tag_ids&&$json.tag_ids.length>0?$json.tag_ids:undefined,created:$json.created_date||undefined}).filter(([_,v])=>v!==null&&v!==undefined))) }}",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n25",
|
||||
"name": "Telegram - Conferma Upload",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
3540,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"chatId": "-4814221197",
|
||||
"text": "={{ '✅ *Documento archiviato su Paperless!*\\n\\n📄 *' + $('Prepara Upload').first().json.filename + '*\\n🗂 ' + $('Prepara Upload').first().json.title + '\\n' + ($('Estrai Document ID').first().json.is_duplicate ? '⚠️ _Duplicato — metadati aggiornati_\\n' : '') + '🔗 https://docs.mt-home.uk/documents/' + $json.doc_id + '/details/' }}",
|
||||
"additionalFields": {
|
||||
"parse_mode": "Markdown"
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "uTXHLqcCJxbOvqN3",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_fw_del",
|
||||
"name": "FileWizard - Elimina File",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
3760,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://filewizard.home.svc.cluster.local:8000/settings/delete-files",
|
||||
"options": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_fw_clr",
|
||||
"name": "FileWizard - Pulisci Job",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
3980,
|
||||
-140
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://filewizard.home.svc.cluster.local:8000/settings/clear-history",
|
||||
"options": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Telegram Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Caption",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check Caption": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Telegram - Scarica File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Telegram - Scarica File": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "FileWizard - Avvia OCR",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"FileWizard - Avvia OCR": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "⏳ Attendi OCR",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"⏳ Attendi OCR": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "FileWizard - Stato OCR",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"FileWizard - Stato OCR": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Corrispondenti",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Corrispondenti": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Tipi Doc",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Tipi Doc": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Tag",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Tag": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Percorsi",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Percorsi": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Prompt",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Prompt": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Ottieni Token Copilot",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Ottieni Token Copilot": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "GPT-4.1 - Inferisci Metadati",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"GPT-4.1 - Inferisci Metadati": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Risposta GPT",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Risposta GPT": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Skip?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Skip?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Telegram - Documento Non Riconosciuto",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Prepara Upload",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepara Upload": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Upload Documento",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Upload Documento": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Estrai Task ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Estrai Task ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "⏳ Attendi Paperless",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"⏳ Attendi Paperless": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Stato Task",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Stato Task": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Estrai Document ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Estrai Document ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Patch Metadati",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Patch Metadati": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Telegram - Conferma Upload",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Telegram - Conferma Upload": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "FileWizard - Elimina File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"FileWizard - Elimina File": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "FileWizard - Pulisci Job",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "ebc247d9-6391-45c1-94f8-f73ded65ce0b",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": true
|
||||
}
|
||||
489
workflows/o3uM1xDLTAKw4D6E.json
Normal file
489
workflows/o3uM1xDLTAKw4D6E.json
Normal file
@@ -0,0 +1,489 @@
|
||||
{
|
||||
"id": "o3uM1xDLTAKw4D6E",
|
||||
"name": "🎬 Pompeo — Media Library Sync [Schedule]",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "weeks",
|
||||
"triggerAtHour": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "b0ccc657-6f9e-43b7-b31e-e753e7c90c53",
|
||||
"name": "⏰ Cron",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
0,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "http://radarr.media.svc.cluster.local:7878/radarr/api/v3/movie?apikey=922d1405ab1147019d98a2997d941765",
|
||||
"options": {}
|
||||
},
|
||||
"id": "4b7a2a61-e7ca-41e4-b6fc-b1d112697306",
|
||||
"name": "🎬 HTTP Radarr",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
224,
|
||||
192
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "http://sonarr.media.svc.cluster.local:8989/sonarr/api/v3/series?apikey=22140655993a4ff6bf12314813ec6982",
|
||||
"options": {}
|
||||
},
|
||||
"id": "a23916f3-e727-4553-bb62-ad3f2602c1f2",
|
||||
"name": "📺 HTTP Sonarr",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
224,
|
||||
432
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "986ab3c1-3474-451a-bfa9-dfbeffbaca91",
|
||||
"name": "🔀 Merge",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
496,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const allItems = $input.all();\nconst movies = [];\nconst series = [];\n\nfor (const item of allItems) {\n const d = item.json;\n // d may be a full array response or a single object\n const src = Array.isArray(d) ? d : [d];\n for (const s of src) {\n if (s != null && s.tmdbId !== undefined) {\n movies.push({\n type: 'movie',\n title: s.title,\n year: s.year,\n genres: s.genres || [],\n status: s.hasFile ? 'available' : 'missing',\n source: 'radarr',\n source_id: s.tmdbId,\n monitored: s.monitored\n });\n } else if (s != null && s.tvdbId !== undefined) {\n series.push({\n type: 'series',\n title: s.title,\n year: s.year,\n genres: s.genres || [],\n status: s.monitored ? 'monitored' : 'unmonitored',\n source: 'sonarr',\n source_id: s.tvdbId,\n monitored: s.monitored\n });\n }\n }\n}\n\nconst items = [...movies, ...series];\n\nconst genreMap = {};\nfor (const item of items) {\n for (const genre of (item.genres || [])) {\n if (!genreMap[genre]) genreMap[genre] = [];\n genreMap[genre].push(item.title);\n }\n}\n\nlet library_summary = '';\nfor (const [genre, titles] of Object.entries(genreMap)) {\n library_summary += genre + ': ' + titles.join(', ') + '\\n';\n}\n\nreturn [{ json: { items, library_summary } }];"
|
||||
},
|
||||
"id": "a4d4acff-c5b1-450f-8428-402ca6e5969c",
|
||||
"name": "🔀 Merge Libreria",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
704,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.github.com/copilot_internal/v2/token",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"id": "57d1cfcb-a838-4382-99d9-4e2aff920511",
|
||||
"name": "🔑 Ottieni Token Copilot",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
928,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "vBwUxlzKrX3oDHyN",
|
||||
"name": "GitHub Copilot OAuth Token"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const mergeData = $('🔀 Merge Libreria').first().json;\nconst library_summary = mergeData.library_summary;\n\nconst prompt = 'Analizza questa libreria multimediale personale e restituisci un JSON con:\\n' +\n '{\\n' +\n ' \"top_genres\": [\"lista\", \"dei\", \"generi\", \"preferiti\"],\\n' +\n ' \"preferred_types\": \"movie|series|both\",\\n' +\n ' \"library_stats\": {\"total_movies\": N, \"total_series\": N, \"available_movies\": N},\\n' +\n ' \"taste_summary\": \"frase descrittiva del gusto cinematografico in italiano\",\\n' +\n ' \"notable_patterns\": [\"pattern1\", \"pattern2\"]\\n' +\n '}\\n\\n' +\n 'Libreria:\\n' + library_summary;\n\nreturn [{ json: { prompt } }];"
|
||||
},
|
||||
"id": "ee1918af-97fd-4c45-8d22-9e9808a47842",
|
||||
"name": "📝 Build Prompt LLM",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1136,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.githubcopilot.com/chat/completions",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "={{ 'Bearer ' + $('🔑 Ottieni Token Copilot').first().json.token }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "Copilot-Integration-Id",
|
||||
"value": "vscode-chat"
|
||||
},
|
||||
{
|
||||
"name": "Editor-Version",
|
||||
"value": "vscode/1.85.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "application/json",
|
||||
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\": $('📝 Build Prompt LLM').first().json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":2048}) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "709416ec-5b5a-4735-a985-9a98822b3910",
|
||||
"name": "🤖 GPT-4.1 Analisi",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1328,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const content = $input.first().json.choices[0].message.content;\nreturn [{ json: JSON.parse(content) }];"
|
||||
},
|
||||
"id": "e2888c8c-f2cf-4920-bab6-a32bf632b54f",
|
||||
"name": "🔍 Parse LLM",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1504,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO memory_facts (user_id, source, category, subject, detail, expires_at, source_ref)\nVALUES (\n 'martin',\n 'media_library',\n 'media_preferences',\n 'Preferenze Media Martin',\n '{{ $json.detail_json }}'::jsonb,\n NOW() + INTERVAL '7 days',\n 'media_preferences_summary'\n)\nON CONFLICT (user_id, source, source_ref) WHERE source_ref IS NOT NULL\nDO UPDATE SET\n detail = EXCLUDED.detail,\n expires_at = EXCLUDED.expires_at,\n created_at = NOW();",
|
||||
"options": {}
|
||||
},
|
||||
"id": "04eb6255-231f-4ed6-bd35-45970280c1cd",
|
||||
"name": "💾 PG — Upsert Preferenze",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1840,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "mRqzxhSboGscolqI",
|
||||
"name": "Pompeo — PostgreSQL"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $('🔀 Merge Libreria').first().json.items;\nreturn items.map(item => ({ json: item }));"
|
||||
},
|
||||
"id": "3fd8b3f4-f409-40e8-a747-2539248a959a",
|
||||
"name": "📋 Espandi Items",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2000,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "09919f3a-5ccb-4003-96b0-f096f9c8e802",
|
||||
"name": "🔁 Loop Items",
|
||||
"type": "n8n-nodes-base.splitInBatches",
|
||||
"typeVersion": 3,
|
||||
"position": [
|
||||
2208,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://ollama.ai.svc.cluster.local:11434/api/embeddings",
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "application/json",
|
||||
"body": "={{ JSON.stringify({\"model\":\"nomic-embed-text\",\"prompt\": $json.title + \" \" + ($json.year||\"\") + \" \" + ($json.genres||[]).join(\" \") + \" \" + ($json.type||\"\")}) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "5b1aabe1-58fe-4290-baa6-8bb02ad144d8",
|
||||
"name": "🔢 Ollama Embed",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2448,
|
||||
128
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "PUT",
|
||||
"url": "http://qdrant.persistence.svc.cluster.local:6333/collections/media_preferences/points",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "api-key",
|
||||
"value": "__Montecarlo00!"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "application/json",
|
||||
"body": "={{ $json.qdrant_body }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "3b04cf16-e00b-4e3d-aef7-beea09153db0",
|
||||
"name": "🗄️ Qdrant Upsert",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2688,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const parsed = $input.first().json;\n// Serialize to JSON and escape single quotes for SQL injection safety\nconst detailJson = JSON.stringify(parsed).split(\"'\").join(\"''\");\nreturn [{ json: { detail_json: detailJson } }];"
|
||||
},
|
||||
"id": "bf16855c-423d-4eb7-b483-a54af827036f",
|
||||
"name": "🔧 Prepara Detail",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1680,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "e52627b8-ef32-47e1-94e4-8ecbdf2cc02b",
|
||||
"name": "🔧 Prepara Qdrant",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2468,
|
||||
320
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "const item = $('🔁 Loop Items').item.json;\nconst embedding = $('🔢 Ollama Embed').item.json.embedding;\n\n// Use source_id as integer point ID (hash if needed)\nconst rawId = item.source_id;\nlet pointId = Math.abs(parseInt(rawId) || 0) % 2147483647;\nif (pointId === 0) pointId = Math.abs(String(rawId).split('').reduce((a,c) => a + c.charCodeAt(0), 0)) % 2147483647;\n\nconst payload = {\n points: [{\n id: pointId,\n vector: embedding,\n payload: {\n title: item.title || '',\n year: item.year || null,\n type: item.type || '',\n genres: item.genres || [],\n status: item.status || '',\n source: item.source || '',\n source_id: rawId,\n expires_at: new Date(Date.now() + 180 * 24 * 60 * 60 * 1000).toISOString()\n }\n }]\n};\n\nreturn [{ json: { qdrant_body: JSON.stringify(payload) } }];"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"⏰ Cron": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🎬 HTTP Radarr",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "📺 HTTP Sonarr",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🎬 HTTP Radarr": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔀 Merge",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📺 HTTP Sonarr": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔀 Merge",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔀 Merge": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔀 Merge Libreria",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔀 Merge Libreria": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔑 Ottieni Token Copilot",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔑 Ottieni Token Copilot": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📝 Build Prompt LLM",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📝 Build Prompt LLM": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🤖 GPT-4.1 Analisi",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🤖 GPT-4.1 Analisi": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔍 Parse LLM",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔍 Parse LLM": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔧 Prepara Detail",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"💾 PG — Upsert Preferenze": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📋 Espandi Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📋 Espandi Items": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔁 Loop Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔁 Loop Items": {
|
||||
"main": [
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "🔢 Ollama Embed",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔢 Ollama Embed": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔧 Prepara Qdrant",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🗄️ Qdrant Upsert": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🔁 Loop Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔧 Prepara Detail": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "💾 PG — Upsert Preferenze",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"🔧 Prepara Qdrant": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "🗄️ Qdrant Upsert",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "ef7c42b4-fddc-4e53-913a-47761d08f5a6",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": false
|
||||
}
|
||||
395
workflows/qtvB3r0cgejyCxUp.json
Normal file
395
workflows/qtvB3r0cgejyCxUp.json
Normal file
File diff suppressed because one or more lines are too long
734
workflows/vbzQ3fgUalOPdcOq.json
Normal file
734
workflows/vbzQ3fgUalOPdcOq.json
Normal file
@@ -0,0 +1,734 @@
|
||||
{
|
||||
"id": "vbzQ3fgUalOPdcOq",
|
||||
"name": "📄 Paperless — Bolletta Allegata",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "wb-bolletta",
|
||||
"name": "📎 Webhook Bolletta",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-2200,
|
||||
0
|
||||
],
|
||||
"webhookId": "paperless-bolletta-001",
|
||||
"parameters": {
|
||||
"path": "paperless-bolletta",
|
||||
"httpMethod": "POST",
|
||||
"responseMode": "onReceived",
|
||||
"options": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gmail-get-msg",
|
||||
"name": "Gmail - Leggi Messaggio",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-1900,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "=https://gmail.googleapis.com/gmail/v1/users/me/messages/{{ $json.body.email_id }}?format=full",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "gmailOAuth2",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "qvOikS6IF0H5khr8",
|
||||
"name": "Gmail account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "find-pdf",
|
||||
"name": "Trova Allegato PDF",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-1600,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const msg = $input.first().json;\n// La risposta Gmail API ha payload.parts con le parti del messaggio\nfunction findAttachments(parts, results) {\n if (!parts) return;\n for (const part of parts) {\n if (part.parts) findAttachments(part.parts, results);\n if (part.body && part.body.attachmentId) {\n results.push({\n attachment_id: part.body.attachmentId,\n filename: part.filename || 'allegato.pdf',\n mime_type: part.mimeType || 'application/octet-stream',\n size: part.body.size || 0\n });\n }\n }\n}\n\nconst parts = msg.payload && msg.payload.parts ? msg.payload.parts : [];\nconst attachments = [];\nfindAttachments(parts, attachments);\n\nconst pdf = attachments.find(a =>\n a.mime_type === 'application/pdf' ||\n a.filename.toLowerCase().endsWith('.pdf')\n) || attachments[0];\n\nif (!pdf) {\n throw new Error('Nessun allegato trovato nel messaggio. Parts: ' + JSON.stringify(parts).substring(0,200));\n}\n\nreturn [{json: {\n email_id: msg.id,\n thread_id: msg.threadId || msg.id,\n attachment_id: pdf.attachment_id,\n filename: pdf.filename,\n mime_type: pdf.mime_type,\n from: msg.payload?.headers?.find(h=>h.name==='From')?.value || '',\n subject: msg.payload?.headers?.find(h=>h.name==='Subject')?.value || '',\n}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dl-att",
|
||||
"name": "Gmail - Download Allegato",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-1300,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "=https://gmail.googleapis.com/gmail/v1/users/me/messages/{{ $json.email_id }}/attachments/{{ $json.attachment_id }}",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "gmailOAuth2",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "qvOikS6IF0H5khr8",
|
||||
"name": "Gmail account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "decode-att",
|
||||
"name": "Decodifica Allegato",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-1000,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "// La Gmail API restituisce il PDF in base64url (non standard base64)\nconst data = $input.first().json.data;\nif (!data) throw new Error('Nessun dato binario nella risposta attachment');\n\n// Converti base64url → base64 standard\nconst base64 = data.replace(/-/g, '+').replace(/_/g, '/');\nconst buffer = Buffer.from(base64, 'base64');\n\n// Prendi filename dal nodo precedente (Trova Allegato PDF)\nconst filename = $('Trova Allegato PDF').first().json.filename || 'bolletta.pdf';\nconst mimeType = $('Trova Allegato PDF').first().json.mime_type || 'application/pdf';\n\nconst binaryData = await this.helpers.prepareBinaryData(buffer, filename, mimeType);\nreturn [{json: {\n email_id: $('Trova Allegato PDF').first().json.email_id,\n filename,\n from: $('Trova Allegato PDF').first().json.from,\n subject: $('Trova Allegato PDF').first().json.subject,\n}, binary: { attachment: binaryData }}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pl-corresp",
|
||||
"name": "Paperless - Corrispondenti",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-700,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/correspondents/?page_size=100",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pl-doctypes",
|
||||
"name": "Paperless - Tipi Doc",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-400,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/document_types/?page_size=100",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pl-tags",
|
||||
"name": "Paperless - Tag",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-100,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/tags/?page_size=100",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pl-paths",
|
||||
"name": "Paperless - Percorsi",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
200,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/storage_paths/?page_size=100",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pl-search",
|
||||
"name": "Paperless - Cerca Simili",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
500,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "=https://docs.mt-home.uk/api/documents/?page_size=5&ordering=-created&search={{ encodeURIComponent($('Trova Allegato PDF').first().json.from.replace(/.*<(.*)>.*/, '$1').split('@')[0]) }}",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "build-prompt",
|
||||
"name": "Build Prompt Inferenza",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
800,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const meta = $input.first().json;\nconst emailMeta = $('Decodifica Allegato').first().json;\n\n// Fetch all Paperless metadata from previous nodes\nconst correspondents = ($('Paperless - Corrispondenti').first().json.results || [])\n .map(c => `[${c.id}] ${c.name} (documenti: ${c.document_count||0})`).join('\\n');\n\nconst docTypes = ($('Paperless - Tipi Doc').first().json.results || [])\n .map(d => `[${d.id}] ${d.name}`).join('\\n');\n\nconst tags = ($('Paperless - Tag').first().json.results || [])\n .map(t => `[${t.id}] ${t.name}`).join('\\n');\n\nconst storagePaths = ($('Paperless - Percorsi').first().json.results || [])\n .map(s => `[${s.id}] ${s.name}`).join('\\n');\n\nconst similarDocs = ($('Paperless - Cerca Simili').first().json.results || []).slice(0, 5)\n .map(d => `- \"${d.title}\" → corrispondente: ${d.correspondent||'?'}, tipo: ${d.document_type||'?'}, tags: ${(d.tags||[]).join(',')}, path: ${d.storage_path||'?'}`)\n .join('\\n');\n\nconst prompt = `Sei un assistente che gestisce l'archivio documenti di Martin su Paperless-NGX.\nAnalizza questa email con allegato e scegli i metadati corretti per archiviare il documento.\n\nEMAIL:\n- Da: ${emailMeta.from}\n- Oggetto: ${emailMeta.subject}\n- Nome file allegato: ${emailMeta.filename}\n\nCORRISPONDENTI DISPONIBILI (usa l'ID numerico):\n${correspondents}\n\nTIPI DOCUMENTO DISPONIBILI:\n${docTypes}\n\nTAG DISPONIBILI:\n${tags}\n\nPERCORSI DI ARCHIVIAZIONE DISPONIBILI:\n${storagePaths}\n\nDOCUMENTI SIMILI GIÀ ARCHIVIATI (per riferimento):\n${similarDocs || 'Nessuno trovato'}\n\nISTRUZIONI:\n1. Identifica il corrispondente più adatto in base al mittente/oggetto. Se non esiste, metti null.\n2. Scegli il tipo documento più appropriato (Bolletta, Fattura, Ricevuta, etc.)\n3. Scegli i tag appropriati (puoi sceglierne più di uno)\n4. Scegli il percorso di archiviazione più adatto\n5. Genera un titolo breve e descrittivo in italiano (es: \"E.ON Bolletta Energia Mar 2026\")\n6. Inferisci la data del documento se possibile dall'oggetto/mittente (formato YYYY-MM-DD)\n\nRispondi SOLO con JSON valido:\n{\n \"correspondent_id\": 44,\n \"document_type_id\": 2,\n \"tag_ids\": [3],\n \"storage_path_id\": 2,\n \"title\": \"E.ON Bolletta Energia Marzo 2026\",\n \"created_date\": \"2026-03-01\",\n \"confidence_note\": \"Breve nota sul ragionamento\"\n}`;\n\nreturn [{ json: { prompt, ...meta } }];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "copilot-token",
|
||||
"name": "Ottieni Token Copilot",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1100,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://api.github.com/copilot_internal/v2/token",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "vBwUxlzKrX3oDHyN",
|
||||
"name": "GitHub Copilot OAuth Token"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gpt41-infer",
|
||||
"name": "GPT-4.1 - Inferisci Metadati",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1400,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.githubcopilot.com/chat/completions",
|
||||
"authentication": "none",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "={{ 'Bearer ' + $('Ottieni Token Copilot').first().json.token }}"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "Copilot-Integration-Id",
|
||||
"value": "vscode-chat"
|
||||
},
|
||||
{
|
||||
"name": "Editor-Version",
|
||||
"value": "vscode/1.85.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"rawContentType": "application/json",
|
||||
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\": $('Build Prompt Inferenza').first().json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":1024}) }}",
|
||||
"options": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "parse-gpt",
|
||||
"name": "Parse Risposta GPT",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1700,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const raw = $input.first().json;\nconst content = (raw.choices && raw.choices[0] && raw.choices[0].message)\n ? raw.choices[0].message.content : (raw.text || '');\n\nconst cleaned = content.replace(/```json\\n?/g,'').replace(/```\\n?/g,'').trim();\n\nlet parsed;\ntry {\n parsed = JSON.parse(cleaned);\n} catch(e) {\n const m = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (m) parsed = JSON.parse(m[0]);\n else throw new Error('Cannot parse GPT response: ' + content.substring(0,300));\n}\n\n// Merge with attachment binary from previous node\nconst attItem = $('Decodifica Allegato').first();\n\nreturn [{\n json: {\n correspondent_id: parsed.correspondent_id || null,\n document_type_id: parsed.document_type_id || null,\n tag_ids: parsed.tag_ids || [],\n storage_path_id: parsed.storage_path_id || null,\n title: parsed.title || 'Documento',\n created_date: parsed.created_date || null,\n confidence_note: parsed.confidence_note || '',\n filename: attItem.json.filename,\n from: attItem.json.from,\n subject: attItem.json.subject,\n },\n binary: attItem.binary\n}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "prepara-upload",
|
||||
"name": "Prepara Upload",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1780,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const meta = $('Parse Risposta GPT').first().json;\nconst decodeNode = $('Decodifica Allegato').first();\nconst emailMeta = decodeNode.json;\n\n// Titolo = filename allegato senza estensione (es: \"10968276\")\nconst filename = emailMeta.filename || 'bolletta.pdf';\nconst title = filename.replace(/\\.[^/.]+$/, '');\n\n// Data = data ricezione email (internalDate è in millisecondi)\nconst internalDate = $('Gmail - Leggi Messaggio').first().json.internalDate;\nconst receivedISO = internalDate\n ? new Date(parseInt(internalDate)).toISOString()\n : null;\n\n// Parse tag_ids\nlet tag_ids = meta.tag_ids;\nif (typeof tag_ids === 'string') {\n try { tag_ids = JSON.parse(tag_ids); } catch(e) { tag_ids = []; }\n}\n\nconst result = {\n title,\n filename,\n correspondent_id: meta.correspondent_id != null ? Number(meta.correspondent_id) : null,\n document_type_id: meta.document_type_id != null ? Number(meta.document_type_id) : null,\n storage_path_id: meta.storage_path_id != null ? Number(meta.storage_path_id) : null,\n tag_ids: Array.isArray(tag_ids) ? tag_ids.map(Number).filter(n => !isNaN(n)) : [],\n created_date: receivedISO,\n};\n\nreturn [{ json: result, binary: decodeNode.binary }];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pl-upload",
|
||||
"name": "Paperless - Upload Documento",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2000,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://docs.mt-home.uk/api/documents/post_document/",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"sendBody": true,
|
||||
"contentType": "multipart-form-data",
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"parameterType": "formBinaryData",
|
||||
"name": "document",
|
||||
"inputDataFieldName": "attachment"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"value": "={{ $json.title }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tg-confirm",
|
||||
"name": "Telegram - Conferma Upload",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
2300,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"chatId": "-4814221197",
|
||||
"text": "={{ '✅ *Documento archiviato su Paperless!*\\n\\n📄 *' + $('Prepara Upload').first().json.filename + '*\\n🗂 ' + $('Prepara Upload').first().json.title + '\\n' + ($('Estrai Document ID').first().json.is_duplicate ? '⚠️ _Duplicato — metadati aggiornati_\\n' : '') + '🔗 https://docs.mt-home.uk/documents/' + $json.id + '/details/' }}",
|
||||
"additionalFields": {
|
||||
"parse_mode": "Markdown"
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "uTXHLqcCJxbOvqN3",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "estrai-task-id",
|
||||
"name": "Estrai Task ID",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2220,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "// Paperless post_document risponde con {data: \"uuid\"} (responseFormat: text → n8n wrappa in {data:...})\nconst raw = $input.first().json;\nconst task_id = raw.data || raw.body || String(raw);\nconst meta = $('Prepara Upload').first().json;\n\n// Parse tag_ids se arriva come stringa\nlet tag_ids = meta.tag_ids;\nif (typeof tag_ids === 'string') {\n try { tag_ids = JSON.parse(tag_ids); } catch(e) { tag_ids = []; }\n}\n\nreturn [{ json: { \n task_id: task_id.replace(/\"/g,'').trim(),\n ...meta,\n tag_ids\n}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "wait-paperless",
|
||||
"name": "⏳ Attendi Paperless",
|
||||
"type": "n8n-nodes-base.wait",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
2440,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"amount": 20,
|
||||
"unit": "seconds"
|
||||
},
|
||||
"webhookId": "wait-paperless-bolletta"
|
||||
},
|
||||
{
|
||||
"id": "get-task-status",
|
||||
"name": "Paperless - Stato Task",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
2660,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "GET",
|
||||
"url": "https://docs.mt-home.uk/api/tasks/",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "task_id",
|
||||
"value": "={{ $json.task_id }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "estrai-doc-id",
|
||||
"name": "Estrai Document ID",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2880,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "runOnceForAllItems",
|
||||
"jsCode": "const tasks = $input.first().json;\nconst taskList = Array.isArray(tasks) ? tasks : [tasks];\nconst task = taskList[0];\nif (!task) throw new Error('Nessun task trovato');\n\nconst isDuplicate = task.status === 'FAILURE' && String(task.result || '').includes('duplicate');\nconst isSuccess = task.status === 'SUCCESS';\nif (!isSuccess && !isDuplicate) {\n throw new Error(`Task ${task.status}: ${task.result || task.task_error || ''}`);\n}\n\nconst doc_id = parseInt(String(task.related_document || '').replace(/\\D/g,''), 10);\nif (!doc_id) throw new Error('Document ID non trovato: ' + JSON.stringify(task));\n\nconst meta = $('Prepara Upload').first().json;\n\nlet tag_ids = meta.tag_ids;\nif (typeof tag_ids === 'string') {\n try { tag_ids = JSON.parse(tag_ids); } catch(e) { tag_ids = []; }\n}\n\n// created_date è già ISO completo (es: \"2026-03-18T21:26:51.000Z\"), prendi solo YYYY-MM-DD\nconst created_date = meta.created_date\n ? String(meta.created_date).substring(0, 10)\n : null;\n\nreturn [{ json: {\n doc_id,\n is_duplicate: isDuplicate,\n title: meta.title,\n correspondent_id: meta.correspondent_id != null ? Number(meta.correspondent_id) : null,\n document_type_id: meta.document_type_id != null ? Number(meta.document_type_id) : null,\n storage_path_id: meta.storage_path_id != null ? Number(meta.storage_path_id) : null,\n tag_ids: Array.isArray(tag_ids) ? tag_ids.map(Number).filter(n => !isNaN(n)) : [],\n created_date,\n}}];"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "patch-doc-meta",
|
||||
"name": "Paperless - Patch Metadati",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
3100,
|
||||
-200
|
||||
],
|
||||
"parameters": {
|
||||
"method": "PATCH",
|
||||
"url": "=https://docs.mt-home.uk/api/documents/{{ $json.doc_id }}/",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpHeaderAuth",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ \n JSON.stringify(\n Object.fromEntries(\n Object.entries({\n correspondent: $json.correspondent_id,\n document_type: $json.document_type_id,\n storage_path: $json.storage_path_id,\n tags: $json.tag_ids && $json.tag_ids.length > 0 ? $json.tag_ids : undefined,\n created: $json.created_date || undefined\n }).filter(([_, v]) => v !== null && v !== undefined)\n )\n )\n}}",
|
||||
"options": {}
|
||||
},
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "uvGjLbrN5yQTQIzv",
|
||||
"name": "Paperless-NGX API"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"📎 Webhook Bolletta": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Gmail - Leggi Messaggio",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Gmail - Leggi Messaggio": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Trova Allegato PDF",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Trova Allegato PDF": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Gmail - Download Allegato",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Gmail - Download Allegato": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Decodifica Allegato",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Decodifica Allegato": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Corrispondenti",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Corrispondenti": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Tipi Doc",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Tipi Doc": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Tag",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Tag": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Percorsi",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Percorsi": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Cerca Simili",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Cerca Simili": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Prompt Inferenza",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Prompt Inferenza": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Ottieni Token Copilot",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Ottieni Token Copilot": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "GPT-4.1 - Inferisci Metadati",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"GPT-4.1 - Inferisci Metadati": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Risposta GPT",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Risposta GPT": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepara Upload",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Upload Documento": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Estrai Task ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepara Upload": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Upload Documento",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Estrai Task ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "⏳ Attendi Paperless",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"⏳ Attendi Paperless": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Stato Task",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Stato Task": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Estrai Document ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Estrai Document ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Paperless - Patch Metadati",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Paperless - Patch Metadati": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Telegram - Conferma Upload",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "cd5d2a62-eea4-419d-b65f-1b4bc134fd33",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": true
|
||||
}
|
||||
146
workflows/w0oJ1i6sESvaB5W1.json
Normal file
146
workflows/w0oJ1i6sESvaB5W1.json
Normal file
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"id": "w0oJ1i6sESvaB5W1",
|
||||
"name": "⏰ Actual — Reminder Estratto Conto [Schedule]",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "n_cron",
|
||||
"name": "⏰ Cron Giornaliero",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
0,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 9,
|
||||
"field": "hours"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_verifica",
|
||||
"name": "📋 Verifica Task Scaduto",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
240,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "\nconst TASK_NAME = 'Actual - Estratto conto';\nconst TASKLIST_NAME = 'Finanze';\nconst today = new Date();\ntoday.setHours(0,0,0,0);\n\ntry {\n // Get task lists\n const listsRes = await this.helpers.requestWithAuthentication.call(\n this,\n 'googleCalendarOAuth2Api',\n {\n method: 'GET',\n url: 'https://tasks.googleapis.com/tasks/v1/users/@me/lists',\n json: true\n }\n );\n \n const taskList = (listsRes.items || []).find(l => l.title === TASKLIST_NAME);\n if (!taskList) {\n return [{ json: { send_reminder: false, reason: `Lista \"${TASKLIST_NAME}\" non trovata` } }];\n }\n \n // Get pending tasks\n const tasksRes = await this.helpers.requestWithAuthentication.call(\n this,\n 'googleCalendarOAuth2Api',\n {\n method: 'GET',\n url: `https://tasks.googleapis.com/tasks/v1/lists/${taskList.id}/tasks?showCompleted=false&showHidden=false`,\n json: true\n }\n );\n \n const task = (tasksRes.items || []).find(t => t.title === TASK_NAME && t.status !== 'completed');\n if (!task) {\n return [{ json: { send_reminder: false, reason: 'Task non trovato o già completato' } }];\n }\n \n // Check if due date is today or past\n let isDue = false;\n let dueInfo = 'nessuna scadenza';\n if (task.due) {\n const dueDate = new Date(task.due);\n dueDate.setHours(0,0,0,0);\n isDue = dueDate <= today;\n dueInfo = task.due.split('T')[0];\n } else {\n // No due date set — send reminder anyway (task exists and is not done)\n isDue = true;\n }\n \n return [{ json: {\n send_reminder: isDue,\n task_id: task.id,\n tasklist_id: taskList.id,\n due: dueInfo,\n reason: isDue ? `Task scaduto il ${dueInfo}` : `Task in scadenza il ${dueInfo}`\n } }];\n} catch(e) {\n return [{ json: { send_reminder: false, reason: `Errore Tasks API: ${e.message}` } }];\n}\n",
|
||||
"mode": "runOnceForAllItems"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_if_due",
|
||||
"name": "❓ Task Scaduto?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
480,
|
||||
300
|
||||
],
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "cond_due",
|
||||
"leftValue": "={{ $json.send_reminder }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n_reminder",
|
||||
"name": "📱 Reminder Telegram",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
720,
|
||||
180
|
||||
],
|
||||
"parameters": {
|
||||
"chatId": "-4814221197",
|
||||
"text": "📊 *Reminder: Estratto Conto*\n\nRicordati di scaricare e caricare l'estratto conto bancario!\n\nMandami il file CSV su Telegram con caption:\n`Estratto conto`\n\n_Il task \"Actual - Estratto conto\" risulta in scadenza — sarà marcato completato automaticamente dopo il caricamento._",
|
||||
"additionalFields": {
|
||||
"parse_mode": "Markdown"
|
||||
}
|
||||
},
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "uTXHLqcCJxbOvqN3",
|
||||
"name": "Telegram account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"⏰ Cron Giornaliero": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📋 Verifica Task Scaduto",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"📋 Verifica Task Scaduto": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "❓ Task Scaduto?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"❓ Task Scaduto?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "📱 Reminder Telegram",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "ed54aeb1-f6ad-4605-a102-09e177e6eb9d",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "Hdttz401OqqtObPo",
|
||||
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
|
||||
"personalEmail": "tahiraj.martin@gmail.com"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": false
|
||||
}
|
||||
Reference in New Issue
Block a user