{ "name": "portfolio-website", "nodes": [ { "parameters": { "path": "/denshooter-71242/status", "responseMode": "responseNode", "options": {} }, "type": "n8n-nodes-base.webhook", "typeVersion": 2.1, "position": [ 0, 96 ], "id": "44d27fdc-49e7-4f86-a917-10781d81104f", "name": "Webhook", "webhookId": "4c292bc7-41f2-423d-86cf-a0384924b539" }, { "parameters": { "url": "https://wakapi.dk0.dev/api/summary", "sendQuery": true, "queryParameters": { "parameters": [ { "name": "interval", "value": "today" }, { "name": "api_key", "value": "2158fa72-e7fa-4dbd-9627-4235f241105e" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 240, 176 ], "id": "4e7559f3-85dc-43b6-b0c2-31313db15fbf", "name": "Wakapi", "onError": "continueErrorOutput" }, { "parameters": { "jsCode": "// --------------------------------------------------------\n// DATEN AUS DEN VORHERIGEN NODES HOLEN\n// --------------------------------------------------------\n\n// 1. Spotify Node\nlet spotifyData = null;\ntry {\n spotifyData = $('Spotify').first().json;\n} catch (e) {}\n\n// 2. Lanyard Node (Discord)\nlet lanyardData = null;\ntry {\n lanyardData = $('Lanyard').first().json.data;\n} catch (e) {}\n\n// 3. Wakapi Summary (Tages-Statistik)\nlet wakapiStats = null;\ntry {\n const wRaw = $('Wakapi').first().json;\n // Manchmal ist es direkt im Root, manchmal unter data\n wakapiStats = wRaw.grand_total ? wRaw : (wRaw.data ? wRaw.data : null);\n} catch (e) {}\n\n// 4. Wakapi Heartbeats (Live Check)\nlet heartbeatsList = [];\ntry {\n // Deine API liefert ein Array mit einem Objekt, das \"data\" enthält\n // Struktur: [ { \"data\": [...] } ]\n const response = $('WakapiLast').last().json;\n if (response.data && Array.isArray(response.data)) {\n heartbeatsList = response.data;\n }\n} catch (e) {}\n\n\n// --------------------------------------------------------\n// LOGIK & FORMATIERUNG\n// --------------------------------------------------------\n\n// --- A. SPOTIFY / MUSIC ---\nlet music = null;\n\nif (spotifyData && spotifyData.item && spotifyData.is_playing) {\n music = {\n isPlaying: true,\n track: spotifyData.item.name,\n artist: spotifyData.item.artists.map(a => a.name).join(', '),\n album: spotifyData.item.album.name,\n albumArt: spotifyData.item.album.images[0]?.url,\n url: spotifyData.item.external_urls.spotify\n };\n} else if (lanyardData?.listening_to_spotify && lanyardData.spotify) {\n music = {\n isPlaying: true,\n track: lanyardData.spotify.song,\n artist: lanyardData.spotify.artist.replace(/;/g, \", \"),\n album: lanyardData.spotify.album,\n albumArt: lanyardData.spotify.album_art_url,\n url: `https://open.spotify.com/track/${lanyardData.spotify.track_id}`\n };\n}\n\n// --- B. GAMING & STATUS ---\nlet gaming = null;\nlet status = {\n text: lanyardData?.discord_status || \"offline\",\n color: 'gray'\n};\n\n// Farben mapping\nif (status.text === 'online') status.color = 'green';\nif (status.text === 'idle') status.color = 'yellow';\nif (status.text === 'dnd') status.color = 'red';\n\nif (lanyardData?.activities) {\n lanyardData.activities.forEach(act => {\n // Type 0 = Game (Spotify ignorieren)\n if (act.type === 0 && act.name !== \"Spotify\") {\n let image = null;\n if (act.assets?.large_image) {\n if (act.assets.large_image.startsWith(\"mp:external\")) {\n image = act.assets.large_image.replace(/mp:external\\/([^\\/]*)\\/(https?)\\/([^\\/]*)\\/(.*)/, \"$2://$3/$4\");\n } else {\n image = `https://cdn.discordapp.com/app-assets/${act.application_id}/${act.assets.large_image}.png`;\n }\n }\n gaming = {\n isPlaying: true,\n name: act.name,\n details: act.details,\n state: act.state,\n image: image\n };\n }\n });\n}\n\n\n// --- C. CODING (Wakapi Logic) ---\nlet coding = null;\n\n// 1. Basis-Stats von heute (Fallback)\nif (wakapiStats && wakapiStats.grand_total) {\n coding = {\n isActive: false, \n stats: {\n time: wakapiStats.grand_total.text, // \"2 hrs 10 mins\"\n topLang: wakapiStats.languages?.[0]?.name || \"Code\",\n topProject: wakapiStats.projects?.[0]?.name || \"Project\"\n }\n };\n}\n\n// 2. Live Check via Heartbeats\nif (heartbeatsList.length > 0) {\n // Nimm den allerletzten Eintrag aus der Liste (das ist der neuste)\n const latestBeat = heartbeatsList[heartbeatsList.length - 1];\n\n if (latestBeat && latestBeat.time) {\n // Zeitstempel vergleichen \n // latestBeat.time ist Unix Seconds (z.B. 1767829137) -> mal 1000 für Millisekunden\n const beatTime = new Date(latestBeat.time * 1000).getTime();\n const now = new Date().getTime();\n const diffMinutes = (now - beatTime) / 1000 / 60;\n\n // Debugging (optional, kannst du in n8n Console sehen)\n // console.log(`Letzter Beat: ${new Date(beatTime).toISOString()} (${diffMinutes.toFixed(1)} min her)`);\n\n // Wenn jünger als 15 Minuten -> AKTIV\n if (diffMinutes < 1) {\n // Falls Summary leer war, erstellen wir ein Dummy\n if (!coding) coding = { stats: { time: \"Just started\" } };\n\n coding.isActive = true;\n \n // Projekt Name\n coding.project = latestBeat.project || coding.stats?.topProject;\n \n // Dateiname extrahieren (funktioniert für Windows \\ und Unix /)\n if (latestBeat.entity) {\n const parts = latestBeat.entity.split(/[/\\\\]/);\n coding.file = parts[parts.length - 1]; // Nimmt \"ActivityFeed.tsx\"\n }\n \n coding.language = latestBeat.language;\n }\n }\n}\n\n// --------------------------------------------------------\n// OUTPUT\n// --------------------------------------------------------\nreturn {\n json: {\n status,\n music,\n gaming,\n coding,\n timestamp: new Date().toISOString()\n }\n};" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 656, 48 ], "id": "103c6314-c48f-46a9-b986-20f94814bcae", "name": "Code in JavaScript" }, { "parameters": { "respondWith": "allIncomingItems", "options": {} }, "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1.5, "position": [ 848, 48 ], "id": "d2ef27ea-34b5-4686-8f24-41205636fc82", "name": "Respond to Webhook" }, { "parameters": { "operation": "currentlyPlaying" }, "type": "n8n-nodes-base.spotify", "typeVersion": 1, "position": [ 240, 0 ], "id": "cac25f50-c8bf-47d3-8812-ef56cd8110df", "name": "Spotify", "credentials": { "spotifyOAuth2Api": { "id": "2bHFkmHiwTxZQsK3", "name": "Spotify account" } } }, { "parameters": { "url": "http://discord-bot:3001/presence", "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 240, -176 ], "id": "febe9caf-2cc2-4bd6-8289-cf4f34249e20", "name": "Lanyard", "onError": "continueErrorOutput" }, { "parameters": { "url": "https://wakapi.dk0.dev/api/compat/wakatime/v1/users/current/heartbeats", "sendQuery": true, "queryParameters": { "parameters": [ { "name": "api_key", "value": "2158fa72-e7fa-4dbd-9627-4235f241105e" }, { "name": "date", "value": "={{ new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/Berlin' }) }}" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 256, 368 ], "id": "8d70ed7a-455d-4961-b735-2df86a8b5c04", "name": "WakapiLast", "onError": "continueErrorOutput" }, { "parameters": { "numberInputs": 4 }, "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ 480, 16 ], "id": "c4a1957a-9863-4dea-95c4-4e55637b403e", "name": "Merge" } ], "pinData": {}, "connections": { "Webhook": { "main": [ [ { "node": "Spotify", "type": "main", "index": 0 }, { "node": "Lanyard", "type": "main", "index": 0 }, { "node": "Wakapi", "type": "main", "index": 0 }, { "node": "WakapiLast", "type": "main", "index": 0 } ] ] }, "Wakapi": { "main": [ [ { "node": "Merge", "type": "main", "index": 2 } ] ] }, "Code in JavaScript": { "main": [ [ { "node": "Respond to Webhook", "type": "main", "index": 0 } ] ] }, "Spotify": { "main": [ [ { "node": "Merge", "type": "main", "index": 1 } ] ] }, "Lanyard": { "main": [ [ { "node": "Merge", "type": "main", "index": 0 } ] ] }, "WakapiLast": { "main": [ [ { "node": "Merge", "type": "main", "index": 3 } ] ] }, "Merge": { "main": [ [ { "node": "Code in JavaScript", "type": "main", "index": 0 } ] ] } }, "active": true, "settings": { "executionOrder": "v1", "availableInMCP": false }, "versionId": "842c4910-5935-4788-aede-b290af8cb96e", "meta": { "templateCredsSetupCompleted": true, "instanceId": "cb28e4db755465d5826da179e87f69603d81f833414cc52c327be9183a217b8d" }, "id": "M6sq0mVBmRYt4sia", "tags": [] }