Cara Membuat Bot Perselisihan: Tinjauan dan Tutorial
Diterbitkan: 2022-03-11Discord adalah platform perpesanan waktu nyata yang menyebut dirinya sebagai "obrolan suara dan teks all-in-one untuk para gamer." Karena antarmuka yang apik, kemudahan penggunaan, dan fitur yang luas, Discord telah mengalami pertumbuhan pesat dan menjadi semakin populer bahkan di antara mereka yang tidak terlalu tertarik dengan video game. Antara Mei 2017 dan Mei 2018, basis penggunanya meledak dari 45 juta pengguna menjadi lebih dari 130 juta, dengan lebih dari dua kali lipat pengguna harian Slack.
Salah satu fitur Discord yang paling menarik dari perspektif pengembang chatbot adalah dukungannya yang kuat untuk bot yang dapat diprogram yang membantu mengintegrasikan Discord dengan dunia luar dan memberi pengguna pengalaman yang lebih menarik. Bot ada di mana-mana di Discord dan menyediakan berbagai layanan, termasuk bantuan moderasi, game, musik, pencarian internet, pemrosesan pembayaran, dan banyak lagi.
Dalam tutorial bot Discord ini, kita akan mulai dengan membahas antarmuka pengguna Discord dan REST dan API WebSocket untuk bot sebelum beralih ke tutorial di mana kita akan menulis bot Discord sederhana dalam JavaScript. Akhirnya, kami akan mendengar dari pengembang, berdasarkan metrik tertentu, bot Discord yang paling populer dan pengalamannya mengembangkan dan memelihara infrastruktur dan basis kode yang signifikan.
Antarmuka Pengguna Perselisihan
Sebelum kita membahas detail teknis, penting untuk memahami bagaimana pengguna berinteraksi dengan Discord dan bagaimana Discord menampilkan dirinya kepada pengguna. Cara menampilkan dirinya ke bot secara konseptual serupa (tapi tentu saja non-visual). Faktanya, aplikasi Discord resmi dibangun di atas API yang sama dengan yang digunakan bot. Secara teknis dimungkinkan untuk menjalankan bot di dalam akun pengguna biasa dengan sedikit modifikasi, tetapi ini dilarang oleh persyaratan layanan Discord. Bot diperlukan untuk berjalan di akun bot.
Berikut tampilan browser versi 1 dari aplikasi Discord yang berjalan di dalam Chrome.
1 Discord UI untuk aplikasi desktop hampir sama dengan aplikasi web, dikemas dengan Electron. Aplikasi iOS dibangun dengan React Native. Aplikasi Android adalah kode Java Android asli.
Mari kita hancurkan.
1. Daftar Server
Sepanjang jalan di sebelah kiri adalah daftar server tempat saya menjadi anggota. Jika Anda terbiasa dengan Slack, server dianalogikan dengan ruang kerja Slack, dan mewakili sekelompok pengguna yang dapat berinteraksi satu sama lain dalam satu atau lebih saluran di server. Server dikelola oleh pembuatnya dan/atau staf apa pun yang mereka pilih dan pilih untuk mendelegasikan tanggung jawab. Pembuat dan/atau staf menentukan aturan, struktur saluran di server, dan mengelola pengguna.
Dalam kasus saya, server Discord API ada di bagian atas daftar server saya. Ini adalah tempat yang bagus untuk mendapatkan bantuan dan berbicara dengan pengembang lain. Di bawah ini adalah server yang saya buat bernama Test . Kami akan menguji bot yang kami buat nanti di sana. Di bawahnya terdapat tombol untuk membuat server baru. Siapa pun dapat membuat server dengan beberapa klik.
Perhatikan bahwa sementara istilah yang digunakan dalam antarmuka pengguna Discord adalah Server , istilah yang digunakan dalam dokumentasi pengembang dan API adalah Guild . Setelah kita beralih ke topik teknis, kita akan beralih ke pembicaraan tentang Persekutuan . Kedua istilah tersebut dapat dipertukarkan.
2. Daftar Saluran
Tepat di sebelah kanan daftar server adalah daftar saluran untuk server yang sedang saya lihat (dalam hal ini, server Discord API). Saluran dapat dipecah menjadi sejumlah kategori yang berubah-ubah. Di server Discord API, kategorinya termasuk INFORMASI, UMUM, dan LIBS, seperti yang ditunjukkan. Setiap saluran berfungsi sebagai ruang obrolan di mana pengguna dapat mendiskusikan topik apa pun yang didedikasikan untuk saluran tersebut. Saluran yang sedang kami lihat (info) memiliki latar belakang yang lebih terang. Saluran yang memiliki pesan baru sejak terakhir kali kami melihatnya memiliki warna teks putih.
3. Tampilan Saluran
Ini adalah tampilan saluran di mana kita dapat melihat apa yang dibicarakan pengguna di saluran yang sedang kita lihat. Kita dapat melihat satu pesan di sini, hanya sebagian yang terlihat. Ini adalah daftar tautan ke server dukungan untuk pustaka bot Discord individual. Administrator server telah mengonfigurasi saluran ini sehingga pengguna biasa seperti saya tidak dapat mengirim pesan di dalamnya. Administrator menggunakan saluran ini sebagai papan buletin untuk memposting beberapa informasi penting yang dapat dengan mudah dilihat dan tidak akan tenggelam oleh obrolan.
4. Daftar Pengguna
Semua jalan di sebelah kanan adalah daftar pengguna yang saat ini online di server ini. Pengguna diatur ke dalam kategori yang berbeda dan nama mereka memiliki warna yang berbeda. Ini adalah hasil dari peran yang mereka miliki. Sebuah peran menjelaskan kategori apa (jika ada) pengguna akan muncul di bawah, apa warna nama mereka seharusnya, dan izin apa yang mereka miliki di server. Seorang pengguna dapat memiliki lebih dari satu peran (dan sangat sering), dan ada beberapa prioritas matematika yang menentukan apa yang terjadi dalam kasus itu. Minimal, setiap pengguna memiliki peran @semua orang. Peran lain dibuat dan ditetapkan oleh staf server.
5. Masukan Teks
Ini adalah input teks di mana saya bisa mengetik dan mengirim pesan, jika saya diizinkan. Karena saya tidak memiliki izin untuk mengirim pesan di saluran ini, saya tidak dapat mengetik di sini.
6. Pengguna
Ini adalah pengguna saat ini. Saya mengatur nama pengguna saya menjadi "Saya", untuk membantu agar saya tidak bingung, dan karena saya buruk dalam memilih nama. Di bawah nama pengguna saya adalah nomor (#9484) yang merupakan pembeda saya. Mungkin ada banyak pengguna lain yang bernama “Saya”, tetapi hanya saya yang “Saya#9484”. Dimungkinkan juga bagi saya untuk menetapkan nama panggilan untuk diri saya sendiri pada basis per server, sehingga saya dapat dikenal dengan nama yang berbeda di server yang berbeda.
Ini adalah bagian dasar dari antarmuka pengguna Discord, tetapi ada lebih banyak lagi. Sangat mudah untuk mulai menggunakan Discord bahkan tanpa membuat akun, jadi silakan luangkan waktu sebentar untuk melihat-lihat. Anda dapat memasuki Discord dengan mengunjungi beranda Discord, mengklik “buka Discord di browser,” memilih nama pengguna, dan mungkin memainkan satu atau dua putaran yang menyegarkan dari “klik gambar bus.”
API Perselisihan
Discord API terdiri dari dua bagian terpisah: WebSocket dan REST API. Secara garis besar, WebSocket API digunakan untuk menerima event dari Discord secara real time, sedangkan REST API digunakan untuk melakukan tindakan di dalam Discord.
API WebSocket
WebSocket API digunakan untuk menerima peristiwa dari Discord, termasuk pembuatan pesan, penghapusan pesan, peristiwa kick/ban pengguna, pembaruan izin pengguna, dan banyak lagi. Komunikasi dari bot ke WebSocket API di sisi lain lebih terbatas. Bot menggunakan WebSocket API untuk meminta koneksi, mengidentifikasi dirinya sendiri, detak jantung, mengelola koneksi suara, dan melakukan beberapa hal mendasar lainnya. Anda dapat membaca detail lebih lanjut di dokumentasi gateway Discord (satu koneksi ke WebSocket API disebut sebagai gateway). Untuk melakukan tindakan lain, REST API digunakan.
Acara dari WebSocket API berisi muatan termasuk informasi yang bergantung pada jenis acara. Misalnya, semua acara Buat Pesan akan disertai dengan objek pengguna yang mewakili penulis pesan. Namun, objek pengguna saja tidak berisi semua informasi yang perlu diketahui tentang pengguna. Misalnya, tidak ada informasi yang disertakan tentang izin pengguna. Jika Anda memerlukan informasi lebih lanjut, Anda dapat menanyakan REST API untuk itu, tetapi untuk alasan yang dijelaskan lebih lanjut di bagian berikutnya, Anda biasanya harus mengakses cache yang seharusnya Anda buat dari muatan yang diterima dari peristiwa sebelumnya. Ada sejumlah peristiwa yang mengirimkan muatan yang relevan dengan izin pengguna, termasuk namun tidak terbatas pada Pembuatan Guild, Pembaruan Peran Guild , dan Pembaruan Saluran .
Bot dapat hadir dalam maksimum 2.500 serikat per koneksi WebSocket. Untuk memungkinkan bot hadir di lebih banyak guild, bot harus menerapkan sharding dan membuka beberapa koneksi WebSocket terpisah ke Discord. Jika bot Anda berjalan di dalam satu proses pada satu node, ini hanya menambah kerumitan bagi Anda yang mungkin tampak tidak perlu. Tetapi jika bot Anda sangat populer dan perlu memiliki back-end yang didistribusikan di seluruh node yang terpisah, dukungan sharding Discord membuat ini jauh lebih mudah daripada sebaliknya.
API REST
Discord REST API digunakan oleh bot untuk melakukan sebagian besar tindakan, seperti mengirim pesan, menendang/melarang pengguna, dan memperbarui izin pengguna (secara umum analog dengan peristiwa yang diterima dari WebSocket API). REST API juga dapat digunakan untuk meminta informasi; namun, bot terutama mengandalkan peristiwa dari WebSocket API dan menyimpan informasi yang diterima dari peristiwa WebSocket dalam cache.
Ada beberapa alasan untuk ini. Membuat kueri REST API untuk mendapatkan informasi pengguna setiap kali peristiwa Pembuatan Pesan diterima, misalnya, tidak dapat diskalakan karena batas kecepatan REST API. Ini juga berlebihan dalam banyak kasus, karena WebSocket API memberikan informasi yang diperlukan dan Anda harus menyimpannya di cache Anda.
Namun, ada beberapa pengecualian, dan terkadang Anda mungkin memerlukan informasi yang tidak ada di cache. Saat bot awalnya terhubung ke gateway WebSocket, acara Ready dan satu acara Guild Create per guild tempat bot berada di pecahan itu awalnya dikirim ke bot sehingga dapat mengisi cache-nya dengan status saat ini. Acara Guild Create untuk guild yang padat penduduknya hanya menyertakan informasi tentang pengguna online. Jika bot Anda perlu mendapatkan informasi tentang pengguna offline, informasi yang relevan mungkin tidak ada di cache Anda. Dalam hal ini, masuk akal untuk membuat permintaan ke REST API. Atau, jika Anda sering merasa perlu mendapatkan informasi tentang pengguna offline, Anda dapat memilih untuk mengirim opcode Request Guild Members ke WebSocket API untuk meminta anggota guild offline.
Pengecualian lainnya adalah jika aplikasi Anda tidak terhubung ke WebSocket API sama sekali. Misalnya jika bot Anda memiliki dasbor web tempat pengguna dapat masuk dan mengubah pengaturan bot di server mereka. Dasbor web dapat berjalan dalam proses terpisah tanpa koneksi ke WebSocket API dan tidak ada cache data dari Discord. Mungkin hanya perlu sesekali membuat beberapa permintaan REST API. Dalam skenario seperti ini, masuk akal untuk mengandalkan REST API untuk mendapatkan informasi yang Anda butuhkan.
Pembungkus API
Meskipun selalu merupakan ide yang baik untuk memiliki pemahaman tentang setiap tingkat tumpukan teknologi Anda, menggunakan Discord WebSocket dan REST API secara langsung memakan waktu, rawan kesalahan, umumnya tidak perlu, dan pada kenyataannya berbahaya.
Discord menyediakan daftar perpustakaan yang diperiksa secara resmi dan memperingatkan bahwa:
Menggunakan implementasi khusus atau pustaka yang tidak sesuai yang menyalahgunakan API atau menyebabkan batas kecepatan yang berlebihan dapat mengakibatkan larangan permanen.
Pustaka yang secara resmi diperiksa oleh Discord umumnya matang, terdokumentasi dengan baik, dan menampilkan cakupan penuh Discord API. Sebagian besar pengembang bot tidak akan pernah memiliki alasan yang baik untuk mengembangkan implementasi kustom, kecuali karena penasaran, atau keberanian!
Saat ini, perpustakaan yang diperiksa secara resmi mencakup implementasi untuk Crystal, C#, D, Go, Java, JavaScript, Lua, Nim, PHP, Python, Ruby, Rust, dan Swift. Mungkin ada dua atau lebih perpustakaan yang berbeda untuk bahasa pilihan Anda. Memilih mana yang akan digunakan bisa menjadi keputusan yang sulit. Selain memeriksa dokumentasi masing-masing, Anda mungkin ingin bergabung dengan server Discord API tidak resmi dan merasakan komunitas seperti apa yang ada di balik setiap perpustakaan.
Cara Membuat Bot Perselisihan
Mari kita turun ke bisnis. Kami akan membuat bot Discord yang nongkrong di server kami dan mendengarkan webhook dari Ko-fi. Ko-fi adalah layanan yang memungkinkan Anda menerima donasi dengan mudah ke akun PayPal Anda. Sangat mudah untuk menyiapkan webhook di sana, berbeda dengan PayPal yang mengharuskan Anda memiliki akun bisnis, jadi sangat bagus untuk tujuan demonstrasi atau pemrosesan donasi skala kecil.
Ketika seorang pengguna menyumbangkan $10 atau lebih, bot akan memberikan mereka peran Premium Member
yang mengubah warna nama mereka dan memindahkan mereka ke bagian atas daftar pengguna online. Untuk proyek ini, kita akan menggunakan Node.js dan perpustakaan Discord API yang disebut Eris (tautan dokumentasi: https://abal.moe/Eris/). Eris bukan satu-satunya pustaka JavaScript. Anda dapat memilih discord.js sebagai gantinya. Kode yang akan kita tulis akan sangat mirip.
Selain itu, Patreon, pemroses donasi lain, menyediakan bot Discord resmi dan mendukung konfigurasi peran Discord sebagai manfaat kontributor. Kami akan menerapkan sesuatu yang serupa, tetapi tentu saja lebih mendasar.
Kode untuk setiap langkah tutorial tersedia di GitHub (https://github.com/mistval/premium_bot). Beberapa langkah yang ditunjukkan dalam posting ini menghilangkan kode yang tidak berubah untuk singkatnya, jadi ikuti tautan yang disediakan ke GitHub jika Anda merasa ada sesuatu yang hilang.
Membuat Akun Bot
Sebelum kita dapat mulai menulis kode, kita memerlukan akun bot. Sebelum kita dapat membuat akun bot, kita memerlukan akun pengguna. Untuk membuat akun pengguna, ikuti petunjuk di sini.
Kemudian, untuk membuat akun bot, kita:
1) Buat aplikasi di portal pengembang.
2) Isi beberapa detail dasar tentang aplikasi (perhatikan ID KLIEN yang ditampilkan di sini—kami akan membutuhkannya nanti).
3) Tambahkan pengguna bot yang terhubung ke aplikasi.
4) Matikan sakelar PUBLIC BOT dan perhatikan token bot yang ditampilkan (kita akan membutuhkan ini nanti juga). Jika Anda pernah membocorkan token bot Anda, misalnya dengan memublikasikannya dalam gambar di posting Blog Toptal, Anda harus segera membuatnya kembali. Siapa pun yang memiliki token bot Anda dapat mengontrol akun bot Anda dan berpotensi menyebabkan masalah serius dan permanen bagi Anda dan pengguna Anda.
5) Tambahkan bot ke serikat uji Anda. Untuk menambahkan bot ke serikat, ganti ID kliennya (ditunjukkan sebelumnya) ke dalam URI berikut dan navigasikan ke sana di browser.
https://discordapp.com/api/oauth2/authorize?scope=bot&client_id=XXX
Setelah mengklik otorisasi, bot sekarang ada di guild pengujian saya dan saya dapat melihatnya di daftar pengguna. Ini offline, tapi kami akan segera memperbaikinya.
Membuat Proyek
Dengan asumsi Anda telah menginstal Node.js, buat proyek dan instal Eris (pustaka bot yang akan kami gunakan), Express (kerangka kerja aplikasi web yang akan kami gunakan untuk membuat pendengar webhook), dan body-parser (untuk mem-parsing badan webhook ).
mkdir premium_bot cd premium_bot npm init npm install eris express body-parser
Mendapatkan Bot Online dan Responsif
Mari kita mulai dengan langkah kecil. Pertama, kami hanya akan membuat bot online dan merespons kami. Kita dapat melakukan ini dalam 10-20 baris kode. Di dalam file bot.js baru, kita perlu membuat instance Eris Client, meneruskannya token bot kami (diperoleh saat kami membuat aplikasi bot di atas), berlangganan beberapa acara pada instance Klien, dan memintanya untuk terhubung ke Discord . Untuk tujuan demonstrasi, kami akan melakukan hardcode token bot kami ke dalam file bot.js, tetapi membuat file konfigurasi terpisah dan mengecualikannya dari kontrol sumber adalah praktik yang baik.
(Tautan kode 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();
Jika semuanya berjalan dengan baik, ketika Anda menjalankan kode ini dengan token bot Anda sendiri, Connected and ready.
akan dicetak ke konsol dan Anda akan melihat bot Anda online di server pengujian Anda. Anda dapat menyebutkan 2 bot Anda dengan mengklik kanan dan memilih “Sebutkan”, atau dengan mengetikkan namanya yang diawali dengan @. Bot harus merespons dengan mengatakan "Hadir."
2 Menyebutkan adalah cara untuk mendapatkan perhatian pengguna lain meskipun mereka tidak hadir. Pengguna biasa, jika disebutkan, akan diberi tahu melalui pemberitahuan desktop, pemberitahuan push seluler, dan/atau ikon merah kecil yang muncul di atas ikon Discord di baki sistem. Cara pengguna diberi tahu bergantung pada pengaturan dan status online mereka. Bot, di sisi lain, tidak mendapatkan pemberitahuan khusus apa pun saat disebutkan. Mereka menerima acara Buat Pesan reguler seperti yang mereka lakukan untuk pesan lainnya, dan mereka dapat memeriksa penyebutan yang dilampirkan ke acara tersebut untuk menentukan apakah mereka disebutkan.
Rekam Perintah Pembayaran
Sekarang kita tahu bahwa kita bisa mendapatkan bot secara online, mari singkirkan pengendali peristiwa Buat Pesan kita saat ini dan buat yang baru yang memungkinkan kita memberi tahu bot bahwa kita telah menerima pembayaran dari pengguna.
Untuk menginformasikan bot pembayaran, kami akan mengeluarkan perintah yang terlihat seperti ini:
pb!addpayment @user_mention payment_amount
Misalnya, pb!addpayment @Me 10.00
untuk mencatat pembayaran sebesar $10,00 yang dilakukan oleh Saya.
pb! bagian disebut sebagai awalan perintah. Ini adalah konvensi yang baik untuk memilih awalan yang harus dimulai dengan semua perintah ke bot Anda. Ini menciptakan ukuran jarak nama untuk bot dan membantu menghindari tabrakan dengan bot lain. Kebanyakan bot menyertakan perintah bantuan, tetapi bayangkan kekacauannya jika Anda memiliki sepuluh bot di serikat Anda dan mereka semua merespons bantuan ! Menggunakan pb! sebagai awalan bukanlah solusi yang sangat mudah, karena mungkin ada bot lain yang juga menggunakan awalan yang sama. Bot paling populer memungkinkan awalan mereka untuk dikonfigurasi pada basis per-guild untuk membantu mencegah tabrakan. Pilihan lain adalah menggunakan penyebutan bot sendiri sebagai awalan, meskipun ini membuat perintah mengeluarkan lebih bertele-tele.
(Tautan kode 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();
Mari kita coba.
Kami tidak hanya membuat bot merespons perintah pb!addpayment
, tetapi kami membuat pola umum untuk menangani perintah. Kita dapat menambahkan lebih banyak perintah hanya dengan menambahkan lebih banyak penangan ke kamus commandHandlerForCommandName
. Kami memiliki bakat kerangka perintah sederhana di sini. Menangani perintah adalah bagian mendasar dari pembuatan bot sehingga banyak orang telah menulis dan kerangka perintah sumber terbuka yang dapat Anda gunakan daripada menulis sendiri. Kerangka kerja perintah sering memungkinkan Anda untuk menentukan cooldown, izin pengguna yang diperlukan, alias perintah, deskripsi perintah dan contoh penggunaan (untuk perintah bantuan yang dibuat secara otomatis), dan banyak lagi. Eris hadir dengan kerangka perintah bawaan.
Berbicara tentang izin, bot kami memiliki sedikit masalah keamanan. Siapa pun dapat menjalankan perintah addpayment
. Mari kita batasi agar hanya pemilik bot yang dapat menggunakannya. Kami akan memfaktorkan ulang kamus commandHandlerForCommandName
dan membuatnya berisi objek JavaScript sebagai nilainya. Objek tersebut akan berisi properti execute
dengan pengendali perintah, dan properti botOwnerOnly
dengan nilai boolean. Kami juga akan meng-hardcode ID pengguna kami ke bagian konstanta bot sehingga bot tahu siapa pemiliknya. Anda dapat menemukan ID pengguna Anda dengan mengaktifkan Mode Pengembang di pengaturan Perselisihan Anda, lalu mengklik kanan nama pengguna Anda dan memilih Salin ID.
(Tautan kode 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();
Sekarang bot akan dengan marah menolak untuk menjalankan perintah addpayment
jika ada orang lain selain pemilik bot yang mencoba menjalankannya.

Selanjutnya, biarkan bot menetapkan peran Premium Member
kepada siapa saja yang menyumbangkan sepuluh dolar atau lebih. Di bagian atas file bot.js:
(Tautan kode 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), ]); }, };
Sekarang saya dapat mencoba mengatakan pb!addpayment @Me 10.00
dan bot harus memberi saya peran Premium Member
.
Ups, kesalahan Izin Hilang muncul di konsol.
DiscordRESTError: DiscordRESTError [50013]: Missing Permissions index.js:85 code:50013
Bot tidak memiliki izin Kelola Peran di serikat uji, sehingga tidak dapat membuat atau menetapkan peran. Kami dapat memberikan bot hak istimewa Administrator dan kami tidak akan pernah memiliki masalah seperti ini lagi, tetapi seperti halnya sistem apa pun, yang terbaik adalah hanya memberi pengguna (atau dalam hal ini bot) hak istimewa minimum yang mereka butuhkan
Kami dapat memberi bot izin Kelola Peran dengan membuat peran di pengaturan server, mengaktifkan izin Kelola Peran untuk peran itu dan menetapkan peran ke bot.
Sekarang, ketika saya mencoba menjalankan perintah lagi, peran dibuat dan diberikan kepada saya dan saya memiliki warna nama yang bagus dan posisi khusus dalam daftar anggota.
Di pengendali perintah, kami memiliki komentar TODO yang menyarankan bahwa kami perlu memeriksa argumen yang tidak valid. Mari kita urus itu sekarang.
(Tautan kode 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), ]); }, };
Berikut kode lengkapnya sejauh ini:
(Tautan kode 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();
Ini akan memberi Anda ide dasar yang bagus tentang cara membuat bot Discord. Sekarang kita akan melihat bagaimana mengintegrasikan bot dengan Ko-fi. Jika mau, Anda dapat membuat webhook di dasbor Anda di Ko-fi, pastikan router Anda dikonfigurasi untuk meneruskan port 80, dan kirim webhook uji langsung ke diri Anda sendiri. Tapi saya hanya akan menggunakan Postman untuk mensimulasikan permintaan.
Webhook dari Ko-fi mengirimkan muatan yang terlihat seperti ini:
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" }
Mari buat file sumber baru bernama webhook_listener.js dan gunakan Express untuk mendengarkan webhook. Kami hanya akan memiliki satu rute Express, dan ini untuk tujuan demonstrasi, jadi kami tidak akan terlalu khawatir tentang penggunaan struktur direktori idiomatik. Kami hanya akan menempatkan semua logika server web dalam satu file.
(Tautan kode 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;
Kemudian kita minta file baru di bagian atas bot.js agar listener mulai saat kita menjalankan bot.js.
(Tautan kode GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step6.js)
const eris = require('eris'); const webhookListener = require('./webhook_listener.js');
Setelah memulai bot, Anda akan melihat "Halo" ketika Anda menavigasi ke http://localhost/kofi di browser Anda.
Sekarang mari kita WebhookListener
memproses data dari webhook dan memancarkan sebuah event. Dan sekarang kita telah menguji bahwa browser kita dapat mengakses rute, mari ubah rute menjadi rute POST, karena webhook dari Ko-fi akan menjadi permintaan POST.
(Tautan kode 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.
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.
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
Langkah selanjutnya
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:
- 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. - 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. - 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.
- 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). - 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.
Membungkusnya
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.