{ "id": "kRZyX9H2uDHHncpE", "name": "MAM Transmission Manager", "nodes": [ { "position": [ 224, 128 ], "type": "n8n-nodes-base.executeWorkflowTrigger", "typeVersion": 1, "id": "start", "name": "Start", "parameters": {} }, { "id": "http-add-torrent", "name": "Add Torrent to Transmission", "onError": "continueRegularOutput", "parameters": { "specifyBody": "json", "options": { "response": { "response": { "fullResponse": true } } }, "headerParameters": { "parameters": [ { "value": "={{ $('Merge Session to Files').item.json.headers[\"X-Transmission-Session-Id\"] }}", "name": "X-Transmission-Session-Id" } ] }, "sendHeaders": true, "genericAuthType": "httpBasicAuth", "method": "POST", "sendBody": true, "url": "http://seed-1.dfw.ben.io:9091/transmission/rpc", "authentication": "genericCredentialType", "jsonBody": "={\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"metainfo\": \"{{ $('Extract from File').item.json.data }}\",\n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": true\n }\n}" }, "position": [ 1792, 128 ], "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "credentials": { "httpBasicAuth": { "id": "iymUPilnVhfL3h5D", "name": "transmission" } } }, { "position": [ 2016, 128 ], "type": "n8n-nodes-base.code", "typeVersion": 2, "id": "code-extract-torrent-id", "name": "Extract Torrent ID", "parameters": { "jsCode": "const responses = $input.all();\nconst originals = $('Start').all();\nconst mergedPayloads = $('Merge Session Context').all();\n\nfunction getOriginal(index) {\n return originals[index]?.json || originals[0]?.json || {};\n}\n\nfunction getSessionId(index) {\n return mergedPayloads[index]?.json?.session_id || mergedPayloads[0]?.json?.session_id || '';\n}\n\nconst outputs = [];\n\nfor (let i = 0; i < responses.length; i++) {\n const item = responses[i];\n const body = item.json.body || item.json;\n const result = body?.result;\n const args = body?.arguments || {};\n const torrentInfo = args['torrent-added'] || args['torrent-duplicate'];\n const sessionId = getSessionId(i);\n const originalInput = getOriginal(i);\n\n if (item.error || (!torrentInfo && result !== 'duplicate' && result !== 'success')) {\n outputs.push({\n json: {\n status: 'failed',\n error: item.error?.message || result || 'Unknown error adding torrent',\n torrent_id: null,\n session_id: sessionId,\n original_input: originalInput\n }\n });\n continue;\n }\n\n if (!torrentInfo) {\n outputs.push({\n json: {\n status: 'failed',\n error: 'Could not extract torrent info from response',\n torrent_id: null,\n session_id: sessionId,\n original_input: originalInput\n }\n });\n continue;\n }\n\n outputs.push({\n json: {\n status: 'queued',\n torrent_id: torrentInfo.id,\n torrent_hash: torrentInfo.hashString,\n torrent_name: torrentInfo.name,\n session_id: sessionId,\n original_input: originalInput\n }\n });\n}\n\nreturn outputs;" } }, { "position": [ 2240, 128 ], "type": "n8n-nodes-base.if", "typeVersion": 2.2, "id": "if-filter-success", "name": "Filter Success", "parameters": { "conditions": { "conditions": [ { "id": "filter-success", "leftValue": "={{ $json.status }}", "operator": { "operation": "equals", "type": "string" }, "rightValue": "queued" } ], "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }, "combinator": "and" } } }, { "name": "Respond Complete", "parameters": { "respondWith": "allIncomingItems", "options": {} }, "position": [ 2688, 128 ], "type": "n8n-nodes-base.respondToWebhook", "typeVersion": 1, "id": "respond-complete" }, { "name": "Merge Session to Files", "parameters": { "language": "pythonNative", "pythonCode": "# 1. Access the input item\nitem = _items[0]['json']\n\n# 2. Extract the data we need\n# UPDATE: The input key is now 'dl_link', not 'download_link'\ndownload_link = item.get('dl_link')\nsession_id = item.get('session_id')\n\n# 3. Create the Transmission Payload\npayload = {\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"filename\": download_link,\n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": True \n }\n}\n\n# 4. Return the formatted data\nreturn [{\n \"json\": {\n # UPDATE: Map 'title' correctly for any old nodes that need it\n \"title\": item.get('meta_title'),\n \n # NEW: Pass through all metadata for the Postgres Logger\n \"meta_title\": item.get('meta_title'),\n \"meta_series\": item.get('meta_series'),\n \"meta_book_number\": item.get('meta_book_number'),\n \"meta_author\": item.get('meta_author'),\n \"mam_id\": item.get('mam_id'),\n \"dl_link\": download_link,\n \n # Transmission Data\n \"transmission_body\": payload,\n \"headers\": {\n \"X-Transmission-Session-Id\": session_id\n }\n }\n}]" }, "position": [ 896, 128 ], "type": "n8n-nodes-base.code", "typeVersion": 2, "id": "d6d9f145-6b10-452f-aa08-2dc669c3b4d6" }, { "id": "dde23141-d708-48e7-942f-08aa1d749189", "name": "Merge Session Context", "parameters": { "options": {}, "combineBy": "combineByPosition", "mode": "combine" }, "position": [ 672, 128 ], "type": "n8n-nodes-base.merge", "typeVersion": 3.2 }, { "id": "acb7d649-a853-44e7-84e8-f82398f3008a", "name": "MAM Get File HTTP Request", "parameters": { "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "options": { "response": { "response": { "fullResponse": true, "responseFormat": "file" } } }, "url": "={{ $json.transmission_body.arguments.filename }}" }, "position": [ 1120, 128 ], "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "credentials": { "httpBasicAuth": { "id": "iymUPilnVhfL3h5D", "name": "transmission" }, "httpHeaderAuth": { "id": "G8eA8XeS9P5axwJd", "name": "mam cookie auth header" } } }, { "typeVersion": 1.1, "id": "acb1b56b-ac9a-41ec-ad60-7ce983b8cdd7", "name": "Extract from File", "parameters": { "operation": "binaryToPropery", "options": {} }, "position": [ 1568, 128 ], "type": "n8n-nodes-base.extractFromFile" }, { "position": [ 1344, 128 ], "type": "n8n-nodes-base.code", "typeVersion": 2, "id": "0b011613-50e9-4612-ac6e-1f6ac0e39ebb", "name": "Prepare Transmission Payload", "parameters": { "pythonCode": "results = []\n\nfor item in _items:\n # 1. Get the binary data from the previous download node\n # n8n stores the file in the 'binary' key. The default property name is usually 'data'.\n # We need the 'data' field inside that, which contains the Base64 string.\n binary_ref = item.get('binary', {}).get('data', {})\n b64_string = binary_ref.get('data')\n\n if b64_string:\n # 2. Create the exact JSON body Transmission expects for file uploads\n # We use \"metainfo\" for the actual file content (Base64)\n payload = {\n \"method\": \"torrent-add\",\n \"arguments\": {\n \"metainfo\": b64_string, \n \"download-dir\": \"/home/seed_/.transmission/downloads/audio\",\n \"paused\": False\n }\n }\n\n # 3. Preserve any existing JSON (like session_id) if it exists, or start fresh\n new_json = item.get('json', {}).copy()\n new_json['transmission_body'] = payload\n \n results.append({\n \"json\": new_json,\n # Pass the binary through just in case, though we don't strictly need it anymore\n \"binary\": item.get('binary', {})\n })\n\nreturn results", "language": "pythonNative" } }, { "parameters": { "workflowId": { "mode": "list", "value": "NChjOd3ILEmt0FdyAt8qA", "__rl": true, "cachedResultName": "Transmission: Get Session", "cachedResultUrl": "/workflow/NChjOd3ILEmt0FdyAt8qA" }, "workflowInputs": { "matchingColumns": [ "server_id" ], "schema": [ { "defaultMatch": false, "display": true, "displayName": "server_id", "id": "server_id", "removed": false, "required": false, "type": "string", "canBeUsedToMatch": true } ], "value": { "server_id": "seed-1.dfw.ben.io" }, "attemptToConvertTypes": false, "convertFieldsToString": true, "mappingMode": "defineBelow" }, "options": {} }, "position": [ 448, 208 ], "type": "n8n-nodes-base.executeWorkflow", "typeVersion": 1.3, "id": "175e270a-b1d9-41d0-981a-1f83258443b1", "name": "Transmission: Get Session" }, { "id": "5a79163a-a211-475c-b53e-79d086a4c9a7", "name": "Insert rows in a table", "parameters": { "schema": { "__rl": true, "mode": "list", "value": "public" }, "table": { "cachedResultName": "download_history", "mode": "list", "value": "download_history", "__rl": true }, "columns": { "value": { "torrent_hash": "={{ $json.torrent_hash }}", "filename": "={{ $json.torrent_name }}", "meta_author": "={{ $json.original_input.meta_author }}", "meta_title": "={{ $json.original_input.meta_title }}", "status": "={{ $json.status }}", "book_id": "={{ null }}", "mam_id": "={{ $json.original_input.mam_id }}", "meta_series": "={{ $json.original_input.meta_series }}", "torrent_url": "={{ $json.original_input.dl_link }}", "meta_book_number": "={{ $json.original_input.meta_book_number }}" }, "attemptToConvertTypes": false, "convertFieldsToString": false, "mappingMode": "defineBelow", "matchingColumns": [ "id" ], "schema": [ { "defaultMatch": true, "display": true, "displayName": "id", "id": "id", "removed": false, "required": false, "type": "string", "canBeUsedToMatch": true }, { "display": true, "displayName": "book_id", "id": "book_id", "required": false, "type": "string", "canBeUsedToMatch": true, "defaultMatch": false }, { "id": "torrent_url", "required": true, "type": "string", "canBeUsedToMatch": true, "defaultMatch": false, "display": true, "displayName": "torrent_url" }, { "defaultMatch": false, "display": true, "displayName": "torrent_hash", "id": "torrent_hash", "required": true, "type": "string", "canBeUsedToMatch": true }, { "defaultMatch": false, "display": true, "displayName": "filename", "id": "filename", "required": true, "type": "string", "canBeUsedToMatch": true }, { "id": "transmission_path", "required": false, "type": "string", "canBeUsedToMatch": true, "defaultMatch": false, "display": true, "displayName": "transmission_path" }, { "display": true, "displayName": "gateway_path", "id": "gateway_path", "required": false, "type": "string", "canBeUsedToMatch": true, "defaultMatch": false }, { "defaultMatch": false, "display": true, "displayName": "status", "id": "status", "required": false, "type": "string", "canBeUsedToMatch": true }, { "defaultMatch": false, "display": true, "displayName": "downloaded_at", "id": "downloaded_at", "required": false, "type": "dateTime", "canBeUsedToMatch": true }, { "required": false, "type": "dateTime", "canBeUsedToMatch": true, "defaultMatch": false, "display": true, "displayName": "created_at", "id": "created_at" }, { "id": "updated_at", "required": false, "type": "dateTime", "canBeUsedToMatch": true, "defaultMatch": false, "display": true, "displayName": "updated_at" }, { "type": "string", "canBeUsedToMatch": true, "defaultMatch": false, "display": true, "displayName": "meta_title", "id": "meta_title", "required": false }, { "canBeUsedToMatch": true, "defaultMatch": false, "display": true, "displayName": "meta_series", "id": "meta_series", "required": false, "type": "string" }, { "type": "string", "canBeUsedToMatch": true, "defaultMatch": false, "display": true, "displayName": "meta_book_number", "id": "meta_book_number", "required": false }, { "canBeUsedToMatch": true, "defaultMatch": false, "display": true, "displayName": "meta_author", "id": "meta_author", "required": false, "type": "string" }, { "displayName": "mam_id", "id": "mam_id", "required": false, "type": "number", "canBeUsedToMatch": true, "defaultMatch": false, "display": true }, { "canBeUsedToMatch": true, "defaultMatch": false, "display": true, "displayName": "error_log", "id": "error_log", "required": false, "type": "string" } ] }, "options": {} }, "position": [ 2464, 48 ], "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "credentials": { "postgres": { "id": "9grzZwW7Br6SzdV8", "name": "n8n-media" } } } ], "connections": { "Filter Success": { "main": [ [ { "node": "Insert rows in a table", "type": "main", "index": 0 } ], [ { "type": "main", "index": 0, "node": "Respond Complete" } ] ] }, "MAM Get File HTTP Request": { "main": [ [ { "type": "main", "index": 0, "node": "Prepare Transmission Payload" } ] ] }, "Add Torrent to Transmission": { "main": [ [ { "index": 0, "node": "Extract Torrent ID", "type": "main" } ] ] }, "Extract from File": { "main": [ [ { "node": "Add Torrent to Transmission", "type": "main", "index": 0 } ] ] }, "Merge Session to Files": { "main": [ [ { "index": 0, "node": "MAM Get File HTTP Request", "type": "main" } ] ] }, "Start": { "main": [ [ { "index": 0, "node": "Transmission: Get Session", "type": "main" }, { "index": 0, "node": "Merge Session Context", "type": "main" } ] ] }, "Prepare Transmission Payload": { "main": [ [ { "type": "main", "index": 0, "node": "Extract from File" } ] ] }, "Insert rows in a table": { "main": [ [ { "type": "main", "index": 0, "node": "Respond Complete" } ] ] }, "Transmission: Get Session": { "main": [ [ { "node": "Merge Session Context", "type": "main", "index": 1 } ] ] }, "Merge Session Context": { "main": [ [ { "type": "main", "index": 0, "node": "Merge Session to Files" } ] ] }, "Extract Torrent ID": { "main": [ [ { "type": "main", "index": 0, "node": "Filter Success" } ] ] } }, "settings": { "callerPolicy": "workflowsFromSameOwner", "availableInMCP": false }, "triggerCount": 0, "versionId": "1e834370-1769-4d3d-a487-f84a442b2dcc", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", "projectName": "Ben W ", "personalEmail": "admin@ben.io" }, "parentFolderId": "6tDyZCwqELStb6Ik", "isArchived": false }