كيف تصنع ديسكورد بوت: نظرة عامة وتعليمية

نشرت: 2022-03-11

Discord عبارة عن نظام أساسي للمراسلة في الوقت الفعلي يصف نفسه بأنه "محادثة صوتية ونصية متكاملة للاعبين". نظرًا لواجهته الرائعة وسهولة استخدامه وميزاته الواسعة ، فقد شهد Discord نموًا سريعًا وأصبح يتمتع بشعبية متزايدة حتى بين أولئك الذين لا يهتمون كثيرًا بألعاب الفيديو. بين مايو 2017 ومايو 2018 ، انتشرت قاعدة مستخدميها من 45 مليون مستخدم إلى أكثر من 130 مليونًا ، مع أكثر من ضعف عدد المستخدمين يوميًا مثل Slack.

تتمثل إحدى الميزات الأكثر جاذبية لـ Discord من منظور مطور برنامج chatbot في دعمه القوي للروبوتات القابلة للبرمجة التي تساعد على دمج Discord مع العالم الخارجي وتزويد المستخدمين بتجربة أكثر جاذبية. تنتشر الروبوتات في كل مكان على Discord وتوفر مجموعة واسعة من الخدمات ، بما في ذلك المساعدة في الإشراف ، والألعاب ، والموسيقى ، وعمليات البحث على الإنترنت ، ومعالجة المدفوعات ، والمزيد.

في هذا البرنامج التعليمي Discord bot ، سنبدأ بمناقشة واجهة مستخدم Discord وواجهات برمجة تطبيقات REST و WebSocket للروبوتات قبل الانتقال إلى البرنامج التعليمي حيث سنكتب Discord bot بسيطًا في JavaScript. أخيرًا ، سنسمع من مطور ، وفقًا لمقاييس معينة ، أكثر روبوتات Discord شهرة وخبراته في تطوير البنية التحتية المهمة وقاعدة الرموز الخاصة به والحفاظ عليها.

واجهة مستخدم الفتنة

قبل أن نناقش التفاصيل الفنية ، من المهم فهم كيفية تفاعل المستخدم مع Discord وكيف يقدم Discord نفسه للمستخدمين. الطريقة التي يقدم بها نفسه للروبوتات متشابهة من الناحية المفاهيمية (لكنها بالطبع غير مرئية). في الواقع ، فإن تطبيقات Discord الرسمية مبنية على نفس واجهات برمجة التطبيقات التي تستخدمها برامج الروبوت. من الممكن تقنيًا تشغيل روبوت داخل حساب مستخدم عادي مع القليل من التعديل ، ولكن هذا ممنوع بموجب شروط خدمة Discord. الروبوتات مطلوبة للتشغيل في حسابات بوت.

إليك نظرة على إصدار المتصفح 1 من تطبيق Discord الذي يعمل داخل Chrome.

Discord Web UI

1 تكون واجهة المستخدم Discord لتطبيق سطح المكتب تقريبًا مماثلة لتطبيق الويب ، مع حزم Electron. تم تصميم تطبيق iOS باستخدام React Native. تطبيق Android هو كود Java أصلي لنظام Android.

دعونا نكسرها.

1. قائمة الخادم

على اليسار على طول قائمة الخوادم التي أنا عضو فيها. إذا كنت معتادًا على Slack ، فإن الخادم يشبه مساحة عمل Slack ، ويمثل مجموعة من المستخدمين الذين يمكنهم التفاعل مع بعضهم البعض داخل قناة واحدة أو أكثر في الخادم. تتم إدارة الخادم من قبل منشئه و / أو أي موظف يختارونه ويختارون تفويض المسؤوليات إليه. يحدد المنشئ و / أو الموظفون القواعد وهيكل القنوات في الخادم وإدارة المستخدمين.

في حالتي ، يكون خادم Discord API في أعلى قائمة الخوادم الخاصة بي. إنه مكان رائع للحصول على المساعدة والتحدث مع مطورين آخرين. يوجد أدناه خادم قمت بإنشائه يسمى Test . سنختبر الروبوت الذي أنشأناه لاحقًا هناك. يوجد أدناه زر لإنشاء خادم جديد. يمكن لأي شخص إنشاء خادم ببضع نقرات.

لاحظ أنه بينما المصطلح المستخدم في واجهة مستخدم Discord هو الخادم ، فإن المصطلح المستخدم في وثائق المطور وواجهة برمجة التطبيقات هو Guild . بمجرد أن ننتقل إلى الحديث عن الموضوعات الفنية ، سننتقل إلى الحديث عن النقابات . المصطلحان قابلان للتبادل.

2. قائمة القنوات

على يمين قائمة الخادم ، توجد قائمة القنوات للخادم الذي أشاهده حاليًا (في هذه الحالة ، خادم Discord API). يمكن تقسيم القنوات إلى عدد تعسفي من الفئات. في خادم Discord API ، تشمل الفئات INFORMATION و GENERAL و LIBS ، كما هو موضح. تعمل كل قناة كغرفة دردشة حيث يمكن للمستخدمين مناقشة أي موضوع تخصص القناة له. القناة التي نعرضها حاليًا (معلومات) لها خلفية أفتح. القنوات التي تحتوي على رسائل جديدة منذ آخر مرة شاهدناها بها لون نص أبيض.

3. عرض القناة

هذا هو عرض القناة حيث يمكننا رؤية ما يتحدث عنه المستخدمون في القناة التي نشاهدها حاليًا. يمكننا رؤية رسالة واحدة هنا ، مرئية جزئيًا فقط. إنها قائمة روابط لخوادم الدعم لمكتبات Discord bot الفردية. قام مسؤولو الخادم بتكوين هذه القناة بحيث لا يتمكن المستخدمون العاديون مثلي من إرسال رسائل فيها. يستخدم المسؤولون هذه القناة كلوحة إعلانات لنشر بعض المعلومات المهمة حيث يمكن رؤيتها بسهولة ولن تغرقها الدردشة.

4. قائمة المستخدمين

يوجد على اليمين قائمة بالمستخدمين المتصلين حاليًا في هذا الخادم. يتم تنظيم المستخدمين في فئات مختلفة وأسمائهم لها ألوان مختلفة. هذا هو نتيجة الأدوار التي لديهم. يصف الدور الفئة (إن وجدت) التي يجب أن يظهر المستخدم تحتها ، ولون الاسم الذي يجب أن يكون عليه ، والأذونات التي لديهم في الخادم. يمكن أن يكون للمستخدم أكثر من دور واحد (وغالبًا ما يفعل ذلك) ، وهناك بعض الرياضيات الأسبقية التي تحدد ما يحدث في هذه الحالة. كحد أدنى ، كل مستخدم لديه دور @ everyone. يتم إنشاء الأدوار الأخرى وتعيينها بواسطة طاقم الخادم.

5. إدخال النص

هذا هو إدخال النص حيث يمكنني كتابة الرسائل وإرسالها ، إذا سمح لي بذلك. نظرًا لأنني لا أمتلك إذنًا لإرسال رسائل في هذه القناة ، لا يمكنني الكتابة هنا.

6. المستخدم

هذا هو المستخدم الحالي. لقد قمت بتعيين اسم المستخدم الخاص بي على "أنا" ، وذلك للمساعدة في تجنب الارتباك ، ولأنني سيء للغاية في اختيار الأسماء. يوجد أسفل اسم المستخدم الخاص بي رقم (# 9484) وهو أداة التمييز الخاصة بي. قد يكون هناك العديد من المستخدمين الآخرين الذين يحملون اسم "أنا" ، لكني أنا الوحيد الذي يحمل اسم "Me # 9484". من الممكن أيضًا أن أحدد اسمًا مستعارًا لنفسي على أساس كل خادم ، لذلك يمكن أن أكون معروفًا بأسماء مختلفة في خوادم مختلفة.

هذه هي الأجزاء الأساسية لواجهة مستخدم Discord ، ولكن هناك الكثير أيضًا. من السهل أن تبدأ في استخدام Discord حتى بدون إنشاء حساب ، لذلك لا تتردد في تخصيص دقيقة واحدة للتنقل. يمكنك الدخول إلى Discord من خلال زيارة صفحة Discord الرئيسية ، والنقر على "فتح Discord في متصفح" ، واختيار اسم مستخدم ، وربما تشغيل جولة أو اثنتين من "النقر على صور الحافلة".

واجهة برمجة تطبيقات Discord

تتكون واجهة برمجة تطبيقات Discord من قطعتين منفصلتين: WebSocket و REST APIs. بشكل عام ، يتم استخدام WebSocket API لتلقي الأحداث من Discord في الوقت الفعلي ، بينما يتم استخدام REST API لأداء الإجراءات داخل Discord.

كيفية عمل حلقة اتصال لروبوت الخلاف

واجهة برمجة تطبيقات WebSocket

يتم استخدام WebSocket API لتلقي الأحداث من Discord ، بما في ذلك إنشاء الرسائل وحذف الرسائل وأحداث طرد / حظر المستخدم وتحديثات أذونات المستخدم وغير ذلك الكثير. الاتصال من الروبوت إلى WebSocket API من ناحية أخرى هو أكثر محدودية. يستخدم الروبوت WebSocket API لطلب اتصال وتعريف نفسه ونبض القلب وإدارة اتصالات الصوت والقيام ببعض الأشياء الأساسية. يمكنك قراءة المزيد من التفاصيل في وثائق بوابة Discord (يُشار إلى اتصال واحد بواجهة برمجة تطبيقات WebSocket على أنه بوابة). لأداء إجراءات أخرى ، يتم استخدام واجهة برمجة تطبيقات REST.

تحتوي الأحداث من WebSocket API على حمولة تتضمن معلومات تعتمد على نوع الحدث. على سبيل المثال ، ستصاحب جميع أحداث إنشاء الرسائل كائن مستخدم يمثل كاتب الرسالة. ومع ذلك ، فإن كائن المستخدم وحده لا يحتوي على جميع المعلومات التي يجب معرفتها عن المستخدم. على سبيل المثال ، لا توجد معلومات مدرجة حول أذونات المستخدم. إذا كنت بحاجة إلى مزيد من المعلومات ، فيمكنك الاستعلام عن واجهة برمجة تطبيقات REST الخاصة بها ، ولكن لأسباب موضحة بمزيد من التفصيل في القسم التالي ، يجب عليك عمومًا الوصول إلى ذاكرة التخزين المؤقت التي كان ينبغي عليك إنشاؤها من الحمولات المستلمة من الأحداث السابقة بدلاً من ذلك. هناك عدد من الأحداث التي تقدم حمولات ذات صلة بأذونات المستخدم ، بما في ذلك على سبيل المثال لا الحصر إنشاء النقابة وتحديث دور الجماعة وتحديث القناة .

يمكن أن يوجد الروبوت في 2500 نقابة كحد أقصى لكل اتصال WebSocket. من أجل السماح لبوت بالتواجد في المزيد من النقابات ، يجب على الروبوت تنفيذ التجزئة وفتح عدة اتصالات WebSocket منفصلة إلى Discord. إذا كان الروبوت الخاص بك يعمل داخل عملية واحدة على عقدة واحدة ، فهذا مجرد تعقيد إضافي بالنسبة لك قد يبدو غير ضروري. ولكن إذا كان الروبوت الخاص بك شائعًا جدًا ويحتاج إلى توزيع نهايته الخلفية عبر عقد منفصلة ، فإن دعم التجزئة في Discord يجعل هذا الأمر أسهل بكثير مما سيكون عليه الأمر بخلاف ذلك.

واجهة برمجة تطبيقات REST

تستخدم الروبوتات واجهة برمجة تطبيقات Discord REST لتنفيذ معظم الإجراءات ، مثل إرسال الرسائل ، وركل / حظر المستخدمين ، وتحديث أذونات المستخدم (التي تشبه إلى حد كبير الأحداث الواردة من WebSocket API). يمكن أيضًا استخدام REST API للاستعلام عن المعلومات ؛ ومع ذلك ، تعتمد الروبوتات بشكل أساسي على الأحداث من WebSocket API بدلاً من ذلك وتقوم بتخزين المعلومات الواردة من أحداث WebSocket مؤقتًا.

هناك عدة أسباب لذلك. الاستعلام عن واجهة برمجة تطبيقات REST للحصول على معلومات المستخدم في كل مرة يتم فيها تلقي حدث إنشاء رسالة ، على سبيل المثال ، لا يتسع نطاقه بسبب حدود معدل REST API. إنها أيضًا زائدة عن الحاجة في معظم الحالات ، حيث توفر WebSocket API المعلومات الضرورية ويجب أن تكون لديك في ذاكرة التخزين المؤقت.

ومع ذلك ، هناك بعض الاستثناءات ، وقد تحتاج أحيانًا إلى معلومات غير موجودة في ذاكرة التخزين المؤقت. عندما يتصل الروبوت في البداية ببوابة WebSocket ، يتم إرسال حدث جاهز وحدث إنشاء نقابة واحد لكل جماعة يوجد فيها الروبوت على هذا الجزء إلى الروبوت حتى يتمكن من ملء ذاكرة التخزين المؤقت الخاصة به بالحالة الحالية. تتضمن أحداث Guild Create للنقابات المكتظة بالسكان فقط معلومات حول المستخدمين عبر الإنترنت. إذا احتاج الروبوت الخاص بك إلى الحصول على معلومات حول مستخدم غير متصل بالإنترنت ، فقد لا تكون المعلومات ذات الصلة موجودة في ذاكرة التخزين المؤقت. في هذه الحالة ، من المنطقي تقديم طلب إلى REST API. أو ، إذا وجدت نفسك كثيرًا ما تحتاج إلى الحصول على معلومات حول المستخدمين غير المتصلين ، يمكنك بدلاً من ذلك اختيار إرسال رمز تشغيل طلب أعضاء النقابة إلى WebSocket API لطلب أعضاء الجماعة غير المتصلين.

استثناء آخر هو إذا كان التطبيق الخاص بك غير متصل بـ WebSocket API على الإطلاق. على سبيل المثال ، إذا كان لدى الروبوت الخاص بك لوحة تحكم على الويب يمكن للمستخدمين تسجيل الدخول وتغيير إعدادات الروبوت في الخادم الخاص بهم. يمكن تشغيل لوحة تحكم الويب في عملية منفصلة دون أي اتصالات بواجهة WebSocket API ولا توجد ذاكرة تخزين مؤقت للبيانات من Discord. قد يحتاج فقط في بعض الأحيان إلى تقديم عدد قليل من طلبات REST API. في هذا النوع من السيناريو ، من المنطقي الاعتماد على واجهة برمجة تطبيقات REST للحصول على المعلومات التي تحتاجها.

أغلفة API

في حين أنه من الجيد دائمًا أن يكون لديك بعض الفهم لكل مستوى من مستويات مكدس التكنولوجيا لديك ، فإن استخدام Discord WebSocket و REST APIs مباشرة يستغرق وقتًا طويلاً ، وعرضة للخطأ ، وغير ضروري بشكل عام ، وخطير في الواقع.

يوفر Discord قائمة منسقة بالمكتبات التي تم فحصها رسميًا ويحذر من:

قد يؤدي استخدام عمليات التنفيذ المخصصة أو المكتبات غير المتوافقة التي تسيء استخدام واجهة برمجة التطبيقات أو تتسبب في فرض حدود مفرطة للمعدلات إلى حظر دائم.

المكتبات التي تم فحصها رسميًا بواسطة Discord هي بشكل عام ناضجة وموثقة جيدًا وتتميز بالتغطية الكاملة لـ Discord API. لن يكون لدى معظم مطوري برامج الروبوت أبدًا سبب وجيه لتطوير تنفيذ مخصص ، إلا بدافع الفضول أو الشجاعة!

في هذا الوقت ، تتضمن المكتبات التي تم فحصها رسميًا تطبيقات لـ Crystal و C # و D و Go و Java و JavaScript و Lua و Nim و PHP و Python و Ruby و Rust و Swift. قد يكون هناك مكتبتان مختلفتان أو أكثر للغة التي تختارها. يمكن أن يكون اختيار أي منها قرارًا صعبًا. بالإضافة إلى التحقق من الوثائق ذات الصلة ، قد ترغب في الانضمام إلى خادم Discord API غير الرسمي والتعرف على نوع المجتمع الذي يقف وراء كل مكتبة.

كيف تصنع ديسكورد بوت

دعونا ننكب على العمل. سنقوم بإنشاء روبوت Discord يتم تعليقه في خادمنا ويستمع إلى خطافات الويب من Ko-fi. Ko-fi هي خدمة تتيح لك قبول التبرعات بسهولة إلى حساب PayPal الخاص بك. من السهل جدًا إعداد webhooks هناك ، على عكس PayPal حيث تحتاج إلى أن يكون لديك حساب تجاري ، لذلك فهو رائع للأغراض التوضيحية أو معالجة التبرعات على نطاق صغير.

عندما يتبرع المستخدم بمبلغ 10 دولارات أو أكثر ، فإن الروبوت سوف يعين له دور Premium Member يغير لون اسمه وينقله إلى أعلى قائمة المستخدمين عبر الإنترنت. بالنسبة لهذا المشروع ، سنستخدم Node.js ومكتبة Discord API تسمى Eris (رابط التوثيق: https://abal.moe/Eris/). إيريس ليست مكتبة JavaScript الوحيدة. يمكنك اختيار discord.js بدلاً من ذلك. الكود الذي سنكتبه سيكون مشابهًا جدًا في كلتا الحالتين.

جانباً ، يوفر Patreon ، وهو معالج تبرعات آخر ، روبوت Discord رسميًا ويدعم تكوين أدوار Discord كمزايا للمساهمين. سنقوم بتنفيذ شيء مشابه ، لكن بالطبع أكثر أساسية.

يتوفر رمز كل خطوة من خطوات البرنامج التعليمي على GitHub (https://github.com/mistval/premium_bot). بعض الخطوات الموضحة في هذا المنشور تحذف الرمز غير المتغير للإيجاز ، لذا اتبع الروابط المقدمة إلى GitHub إذا كنت تعتقد أنك قد تفقد شيئًا ما.

إنشاء حساب بوت

قبل أن نبدأ في كتابة الكود ، نحتاج إلى حساب بوت. قبل أن نتمكن من إنشاء حساب بوت ، نحتاج إلى حساب مستخدم. لإنشاء حساب مستخدم ، اتبع التعليمات الموجودة هنا.

بعد ذلك ، لإنشاء حساب بوت ، نقوم بما يلي:

1) إنشاء تطبيق في بوابة المطور.

لقطة شاشة لبوابة المطورين

2) املأ بعض التفاصيل الأساسية حول التطبيق (لاحظ معرف العميل الموضح هنا - سنحتاجه لاحقًا).

لقطة شاشة لملء التفاصيل الأساسية

3) إضافة مستخدم بوت متصل بالتطبيق.

لقطة شاشة لإضافة مستخدم الروبوت

4) قم بإيقاف تشغيل مفتاح PUBLIC BOT ولاحظ رمز bot المعروض (سنحتاج ذلك لاحقًا أيضًا). إذا قمت بتسريب رمز الروبوت الخاص بك ، على سبيل المثال عن طريق نشره في صورة في منشور مدونة Toptal ، فمن الضروري أن تقوم بإعادة إنشائه على الفور. يمكن لأي شخص يمتلك رمز الروبوت الخاص بك التحكم في حساب الروبوت الخاص بك والتسبب في مشاكل خطيرة ودائمة لك وللمستخدمين لديك.

لقطة شاشة لـ "ظهر روبوت بري"

5) أضف الروبوت إلى مجموعة الاختبار الخاصة بك. لإضافة روبوت إلى نقابة ، استبدل معرف العميل الخاص بها (كما هو موضح سابقًا) في URI التالي وانتقل إليه في المستعرض.

https://discordapp.com/api/oauth2/authorize?scope=bot&client_id=XXX

أضف الروبوت إلى مجموعة الاختبار الخاصة بك

بعد النقر فوق تخويل ، أصبح الروبوت الآن في مجموعة الاختبار الخاصة بي ويمكنني رؤيته في قائمة المستخدمين. إنه غير متصل بالإنترنت ، لكننا سنصلح ذلك قريبًا.

خلق المشروع

بافتراض أن لديك Node.js مثبتًا ، قم بإنشاء مشروع وقم بتثبيت Eris (مكتبة الروبوت التي سنستخدمها) ، و Express (إطار تطبيق ويب سنستخدمه لإنشاء مستمع ويب هوك) ، ومحلل الجسم (لتحليل أجسام خطاف الويب) ).

 mkdir premium_bot cd premium_bot npm init npm install eris express body-parser

الحصول على الروبوت على الإنترنت والاستجابة

لنبدأ بخطوات صغيرة. أولاً ، سنقوم بتشغيل الروبوت على الإنترنت والرد علينا. يمكننا القيام بذلك في 10-20 سطرًا من التعليمات البرمجية. داخل ملف bot.js جديد ، نحتاج إلى إنشاء مثيل Eris Client ، وتمريره رمز bot الخاص بنا (الذي تم الحصول عليه عندما أنشأنا تطبيق bot أعلاه) ، والاشتراك في بعض الأحداث على مثيل Client ، وإخباره بالاتصال بـ Discord . لأغراض التوضيح ، سنقوم بتشفير رمز bot الخاص بنا في ملف bot.js ، ولكن إنشاء ملف تكوين منفصل وإعفائه من التحكم في المصدر يعد ممارسة جيدة.

(رابط كود GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step1.js)

 const eris = require('eris'); // Create a Client instance with our bot token. const bot = new eris.Client('my_token'); // When the bot is connected and ready, log to console. bot.on('ready', () => { console.log('Connected and ready.'); }); // Every time a message is sent anywhere the bot is present, // this event will fire and we will check if the bot was mentioned. // If it was, the bot will attempt to respond with "Present". bot.on('messageCreate', async (msg) => { const botWasMentioned = msg.mentions.find( mentionedUser => mentionedUser.id === bot.user.id, ); if (botWasMentioned) { try { await msg.channel.createMessage('Present'); } catch (err) { // There are various reasons why sending a message may fail. // The API might time out or choke and return a 5xx status, // or the bot may not have permission to send the // message (403 status). console.warn('Failed to respond to mention.'); console.warn(err); } } }); bot.on('error', err => { console.warn(err); }); bot.connect();

إذا سارت الأمور على ما يرام ، فعند تشغيل هذا الرمز باستخدام رمز bot الخاص بك ، Connected and ready. ستتم طباعته على وحدة التحكم وسترى أن الروبوت الخاص بك يأتي عبر الإنترنت في خادم الاختبار الخاص بك. يمكنك ذكر 2 الروبوت الخاص بك إما عن طريق النقر بزر الماوس الأيمن عليه واختيار "ذكر" ، أو بكتابة اسمه مسبوقًا بـ @. يجب أن يرد الروبوت بقول "الحاضر".

الروبوت الخاص بك موجود

2 تعتبر الإشارة طريقة لجذب انتباه مستخدم آخر حتى لو لم يكن موجودًا. سيتم إخطار المستخدم العادي ، عند ذكر ذلك ، عن طريق إشعار سطح المكتب ، وإشعار الدفع عبر الهاتف المحمول ، و / أو ظهور رمز أحمر صغير فوق أيقونة Discord في علبة النظام. تعتمد الطريقة (الأساليب) التي يتم بها إخطار المستخدم على إعداداته وحالته عبر الإنترنت. من ناحية أخرى ، لا تتلقى برامج الروبوت أي نوع من الإشعارات الخاصة عند ذكرها. يتلقون حدث إنشاء رسالة عادي كما يفعلون مع أي رسالة أخرى ، ويمكنهم التحقق من الإشارات المرفقة بالحدث لتحديد ما إذا تم ذكرها.

تسجيل أمر الدفع

الآن بعد أن علمنا أنه يمكننا الحصول على روبوت عبر الإنترنت ، دعنا نتخلص من معالج حدث Message Create الحالي الخاص بنا وننشئ واحدًا جديدًا يتيح لنا إبلاغ الروبوت بأننا تلقينا دفعة من أحد المستخدمين.

لإبلاغ الروبوت بالدفع ، سنصدر أمرًا يشبه هذا:

 pb!addpayment @user_mention payment_amount

على سبيل المثال ، pb!addpayment @Me 10.00 لتسجيل دفعة بقيمة 10.00 دولارات أجريتها أنا.

الرصاص! يشار إلى الجزء ببادئة الأمر. إنها اتفاقية جيدة أن تختار بادئة يجب أن تبدأ بها جميع أوامر الروبوت الخاص بك. يؤدي هذا إلى إنشاء مقياس لمساحة الأسماء للروبوتات ويساعد على تجنب الاصطدام مع الروبوتات الأخرى. تتضمن معظم برامج الروبوت أمر مساعدة ، لكن تخيل الفوضى إذا كان لديك عشرة روبوتات في نقابتك واستجابوا جميعًا للمساعدة ! باستخدام pb! كبادئة ليس حلاً مضمونًا ، حيث قد تكون هناك روبوتات أخرى تستخدم نفس البادئة أيضًا. تسمح معظم الروبوتات الشائعة بتكوين بادئتها على أساس كل جماعة للمساعدة في منع الاصطدام. هناك خيار آخر وهو استخدام الإشارة الخاصة بالبوت كبادئة لها ، على الرغم من أن هذا يجعل إصدار الأوامر أكثر تفصيلاً.

(رابط كود GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step2.js)

 const eris = require('eris'); const PREFIX = 'pb!'; const bot = new eris.Client('my_token'); const commandHandlerForCommandName = {}; commandHandlerForCommandName['addpayment'] = (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); // TODO: Handle invalid command arguments, such as: // 1. No mention or invalid mention. // 2. No amount or invalid amount. return msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`); }; bot.on('messageCreate', async (msg) => { const content = msg.content; // Ignore any messages sent as direct messages. // The bot will only accept commands issued in // a guild. if (!msg.channel.guild) { return; } // Ignore any message that doesn't start with the correct prefix. if (!content.startsWith(PREFIX)) { return; } // Extract the parts of the command and the command name const parts = content.split(' ').map(s => s.trim()).filter(s => s); const commandName = parts[0].substr(PREFIX.length); // Get the appropriate handler for the command, if there is one. const commandHandler = commandHandlerForCommandName[commandName]; if (!commandHandler) { return; } // Separate the command arguments from the command prefix and command name. const args = parts.slice(1); try { // Execute the command. await commandHandler(msg, args); } catch (err) { console.warn('Error handling command'); console.warn(err); } }); bot.on('error', err => { console.warn(err); }); bot.connect();

دعونا نحاول ذلك.

التعامل مع الروبوت

لم يقتصر الأمر على جعل الروبوت يستجيب للأمر pb!addpayment ، ولكننا أنشأنا نمطًا عامًا للتعامل مع الأوامر. يمكننا إضافة المزيد من الأوامر فقط عن طريق إضافة المزيد من المعالجات إلى قاموس commandHandlerForCommandName . لدينا مقومات إطار عمل أوامر بسيط هنا. تعتبر معالجة الأوامر جزءًا أساسيًا من إنشاء روبوت حيث قام العديد من الأشخاص بكتابته وأطر أوامر مفتوحة المصدر يمكنك استخدامها بدلاً من كتابتها بنفسك. تسمح لك أطر الأوامر غالبًا بتحديد فترات الانتظار وأذونات المستخدم المطلوبة والأسماء المستعارة للأوامر وأوصاف الأوامر وأمثلة الاستخدام (لأمر تعليمات تم إنشاؤه تلقائيًا) والمزيد. يأتي Eris مع إطار عمل أوامر مدمج.

عند الحديث عن الأذونات ، يعاني الروبوت الخاص بنا من مشكلة أمنية. يمكن لأي شخص تنفيذ الأمر addpayment . دعنا نقيدها بحيث لا يتمكن سوى مالك الروبوت من استخدامها. سنقوم بإعادة تشكيل قاموس commandHandlerForCommandName وجعله يحتوي على كائنات JavaScript كقيم لها. ستحتوي هذه الكائنات على خاصية execute مع معالج أوامر ، botOwnerOnly ذات قيمة منطقية. سنقوم أيضًا بتشفير معرف المستخدم الخاص بنا في قسم الثوابت في الروبوت حتى يعرف من هو مالكه. يمكنك العثور على معرف المستخدم الخاص بك عن طريق تمكين وضع المطور في إعدادات Discord الخاصة بك ، ثم النقر بزر الماوس الأيمن فوق اسم المستخدم الخاص بك وتحديد نسخ المعرف.

(رابط كود GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step3.js)

 const eris = require('eris'); const PREFIX = 'pb!'; const BOT_OWNER_; const bot = new eris.Client('my_token'); const commandForName = {}; commandForName['addpayment'] = { botOwnerOnly: true, execute: (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); // TODO: Handle invalid command arguments, such as: // 1. No mention or invalid mention. // 2. No amount or invalid amount. return msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`); }, }; bot.on('messageCreate', async (msg) => { try { const content = msg.content; // Ignore any messages sent as direct messages. // The bot will only accept commands issued in // a guild. if (!msg.channel.guild) { return; } // Ignore any message that doesn't start with the correct prefix. if (!content.startsWith(PREFIX)) { return; } // Extract the parts and name of the command const parts = content.split(' ').map(s => s.trim()).filter(s => s); const commandName = parts[0].substr(PREFIX.length); // Get the requested command, if there is one. const command = commandForName[commandName]; if (!command) { return; } // If this command is only for the bot owner, refuse // to execute it for any other user. const authorIsBotOwner = msg.author.id === BOT_OWNER_ID; if (command.botOwnerOnly && !authorIsBotOwner) { return await msg.channel.createMessage('Hey, only my owner can issue that command!'); } // Separate the command arguments from the command prefix and name. const args = parts.slice(1); // Execute the command. await command.execute(msg, args); } catch (err) { console.warn('Error handling message create event'); console.warn(err); } }); bot.on('error', err => { console.warn(err); }); bot.connect();

الآن سيرفض الروبوت بغضب تنفيذ الأمر addpayment إذا حاول أي شخص آخر غير مالك الروبوت تنفيذه.

بعد ذلك ، دعونا نجعل الروبوت يعين دور Premium Member لأي شخص يتبرع بعشرة دولارات أو أكثر. في الجزء العلوي من ملف bot.js:

(رابط كود GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step4.js)

 const eris = require('eris'); const PREFIX = 'pb!'; const BOT_OWNER_; const PREMIUM_CUTOFF = 10; const bot = new eris.Client('my_token'); const premiumRole = { name: 'Premium Member', color: 0x6aa84f, hoist: true, // Show users with this role in their own section of the member list. }; async function updateMemberRoleForDonation(guild, member, donationAmount) { // If the user donated more than $10, give them the premium role. if (guild && member && donationAmount >= PREMIUM_CUTOFF) { // Get the role, or if it doesn't exist, create it. let role = Array.from(guild.roles.values()) .find(role => role.name === premiumRole.name); if (!role) { role = await guild.createRole(premiumRole); } // Add the role to the user, along with an explanation // for the guild log (the "audit log"). return member.addRole(role.id, 'Donated $10 or more.'); } } const commandForName = {}; commandForName['addpayment'] = { botOwnerOnly: true, execute: (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); const guild = msg.channel.guild; const userId = mention.replace(/<@(.*?)>/, (match, group1) => group1); const member = guild.members.get(userId); // TODO: Handle invalid command arguments, such as: // 1. No mention or invalid mention. // 2. No amount or invalid amount. return Promise.all([ msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`), updateMemberRoleForDonation(guild, member, amount), ]); }, };

الآن يمكنني أن أحاول قول pb!addpayment @Me 10.00 على الروبوت أن يعطيني دور Premium Member .

عفوًا ، يظهر خطأ "الأذونات المفقودة" في وحدة التحكم.

 DiscordRESTError: DiscordRESTError [50013]: Missing Permissions index.js:85 code:50013

ليس لدى الروبوت إذن إدارة الأدوار في مجموعة الاختبار ، لذلك لا يمكنه إنشاء الأدوار أو تعيينها. يمكننا منح الروبوت امتياز المسؤول ولن نواجه هذا النوع من المشاكل مرة أخرى ، ولكن كما هو الحال مع أي نظام ، من الأفضل منح المستخدم (أو في هذه الحالة روبوت) الحد الأدنى من الامتيازات التي يحتاجون إليها

يمكننا منح الروبوت إذن إدارة الأدوار عن طريق إنشاء دور في إعدادات الخادم ، وتمكين إذن إدارة الأدوار لهذا الدور وتعيين الدور إلى الروبوت.

ابدأ في إدارة الأدوار

أنشئ دورًا جديدًا

الآن ، عندما أحاول تنفيذ الأمر مرة أخرى ، يتم إنشاء الدور وتعيينه لي ولدي اسم مميز بلون وموقع خاص في قائمة الأعضاء.

تم تعيين دور جديد

في معالج الأوامر ، لدينا تعليق TODO يشير إلى أننا بحاجة إلى التحقق من الوسائط غير الصالحة. دعنا نعتني بذلك الآن.

(رابط كود GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step5.js)

 const commandForName = {}; commandForName['addpayment'] = { botOwnerOnly: true, execute: (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); const guild = msg.channel.guild; const userId = mention.replace(/<@(.*?)>/, (match, group1) => group1); const member = guild.members.get(userId); const userIsInGuild = !!member; if (!userIsInGuild) { return msg.channel.createMessage('User not found in this guild.'); } const amountIsValid = amount && !Number.isNaN(amount); if (!amountIsValid) { return msg.channel.createMessage('Invalid donation amount'); } return Promise.all([ msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`), updateMemberRoleForDonation(guild, member, amount), ]); }, };

هذا هو الكود الكامل حتى الآن:

(رابط كود GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step5.js)

 const eris = require('eris'); const PREFIX = 'pb!'; const BOT_OWNER_; const PREMIUM_CUTOFF = 10; const bot = new eris.Client('my_token'); const premiumRole = { name: 'Premium Member', color: 0x6aa84f, hoist: true, // Show users with this role in their own section of the member list. }; async function updateMemberRoleForDonation(guild, member, donationAmount) { // If the user donated more than $10, give them the premium role. if (guild && member && donationAmount >= PREMIUM_CUTOFF) { // Get the role, or if it doesn't exist, create it. let role = Array.from(guild.roles.values()) .find(role => role.name === premiumRole.name); if (!role) { role = await guild.createRole(premiumRole); } // Add the role to the user, along with an explanation // for the guild log (the "audit log"). return member.addRole(role.id, 'Donated $10 or more.'); } } const commandForName = {}; commandForName['addpayment'] = { botOwnerOnly: true, execute: (msg, args) => { const mention = args[0]; const amount = parseFloat(args[1]); const guild = msg.channel.guild; const userId = mention.replace(/<@(.*?)>/, (match, group1) => group1); const member = guild.members.get(userId); const userIsInGuild = !!member; if (!userIsInGuild) { return msg.channel.createMessage('User not found in this guild.'); } const amountIsValid = amount && !Number.isNaN(amount); if (!amountIsValid) { return msg.channel.createMessage('Invalid donation amount'); } return Promise.all([ msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`), updateMemberRoleForDonation(guild, member, amount), ]); }, }; bot.on('messageCreate', async (msg) => { try { const content = msg.content; // Ignore any messages sent as direct messages. // The bot will only accept commands issued in // a guild. if (!msg.channel.guild) { return; } // Ignore any message that doesn't start with the correct prefix. if (!content.startsWith(PREFIX)) { return; } // Extract the parts and name of the command const parts = content.split(' ').map(s => s.trim()).filter(s => s); const commandName = parts[0].substr(PREFIX.length); // Get the requested command, if there is one. const command = commandForName[commandName]; if (!command) { return; } // If this command is only for the bot owner, refuse // to execute it for any other user. const authorIsBotOwner = msg.author.id === BOT_OWNER_ID; if (command.botOwnerOnly && !authorIsBotOwner) { return await msg.channel.createMessage('Hey, only my owner can issue that command!'); } // Separate the command arguments from the command prefix and name. const args = parts.slice(1); // Execute the command. await command.execute(msg, args); } catch (err) { console.warn('Error handling message create event'); console.warn(err); } }); bot.on('error', err => { console.warn(err); }); bot.connect();

يجب أن يمنحك هذا فكرة أساسية جيدة عن كيفية إنشاء روبوت Discord. سنرى الآن كيفية دمج الروبوت مع Ko-fi. إذا كنت ترغب في ذلك ، يمكنك إنشاء خطاف ويب في لوحة المعلومات الخاصة بك في Ko-fi ، وتأكد من تكوين جهاز التوجيه الخاص بك لإعادة توجيه المنفذ 80 ، وإرسال خطافات الويب التجريبية الحقيقية إلى نفسك. لكنني سأستخدم ساعي البريد لمحاكاة الطلبات.

تقدم Webhooks من Ko-fi حمولات تبدو كالتالي:

 data: { "message_id":"3a1fac0c-f960-4506-a60e-824979a74e74", "timestamp":"2017-08-21T13:04:30.7296166Z", "type":"Donation","from_name":"John Smith", "message":"Good luck with the integration!", "amount":"3.00", "url":"https://ko-fi.com" }

لنقم بإنشاء ملف مصدر جديد يسمى webhook_listener.js ونستخدم Express للاستماع إلى webhooks. سيكون لدينا مسار Express واحد فقط ، وهذا لأغراض العرض التوضيحي ، لذلك لن نقلق كثيرًا بشأن استخدام بنية دليل اصطلاحية. سنضع كل منطق خادم الويب في ملف واحد.

(رابط كود GitHub: https://github.com/mistval/premium_bot/blob/master/src/webhook_listener_step6.js)

 const express = require('express'); const app = express(); const PORT = process.env.PORT || 80; class WebhookListener { listen() { app.get('/kofi', (req, res) => { res.send('Hello'); }); app.listen(PORT); } } const listener = new WebhookListener(); listener.listen(); module.exports = listener;

ثم دعونا نطلب الملف الجديد أعلى bot.js حتى يبدأ المستمع عند تشغيل bot.js.

(رابط كود GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step6.js)

 const eris = require('eris'); const webhookListener = require('./webhook_listener.js');

بعد بدء الروبوت ، من المفترض أن ترى كلمة "مرحبًا" عند الانتقال إلى http: // localhost / kofi في متصفحك.

الآن ، دعنا WebhookListener يعالج البيانات من webhook ويصدر حدثًا. والآن بعد أن اختبرنا أن متصفحنا يمكنه الوصول إلى المسار ، دعنا نغير المسار إلى مسار POST ، لأن خطاف الويب من Ko-fi سيكون طلب POST.

(رابط كود GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step7.js)

 const express = require('express'); const bodyParser = require('body-parser'); const EventEmitter = require('events'); const PORT = process.env.PORT || 80; const app = express(); app.use(bodyParser.json()); class WebhookListener extends EventEmitter { listen() { app.post('/kofi', (req, res) => { const data = req.body.data; const { message, timestamp } = data; const amount = parseFloat(data.amount); const senderName = data.from_name; const paymentId = data.message_id; const paymentSource = 'Ko-fi'; // The OK is just for us to see in Postman. Ko-fi doesn't care // about the response body, it just wants a 200. res.send({ status: 'OK' }); this.emit( 'donation', paymentSource, paymentId, timestamp, amount, senderName, message, ); }); app.listen(PORT); } } const listener = new WebhookListener(); listener.listen(); module.exports = listener;

Next we need to have the bot listen for the event, decide which user donated, and assign them a role. To decide which user donated, we'll try to find a user whose username is a substring of the message received from Ko-fi. Donors must be instructed to provide their username (with the discriminator) in the message than they write when they make their donation.

At the bottom of bot.js:

(GitHub code link: https://github.com/mistval/premium_bot/blob/master/src/bot_step7.js)

 function findUserInString(str) { const lowercaseStr = str.toLowerCase(); // Look for a matching username in the form of username#discriminator. const user = bot.users.find( user => lowercaseStr.indexOf(`${user.username.toLowerCase()}#${user.discriminator}`) !== -1, ); return user; } async function onDonation( paymentSource, paymentId, timestamp, amount, senderName, message, ) { try { const user = findUserInString(message); const guild = user ? bot.guilds.find(guild => guild.members.has(user.id)) : null; const guildMember = guild ? guild.members.get(user.id) : null; return await updateMemberRoleForDonation(guild, guildMember, amount); } catch (err) { console.warn('Error handling donation event.'); console.warn(err); } } webhookListener.on('donation', onDonation); bot.connect();

In the onDonation function, we see two representations of a user: as a User, and as a Member. These both represent the same person, but the Member object contains guild-specific information about the User, such as their roles in the guild and their nickname. Since we want to add a role, we need to use the Member representation of the user. Each User in Discord has one Member representation for each guild that they are in.

Now I can use Postman to test the code.

Testing with Postman

I receive a 200 status code, and I get the role granted to me in the server.

If the message from Ko-fi does not contain a valid username; however, nothing happens. The donor doesn't get a role, and we are not aware that we received an orphaned donation. Let's add a log for logging donations, including donations that can't be attributed to a guild member.

First we need to create a log channel in Discord and get its channel ID. The channel ID can be found using the developer tools, which can be enabled in Discord's settings. Then you can right-click any channel and click “Copy ID.”

The log channel ID should be added to the constants section of bot.js.

(GitHub code link: https://github.com/mistval/premium_bot/blob/master/src/bot_step8.js)

 const LOG_CHANNEL_;

And then we can write a logDonation function.

(GitHub code link: https://github.com/mistval/premium_bot/blob/master/src/bot_step8.js)

 function logDonation(member, donationAmount, paymentSource, paymentId, senderName, message, timestamp) { const isKnownMember = !!member; const memberName = isKnownMember ? `${member.username}#${member.discriminator}` : 'Unknown'; const embedColor = isKnownMember ? 0x00ff00 : 0xff0000; const logMessage = { embed: { title: 'Donation received', color: embedColor, timestamp: timestamp, fields: [ { name: 'Payment Source', value: paymentSource, inline: true }, { name: 'Payment ID', value: paymentId, inline: true }, { name: 'Sender', value: senderName, inline: true }, { name: 'Donor Discord name', value: memberName, inline: true }, { name: 'Donation amount', value: donationAmount.toString(), inline: true }, { name: 'Message', value: message, inline: true }, ], } } bot.createMessage(LOG_CHANNEL_ID, logMessage); }

Now we can update onDonation to call the log function:

 async function onDonation( paymentSource, paymentId, timestamp, amount, senderName, message, ) { try { const user = findUserInString(message); const guild = user ? bot.guilds.find(guild => guild.members.has(user.id)) : null; const guildMember = guild ? guild.members.get(user.id) : null; return await Promise.all([ updateMemberRoleForDonation(guild, guildMember, amount), logDonation(guildMember, amount, paymentSource, paymentId, senderName, message, timestamp), ]); } catch (err) { console.warn('Error updating donor role and logging donation'); console.warn(err); } }

Now I can invoke the webhook again, first with a valid username, and then without one, and I get two nice log messages in the log channel.

Two nice messages

Previously, we were just sending strings to Discord to display as messages. The more complex JavaScript object that we create and send to Discord in the new logDonation function is a special type of message referred to as a rich embed. An embed gives you some scaffolding for making attractive messages like those shown. Only bots can create embeds, users cannot.

Now we are being notified of donations, logging them, and rewarding our supporters. We can also add donations manually with the addpayment command in case a user forgets to specify their username when they donate. Let's call it a day.

The completed code for this tutorial is available on GitHub here https://github.com/mistval/premium_bot

الخطوات التالية

We've successfully created a bot that can help us track donations. Is this something we can actually use? Well, perhaps. It covers the basics, but not much more. Here are some shortcomings you might want to think about first:

  1. If a user leaves our guild (or if they weren't even in our guild in the first place), they will lose their Premium Member role, and if they rejoin, they won't get it back. We should store payments by user ID in a database, so if a premium member rejoins, we can give them their role back and maybe send them a nice welcome-back message if we were so inclined.
  2. Paying in installments won't work. If a user sends $5 and then later sends another $5, they won't get a premium role. Similar to the above issue, storing payments in a database and issuing the Premium Member role when the total payments from a user reaches $10 would help here.
  3. It's possible to receive the same webhook more than once, and this bot will record the payment multiple times. If Ko-fi doesn't receive or doesn't properly acknowledge a code 200 response from the webhook listener, it will try to send the webhook again later. Keeping track of payments in a database and ignoring webhooks with the same ID as previously received ones would help here.
  4. Our webhook listener isn't very secure. Anyone could forge a webhook and get a Premium Member role for free. Ko-fi doesn't seem to sign webhooks, so you'll have to rely on either no one knowing your webhook address (bad), or IP whitelisting (a bit better).
  5. The bot is designed to be used in one guild only.

Interview: When a Bot Gets Big

There are over a dozen websites for listing Discord bots and making them available to the public at large, including DiscordBots.org and Discord.Bots.gg. Although Discord bots are mostly the foray of small-time hobbyists, some bots experience tremendous popularity and maintaining them evolves into a complex and demanding job.

By guild-count, Rythm is currently the most widespread bot on Discord. Rythm is a music bot whose specialty is connecting to voice channels in Discord and playing music requested by users. Rythm is currently present in over 2,850,000 guilds containing a sum population of around 90 million users, and at its peak plays audio for around 100,000 simultaneous users in 20,000 separate guilds. Rythm's creator and main developer, ImBursting, kindly agreed to answer a few questions about what it's like to develop and maintain a large-scale bot like Rythm.

Interviewer: Can you tell us a bit about Rythm's high level architecture and how it's hosted?

ImBursting: Rythm is scaled across 9 physical servers, each have 32 cores, 96GB of RAM and a 10gbps connection. These servers are collocated at a data center with help from a small hosting company, GalaxyGate.

I imagine that when you started working on Rythm, you didn't design it to scale anywhere near as much as it has. Can you tell us about about how Rythm started, and its technical evolution over time?

Rythm's first evolution was written in Python, which isn't a very performant language, so around the time we hit 10,000 servers (after many scaling attempts) I realised this was the biggest roadblock and so I began recoding the bot to Java, the reason being Java's audio libraries were a lot more optimised and it was generally a better suited language for such a huge application. After re-coding, performance improved tenfold and kept the issues at bay for a while. And then we hit the 300,000 servers milestone when issues started surfacing again, at which point I realised that more scaling was required since one JVM just wasn't able to handle all that. So we slowly started implementing improvements and major changes like tuning the garbage collector and splitting voice connections onto separate microservices using an open source server called Lavalink. This improved performance quite a bit but the final round of infrastructure was when we split this into 9 seperate clusters to run on 9 physical servers, and made custom gateway and stats microservices to make sure everything ran smoothly like it would on one machine.

I noticed that Rythm has a canary version and you get some help from other developers and staff. I imagine you and your team must put a lot of effort into making sure things are done right. Can you tell us about what processes are involved in updating Rythm?

Rythm canary is the alpha bot we use to test freshly made features and performance improvements before usually deploying them to Rythm 2 to test on a wider scale and then production Rythm. The biggest issue we encounter is really long reboot times due to Discord rate limits, and is the reason I try my best to make sure an update is ready before deciding to push it.

I do get a lot of help from volunteer developers and people who genuinely want to help the community, I want to make sure everything is done correctly and that people will always get their questions answered and get the best support possible which means im constantly on the lookout for new opportunities.

Wrapping It Up

Discord's days of being a new kid on the block are past, and it is now one of the largest real-time communication platforms in the world. While Discord bots are largely the foray of small-time hobbyists, we may well see commercial opportunities increase as the population of the service continues to increase. Some companies, like the aforementioned Patreon, have already waded in.

In this article, we saw a high-level overview of Discord's user interface, a high-level overview of its APIs, a complete lesson in Discord bot programming, and we got to hear about what it's like to operate a bot at enterprise scale. I hope you come away interested in the technology and feeling like you understand the fundamentals of how it works.

Chatbots are generally fun, except when their responses to your intricate queries have the intellectual the depth of a cup of water. To ensure a great UX for your users see The Chat Crash - When a Chatbot Fails by the Toptal Design Blog for 5 design problems to avoid.

الموضوعات ذات الصلة: JS Best Practices: إنشاء روبوت للخلاف باستخدام TypeScript وحقن التبعية