feat: Thoroughly type ContextCommand

This commit is contained in:
Linnea Gräf 2025-04-09 18:55:03 +02:00
parent f83a6dd1a3
commit c1670b8abd
No known key found for this signature in database
GPG key ID: AA563E93EB628D91
3 changed files with 20 additions and 8 deletions

View file

@ -1,11 +1,15 @@
import { AutocompleteFocusedOption, AutocompleteInteraction, ChatInputCommandInteraction, ContextMenuCommandBuilder, ContextMenuCommandInteraction, SharedSlashCommand, Snowflake, User } from "discord.js"; import { ApplicationCommandType, AutocompleteFocusedOption, AutocompleteInteraction, ChatInputCommandInteraction, ContextMenuCommandBuilder, ContextMenuCommandInteraction, Message, SharedSlashCommand, Snowflake, User } from "discord.js";
import { Config } from "./config"; import { Config } from "./config";
export abstract class ICommand { } export abstract class ICommand { }
export abstract class ContextCommand extends ICommand { export abstract class ContextCommand<T extends User | Message> extends ICommand {
abstract targetType:
T extends User ? ApplicationCommandType.User :
T extends Message ? ApplicationCommandType.Message :
never;
abstract contextDefinition: ContextMenuCommandBuilder abstract contextDefinition: ContextMenuCommandBuilder
abstract run(interaction: ContextMenuCommandInteraction, target: Snowflake): Promise<void> abstract run(interaction: ContextMenuCommandInteraction, target: T extends User ? User : T extends Message ? Message : never): Promise<void>
} }
export abstract class Command extends ICommand { export abstract class Command extends ICommand {

View file

@ -1,13 +1,14 @@
import { ApplicationCommandType, ContextMenuCommandBuilder, ContextMenuCommandInteraction, InteractionContextType, Snowflake } from "discord.js"; import { ApplicationCommandType, ContextMenuCommandBuilder, ContextMenuCommandInteraction, InteractionContextType, Snowflake, User } from "discord.js";
import { ContextCommand } from "../command.ts"; import { ContextCommand } from "../command.ts";
export default class RailUser extends ContextCommand { export default class RailUser extends ContextCommand<User> {
targetType: ApplicationCommandType.User = ApplicationCommandType.User;
contextDefinition: ContextMenuCommandBuilder = contextDefinition: ContextMenuCommandBuilder =
new ContextMenuCommandBuilder() new ContextMenuCommandBuilder()
.setName('rail') .setName('rail')
.setType(ApplicationCommandType.User) .setType(ApplicationCommandType.User)
async run(interaction: ContextMenuCommandInteraction, target: Snowflake): Promise<void> { async run(interaction: ContextMenuCommandInteraction, target: User): Promise<void> {
await interaction.reply(`Raililng <@${target}>.`) await interaction.reply(`Raililng <@${target.id}>.`)
await new Promise(resolve => setTimeout(resolve, 1000)) await new Promise(resolve => setTimeout(resolve, 1000))
await interaction.editReply(`UHGhghgghghgh. Railing successfull.`) await interaction.editReply(`UHGhghgghghgh. Railing successfull.`)
} }

View file

@ -1,4 +1,6 @@
import { import {
ApplicationCommand,
ApplicationCommandType,
Client, Client,
Events, Events,
GatewayIntentBits, GatewayIntentBits,
@ -43,6 +45,8 @@ const contextCommands = allCommands.filter(it => it instanceof ContextCommand);
const contextCommandLUT = Object.fromEntries(contextCommands.map(it => [it.contextDefinition.name, it])) const contextCommandLUT = Object.fromEntries(contextCommands.map(it => [it.contextDefinition.name, it]))
const commandLookup = Object.fromEntries(commands.map(it => [it.slashCommand.name, it])) const commandLookup = Object.fromEntries(commands.map(it => [it.slashCommand.name, it]))
contextCommands.forEach(it => it.contextDefinition.type === it.targetType)
function makeDefaultAvailableEverywhere<T extends { setContexts(...contexts: Array<InteractionContextType>): any, readonly contexts?: InteractionContextType[] }>(t: T): T { function makeDefaultAvailableEverywhere<T extends { setContexts(...contexts: Array<InteractionContextType>): any, readonly contexts?: InteractionContextType[] }>(t: T): T {
if (!t.contexts) if (!t.contexts)
t.setContexts(InteractionContextType.BotDM, InteractionContextType.Guild, InteractionContextType.PrivateChannel) t.setContexts(InteractionContextType.BotDM, InteractionContextType.Guild, InteractionContextType.PrivateChannel)
@ -69,8 +73,11 @@ client.on(Events.InteractionCreate, async (interaction) => {
console.error("unknown context command: " + commandName) console.error("unknown context command: " + commandName)
return return
} }
// The <any> cast here is valid, since the type of the interaction is set in accordance to the definition
if (command.targetType != (interaction.isUserContextMenuCommand() ? ApplicationCommandType.User : ApplicationCommandType.Message))
console.error("Out of date discord definition of this context command")
try { try {
await command.run(interaction, interaction.targetId) await command.run(interaction, interaction.isUserContextMenuCommand() ? interaction.targetUser : interaction.targetMessage)
} catch (e) { } catch (e) {
console.error("error during context command execution: " + commandName, e) console.error("error during context command execution: " + commandName, e)
interaction.reply("something sharted itself") interaction.reply("something sharted itself")