From 3298dd64f235e9f187c32a321240303301bc610b Mon Sep 17 00:00:00 2001 From: Ben W Date: Thu, 5 Feb 2026 21:59:40 +0000 Subject: [PATCH] update workflows --- credential_stubs/QrDR9b0GKJWk2woe.json | 16 + ...gYndzFIHJAf.json => Qtd00hn8bJt3xzhU.json} | 9 +- credential_stubs/TE5lVqfBn06ypxIU.json | 16 - ...w7hDLHB7DYY.json => UDrpaip2ru9PZoaJ.json} | 10 +- ...RMx4KoFwGgl.json => sd09ohUHtJi2kPmv.json} | 10 +- credential_stubs/sxSUdecXdMfKPuTu.json | 2 +- ...F30uXa32ly3.json => uT2JnQiHHw0pJIo2.json} | 9 +- credential_stubs/uYjjcswcBGrqXgpk.json | 19 - folders.json | 8 + tags.json | 24 +- workflows/-VWKaOsZlm3sb4OnTw7du.json | 769 ++++++++++++++++++ workflows/21kEHGQ_E07Uq7irNExUD.json | 566 +++++++++++++ workflows/Pv-qCW0IA2IxNGm-fPXxh.json | 21 + workflows/WkAdUd9jXTtPagGO.json | 8 +- workflows/cPWZKfrHOUSUZjIp.json | 66 +- workflows/kQIViyYKgY1d4E99.json | 236 ++++++ workflows/kRZyX9H2uDHHncpE.json | 502 +++++++----- workflows/qR33gCw5iScfnj9s.json | 360 +++++--- workflows/xXUnt2hL2FKxzOhBnkd3Z.json | 693 +++++++++++----- 19 files changed, 2726 insertions(+), 618 deletions(-) create mode 100644 credential_stubs/QrDR9b0GKJWk2woe.json rename credential_stubs/{9UFJNgYndzFIHJAf.json => Qtd00hn8bJt3xzhU.json} (65%) delete mode 100644 credential_stubs/TE5lVqfBn06ypxIU.json rename credential_stubs/{SItxcw7hDLHB7DYY.json => UDrpaip2ru9PZoaJ.json} (61%) rename credential_stubs/{QRBx9RMx4KoFwGgl.json => sd09ohUHtJi2kPmv.json} (62%) rename credential_stubs/{7dAOuF30uXa32ly3.json => uT2JnQiHHw0pJIo2.json} (64%) delete mode 100644 credential_stubs/uYjjcswcBGrqXgpk.json create mode 100644 workflows/-VWKaOsZlm3sb4OnTw7du.json create mode 100644 workflows/21kEHGQ_E07Uq7irNExUD.json create mode 100644 workflows/Pv-qCW0IA2IxNGm-fPXxh.json create mode 100644 workflows/kQIViyYKgY1d4E99.json diff --git a/credential_stubs/QrDR9b0GKJWk2woe.json b/credential_stubs/QrDR9b0GKJWk2woe.json new file mode 100644 index 0000000..2c6a299 --- /dev/null +++ b/credential_stubs/QrDR9b0GKJWk2woe.json @@ -0,0 +1,16 @@ +{ + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com", + "type": "microsoftOutlookOAuth2Api", + "data": { + "clientId": "", + "clientSecret": "" + }, + "ownedBy": { + "type": "personal", + "projectId": "FeLO36wNUAcn61Wj", + "projectName": "Ben W ", + "personalEmail": "admin@ben.io" + }, + "isGlobal": false +} \ No newline at end of file diff --git a/credential_stubs/9UFJNgYndzFIHJAf.json b/credential_stubs/Qtd00hn8bJt3xzhU.json similarity index 65% rename from credential_stubs/9UFJNgYndzFIHJAf.json rename to credential_stubs/Qtd00hn8bJt3xzhU.json index e41443a..5eb942e 100644 --- a/credential_stubs/9UFJNgYndzFIHJAf.json +++ b/credential_stubs/Qtd00hn8bJt3xzhU.json @@ -1,10 +1,9 @@ { - "id": "9UFJNgYndzFIHJAf", - "name": "Nano-GPT", - "type": "openAiApi", + "id": "Qtd00hn8bJt3xzhU", + "name": "Qdrant Local", + "type": "qdrantApi", "data": { - "apiKey": "", - "url": "" + "qdrantUrl": "" }, "ownedBy": { "type": "personal", diff --git a/credential_stubs/TE5lVqfBn06ypxIU.json b/credential_stubs/TE5lVqfBn06ypxIU.json deleted file mode 100644 index 60d51ad..0000000 --- a/credential_stubs/TE5lVqfBn06ypxIU.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "TE5lVqfBn06ypxIU", - "name": "cerebras.ai", - "type": "openAiApi", - "data": { - "apiKey": "", - "url": "" - }, - "ownedBy": { - "type": "personal", - "projectId": "FeLO36wNUAcn61Wj", - "projectName": "Ben W ", - "personalEmail": "admin@ben.io" - }, - "isGlobal": false -} \ No newline at end of file diff --git a/credential_stubs/SItxcw7hDLHB7DYY.json b/credential_stubs/UDrpaip2ru9PZoaJ.json similarity index 61% rename from credential_stubs/SItxcw7hDLHB7DYY.json rename to credential_stubs/UDrpaip2ru9PZoaJ.json index f753435..12081b2 100644 --- a/credential_stubs/SItxcw7hDLHB7DYY.json +++ b/credential_stubs/UDrpaip2ru9PZoaJ.json @@ -1,10 +1,10 @@ { - "id": "SItxcw7hDLHB7DYY", - "name": "Chutes AI", - "type": "openAiApi", + "id": "UDrpaip2ru9PZoaJ", + "name": "rizon-gmail", + "type": "gmailOAuth2", "data": { - "apiKey": "", - "url": "" + "clientId": "", + "clientSecret": "" }, "ownedBy": { "type": "personal", diff --git a/credential_stubs/QRBx9RMx4KoFwGgl.json b/credential_stubs/sd09ohUHtJi2kPmv.json similarity index 62% rename from credential_stubs/QRBx9RMx4KoFwGgl.json rename to credential_stubs/sd09ohUHtJi2kPmv.json index 71cba17..6e24c7f 100644 --- a/credential_stubs/QRBx9RMx4KoFwGgl.json +++ b/credential_stubs/sd09ohUHtJi2kPmv.json @@ -1,10 +1,10 @@ { - "id": "QRBx9RMx4KoFwGgl", - "name": "Nvidia account", - "type": "openAiApi", + "id": "sd09ohUHtJi2kPmv", + "name": "GitHub - b3nw", + "type": "githubApi", "data": { - "apiKey": "", - "url": "" + "user": "", + "accessToken": "" }, "ownedBy": { "type": "personal", diff --git a/credential_stubs/sxSUdecXdMfKPuTu.json b/credential_stubs/sxSUdecXdMfKPuTu.json index 16583aa..f2b6de1 100644 --- a/credential_stubs/sxSUdecXdMfKPuTu.json +++ b/credential_stubs/sxSUdecXdMfKPuTu.json @@ -1,6 +1,6 @@ { "id": "sxSUdecXdMfKPuTu", - "name": "llm-proxy.ben.io", + "name": "llm-proxy.ext.ben.io", "type": "openAiApi", "data": { "apiKey": "", diff --git a/credential_stubs/7dAOuF30uXa32ly3.json b/credential_stubs/uT2JnQiHHw0pJIo2.json similarity index 64% rename from credential_stubs/7dAOuF30uXa32ly3.json rename to credential_stubs/uT2JnQiHHw0pJIo2.json index 808ff32..38470bf 100644 --- a/credential_stubs/7dAOuF30uXa32ly3.json +++ b/credential_stubs/uT2JnQiHHw0pJIo2.json @@ -1,10 +1,9 @@ { - "id": "7dAOuF30uXa32ly3", - "name": "ollama local", - "type": "openAiApi", + "id": "uT2JnQiHHw0pJIo2", + "name": "Mistral Cloud account", + "type": "mistralCloudApi", "data": { - "apiKey": "", - "url": "" + "apiKey": "" }, "ownedBy": { "type": "personal", diff --git a/credential_stubs/uYjjcswcBGrqXgpk.json b/credential_stubs/uYjjcswcBGrqXgpk.json deleted file mode 100644 index 244bc17..0000000 --- a/credential_stubs/uYjjcswcBGrqXgpk.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "id": "uYjjcswcBGrqXgpk", - "name": "openrouter", - "type": "openAiApi", - "data": { - "apiKey": "", - "url": "", - "header": true, - "headerName": "", - "headerValue": "" - }, - "ownedBy": { - "type": "personal", - "projectId": "FeLO36wNUAcn61Wj", - "projectName": "Ben W ", - "personalEmail": "admin@ben.io" - }, - "isGlobal": false -} \ No newline at end of file diff --git a/folders.json b/folders.json index 8b2bb5b..e3b84e5 100644 --- a/folders.json +++ b/folders.json @@ -55,6 +55,14 @@ "homeProjectId": "FeLO36wNUAcn61Wj", "createdAt": "2025-12-21T15:55:27.361Z", "updatedAt": "2025-12-21T15:55:27.361Z" + }, + { + "id": "caxOx4NfY2vHcoNv", + "name": "Rizon", + "parentFolderId": null, + "homeProjectId": "FeLO36wNUAcn61Wj", + "createdAt": "2026-01-28T14:10:15.298Z", + "updatedAt": "2026-01-28T14:10:15.298Z" } ] } \ No newline at end of file diff --git a/tags.json b/tags.json index ff05575..ea52429 100644 --- a/tags.json +++ b/tags.json @@ -42,10 +42,6 @@ "workflowId": "7kAZyLHOpYKg4riN", "tagId": "ct0Rtzpu15B497av" }, - { - "workflowId": "cPWZKfrHOUSUZjIp", - "tagId": "FydpKYmttDwoZVAA" - }, { "workflowId": "Z_YHsJaf_pyFQR6e7VuLo", "tagId": "zrmVqhwdDmkuhhaQ" @@ -62,10 +58,6 @@ "workflowId": "J3uKCCbSuQ1fdJkC", "tagId": "FydpKYmttDwoZVAA" }, - { - "workflowId": "WkAdUd9jXTtPagGO", - "tagId": "FydpKYmttDwoZVAA" - }, { "workflowId": "6S41oPplwN1S9Lz0", "tagId": "FydpKYmttDwoZVAA" @@ -74,6 +66,18 @@ "workflowId": "NChjOd3ILEmt0FdyAt8qA", "tagId": "FydpKYmttDwoZVAA" }, + { + "workflowId": "kRZyX9H2uDHHncpE", + "tagId": "FydpKYmttDwoZVAA" + }, + { + "workflowId": "WkAdUd9jXTtPagGO", + "tagId": "FydpKYmttDwoZVAA" + }, + { + "workflowId": "cPWZKfrHOUSUZjIp", + "tagId": "FydpKYmttDwoZVAA" + }, { "workflowId": "xXUnt2hL2FKxzOhBnkd3Z", "tagId": "FydpKYmttDwoZVAA" @@ -81,10 +85,6 @@ { "workflowId": "xXUnt2hL2FKxzOhBnkd3Z", "tagId": "ct0Rtzpu15B497av" - }, - { - "workflowId": "kRZyX9H2uDHHncpE", - "tagId": "FydpKYmttDwoZVAA" } ] } \ No newline at end of file diff --git a/workflows/-VWKaOsZlm3sb4OnTw7du.json b/workflows/-VWKaOsZlm3sb4OnTw7du.json new file mode 100644 index 0000000..a89f496 --- /dev/null +++ b/workflows/-VWKaOsZlm3sb4OnTw7du.json @@ -0,0 +1,769 @@ +{ + "id": "-VWKaOsZlm3sb4OnTw7du", + "name": "Mail Filtering", + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + { + "field": "minutes", + "minutesInterval": 15 + } + ] + } + }, + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.3, + "position": [ + -720, + 312 + ], + "id": "72afcdf5-3f41-42df-944e-116b3aa5149e", + "name": "Schedule Trigger" + }, + { + "parameters": { + "operation": "getAll", + "output": "fields", + "fields": [ + "flag", + "from", + "importance", + "replyTo", + "sender", + "subject", + "toRecipients", + "body", + "categories", + "isRead" + ], + "filtersUI": { + "values": { + "filters": { + "foldersToInclude": [ + "AQMkADAwATMwMAItOGQ1OS0yNWYxLTAwAi0wMAoALgAAA4foSHqzPq5LgyYr5fyXfDwBAMOp3uJuhWlAqL91oN-mo00AAAIBDAAAAA==" + ], + "readStatus": "unread" + } + } + }, + "options": {} + }, + "type": "n8n-nodes-base.microsoftOutlook", + "typeVersion": 2, + "position": [ + -496, + 320 + ], + "id": "70ea01a0-6c6a-4600-8892-53dea755bfe9", + "name": "Get many messages", + "webhookId": "f456d648-ebe8-4842-845a-f4e63f087ce2", + "credentials": { + "microsoftOutlookOAuth2Api": { + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "e9fa4b1b-b120-4661-81bb-9356a02135fe", + "leftValue": "={{ $json.categories }}", + "rightValue": "", + "operator": { + "type": "array", + "operation": "empty", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.filter", + "typeVersion": 2.3, + "position": [ + -272, + 312 + ], + "id": "adf2f862-7fa2-42c4-84cc-01f2bb130b5c", + "name": "Filter" + }, + { + "parameters": { + "options": {} + }, + "type": "n8n-nodes-base.splitInBatches", + "typeVersion": 3, + "position": [ + -48, + 312 + ], + "id": "9649fa03-18d3-4c1f-8004-fe88223bc6f7", + "name": "Loop Over Items" + }, + { + "parameters": {}, + "type": "n8n-nodes-base.noOp", + "name": "Replace Me", + "typeVersion": 1, + "position": [ + 1200, + -52 + ], + "id": "c1114ad0-3434-427b-82c6-21693037278c" + }, + { + "parameters": { + "html": "={{ $('Loop Over Items').item.json.body.content }}", + "options": {} + }, + "type": "n8n-nodes-base.markdown", + "typeVersion": 1, + "position": [ + 176, + -48 + ], + "id": "43eaa2ff-8b3f-48d9-a707-9894a8a60e8f", + "name": "Markdown" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "e47ef059-004e-4271-ba5d-5feef5a657ea", + "name": "subject", + "value": "={{ $json.subject }}", + "type": "string" + }, + { + "id": "8496c9f2-99c8-4434-9dda-baec131b36ab", + "name": "importance", + "value": "={{ $json.importance }}", + "type": "string" + }, + { + "id": "739290db-6b7d-4eff-bdf4-4942befb9a30", + "name": "from", + "value": "={{ $json.sender.emailAddress }}", + "type": "object" + }, + { + "id": "d24a9321-6006-401d-96b4-0513368f7df7", + "name": "sender", + "value": "={{ $json.from.emailAddress }}", + "type": "object" + }, + { + "id": "76379c26-25e6-4088-b631-c7cf48489837", + "name": "body", + "value": "={{ $json.data .replace(/<[^>]*>/g, '') // Remove HTML tags .replace(/\\[(.*?)\\]\\((.*?)\\)/g, '') // Remove Markdown links like [text](link) .replace(/!\\[.*?\\]\\(.*?\\)/g, '') // Remove Markdown images like ![alt](image-link) .replace(/\\|/g, '') // Remove table separators \"|\" .replace(/-{3,}/g, '') // Remove horizontal rule \"---\" .replace(/\\n+/g, ' ') // Remove multiple newlines .replace(/([^\\w\\s.,!?@])/g, '') // Remove special characters except essential ones .replace(/\\s{2,}/g, ' ') // Replace multiple spaces with a single space .trim() // Trim leading/trailing whitespace }}", + "type": "string" + }, + { + "id": "2a39ff4c-bc61-4da0-b1cd-5c5a85ce824e", + "name": "id", + "value": "={{ $json.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 400, + -48 + ], + "id": "417ccbbb-b21d-430d-9bfc-121e78d166d3", + "name": "var mapping" + }, + { + "parameters": { + "inputText": "={{ $json.body }}", + "categories": { + "categories": [ + { + "category": "Childcare & School", + "description": "Identify all correspondence from Tadpoles or Stepping Stone School, specifically including \"Daily Reports,\" \"Snapshots,\" and teacher notes for August (Gus) and Quinn. This also includes enrichment programs like Soccer Shots or Techie Kids Club" + }, + { + "category": "Home & Maintenance", + "description": "Capture emails from PreFix regarding \"Home Maintenance,\" \"HomeHub\" logins, or service requests like \"Assemble Playscape\". Include utility-related alerts from the City of Austin and neighborhood-specific items like \"1415 Dwyce\"" + }, + { + "category": "Finance & Accounts", + "description": "Filter for documents related to HealthEquity (HSA contributions or investment activity), Schwab brokerage accounts, and employee benefits or leave processing from Sedgwick. Include tax documents and insurance updates." + }, + { + "category": "Travel & Reservations", + "description": "Focus on airline communications, specifically American Airlines AAdvantage updates regarding \"mileage expiration\" or flight bookings. This should also cover hotel, rental car, and trip itinerary confirmations." + }, + { + "category": "Health & Wellness", + "description": "Identify appointment reminders and \"Action Requested\" notifications from Form Health or Pediatric Associates of Austin. Include any correspondence related to care team updates or medical follow-ups." + }, + { + "category": "Community & Religious", + "description": "Select emails related to local community organizations or religious centers, such as \"Welcome to Temple Beth Shalom\" or correspondence involving Mallory and Rachel Walter regarding local events" + }, + { + "category": "Shopping & Rewards", + "description": "Organize order confirmations, \"Autoship\" reminders from Chewy, and promotional updates from retailers like Chipotle. This includes tracking notifications from USPS Informed Delivery" + }, + { + "category": "Tech & Support", + "description": "Capture automated security alerts from the Microsoft account team (e.g., \"New app(s) connected\"), system status updates, and professional tool notifications from platforms like n8n or Atlassian." + } + ] + }, + "options": { + "multiClass": true, + "systemPromptTemplate": "Please classify the text provided by the user into one of the following categories: {categories}, and use the provided formatting instructions below. Don't explain, and only output the json." + } + }, + "type": "@n8n/n8n-nodes-langchain.textClassifier", + "typeVersion": 1.1, + "position": [ + 624, + -48 + ], + "id": "5399726f-2bb8-45bc-838c-4c8472ec29e6", + "name": "Text Classifier" + }, + { + "parameters": { + "model": { + "__rl": true, + "value": "antigravity/gemini-2.5-flash-lite", + "mode": "list", + "cachedResultName": "antigravity/gemini-2.5-flash-lite" + }, + "responsesApiEnabled": false, + "options": { + "responseFormat": "json_object" + } + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [ + 624, + 336 + ], + "id": "427926f3-ff5f-449d-800f-d28aa557a8d8", + "name": "OpenAI Chat Model", + "credentials": { + "openAiApi": { + "id": "sxSUdecXdMfKPuTu", + "name": "llm-proxy.ben.io" + } + } + }, + { + "parameters": { + "operation": "update", + "messageId": { + "__rl": true, + "value": "={{ $json.id }}", + "mode": "id" + }, + "updateFields": { + "categories": [ + "Tech & Support" + ] + } + }, + "type": "n8n-nodes-base.microsoftOutlook", + "typeVersion": 2, + "position": [ + 976, + 432 + ], + "id": "fb6afde8-ee15-4fec-8fb9-54573e7585a9", + "name": "Update - Tech", + "webhookId": "58b45295-0db5-4e67-a437-9ba8ede35c13", + "credentials": { + "microsoftOutlookOAuth2Api": { + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com" + } + } + }, + { + "parameters": { + "operation": "update", + "messageId": { + "__rl": true, + "value": "={{ $json.id }}", + "mode": "id" + }, + "updateFields": { + "categories": [ + "Shopping & Rewards" + ] + } + }, + "type": "n8n-nodes-base.microsoftOutlook", + "typeVersion": 2, + "position": [ + 976, + 240 + ], + "id": "bab657d3-f297-461d-af84-5ecaa4f29773", + "name": "Update - Shopping", + "webhookId": "58b45295-0db5-4e67-a437-9ba8ede35c13", + "credentials": { + "microsoftOutlookOAuth2Api": { + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com" + } + } + }, + { + "parameters": { + "operation": "update", + "messageId": { + "__rl": true, + "value": "={{ $json.id }}", + "mode": "id" + }, + "updateFields": { + "categories": [ + "Community & Religious" + ] + } + }, + "type": "n8n-nodes-base.microsoftOutlook", + "typeVersion": 2, + "position": [ + 976, + 48 + ], + "id": "199143aa-46fd-40a1-b3a1-a864430dd194", + "name": "Update - Community", + "webhookId": "58b45295-0db5-4e67-a437-9ba8ede35c13", + "credentials": { + "microsoftOutlookOAuth2Api": { + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com" + } + } + }, + { + "parameters": { + "operation": "update", + "messageId": { + "__rl": true, + "value": "={{ $json.id }}", + "mode": "id" + }, + "updateFields": { + "categories": [ + "Health & Wellness" + ] + } + }, + "type": "n8n-nodes-base.microsoftOutlook", + "typeVersion": 2, + "position": [ + 976, + 624 + ], + "id": "7a37c92e-9d95-4439-819f-ed3e5047e720", + "name": "Update - Health", + "webhookId": "58b45295-0db5-4e67-a437-9ba8ede35c13", + "credentials": { + "microsoftOutlookOAuth2Api": { + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com" + } + } + }, + { + "parameters": { + "operation": "update", + "messageId": { + "__rl": true, + "value": "={{ $json.id }}", + "mode": "id" + }, + "updateFields": { + "categories": [ + "Travel & Reservations" + ] + } + }, + "type": "n8n-nodes-base.microsoftOutlook", + "typeVersion": 2, + "position": [ + 976, + -144 + ], + "id": "0973245d-91cc-4c85-b451-ca94cf076488", + "name": "Update - Travel", + "webhookId": "58b45295-0db5-4e67-a437-9ba8ede35c13", + "credentials": { + "microsoftOutlookOAuth2Api": { + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com" + } + } + }, + { + "parameters": { + "operation": "update", + "messageId": { + "__rl": true, + "value": "={{ $json.id }}", + "mode": "id" + }, + "updateFields": { + "categories": [ + "Finance & Accounts" + ] + } + }, + "type": "n8n-nodes-base.microsoftOutlook", + "typeVersion": 2, + "position": [ + 976, + -336 + ], + "id": "10b8fbc6-37e0-4a62-9fb6-bddeac773532", + "name": "Update - Finance", + "webhookId": "58b45295-0db5-4e67-a437-9ba8ede35c13", + "credentials": { + "microsoftOutlookOAuth2Api": { + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com" + } + } + }, + { + "parameters": { + "operation": "update", + "messageId": { + "__rl": true, + "value": "={{ $json.id }}", + "mode": "id" + }, + "updateFields": { + "categories": [ + "Finance & Accounts" + ] + } + }, + "type": "n8n-nodes-base.microsoftOutlook", + "typeVersion": 2, + "position": [ + 976, + -528 + ], + "id": "173fdbb7-5be2-4915-b194-5ee6fd2bbaef", + "name": "Update - Home", + "webhookId": "58b45295-0db5-4e67-a437-9ba8ede35c13", + "credentials": { + "microsoftOutlookOAuth2Api": { + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com" + } + } + }, + { + "parameters": { + "operation": "update", + "messageId": { + "__rl": true, + "value": "={{ $json.id }}", + "mode": "id" + }, + "updateFields": { + "categories": [ + "Finance & Accounts" + ] + } + }, + "type": "n8n-nodes-base.microsoftOutlook", + "typeVersion": 2, + "position": [ + 976, + -720 + ], + "id": "74d245b0-eecd-4622-8670-d79beba32a12", + "name": "Update - Childcare", + "webhookId": "58b45295-0db5-4e67-a437-9ba8ede35c13", + "credentials": { + "microsoftOutlookOAuth2Api": { + "id": "QrDR9b0GKJWk2woe", + "name": "b3nw@outlook.com" + } + } + } + ], + "connections": { + "Schedule Trigger": { + "main": [ + [ + { + "node": "Get many messages", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get many messages": { + "main": [ + [ + { + "node": "Filter", + "type": "main", + "index": 0 + } + ] + ] + }, + "Filter": { + "main": [ + [ + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + }, + "Loop Over Items": { + "main": [ + [], + [ + { + "node": "Markdown", + "type": "main", + "index": 0 + } + ] + ] + }, + "Replace Me": { + "main": [ + [ + { + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + }, + "Markdown": { + "main": [ + [ + { + "node": "var mapping", + "type": "main", + "index": 0 + } + ] + ] + }, + "var mapping": { + "main": [ + [ + { + "node": "Text Classifier", + "type": "main", + "index": 0 + } + ] + ] + }, + "OpenAI Chat Model": { + "ai_languageModel": [ + [ + { + "node": "Text Classifier", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "Text Classifier": { + "main": [ + [ + { + "node": "Update - Childcare", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Update - Home", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Update - Finance", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Update - Travel", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Update - Health", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Update - Community", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Update - Shopping", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Update - Tech", + "type": "main", + "index": 0 + } + ] + ] + }, + "Update - Health": { + "main": [ + [ + { + "node": "Replace Me", + "type": "main", + "index": 0 + } + ] + ] + }, + "Update - Tech": { + "main": [ + [ + { + "node": "Replace Me", + "type": "main", + "index": 0 + } + ] + ] + }, + "Update - Shopping": { + "main": [ + [ + { + "node": "Replace Me", + "type": "main", + "index": 0 + } + ] + ] + }, + "Update - Community": { + "main": [ + [ + { + "node": "Replace Me", + "type": "main", + "index": 0 + } + ] + ] + }, + "Update - Travel": { + "main": [ + [ + { + "node": "Replace Me", + "type": "main", + "index": 0 + } + ] + ] + }, + "Update - Finance": { + "main": [ + [ + { + "node": "Replace Me", + "type": "main", + "index": 0 + } + ] + ] + }, + "Update - Home": { + "main": [ + [ + { + "node": "Replace Me", + "type": "main", + "index": 0 + } + ] + ] + }, + "Update - Childcare": { + "main": [ + [ + { + "node": "Replace Me", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "availableInMCP": false + }, + "triggerCount": 0, + "versionId": "40fb01ad-0fbe-4397-b50f-88d0acca709c", + "owner": { + "type": "personal", + "projectId": "FeLO36wNUAcn61Wj", + "projectName": "Ben W ", + "personalEmail": "admin@ben.io" + }, + "parentFolderId": "OJ2UfPNUOAOHlllh", + "isArchived": false +} \ No newline at end of file diff --git a/workflows/21kEHGQ_E07Uq7irNExUD.json b/workflows/21kEHGQ_E07Uq7irNExUD.json new file mode 100644 index 0000000..28347c5 --- /dev/null +++ b/workflows/21kEHGQ_E07Uq7irNExUD.json @@ -0,0 +1,566 @@ +{ + "id": "21kEHGQ_E07Uq7irNExUD", + "name": "Email Review", + "nodes": [ + { + "parameters": { + "pollTimes": { + "item": [ + { + "mode": "everyHour" + } + ] + }, + "filters": { + "readStatus": "unread" + } + }, + "type": "n8n-nodes-base.gmailTrigger", + "typeVersion": 1.3, + "position": [ + -640, + -208 + ], + "id": "f83c959d-3d77-4a81-983f-e0c960945209", + "name": "Gmail Trigger", + "credentials": { + "gmailOAuth2": { + "id": "UDrpaip2ru9PZoaJ", + "name": "rizon-gmail" + } + } + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "leftValue": "={{ $json.To }}", + "rightValue": "kline@rizon.net", + "operator": { + "type": "string", + "operation": "equals" + }, + "id": "0ae6c938-bc7e-4d81-a117-7752355ad4a7" + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "kline@rizon.net" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "bb4620f5-6d35-4ce0-819e-bae2e04904eb", + "leftValue": "={{ $json.To }}", + "rightValue": "rizon@list.shadowserver.org", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "shadowserver@rizon.net" + } + ] + }, + "options": { + "allMatchingOutputs": true + } + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.4, + "position": [ + -320, + -96 + ], + "id": "e0376f85-0771-49a2-83a0-2729157eff30", + "name": "Route" + }, + { + "parameters": { + "method": "=GET", + "url": "={{ $json.report_url }}", + "options": { + "response": { + "response": { + "responseFormat": "file", + "outputPropertyName": "csv" + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.3, + "position": [ + 96, + 0 + ], + "id": "4e9f294b-5d5f-4fd0-965b-3f30c546de94", + "name": "Report Download" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "d9b5b017-4602-4622-9207-bf5063b7f148", + "name": "=report_url", + "value": "={{ $json.snippet.match(/https:\\/\\/[^\\s]+/)[0] }}", + "type": "string" + } + ] + }, + "includeOtherFields": true, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -112, + 0 + ], + "id": "703f00f1-c533-4793-b6b9-59c45982f682", + "name": "Parse Report URL" + }, + { + "parameters": { + "operation": "html", + "binaryPropertyName": "csv", + "options": { + "headerRow": true + } + }, + "type": "n8n-nodes-base.extractFromFile", + "typeVersion": 1.1, + "position": [ + 304, + 0 + ], + "id": "5a99cb18-40b6-4f5f-bb50-659f4a1c7240", + "name": "Extract from File" + }, + { + "parameters": { + "operation": "upsert", + "schema": { + "__rl": true, + "mode": "list", + "value": "public" + }, + "table": { + "__rl": true, + "value": "shadowserver_rizon", + "mode": "list", + "cachedResultName": "shadowserver_rizon" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "report_timestamp": "={{ $json.timestamp }}", + "port": "={{ $json.port }}", + "asn": "={{ $json.asn }}", + "naics": "={{ $json.naics }}", + "tag": "={{ $json.tag }}", + "ip": "={{ $json.ip }}", + "last_seen_at": "={{ $now }}", + "hostname_source": "={{ $json.hostname_source }}", + "hostname": "={{ $json.hostname }}", + "device_vendor": "={{ $json.device_vendor }}", + "device_type": "={{ $json.device_type }}", + "device_model": "={{ $json.device_model }}", + "device_version": "={{ $json.device_version }}", + "sector": "={{ $json.sector }}", + "city": "={{ $json.city }}", + "region": "={{ $json.region }}", + "geo": "={{ $json.geo }}", + "protocol": "={{ $json.protocol }}", + "severity": "={{ $json.severity }}", + "human_timestamp": "={{ DateTime.fromMillis(($json.timestamp - 25569) * 86400 * 1000).toISO() }}" + }, + "matchingColumns": [ + "port", + "ip", + "tag" + ], + "schema": [ + { + "id": "id", + "displayName": "id", + "required": false, + "defaultMatch": true, + "display": true, + "type": "number", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "report_timestamp", + "displayName": "report_timestamp", + "required": false, + "defaultMatch": false, + "display": true, + "type": "number", + "canBeUsedToMatch": false + }, + { + "id": "human_timestamp", + "displayName": "human_timestamp", + "required": false, + "defaultMatch": false, + "display": true, + "type": "dateTime", + "canBeUsedToMatch": false, + "removed": false + }, + { + "id": "severity", + "displayName": "severity", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "ip", + "displayName": "ip", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": false + }, + { + "id": "protocol", + "displayName": "protocol", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "port", + "displayName": "port", + "required": false, + "defaultMatch": false, + "display": true, + "type": "number", + "canBeUsedToMatch": true, + "removed": false + }, + { + "id": "tag", + "displayName": "tag", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": false + }, + { + "id": "asn", + "displayName": "asn", + "required": false, + "defaultMatch": false, + "display": true, + "type": "number", + "canBeUsedToMatch": false + }, + { + "id": "geo", + "displayName": "geo", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "region", + "displayName": "region", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "city", + "displayName": "city", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "naics", + "displayName": "naics", + "required": false, + "defaultMatch": false, + "display": true, + "type": "number", + "canBeUsedToMatch": false + }, + { + "id": "sector", + "displayName": "sector", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "device_vendor", + "displayName": "device_vendor", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "device_type", + "displayName": "device_type", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "device_model", + "displayName": "device_model", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "device_version", + "displayName": "device_version", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "hostname", + "displayName": "hostname", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "hostname_source", + "displayName": "hostname_source", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false + }, + { + "id": "created_at", + "displayName": "created_at", + "required": false, + "defaultMatch": false, + "display": true, + "type": "dateTime", + "canBeUsedToMatch": false, + "removed": true + }, + { + "id": "last_seen_at", + "displayName": "last_seen_at", + "required": false, + "defaultMatch": false, + "display": true, + "type": "dateTime", + "canBeUsedToMatch": false, + "removed": false + } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + }, + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 528, + 0 + ], + "id": "e56c9aa3-923d-4088-a502-d075e348461c", + "name": "Insert or update rows in a table", + "credentials": { + "postgres": { + "id": "Ik8CFyap8ic2Md3M", + "name": "n8n-infra" + } + } + }, + { + "parameters": { + "operation": "getAll", + "limit": 5, + "filters": { + "labelIds": [ + "Label_734823692276842075" + ], + "receivedAfter": "={{ $now.minus({ minutes: 300 }).toFormat('yyyy/MM/dd HH:mm:ss') }}" + } + }, + "type": "n8n-nodes-base.gmail", + "typeVersion": 2.2, + "position": [ + -640, + 32 + ], + "id": "436506e9-2fca-4dac-9a57-e9cef92193b6", + "name": "Get many messages", + "webhookId": "4835dd9b-345a-4ef3-b5e6-524ec0d83e29", + "credentials": { + "gmailOAuth2": { + "id": "UDrpaip2ru9PZoaJ", + "name": "rizon-gmail" + } + } + }, + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + -848, + 32 + ], + "id": "87718be7-f310-470b-acc2-918f9c146203", + "name": "When clicking ‘Execute workflow’" + } + ], + "connections": { + "Gmail Trigger": { + "main": [ + [ + { + "node": "Route", + "type": "main", + "index": 0 + } + ] + ] + }, + "Route": { + "main": [ + [], + [ + { + "node": "Parse Report URL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse Report URL": { + "main": [ + [ + { + "node": "Report Download", + "type": "main", + "index": 0 + } + ] + ] + }, + "Report Download": { + "main": [ + [ + { + "node": "Extract from File", + "type": "main", + "index": 0 + } + ] + ] + }, + "Extract from File": { + "main": [ + [ + { + "node": "Insert or update rows in a table", + "type": "main", + "index": 0 + } + ] + ] + }, + "Get many messages": { + "main": [ + [ + { + "node": "Route", + "type": "main", + "index": 0 + } + ] + ] + }, + "When clicking ‘Execute workflow’": { + "main": [ + [ + { + "node": "Get many messages", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "availableInMCP": false + }, + "triggerCount": 1, + "versionId": "5fb89ea8-06ae-4f75-a759-7d24018e255b", + "owner": { + "type": "personal", + "projectId": "FeLO36wNUAcn61Wj", + "projectName": "Ben W ", + "personalEmail": "admin@ben.io" + }, + "parentFolderId": "caxOx4NfY2vHcoNv", + "isArchived": false +} \ No newline at end of file diff --git a/workflows/Pv-qCW0IA2IxNGm-fPXxh.json b/workflows/Pv-qCW0IA2IxNGm-fPXxh.json new file mode 100644 index 0000000..13b5b87 --- /dev/null +++ b/workflows/Pv-qCW0IA2IxNGm-fPXxh.json @@ -0,0 +1,21 @@ +{ + "id": "Pv-qCW0IA2IxNGm-fPXxh", + "name": "Github Notifications", + "nodes": [], + "connections": {}, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "triggerCount": 0, + "versionId": "21d1b6ec-1622-426a-a06a-5e2e70d81513", + "owner": { + "type": "personal", + "projectId": "FeLO36wNUAcn61Wj", + "projectName": "Ben W ", + "personalEmail": "admin@ben.io" + }, + "parentFolderId": "OJ2UfPNUOAOHlllh", + "isArchived": false +} \ No newline at end of file diff --git a/workflows/WkAdUd9jXTtPagGO.json b/workflows/WkAdUd9jXTtPagGO.json index 1d049db..89cf9b5 100644 --- a/workflows/WkAdUd9jXTtPagGO.json +++ b/workflows/WkAdUd9jXTtPagGO.json @@ -72,7 +72,7 @@ 304 ], "id": "2f0708e1-996c-4705-b374-2a119fcd6b18", - "name": "Get Active Followed Series1", + "name": "Get Active Followed Series", "credentials": { "postgres": { "id": "dsnKfvOBMkgU21Lt", @@ -86,7 +86,7 @@ "main": [ [ { - "node": "Get Active Followed Series1", + "node": "Get Active Followed Series", "type": "main", "index": 0 } @@ -104,7 +104,7 @@ ] ] }, - "Get Active Followed Series1": { + "Get Active Followed Series": { "main": [ [ { @@ -124,7 +124,7 @@ "saveExecutionProgress": true }, "triggerCount": 0, - "versionId": "5d298782-fb60-4d33-9e38-97878b179d62", + "versionId": "34e2aea3-0a6e-41fd-b277-207bfd0f6804", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", diff --git a/workflows/cPWZKfrHOUSUZjIp.json b/workflows/cPWZKfrHOUSUZjIp.json index 8a14cd6..5b67d66 100644 --- a/workflows/cPWZKfrHOUSUZjIp.json +++ b/workflows/cPWZKfrHOUSUZjIp.json @@ -123,7 +123,8 @@ "series_name": "={{ $('Merge').item.json.output.series }}", "audible_asin": "={{ $('Merge').item.json.output.asin }}", "failure_reason": "={{ $('Merge').item.json.output.failure_reason }}", - "category": "general" + "category": "general", + "followed_series_id": "={{ $('Call MAM Series Matcher').item.json.series_id }}" }, "matchingColumns": [ "smb_path" @@ -265,6 +266,16 @@ "display": true, "type": "dateTime", "canBeUsedToMatch": false + }, + { + "id": "followed_series_id", + "displayName": "followed_series_id", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": false, + "removed": false } ], "attemptToConvertTypes": false, @@ -860,9 +871,9 @@ "parameters": { "model": { "__rl": true, - "value": "minimax/minimax-m2.1", + "value": "antigravity/gemini-3-flash", "mode": "list", - "cachedResultName": "minimax/minimax-m2.1" + "cachedResultName": "antigravity/gemini-3-flash" }, "responsesApiEnabled": false, "options": {} @@ -877,10 +888,40 @@ "name": "OpenAI Chat Model", "credentials": { "openAiApi": { - "id": "9UFJNgYndzFIHJAf", - "name": "Nano-GPT" + "id": "sxSUdecXdMfKPuTu", + "name": "llm-proxy.ben.io" } } + }, + { + "parameters": { + "workflowId": { + "__rl": true, + "value": "WkAdUd9jXTtPagGO", + "mode": "list", + "cachedResultUrl": "/workflow/WkAdUd9jXTtPagGO", + "cachedResultName": "MAM Series Matcher" + }, + "workflowInputs": { + "mappingMode": "defineBelow", + "value": {}, + "matchingColumns": [], + "schema": [], + "attemptToConvertTypes": false, + "convertFieldsToString": true + }, + "options": { + "waitForSubWorkflow": true + } + }, + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.3, + "position": [ + 2112, + 352 + ], + "id": "5e966fb1-22e6-405c-b2cb-14cc7ae53cb1", + "name": "Call 'MAM Series Matcher'" } ], "connections": { @@ -937,7 +978,7 @@ "main": [ [ { - "node": "Merge", + "node": "Call 'MAM Series Matcher'", "type": "main", "index": 0 } @@ -1199,6 +1240,17 @@ } ] ] + }, + "Call 'MAM Series Matcher'": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 0 + } + ] + ] } }, "settings": { @@ -1207,7 +1259,7 @@ "availableInMCP": false }, "triggerCount": 1, - "versionId": "507140b0-a21b-428f-a230-906217819b6c", + "versionId": "7e06408b-8b2a-4d35-83fa-00ffadece2f8", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", diff --git a/workflows/kQIViyYKgY1d4E99.json b/workflows/kQIViyYKgY1d4E99.json new file mode 100644 index 0000000..294fea6 --- /dev/null +++ b/workflows/kQIViyYKgY1d4E99.json @@ -0,0 +1,236 @@ +{ + "id": "kQIViyYKgY1d4E99", + "name": "RizonHelp IRC Bot", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "rizonhelp", + "responseMode": "lastNode", + "options": {} + }, + "id": "webhook-trigger", + "name": "Webhook Trigger", + "position": [ + -560, + 208 + ], + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "webhookId": "7c1b6e59-8b48-498c-b166-55127d0e298f" + }, + { + "parameters": { + "jsCode": "// Format response for IRC - handle various QA Chain output formats\nconst input = $input.first().json;\n\n// Extract response text\nlet response = '';\nif (typeof input.response === 'string') {\n response = input.response;\n} else if (typeof input.text === 'string') {\n response = input.text;\n} else if (typeof input.output === 'string') {\n response = input.output;\n} else if (input.response && typeof input.response.text === 'string') {\n response = input.response.text;\n} else {\n response = JSON.stringify(input);\n}\n\nconst lowerResponse = response.toLowerCase();\n\n// REJECT patterns - common off-topic response indicators\nconst rejectPatterns = [\n '__ignore__',\n 'no_response',\n \"i don't know\",\n \"i do not know\",\n \"you're welcome\",\n \"is there anything else\",\n \"can i help you with\",\n \"what do you need help with\",\n \"how can i assist\",\n \"hello.\",\n \"hi there\",\n \"hi!\",\n \"hello!\",\n \"not in the context\",\n \"not related to\",\n \"no information about\",\n \"not mentioned\",\n \"outside the scope\",\n \"large language model\",\n \"llm\",\n \"artificial intelligence\",\n \"ai model\",\n \"i am an ai\",\n \"i'm an ai\",\n \"as an ai\",\n \"i am a bot\",\n \"i'm a bot\"\n];\n\n// REQUIRE patterns - valid responses MUST contain at least one of these\nconst requirePatterns = [\n '/msg',\n '/ns',\n '/cs',\n '/hs',\n '/bs',\n '/mode',\n '/join',\n '/part',\n '/kick',\n '/ban',\n '/topic',\n 'wiki.rizon.net',\n 'chanserv',\n 'nickserv',\n 'hostserv',\n 'memoserv',\n 'botserv',\n 'operserv'\n];\n\n// Check for rejection patterns\nconst hasRejectPattern = rejectPatterns.some(p => lowerResponse.includes(p));\n\n// Check for required patterns (valid IRC help content)\nconst hasRequiredPattern = requirePatterns.some(p => lowerResponse.includes(p));\n\n// Reject if: empty, has reject pattern, OR lacks any valid IRC content\nif (response.trim() === '' || hasRejectPattern || !hasRequiredPattern) {\n return { json: { response: '', wiki_url: null } };\n}\n\n// Extract wiki URL if mentioned\nlet wikiUrl = null;\nconst wikiMatch = response.match(/https:\\/\\/wiki\\.rizon\\.net\\S*/);\nif (wikiMatch) {\n wikiUrl = wikiMatch[0].replace(/[).,]$/, '');\n}\n\nreturn {\n json: {\n response: response.trim().replace(/\\n/g, ' '),\n wiki_url: wikiUrl\n }\n};" + }, + "id": "response-formatter", + "name": "Response Formatter", + "position": [ + 160, + 208 + ], + "type": "n8n-nodes-base.code", + "typeVersion": 2 + }, + { + "parameters": { + "model": { + "__rl": true, + "value": "nvidia_nim/moonshotai/kimi-k2.5", + "mode": "list", + "cachedResultName": "nvidia_nim/moonshotai/kimi-k2.5" + }, + "options": { + "timeout": 30000 + } + }, + "id": "55629594-7004-4807-94a8-c842558e5db9", + "name": "Chat Model Primary", + "position": [ + -432, + 480 + ], + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.2, + "credentials": { + "openAiApi": { + "id": "sxSUdecXdMfKPuTu", + "name": "llm-proxy.ext.ben.io" + } + } + }, + { + "parameters": { + "promptType": "define", + "text": "={{ $json.body.question }}", + "options": { + "systemPromptTemplate": "Rizon IRC help context:\n{context}\n\nYou answer \"how do I\" questions about Rizon IRC using the context above.\nFormat: [command] - More info: [URL]\n\nIMPORTANT: If the user's question is NOT asking something relevant to IRC, output ONLY this exact text with no explanation:\n__IGNORE__\n\nThings that are NOT IRC help questions (output __IGNORE__):\n- Statements like \"thanks\", \"ok\", \"interesting\", \"cool\"\n- Random text that isn't a question\n- Greetings like \"hello\", \"hi\"\n- Anything not asking how to use IRC or IRC services" + } + }, + "id": "bc8a8f9c-dfa8-4b64-ac87-b81ebed1e9fa", + "name": "QA Chain", + "position": [ + -304, + 208 + ], + "retryOnFail": true, + "type": "@n8n/n8n-nodes-langchain.chainRetrievalQa", + "typeVersion": 1.4 + }, + { + "parameters": { + "topK": 5 + }, + "id": "922a7ee0-42bc-4d92-9c11-16772f20119d", + "name": "Vector Store Retriever", + "position": [ + -208, + 432 + ], + "type": "@n8n/n8n-nodes-langchain.retrieverVectorStore", + "typeVersion": 1 + }, + { + "parameters": { + "qdrantCollection": { + "__rl": true, + "cachedResultName": "rizon-help", + "mode": "list", + "value": "rizon-help" + }, + "options": { + "contentPayloadKey": "page_content" + } + }, + "id": "31f65541-f828-45ca-bc2e-64118aa718c7", + "name": "Qdrant Vector Store", + "position": [ + -208, + 640 + ], + "type": "@n8n/n8n-nodes-langchain.vectorStoreQdrant", + "typeVersion": 1.3, + "credentials": { + "qdrantApi": { + "id": "Qtd00hn8bJt3xzhU", + "name": "Qdrant Local" + } + } + }, + { + "parameters": { + "content": "## Model Notes\n\nfast dedalus model: \n* dedaluslabs/moonshot/kimi-k2-thinking-turbo" + }, + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -1040, + 256 + ], + "id": "a8dcf9fe-8728-4af8-a1e5-7a0d62ff26d6", + "name": "Sticky Note" + }, + { + "parameters": { + "model": "embeddinggemma:latest" + }, + "type": "@n8n/n8n-nodes-langchain.embeddingsOllama", + "typeVersion": 1, + "position": [ + -128, + 848 + ], + "id": "14c8c8f8-e081-4dc7-9eb5-971aa152a92c", + "name": "Embeddings Ollama", + "credentials": { + "ollamaApi": { + "id": "l1g3pgQImkg18AzR", + "name": "Ollama account" + } + } + } + ], + "connections": { + "Chat Model Primary": { + "ai_languageModel": [ + [ + { + "index": 0, + "node": "QA Chain", + "type": "ai_languageModel" + } + ] + ] + }, + "QA Chain": { + "main": [ + [ + { + "index": 0, + "node": "Response Formatter", + "type": "main" + } + ] + ] + }, + "Qdrant Vector Store": { + "ai_vectorStore": [ + [ + { + "index": 0, + "node": "Vector Store Retriever", + "type": "ai_vectorStore" + } + ] + ] + }, + "Vector Store Retriever": { + "ai_retriever": [ + [ + { + "index": 0, + "node": "QA Chain", + "type": "ai_retriever" + } + ] + ] + }, + "Webhook Trigger": { + "main": [ + [ + { + "index": 0, + "node": "QA Chain", + "type": "main" + } + ] + ] + }, + "Embeddings Ollama": { + "ai_embedding": [ + [ + { + "node": "Qdrant Vector Store", + "type": "ai_embedding", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": false + }, + "triggerCount": 1, + "versionId": "f5d59633-0e3c-4a64-a228-655e71ae20b3", + "owner": { + "type": "personal", + "projectId": "FeLO36wNUAcn61Wj", + "projectName": "Ben W ", + "personalEmail": "admin@ben.io" + }, + "parentFolderId": "caxOx4NfY2vHcoNv", + "isArchived": false +} \ No newline at end of file diff --git a/workflows/kRZyX9H2uDHHncpE.json b/workflows/kRZyX9H2uDHHncpE.json index 23b0c63..44ea69e 100644 --- a/workflows/kRZyX9H2uDHHncpE.json +++ b/workflows/kRZyX9H2uDHHncpE.json @@ -3,44 +3,45 @@ "name": "MAM Transmission Manager", "nodes": [ { - "parameters": {}, - "id": "start", - "name": "Start", "position": [ 224, 128 ], "type": "n8n-nodes-base.executeWorkflowTrigger", - "typeVersion": 1 + "typeVersion": 1, + "id": "start", + "name": "Start", + "parameters": {} }, { + "id": "http-add-torrent", + "name": "Add Torrent to Transmission", + "onError": "continueRegularOutput", "parameters": { - "method": "POST", - "url": "http://seed-1.dfw.ben.io:9091/transmission/rpc", - "authentication": "genericCredentialType", - "genericAuthType": "httpBasicAuth", - "sendHeaders": true, - "headerParameters": { - "parameters": [ - { - "name": "X-Transmission-Session-Id", - "value": "={{ $('Merge Session to Files').item.json.headers[\"X-Transmission-Session-Id\"] }}" - } - ] - }, - "sendBody": true, "specifyBody": "json", - "jsonBody": "={\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"metainfo\": \"{{ $('Extract from File').item.json.data }}\",\n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": true\n }\n}", "options": { "response": { "response": { "fullResponse": true } } - } + }, + "headerParameters": { + "parameters": [ + { + "value": "={{ $('Merge Session to Files').item.json.headers[\"X-Transmission-Session-Id\"] }}", + "name": "X-Transmission-Session-Id" + } + ] + }, + "sendHeaders": true, + "genericAuthType": "httpBasicAuth", + "method": "POST", + "sendBody": true, + "url": "http://seed-1.dfw.ben.io:9091/transmission/rpc", + "authentication": "genericCredentialType", + "jsonBody": "={\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"metainfo\": \"{{ $('Extract from File').item.json.data }}\",\n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": true\n }\n}" }, - "id": "http-add-torrent", - "name": "Add Torrent to Transmission", "position": [ 1792, 128 @@ -52,68 +53,99 @@ "id": "iymUPilnVhfL3h5D", "name": "transmission" } - }, - "onError": "continueRegularOutput" + } }, { - "parameters": { - "jsCode": "const responses = $input.all();\nconst originals = $('Start').all();\nconst mergedPayloads = $('Merge Session Context').all();\n\nfunction getOriginal(index) {\n return originals[index]?.json || originals[0]?.json || {};\n}\n\nfunction getSessionId(index) {\n return mergedPayloads[index]?.json?.session_id || mergedPayloads[0]?.json?.session_id || '';\n}\n\nconst outputs = [];\n\nfor (let i = 0; i < responses.length; i++) {\n const item = responses[i];\n const body = item.json.body || item.json;\n const result = body?.result;\n const args = body?.arguments || {};\n const torrentInfo = args['torrent-added'] || args['torrent-duplicate'];\n const sessionId = getSessionId(i);\n const originalInput = getOriginal(i);\n\n if (item.error || (!torrentInfo && result !== 'duplicate' && result !== 'success')) {\n outputs.push({\n json: {\n status: 'failed',\n error: item.error?.message || result || 'Unknown error adding torrent',\n torrent_id: null,\n session_id: sessionId,\n original_input: originalInput\n }\n });\n continue;\n }\n\n if (!torrentInfo) {\n outputs.push({\n json: {\n status: 'failed',\n error: 'Could not extract torrent info from response',\n torrent_id: null,\n session_id: sessionId,\n original_input: originalInput\n }\n });\n continue;\n }\n\n outputs.push({\n json: {\n status: 'queued',\n torrent_id: torrentInfo.id,\n torrent_hash: torrentInfo.hashString,\n torrent_name: torrentInfo.name,\n session_id: sessionId,\n original_input: originalInput\n }\n });\n}\n\nreturn outputs;" - }, - "id": "code-extract-torrent-id", - "name": "Extract Torrent ID", "position": [ 2016, 128 ], "type": "n8n-nodes-base.code", - "typeVersion": 2 + "typeVersion": 2, + "id": "code-extract-torrent-id", + "name": "Extract Torrent ID", + "parameters": { + "jsCode": "const responses = $input.all();\nconst originals = $('Start').all();\nconst mergedPayloads = $('Merge Session Context').all();\n\nfunction getOriginal(index) {\n return originals[index]?.json || originals[0]?.json || {};\n}\n\nfunction getSessionId(index) {\n return mergedPayloads[index]?.json?.session_id || mergedPayloads[0]?.json?.session_id || '';\n}\n\nconst outputs = [];\n\nfor (let i = 0; i < responses.length; i++) {\n const item = responses[i];\n const body = item.json.body || item.json;\n const result = body?.result;\n const args = body?.arguments || {};\n const torrentInfo = args['torrent-added'] || args['torrent-duplicate'];\n const sessionId = getSessionId(i);\n const originalInput = getOriginal(i);\n\n if (item.error || (!torrentInfo && result !== 'duplicate' && result !== 'success')) {\n outputs.push({\n json: {\n status: 'failed',\n error: item.error?.message || result || 'Unknown error adding torrent',\n torrent_id: null,\n session_id: sessionId,\n original_input: originalInput\n }\n });\n continue;\n }\n\n if (!torrentInfo) {\n outputs.push({\n json: {\n status: 'failed',\n error: 'Could not extract torrent info from response',\n torrent_id: null,\n session_id: sessionId,\n original_input: originalInput\n }\n });\n continue;\n }\n\n outputs.push({\n json: {\n status: 'queued',\n torrent_id: torrentInfo.id,\n torrent_hash: torrentInfo.hashString,\n torrent_name: torrentInfo.name,\n session_id: sessionId,\n original_input: originalInput\n }\n });\n}\n\nreturn outputs;" + } }, { + "position": [ + 2240, + 128 + ], + "type": "n8n-nodes-base.if", + "typeVersion": 2.2, + "id": "if-filter-success", + "name": "Filter Success", + "parameters": { + "conditions": { + "conditions": [ + { + "id": "filter-success", + "leftValue": "={{ $json.status }}", + "operator": { + "operation": "equals", + "type": "string" + }, + "rightValue": "queued" + } + ], + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "combinator": "and" + } + } + }, + { + "name": "Respond Complete", "parameters": { "respondWith": "allIncomingItems", "options": {} }, - "id": "respond-complete", - "name": "Respond Complete", "position": [ - 2464, + 2688, 128 ], "type": "n8n-nodes-base.respondToWebhook", - "typeVersion": 1 + "typeVersion": 1, + "id": "respond-complete" }, { + "name": "Merge Session to Files", "parameters": { "language": "pythonNative", "pythonCode": "# 1. Access the input item\nitem = _items[0]['json']\n\n# 2. Extract the data we need\n# UPDATE: The input key is now 'dl_link', not 'download_link'\ndownload_link = item.get('dl_link')\nsession_id = item.get('session_id')\n\n# 3. Create the Transmission Payload\npayload = {\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"filename\": download_link,\n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": True \n }\n}\n\n# 4. Return the formatted data\nreturn [{\n \"json\": {\n # UPDATE: Map 'title' correctly for any old nodes that need it\n \"title\": item.get('meta_title'),\n \n # NEW: Pass through all metadata for the Postgres Logger\n \"meta_title\": item.get('meta_title'),\n \"meta_series\": item.get('meta_series'),\n \"meta_book_number\": item.get('meta_book_number'),\n \"meta_author\": item.get('meta_author'),\n \"mam_id\": item.get('mam_id'),\n \"dl_link\": download_link,\n \n # Transmission Data\n \"transmission_body\": payload,\n \"headers\": {\n \"X-Transmission-Session-Id\": session_id\n }\n }\n}]" }, - "type": "n8n-nodes-base.code", - "typeVersion": 2, "position": [ 896, 128 ], - "id": "d6d9f145-6b10-452f-aa08-2dc669c3b4d6", - "name": "Merge Session to Files" + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "id": "d6d9f145-6b10-452f-aa08-2dc669c3b4d6" }, { + "id": "dde23141-d708-48e7-942f-08aa1d749189", + "name": "Merge Session Context", "parameters": { - "mode": "combine", + "options": {}, "combineBy": "combineByPosition", - "options": {} + "mode": "combine" }, - "type": "n8n-nodes-base.merge", - "typeVersion": 3.2, "position": [ 672, 128 ], - "id": "dde23141-d708-48e7-942f-08aa1d749189", - "name": "Merge Session Context" + "type": "n8n-nodes-base.merge", + "typeVersion": 3.2 }, { + "id": "acb7d649-a853-44e7-84e8-f82398f3008a", + "name": "MAM Get File HTTP Request", "parameters": { - "url": "={{ $json.transmission_body.arguments.filename }}", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { @@ -123,16 +155,15 @@ "responseFormat": "file" } } - } + }, + "url": "={{ $json.transmission_body.arguments.filename }}" }, - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.3, "position": [ 1120, 128 ], - "id": "acb7d649-a853-44e7-84e8-f82398f3008a", - "name": "MAM Get File HTTP Request", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.3, "credentials": { "httpBasicAuth": { "id": "iymUPilnVhfL3h5D", @@ -145,77 +176,79 @@ } }, { + "typeVersion": 1.1, + "id": "acb1b56b-ac9a-41ec-ad60-7ce983b8cdd7", + "name": "Extract from File", "parameters": { "operation": "binaryToPropery", "options": {} }, - "type": "n8n-nodes-base.extractFromFile", - "typeVersion": 1.1, "position": [ 1568, 128 ], - "id": "acb1b56b-ac9a-41ec-ad60-7ce983b8cdd7", - "name": "Extract from File" + "type": "n8n-nodes-base.extractFromFile" }, { - "parameters": { - "language": "pythonNative", - "pythonCode": "results = []\n\nfor item in _items:\n # 1. Get the binary data from the previous download node\n # n8n stores the file in the 'binary' key. The default property name is usually 'data'.\n # We need the 'data' field inside that, which contains the Base64 string.\n binary_ref = item.get('binary', {}).get('data', {})\n b64_string = binary_ref.get('data')\n\n if b64_string:\n # 2. Create the exact JSON body Transmission expects for file uploads\n # We use \"metainfo\" for the actual file content (Base64)\n payload = {\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"metainfo\": b64_string, \n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": False\n }\n }\n\n # 3. Preserve any existing JSON (like session_id) if it exists, or start fresh\n new_json = item.get('json', {}).copy()\n new_json['transmission_body'] = payload\n \n results.append({\n \"json\": new_json,\n # Pass the binary through just in case, though we don't strictly need it anymore\n \"binary\": item.get('binary', {})\n })\n\nreturn results" - }, - "type": "n8n-nodes-base.code", - "typeVersion": 2, "position": [ 1344, 128 ], + "type": "n8n-nodes-base.code", + "typeVersion": 2, "id": "0b011613-50e9-4612-ac6e-1f6ac0e39ebb", - "name": "Prepare Transmission Payload" + "name": "Prepare Transmission Payload", + "parameters": { + "pythonCode": "results = []\n\nfor item in _items:\n # 1. Get the binary data from the previous download node\n # n8n stores the file in the 'binary' key. The default property name is usually 'data'.\n # We need the 'data' field inside that, which contains the Base64 string.\n binary_ref = item.get('binary', {}).get('data', {})\n b64_string = binary_ref.get('data')\n\n if b64_string:\n # 2. Create the exact JSON body Transmission expects for file uploads\n # We use \"metainfo\" for the actual file content (Base64)\n payload = {\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"metainfo\": b64_string, \n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": False\n }\n }\n\n # 3. Preserve any existing JSON (like session_id) if it exists, or start fresh\n new_json = item.get('json', {}).copy()\n new_json['transmission_body'] = payload\n \n results.append({\n \"json\": new_json,\n # Pass the binary through just in case, though we don't strictly need it anymore\n \"binary\": item.get('binary', {})\n })\n\nreturn results", + "language": "pythonNative" + } }, { "parameters": { "workflowId": { - "__rl": true, - "value": "NChjOd3ILEmt0FdyAt8qA", "mode": "list", - "cachedResultUrl": "/workflow/NChjOd3ILEmt0FdyAt8qA", - "cachedResultName": "Transmission: Get Session" + "value": "NChjOd3ILEmt0FdyAt8qA", + "__rl": true, + "cachedResultName": "Transmission: Get Session", + "cachedResultUrl": "/workflow/NChjOd3ILEmt0FdyAt8qA" }, "workflowInputs": { - "mappingMode": "defineBelow", - "value": { - "server_id": "seed-1.dfw.ben.io" - }, "matchingColumns": [ "server_id" ], "schema": [ { - "id": "server_id", - "displayName": "server_id", - "required": false, "defaultMatch": false, "display": true, - "canBeUsedToMatch": true, + "displayName": "server_id", + "id": "server_id", + "removed": false, + "required": false, "type": "string", - "removed": false + "canBeUsedToMatch": true } ], + "value": { + "server_id": "seed-1.dfw.ben.io" + }, "attemptToConvertTypes": false, - "convertFieldsToString": true + "convertFieldsToString": true, + "mappingMode": "defineBelow" }, "options": {} }, - "type": "n8n-nodes-base.executeWorkflow", - "typeVersion": 1.3, "position": [ 448, 208 ], + "type": "n8n-nodes-base.executeWorkflow", + "typeVersion": 1.3, "id": "175e270a-b1d9-41d0-981a-1f83258443b1", "name": "Transmission: Get Session" }, { + "id": "5a79163a-a211-475c-b53e-79d086a4c9a7", + "name": "Insert rows in a table", "parameters": { "schema": { "__rl": true, @@ -223,197 +256,195 @@ "value": "public" }, "table": { - "__rl": true, - "value": "download_history", + "cachedResultName": "download_history", "mode": "list", - "cachedResultName": "download_history" + "value": "download_history", + "__rl": true }, "columns": { - "mappingMode": "defineBelow", "value": { - "mam_id": "={{ $json.original_input.mam_id }}", - "torrent_url": "={{ $('Extract Torrent ID').item.json.original_input.dl_link }}", - "book_id": "={{ null }}", "torrent_hash": "={{ $json.torrent_hash }}", - "filename": "={{ $('Extract Torrent ID').item.json.torrent_name }}", - "status": "={{ $json.status }}", + "filename": "={{ $json.torrent_name }}", + "meta_author": "={{ $json.original_input.meta_author }}", "meta_title": "={{ $json.original_input.meta_title }}", + "status": "={{ $json.status }}", + "book_id": "={{ null }}", + "mam_id": "={{ $json.original_input.mam_id }}", "meta_series": "={{ $json.original_input.meta_series }}", - "meta_book_number": "={{ $json.original_input.meta_book_number }}", - "meta_author": "={{ $json.original_input.meta_author }}" + "torrent_url": "={{ $json.original_input.dl_link }}", + "meta_book_number": "={{ $json.original_input.meta_book_number }}" }, + "attemptToConvertTypes": false, + "convertFieldsToString": false, + "mappingMode": "defineBelow", "matchingColumns": [ "id" ], "schema": [ { - "id": "id", - "displayName": "id", - "required": false, "defaultMatch": true, "display": true, - "type": "string", - "canBeUsedToMatch": true, - "removed": false - }, - { - "id": "book_id", - "displayName": "book_id", + "displayName": "id", + "id": "id", + "removed": false, "required": false, - "defaultMatch": false, - "display": true, "type": "string", "canBeUsedToMatch": true }, + { + "display": true, + "displayName": "book_id", + "id": "book_id", + "required": false, + "type": "string", + "canBeUsedToMatch": true, + "defaultMatch": false + }, { "id": "torrent_url", - "displayName": "torrent_url", "required": true, + "type": "string", + "canBeUsedToMatch": true, "defaultMatch": false, "display": true, - "type": "string", - "canBeUsedToMatch": true + "displayName": "torrent_url" }, { - "id": "torrent_hash", + "defaultMatch": false, + "display": true, "displayName": "torrent_hash", + "id": "torrent_hash", "required": true, - "defaultMatch": false, - "display": true, "type": "string", "canBeUsedToMatch": true }, { - "id": "filename", - "displayName": "filename", - "required": true, "defaultMatch": false, "display": true, + "displayName": "filename", + "id": "filename", + "required": true, "type": "string", "canBeUsedToMatch": true }, { "id": "transmission_path", - "displayName": "transmission_path", "required": false, + "type": "string", + "canBeUsedToMatch": true, "defaultMatch": false, "display": true, - "type": "string", - "canBeUsedToMatch": true + "displayName": "transmission_path" }, { - "id": "gateway_path", + "display": true, "displayName": "gateway_path", + "id": "gateway_path", "required": false, - "defaultMatch": false, - "display": true, "type": "string", - "canBeUsedToMatch": true + "canBeUsedToMatch": true, + "defaultMatch": false }, { - "id": "status", + "defaultMatch": false, + "display": true, "displayName": "status", + "id": "status", "required": false, - "defaultMatch": false, - "display": true, "type": "string", "canBeUsedToMatch": true }, { - "id": "downloaded_at", - "displayName": "downloaded_at", - "required": false, "defaultMatch": false, "display": true, + "displayName": "downloaded_at", + "id": "downloaded_at", + "required": false, "type": "dateTime", "canBeUsedToMatch": true }, { - "id": "created_at", - "displayName": "created_at", "required": false, + "type": "dateTime", + "canBeUsedToMatch": true, "defaultMatch": false, "display": true, - "type": "dateTime", - "canBeUsedToMatch": true + "displayName": "created_at", + "id": "created_at" }, { "id": "updated_at", - "displayName": "updated_at", "required": false, - "defaultMatch": false, - "display": true, "type": "dateTime", - "canBeUsedToMatch": true + "canBeUsedToMatch": true, + "defaultMatch": false, + "display": true, + "displayName": "updated_at" }, { - "id": "meta_title", + "type": "string", + "canBeUsedToMatch": true, + "defaultMatch": false, + "display": true, "displayName": "meta_title", - "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true + "id": "meta_title", + "required": false }, { - "id": "meta_series", + "canBeUsedToMatch": true, + "defaultMatch": false, + "display": true, "displayName": "meta_series", + "id": "meta_series", "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true + "type": "string" }, { - "id": "meta_book_number", + "type": "string", + "canBeUsedToMatch": true, + "defaultMatch": false, + "display": true, "displayName": "meta_book_number", - "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true + "id": "meta_book_number", + "required": false }, { - "id": "meta_author", + "canBeUsedToMatch": true, + "defaultMatch": false, + "display": true, "displayName": "meta_author", + "id": "meta_author", "required": false, - "defaultMatch": false, - "display": true, - "type": "string", - "canBeUsedToMatch": true + "type": "string" }, { - "id": "mam_id", "displayName": "mam_id", + "id": "mam_id", "required": false, - "defaultMatch": false, - "display": true, "type": "number", - "canBeUsedToMatch": true + "canBeUsedToMatch": true, + "defaultMatch": false, + "display": true }, { - "id": "error_log", - "displayName": "error_log", - "required": false, + "canBeUsedToMatch": true, "defaultMatch": false, "display": true, - "type": "string", - "canBeUsedToMatch": true + "displayName": "error_log", + "id": "error_log", + "required": false, + "type": "string" } - ], - "attemptToConvertTypes": false, - "convertFieldsToString": false + ] }, "options": {} }, + "position": [ + 2464, + 48 + ], "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, - "position": [ - 2240, - 128 - ], - "id": "5a79163a-a211-475c-b53e-79d086a4c9a7", - "name": "Insert rows in a table", "credentials": { "postgres": { "id": "9grzZwW7Br6SzdV8", @@ -423,18 +454,7 @@ } ], "connections": { - "Add Torrent to Transmission": { - "main": [ - [ - { - "index": 0, - "node": "Extract Torrent ID", - "type": "main" - } - ] - ] - }, - "Extract Torrent ID": { + "Filter Success": { "main": [ [ { @@ -442,43 +462,12 @@ "type": "main", "index": 0 } - ] - ] - }, - "Start": { - "main": [ + ], [ { - "node": "Transmission: Get Session", "type": "main", - "index": 0 - }, - { - "node": "Merge Session Context", - "type": "main", - "index": 0 - } - ] - ] - }, - "Merge Session to Files": { - "main": [ - [ - { - "node": "MAM Get File HTTP Request", - "type": "main", - "index": 0 - } - ] - ] - }, - "Merge Session Context": { - "main": [ - [ - { - "node": "Merge Session to Files", - "type": "main", - "index": 0 + "index": 0, + "node": "Respond Complete" } ] ] @@ -487,9 +476,20 @@ "main": [ [ { - "node": "Prepare Transmission Payload", "type": "main", - "index": 0 + "index": 0, + "node": "Prepare Transmission Payload" + } + ] + ] + }, + "Add Torrent to Transmission": { + "main": [ + [ + { + "index": 0, + "node": "Extract Torrent ID", + "type": "main" } ] ] @@ -505,13 +505,51 @@ ] ] }, + "Merge Session to Files": { + "main": [ + [ + { + "index": 0, + "node": "MAM Get File HTTP Request", + "type": "main" + } + ] + ] + }, + "Start": { + "main": [ + [ + { + "index": 0, + "node": "Transmission: Get Session", + "type": "main" + }, + { + "index": 0, + "node": "Merge Session Context", + "type": "main" + } + ] + ] + }, "Prepare Transmission Payload": { "main": [ [ { - "node": "Extract from File", "type": "main", - "index": 0 + "index": 0, + "node": "Extract from File" + } + ] + ] + }, + "Insert rows in a table": { + "main": [ + [ + { + "type": "main", + "index": 0, + "node": "Respond Complete" } ] ] @@ -527,21 +565,35 @@ ] ] }, - "Insert rows in a table": { + "Merge Session Context": { "main": [ [ { - "node": "Respond Complete", "type": "main", - "index": 0 + "index": 0, + "node": "Merge Session to Files" + } + ] + ] + }, + "Extract Torrent ID": { + "main": [ + [ + { + "type": "main", + "index": 0, + "node": "Filter Success" } ] ] } }, - "settings": {}, + "settings": { + "callerPolicy": "workflowsFromSameOwner", + "availableInMCP": false + }, "triggerCount": 0, - "versionId": "ea21c736-ae26-492b-aa4d-f1096fa4af15", + "versionId": "1e834370-1769-4d3d-a487-f84a442b2dcc", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", diff --git a/workflows/qR33gCw5iScfnj9s.json b/workflows/qR33gCw5iScfnj9s.json index d0c54b3..3cf6385 100644 --- a/workflows/qR33gCw5iScfnj9s.json +++ b/workflows/qR33gCw5iScfnj9s.json @@ -59,69 +59,6 @@ "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2 }, - { - "parameters": { - "authentication": "privateKey", - "command": "/home/b3nw/.npm-global/bin/opencode run -m antigravity/gemini-3-pro-preview --title \"quota-reset-gemini-pro\" \"ping\"", - "cwd": "~/" - }, - "id": "ssh-gemini-pro", - "name": "SSH - Gemini Pro Ping", - "position": [ - 304, - 0 - ], - "type": "n8n-nodes-base.ssh", - "typeVersion": 1, - "credentials": { - "sshPrivateKey": { - "id": "S2dcVMjrpg0I0kdV", - "name": "vscode-dev.local.ben.io" - } - } - }, - { - "parameters": { - "authentication": "privateKey", - "command": "/home/b3nw/.npm-global/bin/opencode run -m antigravity/gemini-3-flash --title \"quota-reset-gemini-flash\" \"ping\"", - "cwd": "~/" - }, - "id": "ssh-gemini-flash", - "name": "SSH - Gemini Flash Ping", - "position": [ - 304, - 208 - ], - "type": "n8n-nodes-base.ssh", - "typeVersion": 1, - "credentials": { - "sshPrivateKey": { - "id": "S2dcVMjrpg0I0kdV", - "name": "vscode-dev.local.ben.io" - } - } - }, - { - "parameters": { - "authentication": "privateKey", - "command": "/home/b3nw/.npm-global/bin/opencode run -m antigravity/claude-sonnet-4-5 --title \"quota-reset-claude\" \"ping\"", - "cwd": "~/" - }, - "id": "ssh-claude", - "name": "SSH - Claude Ping", - "position": [ - 304, - 400 - ], - "type": "n8n-nodes-base.ssh", - "typeVersion": 1, - "credentials": { - "sshPrivateKey": { - "id": "S2dcVMjrpg0I0kdV", - "name": "vscode-dev.local.ben.io" - } - } - }, { "parameters": { "rule": { @@ -161,6 +98,176 @@ "name": "vscode-dev.local.ben.io" } } + }, + { + "parameters": { + "model": { + "__rl": true, + "value": "antigravity/gemini-3-flash", + "mode": "list", + "cachedResultName": "antigravity/gemini-3-flash" + }, + "responsesApiEnabled": false, + "options": { + "responseFormat": "text" + } + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [ + 1488, + 592 + ], + "id": "6692ee3d-d084-4e2e-baf1-57f4dc54e31f", + "name": "antigravity/gemini-3-flash-preview", + "credentials": { + "openAiApi": { + "id": "sxSUdecXdMfKPuTu", + "name": "llm-proxy.ext.ben.io" + } + } + }, + { + "parameters": { + "promptType": "define", + "text": "Hello, what is today's date?", + "batching": {} + }, + "type": "@n8n/n8n-nodes-langchain.chainLlm", + "typeVersion": 1.9, + "position": [ + 416, + 400 + ], + "id": "95a4b9f5-fd77-4425-9381-dfaa47de5f48", + "name": "Firmware Wakeup" + }, + { + "parameters": { + "model": { + "__rl": true, + "value": "firmware/openai/gpt-5.2", + "mode": "list", + "cachedResultName": "firmware/openai/gpt-5.2" + }, + "responsesApiEnabled": false, + "options": { + "responseFormat": "text" + } + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [ + 416, + 592 + ], + "id": "dcab26b6-21f7-4204-b82e-00e171bd6cde", + "name": "openai/gpt-5.2", + "credentials": { + "openAiApi": { + "id": "sxSUdecXdMfKPuTu", + "name": "llm-proxy.ext.ben.io" + } + } + }, + { + "parameters": { + "model": { + "__rl": true, + "mode": "id", + "value": "antigravity/claude-sonnet-4.5" + }, + "responsesApiEnabled": false, + "options": { + "responseFormat": "text" + } + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [ + 768, + 592 + ], + "id": "5ba81750-7c3d-4d0c-902f-8ec17c4a031d", + "name": "claude-sonnet-4.5", + "credentials": { + "openAiApi": { + "id": "sxSUdecXdMfKPuTu", + "name": "llm-proxy.ext.ben.io" + } + } + }, + { + "parameters": { + "model": { + "__rl": true, + "mode": "id", + "value": "antigravity/gemini-3-pro-preview" + }, + "responsesApiEnabled": false, + "options": { + "responseFormat": "text" + } + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [ + 1120, + 592 + ], + "id": "8771c6b9-3a31-4232-adf1-d6b3e20f4db3", + "name": "gemini-3-pro-preview", + "credentials": { + "openAiApi": { + "id": "sxSUdecXdMfKPuTu", + "name": "llm-proxy.ext.ben.io" + } + } + }, + { + "parameters": { + "promptType": "define", + "text": "Hello, what is today's date?", + "batching": {} + }, + "type": "@n8n/n8n-nodes-langchain.chainLlm", + "typeVersion": 1.9, + "position": [ + 768, + 400 + ], + "id": "bacdcfb7-e50c-489c-ad4f-3d6db55d717e", + "name": "Antigravity Wakeup Claude" + }, + { + "parameters": { + "promptType": "define", + "text": "Hello, what is today's date?", + "batching": {} + }, + "type": "@n8n/n8n-nodes-langchain.chainLlm", + "typeVersion": 1.9, + "position": [ + 1488, + 400 + ], + "id": "7b32e064-bc9e-4336-b97d-835df22495d4", + "name": "Antigravity Wakeup G3Flash" + }, + { + "parameters": { + "promptType": "define", + "text": "Hello, what is today's date?", + "batching": {} + }, + "type": "@n8n/n8n-nodes-langchain.chainLlm", + "typeVersion": 1.9, + "position": [ + 1120, + 400 + ], + "id": "7ccf1ef6-7fab-4193-bef5-09056f483e19", + "name": "Antigravity Wakeup G3Pro" } ], "connections": { @@ -168,19 +275,9 @@ "main": [ [ { - "index": 0, - "node": "SSH - Gemini Pro Ping", - "type": "main" - }, - { - "index": 0, - "node": "SSH - Gemini Flash Ping", - "type": "main" - }, - { - "index": 0, - "node": "SSH - Claude Ping", - "type": "main" + "node": "Firmware Wakeup", + "type": "main", + "index": 0 } ] ] @@ -189,19 +286,9 @@ "main": [ [ { - "index": 0, - "node": "SSH - Gemini Pro Ping", - "type": "main" - }, - { - "index": 0, - "node": "SSH - Gemini Flash Ping", - "type": "main" - }, - { - "index": 0, - "node": "SSH - Claude Ping", - "type": "main" + "node": "Firmware Wakeup", + "type": "main", + "index": 0 } ] ] @@ -210,19 +297,9 @@ "main": [ [ { - "index": 0, - "node": "SSH - Gemini Pro Ping", - "type": "main" - }, - { - "index": 0, - "node": "SSH - Gemini Flash Ping", - "type": "main" - }, - { - "index": 0, - "node": "SSH - Claude Ping", - "type": "main" + "node": "Firmware Wakeup", + "type": "main", + "index": 0 } ] ] @@ -237,6 +314,83 @@ } ] ] + }, + "antigravity/gemini-3-flash-preview": { + "ai_languageModel": [ + [ + { + "node": "Antigravity Wakeup G3Flash", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "Firmware Wakeup": { + "main": [ + [ + { + "node": "Antigravity Wakeup Claude", + "type": "main", + "index": 0 + } + ] + ] + }, + "openai/gpt-5.2": { + "ai_languageModel": [ + [ + { + "node": "Firmware Wakeup", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "claude-sonnet-4.5": { + "ai_languageModel": [ + [ + { + "node": "Antigravity Wakeup Claude", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "gemini-3-pro-preview": { + "ai_languageModel": [ + [ + { + "node": "Antigravity Wakeup G3Pro", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "Antigravity Wakeup Claude": { + "main": [ + [ + { + "node": "Antigravity Wakeup G3Pro", + "type": "main", + "index": 0 + } + ] + ] + }, + "Antigravity Wakeup G3Pro": { + "main": [ + [ + { + "node": "Antigravity Wakeup G3Flash", + "type": "main", + "index": 0 + } + ] + ] } }, "settings": { @@ -248,7 +402,7 @@ "availableInMCP": false }, "triggerCount": 4, - "versionId": "19367657-60c0-4f22-9cdf-84508906970c", + "versionId": "de114a0a-4971-4f4d-9969-59f44015336a", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", diff --git a/workflows/xXUnt2hL2FKxzOhBnkd3Z.json b/workflows/xXUnt2hL2FKxzOhBnkd3Z.json index 73e7acb..e62d81d 100644 --- a/workflows/xXUnt2hL2FKxzOhBnkd3Z.json +++ b/workflows/xXUnt2hL2FKxzOhBnkd3Z.json @@ -10,27 +10,27 @@ ] } }, - "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.3, + "name": "Schedule Trigger", + "type": "n8n-nodes-base.scheduleTrigger", "position": [ -832, - -16 + 496 ], - "id": "279ca2c5-3c3e-4cd6-bc97-e43010fb693c", - "name": "Schedule Trigger" + "id": "279ca2c5-3c3e-4cd6-bc97-e43010fb693c" }, { "parameters": { "options": {} }, - "type": "n8n-nodes-base.splitInBatches", "typeVersion": 3, - "position": [ - -192, - -16 - ], "id": "2412dfa0-8362-4cc4-8a18-3f233d5338b2", - "name": "Loop Over Items" + "type": "n8n-nodes-base.splitInBatches", + "name": "Loop Over Items", + "position": [ + -384, + 496 + ] }, { "parameters": { @@ -40,17 +40,17 @@ "genericAuthType": "httpHeaderAuth", "sendBody": true, "specifyBody": "json", - "jsonBody": "={\n \"dlLink\": \"1\",\n \"tor\": {\n \"text\": \" {{ $json.series_name }} m4b\",\n \"srchIn\": [\n \"title\",\n \"author\",\n \"filenames\",\n \"fileTypes\"\n ],\n \"main_cat\": [\n \"13\"\n ],\n \"searchType\": \"all\",\n \"sortType\": \"seedersDesc\"\n }\n}", + "jsonBody": "= {\n \"dlLink\": \"1\",\n \"tor\": {\n \"text\": \"{{ $json.series_name }}\",\n \"srchIn\": [\"series\"],\n \"main_cat\": [\"13\"],\n \"searchType\": \"all\",\n \"sortType\": \"seedersDesc\"\n }\n }\n\n", "options": {} }, - "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, - "position": [ - 48, - 0 - ], - "id": "f387a918-881e-492c-b41a-1dc621e8e822", "name": "HTTP Request", + "position": [ + -160, + 416 + ], + "type": "n8n-nodes-base.httpRequest", + "id": "f387a918-881e-492c-b41a-1dc621e8e822", "credentials": { "httpHeaderAuth": { "id": "G8eA8XeS9P5axwJd", @@ -69,135 +69,46 @@ }, "options": {} }, + "id": "019177aa-2c4f-4291-90f7-c58750033e77", "type": "n8n-nodes-base.aggregate", "typeVersion": 1, + "name": "Aggregate", "position": [ - 816, - 0 - ], - "id": "019177aa-2c4f-4291-90f7-c58750033e77", - "name": "Aggregate" + 736, + 272 + ] }, { "parameters": { "promptType": "define", - "text": "=You are a Librarian Agent tasked with reviewing potential new books that have been made available. Provide no commentary or additional information other than what is requested.\n\nWe are reviewing the series: {{ $('Loop Over Items').item.json.series_name }} by {{ $('Loop Over Items').item.json.author }}\n\nOur current library contains the following:\n{{ $json.book_name }}\n\nOur audiobook provider has the following avalible:\n{{ $('Aggregate Available Books').item.json.title }}\n\nProvide a json formatted list of books we are missing. If none, state \"No books missing\".", + "text": "=You are a Librarian checking for missing audiobooks.\n\n Series: {{ $('Loop Over Items').item.json.series_name }}\n\n Our library contains:\n {{ $json.book_name }}\n\n Available from provider (ID | Title):\n {{ $('Format for LLM').item.json.llm_available }}\n\n IMPORTANT MATCHING RULES:\n - Match by book NUMBER, not exact title. \"Book 7\", \"Vol. 7\", \"Volume 7\", and just \"7\" are the same.\n - Ignore subtitles after the number. \"The Primal Hunter 7\" matches \"The Primal Hunter 7 - A LitRPG Adventure\".\n - Ignore format differences like \"(Light Novel)\", \"[Audiobook]\", \"A LitRPG Saga\", etc.\n - If unsure, assume we already own it (don't include in missing).\n\n Return ONLY a JSON object with IDs of books we are CERTAIN we don't have:\n {\"missing_ids\": [123, 456]}\n\n If we have all books or are unsure, return: {\"missing_ids\": []}", "batching": {} }, - "type": "@n8n/n8n-nodes-langchain.chainLlm", + "id": "aef43627-3228-42ab-a2fe-9f4b50d1a855", "typeVersion": 1.9, "position": [ - 976, - 0 + 960, + 160 ], - "id": "aef43627-3228-42ab-a2fe-9f4b50d1a855", + "type": "@n8n/n8n-nodes-langchain.chainLlm", "name": "Basic LLM Chain", - "onError": "continueErrorOutput" + "retryOnFail": true, + "onError": "continueRegularOutput" }, { "parameters": { - "fieldToSplitOut": "data", - "options": { - "destinationFieldName": "data" - } - }, - "type": "n8n-nodes-base.splitOut", - "typeVersion": 1, - "position": [ - 256, - 0 - ], - "id": "a857682f-9e3d-4e87-8b06-699b9c8039c2", - "name": "Split Out" - }, - { - "parameters": { - "model": { - "__rl": true, - "value": "gemini_cli/gemini-2.5-flash", - "mode": "list", - "cachedResultName": "gemini_cli/gemini-2.5-flash" - }, - "responsesApiEnabled": false, - "options": { - "responseFormat": "json_object", - "maxRetries": 2 - } - }, - "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", - "typeVersion": 1.3, - "position": [ - 976, - 208 - ], - "id": "c4da46be-36bf-463e-bb4a-756ef57d24d5", - "name": "OpenAI Chat Model", - "credentials": { - "openAiApi": { - "id": "sxSUdecXdMfKPuTu", - "name": "llm-proxy.ben.io" - } - } - }, - { - "parameters": { - "fieldsToAggregate": { - "fieldToAggregate": [ - { - "fieldToAggregate": "data.title" - }, - { - "fieldToAggregate": "=data.dl", - "renameField": true - }, - { - "fieldToAggregate": "data.id" - }, - { - "fieldToAggregate": "data.series_info" - }, - { - "fieldToAggregate": "data.author_info" - } - ] - }, - "options": {} - }, - "type": "n8n-nodes-base.aggregate", - "typeVersion": 1, - "position": [ - 464, - 0 - ], - "id": "d6f24deb-4863-41a7-a703-2c31e2363d45", - "name": "Aggregate Available Books" - }, - { - "parameters": { - "operation": "select", - "schema": { - "__rl": true, - "value": "public", - "mode": "list", - "cachedResultName": "public" - }, - "table": { - "__rl": true, - "value": "followed_series", - "mode": "list", - "cachedResultName": "followed_series" - }, - "returnAll": true, + "operation": "executeQuery", + "query": "SELECT * FROM followed_series\n WHERE active = true\n AND (last_checked_at IS NULL OR last_checked_at < NOW() - INTERVAL '24 hours')\n ORDER BY last_checked_at ASC NULLS FIRST\n", "options": {} }, + "name": "Select Series", "type": "n8n-nodes-base.postgres", + "id": "e7bc495c-a3e1-46b8-b0f1-ff49286344e7", "typeVersion": 2.6, "position": [ - -624, - -16 + -608, + 496 ], - "id": "e7bc495c-a3e1-46b8-b0f1-ff49286344e7", - "name": "Select Series", "credentials": { "postgres": { "id": "9grzZwW7Br6SzdV8", @@ -209,35 +120,35 @@ "parameters": { "operation": "select", "schema": { - "__rl": true, "mode": "list", - "value": "public" + "value": "public", + "__rl": true }, "table": { "__rl": true, + "cachedResultName": "smb_general_books", "value": "smb_general_books", - "mode": "list", - "cachedResultName": "smb_general_books" + "mode": "list" }, "returnAll": true, "where": { "values": [ { - "column": "series_name", - "value": "={{ $('Loop Over Items').item.json.series_name }}" + "column": "followed_series_id", + "value": "={{ $('Loop Over Items').item.json.id }}" } ] }, "options": {} }, - "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, + "name": "Select Book Titles", + "type": "n8n-nodes-base.postgres", "position": [ - 656, - 0 + 512, + 272 ], "id": "62be829f-6d74-4142-9420-eb4f6947df09", - "name": "Select Book Titles", "executeOnce": true, "credentials": { "postgres": { @@ -246,21 +157,6 @@ } } }, - { - "parameters": { - "mode": "combine", - "combineBy": "combineByPosition", - "options": {} - }, - "type": "n8n-nodes-base.merge", - "typeVersion": 3.2, - "position": [ - 1328, - 0 - ], - "id": "d8c36a24-7337-453f-b403-f5c631025096", - "name": "Merge" - }, { "parameters": { "workflowId": { @@ -271,38 +167,343 @@ "cachedResultName": "MAM Transmission Manager" }, "workflowInputs": { - "mappingMode": "defineBelow", - "value": {}, - "matchingColumns": [], "schema": [], + "matchingColumns": [], + "value": {}, + "mappingMode": "defineBelow", "attemptToConvertTypes": false, "convertFieldsToString": true }, "mode": "each", "options": {} }, + "position": [ + 1984, + 272 + ], "type": "n8n-nodes-base.executeWorkflow", "typeVersion": 1.3, - "position": [ - 1728, - 0 - ], "id": "b6ec81b3-4f62-494e-9437-1a31c82a0299", "name": "Call 'MAM Transmission Manager'" }, + { + "parameters": {}, + "type": "n8n-nodes-base.merge", + "typeVersion": 3.2, + "position": [ + 1312, + 416 + ], + "id": "0e6b9d38-bf8b-4f52-83a7-55256eec306a", + "name": "Merge" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "d8fe3601-6f8f-4502-8231-a533dad5e5e5", + "leftValue": "={{ $json.error }}", + "rightValue": "", + "operator": { + "type": "string", + "operation": "notExists", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 64, + 416 + ], + "id": "2291aa2e-fb6b-42ec-892b-ee2b015514f7", + "name": "Search Error Check" + }, + { + "parameters": { + "operation": "update", + "schema": { + "__rl": true, + "mode": "list", + "value": "public" + }, + "table": { + "__rl": true, + "value": "followed_series", + "mode": "list", + "cachedResultName": "followed_series" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "series_name": "={{ $('Loop Over Items').last().json.series_name }}", + "last_checked_at": "={{ $now }}" + }, + "matchingColumns": [ + "series_name" + ], + "schema": [ + { + "id": "id", + "displayName": "id", + "required": false, + "defaultMatch": true, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "series_name", + "displayName": "series_name", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": false + }, + { + "id": "author", + "displayName": "author", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "category", + "displayName": "category", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "smb_path", + "displayName": "smb_path", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "active", + "displayName": "active", + "required": false, + "defaultMatch": false, + "display": true, + "type": "boolean", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "created_at", + "displayName": "created_at", + "required": false, + "defaultMatch": false, + "display": true, + "type": "dateTime", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "updated_at", + "displayName": "updated_at", + "required": false, + "defaultMatch": false, + "display": true, + "type": "dateTime", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "mam_series_id", + "displayName": "mam_series_id", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "enrichment_status", + "displayName": "enrichment_status", + "required": false, + "defaultMatch": false, + "display": true, + "type": "string", + "canBeUsedToMatch": true, + "removed": true + }, + { + "id": "last_checked_at", + "displayName": "last_checked_at", + "required": false, + "defaultMatch": false, + "display": true, + "type": "dateTime", + "canBeUsedToMatch": true + } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + }, + "options": {} + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.6, + "position": [ + 2432, + 624 + ], + "id": "62ec7f39-4f13-4d8e-8676-076e281677a0", + "name": "Update rows in a table", + "credentials": { + "postgres": { + "id": "9grzZwW7Br6SzdV8", + "name": "n8n-media" + } + } + }, + { + "parameters": { + "model": { + "__rl": true, + "mode": "id", + "value": "gemini_cli/gemini-3-flash-preview" + }, + "responsesApiEnabled": false, + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", + "typeVersion": 1.3, + "position": [ + 960, + 352 + ], + "id": "c6a2245c-ad50-465c-813a-a8ec206110e1", + "name": "OpenAI Chat Model", + "credentials": { + "openAiApi": { + "id": "sxSUdecXdMfKPuTu", + "name": "llm-proxy.ext.ben.io" + } + } + }, { "parameters": { "language": "pythonNative", - "pythonCode": "# Output list\nresults = []\n\n# Access the input item\ninput_item = _items[0]['json']\n\n# Extract Lists\nmissing_books = input_item.get('data', [])\navailable_titles = input_item.get('title', [])\navailable_ids = input_item.get('id', [])\navailable_series_info = input_item.get('series_info', [])\navailable_author_info = input_item.get('author_info', [])\navailable_catnames = input_item.get('catname', []) # New: For category detection\navailable_tags = input_item.get('tags', []) # New: For category detection\n\n# 1. Match missing titles to their IDs & Metadata\nfor book_title in missing_books:\n # Normalize the target title\n target_clean = str(book_title).strip()\n \n # Find index\n found_index = -1\n for i, title in enumerate(available_titles):\n if str(title).strip() == target_clean:\n found_index = i\n break\n \n if found_index != -1:\n # Get raw data\n book_id = available_ids[found_index]\n series_raw = available_series_info[found_index]\n author_raw = available_author_info[found_index]\n catname_raw = available_catnames[found_index] if i < len(available_catnames) else \"\"\n tags_raw = available_tags[found_index] if i < len(available_tags) else \"\"\n \n # --- PARSING LOGIC ---\n \n # Parse Series\n meta_series = \"Unknown\"\n meta_book_num = \"0\"\n \n if isinstance(series_raw, str):\n import json\n try:\n series_raw = json.loads(series_raw)\n except:\n pass\n\n if isinstance(series_raw, dict):\n keys = list(series_raw.keys())\n if keys:\n data = series_raw[keys[0]]\n if isinstance(data, list) and len(data) >= 2:\n meta_series = str(data[0]).replace(''', \"'\")\n meta_book_num = str(data[1])\n\n # Parse Author\n meta_author = \"Unknown\"\n \n if isinstance(author_raw, str):\n import json\n try:\n author_raw = json.loads(author_raw)\n except:\n pass\n\n if isinstance(author_raw, dict):\n keys = list(author_raw.keys())\n if keys:\n meta_author = str(author_raw[keys[0]])\n\n # --- CATEGORY DETECTION ---\n # Check if \"Light Novel\" or \"Anime\" appears in category name or tags\n is_anime = \"Light Novel\" in str(catname_raw) or \"anime\" in str(tags_raw).lower()\n category = 'anime' if is_anime else 'general'\n\n # Construct download link\n full_url = f\"https://www.myanonamouse.net/tor/download.php?tid={book_id}\"\n \n results.append({\n \"json\": {\n # Metadata\n \"meta_title\": target_clean,\n \"meta_series\": meta_series,\n \"meta_book_number\": meta_book_num,\n \"meta_author\": meta_author,\n \"mam_id\": book_id,\n \"category\": category, # <--- Sending this to the DB now\n \n # Download Info\n \"dl_link\": full_url,\n \"torrent_hash\": \"PENDING\"\n }\n })\n\nreturn results" + "pythonCode": "import json\n\n# Format search results for LLM consumption\n# Filters by m4b, English, and outputs ID|Title pairs\n\ninput_json = _items[0].get('json', {})\n\n# Handle both response structures\nbody = input_json.get('body', {})\nsearch_data = []\n\nif isinstance(body, dict):\n search_data = body.get('data', [])\n\nif not search_data and isinstance(input_json, dict):\n search_data = input_json.get('data', [])\n\nif not isinstance(search_data, list):\n search_data = []\n\n# Filter and format\nfiltered = []\nllm_lines = []\n\nfor x in search_data:\n filetype = str(x.get('filetype', '')).lower()\n lang_code = str(x.get('lang_code', '')).upper()\n\n # Must be m4b and English\n if 'm4b' not in filetype or lang_code != 'ENG':\n continue\n\n book_id = x.get('id')\n title = str(x.get('title', '')).strip()\n\n if book_id and title:\n filtered.append(x)\n llm_lines.append(f\"{book_id} | {title}\")\n\n# Output: full data for later lookup + formatted string for LLM\nresults = [{\n \"json\": {\n \"search_data\": filtered, # Full objects for ID lookup later\n \"llm_available\": \"\\n\".join(llm_lines), # Formatted for LLM prompt\n \"count\": len(filtered)\n }\n}]\n\nreturn results" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ - 1520, - 0 + 272, + 400 ], - "id": "c7496e8c-e307-4659-bdd0-530cd5feeef3", - "name": "Create Download Links" + "id": "41482579-dd45-4e60-be7b-2e9f7d08f8b3", + "name": "Format for LLM" + }, + { + "parameters": { + "language": "pythonNative", + "pythonCode": "import json\nimport re\n\n# Build download links from LLM output\n# LLM returns {\"missing_ids\": [123, 456]}\n# We match IDs to the search_data stored earlier\n\n# Collect inputs\nsearch_data = []\nmissing_ids = []\n\nfor item in _items:\n data = item.get('json', {})\n\n # From Format for LLM - the full search data\n if 'search_data' in data:\n search_data = data['search_data']\n\n # From LLM - parse the text field\n if 'text' in data:\n try:\n parsed = json.loads(data['text'])\n # Handle various formats\n if 'missing_ids' in parsed:\n missing_ids = parsed['missing_ids']\n elif 'missing_books' in parsed:\n # Fallback if LLM uses old format\n missing_ids = parsed['missing_books']\n except:\n pass\n\n # Direct missing_ids field\n if 'missing_ids' in data:\n missing_ids = data['missing_ids']\n\n# Ensure missing_ids is a list\nif not isinstance(missing_ids, list):\n missing_ids = []\n\n# Convert to set of ints for lookup\ntry:\n missing_id_set = set(int(x) for x in missing_ids if x)\nexcept:\n missing_id_set = set()\n\nif not missing_id_set:\n return [{\"json\": {\"found\": False, \"reason\": \"No missing books identified\"}}]\n\n# Build search_data lookup by ID\nid_to_item = {}\nfor item in search_data:\n book_id = item.get('id')\n if book_id:\n id_to_item[int(book_id)] = item\n\n# Patterns to filter out (box sets, collections, etc.)\nSKIP_PATTERNS = [\n 'box set',\n 'boxset',\n 'collection',\n \"publisher's pack\",\n 'publishers pack',\n 'omnibus',\n 'complete series',\n 'books 1',\n 'books 2',\n 'books 3',\n ', books ',\n 'audio immersion tunnel',\n]\n\ndef should_skip_title(title):\n \"\"\"Check if title matches skip patterns.\"\"\"\n title_lower = title.lower()\n for pattern in SKIP_PATTERNS:\n if pattern in title_lower:\n return True\n # Skip if title ends with \"series\" (e.g., \"The Land: Chaos Seeds Series\")\n if title_lower.rstrip().endswith(' series'):\n return True\n return False\n\ndef normalize_title(title):\n \"\"\"Normalize title for deduplication - extracts core title without subtitles.\"\"\"\n t = title.lower().strip()\n # Remove common suffixes/subtitles\n t = re.sub(r'\\s*[-:]\\s*a\\s+litrpg.*$', '', t, flags=re.IGNORECASE)\n t = re.sub(r'\\s*\\(light novel\\).*$', '', t, flags=re.IGNORECASE)\n t = re.sub(r'\\s*\\(audiobook\\).*$', '', t, flags=re.IGNORECASE)\n t = re.sub(r'\\s*\\[.*?\\]', '', t) # Remove bracketed text like [B00I2VWW5U]\n # Remove trailing subtitle after number (e.g., \"Book 7 - The Subtitle\" -> \"Book 7\")\n t = re.sub(r'(\\d+)\\s*[-:]\\s*.+$', r'\\1', t)\n # Normalize \"Vol.\" variations\n t = re.sub(r'\\bvol\\.?\\s*', 'vol ', t, flags=re.IGNORECASE)\n t = re.sub(r'\\bvolume\\s*', 'vol ', t, flags=re.IGNORECASE)\n # Normalize \"Book\" to just number\n t = re.sub(r'\\bbook\\s+(\\d+)', r'\\1', t, flags=re.IGNORECASE)\n # Normalize spacing and punctuation\n t = re.sub(r'[,:]', '', t)\n t = re.sub(r'\\s+', ' ', t).strip()\n return t\n\nresults = []\nseen_ids = set() # For deduplication by ID\nseen_titles = set() # For deduplication by normalized title\n\nfor book_id in missing_id_set:\n # Dedupe by ID check\n if book_id in seen_ids:\n continue\n seen_ids.add(book_id)\n\n if book_id not in id_to_item:\n continue\n\n item = id_to_item[book_id]\n title = str(item.get('title', '')).strip()\n\n # Skip box sets and collections\n if should_skip_title(title):\n continue\n\n # Dedupe by normalized title (prevents multiple uploads of same book)\n normalized = normalize_title(title)\n if normalized in seen_titles:\n continue\n seen_titles.add(normalized)\n\n # Parse series info\n meta_series = \"Unknown\"\n meta_book_num = \"0\"\n try:\n series_raw = item.get('series_info', '{}')\n s_data = json.loads(series_raw) if isinstance(series_raw, str) else series_raw\n if isinstance(s_data, dict) and s_data:\n for key, val in s_data.items():\n if isinstance(val, list) and len(val) >= 2:\n meta_series = str(val[0]).replace(''', \"'\")\n meta_book_num = str(val[1])\n break\n except:\n pass\n\n # Skip items with Unknown series\n if meta_series == \"Unknown\":\n continue\n\n # Parse author info\n meta_author = \"Unknown\"\n try:\n author_raw = item.get('author_info', '{}')\n a_data = json.loads(author_raw) if isinstance(author_raw, str) else author_raw\n if isinstance(a_data, dict) and a_data:\n meta_author = str(list(a_data.values())[0])\n except:\n pass\n\n results.append({\n \"json\": {\n \"meta_title\": title,\n \"meta_series\": meta_series,\n \"meta_book_number\": meta_book_num,\n \"meta_author\": meta_author,\n \"mam_id\": book_id,\n \"dl_link\": f\"https://www.myanonamouse.net/tor/download.php?tid={book_id}\",\n \"found\": True\n }\n })\n\nif not results:\n return [{\"json\": {\"found\": False, \"reason\": \"No matching IDs found after filtering\"}}]\n\nreturn results\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1536, + 416 + ], + "id": "fb4fed9b-1d34-4d07-85cf-ad3c50a52218", + "name": "Build Downloads from LLM" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "ab236a62-6482-49d4-b3b4-00d8a692cf61", + "leftValue": "={{ $json.dl_link }}", + "rightValue": "", + "operator": { + "type": "string", + "operation": "exists", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1760, + 416 + ], + "id": "a5baf7a6-b47f-4628-81f8-c5bf0c06f21a", + "name": "If" + }, + { + "parameters": {}, + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 1024, + 560 + ], + "id": "356d68c5-4ab8-4bbf-a91e-29567501d6e2", + "name": "do nothing" + }, + { + "parameters": {}, + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + 2208, + 416 + ], + "id": "48de6e72-1427-46aa-87ce-0225323b78b4", + "name": "do nothing 2" + }, + { + "parameters": {}, + "type": "n8n-nodes-base.noOp", + "typeVersion": 1, + "position": [ + -176, + 160 + ], + "id": "4b6e7bf3-ed30-4518-883c-b9e39bdbf982", + "name": "No Operation, do nothing" } ], "connections": { @@ -310,8 +511,8 @@ "main": [ [ { - "node": "Select Series", "type": "main", + "node": "Select Series", "index": 0 } ] @@ -319,13 +520,19 @@ }, "Loop Over Items": { "main": [ - [], [ { - "node": "HTTP Request", + "node": "No Operation, do nothing", "type": "main", "index": 0 } + ], + [ + { + "type": "main", + "index": 0, + "node": "HTTP Request" + } ] ] }, @@ -333,7 +540,7 @@ "main": [ [ { - "node": "Split Out", + "node": "Search Error Check", "type": "main", "index": 0 } @@ -344,18 +551,91 @@ "main": [ [ { - "node": "Basic LLM Chain", + "index": 0, + "type": "main", + "node": "Basic LLM Chain" + } + ] + ] + }, + "Select Book Titles": { + "main": [ + [ + { + "node": "Aggregate", "type": "main", "index": 0 } ] ] }, - "Split Out": { + "Select Series": { "main": [ [ { - "node": "Aggregate Available Books", + "node": "Loop Over Items", + "type": "main", + "index": 0 + } + ] + ] + }, + "Call 'MAM Transmission Manager'": { + "main": [ + [ + { + "node": "do nothing 2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Basic LLM Chain": { + "main": [ + [ + { + "node": "Merge", + "type": "main", + "index": 0 + } + ] + ] + }, + "Merge": { + "main": [ + [ + { + "node": "Build Downloads from LLM", + "type": "main", + "index": 0 + } + ] + ] + }, + "Search Error Check": { + "main": [ + [ + { + "node": "Format for LLM", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Update rows in a table", + "type": "main", + "index": 0 + } + ] + ] + }, + "Update rows in a table": { + "main": [ + [ + { + "node": "Loop Over Items", "type": "main", "index": 0 } @@ -373,7 +653,7 @@ ] ] }, - "Aggregate Available Books": { + "Format for LLM": { "main": [ [ { @@ -382,59 +662,25 @@ "index": 0 }, { - "node": "Merge", - "type": "main", - "index": 1 - } - ] - ] - }, - "Select Series": { - "main": [ - [ - { - "node": "Loop Over Items", + "node": "do nothing", "type": "main", "index": 0 } ] ] }, - "Select Book Titles": { + "Build Downloads from LLM": { "main": [ [ { - "node": "Aggregate", + "node": "If", "type": "main", "index": 0 } ] ] }, - "Basic LLM Chain": { - "main": [ - [ - { - "node": "Merge", - "type": "main", - "index": 0 - } - ], - [] - ] - }, - "Merge": { - "main": [ - [ - { - "node": "Create Download Links", - "type": "main", - "index": 0 - } - ] - ] - }, - "Create Download Links": { + "If": { "main": [ [ { @@ -442,21 +688,46 @@ "type": "main", "index": 0 } + ], + [ + { + "node": "do nothing 2", + "type": "main", + "index": 0 + } ] ] }, - "Call 'MAM Transmission Manager'": { + "do nothing": { "main": [ - [] + [ + { + "node": "Merge", + "type": "main", + "index": 1 + } + ] + ] + }, + "do nothing 2": { + "main": [ + [ + { + "node": "Update rows in a table", + "type": "main", + "index": 0 + } + ] ] } }, "settings": { "executionOrder": "v1", - "availableInMCP": false + "availableInMCP": false, + "callerPolicy": "workflowsFromSameOwner" }, "triggerCount": 1, - "versionId": "8a3af598-bd9e-48e1-a025-cb2059cfebbd", + "versionId": "1b02aafb-2cc4-4541-a26f-be001d7e7741", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj",