كيف تصنع ديسكورد بوت؟| الجزء الثاني

Discord bot

نتابع ما قلنا في المقالة السابقة: كيف تصنع ديسكورد بوت؟| الجزء الأول

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

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

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

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

كيف تصنع ديسكورد بوت؟| الجزء الثاني

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

Discord bot

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

Discord bot

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

Discord bot

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

Discord bot

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

خلق المشروع

بافتراض أن لديك 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 ولكن إنشاء ملف تكوين منفصل وإعفائه من التحكم في المصدر يعد ممارسة جيدة.

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();

إذا سارت الأمور على ما يرام فعند تشغيل هذا الرمز باستخدام رمز الروبوت الخاص بك يكون متصلًا وجاهزًا. ستتم طباعته على وحدة التحكم وسترى أن الروبوت الخاص بك يأتي عبر الإنترنت في خادم الاختبار الخاص بك. يمكنك ذكر2 الروبوت الخاص بك إما عن طريق النقر بزر الماوس الأيمن عليه واختيار “mention” أو بكتابة اسمه مسبوقًا بـ @. يجب أن يرد الروبوت بقول “present”.

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

تسجيل أمر الدفع (record payment command)

الآن بعد أن علمنا أنه بإمكاننا الحصول على روبوت عبر الإنترنت فلنتخلص من معالج حدث إنشاء الرسائل (message create event) الحالي الخاص بنا وأنشئ واحدًا جديدًا يتيح لنا إبلاغ الروبوت بأننا تلقينا دفعة من أحد المستخدمين.

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

pb!addpayment @user_mention payment_amount

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

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

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 وجعله يحتوي على كائنات جافا سكريبت كقيم له. ستحتوي هذه الكائنات على خاصية execute مع معالج أوامر وخاصية botOwnerOnly ذات قيمة منطقية. سنقوم أيضًا بتشفير معرف المستخدم الخاص بنا في قسم الثوابت في الروبوت حتى يعرف من هو مالكه. يمكنك العثور على معرف المستخدم الخاص بك عن طريق تمكين وضع المطور في إعدادات Discord الخاصة بك ثم النقر بزر الماوس الأيمن فوق اسم المستخدم الخاص بك وتحديد نسخ المعرف.

const eris = require('eris');

const PREFIX = 'pb!';
const BOT_OWNER_ID = '123456789';

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:

const PREFIX = 'pb!';
const BOT_OWNER_ID = '523407722880827415';
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! addpaymentMe 10.00 ويجب على الروبوت تعيين دور العضو المميز لي.

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

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

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

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

Discord bot

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

روبوت Discord

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

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

const eris = require('eris');

const PREFIX = 'pb!';
const BOT_OWNER_ID = '123456789';
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.

منشور ذات صلة
سلسلة دروس: Discord bot

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

السلة