Skip to content

Instantly share code, notes, and snippets.

@calebhearth
Last active October 14, 2020 20:06
Show Gist options
  • Save calebhearth/162e122dbb09750aa2a8dd84e1b752fb to your computer and use it in GitHub Desktop.
Save calebhearth/162e122dbb09750aa2a8dd84e1b752fb to your computer and use it in GitHub Desktop.
import { WebClient, WebAPICallResult, LogLevel } from '@slack/web-api';
import express from 'express';
import fetch from "node-fetch";
import pgPromise from 'pg-promise';
const slack = new WebClient(process.env.SLACK_TOKEN, { logLevel: LogLevel.DEBUG });
var dbu: string
if (process.env.DATABASE_URL !== undefined) {
dbu = process.env.DATABASE_URL
} else {
console.log("DATABASE_URL not set");
process.exit(1);
}
if (process.env.ENV == "production") {
dbu+="?ssl=true"
}
interface DBScopes {
createInitiative(ts: string, name: string, initiative: number | undefined): void;
initiativeExists(ts: string): Promise<boolean>;
initiativesForTs(ts: string): Promise<inititiativeLineItem[]>;
}
const pgpOptions: pgPromise.IInitOptions<DBScopes> = {
extend(db) {
db.createInitiative = (ts: string, name: string, initiative: number | undefined): void => {
db.one(
"INSERT INTO initiatives(ts, name, initiative) \
VALUES(${ts}, ${name}, ${initiative}) \
RETURNING id;",
{ ts: ts, name: name, initiative: initiative }
);
}
db.initiativeExists = (ts: string) => {
return db.one(
"SELECT exists(SELECT 1 FROM initiatives WHERE ts = ${ts}) AS exists;",
{ ts: ts }
)
}
}
}
var pgp = pgPromise(pgpOptions)
const QueryResultError = pgp.errors.QueryResultError;
var db = pgp(dbu);
const app: express.Application = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
type inititiativeLineItem = {
name: string;
bonus: number;
initiative?: number;
}
app.post("/initiative", (req, resp) => {
console.log("BODY:", req.body);
resp.status(200).send()
var items = parseInitiative(req.body.text)
var lines: string[] = [];
items.forEach((item: inititiativeLineItem) => {
item.initiative = Math.ceil(Math.random() * 20) + item.bonus
lines.push(`• ${item.initiative} ${item.name}\n`)
});
var collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' })
var response: string = lines
.sort(collator.compare)
.reverse()
.join("\n");
console.log("WILL RESPOND WITH: ", response)
slack.chat.postMessage({channel: req.body.channel_id, text: response})
.then(body => {
console.log(body)
var msg = body.message as message
return items.forEach((item: inititiativeLineItem) => {
db.createInitiative(msg.ts, item.name, item.initiative);
});
});
});
type exists = {
exists: boolean
};
app.post("/slack-events", async (req, resp) => {
console.log("EVENT: ", req.body);
if (req.body.type == "url_verification")
resp.status(200).send(req.body.challenge);
return;
interface message_replied {
thread_ts: string;
user: string;
text: string;
}
switch (req.body.event?.type) {
case "message":
if (req.body.event as message_replied) {
var reply: message_replied = req.body.event;
var inInitiativeThread: boolean | void = await db.initiativeExists(reply.thread_ts)
.then((value: any): boolean => value.exists!)
.catch(error => console.log(error));
if (inInitiativeThread) {
const matches: RegExpMatchArray | null = reply.text!.match(/\d+/)
if (matches) {
if (matches.length > 0) {
var initiative_str: string = matches[0];
if (initiative_str != null) {
var initiative: number = +initiative_str;
await db.createInitiative(reply.thread_ts, reply.user, initiative)
}
}
}
}
resp.status(200).send()
break;
}
default:
resp.status(200).send();
console.log("Unable to process event.");
break;
}
});
app.post("/*", (req, resp) => {
console.log("UNKNOWN: ", req.body);
resp.send("???")
});
interface ChatPostMessageResult extends WebAPICallResult {
channel: string;
ts: string;
message: {
text: string;
}
}
interface message {
ts: string;
}
// 0 or more [<number>x]<name>[[+/-]<bonus>] entries separated by whitespace
// 2xGoblin+2 => 2 goblins each with a +2 bonus
// Bugbear => 1 Bugbear with a 0 bonus
function parseInitiative(text: string): inititiativeLineItem[] {
const re = /((?<number>\d+)x)?(?<name>[A-Za-z][A-Za-z\s]+[A-Za-z])(?<bonus>[+-]\d+)?/g
var lineItems: inititiativeLineItem[] = [];
for (var match of text.matchAll(re)) {
var number: number = +(match?.groups?.number ?? 1);
for (let i: number = 0; i < number; i++) {
var name = match?.groups?.name ?? "No name"
if (number > 1) name += ` ${i+1}`
lineItems.push({ name: name, bonus: +(match?.groups?.bonus ?? 0) });
}
}
return lineItems;
}
app.listen(process.env.PORT, ()=> {
console.log(`Server listening on :${process.env.PORT}`);
});
{
"name": "bigby",
"version": "1.0.0",
"description": "Slack Bot for D&D",
"main": "index.js",
"dependencies": {
"@slack/web-api": "^5.12.0",
"ejs": "^3.1.5",
"express": "^4.17.1",
"node-fetch": "^2.6.1",
"node-pg-migrate": "^5.8.0",
"pg": "^8.3.3",
"pg-promise": "^10.6.1",
"typescript": "^4.0.3"
},
"devDependencies": {
"@types/express": "^4.17.8",
"@types/node": "^14.11.2",
"@types/node-fetch": "^2.5.7"
},
"scripts": {
"build": "tsc --build tsconfig.json",
"migrate": "node-pg-migrate",
"postinstall": "npm run build",
"start": "node dist/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/calebthompson/bigby.git"
},
"keywords": [
"slack",
"D&D",
"bot"
],
"author": "Caleb Hearth <caleb@calebhearth.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/calebthompson/bigby/issues"
},
"homepage": "https://github.com/calebthompson/bigby#readme"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment