Repository: Pycord-Development/pycord

Issue:
@commands.Cog.listener() raises errors
### Summary

We currently are unable to setup listeners in Cogs

### Reproduction Steps

Try to setup a listener in a Cog

### Minimal Reproducible Code

```python
import discord
from discord.ext import commands

class Palindrome(commands.Cog):
    def __init__(self, bot):
        self.BlackHole = bot

    @commands.Cog.listener()
    async def on_message(self, message: discord.Message):
        print(message.content)

def setup(bot):
    bot.add_cog(Palindrome(bot))
```


### Expected Results

Have a functioning listener

### Actual Results

Traceback (most recent call last):
  File "C:\Users\foobar\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\cog.py", line 668, in _load_from_module_spec
    setup(self)
  File "C:\Users\foobar\Desktop\Code\DiscordBots\BlackHole\Modules\Fun\Palindrome.py", line 36, in setup
    bot.add_cog(Palindrome(bot))
  File "C:\Users\foobar\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\cog.py", line 550, in add_cog
    cog = cog._inject(self)
  File "C:\Users\foobar\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\cog.py", line 474, in _inject
    bot.add_listener(getattr(self, method_name), name)
AttributeError: 'Bot' object has no attribute 'add_listener'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\foobar\Desktop\Code\DiscordBots\BlackHole\Bot.py", line 89, in <module>
    BlackHole.load_extension(fullpath[:-3])
  File "C:\Users\foobar\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\cog.py", line 728, in load_extension
    self._load_from_module_spec(spec, name)
  File "C:\Users\foobar\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\cog.py", line 671, in _load_from_module_spec
    self._remove_module_references(lib.__name__)
  File "C:\Users\foobar\AppData\Local\Programs\Python\Python39\lib\site-packages\discord\cog.py", line 617, in _remove_module_references
    for cmd in self.all_commands.copy().values():
AttributeError: 'Bot' object has no attribute 'all_commands'

### Intents

discord.Intents.all()

### System Information

- Python v3.9.7-final
- py-cord v2.0.0-alpha
    - py-cord pkg_resources: v2.0.0a4089+g017deaeb
- aiohttp v3.7.4.post0
- system info: Windows 10 10.0.19043

### Checklist

- [X] I have searched the open issues for duplicates.
- [X] I have shown the entire traceback, if possible.
- [X] I have removed my token from display, if visible.

### Additional Context

_No response_


Discussion:
Can you try with discord.Cog?
Also, are you using commands.Bot or discord.Bot?
> Can you try with discord.Cog? Also, are you using commands.Bot or discord.Bot?

Seems like this only happens when using discord.Bot, as it does not implement add_listener.
commands.Bot however does implement add_listener and it works without issues.

should they be implemented into discord.Bot?
> Seems like this only happens when using discord.Bot, as it does not implement add_listener. commands.Bot however does implement add_listener and it works without issues.
> 
> should they be implemented into discord.Bot?

This is more of a bug, considering `discord.Cog.listener` is implemented but only works with `commands.Bot`.

> > Seems like this only happens when using discord.Bot, as it does not implement add_listener. commands.Bot however does implement add_listener and it works without issues.
> > should they be implemented into discord.Bot?
> 
> This is more of a bug, considering `discord.Cog.listener` is implemented but only works with `commands.Bot`.

I agree, just didn't want to work on something that may not be merged.
cause how was this not caught in discord.py originally lol
`discord.Cog` does not exist in discord.py

Resolving patch:
diff --git a/discord/bot.py b/discord/bot.py
--- a/discord/bot.py
+++ b/discord/bot.py
@@ -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,
@@ -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
@@ -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
diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py
--- a/discord/ext/commands/bot.py
+++ b/discord/ext/commands/bot.py
@@ -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):
@@ -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
 


Test patch: