DEV Community

Cover image for How I Made GitHub Issues Hilarious! Build Your Own Github Bot.
Ali Shirani
Ali Shirani

Posted on

How I Made GitHub Issues Hilarious! Build Your Own Github Bot.

Okay, this is going to be FUN! Building a bot that slings GIFs into GitHub issues? Sign me up! Let's transform this awesome tutorial into a blog post that'll have developers everywhere scrambling to build their own GIF-slinging sidekick.

Here we go!


Level Up Your GitHub Game: Build an Automated GIF Bot (From Scratch!) โœจ๐Ÿค–

Ever been in a GitHub issue, deep in a technical discussion, and thought, "You know what this conversation really needs? A perfectly timed GIF!" Of course, you have. We all have. Manually searching and pasting GIFs is so 2023. What if you could summon the perfect GIF with a simple command, right there in the issue?

Well, buckle up, buttercup, because that's exactly what we're building today! We're diving headfirst into creating a fully functional GitHub GIF Bot from scratch, leveraging the latest and greatest official GitHub libraries. By the end of this guide, you'll have your very own bot, ready to inject a healthy dose of fun and expression into any repository.

Ready to become a GitHub legend? Let's get into it!


Step 1: Birth Your Bot - Registering as a GitHub App ๐Ÿ‘ถ

First things first, our bot needs an identity on GitHub. Think of it like getting its official GitHub passport.

  1. Navigate to GitHub Apps: Head to your GitHub Settings > Developer settings > GitHub Apps.
  2. New GitHub App: Click that shiny "New GitHub App" button.
  3. Name Your Creation: Give your bot a cool, memorable name. I'm calling mine "GiphyBot Deluxe," but you do you!

  4. Homepage URL: This can be anything for now โ€“ even your GitHub profile URL works.

  5. The All-Important Webhook URL (with a Smee.io Detour!):

    • This is CRITICAL. For local development, we need a public URL that tunnels requests to our local machine.
    • Open a new tab and go to smee.io.
    • Click "Start a new channel."
    • Copy the "Webhook Proxy URL" it gives you. It'll look something like https://smee.io/YourUniqueChannel.
*   Paste this Smee.io URL back into the "Webhook URL" field on GitHub.
Enter fullscreen mode Exit fullscreen mode
  1. Webhook Secret: This is like a password for your webhooks, ensuring they're legit.
    • Generate a strong, random string (use a password manager or online generator).
    • SAVE THIS SECRET! Put it in a secure note-taking app or password manager. We'll need it soon.
  2. Permissions - The Need-to-Know Basis: For security, our bot only needs two things:

    • Scroll to "Repository permissions."
    • Find Issues and set it to Read & write.
    • Just below that, find Pull requests and also set it to Read & write.
  3. Subscribe to Events: We need to tell GitHub what our bot cares about.

    • Under "Subscribe to events," check the box for Issue comment.
  4. Create GitHub App: Leave everything else as default and hit that glorious "Create GitHub App" button.

Phew! On the next page, take note of the App ID (you'll need it). Scroll down and click "Generate a private key." This will download a .pem file.

๐Ÿšจ SECURITY ALERT! ๐Ÿšจ
Treat this .pem file like the master key to your kingdom. NEVER, EVER commit it to your repository. Keep it secret, keep it safe!


Step 2: Unleash the GIFs - Getting Your Giphy API Key ๐Ÿ”‘

Our bot needs to talk to the GIF gods at Giphy.

  1. Head over to developers.giphy.com.
  2. Create an account or log in.
  3. Click on "Create an App."
  4. Choose the "API" option (not SDK).
  5. Give your Giphy app a name and description.
  6. BAM! You'll get an API Key. Copy it.

  7. Add this Giphy API Key to your collection of secrets (with your App ID, Webhook Secret, and the path to your .pem file).


Step 3: Setting Up Shop - Project & Dependencies ๐Ÿ’ป

Alright, with our keys and secrets gathered, it's time to write some code!

  1. Create Your Project: Open your favorite code editor and terminal. Make a new project directory:

    mkdir github-gif-bot
    cd github-gif-bot
    npm init -y
    
  2. Install the Magic (Packages): We're using ESM syntax, so we need a few specific packages.

    npm install express octokit @octokit/webhooks dotenv axios
    
*   `express`: For our web server.
*   `octokit`: The official GitHub client library.
*   `@octokit/webhooks`: To handle incoming GitHub webhooks.
*   `dotenv`: To manage our secrets from an environment file.
*   `axios`: To make HTTP requests to the Giphy API.
Enter fullscreen mode Exit fullscreen mode
  1. Create Your Files:
    • index.js: This will be our main bot code.
    • .env: This is where our precious secrets will live, safely outside our code.
    • .gitignore: Add .env and node_modules/ to this file!
  2. Populate .env: Open your .env file and add your secrets like this:

    APP_ID=your_app_id_here
    WEBHOOK_SECRET=your_webhook_secret_here
    PRIVATE_KEY_PATH=./your_downloaded_private_key.pem  # Or the full path
    GIPHY_API_KEY=your_giphy_api_key_here
    PORT=3000 # Or any port you prefer
    

    (Make sure your .pem file is in the location you specify, or update the path).


Step 4: The Brains of the Operation - Code Breakdown ๐Ÿง 

Open up index.js. Let's walk through the code that brings our bot to life.

// index.js
import express from 'express';
import { App } from 'octokit';
import { createNodeMiddleware } from '@octokit/webhooks';
import dotenv from 'dotenv';
import fs from 'fs'; // To read the private key
import axios from 'axios'; // For Giphy API calls

// Load environment variables
dotenv.config();

const app = express();
const port = process.env.PORT || 3000;

// Read the private key
// Ensure your .env file has PRIVATE_KEY_PATH pointing to your .pem file
const privateKey = fs.readFileSync(process.env.PRIVATE_KEY_PATH, 'utf8');

// Initialize our GitHub App instance
const githubApp = new App({
  appId: process.env.APP_ID,
  privateKey: privateKey,
  webhooks: {
    secret: process.env.WEBHOOK_SECRET,
  },
});

// The heart of our bot: Listening for issue comments
githubApp.webhooks.on('issue_comment.created', async ({ octokit, payload }) => {
  console.log('Received an issue_comment.created event');

  const commentBody = payload.comment.body;
  const commenter = payload.comment.user.login;
  const botUsername = (await octokit.rest.apps.getAuthenticated()).data.slug + '[bot]'; // Gets the bot's username like "app-name[bot]"

  // 1. Check if the comment starts with /gif
  // 2. Check if the comment is NOT from the bot itself (to avoid infinite loops!)
  if (commentBody.startsWith('/gif') && commenter !== botUsername) {
    console.log(`GIF command detected from ${commenter}: ${commentBody}`);
    const searchQuery = commentBody.substring(5).trim(); // Get text after "/gif "

    if (searchQuery) {
      try {
        // Fetch GIF from Giphy
        const giphyResponse = await axios.get('https://api.giphy.com/v1/gifs/search', {
          params: {
            api_key: process.env.GIPHY_API_KEY,
            q: searchQuery,
            limit: 1, // Get the top GIF
            offset: Math.floor(Math.random() * 25), // Get a bit of randomness
            rating: 'g', // Keep it SFW
            lang: 'en',
          },
        });

        if (giphyResponse.data.data && giphyResponse.data.data.length > 0) {
          const gifUrl = giphyResponse.data.data[0].images.original.url;
          const replyComment = `Here's a GIF for "${searchQuery}":\n\n![${searchQuery}](${gifUrl})`;

          console.log(`Replying with: ${replyComment}`);

          // Post the GIF as a new comment
          await octokit.rest.issues.createComment({
            owner: payload.repository.owner.login,
            repo: payload.repository.name,
            issue_number: payload.issue.number,
            body: replyComment,
          });
          console.log('GIF comment posted successfully!');
        } else {
          console.log(`No GIF found for "${searchQuery}"`);
          await octokit.rest.issues.createComment({
            owner: payload.repository.owner.login,
            repo: payload.repository.name,
            issue_number: payload.issue.number,
            body: `Sorry, I couldn't find a GIF for "${searchQuery}". ๐Ÿ˜•`,
          });
        }
      } catch (error) {
        console.error('Error fetching from Giphy or posting to GitHub:', error);
         await octokit.rest.issues.createComment({
            owner: payload.repository.owner.login,
            repo: payload.repository.name,
            issue_number: payload.issue.number,
            body: `Oops, something went wrong while I was fetching your GIF. Please try again!`,
          });
      }
    } else {
      console.log('Empty search query after /gif');
       await octokit.rest.issues.createComment({
            owner: payload.repository.owner.login,
            repo: payload.repository.name,
            issue_number: payload.issue.number,
            body: `You need to tell me what GIF to search for! Usage: \`/gif <search term>\``,
          });
    }
  }
});

// Standard Express server setup
// Use the Octokit webhook middleware
app.use(createNodeMiddleware(githubApp.webhooks, { path: '/api/github/webhooks' }));

app.listen(port, () => {
  console.log(`Server listening for webhooks at http://localhost:${port}/api/github/webhooks`);
});
Enter fullscreen mode Exit fullscreen mode

Key Parts Explained:

  • Imports & Setup: We grab express, App from octokit, createNodeMiddleware, dotenv, fs (for reading the private key file), and axios. We initialize Express and load our .env variables.
  • GitHub App Initialization: We create an instance of our App, passing in the appId, privateKey (read from the file), and webhookSecret.
  • githubApp.webhooks.on('issue_comment.created', ...): This is the core listener.
    • It triggers whenever a new issue comment is created on a repository where our app is installed.
    • It gives us:
      • octokit: A pre-authenticated Octokit client instance. This is how our bot performs actions (like posting comments) AS ITSELF. Magic!
      • payload: All the data about the event (who commented, what they said, where it happened, etc.).
  • Logic Inside the Listener:
    1. We grab the commentBody and the commenter.
    2. We also fetch the bot's own username to prevent it from replying to its own GIF posts (crucial for avoiding infinite loops of GIF-ception!).
    3. We check if the comment startsWith('/gif') AND if the commenter isn't our bot.
    4. We extract the searchQuery (the text after /gif).
    5. We use axios to hit the Giphy API with our search query.
    6. We construct a replyComment using Markdown to embed the GIF image.
    7. Finally, we use octokit.rest.issues.createComment({...}) to post our GIF-laden reply back to the GitHub issue.
  • Express Server: The rest is standard Express setup, telling our app to listen for incoming webhook requests on /api/github/webhooks (or whatever path you configured in Smee/GitHub).

Step 5: Showtime! Running & Testing Your Bot ๐Ÿš€

The moment of truth! Let's bring our GIF-tastic creation to life.

  1. Start the Smee.io Tunnel (Terminal 1):

    • Remember that Smee.io URL? We need to tell the Smee client to forward requests from that public URL to our local server.
    • First, if you haven't already, install the Smee client globally:

      npm install --global smee-client
      
*   Then run the client, replacing `YOUR_SMEE_CHANNEL_URL` with the URL Smee.io gave you, and `/api/github/webhooks` if you used a different path:
Enter fullscreen mode Exit fullscreen mode
    ```bash
    smee --url YOUR_SMEE_CHANNEL_URL --path /api/github/webhooks --port 3000
    ```
Enter fullscreen mode Exit fullscreen mode
    (Ensure the `--port` matches the port your Node.js app is listening on, and `--path` matches the webhook path in your `index.js` and GitHub App settings).
    You should see it connect and say "Forwarding `YOUR_SMEE_CHANNEL_URL`/api/github/webhooks to http://localhost:3000/api/github/webhooks". Perfect!
Enter fullscreen mode Exit fullscreen mode
  1. Start Your Bot Server (Terminal 2):

    • In a new terminal window, navigate to your project directory and run:

      node index.js
      
*   You should see "Server listening for webhooks at http://localhost:3000/api/github/webhooks".
Enter fullscreen mode Exit fullscreen mode
  1. Install Your App on a Repository:

    • Go back to your GitHub App's settings page on GitHub.
    • Click "Install App" in the left sidebar.
    • Choose a test repository (or create a new one) to install your bot on. Grant access.
  2. Test Drive!

    • Go to the repository where you just installed the app.
    • Create a new issue, or open an existing one.
    • In a comment, type: /gif happy cat
    • Hit "Comment."

    If all goes well...

    • You'll see activity in your Smee client terminal (a webhook being received).
    • You'll see logs in your node index.js terminal (event received, talking to Giphy, posting back).
    • And then... BOOM! Your bot should post a comment with a happy cat GIF!

๐ŸŽ‰ You Did It! Your GitHub Is Now More Awesome! ๐ŸŽ‰

How cool is that? You've just built a fully automated GitHub bot that brings joy, laughter, and a whole lotta GIFs to the often-serious world of software development.

This is just the beginning. You can expand this bot to do all sorts of cool things:

  • Add more commands.
  • Integrate with other APIs.
  • Help automate repository tasks.

Thanks for building along, and happy coding! See you in the next one!

Top comments (7)

Collapse
 
nevodavid profile image
Nevo David

this is actually hilarious and super useful tbh, makes me wanna code one just for fun - you think adding more playful stuff like this ever makes team work better or does it get distracting after a while?

Collapse
 
alishirani profile image
Ali Shirani

I think these kinds of projects is what makes developers to enjoy coding and this will probably effect their intention for work.

Collapse
 
vidakhoshpey22 profile image
Vida Khoshpey

I will try it for sure๐Ÿ˜๐Ÿ’ช๐Ÿป๐Ÿ˜Ž

Collapse
 
dotallio profile image
Dotallio

Love how you turned boring issue threads into instant meme zones! Have you tried adding reactions or other fun commands to your bot yet?

Collapse
 
alishirani profile image
Ali Shirani

Not yet but having it on my mind๐Ÿง‘โ€๐Ÿ’ป

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

Pretty cool, I gotta admit adding GIFs to issues would make my day way better

Some comments may only be visible to logged-in visitors. Sign in to view all comments.