diff options
Diffstat (limited to 'src')
32 files changed, 254 insertions, 468 deletions
diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts index 0de0aa1..f8ba7e1 100644 --- a/src/commands/dev/eval.ts +++ b/src/commands/dev/eval.ts @@ -181,7 +181,7 @@ export default class EvalCommand extends BushCommand { members = message.guild?.members, roles = message.guild?.roles, client = this.client, - { Ban, Global, Guild, Level, ModLog, StickyRole } = await import('@lib'), + { ActivePunishment, Global, Guild, Level, ModLog, StickyRole } = await import('@lib'), { ButtonInteraction, Collector, diff --git a/src/commands/dev/servers.ts b/src/commands/dev/servers.ts index 08f74e8..3986451 100644 --- a/src/commands/dev/servers.ts +++ b/src/commands/dev/servers.ts @@ -4,7 +4,7 @@ import { BushCommand, BushMessage, BushSlashMessage } from '../../lib'; export default class ServersCommand extends BushCommand { public constructor() { super('servers', { - aliases: ['servers'], + aliases: ['servers', 'guilds'], category: 'dev', description: { content: 'Displays all the severs the bot is in', @@ -37,7 +37,7 @@ export default class ServersCommand extends BushCommand { `**ID:** ${g.id}\n**Owner:** ${owner ? owner : g.ownerId}\n**Members:** ${g.memberCount.toLocaleString()}`, false ) - .setTitle('Server List') + .setTitle(`Server List [${this.client.guilds.cache.size}]`) .setColor(this.client.util.colors.default); }); embeds.push(embed); diff --git a/src/commands/moderation/removeReactionEmoji.ts b/src/commands/moderation/removeReactionEmoji.ts index 3d274e1..b0079c6 100644 --- a/src/commands/moderation/removeReactionEmoji.ts +++ b/src/commands/moderation/removeReactionEmoji.ts @@ -13,7 +13,7 @@ export default class RemoveReactionEmojiCommand extends BushCommand { examples: ['removereactionemoji 791413052347252786 <:omegaclown:782630946435366942>'] }, clientPermissions: ['MANAGE_MESSAGES', 'SEND_MESSAGES', 'EMBED_LINKS'], - userPermissions: ['MANAGE_MESSAGES', 'MANAGE_EMOJIS'], // Can't undo the removal of 1000s of reactions + userPermissions: ['MANAGE_MESSAGES', 'MANAGE_EMOJIS_AND_STICKERS'], // Can't undo the removal of 1000s of reactions args: [ { id: 'messageToRemoveFrom', diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index 66204ac..8eec3ce 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -18,13 +18,11 @@ import { Sequelize } from 'sequelize'; import { contentWithDurationTypeCaster } from '../../../arguments/contentWithDuration'; import { durationTypeCaster } from '../../../arguments/duration'; import { UpdateCacheTask } from '../../../tasks/updateCache'; -import { Ban } from '../../models/Ban'; +import { ActivePunishment } from '../../models/ActivePunishment'; import { Global } from '../../models/Global'; import { Guild as GuildModel } from '../../models/Guild'; import { Level } from '../../models/Level'; import { ModLog } from '../../models/ModLog'; -import { Mute } from '../../models/Mute'; -import { PunishmentRole } from '../../models/PunishmentRole'; import { StickyRole } from '../../models/StickyRole'; import { AllowedMentions } from '../../utils/AllowedMentions'; import { BushCache } from '../../utils/BushCache'; @@ -107,39 +105,23 @@ export class BushClient extends AkairoClient { public constants = BushConstants; public cache = BushCache; public constructor(config: Config) { - super( - { - ownerID: config.owners, - intents: Object.values(Intents.FLAGS).reduce((acc, p) => acc | p, 0), - presence: { - activities: [ - { - name: 'Beep Boop', - type: 'WATCHING' - } - ], - status: 'online' - } + super({ + ownerID: config.owners, + intents: Object.values(Intents.FLAGS).reduce((acc, p) => acc | p, 0), + presence: { + activities: [ + { + name: 'Beep Boop', + type: 'WATCHING' + } + ], + status: 'online' }, - { - allowedMentions: AllowedMentions.users(), // No everyone or role mentions by default - intents: Object.values(Intents.FLAGS).reduce((acc, p) => acc | p, 0), - presence: { - activities: [ - { - name: 'Beep Boop', - type: 'WATCHING' - } - ], - status: 'online' - } - } - ); + http: { api: 'https://canary.discord.com/api' }, + allowedMentions: AllowedMentions.users() // No everyone or role mentions by default + }); - // Set token this.token = config.token; - - // Set config this.config = config; // Create listener handler @@ -170,7 +152,7 @@ export class BushClient extends AkairoClient { allowMention: true, handleEdits: true, commandUtil: true, - commandUtilLifetime: 300_000, + commandUtilLifetime: 300_000, // 5 minutes argumentDefaults: { prompt: { start: 'Placeholder argument prompt. If you see this please tell my developers.', @@ -259,11 +241,9 @@ export class BushClient extends AkairoClient { Global.initModel(this.db); GuildModel.initModel(this.db, this); ModLog.initModel(this.db); - Ban.initModel(this.db); - Mute.initModel(this.db); + ActivePunishment.initModel(this.db); Level.initModel(this.db); StickyRole.initModel(this.db); - PunishmentRole.initModel(this.db); await this.db.sync({ alter: true }); // Sync all tables to fix everything if updated await this.console.success('Startup', `Successfully connected to <<database>>.`, false); } catch (e) { @@ -277,7 +257,7 @@ export class BushClient extends AkairoClient { /** Starts the bot */ public async start(): Promise<void> { - global.client = this; + global.client = this; // makes the client a global object try { await this._init(); diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 0e3a904..306e049 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -2,7 +2,6 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { - Ban, BushCache, BushClient, BushConstants, @@ -14,9 +13,7 @@ import { Global, Guild, ModLog, - ModLogType, - Mute, - PunishmentRole + ModLogType } from '@lib'; import { exec } from 'child_process'; import { ClientUtil } from 'discord-akairo'; @@ -43,8 +40,8 @@ import { } from 'discord.js'; import got from 'got'; import humanizeDuration from 'humanize-duration'; -import { Op } from 'sequelize'; import { promisify } from 'util'; +import { ActivePunishment, ActivePunishmentType } from '../../models/ActivePunishment'; import { BushNewsChannel } from '../discord.js/BushNewsChannel'; import { BushTextChannel } from '../discord.js/BushTextChannel'; import { BushUserResolvable } from './BushClient'; @@ -96,12 +93,6 @@ interface bushColors { orange: '#E86100'; } -interface punishmentModels { - mute: Mute; - ban: Ban; - role: PunishmentRole; -} - interface MojangProfile { username: string; uuid: string; @@ -664,22 +655,21 @@ export class BushClientUtil extends ClientUtil { } public async createPunishmentEntry(options: { - type: 'mute' | 'ban' | 'role'; + type: 'mute' | 'ban' | 'role' | 'block'; user: BushGuildMemberResolvable; duration: number; guild: BushGuildResolvable; modlog: string; - role?: Snowflake; - }): Promise<Mute | Ban | PunishmentRole> { - const dbModel = this.findPunishmentModel(options.type); + extraInfo?: Snowflake; + }): Promise<ActivePunishment> { const expires = options.duration ? new Date(new Date().getTime() + options.duration) : null; const user = this.client.users.resolveId(options.user); const guild = this.client.guilds.resolveId(options.guild); + const type = this.findTypeEnum(options.type); - const entry = - options.type === 'role' - ? (dbModel as typeof PunishmentRole).build({ user, guild, expires, modlog: options.modlog, role: options.role }) - : dbModel.build({ user, guild, expires, modlog: options.modlog }); + const entry = options.extraInfo + ? ActivePunishment.build({ user, type, guild, expires, modlog: options.modlog, extraInfo: options.extraInfo }) + : ActivePunishment.build({ user, type, guild, expires, modlog: options.modlog }); return await entry.save().catch((e) => { this.client.console.error('createPunishmentEntry', e?.stack || e); return null; @@ -687,28 +677,23 @@ export class BushClientUtil extends ClientUtil { } public async removePunishmentEntry(options: { - type: 'mute' | 'ban' | 'role'; + type: 'mute' | 'ban' | 'role' | 'block'; user: BushGuildMemberResolvable; guild: BushGuildResolvable; }): Promise<boolean> { - const dbModel = this.findPunishmentModel(options.type); const user = this.client.users.resolveId(options.user); const guild = this.client.guilds.resolveId(options.guild); + const type = this.findTypeEnum(options.type); let success = true; - const entries = await dbModel - .findAll({ - // finding all cases of a certain type incase there were duplicates or something - where: { - user, - guild - } - }) - .catch((e) => { - this.client.console.error('removePunishmentEntry', e?.stack || e); - success = false; - }); + const entries = await ActivePunishment.findAll({ + // finding all cases of a certain type incase there were duplicates or something + where: { user, guild, type } + }).catch((e) => { + this.client.console.error('removePunishmentEntry', e?.stack || e); + success = false; + }); if (entries) { entries.forEach(async (entry) => { await entry.destroy().catch((e) => { @@ -720,33 +705,14 @@ export class BushClientUtil extends ClientUtil { return success; } - public async findExpiredEntries<K extends keyof punishmentModels>(type: K): Promise<punishmentModels[K][]> { - const dbModel = this.findPunishmentModel(type); - //@ts-ignore: stfu idc - return await dbModel.findAll({ - where: { - [Op.and]: [ - { - expires: { - [Op.lt]: new Date() // Find all rows with an expiry date before now - } - } - ] - } - }); - } - - private findPunishmentModel<K extends keyof punishmentModels>(type: K): typeof Mute | typeof Ban | typeof PunishmentRole { - switch (type) { - case 'mute': - return Mute; - case 'ban': - return Ban; - case 'role': - return PunishmentRole; - default: - throw 'choose a valid punishment entry type'; - } + private findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') { + const typeMap = { + ['mute']: ActivePunishmentType.MUTE, + ['ban']: ActivePunishmentType.BAN, + ['role']: ActivePunishmentType.ROLE, + ['block']: ActivePunishmentType.BLOCK + }; + return typeMap[type]; } public humanizeDuration(duration: number): string { @@ -782,5 +748,7 @@ export class BushClientUtil extends ClientUtil { return arrByte[1] + ', ' + arrByte[2] + ', ' + arrByte[3]; } + /* eslint-disable @typescript-eslint/no-unused-vars */ public async lockdownChannel(options: { channel: BushTextChannel | BushNewsChannel; moderator: BushUserResolvable }) {} + /* eslint-enable @typescript-eslint/no-unused-vars */ } diff --git a/src/lib/extensions/discord-akairo/BushCommandHandler.ts b/src/lib/extensions/discord-akairo/BushCommandHandler.ts index 09baf2e..dacd17f 100644 --- a/src/lib/extensions/discord-akairo/BushCommandHandler.ts +++ b/src/lib/extensions/discord-akairo/BushCommandHandler.ts @@ -1,15 +1,26 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Category, CommandHandler, CommandHandlerOptions } from 'discord-akairo'; -import { Collection } from 'discord.js'; +import { Category, CommandHandler, CommandHandlerEvents, CommandHandlerOptions } from 'discord-akairo'; +import { Collection, PermissionString } from 'discord.js'; import { BushConstants } from '../../utils/BushConstants'; import { BushMessage } from '../discord.js/BushMessage'; import { BushClient } from './BushClient'; import { BushCommand } from './BushCommand'; +import { BushSlashMessage } from './BushSlashMessage'; export type BushCommandHandlerOptions = CommandHandlerOptions; -const CommandHandlerEvents = BushConstants.CommandHandlerEvents; -const BlockedReasons = BushConstants.BlockedReasons; +const commandHandlerEvents = BushConstants.CommandHandlerEvents; +const blockedReasons = BushConstants.BlockedReasons; + +export interface BushCommandHandlerEvents extends CommandHandlerEvents { + commandBlocked: [message: BushMessage, command: BushCommand, reason: string]; + + missingPermissions: [message: BushMessage, command: BushCommand, type: 'client' | 'user', missing: Array<PermissionString>]; + + slashBlocked: [message: BushSlashMessage, command: BushCommand, reason: string]; + + slashMissingPermissions: [message: BushSlashMessage, command: BushCommand, type: 'client' | 'user', missing: Array<PermissionString>]; +} export class BushCommandHandler extends CommandHandler { public declare client: BushClient; @@ -24,10 +35,10 @@ export class BushCommandHandler extends CommandHandler { const isOwner = this.client.isOwner(message.author); if (!isOwner) { this.emit( - slash ? CommandHandlerEvents.SLASH_BLOCKED : CommandHandlerEvents.COMMAND_BLOCKED, + slash ? commandHandlerEvents.SLASH_BLOCKED : commandHandlerEvents.COMMAND_BLOCKED, message, command, - BlockedReasons.OWNER + blockedReasons.OWNER ); return true; } @@ -37,10 +48,10 @@ export class BushCommandHandler extends CommandHandler { const isSuperUser = this.client.isSuperUser(message.author); if (!isSuperUser) { this.emit( - slash ? CommandHandlerEvents.SLASH_BLOCKED : CommandHandlerEvents.COMMAND_BLOCKED, + slash ? commandHandlerEvents.SLASH_BLOCKED : commandHandlerEvents.COMMAND_BLOCKED, message, command, - BlockedReasons.OWNER + blockedReasons.OWNER ); return true; } @@ -48,32 +59,32 @@ export class BushCommandHandler extends CommandHandler { if (command.channel === 'guild' && !message.guild) { this.emit( - slash ? CommandHandlerEvents.SLASH_BLOCKED : CommandHandlerEvents.COMMAND_BLOCKED, + slash ? commandHandlerEvents.SLASH_BLOCKED : commandHandlerEvents.COMMAND_BLOCKED, message, command, - BlockedReasons.GUILD + blockedReasons.GUILD ); return true; } if (command.channel === 'dm' && message.guild) { this.emit( - slash ? CommandHandlerEvents.SLASH_BLOCKED : CommandHandlerEvents.COMMAND_BLOCKED, + slash ? commandHandlerEvents.SLASH_BLOCKED : commandHandlerEvents.COMMAND_BLOCKED, message, command, - BlockedReasons.DM + blockedReasons.DM ); return true; } if (command.restrictedChannels?.length && message.channel) { if (!command.restrictedChannels.includes(message.channel.id)) { - this.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, BlockedReasons.RESTRICTED_CHANNEL); + this.emit(commandHandlerEvents.COMMAND_BLOCKED, message, command, blockedReasons.RESTRICTED_CHANNEL); return true; } } if (command.restrictedGuilds?.length && message.guild) { if (!command.restrictedGuilds.includes(message.guild.id)) { - this.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, BlockedReasons.RESTRICTED_GUILD); + this.emit(commandHandlerEvents.COMMAND_BLOCKED, message, command, blockedReasons.RESTRICTED_GUILD); return true; } } @@ -82,7 +93,7 @@ export class BushCommandHandler extends CommandHandler { } const reason = this.inhibitorHandler ? await this.inhibitorHandler.test('post', message, command) : null; if (reason != null) { - this.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, reason); + this.emit(commandHandlerEvents.COMMAND_BLOCKED, message, command, reason); return true; } return !!this.runCooldowns(message, command); diff --git a/src/lib/extensions/discord-akairo/BushListener.ts b/src/lib/extensions/discord-akairo/BushListener.ts index e555e89..2583e85 100644 --- a/src/lib/extensions/discord-akairo/BushListener.ts +++ b/src/lib/extensions/discord-akairo/BushListener.ts @@ -1,6 +1,9 @@ import { Listener } from 'discord-akairo'; +import EventEmitter from 'events'; import { BushClient } from './BushClient'; - export class BushListener extends Listener { public declare client: BushClient; + public constructor(id: string, options?:{emitter: string|EventEmitter, event: string, type?: 'on'|'once', category?: string}){ + super(id, options) + } } diff --git a/src/lib/extensions/discord.js/BushButtonInteraction.ts b/src/lib/extensions/discord.js/BushButtonInteraction.ts index 3a54f61..846786c 100644 --- a/src/lib/extensions/discord.js/BushButtonInteraction.ts +++ b/src/lib/extensions/discord.js/BushButtonInteraction.ts @@ -1,4 +1,4 @@ -import { APIInteractionGuildMember } from 'discord-api-types/v8'; +import { APIInteractionGuildMember } from 'discord-api-types/v9'; import { ButtonInteraction, PartialDMChannel } from 'discord.js'; import { BushClient } from '../discord-akairo/BushClient'; import { BushDMChannel } from './BushDMChannel'; diff --git a/src/lib/extensions/discord.js/BushCommandInteraction.ts b/src/lib/extensions/discord.js/BushCommandInteraction.ts index 84c0707..e4fdb53 100644 --- a/src/lib/extensions/discord.js/BushCommandInteraction.ts +++ b/src/lib/extensions/discord.js/BushCommandInteraction.ts @@ -1,4 +1,4 @@ -import { APIInteractionGuildMember } from 'discord-api-types/v8'; +import { APIInteractionGuildMember } from 'discord-api-types/v9'; import { ApplicationCommand, CommandInteraction, diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts index 40e4a3a..50875cc 100644 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ b/src/lib/extensions/discord.js/BushGuildMember.ts @@ -137,7 +137,7 @@ export class BushGuildMember extends GuildMember { guild: this.guild, duration: options.duration, modlog: modlog.id, - role: options.role.id + extraInfo: options.role.id }) .catch(() => null); if (!punishmentEntrySuccess) return 'error creating role entry'; diff --git a/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts b/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts index 1dd1638..18db8ef 100644 --- a/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts +++ b/src/lib/extensions/discord.js/BushSelectMenuInteraction.ts @@ -1,4 +1,4 @@ -import { APIInteractionGuildMember } from 'discord-api-types/v8'; +import { APIInteractionGuildMember } from 'discord-api-types/v9'; import { PartialDMChannel, SelectMenuInteraction } from 'discord.js'; import { BushClient } from '../discord-akairo/BushClient'; import { BushDMChannel } from './BushDMChannel'; diff --git a/src/lib/index.ts b/src/lib/index.ts index 1059e2b..73cee56 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -46,14 +46,12 @@ export * from './extensions/discord.js/BushThreadMemberManager'; export * from './extensions/discord.js/BushUser'; export * from './extensions/discord.js/BushVoiceChannel'; export * from './extensions/discord.js/BushVoiceState'; -export * from './models/Ban'; +export * from './models/ActivePunishment'; export * from './models/BaseModel'; export * from './models/Global'; export * from './models/Guild'; export * from './models/Level'; export * from './models/ModLog'; -export * from './models/Mute'; -export * from './models/PunishmentRole'; export * from './models/StickyRole'; export * from './utils/AllowedMentions'; export * from './utils/BushCache'; diff --git a/src/lib/models/PunishmentRole.ts b/src/lib/models/ActivePunishment.ts index 0b54f31..9fcafbe 100644 --- a/src/lib/models/PunishmentRole.ts +++ b/src/lib/models/ActivePunishment.ts @@ -3,54 +3,67 @@ import { DataTypes, Sequelize } from 'sequelize'; import { v4 as uuidv4 } from 'uuid'; import { BaseModel } from './BaseModel'; -export interface PunishmentRoleModel { +export enum ActivePunishmentType { + BAN = 'BAN', + MUTE = 'MUTE', + ROLE = 'ROLE', + BLOCK = 'BLOCK' +} + +export interface ActivePunishmentModel { id: string; + type: ActivePunishmentType; user: Snowflake; - role: Snowflake; guild: Snowflake; + extraInfo: Snowflake; expires: Date; modlog: string; } -export interface PunishmentRoleModelCreationAttributes { +export interface ActivePunishmentModelCreationAttributes { id?: string; + type: ActivePunishmentType; user: Snowflake; - role?: Snowflake; guild: Snowflake; + extraInfo?: Snowflake; expires?: Date; modlog: string; } -export class PunishmentRole - extends BaseModel<PunishmentRoleModel, PunishmentRoleModelCreationAttributes> - implements PunishmentRoleModel +export class ActivePunishment + extends BaseModel<ActivePunishmentModel, ActivePunishmentModelCreationAttributes> + implements ActivePunishmentModel { /** - * The ID of this punishment role (no real use just for a primary key) + * The ID of this punishment (no real use just for a primary key) */ id: string; /** - * The user who received a role + * The type of punishment. */ - user: Snowflake; + type: ActivePunishmentType; /** - * The role added to the user. + * The user who is punished. */ - role: Snowflake; + user: Snowflake; /** - * The guild they received a role in + * The guild they are punished in. */ guild: Snowflake; /** - * The date at which this role expires and should be removed (optional) + * Additional info about the punishment if applicable. The channel id for channel blocks and role for punishment roles. + */ + extraInfo: Snowflake; + /** + * The date when this punishment expires (optional). */ expires: Date | null; /** - * The ref to the modlog entry + * The reference to the modlog entry. */ modlog: string; static initModel(sequelize: Sequelize): void { - PunishmentRole.init( + ActivePunishment.init( { id: { type: DataTypes.STRING, @@ -58,11 +71,11 @@ export class PunishmentRole allowNull: false, defaultValue: uuidv4 }, - user: { + type: { type: DataTypes.STRING, allowNull: false }, - role: { + user: { type: DataTypes.STRING, allowNull: false }, @@ -74,6 +87,10 @@ export class PunishmentRole key: 'id' } }, + extraInfo: { + type: DataTypes.DATE, + allowNull: true + }, expires: { type: DataTypes.DATE, allowNull: true diff --git a/src/lib/models/Ban.ts b/src/lib/models/Ban.ts deleted file mode 100644 index 1bdda6f..0000000 --- a/src/lib/models/Ban.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Snowflake } from 'discord.js'; -import { DataTypes, Sequelize } from 'sequelize'; -import { v4 as uuidv4 } from 'uuid'; -import { BaseModel } from './BaseModel'; - -export interface BanModel { - id: string; - user: Snowflake; - guild: Snowflake; - expires: Date; - modlog: string; -} -export interface BanModelCreationAttributes { - id?: string; - user: Snowflake; - guild: Snowflake; - expires?: Date; - modlog: string; -} - -export class Ban extends BaseModel<BanModel, BanModelCreationAttributes> implements BanModel { - /** - * The ID of this ban (no real use just for a primary key) - */ - id: string; - /** - * The user who is banned - */ - user: Snowflake; - /** - * The guild they are banned from - */ - guild: Snowflake; - /** - * The date at which this ban expires and should be unbanned (optional) - */ - expires: Date | null; - /** - * The ref to the modlog entry - */ - modlog: string; - - static initModel(sequelize: Sequelize): void { - 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: 'Guilds', - key: 'id' - } - }, - expires: { - type: DataTypes.DATE, - allowNull: true - }, - modlog: { - type: DataTypes.STRING, - allowNull: false, - references: { - model: 'ModLogs', - key: 'id' - } - } - }, - { sequelize: sequelize } - ); - } -} diff --git a/src/lib/models/ModLog.ts b/src/lib/models/ModLog.ts index 40dc86d..3375751 100644 --- a/src/lib/models/ModLog.ts +++ b/src/lib/models/ModLog.ts @@ -14,7 +14,10 @@ export enum ModLogType { WARN = 'WARN', PERM_PUNISHMENT_ROLE = 'PERM_PUNISHMENT_ROLE', TEMP_PUNISHMENT_ROLE = 'TEMP_PUNISHMENT_ROLE', - REMOVE_PUNISHMENT_ROLE = 'REMOVE_PUNISHMENT_ROLE' + REMOVE_PUNISHMENT_ROLE = 'REMOVE_PUNISHMENT_ROLE', + PERM_CHANNEL_BLOCK = 'PERM_CHANNEL_BLOCK', + TEMP_CHANNEL_BLOCK = 'TEMP_CHANNEL_BLOCK', + CHANNEL_UNBLOCK = 'CHANNEL_UNBLOCK' } export interface ModLogModel { diff --git a/src/lib/models/Mute.ts b/src/lib/models/Mute.ts deleted file mode 100644 index 4208d02..0000000 --- a/src/lib/models/Mute.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Snowflake } from 'discord.js'; -import { DataTypes, Sequelize } from 'sequelize'; -import { v4 as uuidv4 } from 'uuid'; -import { BaseModel } from './BaseModel'; - -export interface MuteModel { - id: string; - user: Snowflake; - guild: Snowflake; - expires: Date; - modlog: string; -} -export interface MuteModelCreationAttributes { - id?: string; - user: Snowflake; - guild: Snowflake; - expires?: Date; - modlog: string; -} - -export class Mute extends BaseModel<MuteModel, MuteModelCreationAttributes> implements MuteModel { - /** - * The ID of this mute (no real use just for a primary key) - */ - id: string; - /** - * The user who is muted - */ - user: Snowflake; - /** - * The guild they are muted in - */ - guild: Snowflake; - /** - * The date at which this Mute expires and should be unmuted (optional) - */ - expires: Date | null; - /** - * The ref to the modlog entry - */ - modlog: string; - - static initModel(sequelize: Sequelize): void { - Mute.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: 'Guilds', - key: 'id' - } - }, - expires: { - type: DataTypes.DATE, - allowNull: true - }, - modlog: { - type: DataTypes.STRING, - allowNull: false, - references: { - model: 'ModLogs', - key: 'id' - } - } - }, - { sequelize: sequelize } - ); - } -} diff --git a/src/listeners/client/interactionCreate.ts b/src/listeners/client/interactionCreate.ts index 1183004..0e77fea 100644 --- a/src/listeners/client/interactionCreate.ts +++ b/src/listeners/client/interactionCreate.ts @@ -1,5 +1,5 @@ import { BushListener } from '@lib'; -import { ButtonInteraction, CommandInteraction, Interaction, SelectMenuInteraction } from 'discord.js'; +import { ClientEvents } from 'discord.js'; export default class InteractionCreateListener extends BushListener { public constructor() { @@ -10,7 +10,7 @@ export default class InteractionCreateListener extends BushListener { }); } - async exec(interaction: Interaction | CommandInteraction | ButtonInteraction | SelectMenuInteraction): Promise<unknown> { + async exec([interaction]: ClientEvents['interactionCreate']): Promise<unknown> { if (!interaction) return; if (interaction.isCommand()) { this.client.console.info( diff --git a/src/listeners/commands/commandBlocked.ts b/src/listeners/commands/commandBlocked.ts index 24d46af..c02f21c 100644 --- a/src/listeners/commands/commandBlocked.ts +++ b/src/listeners/commands/commandBlocked.ts @@ -1,4 +1,4 @@ -import { BushCommand, BushListener, BushMessage } from '@lib'; +import { BushCommandHandlerEvents, BushListener } from '@lib'; export default class CommandBlockedListener extends BushListener { public constructor() { @@ -8,7 +8,7 @@ export default class CommandBlockedListener extends BushListener { }); } - public async exec(message: BushMessage, command: BushCommand, reason: string): Promise<unknown> { + public async exec([message, command, reason]: BushCommandHandlerEvents['commandBlocked']): Promise<unknown> { this.client.console.info( 'CommandBlocked', `<<${message.author.tag}>> tried to run <<${message.util.parsed.command}>> but was blocked because <<${reason}>>.`, diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts index 696b59b..1aaefad 100644 --- a/src/listeners/commands/commandError.ts +++ b/src/listeners/commands/commandError.ts @@ -1,6 +1,5 @@ -import { BushListener, BushMessage } from '@lib'; +import { BushCommandHandlerEvents, BushListener } from '@lib'; import { stripIndents } from 'common-tags'; -import { Command } from 'discord-akairo'; import { MessageEmbed } from 'discord.js'; export default class CommandErrorListener extends BushListener { @@ -11,7 +10,7 @@ export default class CommandErrorListener extends BushListener { }); } - public async exec(error: Error, message: BushMessage, command: Command | null | undefined): Promise<void> { + public async exec([error, message, command]: BushCommandHandlerEvents['error']): Promise<void> { const errorNo = Math.floor(Math.random() * 6969696969) + 69; // hehe funny number const errorEmbed: MessageEmbed = new MessageEmbed() .setTitle(`Error # \`${errorNo}\`: An error occurred`) diff --git a/src/listeners/commands/commandMissingPermissions.ts b/src/listeners/commands/commandMissingPermissions.ts index f3d2218..765bc72 100644 --- a/src/listeners/commands/commandMissingPermissions.ts +++ b/src/listeners/commands/commandMissingPermissions.ts @@ -1,5 +1,4 @@ -import { BushCommand, BushListener, BushMessage } from '@lib'; -import { PermissionString } from 'discord.js'; +import { BushCommandHandlerEvents, BushListener } from '@lib'; export default class CommandMissingPermissionsListener extends BushListener { public constructor() { @@ -10,12 +9,7 @@ export default class CommandMissingPermissionsListener extends BushListener { }); } - public async exec( - message: BushMessage, - command: BushCommand | null | undefined, - type: 'client' | 'user', - missing: Array<PermissionString> - ): Promise<void> { + public async exec([message, command, type, missing]: BushCommandHandlerEvents['missingPermissions']): Promise<void> { const niceMissing = []; missing.forEach((missing) => { if (this.client.consts.mappings.permissions[missing]) { diff --git a/src/listeners/commands/commandStarted.ts b/src/listeners/commands/commandStarted.ts index 24bb041..dcc1d83 100644 --- a/src/listeners/commands/commandStarted.ts +++ b/src/listeners/commands/commandStarted.ts @@ -1,20 +1,20 @@ -import { BushCommand, BushListener } from '@lib'; -import { Message } from 'discord.js'; +import { BushCommandHandlerEvents, BushListener } from '@lib'; export default class CommandStartedListener extends BushListener { public constructor() { super('commandStarted', { emitter: 'commandHandler', - event: 'commandStarted' + event: 'commandStarted', + category: 'commands' }); } - exec(message: Message, command: BushCommand): void { + exec([message, command]: BushCommandHandlerEvents['commandStarted']): void { this.client.logger.info( 'Command', `The <<${command.id}>> command was used by <<${message.author.tag}>> in ${ message.channel.type === 'DM' ? `their <<DMs>>` : `<<#${message.channel.name}>> in <<${message.guild?.name}>>` }.`, - true //// I don't want to spam the log channel when people use commands + true ); } } diff --git a/src/listeners/commands/slashBlocked.ts b/src/listeners/commands/slashBlocked.ts index 2443efb..ecbe863 100644 --- a/src/listeners/commands/slashBlocked.ts +++ b/src/listeners/commands/slashBlocked.ts @@ -1,14 +1,15 @@ -import { BushCommand, BushListener, BushSlashMessage } from '@lib'; +import { BushCommandHandlerEvents, BushListener } from '@lib'; export default class SlashBlockedListener extends BushListener { public constructor() { super('slashBlocked', { emitter: 'commandHandler', - event: 'slashBlocked' + event: 'slashBlocked', + category: 'commands' }); } - public async exec(message: BushSlashMessage, command: BushCommand, reason: string): Promise<unknown> { + public async exec([message, command, reason]: BushCommandHandlerEvents['slashBlocked']): Promise<unknown> { this.client.console.info( 'SlashBlocked', `<<${message.author.tag}>> tried to run <<${message.util.parsed.command}>> but was blocked because <<${reason}>>.`, diff --git a/src/listeners/commands/slashCommandError.ts b/src/listeners/commands/slashCommandError.ts index 8abe788..4eaf293 100644 --- a/src/listeners/commands/slashCommandError.ts +++ b/src/listeners/commands/slashCommandError.ts @@ -1,4 +1,4 @@ -import { BushCommand, BushListener, BushSlashMessage } from '@lib'; +import { BushCommandHandlerEvents, BushListener } from '@lib'; import { stripIndents } from 'common-tags'; import { GuildChannel, MessageEmbed } from 'discord.js'; @@ -6,10 +6,11 @@ export default class SlashCommandErrorListener extends BushListener { public constructor() { super('slashError', { emitter: 'commandHandler', - event: 'slashError' + event: 'slashError', + category: 'commands' }); } - async exec(error: Error, message: BushSlashMessage, command: BushCommand): Promise<void> { + async exec([error, message, command]: BushCommandHandlerEvents['slashError']): Promise<void> { const errorNo = Math.floor(Math.random() * 6969696969) + 69; // hehe funny number const errorEmbed: MessageEmbed = new MessageEmbed() .setTitle(`Slash Error # \`${errorNo}\`: An error occurred`) diff --git a/src/listeners/commands/slashMissingPermissions.ts b/src/listeners/commands/slashMissingPermissions.ts index a410bef..45966bb 100644 --- a/src/listeners/commands/slashMissingPermissions.ts +++ b/src/listeners/commands/slashMissingPermissions.ts @@ -1,22 +1,15 @@ -import { BushListener } from '@lib'; -import { Command } from 'discord-akairo'; -import { CommandInteraction } from 'discord.js'; +import { BushCommandHandlerEvents, BushListener } from '@lib'; export default class SlashMissingPermissionsListener extends BushListener { public constructor() { super('slashMissingPermissions', { emitter: 'commandHandler', event: 'slashMissingPermissions', - category: 'slashCommands' + category: 'commands' }); } - public async exec( - interaction: CommandInteraction, - command: Command, - type: 'user' | 'client', - missing?: string[] - ): Promise<void> { + public async exec([message, command, type, missing]: BushCommandHandlerEvents['slashMissingPermissions']): Promise<void> { const niceMissing = []; missing.forEach((missing) => { if (this.client.consts.mappings.permissions[missing]) { @@ -30,24 +23,22 @@ export default class SlashMissingPermissionsListener extends BushListener { const consoleFormat = this.client.util.oxford(this.client.util.surroundArray(niceMissing, '<<', '>>'), 'and', ''); this.client.console.info( 'CommandMissingPermissions', - `<<${interaction.user.tag}>> tried to run <<${ + `<<${message.author.tag}>> tried to run <<${ command?.id }>> but could not because <<${type}>> is missing the ${consoleFormat} permissions${missing.length ? 's' : ''}.`, true ); if (type == 'client') { - await this.client.util - .slashRespond( - interaction, + await message.util + .reply( `${this.client.util.emojis.error} I am missing the ${discordFormat} permission${ missing.length ? 's' : '' } required for the \`${command?.id}\` command.` ) .catch(() => {}); } else if (type == 'user') { - await this.client.util - .slashRespond( - interaction, + await message.util + .reply( `${this.client.util.emojis.error} You are missing the ${discordFormat} permission${ missing.length ? 's' : '' } required for the \`${command?.id}\` command.` diff --git a/src/listeners/commands/slashStarted.ts b/src/listeners/commands/slashStarted.ts index a0201c5..6ff3c6e 100644 --- a/src/listeners/commands/slashStarted.ts +++ b/src/listeners/commands/slashStarted.ts @@ -1,19 +1,20 @@ -import { BushCommand, BushListener, BushSlashMessage } from '@lib'; +import { BushCommandHandlerEvents, BushListener } from '@lib'; export default class SlashStartedListener extends BushListener { public constructor() { super('slashStarted', { emitter: 'commandHandler', - event: 'slashStarted' + event: 'slashStarted', + category: 'commands' }); } - exec(message: BushSlashMessage, command: BushCommand): void { - this.client.logger.info( + async exec([message, command]: BushCommandHandlerEvents['slashStarted']): Promise<unknown> { + return await this.client.logger.info( 'SlashCommand', `The <<${command.id}>> command was used by <<${message.author.tag}>> in ${ message.channel.type === 'DM' ? `their <<DMs>>` : `<<#${message.channel.name}>> in <<${message.guild?.name}>>` }.`, - true //// I don't want to spam the log channel when people use commands + true ); } } diff --git a/src/listeners/guild/syncUnban.ts b/src/listeners/guild/syncUnban.ts index aa148f9..c9ba0cb 100644 --- a/src/listeners/guild/syncUnban.ts +++ b/src/listeners/guild/syncUnban.ts @@ -1,5 +1,5 @@ -import { Ban, BushListener } from '@lib'; -import { Guild, User } from 'discord.js'; +import { ActivePunishment, ActivePunishmentType, BushListener } from '@lib'; +import { ClientEvents } from 'discord.js'; export default class SyncUnbanListener extends BushListener { public constructor() { @@ -9,11 +9,12 @@ export default class SyncUnbanListener extends BushListener { }); } - public async exec(guild: Guild, user: User): Promise<void> { - const bans = await Ban.findAll({ + public async exec([ban]: ClientEvents['guildBanRemove']): Promise<void> { + const bans = await ActivePunishment.findAll({ where: { - user: user.id, - guild: guild.id + user: ban.user, + guild: ban.guild, + type: ActivePunishmentType.BAN } }); for (const dbBan of bans) { diff --git a/src/listeners/message/level.ts b/src/listeners/message/level.ts index 1f57930..b06fdd2 100644 --- a/src/listeners/message/level.ts +++ b/src/listeners/message/level.ts @@ -1,5 +1,5 @@ import { BushListener, Level } from '@lib'; -import { Message } from 'discord.js'; +import { Message, MessageType } from 'discord.js'; export default class LevelListener extends BushListener { private levelCooldowns: Set<string> = new Set(); @@ -17,7 +17,8 @@ export default class LevelListener extends BushListener { if (message.util?.parsed?.command) return; if (this.levelCooldowns.has(`${message.guild.id}-${message.author.id}`)) return; if (this.blacklistedChannels.includes(message.channel.id)) return; - if (!['DEFAULT', 'REPLY'].includes(message.type)) return; //checks for join messages, slash commands, booster messages etc + const allowedMessageTypes: MessageType[] = ['DEFAULT', 'REPLY']; // this is so ts will yell at me when discord.js makes some unnecessary breaking change + if (!allowedMessageTypes.includes(message.type)) return; //checks for join messages, slash commands, booster messages etc const [user] = await Level.findOrBuild({ where: { user: message.author.id, diff --git a/src/listeners/other/consoleListener.ts b/src/listeners/other/consoleListener.ts index ef1efd5..90f9871 100644 --- a/src/listeners/other/consoleListener.ts +++ b/src/listeners/other/consoleListener.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-unused-vars */ import { BushListener } from '@lib'; +import { exec } from 'child_process'; +import { promisify } from 'util'; export default class ConsoleListener extends BushListener { public constructor() { @@ -12,10 +14,12 @@ export default class ConsoleListener extends BushListener { public async exec(line: string): Promise<void> { if (line.startsWith('eval ') || line.startsWith('ev ')) { - const bot = this.client, + const + sh = promisify(exec), + bot = this.client, config = this.client.config, client = this.client, - { Ban, Global, Guild, Level, ModLog, StickyRole } = await import('@lib'), + { ActivePunishment, Global, Guild, Level, ModLog, StickyRole } = await import('@lib'), { ButtonInteraction, Collector, @@ -26,12 +30,14 @@ export default class ConsoleListener extends BushListener { MessageAttachment, MessageButton, MessageCollector, - MessageComponentInteractionCollector, + InteractionCollector, MessageEmbed, MessageSelectMenu, ReactionCollector, - Util - } = require('discord.js'); + Util, + Collection + } = await import('discord.js'), + { Canvas } = await import('node-canvas'); try { const input = line.replace('eval ', '').replace('ev ', ''); let output = eval(input); diff --git a/src/tasks/removeExpiredPunishements.ts b/src/tasks/removeExpiredPunishements.ts new file mode 100644 index 0000000..d0220ba --- /dev/null +++ b/src/tasks/removeExpiredPunishements.ts @@ -0,0 +1,74 @@ +import { BushGuild, BushGuildMember, BushTask } from '@lib'; +import { Op } from 'sequelize'; +import { ActivePunishment, ActivePunishmentType } from '../lib/models/ActivePunishment'; + +export default class RemoveExpiredPunishmentsTask extends BushTask { + public constructor() { + super('removeExpiredPunishments', { + delay: 15_000, // 15 seconds + runOnStart: true + }); + } + async exec(): Promise<void> { + const expiredEntries = await ActivePunishment.findAll({ + where: { + [Op.and]: [ + { + expires: { + [Op.lt]: new Date() // Find all rows with an expiry date before now + } + } + ] + } + }); + + this.client.logger.verbose( + `removeExpiredPunishments`, + `Queried punishments, found <<${expiredEntries.length}>> expired punishments.` + ); + + for (const entry of expiredEntries) { + const guild = this.client.guilds.cache.get(entry.guild) as BushGuild; + const member = guild.members.cache.get(entry.user) as BushGuildMember; + + if (!guild) { + await entry.destroy(); + continue; + } + + switch (entry.type) { + case ActivePunishmentType.BAN: { + const result = await guild.unban({ user: entry.user, reason: 'Punishment expired.' }); + if (['success', 'user not banned'].includes(result)) await entry.destroy(); + else throw result; + this.client.logger.verbose(`removeExpiredPunishments`, `Unbanned ${entry.user}.`); + break; + } + case ActivePunishmentType.BLOCK: { + //todo + break; + } + case ActivePunishmentType.MUTE: { + const result = await member.unmute({ reason: 'Punishment expired.' }); + if (['success', 'failed to dm'].includes(result)) await entry.destroy(); + else throw result; + this.client.logger.verbose(`removeExpiredPunishments`, `Unmuted ${entry.user}.`); + break; + } + case ActivePunishmentType.ROLE: { + const role = guild?.roles?.cache?.get(entry.extraInfo); + const result = await member.removeRole({ + reason: 'Punishment expired.', + role: role, + addToModlog: true + }); + + if (['success', 'failed to dm'].includes(result)) await entry.destroy(); + else throw result; + this.client.logger.verbose(`removeExpiredPunishments`, `Removed a punishment role from ${entry.user}.`); + break; + } + } + } + } +} diff --git a/src/tasks/removePunishmentRole.ts b/src/tasks/removePunishmentRole.ts deleted file mode 100644 index 9830338..0000000 --- a/src/tasks/removePunishmentRole.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { BushGuildMember, BushTask } from '@lib'; - -export default class RemovePunishmentRole extends BushTask { - public constructor() { - super('removePunishmentRole', { - delay: 30_000, // 1/2 min - runOnStart: true - }); - } - async exec(): Promise<void> { - const expiredEntries = await this.client.util.findExpiredEntries('role'); - this.client.logger.verbose( - `RemovePunishmentRoleTask`, - `Queried punishment roles, found <<${expiredEntries.length}>> expired punishment roles.` - ); - - for (const entry of expiredEntries) { - const guild = this.client.guilds.cache.get(entry.guild); - const role = guild?.roles?.cache?.get(entry.role); - if (!guild || !role) { - await entry.destroy(); - continue; - } - - const member = guild.members.cache.get(entry.user) as BushGuildMember; - const result = await member.removeRole({ - reason: 'Punishment expired.', - role: role, - addToModlog: true - }); - if (['success', 'failed to dm'].includes(result)) await entry.destroy(); - else throw result; - - this.client.logger.verbose(`RemovePunishmentRoleTask`, `Removed a punishment role from ${entry.user}.`); - } - } -} diff --git a/src/tasks/unban.ts b/src/tasks/unban.ts deleted file mode 100644 index 136e6c2..0000000 --- a/src/tasks/unban.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { BushGuild, BushTask } from '@lib'; - -export default class UnbanTask extends BushTask { - public constructor() { - super('unban', { - delay: 30_000, // 1/2 min - runOnStart: true - }); - } - async exec(): Promise<void> { - const rows = await this.client.util.findExpiredEntries('mute'); - this.client.logger.verbose(`UnbanTask`, `Queried bans, found <<${rows.length}>> expired bans.`); - - for (const row of rows) { - const guild = this.client.guilds.cache.get(row.guild) as BushGuild; - if (!guild) { - await row.destroy(); - continue; - } - - const result = await guild.unban({ user: row.user, reason: 'Punishment expired.' }); - if (['success', 'user not banned'].includes(result)) await row.destroy(); - else throw result; - this.client.logger.verbose(`UnbanTask`, `Unbanned ${row.user}`); - } - } -} diff --git a/src/tasks/unmute.ts b/src/tasks/unmute.ts deleted file mode 100644 index c61c6e9..0000000 --- a/src/tasks/unmute.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { BushGuildMember, BushTask, Mute } from '@lib'; -import { Op } from 'sequelize'; - -export default class UnmuteTask extends BushTask { - public constructor() { - super('unmute', { - delay: 30_000, // 1/2 min - runOnStart: true - }); - } - async exec(): Promise<void> { - const rows = await Mute.findAll({ - where: { - [Op.and]: [ - { - expires: { - [Op.lt]: new Date() // Find all rows with an expiry date before now - } - } - ] - } - }); - this.client.logger.verbose(`UnmuteTask`, `Queried mutes, found <<${rows.length}>> expired mutes.`); - for (const row of rows) { - const guild = this.client.guilds.cache.get(row.guild); - if (!guild) { - await row.destroy(); - continue; - } - - const member = guild.members.cache.get(row.user) as BushGuildMember; - const result = await member.unmute({ reason: 'Punishment expired.' }); - if (['success', 'failed to dm'].includes(result)) await row.destroy(); - else throw result; - - this.client.logger.verbose(`UnmuteTask`, `Unmuted ${row.user}`); - } - } -} |