Kaynağa Gözat

Long help text is broken into navigable pages

pull/13/head
Rocketsoup 2 ay önce
ebeveyn
işleme
f606dfa2b5
1 değiştirilmiş dosya ile 99 ekleme ve 14 silme
  1. 99
    14
      rocketbot/cogs/helpcog.py

+ 99
- 14
rocketbot/cogs/helpcog.py Dosyayı Görüntüle

@@ -2,8 +2,9 @@
2 2
 import re
3 3
 from typing import Union, Optional
4 4
 
5
-from discord import Interaction, Permissions, AppCommandType
5
+from discord import Interaction, Permissions, AppCommandType, ButtonStyle, InteractionResponse
6 6
 from discord.app_commands import Group, Command, autocomplete, guild_only, command, Choice
7
+from discord.ui import ActionRow, Button, LayoutView, TextDisplay
7 8
 
8 9
 from config import CONFIG
9 10
 from rocketbot.bot import Rocketbot
@@ -12,6 +13,9 @@ from rocketbot.utils import MOD_PERMISSIONS, dump_stacktrace
12 13
 
13 14
 HelpTopic = Union[Command, Group, BaseCog]
14 15
 
16
+# Potential place to break text neatly in large help content
17
+PAGE_BREAK = '\f'
18
+
15 19
 def choice_from_topic(topic: HelpTopic, include_full_command: bool = False) -> Choice:
16 20
 	if isinstance(topic, BaseCog):
17 21
 		return Choice(name=f'⚙ {topic.qualified_name}', value=f'cog:{topic.qualified_name}')
@@ -272,6 +276,7 @@ class HelpCog(BaseCog, name='Help'):
272 276
 				if isinstance(cmd, Group):
273 277
 					subcommand_count = len(cmd.commands)
274 278
 					text += f' ({subcommand_count} subcommands)'
279
+			text += PAGE_BREAK
275 280
 
276 281
 		if len(all_cog_tuples) > 0:
277 282
 			text += '\n### Module Configuration'
@@ -284,10 +289,9 @@ class HelpCog(BaseCog, name='Help'):
284 289
 					if setting.name == 'enabled':
285 290
 						continue
286 291
 					text += f'\n   - `/get` or `/set {cog.config_prefix}_{setting.name}`'
287
-		await interaction.response.send_message(
288
-			text,
289
-			ephemeral=True,
290
-		)
292
+				text += PAGE_BREAK
293
+
294
+		await self.__send_paged_help(interaction, text)
291 295
 
292 296
 	async def __send_keyword_help(self, interaction: Interaction, matching_topics: Optional[list[HelpTopic]]) -> None:
293 297
 		matching_commands = [
@@ -324,10 +328,8 @@ class HelpCog(BaseCog, name='Help'):
324 328
 			text += '\n### Modules'
325 329
 			for cog in matching_cogs:
326 330
 				text += f'\n- {cog.qualified_name}'
327
-		await interaction.response.send_message(
328
-			text,
329
-			ephemeral=True,
330
-		)
331
+
332
+		await self.__send_paged_help(interaction, text)
331 333
 
332 334
 	async def __send_command_help(self, interaction: Interaction, command_or_group: Union[Command, Group], addendum: Optional[str] = None) -> None:
333 335
 		text = ''
@@ -371,7 +373,8 @@ class HelpCog(BaseCog, name='Help'):
371 373
 					text += f'\n- `{param.name}`: {param.description}'
372 374
 					if not param.required:
373 375
 						text += ' (optional)'
374
-		await interaction.response.send_message(text, ephemeral=True)
376
+
377
+		await self.__send_paged_help(interaction, text)
375 378
 
376 379
 	async def __send_cog_help(self, interaction: Interaction, cog: BaseCog) -> None:
377 380
 		text = f'## :information_source: Module Help'
@@ -405,10 +408,92 @@ class HelpCog(BaseCog, name='Help'):
405 408
 				if setting.name == 'enabled':
406 409
 					continue
407 410
 				text += f'\n- `/get` or `/set {cog.config_prefix}_{setting.name}` - {setting.brief}'
408
-		await interaction.response.send_message(
409
-			text,
410
-			ephemeral=True,
411
-		)
411
+
412
+		await self.__send_paged_help(interaction, text)
413
+
414
+	async def __send_paged_help(self, interaction: Interaction, text: str) -> None:
415
+		pages = _paginate(text)
416
+		if len(pages) == 1:
417
+			await interaction.response.send_message(pages[0], ephemeral=True)
418
+		else:
419
+			await _update_paged_help(interaction, None, 0, pages)
420
+
421
+def _paginate(text: str) -> list[str]:
422
+	max_page_size = 2000
423
+	chunks = text.split(PAGE_BREAK)
424
+	pages = [ '' ]
425
+	for chunk in chunks:
426
+		if len(chunk) > max_page_size:
427
+			raise ValueError('Help content needs more page breaks! One chunk is too big for message.')
428
+		if len(pages[-1] + chunk) < max_page_size:
429
+			pages[-1] += chunk
430
+		else:
431
+			pages.append(chunk)
432
+	page_count = len(pages)
433
+	if page_count == 1:
434
+		return pages
435
+
436
+	# Do another pass and try to even out the page lengths
437
+	indices = [ i * len(chunks) // page_count for i in range(page_count + 1) ]
438
+	even_pages = [
439
+		''.join(chunks[indices[i]:indices[i + 1]])
440
+		for i in range(page_count)
441
+	]
442
+	for page in even_pages:
443
+		if len(page) > max_page_size:
444
+			# We made a page too big. Give up.
445
+			return pages
446
+	return even_pages
447
+
448
+async def _update_paged_help(interaction: Interaction, original_interaction: Optional[Interaction], current_page: int, pages: list[str]) -> None:
449
+	try:
450
+		view = _PagingLayoutView(current_page, pages, original_interaction or interaction)
451
+		resolved = interaction
452
+		if original_interaction is not None:
453
+			# We have an original interaction from the initial command and a
454
+			# new one from the button press. Use the original to swap in the
455
+			# new page in place, then acknowledge the new one to satisfy the
456
+			# API that we didn't fail.
457
+			await original_interaction.edit_original_response(
458
+				view=view,
459
+			)
460
+			if interaction is not original_interaction:
461
+				await interaction.response.defer(ephemeral=True, thinking=False)
462
+		else:
463
+			# Initial send
464
+			await resolved.response.send_message(
465
+				view=view,
466
+				ephemeral=True,
467
+				delete_message_after=60,
468
+			)
469
+	except BaseException as e:
470
+		dump_stacktrace(e)
471
+
472
+class _PagingLayoutView(LayoutView):
473
+	def __init__(self, current_page: int, pages: list[str], original_interaction: Optional[Interaction]):
474
+		super().__init__()
475
+		self.current_page: int = current_page
476
+		self.pages: list[str] = pages
477
+		self.text.content = self.pages[self.current_page]
478
+		self.original_interaction = original_interaction
479
+		if current_page <= 0:
480
+			self.handle_prev_button.disabled = True
481
+		if current_page >= len(self.pages) - 1:
482
+			self.handle_next_button.disabled = True
483
+
484
+	text = TextDisplay('')
485
+
486
+	row = ActionRow()
487
+
488
+	@row.button(label='< Prev')
489
+	async def handle_prev_button(self, interaction: Interaction, button: Button) -> None:
490
+		new_page = max(0, self.current_page - 1)
491
+		await _update_paged_help(interaction, self.original_interaction, new_page, self.pages)
492
+
493
+	@row.button(label='Next >')
494
+	async def handle_next_button(self, interaction: Interaction, button: Button) -> None:
495
+		new_page = min(len(self.pages) - 1, self.current_page + 1)
496
+		await _update_paged_help(interaction, self.original_interaction, new_page, self.pages)
412 497
 
413 498
 # Exclusions from keyword indexing
414 499
 trivial_words = {

Loading…
İptal
Kaydet