diff --git a/tags.json b/tags.json index 1d9e68e..ff05575 100644 --- a/tags.json +++ b/tags.json @@ -74,10 +74,6 @@ "workflowId": "NChjOd3ILEmt0FdyAt8qA", "tagId": "FydpKYmttDwoZVAA" }, - { - "workflowId": "kRZyX9H2uDHHncpE", - "tagId": "FydpKYmttDwoZVAA" - }, { "workflowId": "xXUnt2hL2FKxzOhBnkd3Z", "tagId": "FydpKYmttDwoZVAA" @@ -85,6 +81,10 @@ { "workflowId": "xXUnt2hL2FKxzOhBnkd3Z", "tagId": "ct0Rtzpu15B497av" + }, + { + "workflowId": "kRZyX9H2uDHHncpE", + "tagId": "FydpKYmttDwoZVAA" } ] } \ No newline at end of file diff --git a/workflows/kRZyX9H2uDHHncpE.json b/workflows/kRZyX9H2uDHHncpE.json index d1b12d4..23b0c63 100644 --- a/workflows/kRZyX9H2uDHHncpE.json +++ b/workflows/kRZyX9H2uDHHncpE.json @@ -210,7 +210,7 @@ "typeVersion": 1.3, "position": [ 448, - 200 + 208 ], "id": "175e270a-b1d9-41d0-981a-1f83258443b1", "name": "Transmission: Get Session" @@ -541,7 +541,7 @@ }, "settings": {}, "triggerCount": 0, - "versionId": "509f1eb5-35d3-4d2a-829d-573deddc24a7", + "versionId": "ea21c736-ae26-492b-aa4d-f1096fa4af15", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", diff --git a/workflows/kn1gehxiWbkRfDFFAKx0x.json b/workflows/kn1gehxiWbkRfDFFAKx0x.json index 6bc2eaf..d265c77 100644 --- a/workflows/kn1gehxiWbkRfDFFAKx0x.json +++ b/workflows/kn1gehxiWbkRfDFFAKx0x.json @@ -102,7 +102,7 @@ { "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" + "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, @@ -124,13 +124,16 @@ }, "table": { "__rl": true, - "value": "smb_general_books", + "value": "download_history", "mode": "list", - "cachedResultName": "smb_general_books" + "cachedResultName": "download_history" }, "where": { "values": [ - {} + { + "column": "torrent_hash", + "value": "={{ $json.hashString }}" + } ] }, "options": {} @@ -138,17 +141,551 @@ "type": "n8n-nodes-base.postgres", "typeVersion": 2.6, "position": [ - 384, - -112 + 336, + -16 ], "id": "0fa625e2-3e86-4abe-a837-f876d2d08f10", - "name": "Select rows from a table", + "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": { @@ -187,7 +724,116 @@ }, "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 + } + ] ] } }, @@ -196,7 +842,7 @@ "availableInMCP": false }, "triggerCount": 0, - "versionId": "26ae4972-43fe-4776-b2ed-af9f7c88aa42", + "versionId": "4523fc65-8080-444f-a097-244dd7aee7d6", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj",