23 Commits

Author SHA1 Message Date
5a51f9dd23 Fix typings in raw_models.py 2021-09-01 22:30:05 +02:00
c34612dd71 Merge branch '2.0' into pr7268
# Conflicts:
#	discord/raw_models.py
2021-09-01 22:27:34 +02:00
6f5614373a Merge pull request #15 from WhoTheOOF/patch-3
Add original dark blurple
2021-09-01 04:40:12 +02:00
2e12746c70 Merge pull request #11 from TheMoksej/patch-2
versionadded needs to be added here
2021-09-01 04:38:26 +02:00
5ef72e4f70 Merge pull request #20 from paris-ci/special_methods
Special methods
2021-09-01 04:27:33 +02:00
7e18d30820 Merge pull request #17 from Astrea49/2.0
Prefer `static_format` over `format` with static assets
2021-09-01 04:26:31 +02:00
923a6a885d Merge pull request #13 from paris-ci/rework_set_in_embeds
Make `Embed.image` and `Embed.thumbnail` full-featured properties
2021-09-01 04:24:57 +02:00
b28893aa36 Merge pull request #40 from Gnome-py/required-intents
Remove intents.default and make intents a required parameter
2021-09-01 04:21:37 +02:00
6e41bd2219 Remove intents.default and make intents a required parameter 2021-08-31 20:53:54 +01:00
773ad6f5bf add back the silent kwarg to message.delete (#9)
* add back the silent kwarg to message.delete

* forgot about versionadded

* shorten the if statement

* simplify raising a bit ig?

* should be versionchanged instead

Co-authored-by: Arthur <site-github@api-d.com>

* remove `Optional` from parameter and doc string

Co-authored-by: Arthur <site-github@api-d.com>
2021-08-29 10:57:07 -07:00
de0e8ef108 V2.0 changelog (#8)
* Copy in messages from Danny, verbatim

* Type whats_new

* Add my changes to the changelog

* Fix a typo
2021-08-29 10:55:49 -07:00
64ee792391 Add int() support to Hashable, making it available across the board for AuditLogEntry, *Channel, Guild, Object, Message, ... 2021-08-29 01:21:20 +02:00
22de755059 Add int() and str() support to Message 2021-08-29 01:09:05 +02:00
fa7f8efc8e Add int() support to Guild 2021-08-29 01:07:26 +02:00
9d1df65af3 Add int() support to Role 2021-08-29 01:06:18 +02:00
3ce86f6cde Add int() support to Emoji 2021-08-29 01:05:28 +02:00
31e3e99c2b Add __int__ special method to User and Member 2021-08-29 00:59:29 +02:00
cc90d312f5 Add original dark blurple
This adds the old discord dark blurple color as a classmethod for embeds and whatever.
2021-08-28 17:26:46 -05:00
75f052b8c9 Prefer static_format over format with static assets 2021-08-28 18:24:05 -04:00
c8cdb275c5 Fix set_* function name 2021-08-28 23:29:49 +02:00
406f0ffe04 Make Embed.image and Embed.thumbnail full-featured properties
This avoids the need for set_* methods.
2021-08-28 23:14:26 +02:00
a4acbd2e08 versionadded needs to be added here 2021-08-28 22:10:58 +02:00
e4750c7105 Add raw thread delete event 2021-07-23 10:13:02 +02:00
46 changed files with 454 additions and 88 deletions

View File

@ -313,10 +313,11 @@ class Asset(AssetMixin):
if self._animated: if self._animated:
if format not in VALID_ASSET_FORMATS: if format not in VALID_ASSET_FORMATS:
raise InvalidArgument(f'format must be one of {VALID_ASSET_FORMATS}') raise InvalidArgument(f'format must be one of {VALID_ASSET_FORMATS}')
else: url = url.with_path(f'{path}.{format}')
elif static_format is MISSING:
if format not in VALID_STATIC_FORMATS: if format not in VALID_STATIC_FORMATS:
raise InvalidArgument(f'format must be one of {VALID_STATIC_FORMATS}') raise InvalidArgument(f'format must be one of {VALID_STATIC_FORMATS}')
url = url.with_path(f'{path}.{format}') url = url.with_path(f'{path}.{format}')
if static_format is not MISSING and not self._animated: if static_format is not MISSING and not self._animated:
if static_format not in VALID_STATIC_FORMATS: if static_format not in VALID_STATIC_FORMATS:

View File

@ -330,6 +330,10 @@ class AuditLogEntry(Hashable):
Returns the entry's hash. Returns the entry's hash.
.. describe:: int(x)
Returns the entry's ID.
.. versionchanged:: 1.7 .. versionchanged:: 1.7
Audit log entries are now comparable and hashable. Audit log entries are now comparable and hashable.

View File

@ -115,6 +115,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
Returns the channel's name. Returns the channel's name.
.. describe:: int(x)
Returns the channel's ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`
@ -1334,6 +1338,10 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
Returns the category's name. Returns the category's name.
.. describe:: int(x)
Returns the category's ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`
@ -1556,6 +1564,10 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
Returns the channel's name. Returns the channel's name.
.. describe:: int(x)
Returns the channel's ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`
@ -1728,6 +1740,10 @@ class DMChannel(discord.abc.Messageable, Hashable):
Returns a string representation of the channel Returns a string representation of the channel
.. describe:: int(x)
Returns the channel's ID.
Attributes Attributes
---------- ----------
recipient: Optional[:class:`User`] recipient: Optional[:class:`User`]
@ -1854,6 +1870,10 @@ class GroupChannel(discord.abc.Messageable, Hashable):
Returns a string representation of the channel Returns a string representation of the channel
.. describe:: int(x)
Returns the channel's ID.
Attributes Attributes
---------- ----------
recipients: List[:class:`User`] recipients: List[:class:`User`]
@ -2000,6 +2020,10 @@ class PartialMessageable(discord.abc.Messageable, Hashable):
Returns the partial messageable's hash. Returns the partial messageable's hash.
.. describe:: int(x)
Returns the messageable's ID.
Attributes Attributes
----------- -----------
id: :class:`int` id: :class:`int`

View File

@ -142,7 +142,6 @@ class Client:
intents: :class:`Intents` intents: :class:`Intents`
The intents that you want to enable for the session. This is a way of The intents that you want to enable for the session. This is a way of
disabling and enabling certain gateway events from triggering and being sent. disabling and enabling certain gateway events from triggering and being sent.
If not given, defaults to a regularly constructed :class:`Intents` class.
.. versionadded:: 1.5 .. versionadded:: 1.5
member_cache_flags: :class:`MemberCacheFlags` member_cache_flags: :class:`MemberCacheFlags`
@ -203,9 +202,12 @@ class Client:
def __init__( def __init__(
self, self,
*, *,
intents: Intents,
loop: Optional[asyncio.AbstractEventLoop] = None, loop: Optional[asyncio.AbstractEventLoop] = None,
**options: Any, **options: Any,
): ):
options["intents"] = intents
# self.ws is set in the connect method # self.ws is set in the connect method
self.ws: DiscordWebSocket = None # type: ignore self.ws: DiscordWebSocket = None # type: ignore
self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() if loop is None else loop

View File

@ -325,5 +325,14 @@ class Colour:
""" """
return cls(0xFEE75C) return cls(0xFEE75C)
@classmethod
def dark_blurple(cls: Type[CT]) -> CT:
"""A factory method that returns a :class:`Colour` with a value of ``0x4E5D94``.
This is the original Dark Blurple branding.
.. versionadded:: 2.0
"""
return cls(0x4E5D94)
Color = Colour Color = Colour

View File

@ -72,30 +72,36 @@ if TYPE_CHECKING:
T = TypeVar('T') T = TypeVar('T')
MaybeEmpty = Union[T, _EmptyEmbed] MaybeEmpty = Union[T, _EmptyEmbed]
class _EmbedFooterProxy(Protocol): class _EmbedFooterProxy(Protocol):
text: MaybeEmpty[str] text: MaybeEmpty[str]
icon_url: MaybeEmpty[str] icon_url: MaybeEmpty[str]
class _EmbedFieldProxy(Protocol): class _EmbedFieldProxy(Protocol):
name: MaybeEmpty[str] name: MaybeEmpty[str]
value: MaybeEmpty[str] value: MaybeEmpty[str]
inline: bool inline: bool
class _EmbedMediaProxy(Protocol): class _EmbedMediaProxy(Protocol):
url: MaybeEmpty[str] url: MaybeEmpty[str]
proxy_url: MaybeEmpty[str] proxy_url: MaybeEmpty[str]
height: MaybeEmpty[int] height: MaybeEmpty[int]
width: MaybeEmpty[int] width: MaybeEmpty[int]
class _EmbedVideoProxy(Protocol): class _EmbedVideoProxy(Protocol):
url: MaybeEmpty[str] url: MaybeEmpty[str]
height: MaybeEmpty[int] height: MaybeEmpty[int]
width: MaybeEmpty[int] width: MaybeEmpty[int]
class _EmbedProviderProxy(Protocol): class _EmbedProviderProxy(Protocol):
name: MaybeEmpty[str] name: MaybeEmpty[str]
url: MaybeEmpty[str] url: MaybeEmpty[str]
class _EmbedAuthorProxy(Protocol): class _EmbedAuthorProxy(Protocol):
name: MaybeEmpty[str] name: MaybeEmpty[str]
url: MaybeEmpty[str] url: MaybeEmpty[str]
@ -175,15 +181,15 @@ class Embed:
Empty: Final = EmptyEmbed Empty: Final = EmptyEmbed
def __init__( def __init__(
self, self,
*, *,
colour: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, colour: Union[int, Colour, _EmptyEmbed] = EmptyEmbed,
color: Union[int, Colour, _EmptyEmbed] = EmptyEmbed, color: Union[int, Colour, _EmptyEmbed] = EmptyEmbed,
title: MaybeEmpty[Any] = EmptyEmbed, title: MaybeEmpty[Any] = EmptyEmbed,
type: EmbedType = 'rich', type: EmbedType = 'rich',
url: MaybeEmpty[Any] = EmptyEmbed, url: MaybeEmpty[Any] = EmptyEmbed,
description: MaybeEmpty[Any] = EmptyEmbed, description: MaybeEmpty[Any] = EmptyEmbed,
timestamp: datetime.datetime = None, timestamp: datetime.datetime = None,
): ):
self.colour = colour if colour is not EmptyEmbed else color self.colour = colour if colour is not EmptyEmbed else color
@ -397,6 +403,19 @@ class Embed:
""" """
return EmbedProxy(getattr(self, '_image', {})) # type: ignore return EmbedProxy(getattr(self, '_image', {})) # type: ignore
@image.setter
def image(self: E, *, url: Any):
self._image = {
'url': str(url),
}
@image.deleter
def image(self: E):
try:
del self._image
except AttributeError:
pass
def set_image(self: E, *, url: MaybeEmpty[Any]) -> E: def set_image(self: E, *, url: MaybeEmpty[Any]) -> E:
"""Sets the image for the embed content. """Sets the image for the embed content.
@ -413,14 +432,9 @@ class Embed:
""" """
if url is EmptyEmbed: if url is EmptyEmbed:
try: del self.image
del self._image
except AttributeError:
pass
else: else:
self._image = { self.image = url
'url': str(url),
}
return self return self
@ -439,7 +453,25 @@ class Embed:
""" """
return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore
def set_thumbnail(self: E, *, url: MaybeEmpty[Any]) -> E: @thumbnail.setter
def thumbnail(self: E, *, url: Any):
"""Sets the thumbnail for the embed content.
"""
self._thumbnail = {
'url': str(url),
}
return
@thumbnail.deleter
def thumbnail(self):
try:
del self.thumbnail
except AttributeError:
pass
def set_thumbnail(self: E, *, 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
@ -453,16 +485,10 @@ class Embed:
url: :class:`str` url: :class:`str`
The source URL for the thumbnail. Only HTTP(S) is supported. The source URL for the thumbnail. Only HTTP(S) is supported.
""" """
if url is EmptyEmbed: if url is EmptyEmbed:
try: del self.thumbnail
del self._thumbnail
except AttributeError:
pass
else: else:
self._thumbnail = { self.thumbnail = url
'url': str(url),
}
return self return self

View File

@ -72,6 +72,10 @@ class Emoji(_EmojiTag, AssetMixin):
Returns the emoji rendered for discord. Returns the emoji rendered for discord.
.. describe:: int(x)
Returns the emoji ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`
@ -137,6 +141,9 @@ class Emoji(_EmojiTag, AssetMixin):
return f'<a:{self.name}:{self.id}>' return f'<a:{self.name}:{self.id}>'
return f'<:{self.name}:{self.id}>' return f'<:{self.name}:{self.id}>'
def __int__(self) -> int:
return self.id
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<Emoji id={self.id} name={self.name!r} animated={self.animated} managed={self.managed}>' return f'<Emoji id={self.id} name={self.name!r} animated={self.animated} managed={self.managed}>'

View File

@ -120,8 +120,8 @@ class _DefaultRepr:
_default = _DefaultRepr() _default = _DefaultRepr()
class BotBase(GroupMixin): class BotBase(GroupMixin):
def __init__(self, command_prefix, help_command=_default, description=None, **options): def __init__(self, command_prefix, help_command=_default, description=None, *, intents: discord.Intents, **options):
super().__init__(**options) super().__init__(**options, intents=intents)
self.command_prefix = command_prefix self.command_prefix = command_prefix
self.extra_events: Dict[str, List[CoroFunc]] = {} self.extra_events: Dict[str, List[CoroFunc]] = {}
self.__cogs: Dict[str, Cog] = {} self.__cogs: Dict[str, Cog] = {}

View File

@ -480,16 +480,6 @@ class Intents(BaseFlags):
self.value = self.DEFAULT_VALUE self.value = self.DEFAULT_VALUE
return self return self
@classmethod
def default(cls: Type[Intents]) -> Intents:
"""A factory method that creates a :class:`Intents` with everything enabled
except :attr:`presences` and :attr:`members`.
"""
self = cls.all()
self.presences = False
self.members = False
return self
@flag_value @flag_value
def guilds(self): def guilds(self):
""":class:`bool`: Whether guild related events are enabled. """:class:`bool`: Whether guild related events are enabled.

View File

@ -140,6 +140,10 @@ class Guild(Hashable):
Returns the guild's name. Returns the guild's name.
.. describe:: int(x)
Returns the guild's ID.
Attributes Attributes
---------- ----------
name: :class:`str` name: :class:`str`
@ -738,12 +742,16 @@ class Guild(Hashable):
@property @property
def humans(self) -> List[Member]: def humans(self) -> List[Member]:
"""List[:class:`Member`]: A list of human members that belong to this guild.""" """List[:class:`Member`]: A list of human members that belong to this guild.
.. versionadded:: 2.0 """
return [member for member in self.members if not member.bot] return [member for member in self.members if not member.bot]
@property @property
def bots(self) -> List[Member]: def bots(self) -> List[Member]:
"""List[:class:`Member`]: A list of bots that belong to this guild.""" """List[:class:`Member`]: A list of bots that belong to this guild.
.. versionadded:: 2.0 """
return [member for member in self.members if member.bot] return [member for member in self.members if member.bot]
def get_member(self, user_id: int, /) -> Optional[Member]: def get_member(self, user_id: int, /) -> Optional[Member]:

View File

@ -230,6 +230,7 @@ class Invite(Hashable):
Returns the invite URL. Returns the invite URL.
The following table illustrates what methods will obtain the attributes: The following table illustrates what methods will obtain the attributes:
+------------------------------------+------------------------------------------------------------+ +------------------------------------+------------------------------------------------------------+
@ -433,6 +434,9 @@ class Invite(Hashable):
def __str__(self) -> str: def __str__(self) -> str:
return self.url return self.url
def __int__(self) -> int:
return 0 # To keep the object compatible with the hashable abc.
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f'<Invite code={self.code!r} guild={self.guild!r} ' f'<Invite code={self.code!r} guild={self.guild!r} '

View File

@ -226,6 +226,10 @@ class Member(discord.abc.Messageable, _UserTag):
Returns the member's name with the discriminator. Returns the member's name with the discriminator.
.. describe:: int(x)
Returns the user's ID.
Attributes Attributes
---------- ----------
joined_at: Optional[:class:`datetime.datetime`] joined_at: Optional[:class:`datetime.datetime`]
@ -300,6 +304,9 @@ class Member(discord.abc.Messageable, _UserTag):
def __str__(self) -> str: def __str__(self) -> str:
return str(self._user) return str(self._user)
def __int__(self) -> int:
return self.id
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f'<Member id={self._user.id} name={self._user.name!r} discriminator={self._user.discriminator!r}' f'<Member id={self._user.id} name={self._user.name!r} discriminator={self._user.discriminator!r}'

View File

@ -125,6 +125,10 @@ class Attachment(Hashable):
Returns the hash of the attachment. Returns the hash of the attachment.
.. describe:: int(x)
Returns the attachment's ID.
.. versionchanged:: 1.7 .. versionchanged:: 1.7
Attachment can now be casted to :class:`str` and is hashable. Attachment can now be casted to :class:`str` and is hashable.
@ -503,6 +507,14 @@ class Message(Hashable):
Returns the message's hash. Returns the message's hash.
.. describe:: str(x)
Returns the message's content.
.. describe:: int(x)
Returns the message's ID.
Attributes Attributes
----------- -----------
tts: :class:`bool` tts: :class:`bool`
@ -712,6 +724,10 @@ class Message(Hashable):
f'<{name} id={self.id} channel={self.channel!r} type={self.type!r} author={self.author!r} flags={self.flags!r}>' f'<{name} id={self.id} channel={self.channel!r} type={self.type!r} author={self.author!r} flags={self.flags!r}>'
) )
def __str__(self) -> Optional[str]:
return self.content
def _try_patch(self, data, key, transform=None) -> None: def _try_patch(self, data, key, transform=None) -> None:
try: try:
value = data[key] value = data[key]
@ -1106,7 +1122,7 @@ class Message(Hashable):
if self.type is MessageType.guild_invite_reminder: if self.type is MessageType.guild_invite_reminder:
return 'Wondering who to invite?\nStart by inviting anyone who can help you build the server!' return 'Wondering who to invite?\nStart by inviting anyone who can help you build the server!'
async def delete(self, *, delay: Optional[float] = None) -> None: async def delete(self, *, delay: Optional[float] = None, silent: bool = False) -> None:
"""|coro| """|coro|
Deletes the message. Deletes the message.
@ -1117,12 +1133,17 @@ class Message(Hashable):
.. versionchanged:: 1.1 .. versionchanged:: 1.1
Added the new ``delay`` keyword-only parameter. Added the new ``delay`` keyword-only parameter.
.. versionchanged:: 2.0
Added the new ``silent`` keyword-only parameter.
Parameters Parameters
----------- -----------
delay: Optional[:class:`float`] delay: Optional[:class:`float`]
If provided, the number of seconds to wait in the background If provided, the number of seconds to wait in the background
before deleting the message. If the deletion fails then it is silently ignored. before deleting the message. If the deletion fails then it is silently ignored.
silent: :class:`bool`
If silent is set to ``True``, the error will not be raised, it will be ignored.
This defaults to ``False``
Raises Raises
------ ------
@ -1144,7 +1165,11 @@ class Message(Hashable):
asyncio.create_task(delete(delay)) asyncio.create_task(delete(delay))
else: else:
await self._state.http.delete_message(self.channel.id, self.id) try:
await self._state.http.delete_message(self.channel.id, self.id)
except Exception:
if not silent:
raise
@overload @overload
async def edit( async def edit(
@ -1625,6 +1650,10 @@ class PartialMessage(Hashable):
Returns the partial message's hash. Returns the partial message's hash.
.. describe:: int(x)
Returns the partial message's ID.
Attributes Attributes
----------- -----------
channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`] channel: Union[:class:`TextChannel`, :class:`Thread`, :class:`DMChannel`]

View File

@ -43,5 +43,8 @@ class EqualityComparable:
class Hashable(EqualityComparable): class Hashable(EqualityComparable):
__slots__ = () __slots__ = ()
def __int__(self) -> int:
return self.id
def __hash__(self) -> int: def __hash__(self) -> int:
return self.id >> 22 return self.id >> 22

View File

@ -69,6 +69,10 @@ class Object(Hashable):
Returns the object's hash. Returns the object's hash.
.. describe:: int(x)
Returns the object's ID.
Attributes Attributes
----------- -----------
id: :class:`int` id: :class:`int`

View File

@ -39,8 +39,11 @@ if TYPE_CHECKING:
from .message import Message from .message import Message
from .partial_emoji import PartialEmoji from .partial_emoji import PartialEmoji
from .member import Member from .member import Member
from .threads import Thread
from .enums import ChannelType, try_enum
__all__ = ( __all__ = (
'RawMessageDeleteEvent', 'RawMessageDeleteEvent',
'RawBulkMessageDeleteEvent', 'RawBulkMessageDeleteEvent',
@ -49,6 +52,7 @@ __all__ = (
'RawReactionClearEvent', 'RawReactionClearEvent',
'RawReactionClearEmojiEvent', 'RawReactionClearEmojiEvent',
'RawIntegrationDeleteEvent', 'RawIntegrationDeleteEvent',
'RawThreadDeleteEvent',
) )
@ -276,3 +280,31 @@ class RawIntegrationDeleteEvent(_RawReprMixin):
self.application_id: Optional[int] = int(data['application_id']) self.application_id: Optional[int] = int(data['application_id'])
except KeyError: except KeyError:
self.application_id: Optional[int] = None self.application_id: Optional[int] = None
class RawThreadDeleteEvent(_RawReprMixin):
"""Represents the payload for a :func:`on_raw_thread_delete` event.
.. versionadded:: 2.0
Attributes
----------
thread_id: :class:`int`
The ID of the thread that was deleted.
thread_type: :class:`discord.ChannelType`
The channel type of the deleted thread.
guild_id: :class:`int`
The ID of the guild the thread was deleted in.
parent_id: :class:`int`
The ID of the channel the thread was belonged to.
thread: Optional[:class:`discord.Thread`]
The thread, if it could be found in the internal cache.
"""
__slots__ = ('thread_id', 'thread_type', 'parent_id', 'guild_id', 'thread')
def __init__(self, data) -> None:
self.thread_id: int = int(data['id'])
self.thread_type: ChannelType = try_enum(ChannelType, data['type'])
self.guild_id: int = int(data['guild_id'])
self.parent_id: int = int(data['parent_id'])
self.thread: Optional[Thread] = None

View File

@ -141,6 +141,14 @@ class Role(Hashable):
Returns the role's name. Returns the role's name.
.. describe:: str(x)
Returns the role's ID.
.. describe:: int(x)
Returns the role's ID.
Attributes Attributes
---------- ----------
id: :class:`int` id: :class:`int`
@ -195,6 +203,9 @@ class Role(Hashable):
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name
def __int__(self) -> int:
return self.id
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<Role id={self.id} name={self.name!r}>' return f'<Role id={self.id} name={self.name!r}>'

View File

@ -61,6 +61,10 @@ class StageInstance(Hashable):
Returns the stage instance's hash. Returns the stage instance's hash.
.. describe:: int(x)
Returns the stage instance's ID.
Attributes Attributes
----------- -----------
id: :class:`int` id: :class:`int`

View File

@ -152,6 +152,7 @@ class ConnectionState:
handlers: Dict[str, Callable], handlers: Dict[str, Callable],
hooks: Dict[str, Callable], hooks: Dict[str, Callable],
http: HTTPClient, http: HTTPClient,
intents: Intents,
loop: asyncio.AbstractEventLoop, loop: asyncio.AbstractEventLoop,
**options: Any, **options: Any,
) -> None: ) -> None:
@ -194,12 +195,8 @@ class ConnectionState:
else: else:
status = str(status) status = str(status)
intents = options.get('intents', None) if not isinstance(intents, Intents):
if intents is not None: raise TypeError(f'intents parameter must be Intent not {type(intents)!r}')
if not isinstance(intents, Intents):
raise TypeError(f'intents parameter must be Intent not {type(intents)!r}')
else:
intents = Intents.default()
if not intents.guilds: if not intents.guilds:
_log.warning('Guilds intent seems to be disabled. This may cause state related issues.') _log.warning('Guilds intent seems to be disabled. This may cause state related issues.')
@ -854,8 +851,10 @@ class ConnectionState:
_log.debug('THREAD_DELETE referencing an unknown guild ID: %s. Discarding', guild_id) _log.debug('THREAD_DELETE referencing an unknown guild ID: %s. Discarding', guild_id)
return return
thread_id = int(data['id']) raw = RawThreadDeleteEvent(data)
thread = guild.get_thread(thread_id) raw.thread = thread = guild.get_thread(raw.thread_id)
self.dispatch('raw_thread_delete', raw)
if thread is not None: if thread is not None:
guild._remove_thread(thread) # type: ignore guild._remove_thread(thread) # type: ignore
self.dispatch('thread_delete', thread) self.dispatch('thread_delete', thread)

View File

@ -67,6 +67,14 @@ class StickerPack(Hashable):
Returns the name of the sticker pack. Returns the name of the sticker pack.
.. describe:: hash(x)
Returns the hash of the sticker pack.
.. describe:: int(x)
Returns the ID of the sticker pack.
.. describe:: x == y .. describe:: x == y
Checks if the sticker pack is equal to another sticker pack. Checks if the sticker pack is equal to another sticker pack.

View File

@ -74,6 +74,10 @@ class Thread(Messageable, Hashable):
Returns the thread's hash. Returns the thread's hash.
.. describe:: int(x)
Returns the thread's ID.
.. describe:: str(x) .. describe:: str(x)
Returns the thread's name. Returns the thread's name.
@ -748,6 +752,10 @@ class ThreadMember(Hashable):
Returns the thread member's hash. Returns the thread member's hash.
.. describe:: int(x)
Returns the thread member's ID.
.. describe:: str(x) .. describe:: str(x)
Returns the thread member's name. Returns the thread member's name.

View File

@ -96,6 +96,9 @@ class BaseUser(_UserTag):
def __str__(self) -> str: def __str__(self) -> str:
return f'{self.name}#{self.discriminator}' return f'{self.name}#{self.discriminator}'
def __int__(self) -> int:
return self.id
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return isinstance(other, _UserTag) and other.id == self.id return isinstance(other, _UserTag) and other.id == self.id
@ -415,6 +418,10 @@ class User(BaseUser, discord.abc.Messageable):
Returns the user's name with discriminator. Returns the user's name with discriminator.
.. describe:: int(x)
Returns the user's ID.
Attributes Attributes
----------- -----------
name: :class:`str` name: :class:`str`

View File

@ -886,6 +886,10 @@ class Webhook(BaseWebhook):
Returns the webhooks's hash. Returns the webhooks's hash.
.. describe:: int(x)
Returns the webhooks's ID.
.. versionchanged:: 1.4 .. versionchanged:: 1.4
Webhooks are now comparable and hashable. Webhooks are now comparable and hashable.

View File

@ -475,6 +475,10 @@ class SyncWebhook(BaseWebhook):
Returns the webhooks's hash. Returns the webhooks's hash.
.. describe:: int(x)
Returns the webhooks's ID.
.. versionchanged:: 1.4 .. versionchanged:: 1.4
Webhooks are now comparable and hashable. Webhooks are now comparable and hashable.

View File

@ -718,7 +718,11 @@ to handle it, which defaults to print a traceback and ignoring the exception.
.. function:: on_thread_delete(thread) .. function:: on_thread_delete(thread)
Called whenever a thread is deleted. Called whenever a thread is deleted. If the thread could
not be found in the internal cache this event will not be called.
Threads will not be in the cache if they are archived.
If you need this information use :func:`on_raw_thread_delete` instead.
Note that you can get the guild from :attr:`Thread.guild`. Note that you can get the guild from :attr:`Thread.guild`.
@ -729,6 +733,18 @@ to handle it, which defaults to print a traceback and ignoring the exception.
:param thread: The thread that got deleted. :param thread: The thread that got deleted.
:type thread: :class:`Thread` :type thread: :class:`Thread`
.. function:: on_raw_thread_delete(payload)
Called whenever a thread is deleted. Unlike :func:`on_thread_delete` this
is called regardless of the thread being in the internal thread cache or not.
This requires :attr:`Intents.guilds` to be enabled.
.. versionadded:: 2.0
:param payload: The raw event payload data.
:type payload: :class:`RawThreadDeleteEvent`
.. function:: on_thread_member_join(member) .. function:: on_thread_member_join(member)
on_thread_member_remove(member) on_thread_member_remove(member)
@ -3902,6 +3918,14 @@ RawIntegrationDeleteEvent
.. autoclass:: RawIntegrationDeleteEvent() .. autoclass:: RawIntegrationDeleteEvent()
:members: :members:
RawThreadDeleteEvent
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. attributetable:: RawThreadDeleteEvent
.. autoclass:: RawThreadDeleteEvent()
:members:
PartialWebhookGuild PartialWebhookGuild
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~

View File

@ -11,6 +11,134 @@ Changelog
This page keeps a detailed human friendly rendering of what's new and changed This page keeps a detailed human friendly rendering of what's new and changed
in specific versions. in specific versions.
.. _vp2p0p0:
v2.0.0
--------
This version was partly developed by Danny, and partly by the enhanced-discord.py contributors.
The library has been updated with breaking changes, and as such the major version was changed.
- Performance of the library has improved significantly (all times with 1 process and 1 AutoShardedBot):
- 735 guilds boot up time (with chunking): 57s/1.7 GiB RAM -> 42s/1.4 GiB RAM
- 27k guilds boot up time (with chunking): 477s/8 GiB RAM -> 303s/7.2 GiB RAM
- 48k guilds boot up time (without chunking): 109s -> 67s
- 106k guilds boot up time (without chunking): 3300s -> 3090s
- The entire public API of the library is now completely type hinted.
- There may still be bugs however.
- For best type hinting experience consider using Pyright.
- Almost all edit methods now return their updated counterpart rather than doing an in-place edit.
- Japanese docs were removed, as we are no longer able to keep them in sync.
Breaking Changes
~~~~~~~~~~~~~~~~~
- :meth:`Asset.replace` now only accepts keyword arguments
- ``Asset.with_`` functions now only accept positional only arguments
- :meth:`TextChannel.get_partial_message` is now pos-only
- :meth:`TextChannel.get_thread` is now pos-only
- ``permissions_for`` is now pos-only
- :attr:`GroupChannel.owner` is now Optional
- ``edit`` methods now only accept None if it actually means something (e.g. clearing it)
- ``timeout`` parameter for ``ui.View.__init__`` is now keyword only
- When an interaction has already been responded and another one is sent, :exc:`InteractionResponded`is now raised.
- Discord's API only allows a single :attr:`interaction.response`.
- Separate :func:`on_member_update` and :func:`on_presence_update`
- The new event :func:`on_presence_update` is now called when status/activity is changed.
- :func:`on_member_update` will now no longer have status/activity changes.
- afk parameter in :meth:`Client.change_presence` is removed
- The undocumented private :func:`on_socket_response` event got removed.
- Consider using the newer documented :func:`on_socket_event_type` event instead.
- Using :func:`on_socket_raw_receive` and :func:`on_socket_raw_send` are now opt-in via :attr:`enable_debug_events` toggle.
- :func:`on_socket_raw_receive` is now only dispatched after decompressing the payload.
- Persistent View dispatch mechanism now uses the ``message_id`` key if provided.
- :meth:`Message.start_thread` was renamed to :meth:`Message.create_thread`
- :meth:`TextChannel.start_thread` was renamed to :meth:`TextChannel.create_thread`
- All ``get_`` lookup functions now use positional-only parameters for the id parameter.
- Remove :meth:`TextChannel.active_threads` due to the endpoint being deprecated and slated for removal.
- Use :meth:`Guild.active_threads` instead.
- :attr:`User.avatar` now returns None if the user did not upload an avatar.
- Use :attr:`User.display_avatar` to get the avatar and fallback to the default avatar to go back to the old behaviour.
New Features
~~~~~~~~~~~~~~
- Channel types are now typed
- Member is now typed
- Client is now typed
- Permissions are now typed
- Core library errors are now typed
- Add various examples showing how to use views. There are more to come.
- :attr:`GroupChannel.owner_id` now gets the owner ID
- ``edit`` methods now don't rely on previous state
- :meth:`View.from_message` converts a Message.components to a View
- :attr:`Thread.type` to get the thread channel type
- :attr:`ButtonStyle.url` alias for :attr:`ButtonStyle.link`
- Add default style for :class:`ui.Button` constructor
- This makes it so creating a URL button is as simple as ``ui.Button(url='...', label='...')``
- :attr:`Thread.mention` to get the mention string for a thread
- :meth:`Thread.is_nsfw` to check whether the parent channel of the thread is NSFW
- Add support for fetching the original interaction response message.
- :meth:`Interaction.original_message` will retrieve it and returns an InteractionMessage
- :meth:`InteractionMessage.edit` or :meth:`Interaction.edit_original_message` will edit it
- :meth:`InteractionMessage.delete` or :meth:`Interaction.delete_original_message` will delete it
- :attr:`MessageFlags.ephemeral` to get whether a message is ephemeral
- :meth:`Client.fetch_channel` now fetches threads as well
- :class:`SelectOption` now has a __str__ that matches the client representation.
- This might change in the future to remove the description from it.
- Add a converter for :class:`discord.Thread`
- Allow ``clean_content`` converter to work stand-alone
- Add :meth:`User.banner` to get a user's banner and :meth:`User.accent_colour` to get the set banner colour.
- Due to an API limitation this requires using :meth:`Client.fetch_user`.
- Add ``reason`` keyword argument to more methods
- Add audit log events for threads
- Allow public threads to be created without a starter message
- Add :meth:`Guild.get_channel_or_thread` helper method
- Add full support for the new sticker API
- Add :func:`on_socket_event_type` event to get the event type of an event
- Add :attr:`TextChannel.default_auto_archive_duration`
- Add :class:`PartialMessageable` type to allow for sending messages to a channel using only its ``channel_id``.
- This is constructed with :meth:`Client.get_partial_messageable`.
- Add :meth:`Guild.active_threads` to get all of a guild's active threads.
- Add :attr:`Thread.members` to get all cached :class:`ThreadMember` instances of a thread.
- Add :meth:`Thread.fetch_members` to fetch all :class:`ThreadMember` instances of a thread.
- These two require :attr:`Intents.members` to be useful.
- Add support for guild avatars for members under :attr:`Member.guild_avatar`
- Add :attr:`User.display_avatar` and :attr:`Member.display_avatar` to get the user's displayed avatar.
- Add :attr:`Colour.brand_green` and :attr:`Colour.brand_red`
- |commands| :attr:`CommandOnCooldown.type` to get back the type of the cooldown since it was removed from :class:`Cooldown`
- Add :attr:`Guild.bots` and :attr:`Guild.humans`
Bug Fixes
~~~~~~~~~~~
- :class:`Channel` converters now work in DMs again
- Fix :attr:`Interaction.channel` being None in threads
- Change timeouts in :class:`ui.View` to work as documented
- :attr:`Message.__repr__` now shows the proper type, e.g. :class:`WebhookMessage` and :class:`InteractionMessage`
- Change :class:`Cooldown` handling to not reset token window when the number of tokens reaches 0
- Fix audit log permission construction breaking due to unexpected type errors.
- Fix :func:`on_thread_join` not dispatching when a thread is unarchived
- Fix :attr:`Message.guild` being None when a thread is unarchived due to a new message
- :class:`MessageConverter` now works with threads
- Retry requests when a 504 is hit
- Fix :attr:`Thread.slowmode_delay` not updating on edit
- Fix ``permissions_for`` for roles
- Update :attr:`Message.system_content` for newer message types
- Fix :class:`PartialMessage` not working with threads
- Fix crash with stage instances not having the documented ``discoverable_enabled`` key
- Fix some built-in checks not working with threads
- Fix :class:`SyncWebhook` not working in a multi-threaded context
- Fix :func:`on_thread_member_remove` not dispatching properly
- Fix :func:`on_typing` not dispatching for threads
- Update :attr:`Message.is_system` to work with newer message types
- Fix some enums like :class:`VerificationLevel` not being comparable.
- Fix ``io.BytesIO`` sources not working with ffmpeg players
- Fix :meth:`Client.fetch_channel` and :meth:`Guild.fetch_channel` not returning threads
.. _vp1p7p3: .. _vp1p7p3:
v1.7.3 v1.7.3

View File

@ -26,5 +26,5 @@ class MyClient(discord.Client):
async def before_my_task(self): async def before_my_task(self):
await self.wait_until_ready() # wait until the bot logs in await self.wait_until_ready() # wait until the bot logs in
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True))
client.run('token') client.run('token')

View File

@ -22,5 +22,5 @@ class MyClient(discord.Client):
await asyncio.sleep(60) # task runs every 60 seconds await asyncio.sleep(60) # task runs every 60 seconds
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True))
client.run('token') client.run('token')

View File

@ -9,10 +9,8 @@ module.
There are a number of utility commands being showcased here.''' There are a number of utility commands being showcased here.'''
intents = discord.Intents.default() intents = discord.Intents(guilds=True, messages=True, members=True)
intents.members = True bot = commands.Bot(command_prefix='t-', description=description, intents=intents)
bot = commands.Bot(command_prefix='?', description=description, intents=intents)
@bot.event @bot.event
async def on_ready(): async def on_ready():

View File

@ -123,8 +123,11 @@ class Music(commands.Cog):
elif ctx.voice_client.is_playing(): elif ctx.voice_client.is_playing():
ctx.voice_client.stop() ctx.voice_client.stop()
bot = commands.Bot(command_prefix=commands.when_mentioned_or("!"), bot = commands.Bot(
description='Relatively simple music bot example') command_prefix=commands.when_mentioned_or("!"),
description='Relatively simple music bot example',
intents=discord.Intents(guilds=True, guild_messages=True, voice_states=True)
)
@bot.event @bot.event
async def on_ready(): async def on_ready():

View File

@ -5,9 +5,8 @@ import typing
import discord import discord
from discord.ext import commands from discord.ext import commands
intents = discord.Intents.default()
intents.members = True
intents = discord.Intents(guilds=True, messages=True, members=True)
bot = commands.Bot('!', intents=intents) bot = commands.Bot('!', intents=intents)

View File

@ -29,7 +29,7 @@ class MyBot(commands.Bot):
return await super().get_context(message, cls=cls) return await super().get_context(message, cls=cls)
bot = MyBot(command_prefix='!') bot = MyBot(command_prefix='!', intents=discord.Intents(guilds=True, messages=True))
@bot.command() @bot.command()
async def guess(ctx, number: int): async def guess(ctx, number: int):

View File

@ -17,5 +17,5 @@ class MyClient(discord.Client):
msg = f'{message.author} has deleted the message: {message.content}' msg = f'{message.author} has deleted the message: {message.content}'
await message.channel.send(msg) await message.channel.send(msg)
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True, messages=True))
client.run('token') client.run('token')

View File

@ -16,5 +16,5 @@ class MyClient(discord.Client):
msg = f'**{before.author}** edited their message:\n{before.content} -> {after.content}' msg = f'**{before.author}** edited their message:\n{before.content} -> {after.content}'
await before.channel.send(msg) await before.channel.send(msg)
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True, messages=True))
client.run('token') client.run('token')

View File

@ -30,5 +30,5 @@ class MyClient(discord.Client):
else: else:
await message.channel.send(f'Oops. It is actually {answer}.') await message.channel.send(f'Oops. It is actually {answer}.')
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True, messages=True))
client.run('token') client.run('token')

View File

@ -14,8 +14,5 @@ class MyClient(discord.Client):
await guild.system_channel.send(to_send) await guild.system_channel.send(to_send)
intents = discord.Intents.default() client = MyClient(intents=discord.Intents(guilds=True, members=True))
intents.members = True
client = MyClient(intents=intents)
client.run('token') client.run('token')

View File

@ -78,8 +78,6 @@ class MyClient(discord.Client):
# If we want to do something in case of errors we'd do it here. # If we want to do something in case of errors we'd do it here.
pass pass
intents = discord.Intents.default() intents = discord.Intents(guilds=True, members=True, guild_reactions=True)
intents.members = True
client = MyClient(intents=intents) client = MyClient(intents=intents)
client.run('token') client.run('token')

View File

@ -13,5 +13,5 @@ class MyClient(discord.Client):
if message.content.startswith('!hello'): if message.content.startswith('!hello'):
await message.reply('Hello!', mention_author=True) await message.reply('Hello!', mention_author=True)
client = MyClient() client = MyClient(intents=discord.Intents(guilds=True, messages=True))
client.run('token') client.run('token')

View File

@ -3,7 +3,11 @@ import typing
import discord import discord
from discord.ext import commands from discord.ext import commands
bot = commands.Bot(command_prefix=commands.when_mentioned, description="Nothing to see here!") bot = commands.Bot(
command_prefix=commands.when_mentioned,
description="Nothing to see here!",
intents=discord.Intents(guilds=True, messages=True)
)
# the `hidden` keyword argument hides it from the help command. # the `hidden` keyword argument hides it from the help command.
@bot.group(hidden=True) @bot.group(hidden=True)

View File

@ -5,7 +5,10 @@ import discord
class Bot(commands.Bot): class Bot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')

View File

@ -5,7 +5,10 @@ import discord
class CounterBot(commands.Bot): class CounterBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')

View File

@ -1,5 +1,3 @@
import typing
import discord import discord
from discord.ext import commands from discord.ext import commands
@ -39,7 +37,10 @@ class DropdownView(discord.ui.View):
class Bot(commands.Bot): class Bot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')

View File

@ -4,7 +4,10 @@ import discord
class EphemeralCounterBot(commands.Bot): class EphemeralCounterBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')

View File

@ -5,7 +5,10 @@ from urllib.parse import quote_plus
class GoogleBot(commands.Bot): class GoogleBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')
@ -36,4 +39,4 @@ async def google(ctx: commands.Context, *, query: str):
await ctx.send(f'Google Result for: `{query}`', view=Google(query)) await ctx.send(f'Google Result for: `{query}`', view=Google(query))
bot.run('token') bot.run()

View File

@ -29,7 +29,11 @@ class PersistentView(discord.ui.View):
class PersistentViewBot(commands.Bot): class PersistentViewBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
self.persistent_views_added = False self.persistent_views_added = False
async def on_ready(self): async def on_ready(self):

View File

@ -120,7 +120,10 @@ class TicTacToe(discord.ui.View):
class TicTacToeBot(commands.Bot): class TicTacToeBot(commands.Bot):
def __init__(self): def __init__(self):
super().__init__(command_prefix=commands.when_mentioned_or('$')) super().__init__(
command_prefix=commands.when_mentioned_or('$'),
intents=discord.Intents(guilds=True, messages=True)
)
async def on_ready(self): async def on_ready(self):
print(f'Logged in as {self.user} (ID: {self.user.id})') print(f'Logged in as {self.user} (ID: {self.user.id})')