From 5a7cfb3ce63c0dc9099b029363b315c942f13d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20N=C3=B8rgaard?= Date: Mon, 5 Jul 2021 05:40:57 +0100 Subject: [PATCH] [commands] Add ThreadConverter --- discord/ext/commands/converter.py | 40 +++++++++++++++++++++++++++++++ discord/ext/commands/errors.py | 17 +++++++++++++ 2 files changed, 57 insertions(+) diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index 2516b4fa4..c07ad07ca 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -71,6 +71,7 @@ __all__ = ( 'CategoryChannelConverter', 'IDConverter', 'StoreChannelConverter', + 'ThreadConverter', 'GuildChannelConverter', 'clean_content', 'Greedy', @@ -426,6 +427,28 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]): return result + @staticmethod + def _resolve_thread(ctx: Context, argument: str, attribute: str, type: Type[CT]) -> CT: + bot = ctx.bot + + match = IDConverter._get_id_match(argument) or re.match(r'<#([0-9]{15,20})>$', argument) + result = None + guild = ctx.guild + + if match is None: + # not a mention + if guild: + iterable: Iterable[CT] = getattr(guild, attribute) + result: Optional[CT] = discord.utils.get(iterable, name=argument) + else: + thread_id = int(match.group(1)) + if guild: + result = guild.get_thread(thread_id) + + if not result or not isinstance(result, type): + raise ThreadNotFound(argument) + + return result class TextChannelConverter(IDConverter[discord.TextChannel]): """Converts to a :class:`~discord.TextChannel`. @@ -524,6 +547,22 @@ class StoreChannelConverter(IDConverter[discord.StoreChannel]): async def convert(self, ctx: Context, argument: str) -> discord.StoreChannel: return GuildChannelConverter._resolve_channel(ctx, argument, 'channels', discord.StoreChannel) +class ThreadConverter(IDConverter[discord.Thread]): + """Coverts to a :class:`~discord.Thread`. + + All lookups are via the local guild. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by mention. + 3. Lookup by name. + + .. versionadded: 2.0 + """ + + async def convert(self, ctx: Context, argument: str) -> discord.Thread: + return GuildChannelConverter._resolve_thread(ctx, argument, 'threads', discord.Thread) class ColourConverter(Converter[discord.Colour]): """Converts to a :class:`~discord.Colour`. @@ -947,6 +986,7 @@ CONVERTER_MAPPING: Dict[Type[Any], Any] = { discord.PartialEmoji: PartialEmojiConverter, discord.CategoryChannel: CategoryChannelConverter, discord.StoreChannel: StoreChannelConverter, + discord.Thread: ThreadConverter, discord.abc.GuildChannel: GuildChannelConverter, } diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py index 901a0e56c..0f162cff8 100644 --- a/discord/ext/commands/errors.py +++ b/discord/ext/commands/errors.py @@ -47,6 +47,7 @@ __all__ = ( 'GuildNotFound', 'UserNotFound', 'ChannelNotFound', + 'ThreadNotFound', 'ChannelNotReadable', 'BadColourArgument', 'BadColorArgument', @@ -336,6 +337,22 @@ class ChannelNotFound(BadArgument): self.argument = argument super().__init__(f'Channel "{argument}" not found.') +class ThreadNotFound(BadArgument): + """Exception raised when the bot can not find the thread. + + This inherits from :exc:`BadArgument` + + ..versionadded:: 2.0 + + Attributes + ----------- + argument: :class:`str` + The thread supplied by the caller that was not found + """ + def __init__(self, argument): + self.argument = argument + super().__init__(f'Thread "{argument}" not found.') + class BadColourArgument(BadArgument): """Exception raised when the colour is not valid.