1- using Disc . NET . Client . SDK . Enums ;
1+ using Disc . NET . Client . SDK . Enums ;
22using Disc . NET . Client . SDK . Messages ;
33using Disc . NET . Client . SDK . Messages . Components ;
4+ using Disc . NET . Client . SDK . Messages . Components . Enums ;
45using Disc . NET . Commands . Contexts ;
56using Disc . NET . Commands . MessageBuilders ;
67using Disc . NET . Shared . Constraints ;
@@ -31,62 +32,42 @@ private List<object> MountActionRows()
3132
3233 var results = new List < object > ( ) ;
3334
34- NormalizeActionRows < ActionRowSelectMenuBuilder > ( ActionRowConstraint . MAX_SELECT_MENUS_PER_ACTION_ROW , message =>
35- new ActionRowSelectMenuBuilder ( ) . AddComponent < ContextBase > ( message . First ( ) ) ) ;
35+ // Normalize for Select Menus (Max 1 per action row)
36+ NormalizeActionRows ( ActionRowConstraint . MAX_SELECT_MENUS_PER_ACTION_ROW , components =>
37+ components . Any ( c => c . Type == MessageComponentType . SelectMenu ) ,
38+ message => new ActionRowBuilder ( ) . AddComponent < ContextBase > ( message . First ( ) ) ) ;
39+
40+ // Normalize for Buttons (Max 5 per action row)
41+ NormalizeActionRows ( ActionRowConstraint . MAX_BUTTONS_PER_ACTION_ROW , components =>
42+ components . All ( c => c . Type == MessageComponentType . Button ) ,
43+ message =>
44+ {
45+ var builder = new ActionRowBuilder ( ) ;
46+ message . ForEach ( c => builder . AddComponent < ContextBase > ( c ) ) ;
47+ return builder ;
48+ } ) ;
3649
3750 ActionRows . ForEach ( x => results . Add ( x . Build ( ) ) ) ;
3851 return results ;
3952 }
4053
41- /// <summary>
42- /// Normalizes action rows of a specific component type to comply with Discord message constraints.
43- ///
44- /// This method validates the maximum number of action rows per message and the maximum number
45- /// of components allowed per action row. If an action row exceeds the allowed number of components,
46- /// it is automatically split into multiple valid action rows.
47- /// </summary>
48- /// <typeparam name="T">
49- /// The type of action row builder to process (e.g., select menu rows or button rows).
50- /// </typeparam>
51- /// <param name="quantityComponentPerActionRow">
52- /// The maximum number of components allowed per action row for the given component type.
53- /// </param>
54- /// <param name="createBuilderFunc">
55- /// A factory function responsible for creating a new action row builder from a list
56- /// of message components.
57- /// </param>
58- /// <exception cref="DiscNetClientSdkException">
59- /// Thrown when the message exceeds Discord constraints, such as:
60- /// - Maximum number of action rows per message
61- /// - Insufficient available action row slots to split invalid rows
62- /// </exception>
63- /// <remarks>
64- /// This method mutates the <see cref="ActionRow"/> collection by:
65- /// - Removing excess components from invalid action rows
66- /// - Adding newly created action rows to the message
67- /// </remarks>
68-
69- private void NormalizeActionRows < T > ( int quantityComponentPerActionRow ,
54+ private void NormalizeActionRows ( int quantityComponentPerActionRow ,
55+ Func < List < IMessageComponent > , bool > predicate ,
7056 Func < List < IMessageComponent > , IActionRowBuilder > createBuilderFunc )
71- where T : IActionRowBuilder
7257 {
73- var actionRows = ActionRows . Where ( x => x is T ) . ToList ( ) ;
58+ var actionRowsToProcess = ActionRows . Where ( x => predicate ( x . Components ) ) . ToList ( ) ;
7459
75- if ( actionRows . Count == 0 ) return ;
60+ if ( actionRowsToProcess . Count == 0 ) return ;
61+
7662 int actionRowsPerMessage = ActionRowConstraint . MAX_ACTION_ROWS_PER_MESSAGE ;
77- int actionRowsCount = actionRows . Count ;
78- if ( actionRowsCount > actionRowsPerMessage )
79- {
80- throw new DiscNetGenericException (
81- $ "The message cannot contain more than { actionRowsPerMessage } top-level components.") ;
82- }
83-
63+
8464 int availableActionRowSlots = actionRowsPerMessage - ActionRows . Count ;
85- var invalidActionRows = actionRows . Where ( x => x . Components . Count > quantityComponentPerActionRow ) . ToList ( ) ;
65+ var invalidActionRows = actionRowsToProcess . Where ( x => x . Components . Count > quantityComponentPerActionRow ) . ToList ( ) ;
8666 var containsActionRowsInvalids = invalidActionRows . Count > 0 ;
8767
8868 if ( ! containsActionRowsInvalids ) return ;
89- if ( availableActionRowSlots == 0 && containsActionRowsInvalids )
69+
70+ if ( availableActionRowSlots <= 0 )
9071 {
9172 throw new DiscNetGenericException ( $ "The message cannot contain more than { actionRowsPerMessage } top-level components.") ;
9273 }
@@ -103,19 +84,16 @@ private void NormalizeActionRows<T>(int quantityComponentPerActionRow,
10384 . Sum ( x => x . Components . Count - 1 ) ;
10485 }
10586
106-
10787 if ( numberNecessaryToCreateNewActionRows > availableActionRowSlots )
10888 {
10989 throw new DiscNetGenericException (
110- $ "The message cannot contain more than { actionRowsPerMessage } top-level components.") ;
90+ $ "The message cannot contain more than { actionRowsPerMessage } top-level components and there is no space to split components into new Action Rows .") ;
11191 }
11292
11393 int newActionRowsCount = 0 ;
11494 foreach ( var actionRow in invalidActionRows )
11595 {
116- var messageComponents = actionRow . Components
117- . OfType < IMessageComponent > ( )
118- . ToList ( ) ;
96+ var messageComponents = actionRow . Components . ToList ( ) ;
11997
12098 int index = 0 ;
12199 while ( messageComponents . Count - index > quantityComponentPerActionRow &&
@@ -125,28 +103,44 @@ private void NormalizeActionRows<T>(int quantityComponentPerActionRow,
125103
126104 if ( quantityComponentPerActionRow > 1 )
127105 {
128- var components = messageComponents
129- . Skip ( index )
106+ var componentsToMove = messageComponents
107+ . Skip ( index + quantityComponentPerActionRow ) // Keep the first N in current row, move others
130108 . Take ( quantityComponentPerActionRow )
131109 . ToList ( ) ;
132- components . ForEach ( x => actionRow . Components . Remove ( x ) ) ;
110+
111+ componentsToMove . ForEach ( x => actionRow . Components . Remove ( x ) ) ;
133112 index += quantityComponentPerActionRow ;
134- newActionRow = createBuilderFunc . Invoke ( components ) ;
113+ newActionRow = createBuilderFunc . Invoke ( componentsToMove ) ;
135114 }
136115 else
137116 {
138- var component = messageComponents [ index ] ;
139-
140- actionRow . Components . Remove ( component ) ;
141- index ++ ;
142- newActionRow = createBuilderFunc . Invoke ( [ component ] ) ;
117+ // For Select Menus (max 1), move everything after the first one
118+ var componentsToMove = messageComponents . Skip ( 1 ) . ToList ( ) ;
119+ componentsToMove . ForEach ( x => actionRow . Components . Remove ( x ) ) ;
120+
121+ // We need to create a new action row for each extra select menu
122+ foreach ( var comp in componentsToMove )
123+ {
124+ if ( newActionRowsCount < availableActionRowSlots )
125+ {
126+ ActionRows . Add ( createBuilderFunc . Invoke ( [ comp ] ) ) ;
127+ newActionRowsCount ++ ;
128+ }
129+ else
130+ {
131+ throw new DiscNetGenericException ( "Not enough slots for all select menus." ) ;
132+ }
133+ }
134+ break ; // Already handled all components for this row
143135 }
144136
145- ActionRows . Add ( newActionRow ) ;
146- newActionRowsCount ++ ;
137+ if ( newActionRow != null )
138+ {
139+ ActionRows . Add ( newActionRow ) ;
140+ newActionRowsCount ++ ;
141+ }
147142 }
148143 }
149144 }
150-
151145 }
152146}
0 commit comments