Skip to content

Commit

Permalink
Reformat so src package is self-contained
Browse files Browse the repository at this point in the history
this isn't a library in any case
  • Loading branch information
xsduan committed Nov 25, 2018
1 parent 431aa01 commit b401460
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 175 deletions.
18 changes: 0 additions & 18 deletions index.ts

This file was deleted.

157 changes: 157 additions & 0 deletions src/conniebot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import c from "config";
import { Client, ClientOptions, Message, RichEmbed } from "discord.js";
import process from "process";
import OuterXRegExp from "xregexp";

import embed from "./embed";
import ConniebotDatabase from "./helper/db-management";
import startup from "./helper/startup";
import { log, messageSummary } from "./helper/utils";
import x2i from "./x2i";

export type CommandCallback =
(this: Conniebot, message: Message, ...args: string[]) => Promise<any>;

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) {
log("verbose", "Starting to load bot...");

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)
.on("error", err => {
if (err && err.message && err.message.includes("ECONNRESET")) {
return log("warn", "connection reset. oops!");
}
this.panicResponsibly(err);
})
.login(token);

process.once("uncaughtException", this.panicResponsibly);
}

/**
* 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) => {
log("error", err);
await this.db.addError(err);
if (exit) {
process.exit(1);
}
}

/**
* 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*) ?(.*)`, [],
);

const toks = message.content.match(prefixRegex);
if (!toks) return;
const [, cmd, args] = toks;

// 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 logItem = await cb(message, ...args.split(" "));
log(`success:command/${cmd}`, logItem);
} catch (err) {
log(`error:command/${cmd}`, err);
}
}

/**
* 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);

const respond = (stat: string, ...ms: any[]) =>
log(`${stat}:x2i/${logCode}`, messageSummary(message), ...ms);

try {
await embed(message.channel, response);
respond("success");
} catch (err) {
respond("error", err);
}
}

return parsed;
}

/**
* 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);
}

/**
* Register multiple commands at once.
*/
public registerCommands(callbacks: ICommands) {
for (const [name, cmd] of Object.entries(callbacks)) {
this.register(name, cmd);
}
}

/**
* 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);
}
}
2 changes: 1 addition & 1 deletion src/embed.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import c from "config";
import { Channel, RichEmbed } from "discord.js";

import { isTextChannel } from "./utils";
import { isTextChannel } from "./helper/utils";

/**
* Grabs body from RichEmbed, optionally discarding headers.
Expand Down
2 changes: 1 addition & 1 deletion src/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import c from "config";
import { Channel, RichEmbed, User } from "discord.js";

import embed from "./embed";
import { isTextChannel } from "./utils";
import { isTextChannel } from "./helper/utils";

const helpMessage: [string, string][] = [
["x,z,p[phonetic] or x,z,p/phonemic/",
Expand Down
4 changes: 2 additions & 2 deletions src/commands.ts → src/helper/commands.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import c from "config";

import { ICommands } from ".";
import help from "./help";
import { ICommands } from "../conniebot";
import help from "../help";
import { log } from "./utils";

/**
Expand Down
File renamed without changes.
File renamed without changes.
7 changes: 6 additions & 1 deletion src/utils.ts → src/helper/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { Channel, Message, TextChannel } from "discord.js";
import npmlog from "npmlog";

// init log style
Object.defineProperty(npmlog, "heading", { get: () => `[${new Date().toISOString()}]` });
Object.defineProperty(npmlog, "heading", {
get: () => `[${new Date().toISOString()}]`,
/* tslint:disable:no-empty */
set: () => {}, // ignore sets since we just need it to be a timestamp
/* tslint:enable:no-empty */
});
npmlog.headingStyle = { fg: "blue" };
npmlog.levels = new Proxy(npmlog.levels, {
get: (o, k) => o[k] || o.info,
Expand Down
161 changes: 11 additions & 150 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,157 +1,18 @@
import c from "config";
import { Client, ClientOptions, Message, RichEmbed } from "discord.js";
import process from "process";
import OuterXRegExp from "xregexp";

import ConniebotDatabase from "./db-management";
import embed from "./embed";
import startup from "./startup";
import { log, messageSummary } from "./utils";
import x2i from "./x2i";
import Conniebot from "./conniebot";
import commands from "./helper/commands";

export type CommandCallback =
(this: Conniebot, message: Message, ...args: string[]) => Promise<any>;
const token = "token";
const database = "database";

export interface ICommands {
[key: string]: CommandCallback;
if (!c.has(token)) {
throw new TypeError("Couldn't find a token to connect with.");
}

export default class Conniebot {
public bot: Client;
public db: ConniebotDatabase;
private commands: ICommands;

constructor(token: string, dbFile: string, clientOptions?: ClientOptions) {
log("verbose", "Starting to load bot...");

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)
.on("error", err => {
if (err && err.message && err.message.includes("ECONNRESET")) {
return log("warn", "connection reset. oops!");
}
this.panicResponsibly(err);
})
.login(token);

process.once("uncaughtException", this.panicResponsibly);
}

/**
* 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) => {
log("error", err);
await this.db.addError(err);
if (exit) {
process.exit(1);
}
}

/**
* 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*) ?(.*)`, [],
);

const toks = message.content.match(prefixRegex);
if (!toks) return;
const [, cmd, args] = toks;

// 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 logItem = await cb(message, ...args.split(" "));
log(`success:command/${cmd}`, logItem);
} catch (err) {
log(`error:command/${cmd}`, err);
}
}

/**
* 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);

const respond = (stat: string, ...ms: any[]) =>
log(`${stat}:x2i/${logCode}`, messageSummary(message), ...ms);

try {
await embed(message.channel, response);
respond("success");
} catch (err) {
respond("error", err);
}
}

return parsed;
}

/**
* 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);
}

/**
* Register multiple commands at once.
*/
public registerCommands(callbacks: ICommands) {
for (const [name, cmd] of Object.entries(callbacks)) {
this.register(name, cmd);
}
}

/**
* 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);
}
if (!c.has(database)) {
throw new TypeError("No database filename listed.");
}

const conniebot = new Conniebot(c.get(token), c.get(database));
conniebot.registerCommands(commands);
2 changes: 1 addition & 1 deletion src/x2i.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import c from "config";
import yaml from "js-yaml";
import OuterXRegExp from "xregexp";

import { log, resolveDatapath } from "./utils";
import { log, resolveDatapath } from "./helper/utils";

interface IRawReplaceKey {
raw: ReplaceKey;
Expand Down
Loading

0 comments on commit b401460

Please sign in to comment.