aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/arguments/duration.ts55
-rw-r--r--src/commands/config/muteRole.ts57
-rw-r--r--src/commands/config/prefix.ts16
-rw-r--r--src/commands/config/welcomeChannel.ts49
-rw-r--r--src/commands/dev/eval.ts4
-rw-r--r--src/commands/dev/reload.ts7
-rw-r--r--src/commands/dev/setLevel.ts12
-rw-r--r--src/commands/dev/superUser.ts2
-rw-r--r--src/commands/info/botInfo.ts17
-rw-r--r--src/commands/info/help.ts10
-rw-r--r--src/commands/info/ping.ts2
-rw-r--r--src/commands/info/pronouns.ts7
-rw-r--r--src/commands/moderation/ban.ts148
-rw-r--r--src/commands/moderation/block.ts (renamed from src/tasks/unrole.ts)0
-rw-r--r--src/commands/moderation/kick.ts18
-rw-r--r--src/commands/moderation/modlog.ts36
-rw-r--r--src/commands/moderation/mute.ts166
-rw-r--r--src/commands/moderation/unban.ts0
-rw-r--r--src/commands/moderation/unblock.ts0
-rw-r--r--src/commands/moderation/unmute.ts0
-rw-r--r--src/commands/moderation/warn.ts6
-rw-r--r--src/commands/moulberry-bush/capePerms.ts138
-rw-r--r--src/commands/moulberry-bush/giveawayPing.ts35
-rw-r--r--src/commands/moulberry-bush/level.ts15
-rw-r--r--src/commands/moulberry-bush/rule.ts228
-rw-r--r--src/inhibitors/blacklist/blacklist.ts14
-rw-r--r--src/inhibitors/blacklist/guildBlacklist.ts19
-rw-r--r--src/inhibitors/blacklist/userBlacklist.ts19
-rw-r--r--src/inhibitors/commands/disabledCommand.ts19
-rw-r--r--src/inhibitors/noCache.ts21
-rw-r--r--src/lib/extensions/BushArgumentOptions.ts59
-rw-r--r--src/lib/extensions/BushArgumentTypeCaster.ts4
-rw-r--r--src/lib/extensions/BushClient.ts44
-rw-r--r--src/lib/extensions/BushClientUtil.ts66
-rw-r--r--src/lib/extensions/BushCommand.ts20
-rw-r--r--src/lib/extensions/BushCommandHandler.ts5
-rw-r--r--src/lib/extensions/BushInhibitor.ts9
-rw-r--r--src/lib/extensions/BushInteractionMessage.ts5
-rw-r--r--src/lib/extensions/BushTaskHandler.ts1
-rw-r--r--src/lib/models/Ban.ts13
-rw-r--r--src/lib/models/Global.ts2
-rw-r--r--src/lib/models/Guild.ts22
-rw-r--r--src/lib/models/Level.ts1
-rw-r--r--src/lib/models/Modlog.ts38
-rw-r--r--src/lib/models/Mute.ts90
-rw-r--r--src/lib/models/PunishmentRole.ts93
-rw-r--r--src/lib/models/index.ts4
-rw-r--r--src/lib/utils/BushCache.ts6
-rw-r--r--src/lib/utils/BushLogger.ts8
-rw-r--r--src/lib/utils/CanvasProgressBar.ts10
-rw-r--r--src/listeners/client/ready.ts14
-rw-r--r--src/listeners/commands/commandBlocked.ts2
-rw-r--r--src/listeners/commands/commandError.ts6
-rw-r--r--src/listeners/commands/commandMissingPermissions.ts55
-rw-r--r--src/listeners/commands/commandStarted.ts2
-rw-r--r--src/listeners/commands/slashBlocked.ts2
-rw-r--r--src/listeners/commands/slashCommandError.ts15
-rw-r--r--src/listeners/commands/slashMissingPermissions.ts58
-rw-r--r--src/listeners/commands/slashStarted.ts8
-rw-r--r--src/listeners/message/level.ts3
-rw-r--r--src/listeners/other/consoleListener.ts17
-rw-r--r--src/listeners/other/promiseRejection.ts2
-rw-r--r--src/tasks/removePunishmentRole.ts0
-rw-r--r--src/tasks/unmute.ts45
-rw-r--r--src/tasks/updateCache.ts18
65 files changed, 1340 insertions, 527 deletions
diff --git a/src/arguments/duration.ts b/src/arguments/duration.ts
new file mode 100644
index 0000000..a2f7751
--- /dev/null
+++ b/src/arguments/duration.ts
@@ -0,0 +1,55 @@
+import { BushArgumentTypeCaster } from '../lib/extensions/BushArgumentTypeCaster';
+import { BushMessage } from '../lib/extensions/BushMessage';
+
+// Stolen from @Mzato0001 (pr to discord akairo that hasn't been merged yet)
+const TimeUnits = {
+ years: {
+ label: '(?:years?|y)',
+ value: 1000 * 60 * 60 * 24 * 365
+ },
+ months: {
+ label: '(?:months?|mo)',
+ value: 1000 * 60 * 60 * 24 * 30
+ },
+ weeks: {
+ label: '(?:weeks?|w)',
+ value: 1000 * 60 * 60 * 24 * 7
+ },
+ days: {
+ label: '(?:days?|d)',
+ value: 1000 * 60 * 60 * 24
+ },
+ hours: {
+ label: '(?:hours?|hrs?|h)',
+ value: 1000 * 60 * 60
+ },
+ minutes: {
+ label: '(?:minutes?|mins?|m)',
+ value: 1000 * 60
+ },
+ seconds: {
+ label: '(?:seconds?|secs?|s)',
+ value: 1000
+ },
+ milliseconds: {
+ label: '(?:milliseconds?|msecs?|ms)',
+ value: 1
+ }
+};
+export const durationTypeCaster: BushArgumentTypeCaster = async (_message: BushMessage, phrase): Promise<number> => {
+ if (!phrase) return null;
+
+ const regexString = Object.entries(TimeUnits)
+ .map(([name, { label }]) => String.raw`(?:(?<${name}>-?(?:\d+)?\.?\d+) *${label})?`)
+ .join('\\s*');
+ const match = new RegExp(`^${regexString}$`, 'i').exec(phrase);
+ if (!match) return null;
+
+ let milliseconds = 0;
+ for (const key in match.groups) {
+ const value = Number(match.groups[key] || 0);
+ milliseconds += value * TimeUnits[key].value;
+ }
+
+ return milliseconds;
+};
diff --git a/src/commands/config/muteRole.ts b/src/commands/config/muteRole.ts
new file mode 100644
index 0000000..f51c5ce
--- /dev/null
+++ b/src/commands/config/muteRole.ts
@@ -0,0 +1,57 @@
+import { Role } from 'discord.js';
+import { BushCommand } from '../../lib/extensions/BushCommand';
+import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
+import { BushMessage } from '../../lib/extensions/BushMessage';
+import { Guild } from '../../lib/models';
+import AllowedMentions from '../../lib/utils/AllowedMentions';
+
+export default class MuteRoleCommand extends BushCommand {
+ constructor() {
+ super('muteRole', {
+ aliases: ['muterole'],
+ category: 'config',
+ description: {
+ content: 'Set the prefix of the current server (resets to default if prefix is not given)',
+ usage: 'prefix [prefix]',
+ examples: ['prefix', 'prefix +']
+ },
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'],
+ args: [
+ {
+ id: 'role',
+ type: 'role',
+ prompt: {
+ start: "What would you like to set the server's mute role to?",
+ retry: '{error} Choose a valid role.',
+ optional: false
+ }
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ type: 'ROLE',
+ name: 'role',
+ description: 'The mute role for this server.',
+ required: true
+ }
+ ]
+ });
+ }
+
+ async exec(message: BushMessage | BushSlashMessage, args: { role: Role }): Promise<void> {
+ let row = await Guild.findByPk(message.guild.id);
+ if (!row) {
+ row = Guild.build({
+ id: message.guild.id
+ });
+ }
+ row.muteRole = args.role.id;
+ await row.save();
+ await message.util.send({
+ content: `${this.client.util.emojis.success} Changed the mute role to <@&${args.role.id}>.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ }
+}
diff --git a/src/commands/config/prefix.ts b/src/commands/config/prefix.ts
index 1326426..5b73a1a 100644
--- a/src/commands/config/prefix.ts
+++ b/src/commands/config/prefix.ts
@@ -1,6 +1,6 @@
-import { Message } from 'discord.js';
import { BushCommand } from '../../lib/extensions/BushCommand';
import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
+import { BushMessage } from '../../lib/extensions/BushMessage';
import { Guild } from '../../lib/models';
export default class PrefixCommand extends BushCommand {
@@ -11,10 +11,16 @@ export default class PrefixCommand extends BushCommand {
args: [
{
id: 'prefix',
- type: 'string'
+ type: 'string',
+ prompt: {
+ start: 'What would you like the new prefix to be?',
+ retry: '{error} Choose a valid prefix',
+ optional: true
+ }
}
],
- userPermissions: ['MANAGE_GUILD'],
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'],
description: {
content: 'Set the prefix of the current server (resets to default if prefix is not given)',
usage: 'prefix [prefix]',
@@ -32,7 +38,7 @@ export default class PrefixCommand extends BushCommand {
});
}
- async exec(message: Message | BushSlashMessage, { prefix }: { prefix?: string }): Promise<void> {
+ async exec(message: BushMessage | BushSlashMessage, { prefix }: { prefix?: string }): Promise<void> {
let row = await Guild.findByPk(message.guild.id);
if (!row) {
row = Guild.build({
@@ -41,7 +47,7 @@ export default class PrefixCommand extends BushCommand {
}
await row.update({ prefix: prefix || this.client.config.prefix });
if (prefix) {
- await message.util.send(`${this.client.util.emojis.success} changed prefix from \`${prefix}\``);
+ await message.util.send(`${this.client.util.emojis.success} changed prefix from \`${prefix}\` to `);
} else {
await message.util.send(`${this.client.util.emojis.success} reset prefix to \`${this.client.config.prefix}\``);
}
diff --git a/src/commands/config/welcomeChannel.ts b/src/commands/config/welcomeChannel.ts
new file mode 100644
index 0000000..72e55f1
--- /dev/null
+++ b/src/commands/config/welcomeChannel.ts
@@ -0,0 +1,49 @@
+import { User } from 'discord.js';
+import { BushCommand } from '../../lib/extensions/BushCommand';
+import { BushMessage } from '../../lib/extensions/BushMessage';
+import { Global } from '../../lib/models';
+
+export default class WelcomeChannelCommand extends BushCommand {
+ public constructor() {
+ super('welcomeChannel', {
+ aliases: ['welcomechannel', 'wc'],
+ category: 'config',
+ description: {
+ content: 'Configure the what channel you want the bot to send a message in when someone joins the server.',
+ usage: 'welcomechannel [channel]',
+ examples: ['welcomechannel #welcome']
+ },
+ clientPermissions: ['SEND_MESSAGES'],
+ ownerOnly: true
+ });
+ }
+ public async exec(message: BushMessage, args: { action: 'add' | 'remove'; user: User }): Promise<unknown> {
+ if (!this.client.config.owners.includes(message.author.id))
+ return await message.util.reply(`${this.client.util.emojis.error} Only my developers can run this command...`);
+
+ const superUsers = (await Global.findByPk(this.client.config.dev ? 'development' : 'production')).superUsers;
+ let success;
+ if (args.action === 'add') {
+ if (superUsers.includes(args.user.id)) {
+ return message.util.reply(`${this.client.util.emojis.warn} \`${args.user.tag}\` is already a superuser.`);
+ }
+ success = await this.client.util.insertOrRemoveFromGlobal('add', 'superUsers', args.user.id).catch(() => false);
+ } else {
+ if (!superUsers.includes(args.user.id)) {
+ return message.util.reply(`${this.client.util.emojis.warn} \`${args.user.tag}\` is not superuser.`);
+ }
+ success = await this.client.util.insertOrRemoveFromGlobal('remove', 'superUsers', args.user.id).catch(() => false);
+ }
+ if (success) {
+ const responses = [args.action == 'remove' ? `` : 'made', args.action == 'remove' ? 'is no longer' : ''];
+ return message.util.reply(
+ `${this.client.util.emojis.success} ${responses[0]} \`${args.user.tag}\` ${responses[1]} a superuser.`
+ );
+ } else {
+ const response = [args.action == 'remove' ? `removing` : 'making', args.action == 'remove' ? `from` : 'to'];
+ return message.util.reply(
+ `${this.client.util.emojis.error} There was an error ${response[0]} \`${args.user.tag}\` ${response[1]} the superuser list.`
+ );
+ }
+ }
+}
diff --git a/src/commands/dev/eval.ts b/src/commands/dev/eval.ts
index d2fe432..310a2c5 100644
--- a/src/commands/dev/eval.ts
+++ b/src/commands/dev/eval.ts
@@ -10,7 +10,7 @@ import { BushMessage } from '../../lib/extensions/BushMessage';
const clean = (text) => {
if (typeof text === 'string') {
- return (text = Util.cleanCodeBlockContent(text));
+ return Util.cleanCodeBlockContent(text);
} else return text;
};
const sh = promisify(exec);
@@ -186,7 +186,7 @@ export default class EvalCommand extends BushCommand {
{ Global } = await import('../../lib/models/Global'),
{ Guild } = await import('../../lib/models/Guild'),
{ Level } = await import('../../lib/models/Level'),
- { Modlog } = await import('../../lib/models/Modlog'),
+ { ModLog } = await import('../../lib/models/ModLog'),
{ StickyRole } = await import('../../lib/models/StickyRole');
if (code[code.lang].replace(/ /g, '').includes('9+10' || '10+9')) {
output = 21;
diff --git a/src/commands/dev/reload.ts b/src/commands/dev/reload.ts
index 94e31b0..f5fee88 100644
--- a/src/commands/dev/reload.ts
+++ b/src/commands/dev/reload.ts
@@ -1,6 +1,5 @@
import { stripIndent } from 'common-tags';
import { Message } from 'discord.js';
-import { SlashCommandOption } from '../../lib/extensions/BushClientUtil';
import { BushCommand } from '../../lib/extensions/BushCommand';
import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
@@ -51,11 +50,7 @@ export default class ReloadCommand extends BushCommand {
}
}
- public async exec(message: Message, { fast }: { fast: boolean }): Promise<void> {
+ public async exec(message: Message | BushSlashMessage, { fast }: { fast: boolean }): Promise<void> {
await message.util.send(await this.getResponse(fast));
}
-
- public async execSlash(message: BushSlashMessage, { fast }: { fast: SlashCommandOption<boolean> }): Promise<void> {
- await message.interaction.reply(await this.getResponse(fast?.value));
- }
}
diff --git a/src/commands/dev/setLevel.ts b/src/commands/dev/setLevel.ts
index ca555db..f536109 100644
--- a/src/commands/dev/setLevel.ts
+++ b/src/commands/dev/setLevel.ts
@@ -1,7 +1,5 @@
import { Message, User } from 'discord.js';
-import { SlashCommandOption } from '../../lib/extensions/BushClientUtil';
import { BushCommand } from '../../lib/extensions/BushCommand';
-import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
import { Level } from '../../lib/models';
import AllowedMentions from '../../lib/utils/AllowedMentions';
@@ -71,14 +69,4 @@ export default class SetLevelCommand extends BushCommand {
allowedMentions: AllowedMentions.none()
});
}
-
- async execSlash(
- message: BushSlashMessage,
- { user, level }: { user: SlashCommandOption<void>; level: SlashCommandOption<number> }
- ): Promise<void> {
- await message.interaction.reply({
- content: await this.setLevel(user.user, level.value),
- allowedMentions: AllowedMentions.none()
- });
- }
}
diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts
index c3ed0b0..4562e6f 100644
--- a/src/commands/dev/superUser.ts
+++ b/src/commands/dev/superUser.ts
@@ -2,7 +2,7 @@ import { Constants } from 'discord-akairo';
import { User } from 'discord.js';
import { BushCommand } from '../../lib/extensions/BushCommand';
import { BushMessage } from '../../lib/extensions/BushMessage';
-import { Global } from '../../lib/models/Global';
+import { Global } from '../../lib/models';
export default class SuperUserCommand extends BushCommand {
public constructor() {
diff --git a/src/commands/info/botInfo.ts b/src/commands/info/botInfo.ts
index 120527d..3db4151 100644
--- a/src/commands/info/botInfo.ts
+++ b/src/commands/info/botInfo.ts
@@ -1,7 +1,6 @@
import { Message, MessageEmbed } from 'discord.js';
import { duration } from 'moment';
import { BushCommand } from '../../lib/extensions/BushCommand';
-import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
export default class BotInfoCommand extends BushCommand {
constructor() {
@@ -13,11 +12,13 @@ export default class BotInfoCommand extends BushCommand {
usage: 'botinfo',
examples: ['botinfo']
},
- slash: true
+ slash: true,
+ clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'],
+ userPermissions: ['SEND_MESSAGES']
});
}
- private async generateEmbed(): Promise<MessageEmbed> {
+ public async exec(message: Message): Promise<void> {
const owners = (await this.client.util.mapIDs(this.client.ownerID)).map((u) => u.tag).join('\n');
const currentCommit = (await this.client.util.shell('git rev-parse HEAD')).stdout.replace('\n', '');
const repoUrl = (await this.client.util.shell('git remote get-url origin')).stdout.replace('\n', '');
@@ -44,14 +45,6 @@ export default class BotInfoCommand extends BushCommand {
}
])
.setTimestamp();
- return embed;
- }
-
- public async exec(message: Message): Promise<void> {
- await message.util.send({ embeds: [await this.generateEmbed()] });
- }
-
- public async execSlash(message: BushSlashMessage): Promise<void> {
- await message.interaction.reply({ embeds: [await this.generateEmbed()] });
+ await message.util.reply({ embeds: [embed] });
}
}
diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts
index 0efc6b3..8969efc 100644
--- a/src/commands/info/help.ts
+++ b/src/commands/info/help.ts
@@ -14,6 +14,7 @@ export default class HelpCommand extends BushCommand {
examples: ['help prefix']
},
clientPermissions: ['EMBED_LINKS'],
+
args: [
{
id: 'command',
@@ -31,15 +32,15 @@ export default class HelpCommand extends BushCommand {
flag: '--hidden'
}
],
+ slash: true,
slashOptions: [
{
type: 'STRING',
name: 'command',
- description: `The command you would like to find information about.`,
+ description: 'The command you would like to find information about.',
required: false
}
- ],
- slash: true
+ ]
});
}
@@ -79,8 +80,7 @@ export default class HelpCommand extends BushCommand {
if (command.superUserOnly && !isSuperUser) {
return false;
}
- if (command.restrictedGuilds?.includes(message.guild.id) == !true && !args.showHidden) return false;
- return true;
+ return !(command.restrictedGuilds?.includes(message.guild.id) == false && !args.showHidden);
});
const categoryNice = category.id
.replace(/(\b\w)/gi, (lc): string => lc.toUpperCase())
diff --git a/src/commands/info/ping.ts b/src/commands/info/ping.ts
index e80cfb3..3038658 100644
--- a/src/commands/info/ping.ts
+++ b/src/commands/info/ping.ts
@@ -12,6 +12,8 @@ export default class PingCommand extends BushCommand {
usage: 'ping',
examples: ['ping']
},
+ clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS'],
+ userPermissions: ['SEND_MESSAGES'],
slash: true
});
}
diff --git a/src/commands/info/pronouns.ts b/src/commands/info/pronouns.ts
index 79baeef..2175233 100644
--- a/src/commands/info/pronouns.ts
+++ b/src/commands/info/pronouns.ts
@@ -1,8 +1,6 @@
import { CommandInteraction, Message, MessageEmbed, User } from 'discord.js';
import got, { HTTPError } from 'got';
-import { SlashCommandOption } from '../../lib/extensions/BushClientUtil';
import { BushCommand } from '../../lib/extensions/BushCommand';
-import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
export const pronounMapping = {
unspecified: 'Unspecified',
@@ -55,7 +53,6 @@ export default class PronounsCommand extends BushCommand {
required: false
}
],
- slashEphemeral: true, // I'll add dynamic checking to this later
slash: true
});
}
@@ -107,8 +104,4 @@ export default class PronounsCommand extends BushCommand {
const u = user || message.author;
await this.sendResponse(message, u, u.id === message.author.id);
}
- async execSlash(message: BushSlashMessage, { user }: { user?: SlashCommandOption<void> }): Promise<void> {
- const u = user?.user || message.author;
- await this.sendResponse(message.interaction, u, u.id === message.author.id);
- }
}
diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts
index 692691e..7ce222a 100644
--- a/src/commands/moderation/ban.ts
+++ b/src/commands/moderation/ban.ts
@@ -1,18 +1,17 @@
+import { Argument } from 'discord-akairo';
import { CommandInteraction, Message, User } from 'discord.js';
import moment from 'moment';
-import { SlashCommandOption } from '../../lib/extensions/BushClientUtil';
import { BushCommand } from '../../lib/extensions/BushCommand';
-import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
-import { Ban, Guild, Modlog, ModlogType } from '../../lib/models';
+import { Ban, Guild, ModLog, ModLogType } from '../../lib/models';
-const durationAliases: Record<string, string[]> = {
+/* const durationAliases: Record<string, string[]> = {
weeks: ['w', 'weeks', 'week', 'wk', 'wks'],
days: ['d', 'days', 'day'],
hours: ['h', 'hours', 'hour', 'hr', 'hrs'],
minutes: ['m', 'min', 'mins', 'minutes', 'minute'],
months: ['mo', 'month', 'months']
};
-const durationRegex = /(?:(\d+)(d(?:ays?)?|h(?:ours?|rs?)?|m(?:inutes?|ins?)?|mo(?:nths?)?|w(?:eeks?|ks?)?)(?: |$))/g;
+const durationRegex = /(?:(\d+)(d(?:ays?)?|h(?:ours?|rs?)?|m(?:inutes?|ins?)?|mo(?:nths?)?|w(?:eeks?|ks?)?)(?: |$))/g; */
export default class BanCommand extends BushCommand {
constructor() {
@@ -25,15 +24,21 @@ export default class BanCommand extends BushCommand {
type: 'user',
prompt: {
start: 'What user would you like to ban?',
- retry: 'Invalid response. What user would you like to ban?'
+ retry: '{error} Choose a valid user to ban.'
}
},
{
id: 'reason',
- match: 'rest'
+ match: 'restContent',
+ prompt: {
+ start: 'Why would you like to ban this user?',
+ retry: '{error} Choose a ban reason.',
+ optional: true
+ }
},
{
id: 'time',
+ type: 'duration',
match: 'option',
flag: '--time'
}
@@ -41,27 +46,27 @@ export default class BanCommand extends BushCommand {
clientPermissions: ['BAN_MEMBERS'],
userPermissions: ['BAN_MEMBERS'],
description: {
- content: 'Ban a member and log it in modlogs (with optional time to unban)',
+ content: 'Ban a member from the server.',
usage: 'ban <member> <reason> [--time]',
- examples: ['ban @Tyman being cool', 'ban @Tyman being cool --time 7days']
+ examples: ['ban @user bad --time 69d']
},
slashOptions: [
{
type: 'USER',
name: 'user',
- description: 'The user to ban',
+ description: 'Who would you like to ban?',
required: true
},
{
type: 'STRING',
name: 'reason',
- description: 'The reason to show in modlogs and audit log',
+ description: 'Why are they getting banned?',
required: false
},
{
type: 'STRING',
name: 'time',
- description: 'The time the user should be banned for (default permanent)',
+ description: 'How long should they be banned for?',
required: false
}
],
@@ -72,12 +77,12 @@ export default class BanCommand extends BushCommand {
message: Message | CommandInteraction,
user: User,
reason?: string,
- time?: string
+ time?: number
): AsyncIterable<string> {
const duration = moment.duration();
- let modlogEnry: Modlog;
+ let modLogEntry: ModLog;
let banEntry: Ban;
- const translatedTime: string[] = [];
+ // const translatedTime: string[] = [];
// Create guild entry so postgres doesn't get mad when I try and add a modlog entry
await Guild.findOrCreate({
where: {
@@ -88,62 +93,58 @@ export default class BanCommand extends BushCommand {
}
});
try {
- try {
- if (time) {
- const parsed = [...time.matchAll(durationRegex)];
+ if (time) {
+ duration.add(time);
+ /* const parsed = [...time.matchAll(durationRegex)];
if (parsed.length < 1) {
- yield 'Invalid time.';
+ yield `${this.client.util.emojis.error} Invalid time.`;
return;
}
for (const part of parsed) {
const translated = Object.keys(durationAliases).find((k) => durationAliases[k].includes(part[2]));
translatedTime.push(part[1] + ' ' + translated);
duration.add(Number(part[1]), translated as 'weeks' | 'days' | 'hours' | 'months' | 'minutes');
- }
- modlogEnry = Modlog.build({
- user: user.id,
- guild: message.guild.id,
- reason,
- type: ModlogType.TEMPBAN,
- duration: duration.asMilliseconds(),
- moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
- });
- banEntry = Ban.build({
- user: user.id,
- guild: message.guild.id,
- reason,
- expires: new Date(new Date().getTime() + duration.asMilliseconds()),
- modlog: modlogEnry.id
- });
- } else {
- modlogEnry = Modlog.build({
- user: user.id,
- guild: message.guild.id,
- reason,
- type: ModlogType.BAN,
- moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
- });
- banEntry = Ban.build({
- user: user.id,
- guild: message.guild.id,
- reason,
- modlog: modlogEnry.id
- });
- }
- await modlogEnry.save();
- await banEntry.save();
- } catch (e) {
- this.client.console.error(`BanCommand`, `Error saving to database. ${e?.stack}`);
- yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`;
- return;
+ } */
+ modLogEntry = ModLog.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ type: ModLogType.TEMP_BAN,
+ duration: duration.asMilliseconds(),
+ moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
+ });
+ banEntry = Ban.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ expires: new Date(new Date().getTime() + duration.asMilliseconds()),
+ modlog: modLogEntry.id
+ });
+ } else {
+ modLogEntry = ModLog.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ type: ModLogType.BAN,
+ moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
+ });
+ banEntry = Ban.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ modlog: modLogEntry.id
+ });
}
+ await modLogEntry.save();
+ await banEntry.save();
+
try {
await user.send(
- `You were banned in ${message.guild.name} ${
- translatedTime.length >= 1 ? `for ${translatedTime.join(', ')}` : 'permanently'
- } with reason \`${reason || 'No reason given'}\``
+ `You were banned in ${message.guild.name} ${duration ? duration.humanize() : 'permanently'} with reason \`${
+ reason || 'No reason given'
+ }\``
);
- } catch (e) {
+ } catch {
yield `${this.client.util.emojis.warn} Unable to dm user`;
}
await message.guild.members.ban(user, {
@@ -152,35 +153,22 @@ export default class BanCommand extends BushCommand {
}`
});
yield `${this.client.util.emojis.success} Banned <@!${user.id}> ${
- translatedTime.length >= 1 ? `for ${translatedTime.join(', ')}` : 'permanently'
+ duration ? duration.humanize() : 'permanently'
} with reason \`${reason || 'No reason given'}\``;
} catch {
yield `${this.client.util.emojis.error} Error banning :/`;
await banEntry.destroy();
- await modlogEnry.destroy();
+ await modLogEntry.destroy();
return;
}
}
- async exec(message: Message, { user, reason, time }: { user: User; reason?: string; time?: string }): Promise<void> {
+ async exec(message: Message, { user, reason, time }: { user: User; reason?: string; time?: number | string }): Promise<void> {
+ if (typeof time === 'string') {
+ time = (await Argument.cast('duration', this.client.commandHandler.resolver, message, time)) as number;
+ //// time = this.client.commandHandler.resolver.type('duration')
+ }
for await (const response of this.genResponses(message, user, reason, time)) {
await message.util.send(response);
}
}
-
- async execSlash(
- message: BushSlashMessage,
- {
- user,
- reason,
- time
- }: {
- user: SlashCommandOption<undefined>;
- reason: SlashCommandOption<string>;
- time: SlashCommandOption<string>;
- }
- ): Promise<void> {
- for await (const response of this.genResponses(message.interaction, user.user, reason?.value, time?.value)) {
- await message.reply(response);
- }
- }
}
diff --git a/src/tasks/unrole.ts b/src/commands/moderation/block.ts
index e69de29..e69de29 100644
--- a/src/tasks/unrole.ts
+++ b/src/commands/moderation/block.ts
diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts
index d00ae55..eed3038 100644
--- a/src/commands/moderation/kick.ts
+++ b/src/commands/moderation/kick.ts
@@ -1,7 +1,7 @@
import { CommandInteraction, GuildMember, Message } from 'discord.js';
import { BushCommand } from '../../lib/extensions/BushCommand';
import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
-import { Guild, Modlog, ModlogType } from '../../lib/models';
+import { Guild, ModLog, ModLogType } from '../../lib/models';
export default class KickCommand extends BushCommand {
constructor() {
@@ -14,17 +14,19 @@ export default class KickCommand extends BushCommand {
type: 'member',
prompt: {
start: 'What user would you like to kick?',
- retry: 'Invalid response. What user would you like to kick?'
+ retry: '{error} Choose a valid user to kick.'
}
},
{
- id: 'reason'
+ id: 'reason',
+ type: 'string',
+ match: 'restContent'
}
],
clientPermissions: ['KICK_MEMBERS'],
userPermissions: ['KICK_MEMBERS'],
description: {
- content: 'Kick a member and log it in modlogs',
+ content: 'Kick a member from the server.',
usage: 'kick <member> <reason>',
examples: ['kick @Tyman being cool']
},
@@ -51,7 +53,7 @@ export default class KickCommand extends BushCommand {
user: GuildMember,
reason?: string
): AsyncIterable<string> {
- let modlogEnry: Modlog;
+ let modlogEnry: ModLog;
// Create guild entry so postgres doesn't get mad when I try and add a modlog entry
await Guild.findOrCreate({
where: {
@@ -62,16 +64,16 @@ export default class KickCommand extends BushCommand {
}
});
try {
- modlogEnry = Modlog.build({
+ modlogEnry = ModLog.build({
user: user.id,
guild: message.guild.id,
moderator: message instanceof Message ? message.author.id : message.user.id,
- type: ModlogType.KICK,
+ type: ModLogType.KICK,
reason
});
await modlogEnry.save();
} catch (e) {
- this.client.console.error(`BanCommand`, `Error saving to database. ${e?.stack}`);
+ this.client.console.error(`KickCommand`, `Error saving to database. ${e?.stack}`);
yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`;
return;
}
diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts
index 862a26d..e32df42 100644
--- a/src/commands/moderation/modlog.ts
+++ b/src/commands/moderation/modlog.ts
@@ -3,7 +3,7 @@ import { Argument } from 'discord-akairo';
import { Message, MessageEmbed } from 'discord.js';
import moment from 'moment';
import { BushCommand } from '../../lib/extensions/BushCommand';
-import { Modlog } from '../../lib/models';
+import { ModLog } from '../../lib/models';
export default class ModlogCommand extends BushCommand {
constructor() {
@@ -35,7 +35,8 @@ export default class ModlogCommand extends BushCommand {
id: 'search',
type: Argument.union('user', 'string'),
prompt: {
- start: 'What modlog id or user would you like to see?'
+ start: 'What modlog id or user would you like to see?',
+ retry: '{error} Choose a valid modlog id or user.'
}
};
if (typeof search === 'string') return { search, page: null };
@@ -45,7 +46,7 @@ export default class ModlogCommand extends BushCommand {
type: 'number',
prompt: {
start: 'What page?',
- retry: 'Not a number. What page?',
+ retry: '{error} Choose a valid page to view.',
optional: true
}
};
@@ -55,7 +56,7 @@ export default class ModlogCommand extends BushCommand {
async exec(message: Message, { search, page }: { search: string; page: number }): Promise<void> {
const foundUser = await this.client.util.resolveUserAsync(search);
if (foundUser) {
- const logs = await Modlog.findAll({
+ const logs = await ModLog.findAll({
where: {
guild: message.guild.id,
user: foundUser.id
@@ -65,24 +66,25 @@ export default class ModlogCommand extends BushCommand {
const niceLogs: string[] = [];
for (const log of logs) {
niceLogs.push(stripIndent`
- ID: ${log.id}
- Type: ${log.type.toLowerCase()}
- User: <@!${log.user}> (${log.user})
- Moderator: <@!${log.moderator}> (${log.moderator})
- Duration: ${log.duration ? moment.duration(log.duration, 'milliseconds').humanize() : 'N/A'}
- Reason: ${log.reason || 'None given'}
- ${this.client.util.ordinal(logs.indexOf(log) + 1)} action
+ **Case ID**: ${log.id}
+ **Type**: ${log.type.toLowerCase()}
+ **User**: <@!${log.user}> (${log.user})
+ **Moderator**: <@!${log.moderator}> (${log.moderator})
+ **Duration**: ${log.duration ? moment.duration(log.duration, 'milliseconds').humanize() : 'N/A'}
+ **Reason**: ${log.reason || 'None given'}
+ **${this.client.util.ordinal(logs.indexOf(log) + 1)}** action
`);
}
const chunked: string[][] = this.client.util.chunk(niceLogs, 3);
const embedPages = chunked.map(
(e, i) =>
new MessageEmbed({
- title: `Modlogs page ${i + 1}`,
- description: e.join('\n-------------------------------------------------------\n'),
+ title: foundUser.tag,
+ description: e.join('\n**---------------------------**\n'),
footer: {
text: `Page ${i + 1}/${chunked.length}`
- }
+ },
+ color: this.client.util.colors.default
})
);
if (page) {
@@ -93,15 +95,15 @@ export default class ModlogCommand extends BushCommand {
return;
}
} else if (search) {
- const entry = await Modlog.findByPk(search);
+ const entry = await ModLog.findByPk(search);
if (!entry) {
- await message.util.send('That modlog does not exist.');
+ await message.util.send(`${this.client.util.emojis.error} That modlog does not exist.`);
return;
}
await message.util.send({
embeds: [
new MessageEmbed({
- title: `Modlog ${entry.id}`,
+ title: `${entry.id}`,
fields: [
{
name: 'Type',
diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts
new file mode 100644
index 0000000..9b6ad70
--- /dev/null
+++ b/src/commands/moderation/mute.ts
@@ -0,0 +1,166 @@
+import { Argument } from 'discord-akairo';
+import { CommandInteraction, Message, User } from 'discord.js';
+import moment from 'moment';
+import { BushCommand } from '../../lib/extensions/BushCommand';
+import { Guild, ModLog, ModLogType, Mute } from '../../lib/models';
+
+export default class MuteCommand extends BushCommand {
+ constructor() {
+ super('mute', {
+ aliases: ['mute'],
+ category: 'moderation',
+ args: [
+ {
+ id: 'user',
+ type: 'user',
+ prompt: {
+ start: 'What user would you like to mute?',
+ retry: '{error} Choose a valid user to mute.'
+ }
+ },
+ {
+ id: 'reason',
+ match: 'separate',
+ prompt: {
+ start: 'Why would you like to mute this user?',
+ retry: '{error} Choose a mute reason.',
+ optional: true
+ }
+ },
+ {
+ id: 'time',
+ type: 'duration',
+ match: 'option',
+ flag: '--time'
+ }
+ ],
+ clientPermissions: ['MANAGE_ROLES'],
+ userPermissions: ['MANAGE_MESSAGES'],
+ description: {
+ content: 'Mute a user.',
+ usage: 'mute <member> <reason> [--time]',
+ examples: ['mute @user bad boi --time 1h']
+ },
+ slashOptions: [
+ {
+ type: 'USER',
+ name: 'user',
+ description: 'The user to mute.',
+ required: true
+ },
+ {
+ type: 'STRING',
+ name: 'reason',
+ description: 'Why the user is getting muted.',
+ required: false
+ },
+ {
+ type: 'STRING',
+ name: 'time',
+ description: 'How long the user should be muted for.',
+ required: false
+ }
+ ],
+ slash: true
+ });
+ }
+ async *genResponses(
+ message: Message | CommandInteraction,
+ user: User,
+ reason?: string,
+ time?: number
+ ): AsyncIterable<string> {
+ const duration = moment.duration(time);
+ let modlogEnry: ModLog;
+ let muteEntry: Mute;
+ // Create guild entry so postgres doesn't get mad when I try and add a modlog entry
+ await Guild.findOrCreate({
+ where: {
+ id: message.guild.id
+ },
+ defaults: {
+ id: message.guild.id
+ }
+ });
+ try {
+ const muteRole = (await Guild.findByPk(message.guild.id)).get('muteRole');
+ try {
+ if (time) {
+ modlogEnry = ModLog.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ type: ModLogType.TEMP_MUTE,
+ duration: duration.asMilliseconds(),
+ moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
+ });
+ muteEntry = Mute.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ expires: new Date(new Date().getTime() + duration.asMilliseconds()),
+ modlog: modlogEnry.id
+ });
+ } else {
+ modlogEnry = ModLog.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ type: ModLogType.MUTE,
+ moderator: message instanceof CommandInteraction ? message.user.id : message.author.id
+ });
+ muteEntry = Mute.build({
+ user: user.id,
+ guild: message.guild.id,
+ reason,
+ modlog: modlogEnry.id
+ });
+ }
+ await modlogEnry.save();
+ await muteEntry.save();
+ } catch (e) {
+ this.client.console.error(`MuteCommand`, `Error saving to database. ${e?.stack}`);
+ yield `${this.client.util.emojis.error} Error saving to database. Please report this to a developer.`;
+ return;
+ }
+ try {
+ await user.send(
+ `You were muted in ${message.guild.name} ${time ? `for ${duration.humanize()}` : 'permanently'} with reason \`${
+ reason || 'No reason given'
+ }\``
+ );
+ } catch (e) {
+ yield `${this.client.util.emojis.warn} Unable to dm user`;
+ }
+ await (
+ await message.guild.members.fetch(user)
+ ).roles.add(
+ muteRole,
+ `Muted by ${message instanceof CommandInteraction ? message.user.tag : message.author.tag} with ${
+ reason ? `reason ${reason}` : 'no reason'
+ }`
+ );
+ yield `${this.client.util.emojis.success} muted <@!${user.id}> ${
+ time ? `for ${duration.humanize()}` : 'permanently'
+ } with reason \`${reason || 'No reason given'}\``;
+ } catch {
+ yield `${this.client.util.emojis.error} Error muting :/`;
+ await muteEntry.destroy();
+ await modlogEnry.destroy();
+ return;
+ }
+ }
+ async exec(
+ message: Message,
+ { user, reason, time }: { user: User; reason?: string[]; time?: string | number }
+ ): Promise<void> {
+ this.client.console.debug(reason);
+
+ if (typeof time === 'string') {
+ time = (await Argument.cast('duration', this.client.commandHandler.resolver, message, time)) as number;
+ }
+ for await (const response of this.genResponses(message, user, reason.join(' '), time)) {
+ await message.util.sendNew(response);
+ }
+ }
+}
diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/unban.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/commands/moderation/unban.ts
diff --git a/src/commands/moderation/unblock.ts b/src/commands/moderation/unblock.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/commands/moderation/unblock.ts
diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/commands/moderation/unmute.ts
diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts
index 3410d81..d48049b 100644
--- a/src/commands/moderation/warn.ts
+++ b/src/commands/moderation/warn.ts
@@ -1,6 +1,6 @@
import { GuildMember, Message } from 'discord.js';
import { BushCommand } from '../../lib/extensions/BushCommand';
-import { Guild, Modlog, ModlogType } from '../../lib/models';
+import { Guild, ModLog, ModLogType } from '../../lib/models';
export default class WarnCommand extends BushCommand {
public constructor() {
@@ -36,11 +36,11 @@ export default class WarnCommand extends BushCommand {
}
});
try {
- const entry = Modlog.build({
+ const entry = ModLog.build({
user: member.id,
guild: message.guild.id,
moderator: message.author.id,
- type: ModlogType.WARN,
+ type: ModLogType.WARN,
reason
});
await entry.save();
diff --git a/src/commands/moulberry-bush/capePerms.ts b/src/commands/moulberry-bush/capePerms.ts
index 3e4563a..d1850c8 100644
--- a/src/commands/moulberry-bush/capePerms.ts
+++ b/src/commands/moulberry-bush/capePerms.ts
@@ -1,17 +1,8 @@
-import { Message, MessageEmbed } from 'discord.js';
+import { Constants } from 'discord-akairo';
+import { MessageEmbed } from 'discord.js';
import got from 'got';
-import { SlashCommandOption } from '../../lib/extensions/BushClientUtil';
import { BushCommand } from '../../lib/extensions/BushCommand';
-import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
-
-interface Capeperms {
- success: boolean;
- perms: User[];
-}
-interface User {
- _id: string;
- perms: string[];
-}
+import { BushMessage } from '../../lib/extensions/BushMessage';
export default class CapePermissionsCommand extends BushCommand {
private nameMap = {
@@ -19,31 +10,32 @@ export default class CapePermissionsCommand extends BushCommand {
patreon2: 'Patreon Tier 2',
fade: 'Fade',
contrib: 'Contributor',
- nullzee: 'Nullzee',
- gravy: 'ThatGravyBoat',
- space: 'Space',
- mcworld: 'Minecraft World',
- lava: 'Lava',
- packshq: 'PacksHQ',
- mbstaff: "Moulberry's Bush staff",
- thebakery: "Biscuit's Bakery",
- negative: 'Negative',
- void: 'Void',
- ironmoon: 'IRONM00N',
- krusty: 'Krusty',
- furf: 'FurfSky Reborn',
- soldier: 'Soldier',
- dsm: "Danker's Skyblock Mod",
- zera: 'Zera',
- tunnel: 'Tunnel',
- alexxoffi: 'Alexxoffi',
- parallax: 'Parallax',
- jakethybro: 'Jakethybro',
- planets: 'Planets'
+ nullzee: 'Patreon Tier 1',
+ gravy: 'Patreon Tier 1',
+ space: 'Patreon Tier 1',
+ mcworld: 'Patreon Tier 1',
+ lava: 'Patreon Tier 1',
+ packshq: 'Patreon Tier 1',
+ mbstaff: 'Patreon Tier 1',
+ thebakery: 'Patreon Tier 1',
+ negative: 'Patreon Tier 1',
+ void: 'Patreon Tier 1',
+ ironmoon: 'Patreon Tier 1',
+ krusty: 'Patreon Tier 1',
+ furf: 'Patreon Tier 1',
+ soldier: 'Patreon Tier 1',
+ dsm: 'Patreon Tier 1',
+ zera: 'Patreon Tier 1',
+ tunnel: 'Patreon Tier 1',
+ alexxoffi: 'Patreon Tier 1',
+ parallax: 'Patreon Tier 1',
+ jakethybro: 'Patreon Tier 1',
+ planets: 'Patreon Tier 1'
};
+
public constructor() {
- super('capeperms', {
- aliases: ['capeperms', 'capeperm', 'capepermissions', 'capepermission'],
+ super('capepermissions', {
+ aliases: ['capeperms', 'capeperm', 'capepermissions'],
category: "Moulberry's Bush",
description: {
content: 'A command to see what capes someone has access to.',
@@ -52,8 +44,9 @@ export default class CapePermissionsCommand extends BushCommand {
},
args: [
{
- id: 'user',
- type: 'string',
+ id: 'ign',
+ type: Constants.ArgumentTypes.STRING,
+ match: Constants.ArgumentMatches.PHRASE,
prompt: {
start: 'Who would you like to see the cape permissions of?',
retry: '{error} Choose someone to see the capes their available capes.',
@@ -63,53 +56,72 @@ export default class CapePermissionsCommand extends BushCommand {
],
clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
channel: 'guild',
+ slash: true,
slashOptions: [
{
+ name: 'ign',
+ description: 'The ign of the player you would like to view the capes permissions of.',
type: 'STRING',
- name: 'user',
- description: 'The username of the player to see the cape permissions of',
required: true
}
- ],
- slash: true
+ ]
});
}
- private async getResponse(user: string): Promise<{ content?: string; embeds?: MessageEmbed[] }> {
+
+ public async exec(message: BushMessage, args: { ign: string }): Promise<unknown> {
+ interface Capeperms {
+ success: boolean;
+ perms: User[];
+ }
+
+ interface User {
+ _id: string;
+ perms: string[];
+ }
+
let capeperms: Capeperms, uuid: string;
try {
- uuid = await this.client.util.mcUUID(user);
+ uuid = await this.client.util.mcUUID(args.ign);
} catch (e) {
- return { content: `${this.client.util.emojis.error} \`${user}\` doesn't appear to be a valid username.` };
+ return await message.util.reply(
+ `${this.client.util.emojis.error} \`${args.ign}\` doesn't appear to be a valid username.`
+ );
}
try {
- capeperms = await got.get('http://moulberry.codes/permscapes.json').json();
+ capeperms = await got.get('https://moulberry.codes/permscapes.json').json();
} catch (error) {
capeperms = null;
}
if (capeperms == null) {
- return { content: `${this.client.util.emojis.error} There was an error finding cape perms for \`${user}\`.` };
+ return await message.util.reply(
+ `${this.client.util.emojis.error} There was an error finding cape perms for \`${args.ign}\`.`
+ );
} else {
if (capeperms?.perms) {
- const foundUser = capeperms.perms.find((u) => u._id === uuid);
- if (foundUser == null)
- return { content: `${this.client.util.emojis.error} \`${user}\` does not appear to have any capes.` };
- const userPerm: string[] = foundUser.perms;
- const embed = this.client.util
- .createEmbed(this.client.util.colors.default)
- .setTitle(`${user}'s Capes`)
- .setDescription(userPerm.join('\n'));
- return { embeds: [embed] };
+ let index = null;
+
+ for (let i = 0; i < capeperms.perms.length; i++) {
+ if (capeperms.perms[i]._id == uuid) {
+ index = i;
+ break;
+ }
+ }
+ if (index == null)
+ return await message.util.reply(
+ `${this.client.util.emojis.error} \`${args.ign}\` does not appear to have any capes.`
+ );
+ const userPerm: string[] = capeperms.perms[index].perms;
+ const embed = new MessageEmbed()
+ .setTitle(`${args.ign}'s Capes`)
+ .setDescription(userPerm.join('\n'))
+ .setColor(this.client.util.colors.default);
+ await message.util.reply({ embeds: [embed] });
} else {
- return { content: `${this.client.util.emojis.error} There was an error finding cape perms for ${user}.` };
+ return await message.util.reply(
+ `${this.client.util.emojis.error} There was an error finding cape perms for ${args.ign}.`
+ );
}
}
}
- public async exec(message: Message, { user }: { user: string }): Promise<void> {
- await message.reply(await this.getResponse(user));
- }
-
- public async execSlash(message: BushSlashMessage, { user }: { user: SlashCommandOption<string> }): Promise<void> {
- await message.reply(await this.getResponse(user.value));
- }
}
diff --git a/src/commands/moulberry-bush/giveawayPing.ts b/src/commands/moulberry-bush/giveawayPing.ts
index d308602..19163d7 100644
--- a/src/commands/moulberry-bush/giveawayPing.ts
+++ b/src/commands/moulberry-bush/giveawayPing.ts
@@ -1,5 +1,5 @@
-import { Message, NewsChannel, TextChannel, WebhookClient } from 'discord.js';
import { BushCommand } from '../../lib/extensions/BushCommand';
+import { BushMessage } from '../../lib/extensions/BushMessage';
import AllowedMentions from '../../lib/utils/AllowedMentions';
export default class GiveawayPingCommand extends BushCommand {
@@ -19,16 +19,26 @@ export default class GiveawayPingCommand extends BushCommand {
ignorePermissions: [],
cooldown: 1.44e7, //4 hours
ratelimit: 1,
- editable: false
+ editable: false,
+ restrictedGuilds: ['516977525906341928'],
+ restrictedChannels: ['767782084981817344', '833855738501267456']
});
}
- public async exec(message: Message): Promise<unknown> {
- if (message.guild.id !== '516977525906341928')
- return message.reply(`${this.client.util.emojis.error} This command may only be run in Moulberry's Bush.`);
- if (!['767782084981817344', '833855738501267456'].includes(message.channel.id))
- return message.reply(`${this.client.util.emojis.error} This command may only be run in giveaway channels.`);
- await message.delete().catch(() => undefined);
- const webhooks = await (message.channel as TextChannel | NewsChannel).fetchWebhooks();
+
+ public async exec(message: BushMessage): Promise<unknown> {
+ if (!message.member.permissions.has('MANAGE_GUILD'))
+ await message.util.reply(`${this.client.util.emojis.error} You are missing the \`manage server\` permission.`);
+
+ await message.delete().catch(() => {});
+
+ return await message.channel.send({
+ content:
+ '🎉 <@&767782793261875210> Giveaway.\n\n<:mad:783046135392239626> Spamming, line breaking, gibberish etc. disqualifies you from winning. We can and will ban you from giveaways. Winners will all be checked and rerolled if needed.',
+ allowedMentions: AllowedMentions.roles()
+ });
+
+ //! Broken
+ /* const webhooks = await (message.channel as TextChannel | NewsChannel).fetchWebhooks();
let webhookClient: WebhookClient;
if (webhooks.size < 1) {
const webhook = await (message.channel as TextChannel | NewsChannel).createWebhook('Giveaway ping webhook');
@@ -37,11 +47,12 @@ export default class GiveawayPingCommand extends BushCommand {
const webhook = webhooks.first();
webhookClient = new WebhookClient(webhook.id, webhook.token);
}
- return webhookClient.send({
- content: `🎉 <@&767782793261875210> Giveaway.\n\n${this.client.util.emojis.mad} Spamming, line breaking, gibberish etc. disqualifies you from winning. We can and will ban you from giveaways. Winners will all be checked and rerolled if needed.`,
+ return await webhookClient.send({
+ content:
+ '🎉 <@&767782793261875210> Giveaway.\n\n<:mad:783046135392239626> Spamming, line breaking, gibberish etc. disqualifies you from winning. We can and will ban you from giveaways. Winners will all be checked and rerolled if needed.',
username: `${message.member.nickname || message.author.username}`,
avatarURL: message.author.avatarURL({ dynamic: true }),
allowedMentions: AllowedMentions.roles()
- });
+ }); */
}
}
diff --git a/src/commands/moulberry-bush/level.ts b/src/commands/moulberry-bush/level.ts
index f822555..41541e0 100644
--- a/src/commands/moulberry-bush/level.ts
+++ b/src/commands/moulberry-bush/level.ts
@@ -1,4 +1,4 @@
-import { CommandInteractionOption, Message, User } from 'discord.js';
+import { Message, User } from 'discord.js';
import { BushCommand } from '../../lib/extensions/BushCommand';
import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
import { Level } from '../../lib/models';
@@ -26,7 +26,7 @@ export default class LevelCommand extends BushCommand {
type: 'user',
prompt: {
start: 'What user would you like to see the level of?',
- retry: 'Invalid user. What user would you like to see the level of?',
+ retry: '{error} Choose a valid user to see the level of.',
optional: true
}
}
@@ -139,7 +139,7 @@ export default class LevelCommand extends BushCommand {
}
}
- async exec(message: Message, { user }: { user?: User }): Promise<void> {
+ async exec(message: Message | BushSlashMessage, { user }: { user?: User }): Promise<void> {
// await message.reply(
// new MessageAttachment(
// await this.getImage(user || message.author),
@@ -148,13 +148,4 @@ export default class LevelCommand extends BushCommand {
// );
await message.reply(await this.getResponse(user || message.author));
}
- async execSlash(message: BushSlashMessage, { user }: { user?: CommandInteractionOption }): Promise<void> {
- // await message.reply(
- // new MessageAttachment(
- // await this.getImage(user?.user || message.user),
- // 'lel.png'
- // )
- // );
- await message.reply(await this.getResponse(user?.user || message.author));
- }
}
diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts
index e9b09eb..3cd9ec6 100644
--- a/src/commands/moulberry-bush/rule.ts
+++ b/src/commands/moulberry-bush/rule.ts
@@ -1,69 +1,60 @@
-import { Argument } from 'discord-akairo';
-import { CommandInteraction, Message, MessageEmbed, User } from 'discord.js';
-import { SlashCommandOption } from '../../lib/extensions/BushClientUtil';
+import { Argument, Constants } from 'discord-akairo';
+import { MessageEmbed, User } from 'discord.js';
import { BushCommand } from '../../lib/extensions/BushCommand';
-import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
+import { BushMessage } from '../../lib/extensions/BushMessage';
+import AllowedMentions from '../../lib/utils/AllowedMentions';
+const rules = [
+ {
+ title: "1.) Follow Discord's TOS",
+ description:
+ "Be sure to follow discord's TOS found at <https://discordapp.com/tos>, you must be 13 to use discord so if you admit to being under 13 you will be banned from the server."
+ },
+ {
+ title: '2.) Be Respectful',
+ description:
+ 'Racist, sexist, homophobic, xenophobic, transphobic, ableist, hate speech, slurs, or any other derogatory, toxic, or discriminatory behavior will not be tolerated.'
+ },
+ {
+ title: '3.) No Spamming',
+ description:
+ 'Including but not limited to: any messages that do not contribute to the conversation, repeated messages, randomly tagging users, and chat flood.'
+ },
+ {
+ title: '4.) English',
+ description: 'The primary language of the server is English, please keep all discussions in English.'
+ },
+ {
+ title: '5.) Safe for Work',
+ description:
+ 'Please keep NSFW and NSFL content out of this server, avoid borderline images as well as keeping your status, profile picture, and banner SFW.'
+ },
+ {
+ title: '6.) No Advertising',
+ description: 'Do not promote anything without prior approval from a staff member, this includes DM advertising.'
+ },
+ {
+ title: '7.) Impersonation',
+ description:
+ 'Do not try to impersonate others for the express intent of being deceitful, defamation , and/or personal gain.'
+ },
+ { title: '8.) Swearing', description: 'Swearing is allowed only when not used as an insult.' },
+ {
+ title: "9.) Sending media that are able to crash a user's Discord",
+ description:
+ "Sending videos, GIFs, emojis, etc. that are able to crash someone's discord will result in a **permanent** ban that cannot be appealed."
+ },
+ {
+ title: '10.) No Backseat Moderating',
+ description: 'If you see a rule being broken be broken, please report it using: `-report <user> [evidence]`.'
+ },
+ {
+ title: '11.) Staff may moderate at their discretion',
+ description:
+ 'If there are loopholes in our rules, the staff team may moderate based on what they deem appropriate. The staff team holds final discretion.'
+ }
+];
export default class RuleCommand extends BushCommand {
- private rules = [
- {
- title: "Follow Discord's TOS",
- description:
- "Be sure to follow discord's TOS found at <https://discordapp.com/tos>, you must be 13 to use discord so if you admit to being under 13 you will be banned from the server."
- },
- {
- title: 'Be Respectful',
- description:
- 'Racist, sexist, homophobic, xenophobic, transphobic, ableist, hate speech, slurs, or any other derogatory, toxic, or discriminatory behavior will not be tolerated.'
- },
- {
- title: 'No Spamming',
- description:
- 'Including but not limited to: any messages that do not contribute to the conversation, repeated messages, randomly tagging users, and chat flood.'
- },
- {
- title: 'English',
- description: 'The primary language of the server is English, please keep all discussions in English.'
- },
- {
- title: 'Safe for Work',
- description:
- 'Please keep NSFW and NSFL content out of this server, avoid borderline images as well as keeping your status and profile picture SFW.'
- },
- {
- title: 'No Advertising',
- description: 'Do not promote anything without prior approval from a staff member, this includes DM advertising.'
- },
- {
- title: 'Impersonation',
- description:
- 'Do not try to impersonate others for the express intent of being deceitful, defamation , and/or personal gain.'
- },
- {
- title: 'Swearing',
- description: 'Swearing is allowed only when not used as an insult.'
- },
- {
- title: 'Only ping @emergency in emergencies',
- description:
- 'Pinging <@&833802660209229854> for no reason will result in severe punishment. <@&833802660209229854> is only to be pinged in true emergencies.'
- },
- {
- title: 'No Backseat Moderating',
- description: 'If you see a rule being broken be broken, please report it using: `-report <user> [evidence]`.'
- },
- {
- title: 'Staff may moderate at their discretion',
- description:
- 'If there are loopholes in our rules, the staff team may moderate based on what they deem appropriate. The staff team holds final discretion.'
- },
- {
- title: "Sending media that are able to crash a user's Discord",
- description:
- "Sending videos, GIFs, emojis, etc. that are able to crash someone's discord will result in a **permanent** mute that cannot be appealed."
- }
- ];
-
public constructor() {
super('rule', {
aliases: ['rule', 'rules'],
@@ -76,98 +67,93 @@ export default class RuleCommand extends BushCommand {
args: [
{
id: 'rule',
- type: Argument.range('number', 1, 12, true),
+ type: Argument.range(Constants.ArgumentTypes.INTEGER, 1, rules.length, true),
+ match: Constants.ArgumentMatches.PHRASE,
prompt: {
start: 'What rule would you like to have cited?',
retry: '{error} Choose a valid rule.',
optional: true
- },
- default: undefined
+ }
},
{
id: 'user',
type: 'user',
+ match: Constants.ArgumentMatches.PHRASE,
prompt: {
start: 'What user would you like to mention?',
retry: '{error} Choose a valid user to mention.',
optional: true
- },
- default: undefined
+ }
}
],
clientPermissions: ['EMBED_LINKS', 'SEND_MESSAGES'],
channel: 'guild',
+ restrictedGuilds: ['516977525906341928'],
+ slash: true,
slashOptions: [
{
- type: 'INTEGER',
name: 'rule',
- description: 'The rule to show',
+ description: 'The rule you would you like to have cited',
+ type: 'INTEGER',
required: false
},
{
- type: 'USER',
name: 'user',
- description: 'The user to ping',
+ description: 'The user you would like to mention.',
+ type: 'USER',
required: false
}
],
- slash: true
+ slashGuilds: ['516977525906341928']
});
}
- private getResponse(
- message: Message | CommandInteraction,
- rule?: number,
- user?: User
- ): { content?: string; embeds?: MessageEmbed[] } | [string, MessageEmbed] {
- if (
- message.guild.id !== '516977525906341928' &&
- !this.client.ownerID.includes(message instanceof Message ? message.author.id : message.user.id)
- ) {
- return { content: `${this.client.util.emojis.error} This command can only be run in Moulberry's Bush.` };
- }
- let rulesEmbed = new MessageEmbed().setColor('ef3929');
- if (message instanceof Message) {
- rulesEmbed = rulesEmbed.setFooter(`Triggered by ${message.author.tag}`, message.author.avatarURL({ dynamic: true }));
+
+ public async exec(message: BushMessage, { rule, user }: { rule: undefined | number; user: User }): Promise<unknown> {
+ const rulesEmbed = new MessageEmbed()
+ .setColor('ef3929')
+ .setFooter(`Triggered by ${message.author.tag}`, message.author.avatarURL({ dynamic: true }))
+ .setTimestamp();
+
+ if (rule > 12 || rule < 1) {
+ rule = undefined;
}
if (rule) {
- const foundRule = this.rules[rule - 1];
- rulesEmbed.addField(`${rule}) ${foundRule.title}`, foundRule.description);
+ if (rules[rule - 1]?.title && rules[rule - 1]?.description)
+ rulesEmbed.addField(rules[rule - 1].title, rules[rule - 1].description);
} else {
- for (const curRule of this.rules) {
- rulesEmbed.addField(`${this.rules.indexOf(curRule) + 1}) ${curRule.title}`, curRule.description);
+ for (let i = 0; i < rules.length; i++) {
+ if (rules[i]?.title && rules[i]?.description) rulesEmbed.addField(rules[i].title, rules[i].description);
}
}
- if (!user) {
- return { embeds: [rulesEmbed] };
- } else {
- return [`<@!${user.id}>`, rulesEmbed];
- }
- }
- public async exec(message: Message, { rule, user }: { rule?: number; user?: User }): Promise<void> {
- const response = this.getResponse(message, rule, user);
- if (Array.isArray(response)) {
- await message.util.send({
- content: response[0],
- embeds: [response[1]]
- });
- } else {
- await message.util.send(response);
+ await respond();
+ if (!message.util.isSlash) {
+ await message.delete().catch(() => {});
}
- await message.delete().catch(() => undefined);
- }
-
- public async execSlash(
- message: BushSlashMessage,
- { rule, user }: { rule?: SlashCommandOption<number>; user?: SlashCommandOption<void> }
- ): Promise<void> {
- const response = this.getResponse(message.interaction, rule?.value, user?.user);
- if (Array.isArray(response)) {
- await message.interaction.reply({
- content: response[0],
- embeds: [response[1]]
- });
- } else {
- await message.interaction.reply(response);
+ return;
+ async function respond(): Promise<unknown> {
+ if (!user) {
+ return (
+ // If the original message was a reply -> imitate it
+ message.reference?.messageID && !message.util.isSlash
+ ? await message.channel.messages.fetch(message.reference.messageID).then(async (message) => {
+ await message.util.reply({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() });
+ })
+ : await message.util.send({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() })
+ );
+ } else {
+ return message.reference?.messageID && !message.util.isSlash
+ ? await message.util.send({
+ content: `<@!${user.id}>`,
+ embeds: [rulesEmbed],
+ allowedMentions: AllowedMentions.users(),
+ reply: { messageReference: message.reference.messageID }
+ })
+ : await message.util.send({
+ content: `<@!${user.id}>`,
+ embeds: [rulesEmbed],
+ allowedMentions: AllowedMentions.users()
+ });
+ }
}
}
}
diff --git a/src/inhibitors/blacklist/blacklist.ts b/src/inhibitors/blacklist/blacklist.ts
deleted file mode 100644
index 309815f..0000000
--- a/src/inhibitors/blacklist/blacklist.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { BushInhibitor } from '../../lib/extensions/BushInhibitor';
-
-export default class BlacklistInhibitor extends BushInhibitor {
- constructor() {
- super('blacklist', {
- reason: 'blacklist'
- });
- }
-
- public exec(): boolean | Promise<boolean> {
- // This is just a placeholder for now
- return false;
- }
-}
diff --git a/src/inhibitors/blacklist/guildBlacklist.ts b/src/inhibitors/blacklist/guildBlacklist.ts
new file mode 100644
index 0000000..93d8aee
--- /dev/null
+++ b/src/inhibitors/blacklist/guildBlacklist.ts
@@ -0,0 +1,19 @@
+import { BushInhibitor } from '../../lib/extensions/BushInhibitor';
+import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
+import { BushMessage } from '../../lib/extensions/BushMessage';
+
+export default class GuildBlacklistInhibitor extends BushInhibitor {
+ constructor() {
+ super('guildBlacklist', {
+ reason: 'guildBlacklist',
+ category: 'blacklist',
+ type: 'all'
+ });
+ }
+
+ public exec(message: BushMessage | BushSlashMessage): boolean {
+ if (!message.guild) return false;
+ if (message.author && (this.client.isOwner(message.author) || this.client.isSuperUser(message.author))) return false;
+ return this.client.cache.global.blacklistedGuilds.includes(message.guild.id);
+ }
+}
diff --git a/src/inhibitors/blacklist/userBlacklist.ts b/src/inhibitors/blacklist/userBlacklist.ts
new file mode 100644
index 0000000..bbced28
--- /dev/null
+++ b/src/inhibitors/blacklist/userBlacklist.ts
@@ -0,0 +1,19 @@
+import { BushInhibitor } from '../../lib/extensions/BushInhibitor';
+import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
+import { BushMessage } from '../../lib/extensions/BushMessage';
+
+export default class UserBlacklistInhibitor extends BushInhibitor {
+ constructor() {
+ super('userBlacklist', {
+ reason: 'userBlacklist',
+ category: 'blacklist',
+ type: 'all'
+ });
+ }
+
+ public exec(message: BushMessage | BushSlashMessage): boolean {
+ if (!message.author) return false;
+ if (this.client.isOwner(message.author) || this.client.isSuperUser(message.author)) return false;
+ return this.client.cache.global.blacklistedUsers.includes(message.author.id);
+ }
+}
diff --git a/src/inhibitors/commands/disabledCommand.ts b/src/inhibitors/commands/disabledCommand.ts
new file mode 100644
index 0000000..8538858
--- /dev/null
+++ b/src/inhibitors/commands/disabledCommand.ts
@@ -0,0 +1,19 @@
+import { BushCommand } from '../../lib/extensions/BushCommand';
+import { BushInhibitor } from '../../lib/extensions/BushInhibitor';
+import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
+import { BushMessage } from '../../lib/extensions/BushMessage';
+
+export default class DisabledCommandInhibitor extends BushInhibitor {
+ constructor() {
+ super('disabledCommand', {
+ reason: 'disabled',
+ type: 'pre',
+ priority: 3
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage, command: BushCommand): Promise<boolean> {
+ if (this.client.isOwner(message.author)) return false;
+ return this.client.cache.global.disabledCommands.includes(command?.id);
+ }
+}
diff --git a/src/inhibitors/noCache.ts b/src/inhibitors/noCache.ts
new file mode 100644
index 0000000..61f0b3e
--- /dev/null
+++ b/src/inhibitors/noCache.ts
@@ -0,0 +1,21 @@
+import { BushInhibitor } from '../lib/extensions/BushInhibitor';
+import { BushSlashMessage } from '../lib/extensions/BushInteractionMessage';
+import { BushMessage } from '../lib/extensions/BushMessage';
+
+export default class noCacheInhibitor extends BushInhibitor {
+ constructor() {
+ super('noCache', {
+ reason: 'noCache',
+ type: 'all',
+ priority: 100
+ });
+ }
+
+ public async exec(message: BushMessage | BushSlashMessage): Promise<boolean> {
+ if (this.client.isOwner(message.author)) return false;
+ for (const property in this.client.cache) {
+ if (property === undefined || property === null) return true;
+ }
+ return false;
+ }
+}
diff --git a/src/lib/extensions/BushArgumentOptions.ts b/src/lib/extensions/BushArgumentOptions.ts
new file mode 100644
index 0000000..bbbc04b
--- /dev/null
+++ b/src/lib/extensions/BushArgumentOptions.ts
@@ -0,0 +1,59 @@
+import { ArgumentOptions, ArgumentTypeCaster } from 'discord-akairo';
+
+type BushArgumentType =
+ | 'string'
+ | 'lowercase'
+ | 'uppercase'
+ | 'charCodes'
+ | 'number'
+ | 'integer'
+ | 'bigint'
+ | 'emojint'
+ | 'url'
+ | 'date'
+ | 'color'
+ | 'user'
+ | 'users'
+ | 'member'
+ | 'members'
+ | 'relevant'
+ | 'relevants'
+ | 'channel'
+ | 'channels'
+ | 'textChannel'
+ | 'textChannels'
+ | 'voiceChannel'
+ | 'voiceChannels'
+ | 'categoryChannel'
+ | 'categoryChannels'
+ | 'newsChannel'
+ | 'newsChannels'
+ | 'storeChannel'
+ | 'storeChannels'
+ | 'role'
+ | 'roles'
+ | 'emoji'
+ | 'emojis'
+ | 'guild'
+ | 'guilds'
+ | 'message'
+ | 'guildMessage'
+ | 'relevantMessage'
+ | 'invite'
+ | 'userMention'
+ | 'memberMention'
+ | 'channelMention'
+ | 'roleMention'
+ | 'emojiMention'
+ | 'commandAlias'
+ | 'command'
+ | 'inhibitor'
+ | 'listener'
+ | 'duration'
+ | (string | string[])[]
+ | RegExp
+ | string;
+
+export interface BushArgumentOptions extends ArgumentOptions {
+ type?: BushArgumentType | ArgumentTypeCaster;
+}
diff --git a/src/lib/extensions/BushArgumentTypeCaster.ts b/src/lib/extensions/BushArgumentTypeCaster.ts
new file mode 100644
index 0000000..e000063
--- /dev/null
+++ b/src/lib/extensions/BushArgumentTypeCaster.ts
@@ -0,0 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { BushMessage } from './BushMessage';
+
+export type BushArgumentTypeCaster = (message: BushMessage, phrase: string) => any;
diff --git a/src/lib/extensions/BushClient.ts b/src/lib/extensions/BushClient.ts
index b12fd52..73b0864 100644
--- a/src/lib/extensions/BushClient.ts
+++ b/src/lib/extensions/BushClient.ts
@@ -1,11 +1,13 @@
import chalk from 'chalk';
-import { AkairoClient, TaskHandler } from 'discord-akairo';
+import { AkairoClient } from 'discord-akairo';
import { APIMessage, Guild, Intents, Message, MessageOptions, Snowflake, UserResolvable } from 'discord.js';
import * as path from 'path';
import { exit } from 'process';
import readline from 'readline';
import { Sequelize } from 'sequelize';
+import { durationTypeCaster } from '../../arguments/duration';
import * as config from '../../config/options';
+import UpdateCacheTask from '../../tasks/updateCache';
import * as Models from '../models';
import AllowedMentions from '../utils/AllowedMentions';
import { BushCache } from '../utils/BushCache';
@@ -15,6 +17,7 @@ import { BushClientUtil } from './BushClientUtil';
import { BushCommandHandler } from './BushCommandHandler';
import { BushInhibitorHandler } from './BushInhinitorHandler';
import { BushListenerHandler } from './BushListenerHandler';
+import { BushTaskHandler } from './BushTaskHandler';
export type BotConfig = typeof config;
export type BushMessageType = string | APIMessage | (MessageOptions & { split?: false });
@@ -30,7 +33,7 @@ export class BushClient extends AkairoClient {
public listenerHandler: BushListenerHandler;
public inhibitorHandler: BushInhibitorHandler;
public commandHandler: BushCommandHandler;
- public taskHandler: TaskHandler;
+ public taskHandler: BushTaskHandler;
public declare util: BushClientUtil;
public declare ownerID: Snowflake[];
public db: Sequelize;
@@ -68,7 +71,7 @@ export class BushClient extends AkairoClient {
});
// Create task handler
- this.taskHandler = new TaskHandler(this, {
+ this.taskHandler = new BushTaskHandler(this, {
directory: path.join(__dirname, '..', '..', 'tasks')
});
@@ -76,14 +79,14 @@ export class BushClient extends AkairoClient {
this.commandHandler = new BushCommandHandler(this, {
directory: path.join(__dirname, '..', '..', 'commands'),
prefix: async ({ guild }: { guild: Guild }) => {
- if (this.config.dev) return 'dev';
+ if (this.config.dev) return 'dev ';
const row = await Models.Guild.findByPk(guild.id);
return (row?.prefix || this.config.prefix) as string;
},
allowMention: true,
handleEdits: true,
commandUtil: true,
- commandUtilLifetime: 3e5,
+ commandUtilLifetime: 300_000,
argumentDefaults: {
prompt: {
start: 'Placeholder argument prompt. If you see this please tell the devs.',
@@ -99,9 +102,8 @@ export class BushClient extends AkairoClient {
},
otherwise: ''
},
- ignorePermissions: this.config.owners,
- ignoreCooldown: this.config.owners,
- automateCategories: true,
+
+ automateCategories: false,
autoRegisterSlashCommands: true
});
@@ -110,7 +112,7 @@ export class BushClient extends AkairoClient {
dialect: 'postgres',
host: this.config.db.host,
port: this.config.db.port,
- logging: this.config.logging ? (a) => this.logger.debug(a) : false
+ logging: this.config.logging.db ? (a) => this.logger.debug(a) : false
});
this.logger = new BushLogger(this);
}
@@ -127,6 +129,8 @@ export class BushClient extends AkairoClient {
private async _init(): Promise<void> {
this.commandHandler.useListenerHandler(this.listenerHandler);
this.commandHandler.useInhibitorHandler(this.inhibitorHandler);
+ this.commandHandler.ignorePermissions = this.config.owners;
+ this.commandHandler.ignoreCooldown = this.config.owners.concat(this.cache.global.superUsers);
this.listenerHandler.setEmitters({
client: this,
commandHandler: this.commandHandler,
@@ -137,6 +141,9 @@ export class BushClient extends AkairoClient {
stdin: rl,
gateway: this.ws
});
+ this.commandHandler.resolver.addTypes({
+ duration: durationTypeCaster
+ });
// loads all the handlers
const loaders = {
commands: this.commandHandler,
@@ -147,13 +154,15 @@ export class BushClient extends AkairoClient {
for (const loader of Object.keys(loaders)) {
try {
loaders[loader].loadAll();
- this.logger.success('Startup', `Successfully loaded <<${loader}>>.`, false);
+ await this.logger.success('Startup', `Successfully loaded <<${loader}>>.`, false);
} catch (e) {
- this.logger.error('Startup', `Unable to load loader <<${loader}>> with error:\n${e?.stack}`, false);
+ await this.logger.error('Startup', `Unable to load loader <<${loader}>> with error:\n${e?.stack}`, false);
}
}
- this.taskHandler.startAll();
await this.dbPreInit();
+ await new UpdateCacheTask().init(this);
+ this.console.success('Startup', `Successfully created <<global cache>>.`, false);
+ this.taskHandler.startAll();
}
public async dbPreInit(): Promise<void> {
@@ -161,14 +170,15 @@ export class BushClient extends AkairoClient {
await this.db.authenticate();
Models.Global.initModel(this.db);
Models.Guild.initModel(this.db, this);
- Models.Modlog.initModel(this.db);
+ Models.ModLog.initModel(this.db);
Models.Ban.initModel(this.db);
+ Models.Mute.initModel(this.db);
Models.Level.initModel(this.db);
Models.StickyRole.initModel(this.db);
await this.db.sync({ alter: true }); // Sync all tables to fix everything if updated
- this.console.success('Startup', `Successfully connected to <<database>>.`, false);
+ await this.console.success('Startup', `Successfully connected to <<database>>.`, false);
} catch (error) {
- this.console.error('Startup', `Failed to connect to <<database>> with error:\n` + error?.stack, false);
+ await this.console.error('Startup', `Failed to connect to <<database>> with error:\n` + error?.stack, false);
}
}
@@ -178,7 +188,7 @@ export class BushClient extends AkairoClient {
await this._init();
await this.login(this.token);
} catch (e) {
- this.console.error('Start', chalk.red(e.stack), false);
+ await this.console.error('Start', chalk.red(e.stack), false);
exit(2);
}
}
@@ -196,6 +206,6 @@ export class BushClient extends AkairoClient {
}
public isSuperUser(user: UserResolvable): boolean {
const userID = this.users.resolveID(user);
- return !!BushCache?.superUsers?.includes(userID) || this.config.owners.includes(userID);
+ return !!BushCache?.global?.superUsers?.includes(userID) || this.config.owners.includes(userID);
}
}
diff --git a/src/lib/extensions/BushClientUtil.ts b/src/lib/extensions/BushClientUtil.ts
index a6b049a..34a9e83 100644
--- a/src/lib/extensions/BushClientUtil.ts
+++ b/src/lib/extensions/BushClientUtil.ts
@@ -2,19 +2,11 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { exec } from 'child_process';
import { ClientUtil } from 'discord-akairo';
-import {
- APIInteractionDataResolvedChannel,
- APIInteractionDataResolvedGuildMember,
- APIMessage,
- APIRole,
- ApplicationCommandOptionType
-} from 'discord-api-types';
+import { APIMessage } from 'discord-api-types';
import {
ButtonInteraction,
CommandInteraction,
- CommandInteractionOption,
Constants,
- GuildChannel,
GuildMember,
InteractionReplyOptions,
Message,
@@ -24,7 +16,6 @@ import {
MessageEditOptions,
MessageEmbed,
MessageOptions,
- Role,
Snowflake,
TextChannel,
User,
@@ -33,7 +24,7 @@ import {
} from 'discord.js';
import got from 'got';
import { promisify } from 'util';
-import { Global } from '../models/Global';
+import { Global } from '../models';
import { BushCache } from '../utils/BushCache';
import { BushClient } from './BushClient';
import { BushMessage } from './BushMessage';
@@ -61,17 +52,6 @@ export interface uuidRes {
created_at: string;
}
-export interface SlashCommandOption<T> {
- name: string;
- type: ApplicationCommandOptionType;
- value?: T;
- options?: CommandInteractionOption[];
- user?: User;
- member?: GuildMember | APIInteractionDataResolvedGuildMember;
- channel?: GuildChannel | APIInteractionDataResolvedChannel;
- role?: Role | APIRole;
-}
-
export class BushClientUtil extends ClientUtil {
/** The client of this ClientUtil */
public declare client: BushClient;
@@ -147,7 +127,6 @@ export class BushClientUtil extends ClientUtil {
return `${url}/${res.key}`;
} catch (e) {
this.client.console.error('Haste', `Unable to upload haste to ${url}`);
- continue;
}
}
return 'Unable to post';
@@ -163,8 +142,7 @@ export class BushClientUtil extends ClientUtil {
const idMatch = text.match(idReg);
if (idMatch) {
try {
- const user = await this.client.users.fetch(text as Snowflake);
- return user;
+ return await this.client.users.fetch(text as Snowflake);
} catch {
// pass
}
@@ -173,8 +151,7 @@ export class BushClientUtil extends ClientUtil {
const mentionMatch = text.match(mentionReg);
if (mentionMatch) {
try {
- const user = await this.client.users.fetch(mentionMatch.groups.id as Snowflake);
- return user;
+ return await this.client.users.fetch(mentionMatch.groups.id as Snowflake);
} catch {
// pass
}
@@ -460,8 +437,8 @@ export class BushClientUtil extends ClientUtil {
}
/** Gets the channel configs as a TextChannel */
- public getConfigChannel(channel: 'log' | 'error' | 'dm'): Promise<TextChannel> {
- return this.client.channels.fetch(this.client.config.channels[channel]) as Promise<TextChannel>;
+ public async getConfigChannel(channel: 'log' | 'error' | 'dm'): Promise<TextChannel> {
+ return (await this.client.channels.fetch(this.client.config.channels[channel])) as TextChannel;
}
/**
@@ -488,7 +465,7 @@ export class BushClientUtil extends ClientUtil {
public async insertOrRemoveFromGlobal(
action: 'add' | 'remove',
- key: keyof typeof BushCache,
+ key: keyof typeof BushCache['global'],
value: any
): Promise<Global | void> {
const environment = this.client.config.dev ? 'development' : 'production';
@@ -502,7 +479,34 @@ export class BushClientUtil extends ClientUtil {
newValue = oldValue.filter((ae) => ae !== value);
}
row[key] = newValue;
- this.client.cache[key] = newValue;
+ this.client.cache.global[key] = newValue;
return await row.save().catch((e) => this.client.logger.error('insertOrRemoveFromGlobal', e));
}
+
+ /**
+ * Surrounds a string to the begging an end of each element in an array.
+ *
+ * @param {string[]} array The array you want to surround.
+ * @param {string} surroundChar1 The character placed in the beginning of the element (or end if surroundChar2 isn't supplied).
+ * @param {string} [surroundChar2=surroundChar1] The character placed in the end of the element.
+ * @returns {string[]}
+ */
+ public surroundArray(array: string[], surroundChar1: string, surroundChar2?: string): string[] {
+ const newArray = [];
+ array.forEach((a) => {
+ newArray.push(`${surroundChar1}${a}${surroundChar2 || surroundChar1}`);
+ });
+ return newArray;
+ }
+
+ // public createModLogEntry(
+ // user: User | Snowflake,
+ // guild: Guild | Snowflake,
+ // reason?: string,
+ // type?: ModLogType,
+ // duration?: number,
+ // moderator: User | Snowflake
+ // ): ModLog {
+
+ // }
}
diff --git a/src/lib/extensions/BushCommand.ts b/src/lib/extensions/BushCommand.ts
index bc6ff68..b62d26e 100644
--- a/src/lib/extensions/BushCommand.ts
+++ b/src/lib/extensions/BushCommand.ts
@@ -1,33 +1,47 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
-import { Command, CommandOptions } from 'discord-akairo';
+import { ArgumentGenerator, ArgumentOptions, ArgumentPromptOptions, Command, CommandOptions } from 'discord-akairo';
import { Snowflake } from 'discord.js';
import { BushClient } from './BushClient';
import { BushCommandHandler } from './BushCommandHandler';
import { BushSlashMessage } from './BushInteractionMessage';
import { BushMessage } from './BushMessage';
+export interface BushArgumentOptions extends ArgumentOptions {
+ id: string;
+ description?: string;
+ prompt?: ArgumentPromptOptions;
+}
+
export interface BushCommandOptions extends CommandOptions {
hidden?: boolean;
restrictedChannels?: Snowflake[];
restrictedGuilds?: Snowflake[];
description: {
content: string;
- usage: string;
- examples: string[];
+ usage: string | string[];
+ examples: string | string[];
};
+ args?: BushArgumentOptions[] | ArgumentGenerator;
+ category: string;
}
export class BushCommand extends Command {
public declare client: BushClient;
+
public declare handler: BushCommandHandler;
+
public options: BushCommandOptions;
+
/** The channels the command is limited to run in. */
public restrictedChannels: Snowflake[];
+
/** The guilds the command is limited to run in. */
public restrictedGuilds: Snowflake[];
+
/** Whether the command is hidden from the help command. */
public hidden: boolean;
+
constructor(id: string, options?: BushCommandOptions) {
super(id, options);
this.options = options;
diff --git a/src/lib/extensions/BushCommandHandler.ts b/src/lib/extensions/BushCommandHandler.ts
index 8e8936e..aeea101 100644
--- a/src/lib/extensions/BushCommandHandler.ts
+++ b/src/lib/extensions/BushCommandHandler.ts
@@ -86,9 +86,6 @@ export class BushCommandHandler extends CommandHandler {
this.emit(CommandHandlerEvents.COMMAND_BLOCKED, message, command, reason);
return true;
}
- if (this.runCooldowns(message, command)) {
- return true;
- }
- return false;
+ return !!this.runCooldowns(message, command);
}
}
diff --git a/src/lib/extensions/BushInhibitor.ts b/src/lib/extensions/BushInhibitor.ts
index 85d6de8..8a31abf 100644
--- a/src/lib/extensions/BushInhibitor.ts
+++ b/src/lib/extensions/BushInhibitor.ts
@@ -1,6 +1,15 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
import { Inhibitor } from 'discord-akairo';
import { BushClient } from './BushClient';
+import { BushCommand } from './BushCommand';
+import { BushSlashMessage } from './BushInteractionMessage';
+import { BushMessage } from './BushMessage';
export class BushInhibitor extends Inhibitor {
public declare client: BushClient;
+
+ public exec(message: BushMessage, command: BushCommand): any;
+ public exec(message: BushMessage | BushSlashMessage, command: BushCommand): any {
+ super.exec(message, command);
+ }
}
diff --git a/src/lib/extensions/BushInteractionMessage.ts b/src/lib/extensions/BushInteractionMessage.ts
index ade11ea..62d2519 100644
--- a/src/lib/extensions/BushInteractionMessage.ts
+++ b/src/lib/extensions/BushInteractionMessage.ts
@@ -1,15 +1,16 @@
import { AkairoMessage } from 'discord-akairo';
import { CommandInteraction } from 'discord.js';
import { BushClient } from './BushClient';
+import { BushCommandUtil } from './BushCommandUtil';
export class BushSlashMessage extends AkairoMessage {
+ public declare client: BushClient;
+ public declare util: BushCommandUtil;
public constructor(
client: BushClient,
interaction: CommandInteraction,
{ slash, replied }: { slash?: boolean; replied?: boolean }
) {
super(client, interaction, { slash, replied });
- this.client = client;
- this.interaction = interaction;
}
}
diff --git a/src/lib/extensions/BushTaskHandler.ts b/src/lib/extensions/BushTaskHandler.ts
index 923e42b..588988d 100644
--- a/src/lib/extensions/BushTaskHandler.ts
+++ b/src/lib/extensions/BushTaskHandler.ts
@@ -6,7 +6,6 @@ export type BushTaskHandlerOptions = AkairoHandlerOptions;
export class BushTaskHandler extends TaskHandler {
public constructor(client: BushClient, options: BushTaskHandlerOptions) {
super(client, options);
- this.client;
}
declare client: BushClient;
}
diff --git a/src/lib/models/Ban.ts b/src/lib/models/Ban.ts
index 8ba55ec..f4463b8 100644
--- a/src/lib/models/Ban.ts
+++ b/src/lib/models/Ban.ts
@@ -1,7 +1,6 @@
import { Snowflake } from 'discord.js';
import { DataTypes, Sequelize } from 'sequelize';
import { v4 as uuidv4 } from 'uuid';
-import * as Models from './';
import { BaseModel } from './BaseModel';
export interface BanModel {
@@ -64,7 +63,7 @@ export class Ban extends BaseModel<BanModel, BanModelCreationAttributes> impleme
type: DataTypes.STRING,
allowNull: false,
references: {
- model: Models.Guild,
+ model: 'Guilds',
key: 'id'
}
},
@@ -78,11 +77,11 @@ export class Ban extends BaseModel<BanModel, BanModelCreationAttributes> impleme
},
modlog: {
type: DataTypes.STRING,
- allowNull: false
- // references: {
- // model: Models.Modlog,
- // key: 'id'
- // }
+ allowNull: false,
+ references: {
+ model: 'ModLogs',
+ key: 'id'
+ }
}
},
{ sequelize: sequelize }
diff --git a/src/lib/models/Global.ts b/src/lib/models/Global.ts
index abe0ab3..842f14b 100644
--- a/src/lib/models/Global.ts
+++ b/src/lib/models/Global.ts
@@ -80,7 +80,7 @@ export class Global extends BaseModel<GlobalModel, GlobalModelCreationAttributes
allowNull: true
}
},
- { sequelize }
+ { sequelize: sequelize }
);
}
}
diff --git a/src/lib/models/Guild.ts b/src/lib/models/Guild.ts
index c4ae53e..480cc60 100644
--- a/src/lib/models/Guild.ts
+++ b/src/lib/models/Guild.ts
@@ -8,16 +8,24 @@ export interface GuildModel {
prefix: string;
autoPublishChannels: string[];
blacklistedChannels: Snowflake[];
+ welcomeChannel: Snowflake;
+ muteRole: Snowflake;
}
-export type GuildModelCreationAttributes = Optional<GuildModel, 'prefix' | 'autoPublishChannels' | 'blacklistedChannels'>;
+export type GuildModelCreationAttributes = Optional<
+ GuildModel,
+ 'prefix' | 'autoPublishChannels' | 'blacklistedChannels' | 'welcomeChannel' | 'muteRole'
+>;
export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> implements GuildModel {
id: string;
prefix: string;
autoPublishChannels: string[];
blacklistedChannels: Snowflake[];
- static initModel(seqeulize: Sequelize, client: BushClient): void {
+ welcomeChannel: Snowflake;
+ muteRole: Snowflake;
+
+ static initModel(sequelize: Sequelize, client: BushClient): void {
Guild.init(
{
id: {
@@ -48,9 +56,17 @@ export class Guild extends BaseModel<GuildModel, GuildModelCreationAttributes> i
return this.setDataValue('blacklistedChannels', JSON.stringify(val) as unknown as Snowflake[]);
},
allowNull: true
+ },
+ welcomeChannel: {
+ type: DataTypes.STRING,
+ allowNull: true
+ },
+ muteRole: {
+ type: DataTypes.STRING,
+ allowNull: true
}
},
- { sequelize: seqeulize }
+ { sequelize: sequelize }
);
}
}
diff --git a/src/lib/models/Level.ts b/src/lib/models/Level.ts
index 426ec1a..e1f30f4 100644
--- a/src/lib/models/Level.ts
+++ b/src/lib/models/Level.ts
@@ -46,7 +46,6 @@ export class Level extends BaseModel<LevelModel, LevelModelCreationAttributes> {
break;
} else {
i++;
- continue;
}
}
return lvl - 1; // I have to do this don't question it ok
diff --git a/src/lib/models/Modlog.ts b/src/lib/models/Modlog.ts
index 15c5030..94c464d 100644
--- a/src/lib/models/Modlog.ts
+++ b/src/lib/models/Modlog.ts
@@ -2,18 +2,20 @@ import { DataTypes, Sequelize } from 'sequelize';
import { v4 as uuidv4 } from 'uuid';
import { BaseModel } from './BaseModel';
-export enum ModlogType {
+export enum ModLogType {
BAN = 'BAN',
- TEMPBAN = 'TEMPBAN',
+ TEMP_BAN = 'TEMP_BAN',
KICK = 'KICK',
MUTE = 'MUTE',
- TEMPMUTE = 'TEMPMUTE',
- WARN = 'WARN'
+ TEMP_MUTE = 'TEMP_MUTE',
+ WARN = 'WARN',
+ PUNISHMENT_ROLE = 'PUNISHMENT_ROLE',
+ TEMP_PUNISHMENT_ROLE = 'TEMP_PUNISHMENT_ROLE'
}
-export interface ModlogModel {
+export interface ModLogModel {
id: string;
- type: ModlogType;
+ type: ModLogType;
user: string;
moderator: string;
reason: string;
@@ -21,9 +23,9 @@ export interface ModlogModel {
guild: string;
}
-export interface ModlogModelCreationAttributes {
+export interface ModLogModelCreationAttributes {
id?: string;
- type: ModlogType;
+ type: ModLogType;
user: string;
moderator: string;
reason?: string;
@@ -31,9 +33,9 @@ export interface ModlogModelCreationAttributes {
guild: string;
}
-export class Modlog extends BaseModel<ModlogModel, ModlogModelCreationAttributes> implements ModlogModel {
+export class ModLog extends BaseModel<ModLogModel, ModLogModelCreationAttributes> implements ModLogModel {
id: string;
- type: ModlogType;
+ type: ModLogType;
user: string;
moderator: string;
guild: string;
@@ -41,7 +43,7 @@ export class Modlog extends BaseModel<ModlogModel, ModlogModelCreationAttributes
duration: number | null;
static initModel(sequelize: Sequelize): void {
- Modlog.init(
+ ModLog.init(
{
id: {
type: DataTypes.STRING,
@@ -50,7 +52,7 @@ export class Modlog extends BaseModel<ModlogModel, ModlogModelCreationAttributes
defaultValue: uuidv4
},
type: {
- type: new DataTypes.ENUM('BAN', 'TEMPBAN', 'MUTE', 'TEMPMUTE', 'KICK', 'WARN'),
+ type: DataTypes.STRING, //# This is not an enum because of a sequelize issue: https://github.com/sequelize/sequelize/issues/2554
allowNull: false
},
user: {
@@ -70,14 +72,14 @@ export class Modlog extends BaseModel<ModlogModel, ModlogModelCreationAttributes
allowNull: true
},
guild: {
- type: DataTypes.STRING
- // references: {
- // model: Models.Guild,
- // key: 'id'
- // }
+ type: DataTypes.STRING,
+ references: {
+ model: 'Guilds',
+ key: 'id'
+ }
}
},
- { sequelize }
+ { sequelize: sequelize }
);
}
}
diff --git a/src/lib/models/Mute.ts b/src/lib/models/Mute.ts
new file mode 100644
index 0000000..273d5b1
--- /dev/null
+++ b/src/lib/models/Mute.ts
@@ -0,0 +1,90 @@
+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: string;
+ guild: string;
+ reason: string;
+ expires: Date;
+ modlog: string;
+}
+export interface MuteModelCreationAttributes {
+ id?: string;
+ user: string;
+ guild: string;
+ reason?: string;
+ 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 reason they are muted (optional)
+ */
+ reason: string | null;
+ /**
+ * 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
+ },
+ reason: {
+ type: DataTypes.STRING,
+ allowNull: true
+ },
+ modlog: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ references: {
+ model: 'ModLogs',
+ key: 'id'
+ }
+ }
+ },
+ { sequelize: sequelize }
+ );
+ }
+}
diff --git a/src/lib/models/PunishmentRole.ts b/src/lib/models/PunishmentRole.ts
new file mode 100644
index 0000000..3326dca
--- /dev/null
+++ b/src/lib/models/PunishmentRole.ts
@@ -0,0 +1,93 @@
+import { Snowflake } from 'discord.js';
+import { DataTypes, Sequelize } from 'sequelize';
+import { v4 as uuidv4 } from 'uuid';
+import { BaseModel } from './BaseModel';
+
+export interface PunishmentRoleModel {
+ id: string;
+ user: string;
+ guild: string;
+ reason: string;
+ expires: Date;
+ modlog: string;
+}
+export interface PunishmentRoleModelCreationAttributes {
+ id?: string;
+ user: string;
+ guild: string;
+ reason?: string;
+ expires?: Date;
+ modlog: string;
+}
+
+export class PunishmentRole
+ extends BaseModel<PunishmentRoleModel, PunishmentRoleModelCreationAttributes>
+ implements PunishmentRoleModel
+{
+ /**
+ * The ID of this punishment role (no real use just for a primary key)
+ */
+ id: string;
+ /**
+ * The user who received a role
+ */
+ user: Snowflake;
+ /**
+ * The guild they received a role in
+ */
+ guild: Snowflake;
+ /**
+ * The reason they received a role (optional)
+ */
+ reason: string | null;
+ /**
+ * The date at which this role expires and should be removed (optional)
+ */
+ expires: Date | null;
+ /**
+ * The ref to the modlog entry
+ */
+ modlog: string;
+
+ static initModel(sequelize: Sequelize): void {
+ PunishmentRole.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
+ },
+ reason: {
+ type: DataTypes.STRING,
+ allowNull: true
+ },
+ modlog: {
+ type: DataTypes.STRING,
+ allowNull: false,
+ references: {
+ model: 'ModLogs',
+ key: 'id'
+ }
+ }
+ },
+ { sequelize: sequelize }
+ );
+ }
+}
diff --git a/src/lib/models/index.ts b/src/lib/models/index.ts
index e38ad69..794c335 100644
--- a/src/lib/models/index.ts
+++ b/src/lib/models/index.ts
@@ -3,5 +3,7 @@ export * from './BaseModel';
export * from './Global';
export * from './Guild';
export * from './Level';
-export * from './Modlog';
+export * from './ModLog';
+export * from './Mute';
+export * from './PunishmentRole';
export * from './StickyRole';
diff --git a/src/lib/utils/BushCache.ts b/src/lib/utils/BushCache.ts
index 947b15d..ffef470 100644
--- a/src/lib/utils/BushCache.ts
+++ b/src/lib/utils/BushCache.ts
@@ -1,9 +1,13 @@
import { Snowflake } from 'discord.js';
-export class BushCache {
+class GlobalCache {
public static superUsers = new Array<Snowflake>();
public static disabledCommands = new Array<string>();
public static blacklistedChannels = new Array<Snowflake>();
public static blacklistedGuilds = new Array<Snowflake>();
public static blacklistedUsers = new Array<Snowflake>();
}
+
+export class BushCache {
+ public static global = GlobalCache;
+}
diff --git a/src/lib/utils/BushLogger.ts b/src/lib/utils/BushLogger.ts
index 2225bde..6adacfd 100644
--- a/src/lib/utils/BushLogger.ts
+++ b/src/lib/utils/BushLogger.ts
@@ -105,7 +105,7 @@ export class BushLogger {
.setDescription(`**[${header}]** ${this.parseFormatting(this.stripColor(newContent), '', true)}`)
.setColor(this.client.util.colors.gray)
.setTimestamp();
- this.channelLog({ embeds: [embed] });
+ await this.channelLog({ embeds: [embed] });
}
/**
@@ -125,7 +125,7 @@ export class BushLogger {
.setDescription(`**[${header}]** ${this.parseFormatting(this.stripColor(newContent), '', true)}`)
.setColor(this.client.util.colors.info)
.setTimestamp();
- this.channelLog({ embeds: [embed] });
+ await this.channelLog({ embeds: [embed] });
}
/**
@@ -146,7 +146,7 @@ export class BushLogger {
.setDescription(`**[${header}]** ${this.parseFormatting(this.stripColor(newContent), '', true)}`)
.setColor(this.client.util.colors.warn)
.setTimestamp();
- this.channelLog({ embeds: [embed] });
+ await this.channelLog({ embeds: [embed] });
}
/**
@@ -166,7 +166,7 @@ export class BushLogger {
.setDescription(`**[${header}]** ${this.parseFormatting(this.stripColor(newContent), '', true)}`)
.setColor(this.client.util.colors.error)
.setTimestamp();
- this.channelError({ embeds: [embed] });
+ await this.channelError({ embeds: [embed] });
}
/**
diff --git a/src/lib/utils/CanvasProgressBar.ts b/src/lib/utils/CanvasProgressBar.ts
index aa8630a..d870bf9 100644
--- a/src/lib/utils/CanvasProgressBar.ts
+++ b/src/lib/utils/CanvasProgressBar.ts
@@ -1,10 +1,10 @@
// I just copy pasted this code from stackoverflow don't yell at me if there is issues for it
export class CanvasProgressBar {
- private x: number;
- private y: number;
- private w: number;
- private h: number;
- private color: string;
+ private readonly x: number;
+ private readonly y: number;
+ private readonly w: number;
+ private readonly h: number;
+ private readonly color: string;
private percentage: number;
private p: number;
private ctx: CanvasRenderingContext2D;
diff --git a/src/listeners/client/ready.ts b/src/listeners/client/ready.ts
index a87d216..1a51527 100644
--- a/src/listeners/client/ready.ts
+++ b/src/listeners/client/ready.ts
@@ -10,15 +10,15 @@ export default class ReadyListener extends BushListener {
}
public async exec(): Promise<void> {
- //@ts-expect-error: ik its private, this is the only time I need to access it outside of its class
- const timeStamp = chalk.bgGreen(this.client.logger.getTimeStamp()),
- tag = chalk.magenta(this.client.user.tag),
- guildCount = chalk.magenta(this.client.guilds.cache.size.toLocaleString()),
- userCount = chalk.magenta(this.client.users.cache.size.toLocaleString());
+ const tag = `<<${this.client.user.tag}>>`,
+ guildCount = `<<${this.client.guilds.cache.size.toLocaleString()}>>`,
+ userCount = `<<${this.client.users.cache.size.toLocaleString()}>>`;
- console.log(`${timeStamp} Logged in to ${tag} serving ${guildCount} guilds and ${userCount} users.`);
+ this.client.logger.success('Ready', `Logged in to ${tag} serving ${guildCount} guilds and ${userCount} users.`);
console.log(
- chalk.blue(`----------------------------------------------------------------------${this.client.config.dev ? '---' : ''}`)
+ chalk.blue(
+ `------------------------------------------------------------------------------${this.client.config.dev ? '---' : ''}`
+ )
);
this.client.user.setPresence({
diff --git a/src/listeners/commands/commandBlocked.ts b/src/listeners/commands/commandBlocked.ts
index febfc93..816005d 100644
--- a/src/listeners/commands/commandBlocked.ts
+++ b/src/listeners/commands/commandBlocked.ts
@@ -14,7 +14,7 @@ export default class CommandBlockedListener extends BushListener {
this.client.console.info(
'CommandBlocked',
`<<${message.author.tag}>> tried to run <<${message.util.parsed.command}>> but was blocked because <<${reason}>>.`,
- false
+ true
);
const reasons = this.client.consts.BlockedReasons;
diff --git a/src/listeners/commands/commandError.ts b/src/listeners/commands/commandError.ts
index 7f765ae..85ff11f 100644
--- a/src/listeners/commands/commandError.ts
+++ b/src/listeners/commands/commandError.ts
@@ -26,13 +26,13 @@ export default class CommandErrorListener extends BushListener {
.setColor(this.client.util.colors.error)
.setTimestamp();
+ await this.client.logger.channelError({ embeds: [errorEmbed] });
if (message) {
if (!this.client.config.owners.includes(message.author.id)) {
const errorUserEmbed: MessageEmbed = new MessageEmbed()
- .setTitle('An error occurred')
+ .setTitle('A Command Error Occurred')
.setColor(this.client.util.colors.error)
.setTimestamp();
- await this.client.logger.channelError({ embeds: [errorEmbed] });
if (!command)
errorUserEmbed.setDescription(`Oh no! An error occurred. Please give the developers code \`${errorNo}\`.`);
else
@@ -45,7 +45,7 @@ export default class CommandErrorListener extends BushListener {
});
} else {
const errorDevEmbed = new MessageEmbed()
- .setTitle('An error occurred')
+ .setTitle('A Command Error Occurred')
.setColor(this.client.util.colors.error)
.setTimestamp()
.setDescription(await this.client.util.codeblock(`${error?.stack}`, 2048, 'js'));
diff --git a/src/listeners/commands/commandMissingPermissions.ts b/src/listeners/commands/commandMissingPermissions.ts
new file mode 100644
index 0000000..d695d25
--- /dev/null
+++ b/src/listeners/commands/commandMissingPermissions.ts
@@ -0,0 +1,55 @@
+import { BushCommand } from '../../lib/extensions/BushCommand';
+import { BushListener } from '../../lib/extensions/BushListener';
+import { BushMessage } from '../../lib/extensions/BushMessage';
+
+export default class CommandMissingPermissionsListener extends BushListener {
+ public constructor() {
+ super('commandMissingPermissions', {
+ emitter: 'commandHandler',
+ event: 'missingPermissions',
+ category: 'commands'
+ });
+ }
+
+ public async exec(
+ message: BushMessage,
+ command: BushCommand | null | undefined,
+ type: 'client' | 'user',
+ missing: Array<string>
+ ): Promise<void> {
+ const niceMissing = [];
+ missing.forEach((missing) => {
+ if (this.client.consts.mappings.permissions[missing]) {
+ niceMissing.push(this.client.consts.mappings.permissions[missing].name);
+ } else {
+ niceMissing.push(missing);
+ }
+ });
+
+ const discordFormat = this.client.util.oxford(this.client.util.surroundArray(niceMissing, '`'), 'and', '');
+ const consoleFormat = this.client.util.oxford(this.client.util.surroundArray(niceMissing, '<<', '>>'), 'and', '');
+ this.client.console.info(
+ 'CommandMissingPermissions',
+ `<<${message.author.tag}>> tried to run <<${
+ command?.id
+ }>> but could not because <<${type}>> is missing the ${consoleFormat} permissions${missing.length ? 's' : ''}.`
+ );
+ if (type == 'client') {
+ 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 message.util
+ .reply(
+ `${this.client.util.emojis.error} You are missing the ${discordFormat} permission${
+ missing.length ? 's' : ''
+ } required for the \`${command?.id}\` command.`
+ )
+ .catch(() => {});
+ }
+ }
+}
diff --git a/src/listeners/commands/commandStarted.ts b/src/listeners/commands/commandStarted.ts
index 8c860f8..9266fc7 100644
--- a/src/listeners/commands/commandStarted.ts
+++ b/src/listeners/commands/commandStarted.ts
@@ -15,7 +15,7 @@ export default class CommandStartedListener extends BushListener {
`The <<${command.id}>> command was used by <<${message.author.tag}>> in ${
message.channel.type === 'dm' ? `their <<DMs>>` : `<<#${message.channel.name}>> in <<${message.guild?.name}>>`
}.`,
- false // I don't want to spam the log channel when people use commands
+ true //// I don't want to spam the log channel when people use commands
);
}
}
diff --git a/src/listeners/commands/slashBlocked.ts b/src/listeners/commands/slashBlocked.ts
index e87ba70..e64253a 100644
--- a/src/listeners/commands/slashBlocked.ts
+++ b/src/listeners/commands/slashBlocked.ts
@@ -14,7 +14,7 @@ export default class SlashBlockedListener extends BushListener {
this.client.console.info(
'SlashBlocked',
`<<${message.author.tag}>> tried to run <<${message.util.parsed.command}>> but was blocked because <<${reason}>>.`,
- false
+ true
);
const reasons = this.client.consts.BlockedReasons;
diff --git a/src/listeners/commands/slashCommandError.ts b/src/listeners/commands/slashCommandError.ts
index b9123e8..1a5f293 100644
--- a/src/listeners/commands/slashCommandError.ts
+++ b/src/listeners/commands/slashCommandError.ts
@@ -18,20 +18,21 @@ export default class SlashCommandErrorListener extends BushListener {
.setDescription(
stripIndents`**User:** ${message.author} (${message.author.tag})
**Slash Command:** ${command}
- **Channel:** ${message.channel} (${message.channel.id})
- **Message:** [link](https://discord.com/${message.guild.id}/${message.guild.id}/${message.id})`
+ **Channel:** ${message.channel || message.interaction.user?.tag} ${message.channel ? `(${message.channel?.id})` : ''}
+ **Message:** [link](https://discord.com/${message.guild?.id}/${message.channel?.id}/${message.id})`
)
.addField('Error', await this.client.util.codeblock(`${error?.stack}`, 1024, 'js'))
.setColor(this.client.util.colors.error)
.setTimestamp();
+ await this.client.logger.channelError({ embeds: [errorEmbed] });
if (message) {
+ const channel = message.channel?.name || message.interaction.user.tag;
if (!this.client.config.owners.includes(message.author.id)) {
const errorUserEmbed: MessageEmbed = new MessageEmbed()
- .setTitle('An error occurred')
+ .setTitle('A Slash Command Error Occurred')
.setColor(this.client.util.colors.error)
.setTimestamp();
- await this.client.logger.channelError({ embeds: [errorEmbed] });
if (!command)
errorUserEmbed.setDescription(`Oh no! An error occurred. Please give the developers code \`${errorNo}\`.`);
else
@@ -39,22 +40,20 @@ export default class SlashCommandErrorListener extends BushListener {
`Oh no! While running the command \`${command.id}\`, an error occurred. Please give the developers code \`${errorNo}\`.`
);
await message.util.send({ embeds: [errorUserEmbed] }).catch((e) => {
- const channel = message.channel.type === 'dm' ? message.channel.recipient.tag : message.channel.name;
this.client.console.warn('SlashError', `Failed to send user error embed in <<${channel}>>:\n` + e?.stack);
});
} else {
const errorDevEmbed = new MessageEmbed()
- .setTitle('An error occurred')
+ .setTitle('A Slash Command Error Occurred')
.setColor(this.client.util.colors.error)
.setTimestamp()
.setDescription(await this.client.util.codeblock(`${error?.stack}`, 2048, 'js'));
await message.util.send({ embeds: [errorDevEmbed] }).catch((e) => {
- const channel = message.channel.type === 'dm' ? message.channel.recipient.tag : message.channel.name;
this.client.console.warn('SlashError', `Failed to send owner error stack in <<${channel}>>.` + e?.stack);
});
}
}
- const channel = message.channel.type === 'dm' ? message.channel.recipient.tag : message.channel.name;
+ const channel = message.channel?.name || message.interaction.user.tag;
this.client.console.error(
'SlashError',
`an error occurred with the <<${command}>> command in <<${channel}>> triggered by <<${message?.author?.tag}>>:\n` +
diff --git a/src/listeners/commands/slashMissingPermissions.ts b/src/listeners/commands/slashMissingPermissions.ts
new file mode 100644
index 0000000..1f3599f
--- /dev/null
+++ b/src/listeners/commands/slashMissingPermissions.ts
@@ -0,0 +1,58 @@
+import { Command } from 'discord-akairo';
+import { CommandInteraction } from 'discord.js';
+import { BushListener } from '../../lib/extensions/BushListener';
+
+export default class SlashMissingPermissionsListener extends BushListener {
+ public constructor() {
+ super('slashMissingPermissions', {
+ emitter: 'commandHandler',
+ event: 'slashMissingPermissions',
+ category: 'slashCommands'
+ });
+ }
+
+ public async exec(
+ interaction: CommandInteraction,
+ command: Command,
+ type: 'user' | 'client',
+ missing?: string[]
+ ): Promise<void> {
+ const niceMissing = [];
+ missing.forEach((missing) => {
+ if (this.client.consts.mappings.permissions[missing]) {
+ niceMissing.push(this.client.consts.mappings.permissions[missing].name);
+ } else {
+ niceMissing.push(missing);
+ }
+ });
+
+ const discordFormat = this.client.util.oxford(this.client.util.surroundArray(niceMissing, '`'), 'and', '');
+ const consoleFormat = this.client.util.oxford(this.client.util.surroundArray(niceMissing, '<<', '>>'), 'and', '');
+ this.client.console.info(
+ 'CommandMissingPermissions',
+ `<<${interaction.user.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,
+ `${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,
+ `${this.client.util.emojis.error} You are missing the ${discordFormat} permission${
+ missing.length ? 's' : ''
+ } required for the \`${command?.id}\` command.`
+ )
+ .catch(() => {});
+ }
+ }
+}
diff --git a/src/listeners/commands/slashStarted.ts b/src/listeners/commands/slashStarted.ts
index 6a45546..c6b966a 100644
--- a/src/listeners/commands/slashStarted.ts
+++ b/src/listeners/commands/slashStarted.ts
@@ -1,5 +1,5 @@
-import { Message } from 'discord.js';
import { BushCommand } from '../../lib/extensions/BushCommand';
+import { BushSlashMessage } from '../../lib/extensions/BushInteractionMessage';
import { BushListener } from '../../lib/extensions/BushListener';
export default class SlashStartedListener extends BushListener {
@@ -9,13 +9,13 @@ export default class SlashStartedListener extends BushListener {
event: 'slashStarted'
});
}
- exec(message: Message, command: BushCommand): void {
+ exec(message: BushSlashMessage, command: BushCommand): void {
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}>>`
+ !message.channel ? `their <<DMs>>` : `<<#${message.channel.name}>> in <<${message.guild?.name}>>`
}.`,
- false // I don't want to spam the log channel when people use commands
+ true //// I don't want to spam the log channel when people use commands
);
}
}
diff --git a/src/listeners/message/level.ts b/src/listeners/message/level.ts
index 74c4db8..9f413e5 100644
--- a/src/listeners/message/level.ts
+++ b/src/listeners/message/level.ts
@@ -32,8 +32,7 @@ export default class LevelListener extends BushListener {
this.client.logger.error('LevelMessageListener', e);
return false;
});
- if (success)
- await this.client.logger.verbose(`LevelMessageListener`, `Gave <<${xpToGive}>> XP to <<${message.author.tag}>>.`);
+ if (success) this.client.logger.verbose(`LevelMessageListener`, `Gave <<${xpToGive}>> XP to <<${message.author.tag}>>.`);
this.levelCooldowns.add(message.author.id);
setTimeout(() => this.levelCooldowns.delete(message.author.id), 60_000);
}
diff --git a/src/listeners/other/consoleListener.ts b/src/listeners/other/consoleListener.ts
index 50c0cf3..d1e318f 100644
--- a/src/listeners/other/consoleListener.ts
+++ b/src/listeners/other/consoleListener.ts
@@ -30,21 +30,6 @@ export default class ConsoleListener extends BushListener {
} catch (e) {
console.error(e);
}
- } /* else if (line.startsWith('reload')) {
- exec('npx tsc', (error) => {
- if (error) {
- return this.client.console.error('Reload', `Error recompiling, \`${error.message}\``);
- }
- try {
- this.client.commandHandler.reloadAll();
- this.client.listenerHandler.reloadAll();
- } catch (e) {
- return this.client.console.error('Reload', e);
- }
- this.client.console.success('Reload', 'Reloaded successfully.');
- });
- } else if (line.startsWith('stop') || line.startsWith('exit')) {
- process.exit();
- } */
+ }
}
}
diff --git a/src/listeners/other/promiseRejection.ts b/src/listeners/other/promiseRejection.ts
index 2d7e316..143659a 100644
--- a/src/listeners/other/promiseRejection.ts
+++ b/src/listeners/other/promiseRejection.ts
@@ -10,7 +10,7 @@ export default class PromiseRejectionListener extends BushListener {
public async exec(error: Error): Promise<void> {
this.client.console.error('PromiseRejection', 'An unhanded promise rejection occurred:\n' + error.stack, false);
- await this.client.console.channelError({
+ this.client.console.channelError({
embeds: [
{
title: 'Unhandled promise rejection',
diff --git a/src/tasks/removePunishmentRole.ts b/src/tasks/removePunishmentRole.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/tasks/removePunishmentRole.ts
diff --git a/src/tasks/unmute.ts b/src/tasks/unmute.ts
index e69de29..33cbd92 100644
--- a/src/tasks/unmute.ts
+++ b/src/tasks/unmute.ts
@@ -0,0 +1,45 @@
+import { DiscordAPIError } from 'discord.js';
+import { Op } from 'sequelize';
+import { BushTask } from '../lib/extensions/BushTask';
+import { Guild } from '../lib/models';
+import { Mute } from '../lib/models/Mute';
+
+export default class UnmuteTask extends BushTask {
+ 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);
+ const muteRole = (await Guild.findByPk(row.guild)).muteRole;
+ if (!guild) {
+ await row.destroy();
+ continue;
+ }
+ try {
+ await (await guild.members.fetch(row.user)).roles.remove(muteRole);
+ } catch (e) {
+ if (e instanceof DiscordAPIError) {
+ // ignore
+ } else throw e;
+ }
+ await row.destroy();
+ this.client.logger.verbose(`UnmuteTask`, `Unmuted ${row.user}`);
+ }
+ }
+}
diff --git a/src/tasks/updateCache.ts b/src/tasks/updateCache.ts
index 6c1f098..3f213f2 100644
--- a/src/tasks/updateCache.ts
+++ b/src/tasks/updateCache.ts
@@ -1,15 +1,26 @@
+import { BushClient } from '../lib/extensions/BushClient';
import { BushTask } from '../lib/extensions/BushTask';
import { Global } from '../lib/models';
+import * as config from './../config/options';
export default class UpdateCacheTask extends BushTask {
constructor() {
super('updateCache', {
delay: 300_000, // 5 minutes
- runOnStart: true
+ runOnStart: false // done in preinit task
});
}
async exec(): Promise<void> {
- const environment = this.client.config.dev ? 'development' : 'production';
+ await this.updateCache(this.client);
+ await this.client.logger.verbose(`UpdateCache`, `Updated cache.`);
+ }
+
+ async init(client: BushClient): Promise<void> {
+ await this.updateCache(client);
+ }
+
+ async updateCache(client: BushClient): Promise<void> {
+ const environment = config.dev ? 'development' : 'production';
const row =
(await Global.findByPk(environment)) ||
(await Global.create({
@@ -22,8 +33,7 @@ export default class UpdateCacheTask extends BushTask {
}));
for (const option in row) {
- if (this.client.cache[option]) this.client.cache[option] = row[option];
+ if (client.cache[option]) this.client.cache[option] = row[option];
}
- this.client.logger.verbose(`UpdateCache`, `Updated cache.`);
}
}