Files
n8n-backup-v2/workflows/aEtoKG8mIocULX5W.json
2026-01-02 22:48:32 +00:00

868 lines
27 KiB
JSON

{
"id": "aEtoKG8mIocULX5W",
"name": "Audiobook Torrents",
"nodes": [
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-736,
-48
],
"id": "286acfc0-180f-407a-aa4a-f76683e5e061",
"name": "When clicking 'Execute workflow'"
},
{
"parameters": {
"url": "https://nyaa.si/?page=rss&q=%5BAudiobook%5D&c=3_1&f=0",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-512,
48
],
"id": "c3a1b112-cd01-4a0e-b843-bd6c635202c6",
"name": "HTTP Request"
},
{
"parameters": {
"options": {
"trim": true
}
},
"type": "n8n-nodes-base.xml",
"typeVersion": 1,
"position": [
-288,
48
],
"id": "0239b8cc-c0b0-4bce-9892-176bf69ab8aa",
"name": "XML"
},
{
"parameters": {
"fieldToSplitOut": "rss.channel.item",
"options": {}
},
"id": "split-items-node",
"name": "Split Feed Items",
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1,
"position": [
-64,
48
]
},
{
"parameters": {
"jsCode": "// Process all input items and return them\nreturn $input.all().map(item => ({\n json: {\n info_hash: item.json['nyaa:infoHash'],\n title: item.json.title,\n link: item.json.link,\n view_link: item.json.guid._ || item.json.guid,\n pub_date: item.json.pubDate,\n seeders: parseInt(item.json['nyaa:seeders']) || 0,\n size: item.json['nyaa:size']\n }\n}));"
},
"id": "extract-torrent-data",
"name": "Extract Torrent Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
160,
48
],
"executeOnce": false
},
{
"parameters": {
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.seeders }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
]
},
"options": {}
},
"id": "filter-has-seeders",
"name": "Has Seeders?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
-592,
624
]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"id": "schedule-trigger-5min",
"name": "Every 5 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-736,
144
]
},
{
"parameters": {
"sendTo": "admin@ben.io",
"subject": "={{ $json.torrentCount }} New Audiobook Torrent(s) Found",
"message": "={{ $json.emailHtmlBody }}",
"options": {}
},
"id": "send-gmail-notification",
"name": "Send Gmail Notification",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
896,
528
],
"webhookId": "8da5c0a8-d147-46e8-bcc5-85d5cd2c87d5",
"credentials": {
"gmailOAuth2": {
"id": "Os1ux3h3zFlC2XkG",
"name": "Gmail account"
}
}
},
{
"parameters": {
"operation": "getAll",
"tableId": "nyaa_audiobooks",
"returnAll": true,
"filterType": "none"
},
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
-368,
704
],
"id": "ea585083-0b0a-410b-b0ac-768badf335fa",
"name": "Get many rows",
"executeOnce": true,
"credentials": {
"supabaseApi": {
"id": "lWyf2ikOGHTTwnSU",
"name": "Supabase account"
}
}
},
{
"parameters": {
"mergeByFields": {
"values": [
{
"field1": "info_hash",
"field2": "info_hash"
}
]
},
"options": {
"skipFields": "seeders,discovered_at,updated_at,mam_torrent_id,mam_checked_at,view_link,id,created_at,search_title,volume_range"
}
},
"type": "n8n-nodes-base.compareDatasets",
"typeVersion": 2.3,
"position": [
-144,
592
],
"id": "13fffea6-eb23-4746-9c21-e9695bd8c377",
"name": "Compare Datasets"
},
{
"parameters": {
"jsCode": "// Get all incoming items using $input.all()\nconst torrents = $input.all();\nconst count = torrents.length;\n\n// If no torrents, return empty to stop workflow\nif (count === 0) {\n return [];\n}\n\n// Helper function to get MAM status display\nfunction getMAMStatus(torrent) {\n if (torrent.mam_torrent_id) {\n return `✅ <a href=\"https://www.myanonamouse.net/t/${torrent.mam_torrent_id}\">Yes</a>`;\n } else {\n return '❌ No';\n }\n}\n\n// Build HTML table rows\nlet tableRows = \"\";\n\nfor (const item of torrents) {\n const torrent = item.json;\n \n tableRows += `\n <tr>\n <td><a href=\"${torrent.view_link}\">${torrent.title}</a></td>\n <td>${torrent.seeders}</td>\n <td>${torrent.size}</td>\n <td>${getMAMStatus(torrent)}</td>\n <td>\n <a href=\"${torrent.link}\">Download</a> | \n <a href=\"${torrent.view_link}\">View</a>\n </td>\n </tr>\n `;\n}\n\n// Build complete HTML email body\nconst emailBody = `\n<h2>${count} New Audiobook Torrent(s) Found</h2>\n<table border=\"1\" cellpadding=\"8\" cellspacing=\"0\" style=\"border-collapse: collapse;\">\n <thead>\n <tr>\n <th>Title</th>\n <th>Seeders</th>\n <th>Size</th>\n <th>On MAM?</th>\n <th>Links</th>\n </tr>\n </thead>\n <tbody>\n ${tableRows}\n </tbody>\n</table>\n`;\n\nreturn [{\n json: {\n torrentCount: count,\n emailHtmlBody: emailBody\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
672,
528
],
"id": "99f5e0ba-0c7e-4c37-9692-ff0c01ab48ae",
"name": "Code in JavaScript",
"executeOnce": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "condition1",
"leftValue": "={{ $input.all().length }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "gt"
}
}
]
},
"options": {}
},
"id": "check-has-new-torrents",
"name": "Has New Torrents?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
224,
528
]
},
{
"parameters": {
"tableId": "nyaa_audiobooks",
"dataToSend": "autoMapInputData",
"inputsToIgnore": "view_link"
},
"id": "insert-new-torrents",
"name": "Insert New Torrents",
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
448,
528
],
"credentials": {
"supabaseApi": {
"id": "lWyf2ikOGHTTwnSU",
"name": "Supabase account"
}
}
},
{
"parameters": {
"url": "https://www.myanonamouse.net/tor/js/loadSearchJSONbasic.php",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "tor[text]",
"value": "={{ $json.search_title }}"
},
{
"name": "tor[srchIn][title]",
"value": "true"
},
{
"name": "tor[searchType]",
"value": "all"
},
{
"name": "tor[main_cat][]",
"value": "13"
},
{
"name": "tor[sortType]",
"value": "default"
},
{
"name": "tor[startNumber]",
"value": "0"
},
{
"name": "perpage",
"value": "10"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json; charset=utf-8"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "search-mam-by-title",
"name": "Search MAM by Title",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1056,
48
],
"credentials": {
"httpHeaderAuth": {
"id": "sxQYMPEq6GLbc9Av",
"name": "n8n_auth_cookie"
}
}
},
{
"parameters": {
"jsCode": "// Parse MAM title search results with volume and format matching\n// Get the filtered torrents that actually went through MAM search\nconst searchedTorrents = $('Filter Unchecked Torrents').all();\nconst mamResults = $input.all();\n\n// Helper function to extract volume number from MAM title\nfunction extractVolume(title) {\n const patterns = [\n /[Vv]ol\\.?\\s*(\\d+)/,\n /[Vv]olume\\s*(\\d+)/,\n /\\bv(\\d+)\\b/,\n /[Bb]ook\\s*(\\d+)/,\n /,\\s*(\\d+)$/ // Match \", 1\" at end of title\n ];\n \n for (const pattern of patterns) {\n const match = title.match(pattern);\n if (match) {\n return parseInt(match[1]);\n }\n }\n return null;\n}\n\n// Helper function to check if volume is in range\nfunction isVolumeInRange(vol, range) {\n if (!range || vol === null) return false;\n return vol >= range.start && vol <= range.end;\n}\n\n// Helper function to check if it's m4b format\nfunction isM4bFormat(filetype) {\n if (!filetype) return false;\n return filetype.toLowerCase().includes('m4b');\n}\n\n// Process each item - match MAM results with the torrents that were searched\nreturn searchedTorrents.map((item, index) => {\n const mamResult = mamResults[index]?.json;\n const torrentData = item.json;\n \n let mamTorrentId = null;\n let mamFound = false;\n \n if (mamResult && mamResult.data && Array.isArray(mamResult.data) && mamResult.data.length > 0) {\n // Search through results for a matching volume and format\n for (const result of mamResult.data) {\n // Only match m4b format\n if (!isM4bFormat(result.filetype)) {\n continue;\n }\n \n // Extract volume from MAM title\n const mamVolume = extractVolume(result.title || '');\n \n // If Nyaa has volume info, match it\n if (torrentData.volume_range) {\n if (isVolumeInRange(mamVolume, torrentData.volume_range)) {\n mamTorrentId = result.id;\n mamFound = true;\n break;\n }\n } else {\n // No volume info in Nyaa title, take first m4b match\n mamTorrentId = result.id;\n mamFound = true;\n break;\n }\n }\n }\n \n return {\n json: {\n ...torrentData,\n mam_torrent_id: mamTorrentId,\n mam_found_by_title: mamFound,\n mam_checked_at: new Date().toISOString()\n }\n };\n});"
},
"id": "parse-mam-title-results",
"name": "Parse MAM Title Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1280,
48
],
"executeOnce": false
},
{
"parameters": {
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.mam_found_by_title }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
]
},
"options": {}
},
"id": "title-match-found",
"name": "Title Match Found?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
-496,
336
]
},
{
"parameters": {
"jsCode": "// Extract author/narrator from Nyaa title for fallback search\nreturn $input.all().map(item => {\n const title = item.json.title || '';\n let searchTerm = '';\n \n // Try to extract author/narrator from common patterns\n // Pattern 1: \"Author Name - Book Title\"\n const dashPattern = /^([^-]+)\\s*-\\s*.+$/;\n const dashMatch = title.match(dashPattern);\n if (dashMatch) {\n searchTerm = dashMatch[1].trim();\n }\n \n // Pattern 2: [Author Name]\n if (!searchTerm) {\n const bracketPattern = /\\[([^\\]]+)\\]/;\n const bracketMatch = title.match(bracketPattern);\n if (bracketMatch) {\n searchTerm = bracketMatch[1].trim();\n }\n }\n \n // Pattern 3: \"by Author Name\"\n if (!searchTerm) {\n const byPattern = /\\bby\\s+([^-,(]+)/i;\n const byMatch = title.match(byPattern);\n if (byMatch) {\n searchTerm = byMatch[1].trim();\n }\n }\n \n // Fallback: use first few words of title\n if (!searchTerm) {\n const words = title.split(/\\s+/).slice(0, 3);\n searchTerm = words.join(' ');\n }\n \n return {\n json: {\n ...item.json,\n search_term: searchTerm\n }\n };\n});"
},
"id": "extract-author-narrator",
"name": "Extract Author/Narrator",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-256,
320
],
"executeOnce": false
},
{
"parameters": {
"url": "https://www.myanonamouse.net/tor/js/loadSearchJSONbasic.php",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "tor[text]",
"value": "={{ $json.search_term }}"
},
{
"name": "tor[srchIn][author]",
"value": "true"
},
{
"name": "tor[srchIn][narrator]",
"value": "true"
},
{
"name": "tor[searchType]",
"value": "all"
},
{
"name": "tor[main_cat][]",
"value": "13"
},
{
"name": "tor[sortType]",
"value": "default"
},
{
"name": "tor[startNumber]",
"value": "0"
},
{
"name": "perpage",
"value": "10"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json; charset=utf-8"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"id": "search-mam-by-author",
"name": "Search MAM by Author/Narrator",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-48,
320
],
"credentials": {
"httpHeaderAuth": {
"id": "sxQYMPEq6GLbc9Av",
"name": "n8n_auth_cookie"
}
}
},
{
"parameters": {
"jsCode": "// Parse MAM author/narrator search results\n// Get the torrents that went through author/narrator search (filtered by title-not-found)\nconst searchedTorrents = $('Extract Author/Narrator').all();\nconst mamResults = $input.all();\n\n// Process each item\nreturn searchedTorrents.map((item, index) => {\n const mamResult = mamResults[index]?.json;\n const torrentData = item.json;\n \n // Check if MAM search returned results\n let mamTorrentId = torrentData.mam_torrent_id;\n \n // Only update if we didn't find by title\n if (!torrentData.mam_found_by_title && mamResult && mamResult.data && Array.isArray(mamResult.data) && mamResult.data.length > 0) {\n // Found at least one match - take the first result\n mamTorrentId = mamResult.data[0].id;\n }\n \n return {\n json: {\n ...torrentData,\n mam_torrent_id: mamTorrentId\n }\n };\n});"
},
"id": "parse-mam-author-results",
"name": "Parse MAM Author Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
160,
320
],
"executeOnce": false
},
{
"parameters": {
"jsCode": "// Merge data from both paths (title match and author/narrator search)\n// Note: Only one of these paths will execute depending on title match result\n\n// Get all input items from the current execution\nconst allResults = $input.all();\n\n// Clean up temporary fields used during MAM search\nreturn allResults.map(item => {\n const { mam_found_by_title, search_term, ...cleanData } = item.json;\n return {\n json: cleanData\n };\n});"
},
"id": "merge-mam-data",
"name": "Merge MAM Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
400,
320
],
"executeOnce": false
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"leftValue": "={{ $json.mam_torrent_id }}",
"rightValue": "",
"operator": {
"type": "number",
"operation": "notEmpty",
"singleValue": true
},
"id": "41cd060f-08bc-4b5c-8396-ac604893a67a"
}
],
"combinator": "and"
},
"options": {}
},
"id": "filter-needs-mam-update",
"name": "Needs MAM Update?",
"type": "n8n-nodes-base.filter",
"typeVersion": 2,
"position": [
224,
720
]
},
{
"parameters": {
"jsCode": "// Extract base title AND volume numbers for matching\nreturn $input.all().map(item => {\n let fullTitle = item.json.title || '';\n let baseTitle = fullTitle;\n let volumeInfo = null;\n \n // Extract volume information first (we'll need it for matching later)\n // Match patterns: Vol. 01-10, Vol 1-5, Volume 1, v01, Book 1, etc.\n const volPatterns = [\n /[Vv]ol\\.?\\s*(\\d+)(?:\\s*-\\s*(\\d+))?/,\n /[Vv]olume\\s*(\\d+)(?:\\s*-\\s*(\\d+))?/,\n /\\bv(\\d+)(?:-v?(\\d+))?\\b/,\n /[Bb]ook\\s*(\\d+)(?:\\s*-\\s*(\\d+))?/\n ];\n \n for (const pattern of volPatterns) {\n const match = fullTitle.match(pattern);\n if (match) {\n const startVol = parseInt(match[1]);\n const endVol = match[2] ? parseInt(match[2]) : startVol;\n volumeInfo = { start: startVol, end: endVol };\n break;\n }\n }\n \n // Remove all metadata to get base title\n baseTitle = baseTitle.replace(/\\s*[Vv]ol\\.?\\s*\\d+(-\\d+)?/gi, '');\n baseTitle = baseTitle.replace(/\\s*[Vv]olume\\s*\\d+(-\\d+)?/gi, '');\n baseTitle = baseTitle.replace(/\\s*\\bv\\d+(-v?\\d+)?\\b/gi, '');\n baseTitle = baseTitle.replace(/\\s*\\[[^\\]]+\\]/g, '');\n baseTitle = baseTitle.replace(/\\s*\\([^)]+\\)/g, '');\n baseTitle = baseTitle.replace(/\\s*[Bb]ook\\s*\\d+(-\\d+)?/gi, '');\n baseTitle = baseTitle.replace(/\\s*[Pp]art\\s*\\d+(-\\d+)?/gi, '');\n baseTitle = baseTitle.replace(/[,\\s-]+$/, '').trim();\n \n return {\n json: {\n ...item.json,\n search_title: baseTitle,\n volume_range: volumeInfo\n }\n };\n});"
},
"id": "extract-base-title",
"name": "Extract Base Title",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
384,
48
]
},
{
"parameters": {
"jsCode": "// Filter out torrents that already have MAM data\nconst nyaaTorrents = $('Extract Base Title').all();\nconst dbTorrents = $('Get DB Torrents for Filtering').all();\n\n// Create a map of info_hash -> mam_torrent_id from database\nconst checkedHashes = new Map();\nfor (const item of dbTorrents) {\n if (item.json.mam_torrent_id) {\n checkedHashes.set(item.json.info_hash, item.json.mam_torrent_id);\n }\n}\n\n// Filter Nyaa torrents to only include unchecked ones\nconst uncheckedTorrents = nyaaTorrents.filter(item => {\n return !checkedHashes.has(item.json.info_hash);\n});\n\nconsole.log(`Total torrents: ${nyaaTorrents.length}, Already checked: ${checkedHashes.size}, Need MAM search: ${uncheckedTorrents.length}`);\n\nreturn uncheckedTorrents;"
},
"id": "filter-unchecked-torrents",
"name": "Filter Unchecked Torrents",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
832,
48
]
},
{
"id": "Get DB Torrents for Filtering",
"name": "Get DB Torrents for Filtering (Postgres)",
"type": "n8n-nodes-base.postgres",
"typeVersion": 1,
"position": [
500,
100
],
"parameters": {
"operation": "executeQuery",
"query": "SELECT * FROM nyaa_audiobooks;"
},
"credentials": {
"postgres": {
"id": "9grzZwW7Br6SzdV8",
"name": "n8n-media"
}
}
},
{
"id": "Update Existing Torrents MAM Data",
"name": "Update Existing Torrents MAM Data (Postgres)",
"type": "n8n-nodes-base.postgres",
"typeVersion": 1,
"position": [
1500,
500
],
"parameters": {
"operation": "executeQuery",
"options": {
"queryParameterValues": "{{$json.mam_torrent_id}},{{$json.id}}"
},
"query": "UPDATE nyaa_audiobooks SET mam_torrent_id = $1, mam_checked_at = NOW(), updated_at = NOW() WHERE id = $2;"
},
"credentials": {
"postgres": {
"id": "9grzZwW7Br6SzdV8",
"name": "n8n-media"
}
}
}
],
"connections": {
"When clicking 'Execute workflow'": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "XML",
"type": "main",
"index": 0
}
]
]
},
"XML": {
"main": [
[
{
"node": "Split Feed Items",
"type": "main",
"index": 0
}
]
]
},
"Split Feed Items": {
"main": [
[
{
"node": "Extract Torrent Data",
"type": "main",
"index": 0
}
]
]
},
"Every 5 Minutes": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"Has Seeders?": {
"main": [
[
{
"node": "Get many rows",
"type": "main",
"index": 0
},
{
"node": "Compare Datasets",
"type": "main",
"index": 0
}
]
]
},
"Get many rows": {
"main": [
[
{
"node": "Compare Datasets",
"type": "main",
"index": 1
}
]
]
},
"Code in JavaScript": {
"main": [
[
{
"node": "Send Gmail Notification",
"type": "main",
"index": 0
}
]
]
},
"Compare Datasets": {
"main": [
[
{
"node": "Has New Torrents?",
"type": "main",
"index": 0
}
],
[
{
"node": "Needs MAM Update?",
"type": "main",
"index": 0
}
]
]
},
"Has New Torrents?": {
"main": [
[
{
"node": "Insert New Torrents",
"type": "main",
"index": 0
}
]
]
},
"Insert New Torrents": {
"main": [
[
{
"node": "Code in JavaScript",
"type": "main",
"index": 0
}
]
]
},
"Search MAM by Title": {
"main": [
[
{
"node": "Parse MAM Title Results",
"type": "main",
"index": 0
}
]
]
},
"Parse MAM Title Results": {
"main": [
[
{
"node": "Title Match Found?",
"type": "main",
"index": 0
}
]
]
},
"Title Match Found?": {
"main": [
[
{
"node": "Extract Author/Narrator",
"type": "main",
"index": 0
},
{
"node": "Merge MAM Data",
"type": "main",
"index": 0
}
]
]
},
"Extract Author/Narrator": {
"main": [
[
{
"node": "Search MAM by Author/Narrator",
"type": "main",
"index": 0
}
]
]
},
"Search MAM by Author/Narrator": {
"main": [
[
{
"node": "Parse MAM Author Results",
"type": "main",
"index": 0
}
]
]
},
"Parse MAM Author Results": {
"main": [
[
{
"node": "Merge MAM Data",
"type": "main",
"index": 0
}
]
]
},
"Merge MAM Data": {
"main": [
[
{
"node": "Has Seeders?",
"type": "main",
"index": 0
}
]
]
},
"Extract Torrent Data": {
"main": [
[
{
"node": "Extract Base Title",
"type": "main",
"index": 0
}
]
]
},
"Filter Unchecked Torrents": {
"main": [
[
{
"node": "Search MAM by Title",
"type": "main",
"index": 0
}
]
]
},
"Extract Base Title": {
"main": [
[
{
"node": "Get DB Torrents for Filtering (Postgres)",
"type": "main",
"index": 0
}
]
]
},
"Get DB Torrents for Filtering (Postgres)": {
"main": [
[
{
"node": "Filter Unchecked Torrents",
"type": "main",
"index": 0
}
]
]
},
"Needs MAM Update?": {
"main": [
[
{
"node": "Update Existing Torrents MAM Data (Postgres)",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"triggerCount": 1,
"versionId": "5a1020ae-9654-4801-8ddc-48d87af2b558",
"owner": {
"type": "personal",
"projectId": "FeLO36wNUAcn61Wj",
"projectName": "Ben W <admin@ben.io>",
"personalEmail": "admin@ben.io"
},
"parentFolderId": "kUg4HIPXraph3M0E",
"isArchived": false
}