@@ -27,6 +27,92 @@ const PRESET_COLORS = [
2727 { name : '⚪ Blanco Discord' , value : '#5865F2' } ,
2828] ;
2929
30+ /**
31+ * Construye los componentes (dropdowns y botones) para el anuncio
32+ */
33+ function buildAnnouncementComponents ( session : any , cfg : any ) {
34+ const rows = [ ] ;
35+
36+ // Dropdown de colores
37+ const colorMenu = new StringSelectMenuBuilder ( )
38+ . setCustomId ( `announce:color:${ session . userId } ` )
39+ . setPlaceholder ( '🎨 Selecciona un color' )
40+ . addOptions (
41+ PRESET_COLORS . map ( ( color ) =>
42+ new StringSelectMenuOptionBuilder ( )
43+ . setLabel ( color . name )
44+ . setValue ( color . value )
45+ . setDefault ( session . announcement . color === color . value ) ,
46+ ) ,
47+ ) ;
48+ rows . push ( new ActionRowBuilder < StringSelectMenuBuilder > ( ) . addComponents ( colorMenu ) ) ;
49+
50+ // Dropdown de roles a mencionar (si existen roles de notificaciones configurados)
51+ const notificationRoles = [ ] ;
52+ const seenRoleIds = new Set < string > ( ) ;
53+
54+ // Agregar roles solo si no están duplicados
55+ if ( cfg ?. roles . laLiga && ! seenRoleIds . has ( cfg . roles . laLiga ) ) {
56+ notificationRoles . push ( { label : '⚽ La Liga' , value : cfg . roles . laLiga } ) ;
57+ seenRoleIds . add ( cfg . roles . laLiga ) ;
58+ }
59+ if ( cfg ?. roles . preParciales && ! seenRoleIds . has ( cfg . roles . preParciales ) ) {
60+ notificationRoles . push ( { label : '📚 Pre-Parciales' , value : cfg . roles . preParciales } ) ;
61+ seenRoleIds . add ( cfg . roles . preParciales ) ;
62+ }
63+ if ( cfg ?. roles . cursos && ! seenRoleIds . has ( cfg . roles . cursos ) ) {
64+ notificationRoles . push ( { label : '📖 Cursos' , value : cfg . roles . cursos } ) ;
65+ seenRoleIds . add ( cfg . roles . cursos ) ;
66+ }
67+ if ( cfg ?. roles . notificacionesGenerales && ! seenRoleIds . has ( cfg . roles . notificacionesGenerales ) ) {
68+ notificationRoles . push ( {
69+ label : '🔔 Notificaciones Generales' ,
70+ value : cfg . roles . notificacionesGenerales ,
71+ } ) ;
72+ seenRoleIds . add ( cfg . roles . notificacionesGenerales ) ;
73+ }
74+
75+ if ( notificationRoles . length > 0 ) {
76+ const rolesMenu = new StringSelectMenuBuilder ( )
77+ . setCustomId ( `announce:roles:${ session . userId } ` )
78+ . setPlaceholder ( '🔔 Selecciona roles a mencionar (opcional)' )
79+ . setMinValues ( 0 )
80+ . setMaxValues ( notificationRoles . length )
81+ . addOptions (
82+ notificationRoles . map ( ( role ) =>
83+ new StringSelectMenuOptionBuilder ( )
84+ . setLabel ( role . label )
85+ . setValue ( role . value )
86+ . setDefault ( session . announcement . roles ?. includes ( role . value ) || false ) ,
87+ ) ,
88+ ) ;
89+ rows . push ( new ActionRowBuilder < StringSelectMenuBuilder > ( ) . addComponents ( rolesMenu ) ) ;
90+ }
91+
92+ // Botones de acción
93+ const actionRow = new ActionRowBuilder < ButtonBuilder > ( ) . addComponents (
94+ new ButtonBuilder ( )
95+ . setCustomId ( `announce:edit:${ session . userId } ` )
96+ . setLabel ( '✏️ Editar Texto' )
97+ . setStyle ( ButtonStyle . Secondary ) ,
98+ new ButtonBuilder ( )
99+ . setCustomId ( `announce:image:${ session . userId } ` )
100+ . setLabel ( '🖼️ Imagen' )
101+ . setStyle ( ButtonStyle . Secondary ) ,
102+ new ButtonBuilder ( )
103+ . setCustomId ( `announce:publish:${ session . userId } ` )
104+ . setLabel ( '📢 Publicar' )
105+ . setStyle ( ButtonStyle . Success ) ,
106+ new ButtonBuilder ( )
107+ . setCustomId ( `announce:cancel:${ session . userId } ` )
108+ . setLabel ( '❌ Cancelar' )
109+ . setStyle ( ButtonStyle . Danger ) ,
110+ ) ;
111+ rows . push ( actionRow ) ;
112+
113+ return rows ;
114+ }
115+
30116/**
31117 * Handler para el modal del announce
32118 */
@@ -98,109 +184,43 @@ export async function handleAnnounceModal(interaction: any) {
98184 flags : 1 << 6 ,
99185 } ) ;
100186
101- // Actualizar el mensaje original del preview usando el mensaje guardado
187+ // Actualizar el mensaje original del preview usando interaction del mensaje original
102188 try {
103- if ( session . previewMessage ) {
189+ logger . info ( 'Attempting to update preview via webhook' , {
190+ requestId : session . requestId ,
191+ hasOriginalInteraction : ! ! session . originalInteraction ,
192+ imageUrl,
193+ } ) ;
194+
195+ if ( session . originalInteraction ) {
104196 const embed = createAnnouncementPreview ( session ) ;
105197 const cfg = getGuildConfig ( session . guildId ) ;
106198
107- // Reconstruir los componentes (necesarios para que Discord re-renderice el embed)
108- const rows = [ ] ;
109-
110- // Dropdown de colores
111- const colorMenu = new StringSelectMenuBuilder ( )
112- . setCustomId ( `announce:color:${ session . userId } ` )
113- . setPlaceholder ( '🎨 Selecciona un color' )
114- . addOptions (
115- PRESET_COLORS . map ( ( color ) =>
116- new StringSelectMenuOptionBuilder ( )
117- . setLabel ( color . name )
118- . setValue ( color . value )
119- . setDefault ( session . announcement . color === color . value ) ,
120- ) ,
121- ) ;
122- rows . push ( new ActionRowBuilder < StringSelectMenuBuilder > ( ) . addComponents ( colorMenu ) ) ;
123-
124- // Dropdown de roles
125- const notificationRoles = [ ] ;
126- const seenRoleIds = new Set < string > ( ) ;
127-
128- if ( cfg ?. roles . laLiga && ! seenRoleIds . has ( cfg . roles . laLiga ) ) {
129- notificationRoles . push ( { label : '⚽ La Liga' , value : cfg . roles . laLiga } ) ;
130- seenRoleIds . add ( cfg . roles . laLiga ) ;
131- }
132- if ( cfg ?. roles . preParciales && ! seenRoleIds . has ( cfg . roles . preParciales ) ) {
133- notificationRoles . push ( { label : '📚 Pre-Parciales' , value : cfg . roles . preParciales } ) ;
134- seenRoleIds . add ( cfg . roles . preParciales ) ;
135- }
136- if ( cfg ?. roles . cursos && ! seenRoleIds . has ( cfg . roles . cursos ) ) {
137- notificationRoles . push ( { label : '📖 Cursos' , value : cfg . roles . cursos } ) ;
138- seenRoleIds . add ( cfg . roles . cursos ) ;
139- }
140- if (
141- cfg ?. roles . notificacionesGenerales &&
142- ! seenRoleIds . has ( cfg . roles . notificacionesGenerales )
143- ) {
144- notificationRoles . push ( {
145- label : '🔔 Notificaciones Generales' ,
146- value : cfg . roles . notificacionesGenerales ,
147- } ) ;
148- seenRoleIds . add ( cfg . roles . notificacionesGenerales ) ;
149- }
150-
151- if ( notificationRoles . length > 0 ) {
152- const rolesMenu = new StringSelectMenuBuilder ( )
153- . setCustomId ( `announce:roles:${ session . userId } ` )
154- . setPlaceholder ( '🔔 Selecciona roles a mencionar (opcional)' )
155- . setMinValues ( 0 )
156- . setMaxValues ( notificationRoles . length )
157- . addOptions (
158- notificationRoles . map ( ( role ) =>
159- new StringSelectMenuOptionBuilder ( )
160- . setLabel ( role . label )
161- . setValue ( role . value )
162- . setDefault ( session . announcement . roles ?. includes ( role . value ) || false ) ,
163- ) ,
164- ) ;
165- rows . push ( new ActionRowBuilder < StringSelectMenuBuilder > ( ) . addComponents ( rolesMenu ) ) ;
166- }
167-
168- // Botones
169- const actionRow = new ActionRowBuilder < ButtonBuilder > ( ) . addComponents (
170- new ButtonBuilder ( )
171- . setCustomId ( `announce:edit:${ session . userId } ` )
172- . setLabel ( '✏️ Editar Texto' )
173- . setStyle ( ButtonStyle . Secondary ) ,
174- new ButtonBuilder ( )
175- . setCustomId ( `announce:image:${ session . userId } ` )
176- . setLabel ( '🖼️ Imagen' )
177- . setStyle ( ButtonStyle . Secondary ) ,
178- new ButtonBuilder ( )
179- . setCustomId ( `announce:publish:${ session . userId } ` )
180- . setLabel ( '📢 Publicar' )
181- . setStyle ( ButtonStyle . Success ) ,
182- new ButtonBuilder ( )
183- . setCustomId ( `announce:cancel:${ session . userId } ` )
184- . setLabel ( '❌ Cancelar' )
185- . setStyle ( ButtonStyle . Danger ) ,
186- ) ;
187- rows . push ( actionRow ) ;
188-
189- // Actualizar el mensaje con embed y componentes
190- await session . previewMessage . edit ( { embeds : [ embed ] , components : rows } ) ;
191-
192- logger . info ( 'Preview updated with image' , {
199+ // Usar la función auxiliar para construir los componentes
200+ const rows = buildAnnouncementComponents ( session , cfg ) ;
201+
202+ logger . info ( 'About to edit preview via editReply' , {
203+ requestId : session . requestId ,
204+ embedHasImage : ! ! session . announcement . image ,
205+ embedImage : session . announcement . image ,
206+ } ) ;
207+
208+ // Usar editReply en la interacción original (funciona con efímeros)
209+ await session . originalInteraction . editReply ( { embeds : [ embed ] , components : rows } ) ;
210+
211+ logger . info ( 'Preview updated successfully with image' , {
193212 requestId : session . requestId ,
194213 hasImage : ! ! imageUrl ,
195214 } ) ;
196215 } else {
197- logger . warn ( 'No preview message found to update' , {
216+ logger . warn ( 'No original interaction found to update' , {
198217 requestId : session . requestId ,
199218 } ) ;
200219 }
201220 } catch ( err ) {
202221 logger . error ( 'Failed to update preview after image change' , {
203222 error : err instanceof Error ? err . message : String ( err ) ,
223+ stack : err instanceof Error ? err . stack : undefined ,
204224 requestId : session . requestId ,
205225 } ) ;
206226 }
@@ -214,94 +234,22 @@ async function showAnnouncementOptions(interaction: any, session: any) {
214234 const embed = createAnnouncementPreview ( session ) ;
215235 const cfg = getGuildConfig ( session . guildId ) ;
216236
217- const rows = [ ] ;
218-
219- // Dropdown de colores
220- const colorMenu = new StringSelectMenuBuilder ( )
221- . setCustomId ( `announce:color:${ session . userId } ` )
222- . setPlaceholder ( '🎨 Selecciona un color' )
223- . addOptions (
224- PRESET_COLORS . map ( ( color ) =>
225- new StringSelectMenuOptionBuilder ( )
226- . setLabel ( color . name )
227- . setValue ( color . value )
228- . setDefault ( session . announcement . color === color . value ) ,
229- ) ,
230- ) ;
231- rows . push ( new ActionRowBuilder < StringSelectMenuBuilder > ( ) . addComponents ( colorMenu ) ) ;
232-
233- // Dropdown de roles a mencionar (si existen roles de notificaciones configurados)
234- const notificationRoles = [ ] ;
235- const seenRoleIds = new Set < string > ( ) ;
236-
237- // Agregar roles solo si no están duplicados
238- if ( cfg ?. roles . laLiga && ! seenRoleIds . has ( cfg . roles . laLiga ) ) {
239- notificationRoles . push ( { label : '⚽ La Liga' , value : cfg . roles . laLiga } ) ;
240- seenRoleIds . add ( cfg . roles . laLiga ) ;
241- }
242- if ( cfg ?. roles . preParciales && ! seenRoleIds . has ( cfg . roles . preParciales ) ) {
243- notificationRoles . push ( { label : '📚 Pre-Parciales' , value : cfg . roles . preParciales } ) ;
244- seenRoleIds . add ( cfg . roles . preParciales ) ;
245- }
246- if ( cfg ?. roles . cursos && ! seenRoleIds . has ( cfg . roles . cursos ) ) {
247- notificationRoles . push ( { label : '📖 Cursos' , value : cfg . roles . cursos } ) ;
248- seenRoleIds . add ( cfg . roles . cursos ) ;
249- }
250- if ( cfg ?. roles . notificacionesGenerales && ! seenRoleIds . has ( cfg . roles . notificacionesGenerales ) ) {
251- notificationRoles . push ( {
252- label : '🔔 Notificaciones Generales' ,
253- value : cfg . roles . notificacionesGenerales ,
254- } ) ;
255- seenRoleIds . add ( cfg . roles . notificacionesGenerales ) ;
256- }
257-
258- if ( notificationRoles . length > 0 ) {
259- const rolesMenu = new StringSelectMenuBuilder ( )
260- . setCustomId ( `announce:roles:${ session . userId } ` )
261- . setPlaceholder ( '🔔 Selecciona roles a mencionar (opcional)' )
262- . setMinValues ( 0 )
263- . setMaxValues ( notificationRoles . length )
264- . addOptions (
265- notificationRoles . map ( ( role ) =>
266- new StringSelectMenuOptionBuilder ( )
267- . setLabel ( role . label )
268- . setValue ( role . value )
269- . setDefault ( session . announcement . roles ?. includes ( role . value ) || false ) ,
270- ) ,
271- ) ;
272- rows . push ( new ActionRowBuilder < StringSelectMenuBuilder > ( ) . addComponents ( rolesMenu ) ) ;
273- }
237+ // Usar la función auxiliar para construir los componentes
238+ const rows = buildAnnouncementComponents ( session , cfg ) ;
274239
275- // Botones de acción
276- const actionRow = new ActionRowBuilder < ButtonBuilder > ( ) . addComponents (
277- new ButtonBuilder ( )
278- . setCustomId ( `announce:edit:${ session . userId } ` )
279- . setLabel ( '✏️ Editar Texto' )
280- . setStyle ( ButtonStyle . Secondary ) ,
281- new ButtonBuilder ( )
282- . setCustomId ( `announce:image:${ session . userId } ` )
283- . setLabel ( '🖼️ Imagen' )
284- . setStyle ( ButtonStyle . Secondary ) ,
285- new ButtonBuilder ( )
286- . setCustomId ( `announce:publish:${ session . userId } ` )
287- . setLabel ( '📢 Publicar' )
288- . setStyle ( ButtonStyle . Success ) ,
289- new ButtonBuilder ( )
290- . setCustomId ( `announce:cancel:${ session . userId } ` )
291- . setLabel ( '❌ Cancelar' )
292- . setStyle ( ButtonStyle . Danger ) ,
293- ) ;
294- rows . push ( actionRow ) ;
295-
296- const reply = await interaction . reply ( {
240+ await interaction . reply ( {
297241 embeds : [ embed ] ,
298242 components : rows ,
299243 flags : 1 << 6 ,
300- fetchReply : true , // Obtener el mensaje para poder editarlo después
301244 } ) ;
302245
303- // Guardar el mensaje para poder actualizarlo después
304- session . previewMessage = reply ;
246+ // Guardar la interacción original para poder usar editReply después
247+ session . originalInteraction = interaction ;
248+
249+ logger . info ( 'Preview message created and saved' , {
250+ requestId : session . requestId ,
251+ hasInteraction : ! ! interaction ,
252+ } ) ;
305253}
306254
307255/**
0 commit comments