feat: Add context commands
This commit is contained in:
parent
03635e748b
commit
f83a6dd1a3
3 changed files with 70 additions and 14 deletions
|
|
@ -1,9 +1,16 @@
|
||||||
import { AutocompleteFocusedOption, AutocompleteInteraction, ChatInputCommandInteraction, SharedSlashCommand } from "discord.js";
|
import { AutocompleteFocusedOption, AutocompleteInteraction, ChatInputCommandInteraction, ContextMenuCommandBuilder, ContextMenuCommandInteraction, SharedSlashCommand, Snowflake, User } from "discord.js";
|
||||||
import { Config } from "./config";
|
import { Config } from "./config";
|
||||||
|
|
||||||
export abstract class Command {
|
export abstract class ICommand { }
|
||||||
|
|
||||||
|
export abstract class ContextCommand extends ICommand {
|
||||||
|
abstract contextDefinition: ContextMenuCommandBuilder
|
||||||
|
abstract run(interaction: ContextMenuCommandInteraction, target: Snowflake): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Command extends ICommand {
|
||||||
abstract run(interaction: ChatInputCommandInteraction, config: Config): Promise<void>;
|
abstract run(interaction: ChatInputCommandInteraction, config: Config): Promise<void>;
|
||||||
autoComplete(interaction: AutocompleteInteraction, config: Config, option: AutocompleteFocusedOption) : Promise<void> {
|
autoComplete(interaction: AutocompleteInteraction, config: Config, option: AutocompleteFocusedOption): Promise<void> {
|
||||||
throw new Error("Autocompletion called on command that does not have #autoComplete implemented.");
|
throw new Error("Autocompletion called on command that does not have #autoComplete implemented.");
|
||||||
}
|
}
|
||||||
abstract slashCommand: SharedSlashCommand
|
abstract slashCommand: SharedSlashCommand
|
||||||
|
|
|
||||||
14
src/commands/botsex.ts
Normal file
14
src/commands/botsex.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { ApplicationCommandType, ContextMenuCommandBuilder, ContextMenuCommandInteraction, InteractionContextType, Snowflake } from "discord.js";
|
||||||
|
import { ContextCommand } from "../command.ts";
|
||||||
|
|
||||||
|
export default class RailUser extends ContextCommand {
|
||||||
|
contextDefinition: ContextMenuCommandBuilder =
|
||||||
|
new ContextMenuCommandBuilder()
|
||||||
|
.setName('rail')
|
||||||
|
.setType(ApplicationCommandType.User)
|
||||||
|
async run(interaction: ContextMenuCommandInteraction, target: Snowflake): Promise<void> {
|
||||||
|
await interaction.reply(`Raililng <@${target}>.`)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
await interaction.editReply(`UHGhghgghghgh. Railing successfull.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/index.ts
57
src/index.ts
|
|
@ -3,12 +3,15 @@ import {
|
||||||
Events,
|
Events,
|
||||||
GatewayIntentBits,
|
GatewayIntentBits,
|
||||||
InteractionCallback,
|
InteractionCallback,
|
||||||
|
InteractionContextType,
|
||||||
REST,
|
REST,
|
||||||
|
RestOrArray,
|
||||||
Routes,
|
Routes,
|
||||||
|
SlashCommandBooleanOption,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { Command } from "./command.ts";
|
import { Command, ContextCommand, ICommand } from "./command.ts";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import { config } from "./config.ts";
|
import { config } from "./config.ts";
|
||||||
|
|
||||||
|
|
@ -19,28 +22,60 @@ const client = new Client({
|
||||||
intents: [],
|
intents: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const commands: Command[] = []
|
const allCommands: ICommand[] = []
|
||||||
|
|
||||||
|
|
||||||
const commandDir = path.join(__dirname, "commands");
|
const commandDir = path.join(__dirname, "commands");
|
||||||
for (const file of fs.readdirSync(commandDir)) {
|
for (const file of fs.readdirSync(commandDir)) {
|
||||||
if (!file.endsWith('.ts')) continue
|
if (!file.endsWith('.ts')) continue
|
||||||
let command = await import(path.join(commandDir, file));
|
let command = await import(path.join(commandDir, file));
|
||||||
commands.push(new command.default())
|
let instance
|
||||||
|
try {
|
||||||
|
instance = new command.default()
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Could not instantiate command from ${file}`, { cause: e })
|
||||||
|
}
|
||||||
|
if (!(instance instanceof ICommand))
|
||||||
|
throw `${instance} is not an ICommand instance (imported from ${file})`;
|
||||||
|
allCommands.push(instance)
|
||||||
}
|
}
|
||||||
|
const commands = allCommands.filter(it => it instanceof Command);
|
||||||
|
const contextCommands = allCommands.filter(it => it instanceof ContextCommand);
|
||||||
|
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]))
|
||||||
|
|
||||||
|
function makeDefaultAvailableEverywhere<T extends { setContexts(...contexts: Array<InteractionContextType>): any, readonly contexts?: InteractionContextType[] }>(t: T): T {
|
||||||
|
if (!t.contexts)
|
||||||
|
t.setContexts(InteractionContextType.BotDM, InteractionContextType.Guild, InteractionContextType.PrivateChannel)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
client.once(Events.ClientReady, async () => {
|
client.once(Events.ClientReady, async () => {
|
||||||
console.log("Ready");
|
console.log("Ready");
|
||||||
const rest = new REST().setToken(config.token);
|
const rest = new REST().setToken(config.token);
|
||||||
const data = await rest.put(
|
const data = await client.application!.commands.set(
|
||||||
Routes.applicationCommands(client.user!.id), { body: commands.map(command => command.slashCommand.toJSON()) },
|
[
|
||||||
);
|
...commands.map(it => it.slashCommand),
|
||||||
|
...contextCommands.map(it => it.contextDefinition)
|
||||||
|
].map(makeDefaultAvailableEverywhere)
|
||||||
|
)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
console.log(`Successfully reloaded ${data.size} application (/) commands.`);
|
||||||
})
|
})
|
||||||
|
client.on(Events.InteractionCreate, async (interaction) => {
|
||||||
|
if (!interaction.isContextMenuCommand()) return;
|
||||||
|
const { commandName } = interaction
|
||||||
|
const command = contextCommandLUT[commandName]
|
||||||
|
if (!command) {
|
||||||
|
console.error("unknown context command: " + commandName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await command.run(interaction, interaction.targetId)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("error during context command execution: " + commandName, e)
|
||||||
|
interaction.reply("something sharted itself")
|
||||||
|
}
|
||||||
|
});
|
||||||
client.on(Events.InteractionCreate, async (interaction) => {
|
client.on(Events.InteractionCreate, async (interaction) => {
|
||||||
if (!interaction.isChatInputCommand()) return;
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
|
||||||
|
|
@ -63,7 +98,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
|
||||||
|
|
||||||
client.on(Events.InteractionCreate, async (interaction) => {
|
client.on(Events.InteractionCreate, async (interaction) => {
|
||||||
if (!interaction.isAutocomplete()) return
|
if (!interaction.isAutocomplete()) return
|
||||||
const {commandName} = interaction;
|
const { commandName } = interaction;
|
||||||
|
|
||||||
const command = commandLookup[commandName]
|
const command = commandLookup[commandName]
|
||||||
if (!command) {
|
if (!command) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue