Twitter reply bot that generates a color grid

Another Tweepy tutorial, but we’re not complaining.

A little background: I love making random fractals art (nft any) I can spend hours just staring at one and seeing all the different things. So what better way to share my creations? A Twitter bot of course, but it also lets it do something else.

You can try it yourself by tweeting ‘make grid’ on Twitter at @machineation. The bot will hash your username and then use it as a seed to create a unique color grid based on the username. Let’s start.

Requirements:

  • python >= 3.10
  • Twitter developer account (not included in the tutorial)
  • tweepy
  • Numpy
  • Pillow

If you are not familiar with Twitter developer account, please look at it Because this tutorial does not cover developer account, creating app and getting authentication.

Let’s start by installing the required packages..feel free to use virtual environment or pip3 if you have both versions of python.

pip install tweepy numpy pillow
enter fullscreen mode

exit fullscreen mode

After everything is installed, let’s create a file called “bot.py” but you can name it anything you like, and then lets import what we need.

import tweepy, random, time, os, pathlib, numpy, hashlib
from PIL import Image
enter fullscreen mode

exit fullscreen mode

After that, we need to add authentication and create API object using Tweepy. You can read about Tweepy authentication in the docs but essentially, you need API Key, API Key Secret, Access Token and Access Token Secret

Your keys are secret. Do not include them in scripts and do not upload them to any publicly visible cloud. Use .env or some other method to keep secrets. For simplicity, the tutorial does not include

auth = tweepy.OAuthHandler("xxxx", "xxxx")
auth.set_access_token("xxxx", "xxxx")

#create API object
api = tweepy.API(auth, wait_on_rate_limit=True)
enter fullscreen mode

exit fullscreen mode

  • wait_on_rate_limit – Whether to automatically wait for the rate limit to be refilled

In order not to reply to the same tweet multiple times and only get mentions that the bot hasn’t seen yet, we will make sure to save the last mention tweet id. We’ll save this in a file so that even if we stop and restart the bot, we’ll start with the mentions we haven’t seen yet, not from the beginning.
Let’s create 2 functions. One to read the ID from the file and another to store the ID.

def read_last_seen(FILE_NAME):
  file_read = open(FILE_NAME,'r')
  last_seen_id = int(file_read.read().strip())
  file_read.close()
  return last_seen_id

def store_last_seen(FILE_NAME, last_seen_id):
  file_write = open(FILE_NAME,'w')
  file_write.write(str(last_seen_id))
  file_write.write("\n")
  file_write.close()
  return
enter fullscreen mode

exit fullscreen mode

Note that the first function to read the ID takes one parameter which is the filename, and the function to store the ID takes 2 parameters, the filename and the ID. In the future, if you want to have more functions that store and read different things, you can call the same functions and change the parameters and for example we will replace “last_seen_id” with “data” .

Now, let’s create a function to create a colors grid from a username. I opted to code it as a separate function.

def make_grid(handle):
    hash = int(hashlib.sha256(handle.encode('utf-8')).hexdigest(), 16) % 10**8
    numpy.random.seed(hash)
    # Generate 100x100 grid of random colours
    grid = numpy.random.randint(0,256, (100,100,3), dtype=numpy.uint8)
    im = Image.fromarray(grid).resize((1600,1600), resample=Image.Resampling.NEAREST)

    im.save(os.path.join(os.path.dirname(__file__), "grids/") + handle + '.png')
enter fullscreen mode

exit fullscreen mode

  • “handle”: The parameter that will be the username we will pass to the function.
  • “hash”: We will hash it using the username sha256 to use as a random seed for numpy.
  • “grid”: Using numpy, we create a grid with random colors (seeded with username hash). You can make it in any shape you want.
  • “IM”: Using pillows to create a 1600×1600 image from the data. You can use any size you want.

Then we save the PNG image in a folder in the same directory called “grid” with the username as the file name.

Next step, let’s create a function that:

  1. Get mentions since last archived tweet id.
  2. Loop on mentions.
  3. Store Tweet ID.
  4. Check if the tweet is not by our bot.
  5. Check if the tweet contains specific words.
  6. Check if the tweet doesn’t have extra words and if so reply with a message.
  7. Reply to tweets based on the matching word.

This means that you can extend the bot to do other things based on the tweet content (commands). But first, we need to set some variables.

def grid_tweet():
    bot_id = xxxxxxxxxxxx
    FILE_NAME = os.path.join(os.path.dirname(__file__), "grid_last_seen.txt")
    words = ["make grid"]
    message = "Here is your unique color grid, @{}"
    grid_unknown = "@{} If you want a grid, You just need to tweet 'make grid' to Me without any other words and I will create and send You one."
enter fullscreen mode

exit fullscreen mode

Let’s go over them:

bot_id

We set this variable so that we can check that the tweet author is not our bot. This way we make sure that if our bot tweets the same words we’re looking for, the bot itself doesn’t reply. You can get bot id in different ways, but here is a simple one

via browser
You can go to the bot page on Twitter, right click anywhere inside the page and click “Inspect”, then search for “Identifiers” in Elements which will look something like this

"identifier": "1572580999673831426"

file name

This is the file we will use to read and store the last mentioned tweet id. This will be used to call the functions we have already created and pass variables to them. Then we can keep separate files for different functions if we want.

words

List of words we are looking for. You can add more commas separated. For our example we are just using “Make Grid”.

message

The message we are going to include in our answer. We are using {} at the end to format it later and add the username to it.

grid_unknown

Another message if the tweet contains additional words other than “Make Grid”. Using {} to format with username afterwards.

Now let’s make the rest of the code.

    while True:
        # Finding tweets that mention our bot
        mentions = api.mentions_timeline(since_id=read_last_seen(FILE_NAME)) 
        # Iterating through each mention tweet
        for mention in mentions:
            # Save the tweet id in the file
            store_last_seen(FILE_NAME, mention.id)
            # Check to see if the tweet author is not our bot
            if mention.author.id != bot_id:
                # Check if the tweet has any of the words we defined in our list after converting the tweet to lower case
                if True in [word in mention.text.lower() for word in words]:
                    # Removes the first part of the mention which is our bot username like so @username
                    command = mention.text.lower().split(' ',1)[1]
                    # Act based on the matched word/command. can later be used to add more functionality.
                    match command:
                        case "make grid":
                            try:
                                # Prints the username and tweet
                                print(f"{mention.author.screen_name} - {mention.text}")
                                print('Creating Grid')
                                # Calls our make_grid function and passing the username.
                                make_grid(mention.author.screen_name)
                                # Set the media to the image created by our function
                                media = os.path.join(os.path.dirname(__file__), "grids/") + mention.author.screen_name + '.png'
                                print("Attempting to reply...")
                                # Reply with the message after formatting to include username and the media
                                api.update_status_with_media(message.format(mention.author.screen_name), media, in_reply_to_status_id=mention.id_str)
                                print("Successfully replied :)")
                            # Error handling. for now it just prints the error.
                            except Exception as exc:
                                print(exc)
                        # If the tweet contains extra words in addition to our command
                        case other:
                            try:
                                print('tweet contains other words')
                                # Reply with the grid_unknown message
                                api.update_status(grid_unknown.format(mention.author.screen_name), in_reply_to_status_id=mention.id_str)
                                print("Successfully replied with explaination :)")
                            except Exception as exc:
                                print(exc)
        # Sleep for 2 minutes
        time.sleep(120)
enter fullscreen mode

exit fullscreen mode

So in the end, our file will look like this:

import tweepy, random, time, os, pathlib, numpy, hashlib
from PIL import Image

auth = tweepy.OAuthHandler("xxxx", "xxxx")
auth.set_access_token("xxxx", "xxxx")

#create API object
api = tweepy.API(auth, wait_on_rate_limit=True)

def read_last_seen(FILE_NAME):
  file_read = open(FILE_NAME,'r')
  last_seen_id = int(file_read.read().strip())
  file_read.close()
  return last_seen_id

def store_last_seen(FILE_NAME, last_seen_id):
  file_write = open(FILE_NAME,'w')
  file_write.write(str(last_seen_id))
  file_write.write("\n")
  file_write.close()
  return    

def make_grid(handle):
    hash = int(hashlib.sha256(handle.encode('utf-8')).hexdigest(), 16) % 10**8
    numpy.random.seed(hash)
    # Generate 100x100 grid of random colours
    grid = numpy.random.randint(0,256, (100,100,3), dtype=numpy.uint8)
    im = Image.fromarray(grid).resize((1600,1600), resample=Image.Resampling.NEAREST)

    im.save(os.path.join(os.path.dirname(__file__), "grids/") + handle + '.png')
def grid_tweet():
    bot_id = xxxxxxxxxxxx
    FILE_NAME = os.path.join(os.path.dirname(__file__), "grid_last_seen.txt")
    words = ["make grid"]
    message = "Here is your unique color grid, @{}"
    grid_unknown = "@{} If you want a grid, You just need to tweet 'make grid' to Me without any other words and I will create and send You one."
    while True:
        # Finding tweets that mention our bot
        mentions = api.mentions_timeline(since_id=read_last_seen(FILE_NAME)) 
        # Iterating through each mention tweet
        for mention in mentions:
            # Save the tweet id in the file
            store_last_seen(FILE_NAME, mention.id)
            # Check to see if the tweet author is not our bot
            if mention.author.id != bot_id:
                # Check if the tweet has any of the words we defined in our list after converting the tweet to lower case
                if True in [word in mention.text.lower() for word in words]:
                    # Removes the first part of the mention which is our bot username like so @username
                    command = mention.text.lower().split(' ',1)[1]
                    # Act based on the matched word/command. can later be used to add more functionality.
                    match command:
                        case "make grid":
                            try:
                                # Prints the username and tweet
                                print(f"{mention.author.screen_name} - {mention.text}")
                                print('Creating Grid')
                                # Calls our make_grid function and passing the username.
                                make_grid(mention.author.screen_name)
                                # Set the media to the image created by our function
                                media = os.path.join(os.path.dirname(__file__), "grids/") + mention.author.screen_name + '.png'
                                print("Attempting to reply...")
                                # Reply with the message after formatting to include username and the media
                                api.update_status_with_media(message.format(mention.author.screen_name), media, in_reply_to_status_id=mention.id_str)
                                print("Successfully replied :)")
                            # Error handling. for now it just prints the error.
                            except Exception as exc:
                                print(exc)
                        # If the tweet contains extra words in addition to our command
                        case other:
                            try:
                                print('tweet contains other words')
                                # Reply with the grid_unknown message
                                api.update_status(grid_unknown.format(mention.author.screen_name), in_reply_to_status_id=mention.id_str)
                                print("Successfully replied with explaination :)")
                            except Exception as exc:
                                print(exc)
        # Sleep for 2 minutes
        time.sleep(120)

if __name__ == "__main__":
    print('The mighty Machineation is starting...')
    grid_tweet()
enter fullscreen mode

exit fullscreen mode

And that’s it.. Now all you have to do is start with:
pyhton bot.py
And tweet your bot saying “make grid” from a different account and it should respond with a color grid in a few minutes.

Leave a Comment