diff --git a/src/command-abstractions/text-based-command-builder.ts b/src/command-abstractions/text-based-command-builder.ts index f94d22c1..a8aef897 100644 --- a/src/command-abstractions/text-based-command-builder.ts +++ b/src/command-abstractions/text-based-command-builder.ts @@ -21,6 +21,17 @@ export type TextBasedCommandParameterOptions = { autocomplete?: (partial: string, command_name: string) => { name: string; value: string }[]; }; +export type CommandCategory = + | "Wiki Articles" + | "References" + | "Thread Control" + | "Utility" + | "Misc" + | "Moderation" + | "Moderation Utilities" + | "Admin utilities" + | "Hidden"; + export enum EarlyReplyMode { ephemeral, visible, @@ -38,7 +49,9 @@ export class TextBasedCommandBuilder< descriptions!: ConditionalOptional; options = new Discord.Collection(); slash_config: boolean[]; - permissions: undefined | bigint = undefined; + permissions: bigint | undefined = undefined; + category: CommandCategory | undefined = undefined; + alias_of: string | undefined = undefined; subcommands: TextBasedCommandBuilder[] = []; type: HasSubcommands extends true ? "top-level" : "default"; allow_trailing_junk: boolean = false; @@ -212,6 +225,16 @@ export class TextBasedCommandBuilder< return this; } + set_category(category: CommandCategory) { + this.category = category; + return this; + } + + set_alias_of(command_name: string) { + this.alias_of = command_name; + return this; + } + set_allow_trailing_junk(allow_trailing_junk: boolean) { this.allow_trailing_junk = allow_trailing_junk; return this; @@ -245,10 +268,12 @@ export class TextBasedCommandBuilder< descriptors.push( new BotTextBasedCommand( name, - name, + "", description, slash, this.permissions, + this.category, + this.alias_of, this.allow_trailing_junk, this.early_reply_mode, this, diff --git a/src/command-abstractions/text-based-command-descriptor.ts b/src/command-abstractions/text-based-command-descriptor.ts index 7722c5db..b9f207c2 100644 --- a/src/command-abstractions/text-based-command-descriptor.ts +++ b/src/command-abstractions/text-based-command-descriptor.ts @@ -11,6 +11,7 @@ import { TextBasedCommandOptionType, TextBasedCommandBuilder, EarlyReplyMode, + CommandCategory, } from "./text-based-command-builder.js"; import { TextBasedCommand } from "./text-based-command.js"; import { BaseBotInteraction } from "./interaction-base.js"; @@ -23,13 +24,18 @@ export class BotTextBasedCommand extends BaseBotInt TextBasedCommandParameterOptions & { type: TextBasedCommandOptionType } >(); public readonly subcommands: Discord.Collection> | null = null; + public readonly display_name: string; + public readonly all_names: string[]; + public readonly all_display_names: string[]; constructor( name: string, - public readonly display_name: string, + public readonly display_prefix: string, public readonly description: string, public readonly slash: boolean, public readonly permissions: undefined | bigint, + public readonly category: CommandCategory | undefined, + public readonly alias_of: string | undefined, public readonly allow_trailing_junk: boolean, public readonly early_reply_mode: EarlyReplyMode, builder: TextBasedCommandBuilder | TextBasedCommandBuilder, @@ -37,6 +43,9 @@ export class BotTextBasedCommand extends BaseBotInt ) { super(name, builder.handler ?? (async () => wheatley.critical_error("This shouldn't happen"))); this.options = builder.options; + this.display_name = `${display_prefix}${name}`; + this.all_names = builder.names; + this.all_display_names = builder.names.map(name => `${display_prefix}${name}`); if (builder.type === "top-level") { this.subcommands = new Discord.Collection(); for (const subcommand of builder.subcommands) { @@ -50,10 +59,12 @@ export class BotTextBasedCommand extends BaseBotInt sub_name, new BotTextBasedCommand( sub_name, - `${display_name} ${sub_name}`, + `${display_prefix}${name} `, sub_description, sub_slash, permissions, + category, + subcommand.alias_of ?? alias_of, allow_trailing_junk, subcommand.early_reply_mode, subcommand, @@ -293,9 +304,10 @@ export class BotTextBasedCommand extends BaseBotInt if (this.subcommands) { return this.subcommands.map(command => command.get_usage()).join("\n"); } else { + const command_part = `!${this.display_prefix}${this.all_names.join("/")}`; return wrap( [ - "!" + this.display_name, + command_part, ...this.options.map(option => (option.required ? `<${option.title}>` : `[${option.title}]`)), ].join(" "), "`", diff --git a/src/command-handler.ts b/src/command-handler.ts index 1035ac34..a14ffa37 100644 --- a/src/command-handler.ts +++ b/src/command-handler.ts @@ -91,6 +91,10 @@ export class CommandHandler { return this.text_commands[command]; } + public get_all_commands() { + return this.text_commands; + } + // // Command dispatch // diff --git a/src/components/code.ts b/src/components/code.ts index c1553853..d84e6681 100644 --- a/src/components/code.ts +++ b/src/components/code.ts @@ -19,6 +19,7 @@ export default class Code extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("code", EarlyReplyMode.visible) + .set_category("Misc") .set_description("code formatting help") .set_allow_trailing_junk(true) .set_handler(this.code.bind(this)), diff --git a/src/components/echo.ts b/src/components/echo.ts index 4428b225..7df09f9a 100644 --- a/src/components/echo.ts +++ b/src/components/echo.ts @@ -22,6 +22,7 @@ export default class Echo extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("echo", EarlyReplyMode.none) + .set_category("Misc") .set_description("Echo") .add_string_option({ title: "input", @@ -32,6 +33,7 @@ export default class Echo extends BotComponent { ); commands.add( new TextBasedCommandBuilder("say", EarlyReplyMode.none) + .set_category("Misc") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Say as wheatley") .set_handler(this.say.bind(this)), diff --git a/src/components/google.ts b/src/components/google.ts index 24e91735..7cabf772 100644 --- a/src/components/google.ts +++ b/src/components/google.ts @@ -17,6 +17,7 @@ export default class Google extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("google", EarlyReplyMode.none) + .set_category("Misc") .set_description("google") .add_string_option({ title: "query", diff --git a/src/components/help.ts b/src/components/help.ts index 97cd20b3..2a0a7328 100644 --- a/src/components/help.ts +++ b/src/components/help.ts @@ -9,117 +9,239 @@ import { colors } from "../common.js"; import { BotComponent } from "../bot-component.js"; import { CommandSetBuilder } from "../command-abstractions/command-set-builder.js"; import { Wheatley } from "../wheatley.js"; -import { EarlyReplyMode, TextBasedCommandBuilder } from "../command-abstractions/text-based-command-builder.js"; +import { + EarlyReplyMode, + TextBasedCommandBuilder, + CommandCategory, +} from "../command-abstractions/text-based-command-builder.js"; import { TextBasedCommand } from "../command-abstractions/text-based-command.js"; -import Wiki from "./wiki.js"; +import { BotTextBasedCommand } from "../command-abstractions/text-based-command-descriptor.js"; + +type CommandInfoWithAliases = { + info: string; + aliases: string[]; +}; export default class Help extends BotComponent { static override get is_freestanding() { return true; } + private static readonly category_order: CommandCategory[] = [ + "References", + "Wiki Articles", + "Thread Control", + "Utility", + "Misc", + "Moderation", + "Moderation Utilities", + "Admin utilities", + ]; + override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("help", EarlyReplyMode.none) + .set_category("Misc") .set_description("Bot help and info") .set_handler(this.help.bind(this)), ); } - command_info(...commands: string[]) { - return commands.map(command => this.wheatley.get_command(command).get_command_info()); + private process_command_for_help( + command: BotTextBasedCommand, + categories_map: Map, + seen_command_groups: Set, + command_name_to_aliases: Map, + user_permissions: Discord.PermissionsBitField, + ): void { + if (command.alias_of) { + return; + } + + if ( + command.permissions !== undefined && + command.permissions !== 0n && + !user_permissions.has(command.permissions) + ) { + return; + } + + const category = command.category ?? "Misc"; + if (category === "Hidden") { + return; + } + + const group_key = command.all_display_names.join(","); + if (seen_command_groups.has(group_key)) { + return; + } + seen_command_groups.add(group_key); + + if (!categories_map.has(category)) { + categories_map.set(category, []); + } + + const aliases = command_name_to_aliases.get(command.display_name) ?? []; + unwrap(categories_map.get(category)).push({ + info: command.get_command_info(), + aliases, + }); } - async help(command: TextBasedCommand) { - const embeds = [ - new Discord.EmbedBuilder() - .setColor(colors.wheatley) - .setTitle("Wheatley") - .setDescription( - build_description( - "Wheatley discord bot for the Together C & C++ server. The bot is open source, contributions " + - "are welcome at https://github.com/TCCPP/wheatley.", - ), - ) - .setThumbnail("https://avatars.githubusercontent.com/u/142943210") - .addFields( - { - name: "Wiki Articles", - value: build_description( - ...this.command_info("wiki", "wiki-preview"), - "Article shortcuts: " + - (unwrap(this.wheatley.components.get("Wiki")) as Wiki).article_aliases - .map((_, alias) => `\`${alias}\``) - .join(", "), - "Article contributions are welcome [here](https://github.com/TCCPP/wiki)!", - ), - }, - { - name: "References", - value: build_description(...this.command_info("cppref", "cref", "man")), - }, - { - name: "Thread Control", - value: build_description(...this.command_info("solved", "unsolved", "archive", "rename")), - }, - { - name: "Utility", - value: build_description( - "`!f ` Format the message being replied to", - ...this.command_info( - "quote", - "quoteb", - "snowflake", - "inspect", - "nodistractions", - "removenodistractions", - ), - ), - }, - { - name: "Misc", - value: build_description(...this.command_info("ping", "echo", "r")), - }, - ), - ]; - if (await this.wheatley.check_permissions(command.user, Discord.PermissionFlagsBits.ModerateMembers)) { - embeds.push( - new Discord.EmbedBuilder().setColor(colors.wheatley).addFields( - { - name: "Moderation", - value: build_description( - ...this.command_info( - "ban", - "unban", - "kick", - "mute", - "unmute", - "rolepersist", - "timeout", - "warn", - "reason", - "duration", - "expunge", - "modlogs", - "case", - ), - "Rolepersist aliases: `noofftopic`, `nosuggestions`, `nosuggestionsatall`, " + - "`noreactions`, `nothreads`, `noseriousofftopic`, `notil`, `nomemes`. " + - `Syntax: \`${this.wheatley - .get_command("noofftopic") - .get_usage() - .replace("noofftopic", "(alias)")}\``, - "Durations: `perm` for permanent or `number unit` (whitespace ignored)." + - " Units are y, M, w, d, h, m, s.", - ), - }, - { - name: "Moderation utilities", - value: build_description(...this.command_info("redirect", "purge")), - }, - ), + private build_commands_map( + user_permissions: Discord.PermissionsBitField, + ): Map { + const all_commands = this.wheatley.get_all_commands(); + const categories_map = new Map(); + const seen_command_groups = new Set(); + const command_name_to_aliases = new Map(); + + // First pass: collect all aliases + for (const command_descriptor of Object.values(all_commands)) { + if (command_descriptor.alias_of) { + if (!command_name_to_aliases.has(command_descriptor.alias_of)) { + command_name_to_aliases.set(command_descriptor.alias_of, []); + } + unwrap(command_name_to_aliases.get(command_descriptor.alias_of)).push( + `\`!${command_descriptor.display_name}\``, + ); + } + } + + // Sort aliases alphabetically + for (const aliases of command_name_to_aliases.values()) { + aliases.sort((a, b) => a.localeCompare(b)); + } + + // Second pass: build command map with aliases + for (const command_descriptor of Object.values(all_commands)) { + // If this command has subcommands, process them instead of the parent + if (command_descriptor.subcommands) { + for (const subcommand of command_descriptor.subcommands.values()) { + this.process_command_for_help( + subcommand, + categories_map, + seen_command_groups, + command_name_to_aliases, + user_permissions, + ); + } + continue; + } + + this.process_command_for_help( + command_descriptor, + categories_map, + seen_command_groups, + command_name_to_aliases, + user_permissions, + ); + } + + return categories_map; + } + + private add_category_specific_content(category: CommandCategory, value_parts: string[]): void { + if (category === "Wiki Articles") { + value_parts.push("Article contributions are welcome [here](https://github.com/TCCPP/wiki)!"); + } else if (category === "Utility") { + value_parts.unshift("`!f ` Format the message being replied to"); + } else if (category === "Moderation") { + value_parts.push( + "Durations: `perm` for permanent or `number unit` (whitespace ignored). Units are y, M, w, d, h, m, s.", ); } + } + + private split_into_fields( + category: CommandCategory, + value_parts: string[], + max_length: number = 1024, + ): Discord.APIEmbedField[] { + const fields: Discord.APIEmbedField[] = []; + let current_parts: string[] = []; + let current_length = 0; + + for (const part of value_parts) { + const part_length = part.length + 1; + if (current_length + part_length > max_length && current_parts.length > 0) { + fields.push({ + name: fields.length === 0 ? category : `${category} (cont.)`, + value: build_description(...current_parts), + }); + current_parts = []; + current_length = 0; + } + current_parts.push(part); + current_length += part_length; + } + + if (current_parts.length > 0) { + fields.push({ + name: fields.length === 0 ? category : `${category} (cont.)`, + value: build_description(...current_parts), + }); + } + + return fields; + } + + private build_category_fields( + categories_map: Map, + ): Discord.APIEmbedField[] { + const fields: Discord.APIEmbedField[] = []; + + const all_categories = [ + ...Help.category_order, + ...Array.from(categories_map.keys()).filter(cat => !Help.category_order.includes(cat)), + ]; + + for (const category of all_categories) { + if (categories_map.has(category)) { + const commands = unwrap(categories_map.get(category)); + commands.sort((a, b) => a.info.localeCompare(b.info)); + const value_parts: string[] = []; + + for (const command of commands) { + value_parts.push(command.info); + if (command.aliases.length > 0) { + value_parts.push(`- Shortcuts: ${command.aliases.join(", ")}`); + } + } + + this.add_category_specific_content(category, value_parts); + + fields.push(...this.split_into_fields(category, value_parts)); + } + } + + return fields; + } + + private build_help_embeds(fields: Discord.APIEmbedField[]): Discord.EmbedBuilder[] { + const embed = new Discord.EmbedBuilder() + .setColor(colors.wheatley) + .setTitle("Wheatley") + .setDescription( + build_description( + "Wheatley discord bot for the Together C & C++ server. The bot is open source, contributions " + + "are welcome at https://github.com/TCCPP/wheatley.", + ), + ) + .setThumbnail("https://avatars.githubusercontent.com/u/142943210") + .addFields(...fields); + + return [embed]; + } + + async help(command: TextBasedCommand) { + const member = await this.wheatley.guild.members.fetch(command.user.id); + const user_permissions = member.permissions; + + const categories_map = this.build_commands_map(user_permissions); + const fields = this.build_category_fields(categories_map); + const embeds = this.build_help_embeds(fields); + await command.reply({ embeds, ephemeral_if_possible: true, diff --git a/src/components/inspect.ts b/src/components/inspect.ts index 3a280156..014e01d9 100644 --- a/src/components/inspect.ts +++ b/src/components/inspect.ts @@ -88,6 +88,7 @@ export default class Inspect extends BotComponent { // Permissions on this command in the interest of preventing spam (intentional or otherwise) commands.add( new TextBasedCommandBuilder("inspect", EarlyReplyMode.ephemeral) + .set_category("Utility") .set_description("Inspect a message") .add_string_option({ title: "url", diff --git a/src/components/members.ts b/src/components/members.ts index c3292b53..7069ab4f 100644 --- a/src/components/members.ts +++ b/src/components/members.ts @@ -18,6 +18,7 @@ export default class Members extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("members", EarlyReplyMode.none) + .set_category("Misc") .set_description("Members") .set_handler(this.members.bind(this)), ); diff --git a/src/components/moderation/ban.ts b/src/components/moderation/ban.ts index 42dc5278..ffa6032a 100644 --- a/src/components/moderation/ban.ts +++ b/src/components/moderation/ban.ts @@ -26,6 +26,7 @@ export default class Ban extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("ban", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Ban user") .add_user_option({ @@ -52,6 +53,7 @@ export default class Ban extends ModerationComponent { commands.add( new TextBasedCommandBuilder("massban", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Ban users") .set_slash(false) @@ -83,6 +85,7 @@ export default class Ban extends ModerationComponent { commands.add( new TextBasedCommandBuilder("unban", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Unban user") .add_user_option({ diff --git a/src/components/moderation/kick.ts b/src/components/moderation/kick.ts index e14f15e4..68abafdb 100644 --- a/src/components/moderation/kick.ts +++ b/src/components/moderation/kick.ts @@ -30,6 +30,7 @@ export default class Kick extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("kick", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.KickMembers) .set_description("Kick user") .add_user_option({ diff --git a/src/components/moderation/moderation-control.ts b/src/components/moderation/moderation-control.ts index 8d6d7c01..3a1185a3 100644 --- a/src/components/moderation/moderation-control.ts +++ b/src/components/moderation/moderation-control.ts @@ -27,6 +27,7 @@ export default class ModerationControl extends BotComponent { this.public_action_log = await this.utilities.get_channel(this.wheatley.channels.public_action_log); commands.add( new TextBasedCommandBuilder("reason", EarlyReplyMode.visible) + .set_category("Moderation") .set_description("Update the reason for a case") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .add_number_option({ @@ -44,6 +45,7 @@ export default class ModerationControl extends BotComponent { commands.add( new TextBasedCommandBuilder("context", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Update case context") .add_subcommand( @@ -65,6 +67,7 @@ export default class ModerationControl extends BotComponent { commands.add( new TextBasedCommandBuilder("duration", EarlyReplyMode.visible) + .set_category("Moderation") .set_description("Update the duration for a case") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .add_number_option({ @@ -82,6 +85,7 @@ export default class ModerationControl extends BotComponent { commands.add( new TextBasedCommandBuilder("expunge", EarlyReplyMode.visible) + .set_category("Moderation") .set_description("Expunge a case") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .add_number_option({ diff --git a/src/components/moderation/modlogs.ts b/src/components/moderation/modlogs.ts index 08d18c8f..083b464c 100644 --- a/src/components/moderation/modlogs.ts +++ b/src/components/moderation/modlogs.ts @@ -30,6 +30,7 @@ export default class Modlogs extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("modlogs", EarlyReplyMode.visible) + .set_category("Moderation") .set_description("Get user moderation logs") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .add_user_option({ @@ -42,6 +43,7 @@ export default class Modlogs extends BotComponent { commands.add( new TextBasedCommandBuilder("case", EarlyReplyMode.visible) + .set_category("Moderation") .set_description("Get case info") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .add_number_option({ diff --git a/src/components/moderation/modmail.ts b/src/components/moderation/modmail.ts index c96e23a7..d1830e08 100644 --- a/src/components/moderation/modmail.ts +++ b/src/components/moderation/modmail.ts @@ -63,6 +63,7 @@ export default class Modmail extends BotComponent { this.staff_member_log = await this.utilities.get_channel(this.wheatley.channels.staff_member_log); commands.add( new TextBasedCommandBuilder("wsetupmodmailsystem", EarlyReplyMode.none) + .set_category("Admin utilities") .set_permissions(Discord.PermissionFlagsBits.Administrator) .set_description("Create modmail message here") .set_slash(false) @@ -70,6 +71,7 @@ export default class Modmail extends BotComponent { ); commands.add( new TextBasedCommandBuilder("wupdatemodmailsystem", EarlyReplyMode.none) + .set_category("Admin utilities") .set_permissions(Discord.PermissionFlagsBits.Administrator) .set_description("Update modmail message") .set_slash(false) diff --git a/src/components/moderation/modstats.ts b/src/components/moderation/modstats.ts index 6e55f711..1c91b4ba 100644 --- a/src/components/moderation/modstats.ts +++ b/src/components/moderation/modstats.ts @@ -22,6 +22,7 @@ export default class ModStats extends BotComponent { this.bot_spam = await this.utilities.get_channel(this.wheatley.channels.bot_spam); commands.add( new TextBasedCommandBuilder("modstats", EarlyReplyMode.none) + .set_category("Moderation") .set_description("Moderator stats") .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .add_user_option({ diff --git a/src/components/moderation/mute.ts b/src/components/moderation/mute.ts index 50e3dced..95488a9a 100644 --- a/src/components/moderation/mute.ts +++ b/src/components/moderation/mute.ts @@ -28,6 +28,7 @@ export default class Mute extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("mute", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Mute user") .add_user_option({ @@ -54,6 +55,7 @@ export default class Mute extends ModerationComponent { commands.add( new TextBasedCommandBuilder("unmute", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Unmute user") .add_user_option({ diff --git a/src/components/moderation/note.ts b/src/components/moderation/note.ts index 68f4e1c5..e0c15642 100644 --- a/src/components/moderation/note.ts +++ b/src/components/moderation/note.ts @@ -29,6 +29,7 @@ export default class Note extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("note", EarlyReplyMode.ephemeral) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Enter note in modlogs") .add_user_option({ diff --git a/src/components/moderation/purge.ts b/src/components/moderation/purge.ts index ef290ddb..5969a2fb 100644 --- a/src/components/moderation/purge.ts +++ b/src/components/moderation/purge.ts @@ -59,6 +59,7 @@ export default class Purge extends BotComponent { commands.add( new TextBasedCommandBuilder("purge", EarlyReplyMode.visible) + .set_category("Moderation Utilities") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Purge messages") .add_subcommand( @@ -151,6 +152,7 @@ export default class Purge extends BotComponent { commands.add( new TextBasedCommandBuilder("dummymessages", EarlyReplyMode.visible) + .set_category("Misc") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("dummymessages") .add_number_option({ diff --git a/src/components/moderation/rolepersist.ts b/src/components/moderation/rolepersist.ts index e0f63567..eb237f18 100644 --- a/src/components/moderation/rolepersist.ts +++ b/src/components/moderation/rolepersist.ts @@ -28,6 +28,7 @@ export default class Rolepersist extends ModerationComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("rolepersist", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Rolepersist add/remove") .add_subcommand( @@ -113,6 +114,8 @@ export default class Rolepersist extends ModerationComponent { for (const [command, role] of Object.entries(aliases)) { commands.add( new TextBasedCommandBuilder(command, EarlyReplyMode.visible) + .set_category("Moderation") + .set_alias_of("rolepersist add") .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description(`${capitalize(role).replace("_", " ")}`) .add_user_option({ diff --git a/src/components/moderation/timeout.ts b/src/components/moderation/timeout.ts index efa63d78..c4a81b3a 100644 --- a/src/components/moderation/timeout.ts +++ b/src/components/moderation/timeout.ts @@ -26,6 +26,7 @@ export default class Timeout extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("timeout", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Timeout add / remove") .add_subcommand( diff --git a/src/components/moderation/voice-deputies.ts b/src/components/moderation/voice-deputies.ts index 56806264..55770d2d 100644 --- a/src/components/moderation/voice-deputies.ts +++ b/src/components/moderation/voice-deputies.ts @@ -20,6 +20,7 @@ export default class VoiceDeputies extends BotComponent { this.voice_hotline = await this.utilities.get_channel(this.wheatley.channels.voice_hotline); commands.add( new TextBasedCommandBuilder("voice", EarlyReplyMode.ephemeral) + .set_category("Misc") .set_permissions(Discord.PermissionFlagsBits.MoveMembers | Discord.PermissionFlagsBits.MuteMembers) .set_description("Voice moderation") .add_subcommand( diff --git a/src/components/moderation/warn.ts b/src/components/moderation/warn.ts index 2dedbd61..64b9ad3d 100644 --- a/src/components/moderation/warn.ts +++ b/src/components/moderation/warn.ts @@ -27,6 +27,7 @@ export default class Warn extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("warn", EarlyReplyMode.visible) + .set_category("Moderation") .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Warn user") .add_user_option({ diff --git a/src/components/nodistractions.ts b/src/components/nodistractions.ts index 89f0bac8..78cc9d27 100644 --- a/src/components/nodistractions.ts +++ b/src/components/nodistractions.ts @@ -78,6 +78,7 @@ export default class Nodistractions extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("nodistractions", EarlyReplyMode.ephemeral) + .set_category("Utility") .set_description("Turns on nodistractions") .add_string_option({ title: "time", @@ -89,6 +90,7 @@ export default class Nodistractions extends BotComponent { commands.add( new TextBasedCommandBuilder("removenodistractions", EarlyReplyMode.ephemeral) + .set_category("Utility") .set_description("Removes nodistractions") .set_handler(this.removenodistractions.bind(this)), ); diff --git a/src/components/ping.ts b/src/components/ping.ts index d4ab82e8..f9aacdf5 100644 --- a/src/components/ping.ts +++ b/src/components/ping.ts @@ -18,6 +18,7 @@ export default class Ping extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder(["ping", "wstatus"], EarlyReplyMode.none) + .set_category("Misc") .set_description("Ping") .set_handler(this.ping.bind(this)), ); diff --git a/src/components/quote.ts b/src/components/quote.ts index 00a8d184..d3bd3f7c 100644 --- a/src/components/quote.ts +++ b/src/components/quote.ts @@ -40,6 +40,7 @@ export default class Quote extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder(["quote", "quoteb"], EarlyReplyMode.none) + .set_category("Utility") .set_description(["Quote a message", "Quote a block of messages"]) .add_string_option({ title: "url", diff --git a/src/components/redirect.ts b/src/components/redirect.ts index 19064999..a8336a9d 100644 --- a/src/components/redirect.ts +++ b/src/components/redirect.ts @@ -14,6 +14,7 @@ export default class Redirect extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("redirect", EarlyReplyMode.visible) + .set_category("Moderation Utilities") .set_description("Redirect a conversation") .add_string_option({ title: "channel", @@ -26,6 +27,7 @@ export default class Redirect extends BotComponent { commands.add( new TextBasedCommandBuilder("r", EarlyReplyMode.none) + .set_category("Moderation Utilities") .set_description( `Redirect a conversation from <#${this.wheatley.channels.c_cpp_discussion}> or ` + `<#${this.wheatley.channels.general_discussion}> to a help channel`, diff --git a/src/components/restart.ts b/src/components/restart.ts index 33b7f262..23d4fe01 100644 --- a/src/components/restart.ts +++ b/src/components/restart.ts @@ -18,6 +18,7 @@ export default class Restart extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("restart", EarlyReplyMode.none) + .set_category("Admin utilities") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Restart") .set_handler(this.restart.bind(this)), diff --git a/src/components/role-manager.ts b/src/components/role-manager.ts index c26e824f..f260266d 100644 --- a/src/components/role-manager.ts +++ b/src/components/role-manager.ts @@ -48,6 +48,7 @@ export default class RoleManager extends BotComponent { commands.add( new TextBasedCommandBuilder("gimmepink", EarlyReplyMode.ephemeral) + .set_category("Misc") .set_description("Gives pink") .set_slash(false) .set_handler(this.gibpink.bind(this)), @@ -55,6 +56,7 @@ export default class RoleManager extends BotComponent { commands.add( new TextBasedCommandBuilder("unpink", EarlyReplyMode.ephemeral) + .set_category("Misc") .set_description("Takes pink") .set_slash(false) .set_handler(this.unpink.bind(this)), diff --git a/src/components/shortcuts.ts b/src/components/shortcuts.ts index ff520063..8fb1b9fa 100644 --- a/src/components/shortcuts.ts +++ b/src/components/shortcuts.ts @@ -19,6 +19,7 @@ export default class Shortcuts extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("nothingtoseehere", EarlyReplyMode.none) + .set_category("Misc") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Nothing to see here") .set_handler(this.nothingtoseehere.bind(this)), @@ -26,18 +27,21 @@ export default class Shortcuts extends BotComponent { commands.add( new TextBasedCommandBuilder(["tryitandsee", "tias"], EarlyReplyMode.none) + .set_category("Misc") .set_description("Try it and see") .set_handler(this.tryitandsee.bind(this)), ); commands.add( new TextBasedCommandBuilder("headbang", EarlyReplyMode.none) + .set_category("Misc") .set_description("Headbang") .set_handler(this.headbang.bind(this)), ); commands.add( new TextBasedCommandBuilder("initgif", EarlyReplyMode.none) + .set_category("Misc") .set_description("initgif") .set_handler(this.initgif.bind(this)), ); diff --git a/src/components/snowflake.ts b/src/components/snowflake.ts index d319ec4d..c3cf0913 100644 --- a/src/components/snowflake.ts +++ b/src/components/snowflake.ts @@ -17,6 +17,7 @@ export default class Snowflake extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("snowflake", EarlyReplyMode.none) + .set_category("Utility") .set_description("Snowflake") .add_string_option({ title: "input", diff --git a/src/components/steal.ts b/src/components/steal.ts index f9f9134b..25ccc480 100644 --- a/src/components/steal.ts +++ b/src/components/steal.ts @@ -20,6 +20,7 @@ export default class Steal extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("steal-emojis-message-url", EarlyReplyMode.visible) + .set_category("Admin utilities") .set_description("Steal emojis from a message") .add_string_option({ title: "url", @@ -32,6 +33,7 @@ export default class Steal extends BotComponent { commands.add( new TextBasedCommandBuilder("add-emojis-url", EarlyReplyMode.visible) + .set_category("Admin utilities") .set_description("Add emojis from a message") .add_string_option({ title: "name", @@ -49,6 +51,7 @@ export default class Steal extends BotComponent { commands.add( new TextBasedCommandBuilder("steal-emojis", EarlyReplyMode.visible) + .set_category("Admin utilities") .set_description("Steal emojis from a message") .add_string_option({ title: "text", diff --git a/src/components/thread-control.ts b/src/components/thread-control.ts index 4c75d643..bdb12365 100644 --- a/src/components/thread-control.ts +++ b/src/components/thread-control.ts @@ -15,12 +15,14 @@ export default class ThreadControl extends BotComponent { this.rules = await this.utilities.get_channel(this.wheatley.channels.rules); commands.add( new TextBasedCommandBuilder("archive", EarlyReplyMode.ephemeral) + .set_category("Thread Control") .set_description("Archives the thread") .set_handler(this.archive.bind(this)), ); commands.add( new TextBasedCommandBuilder("rename", EarlyReplyMode.ephemeral) + .set_category("Thread Control") .set_description("Rename the thread") .add_string_option({ title: "name", diff --git a/src/components/topic.ts b/src/components/topic.ts index d636587c..cb009e14 100644 --- a/src/components/topic.ts +++ b/src/components/topic.ts @@ -19,6 +19,7 @@ export default class Topic extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("topic", EarlyReplyMode.none) + .set_category("Misc") .set_description("Posts the channel topic") .set_handler(this.topic.bind(this)), ); diff --git a/src/components/wiki.ts b/src/components/wiki.ts index 7bc6fcad..9ff96c30 100644 --- a/src/components/wiki.ts +++ b/src/components/wiki.ts @@ -350,6 +350,7 @@ export default class Wiki extends BotComponent { commands.add( new TextBasedCommandBuilder(["wiki", "howto"], EarlyReplyMode.none) + .set_category("Wiki Articles") .set_description(["Retrieve wiki articles", "Retrieve wiki articles (alternatively /wiki)"]) .add_string_option({ title: "query", @@ -372,8 +373,9 @@ export default class Wiki extends BotComponent { commands.add( new TextBasedCommandBuilder(["wiki-preview", "wp"], EarlyReplyMode.none) + .set_category("Wiki Articles") .set_slash(false) - .set_description("Preview a wiki article") + .set_description("Preview wiki article markdown") .add_string_option({ title: "content", description: "Content", @@ -388,6 +390,8 @@ export default class Wiki extends BotComponent { const article = this.articles[article_name]; commands.add( new TextBasedCommandBuilder(alias, EarlyReplyMode.none) + .set_category("Wiki Articles") + .set_alias_of("wiki") .set_slash(false) .set_description(article.title) .add_user_option({ diff --git a/src/modules/tccpp/components/c-help-redirect.ts b/src/modules/tccpp/components/c-help-redirect.ts index ee094613..739c5a3d 100644 --- a/src/modules/tccpp/components/c-help-redirect.ts +++ b/src/modules/tccpp/components/c-help-redirect.ts @@ -75,6 +75,7 @@ export default class CHelpRedirect extends BotComponent { commands.add( new TextBasedCommandBuilder("not-c", EarlyReplyMode.none) + .set_category("Misc") .set_description("Mark C++ code in the C help channel") .add_user_option({ title: "user", @@ -86,6 +87,7 @@ export default class CHelpRedirect extends BotComponent { commands.add( new TextBasedCommandBuilder("not-cpp", EarlyReplyMode.none) + .set_category("Misc") .set_description("Mark C code in the C++ help channel") .add_user_option({ title: "user", diff --git a/src/modules/tccpp/components/core-guidelines.ts b/src/modules/tccpp/components/core-guidelines.ts index 56543f48..c4cf0119 100644 --- a/src/modules/tccpp/components/core-guidelines.ts +++ b/src/modules/tccpp/components/core-guidelines.ts @@ -63,6 +63,7 @@ export default class CoreGuidelines extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("guide", EarlyReplyMode.none) + .set_category("References") .set_description("Query C++ Core Guidelines") .add_string_option({ title: "query", diff --git a/src/modules/tccpp/components/cppref.ts b/src/modules/tccpp/components/cppref.ts index 7446ab29..401672de 100644 --- a/src/modules/tccpp/components/cppref.ts +++ b/src/modules/tccpp/components/cppref.ts @@ -156,6 +156,7 @@ export default class Cppref extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder(["cref", "cppref"], EarlyReplyMode.none) + .set_category("References") .set_description(["Query C reference pages", "Query C++ reference pages"]) .add_string_option({ title: "query", diff --git a/src/modules/tccpp/components/forum-control.ts b/src/modules/tccpp/components/forum-control.ts index b233b782..7a4570e5 100644 --- a/src/modules/tccpp/components/forum-control.ts +++ b/src/modules/tccpp/components/forum-control.ts @@ -28,12 +28,14 @@ export default class ForumControl extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder(["solve", "solved", "close"], EarlyReplyMode.visible) + .set_category("Thread Control") .set_description("Close forum post and mark it as solved") .set_handler(this.solve.bind(this)), ); commands.add( new TextBasedCommandBuilder(["unsolve", "unsolved", "open"], EarlyReplyMode.visible) + .set_category("Thread Control") .set_description("Re-open forum post") .set_handler(this.unsolve.bind(this)), ); diff --git a/src/modules/tccpp/components/man7.ts b/src/modules/tccpp/components/man7.ts index b61fa0f7..f017a1c5 100644 --- a/src/modules/tccpp/components/man7.ts +++ b/src/modules/tccpp/components/man7.ts @@ -97,6 +97,7 @@ export default class Man7 extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("man", EarlyReplyMode.none) + .set_category("References") .set_description("Query linux man pages") .add_string_option({ title: "query", diff --git a/src/modules/tccpp/components/roulette.ts b/src/modules/tccpp/components/roulette.ts index 8453284b..f94f1841 100644 --- a/src/modules/tccpp/components/roulette.ts +++ b/src/modules/tccpp/components/roulette.ts @@ -34,12 +34,14 @@ export default class Roulette extends BotComponent { commands.add( new TextBasedCommandBuilder("roulette", EarlyReplyMode.none) + .set_category("Misc") .set_description("roulette") .set_handler(this.roulette.bind(this)), ); commands.add( new TextBasedCommandBuilder("leaderboard", EarlyReplyMode.visible) + .set_category("Misc") .set_description("Leaderboard") .set_handler(this.leaderboard.bind(this)), ); diff --git a/src/modules/tccpp/components/server-suggestion-tracker.ts b/src/modules/tccpp/components/server-suggestion-tracker.ts index 6b4c04f5..33d29ab0 100644 --- a/src/modules/tccpp/components/server-suggestion-tracker.ts +++ b/src/modules/tccpp/components/server-suggestion-tracker.ts @@ -62,6 +62,7 @@ export default class ServerSuggestionTracker extends BotComponent { this.server_suggestions = await this.utilities.get_channel(this.wheatley.channels.server_suggestions); commands.add( new TextBasedCommandBuilder("suggestions-dashboard-count", EarlyReplyMode.visible) + .set_category("Admin utilities") .set_description("Server suggestions count") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_handler(this.dashboard_count.bind(this)), diff --git a/src/modules/tccpp/components/skill-role-suggestion.ts b/src/modules/tccpp/components/skill-role-suggestion.ts index bb88a9d0..46e108cf 100644 --- a/src/modules/tccpp/components/skill-role-suggestion.ts +++ b/src/modules/tccpp/components/skill-role-suggestion.ts @@ -65,6 +65,7 @@ export default class SkillRoleSuggestion extends BotComponent { commands.add( new TextBasedCommandBuilder("close-skill-suggestion-thread", EarlyReplyMode.ephemeral) + .set_category("Admin utilities") .set_description("Closes a skill role suggestions thread") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_handler(this.close_thread.bind(this)), diff --git a/src/modules/tccpp/components/starboard.ts b/src/modules/tccpp/components/starboard.ts index 2683a4dc..2c6e73b0 100644 --- a/src/modules/tccpp/components/starboard.ts +++ b/src/modules/tccpp/components/starboard.ts @@ -91,6 +91,7 @@ export default class Starboard extends BotComponent { commands.add( new TextBasedCommandBuilder("add-negative-emoji", EarlyReplyMode.visible) + .set_category("Admin utilities") .set_description("Register a negative emoji") .add_string_option({ title: "emojis", @@ -103,6 +104,7 @@ export default class Starboard extends BotComponent { commands.add( new TextBasedCommandBuilder("add-delete-emoji", EarlyReplyMode.visible) + .set_category("Admin utilities") .set_description("Register a delete emoji") .add_string_option({ title: "emojis", @@ -115,6 +117,7 @@ export default class Starboard extends BotComponent { commands.add( new TextBasedCommandBuilder("add-ignored-emoji", EarlyReplyMode.visible) + .set_category("Admin utilities") .set_description("Register an ignored emoji") .add_string_option({ title: "emojis", @@ -127,6 +130,7 @@ export default class Starboard extends BotComponent { commands.add( new TextBasedCommandBuilder("add-repost-emoji", EarlyReplyMode.visible) + .set_category("Admin utilities") .set_description("Register a repost emoji") .add_string_option({ title: "emojis", @@ -139,6 +143,7 @@ export default class Starboard extends BotComponent { commands.add( new TextBasedCommandBuilder("list-starboard-config", EarlyReplyMode.visible) + .set_category("Admin utilities") .set_description("List starboard config") .set_permissions(Discord.PermissionFlagsBits.Administrator) .set_handler(this.list_config.bind(this)), diff --git a/src/modules/tccpp/components/the-button.ts b/src/modules/tccpp/components/the-button.ts index 795d3253..e68a22ab 100644 --- a/src/modules/tccpp/components/the-button.ts +++ b/src/modules/tccpp/components/the-button.ts @@ -87,6 +87,7 @@ export default class TheButton extends BotComponent { commands.add( new TextBasedCommandBuilder("wsetupthebutton", EarlyReplyMode.none) + .set_category("Admin utilities") .set_permissions(Discord.PermissionFlagsBits.Administrator) .set_description("Setup The Button here") .set_slash(false) @@ -94,6 +95,7 @@ export default class TheButton extends BotComponent { ); commands.add( new TextBasedCommandBuilder("wresetthebutton", EarlyReplyMode.none) + .set_category("Admin utilities") .set_permissions(Discord.PermissionFlagsBits.Administrator) .set_description("Reset The Button") .set_slash(false) @@ -101,6 +103,7 @@ export default class TheButton extends BotComponent { ); commands.add( new TextBasedCommandBuilder("wresetthebuttonscoreboard", EarlyReplyMode.none) + .set_category("Admin utilities") .set_permissions(Discord.PermissionFlagsBits.Administrator) .set_description("Reset The Button scoreboard") .set_slash(false) @@ -108,6 +111,7 @@ export default class TheButton extends BotComponent { ); commands.add( new TextBasedCommandBuilder("wadjustscores", EarlyReplyMode.none) + .set_category("Admin utilities") .set_permissions(Discord.PermissionFlagsBits.Administrator) .set_description("Adjust The Button scores") .set_slash(false) diff --git a/src/modules/tccpp/components/utility-tools.ts b/src/modules/tccpp/components/utility-tools.ts index f69925f8..6777781a 100644 --- a/src/modules/tccpp/components/utility-tools.ts +++ b/src/modules/tccpp/components/utility-tools.ts @@ -16,6 +16,7 @@ export default class UtilityTools extends BotComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("count", EarlyReplyMode.none) + .set_category("Misc") .set_permissions(Discord.PermissionFlagsBits.BanMembers) .set_description("Count") .add_number_option({ diff --git a/src/wheatley.ts b/src/wheatley.ts index 99db89df..72bb32d3 100644 --- a/src/wheatley.ts +++ b/src/wheatley.ts @@ -684,6 +684,10 @@ export class Wheatley { return this.command_handler.get_command(command); } + get_all_commands() { + return this.command_handler.get_all_commands(); + } + register_non_command_bot_reply(trigger: Discord.Message, message: Discord.Message) { this.command_handler.register_non_command_bot_reply(trigger, message); }