diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index b7abfdaa2..3413dc084 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -80,6 +80,7 @@ __all__ = ( 'ThreadConverter', 'GuildChannelConverter', 'GuildStickerConverter', + 'ScheduledEventConverter', 'clean_content', 'Greedy', 'run_converters', @@ -844,7 +845,7 @@ class GuildStickerConverter(IDConverter[discord.GuildSticker]): The lookup strategy is as follows (in order): 1. Lookup by ID. - 3. Lookup by name + 2. Lookup by name. .. versionadded:: 2.0 """ @@ -874,6 +875,65 @@ class GuildStickerConverter(IDConverter[discord.GuildSticker]): return result +class ScheduledEventConverter(IDConverter[discord.ScheduledEvent]): + """Converts to a :class:`~discord.ScheduledEvent`. + + All lookups are done for the local guild first, if available. If that lookup + fails, then it checks the client's global cache. + + The lookup strategy is as follows (in order): + + 1. Lookup by ID. + 2. Lookup by url. + 3. Lookup by name. + + .. versionadded:: 2.0 + """ + + async def convert(self, ctx: Context[_Bot], argument: str) -> discord.ScheduledEvent: + guild = ctx.guild + match = self._get_id_match(argument) + result = None + + if match: + # ID match + event_id = int(match.group(1)) + if guild: + result = guild.get_scheduled_event(event_id) + else: + for guild in ctx.bot.guilds: + result = guild.get_scheduled_event(event_id) + if result: + break + else: + pattern = ( + r'https?://(?:(ptb|canary|www)\.)?discord\.com/events/' + r'(?P[0-9]{15,20})/' + r'(?P[0-9]{15,20})$' + ) + match = re.match(pattern, argument, flags=re.I) + if match: + # URL match + guild = ctx.bot.get_guild(int(match.group('guild_id'))) + + if guild: + event_id = int(match.group('event_id')) + result = guild.get_scheduled_event(event_id) + else: + # lookup by name + if guild: + result = discord.utils.get(guild.scheduled_events, name=argument) + else: + for guild in ctx.bot.guilds: + result = discord.utils.get(guild.scheduled_events, name=argument) + if result: + break + if result is None: + raise ScheduledEventNotFound(argument) + + return result + + class clean_content(Converter[str]): """Converts the argument to mention scrubbed version of said content. @@ -1064,6 +1124,7 @@ CONVERTER_MAPPING: Dict[Type[Any], Any] = { discord.Thread: ThreadConverter, discord.abc.GuildChannel: GuildChannelConverter, discord.GuildSticker: GuildStickerConverter, + discord.ScheduledEvent: ScheduledEventConverter, } diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py index dab8290dc..2047759e0 100644 --- a/discord/ext/commands/errors.py +++ b/discord/ext/commands/errors.py @@ -70,6 +70,7 @@ __all__ = ( 'BadInviteArgument', 'EmojiNotFound', 'GuildStickerNotFound', + 'ScheduledEventNotFound', 'PartialEmojiConversionFailure', 'BadBoolArgument', 'MissingRole', @@ -515,6 +516,24 @@ class GuildStickerNotFound(BadArgument): super().__init__(f'Sticker "{argument}" not found.') +class ScheduledEventNotFound(BadArgument): + """Exception raised when the bot can not find the scheduled event. + + This inherits from :exc:`BadArgument` + + .. versionadded:: 2.0 + + Attributes + ----------- + argument: :class:`str` + The event supplied by the caller that was not found + """ + + def __init__(self, argument: str) -> None: + self.argument: str = argument + super().__init__(f'ScheduledEvent "{argument}" not found.') + + class BadBoolArgument(BadArgument): """Exception raised when a boolean argument was not convertable. diff --git a/docs/ext/commands/api.rst b/docs/ext/commands/api.rst index 32d3f6f4b..fa95b30e1 100644 --- a/docs/ext/commands/api.rst +++ b/docs/ext/commands/api.rst @@ -419,6 +419,9 @@ Converters .. autoclass:: discord.ext.commands.GuildStickerConverter :members: +.. autoclass:: discord.ext.commands.ScheduledEventConverter + :members: + .. autoclass:: discord.ext.commands.clean_content :members: @@ -547,6 +550,9 @@ Exceptions .. autoexception:: discord.ext.commands.GuildStickerNotFound :members: +.. autoexception:: discord.ext.commands.ScheduledEventNotFound + :members: + .. autoexception:: discord.ext.commands.BadBoolArgument :members: @@ -631,6 +637,7 @@ Exception Hierarchy - :exc:`~.commands.BadInviteArgument` - :exc:`~.commands.EmojiNotFound` - :exc:`~.commands.GuildStickerNotFound` + - :exc:`~.commands.ScheduledEventNotFound` - :exc:`~.commands.PartialEmojiConversionFailure` - :exc:`~.commands.BadBoolArgument` - :exc:`~.commands.ThreadNotFound` diff --git a/docs/ext/commands/commands.rst b/docs/ext/commands/commands.rst index 22e997fa0..77f11cd29 100644 --- a/docs/ext/commands/commands.rst +++ b/docs/ext/commands/commands.rst @@ -393,6 +393,8 @@ A lot of discord models work out of the gate as a parameter: - :class:`Emoji` - :class:`PartialEmoji` - :class:`Thread` (since v2.0) +- :class:`GuildSticker` (since v2.0) +- :class:`ScheduledEvent` (since v2.0) Having any of these set as the converter will intelligently convert the argument to the appropriate target type you specify. @@ -441,6 +443,10 @@ converter is given below: +--------------------------+-------------------------------------------------+ | :class:`Thread` | :class:`~ext.commands.ThreadConverter` | +--------------------------+-------------------------------------------------+ +| :class:`GuildSticker` | :class:`~ext.commands.GuildStickerConverter` | ++--------------------------+-------------------------------------------------+ +| :class:`ScheduledEvent` | :class:`~ext.commands.ScheduledEventConverter` | ++--------------------------+-------------------------------------------------+ By providing the converter it allows us to use them as building blocks for another converter: