Task: Spotify Discover Weekly Archiver

n8n workflow JSON:
{"id":3,"name":"Spotify Weekly Archiver","nodes":[{"name":"Save to Archive","type":"n8n-nodes-base.spotify","position":[1670,270],"parameters":{"id":"={{$item(0).$node[\"Create Playlist\"].json[\"uri\"]}}","trackID":"={{$json[\"track\"][\"uri\"]}}","resource":"playlist","additionalFields":{}},"credentials":{"spotifyOAuth2Api":{"id":"1","name":"Spotify"}},"executeOnce":false,"typeVersion":1},{"name":"Cron","type":"n8n-nodes-base.cron","position":[290,270],"parameters":{"triggerTimes":{"item":[{"mode":"everyX","unit":"minutes","value":15}]}},"notesInFlow":false,"typeVersion":1},{"name":"Create Playlist","type":"n8n-nodes-base.spotify","position":[1350,270],"parameters":{"name":"={{$node[\"Generate Playlist Name\"].json[\"name\"]}}","resource":"playlist","operation":"create","additionalFields":{}},"credentials":{"spotifyOAuth2Api":{"id":"1","name":"Spotify"}},"typeVersion":1},{"name":"Generate Playlist Name","type":"n8n-nodes-base.function","position":[1190,270],"parameters":{"functionCode":"function getWeekNumber(d) {\n    // Copy date so don't modify original\n    d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));\n    // Set to nearest Thursday: current date + 4 - current day number\n    // Make Sunday's day number 7\n    d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay()||7));\n    // Get first day of year\n    var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));\n    // Calculate full weeks to nearest Thursday\n    var weekNo = Math.ceil(( ( (d - yearStart) / 86400000) + 1)/7);\n    // Return array of year and week number\n    return [d.getUTCFullYear(), weekNo];\n}\n\nweek = getWeekNumber(new Date());\nreturn [{json: {name: `Weekly ${week[0]}/${week[1]}`}}]\n"},"typeVersion":1},{"name":"Crypto","type":"n8n-nodes-base.crypto","position":[750,270],"parameters":{"type":"SHA256","value":"={{$json[\"string\"]}}","encoding":"base64","dataPropertyName":"hash"},"typeVersion":1},{"name":"IF","type":"n8n-nodes-base.if","position":[1030,270],"parameters":{"conditions":{"boolean":[{"value1":"={{$json[\"create\"]}}","value2":true}]}},"typeVersion":1},{"name":"Function","type":"n8n-nodes-base.function","position":[1510,270],"parameters":{"functionCode":"return $items('Get Playlist')"},"typeVersion":1},{"name":"Get Playlist","type":"n8n-nodes-base.spotify","position":[450,270],"parameters":{"id":"","resource":"playlist","operation":"getTracks","returnAll":true},"credentials":{"spotifyOAuth2Api":{"id":"1","name":"Spotify"}},"executeOnce":false,"typeVersion":1},{"name":"Create URI String","type":"n8n-nodes-base.function","position":[600,270],"parameters":{"functionCode":"let uriString = '';\n\nitems.sort((a, b) => a.json.track.uri.localeCompare(b.json.track.uri))\n\nfor (item of items) {\n  uriString += item.json.track.uri\n}\n\nreturn [{json:{string: uriString}}];"},"typeVersion":1},{"name":"Compare Hash","type":"n8n-nodes-base.function","position":[890,270],"parameters":{"functionCode":"const staticData = getWorkflowStaticData('global');\n\nconst oldHash = staticData.hash || '';\nconst newHash = items[0].json.hash;\n\nlet create = false;\n\nif (oldHash != newHash) {\n  create = true;\n  staticData.hash = newHash;\n}\n\nreturn [{json: {create: create, oldHash: oldHash, newHash: newHash}}];"},"typeVersion":1}],"active":true,"settings":{},"connections":{"IF":{"main":[[{"node":"Generate Playlist Name","type":"main","index":0}]]},"Cron":{"main":[[{"node":"Get Playlist","type":"main","index":0}]]},"Crypto":{"main":[[{"node":"Compare Hash","type":"main","index":0}]]},"Function":{"main":[[{"node":"Save to Archive","type":"main","index":0}]]},"Compare Hash":{"main":[[{"node":"IF","type":"main","index":0}]]},"Get Playlist":{"main":[[{"node":"Create URI String","type":"main","index":0}]]},"Create Playlist":{"main":[[{"node":"Function","type":"main","index":0}]]},"Create URI String":{"main":[[{"node":"Crypto","type":"main","index":0}]]},"Generate Playlist Name":{"main":[[{"node":"Create Playlist","type":"main","index":0}]]}}}