From a4dbfffc3e50d195dcbafd6db1f8a2b3a3ec1392 Mon Sep 17 00:00:00 2001 From: Shane Duan Date: Sun, 26 Aug 2018 09:14:29 -0700 Subject: [PATCH 01/11] Add more comments to regex, fix z/a_// issue --- x2i/index.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/x2i/index.ts b/x2i/index.ts index 0d99cfd..fe25fd5 100644 --- a/x2i/index.ts +++ b/x2i/index.ts @@ -26,12 +26,23 @@ interface IMatchInstructions { } const regex = OuterXRegExp( - `(?:(^|[\`\\p{White_Space}])) # must be preceded by whitespace or surrounded by code brackets - ([A-Za-z]*) # key, to lower (2) - ([/[]) # bracket left (3) - (\\S|\\S.*?\\S) # body (4) - ([/\\]]) # bracket right (5) - (?=$|[\`\\p{White_Space}\\pP]) # must be followed by a white space or punctuation`, + `# must be preceded by whitespace or surrounded by code brackets, or on its own line + (?:(^|[\`\\p{White_Space}])) + + # ($2) key, to lower + ([A-Za-z]*) # consumes non-tagged brackets to avoid reading the insides accidentally + # ($3) bracket left + ([/[]) + # ($4) body + ( + \\S # single character (eg x/t/) + |\\S.*?[^_\p{White_Space}] # any characters not surrounded by whitespace, ignores _/ + ) + # ($5) bracket right + ([/\\]]) + + # must be followed by a white space or punctuation (lookahead) + (?=$|[\`\\p{White_Space}\\pP])`, "gmx"); const defaultMatchAction = (left: string, match: string, right: string) => left + match + right; From ab289aeca4200a612c3ca600a838fdf529d41e05 Mon Sep 17 00:00:00 2001 From: Shane Duan Date: Sun, 26 Aug 2018 09:59:05 -0700 Subject: [PATCH 02/11] Fix error columns insert getting swapped OOPS --- db-management.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db-management.ts b/db-management.ts index 3940d4b..1963559 100644 --- a/db-management.ts +++ b/db-management.ts @@ -84,7 +84,7 @@ export default class ConniebotDatabase { public async addError(err: any) { return (await this.db).run( SQL`INSERT INTO unsentErrors(date, stacktrace, message) - VALUES(${new Date()}, ${err.message || String(err)}, ${err.stack})`, + VALUES(${new Date()}, ${err.stack}, ${err.message || String(err)})`, ); } From dcda1ab2ac1d71f9e3d8491a601f20d0b2ab1a2c Mon Sep 17 00:00:00 2001 From: gufferdk Date: Sun, 26 Aug 2018 20:06:46 +0200 Subject: [PATCH 03/11] Minor reorderings and fixes, added global fall/rise --- x2i/z2i-keys.yaml | 64 ++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/x2i/z2i-keys.yaml b/x2i/z2i-keys.yaml index a477c16..6776ee5 100644 --- a/x2i/z2i-keys.yaml +++ b/x2i/z2i-keys.yaml @@ -99,10 +99,6 @@ - ƙ - - q_< - ʠ -- - ;\ - - ǃ͡¡ -- - +\ - - '|||' - - t`_m - ȶ - - d`_m @@ -293,8 +289,6 @@ - ̩ - - "=" - ̩ -- - _> - - ʼ - - _?\ - ˁ - - _? @@ -520,6 +514,34 @@ - ˤ - - +? - ˀ +- - _<\ + - "↓" +- - _>\ + - "↑" +- - _> + - ʼ +- - _< + - ʼ↓ +- - <\ + - ʢ +- - '>\' + - ʡ +- delimiters: ['<', '>'] + translations: + - - '1' + - ˩ + - - '2' + - ˨ + - - '3' + - ˧ + - - '4' + - ˦ + - - '5' + - ˥ + - - 'F' + - ↘ + - - 'R' + - ↗ - - a - a - - ä @@ -796,8 +818,6 @@ - ʫ - - Z - ʒ -- - ^\ - - ğ - - '.' - '.' - - '"' @@ -818,28 +838,6 @@ - æ - - '}' - ʉ -- - _<\ - - "↓" -- - _>\ - - "↑" -- - _< - - ʼ↓ -- - <\ - - ʢ -- - '>\' - - ʡ -- delimiters: ['<', '>'] - translations: - - - '1' - - ˩ - - - '2' - - ˨ - - - '3' - - ˧ - - - '4' - - ˦ - - - '5' - - ˥ - - '1' - ɨ - - 2\ @@ -886,7 +884,7 @@ - ʕ - - '?' - ʔ -- - \^ +- - ^\ - ğ - - ^ - ꜛ @@ -920,6 +918,10 @@ - ‖ - - '|' - '|' +- - ;\ + - ǃ͡¡ +- - +\ + - '⦀' - - '`' - ˞ - - ; From 6ca2f0d876139b04fc8e4ce4eff0b3ddab7c7d06 Mon Sep 17 00:00:00 2001 From: Shane Duan Date: Sun, 26 Aug 2018 11:49:40 -0700 Subject: [PATCH 04/11] Refactor to remove all global variables from bot.ts --- bot.ts | 206 +++++++++++++++++++++++++---------------------- db-management.ts | 9 +-- index.ts | 16 ++++ package.json | 2 +- utils.ts | 10 ++- 5 files changed, 135 insertions(+), 108 deletions(-) create mode 100644 index.ts diff --git a/bot.ts b/bot.ts index 5817c1d..25f03db 100644 --- a/bot.ts +++ b/bot.ts @@ -1,5 +1,5 @@ import c from "config"; -import { Client, Message, RichEmbed } from "discord.js"; +import { Client, ClientOptions, Message, RichEmbed } from "discord.js"; import process from "process"; import OuterXRegExp from "xregexp"; @@ -7,124 +7,134 @@ import commands from "./commands"; import ConniebotDatabase from "./db-management"; import embed from "./embed"; import startup from "./startup"; -import { logMessage } from "./utils"; +import { logMessage, messageSummary } from "./utils"; import x2i from "./x2i"; const bot = new Client(); const db = new ConniebotDatabase(); -/** - * Convert a message object into a string in the form of guildname: message{0, 100} - */ -function messageSummary({ guild, content }: Message) { - const guildName = guild ? guild.name : "unknown guild"; - return `${guildName}: ${content.substr(0, 100)}`; -} +export class Conniebot { + public bot: Client; + public db: ConniebotDatabase; + + constructor(token: string, dbFile: string, clientOptions?: ClientOptions) { + this.bot = new Client(clientOptions); + this.db = new ConniebotDatabase(dbFile); + + this.bot.on("ready", () => startup(this.bot, this.db)) + .on("message", this.parse) + .on("error", err => { + if (err && err.message && err.message.includes("ECONNRESET")) { + return console.log("connection reset. oops!"); + } + this.panicResponsibly(err); + }) + .login(token); + + process.once("uncaughtException", this.panicResponsibly); + } -/** - * Looks for a reply message. - * - * @param message Received message. - */ -async function command(message: Message) { - // commands - const prefixRegex = OuterXRegExp.build( - `(?:^${OuterXRegExp.escape(c.get("prefix"))})(\\S*) ?(.*)`, [], - ); - - const toks = message.content.match(prefixRegex); - if (!toks) return; - - const [, cmd, args] = toks; - const cb = commands[cmd]; - if (!cb) return; - - try { - const log = await cb(message, db, ...args.split(" ")); - logMessage(`success:command/${cmd}`, log); - } catch (err) { - // TODO: error reporting - logMessage(`error:command/${cmd}`, err); + /** + * Record the error and proceed to crash. + * + * @param err The error to catch. + * @param exit Should exit? (eg ECONNRESET would not require reset) + */ + private panicResponsibly = async (err: any, exit = true) => { + console.log(err); + await this.db.addError(err); + if (exit) { + process.exit(1); + } } -} -/** - * Sends an x2i string (but also could be used for simple embeds) - * - * @param message Message to reply to - */ -async function x2iExec(message: Message) { - let results = x2i(message.content); - const parsed = Boolean(results && results.length !== 0); - if (parsed) { - const response = new RichEmbed().setColor( - c.get("embeds.colors.success"), + /** + * Looks for a reply message. + * + * @param message Received message. + */ + private async command(message: Message) { + // commands + const prefixRegex = OuterXRegExp.build( + `(?:^${OuterXRegExp.escape(c.get("prefix"))})(\\S*) ?(.*)`, [], ); - let logCode = "all"; - // check timeout - const charMax = parseInt(c.get("embeds.timeoutChars"), 10); - if (results.length > charMax) { - results = `${results.slice(0, charMax - 1)}…`; + const toks = message.content.match(prefixRegex); + if (!toks) return; - response - .addField("Timeout", c.get("embeds.timeoutMessage")) - .setColor(c.get("embeds.colors.warning")); - - logCode = "partial"; - } - - response.setDescription(results); - logMessage(`processed:x2i/${logCode}`, messageSummary(message)); + const [, cmd, args] = toks; + const cb = commands[cmd]; + if (!cb) return; try { - await embed(message.channel, response); - logMessage("success:x2i"); + const log = await cb(message, this.db, ...args.split(" ")); + logMessage(`success:command/${cmd}`, log); } catch (err) { - logMessage("error:x2i", err); + // TODO: error reporting + logMessage(`error:command/${cmd}`, err); } } - return parsed; -} + /** + * Sends an x2i string (but also could be used for simple embeds) + * + * @param message Message to reply to + */ + private async x2iExec(message: Message) { + let results = x2i(message.content); + const parsed = Boolean(results && results.length !== 0); + if (parsed) { + const response = new RichEmbed().setColor( + c.get("embeds.colors.success"), + ); + let logCode = "all"; + + // check timeout + const charMax = parseInt(c.get("embeds.timeoutChars"), 10); + if (results.length > charMax) { + results = `${results.slice(0, charMax - 1)}…`; + + response + .addField("Timeout", c.get("embeds.timeoutMessage")) + .setColor(c.get("embeds.colors.warning")); + + logCode = "partial"; + } + + response.setDescription(results); + logMessage(`processed:x2i/${logCode}`, messageSummary(message)); + + try { + await embed(message.channel, response); + logMessage("success:x2i"); + } catch (err) { + logMessage("error:x2i", err); + } + } -/** - * Acts for a response to a message. - * - * @param message Message to parse for responses - */ -async function parse(message: Message) { - if (message.author.bot) return; - if (await x2iExec(message)) return; - await command(message); -} + return parsed; + } -/** - * Record the error and proceed to crash. - * - * @param err The error to catch. - * @param exit Should exit? (eg ECONNRESET would not require reset) - */ -async function panicResponsibly(err: any, exit = true) { - console.log(err); - await db.addError(err); - if (exit) { - process.exit(1); + /** + * Acts for a response to a message. + * + * @param message Message to parse for responses + */ + protected parse = async (message: Message) => { + if (message.author.bot) return; + if (await this.x2iExec(message)) return; + await this.command(message); } } -process.once("uncaughtException", panicResponsibly); +export default function init() { + if (!c.has("token")) { + throw new TypeError("Couldn't find a token to connect with."); + } + + if (!c.has("database")) { + throw new TypeError("No database filename listed."); + } -if (!c.has("token")) { - throw new Error("Couldn't find a token to connect with."); + return new Conniebot(c.get("token"), c.get("database")); } - -bot.on("ready", () => startup(bot, db)) - .on("message", parse) - .on("error", err => { - if (err && err.message && err.message.includes("ECONNRESET")) { - return console.log("connection reset. oops!"); - } - panicResponsibly(err); - }) - .login(c.get("token")); diff --git a/db-management.ts b/db-management.ts index 1963559..cbb196e 100644 --- a/db-management.ts +++ b/db-management.ts @@ -1,4 +1,3 @@ -import c from "config"; import SQL from "sql-template-strings"; import sqlite, { Database } from "sqlite"; @@ -21,13 +20,7 @@ interface ISentErrorsRow extends IUnsentErrorsRow { export default class ConniebotDatabase { private db: Promise; - constructor(dbFile?: string) { - dbFile = dbFile || c.get("database"); - - if (!dbFile) { - throw TypeError("No database filename listed."); - } - + constructor(dbFile: string) { if (!dbFile.endsWith(".sqlite")) { console.log("Database file is not marked as `.sqlite`."); } diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..99ecada --- /dev/null +++ b/index.ts @@ -0,0 +1,16 @@ +import c from "config"; + +import Conniebot from "./bot"; + +const token = "token"; +const database = "database"; + +if (!c.has(token)) { + throw new TypeError("Couldn't find a token to connect with."); +} + +if (!c.has(database)) { + throw new TypeError("No database filename listed."); +} + +const conniebot = new Conniebot(c.get(token), c.get(database)); diff --git a/package.json b/package.json index bea2e43..90590c9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "lint": "tslint -p . -c tslint.json -e './**/*.json'", "fix": "tslint --fix -p . -c tslint.json -e './**/*.json'", - "start": "nodemon --exitcrash --ignore *.sqlite -x ts-node bot.ts", + "start": "nodemon --exitcrash --ignore *.sqlite -x ts-node index.ts", "forever": "forever start --uid conniebot --killSignal=SIGTERM -a -c \"npm start\" ./" }, "description": "Does various language stuff.", diff --git a/utils.ts b/utils.ts index 28117b2..355cf4a 100644 --- a/utils.ts +++ b/utils.ts @@ -1,4 +1,4 @@ -import { Channel, TextChannel } from "discord.js"; +import { Channel, Message, TextChannel } from "discord.js"; /** * Prints a formatted message with a related object. @@ -32,3 +32,11 @@ export async function sendMessage(msg: string, channel: TextChannel) { return false; } } + +/** + * Convert a message object into a string in the form of guildname: message{0, 100} + */ +export function messageSummary({ guild, content }: Message) { + const guildName = guild ? guild.name : "unknown guild"; + return `${guildName}: ${content.substr(0, 100)}`; +} From df2b16d1daf29a9e380979ae79f5bb30e1510bdc Mon Sep 17 00:00:00 2001 From: Shane Duan Date: Sun, 26 Aug 2018 12:57:42 -0700 Subject: [PATCH 05/11] Refactor command to be extension methods --- bot.ts | 44 +++++++++++++------- commands.ts | 108 ++++++++++++++++++++++++++------------------------ index.ts | 2 + tsconfig.json | 2 +- 4 files changed, 88 insertions(+), 68 deletions(-) diff --git a/bot.ts b/bot.ts index 25f03db..b1b59cd 100644 --- a/bot.ts +++ b/bot.ts @@ -3,23 +3,28 @@ import { Client, ClientOptions, Message, RichEmbed } from "discord.js"; import process from "process"; import OuterXRegExp from "xregexp"; -import commands from "./commands"; import ConniebotDatabase from "./db-management"; import embed from "./embed"; import startup from "./startup"; import { logMessage, messageSummary } from "./utils"; import x2i from "./x2i"; -const bot = new Client(); -const db = new ConniebotDatabase(); +export type CommandCallback = + (this: Conniebot, message: Message, ...args: string[]) => Promise; -export class Conniebot { +export interface ICommands { + [key: string]: CommandCallback; +} + +export default class Conniebot { public bot: Client; public db: ConniebotDatabase; + private commands: ICommands; constructor(token: string, dbFile: string, clientOptions?: ClientOptions) { this.bot = new Client(clientOptions); this.db = new ConniebotDatabase(dbFile); + this.commands = {}; this.bot.on("ready", () => startup(this.bot, this.db)) .on("message", this.parse) @@ -61,13 +66,15 @@ export class Conniebot { const toks = message.content.match(prefixRegex); if (!toks) return; - const [, cmd, args] = toks; - const cb = commands[cmd]; + + // assume that command has already been bound + // no way currently to express this without clearing the types + const cb: any = this.commands[cmd]; if (!cb) return; try { - const log = await cb(message, this.db, ...args.split(" ")); + const log = await cb(message, ...args.split(" ")); logMessage(`success:command/${cmd}`, log); } catch (err) { // TODO: error reporting @@ -125,16 +132,23 @@ export class Conniebot { if (await this.x2iExec(message)) return; await this.command(message); } -} -export default function init() { - if (!c.has("token")) { - throw new TypeError("Couldn't find a token to connect with."); + /** + * Register multiple commands at once. + */ + public registerCommands(callbacks: ICommands) { + for (const [name, cmd] of Object.entries(callbacks)) { + this.register(name, cmd); + } } - if (!c.has("database")) { - throw new TypeError("No database filename listed."); + /** + * Register a single custom command. + * + * @param command Command name that comes after prefix. Name must be `\S+`. + * @param callback Callback upon seeing the name. `this` will be bound automatically. + */ + public register(command: string, callback: CommandCallback) { + this.commands[command] = callback.bind(this); } - - return new Conniebot(c.get("token"), c.get("database")); } diff --git a/commands.ts b/commands.ts index 30ad4f2..0c1e025 100644 --- a/commands.ts +++ b/commands.ts @@ -1,71 +1,75 @@ import c from "config"; import { Message } from "discord.js"; -import ConniebotDatabase from "./db-management"; +import { ICommands } from "./bot"; import help from "./help"; -type Command = (message: Message, db: ConniebotDatabase, ...args: string[]) => Promise; - -interface ICommands { - [key: string]: Command; -} - /** - * Tries to respond in a timely fashion. + * Extension methods for different reply commands. * - * @param message Message to respond to (read time) - * @param roundtrip Should the heartbeat be sent to the message ("roundtrip") + * All functions are bound to the instance of the currently running Conniebot. */ -async function ping(message: Message, _: any, roundtrip?: string) { - // received message - const created = message.createdTimestamp; - const elapsedMsg = `${Date.now() - created} ms`; +const commands: ICommands = { + /** + * Funnels a message object to the actual {@link help} function. + */ + async help({ channel, client }) { + help(channel, client.user); + }, - // wait for send - const pingReturn = await message.channel.send(`I'm alive! (${elapsedMsg})`); - const pingMsg = Array.isArray(pingReturn) ? pingReturn[0] : pingReturn; - const roundtripMsg = `${Date.now() - created} ms`; + /** + * Set channel for an arbitrary event + * + * Currently used events: + * - `restart`: Notify restart. + * - `errors`: Notify errors. (may want to keep stack traces secret, etc) + * + * @param event The event name (only the first 50 characters are used) + */ + async notif(message: Message, event: string) { + if (message.author.id !== c.get("owner")) { + return message.reply("Sorry, but you don't have permissions to do that."); + } - if (roundtrip === "roundtrip") { - pingMsg.edit(`${pingMsg}, roundtrip ${roundtripMsg}`); - } + if (!event) { + return message.reply("Sorry, you need to specify an event."); + } - return `${elapsedMsg}, ${roundtripMsg}`; -} + const channel = message.channel; + let returnMessage: string; -/** - * Set channel for an arbitrary event (currently only uses `restart` and `events`) - * - * @param db Database instance. - * @param event The event name (only the first 50 characters are used) - */ -async function setChannel(message: Message, db: ConniebotDatabase, event: string) { - if (message.author.id !== c.get("owner")) { - return message.reply("Sorry, but you don't have permissions to do that."); - } + try { + await this.db.setChannel(event.substr(0, 50), channel.id); + returnMessage = `Got it! Will send notifications for ${event} to ${message.channel}.`; + } catch (err) { + console.log(err); + returnMessage = "Something went wrong while trying to set notifications."; + } - if (!event) { - return message.reply("Sorry, you need to specify an event."); - } + return channel.send(returnMessage); + }, - const channel = message.channel; - let returnMessage: string; + /** + * Tries to respond in a timely fashion. + * + * @param roundtrip Should the heartbeat be sent to the message ("roundtrip") + */ + async ping(message: Message, roundtrip?: string) { + // received message + const created = message.createdTimestamp; + const elapsedMsg = `${Date.now() - created} ms`; - try { - await db.setChannel(event.substr(0, 50), channel.id); - returnMessage = `Got it! Will send notifications for ${event} to ${message.channel}.`; - } catch (err) { - console.log(err); - returnMessage = "Something went wrong while trying to set notifications."; - } + // wait for send + const pingReturn = await message.channel.send(`I'm alive! (${elapsedMsg})`); + const pingMsg = Array.isArray(pingReturn) ? pingReturn[0] : pingReturn; + const roundtripMsg = `${Date.now() - created} ms`; - return channel.send(returnMessage); -} + if (roundtrip === "roundtrip") { + pingMsg.edit(`${pingMsg}, roundtrip ${roundtripMsg}`); + } -const commands = { - help: message => help(message.channel, message.client.user), - notif: setChannel, - ping, -} as ICommands; + return `${elapsedMsg}, ${roundtripMsg}`; + }, +}; export default commands; diff --git a/index.ts b/index.ts index 99ecada..a281bb5 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,7 @@ import c from "config"; import Conniebot from "./bot"; +import commands from "./commands"; const token = "token"; const database = "database"; @@ -14,3 +15,4 @@ if (!c.has(database)) { } const conniebot = new Conniebot(c.get(token), c.get(database)); +conniebot.registerCommands(commands); diff --git a/tsconfig.json b/tsconfig.json index 82f1c4e..8d236b6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2016", + "target": "es2017", "module": "commonjs", "strict": true, "noImplicitAny": true, From 638bbf835c40e98d53d8a38d730bc09cb6433ddd Mon Sep 17 00:00:00 2001 From: Shane Duan Date: Sun, 26 Aug 2018 13:48:39 -0700 Subject: [PATCH 06/11] Move all source files into ./src folder Uncluttering the top layer --- index.ts | 4 ++-- commands.ts => src/commands.ts | 2 +- db-management.ts => src/db-management.ts | 0 embed.ts => src/embed.ts | 0 help.ts => src/help.ts | 0 bot.ts => src/index.ts | 0 startup.ts => src/startup.ts | 0 utils.ts => src/utils.ts | 0 {x2i => src/x2i}/apie-keys.yaml | 0 {x2i => src/x2i}/index.ts | 12 +++++++----- {x2i => src/x2i}/x2i-keys.yaml | 0 {x2i => src/x2i}/z2i-keys.yaml | 0 12 files changed, 10 insertions(+), 8 deletions(-) rename commands.ts => src/commands.ts (98%) rename db-management.ts => src/db-management.ts (100%) rename embed.ts => src/embed.ts (100%) rename help.ts => src/help.ts (100%) rename bot.ts => src/index.ts (100%) rename startup.ts => src/startup.ts (100%) rename utils.ts => src/utils.ts (100%) rename {x2i => src/x2i}/apie-keys.yaml (100%) rename {x2i => src/x2i}/index.ts (92%) rename {x2i => src/x2i}/x2i-keys.yaml (100%) rename {x2i => src/x2i}/z2i-keys.yaml (100%) diff --git a/index.ts b/index.ts index a281bb5..25d4974 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,7 @@ import c from "config"; -import Conniebot from "./bot"; -import commands from "./commands"; +import Conniebot from "./src"; +import commands from "./src/commands"; const token = "token"; const database = "database"; diff --git a/commands.ts b/src/commands.ts similarity index 98% rename from commands.ts rename to src/commands.ts index 0c1e025..2751bf2 100644 --- a/commands.ts +++ b/src/commands.ts @@ -1,7 +1,7 @@ import c from "config"; import { Message } from "discord.js"; -import { ICommands } from "./bot"; +import { ICommands } from "."; import help from "./help"; /** diff --git a/db-management.ts b/src/db-management.ts similarity index 100% rename from db-management.ts rename to src/db-management.ts diff --git a/embed.ts b/src/embed.ts similarity index 100% rename from embed.ts rename to src/embed.ts diff --git a/help.ts b/src/help.ts similarity index 100% rename from help.ts rename to src/help.ts diff --git a/bot.ts b/src/index.ts similarity index 100% rename from bot.ts rename to src/index.ts diff --git a/startup.ts b/src/startup.ts similarity index 100% rename from startup.ts rename to src/startup.ts diff --git a/utils.ts b/src/utils.ts similarity index 100% rename from utils.ts rename to src/utils.ts diff --git a/x2i/apie-keys.yaml b/src/x2i/apie-keys.yaml similarity index 100% rename from x2i/apie-keys.yaml rename to src/x2i/apie-keys.yaml diff --git a/x2i/index.ts b/src/x2i/index.ts similarity index 92% rename from x2i/index.ts rename to src/x2i/index.ts index fe25fd5..a90fac6 100644 --- a/x2i/index.ts +++ b/src/x2i/index.ts @@ -1,4 +1,6 @@ import fs from "fs"; +import path from "path"; + import yaml from "js-yaml"; import OuterXRegExp from "xregexp"; @@ -50,25 +52,25 @@ const defaultMatchAction = (left: string, match: string, right: string) => left const matchType: { [key: string]: IMatchInstructions } = { p: { join: (_, match) => `*${match}`, - keys: readKeys("./x2i/apie-keys.yaml"), + keys: readKeys("./apie-keys.yaml"), }, x: { - keys: readKeys("./x2i/x2i-keys.yaml"), + keys: readKeys("./x2i-keys.yaml"), }, z: { - keys: readKeys("./x2i/z2i-keys.yaml"), + keys: readKeys("./z2i-keys.yaml"), }, }; /** * Read translation keys from file. Escapes strings first. * - * @param fpath File to key definitions. (yaml, utf8) + * @param fpath File to key definitions. (yaml, utf8) Relative to {@link __dirname}. * @returns Compiled keys. */ function readKeys(fpath: string) { return yaml - .safeLoad(fs.readFileSync(fpath, "utf8")) + .safeLoad(fs.readFileSync(path.join(__dirname, fpath), "utf8")) .map(compileKey) .filter(Boolean) as CompiledReplacer[]; } diff --git a/x2i/x2i-keys.yaml b/src/x2i/x2i-keys.yaml similarity index 100% rename from x2i/x2i-keys.yaml rename to src/x2i/x2i-keys.yaml diff --git a/x2i/z2i-keys.yaml b/src/x2i/z2i-keys.yaml similarity index 100% rename from x2i/z2i-keys.yaml rename to src/x2i/z2i-keys.yaml From d43ccbdd5731aa8859ab8864ae67f307f4ae6007 Mon Sep 17 00:00:00 2001 From: Shane Duan Date: Sun, 26 Aug 2018 17:17:20 -0700 Subject: [PATCH 07/11] Add more documentation --- src/commands.ts | 17 ++++------- src/db-management.ts | 71 +++++++++++++++++++++++++++++++++++++++++++- src/embed.ts | 6 ++-- src/startup.ts | 6 ++++ src/utils.ts | 2 +- 5 files changed, 86 insertions(+), 16 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 2751bf2..9ed1bb2 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,5 +1,4 @@ import c from "config"; -import { Message } from "discord.js"; import { ICommands } from "."; import help from "./help"; @@ -13,20 +12,16 @@ const commands: ICommands = { /** * Funnels a message object to the actual {@link help} function. */ - async help({ channel, client }) { - help(channel, client.user); + async help(message) { + help(message.channel, message.client.user); }, /** - * Set channel for an arbitrary event - * - * Currently used events: - * - `restart`: Notify restart. - * - `errors`: Notify errors. (may want to keep stack traces secret, etc) + * Set channel for an arbitrary event. (see {@link INotifRow}) * * @param event The event name (only the first 50 characters are used) */ - async notif(message: Message, event: string) { + async notif(message, event) { if (message.author.id !== c.get("owner")) { return message.reply("Sorry, but you don't have permissions to do that."); } @@ -39,7 +34,7 @@ const commands: ICommands = { let returnMessage: string; try { - await this.db.setChannel(event.substr(0, 50), channel.id); + await this.db.setChannel(event, channel.id); returnMessage = `Got it! Will send notifications for ${event} to ${message.channel}.`; } catch (err) { console.log(err); @@ -54,7 +49,7 @@ const commands: ICommands = { * * @param roundtrip Should the heartbeat be sent to the message ("roundtrip") */ - async ping(message: Message, roundtrip?: string) { + async ping(message, roundtrip?) { // received message const created = message.createdTimestamp; const elapsedMsg = `${Date.now() - created} ms`; diff --git a/src/db-management.ts b/src/db-management.ts index cbb196e..ab4acac 100644 --- a/src/db-management.ts +++ b/src/db-management.ts @@ -1,25 +1,84 @@ import SQL from "sql-template-strings"; import sqlite, { Database } from "sqlite"; +/** + * Key-value table of events. + * + * Currently used events: + * - `restart`: Notify restart. + * - `errors`: Notify errors. (may want to keep stack traces secret, etc) + */ interface INotifRow { + /** + * Event name (cuts off at 50 characters). + */ event: string; + + /** + * Channel ID that corresponds to the string, taken from + * [`Channel.id`](https://discord.js.org/#/docs/main/stable/class/Channel?scrollTo=id). + */ channel: string; } +/** + * A whole bunch of unsent errors. + */ interface IUnsentErrorsRow { + /** + * Autoincremented ID column. + */ id: number; + + /** + * Date that error happened (more specifically, when it was caught). + */ date: Date; + + /* tslint:disable: max-line-length */ + /** + * Stacktrace, if available. (see + * [`Error.prototype.stack`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack)) + * + * `stack` is technically non-standard, and not every throw will give an Error object, so we + * default to {@link message}. + */ + /* tslint:enable: max-line-length */ stacktrace: string; + + /* tslint:disable: max-line-length */ + /** + * Message, if available. (first tries + * [`Error.message`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/message), + * then defaults to stringifying) + */ + /* tslint:enable: max-line-length */ message: string; } +/** + * Sent errors, for future auditing purposes. + */ interface ISentErrorsRow extends IUnsentErrorsRow { + /** + * Date that error was sent. + */ dateSent: Date; } +/** + * Database manager for Conniebot. Uses SQLite. + */ export default class ConniebotDatabase { + /** + * Pending or completed database connection. + */ private db: Promise; + /** + * @param dbFile Filename of database file. Should be a `.sqlite` file. Relative to command + * directory. + */ constructor(dbFile: string) { if (!dbFile.endsWith(".sqlite")) { console.log("Database file is not marked as `.sqlite`."); @@ -28,6 +87,11 @@ export default class ConniebotDatabase { this.db = this.init(dbFile); } + /** + * Open a file and initialize the tables if they haven't already been created. + * + * @param fname Database filename. Relative to command directory. + */ private async init(fname: string) { const db = await sqlite.open(fname); @@ -65,7 +129,7 @@ export default class ConniebotDatabase { public async setChannel(event: string, channel: string) { return (await this.db).run( - SQL`INSERT INTO notifs(event, channel) VALUES(${event}, ${channel}) + SQL`INSERT INTO notifs(event, channel) VALUES(${event.substr(0, 50)}, ${channel}) ON CONFLICT(event) DO UPDATE SET channel=excluded.channel`, ); } @@ -81,6 +145,11 @@ export default class ConniebotDatabase { ); } + /** + * Migrate error to Sent Errors table, black-holing it if the ID already exists for some reason. + * + * @param id Error ID to migrate. + */ public async moveError(id: number) { const db = await this.db; diff --git a/src/embed.ts b/src/embed.ts index 0520849..17a578c 100644 --- a/src/embed.ts +++ b/src/embed.ts @@ -4,7 +4,7 @@ import { Channel, RichEmbed } from "discord.js"; import { isTextChannel } from "./utils"; /** - * Grabs body from RichEmbed, optionally discarding headers + * Grabs body from RichEmbed, optionally discarding headers. * * @param message Message to grab body text from * @param headersImportant Should keep headers? @@ -18,7 +18,7 @@ function handleBody(message: RichEmbed, headersImportant = false) { } /** - * Convert RichEmbed to String + * Convert RichEmbed to String. * * @param message Message to grab body text from * @param headersImportant Should keep headers? @@ -33,7 +33,7 @@ function strip(message: RichEmbed, headersImportant = false) { } /** - * Send message to channel + * Send message to channel. * * @param channel Channel to send message to * @param message Message to send diff --git a/src/startup.ts b/src/startup.ts index 2e93df5..c408c1e 100644 --- a/src/startup.ts +++ b/src/startup.ts @@ -27,6 +27,9 @@ async function notifyRestart(bot: Client, db: ConniebotDatabase) { } } +/** + * Notify channel of any new errors that haven't been able to send. + */ async function notifyNewErrors(bot: Client, db: ConniebotDatabase) { const [errors, errorChannelId] = await Promise.all( [db.getUnsentErrors(), db.getChannel("errors")], @@ -67,6 +70,9 @@ async function updateActivity(bot: Client) { } } +/** + * Run through {@link updateActivity}, {@link notifyRestart}, {@link notifyNewErrors}. + */ export default async function startup(bot: Client, db: ConniebotDatabase) { console.log("Bot ready. Setting up..."); await Promise.all([updateActivity, notifyRestart, notifyNewErrors].map(fn => fn(bot, db))); diff --git a/src/utils.ts b/src/utils.ts index 355cf4a..b987f8c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,7 +13,7 @@ export function logMessage(status: string, message?: any) { /** * Check if channel is a TextChannel. Technically it can be a guild, dm or group dm channel, but - * the default discord.js type for a text based channel is not actually a type and so must have + * the default discord.js type for a text based channel is not actually a type, so we have to have * this workaround. */ export function isTextChannel(channel: Channel): channel is TextChannel { From 59d7f95966b8cc77b152585ec5011c3a49dcc520 Mon Sep 17 00:00:00 2001 From: Shane Duan Date: Sun, 26 Aug 2018 17:21:58 -0700 Subject: [PATCH 08/11] Convert default match action to function --- src/x2i/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/x2i/index.ts b/src/x2i/index.ts index a90fac6..ddd4ee4 100644 --- a/src/x2i/index.ts +++ b/src/x2i/index.ts @@ -47,8 +47,6 @@ const regex = OuterXRegExp( (?=$|[\`\\p{White_Space}\\pP])`, "gmx"); -const defaultMatchAction = (left: string, match: string, right: string) => left + match + right; - const matchType: { [key: string]: IMatchInstructions } = { p: { join: (_, match) => `*${match}`, @@ -62,6 +60,10 @@ const matchType: { [key: string]: IMatchInstructions } = { }, }; +function defaultMatchAction(left: string, match: string, right: string) { + return left + match + right; +} + /** * Read translation keys from file. Escapes strings first. * From 2c3a60a0eda47e2a0ce77408eb7d9e246cd27a2e Mon Sep 17 00:00:00 2001 From: Shane Duan Date: Sun, 26 Aug 2018 18:39:05 -0700 Subject: [PATCH 09/11] Remove react plugin from tslint --- tslint.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/tslint.json b/tslint.json index c011757..700df1e 100644 --- a/tslint.json +++ b/tslint.json @@ -68,9 +68,6 @@ "es6": true, "node": true }, - "plugins": [ - "react" - ], "extends": [ "tslint:recommended" ] From d856fedb09022c5641d2691387da20ccc3a2b9cb Mon Sep 17 00:00:00 2001 From: gufferdk Date: Sun, 18 Nov 2018 21:00:15 +0100 Subject: [PATCH 10/11] Fix nareal fricatives Changed the substitution of _; from U+207F SUPERSCRIPT LATIN SMALL LETTER N to U+034B COMBINING HOMOTHETIC ABOVE to ensure correct parsing of nareal fricatives. --- x2i/z2i-keys.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x2i/z2i-keys.yaml b/x2i/z2i-keys.yaml index a477c16..48dd1fe 100644 --- a/x2i/z2i-keys.yaml +++ b/x2i/z2i-keys.yaml @@ -286,7 +286,7 @@ - - _/ - ̌ - - _; - - ⁿ + - ͋ - - _=\ - "˭" - - _= From f7f0fd49067db9321fea5501e665a96448f73e48 Mon Sep 17 00:00:00 2001 From: Shane Duan Date: Wed, 21 Nov 2018 16:50:04 -0800 Subject: [PATCH 11/11] 3.1.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e23d6f2..36b3aa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "conniebot", - "version": "3.1.0", + "version": "3.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3aedd44..f7c9c46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "conniebot", - "version": "3.1.0", + "version": "3.1.1", "license": "MIT", "repository": { "type": "git",