المعالج الخارجي
المعالج الخارجي هو جسر بين كودك وTelegram. عندما لا تكفي منطق المنشئ البصري، تُسلّم تفاعلاً بأكمله لخادمك: يستقبل الأحداث ويردّ بأوامر بتنسيق Telegram Bot API، بينما يتولى GetMyBot النقل والتسليم وكل ما يخص Telegram.
ما هو
تُبنى التفاعلات في GetMyBot بصرياً: مُشغِّل ← شروط ← أفعال. المنطق المعقد أو غير المعياري — قاعدة البيانات الخاصة بك، الحسابات، الاتصالات بأنظمة خارجية، تعلم الآلة، السيناريوهات المتشعبة — يصعب التعبير عنها في المنشئ. يرفع المعالج الخارجي هذا السقف: تُوصّل الـ backend الخاص بك وتُسلّم إليه تفاعلات بأكملها.
يستقبل الخدمة الخارجية من GetMyBot أحداثاً (تفعّل مُشغِّل، المستخدم أرسل نصاً، ضغط زراً) ويصدر أوامر للبوت بما يُرسله. ينفّذ GetMyBot الأوامر بتوكن البوت الخاص به — مع تطبيق rate-limit والإعادة وإزالة التكرار وسجل المحادثة فوق ذلك.
نموذج العمل
- GetMyBot هو WebSocket-سيرفر. خدمتك هي التي تتصل به ولا تحتاج عنواناً عاماً أو endpoint للـ webhook. GetMyBot لا يُنشئ اتصالات صادرة نحو خدمتك، لذا لا يوجد سطح SSRF.
- المصادقة برمز التكامل. تُقدّم الخدمة الرمز عند الاتصال؛ يتحقق GetMyBot منه بالمقارنة مع الهاش ويُبقي الاتصال.
- الأحداث والأوامر بتنسيق Telegram Bot API. تستقبل الخدمة حدثاً وتردّ بمصفوفة من الاستدعاءات بصيغة «طريقة + معاملات»، تماماً كما عند العمل مع بوت عادي.
- الأوامر يُنفّذها GetMyBot بتوكنه. الخدمة لا تتواصل مع Telegram مباشرةً أبداً: تمر الأوامر عبر التحقق وتدخل outbox GetMyBot الداخلي، ثم تُرسَل بتوكن البوت مع rate-limit والإعادة وإزالة التكرار والسجل.
اتصال واحد يخدم جميع محادثات تكامل واحد — تتشابك أحداث المستخدمين المختلفين وتتمايز بالحقل session_id.
إعداد التكامل
- افتح قسم التكاملات في القائمة اليسرى وانقر إضافة تكامل.
- اختر خدمة المعالج الخارجي وحدّد اسماً.
- بعد الإنشاء ستُظهر البطاقة:
- عنوان WebSocket — بالشكل
wss://api.mybot.app/ext/ws/<integrationID>، حيث<integrationID>هو UUID هذا التكامل. به تتصل خدمتك. بدون المعرّف في المسار لن يتم الاتصال. - رمز الاتصال — يظهر مرة واحدة عند الإنشاء. انسخه واحفظه في مكان آمن؛ لا يمكن الاطلاع على الرمز مجدداً (مخزون فقط بالهاش).
- إعادة إصدار الرمز — الزر يُنشئ رمزاً جديداً ويُلغي القديم فوراً. بعد إعادة الإصدار حدّث الرمز في خدمتك.
- مؤشر الاتصال — يُظهر هل تحتفظ خدمتك بالاتصال النشط الآن.
- عنوان WebSocket — بالشكل
معاملات تكامل إضافية تؤثر على البروتوكول:
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. رأس عند المصافحة (مُفضَّل). مرّر الرمز في رأس Authorization:
Authorization: Bearer <token>
2. إطار auth. إذا لم يُمرَّر رأس Bearer، أرسل كأول إطار خلال 10 ثوانٍ من إنشاء الاتصال:
{ "type": "auth", "token": "<token>" }
إذا لم يصل إطار auth خلال 10 ثوانٍ — يُغلق الاتصال بالكود 4401. الرمز الخاطئ في أي من الطريقتين — أيضاً close 4401.
يُنشأ الرمز عند إنشاء التكامل، يظهر مرة واحدة ومخزون فقط بالهاش (sha256)، والمقارنة constant-time. تدوير الرمز:
POST /api/bots/{botID}/integrations/{integrationID}/rotate-token
يُعيد الـ endpoint الرمز الجديد بنص واضح؛ القديم يتوقف عن العمل فوراً.
الأحداث: البوت ← الخدمة
كل حدث إطار 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— 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 (مثلاًsendMessage)،params— كائن معاملات الطريقة.expect— هل تنتظر الخدمة ردّ المستخدم:"none"،"text"أو"any"(القيمة الفارغة تُعامَل كـnone).session.close— إغلاق المحادثة بشكل صريح.
الأوامر لا تذهب مباشرة إلى Telegram: يتحقق منها GetMyBot ويضعها في outbox الخاص به، ومنه يُرسلها بتوكن البوت (مع rate-limit والإعادة وإزالة التكرار والسجل).
كيف تُعالَج execute
يُسقَط أمر execute بأكمله إذا: session_id فارغ؛ الجلسة غير موجودة أو ليست بحالة open؛ الجلسة تنتمي لتكامل آخر؛ الجلسة منتهية الصلاحية.
ثم:
- Rate-limit: حتى 60
executeفي الدقيقة لكل جلسة (نافذة متحركة). ما فوق الحد — يُسقَط. - حتى 30 طريقة في
executeواحد؛ الزائدة تُتجاهل بصمت. - لكل طريقة يُتحقق من القائمة البيضاء (انظر الطرق المسموح بها) — الطريقة غير المدرجة تُرفض.
- حارس المحادثة (chat-guard): إذا كان في
paramsحقلchat_idأوfrom_chat_idبقيمة لا تساوي0وغير مساوية لـchat_idالجلسة — تُرفض الطريقة بأكملها (لا يمكن الإرسال لمحادثة أخرى). يمكن عدم تحديدchat_idأصلاً: سيضع GetMyBot إلزامياً محادثة الجلسة. - الأزرار المضمّنة ذات
callback_dataتُرمَّز تلقائياً (التنسيق الداخليx:<token>)؛ مدة حياة الزر = حتى انتهاء الجلسة. أزرارurl/webapp/switch_inlineلا تُعدَّل.
الطرق المسموح بها
تتحكم الخدمة في البوت، لذا مجموعة الطرق محدودة بالقائمة البيضاء — فقط الإرسال والعمل مع رسائل المحادثة الحالية. الطرق غير المدرجة تُتجاهل بصمت (هذا ليس خطأً).
- الإرسال:
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 في الأمر:
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.
الحدود والمهل
- حجم الإطار: 256 KiB.
- TTL الجلسة: 3600 ثانية افتراضياً (قابل للتهيئة بمعامل
session_ttl_secondsللتكامل). - تكرار الأوامر: 60
executeفي الدقيقة لكل جلسة (نافذة متحركة). - طرق في
executeواحد: حتى 30 (الزائد يُتجاهل). - جلسات مفتوحة لكل تكامل: حتى 1000.
- قائمة انتظار الغير متصل: حتى 100 حدث لكل جلسة.
- Heartbeat:
pingكل 30 ثانية؛ مهلة غياب النشاط — 60 ثانية. - مهلة إطار auth: 10 ثوانٍ.
الموثوقية (Heartbeat، غير متصل، القائمة، والـ fallback)
- Heartbeat. يُرسل السيرفر إطار
{"type":"ping"}كل 30 ثانية. إذا لم يكن هناك نشاط من العميل أكثر من 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، التدوير مدعوم.
- عزل المستأجر. الأمر مقيّد بتكامله بشكل صارم؛ الجلسة مقيّدة بالبوت + المستخدم + المحادثة. أحداث البوتات الأخرى لا تصل إلى Socket الخاص بك.
- القائمة البيضاء للطرق + إلزامية
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())
زر مضمّن ومعالجة 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 خارجي دون محادثة.