feat(03-02): handle suggestion callback and execute action
- Add Parse Callback Data code node to decode callback_query JSON - Add Route Callback switch for cancel/expired/execute branches - Add Handle Cancel with answer query and delete message - Add Handle Expired with alert message and delete message - Add Build Callback Action to construct curl command from callback - Add Execute Callback Action to run Docker API call - Add Parse Callback Result to check status and build response - Add Answer Action Query, Delete Suggestion Message, Send Callback Result - 2-minute timeout enforced via timestamp in callback_data
This commit is contained in:
+416
-1
@@ -641,6 +641,279 @@
|
||||
"name": "Telegram API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Parse callback data from suggestion button click\nconst callback = $json.callback_query;\nlet data;\ntry {\n data = JSON.parse(callback.data);\n} catch (e) {\n data = { a: 'x' }; // Treat parse error as cancel\n}\n\nconst queryId = callback.id;\nconst chatId = callback.message.chat.id;\nconst messageId = callback.message.message_id;\n\n// Check 2-minute timeout (120000ms)\nconst TWO_MINUTES = 120000;\nconst isExpired = data.t && (Date.now() - data.t > TWO_MINUTES);\n\n// Decode action: s=start, t=stop, r=restart, x=cancel\nconst actionMap = { s: 'start', t: 'stop', r: 'restart', x: 'cancel' };\nconst action = actionMap[data.a] || 'cancel';\n\nreturn {\n json: {\n queryId,\n chatId,\n messageId,\n action,\n containerId: data.c || null,\n expired: isExpired,\n isCancel: action === 'cancel'\n }\n};"
|
||||
},
|
||||
"id": "code-parse-callback",
|
||||
"name": "Parse Callback Data",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [900, 500]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"rules": {
|
||||
"values": [
|
||||
{
|
||||
"id": "is-cancel",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "cancel-true",
|
||||
"leftValue": "={{ $json.isCancel }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "cancel"
|
||||
},
|
||||
{
|
||||
"id": "is-expired",
|
||||
"conditions": {
|
||||
"options": {
|
||||
"caseSensitive": true,
|
||||
"leftValue": "",
|
||||
"typeValidation": "strict"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"id": "expired-true",
|
||||
"leftValue": "={{ $json.expired }}",
|
||||
"rightValue": true,
|
||||
"operator": {
|
||||
"type": "boolean",
|
||||
"operation": "equals"
|
||||
}
|
||||
}
|
||||
],
|
||||
"combinator": "and"
|
||||
},
|
||||
"renameOutput": true,
|
||||
"outputKey": "expired"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"fallbackOutput": "extra"
|
||||
}
|
||||
},
|
||||
"id": "switch-route-callback",
|
||||
"name": "Route Callback",
|
||||
"type": "n8n-nodes-base.switch",
|
||||
"typeVersion": 3.2,
|
||||
"position": [1120, 500]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Prepare cancel response - answer callback query and delete message\nconst { queryId, chatId, messageId } = $json;\nreturn {\n json: {\n queryId,\n chatId,\n messageId,\n answerText: 'Cancelled'\n }\n};"
|
||||
},
|
||||
"id": "code-handle-cancel",
|
||||
"name": "Handle Cancel",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [1340, 500]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId, text: $json.answerText }) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "http-answer-cancel",
|
||||
"name": "Answer Cancel Query",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1560, 500],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "telegram-credential",
|
||||
"name": "Telegram API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/deleteMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $('Handle Cancel').item.json.chatId, message_id: $('Handle Cancel').item.json.messageId }) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "http-delete-cancel-msg",
|
||||
"name": "Delete Cancel Message",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1780, 500],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "telegram-credential",
|
||||
"name": "Telegram API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Prepare expired response\nconst { queryId, chatId, messageId } = $json;\nreturn {\n json: {\n queryId,\n chatId,\n messageId,\n answerText: 'Confirmation expired. Please try again.'\n }\n};"
|
||||
},
|
||||
"id": "code-handle-expired",
|
||||
"name": "Handle Expired",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [1340, 600]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId, text: $json.answerText, show_alert: true }) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "http-answer-expired",
|
||||
"name": "Answer Expired Query",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1560, 600],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "telegram-credential",
|
||||
"name": "Telegram API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/deleteMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $('Handle Expired').item.json.chatId, message_id: $('Handle Expired').item.json.messageId }) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "http-delete-expired-msg",
|
||||
"name": "Delete Expired Message",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [1780, 600],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "telegram-credential",
|
||||
"name": "Telegram API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Build curl command for callback action execution\nconst data = $input.item.json;\nconst containerId = data.containerId;\nconst action = data.action;\nconst chatId = data.chatId;\nconst messageId = data.messageId;\nconst queryId = data.queryId;\n\n// stop and restart use ?t=10 for graceful timeout\nconst timeout = (action === 'stop' || action === 'restart') ? '?t=10' : '';\n\n// Build curl command that returns HTTP status code\nconst cmd = `curl -s -o /dev/null -w \"%{http_code}\" --unix-socket /var/run/docker.sock -X POST 'http://localhost/v1.47/containers/${containerId}/${action}${timeout}'`;\n\nreturn {\n json: {\n cmd,\n containerId,\n action,\n chatId,\n messageId,\n queryId\n }\n};"
|
||||
},
|
||||
"id": "code-build-callback-cmd",
|
||||
"name": "Build Callback Action",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [1340, 700]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"command": "={{ $json.cmd }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "exec-callback-action",
|
||||
"name": "Execute Callback Action",
|
||||
"type": "n8n-nodes-base.executeCommand",
|
||||
"typeVersion": 1,
|
||||
"position": [1560, 700]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Parse callback action result and get container name\nconst stdout = $input.item.json.stdout;\nconst stderr = $input.item.json.stderr;\nconst cmdData = $('Build Callback Action').item.json;\nconst containerId = cmdData.containerId;\nconst action = cmdData.action;\nconst chatId = cmdData.chatId;\nconst messageId = cmdData.messageId;\nconst queryId = cmdData.queryId;\n\n// Check for curl-level errors first\nif (stderr && stderr.trim()) {\n return {\n json: {\n success: false,\n chatId,\n messageId,\n queryId,\n text: `Failed to ${action}: ${stderr.trim()}`,\n answerText: 'Action failed'\n }\n };\n}\n\nconst statusCode = parseInt(stdout.trim());\n\n// 204: Success, 304: Already in state (also success)\nif (statusCode === 204 || statusCode === 304) {\n const verb = action === 'start' ? 'started' :\n action === 'stop' ? 'stopped' : 'restarted';\n return {\n json: {\n success: true,\n chatId,\n messageId,\n queryId,\n containerId,\n text: `Container ${verb} successfully`,\n answerText: `Container ${verb}`\n }\n };\n}\n\n// Handle error codes\nlet errorMsg;\nswitch (statusCode) {\n case 404:\n errorMsg = 'Container not found';\n break;\n case 409:\n errorMsg = 'Container is in a conflicting state';\n break;\n case 500:\n errorMsg = 'Docker server error';\n break;\n default:\n errorMsg = `HTTP ${statusCode}`;\n}\n\nreturn {\n json: {\n success: false,\n chatId,\n messageId,\n queryId,\n text: `Failed to ${action}: ${errorMsg}`,\n answerText: 'Action failed'\n }\n};"
|
||||
},
|
||||
"id": "code-parse-callback-result",
|
||||
"name": "Parse Callback Result",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [1780, 700]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/answerCallbackQuery",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ callback_query_id: $json.queryId, text: $json.answerText }) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "http-answer-action",
|
||||
"name": "Answer Action Query",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [2000, 700],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "telegram-credential",
|
||||
"name": "Telegram API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.telegram.org/bot{{ $credentials.telegramApi.accessToken }}/deleteMessage",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={{ JSON.stringify({ chat_id: $('Parse Callback Result').item.json.chatId, message_id: $('Parse Callback Result').item.json.messageId }) }}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "http-delete-suggestion-msg",
|
||||
"name": "Delete Suggestion Message",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [2220, 700],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "telegram-credential",
|
||||
"name": "Telegram API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "message",
|
||||
"operation": "sendMessage",
|
||||
"chatId": "={{ $('Parse Callback Result').item.json.chatId }}",
|
||||
"text": "={{ $('Parse Callback Result').item.json.text }}",
|
||||
"additionalFields": {
|
||||
"parse_mode": "HTML"
|
||||
}
|
||||
},
|
||||
"id": "telegram-send-callback-result",
|
||||
"name": "Send Callback Result",
|
||||
"type": "n8n-nodes-base.telegram",
|
||||
"typeVersion": 1.2,
|
||||
"position": [2440, 700],
|
||||
"credentials": {
|
||||
"telegramApi": {
|
||||
"id": "telegram-credential",
|
||||
"name": "Telegram API"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
@@ -687,10 +960,152 @@
|
||||
},
|
||||
"IF Callback Authenticated": {
|
||||
"main": [
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "Parse Callback Data",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[]
|
||||
]
|
||||
},
|
||||
"Parse Callback Data": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Route Callback",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Route Callback": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Handle Cancel",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"node": "Handle Expired",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
[],
|
||||
[
|
||||
{
|
||||
"node": "Build Callback Action",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Handle Cancel": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer Cancel Query",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer Cancel Query": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Delete Cancel Message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Handle Expired": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer Expired Query",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer Expired Query": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Delete Expired Message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Build Callback Action": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Execute Callback Action",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Execute Callback Action": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Callback Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Callback Result": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Answer Action Query",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Answer Action Query": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Delete Suggestion Message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Delete Suggestion Message": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Callback Result",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Route Message": {
|
||||
"main": [
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user