{ "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 = 36 * 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": "download_history", "mode": "list", "cachedResultName": "download_history" }, "where": { "values": [ { "column": "torrent_hash", "value": "={{ $json.hashString }}" } ] }, "options": {} }, "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ 336, -16 ], "id": "0fa625e2-3e86-4abe-a837-f876d2d08f10", "name": "Check download_history", "credentials": { "postgres": { "id": "9grzZwW7Br6SzdV8", "name": "n8n-media" } } }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 3 }, "conditions": [ { "id": "2c8975f2-e286-4128-a04b-855242c68454", "leftValue": "={{ $json.book_id }}", "rightValue": "null", "operator": { "type": "string", "operation": "notEquals" } } ], "combinator": "and" }, "options": {} }, "type": "n8n-nodes-base.if", "typeVersion": 2.3, "position": [ 544, -16 ], "id": "319bd909-34d0-4ba0-be4f-9cb7266702d1", "name": "check book_id" }, { "parameters": { "operation": "upsert", "schema": { "__rl": true, "mode": "list", "value": "public" }, "table": { "__rl": true, "value": "smb_general_books", "mode": "list", "cachedResultName": "smb_general_books" }, "columns": { "mappingMode": "defineBelow", "value": { "identified": true, "needs_review": false, "book_folder": "={{ $json.meta_title }}", "smb_path": "={{\n$json.category == 'anime'\n? '/mnt/nas/Anime/Audiobooks/' + $json.meta_series + '/' + $json.meta_title + '.m4b'\n: '/mnt/nas/audiobooks/Main/' + $json.meta_author + '/' + $json.meta_series + '/' + $json.meta_title + '.m4b'\n}}", "author_folder": "={{ $json.meta_author }}", "book_name": "={{ $json.meta_title }}", "series_name": "={{ $json.meta_series }}", "audible_asin": "={{ null }}", "category": "={{\n$json.category == 'anime'\n? 'anime'\n: 'general'\n}}", "volume_num": "={{ $json.meta_book_number }}" }, "matchingColumns": [ "smb_path" ], "schema": [ { "id": "id", "displayName": "id", "required": false, "defaultMatch": true, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "smb_path", "displayName": "smb_path", "required": true, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "book_folder", "displayName": "book_folder", "required": true, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": false }, { "id": "author_folder", "displayName": "author_folder", "required": true, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": false }, { "id": "book_name", "displayName": "book_name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": false }, { "id": "series_name", "displayName": "series_name", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": false }, { "id": "audible_asin", "displayName": "audible_asin", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": false }, { "id": "identified", "displayName": "identified", "required": false, "defaultMatch": false, "display": true, "type": "boolean", "canBeUsedToMatch": false }, { "id": "needs_review", "displayName": "needs_review", "required": false, "defaultMatch": false, "display": true, "type": "boolean", "canBeUsedToMatch": false }, { "id": "retry_stage", "displayName": "retry_stage", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": false }, { "id": "failure_reason", "displayName": "failure_reason", "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": "volume_num", "displayName": "volume_num", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": false }, { "id": "created_at", "displayName": "created_at", "required": false, "defaultMatch": false, "display": true, "type": "dateTime", "canBeUsedToMatch": false }, { "id": "updated_at", "displayName": "updated_at", "required": false, "defaultMatch": false, "display": true, "type": "dateTime", "canBeUsedToMatch": false } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ 848, -32 ], "id": "473a1d96-9c64-4eee-81a0-3544cb7e10e3", "name": "Insert or update rows in a table", "credentials": { "postgres": { "id": "9grzZwW7Br6SzdV8", "name": "n8n-media" } } }, { "parameters": { "operation": "update", "schema": { "__rl": true, "mode": "list", "value": "public" }, "table": { "__rl": true, "value": "download_history", "mode": "list", "cachedResultName": "download_history" }, "columns": { "mappingMode": "defineBelow", "value": { "torrent_hash": "={{ $('Check download_history').item.json.torrent_hash }}", "book_id": "={{ $json.id }}" }, "matchingColumns": [ "torrent_hash" ], "schema": [ { "id": "id", "displayName": "id", "required": false, "defaultMatch": true, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "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, "removed": true }, { "id": "torrent_hash", "displayName": "torrent_hash", "required": true, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": false }, { "id": "filename", "displayName": "filename", "required": true, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "transmission_path", "displayName": "transmission_path", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "gateway_path", "displayName": "gateway_path", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "status", "displayName": "status", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "downloaded_at", "displayName": "downloaded_at", "required": false, "defaultMatch": false, "display": true, "type": "dateTime", "canBeUsedToMatch": true, "removed": true }, { "id": "created_at", "displayName": "created_at", "required": false, "defaultMatch": false, "display": true, "type": "dateTime", "canBeUsedToMatch": true, "removed": true }, { "id": "updated_at", "displayName": "updated_at", "required": false, "defaultMatch": false, "display": true, "type": "dateTime", "canBeUsedToMatch": true, "removed": false }, { "id": "meta_title", "displayName": "meta_title", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "meta_series", "displayName": "meta_series", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "meta_book_number", "displayName": "meta_book_number", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "meta_author", "displayName": "meta_author", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true }, { "id": "mam_id", "displayName": "mam_id", "required": false, "defaultMatch": false, "display": true, "type": "number", "canBeUsedToMatch": true, "removed": true }, { "id": "error_log", "displayName": "error_log", "required": false, "defaultMatch": false, "display": true, "type": "string", "canBeUsedToMatch": true, "removed": true } ], "attemptToConvertTypes": false, "convertFieldsToString": false }, "options": {} }, "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ 1056, -32 ], "id": "973faa87-a40b-4092-800e-751e22002bc8", "name": "Link Download", "credentials": { "postgres": { "id": "9grzZwW7Br6SzdV8", "name": "n8n-media" } } }, { "parameters": { "mode": "runOnceForEachItem", "language": "pythonNative", "pythonCode": "# MODE: Run Once for Each Item\n# No imports needed\n\n# 1. Access the specific item for this run using '_item' (with underscore)\ndata = _item['json']\n\n# 2. Get Paths\nfull_dest_path = data.get('smb_path')\n# Try 'downloadDir' or fallback to 'transmission_path'\nsource_dir = data.get('downloadDir') or data.get('transmission_path')\n# Try 'name' or fallback to 'filename'\nsource_name = data.get('name') or data.get('filename')\n\n# Initialize defaults\nfull_source_path = None\ndest_folder = ''\ndest_filename = ''\n\nif full_dest_path and (source_dir or source_name):\n # Join Source Path\n if source_dir and source_name:\n full_source_path = f\"{source_dir.rstrip('/')}/{source_name.lstrip('/')}\"\n else:\n full_source_path = source_name \n\n # Split Destination Path\n if '/' in full_dest_path:\n dest_folder, dest_filename = full_dest_path.rsplit('/', 1)\n else:\n dest_filename = full_dest_path\n\n# Return the single dictionary for this item\nreturn {\n \"json\": {\n \"source_path\": full_source_path,\n \"destination_folder\": dest_folder,\n \"destination_filename\": dest_filename,\n \"full_destination_path\": full_dest_path,\n \"book_id\": data.get('book_id')\n }\n}" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1488, -32 ], "id": "01001046-ee17-4e6d-9977-46de26e530e4", "name": "File Name Structure", "executeOnce": false }, { "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 }, "mode": "each", "options": {} }, "type": "n8n-nodes-base.executeWorkflow", "typeVersion": 1.3, "position": [ 2064, -16 ], "id": "ecc05269-aaae-49e5-b029-c63a953f82f9", "name": "Call 'MAM Remote File Transfer'" }, { "parameters": { "mode": "combine", "advanced": true, "mergeByFields": { "values": [ { "field1": "book_id", "field2": "id" } ] }, "options": { "fuzzyCompare": true } }, "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ 1312, -32 ], "id": "b2aeacf6-b76d-4f33-a62b-93087da796e9", "name": "Merge DB Data" }, { "parameters": { "mode": "combine", "combineBy": "combineByPosition", "numberInputs": 3, "options": {} }, "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ 1760, -16 ], "id": "e5546816-5ff2-42c2-8352-e2abae7ea635", "name": "Merge" }, { "parameters": {}, "type": "n8n-nodes-base.noOp", "typeVersion": 1, "position": [ 1088, 240 ], "id": "eddd2620-8c48-49ff-9b45-dc02091698ce", "name": "No Operation, do nothing" } ], "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": [ [ { "node": "Check download_history", "type": "main", "index": 0 }, { "node": "No Operation, do nothing", "type": "main", "index": 0 } ] ] }, "Check download_history": { "main": [ [ { "node": "check book_id", "type": "main", "index": 0 } ] ] }, "check book_id": { "main": [ [ { "node": "Insert or update rows in a table", "type": "main", "index": 0 } ] ] }, "Insert or update rows in a table": { "main": [ [ { "node": "Link Download", "type": "main", "index": 0 }, { "node": "Merge DB Data", "type": "main", "index": 1 }, { "node": "Merge", "type": "main", "index": 1 } ] ] }, "Link Download": { "main": [ [ { "node": "Merge DB Data", "type": "main", "index": 0 } ] ] }, "File Name Structure": { "main": [ [ { "node": "Merge", "type": "main", "index": 0 } ] ] }, "Merge DB Data": { "main": [ [ { "node": "File Name Structure", "type": "main", "index": 0 } ] ] }, "No Operation, do nothing": { "main": [ [ { "node": "Merge", "type": "main", "index": 2 } ] ] }, "Merge": { "main": [ [ { "node": "Call 'MAM Remote File Transfer'", "type": "main", "index": 0 } ] ] } }, "settings": { "executionOrder": "v1", "availableInMCP": false }, "triggerCount": 0, "versionId": "4523fc65-8080-444f-a097-244dd7aee7d6", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", "projectName": "Ben W ", "personalEmail": "admin@ben.io" }, "parentFolderId": "6tDyZCwqELStb6Ik", "isArchived": false }