From 92d3e5ed90182e8c933d8d948fd2ea54618997d6 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 2 Aug 2023 04:29:46 -0700 Subject: [PATCH] Add tip when using a redundant rtfm selector (#51) Add a tip to rtfm/rtfs and update docs/ux around them --- core/utils/paginator.py | 49 +++++++++----- modules/manuals.py | 147 ++++++++++++++++++++-------------------- 2 files changed, 107 insertions(+), 89 deletions(-) diff --git a/core/utils/paginator.py b/core/utils/paginator.py index 9dc5fdd..8a97156 100644 --- a/core/utils/paginator.py +++ b/core/utils/paginator.py @@ -29,12 +29,13 @@ from discord import ui # shortcut because I'm lazy from discord.ext.commands import CommandError, Paginator as _Paginator # type: ignore # why does this need a stub file? from discord.utils import MISSING +from typing_extensions import Self +from core import Bot, Context -if TYPE_CHECKING: - from typing_extensions import Self -from core import Context +if TYPE_CHECKING: + from discord.abc import MessageableChannel __all__ = ("CannotPaginate", "Pager", "KVPager", "TextPager") @@ -61,19 +62,27 @@ def __init__( author: discord.User | discord.Member | None = None, author_url: str | None = None, stop: bool = False, + reply_author_takes_paginator: bool = False, ): super().__init__() - self.bot = ctx.bot - self.stoppable = stop - self.ctx = ctx - self.delete_after = delete_after - self.entries = entries - self.embed_author = author, author_url - self.channel = ctx.channel - self.author = ctx.author - self.nocount = nocount - self.title = title - self.per_page = per_page + self.bot: Bot = ctx.bot + self.stoppable: bool = stop + self.ctx: Context = ctx + self.delete_after: bool = delete_after + self.entries: list[Any] = entries + self.embed_author: tuple[discord.User | discord.Member | None, str | None] = author, author_url + self.channel: MessageableChannel = ctx.channel + self.nocount: bool = nocount + self.title: str | None = title + self.per_page: int = per_page + + if reply_author_takes_paginator: + if ctx.replied_reference: + self.author = ctx.replied_message.author + else: + self.author = ctx.author + else: + self.author = ctx.author pages, left_over = divmod(len(self.entries), self.per_page) if left_over: @@ -298,13 +307,21 @@ def prepare_embed(self, entries: list[Any], page: int, *, first: bool = False): class TextPager(Pager): def __init__( - self, ctx: Context, text: str, *, prefix: str = "```", suffix: str = "```", max_size: int = 2000, stop: bool = False + self, + ctx: Context, + text: str, + *, + prefix: str = "```", + suffix: str = "```", + max_size: int = 2000, + stop: bool = False, + **kwargs: Any, ) -> None: paginator = _Paginator(prefix=prefix, suffix=suffix, max_size=max_size - 200) for line in text.split("\n"): paginator.add_line(line) - super().__init__(ctx, entries=paginator.pages, per_page=1, show_entry_count=False, stop=stop) + super().__init__(ctx, entries=paginator.pages, per_page=1, show_entry_count=False, stop=stop, **kwargs) def get_page(self, page: int) -> Any: return self.entries[page - 1] diff --git a/modules/manuals.py b/modules/manuals.py index e3aef6d..59bab2a 100644 --- a/modules/manuals.py +++ b/modules/manuals.py @@ -97,6 +97,49 @@ def _smart_guess_lib(self, ctx: core.Context) -> LibEnum | None: return None + async def get_lib(self, ctx: core.Context, query: str) -> tuple[LibEnum, str, str] | None: # enum, query, notice + if not query: + lib = self._smart_guess_lib(ctx) + + if not lib: + await ctx.reply("Sorry, I couldn't apply a default library to this channel. Try again with a library?") + return None + + await ctx.send(str(lib.value), reference=ctx.replied_message) + return None + + view = StringView(query) + maybe_lib = view.get_word() + view.skip_ws() + final_query = view.read_rest() + + tip = "" + lib: LibEnum | None = None + + if maybe_lib in lib_names: + lib = lib_names[maybe_lib] + else: + maybe_lib = None + final_query = query # ignore the stringview stuff then + + if lib is None: + lib = self._smart_guess_lib(ctx) + + if lib is None: + await ctx.reply("Sorry, I couldn't find a library that matched. Try again with a different library?") + return None + + elif ( + maybe_lib + and isinstance(ctx.channel, discord.Thread) + and ctx.channel.parent_id == constants.Channels.HELP_FORUM + and lib == self._smart_guess_lib(ctx) + ): + if 1006717008613740596 not in ctx.channel._applied_tags: # type: ignore # other-help tag, that one doesnt get a smart guess + tip += "\n• Tip: Forum posts with tags will automatically have the relevant libraries used, no need to specify it!" + + return lib, final_query, tip + @commands.command( "rtfm", brief="Searches documentation", @@ -111,28 +154,18 @@ async def rtfm(self, ctx: core.Context, *, query: str) -> None: On its own it will do its best to figure out the most relevant documentation, but you can always specify by prefixing the query with the library you wish to use. The following libraries are supported (you can use either the full name or the shorthand): - ``` - - wavelink | wl - - twitchio | tio - - python | py - - discordpy | dpy - ``` - The following flags are available for this command: - ``` - - --labels (Include labels in search results) - - --clear (Clearly labels labels with a `label:` prefix. If --labels has not be set it will be implicitly set) - """ - if not query: - lib = self._smart_guess_lib(ctx) + • wavelink | wl + • twitchio | tio + • python | py + • discordpy | dpy - if not lib: - await ctx.reply("Sorry, I couldn't apply a default library to this channel. Try again with a library?") - return - await ctx.send(str(lib.value), reference=ctx.replied_message) - return + The following flags are available for this command: + • --labels (Include labels in search results) + • --clear (Clearly labels labels with a `label:` prefix. If --labels has not be set it will be implicitly set) + """ labels = False clear_labels = False @@ -144,26 +177,14 @@ async def rtfm(self, ctx: core.Context, *, query: str) -> None: labels = clear_labels = True # implicitly set --labels query = query.replace("--clear", "") - view = StringView(query) - maybe_lib = view.get_word() - view.skip_ws() - final_query = view.read_rest() - lib: LibEnum | None = None - - if maybe_lib in lib_names: - lib = lib_names[maybe_lib] - else: - final_query = query # ignore the stringview stuff then - - if lib is None: - lib = self._smart_guess_lib(ctx) - - if lib is None: - await ctx.reply("Sorry, I couldn't find a library that matched. Try again with a different library?") + optional = await self.get_lib(ctx, query) + if not optional: return + lib, final_query, tip = optional + if not final_query: - await ctx.send(str(lib.value[0]), reference=ctx.replied_message) + await ctx.send(str(lib.value[0]) + tip, reference=ctx.replied_message) return url = self.target.with_path("/api/public/rtfm.sphinx").with_query( @@ -196,7 +217,7 @@ async def rtfm(self, ctx: core.Context, *, query: str) -> None: e.description = "\n".join(f"[`{key}`]({url})" for key, url in matches["nodes"].items()) e.set_author(name=f"Query Time: {float(matches['query_time']):.2f}") - await ctx.send(embed=e) + await ctx.send(tip or None, embed=e) @commands.command( name="rtfs", @@ -212,26 +233,16 @@ async def rtfs(self, ctx: core.Context, *, query: str) -> None: On its own it will do its best to figure out the most relevant library, but you can always specify by prefixing the query with the library you wish to use. The following libraries are supported (you can use either the full name or the shorthand): - ``` - - wavelink | wl - - twitchio | tio - - discordpy | dpy - - aiohttp | - ``` - The following flags are available for this command: - ``` - - --source (Sends source code instead of links to the github repository) - """ - if not query: - lib = self._smart_guess_lib(ctx) + • wavelink | wl + • twitchio | tio + • discordpy | dpy + • aiohttp | - if not lib: - await ctx.reply("Sorry, I couldn't apply a default library to this channel. Try again with a library?") - return + The following flags are available for this command: - await ctx.send(str(lib.value), reference=ctx.replied_message) - return + • --source (Sends source code instead of links to the github repository) + """ source = False @@ -239,26 +250,14 @@ async def rtfs(self, ctx: core.Context, *, query: str) -> None: source = True query = query.replace("--source", "") - view = StringView(query) - maybe_lib = view.get_word() - view.skip_ws() - final_query = view.read_rest() - lib: LibEnum | None = None - - if maybe_lib in lib_names: - lib = lib_names[maybe_lib] - else: - final_query = query # ignore the stringview stuff then - - if lib is None: - lib = self._smart_guess_lib(ctx) - - if lib is None: - await ctx.reply("Sorry, I couldn't find a library that matched. Try again with a different library?") + optional = await self.get_lib(ctx, query) + if not optional: return + lib, final_query, tip = optional + if not final_query: - await ctx.reply(str(lib.value[0])) + await ctx.reply(str(lib.value[0]) + tip) return url = self.target.with_path("/api/public/rtfs").with_query( @@ -291,7 +290,7 @@ async def rtfs(self, ctx: core.Context, *, query: str) -> None: out = [f"[{name}]({url})" for name, url in nodes.items()] author: str = f"query Time: {float(matches['query_time']):.03f} • commit {matches['commit'][:6]}" - footer: str = f"Is the api behind on commits? Use {discord.utils.escape_mentions(ctx.prefix)}rtfs-reload" # type: ignore + footer: str | None = tip or None embed: discord.Embed = discord.Embed(title=f"{lib.name.title()}: {final_query}", colour=lib.value[1]) embed.description = "\n".join(out) @@ -302,9 +301,11 @@ async def rtfs(self, ctx: core.Context, *, query: str) -> None: else: n = next(iter(nodes.items())) - await ctx.send(f"Showing source for `{n[0]}`\nCommit: {matches['commit'][:6]}", reference=ctx.replied_message) + await ctx.send( + f"Showing source for `{n[0]}`\nCommit: {matches['commit'][:6]}" + tip, reference=ctx.replied_message + ) - pages = TextPager(ctx, n[1], prefix="```py") + pages = TextPager(ctx, n[1], prefix="```py", reply_author_takes_paginator=True) await pages.paginate()