बाहरी हैंडलर

बाहरी हैंडलर आपके कोड और Telegram के बीच एक ब्रिज है। जब बिल्डर की लॉजिक पर्याप्त न हो, तो आप एक अलग रिएक्शन अपने सर्वर को सौंप देते हैं: वह इवेंट प्राप्त करता है और Telegram Bot API फ़ॉर्मेट में कमांड से जवाब देता है, जबकि GetMyBot ट्रांसपोर्ट, डिलीवरी और सारे Telegram का ख्याल रखता है।

यह क्या है

GetMyBot में रिएक्शनें विज़ुअली बनाई जाती हैं: ट्रिगर → शर्तें → कार्रवाइयाँ। जटिल या गैर-मानक लॉजिक — अपना डेटाबेस, गणनाएँ, अन्य सिस्टम को कॉल, ML, शाखायुक्त परिदृश्य — बिल्डर में व्यक्त करना मुश्किल है। बाहरी हैंडलर इस सीमा को हटाता है: आप अपना बैकएंड जोड़ते हैं और उसे पूरी रिएक्शनें प्रोसेस करने के लिए देते हैं।

बाहरी सेवा GetMyBot से इवेंट प्राप्त करती है (ट्रिगर सक्रिय हुआ, उपयोगकर्ता ने टेक्स्ट भेजा, बटन दबाया) और जवाब में बॉट को बताती है क्या भेजना है। GetMyBot कमांड अपने बॉट टोकन से निष्पादित करता है — इसके ऊपर rate-limit, रिट्राई, डिडुप्लिकेशन, डायलॉग जर्नल काम करते हैं।

कार्य मॉडल

  • GetMyBot एक WebSocket-सर्वर है। आपकी सेवा खुद इससे कनेक्ट होती है और उसे कोई सार्वजनिक पता या webhook एंडपॉइंट नहीं चाहिए। GetMyBot आपकी सेवा से कोई आउटगोइंग कनेक्शन नहीं करता, इसलिए कोई SSRF सतह नहीं है।
  • प्रमाणीकरण — इंटीग्रेशन टोकन से। सेवा कनेक्शन पर टोकन प्रस्तुत करती है; GetMyBot इसे हैश से मिलाता है और कनेक्शन बनाए रखता है।
  • इवेंट और कमांड — Telegram Bot API फ़ॉर्मेट में। सेवा इवेंट प्राप्त करती है, और "मेथड + पैरामीटर" कॉल के array से जवाब देती है, बिल्कुल वैसे जैसे सामान्य बॉट के साथ काम करते समय।
  • कमांड GetMyBot अपने टोकन से निष्पादित करता है। सेवा कभी Telegram से सीधे संपर्क नहीं करती: कमांड वैलिडेशन के बाद GetMyBot के आंतरिक outbox में जाते हैं, जहाँ से बॉट टोकन से 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) से बैकअप रिएक्शन चलाएगा। यदि बैकअप सेट नहीं है — कुछ नहीं होगा (शांत, उपयोगकर्ता को कोई त्रुटि नहीं)।

कनेक्शन और प्रमाणीकरण

एंडपॉइंट: GET /ext/ws/{integrationID} WebSocket (wss) पर। integrationID — "बाहरी हैंडलर" प्रकार की इंटीग्रेशन का UUID। फ़्रेम आकार सीमा — 256 KiB।

दो तरीकों में से किसी एक से प्रमाणीकरण किया जा सकता है।

1. हैंडशेक पर हेडर (अनुशंसित)। Authorization हेडर में टोकन पास करें:

Authorization: Bearer <token>

2. Auth-फ़्रेम से। यदि Bearer हेडर नहीं पास किया, तो कनेक्शन स्थापित होने के 10 सेकंड के भीतर पहले फ़्रेम के रूप में भेजें:

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

यदि 10 सेकंड में auth-फ़्रेम नहीं आया — कनेक्शन कोड 4401 से बंद होता है। किसी भी तरीके में गलत टोकन — वही 4401 close।

टोकन इंटीग्रेशन बनाते समय जनरेट होता है, एक बार दिखाया जाता है और केवल हैश (sha256) रूप में संग्रहीत होता है, जाँच — constant-time। टोकन रोटेशन:

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

एंडपॉइंट नया 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 — उपयोगकर्ता पैरामीटर (लिंक से सहित), key→value 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 raw है; user में केवल tg_user_id भरा है; event में केवल वह callback_data है जो सेवा ने बटन में सेट किया था:
{ "callback_data": "<original value set by the service in the button>" }
  • 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": "Hello" } }
  ],
  "expect": "none"
}
  • session_id — वह सेशन जिससे कमांड संबंधित है।
  • token — केवल type: "auth" के लिए (देखें कनेक्शन और प्रमाणीकरण)।
  • methods{ "method": "<Telegram Bot API मेथड नाम>", "params": { ... } } ऑब्जेक्ट का array। मेथड का नाम — बिल्कुल Telegram Bot API जैसा (जैसे sendMessage), params — उस मेथड के पैरामीटर ऑब्जेक्ट।
  • expect — सेवा उपयोगकर्ता के उत्तर की प्रतीक्षा करती है या नहीं: "none", "text" या "any" (खाली मान none माना जाता है)।
  • session.close — डायलॉग स्पष्ट रूप से बंद करें।

कमांड सीधे Telegram नहीं जाते: GetMyBot उन्हें वैलिडेट करता है और अपने outbox में डालता है, जहाँ से बॉट टोकन (rate-limit, रिट्राई, डिडुप्लिकेशन, जर्नल) से भेजे जाते हैं।

execute कैसे प्रोसेस होता है

execute कमांड पूरी तरह ड्रॉप होती है यदि: session_id खाली हो; सेशन नहीं मिला या open स्थिति में नहीं; सेशन किसी अन्य इंटीग्रेशन का हो; सेशन समाप्त हो।

आगे:

  • Rate-limit: प्रति सेशन प्रति मिनट 60 execute तक (स्लाइडिंग विंडो)। सीमा से अधिक — ड्रॉप।
  • एक execute में 30 मेथड तक; अतिरिक्त चुपचाप छोड़ दिए जाते हैं।
  • प्रत्येक मेथड पर whitelist जाँच होती है (देखें अनुमत मेथड) — सूची में नहीं है तो अस्वीकृत।
  • Chat-guard: यदि params में chat_id या from_chat_id है जिसकी वैल्यू 0 नहीं और सेशन के chat_id से भी अलग है — पूरा मेथड अस्वीकृत (किसी दूसरे चैट में नहीं भेज सकते)। chat_id बिल्कुल न भी दें: GetMyBot स्वचालित रूप से सेशन का चैट डाल देगा।
  • callback_data वाले Inline-बटन स्वचालित रूप से टोकनाइज़ होते हैं (आंतरिक रूप 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 और कमांड में callback_data वाले inline-बटनों पर निर्भर करती है:

  • expect = text या any — उपयोगकर्ता का अगला संदेश message इवेंट से सेवा को भेजा जाएगा।
  • expect = none AND कमांड में callback वाले inline-बटन नहीं थे — सेशन स्वचालित रूप से बंद होता है। यह "टर्मिनल" संदेश है।
  • 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: हर 30 सेकंड में ping; गतिविधि अनुपस्थिति टाइमआउट — 60 सेकंड।
  • Auth-फ़्रेम टाइमआउट: 10 सेकंड।

विश्वसनीयता (heartbeat, ऑफलाइन, कतार, fallback)

  • Heartbeat। सर्वर हर 30 सेकंड में {"type":"ping"} फ़्रेम भेजता है। यदि क्लाइंट से 60 सेकंड से अधिक कोई गतिविधि नहीं — कनेक्शन बंद होता है। क्लाइंट गतिविधि बनाए रखने के लिए अपने ping/pong भेज सकता है। कनेक्शन टूटना डायलॉग नहीं मारता: सेशन TTL या पुनः कनेक्शन तक DB में जीवित रहते हैं।
  • पुनः कनेक्शन — सेवा की ज़िम्मेदारी। यदि कनेक्शन टूट गया, तो आपकी सेवा को दोबारा कनेक्ट करना होगा। DB में खुले सेशन TTL समाप्त होने तक रिस्टार्ट से बचते हैं।
  • ऑफलाइन पर Fallback स्टार्टअप में। यदि रिएक्शन सक्रिय होने के समय सेवा कनेक्ट नहीं है — इंटीग्रेशन सेटिंग (on_unavailable_reaction_id) से बैकअप रिएक्शन चलती है। यदि वह सेट नहीं है — कुछ नहीं होता (शांत)।
  • ऑफलाइन-कतार। ऑफलाइन के कारण डिलीवर न हुए message/callback इवेंट ऑफलाइन-कतार में रखे जाते हैं (100 इवेंट तक, FIFO, सेशन समाप्ति तक संग्रहीत) और पुनः कनेक्शन पर भेजे जाते हैं। session.open/session.cancel/session.expired इवेंट कतार में नहीं रखे जाते
  • डिलीवरी गारंटी — at-most-once। कतार ओवरफ्लो या पुरानी होने पर इवेंट छोड़ दिए जाते हैं। लॉजिक इस तरह डिज़ाइन करें कि किसी अलग इवेंट का छूटना परिदृश्य तोड़े नहीं।

ऑनलाइन इंडिकेटर

सेवा सक्रिय कनेक्शन रख रही है या नहीं, यह एंडपॉइंट से जाँचा जा सकता है:

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")। इंटीग्रेशन ID और टोकन डालें। पते में पथ में <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: "Hello from external handler!" } }],
    }));
  }
});

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": "Hello from external handler!"}}
                    ],
                }))

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: "Press the button",
        reply_markup: { inline_keyboard: [[{ text: "Go", 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: "Button pressed!" } }],
  }));
}

आगे क्या करें