From 2d28e63a4f8fa38d75f8b1ebd0a75c720747bbba Mon Sep 17 00:00:00 2001 From: shni Date: Fri, 3 Oct 2025 23:52:15 -0500 Subject: [PATCH] feat: add runtime patch for ModalSubmitInteraction to prevent crashes during member resolution --- src/core/patches/discordModalPatch.ts | 97 +++++++++++++++++++++++++++ src/main.ts | 8 +++ 2 files changed, 105 insertions(+) create mode 100644 src/core/patches/discordModalPatch.ts diff --git a/src/core/patches/discordModalPatch.ts b/src/core/patches/discordModalPatch.ts new file mode 100644 index 0000000..c033a33 --- /dev/null +++ b/src/core/patches/discordModalPatch.ts @@ -0,0 +1,97 @@ +// filepath: src/core/patches/discordModalPatch.ts +/* + Runtime patch for discord.js 15.0.0-dev to avoid crashes when ModalSubmitInteraction + tries to cache members from resolved.members lacking the `user` field. + Source of truth: node_modules/discord.js/src/structures/ModalSubmitInteraction.js +*/ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { Collection } = require('@discordjs/collection'); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const ModalModule = require('../../../node_modules/discord.js/src/structures/ModalSubmitInteraction.js'); + +export function applyModalSubmitInteractionPatch() { + const ModalSubmitInteraction = ModalModule?.ModalSubmitInteraction; + if (!ModalSubmitInteraction || typeof ModalSubmitInteraction !== 'function') { + return; // Nothing to patch + } + + const original = ModalSubmitInteraction.prototype.transformComponent; + + // Override with a safer version + // eslint-disable-next-line func-names + ModalSubmitInteraction.prototype.transformComponent = function (rawComponent: any, resolved: any) { + if ('components' in rawComponent) { + return { + type: rawComponent.type, + id: rawComponent.id, + components: rawComponent.components.map((component: any) => this.transformComponent(component, resolved)), + }; + } + + if ('component' in rawComponent) { + return { + type: rawComponent.type, + id: rawComponent.id, + component: this.transformComponent(rawComponent.component, resolved), + }; + } + + const data: any = { + type: rawComponent.type, + id: rawComponent.id, + }; + + if ('custom_id' in rawComponent) data.customId = rawComponent.custom_id; + if ('value' in rawComponent) data.value = rawComponent.value; + + if (rawComponent.values) { + data.values = rawComponent.values; + + if (resolved) { + const collect = (resolvedData: any, resolver: (val: any, id: string) => any) => { + const collection = new Collection(); + for (const value of data.values as string[]) { + if (resolvedData?.[value]) { + const resolvedVal = resolver(resolvedData[value], value); + if (resolvedVal) collection.set(value, resolvedVal); + } + } + return collection.size ? collection : null; + }; + + const users = collect(resolved.users, (user: any) => this.client.users._add(user)); + if (users) data.users = users; + + const channels = collect( + resolved.channels, + (channel: any) => this.client.channels._add(channel, this.guild) ?? channel, + ); + if (channels) data.channels = channels; + + // SAFE members resolution: ensure member.user exists or skip _add fallbacking to raw + const members = collect(resolved.members, (member: any, id: string) => { + try { + if (!member?.user && resolved.users?.[id]) { + member.user = resolved.users[id]; + } + // If still no user, don't call _add which would crash; return raw member + if (!member?.user) return member; + return this.guild?.members._add(member) ?? member; + } catch { + return member; + } + }); + if (members) data.members = members; + + const roles = collect(resolved.roles, (role: any) => this.guild?.roles._add(role) ?? role); + if (roles) data.roles = roles; + } + } + + return data; + }; + + // Keep a reference in case we need to restore + (ModalSubmitInteraction.prototype as any).__originalTransformComponent = original; +} + diff --git a/src/main.ts b/src/main.ts index 5cb401d..781a0bf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,7 @@ import {memoryOptimizer} from "./core/memory/memoryOptimizer"; import { startReminderPoller } from "./core/api/reminders"; import { ensureRemindersSchema } from "./core/api/remindersSchema"; import logger from "./core/lib/logger"; +import { applyModalSubmitInteractionPatch } from "./core/patches/discordModalPatch"; // Activar monitor de memoria si se define la variable const __memInt = parseInt(process.env.MEMORY_LOG_INTERVAL_SECONDS || '0', 10); @@ -21,6 +22,13 @@ if (process.env.ENABLE_MEMORY_OPTIMIZER === 'true') { memoryOptimizer.start(); } +// Apply safety patch for ModalSubmitInteraction members resolution before anything else +try { + applyModalSubmitInteractionPatch(); +} catch (e) { + logger.warn({ err: e }, 'No se pudo aplicar el patch de ModalSubmitInteraction'); +} + export const bot = new Amayo(); // Listeners de robustez del cliente Discord