diff options
26 files changed, 257 insertions, 85 deletions
diff --git a/package.json b/package.json index e3395fe..50c1762 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@types/module-alias": "^2", "@types/node": "^14.14.22", "@types/node-fetch": "^2", + "@types/node-os-utils": "^1", "@types/numeral": "^2", "@types/tinycolor2": "^1", "@types/uuid": "^8.3.0", @@ -69,10 +70,12 @@ "module-alias": "^2.2.2", "moment": "^2.29.1", "node-fetch": "^2.6.1", + "node-os-utils": "^1.3.5", "numeral": "^2.0.6", "pg": "^8.5.1", "pg-hstore": "^2.3.3", "prettier": "^2.3.2", + "pretty-bytes": "^5.6.0", "rimraf": "^3.0.2", "sequelize": "^6.5.0", "simplify-number": "^1.0.0", diff --git a/src/arguments/roleWithDuation.ts b/src/arguments/roleWithDuation.ts index 03a6035..54e6390 100644 --- a/src/arguments/roleWithDuation.ts +++ b/src/arguments/roleWithDuation.ts @@ -10,8 +10,6 @@ export const roleWithDurationTypeCaster: BushArgumentTypeCaster = async ( contentWithoutTime = contentWithoutTime.trim(); const role = await util.arg.cast('role', client.commandHandler.resolver, message, contentWithoutTime); if (!role) { - client.console.debug(contentWithoutTime); - client.console.debug(duration); return null; } return { duration, role }; diff --git a/src/commands/config/config.ts b/src/commands/config/config.ts index 8362144..0c466e6 100644 --- a/src/commands/config/config.ts +++ b/src/commands/config/config.ts @@ -94,7 +94,6 @@ export default class SettingsCommand extends BushCommand { ] }; }), - slashGuilds: ['516977525906341928', '812400566235430912'], channel: 'guild', clientPermissions: ['SEND_MESSAGES'], userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'], diff --git a/src/commands/dev/reload.ts b/src/commands/dev/reload.ts index 91cabfb..f7afbca 100644 --- a/src/commands/dev/reload.ts +++ b/src/commands/dev/reload.ts @@ -45,7 +45,9 @@ export default class ReloadCommand extends BushCommand { return message.util.send(`🔁 Successfully reloaded! (${new Date().getTime() - s.getTime()}ms)`); } catch (e) { if (output!) void client.logger.error('reloadCommand', output); - return message.util.send(`An error occurred while reloading:\n${await util.codeblock(e?.stack || e, 2048 - 34, 'js')}`); + return message.util.send( + `An error occurred while reloading:\n${await util.codeblock(e?.stack || e, 2048 - 34, 'js', true)}` + ); } } } diff --git a/src/commands/dev/sh.ts b/src/commands/dev/sh.ts index 93f0d40..067a0e6 100644 --- a/src/commands/dev/sh.ts +++ b/src/commands/dev/sh.ts @@ -47,7 +47,7 @@ export default class ShCommand extends BushCommand { .setFooter(message.author.tag, message.author.avatarURL({ dynamic: true }) ?? undefined) .setTimestamp() .setTitle('Shell Command') - .addField('📥 Input', await util.codeblock(input, 1024, 'sh')) + .addField('📥 Input', await util.codeblock(input, 1024, 'sh', true)) .addField('Running', util.emojis.loading); await message.util.reply({ embeds: [embed] }); @@ -69,15 +69,15 @@ export default class ShCommand extends BushCommand { .setColor(util.colors.success) .spliceFields(1, 1); - if (stdout) embed.addField('📤 stdout', await util.codeblock(stdout, 1024, 'json')); - if (stderr) embed.addField('📤 stderr', await util.codeblock(stderr, 1024, 'json')); + if (stdout) embed.addField('📤 stdout', await util.codeblock(stdout, 1024, 'json', true)); + if (stderr) embed.addField('📤 stderr', await util.codeblock(stderr, 1024, 'json', true)); } catch (e) { embed .setTitle(`${util.emojis.errorFull} An error occurred while executing.`) .setColor(util.colors.error) .spliceFields(1, 1); - embed.addField('📤 Output', await util.codeblock(e?.stack, 1024, 'js')); + embed.addField('📤 Output', await util.codeblock(e?.stack, 1024, 'js', true)); } await message.util.edit({ embeds: [embed] }); } diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts index 4bab8a1..a36972b 100644 --- a/src/commands/dev/superUser.ts +++ b/src/commands/dev/superUser.ts @@ -42,36 +42,29 @@ export default class SuperUserCommand extends BushCommand { public override async exec( message: BushMessage | BushSlashMessage, - args: { action: 'add' | 'remove'; user: User } + { action, user }: { action: 'add' | 'remove'; user: User } ): Promise<unknown> { if (!message.author.isOwner()) return await message.util.reply(`${util.emojis.error} Only my developers can run this command.`); - if (!args.user?.id) - return await message.util.reply( - `${util.emojis.error} I fucked up here is args ${await util.inspectCleanRedactCodeblock(args, 'ts')}` - ); - const superUsers: string[] = (await Global.findByPk(client.config.environment))?.superUsers ?? []; - let success; - if (args.action === 'add') { - if (superUsers.includes(args.user.id)) { - return message.util.reply(`${util.emojis.warn} \`${args.user.tag}\` is already a superuser.`); - } - success = await util.insertOrRemoveFromGlobal('add', 'superUsers', args.user.id).catch(() => false); - } else { - if (!superUsers.includes(args.user.id)) { - return message.util.reply(`${util.emojis.warn} \`${args.user.tag}\` is not superuser.`); - } - success = await util.insertOrRemoveFromGlobal('remove', 'superUsers', args.user.id).catch(() => false); - } + + if (action === 'add' ? superUsers.includes(user.id) : !superUsers.includes(user.id)) + return message.util.reply(`${util.emojis.warn} \`${user.tag}\` is ${action === 'add' ? 'already' : 'not'} a superuser.`); + + const success = await util.insertOrRemoveFromGlobal(action, 'superUsers', user.id).catch(() => false); + if (success) { - const responses = [args.action == 'remove' ? '' : 'made', args.action == 'remove' ? 'is no longer' : '']; - return message.util.reply(`${util.emojis.success} ${responses[0]} \`${args.user.tag}\` ${responses[1]} a superuser.`); + return await message.util.reply( + `${util.emojis.success} ${action == 'remove' ? '' : 'made'} \`${user.tag}\` ${ + action == 'remove' ? 'is no longer ' : '' + }a superuser.` + ); } else { - const response = [args.action == 'remove' ? `removing` : 'making', args.action == 'remove' ? `from` : 'to']; - return message.util.reply( - `${util.emojis.error} There was an error ${response[0]} \`${args.user.tag}\` ${response[1]} the superuser list.` + return await message.util.reply( + `${util.emojis.error} There was an error ${action == 'remove' ? `removing` : 'making'} \`${user.tag}\` ${ + action == 'remove' ? `from` : 'to' + } the superuser list.` ); } } diff --git a/src/commands/info/botInfo.ts b/src/commands/info/botInfo.ts index 37a63ce..45c3dd8 100644 --- a/src/commands/info/botInfo.ts +++ b/src/commands/info/botInfo.ts @@ -1,5 +1,7 @@ import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; import { MessageEmbed, version as discordJSVersion } from 'discord.js'; +import * as os from 'os'; +import prettyBytes from 'pretty-bytes'; export default class BotInfoCommand extends BushCommand { public constructor() { @@ -18,6 +20,19 @@ export default class BotInfoCommand extends BushCommand { } public override async exec(message: BushMessage | BushSlashMessage): Promise<void> { + enum Platform { + aix = 'AIX', + android = 'Android', + darwin = 'MacOS', + freebsd = 'FreeBSD', + linux = 'Linux', + openbsd = 'OpenBSD', + sunos = 'SunOS', + win32 = 'Windows', + cygwin = 'Cygwin', + netbsd = 'NetBSD' + } + const developers = (await util.mapIDs(client.config.owners)).map((u) => u?.tag).join('\n'); const currentCommit = (await util.shell('git rev-parse HEAD')).stdout.replace('\n', ''); let repoUrl = (await util.shell('git remote get-url origin')).stdout.replace('\n', ''); @@ -25,6 +40,19 @@ export default class BotInfoCommand extends BushCommand { const embed = new MessageEmbed() .setTitle('Bot Info:') .addField('**Uptime**', util.humanizeDuration(client.uptime!), true) + .addField( + '**Memory Usage**', + `System: ${prettyBytes(os.totalmem() - os.freemem(), { binary: true })}/${prettyBytes(os.totalmem(), { + binary: true + })}\nHeap: ${prettyBytes(process.memoryUsage().heapUsed, { binary: true })}/${prettyBytes( + process.memoryUsage().heapTotal, + { binary: true } + )}`, + true + ) + .addField('**CPU Usage**', `${client.stats.cpu}%`, true) + .addField('**Platform**', Platform[process.platform], true) + .addField('**Commands Used**', `${client.stats.commandsUsed}`, true) .addField('**Servers**', client.guilds.cache.size.toLocaleString(), true) .addField('**Users**', client.users.cache.size.toLocaleString(), true) .addField('**Discord.js Version**', discordJSVersion, true) diff --git a/src/commands/moderation/evidence.ts b/src/commands/moderation/evidence.ts index 96c3944..ae0a128 100644 --- a/src/commands/moderation/evidence.ts +++ b/src/commands/moderation/evidence.ts @@ -12,7 +12,7 @@ export default class EvidenceCommand extends BushCommand { }, args: [ { - id: 'required_argument', + id: 'case', type: 'string', prompt: { start: 'What would you like to set your first argument to be?', @@ -21,7 +21,7 @@ export default class EvidenceCommand extends BushCommand { } }, { - id: 'optional_argument', + id: 'evidence', type: 'string', prompt: { start: 'What would you like to set your second argument to be?', @@ -33,13 +33,13 @@ export default class EvidenceCommand extends BushCommand { slash: true, slashOptions: [ { - name: 'required_argument', + name: 'case', description: 'What would you like to set your first argument to be?', type: 'STRING', required: true }, { - name: 'optional_argument', + name: 'evidence', description: 'What would you like to set your second argument to be?', type: 'STRING', required: false @@ -55,6 +55,6 @@ export default class EvidenceCommand extends BushCommand { } public override async exec(message: BushMessage | BushSlashMessage): Promise<unknown> { - return await message.util.reply(`${util.emojis.error} Do not use the template command.`); + return await message.util.reply(`${util.emojis.error} Soon:tm:.`); } } diff --git a/src/commands/moulberry-bush/report.ts b/src/commands/moulberry-bush/report.ts index 878337b..e387e7d 100644 --- a/src/commands/moulberry-bush/report.ts +++ b/src/commands/moulberry-bush/report.ts @@ -35,7 +35,6 @@ export default class ReportCommand extends BushCommand { ], clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'], channel: 'guild', - restrictedGuilds: ['516977525906341928'], slash: true, slashOptions: [ { @@ -50,8 +49,7 @@ export default class ReportCommand extends BushCommand { type: 'STRING', required: false } - ], - slashGuilds: ['516977525906341928'] + ] }); } @@ -59,8 +57,11 @@ export default class ReportCommand extends BushCommand { message: BushMessage, { member, evidence }: { member: GuildMember; evidence: string } ): Promise<unknown> { - if (message.guild!.id != client.consts.mappings.guilds.bush) - return await message.util.reply(`${util.emojis.error} This command can only be run in Moulberry's bush.`); + if (!message.guild || !(await message.guild.hasFeature('reporting'))) + return await message.util.reply( + `${util.emojis.error} This command can only be used in servers where reporting is enabled.` + ); + if (!member) return await message.util.reply(`${util.emojis.error} Choose someone to report`); if (member.user.id === '322862723090219008') return await message.util.reply({ @@ -70,8 +71,10 @@ export default class ReportCommand extends BushCommand { if (member.user.bot) return await message.util.reply(`${util.emojis.error} You cannot report a bot <:WeirdChamp:756283321301860382>.`); - //// if (!evidence) evidence = 'No Evidence.'; - //todo: Add channel id to db instead of hard coding it & allow in any guild + const reportChannelId = (await message.guild.getSetting('logChannels')).report; + if (!reportChannelId) + return await message.util.reply(`${util.emojis.error} This server has not setup a report logging channel.`); + //The formatting of the report is mostly copied from carl since it is pretty good when it actually works const reportEmbed = new MessageEmbed() .setFooter(`Reporter ID: ${message.author.id} Reported ID: ${member.user.id}`) @@ -106,11 +109,11 @@ export default class ReportCommand extends BushCommand { reportEmbed.addField('Attachment', message.attachments.first()!.url); } } - const reportChannel = client.channels.cache.get('782972723654688848') as unknown as BushTextChannel; + const reportChannel = client.channels.cache.get(reportChannelId) as unknown as BushTextChannel; await reportChannel.send({ embeds: [reportEmbed] }).then(async (ReportMessage) => { try { - await ReportMessage.react(util.emojis.success); - await ReportMessage.react(util.emojis.error); + await ReportMessage.react(util.emojis.check); + await ReportMessage.react(util.emojis.cross); } catch { void client.console.warn('ReportCommand', 'Could not react to report message.'); } diff --git a/src/commands/utilities/price.ts b/src/commands/utilities/price.ts index 64d335d..9ef997a 100644 --- a/src/commands/utilities/price.ts +++ b/src/commands/utilities/price.ts @@ -137,7 +137,6 @@ export default class PriceCommand extends BushCommand { threshold: 0.7, ignoreLocation: true })?.search(parsedItem); - client.console.debug(_, 4); parsedItem = _[0]?.item; } diff --git a/src/commands/utilities/whoHasRole.ts b/src/commands/utilities/whoHasRole.ts index 73a9920..f096cee 100644 --- a/src/commands/utilities/whoHasRole.ts +++ b/src/commands/utilities/whoHasRole.ts @@ -1,5 +1,5 @@ import { BushCommand, BushMessage, BushSlashMessage } from '@lib'; -import { Role, Util } from 'discord.js'; +import { CommandInteraction, Role, Util } from 'discord.js'; export default class WhoHasRoleCommand extends BushCommand { public constructor() { @@ -38,15 +38,11 @@ export default class WhoHasRoleCommand extends BushCommand { }); } public override async exec(message: BushMessage | BushSlashMessage, args: { role: Role }): Promise<unknown> { - // console.time('whohasrole1'); + if (message.util.isSlash) await (message.interaction as CommandInteraction).deferReply(); const roleMembers = args.role.members.map((member) => `${member.user} (${Util.escapeMarkdown(member.user.tag)})`); - // console.timeEnd('whohasrole1'); - // console.time('whohasrole2'); const chunkedRoleMembers = util.chunk(roleMembers, 30); - // console.timeEnd('whohasrole2'); - // console.time('whohasrole3'); const title = `${args.role.name}'s Members [\`${args.role.members.size.toLocaleString()}\`]`; const color = util.colors.default; const embedPages = chunkedRoleMembers.map((chunk) => ({ @@ -54,7 +50,6 @@ export default class WhoHasRoleCommand extends BushCommand { description: chunk.join('\n'), color })); - // console.timeEnd('whohasrole3'); return await util.buttonPaginate(message, embedPages, null, true); } diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts index 4cc8712..5c1cb35 100644 --- a/src/lib/extensions/discord-akairo/BushClient.ts +++ b/src/lib/extensions/discord-akairo/BushClient.ts @@ -29,12 +29,13 @@ import { durationTypeCaster } from '../../../arguments/duration'; import { permissionTypeCaster } from '../../../arguments/permission'; import { roleWithDurationTypeCaster } from '../../../arguments/roleWithDuation'; import { snowflakeTypeCaster } from '../../../arguments/snowflake'; -import { UpdateCacheTask } from '../../../tasks/updateCache'; +import UpdateCacheTask from '../../../tasks/updateCache'; 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 { Stat } from '../../models/Stat'; import { StickyRole } from '../../models/StickyRole'; import { AllowedMentions } from '../../utils/AllowedMentions'; import { BushCache } from '../../utils/BushCache'; @@ -137,6 +138,15 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re public declare user: If<Ready, BushClientUser>; public declare users: BushUserManager; + public customReady = false; + public stats: { + cpu: number | undefined; + commandsUsed: bigint; + } = { + cpu: undefined, + commandsUsed: 0n + }; + public config: Config; public listenerHandler: BushListenerHandler; public inhibitorHandler: BushInhibitorHandler; @@ -301,6 +311,7 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re ActivePunishment.initModel(this.db); Level.initModel(this.db); StickyRole.initModel(this.db); + Stat.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) { @@ -312,16 +323,19 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re } } - /** Starts the bot */ + /** + * Starts the bot + */ public async start(): Promise<void> { - const that = this; eventsIntercept.patch(this); - //@ts-ignore: no typings + //@ts-expect-error: no typings this.intercept('ready', async (arg, done) => { - const promises = that.guilds.cache.map((guild) => { + await this.guilds.fetch(); + const promises = this.guilds.cache.map((guild) => { return guild.members.fetch(); }); await Promise.all(promises); + this.customReady = true; return done(null, `intercepted ${arg}`); }); diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts index 1a13c13..3f9e0b6 100644 --- a/src/lib/extensions/discord-akairo/BushClientUtil.ts +++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts @@ -568,16 +568,27 @@ export class BushClientUtil extends ClientUtil { * @param content The text to post * @returns The url of the posted text */ - public async haste(content: string): Promise<string> { + public async haste( + content: string, + substr = false + ): Promise<{ url?: string; error?: 'content too long' | 'substr' | 'unable to post' }> { + let isSubstr = false; + if (content.length > 400_000 && !substr) { + void this.handleError('haste', new Error(`content over 400,000 characters (${content.length.toLocaleString()})`)); + return { error: 'content too long' }; + } else { + content = content.substr(0, 400_000); + isSubstr = true; + } for (const url of this.#hasteURLs) { try { const res: hastebinRes = await got.post(`${url}/documents`, { body: content }).json(); - return `${url}/${res.key}`; + return { url: `${url}/${res.key}`, error: isSubstr ? 'substr' : undefined }; } catch { void client.console.error('haste', `Unable to upload haste to ${url}`); } } - return 'Unable to post'; + return { error: 'unable to post' }; } /** @@ -856,13 +867,19 @@ export class BushClientUtil extends ClientUtil { * * Embed Description Limit = 4096 characters * * Embed Field Limit = 1024 characters */ - public async codeblock(code: string, length: number, language?: CodeBlockLang): Promise<string> { + public async codeblock(code: string, length: number, language?: CodeBlockLang, substr = false): Promise<string> { let hasteOut = ''; const prefix = `\`\`\`${language}\n`; const suffix = '\n```'; language = language ?? 'txt'; - if (code.length + (prefix + suffix).length >= length) - hasteOut = `Too large to display. Hastebin: ${await this.haste(code)}`; + if (code.length + (prefix + suffix).length >= length) { + const haste = await this.haste(code, substr); + hasteOut = `Too large to display. ${ + haste.url + ? `Hastebin: ${haste.url}${haste.error ? `(${haste.error})` : ''}` + : `${this.emojis.error} Hastebin: ${haste.error}` + }`; + } const FormattedHaste = hasteOut.length ? `\n${hasteOut}` : ''; const shortenedCode = hasteOut ? code.substring(0, length - (prefix + FormattedHaste + suffix).length) : code; @@ -946,7 +963,7 @@ export class BushClientUtil extends ClientUtil { input = typeof input !== 'string' ? this.inspect(input, inspectOptions ?? undefined) : input; input = this.discord.cleanCodeBlockContent(input); input = this.redact(input); - return this.codeblock(input, length, language); + return this.codeblock(input, length, language, true); } public async inspectCleanRedactHaste(input: any, inspectOptions?: BushInspectOptions) { @@ -1162,8 +1179,6 @@ export class BushClientUtil extends ClientUtil { extraInfo?: Snowflake; }): Promise<ActivePunishment | null> { const expires = options.duration ? new Date(new Date().getTime() + options.duration ?? 0) : undefined; - client.console.debug(expires, 1); - client.console.debug(typeof expires); const user = (await util.resolveNonCachedUser(options.user))!.id; const guild = client.guilds.resolveId(options.guild)!; const type = this.#findTypeEnum(options.type)!; diff --git a/src/lib/extensions/discord-akairo/BushCommandHandler.ts b/src/lib/extensions/discord-akairo/BushCommandHandler.ts index c533832..f8dcd93 100644 --- a/src/lib/extensions/discord-akairo/BushCommandHandler.ts +++ b/src/lib/extensions/discord-akairo/BushCommandHandler.ts @@ -1,6 +1,5 @@ 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'; @@ -8,8 +7,6 @@ import { BushSlashMessage } from './BushSlashMessage'; export type BushCommandHandlerOptions = CommandHandlerOptions; -const commandHandlerEvents = BushConstants.CommandHandlerEvents; - export interface BushCommandHandlerEvents extends CommandHandlerEvents { commandBlocked: [message: BushMessage, command: BushCommand, reason: string]; diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts index 12db49a..efecdcd 100644 --- a/src/lib/extensions/discord.js/BushGuild.ts +++ b/src/lib/extensions/discord.js/BushGuild.ts @@ -37,7 +37,6 @@ export class BushGuild extends Guild { } public async getSetting<K extends keyof GuildModel>(setting: K): Promise<GuildModel[K]> { - // client.console.debug(`getSetting: ${setting}`); return ( client.cache.guilds.get(this.id)?.[setting] ?? ((await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }))[setting] @@ -45,7 +44,6 @@ export class BushGuild extends Guild { } public async setSetting<K extends keyof GuildModel>(setting: K, value: GuildDB[K]): Promise<GuildDB> { - // client.console.debug(`setSetting: ${setting}`); const row = (await GuildDB.findByPk(this.id)) ?? GuildDB.build({ id: this.id }); row[setting] = value; client.cache.guilds.set(this.id, row.toJSON() as GuildDB); diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts index 2c41873..ab4eee4 100644 --- a/src/lib/extensions/discord.js/BushGuildMember.ts +++ b/src/lib/extensions/discord.js/BushGuildMember.ts @@ -93,8 +93,12 @@ export class BushGuildMember extends GuildMember { : undefined; const dmSuccess = await this.send({ content: `You have been ${punishment} in **${this.guild.name}** ${ - duration !== null || duration !== undefined ? (duration ? `for ${util.humanizeDuration(duration)}` : 'permanently') : '' - }for **${reason ?? 'No reason provided'}**.${ending ? `\n\n${ending}` : ''}`, + duration !== null && duration !== undefined + ? duration + ? `for ${util.humanizeDuration(duration)} ` + : 'permanently ' + : '' + }for **${reason?.trim() ?? 'No reason provided'}**.`, embeds: dmEmbed ? [dmEmbed] : undefined }).catch(() => false); return !!dmSuccess; @@ -124,7 +128,6 @@ export class BushGuildMember extends GuildMember { } public async addRole(options: AddRoleOptions): Promise<AddRoleResponse> { - client.console.debug(`addRole: ${options.role.name}`); const ifShouldAddRole = this.#checkIfShouldAddRole(options.role); if (ifShouldAddRole !== true) return ifShouldAddRole; @@ -144,7 +147,6 @@ export class BushGuildMember extends GuildMember { if (!modlog && options.addToModlog) return 'error creating modlog entry'; if (options.addToModlog || options.duration) { - client.console.debug('got to punishment'); const punishmentEntrySuccess = await util.createPunishmentEntry({ type: 'role', user: this, @@ -164,7 +166,6 @@ export class BushGuildMember extends GuildMember { } public async removeRole(options: RemoveRoleOptions): Promise<RemoveRoleResponse> { - client.console.debug(`removeRole: ${options.role.name}`); const ifShouldAddRole = this.#checkIfShouldAddRole(options.role); if (ifShouldAddRole !== true) return ifShouldAddRole; diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts index 3dbb0ea..6933794 100644 --- a/src/lib/models/Guild.ts +++ b/src/lib/models/Guild.ts @@ -82,6 +82,10 @@ export const guildFeaturesObj = { name: 'Sticky Roles', description: 'Restores past roles to a user when they rejoin.' }, + reporting: { + name: 'Reporting', + description: 'Allow users to make reports.' + }, modsCanPunishMods: { name: 'Mods Can Punish Mods', description: 'Allow moderators to punish other moderators.' @@ -96,6 +100,10 @@ export const guildLogsObj = { moderation: { description: 'Sends a message in this channel every time a moderation action is performed.', configurable: false + }, + report: { + description: 'Logs user reports.', + configurable: true } }; export type GuildLogType = keyof typeof guildLogsObj; diff --git a/src/lib/models/Stat.ts b/src/lib/models/Stat.ts new file mode 100644 index 0000000..9391ad4 --- /dev/null +++ b/src/lib/models/Stat.ts @@ -0,0 +1,58 @@ +import { DataTypes, Sequelize } from 'sequelize'; +import { BaseModel } from './BaseModel'; +import { NEVER_USED } from './__helpers'; + +export interface StatModel { + environment: 'production' | 'development' | 'beta'; + commandsUsed: bigint; +} + +export interface StatModelCreationAttributes { + environment: 'production' | 'development' | 'beta'; + commandsUsed: bigint; +} + +export class Stat extends BaseModel<StatModel, StatModelCreationAttributes> implements StatModel { + /** + * The bot's environment. + */ + public get environment(): 'production' | 'development' | 'beta' { + throw new Error(NEVER_USED); + } + public set environment(_: 'production' | 'development' | 'beta') { + throw new Error(NEVER_USED); + } + + /** + * The number of commands used + */ + public get commandsUsed(): bigint { + throw new Error(NEVER_USED); + } + public set commandsUsed(_: bigint) { + throw new Error(NEVER_USED); + } + + public static initModel(sequelize: Sequelize): void { + Stat.init( + { + environment: { + type: DataTypes.STRING, + primaryKey: true + }, + commandsUsed: { + type: DataTypes.TEXT, + allowNull: false, + get: function (): bigint { + return BigInt(this.getDataValue('commandsUsed') as unknown as string); + }, + set: function (val: bigint) { + return this.setDataValue('commandsUsed', `${val}` as any); + }, + defaultValue: '0' + } + }, + { sequelize } + ); + } +} diff --git a/src/listeners/client/ready.ts b/src/listeners/client/ready.ts index cf616ce..fc71bb9 100644 --- a/src/listeners/client/ready.ts +++ b/src/listeners/client/ready.ts @@ -1,4 +1,4 @@ -import { BushListener } from '@lib'; +import { BushListener, Guild } from '@lib'; import chalk from 'chalk'; export default class ReadyListener extends BushListener { @@ -23,5 +23,13 @@ export default class ReadyListener extends BushListener { }` ) ); + + const guilds = await Guild.findAll(); + const needToCreate = []; + for (const [, guild] of client.guilds.cache) { + const find = guilds.find((g) => guild.id === g.id); + if (!find) needToCreate.push(guild.id); + } + await Guild.bulkCreate(needToCreate.map((id) => ({ id }))); } } diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts index 4514ca0..567cd27 100644 --- a/src/listeners/commands/commandError.ts +++ b/src/listeners/commands/commandError.ts @@ -159,8 +159,16 @@ export default class CommandErrorListener extends BushListener { }; const ret: string[] = []; - const promises: Promise<string>[] = []; - const pair: { [key: string]: string } = {}; + const promises: Promise<{ + url?: string | undefined; + error?: 'content too long' | 'substr' | 'unable to post' | undefined; + }>[] = []; + const pair: { + [key: string]: { + url?: string | undefined; + error?: 'content too long' | 'substr' | 'unable to post' | undefined; + }; + } = {}; for (const element in error) { if (['stack', 'name', 'message'].includes(element)) continue; @@ -186,7 +194,11 @@ export default class CommandErrorListener extends BushListener { ret.push( `**Error ${util.capitalizeFirstLetter(element)}:** ${ typeof (error as any)[element] === 'object' - ? `[haste](${pair[element]})` + ? `${ + pair[element].url + ? `[haste](${pair[element].url})${pair[element].error ? ` - ${pair[element].error}` : ''}` + : pair[element].error + }` : `\`${util.discord.escapeInlineCode(util.inspectAndRedact((error as any)[element], inspectOptions))}\`` }` ); diff --git a/src/listeners/commands/commandStarted.ts b/src/listeners/commands/commandStarted.ts index f74a765..a9284ed 100644 --- a/src/listeners/commands/commandStarted.ts +++ b/src/listeners/commands/commandStarted.ts @@ -16,5 +16,7 @@ export default class CommandStartedListener extends BushListener { }.`, true ); + + client.stats.commandsUsed = client.stats.commandsUsed + 1n; } } diff --git a/src/listeners/message/automodCreate.ts b/src/listeners/message/automodCreate.ts index 5993e7a..94b73c7 100644 --- a/src/listeners/message/automodCreate.ts +++ b/src/listeners/message/automodCreate.ts @@ -116,7 +116,7 @@ export default class AutomodMessageCreateListener extends BushListener { message.url })\n**Blacklisted Words:** ${util.surroundArray(Object.keys(offences), '`').join(', ')}` ) - .addField('Message Content', `${await util.codeblock(message.content, 1024)}`) + .addField('Message Content', `${(await util.codeblock(message.content, 1024), true)}`) .setColor(color) .setTimestamp() ] diff --git a/src/listeners/message/verbose.ts b/src/listeners/message/verbose.ts index 517771e..b6085fb 100644 --- a/src/listeners/message/verbose.ts +++ b/src/listeners/message/verbose.ts @@ -11,7 +11,7 @@ export default class MessageVerboseListener extends BushListener { } public override exec(...[message]: BushClientEvents['messageCreate']): void { - if (client.isReady()) { + if (client.customReady) { if (message.channel?.type === 'DM') return; void client.console.verbose( 'messageVerbose', diff --git a/src/tasks/cpuUsage.ts b/src/tasks/cpuUsage.ts new file mode 100644 index 0000000..a7398d7 --- /dev/null +++ b/src/tasks/cpuUsage.ts @@ -0,0 +1,15 @@ +import { BushTask } from '@lib'; +import * as osu from 'node-os-utils'; + +export default class CpuUsageTask extends BushTask { + public constructor() { + super('cpuUsage', { + delay: 60_000, // 1 minute + runOnStart: true + }); + } + public override async exec(): Promise<void> { + const cpu = await osu.cpu.usage(client.stats.cpu === undefined ? 100 : 60_000); + client.stats.cpu = cpu; + } +} diff --git a/src/tasks/updateCache.ts b/src/tasks/updateCache.ts index 69919d8..e9d0cc6 100644 --- a/src/tasks/updateCache.ts +++ b/src/tasks/updateCache.ts @@ -4,7 +4,7 @@ import { Global } from '../lib/models/Global'; import { Guild } from '../lib/models/Guild'; import config from './../config/options'; -export class UpdateCacheTask extends BushTask { +export default class UpdateCacheTask extends BushTask { public constructor() { super('updateCache', { delay: 300_000, // 5 minutes @@ -358,6 +358,13 @@ __metadata: languageName: node linkType: hard +"@types/node-os-utils@npm:^1": + version: 1.2.0 + resolution: "@types/node-os-utils@npm:1.2.0" + checksum: 5dc8339a297689e9af275e17eedcf01bd601192d248aa8153d58d4a0969114703bc31768259534ffe6128a39665706f401f210c2b95a830cc975a7870b1a60c5 + languageName: node + linkType: hard + "@types/node@npm:*": version: 16.7.10 resolution: "@types/node@npm:16.7.10" @@ -773,6 +780,7 @@ __metadata: "@types/module-alias": ^2 "@types/node": ^14.14.22 "@types/node-fetch": ^2 + "@types/node-os-utils": ^1 "@types/numeral": ^2 "@types/tinycolor2": ^1 "@types/uuid": ^8.3.0 @@ -800,10 +808,12 @@ __metadata: module-alias: ^2.2.2 moment: ^2.29.1 node-fetch: ^2.6.1 + node-os-utils: ^1.3.5 numeral: ^2.0.6 pg: ^8.5.1 pg-hstore: ^2.3.3 prettier: ^2.3.2 + pretty-bytes: ^5.6.0 rimraf: ^3.0.2 sequelize: ^6.5.0 simplify-number: ^1.0.0 @@ -2312,6 +2322,13 @@ discord.js@NotEnoughUpdates/discord.js: languageName: node linkType: hard +"node-os-utils@npm:^1.3.5": + version: 1.3.5 + resolution: "node-os-utils@npm:1.3.5" + checksum: be296de8de05b0b577ea71c46a37d79f01b8693c8e038eaf252b6fa93898446385efaab677b64505e4bffa83954525fceff15eaecad200c5763f4c90f5a21246 + languageName: node + linkType: hard + "nopt@npm:^5.0.0": version: 5.0.0 resolution: "nopt@npm:5.0.0" @@ -2587,6 +2604,13 @@ discord.js@NotEnoughUpdates/discord.js: languageName: node linkType: hard +"pretty-bytes@npm:^5.6.0": + version: 5.6.0 + resolution: "pretty-bytes@npm:5.6.0" + checksum: 9c082500d1e93434b5b291bd651662936b8bd6204ec9fa17d563116a192d6d86b98f6d328526b4e8d783c07d5499e2614a807520249692da9ec81564b2f439cd + languageName: node + linkType: hard + "prism-media@npm:^1.3.1": version: 1.3.2 resolution: "prism-media@npm:1.3.2" |