conflict fixes
This commit is contained in:
commit
86fd3fb738
@ -62,6 +62,6 @@ from .sticker import Sticker
|
|||||||
|
|
||||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro enhanced releaselevel serial')
|
VersionInfo = namedtuple('VersionInfo', 'major minor micro enhanced releaselevel serial')
|
||||||
|
|
||||||
version_info = VersionInfo(major=1, minor=6, micro=0, enhanced=6, releaselevel='alpha', serial=0)
|
version_info = VersionInfo(major=1, minor=6, micro=0, enhanced=7, releaselevel='alpha', serial=0)
|
||||||
|
|
||||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||||
|
@ -758,7 +758,7 @@ class GuildChannel:
|
|||||||
|
|
||||||
Returns a list of all active instant invites from this channel.
|
Returns a list of all active instant invites from this channel.
|
||||||
|
|
||||||
You must have :attr:`~Permissions.manage_guild` to get this information.
|
You must have :attr:`~Permissions.manage_channels` to get this information.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
|
@ -153,6 +153,19 @@ class Asset:
|
|||||||
|
|
||||||
return cls(state, '/stickers/{0.id}/{0.image}.png?size={2}'.format(sticker, format, size))
|
return cls(state, '/stickers/{0.id}/{0.image}.png?size={2}'.format(sticker, format, size))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _from_emoji(cls, state, emoji, *, format=None, static_format='png'):
|
||||||
|
if format is not None and format not in VALID_AVATAR_FORMATS:
|
||||||
|
raise InvalidArgument("format must be None or one of {}".format(VALID_AVATAR_FORMATS))
|
||||||
|
if format == "gif" and not emoji.animated:
|
||||||
|
raise InvalidArgument("non animated emoji's do not support gif format")
|
||||||
|
if static_format not in VALID_STATIC_FORMATS:
|
||||||
|
raise InvalidArgument("static_format must be one of {}".format(VALID_STATIC_FORMATS))
|
||||||
|
if format is None:
|
||||||
|
format = 'gif' if emoji.animated else static_format
|
||||||
|
|
||||||
|
return cls(state, '/emojis/{0.id}.{1}'.format(emoji, format))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.BASE + self._url if self._url is not None else ''
|
return self.BASE + self._url if self._url is not None else ''
|
||||||
|
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -34,7 +34,6 @@ from .mixins import Hashable
|
|||||||
from . import utils
|
from . import utils
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
from .errors import ClientException, NoMoreItems, InvalidArgument
|
from .errors import ClientException, NoMoreItems, InvalidArgument
|
||||||
from .webhook import Webhook
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'TextChannel',
|
'TextChannel',
|
||||||
@ -228,7 +227,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
A value of `0` disables slowmode. The maximum value possible is `21600`.
|
A value of `0` disables slowmode. The maximum value possible is `21600`.
|
||||||
type: :class:`ChannelType`
|
type: :class:`ChannelType`
|
||||||
Change the type of this text channel. Currently, only conversion between
|
Change the type of this text channel. Currently, only conversion between
|
||||||
:attr:`ChannelType.text` and :attr:`ChannelType.news` is supported. This
|
:attr:`ChannelType.text` and :attr:`ChannelType.news` is supported. This
|
||||||
is only available to guilds that contain ``NEWS`` in :attr:`Guild.features`.
|
is only available to guilds that contain ``NEWS`` in :attr:`Guild.features`.
|
||||||
reason: Optional[:class:`str`]
|
reason: Optional[:class:`str`]
|
||||||
The reason for editing this channel. Shows up on the audit log.
|
The reason for editing this channel. Shows up on the audit log.
|
||||||
@ -436,6 +435,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
The webhooks for this channel.
|
The webhooks for this channel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .webhook import Webhook
|
||||||
data = await self._state.http.channel_webhooks(self.id)
|
data = await self._state.http.channel_webhooks(self.id)
|
||||||
return [Webhook.from_state(d, state=self._state) for d in data]
|
return [Webhook.from_state(d, state=self._state) for d in data]
|
||||||
|
|
||||||
@ -472,6 +472,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
The created webhook.
|
The created webhook.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .webhook import Webhook
|
||||||
if avatar is not None:
|
if avatar is not None:
|
||||||
avatar = utils._bytes_to_base64_data(avatar)
|
avatar = utils._bytes_to_base64_data(avatar)
|
||||||
|
|
||||||
@ -519,9 +520,32 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
|||||||
if not isinstance(destination, TextChannel):
|
if not isinstance(destination, TextChannel):
|
||||||
raise InvalidArgument('Expected TextChannel received {0.__name__}'.format(type(destination)))
|
raise InvalidArgument('Expected TextChannel received {0.__name__}'.format(type(destination)))
|
||||||
|
|
||||||
|
from .webhook import Webhook
|
||||||
data = await self._state.http.follow_webhook(self.id, webhook_channel_id=destination.id, reason=reason)
|
data = await self._state.http.follow_webhook(self.id, webhook_channel_id=destination.id, reason=reason)
|
||||||
return Webhook._as_follower(data, channel=destination, user=self._state.user)
|
return Webhook._as_follower(data, channel=destination, user=self._state.user)
|
||||||
|
|
||||||
|
def get_partial_message(self, message_id):
|
||||||
|
"""Creates a :class:`PartialMessage` from the message ID.
|
||||||
|
|
||||||
|
This is useful if you want to work with a message and only have its ID without
|
||||||
|
doing an unnecessary API call.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
message_id: :class:`int`
|
||||||
|
The message ID to create a partial message for.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
:class:`PartialMessage`
|
||||||
|
The partial message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .message import PartialMessage
|
||||||
|
return PartialMessage(channel=self, id=message_id)
|
||||||
|
|
||||||
class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
|
||||||
"""Represents a Discord guild voice channel.
|
"""Represents a Discord guild voice channel.
|
||||||
|
|
||||||
@ -1079,6 +1103,28 @@ class DMChannel(discord.abc.Messageable, Hashable):
|
|||||||
base.manage_messages = False
|
base.manage_messages = False
|
||||||
return base
|
return base
|
||||||
|
|
||||||
|
def get_partial_message(self, message_id):
|
||||||
|
"""Creates a :class:`PartialMessage` from the message ID.
|
||||||
|
|
||||||
|
This is useful if you want to work with a message and only have its ID without
|
||||||
|
doing an unnecessary API call.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
message_id: :class:`int`
|
||||||
|
The message ID to create a partial message for.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
:class:`PartialMessage`
|
||||||
|
The partial message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .message import PartialMessage
|
||||||
|
return PartialMessage(channel=self, id=message_id)
|
||||||
|
|
||||||
class GroupChannel(discord.abc.Messageable, Hashable):
|
class GroupChannel(discord.abc.Messageable, Hashable):
|
||||||
"""Represents a Discord group channel.
|
"""Represents a Discord group channel.
|
||||||
|
|
||||||
|
@ -134,10 +134,12 @@ class Emoji(_EmojiTag):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
""":class:`Asset`: Returns the asset of the emoji."""
|
""":class:`Asset`: Returns the asset of the emoji.
|
||||||
_format = 'gif' if self.animated else 'png'
|
|
||||||
url = "/emojis/{0.id}.{1}".format(self, _format)
|
This is equivalent to calling :meth:`url_as` with
|
||||||
return Asset(self._state, url)
|
the default parameters (i.e. png/gif detection).
|
||||||
|
"""
|
||||||
|
return self.url_as(format=None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def roles(self):
|
def roles(self):
|
||||||
@ -156,6 +158,39 @@ class Emoji(_EmojiTag):
|
|||||||
""":class:`Guild`: The guild this emoji belongs to."""
|
""":class:`Guild`: The guild this emoji belongs to."""
|
||||||
return self._state._get_guild(self.guild_id)
|
return self._state._get_guild(self.guild_id)
|
||||||
|
|
||||||
|
|
||||||
|
def url_as(self, *, format=None, static_format="png"):
|
||||||
|
"""Returns an :class:`Asset` for the emoji's url.
|
||||||
|
|
||||||
|
The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif'.
|
||||||
|
'gif' is only valid for animated emojis.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
format: Optional[:class:`str`]
|
||||||
|
The format to attempt to convert the emojis to.
|
||||||
|
If the format is ``None``, then it is automatically
|
||||||
|
detected as either 'gif' or static_format, depending on whether the
|
||||||
|
emoji is animated or not.
|
||||||
|
static_format: Optional[:class:`str`]
|
||||||
|
Format to attempt to convert only non-animated emoji's to.
|
||||||
|
Defaults to 'png'
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
InvalidArgument
|
||||||
|
Bad image format passed to ``format`` or ``static_format``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Asset`
|
||||||
|
The resulting CDN asset.
|
||||||
|
"""
|
||||||
|
return Asset._from_emoji(self._state, self, format=format, static_format=static_format)
|
||||||
|
|
||||||
|
|
||||||
def is_usable(self):
|
def is_usable(self):
|
||||||
""":class:`bool`: Whether the bot can use this emoji.
|
""":class:`bool`: Whether the bot can use this emoji.
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@ class MessageConverter(Converter):
|
|||||||
3. Lookup by message URL
|
3. Lookup by message URL
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
.. versionchanged:: 1.5
|
||||||
Raise :exc:`.ChannelNotFound`, `MessageNotFound` or `ChannelNotReadable` instead of generic :exc:`.BadArgument`
|
Raise :exc:`.ChannelNotFound`, :exc:`.MessageNotFound` or :exc:`.ChannelNotReadable` instead of generic :exc:`.BadArgument`
|
||||||
"""
|
"""
|
||||||
async def convert(self, ctx, argument):
|
async def convert(self, ctx, argument):
|
||||||
id_regex = re.compile(r'(?:(?P<channel_id>[0-9]{15,21})-)?(?P<message_id>[0-9]{15,21})$')
|
id_regex = re.compile(r'(?:(?P<channel_id>[0-9]{15,21})-)?(?P<message_id>[0-9]{15,21})$')
|
||||||
|
@ -144,7 +144,7 @@ class Command(_BaseCommand):
|
|||||||
The long help text for the command.
|
The long help text for the command.
|
||||||
brief: Optional[:class:`str`]
|
brief: Optional[:class:`str`]
|
||||||
The short help text for the command.
|
The short help text for the command.
|
||||||
usage: :class:`str`
|
usage: Optional[:class:`str`]
|
||||||
A replacement for arguments in the default help text.
|
A replacement for arguments in the default help text.
|
||||||
aliases: Union[List[:class:`str`], Tuple[:class:`str`]]
|
aliases: Union[List[:class:`str`], Tuple[:class:`str`]]
|
||||||
The list of aliases the command can be invoked under.
|
The list of aliases the command can be invoked under.
|
||||||
@ -778,17 +778,22 @@ class Command(_BaseCommand):
|
|||||||
if not await self.can_run(ctx):
|
if not await self.can_run(ctx):
|
||||||
raise CheckFailure('The check functions for command {0.qualified_name} failed.'.format(self))
|
raise CheckFailure('The check functions for command {0.qualified_name} failed.'.format(self))
|
||||||
|
|
||||||
if self.cooldown_after_parsing:
|
|
||||||
await self._parse_arguments(ctx)
|
|
||||||
self._prepare_cooldowns(ctx)
|
|
||||||
else:
|
|
||||||
self._prepare_cooldowns(ctx)
|
|
||||||
await self._parse_arguments(ctx)
|
|
||||||
|
|
||||||
if self._max_concurrency is not None:
|
if self._max_concurrency is not None:
|
||||||
await self._max_concurrency.acquire(ctx)
|
await self._max_concurrency.acquire(ctx)
|
||||||
|
|
||||||
await self.call_before_hooks(ctx)
|
try:
|
||||||
|
if self.cooldown_after_parsing:
|
||||||
|
await self._parse_arguments(ctx)
|
||||||
|
self._prepare_cooldowns(ctx)
|
||||||
|
else:
|
||||||
|
self._prepare_cooldowns(ctx)
|
||||||
|
await self._parse_arguments(ctx)
|
||||||
|
|
||||||
|
await self.call_before_hooks(ctx)
|
||||||
|
except:
|
||||||
|
if self._max_concurrency is not None:
|
||||||
|
await self._max_concurrency.release(ctx)
|
||||||
|
raise
|
||||||
|
|
||||||
def is_on_cooldown(self, ctx):
|
def is_on_cooldown(self, ctx):
|
||||||
"""Checks whether the command is currently on cooldown.
|
"""Checks whether the command is currently on cooldown.
|
||||||
@ -1140,6 +1145,7 @@ class GroupMixin:
|
|||||||
self.all_commands[command.name] = command
|
self.all_commands[command.name] = command
|
||||||
for alias in command.aliases:
|
for alias in command.aliases:
|
||||||
if alias in self.all_commands:
|
if alias in self.all_commands:
|
||||||
|
self.remove_command(command.name)
|
||||||
raise CommandRegistrationError(alias, alias_conflict=True)
|
raise CommandRegistrationError(alias, alias_conflict=True)
|
||||||
self.all_commands[alias] = command
|
self.all_commands[alias] = command
|
||||||
|
|
||||||
@ -1172,7 +1178,12 @@ class GroupMixin:
|
|||||||
|
|
||||||
# we're not removing the alias so let's delete the rest of them.
|
# we're not removing the alias so let's delete the rest of them.
|
||||||
for alias in command.aliases:
|
for alias in command.aliases:
|
||||||
self.all_commands.pop(alias, None)
|
cmd = self.all_commands.pop(alias, None)
|
||||||
|
# in the case of a CommandRegistrationError, an alias might conflict
|
||||||
|
# with an already existing command. If this is the case, we want to
|
||||||
|
# make sure the pre-existing command is not removed.
|
||||||
|
if cmd not in (None, command):
|
||||||
|
self.all_commands[alias] = cmd
|
||||||
return command
|
return command
|
||||||
|
|
||||||
def walk_commands(self):
|
def walk_commands(self):
|
||||||
|
@ -289,7 +289,7 @@ class ChannelNotFound(BadArgument):
|
|||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
channel: :class:`str`
|
argument: :class:`str`
|
||||||
The channel supplied by the caller that was not found
|
The channel supplied by the caller that was not found
|
||||||
"""
|
"""
|
||||||
def __init__(self, argument):
|
def __init__(self, argument):
|
||||||
|
@ -160,6 +160,26 @@ class Loop:
|
|||||||
return None
|
return None
|
||||||
return self._next_iteration
|
return self._next_iteration
|
||||||
|
|
||||||
|
async def __call__(self, *args, **kwargs):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Calls the internal callback that the task holds.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
\*args
|
||||||
|
The arguments to use.
|
||||||
|
\*\*kwargs
|
||||||
|
The keyword arguments to use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self._injected is not None:
|
||||||
|
args = (self._injected, *args)
|
||||||
|
|
||||||
|
return await self.coro(*args, **kwargs)
|
||||||
|
|
||||||
def start(self, *args, **kwargs):
|
def start(self, *args, **kwargs):
|
||||||
r"""Starts the internal task in the event loop.
|
r"""Starts the internal task in the event loop.
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ import os.path
|
|||||||
import io
|
import io
|
||||||
|
|
||||||
class File:
|
class File:
|
||||||
"""A parameter object used for :meth:`abc.Messageable.send`
|
r"""A parameter object used for :meth:`abc.Messageable.send`
|
||||||
for sending file objects.
|
for sending file objects.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
@ -636,11 +636,12 @@ class DiscordWebSocket:
|
|||||||
}
|
}
|
||||||
await self.send_as_json(payload)
|
await self.send_as_json(payload)
|
||||||
|
|
||||||
async def request_chunks(self, guild_id, query=None, *, limit, user_ids=None, nonce=None):
|
async def request_chunks(self, guild_id, query=None, *, limit, user_ids=None, presences=False, nonce=None):
|
||||||
payload = {
|
payload = {
|
||||||
'op': self.REQUEST_MEMBERS,
|
'op': self.REQUEST_MEMBERS,
|
||||||
'd': {
|
'd': {
|
||||||
'guild_id': guild_id,
|
'guild_id': guild_id,
|
||||||
|
'presences': presences,
|
||||||
'limit': limit
|
'limit': limit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,6 @@ from .mixins import Hashable
|
|||||||
from .user import User
|
from .user import User
|
||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
from .iterators import AuditLogIterator, MemberIterator
|
from .iterators import AuditLogIterator, MemberIterator
|
||||||
from .webhook import Webhook
|
|
||||||
from .widget import Widget
|
from .widget import Widget
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
from .flags import SystemChannelFlags
|
from .flags import SystemChannelFlags
|
||||||
@ -145,6 +144,8 @@ class Guild(Hashable):
|
|||||||
- ``ANIMATED_ICON``: Guild can upload an animated icon.
|
- ``ANIMATED_ICON``: Guild can upload an animated icon.
|
||||||
- ``PUBLIC_DISABLED``: Guild cannot be public.
|
- ``PUBLIC_DISABLED``: Guild cannot be public.
|
||||||
- ``WELCOME_SCREEN_ENABLED``: Guild has enabled the welcome screen
|
- ``WELCOME_SCREEN_ENABLED``: Guild has enabled the welcome screen
|
||||||
|
- ``MEMBER_VERIFICATION_GATE_ENABLED``: Guild has Membership Screening enabled.
|
||||||
|
- ``PREVIEW_ENABLED``: Guild can be viewed before being accepted via Membership Screening.
|
||||||
|
|
||||||
splash: Optional[:class:`str`]
|
splash: Optional[:class:`str`]
|
||||||
The guild's invite splash.
|
The guild's invite splash.
|
||||||
@ -485,7 +486,7 @@ class Guild(Hashable):
|
|||||||
@property
|
@property
|
||||||
def public_updates_channel(self):
|
def public_updates_channel(self):
|
||||||
"""Optional[:class:`TextChannel`]: Return's the guild's channel where admins and
|
"""Optional[:class:`TextChannel`]: Return's the guild's channel where admins and
|
||||||
moderators of the guilds receive notices from Discord. The guild must be a
|
moderators of the guilds receive notices from Discord. The guild must be a
|
||||||
Community guild.
|
Community guild.
|
||||||
|
|
||||||
If no channel is set, then this returns ``None``.
|
If no channel is set, then this returns ``None``.
|
||||||
@ -1100,6 +1101,9 @@ class Guild(Hashable):
|
|||||||
The new channel that is used for the system channel. Could be ``None`` for no system channel.
|
The new channel that is used for the system channel. Could be ``None`` for no system channel.
|
||||||
system_channel_flags: :class:`SystemChannelFlags`
|
system_channel_flags: :class:`SystemChannelFlags`
|
||||||
The new system channel settings to use with the new system channel.
|
The new system channel settings to use with the new system channel.
|
||||||
|
preferred_locale: :class:`str`
|
||||||
|
The new preferred locale for the guild. Used as the primary language in the guild.
|
||||||
|
If set, this must be an ISO 639 code, e.g. ``en-US`` or ``ja`` or ``zh-CN``.
|
||||||
rules_channel: Optional[:class:`TextChannel`]
|
rules_channel: Optional[:class:`TextChannel`]
|
||||||
The new channel that is used for rules. This is only available to
|
The new channel that is used for rules. This is only available to
|
||||||
guilds that contain ``PUBLIC`` in :attr:`Guild.features`. Could be ``None`` for no rules
|
guilds that contain ``PUBLIC`` in :attr:`Guild.features`. Could be ``None`` for no rules
|
||||||
@ -1535,6 +1539,7 @@ class Guild(Hashable):
|
|||||||
The webhooks for this guild.
|
The webhooks for this guild.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .webhook import Webhook
|
||||||
data = await self._state.http.guild_webhooks(self.id)
|
data = await self._state.http.guild_webhooks(self.id)
|
||||||
return [Webhook.from_state(d, state=self._state) for d in data]
|
return [Webhook.from_state(d, state=self._state) for d in data]
|
||||||
|
|
||||||
@ -1787,7 +1792,7 @@ class Guild(Hashable):
|
|||||||
The role name. Defaults to 'new role'.
|
The role name. Defaults to 'new role'.
|
||||||
permissions: :class:`Permissions`
|
permissions: :class:`Permissions`
|
||||||
The permissions to have. Defaults to no permissions.
|
The permissions to have. Defaults to no permissions.
|
||||||
colour: :class:`Colour`
|
colour: Union[:class:`Colour`, :class:`int`]
|
||||||
The colour for the role. Defaults to :meth:`Colour.default`.
|
The colour for the role. Defaults to :meth:`Colour.default`.
|
||||||
This is aliased to ``color`` as well.
|
This is aliased to ``color`` as well.
|
||||||
hoist: :class:`bool`
|
hoist: :class:`bool`
|
||||||
@ -1826,6 +1831,8 @@ class Guild(Hashable):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
colour = fields.get('color', Colour.default())
|
colour = fields.get('color', Colour.default())
|
||||||
finally:
|
finally:
|
||||||
|
if isinstance(colour, int):
|
||||||
|
colour = Colour(value=colour)
|
||||||
fields['color'] = colour.value
|
fields['color'] = colour.value
|
||||||
|
|
||||||
valid_keys = ('name', 'permissions', 'color', 'hoist', 'mentionable')
|
valid_keys = ('name', 'permissions', 'color', 'hoist', 'mentionable')
|
||||||
@ -2157,7 +2164,7 @@ class Guild(Hashable):
|
|||||||
|
|
||||||
return await self._state.chunk_guild(self, cache=cache)
|
return await self._state.chunk_guild(self, cache=cache)
|
||||||
|
|
||||||
async def query_members(self, query=None, *, limit=5, user_ids=None, cache=True):
|
async def query_members(self, query=None, *, limit=5, user_ids=None, presences=False, cache=True):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Request members that belong to this guild whose username starts with
|
Request members that belong to this guild whose username starts with
|
||||||
@ -2174,6 +2181,12 @@ class Guild(Hashable):
|
|||||||
limit: :class:`int`
|
limit: :class:`int`
|
||||||
The maximum number of members to send back. This must be
|
The maximum number of members to send back. This must be
|
||||||
a number between 5 and 100.
|
a number between 5 and 100.
|
||||||
|
presences: :class:`bool`
|
||||||
|
Whether to request for presences to be provided. This defaults
|
||||||
|
to ``False``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
cache: :class:`bool`
|
cache: :class:`bool`
|
||||||
Whether to cache the members internally. This makes operations
|
Whether to cache the members internally. This makes operations
|
||||||
such as :meth:`get_member` work for those that matched.
|
such as :meth:`get_member` work for those that matched.
|
||||||
@ -2189,6 +2202,8 @@ class Guild(Hashable):
|
|||||||
The query timed out waiting for the members.
|
The query timed out waiting for the members.
|
||||||
ValueError
|
ValueError
|
||||||
Invalid parameters were passed to the function
|
Invalid parameters were passed to the function
|
||||||
|
ClientException
|
||||||
|
The presences intent is not enabled.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
--------
|
--------
|
||||||
@ -2196,6 +2211,9 @@ class Guild(Hashable):
|
|||||||
The list of members that have matched the query.
|
The list of members that have matched the query.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if presences and not self._state._intents.presences:
|
||||||
|
raise ClientException('Intents.presences must be enabled to use this.')
|
||||||
|
|
||||||
if query is None:
|
if query is None:
|
||||||
if query == '':
|
if query == '':
|
||||||
raise ValueError('Cannot pass empty query string.')
|
raise ValueError('Cannot pass empty query string.')
|
||||||
@ -2207,7 +2225,7 @@ class Guild(Hashable):
|
|||||||
raise ValueError('Cannot pass both query and user_ids')
|
raise ValueError('Cannot pass both query and user_ids')
|
||||||
|
|
||||||
limit = min(100, limit or 5)
|
limit = min(100, limit or 5)
|
||||||
return await self._state.query_members(self, query=query, limit=limit, user_ids=user_ids, cache=cache)
|
return await self._state.query_members(self, query=query, limit=limit, user_ids=user_ids, presences=presences, cache=cache)
|
||||||
|
|
||||||
async def change_voice_state(self, *, channel, self_mute=False, self_deaf=False):
|
async def change_voice_state(self, *, channel, self_mute=False, self_deaf=False):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
@ -659,7 +659,7 @@ class HTTPClient:
|
|||||||
'system_channel_id', 'default_message_notifications',
|
'system_channel_id', 'default_message_notifications',
|
||||||
'description', 'explicit_content_filter', 'banner',
|
'description', 'explicit_content_filter', 'banner',
|
||||||
'system_channel_flags', 'rules_channel_id',
|
'system_channel_flags', 'rules_channel_id',
|
||||||
'public_updates_channel_id')
|
'public_updates_channel_id', 'preferred_locale',)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
k: v for k, v in fields.items() if k in valid_keys
|
k: v for k, v in fields.items() if k in valid_keys
|
||||||
|
@ -157,13 +157,17 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
The guild that the member belongs to.
|
The guild that the member belongs to.
|
||||||
nick: Optional[:class:`str`]
|
nick: Optional[:class:`str`]
|
||||||
The guild specific nickname of the user.
|
The guild specific nickname of the user.
|
||||||
|
pending: :class:`bool`
|
||||||
|
Whether the member is pending member verification.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
premium_since: Optional[:class:`datetime.datetime`]
|
premium_since: Optional[:class:`datetime.datetime`]
|
||||||
A datetime object that specifies the date and time in UTC when the member used their
|
A datetime object that specifies the date and time in UTC when the member used their
|
||||||
Nitro boost on the guild, if available. This could be ``None``.
|
Nitro boost on the guild, if available. This could be ``None``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('_roles', 'joined_at', 'premium_since', '_client_status',
|
__slots__ = ('_roles', 'joined_at', 'premium_since', '_client_status',
|
||||||
'activities', 'guild', 'nick', '_user', '_state')
|
'activities', 'guild', 'pending', 'nick', '_user', '_state')
|
||||||
|
|
||||||
def __init__(self, *, data, guild, state):
|
def __init__(self, *, data, guild, state):
|
||||||
self._state = state
|
self._state = state
|
||||||
@ -177,6 +181,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
}
|
}
|
||||||
self.activities = tuple(map(create_activity, data.get('activities', [])))
|
self.activities = tuple(map(create_activity, data.get('activities', [])))
|
||||||
self.nick = data.get('nick', None)
|
self.nick = data.get('nick', None)
|
||||||
|
self.pending = data.get('pending', False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self._user)
|
return str(self._user)
|
||||||
@ -208,6 +213,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
self.premium_since = utils.parse_time(data.get('premium_since'))
|
self.premium_since = utils.parse_time(data.get('premium_since'))
|
||||||
self._update_roles(data)
|
self._update_roles(data)
|
||||||
self.nick = data.get('nick', None)
|
self.nick = data.get('nick', None)
|
||||||
|
self.pending = data.get('pending', False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _try_upgrade(cls, *, data, guild, state):
|
def _try_upgrade(cls, *, data, guild, state):
|
||||||
@ -241,6 +247,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
self._client_status = member._client_status.copy()
|
self._client_status = member._client_status.copy()
|
||||||
self.guild = member.guild
|
self.guild = member.guild
|
||||||
self.nick = member.nick
|
self.nick = member.nick
|
||||||
|
self.pending = member.pending
|
||||||
self.activities = member.activities
|
self.activities = member.activities
|
||||||
self._state = member._state
|
self._state = member._state
|
||||||
|
|
||||||
@ -264,6 +271,11 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.pending = data['pending']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
self.premium_since = utils.parse_time(data.get('premium_since'))
|
self.premium_since = utils.parse_time(data.get('premium_since'))
|
||||||
self._update_roles(data)
|
self._update_roles(data)
|
||||||
|
|
||||||
@ -634,7 +646,8 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
Gives the member a number of :class:`Role`\s.
|
Gives the member a number of :class:`Role`\s.
|
||||||
|
|
||||||
You must have the :attr:`~Permissions.manage_roles` permission to
|
You must have the :attr:`~Permissions.manage_roles` permission to
|
||||||
use this.
|
use this, and the added :class:`Role`\s must appear lower in the list
|
||||||
|
of roles than the highest role of the member.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
@ -672,7 +685,8 @@ class Member(discord.abc.Messageable, _BaseUser):
|
|||||||
Removes :class:`Role`\s from this member.
|
Removes :class:`Role`\s from this member.
|
||||||
|
|
||||||
You must have the :attr:`~Permissions.manage_roles` permission to
|
You must have the :attr:`~Permissions.manage_roles` permission to
|
||||||
use this.
|
use this, and the removed :class:`Role`\s must appear lower in the list
|
||||||
|
of roles than the highest role of the member.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
|
@ -34,7 +34,7 @@ from .reaction import Reaction
|
|||||||
from .emoji import Emoji
|
from .emoji import Emoji
|
||||||
from .partial_emoji import PartialEmoji
|
from .partial_emoji import PartialEmoji
|
||||||
from .calls import CallMessage
|
from .calls import CallMessage
|
||||||
from .enums import MessageType, try_enum
|
from .enums import MessageType, ChannelType, try_enum
|
||||||
from .errors import InvalidArgument, ClientException, HTTPException
|
from .errors import InvalidArgument, ClientException, HTTPException
|
||||||
from .embeds import Embed
|
from .embeds import Embed
|
||||||
from .member import Member
|
from .member import Member
|
||||||
@ -48,10 +48,26 @@ from .sticker import Sticker
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'Attachment',
|
'Attachment',
|
||||||
'Message',
|
'Message',
|
||||||
|
'PartialMessage',
|
||||||
'MessageReference',
|
'MessageReference',
|
||||||
'DeletedReferencedMessage',
|
'DeletedReferencedMessage',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def convert_emoji_reaction(emoji):
|
||||||
|
if isinstance(emoji, Reaction):
|
||||||
|
emoji = emoji.emoji
|
||||||
|
|
||||||
|
if isinstance(emoji, Emoji):
|
||||||
|
return '%s:%s' % (emoji.name, emoji.id)
|
||||||
|
if isinstance(emoji, PartialEmoji):
|
||||||
|
return emoji._as_reaction()
|
||||||
|
if isinstance(emoji, str):
|
||||||
|
# Reactions can be in :name:id format, but not <:name:id>.
|
||||||
|
# No existing emojis have <> in them, so this should be okay.
|
||||||
|
return emoji.strip('<>')
|
||||||
|
|
||||||
|
raise InvalidArgument('emoji argument must be str, Emoji, or Reaction not {.__class__.__name__}.'.format(emoji))
|
||||||
|
|
||||||
class Attachment:
|
class Attachment:
|
||||||
"""Represents an attachment from Discord.
|
"""Represents an attachment from Discord.
|
||||||
|
|
||||||
@ -268,7 +284,7 @@ class MessageReference:
|
|||||||
The guild id of the message referenced.
|
The guild id of the message referenced.
|
||||||
resolved: Optional[Union[:class:`Message`, :class:`DeletedReferencedMessage`]]
|
resolved: Optional[Union[:class:`Message`, :class:`DeletedReferencedMessage`]]
|
||||||
The message that this reference resolved to. If this is ``None``
|
The message that this reference resolved to. If this is ``None``
|
||||||
then the original message was not fetched either due to the discord API
|
then the original message was not fetched either due to the Discord API
|
||||||
not attempting to resolve it or it not being available at the time of creation.
|
not attempting to resolve it or it not being available at the time of creation.
|
||||||
If the message was resolved at a prior point but has since been deleted then
|
If the message was resolved at a prior point but has since been deleted then
|
||||||
this will be of type :class:`DeletedReferencedMessage`.
|
this will be of type :class:`DeletedReferencedMessage`.
|
||||||
@ -372,7 +388,19 @@ def flatten_handlers(cls):
|
|||||||
class Message(Hashable):
|
class Message(Hashable):
|
||||||
r"""Represents a message from Discord.
|
r"""Represents a message from Discord.
|
||||||
|
|
||||||
There should be no need to create one of these manually.
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if two messages are equal.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if two messages are not equal.
|
||||||
|
|
||||||
|
.. describe:: hash(x)
|
||||||
|
|
||||||
|
Returns the message's hash.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
@ -423,7 +451,7 @@ class Message(Hashable):
|
|||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
The order of the mentions list is not in any particular order so you should
|
The order of the mentions list is not in any particular order so you should
|
||||||
not rely on it. This is a discord limitation, not one with the library.
|
not rely on it. This is a Discord limitation, not one with the library.
|
||||||
channel_mentions: List[:class:`abc.GuildChannel`]
|
channel_mentions: List[:class:`abc.GuildChannel`]
|
||||||
A list of :class:`abc.GuildChannel` that were mentioned. If the message is in a private message
|
A list of :class:`abc.GuildChannel` that were mentioned. If the message is in a private message
|
||||||
then the list is always empty.
|
then the list is always empty.
|
||||||
@ -996,14 +1024,6 @@ class Message(Hashable):
|
|||||||
are used instead.
|
are used instead.
|
||||||
|
|
||||||
.. versionadded:: 1.4
|
.. versionadded:: 1.4
|
||||||
.. versionchanged:: 1.6
|
|
||||||
:attr:`~discord.Client.allowed_mentions` serves as defaults unconditionally.
|
|
||||||
|
|
||||||
mention_author: Optional[:class:`bool`]
|
|
||||||
Overrides the :attr:`~discord.AllowedMentions.replied_user` attribute
|
|
||||||
of ``allowed_mentions``.
|
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
@ -1041,24 +1061,17 @@ class Message(Hashable):
|
|||||||
|
|
||||||
delete_after = fields.pop('delete_after', None)
|
delete_after = fields.pop('delete_after', None)
|
||||||
|
|
||||||
mention_author = fields.pop('mention_author', None)
|
try:
|
||||||
allowed_mentions = fields.pop('allowed_mentions', None)
|
allowed_mentions = fields.pop('allowed_mentions')
|
||||||
if allowed_mentions is not None:
|
except KeyError:
|
||||||
if self._state.allowed_mentions is not None:
|
pass
|
||||||
allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions)
|
else:
|
||||||
allowed_mentions = allowed_mentions.to_dict()
|
if allowed_mentions is not None:
|
||||||
if mention_author is not None:
|
if self._state.allowed_mentions is not None:
|
||||||
allowed_mentions['replied_user'] = mention_author
|
allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions).to_dict()
|
||||||
fields['allowed_mentions'] = allowed_mentions
|
else:
|
||||||
elif mention_author is not None:
|
allowed_mentions = allowed_mentions.to_dict()
|
||||||
if self._state.allowed_mentions is not None:
|
fields['allowed_mentions'] = allowed_mentions
|
||||||
allowed_mentions = self._state.allowed_mentions.to_dict()
|
|
||||||
allowed_mentions['replied_user'] = mention_author
|
|
||||||
else:
|
|
||||||
allowed_mentions = {'replied_user': mention_author}
|
|
||||||
fields['allowed_mentions'] = allowed_mentions
|
|
||||||
elif self._state.allowed_mentions is not None:
|
|
||||||
fields['allowed_mentions'] = self._state.allowed_mentions.to_dict()
|
|
||||||
|
|
||||||
if fields:
|
if fields:
|
||||||
data = await self._state.http.edit_message(self.channel.id, self.id, **fields)
|
data = await self._state.http.edit_message(self.channel.id, self.id, **fields)
|
||||||
@ -1170,7 +1183,7 @@ class Message(Hashable):
|
|||||||
The emoji parameter is invalid.
|
The emoji parameter is invalid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emoji = self._emoji_reaction(emoji)
|
emoji = convert_emoji_reaction(emoji)
|
||||||
await self._state.http.add_reaction(self.channel.id, self.id, emoji)
|
await self._state.http.add_reaction(self.channel.id, self.id, emoji)
|
||||||
|
|
||||||
async def remove_reaction(self, emoji, member):
|
async def remove_reaction(self, emoji, member):
|
||||||
@ -1205,7 +1218,7 @@ class Message(Hashable):
|
|||||||
The emoji parameter is invalid.
|
The emoji parameter is invalid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emoji = self._emoji_reaction(emoji)
|
emoji = convert_emoji_reaction(emoji)
|
||||||
|
|
||||||
if member.id == self._state.self_id:
|
if member.id == self._state.self_id:
|
||||||
await self._state.http.remove_own_reaction(self.channel.id, self.id, emoji)
|
await self._state.http.remove_own_reaction(self.channel.id, self.id, emoji)
|
||||||
@ -1240,25 +1253,9 @@ class Message(Hashable):
|
|||||||
The emoji parameter is invalid.
|
The emoji parameter is invalid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
emoji = self._emoji_reaction(emoji)
|
emoji = convert_emoji_reaction(emoji)
|
||||||
await self._state.http.clear_single_reaction(self.channel.id, self.id, emoji)
|
await self._state.http.clear_single_reaction(self.channel.id, self.id, emoji)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _emoji_reaction(emoji):
|
|
||||||
if isinstance(emoji, Reaction):
|
|
||||||
emoji = emoji.emoji
|
|
||||||
|
|
||||||
if isinstance(emoji, Emoji):
|
|
||||||
return '%s:%s' % (emoji.name, emoji.id)
|
|
||||||
if isinstance(emoji, PartialEmoji):
|
|
||||||
return emoji._as_reaction()
|
|
||||||
if isinstance(emoji, str):
|
|
||||||
# Reactions can be in :name:id format, but not <:name:id>.
|
|
||||||
# No existing emojis have <> in them, so this should be okay.
|
|
||||||
return emoji.strip('<>')
|
|
||||||
|
|
||||||
raise InvalidArgument('emoji argument must be str, Emoji, or Reaction not {.__class__.__name__}.'.format(emoji))
|
|
||||||
|
|
||||||
async def clear_reactions(self):
|
async def clear_reactions(self):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -1301,7 +1298,7 @@ class Message(Hashable):
|
|||||||
A shortcut method to :meth:`abc.Messageable.send` to reply to the
|
A shortcut method to :meth:`abc.Messageable.send` to reply to the
|
||||||
:class:`Message`.
|
:class:`Message`.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
--------
|
--------
|
||||||
@ -1344,3 +1341,116 @@ class Message(Hashable):
|
|||||||
data['guild_id'] = self.guild.id
|
data['guild_id'] = self.guild.id
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def implement_partial_methods(cls):
|
||||||
|
msg = Message
|
||||||
|
for name in cls._exported_names:
|
||||||
|
func = getattr(msg, name)
|
||||||
|
setattr(cls, name, func)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
@implement_partial_methods
|
||||||
|
class PartialMessage(Hashable):
|
||||||
|
"""Represents a partial message to aid with working messages when only
|
||||||
|
a message and channel ID are present.
|
||||||
|
|
||||||
|
There are two ways to construct this class. The first one is through
|
||||||
|
the constructor itself, and the second is via
|
||||||
|
:meth:`TextChannel.get_partial_message` or :meth:`DMChannel.get_partial_message`.
|
||||||
|
|
||||||
|
Note that this class is trimmed down and has no rich attributes.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if two partial messages are equal.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if two partial messages are not equal.
|
||||||
|
|
||||||
|
.. describe:: hash(x)
|
||||||
|
|
||||||
|
Returns the partial message's hash.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
-----------
|
||||||
|
channel: Union[:class:`TextChannel`, :class:`DMChannel`]
|
||||||
|
The channel associated with this partial message.
|
||||||
|
id: :class:`int`
|
||||||
|
The message ID.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('channel', 'id', '_cs_guild', '_state')
|
||||||
|
|
||||||
|
_exported_names = (
|
||||||
|
'jump_url',
|
||||||
|
'delete',
|
||||||
|
'edit',
|
||||||
|
'publish',
|
||||||
|
'pin',
|
||||||
|
'unpin',
|
||||||
|
'add_reaction',
|
||||||
|
'remove_reaction',
|
||||||
|
'clear_reaction',
|
||||||
|
'clear_reactions',
|
||||||
|
'reply',
|
||||||
|
'to_reference',
|
||||||
|
'to_message_reference_dict',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *, channel, id):
|
||||||
|
if channel.type not in (ChannelType.text, ChannelType.news, ChannelType.private):
|
||||||
|
raise TypeError('Expected TextChannel or DMChannel not %r' % type(channel))
|
||||||
|
|
||||||
|
self.channel = channel
|
||||||
|
self._state = channel._state
|
||||||
|
self.id = id
|
||||||
|
|
||||||
|
def _update(self, data):
|
||||||
|
# This is used for duck typing purposes.
|
||||||
|
# Just do nothing with the data.
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Also needed for duck typing purposes
|
||||||
|
# n.b. not exposed
|
||||||
|
pinned = property(None, lambda x, y: ...)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<PartialMessage id={0.id} channel={0.channel!r}>'.format(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_at(self):
|
||||||
|
""":class:`datetime.datetime`: The partial message's creation time in UTC."""
|
||||||
|
return utils.snowflake_time(self.id)
|
||||||
|
|
||||||
|
@utils.cached_slot_property('_cs_guild')
|
||||||
|
def guild(self):
|
||||||
|
"""Optional[:class:`Guild`]: The guild that the partial message belongs to, if applicable."""
|
||||||
|
return getattr(self.channel, 'guild', None)
|
||||||
|
|
||||||
|
async def fetch(self):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Fetches the partial message to a full :class:`Message`.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
--------
|
||||||
|
NotFound
|
||||||
|
The message was not found.
|
||||||
|
Forbidden
|
||||||
|
You do not have the permissions required to get a message.
|
||||||
|
HTTPException
|
||||||
|
Retrieving the message failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`Message`
|
||||||
|
The full message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = await self._state.http.get_message(self.channel.id, self.id)
|
||||||
|
return self._state.create_message(channel=self.channel, data=data)
|
||||||
|
214
discord/opus.py
214
discord/opus.py
@ -28,13 +28,16 @@ import array
|
|||||||
import ctypes
|
import ctypes
|
||||||
import ctypes.util
|
import ctypes.util
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import os.path
|
import os.path
|
||||||
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .errors import DiscordException
|
from .errors import DiscordException
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
c_int_ptr = ctypes.POINTER(ctypes.c_int)
|
|
||||||
|
c_int_ptr = ctypes.POINTER(ctypes.c_int)
|
||||||
c_int16_ptr = ctypes.POINTER(ctypes.c_int16)
|
c_int16_ptr = ctypes.POINTER(ctypes.c_int16)
|
||||||
c_float_ptr = ctypes.POINTER(ctypes.c_float)
|
c_float_ptr = ctypes.POINTER(ctypes.c_float)
|
||||||
|
|
||||||
@ -43,17 +46,55 @@ _lib = None
|
|||||||
class EncoderStruct(ctypes.Structure):
|
class EncoderStruct(ctypes.Structure):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class DecoderStruct(ctypes.Structure):
|
||||||
|
pass
|
||||||
|
|
||||||
EncoderStructPtr = ctypes.POINTER(EncoderStruct)
|
EncoderStructPtr = ctypes.POINTER(EncoderStruct)
|
||||||
|
DecoderStructPtr = ctypes.POINTER(DecoderStruct)
|
||||||
|
|
||||||
|
## Some constants from opus_defines.h
|
||||||
|
# Error codes
|
||||||
|
OK = 0
|
||||||
|
BAD_ARG = -1
|
||||||
|
|
||||||
|
# Encoder CTLs
|
||||||
|
APPLICATION_AUDIO = 2049
|
||||||
|
APPLICATION_VOIP = 2048
|
||||||
|
APPLICATION_LOWDELAY = 2051
|
||||||
|
|
||||||
|
CTL_SET_BITRATE = 4002
|
||||||
|
CTL_SET_BANDWIDTH = 4008
|
||||||
|
CTL_SET_FEC = 4012
|
||||||
|
CTL_SET_PLP = 4014
|
||||||
|
CTL_SET_SIGNAL = 4024
|
||||||
|
|
||||||
|
# Decoder CTLs
|
||||||
|
CTL_SET_GAIN = 4034
|
||||||
|
CTL_LAST_PACKET_DURATION = 4039
|
||||||
|
|
||||||
|
band_ctl = {
|
||||||
|
'narrow': 1101,
|
||||||
|
'medium': 1102,
|
||||||
|
'wide': 1103,
|
||||||
|
'superwide': 1104,
|
||||||
|
'full': 1105,
|
||||||
|
}
|
||||||
|
|
||||||
|
signal_ctl = {
|
||||||
|
'auto': -1000,
|
||||||
|
'voice': 3001,
|
||||||
|
'music': 3002,
|
||||||
|
}
|
||||||
|
|
||||||
def _err_lt(result, func, args):
|
def _err_lt(result, func, args):
|
||||||
if result < 0:
|
if result < OK:
|
||||||
log.info('error has happened in %s', func.__name__)
|
log.info('error has happened in %s', func.__name__)
|
||||||
raise OpusError(result)
|
raise OpusError(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _err_ne(result, func, args):
|
def _err_ne(result, func, args):
|
||||||
ret = args[-1]._obj
|
ret = args[-1]._obj
|
||||||
if ret.value != 0:
|
if ret.value != OK:
|
||||||
log.info('error has happened in %s', func.__name__)
|
log.info('error has happened in %s', func.__name__)
|
||||||
raise OpusError(ret.value)
|
raise OpusError(ret.value)
|
||||||
return result
|
return result
|
||||||
@ -64,18 +105,53 @@ def _err_ne(result, func, args):
|
|||||||
# The third is the result type.
|
# The third is the result type.
|
||||||
# The fourth is the error handler.
|
# The fourth is the error handler.
|
||||||
exported_functions = [
|
exported_functions = [
|
||||||
|
# Generic
|
||||||
|
('opus_get_version_string',
|
||||||
|
None, ctypes.c_char_p, None),
|
||||||
('opus_strerror',
|
('opus_strerror',
|
||||||
[ctypes.c_int], ctypes.c_char_p, None),
|
[ctypes.c_int], ctypes.c_char_p, None),
|
||||||
|
|
||||||
|
# Encoder functions
|
||||||
('opus_encoder_get_size',
|
('opus_encoder_get_size',
|
||||||
[ctypes.c_int], ctypes.c_int, None),
|
[ctypes.c_int], ctypes.c_int, None),
|
||||||
('opus_encoder_create',
|
('opus_encoder_create',
|
||||||
[ctypes.c_int, ctypes.c_int, ctypes.c_int, c_int_ptr], EncoderStructPtr, _err_ne),
|
[ctypes.c_int, ctypes.c_int, ctypes.c_int, c_int_ptr], EncoderStructPtr, _err_ne),
|
||||||
('opus_encode',
|
('opus_encode',
|
||||||
[EncoderStructPtr, c_int16_ptr, ctypes.c_int, ctypes.c_char_p, ctypes.c_int32], ctypes.c_int32, _err_lt),
|
[EncoderStructPtr, c_int16_ptr, ctypes.c_int, ctypes.c_char_p, ctypes.c_int32], ctypes.c_int32, _err_lt),
|
||||||
|
('opus_encode_float',
|
||||||
|
[EncoderStructPtr, c_float_ptr, ctypes.c_int, ctypes.c_char_p, ctypes.c_int32], ctypes.c_int32, _err_lt),
|
||||||
('opus_encoder_ctl',
|
('opus_encoder_ctl',
|
||||||
None, ctypes.c_int32, _err_lt),
|
None, ctypes.c_int32, _err_lt),
|
||||||
('opus_encoder_destroy',
|
('opus_encoder_destroy',
|
||||||
[EncoderStructPtr], None, None),
|
[EncoderStructPtr], None, None),
|
||||||
|
|
||||||
|
# Decoder functions
|
||||||
|
('opus_decoder_get_size',
|
||||||
|
[ctypes.c_int], ctypes.c_int, None),
|
||||||
|
('opus_decoder_create',
|
||||||
|
[ctypes.c_int, ctypes.c_int, c_int_ptr], DecoderStructPtr, _err_ne),
|
||||||
|
('opus_decode',
|
||||||
|
[DecoderStructPtr, ctypes.c_char_p, ctypes.c_int32, c_int16_ptr, ctypes.c_int, ctypes.c_int],
|
||||||
|
ctypes.c_int, _err_lt),
|
||||||
|
('opus_decode_float',
|
||||||
|
[DecoderStructPtr, ctypes.c_char_p, ctypes.c_int32, c_float_ptr, ctypes.c_int, ctypes.c_int],
|
||||||
|
ctypes.c_int, _err_lt),
|
||||||
|
('opus_decoder_ctl',
|
||||||
|
None, ctypes.c_int32, _err_lt),
|
||||||
|
('opus_decoder_destroy',
|
||||||
|
[DecoderStructPtr], None, None),
|
||||||
|
('opus_decoder_get_nb_samples',
|
||||||
|
[DecoderStructPtr, ctypes.c_char_p, ctypes.c_int32], ctypes.c_int, _err_lt),
|
||||||
|
|
||||||
|
# Packet functions
|
||||||
|
('opus_packet_get_bandwidth',
|
||||||
|
[ctypes.c_char_p], ctypes.c_int, _err_lt),
|
||||||
|
('opus_packet_get_nb_channels',
|
||||||
|
[ctypes.c_char_p], ctypes.c_int, _err_lt),
|
||||||
|
('opus_packet_get_nb_frames',
|
||||||
|
[ctypes.c_char_p, ctypes.c_int], ctypes.c_int, _err_lt),
|
||||||
|
('opus_packet_get_samples_per_frame',
|
||||||
|
[ctypes.c_char_p, ctypes.c_int], ctypes.c_int, _err_lt),
|
||||||
]
|
]
|
||||||
|
|
||||||
def libopus_loader(name):
|
def libopus_loader(name):
|
||||||
@ -107,8 +183,9 @@ def _load_default():
|
|||||||
try:
|
try:
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
_basedir = os.path.dirname(os.path.abspath(__file__))
|
_basedir = os.path.dirname(os.path.abspath(__file__))
|
||||||
_bitness = 'x64' if sys.maxsize > 2**32 else 'x86'
|
_bitness = struct.calcsize('P') * 8
|
||||||
_filename = os.path.join(_basedir, 'bin', 'libopus-0.{}.dll'.format(_bitness))
|
_target = 'x64' if _bitness > 32 else 'x86'
|
||||||
|
_filename = os.path.join(_basedir, 'bin', 'libopus-0.{}.dll'.format(_target))
|
||||||
_lib = libopus_loader(_filename)
|
_lib = libopus_loader(_filename)
|
||||||
else:
|
else:
|
||||||
_lib = libopus_loader(ctypes.util.find_library('opus'))
|
_lib = libopus_loader(ctypes.util.find_library('opus'))
|
||||||
@ -188,48 +265,30 @@ class OpusNotLoaded(DiscordException):
|
|||||||
"""An exception that is thrown for when libopus is not loaded."""
|
"""An exception that is thrown for when libopus is not loaded."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class _OpusStruct:
|
||||||
# Some constants...
|
|
||||||
OK = 0
|
|
||||||
APPLICATION_AUDIO = 2049
|
|
||||||
APPLICATION_VOIP = 2048
|
|
||||||
APPLICATION_LOWDELAY = 2051
|
|
||||||
CTL_SET_BITRATE = 4002
|
|
||||||
CTL_SET_BANDWIDTH = 4008
|
|
||||||
CTL_SET_FEC = 4012
|
|
||||||
CTL_SET_PLP = 4014
|
|
||||||
CTL_SET_SIGNAL = 4024
|
|
||||||
|
|
||||||
band_ctl = {
|
|
||||||
'narrow': 1101,
|
|
||||||
'medium': 1102,
|
|
||||||
'wide': 1103,
|
|
||||||
'superwide': 1104,
|
|
||||||
'full': 1105,
|
|
||||||
}
|
|
||||||
|
|
||||||
signal_ctl = {
|
|
||||||
'auto': -1000,
|
|
||||||
'voice': 3001,
|
|
||||||
'music': 3002,
|
|
||||||
}
|
|
||||||
|
|
||||||
class Encoder:
|
|
||||||
SAMPLING_RATE = 48000
|
SAMPLING_RATE = 48000
|
||||||
CHANNELS = 2
|
CHANNELS = 2
|
||||||
FRAME_LENGTH = 20
|
FRAME_LENGTH = 20 # in milliseconds
|
||||||
SAMPLE_SIZE = 4 # (bit_rate / 8) * CHANNELS (bit_rate == 16)
|
SAMPLE_SIZE = struct.calcsize('h') * CHANNELS
|
||||||
SAMPLES_PER_FRAME = int(SAMPLING_RATE / 1000 * FRAME_LENGTH)
|
SAMPLES_PER_FRAME = int(SAMPLING_RATE / 1000 * FRAME_LENGTH)
|
||||||
|
|
||||||
FRAME_SIZE = SAMPLES_PER_FRAME * SAMPLE_SIZE
|
FRAME_SIZE = SAMPLES_PER_FRAME * SAMPLE_SIZE
|
||||||
|
|
||||||
def __init__(self, application=APPLICATION_AUDIO):
|
@staticmethod
|
||||||
self.application = application
|
def get_opus_version() -> str:
|
||||||
|
|
||||||
if not is_loaded():
|
if not is_loaded():
|
||||||
if not _load_default():
|
if not _load_default():
|
||||||
raise OpusNotLoaded()
|
raise OpusNotLoaded()
|
||||||
|
|
||||||
|
return _lib.opus_get_version_string().decode('utf-8')
|
||||||
|
|
||||||
|
class Encoder(_OpusStruct):
|
||||||
|
def __init__(self, application=APPLICATION_AUDIO):
|
||||||
|
if not is_loaded():
|
||||||
|
if not _load_default():
|
||||||
|
raise OpusNotLoaded()
|
||||||
|
|
||||||
|
self.application = application
|
||||||
self._state = self._create_state()
|
self._state = self._create_state()
|
||||||
self.set_bitrate(128)
|
self.set_bitrate(128)
|
||||||
self.set_fec(True)
|
self.set_fec(True)
|
||||||
@ -280,3 +339,84 @@ class Encoder:
|
|||||||
ret = _lib.opus_encode(self._state, pcm, frame_size, data, max_data_bytes)
|
ret = _lib.opus_encode(self._state, pcm, frame_size, data, max_data_bytes)
|
||||||
|
|
||||||
return array.array('b', data[:ret]).tobytes()
|
return array.array('b', data[:ret]).tobytes()
|
||||||
|
|
||||||
|
class Decoder(_OpusStruct):
|
||||||
|
def __init__(self):
|
||||||
|
if not is_loaded():
|
||||||
|
if not _load_default():
|
||||||
|
raise OpusNotLoaded()
|
||||||
|
|
||||||
|
self._state = self._create_state()
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if hasattr(self, '_state'):
|
||||||
|
_lib.opus_decoder_destroy(self._state)
|
||||||
|
self._state = None
|
||||||
|
|
||||||
|
def _create_state(self):
|
||||||
|
ret = ctypes.c_int()
|
||||||
|
return _lib.opus_decoder_create(self.SAMPLING_RATE, self.CHANNELS, ctypes.byref(ret))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def packet_get_nb_frames(data):
|
||||||
|
"""Gets the number of frames in an Opus packet"""
|
||||||
|
return _lib.opus_packet_get_nb_frames(data, len(data))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def packet_get_nb_channels(data):
|
||||||
|
"""Gets the number of channels in an Opus packet"""
|
||||||
|
return _lib.opus_packet_get_nb_channels(data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def packet_get_samples_per_frame(cls, data):
|
||||||
|
"""Gets the number of samples per frame from an Opus packet"""
|
||||||
|
return _lib.opus_packet_get_samples_per_frame(data, cls.SAMPLING_RATE)
|
||||||
|
|
||||||
|
def _set_gain(self, adjustment):
|
||||||
|
"""Configures decoder gain adjustment.
|
||||||
|
|
||||||
|
Scales the decoded output by a factor specified in Q8 dB units.
|
||||||
|
This has a maximum range of -32768 to 32767 inclusive, and returns
|
||||||
|
OPUS_BAD_ARG (-1) otherwise. The default is zero indicating no adjustment.
|
||||||
|
This setting survives decoder reset (irrelevant for now).
|
||||||
|
gain = 10**x/(20.0*256)
|
||||||
|
(from opus_defines.h)
|
||||||
|
"""
|
||||||
|
return _lib.opus_decoder_ctl(self._state, CTL_SET_GAIN, adjustment)
|
||||||
|
|
||||||
|
def set_gain(self, dB):
|
||||||
|
"""Sets the decoder gain in dB, from -128 to 128."""
|
||||||
|
|
||||||
|
dB_Q8 = max(-32768, min(32767, round(dB * 256))) # dB * 2^n where n is 8 (Q8)
|
||||||
|
return self._set_gain(dB_Q8)
|
||||||
|
|
||||||
|
def set_volume(self, mult):
|
||||||
|
"""Sets the output volume as a float percent, i.e. 0.5 for 50%, 1.75 for 175%, etc."""
|
||||||
|
return self.set_gain(20 * math.log10(mult)) # amplitude ratio
|
||||||
|
|
||||||
|
def _get_last_packet_duration(self):
|
||||||
|
"""Gets the duration (in samples) of the last packet successfully decoded or concealed."""
|
||||||
|
|
||||||
|
ret = ctypes.c_int32()
|
||||||
|
_lib.opus_decoder_ctl(self._state, CTL_LAST_PACKET_DURATION, ctypes.byref(ret))
|
||||||
|
return ret.value
|
||||||
|
|
||||||
|
def decode(self, data, *, fec=False):
|
||||||
|
if data is None and fec:
|
||||||
|
raise OpusError("Invalid arguments: FEC cannot be used with null data")
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
frame_size = self._get_last_packet_duration() or self.SAMPLES_PER_FRAME
|
||||||
|
channel_count = self.CHANNELS
|
||||||
|
else:
|
||||||
|
frames = self.packet_get_nb_frames(data)
|
||||||
|
channel_count = self.packet_get_nb_channels(data)
|
||||||
|
samples_per_frame = self.packet_get_samples_per_frame(data)
|
||||||
|
frame_size = frames * samples_per_frame
|
||||||
|
|
||||||
|
pcm = (ctypes.c_int16 * (frame_size * channel_count))()
|
||||||
|
pcm_ptr = ctypes.cast(pcm, c_int16_ptr)
|
||||||
|
|
||||||
|
ret = _lib.opus_decode(self._state, data, len(data) if data else 0, pcm_ptr, frame_size, fec)
|
||||||
|
|
||||||
|
return array.array('h', pcm[:ret * channel_count]).tobytes()
|
||||||
|
@ -136,9 +136,20 @@ class PartialEmoji(_EmojiTag):
|
|||||||
return self.name
|
return self.name
|
||||||
return '%s:%s' % (self.name, self.id)
|
return '%s:%s' % (self.name, self.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_at(self):
|
||||||
|
"""Optional[:class:`datetime.datetime`]: Returns the emoji's creation time in UTC, or None if Unicode emoji.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
if self.is_unicode_emoji():
|
||||||
|
return None
|
||||||
|
|
||||||
|
return utils.snowflake_time(self.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
""":class:`Asset`:Returns an asset of the emoji, if it is custom."""
|
""":class:`Asset`: Returns an asset of the emoji, if it is custom."""
|
||||||
if self.is_unicode_emoji():
|
if self.is_unicode_emoji():
|
||||||
return Asset(self._state)
|
return Asset(self._state)
|
||||||
|
|
||||||
|
@ -382,11 +382,11 @@ class ConnectionState:
|
|||||||
|
|
||||||
return channel or Object(id=channel_id), guild
|
return channel or Object(id=channel_id), guild
|
||||||
|
|
||||||
async def chunker(self, guild_id, query='', limit=0, *, nonce=None):
|
async def chunker(self, guild_id, query='', limit=0, presences=False, *, nonce=None):
|
||||||
ws = self._get_websocket(guild_id) # This is ignored upstream
|
ws = self._get_websocket(guild_id) # This is ignored upstream
|
||||||
await ws.request_chunks(guild_id, query=query, limit=limit, nonce=nonce)
|
await ws.request_chunks(guild_id, query=query, limit=limit, presences=presences, nonce=nonce)
|
||||||
|
|
||||||
async def query_members(self, guild, query, limit, user_ids, cache):
|
async def query_members(self, guild, query, limit, user_ids, cache, presences):
|
||||||
guild_id = guild.id
|
guild_id = guild.id
|
||||||
ws = self._get_websocket(guild_id)
|
ws = self._get_websocket(guild_id)
|
||||||
if ws is None:
|
if ws is None:
|
||||||
@ -397,7 +397,7 @@ class ConnectionState:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# start the query operation
|
# start the query operation
|
||||||
await ws.request_chunks(guild_id, query=query, limit=limit, user_ids=user_ids, nonce=request.nonce)
|
await ws.request_chunks(guild_id, query=query, limit=limit, user_ids=user_ids, presences=presences, nonce=request.nonce)
|
||||||
return await asyncio.wait_for(request.wait(), timeout=30.0)
|
return await asyncio.wait_for(request.wait(), timeout=30.0)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
log.warning('Timed out waiting for chunks with query %r and limit %d for guild_id %d', query, limit, guild_id)
|
log.warning('Timed out waiting for chunks with query %r and limit %d for guild_id %d', query, limit, guild_id)
|
||||||
@ -688,8 +688,6 @@ class ConnectionState:
|
|||||||
log.debug('CHANNEL_CREATE referencing an unknown channel type %s. Discarding.', data['type'])
|
log.debug('CHANNEL_CREATE referencing an unknown channel type %s. Discarding.', data['type'])
|
||||||
return
|
return
|
||||||
|
|
||||||
channel = None
|
|
||||||
|
|
||||||
if ch_type in (ChannelType.group, ChannelType.private):
|
if ch_type in (ChannelType.group, ChannelType.private):
|
||||||
channel_id = int(data['id'])
|
channel_id = int(data['id'])
|
||||||
if self._get_private_channel(channel_id) is None:
|
if self._get_private_channel(channel_id) is None:
|
||||||
@ -795,6 +793,12 @@ class ConnectionState:
|
|||||||
else:
|
else:
|
||||||
if self.member_cache_flags.joined:
|
if self.member_cache_flags.joined:
|
||||||
member = Member(data=data, guild=guild, state=self)
|
member = Member(data=data, guild=guild, state=self)
|
||||||
|
|
||||||
|
# Force an update on the inner user if necessary
|
||||||
|
user_update = member._update_inner_user(user)
|
||||||
|
if user_update:
|
||||||
|
self.dispatch('user_update', user_update[0], user_update[1])
|
||||||
|
|
||||||
guild._add_member(member)
|
guild._add_member(member)
|
||||||
log.debug('GUILD_MEMBER_UPDATE referencing an unknown member ID: %s. Discarding.', user_id)
|
log.debug('GUILD_MEMBER_UPDATE referencing an unknown member ID: %s. Discarding.', user_id)
|
||||||
|
|
||||||
@ -969,8 +973,19 @@ class ConnectionState:
|
|||||||
def parse_guild_members_chunk(self, data):
|
def parse_guild_members_chunk(self, data):
|
||||||
guild_id = int(data['guild_id'])
|
guild_id = int(data['guild_id'])
|
||||||
guild = self._get_guild(guild_id)
|
guild = self._get_guild(guild_id)
|
||||||
|
presences = data.get('presences', [])
|
||||||
|
|
||||||
members = [Member(guild=guild, data=member, state=self) for member in data.get('members', [])]
|
members = [Member(guild=guild, data=member, state=self) for member in data.get('members', [])]
|
||||||
log.debug('Processed a chunk for %s members in guild ID %s.', len(members), guild_id)
|
log.debug('Processed a chunk for %s members in guild ID %s.', len(members), guild_id)
|
||||||
|
|
||||||
|
if presences:
|
||||||
|
member_dict = {str(member.id): member for member in members}
|
||||||
|
for presence in presences:
|
||||||
|
user = presence['user']
|
||||||
|
member_id = user['id']
|
||||||
|
member = member_dict.get(member_id)
|
||||||
|
member._presence_update(presence, user)
|
||||||
|
|
||||||
complete = data.get('chunk_index', 0) + 1 == data.get('chunk_count')
|
complete = data.get('chunk_index', 0) + 1 == data.get('chunk_count')
|
||||||
self.process_chunk_requests(guild_id, data.get('nonce'), members, complete)
|
self.process_chunk_requests(guild_id, data.get('nonce'), members, complete)
|
||||||
|
|
||||||
@ -1123,9 +1138,9 @@ class AutoShardedConnectionState(ConnectionState):
|
|||||||
channel = new_guild.get_channel(channel_id) or Object(id=channel_id)
|
channel = new_guild.get_channel(channel_id) or Object(id=channel_id)
|
||||||
msg._rebind_channel_reference(channel)
|
msg._rebind_channel_reference(channel)
|
||||||
|
|
||||||
async def chunker(self, guild_id, query='', limit=0, *, shard_id=None, nonce=None):
|
async def chunker(self, guild_id, query='', limit=0, presences=False, *, shard_id=None, nonce=None):
|
||||||
ws = self._get_websocket(guild_id, shard_id=shard_id)
|
ws = self._get_websocket(guild_id, shard_id=shard_id)
|
||||||
await ws.request_chunks(guild_id, query=query, limit=limit, nonce=nonce)
|
await ws.request_chunks(guild_id, query=query, limit=limit, presences=presences, nonce=nonce)
|
||||||
|
|
||||||
async def _delay_ready(self):
|
async def _delay_ready(self):
|
||||||
await self.shards_launched.wait()
|
await self.shards_launched.wait()
|
||||||
|
@ -35,6 +35,7 @@ import aiohttp
|
|||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .errors import InvalidArgument, HTTPException, Forbidden, NotFound, DiscordServerError
|
from .errors import InvalidArgument, HTTPException, Forbidden, NotFound, DiscordServerError
|
||||||
|
from .message import Message
|
||||||
from .enums import try_enum, WebhookType
|
from .enums import try_enum, WebhookType
|
||||||
from .user import BaseUser, User
|
from .user import BaseUser, User
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
@ -45,6 +46,7 @@ __all__ = (
|
|||||||
'AsyncWebhookAdapter',
|
'AsyncWebhookAdapter',
|
||||||
'RequestsWebhookAdapter',
|
'RequestsWebhookAdapter',
|
||||||
'Webhook',
|
'Webhook',
|
||||||
|
'WebhookMessage',
|
||||||
)
|
)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -66,6 +68,9 @@ class WebhookAdapter:
|
|||||||
self._request_url = '{0.BASE}/webhooks/{1}/{2}'.format(self, webhook.id, webhook.token)
|
self._request_url = '{0.BASE}/webhooks/{1}/{2}'.format(self, webhook.id, webhook.token)
|
||||||
self.webhook = webhook
|
self.webhook = webhook
|
||||||
|
|
||||||
|
def is_async(self):
|
||||||
|
return False
|
||||||
|
|
||||||
def request(self, verb, url, payload=None, multipart=None):
|
def request(self, verb, url, payload=None, multipart=None):
|
||||||
"""Actually does the request.
|
"""Actually does the request.
|
||||||
|
|
||||||
@ -94,6 +99,12 @@ class WebhookAdapter:
|
|||||||
def edit_webhook(self, *, reason=None, **payload):
|
def edit_webhook(self, *, reason=None, **payload):
|
||||||
return self.request('PATCH', self._request_url, payload=payload, reason=reason)
|
return self.request('PATCH', self._request_url, payload=payload, reason=reason)
|
||||||
|
|
||||||
|
def edit_webhook_message(self, message_id, payload):
|
||||||
|
return self.request('PATCH', '{}/messages/{}'.format(self._request_url, message_id), payload=payload)
|
||||||
|
|
||||||
|
def delete_webhook_message(self, message_id):
|
||||||
|
return self.request('DELETE', '{}/messages/{}'.format(self._request_url, message_id))
|
||||||
|
|
||||||
def handle_execution_response(self, data, *, wait):
|
def handle_execution_response(self, data, *, wait):
|
||||||
"""Transforms the webhook execution response into something
|
"""Transforms the webhook execution response into something
|
||||||
more meaningful.
|
more meaningful.
|
||||||
@ -178,6 +189,9 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
|||||||
self.session = session
|
self.session = session
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
def is_async(self):
|
||||||
|
return True
|
||||||
|
|
||||||
async def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None):
|
async def request(self, verb, url, payload=None, multipart=None, *, files=None, reason=None):
|
||||||
headers = {}
|
headers = {}
|
||||||
data = None
|
data = None
|
||||||
@ -253,8 +267,9 @@ class AsyncWebhookAdapter(WebhookAdapter):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
# transform into Message object
|
# transform into Message object
|
||||||
from .message import Message
|
# Make sure to coerce the state to the partial one to allow message edits/delete
|
||||||
return Message(data=data, state=self.webhook._state, channel=self.webhook.channel)
|
state = _PartialWebhookState(self, self.webhook, parent=self.webhook._state)
|
||||||
|
return WebhookMessage(data=data, state=state, channel=self.webhook.channel)
|
||||||
|
|
||||||
class RequestsWebhookAdapter(WebhookAdapter):
|
class RequestsWebhookAdapter(WebhookAdapter):
|
||||||
"""A webhook adapter suited for use with ``requests``.
|
"""A webhook adapter suited for use with ``requests``.
|
||||||
@ -356,8 +371,9 @@ class RequestsWebhookAdapter(WebhookAdapter):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
# transform into Message object
|
# transform into Message object
|
||||||
from .message import Message
|
# Make sure to coerce the state to the partial one to allow message edits/delete
|
||||||
return Message(data=response, state=self.webhook._state, channel=self.webhook.channel)
|
state = _PartialWebhookState(self, self.webhook, parent=self.webhook._state)
|
||||||
|
return WebhookMessage(data=response, state=state, channel=self.webhook.channel)
|
||||||
|
|
||||||
class _FriendlyHttpAttributeErrorHelper:
|
class _FriendlyHttpAttributeErrorHelper:
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
@ -366,9 +382,16 @@ class _FriendlyHttpAttributeErrorHelper:
|
|||||||
raise AttributeError('PartialWebhookState does not support http methods.')
|
raise AttributeError('PartialWebhookState does not support http methods.')
|
||||||
|
|
||||||
class _PartialWebhookState:
|
class _PartialWebhookState:
|
||||||
__slots__ = ('loop',)
|
__slots__ = ('loop', 'parent', '_webhook')
|
||||||
|
|
||||||
|
def __init__(self, adapter, webhook, parent):
|
||||||
|
self._webhook = webhook
|
||||||
|
|
||||||
|
if isinstance(parent, self.__class__):
|
||||||
|
self.parent = None
|
||||||
|
else:
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
def __init__(self, adapter):
|
|
||||||
# Fetch the loop from the adapter if it's there
|
# Fetch the loop from the adapter if it's there
|
||||||
try:
|
try:
|
||||||
self.loop = adapter.loop
|
self.loop = adapter.loop
|
||||||
@ -387,13 +410,111 @@ class _PartialWebhookState:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def http(self):
|
def http(self):
|
||||||
|
if self.parent is not None:
|
||||||
|
return self.parent.http
|
||||||
|
|
||||||
# Some data classes assign state.http and that should be kosher
|
# Some data classes assign state.http and that should be kosher
|
||||||
# however, using it should result in a late-binding error.
|
# however, using it should result in a late-binding error.
|
||||||
return _FriendlyHttpAttributeErrorHelper()
|
return _FriendlyHttpAttributeErrorHelper()
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
|
if self.parent is not None:
|
||||||
|
return getattr(self.parent, attr)
|
||||||
|
|
||||||
raise AttributeError('PartialWebhookState does not support {0!r}.'.format(attr))
|
raise AttributeError('PartialWebhookState does not support {0!r}.'.format(attr))
|
||||||
|
|
||||||
|
class WebhookMessage(Message):
|
||||||
|
"""Represents a message sent from your webhook.
|
||||||
|
|
||||||
|
This allows you to edit or delete a message sent by your
|
||||||
|
webhook.
|
||||||
|
|
||||||
|
This inherits from :class:`discord.Message` with changes to
|
||||||
|
:meth:`edit` and :meth:`delete` to work.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
|
||||||
|
def edit(self, **fields):
|
||||||
|
"""|maybecoro|
|
||||||
|
|
||||||
|
Edits the message.
|
||||||
|
|
||||||
|
The content must be able to be transformed into a string via ``str(content)``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
content: Optional[:class:`str`]
|
||||||
|
The content to edit the message with or ``None`` to clear it.
|
||||||
|
embeds: List[:class:`Embed`]
|
||||||
|
A list of embeds to edit the message with.
|
||||||
|
embed: Optional[:class:`Embed`]
|
||||||
|
The embed to edit the message with. ``None`` suppresses the embeds.
|
||||||
|
This should not be mixed with the ``embeds`` parameter.
|
||||||
|
allowed_mentions: :class:`AllowedMentions`
|
||||||
|
Controls the mentions being processed in this message.
|
||||||
|
See :meth:`.abc.Messageable.send` for more information.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Editing the message failed.
|
||||||
|
Forbidden
|
||||||
|
Edited a message that is not yours.
|
||||||
|
InvalidArgument
|
||||||
|
You specified both ``embed`` and ``embeds`` or the length of
|
||||||
|
``embeds`` was invalid or there was no token associated with
|
||||||
|
this webhook.
|
||||||
|
"""
|
||||||
|
return self._state._webhook.edit_message(self.id, **fields)
|
||||||
|
|
||||||
|
def _delete_delay_sync(self, delay):
|
||||||
|
time.sleep(delay)
|
||||||
|
return self._state._webhook.delete_message(self.id)
|
||||||
|
|
||||||
|
async def _delete_delay_async(self, delay):
|
||||||
|
async def inner_call():
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
try:
|
||||||
|
await self._state._webhook.delete_message(self.id)
|
||||||
|
except HTTPException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
asyncio.ensure_future(inner_call(), loop=self._state.loop)
|
||||||
|
return await asyncio.sleep(0)
|
||||||
|
|
||||||
|
def delete(self, *, delay=None):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Deletes the message.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
delay: Optional[:class:`float`]
|
||||||
|
If provided, the number of seconds to wait before deleting the message.
|
||||||
|
If this is a coroutine, the waiting is done in the background and deletion failures
|
||||||
|
are ignored. If this is not a coroutine then the delay blocks the thread.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
Forbidden
|
||||||
|
You do not have proper permissions to delete the message.
|
||||||
|
NotFound
|
||||||
|
The message was deleted already.
|
||||||
|
HTTPException
|
||||||
|
Deleting the message failed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if delay is not None:
|
||||||
|
if self._state.parent._adapter.is_async():
|
||||||
|
return self._delete_delay_async(delay)
|
||||||
|
else:
|
||||||
|
return self._delete_delay_sync(delay)
|
||||||
|
|
||||||
|
return self._state._webhook.delete_message(self.id)
|
||||||
|
|
||||||
class Webhook(Hashable):
|
class Webhook(Hashable):
|
||||||
"""Represents a Discord webhook.
|
"""Represents a Discord webhook.
|
||||||
|
|
||||||
@ -488,7 +609,7 @@ class Webhook(Hashable):
|
|||||||
self.name = data.get('name')
|
self.name = data.get('name')
|
||||||
self.avatar = data.get('avatar')
|
self.avatar = data.get('avatar')
|
||||||
self.token = data.get('token')
|
self.token = data.get('token')
|
||||||
self._state = state or _PartialWebhookState(adapter)
|
self._state = state or _PartialWebhookState(adapter, self, parent=state)
|
||||||
self._adapter = adapter
|
self._adapter = adapter
|
||||||
self._adapter._prepare(self)
|
self._adapter._prepare(self)
|
||||||
|
|
||||||
@ -785,7 +906,7 @@ class Webhook(Hashable):
|
|||||||
wait: :class:`bool`
|
wait: :class:`bool`
|
||||||
Whether the server should wait before sending a response. This essentially
|
Whether the server should wait before sending a response. This essentially
|
||||||
means that the return type of this function changes from ``None`` to
|
means that the return type of this function changes from ``None`` to
|
||||||
a :class:`Message` if set to ``True``.
|
a :class:`WebhookMessage` if set to ``True``.
|
||||||
username: :class:`str`
|
username: :class:`str`
|
||||||
The username to send with this message. If no username is provided
|
The username to send with this message. If no username is provided
|
||||||
then the default username for the webhook is used.
|
then the default username for the webhook is used.
|
||||||
@ -825,7 +946,7 @@ class Webhook(Hashable):
|
|||||||
|
|
||||||
Returns
|
Returns
|
||||||
---------
|
---------
|
||||||
Optional[:class:`Message`]
|
Optional[:class:`WebhookMessage`]
|
||||||
The message that was sent.
|
The message that was sent.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -869,3 +990,115 @@ class Webhook(Hashable):
|
|||||||
def execute(self, *args, **kwargs):
|
def execute(self, *args, **kwargs):
|
||||||
"""An alias for :meth:`~.Webhook.send`."""
|
"""An alias for :meth:`~.Webhook.send`."""
|
||||||
return self.send(*args, **kwargs)
|
return self.send(*args, **kwargs)
|
||||||
|
|
||||||
|
def edit_message(self, message_id, **fields):
|
||||||
|
"""|maybecoro|
|
||||||
|
|
||||||
|
Edits a message owned by this webhook.
|
||||||
|
|
||||||
|
This is a lower level interface to :meth:`WebhookMessage.edit` in case
|
||||||
|
you only have an ID.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
message_id: :class:`int`
|
||||||
|
The message ID to edit.
|
||||||
|
content: Optional[:class:`str`]
|
||||||
|
The content to edit the message with or ``None`` to clear it.
|
||||||
|
embeds: List[:class:`Embed`]
|
||||||
|
A list of embeds to edit the message with.
|
||||||
|
embed: Optional[:class:`Embed`]
|
||||||
|
The embed to edit the message with. ``None`` suppresses the embeds.
|
||||||
|
This should not be mixed with the ``embeds`` parameter.
|
||||||
|
allowed_mentions: :class:`AllowedMentions`
|
||||||
|
Controls the mentions being processed in this message.
|
||||||
|
See :meth:`.abc.Messageable.send` for more information.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Editing the message failed.
|
||||||
|
Forbidden
|
||||||
|
Edited a message that is not yours.
|
||||||
|
InvalidArgument
|
||||||
|
You specified both ``embed`` and ``embeds`` or the length of
|
||||||
|
``embeds`` was invalid or there was no token associated with
|
||||||
|
this webhook.
|
||||||
|
"""
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
|
||||||
|
if self.token is None:
|
||||||
|
raise InvalidArgument('This webhook does not have a token associated with it')
|
||||||
|
|
||||||
|
try:
|
||||||
|
content = fields['content']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if content is not None:
|
||||||
|
content = str(content)
|
||||||
|
payload['content'] = content
|
||||||
|
|
||||||
|
# Check if the embeds interface is being used
|
||||||
|
try:
|
||||||
|
embeds = fields['embeds']
|
||||||
|
except KeyError:
|
||||||
|
# Nope
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if embeds is None or len(embeds) > 10:
|
||||||
|
raise InvalidArgument('embeds has a maximum of 10 elements')
|
||||||
|
payload['embeds'] = [e.to_dict() for e in embeds]
|
||||||
|
|
||||||
|
try:
|
||||||
|
embed = fields['embed']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if 'embeds' in payload:
|
||||||
|
raise InvalidArgument('Cannot mix embed and embeds keyword arguments')
|
||||||
|
|
||||||
|
if embed is None:
|
||||||
|
payload['embeds'] = []
|
||||||
|
else:
|
||||||
|
payload['embeds'] = [embed.to_dict()]
|
||||||
|
|
||||||
|
allowed_mentions = fields.pop('allowed_mentions', None)
|
||||||
|
previous_mentions = getattr(self._state, 'allowed_mentions', None)
|
||||||
|
|
||||||
|
if allowed_mentions:
|
||||||
|
if previous_mentions is not None:
|
||||||
|
payload['allowed_mentions'] = previous_mentions.merge(allowed_mentions).to_dict()
|
||||||
|
else:
|
||||||
|
payload['allowed_mentions'] = allowed_mentions.to_dict()
|
||||||
|
elif previous_mentions is not None:
|
||||||
|
payload['allowed_mentions'] = previous_mentions.to_dict()
|
||||||
|
|
||||||
|
return self._adapter.edit_webhook_message(message_id, payload=payload)
|
||||||
|
|
||||||
|
def delete_message(self, message_id):
|
||||||
|
"""|maybecoro|
|
||||||
|
|
||||||
|
Deletes a message owned by this webhook.
|
||||||
|
|
||||||
|
This is a lower level interface to :meth:`WebhookMessage.delete` in case
|
||||||
|
you only have an ID.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
------------
|
||||||
|
message_id: :class:`int`
|
||||||
|
The message ID to delete.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Deleting the message failed.
|
||||||
|
Forbidden
|
||||||
|
Deleted a message that is not yours.
|
||||||
|
"""
|
||||||
|
return self._adapter.delete_webhook_message(message_id)
|
||||||
|
1
docs/_static/codeblocks.css
vendored
1
docs/_static/codeblocks.css
vendored
@ -112,6 +112,7 @@
|
|||||||
:root[data-theme="dark"] .highlight .ni { color: #d0d0d0; } /* Name.Entity */
|
:root[data-theme="dark"] .highlight .ni { color: #d0d0d0; } /* Name.Entity */
|
||||||
:root[data-theme="dark"] .highlight .ne { color: #bbbbbb; } /* Name.Exception */
|
:root[data-theme="dark"] .highlight .ne { color: #bbbbbb; } /* Name.Exception */
|
||||||
:root[data-theme="dark"] .highlight .nf { color: #6494d8; } /* Name.Function */
|
:root[data-theme="dark"] .highlight .nf { color: #6494d8; } /* Name.Function */
|
||||||
|
:root[data-theme="dark"] .highlight .fm { color: #6494d8; } /* Name.Function.Magic */
|
||||||
:root[data-theme="dark"] .highlight .nl { color: #d0d0d0; } /* Name.Label */
|
:root[data-theme="dark"] .highlight .nl { color: #d0d0d0; } /* Name.Label */
|
||||||
:root[data-theme="dark"] .highlight .nn { color: #6494d8;} /* Name.Namespace */
|
:root[data-theme="dark"] .highlight .nn { color: #6494d8;} /* Name.Namespace */
|
||||||
:root[data-theme="dark"] .highlight .nx { color: #d0d0d0; } /* Name.Other */
|
:root[data-theme="dark"] .highlight .nx { color: #d0d0d0; } /* Name.Other */
|
||||||
|
18
docs/_static/custom.js
vendored
18
docs/_static/custom.js
vendored
@ -5,6 +5,7 @@ let bottomHeightThreshold, sections;
|
|||||||
let hamburgerToggle;
|
let hamburgerToggle;
|
||||||
let mobileSearch;
|
let mobileSearch;
|
||||||
let sidebar;
|
let sidebar;
|
||||||
|
let toTop;
|
||||||
|
|
||||||
class Modal {
|
class Modal {
|
||||||
constructor(element) {
|
constructor(element) {
|
||||||
@ -49,12 +50,19 @@ class SearchBar {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scrollToTop() {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
mobileSearch = new SearchBar();
|
mobileSearch = new SearchBar();
|
||||||
|
|
||||||
bottomHeightThreshold = document.documentElement.scrollHeight - 30;
|
bottomHeightThreshold = document.documentElement.scrollHeight - 30;
|
||||||
sections = document.querySelectorAll('section');
|
sections = document.querySelectorAll('section');
|
||||||
hamburgerToggle = document.getElementById('hamburger-toggle');
|
hamburgerToggle = document.getElementById('hamburger-toggle');
|
||||||
|
|
||||||
|
toTop = document.getElementById('to-top');
|
||||||
|
toTop.hidden = !(window.scrollY > 0);
|
||||||
|
|
||||||
if (hamburgerToggle) {
|
if (hamburgerToggle) {
|
||||||
hamburgerToggle.addEventListener('click', (e) => {
|
hamburgerToggle.addEventListener('click', (e) => {
|
||||||
@ -76,6 +84,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// insert ourselves after the element
|
// insert ourselves after the element
|
||||||
parent.insertBefore(table, element.nextSibling);
|
parent.insertBefore(table, element.nextSibling);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
toTop.hidden = !(window.scrollY > 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.code == "Escape" && activeModal) {
|
||||||
|
activeModal.close();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
|
5
docs/_static/settings.js
vendored
5
docs/_static/settings.js
vendored
@ -94,10 +94,13 @@ function updateSetting(element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const setting of settings) {
|
||||||
|
setting.load();
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
settingsModal = new Modal(document.querySelector('div#settings.modal'));
|
settingsModal = new Modal(document.querySelector('div#settings.modal'));
|
||||||
for (const setting of settings) {
|
for (const setting of settings) {
|
||||||
setting.load();
|
|
||||||
setting.setElement();
|
setting.setElement();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
63
docs/_static/style.css
vendored
63
docs/_static/style.css
vendored
@ -19,6 +19,7 @@ Historically however, thanks to:
|
|||||||
/* CSS variables would go here */
|
/* CSS variables would go here */
|
||||||
:root {
|
:root {
|
||||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
--monospace-font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||||
|
|
||||||
/* palette goes here */
|
/* palette goes here */
|
||||||
--white: #ffffff;
|
--white: #ffffff;
|
||||||
@ -96,6 +97,8 @@ Historically however, thanks to:
|
|||||||
--rtd-ad-background: var(--grey-2);
|
--rtd-ad-background: var(--grey-2);
|
||||||
--rtd-ad-main-text: var(--grey-6);
|
--rtd-ad-main-text: var(--grey-6);
|
||||||
--rtd-ad-small-text: var(--grey-4);
|
--rtd-ad-small-text: var(--grey-4);
|
||||||
|
--rtd-version-background: #272525;
|
||||||
|
--rtd-version-main-text: #fcfcfc;
|
||||||
--attribute-table-title: var(--grey-6);
|
--attribute-table-title: var(--grey-6);
|
||||||
--attribute-table-entry-border: var(--grey-3);
|
--attribute-table-entry-border: var(--grey-3);
|
||||||
--attribute-table-entry-text: var(--grey-5);
|
--attribute-table-entry-text: var(--grey-5);
|
||||||
@ -103,6 +106,7 @@ Historically however, thanks to:
|
|||||||
--attribute-table-entry-hover-background: var(--grey-2);
|
--attribute-table-entry-hover-background: var(--grey-2);
|
||||||
--attribute-table-entry-hover-text: var(--blue-2);
|
--attribute-table-entry-hover-text: var(--blue-2);
|
||||||
--attribute-table-badge: var(--grey-7);
|
--attribute-table-badge: var(--grey-7);
|
||||||
|
--highlighted-text: rgb(252, 233, 103);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-font="serif"] {
|
:root[data-font="serif"] {
|
||||||
@ -162,6 +166,7 @@ Historically however, thanks to:
|
|||||||
--attribute-table-entry-hover-background: var(--grey-6);
|
--attribute-table-entry-hover-background: var(--grey-6);
|
||||||
--attribute-table-entry-hover-text: var(--blue-1);
|
--attribute-table-entry-hover-text: var(--blue-1);
|
||||||
--attribute-table-badge: var(--grey-4);
|
--attribute-table-badge: var(--grey-4);
|
||||||
|
--highlighted-text: rgba(250, 166, 26, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
img[src$="snake_dark.svg"] {
|
img[src$="snake_dark.svg"] {
|
||||||
@ -247,6 +252,7 @@ header > nav {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
header > nav a {
|
header > nav a {
|
||||||
@ -265,6 +271,12 @@ header > nav.mobile-only {
|
|||||||
|
|
||||||
header > nav.mobile-only .search {
|
header > nav.mobile-only .search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: -1;
|
||||||
|
padding-top: 0;
|
||||||
|
transition: top 0.5s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
header > nav.mobile-only .search-wrapper {
|
header > nav.mobile-only .search-wrapper {
|
||||||
@ -316,6 +328,11 @@ header > nav > a:hover {
|
|||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sub-header option {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
.sub-header > select:focus {
|
.sub-header > select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
@ -380,12 +397,12 @@ aside h3 {
|
|||||||
position: relative;
|
position: relative;
|
||||||
line-height: 0.5em;
|
line-height: 0.5em;
|
||||||
transition: transform 0.4s;
|
transition: transform 0.4s;
|
||||||
transform: rotate(0deg);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.expanded {
|
.expanded {
|
||||||
transition: transform 0.4s;
|
transition: transform 0.4s;
|
||||||
transform: rotate(-90deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ref-internal-padding {
|
.ref-internal-padding {
|
||||||
@ -567,6 +584,37 @@ div.modal input {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* scroll to top button */
|
||||||
|
|
||||||
|
#to-top {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 50px;
|
||||||
|
right: 20px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#to-top.is-rtd {
|
||||||
|
bottom: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#to-top > span {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
width: auto;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 6px;
|
||||||
|
|
||||||
|
background-color: var(--rtd-version-background);
|
||||||
|
color: var(--rtd-version-main-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#to-top span {
|
||||||
|
line-height: 30px;
|
||||||
|
font-size: 90%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* copy button */
|
/* copy button */
|
||||||
|
|
||||||
.relative-copy {
|
.relative-copy {
|
||||||
@ -855,7 +903,7 @@ dl.field-list {
|
|||||||
/* internal references are forced to bold for some reason */
|
/* internal references are forced to bold for some reason */
|
||||||
a.reference.internal > strong {
|
a.reference.internal > strong {
|
||||||
font-weight: unset;
|
font-weight: unset;
|
||||||
font-family: monospace;
|
font-family: var(--monospace-font-family);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* exception hierarchy */
|
/* exception hierarchy */
|
||||||
@ -950,7 +998,7 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pre, code {
|
pre, code {
|
||||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
font-family: var(--monospace-font-family);
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
@ -1007,6 +1055,13 @@ dd {
|
|||||||
margin-left: 1.5em;
|
margin-left: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dt:target, span.highlighted {
|
||||||
|
background-color: var(--highlighted-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
rect.highlighted {
|
||||||
|
fill: var(--highlighted-text);
|
||||||
|
}
|
||||||
|
|
||||||
.container.operations {
|
.container.operations {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
10
docs/_templates/layout.html
vendored
10
docs/_templates/layout.html
vendored
@ -67,7 +67,7 @@
|
|||||||
<a onclick="mobileSearch.close();" title="{{ _('Close') }}" id="close-search" class="mobile-only" hidden><span class="material-icons">close</span></a>
|
<a onclick="mobileSearch.close();" title="{{ _('Close') }}" id="close-search" class="mobile-only" hidden><span class="material-icons">close</span></a>
|
||||||
</nav>
|
</nav>
|
||||||
<nav class="mobile-only">
|
<nav class="mobile-only">
|
||||||
<form role="search" class="search" action="search.html" method="get">
|
<form role="search" class="search" action="{{ pathto('search') }}" method="get">
|
||||||
<div class="search-wrapper">
|
<div class="search-wrapper">
|
||||||
<input type="search" name="q" placeholder="{{ _('Search documentation') }}" />
|
<input type="search" name="q" placeholder="{{ _('Search documentation') }}" />
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
@ -90,7 +90,7 @@
|
|||||||
<option value="{{ pathto(p + '/index')|e }}" {% if pagename is prefixedwith p %}selected{% endif %}>{{ ext }}</option>
|
<option value="{{ pathto(p + '/index')|e }}" {% if pagename is prefixedwith p %}selected{% endif %}>{{ ext }}</option>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</select>
|
</select>
|
||||||
<form role="search" class="search" action="search.html" method="get">
|
<form role="search" class="search" action="{{ pathto('search') }}" method="get">
|
||||||
<div class="search-wrapper">
|
<div class="search-wrapper">
|
||||||
<input type="search" name="q" placeholder="{{ _('Search documentation') }}" />
|
<input type="search" name="q" placeholder="{{ _('Search documentation') }}" />
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
@ -115,7 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
{#- The actual body of the contents #}
|
{#- The actual body of the contents #}
|
||||||
<main class="grid-item">
|
<main class="grid-item" role="main">
|
||||||
{% block body %} {% endblock %}
|
{% block body %} {% endblock %}
|
||||||
</main>
|
</main>
|
||||||
{%- block footer %}
|
{%- block footer %}
|
||||||
@ -190,5 +190,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="to-top" onclick="scrollToTop()"{%- if READTHEDOCS %} class="is-rtd"{%- endif %} hidden>
|
||||||
|
<span><span class="material-icons">arrow_upward</span> to top</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
40
docs/api.rst
40
docs/api.rst
@ -87,6 +87,14 @@ VoiceClient
|
|||||||
.. autoclass:: VoiceClient()
|
.. autoclass:: VoiceClient()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
VoiceProtocol
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: VoiceProtocol
|
||||||
|
|
||||||
|
.. autoclass:: VoiceProtocol
|
||||||
|
:members:
|
||||||
|
|
||||||
AudioSource
|
AudioSource
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -147,7 +155,7 @@ Opus Library
|
|||||||
Event Reference
|
Event Reference
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
This page outlines the different types of events listened by :class:`Client`.
|
This section outlines the different types of events listened by :class:`Client`.
|
||||||
|
|
||||||
There are two ways to register an event, the first way is through the use of
|
There are two ways to register an event, the first way is through the use of
|
||||||
:meth:`Client.event`. The second way is through subclassing :class:`Client` and
|
:meth:`Client.event`. The second way is through subclassing :class:`Client` and
|
||||||
@ -661,6 +669,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
|||||||
- activity
|
- activity
|
||||||
- nickname
|
- nickname
|
||||||
- roles
|
- roles
|
||||||
|
- pending
|
||||||
|
|
||||||
This requires :attr:`Intents.members` to be enabled.
|
This requires :attr:`Intents.members` to be enabled.
|
||||||
|
|
||||||
@ -2150,9 +2159,9 @@ Certain utilities make working with async iterators easier, detailed below.
|
|||||||
Collects items into chunks of up to a given maximum size.
|
Collects items into chunks of up to a given maximum size.
|
||||||
Another :class:`AsyncIterator` is returned which collects items into
|
Another :class:`AsyncIterator` is returned which collects items into
|
||||||
:class:`list`\s of a given size. The maximum chunk size must be a positive integer.
|
:class:`list`\s of a given size. The maximum chunk size must be a positive integer.
|
||||||
|
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
Collecting groups of users: ::
|
Collecting groups of users: ::
|
||||||
|
|
||||||
async for leader, *users in reaction.users().chunk(3):
|
async for leader, *users in reaction.users().chunk(3):
|
||||||
@ -2616,11 +2625,22 @@ Webhook Support
|
|||||||
|
|
||||||
discord.py offers support for creating, editing, and executing webhooks through the :class:`Webhook` class.
|
discord.py offers support for creating, editing, and executing webhooks through the :class:`Webhook` class.
|
||||||
|
|
||||||
|
Webhook
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
.. attributetable:: Webhook
|
.. attributetable:: Webhook
|
||||||
|
|
||||||
.. autoclass:: Webhook
|
.. autoclass:: Webhook
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
WebhookMessage
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: WebhookMessage
|
||||||
|
|
||||||
|
.. autoclass:: WebhookMessage
|
||||||
|
:members:
|
||||||
|
|
||||||
Adapters
|
Adapters
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
@ -2790,6 +2810,8 @@ Message
|
|||||||
DeletedReferencedMessage
|
DeletedReferencedMessage
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: DeletedReferencedMessage
|
||||||
|
|
||||||
.. autoclass:: DeletedReferencedMessage()
|
.. autoclass:: DeletedReferencedMessage()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -2998,6 +3020,8 @@ Invite
|
|||||||
Template
|
Template
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: Template
|
||||||
|
|
||||||
.. autoclass:: Template()
|
.. autoclass:: Template()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -3029,6 +3053,8 @@ Widget
|
|||||||
Sticker
|
Sticker
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: Sticker
|
||||||
|
|
||||||
.. autoclass:: Sticker()
|
.. autoclass:: Sticker()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -3101,6 +3127,8 @@ dynamic attributes in mind.
|
|||||||
Object
|
Object
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: Object
|
||||||
|
|
||||||
.. autoclass:: Object
|
.. autoclass:: Object
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -3126,6 +3154,12 @@ MessageReference
|
|||||||
.. autoclass:: MessageReference
|
.. autoclass:: MessageReference
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
PartialMessage
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. autoclass:: PartialMessage
|
||||||
|
:members:
|
||||||
|
|
||||||
Intents
|
Intents
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
14
docs/conf.py
14
docs/conf.py
@ -40,6 +40,7 @@ extensions = [
|
|||||||
'details',
|
'details',
|
||||||
'exception_hierarchy',
|
'exception_hierarchy',
|
||||||
'attributetable',
|
'attributetable',
|
||||||
|
'resourcelinks',
|
||||||
]
|
]
|
||||||
|
|
||||||
autodoc_member_order = 'bysource'
|
autodoc_member_order = 'bysource'
|
||||||
@ -76,7 +77,7 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'discord.py'
|
project = u'discord.py'
|
||||||
copyright = u'2015-2020, Rapptz'
|
copyright = u'2015-2021, Rapptz'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
@ -91,6 +92,9 @@ with open('../discord/__init__.py') as f:
|
|||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = version
|
release = version
|
||||||
|
|
||||||
|
# This assumes a tag is available for final releases
|
||||||
|
branch = 'master' if version.endswith('a') else 'v' + version
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
#
|
#
|
||||||
@ -152,6 +156,13 @@ html_context = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource_links = {
|
||||||
|
'discord': 'https://discord.gg/r3sSKJJ',
|
||||||
|
'issues': 'https://github.com/Rapptz/discord.py/issues',
|
||||||
|
'discussions': 'https://github.com/Rapptz/discord.py/discussions',
|
||||||
|
'examples': 'https://github.com/Rapptz/discord.py/tree/%s/examples' % branch,
|
||||||
|
}
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
@ -337,3 +348,4 @@ def setup(app):
|
|||||||
if app.config.language == 'ja':
|
if app.config.language == 'ja':
|
||||||
app.config.intersphinx_mapping['py'] = ('https://docs.python.org/ja/3', None)
|
app.config.intersphinx_mapping['py'] = ('https://docs.python.org/ja/3', None)
|
||||||
app.config.html_context['discord_invite'] = 'https://discord.gg/nXzj3dg'
|
app.config.html_context['discord_invite'] = 'https://discord.gg/nXzj3dg'
|
||||||
|
app.config.resource_links['discord'] = 'https://discord.gg/nXzj3dg'
|
@ -35,7 +35,7 @@ Creating a Bot account is a pretty straightforward process.
|
|||||||
|
|
||||||
7. Copy the token using the "Copy" button.
|
7. Copy the token using the "Copy" button.
|
||||||
|
|
||||||
- **This is not the Client Secret at the General Information page**
|
- **This is not the Client Secret at the General Information page.**
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
@ -7,16 +7,29 @@ The following section outlines the API of discord.py's command extension module.
|
|||||||
|
|
||||||
.. _ext_commands_api_bot:
|
.. _ext_commands_api_bot:
|
||||||
|
|
||||||
|
Bots
|
||||||
|
------
|
||||||
|
|
||||||
Bot
|
Bot
|
||||||
----
|
~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.Bot
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.Bot
|
.. autoclass:: discord.ext.commands.Bot
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
|
AutoShardedBot
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.AutoShardedBot
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.AutoShardedBot
|
.. autoclass:: discord.ext.commands.AutoShardedBot
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Prefix Helpers
|
||||||
|
----------------
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.when_mentioned
|
.. autofunction:: discord.ext.commands.when_mentioned
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.when_mentioned_or
|
.. autofunction:: discord.ext.commands.when_mentioned_or
|
||||||
@ -64,21 +77,39 @@ are custom to the command extension module.
|
|||||||
|
|
||||||
.. _ext_commands_api_command:
|
.. _ext_commands_api_command:
|
||||||
|
|
||||||
Command
|
Commands
|
||||||
--------
|
----------
|
||||||
|
|
||||||
|
Decorators
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.command
|
.. autofunction:: discord.ext.commands.command
|
||||||
|
|
||||||
.. autofunction:: discord.ext.commands.group
|
.. autofunction:: discord.ext.commands.group
|
||||||
|
|
||||||
|
Command
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.Command
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.Command
|
.. autoclass:: discord.ext.commands.Command
|
||||||
:members:
|
:members:
|
||||||
:special-members: __call__
|
:special-members: __call__
|
||||||
|
|
||||||
|
Group
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.Group
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.Group
|
.. autoclass:: discord.ext.commands.Group
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
|
GroupMixin
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.GroupMixin
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.GroupMixin
|
.. autoclass:: discord.ext.commands.GroupMixin
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -87,28 +118,58 @@ Command
|
|||||||
Cogs
|
Cogs
|
||||||
------
|
------
|
||||||
|
|
||||||
|
Cog
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.Cog
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.Cog
|
.. autoclass:: discord.ext.commands.Cog
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
CogMeta
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.CogMeta
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.CogMeta
|
.. autoclass:: discord.ext.commands.CogMeta
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. _ext_commands_help_command:
|
.. _ext_commands_help_command:
|
||||||
|
|
||||||
Help Commands
|
Help Commands
|
||||||
-----------------
|
---------------
|
||||||
|
|
||||||
|
HelpCommand
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.HelpCommand
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.HelpCommand
|
.. autoclass:: discord.ext.commands.HelpCommand
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
DefaultHelpCommand
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.DefaultHelpCommand
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.DefaultHelpCommand
|
.. autoclass:: discord.ext.commands.DefaultHelpCommand
|
||||||
:members:
|
:members:
|
||||||
:exclude-members: send_bot_help, send_cog_help, send_group_help, send_command_help, prepare_help_command
|
:exclude-members: send_bot_help, send_cog_help, send_group_help, send_command_help, prepare_help_command
|
||||||
|
|
||||||
|
MinimalHelpCommand
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.MinimalHelpCommand
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.MinimalHelpCommand
|
.. autoclass:: discord.ext.commands.MinimalHelpCommand
|
||||||
:members:
|
:members:
|
||||||
:exclude-members: send_bot_help, send_cog_help, send_group_help, send_command_help, prepare_help_command
|
:exclude-members: send_bot_help, send_cog_help, send_group_help, send_command_help, prepare_help_command
|
||||||
|
|
||||||
|
Paginator
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.Paginator
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.Paginator
|
.. autoclass:: discord.ext.commands.Paginator
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -190,6 +251,8 @@ Checks
|
|||||||
Context
|
Context
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.commands.Context
|
||||||
|
|
||||||
.. autoclass:: discord.ext.commands.Context
|
.. autoclass:: discord.ext.commands.Context
|
||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
@ -353,9 +416,15 @@ Exceptions
|
|||||||
.. autoexception:: discord.ext.commands.ChannelNotReadable
|
.. autoexception:: discord.ext.commands.ChannelNotReadable
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoexception:: discord.ext.commands.BadColourArgument
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoexception:: discord.ext.commands.RoleNotFound
|
.. autoexception:: discord.ext.commands.RoleNotFound
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoexception:: discord.ext.commands.BadInviteArgument
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoexception:: discord.ext.commands.EmojiNotFound
|
.. autoexception:: discord.ext.commands.EmojiNotFound
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -409,7 +478,7 @@ Exceptions
|
|||||||
|
|
||||||
|
|
||||||
Exception Hierarchy
|
Exception Hierarchy
|
||||||
+++++++++++++++++++++
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. exception_hierarchy::
|
.. exception_hierarchy::
|
||||||
|
|
||||||
|
@ -713,7 +713,7 @@ Global Checks
|
|||||||
Sometimes we want to apply a check to **every** command, not just certain commands. The library supports this as well
|
Sometimes we want to apply a check to **every** command, not just certain commands. The library supports this as well
|
||||||
using the global check concept.
|
using the global check concept.
|
||||||
|
|
||||||
Global checks work similarly to regular checks except they are registered with the :func:`.Bot.check` decorator.
|
Global checks work similarly to regular checks except they are registered with the :meth:`.Bot.check` decorator.
|
||||||
|
|
||||||
For example, to block all DMs we could do the following:
|
For example, to block all DMs we could do the following:
|
||||||
|
|
||||||
|
@ -135,6 +135,8 @@ Doing something during cancellation:
|
|||||||
API Reference
|
API Reference
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
.. attributetable:: discord.ext.tasks.Loop
|
||||||
|
|
||||||
.. autoclass:: discord.ext.tasks.Loop()
|
.. autoclass:: discord.ext.tasks.Loop()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
44
docs/extensions/resourcelinks.py
Normal file
44
docs/extensions/resourcelinks.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Credit to sphinx.ext.extlinks for being a good starter
|
||||||
|
# Copyright 2007-2020 by the Sphinx team
|
||||||
|
# Licensed under BSD.
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Tuple
|
||||||
|
|
||||||
|
from docutils import nodes, utils
|
||||||
|
from docutils.nodes import Node, system_message
|
||||||
|
from docutils.parsers.rst.states import Inliner
|
||||||
|
|
||||||
|
import sphinx
|
||||||
|
from sphinx.application import Sphinx
|
||||||
|
from sphinx.util.nodes import split_explicit_title
|
||||||
|
from sphinx.util.typing import RoleFunction
|
||||||
|
|
||||||
|
|
||||||
|
def make_link_role(resource_links: Dict[str, str]) -> RoleFunction:
|
||||||
|
def role(
|
||||||
|
typ: str,
|
||||||
|
rawtext: str,
|
||||||
|
text: str,
|
||||||
|
lineno: int,
|
||||||
|
inliner: Inliner,
|
||||||
|
options: Dict = {},
|
||||||
|
content: List[str] = []
|
||||||
|
) -> Tuple[List[Node], List[system_message]]:
|
||||||
|
|
||||||
|
text = utils.unescape(text)
|
||||||
|
has_explicit_title, title, key = split_explicit_title(text)
|
||||||
|
full_url = resource_links[key]
|
||||||
|
if not has_explicit_title:
|
||||||
|
title = full_url
|
||||||
|
pnode = nodes.reference(title, title, internal=False, refuri=full_url)
|
||||||
|
return [pnode], []
|
||||||
|
return role
|
||||||
|
|
||||||
|
|
||||||
|
def add_link_role(app: Sphinx) -> None:
|
||||||
|
app.add_role('resource', make_link_role(app.config.resource_links))
|
||||||
|
|
||||||
|
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||||
|
app.add_config_value('resource_links', {}, 'env')
|
||||||
|
app.connect('builder-inited', add_link_role)
|
||||||
|
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
|
@ -30,41 +30,57 @@ Features
|
|||||||
- Easy to use with an object oriented design
|
- Easy to use with an object oriented design
|
||||||
- Optimised for both speed and memory
|
- Optimised for both speed and memory
|
||||||
|
|
||||||
Documentation Contents
|
Getting started
|
||||||
-----------------------
|
-----------------
|
||||||
|
|
||||||
.. toctree::
|
Is this your first time using the library? This is the place to get started!
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
intro
|
- **First steps:** :doc:`intro` | :doc:`quickstart` | :doc:`logging`
|
||||||
quickstart
|
- **Working with Discord:** :doc:`discord` | :doc:`intents`
|
||||||
migrating
|
- **Examples:** Many examples are available in the :resource:`repository <examples>`.
|
||||||
logging
|
|
||||||
api
|
Getting help
|
||||||
|
--------------
|
||||||
|
|
||||||
|
If you're having trouble with something, these resources might help.
|
||||||
|
|
||||||
|
- Try the :doc:`faq` first, it's got answers to all common questions.
|
||||||
|
- Ask us and hang out with us in our :resource:`Discord <discord>` server.
|
||||||
|
- If you're looking for something specific, try the :ref:`index <genindex>` or :ref:`searching <search>`.
|
||||||
|
- Report bugs in the :resource:`issue tracker <issues>`.
|
||||||
|
- Ask in our :resource:`GitHub discussions page <discussions>`.
|
||||||
|
|
||||||
Extensions
|
Extensions
|
||||||
-----------
|
------------
|
||||||
|
|
||||||
|
These extensions help you during development when it comes to common tasks.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 3
|
:maxdepth: 1
|
||||||
|
|
||||||
ext/commands/index.rst
|
ext/commands/index.rst
|
||||||
ext/tasks/index.rst
|
ext/tasks/index.rst
|
||||||
|
|
||||||
|
Manuals
|
||||||
|
---------
|
||||||
|
|
||||||
Additional Information
|
These pages go into great detail about everything the API can do.
|
||||||
-----------------------
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 1
|
||||||
|
|
||||||
discord
|
api
|
||||||
intents
|
discord.ext.commands API Reference <ext/commands/api.rst>
|
||||||
faq
|
discord.ext.tasks API Reference <ext/tasks/index.rst>
|
||||||
whats_new
|
|
||||||
version_guarantees
|
|
||||||
|
|
||||||
If you still can't find what you're looking for, try in one of the following pages:
|
Meta
|
||||||
|
------
|
||||||
|
|
||||||
* :ref:`genindex`
|
If you're looking for something related to the project itself, it's here.
|
||||||
* :ref:`search`
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
whats_new
|
||||||
|
version_guarantees
|
||||||
|
migrating
|
||||||
|
@ -60,7 +60,7 @@ With the API change requiring bot authors to specify intents, some intents were
|
|||||||
A privileged intent is one that requires you to go to the developer portal and manually enable it. To enable privileged intents do the following:
|
A privileged intent is one that requires you to go to the developer portal and manually enable it. To enable privileged intents do the following:
|
||||||
|
|
||||||
1. Make sure you're logged on to the `Discord website <https://discord.com>`_.
|
1. Make sure you're logged on to the `Discord website <https://discord.com>`_.
|
||||||
2. Navigate to the `application page <https://discord.com/developers/applications>`_
|
2. Navigate to the `application page <https://discord.com/developers/applications>`_.
|
||||||
3. Click on the bot you want to enable privileged intents for.
|
3. Click on the bot you want to enable privileged intents for.
|
||||||
4. Navigate to the bot tab on the left side of the screen.
|
4. Navigate to the bot tab on the left side of the screen.
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ A privileged intent is one that requires you to go to the developer portal and m
|
|||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
Enabling privileged intents when your bot is in over 100 guilds requires going through `bot verification <https://support.discord.com/hc/en-us/articles/360040720412>`_. If your bot is already verified and you would like to enable a privileged intent you must go through `discord support <https://dis.gd/contact>`_ and talk to them about it.
|
Enabling privileged intents when your bot is in over 100 guilds requires going through `bot verification <https://support.discord.com/hc/en-us/articles/360040720412>`_. If your bot is already verified and you would like to enable a privileged intent you must go through `Discord support <https://dis.gd/contact>`_ and talk to them about it.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@ -203,4 +203,4 @@ On Windows use ``py -3`` instead of ``python3``.
|
|||||||
|
|
||||||
There is no currently set date in which the old gateway will stop working so it is recommended to update your code instead.
|
There is no currently set date in which the old gateway will stop working so it is recommended to update your code instead.
|
||||||
|
|
||||||
If you truly dislike the direction Discord is going with their API, you can contact them via `support <https://dis.gd/contact>`_
|
If you truly dislike the direction Discord is going with their API, you can contact them via `support <https://dis.gd/contact>`_.
|
||||||
|
@ -52,7 +52,7 @@ Virtual Environments
|
|||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Sometimes you want to keep libraries from polluting system installs or use a different version of
|
Sometimes you want to keep libraries from polluting system installs or use a different version of
|
||||||
libraries than the ones installed on the system. You might also not have permissions to install libaries system-wide.
|
libraries than the ones installed on the system. You might also not have permissions to install libraries system-wide.
|
||||||
For this purpose, the standard library as of Python 3.3 comes with a concept called "Virtual Environment"s to
|
For this purpose, the standard library as of Python 3.3 comes with a concept called "Virtual Environment"s to
|
||||||
help maintain these separate versions.
|
help maintain these separate versions.
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ if you don't check the :ref:`installing` portion.
|
|||||||
A Minimal Bot
|
A Minimal Bot
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
Let's make a bot that replies to a specific message and walk you through it.
|
Let's make a bot that responds to a specific message and walk you through it.
|
||||||
|
|
||||||
It looks something like this:
|
It looks something like this:
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ There's a lot going on here, so let's walk you through it step by step.
|
|||||||
sure that we ignore messages from ourselves. We do this by checking if the :attr:`Message.author`
|
sure that we ignore messages from ourselves. We do this by checking if the :attr:`Message.author`
|
||||||
is the same as the :attr:`Client.user`.
|
is the same as the :attr:`Client.user`.
|
||||||
5. Afterwards, we check if the :class:`Message.content` starts with ``'$hello'``. If it is,
|
5. Afterwards, we check if the :class:`Message.content` starts with ``'$hello'``. If it is,
|
||||||
then we reply in the channel it was used in with ``'Hello!'``.
|
then we send a message in the channel it was used in with ``'Hello!'``.
|
||||||
6. Finally, we run the bot with our login token. If you need help getting your token or creating a bot,
|
6. Finally, we run the bot with our login token. If you need help getting your token or creating a bot,
|
||||||
look in the :ref:`discord-intro` section.
|
look in the :ref:`discord-intro` section.
|
||||||
|
|
||||||
|
@ -66,6 +66,78 @@ New Features
|
|||||||
|
|
||||||
- |commands| Add :attr:`Context.clean_prefix <ext.commands.Context>`
|
- |commands| Add :attr:`Context.clean_prefix <ext.commands.Context>`
|
||||||
|
|
||||||
|
.. _vp1p6p0:
|
||||||
|
|
||||||
|
v1.6.0
|
||||||
|
--------
|
||||||
|
|
||||||
|
This version comes with support for replies and stickers.
|
||||||
|
|
||||||
|
New Features
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- An entirely redesigned documentation. This was the cumulation of multiple months of effort.
|
||||||
|
- There's now a dark theme, feel free to navigate to the cog on the screen to change your setting, though this should be automatic.
|
||||||
|
- Add support for :meth:`AppInfo.icon_url_as` and :meth:`AppInfo.cover_image_url_as` (:issue:`5888`)
|
||||||
|
- Add :meth:`Colour.random` to get a random colour (:issue:`6067`)
|
||||||
|
- Add support for stickers via :class:`Sticker` (:issue:`5946`)
|
||||||
|
- Add support for replying via :meth:`Message.reply` (:issue:`6061`)
|
||||||
|
- This also comes with the :attr:`AllowedMentions.replied_user` setting.
|
||||||
|
- :meth:`abc.Messageable.send` can now accept a :class:`MessageReference`.
|
||||||
|
- :class:`MessageReference` can now be constructed by users.
|
||||||
|
- :meth:`Message.to_reference` can now convert a message to a :class:`MessageReference`.
|
||||||
|
- Add support for getting the replied to resolved message through :attr:`MessageReference.resolved`.
|
||||||
|
- Add support for role tags.
|
||||||
|
- :attr:`Guild.premium_subscriber_role` to get the "Nitro Booster" role (if available).
|
||||||
|
- :attr:`Guild.self_role` to get the bot's own role (if available).
|
||||||
|
- :attr:`Role.tags` to get the role's tags.
|
||||||
|
- :meth:`Role.is_premium_subscriber` to check if a role is the "Nitro Booster" role.
|
||||||
|
- :meth:`Role.is_bot_managed` to check if a role is a bot role (i.e. the automatically created role for bots).
|
||||||
|
- :meth:`Role.is_integration` to check if a role is role created by an integration.
|
||||||
|
- Add :meth:`Client.is_ws_ratelimited` to check if the websocket is rate limited.
|
||||||
|
- :meth:`ShardInfo.is_ws_ratelimited` is the equivalent for checking a specific shard.
|
||||||
|
- Add support for chunking an :class:`AsyncIterator` through :meth:`AsyncIterator.chunk` (:issue:`6100`, :issue:`6082`)
|
||||||
|
- Add :attr:`PartialEmoji.created_at` (:issue:`6128`)
|
||||||
|
- Add support for editing and deleting webhook sent messages (:issue:`6058`)
|
||||||
|
- This adds :class:`WebhookMessage` as well to power this behaviour.
|
||||||
|
- Add :class:`PartialMessage` to allow working with a message via channel objects and just a message_id (:issue:`5905`)
|
||||||
|
- This is useful if you don't want to incur an extra API call to fetch the message.
|
||||||
|
- Add :meth:`Emoji.url_as` (:issue:`6162`)
|
||||||
|
- Add support for :attr:`Member.pending` for the membership gating feature.
|
||||||
|
- Allow ``colour`` parameter to take ``int`` in :meth:`Guild.create_role` (:issue:`6195`)
|
||||||
|
- Add support for ``presences`` in :meth:`Guild.query_members` (:issue:`2354`)
|
||||||
|
- |commands| Add support for ``description`` keyword argument in :class:`commands.Cog <ext.commands.Cog>` (:issue:`6028`)
|
||||||
|
- |tasks| Add support for calling the wrapped coroutine as a function via ``__call__``.
|
||||||
|
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Raise :exc:`DiscordServerError` when reaching 503s repeatedly (:issue:`6044`)
|
||||||
|
- Fix :exc:`AttributeError` when :meth:`Client.fetch_template` is called (:issue:`5986`)
|
||||||
|
- Fix errors when playing audio and moving to another channel (:issue:`5953`)
|
||||||
|
- Fix :exc:`AttributeError` when voice channels disconnect too fast (:issue:`6039`)
|
||||||
|
- Fix stale :class:`User` references when the members intent is off.
|
||||||
|
- Fix :func:`on_user_update` not dispatching in certain cases when a member is not cached but the user somehow is.
|
||||||
|
- Fix :attr:`Message.author` being overwritten in certain cases during message update.
|
||||||
|
- This would previously make it so :attr:`Message.author` is a :class:`User`.
|
||||||
|
- Fix :exc:`UnboundLocalError` for editing ``public_updates_channel`` in :meth:`Guild.edit` (:issue:`6093`)
|
||||||
|
- Fix uninitialised :attr:`CustomActivity.created_at` (:issue:`6095`)
|
||||||
|
- |commands| Errors during cog unload no longer stops module cleanup (:issue:`6113`)
|
||||||
|
- |commands| Properly cleanup lingering commands when a conflicting alias is found when adding commands (:issue:`6217`)
|
||||||
|
|
||||||
|
Miscellaneous
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- ``ffmpeg`` spawned processes no longer open a window in Windows (:issue:`6038`)
|
||||||
|
- Update dependencies to allow the library to work on Python 3.9+ without requiring build tools. (:issue:`5984`, :issue:`5970`)
|
||||||
|
- Fix docstring issue leading to a SyntaxError in 3.9 (:issue:`6153`)
|
||||||
|
- Update Windows opus binaries from 1.2.1 to 1.3.1 (:issue:`6161`)
|
||||||
|
- Allow :meth:`Guild.create_role` to accept :class:`int` as the ``colour`` parameter (:issue:`6195`)
|
||||||
|
- |commands| :class:`MessageConverter <ext.commands.MessageConverter>` regex got updated to support ``www.`` prefixes (:issue:`6002`)
|
||||||
|
- |commands| :class:`UserConverter <ext.commands.UserConverter>` now fetches the API if an ID is passed and the user is not cached.
|
||||||
|
- |commands| :func:`max_concurrency <ext.commands.max_concurrency>` is now called before cooldowns (:issue:`6172`)
|
||||||
|
|
||||||
.. _vp1p5p1:
|
.. _vp1p5p1:
|
||||||
|
|
||||||
v1.5.1
|
v1.5.1
|
||||||
|
@ -44,7 +44,7 @@ class RoleReactClient(discord.Client):
|
|||||||
async def on_raw_reaction_remove(self, payload):
|
async def on_raw_reaction_remove(self, payload):
|
||||||
"""Removes a role based on a reaction emoji."""
|
"""Removes a role based on a reaction emoji."""
|
||||||
# Make sure that the message the user is reacting to is the one we care about
|
# Make sure that the message the user is reacting to is the one we care about
|
||||||
if payload.message_id == self.role_message_id:
|
if payload.message_id != self.role_message_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user