fix merge conflict
This commit is contained in:
commit
6e024871ec
@ -15,7 +15,7 @@ __title__ = 'discord'
|
||||
__author__ = 'Rapptz'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright 2015-2020 Rapptz'
|
||||
__version__ = '1.5.1.6'
|
||||
__version__ = '1.6.0.6a'
|
||||
|
||||
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
||||
|
||||
@ -58,10 +58,11 @@ from .voice_client import VoiceClient, VoiceProtocol
|
||||
from .audit_logs import AuditLogChanges, AuditLogEntry, AuditLogDiff
|
||||
from .raw_models import *
|
||||
from .team import *
|
||||
from .sticker import Sticker
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel serial')
|
||||
|
||||
version_info = VersionInfo(major=1, minor=5, micro=1, releaselevel='final', serial=0)
|
||||
version_info = VersionInfo(major=1, minor=6, micro=0, releaselevel='alpha', serial=0)
|
||||
|
||||
try:
|
||||
from logging import NullHandler
|
||||
|
@ -713,7 +713,7 @@ class GuildChannel:
|
||||
async def create_invite(self, *, reason=None, **fields):
|
||||
"""|coro|
|
||||
|
||||
Creates an instant invite.
|
||||
Creates an instant invite from a text or voice channel.
|
||||
|
||||
You must have the :attr:`~Permissions.create_instant_invite` permission to
|
||||
do this.
|
||||
@ -741,6 +741,9 @@ class GuildChannel:
|
||||
~discord.HTTPException
|
||||
Invite creation failed.
|
||||
|
||||
~discord.NotFound
|
||||
The channel that was passed is a category or an invalid channel.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`~discord.Invite`
|
||||
|
@ -131,17 +131,79 @@ class AppInfo:
|
||||
def icon_url(self):
|
||||
""":class:`.Asset`: Retrieves the application's icon asset.
|
||||
|
||||
This is equivalent to calling :meth:`icon_url_as` with
|
||||
the default parameters ('webp' format and a size of 1024).
|
||||
|
||||
.. versionadded:: 1.3
|
||||
"""
|
||||
return Asset._from_icon(self._state, self, 'app')
|
||||
return self.icon_url_as()
|
||||
|
||||
def icon_url_as(self, *, format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the icon the application has.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the icon to. Defaults to 'webp'.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_icon(self._state, self, 'app', format=format, size=size)
|
||||
|
||||
|
||||
@property
|
||||
def cover_image_url(self):
|
||||
""":class:`.Asset`: Retrieves the cover image on a store embed.
|
||||
|
||||
This is equivalent to calling :meth:`cover_image_url_as` with
|
||||
the default parameters ('webp' format and a size of 1024).
|
||||
|
||||
.. versionadded:: 1.3
|
||||
"""
|
||||
return Asset._from_cover_image(self._state, self)
|
||||
return self.cover_image_url_as()
|
||||
|
||||
def cover_image_url_as(self, *, format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the image on store embeds
|
||||
if this application is a game sold on Discord.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the image to. Defaults to 'webp'.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_cover_image(self._state, self, format=format, size=size)
|
||||
|
||||
@property
|
||||
def guild(self):
|
||||
|
@ -89,19 +89,29 @@ class Asset:
|
||||
return cls(state, '/avatars/{0.id}/{0.avatar}.{1}?size={2}'.format(user, format, size))
|
||||
|
||||
@classmethod
|
||||
def _from_icon(cls, state, object, path):
|
||||
def _from_icon(cls, state, object, path, *, format='webp', size=1024):
|
||||
if object.icon is None:
|
||||
return cls(state)
|
||||
|
||||
url = '/{0}-icons/{1.id}/{1.icon}.jpg'.format(path, object)
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
if format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument("format must be None or one of {}".format(VALID_STATIC_FORMATS))
|
||||
|
||||
url = '/{0}-icons/{1.id}/{1.icon}.{2}?size={3}'.format(path, object, format, size)
|
||||
return cls(state, url)
|
||||
|
||||
@classmethod
|
||||
def _from_cover_image(cls, state, obj):
|
||||
def _from_cover_image(cls, state, obj, *, format='webp', size=1024):
|
||||
if obj.cover_image is None:
|
||||
return cls(state)
|
||||
|
||||
url = '/app-assets/{0.id}/store/{0.cover_image}.jpg'.format(obj)
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
if format not in VALID_STATIC_FORMATS:
|
||||
raise InvalidArgument("format must be None or one of {}".format(VALID_STATIC_FORMATS))
|
||||
|
||||
url = '/app-assets/{0.id}/store/{0.cover_image}.{1}?size={2}'.format(obj, format, size)
|
||||
return cls(state, url)
|
||||
|
||||
@classmethod
|
||||
@ -136,6 +146,12 @@ class Asset:
|
||||
|
||||
return cls(state, '/icons/{0.id}/{0.icon}.{1}?size={2}'.format(guild, format, size))
|
||||
|
||||
@classmethod
|
||||
def _from_sticker_url(cls, state, sticker, *, size=1024):
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
|
||||
return cls(state, '/stickers/{0.id}/{0.image}.png?size={2}'.format(sticker, format, size))
|
||||
|
||||
def __str__(self):
|
||||
return self.BASE + self._url if self._url is not None else ''
|
||||
|
@ -1161,8 +1161,39 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
""":class:`Asset`: Returns the channel's icon asset if available."""
|
||||
return Asset._from_icon(self._state, self, 'channel')
|
||||
""":class:`Asset`: Returns the channel's icon asset if available.
|
||||
|
||||
This is equivalent to calling :meth:`icon_url_as` with
|
||||
the default parameters ('webp' format and a size of 1024).
|
||||
"""
|
||||
return self.icon_url_as()
|
||||
|
||||
def icon_url_as(self, *, format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the icon the channel has.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the icon to. Defaults to 'webp'.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_icon(self._state, self, 'channel', format=format, size=size)
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
|
@ -129,6 +129,19 @@ class Colour:
|
||||
"""A factory method that returns a :class:`Colour` with a value of ``0``."""
|
||||
return cls(0)
|
||||
|
||||
@classmethod
|
||||
def random(cls):
|
||||
"""A factory method that returns a :class:`Colour` with a random hue.
|
||||
|
||||
.. note::
|
||||
|
||||
The random algorithm works by choosing a colour with a random hue but
|
||||
with maxed out saturation and value.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
return cls.from_hsv(random.random(), 1, 1)
|
||||
|
||||
@classmethod
|
||||
def teal(cls):
|
||||
"""A factory method that returns a :class:`Colour` with a value of ``0x1abc9c``."""
|
||||
@ -251,7 +264,7 @@ class Colour:
|
||||
def dark_theme(cls):
|
||||
"""A factory method that returns a :class:`Colour` with a value of ``0x36393F``.
|
||||
This will appear transparent on Discord's dark theme.
|
||||
|
||||
|
||||
.. versionadded:: 1.5
|
||||
"""
|
||||
return cls(0x36393F)
|
||||
|
@ -52,6 +52,7 @@ __all__ = (
|
||||
'WebhookType',
|
||||
'ExpireBehaviour',
|
||||
'ExpireBehavior',
|
||||
'StickerType',
|
||||
)
|
||||
|
||||
def _create_value_cls(name):
|
||||
@ -455,3 +456,8 @@ def try_enum(cls, val):
|
||||
return cls._enum_value_map_[val]
|
||||
except (KeyError, TypeError, AttributeError):
|
||||
return val
|
||||
|
||||
class StickerType(Enum):
|
||||
png = 1
|
||||
apng = 2
|
||||
lottie = 3
|
||||
|
@ -129,7 +129,7 @@ class MemberConverter(IDConverter):
|
||||
"""
|
||||
|
||||
async def query_member_named(self, guild, argument):
|
||||
cache = guild._state._member_cache_flags.joined
|
||||
cache = guild._state.member_cache_flags.joined
|
||||
if len(argument) > 5 and argument[-5] == '#':
|
||||
username, _, discriminator = argument.rpartition('#')
|
||||
members = await guild.query_members(username, limit=100, cache=cache)
|
||||
@ -140,7 +140,7 @@ class MemberConverter(IDConverter):
|
||||
|
||||
async def query_member_by_id(self, bot, guild, user_id):
|
||||
ws = bot._get_websocket(shard_id=guild.shard_id)
|
||||
cache = guild._state._member_cache_flags.joined
|
||||
cache = guild._state.member_cache_flags.joined
|
||||
if ws.is_ratelimited():
|
||||
# If we're being rate limited on the WS, then fall back to using the HTTP API
|
||||
# So we don't have to wait ~60 seconds for the query to finish
|
||||
@ -206,6 +206,10 @@ class UserConverter(IDConverter):
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.UserNotFound` instead of generic :exc:`.BadArgument`
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
This converter now lazily fetches users from the HTTP APIs if an ID is passed
|
||||
and it's not available in cache.
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
match = self._get_id_match(argument) or re.match(r'<@!?([0-9]+)>$', argument)
|
||||
@ -214,26 +218,33 @@ class UserConverter(IDConverter):
|
||||
|
||||
if match is not None:
|
||||
user_id = int(match.group(1))
|
||||
result = await ctx.bot.try_user(user_id) or _utils_get(ctx.message.mentions, id=user_id)
|
||||
else:
|
||||
arg = argument
|
||||
result = ctx.bot.get_user(user_id) or _utils_get(ctx.message.mentions, id=user_id)
|
||||
if result is None:
|
||||
try:
|
||||
result = await ctx.bot.try_user(user_id)
|
||||
except discord.HTTPException:
|
||||
raise UserNotFound(argument) from None
|
||||
|
||||
# Remove the '@' character if this is the first character from the argument
|
||||
if arg[0] == '@':
|
||||
# Remove first character
|
||||
arg = arg[1:]
|
||||
return result
|
||||
|
||||
# check for discriminator if it exists,
|
||||
if len(arg) > 5 and arg[-5] == '#':
|
||||
discrim = arg[-4:]
|
||||
name = arg[:-5]
|
||||
predicate = lambda u: u.name == name and u.discriminator == discrim
|
||||
result = discord.utils.find(predicate, state._users.values())
|
||||
if result is not None:
|
||||
return result
|
||||
arg = argument
|
||||
|
||||
predicate = lambda u: u.name == arg
|
||||
# Remove the '@' character if this is the first character from the argument
|
||||
if arg[0] == '@':
|
||||
# Remove first character
|
||||
arg = arg[1:]
|
||||
|
||||
# check for discriminator if it exists,
|
||||
if len(arg) > 5 and arg[-5] == '#':
|
||||
discrim = arg[-4:]
|
||||
name = arg[:-5]
|
||||
predicate = lambda u: u.name == name and u.discriminator == discrim
|
||||
result = discord.utils.find(predicate, state._users.values())
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
predicate = lambda u: u.name == arg
|
||||
result = discord.utils.find(predicate, state._users.values())
|
||||
|
||||
if result is None:
|
||||
raise UserNotFound(argument)
|
||||
@ -254,12 +265,11 @@ class MessageConverter(Converter):
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.ChannelNotFound`, `MessageNotFound` or `ChannelNotReadable` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
|
||||
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})$')
|
||||
link_regex = re.compile(
|
||||
r'^https?://(?:(ptb|canary)\.)?discord(?:app)?\.com/channels/'
|
||||
r'(?:([0-9]{15,21})|(@me))'
|
||||
r'https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/'
|
||||
r'(?:[0-9]{15,21}|@me)'
|
||||
r'/(?P<channel_id>[0-9]{15,21})/(?P<message_id>[0-9]{15,21})/?$'
|
||||
)
|
||||
match = id_regex.match(argument) or link_regex.match(argument)
|
||||
|
@ -868,6 +868,10 @@ class MemberCacheFlags(BaseFlags):
|
||||
self.value = self.DEFAULT_VALUE
|
||||
return self
|
||||
|
||||
@property
|
||||
def _empty(self):
|
||||
return self.value == self.DEFAULT_VALUE
|
||||
|
||||
@flag_value
|
||||
def online(self):
|
||||
""":class:`bool`: Whether to cache members with a status.
|
||||
|
@ -308,8 +308,8 @@ class Guild(Hashable):
|
||||
self._rules_channel_id = utils._get_as_snowflake(guild, 'rules_channel_id')
|
||||
self._public_updates_channel_id = utils._get_as_snowflake(guild, 'public_updates_channel_id')
|
||||
|
||||
cache_online_members = self._state._member_cache_flags.online
|
||||
cache_joined = self._state._member_cache_flags.joined
|
||||
cache_online_members = self._state.member_cache_flags.online
|
||||
cache_joined = self._state.member_cache_flags.joined
|
||||
self_id = self._state.self_id
|
||||
for mdata in guild.get('members', []):
|
||||
member = Member(data=mdata, guild=self, state=state)
|
||||
@ -1253,7 +1253,8 @@ class Guild(Hashable):
|
||||
def fetch_members(self, *, limit=1000, after=None):
|
||||
"""|coro|
|
||||
|
||||
Retrieves an :class:`.AsyncIterator` that enables receiving the guild's members.
|
||||
Retrieves an :class:`.AsyncIterator` that enables receiving the guild's members. In order to use this,
|
||||
:meth:`Intents.members` must be enabled.
|
||||
|
||||
.. note::
|
||||
|
||||
@ -1274,6 +1275,8 @@ class Guild(Hashable):
|
||||
|
||||
Raises
|
||||
------
|
||||
ClientException
|
||||
The members intent is not enabled.
|
||||
HTTPException
|
||||
Getting the members failed.
|
||||
|
||||
@ -1295,6 +1298,10 @@ class Guild(Hashable):
|
||||
members = await guild.fetch_members(limit=150).flatten()
|
||||
# members is now a list of Member...
|
||||
"""
|
||||
|
||||
if not self._state._intents.members:
|
||||
raise ClientException('Intents.members must be enabled to use this.')
|
||||
|
||||
return MemberIterator(self, limit=limit, after=after)
|
||||
|
||||
async def try_member(self, member_id):
|
||||
|
@ -241,6 +241,8 @@ class HTTPClient:
|
||||
raise Forbidden(r, data)
|
||||
elif r.status == 404:
|
||||
raise NotFound(r, data)
|
||||
elif r.status == 503:
|
||||
raise DiscordServerError(r, data)
|
||||
else:
|
||||
raise HTTPException(r, data)
|
||||
|
||||
|
@ -43,6 +43,7 @@ from .file import File
|
||||
from .utils import escape_mentions
|
||||
from .guild import Guild
|
||||
from .mixins import Hashable
|
||||
from .sticker import Sticker
|
||||
|
||||
|
||||
class Attachment:
|
||||
@ -286,11 +287,15 @@ class MessageReference:
|
||||
|
||||
def flatten_handlers(cls):
|
||||
prefix = len('_handle_')
|
||||
cls._HANDLERS = {
|
||||
key[prefix:]: value
|
||||
handlers = [
|
||||
(key[prefix:], value)
|
||||
for key, value in cls.__dict__.items()
|
||||
if key.startswith('_handle_')
|
||||
}
|
||||
if key.startswith('_handle_') and key != '_handle_member'
|
||||
]
|
||||
|
||||
# store _handle_member last
|
||||
handlers.append(('member', cls._handle_member))
|
||||
cls._HANDLERS = handlers
|
||||
cls._CACHED_SLOTS = [
|
||||
attr for attr in cls.__slots__ if attr.startswith('_cs_')
|
||||
]
|
||||
@ -392,6 +397,10 @@ class Message(Hashable):
|
||||
- ``description``: A string representing the application's description.
|
||||
- ``icon``: A string representing the icon ID of the application.
|
||||
- ``cover_image``: A string representing the embed's image asset ID.
|
||||
stickers: List[:class:`Sticker`]
|
||||
A list of stickers given to the message.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
__slots__ = ('_edited_timestamp', 'tts', 'content', 'channel', 'webhook_id',
|
||||
@ -399,8 +408,8 @@ class Message(Hashable):
|
||||
'_cs_channel_mentions', '_cs_raw_mentions', 'attachments',
|
||||
'_cs_clean_content', '_cs_raw_channel_mentions', 'nonce', 'pinned',
|
||||
'role_mentions', '_cs_raw_role_mentions', 'type', 'call', 'flags',
|
||||
'_cs_system_content', '_cs_guild', '_state', 'reactions', 'reference',
|
||||
'application', 'activity')
|
||||
'_cs_system_content', '_cs_guild', '_state', 'reactions', 'reference',
|
||||
'application', 'activity', 'stickers')
|
||||
|
||||
def __init__(self, *, state, channel, data):
|
||||
self._state = state
|
||||
@ -420,6 +429,7 @@ class Message(Hashable):
|
||||
self.tts = data['tts']
|
||||
self.content = data['content']
|
||||
self.nonce = data.get('nonce')
|
||||
self.stickers = [Sticker(data=data, state=state) for data in data.get('stickers', [])]
|
||||
|
||||
ref = data.get('message_reference')
|
||||
self.reference = MessageReference(state, **ref) if ref is not None else None
|
||||
@ -496,10 +506,13 @@ class Message(Hashable):
|
||||
return reaction
|
||||
|
||||
def _update(self, data):
|
||||
handlers = self._HANDLERS
|
||||
for key, value in data.items():
|
||||
# In an update scheme, 'author' key has to be handled before 'member'
|
||||
# otherwise they overwrite each other which is undesirable.
|
||||
# Since there's no good way to do this we have to iterate over every
|
||||
# handler rather than iterating over the keys which is a little slower
|
||||
for key, handler in self._HANDLERS:
|
||||
try:
|
||||
handler = handlers[key]
|
||||
value = data[key]
|
||||
except KeyError:
|
||||
continue
|
||||
else:
|
||||
|
@ -51,6 +51,11 @@ __all__ = (
|
||||
'PCMVolumeTransformer',
|
||||
)
|
||||
|
||||
if sys.platform != 'win32':
|
||||
CREATE_NO_WINDOW = 0
|
||||
else:
|
||||
CREATE_NO_WINDOW = 0x08000000
|
||||
|
||||
class AudioSource:
|
||||
"""Represents an audio stream.
|
||||
|
||||
@ -136,7 +141,7 @@ class FFmpegAudio(AudioSource):
|
||||
def _spawn_process(self, args, **subprocess_kwargs):
|
||||
process = None
|
||||
try:
|
||||
process = subprocess.Popen(args, **subprocess_kwargs)
|
||||
process = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, **subprocess_kwargs)
|
||||
except FileNotFoundError:
|
||||
executable = args.partition(' ')[0] if isinstance(args, str) else args[0]
|
||||
raise ClientException(executable + ' was not found.') from None
|
||||
@ -469,7 +474,7 @@ class FFmpegOpusAudio(FFmpegAudio):
|
||||
@staticmethod
|
||||
def _probe_codec_fallback(source, executable='ffmpeg'):
|
||||
args = [executable, '-hide_banner', '-i', source]
|
||||
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
proc = subprocess.Popen(args, creationflags=CREATE_NO_WINDOW, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
out, _ = proc.communicate(timeout=20)
|
||||
output = out.decode('utf8')
|
||||
codec = bitrate = None
|
||||
|
@ -181,11 +181,14 @@ class ConnectionState:
|
||||
|
||||
cache_flags._verify_intents(intents)
|
||||
|
||||
self._member_cache_flags = cache_flags
|
||||
self.member_cache_flags = cache_flags
|
||||
self._activity = activity
|
||||
self._status = status
|
||||
self._intents = intents
|
||||
|
||||
if not intents.members or cache_flags._empty:
|
||||
self.store_user = self.store_user_no_intents
|
||||
|
||||
self.parsers = parsers = {}
|
||||
for attr, func in inspect.getmembers(self):
|
||||
if attr.startswith('parse_'):
|
||||
@ -279,6 +282,9 @@ class ConnectionState:
|
||||
self._users[user_id] = user
|
||||
return user
|
||||
|
||||
def store_user_no_intents(self, data):
|
||||
return User(state=self, data=data)
|
||||
|
||||
def get_user(self, id):
|
||||
return self._users.get(id)
|
||||
|
||||
@ -520,6 +526,9 @@ class ConnectionState:
|
||||
raw.cached_message = older_message
|
||||
self.dispatch('raw_message_edit', raw)
|
||||
message._update(data)
|
||||
# Coerce the `after` parameter to take the new updated Member
|
||||
# ref: #5999
|
||||
older_message.author = message.author
|
||||
self.dispatch('message_edit', older_message, message)
|
||||
else:
|
||||
self.dispatch('raw_message_edit', raw)
|
||||
@ -604,7 +613,7 @@ class ConnectionState:
|
||||
user = data['user']
|
||||
member_id = int(user['id'])
|
||||
member = guild.get_member(member_id)
|
||||
flags = self._member_cache_flags
|
||||
flags = self.member_cache_flags
|
||||
if member is None:
|
||||
if 'username' not in user:
|
||||
# sometimes we receive 'incomplete' member data post-removal.
|
||||
@ -742,7 +751,7 @@ class ConnectionState:
|
||||
return
|
||||
|
||||
member = Member(guild=guild, data=data, state=self)
|
||||
if self._member_cache_flags.joined:
|
||||
if self.member_cache_flags.joined:
|
||||
guild._add_member(member)
|
||||
|
||||
try:
|
||||
@ -786,7 +795,7 @@ class ConnectionState:
|
||||
|
||||
self.dispatch('member_update', old_member, member)
|
||||
else:
|
||||
if self._member_cache_flags.joined:
|
||||
if self.member_cache_flags.joined:
|
||||
member = Member(data=data, guild=guild, state=self)
|
||||
guild._add_member(member)
|
||||
log.debug('GUILD_MEMBER_UPDATE referencing an unknown member ID: %s. Discarding.', user_id)
|
||||
@ -817,7 +826,7 @@ class ConnectionState:
|
||||
return self._add_guild_from_data(data)
|
||||
|
||||
async def chunk_guild(self, guild, *, wait=True, cache=None):
|
||||
cache = cache or self._member_cache_flags.joined
|
||||
cache = cache or self.member_cache_flags.joined
|
||||
request = self._chunk_requests.get(guild.id)
|
||||
if request is None:
|
||||
self._chunk_requests[guild.id] = request = ChunkRequest(guild.id, self.loop, self._get_guild, cache=cache)
|
||||
@ -984,7 +993,7 @@ class ConnectionState:
|
||||
def parse_voice_state_update(self, data):
|
||||
guild = self._get_guild(utils._get_as_snowflake(data, 'guild_id'))
|
||||
channel_id = utils._get_as_snowflake(data, 'channel_id')
|
||||
flags = self._member_cache_flags
|
||||
flags = self.member_cache_flags
|
||||
self_id = self.user.id
|
||||
if guild is not None:
|
||||
if int(data['user_id']) == self_id:
|
||||
|
139
discord/sticker.py
Normal file
139
discord/sticker.py
Normal file
@ -0,0 +1,139 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2020 Rapptz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from .mixins import Hashable
|
||||
from .asset import Asset
|
||||
from .utils import snowflake_time
|
||||
from .enums import StickerType, try_enum
|
||||
|
||||
class Sticker(Hashable):
|
||||
"""Represents a sticker
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
.. container:: operations
|
||||
|
||||
.. describe:: str(x)
|
||||
|
||||
Returns the name of the sticker
|
||||
|
||||
.. describe:: x == y
|
||||
|
||||
Checks if the sticker is equal to another sticker
|
||||
|
||||
.. describe:: x != y
|
||||
|
||||
Checks if the sticker is not equal to another sticker
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name: :class:`str`
|
||||
The sticker's name
|
||||
id: :class:`int`
|
||||
The id of the sticker
|
||||
description: :class:`str`
|
||||
The description of the sticker
|
||||
pack_id: :class:`int`
|
||||
The id of the sticker's pack
|
||||
format: :class:`StickerType`
|
||||
The format for the sticker's image
|
||||
image: :class:`str`
|
||||
The sticker's image
|
||||
tags: List[:class:`str`]
|
||||
A list of tags for the sticker
|
||||
preview_image: Optional[:class:`str`]
|
||||
The sticker's preview asset hash
|
||||
"""
|
||||
__slots__ = ('_state', 'id', 'name', 'description', 'pack_id', 'format', 'image', 'tags', 'preview_image')
|
||||
|
||||
def __init__(self, *, state, data):
|
||||
self._state = state
|
||||
self.id = int(data['id'])
|
||||
self.name = data['name']
|
||||
self.description = data['description']
|
||||
self.pack_id = int(data['pack_id'])
|
||||
self.format = try_enum(StickerType, data['format_type'])
|
||||
self.image = data['asset']
|
||||
|
||||
try:
|
||||
self.tags = [tag.strip() for tag in data['tags'].split(',')]
|
||||
except KeyError:
|
||||
self.tags = []
|
||||
|
||||
self.preview_image = data.get('preview_asset')
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0.__class__.__name__} id={0.id} name={0.name!r}>'.format(self)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
""":class:`datetime.datetime`: Returns the sticker's creation time in UTC as a naive datetime."""
|
||||
return snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
def image_url(self):
|
||||
"""Returns an :class:`Asset` for the sticker's image.
|
||||
|
||||
.. note::
|
||||
This will return ``None`` if the format is ``StickerType.lottie``
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[:class:`Asset`]
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return self.image_url_as()
|
||||
|
||||
def image_url_as(self, *, size=1024):
|
||||
"""Optionally returns an :class:`Asset` for the sticker's image.
|
||||
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. note::
|
||||
This will return ``None`` if the format is ``StickerType.lottie``.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Invalid ``size``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[:class:`Asset`]
|
||||
The resulting CDN asset or ``None``.
|
||||
"""
|
||||
if self.format is StickerType.lottie:
|
||||
return None
|
||||
|
||||
return Asset._from_sticker_url(self._state, self, size=size)
|
@ -68,8 +68,39 @@ class Team:
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
""":class:`.Asset`: Retrieves the team's icon asset."""
|
||||
return Asset._from_icon(self._state, self, 'team')
|
||||
""":class:`.Asset`: Retrieves the team's icon asset.
|
||||
|
||||
This is equivalent to calling :meth:`icon_url_as` with
|
||||
the default parameters ('webp' format and a size of 1024).
|
||||
"""
|
||||
return self.icon_url_as()
|
||||
|
||||
def icon_url_as(self, *, format='None', size=1024):
|
||||
"""Returns an :class:`Asset` for the icon the team has.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||
The size must be a power of 2 between 16 and 4096.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: :class:`str`
|
||||
The format to attempt to convert the icon to. Defaults to 'webp'.
|
||||
size: :class:`int`
|
||||
The size of the image to display.
|
||||
|
||||
Raises
|
||||
------
|
||||
InvalidArgument
|
||||
Bad image format passed to ``format`` or invalid ``size``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Asset`
|
||||
The resulting CDN asset.
|
||||
"""
|
||||
return Asset._from_icon(self._state, self, 'team', format=format, size=size)
|
||||
|
||||
@property
|
||||
def owner(self):
|
||||
|
@ -42,11 +42,11 @@ class _PartialTemplateState:
|
||||
def __init__(self, *, state):
|
||||
self.__state = state
|
||||
self.http = _FriendlyHttpAttributeErrorHelper()
|
||||
|
||||
|
||||
@property
|
||||
def is_bot(self):
|
||||
return self.__state.is_bot
|
||||
|
||||
|
||||
@property
|
||||
def shard_count(self):
|
||||
return self.__state.shard_count
|
||||
@ -54,14 +54,18 @@ class _PartialTemplateState:
|
||||
@property
|
||||
def user(self):
|
||||
return self.__state.user
|
||||
|
||||
|
||||
@property
|
||||
def self_id(self):
|
||||
return self.__state.user.id
|
||||
|
||||
|
||||
@property
|
||||
def member_cache_flags(self):
|
||||
return self.__state.member_cache_flags
|
||||
|
||||
def store_emoji(self, guild, packet):
|
||||
return None
|
||||
|
||||
|
||||
def _get_voice_client(self, id):
|
||||
return None
|
||||
|
||||
|
@ -220,6 +220,7 @@ class VoiceClient(VoiceProtocol):
|
||||
self._player = None
|
||||
self.encoder = None
|
||||
self._lite_nonce = 0
|
||||
self.ws = None
|
||||
|
||||
warn_nacl = not has_nacl
|
||||
supported_modes = (
|
||||
@ -364,6 +365,8 @@ class VoiceClient(VoiceProtocol):
|
||||
self._runner = self.loop.create_task(self.poll_voice_ws(reconnect))
|
||||
|
||||
async def potential_reconnect(self):
|
||||
# Attempt to stop the player thread from playing early
|
||||
self._connected.clear()
|
||||
self.prepare_handshake()
|
||||
self._potentially_reconnecting = True
|
||||
try:
|
||||
|
24
docs/api.rst
24
docs/api.rst
@ -2053,6 +2053,23 @@ of :class:`enum.Enum`.
|
||||
Represents the default avatar with the color red.
|
||||
See also :attr:`Colour.red`
|
||||
|
||||
.. class:: StickerType
|
||||
|
||||
Represents the type of sticker images.
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
.. attribute:: png
|
||||
|
||||
Represents a sticker with a png image.
|
||||
|
||||
.. attribute:: apng
|
||||
|
||||
Represents a sticker with an apng image.
|
||||
|
||||
.. attribute:: lottie
|
||||
|
||||
Represents a sticker with a lottie image.
|
||||
|
||||
Async Iterator
|
||||
----------------
|
||||
@ -2976,6 +2993,12 @@ Widget
|
||||
.. autoclass:: Widget()
|
||||
:members:
|
||||
|
||||
Sticker
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: Sticker()
|
||||
:members:
|
||||
|
||||
MessageReference
|
||||
~~~~~~~~~~~~~~~~~
|
||||
.. autoclass:: MessageReference()
|
||||
@ -3177,7 +3200,6 @@ PublicUserFlags
|
||||
.. autoclass:: PublicUserFlags()
|
||||
:members:
|
||||
|
||||
|
||||
Exceptions
|
||||
------------
|
||||
|
||||
|
@ -19,6 +19,7 @@ The intents that are necessary for your bot can only be dictated by yourself. Ea
|
||||
For example, if you want a bot that functions without spammy events like presences or typing then we could do the following:
|
||||
|
||||
.. code-block:: python3
|
||||
:emphasize-lines: 7,9,10
|
||||
|
||||
import discord
|
||||
intents = discord.Intents.default()
|
||||
@ -36,6 +37,7 @@ Note that this doesn't enable :attr:`Intents.members` since it's a privileged in
|
||||
Another example showing a bot that only deals with messages and guild information:
|
||||
|
||||
.. code-block:: python3
|
||||
:emphasize-lines: 7,9,10
|
||||
|
||||
import discord
|
||||
intents = discord.Intents(messages=True, guilds=True)
|
||||
@ -155,6 +157,7 @@ Due to an :ref:`API change <intents_member_cache>` Discord is now forcing develo
|
||||
For example:
|
||||
|
||||
.. code-block:: python3
|
||||
:emphasize-lines: 3,6,8,9
|
||||
|
||||
import discord
|
||||
intents = discord.Intents.default()
|
||||
|
@ -1 +1 @@
|
||||
aiohttp>=3.6.0,<3.7.0
|
||||
aiohttp>=3.6.0,<3.8.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user