Tutorial: Creating a Twitter (X) Bot using Python

Published at Oct 6, 2024

6 min read
#python
#twitter
#api
#bot
#tutorial

During my last semester at university, I found myself overwhelmed by the workload and the feeling that time was standing still. To cope, I decided to create a simple X bot that would remind me of the semester’s progress by tweeting the percentage of time that had passed. I’ll walk you through building your own X bot using Python and the X API, complete with scheduling and image generation.

This tutorial is up-to-date as of October 2024 February 2025 May 2025 using the new X API. If you are reading this in the future, please check the official documentation for any changes.

Here is an example tweet from the bot:

I know most of my audience doesn’t speak Turkish, but the tweet simply says that the semester has progressed by 100%.

You can reach the source code of the project from my github repository.

Structure

Features

  • Post a tweet autonomously.
  • Schedule the bot to tweet at a specific time, you can also set the interval.
  • Create a beautiful progress graph that shows the progress of the semester.
  • Completely free to deploy and use.

Getting Started

First, you need to create an X account for your bot. Then, you need to sign-in to the X Developer Portal and create a new project. After creating a project, you need to create an app and get the API keys.

X Developer Portal
X Developer Portal

Now, copy all of these keys and paste them into a .env file in the root directory of your project. Here is an example of the .env file:

API_KEY=YOUR_API_KEY
API_KEY_SECRET=YOUR_API_KEY_SECRET
BEARER_TOKEN=YOUR_BEARER
ACCESS_TOKEN=YOUR_ACCESS_TOKEN
ACCESS_TOKEN_SECRET=YOUR_ACCESS_TOKEN_SECRET

Hint: Notice that we ignore the .env file in the .gitignore file so that we don’t share our keys with the public.

Requirements

First, you can find the required libraries in the requirements.txt file. You can install them by running:

pip install -r requirements.txt

I will explain why we need each required library:

  • tweepy: Tweepy is an easy-to-use Python library for accessing the X API.
  • python-dotenv: Reads the key-value pair from .env file and adds them to the environment variable.
  • matplotlib: We use these libraries to create the graph that shows the progress of the semester.

Writing the Bot

  • We set up basic logging to see the logs of the bot:
# Setup basic logging
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
  • Then, I have a function that calculates how many seconds have passed since the beginning of the semester and how many seconds are left until the end of the semester. Then I calculate the percentage of the semester that has passed. Pretty simple, right?
def calculate_percentage(start_date, end_date):
    now = datetime.now()
    if now < start_date:
        return 0
    elif now.date() == (end_date - timedelta(days=1)).date():
        return 100

    total_seconds = (end_date - start_date).total_seconds()
    elapsed_seconds = (now - start_date).total_seconds()
    percentage = round((elapsed_seconds / total_seconds) * 100, 2)
    return int(percentage) if percentage.is_integer() else percentage
  • The progress graph is created using matplotlib library and saved to a PNG file. (Check the source code for details.)

  • I have two functions that connect to the X API and post the progress image on X:

def connect_twitter() -> tuple:
    """
    Connect to the Twitter API using Tweepy and environment variables.

    Returns:
        tuple: A tuple containing the Twitter client and API instances.
    """
    load_dotenv()
    tweepy_auth = tweepy.OAuth1UserHandler(
        os.getenv("API_KEY"),
        os.getenv("API_KEY_SECRET"),
        os.getenv("ACCESS_TOKEN"),
        os.getenv("ACCESS_TOKEN_SECRET"),
    )
    api = tweepy.API(tweepy_auth)
    client = tweepy.Client(
        os.getenv("BEARER_TOKEN"),
        os.getenv("API_KEY"),
        os.getenv("API_KEY_SECRET"),
        os.getenv("ACCESS_TOKEN"),
        os.getenv("ACCESS_TOKEN_SECRET"),
    )
    logging.info("Connected to Twitter API")
    return client, api

def post_photo():
    """
    Generate and post a progress image on Twitter with the remaining days and progress.
    """
    client, api = connect_twitter()
    remaining_days = (END_DATE - datetime.now()).days
    percentage = calculate_percentage(START_DATE, END_DATE)
    img_path = create_progress_image(percentage)

    text = f"🔴 ODTÜ'de 2024-2025 bahar dönemi ilerlemesi: %{percentage}"
    media = api.media_upload(filename=img_path)
    client.create_tweet(text=text, media_ids=[media.media_id])
    logging.info("Successfully posted progress image on Twitter")
  • Finally, I have a main section that handles how the bot runs, and it includes a simple CLI interface.
if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Post semester progress on Twitter or view percentage."
    )
    parser.add_argument(
        "--percentage-only",
        action="store_true",
        help="Only show the current percentage and exit.",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Run everything except actually posting to Twitter.",
    )
    args = parser.parse_args()

    percentage = calculate_percentage(START_DATE, END_DATE)

    if args.percentage_only:
        print(f"Current progress: %{percentage}")
    else:
        client, api = connect_twitter()
        logging.info(f"Dry run: {args.dry_run}")
        remaining_days = (END_DATE - datetime.now()).days
        img_path = create_progress_image(percentage)
        text = f"🔴 ODTÜ'de 2024-2025 bahar dönemi ilerlemesi: %{percentage}"

        if args.dry_run:
            logging.info(
                "Dry run mode: Image would be posted with the following content:"
            )
            logging.info(f"Text: {text}")
            logging.info(f"Image path: {img_path}")
        else:
            media = api.media_upload(filename=img_path)
            client.create_tweet(text=text, media_ids=[media.media_id])
            logging.info("Successfully posted progress image on Twitter")

This block allows you to use the script from the command line with two optional arguments:

  • --percentage-only: Prints the current progress percentage and exits. Useful for debugging or using the percentage elsewhere.
  python main.py --percentage-only
  • --dry-run: Runs the full process (calculating, generating image, preparing the tweet) but does not post anything to X. It logs what it would post instead.
  python main.py --dry-run
  • If you run the script without any arguments, it will calculate the percentage, create the image, and tweet it directly:
python main.py

Deployment

I deployed the function using GitHub Actions. The bot runs daily and posts the progress image on X. You will first need to secrets we use to the Github:

  • Go to your Github Repository.
  • Go to settings.
  • Go to secrets and variables, select actions.
  • Click “New Repository Secret” for each of the environment variables.

Then add the “daily_twitter_post” yaml file to ./.github/workflows Here is the daily_twitter_post.yml file:

name: Daily Twitter Post
on:
  schedule:
    - cron: "0 15 * * *" # Runs daily at 18:00 in Turkiye time
  workflow_dispatch: # Allows manual triggering

jobs:
  post_tweet:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.10"

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run script
        env:
          API_KEY: ${{ secrets.API_KEY }}
          API_KEY_SECRET: ${{ secrets.API_KEY_SECRET }}
          ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
          ACCESS_TOKEN_SECRET: ${{ secrets.ACCESS_TOKEN_SECRET }}
          BEARER_TOKEN: ${{ secrets.BEARER_TOKEN }}
        run: python main.py

Once the workflow is set up:

  • Automatic schedule: The bot tweets every day at the time you specify in the cron line.
    • Want a different schedule? Change the cron expression to run hourly, weekly, monthly—whatever you need.
  • Manual trigger: Open your repository’s Actions tab and click Run workflow to post on demand.

Conclusion

This project is just the beginning. You can extend the bot to post about other events or even integrate it with other APIs. I encourage you to explore different ways to make this bot more interactive. Have fun experimenting, and feel free to reach out with any questions or feedback!

← Back to Blog