Categories
DevOps

Creating a Simple Github App using Probot.

Personally, I do not like or use swear words while commenting on my code. But I see a lot of people are very casual about it and would go ahead and do it anyways. A few years ago Microsoft made the source code for MS-DOS and Word public and here is what one of the developers tweeted back then —

So this time when I was playing around on how to make a simple hello world kinda GitHub app. I wanted to build an app that checks for such words in a pull request and blocks the pull request if it has any of them. Simple? Let’s go.

PROBOT

Probot is a beautiful framework for building a GitHub App in Node.js. When using Probot you do not have to worry about receiving and validating webhook, authentication, etc, you get to focus on your business logic and be able to finish your app faster.

Probot comes with many starter templates, for this project I chose this template.

CODE

app.on can be configured to listen to any GitHub webhook events, in my case I made it listen to these 2 events

  • Pull request open
  • Pull request synchronize (update)
module.exports = app => {
robot.on([‘pull_request.opened’, ‘pull_request.synchronize’], async context => {
// do something John Doe!
context.log(context.payload)
})
}

Once we have the event, we are able to read the file content using the following code —

const files = await context.github.pullRequests.getFiles({
owner,
repo,
number,
headers: {accept: 'application/vnd.github.v3.diff'},
page,
per_page: 100
})

Then I simply loop thru each line of code per file and scan each line to find mentions of the words shits, f**ks, wtf, etc using regex. If we find any such line, we create a comment on that line, and these comments ultimately become part of the pull request review and are submitted. We also block the PR using REQUEST_CHANGES event.

Below is the source code that does everything —

module.exports = (robot) => {
robot.on(['pull_request.opened', 'pull_request.synchronize'], async context => {
const owner = context.payload.repository.owner.login
const repo = context.payload.repository.name
const number = context.payload.number

const {commentLimit, commentMessage, skipBranchMatching} = await context.config('better-comments-bot.yml', {
commentLimit: 10,
commentMessage: 'Please use better language in your comments :pray:',
skipBranchMatching: null
})
// Check if we should skip this branch
const branchName = context.payload.pull_request.head.ref
const regex = new RegExp(skipBranchMatching)
if (skipBranchMatching && branchName.match(regex)) {
context.log.warn(`Skipping branch: ${branchName} because of regex ${regex}`)
return
}

// Find all the comments on the PR to make sure we don't comment on something we have already commented on.
const linesCommentedOnByBot = await getAllLinesCommentedOnByBot(context, owner, repo, number)

const comments = []
let page = 0
while (true) {
const files = await context.github.pullRequests.getFiles({
owner,
repo,
number,
headers: {accept: 'application/vnd.github.v3.diff'},
page,
per_page: 100
})

for (const file of files.data) {
let currentPosition = 0
if (!file.filename.endsWith('.js')) return

// In order to not spam the PR with comments we'll stop after a certain number of comments
if (comments.length > commentLimit) return

const lines = file.patch.split('n')
for (const line of lines) {
console.log("verifying")
if (line.startsWith('+') && isDecent(line)) {
if (!linesCommentedOnByBot.includes(currentPosition)) {
comments.push({
path: file.filename,
position: currentPosition,
body: commentMessage
})
}
}
// We need to keep a running position of where we are in the file so we comment on the right line
currentPosition += 1
}
}
page += 1

if (files.data.length < 100 || comments.length >= commentLimit) break
}

// Only post a review if we have some comments
if (comments.length) {
await context.github.pullRequests.createReview({
owner,
repo,
number,
commit_id: context.payload.pull_request.head.sha,
event: 'REQUEST_CHANGES',
comments
})
}
})
}

function isDecent(line) {
return line.includes('shit') || line.includes('wtf');
}

DEPLOYMENT

I deployed my app on Glitch, because of its easy to use and edit and commit on-fly web editor interface. Probot has a dedicated “Deploy on Glitch” too here — https://probot.github.io/docs/deployment/#glitch

I created a pull request to test this out on a test project. And voila!


Here is the link to the repo – https://github.com/yrshaikh/better-comments and link to the app – https://github.com/apps/better-comments.

Happy coding 🙂