Merge branch '2.0' into updated-links

This commit is contained in:
Duck 2021-09-22 17:40:47 -04:00 committed by GitHub
commit 432b4d442a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 114 additions and 74 deletions

View File

@ -6,7 +6,7 @@ body:
attributes: attributes:
value: > value: >
Thanks for taking the time to fill out a bug. Thanks for taking the time to fill out a bug.
If you want real-time support, consider joining our Discord at https://discord.gg/r3sSKJJ instead. If you want real-time support, consider joining our Discord at https://discord.gg/TvqYBrGXEm instead.
Please note that this form is for bugs only! Please note that this form is for bugs only!
- type: input - type: input

View File

@ -5,4 +5,4 @@ contact_links:
url: https://github.com/Rapptz/discord.py/discussions url: https://github.com/Rapptz/discord.py/discussions
- name: Discord Server - name: Discord Server
about: Use our official Discord server to ask for help and questions as well. about: Use our official Discord server to ask for help and questions as well.
url: https://discord.gg/r3sSKJJ url: https://discord.gg/TvqYBrGXEm

View File

@ -1,25 +0,0 @@
name: Lint
on: [push]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: install black
run: pip install black
- name: run linter
uses: wearerequired/lint-action@v1
with:
black: true
black_args: ". --line-length 120"
auto_fix: true

38
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: CI
on: [push, pull_request]
jobs:
pyright:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Setup node.js (for pyright)
uses: actions/setup-node@v1
with:
node-version: "14"
- name: Run type checking
run: |
npm install -g pyright
pip install .
pyright --lib --verifytypes discord --ignoreexternal
black:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Run linter
uses: psf/black@stable
with:
options: "--line-length 120 --check"
src: "./discord"

1
.python-black Normal file
View File

@ -0,0 +1 @@

View File

@ -2,7 +2,7 @@ enhanced-discord.py
=================== ===================
.. image:: https://discord.com/api/guilds/514232441498763279/embed.png .. image:: https://discord.com/api/guilds/514232441498763279/embed.png
:target: https://discord.gg/PYAfZzpsjG :target: https://discord.gg/TvqYBrGXEm
:alt: Discord server invite :alt: Discord server invite
.. image:: https://img.shields.io/pypi/v/enhanced-dpy.svg .. image:: https://img.shields.io/pypi/v/enhanced-dpy.svg
:target: https://pypi.python.org/pypi/enhanced-dpy :target: https://pypi.python.org/pypi/enhanced-dpy
@ -117,5 +117,5 @@ Links
------ ------
- `Documentation <https://enhanced-dpy.readthedocs.io/en/latest/index.html>`_ - `Documentation <https://enhanced-dpy.readthedocs.io/en/latest/index.html>`_
- `Official Discord Server <https://discord.gg/PYAfZzpsjG>`_ - `Official Discord Server <https://discord.gg/TvqYBrGXEm>`_
- `Discord API <https://discord.gg/discord-api>`_ - `Discord API <https://discord.gg/discord-api>`_

View File

@ -398,16 +398,14 @@ class Embed:
return EmbedProxy(getattr(self, "_image", {})) # type: ignore return EmbedProxy(getattr(self, "_image", {})) # type: ignore
@image.setter @image.setter
def image(self: E, url: Any): def image(self, url: Any):
if url is EmptyEmbed: if url is EmptyEmbed:
del self._image del self.image
else: else:
self._image = { self._image = {"url": str(url)}
"url": str(url),
}
@image.deleter @image.deleter
def image(self: E): def image(self):
try: try:
del self._image del self._image
except AttributeError: except AttributeError:
@ -429,7 +427,6 @@ class Embed:
""" """
self.image = url self.image = url
return self return self
@property @property
@ -448,13 +445,11 @@ class Embed:
return EmbedProxy(getattr(self, "_thumbnail", {})) # type: ignore return EmbedProxy(getattr(self, "_thumbnail", {})) # type: ignore
@thumbnail.setter @thumbnail.setter
def thumbnail(self: E, url: Any): def thumbnail(self, url: Any):
if url is EmptyEmbed: if url is EmptyEmbed:
del self._thumbnail del self.thumbnail
else: else:
self._thumbnail = { self._thumbnail = {"url": str(url)}
"url": str(url),
}
@thumbnail.deleter @thumbnail.deleter
def thumbnail(self): def thumbnail(self):
@ -463,7 +458,7 @@ class Embed:
except AttributeError: except AttributeError:
pass pass
def set_thumbnail(self: E, *, url: MaybeEmpty[Any]): def set_thumbnail(self, *, url: MaybeEmpty[Any]):
"""Sets the thumbnail for the embed content. """Sets the thumbnail for the embed content.
This function returns the class instance to allow for fluent-style This function returns the class instance to allow for fluent-style
@ -479,7 +474,6 @@ class Embed:
""" """
self.thumbnail = url self.thumbnail = url
return self return self
@property @property

View File

@ -330,6 +330,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
__original_kwargs__: Dict[str, Any] __original_kwargs__: Dict[str, Any]
_max_concurrency: Optional[MaxConcurrency]
def __new__(cls: Type[CommandT], *args: Any, **kwargs: Any) -> CommandT: def __new__(cls: Type[CommandT], *args: Any, **kwargs: Any) -> CommandT:
# if you're wondering why this is done, it's because we need to ensure # if you're wondering why this is done, it's because we need to ensure
@ -392,17 +393,20 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
self.description: str = inspect.cleandoc(kwargs.get("description", "")) self.description: str = inspect.cleandoc(kwargs.get("description", ""))
self.hidden: bool = kwargs.get("hidden", False) self.hidden: bool = kwargs.get("hidden", False)
if hasattr(func, "__command_attrs__"):
command_attrs: Dict[str, Any] = func.__command_attrs__
else:
command_attrs = {}
try: try:
checks = func.__commands_checks__ checks = func.__commands_checks__
checks.reverse() checks.reverse()
except AttributeError: except AttributeError:
checks = kwargs.get("checks", []) checks = kwargs.get("checks", [])
self.checks: List[Check] = checks
try: try:
cooldown = func.__commands_cooldown__ cooldown = command_attrs.pop("cooldown")
except AttributeError: except KeyError:
cooldown = kwargs.get("cooldown") cooldown = kwargs.get("cooldown")
if cooldown is None: if cooldown is None:
@ -411,14 +415,10 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
buckets = cooldown buckets = cooldown
else: else:
raise TypeError("Cooldown must be a an instance of CooldownMapping or None.") raise TypeError("Cooldown must be a an instance of CooldownMapping or None.")
self.checks: List[Check] = checks
self._buckets: CooldownMapping = buckets self._buckets: CooldownMapping = buckets
self._max_concurrency = kwargs.get("max_concurrency")
try:
max_concurrency = func.__commands_max_concurrency__
except AttributeError:
max_concurrency = kwargs.get("max_concurrency")
self._max_concurrency: Optional[MaxConcurrency] = max_concurrency
self.require_var_positional: bool = kwargs.get("require_var_positional", False) self.require_var_positional: bool = kwargs.get("require_var_positional", False)
self.ignore_extra: bool = kwargs.get("ignore_extra", True) self.ignore_extra: bool = kwargs.get("ignore_extra", True)
@ -435,20 +435,23 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
self._before_invoke: Optional[Hook] = None self._before_invoke: Optional[Hook] = None
try: try:
before_invoke = func.__before_invoke__ before_invoke = command_attrs.pop("before_invoke")
except AttributeError: except KeyError:
pass pass
else: else:
self.before_invoke(before_invoke) self.before_invoke(before_invoke)
self._after_invoke: Optional[Hook] = None self._after_invoke: Optional[Hook] = None
try: try:
after_invoke = func.__after_invoke__ after_invoke = command_attrs.pop("after_invoke")
except AttributeError: except KeyError:
pass pass
else: else:
self.after_invoke(after_invoke) self.after_invoke(after_invoke)
# Handle user provided command attrs
self._update_attrs(**command_attrs)
@property @property
def callback( def callback(
self, self,
@ -474,6 +477,10 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
self.params, self.option_descriptions = get_signature_parameters(function, globalns) self.params, self.option_descriptions = get_signature_parameters(function, globalns)
def _update_attrs(self, **command_attrs: Any):
for key, value in command_attrs.items():
setattr(self, key, value)
def add_check(self, func: Check) -> None: def add_check(self, func: Check) -> None:
"""Adds a check to the command. """Adds a check to the command.
@ -1829,7 +1836,7 @@ def group(
return command(name=name, cls=cls, **attrs) # type: ignore return command(name=name, cls=cls, **attrs) # type: ignore
def check(predicate: Check) -> Callable[[T], T]: def check(predicate: Check, **command_attrs: Any) -> Callable[[T], T]:
r"""A decorator that adds a check to the :class:`.Command` or its r"""A decorator that adds a check to the :class:`.Command` or its
subclasses. These checks could be accessed via :attr:`.Command.checks`. subclasses. These checks could be accessed via :attr:`.Command.checks`.
@ -1898,16 +1905,22 @@ def check(predicate: Check) -> Callable[[T], T]:
----------- -----------
predicate: Callable[[:class:`Context`], :class:`bool`] predicate: Callable[[:class:`Context`], :class:`bool`]
The predicate to check if the command should be invoked. The predicate to check if the command should be invoked.
**command_attrs: Dict[:class:`str`, Any]
key: value pairs to be added to the command's attributes.
""" """
def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]: def decorator(func: Union[Command, CoroFunc]) -> Union[Command, CoroFunc]:
if isinstance(func, Command): if isinstance(func, Command):
func.checks.append(predicate) func.checks.append(predicate)
func._update_attrs(**command_attrs)
else: else:
if not hasattr(func, "__commands_checks__"): if not hasattr(func, "__commands_checks__"):
func.__commands_checks__ = [] func.__commands_checks__ = []
if not hasattr(func, "__command_attrs__"):
func.__command_attrs__ = {}
func.__commands_checks__.append(predicate) func.__commands_checks__.append(predicate)
func.__command_attrs__.update(command_attrs)
return func return func
@ -2080,7 +2093,7 @@ def has_any_role(*items: Union[int, str]) -> Callable[[T], T]:
return True return True
raise MissingAnyRole(list(items)) raise MissingAnyRole(list(items))
return check(predicate) return check(predicate, required_roles=items)
def bot_has_role(item: int) -> Callable[[T], T]: def bot_has_role(item: int) -> Callable[[T], T]:
@ -2110,7 +2123,7 @@ def bot_has_role(item: int) -> Callable[[T], T]:
raise BotMissingRole(item) raise BotMissingRole(item)
return True return True
return check(predicate) return check(predicate, bot_required_role=item)
def bot_has_any_role(*items: int) -> Callable[[T], T]: def bot_has_any_role(*items: int) -> Callable[[T], T]:
@ -2139,7 +2152,7 @@ def bot_has_any_role(*items: int) -> Callable[[T], T]:
return True return True
raise BotMissingAnyRole(list(items)) raise BotMissingAnyRole(list(items))
return check(predicate) return check(predicate, bot_required_roles=items)
def has_permissions(**perms: bool) -> Callable[[T], T]: def has_permissions(**perms: bool) -> Callable[[T], T]:
@ -2187,7 +2200,7 @@ def has_permissions(**perms: bool) -> Callable[[T], T]:
raise MissingPermissions(missing) raise MissingPermissions(missing)
return check(predicate) return check(predicate, required_permissions=perms)
def bot_has_permissions(**perms: bool) -> Callable[[T], T]: def bot_has_permissions(**perms: bool) -> Callable[[T], T]:
@ -2214,7 +2227,7 @@ def bot_has_permissions(**perms: bool) -> Callable[[T], T]:
raise BotMissingPermissions(missing) raise BotMissingPermissions(missing)
return check(predicate) return check(predicate, bot_required_permissions=perms)
def has_guild_permissions(**perms: bool) -> Callable[[T], T]: def has_guild_permissions(**perms: bool) -> Callable[[T], T]:
@ -2243,7 +2256,7 @@ def has_guild_permissions(**perms: bool) -> Callable[[T], T]:
raise MissingPermissions(missing) raise MissingPermissions(missing)
return check(predicate) return check(predicate, required_guild_permissions=perms)
def bot_has_guild_permissions(**perms: bool) -> Callable[[T], T]: def bot_has_guild_permissions(**perms: bool) -> Callable[[T], T]:
@ -2269,7 +2282,7 @@ def bot_has_guild_permissions(**perms: bool) -> Callable[[T], T]:
raise BotMissingPermissions(missing) raise BotMissingPermissions(missing)
return check(predicate) return check(predicate, bot_required_guild_permissions=perms)
def dm_only() -> Callable[[T], T]: def dm_only() -> Callable[[T], T]:
@ -2380,7 +2393,10 @@ def cooldown(
if isinstance(func, Command): if isinstance(func, Command):
func._buckets = CooldownMapping(Cooldown(rate, per), type) func._buckets = CooldownMapping(Cooldown(rate, per), type)
else: else:
func.__commands_cooldown__ = CooldownMapping(Cooldown(rate, per), type) if not hasattr(func, "__command_attrs__"):
func.__command_attrs__ = {}
func.__command_attrs__["cooldown"] = CooldownMapping(Cooldown(rate, per), type)
return func return func
return decorator # type: ignore return decorator # type: ignore
@ -2424,7 +2440,10 @@ def dynamic_cooldown(
if isinstance(func, Command): if isinstance(func, Command):
func._buckets = DynamicCooldownMapping(cooldown, type) func._buckets = DynamicCooldownMapping(cooldown, type)
else: else:
func.__commands_cooldown__ = DynamicCooldownMapping(cooldown, type) if not hasattr(func, "__command_attrs__"):
func.__command_attrs__ = {}
func.__command_attrs__["cooldown"] = DynamicCooldownMapping(cooldown, type)
return func return func
return decorator # type: ignore return decorator # type: ignore
@ -2459,7 +2478,10 @@ def max_concurrency(number: int, per: BucketType = BucketType.default, *, wait:
if isinstance(func, Command): if isinstance(func, Command):
func._max_concurrency = value func._max_concurrency = value
else: else:
func.__commands_max_concurrency__ = value if not hasattr(func, "__command_attrs__"):
func.__command_attrs__ = {}
func.__command_attrs__["_max_concurrency"] = value
return func return func
return decorator # type: ignore return decorator # type: ignore
@ -2508,7 +2530,10 @@ def before_invoke(coro) -> Callable[[T], T]:
if isinstance(func, Command): if isinstance(func, Command):
func.before_invoke(coro) func.before_invoke(coro)
else: else:
func.__before_invoke__ = coro if not hasattr(func, "__command_attrs__"):
func.__command_attrs__ = {}
func.__command_attrs__["before_invoke"] = coro
return func return func
return decorator # type: ignore return decorator # type: ignore
@ -2527,7 +2552,7 @@ def after_invoke(coro) -> Callable[[T], T]:
if isinstance(func, Command): if isinstance(func, Command):
func.after_invoke(coro) func.after_invoke(coro)
else: else:
func.__after_invoke__ = coro func.__command_attrs__["after_invoke"] = coro
return func return func
return decorator # type: ignore return decorator # type: ignore

View File

@ -169,9 +169,14 @@ class Attachment(Hashable):
The attachment's `media type <https://en.wikipedia.org/wiki/Media_type>`_ The attachment's `media type <https://en.wikipedia.org/wiki/Media_type>`_
.. versionadded:: 1.7 .. versionadded:: 1.7
ephemeral: Optional[:class:`bool`]
If the attachment is ephemeral. Ephemeral attachments are temporary and
will automatically be removed after a set period of time.
.. versionadded:: 2.0
""" """
__slots__ = ("id", "size", "height", "width", "filename", "url", "proxy_url", "_http", "content_type") __slots__ = ("id", "size", "height", "width", "filename", "url", "proxy_url", "ephemeral", "_http", "content_type")
def __init__(self, *, data: AttachmentPayload, state: ConnectionState): def __init__(self, *, data: AttachmentPayload, state: ConnectionState):
self.id: int = int(data["id"]) self.id: int = int(data["id"])
@ -183,6 +188,7 @@ class Attachment(Hashable):
self.proxy_url: str = data.get("proxy_url") self.proxy_url: str = data.get("proxy_url")
self._http = state.http self._http = state.http
self.content_type: Optional[str] = data.get("content_type") self.content_type: Optional[str] = data.get("content_type")
self.ephemeral: Optional[bool] = data.get("ephemeral")
def is_spoiler(self) -> bool: def is_spoiler(self) -> bool:
""":class:`bool`: Whether this attachment contains a spoiler.""" """:class:`bool`: Whether this attachment contains a spoiler."""

View File

@ -53,6 +53,7 @@ class _AttachmentOptional(TypedDict, total=False):
height: Optional[int] height: Optional[int]
width: Optional[int] width: Optional[int]
content_type: str content_type: str
ephemeral: bool
spoiler: bool spoiler: bool

View File

@ -80,7 +80,7 @@ class BaseUser(_UserTag):
_state: ConnectionState _state: ConnectionState
_avatar: Optional[str] _avatar: Optional[str]
_banner: Optional[str] _banner: Optional[str]
_accent_colour: Optional[str] _accent_colour: Optional[int]
_public_flags: int _public_flags: int
def __init__(self, *, state: ConnectionState, data: UserPayload) -> None: def __init__(self, *, state: ConnectionState, data: UserPayload) -> None:

View File

@ -27,7 +27,7 @@ async def userinfo(ctx: commands.Context, user: discord.User):
# and can do the following: # and can do the following:
user_id = user.id user_id = user.id
username = user.name username = user.name
avatar = user.avatar.url avatar = user.display_avatar.url
await ctx.send(f"User found: {user_id} -- {username}\n{avatar}") await ctx.send(f"User found: {user_id} -- {username}\n{avatar}")