Skip to content

Commit 25b1631

Browse files
authored
Merge pull request #305 from ToxicKidz/fix/bot-listeners
Move commands.Bot listener methods to discord.Bot
2 parents 7441ce8 + c0ab421 commit 25b1631

File tree

3 files changed

+112
-101
lines changed

3 files changed

+112
-101
lines changed

discord/bot.py

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,21 @@
3131
import traceback
3232
from .commands.errors import CheckFailure
3333

34-
from typing import List, Optional, Union
34+
from typing import (
35+
Any,
36+
Callable,
37+
Coroutine,
38+
List,
39+
Optional,
40+
TypeVar,
41+
Union,
42+
)
3543

3644
import sys
3745

3846
from .client import Client
3947
from .shard import AutoShardedClient
40-
from .utils import get, async_all
48+
from .utils import MISSING, get, async_all
4149
from .commands import (
4250
SlashCommand,
4351
SlashCommandGroup,
@@ -52,6 +60,8 @@
5260
from .errors import Forbidden, DiscordException
5361
from .interactions import Interaction
5462

63+
CoroFunc = Callable[..., Coroutine[Any, Any, Any]]
64+
CFT = TypeVar('CFT', bound=CoroFunc)
5565

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

705+
# listener registration
706+
707+
def add_listener(self, func: CoroFunc, name: str = MISSING) -> None:
708+
"""The non decorator alternative to :meth:`.listen`.
709+
710+
Parameters
711+
-----------
712+
func: :ref:`coroutine <coroutine>`
713+
The function to call.
714+
name: :class:`str`
715+
The name of the event to listen for. Defaults to ``func.__name__``.
716+
717+
Example
718+
--------
719+
720+
.. code-block:: python3
721+
722+
async def on_ready(): pass
723+
async def my_message(message): pass
724+
725+
bot.add_listener(on_ready)
726+
bot.add_listener(my_message, 'on_message')
727+
"""
728+
name = func.__name__ if name is MISSING else name
729+
730+
if not asyncio.iscoroutinefunction(func):
731+
raise TypeError('Listeners must be coroutines')
732+
733+
if name in self.extra_events:
734+
self.extra_events[name].append(func)
735+
else:
736+
self.extra_events[name] = [func]
737+
738+
def remove_listener(self, func: CoroFunc, name: str = MISSING) -> None:
739+
"""Removes a listener from the pool of listeners.
740+
741+
Parameters
742+
-----------
743+
func
744+
The function that was used as a listener to remove.
745+
name: :class:`str`
746+
The name of the event we want to remove. Defaults to
747+
``func.__name__``.
748+
"""
749+
750+
name = func.__name__ if name is MISSING else name
751+
752+
if name in self.extra_events:
753+
try:
754+
self.extra_events[name].remove(func)
755+
except ValueError:
756+
pass
757+
758+
def listen(self, name: str = MISSING) -> Callable[[CFT], CFT]:
759+
"""A decorator that registers another function as an external
760+
event listener. Basically this allows you to listen to multiple
761+
events from different places e.g. such as :func:`.on_ready`
762+
763+
The functions being listened to must be a :ref:`coroutine <coroutine>`.
764+
765+
Example
766+
--------
767+
768+
.. code-block:: python3
769+
770+
@bot.listen()
771+
async def on_message(message):
772+
print('one')
773+
774+
# in some other file...
775+
776+
@bot.listen('on_message')
777+
async def my_message(message):
778+
print('two')
779+
780+
Would print one and two in an unspecified order.
781+
782+
Raises
783+
-------
784+
TypeError
785+
The function being listened to is not a coroutine.
786+
"""
787+
788+
def decorator(func: CFT) -> CFT:
789+
self.add_listener(func, name)
790+
return func
791+
792+
return decorator
793+
794+
def dispatch(self, event_name: str, *args: Any, **kwargs: Any) -> None:
795+
# super() will resolve to Client
796+
super().dispatch(event_name, *args, **kwargs) # type: ignore
797+
ev = 'on_' + event_name
798+
for event in self.extra_events.get(ev, []):
799+
self._schedule_event(event, ev, *args, **kwargs) # type: ignore
800+
695801
def before_invoke(self, coro):
696802
"""A decorator that registers a coroutine as a pre-invoke hook.
697803
A pre-invoke hook is called directly before the command is

discord/ext/commands/bot.py

Lines changed: 0 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,6 @@ def __init__(self, command_prefix=when_mentioned, help_command=_default, **optio
133133
else:
134134
self.help_command = help_command
135135

136-
# internal helpers
137-
138-
def dispatch(self, event_name: str, *args: Any, **kwargs: Any) -> None:
139-
# super() will resolve to Client
140-
super().dispatch(event_name, *args, **kwargs) # type: ignore
141-
ev = 'on_' + event_name
142-
for event in self.extra_events.get(ev, []):
143-
self._schedule_event(event, ev, *args, **kwargs) # type: ignore
144-
145136
@discord.utils.copy_doc(discord.Client.close)
146137
async def close(self) -> None:
147138
for extension in tuple(self.__extensions):
@@ -404,95 +395,6 @@ def after_invoke(self, coro: CFT) -> CFT:
404395
self._after_invoke = coro
405396
return coro
406397

407-
# listener registration
408-
409-
def add_listener(self, func: CoroFunc, name: str = MISSING) -> None:
410-
"""The non decorator alternative to :meth:`.listen`.
411-
412-
Parameters
413-
-----------
414-
func: :ref:`coroutine <coroutine>`
415-
The function to call.
416-
name: :class:`str`
417-
The name of the event to listen for. Defaults to ``func.__name__``.
418-
419-
Example
420-
--------
421-
422-
.. code-block:: python3
423-
424-
async def on_ready(): pass
425-
async def my_message(message): pass
426-
427-
bot.add_listener(on_ready)
428-
bot.add_listener(my_message, 'on_message')
429-
430-
"""
431-
name = func.__name__ if name is MISSING else name
432-
433-
if not asyncio.iscoroutinefunction(func):
434-
raise TypeError('Listeners must be coroutines')
435-
436-
if name in self.extra_events:
437-
self.extra_events[name].append(func)
438-
else:
439-
self.extra_events[name] = [func]
440-
441-
def remove_listener(self, func: CoroFunc, name: str = MISSING) -> None:
442-
"""Removes a listener from the pool of listeners.
443-
444-
Parameters
445-
-----------
446-
func
447-
The function that was used as a listener to remove.
448-
name: :class:`str`
449-
The name of the event we want to remove. Defaults to
450-
``func.__name__``.
451-
"""
452-
453-
name = func.__name__ if name is MISSING else name
454-
455-
if name in self.extra_events:
456-
try:
457-
self.extra_events[name].remove(func)
458-
except ValueError:
459-
pass
460-
461-
def listen(self, name: str = MISSING) -> Callable[[CFT], CFT]:
462-
"""A decorator that registers another function as an external
463-
event listener. Basically this allows you to listen to multiple
464-
events from different places e.g. such as :func:`.on_ready`
465-
466-
The functions being listened to must be a :ref:`coroutine <coroutine>`.
467-
468-
Example
469-
--------
470-
471-
.. code-block:: python3
472-
473-
@bot.listen()
474-
async def on_message(message):
475-
print('one')
476-
477-
# in some other file...
478-
479-
@bot.listen('on_message')
480-
async def my_message(message):
481-
print('two')
482-
483-
Would print one and two in an unspecified order.
484-
485-
Raises
486-
-------
487-
TypeError
488-
The function being listened to is not a coroutine.
489-
"""
490-
491-
def decorator(func: CFT) -> CFT:
492-
self.add_listener(func, name)
493-
return func
494-
495-
return decorator
496398

497399
# cogs
498400

docs/api.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Bot
6666
.. autoclass:: Bot
6767
:members:
6868
:inherited-members:
69-
:exclude-members: command, event, message_command, slash_command, user_command
69+
:exclude-members: command, event, message_command, slash_command, user_command, listen
7070

7171
.. automethod:: Bot.command(**kwargs)
7272
:decorator:
@@ -83,6 +83,9 @@ Bot
8383
.. automethod:: Bot.user_command(**kwargs)
8484
:decorator:
8585

86+
.. automethod:: Bot.listen(name=None)
87+
:decorator:
88+
8689
AutoShardedBot
8790
~~~~~~~~~~~~~~~
8891
.. attributetable:: AutoShardedBot

0 commit comments

Comments
 (0)