Skip to content

Commit 699849d

Browse files
authored
Merge pull request #195 from Pycord-Development/conflict
merge master into feature/slash
2 parents d850ed4 + 5ff45e4 commit 699849d

File tree

7 files changed

+356
-11
lines changed

7 files changed

+356
-11
lines changed

about.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# About
22
## What Happened to Discord.py?
3-
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>
3+
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)
4+
</br>
45
[Here](https://gist.github.com/Rapptz/4a2f62751b9600a31a0d3c78100287f1#FAQ) is a FAQ written by him.
56

67
## What is Pycord?
78
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.
89

910
## Features
1011
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>
11-
We also have a FAQ channel in our Discord server. You can [click here](https://discord.gg/nRMbjMnxCz) to join.
12+
We also have a FAQ channel in our Discord server. You can [click here](https://discord.gg/nRMbjMnxCz) to join.

discord/ext/commands/bot.py

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,338 @@ def decorator(func: CFT) -> CFT:
508508

509509
return decorator
510510

511+
# cogs
512+
513+
def add_cog(self, cog: Cog, *, override: bool = False) -> None:
514+
"""Adds a "cog" to the bot.
515+
516+
A cog is a class that has its own event listeners and commands.
517+
518+
.. versionchanged:: 2.0
519+
520+
:exc:`.ClientException` is raised when a cog with the same name
521+
is already loaded.
522+
523+
Parameters
524+
-----------
525+
cog: :class:`.Cog`
526+
The cog to register to the bot.
527+
override: :class:`bool`
528+
If a previously loaded cog with the same name should be ejected
529+
instead of raising an error.
530+
531+
.. versionadded:: 2.0
532+
533+
Raises
534+
-------
535+
TypeError
536+
The cog does not inherit from :class:`.Cog`.
537+
CommandError
538+
An error happened during loading.
539+
.ClientException
540+
A cog with the same name is already loaded.
541+
"""
542+
543+
if not isinstance(cog, Cog):
544+
raise TypeError('cogs must derive from Cog')
545+
546+
cog_name = cog.__cog_name__
547+
existing = self.__cogs.get(cog_name)
548+
549+
if existing is not None:
550+
if not override:
551+
raise discord.ClientException(f'Cog named {cog_name!r} already loaded')
552+
self.remove_cog(cog_name)
553+
554+
cog = cog._inject(self)
555+
self.__cogs[cog_name] = cog
556+
557+
def get_cog(self, name: str) -> Optional[Cog]:
558+
"""Gets the cog instance requested.
559+
560+
If the cog is not found, ``None`` is returned instead.
561+
562+
Parameters
563+
-----------
564+
name: :class:`str`
565+
The name of the cog you are requesting.
566+
This is equivalent to the name passed via keyword
567+
argument in class creation or the class name if unspecified.
568+
569+
Returns
570+
--------
571+
Optional[:class:`Cog`]
572+
The cog that was requested. If not found, returns ``None``.
573+
"""
574+
return self.__cogs.get(name)
575+
576+
def remove_cog(self, name: str) -> Optional[Cog]:
577+
"""Removes a cog from the bot and returns it.
578+
579+
All registered commands and event listeners that the
580+
cog has registered will be removed as well.
581+
582+
If no cog is found then this method has no effect.
583+
584+
Parameters
585+
-----------
586+
name: :class:`str`
587+
The name of the cog to remove.
588+
589+
Returns
590+
-------
591+
Optional[:class:`.Cog`]
592+
The cog that was removed. ``None`` if not found.
593+
"""
594+
595+
cog = self.__cogs.pop(name, None)
596+
if cog is None:
597+
return
598+
599+
help_command = self._help_command
600+
if help_command and help_command.cog is cog:
601+
help_command.cog = None
602+
cog._eject(self)
603+
604+
return cog
605+
606+
@property
607+
def cogs(self) -> Mapping[str, Cog]:
608+
"""Mapping[:class:`str`, :class:`Cog`]: A read-only mapping of cog name to cog."""
609+
return types.MappingProxyType(self.__cogs)
610+
611+
# extensions
612+
613+
def _remove_module_references(self, name: str) -> None:
614+
# find all references to the module
615+
# remove the cogs registered from the module
616+
for cogname, cog in self.__cogs.copy().items():
617+
if _is_submodule(name, cog.__module__):
618+
self.remove_cog(cogname)
619+
620+
# remove all the commands from the module
621+
for cmd in self.all_commands.copy().values():
622+
if cmd.module is not None and _is_submodule(name, cmd.module):
623+
if isinstance(cmd, GroupMixin):
624+
cmd.recursively_remove_all_commands()
625+
self.remove_command(cmd.name)
626+
627+
# remove all the listeners from the module
628+
for event_list in self.extra_events.copy().values():
629+
remove = []
630+
for index, event in enumerate(event_list):
631+
if event.__module__ is not None and _is_submodule(name, event.__module__):
632+
remove.append(index)
633+
634+
for index in reversed(remove):
635+
del event_list[index]
636+
637+
def _call_module_finalizers(self, lib: types.ModuleType, key: str) -> None:
638+
try:
639+
func = getattr(lib, 'teardown')
640+
except AttributeError:
641+
pass
642+
else:
643+
try:
644+
func(self)
645+
except Exception:
646+
pass
647+
finally:
648+
self.__extensions.pop(key, None)
649+
sys.modules.pop(key, None)
650+
name = lib.__name__
651+
for module in list(sys.modules.keys()):
652+
if _is_submodule(name, module):
653+
del sys.modules[module]
654+
655+
def _load_from_module_spec(self, spec: importlib.machinery.ModuleSpec, key: str) -> None:
656+
# precondition: key not in self.__extensions
657+
lib = importlib.util.module_from_spec(spec)
658+
sys.modules[key] = lib
659+
try:
660+
spec.loader.exec_module(lib) # type: ignore
661+
except Exception as e:
662+
del sys.modules[key]
663+
raise discord.ExtensionFailed(key, e) from e
664+
665+
try:
666+
setup = getattr(lib, 'setup')
667+
except AttributeError:
668+
del sys.modules[key]
669+
raise discord.NoEntryPointError(key)
670+
671+
try:
672+
setup(self)
673+
except Exception as e:
674+
del sys.modules[key]
675+
self._remove_module_references(lib.__name__)
676+
self._call_module_finalizers(lib, key)
677+
raise discord.ExtensionFailed(key, e) from e
678+
else:
679+
self.__extensions[key] = lib
680+
681+
def _resolve_name(self, name: str, package: Optional[str]) -> str:
682+
try:
683+
return importlib.util.resolve_name(name, package)
684+
except ImportError:
685+
raise discord.ExtensionNotFound(name)
686+
687+
def load_extension(self, name: str, *, package: Optional[str] = None) -> None:
688+
"""Loads an extension.
689+
690+
An extension is a python module that contains commands, cogs, or
691+
listeners.
692+
693+
An extension must have a global function, ``setup`` defined as
694+
the entry point on what to do when the extension is loaded. This entry
695+
point must have a single argument, the ``bot``.
696+
697+
Parameters
698+
------------
699+
name: :class:`str`
700+
The extension name to load. It must be dot separated like
701+
regular Python imports if accessing a sub-module. e.g.
702+
``foo.test`` if you want to import ``foo/test.py``.
703+
package: Optional[:class:`str`]
704+
The package name to resolve relative imports with.
705+
This is required when loading an extension using a relative path, e.g ``.foo.test``.
706+
Defaults to ``None``.
707+
708+
.. versionadded:: 1.7
709+
710+
Raises
711+
--------
712+
ExtensionNotFound
713+
The extension could not be imported.
714+
This is also raised if the name of the extension could not
715+
be resolved using the provided ``package`` parameter.
716+
ExtensionAlreadyLoaded
717+
The extension is already loaded.
718+
NoEntryPointError
719+
The extension does not have a setup function.
720+
ExtensionFailed
721+
The extension or its setup function had an execution error.
722+
"""
723+
724+
name = self._resolve_name(name, package)
725+
if name in self.__extensions:
726+
raise discord.ExtensionAlreadyLoaded(name)
727+
728+
spec = importlib.util.find_spec(name)
729+
if spec is None:
730+
raise discord.ExtensionNotFound(name)
731+
732+
self._load_from_module_spec(spec, name)
733+
734+
def unload_extension(self, name: str, *, package: Optional[str] = None) -> None:
735+
"""Unloads an extension.
736+
737+
When the extension is unloaded, all commands, listeners, and cogs are
738+
removed from the bot and the module is un-imported.
739+
740+
The extension can provide an optional global function, ``teardown``,
741+
to do miscellaneous clean-up if necessary. This function takes a single
742+
parameter, the ``bot``, similar to ``setup`` from
743+
:meth:`~.Bot.load_extension`.
744+
745+
Parameters
746+
------------
747+
name: :class:`str`
748+
The extension name to unload. It must be dot separated like
749+
regular Python imports if accessing a sub-module. e.g.
750+
``foo.test`` if you want to import ``foo/test.py``.
751+
package: Optional[:class:`str`]
752+
The package name to resolve relative imports with.
753+
This is required when unloading an extension using a relative path, e.g ``.foo.test``.
754+
Defaults to ``None``.
755+
756+
.. versionadded:: 1.7
757+
758+
Raises
759+
-------
760+
ExtensionNotFound
761+
The name of the extension could not
762+
be resolved using the provided ``package`` parameter.
763+
ExtensionNotLoaded
764+
The extension was not loaded.
765+
"""
766+
767+
name = self._resolve_name(name, package)
768+
lib = self.__extensions.get(name)
769+
if lib is None:
770+
raise discord.ExtensionNotLoaded(name)
771+
772+
self._remove_module_references(lib.__name__)
773+
self._call_module_finalizers(lib, name)
774+
775+
def reload_extension(self, name: str, *, package: Optional[str] = None) -> None:
776+
"""Atomically reloads an extension.
777+
778+
This replaces the extension with the same extension, only refreshed. This is
779+
equivalent to a :meth:`unload_extension` followed by a :meth:`load_extension`
780+
except done in an atomic way. That is, if an operation fails mid-reload then
781+
the bot will roll-back to the prior working state.
782+
783+
Parameters
784+
------------
785+
name: :class:`str`
786+
The extension name to reload. It must be dot separated like
787+
regular Python imports if accessing a sub-module. e.g.
788+
``foo.test`` if you want to import ``foo/test.py``.
789+
package: Optional[:class:`str`]
790+
The package name to resolve relative imports with.
791+
This is required when reloading an extension using a relative path, e.g ``.foo.test``.
792+
Defaults to ``None``.
793+
794+
.. versionadded:: 1.7
795+
796+
Raises
797+
-------
798+
ExtensionNotLoaded
799+
The extension was not loaded.
800+
ExtensionNotFound
801+
The extension could not be imported.
802+
This is also raised if the name of the extension could not
803+
be resolved using the provided ``package`` parameter.
804+
NoEntryPointError
805+
The extension does not have a setup function.
806+
ExtensionFailed
807+
The extension setup function had an execution error.
808+
"""
809+
810+
name = self._resolve_name(name, package)
811+
lib = self.__extensions.get(name)
812+
if lib is None:
813+
raise discord.ExtensionNotLoaded(name)
814+
815+
# get the previous module states from sys modules
816+
modules = {
817+
name: module
818+
for name, module in sys.modules.items()
819+
if _is_submodule(lib.__name__, name)
820+
}
821+
822+
try:
823+
# Unload and then load the module...
824+
self._remove_module_references(lib.__name__)
825+
self._call_module_finalizers(lib, name)
826+
self.load_extension(name)
827+
except Exception:
828+
# if the load failed, the remnants should have been
829+
# cleaned from the load_extension function call
830+
# so let's load it from our old compiled library.
831+
lib.setup(self) # type: ignore
832+
self.__extensions[name] = lib
833+
834+
# revert sys.modules back to normal and raise back to caller
835+
sys.modules.update(modules)
836+
raise
837+
838+
@property
839+
def extensions(self) -> Mapping[str, types.ModuleType]:
840+
"""Mapping[:class:`str`, :class:`py:types.ModuleType`]: A read-only mapping of extension name to extension."""
841+
return types.MappingProxyType(self.__extensions)
842+
511843
# help command stuff
512844

513845
@property

discord/guild.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3008,7 +3008,9 @@ async def edit_welcome_screen(self, **options):
30083008
The welcome channels. The order of the channels would be same as the passed list order.
30093009
enabled: Optional[:class:`bool`]
30103010
Whether the welcome screen should be displayed.
3011-
3011+
reason: Optional[:class:`str`]
3012+
The reason that shows up on audit log.
3013+
30123014
Raises
30133015
-------
30143016
@@ -3038,6 +3040,6 @@ async def edit_welcome_screen(self, **options):
30383040
options['welcome_channels'] = welcome_channels_data
30393041

30403042
if options:
3041-
new = await self._state.http.edit_welcome_screen(self.id, options)
3043+
new = await self._state.http.edit_welcome_screen(self.id, options, reason=options.get('reason'))
30423044
return WelcomeScreen(data=new, guild=self)
30433045

discord/http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,7 +1496,7 @@ def delete_channel_permissions(
14961496
def get_welcome_screen(self, guild_id: Snowflake) -> Response[welcome_screen.WelcomeScreen]:
14971497
return self.request(Route('GET', '/guilds/{guild_id}/welcome-screen', guild_id=guild_id))
14981498

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

15101510
# Voice management
15111511

0 commit comments

Comments
 (0)