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