init after v1 corruption
This commit is contained in:
15
credential_stubs/0g0kU1diEfzjmF4T.json
Normal file
15
credential_stubs/0g0kU1diEfzjmF4T.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "0g0kU1diEfzjmF4T",
|
||||
"name": "Jina AI account",
|
||||
"type": "jinaAiApi",
|
||||
"data": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
15
credential_stubs/4niF5T7sRQdREmPy.json
Normal file
15
credential_stubs/4niF5T7sRQdREmPy.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "4niF5T7sRQdREmPy",
|
||||
"name": "SerpAPI account",
|
||||
"type": "serpApi",
|
||||
"data": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
19
credential_stubs/5ENroY8sSuOXtF9l.json
Normal file
19
credential_stubs/5ENroY8sSuOXtF9l.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "5ENroY8sSuOXtF9l",
|
||||
"name": "n8n-games",
|
||||
"type": "postgres",
|
||||
"data": {
|
||||
"host": "",
|
||||
"database": "",
|
||||
"user": "",
|
||||
"password": "",
|
||||
"ssl": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/7RU5tEhxEeWWpWU9.json
Normal file
16
credential_stubs/7RU5tEhxEeWWpWU9.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "7RU5tEhxEeWWpWU9",
|
||||
"name": "ben.io-cal",
|
||||
"type": "googleCalendarOAuth2Api",
|
||||
"data": {
|
||||
"clientId": "",
|
||||
"clientSecret": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
15
credential_stubs/7XEtlKn9X5YzJfiJ.json
Normal file
15
credential_stubs/7XEtlKn9X5YzJfiJ.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "7XEtlKn9X5YzJfiJ",
|
||||
"name": "NPM Admin Credentials",
|
||||
"type": "httpCustomAuth",
|
||||
"data": {
|
||||
"json": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/7dAOuF30uXa32ly3.json
Normal file
16
credential_stubs/7dAOuF30uXa32ly3.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "7dAOuF30uXa32ly3",
|
||||
"name": "ollama local",
|
||||
"type": "openAiApi",
|
||||
"data": {
|
||||
"apiKey": "",
|
||||
"url": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/9IAr5DfhRDKBMABQ.json
Normal file
16
credential_stubs/9IAr5DfhRDKBMABQ.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "9IAr5DfhRDKBMABQ",
|
||||
"name": "Header Auth - Gitea",
|
||||
"type": "httpHeaderAuth",
|
||||
"data": {
|
||||
"name": "",
|
||||
"value": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
15
credential_stubs/9IvQHDCVzfNfB9hU.json
Normal file
15
credential_stubs/9IvQHDCVzfNfB9hU.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "9IvQHDCVzfNfB9hU",
|
||||
"name": "base-1-flash-only",
|
||||
"type": "googlePalmApi",
|
||||
"data": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/9UFJNgYndzFIHJAf.json
Normal file
16
credential_stubs/9UFJNgYndzFIHJAf.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "9UFJNgYndzFIHJAf",
|
||||
"name": "Nano-GPT",
|
||||
"type": "openAiApi",
|
||||
"data": {
|
||||
"apiKey": "",
|
||||
"url": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
19
credential_stubs/9grzZwW7Br6SzdV8.json
Normal file
19
credential_stubs/9grzZwW7Br6SzdV8.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media",
|
||||
"type": "postgres",
|
||||
"data": {
|
||||
"host": "",
|
||||
"database": "",
|
||||
"user": "",
|
||||
"password": "",
|
||||
"ssl": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/A66wBBekagQkUFzd.json
Normal file
16
credential_stubs/A66wBBekagQkUFzd.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "A66wBBekagQkUFzd",
|
||||
"name": "bhw-gmail",
|
||||
"type": "gmailOAuth2",
|
||||
"data": {
|
||||
"clientId": "",
|
||||
"clientSecret": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/BRiFacyi0A60Y7ZZ.json
Normal file
16
credential_stubs/BRiFacyi0A60Y7ZZ.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "BRiFacyi0A60Y7ZZ",
|
||||
"name": "mam_id",
|
||||
"type": "httpBasicAuth",
|
||||
"data": {
|
||||
"user": "",
|
||||
"password": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
15
credential_stubs/ERgv4rlSLgq8fsK9.json
Normal file
15
credential_stubs/ERgv4rlSLgq8fsK9.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "ERgv4rlSLgq8fsK9",
|
||||
"name": "OpenRouter account",
|
||||
"type": "openRouterApi",
|
||||
"data": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/ErcYIjq8Xs0GRaPF.json
Normal file
16
credential_stubs/ErcYIjq8Xs0GRaPF.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "ErcYIjq8Xs0GRaPF",
|
||||
"name": "ben.io-gtasks",
|
||||
"type": "googleTasksOAuth2Api",
|
||||
"data": {
|
||||
"clientId": "",
|
||||
"clientSecret": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/G8eA8XeS9P5axwJd.json
Normal file
16
credential_stubs/G8eA8XeS9P5axwJd.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "G8eA8XeS9P5axwJd",
|
||||
"name": "mam cookie auth header",
|
||||
"type": "httpHeaderAuth",
|
||||
"data": {
|
||||
"name": "",
|
||||
"value": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": true
|
||||
}
|
||||
16
credential_stubs/GDcJYxgCLDRHH8m7.json
Normal file
16
credential_stubs/GDcJYxgCLDRHH8m7.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "GDcJYxgCLDRHH8m7",
|
||||
"name": "bhw2249-gsheets",
|
||||
"type": "googleSheetsOAuth2Api",
|
||||
"data": {
|
||||
"clientId": "",
|
||||
"clientSecret": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
19
credential_stubs/Ik8CFyap8ic2Md3M.json
Normal file
19
credential_stubs/Ik8CFyap8ic2Md3M.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "Ik8CFyap8ic2Md3M",
|
||||
"name": "n8n-infra",
|
||||
"type": "postgres",
|
||||
"data": {
|
||||
"host": "",
|
||||
"database": "",
|
||||
"user": "",
|
||||
"password": "",
|
||||
"ssl": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
18
credential_stubs/N7YEwAQv26RHwoLx.json
Normal file
18
credential_stubs/N7YEwAQv26RHwoLx.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "N7YEwAQv26RHwoLx",
|
||||
"name": "SMTP account",
|
||||
"type": "smtp",
|
||||
"data": {
|
||||
"user": "",
|
||||
"password": "",
|
||||
"host": "",
|
||||
"hostName": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/QRBx9RMx4KoFwGgl.json
Normal file
16
credential_stubs/QRBx9RMx4KoFwGgl.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "QRBx9RMx4KoFwGgl",
|
||||
"name": "Nvidia account",
|
||||
"type": "openAiApi",
|
||||
"data": {
|
||||
"apiKey": "",
|
||||
"url": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
18
credential_stubs/S2dcVMjrpg0I0kdV.json
Normal file
18
credential_stubs/S2dcVMjrpg0I0kdV.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "S2dcVMjrpg0I0kdV",
|
||||
"name": "vscode-dev.local.ben.io",
|
||||
"type": "sshPrivateKey",
|
||||
"data": {
|
||||
"host": "",
|
||||
"username": "",
|
||||
"privateKey": "",
|
||||
"passphrase": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/SItxcw7hDLHB7DYY.json
Normal file
16
credential_stubs/SItxcw7hDLHB7DYY.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "SItxcw7hDLHB7DYY",
|
||||
"name": "Chutes AI",
|
||||
"type": "openAiApi",
|
||||
"data": {
|
||||
"apiKey": "",
|
||||
"url": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/TE5lVqfBn06ypxIU.json
Normal file
16
credential_stubs/TE5lVqfBn06ypxIU.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "TE5lVqfBn06ypxIU",
|
||||
"name": "cerebras.ai",
|
||||
"type": "openAiApi",
|
||||
"data": {
|
||||
"apiKey": "",
|
||||
"url": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/VHbUFo39yKkrSroG.json
Normal file
16
credential_stubs/VHbUFo39yKkrSroG.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "VHbUFo39yKkrSroG",
|
||||
"name": "ben.io-gmail",
|
||||
"type": "gmailOAuth2",
|
||||
"data": {
|
||||
"clientId": "",
|
||||
"clientSecret": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
15
credential_stubs/X0JzSAfmjzASQVau.json
Normal file
15
credential_stubs/X0JzSAfmjzASQVau.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "X0JzSAfmjzASQVau",
|
||||
"name": "supa base custom auth",
|
||||
"type": "httpCustomAuth",
|
||||
"data": {
|
||||
"json": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
15
credential_stubs/YMRbpffqZG7PHJb6.json
Normal file
15
credential_stubs/YMRbpffqZG7PHJb6.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "YMRbpffqZG7PHJb6",
|
||||
"name": "Google Gemini(PaLM) Api account",
|
||||
"type": "googlePalmApi",
|
||||
"data": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/Z7DyxPOpTq3ClEPl.json
Normal file
16
credential_stubs/Z7DyxPOpTq3ClEPl.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "Z7DyxPOpTq3ClEPl",
|
||||
"name": "n8n account",
|
||||
"type": "n8nApi",
|
||||
"data": {
|
||||
"apiKey": "",
|
||||
"baseUrl": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/dsnKfvOBMkgU21Lt.json
Normal file
16
credential_stubs/dsnKfvOBMkgU21Lt.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "dsnKfvOBMkgU21Lt",
|
||||
"name": "supabase postgres account",
|
||||
"type": "postgres",
|
||||
"data": {
|
||||
"host": "",
|
||||
"password": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/eKd8zVYf3jN5n9hq.json
Normal file
16
credential_stubs/eKd8zVYf3jN5n9hq.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "eKd8zVYf3jN5n9hq",
|
||||
"name": "bhw-gcal",
|
||||
"type": "googleCalendarOAuth2Api",
|
||||
"data": {
|
||||
"clientId": "",
|
||||
"clientSecret": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
18
credential_stubs/foyggnWz8Sv0OO5w.json
Normal file
18
credential_stubs/foyggnWz8Sv0OO5w.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "foyggnWz8Sv0OO5w",
|
||||
"name": "seed-1.dfw.ben.io",
|
||||
"type": "sshPrivateKey",
|
||||
"data": {
|
||||
"host": "",
|
||||
"port": 29000,
|
||||
"username": "",
|
||||
"privateKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
18
credential_stubs/gsbVEcmxwWZIB4ef.json
Normal file
18
credential_stubs/gsbVEcmxwWZIB4ef.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"id": "gsbVEcmxwWZIB4ef",
|
||||
"name": "IMAP account",
|
||||
"type": "imap",
|
||||
"data": {
|
||||
"user": "",
|
||||
"password": "",
|
||||
"host": "",
|
||||
"allowUnauthorizedCerts": true
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/iymUPilnVhfL3h5D.json
Normal file
16
credential_stubs/iymUPilnVhfL3h5D.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "iymUPilnVhfL3h5D",
|
||||
"name": "transmission",
|
||||
"type": "httpBasicAuth",
|
||||
"data": {
|
||||
"user": "",
|
||||
"password": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/j03eT0qxJpv7H5an.json
Normal file
16
credential_stubs/j03eT0qxJpv7H5an.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "j03eT0qxJpv7H5an",
|
||||
"name": "Zabbix account",
|
||||
"type": "zabbixApi",
|
||||
"data": {
|
||||
"url": "",
|
||||
"apiToken": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
17
credential_stubs/jBV4AQlsHxWnFXsp.json
Normal file
17
credential_stubs/jBV4AQlsHxWnFXsp.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "jBV4AQlsHxWnFXsp",
|
||||
"name": "homepage.sshkey",
|
||||
"type": "sshPrivateKey",
|
||||
"data": {
|
||||
"host": "",
|
||||
"username": "",
|
||||
"privateKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
15
credential_stubs/l1g3pgQImkg18AzR.json
Normal file
15
credential_stubs/l1g3pgQImkg18AzR.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "l1g3pgQImkg18AzR",
|
||||
"name": "Ollama account",
|
||||
"type": "ollamaApi",
|
||||
"data": {
|
||||
"baseUrl": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/lWyf2ikOGHTTwnSU.json
Normal file
16
credential_stubs/lWyf2ikOGHTTwnSU.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "lWyf2ikOGHTTwnSU",
|
||||
"name": "Supabase account",
|
||||
"type": "supabaseApi",
|
||||
"data": {
|
||||
"host": "",
|
||||
"serviceRole": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/nzr2GDHOdD1PjK08.json
Normal file
16
credential_stubs/nzr2GDHOdD1PjK08.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "nzr2GDHOdD1PjK08",
|
||||
"name": "Gitea account",
|
||||
"type": "giteaApi",
|
||||
"data": {
|
||||
"url": "",
|
||||
"accessToken": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
15
credential_stubs/o6A594KZAPzN3LTR.json
Normal file
15
credential_stubs/o6A594KZAPzN3LTR.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "o6A594KZAPzN3LTR",
|
||||
"name": "Brave Search account",
|
||||
"type": "braveSearchApi",
|
||||
"data": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/qQNqeSAtYrG6Vi5N.json
Normal file
16
credential_stubs/qQNqeSAtYrG6Vi5N.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "qQNqeSAtYrG6Vi5N",
|
||||
"name": "ben.io-gsheet",
|
||||
"type": "googleSheetsOAuth2Api",
|
||||
"data": {
|
||||
"clientId": "",
|
||||
"clientSecret": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/sxQYMPEq6GLbc9Av.json
Normal file
16
credential_stubs/sxQYMPEq6GLbc9Av.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "sxQYMPEq6GLbc9Av",
|
||||
"name": "n8n_auth_cookie",
|
||||
"type": "httpHeaderAuth",
|
||||
"data": {
|
||||
"name": "",
|
||||
"value": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/sxSUdecXdMfKPuTu.json
Normal file
16
credential_stubs/sxSUdecXdMfKPuTu.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "sxSUdecXdMfKPuTu",
|
||||
"name": "llm-proxy.ben.io",
|
||||
"type": "openAiApi",
|
||||
"data": {
|
||||
"apiKey": "",
|
||||
"url": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
16
credential_stubs/tcNjvEki2DGt40hn.json
Normal file
16
credential_stubs/tcNjvEki2DGt40hn.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "tcNjvEki2DGt40hn",
|
||||
"name": "bhw-gtasks",
|
||||
"type": "googleTasksOAuth2Api",
|
||||
"data": {
|
||||
"clientId": "",
|
||||
"clientSecret": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
19
credential_stubs/uYjjcswcBGrqXgpk.json
Normal file
19
credential_stubs/uYjjcswcBGrqXgpk.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "uYjjcswcBGrqXgpk",
|
||||
"name": "openrouter",
|
||||
"type": "openAiApi",
|
||||
"data": {
|
||||
"apiKey": "",
|
||||
"url": "",
|
||||
"header": true,
|
||||
"headerName": "",
|
||||
"headerValue": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
17
credential_stubs/vOzhicmbOwx1XDF8.json
Normal file
17
credential_stubs/vOzhicmbOwx1XDF8.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "vOzhicmbOwx1XDF8",
|
||||
"name": "seed-0.local.ben.io",
|
||||
"type": "sshPrivateKey",
|
||||
"data": {
|
||||
"host": "",
|
||||
"username": "",
|
||||
"privateKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
15
credential_stubs/y1IVnGq2Wiqy3HBf.json
Normal file
15
credential_stubs/y1IVnGq2Wiqy3HBf.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "y1IVnGq2Wiqy3HBf",
|
||||
"name": "Tavily account",
|
||||
"type": "tavilyApi",
|
||||
"data": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"ownedBy": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"isGlobal": false
|
||||
}
|
||||
60
folders.json
Normal file
60
folders.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"id": "of8yoeyjjIAhYdnQ",
|
||||
"name": "core",
|
||||
"parentFolderId": null,
|
||||
"homeProjectId": "FeLO36wNUAcn61Wj",
|
||||
"createdAt": "2025-10-13T01:30:54.855Z",
|
||||
"updatedAt": "2025-10-13T01:30:54.855Z"
|
||||
},
|
||||
{
|
||||
"id": "eWW72giJDI4fxlWw",
|
||||
"name": "NPM",
|
||||
"parentFolderId": null,
|
||||
"homeProjectId": "FeLO36wNUAcn61Wj",
|
||||
"createdAt": "2025-10-20T03:42:27.697Z",
|
||||
"updatedAt": "2025-10-20T03:42:27.697Z"
|
||||
},
|
||||
{
|
||||
"id": "kUg4HIPXraph3M0E",
|
||||
"name": "Audiobooks",
|
||||
"parentFolderId": null,
|
||||
"homeProjectId": "FeLO36wNUAcn61Wj",
|
||||
"createdAt": "2025-11-02T14:20:53.630Z",
|
||||
"updatedAt": "2025-11-02T14:20:53.630Z"
|
||||
},
|
||||
{
|
||||
"id": "6tDyZCwqELStb6Ik",
|
||||
"name": "MAM",
|
||||
"parentFolderId": null,
|
||||
"homeProjectId": "FeLO36wNUAcn61Wj",
|
||||
"createdAt": "2025-12-09T03:50:09.700Z",
|
||||
"updatedAt": "2025-12-09T03:50:09.700Z"
|
||||
},
|
||||
{
|
||||
"id": "OJ2UfPNUOAOHlllh",
|
||||
"name": "Personal Dev",
|
||||
"parentFolderId": null,
|
||||
"homeProjectId": "FeLO36wNUAcn61Wj",
|
||||
"createdAt": "2025-12-17T14:15:10.406Z",
|
||||
"updatedAt": "2025-12-17T14:15:10.406Z"
|
||||
},
|
||||
{
|
||||
"id": "HWgaFb7kLF649L7l",
|
||||
"name": "EVE",
|
||||
"parentFolderId": null,
|
||||
"homeProjectId": "FeLO36wNUAcn61Wj",
|
||||
"createdAt": "2025-12-21T15:55:12.366Z",
|
||||
"updatedAt": "2025-12-21T15:55:12.366Z"
|
||||
},
|
||||
{
|
||||
"id": "LTWZD96boqxk9sIs",
|
||||
"name": "Home Lab",
|
||||
"parentFolderId": null,
|
||||
"homeProjectId": "FeLO36wNUAcn61Wj",
|
||||
"createdAt": "2025-12-21T15:55:27.361Z",
|
||||
"updatedAt": "2025-12-21T15:55:27.361Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
66
tags.json
Normal file
66
tags.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"tags": [
|
||||
{
|
||||
"id": "FydpKYmttDwoZVAA",
|
||||
"name": "n8n-media"
|
||||
},
|
||||
{
|
||||
"id": "zrmVqhwdDmkuhhaQ",
|
||||
"name": "ai_tool"
|
||||
},
|
||||
{
|
||||
"id": "ct0Rtzpu15B497av",
|
||||
"name": "ai_agent"
|
||||
}
|
||||
],
|
||||
"mappings": [
|
||||
{
|
||||
"workflowId": "VUwFjFF2UhNout2T",
|
||||
"tagId": "zrmVqhwdDmkuhhaQ"
|
||||
},
|
||||
{
|
||||
"workflowId": "0gxdxCdYQ7oXk7gC",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "v3KQi4UoMlhH7JIW",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "f2rn29FKq1ejX2ax",
|
||||
"tagId": "zrmVqhwdDmkuhhaQ"
|
||||
},
|
||||
{
|
||||
"workflowId": "Mdopqz1Tq0OHDFq1",
|
||||
"tagId": "ct0Rtzpu15B497av"
|
||||
},
|
||||
{
|
||||
"workflowId": "Mdopqz1Tq0OHDFq1",
|
||||
"tagId": "zrmVqhwdDmkuhhaQ"
|
||||
},
|
||||
{
|
||||
"workflowId": "7kAZyLHOpYKg4riN",
|
||||
"tagId": "ct0Rtzpu15B497av"
|
||||
},
|
||||
{
|
||||
"workflowId": "cPWZKfrHOUSUZjIp",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "Z_YHsJaf_pyFQR6e7VuLo",
|
||||
"tagId": "zrmVqhwdDmkuhhaQ"
|
||||
},
|
||||
{
|
||||
"workflowId": "J3uKCCbSuQ1fdJkC",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "H6TZCHyiYOr1X6Xf",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "c3N3bYrOAy0rNGGq",
|
||||
"tagId": "zrmVqhwdDmkuhhaQ"
|
||||
}
|
||||
]
|
||||
}
|
||||
378
workflows/0gxdxCdYQ7oXk7gC.json
Normal file
378
workflows/0gxdxCdYQ7oXk7gC.json
Normal file
@@ -0,0 +1,378 @@
|
||||
{
|
||||
"id": "0gxdxCdYQ7oXk7gC",
|
||||
"name": "MAM Series Enricher",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "execute-workflow-trigger",
|
||||
"name": "Execute Workflow Trigger",
|
||||
"position": [
|
||||
224,
|
||||
640
|
||||
],
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "condition-1",
|
||||
"leftValue": "={{ $json.enrichment_status }}",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEquals"
|
||||
},
|
||||
"rightValue": "enriched"
|
||||
}
|
||||
],
|
||||
"combinator": "or"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "check-enrichment-needs",
|
||||
"name": "Check Enrichment Needs",
|
||||
"position": [
|
||||
448,
|
||||
640
|
||||
],
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "assign-1",
|
||||
"name": "success",
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"id": "assign-2",
|
||||
"name": "series_id",
|
||||
"type": "string",
|
||||
"value": "={{ $json.id }}"
|
||||
},
|
||||
{
|
||||
"id": "assign-3",
|
||||
"name": "series_name",
|
||||
"type": "string",
|
||||
"value": "={{ $json.series_name }}"
|
||||
},
|
||||
{
|
||||
"id": "assign-4",
|
||||
"name": "message",
|
||||
"type": "string",
|
||||
"value": "Series already fully enriched"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "already-enriched",
|
||||
"name": "Already Enriched",
|
||||
"position": [
|
||||
848,
|
||||
832
|
||||
],
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "condition-1",
|
||||
"leftValue": "={{ $json.mam_series_id }}",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
},
|
||||
"rightValue": ""
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "route-by-success",
|
||||
"name": "Route by Success",
|
||||
"position": [
|
||||
1472,
|
||||
432
|
||||
],
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=Find the Series ID for: {{ $json.series_name }} by {{ $json.author_folder }}",
|
||||
"hasOutputParser": true,
|
||||
"needsFallback": true,
|
||||
"options": {
|
||||
"systemMessage": "=You are an expert metadata librarian.\n\n### YOUR GOAL\nFind the unique Series ID (integer) on MyAnonaMouse (MAM) for the input Series Name.\n\n### YOUR TOOL\n- **search_mam**: Input is a search string. It searches Titles, Authors, and Series names. Returns a list of results.\n\n### EXECUTION STEPS\n1. **Direct Search:** Start by searching for the exact Series Name.\n2. **Author Strategy (Fallback):** If the series search fails or returns 0 results:\n - Search for the **Author's Name** (from `author_folder` or `author`).\n - Scan the list of books returned.\n - Look for ANY book that appears to belong to the target series (check `series_info`).\n - If you find a book in the series, extract the Series ID from its `series_info`.\n3. **Analyze:**\n - Look for `series_info` (e.g., `{\"669061\": [\"Series Title\", \"1\"]}`).\n - The Series ID is the key (`669061`).\n4. **Validation:** Ensure the found series name matches the user's request.\n\n### OUTPUT FORMAT\nReturn a single JSON object.\n- `mam_series_id`: The ID found (or null).\n- `found_series_name`: The exact series name from the `series_info` field.",
|
||||
"maxIterations": 20
|
||||
}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3,
|
||||
"position": [
|
||||
672,
|
||||
224
|
||||
],
|
||||
"id": "882d88ae-7d06-468f-9524-c7dbad701365",
|
||||
"name": "AI Agent",
|
||||
"alwaysOutputData": true,
|
||||
"onError": "continueErrorOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"value": "c3N3bYrOAy0rNGGq",
|
||||
"mode": "list",
|
||||
"cachedResultUrl": "/workflow/c3N3bYrOAy0rNGGq",
|
||||
"cachedResultName": "MAM Search API Tool"
|
||||
},
|
||||
"workflowInputs": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {
|
||||
"search_term": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('search_term', ``, 'string') }}"
|
||||
},
|
||||
"matchingColumns": [
|
||||
"search_term"
|
||||
],
|
||||
"schema": [
|
||||
{
|
||||
"id": "search_term",
|
||||
"displayName": "search_term",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"canBeUsedToMatch": true,
|
||||
"removed": false
|
||||
}
|
||||
],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": false
|
||||
}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
752,
|
||||
448
|
||||
],
|
||||
"id": "d8a735da-8f5b-4020-816a-1a889c546d44",
|
||||
"name": "Call 'MAM Search API Tool'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"value": "minimaxai/minimax-m2",
|
||||
"mode": "list",
|
||||
"cachedResultName": "minimaxai/minimax-m2"
|
||||
},
|
||||
"responsesApiEnabled": false,
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
816,
|
||||
656
|
||||
],
|
||||
"id": "54820ab8-524b-4a1a-a939-6e9d0ecf777f",
|
||||
"name": "OpenAI Chat Model",
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "QRBx9RMx4KoFwGgl",
|
||||
"name": "Nvidia account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"schemaType": "manual",
|
||||
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"mam_series_id\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"The unique Series ID found. Null if not found.\"\n },\n \"found_series_name\": {\n \"type\": [\"string\", \"null\"],\n \"description\": \"The name of the series exactly as it appears in the MAM database.\"\n }\n },\n \"required\": [\n \"mam_series_id\",\n \"found_series_name\"\n ]\n}",
|
||||
"autoFix": true
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
880,
|
||||
448
|
||||
],
|
||||
"id": "662ea129-ecfd-4232-ad37-200ad912ea62",
|
||||
"name": "Structured Output Parser"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "cc0ebae4-c92b-4d03-8786-9f7e4e6438f2",
|
||||
"name": "mam_series_id",
|
||||
"value": "={{ $json.output.mam_series_id }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "bd249040-fe5c-4776-9ee0-0cececd613d1",
|
||||
"name": "series_name",
|
||||
"value": "={{ $json.output.found_series_name }}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
1248,
|
||||
432
|
||||
],
|
||||
"id": "890dab5a-2306-40da-b498-564e77d1fb67",
|
||||
"name": "Edit Fields"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Check Enrichment Needs": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Already Enriched",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Workflow Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Enrichment Needs",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Route by Success": {
|
||||
"main": [
|
||||
[],
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Call 'MAM Search API Tool'": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"AI Agent": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
"OpenAI Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Structured Output Parser",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Structured Output Parser": {
|
||||
"ai_outputParser": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_outputParser",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Edit Fields": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Route by Success",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"saveExecutionProgress": true,
|
||||
"saveManualExecutions": true,
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "6979f441-1ec7-4907-84ab-0255a9f21c26",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": false
|
||||
}
|
||||
829
workflows/2tDusEAKIJ6bwDl2.json
Normal file
829
workflows/2tDusEAKIJ6bwDl2.json
Normal file
@@ -0,0 +1,829 @@
|
||||
{
|
||||
"id": "2tDusEAKIJ6bwDl2",
|
||||
"name": "EVE Character Bazar Monitor",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "minutes",
|
||||
"minutesInterval": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "Schedule Trigger",
|
||||
"name": "Schedule Trigger",
|
||||
"position": [
|
||||
2464,
|
||||
16
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return $input.all().map(item => {\n return {\n json: {\n ...item.json,\n // Ensure character_id is treated as null if missing/empty\n character_id: item.json.character_id || null\n }\n };\n});"
|
||||
},
|
||||
"id": "Check ID Code",
|
||||
"name": "Check ID Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2912,
|
||||
-80
|
||||
],
|
||||
"notes": "Debug: Check logic"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{ $json.character_id ? 'https://evewho.com/api/character/' + $json.character_id : 'http://skip-verification' }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Verify Character",
|
||||
"name": "Verify Character",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
3360,
|
||||
-8
|
||||
],
|
||||
"continueOnFail": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nreturn items.map(item => {\n const eveData = item.json.data; // Response from EveWho\n const compliance = {\n status: 'compliant',\n issues: []\n };\n\n if (!eveData) {\n // If no EveWho data (e.g. no character ID found or API failed), we can't verify mechanics.\n // But we can still let the AI check the text.\n compliance.status = 'manual_review';\n compliance.issues.push('No character ID found or verification failed');\n } else {\n // Check Wallet Balance (must be positive, but API doesn't show wallet)\n // Check Kill Rights (must be 0)\n // EveWho API returns: { info: { kill_rights: ... }, ... }\n // Note: EveWho API structure might vary. Assuming standard ESI-like or EveWho structure.\n \n // Actually, EveWho API is often just scraping or basic info.\n // Let's assume we check what we can.\n \n if (eveData.kill_rights && eveData.kill_rights > 0) {\n compliance.status = 'non_compliant';\n compliance.issues.push('Character has kill rights');\n }\n \n // Security Status check?\n if (eveData.sec_status && eveData.sec_status < -2.0) {\n // compliance.issues.push('Low security status');\n }\n }\n\n item.json.compliance_check = compliance;\n return item;\n});"
|
||||
},
|
||||
"id": "Compliance Logic",
|
||||
"name": "Compliance Logic",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
3584,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nreturn items.map(item => {\n const json = item.json;\n const newItemJson = { ...json }; // Clone to avoid mutation issues\n\n // Ensure post_id is preserved and valid\n if (!newItemJson.post_id) {\n // Try to recover from other fields if possible, or generate debug ID\n newItemJson.post_id = json.id || json.guid || (\"MISSING_\" + Math.random().toString(36).substring(7));\n }\n\n // Map AI output to status/issues\n if (newItemJson.output) {\n let aiData = {};\n if (typeof newItemJson.output === 'string') {\n try {\n aiData = JSON.parse(newItemJson.output);\n } catch (e) {\n aiData = { compliance_status: 'error', compliance_issues: ['Invalid JSON from AI'] };\n }\n } else if (typeof newItemJson.output === 'object') {\n aiData = newItemJson.output;\n }\n \n newItemJson.post_status = aiData.compliance_status || 'pending_manual_review';\n \n const issues = aiData.compliance_issues || [];\n newItemJson.compliance_issues = { issues: Array.isArray(issues) ? issues : [issues] };\n \n if (aiData.character_name) {\n newItemJson.character_name = aiData.character_name;\n }\n } else if (newItemJson.post_status) {\n // Bypass items (already have status)\n if (!newItemJson.compliance_issues) {\n newItemJson.compliance_issues = { issues: [] };\n }\n } else {\n // Fallback\n newItemJson.post_status = 'pending_manual_review';\n newItemJson.compliance_issues = { issues: ['No status determined'] };\n }\n\n return { json: newItemJson, pairedItem: item.pairedItem };\n});"
|
||||
},
|
||||
"id": "Prepare Data",
|
||||
"name": "Prepare Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
4608,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=You are a Compliance Officer for the EVE Online Character Bazaar.\nAnalyze the following forum post content.\nRules:\n1. Wallet balance must be positive.\n2. Kill rights must be disclosed (usually 'No kill rights').\n3. Jump clones must be disclosed (location and implants).\n4. Character location must be disclosed.\n\nReturn ONLY a JSON object with this structure:\n{\n \"compliance_status\": \"compliant\" | \"non_compliant\",\n \"compliance_issues\": [\"issue 1\", \"issue 2\"],\n \"character_name\": \"extracted name if found, else null\"\n}\nIf compliant, 'compliance_issues' must be an empty array.\nDo not include markdown formatting (```json). Just the raw JSON string.\n\nPost content is below\n--------------------------\n\n{{ $json.content }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "AI Compliance Agent",
|
||||
"name": "AI Compliance Agent",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 1.7,
|
||||
"position": [
|
||||
4032,
|
||||
-400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "z-ai/glm-4.6",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
4104,
|
||||
-176
|
||||
],
|
||||
"id": "98305d4e-91f6-4ca5-9883-326c4b4941d2",
|
||||
"name": "OpenRouter Chat Model",
|
||||
"credentials": {
|
||||
"openRouterApi": {
|
||||
"id": "ERgv4rlSLgq8fsK9",
|
||||
"name": "OpenRouter account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nreturn items.map(item => {\n // This node was previously extracting topic IDs from GUID/Link.\n // We want to keep that behavior for the \"Lookup Posts\" node which uses post_id (topic ID).\n \n const guid = item.json.guid || item.json.link || \"\";\n const match = guid.match(/topic[-/](\\d+)/) || guid.match(/\\/t\\/[^\\/]+\\/(\\d+)/);\n \n if (match) {\n item.json.post_id = match[1];\n }\n \n // Ensure 'ids' field is NOT overwritten here if it's meant for Character IDs later.\n // But wait, 'Prepare ID List' is BEFORE 'Lookup Posts'.\n // And 'Extract Character ID' is BEFORE 'Check ID Code'.\n \n // Let's check the workflow flow again.\n // RSS -> Filter WTS -> Extract Character ID -> Check ID Code -> Verify Character\n // RSS -> Filter WTS -> Prepare ID List -> Lookup Posts\n \n // Ah! There are PARALLEL branches!\n \n return item;\n});"
|
||||
},
|
||||
"id": "Prepare ID List",
|
||||
"name": "Prepare ID List",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2016,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return $input.all();"
|
||||
},
|
||||
"id": "Filter New/Updated",
|
||||
"name": "Filter New/Updated",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2688,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "1",
|
||||
"leftValue": "={{ $json.post_status }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "isNotEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "Check Pre-Filter",
|
||||
"name": "Check Pre-Filter",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
3808,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "1",
|
||||
"leftValue": "={{ $json.extracted_char_id }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "isNotEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "Split by ID",
|
||||
"name": "Split by ID",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
3136,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $('Prepare ID List').all();\nconst lookupResults = $input.all();\n\nreturn items.map(item => {\n const postId = item.json.post_id;\n // Ensure we handle potential type mismatches (string vs number)\n const match = lookupResults.find(r => String(r.json.post_id) === String(postId));\n \n item.json.exists = !!match;\n if (match) {\n item.json.db_status = match.json.post_status;\n item.json.db_compliance_issues = match.json.compliance_issues;\n item.json.last_reviewed_at = match.json.last_reviewed_at;\n }\n \n return item;\n});"
|
||||
},
|
||||
"id": "9d0240b8-2a28-46a1-828d-75bcc0e1fe89",
|
||||
"name": "Merge Manual",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2464,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const aiResults = $input.all(0);\nconst bypassItems = $input.all(1);\nconst originalAiItems = $input.all(2);\n\n// Merge AI results with originals to restore fields\nconst mergedAiItems = originalAiItems.map((item, index) => {\n // Assuming 1-to-1 order with AI results\n const aiResult = aiResults[index]; \n if (aiResult) {\n // Copy AI output fields to original item\n item.json.output = aiResult.json.output;\n item.json.text = aiResult.json.text;\n }\n return item;\n});\n\n// Combine with bypass items\nreturn [...bypassItems, ...mergedAiItems];"
|
||||
},
|
||||
"id": "b9a2804d-fc92-492c-8855-f94f4cb180ed",
|
||||
"name": "Merge AI Data Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
4384,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f1d2a3f6-7d92-46e5-b192-b1df531581a6",
|
||||
"name": "Pass Through",
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
4096,
|
||||
112
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst newItems = [];\nfor (const item of items) {\n const json = item.json;\n // Strict check for post_id\n if (json.post_id && json.post_id !== \"null\" && String(json.post_id).trim() !== \"\") {\n newItems.push({\n json: {\n id: json.id, // Preserve DB ID for update logic\n post_id: String(json.post_id),\n post_status: json.post_status || 'pending_manual_review',\n compliance_issues: json.compliance_issues || { issues: [] },\n last_reviewed_at: new Date().toISOString()\n }\n });\n }\n}\nreturn newItems;"
|
||||
},
|
||||
"id": "4dd8d766-7b16-4d4f-b84a-07954c90c42d",
|
||||
"name": "Clean Data Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
4832,
|
||||
-80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "=SELECT * FROM character_bazaar_posts WHERE post_id IN ('{{ $json.post_id }}')",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Lookup Posts",
|
||||
"name": "Lookup Posts",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.4,
|
||||
"position": [
|
||||
2240,
|
||||
-176
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "5ENroY8sSuOXtF9l",
|
||||
"name": "n8n-games"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "upsert",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "character_bazaar_posts"
|
||||
},
|
||||
"columns": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {
|
||||
"character_name": "={{ $json.character_name }}",
|
||||
"compliance_issues": "={{ $json.compliance_issues }}",
|
||||
"last_reviewed_at": "={{ $now }}",
|
||||
"post_id": "={{ $json.post_id }}",
|
||||
"post_status": "={{ $json.post_status }}"
|
||||
}
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "Store Results",
|
||||
"name": "Store Results",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.4,
|
||||
"position": [
|
||||
5056,
|
||||
-80
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "5ENroY8sSuOXtF9l",
|
||||
"name": "n8n-games"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{ $json.link }}.rss",
|
||||
"options": {}
|
||||
},
|
||||
"id": "8417128a-ba4f-48a0-92a7-c5e7e54db8e8",
|
||||
"name": "Fetch Forum Post",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"position": [
|
||||
0,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\n\nreturn items.map(item => {\n const rss = item.json.data;\n \n // DEBUG: Log the actual structure we're working with\n console.log('=== Parse Forum Post DEBUG ===');\n console.log('Raw data type:', typeof rss);\n console.log('Raw data length:', rss ? rss.length : 'null');\n console.log('First 500 chars:', rss ? rss.substring(0, 500) : 'null');\n \n // Extract Creator - try multiple patterns\n let creator = null;\n const creatorPatterns = [\n /<dc:creator><!\\[CDATA\\[(.*?)\\]\\]><\\/dc:creator>/,\n /<dc:creator>(.*?)<\\/dc:creator>/,\n /<span class=\"creator\"[^>]*>.*?<span[^>]* itemprop=\"name\">(.*?)<\\/span>/,\n /itemprop=\"author\"[^>]*>.*?<span[^>]* itemprop=\"name\">(.*?)<\\/span>/\n ];\n \n for (const pattern of creatorPatterns) {\n const match = rss.match(pattern);\n if (match) {\n creator = match[1];\n console.log('Creator found with pattern:', pattern.toString(), 'Value:', creator);\n break;\n }\n }\n \n // Extract Description - try multiple patterns\n let description = \"\";\n const descPatterns = [\n /<description><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/description>/,\n /<description>([\\s\\S]*?)<\\/description>/,\n /<div class=\"post\"[^>]* itemprop=\"text\">([\\s\\S]*?)<\\/div>/\n ];\n \n for (const pattern of descPatterns) {\n const match = rss.match(pattern);\n if (match) {\n description = match[1];\n console.log('Description found with pattern:', pattern.toString(), 'Length:', description.length);\n break;\n }\n }\n \n // Clean HTML entities\n const cleanDescription = description\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\");\n \n // Extract Qsna IDs - search in both raw and cleaned content\n const qsnaRegex = /qsna\\.eu\\/eve\\/characters\\/(\\d+)/g;\n const qsnaMatchesRaw = [...rss.matchAll(qsnaRegex)];\n const qsnaMatchesClean = [...cleanDescription.matchAll(qsnaRegex)];\n \n const qsnaIds = [...new Set([\n ...qsnaMatchesRaw.map(m => m[1]),\n ...qsnaMatchesClean.map(m => m[1])\n ])];\n \n console.log('Qsna matches in raw:', qsnaMatchesRaw.length);\n console.log('Qsna matches in clean:', qsnaMatchesClean.length);\n console.log('Final Qsna IDs:', qsnaIds);\n \n // Extract SkillQ Links - search in both raw and cleaned content\n const skillqRegex = /https?:\\/\\/skillq\\.net\\/char\\/[^\\/\\s]+\\/share\\/[a-f0-9\\-]+/g;\n const skillqMatchesRaw = rss.match(skillqRegex) || [];\n const skillqMatchesClean = cleanDescription.match(skillqRegex) || [];\n \n const skillqLinks = [...new Set([...skillqMatchesRaw, ...skillqMatchesClean])];\n \n console.log('SkillQ matches in raw:', skillqMatchesRaw.length);\n console.log('SkillQ matches in clean:', skillqMatchesClean.length);\n console.log('Final SkillQ links:', skillqLinks);\n \n item.json.creator_name = creator;\n item.json.qsna_ids = qsnaIds;\n item.json.skillq_links = skillqLinks;\n \n console.log('=== END DEBUG ===');\n \n return item;\n});"
|
||||
},
|
||||
"id": "ad7ed8fd-8460-4659-b9e2-ed0c50483b6c",
|
||||
"name": "Parse Forum Post",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
224,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://esi.evetech.net/latest/universe/ids/",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify([$json.creator_name]) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "b3cd5b60-30d6-4089-b51f-3ccad131d548",
|
||||
"name": "Resolve Creator ID",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"position": [
|
||||
448,
|
||||
-296
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst result = [];\n\n// DEBUG: Log input data structure\nconsole.log('=== Split SkillQ Links DEBUG ===');\nconsole.log('Input items length:', items.length);\nif (items.length > 0) {\n console.log('First item keys:', Object.keys(items[0].json));\n console.log('First item skillq_links:', items[0].json.skillq_links);\n console.log('First item post_id:', items[0].json.post_id);\n console.log('First item temp_id:', items[0].json.temp_id);\n}\n\nfor (const item of items) {\n const links = item.json.skillq_links || [];\n // Add temp_id to group back later\n item.json.temp_id = item.json.post_id || Math.random().toString(36).substring(7);\n\n console.log('Processing item with temp_id:', item.json.temp_id, 'links count:', links.length);\n\n if (links.length === 0) {\n item.json.scrape_skipped = true;\n result.push(item);\n console.log('No links found, marking as scrape_skipped');\n } else {\n console.log('Found links:', links);\n for (const link of links) {\n const newItem = {\n json: {\n ...item.json,\n skillq_url_to_scrape: link,\n scrape_skipped: false\n }\n };\n result.push(newItem);\n console.log('Created item for URL:', link);\n }\n }\n}\n\nconsole.log('Total output items:', result.length);\nconsole.log('=== END Split SkillQ Links DEBUG ===');\nreturn result;"
|
||||
},
|
||||
"id": "21333d91-85ba-4baa-a482-ec59241a076f",
|
||||
"name": "Split SkillQ Links",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
896,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "1",
|
||||
"leftValue": "={{ $json.scrape_skipped }}.toString()",
|
||||
"rightValue": "true",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "contains"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "67ec4026-ee76-4818-9018-1a289caefb76",
|
||||
"name": "If Scrape Needed",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
1120,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{ $json.skillq_url_to_scrape }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "36697258-f606-436e-a173-74577c77f502",
|
||||
"name": "Scrape SkillQ",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"position": [
|
||||
1344,
|
||||
-296
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst groups = {};\n\nfor (const item of items) {\n const id = item.json.temp_id;\n if (!groups[id]) {\n groups[id] = { ...item.json, scraped_ids: [], skillq_404: false };\n }\n \n if (!item.json.scrape_skipped) {\n const html = item.json.data || \"\";\n \n // Check for 404\n if (html.includes('<meta name=\"title\" content=\"SkillQ.net - 404\">')) {\n groups[id].skillq_404 = true;\n } else {\n // Extract ID\n const idMatch = html.match(/images\\.evetech\\.net\\/characters\\/(\\d+)\\/portrait/);\n if (idMatch) {\n groups[id].scraped_ids.push(idMatch[1]);\n }\n }\n }\n}\n\nreturn Object.values(groups).map(json => ({ json }));"
|
||||
},
|
||||
"id": "69371473-c0d1-400a-b7cc-6a3d4f996f87",
|
||||
"name": "Aggregate SkillQ",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1568,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\n\nreturn items.map(item => {\n const creatorId = item.json.characters ? item.json.characters[0].id : null;\n const creatorName = item.json.characters ? item.json.characters[0].name : item.json.creator_name;\n const qsnaIds = item.json.qsna_ids || [];\n const scrapedIds = item.json.scraped_ids || [];\n const linkIds = [...new Set([...qsnaIds, ...scrapedIds])]; // Unique IDs\n const title = (item.json.title || \"\").toUpperCase();\n \n let characterId = creatorId;\n let characterName = creatorName;\n let status = \"compliant\"; // Default\n \n // Compliance Logic\n if (item.json.skillq_404) {\n status = \"non_compliant_skillq_404\";\n } else if (linkIds.length > 0) {\n // Check if Creator ID matches any Link ID\n const match = linkIds.some(id => String(id) === String(creatorId));\n if (match) {\n status = \"compliant\";\n characterId = creatorId; // Confirmed\n } else {\n status = \"non_compliant_mismatch\";\n item.json.mismatch_ids = linkIds;\n }\n } else {\n // No Links\n if (title.includes(\"WTS\")) {\n status = \"non_compliant_missing_link\";\n } else if (title.includes(\"WTB\")) {\n status = \"ignored_wtb\";\n }\n }\n \n item.json.character_id = characterId;\n item.json.character_name = characterName;\n item.json.compliance_status_initial = status;\n \n return item;\n});"
|
||||
},
|
||||
"id": "91757929-87e4-480c-b2ca-6addf4e26542",
|
||||
"name": "Consolidate Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1792,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const creators = $input.all(0).map(i => i.json);\nconst posts = $input.all(1).map(i => i.json);\n\n// DEBUG: Log input structures\nconsole.log('=== Merge Creator Code DEBUG ===');\nconsole.log('Creators input length:', creators.length);\nconsole.log('Posts input length:', posts.length);\nconsole.log('First creator keys:', creators[0] ? Object.keys(creators[0]) : 'null');\nconsole.log('First post keys:', posts[0] ? Object.keys(posts[0]) : 'null');\nconsole.log('First post skillq_links:', posts[0] ? posts[0].skillq_links : 'null');\nconsole.log('First creator skillq_links:', creators[0] ? creators[0].skillq_links : 'null');\n\nconst result = posts.map((post, index) => {\n const creator = creators[index] || {};\n \n // CRITICAL FIX: Preserve ALL post data first, then add creator data\n // This ensures skillq_links and other post fields are not overwritten\n const merged = {\n json: {\n ...post, // Post data takes priority (includes skillq_links, creator_name, qsna_ids, etc.)\n ...creator // Creator data added (only characters array, should not overwrite existing fields)\n }\n };\n \n // DEBUG: Log merge result\n if (index === 0) {\n console.log('Merged result keys:', Object.keys(merged.json));\n console.log('Merged skillq_links:', merged.json.skillq_links);\n console.log('Merged creator_name:', merged.json.creator_name);\n console.log('Merged qsna_ids:', merged.json.qsna_ids);\n }\n \n return merged;\n});\n\nconsole.log('=== END Merge Creator Code DEBUG ===');\nreturn result;"
|
||||
},
|
||||
"id": "dac9a7f5-bff6-48de-a8a2-8a9222cb0b8a",
|
||||
"name": "Merge Creator Code",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
672,
|
||||
-176
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM character_bazaar_posts WHERE post_status IN ('new', 'updated') ORDER BY last_reviewed_at ASC NULLS FIRST LIMIT 10;",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "Poll Pending Posts",
|
||||
"name": "Poll Pending Posts",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2688,
|
||||
16
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "5ENroY8sSuOXtF9l",
|
||||
"name": "n8n-games"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Check ID Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split by ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Verify Character": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compliance Logic",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Compliance Logic": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Pre-Filter",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"OpenRouter Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "AI Compliance Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter New/Updated": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check ID Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check Pre-Filter": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge AI Data Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Pass Through",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "AI Compliance Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split by ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compliance Logic",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Verify Character",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Manual": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter New/Updated",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge AI Data Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"AI Compliance Agent": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge AI Data Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Pass Through": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge AI Data Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Clean Data Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare ID List": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Lookup Posts",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Lookup Posts": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Manual",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Clean Data Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Store Results",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Fetch Forum Post": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Forum Post",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Forum Post": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Resolve Creator ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge Creator Code",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split SkillQ Links": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If Scrape Needed",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If Scrape Needed": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Scrape SkillQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Aggregate SkillQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Scrape SkillQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Aggregate SkillQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Aggregate SkillQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Consolidate Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Consolidate Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare ID List",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Resolve Creator ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Creator Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Creator Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split SkillQ Links",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Poll Pending Posts",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Poll Pending Posts": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check ID Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveManualExecutions": true,
|
||||
"saveExecutionProgress": true,
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "87cd1f48-d40b-4a15-81e0-0c4e17058983",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "HWgaFb7kLF649L7l",
|
||||
"isArchived": false
|
||||
}
|
||||
67
workflows/3f-bV5cT-rE_QTQxPEZjk.json
Normal file
67
workflows/3f-bV5cT-rE_QTQxPEZjk.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"id": "3f-bV5cT-rE_QTQxPEZjk",
|
||||
"name": "change-io-review",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"path": "f122c212-7644-4634-8e1f-3a99a8b0e770",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
-656,
|
||||
0
|
||||
],
|
||||
"id": "998d5e15-941c-4a2b-b155-dec787e105a9",
|
||||
"name": "Webhook",
|
||||
"webhookId": "f122c212-7644-4634-8e1f-3a99a8b0e770"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "text",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-448,
|
||||
0
|
||||
],
|
||||
"id": "510007f6-2ef5-490b-a4a1-b35d2b2cbffa",
|
||||
"name": "Extract from File"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract from File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract from File": {
|
||||
"main": [
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "3f71c8c4-ce1c-49c8-8822-ba8a05bee6fd",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "of8yoeyjjIAhYdnQ",
|
||||
"isArchived": false
|
||||
}
|
||||
606
workflows/3l7tJfcRoA1T1o6g.json
Normal file
606
workflows/3l7tJfcRoA1T1o6g.json
Normal file
@@ -0,0 +1,606 @@
|
||||
{
|
||||
"id": "3l7tJfcRoA1T1o6g",
|
||||
"name": "NPM Auth Token Manager",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "start-node",
|
||||
"name": "Manual Trigger",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
240,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "months",
|
||||
"monthsInterval": 11
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-node",
|
||||
"name": "Schedule Token Refresh",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
240,
|
||||
480
|
||||
],
|
||||
"notes": "Runs every 11 months to check token validity and refresh if needed"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Check if existing token is still valid based on expiration date\nconst items_data = items || [];\n\nconsole.log('=== Token Validation Starting ===');\nconsole.log('Items received:', items_data.length);\n\n// If no token exists or Supabase query returned no data, proceed to generate new one\nif (items_data.length === 0) {\n console.log('❌ No items received from Supabase query.');\n console.log('→ Will generate new token.');\n return [\n {\n json: {\n tokenValid: false,\n reason: 'no_token_found',\n message: 'No token exists in database'\n }\n }\n ];\n}\n\nconst firstItem = items_data[0].json || {};\nconsole.log('First item keys:', Object.keys(firstItem));\n\n// Check if the item has actual token data\nif (Object.keys(firstItem).length === 0 || !firstItem.token) {\n console.log('❌ No token data found in Supabase result.');\n console.log('→ Will generate new token.');\n return [\n {\n json: {\n tokenValid: false,\n reason: 'no_token_found',\n message: 'Token field is empty or missing'\n }\n }\n ];\n}\n\nconst tokenRecord = firstItem;\nconst expiresAt = tokenRecord.expires_at;\n\n// Check if expires_at exists and is in the future\nif (!expiresAt) {\n console.log('⚠️ Token has no expiration date.');\n console.log('→ Will generate new token.');\n return [\n {\n json: {\n tokenValid: false,\n reason: 'no_expiration_date',\n message: 'Token missing expiration date',\n existingToken: tokenRecord\n }\n }\n ];\n}\n\n// Parse expiration date\nconst expirationDate = new Date(expiresAt);\nconst now = new Date();\nconst daysUntilExpiration = Math.floor((expirationDate - now) / (1000 * 60 * 60 * 24));\n\nconsole.log('=== Token Expiration Check ===');\nconsole.log('Current time:', now.toISOString());\nconsole.log('Token expires:', expirationDate.toISOString());\nconsole.log('Days until expiration:', daysUntilExpiration);\n\n// If token expires in less than 30 days, regenerate\nif (daysUntilExpiration < 30) {\n console.log('⚠️ Token expires in less than 30 days.');\n console.log('→ Will generate new token.');\n return [\n {\n json: {\n tokenValid: false,\n reason: 'expiring_soon',\n message: `Token expires in ${daysUntilExpiration} days`,\n daysUntilExpiration: daysUntilExpiration,\n existingToken: tokenRecord\n }\n }\n ];\n}\n\n// Token expiration looks good, proceed to API validation\nconsole.log('✅ Token expiration date is valid (', daysUntilExpiration, 'days remaining)');\nconsole.log('→ Next: Testing token against NPM API...');\nreturn [\n {\n json: {\n tokenValid: true,\n reason: 'expiration_valid',\n message: `Token expires in ${daysUntilExpiration} days`,\n daysUntilExpiration: daysUntilExpiration,\n existingToken: tokenRecord\n }\n }\n];"
|
||||
},
|
||||
"id": "validate-existing-token-node",
|
||||
"name": "Check Token Expiration",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
640,
|
||||
400
|
||||
],
|
||||
"alwaysOutputData": true,
|
||||
"notes": "Step 1: Validates token expiration date. Handles all edge cases for empty/missing data. Always returns a clear tokenValid boolean for routing."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://npm.dfw.ben.io/api/nginx/proxy-hosts",
|
||||
"options": {
|
||||
"timeout": 5000
|
||||
},
|
||||
"headerParametersUi": {
|
||||
"parameter": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "=Bearer {{ $json.existingToken.token }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "test-token-validity-node",
|
||||
"name": "Test Token Against NPM API",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1040,
|
||||
288
|
||||
],
|
||||
"notes": "Tests if the existing token actually works by making a test API call to NPM. Returns 200 if valid, 401 if invalid."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Check if the token actually works with NPM API\nconst response = $input.item.json;\nconst statusCode = response.statusCode || 200;\nconst previousValidation = $('Check Token Expiration').item.json;\n\nconsole.log('=== NPM Token API Test ===');\nconsole.log('API Response Status:', statusCode);\n\n// Token works if we get 200 OK\nif (statusCode === 200 || statusCode === 304) {\n console.log('✅ Token successfully validated against NPM API');\n console.log('Token expires in', previousValidation.daysUntilExpiration, 'days');\n return [{\n json: {\n tokenValid: true,\n apiVerified: true,\n daysUntilExpiration: previousValidation.daysUntilExpiration,\n existingToken: previousValidation.existingToken\n }\n }];\n}\n\n// Token is invalid (401 Unauthorized or other error)\nconsole.log('❌ Token validation failed against NPM API');\nconsole.log('Status code:', statusCode);\nreturn [{\n json: {\n tokenValid: false,\n apiVerified: false,\n reason: 'api_validation_failed',\n statusCode: statusCode,\n daysUntilExpiration: previousValidation.daysUntilExpiration\n }\n}];"
|
||||
},
|
||||
"id": "evaluate-token-test-node",
|
||||
"name": "Evaluate API Test Result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1248,
|
||||
288
|
||||
],
|
||||
"notes": "Evaluates the API test response. If 200/304, token is valid. If 401 or other error, token needs regeneration."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"boolean": [
|
||||
{
|
||||
"value1": "={{ $json.tokenValid }}",
|
||||
"value2": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "route-based-on-validity-node",
|
||||
"name": "API Test Passed?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1440,
|
||||
400
|
||||
],
|
||||
"notes": "Routes based on API validation result. True = token valid and working. False = regenerate token."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "console.log('✅ Token fully validated and working!');\nconsole.log('- Expiration: Valid (', $json.daysUntilExpiration, 'days remaining)');\nconsole.log('- API Test: Passed');\nconsole.log('No action needed. Next check in ~11 months.');\n\nreturn [{\n json: {\n status: 'skipped',\n message: 'Existing token is valid and working',\n daysUntilExpiration: $json.daysUntilExpiration,\n expiresAt: $json.existingToken.expires_at,\n apiVerified: true\n }\n}];"
|
||||
},
|
||||
"id": "token-still-valid-node",
|
||||
"name": "Token Valid - Skip",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1648,
|
||||
288
|
||||
],
|
||||
"notes": "Token passed both expiration and API validation. No regeneration needed."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://npm.dfw.ben.io/api/tokens",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpCustomAuth",
|
||||
"options": {}
|
||||
},
|
||||
"id": "npm-login-node",
|
||||
"name": "NPM Login",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1168,
|
||||
656
|
||||
],
|
||||
"credentials": {
|
||||
"httpCustomAuth": {
|
||||
"id": "7XEtlKn9X5YzJfiJ",
|
||||
"name": "NPM Admin Credentials"
|
||||
}
|
||||
},
|
||||
"notes": "Authenticates with NPM using stored credentials to generate new token"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://npm.dfw.ben.io/api/tokens?expiry=1y",
|
||||
"options": {},
|
||||
"headerParametersUi": {
|
||||
"parameter": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "=Bearer {{ $json.token }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "get-long-lived-token-node",
|
||||
"name": "Get Long-Lived Token",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1360,
|
||||
656
|
||||
],
|
||||
"notes": "Requests a 1-year long-lived token"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Extract the long-lived token from the response\nconst token = $input.item.json.token;\nconst expiresAt = $input.item.json.expires;\n\nconsole.log('Generated new NPM long-lived token');\nconsole.log('Expires at:', expiresAt);\n\nreturn {\n json: {\n token: token,\n expiresAt: expiresAt,\n generatedAt: new Date().toISOString(),\n tokenPreview: token.substring(0, 20) + '...'\n }\n};"
|
||||
},
|
||||
"id": "extract-token-node",
|
||||
"name": "Extract Token",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1568,
|
||||
656
|
||||
],
|
||||
"notes": "Extracts and formats the token for storage"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Prepare the record for Supabase storage\nconst inputData = $input.item.json;\n\nreturn {\n json: {\n service_name: 'npm_dfw',\n token: inputData.token,\n expires_at: inputData.expiresAt,\n generated_at: inputData.generatedAt,\n last_updated: new Date().toISOString()\n }\n};"
|
||||
},
|
||||
"id": "prepare-token-record",
|
||||
"name": "Prepare Token Record",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1760,
|
||||
656
|
||||
],
|
||||
"notes": "Formats the token data for Supabase storage"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Check if UPDATE found and updated a row\nconst input = $input.item.json || {};\nconst hasData = Object.keys(input).length > 0;\n\nconsole.log('=== Checking UPDATE Result ===');\nconsole.log('Input item:', JSON.stringify(input, null, 2));\nconsole.log('Has data:', hasData);\nconsole.log('Keys:', Object.keys(input));\n\n// Check if we have the key database fields\nconst hasToken = input.token !== undefined && input.token !== null;\nconst hasServiceName = input.service_name !== undefined && input.service_name !== null;\n\nconsole.log('Has token field:', hasToken);\nconsole.log('Has service_name field:', hasServiceName);\n\n// If UPDATE returned token data, it succeeded\nif (hasData && (hasToken || hasServiceName)) {\n console.log('✅ UPDATE succeeded - row was found and updated');\n return { json: { ...input, needsCreate: false, operation: 'updated' } };\n}\n\n// UPDATE found no rows - need to CREATE\nconsole.log('⚠️ UPDATE found no rows or returned empty - will CREATE new row');\nconst tokenData = $('Prepare Token Record').item.json;\nreturn { json: { ...tokenData, needsCreate: true, operation: 'create_needed' } };"
|
||||
},
|
||||
"id": "check-update-result",
|
||||
"name": "Did Update Succeed?",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2112,
|
||||
656
|
||||
],
|
||||
"alwaysOutputData": true,
|
||||
"notes": "Checks if UPDATE succeeded. If no rows were updated, routes to CREATE."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"boolean": [
|
||||
{
|
||||
"value1": "={{ $json.needsCreate }}",
|
||||
"value2": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "route-create-check",
|
||||
"name": "Need to Create?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2288,
|
||||
656
|
||||
],
|
||||
"notes": "Routes to CREATE if UPDATE found no rows (first run). Otherwise skips to success."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "console.log('✅ NPM Token successfully refreshed and stored');\nconsole.log('Token expires at:', $input.item.json.expires_at || 'N/A');\nconsole.log('Operation:', $input.item.json.operation || 'completed');\nconsole.log('Next refresh in ~11 months or when token has <30 days remaining');\n\nreturn {\n json: {\n status: 'success',\n operation: 'token_refreshed',\n message: 'NPM long-lived token refreshed and stored in Supabase',\n expiresAt: $input.item.json.expires_at\n }\n};"
|
||||
},
|
||||
"id": "success-notification",
|
||||
"name": "Log Success",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2800,
|
||||
848
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rules": {
|
||||
"values": [
|
||||
{
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 1
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.tokenValid }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
},
|
||||
"id": "f484389b-d67b-4d23-a498-4efb7afe257c"
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "Valid - Test API"
|
||||
},
|
||||
{
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 1
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.tokenValid }}",
|
||||
"rightValue": false,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
},
|
||||
"id": "ad992289-38cb-42b1-8eff-ff14b3fa47c4"
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "Invalid - Generate"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"fallbackOutput": "extra"
|
||||
}
|
||||
},
|
||||
"id": "switch-token-status",
|
||||
"name": "Route By Token Status",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3,
|
||||
"position": [
|
||||
848,
|
||||
400
|
||||
],
|
||||
"notes": "Routes based on tokenValid. Case 0: false=Generate. Case 1: true=Test API. Fallback case 2 for unexpected values."
|
||||
},
|
||||
{
|
||||
"id": "check-existing-token-node",
|
||||
"name": "Check Existing Token (Postgres)",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
448,
|
||||
400
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM npm_tokens WHERE service_name = 'npm_dfw' LIMIT 1;"
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "Ik8CFyap8ic2Md3M",
|
||||
"name": "n8n-infra"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Update Token in Supabase",
|
||||
"name": "Update Token in Postgres",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2000,
|
||||
400
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"options": {
|
||||
"queryParameterValues": "{{$json.token}},{{$json.expires_at}},{{$json.generated_at}},{{$json.last_updated}},{{$json.service_name}}"
|
||||
},
|
||||
"query": "UPDATE npm_tokens SET token = $1, expires_at = $2, generated_at = $3, last_updated = $4 WHERE service_name = $5;"
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "Ik8CFyap8ic2Md3M",
|
||||
"name": "n8n-infra"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Create Token in Supabase",
|
||||
"name": "Create Token in Postgres",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2400,
|
||||
500
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"options": {
|
||||
"queryParameterValues": "{{$json.service_name}},{{$json.token}},{{$json.expires_at}},{{$json.generated_at}},{{$json.last_updated}}"
|
||||
},
|
||||
"query": "INSERT INTO npm_tokens (service_name, token, expires_at, generated_at, last_updated) VALUES ($1, $2, $3, $4, $5);"
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "Ik8CFyap8ic2Md3M",
|
||||
"name": "n8n-infra"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Test Token Against NPM API": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Evaluate API Test Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Evaluate API Test Result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "API Test Passed?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"API Test Passed?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Token Valid - Skip",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "NPM Login",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"NPM Login": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Long-Lived Token",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get Long-Lived Token": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Token",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Token": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Token Record",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Did Update Succeed?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Need to Create?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Need to Create?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Log Success",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Create Token in Postgres",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check Token Expiration": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Route By Token Status",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Route By Token Status": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Test Token Against NPM API",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "NPM Login",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "NPM Login",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Manual Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Existing Token (Postgres)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Schedule Token Refresh": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Existing Token (Postgres)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check Existing Token (Postgres)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check Token Expiration",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Token Record": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update Token in Postgres",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Update Token in Postgres": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Did Update Succeed?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create Token in Postgres": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Log Success",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "a927774e-18ea-41b2-b3da-9dee36c2b7d1",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "of8yoeyjjIAhYdnQ",
|
||||
"isArchived": false
|
||||
}
|
||||
264
workflows/6S41oPplwN1S9Lz0.json
Normal file
264
workflows/6S41oPplwN1S9Lz0.json
Normal file
@@ -0,0 +1,264 @@
|
||||
{
|
||||
"id": "6S41oPplwN1S9Lz0",
|
||||
"name": "MAM Remote File Transfer",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "trigger-execute-workflow",
|
||||
"name": "When Called by Another Workflow",
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
224,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Extract directory path from destination path\nconst item = $input.first();\nconst destPath = item.json.dest_path || '';\n\n// Get everything before the last slash\nconst lastSlash = destPath.lastIndexOf('/');\nconst dirName = lastSlash >= 0 ? destPath.substring(0, lastSlash) : '';\n\nreturn [{\n json: {\n ...item.json,\n dir_name: dirName\n }\n}];"
|
||||
},
|
||||
"id": "code-extract-dirname",
|
||||
"name": "Extract Directory Name",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
448,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=mkdir -p '{{ $('Extract Directory Name').item.json.dir_name }}'"
|
||||
},
|
||||
"id": "ssh-mkdir",
|
||||
"name": "Create Directory on seed-0",
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
672,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "vOzhicmbOwx1XDF8",
|
||||
"name": "seed-0.local.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=scp -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/n8n.priv.key '{{ $('Extract Directory Name').item.json.source_path }}' 'root@{{ $('Extract Directory Name').item.json.dest_host }}:\"{{ $('Extract Directory Name').item.json.dest_path }}\"'"
|
||||
},
|
||||
"id": "ssh-scp",
|
||||
"name": "SCP from seed-1 to seed-0",
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
880,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "foyggnWz8Sv0OO5w",
|
||||
"name": "seed-1.dfw.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=stat -c '%s' '{{ $('Extract Directory Name').item.json.dest_path }}'"
|
||||
},
|
||||
"id": "ssh-verify",
|
||||
"name": "Verify File on seed-0",
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1104,
|
||||
304
|
||||
],
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "vOzhicmbOwx1XDF8",
|
||||
"name": "seed-0.local.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Verify file size matches expected\nconst item = $input.first(); // Output from ssh-verify (stdout)\nconst statOutput = item.json.stdout || item.json.output || '';\nconst actualSize = parseInt(statOutput.trim());\n\n// Get original context from earlier node\nconst originalItem = $('Extract Directory Name').item.json;\nconst expectedSize = originalItem.expected_size;\nconst destPath = originalItem.dest_path;\n\nconst verified = !isNaN(actualSize) && (!expectedSize || actualSize === expectedSize);\n\nreturn [{\n json: {\n ...originalItem, // Preserve full context\n status: verified ? 'success' : 'failed',\n final_path: destPath,\n verified: verified,\n actual_size: actualSize,\n expected_size: expectedSize,\n error: verified ? null : `Size mismatch: expected ${expectedSize}, got ${actualSize}`\n }\n}];"
|
||||
},
|
||||
"id": "code-check-size",
|
||||
"name": "Check File Size",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1328,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "condition-1",
|
||||
"leftValue": "={{ $json.verified }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-verified",
|
||||
"name": "File Verified?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1552,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "allIncomingItems",
|
||||
"options": {}
|
||||
},
|
||||
"id": "respond-success",
|
||||
"name": "Respond Success",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1984,
|
||||
224
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "allIncomingItems",
|
||||
"options": {}
|
||||
},
|
||||
"id": "respond-failed",
|
||||
"name": "Respond Failed",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1984,
|
||||
384
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When Called by Another Workflow": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Directory Name",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Directory Name": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Directory on seed-0",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create Directory on seed-0": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "SCP from seed-1 to seed-0",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"SCP from seed-1 to seed-0": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Verify File on seed-0",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Verify File on seed-0": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check File Size",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check File Size": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "File Verified?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"File Verified?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond Failed",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Respond Success",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"saveExecutionProgress": true,
|
||||
"saveManualExecutions": true,
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "0f762959-8423-4af4-92a9-82e006e0f90c",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": false
|
||||
}
|
||||
115
workflows/7kAZyLHOpYKg4riN.json
Normal file
115
workflows/7kAZyLHOpYKg4riN.json
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"id": "7kAZyLHOpYKg4riN",
|
||||
"name": "gemini-cli-vscode",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Defaults and Input Parsing\nconst input = items[0].json;\n\n// 1. Prompt Handling (Escape quotes)\nconst rawPrompt = input.prompt || \"\";\nconst safePrompt = rawPrompt.replace(/\"/g, '\\\\\"');\n\n// 2. Defaults\nconst model = input.model || \"gemini-3-flash-preview\";\nconst approvalMode = input['approval-mode'] || \"yolo\";\nconst startDir = input['starting-directory'] || \"~/projects/\";\n\n// 3. Construct Command\n// START: Base command with flags FIRST (model, approval-mode)\nconst geminiPath = \"/home/b3nw/.npm-global/bin/gemini\";\nlet cmd = `${geminiPath} --model \"${model}\" --approval-mode \"${approvalMode}\"`;\n\n// Handle Allowed MCP Servers\n// Logic: If input is provided and valid, use it. Otherwise, force \"\" to disable all.\nlet mcpInput = input['allowed-mcp-server-names'];\nlet mcpString = \"\";\n\n// Normalize input to a string (handle array or CSV string)\nif (mcpInput !== undefined && mcpInput !== null) {\n mcpString = Array.isArray(mcpInput) ? mcpInput.join(\" \") : mcpInput.toString().split(\",\").join(\" \");\n}\n\n// Append flag: Use the list if we have one, otherwise explicitly pass \"\"\nif (mcpString && mcpString.trim().length > 0) {\n cmd += ` --allowed-mcp-server-names ${mcpString}`;\n} else {\n // This ensures we explicitly disable MCP tools when no specific servers are named\n cmd += ` --allowed-mcp-server-names \"\"`;\n}\n\n// Handle Include Directories\nlet incDirInput = input['include-directories'];\nif (incDirInput) {\n const incDirList = Array.isArray(incDirInput) ? incDirInput.join(\",\") : incDirInput;\n if (incDirList.trim().length > 0) {\n cmd += ` --include-directories ${incDirList}`;\n }\n}\n\n// Handle Resume\nif (input.resume) {\n cmd += ` --resume \"${input.resume}\"`;\n}\n\n// Force json output\ncmd += ` --output-format json`;\n\n// END: Append the prompt LAST as the positional argument\ncmd += ` \"${safePrompt}\"`;\n\nreturn [\n {\n json: {\n command: cmd,\n starting_directory: startDir\n }\n }\n];"
|
||||
},
|
||||
"id": "61fecf15-8117-4a65-98ee-d3b019d2b89e",
|
||||
"name": "Construct Command",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
464,
|
||||
32
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "={{ $json.command }}",
|
||||
"cwd": "={{ $json.starting_directory }}"
|
||||
},
|
||||
"id": "4bfebf4e-2390-405e-ae3d-8ce8ff6ddde4",
|
||||
"name": "Run Gemini via SSH",
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
688,
|
||||
32
|
||||
],
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "S2dcVMjrpg0I0kdV",
|
||||
"name": "vscode-dev.local.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"inputSource": "jsonExample",
|
||||
"jsonExample": "{\n \"prompt\": \"Describe the current directory\",\n \"starting-directory\": \"~/projects/\",\n \"allowed-mcp-server-names\": \"\",\n \"resume\": \"\",\n \"approval-mode\": \"yolo\",\n \"model\": \"gemini-3-flash-preview\",\n \"include-directories\": []\n}"
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
224,
|
||||
32
|
||||
],
|
||||
"id": "6c73c52e-377a-44d3-9265-ade1ec3c0cf7",
|
||||
"name": "When Executed by Another Workflow"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Get raw inputs\nconst inputItem = items[0].json;\nconst rawOutput = inputItem.stdout;\nconst rawError = inputItem.stderr || \"\";\n\ntry {\n // 1. Parse the main JSON output\n const parsed = JSON.parse(rawOutput);\n\n // 2. Add code and signal to the 'stats' object\n // We ensure parsed.stats exists, then merge the new fields in\n parsed.stats = {\n ...parsed.stats,\n exit_code: inputItem.code,\n signal: inputItem.signal\n };\n\n // 3. Clean up Response (Flatten newlines to spaces)\n if (parsed.response && typeof parsed.response === 'string') {\n parsed.response = parsed.response.replace(/\\n+/g, ' ').trim();\n }\n\n // 4. Clean up Logs (Split into an array for better readability)\n // This removes the \\n characters and creates a clean list of strings\n const cleanLogs = rawError.split('\\n').filter(line => line.trim() !== '');\n\n return [{\n json: {\n ...parsed,\n cli_logs: cleanLogs\n }\n }];\n\n} catch (error) {\n // Fallback for parsing errors\n return [{\n json: {\n error: \"Failed to parse CLI output\",\n raw_output: rawOutput,\n // Still try to save the code/signal even on error\n stats: {\n exit_code: inputItem.code,\n signal: inputItem.signal\n },\n cli_logs: rawError.split('\\n')\n }\n }];\n}"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
896,
|
||||
32
|
||||
],
|
||||
"id": "b6372a48-5e24-4dc6-9b9b-692b6eed4ef4",
|
||||
"name": "Code in JavaScript"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Construct Command": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Run Gemini via SSH",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"When Executed by Another Workflow": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Construct Command",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Run Gemini via SSH": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code in JavaScript",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "c77abb27-e648-4382-a9d5-49aa2f38e03d",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "of8yoeyjjIAhYdnQ",
|
||||
"isArchived": false
|
||||
}
|
||||
148
workflows/A2F85aYzVDvQnUrc.json
Normal file
148
workflows/A2F85aYzVDvQnUrc.json
Normal file
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"id": "A2F85aYzVDvQnUrc",
|
||||
"name": "Project Orchestrator",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Get raw inputs\nconst inputItem = items[0].json;\nconst rawOutput = inputItem.stdout;\nconst rawError = inputItem.stderr || \"\";\n\n// If stdout is empty but stderr has content, the agent likely failed to start\nif (!rawOutput.trim() && rawError.trim()) {\n throw new Error(`OpenCode Agent failed to produce output. Error logs: ${rawError}`);\n}\n\ntry {\n // OpenCode outputs newline-delimited JSON (NDJSON)\n const lines = rawOutput.split('\\n').filter(line => line.trim() !== '');\n if (lines.length === 0) {\n throw new Error(\"No output events received from agent.\");\n }\n \n const events = lines.map(line => JSON.parse(line));\n\n // Extract text responses (agent's messages to user)\n const textEvents = events.filter(e => e.type === 'text');\n const response = textEvents.map(e => e.part?.text || '').join('\\n').trim();\n\n // Extract tool usage summary\n const toolEvents = events.filter(e => e.type === 'tool_use');\n const tools = toolEvents.map(e => ({\n tool: e.part?.tool,\n status: e.part?.state?.status,\n input: e.part?.state?.input\n }));\n\n // Extract step finish events for token/cost stats\n const stepFinishes = events.filter(e => e.type === 'step_finish');\n \n // Validation: If no steps finished and no text was produced, it's a failure\n if (stepFinishes.length === 0 && textEvents.length === 0) {\n throw new Error(\"Agent session ended prematurely without output.\");\n }\n\n const totalTokens = stepFinishes.reduce((acc, e) => {\n const t = e.part?.tokens || {};\n return {\n input: (acc.input || 0) + (t.input || 0),\n output: (acc.output || 0) + (t.output || 0),\n reasoning: (acc.reasoning || 0) + (t.reasoning || 0)\n };\n }, {});\n const totalCost = stepFinishes.reduce((acc, e) => acc + (e.part?.cost || 0), 0);\n\n // Get session info from first event\n const sessionID = events[0]?.sessionID || null;\n\n // Clean up logs\n const cleanLogs = rawError.split('\\n').filter(line => line.trim() !== '');\n\n return [{\n json: {\n response: response.replace(/\\n+/g, ' ').trim(),\n sessionID,\n stats: {\n exit_code: inputItem.code,\n signal: inputItem.signal,\n total_cost: totalCost,\n tokens: totalTokens,\n steps: stepFinishes.length,\n tool_calls: toolEvents.length\n },\n tools,\n cli_logs: cleanLogs\n }\n }];\n\n} catch (error) {\n // Fail the node if parsing or validation fails\n throw new Error(\"Failed to validate OpenCode output: \" + error.message);\n}\n"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-96,
|
||||
48
|
||||
],
|
||||
"name": "Code in JavaScript",
|
||||
"id": "2dd84c9e-0fc0-4b34-911e-4a2cea1aae86"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "/home/b3nw/projects/core/project-orchestrator/bin/run-agent.sh",
|
||||
"cwd": "/home/b3nw/projects/"
|
||||
},
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"position": [
|
||||
-304,
|
||||
48
|
||||
],
|
||||
"typeVersion": 1,
|
||||
"id": "f7a8fc32-699d-4e5b-afad-eb269855dbab",
|
||||
"name": "execute run_agent.sh",
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "S2dcVMjrpg0I0kdV",
|
||||
"name": "vscode-dev.local.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "/home/b3nw/projects/core/project-orchestrator/bin/session-cleanup.sh",
|
||||
"cwd": "/home/b3nw/projects/core/project-orchestrator"
|
||||
},
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"position": [
|
||||
-304,
|
||||
-128
|
||||
],
|
||||
"typeVersion": 1,
|
||||
"id": "67e5e087-a6db-4013-87b3-a7fbb02421d1",
|
||||
"name": "execute cleanup_sessions.sh",
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "S2dcVMjrpg0I0kdV",
|
||||
"name": "vscode-dev.local.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"daysInterval": 7,
|
||||
"triggerAtMinute": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
-528,
|
||||
-128
|
||||
],
|
||||
"id": "fc10c6c7-dd2c-4458-9d75-624f8a22124c",
|
||||
"name": "Weekly Trigger"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "Nightly Trigger",
|
||||
"id": "343d37fc-f49d-4a30-969d-11abfed0594f",
|
||||
"position": [
|
||||
-512,
|
||||
48
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"execute run_agent.sh": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"type": "main",
|
||||
"index": 0,
|
||||
"node": "Code in JavaScript"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Weekly Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "execute cleanup_sessions.sh",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Nightly Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "execute run_agent.sh",
|
||||
"index": 0,
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 2,
|
||||
"versionId": "ae676149-56f0-4096-89d3-cdb6ab12fe44",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "LTWZD96boqxk9sIs",
|
||||
"isArchived": false
|
||||
}
|
||||
270
workflows/ERCWB3oSYbgsUiqL.json
Normal file
270
workflows/ERCWB3oSYbgsUiqL.json
Normal file
@@ -0,0 +1,270 @@
|
||||
{
|
||||
"id": "ERCWB3oSYbgsUiqL",
|
||||
"name": "NPM SSL Certificate Monitor",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "cronExpression",
|
||||
"expression": "0 9 * * *"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-trigger",
|
||||
"name": "Daily SSL Check",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
240,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"tableId": "npm_tokens",
|
||||
"limit": 1,
|
||||
"matchType": "allFilters",
|
||||
"filters": {
|
||||
"conditions": [
|
||||
{
|
||||
"keyName": "service_name",
|
||||
"condition": "eq",
|
||||
"keyValue": "npm_dfw"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "get-npm-token",
|
||||
"name": "Get NPM Token",
|
||||
"type": "n8n-nodes-base.supabase",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
448,
|
||||
400
|
||||
],
|
||||
"credentials": {
|
||||
"supabaseApi": {
|
||||
"id": "lWyf2ikOGHTTwnSU",
|
||||
"name": "Supabase account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Process SSL certificates and check expiration\nconst certificates = $input.all();\nconst today = new Date();\nconst WARNING_DAYS = 7;\n\nconsole.log('=== NPM SSL Certificate Monitor ===');\nconsole.log('Total certificates:', certificates.length);\n\nconst certificateStatus = [];\nlet expiringCerts = [];\nlet skippedCount = 0;\n\nfor (const item of certificates) {\n const cert = item.json;\n \n // Skip if no expiration date or invalid\n if (!cert.expires_on || cert.expires_on === 0) {\n console.log('Skipping certificate (no expiration):', cert.nice_name || cert.id);\n skippedCount++;\n continue;\n }\n \n const domainNames = cert.domain_names?.join(', ') || cert.nice_name || `Certificate ${cert.id}`;\n \n // Create date and validate it\n const expiryDate = new Date(cert.expires_on * 1000);\n if (isNaN(expiryDate.getTime())) {\n console.log('Skipping certificate (invalid date):', domainNames);\n skippedCount++;\n continue;\n }\n \n const daysRemaining = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24));\n const isExpiring = daysRemaining <= WARNING_DAYS;\n \n let status = 'OK';\n if (daysRemaining < 0) {\n status = 'EXPIRED';\n } else if (isExpiring) {\n status = 'EXPIRING SOON';\n }\n \n const certInfo = {\n domain: domainNames,\n expiryDate: expiryDate.toISOString().split('T')[0],\n daysRemaining: daysRemaining,\n status: status,\n isExpiring: isExpiring\n };\n \n certificateStatus.push(certInfo);\n \n if (isExpiring) {\n expiringCerts.push(certInfo);\n console.log(`🔴 ${domainNames}: ${daysRemaining} days (expires ${certInfo.expiryDate})`);\n } else {\n console.log(`✅ ${domainNames}: ${daysRemaining} days`);\n }\n}\n\nconsole.log('\\n=== Summary ===');\nconsole.log('Valid certificates:', certificateStatus.length);\nconsole.log('Skipped (invalid/missing expiration):', skippedCount);\nconsole.log('Expiring within', WARNING_DAYS, 'days:', expiringCerts.length);\n\nreturn [{\n json: {\n totalCerts: certificateStatus.length,\n expiringCount: expiringCerts.length,\n hasExpiringCerts: expiringCerts.length > 0,\n allCertificates: certificateStatus,\n expiringCertificates: expiringCerts\n }\n}];"
|
||||
},
|
||||
"id": "process-certificates",
|
||||
"name": "Process SSL Certificates",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
848,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 1
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "f5646765-356f-4270-b42b-8e86a3a64568",
|
||||
"leftValue": "={{ $json.hasExpiringCerts }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "true",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "check-expiring",
|
||||
"name": "Any Expiring Certs?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1040,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Format warning email for expiring certificates\nconst data = $input.item.json;\nconst expiringCerts = data.expiringCertificates || [];\n\nlet tableRows = '';\nfor (const cert of expiringCerts) {\n const statusIcon = cert.status === 'EXPIRED' ? '🔴' : '⚠️';\n tableRows += `\n <tr>\n <td style=\"padding: 8px; border: 1px solid #ddd;\">${statusIcon} ${cert.domain}</td>\n <td style=\"padding: 8px; border: 1px solid #ddd;\">${cert.expiresOnFormatted}</td>\n <td style=\"padding: 8px; border: 1px solid #ddd; font-weight: bold; color: ${cert.daysRemaining <= 0 ? '#d32f2f' : '#f57c00'};\">${cert.daysRemaining} days</td>\n <td style=\"padding: 8px; border: 1px solid #ddd;\">${cert.status}</td>\n </tr>`;\n}\n\nconst subject = `⚠️ SSL Certificate Expiration Warning - ${expiringCerts.length} Certificate${expiringCerts.length > 1 ? 's' : ''}`;\n\nconst htmlBody = `\n<!DOCTYPE html>\n<html>\n<head>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }\n .container { max-width: 800px; margin: 0 auto; padding: 20px; }\n .header { background-color: #f57c00; color: white; padding: 20px; border-radius: 5px; margin-bottom: 20px; }\n .summary { background-color: #fff3e0; padding: 15px; border-radius: 5px; margin-bottom: 20px; border-left: 4px solid #f57c00; }\n table { width: 100%; border-collapse: collapse; margin: 20px 0; }\n th { background-color: #f5f5f5; padding: 10px; border: 1px solid #ddd; text-align: left; }\n .footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 12px; color: #666; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1>⚠️ SSL Certificate Expiration Warning</h1>\n </div>\n \n <div class=\"summary\">\n <h2>Summary</h2>\n <ul>\n <li><strong>${data.totalCertificates}</strong> SSL certificates checked</li>\n <li><strong>${expiringCerts.length}</strong> certificate${expiringCerts.length > 1 ? 's' : ''} expiring within ${data.warningDays} days</li>\n <li>Checked at: ${new Date(data.checkedAt).toLocaleString('en-US')}</li>\n </ul>\n </div>\n \n <h2>Certificates Requiring Attention</h2>\n <table>\n <thead>\n <tr>\n <th>Domain</th>\n <th>Expiration Date</th>\n <th>Days Remaining</th>\n <th>Status</th>\n </tr>\n </thead>\n <tbody>\n ${tableRows}\n </tbody>\n </table>\n \n <div class=\"footer\">\n <p><strong>Action Required:</strong> Please renew or update these SSL certificates in your Nginx Proxy Manager.</p>\n <p><a href=\"https://npm.dfw.ben.io\" style=\"color: #f57c00;\">Open Nginx Proxy Manager →</a></p>\n <p style=\"margin-top: 20px;\">This is an automated alert from your NPM SSL Certificate Monitor workflow.</p>\n </div>\n </div>\n</body>\n</html>\n`;\n\nreturn [{\n json: {\n subject: subject,\n htmlBody: htmlBody,\n to: 'admin@ben.io',\n expiringCount: expiringCerts.length\n }\n}];"
|
||||
},
|
||||
"id": "format-warning-email",
|
||||
"name": "Format Warning Email",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1248,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "manual-trigger",
|
||||
"name": "Manual Trigger",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
240,
|
||||
272
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"sendTo": "={{ $json.to }}",
|
||||
"subject": "={{ $json.subject }}",
|
||||
"message": "={{ $json.htmlBody }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "send-gmail",
|
||||
"name": "Send Gmail Alert",
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
1440,
|
||||
400
|
||||
],
|
||||
"webhookId": "c9d0a896-4275-4387-a2c8-af1f138559a9",
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "Os1ux3h3zFlC2XkG",
|
||||
"name": "Gmail account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://npm.dfw.ben.io/api/nginx/certificates",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "=Bearer {{ $json.token }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "fetch-certs",
|
||||
"name": "Fetch Certificates",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
640,
|
||||
400
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Daily SSL Check": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get NPM Token",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get NPM Token": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Certificates",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Process SSL Certificates": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Any Expiring Certs?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Manual Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get NPM Token",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Format Warning Email": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Gmail Alert",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Any Expiring Certs?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Format Warning Email",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Fetch Certificates": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Process SSL Certificates",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"saveExecutionProgress": true,
|
||||
"saveManualExecutions": true,
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "708d8124-5275-445c-92da-43b0f43e9826",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "eWW72giJDI4fxlWw",
|
||||
"isArchived": false
|
||||
}
|
||||
196
workflows/EVctKxhQ2eyGd3gD.json
Normal file
196
workflows/EVctKxhQ2eyGd3gD.json
Normal file
@@ -0,0 +1,196 @@
|
||||
{
|
||||
"id": "EVctKxhQ2eyGd3gD",
|
||||
"name": "GTasks: Get All Tasks",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"inputSource": "passthrough"
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-496,
|
||||
-32
|
||||
],
|
||||
"id": "4d0aa270-fd61-4aaf-94e0-38546c9f332a",
|
||||
"name": "When Executed by Another Workflow"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://tasks.googleapis.com/tasks/v1/users/@me/lists",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "googleTasksOAuth2Api",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
-288,
|
||||
-32
|
||||
],
|
||||
"id": "27dac4dc-08a7-4dd5-b526-a600d7aa3133",
|
||||
"name": "HTTP Request",
|
||||
"credentials": {
|
||||
"googleTasksOAuth2Api": {
|
||||
"id": "ErcYIjq8Xs0GRaPF",
|
||||
"name": "ben.io-gtasks"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "items",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-80,
|
||||
-32
|
||||
],
|
||||
"id": "d46ab354-a184-4dfc-b378-ac31cd986be4",
|
||||
"name": "Split Out"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "=https://tasks.googleapis.com/tasks/v1/lists/{{ $json.id }}/tasks",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "googleTasksOAuth2Api",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
128,
|
||||
-32
|
||||
],
|
||||
"id": "d7de2397-4e7f-4b27-aeed-231959bf4939",
|
||||
"name": "Get List of Tasks",
|
||||
"credentials": {
|
||||
"googleTasksOAuth2Api": {
|
||||
"id": "ErcYIjq8Xs0GRaPF",
|
||||
"name": "ben.io-gtasks"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "raw",
|
||||
"jsonOutput": "={{ \n (() => {\n // We don't need to find listId outside the loop anymore\n \n const cleanTasks = $json.items.map(t => {\n // FIX: Extract List ID from the 'selfLink' URL\n // regex captures the text between \"lists/\" and \"/tasks\"\n const listId = t.selfLink.match(/lists\\/([^\\/]+)\\/tasks/)[1];\n\n return {\n \"list_id\": listId, \n \"task_id\": t.id,\n \"title\": t.title,\n \"status\": t.status,\n \"due\": t.due,\n \"updated\": t.updated,\n \"notes\": t.notes,\n \"links\": t.links,\n \"webViewLink\": t.webViewLink\n };\n });\n\n return { \"tasks\": cleanTasks };\n })() \n}}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
528,
|
||||
-32
|
||||
],
|
||||
"id": "9aa7eef0-f197-4491-bbff-39f52343e050",
|
||||
"name": "Edit Fields"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 3
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "564f2ac0-81d2-4c47-a090-7a7e73dff62b",
|
||||
"leftValue": "={{ $json.items }}",
|
||||
"rightValue": 1,
|
||||
"operator": {
|
||||
"type": "array",
|
||||
"operation": "lengthGt",
|
||||
"rightType": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.filter",
|
||||
"typeVersion": 2.3,
|
||||
"position": [
|
||||
336,
|
||||
-32
|
||||
],
|
||||
"id": "6ff11d12-d94a-4148-bb21-cda69e06536d",
|
||||
"name": "Filter Empty List"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When Executed by Another Workflow": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"HTTP Request": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Out",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Out": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get List of Tasks",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get List of Tasks": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter Empty List",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter Empty List": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "993969fa-c72b-43e0-8a7e-498badc87e2c",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "OJ2UfPNUOAOHlllh",
|
||||
"isArchived": false
|
||||
}
|
||||
1181
workflows/H6TZCHyiYOr1X6Xf.json
Normal file
1181
workflows/H6TZCHyiYOr1X6Xf.json
Normal file
File diff suppressed because it is too large
Load Diff
273
workflows/J3uKCCbSuQ1fdJkC.json
Normal file
273
workflows/J3uKCCbSuQ1fdJkC.json
Normal file
@@ -0,0 +1,273 @@
|
||||
{
|
||||
"id": "J3uKCCbSuQ1fdJkC",
|
||||
"name": "Audible Token Refresh",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "hours"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-trigger",
|
||||
"name": "Schedule Trigger",
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.1
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM audible_credentials ORDER BY updated_at DESC LIMIT 1;",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "get-credentials",
|
||||
"name": "Get Credentials",
|
||||
"position": [
|
||||
208,
|
||||
0
|
||||
],
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Encode raw_data to base64 for safe shell passing\nconst item = items[0];\nconst rawData = item.json.raw_data;\nconst base64Data = Buffer.from(JSON.stringify(rawData)).toString('base64');\nitem.json.creds_base64 = base64Data;\nreturn [item];"
|
||||
},
|
||||
"id": "prepare-credentials",
|
||||
"name": "Prepare Credentials",
|
||||
"position": [
|
||||
400,
|
||||
0
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return JSON.parse($input.first().json.stdout);"
|
||||
},
|
||||
"id": "parse-output",
|
||||
"name": "Parse Output",
|
||||
"position": [
|
||||
800,
|
||||
0
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=/home/b3nw/.local/bin/uv run setup_auth.py -json",
|
||||
"cwd": "/home/b3nw/projects/media/audible-script"
|
||||
},
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
608,
|
||||
0
|
||||
],
|
||||
"id": "f858cc65-d12f-4b4f-8d49-03ba2a332b1c",
|
||||
"name": "Execute a command",
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "S2dcVMjrpg0I0kdV",
|
||||
"name": "vscode-dev.local.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "update",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "audible_credentials",
|
||||
"mode": "list",
|
||||
"cachedResultName": "audible_credentials"
|
||||
},
|
||||
"columns": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {
|
||||
"access_token": "={{ $json.auth.access_token }}",
|
||||
"refresh_token": "={{ $json.auth.refresh_token }}",
|
||||
"id": 1,
|
||||
"raw_data": "={{ $json.auth }}",
|
||||
"expires_at": "={{ DateTime.fromSeconds($json.auth.expires).setZone('UTC').toFormat('yyyy-MM-dd HH:mm:ss.SSS') }}+00",
|
||||
"updated_at": "={{ $now.setZone('UTC').toFormat('yyyy-MM-dd HH:mm:ss.SSS') }}+00"
|
||||
},
|
||||
"matchingColumns": [
|
||||
"id"
|
||||
],
|
||||
"schema": [
|
||||
{
|
||||
"id": "id",
|
||||
"displayName": "id",
|
||||
"required": false,
|
||||
"defaultMatch": true,
|
||||
"display": true,
|
||||
"type": "number",
|
||||
"canBeUsedToMatch": true,
|
||||
"removed": false
|
||||
},
|
||||
{
|
||||
"id": "access_token",
|
||||
"displayName": "access_token",
|
||||
"required": true,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "refresh_token",
|
||||
"displayName": "refresh_token",
|
||||
"required": true,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "expires_at",
|
||||
"displayName": "expires_at",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "dateTime",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "raw_data",
|
||||
"displayName": "raw_data",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "object",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "updated_at",
|
||||
"displayName": "updated_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": [
|
||||
976,
|
||||
0
|
||||
],
|
||||
"id": "be8ac7c8-c6ee-464c-8483-421f0959dae6",
|
||||
"name": "Update rows in a table",
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Get Credentials": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Prepare Credentials",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Output": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update rows in a table",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Credentials": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute a command",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Get Credentials",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute a command": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Output",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveManualExecutions": true,
|
||||
"saveExecutionProgress": true,
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "efe8c6e8-c102-4086-ace6-89dc1ac2a381",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "of8yoeyjjIAhYdnQ",
|
||||
"isArchived": false
|
||||
}
|
||||
122
workflows/KGpJklJeOqX8c3VG.json
Normal file
122
workflows/KGpJklJeOqX8c3VG.json
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"id": "KGpJklJeOqX8c3VG",
|
||||
"name": "Daily Tasks",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 6
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
-656,
|
||||
-32
|
||||
],
|
||||
"id": "7c870564-f742-41b4-99e7-219e0ed184e9",
|
||||
"name": "Schedule Trigger"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "Review my open tasks in each list and prepare an HTML summary of the tasks in order of their due date. Include a web link to each task.",
|
||||
"options": {
|
||||
"systemMessage": "You are a helpful assistant. You have access to the users Google Tasks via tools.",
|
||||
"returnIntermediateSteps": false
|
||||
}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3,
|
||||
"position": [
|
||||
-448,
|
||||
-32
|
||||
],
|
||||
"id": "5019e966-7659-43af-90c2-cb10a560874d",
|
||||
"name": "AI Agent"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "gpt-oss:20b",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-448,
|
||||
176
|
||||
],
|
||||
"id": "89002475-cd04-4451-9b16-a7ea316e5ee4",
|
||||
"name": "Ollama Chat Model",
|
||||
"credentials": {
|
||||
"ollamaApi": {
|
||||
"id": "l1g3pgQImkg18AzR",
|
||||
"name": "Ollama account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "@n8n/n8n-nodes-langchain.toolCode",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
-208,
|
||||
272
|
||||
],
|
||||
"id": "2936a35b-ade5-4dca-b82d-ee5418ca5add",
|
||||
"name": "Code Tool"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Ollama Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code Tool": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "d0763a35-96bb-40a5-9c09-f9f4199cb231",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "OJ2UfPNUOAOHlllh",
|
||||
"isArchived": true
|
||||
}
|
||||
98
workflows/KanydpqJrpsAGDCMqkpgD.json
Normal file
98
workflows/KanydpqJrpsAGDCMqkpgD.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"id": "KanydpqJrpsAGDCMqkpgD",
|
||||
"name": "Outline Daily Notes",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"id": "5d2e1844-e4ba-4e6a-92fa-86c3ded88897",
|
||||
"name": "Schedule Trigger"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "/home/b3nw/projects/personal/daily-ideas/run-daily.sh",
|
||||
"cwd": "/home/b3nw/projects/personal/daily-ideas"
|
||||
},
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
208,
|
||||
0
|
||||
],
|
||||
"id": "6cb707c1-1ee5-409b-84cb-3ed810136c70",
|
||||
"name": "Execute a command",
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "S2dcVMjrpg0I0kdV",
|
||||
"name": "vscode-dev.local.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"language": "pythonNative",
|
||||
"pythonCode": "import json\nimport re\n\n# Get stdout from Execute Command node\nstdout_content = _items[0][\"json\"].get(\"stdout\", \"\")\n\n# Parse NDJSON\nlines = stdout_content.strip().split(\"\\n\")\n\n# Collect all events by type\nresult = {\n \"session_id\": None,\n \"status\": \"unknown\",\n \"created_document\": None,\n \"tool_calls\": [],\n \"text_responses\": [],\n \"steps\": [],\n \"total_cost\": 0.0,\n \"total_tokens\": {\n \"input\": 0,\n \"output\": 0,\n \"reasoning\": 0\n }\n}\n\nfor line in lines:\n try:\n event = json.loads(line)\n event_type = event.get(\"type\")\n \n # Capture session ID\n if not result[\"session_id\"]:\n result[\"session_id\"] = event.get(\"sessionID\")\n \n part = event.get(\"part\", {})\n \n if event_type == \"text\":\n result[\"text_responses\"].append({\n \"id\": part.get(\"id\"),\n \"text\": part.get(\"text\", \"\"),\n \"timestamp\": event.get(\"timestamp\")\n })\n \n elif event_type == \"tool_use\":\n tool_name = part.get(\"tool\", \"\")\n state = part.get(\"state\", {})\n \n tool_call = {\n \"tool\": tool_name,\n \"call_id\": part.get(\"callID\"),\n \"status\": state.get(\"status\"),\n \"input\": state.get(\"input\"),\n \"timestamp\": event.get(\"timestamp\")\n }\n \n # Parse output if completed\n if state.get(\"status\") == \"completed\":\n output_str = state.get(\"output\", \"{}\")\n try:\n tool_call[\"output\"] = json.loads(output_str)\n except:\n tool_call[\"output\"] = output_str\n \n result[\"tool_calls\"].append(tool_call)\n \n # Check for document creation\n if \"create_document\" in tool_name and state.get(\"status\") == \"completed\":\n try:\n output_data = json.loads(state.get(\"output\", \"{}\"))\n if \"data\" in output_data and \"id\" in output_data[\"data\"]:\n result[\"created_document\"] = output_data[\"data\"]\n result[\"status\"] = \"document_created\"\n except:\n pass\n \n elif event_type == \"step_finish\":\n step_info = {\n \"id\": part.get(\"id\"),\n \"reason\": part.get(\"reason\"),\n \"cost\": part.get(\"cost\", 0),\n \"tokens\": part.get(\"tokens\", {}),\n \"timestamp\": event.get(\"timestamp\")\n }\n result[\"steps\"].append(step_info)\n \n # Accumulate costs and tokens\n result[\"total_cost\"] += part.get(\"cost\", 0)\n tokens = part.get(\"tokens\", {})\n result[\"total_tokens\"][\"input\"] += tokens.get(\"input\", 0)\n result[\"total_tokens\"][\"output\"] += tokens.get(\"output\", 0)\n result[\"total_tokens\"][\"reasoning\"] += tokens.get(\"reasoning\", 0)\n \n except json.JSONDecodeError:\n continue\n\n# Determine final status\nif result[\"created_document\"]:\n result[\"status\"] = \"document_created\"\nelif result[\"tool_calls\"]:\n # Check if any tool calls failed\n failed = [t for t in result[\"tool_calls\"] if t.get(\"status\") != \"completed\"]\n if failed:\n result[\"status\"] = \"tool_error\"\n else:\n result[\"status\"] = \"tools_completed\"\nelif result[\"text_responses\"]:\n result[\"status\"] = \"text_response\"\n\n# Combine all text for summary\nresult[\"full_text\"] = \"\\n\\n\".join([t[\"text\"] for t in result[\"text_responses\"]])\n\nreturn result"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
416,
|
||||
0
|
||||
],
|
||||
"id": "74be33c5-e738-4b83-9bd6-9ae19186f86a",
|
||||
"name": "Code in Python (Native)"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute a command",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute a command": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code in Python (Native)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "4a28d8c0-0420-4555-8019-4b972d63cdac",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "OJ2UfPNUOAOHlllh",
|
||||
"isArchived": false
|
||||
}
|
||||
284
workflows/Mdopqz1Tq0OHDFq1.json
Normal file
284
workflows/Mdopqz1Tq0OHDFq1.json
Normal file
@@ -0,0 +1,284 @@
|
||||
{
|
||||
"id": "Mdopqz1Tq0OHDFq1",
|
||||
"name": "ben.io-task agent",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"workflowInputs": {
|
||||
"values": [
|
||||
{
|
||||
"name": "request"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-752,
|
||||
16
|
||||
],
|
||||
"id": "781db6ec-9be3-4db2-a194-1ef21b550f42",
|
||||
"name": "When Executed by Another Workflow"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"promptType": "define",
|
||||
"text": "=Generate and send my daily task briefing email based on my current open tasks.",
|
||||
"needsFallback": true,
|
||||
"options": {
|
||||
"systemMessage": "You are an executive assistant responsible for generating a daily task briefing.\n\n## Data Processing Rules\n1. **Fetch Data**: Always call both `GTasks: Get All Tasklists` and `GTasks: Get All Tasks`.\n2. **Map Categories**: \n - You must link Tasks to their List Name. \n - Extract the List ID from the task's `selfLink` attribute (the string between `/lists/` and `/tasks/`).\n - Match this ID to the `id` from the Tasklists output to get the human-readable Category Name (e.g., \"Finance\", \"House\").\n3. **Determine Priority**:\n - **High**: Due date is today, in the past (overdue), or status is 'needsAction' with a due date today/past.\n - **Normal**: Due date is within the next 7 days.\n - **Low**: No due date or due date is > 7 days away.\n - *Exclude tasks with status 'completed'.*\n\n## Output Formatting\n1. **Style**: Use simple, semantic HTML only. No CSS or `<style>` blocks.\n2. **Structure**:\n - Use `<h3>` headers for Priorities (High, Normal, Low).\n - Use `<ul>` and `<li>` for the tasks.\n - Format each item as: `[Category Name]: [Task Title]`.\n - If a priority group is empty, do not include that header.\n3. **Execution**: \n - Pass the final HTML string to the `Email Tasklist` tool. \n - Do not worry about the Subject line or Recipient; the tool handles those.",
|
||||
"enableStreaming": true
|
||||
}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"typeVersion": 3,
|
||||
"position": [
|
||||
-416,
|
||||
16
|
||||
],
|
||||
"id": "0bb8a676-8339-4333-a983-9ff1f903182e",
|
||||
"name": "AI Agent"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"value": "openai/gpt-oss-120b",
|
||||
"mode": "list",
|
||||
"cachedResultName": "openai/gpt-oss-120b"
|
||||
},
|
||||
"responsesApiEnabled": false,
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
-544,
|
||||
272
|
||||
],
|
||||
"id": "a10a4ade-9848-444b-8209-efa96b9af2d6",
|
||||
"name": "OpenAI Chat Model",
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "QRBx9RMx4KoFwGgl",
|
||||
"name": "Nvidia account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"description": "Call this tool to get a list of all current tasks.",
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"value": "EVctKxhQ2eyGd3gD",
|
||||
"mode": "list",
|
||||
"cachedResultUrl": "/workflow/EVctKxhQ2eyGd3gD",
|
||||
"cachedResultName": "GTasks: Get All Tasks"
|
||||
},
|
||||
"workflowInputs": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {},
|
||||
"matchingColumns": [],
|
||||
"schema": [],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": false
|
||||
}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
-272,
|
||||
224
|
||||
],
|
||||
"id": "8349898e-b0ed-4063-b7c9-1deecd783bc4",
|
||||
"name": "Call 'GTasks: Get All Tasks'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"description": "Call this tool to get a list of all task lists.",
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"value": "f2rn29FKq1ejX2ax",
|
||||
"mode": "list",
|
||||
"cachedResultUrl": "/workflow/f2rn29FKq1ejX2ax",
|
||||
"cachedResultName": "GTasks: Get All Tasklists"
|
||||
},
|
||||
"workflowInputs": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {},
|
||||
"matchingColumns": [],
|
||||
"schema": [],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": false
|
||||
}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
-144,
|
||||
288
|
||||
],
|
||||
"id": "e650465e-6ef2-4fe9-a764-9fb71ed60473",
|
||||
"name": "Call 'GTasks: Get All Tasklists'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"sendTo": "daily-tasks@ben.io",
|
||||
"subject": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Subject', ``, 'string') }}",
|
||||
"message": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Message', ``, 'string') }}",
|
||||
"options": {
|
||||
"appendAttribution": true,
|
||||
"senderName": "n8n Daily Tasks Update"
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.gmailTool",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
16,
|
||||
288
|
||||
],
|
||||
"id": "fd8fa856-2d8a-482a-b5c1-9767510b613e",
|
||||
"name": "Email Tasklist",
|
||||
"webhookId": "ca394aa0-ee70-427d-a03f-c60acf6bc7d7",
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "VHbUFo39yKkrSroG",
|
||||
"name": "ben.io-gmail"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 6,
|
||||
"triggerAtMinute": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
-752,
|
||||
192
|
||||
],
|
||||
"id": "99ef5114-4970-41d7-bf7e-a313215ed75f",
|
||||
"name": "Schedule Trigger"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"modelName": "models/gemini-3-flash-preview",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-416,
|
||||
304
|
||||
],
|
||||
"id": "7bde7a57-02a0-4ff9-97de-2a298a57664d",
|
||||
"name": "google/gemini-3-flash",
|
||||
"credentials": {
|
||||
"googlePalmApi": {
|
||||
"id": "YMRbpffqZG7PHJb6",
|
||||
"name": "Google Gemini(PaLM) Api account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When Executed by Another Workflow": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"OpenAI Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Call 'GTasks: Get All Tasks'": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Call 'GTasks: Get All Tasklists'": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Email Tasklist": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"google/gemini-3-flash": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "1691b6e0-90bc-47b5-9f6e-3408e9d0da31",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "OJ2UfPNUOAOHlllh",
|
||||
"isArchived": false
|
||||
}
|
||||
68
workflows/RTkNtAszYhwXqesj.json
Normal file
68
workflows/RTkNtAszYhwXqesj.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"id": "RTkNtAszYhwXqesj",
|
||||
"name": "Test Wrapper for General Series",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Manual Trigger",
|
||||
"name": "Manual Trigger",
|
||||
"parameters": {
|
||||
"notice": ""
|
||||
},
|
||||
"position": [
|
||||
112,
|
||||
112
|
||||
],
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "Execute Sub-Workflow",
|
||||
"name": "Execute Sub-Workflow",
|
||||
"parameters": {
|
||||
"executeWorkflowNotice": "",
|
||||
"mode": "once",
|
||||
"operation": "call_workflow",
|
||||
"options": {},
|
||||
"outdatedVersionWarning": "",
|
||||
"source": "database",
|
||||
"workflowId": "cPWZKfrHOUSUZjIp"
|
||||
},
|
||||
"position": [
|
||||
304,
|
||||
112
|
||||
],
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Manual Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Execute Sub-Workflow",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"saveExecutionProgress": true,
|
||||
"saveManualExecutions": true,
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "fd2d2e20-1a6c-4b4f-8240-d3bb907f2f63",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": true
|
||||
}
|
||||
566
workflows/Sq6pKxEiFPBHqOXm.json
Normal file
566
workflows/Sq6pKxEiFPBHqOXm.json
Normal file
@@ -0,0 +1,566 @@
|
||||
{
|
||||
"id": "Sq6pKxEiFPBHqOXm",
|
||||
"name": "MAM RSS Orchestrator",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "select",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "followed_series",
|
||||
"mode": "list",
|
||||
"cachedResultName": "followed_series"
|
||||
},
|
||||
"returnAll": true,
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
448,
|
||||
320
|
||||
],
|
||||
"id": "ba75080b-cddb-4780-a1ce-01b667c6e732",
|
||||
"name": "list_series",
|
||||
"executeOnce": true,
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://www.myanonamouse.net/tor/js/loadSearchJSONbasic.php",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tor[searchType]",
|
||||
"value": "all"
|
||||
},
|
||||
{
|
||||
"name": "tor[main_cat][]",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"name": "perpage",
|
||||
"value": "1000"
|
||||
},
|
||||
{
|
||||
"name": "tor[startDate]",
|
||||
"value": "={{ $now.minus({minutes: 1480}).toFormat('X') }}"
|
||||
},
|
||||
{
|
||||
"name": "tor[sortType]",
|
||||
"value": "dateDesc"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "6118b58c-4957-4057-8331-d4dda15dfeae",
|
||||
"name": "Search MAM API",
|
||||
"position": [
|
||||
448,
|
||||
512
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "G8eA8XeS9P5axwJd",
|
||||
"name": "mam cookie auth header"
|
||||
},
|
||||
"httpBasicAuth": {
|
||||
"id": "BRiFacyi0A60Y7ZZ",
|
||||
"name": "mam_id"
|
||||
}
|
||||
},
|
||||
"onError": "continueRegularOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "hours",
|
||||
"hoursInterval": 3,
|
||||
"triggerAtMinute": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-trigger",
|
||||
"name": "Every 3 Hours",
|
||||
"position": [
|
||||
176,
|
||||
336
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "data",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
656,
|
||||
512
|
||||
],
|
||||
"id": "fc645f39-d4bf-42a0-97ef-053f724190f1",
|
||||
"name": "Split Out"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "ae6bf406-f09b-408d-8362-a3afe20c1e94",
|
||||
"name": "mam_series_id",
|
||||
"value": "={{ Object.keys(JSON.parse($json.series_info || '{}'))[0] }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "355cac56-61a4-4222-ac62-f5ef32cf108e",
|
||||
"name": "=join_id",
|
||||
"value": "={{ $json.id.toString().trim() }}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"includeOtherFields": true,
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
864,
|
||||
512
|
||||
],
|
||||
"id": "1339f915-626e-4f45-af56-30c9cde4a68f",
|
||||
"name": "Edit Fields"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "combine",
|
||||
"fieldsToMatchString": "mam_series_id",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1104,
|
||||
336
|
||||
],
|
||||
"id": "a0b9bef0-392e-4605-bf1c-273d7d16aa32",
|
||||
"name": "Merge"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return [\n {\n json: {\n series_name: \"Loner Life in Another World\",\n mam_series_id: \"97284\", // <--- Added quotes\n smb_path: \"/mnt/nas/Anime/Audiobooks/Loner Life in Another World\"\n }\n },\n {\n json: {\n series_name: \"Skeleton Knight in Another World\",\n mam_series_id: \"57788\", // <--- Added quotes\n smb_path: \"/mnt/nas/Anime/Audiobooks/Skeleton Knight in Another World\"\n }\n },\n {\n json: {\n series_name: \"My Status as an Assassin Obviously Exceeds the Hero's\",\n mam_series_id: \"139807\", // <--- Added quotes\n smb_path: \"/mnt/nas/Anime/Audiobooks/My Status as an Assassin Obviously Exceeds the Hero's\"\n }\n }\n];"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
656,
|
||||
320
|
||||
],
|
||||
"id": "37c7eab2-c14e-4e51-b7f8-0688c6b66fd5",
|
||||
"name": "Code in JavaScript",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=find \"{{ $json.smb_path }}\" -maxdepth 1 -name \"*{{ $json.title.replace(/[^a-zA-Z0-9 ]/g, \"\") }}*\" | wc -l"
|
||||
},
|
||||
"id": "e39493ef-57a9-4c0c-834b-1f89be1fcb59",
|
||||
"name": "List Folders",
|
||||
"position": [
|
||||
1952,
|
||||
224
|
||||
],
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "vOzhicmbOwx1XDF8",
|
||||
"name": "seed-0.local.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://04ekn.mrd.ninja/rss/81965ade",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpBasicAuth",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
960,
|
||||
160
|
||||
],
|
||||
"id": "2280bc7b-270d-486c-9355-4d966830402c",
|
||||
"name": "HTTP Request",
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"id": "BRiFacyi0A60Y7ZZ",
|
||||
"name": "mam_id"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.xml",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1168,
|
||||
160
|
||||
],
|
||||
"id": "d1093731-ce22-4ecf-9888-0c0f0aac4531",
|
||||
"name": "XML"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "1856ed53-6ebb-411c-a2fa-35da18d18f52",
|
||||
"name": "=join_id",
|
||||
"value": "={{ parseInt($json.guid.match(/\\/t\\/(\\d+)/)[1]) }}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"includeOtherFields": true,
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
1568,
|
||||
160
|
||||
],
|
||||
"id": "19cccfb1-5684-4c5c-ac2b-37913d545b9e",
|
||||
"name": "Edit Fields1"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "rss.channel.item",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1376,
|
||||
160
|
||||
],
|
||||
"id": "07d95513-bc0c-4d94-a492-6a2ee6810af9",
|
||||
"name": "Split Out1"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "combine",
|
||||
"fieldsToMatchString": "join_id",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1776,
|
||||
224
|
||||
],
|
||||
"id": "8b5484e6-5539-48e2-a9f9-87c43d881e92",
|
||||
"name": "Merge1"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 3
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "9da81979-9723-4fd6-b641-e9e52e475ce5",
|
||||
"leftValue": "={{ $json.code }}",
|
||||
"rightValue": 0,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.3,
|
||||
"position": [
|
||||
2160,
|
||||
224
|
||||
],
|
||||
"id": "99a7ca2b-38b0-472d-9044-fa9e1b1b837c",
|
||||
"name": "If"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{ $('Edit Fields1').item.json.link }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
2432,
|
||||
208
|
||||
],
|
||||
"id": "47a848a9-c815-47d9-8493-36d6ac3ada1c",
|
||||
"name": "HTTP Request1"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"value": "6S41oPplwN1S9Lz0",
|
||||
"mode": "list",
|
||||
"cachedResultUrl": "/workflow/6S41oPplwN1S9Lz0",
|
||||
"cachedResultName": "MAM Remote File Transfer"
|
||||
},
|
||||
"workflowInputs": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {},
|
||||
"matchingColumns": [],
|
||||
"schema": [],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": true
|
||||
},
|
||||
"options": {
|
||||
"waitForSubWorkflow": true
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
2640,
|
||||
208
|
||||
],
|
||||
"id": "c1fc40bb-2ab1-47a3-9df9-134ba313b749",
|
||||
"name": "Call 'MAM Remote File Transfer'"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Every 3 Hours": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "list_series",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Search MAM API",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "HTTP Request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search MAM API": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Out",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Out": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Edit Fields": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"list_series": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code in JavaScript",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code in JavaScript": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge1",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"List Folders": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"HTTP Request": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "XML",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"XML": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Out1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Out1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Edit Fields1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "List Folders",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"HTTP Request1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Call 'MAM Remote File Transfer'",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "8a62f940-23db-4004-b207-94299f83038b",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": true
|
||||
}
|
||||
96
workflows/VUwFjFF2UhNout2T.json
Normal file
96
workflows/VUwFjFF2UhNout2T.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"id": "VUwFjFF2UhNout2T",
|
||||
"name": "tool_brave_search",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"inputSource": "jsonExample",
|
||||
"jsonExample": "{\n \"query\": \"Spice and Wolf\"\n}"
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-720,
|
||||
-176
|
||||
],
|
||||
"id": "94d07541-655c-4c2d-aa22-44e4cd502d92",
|
||||
"name": "When Executed by Another Workflow"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"query": "={{ $json.query }}",
|
||||
"count": 5,
|
||||
"additionalParameters": {}
|
||||
},
|
||||
"type": "@brave/n8n-nodes-brave-search.braveSearch",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-512,
|
||||
-176
|
||||
],
|
||||
"id": "e633ffd1-1d64-4455-a9cb-626562b7ea09",
|
||||
"name": "Brave Search",
|
||||
"credentials": {
|
||||
"braveSearchApi": {
|
||||
"id": "o6A594KZAPzN3LTR",
|
||||
"name": "Brave Search account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Get the raw search results\nconst results = items[0].json.web?.results || items; \n\n// Map to ONLY Title and URL\nconst cleanResults = results.map(r => ({\n title: r.title,\n url: r.url\n}));\n\n// Return as a single simplified JSON object\nreturn [\n {\n json: {\n search_summary: cleanResults\n }\n }\n];"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-304,
|
||||
-176
|
||||
],
|
||||
"id": "0a797ece-4868-4e1e-9dbe-b3bbb9aeb74b",
|
||||
"name": "Code in JavaScript"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When Executed by Another Workflow": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Brave Search",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Brave Search": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code in JavaScript",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code in JavaScript": {
|
||||
"main": [
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "6fa8f712-deaa-467d-ac3c-f0c37f7867fb",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "of8yoeyjjIAhYdnQ",
|
||||
"isArchived": false
|
||||
}
|
||||
205
workflows/VhdYU2pQ2MIZVokG.json
Normal file
205
workflows/VhdYU2pQ2MIZVokG.json
Normal file
@@ -0,0 +1,205 @@
|
||||
{
|
||||
"id": "VhdYU2pQ2MIZVokG",
|
||||
"name": "Cron Fan-Out Diagnostic",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "schedule-test",
|
||||
"name": "Test Schedule Trigger",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
-400,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "minutes",
|
||||
"minutesInterval": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "set-branch-a",
|
||||
"name": "Emit Branch A",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-160,
|
||||
-160
|
||||
],
|
||||
"parameters": {
|
||||
"keepOnlySet": true,
|
||||
"values": {
|
||||
"string": [
|
||||
{
|
||||
"name": "branch",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"value": "schedule"
|
||||
}
|
||||
],
|
||||
"number": [
|
||||
{
|
||||
"name": "branchIndex",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "set-branch-b",
|
||||
"name": "Emit Branch B",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-160,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"keepOnlySet": true,
|
||||
"values": {
|
||||
"string": [
|
||||
{
|
||||
"name": "branch",
|
||||
"value": "B"
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"value": "schedule"
|
||||
}
|
||||
],
|
||||
"number": [
|
||||
{
|
||||
"name": "branchIndex",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "set-branch-c",
|
||||
"name": "Emit Branch C",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-160,
|
||||
160
|
||||
],
|
||||
"parameters": {
|
||||
"keepOnlySet": true,
|
||||
"values": {
|
||||
"string": [
|
||||
{
|
||||
"name": "branch",
|
||||
"value": "C"
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"value": "schedule"
|
||||
}
|
||||
],
|
||||
"number": [
|
||||
{
|
||||
"name": "branchIndex",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "code-collector",
|
||||
"name": "Collect Branch Events",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
80,
|
||||
0
|
||||
],
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nreturn items.map((item, index) => ({\n json: {\n branch: item.json.branch,\n branchIndex: item.json.branchIndex,\n receivedOrder: index,\n receivedAt: new Date().toISOString()\n }\n}));"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Test Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Emit Branch A",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Emit Branch B",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Emit Branch C",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Emit Branch A": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Collect Branch Events",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Emit Branch B": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Collect Branch Events",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Emit Branch C": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Collect Branch Events",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveManualExecutions": true,
|
||||
"saveExecutionProgress": true
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "f7b5c755-f6c0-4d1d-afb1-ba779a271c36",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": true
|
||||
}
|
||||
136
workflows/WkAdUd9jXTtPagGO.json
Normal file
136
workflows/WkAdUd9jXTtPagGO.json
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"id": "WkAdUd9jXTtPagGO",
|
||||
"name": "MAM Series Matcher",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "trigger-execute-workflow",
|
||||
"name": "When Called by Another Workflow",
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
224,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Fuzzy match incoming title against followed series\n// Input: title from Execute Workflow trigger\n// Output: matched series or null\n\nconst inputItem = $input.first();\nconst title = inputItem.json.title || '';\nconst metadata = inputItem.json.metadata || {};\n\n// Get all followed series from Supabase\nconst seriesList = $('Get Active Followed Series').all();\n\n// Helper function to normalize strings for comparison\nfunction normalize(str) {\n return str.toLowerCase()\n .replace(/[^a-z0-9\\s]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n// Helper function to extract patterns from title\nfunction extractPatterns(title) {\n const patterns = {\n volume: null,\n author: null,\n cleanTitle: title\n };\n \n // Extract volume numbers (e.g., \"Vol 01\", \"Volume 1\", \"v01\")\n const volumeMatch = title.match(/\\b(?:vol(?:ume)?\\.?\\s*(\\d+)|v(\\d+))\\b/i);\n if (volumeMatch) {\n patterns.volume = parseInt(volumeMatch[1] || volumeMatch[2]);\n }\n \n // Extract author (common patterns: \"Author Name - Title\", \"[Author Name]\")\n const authorMatch1 = title.match(/^([^-\\[\\]]+?)\\s*-\\s*(.+)/);\n const authorMatch2 = title.match(/\\[([^\\]]+)\\]/);\n \n if (authorMatch1) {\n patterns.author = authorMatch1[1].trim();\n patterns.cleanTitle = authorMatch1[2].trim();\n } else if (authorMatch2) {\n patterns.author = authorMatch2[1].trim();\n }\n \n return patterns;\n}\n\n// Extract patterns from input title\nconst inputPatterns = extractPatterns(title);\nconst normalizedInput = normalize(inputPatterns.cleanTitle);\n\n// Try to match against series\nlet bestMatch = null;\nlet bestScore = 0;\n\nfor (const series of seriesList) {\n const seriesName = series.json.series_name || '';\n const author = series.json.author || '';\n const normalizedSeries = normalize(seriesName);\n const normalizedAuthor = normalize(author);\n \n let score = 0;\n \n // Check if series name is contained in title\n if (normalizedInput.includes(normalizedSeries)) {\n score += 50;\n }\n \n // Check if title is contained in series name\n if (normalizedSeries.includes(normalizedInput)) {\n score += 40;\n }\n \n // Check author match\n if (inputPatterns.author && normalizedAuthor) {\n if (normalize(inputPatterns.author).includes(normalizedAuthor)) {\n score += 30;\n }\n }\n \n // Simple word overlap check\n const inputWords = normalizedInput.split(' ').filter(w => w.length > 2);\n const seriesWords = normalizedSeries.split(' ').filter(w => w.length > 2);\n const commonWords = inputWords.filter(w => seriesWords.includes(w));\n score += commonWords.length * 5;\n \n if (score > bestScore) {\n bestScore = score;\n bestMatch = series.json;\n }\n}\n\n// Return match if score is above threshold\nif (bestMatch && bestScore >= 40) {\n return [{\n json: {\n matched: true,\n series_id: bestMatch.id,\n series_name: bestMatch.series_name,\n author: bestMatch.author,\n category: bestMatch.category,\n smb_path: bestMatch.smb_path,\n match_score: bestScore,\n original_title: title,\n extracted_volume: inputPatterns.volume,\n extracted_author: inputPatterns.author\n }\n }];\n} else {\n return [{\n json: {\n matched: false,\n series_id: null,\n series_name: null,\n author: null,\n category: null,\n smb_path: null,\n match_score: bestScore,\n original_title: title\n }\n }];\n}"
|
||||
},
|
||||
"id": "code-fuzzy-match",
|
||||
"name": "Fuzzy Match Title",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
608,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "allIncomingItems",
|
||||
"options": {}
|
||||
},
|
||||
"id": "respond-to-workflow",
|
||||
"name": "Respond to Workflow",
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
800,
|
||||
304
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "select",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "followed_series",
|
||||
"mode": "list",
|
||||
"cachedResultName": "followed_series"
|
||||
},
|
||||
"returnAll": true,
|
||||
"where": {
|
||||
"values": [
|
||||
{
|
||||
"column": "active",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
432,
|
||||
304
|
||||
],
|
||||
"id": "2f0708e1-996c-4705-b374-2a119fcd6b18",
|
||||
"name": "Get Active Followed Series1",
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "dsnKfvOBMkgU21Lt",
|
||||
"name": "supabase postgres account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When Called by Another Workflow": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Active Followed Series1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Fuzzy Match Title": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond to Workflow",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get Active Followed Series1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fuzzy Match Title",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveManualExecutions": true,
|
||||
"saveExecutionProgress": true
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "5d298782-fb60-4d33-9e38-97878b179d62",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": false
|
||||
}
|
||||
449
workflows/Yr0iTWPRAlDJyfAj.json
Normal file
449
workflows/Yr0iTWPRAlDJyfAj.json
Normal file
File diff suppressed because one or more lines are too long
141
workflows/Z_YHsJaf_pyFQR6e7VuLo.json
Normal file
141
workflows/Z_YHsJaf_pyFQR6e7VuLo.json
Normal file
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"id": "Z_YHsJaf_pyFQR6e7VuLo",
|
||||
"name": "search_audible",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"inputSource": "jsonExample",
|
||||
"jsonExample": "{\n \"book-name\": \"a string\",\n \"author\": \"a string\",\n \"asin\": \"a string\"\n}"
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-496,
|
||||
-208
|
||||
],
|
||||
"id": "ca4bf486-440d-452e-a1b8-1ad650789082",
|
||||
"name": "When Executed by Another Workflow"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 3
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "2399bcd9-fb6b-4dbc-88a5-1de89bea8afd",
|
||||
"leftValue": "={{ $json.asin }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.3,
|
||||
"position": [
|
||||
-272,
|
||||
-208
|
||||
],
|
||||
"id": "2c952384-9012-4382-a394-fe9e2c00d6c0",
|
||||
"name": "asin check"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=/home/b3nw/.local/bin/uv run search_audible.py --book-name \"{{ $json['book-name'] }}\" --author \"{{ $json.author }}\"",
|
||||
"cwd": "/home/b3nw/projects/media/audible-script"
|
||||
},
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-48,
|
||||
-112
|
||||
],
|
||||
"id": "789e7924-171a-4dd3-b003-752597c7e98f",
|
||||
"name": "audible_search_book",
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "S2dcVMjrpg0I0kdV",
|
||||
"name": "vscode-dev.local.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=/home/b3nw/.local/bin/uv run search_audible.py --asin {{ $json.asin }}",
|
||||
"cwd": "/home/b3nw/projects/media/audible-script"
|
||||
},
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-48,
|
||||
-304
|
||||
],
|
||||
"id": "c17ae2cf-0d46-4efb-9dae-fd01e58f3837",
|
||||
"name": "audible_lookup_asin",
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "S2dcVMjrpg0I0kdV",
|
||||
"name": "vscode-dev.local.ben.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When Executed by Another Workflow": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "asin check",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"asin check": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "audible_lookup_asin",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "audible_search_book",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "05a6eaf5-abf0-4f48-895a-d05563a32009",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "of8yoeyjjIAhYdnQ",
|
||||
"isArchived": false
|
||||
}
|
||||
868
workflows/aEtoKG8mIocULX5W.json
Normal file
868
workflows/aEtoKG8mIocULX5W.json
Normal file
@@ -0,0 +1,868 @@
|
||||
{
|
||||
"id": "aEtoKG8mIocULX5W",
|
||||
"name": "Audiobook Torrents",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-736,
|
||||
-48
|
||||
],
|
||||
"id": "286acfc0-180f-407a-aa4a-f76683e5e061",
|
||||
"name": "When clicking 'Execute workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://nyaa.si/?page=rss&q=%5BAudiobook%5D&c=3_1&f=0",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-512,
|
||||
48
|
||||
],
|
||||
"id": "c3a1b112-cd01-4a0e-b843-bd6c635202c6",
|
||||
"name": "HTTP Request"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {
|
||||
"trim": true
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.xml",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-288,
|
||||
48
|
||||
],
|
||||
"id": "0239b8cc-c0b0-4bce-9892-176bf69ab8aa",
|
||||
"name": "XML"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "rss.channel.item",
|
||||
"options": {}
|
||||
},
|
||||
"id": "split-items-node",
|
||||
"name": "Split Feed Items",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-64,
|
||||
48
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Process all input items and return them\nreturn $input.all().map(item => ({\n json: {\n info_hash: item.json['nyaa:infoHash'],\n title: item.json.title,\n link: item.json.link,\n view_link: item.json.guid._ || item.json.guid,\n pub_date: item.json.pubDate,\n seeders: parseInt(item.json['nyaa:seeders']) || 0,\n size: item.json['nyaa:size']\n }\n}));"
|
||||
},
|
||||
"id": "extract-torrent-data",
|
||||
"name": "Extract Torrent Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
160,
|
||||
48
|
||||
],
|
||||
"executeOnce": false
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.seeders }}",
|
||||
"rightValue": 0,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "gt"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "filter-has-seeders",
|
||||
"name": "Has Seeders?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-592,
|
||||
624
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "minutes"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-trigger-5min",
|
||||
"name": "Every 5 Minutes",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
-736,
|
||||
144
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"sendTo": "admin@ben.io",
|
||||
"subject": "={{ $json.torrentCount }} New Audiobook Torrent(s) Found",
|
||||
"message": "={{ $json.emailHtmlBody }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "send-gmail-notification",
|
||||
"name": "Send Gmail Notification",
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
896,
|
||||
528
|
||||
],
|
||||
"webhookId": "8da5c0a8-d147-46e8-bcc5-85d5cd2c87d5",
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "Os1ux3h3zFlC2XkG",
|
||||
"name": "Gmail account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"tableId": "nyaa_audiobooks",
|
||||
"returnAll": true,
|
||||
"filterType": "none"
|
||||
},
|
||||
"type": "n8n-nodes-base.supabase",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-368,
|
||||
704
|
||||
],
|
||||
"id": "ea585083-0b0a-410b-b0ac-768badf335fa",
|
||||
"name": "Get many rows",
|
||||
"executeOnce": true,
|
||||
"credentials": {
|
||||
"supabaseApi": {
|
||||
"id": "lWyf2ikOGHTTwnSU",
|
||||
"name": "Supabase account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mergeByFields": {
|
||||
"values": [
|
||||
{
|
||||
"field1": "info_hash",
|
||||
"field2": "info_hash"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"skipFields": "seeders,discovered_at,updated_at,mam_torrent_id,mam_checked_at,view_link,id,created_at,search_title,volume_range"
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.compareDatasets",
|
||||
"typeVersion": 2.3,
|
||||
"position": [
|
||||
-144,
|
||||
592
|
||||
],
|
||||
"id": "13fffea6-eb23-4746-9c21-e9695bd8c377",
|
||||
"name": "Compare Datasets"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Get all incoming items using $input.all()\nconst torrents = $input.all();\nconst count = torrents.length;\n\n// If no torrents, return empty to stop workflow\nif (count === 0) {\n return [];\n}\n\n// Helper function to get MAM status display\nfunction getMAMStatus(torrent) {\n if (torrent.mam_torrent_id) {\n return `✅ <a href=\"https://www.myanonamouse.net/t/${torrent.mam_torrent_id}\">Yes</a>`;\n } else {\n return '❌ No';\n }\n}\n\n// Build HTML table rows\nlet tableRows = \"\";\n\nfor (const item of torrents) {\n const torrent = item.json;\n \n tableRows += `\n <tr>\n <td><a href=\"${torrent.view_link}\">${torrent.title}</a></td>\n <td>${torrent.seeders}</td>\n <td>${torrent.size}</td>\n <td>${getMAMStatus(torrent)}</td>\n <td>\n <a href=\"${torrent.link}\">Download</a> | \n <a href=\"${torrent.view_link}\">View</a>\n </td>\n </tr>\n `;\n}\n\n// Build complete HTML email body\nconst emailBody = `\n<h2>${count} New Audiobook Torrent(s) Found</h2>\n<table border=\"1\" cellpadding=\"8\" cellspacing=\"0\" style=\"border-collapse: collapse;\">\n <thead>\n <tr>\n <th>Title</th>\n <th>Seeders</th>\n <th>Size</th>\n <th>On MAM?</th>\n <th>Links</th>\n </tr>\n </thead>\n <tbody>\n ${tableRows}\n </tbody>\n</table>\n`;\n\nreturn [{\n json: {\n torrentCount: count,\n emailHtmlBody: emailBody\n }\n}];"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
672,
|
||||
528
|
||||
],
|
||||
"id": "99f5e0ba-0c7e-4c37-9692-ff0c01ab48ae",
|
||||
"name": "Code in JavaScript",
|
||||
"executeOnce": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "condition1",
|
||||
"leftValue": "={{ $input.all().length }}",
|
||||
"rightValue": 0,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "gt"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "check-has-new-torrents",
|
||||
"name": "Has New Torrents?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
224,
|
||||
528
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"tableId": "nyaa_audiobooks",
|
||||
"dataToSend": "autoMapInputData",
|
||||
"inputsToIgnore": "view_link"
|
||||
},
|
||||
"id": "insert-new-torrents",
|
||||
"name": "Insert New Torrents",
|
||||
"type": "n8n-nodes-base.supabase",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
448,
|
||||
528
|
||||
],
|
||||
"credentials": {
|
||||
"supabaseApi": {
|
||||
"id": "lWyf2ikOGHTTwnSU",
|
||||
"name": "Supabase account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://www.myanonamouse.net/tor/js/loadSearchJSONbasic.php",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tor[text]",
|
||||
"value": "={{ $json.search_title }}"
|
||||
},
|
||||
{
|
||||
"name": "tor[srchIn][title]",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tor[searchType]",
|
||||
"value": "all"
|
||||
},
|
||||
{
|
||||
"name": "tor[main_cat][]",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"name": "tor[sortType]",
|
||||
"value": "default"
|
||||
},
|
||||
{
|
||||
"name": "tor[startNumber]",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": "perpage",
|
||||
"value": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "search-mam-by-title",
|
||||
"name": "Search MAM by Title",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1056,
|
||||
48
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "sxQYMPEq6GLbc9Av",
|
||||
"name": "n8n_auth_cookie"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Parse MAM title search results with volume and format matching\n// Get the filtered torrents that actually went through MAM search\nconst searchedTorrents = $('Filter Unchecked Torrents').all();\nconst mamResults = $input.all();\n\n// Helper function to extract volume number from MAM title\nfunction extractVolume(title) {\n const patterns = [\n /[Vv]ol\\.?\\s*(\\d+)/,\n /[Vv]olume\\s*(\\d+)/,\n /\\bv(\\d+)\\b/,\n /[Bb]ook\\s*(\\d+)/,\n /,\\s*(\\d+)$/ // Match \", 1\" at end of title\n ];\n \n for (const pattern of patterns) {\n const match = title.match(pattern);\n if (match) {\n return parseInt(match[1]);\n }\n }\n return null;\n}\n\n// Helper function to check if volume is in range\nfunction isVolumeInRange(vol, range) {\n if (!range || vol === null) return false;\n return vol >= range.start && vol <= range.end;\n}\n\n// Helper function to check if it's m4b format\nfunction isM4bFormat(filetype) {\n if (!filetype) return false;\n return filetype.toLowerCase().includes('m4b');\n}\n\n// Process each item - match MAM results with the torrents that were searched\nreturn searchedTorrents.map((item, index) => {\n const mamResult = mamResults[index]?.json;\n const torrentData = item.json;\n \n let mamTorrentId = null;\n let mamFound = false;\n \n if (mamResult && mamResult.data && Array.isArray(mamResult.data) && mamResult.data.length > 0) {\n // Search through results for a matching volume and format\n for (const result of mamResult.data) {\n // Only match m4b format\n if (!isM4bFormat(result.filetype)) {\n continue;\n }\n \n // Extract volume from MAM title\n const mamVolume = extractVolume(result.title || '');\n \n // If Nyaa has volume info, match it\n if (torrentData.volume_range) {\n if (isVolumeInRange(mamVolume, torrentData.volume_range)) {\n mamTorrentId = result.id;\n mamFound = true;\n break;\n }\n } else {\n // No volume info in Nyaa title, take first m4b match\n mamTorrentId = result.id;\n mamFound = true;\n break;\n }\n }\n }\n \n return {\n json: {\n ...torrentData,\n mam_torrent_id: mamTorrentId,\n mam_found_by_title: mamFound,\n mam_checked_at: new Date().toISOString()\n }\n };\n});"
|
||||
},
|
||||
"id": "parse-mam-title-results",
|
||||
"name": "Parse MAM Title Results",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1280,
|
||||
48
|
||||
],
|
||||
"executeOnce": false
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.mam_found_by_title }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "title-match-found",
|
||||
"name": "Title Match Found?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-496,
|
||||
336
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Extract author/narrator from Nyaa title for fallback search\nreturn $input.all().map(item => {\n const title = item.json.title || '';\n let searchTerm = '';\n \n // Try to extract author/narrator from common patterns\n // Pattern 1: \"Author Name - Book Title\"\n const dashPattern = /^([^-]+)\\s*-\\s*.+$/;\n const dashMatch = title.match(dashPattern);\n if (dashMatch) {\n searchTerm = dashMatch[1].trim();\n }\n \n // Pattern 2: [Author Name]\n if (!searchTerm) {\n const bracketPattern = /\\[([^\\]]+)\\]/;\n const bracketMatch = title.match(bracketPattern);\n if (bracketMatch) {\n searchTerm = bracketMatch[1].trim();\n }\n }\n \n // Pattern 3: \"by Author Name\"\n if (!searchTerm) {\n const byPattern = /\\bby\\s+([^-,(]+)/i;\n const byMatch = title.match(byPattern);\n if (byMatch) {\n searchTerm = byMatch[1].trim();\n }\n }\n \n // Fallback: use first few words of title\n if (!searchTerm) {\n const words = title.split(/\\s+/).slice(0, 3);\n searchTerm = words.join(' ');\n }\n \n return {\n json: {\n ...item.json,\n search_term: searchTerm\n }\n };\n});"
|
||||
},
|
||||
"id": "extract-author-narrator",
|
||||
"name": "Extract Author/Narrator",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-256,
|
||||
320
|
||||
],
|
||||
"executeOnce": false
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://www.myanonamouse.net/tor/js/loadSearchJSONbasic.php",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tor[text]",
|
||||
"value": "={{ $json.search_term }}"
|
||||
},
|
||||
{
|
||||
"name": "tor[srchIn][author]",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tor[srchIn][narrator]",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tor[searchType]",
|
||||
"value": "all"
|
||||
},
|
||||
{
|
||||
"name": "tor[main_cat][]",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"name": "tor[sortType]",
|
||||
"value": "default"
|
||||
},
|
||||
{
|
||||
"name": "tor[startNumber]",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": "perpage",
|
||||
"value": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "search-mam-by-author",
|
||||
"name": "Search MAM by Author/Narrator",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
-48,
|
||||
320
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "sxQYMPEq6GLbc9Av",
|
||||
"name": "n8n_auth_cookie"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Parse MAM author/narrator search results\n// Get the torrents that went through author/narrator search (filtered by title-not-found)\nconst searchedTorrents = $('Extract Author/Narrator').all();\nconst mamResults = $input.all();\n\n// Process each item\nreturn searchedTorrents.map((item, index) => {\n const mamResult = mamResults[index]?.json;\n const torrentData = item.json;\n \n // Check if MAM search returned results\n let mamTorrentId = torrentData.mam_torrent_id;\n \n // Only update if we didn't find by title\n if (!torrentData.mam_found_by_title && mamResult && mamResult.data && Array.isArray(mamResult.data) && mamResult.data.length > 0) {\n // Found at least one match - take the first result\n mamTorrentId = mamResult.data[0].id;\n }\n \n return {\n json: {\n ...torrentData,\n mam_torrent_id: mamTorrentId\n }\n };\n});"
|
||||
},
|
||||
"id": "parse-mam-author-results",
|
||||
"name": "Parse MAM Author Results",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
160,
|
||||
320
|
||||
],
|
||||
"executeOnce": false
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Merge data from both paths (title match and author/narrator search)\n// Note: Only one of these paths will execute depending on title match result\n\n// Get all input items from the current execution\nconst allResults = $input.all();\n\n// Clean up temporary fields used during MAM search\nreturn allResults.map(item => {\n const { mam_found_by_title, search_term, ...cleanData } = item.json;\n return {\n json: cleanData\n };\n});"
|
||||
},
|
||||
"id": "merge-mam-data",
|
||||
"name": "Merge MAM Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
400,
|
||||
320
|
||||
],
|
||||
"executeOnce": false
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 1
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.mam_torrent_id }}",
|
||||
"rightValue": "",
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
},
|
||||
"id": "41cd060f-08bc-4b5c-8396-ac604893a67a"
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "filter-needs-mam-update",
|
||||
"name": "Needs MAM Update?",
|
||||
"type": "n8n-nodes-base.filter",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
224,
|
||||
720
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Extract base title AND volume numbers for matching\nreturn $input.all().map(item => {\n let fullTitle = item.json.title || '';\n let baseTitle = fullTitle;\n let volumeInfo = null;\n \n // Extract volume information first (we'll need it for matching later)\n // Match patterns: Vol. 01-10, Vol 1-5, Volume 1, v01, Book 1, etc.\n const volPatterns = [\n /[Vv]ol\\.?\\s*(\\d+)(?:\\s*-\\s*(\\d+))?/,\n /[Vv]olume\\s*(\\d+)(?:\\s*-\\s*(\\d+))?/,\n /\\bv(\\d+)(?:-v?(\\d+))?\\b/,\n /[Bb]ook\\s*(\\d+)(?:\\s*-\\s*(\\d+))?/\n ];\n \n for (const pattern of volPatterns) {\n const match = fullTitle.match(pattern);\n if (match) {\n const startVol = parseInt(match[1]);\n const endVol = match[2] ? parseInt(match[2]) : startVol;\n volumeInfo = { start: startVol, end: endVol };\n break;\n }\n }\n \n // Remove all metadata to get base title\n baseTitle = baseTitle.replace(/\\s*[Vv]ol\\.?\\s*\\d+(-\\d+)?/gi, '');\n baseTitle = baseTitle.replace(/\\s*[Vv]olume\\s*\\d+(-\\d+)?/gi, '');\n baseTitle = baseTitle.replace(/\\s*\\bv\\d+(-v?\\d+)?\\b/gi, '');\n baseTitle = baseTitle.replace(/\\s*\\[[^\\]]+\\]/g, '');\n baseTitle = baseTitle.replace(/\\s*\\([^)]+\\)/g, '');\n baseTitle = baseTitle.replace(/\\s*[Bb]ook\\s*\\d+(-\\d+)?/gi, '');\n baseTitle = baseTitle.replace(/\\s*[Pp]art\\s*\\d+(-\\d+)?/gi, '');\n baseTitle = baseTitle.replace(/[,\\s-]+$/, '').trim();\n \n return {\n json: {\n ...item.json,\n search_title: baseTitle,\n volume_range: volumeInfo\n }\n };\n});"
|
||||
},
|
||||
"id": "extract-base-title",
|
||||
"name": "Extract Base Title",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
384,
|
||||
48
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Filter out torrents that already have MAM data\nconst nyaaTorrents = $('Extract Base Title').all();\nconst dbTorrents = $('Get DB Torrents for Filtering').all();\n\n// Create a map of info_hash -> mam_torrent_id from database\nconst checkedHashes = new Map();\nfor (const item of dbTorrents) {\n if (item.json.mam_torrent_id) {\n checkedHashes.set(item.json.info_hash, item.json.mam_torrent_id);\n }\n}\n\n// Filter Nyaa torrents to only include unchecked ones\nconst uncheckedTorrents = nyaaTorrents.filter(item => {\n return !checkedHashes.has(item.json.info_hash);\n});\n\nconsole.log(`Total torrents: ${nyaaTorrents.length}, Already checked: ${checkedHashes.size}, Need MAM search: ${uncheckedTorrents.length}`);\n\nreturn uncheckedTorrents;"
|
||||
},
|
||||
"id": "filter-unchecked-torrents",
|
||||
"name": "Filter Unchecked Torrents",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
832,
|
||||
48
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "Get DB Torrents for Filtering",
|
||||
"name": "Get DB Torrents for Filtering (Postgres)",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
500,
|
||||
100
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM nyaa_audiobooks;"
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "Update Existing Torrents MAM Data",
|
||||
"name": "Update Existing Torrents MAM Data (Postgres)",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1500,
|
||||
500
|
||||
],
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"options": {
|
||||
"queryParameterValues": "{{$json.mam_torrent_id}},{{$json.id}}"
|
||||
},
|
||||
"query": "UPDATE nyaa_audiobooks SET mam_torrent_id = $1, mam_checked_at = NOW(), updated_at = NOW() WHERE id = $2;"
|
||||
},
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking 'Execute workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"HTTP Request": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "XML",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"XML": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Feed Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Feed Items": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Torrent Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Every 5 Minutes": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has Seeders?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get many rows",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Compare Datasets",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get many rows": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compare Datasets",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code in JavaScript": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Gmail Notification",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Compare Datasets": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has New Torrents?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Needs MAM Update?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has New Torrents?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Insert New Torrents",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Insert New Torrents": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code in JavaScript",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search MAM by Title": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse MAM Title Results",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse MAM Title Results": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Title Match Found?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Title Match Found?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Author/Narrator",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge MAM Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Author/Narrator": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Search MAM by Author/Narrator",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search MAM by Author/Narrator": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse MAM Author Results",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse MAM Author Results": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge MAM Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge MAM Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has Seeders?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Torrent Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Base Title",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter Unchecked Torrents": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Search MAM by Title",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Base Title": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get DB Torrents for Filtering (Postgres)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get DB Torrents for Filtering (Postgres)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter Unchecked Torrents",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Needs MAM Update?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update Existing Torrents MAM Data (Postgres)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "5a1020ae-9654-4801-8ddc-48d87af2b558",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "kUg4HIPXraph3M0E",
|
||||
"isArchived": false
|
||||
}
|
||||
454
workflows/aS3YcO4RhtvsdH6q.json
Normal file
454
workflows/aS3YcO4RhtvsdH6q.json
Normal file
@@ -0,0 +1,454 @@
|
||||
{
|
||||
"id": "aS3YcO4RhtvsdH6q",
|
||||
"name": "transmission session mgmt",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-832,
|
||||
-256
|
||||
],
|
||||
"id": "6a15d79a-58f3-4bd0-8ad7-d78c3d2e1ef8",
|
||||
"name": "When clicking ‘Execute workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "condition-1",
|
||||
"leftValue": "={{ $json.exists_in_db }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-exists-branch",
|
||||
"name": "Record Exists?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1184,
|
||||
-160
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Configure all Transmission servers to manage\n// Add or remove server URLs as needed\n// Credentials are managed by n8n and applied to HTTP requests\n\nconst servers = [\n 'http://seed-1.dfw.ben.io:9091/transmission/rpc',\n 'http://seed-1.rdu.ben.io:9091/transmission/rpc',\n 'http://seed-1.lax.ben.io:9091/transmission/rpc',\n // Add more servers here:\n // 'http://seed-2.dfw.ben.io:9091/transmission/rpc',\n];\n\nreturn servers.map(url => ({\n json: { url }\n}));"
|
||||
},
|
||||
"id": "configure-servers-code",
|
||||
"name": "Configure Servers",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-608,
|
||||
-160
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Match each server with its existing session (if any)\n// Takes servers from Configure Servers and sessions from Get All Stored Sessions\n\nconst servers = $('Configure Servers').all();\nconst sessions = $('Get All Stored Sessions').all();\n\n// Create a lookup map of server_url -> session data\nconst sessionMap = {};\nfor (const session of sessions) {\n sessionMap[session.json.server_url] = session.json;\n}\n\n// Match each server with its session\nconst results = [];\nfor (const server of servers) {\n const url = server.json.url;\n const existingSession = sessionMap[url];\n \n results.push({\n json: {\n url: url,\n session_id: existingSession?.session_id || null,\n has_session: !!existingSession\n }\n });\n}\n\nreturn results;"
|
||||
},
|
||||
"id": "match-servers-sessions",
|
||||
"name": "Match Servers with Sessions",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
-160,
|
||||
-160
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 1
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "condition-1",
|
||||
"leftValue": "={{ $json.has_session }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "has-existing-session-if",
|
||||
"name": "Has Existing Session?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
64,
|
||||
-160
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $json.url }}",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpBasicAuth",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-Transmission-Session-Id",
|
||||
"value": "={{ $json.session_id }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "{\"method\": \"session-get\"}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"fullResponse": true,
|
||||
"neverError": true
|
||||
}
|
||||
},
|
||||
"timeout": 5000
|
||||
}
|
||||
},
|
||||
"id": "validate-session-http",
|
||||
"name": "Validate Session",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
288,
|
||||
-240
|
||||
],
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"id": "iymUPilnVhfL3h5D",
|
||||
"name": "transmission"
|
||||
}
|
||||
},
|
||||
"onError": "continueErrorOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "={{ $json.url }}",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpBasicAuth",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"fullResponse": true,
|
||||
"neverError": true
|
||||
}
|
||||
},
|
||||
"timeout": 10000
|
||||
}
|
||||
},
|
||||
"id": "get-new-session-http",
|
||||
"name": "Get New Session ID",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
512,
|
||||
-160
|
||||
],
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"id": "iymUPilnVhfL3h5D",
|
||||
"name": "transmission"
|
||||
}
|
||||
},
|
||||
"onError": "continueErrorOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Extract the X-Transmission-Session-Id from the 409 error response\n// Transmission returns a 409 error with the session ID in the HTML body\n\nconst items = $input.all();\nconst results = [];\n\n// Get all items from the Get New Session ID node to preserve the URL\nconst httpRequestItems = $('Get New Session ID').all();\n\nfor (let i = 0; i < items.length; i++) {\n const item = items[i];\n let sessionId = null;\n \n // Get the server URL from the corresponding HTTP request input\n let serverUrl = httpRequestItems[i]?.json?.url;\n \n if (!serverUrl) {\n // Fallback: try to get from Match Servers node\n const matchedItems = $('Match Servers with Sessions').all();\n if (matchedItems[i]?.json?.url) {\n serverUrl = matchedItems[i].json.url;\n } else {\n throw new Error('Could not determine server URL from workflow context');\n }\n }\n \n // Try to get session ID from headers first (if available)\n if (item.json?.headers?.['x-transmission-session-id']) {\n sessionId = item.json.headers['x-transmission-session-id'];\n }\n // Check if it's in the response body directly\n else if (item.json?.body && typeof item.json.body === 'string') {\n const match = item.json.body.match(/X-Transmission-Session-Id:\\s*([A-Za-z0-9]+)/i);\n if (match && match[1]) {\n sessionId = match[1];\n }\n }\n // Try to extract from error message HTML\n else if (item.json?.error?.message) {\n const errorMessage = item.json.error.message;\n const match = errorMessage.match(/X-Transmission-Session-Id:\\s*([A-Za-z0-9]+)/i);\n if (match && match[1]) {\n sessionId = match[1];\n }\n }\n // Check error object directly\n else if (item.error?.message) {\n const errorMessage = item.error.message;\n const match = errorMessage.match(/X-Transmission-Session-Id:\\s*([A-Za-z0-9]+)/i);\n if (match && match[1]) {\n sessionId = match[1];\n }\n }\n \n if (sessionId) {\n results.push({\n json: {\n session_id: sessionId,\n server_url: serverUrl\n }\n });\n } else {\n // Debug: Show what data we received\n const debugInfo = {\n hasHeaders: !!item.json?.headers,\n hasBody: !!item.json?.body,\n hasError: !!item.json?.error,\n hasErrorDirect: !!item.error,\n bodyType: typeof item.json?.body,\n statusCode: item.json?.statusCode,\n keys: Object.keys(item.json || {})\n };\n throw new Error(`Could not extract session ID from response for server: ${serverUrl}. Debug: ${JSON.stringify(debugInfo)}`);\n }\n}\n\nreturn results;"
|
||||
},
|
||||
"id": "extract-new-session-code",
|
||||
"name": "Extract Session ID",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
736,
|
||||
-160
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Check if each session already exists in the database\n// This preserves ALL items and adds a database_record field\n\nconst items = $input.all();\nconst allStoredSessions = $('Get All Stored Sessions').all();\n\n// Create a lookup map of server_url -> database record\nconst dbSessionMap = {};\nfor (const session of allStoredSessions) {\n dbSessionMap[session.json.server_url] = session.json;\n}\n\nconst results = [];\nfor (const item of items) {\n const serverUrl = item.json.server_url;\n const dbRecord = dbSessionMap[serverUrl];\n \n results.push({\n json: {\n ...item.json,\n db_record: dbRecord || null,\n exists_in_db: !!dbRecord\n }\n });\n}\n\nreturn results;"
|
||||
},
|
||||
"id": "check-db-exists-code",
|
||||
"name": "Lookup Existing Records",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
960,
|
||||
-160
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
-832,
|
||||
-64
|
||||
],
|
||||
"id": "d3f1c5f5-2c71-4295-b383-66223212d448",
|
||||
"name": "Schedule Trigger"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "INSERT INTO transmission_sessions (server_url, session_id, last_updated) VALUES ($1, $2, NOW());",
|
||||
"additionalFields": {
|
||||
"queryParams": "={{ $json.session_id }}, {{ $json.server_url }}"
|
||||
}
|
||||
},
|
||||
"id": "supabase-create",
|
||||
"name": "Create New Session (Postgres)",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1408,
|
||||
-64
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "UPDATE transmission_sessions SET session_id = $1, last_updated = NOW() WHERE server_url = $2;",
|
||||
"additionalFields": {
|
||||
"queryParams": "={{ $json.session_id }}, {{ $json.server_url }}"
|
||||
}
|
||||
},
|
||||
"id": "supabase-update",
|
||||
"name": "Update Existing Session (Postgres)",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1408,
|
||||
-256
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM transmission_sessions;",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "get-all-sessions",
|
||||
"name": "Get All Stored Sessions",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-384,
|
||||
-160
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Configure Servers",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Match Servers with Sessions": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has Existing Session?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has Existing Session?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Validate Session",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Get New Session ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get New Session ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Session ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Extract Session ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Session ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Lookup Existing Records",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Lookup Existing Records": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Record Exists?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Validate Session": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get New Session ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Configure Servers",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Configure Servers": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get All Stored Sessions",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Record Exists?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update Existing Session (Postgres)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Create New Session (Postgres)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get All Stored Sessions": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Match Servers with Sessions",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "05f3ebfa-af37-4ebb-a75d-755f54238d2e",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "kUg4HIPXraph3M0E",
|
||||
"isArchived": false
|
||||
}
|
||||
294
workflows/akfNprhHotiw4e2s.json
Normal file
294
workflows/akfNprhHotiw4e2s.json
Normal file
@@ -0,0 +1,294 @@
|
||||
{
|
||||
"id": "akfNprhHotiw4e2s",
|
||||
"name": "Zabbix Alert Ingestion",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "zabbix-alert",
|
||||
"options": {}
|
||||
},
|
||||
"id": "webhook-trigger",
|
||||
"name": "Zabbix Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
240,
|
||||
400
|
||||
],
|
||||
"webhookId": "zabbix-alert-webhook",
|
||||
"notes": "Receives POST requests from Zabbix with alert data"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Parse and normalize incoming Zabbix alert data\nconst body = $input.item.json.body || $input.item.json;\n\nconsole.log('=== Zabbix Alert Received ===');\nconsole.log('Raw payload:', JSON.stringify(body, null, 2));\n\n// Extract alert fields from Zabbix webhook payload\nconst alert = {\n event_id: body.event_id || body.eventid || 'unknown',\n event_value: body.event_value || body.value || '1',\n problem_name: body.trigger_name || body.name || 'Unknown Problem',\n severity: parseInt(body.trigger_severity || body.severity || '0'),\n host_id: body.host_id || body.hostid || '',\n host_name: body.host_name || body.host || 'Unknown Host',\n trigger_id: body.trigger_id || body.triggerid || '',\n operational_data: body.event_opdata || body.opdata || '',\n tags: body.event_tags || body.tags || [],\n timestamp: body.event_date && body.event_time ? `${body.event_date} ${body.event_time}` : new Date().toISOString(),\n clock: body.clock || Math.floor(Date.now() / 1000)\n};\n\nif (typeof alert.tags === 'string') {\n try {\n alert.tags = JSON.parse(alert.tags);\n } catch (e) {\n alert.tags = alert.tags.includes(',') ? alert.tags.split(',').map(t => ({tag: t.trim()})) : [{tag: alert.tags}];\n }\n}\n\nif (!Array.isArray(alert.tags)) {\n alert.tags = [];\n}\n\nconst severityNames = {0: 'Not classified', 1: 'Information', 2: 'Warning', 3: 'Average', 4: 'High', 5: 'Disaster'};\nalert.severity_name = severityNames[alert.severity] || 'Unknown';\n\nconst locationMatch = alert.host_name.match(/\\.(dfw|lax|rdu|tpa|local)\\./i);\nalert.location = locationMatch ? locationMatch[1].toLowerCase() : 'unknown';\n\nlet systemType = 'unknown';\nconst hostname = alert.host_name.toLowerCase();\nconst tagStr = alert.tags.map(t => (t.tag || t.value || '').toLowerCase()).join(' ');\n\nif (hostname.includes('pve') || tagStr.includes('proxmox')) {\n systemType = 'proxmox';\n} else if (hostname.includes('pfsense') || tagStr.includes('pfsense')) {\n systemType = 'pfsense';\n} else if (hostname.includes('truenas') || tagStr.includes('truenas')) {\n systemType = 'truenas';\n} else if (hostname.includes('mysql') || tagStr.includes('mysql')) {\n systemType = 'database';\n} else if (hostname.includes('web') || hostname.includes('www')) {\n systemType = 'web';\n} else if (hostname.includes('usw') || hostname.includes('uap') || hostname.includes('ucg')) {\n systemType = 'network';\n} else if (hostname.includes('qnap') || hostname.includes('nas')) {\n systemType = 'storage';\n} else if (tagStr.includes('linux')) {\n systemType = 'linux';\n}\nalert.system_type = systemType;\n\nlet category = 'unknown';\nif (tagStr.includes('availability')) {\n category = 'availability';\n} else if (tagStr.includes('performance')) {\n category = 'performance';\n} else if (tagStr.includes('capacity')) {\n category = 'capacity';\n} else if (tagStr.includes('hardware') || tagStr.includes('temperature')) {\n category = 'hardware';\n}\nalert.category = category;\n\nlet priorityScore = alert.severity * 10;\nif (category === 'availability') priorityScore += 5;\nif (systemType === 'proxmox' || systemType === 'pfsense') priorityScore += 3;\nalert.priority_score = priorityScore;\nalert.raw_payload = body;\n\nconsole.log('=== Parsed Alert ===');\nconsole.log('Event ID:', alert.event_id);\nconsole.log('Host:', alert.host_name);\nconsole.log('Problem:', alert.problem_name);\nconsole.log('Severity:', alert.severity_name, `(${alert.severity})`);\nconsole.log('Location:', alert.location);\nconsole.log('System Type:', alert.system_type);\nconsole.log('Category:', alert.category);\n\nreturn { json: alert };"
|
||||
},
|
||||
"id": "parse-alert-data",
|
||||
"name": "Parse Alert Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
464,
|
||||
400
|
||||
],
|
||||
"notes": "Extracts and normalizes alert data from webhook payload"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://zabbix.ext.ben.io/api_jsonrpc.php",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "zabbixApi",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\"jsonrpc\": \"2.0\", \"method\": \"host.get\", \"params\": {\"hostids\": \"{{ $json.host_id }}\", \"output\": [\"hostid\", \"host\", \"name\", \"status\"], \"selectGroups\": \"extend\", \"selectInterfaces\": [\"type\", \"ip\", \"port\"], \"selectInventory\": [\"location\", \"notes\", \"os\"]}, \"id\": 1}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "query-host-details",
|
||||
"name": "Query Host Details",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
688,
|
||||
400
|
||||
],
|
||||
"credentials": {
|
||||
"zabbixApi": {
|
||||
"id": "j03eT0qxJpv7H5an",
|
||||
"name": "Zabbix account"
|
||||
}
|
||||
},
|
||||
"notes": "Fetches full host information from Zabbix API"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://zabbix.ext.ben.io/api_jsonrpc.php",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "zabbixApi",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\"jsonrpc\": \"2.0\", \"method\": \"problem.get\", \"params\": {\"eventids\": \"{{ $('Parse Alert Data').item.json.event_id }}\", \"output\": \"extend\", \"selectAcknowledges\": \"extend\", \"selectTags\": \"extend\", \"selectSuppressionData\": \"extend\"}, \"id\": 2}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "query-problem-details",
|
||||
"name": "Query Problem Details",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
912,
|
||||
400
|
||||
],
|
||||
"credentials": {
|
||||
"zabbixApi": {
|
||||
"id": "j03eT0qxJpv7H5an",
|
||||
"name": "Zabbix account"
|
||||
}
|
||||
},
|
||||
"notes": "Fetches problem details including acknowledgements"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://zabbix.ext.ben.io/api_jsonrpc.php",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "zabbixApi",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\"jsonrpc\": \"2.0\", \"method\": \"trigger.get\", \"params\": {\"triggerids\": \"{{ $('Parse Alert Data').item.json.trigger_id }}\", \"output\": \"extend\", \"selectTags\": \"extend\"}, \"id\": 3}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "query-trigger-details",
|
||||
"name": "Query Trigger Details",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1120,
|
||||
400
|
||||
],
|
||||
"credentials": {
|
||||
"zabbixApi": {
|
||||
"id": "j03eT0qxJpv7H5an",
|
||||
"name": "Zabbix account"
|
||||
}
|
||||
},
|
||||
"notes": "Fetches trigger configuration including resolution hints"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const parsedAlert = $('Parse Alert Data').item.json;\nconst hostData = $('Query Host Details').item.json.result?.[0] || {};\nconst problemData = $('Query Problem Details').item.json.result?.[0] || {};\nconst triggerData = $('Query Trigger Details').item.json.result?.[0] || {};\n\nconst enrichedAlert = {\n ...parsedAlert,\n host_status: hostData.status === '0' ? 'enabled' : 'disabled',\n host_groups: (hostData.groups || []).map(g => g.name),\n host_interfaces: hostData.interfaces || [],\n host_inventory: hostData.inventory || {},\n acknowledged: problemData.acknowledged === '1',\n suppressed: problemData.suppressed === '1',\n acknowledgements: problemData.acknowledges || [],\n problem_tags: problemData.tags || [],\n trigger_description: triggerData.description || parsedAlert.problem_name,\n trigger_comments: triggerData.comments || '',\n trigger_expression: triggerData.expression || '',\n trigger_priority: triggerData.priority || parsedAlert.severity,\n enriched_at: new Date().toISOString(),\n enrichment_success: true\n};\n\nif (enrichedAlert.trigger_comments) {\n const comments = enrichedAlert.trigger_comments.toLowerCase();\n enrichedAlert.resolution_hints = [];\n if (comments.includes('restart')) enrichedAlert.resolution_hints.push('Service restart may resolve this issue');\n if (comments.includes('connectivity') || comments.includes('ping')) enrichedAlert.resolution_hints.push('Check network connectivity');\n if (comments.includes('acknowledge')) enrichedAlert.resolution_hints.push('Manual acknowledgement suggested');\n}\n\nenrichedAlert.should_notify = enrichedAlert.severity >= 3;\nenrichedAlert.notification_channels = [];\nif (enrichedAlert.severity >= 4) {\n enrichedAlert.notification_channels.push('email');\n} else if (enrichedAlert.severity === 3 && !enrichedAlert.suppressed) {\n enrichedAlert.notification_channels.push('email');\n}\n\nconsole.log('=== Enriched Alert Complete ===');\nconsole.log('Should Notify:', enrichedAlert.should_notify);\n\nreturn { json: enrichedAlert };"
|
||||
},
|
||||
"id": "enrich-alert",
|
||||
"name": "Enrich Alert",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1344,
|
||||
400
|
||||
],
|
||||
"notes": "Combines all enrichment data and determines notification strategy"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"boolean": [
|
||||
{
|
||||
"value1": "={{ $json.should_notify }}",
|
||||
"value2": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "should-notify-check",
|
||||
"name": "Should Notify?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1568,
|
||||
400
|
||||
],
|
||||
"notes": "Checks if notification should be sent based on severity and suppression"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const alert = $input.item.json;\nconsole.log('=== Notification Skipped ===');\nconsole.log('Reason: Severity too low or alert suppressed');\nconsole.log('Severity:', alert.severity_name);\nconsole.log('Suppressed:', alert.suppressed);\nreturn {json: {skipped: true, reason: alert.suppressed ? 'Alert is suppressed' : 'Severity below notification threshold', alert_summary: `${alert.problem_name} on ${alert.host_name}`}};"
|
||||
},
|
||||
"id": "skip-notification-log",
|
||||
"name": "Skip Notification Log",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1792,
|
||||
512
|
||||
],
|
||||
"notes": "Logs when notification is skipped"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"sendTo": "admin@ben.io",
|
||||
"subject": "=[{{ $json.severity_name }}] {{ $json.problem_name }} on {{ $json.host_name }}",
|
||||
"message": "Test Message",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
1776,
|
||||
304
|
||||
],
|
||||
"id": "e814bfea-ec17-46ac-8f98-7ab9b2e6244e",
|
||||
"name": "Send a message",
|
||||
"webhookId": "daf00ce2-f6a0-4c4e-94a8-0962bd3a1224",
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "Os1ux3h3zFlC2XkG",
|
||||
"name": "Gmail account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Zabbix Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Alert Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Alert Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Query Host Details",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Query Host Details": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Query Problem Details",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Query Problem Details": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Query Trigger Details",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Query Trigger Details": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Enrich Alert",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Enrich Alert": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Should Notify?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Should Notify?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send a message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Skip Notification Log",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"saveExecutionProgress": true,
|
||||
"saveManualExecutions": true,
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "baf99192-61ed-4321-b831-5c212e89bf23",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "LTWZD96boqxk9sIs",
|
||||
"isArchived": false
|
||||
}
|
||||
105
workflows/b0-vjBKMduuAdkCRjpbqb.json
Normal file
105
workflows/b0-vjBKMduuAdkCRjpbqb.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"id": "b0-vjBKMduuAdkCRjpbqb",
|
||||
"name": "ai chat testing workflow",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"batching": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.chainLlm",
|
||||
"typeVersion": 1.9,
|
||||
"position": [
|
||||
176,
|
||||
-32
|
||||
],
|
||||
"id": "df224c8b-a9cf-4e28-af01-3f04f2957c1a",
|
||||
"name": "Basic LLM Chain"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"value": "nvidia_nim/openai/gpt-oss-120b",
|
||||
"mode": "list",
|
||||
"cachedResultName": "nvidia_nim/openai/gpt-oss-120b"
|
||||
},
|
||||
"responsesApiEnabled": false,
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
176,
|
||||
144
|
||||
],
|
||||
"id": "0ba4513e-44ab-4253-8ec7-6acce6496cbd",
|
||||
"name": "OpenAI Chat Model",
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "sxSUdecXdMfKPuTu",
|
||||
"name": "llm-proxy.ben.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"availableInChat": true,
|
||||
"agentName": "Test Agent",
|
||||
"agentDescription": "Test Agent",
|
||||
"options": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
|
||||
"typeVersion": 1.4,
|
||||
"position": [
|
||||
-16,
|
||||
-32
|
||||
],
|
||||
"id": "f2f350fc-97ce-49fc-bf2b-0315fc0a341e",
|
||||
"name": "When chat message received",
|
||||
"webhookId": "b84e5f82-65f1-456c-8b50-0220cf2e7fb0"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"OpenAI Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Basic LLM Chain",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Basic LLM Chain": {
|
||||
"main": [
|
||||
[]
|
||||
]
|
||||
},
|
||||
"When chat message received": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Basic LLM Chain",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "7e520306-b51d-4902-afd0-0564ec0c2ac6",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": false
|
||||
}
|
||||
116
workflows/c3N3bYrOAy0rNGGq.json
Normal file
116
workflows/c3N3bYrOAy0rNGGq.json
Normal file
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"id": "c3N3bYrOAy0rNGGq",
|
||||
"name": "MAM Series Search API Tool",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://www.myanonamouse.net/tor/js/loadSearchJSONbasic.php",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tor[text]",
|
||||
"value": "={{ $json.search_term }}"
|
||||
},
|
||||
{
|
||||
"name": "tor[srchIn][title]",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tor[searchType]",
|
||||
"value": "all"
|
||||
},
|
||||
{
|
||||
"name": "tor[main_cat][]",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"name": "perpage",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"name": "tor[srchIn][author]",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tor[srchIn][series]",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "search-mam",
|
||||
"name": "Search MAM API",
|
||||
"position": [
|
||||
1120,
|
||||
208
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "G8eA8XeS9P5axwJd",
|
||||
"name": "mam cookie auth header"
|
||||
},
|
||||
"httpBasicAuth": {
|
||||
"id": "BRiFacyi0A60Y7ZZ",
|
||||
"name": "mam_id"
|
||||
}
|
||||
},
|
||||
"onError": "continueRegularOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowInputs": {
|
||||
"values": [
|
||||
{
|
||||
"name": "search_term",
|
||||
"type": "any"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "40e97f3c-ba0d-4830-930c-7bcef06f9cfc",
|
||||
"typeVersion": 1.1,
|
||||
"name": "Start",
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"position": [
|
||||
912,
|
||||
208
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Start": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Search MAM API",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "40888e29-e899-4892-b5b7-4fa691b7f007",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": false
|
||||
}
|
||||
1219
workflows/cPWZKfrHOUSUZjIp.json
Normal file
1219
workflows/cPWZKfrHOUSUZjIp.json
Normal file
File diff suppressed because it is too large
Load Diff
376
workflows/cSg8UMX1OvzDoLrk.json
Normal file
376
workflows/cSg8UMX1OvzDoLrk.json
Normal file
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"id": "cSg8UMX1OvzDoLrk",
|
||||
"name": "EVE Bazaar Ingest",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "minutes",
|
||||
"minutesInterval": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "Schedule Trigger",
|
||||
"name": "Schedule Trigger",
|
||||
"position": [
|
||||
0,
|
||||
-24
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.1
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://forums.eveonline.com/c/marketplace/character-bazaar/60.rss",
|
||||
"options": {}
|
||||
},
|
||||
"id": "RSS Feed Read",
|
||||
"name": "RSS Feed Read",
|
||||
"position": [
|
||||
224,
|
||||
-24
|
||||
],
|
||||
"type": "n8n-nodes-base.rssFeedRead",
|
||||
"typeVersion": 1.2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return $input.all().filter(item => {\n const title = (item.json.title || \"\").toUpperCase();\n return title.includes(\"WTS\") && !title.includes(\"WTB\");\n});"
|
||||
},
|
||||
"id": "Filter WTS",
|
||||
"name": "Filter WTS",
|
||||
"position": [
|
||||
448,
|
||||
-24
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nreturn items.map(item => {\n const content = item.json.content || item.json.contentSnippet || \"\";\n const link = item.json.link || \"\";\n const text = content + \" \" + link;\n \n let charId = null;\n let skillqUrl = null;\n \n const patterns = [\n /evewho\\.com\\/character\\/(\\d+)/,\n /zkillboard\\.com\\/character\\/(\\d+)/,\n /qsna\\.eu\\/eve\\/characters?\\/(\\d+)/,\n /eveboard\\.com\\/pilot\\/([^\\/]+)/,\n /images\\.evetech\\.net\\/characters\\/(\\d+)\\//\n ];\n \n for (const pattern of patterns) {\n const match = text.match(pattern);\n if (match) {\n charId = match[1];\n break;\n }\n }\n \n // Look for SkillQ URL if no ID found\n if (!charId) {\n // Updated regex to require /share/ component and capture full path\n const skillqMatch = text.match(/skillq\\.net\\/char\\/([^\\/]+)\\/share\\/([a-f0-9\\-]+)/);\n if (skillqMatch) {\n // Construct full URL with share token\n // match[1] is name, match[2] is token\n skillqUrl = `https://skillq.net/char/${skillqMatch[1]}/share/${skillqMatch[2]}`;\n }\n }\n \n return {\n json: {\n ...item.json,\n character_id: charId,\n skillq_url: skillqUrl\n }\n };\n});"
|
||||
},
|
||||
"id": "Extract Character ID",
|
||||
"name": "Extract Character ID",
|
||||
"position": [
|
||||
672,
|
||||
-24
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst ids = items.map(item => item.json.post_id).filter(id => id);\nreturn [{\n json: {\n ids: ids\n }\n}];"
|
||||
},
|
||||
"id": "Prepare ID List",
|
||||
"name": "Prepare ID List",
|
||||
"position": [
|
||||
2016,
|
||||
-24
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM character_bazaar_posts WHERE post_id IN ('{{ $json.ids.join(\"','\") }}')",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Lookup Posts",
|
||||
"name": "Lookup Posts",
|
||||
"position": [
|
||||
2240,
|
||||
-24
|
||||
],
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.4,
|
||||
"alwaysOutputData": true,
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "dsnKfvOBMkgU21Lt",
|
||||
"name": "supabase postgres account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "return $input.all().map(item => {\n const json = item.json;\n const escape = (str) => str ? str.replace(/'/g, \"''\") : '';\n \n const post_id = escape(json.post_id);\n const title = escape(json.title);\n const url = escape(json.url);\n const author = escape(json.author);\n const content = escape(json.content);\n const character_id = json.character_id ? `'${json.character_id}'` : 'NULL';\n\n const query = `INSERT INTO character_bazaar_posts (post_id, title, url, author, content, post_status, character_id)\nVALUES ('${post_id}', '${title}', '${url}', '${author}', '${content}', 'new', ${character_id})\nON CONFLICT (post_id) DO UPDATE SET\ntitle = EXCLUDED.title,\nurl = EXCLUDED.url,\nauthor = EXCLUDED.author,\ncontent = EXCLUDED.content,\npost_status = 'updated',\ncharacter_id = EXCLUDED.character_id;`;\n\n return {\n json: {\n ...json,\n query\n }\n };\n});"
|
||||
},
|
||||
"id": "Routing",
|
||||
"name": "Routing",
|
||||
"position": [
|
||||
2464,
|
||||
-24
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "={{ $json.query }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Store Results",
|
||||
"name": "Store Results",
|
||||
"position": [
|
||||
2688,
|
||||
-24
|
||||
],
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.4,
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "dsnKfvOBMkgU21Lt",
|
||||
"name": "supabase postgres account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"boolean": [
|
||||
{
|
||||
"value1": "={{ $json.skillq_url && !$json.character_id }}",
|
||||
"value2": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "If SkillQ Repair",
|
||||
"name": "If SkillQ Repair",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
896,
|
||||
-24
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{ $json.skillq_url }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Scrape SkillQ",
|
||||
"name": "Scrape SkillQ",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.1,
|
||||
"position": [
|
||||
1120,
|
||||
-24
|
||||
],
|
||||
"onError": "continueRegularOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\n\nreturn items.map(item => {\n // HTML should be in skillq_html (from Scrape SkillQ) or data/body\n const html = item.json.skillq_html || item.json.data || item.json.body;\n \n let charId = null;\n let error = null;\n \n try {\n if (html && typeof html === 'string') {\n const match = html.match(/images\\.evetech\\.net\\/characters\\/(\\d+)\\//);\n if (match) {\n charId = match[1];\n } else {\n error = \"Could not find character ID in SkillQ page\";\n }\n } else {\n error = \"Invalid or empty response from SkillQ\";\n }\n } catch (e) {\n error = \"Error parsing SkillQ response: \" + e.message;\n }\n \n return {\n json: {\n ...item.json, // Keep all original data (merged from context)\n character_id: charId || item.json.character_id,\n scraping_error: error\n }\n };\n});"
|
||||
},
|
||||
"id": "Parse Scraped ID",
|
||||
"name": "Parse Scraped ID",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1568,
|
||||
-96
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "let scraped = [];\ntry {\n // Try to get items from Parse Scraped ID\n // This will throw if the node hasn't executed (e.g. no items went to True branch)\n scraped = $('Parse Scraped ID').all();\n} catch (e) {\n // Node didn't execute, which is fine\n scraped = [];\n}\n\nconst skipped = $('If SkillQ Repair').all(1); // Get False branch items\n\nreturn [...scraped, ...skipped];"
|
||||
},
|
||||
"id": "Merge Manual",
|
||||
"name": "Merge Manual",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1792,
|
||||
-24
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "combine",
|
||||
"combineBy": "combineByPosition",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Merge Context",
|
||||
"name": "Merge Context",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3,
|
||||
"position": [
|
||||
1344,
|
||||
-96
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Extract Character ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If SkillQ Repair",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter WTS": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Extract Character ID",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Lookup Posts": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Routing",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare ID List": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Lookup Posts",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"RSS Feed Read": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Filter WTS",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Routing": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Store Results",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "RSS Feed Read",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If SkillQ Repair": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Scrape SkillQ",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge Context",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Merge Manual",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Scraped ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Manual",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Manual": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare ID List",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Scrape SkillQ": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Context",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Context": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Scraped ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveManualExecutions": true,
|
||||
"saveExecutionProgress": true,
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "51b5294b-1a43-4f17-a9e2-440a026e24fd",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "HWgaFb7kLF649L7l",
|
||||
"isArchived": false
|
||||
}
|
||||
155
workflows/dnhuvwoSObAdZ6Ha.json
Normal file
155
workflows/dnhuvwoSObAdZ6Ha.json
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"id": "dnhuvwoSObAdZ6Ha",
|
||||
"name": "Post RSS feed items from yesterday to Slack",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"functionCode": "// Create our Slack message\n// This will output a list of RSS items in the following format\n// Title - Description\nlet message = \"*:new: Posts from yesterday :new:*\\n\\n\";\n\n// Loop the input items\nfor (item of items) {\n message += \"*<\" + item.json.link + \"|\" + item.json.title + \">*\\n\" + item.json.contentSnippet + \"\\n\\n\"; \n}\n\n// Return our message\nreturn [{json: {message}}];"
|
||||
},
|
||||
"name": "Build our message",
|
||||
"type": "n8n-nodes-base.function",
|
||||
"position": [
|
||||
368,
|
||||
240
|
||||
],
|
||||
"typeVersion": 1,
|
||||
"id": "cb2da17f-e839-420a-bde5-45a7f4717118"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"triggerTimes": {
|
||||
"item": [
|
||||
{
|
||||
"hour": 8
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "Every Morning",
|
||||
"type": "n8n-nodes-base.cron",
|
||||
"position": [
|
||||
-416,
|
||||
272
|
||||
],
|
||||
"typeVersion": 1,
|
||||
"id": "961e8b96-1de5-44da-9f61-3c829b0dfead"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"action": "calculate",
|
||||
"value": "={{Date()}}",
|
||||
"operation": "subtract",
|
||||
"duration": 1,
|
||||
"options": {}
|
||||
},
|
||||
"name": "Get Yesterdays Date",
|
||||
"type": "n8n-nodes-base.dateTime",
|
||||
"position": [
|
||||
-240,
|
||||
272
|
||||
],
|
||||
"typeVersion": 1,
|
||||
"id": "960ec636-ff35-486f-bafd-0808bc5c9969"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://n8n.io/blog/rss",
|
||||
"options": {}
|
||||
},
|
||||
"name": "Get the RSS Feed",
|
||||
"type": "n8n-nodes-base.rssFeedRead",
|
||||
"position": [
|
||||
-48,
|
||||
272
|
||||
],
|
||||
"typeVersion": 1,
|
||||
"id": "9b4912a7-adf3-4bba-8fc6-207c4973abf2"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"dateTime": [
|
||||
{
|
||||
"value1": "={{$item(0).$node[\"Get Yesterdays Date\"].json.data}}",
|
||||
"operation": "before",
|
||||
"value2": "={{$json[\"pubDate\"]}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "If it was published after yesterday",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"position": [
|
||||
160,
|
||||
272
|
||||
],
|
||||
"typeVersion": 1,
|
||||
"id": "7a4f841b-38c8-4e5a-92cd-16b8ba3ba495",
|
||||
"continueOnFail": true
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Every Morning": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get Yesterdays Date",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get the RSS Feed": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "If it was published after yesterday",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build our message": {
|
||||
"main": [
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Get Yesterdays Date": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get the RSS Feed",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"If it was published after yesterday": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build our message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "44effa1f-4485-4803-8049-970f8fffcfc1",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": true
|
||||
}
|
||||
93
workflows/eDpNvtTwQJSGR279.json
Normal file
93
workflows/eDpNvtTwQJSGR279.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"id": "eDpNvtTwQJSGR279",
|
||||
"name": "Test Character Bazaar Logic",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Manual Trigger",
|
||||
"name": "Manual Trigger",
|
||||
"parameters": {},
|
||||
"position": [
|
||||
100,
|
||||
100
|
||||
],
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"id": "Filter WTS Code",
|
||||
"name": "Filter WTS Code",
|
||||
"parameters": {
|
||||
"jsCode": "const items = [\n {\n json: {\n title: \"WTS [SOLD] Character A\",\n content: \"Selling my character\",\n contentSnippet: \"Selling my character\"\n }\n },\n {\n json: {\n title: \"WTS Character B [LOCKED]\",\n content: \"Topic locked\",\n contentSnippet: \"Topic locked\"\n }\n },\n {\n json: {\n title: \"WTS Character C\",\n content: \"Active sale\",\n contentSnippet: \"Active sale\"\n }\n }\n];\n\nreturn items.map(item => {\n const title = (item.json.title || \"\").toUpperCase();\n const content = item.json.content || item.json.contentSnippet || \"\";\n let status = null;\n\n // Check for SOLD\n if (title.includes('[SOLD]') || title.startsWith('SOLD ') || title === 'SOLD') {\n status = 'sale_completed';\n }\n \n // Check for Locked/Hidden (Best effort from RSS)\n if (title.includes('[LOCKED]') || content.includes('Topic locked')) {\n status = 'pending_response';\n }\n\n item.json.post_status = status;\n return item;\n});"
|
||||
},
|
||||
"position": [
|
||||
300,
|
||||
100
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"id": "Check Pre-Filter",
|
||||
"name": "Check Pre-Filter",
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"string": [
|
||||
{
|
||||
"operation": "isNotEmpty",
|
||||
"value1": "={{ $json.post_status }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"position": [
|
||||
500,
|
||||
100
|
||||
],
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 1
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Filter WTS Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Check Pre-Filter",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Manual Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Filter WTS Code",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveManualExecutions": true,
|
||||
"saveExecutionProgress": true,
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "20a120b7-d765-489d-8437-5448581214e2",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": true
|
||||
}
|
||||
138
workflows/f2rn29FKq1ejX2ax.json
Normal file
138
workflows/f2rn29FKq1ejX2ax.json
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
"id": "f2rn29FKq1ejX2ax",
|
||||
"name": "GTasks: Get All Tasklists",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"inputSource": "passthrough"
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-496,
|
||||
-32
|
||||
],
|
||||
"id": "f59e1396-3f70-4fbe-b3e7-55bf3dc81643",
|
||||
"name": "When Executed by Another Workflow"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://tasks.googleapis.com/tasks/v1/users/@me/lists",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "googleTasksOAuth2Api",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
-288,
|
||||
-32
|
||||
],
|
||||
"id": "e12cd0e7-b2ce-411d-a8bc-91ffadde6d61",
|
||||
"name": "HTTP Request",
|
||||
"credentials": {
|
||||
"googleTasksOAuth2Api": {
|
||||
"id": "ErcYIjq8Xs0GRaPF",
|
||||
"name": "ben.io-gtasks"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "items",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-64,
|
||||
-32
|
||||
],
|
||||
"id": "f0e3ebe4-5356-4598-97df-cde07e4e1c05",
|
||||
"name": "Split Out"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "f442a5ba-055a-448d-8369-45bf2bf80f66",
|
||||
"name": "tasklist_id",
|
||||
"value": "={{ $json.id }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "240d364a-e9fe-4e4f-94d8-84bbca5af83f",
|
||||
"name": "tasklist_title",
|
||||
"value": "={{ $json.title }}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "2ced6d2c-0606-4db1-b85c-a8fb23634f4b",
|
||||
"name": "updated",
|
||||
"value": "={{ $json.updated }}",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
144,
|
||||
-32
|
||||
],
|
||||
"id": "4abab24d-e1ed-4869-9ba5-86af722ec6cf",
|
||||
"name": "Edit Fields"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When Executed by Another Workflow": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"HTTP Request": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Out",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Out": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Edit Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "ad7d4359-aa25-4ecf-9f7e-467fdf906dbc",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "OJ2UfPNUOAOHlllh",
|
||||
"isArchived": false
|
||||
}
|
||||
326
workflows/gHvDsRXdGMxMZ4o6.json
Normal file
326
workflows/gHvDsRXdGMxMZ4o6.json
Normal file
@@ -0,0 +1,326 @@
|
||||
{
|
||||
"id": "gHvDsRXdGMxMZ4o6",
|
||||
"name": "EVE Bazaar Analyzer",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "minutes"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "Schedule Trigger",
|
||||
"name": "Schedule Trigger",
|
||||
"position": [
|
||||
0,
|
||||
80
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM character_bazaar_posts WHERE post_status IN ('new', 'updated') LIMIT 10",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Fetch Pending",
|
||||
"name": "Fetch Pending",
|
||||
"position": [
|
||||
224,
|
||||
80
|
||||
],
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "dsnKfvOBMkgU21Lt",
|
||||
"name": "supabase postgres account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://esi.evetech.net/universe/ids",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Accept",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "Accept-Language",
|
||||
"value": "en"
|
||||
},
|
||||
{
|
||||
"name": "X-Compatibility-Date",
|
||||
"value": "2025-11-06"
|
||||
},
|
||||
{
|
||||
"name": "User-Agent",
|
||||
"value": "n8n-workflow-testing"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"contentType": "raw",
|
||||
"body": "=[ \"{{ $json.author }}\" ] ",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Lookup Character ID",
|
||||
"name": "Lookup Character ID",
|
||||
"position": [
|
||||
672,
|
||||
144
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"continueOnFail": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "=https://evewho.com/api/character/{{ $json.character_id }}",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "User-Agent",
|
||||
"value": "n8n-workflow-testing"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "Verify Character",
|
||||
"name": "Verify Character",
|
||||
"position": [
|
||||
1344,
|
||||
80
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"continueOnFail": true
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "Merge",
|
||||
"name": "Merge",
|
||||
"position": [
|
||||
1104,
|
||||
80
|
||||
],
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const json = item.json;\n const compliance_issues = [];\n let post_status = 'compliant';\n\n // Check if verification failed (Cloudflare or 404)\n if (json.error || !json.character_id) {\n compliance_issues.push(\"Character verification failed (Cloudflare)\");\n }\n\n // Generate SQL update query\n const issuesJson = JSON.stringify(compliance_issues);\n const now = new Date().toISOString();\n \n // Escape single quotes in JSON string for SQL\n const safeIssues = issuesJson.replace(/'/g, \"''\");\n \n let charIdUpdate = \"\";\n if (json.character_id) {\n charIdUpdate = `, character_id = ${json.character_id}`;\n }\n \n json.query = `UPDATE character_bazaar_posts SET compliance_issues = '${safeIssues}'::jsonb, last_reviewed_at = '${now}', post_status = '${post_status}'${charIdUpdate} WHERE id = '${json.id}'`;\n \n results.push({json});\n}\n\nreturn results;"
|
||||
},
|
||||
"id": "Simulate AI",
|
||||
"name": "Simulate AI",
|
||||
"position": [
|
||||
1568,
|
||||
80
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "No Op",
|
||||
"name": "No Op",
|
||||
"position": [
|
||||
1792,
|
||||
80
|
||||
],
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "={{ $json.query }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "Update Post",
|
||||
"name": "Update Post",
|
||||
"position": [
|
||||
2016,
|
||||
80
|
||||
],
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "dsnKfvOBMkgU21Lt",
|
||||
"name": "supabase postgres account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"string": [
|
||||
{
|
||||
"value1": "={{ $json.character_id }}",
|
||||
"operation": "isNotEmpty"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "Check ID",
|
||||
"name": "Check ID",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
448,
|
||||
80
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const results = [];\n// Access items from the \"False\" output (index 1) of the \"Check ID\" node\nconst originalItems = $('Check ID').all(1);\n\nfor (let i = 0; i < items.length; i++) {\n const lookupData = items[i].json;\n const original = originalItems[i] ? originalItems[i].json : {};\n \n let characterId = null;\n let characterName = null;\n\n // Extract ID and Name from the ESI response\n if (lookupData.characters && lookupData.characters.length > 0) {\n characterId = lookupData.characters[0].id;\n characterName = lookupData.characters[0].name;\n }\n \n results.push({\n json: {\n ...original,\n character_id: characterId,\n character_name: characterName\n }\n });\n}\n\nreturn results;"
|
||||
},
|
||||
"id": "Apply Lookup",
|
||||
"name": "Apply Lookup",
|
||||
"position": [
|
||||
896,
|
||||
144
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Lookup Character ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Apply Lookup",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Verify Character",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"No Op": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Update Post",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Fetch Pending",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Simulate AI": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "No Op",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Fetch Pending": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Check ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Lookup Character ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Verify Character": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Simulate AI",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Apply Lookup": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveManualExecutions": true,
|
||||
"saveExecutionProgress": true,
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "ae0ae4fe-110e-4c9a-905e-328d85b371b0",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "HWgaFb7kLF649L7l",
|
||||
"isArchived": false
|
||||
}
|
||||
470
workflows/kRZyX9H2uDHHncpE.json
Normal file
470
workflows/kRZyX9H2uDHHncpE.json
Normal file
@@ -0,0 +1,470 @@
|
||||
{
|
||||
"id": "kRZyX9H2uDHHncpE",
|
||||
"name": "MAM Transmission Manager",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "start",
|
||||
"name": "Start",
|
||||
"position": [
|
||||
224,
|
||||
128
|
||||
],
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"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": "={{ $json.session_id }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ $json.transmission_body }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"fullResponse": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "http-add-torrent",
|
||||
"name": "Add Torrent to Transmission",
|
||||
"position": [
|
||||
2192,
|
||||
128
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"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": [
|
||||
2416,
|
||||
128
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"amount": 30
|
||||
},
|
||||
"id": "wait-60s",
|
||||
"name": "Wait 60 Seconds",
|
||||
"position": [
|
||||
2688,
|
||||
288
|
||||
],
|
||||
"type": "n8n-nodes-base.wait",
|
||||
"typeVersion": 1.1,
|
||||
"webhookId": "ae0f8400-3629-4965-989e-11e14a60a260"
|
||||
},
|
||||
{
|
||||
"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": "={{ $json.session_id || '' }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ { method: 'torrent-get', arguments: { ids: [$json.torrent_id], fields: ['id','name','status','percentDone','files','downloadDir', 'hashString'] } } }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "http-check-status",
|
||||
"name": "Check Torrent Status",
|
||||
"position": [
|
||||
2688,
|
||||
64
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"id": "iymUPilnVhfL3h5D",
|
||||
"name": "transmission"
|
||||
}
|
||||
},
|
||||
"onError": "continueRegularOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Check if torrent download is complete\n// Transmission status codes: 0=stopped, 4=downloading, 6=seeding\nconst item = $input.first();\nconst response = item.json;\n\nif (item.error || !response.arguments || !response.arguments.torrents) {\n return [{\n json: {\n complete: false,\n status: 'error',\n error: item.error?.message || 'Failed to get torrent status',\n retry: true\n }\n }];\n}\n\nconst torrent = response.arguments.torrents[0];\n\nif (!torrent) {\n return [{\n json: {\n complete: false,\n status: 'error',\n error: 'Torrent not found in Transmission',\n retry: false\n }\n }];\n}\n\nconst isComplete = torrent.status === 6 || (torrent.status === 0 && torrent.percentDone === 1);\n\n// Get original input to preserve history_id\nconst waitNode = $('Wait 60 Seconds').last();\nconst originalInput = waitNode ? (waitNode.json.original_input || {}) : {};\nconst historyId = originalInput.history_id;\n\nif (isComplete) {\n const downloadDir = torrent.downloadDir || '/home/seed_/.transmission/downloads/audio';\n \n // Find largest file (likely the main audiobook) to handle multi-file/directory torrents\n let largestFile = null;\n let maxBytes = -1;\n \n if (torrent.files && torrent.files.length > 0) {\n for (const file of torrent.files) {\n if (file.length > maxBytes) {\n maxBytes = file.length;\n largestFile = file;\n }\n }\n }\n \n // Use largest file path if found, otherwise fallback to torrent name (single file)\n let relativePath = torrent.name;\n if (largestFile) {\n relativePath = largestFile.name;\n }\n \n const filePath = `${downloadDir}/${relativePath}`;\n \n return [{\n json: {\n complete: true,\n status: 'complete',\n file_path: filePath,\n filename: relativePath.split('/').pop(), // Just the filename part\n torrent_id: torrent.id,\n torrent_name: torrent.name,\n torrent_hash: torrent.hashString,\n percent_done: torrent.percentDone,\n transmission_status: torrent.status,\n history_id: historyId,\n debug_original_keys: Object.keys(originalInput),\n debug_wait_node_exists: !!waitNode\n }\n }];\n} else {\n return [{\n json: {\n complete: false,\n status: 'downloading',\n percent_done: torrent.percentDone,\n transmission_status: torrent.status,\n retry: true\n }\n }];\n}"
|
||||
},
|
||||
"id": "code-check-completion",
|
||||
"name": "Check If Complete",
|
||||
"position": [
|
||||
2912,
|
||||
64
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"conditions": [
|
||||
{
|
||||
"id": "condition-1",
|
||||
"leftValue": "={{ $json.complete }}",
|
||||
"operator": {
|
||||
"operation": "equals",
|
||||
"type": "boolean"
|
||||
},
|
||||
"rightValue": true
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
}
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "if-complete",
|
||||
"name": "Is Download Complete?",
|
||||
"position": [
|
||||
3136,
|
||||
64
|
||||
],
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"respondWith": "allIncomingItems",
|
||||
"options": {}
|
||||
},
|
||||
"id": "respond-complete",
|
||||
"name": "Respond Complete",
|
||||
"position": [
|
||||
3360,
|
||||
16
|
||||
],
|
||||
"type": "n8n-nodes-base.respondToWebhook",
|
||||
"typeVersion": 1
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "keep-context",
|
||||
"name": "torrent_id",
|
||||
"type": "string",
|
||||
"value": "={{ $('Extract Torrent ID').item.json.torrent_id }}"
|
||||
},
|
||||
{
|
||||
"id": "keep-hash",
|
||||
"name": "torrent_hash",
|
||||
"type": "string",
|
||||
"value": "={{ $('Extract Torrent ID').item.json.torrent_hash }}"
|
||||
},
|
||||
{
|
||||
"id": "keep-original",
|
||||
"name": "original_input",
|
||||
"type": "object",
|
||||
"value": "={{ $('Extract Torrent ID').item.json.original_input }}"
|
||||
},
|
||||
{
|
||||
"id": "keep-session",
|
||||
"name": "session_id",
|
||||
"type": "string",
|
||||
"value": "={{ $json.session_id || '' }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "loop-back",
|
||||
"name": "Loop Back to Wait",
|
||||
"position": [
|
||||
3360,
|
||||
240
|
||||
],
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://seed-1.dfw.ben.io:9091/transmission/rpc",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpBasicAuth",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ { method: 'session-get' } }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"fullResponse": true,
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "http-request-session",
|
||||
"name": "Request Transmission Session",
|
||||
"position": [
|
||||
896,
|
||||
192
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"id": "iymUPilnVhfL3h5D",
|
||||
"name": "transmission"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const item = $input.first();\nconst data = item.json || {};\nconst headers = data.headers || {};\nconst sessionId = headers['x-transmission-session-id'] || headers['X-Transmission-Session-Id'];\n\nif (!sessionId) {\n throw new Error('Transmission session header missing');\n}\n\nreturn [{ json: { session_id: sessionId } }];"
|
||||
},
|
||||
"id": "code-session-header",
|
||||
"name": "Extract Session Header",
|
||||
"position": [
|
||||
1168,
|
||||
192
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Loop over all items (books)\nreturn items.map(item => {\n // Get the Base64 file string safely\n const binaryData = item.binary.data.data;\n \n // Create the exact JSON body Transmission expects\n const payload = {\n method: \"torrent-add\",\n arguments: {\n metainfo: binaryData,\n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\", // Using your seedbox path\n paused: true\n }\n };\n\n // Return it as a normal JSON field called 'transmission_body'\n return {\n json: {\n ...item.json,\n transmission_body: payload\n }\n };\n});"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1936,
|
||||
128
|
||||
],
|
||||
"id": "0b011613-50e9-4612-ac6e-1f6ac0e39ebb",
|
||||
"name": "Code in JavaScript"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// 1. Get the Session ID from the \"Extract\" node\n// (We use .first() because there is only one session ID)\nconst sessionNode = $('Extract Session Header').first();\n// Robustly find the ID whether it's in headers or root\nconst sessionId = sessionNode.json.headers ? sessionNode.json.headers['x-transmission-session-id'] : sessionNode.json.session_id;\n\n// 2. Get all Files from the \"Start\" node\nconst files = $('Start').all();\n\n// 3. Map the ID onto every file while PRESERVING the binary data\nreturn files.map(file => {\n return {\n json: {\n ...file.json,\n session_id: sessionId\n },\n binary: file.binary // <--- This is the critical part we were losing!\n };\n});"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1712,
|
||||
128
|
||||
],
|
||||
"id": "d6d9f145-6b10-452f-aa08-2dc669c3b4d6",
|
||||
"name": "Merge Session to Files"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "combine",
|
||||
"combineBy": "combineByPosition",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1488,
|
||||
128
|
||||
],
|
||||
"id": "dde23141-d708-48e7-942f-08aa1d749189",
|
||||
"name": "Merge Session Context"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Add Torrent to Transmission": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Extract Torrent ID",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check If Complete": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Is Download Complete?",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Check Torrent Status": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Check If Complete",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Session Header": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Session Context",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Torrent ID": {
|
||||
"main": [
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Is Download Complete?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Respond Complete",
|
||||
"type": "main"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Loop Back to Wait",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Loop Back to Wait": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Wait 60 Seconds",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Request Transmission Session": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Extract Session Header",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Request Transmission Session",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge Session Context",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Wait 60 Seconds": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "Check Torrent Status",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code in JavaScript": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Add Torrent to Transmission",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Session to Files": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Code in JavaScript",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Session Context": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Session to Files",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {},
|
||||
"triggerCount": 0,
|
||||
"versionId": "ef553d09-89f5-4f19-9003-0f2040bf82ba",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": false
|
||||
}
|
||||
127
workflows/kubWVFN16Jar4v4Q.json
Normal file
127
workflows/kubWVFN16Jar4v4Q.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"id": "kubWVFN16Jar4v4Q",
|
||||
"name": "Audiobook Notification Router",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "workflow-trigger",
|
||||
"name": "Execute Workflow Trigger",
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
100,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "send",
|
||||
"sendTo": "admin@ben.io",
|
||||
"subject": "={{ $json.emailSubject }}",
|
||||
"message": "={{ $json.emailHtmlBody }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "send-gmail",
|
||||
"name": "Send Gmail",
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.1,
|
||||
"position": [
|
||||
700,
|
||||
200
|
||||
],
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "Os1ux3h3zFlC2XkG",
|
||||
"name": "Gmail account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "status",
|
||||
"name": "status",
|
||||
"value": "sent",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "return-status",
|
||||
"name": "Return Status",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
900,
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "685a8518-fcbf-4a0c-84d0-3ab4dacb61a4",
|
||||
"name": "Build Email HTML (Item)",
|
||||
"type": "n8n-nodes-base.functionItem",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
500,
|
||||
200
|
||||
],
|
||||
"parameters": {
|
||||
"functionCode": "const torrentCount = Number($json.torrent_count) || 0;\nconst torrents = Array.isArray($json.torrents) ? $json.torrents : [];\n\nfunction getMAMStatus(t) {\n return t.mam_torrent_id\n ? `✅ <a href=\"https://www.myanonamouse.net/t/${t.mam_torrent_id}\">Yes</a>`\n : '❌ No';\n}\n\nconst rows = torrents.length\n ? torrents.map(t => `\n <tr>\n <td>${t.title || ''}</td>\n <td>${t.seeders ?? ''}</td>\n <td>${t.size || ''}</td>\n <td>${getMAMStatus(t)}</td>\n <td>${t.link ? `<a href=\"${t.link}\">Download</a>` : ''}</td>\n </tr>\n `).join('')\n : '<tr><td colspan=\"5\">No torrents this run</td></tr>';\n\nconst emailHtmlBody = `\n <h2>${torrentCount} New Audiobook Torrent(s) Found</h2>\n <table border=\"1\" cellpadding=\"8\" cellspacing=\"0\" style=\"border-collapse: collapse;\">\n <thead>\n <tr>\n <th>Title</th>\n <th>Seeders</th>\n <th>Size</th>\n <th>On MAM?</th>\n <th>Links</th>\n </tr>\n </thead>\n <tbody>\n ${rows}\n </tbody>\n </table>\n`;\n\nreturn {\n emailSubject: `${torrentCount} New Audiobook Torrent(s) Found`,\n emailHtmlBody,\n torrentCount,\n};"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Send Gmail": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Return Status",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Workflow Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Build Email HTML (Item)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Email HTML (Item)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Gmail",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"saveExecutionProgress": true,
|
||||
"saveManualExecutions": true,
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "8496ee58-493a-4604-bba1-e06c5b27879a",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "kUg4HIPXraph3M0E",
|
||||
"isArchived": false
|
||||
}
|
||||
500
workflows/lozEf1Wq3012xbAk.json
Normal file
500
workflows/lozEf1Wq3012xbAk.json
Normal file
@@ -0,0 +1,500 @@
|
||||
{
|
||||
"id": "lozEf1Wq3012xbAk",
|
||||
"name": "NPM to Homepage (SSH)",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Extract the token from Supabase response\nconst tokenRecords = items;\n\nif (!tokenRecords || tokenRecords.length === 0 || !tokenRecords[0].json.token) {\n throw new Error('❌ NPM token not found in Supabase. Please run the NPM Auth Token Manager workflow first.');\n}\n\nconst tokenRecord = tokenRecords[0].json;\n\nconsole.log('✅ Loaded NPM token from Supabase');\nconsole.log('Token expires at:', tokenRecord.expires_at);\nconsole.log('Last updated:', tokenRecord.last_updated);\n\nreturn [{\n json: {\n token: tokenRecord.token,\n expiresAt: tokenRecord.expires_at\n }\n}];"
|
||||
},
|
||||
"id": "401c1f93-a150-4dc3-aa2d-da007edacb8f",
|
||||
"name": "Extract Token Value",
|
||||
"position": [
|
||||
496,
|
||||
32
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"notes": "Extracts the token value and validates it exists."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://npm.dfw.ben.io/api/nginx/proxy-hosts",
|
||||
"options": {},
|
||||
"headerParametersUi": {
|
||||
"parameter": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "=Bearer {{ $json.token }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "85e32a06-5c0e-409a-95d9-5eec7d4c71f3",
|
||||
"name": "Get NPM Hosts",
|
||||
"position": [
|
||||
688,
|
||||
32
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 1,
|
||||
"notes": "Fetches the list of all configured proxy hosts from NPM using the stored long-lived token."
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "796fba9f-a65d-49d4-bdda-4f09ac7ad926",
|
||||
"name": "Merge Data Sources",
|
||||
"position": [
|
||||
880,
|
||||
48
|
||||
],
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3,
|
||||
"notes": "Merges NPM hosts and Supabase mappings into a single execution path to prevent duplicate processing."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Process merged data: NPM hosts array and Supabase mappings\n// Input 1: NPM hosts (array)\n// Input 2: Supabase mappings (records)\n\nlet hosts = [];\nlet mappings = {};\n\n// Process all incoming items to extract hosts and mappings\nfor (const item of items) {\n const data = item.json;\n \n // Check if this is NPM hosts data (array at root)\n if (Array.isArray(data)) {\n hosts = data;\n console.log('Received NPM hosts:', hosts.length);\n }\n // Check if this is Supabase data (has service_name and icon_reference)\n else if (data.service_name && data.icon_reference) {\n mappings[data.service_name.toLowerCase()] = data.icon_reference;\n }\n}\n\nconsole.log('=== Supabase Mappings Loaded ===');\nconsole.log('Total mappings from Supabase:', Object.keys(mappings).length);\nfor (const [key, value] of Object.entries(mappings).slice(0, 5)) {\n console.log(` ${key} -> ${value}`);\n}\nif (Object.keys(mappings).length > 5) {\n console.log(` ... and ${Object.keys(mappings).length - 5} more`);\n}\n\nconsole.log('NPM hosts retrieved:', hosts.length);\n\nreturn [{ json: { databaseMappings: mappings, unmappedDefault: 'streamlink', npmHosts: hosts } }];"
|
||||
},
|
||||
"id": "e04e3082-2173-4615-b9f2-cd6aa0ddefcd",
|
||||
"name": "Cache Mappings in Memory",
|
||||
"position": [
|
||||
1088,
|
||||
48
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"notes": "Processes merged data from both sources (NPM hosts and Supabase mappings). Combines them into a single output for downstream processing."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Process NPM hosts with Supabase mappings\n// This node receives a single input from Cache Mappings in Memory\n// which contains both databaseMappings and npmHosts\n\nconst inputData = items[0].json;\nconst hosts = inputData.npmHosts || [];\nconst databaseMappings = inputData.databaseMappings || {};\n\nconsole.log('=== Process ALL Hosts (Supabase Mappings) ===');\nconsole.log('NPM hosts received:', hosts.length);\nconsole.log('Database mappings loaded:', Object.keys(databaseMappings).length);\n\nif (!Array.isArray(hosts) || hosts.length === 0) {\n console.log('No hosts to process');\n return [{ json: { hosts: [], staticMappings: databaseMappings } }];\n}\n\n// Filter enabled hosts and extract service names\nconst enabledHosts = hosts.filter(host => host && host.enabled && host.domain_names && host.domain_names.length > 0);\nconst serviceNames = enabledHosts.map(host => host.domain_names[0].split('.')[0]);\n\nconsole.log('Enabled hosts:', enabledHosts.length);\nconsole.log('Found services:', serviceNames.length);\nconsole.log('Services:', serviceNames.slice(0, 10).join(', ') + (serviceNames.length > 10 ? '...' : ''));\n\nreturn [{ json: { hosts: enabledHosts, staticMappings: databaseMappings } }];"
|
||||
},
|
||||
"id": "71e9143a-8ea4-4f98-9149-be75edf02731",
|
||||
"name": "Process ALL Hosts",
|
||||
"position": [
|
||||
880,
|
||||
256
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"notes": "Receives NPM hosts array directly. Uses mappings from Supabase database. Pure dynamic lookup - no hardcoded values."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const item_json = items.length > 0 && items[0].json ? items[0].json : {};\nconst hosts = item_json.hosts || [];\nconst staticMappings = item_json.staticMappings || {};\nconst unmappedDefault = $node['Cache Mappings in Memory'].json.unmappedDefault || 'streamlink';\n\nconsole.log('=== Create Services from Supabase Mappings ===');\nconsole.log('Unmapped default:', unmappedDefault);\n\nconst allNewServices = [];\nif (hosts && hosts.length > 0) {\n for (const host of hosts) {\n if (!host?.enabled || !host?.domain_names?.length) continue;\n const domain = host.domain_names[0];\n const serviceName = domain.split('.')[0];\n const capitalizedServiceName = serviceName.charAt(0).toUpperCase() + serviceName.slice(1);\n const protocol = host.ssl_forced ? 'https' : 'http';\n const url = `${protocol}://${domain}`;\n \n // Check Supabase mappings (ground truth)\n let iconReference = staticMappings[serviceName.toLowerCase()];\n \n // Fallback to unmapped default if not found in Supabase\n if (!iconReference) {\n iconReference = unmappedDefault;\n }\n \n const iconName = `sh-${iconReference}`;\n const serviceObject = {};\n serviceObject[capitalizedServiceName] = { icon: iconName, href: url, ping: url, statusStyle: 'dot' };\n allNewServices.push(serviceObject);\n \n console.log(`${serviceName} -> ${iconReference}`);\n }\n}\n\nconsole.log('Total services created:', allNewServices.length);\nreturn [{ json: { newServices: allNewServices } }];"
|
||||
},
|
||||
"id": "7c1703e7-6bec-48e5-a433-d6fce1dd4009",
|
||||
"name": "Create Homepage Services",
|
||||
"position": [
|
||||
1120,
|
||||
256
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"notes": "Creates Homepage service objects using mappings from Supabase database. Uses 'streamlink' as unmapped default for any services not in database."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"values": {
|
||||
"string": [
|
||||
{
|
||||
"name": "homepageServicesPath",
|
||||
"value": "/opt/homepage/config/services.yaml"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "961f50ee-94d0-4ffd-ad8a-6ff68ff19710",
|
||||
"name": "SSH Config",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
96,
|
||||
464
|
||||
],
|
||||
"notes": "Stores SSH path configuration for Homepage services.yaml"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Reset pairedItem tracking by returning items without paired metadata\n// This allows the SSH command to access $() references without pairing conflicts\nreturn items.map(item => ({ json: item.json || item }));"
|
||||
},
|
||||
"id": "f5c635b4-f37e-40b9-a66f-e08f89dc07a4",
|
||||
"name": "Reset Pairing for SSH",
|
||||
"position": [
|
||||
288,
|
||||
464
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"notes": "Resets pairedItem tracking to prevent errors when accessing variables from earlier in the workflow. Allows SSH node to reference config without pairing conflicts."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=cat {{ $node[\"SSH Config\"].json.homepageServicesPath }}"
|
||||
},
|
||||
"id": "78840546-a019-4ab7-be64-34bfb641847c",
|
||||
"name": "Read services.yaml via SSH",
|
||||
"position": [
|
||||
496,
|
||||
464
|
||||
],
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "jBV4AQlsHxWnFXsp",
|
||||
"name": "homepage.sshkey"
|
||||
}
|
||||
},
|
||||
"notes": "Reads the current services.yaml file from your remote Homepage server via SSH."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Enhanced YAML parser for Homepage format - PRESERVES INDENTATION\nconsole.log('=== DEBUG YAML to JSON ===');\n\nconst existingYamlString = $('Read services.yaml via SSH').item.json.stdout;\nconsole.log('Existing YAML length:', existingYamlString?.length || 0);\n\nfunction parseHomepageYaml(yamlString) {\n try {\n const lines = yamlString.split('\\n');\n const result = [];\n let currentGroup = null;\n let currentServices = [];\n let currentService = null;\n \n for (let i = 0; i < lines.length; i++) {\n const fullLine = lines[i];\n const line = fullLine.trim();\n \n if (!line || line.startsWith('#')) continue;\n \n const indent = fullLine.length - fullLine.trimStart().length;\n \n if (indent === 0 && line.match(/^-\\s*([^:]+):$/)) {\n if (currentGroup !== null && currentServices.length > 0) {\n result.push({ [currentGroup]: currentServices });\n }\n currentGroup = line.match(/^-\\s*([^:]+):$/)[1].trim();\n currentServices = [];\n currentService = null;\n console.log('Found group:', currentGroup);\n continue;\n }\n \n if (indent === 4 && line.match(/^-\\s*([^:]+):$/) && currentGroup !== null) {\n const serviceName = line.match(/^-\\s*([^:]+):$/)[1].trim();\n currentService = { [serviceName]: {} };\n currentServices.push(currentService);\n console.log('Found service:', serviceName);\n continue;\n }\n \n if (indent >= 8 && line.includes(':') && currentService !== null) {\n const match = line.match(/^([^:]+):\\s*(.*)$/);\n if (match) {\n const key = match[1].trim();\n let value = match[2].trim();\n \n if (value === 'null' || value === '~') {\n value = null;\n } else if (value === 'true' || value === 'false') {\n value = value === 'true';\n } else if (!isNaN(value) && value !== '') {\n value = Number(value);\n } else if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n \n const serviceName = Object.keys(currentService)[0];\n currentService[serviceName][key] = value;\n }\n }\n }\n \n if (currentGroup !== null && currentServices.length > 0) {\n result.push({ [currentGroup]: currentServices });\n }\n \n console.log('Final result groups:', result.length);\n result.forEach(g => {\n const groupName = Object.keys(g)[0];\n console.log(` ${groupName}: ${g[groupName].length} services`);\n });\n \n return result;\n } catch (error) {\n console.error('Error parsing YAML:', error);\n return [];\n }\n}\n\nlet doc;\ntry {\n doc = parseHomepageYaml(existingYamlString);\n console.log('Parsed YAML groups:', doc.length);\n} catch (e) {\n console.error('Error parsing existing YAML', e);\n doc = [];\n}\n\nreturn [{ json: { services: doc } }];"
|
||||
},
|
||||
"id": "d145ccd1-4804-4143-b849-3a0b46bb5c22",
|
||||
"name": "YAML to JSON",
|
||||
"position": [
|
||||
672,
|
||||
464
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"notes": "Parses the existing YAML using built-in parser and converts it to a JSON object."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// DE-DUPLICATE Links section - keep only unique services\nconsole.log('=== UPDATE YAML file (De-duplication Fix v2) ===');\n\nconst newServices = $('Create Homepage Services').item.json.newServices;\nconst existingYamlString = $('Read services.yaml via SSH').item.json.stdout;\n\nconst lines = existingYamlString.split('\\n');\nlet output = [];\nlet linksStartIndex = -1;\n\n// Find where Links section starts\nfor (let i = 0; i < lines.length; i++) {\n if (lines[i].trim() === '- Links:') {\n linksStartIndex = i;\n console.log('Found Links section at line:', i);\n break;\n }\n}\n\nif (linksStartIndex === -1) {\n // No Links section - add everything plus new Links section\n output = lines.filter(l => l.trim());\n output.push('');\n output.push('- Links:');\n for (const service of newServices) {\n const serviceName = Object.keys(service)[0];\n const config = service[serviceName];\n output.push(` - ${serviceName}:`);\n for (const [key, value] of Object.entries(config)) {\n const formattedValue = typeof value === 'string' ? `\"${value}\"` : value;\n output.push(` ${key}: ${formattedValue}`);\n }\n }\n} else {\n // Links section exists - replace it entirely with new services\n // Step 1: Add everything BEFORE Links section\n for (let i = 0; i < linksStartIndex; i++) {\n output.push(lines[i]);\n }\n \n // Step 2: Add Links header\n output.push('- Links:');\n \n // Step 3: Add ONLY new NPM services (no duplicates)\n console.log('Adding', newServices.length, 'unique NPM services');\n for (const service of newServices) {\n const serviceName = Object.keys(service)[0];\n const config = service[serviceName];\n output.push(` - ${serviceName}:`);\n for (const [key, value] of Object.entries(config)) {\n const formattedValue = typeof value === 'string' ? `\"${value}\"` : value;\n output.push(` ${key}: ${formattedValue}`);\n }\n }\n \n console.log('Removed all old Links content, added fresh NPM services');\n}\n\n// Remove trailing empty lines and ensure single trailing newline\nwhile (output.length > 0 && !output[output.length - 1].trim()) {\n output.pop();\n}\noutput.push('');\n\nconst newYamlString = output.join('\\n');\nconsole.log('Final YAML length:', newYamlString.length, 'lines:', output.length);\nconsole.log('NPM services added:', newServices.length);\n\nreturn [{ json: { yaml: newYamlString } }];"
|
||||
},
|
||||
"id": "307414bb-d7c2-4513-a7ca-8c09e9fd1094",
|
||||
"name": "Update YAML file",
|
||||
"position": [
|
||||
848,
|
||||
464
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"notes": "REPLACES the entire Links section with fresh NPM hosts (de-duplication fix). Previously preserved old services causing endless growth. Now clears and rebuilds Links section each run."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Encode YAML as Base64 to safely transfer via SSH\nconst yamlContent = $('Update YAML file').item.json.yaml;\nconsole.log('Original YAML length:', yamlContent.length);\n\n// Convert string to Base64\nconst base64Yaml = Buffer.from(yamlContent).toString('base64');\nconsole.log('Base64 encoded length:', base64Yaml.length);\nconsole.log('Base64 preview:', base64Yaml.substring(0, 50) + '...');\n\nreturn [{ json: { base64Yaml: base64Yaml, originalLength: yamlContent.length } }];"
|
||||
},
|
||||
"id": "eeba120e-fb90-4a9f-b395-1c06cfb46e11",
|
||||
"name": "Prepare Base64 YAML",
|
||||
"position": [
|
||||
1040,
|
||||
464
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"notes": "Encodes the YAML content as Base64 to safely pass it through SSH without shell interpretation issues."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=cp {{ $node[\"SSH Config\"].json.homepageServicesPath }} {{ $node[\"SSH Config\"].json.homepageServicesPath }}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
},
|
||||
"id": "769f1bc8-5922-4d5f-993c-e1741e7b3dbf",
|
||||
"name": "Backup Current Config via SSH",
|
||||
"position": [
|
||||
1232,
|
||||
464
|
||||
],
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "jBV4AQlsHxWnFXsp",
|
||||
"name": "homepage.sshkey"
|
||||
}
|
||||
},
|
||||
"notes": "Creates a timestamped backup of the current services.yaml before writing the new one."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "=echo {{ $node[\"Prepare Base64 YAML\"].json.base64Yaml }} | base64 -d > {{$node[\"SSH Config\"].json.homepageServicesPath}}\necho 'Write successful' && wc -l {{$node[\"SSH Config\"].json.homepageServicesPath}}"
|
||||
},
|
||||
"id": "84611366-6e89-4d1c-a36a-9ceaf7cb9068",
|
||||
"name": "Write services.yaml via SSH",
|
||||
"position": [
|
||||
1440,
|
||||
464
|
||||
],
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "jBV4AQlsHxWnFXsp",
|
||||
"name": "homepage.sshkey"
|
||||
}
|
||||
},
|
||||
"notes": "Decodes Base64 YAML and writes to remote file, safely avoiding shell escaping issues."
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
96,
|
||||
32
|
||||
],
|
||||
"id": "afe8836d-d01d-435a-a4a1-e208251da69b",
|
||||
"name": "Schedule Trigger"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM npm_tokens WHERE service_name = 'npm_dfw';",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "96cf0d45-237b-4d33-a50d-fb54fa975761",
|
||||
"name": "Load NPM Token from Postgres",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
304,
|
||||
32
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "Ik8CFyap8ic2Md3M",
|
||||
"name": "n8n-infra"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT * FROM service_mappings;",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"id": "953a7ece-973f-41ef-a9ba-c9d3dffd8f01",
|
||||
"name": "Load Icon Mappings from Postgres",
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
496,
|
||||
224
|
||||
],
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "Ik8CFyap8ic2Md3M",
|
||||
"name": "n8n-infra"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Extract Token Value": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get NPM Hosts",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get NPM Hosts": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Data Sources",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Load Icon Mappings from Postgres",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Data Sources": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Cache Mappings in Memory",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Cache Mappings in Memory": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Process ALL Hosts",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Process ALL Hosts": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Homepage Services",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create Homepage Services": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Reset Pairing for SSH",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "SSH Config",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"SSH Config": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Reset Pairing for SSH",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Reset Pairing for SSH": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Read services.yaml via SSH",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Read services.yaml via SSH": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "YAML to JSON",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"YAML to JSON": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update YAML file",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Update YAML file": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Base64 YAML",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Base64 YAML": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Backup Current Config via SSH",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Backup Current Config via SSH": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Write services.yaml via SSH",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Load NPM Token from Postgres",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Load NPM Token from Postgres": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Token Value",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Load Icon Mappings from Postgres": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Data Sources",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "0720cdd2-cd1e-4b49-8d1e-25d1dfaefa66",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "eWW72giJDI4fxlWw",
|
||||
"isArchived": false
|
||||
}
|
||||
37
workflows/nPu0CMgm47WQAZ7G.json
Normal file
37
workflows/nPu0CMgm47WQAZ7G.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"id": "nPu0CMgm47WQAZ7G",
|
||||
"name": "Zabbix Experiments",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"id": "ee5c84b1-9bbc-4743-abae-1976ede4703f",
|
||||
"name": "When clicking ‘Execute workflow’"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "5670d8bb-3036-46e2-950b-5a8eb1bab920",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "LTWZD96boqxk9sIs",
|
||||
"isArchived": false
|
||||
}
|
||||
104
workflows/psIjaVVBVpOPcVju.json
Normal file
104
workflows/psIjaVVBVpOPcVju.json
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"id": "psIjaVVBVpOPcVju",
|
||||
"name": "Test Sheets",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"id": "378b6472-b211-405b-bc78-9d4c904ce6a5",
|
||||
"name": "When clicking ‘Execute workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "spreadsheet",
|
||||
"title": "N8N Test Sheet",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.googleSheets",
|
||||
"typeVersion": 4.7,
|
||||
"position": [
|
||||
208,
|
||||
0
|
||||
],
|
||||
"id": "5331da6f-c549-43b3-9ef7-558acbef9ecc",
|
||||
"name": "Create spreadsheet",
|
||||
"credentials": {
|
||||
"googleSheetsOAuth2Api": {
|
||||
"id": "qQNqeSAtYrG6Vi5N",
|
||||
"name": "Google Sheets account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"documentId": {
|
||||
"__rl": true,
|
||||
"value": "={{ $json.spreadsheetId }}",
|
||||
"mode": "id"
|
||||
},
|
||||
"sheetName": {
|
||||
"__rl": true,
|
||||
"value": "={{ $json.sheets[0].properties.sheetId }}",
|
||||
"mode": "id"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.googleSheets",
|
||||
"typeVersion": 4.7,
|
||||
"position": [
|
||||
416,
|
||||
0
|
||||
],
|
||||
"id": "b168d421-315d-43a1-8c12-e5569b8e3d1e",
|
||||
"name": "Get row(s) in sheet",
|
||||
"credentials": {
|
||||
"googleSheetsOAuth2Api": {
|
||||
"id": "qQNqeSAtYrG6Vi5N",
|
||||
"name": "Google Sheets account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create spreadsheet",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create spreadsheet": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get row(s) in sheet",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "83e65963-9f1d-44d6-9bfc-90a064e46f25",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": true
|
||||
}
|
||||
68
workflows/qGbWjuTaMrxGCpAX.json
Normal file
68
workflows/qGbWjuTaMrxGCpAX.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"id": "qGbWjuTaMrxGCpAX",
|
||||
"name": "Test Execution of NPM to Homepage Workflow",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": "lozEf1Wq3012xbAk",
|
||||
"options": {}
|
||||
},
|
||||
"id": "execute-workflow",
|
||||
"name": "Execute Workflow",
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
208,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "test",
|
||||
"responseMode": "lastNode",
|
||||
"options": {}
|
||||
},
|
||||
"id": "test-trigger",
|
||||
"name": "Test",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-16,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Test": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Workflow",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveManualExecutions": true,
|
||||
"saveExecutionProgress": true,
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "84d58610-7bbb-4f14-bbf8-9f01b54893ac",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": null,
|
||||
"isArchived": true
|
||||
}
|
||||
260
workflows/qR33gCw5iScfnj9s.json
Normal file
260
workflows/qR33gCw5iScfnj9s.json
Normal file
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"id": "qR33gCw5iScfnj9s",
|
||||
"name": "Antigravity Quota Reset Timers",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-5am",
|
||||
"name": "5:00 AM - Refresh All",
|
||||
"position": [
|
||||
0,
|
||||
208
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 12
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-12pm",
|
||||
"name": "12:00 PM - Refresh All",
|
||||
"position": [
|
||||
0,
|
||||
400
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 19
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-7pm",
|
||||
"name": "7:00 PM - Refresh All",
|
||||
"position": [
|
||||
0,
|
||||
608
|
||||
],
|
||||
"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": {
|
||||
"interval": [
|
||||
{
|
||||
"triggerAtHour": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-cleanup",
|
||||
"name": "Sunday 3:00 AM - Cleanup",
|
||||
"position": [
|
||||
0,
|
||||
800
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "privateKey",
|
||||
"command": "find ~/.local/share/opencode/storage/session -type f -name \"*.json\" -mtime +7 -exec sh -c 'grep -q \"quota-reset\" \"$1\" && rm \"$1\"' _ {} \\;",
|
||||
"cwd": "~/"
|
||||
},
|
||||
"id": "ssh-cleanup",
|
||||
"name": "SSH - Session Cleanup",
|
||||
"position": [
|
||||
304,
|
||||
800
|
||||
],
|
||||
"type": "n8n-nodes-base.ssh",
|
||||
"typeVersion": 1,
|
||||
"credentials": {
|
||||
"sshPrivateKey": {
|
||||
"id": "S2dcVMjrpg0I0kdV",
|
||||
"name": "vscode-dev.local.ben.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"5:00 AM - Refresh All": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"12:00 PM - Refresh All": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"7:00 PM - Refresh All": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Sunday 3:00 AM - Cleanup": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"index": 0,
|
||||
"node": "SSH - Session Cleanup",
|
||||
"type": "main"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"timezone": "America/Chicago",
|
||||
"saveManualExecutions": true,
|
||||
"saveDataSuccessExecution": "all",
|
||||
"saveDataErrorExecution": "all",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 4,
|
||||
"versionId": "19367657-60c0-4f22-9cdf-84508906970c",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "of8yoeyjjIAhYdnQ",
|
||||
"isArchived": false
|
||||
}
|
||||
615
workflows/sJhcphTjZcqASzwF.json
Normal file
615
workflows/sJhcphTjZcqASzwF.json
Normal file
@@ -0,0 +1,615 @@
|
||||
{
|
||||
"id": "sJhcphTjZcqASzwF",
|
||||
"name": "Audiobook Torrents - Main Orchestrator",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "manual-trigger",
|
||||
"name": "When clicking 'Execute workflow'",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
256,
|
||||
256
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "minutes"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-trigger",
|
||||
"name": "Every 5 Minutes",
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2,
|
||||
"position": [
|
||||
256,
|
||||
448
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://nyaa.si/?page=rss&q=%5BAudiobook%5D&c=3_1&f=0",
|
||||
"options": {}
|
||||
},
|
||||
"id": "fetch-rss",
|
||||
"name": "Fetch RSS from Nyaa",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
480,
|
||||
352
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {
|
||||
"trim": true
|
||||
}
|
||||
},
|
||||
"id": "parse-xml",
|
||||
"name": "Parse XML",
|
||||
"type": "n8n-nodes-base.xml",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
704,
|
||||
352
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "rss.channel.item",
|
||||
"options": {}
|
||||
},
|
||||
"id": "split-items",
|
||||
"name": "Split Feed Items",
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
928,
|
||||
352
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Process all input items and return them\nreturn $input.all().map(item => ({\n json: {\n info_hash: item.json['nyaa:infoHash'],\n title: item.json.title,\n link: item.json.link,\n view_link: item.json.guid._ || item.json.guid,\n pub_date: item.json.pubDate,\n seeders: parseInt(item.json['nyaa:seeders']) || 0,\n size: item.json['nyaa:size']\n }\n}));"
|
||||
},
|
||||
"id": "extract-torrent-data",
|
||||
"name": "Extract Torrent Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1152,
|
||||
352
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "t2BEdqrbF474ul3B"
|
||||
},
|
||||
"workflowInputs": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {},
|
||||
"matchingColumns": [],
|
||||
"schema": [],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": true
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "call-tracker-crossref",
|
||||
"name": "Execute Tracker Cross-Reference",
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
1376,
|
||||
352
|
||||
],
|
||||
"disabled": false
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mergeByFields": {
|
||||
"values": [
|
||||
{
|
||||
"field1": "info_hash",
|
||||
"field2": "info_hash"
|
||||
}
|
||||
]
|
||||
},
|
||||
"resolve": "mix",
|
||||
"exceptWhenMix": "last_processed,processing_workflow",
|
||||
"options": {
|
||||
"skipFields": "id,title,link,pub_date,seeders,size,discovered_at,created_at,updated_at,view_link,mam_torrent_id,mam_checked_at,download_status,download_path,download_size_bytes,downloaded_at,download_error,torrent_created,torrent_file_path,torrent_created_at,torrent_info_hash_verified,upload_status,uploaded_mam_id,uploaded_at,upload_notes,last_processed,processing_workflow,debug_stage,debug_count,debug_timestamp"
|
||||
}
|
||||
},
|
||||
"id": "compare-datasets",
|
||||
"name": "Compare Datasets",
|
||||
"type": "n8n-nodes-base.compareDatasets",
|
||||
"typeVersion": 2.3,
|
||||
"position": [
|
||||
2048,
|
||||
320
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.seeders }}",
|
||||
"rightValue": 0,
|
||||
"operator": {
|
||||
"type": "number",
|
||||
"operation": "gt"
|
||||
},
|
||||
"id": "condition-1762096826037-masiy0yzf"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "has-seeders",
|
||||
"name": "Has Seeders?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
1600,
|
||||
352
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"tableId": "nyaa_audiobooks",
|
||||
"dataToSend": "autoMapInputData",
|
||||
"inputsToIgnore": "view_link"
|
||||
},
|
||||
"id": "insert-new-torrents",
|
||||
"name": "Insert New Torrents",
|
||||
"type": "n8n-nodes-base.supabase",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2496,
|
||||
208
|
||||
],
|
||||
"credentials": {
|
||||
"supabaseApi": {
|
||||
"id": "lWyf2ikOGHTTwnSU",
|
||||
"name": "Supabase account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "update",
|
||||
"tableId": "nyaa_audiobooks",
|
||||
"filters": {
|
||||
"conditions": [
|
||||
{
|
||||
"keyName": "info_hash",
|
||||
"condition": "eq",
|
||||
"keyValue": "={{ $json.info_hash }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"fieldsUi": {
|
||||
"fieldValues": [
|
||||
{
|
||||
"fieldId": "mam_torrent_id",
|
||||
"fieldValue": "={{ $json.mam_torrent_id }}"
|
||||
},
|
||||
{
|
||||
"fieldId": "mam_checked_at",
|
||||
"fieldValue": "={{ $json.mam_checked_at }}"
|
||||
},
|
||||
{
|
||||
"fieldId": "last_processed",
|
||||
"fieldValue": "={{ $now.toISO() }}"
|
||||
},
|
||||
{
|
||||
"fieldId": "processing_workflow",
|
||||
"fieldValue": "main-orchestrator"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "update-existing-mam",
|
||||
"name": "Update Existing with MAM Data",
|
||||
"type": "n8n-nodes-base.supabase",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
3168,
|
||||
496
|
||||
],
|
||||
"credentials": {
|
||||
"supabaseApi": {
|
||||
"id": "lWyf2ikOGHTTwnSU",
|
||||
"name": "Supabase account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Prepare data for notification router\nconst torrents = $input.all().map(item => item.json);\n\nreturn [{\n json: {\n torrent_count: torrents.length,\n torrents: torrents\n }\n}];"
|
||||
},
|
||||
"id": "prepare-notification-data",
|
||||
"name": "Prepare Notification Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2944,
|
||||
256
|
||||
],
|
||||
"executeOnce": true
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "kubWVFN16Jar4v4Q"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "call-notification-router",
|
||||
"name": "Execute Notification Router",
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
3168,
|
||||
256
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict",
|
||||
"combineOperation": "all"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.needs_mam_update }}",
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "isTrue"
|
||||
},
|
||||
"id": "condition-1762110814452-nceww466w"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "7ea9bc8b-a092-472d-81f3-9aede474a784",
|
||||
"name": "Needs MAM Update (IF)",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
2944,
|
||||
496
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"functionCode": "const data = { ...$json };\nconst hasMam = !!data.mam_torrent_id && String(data.mam_torrent_id).trim() !== '';\nconst lastProcessed = data.last_processed;\nconst notProcessed = !lastProcessed || String(lastProcessed).trim() === '';\nreturn {\n ...data,\n needs_mam_update: hasMam && notProcessed,\n};"
|
||||
},
|
||||
"id": "2accc734-d3ab-421d-8769-79a74753d677",
|
||||
"name": "Flag Needs MAM Update",
|
||||
"type": "n8n-nodes-base.functionItem",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2720,
|
||||
496
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"functionCode": "const raw = { ...$json };\nconst hasStructuredDiff = raw.keys !== undefined || raw.same !== undefined || raw.different !== undefined;\nif (!hasStructuredDiff) {\n return {\n ...raw,\n compare_status: raw.compare_status ?? 'different',\n previous_mam_torrent_id: raw.previous_mam_torrent_id ?? null,\n previous_mam_checked_at: raw.previous_mam_checked_at ?? null,\n };\n}\nconst keys = raw.keys ?? {};\nconst same = raw.same ?? {};\nconst different = raw.different ?? {};\nconst toValue = (value) => {\n if (value && typeof value === 'object' && value.inputA !== undefined) {\n return value.inputA;\n }\n return value;\n};\nconst result = { ...same };\nfor (const [field, value] of Object.entries(different)) {\n result[field] = toValue(value);\n}\nif (keys.info_hash) {\n result.info_hash = keys.info_hash;\n}\nif (!result.info_hash && same.info_hash) {\n result.info_hash = same.info_hash;\n}\nconst mamTorrentId = toValue(different.mam_torrent_id);\nif (mamTorrentId !== undefined) {\n result.mam_torrent_id = mamTorrentId;\n}\nconst mamCheckedAt = toValue(different.mam_checked_at);\nif (mamCheckedAt !== undefined) {\n result.mam_checked_at = mamCheckedAt;\n}\nif (same.mam_checked_at && !result.mam_checked_at) {\n result.mam_checked_at = same.mam_checked_at;\n}\nresult.last_processed = same.last_processed ?? result.last_processed ?? null;\nresult.processing_workflow = same.processing_workflow ?? result.processing_workflow ?? null;\nresult.compare_status = 'different';\nresult.previous_mam_torrent_id = same.mam_torrent_id ?? null;\nresult.previous_mam_checked_at = same.mam_checked_at ?? null;\nreturn result;"
|
||||
},
|
||||
"id": "d659029a-5a44-4d43-a11b-4dc033d927d3",
|
||||
"name": "Normalize Updated Torrents",
|
||||
"type": "n8n-nodes-base.functionItem",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2496,
|
||||
448
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "5f2aeff8-f616-44c3-aaa1-d6266e1945a5",
|
||||
"name": "Collect Notification Items",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
2720,
|
||||
256
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"tableId": "nyaa_audiobooks",
|
||||
"returnAll": true,
|
||||
"filterType": "none"
|
||||
},
|
||||
"id": "get-db-rows",
|
||||
"name": "Get DB Rows for Deduplication",
|
||||
"type": "n8n-nodes-base.supabase",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1824,
|
||||
432
|
||||
],
|
||||
"executeOnce": true,
|
||||
"credentials": {
|
||||
"supabaseApi": {
|
||||
"id": "lWyf2ikOGHTTwnSU",
|
||||
"name": "Supabase account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.info_hash != \"\" }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
},
|
||||
"id": "condition-infohash-not-empty"
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "52f493b9-66bc-48ac-8b1d-f301d12131ee",
|
||||
"name": "Has Info Hash Guard",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
2272,
|
||||
208
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking 'Execute workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch RSS from Nyaa",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Every 5 Minutes": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch RSS from Nyaa",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Fetch RSS from Nyaa": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse XML",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse XML": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Feed Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Feed Items": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Torrent Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Torrent Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Tracker Cross-Reference",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Has Seeders?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has Seeders?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compare Datasets",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Get DB Rows for Deduplication",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Compare Datasets": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Has Info Hash Guard",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "Normalize Updated Torrents",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Notification Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Notification Router",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Needs MAM Update (IF)": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update Existing with MAM Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Flag Needs MAM Update": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Needs MAM Update (IF)",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Insert New Torrents": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Collect Notification Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Collect Notification Items": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Notification Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Normalize Updated Torrents": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Collect Notification Items",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
},
|
||||
{
|
||||
"node": "Flag Needs MAM Update",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get DB Rows for Deduplication": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Compare Datasets",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Has Info Hash Guard": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Insert New Torrents",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {},
|
||||
"triggerCount": 1,
|
||||
"versionId": "91ae4190-31f6-4a86-b38d-6a69a171d9bf",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "kUg4HIPXraph3M0E",
|
||||
"isArchived": false
|
||||
}
|
||||
603
workflows/t2BEdqrbF474ul3B.json
Normal file
603
workflows/t2BEdqrbF474ul3B.json
Normal file
@@ -0,0 +1,603 @@
|
||||
{
|
||||
"id": "t2BEdqrbF474ul3B",
|
||||
"name": "Audiobook Tracker Cross-Reference",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"inputSource": "passthrough"
|
||||
},
|
||||
"id": "workflow-trigger",
|
||||
"name": "Execute Workflow Trigger",
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
112,
|
||||
280
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"tableId": "nyaa_audiobooks",
|
||||
"returnAll": true,
|
||||
"filterType": "none"
|
||||
},
|
||||
"id": "get-db-torrents",
|
||||
"name": "Get DB Torrents for Filtering",
|
||||
"type": "n8n-nodes-base.supabase",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
560,
|
||||
208
|
||||
],
|
||||
"executeOnce": true,
|
||||
"credentials": {
|
||||
"supabaseApi": {
|
||||
"id": "lWyf2ikOGHTTwnSU",
|
||||
"name": "Supabase account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://www.myanonamouse.net/tor/js/loadSearchJSONbasic.php",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tor[text]",
|
||||
"value": "={{ $json.search_title }}"
|
||||
},
|
||||
{
|
||||
"name": "tor[srchIn][title]",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tor[searchType]",
|
||||
"value": "all"
|
||||
},
|
||||
{
|
||||
"name": "tor[main_cat][]",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"name": "tor[sortType]",
|
||||
"value": "default"
|
||||
},
|
||||
{
|
||||
"name": "tor[startNumber]",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": "perpage",
|
||||
"value": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "search-mam-title",
|
||||
"name": "Search MAM by Title",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1456,
|
||||
352
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "sxQYMPEq6GLbc9Av",
|
||||
"name": "n8n_auth_cookie"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"conditions": [
|
||||
{
|
||||
"leftValue": "={{ $json.mam_found_by_title }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "title-match-if",
|
||||
"name": "Title Match Found?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2576,
|
||||
280
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://www.myanonamouse.net/tor/js/loadSearchJSONbasic.php",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendQuery": true,
|
||||
"queryParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "tor[text]",
|
||||
"value": "={{ $json.search_term }}"
|
||||
},
|
||||
{
|
||||
"name": "tor[srchIn][author]",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tor[srchIn][narrator]",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tor[searchType]",
|
||||
"value": "all"
|
||||
},
|
||||
{
|
||||
"name": "tor[main_cat][]",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"name": "tor[sortType]",
|
||||
"value": "default"
|
||||
},
|
||||
{
|
||||
"name": "tor[startNumber]",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": "perpage",
|
||||
"value": "10"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "search-mam-author",
|
||||
"name": "Search MAM by Author/Narrator",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
3024,
|
||||
352
|
||||
],
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "sxQYMPEq6GLbc9Av",
|
||||
"name": "n8n_auth_cookie"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mergeByFields": {
|
||||
"values": [
|
||||
{
|
||||
"field1": "info_hash",
|
||||
"field2": "info_hash"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "compare-datasets",
|
||||
"name": "Filter Unchecked Torrents",
|
||||
"type": "n8n-nodes-base.compareDatasets",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
784,
|
||||
248
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"includeOtherFields": true,
|
||||
"options": {}
|
||||
},
|
||||
"id": "extract-base-title-set",
|
||||
"name": "Extract Base Title",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
336,
|
||||
280
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"includeOtherFields": true,
|
||||
"options": {}
|
||||
},
|
||||
"id": "extract-author-set",
|
||||
"name": "Extract Author/Narrator",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
2800,
|
||||
352
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "parse-mam-author-set",
|
||||
"name": "Parse MAM Author Results",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
3248,
|
||||
352
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "combine",
|
||||
"combineBy": "combineByPosition",
|
||||
"options": {}
|
||||
},
|
||||
"id": "merge-title-enrich",
|
||||
"name": "Merge Title Enrich",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
2128,
|
||||
280
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"functionCode": "const data = Array.isArray($json.data) ? $json.data : [];\nconst volNum = $json.volume_start ?? null;\nconst norm = (s) => (s || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();\nconst seriesRoot = norm($json.search_title);\nfunction matchesVol(title, vol) {\n const t = (title || '').toLowerCase();\n const v = String(vol);\n const re = new RegExp(`(\\\\bvol(?:ume)?\\\\.?\\\\s*0*${v}\\\\b|\\\\bv0*${v}\\\\b|\\\\bbook\\\\s*0*${v}\\\\b)`,'i');\n return re.test(t);\n}\nlet best = null;\nif (volNum != null) {\n best = data.find(r => matchesVol(r.title, volNum));\n}\nif (!best) {\n const m4bSeries = data.filter(r => ((r.filetype || '').toLowerCase().includes('m4b')) && norm(r.title).includes(seriesRoot));\n best = m4bSeries[0] || data.find(r => (r.filetype || '').toLowerCase().includes('m4b')) || data[0] || null;\n}\nreturn {\n mam_torrent_id: best ? best.id : null,\n mam_found_by_title: !!best,\n mam_checked_at: $now.toISO(),\n};"
|
||||
},
|
||||
"id": "68c5d252-c28f-4a96-974b-fde708933d9f",
|
||||
"name": "Pick Best MAM Match",
|
||||
"type": "n8n-nodes-base.functionItem",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1680,
|
||||
352
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "append",
|
||||
"numberInputs": 2
|
||||
},
|
||||
"id": "92d01c6d-165b-483a-8738-715006a31647",
|
||||
"name": "Combine A-only + Different",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1008,
|
||||
280
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst total = items.length;\nconst timestamp = $now.toISO();\nreturn items.map(item => ({\n json: {\n ...item.json,\n debug_stage: \"combine-output\",\n debug_count: total,\n debug_timestamp: timestamp,\n }\n}));"
|
||||
},
|
||||
"id": "debug-combine-output",
|
||||
"name": "Debug Combine Output",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1232,
|
||||
280
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst total = items.length;\nconst timestamp = $now.toISO();\nreturn items.map(item => ({\n json: {\n ...item.json,\n debug_stage: \"pick-match\",\n debug_count: total,\n debug_timestamp: timestamp,\n }\n}));"
|
||||
},
|
||||
"id": "debug-pick-match",
|
||||
"name": "Debug Pick Match",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1904,
|
||||
352
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const items = $input.all();\nconst total = items.length;\nconst timestamp = $now.toISO();\nreturn items.map(item => ({\n json: {\n ...item.json,\n debug_stage: \"merge-output\",\n debug_count: total,\n debug_timestamp: timestamp,\n }\n}));"
|
||||
},
|
||||
"id": "debug-merge-output",
|
||||
"name": "Debug Merge Output",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
2352,
|
||||
280
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "merge-results-merge",
|
||||
"name": "Merge Results",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
3472,
|
||||
280
|
||||
],
|
||||
"parameters": {
|
||||
"mode": "append",
|
||||
"numberInputs": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "clean-merge-results",
|
||||
"name": "Clean Merge Results",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
3720,
|
||||
280
|
||||
],
|
||||
"parameters": {
|
||||
"includeOtherFields": true,
|
||||
"include": "except",
|
||||
"excludeFields": "debug_stage,debug_count,debug_timestamp,mam_found_by_title,search_term,search_title,volume_range"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Title Match Found?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Results",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Extract Author/Narrator",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get DB Torrents for Filtering": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter Unchecked Torrents",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Workflow Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Base Title",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Base Title": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get DB Torrents for Filtering",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Filter Unchecked Torrents",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Author/Narrator": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Search MAM by Author/Narrator",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search MAM by Author/Narrator": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse MAM Author Results",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Title Enrich": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Debug Merge Output",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Search MAM by Title": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Pick Best MAM Match",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Pick Best MAM Match": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Debug Pick Match",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter Unchecked Torrents": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Combine A-only + Different",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "Combine A-only + Different",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Combine A-only + Different": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Debug Combine Output",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Debug Combine Output": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Search MAM by Title",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge Title Enrich",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Debug Merge Output": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Title Match Found?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Debug Pick Match": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Title Enrich",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse MAM Author Results": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Results",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge Results": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Clean Merge Results",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"saveExecutionProgress": true,
|
||||
"saveManualExecutions": true,
|
||||
"saveDataErrorExecution": "all",
|
||||
"saveDataSuccessExecution": "all",
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "7080e712-9bf6-4a42-ba94-924e41f4aab8",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "kUg4HIPXraph3M0E",
|
||||
"isArchived": false
|
||||
}
|
||||
65
workflows/t2peFUn5eWavYRnf.json
Normal file
65
workflows/t2peFUn5eWavYRnf.json
Normal file
File diff suppressed because one or more lines are too long
557
workflows/uyqbbMRpaHAQtWUP.json
Normal file
557
workflows/uyqbbMRpaHAQtWUP.json
Normal file
File diff suppressed because one or more lines are too long
563
workflows/v3KQi4UoMlhH7JIW.json
Normal file
563
workflows/v3KQi4UoMlhH7JIW.json
Normal file
@@ -0,0 +1,563 @@
|
||||
{
|
||||
"id": "v3KQi4UoMlhH7JIW",
|
||||
"name": "MAM Followed Series Sync",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "hours",
|
||||
"hoursInterval": 24
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "schedule-daily",
|
||||
"name": "Daily at 3 AM",
|
||||
"position": [
|
||||
-256,
|
||||
160
|
||||
],
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"mode": "id",
|
||||
"value": "0gxdxCdYQ7oXk7gC"
|
||||
},
|
||||
"options": {
|
||||
"waitForSubWorkflow": true
|
||||
}
|
||||
},
|
||||
"id": "call-enricher",
|
||||
"name": "Call MAM Series Enricher",
|
||||
"position": [
|
||||
1088,
|
||||
-112
|
||||
],
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.1,
|
||||
"alwaysOutputData": true,
|
||||
"onError": "continueRegularOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"version": 2,
|
||||
"leftValue": "",
|
||||
"caseSensitive": true,
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "c1",
|
||||
"leftValue": "={{ $json.mam_series_id }}",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEmpty",
|
||||
"singleValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "enrichment-successful",
|
||||
"name": "Enrichment Successful?",
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
1312,
|
||||
-112
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {
|
||||
"reset": false
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.splitInBatches",
|
||||
"typeVersion": 3,
|
||||
"position": [
|
||||
640,
|
||||
160
|
||||
],
|
||||
"id": "3114a985-aaff-49fd-9696-9b71a1cf268a",
|
||||
"name": "Loop Over Items"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "executeQuery",
|
||||
"query": "SELECT DISTINCT series_name \nFROM smb_general_books \nWHERE series_name IS NOT NULL \n AND series_name != ''\nORDER BY series_name ASC;",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
-32,
|
||||
208
|
||||
],
|
||||
"id": "45b4c538-fd2b-4cfa-8482-b14337a53042",
|
||||
"name": "select unique series",
|
||||
"notesInFlow": true,
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
},
|
||||
"notes": "from smb_ database"
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
-32,
|
||||
16
|
||||
],
|
||||
"id": "d93903aa-c756-4955-ad05-1c4a98ef7621",
|
||||
"name": "select followed series",
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "combine",
|
||||
"fieldsToMatchString": "series_name",
|
||||
"joinMode": "keepNonMatches",
|
||||
"outputDataFrom": "input2",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
192,
|
||||
160
|
||||
],
|
||||
"id": "7d6a56a8-dce0-4577-a6ad-0a98fc5bd4de",
|
||||
"name": "Merge, Keep new from SMB"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "upsert",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "followed_series",
|
||||
"mode": "list",
|
||||
"cachedResultName": "followed_series"
|
||||
},
|
||||
"columns": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {
|
||||
"active": true,
|
||||
"id": "={{ $json.id }}",
|
||||
"series_name": "={{ $json.series_name }}",
|
||||
"author": "={{ $json.author_folder }}",
|
||||
"category": "={{ $json.category }}",
|
||||
"smb_path": "={{ $json.smb_path }}",
|
||||
"created_at": "={{ $now }}",
|
||||
"updated_at": "={{ $now }}",
|
||||
"mam_series_id": "={{ $json.mam_series_id }}",
|
||||
"enrichment_status": "enriched"
|
||||
},
|
||||
"matchingColumns": [
|
||||
"id"
|
||||
],
|
||||
"schema": [
|
||||
{
|
||||
"id": "id",
|
||||
"displayName": "id",
|
||||
"required": false,
|
||||
"defaultMatch": true,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true,
|
||||
"removed": false
|
||||
},
|
||||
{
|
||||
"id": "series_name",
|
||||
"displayName": "series_name",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": false
|
||||
},
|
||||
{
|
||||
"id": "author",
|
||||
"displayName": "author",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": false
|
||||
},
|
||||
{
|
||||
"id": "category",
|
||||
"displayName": "category",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": false
|
||||
},
|
||||
{
|
||||
"id": "smb_path",
|
||||
"displayName": "smb_path",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": false
|
||||
},
|
||||
{
|
||||
"id": "active",
|
||||
"displayName": "active",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "boolean",
|
||||
"canBeUsedToMatch": false
|
||||
},
|
||||
{
|
||||
"id": "created_at",
|
||||
"displayName": "created_at",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "dateTime",
|
||||
"canBeUsedToMatch": false
|
||||
},
|
||||
{
|
||||
"id": "updated_at",
|
||||
"displayName": "updated_at",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "dateTime",
|
||||
"canBeUsedToMatch": false
|
||||
},
|
||||
{
|
||||
"id": "mam_series_id",
|
||||
"displayName": "mam_series_id",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": false
|
||||
},
|
||||
{
|
||||
"id": "enrichment_status",
|
||||
"displayName": "enrichment_status",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": false
|
||||
}
|
||||
],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": false
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
1760,
|
||||
32
|
||||
],
|
||||
"id": "a6f4d977-bc0e-4254-829b-b53d65eecdee",
|
||||
"name": "Insert or update rows in a table",
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "select",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "smb_general_books",
|
||||
"mode": "list",
|
||||
"cachedResultName": "smb_general_books"
|
||||
},
|
||||
"limit": 1,
|
||||
"where": {
|
||||
"values": [
|
||||
{
|
||||
"column": "series_name",
|
||||
"value": "={{ $json.series_name }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
864,
|
||||
-48
|
||||
],
|
||||
"id": "6136715e-cbc5-498d-bc68-7cc9be5b4537",
|
||||
"name": "Select rows from a table",
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "combine",
|
||||
"combineBy": "combineByPosition",
|
||||
"options": {
|
||||
"clashHandling": {
|
||||
"values": {
|
||||
"resolveClash": "preferLast"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1584,
|
||||
-32
|
||||
],
|
||||
"id": "e2dbcac5-5005-4e21-ac16-3dba2b5605ac",
|
||||
"name": "Merge"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 3
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "eea3e003-b227-4022-a33f-608d2602103b",
|
||||
"leftValue": "={{ $json.series_name }}",
|
||||
"rightValue": "The Lord of the Rings",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "notEquals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.filter",
|
||||
"typeVersion": 2.3,
|
||||
"position": [
|
||||
416,
|
||||
160
|
||||
],
|
||||
"id": "a8c0fc3e-fa7b-42e0-ac99-b5de3d3b225f",
|
||||
"name": "Filter"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Daily at 3 AM": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "select unique series",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "select followed series",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Enrichment Successful?": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Loop Over Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Call MAM Series Enricher": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Enrichment Successful?",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Loop Over Items": {
|
||||
"main": [
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "Select rows from a table",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"select followed series": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge, Keep new from SMB",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"select unique series": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge, Keep new from SMB",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge, Keep new from SMB": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Select rows from a table": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Call MAM Series Enricher",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Insert or update rows in a table",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Insert or update rows in a table": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Loop Over Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Loop Over Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v0",
|
||||
"callerPolicy": "workflowsFromSameOwner",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "ad7526e8-17ed-420c-8deb-d7b4db5ba4bb",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": false
|
||||
}
|
||||
354
workflows/xXUnt2hL2FKxzOhBnkd3Z.json
Normal file
354
workflows/xXUnt2hL2FKxzOhBnkd3Z.json
Normal file
@@ -0,0 +1,354 @@
|
||||
{
|
||||
"id": "xXUnt2hL2FKxzOhBnkd3Z",
|
||||
"name": "MAM Series Checker",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
-832,
|
||||
-16
|
||||
],
|
||||
"id": "279ca2c5-3c3e-4cd6-bc97-e43010fb693c",
|
||||
"name": "Schedule Trigger"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.splitInBatches",
|
||||
"typeVersion": 3,
|
||||
"position": [
|
||||
-192,
|
||||
-16
|
||||
],
|
||||
"id": "2412dfa0-8362-4cc4-8a18-3f233d5338b2",
|
||||
"name": "Loop Over Items"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://www.myanonamouse.net/tor/js/loadSearchJSONbasic.php",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\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}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
48,
|
||||
0
|
||||
],
|
||||
"id": "f387a918-881e-492c-b41a-1dc621e8e822",
|
||||
"name": "HTTP Request",
|
||||
"credentials": {
|
||||
"httpHeaderAuth": {
|
||||
"id": "G8eA8XeS9P5axwJd",
|
||||
"name": "mam cookie auth header"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldsToAggregate": {
|
||||
"fieldToAggregate": [
|
||||
{
|
||||
"fieldToAggregate": "book_name"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.aggregate",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
816,
|
||||
0
|
||||
],
|
||||
"id": "019177aa-2c4f-4291-90f7-c58750033e77",
|
||||
"name": "Aggregate"
|
||||
},
|
||||
{
|
||||
"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\".",
|
||||
"batching": {}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.chainLlm",
|
||||
"typeVersion": 1.9,
|
||||
"position": [
|
||||
976,
|
||||
0
|
||||
],
|
||||
"id": "aef43627-3228-42ab-a2fe-9f4b50d1a855",
|
||||
"name": "Basic LLM Chain"
|
||||
},
|
||||
{
|
||||
"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-lite",
|
||||
"mode": "list",
|
||||
"cachedResultName": "gemini_cli/gemini-2.5-flash-lite"
|
||||
},
|
||||
"responsesApiEnabled": false,
|
||||
"options": {}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"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,
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
-624,
|
||||
-16
|
||||
],
|
||||
"id": "e7bc495c-a3e1-46b8-b0f1-ff49286344e7",
|
||||
"name": "Select Series",
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "select",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "smb_general_books",
|
||||
"mode": "list",
|
||||
"cachedResultName": "smb_general_books"
|
||||
},
|
||||
"returnAll": true,
|
||||
"where": {
|
||||
"values": [
|
||||
{
|
||||
"column": "series_name",
|
||||
"value": "={{ $('Loop Over Items').item.json.series_name }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
656,
|
||||
0
|
||||
],
|
||||
"id": "62be829f-6d74-4142-9420-eb4f6947df09",
|
||||
"name": "Select Book Titles",
|
||||
"executeOnce": true,
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Select Series",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Loop Over Items": {
|
||||
"main": [
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"HTTP Request": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Out",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Aggregate": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Basic LLM Chain",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Out": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Aggregate Available Books",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"OpenAI Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "Basic LLM Chain",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Aggregate Available Books": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Select Book Titles",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Select Series": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Loop Over Items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Select Book Titles": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Aggregate",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Basic LLM Chain": {
|
||||
"main": [
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "b179028c-8bde-4e62-8183-1464046a348b",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": false
|
||||
}
|
||||
Reference in New Issue
Block a user