diff options
author | TymanWasTaken <32660892+tymanwastaken@users.noreply.github.com> | 2021-04-27 21:06:22 -0600 |
---|---|---|
committer | TymanWasTaken <32660892+tymanwastaken@users.noreply.github.com> | 2021-04-27 21:06:22 -0600 |
commit | 763fb7d98c3accbb21adf035a7cf0a83cb9533c9 (patch) | |
tree | 9d333fbca2a2a8e19d79904a4e29226174925cfc /src/lib/extensions | |
download | tanzanite-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.ts | 274 | ||||
-rw-r--r-- | src/lib/extensions/BotCommand.ts | 6 | ||||
-rw-r--r-- | src/lib/extensions/BotGuild.ts | 38 | ||||
-rw-r--r-- | src/lib/extensions/BotInhibitor.ts | 6 | ||||
-rw-r--r-- | src/lib/extensions/BotListener.ts | 6 | ||||
-rw-r--r-- | src/lib/extensions/BotMessage.ts | 50 | ||||
-rw-r--r-- | src/lib/extensions/Util.ts | 196 |
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}`); + } +} |