البوابة
البوابة اتصال WebSocket يدفع الأحداث إلى بوتك. أبقِ اتصالًا واحدًا مفتوحًا لكل عملية بوت.
ما تحتاج إليه
يتضمن Node 22 أو أحدث WebSocket جاهزًا، ويصلح أي عميل WebSocket آخر. تحتاج إلى رمز بوتك (YOUR_BOT_TOKEN).
الاتصال
wss://events.zrzur.com/?version=1&format=json| معامل الاستعلام | القيم | الافتراضي |
|---|---|---|
version | 1 | 1 |
format | json أو msgpack | json |
token | رمز بوتك، بدل Authenticate | لا شيء |
intents | مجموع نيّات الاتصال، بدل الحقل في Authenticate | انظر أدناه |
ready | الحقول المطلوبة في Ready، مثل ready=servers&ready=channels | كل شيء |
مع format=msgpack يرسل الطرفان إطارات ثنائية.
استخدم Authenticate بدل ?token=، فالعناوين تنتهي في السجلات. أرسله خلال 15 ثانية:
{ "type": "Authenticate", "token": "YOUR_BOT_TOKEN", "intents": 33 }يجيب السيرفر بهذا الترتيب:
Authenticated: الرمز صحيح.Ready: بياناتك الأولية. لا تبدأ الأحداث إلا بعده.
كل ما ترسله بعد ذلك محدود بـ1 ميبيبايت للرسالة الواحدة.
حين ترفضك البوابة
| ما يصلك | السبب | ما تفعله |
|---|---|---|
{ "type": "Error", "data": { "type": "InvalidSession" } } ثم يُغلق الاتصال | رمز خاطئ أو أُعيد تعيينه، أو بوت معلّق، أو لا Authenticate خلال 15 ثانية | توقف. لا تُعد الاتصال في حلقة |
{ "type": "Error", "data": { "type": "MissingIntent", "intent": "SERVER_MEMBERS" } } ثم إغلاق بالرمز 4014 | نية خاصة معطلة. يذكرها سبب الإغلاق، مثل Disallowed intents: MESSAGE_CONTENT (switched on: none) | فعّلها في بوابة المطورين ثم اتصل مجددًا، أو اتصل دونها |
{ "type": "Error", "data": { "type": "InternalError" } } ثم إغلاق بالرمز 1013 | تعذّر على الخادم قراءة إعدادات البوت في تلك اللحظة | اتصل مجددًا، بالانتظار المذكور في إعادة الاتصال |
ابقَ متصلًا
أرسل Ping كل 20 ثانية:
{ "type": "Ping", "data": 1730000000000 }يجيب السيرفر بـPong مع data نفسها. وdata رقم أو قائمة بايتات.
لا تغلق بوابة زرزور نفسها اتصال بوت هادئًا، لكن الوسطاء وموجّهات المنازل يفعلون، والخط الميت لا يعطي أي إشارة. لذلك فالنبضة وسيلتك أنت للاكتشاف: إذا لم يصل Pong خلال 60 ثانية، أغلق المقبس واتصل مجددًا.
إعادة الاتصال
قد ينتهي الاتصال في أي وقت: تحديث من جهتنا، أو عطل شبكة، أو نوم حاسوبك. خطط لذلك.
- عند إغلاق المقبس انتظر ثانية واحدة، وضاعف الانتظار بعد كل فشل حتى 30 ثانية. أضف قليلًا من العشوائية كي لا تعود البوتات كلها معًا.
- اتصل وأرسل
Resumeإن كانت الجلسة قابلة للاستئناف (انظر الاستئناف بعد انقطاع)، وإلا فأرسلAuthenticate. بعدAuthenticateيصلكReadyجديد. - أعد ضبط الانتظار بعد
ReadyأوResumed. - لا تُعد المحاولة بعد حدث
Errorمن النوعInvalidSession، ولا بعد الرمز4014: سيفشل الطلب نفسه مجددًا. توقف وأخبر شخصًا. أما بعد الرمز1013فأعد المحاولة: تعذّر على الخادم قراءة إعدادات البوت في تلك اللحظة.
الاستئناف بعد انقطاع
تستطيع الجلسة أن تكمل من حيث توقفت، فلا يضيع شيء في انقطاع قصير. اطلب ذلك عند المصادقة:
{
"type": "Authenticate",
"token": "YOUR_BOT_TOKEN",
"intents": 33,
"resumable": true
}عندها يحمل Ready الحقل session_id. وتحمل كل حزمة بعد Ready الحقل seq، وهو عدّاد لكل جلسة يبدأ من 1: احتفظ بآخر رقم عالجته. لحزمة Bulk رقم seq واحد، وليس لعناصرها أرقام. ولا يحمل Authenticated وReady وPong وError وInvalidSession وResumed الحقل seq.
بعد الانقطاع تُبقي البوابة الجلسة دقيقتين، ومعها آخر 1000 حدث على الأكثر وبحجم لا يتجاوز 4 ميبيبايت. افتح مقبسًا جديدًا وأرسل هذه الرسالة أولًا، بدلًا من Authenticate:
{
"type": "Resume",
"token": "YOUR_BOT_TOKEN",
"session_id": "YOUR_SESSION_ID",
"seq": 41
}| ما يصلك | المعنى | ما تفعله |
|---|---|---|
الأحداث الفائتة بالترتيب، ثم Resumed | الجلسة مستمرة. لا يصل Ready جديد | تابع عملك، واستمر في عدّ seq |
{ "type": "InvalidSession", "resumable": false } | زالت الجلسة، أو لم يعد ما فاتك يتسع في المحفوظ | أرسل Authenticate على المقبس نفسه، ثم استدرك ما فاتك عبر REST |
قد تظهر فجوات في seq عند الإعادة: أحداث الكتابة لا تُحفظ، وأحداث الأماكن التي لم يعد البوت يراها تُحذف. تُبقي البوابة 3 جلسات منقطعة على الأكثر لكل بوت. ورفض Resume مرة ثانية على المقبس نفسه يغلقه.
دون "resumable": true لا يُحفظ شيء، والأحداث المرسلة في أثناء غيابك تضيع. بعد Ready جديد اجلب عبر REST ما لا يجوز أن يفوت بوتك، مثل الرسائل الأخيرة في قناة (GET /channels/{target}/messages مع after وsort=Oldest، انظر الرسائل). الأمر المستخدم في أثناء غياب البوت يفشل عند من استخدمه، ويستطيع إعادة المحاولة.
التجزئة غير مدعومة
يستقبل الاتصال كل ما تغطيه نيّات الاتصال. لا سبيل إلى توزيع السيرفرات بين الاتصالات. لا يمكن بعد تقسيم عملية بوت كبرت كثيرًا، فأبقِ العمل لكل حدث صغيرًا، وانقل العمل البطيء إلى طابور. انظر التشغيل الفعلي.
يعمل اتصالان بالرمز نفسه، ويستقبل كلاهما كل شيء. عندها يصل InteractionCreate إلى الاثنين، فلا تشغّل اتصالين إلا في أثناء إعادة التشغيل.
حزم Bulk
قد تصل عدة أحداث في حزمة واحدة:
{
"type": "Bulk",
"v": [{ "type": "ServerMemberUpdate" }, { "type": "InteractionCreate" }]
}افتح Bulk دائمًا وعالج كل حدث. البوت الذي يتجاهلها تفوته أحداث، منها استخدام الأوامر.
ما تستطيع إرساله
| Message | Fields | |
|---|---|---|
Authenticate | token, intents, resumable | رسالتك الأولى. token هو رمز البوت، وintents مجموع نيّات الاتصال التي تريدها. مع resumable: true يمكن استئناف الجلسة بعد انقطاع، ويحمل Ready الحقل session_id. |
Resume | token, session_id, seq | الرسالة الأولى على مقبس جديد بعد انقطاع، بدلًا من Authenticate. تصل بالترتيب الأحداث التي تلي seq وما زال يحق لك رؤيتها، ثم Resumed. إن زالت الجلسة تتلقى InvalidSession. |
BeginTyping | channel | يُظهر البوت وهو يكتب في channel. أعد إرساله كل بضع ثوانٍ لإبقائه ظاهرًا. |
EndTyping | channel | يوقف إظهار الكتابة في channel. |
Subscribe | server_id | اطلب تحديثات الحضور والأعضاء في server_id. يحتاج البوت إلى SERVER_MEMBERS أو PRESENCE ليرى هذه التحديثات. |
Ping | data, responded | يختبر الخط. data رقم من اختيارك ويعود في Pong. |
نيّات الاتصال
تحدد نيّات الاتصال (intents) ما يستقبله الاتصال. تطبقها البوابة، فلا يصل البوت أبدًا ما لم يطلبه. اجمع أرقام النيّات التي تريدها وأرسل المجموع. دون intents يحصل البوت على كل النيّات المجانية، أي 63.
| النية | القيمة | ما يصلك |
|---|---|---|
SERVERS | 1 | أحداث السيرفرات والقنوات والأدوار والرموز التعبيرية والويب هوك، والسيرفرات وقنواتها في Ready. تصل الرسائل الخاصة والمجموعات في كل حال، في Ready وفي ChannelCreate |
SERVER_MESSAGES | 2 | الرسائل والكتابة في قنوات السيرفرات |
DIRECT_MESSAGES | 4 | الرسائل والكتابة في الرسائل الخاصة والمجموعات |
REACTIONS | 8 | إضافة التفاعلات وإزالتها |
VOICE_STATES | 16 | دخول الناس القنوات الصوتية وخروجهم، والمكالمات، وتغيّرات المنصّة، وتشغيل أصوات لوحة الأصوات، وحالات الصوت في Ready |
INTERACTIONS | 32 | InteractionCreate |
MESSAGE_CONTENT | 1024 | خاصة. نص الرسائل وتضميناتها وملفاتها ومكوّناتها |
SERVER_MEMBERS | 2048 | خاصة. انضمام الأعضاء ومغادرتهم وتغيرهم، وقائمة الأعضاء |
PRESENCE | 4096 | خاصة. من المتصل، وحالته |
البوت الذي يجيب عن الأوامر فقط يحتاج إلى SERVERS + INTERACTIONS = 33. دون INTERACTIONS لا يسمع بوتك الأوامر.
تُفعَّل النيّات الخاصة للبوت في بوابة المطورين. وطلب نية معطلة يغلق الاتصال بالرمز 4014. بعد 100 سيرفر لا تبقى إلا النيّات الخاصة التي وافق عليها زرزور: تُستبعد الأخرى من الاتصال دون إغلاقه، وتنطبق قواعد REST أدناه.
القواعد نفسها تسري على REST:
- دون
MESSAGE_CONTENTتأتي الرسالة بلا نص أو تضمينات أو ملفات أو مكوّنات، إلا إذا كتبها البوت أو ذُكر فيها أو كانت رسالة خاصة معه. - دون
SERVER_MEMBERSتجيب قائمة الأعضاء والبحث فيهم بالخطأMissingIntent. يبقى عضو واحد (GET /servers/{target}/members/{member}) متاحًا. - دون
PRESENCEيظهر كل مستخدم غير متصل، بلا حالة. يرى البوت نفسه كما هو.
يسري تغيير نيّات البوت في اتصاله التالي.
حين تكون نية خاصة معطلة
يُحفظ المفتاح في بوابة المطورين لحظة تقلبه، وتؤكد البوابة ذلك. تقرأ البوابة المفاتيح حين يبدأ الاتصال، فيحتفظ الاتصال المفتوح بالنيّات التي بدأ بها: أغلق المقبس واتصل مجددًا. يبقى الرمز كما هو. أما REST فيقرؤها مع كل طلب، فتتبع إجاباته المفتاح فورًا.
يسمّي MissingIntent النيّات المعطلة. إذا كان بوتك يعمل دون إحداها، فاتصل مجددًا دونها بدل أن تتوقف:
const MESSAGE_CONTENT = 1024;
// SERVERS + SERVER_MESSAGES + DIRECT_MESSAGES + INTERACTIONS + MESSAGE_CONTENT
let intents = 1 + 2 + 4 + 32 + MESSAGE_CONTENT;
function connect() {
const socket = new WebSocket("wss://events.zrzur.com/?version=1&format=json");
socket.addEventListener("open", () =>
socket.send(
JSON.stringify({
type: "Authenticate",
token: process.env.ZRZUR_BOT_TOKEN,
intents,
}),
),
);
socket.addEventListener("message", ({ data }) => {
const event = JSON.parse(data);
if (event.type === "Error" && event.data.type === "MissingIntent")
console.warn(`Switched off in the Developer Portal: ${event.data.intent}`);
});
socket.addEventListener("close", ({ code }) => {
if (code === 4014 && intents & MESSAGE_CONTENT) {
// go on without message text: mentions and direct messages keep theirs
intents &= ~MESSAGE_CONTENT;
connect();
}
});
}
connect();دون MESSAGE_CONTENT يظل بوتك يقرأ الرسائل التي تذكره، ورسائله هو، والرسائل الخاصة معه. وأضف حلقة إعادة الاتصال من إعادة الاتصال لكل إغلاق آخر.
مخطط قابل للقراءة آليًا
يعيد GET /gateway/schema من الواجهة كل حدث (server) وكل ما يستطيع العميل إرساله (client) في صورة JSON Schema، مع نيّات الاتصال. وهو مولّد من الأنواع التي تستخدمها البوابة.
مثال كامل
في البوت الكامل كل هذا: نيّات الاتصال، والنبضة، والمراقبة، وBulk، والانتظار المتزايد، والحالتان اللتان يتوقف فيهما.