پردازشگر خارجی

پردازشگر خارجی پلی است بین کد شما و Telegram. وقتی منطق سازنده کافی نیست، یک واکنش خاص را به سرور خود تحویل می‌دهید: سرور رویدادها را دریافت می‌کند و با دستوراتی در قالب Telegram Bot API پاسخ می‌دهد، در حالی که GetMyBot حمل‌ونقل، تحویل و تمام Telegram را بر عهده می‌گیرد.

این چیست

واکنش‌ها در GetMyBot به صورت بصری ساخته می‌شوند: ماشه ← شرایط ← اقدامات. منطق پیچیده یا غیراستاندارد — پایگاه داده خودتان، محاسبات، ارتباط با سیستم‌های خارجی، ML، سناریوهای شاخه‌ای — بیان آن در سازنده دشوار است. پردازشگر خارجی این محدودیت را برمی‌دارد: بک‌اند اختصاصی خود را متصل می‌کنید و پردازش واکنش‌های کامل را به آن می‌سپارید.

سرویس خارجی رویدادهایی از GetMyBot دریافت می‌کند (ماشه فعال شد، کاربر متن فرستاد، دکمه‌ای فشار داد) و در پاسخ به بات دستور می‌دهد چه چیزی بفرستد. GetMyBot دستورات را با توکن بات خودش اجرا می‌کند — در بالای آن rate-limit، تکرار، بی‌تکراری و گزارش مکالمه کار می‌کنند.

مدل کار

  • GetMyBot یک WebSocket-سرور است. سرویس شما خودش به آن متصل می‌شود و نیازی به آدرس عمومی یا endpoint وبهوک ندارد. GetMyBot اتصالات خروجی به سرویس شما برقرار نمی‌کند، پس سطح SSRF وجود ندارد.
  • احراز هویت — با توکن یکپارچه‌سازی. سرویس هنگام اتصال توکن را ارائه می‌دهد؛ GetMyBot آن را با هش مقایسه می‌کند و اتصال را نگه می‌دارد.
  • رویدادها و دستورات — در قالب Telegram Bot API. سرویس یک رویداد دریافت می‌کند و با آرایه‌ای از فراخوانی‌های «متد + پارامترها» پاسخ می‌دهد، دقیقاً مانند کار با یک بات معمولی.
  • دستورات را GetMyBot با توکن خودش اجرا می‌کند. سرویس هرگز مستقیم با Telegram ارتباط برقرار نمی‌کند: دستورات اعتبارسنجی می‌شوند و وارد outbox داخلی GetMyBot می‌شوند، که از آنجا با توکن بات با rate-limit، تکرار، بی‌تکراری و گزارش ارسال می‌شوند.

یک اتصال همه مکالمات یک یکپارچه‌سازی را پردازش می‌کند — رویدادهای کاربران مختلف مالتی‌پلکس می‌شوند و با فیلد session_id از هم تمایز می‌یابند.

تنظیم یکپارچه‌سازی

  1. بخش یکپارچه‌سازی‌ها را در منوی چپ باز کنید و روی افزودن یکپارچه‌سازی کلیک کنید.
  2. سرویس پردازشگر خارجی را انتخاب کنید و یک نام وارد کنید.
  3. پس از ایجاد، کارت موارد زیر را نشان می‌دهد:
    • آدرس WebSocket — به شکل wss://api.mybot.app/ext/ws/<integrationID>، که در آن <integrationID> UUID این یکپارچه‌سازی است. سرویس شما از طریق آن متصل می‌شود. بدون id در مسیر اتصال برقرار نمی‌شود.
    • توکن اتصالیک بار هنگام ایجاد نشان داده می‌شود. آن را کپی و در مکان امنی ذخیره کنید؛ امکان مشاهده مجدد توکن وجود ندارد (فقط هش آن ذخیره می‌شود).
    • بازصدور توکن — دکمه‌ای که توکن جدیدی تولید می‌کند و فوراً توکن قدیمی را بی‌اعتبار می‌کند. پس از بازصدور، توکن را در سرویس خود به‌روز کنید.
    • نشانگر آنلاین — نشان می‌دهد آیا سرویس شما در این لحظه اتصال فعال دارد.

پارامترهای اضافی یکپارچه‌سازی که بر پروتکل تأثیر می‌گذارند:

  • session_ttl_seconds — زمان زندگی سشن به ثانیه (پیش‌فرض 3600).
  • on_unavailable_reaction_id — واکنش پشتیبان که اگر سرویس در لحظه فعال‌سازی آفلاین باشد راه‌اندازی می‌شود (ببینید قابلیت اطمینان).

اتصال به واکنش

پردازشگر خارجی نه به عنوان ماشه جداگانه، بلکه به عنوان اقدام متصل می‌شود. در ویرایشگر واکنش، اقدام انتقال به پردازشگر خارجی را اضافه کنید و یکپارچه‌سازی مورد نظر را انتخاب کنید.

هر ماشه موجود در GetMyBot می‌تواند مکالمه را باز کند — دستور، متن، فشار دکمه، پارامتر از لینک، درخواست وب ورودی، برنامه‌ریزی. وقتی چنین ماشه‌ای فعال می‌شود و به اقدام می‌رسد، GetMyBot یک سشن پروکسی باز می‌کند و رویداد session.open را به سرویس می‌فرستد.

اگر در آن لحظه سرویس متصل نباشد، GetMyBot واکنش پشتیبان را از تنظیمات یکپارچه‌سازی (on_unavailable_reaction_id) اجرا می‌کند. اگر واکنش پشتیبانی تعریف نشده باشد — هیچ اتفاقی نمی‌افتد (بی‌صدا، بدون خطا برای کاربر).

اتصال و احراز هویت

Endpoint: GET /ext/ws/{integrationID} روی WebSocket (wss). integrationID — UUID یکپارچه‌سازی نوع «پردازشگر خارجی». محدودیت اندازه فریم — 256 KiB.

احراز هویت از دو روش امکان‌پذیر است.

1. هدر در زمان handshake (ترجیحی). توکن را در هدر Authorization ارسال کنید:

Authorization: Bearer <token>

2. فریم auth. اگر هدر Bearer ارسال نشده، در طول 10 ثانیه پس از برقراری اتصال، اولین فریم را ارسال کنید:

{ "type": "auth", "token": "<token>" }

اگر در 10 ثانیه فریم auth نرسید — اتصال با کد 4401 بسته می‌شود. توکن نادرست در هر یک از روش‌ها — نیز close با 4401.

توکن هنگام ایجاد یکپارچه‌سازی تولید می‌شود، یک بار نشان داده می‌شود و فقط به صورت هش (sha256) ذخیره می‌شود، مقایسه با constant-time انجام می‌شود. چرخش توکن:

POST /api/bots/{botID}/integrations/{integrationID}/rotate-token

Endpoint توکن plaintext جدید را برمی‌گرداند؛ توکن قدیمی فوراً از کار می‌افتد.

رویدادها: بات ← سرویس

هر رویداد یک فریم JSON است (EventEnvelope). فیلد type یکی از: session.open، message، callback، session.cancel، session.expired.

مجموعه کامل فیلدهای پوشش:

{
  "type": "session.open",
  "session_id": "string",
  "bot_id": "string",
  "user": {
    "tg_user_id": 123456789,
    "first_name": "Ivan",
    "username": "ivan",
    "params": { "utm": "promo" }
  },
  "chat": { "id": 123456789, "type": "private" },
  "update": { "...": "raw Telegram Update (raw JSON)" },
  "event": { "...": "normalized GetMyBot event (raw JSON)" }
}
  • session_id — شناسه سشن (مکالمه).
  • bot_id — شناسه بات.
  • user — داده‌های کاربر: tg_user_id (int64)، اختیاری first_name، username، همچنین params — پارامترهای کاربر (از جمله از لینک)، map کلید←مقدار.
  • chat — چت: id (int64) و type (مثلاً "private").
  • update — raw Telegram Update (raw JSON). برای session.open، message، callback موجود است.
  • event — رویداد نرمال‌شده GetMyBot (raw JSON).

محتوا بر اساس نوع متفاوت است:

  • session.open — ماشه فعال شد، مکالمه جدیدی باز شد. دارای update (raw Update) و event (رویداد نرمال‌شده کامل)، user.params پر شده است.
  • message — زمانی ارسال می‌شود که سرویس «در انتظار» (expect = text/any) باشد و کاربر پیامی فرستاده باشد. محتوا مانند session.open است.
  • callback — کاربر دکمه‌ای را که سرویس قبلاً فرستاده فشار داد. update خام است؛ در user فقط tg_user_id پر شده؛ event فقط callback_data اصلی تعریف‌شده توسط سرویس در دکمه را دارد:
{ "callback_data": "<مقدار اصلی تعریف‌شده توسط سرویس در دکمه>" }
  • session.cancel / session.expired — پوشش حداقلی: session_id، bot_id، user.tg_user_id، chat.id (برای cancel همچنین chat.type). فیلدهای update/event وجود ندارند. این رویدادها فقط اگر سرویس آنلاین باشد تحویل داده می‌شوند — در صف آفلاین قرار نمی‌گیرند.

دستورات: سرویس ← بات

دستور یک فریم JSON است (CommandEnvelope). فیلد type یکی از: execute، session.close، auth، ping، pong.

{
  "type": "execute",
  "session_id": "string",
  "methods": [
    { "method": "sendMessage", "params": { "text": "سلام" } }
  ],
  "expect": "none"
}
  • session_id — سشنی که دستور به آن تعلق دارد.
  • token — فقط برای type: "auth" (ببینید اتصال و احراز هویت).
  • methods — آرایه‌ای از اشیاء { "method": "<نام متد Telegram Bot API>", "params": { ... } }. نام متد دقیقاً مانند Telegram Bot API (مثلاً sendMessageparams — شیء پارامترهای آن متد.
  • expect — آیا سرویس منتظر پاسخ کاربر است: "none"، "text" یا "any" (مقدار خالی به عنوان none تفسیر می‌شود).
  • session.close — مکالمه را صریحاً ببندید.

دستورات مستقیماً به Telegram نمی‌روند: GetMyBot آن‌ها را اعتبارسنجی می‌کند و در outbox خود قرار می‌دهد، از آنجا با توکن بات (با rate-limit، تکرار، بی‌تکراری، گزارش) ارسال می‌شوند.

نحوه پردازش execute

دستور execute کاملاً رها می‌شود اگر: session_id خالی باشد؛ سشن پیدا نشود یا در وضعیت open نباشد؛ سشن به یکپارچه‌سازی دیگری تعلق داشته باشد؛ سشن منقضی شده باشد.

سپس:

  • Rate-limit: تا 60 execute در دقیقه برای هر سشن (پنجره لغزان). بیش از این حد — رها می‌شود.
  • تا 30 متد در یک execute؛ اضافی‌ها بی‌صدا رها می‌شوند.
  • برای هر متد، whitelist بررسی می‌شود (ببینید متدهای مجاز) — متد خارج از لیست رد می‌شود.
  • Chat-guard: اگر در params فیلد chat_id یا from_chat_id با مقداری غیر از 0 و غیر از chat_id سشن وجود داشته باشد — کل متد رد می‌شود (نمی‌توان به چت دیگری فرستاد). می‌توان chat_id را اصلاً ذکر نکرد: GetMyBot به اجبار چت سشن را تعیین می‌کند.
  • دکمه‌های inline با callback_data به صورت خودکار توکنیزه می‌شوند (نمای داخلی x:<token>)؛ عمر دکمه = تا انقضای سشن. دکمه‌های url/webapp/switch_inline دست نخورده می‌مانند.

متدهای مجاز

سرویس به بات دستور می‌دهد، بنابراین مجموعه متدها با whitelist محدود شده — فقط ارسال و کار با پیام‌های مکالمه فعلی. متدهای خارج از لیست بی‌صدا نادیده گرفته می‌شوند (این خطا نیست).

  • ارسال: sendMessage، sendPhoto، sendDocument، sendVideo، sendAudio، sendMediaGroup، sendAnimation، sendVoice، sendLocation، sendChatAction؛
  • ویرایش و حذف: editMessageText، editMessageCaption، editMessageReplyMarkup، deleteMessage؛
  • پاسخ به فشار: answerCallbackQuery؛
  • فوروارد و کپی: forwardMessage، copyMessage؛
  • پین: pinChatMessage، unpinChatMessage.

متدهای سطح حساب (setWebhook، getUpdates، logOut، close، setMyCommands و غیره) مجاز نیستند.

انتظار و چرخه زندگی سشن

پس از هر execute، سرنوشت سشن به expect و وجود یا عدم وجود دکمه‌های inline با callback_data در دستور بستگی دارد:

  • expect = text یا any — پیام بعدی کاربر با رویداد message به سرویس فوروارد می‌شود.
  • expect = none و در دستور هیچ دکمه inline با callback نبود — سشن به طور خودکار بسته می‌شود. این یک پیام «پایانی» است.
  • expect = none، اما دکمه‌های callback وجود دارند — سشن باز می‌ماند: فشارها تا زمانی که توکن دکمه‌ها زنده هستند (تا TTL) دریافت می‌شوند.

مهم: expect بر دریافت فشار دکمه‌ها تأثیر نمی‌گذارد. فشار دکمه‌های توکنیزه مستقل از مقدار expect دریافت می‌شوند — تا زمانی که سشن و توکن دکمه زنده هستند. expect فقط کنترل می‌کند که آیا سرویس منتظر پاسخ متنی/دلخواه است.

مرزهای مکالمه

زمینه مکالمه دقیقاً تا زمانی که سشن باز است زندگی می‌کند:

  • پاسخ متنی با انتظار فعال (expect = text/any) — به سرویس با رویداد message می‌رود، نه اینکه واکنش‌های معمولی بات را راه‌اندازی کند.
  • فشار دکمه توکنیزه — با رویداد callback با callback_data اصلی به سرویس می‌رود. GetMyBot همیشه خودش فشار را تأیید می‌کند (answerCallbackQuery)؛ تعلق فشار (بات + کاربر + چت سشن) بررسی می‌شود. دکمه سشن را نمی‌بندد.
  • /cancel (همچنین /cancel@bot و «لغو») با سشن خارجی فعال — سشن بسته می‌شود، به سرویس session.cancel فرستاده می‌شود، به کاربر «لغو شد.» ارسال می‌شود.
  • یک مکالمه برای هر کاربر. باز کردن سشن جدید، سشن باز قبلی این کاربر را جابجا می‌کند — برای آن session.cancel فرستاده می‌شود.
  • انقضای TTL. توسط تایمر، سشن expired علامت‌گذاری می‌شود و (اگر سرویس آنلاین باشد) session.expired به سرویس فرستاده می‌شود.

محدودیت‌ها و تایم‌اوت‌ها

  • اندازه فریم: 256 KiB.
  • TTL سشن: پیش‌فرض 3600 ثانیه (با پارامتر session_ttl_seconds یکپارچه‌سازی قابل تنظیم است).
  • تکرار دستورات: 60 execute در دقیقه برای هر سشن (پنجره لغزان).
  • متدها در یک execute: تا 30 (اضافی‌ها رها می‌شوند).
  • سشن‌های باز برای هر یکپارچه‌سازی: تا 1000.
  • صف آفلاین: تا 100 رویداد برای هر سشن.
  • Heartbeat: ping هر 30 ثانیه؛ تایم‌اوت عدم فعالیت — 60 ثانیه.
  • تایم‌اوت فریم auth: 10 ثانیه.

قابلیت اطمینان (heartbeat، آفلاین، صف، fallback)

  • Heartbeat. سرور هر 30 ثانیه یک فریم {"type":"ping"} می‌فرستد. اگر از کلاینت بیش از 60 ثانیه فعالیتی نبود — اتصال بسته می‌شود. کلاینت می‌تواند ping/pong خودش را برای نگه داشتن فعالیت ارسال کند. قطع اتصال مکالمات را از بین نمی‌برد: سشن‌ها تا TTL یا اتصال مجدد در پایگاه داده زنده می‌مانند.
  • اتصال مجدد — در طرف سرویس. اگر اتصال قطع شد، سرویس شما باید دوباره متصل شود. سشن‌های باز در پایگاه داده تا انقضای TTL از ری‌استارت‌ها جان سالم به در می‌برند.
  • Fallback هنگام آفلاین در شروع. اگر در لحظه فعال‌سازی واکنش سرویس متصل نباشد — واکنش پشتیبان از تنظیمات یکپارچه‌سازی (on_unavailable_reaction_id) اجرا می‌شود. اگر تعریف نشده باشد — هیچ اتفاقی نمی‌افتد (بی‌صدا).
  • صف آفلاین. رویدادهای message/callback که به دلیل آفلاین بودن تحویل داده نشده‌اند، در صف آفلاین (تا 100 رویداد، FIFO، تا انقضای سشن ذخیره می‌شود) قرار می‌گیرند و هنگام اتصال مجدد ارسال می‌شوند. رویدادهای session.open/session.cancel/session.expired در صف قرار نمی‌گیرند.
  • ضمانت تحویل — at-most-once. در صورت پر شدن یا منقضی شدن صف، رویدادها رها می‌شوند. منطق را طوری طراحی کنید که از دست دادن یک رویداد خاص سناریو را خراب نکند.

نشانگر آنلاین

برای بررسی اینکه سرویس اتصال فعال دارد یا خیر، از endpoint استفاده کنید:

GET /api/bots/{botID}/integrations/{integrationID}/status

پاسخ:

{ "online": true }

همین نشانگر روی کارت یکپارچه‌سازی در پنل در دسترس است. معادل آن در MCP — ابزار get_integration_status.

امنیت

GetMyBot دستورات سرویس خارجی را با توکن خودش اجرا می‌کند، بنابراین حفاظت سختگیرانه است.

  • بدون سطح SSRF. GetMyBot به عنوان سرور WebSocket عمل می‌کند و خودش اتصالات خروجی به سرویس برقرار نمی‌کند — این سرویس است که به GetMyBot متصل می‌شود. URL کنترل‌شده از خارج که پلتفرم به آن مراجعه کند، وجود ندارد.
  • توکن به صورت هش ذخیره می‌شود (sha256)، مقایسه با constant-time، چرخش پشتیبانی می‌شود.
  • ایزوله‌سازی مستأجر. دستور به یکپارچه‌سازی خودش سخت وابسته است؛ سشن — به بات + کاربر + چت. رویدادهای بات‌های دیگران وارد سوکت شما نمی‌شوند.
  • Whitelist متدها + chat_id اجباری اجازه نمی‌دهند بات به یک فرستنده انبوه پیام به چت‌های دلخواه تبدیل شود.
  • توکن بات به بیرون منتقل نمی‌شود — سرویس هرگز مستقیم با Telegram ارتباط برقرار نمی‌کند.
  • ماسک‌زدن اسرار. توکن یکپارچه‌سازی و مقادیر حساس در گزارش‌ها و لاگ‌ها پنهان می‌شوند.

نمونه‌ها

یک پردازشگر حداقلی: با باز شدن مکالمه، پیامی ارسال می‌کند و سشن را می‌بندد (expect: "none"). شناسه یکپارچه‌سازی و توکن را جایگزین کنید. آدرس حتماً باید شامل <integrationID> در مسیر باشد.

Node.js

import WebSocket from "ws";

const URL = `wss://api.mybot.app/ext/ws/${process.env.MYBOT_INTEGRATION_ID}`;
const ws = new WebSocket(URL, {
  headers: { Authorization: `Bearer ${process.env.MYBOT_TOKEN}` },
});

ws.on("message", (raw) => {
  const ev = JSON.parse(raw);
  if (ev.type === "session.open") {
    ws.send(JSON.stringify({
      type: "execute",
      session_id: ev.session_id,
      expect: "none",
      methods: [{ method: "sendMessage", params: { text: "سلام از پردازشگر خارجی!" } }],
    }));
  }
});

Python

import json, os, asyncio, websockets

async def main():
    url = f"wss://api.mybot.app/ext/ws/{os.environ['MYBOT_INTEGRATION_ID']}"
    headers = {"Authorization": f"Bearer {os.environ['MYBOT_TOKEN']}"}
    async with websockets.connect(url, additional_headers=headers) as ws:
        async for raw in ws:
            ev = json.loads(raw)
            if ev["type"] == "session.open":
                await ws.send(json.dumps({
                    "type": "execute",
                    "session_id": ev["session_id"],
                    "expect": "none",
                    "methods": [
                        {"method": "sendMessage", "params": {"text": "سلام از پردازشگر خارجی!"}}
                    ],
                }))

asyncio.run(main())

دکمه Inline و پردازش callback

برای دریافت فشار دکمه، دکمه‌ای با callback_data ارسال کنید (GetMyBot آن را به صورت خودکار توکنیزه می‌کند). با expect: "none" همراه دکمه، سشن باز می‌ماند تا زمانی که توکن دکمه زنده است — فشار با رویداد callback می‌رسد که در آن event.callback_data برابر مقدار اصلی است. نیازی به تأیید فشار (answerCallbackQuery) نیست — GetMyBot این کار را خودش انجام می‌دهد.

if (ev.type === "session.open") {
  ws.send(JSON.stringify({
    type: "execute",
    session_id: ev.session_id,
    expect: "none",
    methods: [{
      method: "sendMessage",
      params: {
        text: "دکمه را فشار دهید",
        reply_markup: { inline_keyboard: [[{ text: "بزن بریم", callback_data: "go" }]] },
      },
    }],
  }));
} else if (ev.type === "callback" && ev.event.callback_data === "go") {
  ws.send(JSON.stringify({
    type: "execute",
    session_id: ev.session_id,
    expect: "none",
    methods: [{ method: "sendMessage", params: { text: "دکمه فشار داده شد!" } }],
  }));
}

مراحل بعدی