Cum să faci un bot Discord: o prezentare generală și un tutorial

Publicat: 2022-03-11

Discord este o platformă de mesagerie în timp real care se autoproclamă ca un „chat complet de voce și text pentru jucători”. Datorită interfeței sale elegante, ușurinței de utilizare și caracteristicilor extinse, Discord a cunoscut o creștere rapidă și devine din ce în ce mai popular chiar și în rândul celor cu puțin interes pentru jocurile video. Între mai 2017 și mai 2018, baza sa de utilizatori a explodat de la 45 de milioane de utilizatori la peste 130 de milioane, cu mai mult de două ori mai mulți utilizatori zilnici decât Slack.

Una dintre cele mai atractive caracteristici ale Discord din perspectiva unui dezvoltator de chatbot este suportul robust pentru roboții programabili, care ajută la integrarea Discord cu lumea exterioară și oferă utilizatorilor o experiență mai captivantă. Boții sunt omniprezent pe Discord și oferă o gamă largă de servicii, inclusiv asistență pentru moderare, jocuri, muzică, căutări pe internet, procesare plăți și multe altele.

În acest tutorial despre bot Discord, vom începe prin a discuta despre interfața de utilizator Discord și despre API-urile REST și WebSocket pentru roboți înainte de a trece la un tutorial în care vom scrie un simplu bot Discord în JavaScript. În cele din urmă, vom auzi de la dezvoltatorul, după anumite valori, cel mai popular bot Discord și experiențele sale în dezvoltarea și întreținerea infrastructurii și bazei de cod semnificative.

Interfața utilizator Discord

Înainte de a discuta detaliile tehnice, este important să înțelegem cum interacționează un utilizator cu Discord și cum se prezintă Discord utilizatorilor. Modul în care se prezintă boților este similar din punct de vedere conceptual (dar desigur non-vizual). De fapt, aplicațiile oficiale Discord sunt construite pe aceleași API-uri pe care le folosesc boții. Din punct de vedere tehnic, este posibil să rulați un bot în interiorul unui cont de utilizator obișnuit cu mici modificări, dar acest lucru este interzis de termenii și condițiile Discord. Boții trebuie să ruleze în conturile bot.

Iată o privire asupra versiunii 1 de browser a aplicației Discord care rulează în Chrome.

Discord Web UI

1 Interfața de utilizare Discord pentru aplicația desktop este practic aceeași cu aplicația web, ambalată cu Electron. Aplicația iOS este construită cu React Native. Aplicația Android este cod nativ Android Java.

Să-l descompunem.

1. Lista de servere

Tot în stânga este lista de servere la care sunt membru. Dacă sunteți familiarizat cu Slack, un server este analog cu un spațiu de lucru Slack și reprezintă un grup de utilizatori care pot interacționa între ei în cadrul unuia sau mai multor canale din server. Un server este gestionat de creatorul său și/sau de orice personal pe care îl selectează și căruia îi delege responsabilități. Creatorul și/sau personalul definesc regulile, structura canalelor din server și gestionează utilizatorii.

În cazul meu, serverul Discord API se află în partea de sus a listei mele de servere. Este un loc grozav pentru a obține ajutor și pentru a discuta cu alți dezvoltatori. Mai jos este un server pe care l-am creat numit Test . Vom testa mai târziu botul creat de noi. Mai jos este un buton pentru a crea un nou server. Oricine poate crea un server cu câteva clicuri.

Rețineți că, în timp ce termenul folosit în interfața cu utilizatorul Discord este Server , termenul folosit în documentația pentru dezvoltatori și API este Guild . Odată ce vom trece la vorbirea despre subiecte tehnice, vom trece la a vorbi despre bresle . Cei doi termeni sunt interschimbabili.

2. Lista de canale

Chiar în dreapta listei de servere este lista de canale pentru serverul pe care îl vizionez în prezent (în acest caz, serverul Discord API). Canalele pot fi împărțite într-un număr arbitrar de categorii. În serverul API Discord, categoriile includ INFORMAȚII, GENERAL și LIBS, după cum se arată. Fiecare canal funcționează ca o cameră de chat unde utilizatorii pot discuta despre subiectul căruia îi este dedicat canalul. Canalul pe care îl vizionam în prezent (informații) are un fundal mai deschis. Canalele care au mesaje noi de când le-am vizualizat ultima dată au o culoare de text albă.

3. Vizualizare canal

Aceasta este vizualizarea canalului unde putem vedea despre ce au vorbit utilizatorii pe canalul pe care îl vizionam în prezent. Putem vedea un mesaj aici, vizibil doar parțial. Este o listă de link-uri către servere de suport pentru bibliotecile individuale de bot Discord. Administratorii serverului au configurat acest canal astfel încât utilizatorii obișnuiți ca mine să nu poată trimite mesaje în el. Administratorii folosesc acest canal ca un buletin pentru a posta câteva informații importante unde pot fi văzute cu ușurință și nu vor fi înecate de chat.

4. Lista utilizatorilor

Tot în partea dreaptă este o listă a utilizatorilor online în prezent pe acest server. Utilizatorii sunt organizați în diferite categorii și numele lor au culori diferite. Acesta este rezultatul rolurilor pe care le au. Un rol descrie în ce categorie (dacă există) utilizatorul ar trebui să apară, care ar trebui să fie culoarea numelui și ce permisiuni au pe server. Un utilizator poate avea mai multe roluri (și are deseori), și există o matematică a priorităților care determină ce se întâmplă în acel caz. Cel puțin, fiecare utilizator are rolul @everyone. Alte roluri sunt create și atribuite de personalul serverului.

5. Introducere text

Aceasta este introducerea textului în care aș putea introduce și trimite mesaje, dacă mi se permite. Deoarece nu am permisiunea de a trimite mesaje pe acest canal, nu pot scrie aici.

6. Utilizator

Acesta este utilizatorul actual. Mi-am setat numele de utilizator la „Eu”, pentru a nu mă încurca și pentru că sunt groaznic în a alege nume. Sub numele meu de utilizator este un număr (#9484) care este discriminatorul meu. S-ar putea să existe mulți alți utilizatori numiți „Eu”, dar eu sunt singurul „Eu#9484”. De asemenea, este posibil să îmi stabilesc un pseudonim pe server, astfel încât să pot fi cunoscut sub diferite nume pe servere diferite.

Acestea sunt părțile de bază ale interfeței de utilizator Discord, dar există și multe altele. Este ușor să începeți să utilizați Discord chiar și fără a crea un cont, așa că nu ezitați să vă luați un minut pentru a căuta. Puteți intra în Discord vizitând pagina de pornire Discord, făcând clic pe „deschideți Discord într-un browser”, alegând un nume de utilizator și, eventual, jucând o rundă sau două de „dați clic pe imaginile autobuzului”.

API-ul Discord

API-ul Discord este format din două părți separate: API-urile WebSocket și REST. În linii mari, API-ul WebSocket este folosit pentru a primi evenimente de la Discord în timp real, în timp ce API-ul REST este folosit pentru a efectua acțiuni în interiorul Discord.

Cum să faci o buclă de comunicare cu bot Discord

API-ul WebSocket

API-ul WebSocket este folosit pentru a primi evenimente de la Discord, inclusiv crearea de mesaje, ștergerea mesajelor, evenimentele de blocare/interdicție a utilizatorului, actualizări ale permisiunilor utilizatorului și multe altele. Comunicarea de la un bot la API-ul WebSocket, pe de altă parte, este mai limitată. Un bot folosește API-ul WebSocket pentru a solicita o conexiune, a se identifica, a bate inima, a gestiona conexiunile vocale și pentru a face câteva lucruri fundamentale. Puteți citi mai multe detalii în documentația gateway-ului Discord (o singură conexiune la API-ul WebSocket este denumită gateway). Pentru efectuarea altor acțiuni, se folosește API-ul REST.

Evenimentele din API-ul WebSocket conțin o sarcină utilă care include informații care depind de tipul evenimentului. De exemplu, toate evenimentele Message Create vor fi însoțite de un obiect utilizator care reprezintă autorul mesajului. Cu toate acestea, obiectul utilizatorului singur nu conține toate informațiile pe care trebuie să le cunoașteți despre utilizator. De exemplu, nu sunt incluse informații despre permisiunile utilizatorului. Dacă aveți nevoie de mai multe informații, puteți interoga API-ul REST pentru aceasta, dar din motive explicate mai departe în secțiunea următoare, ar trebui să accesați, în general, memoria cache pe care ar fi trebuit să o creați din încărcături primite de la evenimentele anterioare. Există o serie de evenimente care furnizează încărcături utile relevante pentru permisiunile unui utilizator, inclusiv, dar fără a se limita la, Guild Create , Guild Role Update și Channel Update .

Un bot poate fi prezent în maximum 2.500 de bresle per conexiune WebSocket. Pentru a permite unui bot să fie prezent în mai multe bresle, acesta trebuie să implementeze sharding și să deschidă mai multe conexiuni WebSocket separate la Discord. Dacă botul tău rulează într-un singur proces pe un singur nod, aceasta este doar o complexitate adăugată pentru tine, care poate părea inutilă. Dar dacă botul tău este foarte popular și trebuie să aibă back-end-ul distribuit pe noduri separate, suportul de fragmentare al Discord face acest lucru mult mai ușor decât ar fi altfel.

API-ul REST

API-ul Discord REST este folosit de roboți pentru a efectua majoritatea acțiunilor, cum ar fi trimiterea de mesaje, lovirea/interzicerea utilizatorilor și actualizarea permisiunilor utilizatorilor (în mare analog cu evenimentele primite de la API-ul WebSocket). API-ul REST poate fi folosit și pentru a solicita informații; totuși, boții se bazează în principal pe evenimentele din API-ul WebSocket și memorează în cache informațiile primite de la evenimentele WebSocket.

Există mai multe motive pentru aceasta. Interogarea API-ului REST pentru a obține informații despre utilizator de fiecare dată când este primit un eveniment Message Create , de exemplu, nu se extinde din cauza limitelor de rată ale API-ului REST. De asemenea, este redundant în majoritatea cazurilor, deoarece API-ul WebSocket oferă informațiile necesare și ar trebui să le aveți în cache.

Există, totuși, unele excepții și uneori este posibil să aveți nevoie de informații care nu sunt prezente în memoria cache. Când un bot se conectează inițial la un gateway WebSocket, un eveniment Ready și un eveniment Guild Create pentru fiecare breaslă în care botul este prezent pe acel fragment sunt inițial trimise către bot, astfel încât să își poată popula memoria cache cu starea curentă. Evenimentele Guild Create pentru bresle foarte populate includ doar informații despre utilizatorii online. Dacă botul dvs. trebuie să obțină informații despre un utilizator offline, este posibil ca informațiile relevante să nu fie prezente în memoria cache. În acest caz, este logic să faceți o solicitare către API-ul REST. Sau, dacă aveți nevoie frecvent să obțineți informații despre utilizatorii offline, puteți opta pentru a trimite un opcode Request Guild Members la API-ul WebSocket pentru a solicita membri offline al breslei.

O altă excepție este dacă aplicația dvs. nu este deloc conectată la API-ul WebSocket. De exemplu, dacă botul dvs. are un tablou de bord web la care utilizatorii se pot conecta și modifica setările botului pe serverul lor. Tabloul de bord web ar putea rula într-un proces separat, fără conexiuni la API-ul WebSocket și fără cache de date de la Discord. Este posibil să fie nevoie doar să facă ocazional câteva solicitări API REST. În acest tip de scenariu, este logic să vă bazați pe API-ul REST pentru a obține informațiile de care aveți nevoie.

API Wrappers

Deși este întotdeauna o idee bună să înțelegeți fiecare nivel al stivei dvs. de tehnologie, utilizarea directă a API-urilor Discord WebSocket și REST este consumatoare de timp, predispusă la erori, în general inutilă și de fapt periculoasă.

Discord oferă o listă curată a bibliotecilor verificate oficial și avertizează că:

Utilizarea implementărilor personalizate sau a bibliotecilor neconforme care abuzează de API sau provoacă limite excesive ale ratei poate duce la o interdicție permanentă.

Bibliotecile verificate oficial de Discord sunt în general mature, bine documentate și oferă o acoperire completă a API-ului Discord. Majoritatea dezvoltatorilor de bot nu vor avea niciodată un motiv bun pentru a dezvolta o implementare personalizată, cu excepția curiozității sau curajului!

În acest moment, bibliotecile verificate oficial includ implementări pentru Crystal, C#, D, Go, Java, JavaScript, Lua, Nim, PHP, Python, Ruby, Rust și Swift. Pot exista două sau mai multe biblioteci diferite pentru limba pe care o alegeți. Alegerea pe care să o utilizați poate fi o decizie dificilă. Pe lângă verificarea documentației respective, este posibil să doriți să vă alăturați serverului neoficial Discord API și să vă dați o idee despre felul de comunitate care se află în spatele fiecărei biblioteci.

Cum să faci un bot Discord

Sa trecem la treaba. Vom crea un bot Discord care sta pe serverul nostru și care ascultă webhook-uri de la Ko-fi. Ko-fi este un serviciu care vă permite să acceptați cu ușurință donații în contul dvs. PayPal. Este foarte simplu să configurați webhook-uri acolo, spre deosebire de PayPal unde trebuie să aveți un cont de afaceri, așa că este grozav pentru scopuri demonstrative sau pentru procesarea donațiilor la scară mică.

Când un utilizator donează 10 USD sau mai mult, bot-ul îi va atribui un rol de Premium Member care își schimbă culoarea numelui și îi va muta în partea de sus a listei de utilizatori online. Pentru acest proiect, vom folosi Node.js și o bibliotecă API Discord numită Eris (link pentru documentație: https://abal.moe/Eris/). Eris nu este singura bibliotecă JavaScript. În schimb, puteți alege discord.js. Codul pe care îl vom scrie ar fi foarte asemănător în orice caz.

În afară de aceasta, Patreon, un alt procesator de donații, oferă un bot oficial Discord și acceptă configurarea rolurilor Discord ca beneficii pentru colaboratori. Vom implementa ceva similar, dar bineînțeles mai de bază.

Codul pentru fiecare pas al tutorialului este disponibil pe GitHub (https://github.com/mistval/premium_bot). Unii dintre pașii indicați în această postare omit codul neschimbat pentru concizie, așa că urmați linkurile furnizate către GitHub dacă credeți că s-ar putea să vă lipsească ceva.

Crearea unui cont de bot

Înainte de a începe să scriem cod, avem nevoie de un cont bot. Înainte de a putea crea un cont bot, avem nevoie de un cont de utilizator. Pentru a crea un cont de utilizator, urmați instrucțiunile de aici.

Apoi, pentru a crea un cont bot, noi:

1) Creați o aplicație în portalul pentru dezvoltatori.

captură de ecran a portalului dezvoltatorilor

2) Completați câteva detalii de bază despre aplicație (rețineți ID-ul CLIENT afișat aici - vom avea nevoie de el mai târziu).

captură de ecran cu completarea detaliilor de bază

3) Adăugați un utilizator bot conectat la aplicație.

captură de ecran cu adăugarea unui utilizator bot

4) Opriți comutatorul PUBLIC BOT și notați simbolul bot afișat (vom avea nevoie și de acesta mai târziu). Dacă vă scurgeți vreodată jetonul bot, de exemplu publicându-l într-o imagine într-o postare de blog Toptal, este imperativ să îl regenerați imediat. Oricine deține simbolul dvs. bot poate controla contul botului dvs. și poate cauza probleme potențial grave și permanente pentru dvs. și utilizatorii dvs.

captură de ecran cu „a apărut un bot sălbatic”

5) Adaugă botul la breasla ta de testare. Pentru a adăuga un bot la o breaslă, înlocuiți ID-ul de client (afișat mai devreme) în următorul URI și navigați la acesta într-un browser.

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

Adaugă botul la breasla ta de testare

După ce am făcut clic pe Autorizare, botul se află acum în breasla mea de testare și îl pot vedea în lista de utilizatori. Este offline, dar îl vom remedia în curând.

Crearea Proiectului

Presupunând că aveți instalat Node.js, creați un proiect și instalați Eris (biblioteca bot pe care o vom folosi), Express (un cadru de aplicație web pe care îl vom folosi pentru a crea un ascultător webhook) și body-parser (pentru analiza corpurilor webhook). ).

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

Obținerea robotului online și receptiv

Să începem cu pași de copil. Mai întâi, vom pune robotul online și ne vom răspunde. Putem face acest lucru în 10-20 de linii de cod. În interiorul unui nou fișier bot.js, trebuie să creăm o instanță client Eris, să îi transmitem jetonul nostru bot (dobândit când am creat o aplicație bot mai sus), să ne abonam la unele evenimente pe instanța client și să îi spunem să se conecteze la Discord . În scopuri demonstrative, vom codifica tokenul nostru bot în fișierul bot.js, dar crearea unui fișier de configurare separat și scutirea acestuia de controlul sursei este o practică bună.

(Link pentru codul 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();

Dacă totul merge bine, atunci când rulați acest cod cu propriul token bot, Connected and ready. va fi tipărit pe consolă și veți vedea botul dvs. venind online pe serverul dvs. de testare. Puteți să menționați botul dvs. fie făcând clic dreapta pe el și selectând „Menționați”, fie introducând numele său precedat de @. Botul ar trebui să răspundă spunând „Prezentă”.

Botul tău este prezent

2 Menționarea este o modalitate de a atrage atenția altui utilizator chiar dacă acesta nu este prezent. Un utilizator obișnuit, atunci când este menționat, va fi notificat prin notificare desktop, notificare push mobil și/sau o pictogramă roșie care apare deasupra pictogramei Discord în bara de sistem. Modul (modalele) în care un utilizator este notificat depinde de setările sale și de starea online. Boții, pe de altă parte, nu primesc niciun fel de notificare specială atunci când sunt menționați. Ei primesc un eveniment obișnuit Message Create la fel ca pentru orice alt mesaj și pot verifica mențiunile atașate evenimentului pentru a determina dacă au fost menționate.

Înregistrați comanda de plată

Acum că știm că putem obține un bot online, să scăpăm de actualul gestionar de evenimente Message Create și să creăm unul nou care ne permite să informăm botul că am primit plată de la un utilizator.

Pentru a informa botul de plată, vom lansa o comandă care arată astfel:

 pb!addpayment @user_mention payment_amount

De exemplu, pb!addpayment @Me 10.00 pentru a înregistra o plată de 10,00 USD efectuată de mine.

Pb! partea este denumită prefix de comandă. Este o convenție bună să alegeți un prefix cu care trebuie să înceapă toate comenzile către bot. Acest lucru creează o măsură de spațiere a numelor pentru roboți și ajută la evitarea coliziunii cu alți roboți. Majoritatea roboților includ o comandă de ajutor, dar imaginează-ți mizeria dacă ai avea zece roboți în breasla ta și toți au răspuns pentru a ajuta ! Folosind pb! ca prefix nu este o soluție sigură, deoarece pot exista și alți roboți care folosesc același prefix. Cei mai populari roboți permit configurarea prefixului lor pe bază de breaslă pentru a ajuta la prevenirea coliziunii. O altă opțiune este să folosiți mențiunea botului ca prefix, deși acest lucru face ca emiterea comenzilor să fie mai pronunțată.

(Link pentru codul 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();

Hai sa incercam.

Interacționează cu botul

Nu numai că am făcut ca botul să răspundă la comanda pb!addpayment , dar am creat un model generalizat pentru gestionarea comenzilor. Putem adăuga mai multe comenzi doar adăugând mai mulți handler la dicționarul commandHandlerForCommandName . Avem elementele unui cadru de comandă simplu aici. Manipularea comenzilor este o parte atât de fundamentală a creării unui bot, încât mulți oameni au cadre de comandă scrise și cu sursă deschisă pe care le puteți folosi în loc să le scrieți pe ale dvs. Cadrele de comandă vă permit adesea să specificați perioadele de încălcare, permisiunile necesare de utilizator, aliasuri de comandă, descrieri de comenzi și exemple de utilizare (pentru o comandă de ajutor generată automat) și multe altele. Eris vine cu un cadru de comandă încorporat.

Apropo de permisiuni, botul nostru are o mică problemă de securitate. Oricine poate executa comanda addpayment . Să-l restricționăm astfel încât să îl poată folosi numai proprietarul botului. Vom refactoriza dicționarul commandHandlerForCommandName și vom face ca acesta să conțină obiecte JavaScript ca valori. Aceste obiecte vor conține o proprietate execute cu un handler de comandă și o proprietate botOwnerOnly cu o valoare booleană. De asemenea, vom codifica ID-ul de utilizator în secțiunea de constante a botului, astfel încât acesta să știe cine este proprietarul acestuia. Puteți găsi ID-ul dvs. de utilizator activând Modul dezvoltator în setările dvs. Discord, apoi făcând clic dreapta pe numele dvs. de utilizator și selectând Copiere ID.

(Link pentru codul 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();

Acum, botul va refuza cu furie să execute comanda addpayment dacă cineva, altul decât proprietarul botului, încearcă să o execute.

Apoi, să-i cerem botului să atribuie un rol de Premium Member oricărei persoane care donează zece dolari sau mai mult. În partea de sus a fișierului bot.js:

(Link pentru codul 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), ]); }, };

Acum pot încerca să spun pb!addpayment @Me 10.00 și botul ar trebui să-mi atribuie rolul de Premium Member .

Hopa, în consolă apare o eroare de permisiuni lipsă.

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

Botul nu are permisiunea de gestionare a rolurilor în breasla de testare, așa că nu poate crea sau atribui roluri. Am putea acorda botului privilegiul de Administrator și nu am mai avea niciodată acest tip de problemă, dar ca în cazul oricărui sistem, cel mai bine este să acordăm doar unui utilizator (sau, în acest caz, unui bot) privilegiile minime de care are nevoie.

Putem acorda botului permisiunea de gestionare a rolurilor creând un rol în setările serverului, activând permisiunea de gestionare a rolurilor pentru acel rol și atribuind rolul botului.

Începeți să gestionați rolurile

Creați un nou rol

Acum, când încerc să execut din nou comanda, rolul este creat și atribuit mie și am o culoare de nume fantezică și o poziție specială în lista de membri.

Se atribuie un nou rol

În handlerul de comandă, avem un comentariu TODO care sugerează că trebuie să verificăm argumentele nevalide. Să ne ocupăm acum de asta.

(Link pentru codul 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), ]); }, };

Iată codul complet de până acum:

(Link pentru codul 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();

Acest lucru ar trebui să vă ofere o idee de bază bună despre cum să creați un bot Discord. Acum vom vedea cum să integrăm botul cu Ko-fi. Dacă doriți, puteți crea un webhook în tabloul de bord la Ko-fi, asigurați-vă că routerul este configurat să redirecționeze portul 80 și să vă trimiteți webhook-uri de testare în direct. Dar voi folosi Postman pentru a simula cereri.

Webhook-urile de la Ko-fi oferă încărcături utile care arată astfel:

 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" }

Să creăm un nou fișier sursă numit webhook_listener.js și să folosim Express pentru a asculta webhook-uri. Vom avea doar o singură rută Express și aceasta este în scop demonstrativ, așa că nu ne vom îngrijora prea mult cu privire la utilizarea unei structuri de directoare idiomatice. Vom pune toată logica serverului web într-un singur fișier.

(Link pentru codul 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;

Apoi, să cerem noul fișier din partea de sus a bot.js, astfel încât ascultătorul să pornească atunci când rulăm bot.js.

(Link pentru codul GitHub: https://github.com/mistval/premium_bot/blob/master/src/bot_step6.js)

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

După pornirea botului, ar trebui să vedeți „Bună ziua” când navigați la http://localhost/kofi în browser.

Acum haideți ca WebhookListener să proceseze datele din webhook și să emită un eveniment. Și acum că am testat că browserul nostru poate accesa ruta, să schimbăm ruta la o rută POST, deoarece webhook-ul de la Ko-fi va fi o solicitare POST.

(Link pentru codul 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

Pasii urmatori

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.

Înrudit : Cele mai bune practici JS: Construiți un bot Discord cu TypeScript și Dependency Injection