{ "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 ", "personalEmail": "tahiraj.martin@gmail.com" }, "parentFolderId": null, "isArchived": false }