Using Webhooks with Python: Part 2

In this post I’m going to go over the send message to Discord webhook function from last time and add in some error handling for when we hit a rate limit. For reference, here is the code we wrote by the end of the last tutorial. This code defines the function send that will send message to the designated webhook.

import http.client

def send(message, webhook):

    conn = http.client.HTTPSConnection("discordapp.com")

    payload = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\n" + message + "\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--"

    headers = {
        'content-type': "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
        'cache-control': "no-cache",
        }

    conn.request("POST", webhook, payload, headers)

    res = conn.getresponse()
    data = res.read()

    print(data.decode("utf-8"))

Discord contains limits to how often a webhook can be used, these limits are updated dynamically but in general if you try to send more than 5 messages within 1-2 seconds you will probably hit a rate limit. Let’s try hitting a rate limit and see what happens. Put the following code into your client of choice after defining the send function above:

for i in range(0,7):
   send("further reading", YOURWEBHOOK)

You will eventually end up with one or two responses like below, but note that the ordering may be different:

{
  "global": false, 
  "message": "You are being rate limited.", 
  "retry_after": 689
}

That is the message that Discord gives you when rate limited. It will start happening after about the fifth message sent in too quick a succession. You will also notice that any rate limited message did not get posted to the channel. Each rate limit will include the phrase “You are being rate limited.” and will give a number is milliseconds to wait before trying again.

To properly handle this issue, we will write an if statement that looks for a response from discord about the rate limit and retries after waiting an appropriate amount of time. You will find an example below with some commenting explaining each step:

import http.client
#import the time module for later
import time

def send(message, webhook):

    conn = http.client.HTTPSConnection("discordapp.com")

    payload = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\n" + message + "\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--"

    headers = {
        'content-type': "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
        'cache-control': "no-cache",
        }

    conn.request("POST", webhook, payload, headers)

    res = conn.getresponse()
    data = res.read()
    response = data.decode("utf-8")

    #checks if there is a response
    if response:
        #check if it's a rate limit 
        if "You are being rate limited" in response:
            #find the wait time, it will be on the same line as "retry after"
            index1 = response.find('"retry_after"')
            wait = ''
            #we are going to go through the string starting after "retry_after" and look for digits
            for character in response[index1 + 15:]:
                #we do '+ 15' to skip the start of the line and just grab the digits after
                if character.isdigit():
                    wait += character
                elif character == '\n':
                    #ordering may be different, so we will set it to stop when the new line character is hit
                    break

            #turn wait into a number
            wait = int(wait)

            #wait is milliseconds, so must convert to seconds
            #we will also add 0.1 seconds to it just to ensure we are over the wait time
            wait = wait/1000 + 0.1

            #now we will wait that amount of time and try sending the message again
            print("waiting " + str(wait) + " seconds")
            time.sleep(wait)
            send(message, webhook)

        else:
            #print the response if its something else
            print(data.decode("utf-8"))

So in the above code, what is happening is it checks for a response from discord. If there is a response, it checks if it is reporting a rate limit. If it is a rate limit, then it looks for the “retry after” line in the string and grabs the digits that appears after it. It converts the digits from a string to an int to get milliseconds and then divides by 1000 to turn it into seconds. It will add 0.1 to ensure that we are waiting long enough, then it’ll wait before trying to send the message again.

Try running the loop again and this time you will get at least one console message informing you of a wait and all 7 messages will eventually be sent to the channel.

Being able to handle a rate limit is important, but what would be better is to avoid the rate limit entirely. While this may not be possible in some implementations, in the case of a loop like we’ve been testing with there is a way to check how close you are to the rate limit. In the next tutorial we will go over this scenario.

This entry was posted in Python, tutorial and tagged , , , . Bookmark the permalink.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.