I have a private Discord channel and a private GitHub repository.
Every time I paste a URL to an issue in the private GitHub repository I want it to be expanded in the channel.
I ended up solving this by running a custom Discord bot on Glitch.
I chose Glitch because I needed convenient hosting where the bot process would be running all the time. Discord bots need to stay connected to Discord via a WebSocket, so hosting that scales-to-zero won't work.
I already have a paid Glitch account that lets me "boost" up to five apps - where boosting keeps the apps running all the time. So I decided to use that.
My bot needs a token for talking to Discord and one for talking to GitHub.
I created the Discord one by following the README in this discord-bot-example by Dan Reeves on Glitch.
I created a new Discord app at https://discordapp.com/developers/applications/me
The token I needed was on the "Bot" page:
I clicked "Reset token" and copied out the token, to use later.
I also needed to turn on "message content intent" for my bot, further down that page:
I needed the "application ID" from the "General Information" page of the bot application. I used that to construct this URL:
https://discordapp.com/oauth2/authorize?&client_id=<APPLICATION_ID>&scope=bot&permissions=0
Then I visited that page and used it to add the bot to my Datasette Discord server.
I'm still not entirely sure the right way to do this. Clicking "Create invite" in the channel menu doesn't seem to allow a bot to be invited. The only thing that definitely works is scrolling to the very top of a channel and clicking the "Add members or roles" button:
That provided a menu that allowed me to invite my bot.
I needed a GitHub API token that could access the issues API for my private repository.
First I tried using the new ability in GitHub to create scoped access tokens that could only access one repo. This seemed to work... but I later found out that my regular GitHub account is highly rate-limited and so that token wasn't able to make very many requests.
Instead, I created a brand new GitHub user account called datasette-github-bot
. I invited this to my private repository, then created a personal access token for it with permission to read repository data.
I started by remixing the discord-bot-example project. I added my two API keys to the .env
panel there:
I decided to use node-fetch
to access the GitHub API. It turns out I needed to use version 2 of that on Glitch, since version 3 uses ES Modules which I don't think are supported yet (or maybe I'm on an older Node version on Glitch?)
I put this in my package.json
on Glitch:
{
"name": "discord-1337-bot",
"version": "0.0.1",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"eris": "^0.16.1",
"node-fetch": "2.6.7"
},
"engines": {
"node": "16"
}
}
Then I iterated my way towards this code in server.js
, with a bit of help from ChatGPT 4:
const Eris = require("eris");
const fetch = require('node-fetch');
function extractIssueId(input) {
const match = input.match(
/https:\/\/github\.com\/simonw\/my-private-repo\/issues\/(\d+)/
);
return match ? match[1] : null;
}
async function fetchIssue(id) {
const response = await fetch(
"https://api.github.com/repos/simonw/my-private-repo/issues/" + id,
{
headers: {
Authorization: "token " + process.env.GITHUB_PAT_TOKEN,
},
}
);
if (!response.ok) {
let text = await response.text();
return { error: `HTTP error! status: ${response.status} ${text}` };
}
return await response.json();
}
const bot = new Eris(process.env.DISCORD_BOT_TOKEN);
bot.on("ready", () => {
console.log("Ready!");
console.log(bot.user.id);
});
bot.on("messageCreate", async (msg) => {
console.log(msg.channel.name, msg.author.username, msg.content);
if (msg.channel.name == "my-private-channel" && msg.author.id != bot.user.id) {
let id = extractIssueId(msg.content);
console.log('issue ID', id);
if (id) {
const issue = await fetchIssue(id);
console.log(issue);
if (!issue.error) {
bot.createMessage(msg.channel.id, `${issue.number}: ${issue.title} - ${issue.html_url}`);
}
}
}
});
bot.connect();
The great thing about Glitch is the server automatically restarted every time I edited the file in my browser.
And... this works!
Now every time I paste a URL to https://github.com/simonw/my-private-repo/issues/361 into the #my-private-channel
channel, the bot responds with the title and URL for that issue:
361: Discord bot to expand issue links - https://github.com/simonw/my-private-repo/issues/361
And since this whole system is extremely easy to hack on, adding additional features should be very straight-forward.
I did have one nasty bug while I was putting this together. The fix was this:
if (msg.channel.name == "my-private-channel" && msg.author.id != bot.user.id) {
That && msg.author.id != bot.user.id
bit is crucial. Before I added that, any time I pasted a URL into the channel the bot would reply... and then it would see its own message and reply over and over again in an infinite loop!
Thankfully since I was running on Glitch I saw what happened and quickly commented out the bot.createMessage()
line to stop the loop.
Created 2023-06-29T22:19:03-07:00 · Edit