aboutsummaryrefslogtreecommitdiff
path: root/src/lib/extensions
diff options
context:
space:
mode:
authorTymanWasTaken <32660892+tymanwastaken@users.noreply.github.com>2021-04-27 21:06:22 -0600
committerTymanWasTaken <32660892+tymanwastaken@users.noreply.github.com>2021-04-27 21:06:22 -0600
commit763fb7d98c3accbb21adf035a7cf0a83cb9533c9 (patch)
tree9d333fbca2a2a8e19d79904a4e29226174925cfc /src/lib/extensions
downloadtanzanite-763fb7d98c3accbb21adf035a7cf0a83cb9533c9.tar.gz
tanzanite-763fb7d98c3accbb21adf035a7cf0a83cb9533c9.tar.bz2
tanzanite-763fb7d98c3accbb21adf035a7cf0a83cb9533c9.zip
legit just copy utilibot v2 code
Diffstat (limited to 'src/lib/extensions')
-rw-r--r--src/lib/extensions/BotClient.ts274
-rw-r--r--src/lib/extensions/BotCommand.ts6
-rw-r--r--src/lib/extensions/BotGuild.ts38
-rw-r--r--src/lib/extensions/BotInhibitor.ts6
-rw-r--r--src/lib/extensions/BotListener.ts6
-rw-r--r--src/lib/extensions/BotMessage.ts50
-rw-r--r--src/lib/extensions/Util.ts196
7 files changed, 576 insertions, 0 deletions
diff --git a/src/lib/extensions/BotClient.ts b/src/lib/extensions/BotClient.ts
new file mode 100644
index 0000000..4d1c31a
--- /dev/null
+++ b/src/lib/extensions/BotClient.ts
@@ -0,0 +1,274 @@
+import {
+ AkairoClient,
+ CommandHandler,
+ InhibitorHandler,
+ ListenerHandler
+} from 'discord-akairo';
+import { Guild } from 'discord.js';
+import * as path from 'path';
+import { DataTypes, Model, Sequelize } from 'sequelize';
+import * as Models from '../types/Models';
+import { BotGuild } from './BotGuild';
+import { BotMessage } from './BotMessage';
+import { Util } from './Util';
+import * as Tasks from '../../tasks';
+import { v4 as uuidv4 } from 'uuid';
+import { exit } from 'process';
+import { TopGGHandler } from '../utils/TopGG';
+
+export interface BotConfig {
+ credentials: {
+ botToken: string;
+ dblToken: string;
+ dblWebhookAuth: string;
+ };
+ owners: string[];
+ prefix: string;
+ dev: boolean;
+ db: {
+ username: string;
+ password: string;
+ host: string;
+ port: number;
+ };
+ topGGPort: number;
+ channels: {
+ dblVote: string;
+ log: string;
+ error: string;
+ dm: string;
+ command: string;
+ };
+}
+
+export class BotClient extends AkairoClient {
+ public config: BotConfig;
+ public listenerHandler: ListenerHandler;
+ public inhibitorHandler: InhibitorHandler;
+ public commandHandler: CommandHandler;
+ public topGGHandler: TopGGHandler;
+ public util: Util;
+ public ownerID: string[];
+ public db: Sequelize;
+ constructor(config: BotConfig) {
+ super(
+ {
+ ownerID: config.owners
+ },
+ {
+ allowedMentions: { parse: ['users'] } // No everyone or role mentions by default
+ }
+ );
+
+ // Set token
+ this.token = config.credentials.botToken;
+
+ // Set config
+ this.config = config;
+
+ // Create listener handler
+ this.listenerHandler = new ListenerHandler(this, {
+ directory: path.join(__dirname, '..', '..', 'listeners'),
+ automateCategories: true
+ });
+
+ // Create inhibitor handler
+ this.inhibitorHandler = new InhibitorHandler(this, {
+ directory: path.join(__dirname, '..', '..', 'inhibitors'),
+ automateCategories: true
+ });
+
+ // Create command handler
+ this.commandHandler = new CommandHandler(this, {
+ directory: path.join(__dirname, '..', '..', 'commands'),
+ prefix: async ({ guild }: { guild: Guild }) => {
+ const row = await Models.Guild.findByPk(guild.id);
+ if (!row) return this.config.prefix;
+ return row.prefix as string;
+ },
+ allowMention: true,
+ handleEdits: true,
+ commandUtil: true,
+ commandUtilLifetime: 3e5,
+ argumentDefaults: {
+ prompt: {
+ timeout: 'Timed out.',
+ ended: 'Too many tries.',
+ cancel: 'Canceled.',
+ time: 3e4
+ }
+ },
+ ignorePermissions: this.config.owners,
+ ignoreCooldown: this.config.owners,
+ automateCategories: true
+ });
+
+ this.util = new Util(this);
+ this.db = new Sequelize(
+ this.config.dev ? 'utilibot-dev' : 'utilibot',
+ this.config.db.username,
+ this.config.db.password,
+ {
+ dialect: 'postgres',
+ host: this.config.db.host,
+ port: this.config.db.port,
+ logging: false
+ }
+ );
+ this.topGGHandler = new TopGGHandler(this);
+ BotGuild.install();
+ BotMessage.install();
+ }
+
+ // Initialize everything
+ private async _init(): Promise<void> {
+ this.commandHandler.useListenerHandler(this.listenerHandler);
+ this.commandHandler.useInhibitorHandler(this.inhibitorHandler);
+ this.listenerHandler.setEmitters({
+ commandHandler: this.commandHandler,
+ listenerHandler: this.listenerHandler,
+ process
+ });
+ // loads all the handlers
+ const loaders = {
+ commands: this.commandHandler,
+ listeners: this.listenerHandler,
+ inhibitors: this.inhibitorHandler
+ };
+ for (const loader of Object.keys(loaders)) {
+ try {
+ loaders[loader].loadAll();
+ console.log('Successfully loaded ' + loader + '.');
+ } catch (e) {
+ console.error('Unable to load loader ' + loader + ' with error ' + e);
+ }
+ }
+ await this.dbPreInit();
+ Object.keys(Tasks).forEach((t) => {
+ setInterval(() => Tasks[t](this), 60000);
+ });
+ this.topGGHandler.init();
+ }
+
+ public async dbPreInit(): Promise<void> {
+ await this.db.authenticate();
+ Models.Guild.init(
+ {
+ id: {
+ type: DataTypes.STRING,
+ primaryKey: true
+ },
+ prefix: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ defaultValue: this.config.prefix
+ }
+ },
+ { sequelize: this.db }
+ );
+ Models.Modlog.init(
+ {
+ id: {
+ type: DataTypes.STRING,
+ primaryKey: true,
+ allowNull: false,
+ defaultValue: uuidv4
+ },
+ type: {
+ type: new DataTypes.ENUM(
+ 'BAN',
+ 'TEMPBAN',
+ 'MUTE',
+ 'TEMPMUTE',
+ 'KICK',
+ 'WARN'
+ ),
+ allowNull: false
+ },
+ user: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ moderator: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ duration: {
+ type: DataTypes.STRING,
+ allowNull: true
+ },
+ reason: {
+ type: DataTypes.STRING,
+ allowNull: true
+ },
+ guild: {
+ type: DataTypes.STRING,
+ references: {
+ model: Models.Guild as typeof Model
+ }
+ }
+ },
+ { sequelize: this.db }
+ );
+ Models.Ban.init(
+ {
+ id: {
+ type: DataTypes.STRING,
+ primaryKey: true,
+ allowNull: false,
+ defaultValue: uuidv4
+ },
+ user: {
+ type: DataTypes.STRING,
+ allowNull: false
+ },
+ guild: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ references: {
+ model: Models.Guild as typeof Model,
+ key: 'id'
+ }
+ },
+ expires: {
+ type: DataTypes.DATE,
+ allowNull: true
+ },
+ reason: {
+ type: DataTypes.STRING,
+ allowNull: true
+ },
+ modlog: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ references: {
+ model: Models.Modlog as typeof Model
+ }
+ }
+ },
+ { sequelize: this.db }
+ );
+ try {
+ await this.db.sync({ alter: true }); // Sync all tables to fix everything if updated
+ } catch {
+ // Ignore error
+ }
+ }
+
+ public async start(): Promise<void> {
+ try {
+ await this._init();
+ await this.login(this.token);
+ } catch (e) {
+ console.error(e.stack);
+ exit(2);
+ }
+ }
+
+ public destroy(relogin = true): void | Promise<string> {
+ super.destroy();
+ if (relogin) {
+ return this.login(this.token);
+ }
+ }
+}
diff --git a/src/lib/extensions/BotCommand.ts b/src/lib/extensions/BotCommand.ts
new file mode 100644
index 0000000..4f62714
--- /dev/null
+++ b/src/lib/extensions/BotCommand.ts
@@ -0,0 +1,6 @@
+import { Command } from 'discord-akairo';
+import { BotClient } from './BotClient';
+
+export class BotCommand extends Command {
+ public client: BotClient;
+}
diff --git a/src/lib/extensions/BotGuild.ts b/src/lib/extensions/BotGuild.ts
new file mode 100644
index 0000000..22d7834
--- /dev/null
+++ b/src/lib/extensions/BotGuild.ts
@@ -0,0 +1,38 @@
+import { Guild, Structures } from 'discord.js';
+import { BotClient } from './BotClient';
+import { Guild as GuildModel } from '../types/Models';
+
+export class GuildSettings {
+ private guild: BotGuild;
+ constructor(guild: BotGuild) {
+ this.guild = guild;
+ }
+ public async getPrefix(): Promise<string> {
+ return await GuildModel.findByPk(this.guild.id).then(
+ (gm) => gm?.prefix || this.guild.client.config.prefix
+ );
+ }
+ public async setPrefix(value: string): Promise<void> {
+ let entry = await GuildModel.findByPk(this.guild.id);
+ if (!entry) {
+ entry = GuildModel.build({
+ id: this.guild.id,
+ prefix: value
+ });
+ } else {
+ entry.prefix = value;
+ }
+ await entry.save();
+ }
+}
+
+export class BotGuild extends Guild {
+ constructor(client: BotClient, data: Record<string, unknown>) {
+ super(client, data);
+ }
+ static install(): void {
+ Structures.extend('Guild', () => BotGuild);
+ }
+ public settings = new GuildSettings(this);
+ public client: BotClient;
+}
diff --git a/src/lib/extensions/BotInhibitor.ts b/src/lib/extensions/BotInhibitor.ts
new file mode 100644
index 0000000..960aade
--- /dev/null
+++ b/src/lib/extensions/BotInhibitor.ts
@@ -0,0 +1,6 @@
+import { Inhibitor } from 'discord-akairo';
+import { BotClient } from './BotClient';
+
+export class BotInhibitor extends Inhibitor {
+ public client: BotClient;
+}
diff --git a/src/lib/extensions/BotListener.ts b/src/lib/extensions/BotListener.ts
new file mode 100644
index 0000000..9ec17b2
--- /dev/null
+++ b/src/lib/extensions/BotListener.ts
@@ -0,0 +1,6 @@
+import { Listener } from 'discord-akairo';
+import { BotClient } from './BotClient';
+
+export class BotListener extends Listener {
+ public client: BotClient;
+}
diff --git a/src/lib/extensions/BotMessage.ts b/src/lib/extensions/BotMessage.ts
new file mode 100644
index 0000000..85c2721
--- /dev/null
+++ b/src/lib/extensions/BotMessage.ts
@@ -0,0 +1,50 @@
+import {
+ TextChannel,
+ NewsChannel,
+ DMChannel,
+ Message,
+ Structures
+} from 'discord.js';
+import { BotClient } from './BotClient';
+import { Guild as GuildModel } from '../types/Models';
+import { BotGuild } from './BotGuild';
+
+export class GuildSettings {
+ private message: BotMessage;
+ constructor(message: BotMessage) {
+ this.message = message;
+ }
+ public async getPrefix(): Promise<string> {
+ return await GuildModel.findByPk(this.message.guild.id).then(
+ (gm) => gm?.prefix || this.message.client.config.prefix
+ );
+ }
+ public async setPrefix(value: string): Promise<void> {
+ let entry = await GuildModel.findByPk(this.message.guild.id);
+ if (!entry) {
+ entry = GuildModel.build({
+ id: this.message.guild.id,
+ prefix: value
+ });
+ } else {
+ entry.prefix = value;
+ }
+ await entry.save();
+ }
+}
+
+export class BotMessage extends Message {
+ constructor(
+ client: BotClient,
+ data: Record<string, unknown>,
+ channel: TextChannel | DMChannel | NewsChannel
+ ) {
+ super(client, data, channel);
+ }
+ public guild: BotGuild;
+ public client: BotClient;
+ static install(): void {
+ Structures.extend('Message', () => BotMessage);
+ }
+ public settings = new GuildSettings(this);
+}
diff --git a/src/lib/extensions/Util.ts b/src/lib/extensions/Util.ts
new file mode 100644
index 0000000..20bfd48
--- /dev/null
+++ b/src/lib/extensions/Util.ts
@@ -0,0 +1,196 @@
+import { ClientUtil } from 'discord-akairo';
+import { BotClient } from './BotClient';
+import { User } from 'discord.js';
+import { promisify } from 'util';
+import { exec } from 'child_process';
+import got from 'got';
+import { TextChannel } from 'discord.js';
+
+interface hastebinRes {
+ key: string;
+}
+
+export class Util extends ClientUtil {
+ /**
+ * The client of this ClientUtil
+ * @type {BotClient}
+ */
+ public client: BotClient;
+ /**
+ * The hastebin urls used to post to hastebin, attempts to post in order
+ * @type {string[]}
+ */
+ public hasteURLs = [
+ 'https://hst.sh',
+ 'https://hasteb.in',
+ 'https://hastebin.com',
+ 'https://mystb.in',
+ 'https://haste.clicksminuteper.net',
+ 'https://paste.pythondiscord.com',
+ 'https://haste.unbelievaboat.com',
+ 'https://haste.tyman.tech'
+ ];
+ /**
+ * A simple promise exec method
+ */
+ private exec = promisify(exec);
+
+ /**
+ * Creates this client util
+ * @param client The client to initialize with
+ */
+ constructor(client: BotClient) {
+ super(client);
+ }
+
+ /**
+ * Maps an array of user ids to user objects.
+ * @param ids The list of IDs to map
+ * @returns The list of users mapped
+ */
+ public async mapIDs(ids: string[]): Promise<User[]> {
+ return await Promise.all(ids.map((id) => this.client.users.fetch(id)));
+ }
+
+ /**
+ * Capitalizes the first letter of the given text
+ * @param text The text to capitalize
+ * @returns The capitalized text
+ */
+ public capitalize(text: string): string {
+ return text.charAt(0).toUpperCase() + text.slice(1);
+ }
+
+ /**
+ * Runs a shell command and gives the output
+ * @param command The shell command to run
+ * @returns The stdout and stderr of the shell command
+ */
+ public async shell(
+ command: string
+ ): Promise<{
+ stdout: string;
+ stderr: string;
+ }> {
+ return await this.exec(command);
+ }
+
+ /**
+ * Posts text to hastebin
+ * @param content The text to post
+ * @returns The url of the posted text
+ */
+ public async haste(content: string): Promise<string> {
+ for (const url of this.hasteURLs) {
+ try {
+ const res: hastebinRes = await got
+ .post(`${url}/documents`, { body: content })
+ .json();
+ return `${url}/${res.key}`;
+ } catch (e) {
+ // pass
+ }
+ }
+ throw new Error('No urls worked. (wtf)');
+ }
+
+ /**
+ * Logs something but only in dev mode
+ * @param content The thing to log
+ */
+ public devLog(content: unknown): void {
+ if (this.client.config.dev) console.log(content);
+ }
+
+ /**
+ * Resolves a user-provided string into a user object, if possible
+ * @param text The text to try and resolve
+ * @returns The user resolved or null
+ */
+ public async resolveUserAsync(text: string): Promise<User | null> {
+ const idReg = /\d{17,19}/;
+ const idMatch = text.match(idReg);
+ if (idMatch) {
+ try {
+ const user = await this.client.users.fetch(text);
+ return user;
+ } catch {
+ // pass
+ }
+ }
+ const mentionReg = /<@!?(?<id>\d{17,19})>/;
+ const mentionMatch = text.match(mentionReg);
+ if (mentionMatch) {
+ try {
+ const user = await this.client.users.fetch(mentionMatch.groups.id);
+ return user;
+ } catch {
+ // pass
+ }
+ }
+ const user = this.client.users.cache.find((u) => u.username === text);
+ if (user) return user;
+ return null;
+ }
+
+ /**
+ * Appends the correct ordinal to the given number
+ * @param n The number to append an ordinal to
+ * @returns The number with the ordinal
+ */
+ public ordinal(n: number): string {
+ const s = ['th', 'st', 'nd', 'rd'],
+ v = n % 100;
+ return n + (s[(v - 20) % 10] || s[v] || s[0]);
+ }
+
+ /**
+ * Chunks an array to the specified size
+ * @param arr The array to chunk
+ * @param perChunk The amount of items per chunk
+ * @returns The chunked array
+ */
+ public chunk<T>(arr: T[], perChunk: number): T[][] {
+ return arr.reduce((all, one, i) => {
+ const ch = Math.floor(i / perChunk);
+ all[ch] = [].concat(all[ch] || [], one);
+ return all;
+ }, []);
+ }
+
+ /**
+ * Logs a message to console and log channel as info
+ * @param message The message to send
+ */
+ public async info(message: string): Promise<void> {
+ console.log(`INFO: ${message}`);
+ const channel = (await this.client.channels.fetch(
+ this.client.config.channels.log
+ )) as TextChannel;
+ await channel.send(`INFO: ${message}`);
+ }
+
+ /**
+ * Logs a message to console and log channel as a warning
+ * @param message The message to send
+ */
+ public async warn(message: string): Promise<void> {
+ console.warn(`WARN: ${message}`);
+ const channel = (await this.client.channels.fetch(
+ this.client.config.channels.log
+ )) as TextChannel;
+ await channel.send(`WARN: ${message}`);
+ }
+
+ /**
+ * Logs a message to console and log channel as an error
+ * @param message The message to send
+ */
+ public async error(message: string): Promise<void> {
+ console.error(`ERROR: ${message}`);
+ const channel = (await this.client.channels.fetch(
+ this.client.config.channels.error
+ )) as TextChannel;
+ await channel.send(`ERROR: ${message}`);
+ }
+}