2018-10-26 06:41:20 +03:00
|
|
|
import path from "path";
|
|
|
|
import yaml from "js-yaml";
|
|
|
|
|
2019-01-15 01:07:02 +02:00
|
|
|
import fs from "fs";
|
|
|
|
const fsp = fs.promises;
|
2018-10-26 06:41:20 +03:00
|
|
|
|
2019-04-20 19:03:30 +03:00
|
|
|
import { Knub, logger, PluginError, Plugin } from "knub";
|
2018-12-14 06:27:41 +02:00
|
|
|
import { SimpleError } from "./SimpleError";
|
|
|
|
|
2019-04-23 05:29:53 +03:00
|
|
|
import DiscordRESTError from "eris/lib/errors/DiscordRESTError"; // tslint:disable-line
|
|
|
|
import DiscordHTTPError from "eris/lib/errors/DiscordHTTPError"; // tslint:disable-line
|
|
|
|
|
2019-06-22 18:52:24 +03:00
|
|
|
import { Configs } from "./data/Configs";
|
|
|
|
|
2019-07-21 14:37:46 +03:00
|
|
|
require("dotenv").config({ path: path.resolve(__dirname, "..", "bot.env") });
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2019-05-07 19:54:16 +03:00
|
|
|
// Error handling
|
2019-01-15 01:05:12 +02:00
|
|
|
let recentPluginErrors = 0;
|
2019-04-23 05:29:53 +03:00
|
|
|
const RECENT_PLUGIN_ERROR_EXIT_THRESHOLD = 5;
|
2019-01-13 16:52:00 +02:00
|
|
|
|
2019-04-23 05:29:53 +03:00
|
|
|
let recentDiscordErrors = 0;
|
|
|
|
const RECENT_DISCORD_ERROR_EXIT_THRESHOLD = 5;
|
2019-01-15 01:05:12 +02:00
|
|
|
|
2019-04-23 05:29:53 +03:00
|
|
|
setInterval(() => (recentPluginErrors = Math.max(0, recentPluginErrors - 1)), 2500);
|
|
|
|
setInterval(() => (recentDiscordErrors = Math.max(0, recentDiscordErrors - 1)), 2500);
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2019-04-23 05:29:53 +03:00
|
|
|
function errorHandler(err) {
|
2019-05-07 19:54:16 +03:00
|
|
|
// tslint:disable:no-console
|
2019-01-13 16:52:00 +02:00
|
|
|
console.error(err);
|
2019-01-15 01:05:12 +02:00
|
|
|
|
|
|
|
if (err instanceof PluginError) {
|
2019-04-23 05:29:53 +03:00
|
|
|
// Tolerate a few recent plugin errors before crashing
|
2019-04-23 05:38:48 +03:00
|
|
|
if (++recentPluginErrors >= RECENT_PLUGIN_ERROR_EXIT_THRESHOLD) {
|
|
|
|
console.error(`Exiting after ${RECENT_PLUGIN_ERROR_EXIT_THRESHOLD} plugin errors`);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
2019-04-23 05:29:53 +03:00
|
|
|
} else if (err instanceof DiscordRESTError || err instanceof DiscordHTTPError) {
|
2019-05-07 19:54:16 +03:00
|
|
|
// Discord API errors, usually safe to just log instead of crash
|
2019-04-23 05:29:53 +03:00
|
|
|
// We still bail if we get a ton of them in a short amount of time
|
2019-04-23 05:38:48 +03:00
|
|
|
if (++recentDiscordErrors >= RECENT_DISCORD_ERROR_EXIT_THRESHOLD) {
|
|
|
|
console.error(`Exiting after ${RECENT_DISCORD_ERROR_EXIT_THRESHOLD} API errors`);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
2019-01-15 01:05:12 +02:00
|
|
|
} else {
|
2019-04-23 05:29:53 +03:00
|
|
|
// On other errors, crash immediately
|
2019-01-15 01:05:12 +02:00
|
|
|
process.exit(1);
|
|
|
|
}
|
2019-05-07 19:54:16 +03:00
|
|
|
// tslint:enable:no-console
|
2019-04-23 05:29:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
process.on("unhandledRejection", errorHandler);
|
|
|
|
process.on("uncaughtException", errorHandler);
|
2018-08-03 19:25:00 +03:00
|
|
|
|
2018-12-14 06:27:41 +02:00
|
|
|
// Verify required Node.js version
|
|
|
|
const REQUIRED_NODE_VERSION = "10.14.2";
|
|
|
|
const requiredParts = REQUIRED_NODE_VERSION.split(".").map(v => parseInt(v, 10));
|
|
|
|
const actualVersionParts = process.versions.node.split(".").map(v => parseInt(v, 10));
|
|
|
|
for (const [i, part] of actualVersionParts.entries()) {
|
2019-01-03 03:47:52 +02:00
|
|
|
if (part > requiredParts[i]) break;
|
|
|
|
if (part === requiredParts[i]) continue;
|
|
|
|
throw new SimpleError(`Unsupported Node.js version! Must be at least ${REQUIRED_NODE_VERSION}`);
|
2018-12-14 06:27:41 +02:00
|
|
|
}
|
|
|
|
|
2019-05-07 19:54:16 +03:00
|
|
|
// Always use UTC internally
|
|
|
|
// This is also enforced for the database in data/db.ts
|
2018-08-02 00:48:48 +03:00
|
|
|
import moment from "moment-timezone";
|
2018-07-07 15:39:56 +03:00
|
|
|
moment.tz.setDefault("UTC");
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
import { Client } from "eris";
|
2018-10-26 06:41:20 +03:00
|
|
|
import { connect } from "./data/db";
|
2019-05-25 14:40:00 +03:00
|
|
|
import { availablePlugins, availableGlobalPlugins, basePlugins } from "./plugins/availablePlugins";
|
2019-04-20 17:36:28 +03:00
|
|
|
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
|
2019-04-20 19:03:30 +03:00
|
|
|
import { customArgumentTypes } from "./customArgumentTypes";
|
2019-05-25 14:40:00 +03:00
|
|
|
import { errorMessage, successMessage } from "./utils";
|
2019-04-23 05:58:50 +03:00
|
|
|
import { startUptimeCounter } from "./uptime";
|
2019-06-22 18:52:24 +03:00
|
|
|
import { AllowedGuilds } from "./data/AllowedGuilds";
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2019-05-25 21:23:09 +03:00
|
|
|
logger.info("Connecting to database");
|
2018-10-26 06:41:20 +03:00
|
|
|
connect().then(async conn => {
|
2019-04-13 03:17:09 +03:00
|
|
|
const client = new Client(`Bot ${process.env.TOKEN}`, {
|
2019-05-02 17:46:04 +03:00
|
|
|
getAllUsers: true,
|
2019-04-13 03:17:09 +03:00
|
|
|
restMode: true,
|
2018-07-31 20:58:48 +03:00
|
|
|
});
|
2018-07-31 02:42:45 +03:00
|
|
|
client.setMaxListeners(100);
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2019-02-07 20:44:26 +02:00
|
|
|
client.on("debug", message => {
|
|
|
|
if (message.includes(" 429 ")) {
|
|
|
|
logger.info(`[RATELIMITED] ${message}`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-06-22 18:52:24 +03:00
|
|
|
const allowedGuilds = new AllowedGuilds();
|
|
|
|
const guildConfigs = new Configs();
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
const bot = new Knub(client, {
|
2019-05-25 14:40:00 +03:00
|
|
|
plugins: availablePlugins,
|
|
|
|
globalPlugins: availableGlobalPlugins,
|
2018-07-31 19:00:17 +03:00
|
|
|
|
|
|
|
options: {
|
2019-06-22 18:52:24 +03:00
|
|
|
canLoadGuild(guildId): Promise<boolean> {
|
|
|
|
return allowedGuilds.isAllowed(guildId);
|
|
|
|
},
|
|
|
|
|
2019-05-07 19:54:16 +03:00
|
|
|
/**
|
|
|
|
* Plugins are enabled if they...
|
|
|
|
* - are base plugins, i.e. always enabled, or
|
|
|
|
* - are dependencies of other enabled plugins, or
|
|
|
|
* - are explicitly enabled in the guild config
|
|
|
|
*/
|
2019-06-22 18:52:24 +03:00
|
|
|
async getEnabledPlugins(guildId, guildConfig): Promise<string[]> {
|
2019-04-20 17:36:28 +03:00
|
|
|
const configuredPlugins = guildConfig.plugins || {};
|
|
|
|
const pluginNames: string[] = Array.from(this.plugins.keys());
|
|
|
|
const plugins: Array<typeof Plugin> = Array.from(this.plugins.values());
|
|
|
|
const zeppelinPlugins: Array<typeof ZeppelinPlugin> = plugins.filter(
|
|
|
|
p => p.prototype instanceof ZeppelinPlugin,
|
|
|
|
) as Array<typeof ZeppelinPlugin>;
|
|
|
|
|
|
|
|
const enabledBasePlugins = pluginNames.filter(n => basePlugins.includes(n));
|
|
|
|
const explicitlyEnabledPlugins = pluginNames.filter(pluginName => {
|
|
|
|
return configuredPlugins[pluginName] && configuredPlugins[pluginName].enabled !== false;
|
2018-07-31 19:00:17 +03:00
|
|
|
});
|
2019-04-20 17:36:28 +03:00
|
|
|
const enabledPlugins = new Set([...enabledBasePlugins, ...explicitlyEnabledPlugins]);
|
|
|
|
|
|
|
|
const pluginsEnabledAsDependencies = zeppelinPlugins.reduce((arr, pluginClass) => {
|
|
|
|
if (!enabledPlugins.has(pluginClass.pluginName)) return arr;
|
|
|
|
return arr.concat(pluginClass.dependencies);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const finalEnabledPlugins = new Set([
|
|
|
|
...basePlugins,
|
|
|
|
...pluginsEnabledAsDependencies,
|
|
|
|
...explicitlyEnabledPlugins,
|
|
|
|
]);
|
|
|
|
return Array.from(finalEnabledPlugins.values());
|
2018-10-26 06:41:20 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
async getConfig(id) {
|
2019-06-22 18:52:24 +03:00
|
|
|
const key = id === "global" ? "global" : `guild-${id}`;
|
|
|
|
const row = await guildConfigs.getActiveByKey(key);
|
|
|
|
if (row) {
|
|
|
|
return yaml.safeLoad(row.config);
|
2018-10-26 06:41:20 +03:00
|
|
|
}
|
|
|
|
|
2019-06-22 18:52:24 +03:00
|
|
|
logger.warn(`No config with key "${key}"`);
|
|
|
|
return {};
|
2018-10-26 06:41:20 +03:00
|
|
|
},
|
|
|
|
|
2019-05-04 10:58:57 +03:00
|
|
|
logFn: (level, msg) => {
|
|
|
|
if (level === "debug") return;
|
|
|
|
// tslint:disable-next-line
|
|
|
|
console.log(`[${level.toUpperCase()}] ${msg}`);
|
|
|
|
},
|
|
|
|
|
2019-02-08 21:04:04 +02:00
|
|
|
performanceDebug: {
|
2019-05-04 10:52:59 +03:00
|
|
|
enabled: false,
|
2019-02-08 21:04:04 +02:00
|
|
|
size: 30,
|
2019-02-19 00:02:46 +02:00
|
|
|
threshold: 200,
|
|
|
|
},
|
2019-04-05 20:05:37 +03:00
|
|
|
|
2019-04-20 19:03:30 +03:00
|
|
|
customArgumentTypes,
|
2019-04-19 12:25:25 +03:00
|
|
|
|
|
|
|
sendSuccessMessageFn(channel, body) {
|
|
|
|
channel.createMessage(successMessage(body));
|
|
|
|
},
|
|
|
|
|
|
|
|
sendErrorMessageFn(channel, body) {
|
|
|
|
channel.createMessage(errorMessage(body));
|
|
|
|
},
|
2019-02-19 00:02:46 +02:00
|
|
|
},
|
2018-07-01 03:35:51 +03:00
|
|
|
});
|
|
|
|
|
2019-04-23 05:58:50 +03:00
|
|
|
client.once("ready", () => {
|
|
|
|
startUptimeCounter();
|
|
|
|
});
|
|
|
|
|
2018-07-01 04:31:24 +03:00
|
|
|
logger.info("Starting the bot");
|
2018-07-01 03:35:51 +03:00
|
|
|
bot.run();
|
|
|
|
});
|