1
1
# Copyright (c) NiceBots all rights reserved
2
2
from collections import defaultdict
3
3
from functools import cached_property
4
- from typing import Any , Final , final , override
4
+ from typing import Any , Final , final
5
5
6
6
import discord
7
7
from discord .ext import commands
8
- from discord .ext import pages as paginator
8
+ from discord .ui import (
9
+ Button ,
10
+ Container ,
11
+ Select ,
12
+ TextDisplay ,
13
+ View ,
14
+ )
9
15
10
16
from src import custom
11
17
from src .extensions .help .pages .classes import (
@@ -82,82 +88,205 @@ def get_gradient_color(shade_index: int, color_index: int, max_shade: int = 50,
82
88
return (final_color [0 ] << 16 ) | (final_color [1 ] << 8 ) | final_color [2 ]
83
89
84
90
85
- class PageIndicatorButton (paginator .PaginatorButton ):
86
- def __init__ (self ) -> None :
87
- super ().__init__ (button_type = "page_indicator" , disabled = True , label = "" , style = discord .ButtonStyle .gray )
88
-
89
-
90
91
@final
91
- class HelpView (paginator . Paginator ):
92
+ class HelpView (View ):
92
93
def __init__ (
93
94
self ,
94
- embeds : dict [str , list [discord . Embed ]],
95
+ categories_data : dict [str , list [dict ]],
95
96
ui_translations : TranslationWrapper [dict [str , RawTranslation ]],
96
97
bot : custom .Bot ,
97
98
) -> None :
99
+ super ().__init__ (timeout = 180 )
98
100
self .bot = bot
101
+ self .categories_data = categories_data
102
+ self .ui_translations = ui_translations
103
+ self .current_category = next (list (categories_data .keys ()))
104
+ self .current_page = 0
105
+ self .total_pages = len (categories_data [self .current_category ])
99
106
100
- the_pages : list [paginator .PageGroup ] = [
101
- paginator .PageGroup (
102
- [paginator .Page (embeds = [embed ]) for embed in data [1 ]],
103
- label = data [0 ],
104
- default = i == 0 ,
105
- )
106
- for i , data in enumerate (embeds .items ())
107
- ]
107
+ # Create navigation buttons (will be added to container in update_content)
108
+ self .first_button = Button (style = discord .ButtonStyle .blurple , emoji = "⏮️" , label = "First" , disabled = True )
109
+ self .first_button .callback = self .first_button_callback
108
110
109
- self .embeds = embeds
110
- self .ui_translations = ui_translations
111
- self .page_indicator = PageIndicatorButton ()
112
- super ().__init__ (
113
- the_pages ,
114
- show_menu = True ,
115
- menu_placeholder = ui_translations .select_category ,
116
- custom_buttons = [
117
- paginator .PaginatorButton ("first" , emoji = "⏮️" , style = discord .ButtonStyle .blurple ),
118
- paginator .PaginatorButton ("prev" , emoji = "◀️" , style = discord .ButtonStyle .red ),
119
- self .page_indicator ,
120
- paginator .PaginatorButton ("next" , emoji = "▶️" , style = discord .ButtonStyle .green ),
121
- paginator .PaginatorButton ("last" , emoji = "⏭️" , style = discord .ButtonStyle .blurple ),
122
- ],
123
- use_default_buttons = False ,
111
+ self .prev_button = Button (style = discord .ButtonStyle .red , emoji = "◀️" , label = "Previous" , disabled = True )
112
+ self .prev_button .callback = self .prev_button_callback
113
+
114
+ self .page_indicator = Button (
115
+ style = discord .ButtonStyle .gray ,
116
+ label = ui_translations .page_indicator .format (current = 1 , total = self .total_pages ),
117
+ disabled = True ,
118
+ )
119
+
120
+ self .next_button = Button (
121
+ style = discord .ButtonStyle .green , emoji = "▶️" , label = "Next" , disabled = self .total_pages <= 1
122
+ )
123
+ self .next_button .callback = self .next_button_callback
124
+
125
+ self .last_button = Button (
126
+ style = discord .ButtonStyle .blurple , emoji = "⏭️" , label = "Last" , disabled = self .total_pages <= 1
127
+ )
128
+ self .last_button .callback = self .last_button_callback
129
+
130
+ # Create category selector
131
+ self .category_select = Select (
132
+ placeholder = ui_translations .select_category ,
133
+ options = [discord .SelectOption (label = category , value = category ) for category in categories_data ],
134
+ )
135
+ self .category_select .callback = self .category_select_callback
136
+
137
+ # Add the main container with content
138
+ self .update_content ()
139
+
140
+ def update_content (self ) -> None :
141
+ """Update the view's content based on the current category and page."""
142
+ # Remove existing containers if any
143
+ for item in list (self .children ):
144
+ if isinstance (item , Container ):
145
+ self .remove_item (item )
146
+
147
+ # Get the current page data
148
+ page_data = self .categories_data [self .current_category ][self .current_page ]
149
+
150
+ # Create the main container
151
+ container = Container (color = discord .Color (page_data ["color" ]))
152
+
153
+ # Add title directly as TextDisplay
154
+ title_text = TextDisplay (f"### { page_data ['title' ]} " )
155
+ container .add_item (title_text )
156
+
157
+ # Add description directly as TextDisplay
158
+ description_text = TextDisplay (page_data ["description" ])
159
+ container .add_item (description_text )
160
+
161
+ # Add separator
162
+ container .add_separator (divider = True )
163
+
164
+ # Add quick tips if available
165
+ if page_data .get ("quick_tips" ):
166
+ tips_title = TextDisplay (f"### { self .ui_translations .quick_tips_title } " )
167
+ container .add_item (tips_title )
168
+ tips_content = TextDisplay ("\n " .join ([f"- { tip } " for tip in page_data ["quick_tips" ]]))
169
+ container .add_item (tips_content )
170
+ container .add_separator ()
171
+
172
+ # Add examples if available
173
+ if page_data .get ("examples" ):
174
+ examples_title = TextDisplay (f"### { self .ui_translations .examples_title } " )
175
+ container .add_item (examples_title )
176
+ examples_content = TextDisplay ("\n " .join ([f"- { example } " for example in page_data ["examples" ]]))
177
+ container .add_item (examples_content )
178
+ container .add_separator ()
179
+
180
+ # Add related commands if available
181
+ if page_data .get ("related_commands" ):
182
+ commands_title = TextDisplay (f"### { self .ui_translations .related_commands_title } " )
183
+ commands_content_list = []
184
+ for cmd_name in page_data ["related_commands" ]:
185
+ cmd = self .bot .get_application_command (cmd_name )
186
+ if cmd :
187
+ commands_content_list .append (f"- { cmd .mention } " )
188
+ if commands_content_list :
189
+ container .add_item (commands_title )
190
+ commands_content = TextDisplay ("\n " .join (commands_content_list ))
191
+ container .add_item (commands_content )
192
+ container .add_separator (divider = True )
193
+
194
+ # Add navigation buttons to the container
195
+ container .add_item (self .first_button )
196
+ container .add_item (self .prev_button )
197
+ container .add_item (self .page_indicator )
198
+ container .add_item (self .next_button )
199
+ container .add_item (self .last_button )
200
+ container .add_item (self .category_select )
201
+
202
+ # Add the container to the view
203
+ self .add_item (container )
204
+
205
+ async def category_select_callback (self , interaction : discord .Interaction ) -> None :
206
+ """Handle category selection."""
207
+ self .current_category = self .category_select .values [0 ]
208
+ self .current_page = 0
209
+ self .total_pages = len (self .categories_data [self .current_category ])
210
+
211
+ # Update navigation buttons
212
+ self .first_button .disabled = True
213
+ self .prev_button .disabled = True
214
+ self .next_button .disabled = self .total_pages <= 1
215
+ self .last_button .disabled = self .total_pages <= 1
216
+ self .page_indicator .label = self .ui_translations .page_indicator .format (current = 1 , total = self .total_pages )
217
+
218
+ # Update content
219
+ self .update_content ()
220
+ await interaction .response .edit_message (view = self )
221
+
222
+ async def first_button_callback (self , interaction : discord .Interaction ) -> None :
223
+ """Go to the first page."""
224
+ self .current_page = 0
225
+ await self .update_page (interaction )
226
+
227
+ async def prev_button_callback (self , interaction : discord .Interaction ) -> None :
228
+ """Go to the previous page."""
229
+ self .current_page = max (0 , self .current_page - 1 )
230
+ await self .update_page (interaction )
231
+
232
+ async def next_button_callback (self , interaction : discord .Interaction ) -> None :
233
+ """Go to the next page."""
234
+ self .current_page = min (self .total_pages - 1 , self .current_page + 1 )
235
+ await self .update_page (interaction )
236
+
237
+ async def last_button_callback (self , interaction : discord .Interaction ) -> None :
238
+ """Go to the last page."""
239
+ self .current_page = self .total_pages - 1
240
+ await self .update_page (interaction )
241
+
242
+ async def update_page (self , interaction : discord .Interaction ) -> None :
243
+ """Update the view after a page change."""
244
+ # Update navigation buttons
245
+ self .first_button .disabled = self .current_page == 0
246
+ self .prev_button .disabled = self .current_page == 0
247
+ self .next_button .disabled = self .current_page == self .total_pages - 1
248
+ self .last_button .disabled = self .current_page == self .total_pages - 1
249
+ self .page_indicator .label = self .ui_translations .page_indicator .format (
250
+ current = self .current_page + 1 , total = self .total_pages
124
251
)
125
252
126
- @override
127
- def update_buttons (self ) -> dict : # pyright: ignore [reportMissingTypeArgument, reportUnknownParameterType]
128
- r = super ().update_buttons () # pyright: ignore [reportUnknownVariableType]
129
- if self .show_indicator :
130
- self .buttons ["page_indicator" ]["object" ].label = self .ui_translations .page_indicator .format (
131
- current = self .current_page + 1 , total = self .page_count + 1
132
- )
133
- return r # pyright: ignore [reportUnknownVariableType]
253
+ # Update content
254
+ self .update_content ()
255
+ await interaction .response .edit_message (view = self )
134
256
135
257
136
- def get_categories_embeds (
137
- ui_translations : TranslationWrapper [dict [str , RawTranslation ]],
258
+ def get_categories_data (
259
+ ui_translations : TranslationWrapper [dict [str , RawTranslation ]], # noqa: ARG001
138
260
categories : dict [str , TranslationWrapper [HelpCategoryTranslation ]],
139
- bot : custom .Bot ,
140
- ) -> dict [str , list [discord .Embed ]]:
141
- embeds : defaultdict [str , list [discord .Embed ]] = defaultdict (list )
261
+ bot : custom .Bot , # noqa: ARG001
262
+ ) -> dict [str , list [dict ]]:
263
+ """Generate category data for the help view.
264
+
265
+ Returns a dictionary where keys are category names and values are lists of page data dictionaries.
266
+ Each page data dictionary contains title, description, color, and optional quick_tips,
267
+ examples, and related_commands.
268
+ """
269
+ categories_data : defaultdict [str , list [dict ]] = defaultdict (list )
142
270
for i , category in enumerate (categories ):
143
271
for j , page in enumerate (category .pages .values ()): # pyright: ignore [reportUnknownArgumentType, reportUnknownVariableType, reportAttributeAccessIssue]
144
- embed = discord .Embed (
145
- title = f"{ category .name } - { page .title } " , # pyright: ignore [reportAttributeAccessIssue]
146
- description = page .description , # pyright: ignore [reportUnknownArgumentType]
147
- color = discord .Color (get_gradient_color (i , j )),
148
- )
272
+ page_data = {
273
+ "title" : f"{ category .name } - { page .title } " , # pyright: ignore [reportAttributeAccessIssue]
274
+ "description" : page .description , # pyright: ignore [reportUnknownArgumentType]
275
+ "color" : get_gradient_color (i , j ),
276
+ }
277
+
149
278
if page .quick_tips :
150
- embed .add_field (name = ui_translations .quick_tips_title , value = "- " + "\n - " .join (page .quick_tips )) # pyright: ignore [reportUnknownArgumentType]
279
+ page_data ["quick_tips" ] = page .quick_tips # pyright: ignore [reportUnknownArgumentType]
280
+
151
281
if page .examples :
152
- embed .add_field (name = ui_translations .examples_title , value = "- " + "\n - " .join (page .examples )) # pyright: ignore [reportUnknownArgumentType]
282
+ page_data ["examples" ] = page .examples # pyright: ignore [reportUnknownArgumentType]
283
+
153
284
if page .related_commands :
154
- embed .add_field (
155
- name = ui_translations .related_commands_title ,
156
- value = "- "
157
- + "\n - " .join (bot .get_application_command (name ).mention for name in page .related_commands ), # pyright: ignore [reportUnknownArgumentType, reportUnknownVariableType, reportAttributeAccessIssue, reportOptionalMemberAccess]
158
- )
159
- embeds [category .name ].append (embed ) # pyright: ignore [reportAttributeAccessIssue]
160
- return dict (embeds )
285
+ page_data ["related_commands" ] = page .related_commands # pyright: ignore [reportUnknownArgumentType, reportUnknownVariableType, reportAttributeAccessIssue]
286
+
287
+ categories_data [category .name ].append (page_data ) # pyright: ignore [reportAttributeAccessIssue]
288
+
289
+ return dict (categories_data )
161
290
162
291
163
292
@final
@@ -168,13 +297,14 @@ def __init__(self, bot: custom.Bot, ui_translations: dict[str, RawTranslation],
168
297
self .locales = locales
169
298
170
299
@cached_property
171
- def embeds (self ) -> dict [str , dict [str , list [discord .Embed ]]]:
172
- embeds : defaultdict [str , dict [str , list [discord .Embed ]]] = defaultdict (dict )
300
+ def categories_data (self ) -> dict [str , dict [str , list [dict ]]]:
301
+ """Generate and cache help category data for all locales."""
302
+ data : defaultdict [str , dict [str , list [dict ]]] = defaultdict (dict )
173
303
for locale in self .locales :
174
304
t = help_translation .get_for_locale (locale )
175
305
ui = apply_locale (self .ui_translations , locale )
176
- embeds [locale ] = get_categories_embeds (ui , t .categories , self .bot )
177
- return dict (embeds )
306
+ data [locale ] = get_categories_data (ui , t .categories , self .bot )
307
+ return dict (data )
178
308
179
309
@discord .slash_command (
180
310
name = "help" ,
@@ -186,12 +316,13 @@ def embeds(self) -> dict[str, dict[str, list[discord.Embed]]]:
186
316
},
187
317
)
188
318
async def help_slash (self , ctx : custom .ApplicationContext ) -> None :
189
- paginator = HelpView (
190
- embeds = self .embeds [ctx .locale ],
319
+ """Display help information using the new UI components."""
320
+ help_view = HelpView (
321
+ categories_data = self .categories_data [ctx .locale ],
191
322
ui_translations = apply_locale (self .ui_translations , ctx .locale ),
192
323
bot = self .bot ,
193
324
)
194
- await paginator .respond (ctx . interaction , ephemeral = True )
325
+ await ctx .respond (view = help_view , ephemeral = True )
195
326
196
327
197
328
def setup (bot : custom .Bot , config : dict [str , Any ]) -> None : # pyright: ignore [reportExplicitAny]
0 commit comments