3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-15 05:41:51 +00:00
zeppelin/backend/src/index.ts

186 lines
6.2 KiB
TypeScript
Raw Normal View History

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;
import { Knub, logger, PluginError, pluginUtils } from "knub";
2018-12-14 06:27:41 +02:00
import { SimpleError } from "./SimpleError";
import { Configs } from "./data/Configs";
require("dotenv").config({ path: path.resolve(process.cwd(), "bot.env") });
2018-07-01 03:35:51 +03:00
declare global {
// This is here so TypeScript doesn't give an error when importing twemoji
// since one of the signatures of twemoji.parse() takes an HTMLElement but
// we're not in a browser environment so including the DOM lib would not make
// sense
type HTMLElement = unknown;
}
2019-05-07 19:54:16 +03:00
// Error handling
let recentPluginErrors = 0;
const RECENT_PLUGIN_ERROR_EXIT_THRESHOLD = 5;
let recentDiscordErrors = 0;
const RECENT_DISCORD_ERROR_EXIT_THRESHOLD = 5;
setInterval(() => (recentPluginErrors = Math.max(0, recentPluginErrors - 1)), 2500);
setInterval(() => (recentDiscordErrors = Math.max(0, recentDiscordErrors - 1)), 2500);
2018-07-01 03:35:51 +03:00
if (process.env.NODE_ENV === "production") {
const errorHandler = err => {
if (err instanceof RecoverablePluginError) {
// Recoverable plugin errors can be, well, recovered from.
// Log it in the console as a warning and post a warning to the guild's log.
// tslint:disable:no-console
console.warn(`${err.guild.name}: [${err.code}] ${err.message}`);
const logs = new GuildLogs(err.guild.id);
logs.log(LogType.BOT_ALERT, { body: `\`[${err.code}]\` ${err.message}` });
return;
}
// tslint:disable:no-console
console.error(err);
if (err instanceof PluginError) {
// Tolerate a few recent plugin errors before crashing
if (++recentPluginErrors >= RECENT_PLUGIN_ERROR_EXIT_THRESHOLD) {
console.error(`Exiting after ${RECENT_PLUGIN_ERROR_EXIT_THRESHOLD} plugin errors`);
process.exit(1);
}
} else if (isDiscordRESTError(err) || isDiscordHTTPError(err)) {
// Discord API errors, usually safe to just log instead of crash
// We still bail if we get a ton of them in a short amount of time
if (++recentDiscordErrors >= RECENT_DISCORD_ERROR_EXIT_THRESHOLD) {
console.error(`Exiting after ${RECENT_DISCORD_ERROR_EXIT_THRESHOLD} API errors`);
process.exit(1);
}
} else {
// On other errors, crash immediately
process.exit(1);
}
// tslint:enable:no-console
};
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 = "14.0.0";
2018-12-14 06:27:41 +02:00
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
import moment from "moment-timezone";
moment.tz.setDefault("UTC");
2020-06-30 17:48:18 +03:00
import { Client, TextChannel } from "eris";
import { connect } from "./data/db";
import { guildPlugins, globalPlugins } from "./plugins/availablePlugins";
import { errorMessage, isDiscordHTTPError, isDiscordRESTError, successMessage } from "./utils";
2019-04-23 05:58:50 +03:00
import { startUptimeCounter } from "./uptime";
import { AllowedGuilds } from "./data/AllowedGuilds";
import { IZeppelinGuildConfig, IZeppelinGlobalConfig } from "./types";
import { RecoverablePluginError } from "./RecoverablePluginError";
import { GuildLogs } from "./data/GuildLogs";
import { LogType } from "./data/LogType";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
2018-07-01 03:35:51 +03:00
logger.info("Connecting to database");
2020-06-30 17:48:18 +03:00
connect().then(async () => {
const client = new Client(`Bot ${process.env.TOKEN}`, {
getAllUsers: false,
restMode: true,
2018-07-31 20:58:48 +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 ")) {
2020-05-28 03:15:06 +03:00
logger.info(`[429] ${message}`);
2019-02-07 20:44:26 +02:00
}
});
const allowedGuilds = new AllowedGuilds();
const guildConfigs = new Configs();
const bot = new Knub<IZeppelinGuildConfig, IZeppelinGlobalConfig>(client, {
guildPlugins,
globalPlugins,
options: {
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 explicitly enabled in the guild config
* Dependencies are also automatically loaded by Knub.
2019-05-07 19:54:16 +03:00
*/
async getEnabledPlugins(this: Knub, guildId, guildConfig): Promise<string[]> {
const configuredPlugins = guildConfig.plugins || {};
const pluginNames: string[] = Array.from(this.guildPlugins.keys());
const plugins: ZeppelinPlugin[] = Array.from(this.guildPlugins.values());
return pluginNames.filter(pluginName => {
return configuredPlugins[pluginName] && configuredPlugins[pluginName].enabled !== false;
});
},
async getConfig(id) {
const key = id === "global" ? "global" : `guild-${id}`;
const row = await guildConfigs.getActiveByKey(key);
if (row) {
return yaml.safeLoad(row.config);
}
logger.warn(`No config with key "${key}"`);
return {};
},
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,
},
2020-01-12 22:19:10 +11:00
sendSuccessMessageFn(channel, body) {
const guildId = channel instanceof TextChannel ? channel.guild.id : undefined;
2020-06-30 17:48:18 +03:00
const emoji = guildId ? bot.getLoadedGuild(guildId).config.success_emoji : undefined;
2020-01-12 22:19:10 +11:00
channel.createMessage(successMessage(body, emoji));
},
sendErrorMessageFn(channel, body) {
const guildId = channel instanceof TextChannel ? channel.guild.id : undefined;
2020-06-30 17:48:18 +03:00
const emoji = guildId ? bot.getLoadedGuild(guildId).config.error_emoji : undefined;
channel.createMessage(errorMessage(body, emoji));
},
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();
});