How to Make a Discord Bot for Availability Alerts

Discord bot for availability alerts

So far we’ve covered a few steps on increasing your chances of getting a PS5 at retail price.  We’ve gone over how to write your own PS5 availability tracker, as well as how to have it send out email notifications. Now let’s upgrade that basic web scraper into a Discord bot for availability alerts.

In the spirit of keeping things beginner friendly, we’re coding in Python with an easy-to-use free library for web scraping uniquely named BeautifulSoup.

What You’ll Need to Upgrade Your Scraper Into a Discord Bot for Availability Alerts

There are some more libraries you’ll need to install and import to expand the web scraper we’ve made so far.

One of them involves making an external file for private variables, rather than including it in the code. This step isn’t necessary if you’re never going to show your code to anyone else. But, it is a good habit to have.

Also, in order for the bot to play nice with Discord, you’ll need to actually use Discord. Crazy, I know. 

Discord Setup

Presumably, you have some degree of familiarity with Discord already, hence why you’re compelled to make a Discord bot. You can use your normal account to set the bot up. Or, you can make a separate account for it if you really want. 

You’ll need admin rights in every server that you want to invite the bot to. Also, to simplify things, the code will assume that the bot has full rights. However, most people won’t casually grant them to a random bot. Because of this fact, I recommend making a new server specifically for the bot’s purposes.

Once you have your server ready, you’ll need to make a bot account to get a unique token password for your bot. The documentation for the Discord Python API wrapper covers how to create a bot account in depth with images. 

For a TLDR version though:

  • Log onto the Discord Site.
  • Then go to the Applications page and create a New Application.
  • Next, the Bot tab and Add Bot.
  • Copy the token and keep it to the side for now.
  • Go back to the Applications Page and go to your bot’s page.
  • Navigate to the OAuth2 tab.
  • Select Bot under Scopes and Administrator under Bot Permissions.
  • Use the generated URL to invite the bot to the server and Authorize it.

In addition to all of this setup, there’s one more thing you want to do inside the server the bot will run in. You want to create two roles via Server Settings > Roles. 

The first role is for people that are allowed to command the bot to manually run a scraping attempt. I’ll refer to it as the authorized role “Bot Boss” in the code. Once the role is created, be sure to give the role to yourself, the bot is going to check for it.

The other role is for people that will get pinged when the bot detects that PS5s are available. I’ll refer to it as the alert role “Tracking PS5 Alerts” in the code. Again, make sure you give the role to yourself, as I’m assuming that’s the whole reason you’re making this bot.

Now, let’s discuss the extra goodies we’ll be using.

Additional Imports to Upgrade Your Scraper Into a Discord Bot for Availability Alerts

As you’ve likely already guessed, interacting with the Discord API isn’t a default feature of Python. So, we’ll need to install and then import it. The Python Discord API Wrapper documentation includes installation instructions for a few different configurations if you need help.

On top of the discord module, we’re going to use something that we could have implemented earlier with the email details; environmental variables. They are saved in a .env file, aka dotenv for syntax reasons.

Environmental Variables

Calling load_dotenv() will look for a .env file within the same directory as your program, as well as its parent directories. Well, unless you specify otherwise. 

That little file is the declaration of sensitive variables, written in normal Python syntax. You can make it in notepad by writing your desired variables, in this case:

DISCORD_TOKEN = ""

Inside of the “” paste the Discord bot token you generated earlier.

Then go to Save As, select Save as Type: All Files, then manually set the .env extension after whatever filename you want to use.

You could theoretically put all of your declared variables in there. But, that would involve more calls than is otherwise necessary to pull each piece of data. So, you should only bother doing that with information you want to keep private from any other people that will potentially see your code. The main use case is protecting account information, particularly passwords.

Operating System Module

Python’s base skeleton isn’t designed to play around with file directories by default. So, to use dotenv, you’ll need to import the OS module, too.

Random Module

While we’re at it, let’s also grab the random module. This will be useful when we have the Discord bot passively loop the scraper function. Repeated calls at fixed time intervals are a red flag for bot shenanigans, as covered in Five Tips for Outsmarting Anti-Scraping Techniques. So, we want to implement a randomized wait time between loops to make it seem more organic.

Import Code

With the explanation breakdowns out of the way, here’s the code snippet for all of the additional imports:

import discord
from discord.ext import commands, tasks
from discord.utils import get
import os
from dotenv import load_dotenv
import random

Additional Variables to Upgrade Your Scraper Into a Discord Bot for Availability Alerts

Simply making the .env file isn’t enough, you need to actually call it and pull the information from it. Assuming you made the .env the same as explained above, pulling your token info from it would look like this:

load_dotenv()
token = os.getenv("DISCORD_TOKEN")

Simple, right? 

Other Variables: Channel

Now we have just a few more variables to add in that don’t have strict privacy requirements.

First up is the channel ID for where we want the bot to send all of its messages. It’s as simple as right-clicking on the desired channel in Discord and selecting the bottom option, Copy ID. 

However, you need to have Developer Mode enabled for your Discord account. To turn it on, go into your Discord client’s User Settings > Advanced and check the first option, Developer Mode. There’s no downside to leaving this on all the time, so you can set it and forget it.

Once you have that channel ID, set the variable with:

channel = bot.get_channel()

Just be sure to paste the recently copied channel ID inside the ().

Other Variables: Roles

Now, let’s also set up the variables for those Discord roles you created earlier.

alert_role = "Tracking PS5 Alerts"
authorized_role = "Bot Boss"

Other Variables: Intents

Speaking of authorization, we need to tell the bot what authorization it has, which it calls intents.

INTENTS = discord.Intents.all()

This is assuming you granted it full admin rights, which will allow for greater flexibility for any additional features you develop in the future. For example, you could program commands for distributing the roles.

Other Variables: Bot

Finally, let’s establish the bot’s settings.

bot = commands.Bot(command_prefix=$, case_insensitive=True, intents=INTENTS)

That command prefix means that when you tell the bot to do something, it has to start with $ so the bot knows that you’re talking to it. 

When you have feature-rich bots, you don’t want them randomly executing commands because someone said a keyword in idle conversation. Just like you need to tell Alexa to answer to a different name when you live with someone with a similar name. It gets awkward real quick.

Case insensitive is so that when you hit caps lock, the bot still functions all the same. This isn’t a big deal when there are only a few simple commands. But, when you have something that has long commands that take variables in, being thrown off by upper vs lower case gets annoying. Trust me, I know.

Additional Functions to Upgrade Your Scraper Into a Discord Bot for Availability Alerts

Now that we’re done with the prep work, let’s get into the meat of the code. The following code is operating under the assumption that your existing functions are the previously explained:

  • get_title(soup)
  • get_price(soup)
  • get_availability(soup)
  • send_email()

With all of their required imports and variables.

We’ll want the bot to not only loop scraping attempts passively, but also on demand. So, let’s take it out of the main code and instead make it a function. 

Function: Scrape

Once we separate the scraper from the main body, its function ends up looking like this:

def scrape(): 
    webpage = requests.get(URL, headers=HEADERS)
    soup = BeautifulSoup(webpage.content, "lxml")
	
	get_availability(soup)
	
	if isAvailable:
		send_email()

If you wanted to get fancy, you’d have it take URL and HEADERS as arguments. That way you could have it look at a different site each call, and use different User Agents too.

For rapid looping scrape attempts, changing User Agents becomes much more important. That’s also when utilizing rotating residential proxies becomes essential. More on that in a bit.

Bot Functions

Now onto the functions that are specifically for the Discord Bot. When these functions get declared, they also need to specify what type of function they are.

Event: On Ready

This little function is triggered when the bot connects to Discord and is ready to start taking commands.

The async before the definition stands for asynchronous. This enables multithreading, so the bot can do more than one thing at a time.

Await means it will pause its multithreaded processes until the await portion is complete. In this case, sending a message.

@bot.event
async def on_ready():
    await channel.send("Ready to work!")
    update_presence.start()

That update_presence function it calls is for rich presence text for the bot. This is what you see when you look at its Discord user status while it’s running. While this isn’t essential, it serves a double duty of ensuring the bot detects the roles properly. Let’s cover that function next.

Task: Update Presence

Here we’ll set the bot to passively loop updates to its rich presence status text every 10 minutes. What it’s going to do is check and display how many people across all of its active servers have the “Tracking PS5 Alerts” role.

@tasks.loop(seconds=600)
async def update_presence():
    guilds = bot.guilds
    members = sum([len(x.members) for x in [get(y.roles, name=alert_role) for y in guilds]])
    rich_presence_text = f"{members} users tracking PS5 availability"

    await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=rich_presence_text))

A sample of the display it could have is “Watching 2 users tracking PS5 availability”. 

If you want to be pedantic, you can have it do an if check for members = 1 and make sure it only outputs “user” instead of “users”.

Task: Passive Loop Scrape

Next up is a passively looping function to check for PS5 availability. 

It’s very risky to set the time between loops too short when you aren’t using a proxy service that rotates IPs for you. For this high-risk scenario, set a randomized time ranging from 500 to 700 seconds, or 8.3 to 11.6 minutes. 
However, if you have your bot go through a reliable proxy provider like KocerRoxy, you can set a tighter schedule without fear of being blocked. You should still randomize it, but you can use much smaller numbers.

@tasks.loop(seconds = random.randint(500, 701))
async def passive_scraper():
    scrape()
    
    if isAvailable:
        await channel.send(f"{alert_role.mention}! {get_title(soup)} is {get_availability(soup)} for {get_price(soup)}")

If it detects that PS5s are available, it will announce a message in the designated channel. This message will ping everyone with the “Tracking PS5 Alerts” role. That way they don’t have to have the channel always sending alerts. Instead, they can set it to only alert them upon being mentioned.

Event: Manual Scrape Call

This function is watching in case someone with the “Bot Boss” role sends a message into chat with the specified command. As long as the start of the message matches it will run the command.

For example, all of the following will trigger it:

  • $r
  • $refresh
  • $RaBbIt FoOt FoR gOoD lUcK

This function will run a manual scrape and output the results regardless of availability. This doubles as a debug command to ensure it’s scraping properly.

@bot.event
@commands.has_role(authorized_role)
async def on_message(message: discord.Message):
    if message.content.lower().startswith(f"{command_prefix}r"):
        scrape()
        await channel.send(f"{get_title(soup)} is {get_availability(soup)} for {get_price(soup)}")

    await bot.process_commands(message)

Main Code Body

After making all of those functions, the main body of the code became hilariously short.

if __name__ == "__main__":

    bot.run(token, reconnect=True)

Seriously. That’s it. Everything else gets called by the bot at the appropriate times.

Conclusion

And that’s a wrap! It may seem like a lot at first glance, but it’s really not all that bad. The code itself is fairly lean, I just wanted to thoroughly cover everything involved.

Several aspects of this bot could become more sophisticated with a little elbow grease. For example, you could have the bot dynamically update what pages it scrapes as well as how often.

Buying a PS5 at retail price is as big of a challenge as the games you can play on it. No matter how frequently you have your bot run its checks, you’ll have an advantage over anyone not following a tracker. Using KocerRoxy’s competitively priced rotating residential IP proxy service will let your Discord bot for availability alerts scrape more often, further increasing that advantage.

By Geminel

Geminel is a multi-format author, but is even moreso a giant nerd. With how many times they’ve fallen into several-hour-long research sprees just to accurately present a one-line joke, they realized they should probably use this power for good. To see their creative work, visit their personal site at: Team Gem

Leave a comment

Your email address will not be published.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.