{ "id": "cSg8UMX1OvzDoLrk", "name": "EVE Bazaar Ingest", "nodes": [ { "parameters": { "rule": { "interval": [ { "field": "minutes", "minutesInterval": 15 } ] } }, "id": "Schedule Trigger", "name": "Schedule Trigger", "position": [ 0, -24 ], "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.1 }, { "parameters": { "url": "https://forums.eveonline.com/c/marketplace/character-bazaar/60.rss", "options": {} }, "id": "RSS Feed Read", "name": "RSS Feed Read", "position": [ 224, -24 ], "type": "n8n-nodes-base.rssFeedRead", "typeVersion": 1.2 }, { "parameters": { "jsCode": "return $input.all().filter(item => {\n const title = (item.json.title || \"\").toUpperCase();\n return title.includes(\"WTS\") && !title.includes(\"WTB\");\n});" }, "id": "Filter WTS", "name": "Filter WTS", "position": [ 448, -24 ], "type": "n8n-nodes-base.code", "typeVersion": 2 }, { "parameters": { "jsCode": "const items = $input.all();\nreturn items.map(item => {\n const content = item.json.content || item.json.contentSnippet || \"\";\n const link = item.json.link || \"\";\n const text = content + \" \" + link;\n \n let charId = null;\n let skillqUrl = null;\n \n const patterns = [\n /evewho\\.com\\/character\\/(\\d+)/,\n /zkillboard\\.com\\/character\\/(\\d+)/,\n /qsna\\.eu\\/eve\\/characters?\\/(\\d+)/,\n /eveboard\\.com\\/pilot\\/([^\\/]+)/,\n /images\\.evetech\\.net\\/characters\\/(\\d+)\\//\n ];\n \n for (const pattern of patterns) {\n const match = text.match(pattern);\n if (match) {\n charId = match[1];\n break;\n }\n }\n \n // Look for SkillQ URL if no ID found\n if (!charId) {\n // Updated regex to require /share/ component and capture full path\n const skillqMatch = text.match(/skillq\\.net\\/char\\/([^\\/]+)\\/share\\/([a-f0-9\\-]+)/);\n if (skillqMatch) {\n // Construct full URL with share token\n // match[1] is name, match[2] is token\n skillqUrl = `https://skillq.net/char/${skillqMatch[1]}/share/${skillqMatch[2]}`;\n }\n }\n \n return {\n json: {\n ...item.json,\n character_id: charId,\n skillq_url: skillqUrl\n }\n };\n});" }, "id": "Extract Character ID", "name": "Extract Character ID", "position": [ 672, -24 ], "type": "n8n-nodes-base.code", "typeVersion": 2 }, { "parameters": { "jsCode": "const items = $input.all();\nconst ids = items.map(item => item.json.post_id).filter(id => id);\nreturn [{\n json: {\n ids: ids\n }\n}];" }, "id": "Prepare ID List", "name": "Prepare ID List", "position": [ 2016, -24 ], "type": "n8n-nodes-base.code", "typeVersion": 2 }, { "parameters": { "operation": "executeQuery", "query": "SELECT * FROM character_bazaar_posts WHERE post_id IN ('{{ $json.ids.join(\"','\") }}')", "options": {} }, "id": "Lookup Posts", "name": "Lookup Posts", "position": [ 2240, -24 ], "type": "n8n-nodes-base.postgres", "typeVersion": 2.4, "alwaysOutputData": true, "credentials": { "postgres": { "id": "dsnKfvOBMkgU21Lt", "name": "supabase postgres account" } } }, { "parameters": { "jsCode": "return $input.all().map(item => {\n const json = item.json;\n const escape = (str) => str ? str.replace(/'/g, \"''\") : '';\n \n const post_id = escape(json.post_id);\n const title = escape(json.title);\n const url = escape(json.url);\n const author = escape(json.author);\n const content = escape(json.content);\n const character_id = json.character_id ? `'${json.character_id}'` : 'NULL';\n\n const query = `INSERT INTO character_bazaar_posts (post_id, title, url, author, content, post_status, character_id)\nVALUES ('${post_id}', '${title}', '${url}', '${author}', '${content}', 'new', ${character_id})\nON CONFLICT (post_id) DO UPDATE SET\ntitle = EXCLUDED.title,\nurl = EXCLUDED.url,\nauthor = EXCLUDED.author,\ncontent = EXCLUDED.content,\npost_status = 'updated',\ncharacter_id = EXCLUDED.character_id;`;\n\n return {\n json: {\n ...json,\n query\n }\n };\n});" }, "id": "Routing", "name": "Routing", "position": [ 2464, -24 ], "type": "n8n-nodes-base.code", "typeVersion": 2 }, { "parameters": { "operation": "executeQuery", "query": "={{ $json.query }}", "options": {} }, "id": "Store Results", "name": "Store Results", "position": [ 2688, -24 ], "type": "n8n-nodes-base.postgres", "typeVersion": 2.4, "credentials": { "postgres": { "id": "dsnKfvOBMkgU21Lt", "name": "supabase postgres account" } } }, { "parameters": { "conditions": { "boolean": [ { "value1": "={{ $json.skillq_url && !$json.character_id }}", "value2": true } ] } }, "id": "If SkillQ Repair", "name": "If SkillQ Repair", "type": "n8n-nodes-base.if", "typeVersion": 1, "position": [ 896, -24 ] }, { "parameters": { "url": "={{ $json.skillq_url }}", "options": {} }, "id": "Scrape SkillQ", "name": "Scrape SkillQ", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.1, "position": [ 1120, -24 ], "onError": "continueRegularOutput" }, { "parameters": { "jsCode": "const items = $input.all();\n\nreturn items.map(item => {\n // HTML should be in skillq_html (from Scrape SkillQ) or data/body\n const html = item.json.skillq_html || item.json.data || item.json.body;\n \n let charId = null;\n let error = null;\n \n try {\n if (html && typeof html === 'string') {\n const match = html.match(/images\\.evetech\\.net\\/characters\\/(\\d+)\\//);\n if (match) {\n charId = match[1];\n } else {\n error = \"Could not find character ID in SkillQ page\";\n }\n } else {\n error = \"Invalid or empty response from SkillQ\";\n }\n } catch (e) {\n error = \"Error parsing SkillQ response: \" + e.message;\n }\n \n return {\n json: {\n ...item.json, // Keep all original data (merged from context)\n character_id: charId || item.json.character_id,\n scraping_error: error\n }\n };\n});" }, "id": "Parse Scraped ID", "name": "Parse Scraped ID", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1568, -96 ] }, { "parameters": { "jsCode": "let scraped = [];\ntry {\n // Try to get items from Parse Scraped ID\n // This will throw if the node hasn't executed (e.g. no items went to True branch)\n scraped = $('Parse Scraped ID').all();\n} catch (e) {\n // Node didn't execute, which is fine\n scraped = [];\n}\n\nconst skipped = $('If SkillQ Repair').all(1); // Get False branch items\n\nreturn [...scraped, ...skipped];" }, "id": "Merge Manual", "name": "Merge Manual", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1792, -24 ] }, { "parameters": { "mode": "combine", "combineBy": "combineByPosition", "options": {} }, "id": "Merge Context", "name": "Merge Context", "type": "n8n-nodes-base.merge", "typeVersion": 3, "position": [ 1344, -96 ] } ], "connections": { "Extract Character ID": { "main": [ [ { "node": "If SkillQ Repair", "type": "main", "index": 0 } ] ] }, "Filter WTS": { "main": [ [ { "index": 0, "node": "Extract Character ID", "type": "main" } ] ] }, "Lookup Posts": { "main": [ [ { "index": 0, "node": "Routing", "type": "main" } ] ] }, "Prepare ID List": { "main": [ [ { "index": 0, "node": "Lookup Posts", "type": "main" } ] ] }, "RSS Feed Read": { "main": [ [ { "index": 0, "node": "Filter WTS", "type": "main" } ] ] }, "Routing": { "main": [ [ { "index": 0, "node": "Store Results", "type": "main" } ] ] }, "Schedule Trigger": { "main": [ [ { "index": 0, "node": "RSS Feed Read", "type": "main" } ] ] }, "If SkillQ Repair": { "main": [ [ { "node": "Scrape SkillQ", "type": "main", "index": 0 }, { "node": "Merge Context", "type": "main", "index": 0 } ], [ { "node": "Merge Manual", "type": "main", "index": 0 } ] ] }, "Parse Scraped ID": { "main": [ [ { "node": "Merge Manual", "type": "main", "index": 0 } ] ] }, "Merge Manual": { "main": [ [ { "node": "Prepare ID List", "type": "main", "index": 0 } ] ] }, "Scrape SkillQ": { "main": [ [ { "node": "Merge Context", "type": "main", "index": 1 } ] ] }, "Merge Context": { "main": [ [ { "node": "Parse Scraped ID", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "saveDataErrorExecution": "all", "saveDataSuccessExecution": "all", "saveManualExecutions": true, "saveExecutionProgress": true, "callerPolicy": "workflowsFromSameOwner", "availableInMCP": false }, "triggerCount": 0, "versionId": "51b5294b-1a43-4f17-a9e2-440a026e24fd", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", "projectName": "Ben W ", "personalEmail": "admin@ben.io" }, "parentFolderId": "HWgaFb7kLF649L7l", "isArchived": false }