Skip to content
This repository was archived by the owner on Apr 15, 2026. It is now read-only.

Commit 8c7b25f

Browse files
authored
Merge pull request #83 from tancred423/82-better-logging-of-missing-access-error
add detailed log for missing access
2 parents 463ddc0 + 3eaadc1 commit 8c7b25f

1 file changed

Lines changed: 136 additions & 2 deletions

File tree

src/handler/ButtonInteractionHandler.ts

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ButtonInteraction, MessageFlags } from "discord.js";
1+
import { ButtonInteraction, DiscordAPIError, MessageFlags, PermissionsBitField } from "discord.js";
22
import { ArrayManipulationService } from "../service/ArrayManipulationService.ts";
33
import { DiscordEmbedService } from "../service/DiscordEmbedService.ts";
44
import { HelpCommandHelper } from "../helper/HelpCommandHelper.ts";
@@ -58,7 +58,7 @@ export class ButtonInteractionHandler {
5858
}
5959

6060
const content = StringManipulationService.buildLoadingText("Generating profile image...");
61-
await interaction.message.edit({ content });
61+
await this.updateInteractionMessage(interaction, content);
6262
await ProfileCommandHandler.handlePageSwapButton(interaction, buttonIdSplit);
6363
break;
6464
}
@@ -97,4 +97,138 @@ export class ButtonInteractionHandler {
9797
);
9898
}
9999
}
100+
101+
private static async updateInteractionMessage(interaction: ButtonInteraction, content: string): Promise<void> {
102+
try {
103+
await interaction.message.edit({ content });
104+
} catch (error: unknown) {
105+
this.logMessageEditFailure(interaction, error);
106+
}
107+
}
108+
109+
private static logMessageEditFailure(interaction: ButtonInteraction, error: unknown): void {
110+
if (error instanceof DiscordAPIError && error.code === 50001) {
111+
const diagnosis = this.buildMissingAccessDiagnosis(interaction);
112+
log.error(
113+
`DiscordAPIError(50001) Missing Access editing interaction message. ${diagnosis} error=${
114+
this.formatError(error)
115+
}`,
116+
);
117+
return;
118+
}
119+
120+
log.error(`Failed to edit interaction message: ${this.formatError(error)}`);
121+
}
122+
123+
private static buildMissingAccessDiagnosis(interaction: ButtonInteraction): string {
124+
const clientUser = interaction.client.user;
125+
const guildChannel = interaction.inGuild() ? interaction.channel : null;
126+
const permissions = clientUser && guildChannel ? guildChannel.permissionsFor(clientUser) : null;
127+
const permissionSnapshot = this.getRelevantPermissionSnapshot(guildChannel, permissions);
128+
const missingPermissions = permissionSnapshot
129+
.filter((entry) => entry.allowed === false)
130+
.map((entry) => entry.name);
131+
const botIsMessageAuthor = interaction.message.author?.id === clientUser?.id;
132+
const likelyCauses: string[] = [];
133+
134+
if (!clientUser) {
135+
likelyCauses.push("client user unavailable");
136+
}
137+
138+
if (!interaction.inGuild()) {
139+
likelyCauses.push("interaction is outside a guild");
140+
}
141+
142+
if (!interaction.channel) {
143+
likelyCauses.push("interaction channel unavailable");
144+
}
145+
146+
if (!botIsMessageAuthor) {
147+
likelyCauses.push("message author is not the bot");
148+
}
149+
150+
if (guildChannel && !guildChannel.viewable) {
151+
likelyCauses.push("bot cannot view the channel");
152+
}
153+
154+
if (guildChannel && !permissions) {
155+
likelyCauses.push("bot permissions could not be resolved for the channel");
156+
}
157+
158+
if (missingPermissions.length > 0) {
159+
likelyCauses.push(`missing relevant permissions: ${missingPermissions.join(",")}`);
160+
}
161+
162+
if (!interaction.message.editable) {
163+
likelyCauses.push("Discord.js reports message.editable=false");
164+
}
165+
166+
if (guildChannel?.isThread()) {
167+
if (!guildChannel.joined) {
168+
likelyCauses.push("bot is not a member of the thread");
169+
}
170+
171+
if (guildChannel.archived) {
172+
likelyCauses.push("thread is archived");
173+
}
174+
175+
if (guildChannel.locked) {
176+
likelyCauses.push("thread is locked");
177+
}
178+
}
179+
180+
return this.formatDiagnosticFields({
181+
guildId: interaction.guild?.id ?? null,
182+
guildName: interaction.guild?.name ?? null,
183+
channelId: interaction.channelId ?? null,
184+
channelName: interaction.inGuild() ? guildChannel?.name ?? null : null,
185+
channelType: interaction.channel?.type ?? null,
186+
channelViewable: guildChannel?.viewable ?? null,
187+
messageId: interaction.message.id,
188+
messageAuthorId: interaction.message.author?.id ?? null,
189+
messageEditable: interaction.message.editable,
190+
botUserId: clientUser?.id ?? null,
191+
botIsMessageAuthor,
192+
relevantPermissions: permissionSnapshot.length > 0
193+
? permissionSnapshot.map((entry) => `${entry.name}:${entry.allowed === null ? "unknown" : entry.allowed}`).join(
194+
",",
195+
)
196+
: null,
197+
threadJoined: guildChannel?.isThread() ? guildChannel.joined : null,
198+
threadArchived: guildChannel?.isThread() ? guildChannel.archived : null,
199+
threadLocked: guildChannel?.isThread() ? guildChannel.locked : null,
200+
likelyCauses: likelyCauses.length > 0 ? likelyCauses.join(" | ") : "unknown",
201+
});
202+
}
203+
204+
private static getRelevantPermissionSnapshot(
205+
channel: ButtonInteraction["channel"],
206+
permissions: Readonly<PermissionsBitField> | null,
207+
): Array<{ name: string; allowed: boolean | null }> {
208+
const entries = [
209+
{ name: "ViewChannel", flag: PermissionsBitField.Flags.ViewChannel },
210+
{ name: "ReadMessageHistory", flag: PermissionsBitField.Flags.ReadMessageHistory },
211+
{
212+
name: channel?.isThread() ? "SendMessagesInThreads" : "SendMessages",
213+
flag: channel?.isThread()
214+
? PermissionsBitField.Flags.SendMessagesInThreads
215+
: PermissionsBitField.Flags.SendMessages,
216+
},
217+
];
218+
219+
return entries.map(({ name, flag }) => ({
220+
name,
221+
allowed: permissions ? permissions.has(flag) : null,
222+
}));
223+
}
224+
225+
private static formatDiagnosticFields(fields: Record<string, boolean | number | string | null>): string {
226+
return Object.entries(fields)
227+
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
228+
.join(" ");
229+
}
230+
231+
private static formatError(error: unknown): string {
232+
return error instanceof Error ? error.stack ?? error.message : String(error);
233+
}
100234
}

0 commit comments

Comments
 (0)