This workflow follows the Google Sheets → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"nodes": [
{
"parameters": {
"content": "## Telegram Command Interface\n\n**Purpose:** Command-line style interface via Telegram for monitoring the home lab automation system.\n\n**Commands:**\n- `/help` - List available commands\n- `/status` - Show recent execution status\n- `/failures` - List failures from FailedItems sheet\n- `/retry <execution_id>` - Retry a failed execution\n- `/search <query>` - Search invoices by supplier\n\n**Security:** Chat ID whitelist check before processing commands.\n\n**Message Handling:** Long responses (>4000 chars) are automatically split into multiple Telegram messages with part numbers.",
"height": 360,
"width": 400
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-100,
-200
],
"id": "overview-sticky-note",
"name": "Overview"
},
{
"parameters": {
"updates": [
"message"
],
"additionalFields": {}
},
"type": "n8n-nodes-base.telegramTrigger",
"typeVersion": 1.2,
"position": [
-100,
200
],
"id": "telegram-trigger-node",
"name": "Telegram Trigger",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "whitelist-check-condition",
"leftValue": "={{ ['YOUR_CHAT_ID_1', 'YOUR_CHAT_ID_2'].includes(String($json.message.chat.id)) }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
120,
200
],
"id": "whitelist-check-node",
"name": "Whitelist Check"
},
{
"parameters": {
"jsCode": "// Parse Command\n// Extracts command name and arguments from Telegram message\n\nconst text = ($json.message.text || '').trim();\nconst chatId = String($json.message.chat.id);\n\n// Match /command or /command args\nconst match = text.match(/^\\/([\\w]+)(?:\\s+(.*))?$/);\n\nif (match) {\n return [{\n json: {\n chatId: chatId,\n command: match[1].toLowerCase(),\n args: (match[2] || '').trim(),\n rawText: text\n }\n }];\n}\n\n// Not a command - return fallback\nreturn [{\n json: {\n chatId: chatId,\n command: '_not_a_command',\n args: '',\n rawText: text\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
340,
200
],
"id": "parse-command-node",
"name": "Parse Command"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.command }}",
"rightValue": "help",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "help"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.command }}",
"rightValue": "status",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "status"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.command }}",
"rightValue": "failures",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "failures"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.command }}",
"rightValue": "retry",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "retry"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"leftValue": "={{ $json.command }}",
"rightValue": "search",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "search"
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.2,
"position": [
560,
200
],
"id": "command-router-node",
"name": "Command Router"
},
{
"parameters": {
"jsCode": "// Format Help Message\nconst chatId = $json.chatId;\n\nconst helpText = `*Available Commands*\n\n/help - Show this help message\n/status - Show recent workflow executions\n/failures - List pending failures from FailedItems\n/retry <id> - Retry a failed execution by ID\n/search <query> - Search invoices by supplier name\n\n_Examples:_\n\\`/status\\`\n\\`/retry 36001\\`\n\\`/search Acme\\``;\n\nreturn [{\n json: {\n chatId: chatId,\n response: helpText\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
800,
-80
],
"id": "help-handler-node",
"name": "Format Help"
},
{
"parameters": {
"method": "GET",
"url": "https://YOUR_N8N_INSTANCE.up.railway.app/api/v1/executions",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "limit",
"value": "10"
}
]
},
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
800,
80
],
"id": "status-api-node",
"name": "Get Executions",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Format Status Response\nconst chatId = $('Parse Command').item.json.chatId;\nconst apiResponse = $json;\n\n// Handle API errors\nif (apiResponse.message && apiResponse.code) {\n return [{\n json: {\n chatId: chatId,\n response: `Error fetching executions: ${apiResponse.message}`\n }\n }];\n}\n\nconst executions = apiResponse.data || [];\n\nif (executions.length === 0) {\n return [{\n json: {\n chatId: chatId,\n response: 'No recent executions found.'\n }\n }];\n}\n\n// Status emoji mapping\nconst statusEmoji = {\n success: '\\u2705', // Green check\n error: '\\u274C', // Red X\n running: '\\u23F3', // Hourglass\n waiting: '\\u23F8', // Pause\n canceled: '\\u26D4' // No entry\n};\n\nlet lines = ['*Recent Executions*', ''];\n\nfor (const exec of executions.slice(0, 10)) {\n const emoji = statusEmoji[exec.status] || '\\u2753';\n const workflow = exec.workflowData?.name || exec.workflowId || 'Unknown';\n const id = exec.id;\n const status = exec.status;\n const startedAt = exec.startedAt ? new Date(exec.startedAt).toLocaleString() : 'N/A';\n \n lines.push(`${emoji} \\`${id}\\` ${workflow}`);\n lines.push(` ${status} | ${startedAt}`);\n}\n\nreturn [{\n json: {\n chatId: chatId,\n response: lines.join('\\n')\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1020,
80
],
"id": "format-status-node",
"name": "Format Status"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "YOUR_FAILEDITEMS_SHEET_ID",
"mode": "list",
"cachedResultName": "007_Error-handler.n8n-sheet",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_FAILEDITEMS_SHEET_ID/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "FailedItems",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_FAILEDITEMS_SHEET_ID/edit#gid=0"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
800,
240
],
"id": "read-failures-node",
"name": "Read FailedItems",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Format Failures Response\nconst chatId = $('Parse Command').item.json.chatId;\nconst items = $input.all();\n\nif (!items || items.length === 0) {\n return [{\n json: {\n chatId: chatId,\n response: 'No failures in FailedItems sheet.'\n }\n }];\n}\n\n// Filter for pending_retry and escalated only\nconst failures = items\n .map(i => i.json)\n .filter(f => f.status === 'pending_retry' || f.status === 'escalated')\n .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))\n .slice(0, 10);\n\nif (failures.length === 0) {\n return [{\n json: {\n chatId: chatId,\n response: 'No pending failures.'\n }\n }];\n}\n\n// Severity emoji mapping\nconst severityEmoji = {\n critical: '\\uD83D\\uDD34',\n high: '\\uD83D\\uDFE0',\n medium: '\\uD83D\\uDFE1',\n low: '\\uD83D\\uDFE2'\n};\n\nlet lines = ['*Pending Failures*', ''];\n\nfor (const f of failures) {\n const emoji = severityEmoji[f.severity] || '\\u26AA';\n const escalatedMarker = f.status === 'escalated' ? ' \\u26A0\\uFE0F ESCALATED' : '';\n \n lines.push(`${emoji} \\`${f.execution_id}\\`${escalatedMarker}`);\n lines.push(` ${f.workflow_name || 'Unknown workflow'}`);\n lines.push(` ${f.error_type}: ${(f.error_message || '').substring(0, 60)}...`);\n lines.push(` Retries: ${f.retry_count || 0} | ${f.timestamp || 'N/A'}`);\n lines.push('');\n}\n\nreturn [{\n json: {\n chatId: chatId,\n response: lines.join('\\n')\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1020,
240
],
"id": "format-failures-node",
"name": "Format Failures"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "has-exec-id-condition",
"leftValue": "={{ $json.args }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
800,
400
],
"id": "retry-has-id-check",
"name": "Has Execution ID?"
},
{
"parameters": {
"jsCode": "// Missing execution ID error\nreturn [{\n json: {\n chatId: $json.chatId,\n response: 'Usage: /retry <execution_id>\\n\\nExample: /retry 36001'\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1020,
480
],
"id": "retry-missing-id-node",
"name": "Retry Missing ID"
},
{
"parameters": {
"method": "POST",
"url": "=https://YOUR_N8N_INSTANCE.up.railway.app/api/v1/executions/{{ $json.args }}/retry",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1020,
360
],
"id": "execute-retry-node",
"name": "Execute Retry",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Format Retry Response\nconst chatId = $('Parse Command').item.json.chatId;\nconst originalId = $('Parse Command').item.json.args;\nconst apiResponse = $json;\n\n// Check if retry started successfully\nif (apiResponse.id) {\n return [{\n json: {\n chatId: chatId,\n response: `\\u2705 Retry started!\\n\\nOriginal: \\`${originalId}\\`\\nNew execution: \\`${apiResponse.id}\\`\\nStatus: ${apiResponse.status}\\n\\n_Use /status to check progress_`\n }\n }];\n}\n\n// Error case\nconst errorMsg = apiResponse.message || 'Unknown error';\nreturn [{\n json: {\n chatId: chatId,\n response: `\\u274C Retry failed for \\`${originalId}\\`\\n\\nError: ${errorMsg}`\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1240,
360
],
"id": "format-retry-node",
"name": "Format Retry"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "has-search-query-condition",
"leftValue": "={{ $json.args }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
800,
560
],
"id": "search-has-query-check",
"name": "Has Search Query?"
},
{
"parameters": {
"jsCode": "// Missing search query error\nreturn [{\n json: {\n chatId: $json.chatId,\n response: 'Usage: /search <supplier_name>\\n\\nExample: /search Acme'\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1020,
640
],
"id": "search-missing-query-node",
"name": "Search Missing Query"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "YOUR_BILLING_LEDGER_SHEET_ID",
"mode": "list",
"cachedResultName": "Billing_Ledger",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_BILLING_LEDGER_SHEET_ID/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_BILLING_LEDGER_SHEET_ID/edit#gid=0"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
1020,
520
],
"id": "read-invoices-node",
"name": "Read Invoices",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Format Search Response\nconst chatId = $('Parse Command').item.json.chatId;\nconst query = $('Parse Command').item.json.args.toLowerCase();\nconst items = $input.all();\n\nif (!items || items.length === 0) {\n return [{\n json: {\n chatId: chatId,\n response: 'No invoices found in database.'\n }\n }];\n}\n\n// Filter by supplier_name (case-insensitive partial match)\nconst matches = items\n .map(i => i.json)\n .filter(inv => {\n const supplier = (inv.supplier_name || '').toLowerCase();\n return supplier.includes(query);\n })\n .slice(0, 10);\n\nif (matches.length === 0) {\n return [{\n json: {\n chatId: chatId,\n response: `No invoices found for \"${query}\".`\n }\n }];\n}\n\nlet lines = [`*Search Results for \"${query}\"*`, ''];\n\nfor (const inv of matches) {\n lines.push(`\\uD83D\\uDCDD ${inv.supplier_name || 'Unknown'}`);\n lines.push(` Date: ${inv.invoice_date || 'N/A'}`);\n lines.push(` Amount: ${inv.total_amount_due || 'N/A'} ${inv.currency_code || ''}`);\n if (inv.invoice_number) lines.push(` Invoice #: ${inv.invoice_number}`);\n lines.push('');\n}\n\nreturn [{\n json: {\n chatId: chatId,\n response: lines.join('\\n')\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1240,
520
],
"id": "format-search-node",
"name": "Format Search"
},
{
"parameters": {
"jsCode": "// Fallback for unknown commands\nconst chatId = $json.chatId;\nconst command = $json.command;\n\nlet response;\nif (command === '_not_a_command') {\n response = 'I only respond to commands. Try /help to see available commands.';\n} else {\n response = `Unknown command: /${command}\\n\\nTry /help to see available commands.`;\n}\n\nreturn [{\n json: {\n chatId: chatId,\n response: response\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
800,
720
],
"id": "fallback-handler-node",
"name": "Unknown Command"
},
{
"parameters": {
"mode": "combine",
"mergeByFields": {
"values": []
},
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.1,
"position": [
1460,
200
],
"id": "merge-responses-node",
"name": "Merge Responses"
},
{
"parameters": {
"jsCode": "// Split Long Message into Chunks\n// Telegram max message length is ~4096 chars, using 4000 to be safe\n\nconst message = $json.response || 'No response';\nconst chatId = $json.chatId;\nconst maxLength = 4000;\nconst chunks = [];\n\nfor (let i = 0; i < message.length; i += maxLength) {\n chunks.push(message.substring(i, i + maxLength));\n}\n\nreturn chunks.map((chunk, idx) => ({\n json: {\n chatId: chatId,\n chunk: chunk,\n part: idx + 1,\n total: chunks.length\n }\n}));"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1680,
200
],
"id": "split-long-message-node",
"name": "Split Long Message"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
1900,
200
],
"id": "loop-over-chunks-node",
"name": "Loop Over Chunks"
},
{
"parameters": {
"chatId": "={{ $json.chatId }}",
"text": "={{ $json.total > 1 ? '[' + $json.part + '/' + $json.total + '] ' : '' }}{{ $json.chunk }}",
"additionalFields": {
"appendAttribution": false,
"parse_mode": "Markdown"
}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
2120,
200
],
"id": "send-telegram-response-node",
"name": "Send Response",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"content": "### Command Handlers\n\nEach command branch processes the request and returns:\n```\n{\n chatId: \"...\",\n response: \"formatted message\"\n}\n```\n\nAll branches merge before the response pipeline.",
"height": 200,
"width": 280
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
780,
-200
],
"id": "handlers-sticky-note",
"name": "Handlers Note"
},
{
"parameters": {
"content": "### Response Pipeline\n\n1. Merge all command outputs\n2. Split messages >4000 chars\n3. Loop and send each chunk\n4. Add part numbers for multi-part messages",
"height": 160,
"width": 280
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
1460,
-40
],
"id": "pipeline-sticky-note",
"name": "Pipeline Note"
}
],
"connections": {
"Telegram Trigger": {
"main": [
[
{
"node": "Whitelist Check",
"type": "main",
"index": 0
}
]
]
},
"Whitelist Check": {
"main": [
[
{
"node": "Parse Command",
"type": "main",
"index": 0
}
],
[]
]
},
"Parse Command": {
"main": [
[
{
"node": "Command Router",
"type": "main",
"index": 0
}
]
]
},
"Command Router": {
"main": [
[
{
"node": "Format Help",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Executions",
"type": "main",
"index": 0
}
],
[
{
"node": "Read FailedItems",
"type": "main",
"index": 0
}
],
[
{
"node": "Has Execution ID?",
"type": "main",
"index": 0
}
],
[
{
"node": "Has Search Query?",
"type": "main",
"index": 0
}
],
[
{
"node": "Unknown Command",
"type": "main",
"index": 0
}
]
]
},
"Format Help": {
"main": [
[
{
"node": "Merge Responses",
"type": "main",
"index": 0
}
]
]
},
"Get Executions": {
"main": [
[
{
"node": "Format Status",
"type": "main",
"index": 0
}
]
]
},
"Format Status": {
"main": [
[
{
"node": "Merge Responses",
"type": "main",
"index": 0
}
]
]
},
"Read FailedItems": {
"main": [
[
{
"node": "Format Failures",
"type": "main",
"index": 0
}
]
]
},
"Format Failures": {
"main": [
[
{
"node": "Merge Responses",
"type": "main",
"index": 0
}
]
]
},
"Has Execution ID?": {
"main": [
[
{
"node": "Execute Retry",
"type": "main",
"index": 0
}
],
[
{
"node": "Retry Missing ID",
"type": "main",
"index": 0
}
]
]
},
"Retry Missing ID": {
"main": [
[
{
"node": "Merge Responses",
"type": "main",
"index": 0
}
]
]
},
"Execute Retry": {
"main": [
[
{
"node": "Format Retry",
"type": "main",
"index": 0
}
]
]
},
"Format Retry": {
"main": [
[
{
"node": "Merge Responses",
"type": "main",
"index": 0
}
]
]
},
"Has Search Query?": {
"main": [
[
{
"node": "Read Invoices",
"type": "main",
"index": 0
}
],
[
{
"node": "Search Missing Query",
"type": "main",
"index": 0
}
]
]
},
"Search Missing Query": {
"main": [
[
{
"node": "Merge Responses",
"type": "main",
"index": 0
}
]
]
},
"Read Invoices": {
"main": [
[
{
"node": "Format Search",
"type": "main",
"index": 0
}
]
]
},
"Format Search": {
"main": [
[
{
"node": "Merge Responses",
"type": "main",
"index": 0
}
]
]
},
"Unknown Command": {
"main": [
[
{
"node": "Merge Responses",
"type": "main",
"index": 0
}
]
]
},
"Merge Responses": {
"main": [
[
{
"node": "Split Long Message",
"type": "main",
"index": 0
}
]
]
},
"Split Long Message": {
"main": [
[
{
"node": "Loop Over Chunks",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Chunks": {
"main": [
[],
[
{
"node": "Send Response",
"type": "main",
"index": 0
}
]
]
},
"Send Response": {
"main": [
[
{
"node": "Loop Over Chunks",
"type": "main",
"index": 0
}
]
]
}
},
"meta": {
"templateCredsSetupCompleted": true
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApihttpHeaderAuthtelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Telegram-Command-Interface.N8N. Uses telegramTrigger, httpRequest, googleSheets, telegram. Event-driven trigger; 25 nodes.
Source: https://github.com/runfish5/micro-services/blob/main/projects/n8n/11_n8n-ops-center/telegram-command-interface.n8n.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow provides a complete solution for handling Telegram Stars payments, invoicing and refunds using n8n. It automates the process of sending invoices, managing pre-checkout approvals, recordi
clients kept booking meetings during my prayer times. i'd either miss a prayer or scramble to reschedule. the problem wasn't the clients — it was that my calendar had no blocked windows for salah. i n
Generate 360° product videos from a single photo using Google Veo 3 and Telegram
02b — Article callback. Uses telegramTrigger, googleSheets, telegram, httpRequest. Event-driven trigger; 30 nodes.
Automates LinkedIn job searches across multiple countries and categories, filters results with AI, stores data in Google Sheets, and sends weekly Telegram notifications. Perfect for professionals seek