Skip to content

Commit

Permalink
Merge pull request Pycord-Development#305 from ToxicKidz/fix/bot-list…
Browse files Browse the repository at this point in the history
…eners

Move commands.Bot listener methods to discord.Bot
  • Loading branch information
BobDotCom authored Oct 25, 2021
2 parents 7441ce8 + c0ab421 commit 25b1631
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 101 deletions.
110 changes: 108 additions & 2 deletions discord/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,21 @@
import traceback
from .commands.errors import CheckFailure

from typing import List, Optional, Union
from typing import (
Any,
Callable,
Coroutine,
List,
Optional,
TypeVar,
Union,
)

import sys

from .client import Client
from .shard import AutoShardedClient
from .utils import get, async_all
from .utils import MISSING, get, async_all
from .commands import (
SlashCommand,
SlashCommandGroup,
Expand All @@ -52,6 +60,8 @@
from .errors import Forbidden, DiscordException
from .interactions import Interaction

CoroFunc = Callable[..., Coroutine[Any, Any, Any]]
CFT = TypeVar('CFT', bound=CoroFunc)

class ApplicationCommandMixin:
"""A mixin that implements common functionality for classes that need
Expand Down Expand Up @@ -692,6 +702,102 @@ async def can_run(
# type-checker doesn't distinguish between functions and methods
return await async_all(f(ctx) for f in data) # type: ignore

# listener registration

def add_listener(self, func: CoroFunc, name: str = MISSING) -> None:
"""The non decorator alternative to :meth:`.listen`.
Parameters
-----------
func: :ref:`coroutine <coroutine>`
The function to call.
name: :class:`str`
The name of the event to listen for. Defaults to ``func.__name__``.
Example
--------
.. code-block:: python3
async def on_ready(): pass
async def my_message(message): pass
bot.add_listener(on_ready)
bot.add_listener(my_message, 'on_message')
"""
name = func.__name__ if name is MISSING else name

if not asyncio.iscoroutinefunction(func):
raise TypeError('Listeners must be coroutines')

if name in self.extra_events:
self.extra_events[name].append(func)
else:
self.extra_events[name] = [func]

def remove_listener(self, func: CoroFunc, name: str = MISSING) -> None:
"""Removes a listener from the pool of listeners.
Parameters
-----------
func
The function that was used as a listener to remove.
name: :class:`str`
The name of the event we want to remove. Defaults to
``func.__name__``.
"""

name = func.__name__ if name is MISSING else name

if name in self.extra_events:
try:
self.extra_events[name].remove(func)
except ValueError:
pass

def listen(self, name: str = MISSING) -> Callable[[CFT], CFT]:
"""A decorator that registers another function as an external
event listener. Basically this allows you to listen to multiple
events from different places e.g. such as :func:`.on_ready`
The functions being listened to must be a :ref:`coroutine <coroutine>`.
Example
--------
.. code-block:: python3
@bot.listen()
async def on_message(message):
print('one')
# in some other file...
@bot.listen('on_message')
async def my_message(message):
print('two')
Would print one and two in an unspecified order.
Raises
-------
TypeError
The function being listened to is not a coroutine.
"""

def decorator(func: CFT) -> CFT:
self.add_listener(func, name)
return func

return decorator

def dispatch(self, event_name: str, *args: Any, **kwargs: Any) -> None:
# super() will resolve to Client
super().dispatch(event_name, *args, **kwargs) # type: ignore
ev = 'on_' + event_name
for event in self.extra_events.get(ev, []):
self._schedule_event(event, ev, *args, **kwargs) # type: ignore

def before_invoke(self, coro):
"""A decorator that registers a coroutine as a pre-invoke hook.
A pre-invoke hook is called directly before the command is
Expand Down
98 changes: 0 additions & 98 deletions discord/ext/commands/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,6 @@ def __init__(self, command_prefix=when_mentioned, help_command=_default, **optio
else:
self.help_command = help_command

# internal helpers

def dispatch(self, event_name: str, *args: Any, **kwargs: Any) -> None:
# super() will resolve to Client
super().dispatch(event_name, *args, **kwargs) # type: ignore
ev = 'on_' + event_name
for event in self.extra_events.get(ev, []):
self._schedule_event(event, ev, *args, **kwargs) # type: ignore

@discord.utils.copy_doc(discord.Client.close)
async def close(self) -> None:
for extension in tuple(self.__extensions):
Expand Down Expand Up @@ -404,95 +395,6 @@ def after_invoke(self, coro: CFT) -> CFT:
self._after_invoke = coro
return coro

# listener registration

def add_listener(self, func: CoroFunc, name: str = MISSING) -> None:
"""The non decorator alternative to :meth:`.listen`.
Parameters
-----------
func: :ref:`coroutine <coroutine>`
The function to call.
name: :class:`str`
The name of the event to listen for. Defaults to ``func.__name__``.
Example
--------
.. code-block:: python3
async def on_ready(): pass
async def my_message(message): pass
bot.add_listener(on_ready)
bot.add_listener(my_message, 'on_message')
"""
name = func.__name__ if name is MISSING else name

if not asyncio.iscoroutinefunction(func):
raise TypeError('Listeners must be coroutines')

if name in self.extra_events:
self.extra_events[name].append(func)
else:
self.extra_events[name] = [func]

def remove_listener(self, func: CoroFunc, name: str = MISSING) -> None:
"""Removes a listener from the pool of listeners.
Parameters
-----------
func
The function that was used as a listener to remove.
name: :class:`str`
The name of the event we want to remove. Defaults to
``func.__name__``.
"""

name = func.__name__ if name is MISSING else name

if name in self.extra_events:
try:
self.extra_events[name].remove(func)
except ValueError:
pass

def listen(self, name: str = MISSING) -> Callable[[CFT], CFT]:
"""A decorator that registers another function as an external
event listener. Basically this allows you to listen to multiple
events from different places e.g. such as :func:`.on_ready`
The functions being listened to must be a :ref:`coroutine <coroutine>`.
Example
--------
.. code-block:: python3
@bot.listen()
async def on_message(message):
print('one')
# in some other file...
@bot.listen('on_message')
async def my_message(message):
print('two')
Would print one and two in an unspecified order.
Raises
-------
TypeError
The function being listened to is not a coroutine.
"""

def decorator(func: CFT) -> CFT:
self.add_listener(func, name)
return func

return decorator

# cogs

Expand Down
5 changes: 4 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Bot
.. autoclass:: Bot
:members:
:inherited-members:
:exclude-members: command, event, message_command, slash_command, user_command
:exclude-members: command, event, message_command, slash_command, user_command, listen

.. automethod:: Bot.command(**kwargs)
:decorator:
Expand All @@ -83,6 +83,9 @@ Bot
.. automethod:: Bot.user_command(**kwargs)
:decorator:

.. automethod:: Bot.listen(name=None)
:decorator:

AutoShardedBot
~~~~~~~~~~~~~~~
.. attributetable:: AutoShardedBot
Expand Down

0 comments on commit 25b1631

Please sign in to comment.