aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/dev/superUser.ts6
-rw-r--r--src/commands/info/help.ts142
-rw-r--r--src/commands/moderation/ban.ts2
-rw-r--r--src/commands/utilities/steal.ts4
-rw-r--r--src/lib/extensions/discord-akairo/BushCommand.ts134
5 files changed, 209 insertions, 79 deletions
diff --git a/src/commands/dev/superUser.ts b/src/commands/dev/superUser.ts
index 57e2b86..d62ac8e 100644
--- a/src/commands/dev/superUser.ts
+++ b/src/commands/dev/superUser.ts
@@ -15,12 +15,12 @@ export default class SuperUserCommand extends BushCommand {
ownerOnly: true,
helpArgs: [
{
- id: 'action',
+ name: 'action',
description: 'Whether to add or remove a user from the superuser list.',
- readableType: 'add|remove'
+ type: "'add'|'remove'"
},
{
- id: 'user',
+ name: 'user',
description: 'The user to add/remove from the superuser list.',
type: 'user',
match: 'restContent'
diff --git a/src/commands/info/help.ts b/src/commands/info/help.ts
index 7a30e64..ea1e965 100644
--- a/src/commands/info/help.ts
+++ b/src/commands/info/help.ts
@@ -11,6 +11,7 @@ import {
} from 'discord.js';
import Fuse from 'fuse.js';
import packageDotJSON from '../../../package.json' assert { type: 'json' };
+import { stripIndent } from '../../lib/common/tags.js';
assert(Fuse);
assert(packageDotJSON);
@@ -52,51 +53,71 @@ export default class HelpCommand extends BushCommand {
});
}
- public override async exec(
- message: BushMessage | BushSlashMessage,
- args: { command: ArgType<'commandAlias'> | string; showHidden?: boolean }
- ) {
- const prefix = util.prefix(message);
+ public override async exec(message: BushMessage | BushSlashMessage, args: HelpArgs) {
const row = this.addLinks(message);
-
- const isOwner = client.isOwner(message.author);
- const isSuperUser = client.isSuperUser(message.author);
const command = args.command
? typeof args.command === 'string'
? client.commandHandler.findCommand(args.command) ?? null
: args.command
: null;
- if (!isOwner) args.showHidden = false;
+
+ if (!message.author.isOwner()) args.showHidden = false;
+
if (!command || command.pseudo) {
- const embed = new EmbedBuilder().setColor(util.colors.default).setTimestamp();
- embed.setFooter({ text: `For more information about a command use ${prefix}help <command>` });
- for (const [, category] of this.handler.categories) {
- const categoryFilter = category.filter((command) => {
- if (command.pseudo) return false;
- if (command.hidden && !args.showHidden) return false;
- if (command.channel == 'guild' && !message.guild && !args.showHidden) return false;
- if (command.ownerOnly && !isOwner) return false;
- if (command.superUserOnly && !isSuperUser) return false;
- if (command.restrictedGuilds?.includes(message.guild?.id ?? '') === false && !args.showHidden) return false;
- if (command.aliases.length === 0) return false;
-
- return true;
- });
- const categoryNice = category.id
- .replace(/(\b\w)/gi, (lc) => lc.toUpperCase())
- .replace(/'(S)/g, (letter) => letter.toLowerCase());
- const categoryCommands = categoryFilter.filter((cmd) => cmd.aliases.length > 0).map((cmd) => `\`${cmd.aliases[0]}\``);
- if (categoryCommands.length > 0) {
- embed.addFields([{ name: `${categoryNice}`, value: `${categoryCommands.join(' ')}` }]);
- }
- }
- return await message.util.reply({ embeds: [embed], components: row.components.length ? [row] : undefined });
+ return this.helpAll(message, args, row);
+ } else {
+ return this.helpIndividual(message, row, command);
}
+ }
+ private helpAll(message: BushMessage | BushSlashMessage, args: HelpArgs, row: ActionRowBuilder<ButtonBuilder>) {
+ const prefix = util.prefix(message);
const embed = new EmbedBuilder()
.setColor(util.colors.default)
- .setTitle(`${command.id} Command`)
- .setDescription(`${command.description ?? '*This command does not have a description.*'}`);
+ .setTimestamp()
+ .setFooter({ text: `For more information about a command use ${prefix}help <command>` });
+ for (const [, category] of this.handler.categories) {
+ const categoryFilter = category.filter((command) => {
+ if (command.pseudo) return false;
+ if (command.hidden && !args.showHidden) return false;
+ if (command.channel == 'guild' && !message.guild && !args.showHidden) return false;
+ if (command.ownerOnly && !message.author.isOwner()) return false;
+ if (command.superUserOnly && !message.author.isSuperUser()) return false;
+ if (command.restrictedGuilds?.includes(message.guild?.id ?? '') === false && !args.showHidden) return false;
+ if (command.aliases.length === 0) return false;
+
+ return true;
+ });
+ const categoryNice = category.id
+ .replace(/(\b\w)/gi, (lc) => lc.toUpperCase())
+ .replace(/'(S)/g, (letter) => letter.toLowerCase());
+ const categoryCommands = categoryFilter.filter((cmd) => cmd.aliases.length > 0).map((cmd) => `\`${cmd.aliases[0]}\``);
+ if (categoryCommands.length > 0) {
+ embed.addFields([{ name: `${categoryNice}`, value: `${categoryCommands.join(' ')}` }]);
+ }
+ }
+ return message.util.reply({ embeds: [embed], components: row.components.length ? [row] : undefined });
+ }
+
+ private helpIndividual(message: BushMessage | BushSlashMessage, row: ActionRowBuilder<ButtonBuilder>, command: BushCommand) {
+ const embed = new EmbedBuilder().setColor(util.colors.default).setTitle(`${command.id} Command`);
+
+ let description = `${command.description ?? '*This command does not have a description.*'}`;
+ if (command.note) description += `\n\n${command.note}`;
+ embed.setDescription(description);
+
+ this.addCommandUsage(embed, command);
+ this.addCommandExamples(embed, command);
+ this.addCommandAliases(embed, command);
+ this.addCommandArguments(embed, command, message.author.isOwner(), message.author.isSuperUser());
+ this.addCommandRestrictions(embed, command);
+ // todo: permissions
+
+ const params = { embeds: [embed], components: row.components.length ? [row] : undefined };
+ return message.util.reply(params);
+ }
+
+ private addCommandUsage(embed: EmbedBuilder, command: BushCommand) {
if (command.usage?.length) {
embed.addFields([
{
@@ -105,6 +126,9 @@ export default class HelpCommand extends BushCommand {
}
]);
}
+ }
+
+ private addCommandExamples(embed: EmbedBuilder, command: BushCommand) {
if (command.examples?.length) {
embed.addFields([
{
@@ -113,7 +137,50 @@ export default class HelpCommand extends BushCommand {
}
]);
}
- if (command.aliases?.length > 1) embed.addFields([{ name: '» Aliases', value: `\`${command.aliases.join('` `')}\`` }]);
+ }
+
+ private addCommandAliases(embed: EmbedBuilder, command: BushCommand) {
+ if (command.aliases?.length > 1)
+ embed.addFields([
+ {
+ name: '» Aliases',
+ value: `\`${command.aliases.join('` `')}\``
+ }
+ ]);
+ }
+
+ private addCommandArguments(embed: EmbedBuilder, command: BushCommand, isOwner = false, isSuperUser = false) {
+ const format = (id: string, req: boolean) => `${req ? '<' : '['}${id}${req ? '>' : ']'}`;
+ const args = (command.argsInfo ?? []).filter((arg) => {
+ if (arg.ownerOnly && !isOwner) return false;
+ if (arg.superUserOnly && !isSuperUser) return false;
+ return true;
+ });
+ if (args.length) {
+ embed.addFields([
+ {
+ name: '» Arguments',
+ value: args
+ .map((a) => {
+ let ret = stripIndent`
+ \`${format(a.name, !!a.optional)}\`
+ ⠀‣ **Desc**: ${a.description}
+ ⠀‣ **Type**: ${typeof a.type !== 'function' ? a.type : '[no readable type]'}`;
+
+ if (a.flag?.length) ret += `\n⠀‣ **Flags**: ${a.flag.map((f) => `"${f}"`).join(', ')}`;
+ ret += `\n⠀‣ **Kind**: ${a.only ?? 'text & slash'}`;
+ if (a.only !== 'slash') ret += `\n⠀‣ **Match**: ${a.match}`;
+ if (a.only !== 'text') ret += `\n⠀‣ **Autocomplete**: ${a.autocomplete}`;
+
+ return ret;
+ })
+ .join('\n')
+ }
+ ]);
+ }
+ }
+
+ private addCommandRestrictions(embed: EmbedBuilder, command: BushCommand) {
if (
command.ownerOnly ||
command.superUserOnly ||
@@ -138,9 +205,6 @@ export default class HelpCommand extends BushCommand {
);
if (restrictions.length) embed.addFields([{ name: '» Restrictions', value: restrictions.join('\n') }]);
}
-
- const params = { embeds: [embed], components: row.components.length ? [row] : undefined };
- return await message.util.reply(params);
}
private addLinks(message: BushMessage | BushSlashMessage) {
@@ -181,3 +245,5 @@ export default class HelpCommand extends BushCommand {
void interaction.respond(res.length ? res : startingCommands);
}
}
+
+type HelpArgs = { command: ArgType<'commandAlias'> | string; showHidden?: boolean };
diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts
index 14bbba6..77951c3 100644
--- a/src/commands/moderation/ban.ts
+++ b/src/commands/moderation/ban.ts
@@ -24,6 +24,7 @@ export default class BanCommand extends BushCommand {
id: 'user',
description: 'The user that will be banned.',
type: util.arg.union('user', 'snowflake'),
+ readableType: 'user|snowflake',
prompt: 'What user would you like to ban?',
retry: '{error} Choose a valid user to ban.',
slashType: ApplicationCommandOptionType.User
@@ -46,6 +47,7 @@ export default class BanCommand extends BushCommand {
prompt: "How many days of the user's messages would you like to delete?",
retry: '{error} Choose between 0 and 7 days to delete messages from the user for.',
type: util.arg.range('integer', 0, 7, true),
+ readableType: 'integer [0, 7]',
optional: true,
slashType: ApplicationCommandOptionType.Integer,
choices: [...Array(8).keys()].map((v) => ({ name: v.toString(), value: v }))
diff --git a/src/commands/utilities/steal.ts b/src/commands/utilities/steal.ts
index 765fb24..e4f08f1 100644
--- a/src/commands/utilities/steal.ts
+++ b/src/commands/utilities/steal.ts
@@ -31,8 +31,8 @@ export default class StealCommand extends BushCommand {
{ name: 'name', description: lang.nameStart, type: ApplicationCommandOptionType.String, required: false }
],
helpArgs: [
- { id: 'emoji', description: lang.emojiDescription, readableType: 'emoji|emojiId|url', optional: false },
- { id: 'name', description: lang.nameDescription, readableType: 'string', optional: true }
+ { name: 'emoji', description: lang.emojiDescription, type: 'emoji|emojiId|url', optional: false },
+ { name: 'name', description: lang.nameDescription, type: 'string', optional: true }
],
slash: true,
channel: 'guild',
diff --git a/src/lib/extensions/discord-akairo/BushCommand.ts b/src/lib/extensions/discord-akairo/BushCommand.ts
index 1797be8..c727e98 100644
--- a/src/lib/extensions/discord-akairo/BushCommand.ts
+++ b/src/lib/extensions/discord-akairo/BushCommand.ts
@@ -26,6 +26,7 @@ import {
type ParsedDuration
} from '#lib';
import {
+ ArgumentMatch,
Command,
type AkairoApplicationCommandAutocompleteOption,
type AkairoApplicationCommandChannelOptionData,
@@ -53,6 +54,7 @@ import {
type PermissionsString,
type Snowflake
} from 'discord.js';
+import _ from 'lodash';
export interface OverriddenBaseArgumentType extends BaseArgumentType {
user: BushUser | null;
@@ -113,7 +115,7 @@ export interface BaseBushArgumentType extends OverriddenBaseArgumentType {
export type BushArgumentType = keyof BaseBushArgumentType | RegExp;
-interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type' | 'prompt'> {
+interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type' | 'prompt'>, ExtraArgumentOptions {
id: string;
description: string;
@@ -168,7 +170,9 @@ interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type' | 'prompt
* The maximum value for an `INTEGER` or `NUMBER` option
*/
maxValue?: number;
+}
+interface ExtraArgumentOptions {
/**
* Restrict this argument to only slash or only text commands.
*/
@@ -178,6 +182,18 @@ interface BaseBushArgumentOptions extends Omit<ArgumentOptions, 'type' | 'prompt
* Readable type for the help command.
*/
readableType?: string;
+
+ /**
+ * Whether the argument is only accessible to the owners.
+ * @default false
+ */
+ ownerOnly?: boolean;
+
+ /**
+ * Whether the argument is only accessible to the super users.
+ * @default false
+ */
+ superUserOnly?: boolean;
}
export interface BushArgumentOptions extends BaseBushArgumentOptions {
@@ -284,7 +300,7 @@ interface ExtendedCommandOptions {
/**
* Use instead of {@link BaseBushCommandOptions.args} when using argument generators or custom slashOptions
*/
- helpArgs?: (Omit<BushArgumentOptions, 'slashType'> & { slashType?: AkairoApplicationCommandOptionData['type'] })[];
+ helpArgs?: ArgsInfo[];
/**
* Extra information about the command, displayed in the help command.
@@ -318,12 +334,12 @@ export interface BaseBushCommandOptions
userPermissions: bigint | bigint[] | BushMissingPermissionSupplier;
/**
- * Restrict this argument to owners
+ * Whether the argument is only accessible to the owners.
*/
ownerOnly?: boolean;
/**
- * Restrict this argument to super users.
+ * Whether the argument is only accessible to the super users.
*/
superUserOnly?: boolean;
}
@@ -331,30 +347,62 @@ export interface BaseBushCommandOptions
export type BushCommandOptions = Omit<BaseBushCommandOptions, 'helpArgs'> | Omit<BaseBushCommandOptions, 'args'>;
export interface ArgsInfo {
- id: string;
- description: string;
- optional?: boolean;
- slashType: AkairoApplicationCommandOptionData['type'] | false;
- slashResolve?: SlashResolveType;
- only?: 'slash' | 'text';
- type: string;
-}
+ /**
+ * The name of the argument.
+ */
+ name: string;
-export interface ArgsInfoText {
- id: string;
+ /**
+ * The description of the argument.
+ */
description: string;
+
+ /**
+ * Whether the argument is optional.
+ * @default false
+ */
optional?: boolean;
- only: 'text';
+
+ /**
+ * Whether or not the argument has autocomplete enabled.
+ * @default false
+ */
+ autocomplete?: boolean;
+
+ /**
+ * Whether the argument is restricted a certain command.
+ * @default 'slash & text'
+ */
+ only?: 'slash & text' | 'slash' | 'text';
+
+ /**
+ * The method that arguments are matched for text commands.
+ * @default 'phrase'
+ */
+ match?: ArgumentMatch;
+
+ /**
+ * The readable type of the argument.
+ */
type: string;
-}
-export interface ArgsInfoSlash {
- id: string;
- description: string;
- optional?: boolean;
- slashType: AkairoApplicationCommandOptionData['type'] | false;
- slashResolve?: SlashResolveType;
- only: 'slash';
+ /**
+ * If {@link match} is 'flag' or 'option', these are the flags that are matched
+ * @default []
+ */
+ flag?: string[];
+
+ /**
+ * Whether the argument is only accessible to the owners.
+ * @default false
+ */
+ ownerOnly?: boolean;
+
+ /**
+ * Whether the argument is only accessible to the super users.
+ * @default false
+ */
+ superUserOnly?: boolean;
}
export class BushCommand extends Command {
@@ -435,11 +483,11 @@ export class BushCommand extends Command {
for (const _key in options_) {
const key = _key as keyof typeof options_; // you got to love typescript
if (key === 'args' && 'args' in options_ && typeof options_.args === 'object') {
- const newTextArgs: ArgumentOptions[] = [];
+ const newTextArgs: (ArgumentOptions & ExtraArgumentOptions)[] = [];
const newSlashArgs: SlashOption[] = [];
for (const arg of options_.args) {
if (arg.only !== 'slash' && !options_.slashOnly) {
- const newArg: ArgumentOptions = {};
+ const newArg: ArgumentOptions & ExtraArgumentOptions = {};
if ('default' in arg) newArg.default = arg.default;
if ('description' in arg) newArg.description = arg.description;
if ('flag' in arg) newArg.flag = arg.flag;
@@ -458,6 +506,8 @@ export class BushCommand extends Command {
}
if ('type' in arg) newArg.type = arg.type as ArgumentType | ArgumentTypeCaster;
if ('unordered' in arg) newArg.unordered = arg.unordered;
+ if ('ownerOnly' in arg) newArg.ownerOnly = arg.ownerOnly;
+ if ('superUserOnly' in arg) newArg.superUserOnly = arg.superUserOnly;
newTextArgs.push(newArg);
}
if (
@@ -495,19 +545,31 @@ export class BushCommand extends Command {
super(id, newOptions);
- if (options_.args || options_.helpArgs) {
+ if (options_.args ?? options_.helpArgs) {
const argsInfo: ArgsInfo[] = [];
+ const combined = (options_.args ?? options_.helpArgs)!.map((arg) => {
+ const norm = options_.args
+ ? options_.args.find((_arg) => _arg.id === ('id' in arg ? arg.id : arg.name)) ?? ({} as BushArgumentOptions)
+ : ({} as BushArgumentOptions);
+ const help = options_.helpArgs
+ ? options_.helpArgs.find((_arg) => _arg.name === ('id' in arg ? arg.id : arg.name)) ?? ({} as ArgsInfo)
+ : ({} as ArgsInfo);
+ return { ...norm, ...help };
+ });
- for (const arg of (options_.args ?? options_.helpArgs)!) {
- argsInfo.push({
- id: arg.id,
- description: arg.description,
- optional: arg.optional,
- slashType: arg.slashType!,
- slashResolve: arg.slashResolve,
- only: arg.only,
- type: (arg.readableType ?? arg.type) as string
- });
+ for (const arg of combined) {
+ const name = _.camelCase('id' in arg ? arg.id : arg.name),
+ description = arg.description || '*No description provided.*',
+ optional = arg.optional ?? false,
+ autocomplete = arg.autocomplete ?? false,
+ only = arg.only ?? 'slash & text',
+ match = arg.match ?? 'phrase',
+ type = match === 'flag' ? 'flag' : arg.readableType ?? arg.type ?? 'string',
+ flag = arg.flag ? (Array.isArray(arg.flag) ? arg.flag : [arg.flag]) : [],
+ ownerOnly = arg.ownerOnly ?? false,
+ superUserOnly = arg.superUserOnly ?? false;
+
+ argsInfo.push({ name, description, optional, autocomplete, only, match, type, flag, ownerOnly, superUserOnly });
}
this.argsInfo = argsInfo;