diff --git a/src/commands/hilfe.ts b/src/commands/hilfe.ts index 27c6d4a3..ea4ac125 100644 --- a/src/commands/hilfe.ts +++ b/src/commands/hilfe.ts @@ -16,7 +16,7 @@ export default class HilfeCommand implements MessageCommand { const prefix = context.prefix.command; const lines = []; - const newCommands = await commandService.readAvailableCommands(context, ["normal"]); + const newCommands = await commandService.readAvailableCommands(context); for (const command of newCommands) { if (command.modCommand) { continue; diff --git a/src/commands/modcommands/ban.ts b/src/commands/modcommands/ban.ts index 2cce6018..eff63111 100644 --- a/src/commands/modcommands/ban.ts +++ b/src/commands/modcommands/ban.ts @@ -16,6 +16,7 @@ import * as banService from "#service/ban.ts"; import { formatDuration } from "#utils/dateUtils.ts"; export default class BanCommand implements ApplicationCommand, MessageCommand { + modCommand = true; // needed if invoked via text, not via slash name = "ban"; description = "Joa, bannt halt einen ne?"; requiredPermissions: readonly PermissionsString[] = ["BanMembers"]; diff --git a/src/commands/modcommands/ghostwriter.ts b/src/commands/modcommands/ghostwriter.ts index 9acc7523..f7bdc105 100644 --- a/src/commands/modcommands/ghostwriter.ts +++ b/src/commands/modcommands/ghostwriter.ts @@ -10,9 +10,11 @@ import type { ApplicationCommand } from "#commands/command.ts"; import type { BotContext } from "#context.ts"; export default class GhostwriterCommand implements ApplicationCommand { + modCommand = true; name = "gw"; description = "Goethe sein Vater"; requiredPermissions: readonly PermissionsString[] = ["BanMembers"]; + lastBlame: Date | null = null; get applicationCommand() { return new SlashCommandBuilder() diff --git a/src/commands/modcommands/hilfe.ts b/src/commands/modcommands/modhilfe.ts similarity index 80% rename from src/commands/modcommands/hilfe.ts rename to src/commands/modcommands/modhilfe.ts index c178ae03..9b13d99c 100644 --- a/src/commands/modcommands/hilfe.ts +++ b/src/commands/modcommands/modhilfe.ts @@ -1,5 +1,3 @@ -import { ContextMenuCommandBuilder } from "discord.js"; - import type { MessageCommand } from "#commands/command.ts"; import type { BotContext } from "#context.ts"; import type { ProcessableMessage } from "#service/command.ts"; @@ -10,27 +8,19 @@ import * as commandService from "#service/command.ts"; export default class ModHilfeCommand implements MessageCommand { modCommand = true; - name = "hilfe"; + name = "modhilfe"; description = "Listet alle mod-commands auf"; async handleMessage(message: ProcessableMessage, context: BotContext): Promise { - const prefix = context.prefix.command; + const prefix = context.prefix.modCommand; const lines = []; - const newCommands = await commandService.readAvailableCommands(context, ["mod"]); + const newCommands = await commandService.readAvailableCommands(context); for (const command of newCommands) { if (!command.modCommand) { continue; } - // TODO: Hack to exclude faulenzerping and video download - if ( - "applicationCommand" in command && - command.applicationCommand instanceof ContextMenuCommandBuilder - ) { - continue; - } - const commandStr = prefix + command.name; lines.push( `${commandStr}: ${replacePrefixPlaceholders(command.description, context)}\n`, diff --git a/src/commands/modcommands/unban.ts b/src/commands/modcommands/unban.ts index 06f19223..8cf197e1 100644 --- a/src/commands/modcommands/unban.ts +++ b/src/commands/modcommands/unban.ts @@ -12,6 +12,7 @@ import type { BotContext } from "#context.ts"; import * as banService from "#service/ban.ts"; export default class UnbanCommand implements ApplicationCommand, MessageCommand { + modCommand = true; // needed if invoked via text, not via slash name = "unban"; description = "Joa, unbannt halt einen ne?"; requiredPermissions: readonly PermissionsString[] = ["BanMembers"]; diff --git a/src/handler/commandHandler.ts b/src/handler/commandHandler.ts index 68d2146b..e9aa78f5 100644 --- a/src/handler/commandHandler.ts +++ b/src/handler/commandHandler.ts @@ -41,17 +41,14 @@ const allCommands: Command[] = []; const getApplicationCommands = () => allCommands.filter(c => "handleInteraction" in c); const getAutocompleteCommands = () => allCommands.filter(c => "autocomplete" in c); -const getMessageCommands = () => allCommands.filter(c => "handleMessage" in c); const getSpecialCommands = () => allCommands.filter(c => "handleSpecialMessage" in c); +const getMessageCommands = () => allCommands.filter(c => "handleMessage" in c); const latestSpecialCommandInvocations: Record = {}; export const loadCommands = async (context: BotContext): Promise => { - // TODO: This breaks hilfe command; we should distinguish between mod commands and normal commands - const availableCommands = await commandService.readAvailableCommands(context, [ - "mod", - "normal", - ]); + const availableCommands = await commandService.readAvailableCommands(context); + const loadedCommandNames = new Set(staticCommands.map(c => c.name)); const dynamicCommands = []; @@ -188,16 +185,21 @@ const hasPermissions = ( * was found or an error if the command would be a mod command but the * invoking user is not a mod */ -const commandMessageHandler = async ( + +async function commandMessageHandler( commandString: string, message: ProcessableMessage, context: BotContext, -): Promise => { +): Promise { const lowerCommand = commandString.toLowerCase(); - const matchingCommand = getMessageCommands().find( + const command = getMessageCommands().find( cmd => cmd.name.toLowerCase() === lowerCommand || cmd.aliases?.includes(lowerCommand), ); + if (!command) { + return; + } + if ( context.roleGuard.hasBotDenyRole(message.member) && !context.channelGuard.isInBotSpam(message) @@ -208,16 +210,14 @@ const commandMessageHandler = async ( return; } - if (!matchingCommand) { - return; - } - const invoker = message.member; - if (hasPermissions(invoker, matchingCommand.requiredPermissions ?? [])) { + const isNormalCommandOrUserIsMod = !command.modCommand || context.roleGuard.isMod(invoker); + + if (isNormalCommandOrUserIsMod && hasPermissions(invoker, command.requiredPermissions ?? [])) { return await sentry.startSpan( - { name: matchingCommand.name, op: "command.message" }, - async () => await matchingCommand.handleMessage(message, context), + { name: command.name, op: "command.message" }, + async () => await command.handleMessage(message, context), ); } @@ -228,7 +228,7 @@ const commandMessageHandler = async ( content: `Tut mir leid, ${message.author}. Du hast nicht genügend Rechte um dieses Command zu verwenden, dafür gibt's erstmal mit dem Willkürhammer einen auf den Deckel.`, }), ]); -}; +} const isCooledDown = (command: SpecialCommand) => { const now = Date.now(); @@ -289,7 +289,6 @@ export const messageCommandHandler = async ( return; } - // TODO: The Prefix is now completely irrelevant, since the commands itself define their permission. const plebPrefix = context.prefix.command; const modPrefix = context.prefix.modCommand; diff --git a/src/service/command.ts b/src/service/command.ts index 4e58615c..92be3bfa 100644 --- a/src/service/command.ts +++ b/src/service/command.ts @@ -12,25 +12,11 @@ import log from "#log"; const commandExtensions = [".ts", ".js"]; const ignoredExtensions = [".spec.ts", ".test.ts", ".d.ts", ".test.js", ".spec.js"]; -export type CommandKind = "mod" | "normal"; - -export async function readAvailableCommands( - context: BotContext, - kinds: readonly CommandKind[], -): Promise { - const modules = []; - - if (kinds.includes("mod")) { - const modCommands = await loadRawCommandModulesFromDirectory( - context, - context.path.modCommands, - ); - modules.push(...modCommands); - } - if (kinds.includes("normal")) { - const commands = await loadRawCommandModulesFromDirectory(context, context.path.commands); - modules.push(...commands); - } +export async function readAvailableCommands(context: BotContext): Promise { + const modules = [ + ...(await loadRawCommandModulesFromDirectory(context.path.modCommands)), + ...(await loadRawCommandModulesFromDirectory(context.path.commands)), + ]; const res = []; for (const { module } of modules) { @@ -52,7 +38,6 @@ type CommandModuleFile = { }; async function loadRawCommandModulesFromDirectory( - context: BotContext, commandDir: string, ): Promise { const commandFiles = await fs.readdir(commandDir); @@ -67,7 +52,7 @@ async function loadRawCommandModulesFromDirectory( continue; } - const filePath = path.join(context.path.commands, file); + const filePath = path.join(commandDir, file); const moduleUrl = new URL("file://"); moduleUrl.pathname = filePath;