init after v1 corruption
This commit is contained in:
829
workflows/2tDusEAKIJ6bwDl2.json
Normal file
829
workflows/2tDusEAKIJ6bwDl2.json
Normal file
@@ -0,0 +1,829 @@
|
||||
{
|
||||
"id": "2tDusEAKIJ6bwDl2",
|
||||
"name": "EVE Character Bazar Monitor",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "minutes",
|
||||
"minutesInterval": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "Schedule Trigger",
|
||||
"name": "Schedule Trigger",
|
||||
"position": [
|
||||
2464,
|
||||
16
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return $input.all().map(item => {\n return {\n json: {\n ...item.json,\n // Ensure character_id is treated as null if missing/empty\n character_id: item.json.character_id || null\n }\n };\n});"
|
||||
},
|
||||
"id": "Check ID Code",
|
||||
"name": "Check ID Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2912,
|
||||
-80
|
||||
],
|
||||
"notes": "Debug: Check logic"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{ $json.character_id ? 'https://evewho.com/api/character/' + $json.character_id : 'http://skip-verification' }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Verify Character",
|
||||
"name": "Verify Character",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
3360,
|
||||
-8
|
||||
],
|
||||
"continueOnFail": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nreturn items.map(item => {\n const eveData = item.json.data; // Response from EveWho\n const compliance = {\n status: 'compliant',\n issues: []\n };\n\n if (!eveData) {\n // If no EveWho data (e.g. no character ID found or API failed), we can't verify mechanics.\n // But we can still let the AI check the text.\n compliance.status = 'manual_review';\n compliance.issues.push('No character ID found or verification failed');\n } else {\n // Check Wallet Balance (must be positive, but API doesn't show wallet)\n // Check Kill Rights (must be 0)\n // EveWho API returns: { info: { kill_rights: ... }, ... }\n // Note: EveWho API structure might vary. Assuming standard ESI-like or EveWho structure.\n \n // Actually, EveWho API is often just scraping or basic info.\n // Let's assume we check what we can.\n \n if (eveData.kill_rights && eveData.kill_rights > 0) {\n compliance.status = 'non_compliant';\n compliance.issues.push('Character has kill rights');\n }\n \n // Security Status check?\n if (eveData.sec_status && eveData.sec_status < -2.0) {\n // compliance.issues.push('Low security status');\n }\n }\n\n item.json.compliance_check = compliance;\n return item;\n});"
|
||||
},
|
||||
"id": "Compliance Logic",
|
||||
"name": "Compliance Logic",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
3584,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nreturn items.map(item => {\n const json = item.json;\n const newItemJson = { ...json }; // Clone to avoid mutation issues\n\n // Ensure post_id is preserved and valid\n if (!newItemJson.post_id) {\n // Try to recover from other fields if possible, or generate debug ID\n newItemJson.post_id = json.id || json.guid || (\"MISSING_\" + Math.random().toString(36).substring(7));\n }\n\n // Map AI output to status/issues\n if (newItemJson.output) {\n let aiData = {};\n if (typeof newItemJson.output === 'string') {\n try {\n aiData = JSON.parse(newItemJson.output);\n } catch (e) {\n aiData = { compliance_status: 'error', compliance_issues: ['Invalid JSON from AI'] };\n }\n } else if (typeof newItemJson.output === 'object') {\n aiData = newItemJson.output;\n }\n \n newItemJson.post_status = aiData.compliance_status || 'pending_manual_review';\n \n const issues = aiData.compliance_issues || [];\n newItemJson.compliance_issues = { issues: Array.isArray(issues) ? issues : [issues] };\n \n if (aiData.character_name) {\n newItemJson.character_name = aiData.character_name;\n }\n } else if (newItemJson.post_status) {\n // Bypass items (already have status)\n if (!newItemJson.compliance_issues) {\n newItemJson.compliance_issues = { issues: [] };\n }\n } else {\n // Fallback\n newItemJson.post_status = 'pending_manual_review';\n newItemJson.compliance_issues = { issues: ['No status determined'] };\n }\n\n return { json: newItemJson, pairedItem: item.pairedItem };\n});"
|
||||
},
|
||||
"id": "Prepare Data",
|
||||
"name": "Prepare Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
4608,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=You are a Compliance Officer for the EVE Online Character Bazaar.\nAnalyze the following forum post content.\nRules:\n1. Wallet balance must be positive.\n2. Kill rights must be disclosed (usually 'No kill rights').\n3. Jump clones must be disclosed (location and implants).\n4. Character location must be disclosed.\n\nReturn ONLY a JSON object with this structure:\n{\n \"compliance_status\": \"compliant\" | \"non_compliant\",\n \"compliance_issues\": [\"issue 1\", \"issue 2\"],\n \"character_name\": \"extracted name if found, else null\"\n}\nIf compliant, 'compliance_issues' must be an empty array.\nDo not include markdown formatting (```json). Just the raw JSON string.\n\nPost content is below\n--------------------------\n\n{{ $json.content }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "AI Compliance Agent",
|
||||
"name": "AI Compliance Agent",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 1.7,
|
||||
"position": [
|
||||
4032,
|
||||
-400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "z-ai/glm-4.6",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
4104,
|
||||
-176
|
||||
],
|
||||
"id": "98305d4e-91f6-4ca5-9883-326c4b4941d2",
|
||||
"name": "OpenRouter Chat Model",
|
||||
"credentials": {
|
||||
"openRouterApi": {
|
||||
"id": "ERgv4rlSLgq8fsK9",
|
||||
"name": "OpenRouter account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nreturn items.map(item => {\n // This node was previously extracting topic IDs from GUID/Link.\n // We want to keep that behavior for the \"Lookup Posts\" node which uses post_id (topic ID).\n \n const guid = item.json.guid || item.json.link || \"\";\n const match = guid.match(/topic[-/](\\d+)/) || guid.match(/\\/t\\/[^\\/]+\\/(\\d+)/);\n \n if (match) {\n item.json.post_id = match[1];\n }\n \n // Ensure 'ids' field is NOT overwritten here if it's meant for Character IDs later.\n // But wait, 'Prepare ID List' is BEFORE 'Lookup Posts'.\n // And 'Extract Character ID' is BEFORE 'Check ID Code'.\n \n // Let's check the workflow flow again.\n // RSS -> Filter WTS -> Extract Character ID -> Check ID Code -> Verify Character\n // RSS -> Filter WTS -> Prepare ID List -> Lookup Posts\n \n // Ah! There are PARALLEL branches!\n \n return item;\n});"
|
||||
},
|
||||
"id": "Prepare ID List",
|
||||
"name": "Prepare ID List",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2016,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return $input.all();"
|
||||
},
|
||||
"id": "Filter New/Updated",
|
||||
"name": "Filter New/Updated",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2688,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "1",
|
||||
"leftValue": "={{ $json.post_status }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "isNotEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "Check Pre-Filter",
|
||||
"name": "Check Pre-Filter",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
3808,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "1",
|
||||
"leftValue": "={{ $json.extracted_char_id }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "isNotEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "Split by ID",
|
||||
"name": "Split by ID",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
3136,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $('Prepare ID List').all();\nconst lookupResults = $input.all();\n\nreturn items.map(item => {\n const postId = item.json.post_id;\n // Ensure we handle potential type mismatches (string vs number)\n const match = lookupResults.find(r => String(r.json.post_id) === String(postId));\n \n item.json.exists = !!match;\n if (match) {\n item.json.db_status = match.json.post_status;\n item.json.db_compliance_issues = match.json.compliance_issues;\n item.json.last_reviewed_at = match.json.last_reviewed_at;\n }\n \n return item;\n});"
|
||||
},
|
||||
"id": "9d0240b8-2a28-46a1-828d-75bcc0e1fe89",
|
||||
"name": "Merge Manual",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2464,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const aiResults = $input.all(0);\nconst bypassItems = $input.all(1);\nconst originalAiItems = $input.all(2);\n\n// Merge AI results with originals to restore fields\nconst mergedAiItems = originalAiItems.map((item, index) => {\n // Assuming 1-to-1 order with AI results\n const aiResult = aiResults[index]; \n if (aiResult) {\n // Copy AI output fields to original item\n item.json.output = aiResult.json.output;\n item.json.text = aiResult.json.text;\n }\n return item;\n});\n\n// Combine with bypass items\nreturn [...bypassItems, ...mergedAiItems];"
|
||||
},
|
||||
"id": "b9a2804d-fc92-492c-8855-f94f4cb180ed",
|
||||
"name": "Merge AI Data Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
4384,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f1d2a3f6-7d92-46e5-b192-b1df531581a6",
|
||||
"name": "Pass Through",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
4096,
|
||||
112
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst newItems = [];\nfor (const item of items) {\n const json = item.json;\n // Strict check for post_id\n if (json.post_id && json.post_id !== \"null\" && String(json.post_id).trim() !== \"\") {\n newItems.push({\n json: {\n id: json.id, // Preserve DB ID for update logic\n post_id: String(json.post_id),\n post_status: json.post_status || 'pending_manual_review',\n compliance_issues: json.compliance_issues || { issues: [] },\n last_reviewed_at: new Date().toISOString()\n }\n });\n }\n}\nreturn newItems;"
|
||||
},
|
||||
"id": "4dd8d766-7b16-4d4f-b84a-07954c90c42d",
|
||||
"name": "Clean Data Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
4832,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "=SELECT * FROM character_bazaar_posts WHERE post_id IN ('{{ $json.post_id }}')",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Lookup Posts",
|
||||
"name": "Lookup Posts",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.4,
|
||||
"position": [
|
||||
2240,
|
||||
-176
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "5ENroY8sSuOXtF9l",
|
||||
"name": "n8n-games"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "upsert",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "character_bazaar_posts"
|
||||
},
|
||||
"columns": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {
|
||||
"character_name": "={{ $json.character_name }}",
|
||||
"compliance_issues": "={{ $json.compliance_issues }}",
|
||||
"last_reviewed_at": "={{ $now }}",
|
||||
"post_id": "={{ $json.post_id }}",
|
||||
"post_status": "={{ $json.post_status }}"
|
||||
}
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "Store Results",
|
||||
"name": "Store Results",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.4,
|
||||
"position": [
|
||||
5056,
|
||||
-80
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "5ENroY8sSuOXtF9l",
|
||||
"name": "n8n-games"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{ $json.link }}.rss",
|
||||
"options": {}
|
||||
},
|
||||
"id": "8417128a-ba4f-48a0-92a7-c5e7e54db8e8",
|
||||
"name": "Fetch Forum Post",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"position": [
|
||||
0,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\n\nreturn items.map(item => {\n const rss = item.json.data;\n \n // DEBUG: Log the actual structure we're working with\n console.log('=== Parse Forum Post DEBUG ===');\n console.log('Raw data type:', typeof rss);\n console.log('Raw data length:', rss ? rss.length : 'null');\n console.log('First 500 chars:', rss ? rss.substring(0, 500) : 'null');\n \n // Extract Creator - try multiple patterns\n let creator = null;\n const creatorPatterns = [\n /<dc:creator><!\\[CDATA\\[(.*?)\\]\\]><\\/dc:creator>/,\n /<dc:creator>(.*?)<\\/dc:creator>/,\n /<span class=\"creator\"[^>]*>.*?<span[^>]* itemprop=\"name\">(.*?)<\\/span>/,\n /itemprop=\"author\"[^>]*>.*?<span[^>]* itemprop=\"name\">(.*?)<\\/span>/\n ];\n \n for (const pattern of creatorPatterns) {\n const match = rss.match(pattern);\n if (match) {\n creator = match[1];\n console.log('Creator found with pattern:', pattern.toString(), 'Value:', creator);\n break;\n }\n }\n \n // Extract Description - try multiple patterns\n let description = \"\";\n const descPatterns = [\n /<description><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/description>/,\n /<description>([\\s\\S]*?)<\\/description>/,\n /<div class=\"post\"[^>]* itemprop=\"text\">([\\s\\S]*?)<\\/div>/\n ];\n \n for (const pattern of descPatterns) {\n const match = rss.match(pattern);\n if (match) {\n description = match[1];\n console.log('Description found with pattern:', pattern.toString(), 'Length:', description.length);\n break;\n }\n }\n \n // Clean HTML entities\n const cleanDescription = description\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\");\n \n // Extract Qsna IDs - search in both raw and cleaned content\n const qsnaRegex = /qsna\\.eu\\/eve\\/characters\\/(\\d+)/g;\n const qsnaMatchesRaw = [...rss.matchAll(qsnaRegex)];\n const qsnaMatchesClean = [...cleanDescription.matchAll(qsnaRegex)];\n \n const qsnaIds = [...new Set([\n ...qsnaMatchesRaw.map(m => m[1]),\n ...qsnaMatchesClean.map(m => m[1])\n ])];\n \n console.log('Qsna matches in raw:', qsnaMatchesRaw.length);\n console.log('Qsna matches in clean:', qsnaMatchesClean.length);\n console.log('Final Qsna IDs:', qsnaIds);\n \n // Extract SkillQ Links - search in both raw and cleaned content\n const skillqRegex = /https?:\\/\\/skillq\\.net\\/char\\/[^\\/\\s]+\\/share\\/[a-f0-9\\-]+/g;\n const skillqMatchesRaw = rss.match(skillqRegex) || [];\n const skillqMatchesClean = cleanDescription.match(skillqRegex) || [];\n \n const skillqLinks = [...new Set([...skillqMatchesRaw, ...skillqMatchesClean])];\n \n console.log('SkillQ matches in raw:', skillqMatchesRaw.length);\n console.log('SkillQ matches in clean:', skillqMatchesClean.length);\n console.log('Final SkillQ links:', skillqLinks);\n \n item.json.creator_name = creator;\n item.json.qsna_ids = qsnaIds;\n item.json.skillq_links = skillqLinks;\n \n console.log('=== END DEBUG ===');\n \n return item;\n});"
|
||||
},
|
||||
"id": "ad7ed8fd-8460-4659-b9e2-ed0c50483b6c",
|
||||
"name": "Parse Forum Post",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
224,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://esi.evetech.net/latest/universe/ids/",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify([$json.creator_name]) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "b3cd5b60-30d6-4089-b51f-3ccad131d548",
|
||||
"name": "Resolve Creator ID",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"position": [
|
||||
448,
|
||||
-296
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst result = [];\n\n// DEBUG: Log input data structure\nconsole.log('=== Split SkillQ Links DEBUG ===');\nconsole.log('Input items length:', items.length);\nif (items.length > 0) {\n console.log('First item keys:', Object.keys(items[0].json));\n console.log('First item skillq_links:', items[0].json.skillq_links);\n console.log('First item post_id:', items[0].json.post_id);\n console.log('First item temp_id:', items[0].json.temp_id);\n}\n\nfor (const item of items) {\n const links = item.json.skillq_links || [];\n // Add temp_id to group back later\n item.json.temp_id = item.json.post_id || Math.random().toString(36).substring(7);\n\n console.log('Processing item with temp_id:', item.json.temp_id, 'links count:', links.length);\n\n if (links.length === 0) {\n item.json.scrape_skipped = true;\n result.push(item);\n console.log('No links found, marking as scrape_skipped');\n } else {\n console.log('Found links:', links);\n for (const link of links) {\n const newItem = {\n json: {\n ...item.json,\n skillq_url_to_scrape: link,\n scrape_skipped: false\n }\n };\n result.push(newItem);\n console.log('Created item for URL:', link);\n }\n }\n}\n\nconsole.log('Total output items:', result.length);\nconsole.log('=== END Split SkillQ Links DEBUG ===');\nreturn result;"
|
||||
},
|
||||
"id": "21333d91-85ba-4baa-a482-ec59241a076f",
|
||||
"name": "Split SkillQ Links",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
896,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "1",
|
||||
"leftValue": "={{ $json.scrape_skipped }}.toString()",
|
||||
"rightValue": "true",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "contains"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "67ec4026-ee76-4818-9018-1a289caefb76",
|
||||
"name": "If Scrape Needed",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
1120,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{ $json.skillq_url_to_scrape }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "36697258-f606-436e-a173-74577c77f502",
|
||||
"name": "Scrape SkillQ",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"position": [
|
||||
1344,
|
||||
-296
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst groups = {};\n\nfor (const item of items) {\n const id = item.json.temp_id;\n if (!groups[id]) {\n groups[id] = { ...item.json, scraped_ids: [], skillq_404: false };\n }\n \n if (!item.json.scrape_skipped) {\n const html = item.json.data || \"\";\n \n // Check for 404\n if (html.includes('<meta name=\"title\" content=\"SkillQ.net - 404\">')) {\n groups[id].skillq_404 = true;\n } else {\n // Extract ID\n const idMatch = html.match(/images\\.evetech\\.net\\/characters\\/(\\d+)\\/portrait/);\n if (idMatch) {\n groups[id].scraped_ids.push(idMatch[1]);\n }\n }\n }\n}\n\nreturn Object.values(groups).map(json => ({ json }));"
|
||||
},
|
||||
"id": "69371473-c0d1-400a-b7cc-6a3d4f996f87",
|
||||
"name": "Aggregate SkillQ",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1568,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\n\nreturn items.map(item => {\n const creatorId = item.json.characters ? item.json.characters[0].id : null;\n const creatorName = item.json.characters ? item.json.characters[0].name : item.json.creator_name;\n const qsnaIds = item.json.qsna_ids || [];\n const scrapedIds = item.json.scraped_ids || [];\n const linkIds = [...new Set([...qsnaIds, ...scrapedIds])]; // Unique IDs\n const title = (item.json.title || \"\").toUpperCase();\n \n let characterId = creatorId;\n let characterName = creatorName;\n let status = \"compliant\"; // Default\n \n // Compliance Logic\n if (item.json.skillq_404) {\n status = \"non_compliant_skillq_404\";\n } else if (linkIds.length > 0) {\n // Check if Creator ID matches any Link ID\n const match = linkIds.some(id => String(id) === String(creatorId));\n if (match) {\n status = \"compliant\";\n characterId = creatorId; // Confirmed\n } else {\n status = \"non_compliant_mismatch\";\n item.json.mismatch_ids = linkIds;\n }\n } else {\n // No Links\n if (title.includes(\"WTS\")) {\n status = \"non_compliant_missing_link\";\n } else if (title.includes(\"WTB\")) {\n status = \"ignored_wtb\";\n }\n }\n \n item.json.character_id = characterId;\n item.json.character_name = characterName;\n item.json.compliance_status_initial = status;\n \n return item;\n});"
|
||||
},
|
||||
"id": "91757929-87e4-480c-b2ca-6addf4e26542",
|
||||
"name": "Consolidate Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1792,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const creators = $input.all(0).map(i => i.json);\nconst posts = $input.all(1).map(i => i.json);\n\n// DEBUG: Log input structures\nconsole.log('=== Merge Creator Code DEBUG ===');\nconsole.log('Creators input length:', creators.length);\nconsole.log('Posts input length:', posts.length);\nconsole.log('First creator keys:', creators[0] ? Object.keys(creators[0]) : 'null');\nconsole.log('First post keys:', posts[0] ? Object.keys(posts[0]) : 'null');\nconsole.log('First post skillq_links:', posts[0] ? posts[0].skillq_links : 'null');\nconsole.log('First creator skillq_links:', creators[0] ? creators[0].skillq_links : 'null');\n\nconst result = posts.map((post, index) => {\n const creator = creators[index] || {};\n \n // CRITICAL FIX: Preserve ALL post data first, then add creator data\n // This ensures skillq_links and other post fields are not overwritten\n const merged = {\n json: {\n ...post, // Post data takes priority (includes skillq_links, creator_name, qsna_ids, etc.)\n ...creator // Creator data added (only characters array, should not overwrite existing fields)\n }\n };\n \n // DEBUG: Log merge result\n if (index === 0) {\n console.log('Merged result keys:', Object.keys(merged.json));\n console.log('Merged skillq_links:', merged.json.skillq_links);\n console.log('Merged creator_name:', merged.json.creator_name);\n console.log('Merged qsna_ids:', merged.json.qsna_ids);\n }\n \n return merged;\n});\n\nconsole.log('=== END Merge Creator Code DEBUG ===');\nreturn result;"
|
||||
},
|
||||
"id": "dac9a7f5-bff6-48de-a8a2-8a9222cb0b8a",
|
||||
"name": "Merge Creator Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
672,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM character_bazaar_posts WHERE post_status IN ('new', 'updated') ORDER BY last_reviewed_at ASC NULLS FIRST LIMIT 10;",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "Poll Pending Posts",
|
||||
"name": "Poll Pending Posts",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2688,
|
||||
16
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "5ENroY8sSuOXtF9l",
|
||||
"name": "n8n-games"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Check ID Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split by ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Verify Character": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compliance Logic",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Compliance Logic": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Pre-Filter",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"OpenRouter Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "AI Compliance Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter New/Updated": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check ID Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check Pre-Filter": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge AI Data Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Pass Through",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "AI Compliance Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split by ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compliance Logic",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Verify Character",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Manual": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter New/Updated",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge AI Data Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"AI Compliance Agent": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge AI Data Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Pass Through": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge AI Data Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Clean Data Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare ID List": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Lookup Posts",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Lookup Posts": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Manual",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Clean Data Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Store Results",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Fetch Forum Post": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Forum Post",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Forum Post": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Resolve Creator ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge Creator Code",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split SkillQ Links": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If Scrape Needed",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If Scrape Needed": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Scrape SkillQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Aggregate SkillQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Scrape SkillQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Aggregate SkillQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Aggregate SkillQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Consolidate Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Consolidate Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare ID List",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Resolve Creator ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Creator Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Creator Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split SkillQ Links",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Poll Pending Posts",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Poll Pending Posts": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check ID Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveManualExecutions": true,
|
||||
"saveExecutionProgress": true,
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "87cd1f48-d40b-4a15-81e0-0c4e17058983",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "HWgaFb7kLF649L7l",
|
||||
"isArchived": false
|
||||
}
|
||||
Reference in New Issue
Block a user