מטפל חיצוני
מטפל חיצוני הוא גשר בין הקוד שלכם ל-Telegram. כאשר לוגיקת הכלי אינה מספיקה, אתם מוסרים ריאקציה שלמה לשרת שלכם: הוא מקבל אירועים ועונה בפקודות בפורמט Telegram Bot API, בעוד GetMyBot מטפל בתעבורה, בהעברה ובכל מה ש-Telegram.
מה זה
ריאקציות ב-GetMyBot נבנות ויזואלית: טריגר ← תנאים ← פעולות. לוגיקה מורכבת או לא סטנדרטית — מסד הנתונים שלכם, חישובים, קריאות למערכות חיצוניות, ML, תסריטים מסועפים — קשה לבטא בכלי. המטפל החיצוני מסיר תקרה זו: אתם מחברים בק-אנד משלכם ומוסרים לו ריאקציות שלמות לעיבוד.
השירות החיצוני מקבל מ-GetMyBot אירועים (טריגר הופעל, המשתמש ענה בטקסט, לחץ על כפתור) ובתגובה מורה לבוט מה לשלוח. GetMyBot מבצע את הפקודות בטוקן הבוט שלו — מעל זה פועלים rate-limit, ניסיונות חוזרים, כפילויות, יומן דיאלוג.
מודל עבודה
- GetMyBot הוא שרת WebSocket. השירות שלכם מתחבר אליו בעצמו ואינו זקוק לכתובת ציבורית או ל-webhook endpoint. GetMyBot אינו יוצר חיבורים יוצאים לשירות שלכם, לכן אין משטח SSRF.
- אימות — בטוקן אינטגרציה. השירות מציג טוקן בעת החיבור; GetMyBot מאמת אותו מול ה-hash ומשמר את החיבור.
- אירועים ופקודות — בפורמט Telegram Bot API. השירות מקבל אירוע ועונה במערך קריאות בסגנון "method + params", בדיוק כמו עבודה עם בוט רגיל.
- פקודות מבצע GetMyBot בטוקן שלו. השירות לעולם אינו פונה ל-Telegram ישירות: פקודות עוברות ולידציה ומגיעות ל-outbox הפנימי של GetMyBot, משם הן נשלחות בטוקן הבוט עם rate-limit, ניסיונות חוזרים, כפילויות ויומן.
חיבור אחד משרת את כל הדיאלוגים של אינטגרציה אחת — אירועי משתמשים שונים מוכפלים ומובחנים על פי שדה session_id.
הגדרת אינטגרציה
- פתחו את הסעיף אינטגרציות בתפריט שמאל ולחצו הוסף אינטגרציה.
- בחרו שירות מטפל חיצוני והגדירו שם.
- לאחר היצירה הכרטיס יציג:
- כתובת WebSocket — בסגנון
wss://api.mybot.app/ext/ws/<integrationID>, כאשר<integrationID>הוא ה-UUID של אינטגרציה זו. השירות שלכם מתחבר לפיה. ללא ה-ID בנתיב החיבור לא יתבסס. - טוקן חיבור — מוצג פעם אחת בעת היצירה. העתיקו ושמרו אותו במקום בטוח; לא ניתן לצפות בטוקן שוב (הוא נשמר רק כ-hash).
- חידוש טוקן — הכפתור מייצר טוקן חדש ומיד מבטל את הישן. לאחר חידוש עדכנו את הטוקן בשירות שלכם.
- אינדיקטור מחובר — מציג האם השירות שלכם מחזיק חיבור פעיל כרגע.
- כתובת WebSocket — בסגנון
פרמטרים נוספים של אינטגרציה המשפיעים על הפרוטוקול:
session_ttl_seconds— זמן חיים של סשן בשניות (ברירת מחדל3600).on_unavailable_reaction_id— ריאקציה חלופית שמופעלת אם השירות אופליין בעת ההפעלה (ראו אמינות).
קישור לריאקציה
המטפל החיצוני אינו מתחבר כטריגר נפרד, אלא כפעולה. בעורך הריאקציה הוסיפו פעולת העבר למטפל חיצוני ובחרו את האינטגרציה הרצויה.
פתיחת דיאלוג יכולה להיות על ידי כל טריגר קיים ב-GetMyBot — פקודה, טקסט, לחיצת כפתור, פרמטר מקישור, בקשת ווב נכנסת, לוח זמנים. כאשר טריגר כזה מופעל ומגיע לפעולה, GetMyBot פותח סשן proxy ושולח לשירות אירוע session.open.
אם באותו רגע השירות אינו מחובר, GetMyBot יפעיל את הריאקציה החלופית מהגדרות האינטגרציה (on_unavailable_reaction_id). אם לא הוגדרה חלופית — לא יקרה דבר (בשקט, ללא שגיאה למשתמש).
חיבור ואימות
נקודת קצה: GET /ext/ws/{integrationID} מעל WebSocket (wss). integrationID — UUID של אינטגרציה מסוג "מטפל חיצוני". מגבלת גודל frame — 256 KiB.
ניתן לאמת באחת משתי דרכים.
1. כותרת בלחיצת יד (מועדף). העבירו את הטוקן בכותרת Authorization:
Authorization: Bearer <token>
2. Frame auth. אם כותרת Bearer לא הועברה, שלחו כ-frame הראשון תוך 10 שניות מביסוס החיבור:
{ "type": "auth", "token": "<token>" }
אם ה-auth frame לא הגיע תוך 10 שניות — החיבור נסגר עם קוד 4401. טוקן שגוי בכל אחת מהדרכים — גם close 4401.
הטוקן נוצר בעת יצירת האינטגרציה, מוצג פעם אחת ונשמר רק כ-hash (sha256), האימות — בזמן קבוע (constant-time). סיבוב טוקן:
POST /api/bots/{botID}/integrations/{integrationID}/rotate-token
נקודת הקצה מחזירה טוקן plaintext חדש; הישן מפסיק לעבוד מיד.
אירועים: בוט ← שירות
כל אירוע הוא JSON frame (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": { "...": "Telegram Update גולמי (raw JSON)" },
"event": { "...": "אירוע GetMyBot מנורמל (raw JSON)" }
}
session_id— מזהה סשן (דיאלוג).bot_id— מזהה בוט.user— נתוני משתמש:tg_user_id(int64),first_nameו-usernameאופציונליים, וכןparams— פרמטרי משתמש (כולל מקישור), map מפתח←ערך.chat— צ'אט:id(int64) ו-type(לדוגמה"private").update— Telegram Update גולמי (raw JSON). קיים עבורsession.open,message,callback.event— אירוע GetMyBot מנורמל (raw JSON).
התוכן לפי סוגים שונה:
session.open— טריגר הופעל, נפתח דיאלוג חדש. ישupdate(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 frame (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": "<שם method ב-Telegram Bot API>", "params": { ... } }. שם ה-method — בדיוק כמו ב-Telegram Bot API (לדוגמהsendMessage),params— אובייקט פרמטרי ה-method.expect— האם השירות מחכה לתשובת משתמש:"none","text"או"any"(ערך ריק נחשב כ-none).session.close— לסגור דיאלוג באופן מפורש.
פקודות אינן הולכות ל-Telegram ישירות: GetMyBot מאמת אותן ומכניס ל-outbox שלו, משם הן נשלחות בטוקן הבוט (עם rate-limit, ניסיונות חוזרים, כפילויות, יומן).
כיצד execute מעובד
הפקודה execute נדחית כולה אם: session_id ריק; הסשן לא נמצא או אינו בסטטוס open; הסשן שייך לאינטגרציה אחרת; הסשן פג.
לאחר מכן:
- Rate-limit: עד 60
executeלדקה לסשן (חלון נגלל). מעל המגבלה — נדחה. - עד 30 methods ב-
executeאחד; עודפים נדחים בשקט. - כל method נבדק מול ה-whitelist (ראו Methods מורשים) — method שאינו ברשימה נדחה.
- Chat-guard: אם ב-
paramsישchat_idאוfrom_chat_idעם ערך שאינו0ואינו שווה ל-chat_idשל הסשן — כל ה-method נדחה (אין לשלוח לצ'אט אחר).chat_idניתן לא לציין בכלל: GetMyBot יוסיף את צ'אט הסשן בכפייה. - כפתורים inline עם
callback_dataעוברים טוקניזציה אוטומטית (פורמט פנימיx:<token>); זמן חיים של כפתור = עד תפוגת הסשן. כפתוריurl/webapp/switch_inlineאינם נגעים.
Methods מורשים
השירות מפקד על הבוט, לכן ערכת ה-methods מוגבלת ל-whitelist — רק שליחה ועבודה עם הודעות הדיאלוג הנוכחי. Methods שאינם ברשימה נדחים בשקט (זו אינה שגיאה).
- שליחה:
sendMessage,sendPhoto,sendDocument,sendVideo,sendAudio,sendMediaGroup,sendAnimation,sendVoice,sendLocation,sendChatAction; - עריכה ומחיקה:
editMessageText,editMessageCaption,editMessageReplyMarkup,deleteMessage; - תגובה ללחיצה:
answerCallbackQuery; - העברה והעתקה:
forwardMessage,copyMessage; - הצמדה:
pinChatMessage,unpinChatMessage.
Methods ברמת חשבון (setWebhook, getUpdates, logOut, close, setMyCommands וכד') אינם מורשים.
ציפייה ומחזור חיים של סשן
לאחר כל execute גורל הסשן תלוי ב-expect ובנוכחות כפתורים inline עם callback_data:
expect=textאוany— ההודעה הבאה של המשתמש תועבר לשירות כאירועmessage.expect=noneולא היו כפתורי 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נשלח לשירות.
מגבלות וזמן קצוב
- גודל frame: 256 KiB.
- TTL סשן: ברירת מחדל 3600 שניות (ניתן להגדרה בפרמטר
session_ttl_secondsשל האינטגרציה). - תדירות פקודות: 60
executeלדקה לסשן (חלון נגלל). - Methods ב-
executeאחד: עד 30 (עודפים נדחים). - סשנים פתוחים לאינטגרציה: עד 1000.
- תור אופליין: עד 100 אירועים לסשן.
- Heartbeat:
pingכל 30 שניות; זמן קצוב לחוסר פעילות — 60 שניות. - זמן קצוב ל-auth frame: 10 שניות.
אמינות (heartbeat, אופליין, תור, fallback)
- Heartbeat. השרת שולח frame
{"type":"ping"}כל 30 שניות. אם לא הגיעה פעילות מהלקוח יותר מ-60 שניות — החיבור נסגר. הלקוח יכול לשלוחping/pongמשלו לשמירת פעילות. ניתוק אינו הורג דיאלוגים: הסשנים חיים ב-DB עד TTL או עד חיבור מחדש. - חיבור מחדש — בצד השירות. אם החיבור התנתק, השירות שלכם חייב להתחבר מחדש. סשנים פתוחים ב-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 הנשלט מבחוץ שהפלטפורמה פונה אליו.
- הטוקן נשמר כ-hash (sha256), אימות בזמן קבוע (constant-time), נתמכת רוטציה.
- בידוד דייר. פקודה קשורה בקפידה לאינטגרציה שלה; סשן — לבוט + משתמש + צ'אט. אירועי בוטים אחרים לא מגיעים לסוקט שלכם.
- Whitelist methods +
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: "שלום מהמטפל החיצוני!" } }],
}));
}
});
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: "הכפתור נלחץ!" } }],
}));
}
המשך מכאן
- אינטגרציות — סקירה כללית של שירותים ניתנים לחיבור.
- ריאקציות — כיצד נבנים טריגרים, תנאים ופעולות.
- בקשות ווב ו-webhooks — דרך פשוטה יותר לקרוא URL חיצוני ללא דיאלוג.