aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json3
-rw-r--r--src/arguments/roleWithDuation.ts2
-rw-r--r--src/commands/config/config.ts1
-rw-r--r--src/commands/dev/reload.ts4
-rw-r--r--src/commands/dev/sh.ts8
-rw-r--r--src/commands/dev/superUser.ts39
-rw-r--r--src/commands/info/botInfo.ts28
-rw-r--r--src/commands/moderation/evidence.ts10
-rw-r--r--src/commands/moulberry-bush/report.ts23
-rw-r--r--src/commands/utilities/price.ts1
-rw-r--r--src/commands/utilities/whoHasRole.ts9
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts24
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts33
-rw-r--r--src/lib/extensions/discord-akairo/BushCommandHandler.ts3
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts2
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts11
-rw-r--r--src/lib/models/Guild.ts8
-rw-r--r--src/lib/models/Stat.ts58
-rw-r--r--src/listeners/client/ready.ts10
-rw-r--r--src/listeners/commands/commandError.ts18
-rw-r--r--src/listeners/commands/commandStarted.ts2
-rw-r--r--src/listeners/message/automodCreate.ts2
-rw-r--r--src/listeners/message/verbose.ts2
-rw-r--r--src/tasks/cpuUsage.ts15
-rw-r--r--src/tasks/updateCache.ts2
-rw-r--r--yarn.lock24
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
diff --git a/yarn.lock b/yarn.lock
index d219cb7..1039a6a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"