fix: soften discord interaction logging
This commit is contained in:
@@ -163,6 +163,35 @@ function decodeDiscordCommandArgValue(value: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDiscordUnknownInteraction(error: unknown): boolean {
|
||||||
|
if (!error || typeof error !== "object") return false;
|
||||||
|
const err = error as {
|
||||||
|
discordCode?: number;
|
||||||
|
status?: number;
|
||||||
|
message?: string;
|
||||||
|
rawBody?: { code?: number; message?: string };
|
||||||
|
};
|
||||||
|
if (err.discordCode === 10062 || err.rawBody?.code === 10062) return true;
|
||||||
|
if (err.status === 404 && /Unknown interaction/i.test(err.message ?? "")) return true;
|
||||||
|
if (/Unknown interaction/i.test(err.rawBody?.message ?? "")) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function safeDiscordInteractionCall<T>(
|
||||||
|
label: string,
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
): Promise<T | null> {
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} catch (error) {
|
||||||
|
if (isDiscordUnknownInteraction(error)) {
|
||||||
|
console.warn(`discord: ${label} skipped (interaction expired)`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildDiscordCommandArgCustomId(params: {
|
function buildDiscordCommandArgCustomId(params: {
|
||||||
command: string;
|
command: string;
|
||||||
arg: string;
|
arg: string;
|
||||||
@@ -196,6 +225,73 @@ function parseDiscordCommandArgData(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DiscordCommandArgContext = {
|
||||||
|
cfg: ReturnType<typeof loadConfig>;
|
||||||
|
discordConfig: DiscordConfig;
|
||||||
|
accountId: string;
|
||||||
|
sessionPrefix: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleDiscordCommandArgInteraction(
|
||||||
|
interaction: ButtonInteraction,
|
||||||
|
data: ComponentData,
|
||||||
|
ctx: DiscordCommandArgContext,
|
||||||
|
) {
|
||||||
|
const parsed = parseDiscordCommandArgData(data);
|
||||||
|
if (!parsed) {
|
||||||
|
await safeDiscordInteractionCall("command arg update", () =>
|
||||||
|
interaction.update({
|
||||||
|
content: "Sorry, that selection is no longer available.",
|
||||||
|
components: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (interaction.user?.id && interaction.user.id !== parsed.userId) {
|
||||||
|
await safeDiscordInteractionCall("command arg ack", () => interaction.acknowledge());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const commandDefinition =
|
||||||
|
findCommandByNativeName(parsed.command) ??
|
||||||
|
listChatCommands().find((entry) => entry.key === parsed.command);
|
||||||
|
if (!commandDefinition) {
|
||||||
|
await safeDiscordInteractionCall("command arg update", () =>
|
||||||
|
interaction.update({
|
||||||
|
content: "Sorry, that command is no longer available.",
|
||||||
|
components: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const updated = await safeDiscordInteractionCall("command arg update", () =>
|
||||||
|
interaction.update({
|
||||||
|
content: `✅ Selected ${parsed.value}.`,
|
||||||
|
components: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (!updated) return;
|
||||||
|
const commandArgs = createCommandArgsWithValue({
|
||||||
|
argName: parsed.arg,
|
||||||
|
value: parsed.value,
|
||||||
|
});
|
||||||
|
const commandArgsWithRaw: CommandArgs = {
|
||||||
|
...commandArgs,
|
||||||
|
raw: serializeCommandArgs(commandDefinition, commandArgs),
|
||||||
|
};
|
||||||
|
const prompt = buildCommandTextFromArgs(commandDefinition, commandArgsWithRaw);
|
||||||
|
await dispatchDiscordCommandInteraction({
|
||||||
|
interaction,
|
||||||
|
prompt,
|
||||||
|
command: commandDefinition,
|
||||||
|
commandArgs: commandArgsWithRaw,
|
||||||
|
cfg: ctx.cfg,
|
||||||
|
discordConfig: ctx.discordConfig,
|
||||||
|
accountId: ctx.accountId,
|
||||||
|
sessionPrefix: ctx.sessionPrefix,
|
||||||
|
preferFollowUp: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class DiscordCommandArgButton extends Button {
|
class DiscordCommandArgButton extends Button {
|
||||||
label: string;
|
label: string;
|
||||||
customId: string;
|
customId: string;
|
||||||
@@ -223,55 +319,34 @@ class DiscordCommandArgButton extends Button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async run(interaction: ButtonInteraction, data: ComponentData) {
|
async run(interaction: ButtonInteraction, data: ComponentData) {
|
||||||
const parsed = parseDiscordCommandArgData(data);
|
await handleDiscordCommandArgInteraction(interaction, data, {
|
||||||
if (!parsed) {
|
|
||||||
await interaction.update({
|
|
||||||
content: "Sorry, that selection is no longer available.",
|
|
||||||
components: [],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (interaction.user?.id && interaction.user.id !== parsed.userId) {
|
|
||||||
await interaction.acknowledge();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const commandDefinition =
|
|
||||||
findCommandByNativeName(parsed.command) ??
|
|
||||||
listChatCommands().find((entry) => entry.key === parsed.command);
|
|
||||||
if (!commandDefinition) {
|
|
||||||
await interaction.update({
|
|
||||||
content: "Sorry, that command is no longer available.",
|
|
||||||
components: [],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await interaction.update({
|
|
||||||
content: `✅ Selected ${parsed.value}.`,
|
|
||||||
components: [],
|
|
||||||
});
|
|
||||||
const commandArgs = createCommandArgsWithValue({
|
|
||||||
argName: parsed.arg,
|
|
||||||
value: parsed.value,
|
|
||||||
});
|
|
||||||
const commandArgsWithRaw: CommandArgs = {
|
|
||||||
...commandArgs,
|
|
||||||
raw: serializeCommandArgs(commandDefinition, commandArgs),
|
|
||||||
};
|
|
||||||
const prompt = buildCommandTextFromArgs(commandDefinition, commandArgsWithRaw);
|
|
||||||
await dispatchDiscordCommandInteraction({
|
|
||||||
interaction,
|
|
||||||
prompt,
|
|
||||||
command: commandDefinition,
|
|
||||||
commandArgs: commandArgsWithRaw,
|
|
||||||
cfg: this.cfg,
|
cfg: this.cfg,
|
||||||
discordConfig: this.discordConfig,
|
discordConfig: this.discordConfig,
|
||||||
accountId: this.accountId,
|
accountId: this.accountId,
|
||||||
sessionPrefix: this.sessionPrefix,
|
sessionPrefix: this.sessionPrefix,
|
||||||
preferFollowUp: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DiscordCommandArgFallbackButton extends Button {
|
||||||
|
label = "cmdarg";
|
||||||
|
customId = "cmdarg:seed=1";
|
||||||
|
private ctx: DiscordCommandArgContext;
|
||||||
|
|
||||||
|
constructor(ctx: DiscordCommandArgContext) {
|
||||||
|
super();
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(interaction: ButtonInteraction, data: ComponentData) {
|
||||||
|
await handleDiscordCommandArgInteraction(interaction, data, this.ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDiscordCommandArgFallbackButton(params: DiscordCommandArgContext): Button {
|
||||||
|
return new DiscordCommandArgFallbackButton(params);
|
||||||
|
}
|
||||||
|
|
||||||
function buildDiscordCommandArgMenu(params: {
|
function buildDiscordCommandArgMenu(params: {
|
||||||
command: ChatCommandDefinition;
|
command: ChatCommandDefinition;
|
||||||
menu: { arg: CommandArgDefinition; choices: string[]; title?: string };
|
menu: { arg: CommandArgDefinition; choices: string[]; title?: string };
|
||||||
@@ -408,11 +483,13 @@ async function dispatchDiscordCommandInteraction(params: {
|
|||||||
content,
|
content,
|
||||||
...(options?.ephemeral !== undefined ? { ephemeral: options.ephemeral } : {}),
|
...(options?.ephemeral !== undefined ? { ephemeral: options.ephemeral } : {}),
|
||||||
};
|
};
|
||||||
if (preferFollowUp) {
|
await safeDiscordInteractionCall("interaction reply", async () => {
|
||||||
await interaction.followUp(payload);
|
if (preferFollowUp) {
|
||||||
return;
|
await interaction.followUp(payload);
|
||||||
}
|
return;
|
||||||
await interaction.reply(payload);
|
}
|
||||||
|
await interaction.reply(payload);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
||||||
@@ -567,18 +644,22 @@ async function dispatchDiscordCommandInteraction(params: {
|
|||||||
sessionPrefix,
|
sessionPrefix,
|
||||||
});
|
});
|
||||||
if (preferFollowUp) {
|
if (preferFollowUp) {
|
||||||
await interaction.followUp({
|
await safeDiscordInteractionCall("interaction follow-up", () =>
|
||||||
|
interaction.followUp({
|
||||||
|
content: menuPayload.content,
|
||||||
|
components: menuPayload.components,
|
||||||
|
ephemeral: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await safeDiscordInteractionCall("interaction reply", () =>
|
||||||
|
interaction.reply({
|
||||||
content: menuPayload.content,
|
content: menuPayload.content,
|
||||||
components: menuPayload.components,
|
components: menuPayload.components,
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
});
|
}),
|
||||||
return;
|
);
|
||||||
}
|
|
||||||
await interaction.reply({
|
|
||||||
content: menuPayload.content,
|
|
||||||
components: menuPayload.components,
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,15 +727,23 @@ async function dispatchDiscordCommandInteraction(params: {
|
|||||||
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix,
|
responsePrefix: resolveEffectiveMessagesConfig(cfg, route.agentId).responsePrefix,
|
||||||
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
|
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
|
||||||
deliver: async (payload) => {
|
deliver: async (payload) => {
|
||||||
await deliverDiscordInteractionReply({
|
try {
|
||||||
interaction,
|
await deliverDiscordInteractionReply({
|
||||||
payload,
|
interaction,
|
||||||
textLimit: resolveTextChunkLimit(cfg, "discord", accountId, {
|
payload,
|
||||||
fallbackLimit: 2000,
|
textLimit: resolveTextChunkLimit(cfg, "discord", accountId, {
|
||||||
}),
|
fallbackLimit: 2000,
|
||||||
maxLinesPerMessage: discordConfig?.maxLinesPerMessage,
|
}),
|
||||||
preferFollowUp: preferFollowUp || didReply,
|
maxLinesPerMessage: discordConfig?.maxLinesPerMessage,
|
||||||
});
|
preferFollowUp: preferFollowUp || didReply,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (isDiscordUnknownInteraction(error)) {
|
||||||
|
console.warn("discord: interaction reply skipped (interaction expired)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
didReply = true;
|
didReply = true;
|
||||||
},
|
},
|
||||||
onError: (err, info) => {
|
onError: (err, info) => {
|
||||||
@@ -697,13 +786,15 @@ async function deliverDiscordInteractionReply(params: {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
: { content };
|
: { content };
|
||||||
if (!preferFollowUp && !hasReplied) {
|
await safeDiscordInteractionCall("interaction send", async () => {
|
||||||
await interaction.reply(payload);
|
if (!preferFollowUp && !hasReplied) {
|
||||||
|
await interaction.reply(payload);
|
||||||
|
hasReplied = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await interaction.followUp(payload);
|
||||||
hasReplied = true;
|
hasReplied = true;
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
await interaction.followUp(payload);
|
|
||||||
hasReplied = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mediaList.length > 0) {
|
if (mediaList.length > 0) {
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ import {
|
|||||||
registerDiscordListener,
|
registerDiscordListener,
|
||||||
} from "./listeners.js";
|
} from "./listeners.js";
|
||||||
import { createDiscordMessageHandler } from "./message-handler.js";
|
import { createDiscordMessageHandler } from "./message-handler.js";
|
||||||
import { createDiscordNativeCommand } from "./native-command.js";
|
import {
|
||||||
|
createDiscordCommandArgFallbackButton,
|
||||||
|
createDiscordNativeCommand,
|
||||||
|
} from "./native-command.js";
|
||||||
|
|
||||||
export type MonitorDiscordOpts = {
|
export type MonitorDiscordOpts = {
|
||||||
token?: string;
|
token?: string;
|
||||||
@@ -149,6 +152,14 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
|||||||
{
|
{
|
||||||
commands,
|
commands,
|
||||||
listeners: [],
|
listeners: [],
|
||||||
|
components: [
|
||||||
|
createDiscordCommandArgFallbackButton({
|
||||||
|
cfg,
|
||||||
|
discordConfig: discordCfg,
|
||||||
|
accountId: account.accountId,
|
||||||
|
sessionPrefix,
|
||||||
|
}),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
new GatewayPlugin({
|
new GatewayPlugin({
|
||||||
|
|||||||
Reference in New Issue
Block a user