{ "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 ", "personalEmail": "admin@ben.io" }, "parentFolderId": "kUg4HIPXraph3M0E", "isArchived": false }