{
  "id": "CODWF03TelegramInbound",
  "name": "COD · WF-03 Telegram Inbound",
  "active": false,
  "nodes": [
    {
      "parameters": {
        "content": "## Layer 3 · Telegram Inbound\nThe bot has exactly one Telegram webhook. This layer normalizes text, buttons, and Arabic voice notes, handles /start linking, and delegates channel-agnostic decisions to WF-03b.",
        "height": 220,
        "width": 1500,
        "color": 4
      },
      "id": "wf03-note-layer",
      "name": "Layer 3 · Telegram Gateway",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -780,
        -360
      ]
    },
    {
      "parameters": {
        "updates": [
          "message",
          "callback_query"
        ],
        "additionalFields": {}
      },
      "id": "wf03-trigger",
      "name": "Telegram Trigger (Single Bot Webhook)",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.2,
      "position": [
        -720,
        0
      ],
      "webhookId": "cod-telegram-inbound-v1",
      "credentials": {
        "telegramApi": {
          "id": "cred_telegram",
          "name": "COD Telegram Bot"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "let __smartCodRun;\n(() => {\n  // n8n/code/inbound.ts\n  var intents = /* @__PURE__ */ new Set([\"confirm\", \"cancel\", \"change_address\", \"question\", \"complaint\", \"unclear\"]);\n  var reasons = /* @__PURE__ */ new Set([\"price\", \"changed_mind\", \"duplicate\", \"delivery_time\", \"other\"]);\n  var sentiments = /* @__PURE__ */ new Set([\"positive\", \"neutral\", \"negative\", \"angry\"]);\n  function object(value) {\n    return value && typeof value === \"object\" ? value : {};\n  }\n  function validateAnalysis(value) {\n    const candidate = typeof value === \"string\" ? object(JSON.parse(value)) : object(value);\n    const intent = String(candidate.intent ?? \"unclear\");\n    const cancelReason = candidate.cancel_reason == null ? null : String(candidate.cancel_reason);\n    const sentiment = String(candidate.sentiment ?? \"neutral\");\n    const confidence = Number(candidate.confidence ?? 0);\n    if (!intents.has(intent) || cancelReason !== null && !reasons.has(cancelReason) || !sentiments.has(sentiment) || !Number.isFinite(confidence)) {\n      throw new Error(\"Invalid AI intent payload\");\n    }\n    return {\n      intent,\n      new_address: candidate.new_address == null ? null : String(candidate.new_address),\n      cancel_reason: cancelReason,\n      sentiment,\n      confidence: Math.max(0, Math.min(1, confidence)),\n      reply_ar: String(candidate.reply_ar ?? \"\")\n    };\n  }\n  function normalizeInbound(payload) {\n    const source = object(payload.body ?? payload);\n    if (source.inbound && typeof source.inbound === \"object\") {\n      return source.inbound;\n    }\n    if (source.chatId) {\n      return {\n        channel: \"TELEGRAM\",\n        chatId: String(source.chatId),\n        username: source.username ? String(source.username) : void 0,\n        text: String(source.text ?? source.transcript ?? \"\"),\n        transcript: source.transcript ? String(source.transcript) : void 0,\n        callbackData: source.callbackData ? String(source.callbackData) : void 0,\n        messageType: String(source.messageType ?? (source.callbackData ? \"BUTTON\" : \"TEXT\")),\n        fileId: source.fileId ? String(source.fileId) : void 0,\n        analysis: source.analysis ? validateAnalysis(source.analysis) : void 0,\n        raw: source\n      };\n    }\n    const message = object(source.message);\n    const callback = object(source.callback_query);\n    const callbackMessage = object(callback.message);\n    const chat = object(message.chat ?? callbackMessage.chat);\n    const from = object(message.from ?? callback.from);\n    const voice = object(message.voice ?? message.audio);\n    const callbackData = callback.data ? String(callback.data) : void 0;\n    const transcript = source.transcript ? String(source.transcript) : void 0;\n    return {\n      channel: \"TELEGRAM\",\n      chatId: String(chat.id ?? \"\"),\n      username: from.username ? String(from.username) : void 0,\n      text: String(message.text ?? message.caption ?? transcript ?? callbackData ?? \"\"),\n      transcript,\n      callbackData,\n      messageType: callbackData ? \"BUTTON\" : voice.file_id ? \"VOICE\" : \"TEXT\",\n      fileId: voice.file_id ? String(voice.file_id) : void 0,\n      raw: source\n    };\n  }\n  function run(items) {\n    return items.map((item) => ({ ...item, json: { inbound: normalizeInbound(item.json) } }));\n  }\n\n  // inbound.bundle-entry.ts\n  __smartCodRun = run;\n})();\nreturn __smartCodRun($input.all(), { now: new Date().toISOString() });"
      },
      "id": "wf03-normalize",
      "name": "Sanitize Incoming Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -480,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "wf03-start",
              "leftValue": "={{ $json.inbound.text }}",
              "rightValue": "^/start\\s+\\S+",
              "operator": {
                "type": "string",
                "operation": "regex"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "wf03-start-switch",
      "name": "/start Token Router",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -240,
        0
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "wf03-token",
              "name": "token",
              "value": "={{ $json.inbound.text.replace(/^\\/start\\s+/, '').trim() }}",
              "type": "string"
            },
            {
              "id": "wf03-link-chat",
              "name": "chatId",
              "value": "={{ $json.inbound.chatId }}",
              "type": "string"
            },
            {
              "id": "wf03-link-username",
              "name": "username",
              "value": "={{ $json.inbound.username || '' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "wf03-token-payload",
      "name": "Parse Deep-Link Token",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        20,
        -140
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.INTERNAL_API_URL }}/api/internal/customers/link-telegram",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ token: $json.token, chatId: $json.chatId, ...($json.username ? { username: $json.username } : {}) }) }}",
        "options": {
          "timeout": 10000
        }
      },
      "id": "wf03-link",
      "name": "Link Telegram to Customer",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        260,
        -140
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 1000,
      "credentials": {
        "httpHeaderAuth": {
          "id": "cred_internal_api",
          "name": "COD Internal API Bearer"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "wf03-linked-audience",
              "name": "audience",
              "value": "customer",
              "type": "string"
            },
            {
              "id": "wf03-linked-channel",
              "name": "channel",
              "value": "telegram",
              "type": "string"
            },
            {
              "id": "wf03-linked-template",
              "name": "template",
              "value": "confirmation",
              "type": "string"
            },
            {
              "id": "wf03-linked-language",
              "name": "language",
              "value": "ar",
              "type": "string"
            },
            {
              "id": "wf03-linked-order",
              "name": "orderId",
              "value": "={{ $json.order.id }}",
              "type": "string"
            },
            {
              "id": "wf03-linked-chat",
              "name": "chatId",
              "value": "={{ $json.customer.telegramChatId }}",
              "type": "string"
            },
            {
              "id": "wf03-linked-vars",
              "name": "vars",
              "value": "={{ { orderId: $json.order.id, customerName: $json.customer.name, codAmount: $json.order.codAmount, currency: $json.order.currency, confirmationUrl: $env.APP_PUBLIC_URL + '/c/' + $json.order.confirmationToken, telegramLink: '' } }}",
              "type": "object"
            },
            {
              "id": "wf03-linked-buttons",
              "name": "buttons",
              "value": "={{ [{ text: '✅ تأكيد', callbackData: 'act:confirm:' + $json.order.id }, { text: '❌ إلغاء', callbackData: 'act:cancel:' + $json.order.id }] }}",
              "type": "array"
            }
          ]
        },
        "options": {}
      },
      "id": "wf03-link-notification",
      "name": "Prepare Linked Order Confirmation",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        500,
        -140
      ]
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "CODWF00SendNotification",
          "mode": "id"
        },
        "mode": "once",
        "options": {
          "waitForSubWorkflow": true
        }
      },
      "id": "wf03-send-linked",
      "name": "Send Order with Action Buttons",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        740,
        -140
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "wf03-media-voice",
                    "leftValue": "={{ $json.inbound.messageType }}",
                    "rightValue": "VOICE",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Voice"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "wf03-media-text",
                    "leftValue": "={{ $json.inbound.messageType }}",
                    "rightValue": "^(TEXT|BUTTON)$",
                    "operator": {
                      "type": "string",
                      "operation": "regex"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Text or Callback"
            }
          ]
        },
        "options": {
          "fallbackOutput": "none"
        }
      },
      "id": "wf03-media-router",
      "name": "Media Router",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        20,
        100
      ]
    },
    {
      "parameters": {
        "resource": "file",
        "operation": "get",
        "fileId": "={{ $json.inbound.fileId }}",
        "download": true,
        "additionalFields": {}
      },
      "id": "wf03-get-file",
      "name": "Get Telegram Voice File",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        280,
        40
      ],
      "credentials": {
        "telegramApi": {
          "id": "cred_telegram",
          "name": "COD Telegram Bot"
        }
      }
    },
    {
      "parameters": {
        "resource": "audio",
        "operation": "transcribe",
        "binaryPropertyName": "data",
        "options": {
          "language": "ar"
        }
      },
      "id": "wf03-whisper",
      "name": "OpenAI Whisper Transcription (Arabic)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.3,
      "position": [
        520,
        40
      ],
      "credentials": {
        "openAiApi": {
          "id": "cred_openai",
          "name": "COD OpenAI"
        }
      }
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "wf03-transcribed",
              "name": "inbound",
              "value": "={{ { ...$('Sanitize Incoming Payload').item.json.inbound, text: $json.text, transcript: $json.text } }}",
              "type": "object"
            }
          ]
        },
        "options": {}
      },
      "id": "wf03-normalized-voice",
      "name": "Normalize Voice Transcript",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        760,
        40
      ]
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "CODWF03bCustomerBrain",
          "mode": "id"
        },
        "mode": "once",
        "options": {
          "waitForSubWorkflow": true
        }
      },
      "id": "wf03-brain-voice",
      "name": "Execute WF-03b (Voice)",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        1000,
        40
      ]
    },
    {
      "parameters": {
        "workflowId": {
          "__rl": true,
          "value": "CODWF03bCustomerBrain",
          "mode": "id"
        },
        "mode": "once",
        "options": {
          "waitForSubWorkflow": true
        }
      },
      "id": "wf03-brain-text",
      "name": "Execute WF-03b (Text or Callback)",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1.3,
      "position": [
        280,
        180
      ]
    }
  ],
  "connections": {
    "Telegram Trigger (Single Bot Webhook)": {
      "main": [
        [
          {
            "node": "Sanitize Incoming Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sanitize Incoming Payload": {
      "main": [
        [
          {
            "node": "/start Token Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "/start Token Router": {
      "main": [
        [
          {
            "node": "Parse Deep-Link Token",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Media Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Deep-Link Token": {
      "main": [
        [
          {
            "node": "Link Telegram to Customer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Link Telegram to Customer": {
      "main": [
        [
          {
            "node": "Prepare Linked Order Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Linked Order Confirmation": {
      "main": [
        [
          {
            "node": "Send Order with Action Buttons",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Media Router": {
      "main": [
        [
          {
            "node": "Get Telegram Voice File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Execute WF-03b (Text or Callback)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Telegram Voice File": {
      "main": [
        [
          {
            "node": "OpenAI Whisper Transcription (Arabic)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Whisper Transcription (Arabic)": {
      "main": [
        [
          {
            "node": "Normalize Voice Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Voice Transcript": {
      "main": [
        [
          {
            "node": "Execute WF-03b (Voice)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "errorWorkflow": "CODWF99ErrorHandler",
    "timezone": "Africa/Cairo"
  },
  "versionId": "f0300000-0000-4000-8000-000000000000",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "tags": []
}
