aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json1
-rw-r--r--src/arguments/roleWithDuation.ts13
-rw-r--r--src/commands/config/blacklist.ts4
-rw-r--r--src/commands/config/settings.ts142
-rw-r--r--src/commands/dev/superUser.ts11
-rw-r--r--src/commands/info/avatar.ts5
-rw-r--r--src/commands/leveling/leaderboard.ts0
-rw-r--r--src/commands/leveling/level.ts (renamed from src/commands/moulberry-bush/level.ts)105
-rw-r--r--src/commands/moderation/ban.ts3
-rw-r--r--src/commands/moderation/modlog.ts3
-rw-r--r--src/commands/moderation/mute.ts3
-rw-r--r--src/commands/moderation/role.ts90
-rw-r--r--src/commands/moderation/slowmode.ts5
-rw-r--r--src/commands/moulberry-bush/rule.ts2
-rw-r--r--src/commands/utilities/decode.ts3
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts4
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts4
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts6
-rw-r--r--src/lib/extensions/discord.js/BushGuild.ts6
-rw-r--r--src/lib/extensions/discord.js/BushGuildMember.ts2
-rw-r--r--yarn.lock12
21 files changed, 275 insertions, 149 deletions
diff --git a/package.json b/package.json
index d7fa089..8bea052 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"sequelize": "^6.5.0",
+ "simplify-number": "^1.0.0",
"source-map-support": "^0.5.19",
"tinycolor2": "^1.4.2",
"typescript": "^4.4.2",
diff --git a/src/arguments/roleWithDuation.ts b/src/arguments/roleWithDuation.ts
new file mode 100644
index 0000000..423e7df
--- /dev/null
+++ b/src/arguments/roleWithDuation.ts
@@ -0,0 +1,13 @@
+import { BushArgumentTypeCaster } from '@lib';
+
+export const roleWithDurationTypeCaster: BushArgumentTypeCaster = async (
+ message,
+ phrase
+): Promise<{ duration: number; role: string | null } | null> => {
+ const { duration, contentWithoutTime } = client.util.parseDuration(phrase);
+ if (contentWithoutTime === null || contentWithoutTime === undefined) return null;
+ const role = await util.arg.cast('role', client.commandHandler.resolver, message, contentWithoutTime);
+ console.debug(['role'], [role], [contentWithoutTime]);
+ if (!role) return null;
+ return { duration, role };
+};
diff --git a/src/commands/config/blacklist.ts b/src/commands/config/blacklist.ts
index 57c3015..ff34567 100644
--- a/src/commands/config/blacklist.ts
+++ b/src/commands/config/blacklist.ts
@@ -68,8 +68,8 @@ export default class BlacklistCommand extends BushCommand {
const global = args.global && message.author.isOwner();
const target =
typeof args.target === 'string'
- ? (await Argument.cast('channel', client.commandHandler.resolver, message as BushMessage, args.target)) ??
- (await Argument.cast('user', client.commandHandler.resolver, message as BushMessage, args.target))
+ ? (await util.arg.cast('channel', client.commandHandler.resolver, message as BushMessage, args.target)) ??
+ (await util.arg.cast('user', client.commandHandler.resolver, message as BushMessage, args.target))
: args.target;
if (!target) return await message.util.reply(`${util.emojis.error} Choose a valid channel or user.`);
const targetID = target.id;
diff --git a/src/commands/config/settings.ts b/src/commands/config/settings.ts
index a8070e2..8a5f290 100644
--- a/src/commands/config/settings.ts
+++ b/src/commands/config/settings.ts
@@ -1,4 +1,5 @@
-import { BushCommand, BushMessage, BushSlashMessage, guildSettingsObj, settingsArr } from '@lib';
+import { BushCommand, BushMessage, BushSlashMessage, GuildSettings, guildSettingsObj, settingsArr } from '@lib';
+import { ArgumentOptions, Flag } from 'discord-akairo';
import {
Message,
MessageActionRow,
@@ -8,6 +9,7 @@ import {
MessageOptions,
MessageSelectMenu
} from 'discord.js';
+import _ from 'lodash';
export default class SettingsCommand extends BushCommand {
public constructor() {
@@ -16,13 +18,15 @@ export default class SettingsCommand extends BushCommand {
category: 'config',
description: {
content: 'Configure server options.',
- usage: 'settings',
+ usage: `settings (${settingsArr.map((s) => `\`${s}\``).join(', ')}) (${['view', 'set', 'add', 'remove'].map(
+ (s) => `\`${s}\``
+ )})`,
examples: ['settings']
},
slash: true,
slashOptions: settingsArr.map((setting) => {
return {
- name: util.camelToSnakeCase(setting),
+ name: _.snakeCase(setting),
description: `Manage the server's ${guildSettingsObj[setting].name.toLowerCase()}`,
type: 'SUB_COMMAND_GROUP',
options: guildSettingsObj[setting].type.includes('-array')
@@ -95,14 +99,122 @@ export default class SettingsCommand extends BushCommand {
});
}
- // *args(): any {}
+ // I make very readable code :)
+ *args(message: BushMessage): IterableIterator<ArgumentOptions | Flag> {
+ const setting = yield {
+ id: 'setting',
+ type: settingsArr,
+ prompt: {
+ start: `What setting would you like to see or change? You can choose one of the following: ${settingsArr
+ .map((s) => `\`${s}\``)
+ .join(', ')}`,
+ retry: `{error} Choose one of the following settings: ${settingsArr.map((s) => `\`${s}\``).join(', ')}`,
+ optional: message.util.parsed!.alias === 'settings'
+ }
+ };
+
+ const action = yield {
+ id: 'action',
+ type: guildSettingsObj[setting as unknown as GuildSettings].type.includes('-array')
+ ? ['view', 'add', 'remove']
+ : ['view', 'set'],
+ prompt: {
+ start: `Would you like to ${util.oxford(
+ (guildSettingsObj[setting as unknown as GuildSettings].type.includes('-array')
+ ? ['view', 'add', 'remove']
+ : ['view', 'set']
+ ).map((a) => `\`${a}\``),
+ 'or'
+ )} the \`${setting}\` setting?`,
+ retry: `{error} Choose one of the following actions to perform on the \`${setting}\` setting: ${util.oxford(
+ (guildSettingsObj[setting as unknown as GuildSettings].type.includes('-array')
+ ? ['view', 'add', 'remove']
+ : ['view', 'set']
+ ).map((a) => `\`${a}\``),
+ 'or'
+ )}`,
+ optional: message.util.parsed!.alias === 'settings'
+ }
+ };
- public override async exec(message: BushMessage | BushSlashMessage, args: unknown): Promise<unknown> {
- client.console.debugRaw(message.interaction);
- client.console.debugRaw(args);
+ const value =
+ action === 'view'
+ ? undefined
+ : yield {
+ id: 'value',
+ type: 'string',
+ match: 'restContent',
+ prompt: {
+ start: `What would you like to ${action} ${
+ (action as unknown as 'add' | 'remove' | 'set') === 'add'
+ ? `to the ${setting} setting`
+ : (action as unknown as 'remove' | 'set') === 'remove'
+ ? `from the ${setting} setting`
+ : `the ${setting} setting to`
+ }?`,
+ retry: `{error} You must choose a value to ${action} ${
+ (action as unknown as 'add' | 'remove' | 'set') === 'add'
+ ? `to the ${setting} setting`
+ : (action as unknown as 'remove' | 'set') === 'remove'
+ ? `from the ${setting} setting`
+ : `the ${setting} setting to`
+ }.`,
+ optional: message.util.parsed!.alias === 'settings'
+ }
+ };
+
+ return { setting, action, value };
+ }
+
+ public override async exec(
+ message: BushMessage | BushSlashMessage,
+ args: { [x in GuildSettings | ('view' | 'set' | 'add' | 'remove') | ('setting' | 'action') | 'value']: string | undefined }
+ ): Promise<unknown> {
if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be used in servers.`);
- const messageOptions = await this.generateMessageOptions(message);
- const msg = (await message.util.reply(messageOptions)) as Message;
+ if (!message.member?.permissions.has('MANAGE_GUILD'))
+ return await message.util.reply(
+ `${util.emojis.error} You must have the **MANAGE_GUILD** permissions to run this command.`
+ );
+ const setting = _.camelCase(args[settingsArr.find((s) => args[s]) ?? 'setting']) as GuildSettings | undefined;
+ const action = (args[
+ (['view', 'set', 'add', 'remove'] as ('view' | 'set' | 'add' | 'remove')[]).find((a) => args[a]) ?? 'action'
+ ] ?? 'view') as 'view' | 'set' | 'add' | 'remove';
+ const value = args.value;
+
+ let msg;
+
+ if (!setting || action === 'view') {
+ const messageOptions = await this.generateMessageOptions(message, setting ?? undefined);
+ msg = (await message.util.reply(messageOptions)) as Message;
+ } else {
+ if (!value)
+ return await message.util.reply(
+ `${util.emojis.error} You must choose a value to ${action} ${
+ (action as unknown as 'add' | 'remove' | 'set') === 'add'
+ ? `to the ${setting} setting`
+ : (action as unknown as 'remove' | 'set') === 'remove'
+ ? `from the ${setting} setting`
+ : `the ${setting} setting to`
+ }`
+ );
+ switch (action) {
+ case 'add':
+ case 'remove': {
+ const existing = (await message.guild.getSetting(setting)) as string[];
+ const updated = util.addOrRemoveFromArray('add', existing, value);
+ await message.guild.setSetting(setting, updated);
+ const messageOptions = await this.generateMessageOptions(message);
+ msg = (await message.util.reply(messageOptions)) as Message;
+ break;
+ }
+ case 'set': {
+ await message.guild.setSetting(setting, value);
+ const messageOptions = await this.generateMessageOptions(message);
+ msg = (await message.util.reply(messageOptions)) as Message;
+ break;
+ }
+ }
+ }
const collector = msg.createMessageComponentCollector({
channel: message.channel ?? undefined,
guild: message.guild,
@@ -121,6 +233,11 @@ export default class SettingsCommand extends BushCommand {
await this.generateMessageOptions(message, interaction.values[0] as keyof typeof guildSettingsObj)
);
}
+ case 'command_settingsBack': {
+ if (!interaction.isButton()) return;
+
+ return interaction.update(await this.generateMessageOptions(message));
+ }
}
} else {
return await interaction?.deferUpdate().catch(() => undefined);
@@ -130,7 +247,7 @@ export default class SettingsCommand extends BushCommand {
public async generateMessageOptions(
message: BushMessage | BushSlashMessage,
- feature?: keyof typeof guildSettingsObj
+ feature?: undefined | keyof typeof guildSettingsObj
): Promise<MessageOptions> {
if (!message.guild) throw new Error('message.guild is null');
const settingsEmbed = new MessageEmbed().setTitle(`${message.guild!.name}'s Settings`).setColor(util.colors.default);
@@ -158,6 +275,7 @@ export default class SettingsCommand extends BushCommand {
type: 'string' | 'channel' | 'channel-array' | 'role' | 'role-array'
): Promise<string> => {
const feat = await message.guild!.getSetting(feature);
+ console.debug(feat);
switch (type.replace('-array', '') as 'string' | 'channel' | 'role') {
case 'string': {
return Array.isArray(feat)
@@ -172,6 +290,7 @@ export default class SettingsCommand extends BushCommand {
}
}
};
+
const components = new MessageActionRow().addComponents(
new MessageButton().setStyle('PRIMARY').setCustomId('command_settingsBack').setLabel('Back')
);
@@ -184,7 +303,8 @@ export default class SettingsCommand extends BushCommand {
);
settingsEmbed.addField(
guildSettingsObj[feature].name,
- await generateCurrentValue(feature as 'string' | 'channel' | 'channel-array' | 'role' | 'role-array')
+ (await generateCurrentValue(feature as 'string' | 'channel' | 'channel-array' | 'role' | 'role-array')) ||
+ '[No Value Set]'
);
return { embeds: [settingsEmbed], components: [components] };
}
diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts
index 957e2b7..1b2fd7c 100644
--- a/src/commands/dev/superUser.ts
+++ b/src/commands/dev/superUser.ts
@@ -1,4 +1,5 @@
import { BushCommand, BushMessage, BushSlashMessage, Global } from '@lib';
+import { ArgumentOptions, Flag } from 'discord-akairo';
import { User } from 'discord.js';
export default class SuperUserCommand extends BushCommand {
@@ -15,14 +16,14 @@ export default class SuperUserCommand extends BushCommand {
ownerOnly: true
});
}
- *args(): unknown {
+ *args(): IterableIterator<ArgumentOptions | Flag> {
const action = yield {
id: 'action',
type: ['add', 'remove'],
prompt: {
start: 'Would you like to `add` or `remove` a user from the superuser list?',
retry: '{error} Choose if you would like to `add` or `remove` a user.',
- required: true
+ optional: false
}
};
const user = yield {
@@ -30,9 +31,9 @@ export default class SuperUserCommand extends BushCommand {
type: 'user',
match: 'restContent',
prompt: {
- start: `Who would you like to ${action || 'add/remove'} from the superuser list?`,
- retry: `Choose a valid user to ${action || 'add/remove'} from the superuser list.`,
- required: true
+ start: `Who would you like to ${action ?? 'add/remove'} from the superuser list?`,
+ retry: `Choose a valid user to ${action ?? 'add/remove'} from the superuser list.`,
+ optional: false
}
};
return { action, user };
diff --git a/src/commands/info/avatar.ts b/src/commands/info/avatar.ts
index 33393b8..7654d2f 100644
--- a/src/commands/info/avatar.ts
+++ b/src/commands/info/avatar.ts
@@ -1,4 +1,4 @@
-import { CommandInteraction, MessageEmbed, User } from 'discord.js';
+import { MessageEmbed, User } from 'discord.js';
import { BushCommand, BushMessage, BushSlashMessage } from '../../lib';
export default class AvatarCommand extends BushCommand {
@@ -36,9 +36,6 @@ export default class AvatarCommand extends BushCommand {
}
override async exec(message: BushMessage | BushSlashMessage, args: { user: User }): Promise<void> {
- client.console.debugRaw(args);
- client.console.debugRaw(message.interaction);
- client.console.debugRaw((message.interaction as CommandInteraction).options.getUser('user'));
const user = args.user ?? message.author;
const embed = new MessageEmbed()
diff --git a/src/commands/leveling/leaderboard.ts b/src/commands/leveling/leaderboard.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/commands/leveling/leaderboard.ts
diff --git a/src/commands/moulberry-bush/level.ts b/src/commands/leveling/level.ts
index 02d66be..a579df0 100644
--- a/src/commands/moulberry-bush/level.ts
+++ b/src/commands/leveling/level.ts
@@ -1,17 +1,15 @@
-import { BushCommand, BushGuild, BushMessage, BushSlashMessage, BushUser, Level } from '@lib';
-/*
+import { BushCommand, BushGuild, BushMessage, BushSlashMessage, BushUser, CanvasProgressBar, Level } from '@lib';
import canvas from 'canvas';
import { MessageAttachment } from 'discord.js';
-import { join } from 'path';
import got from 'got/dist/source';
-import { CanvasProgressBar } from '@lib';
-*/
+import { join } from 'path';
+import SimplifyNumber from 'simplify-number';
export default class LevelCommand extends BushCommand {
public constructor() {
super('level', {
- aliases: ['level', 'rank'],
- category: "Moulberry's Bush",
+ aliases: ['level', 'rank', 'lvl'],
+ category: 'leveling',
description: {
content: 'Shows the level of a user',
usage: 'level [user]',
@@ -41,71 +39,62 @@ export default class LevelCommand extends BushCommand {
});
}
- /* private simplifyXP(xp: number): string {
-
- }
-
- private async getImage(user: User): Promise<Buffer> {
+ private async getImage(user: BushUser, guild: BushGuild): Promise<Buffer> {
// I added comments because this code is impossible to read
const [userLevelRow] = await Level.findOrBuild({
where: {
- id: user.id
+ user: user.id,
+ guild: guild.id
},
defaults: {
- id: user.id
+ user: user.id,
+ guild: guild.id
}
});
- const userLevel = userLevelRow.level
+ const rank = (await Level.findAll({ where: { guild: guild.id } })).sort((a, b) => b.xp - a.xp);
+ const userLevel = userLevelRow.level;
const currentLevelXP = Level.convertLevelToXp(userLevel);
const currentLevelXPProgress = userLevelRow.xp - currentLevelXP;
- const xpForNextLevel =
- Level.convertLevelToXp(userLevelRow.level + 1) - currentLevelXP;
+ const xpForNextLevel = Level.convertLevelToXp(userLevelRow.level + 1) - currentLevelXP;
+ const white = '#FFFFFF',
+ gray = '#23272A',
+ newBlurple = '#5865F2';
// Load roboto font because yes
- canvas.registerFont(
- join(__dirname, '..', '..', '..', 'Roboto-Regular.ttf'),
- {
- family: 'Roboto'
- }
- );
+ canvas.registerFont(join(__dirname, '..', '..', '..', '..', 'lib', 'assets', 'Roboto-Regular.ttf'), {
+ family: 'Roboto'
+ });
// Create image canvas
const image = canvas.createCanvas(800, 200),
ctx = image.getContext('2d');
// Fill background
- ctx.fillStyle = '#00c7eb';
+ ctx.fillStyle = gray;
ctx.fillRect(0, 0, image.width, image.height);
// Draw avatar
- const avatarBuffer = await got
- .get(user.displayAvatarURL({ format: 'png', size: 128 }))
- .buffer();
+ const avatarBuffer = await got.get(user.displayAvatarURL({ format: 'png', size: 128 })).buffer();
const avatarImage = new canvas.Image();
avatarImage.src = avatarBuffer;
- avatarImage.height = 128
- avatarImage.width = 128
- const imageTopCoord = (image.height / 2) - (avatarImage.height / 2)
+ avatarImage.height = 128;
+ avatarImage.width = 128;
+ const imageTopCoord = image.height / 2 - avatarImage.height / 2;
ctx.drawImage(avatarImage, imageTopCoord, imageTopCoord);
// Write tag of user
ctx.font = '30px Roboto';
- ctx.fillStyle = 'black';
+ ctx.fillStyle = white;
const measuredTag = ctx.measureText(user.tag);
ctx.fillText(user.tag, avatarImage.width + 70, 60);
// Draw line under tag
- ctx.fillStyle = 'yellow';
- ctx.fillRect(
- avatarImage.width + 70,
- 65 + measuredTag.actualBoundingBoxDescent,
- measuredTag.width,
- 3
- );
+ ctx.fillStyle = newBlurple;
+ ctx.fillRect(avatarImage.width + 70, 65 + measuredTag.actualBoundingBoxDescent, measuredTag.width, 3);
// Draw leveling bar
const fullProgressBar = new CanvasProgressBar(
ctx,
{
x: avatarImage.width + 70,
- y: avatarImage.height - 10,
+ y: avatarImage.height - 0,
height: 30,
width: 550
},
- '#6e6e6e',
+ white,
1
);
fullProgressBar.draw();
@@ -113,38 +102,30 @@ export default class LevelCommand extends BushCommand {
ctx,
{
x: avatarImage.width + 70,
- y: avatarImage.height - 10,
+ y: avatarImage.height - 0,
height: 30,
width: 550
},
- 'yellow',
+ newBlurple,
currentLevelXPProgress / xpForNextLevel
);
progressBar.draw();
// Draw level data text
- ctx.fillStyle = 'black'
- ctx.fillText(`Level: ${userLevel} XP: $`, avatarImage.width + 70, avatarImage.height - 20)
+ ctx.fillStyle = white;
+ ctx.fillText(
+ `Level: ${userLevel} XP: ${SimplifyNumber(currentLevelXPProgress)}/${SimplifyNumber(
+ xpForNextLevel
+ )} Rank: ${SimplifyNumber(rank.indexOf(rank.find((x) => x.user === user.id)!) + 1)}`,
+ avatarImage.width + 70,
+ avatarImage.height - 20
+ );
// Return image in buffer form
return image.toBuffer();
- } */
-
- private async getResponse(user: BushUser, guild: BushGuild): Promise<string> {
- const userLevelRow = await Level.findOne({ where: { user: user.id, guild: guild.id } });
- if (userLevelRow) {
- return `${user ? `${user.tag}'s` : 'Your'} level is ${userLevelRow.level} (${userLevelRow.xp} XP)`;
- } else {
- return `${user ? `${user.tag} does` : 'You do'} not have a level yet!`;
- }
}
- public override async exec(message: BushMessage | BushSlashMessage, { user }: { user?: BushUser }): Promise<void> {
- // await message.reply(
- // new MessageAttachment(
- // await this.getImage(user || message.author),
- // 'lel.png'
- // )
- // );
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- await message.reply(await this.getResponse(user || message.author, message.guild!));
+ public override async exec(message: BushMessage | BushSlashMessage, args: { user?: BushUser }): Promise<unknown> {
+ return await message.reply({
+ files: [new MessageAttachment(await this.getImage(args.user ?? message.author, message.guild!), 'level.png')]
+ });
}
}
diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts
index 7f1a67c..c33b39a 100644
--- a/src/commands/moderation/ban.ts
+++ b/src/commands/moderation/ban.ts
@@ -1,5 +1,4 @@
import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushSlashMessage } from '@lib';
-import { Argument } from 'discord-akairo';
import { User } from 'discord.js';
export default class BanCommand extends BushCommand {
@@ -110,7 +109,7 @@ export default class BanCommand extends BushCommand {
if (reason) {
time =
typeof reason === 'string'
- ? await Argument.cast('duration', client.commandHandler.resolver, message as BushMessage, reason)
+ ? await util.arg.cast('duration', client.commandHandler.resolver, message as BushMessage, reason)
: reason.duration;
}
const parsedReason = reason?.contentWithoutTime ?? '';
diff --git a/src/commands/moderation/modlog.ts b/src/commands/moderation/modlog.ts
index 04264b8..ef0a56e 100644
--- a/src/commands/moderation/modlog.ts
+++ b/src/commands/moderation/modlog.ts
@@ -42,8 +42,7 @@ export default class ModlogCommand extends BushCommand {
`**Moderator**: <@!${log.moderator}> (${log.moderator})`
];
if (log.duration) modLog.push(`**Duration**: ${util.humanizeDuration(log.duration)}`);
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- modLog.push(`**Reason**: ${log.reason || 'No Reason Specified.'}`);
+ modLog.push(`**Reason**: ${log.reason ?? 'No Reason Specified.'}`);
if (log.evidence) modLog.push(`**Evidence:** ${log.evidence}`);
return modLog.join(`\n`);
}
diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts
index 7b8689a..915302e 100644
--- a/src/commands/moderation/mute.ts
+++ b/src/commands/moderation/mute.ts
@@ -1,5 +1,4 @@
import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage, BushUser } from '@lib';
-import { Argument } from 'discord-akairo';
export default class MuteCommand extends BushCommand {
public constructor() {
@@ -77,7 +76,7 @@ export default class MuteCommand extends BushCommand {
if (reason) {
time =
typeof reason === 'string'
- ? await Argument.cast('duration', client.commandHandler.resolver, message as BushMessage, reason)
+ ? await util.arg.cast('duration', client.commandHandler.resolver, message as BushMessage, reason)
: reason.duration;
}
const parsedReason = reason?.contentWithoutTime ?? '';
diff --git a/src/commands/moderation/role.ts b/src/commands/moderation/role.ts
index 4575a11..ddaefaa 100644
--- a/src/commands/moderation/role.ts
+++ b/src/commands/moderation/role.ts
@@ -1,51 +1,16 @@
-/* eslint-disable @typescript-eslint/no-empty-function */
import { AllowedMentions, BushCommand, BushGuildMember, BushMessage, BushRole, BushSlashMessage } from '@lib';
+import { ArgumentOptions, Flag } from 'discord-akairo';
export default class RoleCommand extends BushCommand {
public constructor() {
super('role', {
- aliases: ['role'],
+ aliases: ['role', 'rr', 'ar', 'ra'],
category: 'moderation',
description: {
content: "Manages users' roles.",
usage: 'role <add|remove> <user> <role> [duration]',
examples: ['role add spammer nogiveaways 7days']
},
- args: [
- {
- id: 'action',
- customType: [['add'], ['remove']],
- prompt: {
- start: 'Would you like to `add` or `remove` a role?',
- retry: '{error} Choose whether you would you like to `add` or `remove` a role.'
- }
- },
- {
- id: 'user',
- type: 'member',
- prompt: {
- start: `What user do you want to add/remove the role to/from?`,
- retry: `{error} Choose a valid user to add/remove the role to/from.`
- }
- },
- {
- id: 'role',
- type: 'role',
- prompt: {
- start: `What role do you want to add/remove to/from the user?`,
- retry: `{error} Choose a valid role to add/remove.`
- }
- },
- {
- id: 'duration',
- type: 'duration',
- prompt: {
- start: 'How long would you like to role to last?',
- retry: '{error} Choose a valid duration.',
- optional: true
- }
- }
- ],
slash: true,
slashOptions: [
{
@@ -90,9 +55,56 @@ export default class RoleCommand extends BushCommand {
});
}
+ *args(message: BushMessage): IterableIterator<ArgumentOptions | Flag> {
+ const action = ['rr'].includes(message.util.parsed?.alias ?? '')
+ ? 'remove'
+ : ['ar', 'ra'].includes(message.util.parsed?.alias ?? '')
+ ? 'add'
+ : yield {
+ id: 'action',
+ type: [['add'], ['remove']],
+ prompt: {
+ start: 'Would you like to `add` or `remove` a role?',
+ retry: (...arg) => {
+ console.debug(...arg);
+ return '{error} Choose whether you would you like to `add` or `remove` a role.';
+ }
+ }
+ };
+ console.debug(action);
+ const user = yield {
+ id: 'user',
+ type: 'member',
+ prompt: {
+ start: `What user do you want to ${action} the role ${action === 'add' ? 'to' : 'from'}?`,
+ retry: (...arg) => {
+ console.debug(...arg);
+ return `{error} Choose a valid user to ${action} the role ${action === 'add' ? 'to' : 'from'}.`;
+ }
+ }
+ };
+ console.debug(user);
+ const _role = yield {
+ id: 'role',
+ type: `${action === 'add' ? 'roleWithDuration' : 'role'}`,
+ match: 'rest',
+ prompt: {
+ start: `What role do you want to ${action} ${action === 'add' ? 'to' : 'from'} the user${
+ action === 'add' ? ', and for how long' : ''
+ }?`,
+ retry: (...arg) => {
+ console.debug(...arg);
+ return `{error} Choose a valid role to ${action}.`;
+ }
+ }
+ };
+ console.debug(_role);
+ return { action, user, role: (_role as any).role ?? _role, duration: (_role as any).duration };
+ }
+
public override async exec(
message: BushMessage | BushSlashMessage,
- { action, user, role, duration }: { action: 'add' | 'remove'; user: BushGuildMember; role: BushRole; duration: number }
+ { action, user, role, duration }: { action: 'add' | 'remove'; user: BushGuildMember; role: BushRole; duration?: number }
): Promise<unknown> {
if (!message.member!.permissions.has('MANAGE_ROLES')) {
const mappings = client.consts.mappings;
@@ -131,6 +143,8 @@ export default class RoleCommand extends BushCommand {
const responseMessage = () => {
switch (responseCode) {
case 'user hierarchy':
+ client.console.debug(role.position);
+ client.console.debug(user.roles.highest.position);
return `${util.emojis.error} <@&${role.id}> is higher or equal to your highest role.`;
case 'role managed':
return `${util.emojis.error} <@&${role.id}> is managed by an integration and cannot be managed.`;
diff --git a/src/commands/moderation/slowmode.ts b/src/commands/moderation/slowmode.ts
index 1d47616..04fe3e4 100644
--- a/src/commands/moderation/slowmode.ts
+++ b/src/commands/moderation/slowmode.ts
@@ -66,12 +66,11 @@ export default class SlowModeCommand extends BushCommand {
if (length) {
length =
typeof length === 'string' && !['off', 'none', 'disable'].includes(length)
- ? await Argument.cast('duration', client.commandHandler.resolver, message as BushMessage, length)
+ ? await util.arg.cast('duration', client.commandHandler.resolver, message as BushMessage, length)
: length;
}
- // @ts-expect-error: stop being dumb smh
- const length2: number = ['off', 'none', 'disable'].includes(length) ? 0 : length;
+ const length2: number = ['off', 'none', 'disable'].includes(length as string) ? 0 : (length as number);
const setSlowmode = await (channel as ThreadChannel | TextChannel)
.setRateLimitPerUser(length2 / 1000, `Changed by ${message.author.tag} (${message.author.id}).`)
diff --git a/src/commands/moulberry-bush/rule.ts b/src/commands/moulberry-bush/rule.ts
index bf44dad..0c1e435 100644
--- a/src/commands/moulberry-bush/rule.ts
+++ b/src/commands/moulberry-bush/rule.ts
@@ -131,7 +131,7 @@ export default class RuleCommand extends BushCommand {
// 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.reply({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() });
})
: await message.util.send({ embeds: [rulesEmbed], allowedMentions: AllowedMentions.users() })
);
diff --git a/src/commands/utilities/decode.ts b/src/commands/utilities/decode.ts
index a5a4c21..e48c644 100644
--- a/src/commands/utilities/decode.ts
+++ b/src/commands/utilities/decode.ts
@@ -95,8 +95,7 @@ export default class DecodeCommand extends BushCommand {
message: BushMessage | AkairoMessage,
{ from, to, data }: { from: BufferEncoding; to: BufferEncoding; data: string }
): Promise<unknown> {
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- const encodeOrDecode = util.capitalizeFirstLetter(message?.util?.parsed?.alias || 'decoded');
+ const encodeOrDecode = util.capitalizeFirstLetter(message?.util?.parsed?.alias ?? 'decoded');
const decodedEmbed = new MessageEmbed()
.setTitle(`${encodeOrDecode} Information`)
.addField('📥 Input', await util.inspectCleanRedactCodeblock(data));
diff --git a/src/lib/extensions/discord-akairo/BushClient.ts b/src/lib/extensions/discord-akairo/BushClient.ts
index ca5f325..2eaf3d3 100644
--- a/src/lib/extensions/discord-akairo/BushClient.ts
+++ b/src/lib/extensions/discord-akairo/BushClient.ts
@@ -26,6 +26,7 @@ import { contentWithDurationTypeCaster } from '../../../arguments/contentWithDur
import { discordEmojiTypeCaster } from '../../../arguments/discordEmoji';
import { durationTypeCaster } from '../../../arguments/duration';
import { permissionTypeCaster } from '../../../arguments/permission';
+import { roleWithDurationTypeCaster } from '../../../arguments/roleWithDuation';
import { snowflakeTypeCaster } from '../../../arguments/snowflake';
import { UpdateCacheTask } from '../../../tasks/updateCache';
import { ActivePunishment } from '../../models/ActivePunishment';
@@ -264,7 +265,8 @@ export class BushClient<Ready extends boolean = boolean> extends AkairoClient<Re
contentWithDuration: contentWithDurationTypeCaster,
permission: permissionTypeCaster,
snowflake: snowflakeTypeCaster,
- discordEmoji: discordEmojiTypeCaster
+ discordEmoji: discordEmojiTypeCaster,
+ roleWithDuration: roleWithDurationTypeCaster
});
// loads all the handlers
const loaders = {
diff --git a/src/lib/extensions/discord-akairo/BushClientUtil.ts b/src/lib/extensions/discord-akairo/BushClientUtil.ts
index 9ed890a..ef51b63 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -1354,10 +1354,6 @@ export class BushClientUtil extends ClientUtil {
return new Promise((resolve) => setTimeout(resolve, s * 1000));
}
- camelToSnakeCase(str: string) {
- return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
- }
-
//~ modified from https://stackoverflow.com/questions/31054910/get-functions-methods-of-a-class
//~ answer by Bruno Grieder
//~ public getMethods(obj: any): string {
diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts
index 7ecb679..3a2c619 100644
--- a/src/lib/extensions/discord-akairo/BushCommand.ts
+++ b/src/lib/extensions/discord-akairo/BushCommand.ts
@@ -63,7 +63,8 @@ export type BaseBushArgumentType =
| 'contentWithDuration'
| 'permission'
| 'snowflake'
- | 'discordEmoji';
+ | 'discordEmoji'
+ | 'roleWithDuration';
export type BushArgumentType = BaseBushArgumentType | RegExp;
@@ -180,8 +181,7 @@ export class BushCommand extends Command {
}
super(id, options);
this.options = options;
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- this.hidden = options.hidden || false;
+ this.hidden = options.hidden ?? false;
this.restrictedChannels = options.restrictedChannels!;
this.restrictedGuilds = options.restrictedGuilds!;
this.completelyHide = options.completelyHide!;
diff --git a/src/lib/extensions/discord.js/BushGuild.ts b/src/lib/extensions/discord.js/BushGuild.ts
index 4fc27a7..2c3b4bd 100644
--- a/src/lib/extensions/discord.js/BushGuild.ts
+++ b/src/lib/extensions/discord.js/BushGuild.ts
@@ -73,8 +73,7 @@ export class BushGuild extends Guild {
if (!bans.has(user)) notBanned = true;
const unbanSuccess = await this.bans
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- .remove(user, `${moderator.tag} | ${options.reason || 'No reason provided.'}`)
+ .remove(user, `${moderator.tag} | ${options.reason ?? 'No reason provided.'}`)
.catch((e) => {
if (e?.code === 'UNKNOWN_BAN') {
notBanned = true;
@@ -108,8 +107,7 @@ export class BushGuild extends Guild {
const userObject = client.users.cache.get(user);
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- userObject?.send(`You have been unbanned from **${this}** for **${options.reason || 'No reason provided'}**.`);
+ userObject?.send(`You have been unbanned from **${this}** for **${options.reason ?? 'No reason provided'}**.`);
if (notBanned) return 'user not banned';
return 'success';
diff --git a/src/lib/extensions/discord.js/BushGuildMember.ts b/src/lib/extensions/discord.js/BushGuildMember.ts
index e596c82..6ce473a 100644
--- a/src/lib/extensions/discord.js/BushGuildMember.ts
+++ b/src/lib/extensions/discord.js/BushGuildMember.ts
@@ -182,7 +182,7 @@ export class BushGuildMember extends GuildMember {
}
#checkIfShouldAddRole(role: BushRole | Role): true | 'user hierarchy' | 'role managed' | 'client hierarchy' {
- if (this.roles.highest.position <= role.position) {
+ if (this.roles.highest.position <= role.position && this.guild.ownerId !== this.id) {
return 'user hierarchy';
} else if (role.managed) {
return 'role managed';
diff --git a/yarn.lock b/yarn.lock
index bb91c1d..ce84919 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -797,6 +797,7 @@ __metadata:
prettier: ^2.3.2
rimraf: ^3.0.2
sequelize: ^6.5.0
+ simplify-number: ^1.0.0
source-map-support: ^0.5.19
tinycolor2: ^1.4.2
typescript: ^4.4.2
@@ -1116,12 +1117,12 @@ discord-akairo-message-util@NotEnoughUpdates/discord-akairo-message-util:
discord-akairo@NotEnoughUpdates/discord-akairo:
version: 8.2.2
- resolution: "discord-akairo@https://github.com/NotEnoughUpdates/discord-akairo.git#commit=0091ae5534d7eefbb401009a15aa7d6fd013f9b4"
+ resolution: "discord-akairo@https://github.com/NotEnoughUpdates/discord-akairo.git#commit=ef17d029e98f6aa10b153a97ad8aa41adbe1ef44"
dependencies:
discord-akairo-message-util: NotEnoughUpdates/discord-akairo-message-util
lodash: ^4.17.21
source-map-support: ^0.5.19
- checksum: 1fcd67033576768a5b8bed52e15473795242860e5661d5f2a781acb0579e334d09fdf5f824e6eedcd7050fa5df88beadda309d19be503c7c3c5554917ccdbd4a
+ checksum: 38fb8103a93bfb913da881d05074f0219c0297b8e19ed88a79ed134ab81a1fff664c46d516c856c8e1b217adc5273355b668717b25dac8c0dbeaf4c83486ac4c
languageName: node
linkType: hard
@@ -2903,6 +2904,13 @@ discord.js@NotEnoughUpdates/discord.js:
languageName: node
linkType: hard
+"simplify-number@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "simplify-number@npm:1.0.0"
+ checksum: a8e1d85dcd390f5c8fc5b8b5f5d49c62590656540904e9809aef8dfc3e3846a9ebd14405e4a54849a1da3bdf7feaa2456886d32f0c27e1a942988dfb762da857
+ languageName: node
+ linkType: hard
+
"slash@npm:^3.0.0":
version: 3.0.0
resolution: "slash@npm:3.0.0"