Hosting and structuring multiple (Discord) bots in a single script with Python, defining Classes

New Features:-
- Bots auto-detect servers they belong to
- Bots loop through their servers lists to send updates
- Merged ~3 pages of code into Bot class
- Created .env based test mode config so that each environment runs on the appropriate mode.

Tl;Dr on how it runs:
1. Initialize each bot instance
2. Start a thread for the bot update scripts to run on an endless loop
3. Loop through all bots to log online
4. Loop_forever to keep bots online.

Transcript

Hey, what's up everyone. It's been a little while. It's been a little while since we made those last discord bots and I've done a few upgrades. So we'll take you through the, the structure of the refactor we did. And yeah, I guess just all in, all much better fundamentals and operation tater here will be probably not really helping us but she's decided to join this video so welcome.
Okay.  moving on. So the biggest thing that we've done, if you take a look at, at some of the past videos is each bot was kind of running in its own way. I'd started to create some definitions of what a bot is. So I created a class and had some really basic depths in there. And so we, we really just expanded it out and we made it so that the bots just essentially run themselves.
So, you know, starting just down here at the bottom we've gone and created a series of basically a bot list, right. With all the defining characteristics. Now, definitely this needs to be broken out into its own file soon. Just didn't do it with all the other potentially breaking changes. And so if we kind of go from through a run from beginning to end, I'll show you how this all works.
So first we do start up start up flask and that is. Just because when I'm running on relet, which is a really useful test server flask is what's gonna basically create the web server part of it. And arguably we're not really using it anymore because I am no longer actually having This pinged by Aron job, which is causing it to run every minute.
So, so this part is a little bit optional and we don't have to spend too much time on it. Now we have the definition of the bots online function, the one that's gonna get everything online. We're using a sync IO to create the event loop. And what we do is we iterate through the bots and our list and we try.
To start each bot. And if that works great, if not, then , we print an error. This isn't a very specific error. So we could go ahead and add in here, if we're in for key in bot list. So we could do bot list P name. And of course I use the wrong brackets here. So if we just use curly brace,
Then that should work out and we'll get the name of the bot actually log. Okay. Moving on. Or else we print key online. Okay, perfect. So, oh yeah, this probably what I would do here. Easier. Okay. Moving on. We have bot start the function, right? So we're actually calling this function and then what I'm doing here is I'm creating a thread.
So. This first event loop right here. What this is doing is it's keeping the bots online. And then in parallel, we're running this never ending loop that I'll show you very shortly. And what that does is it actually runs all the updates for the bots. So basically there we are. And then, so just in structure, we're declaring this function, we're declaring the second function.
We're calling that second function. We're initializing the threads. We're creating this loop run forever. And then basically nothing runs after this, at least within the context of the function in the flow. And then we have bots online. So we're just calling that function. So, so. Essentially everything at the high level.
So let's go one level deeper. What is bot starting? If we take a look at our bot class definition, I've already showed you the bot list. And some of the things I would find are a name, a display name, a symbol base currency the number of decimals to use in the currency, the base currencies, if it's USD or in this case forum some of the bots actually represent the.
Token prices for things that are in game items. And so we actually use the Orum price as the base, rather than USD. If we start at the top over here of this definition. So we're importing discord. Even though, and this is one of the reasons by the way that I use relet is that visual studio, I, something in my Python setup.
Isn't great. It doesn't recognize all of. All of the dependencies that I'm using all the libraries. And so it's really hard to run a preview here. So I just do that on re. So we declare an empty bot list variables that it's global test mode is something that I've configured in the environmental variables, so that in each different, you know, whether I'm running here it's on test mode.
If I'm running in relet. It might be in test mode. I have a test rep and then I have a live one and actually this gets deployed to Heroku. And in there it's not on test mode anymore. So we're able to configure both of those. And what test mode does is it means it only pings the test server. And the test server ID is right there.
So as we move along, we have the definition of the bot and the initializing functions. So we take in the name we take in the symbol, we take in the display name, which I'm not actually sure I'm fully using yet. We have self dot client, which initializes the disc, the bot as a discord client, we have the token which we get from the envir the dot M.
File. We have self.online, which is a property. So we can declare if this is online, but actually I think we've stopped using that. So I might be able to co to remove it. The runner is what is actually getting this online. So we create the instance of the client. We get the token and then we have the client start using the.
And then bot type is so that I can know what type of bot it is. Is it a token bot? Do I need to update it? Is it a timer bot? Do I need to do a different function with it? What I thought would be really nifty is actually to include a property here. I won't write it in right now, but it would be something like.
Self dot function and that would equal a name of update function. And that way we could basically invoke the function for the bot. So I could just say, you know, bot update and it'll already know what function to use the update. So then we also added creating a server list. So now when the bot starts up, we actually get the list of servers before I was manually populating the server list.
And every time someone added the bot to a new server, I had to add the ID, obviously it wasn't great. So we have an ready function and on Guild join function that we're defining as well. And so if we go a little bit lower down, we can see all of. So here is the ready. What we do is we set self.online as true.
We run update server list. So we get all the new servers and then we just print, you know, this is online and then. If we go down here, we have on Guild joint. Technically I don't even know if this is necessary and I'm pretty sure it's not at this point because every time we run update, we update server list anyways.
So all of this is again, arguably not even necessary, but we have a couple of iterations in one then moving. Def update server list. So this is where we actually do it. We are setting the updating now to true, and I did this. So so that basically we cannot run. A second update function at the same time or so that we can wait till update is done before.
We run the following functions, which essentially is something that using ASIN functions could do as well, but I didn't get it working. So I did this. So here we have self dot server list. So we're initializing that now if we're in test mode, it's just the test server ID, which we are pulling in from the top and otherwise.
It goes to else. And so for each Guild in self dot client dot guilds, which is a reference to the bought client's property guilds. So we print out the Guild name and then if that Guild, I don't even know why I have to do a duplicate check here. It seemed like a good idea at the time. But if that Guild is not already in the list and it's not test server because here we're not in test mode and implicitly, I don't want to hit the test server with the live version, cuz it'll interfere with tests.
So if we pass all of these, then we print a new Guild name Guild. I. And we add it to the list using self dot server list append. Moving ahead then we just print it out. And that this function is completed. We print the whole server list. Now this was really cool, cause we were surprised how many servers our bots had been added to and self dot, updating now is false so that we can determine that this update has run for that bot.
Okay. What's next to run the actual bot update. So we're taking self we're, taking in a parameter called new name. New name is actually going to come from the function that is calling self done update. So we've built this out basically. Here. So we print that. So we print the server list out that it's going to update for, from there, we loop through the server list and we print that we are updating.
So we don't need all these prints. I like a bit of a verbose log, cuz it makes me feel like things are happening and some people hate them cuz they're messy and advise that you just clean up all these prints as soon as you're not using them. So again, if test mode is true and the server Is not test server and it's not empty.
And I was debating if I needed this, I probably don't, but I just didn't remove it yet. So we skip, if we're, you know, this would basically say we're in test mode, but the server is not test mode, so we're skipping it. This is also I think no longer needed as I've assigned the server forcibly to be test mode.
So we wouldn't really get here, but this is an extra check. And if server list is empty or. Then we just skip it. So that is something that is helping because on the first boot, not everything happens exactly in the right order. And so we were attempting to do updates even when there was no server list.
Now you would say that maybe I could use the update function or sorry, the what, what was it? The. Self dot updating now equals true. It's possible that I could use this. And originally one of the intents of self.online was just to run, you know, change that once the server list had actually run.
So there was a couple of different attempts at implementation, but ultimately this does work. And then finally you know, we're still in this loop of serverless. So here we have our request and we send that request to discord with the new name over. And that's it. We do a try. If it fails, then we print the error and that is the bot class.
So it's a little bit more complex in one way and much more straightforward and better organized than the other. The cool thing is that this foundation allows each bot to really behave in a modular way and essentially have its own properties and be called much more useful. So if we take a look at, get token vows, right?
Cause when we actually let me just show you one thing first. So what actually happens when we run, update all the bots, you may remember from Maine over here that we are starting a new thread with update all the bots. So we just went through the online thread to get the bots online and that's how you get them to show in the discord member list on the right side as.
So now we're calling from endpoints, which I can really rename right now or move this function elsewhere, cuz it no longer has anything to do with an endpoint. Other than maybe I would call it from here. If somebody pings the app, I could use that as a way to update right now. I'm just returning. Okay.
If you ping the app at the, you know, at whatever address it's running at and previously this is no longer true, but I used to actually jog the function by pinging it every minute using Chrome job.org. But here, instead we decide to make an, you know, an endless. So while true, which is the endless loop part, we try get token values and that's gonna update all the token bots.
Then we try reset timer update and then we try tournament timer. And that is it. And then the last thing we do is we ping dead man snitch, which is something I'm using on Heroku as a uptime monitor. And so as long as we check in every hour, it doesn't complain. And if we don't check in once an hour, then I know something's broken and it'll email me.
So looking at get token values, we've already covered reset timer and tournament timer. And I don't think there's a whole lot of change since the past videos. We have a few things here, the function that expresses a price in, or instead of in U S D we have this function that is mapping an array of our token prices.
And that's based on the response we're getting from Nomics. When we ask for the token prices. And then we have really the body of the function right here. And what's cool is I haven't deleted the stuff I commented out. So I'm gonna show you how much of a code cleanup expanding our bot class and integrating the functions into the bots actually yielded after pausing for a quick sip of coffee.
Okay. So if we take a look through here, we have the constructor of our token list. And what this is doing is filtering all of our bot lists into a series of keys. And what we're grabbing here is the symbol. So we're essentially creating a flat list of all of the keys from bot list. And the reason we're doing that is so that we can go here and join them.
You see our map function over here. So this is actually one of the more complex parts when it comes to array mapping. And so what you'll see here is that we're joining a. That we're creating using this map function. And the map right here is basically item currency and item price. And so we're ending up with an array of those two items, which is a key value pair.
Okay. So that is what's happening here. And you can see our IDs is plugged in. So we're asking at the end of all that, just for a list of all the currency symbol, That belonged to the bots and our list. So now when I add a new bot to the list, it automatically starts asking for its currency. Another note is that if you followed past videos and recently NOx has added a per page variable that you need to ask for.
So how many responses per page? I've just set it at a thousand. So we get the Nomics response and we use here token prices. Is that? Oh actually I was mistaken. Okay. So I was talking about using this function before, but that is not true. What we're doing here is just mapping a quick list of of Yeah, I mean, and still the same outcome, but the explanation was a bit off.
So all of what I explained was actually happening here. And then what we're doing is mapping a key value pairing from the response of extracting currency and price, and then we're returning them. So now what we have is, as I said, that key value pairing of Nomics responses of the token. So let's say Rader or Orum or whatever it is, and that will be coupled with the actual price, which makes it much easier to just grab the values.
So, what I was doing before was this really hacky wild loop, which was the length of the response. And I was manually adding each bot's name here. And I said, if the response is currency field is equal to Orum, then assign it to this value. Grab the price from that, you know, same from that same index.
And. And it was a bit tedious, cuz one, it feels dumb to do, you know, there's a better way to do it. And two it's just, again, requiring me to manually add something. Every time we have a new bot, we've been adding a lot of bots, so we couldn't, it was time to eliminate that work. And so that's what we did with these fancy functions over here of automatically sorry, this one up here of automatically mapping into a key value pair.
So once we do that, I still am doing one manual part over here, which would be really nice to loop through. And I know we can figure out a way to do that soon. Some of the variables are the number of decimals that we're formatting here. And then the fact that some of these, as I said, do need to be expressed in or price.
And so this will be the next thing to kinda remove. Now because we're using a loop that is able to go through all of the bots and just call their update function. We eliminate all of this code where I'm manually for each. So what we're doing is each bot. I would call all of the servers on the global list and that would yield a lot of errors.
And the reason it yield a lot of errors is because not every bot was in every server and discord will rate, limit you more frequently if you're sending erroneous calls. So it's something to watch out for. And it was a, an important optimiz. So I've still kind of done a little bit of a hack here. You can see the room for improvement on these last two sections.
And here I've just said for the bot and the list, if the base currency is USD, then run the update. And if the base currency is not in us D then run the update, but express the price in forum instead, and run that extra function so that it Basically shown Inor. So that, that is the meat of how we've done this meaningful refactor and also just created a bot class that handles most of the work so that the code isn't kind of broken out into these disparate functions.
So I hope this is useful then I suppose, in the next video, maybe we'll talk about the pipeline set up in Heroku some of the GitHub stuff and how we've optimized delivering this and, and made it a little bit more stable. Thanks for watching remember to like, and subscribe and we'll keep putting out videos on how to build discord bots.

Mitch Schwartz

More of our stories from

Discord
Discord Bots V4 - Building and hosting multiple discord bots in one python script

How to keep multiple discord bots to appear online

Read more...
Creating Discord Token Price & Countdown Timer bots that update every minute, with free hosting.

Create a Discord bot that keeps track of token prices and countdowns every minute, with free hosting.

Read more...
Make a Low Code Discord Bot with Integromat Part 1 and Part 2

How to create and host a discord token price bot (or any other name-changing bot)

Read more...
Hosting and structuring multiple (Discord) bots in a single script with Python, defining Classes

How we implemented bot classes as the foundation for the script, setting the foundation for each bot to operate as a mod

Read more...

All Topics