diff --git a/readme.md b/readme.md index a9f52d9..234f83b 100644 --- a/readme.md +++ b/readme.md @@ -80,6 +80,8 @@ $ npm start - `help` gives the feature list of this bot +- `help ` gives the description of the given feature of this bot + - `leaderboard` gives the links to the speedrun leaderboards of the game on `www.speedrun.com` - `mission` gives the schedule of the next three missions in the shop diff --git a/src/commands/help.ts b/src/commands/help.ts index 7d5b4aa..e75398a 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,16 +1,19 @@ import type { + ApplicationCommandOptionChoiceData, ApplicationCommandPermissions, AutoModerationAction, AutoModerationActionMetadata, AutoModerationRule, + AutocompleteFocusedOption, + AutocompleteInteraction, ChatInputCommandInteraction, Client, ClientApplication, - Collection, GuildBasedChannel, GuildMember, NewsChannel, Role, + Snowflake, TextChannel, Webhook, } from "discord.js"; @@ -23,8 +26,10 @@ import type Hook from "../hooks.js"; import type Rule from "../rules.js"; import type {Locale, Localized} from "../utils/string.js"; import { + ApplicationCommandOptionType, ApplicationCommandPermissionType, ApplicationCommandType, + Collection, PermissionsBitField, } from "discord.js"; import * as commands from "../commands.js"; @@ -32,16 +37,54 @@ import {help as helpCompilation} from "../compilations.js"; import {help as helpDefinition} from "../definitions.js"; import * as hooks from "../hooks.js"; import * as rules from "../rules.js"; -import {composeAll, list, localize, resolve} from "../utils/string.js"; +import {composeAll, list, localize, nearest, resolve} from "../utils/string.js"; type HelpGroups = HelpDependency["help"]; +type Feature = { + id: number, + type: "command" | "hook" | "rule", + name: string, +}; const { commandName, commandDescription, + featureOptionName, + featureOptionDescription, }: HelpDefinition = helpDefinition; const { help: helpLocalizations, reply: replyLocalizations, + bareReply: bareReplyLocalizations, + noFeatureReply: noFeatureReplyLocalizations, }: HelpCompilation = helpCompilation; +const commandCount: number = Object.getOwnPropertyNames(commands).length; +const hookCount: number = Object.getOwnPropertyNames(hooks).length; +const features: Feature[] = [ + ...Object.getOwnPropertyNames(commands).map((commandName: string, commandIndex: number): Feature => { + return { + id: commandIndex, + type: "command", + name: commandName, + }; + }), + ...Object.getOwnPropertyNames(hooks).map((hookName: string, hookIndex: number): Feature => { + return { + id: commandCount + hookIndex, + type: "hook", + name: hookName, + }; + }), + ...Object.getOwnPropertyNames(rules).map((ruleName: string, ruleIndex: number): Feature => { + return { + id: commandCount + hookCount + ruleIndex, + type: "rule", + name: ruleName, + }; + }), +]; +const guildFetchedTimestamps: Collection = new Collection(); +const guildApplicationCommandPermissions: Collection | undefined> = new Collection | undefined>(); +const guildWebhooks: Collection | undefined> = new Collection | undefined>(); +const guildAutoModerationRules: Collection | undefined> = new Collection | undefined>(); function naiveStream(content: string): string[] { content = content.replace(/^\n+|\n+$/g, "").replace(/\n+/g, "\n"); if (content.length === 0) { @@ -183,151 +226,466 @@ const helpCommand: Command = { name: commandName, description: commandDescription["en-US"], descriptionLocalizations: commandDescription, + options: [ + { + type: ApplicationCommandOptionType.Integer, + name: featureOptionName, + description: featureOptionDescription["en-US"], + descriptionLocalizations: featureOptionDescription, + minValue: 0, + maxValue: features.length - 1, + autocomplete: true, + }, + ], }; }, async interact(interaction: ApplicationUserInteraction): Promise { - if (!interaction.isChatInputCommand()) { - return; - } - const {client, guild, locale, member}: ChatInputCommandInteraction<"cached"> = interaction; - const resolvedLocale: Locale = resolve(locale); - const channel: GuildBasedChannel | null = ((): GuildBasedChannel | null => { - const {channel}: ChatInputCommandInteraction<"cached"> = interaction; - if (channel == null) { - return null; - } - if (channel.isThread()) { - return channel.parent; - } - return channel; - })(); - if (channel == null) { - return; + const {client, createdTimestamp, member, guild}: ApplicationUserInteraction = interaction; + const fetchedTimestamp: number = guildFetchedTimestamps.get(guild.id) ?? Number.NEGATIVE_INFINITY; + const elapsedTime: number = createdTimestamp - fetchedTimestamp; + const forceFetch: boolean = elapsedTime >= 60000; + if (forceFetch) { + guildFetchedTimestamps.set(guild.id, createdTimestamp); } const applicationCommands: Collection = guild.commands.cache; const permissions: Collection | undefined = await (async (): Promise | undefined> => { - try { - return await guild.commands.permissions.fetch({}); - } catch {} + if (forceFetch) { + try { + guildApplicationCommandPermissions.set(guild.id, await guild.commands.permissions.fetch({})); + } catch { + guildApplicationCommandPermissions.delete(guild.id); + } + } + return guildApplicationCommandPermissions.get(guild.id); })(); if (permissions == null) { return; } const webhooks: Collection | undefined = await (async (): Promise | undefined> => { - try { - return await guild.fetchWebhooks(); - } catch {} + if (forceFetch) { + try { + guildWebhooks.set(guild.id, await guild.fetchWebhooks()); + } catch { + guildWebhooks.delete(guild.id); + } + } + return guildWebhooks.get(guild.id); })(); if (webhooks == null) { return; } const autoModerationRules: Collection | undefined = await (async (): Promise | undefined> => { - try { - return await guild.autoModerationRules.fetch(); - } catch {} + if (forceFetch) { + try { + guildAutoModerationRules.set(guild.id, await guild.autoModerationRules.fetch()); + } catch { + guildAutoModerationRules.delete(guild.id); + } + } + return guildAutoModerationRules.get(guild.id); })(); if (autoModerationRules == null) { return; } + const channel: GuildBasedChannel | null = ((): GuildBasedChannel | null => { + const {channel}: ApplicationUserInteraction = interaction; + if (channel == null) { + return null; + } + if (channel.isThread()) { + return channel.parent; + } + return channel; + })(); + if (channel == null) { + return; + } const {user}: Client = client; - const descriptions: Localized<(groups: {}) => string>[] = [ - Object.keys(commands).map string> | null>((commandName: string): Localized<(groups: {}) => string> | null => { - const command: Command = commands[commandName as keyof typeof commands]; - const applicationCommand: ApplicationCommand | undefined = applicationCommands.find((applicationCommand: ApplicationCommand): boolean => { - return applicationCommand.name === commandName; - }); - if (applicationCommand == null) { - return null; - } - if (applicationCommand.guild == null) { - return null; - } - if (applicationCommand.type !== ApplicationCommandType.ChatInput && applicationCommand.type !== ApplicationCommandType.Message) { - return null; - } - if (applicationCommand.applicationId !== user.id) { - return null; - } - if (!hasPermission(permissions, applicationCommand.client.application, applicationCommand, channel, member)) { - return null; - } - const description: Localized<(groups: {}) => string> | null = command.describe(applicationCommand); - return description; - }), - Object.keys(hooks).map string> | null>((hookName: string): Localized<(groups: {}) => string> | null => { - const hook: Hook = hooks[hookName as keyof typeof hooks]; - const webhook: Webhook | undefined = webhooks.find((webhook: Webhook): boolean => { - return webhook.name === hookName; - }); - if (webhook == null) { - return null; - } - if (!webhook.isIncoming()) { - return null; - } - const {channel, owner}: Webhook = webhook; - if (owner == null || owner.id !== user.id) { - return null; - } - if (channel == null || !hasManageWebhooksPermission(channel, member)) { - return null; - } - const description: Localized<(groups: {}) => string> | null = hook.describe(webhook); - return description; - }), - Object.keys(rules).map string> | null>((ruleName: string): Localized<(groups: {}) => string> | null => { - const rule: Rule = rules[ruleName as keyof typeof rules]; - const autoModerationRule: AutoModerationRule | undefined = autoModerationRules.find((autoModerationRule: AutoModerationRule): boolean => { - return autoModerationRule.name === ruleName; - }); - if (autoModerationRule == null) { - return null; + if (interaction.isAutocomplete()) { + const {locale, options}: AutocompleteInteraction<"cached"> = interaction; + const resolvedLocale: Locale = resolve(locale); + const {name, value}: AutocompleteFocusedOption = options.getFocused(true); + if (name !== featureOptionName) { + await interaction.respond([]); + return; + } + const results: Feature[] = nearest(value.toLocaleLowerCase(resolvedLocale), features, 7, (feature: Feature): string => { + const {name}: Feature = feature; + return name.toLocaleLowerCase(resolvedLocale); + }); + const suggestions: ApplicationCommandOptionChoiceData[] = results.map | null>((feature: Feature): ApplicationCommandOptionChoiceData | null => { + const {id, type, name}: Feature = feature; + const featureName: string = `${name} (${type})`; + switch (type) { + case "command": { + const commandName: string = name; + const applicationCommand: ApplicationCommand | undefined = applicationCommands.find((applicationCommand: ApplicationCommand): boolean => { + return applicationCommand.name === commandName; + }); + if (applicationCommand == null) { + return null; + } + if (applicationCommand.guild == null) { + return null; + } + if (applicationCommand.type !== ApplicationCommandType.ChatInput && applicationCommand.type !== ApplicationCommandType.Message) { + return null; + } + if (applicationCommand.applicationId !== user.id) { + return null; + } + if (!hasPermission(permissions, applicationCommand.client.application, applicationCommand, channel, member)) { + return null; + } + break; + } + case "hook": { + const hookName: string = name; + const webhook: Webhook | undefined = webhooks.find((webhook: Webhook): boolean => { + return webhook.name === hookName; + }); + if (webhook == null) { + return null; + } + if (!webhook.isIncoming()) { + return null; + } + const {channel, owner}: Webhook = webhook; + if (owner == null || owner.id !== user.id) { + return null; + } + if (channel == null || !hasManageWebhooksPermission(channel, member)) { + return null; + } + break; + } + case "rule": { + const ruleName: string = feature.name; + const autoModerationRule: AutoModerationRule | undefined = autoModerationRules.find((autoModerationRule: AutoModerationRule): boolean => { + return autoModerationRule.name === ruleName; + }); + if (autoModerationRule == null) { + return null; + } + if (!autoModerationRule.enabled) { + return null; + } + if (autoModerationRule.creatorId !== user.id) { + return null; + } + const channels: (TextChannel | NewsChannel)[] = autoModerationRule.actions.map((action: AutoModerationAction): TextChannel | NewsChannel | null => { + const {metadata}: AutoModerationAction = action; + const {channelId}: AutoModerationActionMetadata = metadata; + if (channelId == null) { + return null; + } + const channel: GuildBasedChannel | undefined = autoModerationRule.guild.channels.cache.get(channelId); + if (channel == null || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { + return null; + } + return channel; + }).filter((channel: TextChannel | NewsChannel | null): channel is TextChannel | NewsChannel => { + return channel != null; + }); + if (channels.length === 0 || channels.some((channel: TextChannel | NewsChannel): boolean => { + return !hasManageAutoModerationRulesPermission(channel, member); + })) { + return null; + } + break; + } } - if (!autoModerationRule.enabled) { - return null; + return { + name: featureName, + value: id, + }; + }).filter>((suggestion: ApplicationCommandOptionChoiceData | null): suggestion is ApplicationCommandOptionChoiceData => { + return suggestion != null; + }); + await interaction.respond(suggestions); + return; + } + if (!interaction.isChatInputCommand()) { + return; + } + const {locale, options}: ChatInputCommandInteraction<"cached"> = interaction; + const resolvedLocale: Locale = resolve(locale); + const id: number | null = options.getInteger(featureOptionName); + if (id == null) { + const descriptions: Localized<(groups: {}) => string>[] = features.map string> | null>((feature: Feature): Localized<(groups: {}) => string> | null => { + switch (feature.type) { + case "command": { + const commandName: string = feature.name; + const command: Command = commands[commandName as keyof typeof commands]; + const applicationCommand: ApplicationCommand | undefined = applicationCommands.find((applicationCommand: ApplicationCommand): boolean => { + return applicationCommand.name === commandName; + }); + if (applicationCommand == null) { + return null; + } + if (applicationCommand.guild == null) { + return null; + } + if (applicationCommand.type !== ApplicationCommandType.ChatInput && applicationCommand.type !== ApplicationCommandType.Message) { + return null; + } + if (applicationCommand.applicationId !== user.id) { + return null; + } + if (!hasPermission(permissions, applicationCommand.client.application, applicationCommand, channel, member)) { + return null; + } + const description: Localized<(groups: {}) => string> | null = command.describe(applicationCommand); + return description; } - if (autoModerationRule.creatorId !== user.id) { - return null; + case "hook": { + const hookName: string = feature.name; + const hook: Hook = hooks[hookName as keyof typeof hooks]; + const webhook: Webhook | undefined = webhooks.find((webhook: Webhook): boolean => { + return webhook.name === hookName; + }); + if (webhook == null) { + return null; + } + if (!webhook.isIncoming()) { + return null; + } + const {channel, owner}: Webhook = webhook; + if (owner == null || owner.id !== user.id) { + return null; + } + if (channel == null || !hasManageWebhooksPermission(channel, member)) { + return null; + } + const description: Localized<(groups: {}) => string> | null = hook.describe(webhook); + return description; } - const channels: (TextChannel | NewsChannel)[] = autoModerationRule.actions.map((action: AutoModerationAction): TextChannel | NewsChannel | null => { - const {metadata}: AutoModerationAction = action; - const {channelId}: AutoModerationActionMetadata = metadata; - if (channelId == null) { + case "rule": { + const ruleName: string = feature.name; + const rule: Rule = rules[ruleName as keyof typeof rules]; + const autoModerationRule: AutoModerationRule | undefined = autoModerationRules.find((autoModerationRule: AutoModerationRule): boolean => { + return autoModerationRule.name === ruleName; + }); + if (autoModerationRule == null) { return null; } - const channel: GuildBasedChannel | undefined = autoModerationRule.guild.channels.cache.get(channelId); - if (channel == null || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { + if (!autoModerationRule.enabled) { return null; } - return channel; - }).filter((channel: TextChannel | NewsChannel | null): channel is TextChannel | NewsChannel => { - return channel != null; - }); - if (channels.length === 0 || channels.some((channel: TextChannel | NewsChannel): boolean => { - return !hasManageAutoModerationRulesPermission(channel, member); - })) { - return null; + if (autoModerationRule.creatorId !== user.id) { + return null; + } + const channels: (TextChannel | NewsChannel)[] = autoModerationRule.actions.map((action: AutoModerationAction): TextChannel | NewsChannel | null => { + const {metadata}: AutoModerationAction = action; + const {channelId}: AutoModerationActionMetadata = metadata; + if (channelId == null) { + return null; + } + const channel: GuildBasedChannel | undefined = autoModerationRule.guild.channels.cache.get(channelId); + if (channel == null || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { + return null; + } + return channel; + }).filter((channel: TextChannel | NewsChannel | null): channel is TextChannel | NewsChannel => { + return channel != null; + }); + if (channels.length === 0 || channels.some((channel: TextChannel | NewsChannel): boolean => { + return !hasManageAutoModerationRulesPermission(channel, member); + })) { + return null; + } + const description: Localized<(groups: {}) => string> = rule.describe(autoModerationRule); + return description; } - const description: Localized<(groups: {}) => string> = rule.describe(autoModerationRule); - return description; - }), - ].flat<(Localized<(groups: {}) => string> | null)[][]>().filter string>>((description: Localized<(groups: {}) => string> | null): description is Localized<(groups: {}) => string> => { + } + }).filter string>>((description: Localized<(groups: {}) => string> | null): description is Localized<(groups: {}) => string> => { return description != null; }); - const features: Localized<(groups: {}) => string[]> = localize<(groups: {}) => string[]>((locale: Locale): (groups: {}) => string[] => { + const subfeatures: Localized<(groups: {}) => string[]> = localize<(groups: {}) => string[]>((locale: Locale): (groups: {}) => string[] => { return (groups: {}): string[] => { return descriptions.map((description: Localized<(groups: {}) => string>): string[] => { return description[locale](groups).split("\n"); }).flat(); }; }); + function formatMessage(locale: Locale): string { + return bareReplyLocalizations[locale]({ + memberMention: (): string => { + return `<@${member.id}>`; + }, + featureList: (): string => { + return list(subfeatures[locale]({})); + }, + }); + } + const persistentContent: string = formatMessage("en-US"); + const persistentContentChunks: string[] = naiveStream(persistentContent); + let replied: boolean = false; + for (const chunk of persistentContentChunks) { + if (!replied) { + await interaction.reply({ + content: chunk, + allowedMentions: { + users: [], + }, + }); + replied = true; + continue; + } + await interaction.followUp({ + content: chunk, + allowedMentions: { + users: [], + }, + }); + } + if (resolvedLocale === "en-US") { + return; + } + const ephemeralContent: string = formatMessage(resolvedLocale); + const ephemeralContentChunks: string[] = naiveStream(ephemeralContent); + for (const chunk of ephemeralContentChunks) { + if (!replied) { + await interaction.reply({ + content: chunk, + ephemeral: true, + allowedMentions: { + users: [], + }, + }); + replied = true; + continue; + } + await interaction.followUp({ + content: chunk, + ephemeral: true, + allowedMentions: { + users: [], + }, + }); + } + return; + } + const description: Localized<(groups: {}) => string> | null = ((): Localized<(groups: {}) => string> | null => { + const feature: Feature = features[id]; + switch (feature.type) { + case "command": { + const commandName: string = feature.name; + const command: Command = commands[commandName as keyof typeof commands]; + const applicationCommand: ApplicationCommand | undefined = applicationCommands.find((applicationCommand: ApplicationCommand): boolean => { + return applicationCommand.name === commandName; + }); + if (applicationCommand == null) { + return null; + } + if (applicationCommand.guild == null) { + return null; + } + if (applicationCommand.type !== ApplicationCommandType.ChatInput && applicationCommand.type !== ApplicationCommandType.Message) { + return null; + } + if (applicationCommand.applicationId !== user.id) { + return null; + } + if (!hasPermission(permissions, applicationCommand.client.application, applicationCommand, channel, member)) { + return null; + } + const description: Localized<(groups: {}) => string> | null = command.describe(applicationCommand); + return description; + } + case "hook": { + const hookName: string = feature.name; + const hook: Hook = hooks[hookName as keyof typeof hooks]; + const webhook: Webhook | undefined = webhooks.find((webhook: Webhook): boolean => { + return webhook.name === hookName; + }); + if (webhook == null) { + return null; + } + if (!webhook.isIncoming()) { + return null; + } + const {channel, owner}: Webhook = webhook; + if (owner == null || owner.id !== user.id) { + return null; + } + if (channel == null || !hasManageWebhooksPermission(channel, member)) { + return null; + } + const description: Localized<(groups: {}) => string> | null = hook.describe(webhook); + return description; + } + case "rule": { + const ruleName: string = feature.name; + const rule: Rule = rules[ruleName as keyof typeof rules]; + const autoModerationRule: AutoModerationRule | undefined = autoModerationRules.find((autoModerationRule: AutoModerationRule): boolean => { + return autoModerationRule.name === ruleName; + }); + if (autoModerationRule == null) { + return null; + } + if (!autoModerationRule.enabled) { + return null; + } + if (autoModerationRule.creatorId !== user.id) { + return null; + } + const channels: (TextChannel | NewsChannel)[] = autoModerationRule.actions.map((action: AutoModerationAction): TextChannel | NewsChannel | null => { + const {metadata}: AutoModerationAction = action; + const {channelId}: AutoModerationActionMetadata = metadata; + if (channelId == null) { + return null; + } + const channel: GuildBasedChannel | undefined = autoModerationRule.guild.channels.cache.get(channelId); + if (channel == null || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { + return null; + } + return channel; + }).filter((channel: TextChannel | NewsChannel | null): channel is TextChannel | NewsChannel => { + return channel != null; + }); + if (channels.length === 0 || channels.some((channel: TextChannel | NewsChannel): boolean => { + return !hasManageAutoModerationRulesPermission(channel, member); + })) { + return null; + } + const description: Localized<(groups: {}) => string> = rule.describe(autoModerationRule); + return description; + } + } + })(); + if (description == null) { + function formatMessage(locale: Locale): string { + return noFeatureReplyLocalizations[locale]({ + memberMention: (): string => { + return `<@${member.id}>`; + }, + }); + } + await interaction.reply({ + content: formatMessage("en-US"), + }); + if (resolvedLocale === "en-US") { + return; + } + await interaction.followUp({ + content: formatMessage(resolvedLocale), + ephemeral: true, + }); + return; + } + const subfeatures: Localized<(groups: {}) => string[]> = localize<(groups: {}) => string[]>((locale: Locale): (groups: {}) => string[] => { + return (groups: {}): string[] => { + return description[locale](groups).split("\n"); + }; + }); function formatMessage(locale: Locale): string { return replyLocalizations[locale]({ memberMention: (): string => { return `<@${member.id}>`; }, featureList: (): string => { - return list(features[locale]({})); + return list(subfeatures[locale]({})); }, }); } @@ -379,11 +737,14 @@ const helpCommand: Command = { } }, describe(applicationCommand: ApplicationCommand): Localized<(groups: {}) => string> { - return composeAll(helpLocalizations, localize((): HelpGroups => { + return composeAll(helpLocalizations, localize((locale: Locale): HelpGroups => { return { commandMention: (): string => { return ``; }, + featureOptionDescription: (): string => { + return featureOptionDescription[locale]; + }, }; })); }, diff --git a/src/commands/raw.ts b/src/commands/raw.ts index 047f6d5..6ddd125 100644 --- a/src/commands/raw.ts +++ b/src/commands/raw.ts @@ -44,7 +44,7 @@ const rawCommand: Command = { description: typeOptionDescription["en-US"], descriptionLocalizations: typeOptionDescription, required: true, - choices: Object.keys(bindings).map<[string, Binding]>((bindingName: string): [string, Binding] => { + choices: Object.getOwnPropertyNames(bindings).map<[string, Binding]>((bindingName: string): [string, Binding] => { const binding: Binding = bindings[bindingName as keyof typeof bindings]; return [bindingName, binding]; }).filter(([bindingName, binding]: [string, Binding]): boolean => { @@ -82,7 +82,7 @@ const rawCommand: Command = { await interaction.reply({ content: noTypeReplyLocalizations[resolvedLocale]({ typeConjunction: (): string => { - return conjunctionFormat.format(Object.keys(bindings).map((bindingName: string): string => { + return conjunctionFormat.format(Object.getOwnPropertyNames(bindings).map((bindingName: string): string => { return `\`${escapeMarkdown(bindingName)}\``; })); }, diff --git a/src/compilations/help.ts b/src/compilations/help.ts index f3ddf8d..ada918e 100644 --- a/src/compilations/help.ts +++ b/src/compilations/help.ts @@ -4,14 +4,22 @@ import {help} from "../definitions.js"; import {compileAll} from "../utils/string.js"; type HelpLocalizations = Localized<(groups: Help["help"]) => string>; type ReplyLocalizations = Localized<(groups: Help["reply"]) => string>; +type BareReplyLocalizations = Localized<(groups: Help["bareReply"]) => string>; +type NoFeatureReplyLocalizations = Localized<(groups: Help["noFeatureReply"]) => string>; type HelpCompilation = { help: HelpLocalizations, reply: ReplyLocalizations, + bareReply: BareReplyLocalizations, + noFeatureReply: NoFeatureReplyLocalizations, }; const helpLocalizations: HelpLocalizations = compileAll(help["help"]); const replyLocalizations: ReplyLocalizations = compileAll(help["reply"]); +const bareReplyLocalizations: BareReplyLocalizations = compileAll(help["bareReply"]); +const noFeatureReplyLocalizations: NoFeatureReplyLocalizations = compileAll(help["noFeatureReply"]); const helpCompilation: HelpCompilation = { help: helpLocalizations, reply: replyLocalizations, + bareReply: bareReplyLocalizations, + noFeatureReply: noFeatureReplyLocalizations, }; export default helpCompilation; diff --git a/src/definitions.ts b/src/definitions.ts index e6d8926..4f4ddbb 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -163,8 +163,12 @@ type Gate = { type Help = { commandName: string, commandDescription: Localized, + featureOptionName: string, + featureOptionDescription: Localized, help: Localized, reply: Localized, + bareReply: Localized, + noFeatureReply: Localized, }; type Leaderboard = { commandName: string, diff --git a/src/definitions/help.json b/src/definitions/help.json index 53bc177..a2b16e5 100644 --- a/src/definitions/help.json +++ b/src/definitions/help.json @@ -1,18 +1,34 @@ { "commandName": "help", "commandDescription": { - "en-US": "Tells you what are the features I offer", - "fr": "Te dit quelles sont les fonctionnalités que je propose", - "pt-BR": "Te diz quais recursos eu apresento" + "en-US": "Tells you what are the features I offer or what they do", + "fr": "Te dit quelles sont les fonctionnalités que je propose ou ce qu'elles font", + "pt-BR": "Te diz quais recursos eu apresento ou o que eles fazem" + }, + "featureOptionName": "feature", + "featureOptionDescription": { + "en-US": "Some feature", + "fr": "Une fonctionnalité", + "pt-BR": "Algum recurso" }, "help": { - "en-US": "Type $ to know what are the features I offer", - "fr": "Tape $ pour savoir quelles sont les fonctionnalités que je propose", - "pt-BR": "Digite $ pra saber quais são os recursos que apresento" + "en-US": "Type $ to know what are the features I offer\nType $ `$` to know what `$` does", + "fr": "Tape $ pour savoir quelles sont les fonctionnalités que je propose\nTape $ `$` pour savoir ce qu'`$` fait", + "pt-BR": "Digite $ pra saber quais são os recursos que apresento\nDigite $ `$` pra saber o que `$` faz" }, "reply": { + "en-US": "Hey $, there you are!\nI can give you some advice about this feature:\n$", + "fr": "Ah $, tu es là !\nJe peux te donner des conseils sur cette fonctionnalité :\n$", + "pt-BR": "Opa $, aí está você!\nEu posso te dar algumas dicas sobre esse recurso:\n$" + }, + "bareReply": { "en-US": "Hey $, there you are!\nI can give you some advice about the server:\n$", "fr": "Ah $, tu es là !\nJe peux te donner des conseils sur le serveur :\n$", "pt-BR": "Opa $, aí está você!\nEu posso te dar algumas dicas sobre o servidor:\n$" + }, + "noFeatureReply": { + "en-US": "Hey $, there you are!\nI can not give you any advice about this feature.", + "fr": "Ah $, tu es là !\nJe ne peux te donner aucun conseil sur cette fonctionnalité.", + "pt-BR": "Opa $, aí está você!\nEu não posso te dar nenhuma dica sobre esse recurso." } } diff --git a/src/dependencies/help.d.ts b/src/dependencies/help.d.ts index 7779171..079d033 100644 --- a/src/dependencies/help.d.ts +++ b/src/dependencies/help.d.ts @@ -1,12 +1,22 @@ type HelpGroups = { commandMention: () => string, + featureOptionDescription: () => string, }; type ReplyGroups = { memberMention: () => string, featureList: () => string, }; +type BareReplyGroups = { + memberMention: () => string, + featureList: () => string, +}; +type NoFeatureReplyGroups = { + memberMention: () => string, +}; type HelpDependency = { help: HelpGroups, reply: ReplyGroups, + bareReply: BareReplyGroups, + noFeatureReply: NoFeatureReplyGroups, }; export type {HelpDependency as default}; diff --git a/src/shicka.ts b/src/shicka.ts index 894a8c1..0cd7f17 100644 --- a/src/shicka.ts +++ b/src/shicka.ts @@ -263,7 +263,7 @@ const client: Client = new Client({ }, }); client.once("ready", async (client: Client): Promise => { - const commandRegistry: ApplicationCommandData[] = Object.keys(commands).map((commandName: string): ApplicationCommandData => { + const commandRegistry: ApplicationCommandData[] = Object.getOwnPropertyNames(commands).map((commandName: string): ApplicationCommandData => { const command: Command = commands[commandName as keyof typeof commands]; return command.register(); }); @@ -277,7 +277,7 @@ client.once("ready", async (client: Client): Promise => { console.error(error); } } - const hookRegistry: WebhookCreateOptionsResolvable[] = Object.keys(hooks).map((hookName: string): WebhookData => { + const hookRegistry: WebhookCreateOptionsResolvable[] = Object.getOwnPropertyNames(hooks).map((hookName: string): WebhookData => { const hook: Hook = hooks[hookName as keyof typeof hooks]; return hook.register(); }).map((webhookData: WebhookData): WebhookCreateOptionsResolvable => { @@ -382,7 +382,7 @@ client.once("ready", async (client: Client): Promise => { console.error(error); } } - const ruleRegistry: AutoModerationRuleCreateOptionsResolvable[] = Object.keys(rules).map((ruleName: string): AutoModerationRuleCreateOptionsResolvable => { + const ruleRegistry: AutoModerationRuleCreateOptionsResolvable[] = Object.getOwnPropertyNames(rules).map((ruleName: string): AutoModerationRuleCreateOptionsResolvable => { const rule: Rule = rules[ruleName as keyof typeof rules]; return rule.register(); });