A small group of friends brought some hilarious bots to my attention over the last little while doing the rounds on twitter. They do some simple searches using GitHub’s search API and send out the results as tweets. This has led to some humorous examples being pulled up to see.

I realised that, in my learning of Typescript, I might go ahead and use this as a funny example project in order to get to grips with the basics. But the irony of this is i didn’t end up learning much more than i already knew.

You can find this lovely nsfw example here Got banned, because saying a naughty word 10 times is fine, but saying it to make it aggressive at a particular person is bannable. Thanks Twitter.

You can find a repo for this silly project here, thought there are some minor changes to do with the typescript environment.

Planning

The first thing i needed to do was set up my environment and download the neccessary packages I needed. For this project I decided to use Octokit‘s Rest.js package to interact with github, and the twitter-api-client to deal with twitter. As well as these I would need a lot of dev-side setup as well to deal with Typescript. After this, it was a matter of just every x (generic time frame) the bot will search using GitHub’s search API for an array of colourful words and post them to twitter using Twitter’s API. There’s much more setup than there is actual code, but that’s fine. Practice makes perfect after all.

Setting up a Basic Directory

First, a new folder was created to work with this project.

Inside the folder, i created two files. .gitignore and README.md.

Inside the .gitignore i add the following lines:

node_modules – Ignore package directory for node
.env – ignore my main .env file.
build – where my typescript is going to compile.

Once this is set up, i opened up a terminal running npm init -y. This sets the directory up with a default package.json to work with.

Setting up Version Control

With this now set up, you’re also going to need to get your repo set up for this project. If there’s one thing i can’t tell people enough it’s to make sure you are putting -every- project into version control. Getting comfortable with the in’s and out’s is vital for anyone. It may also just save you in the event of a screw up. A simple git init in terminal will suffice to create an empty repo.

Add and commit everything in the folder currently. If you’ve set up a remote repo, you can add this here as well and push to it.

git add .
git commit -m "Initial Repo"

Setting up Typescript

Now that the basics are laid out, it’s time to get into the nitty gritty of why i wanted to do this project in the first place. Typescript.

Typescript is a way to do some basic unit tests prior to build. Errors such as type errors, mis-spellings or wrong returns can all be pulled out by typescripts compiler. And the best part is Typescript compiles into javascript, meaning it can still be run anywhere without any issues.

Using typescript is a great way to cut out a lot of simplified testing in your programs. Testing should always be a big part of any written pipeline.

First, you’re going to want to install a few typescript packages and save them as dev dependancies.

npm install typescript ts-node @types/node --save-dev

Then, you’re going to want to create a tsconfig.json file. This can be done via the tsc package:

npx tsc --init

Once the file is created, open it up. You’ll find a large array of different settings, however there are only two we’re interested in. Uncomment the outDir and rootDir lines, and replace them with the following:

"outDir": "./build",
"rootDir": "./",  

This will tell typescript to use a folder called build for the compiled output and set the root directory correctly.

As well as this, we’ll need to adjust our package.json so that we can build and run our program in the correct directory.

Open up package.json and update your “scripts” section with the following:

  "scripts": {
    "start": "node build/index.js",
    "build": "tsc --project ./",
  },

With that, we can finally make a start on our twitter bot.

Getting Started

The first thing we’re going to need are some twitter API credentials. Make your way over to twitter developer portal and apply for a developer account. You’re going to want to create a new twitter account for this bot. Please note, that in order to use this, you will need to provide a valid, verified phone number for your twitter account. Once this is done, you’ll be provided with this screen:

Choose Hobbyist and Making a Bot from the dropdown that appears. Then click the Get Started button.

The next few pages are just general registration questions. It will ask you to double check and verify your number, make sure the email account is set up correctly, intended use and confirm that you understand their terms of service.

Once you’ve filled this out, you’ll be finally taken to the developer project portal.

Create a new project, and create a new app within the project. Call it whatever you want. Take note of your api key, your api secret and your bearer token. These are very important.

Next, you’re going to want to change the app permissions to both read and write.

Finally, you’re going to want to set up OAuth. Go into the settings and enable it. Set the callback URL as just http://localhost for now. Leave everything else and just hit save.

Your bot settings are all ready to go!

Package and skeleton setup

Having a bot ready to go is great, but we’ll need to have the information we want to send as well. The goal is to pull out pull requests from public repo’s that use naughty (read: Scottish) words. These can be replaced with whatever fancy words you might want, i just chose to use these because i’m an awful human being and it brought out some of the most hilarious results.

We’ll be using GitHub public search API for this, so there won’t be any need for keys.

To get this information, as mentioned previously, we’re going to use Octokit. Octokit is a client for interacting with Githubs API. There are a number of different version of this for different languages, but in this case we’ll be using octokit/rest.js.

Octokit works using the same syntax as githubs general search. If you go to github and use their search bar to look for anything, you’ll see your url alter. If you go to advanced settings and play with them you’ll see a lot of different settings go into the url. This is the basis of the rest client.

Getting back to our program, we can now make a start building it. First, create a folder called src in your root directory. Inside this, create a file called index.ts.

We need to install octobot, it’s type definitions, a twitter client and a timer to set off when it’s supposed to. We’ll use node-cron for this. Type the following into your terminal:

npm install @octokit/rest – install rest client
npm install @octokit/types – install types for rest client
npm install node-cron – install a cron-like timer for node
npm install twitter-api-client – install a twitter client to deal with the bot
npm install dotenv – install to deal with your api keys

Once all this is installed, we can finally chip away at the code.

Create a new file in your root directory called .env. Inside this file, copy the settings below, but fill in the x‘s with your own tokens:

APIKEY=xxxxxx
APISECRET=xxxxxx
ACCESSTOKEN=xxxxxx
ACCESSTOKENSECRET=xxxxxx

If you’re pushing this to your own repo, i recommend making a second file called .env.example and fill that with only the variables, so you can clone and know what the .env needs in it.

Once you’ve done this, open up index.ts and start importing your modules:

import { Endpoints } from '@octokit/types';

require('dotenv').config();

const cron = require('node-cron');

const { Octokit } = require('@octokit/rest');

const { TwitterClient } = require('twitter-api-client');

Next, you need to feed your API details into the twitter client. dotenv will allow you to include these variables in a safe and secure way.

const twitterClient = new TwitterClient({
  apiKey: process.env.APIKEY,
  apiSecret: process.env.APISECRET,
  accessToken: process.env.ACCESSTOKEN,
  accessTokenSecret: process.env.ACCESSTOKENSECRET,
});

Next we’ll create a simple function that will run when node-cron says so:

const sendTweet = () => {
  console.log("Running function!");
}

..and finally we’ll add the cron itself to run this function every 10 seconds.

cron.schedule('*/10 * * * * *), () => {
  sendTweet();
}

With this done, we’re ready to go. Go to your terminal and type:

npm run build

You will notice that a build folder will appear in your applications root directory. Inside that is the compiled Javascript created from the typescript.

After this, run:

npm start

The bot will start, and it will post the sendTweet function every 10 seconds.

Now that that’s working as intended, it’s time to get to the meat of the matter.

Talking to GitHub

By using Octokit, we return a JSON object of our search results. From this, we want to pull out the data we need to form our tweet.

First, we’ll create an instance of octokit in our function, removing the console.log and replace it with the following:

  const octokit = new Octokit({
    userAgent: 'scotbot/rest.js v1.0.0',
  });

The userAgent can be whatever you want it to be. As long as it’s something unique and says what your bot is.

Next, we want to define a response type. Underneath the new octokit instance add the following:

type searchResponse = Endpoints['GET /search/issues']['response'];

This creates the correct response type for the issues we’ll be returning from our search. Types are important. Without them, our compiler will respond with errors as we haven’t defined the data correctly.

Our next step is to create our search function.

Disclaimer: bad word choice -will- get you warned and eventually banned because reasons. That’s just a given. Be careful with your word choice, or alternatively, have a good laugh with it. It’s only twitter. There are much worse things on there.

We are going to search on the issues and pull requests endpoint. In order to do this correctly, we’ll need to turn our full function into an asyncronous function. This will allow us to await octokits response as well as dealing with the twitter client later.

Change the initial function line to the following:

const sendTweet = async () => {

After ammending this, we will add the following underneath our searchResponse type:

await octokit.search.issuesAndPullRequests({
  q: 'wank+OR+bellend+OR+bawbag+in:title',
  sort: 'updated',
  order: 'desc',
  per_page: 100,
}).then(async (result: searchResponse) => {
  console.log(result);
}).catch((error) => console.log(error));

The code here should be self explanitory. The issues and pull requests search endpoint uses the attribute object to create a github search, returning the latest 100 records to match. In the event of an error, it will print it out in the console. Otherwise, the results will be printed out in full. simple.

I used more colourful and vulgar search items in my one, however the example above is much more tame and should give you some entertaining results to look at.

Once your satisfied it’s working as intended, a while loop will need to be made to get a reasonable result and store the information from it. I kept this part very simple.

let found: boolean = false;
let title: String = '';
let html_url: String = '';
let body: String = '';

while (!found) {
  const chosenData = result.data.items[Math.floor(Math.random() * result.data.items.length)];
  console.log(chosenData);
  if (chosenData.title.split(' ').length < 2) {
    console.log('re-rollling...');
  } else {
    title = chosenData.title;
    html_url = chosenData.html_url;
    body = chosenData.body;
    found = true;
  }
}

In simple terms, we’ll set some basic variables up to store our information when it’s selected. While we don’t have a result, go through all results and pick a random entry. If it’s title is only a single word, we don’t want it. We want entertaining results not just BAWBAG HAHA.

When an entry is finally chosen, we’ll set the variables up correctly and make sure to break out of the while loop by saying it’s found.

Posting to Twitter

The final part of this is sending it to twitter itself. Which is acutally one of the much easier parts. Our Twitter Client only requires one function:

await twitterClient.tweets.statusesUpdate({
  status: `${title} - ${html_url} - ${body.substring(0, 60)}...`,
});

…and that’s it. This will take the title, url and a small substring of the body for each tweet. Occasionally it won’t fire if it finds a duplicate tweet but that’s not really a big issue. If you run this in terminal, every 10 minutes a new tweet will be pulled out and placed on your feed. Like i said before, the setup will take much longer than the execution.

Conclusion

This bot is very silly, but it was a great way for me to get my feet wet with some simple typescript mannerisms and just have a laugh at GitHub and Twitter’s expense.