clean up supabase, MAM automation continues.
This commit is contained in:
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
24
tags.json
24
tags.json
@@ -61,6 +61,30 @@
|
||||
{
|
||||
"workflowId": "J3uKCCbSuQ1fdJkC",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "WkAdUd9jXTtPagGO",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "6S41oPplwN1S9Lz0",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "NChjOd3ILEmt0FdyAt8qA",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "kRZyX9H2uDHHncpE",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "xXUnt2hL2FKxzOhBnkd3Z",
|
||||
"tagId": "FydpKYmttDwoZVAA"
|
||||
},
|
||||
{
|
||||
"workflowId": "xXUnt2hL2FKxzOhBnkd3Z",
|
||||
"tagId": "ct0Rtzpu15B497av"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -601,6 +601,6 @@
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "of8yoeyjjIAhYdnQ",
|
||||
"parentFolderId": "eWW72giJDI4fxlWw",
|
||||
"isArchived": false
|
||||
}
|
||||
@@ -22,37 +22,6 @@
|
||||
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}];"
|
||||
@@ -169,6 +138,45 @@
|
||||
640,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "select",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "npm_tokens",
|
||||
"mode": "list",
|
||||
"cachedResultName": "npm_tokens"
|
||||
},
|
||||
"where": {
|
||||
"values": [
|
||||
{
|
||||
"column": "service_name",
|
||||
"value": "npm_dfw"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
448,
|
||||
400
|
||||
],
|
||||
"id": "18c7d30b-bd5e-450c-9800-4f883c641b1c",
|
||||
"name": "Select rows from a table",
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "Ik8CFyap8ic2Md3M",
|
||||
"name": "n8n-infra"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
@@ -176,18 +184,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get NPM Token",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get NPM Token": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Certificates",
|
||||
"node": "Select rows from a table",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -209,7 +206,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get NPM Token",
|
||||
"node": "Select rows from a table",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -248,6 +245,17 @@
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Select rows from a table": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Certificates",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@@ -258,7 +266,7 @@
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"triggerCount": 1,
|
||||
"versionId": "708d8124-5275-445c-92da-43b0f43e9826",
|
||||
"versionId": "67bbccd4-8c5e-4cc5-8969-9460ed529b23",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
|
||||
185
workflows/NChjOd3ILEmt0FdyAt8qA.json
Normal file
185
workflows/NChjOd3ILEmt0FdyAt8qA.json
Normal file
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"id": "NChjOd3ILEmt0FdyAt8qA",
|
||||
"name": "Transmission: Get Session",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=http://{{ $json.server_id }}:9091/transmission/rpc",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpBasicAuth",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ { method: 'session-get' } }}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"fullResponse": true,
|
||||
"neverError": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "4c14375e-3d69-46eb-b263-d333a775dc02",
|
||||
"name": "Request Transmission Session",
|
||||
"position": [
|
||||
-176,
|
||||
16
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"id": "iymUPilnVhfL3h5D",
|
||||
"name": "transmission"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"language": "pythonNative",
|
||||
"pythonCode": "# Access the first item's JSON data\n# In n8n Python nodes, '_items' is the global list of input items\nfirst_item = _items[0]['json']\n\n# Get headers safely\nheaders = first_item.get('headers', {})\n\n# Extract session ID (check both lowercase and capitalized keys)\nsession_id = headers.get('x-transmission-session-id') or headers.get('X-Transmission-Session-Id')\n\nif not session_id:\n raise Exception('Transmission session header missing')\n\n# Return the formatted list with 'json' key\nreturn [{'json': {'session_id': session_id}}]"
|
||||
},
|
||||
"id": "7880edb1-b575-4f9f-b744-987d22cd18a6",
|
||||
"name": "Extract Session Header",
|
||||
"position": [
|
||||
48,
|
||||
16
|
||||
],
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowInputs": {
|
||||
"values": [
|
||||
{
|
||||
"name": "server_id"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
-624,
|
||||
112
|
||||
],
|
||||
"id": "f39637d4-bd93-470a-a704-7947450edfb5",
|
||||
"name": "When Executed by Another Workflow"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"errorMessage": "Invalid server_name, must be a seed server."
|
||||
},
|
||||
"type": "n8n-nodes-base.stopAndError",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-176,
|
||||
208
|
||||
],
|
||||
"id": "1299a1c0-5147-4da1-8fab-1eb0fb696e68",
|
||||
"name": "Stop and Error"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": false,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict",
|
||||
"version": 3
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "d1351268-ab96-4a81-905a-336fd784da32",
|
||||
"leftValue": "={{ $json.server_id }}",
|
||||
"rightValue": "seed",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "startsWith"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c3d2d00a-ce5c-49cc-b468-4088623a773b",
|
||||
"leftValue": "={{ $json.server_id }}",
|
||||
"rightValue": "ben.io",
|
||||
"operator": {
|
||||
"type": "string",
|
||||
"operation": "endsWith"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"options": {
|
||||
"ignoreCase": true
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.if",
|
||||
"typeVersion": 2.3,
|
||||
"position": [
|
||||
-400,
|
||||
112
|
||||
],
|
||||
"id": "c31c8c1a-bba5-47b2-a7b8-e30b53ccb38d",
|
||||
"name": "server name check"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Request Transmission Session": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Session Header",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"When Executed by Another Workflow": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "server name check",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"server name check": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Request Transmission Session",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Stop and Error",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "b1a4dc71-5217-4bc9-8f23-ec7b92a7d23a",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": false
|
||||
}
|
||||
@@ -24,13 +24,13 @@
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-Transmission-Session-Id",
|
||||
"value": "={{ $json.session_id }}"
|
||||
"value": "={{ $('Merge Session to Files').item.json.headers[\"X-Transmission-Session-Id\"] }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ $json.transmission_body }}",
|
||||
"jsonBody": "={\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"metainfo\": \"{{ $('Extract from File').item.json.data }}\",\n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": true\n }\n}",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
@@ -42,7 +42,7 @@
|
||||
"id": "http-add-torrent",
|
||||
"name": "Add Torrent to Transmission",
|
||||
"position": [
|
||||
2192,
|
||||
1792,
|
||||
128
|
||||
],
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
@@ -62,106 +62,12 @@
|
||||
"id": "code-extract-torrent-id",
|
||||
"name": "Extract Torrent ID",
|
||||
"position": [
|
||||
2416,
|
||||
2016,
|
||||
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",
|
||||
@@ -170,120 +76,21 @@
|
||||
"id": "respond-complete",
|
||||
"name": "Respond Complete",
|
||||
"position": [
|
||||
3360,
|
||||
16
|
||||
2464,
|
||||
128
|
||||
],
|
||||
"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 }}"
|
||||
"language": "pythonNative",
|
||||
"pythonCode": "# 1. Access the input item\nitem = _items[0]['json']\n\n# 2. Extract the data we need\n# UPDATE: The input key is now 'dl_link', not 'download_link'\ndownload_link = item.get('dl_link')\nsession_id = item.get('session_id')\n\n# 3. Create the Transmission Payload\npayload = {\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"filename\": download_link,\n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": True \n }\n}\n\n# 4. Return the formatted data\nreturn [{\n \"json\": {\n # UPDATE: Map 'title' correctly for any old nodes that need it\n \"title\": item.get('meta_title'),\n \n # NEW: Pass through all metadata for the Postgres Logger\n \"meta_title\": item.get('meta_title'),\n \"meta_series\": item.get('meta_series'),\n \"meta_book_number\": item.get('meta_book_number'),\n \"meta_author\": item.get('meta_author'),\n \"mam_id\": item.get('mam_id'),\n \"dl_link\": download_link,\n \n # Transmission Data\n \"transmission_body\": payload,\n \"headers\": {\n \"X-Transmission-Session-Id\": session_id\n }\n }\n}]"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"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",
|
||||
@@ -298,11 +105,321 @@
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1488,
|
||||
672,
|
||||
128
|
||||
],
|
||||
"id": "dde23141-d708-48e7-942f-08aa1d749189",
|
||||
"name": "Merge Session Context"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "={{ $json.transmission_body.arguments.filename }}",
|
||||
"authentication": "genericCredentialType",
|
||||
"genericAuthType": "httpHeaderAuth",
|
||||
"options": {
|
||||
"response": {
|
||||
"response": {
|
||||
"fullResponse": true,
|
||||
"responseFormat": "file"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
1120,
|
||||
128
|
||||
],
|
||||
"id": "acb7d649-a853-44e7-84e8-f82398f3008a",
|
||||
"name": "MAM Get File HTTP Request",
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"id": "iymUPilnVhfL3h5D",
|
||||
"name": "transmission"
|
||||
},
|
||||
"httpHeaderAuth": {
|
||||
"id": "G8eA8XeS9P5axwJd",
|
||||
"name": "mam cookie auth header"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "binaryToPropery",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.extractFromFile",
|
||||
"typeVersion": 1.1,
|
||||
"position": [
|
||||
1568,
|
||||
128
|
||||
],
|
||||
"id": "acb1b56b-ac9a-41ec-ad60-7ce983b8cdd7",
|
||||
"name": "Extract from File"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"language": "pythonNative",
|
||||
"pythonCode": "results = []\n\nfor item in _items:\n # 1. Get the binary data from the previous download node\n # n8n stores the file in the 'binary' key. The default property name is usually 'data'.\n # We need the 'data' field inside that, which contains the Base64 string.\n binary_ref = item.get('binary', {}).get('data', {})\n b64_string = binary_ref.get('data')\n\n if b64_string:\n # 2. Create the exact JSON body Transmission expects for file uploads\n # We use \"metainfo\" for the actual file content (Base64)\n payload = {\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"metainfo\": b64_string, \n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": False\n }\n }\n\n # 3. Preserve any existing JSON (like session_id) if it exists, or start fresh\n new_json = item.get('json', {}).copy()\n new_json['transmission_body'] = payload\n \n results.append({\n \"json\": new_json,\n # Pass the binary through just in case, though we don't strictly need it anymore\n \"binary\": item.get('binary', {})\n })\n\nreturn results"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1344,
|
||||
128
|
||||
],
|
||||
"id": "0b011613-50e9-4612-ac6e-1f6ac0e39ebb",
|
||||
"name": "Prepare Transmission Payload"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"value": "NChjOd3ILEmt0FdyAt8qA",
|
||||
"mode": "list",
|
||||
"cachedResultUrl": "/workflow/NChjOd3ILEmt0FdyAt8qA",
|
||||
"cachedResultName": "Transmission: Get Session"
|
||||
},
|
||||
"workflowInputs": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {
|
||||
"server_id": "seed-1.dfw.ben.io"
|
||||
},
|
||||
"matchingColumns": [
|
||||
"server_id"
|
||||
],
|
||||
"schema": [
|
||||
{
|
||||
"id": "server_id",
|
||||
"displayName": "server_id",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"canBeUsedToMatch": true,
|
||||
"type": "string",
|
||||
"removed": false
|
||||
}
|
||||
],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": true
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
448,
|
||||
200
|
||||
],
|
||||
"id": "175e270a-b1d9-41d0-981a-1f83258443b1",
|
||||
"name": "Transmission: Get Session"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"mode": "list",
|
||||
"value": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "download_history",
|
||||
"mode": "list",
|
||||
"cachedResultName": "download_history"
|
||||
},
|
||||
"columns": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {
|
||||
"mam_id": "={{ $json.original_input.mam_id }}",
|
||||
"torrent_url": "={{ $('Extract Torrent ID').item.json.original_input.dl_link }}",
|
||||
"book_id": "={{ null }}",
|
||||
"torrent_hash": "={{ $json.torrent_hash }}",
|
||||
"filename": "={{ $('Extract Torrent ID').item.json.torrent_name }}",
|
||||
"status": "={{ $json.status }}",
|
||||
"meta_title": "={{ $json.original_input.meta_title }}",
|
||||
"meta_series": "={{ $json.original_input.meta_series }}",
|
||||
"meta_book_number": "={{ $json.original_input.meta_book_number }}",
|
||||
"meta_author": "={{ $json.original_input.meta_author }}"
|
||||
},
|
||||
"matchingColumns": [
|
||||
"id"
|
||||
],
|
||||
"schema": [
|
||||
{
|
||||
"id": "id",
|
||||
"displayName": "id",
|
||||
"required": false,
|
||||
"defaultMatch": true,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true,
|
||||
"removed": false
|
||||
},
|
||||
{
|
||||
"id": "book_id",
|
||||
"displayName": "book_id",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "torrent_url",
|
||||
"displayName": "torrent_url",
|
||||
"required": true,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "torrent_hash",
|
||||
"displayName": "torrent_hash",
|
||||
"required": true,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "filename",
|
||||
"displayName": "filename",
|
||||
"required": true,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "transmission_path",
|
||||
"displayName": "transmission_path",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "gateway_path",
|
||||
"displayName": "gateway_path",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "status",
|
||||
"displayName": "status",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "downloaded_at",
|
||||
"displayName": "downloaded_at",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "dateTime",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "created_at",
|
||||
"displayName": "created_at",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "dateTime",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "updated_at",
|
||||
"displayName": "updated_at",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "dateTime",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "meta_title",
|
||||
"displayName": "meta_title",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "meta_series",
|
||||
"displayName": "meta_series",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "meta_book_number",
|
||||
"displayName": "meta_book_number",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "meta_author",
|
||||
"displayName": "meta_author",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "mam_id",
|
||||
"displayName": "mam_id",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "number",
|
||||
"canBeUsedToMatch": true
|
||||
},
|
||||
{
|
||||
"id": "error_log",
|
||||
"displayName": "error_log",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"type": "string",
|
||||
"canBeUsedToMatch": true
|
||||
}
|
||||
],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": false
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
2240,
|
||||
128
|
||||
],
|
||||
"id": "5a79163a-a211-475c-b53e-79d086a4c9a7",
|
||||
"name": "Insert rows in a table",
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
@@ -317,80 +434,13 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"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"
|
||||
"node": "Insert rows in a table",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -399,7 +449,7 @@
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Request Transmission Session",
|
||||
"node": "Transmission: Get Session",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
@@ -411,33 +461,11 @@
|
||||
]
|
||||
]
|
||||
},
|
||||
"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",
|
||||
"node": "MAM Get File HTTP Request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
@@ -454,11 +482,66 @@
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"MAM Get File HTTP Request": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Transmission Payload",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract from File": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Add Torrent to Transmission",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Transmission Payload": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract from File",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Transmission: Get Session": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge Session Context",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Insert rows in a table": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Respond Complete",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {},
|
||||
"triggerCount": 0,
|
||||
"versionId": "ef553d09-89f5-4f19-9003-0f2040bf82ba",
|
||||
"versionId": "509f1eb5-35d3-4d2a-829d-573deddc24a7",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
|
||||
208
workflows/kn1gehxiWbkRfDFFAKx0x.json
Normal file
208
workflows/kn1gehxiWbkRfDFFAKx0x.json
Normal file
@@ -0,0 +1,208 @@
|
||||
{
|
||||
"id": "kn1gehxiWbkRfDFFAKx0x",
|
||||
"name": "MAM Check for Completed",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"rule": {
|
||||
"interval": [
|
||||
{
|
||||
"field": "hours"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.scheduleTrigger",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
-512,
|
||||
-16
|
||||
],
|
||||
"id": "e621dd75-f5c0-48e6-816c-15f22fb500ba",
|
||||
"name": "Schedule Trigger"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"value": "NChjOd3ILEmt0FdyAt8qA",
|
||||
"mode": "list",
|
||||
"cachedResultUrl": "/workflow/NChjOd3ILEmt0FdyAt8qA",
|
||||
"cachedResultName": "Transmission: Get Session"
|
||||
},
|
||||
"workflowInputs": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {
|
||||
"server_id": "seed-1.dfw.ben.io"
|
||||
},
|
||||
"matchingColumns": [
|
||||
"server_id"
|
||||
],
|
||||
"schema": [
|
||||
{
|
||||
"id": "server_id",
|
||||
"displayName": "server_id",
|
||||
"required": false,
|
||||
"defaultMatch": false,
|
||||
"display": true,
|
||||
"canBeUsedToMatch": true,
|
||||
"type": "string",
|
||||
"removed": false
|
||||
}
|
||||
],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": true
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
-304,
|
||||
-16
|
||||
],
|
||||
"id": "10e82b7b-fdc4-4d95-a5e7-4df97bb700f7",
|
||||
"name": "Call 'Transmission: Get Session'"
|
||||
},
|
||||
{
|
||||
"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": "{\n \"method\": \"torrent-get\",\n \"arguments\": {\n \"fields\": [\n \"id\",\n \"name\",\n \"hashString\",\n \"percentDone\",\n \"isFinished\",\n \"downloadDir\",\n \"addedDate\" \n ]\n }\n}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
-96,
|
||||
-16
|
||||
],
|
||||
"id": "c49a3e03-7fab-43d7-a430-4cbaa089ca2c",
|
||||
"name": "HTTP Request",
|
||||
"credentials": {
|
||||
"httpBasicAuth": {
|
||||
"id": "iymUPilnVhfL3h5D",
|
||||
"name": "transmission"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"language": "pythonNative",
|
||||
"pythonCode": "import time\n\n# Output list\nresults = []\n\n# Define your time window (2 hours in seconds to match your node name)\n# 2 * 60 * 60 = 7200\nTIME_WINDOW = 2 * 60 * 60\ncurrent_time = time.time()\n\n# 1. Get the list of torrents from the HTTP response\ninput_data = _items[0]['json']\ntorrents = input_data.get('arguments', {}).get('torrents', [])\n\nfor torrent in torrents:\n # 2. Check if download is 100% complete (1.0)\n is_downloaded = torrent.get('percentDone') == 1\n \n # 3. Filter Logic (Check if added recently)\n added_date = torrent.get('addedDate', 0)\n age_in_seconds = current_time - added_date\n \n # Optional: Uncomment this if you ALSO only want completed items\n # is_complete = torrent.get('percentDone') == 1\n\n if age_in_seconds <= TIME_WINDOW: # and is_complete:\n # 4. Return the FULL object\n # We pass the entire 'torrent' dictionary directly\n results.append({\n \"json\": torrent\n })\n\nreturn results"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
112,
|
||||
-16
|
||||
],
|
||||
"id": "18ad2b6e-824b-4fac-8924-ddbf35f45579",
|
||||
"name": "Filter for last 2 hours"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "select",
|
||||
"schema": {
|
||||
"__rl": true,
|
||||
"value": "public",
|
||||
"mode": "list",
|
||||
"cachedResultName": "public"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "smb_general_books",
|
||||
"mode": "list",
|
||||
"cachedResultName": "smb_general_books"
|
||||
},
|
||||
"where": {
|
||||
"values": [
|
||||
{}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.postgres",
|
||||
"typeVersion": 2.6,
|
||||
"position": [
|
||||
384,
|
||||
-112
|
||||
],
|
||||
"id": "0fa625e2-3e86-4abe-a837-f876d2d08f10",
|
||||
"name": "Select rows from a table",
|
||||
"credentials": {
|
||||
"postgres": {
|
||||
"id": "9grzZwW7Br6SzdV8",
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Schedule Trigger": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Call 'Transmission: Get Session'",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Call 'Transmission: Get Session'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "HTTP Request",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"HTTP Request": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Filter for last 2 hours",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Filter for last 2 hours": {
|
||||
"main": [
|
||||
[]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "26ae4972-43fe-4776-b2ed-af9f7c88aa42",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
"projectName": "Ben W <admin@ben.io>",
|
||||
"personalEmail": "admin@ben.io"
|
||||
},
|
||||
"parentFolderId": "6tDyZCwqELStb6Ik",
|
||||
"isArchived": false
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
"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}",
|
||||
"jsonBody": "={\n \"dlLink\": \"1\",\n \"tor\": {\n \"text\": \" {{ $json.series_name }} m4b\",\n \"srchIn\": [\n \"title\",\n \"author\",\n \"filenames\",\n \"fileTypes\"\n ],\n \"main_cat\": [\n \"13\"\n ],\n \"searchType\": \"all\",\n \"sortType\": \"seedersDesc\"\n }\n}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
@@ -91,7 +91,8 @@
|
||||
0
|
||||
],
|
||||
"id": "aef43627-3228-42ab-a2fe-9f4b50d1a855",
|
||||
"name": "Basic LLM Chain"
|
||||
"name": "Basic LLM Chain",
|
||||
"onError": "continueErrorOutput"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
@@ -113,12 +114,15 @@
|
||||
"parameters": {
|
||||
"model": {
|
||||
"__rl": true,
|
||||
"value": "gemini_cli/gemini-2.5-flash-lite",
|
||||
"value": "gemini_cli/gemini-2.5-flash",
|
||||
"mode": "list",
|
||||
"cachedResultName": "gemini_cli/gemini-2.5-flash-lite"
|
||||
"cachedResultName": "gemini_cli/gemini-2.5-flash"
|
||||
},
|
||||
"responsesApiEnabled": false,
|
||||
"options": {}
|
||||
"options": {
|
||||
"responseFormat": "json_object",
|
||||
"maxRetries": 2
|
||||
}
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"typeVersion": 1.3,
|
||||
@@ -141,6 +145,19 @@
|
||||
"fieldToAggregate": [
|
||||
{
|
||||
"fieldToAggregate": "data.title"
|
||||
},
|
||||
{
|
||||
"fieldToAggregate": "=data.dl",
|
||||
"renameField": true
|
||||
},
|
||||
{
|
||||
"fieldToAggregate": "data.id"
|
||||
},
|
||||
{
|
||||
"fieldToAggregate": "data.series_info"
|
||||
},
|
||||
{
|
||||
"fieldToAggregate": "data.author_info"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -228,6 +245,64 @@
|
||||
"name": "n8n-media"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "combine",
|
||||
"combineBy": "combineByPosition",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 3.2,
|
||||
"position": [
|
||||
1328,
|
||||
0
|
||||
],
|
||||
"id": "d8c36a24-7337-453f-b403-f5c631025096",
|
||||
"name": "Merge"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"workflowId": {
|
||||
"__rl": true,
|
||||
"value": "kRZyX9H2uDHHncpE",
|
||||
"mode": "list",
|
||||
"cachedResultUrl": "/workflow/kRZyX9H2uDHHncpE",
|
||||
"cachedResultName": "MAM Transmission Manager"
|
||||
},
|
||||
"workflowInputs": {
|
||||
"mappingMode": "defineBelow",
|
||||
"value": {},
|
||||
"matchingColumns": [],
|
||||
"schema": [],
|
||||
"attemptToConvertTypes": false,
|
||||
"convertFieldsToString": true
|
||||
},
|
||||
"mode": "each",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.executeWorkflow",
|
||||
"typeVersion": 1.3,
|
||||
"position": [
|
||||
1728,
|
||||
0
|
||||
],
|
||||
"id": "b6ec81b3-4f62-494e-9437-1a31c82a0299",
|
||||
"name": "Call 'MAM Transmission Manager'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"language": "pythonNative",
|
||||
"pythonCode": "# Output list\nresults = []\n\n# Access the input item\ninput_item = _items[0]['json']\n\n# Extract Lists\nmissing_books = input_item.get('data', [])\navailable_titles = input_item.get('title', [])\navailable_ids = input_item.get('id', [])\navailable_series_info = input_item.get('series_info', [])\navailable_author_info = input_item.get('author_info', [])\navailable_catnames = input_item.get('catname', []) # New: For category detection\navailable_tags = input_item.get('tags', []) # New: For category detection\n\n# 1. Match missing titles to their IDs & Metadata\nfor book_title in missing_books:\n # Normalize the target title\n target_clean = str(book_title).strip()\n \n # Find index\n found_index = -1\n for i, title in enumerate(available_titles):\n if str(title).strip() == target_clean:\n found_index = i\n break\n \n if found_index != -1:\n # Get raw data\n book_id = available_ids[found_index]\n series_raw = available_series_info[found_index]\n author_raw = available_author_info[found_index]\n catname_raw = available_catnames[found_index] if i < len(available_catnames) else \"\"\n tags_raw = available_tags[found_index] if i < len(available_tags) else \"\"\n \n # --- PARSING LOGIC ---\n \n # Parse Series\n meta_series = \"Unknown\"\n meta_book_num = \"0\"\n \n if isinstance(series_raw, str):\n import json\n try:\n series_raw = json.loads(series_raw)\n except:\n pass\n\n if isinstance(series_raw, dict):\n keys = list(series_raw.keys())\n if keys:\n data = series_raw[keys[0]]\n if isinstance(data, list) and len(data) >= 2:\n meta_series = str(data[0]).replace(''', \"'\")\n meta_book_num = str(data[1])\n\n # Parse Author\n meta_author = \"Unknown\"\n \n if isinstance(author_raw, str):\n import json\n try:\n author_raw = json.loads(author_raw)\n except:\n pass\n\n if isinstance(author_raw, dict):\n keys = list(author_raw.keys())\n if keys:\n meta_author = str(author_raw[keys[0]])\n\n # --- CATEGORY DETECTION ---\n # Check if \"Light Novel\" or \"Anime\" appears in category name or tags\n is_anime = \"Light Novel\" in str(catname_raw) or \"anime\" in str(tags_raw).lower()\n category = 'anime' if is_anime else 'general'\n\n # Construct download link\n full_url = f\"https://www.myanonamouse.net/tor/download.php?tid={book_id}\"\n \n results.append({\n \"json\": {\n # Metadata\n \"meta_title\": target_clean,\n \"meta_series\": meta_series,\n \"meta_book_number\": meta_book_num,\n \"meta_author\": meta_author,\n \"mam_id\": book_id,\n \"category\": category, # <--- Sending this to the DB now\n \n # Download Info\n \"dl_link\": full_url,\n \"torrent_hash\": \"PENDING\"\n }\n })\n\nreturn results"
|
||||
},
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1520,
|
||||
0
|
||||
],
|
||||
"id": "c7496e8c-e307-4659-bdd0-530cd5feeef3",
|
||||
"name": "Create Download Links"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
@@ -305,6 +380,11 @@
|
||||
"node": "Select Book Titles",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -332,6 +412,40 @@
|
||||
]
|
||||
},
|
||||
"Basic LLM Chain": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Merge": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Download Links",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create Download Links": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Call 'MAM Transmission Manager'",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Call 'MAM Transmission Manager'": {
|
||||
"main": [
|
||||
[]
|
||||
]
|
||||
@@ -341,8 +455,8 @@
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"triggerCount": 0,
|
||||
"versionId": "b179028c-8bde-4e62-8183-1464046a348b",
|
||||
"triggerCount": 1,
|
||||
"versionId": "8a3af598-bd9e-48e1-a025-cb2059cfebbd",
|
||||
"owner": {
|
||||
"type": "personal",
|
||||
"projectId": "FeLO36wNUAcn61Wj",
|
||||
|
||||
Reference in New Issue
Block a user