Skip to content
This repository has been archived by the owner on Oct 12, 2024. It is now read-only.

Commit

Permalink
Interactions: Dispatch handling
Browse files Browse the repository at this point in the history
  • Loading branch information
RadiatedExodus committed Feb 14, 2024
1 parent 2a0a722 commit e7dd8e8
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/events/interactionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import type { MeteoriumEvent } from "./eventsEntry.js";

export const Event: MeteoriumEvent<"interactionCreate"> = {
event: "interactionCreate",
async callback(client, interaction) {},
async callback(client, interaction) {
await client.interactions.dispatchInteraction(interaction);
return;
},
once: false,
};
147 changes: 147 additions & 0 deletions src/interactions/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import util from "node:util";

import {
ChatInputCommandInteraction,
AutocompleteInteraction,
Expand All @@ -10,13 +12,18 @@ import {
ApplicationCommandType,
Awaitable,
Collection,
Interaction,
codeBlock,
userMention,
User,
} from "discord.js";
import type MeteoriumClient from "../classes/client.js";
import type { LoggingNamespace } from "../classes/logging.js";

import * as commands from "./commands/index.js";
import * as userContextMenuActions from "./userContextMenuActions/index.js";
import * as messageContextMenuActions from "./messageContextMenuActions/index.js";
import MeteoriumEmbedBuilder from "../classes/embedBuilder.js";

export type MeteoriumChatCommand = {
interactionData: SlashCommandBuilder;
Expand Down Expand Up @@ -113,6 +120,24 @@ export default class MeteoriumInteractionManager {
return;
}

public getInteractionData(type: ApplicationCommandType, name: string) {
const collection =
type == ApplicationCommandType.ChatInput
? this.chatInputInteractions
: type == ApplicationCommandType.User
? this.userContextMenuActionInteractions
: type == ApplicationCommandType.Message
? this.messageContextMenuActionInteractions
: undefined;
if (!collection) throw new Error(`invalid interaction type ${type}`);

for (const [_, data] of collection) {
if (data.interactionData.name == name) return data;
}

return undefined;
}

public generateAppsJsonData() {
const merged: Array<
RESTPostAPIChatInputApplicationCommandsJSONBody | RESTPostAPIContextMenuApplicationCommandsJSONBody
Expand All @@ -127,4 +152,126 @@ export default class MeteoriumInteractionManager {

return merged;
}

private async dispatchInteractionOccurredLog(name: string, type: string, guildId: string, dispatcher: User) {
const guildDb = await this.client.db.guild.findUnique({ where: { GuildId: guildId } });
const channel =
guildDb && guildDb.LoggingChannelId != ""
? await this.client.channels.fetch(guildDb.LoggingChannelId).catch(() => null)
: null;
if (!guildDb) throw new Error(`no guild data for ${guildId}`);
if (!channel || !channel.isTextBased()) return;

const embed = new MeteoriumEmbedBuilder().setTitle("Interaction dispatched").addFields([
{ name: "Dispatcher", value: `${userMention(dispatcher.id)} (${dispatcher.username} - ${dispatcher.id})` },
{ name: "Interaction name", value: name },
{ name: "Interaction type", value: type },
]);

return await channel.send({ embeds: [embed] });
}

public async dispatchInteraction(interaction: Interaction) {
if (!interaction.inCachedGuild()) return;

// Get logging namespace
const dispatchNS = this.loggingNS.getNamespace("Dispatch");

// Interaction type
const interactionType = interaction.isChatInputCommand()
? ApplicationCommandType.ChatInput
: interaction.isAutocomplete()
? ApplicationCommandType.ChatInput
: interaction.isUserContextMenuCommand()
? ApplicationCommandType.User
: interaction.isMessageContextMenuCommand()
? ApplicationCommandType.Message
: undefined;
const interactionName =
interaction.isCommand() || interaction.isAutocomplete() ? interaction.commandName : undefined;
const isAutocomplete = interaction.isAutocomplete();
if (!interactionType || !interactionName) return;

// Get interaction data
const data = this.getInteractionData(interactionType, interactionName);
if (!data)
return dispatchNS.error(
`could not find interaction data for ${interactionName}? ignoring this interaction (${interaction.id})`,
);

// Logging to guild internal logs
if (!isAutocomplete)
this.dispatchInteractionOccurredLog(
interactionName,
interactionType == ApplicationCommandType.ChatInput
? "Chat command"
: `${interactionType == ApplicationCommandType.User ? "User" : "Message"} context menu action`,
interaction.guildId,
interaction.user,
).catch((e) =>
dispatchNS.warn(
`could not send interaction dispatch log for ${interaction.id} (guild ${interaction.guildId}):\n${util.inspect(e)}`,
),
);

// Execute callback
try {
if (isAutocomplete) {
const dataTyped = data as MeteoriumChatCommand;
if (!dataTyped.autocomplete)
return dispatchNS.error(
`autocomplete interaction running on a command that doesn't have a autocomplete callback set (${interactionName})`,
);
await dataTyped.autocomplete(interaction, this.client);
} else {
switch (interactionType) {
case ApplicationCommandType.ChatInput: {
const dataTyped = data as MeteoriumChatCommand;
await dataTyped.callback(interaction as ChatInputCommandInteraction<"cached">, this.client);
break;
}
case ApplicationCommandType.User: {
const dataTyped = data as MeteoriumUserContextMenuAction;
await dataTyped.callback(
interaction as UserContextMenuCommandInteraction<"cached">,
this.client,
);
break;
}
case ApplicationCommandType.Message: {
const dataTyped = data as MeteoriumMessageContextMenuAction;
await dataTyped.callback(
interaction as MessageContextMenuCommandInteraction<"cached">,
this.client,
);
break;
}
default: {
throw new Error("invalid interaction type");
}
}
}
} catch (e) {
const inspected = util.inspect(e);
dispatchNS.error(`error occurred when during interaction dispatch (${interactionName}):\n${inspected}`);

const errEmbed = new MeteoriumEmbedBuilder(interaction.user)
.setTitle("Error occurred during interaction dispatch")
.setDescription(codeBlock(inspected.substring(0, 4500)))
.setErrorColor();

try {
if (interaction.isChatInputCommand() || interaction.isContextMenuCommand()) {
if (interaction.deferred) await interaction.editReply({ embeds: [errEmbed] });
else await interaction.reply({ embeds: [errEmbed] });
}
} catch (e) {
dispatchNS.warn(
`could not send interaction error for ${interaction.id} (${interactionName}):\n${util.inspect(e)}`,
);
}
}

return;
}
}

0 comments on commit e7dd8e8

Please sign in to comment.