From 9d0cf91e237e7716c618014676a90ed4d780495a Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Sun, 26 Jun 2022 19:22:38 +0400 Subject: Add Typehints To Typing Patcher Signed-off-by: Hassan Abouelela --- botcore/utils/_monkey_patches.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/_monkey_patches.py b/botcore/utils/_monkey_patches.py index f2c6c100..c2f8aa10 100644 --- a/botcore/utils/_monkey_patches.py +++ b/botcore/utils/_monkey_patches.py @@ -1,6 +1,7 @@ """Contains all common monkey patches, used to alter discord to fit our needs.""" import logging +import typing from datetime import datetime, timedelta from functools import partial, partialmethod @@ -46,9 +47,9 @@ def _patch_typing() -> None: log.debug("Patching send_typing, which should fix things breaking when Discord disables typing events. Stay safe!") original = http.HTTPClient.send_typing - last_403 = None + last_403: typing.Optional[datetime] = None - async def honeybadger_type(self, channel_id: int) -> None: # noqa: ANN001 + async def honeybadger_type(self: http.HTTPClient, channel_id: int) -> None: nonlocal last_403 if last_403 and (datetime.utcnow() - last_403) < timedelta(minutes=5): log.warning("Not sending typing event, we got a 403 less than 5 minutes ago.") -- cgit v1.2.3 From a9966239af6edc1cd9b6133de41f03d15ceec285 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Sun, 26 Jun 2022 19:23:14 +0400 Subject: Fix Docstring For Role Change Wrapper Util Signed-off-by: Hassan Abouelela --- botcore/utils/members.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/members.py b/botcore/utils/members.py index e89b4618..10513953 100644 --- a/botcore/utils/members.py +++ b/botcore/utils/members.py @@ -1,5 +1,4 @@ """Useful helper functions for interactin with :obj:`discord.Member` objects.""" - import typing import discord @@ -30,18 +29,19 @@ async def get_or_fetch_member(guild: discord.Guild, member_id: int) -> typing.Op async def handle_role_change( member: discord.Member, - coro: typing.Callable[..., typing.Coroutine], + coro: typing.Callable[[discord.Role], typing.Coroutine], role: discord.Role ) -> None: """ - Await the given ``coro`` with ``member`` as the sole argument. + Await the given ``coro`` with ``role`` as the sole argument. Handle errors that we expect to be raised from :obj:`discord.Member.add_roles` and :obj:`discord.Member.remove_roles`. Args: - member: The member to pass to ``coro``. + member: The member that is being modified for logging purposes. coro: This is intended to be :obj:`discord.Member.add_roles` or :obj:`discord.Member.remove_roles`. + role: The role to be passed to ``coro``. """ try: await coro(role) -- cgit v1.2.3 From f17f947006ae55f3baca2eeec7ce804dbfdac238 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Sun, 26 Jun 2022 19:23:46 +0400 Subject: Fix Incorrect Typehints & Docstrings Signed-off-by: Hassan Abouelela --- botcore/site_api.py | 2 +- botcore/utils/scheduling.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/site_api.py b/botcore/site_api.py index dbdf4f3b..d3a58b9c 100644 --- a/botcore/site_api.py +++ b/botcore/site_api.py @@ -26,7 +26,7 @@ class ResponseCodeError(ValueError): Args: response (:obj:`aiohttp.ClientResponse`): The response object from the request. response_json: The JSON response returned from the request, if any. - request_text: The text of the request, if any. + response_text: The text of the request, if any. """ self.status = response.status self.response_json = response_json or {} diff --git a/botcore/utils/scheduling.py b/botcore/utils/scheduling.py index 164f6b10..d9c3937a 100644 --- a/botcore/utils/scheduling.py +++ b/botcore/utils/scheduling.py @@ -209,7 +209,7 @@ class Scheduler: def create_task( - coro: typing.Awaitable, + coro: typing.Coroutine, *, suppressed_exceptions: tuple[typing.Type[Exception]] = (), event_loop: typing.Optional[asyncio.AbstractEventLoop] = None, -- cgit v1.2.3 From ecb9a7e4f6b1ddeecbf0e00af4a149a99b4fa4c9 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Sun, 26 Jun 2022 21:18:30 +0400 Subject: Document Create Task Return Type Signed-off-by: Hassan Abouelela --- botcore/utils/scheduling.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/scheduling.py b/botcore/utils/scheduling.py index d9c3937a..8d2f875e 100644 --- a/botcore/utils/scheduling.py +++ b/botcore/utils/scheduling.py @@ -208,13 +208,16 @@ class Scheduler: self._log.error(f"Error in task #{task_id} {id(done_task)}!", exc_info=exception) +TASK_RETURN = typing.TypeVar("TASK_RETURN") + + def create_task( - coro: typing.Coroutine, + coro: typing.Coroutine[typing.Any, typing.Any, TASK_RETURN], *, suppressed_exceptions: tuple[typing.Type[Exception]] = (), event_loop: typing.Optional[asyncio.AbstractEventLoop] = None, **kwargs, -) -> asyncio.Task: +) -> asyncio.Task[TASK_RETURN]: """ Wrapper for creating an :obj:`asyncio.Task` which logs exceptions raised in the task. -- cgit v1.2.3 From 5efb69b2070ee972dd58401c06c0313513593a38 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Sun, 26 Jun 2022 21:42:07 +0400 Subject: Replace Typing Generics Replaces all typing generics with collection equivalents as per PEP 585. `typing.Callable` was not included in this due to a sphinx-autodoc bug not handling it well. Signed-off-by: Hassan Abouelela --- botcore/utils/members.py | 3 ++- botcore/utils/scheduling.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/members.py b/botcore/utils/members.py index 10513953..1536a8d1 100644 --- a/botcore/utils/members.py +++ b/botcore/utils/members.py @@ -1,5 +1,6 @@ """Useful helper functions for interactin with :obj:`discord.Member` objects.""" import typing +from collections import abc import discord @@ -29,7 +30,7 @@ async def get_or_fetch_member(guild: discord.Guild, member_id: int) -> typing.Op async def handle_role_change( member: discord.Member, - coro: typing.Callable[[discord.Role], typing.Coroutine], + coro: typing.Callable[[discord.Role], abc.Coroutine], role: discord.Role ) -> None: """ diff --git a/botcore/utils/scheduling.py b/botcore/utils/scheduling.py index 8d2f875e..8e30d63b 100644 --- a/botcore/utils/scheduling.py +++ b/botcore/utils/scheduling.py @@ -4,6 +4,7 @@ import asyncio import contextlib import inspect import typing +from collections import abc from datetime import datetime from functools import partial @@ -38,7 +39,7 @@ class Scheduler: self.name = name self._log = logging.get_logger(f"{__name__}.{name}") - self._scheduled_tasks: typing.Dict[typing.Hashable, asyncio.Task] = {} + self._scheduled_tasks: dict[typing.Hashable, asyncio.Task] = {} def __contains__(self, task_id: typing.Hashable) -> bool: """ @@ -52,7 +53,7 @@ class Scheduler: """ return task_id in self._scheduled_tasks - def schedule(self, task_id: typing.Hashable, coroutine: typing.Coroutine) -> None: + def schedule(self, task_id: typing.Hashable, coroutine: abc.Coroutine) -> None: """ Schedule the execution of a ``coroutine``. @@ -79,7 +80,7 @@ class Scheduler: self._scheduled_tasks[task_id] = task self._log.debug(f"Scheduled task #{task_id} {id(task)}.") - def schedule_at(self, time: datetime, task_id: typing.Hashable, coroutine: typing.Coroutine) -> None: + def schedule_at(self, time: datetime, task_id: typing.Hashable, coroutine: abc.Coroutine) -> None: """ Schedule ``coroutine`` to be executed at the given ``time``. @@ -107,7 +108,7 @@ class Scheduler: self, delay: typing.Union[int, float], task_id: typing.Hashable, - coroutine: typing.Coroutine + coroutine: abc.Coroutine ) -> None: """ Schedule ``coroutine`` to be executed after ``delay`` seconds. @@ -151,7 +152,7 @@ class Scheduler: self, delay: typing.Union[int, float], task_id: typing.Hashable, - coroutine: typing.Coroutine + coroutine: abc.Coroutine ) -> None: """Await ``coroutine`` after ``delay`` seconds.""" try: @@ -212,7 +213,7 @@ TASK_RETURN = typing.TypeVar("TASK_RETURN") def create_task( - coro: typing.Coroutine[typing.Any, typing.Any, TASK_RETURN], + coro: abc.Coroutine[typing.Any, typing.Any, TASK_RETURN], *, suppressed_exceptions: tuple[typing.Type[Exception]] = (), event_loop: typing.Optional[asyncio.AbstractEventLoop] = None, @@ -241,7 +242,7 @@ def create_task( return task -def _log_task_exception(task: asyncio.Task, *, suppressed_exceptions: typing.Tuple[typing.Type[Exception]]) -> None: +def _log_task_exception(task: asyncio.Task, *, suppressed_exceptions: tuple[type[Exception]]) -> None: """Retrieve and log the exception raised in ``task`` if one exists.""" with contextlib.suppress(asyncio.CancelledError): exception = task.exception() -- cgit v1.2.3 From 7913afabf954426119fe353485bdaaf4074a7e26 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Wed, 29 Jun 2022 13:38:05 +0400 Subject: Switch `typing.Hashable` With `Collections.abc.Hashable` Switches out the Hashable type from the typing library for the generic from collections. Signed-off-by: Hassan Abouelela --- botcore/utils/scheduling.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/scheduling.py b/botcore/utils/scheduling.py index 8e30d63b..ebc42665 100644 --- a/botcore/utils/scheduling.py +++ b/botcore/utils/scheduling.py @@ -39,9 +39,9 @@ class Scheduler: self.name = name self._log = logging.get_logger(f"{__name__}.{name}") - self._scheduled_tasks: dict[typing.Hashable, asyncio.Task] = {} + self._scheduled_tasks: dict[abc.Hashable, asyncio.Task] = {} - def __contains__(self, task_id: typing.Hashable) -> bool: + def __contains__(self, task_id: abc.Hashable) -> bool: """ Return :obj:`True` if a task with the given ``task_id`` is currently scheduled. @@ -53,7 +53,7 @@ class Scheduler: """ return task_id in self._scheduled_tasks - def schedule(self, task_id: typing.Hashable, coroutine: abc.Coroutine) -> None: + def schedule(self, task_id: abc.Hashable, coroutine: abc.Coroutine) -> None: """ Schedule the execution of a ``coroutine``. @@ -80,7 +80,7 @@ class Scheduler: self._scheduled_tasks[task_id] = task self._log.debug(f"Scheduled task #{task_id} {id(task)}.") - def schedule_at(self, time: datetime, task_id: typing.Hashable, coroutine: abc.Coroutine) -> None: + def schedule_at(self, time: datetime, task_id: abc.Hashable, coroutine: abc.Coroutine) -> None: """ Schedule ``coroutine`` to be executed at the given ``time``. @@ -107,7 +107,7 @@ class Scheduler: def schedule_later( self, delay: typing.Union[int, float], - task_id: typing.Hashable, + task_id: abc.Hashable, coroutine: abc.Coroutine ) -> None: """ @@ -123,7 +123,7 @@ class Scheduler: """ self.schedule(task_id, self._await_later(delay, task_id, coroutine)) - def cancel(self, task_id: typing.Hashable) -> None: + def cancel(self, task_id: abc.Hashable) -> None: """ Unschedule the task identified by ``task_id``. Log a warning if the task doesn't exist. @@ -151,7 +151,7 @@ class Scheduler: async def _await_later( self, delay: typing.Union[int, float], - task_id: typing.Hashable, + task_id: abc.Hashable, coroutine: abc.Coroutine ) -> None: """Await ``coroutine`` after ``delay`` seconds.""" @@ -174,7 +174,7 @@ class Scheduler: else: self._log.debug(f"Finally block reached for #{task_id}; {state=}") - def _task_done_callback(self, task_id: typing.Hashable, done_task: asyncio.Task) -> None: + def _task_done_callback(self, task_id: abc.Hashable, done_task: asyncio.Task) -> None: """ Delete the task and raise its exception if one exists. @@ -215,7 +215,7 @@ TASK_RETURN = typing.TypeVar("TASK_RETURN") def create_task( coro: abc.Coroutine[typing.Any, typing.Any, TASK_RETURN], *, - suppressed_exceptions: tuple[typing.Type[Exception]] = (), + suppressed_exceptions: tuple[type[Exception]] = (), event_loop: typing.Optional[asyncio.AbstractEventLoop] = None, **kwargs, ) -> asyncio.Task[TASK_RETURN]: -- cgit v1.2.3 From 65410a9c11d70cadd8a6d16ffc386a7cad3d1f0b Mon Sep 17 00:00:00 2001 From: Izan Date: Thu, 14 Jul 2022 21:41:32 +0100 Subject: Add `clean_text_or_reply` util. --- botcore/utils/commands.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 botcore/utils/commands.py (limited to 'botcore/utils') diff --git a/botcore/utils/commands.py b/botcore/utils/commands.py new file mode 100644 index 00000000..2d380bef --- /dev/null +++ b/botcore/utils/commands.py @@ -0,0 +1,21 @@ +from typing import Optional + +from discord import Message +from discord.ext.commands import Context, clean_content + + +async def clean_text_or_reply(ctx: Context, text: Optional[str] = None) -> Optional[str]: + """Returns cleaned version of `text`, if given, else referenced message, if found, else `None`.""" + clean_content_converter = clean_content(fix_channel_mentions=True) + + if text: + return await clean_content_converter.convert(ctx, text) + + if ( + (replied_message := getattr(ctx.message.reference, "resolved", None)) # message has a cached reference + and isinstance(replied_message, Message) # referenced message hasn't been deleted + ): + return await clean_content_converter.convert(ctx, ctx.message.reference.resolved.content) + + # No text provided, and either no message was referenced or we can't access the content + return None -- cgit v1.2.3 From d1ec5b0a78d8bc2a608b61585eab3b2e3c44de24 Mon Sep 17 00:00:00 2001 From: Izan Date: Fri, 15 Jul 2022 21:56:34 +0100 Subject: Add commands.py to __init__.py --- botcore/utils/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'botcore/utils') diff --git a/botcore/utils/__init__.py b/botcore/utils/__init__.py index fa389743..2e493374 100644 --- a/botcore/utils/__init__.py +++ b/botcore/utils/__init__.py @@ -1,6 +1,6 @@ """Useful utilities and tools for Discord bot development.""" -from botcore.utils import _monkey_patches, caching, channel, logging, members, regex, scheduling +from botcore.utils import _monkey_patches, caching, channel, commands, logging, members, regex, scheduling from botcore.utils._extensions import unqualify @@ -24,6 +24,7 @@ __all__ = [ apply_monkey_patches, caching, channel, + commands, logging, members, regex, -- cgit v1.2.3 From c20c19c9cefb207586e9b51edf98948e1d710333 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sat, 16 Jul 2022 19:13:11 +0100 Subject: Add a generic view and button The view implements an interaction check for allowed_users and allowed_roles. The button deleted the message attached the the parent view on click. --- botcore/utils/__init__.py | 3 +- botcore/utils/interactions.py | 86 +++++++++++++++++++++++++++++++++++++++++++ docs/changelog.rst | 5 +++ pyproject.toml | 2 +- 4 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 botcore/utils/interactions.py (limited to 'botcore/utils') diff --git a/botcore/utils/__init__.py b/botcore/utils/__init__.py index fa389743..dfdd6df5 100644 --- a/botcore/utils/__init__.py +++ b/botcore/utils/__init__.py @@ -1,6 +1,6 @@ """Useful utilities and tools for Discord bot development.""" -from botcore.utils import _monkey_patches, caching, channel, logging, members, regex, scheduling +from botcore.utils import _monkey_patches, caching, channel, interactions, logging, members, regex, scheduling from botcore.utils._extensions import unqualify @@ -24,6 +24,7 @@ __all__ = [ apply_monkey_patches, caching, channel, + interactions, logging, members, regex, diff --git a/botcore/utils/interactions.py b/botcore/utils/interactions.py new file mode 100644 index 00000000..6d632c53 --- /dev/null +++ b/botcore/utils/interactions.py @@ -0,0 +1,86 @@ +from typing import Optional, Sequence + +from discord import ButtonStyle, Interaction, ui + +from botcore.utils.logging import get_logger + +log = get_logger(__name__) + + +class ViewWithUserAndRoleCheck(ui.View): + """ + A view that allows the original invoker and moderators to interact with it. + + Args: + allowed_users: A sequence of user's ids who are allowed to interact with the view. + allowed_roles: A sequence of role ids that are allowed to interact with the view. + timeout: Timeout in seconds from last interaction with the UI before no longer accepting input. + If ``None`` then there is no timeout. + """ + + def __init__( + self, + *, + allowed_users: Sequence[int], + allowed_roles: Sequence[int], + timeout: Optional[float] = 180.0 + ) -> None: + super().__init__(timeout=timeout) + self.allowed_users = allowed_users + self.allowed_roles = allowed_roles + + async def interaction_check(self, interaction: Interaction) -> bool: + """ + Ensure the user clicking the button is the view invoker, or a moderator. + + Args: + interaction: The interaction that occurred. + """ + if interaction.user.id in self.allowed_users: + log.trace( + "Allowed interaction by %s (%d) on %d as they are an allowed user.", + interaction.user, + interaction.user.id, + interaction.message.id, + ) + return True + + if any(role.id in self.allowed_roles for role in getattr(interaction.user, "roles", [])): + log.trace( + "Allowed interaction by %s (%d)on %d as they have an allowed role.", + interaction.user, + interaction.user.id, + interaction.message.id, + ) + return True + + await interaction.response.send_message("This is not your button to click!", ephemeral=True) + return False + + +class DeleteMessageButton(ui.Button): + """ + A button that can be added to a view to delete the message containing the view on click. + + This button itself carries out no interaction checks, these should be done by the parent view. + + See :obj:`botcore.utils.interactions.ViewWithUserAndRoleCheck` for a view that implements basic checks. + + Args: + style (:literal-url:`ButtonStyle `): + The style of the button, set to ``ButtonStyle.secondary`` if not specified. + label: The label of the button, set to "Delete" if not specified. + """ # noqa: E501 + + def __init__( + self, + *, + style: ButtonStyle = ButtonStyle.secondary, + label: str = "Delete", + **kwargs + ): + super().__init__(style=style, label=label, **kwargs) + + async def callback(self, interaction: Interaction) -> None: + """Delete the original message on button click.""" + await interaction.delete_original_message() diff --git a/docs/changelog.rst b/docs/changelog.rst index 9d74eee8..cbe37062 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,11 @@ Changelog ========= +- :release:`7.3.0 <16th July 2022>` +- :feature:`103` Add a generic view :obj:`botcore.utils.interactions.ViewWithUserAndRoleCheck` that only allows specified users and roles to interaction with it +- :feature:`103` Add a button :obj:`botcore.utils.interactions.DeleteMessageButton` that deletes the message attached to its parent view. + + - :release:`7.2.2 <9th July 2022>` - :bug:`98` Only close ``BotBase.stats._transport`` if ``BotBase.stats`` was created diff --git a/pyproject.toml b/pyproject.toml index ccb1a39d..cff654ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bot-core" -version = "7.2.2" +version = "7.3.0" description = "Bot-Core provides the core functionality and utilities for the bots of the Python Discord community." authors = ["Python Discord "] license = "MIT" -- cgit v1.2.3 From f99b395b74bdbdcbdf36566966162cc06ed4d9b4 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sat, 16 Jul 2022 22:06:26 +0100 Subject: Fix interactions.DeleteMessageButton not working due to using wrong delete method. --- botcore/utils/interactions.py | 2 +- docs/changelog.rst | 4 ++++ pyproject.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/interactions.py b/botcore/utils/interactions.py index 6d632c53..60b8c1f7 100644 --- a/botcore/utils/interactions.py +++ b/botcore/utils/interactions.py @@ -83,4 +83,4 @@ class DeleteMessageButton(ui.Button): async def callback(self, interaction: Interaction) -> None: """Delete the original message on button click.""" - await interaction.delete_original_message() + await interaction.message.delete() diff --git a/docs/changelog.rst b/docs/changelog.rst index cbe37062..493b34e2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changelog ========= +- :release:`7.3.1 <16th July 2022>` +- :bug:`104` Fix :obj:`botcore.utils.interactions.DeleteMessageButton` not working due to using wrong delete method. + + - :release:`7.3.0 <16th July 2022>` - :feature:`103` Add a generic view :obj:`botcore.utils.interactions.ViewWithUserAndRoleCheck` that only allows specified users and roles to interaction with it - :feature:`103` Add a button :obj:`botcore.utils.interactions.DeleteMessageButton` that deletes the message attached to its parent view. diff --git a/pyproject.toml b/pyproject.toml index cff654ca..ab406c86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bot-core" -version = "7.3.0" +version = "7.3.1" description = "Bot-Core provides the core functionality and utilities for the bots of the Python Discord community." authors = ["Python Discord "] license = "MIT" -- cgit v1.2.3 From 603fc83bcfde790f184ddaf0ab2bd5364431d067 Mon Sep 17 00:00:00 2001 From: ChrisJL Date: Thu, 21 Jul 2022 13:00:34 +0100 Subject: Add an optional message attr to ViewWithUserAndRoleCheck On view timeout, this message has it's view removed if set. Co-authored-by: Boris Muratov --- botcore/utils/interactions.py | 16 ++++++++++++++-- docs/changelog.rst | 4 ++++ pyproject.toml | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/interactions.py b/botcore/utils/interactions.py index 60b8c1f7..26bd92f2 100644 --- a/botcore/utils/interactions.py +++ b/botcore/utils/interactions.py @@ -1,6 +1,7 @@ +import contextlib from typing import Optional, Sequence -from discord import ButtonStyle, Interaction, ui +from discord import ButtonStyle, Interaction, Message, NotFound, ui from botcore.utils.logging import get_logger @@ -16,6 +17,8 @@ class ViewWithUserAndRoleCheck(ui.View): allowed_roles: A sequence of role ids that are allowed to interact with the view. timeout: Timeout in seconds from last interaction with the UI before no longer accepting input. If ``None`` then there is no timeout. + message: The message to remove the view from on timeout. This can also be set with + ``view.message = await ctx.send( ... )``` , or similar, after the view is instantiated. """ def __init__( @@ -23,11 +26,13 @@ class ViewWithUserAndRoleCheck(ui.View): *, allowed_users: Sequence[int], allowed_roles: Sequence[int], - timeout: Optional[float] = 180.0 + timeout: Optional[float] = 180.0, + message: Optional[Message] = None ) -> None: super().__init__(timeout=timeout) self.allowed_users = allowed_users self.allowed_roles = allowed_roles + self.message = message async def interaction_check(self, interaction: Interaction) -> bool: """ @@ -57,6 +62,13 @@ class ViewWithUserAndRoleCheck(ui.View): await interaction.response.send_message("This is not your button to click!", ephemeral=True) return False + async def on_timeout(self) -> None: + """Remove the view from ``self.message`` if set.""" + if self.message: + with contextlib.suppress(NotFound): + # Cover the case where this message has already been deleted by external means + await self.message.edit(view=None) + class DeleteMessageButton(ui.Button): """ diff --git a/docs/changelog.rst b/docs/changelog.rst index 493b34e2..e35e75d0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changelog ========= +- :release:`7.4.0 <17th July 2022>` +- :feature:`106` Add an optional ``message`` attr to :obj:`botcore.utils.interactions.ViewWithUserAndRoleCheck`. On view timeout, this message has its view removed if set. + + - :release:`7.3.1 <16th July 2022>` - :bug:`104` Fix :obj:`botcore.utils.interactions.DeleteMessageButton` not working due to using wrong delete method. diff --git a/pyproject.toml b/pyproject.toml index ab406c86..6c91dde3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bot-core" -version = "7.3.1" +version = "7.4.0" description = "Bot-Core provides the core functionality and utilities for the bots of the Python Discord community." authors = ["Python Discord "] license = "MIT" -- cgit v1.2.3 From 85063ab4cd2ad6a7d6621645c049d0917affbfbe Mon Sep 17 00:00:00 2001 From: Izan Date: Sat, 23 Jul 2022 14:22:30 +0100 Subject: Reformat docstring to use Google's style & raise error instead of returning None --- botcore/utils/commands.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/commands.py b/botcore/utils/commands.py index 2d380bef..a99b3852 100644 --- a/botcore/utils/commands.py +++ b/botcore/utils/commands.py @@ -1,11 +1,24 @@ from typing import Optional from discord import Message -from discord.ext.commands import Context, clean_content +from discord.ext.commands import BadArgument, Context, clean_content -async def clean_text_or_reply(ctx: Context, text: Optional[str] = None) -> Optional[str]: - """Returns cleaned version of `text`, if given, else referenced message, if found, else `None`.""" +async def clean_text_or_reply(ctx: Context, text: Optional[str] = None) -> str: + """ + Cleans a text argument or replied message's content. + + Args: + ctx: The command's context + text: The provided text argument of the command (if given) + + Raises: + :exc:`discord.ext.commands.BadArgument` + `text` wasn't provided and there's no reply message. + + Returns: + The cleaned version of `text`, if given, else replied message. + """ clean_content_converter = clean_content(fix_channel_mentions=True) if text: @@ -18,4 +31,4 @@ async def clean_text_or_reply(ctx: Context, text: Optional[str] = None) -> Optio return await clean_content_converter.convert(ctx, ctx.message.reference.resolved.content) # No text provided, and either no message was referenced or we can't access the content - return None + raise BadArgument("Couldn't find text to clean. Provide a string or reply to a message to use its content.") -- cgit v1.2.3 From 4e43f3c4705c8c26cb2b4c5ca01e1f2885766c80 Mon Sep 17 00:00:00 2001 From: Izan Date: Sat, 23 Jul 2022 14:25:45 +0100 Subject: Raise error when referenced message has no content --- botcore/utils/commands.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/commands.py b/botcore/utils/commands.py index a99b3852..7afd8137 100644 --- a/botcore/utils/commands.py +++ b/botcore/utils/commands.py @@ -14,7 +14,7 @@ async def clean_text_or_reply(ctx: Context, text: Optional[str] = None) -> str: Raises: :exc:`discord.ext.commands.BadArgument` - `text` wasn't provided and there's no reply message. + `text` wasn't provided and there's no reply message / reply message content. Returns: The cleaned version of `text`, if given, else replied message. @@ -28,7 +28,11 @@ async def clean_text_or_reply(ctx: Context, text: Optional[str] = None) -> str: (replied_message := getattr(ctx.message.reference, "resolved", None)) # message has a cached reference and isinstance(replied_message, Message) # referenced message hasn't been deleted ): - return await clean_content_converter.convert(ctx, ctx.message.reference.resolved.content) + if not (content := ctx.message.reference.resolved.content): + # The referenced message doesn't have a content (e.g. embed/image), so raise error + raise BadArgument("The referenced message doesn't have a text content.") + + return await clean_content_converter.convert(ctx, content) # No text provided, and either no message was referenced or we can't access the content raise BadArgument("Couldn't find text to clean. Provide a string or reply to a message to use its content.") -- cgit v1.2.3 From 20ed26e794c677205417a475b5d3719822e4bc4b Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 26 Jul 2022 17:58:23 +0200 Subject: Fix suppressed_exceptions type hint (#112) The previous type hint expected a tuple with a single exception type instead of a variable length tuple of exception types --- botcore/utils/scheduling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/scheduling.py b/botcore/utils/scheduling.py index ebc42665..9517df6d 100644 --- a/botcore/utils/scheduling.py +++ b/botcore/utils/scheduling.py @@ -215,7 +215,7 @@ TASK_RETURN = typing.TypeVar("TASK_RETURN") def create_task( coro: abc.Coroutine[typing.Any, typing.Any, TASK_RETURN], *, - suppressed_exceptions: tuple[type[Exception]] = (), + suppressed_exceptions: tuple[type[Exception], ...] = (), event_loop: typing.Optional[asyncio.AbstractEventLoop] = None, **kwargs, ) -> asyncio.Task[TASK_RETURN]: @@ -242,7 +242,7 @@ def create_task( return task -def _log_task_exception(task: asyncio.Task, *, suppressed_exceptions: tuple[type[Exception]]) -> None: +def _log_task_exception(task: asyncio.Task, *, suppressed_exceptions: tuple[type[Exception], ...]) -> None: """Retrieve and log the exception raised in ``task`` if one exists.""" with contextlib.suppress(asyncio.CancelledError): exception = task.exception() -- cgit v1.2.3 From 4ff87abda66ac8c6b988477c03fd7163f95e259f Mon Sep 17 00:00:00 2001 From: ionite34 Date: Tue, 16 Aug 2022 18:29:48 -0400 Subject: Added regex for leading https or www --- botcore/utils/regex.py | 1 + 1 file changed, 1 insertion(+) (limited to 'botcore/utils') diff --git a/botcore/utils/regex.py b/botcore/utils/regex.py index 56c50dad..e074a342 100644 --- a/botcore/utils/regex.py +++ b/botcore/utils/regex.py @@ -3,6 +3,7 @@ import re DISCORD_INVITE = re.compile( + r"((https?://)?(www\.)?)?" r"(discord([.,]|dot)gg|" # Could be discord.gg/ r"discord([.,]|dot)com(/|slash)invite|" # or discord.com/invite/ r"discordapp([.,]|dot)com(/|slash)invite|" # or discordapp.com/invite/ -- cgit v1.2.3 From 360ef04dba45950c013e4aee9ab63d0dc5386000 Mon Sep 17 00:00:00 2001 From: ionite34 Date: Tue, 16 Aug 2022 18:31:38 -0400 Subject: Removed a redundant regex character class --- botcore/utils/regex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'botcore/utils') diff --git a/botcore/utils/regex.py b/botcore/utils/regex.py index e074a342..76311ca7 100644 --- a/botcore/utils/regex.py +++ b/botcore/utils/regex.py @@ -11,7 +11,7 @@ DISCORD_INVITE = re.compile( r"discord([.,]|dot)li|" # or discord.li r"discord([.,]|dot)io|" # or discord.io. r"((?\S+)", # the invite code itself flags=re.IGNORECASE ) -- cgit v1.2.3 From 11327c6d9e9e702afe5e3dfa158fa78251d87e9b Mon Sep 17 00:00:00 2001 From: ionite34 Date: Tue, 16 Aug 2022 18:41:49 -0400 Subject: Unified usage of flags kwarg --- botcore/utils/regex.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'botcore/utils') diff --git a/botcore/utils/regex.py b/botcore/utils/regex.py index 76311ca7..b13b0408 100644 --- a/botcore/utils/regex.py +++ b/botcore/utils/regex.py @@ -33,7 +33,7 @@ FORMATTED_CODE_REGEX = re.compile( r"(?P.*?)" # extract all code inside the markup r"\s*" # any more whitespace before the end of the code markup r"(?P=delim)", # match the exact same delimiter from the start again - re.DOTALL | re.IGNORECASE # "." also matches newlines, case insensitive + flags=re.DOTALL | re.IGNORECASE # "." also matches newlines, case insensitive ) """ Regex for formatted code, using Discord's code blocks. @@ -45,7 +45,7 @@ RAW_CODE_REGEX = re.compile( r"^(?:[ \t]*\n)*" # any blank (empty or tabs/spaces only) lines before the code r"(?P.*?)" # extract all the rest as code r"\s*$", # any trailing whitespace until the end of the string - re.DOTALL # "." also matches newlines + flags=re.DOTALL # "." also matches newlines ) """ Regex for raw code, *not* using Discord's code blocks. -- cgit v1.2.3 From d8735bc057d2100ecabcd49827b94ae0ceaf224f Mon Sep 17 00:00:00 2001 From: ionite34 Date: Tue, 16 Aug 2022 18:44:39 -0400 Subject: Removed redundant capture group --- botcore/utils/regex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'botcore/utils') diff --git a/botcore/utils/regex.py b/botcore/utils/regex.py index b13b0408..4f91d3e0 100644 --- a/botcore/utils/regex.py +++ b/botcore/utils/regex.py @@ -3,7 +3,7 @@ import re DISCORD_INVITE = re.compile( - r"((https?://)?(www\.)?)?" + r"(https?://)?(www\.)?" r"(discord([.,]|dot)gg|" # Could be discord.gg/ r"discord([.,]|dot)com(/|slash)invite|" # or discord.com/invite/ r"discordapp([.,]|dot)com(/|slash)invite|" # or discordapp.com/invite/ -- cgit v1.2.3 From 8330044eae8c7d40151538d7403f97ca22759640 Mon Sep 17 00:00:00 2001 From: ionite34 Date: Tue, 16 Aug 2022 19:11:29 -0400 Subject: Added comments for regex addition --- botcore/utils/regex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'botcore/utils') diff --git a/botcore/utils/regex.py b/botcore/utils/regex.py index 4f91d3e0..de82a1ed 100644 --- a/botcore/utils/regex.py +++ b/botcore/utils/regex.py @@ -3,7 +3,7 @@ import re DISCORD_INVITE = re.compile( - r"(https?://)?(www\.)?" + r"(https?://)?(www\.)?" # Optional http(s) and www. r"(discord([.,]|dot)gg|" # Could be discord.gg/ r"discord([.,]|dot)com(/|slash)invite|" # or discord.com/invite/ r"discordapp([.,]|dot)com(/|slash)invite|" # or discordapp.com/invite/ -- cgit v1.2.3