Skip to content

Commit

Permalink
Merge pull request #195 from Pycord-Development/conflict
Browse files Browse the repository at this point in the history
merge master into feature/slash
  • Loading branch information
BobDotCom authored Sep 15, 2021
2 parents d850ed4 + 5ff45e4 commit 699849d
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 11 deletions.
5 changes: 3 additions & 2 deletions about.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# About
## What Happened to Discord.py?
Rapptz, also known as Danny, the maintainer and core developer of discord.py will no longer be updating it. Here's his full explanation: https://gist.github.com/Rapptz/4a2f62751b9600a31a0d3c78100287f1 <br>
Rapptz, also known as Danny, the maintainer and core developer of discord.py will no longer be updating it. Here's his [Full explanation](https://gist.github.com/Rapptz/4a2f62751b9600a31a0d3c78100287f1)
</br>
[Here](https://gist.github.com/Rapptz/4a2f62751b9600a31a0d3c78100287f1#FAQ) is a FAQ written by him.

## What is Pycord?
Pycord ("py-cord" on PyPI) is a maintained fork of discord.py which will be updated with new changes to the API. Pycord was created by a group of developers that want to continue developing this Python module.

## Features
Pycord v1.7.3 is the same as discord.py v1.7.3. However Pycord v2.0.0 will support interactions and other features introduces in v2.0.0a as it's a fork of the master branch. <br>
We also have a FAQ channel in our Discord server. You can [click here](https://discord.gg/nRMbjMnxCz) to join.
We also have a FAQ channel in our Discord server. You can [click here](https://discord.gg/nRMbjMnxCz) to join.
332 changes: 332 additions & 0 deletions discord/ext/commands/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,338 @@ def decorator(func: CFT) -> CFT:

return decorator

# cogs

def add_cog(self, cog: Cog, *, override: bool = False) -> None:
"""Adds a "cog" to the bot.
A cog is a class that has its own event listeners and commands.
.. versionchanged:: 2.0
:exc:`.ClientException` is raised when a cog with the same name
is already loaded.
Parameters
-----------
cog: :class:`.Cog`
The cog to register to the bot.
override: :class:`bool`
If a previously loaded cog with the same name should be ejected
instead of raising an error.
.. versionadded:: 2.0
Raises
-------
TypeError
The cog does not inherit from :class:`.Cog`.
CommandError
An error happened during loading.
.ClientException
A cog with the same name is already loaded.
"""

if not isinstance(cog, Cog):
raise TypeError('cogs must derive from Cog')

cog_name = cog.__cog_name__
existing = self.__cogs.get(cog_name)

if existing is not None:
if not override:
raise discord.ClientException(f'Cog named {cog_name!r} already loaded')
self.remove_cog(cog_name)

cog = cog._inject(self)
self.__cogs[cog_name] = cog

def get_cog(self, name: str) -> Optional[Cog]:
"""Gets the cog instance requested.
If the cog is not found, ``None`` is returned instead.
Parameters
-----------
name: :class:`str`
The name of the cog you are requesting.
This is equivalent to the name passed via keyword
argument in class creation or the class name if unspecified.
Returns
--------
Optional[:class:`Cog`]
The cog that was requested. If not found, returns ``None``.
"""
return self.__cogs.get(name)

def remove_cog(self, name: str) -> Optional[Cog]:
"""Removes a cog from the bot and returns it.
All registered commands and event listeners that the
cog has registered will be removed as well.
If no cog is found then this method has no effect.
Parameters
-----------
name: :class:`str`
The name of the cog to remove.
Returns
-------
Optional[:class:`.Cog`]
The cog that was removed. ``None`` if not found.
"""

cog = self.__cogs.pop(name, None)
if cog is None:
return

help_command = self._help_command
if help_command and help_command.cog is cog:
help_command.cog = None
cog._eject(self)

return cog

@property
def cogs(self) -> Mapping[str, Cog]:
"""Mapping[:class:`str`, :class:`Cog`]: A read-only mapping of cog name to cog."""
return types.MappingProxyType(self.__cogs)

# extensions

def _remove_module_references(self, name: str) -> None:
# find all references to the module
# remove the cogs registered from the module
for cogname, cog in self.__cogs.copy().items():
if _is_submodule(name, cog.__module__):
self.remove_cog(cogname)

# remove all the commands from the module
for cmd in self.all_commands.copy().values():
if cmd.module is not None and _is_submodule(name, cmd.module):
if isinstance(cmd, GroupMixin):
cmd.recursively_remove_all_commands()
self.remove_command(cmd.name)

# remove all the listeners from the module
for event_list in self.extra_events.copy().values():
remove = []
for index, event in enumerate(event_list):
if event.__module__ is not None and _is_submodule(name, event.__module__):
remove.append(index)

for index in reversed(remove):
del event_list[index]

def _call_module_finalizers(self, lib: types.ModuleType, key: str) -> None:
try:
func = getattr(lib, 'teardown')
except AttributeError:
pass
else:
try:
func(self)
except Exception:
pass
finally:
self.__extensions.pop(key, None)
sys.modules.pop(key, None)
name = lib.__name__
for module in list(sys.modules.keys()):
if _is_submodule(name, module):
del sys.modules[module]

def _load_from_module_spec(self, spec: importlib.machinery.ModuleSpec, key: str) -> None:
# precondition: key not in self.__extensions
lib = importlib.util.module_from_spec(spec)
sys.modules[key] = lib
try:
spec.loader.exec_module(lib) # type: ignore
except Exception as e:
del sys.modules[key]
raise discord.ExtensionFailed(key, e) from e

try:
setup = getattr(lib, 'setup')
except AttributeError:
del sys.modules[key]
raise discord.NoEntryPointError(key)

try:
setup(self)
except Exception as e:
del sys.modules[key]
self._remove_module_references(lib.__name__)
self._call_module_finalizers(lib, key)
raise discord.ExtensionFailed(key, e) from e
else:
self.__extensions[key] = lib

def _resolve_name(self, name: str, package: Optional[str]) -> str:
try:
return importlib.util.resolve_name(name, package)
except ImportError:
raise discord.ExtensionNotFound(name)

def load_extension(self, name: str, *, package: Optional[str] = None) -> None:
"""Loads an extension.
An extension is a python module that contains commands, cogs, or
listeners.
An extension must have a global function, ``setup`` defined as
the entry point on what to do when the extension is loaded. This entry
point must have a single argument, the ``bot``.
Parameters
------------
name: :class:`str`
The extension name to load. It must be dot separated like
regular Python imports if accessing a sub-module. e.g.
``foo.test`` if you want to import ``foo/test.py``.
package: Optional[:class:`str`]
The package name to resolve relative imports with.
This is required when loading an extension using a relative path, e.g ``.foo.test``.
Defaults to ``None``.
.. versionadded:: 1.7
Raises
--------
ExtensionNotFound
The extension could not be imported.
This is also raised if the name of the extension could not
be resolved using the provided ``package`` parameter.
ExtensionAlreadyLoaded
The extension is already loaded.
NoEntryPointError
The extension does not have a setup function.
ExtensionFailed
The extension or its setup function had an execution error.
"""

name = self._resolve_name(name, package)
if name in self.__extensions:
raise discord.ExtensionAlreadyLoaded(name)

spec = importlib.util.find_spec(name)
if spec is None:
raise discord.ExtensionNotFound(name)

self._load_from_module_spec(spec, name)

def unload_extension(self, name: str, *, package: Optional[str] = None) -> None:
"""Unloads an extension.
When the extension is unloaded, all commands, listeners, and cogs are
removed from the bot and the module is un-imported.
The extension can provide an optional global function, ``teardown``,
to do miscellaneous clean-up if necessary. This function takes a single
parameter, the ``bot``, similar to ``setup`` from
:meth:`~.Bot.load_extension`.
Parameters
------------
name: :class:`str`
The extension name to unload. It must be dot separated like
regular Python imports if accessing a sub-module. e.g.
``foo.test`` if you want to import ``foo/test.py``.
package: Optional[:class:`str`]
The package name to resolve relative imports with.
This is required when unloading an extension using a relative path, e.g ``.foo.test``.
Defaults to ``None``.
.. versionadded:: 1.7
Raises
-------
ExtensionNotFound
The name of the extension could not
be resolved using the provided ``package`` parameter.
ExtensionNotLoaded
The extension was not loaded.
"""

name = self._resolve_name(name, package)
lib = self.__extensions.get(name)
if lib is None:
raise discord.ExtensionNotLoaded(name)

self._remove_module_references(lib.__name__)
self._call_module_finalizers(lib, name)

def reload_extension(self, name: str, *, package: Optional[str] = None) -> None:
"""Atomically reloads an extension.
This replaces the extension with the same extension, only refreshed. This is
equivalent to a :meth:`unload_extension` followed by a :meth:`load_extension`
except done in an atomic way. That is, if an operation fails mid-reload then
the bot will roll-back to the prior working state.
Parameters
------------
name: :class:`str`
The extension name to reload. It must be dot separated like
regular Python imports if accessing a sub-module. e.g.
``foo.test`` if you want to import ``foo/test.py``.
package: Optional[:class:`str`]
The package name to resolve relative imports with.
This is required when reloading an extension using a relative path, e.g ``.foo.test``.
Defaults to ``None``.
.. versionadded:: 1.7
Raises
-------
ExtensionNotLoaded
The extension was not loaded.
ExtensionNotFound
The extension could not be imported.
This is also raised if the name of the extension could not
be resolved using the provided ``package`` parameter.
NoEntryPointError
The extension does not have a setup function.
ExtensionFailed
The extension setup function had an execution error.
"""

name = self._resolve_name(name, package)
lib = self.__extensions.get(name)
if lib is None:
raise discord.ExtensionNotLoaded(name)

# get the previous module states from sys modules
modules = {
name: module
for name, module in sys.modules.items()
if _is_submodule(lib.__name__, name)
}

try:
# Unload and then load the module...
self._remove_module_references(lib.__name__)
self._call_module_finalizers(lib, name)
self.load_extension(name)
except Exception:
# if the load failed, the remnants should have been
# cleaned from the load_extension function call
# so let's load it from our old compiled library.
lib.setup(self) # type: ignore
self.__extensions[name] = lib

# revert sys.modules back to normal and raise back to caller
sys.modules.update(modules)
raise

@property
def extensions(self) -> Mapping[str, types.ModuleType]:
"""Mapping[:class:`str`, :class:`py:types.ModuleType`]: A read-only mapping of extension name to extension."""
return types.MappingProxyType(self.__extensions)

# help command stuff

@property
Expand Down
6 changes: 4 additions & 2 deletions discord/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -3008,7 +3008,9 @@ async def edit_welcome_screen(self, **options):
The welcome channels. The order of the channels would be same as the passed list order.
enabled: Optional[:class:`bool`]
Whether the welcome screen should be displayed.
reason: Optional[:class:`str`]
The reason that shows up on audit log.
Raises
-------
Expand Down Expand Up @@ -3038,6 +3040,6 @@ async def edit_welcome_screen(self, **options):
options['welcome_channels'] = welcome_channels_data

if options:
new = await self._state.http.edit_welcome_screen(self.id, options)
new = await self._state.http.edit_welcome_screen(self.id, options, reason=options.get('reason'))
return WelcomeScreen(data=new, guild=self)

4 changes: 2 additions & 2 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -1496,7 +1496,7 @@ def delete_channel_permissions(
def get_welcome_screen(self, guild_id: Snowflake) -> Response[welcome_screen.WelcomeScreen]:
return self.request(Route('GET', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id))

def edit_welcome_screen(self, guild_id: Snowflake, payload: Any) -> Response[welcome_screen.WelcomeScreen]:
def edit_welcome_screen(self, guild_id: Snowflake, payload: Any, *, reason: Optional[str] = None) -> Response[welcome_screen.WelcomeScreen]:
keys = (
'description',
'welcome_channels',
Expand All @@ -1505,7 +1505,7 @@ def edit_welcome_screen(self, guild_id: Snowflake, payload: Any) -> Response[wel
payload = {
key: val for key, val in payload.items() if key in keys
}
return self.request(Route('PATCH', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id), json=payload)
return self.request(Route('PATCH', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id), json=payload, reason=reason)

# Voice management

Expand Down
Loading

0 comments on commit 699849d

Please sign in to comment.