Skip to content Skip to sidebar Skip to footer

And Make an Announcement to Get More Members in My Server Again

Discord is a existent-time messaging platform that bills itself as an "all-in-i vocalisation and text chat for gamers." Due to its slick interface, ease of use, and all-encompassing features, Discord has experienced rapid growth and is becoming increasingly popular even amidst those with little involvement in video games. Betwixt May 2022 and May 2018, its user base of operations exploded from 45 meg users to more than 130 million, with more than than twice as many daily users as Slack.

I of the near bonny features of Discord from a chatbot programmer'south perspective is its robust support for programmable bots that help to integrate Discord with the exterior world and provide users with a more than engaging feel. Bots are ubiquitous on Discord and provide a broad range of services, including moderation assist, games, music, internet searches, payment processing, and more.

In this Discord bot tutorial, nosotros volition beginning past discussing the Discord user interface and its REST and WebSocket APIs for bots before moving on to a tutorial where nosotros will write a uncomplicated Discord bot in JavaScript. Finally, we'll hear from the programmer of, by certain metrics, Discord's about popular bot and his experiences developing and maintaining his significant infrastructure and codebase.

Discord User Interface

Before we discuss technical details, information technology'southward important to empathise how a user interacts with Discord and how Discord presents itself to users. The fashion it presents itself to bots is conceptually similar (but of course non-visual). In fact, the official Discord applications are built on the same APIs that bots use. Information technology is technically possible to run a bot inside of a regular user account with little modification, but this is forbidden by Discord'due south terms of service. Bots are required to run in bot accounts.

Here's a look at the browser version1 of the Discord application running inside of Chrome.

Discord Web UI

1 The Discord UI for the desktop application is near the same as the web application, packaged with Electron. The iOS application is built with React Native. The Android application is native Android Java code.

Let'south break information technology downwards.

1. Server List

All the way on the left is the list of servers that I am a member of. If y'all're familiar with Slack, a server is analogous to a Slack workspace, and represents a group of users who can interact with each other inside 1 or more than channels in the server. A server is managed by its creator and/or whatsoever staff they select and choose to delegate responsibilities to. The creator and/or staff define the rules, the structure of the channels in the server, and manage users.

In my instance, the Discord API server is at the height of my server list. It's a dandy place to get assistance and talk with other developers. Below that is a server that I created called Examination. Nosotros'll be testing the bot we create after there. Below that is a button to create a new server. Anyone tin create a server with a few clicks.

Annotation that while the term used in Discord'due south user interface is Server, the term used in the developer documentation and API is Club. Once we motion on to talking about technical topics, we will switch to talking about Guilds. The 2 terms are interchangeable.

2. Channel List

Simply to the correct of the server list is the list of channels for the server I am currently viewing (in this instance, the Discord API server). Channels can exist broken up into an capricious number of categories. In the Discord API server, the categories include Data, Full general, and LIBS, as shown. Each channel functions as a chat room where users can hash out whatsoever topic the aqueduct is dedicated to. The aqueduct we are currently viewing (info) has a lighter groundwork. Channels that have new messages since nosotros last viewed them take a white text color.

3. Aqueduct View

This is the channel view where nosotros can see what users accept been talking about in the aqueduct we are currently viewing. We can meet ane message here, only partially visible. It's a list of links to back up servers for individual Discord bot libraries. The server administrators have configured this aqueduct then that regular users like myself cannot send messages in information technology. The administrators use this channel as a bulletin board to post some of import information where it can easily be seen and won't be drowned out past chat.

4. User Listing

All the way on the correct is a list of the users currently online in this server. The users are organized into different categories and their names take different colors. This is a upshot of the roles that they accept. A role describes what category (if any) the user should appear under, what their proper noun colour should be, and what permissions they have in the server. A user can have more than than i role (and very oftentimes does), and there is some precedence math that determines what happens in that case. At a minimum, every user has the @everyone role. Other roles are created and assigned by server staff.

5. Text Input

This is the text input where I could type and send messages, if I were allowed to. Since I don't take permission to send messages in this channel, I can't blazon in here.

6. User

This is the electric current user. I set my username to "Me," to help keep me from getting confused, and because I'm terrible at choosing names. Beneath my username is a number (#9484) which is my discriminator. In that location may be many other users named "Me," only I'grand the only "Me#9484." It is also possible for me to fix a nickname for myself on a per-server basis, so I tin can be known by different names in unlike servers.

These are the basic parts of the Discord user interface, just there's a lot more too. It'south easy to commencement using Discord even without creating an account, so feel complimentary to take a minute to poke around. Yous tin enter Discord past visiting the Discord homepage, clicking "open up Discord in a browser," choosing a username, and possibly playing a refreshing round or two of "click the autobus pictures."

The Discord API

The Discord API consists of two separate pieces: the WebSocket and Remainder APIs. Broadly speaking, the WebSocket API is used to receive events from Discord in real time, while the Balance API is used to perform deportment inside of Discord.

How to make a discord bot communication loop

The WebSocket API

The WebSocket API is used to receive events from Discord, including message creation, message deletion, user kicking/ban events, user permission updates, and many more than. Advice from a bot to the WebSocket API on the other hand is more than limited. A bot uses the WebSocket API to request a connection, identify itself, heartbeat, manage vox connections, and do a few more central things. Y'all can read more details at Discord's gateway documentation (a single connexion to the WebSocket API is referred to as a gateway). For performing other actions, the REST API is used.

Events from the WebSocket API contain a payload including data that depends on the type of the event. For example, all Bulletin Create events will exist accompanied by a user object representing the writer of the bulletin. However, the user object lonely does not contain all of the information at that place is to know about the user. For instance, there is no information included about the user's permissions. If you require more data, yous could query the REST API for it, but for reasons explained further in the side by side section, you should generally access the enshroud that y'all should have built from payloads received from previous events instead. There are a number of events that deliver payloads relevant to a user's permissions, including but not limited to Order Create, Order Part Update, and Aqueduct Update.

A bot tin be nowadays in a maximum of ii,500 guilds per WebSocket connection. In society to allow a bot to be nowadays in more guilds, the bot must implement sharding and open up several separate WebSocket connections to Discord. If your bot runs inside of a single process on a single node, this is simply added complication for yous that may seem unnecessary. But if your bot is very pop and needs to have its back-end distributed across separate nodes, Discord'due south sharding back up makes this much easier than it would be otherwise.

The Residual API

The Discord REST API is used past bots to perform virtually actions, such as sending messages, kicking/banning users, and updating user permissions (broadly analogous to the events received from the WebSocket API). The REST API tin can also exist used to query for information; still, bots mainly rely on events from the WebSocket API instead and cache the data received from the WebSocket events.

There are several reasons for this. Querying the REST API to get user information every time a Message Create event is received, for case, does non calibration due to the REST API's rate limits. It's also redundant in most cases, equally the WebSocket API delivers the necessary data and you should have it in your cache.

There are some exceptions, however, and you may sometimes need information that is non present in your enshroud. When a bot initially connects to a WebSocket gateway, a Ready event and 1 Guild Create outcome per social club that the bot is present in on that shard are initially sent to the bot so that it tin can populate its enshroud with the current state. The Guild Create events for heavily populated guilds only include information about online users. If your bot needs to get information almost an offline user, the relevant information may not exist nowadays in your cache. In this example, it makes sense to brand a request to the Remainder API. Or, if you find yourself often needing to become data about offline users, you tin instead opt to transport a Request Guild Members opcode to the WebSocket API to request offline guild members.

Another exception is if your awarding isn't connected to the WebSocket API at all. For instance if your bot has a web dashboard that users tin log into and change the bot's settings in their server. The spider web dashboard could be running in a split procedure without any connections to the WebSocket API and no cache of data from Discord. Information technology may only need to occasionally make a few Balance API requests. In this kind of scenario, it makes sense to rely on the REST API to become the information you need.

API Wrappers

While it'due south always a skilful idea to have some agreement of every level of your technology stack, using the Discord WebSocket and Rest APIs direct is time-consuming, error-decumbent, generally unnecessary, and in fact dangerous.

Discord provides a curated list of officially vetted libraries and warns that:

Using custom implementations or non-compliant libraries which abuse the API or crusade excessive rate limits may consequence in a permanent ban.

The libraries officially vetted past Discord are by and large mature, well-documented, and feature total coverage of the Discord API. Most bot developers will never have a good reason to develop a custom implementation, except out of curiosity, or bravery!

At this fourth dimension, the officially vetted libraries include implementations for Crystal, C#, D, Go, Java, JavaScript, Lua, Nim, PHP, Python, Ruby, Rust, and Swift. There may be 2 or more different libraries for your language of pick. Choosing which to utilize can exist a difficult decision. In addition to checking out the respective documentation, y'all might want to join the unofficial Discord API server and get a feel for what kind of customs is behind each library.

How to Make a Discord Bot

Let's get down to business. We're going to create a Discord bot that hangs out in our server and listens for webhooks from Ko-fi. Ko-fi is a service that allows y'all to easily accept donations to your PayPal account. It's very simple to ready webhooks there, as opposed to PayPal where you need to have a business organization account, so it's dandy for demonstration purposes or small-scale donation processing.

When a user donates $10 or more, the bot will assign them a Premium Member role that changes their proper name color and moves them to the top of the online users' listing. For this project, we're going to use Node.js and a Discord API library called Eris (documentation link: https://abal.moe/Eris/). Eris is non the only JavaScript library. You could cull discord.js instead. The code that we will write would be very similar either manner.

As an aside, Patreon, another donation processor, provides an official Discord bot and supports configuring Discord roles as contributor benefits. We're going to implement something similar, merely of class more basic.

The code for every step of the tutorial is available on GitHub (https://github.com/mistval/premium_bot). Some of the steps shown in this post omit unchanged code for brevity, and so follow the provided links to GitHub if yous think y'all might be missing something.

Creating a Bot Account

Before we tin start writing lawmaking, we need a bot account. Before we can create a bot account, we demand a user business relationship. To create a user account, follow the instructions here.

Then, to create a bot account, we:

ane) Create an application in the developer portal.

screenshot of the developers portal

2) Fill in some basic details about the awarding (note the CLIENT ID shown here—we'll need information technology later).

screenshot of filling in basic details

3) Add a bot user connected to the application.

screenshot of adding a bot user

four) Turn off the PUBLIC BOT switch and note the bot token shown (nosotros'll need this afterward every bit well). If you ever leak your bot token, for case by publishing it in an image in a Toptal Blog mail service, it is imperative that yous regenerate information technology immediately. Anyone in possession of your bot token tin can control your bot's account and cause potentially serious and permanent trouble for you lot and your users.

screenshot of "a wild bot has appeared"

5) Add together the bot to your examination guild. To add together a bot to a gild, substitute its client ID (shown earlier) into the post-obit URI and navigate to information technology in a browser.

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

Add the bot to your test guild

After clicking authorize, the bot is at present in my examination club and I can see it in the users list. Information technology's offline, but we'll fix that soon.

Creating the Project

Bold you have Node.js installed, create a projection and install Eris (the bot library we'll use), Limited (a web awarding framework we'll use to create a webhook listener), and trunk-parser (for parsing webhook bodies).

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

Getting the Bot Online and Responsive

Let's get-go with babe-steps. First we volition just get the bot online and responding to u.s.a.. Nosotros can do this in ten-twenty lines of code. Inside of a new bot.js file, we need to create an Eris Customer instance, pass it our bot token (acquired when we created a bot application above), subscribe to some events on the Client instance, and tell information technology to connect to Discord. For demonstration purposes, nosotros'll hardcode our bot token into the bot.js file, but creating a separate config file and exempting it from source control is good practise.

(GitHub code link: https://github.com/mistval/premium_bot/blob/main/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 continued and ready, log to panel. bot.on('gear up', () => {    panel.log('Connected and set.'); });  // Every time a message is sent anywhere the bot is present, // this event will fire and we volition cheque if the bot was mentioned. // If information technology was, the bot will try to answer 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) {            // In that location are various reasons why sending a message may fail.            // The API might time out or asphyxiate and render a 5xx status,            // or the bot may not have permission to send the            // message (403 status).            console.warn('Failed to answer to mention.');            panel.warn(err);        }    } });  bot.on('mistake', err => {    console.warn(err); });  bot.connect();                  

If all goes well, when you run this code with your own bot token, Connected and set. will be printed to the console and you will run across your bot come up online in your examination server. You can mention2 your bot either past right-clicking it and selecting "Mention," or by typing its name preceded by @. The bot should respond by saying "Nowadays."

Your bot is present

2 Mentioning is a manner to become some other user'southward attention fifty-fifty if they aren't present. A regular user, when mentioned, volition be notified by desktop notification, mobile push notification, and/or a lilliputian ruddy icon appearing over Discord's icon in the organisation tray. The manner(due south) in which a user is notified depends on their settings and their online state. Bots, on the other hand, exercise not get whatever kind of special notification when they are mentioned. They receive a regular Message Create event similar they do for any other message, and they can check the mentions attached to the outcome to determine if they were mentioned.

Record Payment Command

Now that we know we can become a bot online, allow'due south get rid of our current Message Create event handler and create a new one that lets us inform the bot that we've received payment from a user.

To inform the bot of payment, we will issue a command that looks like this:

          pb!addpayment @user_mention payment_amount                  

For case, pb!addpayment @Me x.00 to record a payment of $x.00 fabricated past Me.

The lead! part is referred to as a command prefix. It is a good convention to choose a prefix that all commands to your bot must begin with. This creates a measure of namespacing for bots and helps avoid collision with other bots. Most bots include a help command, but imagine the mess if you had ten bots in your guild and they all responded to aid! Using pb! as a prefix is not a foolproof solution, as at that place may exist other bots that also use the same prefix. Virtually popular bots allow their prefix to be configured on a per-social club ground to help prevent collision. Another option is to utilize the bot's own mention as its prefix, although this makes issuing commands more verbose.

(GitHub code link: https://github.com/mistval/premium_bot/hulk/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 corporeality = parseFloat(args[one]);    // TODO: Handle invalid command arguments, such as:   // 1. No mention or invalid mention.   // two. No amount or invalid amount.    render msg.channel.createMessage(`${mention} paid $${amount.toFixed(two)}`); };  bot.on('messageCreate', async (msg) => {   const content = msg.content;    // Ignore any messages sent as direct messages.   // The bot will only have commands issued in   // a guild.   if (!msg.aqueduct.guild) {     return;   }    // Ignore whatever message that doesn't commencement with the correct prefix.   if (!content.startsWith(PREFIX)) {       render;   }    // Extract the parts of the command and the command name   const parts = content.separate(' ').map(s => due south.trim()).filter(south => s);   const commandName = parts[0].substr(PREFIX.length);    // Go the advisable handler for the command, if there is one.   const commandHandler = commandHandlerForCommandName[commandName];   if (!commandHandler) {       render;   }    // Divide the command arguments from the command prefix and control name.   const args = parts.piece(1);    try {       // Execute the control.       await commandHandler(msg, args);   } take hold of (err) {       console.warn('Mistake handling control');       console.warn(err);   } });  bot.on('fault', err => {   panel.warn(err); });  bot.connect();                  

Let'due south endeavour it.

Interacting with the bot

Non only did we become the bot to respond to the pb!addpayment control, just we created a generalized blueprint for handling commands. We can add more commands but by adding more handlers to the commandHandlerForCommandName lexicon. We have the makings of a uncomplicated command framework here. Treatment commands is such a central function of making a bot that many people have written and open-sourced command frameworks that yous could use instead of writing your own. Command frameworks often permit you to specify cooldowns, required user permissions, command aliases, command descriptions and usage examples (for an automatically generated aid control), and more. Eris comes with a built-in command framework.

Speaking of permissions, our bot has a bit of a security problem. Anyone tin execute the addpayment command. Let's restrict it and then that only the bot's owner can use it. We'll refactor the commandHandlerForCommandName dictionary and have it incorporate JavaScript objects as its values. Those objects volition comprise an execute property with a command handler, and a botOwnerOnly property with a boolean value. Nosotros'll besides hardcode our user ID into the constants section of the bot and then that information technology knows who its owner is. You lot can discover your user ID by enabling Developer Mode in your Discord settings, and so right clicking your username and selecting Copy ID.

(GitHub lawmaking link: https://github.com/mistval/premium_bot/blob/main/src/bot_step3.js)

          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 corporeality = parseFloat(args[ane]);        // TODO: Handle invalid command arguments, such equally:       // 1. No mention or invalid mention.       // 2. No corporeality or invalid amount.        return msg.channel.createMessage(`${mention} paid $${amount.toFixed(2)}`);   }, };  bot.on('messageCreate', async (msg) => {   try {       const content = msg.content;        // Ignore any letters sent as direct messages.       // The bot volition simply take commands issued in       // a lodge.       if (!msg.channel.society) {           return;       }        // Ignore any bulletin that doesn't get-go with the right prefix.       if (!content.startsWith(PREFIX)) {           return;       }        // Extract the parts and proper name of the command       const parts = content.split up(' ').map(due south => due south.trim()).filter(s => south);       const commandName = parts[0].substr(PREFIX.length);        // Get the requested control, if in that location is one.       const command = commandForName[commandName];       if (!control) {           return;       }        // If this command is only for the bot owner, refuse       // to execute information technology for any other user.       const authorIsBotOwner = msg.author.id === BOT_OWNER_ID;       if (control.botOwnerOnly && !authorIsBotOwner) {           return await msg.channel.createMessage('Hey, but my owner can issue that command!');       }        // Separate the command arguments from the command prefix and name.       const args = parts.piece(ane);        // Execute the control.       expect command.execute(msg, args);   } catch (err) {       console.warn('Fault handling message create outcome');       console.warn(err);   } });  bot.on('error', err => {  console.warn(err); });  bot.connect();                  

Now the bot will angrily refuse to execute the addpayment control if anyone other than the bot possessor tries to execute it.

Next let's have the bot assign a Premium Member role to anyone who donates 10 dollars or more. In the peak part of the bot.js file:

(GitHub lawmaking link: https://github.com/mistval/premium_bot/blob/principal/src/bot_step4.js)

          const eris = crave('eris');  const PREFIX = 'lead!'; const BOT_OWNER_ID = '523407722880827415'; const PREMIUM_CUTOFF = 10;  const bot = new eris.Customer('my_token');  const premiumRole = {    name: 'Premium Fellow member',    color: 0x6aa84f,    hoist: true, // Show users with this role in their own section of the member listing. };  async function updateMemberRoleForDonation(lodge, member, donationAmount) {    // If the user donated more than $10, give them the premium role.    if (order && member && donationAmount >= PREMIUM_CUTOFF) {        // Get the role, or if it doesn't be, create information technology.        allow role = Assortment.from(guild.roles.values())            .observe(part => role.name === premiumRole.name);         if (!role) {            part = await club.createRole(premiumRole);        }         // Add the role to the user, along with an explanation        // for the guild log (the "inspect log").        return fellow 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.club;        const userId = mention.replace(/<@(.*?)>/, (friction match, group1) => group1);        const member = guild.members.get(userId);         // TODO: Handle invalid command arguments, such as:        // one. No mention or invalid mention.        // 2. No amount or invalid amount.         return Hope.all([            msg.aqueduct.createMessage(`${mention} paid $${amount.toFixed(2)}`),            updateMemberRoleForDonation(guild, member, amount),        ]);    }, };                  

Now I can try saying atomic number 82!addpayment @Me 10.00 and the bot should assign me the Premium Member role.

Oops, a Missing Permissions error appears in the console.

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

The bot doesn't have the Manage Roles permission in the exam guild, so it cannot create or assign roles. We could give the bot the Administrator privilege and we'd never have this kind of trouble again, but as with any system, it's all-time to merely requite a user (or in this case a bot) the minimum privileges that they require

Nosotros tin can give the bot the Manage Roles permission by creating a role in the server settings, enabling the Manage Roles permission for that role and assigning the role to the bot.

Start managing roles
Create a new role

Now, when I attempt to execute the command again, the role is created and assigned to me and I have a fancy name color and a special position in the member listing.

A new role is assigned

In the command handler, we have a TODO comment suggesting that we need to cheque for invalid arguments. Let's take care of that now.

(GitHub lawmaking link: https://github.com/mistval/premium_bot/hulk/master/src/bot_step5.js)

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

Here's the full code so far:

(GitHub lawmaking link: https://github.com/mistval/premium_bot/hulk/master/src/bot_step5.js)

          const eris = crave('eris');  const PREFIX = 'pb!'; const BOT_OWNER_ID = '123456789'; const PREMIUM_CUTOFF = 10;  const bot = new eris.Client('my_token');  const premiumRole = {    name: 'Premium Fellow member',    colour: 0x6aa84f,    hoist: true, // Bear witness users with this role in their own section of the member listing. }; async function updateMemberRoleForDonation(guild, member, donationAmount) {    // If the user donated more than $ten, give them the premium role.    if (guild && member && donationAmount >= PREMIUM_CUTOFF) {        // Go the role, or if information technology doesn't exist, create it.        let role = Array.from(gild.roles.values())            .notice(role => part.name === premiumRole.proper name);         if (!role) {            role = await guild.createRole(premiumRole);        }         // Add the office to the user, forth with an explanation        // for the guild log (the "inspect log").        return fellow 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 social club = msg.channel.guild;        const userId = mention.replace(/<@(.*?)>/, (friction match, group1) => group1);        const member = order.members.get(userId);         const userIsInGuild = !!member;        if (!userIsInGuild) {            render msg.channel.createMessage('User not found in this guild.');        }         const amountIsValid = amount && !Number.isNaN(amount);        if (!amountIsValid) {            return msg.channel.createMessage('Invalid donation corporeality');        }         return Promise.all([            msg.aqueduct.createMessage(`${mention} paid $${amount.toFixed(two)}`),            updateMemberRoleForDonation(social club, member, amount),        ]);    }, };  bot.on('messageCreate', async (msg) => {   try {       const content = msg.content;        // Ignore whatsoever messages sent equally straight messages.       // The bot volition only accept commands issued in       // a club.       if (!msg.aqueduct.society) {           return;       }        // Ignore whatsoever message that doesn't start with the right prefix.       if (!content.startsWith(PREFIX)) {           return;       }        // Extract the parts and name of the command       const parts = content.split(' ').map(southward => due south.trim()).filter(s => s);       const commandName = parts[0].substr(PREFIX.length);        // Get the requested control, if at that place is one.       const command = commandForName[commandName];       if (!command) {           return;       }        // If this command is merely for the bot owner, refuse       // to execute information technology for any other user.       const authorIsBotOwner = msg.author.id === BOT_OWNER_ID;       if (command.botOwnerOnly && !authorIsBotOwner) {           return look msg.channel.createMessage('Hey, only my owner can upshot that command!');       }        // Divide the command arguments from the command prefix and name.       const args = parts.slice(1);        // Execute the command.       await command.execute(msg, args);   } take hold of (err) {       console.warn('Error treatment message create event');       console.warn(err);   } });  bot.on('fault', err => {  console.warn(err); });  bot.connect();                  

This should give yous a good bones idea of how to create a Discord bot. Now we are going to see how to integrate the bot with Ko-fi. If you'd like, you lot can create a webhook in your dashboard at Ko-fi, make certain your router is configured to forward port 80, and ship real live test webhooks to yourself. Merely I'm just going to use Postman to simulate requests.

Webhooks from Ko-fi evangelize payloads that look like this:

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

Let's create a new source file called webhook_listener.js and use Express to listen for webhooks. We'll only have one Express road, and this is for demonstration purposes, so nosotros won't worry too much almost using an idiomatic directory structure. We'll just put all of the web server logic in one file.

(GitHub code link: 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;  form WebhookListener {  listen() {    app.get('/kofi', (req, res) => {      res.send('Hello');    });     app.listen(PORT);  } }  const listener = new WebhookListener(); listener.listen();  module.exports = listener;                  

And then let's require the new file at the elevation of bot.js and so that the listener starts when we run bot.js.

(GitHub code link: https://github.com/mistval/premium_bot/hulk/main/src/bot_step6.js)

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

After starting the bot, you should see "Hullo" when yous navigate to http://localhost/kofi in your browser.

Now let's have the WebhookListener procedure the information from the webhook and emit an result. And now that we've tested that our browser can admission the route, let's change the route to a Postal service route, as the webhook from Ko-fi will be a Postal service asking.

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

          const express = require('express'); const bodyParser = require('trunk-parser'); const EventEmitter = require('events');  const PORT = process.env.PORT || fourscore;  const app = express(); app.use(bodyParser.json());  form WebhookListener extends EventEmitter {  listen() {    app.post('/kofi', (req, res) => {      const data = req.torso.information;      const { bulletin, timestamp } = data;      const corporeality = parseFloat(data.amount);      const senderName = data.from_name;      const paymentId = information.message_id;      const paymentSource = 'Ko-fi';       // The OK is only for us to see in Postman. Ko-fi doesn't care      // well-nigh the response trunk, information technology but wants a 200.      res.ship({ status: 'OK' });       this.emit(        'donation',        paymentSource,        paymentId,        timestamp,        corporeality,        senderName,        message,      );    });     app.listen(PORT);  } }  const listener = new WebhookListener(); listener.mind();  module.exports = listener;                  

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

At the lesser of bot.js:

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

          role findUserInString(str) {    const lowercaseStr = str.toLowerCase();     // Await 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 part onDonation(    paymentSource,    paymentId,    timestamp,    amount,    senderName,    message, ) {    endeavor {        const user = findUserInString(bulletin);        const guild = user ? bot.guilds.find(lodge => guild.members.has(user.id)) : null;        const guildMember = lodge ? guild.members.get(user.id) : aught;         return look updateMemberRoleForDonation(lodge, guildMember, corporeality);    } take hold of (err) {        console.warn('Mistake handling donation issue.');        console.warn(err);    } }  webhookListener.on('donation', onDonation); bot.connect();                  

In the onDonation office, we run into two representations of a user: as a User, and as a Member. These both represent the aforementioned person, but the Fellow member object contains guild-specific data about the User, such every bit their roles in the guild and their nickname. Since we desire to add a office, we demand to use the Member representation of the user. Each User in Discord has 1 Member representation for each guild that they are in.

Now I can use Postman to examination the code.

Testing with Postman

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

If the bulletin from Ko-fi does not comprise a valid username; nonetheless, zippo happens. The donor doesn't get a role, and we are not aware that we received an orphaned donation. Allow'southward add a log for logging donations, including donations that can't be attributed to a guild fellow member.

Offset we need to create a log channel in Discord and go its channel ID. The channel ID can be found using the developer tools, which tin be enabled in Discord's settings. Then y'all can correct-click whatsoever channel and click "Copy ID."

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

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

          const LOG_CHANNEL_ID = '526653321109438474';                  

So nosotros can write a logDonation office.

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

          office 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: {            championship: 'Donation received',            colour: embedColor,            timestamp: timestamp,            fields: [                { proper 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 },                { proper noun: 'Message', value: message, inline: true },            ],        }    }     bot.createMessage(LOG_CHANNEL_ID, logMessage); }                  

Now we tin update onDonation to call the log function:

          async role onDonation(    paymentSource,    paymentId,    timestamp,    amount,    senderName,    message, ) {    try {        const user = findUserInString(message);        const gild = user ? bot.guilds.observe(club => guild.members.has(user.id)) : goose egg;        const guildMember = order ? guild.members.go(user.id) : nada;         return expect Hope.all([            updateMemberRoleForDonation(society, guildMember, amount),            logDonation(guildMember, amount, paymentSource, paymentId, senderName, bulletin, timestamp),        ]);    } catch (err) {        console.warn('Error updating donor role and logging donation');        console.warn(err);    } }                  

Now I tin invoke the webhook once more, beginning with a valid username, and 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 than complex JavaScript object that we create and ship to Discord in the new logDonation function is a special type of message referred to every bit a rich embed. An embed gives you some scaffolding for making attractive letters similar those shown. Merely bots tin can create embeds, users cannot.

Now nosotros are beingness notified of donations, logging them, and rewarding our supporters. Nosotros can too add donations manually with the addpayment control in case a user forgets to specify their username when they donate. Let's call it a twenty-four hours.

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

Next Steps

We've successfully created a bot that can help usa track donations. Is this something we tin really 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 beginning place), they volition lose their Premium Member office, and if they rejoin, they won't go it back. We should shop payments by user ID in a database, so if a premium fellow member rejoins, nosotros can requite them their function back and maybe send them a nice welcome-dorsum message if we were so inclined.
  2. Paying in installments won't work. If a user sends $5 and then afterward sends some other $five, they won't get a premium part. Similar to the to a higher place issue, storing payments in a database and issuing the Premium Member role when the full payments from a user reaches $ten would assistance here.
  3. It'due south possible to receive the aforementioned webhook more than once, and this bot will tape 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 transport the webhook over again later. Keeping rails of payments in a database and ignoring webhooks with the same ID every bit previously received ones would help here.
  4. Our webhook listener isn't very secure. Anyone could forge a webhook and become a Premium Fellow member role for complimentary. Ko-fi doesn't seem to sign webhooks, and then y'all'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 1 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-fourth dimension hobbyists, some bots experience tremendous popularity and maintaining them evolves into a circuitous and demanding job.

By social club-count, Rythm is currently the most widespread bot on Discord. Rythm is a music bot whose specialty is connecting to vocalism 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 ninety million users, and at its peak plays audio for around 100,000 simultaneous users in xx,000 separate guilds. Rythm'south creator and chief developer, ImBursting, kindly agreed to answer a few questions almost what information technology's like to develop and maintain a big-calibration bot similar Rythm.

Interviewer: Can you tell u.s.a. a bit about Rythm'due south high level compages and how it'southward 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 assistance from a small hosting company, GalaxyGate.

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

Rythm'due south first evolution was written in Python, which isn't a very performant language, and so around the time we hit x,000 servers (later 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. Afterwards re-coding, functioning improved tenfold and kept the issues at bay for a while. And then we striking the 300,000 servers milestone when bug started surfacing once again, at which indicate I realised that more scaling was required since ane 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 divide microservices using an open source server called Lavalink. This improved operation quite a bit but the concluding round of infrastructure was when we divide this into 9 seperate clusters to run on 9 physical servers, and made custom gateway and stats microservices to brand sure everything ran smoothly like it would on i machine.

I noticed that Rythm has a canary version and you lot 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. Tin you tell u.s. 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 ordinarily deploying them to Rythm 2 to test on a wider calibration and then product Rythm. The biggest issue we see is really long reboot times due to Discord rate limits, and is the reason I endeavor my best to make sure an update is ready before deciding to push information technology.

I exercise get a lot of help from volunteer developers and people who genuinely want to help the customs, I want to make certain everything is done correctly and that people will always get their questions answered and get the all-time support possible which ways im constantly on the lookout for new opportunities.

Wrapping It Up

Discord's days of existence a new kid on the block are by, and it is now i of the largest existent-time communication platforms in the world. While Discord bots are largely the foray of small-fourth dimension hobbyists, we may well come across commercial opportunities increase as the population of the service continues to increment. Some companies, similar the aforementioned Patreon, have already waded in.

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

Chatbots are generally fun, except when their responses to your intricate queries accept the intellectual the depth of a cup of water. To ensure a cracking UX for your users run across The Chat Crash - When a Chatbot Fails past the Toptal Design Weblog for 5 design problems to avert.

lyonsbeetting1968.blogspot.com

Source: https://www.toptal.com/chatbot/how-to-make-a-discord-bot

Post a Comment for "And Make an Announcement to Get More Members in My Server Again"