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/config.ts351
-rw-r--r--src/commands/config/joinRoles.ts2
-rw-r--r--src/commands/config/settings.ts192
-rw-r--r--src/commands/dev/superUser.ts11
-rw-r--r--src/commands/info/avatar.ts5
-rw-r--r--src/commands/info/help.ts6
-rw-r--r--src/commands/leveling/leaderboard.ts52
-rw-r--r--src/commands/leveling/level.ts143
-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/level.ts150
-rw-r--r--src/commands/moulberry-bush/rule.ts2
-rw-r--r--src/commands/utilities/decode.ts3
-rw-r--r--src/commands/utilities/suicide.ts52
-rw-r--r--src/lib/extensions/discord-akairo/BushClient.ts4
-rw-r--r--src/lib/extensions/discord-akairo/BushClientUtil.ts11
-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--src/lib/models/Guild.ts18
-rw-r--r--yarn.lock24
27 files changed, 723 insertions, 439 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/config.ts b/src/commands/config/config.ts
new file mode 100644
index 0000000..8362144
--- /dev/null
+++ b/src/commands/config/config.ts
@@ -0,0 +1,351 @@
+import { BushCommand, BushMessage, BushSlashMessage, GuildSettings, guildSettingsObj, settingsArr } from '@lib';
+import { ArgumentOptions, Flag } from 'discord-akairo';
+import {
+ Channel,
+ Formatters,
+ Message,
+ MessageActionRow,
+ MessageButton,
+ MessageComponentInteraction,
+ MessageEmbed,
+ MessageOptions,
+ MessageSelectMenu,
+ Role
+} from 'discord.js';
+import _ from 'lodash';
+
+export default class SettingsCommand extends BushCommand {
+ public constructor() {
+ super('config', {
+ aliases: ['config', 'settings', 'setting', 'configure'],
+ category: 'config',
+ description: {
+ content: 'Configure server settings.',
+ usage: `settings (${settingsArr.map((s) => `\`${s}\``).join(', ')}) (${['view', 'set', 'add', 'remove'].map(
+ (s) => `\`${s}\``
+ )})`,
+ examples: ['settings', 'config prefix set -']
+ },
+ slash: true,
+ slashOptions: settingsArr.map((setting) => {
+ return {
+ name: _.snakeCase(setting),
+ description: `Manage the server's ${guildSettingsObj[setting].name.toLowerCase()}`,
+ type: 'SUB_COMMAND_GROUP',
+ options: guildSettingsObj[setting].type.includes('-array')
+ ? [
+ {
+ name: 'view',
+ description: `View the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
+ type: 'SUB_COMMAND'
+ },
+ {
+ name: 'add',
+ description: `Add a value to the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
+ type: 'SUB_COMMAND',
+ options: [
+ {
+ name: 'value',
+ description: `What would you like to add to the server's ${guildSettingsObj[
+ setting
+ ].name.toLowerCase()}?'`,
+ type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL',
+ required: true
+ }
+ ]
+ },
+ {
+ name: 'remove',
+ description: `Remove a value from the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
+ type: 'SUB_COMMAND',
+ options: [
+ {
+ name: 'value',
+ description: `What would you like to remove from the server's ${guildSettingsObj[
+ setting
+ ].name.toLowerCase()}?'`,
+ type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL',
+ required: true
+ }
+ ]
+ }
+ ]
+ : [
+ {
+ name: 'view',
+ description: `View the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
+ type: 'SUB_COMMAND'
+ },
+ {
+ name: 'set',
+ description: `Set the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
+ type: 'SUB_COMMAND',
+ options: [
+ {
+ name: 'value',
+ description: `What would you like to set the server's ${guildSettingsObj[
+ setting
+ ].name.toLowerCase()} to?'`,
+ type: guildSettingsObj[setting].type.toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL',
+ required: true
+ }
+ ]
+ }
+ ]
+ };
+ }),
+ slashGuilds: ['516977525906341928', '812400566235430912'],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'],
+ ownerOnly: true
+ });
+ }
+
+ // I make very readable code :)
+ *args(message: BushMessage): IterableIterator<ArgumentOptions | Flag> {
+ const optional = message.util.parsed!.alias === 'settings';
+ 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
+ }
+ };
+
+ const actionType = setting
+ ? guildSettingsObj[setting as unknown as GuildSettings]?.type.includes('-array')
+ ? ['view', 'add', 'remove']
+ : ['view', 'set']
+ : undefined;
+
+ const action = setting
+ ? yield {
+ id: 'action',
+ type: actionType,
+ prompt: {
+ start: `Would you like to ${util.oxford(
+ actionType!.map((a) => `\`${a}\``),
+ 'or'
+ )} the \`${setting}\` setting?`,
+ retry: `{error} Choose one of the following actions to perform on the ${setting} setting: ${util.oxford(
+ actionType!.map((a) => `\`${a}\``),
+ 'or'
+ )}`,
+ optional
+ }
+ }
+ : undefined;
+
+ const valueType =
+ setting && action && action !== 'view'
+ ? (guildSettingsObj[setting as unknown as GuildSettings].type.replace('-array', '') as 'string' | 'channel' | 'role')
+ : undefined;
+ const grammar =
+ setting && action && action !== 'view'
+ ? (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`
+ : undefined;
+
+ const value =
+ setting && action && action !== 'view'
+ ? yield {
+ id: 'value',
+ type: valueType,
+ match: 'restContent',
+ prompt: {
+ start: `What would you like to ${action} ${grammar}?`,
+ retry: `{error} You must choose a ${valueType === 'string' ? 'value' : valueType} to ${action} ${grammar}.`,
+ optional
+ }
+ }
+ : undefined;
+
+ return { setting, action, value };
+ }
+
+ public override async exec(
+ message: BushMessage | BushSlashMessage,
+ args: {
+ setting?: GuildSettings;
+ subcommandGroup?: GuildSettings;
+ action?: 'view' | 'add' | 'remove' | 'set';
+ subcommand?: 'view' | 'add' | 'remove' | 'set';
+ value: string | Channel | Role;
+ }
+ ): Promise<unknown> {
+ if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be used in servers.`);
+ 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 = message.util.isSlash ? (_.camelCase(args.subcommandGroup)! as GuildSettings) : args.setting!;
+ const action = message.util.isSlash ? args.subcommand! : args.action!;
+ 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 {
+ const parseVal = (val: string | Channel | Role) => {
+ if (val instanceof Channel || val instanceof Role) {
+ return val.id;
+ }
+ return val;
+ };
+
+ 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, parseVal(value));
+ await message.guild.setSetting(setting, updated);
+ const messageOptions = await this.generateMessageOptions(message, setting);
+ msg = (await message.util.reply(messageOptions)) as Message;
+ break;
+ }
+ case 'set': {
+ await message.guild.setSetting(setting, parseVal(value));
+ const messageOptions = await this.generateMessageOptions(message, setting);
+ msg = (await message.util.reply(messageOptions)) as Message;
+ break;
+ }
+ }
+ }
+ const collector = msg.createMessageComponentCollector({
+ channel: message.channel ?? undefined,
+ guild: message.guild,
+ message: message as Message,
+ time: 300_000
+ });
+
+ collector.on('collect', async (interaction: MessageComponentInteraction) => {
+ if (interaction.user.id === message.author.id || client.config.owners.includes(interaction.user.id)) {
+ if (!message.guild) throw new Error('message.guild is null');
+ switch (interaction.customId) {
+ case 'command_settingsSel': {
+ if (!interaction.isSelectMenu()) return;
+
+ return interaction.update(
+ 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);
+ }
+ });
+ }
+
+ public async generateMessageOptions(
+ message: BushMessage | BushSlashMessage,
+ setting?: undefined | keyof typeof guildSettingsObj
+ ): Promise<MessageOptions> {
+ if (!message.guild) throw new Error('message.guild is null');
+ const settingsEmbed = new MessageEmbed().setColor(util.colors.default);
+ if (!setting) {
+ settingsEmbed.setTitle(`${message.guild!.name}'s Settings`);
+ const desc = settingsArr.map((s) => `:wrench: **${guildSettingsObj[s].name}**`).join('\n');
+ settingsEmbed.setDescription(desc);
+
+ const selMenu = new MessageActionRow().addComponents(
+ new MessageSelectMenu()
+ .addOptions(
+ ...settingsArr.map((s) => ({
+ label: guildSettingsObj[s].name,
+ value: s,
+ description: guildSettingsObj[s].description
+ }))
+ )
+ .setPlaceholder('Select A Setting to View')
+ .setMaxValues(1)
+ .setMinValues(1)
+ .setCustomId('command_settingsSel')
+ );
+ return { embeds: [settingsEmbed], components: [selMenu] };
+ } else {
+ settingsEmbed.setTitle(guildSettingsObj[setting].name);
+ const generateCurrentValue = async (
+ type: 'string' | 'channel' | 'channel-array' | 'role' | 'role-array'
+ ): Promise<string> => {
+ const feat = await message.guild!.getSetting(setting);
+
+ switch (type.replace('-array', '') as 'string' | 'channel' | 'role') {
+ case 'string': {
+ return Array.isArray(feat)
+ ? feat.length
+ ? feat.map((feat) => util.discord.escapeInlineCode(util.inspectAndRedact(feat))).join('\n')
+ : '[Empty Array]'
+ : feat !== null
+ ? util.discord.escapeInlineCode(util.inspectAndRedact(feat))
+ : '[No Value Set]';
+ }
+ case 'channel': {
+ return Array.isArray(feat)
+ ? feat.length
+ ? feat.map((feat) => `<#${feat}>`).join('\n')
+ : '[Empty Array]'
+ : `<#${feat}>`;
+ }
+ case 'role': {
+ return Array.isArray(feat)
+ ? feat.length
+ ? feat.map((feat) => `<@&${feat}>`).join('\n')
+ : '[Empty Array]'
+ : `<@&${feat}>`;
+ }
+ }
+ };
+
+ const components = new MessageActionRow().addComponents(
+ new MessageButton().setStyle('PRIMARY').setCustomId('command_settingsBack').setLabel('Back')
+ );
+ settingsEmbed.setDescription(
+ `${Formatters.italic(guildSettingsObj[setting].description)}\n\n**Type**: ${guildSettingsObj[setting].type}`
+ );
+
+ settingsEmbed.setFooter(
+ `Run "${
+ message.util.isSlash
+ ? '/'
+ : client.config.isDevelopment
+ ? 'dev '
+ : message.util.parsed?.prefix ?? client.config.prefix
+ }${message.util.parsed?.alias ?? 'config'} ${setting} ${
+ guildSettingsObj[setting].type.includes('-array') ? 'add/remove' : 'set'
+ } <value>" to set this setting.`
+ );
+ settingsEmbed.addField(
+ 'value',
+ (await generateCurrentValue(
+ guildSettingsObj[setting].type as 'string' | 'channel' | 'channel-array' | 'role' | 'role-array'
+ )) || '[No Value Set]'
+ );
+ return { embeds: [settingsEmbed], components: [components] };
+ }
+ }
+}
diff --git a/src/commands/config/joinRoles.ts b/src/commands/config/joinRoles.ts
index 9507d4b..0b9ac21 100644
--- a/src/commands/config/joinRoles.ts
+++ b/src/commands/config/joinRoles.ts
@@ -40,10 +40,8 @@ export default class JoinRolesCommand extends BushCommand {
public override async exec(message: BushMessage | BushSlashMessage, { role }: { role: Role }): Promise<unknown> {
const joinRoles = await message.guild!.getSetting('joinRoles');
const includes = joinRoles.includes(role.id);
- client.console.debug(joinRoles);
const newValue = util.addOrRemoveFromArray(includes ? 'remove' : 'add', joinRoles, role.id);
await message.guild!.setSetting('joinRoles', newValue);
- client.console.debug(joinRoles);
return await message.util.reply({
content: `${util.emojis.success} Successfully ${includes ? 'removed' : 'added'} <@&${role.id}> ${
includes ? 'from' : 'to'
diff --git a/src/commands/config/settings.ts b/src/commands/config/settings.ts
deleted file mode 100644
index a8070e2..0000000
--- a/src/commands/config/settings.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import { BushCommand, BushMessage, BushSlashMessage, guildSettingsObj, settingsArr } from '@lib';
-import {
- Message,
- MessageActionRow,
- MessageButton,
- MessageComponentInteraction,
- MessageEmbed,
- MessageOptions,
- MessageSelectMenu
-} from 'discord.js';
-
-export default class SettingsCommand extends BushCommand {
- public constructor() {
- super('settings', {
- aliases: ['settings', 'setting', 'configure', 'config'],
- category: 'config',
- description: {
- content: 'Configure server options.',
- usage: 'settings',
- examples: ['settings']
- },
- slash: true,
- slashOptions: settingsArr.map((setting) => {
- return {
- name: util.camelToSnakeCase(setting),
- description: `Manage the server's ${guildSettingsObj[setting].name.toLowerCase()}`,
- type: 'SUB_COMMAND_GROUP',
- options: guildSettingsObj[setting].type.includes('-array')
- ? [
- {
- name: 'view',
- description: `View the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
- type: 'SUB_COMMAND'
- },
- {
- name: 'add',
- description: `Add a value to the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
- type: 'SUB_COMMAND',
- options: [
- {
- name: 'value',
- description: `What would you like to add to the server's ${guildSettingsObj[
- setting
- ].name.toLowerCase()}?'`,
- type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL',
- required: true
- }
- ]
- },
- {
- name: 'remove',
- description: `Remove a value from the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
- type: 'SUB_COMMAND',
- options: [
- {
- name: 'value',
- description: `What would you like to remove from the server's ${guildSettingsObj[
- setting
- ].name.toLowerCase()}?'`,
- type: guildSettingsObj[setting].type.replace('-array', '').toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL',
- required: true
- }
- ]
- }
- ]
- : [
- {
- name: 'view',
- description: `View the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
- type: 'SUB_COMMAND'
- },
- {
- name: 'set',
- description: `Set the server's ${guildSettingsObj[setting].name.toLowerCase()}.`,
- type: 'SUB_COMMAND',
- options: [
- {
- name: 'value',
- description: `What would you like to set the server's ${guildSettingsObj[
- setting
- ].name.toLowerCase()} to?'`,
- type: guildSettingsObj[setting].type.toUpperCase() as 'ROLE' | 'STRING' | 'CHANNEL',
- required: true
- }
- ]
- }
- ]
- };
- }),
- slashGuilds: ['516977525906341928', '812400566235430912'],
- channel: 'guild',
- clientPermissions: ['SEND_MESSAGES'],
- userPermissions: ['SEND_MESSAGES', 'MANAGE_GUILD'],
- ownerOnly: true
- });
- }
-
- // *args(): any {}
-
- public override async exec(message: BushMessage | BushSlashMessage, args: unknown): Promise<unknown> {
- client.console.debugRaw(message.interaction);
- client.console.debugRaw(args);
- 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;
- const collector = msg.createMessageComponentCollector({
- channel: message.channel ?? undefined,
- guild: message.guild,
- message: message as Message,
- time: 300_000
- });
-
- collector.on('collect', async (interaction: MessageComponentInteraction) => {
- if (interaction.user.id === message.author.id || client.config.owners.includes(interaction.user.id)) {
- if (!message.guild) throw new Error('message.guild is null');
- switch (interaction.customId) {
- case 'command_settingsSel': {
- if (!interaction.isSelectMenu()) return;
-
- return interaction.update(
- await this.generateMessageOptions(message, interaction.values[0] as keyof typeof guildSettingsObj)
- );
- }
- }
- } else {
- return await interaction?.deferUpdate().catch(() => undefined);
- }
- });
- }
-
- public async generateMessageOptions(
- message: BushMessage | BushSlashMessage,
- feature?: 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);
- if (!feature) {
- const desc = settingsArr.map((s) => `**${guildSettingsObj[s].name}**`).join('\n');
- settingsEmbed.setDescription(desc);
-
- const selMenu = new MessageActionRow().addComponents(
- new MessageSelectMenu()
- .addOptions(
- ...settingsArr.map((s) => ({
- label: guildSettingsObj[s].name,
- value: s,
- description: guildSettingsObj[s].description
- }))
- )
- .setPlaceholder('Select A Setting to View')
- .setMaxValues(1)
- .setMinValues(1)
- .setCustomId('command_settingsSel')
- );
- return { embeds: [settingsEmbed], components: [selMenu] };
- } else {
- const generateCurrentValue = async (
- type: 'string' | 'channel' | 'channel-array' | 'role' | 'role-array'
- ): Promise<string> => {
- const feat = await message.guild!.getSetting(feature);
- switch (type.replace('-array', '') as 'string' | 'channel' | 'role') {
- case 'string': {
- return Array.isArray(feat)
- ? feat.map((feat) => util.discord.escapeInlineCode(util.inspectAndRedact(feat))).join('\n')
- : util.discord.escapeInlineCode(util.inspectAndRedact(feat));
- }
- case 'channel': {
- return Array.isArray(feat) ? feat.map((feat) => `<#${feat}>`).join('\n') : `<#${feat}>`;
- }
- case 'role': {
- return Array.isArray(feat) ? feat.map((feat) => `<@&${feat}>`).join('\n') : `<@&${feat}>`;
- }
- }
- };
- const components = new MessageActionRow().addComponents(
- new MessageButton().setStyle('PRIMARY').setCustomId('command_settingsBack').setLabel('Back')
- );
- settingsEmbed.setDescription(guildSettingsObj[feature].description);
-
- settingsEmbed.setFooter(
- `Run "${message.util.isSlash ? '/' : await message.guild.getSetting('prefix')}settings ${feature} ${
- guildSettingsObj[feature].type.includes('-array') ? 'add/remove' : 'set'
- } <value>" to set this setting.`
- );
- settingsEmbed.addField(
- guildSettingsObj[feature].name,
- await generateCurrentValue(feature as 'string' | 'channel' | 'channel-array' | 'role' | 'role-array')
- );
- 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/info/help.ts b/src/commands/info/help.ts
index 97811da..ad4e00f 100644
--- a/src/commands/info/help.ts
+++ b/src/commands/info/help.ts
@@ -47,7 +47,11 @@ export default class HelpCommand extends BushCommand {
message: BushMessage | BushSlashMessage,
args: { command: BushCommand | string; showHidden?: boolean }
): Promise<unknown> {
- const prefix = client.config.isDevelopment ? 'dev ' : message.util.parsed?.prefix ?? client.config.prefix;
+ const prefix = message.util.isSlash
+ ? '/'
+ : client.config.isDevelopment
+ ? 'dev '
+ : message.util.parsed?.prefix ?? client.config.prefix;
const row = new MessageActionRow();
if (!client.config.isDevelopment && !client.guilds.cache.some((guild) => guild.ownerId === message.author.id)) {
diff --git a/src/commands/leveling/leaderboard.ts b/src/commands/leveling/leaderboard.ts
new file mode 100644
index 0000000..b8838b7
--- /dev/null
+++ b/src/commands/leveling/leaderboard.ts
@@ -0,0 +1,52 @@
+import { BushCommand, BushMessage, BushSlashMessage, Level } from '@lib';
+import { MessageEmbed } from 'discord.js';
+
+export default class LeaderboardCommand extends BushCommand {
+ public constructor() {
+ super('leaderboard', {
+ aliases: ['leaderboard', 'lb'],
+ category: 'leveling',
+ description: {
+ content: 'Allows you to see the users with the highest levels in the server.',
+ usage: 'leaderboard [page]',
+ examples: ['leaderboard 5']
+ },
+ args: [
+ {
+ id: 'page',
+ type: 'integer',
+ prompt: {
+ start: 'What would you like to set your first argument to be?',
+ retry: '{error} Pick a valid argument.',
+ optional: true
+ }
+ }
+ ],
+ slash: true,
+ slashOptions: [
+ {
+ name: 'page',
+ description: 'What would you like to set your first argument to be?',
+ type: 'INTEGER',
+ required: false
+ }
+ ],
+ channel: 'guild',
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
+ });
+ }
+
+ public override async exec(message: BushMessage | BushSlashMessage, args: { page: number }): Promise<unknown> {
+ if (!message.guild) return await message.util.reply(`${util.emojis.error} This command can only be run in a server.`);
+ const ranks = (await Level.findAll({ where: { guild: message.guild.id } })).sort((a, b) => b.xp - a.xp);
+ const mapedRanks = ranks.map(
+ (val, index) => `\`${index + 1}\` <@${val.user}> - Level ${val.level} (${val.xp.toLocaleString()} xp)`
+ );
+ const chunked = util.chunk(mapedRanks, 25);
+ const embeds = chunked.map((c) =>
+ new MessageEmbed().setTitle(`${message.guild!.name}'s Leaderboard`).setDescription(c.join('\n'))
+ );
+ return await util.buttonPaginate(message, embeds, null, true, args?.page ?? undefined);
+ }
+}
diff --git a/src/commands/leveling/level.ts b/src/commands/leveling/level.ts
new file mode 100644
index 0000000..6640744
--- /dev/null
+++ b/src/commands/leveling/level.ts
@@ -0,0 +1,143 @@
+import {
+ AllowedMentions,
+ BushCommand,
+ BushGuild,
+ BushMessage,
+ BushSlashMessage,
+ BushUser,
+ CanvasProgressBar,
+ Level
+} from '@lib';
+import canvas from 'canvas';
+import { MessageAttachment } from 'discord.js';
+import got from 'got/dist/source';
+import { join } from 'path';
+import SimplifyNumber from 'simplify-number';
+
+export default class LevelCommand extends BushCommand {
+ public constructor() {
+ super('level', {
+ aliases: ['level', 'rank', 'lvl'],
+ category: 'leveling',
+ description: {
+ content: 'Shows the level of a user',
+ usage: 'level [user]',
+ examples: ['level', 'level @Tyman']
+ },
+ args: [
+ {
+ id: 'user',
+ type: 'user',
+ prompt: {
+ start: 'What user would you like to see the level of?',
+ retry: '{error} Choose a valid user to see the level of.',
+ optional: true
+ }
+ }
+ ],
+ slashOptions: [
+ {
+ name: 'user',
+ description: 'The user to get the level of',
+ type: 'USER',
+ required: false
+ }
+ ],
+ slash: true,
+ channel: 'guild'
+ });
+ }
+
+ private async getImage(user: BushUser, guild: BushGuild): Promise<Buffer> {
+ // I added comments because this code is impossible to read
+ const guildRows = await Level.findAll({ where: { guild: guild.id } });
+ const rank = guildRows.sort((a, b) => b.xp - a.xp);
+ const userLevelRow = guildRows.find((a) => a.user === user.id);
+ if (!userLevelRow) throw new Error('User does not have a level');
+ const userLevel = userLevelRow.level;
+ const currentLevelXP = Level.convertLevelToXp(userLevel);
+ const currentLevelXPProgress = userLevelRow.xp - currentLevelXP;
+ const xpForNextLevel = Level.convertLevelToXp(userLevelRow.level + 1) - currentLevelXP;
+ const white = '#FFFFFF',
+ gray = '#23272A',
+ highlight = user.hexAccentColor ?? '#5865F2';
+ // Load roboto font because yes
+ 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 = gray;
+ ctx.fillRect(0, 0, image.width, image.height);
+ // Draw avatar
+ 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;
+ ctx.drawImage(avatarImage, imageTopCoord, imageTopCoord);
+ // Write tag of user
+ ctx.font = '30px Roboto';
+ ctx.fillStyle = white;
+ const measuredTag = ctx.measureText(user.tag);
+ ctx.fillText(user.tag, avatarImage.width + 70, 60);
+ // Draw line under tag
+ ctx.fillStyle = highlight;
+ 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 - 0,
+ height: 30,
+ width: 550
+ },
+ white,
+ 1
+ );
+ fullProgressBar.draw();
+ const progressBar = new CanvasProgressBar(
+ ctx,
+ {
+ x: avatarImage.width + 70,
+ y: avatarImage.height - 0,
+ height: 30,
+ width: 550
+ },
+ highlight,
+ currentLevelXPProgress / xpForNextLevel
+ );
+ progressBar.draw();
+ // Draw level data text
+ 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();
+ }
+
+ public override async exec(message: BushMessage | BushSlashMessage, args: { user?: BushUser }): Promise<unknown> {
+ const user = args.user ?? message.author;
+ try {
+ return await message.util.reply({
+ files: [new MessageAttachment(await this.getImage(user, message.guild!), 'level.png')]
+ });
+ } catch (e) {
+ if (e instanceof Error && e.message === 'User does not have a level') {
+ return await message.util.reply({
+ content: `${util.emojis.error} ${user} does not have a level.`,
+ allowedMentions: AllowedMentions.none()
+ });
+ } else throw e;
+ }
+ }
+}
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/level.ts b/src/commands/moulberry-bush/level.ts
deleted file mode 100644
index 02d66be..0000000
--- a/src/commands/moulberry-bush/level.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { BushCommand, BushGuild, BushMessage, BushSlashMessage, BushUser, 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';
-*/
-
-export default class LevelCommand extends BushCommand {
- public constructor() {
- super('level', {
- aliases: ['level', 'rank'],
- category: "Moulberry's Bush",
- description: {
- content: 'Shows the level of a user',
- usage: 'level [user]',
- examples: ['level', 'level @Tyman']
- },
- args: [
- {
- id: 'user',
- type: 'user',
- prompt: {
- start: 'What user would you like to see the level of?',
- retry: '{error} Choose a valid user to see the level of.',
- optional: true
- }
- }
- ],
- slashOptions: [
- {
- name: 'user',
- description: 'The user to get the level of',
- type: 'USER',
- required: false
- }
- ],
- slash: true,
- channel: 'guild'
- });
- }
-
- /* private simplifyXP(xp: number): string {
-
- }
-
- private async getImage(user: User): Promise<Buffer> {
- // I added comments because this code is impossible to read
- const [userLevelRow] = await Level.findOrBuild({
- where: {
- id: user.id
- },
- defaults: {
- id: user.id
- }
- });
- const userLevel = userLevelRow.level
- const currentLevelXP = Level.convertLevelToXp(userLevel);
- const currentLevelXPProgress = userLevelRow.xp - currentLevelXP;
- const xpForNextLevel =
- Level.convertLevelToXp(userLevelRow.level + 1) - currentLevelXP;
- // Load roboto font because yes
- canvas.registerFont(
- join(__dirname, '..', '..', '..', 'Roboto-Regular.ttf'),
- {
- family: 'Roboto'
- }
- );
- // Create image canvas
- const image = canvas.createCanvas(800, 200),
- ctx = image.getContext('2d');
- // Fill background
- ctx.fillStyle = '#00c7eb';
- ctx.fillRect(0, 0, image.width, image.height);
- // Draw avatar
- 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)
- ctx.drawImage(avatarImage, imageTopCoord, imageTopCoord);
- // Write tag of user
- ctx.font = '30px Roboto';
- ctx.fillStyle = 'black';
- 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
- );
- // Draw leveling bar
- const fullProgressBar = new CanvasProgressBar(
- ctx,
- {
- x: avatarImage.width + 70,
- y: avatarImage.height - 10,
- height: 30,
- width: 550
- },
- '#6e6e6e',
- 1
- );
- fullProgressBar.draw();
- const progressBar = new CanvasProgressBar(
- ctx,
- {
- x: avatarImage.width + 70,
- y: avatarImage.height - 10,
- height: 30,
- width: 550
- },
- 'yellow',
- currentLevelXPProgress / xpForNextLevel
- );
- progressBar.draw();
- // Draw level data text
- ctx.fillStyle = 'black'
- ctx.fillText(`Level: ${userLevel} XP: $`, 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!));
- }
-}
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/commands/utilities/suicide.ts b/src/commands/utilities/suicide.ts
new file mode 100644
index 0000000..0ec84a5
--- /dev/null
+++ b/src/commands/utilities/suicide.ts
@@ -0,0 +1,52 @@
+import { AllowedMentions, BushCommand, BushMessage, BushSlashMessage } from '@lib';
+import { MessageEmbed } from 'discord.js';
+
+export default class TemplateCommand extends BushCommand {
+ public constructor() {
+ super('suicide', {
+ aliases: ['suicide'],
+ category: 'utilities',
+ description: {
+ content: 'Mental Health Resources. Credit to https://github.com/dexbiobot/Zeppelin.',
+ usage: 'suicide',
+ examples: ['suicide']
+ },
+ slash: true,
+ clientPermissions: ['SEND_MESSAGES'],
+ userPermissions: ['SEND_MESSAGES']
+ });
+ }
+
+ public override async exec(message: BushMessage | BushSlashMessage): Promise<unknown> {
+ // stolen from https://github.com/dexbiobot/Zeppelin
+ const suicideEmbed = new MessageEmbed()
+ .setTitle('Mental Health Resources')
+ .setColor(util.colors.red)
+ .setAuthor(
+ 'Remember, You Matter <3',
+ 'https://media.discordapp.net/attachments/770256340639416320/854689949193076737/Medical_31-60_974.jpg?width=523&height=523'
+ )
+ .addField(
+ '**National Suicide Prevention Hotline (U.S.):**',
+ `**Call:** 1-800-273-8255, available 24/7 for emotional support
+**Text: HOME** to 741741
+https://suicidepreventionlifeline.org/chat/
+
+Outside the U.S: Find a supportive resource on [this Wikipedia list of worldwide crisis hotlines](https://en.wikipedia.org/wiki/List_of_suicide_crisis_lines)`
+ )
+ .addField(
+ '**More Support**',
+ `For Substance Abuse Support, Eating Disorder Support & Child Abuse and Domestic Violence:
+[Click to go to Discord's Health & Safety Page](https://discord.com/safety/360044103771-Mental-health-on-Discord#h_01EGRGT08QSZ5BNCH2E9HN0NYV)`
+ );
+
+ return (
+ // If the original message was a reply -> imitate it
+ (message as BushMessage).reference?.messageId && !message.util.isSlash && message.guild && message.channel
+ ? await message.channel.messages.fetch((message as BushMessage).reference!.messageId!).then(async (message1) => {
+ await message1.reply({ embeds: [suicideEmbed], allowedMentions: AllowedMentions.users(), target: message1 });
+ })
+ : await message.util.send({ embeds: [suicideEmbed], allowedMentions: AllowedMentions.users() })
+ );
+ }
+}
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..9a5a07f 100644
--- a/src/lib/extensions/discord-akairo/BushClientUtil.ts
+++ b/src/lib/extensions/discord-akairo/BushClientUtil.ts
@@ -673,7 +673,8 @@ export class BushClientUtil extends ClientUtil {
message: BushMessage | BushSlashMessage,
embeds: MessageEmbed[],
text: string | null = null,
- deleteOnExit?: boolean
+ deleteOnExit?: boolean,
+ startOn?: number
): Promise<void> {
const paginateEmojis = this.#paginateEmojis;
if (deleteOnExit === undefined) deleteOnExit = true;
@@ -687,7 +688,7 @@ export class BushClientUtil extends ClientUtil {
});
const style = Constants.MessageButtonStyles.PRIMARY;
- let curPage = 0;
+ let curPage = startOn ? startOn - 1 : undefined ?? 0;
if (typeof embeds !== 'object') throw new Error('embeds must be an object');
const msg = (await message.util.reply({
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
@@ -999,7 +1000,7 @@ export class BushClientUtil extends ClientUtil {
/**
* Add or remove an item from an array. All duplicates will be removed.
*/
- public addOrRemoveFromArray(action: 'add' | 'remove', array: any[], value: any): any[] {
+ public addOrRemoveFromArray<T extends any>(action: 'add' | 'remove', array: T[], value: T): T[] {
const set = new Set(array);
action === 'add' ? set.add(value) : set.delete(value);
return [...set];
@@ -1354,10 +1355,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/src/lib/models/Guild.ts b/src/lib/models/Guild.ts
index 4a5ede4..1974725 100644
--- a/src/lib/models/Guild.ts
+++ b/src/lib/models/Guild.ts
@@ -41,49 +41,49 @@ export interface GuildModelCreationAttributes {
export const guildSettingsObj = {
prefix: {
name: 'Prefix',
- description: 'description goes here',
+ description: 'The phrase required to trigger text commands in this server.',
type: 'string',
configurable: true
},
autoPublishChannels: {
name: 'Auto Publish Channels',
- description: 'description goes here',
+ description: 'Channels were every message is automatically published.',
type: 'channel-array',
configurable: true
},
welcomeChannel: {
name: 'Welcome Channel',
- description: 'description goes here',
- type: 'channel-array',
+ description: 'The channel where the bot will send join and leave message.',
+ type: 'channel',
configurable: true
},
muteRole: {
name: 'Mute Role',
- description: 'description goes here',
+ description: 'The role assigned when muting someone.',
type: 'role',
configurable: true
},
punishmentEnding: {
name: 'Punishment Ending',
- description: 'description goes here',
+ description: 'The message after punishment information to a user in a dm.',
type: 'string',
configurable: true
},
lockdownChannels: {
name: 'Lockdown Channels',
- description: 'description goes here',
+ description: 'Channels that are locked down when a mass lockdown is specified.',
type: 'channel-array',
configurable: false // not implemented yet
},
joinRoles: {
name: 'Join Roles',
- description: 'description goes here',
+ description: 'Roles assigned to users on join who do not have sticky role information.',
type: 'role-array',
configurable: true
},
automodLogChannel: {
name: 'Automod Log Channel',
- description: 'description goes here',
+ description: 'The channel where all automod information is sent.',
type: 'channel',
configurable: true
}
diff --git a/yarn.lock b/yarn.lock
index bb91c1d..59e2c70 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -359,9 +359,9 @@ __metadata:
linkType: hard
"@types/node@npm:*":
- version: 16.7.4
- resolution: "@types/node@npm:16.7.4"
- checksum: da7813e6c37e7813645a9d40de6d9f803fbadb2975748d307ec31d8e4f9baabccf49b667a39e4b1288d477ec7d34a339e8a41d8703a1d7ab0ec8eb2516073f27
+ version: 16.7.6
+ resolution: "@types/node@npm:16.7.6"
+ checksum: a8533386a1d4ca0ed67885413001af8789c63948df288f3d36e31bd8fccffacf5dffb95e190c8cd57bb40385f010fb9a30f596bad6bb26b2bb88737d54d8ed95
languageName: node
linkType: hard
@@ -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=8c34349a3eb03164e34bcf538787cce259c76aa9"
dependencies:
discord-akairo-message-util: NotEnoughUpdates/discord-akairo-message-util
lodash: ^4.17.21
source-map-support: ^0.5.19
- checksum: 1fcd67033576768a5b8bed52e15473795242860e5661d5f2a781acb0579e334d09fdf5f824e6eedcd7050fa5df88beadda309d19be503c7c3c5554917ccdbd4a
+ checksum: 7b71844b4955fed0f383b59e23770e526140459dc55b3a8af4707bd6be7a9295c2ac6812df47282a5b922a50f4569b597151f21f893ecce7ef9b1e8b87f88a64
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"
@@ -3369,8 +3377,8 @@ typescript@^4.4.2:
linkType: hard
"ws@npm:^7.4.4, ws@npm:^7.5.1":
- version: 7.5.3
- resolution: "ws@npm:7.5.3"
+ version: 7.5.4
+ resolution: "ws@npm:7.5.4"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
@@ -3379,7 +3387,7 @@ typescript@^4.4.2:
optional: true
utf-8-validate:
optional: true
- checksum: 423dc0d859fa74020f5555140905b862470a60ea1567bb9ad55a087263d7718b9c94f69678be1cee9868925c570f1e6fc79d09f90c39057bc63fa2edbb2c547b
+ checksum: 48582e4feb1fc6b6b977a0ee6136e5cd1c6a14bc5cb6ce5acf596652b34be757cdf0c225235b3263d56d057bc5d6e528dbe27fc88a3d09828aa803c6696f4b2c
languageName: node
linkType: hard