diff --git a/docs/source/client.rst b/docs/source/client.rst index 6b18d0c..ee64ad9 100644 --- a/docs/source/client.rst +++ b/docs/source/client.rst @@ -14,3 +14,110 @@ Client .. automethod:: Client.listen() :decorator: + + +Event Reference +--------------- + +This section outlines the different types of events. + +To register an listener for an event you can use :meth:`Client.event`. + +If an your listener raises an exception, :func:`on_error` will be called +to handle it, which defaults to print a traceback and ignoring the exception. + +.. warning:: + + Listeners functions must be a **coroutine**. If they aren't, then you might get unexpected + errors. In order to turn a function into a coroutine they must be ``async def`` + functions. + +.. function:: on_error(exception) + + Usually when an event raises an uncaught exception, a traceback is + printed to stderr and the exception is ignored. If you want to + change this behaviour and handle the exception for whatever reason + yourself, this event can be overridden. Which, when done, will + suppress the default action of printing the traceback. + + .. note:: + It will not be received by :meth:`Client.wait_for`. + + :param exception: Produced error. + :type exception: :class:`Exception` + +.. function:: on_guild_channel_delete(channel) + on_guild_channel_create(channel) + + Called whenever a guild channel is deleted or created. + + :param channel: The guild channel that got created or deleted. + :type channel: :class:`models.guild.channel.Channel` + +.. function:: on_guild_channel_update(before, after) + + Called whenever a guild channel is updated. e.g. changed name, topic, permissions. + + :param before: The updated guild channel's old info. + :type before: :class:`models.guild.channel.Channel` + :param after: The updated guild channel's new info. + :type after: :class:`models.guild.channel.Channel` + +.. function:: on_guild_create(guild) + + Called when a :class:`models.guild.guild.Guild` is either created by the :class:`Client` or when the + :class:`Client` joins a guild. + + :param guild: The guild that was joined or created. + :type guild: :class:`models.guild.guild.Guild` + +.. function:: on_guild_remove(guild) + + Called when a :class:`models.guild.guild.Guild` is removed from the :class:`Client`. + + This happens through, but not limited to, these circumstances: + + - The client got banned. + - The client got kicked. + - The client left the guild. + - The client or the guild owner deleted the guild. + + In order for this event to be invoked then the :class:`Client` must have + been part of the guild to begin with. + + :param guild: The guild that got removed. + :type guild: :class:`models.guild.guild.Guild` + +.. function:: on_guild_update(before, after) + + Called when a :class:`models.guild.guild.Guild` updates, for example: + + - Changed name + - Changed AFK channel + - Changed AFK timeout + - etc + + :param before: The guild prior to being updated. + :type before: :class:`models.guild.guild.Guild` + :param after: The guild after being updated. + :type after: :class:`models.guild.guild.Guild` + +.. function:: on_message_create(message) + + Called when a :class:`models.message.message.Message` is created and sent. + + .. note:: + + Not all messages will have ``content``. This is a Discord limitation. + See the docs of :attr:`Intents.MESSAGE_CONTENT` for more information. + + :param message: The current message. + :type message: :class:`models.message.message.Message + +.. function:: on_shard_ready(shard_id) + + Called when particular shard becomes ready. + + :param shard_id: The shard ID that is ready. + :type shard_id: :class:`int`` + diff --git a/melisa/client.py b/melisa/client.py index d6056ff..266235f 100644 --- a/melisa/client.py +++ b/melisa/client.py @@ -62,15 +62,15 @@ class Client: """ def __init__( - self, - token: str, - *, - asyncio_debug: bool = False, - intents: Union[Intents, Iterable[Intents]] = None, - activity: Optional[Activity] = None, - status: str = None, - mobile: bool = False, - logs: Union[None, int, str, Dict[str, Any]] = "INFO", + self, + token: str, + *, + asyncio_debug: bool = False, + intents: Union[Intents, Iterable[Intents]] = None, + activity: Optional[Activity] = None, + status: str = None, + mobile: bool = False, + logs: Union[None, int, str, Dict[str, Any]] = "INFO", ): self._loop = asyncio.get_event_loop() @@ -89,7 +89,7 @@ class Client: self.intents = sum(intents) elif intents is None: self.intents = ( - Intents.all() - Intents.GUILD_PRESENCES - Intents.GUILD_MEMBERS + Intents.all() - Intents.GUILD_PRESENCES - Intents.GUILD_MEMBERS ) else: self.intents = intents @@ -139,11 +139,7 @@ class Client: _logger.debug(f"Listener {callback.__qualname__} added successfully!") return self - async def dispatch( - self, - name: str, - *args - ): + async def dispatch(self, name: str, *args): """ Dispatches an event @@ -168,7 +164,7 @@ class Client: self._waiter_mgr.process_events(name, *args) - def run(self) -> None: + def run(self): """ Run Bot without shards (only 0 shard) """ @@ -260,7 +256,7 @@ class Client: return Guild.from_dict(data) async def fetch_channel( - self, channel_id: Union[Snowflake, str, int] + self, channel_id: Union[Snowflake, str, int] ) -> Union[Channel, Any]: """ Fetch Channel from the Discord API (by id). @@ -309,6 +305,7 @@ class Client: Examples -------- Waiting for a user reply: :: + @client.listen async def on_message_create(message): if message.content.startswith('$greet'): diff --git a/melisa/exceptions.py b/melisa/exceptions.py index 18aec68..c41debc 100644 --- a/melisa/exceptions.py +++ b/melisa/exceptions.py @@ -15,8 +15,7 @@ class ClientException(MelisaException): class MelisaTimeoutError(MelisaException): - """Exception raised when `wait_for` method timed out - """ + """Exception raised when `wait_for` method timed out""" class LoginFailure(ClientException): diff --git a/melisa/models/guild/channel.py b/melisa/models/guild/channel.py index 5c7da02..5b06310 100644 --- a/melisa/models/guild/channel.py +++ b/melisa/models/guild/channel.py @@ -47,12 +47,12 @@ class ChannelType(IntEnum): GUILD_STORE: A channel in which game developers can sell their game on Discord GUILD_NEWS_THREAD: - A temporary sub-channel within a **GUILD_NEWS** channel + A temporary sub-channel within a ``GUILD_NEWS`` channel GUILD_PUBLIC_THREAD: - A temporary sub-channel within a GUILD_TEXT channel + A temporary sub-channel within a ``GUILD_TEXT`` channel GUILD_PRIVATE_THREAD: - A temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited - and those with the MANAGE_THREADS permission + A temporary sub-channel within a ``GUILD_TEXT`` channel that is only viewable + by those invited and those with the MANAGE_THREADS permission GUILD_STAGE_VOICE: A voice channel for hosting events with an audience """ @@ -124,7 +124,7 @@ class Channel(APIModelBase): rate_limit_per_user: :class:`int` Amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission - `manage_messages` or `manage_channel`, are unaffected + ``MANAGE_MESSAGES`` and ``MANAGE_CHANNEL``, are unaffected recipients: :class:`typing.Any` The recipients of the DM icon: :class:`str` @@ -333,12 +333,11 @@ class MessageableChannel(Channel): Examples --------- - Flattening messages into a list: - .. code-block:: python - :linenos: + Flattening messages into a list: :: messages = [message async for message in channel.history(limit=111)] + All parameters are optional. Parameters @@ -468,7 +467,7 @@ class MessageableChannel(Channel): The request to perform the action failed with other http exception. ForbiddenError You do not have proper permissions to do the actions required. - (You must have **MANAGE_MESSAGES** permission) + (You must have ``MANAGE_MESSAGES`` permission) """ await self._http.post( f"channels/{self.id}/messages/bulk-delete", @@ -496,16 +495,14 @@ class MessageableChannel(Channel): The request to perform the action failed with other http exception. ForbiddenError You do not have proper permissions to do the actions required. - (You must have **MANAGE_MESSAGES** permission) + (You must have ``MANAGE_MESSAGES`` permission) """ await self._http.delete( f"channels/{self.id}/messages/{message_id}", headers={"X-Audit-Log-Reason": reason}, ) - async def send( - self, content: str = None - ) -> Message: + async def send(self, content: str = None) -> Message: """|coro| Sends a message to the destination with the content given. @@ -533,8 +530,7 @@ class MessageableChannel(Channel): return Message.from_dict( await self._http.post( - f"/channels/{self.id}/messages", - data={"content": content} + f"/channels/{self.id}/messages", data={"content": content} ) ) @@ -703,7 +699,7 @@ class TextChannel(MessageableChannel): class Thread(MessageableChannel): """A subclass of ``Channel`` for threads with all the same attributes.""" - async def add_user(self, user_id: Snowflake) -> None: + async def add_user(self, user_id: Snowflake): """|coro| Adds a user to this thread. @@ -727,7 +723,7 @@ class Thread(MessageableChannel): await self._http.put(f"channels/{self.id}/thread-members/{user_id}") - async def remove_user(self, user_id: Snowflake) -> None: + async def remove_user(self, user_id: Snowflake): """|coro| Removes a user from this thread. @@ -749,7 +745,7 @@ class Thread(MessageableChannel): await self._http.delete(f"channels/{self.id}/thread-members/{user_id}") - async def join(self) -> None: + async def join(self): """|coro| Joins this thread. @@ -767,7 +763,7 @@ class Thread(MessageableChannel): await self._http.put(f"/channels/{self.id}/thread-members/@me") - async def leave(self) -> None: + async def leave(self): """|coro| Leaves this thread. diff --git a/melisa/models/guild/guild.py b/melisa/models/guild/guild.py index 8717dfc..f31e0bf 100644 --- a/melisa/models/guild/guild.py +++ b/melisa/models/guild/guild.py @@ -475,7 +475,7 @@ class Guild(APIModelBase): amount of seconds a user has to wait before sending another message (0-21600) bots, as well as users with the permission - manage_messages or manage_channel, are unaffected + ``MANAGE_MESSAGES`` or ``MANAGE_CHANNEL``, are unaffected position: Optional[:class:`int`] sorting position of the channel permission_overwrites: Optional[List[Any]] diff --git a/melisa/utils/waiters.py b/melisa/utils/waiters.py index 5178721..f474545 100644 --- a/melisa/utils/waiters.py +++ b/melisa/utils/waiters.py @@ -3,7 +3,12 @@ from __future__ import annotations -from asyncio import AbstractEventLoop, Event, wait_for as async_wait, TimeoutError as AsyncTimeOut +from asyncio import ( + AbstractEventLoop, + Event, + wait_for as async_wait, + TimeoutError as AsyncTimeOut, +) from typing import List, Callable, Optional from ..exceptions import MelisaTimeoutError @@ -39,7 +44,15 @@ class _Waiter: """Waits until ``self.event`` is set.""" await self.event.wait() - def process(self, event_name: str, event_value): + def process(self, event_name: str, event_value=None): + """ + Parameters + ---------- + event_name: str + The name of the event. + event_value: Optional[Any] + evemt value + """ if self.event_name != event_name: return False @@ -83,7 +96,7 @@ class WaiterMgr: self, event_name: str, check: Optional[Callable[..., bool]] = None, - timeout: Optional[float] = None + timeout: Optional[float] = None, ): """ Parameters @@ -109,9 +122,6 @@ class WaiterMgr: self.waiter_list.remove(waiter) except AsyncTimeOut: self.waiter_list.remove(waiter) - raise MelisaTimeoutError( - "wait_for() timed out while waiting for an event." - ) + raise MelisaTimeoutError("wait_for() timed out while waiting for an event.") return waiter.return_value -