DEV Community

Cover image for Twitter Replying Bot that generates color grids
Mohamed Adel
Mohamed Adel

Posted on

Twitter Replying Bot that generates color grids

Yet another tweepy tutorial, but we are not complaining.

a little background: i like to create random fractals art (NFT anyone) I can just can spend hours staring at one and seeing all different things. so what's a better way to share my creations? a twitter bot of course, but lets also make it do something else.

Preview

you can try it your self by tweeting 'make grid' to @machineation on twitter. the bot will hash your username then use it as a seed to generate a unique color grid based on the username. lets start.

requirements:

  • python >= 3.10
  • twitter developer account (not covered in the tutorial)
  • tweepy
  • numpy
  • pillow

if your not familiar with twitter developer account, please Check this because creating the developer account, app, and getting the authentication are not covered in this tutorial.

lets start by installing the required packages..feel free to use virtual environment or pip3 if you have both version of python.

pip install tweepy numpy pillow
Enter fullscreen mode Exit fullscreen mode

after everything is installed, lets create a file called "bot.py" but you can name yours anything you like, and then lets import what we will 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 the authentication and create the API object using tweepy. you can read about the tweepy authentication in the docs but essientially, you need the API Key, API Key Secret, Access Token and Access Token Secret

your keys are secret. don't include them in the script and upload them to to any publicly visible cloud. use .env or some other method to keep secrets. for simplicity, the tutorial doesn't cover that

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 or not to automatically wait for rate limits to replenish

In order not to reply to the same tweet multiple times and also to get only the mentions that the bot haven't seen yet, we will make sure to save the last mention tweet id. We will save it to a file so that even if we stopped the bot and then resumed, we will start from the mentions we haven't seen yet, not from the beginning.
Let's create 2 functions. One for reading the id from file and one 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

Notice that the first function for reading the id takes one parameter which is the file name, and the function to store the id takes 2 parameters, the file name 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 changing the parameters and we would change "last_seen_id" with "data" for example.

Now, Lets create the function to create the colors grid from the 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 that we will pass to the function.
  • "hash": we will hash the username using sha256 to use it as a random seed for numpy.
  • "grid": using numpy, we create a grid with random colors (seeded with the username hash). you can make it any size you want.
  • "im": using pillow to create the 1600x1600 image from the data. you can use any size you want.

we then save the png image in a folder in the same directory called "grids" with the username as the file name.

Next step, let's create the function that will:

  1. Fetch the mentions since last stored tweet id.
  2. Loop over the mentions.
  3. Store the tweet id.
  4. Check if the tweet is not by our bot.
  5. Check if tweet contains specific words.
  6. Check if the tweet doesn't contain extra words and reply with a message if so.
  7. Reply to tweet depending on the matched word.

this means you can expand the bot to do other things based on the tweet content (command). 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 if the tweet author is not our bot. this way we make sure that if our bot tweeted the same words we are looking for, the bot doesn't reply to itself. you can get the bot id through different ways, but here is a simple one

Through the Browser
you can go to the bot page on twitter, right click anywhere inside the page and click "Inspect" then search for "identifier" in the elements which will look something like this

"identifier": "1572580999673831426"

FILE_NAME

this is the file we will use to read and store the last mention tweet id. This will be used to call the functions we already created and pass the variable to them. We can then have different files for different functions if we want.

words

The list of words we are looking for. you can add more separated with a comma. For our example we are only using "make grid".

message

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

grid_unknown

Another message if the tweet has extra words in addition to "make grid". also using {} to be formatted later with the username.

now let's create 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 at 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 it with:
pyhton bot.py
and tweet from a different account to your bot saying "make grid" and it should reply in a couple of minutes with the color grid.

Top comments (0)