looks like I changed stuff?
This commit is contained in:
commit
34f6c5db10
@ -10,7 +10,7 @@ sphinx:
|
||||
builder: html
|
||||
|
||||
python:
|
||||
version: 3.7
|
||||
version: 3.8
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Discord API Wrapper
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@ -15,53 +13,60 @@ __title__ = 'discord'
|
||||
__author__ = 'Rapptz'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright 2015-present Rapptz'
|
||||
<<<<<<< HEAD
|
||||
__version__ = '1.7.1.7'
|
||||
=======
|
||||
__version__ = '2.0.0.7a'
|
||||
>>>>>>> 523e35e4f3c3c49d4e471359f9fb559242bbecc8
|
||||
|
||||
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
||||
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
|
||||
from .client import Client
|
||||
from .appinfo import AppInfo
|
||||
from .user import User, ClientUser, Profile
|
||||
from .emoji import Emoji
|
||||
from .partial_emoji import PartialEmoji
|
||||
from .client import *
|
||||
from .appinfo import *
|
||||
from .user import *
|
||||
from .emoji import *
|
||||
from .partial_emoji import *
|
||||
from .activity import *
|
||||
from .channel import *
|
||||
from .guild import Guild
|
||||
from .guild import *
|
||||
from .flags import *
|
||||
from .relationship import Relationship
|
||||
from .member import Member, VoiceState
|
||||
from .member import *
|
||||
from .message import *
|
||||
from .asset import Asset
|
||||
from .asset import *
|
||||
from .errors import *
|
||||
from .calls import CallMessage, GroupCall
|
||||
from .permissions import Permissions, PermissionOverwrite
|
||||
from .role import Role, RoleTags
|
||||
from .file import File
|
||||
from .colour import Color, Colour
|
||||
from .integrations import Integration, IntegrationAccount
|
||||
from .invite import Invite, PartialInviteChannel, PartialInviteGuild
|
||||
from .template import Template
|
||||
from .widget import Widget, WidgetMember, WidgetChannel
|
||||
from .object import Object
|
||||
from .reaction import Reaction
|
||||
from .permissions import *
|
||||
from .role import *
|
||||
from .file import *
|
||||
from .colour import *
|
||||
from .integrations import *
|
||||
from .invite import *
|
||||
from .template import *
|
||||
from .widget import *
|
||||
from .object import *
|
||||
from .reaction import *
|
||||
from . import utils, opus, abc
|
||||
from .enums import *
|
||||
from .embeds import Embed
|
||||
from .mentions import AllowedMentions
|
||||
from .shard import AutoShardedClient, ShardInfo
|
||||
from .embeds import *
|
||||
from .mentions import *
|
||||
from .shard import *
|
||||
from .player import *
|
||||
from .webhook import *
|
||||
from .voice_client import VoiceClient, VoiceProtocol
|
||||
from .audit_logs import AuditLogChanges, AuditLogEntry, AuditLogDiff
|
||||
from .voice_client import *
|
||||
from .audit_logs import *
|
||||
from .raw_models import *
|
||||
from .team import *
|
||||
from .sticker import Sticker
|
||||
from .sticker import *
|
||||
from .interactions import *
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro enhanced releaselevel serial')
|
||||
|
||||
<<<<<<< HEAD
|
||||
version_info = VersionInfo(major=1, minor=7, micro=1, enhanced=7, releaselevel='final', serial=0)
|
||||
=======
|
||||
version_info = VersionInfo(major=2, minor=0, micro=0, enhanced=7, releaselevel='alpha', serial=0)
|
||||
>>>>>>> 523e35e4f3c3c49d4e471359f9fb559242bbecc8
|
||||
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -42,9 +40,9 @@ def show_version():
|
||||
if version_info.releaselevel != 'final':
|
||||
pkg = pkg_resources.get_distribution('discord.py')
|
||||
if pkg:
|
||||
entries.append(' - discord.py pkg_resources: v{0}'.format(pkg.version))
|
||||
entries.append(f' - discord.py pkg_resources: v{pkg.version}')
|
||||
|
||||
entries.append('- aiohttp v{0.__version__}'.format(aiohttp))
|
||||
entries.append(f'- aiohttp v{aiohttp.__version__}')
|
||||
uname = platform.uname()
|
||||
entries.append('- system info: {0.system} {0.release} {0.version}'.format(uname))
|
||||
print('\n'.join(entries))
|
||||
@ -54,7 +52,6 @@ def core(parser, args):
|
||||
show_version()
|
||||
|
||||
bot_template = """#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from discord.ext import commands
|
||||
import discord
|
||||
@ -110,9 +107,7 @@ var/
|
||||
config.py
|
||||
"""
|
||||
|
||||
cog_template = '''# -*- coding: utf-8 -*-
|
||||
|
||||
from discord.ext import commands
|
||||
cog_template = '''from discord.ext import commands
|
||||
import discord
|
||||
|
||||
class {name}(commands.Cog{attrs}):
|
||||
@ -200,7 +195,7 @@ def newbot(parser, args):
|
||||
try:
|
||||
new_directory.mkdir(exist_ok=True, parents=True)
|
||||
except OSError as exc:
|
||||
parser.error('could not create our bot directory ({})'.format(exc))
|
||||
parser.error(f'could not create our bot directory ({exc})')
|
||||
|
||||
cogs = new_directory / 'cogs'
|
||||
|
||||
@ -209,27 +204,27 @@ def newbot(parser, args):
|
||||
init = cogs / '__init__.py'
|
||||
init.touch()
|
||||
except OSError as exc:
|
||||
print('warning: could not create cogs directory ({})'.format(exc))
|
||||
print(f'warning: could not create cogs directory ({exc})')
|
||||
|
||||
try:
|
||||
with open(str(new_directory / 'config.py'), 'w', encoding='utf-8') as fp:
|
||||
fp.write('token = "place your token here"\ncogs = []\n')
|
||||
except OSError as exc:
|
||||
parser.error('could not create config file ({})'.format(exc))
|
||||
parser.error(f'could not create config file ({exc})')
|
||||
|
||||
try:
|
||||
with open(str(new_directory / 'bot.py'), 'w', encoding='utf-8') as fp:
|
||||
base = 'Bot' if not args.sharded else 'AutoShardedBot'
|
||||
fp.write(bot_template.format(base=base, prefix=args.prefix))
|
||||
except OSError as exc:
|
||||
parser.error('could not create bot file ({})'.format(exc))
|
||||
parser.error(f'could not create bot file ({exc})')
|
||||
|
||||
if not args.no_git:
|
||||
try:
|
||||
with open(str(new_directory / '.gitignore'), 'w', encoding='utf-8') as fp:
|
||||
fp.write(gitignore_template)
|
||||
except OSError as exc:
|
||||
print('warning: could not create .gitignore file ({})'.format(exc))
|
||||
print(f'warning: could not create .gitignore file ({exc})')
|
||||
|
||||
print('successfully made bot at', new_directory)
|
||||
|
||||
@ -238,7 +233,7 @@ def newcog(parser, args):
|
||||
try:
|
||||
cog_dir.mkdir(exist_ok=True)
|
||||
except OSError as exc:
|
||||
print('warning: could not create cogs directory ({})'.format(exc))
|
||||
print(f'warning: could not create cogs directory ({exc})')
|
||||
|
||||
directory = cog_dir / to_path(parser, args.name)
|
||||
directory = directory.with_suffix('.py')
|
||||
@ -257,12 +252,12 @@ def newcog(parser, args):
|
||||
name = name.title()
|
||||
|
||||
if args.display_name:
|
||||
attrs += ', name="{}"'.format(args.display_name)
|
||||
attrs += f', name="{args.display_name}"'
|
||||
if args.hide_commands:
|
||||
attrs += ', command_attrs=dict(hidden=True)'
|
||||
fp.write(cog_template.format(name=name, extra=extra, attrs=attrs))
|
||||
except OSError as exc:
|
||||
parser.error('could not create cog file ({})'.format(exc))
|
||||
parser.error(f'could not create cog file ({exc})')
|
||||
else:
|
||||
print('successfully made cog at', directory)
|
||||
|
||||
|
136
discord/abc.py
136
discord/abc.py
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,10 +22,12 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import abc
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import copy
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING, Optional, Protocol, runtime_checkable
|
||||
|
||||
from .iterators import HistoryIterator
|
||||
from .context_managers import Typing
|
||||
@ -41,13 +41,31 @@ from .file import File
|
||||
from .voice_client import VoiceClient, VoiceProtocol
|
||||
from . import utils
|
||||
|
||||
__all__ = (
|
||||
'Snowflake',
|
||||
'User',
|
||||
'PrivateChannel',
|
||||
'GuildChannel',
|
||||
'Messageable',
|
||||
'Connectable',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datetime import datetime
|
||||
|
||||
from .user import ClientUser
|
||||
|
||||
|
||||
class _Undefined:
|
||||
def __repr__(self):
|
||||
return 'see-below'
|
||||
|
||||
|
||||
_undefined = _Undefined()
|
||||
|
||||
class Snowflake(metaclass=abc.ABCMeta):
|
||||
|
||||
@runtime_checkable
|
||||
class Snowflake(Protocol):
|
||||
"""An ABC that details the common operations on a Discord model.
|
||||
|
||||
Almost all :ref:`Discord models <discord_api_models>` meet this
|
||||
@ -62,27 +80,16 @@ class Snowflake(metaclass=abc.ABCMeta):
|
||||
The model's unique ID.
|
||||
"""
|
||||
__slots__ = ()
|
||||
id: int
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def created_at(self):
|
||||
""":class:`datetime.datetime`: Returns the model's creation time as a naive datetime in UTC."""
|
||||
def created_at(self) -> datetime:
|
||||
""":class:`datetime.datetime`: Returns the model's creation time as an aware datetime in UTC."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, C):
|
||||
if cls is Snowflake:
|
||||
mro = C.__mro__
|
||||
for attr in ('created_at', 'id'):
|
||||
for base in mro:
|
||||
if attr in base.__dict__:
|
||||
break
|
||||
else:
|
||||
return NotImplemented
|
||||
return True
|
||||
return NotImplemented
|
||||
|
||||
class User(metaclass=abc.ABCMeta):
|
||||
@runtime_checkable
|
||||
class User(Snowflake, Protocol):
|
||||
"""An ABC that details the common operations on a Discord user.
|
||||
|
||||
The following implement this ABC:
|
||||
@ -106,35 +113,24 @@ class User(metaclass=abc.ABCMeta):
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
name: str
|
||||
discriminator: str
|
||||
avatar: Optional[str]
|
||||
bot: bool
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def display_name(self):
|
||||
def display_name(self) -> str:
|
||||
""":class:`str`: Returns the user's display name."""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def mention(self):
|
||||
def mention(self) -> str:
|
||||
""":class:`str`: Returns a string that allows you to mention the given user."""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, C):
|
||||
if cls is User:
|
||||
if Snowflake.__subclasshook__(C) is NotImplemented:
|
||||
return NotImplemented
|
||||
|
||||
mro = C.__mro__
|
||||
for attr in ('display_name', 'mention', 'name', 'avatar', 'discriminator', 'bot'):
|
||||
for base in mro:
|
||||
if attr in base.__dict__:
|
||||
break
|
||||
else:
|
||||
return NotImplemented
|
||||
return True
|
||||
return NotImplemented
|
||||
|
||||
class PrivateChannel(metaclass=abc.ABCMeta):
|
||||
@runtime_checkable
|
||||
class PrivateChannel(Snowflake, Protocol):
|
||||
"""An ABC that details the common operations on a private Discord channel.
|
||||
|
||||
The following implement this ABC:
|
||||
@ -151,18 +147,8 @@ class PrivateChannel(metaclass=abc.ABCMeta):
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, C):
|
||||
if cls is PrivateChannel:
|
||||
if Snowflake.__subclasshook__(C) is NotImplemented:
|
||||
return NotImplemented
|
||||
me: ClientUser
|
||||
|
||||
mro = C.__mro__
|
||||
for base in mro:
|
||||
if 'me' in base.__dict__:
|
||||
return True
|
||||
return NotImplemented
|
||||
return NotImplemented
|
||||
|
||||
class _Overwrites:
|
||||
__slots__ = ('id', 'allow', 'deny', 'type')
|
||||
@ -181,7 +167,8 @@ class _Overwrites:
|
||||
'type': self.type,
|
||||
}
|
||||
|
||||
class GuildChannel:
|
||||
|
||||
class GuildChannel(Protocol):
|
||||
"""An ABC that details the common operations on a Discord guild channel.
|
||||
|
||||
The following implement this ABC:
|
||||
@ -193,6 +180,11 @@ class GuildChannel:
|
||||
|
||||
This ABC must also implement :class:`~discord.abc.Snowflake`.
|
||||
|
||||
Note
|
||||
----
|
||||
This ABC is not decorated with :func:`typing.runtime_checkable`, so will fail :func:`isinstance`/:func:`issubclass`
|
||||
checks.
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
name: :class:`str`
|
||||
@ -365,7 +357,7 @@ class GuildChannel:
|
||||
@property
|
||||
def mention(self):
|
||||
""":class:`str`: The string that allows you to mention the channel."""
|
||||
return '<#%s>' % self.id
|
||||
return f'<#{self.id}>'
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
@ -832,14 +824,13 @@ class GuildChannel:
|
||||
lock_permissions = kwargs.get('sync_permissions', False)
|
||||
reason = kwargs.get('reason')
|
||||
for index, channel in enumerate(channels):
|
||||
d = { 'id': channel.id, 'position': index }
|
||||
d = {'id': channel.id, 'position': index}
|
||||
if parent_id is not ... and channel.id == self.id:
|
||||
d.update(parent_id=parent_id, lock_permissions=lock_permissions)
|
||||
payload.append(d)
|
||||
|
||||
await self._state.http.bulk_channel_update(self.guild.id, payload, reason=reason)
|
||||
|
||||
|
||||
async def create_invite(self, *, reason=None, **fields):
|
||||
"""|coro|
|
||||
|
||||
@ -914,7 +905,8 @@ class GuildChannel:
|
||||
|
||||
return result
|
||||
|
||||
class Messageable(metaclass=abc.ABCMeta):
|
||||
|
||||
class Messageable(Protocol):
|
||||
"""An ABC that details the common operations on a model that can send messages.
|
||||
|
||||
The following implement this ABC:
|
||||
@ -925,11 +917,16 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
- :class:`~discord.User`
|
||||
- :class:`~discord.Member`
|
||||
- :class:`~discord.ext.commands.Context`
|
||||
|
||||
|
||||
Note
|
||||
----
|
||||
This ABC is not decorated with :func:`typing.runtime_checkable`, so will fail :func:`isinstance`/:func:`issubclass`
|
||||
checks.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
@abc.abstractmethod
|
||||
async def _get_channel(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@ -1072,8 +1069,8 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
f.close()
|
||||
else:
|
||||
data = await state.http.send_message(channel.id, content, tts=tts, embed=embed,
|
||||
nonce=nonce, allowed_mentions=allowed_mentions,
|
||||
message_reference=reference)
|
||||
nonce=nonce, allowed_mentions=allowed_mentions,
|
||||
message_reference=reference)
|
||||
|
||||
ret = state.create_message(channel=channel, data=data)
|
||||
if delete_after is not None:
|
||||
@ -1198,13 +1195,16 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
that this would make it a slow operation.
|
||||
before: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]]
|
||||
Retrieve messages before this date or message.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
after: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]]
|
||||
Retrieve messages after this date or message.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
around: Optional[Union[:class:`~discord.abc.Snowflake`, :class:`datetime.datetime`]]
|
||||
Retrieve messages around this date or message.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
When using this argument, the maximum limit is 101. Note that if the limit is an
|
||||
even number then this will return at most limit + 1 messages.
|
||||
oldest_first: Optional[:class:`bool`]
|
||||
@ -1225,21 +1225,25 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
"""
|
||||
return HistoryIterator(self, limit=limit, before=before, after=after, around=around, oldest_first=oldest_first)
|
||||
|
||||
class Connectable(metaclass=abc.ABCMeta):
|
||||
|
||||
class Connectable(Protocol):
|
||||
"""An ABC that details the common operations on a channel that can
|
||||
connect to a voice server.
|
||||
|
||||
The following implement this ABC:
|
||||
|
||||
- :class:`~discord.VoiceChannel`
|
||||
|
||||
Note
|
||||
----
|
||||
This ABC is not decorated with :func:`typing.runtime_checkable`, so will fail :func:`isinstance`/:func:`issubclass`
|
||||
checks.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_voice_client_key(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_voice_state_pair(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@ -1298,6 +1302,6 @@ class Connectable(metaclass=abc.ABCMeta):
|
||||
except Exception:
|
||||
# we don't care if disconnect failed because connection failed
|
||||
pass
|
||||
raise # re-raise
|
||||
raise # re-raise
|
||||
|
||||
return voice
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -187,7 +185,10 @@ class Activity(BaseActivity):
|
||||
self.flags = kwargs.pop('flags', 0)
|
||||
self.sync_id = kwargs.pop('sync_id', None)
|
||||
self.session_id = kwargs.pop('session_id', None)
|
||||
self.type = try_enum(ActivityType, kwargs.pop('type', -1))
|
||||
|
||||
activity_type = kwargs.pop('type', -1)
|
||||
self.type = activity_type if isinstance(activity_type, ActivityType) else try_enum(ActivityType, activity_type)
|
||||
|
||||
emoji = kwargs.pop('emoji', None)
|
||||
if emoji is not None:
|
||||
self.emoji = PartialEmoji.from_dict(emoji)
|
||||
@ -196,16 +197,16 @@ class Activity(BaseActivity):
|
||||
|
||||
def __repr__(self):
|
||||
attrs = (
|
||||
'type',
|
||||
'name',
|
||||
'url',
|
||||
'details',
|
||||
'application_id',
|
||||
'session_id',
|
||||
'emoji',
|
||||
('type', self.type),
|
||||
('name', self.name),
|
||||
('url', self.url),
|
||||
('details', self.details),
|
||||
('application_id', self.application_id),
|
||||
('session_id', self.session_id),
|
||||
('emoji', self.emoji),
|
||||
)
|
||||
mapped = ' '.join('%s=%r' % (attr, getattr(self, attr)) for attr in attrs)
|
||||
return '<Activity %s>' % mapped
|
||||
inner = ' '.join('%s=%r' % t for t in attrs)
|
||||
return f'<Activity {inner}>'
|
||||
|
||||
def to_dict(self):
|
||||
ret = {}
|
||||
@ -227,17 +228,21 @@ class Activity(BaseActivity):
|
||||
def start(self):
|
||||
"""Optional[:class:`datetime.datetime`]: When the user started doing this activity in UTC, if applicable."""
|
||||
try:
|
||||
return datetime.datetime.utcfromtimestamp(self.timestamps['start'] / 1000)
|
||||
timestamp = self.timestamps['start'] / 1000
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
"""Optional[:class:`datetime.datetime`]: When the user will stop doing this activity in UTC, if applicable."""
|
||||
try:
|
||||
return datetime.datetime.utcfromtimestamp(self.timestamps['end'] / 1000)
|
||||
timestamp = self.timestamps['end'] / 1000
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
@property
|
||||
def large_image_url(self):
|
||||
@ -250,7 +255,7 @@ class Activity(BaseActivity):
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return Asset.BASE + '/app-assets/{0}/{1}.png'.format(self.application_id, large_image)
|
||||
return Asset.BASE + f'/app-assets/{self.application_id}/{large_image}.png'
|
||||
|
||||
@property
|
||||
def small_image_url(self):
|
||||
@ -263,7 +268,7 @@ class Activity(BaseActivity):
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return Asset.BASE + '/app-assets/{0}/{1}.png'.format(self.application_id, small_image)
|
||||
return Asset.BASE + f'/app-assets/{self.application_id}/{small_image}.png'
|
||||
@property
|
||||
def large_image_text(self):
|
||||
"""Optional[:class:`str`]: Returns the large image asset hover text of this activity if applicable."""
|
||||
@ -302,10 +307,6 @@ class Game(BaseActivity):
|
||||
-----------
|
||||
name: :class:`str`
|
||||
The game's name.
|
||||
start: Optional[:class:`datetime.datetime`]
|
||||
A naive UTC timestamp representing when the game started. Keyword-only parameter. Ignored for bots.
|
||||
end: Optional[:class:`datetime.datetime`]
|
||||
A naive UTC timestamp representing when the game ends. Keyword-only parameter. Ignored for bots.
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
@ -322,20 +323,12 @@ class Game(BaseActivity):
|
||||
try:
|
||||
timestamps = extra['timestamps']
|
||||
except KeyError:
|
||||
self._extract_timestamp(extra, 'start')
|
||||
self._extract_timestamp(extra, 'end')
|
||||
self._start = 0
|
||||
self._end = 0
|
||||
else:
|
||||
self._start = timestamps.get('start', 0)
|
||||
self._end = timestamps.get('end', 0)
|
||||
|
||||
def _extract_timestamp(self, data, key):
|
||||
try:
|
||||
dt = data[key]
|
||||
except KeyError:
|
||||
setattr(self, '_' + key, 0)
|
||||
else:
|
||||
setattr(self, '_' + key, dt.timestamp() * 1000.0)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
""":class:`ActivityType`: Returns the game's type. This is for compatibility with :class:`Activity`.
|
||||
@ -348,21 +341,21 @@ class Game(BaseActivity):
|
||||
def start(self):
|
||||
"""Optional[:class:`datetime.datetime`]: When the user started playing this game in UTC, if applicable."""
|
||||
if self._start:
|
||||
return datetime.datetime.utcfromtimestamp(self._start / 1000)
|
||||
return datetime.datetime.utcfromtimestamp(self._start / 1000).replace(tzinfo=datetime.timezone.utc)
|
||||
return None
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
"""Optional[:class:`datetime.datetime`]: When the user will stop playing this game in UTC, if applicable."""
|
||||
if self._end:
|
||||
return datetime.datetime.utcfromtimestamp(self._end / 1000)
|
||||
return datetime.datetime.utcfromtimestamp(self._end / 1000).replace(tzinfo=datetime.timezone.utc)
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return str(self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Game name={0.name!r}>'.format(self)
|
||||
return f'<Game name={self.name!r}>'
|
||||
|
||||
def to_dict(self):
|
||||
timestamps = {}
|
||||
@ -455,7 +448,7 @@ class Streaming(BaseActivity):
|
||||
return str(self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Streaming name={0.name!r}>'.format(self)
|
||||
return f'<Streaming name={self.name!r}>'
|
||||
|
||||
@property
|
||||
def twitch_name(self):
|
||||
@ -700,7 +693,7 @@ class CustomActivity(BaseActivity):
|
||||
elif isinstance(emoji, PartialEmoji):
|
||||
self.emoji = emoji
|
||||
else:
|
||||
raise TypeError('Expected str, PartialEmoji, or None, received {0!r} instead.'.format(type(emoji)))
|
||||
raise TypeError(f'Expected str, PartialEmoji, or None, received {type(emoji)!r} instead.')
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
@ -739,7 +732,7 @@ class CustomActivity(BaseActivity):
|
||||
def __str__(self):
|
||||
if self.emoji:
|
||||
if self.name:
|
||||
return '%s %s' % (self.emoji, self.name)
|
||||
return f'{self.emoji} {self.name}'
|
||||
return str(self.emoji)
|
||||
else:
|
||||
return str(self.name)
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -29,6 +27,9 @@ from .user import User
|
||||
from .asset import Asset
|
||||
from .team import Team
|
||||
|
||||
__all__ = (
|
||||
'AppInfo',
|
||||
)
|
||||
|
||||
class AppInfo:
|
||||
"""Represents the application info for the bot provided by Discord.
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -29,6 +27,10 @@ from .errors import DiscordException
|
||||
from .errors import InvalidArgument
|
||||
from . import utils
|
||||
|
||||
__all__ = (
|
||||
'Asset',
|
||||
)
|
||||
|
||||
VALID_STATIC_FORMATS = frozenset({"jpeg", "jpg", "webp", "png"})
|
||||
VALID_AVATAR_FORMATS = VALID_STATIC_FORMATS | {"gif"}
|
||||
|
||||
@ -74,11 +76,11 @@ class Asset:
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
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))
|
||||
raise InvalidArgument(f"format must be None or one of {VALID_AVATAR_FORMATS}")
|
||||
if format == "gif" and not user.is_avatar_animated():
|
||||
raise InvalidArgument("non animated avatars 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))
|
||||
raise InvalidArgument(f"static_format must be one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
if user.avatar is None:
|
||||
return user.default_avatar_url
|
||||
@ -96,7 +98,7 @@ class Asset:
|
||||
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))
|
||||
raise InvalidArgument(f"format must be None or one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
url = '/{0}-icons/{1.id}/{1.icon}.{2}?size={3}'.format(path, object, format, size)
|
||||
return cls(state, url)
|
||||
@ -109,7 +111,7 @@ class Asset:
|
||||
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))
|
||||
raise InvalidArgument(f"format must be None or one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
url = '/app-assets/{0.id}/store/{0.cover_image}.{1}?size={2}'.format(obj, format, size)
|
||||
return cls(state, url)
|
||||
@ -119,7 +121,7 @@ class Asset:
|
||||
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 one of {}".format(VALID_STATIC_FORMATS))
|
||||
raise InvalidArgument(f"format must be one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
if hash is None:
|
||||
return cls(state)
|
||||
@ -132,11 +134,11 @@ class Asset:
|
||||
if not utils.valid_icon_size(size):
|
||||
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
|
||||
if format is not None and format not in VALID_AVATAR_FORMATS:
|
||||
raise InvalidArgument("format must be one of {}".format(VALID_AVATAR_FORMATS))
|
||||
raise InvalidArgument(f"format must be one of {VALID_AVATAR_FORMATS}")
|
||||
if format == "gif" and not guild.is_icon_animated():
|
||||
raise InvalidArgument("non animated guild icons 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))
|
||||
raise InvalidArgument(f"static_format must be one of {VALID_STATIC_FORMATS}")
|
||||
|
||||
if guild.icon is None:
|
||||
return cls(state)
|
||||
@ -156,15 +158,15 @@ class Asset:
|
||||
@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))
|
||||
raise InvalidArgument(f"format must be None or one of {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))
|
||||
raise InvalidArgument(f"static_format must be one of {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))
|
||||
return cls(state, f'/emojis/{emoji.id}.{format}')
|
||||
|
||||
def __str__(self):
|
||||
return self.BASE + self._url if self._url is not None else ''
|
||||
@ -178,7 +180,7 @@ class Asset:
|
||||
return self._url is not None
|
||||
|
||||
def __repr__(self):
|
||||
return '<Asset url={0._url!r}>'.format(self)
|
||||
return f'<Asset url={self._url!r}>'
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Asset) and self._url == other._url
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -31,6 +29,12 @@ from .colour import Colour
|
||||
from .invite import Invite
|
||||
from .mixins import Hashable
|
||||
|
||||
__all__ = (
|
||||
'AuditLogDiff',
|
||||
'AuditLogChanges',
|
||||
'AuditLogEntry',
|
||||
)
|
||||
|
||||
def _transform_verification_level(entry, data):
|
||||
return enums.try_enum(enums.VerificationLevel, data)
|
||||
|
||||
@ -94,7 +98,7 @@ class AuditLogDiff:
|
||||
|
||||
def __repr__(self):
|
||||
values = ' '.join('%s=%r' % item for item in self.__dict__.items())
|
||||
return '<AuditLogDiff %s>' % values
|
||||
return f'<AuditLogDiff {values}>'
|
||||
|
||||
class AuditLogChanges:
|
||||
TRANSFORMERS = {
|
||||
@ -166,7 +170,7 @@ class AuditLogChanges:
|
||||
self.before.color = self.before.colour
|
||||
|
||||
def __repr__(self):
|
||||
return '<AuditLogChanges before=%r after=%r>' % (self.before, self.after)
|
||||
return f'<AuditLogChanges before={self.before!r} after={self.after!r}>'
|
||||
|
||||
def _handle_role(self, first, second, entry, elem):
|
||||
if not hasattr(first, 'roles'):
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -27,6 +25,10 @@ DEALINGS IN THE SOFTWARE.
|
||||
import time
|
||||
import random
|
||||
|
||||
__all__ = (
|
||||
'ExponentialBackoff',
|
||||
)
|
||||
|
||||
class ExponentialBackoff:
|
||||
"""An implementation of the exponential backoff algorithm
|
||||
|
||||
|
176
discord/calls.py
176
discord/calls.py
@ -1,176 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present 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.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
from . import utils
|
||||
from .enums import VoiceRegion, try_enum
|
||||
from .member import VoiceState
|
||||
|
||||
class CallMessage:
|
||||
"""Represents a group call message from Discord.
|
||||
|
||||
This is only received in cases where the message type is equivalent to
|
||||
:attr:`MessageType.call`.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
ended_timestamp: Optional[:class:`datetime.datetime`]
|
||||
A naive UTC datetime object that represents the time that the call has ended.
|
||||
participants: List[:class:`User`]
|
||||
The list of users that are participating in this call.
|
||||
message: :class:`Message`
|
||||
The message associated with this call message.
|
||||
"""
|
||||
|
||||
def __init__(self, message, **kwargs):
|
||||
self.message = message
|
||||
self.ended_timestamp = utils.parse_time(kwargs.get('ended_timestamp'))
|
||||
self.participants = kwargs.get('participants')
|
||||
|
||||
@property
|
||||
def call_ended(self):
|
||||
""":class:`bool`: Indicates if the call has ended.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
"""
|
||||
return self.ended_timestamp is not None
|
||||
|
||||
@property
|
||||
def channel(self):
|
||||
r""":class:`GroupChannel`\: The private channel associated with this message.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
"""
|
||||
return self.message.channel
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
"""Queries the duration of the call.
|
||||
|
||||
If the call has not ended then the current duration will
|
||||
be returned.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Returns
|
||||
---------
|
||||
:class:`datetime.timedelta`
|
||||
The timedelta object representing the duration.
|
||||
"""
|
||||
if self.ended_timestamp is None:
|
||||
return datetime.datetime.utcnow() - self.message.created_at
|
||||
else:
|
||||
return self.ended_timestamp - self.message.created_at
|
||||
|
||||
class GroupCall:
|
||||
"""Represents the actual group call from Discord.
|
||||
|
||||
This is accompanied with a :class:`CallMessage` denoting the information.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
call: :class:`CallMessage`
|
||||
The call message associated with this group call.
|
||||
unavailable: :class:`bool`
|
||||
Denotes if this group call is unavailable.
|
||||
ringing: List[:class:`User`]
|
||||
A list of users that are currently being rung to join the call.
|
||||
region: :class:`VoiceRegion`
|
||||
The guild region the group call is being hosted on.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.call = kwargs.get('call')
|
||||
self.unavailable = kwargs.get('unavailable')
|
||||
self._voice_states = {}
|
||||
|
||||
for state in kwargs.get('voice_states', []):
|
||||
self._update_voice_state(state)
|
||||
|
||||
self._update(**kwargs)
|
||||
|
||||
def _update(self, **kwargs):
|
||||
self.region = try_enum(VoiceRegion, kwargs.get('region'))
|
||||
lookup = {u.id: u for u in self.call.channel.recipients}
|
||||
me = self.call.channel.me
|
||||
lookup[me.id] = me
|
||||
self.ringing = list(filter(None, map(lookup.get, kwargs.get('ringing', []))))
|
||||
|
||||
def _update_voice_state(self, data):
|
||||
user_id = int(data['user_id'])
|
||||
# left the voice channel?
|
||||
if data['channel_id'] is None:
|
||||
self._voice_states.pop(user_id, None)
|
||||
else:
|
||||
self._voice_states[user_id] = VoiceState(data=data, channel=self.channel)
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
"""List[:class:`User`]: A property that returns all users that are currently in this call.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
"""
|
||||
ret = [u for u in self.channel.recipients if self.voice_state_for(u) is not None]
|
||||
me = self.channel.me
|
||||
if self.voice_state_for(me) is not None:
|
||||
ret.append(me)
|
||||
|
||||
return ret
|
||||
|
||||
@property
|
||||
def channel(self):
|
||||
r""":class:`GroupChannel`\: Returns the channel the group call is in.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
"""
|
||||
return self.call.channel
|
||||
|
||||
@utils.deprecated()
|
||||
def voice_state_for(self, user):
|
||||
"""Retrieves the :class:`VoiceState` for a specified :class:`User`.
|
||||
|
||||
If the :class:`User` has no voice state then this function returns
|
||||
``None``.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Parameters
|
||||
------------
|
||||
user: :class:`User`
|
||||
The user to retrieve the voice state for.
|
||||
|
||||
Returns
|
||||
--------
|
||||
Optional[:class:`VoiceState`]
|
||||
The voice state associated with this user.
|
||||
"""
|
||||
|
||||
return self._voice_states.get(user.id)
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -115,7 +113,8 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
('news', self.is_news()),
|
||||
('category_id', self.category_id)
|
||||
]
|
||||
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
|
||||
joined = ' '.join('%s=%r' % t for t in attrs)
|
||||
return f'<{self.__class__.__name__} {joined}>'
|
||||
|
||||
def _update(self, guild, data):
|
||||
self.guild = guild
|
||||
@ -319,10 +318,6 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
account). The :attr:`~Permissions.read_message_history` permission is
|
||||
also needed to retrieve message history.
|
||||
|
||||
Internally, this employs a different number of strategies depending
|
||||
on the conditions met such as if a bulk delete is possible or if
|
||||
the account is a user bot or not.
|
||||
|
||||
Examples
|
||||
---------
|
||||
|
||||
@ -353,8 +348,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
bulk: :class:`bool`
|
||||
If ``True``, use bulk delete. Setting this to ``False`` is useful for mass-deleting
|
||||
a bot's own messages without :attr:`Permissions.manage_messages`. When ``True``, will
|
||||
fall back to single delete if current account is a user bot (now deprecated), or if messages are
|
||||
older than two weeks.
|
||||
fall back to single delete if messages are older than two weeks.
|
||||
|
||||
Raises
|
||||
-------
|
||||
@ -377,7 +371,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
count = 0
|
||||
|
||||
minimum_time = int((time.time() - 14 * 24 * 60 * 60) * 1000.0 - 1420070400000) << 22
|
||||
strategy = self.delete_messages if self._state.is_bot and bulk else _single_delete_strategy
|
||||
strategy = self.delete_messages if bulk else _single_delete_strategy
|
||||
|
||||
while True:
|
||||
try:
|
||||
@ -675,7 +669,8 @@ class VoiceChannel(VocalGuildChannel):
|
||||
('user_limit', self.user_limit),
|
||||
('category_id', self.category_id)
|
||||
]
|
||||
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
|
||||
joined = ' '.join('%s=%r' % t for t in attrs)
|
||||
return f'<{self.__class__.__name__} {joined}>'
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
@ -798,7 +793,8 @@ class StageChannel(VocalGuildChannel):
|
||||
('user_limit', self.user_limit),
|
||||
('category_id', self.category_id)
|
||||
]
|
||||
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
|
||||
joined = ' '.join('%s=%r' % t for t in attrs)
|
||||
return f'<{self.__class__.__name__} {joined}>'
|
||||
|
||||
def _update(self, guild, data):
|
||||
super()._update(guild, data)
|
||||
@ -1226,7 +1222,7 @@ class DMChannel(discord.abc.Messageable, Hashable):
|
||||
return self
|
||||
|
||||
def __str__(self):
|
||||
return 'Direct Message with %s' % self.recipient
|
||||
return f'Direct Message with {self.recipient}'
|
||||
|
||||
def __repr__(self):
|
||||
return '<DMChannel id={0.id} recipient={0.recipient!r}>'.format(self)
|
||||
@ -1448,95 +1444,6 @@ class GroupChannel(discord.abc.Messageable, Hashable):
|
||||
|
||||
return base
|
||||
|
||||
@utils.deprecated()
|
||||
async def add_recipients(self, *recipients):
|
||||
r"""|coro|
|
||||
|
||||
Adds recipients to this group.
|
||||
|
||||
A group can only have a maximum of 10 members.
|
||||
Attempting to add more ends up in an exception. To
|
||||
add a recipient to the group, you must have a relationship
|
||||
with the user of type :attr:`RelationshipType.friend`.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
\*recipients: :class:`User`
|
||||
An argument list of users to add to this group.
|
||||
|
||||
Raises
|
||||
-------
|
||||
HTTPException
|
||||
Adding a recipient to this group failed.
|
||||
"""
|
||||
|
||||
# TODO: wait for the corresponding WS event
|
||||
|
||||
req = self._state.http.add_group_recipient
|
||||
for recipient in recipients:
|
||||
await req(self.id, recipient.id)
|
||||
|
||||
@utils.deprecated()
|
||||
async def remove_recipients(self, *recipients):
|
||||
r"""|coro|
|
||||
|
||||
Removes recipients from this group.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
\*recipients: :class:`User`
|
||||
An argument list of users to remove from this group.
|
||||
|
||||
Raises
|
||||
-------
|
||||
HTTPException
|
||||
Removing a recipient from this group failed.
|
||||
"""
|
||||
|
||||
# TODO: wait for the corresponding WS event
|
||||
|
||||
req = self._state.http.remove_group_recipient
|
||||
for recipient in recipients:
|
||||
await req(self.id, recipient.id)
|
||||
|
||||
@utils.deprecated()
|
||||
async def edit(self, **fields):
|
||||
"""|coro|
|
||||
|
||||
Edits the group.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
name: Optional[:class:`str`]
|
||||
The new name to change the group to.
|
||||
Could be ``None`` to remove the name.
|
||||
icon: Optional[:class:`bytes`]
|
||||
A :term:`py:bytes-like object` representing the new icon.
|
||||
Could be ``None`` to remove the icon.
|
||||
|
||||
Raises
|
||||
-------
|
||||
HTTPException
|
||||
Editing the group failed.
|
||||
"""
|
||||
|
||||
try:
|
||||
icon_bytes = fields['icon']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if icon_bytes is not None:
|
||||
fields['icon'] = utils._bytes_to_base64_data(icon_bytes)
|
||||
|
||||
data = await self._state.http.edit_group(self.id, **fields)
|
||||
self._update_group(data)
|
||||
|
||||
async def leave(self):
|
||||
"""|coro|
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -34,7 +32,7 @@ import re
|
||||
|
||||
import aiohttp
|
||||
|
||||
from .user import User, Profile
|
||||
from .user import User
|
||||
from .invite import Invite
|
||||
from .template import Template
|
||||
from .widget import Widget
|
||||
@ -57,16 +55,14 @@ from .iterators import GuildIterator
|
||||
from .appinfo import AppInfo
|
||||
from .colour import Color, Colour
|
||||
|
||||
__all__ = (
|
||||
'Client',
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def _cancel_tasks(loop):
|
||||
try:
|
||||
task_retriever = asyncio.Task.all_tasks
|
||||
except AttributeError:
|
||||
# future proofing for 3.9 I guess
|
||||
task_retriever = asyncio.all_tasks
|
||||
|
||||
tasks = {t for t in task_retriever(loop=loop) if not t.done()}
|
||||
tasks = {t for t in asyncio.all_tasks(loop=loop) if not t.done()}
|
||||
|
||||
if not tasks:
|
||||
return
|
||||
@ -91,28 +87,11 @@ def _cancel_tasks(loop):
|
||||
def _cleanup_loop(loop):
|
||||
try:
|
||||
_cancel_tasks(loop)
|
||||
if sys.version_info >= (3, 6):
|
||||
loop.run_until_complete(loop.shutdown_asyncgens())
|
||||
loop.run_until_complete(loop.shutdown_asyncgens())
|
||||
finally:
|
||||
log.info('Closing the event loop.')
|
||||
loop.close()
|
||||
|
||||
class _ClientEventTask(asyncio.Task):
|
||||
def __init__(self, original_coro, event_name, coro, *, loop):
|
||||
super().__init__(coro, loop=loop)
|
||||
self.__event_name = event_name
|
||||
self.__original_coro = original_coro
|
||||
|
||||
def __repr__(self):
|
||||
info = [
|
||||
('state', self._state.lower()),
|
||||
('event', self.__event_name),
|
||||
('coro', repr(self.__original_coro)),
|
||||
]
|
||||
if self._exception is not None:
|
||||
info.append(('exception', repr(self._exception)))
|
||||
return '<ClientEventTask {}>'.format(' '.join('%s=%s' % t for t in info))
|
||||
|
||||
class Client:
|
||||
r"""Represents a client connection that connects to Discord.
|
||||
This class is used to interact with the Discord WebSocket and API.
|
||||
@ -141,6 +120,8 @@ class Client:
|
||||
Integer starting at ``0`` and less than :attr:`.shard_count`.
|
||||
shard_count: Optional[:class:`int`]
|
||||
The total number of shards.
|
||||
application_id: :class:`int`
|
||||
The client's application ID.
|
||||
intents: :class:`Intents`
|
||||
The intents that you want to enable for the session. This is a way of
|
||||
disabling and enabling certain gateway events from triggering and being sent.
|
||||
@ -344,10 +325,7 @@ class Client:
|
||||
|
||||
def _get_state(self, **options):
|
||||
return ConnectionState(dispatch=self.dispatch, handlers=self._handlers,
|
||||
hooks=self._hooks, syncer=self._syncer, http=self.http, loop=self.loop, **options)
|
||||
|
||||
async def _syncer(self, guilds):
|
||||
await self.ws.request_sync(guilds)
|
||||
hooks=self._hooks, http=self.http, loop=self.loop, **options)
|
||||
|
||||
def _handle_ready(self):
|
||||
self._ready.set()
|
||||
@ -415,6 +393,16 @@ class Client:
|
||||
"""
|
||||
return self._connection.voice_clients
|
||||
|
||||
@property
|
||||
def application_id(self):
|
||||
"""Optional[:class:`int`]: The client's application ID.
|
||||
|
||||
If this is not passed via ``__init__`` then this is retrieved
|
||||
through the gateway when an event contains the data. Usually
|
||||
after :func:`on_connect` is called.
|
||||
"""
|
||||
return self._connection.application_id
|
||||
|
||||
def is_ready(self):
|
||||
""":class:`bool`: Specifies if the client's internal cache is ready for use."""
|
||||
return self._ready.is_set()
|
||||
@ -433,7 +421,7 @@ class Client:
|
||||
def _schedule_event(self, coro, event_name, *args, **kwargs):
|
||||
wrapped = self._run_event(coro, event_name, *args, **kwargs)
|
||||
# Schedules the task
|
||||
return _ClientEventTask(original_coro=coro, event_name=event_name, coro=wrapped, loop=self.loop)
|
||||
return asyncio.create_task(wrapped, name=f'discord.py: {event_name}')
|
||||
|
||||
def dispatch(self, event, *args, **kwargs):
|
||||
log.debug('Dispatching event %s', event)
|
||||
@ -484,43 +472,9 @@ class Client:
|
||||
overridden to have a different implementation.
|
||||
Check :func:`~discord.on_error` for more details.
|
||||
"""
|
||||
print('Ignoring exception in {}'.format(event_method), file=sys.stderr)
|
||||
print(f'Ignoring exception in {event_method}', file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
|
||||
@utils.deprecated('Guild.chunk')
|
||||
async def request_offline_members(self, *guilds):
|
||||
r"""|coro|
|
||||
|
||||
Requests previously offline members from the guild to be filled up
|
||||
into the :attr:`.Guild.members` cache. This function is usually not
|
||||
called. It should only be used if you have the ``fetch_offline_members``
|
||||
parameter set to ``False``.
|
||||
|
||||
When the client logs on and connects to the websocket, Discord does
|
||||
not provide the library with offline members if the number of members
|
||||
in the guild is larger than 250. You can check if a guild is large
|
||||
if :attr:`.Guild.large` is ``True``.
|
||||
|
||||
.. warning::
|
||||
|
||||
This method is deprecated. Use :meth:`Guild.chunk` instead.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
\*guilds: :class:`.Guild`
|
||||
An argument list of guilds to request offline members for.
|
||||
|
||||
Raises
|
||||
-------
|
||||
:exc:`.InvalidArgument`
|
||||
If any guild is unavailable in the collection.
|
||||
"""
|
||||
if any(g.unavailable for g in guilds):
|
||||
raise InvalidArgument('An unavailable guild was passed.')
|
||||
|
||||
for guild in guilds:
|
||||
await self._connection.chunk_guild(guild)
|
||||
|
||||
# hooks
|
||||
|
||||
async def _call_before_identify_hook(self, shard_id, *, initial=False):
|
||||
@ -553,7 +507,7 @@ class Client:
|
||||
|
||||
# login state management
|
||||
|
||||
async def login(self, token, *, bot=True):
|
||||
async def login(self, token):
|
||||
"""|coro|
|
||||
|
||||
Logs in the client with the specified credentials.
|
||||
@ -572,11 +526,6 @@ class Client:
|
||||
token: :class:`str`
|
||||
The authentication token. Do not prefix this token with
|
||||
anything as the library will do it for you.
|
||||
bot: :class:`bool`
|
||||
Keyword argument that specifies if the account logging on is a bot
|
||||
token or not.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Raises
|
||||
------
|
||||
@ -589,24 +538,7 @@ class Client:
|
||||
"""
|
||||
|
||||
log.info('logging in using static token')
|
||||
await self.http.static_login(token.strip(), bot=bot)
|
||||
self._connection.is_bot = bot
|
||||
|
||||
@utils.deprecated('Client.close')
|
||||
async def logout(self):
|
||||
"""|coro|
|
||||
|
||||
Logs out of Discord and closes all connections.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This is just an alias to :meth:`close`. If you want
|
||||
to do extraneous cleanup when subclassing, it is suggested
|
||||
to override :meth:`close` instead.
|
||||
"""
|
||||
await self.close()
|
||||
await self.http.static_login(token.strip())
|
||||
|
||||
async def connect(self, *, reconnect=True):
|
||||
"""|coro|
|
||||
@ -727,7 +659,7 @@ class Client:
|
||||
self._connection.clear()
|
||||
self.http.recreate()
|
||||
|
||||
async def start(self, *args, **kwargs):
|
||||
async def start(self, token, *, reconnect=True):
|
||||
"""|coro|
|
||||
|
||||
A shorthand coroutine for :meth:`login` + :meth:`connect`.
|
||||
@ -737,13 +669,7 @@ class Client:
|
||||
TypeError
|
||||
An unexpected keyword argument was received.
|
||||
"""
|
||||
bot = kwargs.pop('bot', True)
|
||||
reconnect = kwargs.pop('reconnect', True)
|
||||
|
||||
if kwargs:
|
||||
raise TypeError("unexpected keyword argument(s) %s" % list(kwargs.keys()))
|
||||
|
||||
await self.login(*args, bot=bot)
|
||||
await self.login(token)
|
||||
await self.connect(reconnect=reconnect)
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
@ -841,7 +767,7 @@ class Client:
|
||||
if value is None or isinstance(value, AllowedMentions):
|
||||
self._connection.allowed_mentions = value
|
||||
else:
|
||||
raise TypeError('allowed_mentions must be AllowedMentions not {0.__class__!r}'.format(value))
|
||||
raise TypeError(f'allowed_mentions must be AllowedMentions not {value.__class__!r}')
|
||||
|
||||
@property
|
||||
def intents(self):
|
||||
@ -940,8 +866,7 @@ class Client:
|
||||
"""
|
||||
|
||||
for guild in self.guilds:
|
||||
for channel in guild.channels:
|
||||
yield channel
|
||||
yield from guild.channels
|
||||
|
||||
def get_all_members(self):
|
||||
"""Returns a generator with every :class:`.Member` the client can see.
|
||||
@ -958,8 +883,7 @@ class Client:
|
||||
A member the client can see.
|
||||
"""
|
||||
for guild in self.guilds:
|
||||
for member in guild.members:
|
||||
yield member
|
||||
yield from guild.members
|
||||
|
||||
# listeners/waiters
|
||||
|
||||
@ -1192,10 +1116,12 @@ class Client:
|
||||
Defaults to ``100``.
|
||||
before: Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`]
|
||||
Retrieves guilds before this date or object.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
after: Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`]
|
||||
Retrieve guilds after this date or object.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
|
||||
Raises
|
||||
------
|
||||
@ -1501,51 +1427,6 @@ class Client:
|
||||
data = await self.http.get_user(user_id)
|
||||
return User(state=self._connection, data=data)
|
||||
|
||||
@utils.deprecated()
|
||||
async def fetch_user_profile(self, user_id):
|
||||
"""|coro|
|
||||
|
||||
Gets an arbitrary user's profile.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Parameters
|
||||
------------
|
||||
user_id: :class:`int`
|
||||
The ID of the user to fetch their profile for.
|
||||
|
||||
Raises
|
||||
-------
|
||||
:exc:`.Forbidden`
|
||||
Not allowed to fetch profiles.
|
||||
:exc:`.HTTPException`
|
||||
Fetching the profile failed.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`.Profile`
|
||||
The profile of the user.
|
||||
"""
|
||||
|
||||
state = self._connection
|
||||
data = await self.http.get_user_profile(user_id)
|
||||
|
||||
def transform(d):
|
||||
return state._get_guild(int(d['id']))
|
||||
|
||||
since = data.get('premium_since')
|
||||
mutual_guilds = list(filter(None, map(transform, data.get('mutual_guilds', []))))
|
||||
user = data['user']
|
||||
return Profile(flags=user.get('flags', 0),
|
||||
premium_since=utils.parse_time(since),
|
||||
mutual_guilds=mutual_guilds,
|
||||
user=User(data=user, state=state),
|
||||
connected_accounts=data['connected_accounts'])
|
||||
|
||||
async def fetch_channel(self, channel_id):
|
||||
"""|coro|
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -27,6 +25,11 @@ DEALINGS IN THE SOFTWARE.
|
||||
import colorsys
|
||||
import random
|
||||
|
||||
__all__ = (
|
||||
'Colour',
|
||||
'Color',
|
||||
)
|
||||
|
||||
class Colour:
|
||||
"""Represents a Discord role colour. This class is similar
|
||||
to a (red, green, blue) :class:`tuple`.
|
||||
@ -61,7 +64,7 @@ class Colour:
|
||||
|
||||
def __init__(self, value):
|
||||
if not isinstance(value, int):
|
||||
raise TypeError('Expected int parameter, received %s instead.' % value.__class__.__name__)
|
||||
raise TypeError(f'Expected int parameter, received {value.__class__.__name__} instead.')
|
||||
|
||||
self.value = value
|
||||
|
||||
@ -75,10 +78,10 @@ class Colour:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __str__(self):
|
||||
return '#{:0>6x}'.format(self.value)
|
||||
return f'#{self.value:0>6x}'
|
||||
|
||||
def __repr__(self):
|
||||
return '<Colour value=%s>' % self.value
|
||||
return f'<Colour value={self.value}>'
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.value)
|
||||
@ -138,7 +141,7 @@ class Colour:
|
||||
Parameters
|
||||
------------
|
||||
seed: Optional[Union[:class:`int`, :class:`str`, :class:`float`, :class:`bytes`, :class:`bytearray`]]
|
||||
The seed to initialize the RNG with. If ``None`` is passed the default RNG is used.
|
||||
The seed to initialize the RNG with. If ``None`` is passed the default RNG is used.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -26,6 +24,10 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import asyncio
|
||||
|
||||
__all__ = (
|
||||
'Typing',
|
||||
)
|
||||
|
||||
def _typing_done_callback(fut):
|
||||
# just retrieve any exception and call it a day
|
||||
try:
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,37 +22,88 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import os
|
||||
from typing import Any, Dict, Final, List, Protocol, TYPE_CHECKING, Type, TypeVar, Union
|
||||
|
||||
from . import utils
|
||||
from .colour import Colour
|
||||
|
||||
__all__ = (
|
||||
'Embed',
|
||||
)
|
||||
|
||||
|
||||
class _EmptyEmbed:
|
||||
def __bool__(self):
|
||||
def __bool__(self) -> bool:
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return 'Embed.Empty'
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
return 0
|
||||
|
||||
EmptyEmbed = _EmptyEmbed()
|
||||
|
||||
EmptyEmbed: Final = _EmptyEmbed()
|
||||
|
||||
|
||||
class EmbedProxy:
|
||||
def __init__(self, layer):
|
||||
def __init__(self, layer: Dict[str, Any]):
|
||||
self.__dict__.update(layer)
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
return len(self.__dict__)
|
||||
|
||||
def __repr__(self):
|
||||
return 'EmbedProxy(%s)' % ', '.join(('%s=%r' % (k, v) for k, v in self.__dict__.items() if not k.startswith('_')))
|
||||
def __repr__(self) -> str:
|
||||
inner = ', '.join((f'{k}={v!r}' for k, v in self.__dict__.items() if not k.startswith('_')))
|
||||
return f'EmbedProxy({inner})'
|
||||
|
||||
def __getattr__(self, attr):
|
||||
def __getattr__(self, attr: str) -> _EmptyEmbed:
|
||||
return EmptyEmbed
|
||||
|
||||
|
||||
E = TypeVar('E', bound='Embed')
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from discord.types.embed import Embed as EmbedData, EmbedType
|
||||
|
||||
T = TypeVar('T')
|
||||
MaybeEmpty = Union[T, _EmptyEmbed]
|
||||
|
||||
class _EmbedFooterProxy(Protocol):
|
||||
text: MaybeEmpty[str]
|
||||
icon_url: MaybeEmpty[str]
|
||||
|
||||
class _EmbedFieldProxy(Protocol):
|
||||
name: MaybeEmpty[str]
|
||||
value: MaybeEmpty[str]
|
||||
inline: bool
|
||||
|
||||
class _EmbedMediaProxy(Protocol):
|
||||
url: MaybeEmpty[str]
|
||||
proxy_url: MaybeEmpty[str]
|
||||
height: MaybeEmpty[int]
|
||||
width: MaybeEmpty[int]
|
||||
|
||||
class _EmbedVideoProxy(Protocol):
|
||||
url: MaybeEmpty[str]
|
||||
height: MaybeEmpty[int]
|
||||
width: MaybeEmpty[int]
|
||||
|
||||
class _EmbedProviderProxy(Protocol):
|
||||
name: MaybeEmpty[str]
|
||||
url: MaybeEmpty[str]
|
||||
|
||||
class _EmbedAuthorProxy(Protocol):
|
||||
name: MaybeEmpty[str]
|
||||
url: MaybeEmpty[str]
|
||||
icon_url: MaybeEmpty[str]
|
||||
proxy_icon_url: MaybeEmpty[str]
|
||||
|
||||
|
||||
class Embed:
|
||||
"""Represents a Discord embed.
|
||||
|
||||
@ -65,6 +114,12 @@ class Embed:
|
||||
Returns the total size of the embed.
|
||||
Useful for checking if it's within the 6000 character limit.
|
||||
|
||||
.. describe:: bool(b)
|
||||
|
||||
Returns whether the embed has any data set.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Certain properties return an ``EmbedProxy``, a type
|
||||
that acts similar to a regular :class:`dict` except using dotted access,
|
||||
e.g. ``embed.author.icon_url``. If the attribute
|
||||
@ -91,7 +146,9 @@ class Embed:
|
||||
The URL of the embed.
|
||||
This can be set during initialisation.
|
||||
timestamp: :class:`datetime.datetime`
|
||||
The timestamp of the embed content. This could be a naive or aware datetime.
|
||||
The timestamp of the embed content. This is an aware datetime.
|
||||
If a naive datetime is passed, it is converted to an aware
|
||||
datetime with the local timezone.
|
||||
colour: Union[:class:`Colour`, :class:`int`]
|
||||
The colour code of the embed. Aliased to ``color`` as well.
|
||||
This can be set during initialisation.
|
||||
@ -100,27 +157,47 @@ class Embed:
|
||||
to denote that the value or attribute is empty.
|
||||
"""
|
||||
|
||||
__slots__ = ('title', 'url', 'type', '_timestamp', '_colour', '_footer',
|
||||
'_image', '_thumbnail', '_video', '_provider', '_author',
|
||||
'_fields', 'description')
|
||||
__slots__ = (
|
||||
'title',
|
||||
'url',
|
||||
'type',
|
||||
'_timestamp',
|
||||
'_colour',
|
||||
'_footer',
|
||||
'_image',
|
||||
'_thumbnail',
|
||||
'_video',
|
||||
'_provider',
|
||||
'_author',
|
||||
'_fields',
|
||||
'description',
|
||||
)
|
||||
|
||||
Empty = EmptyEmbed
|
||||
Empty: Final = EmptyEmbed
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# swap the colour/color aliases
|
||||
try:
|
||||
colour = kwargs['colour']
|
||||
except KeyError:
|
||||
colour = kwargs.get('color', os.getenv("DEFAULT_EMBED_COLOR", default=EmptyEmbed))
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
colour: Union[int, Colour, _EmptyEmbed] = EmptyEmbed,
|
||||
color: Union[int, Colour, _EmptyEmbed] = EmptyEmbed,
|
||||
title: MaybeEmpty[str] = EmptyEmbed,
|
||||
type: EmbedType = 'rich',
|
||||
url: MaybeEmpty[str] = EmptyEmbed,
|
||||
description: MaybeEmpty[str] = EmptyEmbed,
|
||||
timestamp: datetime.datetime = None,
|
||||
):
|
||||
|
||||
if colour is EmptyEmbed and color is EmptyEmbed:
|
||||
colour = os.getenv("DEFAULT_EMBED_COLOR", default=EmptyEmbed)
|
||||
if isinstance(colour, str):
|
||||
colour = int(colour, 16)
|
||||
|
||||
|
||||
else:
|
||||
colour = colour if colour is not EmptyEmbed else color
|
||||
self.colour = colour
|
||||
self.title = kwargs.get('title', EmptyEmbed)
|
||||
self.type = kwargs.get('type', 'rich')
|
||||
self.url = kwargs.get('url', EmptyEmbed)
|
||||
self.description = kwargs.get('description', EmptyEmbed)
|
||||
self.title = title
|
||||
self.type = type
|
||||
self.url = url
|
||||
self.description = description
|
||||
|
||||
if self.title is not EmptyEmbed:
|
||||
self.title = str(self.title)
|
||||
@ -131,15 +208,13 @@ class Embed:
|
||||
if self.url is not EmptyEmbed:
|
||||
self.url = str(self.url)
|
||||
|
||||
try:
|
||||
timestamp = kwargs['timestamp']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if timestamp:
|
||||
if timestamp.tzinfo is None:
|
||||
timestamp = timestamp.astimezone()
|
||||
self.timestamp = timestamp
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data):
|
||||
def from_dict(cls: Type[E], data: EmbedData) -> E:
|
||||
"""Converts a :class:`dict` to a :class:`Embed` provided it is in the
|
||||
format that Discord expects it to be in.
|
||||
|
||||
@ -155,7 +230,7 @@ class Embed:
|
||||
The dictionary to convert into an embed.
|
||||
"""
|
||||
# we are bypassing __init__ here since it doesn't apply here
|
||||
self = cls.__new__(cls)
|
||||
self: E = cls.__new__(cls)
|
||||
|
||||
# fill in the basic fields
|
||||
|
||||
@ -195,11 +270,11 @@ class Embed:
|
||||
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
def copy(self: E) -> E:
|
||||
"""Returns a shallow copy of the embed."""
|
||||
return Embed.from_dict(self.to_dict())
|
||||
return self.__class__.from_dict(self.to_dict())
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
total = len(self.title) + len(self.description)
|
||||
for field in getattr(self, '_fields', []):
|
||||
total += len(field['name']) + len(field['value'])
|
||||
@ -220,43 +295,61 @@ class Embed:
|
||||
|
||||
return total
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return any(
|
||||
(
|
||||
self.title,
|
||||
self.url,
|
||||
self.description,
|
||||
self.colour,
|
||||
self.fields,
|
||||
self.timestamp,
|
||||
self.author,
|
||||
self.thumbnail,
|
||||
self.footer,
|
||||
self.image,
|
||||
self.provider,
|
||||
self.video,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def colour(self):
|
||||
def colour(self) -> MaybeEmpty[Colour]:
|
||||
return getattr(self, '_colour', EmptyEmbed)
|
||||
|
||||
@colour.setter
|
||||
def colour(self, value):
|
||||
def colour(self, value: Union[int, Colour, _EmptyEmbed]): # type: ignore
|
||||
if isinstance(value, (Colour, _EmptyEmbed)):
|
||||
self._colour = value
|
||||
elif isinstance(value, int):
|
||||
self._colour = Colour(value=value)
|
||||
else:
|
||||
raise TypeError('Expected discord.Colour, int, or Embed.Empty but received %s instead.' % value.__class__.__name__)
|
||||
raise TypeError(f'Expected discord.Colour, int, or Embed.Empty but received {value.__class__.__name__} instead.')
|
||||
|
||||
color = colour
|
||||
|
||||
@property
|
||||
def timestamp(self):
|
||||
def timestamp(self) -> MaybeEmpty[datetime.datetime]:
|
||||
return getattr(self, '_timestamp', EmptyEmbed)
|
||||
|
||||
@timestamp.setter
|
||||
def timestamp(self, value):
|
||||
def timestamp(self, value: MaybeEmpty[datetime.datetime]):
|
||||
if isinstance(value, (datetime.datetime, _EmptyEmbed)):
|
||||
self._timestamp = value
|
||||
else:
|
||||
raise TypeError("Expected datetime.datetime or Embed.Empty received %s instead" % value.__class__.__name__)
|
||||
raise TypeError(f"Expected datetime.datetime or Embed.Empty received {value.__class__.__name__} instead")
|
||||
|
||||
@property
|
||||
def footer(self):
|
||||
"""Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the footer contents.
|
||||
def footer(self) -> _EmbedFooterProxy:
|
||||
"""Returns an ``EmbedProxy`` denoting the footer contents.
|
||||
|
||||
See :meth:`set_footer` for possible values you can access.
|
||||
|
||||
If the attribute has no value then :attr:`Empty` is returned.
|
||||
"""
|
||||
return EmbedProxy(getattr(self, '_footer', {}))
|
||||
return EmbedProxy(getattr(self, '_footer', {})) # type: ignore
|
||||
|
||||
def set_footer(self, *, text=EmptyEmbed, icon_url=EmptyEmbed):
|
||||
def set_footer(self: E, *, text: MaybeEmpty[str] = EmptyEmbed, icon_url: MaybeEmpty[str] = EmptyEmbed) -> E:
|
||||
"""Sets the footer for the embed content.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
@ -280,8 +373,8 @@ class Embed:
|
||||
return self
|
||||
|
||||
@property
|
||||
def image(self):
|
||||
"""Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the image contents.
|
||||
def image(self) -> _EmbedMediaProxy:
|
||||
"""Returns an ``EmbedProxy`` denoting the image contents.
|
||||
|
||||
Possible attributes you can access are:
|
||||
|
||||
@ -292,9 +385,9 @@ class Embed:
|
||||
|
||||
If the attribute has no value then :attr:`Empty` is returned.
|
||||
"""
|
||||
return EmbedProxy(getattr(self, '_image', {}))
|
||||
return EmbedProxy(getattr(self, '_image', {})) # type: ignore
|
||||
|
||||
def set_image(self, *, url):
|
||||
def set_image(self: E, *, url: MaybeEmpty[str]) -> E:
|
||||
"""Sets the image for the embed content.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
@ -316,14 +409,14 @@ class Embed:
|
||||
pass
|
||||
else:
|
||||
self._image = {
|
||||
'url': str(url)
|
||||
'url': str(url),
|
||||
}
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def thumbnail(self):
|
||||
"""Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the thumbnail contents.
|
||||
def thumbnail(self) -> _EmbedMediaProxy:
|
||||
"""Returns an ``EmbedProxy`` denoting the thumbnail contents.
|
||||
|
||||
Possible attributes you can access are:
|
||||
|
||||
@ -334,9 +427,9 @@ class Embed:
|
||||
|
||||
If the attribute has no value then :attr:`Empty` is returned.
|
||||
"""
|
||||
return EmbedProxy(getattr(self, '_thumbnail', {}))
|
||||
return EmbedProxy(getattr(self, '_thumbnail', {})) # type: ignore
|
||||
|
||||
def set_thumbnail(self, *, url):
|
||||
def set_thumbnail(self: E, *, url: MaybeEmpty[str]) -> E:
|
||||
"""Sets the thumbnail for the embed content.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
@ -358,14 +451,14 @@ class Embed:
|
||||
pass
|
||||
else:
|
||||
self._thumbnail = {
|
||||
'url': str(url)
|
||||
'url': str(url),
|
||||
}
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def video(self):
|
||||
"""Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the video contents.
|
||||
def video(self) -> _EmbedVideoProxy:
|
||||
"""Returns an ``EmbedProxy`` denoting the video contents.
|
||||
|
||||
Possible attributes include:
|
||||
|
||||
@ -375,29 +468,29 @@ class Embed:
|
||||
|
||||
If the attribute has no value then :attr:`Empty` is returned.
|
||||
"""
|
||||
return EmbedProxy(getattr(self, '_video', {}))
|
||||
return EmbedProxy(getattr(self, '_video', {})) # type: ignore
|
||||
|
||||
@property
|
||||
def provider(self):
|
||||
"""Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the provider contents.
|
||||
def provider(self) -> _EmbedProviderProxy:
|
||||
"""Returns an ``EmbedProxy`` denoting the provider contents.
|
||||
|
||||
The only attributes that might be accessed are ``name`` and ``url``.
|
||||
|
||||
If the attribute has no value then :attr:`Empty` is returned.
|
||||
"""
|
||||
return EmbedProxy(getattr(self, '_provider', {}))
|
||||
return EmbedProxy(getattr(self, '_provider', {})) # type: ignore
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
"""Union[:class:`EmbedProxy`, :attr:`Empty`]: Returns an ``EmbedProxy`` denoting the author contents.
|
||||
def author(self) -> _EmbedAuthorProxy:
|
||||
"""Returns an ``EmbedProxy`` denoting the author contents.
|
||||
|
||||
See :meth:`set_author` for possible values you can access.
|
||||
|
||||
If the attribute has no value then :attr:`Empty` is returned.
|
||||
"""
|
||||
return EmbedProxy(getattr(self, '_author', {}))
|
||||
return EmbedProxy(getattr(self, '_author', {})) # type: ignore
|
||||
|
||||
def set_author(self, *, name, url=EmptyEmbed, icon_url=EmptyEmbed):
|
||||
def set_author(self: E, *, name: str, url: MaybeEmpty[str] = EmptyEmbed, icon_url: MaybeEmpty[str] = EmptyEmbed) -> E:
|
||||
"""Sets the author for the embed content.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
@ -414,7 +507,7 @@ class Embed:
|
||||
"""
|
||||
|
||||
self._author = {
|
||||
'name': str(name)
|
||||
'name': str(name),
|
||||
}
|
||||
|
||||
if url is not EmptyEmbed:
|
||||
@ -425,7 +518,7 @@ class Embed:
|
||||
|
||||
return self
|
||||
|
||||
def remove_author(self):
|
||||
def remove_author(self: E) -> E:
|
||||
"""Clears embed's author information.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
@ -441,16 +534,21 @@ class Embed:
|
||||
return self
|
||||
|
||||
@property
|
||||
<<<<<<< HEAD
|
||||
def fields(self):
|
||||
"""List[Union[``EmbedProxy``, :attr:`Empty`]]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.
|
||||
=======
|
||||
def fields(self) -> List[_EmbedFieldProxy]:
|
||||
"""Union[List[:class:`EmbedProxy`], :attr:`Empty`]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.
|
||||
>>>>>>> 523e35e4f3c3c49d4e471359f9fb559242bbecc8
|
||||
|
||||
See :meth:`add_field` for possible values you can access.
|
||||
|
||||
If the attribute has no value then :attr:`Empty` is returned.
|
||||
"""
|
||||
return [EmbedProxy(d) for d in getattr(self, '_fields', [])]
|
||||
return [EmbedProxy(d) for d in getattr(self, '_fields', [])] # type: ignore
|
||||
|
||||
def add_field(self, *, name, value, inline=True):
|
||||
def add_field(self: E, *, name: str, value: str, inline: bool = True) -> E:
|
||||
"""Adds a field to the embed object.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
@ -469,7 +567,7 @@ class Embed:
|
||||
field = {
|
||||
'inline': inline,
|
||||
'name': str(name),
|
||||
'value': str(value)
|
||||
'value': str(value),
|
||||
}
|
||||
|
||||
try:
|
||||
@ -479,7 +577,7 @@ class Embed:
|
||||
|
||||
return self
|
||||
|
||||
def insert_field_at(self, index, *, name, value, inline=True):
|
||||
def insert_field_at(self: E, index: int, *, name: str, value: str, inline: bool = True) -> E:
|
||||
"""Inserts a field before a specified index to the embed.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
@ -502,7 +600,7 @@ class Embed:
|
||||
field = {
|
||||
'inline': inline,
|
||||
'name': str(name),
|
||||
'value': str(value)
|
||||
'value': str(value),
|
||||
}
|
||||
|
||||
try:
|
||||
@ -512,14 +610,14 @@ class Embed:
|
||||
|
||||
return self
|
||||
|
||||
def clear_fields(self):
|
||||
def clear_fields(self) -> None:
|
||||
"""Removes all fields from this embed."""
|
||||
try:
|
||||
self._fields.clear()
|
||||
except AttributeError:
|
||||
self._fields = []
|
||||
|
||||
def remove_field(self, index):
|
||||
def remove_field(self, index: int) -> None:
|
||||
"""Removes a field at a specified index.
|
||||
|
||||
If the index is invalid or out of bounds then the error is
|
||||
@ -540,7 +638,7 @@ class Embed:
|
||||
except (AttributeError, IndexError):
|
||||
pass
|
||||
|
||||
def set_field_at(self, index, *, name, value, inline=True):
|
||||
def set_field_at(self: E, index: int, *, name: str, value: str, inline: bool = True) -> E:
|
||||
"""Modifies a field to the embed object.
|
||||
|
||||
The index must point to a valid pre-existing field.
|
||||
@ -575,15 +673,17 @@ class Embed:
|
||||
field['inline'] = inline
|
||||
return self
|
||||
|
||||
def to_dict(self):
|
||||
def to_dict(self) -> EmbedData:
|
||||
"""Converts this embed object into a dict."""
|
||||
|
||||
# add in the raw data into the dict
|
||||
# fmt: off
|
||||
result = {
|
||||
key[1:]: getattr(self, key)
|
||||
for key in self.__slots__
|
||||
if key[0] == '_' and hasattr(self, key)
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
# deal with basic convenience wrappers
|
||||
|
||||
@ -619,4 +719,4 @@ class Embed:
|
||||
if self.title:
|
||||
result['title'] = self.title
|
||||
|
||||
return result
|
||||
return result # type: ignore
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -29,6 +27,10 @@ from . import utils
|
||||
from .partial_emoji import _EmojiTag
|
||||
from .user import User
|
||||
|
||||
__all__ = (
|
||||
'Emoji',
|
||||
)
|
||||
|
||||
class Emoji(_EmojiTag):
|
||||
"""Represents a custom emoji.
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -26,6 +24,7 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import types
|
||||
from collections import namedtuple
|
||||
from typing import Any, TYPE_CHECKING, Type, TypeVar
|
||||
|
||||
__all__ = (
|
||||
'Enum',
|
||||
@ -37,18 +36,12 @@ __all__ = (
|
||||
'ContentFilter',
|
||||
'Status',
|
||||
'DefaultAvatar',
|
||||
'RelationshipType',
|
||||
'AuditLogAction',
|
||||
'AuditLogActionCategory',
|
||||
'UserFlags',
|
||||
'ActivityType',
|
||||
'HypeSquadHouse',
|
||||
'NotificationLevel',
|
||||
'PremiumType',
|
||||
'UserContentFilter',
|
||||
'FriendFlags',
|
||||
'TeamMembershipState',
|
||||
'Theme',
|
||||
'WebhookType',
|
||||
'ExpireBehaviour',
|
||||
'ExpireBehavior',
|
||||
@ -57,8 +50,8 @@ __all__ = (
|
||||
|
||||
def _create_value_cls(name):
|
||||
cls = namedtuple('_EnumValue_' + name, 'name value')
|
||||
cls.__repr__ = lambda self: '<%s.%s: %r>' % (name, self.name, self.value)
|
||||
cls.__str__ = lambda self: '%s.%s' % (name, self.name)
|
||||
cls.__repr__ = lambda self: f'<{name}.{self.name}: {self.value!r}>'
|
||||
cls.__str__ = lambda self: f'{name}.{self.name}'
|
||||
return cls
|
||||
|
||||
def _is_descriptor(obj):
|
||||
@ -98,6 +91,7 @@ class EnumMeta(type):
|
||||
attrs['_enum_value_map_'] = value_mapping
|
||||
attrs['_enum_member_map_'] = member_mapping
|
||||
attrs['_enum_member_names_'] = member_names
|
||||
attrs['_enum_value_cls_'] = value_cls
|
||||
actual_cls = super().__new__(cls, name, bases, attrs)
|
||||
value_cls._actual_enum_cls_ = actual_cls
|
||||
return actual_cls
|
||||
@ -112,7 +106,7 @@ class EnumMeta(type):
|
||||
return len(cls._enum_member_names_)
|
||||
|
||||
def __repr__(cls):
|
||||
return '<enum %r>' % cls.__name__
|
||||
return f'<enum {cls.__name__}>'
|
||||
|
||||
@property
|
||||
def __members__(cls):
|
||||
@ -122,7 +116,7 @@ class EnumMeta(type):
|
||||
try:
|
||||
return cls._enum_value_map_[value]
|
||||
except (KeyError, TypeError):
|
||||
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
|
||||
raise ValueError(f"{value!r} is not a valid {cls.__name__}")
|
||||
|
||||
def __getitem__(cls, key):
|
||||
return cls._enum_member_map_[key]
|
||||
@ -141,14 +135,16 @@ class EnumMeta(type):
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
class Enum(metaclass=EnumMeta):
|
||||
@classmethod
|
||||
def try_value(cls, value):
|
||||
try:
|
||||
return cls._enum_value_map_[value]
|
||||
except (KeyError, TypeError):
|
||||
return value
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from enum import Enum
|
||||
else:
|
||||
class Enum(metaclass=EnumMeta):
|
||||
@classmethod
|
||||
def try_value(cls, value):
|
||||
try:
|
||||
return cls._enum_value_map_[value]
|
||||
except (KeyError, TypeError):
|
||||
return value
|
||||
|
||||
class ChannelType(Enum):
|
||||
text = 0
|
||||
@ -244,22 +240,6 @@ class ContentFilter(Enum):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class UserContentFilter(Enum):
|
||||
disabled = 0
|
||||
friends = 1
|
||||
all_messages = 2
|
||||
|
||||
class FriendFlags(Enum):
|
||||
noone = 0
|
||||
mutual_guilds = 1
|
||||
mutual_friends = 2
|
||||
guild_and_friends = 3
|
||||
everyone = 4
|
||||
|
||||
class Theme(Enum):
|
||||
light = 'light'
|
||||
dark = 'dark'
|
||||
|
||||
class Status(Enum):
|
||||
online = 'online'
|
||||
offline = 'offline'
|
||||
@ -282,12 +262,6 @@ class DefaultAvatar(Enum):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class RelationshipType(Enum):
|
||||
friend = 1
|
||||
blocked = 2
|
||||
incoming_request = 3
|
||||
outgoing_request = 4
|
||||
|
||||
class NotificationLevel(Enum):
|
||||
all_messages = 0
|
||||
only_mentions = 1
|
||||
@ -429,15 +403,6 @@ class ActivityType(Enum):
|
||||
def __int__(self):
|
||||
return self.value
|
||||
|
||||
class HypeSquadHouse(Enum):
|
||||
bravery = 1
|
||||
brilliance = 2
|
||||
balance = 3
|
||||
|
||||
class PremiumType(Enum):
|
||||
nitro_classic = 1
|
||||
nitro = 2
|
||||
|
||||
class TeamMembershipState(Enum):
|
||||
invited = 1
|
||||
accepted = 2
|
||||
@ -457,13 +422,24 @@ class StickerType(Enum):
|
||||
apng = 2
|
||||
lottie = 3
|
||||
|
||||
def try_enum(cls, val):
|
||||
class InteractionType(Enum):
|
||||
ping = 1
|
||||
application_command = 2
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
def create_unknown_value(cls: Type[T], val: Any) -> T:
|
||||
value_cls = cls._enum_value_cls_ # type: ignore
|
||||
name = f'unknown_{val}'
|
||||
return value_cls(name=name, value=val)
|
||||
|
||||
def try_enum(cls: Type[T], val: Any) -> T:
|
||||
"""A function that tries to turn the value into enum ``cls``.
|
||||
|
||||
If it fails it returns the value instead.
|
||||
If it fails it returns a proxy invalid value instead.
|
||||
"""
|
||||
|
||||
try:
|
||||
return cls._enum_value_map_[val]
|
||||
return cls._enum_value_map_[val] # type: ignore
|
||||
except (KeyError, TypeError, AttributeError):
|
||||
return val
|
||||
return create_unknown_value(cls, val)
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,6 +22,22 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
'DiscordException',
|
||||
'ClientException',
|
||||
'NoMoreItems',
|
||||
'GatewayNotFound',
|
||||
'HTTPException',
|
||||
'Forbidden',
|
||||
'NotFound',
|
||||
'DiscordServerError',
|
||||
'InvalidData',
|
||||
'InvalidArgument',
|
||||
'LoginFailure',
|
||||
'ConnectionClosed',
|
||||
'PrivilegedIntentsRequired',
|
||||
)
|
||||
|
||||
class DiscordException(Exception):
|
||||
"""Base exception class for discord.py
|
||||
|
||||
@ -48,7 +62,7 @@ class GatewayNotFound(DiscordException):
|
||||
for the :class:`Client` websocket is not found."""
|
||||
def __init__(self):
|
||||
message = 'The gateway to connect to discord was not found.'
|
||||
super(GatewayNotFound, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
def flatten_error_dict(d, key=''):
|
||||
items = []
|
||||
@ -174,7 +188,7 @@ class ConnectionClosed(ClientException):
|
||||
# aiohttp doesn't seem to consistently provide close reason
|
||||
self.reason = ''
|
||||
self.shard_id = shard_id
|
||||
super().__init__('Shard ID %s WebSocket closed with %s' % (self.shard_id, self.code))
|
||||
super().__init__(f'Shard ID {self.shard_id} WebSocket closed with {self.code}')
|
||||
|
||||
class PrivilegedIntentsRequired(ClientException):
|
||||
"""Exception that's thrown when the gateway is requesting privileged intents
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
discord.ext.commands
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -10,8 +8,8 @@ An extension module to facilitate creation of bot commands.
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from .bot import Bot, AutoShardedBot, when_mentioned, when_mentioned_or
|
||||
from .context import Context
|
||||
from .bot import *
|
||||
from .context import *
|
||||
from .core import *
|
||||
from .errors import *
|
||||
from .help import *
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -42,12 +40,19 @@ from . import errors
|
||||
from .help import HelpCommand, DefaultHelpCommand
|
||||
from .cog import Cog
|
||||
|
||||
__all__ = (
|
||||
'when_mentioned',
|
||||
'when_mentioned_or',
|
||||
'Bot',
|
||||
'AutoShardedBot',
|
||||
)
|
||||
|
||||
def when_mentioned(bot, msg):
|
||||
"""A callable that implements a command prefix equivalent to being mentioned.
|
||||
|
||||
These are meant to be passed into the :attr:`.Bot.command_prefix` attribute.
|
||||
"""
|
||||
return [bot.user.mention + ' ', '<@!%s> ' % bot.user.id]
|
||||
return [f'<@{bot.user.id}> ', f'<@!{bot.user.id}> ']
|
||||
|
||||
def when_mentioned_or(*prefixes):
|
||||
"""A callable that implements when mentioned or other prefixes provided.
|
||||
@ -116,7 +121,7 @@ class BotBase(GroupMixin):
|
||||
raise TypeError('Both owner_id and owner_ids are set.')
|
||||
|
||||
if self.owner_ids and not isinstance(self.owner_ids, collections.abc.Collection):
|
||||
raise TypeError('owner_ids must be a collection not {0.__class__!r}'.format(self.owner_ids))
|
||||
raise TypeError(f'owner_ids must be a collection not {self.owner_ids.__class__!r}')
|
||||
|
||||
if options.pop('self_bot', False):
|
||||
self._skip_check = lambda x, y: x != y
|
||||
@ -194,7 +199,7 @@ class BotBase(GroupMixin):
|
||||
if cog and Cog._get_overridden_method(cog.cog_command_error) is not None:
|
||||
return
|
||||
|
||||
print('Ignoring exception in command {}:'.format(context.command), file=sys.stderr)
|
||||
print(f'Ignoring exception in command {context.command}:', file=sys.stderr)
|
||||
traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr)
|
||||
|
||||
# global check registration
|
||||
@ -986,7 +991,7 @@ class BotBase(GroupMixin):
|
||||
else:
|
||||
self.dispatch('command_completion', ctx)
|
||||
elif ctx.invoked_with:
|
||||
exc = errors.CommandNotFound('Command "{}" is not found'.format(ctx.invoked_with))
|
||||
exc = errors.CommandNotFound(f'Command "{ctx.invoked_with}" is not found')
|
||||
self.dispatch('command_error', ctx, exc)
|
||||
|
||||
async def process_commands(self, message):
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -130,7 +128,7 @@ class CogMeta(type):
|
||||
value = value.__func__
|
||||
if isinstance(value, _BaseCommand):
|
||||
if is_static_method:
|
||||
raise TypeError('Command in method {0}.{1!r} must not be staticmethod.'.format(base, elem))
|
||||
raise TypeError(f'Command in method {base}.{elem!r} must not be staticmethod.')
|
||||
if elem.startswith(('cog_', 'bot_')):
|
||||
raise TypeError(no_bot_cog.format(base, elem))
|
||||
commands[elem] = value
|
||||
@ -285,7 +283,7 @@ class Cog(metaclass=CogMeta):
|
||||
"""
|
||||
|
||||
if name is not None and not isinstance(name, str):
|
||||
raise TypeError('Cog.listener expected str but received {0.__class__.__name__!r} instead.'.format(name))
|
||||
raise TypeError(f'Cog.listener expected str but received {name.__class__.__name__!r} instead.')
|
||||
|
||||
def decorator(func):
|
||||
actual = func
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -28,6 +26,10 @@ import re
|
||||
import discord.abc
|
||||
import discord.utils
|
||||
|
||||
__all__ = (
|
||||
'Context',
|
||||
)
|
||||
|
||||
class Context(discord.abc.Messageable):
|
||||
r"""Represents the context in which a command is being invoked under.
|
||||
|
||||
@ -103,7 +105,7 @@ class Context(discord.abc.Messageable):
|
||||
pattern = re.compile(r"<@!?%s>" % user.id)
|
||||
return pattern.sub("@%s" % user.display_name.replace('\\', r'\\'), self.prefix)
|
||||
|
||||
async def invoke(self, *args, **kwargs):
|
||||
async def invoke(self, command, /, *args, **kwargs):
|
||||
r"""|coro|
|
||||
|
||||
Calls a command with the arguments given.
|
||||
@ -120,10 +122,6 @@ class Context(discord.abc.Messageable):
|
||||
You must take care in passing the proper arguments when
|
||||
using this function.
|
||||
|
||||
.. warning::
|
||||
|
||||
The first parameter passed **must** be the command being invoked.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
command: :class:`.Command`
|
||||
@ -138,18 +136,12 @@ class Context(discord.abc.Messageable):
|
||||
TypeError
|
||||
The command argument to invoke is missing.
|
||||
"""
|
||||
|
||||
try:
|
||||
command = args[0]
|
||||
except IndexError:
|
||||
raise TypeError('Missing command to invoke.') from None
|
||||
|
||||
arguments = []
|
||||
if command.cog is not None:
|
||||
arguments.append(command.cog)
|
||||
|
||||
arguments.append(self)
|
||||
arguments.extend(args[1:])
|
||||
arguments.extend(args)
|
||||
|
||||
ret = await command.callback(*arguments, **kwargs)
|
||||
return ret
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,14 +22,19 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import inspect
|
||||
import typing
|
||||
from typing import TYPE_CHECKING, Generic, Protocol, TypeVar, Union, runtime_checkable
|
||||
|
||||
import discord
|
||||
|
||||
from .errors import *
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .context import Context
|
||||
|
||||
|
||||
__all__ = (
|
||||
'Converter',
|
||||
'MemberConverter',
|
||||
@ -56,6 +59,7 @@ __all__ = (
|
||||
'Greedy',
|
||||
)
|
||||
|
||||
|
||||
def _get_from_guilds(bot, getter, argument):
|
||||
result = None
|
||||
for guild in bot.guilds:
|
||||
@ -64,9 +68,13 @@ def _get_from_guilds(bot, getter, argument):
|
||||
return result
|
||||
return result
|
||||
|
||||
_utils_get = discord.utils.get
|
||||
|
||||
class Converter:
|
||||
_utils_get = discord.utils.get
|
||||
T = TypeVar('T', covariant=True)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class Converter(Protocol[T]):
|
||||
"""The base class of custom converters that require the :class:`.Context`
|
||||
to be passed to be useful.
|
||||
|
||||
@ -77,7 +85,7 @@ class Converter:
|
||||
method to do its conversion logic. This method must be a :ref:`coroutine <coroutine>`.
|
||||
"""
|
||||
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> T:
|
||||
"""|coro|
|
||||
|
||||
The method to override to do conversion logic.
|
||||
@ -102,7 +110,7 @@ class Converter:
|
||||
"""
|
||||
raise NotImplementedError('Derived classes need to implement this.')
|
||||
|
||||
class IDConverter(Converter):
|
||||
class IDConverter(Converter[T]):
|
||||
def __init__(self):
|
||||
self._id_regex = re.compile(r'([0-9]{15,20})$')
|
||||
super().__init__()
|
||||
@ -110,7 +118,7 @@ class IDConverter(Converter):
|
||||
def _get_id_match(self, argument):
|
||||
return self._id_regex.match(argument)
|
||||
|
||||
class MemberConverter(IDConverter):
|
||||
class MemberConverter(IDConverter[discord.Member]):
|
||||
"""Converts to a :class:`~discord.Member`.
|
||||
|
||||
All lookups are via the local guild. If in a DM context, then the lookup
|
||||
@ -163,7 +171,7 @@ class MemberConverter(IDConverter):
|
||||
return None
|
||||
return members[0]
|
||||
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.Member:
|
||||
bot = ctx.bot
|
||||
match = self._get_id_match(argument) or re.match(r'<@!?([0-9]+)>$', argument)
|
||||
guild = ctx.guild
|
||||
@ -196,7 +204,7 @@ class MemberConverter(IDConverter):
|
||||
|
||||
return result
|
||||
|
||||
class UserConverter(IDConverter):
|
||||
class UserConverter(IDConverter[discord.User]):
|
||||
"""Converts to a :class:`~discord.User`.
|
||||
|
||||
All lookups are via the global user cache.
|
||||
@ -215,7 +223,7 @@ class UserConverter(IDConverter):
|
||||
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):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.User:
|
||||
match = self._get_id_match(argument) or re.match(r'<@!?([0-9]+)>$', argument)
|
||||
result = None
|
||||
state = ctx._state
|
||||
@ -255,7 +263,7 @@ class UserConverter(IDConverter):
|
||||
|
||||
return result
|
||||
|
||||
class PartialMessageConverter(Converter):
|
||||
class PartialMessageConverter(Converter[discord.PartialMessage]):
|
||||
"""Converts to a :class:`discord.PartialMessage`.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
@ -266,7 +274,8 @@ class PartialMessageConverter(Converter):
|
||||
2. By message ID (The message is assumed to be in the context channel.)
|
||||
3. By message URL
|
||||
"""
|
||||
def _get_id_matches(self, argument):
|
||||
@staticmethod
|
||||
def _get_id_matches(argument):
|
||||
id_regex = re.compile(r'(?:(?P<channel_id>[0-9]{15,20})-)?(?P<message_id>[0-9]{15,20})$')
|
||||
link_regex = re.compile(
|
||||
r'https?://(?:(ptb|canary|www)\.)?discord(?:app)?\.com/channels/'
|
||||
@ -279,14 +288,14 @@ class PartialMessageConverter(Converter):
|
||||
channel_id = match.group("channel_id")
|
||||
return int(match.group("message_id")), int(channel_id) if channel_id else None
|
||||
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.PartialMessage:
|
||||
message_id, channel_id = self._get_id_matches(argument)
|
||||
channel = ctx.bot.get_channel(channel_id) if channel_id else ctx.channel
|
||||
if not channel:
|
||||
raise ChannelNotFound(channel_id)
|
||||
return discord.PartialMessage(channel=channel, id=message_id)
|
||||
|
||||
class MessageConverter(PartialMessageConverter):
|
||||
class MessageConverter(IDConverter[discord.Message]):
|
||||
"""Converts to a :class:`discord.Message`.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
@ -300,8 +309,8 @@ class MessageConverter(PartialMessageConverter):
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.ChannelNotFound`, :exc:`.MessageNotFound` or :exc:`.ChannelNotReadable` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
message_id, channel_id = self._get_id_matches(argument)
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.Message:
|
||||
message_id, channel_id = PartialMessageConverter._get_id_matches(argument)
|
||||
message = ctx.bot._connection._get_message(message_id)
|
||||
if message:
|
||||
return message
|
||||
@ -315,7 +324,7 @@ class MessageConverter(PartialMessageConverter):
|
||||
except discord.Forbidden:
|
||||
raise ChannelNotReadable(channel)
|
||||
|
||||
class TextChannelConverter(IDConverter):
|
||||
class TextChannelConverter(IDConverter[discord.TextChannel]):
|
||||
"""Converts to a :class:`~discord.TextChannel`.
|
||||
|
||||
All lookups are via the local guild. If in a DM context, then the lookup
|
||||
@ -330,7 +339,7 @@ class TextChannelConverter(IDConverter):
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.TextChannel:
|
||||
bot = ctx.bot
|
||||
|
||||
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||
@ -357,7 +366,7 @@ class TextChannelConverter(IDConverter):
|
||||
|
||||
return result
|
||||
|
||||
class VoiceChannelConverter(IDConverter):
|
||||
class VoiceChannelConverter(IDConverter[discord.VoiceChannel]):
|
||||
"""Converts to a :class:`~discord.VoiceChannel`.
|
||||
|
||||
All lookups are via the local guild. If in a DM context, then the lookup
|
||||
@ -372,7 +381,7 @@ class VoiceChannelConverter(IDConverter):
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.VoiceChannel:
|
||||
bot = ctx.bot
|
||||
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||
result = None
|
||||
@ -398,7 +407,7 @@ class VoiceChannelConverter(IDConverter):
|
||||
|
||||
return result
|
||||
|
||||
class StageChannelConverter(IDConverter):
|
||||
class StageChannelConverter(IDConverter[discord.StageChannel]):
|
||||
"""Converts to a :class:`~discord.StageChannel`.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
@ -412,7 +421,7 @@ class StageChannelConverter(IDConverter):
|
||||
2. Lookup by mention.
|
||||
3. Lookup by name
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.StageChannel:
|
||||
bot = ctx.bot
|
||||
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||
result = None
|
||||
@ -438,7 +447,7 @@ class StageChannelConverter(IDConverter):
|
||||
|
||||
return result
|
||||
|
||||
class CategoryChannelConverter(IDConverter):
|
||||
class CategoryChannelConverter(IDConverter[discord.CategoryChannel]):
|
||||
"""Converts to a :class:`~discord.CategoryChannel`.
|
||||
|
||||
All lookups are via the local guild. If in a DM context, then the lookup
|
||||
@ -453,7 +462,7 @@ class CategoryChannelConverter(IDConverter):
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.CategoryChannel:
|
||||
bot = ctx.bot
|
||||
|
||||
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||
@ -480,7 +489,7 @@ class CategoryChannelConverter(IDConverter):
|
||||
|
||||
return result
|
||||
|
||||
class StoreChannelConverter(IDConverter):
|
||||
class StoreChannelConverter(IDConverter[discord.StoreChannel]):
|
||||
"""Converts to a :class:`~discord.StoreChannel`.
|
||||
|
||||
All lookups are via the local guild. If in a DM context, then the lookup
|
||||
@ -495,7 +504,7 @@ class StoreChannelConverter(IDConverter):
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.StoreChannel:
|
||||
bot = ctx.bot
|
||||
match = self._get_id_match(argument) or re.match(r'<#([0-9]+)>$', argument)
|
||||
result = None
|
||||
@ -521,7 +530,7 @@ class StoreChannelConverter(IDConverter):
|
||||
|
||||
return result
|
||||
|
||||
class ColourConverter(Converter):
|
||||
class ColourConverter(Converter[discord.Colour]):
|
||||
"""Converts to a :class:`~discord.Colour`.
|
||||
|
||||
.. versionchanged:: 1.5
|
||||
@ -582,7 +591,7 @@ class ColourConverter(Converter):
|
||||
blue = self.parse_rgb_number(argument, match.group('b'))
|
||||
return discord.Color.from_rgb(red, green, blue)
|
||||
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.Colour:
|
||||
if argument[0] == '#':
|
||||
return self.parse_hex_number(argument[1:])
|
||||
|
||||
@ -605,7 +614,7 @@ class ColourConverter(Converter):
|
||||
|
||||
ColorConverter = ColourConverter
|
||||
|
||||
class RoleConverter(IDConverter):
|
||||
class RoleConverter(IDConverter[discord.Role]):
|
||||
"""Converts to a :class:`~discord.Role`.
|
||||
|
||||
All lookups are via the local guild. If in a DM context, the converter raises
|
||||
@ -620,7 +629,7 @@ class RoleConverter(IDConverter):
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.RoleNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.Role:
|
||||
guild = ctx.guild
|
||||
if not guild:
|
||||
raise NoPrivateMessage()
|
||||
@ -635,12 +644,12 @@ class RoleConverter(IDConverter):
|
||||
raise RoleNotFound(argument)
|
||||
return result
|
||||
|
||||
class GameConverter(Converter):
|
||||
class GameConverter(Converter[discord.Game]):
|
||||
"""Converts to :class:`~discord.Game`."""
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.Game:
|
||||
return discord.Game(name=argument)
|
||||
|
||||
class InviteConverter(Converter):
|
||||
class InviteConverter(Converter[discord.Invite]):
|
||||
"""Converts to a :class:`~discord.Invite`.
|
||||
|
||||
This is done via an HTTP request using :meth:`.Bot.fetch_invite`.
|
||||
@ -648,14 +657,14 @@ class InviteConverter(Converter):
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.BadInviteArgument` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.Invite:
|
||||
try:
|
||||
invite = await ctx.bot.fetch_invite(argument)
|
||||
return invite
|
||||
except Exception as exc:
|
||||
raise BadInviteArgument() from exc
|
||||
|
||||
class GuildConverter(IDConverter):
|
||||
class GuildConverter(IDConverter[discord.Guild]):
|
||||
"""Converts to a :class:`~discord.Guild`.
|
||||
|
||||
The lookup strategy is as follows (in order):
|
||||
@ -666,7 +675,7 @@ class GuildConverter(IDConverter):
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.Guild:
|
||||
match = self._get_id_match(argument)
|
||||
result = None
|
||||
|
||||
@ -681,7 +690,7 @@ class GuildConverter(IDConverter):
|
||||
raise GuildNotFound(argument)
|
||||
return result
|
||||
|
||||
class EmojiConverter(IDConverter):
|
||||
class EmojiConverter(IDConverter[discord.Emoji]):
|
||||
"""Converts to a :class:`~discord.Emoji`.
|
||||
|
||||
All lookups are done for the local guild first, if available. If that lookup
|
||||
@ -696,7 +705,7 @@ class EmojiConverter(IDConverter):
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.EmojiNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.Emoji:
|
||||
match = self._get_id_match(argument) or re.match(r'<a?:[a-zA-Z0-9\_]+:([0-9]+)>$', argument)
|
||||
result = None
|
||||
bot = ctx.bot
|
||||
@ -724,7 +733,7 @@ class EmojiConverter(IDConverter):
|
||||
|
||||
return result
|
||||
|
||||
class PartialEmojiConverter(Converter):
|
||||
class PartialEmojiConverter(Converter[discord.PartialEmoji]):
|
||||
"""Converts to a :class:`~discord.PartialEmoji`.
|
||||
|
||||
This is done by extracting the animated flag, name and ID from the emoji.
|
||||
@ -732,7 +741,7 @@ class PartialEmojiConverter(Converter):
|
||||
.. versionchanged:: 1.5
|
||||
Raise :exc:`.PartialEmojiConversionFailure` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> discord.PartialEmoji:
|
||||
match = re.match(r'<(a?):([a-zA-Z0-9\_]+):([0-9]+)>$', argument)
|
||||
|
||||
if match:
|
||||
@ -745,7 +754,7 @@ class PartialEmojiConverter(Converter):
|
||||
|
||||
raise PartialEmojiConversionFailure(argument)
|
||||
|
||||
class clean_content(Converter):
|
||||
class clean_content(Converter[str]):
|
||||
"""Converts the argument to mention scrubbed version of
|
||||
said content.
|
||||
|
||||
@ -770,14 +779,14 @@ class clean_content(Converter):
|
||||
self.escape_markdown = escape_markdown
|
||||
self.remove_markdown = remove_markdown
|
||||
|
||||
async def convert(self, ctx, argument):
|
||||
async def convert(self, ctx: Context, argument: str) -> str:
|
||||
message = ctx.message
|
||||
transformations = {}
|
||||
|
||||
if self.fix_channel_mentions and ctx.guild:
|
||||
def resolve_channel(id, *, _get=ctx.guild.get_channel):
|
||||
ch = _get(id)
|
||||
return ('<#%s>' % id), ('#' + ch.name if ch else '#deleted-channel')
|
||||
return f'<#{id}>', ('#' + ch.name if ch else '#deleted-channel')
|
||||
|
||||
transformations.update(resolve_channel(channel) for channel in message.raw_channel_mentions)
|
||||
|
||||
@ -792,12 +801,12 @@ class clean_content(Converter):
|
||||
|
||||
|
||||
transformations.update(
|
||||
('<@%s>' % member_id, resolve_member(member_id))
|
||||
(f'<@{member_id}>', resolve_member(member_id))
|
||||
for member_id in message.raw_mentions
|
||||
)
|
||||
|
||||
transformations.update(
|
||||
('<@!%s>' % member_id, resolve_member(member_id))
|
||||
(f'<@!{member_id}>', resolve_member(member_id))
|
||||
for member_id in message.raw_mentions
|
||||
)
|
||||
|
||||
@ -807,7 +816,7 @@ class clean_content(Converter):
|
||||
return '@' + r.name if r else '@deleted-role'
|
||||
|
||||
transformations.update(
|
||||
('<@&%s>' % role_id, resolve_role(role_id))
|
||||
(f'<@&{role_id}>', resolve_role(role_id))
|
||||
for role_id in message.raw_role_mentions
|
||||
)
|
||||
|
||||
@ -842,10 +851,10 @@ class _Greedy:
|
||||
raise TypeError('Greedy[...] expects a type or a Converter instance.')
|
||||
|
||||
if converter is str or converter is type(None) or converter is _Greedy:
|
||||
raise TypeError('Greedy[%s] is invalid.' % converter.__name__)
|
||||
raise TypeError(f'Greedy[{converter.__name__}] is invalid.')
|
||||
|
||||
if getattr(converter, '__origin__', None) is typing.Union and type(None) in converter.__args__:
|
||||
raise TypeError('Greedy[%r] is invalid.' % converter)
|
||||
if getattr(converter, '__origin__', None) is Union and type(None) in converter.__args__:
|
||||
raise TypeError(f'Greedy[{converter!r}] is invalid.')
|
||||
|
||||
return self.__class__(converter=converter)
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -255,7 +253,7 @@ class MaxConcurrency:
|
||||
raise ValueError('max_concurrency \'number\' cannot be less than 1')
|
||||
|
||||
if not isinstance(per, BucketType):
|
||||
raise TypeError('max_concurrency \'per\' must be of type BucketType not %r' % type(per))
|
||||
raise TypeError(f'max_concurrency \'per\' must be of type BucketType not {type(per)!r}')
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self.number, per=self.per, wait=self.wait)
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -29,6 +27,7 @@ import functools
|
||||
import inspect
|
||||
import typing
|
||||
import datetime
|
||||
import sys
|
||||
|
||||
import discord
|
||||
|
||||
@ -301,17 +300,37 @@ class Command(_BaseCommand):
|
||||
signature = inspect.signature(function)
|
||||
self.params = signature.parameters.copy()
|
||||
|
||||
# PEP-563 allows postponing evaluation of annotations with a __future__
|
||||
# import. When postponed, Parameter.annotation will be a string and must
|
||||
# be replaced with the real value for the converters to work later on
|
||||
# see: https://bugs.python.org/issue41341
|
||||
resolve = self._recursive_resolve if sys.version_info < (3, 9) else self._return_resolved
|
||||
|
||||
try:
|
||||
type_hints = {k: resolve(v) for k, v in typing.get_type_hints(function).items()}
|
||||
except NameError as e:
|
||||
raise NameError(f'unresolved forward reference: {e.args[0]}') from None
|
||||
|
||||
for key, value in self.params.items():
|
||||
if isinstance(value.annotation, str):
|
||||
self.params[key] = value = value.replace(annotation=eval(value.annotation, function.__globals__))
|
||||
# coalesce the forward references
|
||||
if key in type_hints:
|
||||
self.params[key] = value = value.replace(annotation=type_hints[key])
|
||||
|
||||
# fail early for when someone passes an unparameterized Greedy type
|
||||
if value.annotation is converters.Greedy:
|
||||
raise TypeError('Unparameterized Greedy[...] is disallowed in signature.')
|
||||
|
||||
def _return_resolved(self, type, **kwargs):
|
||||
return type
|
||||
|
||||
def _recursive_resolve(self, type, *, globals=None):
|
||||
if not isinstance(type, typing.ForwardRef):
|
||||
return type
|
||||
|
||||
resolved = eval(type.__forward_arg__, globals)
|
||||
args = typing.get_args(resolved)
|
||||
for index, arg in enumerate(args):
|
||||
inner_resolve_result = self._recursive_resolve(arg, globals=globals)
|
||||
resolved[index] = inner_resolve_result
|
||||
return resolved
|
||||
|
||||
def add_check(self, func):
|
||||
"""Adds a check to the command.
|
||||
|
||||
@ -445,19 +464,13 @@ class Command(_BaseCommand):
|
||||
converter = getattr(converters, converter.__name__ + 'Converter', converter)
|
||||
|
||||
try:
|
||||
if inspect.isclass(converter):
|
||||
if issubclass(converter, converters.Converter):
|
||||
instance = converter()
|
||||
ret = await instance.convert(ctx, argument)
|
||||
return ret
|
||||
if inspect.isclass(converter) and issubclass(converter, converters.Converter):
|
||||
if inspect.ismethod(converter.convert):
|
||||
return await converter.convert(ctx, argument)
|
||||
else:
|
||||
method = getattr(converter, 'convert', None)
|
||||
if method is not None and inspect.ismethod(method):
|
||||
ret = await method(ctx, argument)
|
||||
return ret
|
||||
return await converter().convert(ctx, argument)
|
||||
elif isinstance(converter, converters.Converter):
|
||||
ret = await converter.convert(ctx, argument)
|
||||
return ret
|
||||
return await converter.convert(ctx, argument)
|
||||
except CommandError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
@ -473,7 +486,7 @@ class Command(_BaseCommand):
|
||||
except AttributeError:
|
||||
name = converter.__class__.__name__
|
||||
|
||||
raise BadArgument('Converting to "{}" failed for parameter "{}".'.format(name, param.name)) from exc
|
||||
raise BadArgument(f'Converting to "{name}" failed for parameter "{param.name}".') from exc
|
||||
|
||||
async def do_conversion(self, ctx, converter, argument, param):
|
||||
try:
|
||||
@ -775,7 +788,7 @@ class Command(_BaseCommand):
|
||||
ctx.command = self
|
||||
|
||||
if not await self.can_run(ctx):
|
||||
raise CheckFailure('The check functions for command {0.qualified_name} failed.'.format(self))
|
||||
raise CheckFailure(f'The check functions for command {self.qualified_name} failed.')
|
||||
|
||||
if self._max_concurrency is not None:
|
||||
await self._max_concurrency.acquire(ctx)
|
||||
@ -1014,23 +1027,23 @@ class Command(_BaseCommand):
|
||||
# do [name] since [name=None] or [name=] are not exactly useful for the user.
|
||||
should_print = param.default if isinstance(param.default, str) else param.default is not None
|
||||
if should_print:
|
||||
result.append('[%s=%s]' % (name, param.default) if not greedy else
|
||||
'[%s=%s]...' % (name, param.default))
|
||||
result.append(f'[{name}={param.default}]' if not greedy else
|
||||
f'[{name}={param.default}]...')
|
||||
continue
|
||||
else:
|
||||
result.append('[%s]' % name)
|
||||
result.append(f'[{name}]')
|
||||
|
||||
elif param.kind == param.VAR_POSITIONAL:
|
||||
if self.require_var_positional:
|
||||
result.append('<%s...>' % name)
|
||||
result.append(f'<{name}...>')
|
||||
else:
|
||||
result.append('[%s...]' % name)
|
||||
result.append(f'[{name}...]')
|
||||
elif greedy:
|
||||
result.append('[%s]...' % name)
|
||||
result.append(f'[{name}]...')
|
||||
elif self._is_typing_optional(param.annotation):
|
||||
result.append('[%s]' % name)
|
||||
result.append(f'[{name}]')
|
||||
else:
|
||||
result.append('<%s>' % name)
|
||||
result.append(f'<{name}>')
|
||||
|
||||
return ' '.join(result)
|
||||
|
||||
@ -1062,14 +1075,14 @@ class Command(_BaseCommand):
|
||||
"""
|
||||
|
||||
if not self.enabled:
|
||||
raise DisabledCommand('{0.name} command is disabled'.format(self))
|
||||
raise DisabledCommand(f'{self.name} command is disabled')
|
||||
|
||||
original = ctx.command
|
||||
ctx.command = self
|
||||
|
||||
try:
|
||||
if not await ctx.bot.can_run(ctx):
|
||||
raise CheckFailure('The global check functions for command {0.qualified_name} failed.'.format(self))
|
||||
raise CheckFailure(f'The global check functions for command {self.qualified_name} failed.')
|
||||
|
||||
cog = self.cog
|
||||
if cog is not None:
|
||||
@ -1588,7 +1601,7 @@ def check_any(*checks):
|
||||
try:
|
||||
pred = wrapped.predicate
|
||||
except AttributeError:
|
||||
raise TypeError('%r must be wrapped by commands.check decorator' % wrapped) from None
|
||||
raise TypeError(f'{wrapped!r} must be wrapped by commands.check decorator') from None
|
||||
else:
|
||||
unwrapped.append(pred)
|
||||
|
||||
@ -1776,7 +1789,7 @@ def has_permissions(**perms):
|
||||
|
||||
invalid = set(perms) - set(discord.Permissions.VALID_FLAGS)
|
||||
if invalid:
|
||||
raise TypeError('Invalid permission(s): %s' % (', '.join(invalid)))
|
||||
raise TypeError(f"Invalid permission(s): {', '.join(invalid)}")
|
||||
|
||||
def predicate(ctx):
|
||||
ch = ctx.channel
|
||||
@ -1801,7 +1814,7 @@ def bot_has_permissions(**perms):
|
||||
|
||||
invalid = set(perms) - set(discord.Permissions.VALID_FLAGS)
|
||||
if invalid:
|
||||
raise TypeError('Invalid permission(s): %s' % (', '.join(invalid)))
|
||||
raise TypeError(f"Invalid permission(s): {', '.join(invalid)}")
|
||||
|
||||
def predicate(ctx):
|
||||
guild = ctx.guild
|
||||
@ -1829,7 +1842,7 @@ def has_guild_permissions(**perms):
|
||||
|
||||
invalid = set(perms) - set(discord.Permissions.VALID_FLAGS)
|
||||
if invalid:
|
||||
raise TypeError('Invalid permission(s): %s' % (', '.join(invalid)))
|
||||
raise TypeError(f"Invalid permission(s): {', '.join(invalid)}")
|
||||
|
||||
def predicate(ctx):
|
||||
if not ctx.guild:
|
||||
@ -1854,7 +1867,7 @@ def bot_has_guild_permissions(**perms):
|
||||
|
||||
invalid = set(perms) - set(discord.Permissions.VALID_FLAGS)
|
||||
if invalid:
|
||||
raise TypeError('Invalid permission(s): %s' % (', '.join(invalid)))
|
||||
raise TypeError(f"Invalid permission(s): {', '.join(invalid)}")
|
||||
|
||||
def predicate(ctx):
|
||||
if not ctx.guild:
|
||||
@ -1961,7 +1974,7 @@ def cooldown(rate, per, type=BucketType.default):
|
||||
The amount of seconds to wait for a cooldown when it's been triggered.
|
||||
type: Union[:class:`.BucketType`, Callable[[:class:`.Message`], Any]]
|
||||
The type of cooldown to have. If callable, should return a key for the mapping.
|
||||
|
||||
|
||||
.. versionchanged:: 1.7
|
||||
Callables are now supported for custom bucket types.
|
||||
"""
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -143,7 +141,7 @@ class MissingRequiredArgument(UserInputError):
|
||||
"""
|
||||
def __init__(self, param):
|
||||
self.param = param
|
||||
super().__init__('{0.name} is a required argument that is missing.'.format(param))
|
||||
super().__init__(f'{param.name} is a required argument that is missing.')
|
||||
|
||||
class TooManyArguments(UserInputError):
|
||||
"""Exception raised when the command was passed too many arguments and its
|
||||
@ -229,7 +227,7 @@ class MemberNotFound(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('Member "{}" not found.'.format(argument))
|
||||
super().__init__(f'Member "{argument}" not found.')
|
||||
|
||||
class GuildNotFound(BadArgument):
|
||||
"""Exception raised when the guild provided was not found in the bot's cache.
|
||||
@ -245,7 +243,7 @@ class GuildNotFound(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('Guild "{}" not found.'.format(argument))
|
||||
super().__init__(f'Guild "{argument}" not found.')
|
||||
|
||||
class UserNotFound(BadArgument):
|
||||
"""Exception raised when the user provided was not found in the bot's
|
||||
@ -262,7 +260,7 @@ class UserNotFound(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('User "{}" not found.'.format(argument))
|
||||
super().__init__(f'User "{argument}" not found.')
|
||||
|
||||
class MessageNotFound(BadArgument):
|
||||
"""Exception raised when the message provided was not found in the channel.
|
||||
@ -278,7 +276,7 @@ class MessageNotFound(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('Message "{}" not found.'.format(argument))
|
||||
super().__init__(f'Message "{argument}" not found.')
|
||||
|
||||
class ChannelNotReadable(BadArgument):
|
||||
"""Exception raised when the bot does not have permission to read messages
|
||||
@ -295,7 +293,7 @@ class ChannelNotReadable(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__("Can't read messages in {}.".format(argument.mention))
|
||||
super().__init__(f"Can't read messages in {argument.mention}.")
|
||||
|
||||
class ChannelNotFound(BadArgument):
|
||||
"""Exception raised when the bot can not find the channel.
|
||||
@ -311,7 +309,7 @@ class ChannelNotFound(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('Channel "{}" not found.'.format(argument))
|
||||
super().__init__(f'Channel "{argument}" not found.')
|
||||
|
||||
class BadColourArgument(BadArgument):
|
||||
"""Exception raised when the colour is not valid.
|
||||
@ -327,7 +325,7 @@ class BadColourArgument(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('Colour "{}" is invalid.'.format(argument))
|
||||
super().__init__(f'Colour "{argument}" is invalid.')
|
||||
|
||||
BadColorArgument = BadColourArgument
|
||||
|
||||
@ -345,7 +343,7 @@ class RoleNotFound(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('Role "{}" not found.'.format(argument))
|
||||
super().__init__(f'Role "{argument}" not found.')
|
||||
|
||||
class BadInviteArgument(BadArgument):
|
||||
"""Exception raised when the invite is invalid or expired.
|
||||
@ -371,7 +369,7 @@ class EmojiNotFound(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('Emoji "{}" not found.'.format(argument))
|
||||
super().__init__(f'Emoji "{argument}" not found.')
|
||||
|
||||
class PartialEmojiConversionFailure(BadArgument):
|
||||
"""Exception raised when the emoji provided does not match the correct
|
||||
@ -388,7 +386,7 @@ class PartialEmojiConversionFailure(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('Couldn\'t convert "{}" to PartialEmoji.'.format(argument))
|
||||
super().__init__(f'Couldn\'t convert "{argument}" to PartialEmoji.')
|
||||
|
||||
class BadBoolArgument(BadArgument):
|
||||
"""Exception raised when a boolean argument was not convertable.
|
||||
@ -404,7 +402,7 @@ class BadBoolArgument(BadArgument):
|
||||
"""
|
||||
def __init__(self, argument):
|
||||
self.argument = argument
|
||||
super().__init__('{} is not a recognised boolean option'.format(argument))
|
||||
super().__init__(f'{argument} is not a recognised boolean option')
|
||||
|
||||
class DisabledCommand(CommandError):
|
||||
"""Exception raised when the command being invoked is disabled.
|
||||
@ -444,7 +442,7 @@ class CommandOnCooldown(CommandError):
|
||||
def __init__(self, cooldown, retry_after):
|
||||
self.cooldown = cooldown
|
||||
self.retry_after = retry_after
|
||||
super().__init__('You are on cooldown. Try again in {:.2f}s'.format(retry_after))
|
||||
super().__init__(f'You are on cooldown. Try again in {retry_after:.2f}s')
|
||||
|
||||
class MaxConcurrencyReached(CommandError):
|
||||
"""Exception raised when the command being invoked has reached its maximum concurrency.
|
||||
@ -466,7 +464,7 @@ class MaxConcurrencyReached(CommandError):
|
||||
suffix = 'per %s' % name if per.name != 'default' else 'globally'
|
||||
plural = '%s times %s' if number > 1 else '%s time %s'
|
||||
fmt = plural % (number, suffix)
|
||||
super().__init__('Too many people using this command. It can only be used {} concurrently.'.format(fmt))
|
||||
super().__init__(f'Too many people using this command. It can only be used {fmt} concurrently.')
|
||||
|
||||
class MissingRole(CheckFailure):
|
||||
"""Exception raised when the command invoker lacks a role to run a command.
|
||||
@ -483,7 +481,7 @@ class MissingRole(CheckFailure):
|
||||
"""
|
||||
def __init__(self, missing_role):
|
||||
self.missing_role = missing_role
|
||||
message = 'Role {0!r} is required to run this command.'.format(missing_role)
|
||||
message = f'Role {missing_role!r} is required to run this command.'
|
||||
super().__init__(message)
|
||||
|
||||
class BotMissingRole(CheckFailure):
|
||||
@ -501,7 +499,7 @@ class BotMissingRole(CheckFailure):
|
||||
"""
|
||||
def __init__(self, missing_role):
|
||||
self.missing_role = missing_role
|
||||
message = 'Bot requires the role {0!r} to run this command'.format(missing_role)
|
||||
message = f'Bot requires the role {missing_role!r} to run this command'
|
||||
super().__init__(message)
|
||||
|
||||
class MissingAnyRole(CheckFailure):
|
||||
@ -521,14 +519,14 @@ class MissingAnyRole(CheckFailure):
|
||||
def __init__(self, missing_roles):
|
||||
self.missing_roles = missing_roles
|
||||
|
||||
missing = ["'{}'".format(role) for role in missing_roles]
|
||||
missing = [f"'{role}'" for role in missing_roles]
|
||||
|
||||
if len(missing) > 2:
|
||||
fmt = '{}, or {}'.format(", ".join(missing[:-1]), missing[-1])
|
||||
else:
|
||||
fmt = ' or '.join(missing)
|
||||
|
||||
message = "You are missing at least one of the required roles: {}".format(fmt)
|
||||
message = f"You are missing at least one of the required roles: {fmt}"
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
@ -550,14 +548,14 @@ class BotMissingAnyRole(CheckFailure):
|
||||
def __init__(self, missing_roles):
|
||||
self.missing_roles = missing_roles
|
||||
|
||||
missing = ["'{}'".format(role) for role in missing_roles]
|
||||
missing = [f"'{role}'" for role in missing_roles]
|
||||
|
||||
if len(missing) > 2:
|
||||
fmt = '{}, or {}'.format(", ".join(missing[:-1]), missing[-1])
|
||||
else:
|
||||
fmt = ' or '.join(missing)
|
||||
|
||||
message = "Bot is missing at least one of the required roles: {}".format(fmt)
|
||||
message = f"Bot is missing at least one of the required roles: {fmt}"
|
||||
super().__init__(message)
|
||||
|
||||
class NSFWChannelRequired(CheckFailure):
|
||||
@ -574,7 +572,7 @@ class NSFWChannelRequired(CheckFailure):
|
||||
"""
|
||||
def __init__(self, channel):
|
||||
self.channel = channel
|
||||
super().__init__("Channel '{}' needs to be NSFW for this command to work.".format(channel))
|
||||
super().__init__(f"Channel '{channel}' needs to be NSFW for this command to work.")
|
||||
|
||||
class MissingPermissions(CheckFailure):
|
||||
"""Exception raised when the command invoker lacks permissions to run a
|
||||
@ -596,7 +594,7 @@ class MissingPermissions(CheckFailure):
|
||||
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
|
||||
else:
|
||||
fmt = ' and '.join(missing)
|
||||
message = 'You are missing {} permission(s) to run this command.'.format(fmt)
|
||||
message = f'You are missing {fmt} permission(s) to run this command.'
|
||||
super().__init__(message, *args)
|
||||
|
||||
class BotMissingPermissions(CheckFailure):
|
||||
@ -619,7 +617,7 @@ class BotMissingPermissions(CheckFailure):
|
||||
fmt = '{}, and {}'.format(", ".join(missing[:-1]), missing[-1])
|
||||
else:
|
||||
fmt = ' and '.join(missing)
|
||||
message = 'Bot requires {} permission(s) to run this command.'.format(fmt)
|
||||
message = f'Bot requires {fmt} permission(s) to run this command.'
|
||||
super().__init__(message, *args)
|
||||
|
||||
class BadUnionArgument(UserInputError):
|
||||
@ -654,7 +652,7 @@ class BadUnionArgument(UserInputError):
|
||||
else:
|
||||
fmt = ' or '.join(to_string)
|
||||
|
||||
super().__init__('Could not convert "{0.name}" into {1}.'.format(param, fmt))
|
||||
super().__init__(f'Could not convert "{param.name}" into {fmt}.')
|
||||
|
||||
class ArgumentParsingError(UserInputError):
|
||||
"""An exception raised when the parser fails to parse a user's input.
|
||||
@ -678,7 +676,7 @@ class UnexpectedQuoteError(ArgumentParsingError):
|
||||
"""
|
||||
def __init__(self, quote):
|
||||
self.quote = quote
|
||||
super().__init__('Unexpected quote mark, {0!r}, in non-quoted string'.format(quote))
|
||||
super().__init__(f'Unexpected quote mark, {quote!r}, in non-quoted string')
|
||||
|
||||
class InvalidEndOfQuotedStringError(ArgumentParsingError):
|
||||
"""An exception raised when a space is expected after the closing quote in a string
|
||||
@ -693,7 +691,7 @@ class InvalidEndOfQuotedStringError(ArgumentParsingError):
|
||||
"""
|
||||
def __init__(self, char):
|
||||
self.char = char
|
||||
super().__init__('Expected space after closing quotation but received {0!r}'.format(char))
|
||||
super().__init__(f'Expected space after closing quotation but received {char!r}')
|
||||
|
||||
class ExpectedClosingQuoteError(ArgumentParsingError):
|
||||
"""An exception raised when a quote character is expected but not found.
|
||||
@ -708,7 +706,7 @@ class ExpectedClosingQuoteError(ArgumentParsingError):
|
||||
|
||||
def __init__(self, close_quote):
|
||||
self.close_quote = close_quote
|
||||
super().__init__('Expected closing {}.'.format(close_quote))
|
||||
super().__init__(f'Expected closing {close_quote}.')
|
||||
|
||||
class ExtensionError(DiscordException):
|
||||
"""Base exception for extension related errors.
|
||||
@ -722,7 +720,7 @@ class ExtensionError(DiscordException):
|
||||
"""
|
||||
def __init__(self, message=None, *args, name):
|
||||
self.name = name
|
||||
message = message or 'Extension {!r} had an error.'.format(name)
|
||||
message = message or f'Extension {name!r} had an error.'
|
||||
# clean-up @everyone and @here mentions
|
||||
m = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere')
|
||||
super().__init__(m, *args)
|
||||
@ -733,7 +731,7 @@ class ExtensionAlreadyLoaded(ExtensionError):
|
||||
This inherits from :exc:`ExtensionError`
|
||||
"""
|
||||
def __init__(self, name):
|
||||
super().__init__('Extension {!r} is already loaded.'.format(name), name=name)
|
||||
super().__init__(f'Extension {name!r} is already loaded.', name=name)
|
||||
|
||||
class ExtensionNotLoaded(ExtensionError):
|
||||
"""An exception raised when an extension was not loaded.
|
||||
@ -741,7 +739,7 @@ class ExtensionNotLoaded(ExtensionError):
|
||||
This inherits from :exc:`ExtensionError`
|
||||
"""
|
||||
def __init__(self, name):
|
||||
super().__init__('Extension {!r} has not been loaded.'.format(name), name=name)
|
||||
super().__init__(f'Extension {name!r} has not been loaded.', name=name)
|
||||
|
||||
class NoEntryPointError(ExtensionError):
|
||||
"""An exception raised when an extension does not have a ``setup`` entry point function.
|
||||
@ -749,7 +747,7 @@ class NoEntryPointError(ExtensionError):
|
||||
This inherits from :exc:`ExtensionError`
|
||||
"""
|
||||
def __init__(self, name):
|
||||
super().__init__("Extension {!r} has no 'setup' function.".format(name), name=name)
|
||||
super().__init__(f"Extension {name!r} has no 'setup' function.", name=name)
|
||||
|
||||
class ExtensionFailed(ExtensionError):
|
||||
"""An exception raised when an extension failed to load during execution of the module or ``setup`` entry point.
|
||||
@ -808,4 +806,4 @@ class CommandRegistrationError(ClientException):
|
||||
self.name = name
|
||||
self.alias_conflict = alias_conflict
|
||||
type_ = 'alias' if alias_conflict else 'command'
|
||||
super().__init__('The {} {} is already an existing command or alias.'.format(type_, name))
|
||||
super().__init__(f'The {type_} {name} is already an existing command or alias.')
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -132,7 +130,7 @@ class Paginator:
|
||||
"""
|
||||
max_page_size = self.max_size - self._prefix_len - self._suffix_len - 2 * self._linesep_len
|
||||
if len(line) > max_page_size:
|
||||
raise RuntimeError('Line exceeds maximum page size %s' % (max_page_size))
|
||||
raise RuntimeError(f'Line exceeds maximum page size {max_page_size}')
|
||||
|
||||
if self._count + len(line) + self._linesep_len > self.max_size - self._suffix_len:
|
||||
self.close_page()
|
||||
@ -386,8 +384,9 @@ class HelpCommand:
|
||||
# consider this to be an *incredibly* strange use case. I'd rather go
|
||||
# for this common use case rather than waste performance for the
|
||||
# odd one.
|
||||
pattern = re.compile(r"<@!?%s>" % user.id)
|
||||
return pattern.sub("@%s" % user.display_name.replace('\\', r'\\'), self.context.prefix)
|
||||
pattern = re.compile(fr"<@!?{user.id}>")
|
||||
display_name = user.display_name.replace('\\', r'\\')
|
||||
return pattern.sub('@' + display_name, self.context.prefix)
|
||||
|
||||
@property
|
||||
def invoked_with(self):
|
||||
@ -436,14 +435,14 @@ class HelpCommand:
|
||||
|
||||
if len(command.aliases) > 0:
|
||||
aliases = '|'.join(command.aliases)
|
||||
fmt = '[%s|%s]' % (command.name, aliases)
|
||||
fmt = f'[{command.name}|{aliases}]'
|
||||
if parent_sig:
|
||||
fmt = parent_sig + ' ' + fmt
|
||||
alias = fmt
|
||||
else:
|
||||
alias = command.name if not parent_sig else parent_sig + ' ' + command.name
|
||||
|
||||
return '%s%s %s' % (self.clean_prefix, alias, command.signature)
|
||||
return f'{self.clean_prefix}{alias} {command.signature}'
|
||||
|
||||
def remove_mentions(self, string):
|
||||
"""Removes mentions from the string to prevent abuse.
|
||||
@ -506,7 +505,7 @@ class HelpCommand:
|
||||
:class:`str`
|
||||
The string to use when a command has not been found.
|
||||
"""
|
||||
return 'No command called "{}" found.'.format(string)
|
||||
return f'No command called "{string}" found.'
|
||||
|
||||
def subcommand_not_found(self, command, string):
|
||||
"""|maybecoro|
|
||||
@ -535,8 +534,8 @@ class HelpCommand:
|
||||
The string to use when the command did not have the subcommand requested.
|
||||
"""
|
||||
if isinstance(command, Group) and len(command.all_commands) > 0:
|
||||
return 'Command "{0.qualified_name}" has no subcommand named {1}'.format(command, string)
|
||||
return 'Command "{0.qualified_name}" has no subcommands.'.format(command)
|
||||
return f'Command "{command.qualified_name}" has no subcommand named {string}'
|
||||
return f'Command "{command.qualified_name}" has no subcommands.'
|
||||
|
||||
async def filter_commands(self, commands, *, sort=False, key=None):
|
||||
"""|coro|
|
||||
@ -941,8 +940,8 @@ class DefaultHelpCommand(HelpCommand):
|
||||
def get_ending_note(self):
|
||||
""":class:`str`: Returns help command's ending note. This is mainly useful to override for i18n purposes."""
|
||||
command_name = self.invoked_with
|
||||
return "Type {0}{1} command for more info on a command.\n" \
|
||||
"You can also type {0}{1} category for more info on a category.".format(self.clean_prefix, command_name)
|
||||
return f"Type {self.clean_prefix}{command_name} command for more info on a command.\n" \
|
||||
f"You can also type {self.clean_prefix}{command_name} category for more info on a category."
|
||||
|
||||
def add_indented_commands(self, commands, *, heading, max_size=None):
|
||||
"""Indents a list of commands after the specified heading.
|
||||
@ -977,7 +976,7 @@ class DefaultHelpCommand(HelpCommand):
|
||||
for command in commands:
|
||||
name = command.name
|
||||
width = max_size - (get_width(name) - len(name))
|
||||
entry = '{0}{1:<{width}} {2}'.format(self.indent * ' ', name, command.short_doc, width=width)
|
||||
entry = f'{self.indent * " "}{name:<{width}} {command.short_doc}'
|
||||
self.paginator.add_line(self.shorten_text(entry))
|
||||
|
||||
async def send_pages(self):
|
||||
@ -1030,7 +1029,7 @@ class DefaultHelpCommand(HelpCommand):
|
||||
# <description> portion
|
||||
self.paginator.add_line(bot.description, empty=True)
|
||||
|
||||
no_category = '\u200b{0.no_category}:'.format(self)
|
||||
no_category = f'\u200b{self.no_category}:'
|
||||
def get_category(command, *, no_category=no_category):
|
||||
cog = command.cog
|
||||
return cog.qualified_name + ':' if cog is not None else no_category
|
||||
@ -1154,7 +1153,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
"You can also use `{0}{1} [category]` for more info on a category.".format(self.clean_prefix, command_name)
|
||||
|
||||
def get_command_signature(self, command):
|
||||
return '%s%s %s' % (self.clean_prefix, command.qualified_name, command.signature)
|
||||
return f'{self.clean_prefix}{command.qualified_name} {command.signature}'
|
||||
|
||||
def get_ending_note(self):
|
||||
"""Return the help command's ending note. This is mainly useful to override for i18n purposes.
|
||||
@ -1186,7 +1185,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
if commands:
|
||||
# U+2002 Middle Dot
|
||||
joined = '\u2002'.join(c.name for c in commands)
|
||||
self.paginator.add_line('__**%s**__' % heading)
|
||||
self.paginator.add_line(f'__**{heading}**__')
|
||||
self.paginator.add_line(joined)
|
||||
|
||||
def add_subcommand_formatting(self, command):
|
||||
@ -1220,7 +1219,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
aliases: Sequence[:class:`str`]
|
||||
A list of aliases to format.
|
||||
"""
|
||||
self.paginator.add_line('**%s** %s' % (self.aliases_heading, ', '.join(aliases)), empty=True)
|
||||
self.paginator.add_line(f'**{self.aliases_heading}** {", ".join(aliases)}', empty=True)
|
||||
|
||||
def add_command_formatting(self, command):
|
||||
"""A utility function to format commands and groups.
|
||||
@ -1273,7 +1272,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
if note:
|
||||
self.paginator.add_line(note, empty=True)
|
||||
|
||||
no_category = '\u200b{0.no_category}'.format(self)
|
||||
no_category = f'\u200b{self.no_category}'
|
||||
def get_category(command, *, no_category=no_category):
|
||||
cog = command.cog
|
||||
return cog.qualified_name if cog is not None else no_category
|
||||
@ -1306,7 +1305,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
|
||||
filtered = await self.filter_commands(cog.get_commands(), sort=self.sort_commands)
|
||||
if filtered:
|
||||
self.paginator.add_line('**%s %s**' % (cog.qualified_name, self.commands_heading))
|
||||
self.paginator.add_line(f'**{cog.qualified_name} {self.commands_heading}**')
|
||||
for command in filtered:
|
||||
self.add_subcommand_formatting(command)
|
||||
|
||||
@ -1326,7 +1325,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
if note:
|
||||
self.paginator.add_line(note, empty=True)
|
||||
|
||||
self.paginator.add_line('**%s**' % self.commands_heading)
|
||||
self.paginator.add_line(f'**{self.commands_heading}**')
|
||||
for command in filtered:
|
||||
self.add_subcommand_formatting(command)
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -37,6 +35,10 @@ from discord.backoff import ExponentialBackoff
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
__all__ = (
|
||||
'loop',
|
||||
)
|
||||
|
||||
class Loop:
|
||||
"""A background task helper that abstracts the loop and reconnection logic for you.
|
||||
|
||||
@ -289,9 +291,9 @@ class Loop:
|
||||
|
||||
for exc in exceptions:
|
||||
if not inspect.isclass(exc):
|
||||
raise TypeError('{0!r} must be a class.'.format(exc))
|
||||
raise TypeError(f'{exc!r} must be a class.')
|
||||
if not issubclass(exc, BaseException):
|
||||
raise TypeError('{0!r} must inherit from BaseException.'.format(exc))
|
||||
raise TypeError(f'{exc!r} must inherit from BaseException.')
|
||||
|
||||
self._valid_exception = (*self._valid_exception, *exceptions)
|
||||
|
||||
@ -345,7 +347,7 @@ class Loop:
|
||||
|
||||
async def _error(self, *args):
|
||||
exception = args[-1]
|
||||
print('Unhandled exception in internal background task {0.__name__!r}.'.format(self.coro), file=sys.stderr)
|
||||
print(f'Unhandled exception in internal background task {self.coro.__name__!r}.', file=sys.stderr)
|
||||
traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr)
|
||||
|
||||
def before_loop(self, coro):
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -27,6 +25,10 @@ DEALINGS IN THE SOFTWARE.
|
||||
import os.path
|
||||
import io
|
||||
|
||||
__all__ = (
|
||||
'File',
|
||||
)
|
||||
|
||||
class File:
|
||||
r"""A parameter object used for :meth:`abc.Messageable.send`
|
||||
for sending file objects.
|
||||
@ -65,7 +67,7 @@ class File:
|
||||
|
||||
if isinstance(fp, io.IOBase):
|
||||
if not (fp.seekable() and fp.readable()):
|
||||
raise ValueError('File buffer {!r} must be seekable and readable'.format(fp))
|
||||
raise ValueError(f'File buffer {fp!r} must be seekable and readable')
|
||||
self.fp = fp
|
||||
self._original_pos = fp.tell()
|
||||
self._owner = False
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,6 +22,10 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, ClassVar, Dict, Generic, Iterator, List, Optional, Tuple, Type, TypeVar, overload
|
||||
|
||||
from .enums import UserFlags
|
||||
|
||||
__all__ = (
|
||||
@ -34,27 +36,38 @@ __all__ = (
|
||||
'MemberCacheFlags',
|
||||
)
|
||||
|
||||
class flag_value:
|
||||
def __init__(self, func):
|
||||
FV = TypeVar('FV', bound='flag_value')
|
||||
BF = TypeVar('BF', bound='BaseFlags')
|
||||
|
||||
class flag_value(Generic[BF]):
|
||||
def __init__(self, func: Callable[[Any], int]):
|
||||
self.flag = func(None)
|
||||
self.__doc__ = func.__doc__
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
@overload
|
||||
def __get__(self: FV, instance: None, owner: Type[BF]) -> FV:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __get__(self, instance: BF, owner: Type[BF]) -> bool:
|
||||
...
|
||||
|
||||
def __get__(self, instance: Optional[BF], owner: Type[BF]) -> Any:
|
||||
if instance is None:
|
||||
return self
|
||||
return instance._has_flag(self.flag)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
def __set__(self, instance: BF, value: bool) -> None:
|
||||
instance._set_flag(self.flag, value)
|
||||
|
||||
def __repr__(self):
|
||||
return '<flag_value flag={.flag!r}>'.format(self)
|
||||
return f'<flag_value flag={self.flag!r}>'
|
||||
|
||||
class alias_flag_value(flag_value):
|
||||
pass
|
||||
|
||||
def fill_with_flags(*, inverted=False):
|
||||
def decorator(cls):
|
||||
def fill_with_flags(*, inverted: bool = False):
|
||||
def decorator(cls: Type[BF]):
|
||||
cls.VALID_FLAGS = {
|
||||
name: value.flag
|
||||
for name, value in cls.__dict__.items()
|
||||
@ -72,13 +85,18 @@ def fill_with_flags(*, inverted=False):
|
||||
|
||||
# n.b. flags must inherit from this and use the decorator above
|
||||
class BaseFlags:
|
||||
VALID_FLAGS: ClassVar[Dict[str, int]]
|
||||
DEFAULT_VALUE: ClassVar[int]
|
||||
|
||||
value: int
|
||||
|
||||
__slots__ = ('value',)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, **kwargs: bool):
|
||||
self.value = self.DEFAULT_VALUE
|
||||
for key, value in kwargs.items():
|
||||
if key not in self.VALID_FLAGS:
|
||||
raise TypeError('%r is not a valid flag name.' % key)
|
||||
raise TypeError(f'{key!r} is not a valid flag name.')
|
||||
setattr(self, key, value)
|
||||
|
||||
@classmethod
|
||||
@ -87,19 +105,19 @@ class BaseFlags:
|
||||
self.value = value
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
return isinstance(other, self.__class__) and self.value == other.value
|
||||
|
||||
def __ne__(self, other):
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s value=%s>' % (self.__class__.__name__, self.value)
|
||||
def __repr__(self) -> str:
|
||||
return f'<{self.__class__.__name__} value={self.value}>'
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> Iterator[Tuple[str, bool]]:
|
||||
for name, value in self.__class__.__dict__.items():
|
||||
if isinstance(value, alias_flag_value):
|
||||
continue
|
||||
@ -107,16 +125,16 @@ class BaseFlags:
|
||||
if isinstance(value, flag_value):
|
||||
yield (name, self._has_flag(value.flag))
|
||||
|
||||
def _has_flag(self, o):
|
||||
def _has_flag(self, o: int) -> bool:
|
||||
return (self.value & o) == o
|
||||
|
||||
def _set_flag(self, o, toggle):
|
||||
def _set_flag(self, o: int, toggle: bool) -> None:
|
||||
if toggle is True:
|
||||
self.value |= o
|
||||
elif toggle is False:
|
||||
self.value &= ~o
|
||||
else:
|
||||
raise TypeError('Value to set for %s must be a bool.' % self.__class__.__name__)
|
||||
raise TypeError(f'Value to set for {self.__class__.__name__} must be a bool.')
|
||||
|
||||
@fill_with_flags(inverted=True)
|
||||
class SystemChannelFlags(BaseFlags):
|
||||
@ -152,6 +170,7 @@ class SystemChannelFlags(BaseFlags):
|
||||
representing the currently available flags. You should query
|
||||
flags via the properties rather than using this raw value.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
# For some reason the flags for system channels are "inverted"
|
||||
@ -159,10 +178,10 @@ class SystemChannelFlags(BaseFlags):
|
||||
# Since this is counter-intuitive from an API perspective and annoying
|
||||
# these will be inverted automatically
|
||||
|
||||
def _has_flag(self, o):
|
||||
def _has_flag(self, o: int) -> bool:
|
||||
return (self.value & o) != o
|
||||
|
||||
def _set_flag(self, o, toggle):
|
||||
def _set_flag(self, o: int, toggle: bool) -> None:
|
||||
if toggle is True:
|
||||
self.value &= ~o
|
||||
elif toggle is False:
|
||||
@ -212,6 +231,7 @@ class MessageFlags(BaseFlags):
|
||||
representing the currently available flags. You should query
|
||||
flags via the properties rather than using this raw value.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
@flag_value
|
||||
@ -348,7 +368,7 @@ class PublicUserFlags(BaseFlags):
|
||||
"""
|
||||
return UserFlags.verified_bot_developer.value
|
||||
|
||||
def all(self):
|
||||
def all(self) -> List[UserFlags]:
|
||||
"""List[:class:`UserFlags`]: Returns all public flags the user has."""
|
||||
return [public_flag for public_flag in UserFlags if self._has_flag(public_flag.value)]
|
||||
|
||||
@ -395,11 +415,11 @@ class Intents(BaseFlags):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, **kwargs: bool):
|
||||
self.value = self.DEFAULT_VALUE
|
||||
for key, value in kwargs.items():
|
||||
if key not in self.VALID_FLAGS:
|
||||
raise TypeError('%r is not a valid flag name.' % key)
|
||||
raise TypeError(f'{key!r} is not a valid flag name.')
|
||||
setattr(self, key, value)
|
||||
|
||||
@classmethod
|
||||
@ -419,7 +439,7 @@ class Intents(BaseFlags):
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
def all(cls: Type[Intents]) -> Intents:
|
||||
"""A factory method that creates a :class:`Intents` with everything enabled."""
|
||||
bits = max(cls.VALID_FLAGS.values()).bit_length()
|
||||
value = (1 << bits) - 1
|
||||
@ -428,14 +448,14 @@ class Intents(BaseFlags):
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def none(cls):
|
||||
def none(cls: Type[Intents]) -> Intents:
|
||||
"""A factory method that creates a :class:`Intents` with everything disabled."""
|
||||
self = cls.__new__(cls)
|
||||
self.value = self.DEFAULT_VALUE
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
def default(cls: Type[Intents]) -> Intents:
|
||||
"""A factory method that creates a :class:`Intents` with everything enabled
|
||||
except :attr:`presences` and :attr:`members`.
|
||||
"""
|
||||
@ -844,16 +864,16 @@ class MemberCacheFlags(BaseFlags):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
def __init__(self, **kwargs: bool):
|
||||
bits = max(self.VALID_FLAGS.values()).bit_length()
|
||||
self.value = (1 << bits) - 1
|
||||
for key, value in kwargs.items():
|
||||
if key not in self.VALID_FLAGS:
|
||||
raise TypeError('%r is not a valid flag name.' % key)
|
||||
raise TypeError(f'{key!r} is not a valid flag name.')
|
||||
setattr(self, key, value)
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
def all(cls: Type[MemberCacheFlags]) -> MemberCacheFlags:
|
||||
"""A factory method that creates a :class:`MemberCacheFlags` with everything enabled."""
|
||||
bits = max(cls.VALID_FLAGS.values()).bit_length()
|
||||
value = (1 << bits) - 1
|
||||
@ -862,7 +882,7 @@ class MemberCacheFlags(BaseFlags):
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def none(cls):
|
||||
def none(cls: Type[MemberCacheFlags]) -> MemberCacheFlags:
|
||||
"""A factory method that creates a :class:`MemberCacheFlags` with everything disabled."""
|
||||
self = cls.__new__(cls)
|
||||
self.value = self.DEFAULT_VALUE
|
||||
@ -905,7 +925,7 @@ class MemberCacheFlags(BaseFlags):
|
||||
return 4
|
||||
|
||||
@classmethod
|
||||
def from_intents(cls, intents):
|
||||
def from_intents(cls: Type[MemberCacheFlags], intents: Intents) -> MemberCacheFlags:
|
||||
"""A factory method that creates a :class:`MemberCacheFlags` based on
|
||||
the currently selected :class:`Intents`.
|
||||
|
||||
@ -933,7 +953,7 @@ class MemberCacheFlags(BaseFlags):
|
||||
|
||||
return self
|
||||
|
||||
def _verify_intents(self, intents):
|
||||
def _verify_intents(self, intents: Intents):
|
||||
if self.online and not intents.presences:
|
||||
raise ValueError('MemberCacheFlags.online requires Intents.presences enabled')
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -162,8 +160,8 @@ class KeepAliveHandler(threading.Thread):
|
||||
except KeyError:
|
||||
msg = self.block_msg
|
||||
else:
|
||||
stack = traceback.format_stack(frame)
|
||||
msg = '%s\nLoop thread traceback (most recent call last):\n%s' % (self.block_msg, ''.join(stack))
|
||||
stack = ''.join(traceback.format_stack(frame))
|
||||
msg = f'{self.block_msg}\nLoop thread traceback (most recent call last):\n{stack}'
|
||||
log.warning(msg, self.shard_id, total)
|
||||
|
||||
except Exception:
|
||||
@ -380,9 +378,6 @@ class DiscordWebSocket:
|
||||
}
|
||||
}
|
||||
|
||||
if not self._connection.is_bot:
|
||||
payload['d']['synced_guilds'] = []
|
||||
|
||||
if self.shard_id is not None and self.shard_count is not None:
|
||||
payload['d']['shard'] = [self.shard_id, self.shard_count]
|
||||
|
||||
@ -624,13 +619,6 @@ class DiscordWebSocket:
|
||||
log.debug('Sending "%s" to change status', sent)
|
||||
await self.send(sent)
|
||||
|
||||
async def request_sync(self, guild_ids):
|
||||
payload = {
|
||||
'op': self.GUILD_SYNC,
|
||||
'd': list(guild_ids)
|
||||
}
|
||||
await self.send_as_json(payload)
|
||||
|
||||
async def request_chunks(self, guild_id, query=None, *, limit, user_ids=None, presences=False, nonce=None):
|
||||
payload = {
|
||||
'op': self.REQUEST_MEMBERS,
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -46,6 +44,9 @@ from .asset import Asset
|
||||
from .flags import SystemChannelFlags
|
||||
from .integrations import Integration
|
||||
|
||||
__all__ = (
|
||||
'Guild',
|
||||
)
|
||||
|
||||
BanEntry = namedtuple('BanEntry', 'reason user')
|
||||
_GuildLimit = namedtuple('_GuildLimit', 'emoji bitrate filesize')
|
||||
@ -212,11 +213,14 @@ class Guild(Hashable):
|
||||
|
||||
def __repr__(self):
|
||||
attrs = (
|
||||
'id', 'name', 'shard_id', 'chunked'
|
||||
('id', self.id),
|
||||
('name', self.name),
|
||||
('shard_id', self.shard_id),
|
||||
('chunked', self.chunked),
|
||||
('member_count', getattr(self, '_member_count', None)),
|
||||
)
|
||||
resolved = ['%s=%r' % (attr, getattr(self, attr)) for attr in attrs]
|
||||
resolved.append('member_count=%r' % getattr(self, '_member_count', None))
|
||||
return '<Guild %s>' % ' '.join(resolved)
|
||||
inner = ' '.join('%s=%r' % t for t in attrs)
|
||||
return f'<Guild {inner}>'
|
||||
|
||||
def _update_voice_state(self, data, channel_id):
|
||||
user_id = int(data['user_id'])
|
||||
@ -1352,7 +1356,8 @@ class Guild(Hashable):
|
||||
Pass ``None`` to fetch all members. Note that this is potentially slow.
|
||||
after: Optional[Union[:class:`.abc.Snowflake`, :class:`datetime.datetime`]]
|
||||
Retrieve members after this date or object.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
|
||||
Raises
|
||||
------
|
||||
@ -1560,7 +1565,7 @@ class Guild(Hashable):
|
||||
"""
|
||||
|
||||
if not isinstance(days, int):
|
||||
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
|
||||
raise InvalidArgument(f'Expected int for ``days``, received {days.__class__.__name__} instead.')
|
||||
|
||||
if roles:
|
||||
roles = [str(role.id) for role in roles]
|
||||
@ -1646,7 +1651,7 @@ class Guild(Hashable):
|
||||
"""
|
||||
|
||||
if not isinstance(days, int):
|
||||
raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
|
||||
raise InvalidArgument(f'Expected int for ``days``, received {days.__class__.__name__} instead.')
|
||||
|
||||
if roles:
|
||||
roles = [str(role.id) for role in roles]
|
||||
@ -1949,7 +1954,7 @@ class Guild(Hashable):
|
||||
valid_keys = ('name', 'permissions', 'color', 'hoist', 'mentionable')
|
||||
for key in fields:
|
||||
if key not in valid_keys:
|
||||
raise InvalidArgument('%r is not a valid field.' % key)
|
||||
raise InvalidArgument(f'{key!r} is not a valid field.')
|
||||
|
||||
data = await self._state.http.create_role(self.id, reason=reason, **fields)
|
||||
role = Role(guild=self, data=data, state=self._state)
|
||||
@ -2142,29 +2147,6 @@ class Guild(Hashable):
|
||||
payload['max_age'] = 0
|
||||
return Invite(state=self._state, data=payload)
|
||||
|
||||
@utils.deprecated()
|
||||
def ack(self):
|
||||
"""|coro|
|
||||
|
||||
Marks every message in this guild as read.
|
||||
|
||||
The user must not be a bot user.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Raises
|
||||
-------
|
||||
HTTPException
|
||||
Acking failed.
|
||||
ClientException
|
||||
You must not be a bot user.
|
||||
"""
|
||||
|
||||
state = self._state
|
||||
if state.is_bot:
|
||||
raise ClientException('Must not be a bot account to ack messages.')
|
||||
return state.http.ack_guild(self.id)
|
||||
|
||||
def audit_logs(self, *, limit=100, before=None, after=None, oldest_first=None, user=None, action=None):
|
||||
"""Returns an :class:`AsyncIterator` that enables receiving the guild's audit logs.
|
||||
|
||||
@ -2194,10 +2176,12 @@ class Guild(Hashable):
|
||||
The number of entries to retrieve. If ``None`` retrieve all entries.
|
||||
before: Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]
|
||||
Retrieve entries before this date or entry.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
after: Union[:class:`abc.Snowflake`, :class:`datetime.datetime`]
|
||||
Retrieve entries after this date or entry.
|
||||
If a date is provided it must be a timezone-naive datetime representing UTC time.
|
||||
If a datetime is provided, it is recommended to use a UTC aware datetime.
|
||||
If the datetime is naive, it is assumed to be local time.
|
||||
oldest_first: :class:`bool`
|
||||
If set to ``True``, return entries in oldest->newest order. Defaults to ``True`` if
|
||||
``after`` is specified, otherwise ``False``.
|
||||
|
673
discord/http.py
673
discord/http.py
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -30,6 +28,11 @@ from .user import User
|
||||
from .errors import InvalidArgument
|
||||
from .enums import try_enum, ExpireBehaviour
|
||||
|
||||
__all__ = (
|
||||
'IntegrationAccount',
|
||||
'Integration',
|
||||
)
|
||||
|
||||
class IntegrationAccount:
|
||||
"""Represents an integration account.
|
||||
|
||||
@ -84,7 +87,7 @@ class Integration:
|
||||
account: :class:`IntegrationAccount`
|
||||
The integration account information.
|
||||
synced_at: :class:`datetime.datetime`
|
||||
When the integration was last synced.
|
||||
An aware UTC datetime representing when the integration was last synced.
|
||||
"""
|
||||
|
||||
__slots__ = ('id', '_state', 'guild', 'name', 'enabled', 'type',
|
||||
@ -186,7 +189,7 @@ class Integration:
|
||||
Syncing the integration failed.
|
||||
"""
|
||||
await self._state.http.sync_integration(self.guild.id, self.id)
|
||||
self.synced_at = datetime.datetime.utcnow()
|
||||
self.synced_at = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
async def delete(self):
|
||||
"""|coro|
|
||||
|
104
discord/interactions.py
Normal file
104
discord/interactions.py
Normal file
@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present 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 __future__ import annotations
|
||||
|
||||
from . import utils
|
||||
from .enums import try_enum, InteractionType
|
||||
|
||||
__all__ = (
|
||||
'Interaction',
|
||||
)
|
||||
|
||||
class Interaction:
|
||||
"""Represents a Discord interaction.
|
||||
|
||||
An interaction happens when a user does an action that needs to
|
||||
be notified. Current examples are slash commands but future examples
|
||||
include forms and buttons.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
id: :class:`int`
|
||||
The interaction's ID.
|
||||
type: :class:`InteractionType`
|
||||
The interaction type.
|
||||
guild_id: Optional[:class:`int`]
|
||||
The guild ID the interaction was sent from.
|
||||
channel_id: Optional[:class:`int`]
|
||||
The channel ID the interaction was sent from.
|
||||
application_id: :class:`int`
|
||||
The application ID that the interaction was for.
|
||||
user: Optional[Union[:class:`User`, :class:`Member`]]
|
||||
The user or member that sent the interaction.
|
||||
token: :class:`str`
|
||||
The token to continue the interaction. These are valid
|
||||
for 15 minutes.
|
||||
"""
|
||||
__slots__ = (
|
||||
'id',
|
||||
'type',
|
||||
'guild_id',
|
||||
'channel_id',
|
||||
'data',
|
||||
'application_id',
|
||||
'user',
|
||||
'token',
|
||||
'version',
|
||||
'_state',
|
||||
)
|
||||
|
||||
def __init__(self, *, data, state=None):
|
||||
self._state = state
|
||||
self._from_data(data)
|
||||
|
||||
def _from_data(self, data):
|
||||
self.id = int(data['id'])
|
||||
self.type = try_enum(InteractionType, data['type'])
|
||||
self.data = data.get('data')
|
||||
self.token = data['token']
|
||||
self.version = data['version']
|
||||
self.channel_id = utils._get_as_snowflake(data, 'channel_id')
|
||||
self.guild_id = utils._get_as_snowflake(data, 'guild_id')
|
||||
self.application_id = utils._get_as_snowflake(data, 'application_id')
|
||||
|
||||
@property
|
||||
def guild(self):
|
||||
"""Optional[:class:`Guild`]: The guild the interaction was sent from."""
|
||||
return self._state and self._state.get_guild(self.guild_id)
|
||||
|
||||
@property
|
||||
def channel(self):
|
||||
"""Optional[:class:`abc.GuildChannel`]: The channel the interaction was sent from.
|
||||
|
||||
Note that due to a Discord limitation, DM channels are not resolved since there is
|
||||
no data to complete them.
|
||||
"""
|
||||
guild = self.guild
|
||||
return guild and guild.get_channel(self.channel_id)
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -30,6 +28,12 @@ from .object import Object
|
||||
from .mixins import Hashable
|
||||
from .enums import ChannelType, VerificationLevel, try_enum
|
||||
|
||||
__all__ = (
|
||||
'PartialInviteChannel',
|
||||
'PartialInviteGuild',
|
||||
'Invite',
|
||||
)
|
||||
|
||||
class PartialInviteChannel:
|
||||
"""Represents a "partial" invite channel.
|
||||
|
||||
@ -80,7 +84,7 @@ class PartialInviteChannel:
|
||||
@property
|
||||
def mention(self):
|
||||
""":class:`str`: The string that allows you to mention the channel."""
|
||||
return '<#%s>' % self.id
|
||||
return f'<#{self.id}>'
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
@ -267,7 +271,7 @@ class Invite(Hashable):
|
||||
revoked: :class:`bool`
|
||||
Indicates if the invite has been revoked.
|
||||
created_at: :class:`datetime.datetime`
|
||||
A datetime object denoting the time the invite was created.
|
||||
An aware UTC datetime object denoting the time the invite was created.
|
||||
temporary: :class:`bool`
|
||||
Indicates that the invite grants temporary membership.
|
||||
If ``True``, members who joined via this invite will be kicked upon disconnect.
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,20 +22,43 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
from typing import TYPE_CHECKING, TypeVar, Optional, Any, Callable, Union, List, AsyncIterator, Coroutine
|
||||
|
||||
from .errors import NoMoreItems
|
||||
from .utils import time_snowflake, maybe_coroutine
|
||||
from .object import Object
|
||||
from .audit_logs import AuditLogEntry
|
||||
|
||||
__all__ = (
|
||||
'ReactionIterator',
|
||||
'HistoryIterator',
|
||||
'AuditLogIterator',
|
||||
'GuildIterator',
|
||||
'MemberIterator',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .member import Member
|
||||
from .user import User
|
||||
from .message import Message
|
||||
from .audit_logs import AuditLogEntry
|
||||
from .guild import Guild
|
||||
|
||||
T = TypeVar('T')
|
||||
OT = TypeVar('OT')
|
||||
_Func = Callable[[T], Union[OT, Coroutine[Any, Any, OT]]]
|
||||
_Predicate = Callable[[T], Union[T, Coroutine[Any, Any, T]]]
|
||||
|
||||
OLDEST_OBJECT = Object(id=0)
|
||||
|
||||
class _AsyncIterator:
|
||||
class _AsyncIterator(AsyncIterator[T]):
|
||||
__slots__ = ()
|
||||
|
||||
def get(self, **attrs):
|
||||
def get(self, **attrs: Any) -> Optional[T]:
|
||||
def predicate(elem):
|
||||
for attr, val in attrs.items():
|
||||
nested = attr.split('__')
|
||||
@ -51,7 +72,7 @@ class _AsyncIterator:
|
||||
|
||||
return self.find(predicate)
|
||||
|
||||
async def find(self, predicate):
|
||||
async def find(self, predicate: _Predicate[T]) -> Optional[T]:
|
||||
while True:
|
||||
try:
|
||||
elem = await self.next()
|
||||
@ -62,47 +83,35 @@ class _AsyncIterator:
|
||||
if ret:
|
||||
return elem
|
||||
|
||||
def chunk(self, max_size):
|
||||
def chunk(self, max_size: int) -> _ChunkedAsyncIterator[T]:
|
||||
if max_size <= 0:
|
||||
raise ValueError('async iterator chunk sizes must be greater than 0.')
|
||||
return _ChunkedAsyncIterator(self, max_size)
|
||||
|
||||
def map(self, func):
|
||||
def map(self, func: _Func[T, OT]) -> _MappedAsyncIterator[OT]:
|
||||
return _MappedAsyncIterator(self, func)
|
||||
|
||||
def filter(self, predicate):
|
||||
def filter(self, predicate: _Predicate[T]) -> _FilteredAsyncIterator[T]:
|
||||
return _FilteredAsyncIterator(self, predicate)
|
||||
|
||||
async def flatten(self):
|
||||
ret = []
|
||||
while True:
|
||||
try:
|
||||
item = await self.next()
|
||||
except NoMoreItems:
|
||||
return ret
|
||||
else:
|
||||
ret.append(item)
|
||||
async def flatten(self) -> List[T]:
|
||||
return [element async for element in self]
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
async def __anext__(self) -> T:
|
||||
try:
|
||||
msg = await self.next()
|
||||
return await self.next()
|
||||
except NoMoreItems:
|
||||
raise StopAsyncIteration()
|
||||
else:
|
||||
return msg
|
||||
|
||||
def _identity(x):
|
||||
return x
|
||||
|
||||
class _ChunkedAsyncIterator(_AsyncIterator):
|
||||
class _ChunkedAsyncIterator(_AsyncIterator[T]):
|
||||
def __init__(self, iterator, max_size):
|
||||
self.iterator = iterator
|
||||
self.max_size = max_size
|
||||
|
||||
async def next(self):
|
||||
async def next(self) -> T:
|
||||
ret = []
|
||||
n = 0
|
||||
while n < self.max_size:
|
||||
@ -117,17 +126,17 @@ class _ChunkedAsyncIterator(_AsyncIterator):
|
||||
n += 1
|
||||
return ret
|
||||
|
||||
class _MappedAsyncIterator(_AsyncIterator):
|
||||
class _MappedAsyncIterator(_AsyncIterator[T]):
|
||||
def __init__(self, iterator, func):
|
||||
self.iterator = iterator
|
||||
self.func = func
|
||||
|
||||
async def next(self):
|
||||
async def next(self) -> T:
|
||||
# this raises NoMoreItems and will propagate appropriately
|
||||
item = await self.iterator.next()
|
||||
return await maybe_coroutine(self.func, item)
|
||||
|
||||
class _FilteredAsyncIterator(_AsyncIterator):
|
||||
class _FilteredAsyncIterator(_AsyncIterator[T]):
|
||||
def __init__(self, iterator, predicate):
|
||||
self.iterator = iterator
|
||||
|
||||
@ -136,7 +145,7 @@ class _FilteredAsyncIterator(_AsyncIterator):
|
||||
|
||||
self.predicate = predicate
|
||||
|
||||
async def next(self):
|
||||
async def next(self) -> T:
|
||||
getter = self.iterator.next
|
||||
pred = self.predicate
|
||||
while True:
|
||||
@ -146,7 +155,7 @@ class _FilteredAsyncIterator(_AsyncIterator):
|
||||
if ret:
|
||||
return item
|
||||
|
||||
class ReactionIterator(_AsyncIterator):
|
||||
class ReactionIterator(_AsyncIterator[Union['User', 'Member']]):
|
||||
def __init__(self, message, emoji, limit=100, after=None):
|
||||
self.message = message
|
||||
self.limit = limit
|
||||
@ -159,7 +168,7 @@ class ReactionIterator(_AsyncIterator):
|
||||
self.channel_id = message.channel.id
|
||||
self.users = asyncio.Queue()
|
||||
|
||||
async def next(self):
|
||||
async def next(self) -> T:
|
||||
if self.users.empty():
|
||||
await self.fill_users()
|
||||
|
||||
@ -194,7 +203,7 @@ class ReactionIterator(_AsyncIterator):
|
||||
else:
|
||||
await self.users.put(User(state=self.state, data=element))
|
||||
|
||||
class HistoryIterator(_AsyncIterator):
|
||||
class HistoryIterator(_AsyncIterator['Message']):
|
||||
"""Iterator for receiving a channel's message history.
|
||||
|
||||
The messages endpoint has two behaviours we care about here:
|
||||
@ -280,7 +289,7 @@ class HistoryIterator(_AsyncIterator):
|
||||
if (self.after and self.after != OLDEST_OBJECT):
|
||||
self._filter = lambda m: int(m['id']) > self.after.id
|
||||
|
||||
async def next(self):
|
||||
async def next(self) -> T:
|
||||
if self.messages.empty():
|
||||
await self.fill_messages()
|
||||
|
||||
@ -298,26 +307,6 @@ class HistoryIterator(_AsyncIterator):
|
||||
self.retrieve = r
|
||||
return r > 0
|
||||
|
||||
async def flatten(self):
|
||||
# this is similar to fill_messages except it uses a list instead
|
||||
# of a queue to place the messages in.
|
||||
result = []
|
||||
channel = await self.messageable._get_channel()
|
||||
self.channel = channel
|
||||
while self._get_retrieve():
|
||||
data = await self._retrieve_messages(self.retrieve)
|
||||
if len(data) < 100:
|
||||
self.limit = 0 # terminate the infinite loop
|
||||
|
||||
if self.reverse:
|
||||
data = reversed(data)
|
||||
if self._filter:
|
||||
data = filter(self._filter, data)
|
||||
|
||||
for element in data:
|
||||
result.append(self.state.create_message(channel=channel, data=element))
|
||||
return result
|
||||
|
||||
async def fill_messages(self):
|
||||
if not hasattr(self, 'channel'):
|
||||
# do the required set up
|
||||
@ -371,7 +360,7 @@ class HistoryIterator(_AsyncIterator):
|
||||
return data
|
||||
return []
|
||||
|
||||
class AuditLogIterator(_AsyncIterator):
|
||||
class AuditLogIterator(_AsyncIterator['AuditLogEntry']):
|
||||
def __init__(self, guild, limit=None, before=None, after=None, oldest_first=None, user_id=None, action_type=None):
|
||||
if isinstance(before, datetime.datetime):
|
||||
before = Object(id=time_snowflake(before, high=False))
|
||||
@ -433,7 +422,7 @@ class AuditLogIterator(_AsyncIterator):
|
||||
self.after = Object(id=int(entries[0]['id']))
|
||||
return data.get('users', []), entries
|
||||
|
||||
async def next(self):
|
||||
async def next(self) -> T:
|
||||
if self.entries.empty():
|
||||
await self._fill()
|
||||
|
||||
@ -476,7 +465,7 @@ class AuditLogIterator(_AsyncIterator):
|
||||
await self.entries.put(AuditLogEntry(data=element, users=self._users, guild=self.guild))
|
||||
|
||||
|
||||
class GuildIterator(_AsyncIterator):
|
||||
class GuildIterator(_AsyncIterator['Guild']):
|
||||
"""Iterator for receiving the client's guilds.
|
||||
|
||||
The guilds endpoint has the same two behaviours as described
|
||||
@ -530,7 +519,7 @@ class GuildIterator(_AsyncIterator):
|
||||
else:
|
||||
self._retrieve_guilds = self._retrieve_guilds_before_strategy
|
||||
|
||||
async def next(self):
|
||||
async def next(self) -> T:
|
||||
if self.guilds.empty():
|
||||
await self.fill_guilds()
|
||||
|
||||
@ -552,20 +541,6 @@ class GuildIterator(_AsyncIterator):
|
||||
from .guild import Guild
|
||||
return Guild(state=self.state, data=data)
|
||||
|
||||
async def flatten(self):
|
||||
result = []
|
||||
while self._get_retrieve():
|
||||
data = await self._retrieve_guilds(self.retrieve)
|
||||
if len(data) < 100:
|
||||
self.limit = 0
|
||||
|
||||
if self._filter:
|
||||
data = filter(self._filter, data)
|
||||
|
||||
for element in data:
|
||||
result.append(self.create_guild(element))
|
||||
return result
|
||||
|
||||
async def fill_guilds(self):
|
||||
if self._get_retrieve():
|
||||
data = await self._retrieve_guilds(self.retrieve)
|
||||
@ -602,7 +577,7 @@ class GuildIterator(_AsyncIterator):
|
||||
self.after = Object(id=int(data[0]['id']))
|
||||
return data
|
||||
|
||||
class MemberIterator(_AsyncIterator):
|
||||
class MemberIterator(_AsyncIterator['Member']):
|
||||
def __init__(self, guild, limit=1000, after=None):
|
||||
|
||||
if isinstance(after, datetime.datetime):
|
||||
@ -616,7 +591,7 @@ class MemberIterator(_AsyncIterator):
|
||||
self.get_members = self.state.http.get_members
|
||||
self.members = asyncio.Queue()
|
||||
|
||||
async def next(self):
|
||||
async def next(self) -> T:
|
||||
if self.members.empty():
|
||||
await self.fill_members()
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -41,6 +39,11 @@ from .enums import Status, try_enum
|
||||
from .colour import Colour
|
||||
from .object import Object
|
||||
|
||||
__all__ = (
|
||||
'VoiceState',
|
||||
'Member',
|
||||
)
|
||||
|
||||
class VoiceState:
|
||||
"""Represents a Discord user's voice state.
|
||||
|
||||
@ -69,7 +72,7 @@ class VoiceState:
|
||||
.. versionadded:: 1.7
|
||||
|
||||
requested_to_speak_at: Optional[:class:`datetime.datetime`]
|
||||
A datetime object that specifies the date and time in UTC that the member
|
||||
An aware datetime object that specifies the date and time in UTC that the member
|
||||
requested to speak. It will be ``None`` if they are not requesting to speak
|
||||
anymore or have been accepted to speak.
|
||||
|
||||
@ -113,7 +116,8 @@ class VoiceState:
|
||||
('requested_to_speak_at', self.requested_to_speak_at),
|
||||
('channel', self.channel)
|
||||
]
|
||||
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
|
||||
inner = ' '.join('%s=%r' % t for t in attrs)
|
||||
return f'<{self.__class__.__name__} {inner}>'
|
||||
|
||||
def flatten_user(cls):
|
||||
for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()):
|
||||
@ -129,7 +133,7 @@ def flatten_user(cls):
|
||||
# slotted members are implemented as member_descriptors in Type.__dict__
|
||||
if not hasattr(value, '__annotations__'):
|
||||
getter = attrgetter('_user.' + attr)
|
||||
setattr(cls, attr, property(getter, doc='Equivalent to :attr:`User.%s`' % attr))
|
||||
setattr(cls, attr, property(getter, doc=f'Equivalent to :attr:`User.{attr}`'))
|
||||
else:
|
||||
# Technically, this can also use attrgetter
|
||||
# However I'm not sure how I feel about "functions" returning properties
|
||||
@ -184,7 +188,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
Attributes
|
||||
----------
|
||||
joined_at: Optional[:class:`datetime.datetime`]
|
||||
A datetime object that specifies the date and time in UTC that the member joined the guild.
|
||||
An aware datetime object that specifies the date and time in UTC that the member joined the guild.
|
||||
If the member left and rejoined the guild, this will be the latest date. In certain cases, this can be ``None``.
|
||||
activities: Tuple[Union[:class:`BaseActivity`, :class:`Spotify`]]
|
||||
The activities that the user is currently doing.
|
||||
@ -204,7 +208,7 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
|
||||
.. versionadded:: 1.6
|
||||
premium_since: Optional[:class:`datetime.datetime`]
|
||||
A datetime object that specifies the date and time in UTC when the member used their
|
||||
An aware 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``.
|
||||
"""
|
||||
|
||||
@ -232,8 +236,8 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
return self.id
|
||||
|
||||
def __repr__(self):
|
||||
return '<Member id={1.id} name={1.name!r} discriminator={1.discriminator!r}' \
|
||||
' bot={1.bot} nick={0.nick!r} guild={0.guild!r}>'.format(self, self._user)
|
||||
return f'<Member id={self._user.id} name={self._user.name!r} discriminator={self._user.discriminator!r}' \
|
||||
f' bot={self._user.bot} nick={self.nick!r} guild={self.guild!r}>'
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, _BaseUser) and other.id == self.id
|
||||
@ -432,8 +436,8 @@ class Member(discord.abc.Messageable, _BaseUser):
|
||||
def mention(self):
|
||||
""":class:`str`: Returns a string that allows you to mention the member."""
|
||||
if self.nick:
|
||||
return '<@!%s>' % self.id
|
||||
return '<@%s>' % self.id
|
||||
return f'<@!{self._user.id}>'
|
||||
return f'<@{self._user.id}>'
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,6 +22,10 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
'AllowedMentions',
|
||||
)
|
||||
|
||||
class _FakeBool:
|
||||
def __repr__(self):
|
||||
return 'True'
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -33,7 +31,6 @@ from . import utils
|
||||
from .reaction import Reaction
|
||||
from .emoji import Emoji
|
||||
from .partial_emoji import PartialEmoji
|
||||
from .calls import CallMessage
|
||||
from .enums import MessageType, ChannelType, try_enum
|
||||
from .errors import InvalidArgument, ClientException, HTTPException
|
||||
from .embeds import Embed
|
||||
@ -58,7 +55,7 @@ def convert_emoji_reaction(emoji):
|
||||
emoji = emoji.emoji
|
||||
|
||||
if isinstance(emoji, Emoji):
|
||||
return '%s:%s' % (emoji.name, emoji.id)
|
||||
return f'{emoji.name}:{emoji.id}'
|
||||
if isinstance(emoji, PartialEmoji):
|
||||
return emoji._as_reaction()
|
||||
if isinstance(emoji, str):
|
||||
@ -66,7 +63,7 @@ def convert_emoji_reaction(emoji):
|
||||
# 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))
|
||||
raise InvalidArgument(f'emoji argument must be str, Emoji, or Reaction not {emoji.__class__.__name__}.')
|
||||
|
||||
class Attachment(Hashable):
|
||||
"""Represents an attachment from Discord.
|
||||
@ -477,12 +474,6 @@ class Message(Hashable):
|
||||
channel: Union[:class:`abc.Messageable`]
|
||||
The :class:`TextChannel` that the message was sent from.
|
||||
Could be a :class:`DMChannel` or :class:`GroupChannel` if it's a private message.
|
||||
call: Optional[:class:`CallMessage`]
|
||||
The call that the message refers to. This is only applicable to messages of type
|
||||
:attr:`MessageType.call`.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
reference: Optional[:class:`~discord.MessageReference`]
|
||||
The message that this message references. This is only applicable to messages of
|
||||
type :attr:`MessageType.pins_add`, crossposted messages created by a
|
||||
@ -558,7 +549,7 @@ class Message(Hashable):
|
||||
'mention_everyone', 'embeds', 'id', 'mentions', 'author',
|
||||
'_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',
|
||||
'role_mentions', '_cs_raw_role_mentions', 'type', 'flags',
|
||||
'_cs_system_content', '_cs_guild', '_state', 'reactions', 'reference',
|
||||
'application', 'activity', 'stickers')
|
||||
|
||||
@ -572,7 +563,6 @@ class Message(Hashable):
|
||||
self.application = data.get('application')
|
||||
self.activity = data.get('activity')
|
||||
self.channel = channel
|
||||
self.call = None
|
||||
self._edited_timestamp = utils.parse_time(data['edited_timestamp'])
|
||||
self.type = try_enum(MessageType, data['type'])
|
||||
self.pinned = data['pinned']
|
||||
@ -605,9 +595,9 @@ class Message(Hashable):
|
||||
|
||||
ref.resolved = self.__class__(channel=chan, data=resolved, state=state)
|
||||
|
||||
for handler in ('author', 'member', 'mentions', 'mention_roles', 'call', 'flags'):
|
||||
for handler in ('author', 'member', 'mentions', 'mention_roles', 'flags'):
|
||||
try:
|
||||
getattr(self, '_handle_%s' % handler)(data[handler])
|
||||
getattr(self, f'_handle_{handler}')(data[handler])
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
@ -779,26 +769,6 @@ class Message(Hashable):
|
||||
if role is not None:
|
||||
self.role_mentions.append(role)
|
||||
|
||||
def _handle_call(self, call):
|
||||
if call is None or self.type is not MessageType.call:
|
||||
self.call = None
|
||||
return
|
||||
|
||||
# we get the participant source from the mentions array or
|
||||
# the author
|
||||
|
||||
participants = []
|
||||
for uid in map(int, call.get('participants', [])):
|
||||
if uid == self.author.id:
|
||||
participants.append(self.author)
|
||||
else:
|
||||
user = utils.find(lambda u: u.id == uid, self.mentions)
|
||||
if user is not None:
|
||||
participants.append(user)
|
||||
|
||||
call['participants'] = participants
|
||||
self.call = CallMessage(message=self, **call)
|
||||
|
||||
def _rebind_channel_reference(self, new_channel):
|
||||
self.channel = new_channel
|
||||
|
||||
@ -856,23 +826,23 @@ class Message(Hashable):
|
||||
.. note::
|
||||
|
||||
This *does not* affect markdown. If you want to escape
|
||||
or remove markdown then use :func:`utils.escape_markdown` or :func:`utils.remove_markdown`
|
||||
or remove markdown then use :func:`utils.escape_markdown` or :func:`utils.remove_markdown`
|
||||
respectively, along with this function.
|
||||
"""
|
||||
|
||||
transformations = {
|
||||
re.escape('<#%s>' % channel.id): '#' + channel.name
|
||||
re.escape(f'<#{channel.id}>'): '#' + channel.name
|
||||
for channel in self.channel_mentions
|
||||
}
|
||||
|
||||
mention_transforms = {
|
||||
re.escape('<@%s>' % member.id): '@' + member.display_name
|
||||
re.escape(f'<@{member.id}>'): '@' + member.display_name
|
||||
for member in self.mentions
|
||||
}
|
||||
|
||||
# add the <@!user_id> cases as well..
|
||||
second_mention_transforms = {
|
||||
re.escape('<@!%s>' % member.id): '@' + member.display_name
|
||||
re.escape(f'<@!{member.id}>'): '@' + member.display_name
|
||||
for member in self.mentions
|
||||
}
|
||||
|
||||
@ -881,7 +851,7 @@ class Message(Hashable):
|
||||
|
||||
if self.guild is not None:
|
||||
role_transforms = {
|
||||
re.escape('<@&%s>' % role.id): '@' + role.name
|
||||
re.escape(f'<@&{role.id}>'): '@' + role.name
|
||||
for role in self.role_mentions
|
||||
}
|
||||
transformations.update(role_transforms)
|
||||
@ -900,7 +870,7 @@ class Message(Hashable):
|
||||
|
||||
@property
|
||||
def edited_at(self):
|
||||
"""Optional[:class:`datetime.datetime`]: A naive UTC datetime object containing the edited time of the message."""
|
||||
"""Optional[:class:`datetime.datetime`]: An aware UTC datetime object containing the edited time of the message."""
|
||||
return self._edited_timestamp
|
||||
|
||||
@property
|
||||
@ -930,7 +900,7 @@ class Message(Hashable):
|
||||
return self.content
|
||||
|
||||
if self.type is MessageType.pins_add:
|
||||
return '{0.name} pinned a message to this channel.'.format(self.author)
|
||||
return f'{self.author.name} pinned a message to this channel.'
|
||||
|
||||
if self.type is MessageType.recipient_add:
|
||||
return '{0.name} added {1.name} to the group.'.format(self.author, self.mentions[0])
|
||||
@ -942,7 +912,7 @@ class Message(Hashable):
|
||||
return '{0.author.name} changed the channel name: {0.content}'.format(self)
|
||||
|
||||
if self.type is MessageType.channel_icon_change:
|
||||
return '{0.author.name} changed the channel icon.'.format(self)
|
||||
return f'{self.author.name} changed the channel icon.'
|
||||
|
||||
if self.type is MessageType.new_member:
|
||||
formats = [
|
||||
@ -961,27 +931,11 @@ class Message(Hashable):
|
||||
"Yay you made it, {0}!",
|
||||
]
|
||||
|
||||
# manually reconstruct the epoch with millisecond precision, because
|
||||
# datetime.datetime.timestamp() doesn't return the exact posix
|
||||
# timestamp with the precision that we need
|
||||
created_at_ms = int((self.created_at - datetime.datetime(1970, 1, 1)).total_seconds() * 1000)
|
||||
created_at_ms = int(self.created_at.timestamp() * 1000)
|
||||
return formats[created_at_ms % len(formats)].format(self.author.name)
|
||||
|
||||
if self.type is MessageType.call:
|
||||
# we're at the call message type now, which is a bit more complicated.
|
||||
# we can make the assumption that Message.channel is a PrivateChannel
|
||||
# with the type ChannelType.group or ChannelType.private
|
||||
call_ended = self.call.ended_timestamp is not None
|
||||
|
||||
if self.channel.me in self.call.participants:
|
||||
return '{0.author.name} started a call.'.format(self)
|
||||
elif call_ended:
|
||||
return 'You missed a call from {0.author.name}'.format(self)
|
||||
else:
|
||||
return '{0.author.name} started a call \N{EM DASH} Join the call.'.format(self)
|
||||
|
||||
if self.type is MessageType.premium_guild_subscription:
|
||||
return '{0.author.name} just boosted the server!'.format(self)
|
||||
return f'{self.author.name} just boosted the server!'
|
||||
|
||||
if self.type is MessageType.premium_guild_tier_1:
|
||||
return '{0.author.name} just boosted the server! {0.guild} has achieved **Level 1!**'.format(self)
|
||||
@ -1050,7 +1004,7 @@ class Message(Hashable):
|
||||
except HTTPException:
|
||||
pass
|
||||
|
||||
asyncio.ensure_future(delete(), loop=self._state.loop)
|
||||
asyncio.create_task(delete())
|
||||
else:
|
||||
try:
|
||||
await self._state.http.delete_message(self.channel.id, self.id)
|
||||
@ -1344,29 +1298,6 @@ class Message(Hashable):
|
||||
"""
|
||||
await self._state.http.clear_reactions(self.channel.id, self.id)
|
||||
|
||||
@utils.deprecated()
|
||||
async def ack(self):
|
||||
"""|coro|
|
||||
|
||||
Marks this message as read.
|
||||
|
||||
The user must not be a bot user.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Raises
|
||||
-------
|
||||
HTTPException
|
||||
Acking failed.
|
||||
ClientException
|
||||
You must not be a bot user.
|
||||
"""
|
||||
|
||||
state = self._state
|
||||
if state.is_bot:
|
||||
raise ClientException('Must not be a bot account to ack messages.')
|
||||
return await state.http.ack_message(self.channel.id, self.id)
|
||||
|
||||
async def reply(self, content=None, **kwargs):
|
||||
"""|coro|
|
||||
|
||||
@ -1486,7 +1417,7 @@ class PartialMessage(Hashable):
|
||||
|
||||
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))
|
||||
raise TypeError(f'Expected TextChannel or DMChannel not {type(channel)!r}')
|
||||
|
||||
self.channel = channel
|
||||
self._state = channel._state
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,6 +22,11 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
'EqualityComparable',
|
||||
'Hashable',
|
||||
)
|
||||
|
||||
class EqualityComparable:
|
||||
__slots__ = ()
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -27,6 +25,10 @@ DEALINGS IN THE SOFTWARE.
|
||||
from . import utils
|
||||
from .mixins import Hashable
|
||||
|
||||
__all__ = (
|
||||
'Object',
|
||||
)
|
||||
|
||||
class Object(Hashable):
|
||||
"""Represents a generic Discord object.
|
||||
|
||||
@ -65,12 +67,12 @@ class Object(Hashable):
|
||||
try:
|
||||
id = int(id)
|
||||
except ValueError:
|
||||
raise TypeError('id parameter must be convertable to int not {0.__class__!r}'.format(id)) from None
|
||||
raise TypeError(f'id parameter must be convertable to int not {id.__class__!r}') from None
|
||||
else:
|
||||
self.id = id
|
||||
|
||||
def __repr__(self):
|
||||
return '<Object id=%r>' % self.id
|
||||
return f'<Object id={self.id!r}>'
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -28,6 +26,12 @@ import struct
|
||||
|
||||
from .errors import DiscordException
|
||||
|
||||
__all__ = (
|
||||
'OggError',
|
||||
'OggPage',
|
||||
'OggStream',
|
||||
)
|
||||
|
||||
class OggError(DiscordException):
|
||||
"""An exception that is thrown for Ogg stream parsing errors."""
|
||||
pass
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -35,6 +33,12 @@ import sys
|
||||
|
||||
from .errors import DiscordException
|
||||
|
||||
__all__ = (
|
||||
'Encoder',
|
||||
'OpusError',
|
||||
'OpusNotLoaded',
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
c_int_ptr = ctypes.POINTER(ctypes.c_int)
|
||||
@ -185,7 +189,7 @@ def _load_default():
|
||||
_basedir = os.path.dirname(os.path.abspath(__file__))
|
||||
_bitness = struct.calcsize('P') * 8
|
||||
_target = 'x64' if _bitness > 32 else 'x86'
|
||||
_filename = os.path.join(_basedir, 'bin', 'libopus-0.{}.dll'.format(_target))
|
||||
_filename = os.path.join(_basedir, 'bin', f'libopus-0.{_target}.dll')
|
||||
_lib = libopus_loader(_filename)
|
||||
else:
|
||||
_lib = libopus_loader(ctypes.util.find_library('opus'))
|
||||
@ -310,14 +314,14 @@ class Encoder(_OpusStruct):
|
||||
|
||||
def set_bandwidth(self, req):
|
||||
if req not in band_ctl:
|
||||
raise KeyError('%r is not a valid bandwidth setting. Try one of: %s' % (req, ','.join(band_ctl)))
|
||||
raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(band_ctl)}')
|
||||
|
||||
k = band_ctl[req]
|
||||
_lib.opus_encoder_ctl(self._state, CTL_SET_BANDWIDTH, k)
|
||||
|
||||
def set_signal_type(self, req):
|
||||
if req not in signal_ctl:
|
||||
raise KeyError('%r is not a valid signal setting. Try one of: %s' % (req, ','.join(signal_ctl)))
|
||||
raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(signal_ctl)}')
|
||||
|
||||
k = signal_ctl[req]
|
||||
_lib.opus_encoder_ctl(self._state, CTL_SET_SIGNAL, k)
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -27,6 +25,9 @@ DEALINGS IN THE SOFTWARE.
|
||||
from .asset import Asset
|
||||
from . import utils
|
||||
|
||||
__all__ = (
|
||||
'PartialEmoji',
|
||||
)
|
||||
|
||||
class _EmojiTag:
|
||||
__slots__ = ()
|
||||
@ -103,8 +104,8 @@ class PartialEmoji(_EmojiTag):
|
||||
if self.id is None:
|
||||
return self.name
|
||||
if self.animated:
|
||||
return '<a:%s:%s>' % (self.name, self.id)
|
||||
return '<:%s:%s>' % (self.name, self.id)
|
||||
return f'<a:{self.name}:{self.id}>'
|
||||
return f'<:{self.name}:{self.id}>'
|
||||
|
||||
def __repr__(self):
|
||||
return '<{0.__class__.__name__} animated={0.animated} name={0.name!r} id={0.id}>'.format(self)
|
||||
@ -134,7 +135,7 @@ class PartialEmoji(_EmojiTag):
|
||||
def _as_reaction(self):
|
||||
if self.id is None:
|
||||
return self.name
|
||||
return '%s:%s' % (self.name, self.id)
|
||||
return f'{self.name}:{self.id}'
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -96,12 +94,12 @@ class Permissions(BaseFlags):
|
||||
|
||||
def __init__(self, permissions=0, **kwargs):
|
||||
if not isinstance(permissions, int):
|
||||
raise TypeError('Expected int parameter, received %s instead.' % permissions.__class__.__name__)
|
||||
raise TypeError(f'Expected int parameter, received {permissions.__class__.__name__} instead.')
|
||||
|
||||
self.value = permissions
|
||||
for key, value in kwargs.items():
|
||||
if key not in self.VALID_FLAGS:
|
||||
raise TypeError('%r is not a valid permission name.' % key)
|
||||
raise TypeError(f'{key!r} is not a valid permission name.')
|
||||
setattr(self, key, value)
|
||||
|
||||
def is_subset(self, other):
|
||||
@ -109,14 +107,14 @@ class Permissions(BaseFlags):
|
||||
if isinstance(other, Permissions):
|
||||
return (self.value & other.value) == self.value
|
||||
else:
|
||||
raise TypeError("cannot compare {} with {}".format(self.__class__.__name__, other.__class__.__name__))
|
||||
raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}")
|
||||
|
||||
def is_superset(self, other):
|
||||
"""Returns ``True`` if self has the same or more permissions as other."""
|
||||
if isinstance(other, Permissions):
|
||||
return (self.value | other.value) == self.value
|
||||
else:
|
||||
raise TypeError("cannot compare {} with {}".format(self.__class__.__name__, other.__class__.__name__))
|
||||
raise TypeError(f"cannot compare {self.__class__.__name__} with {other.__class__.__name__}")
|
||||
|
||||
def is_strict_subset(self, other):
|
||||
"""Returns ``True`` if the permissions on other are a strict subset of those on self."""
|
||||
@ -541,7 +539,7 @@ class PermissionOverwrite:
|
||||
|
||||
for key, value in kwargs.items():
|
||||
if key not in self.VALID_NAMES:
|
||||
raise ValueError('no permission called {0}.'.format(key))
|
||||
raise ValueError(f'no permission called {key}.')
|
||||
|
||||
setattr(self, key, value)
|
||||
|
||||
@ -550,7 +548,7 @@ class PermissionOverwrite:
|
||||
|
||||
def _set(self, key, value):
|
||||
if value not in (True, None, False):
|
||||
raise TypeError('Expected bool or NoneType, received {0.__class__.__name__}'.format(value))
|
||||
raise TypeError(f'Expected bool or NoneType, received {value.__class__.__name__}')
|
||||
|
||||
if value is None:
|
||||
self._values.pop(key, None)
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -313,7 +311,7 @@ class FFmpegOpusAudio(FFmpegAudio):
|
||||
'-c:a', codec,
|
||||
'-ar', '48000',
|
||||
'-ac', '2',
|
||||
'-b:a', '%sk' % bitrate,
|
||||
'-b:a', f'{bitrate}k',
|
||||
'-loglevel', 'warning'))
|
||||
|
||||
if isinstance(options, str):
|
||||
@ -421,7 +419,7 @@ class FFmpegOpusAudio(FFmpegAudio):
|
||||
if isinstance(method, str):
|
||||
probefunc = getattr(cls, '_probe_codec_' + method, None)
|
||||
if probefunc is None:
|
||||
raise AttributeError("Invalid probe method '%s'" % method)
|
||||
raise AttributeError(f"Invalid probe method {method!r}")
|
||||
|
||||
if probefunc is cls._probe_codec_native:
|
||||
fallback = cls._probe_codec_fallback
|
||||
@ -431,7 +429,7 @@ class FFmpegOpusAudio(FFmpegAudio):
|
||||
fallback = cls._probe_codec_fallback
|
||||
else:
|
||||
raise TypeError("Expected str or callable for parameter 'probe', " \
|
||||
"not '{0.__class__.__name__}'" .format(method))
|
||||
f"not '{method.__class__.__name__}'")
|
||||
|
||||
codec = bitrate = None
|
||||
loop = asyncio.get_event_loop()
|
||||
@ -519,7 +517,7 @@ class PCMVolumeTransformer(AudioSource):
|
||||
|
||||
def __init__(self, original, volume=1.0):
|
||||
if not isinstance(original, AudioSource):
|
||||
raise TypeError('expected AudioSource not {0.__class__.__name__}.'.format(original))
|
||||
raise TypeError(f'expected AudioSource not {original.__class__.__name__}.')
|
||||
|
||||
if original.is_opus():
|
||||
raise ClientException('AudioSource must not be Opus encoded.')
|
||||
@ -619,7 +617,7 @@ class AudioPlayer(threading.Thread):
|
||||
exc.__context__ = error
|
||||
traceback.print_exception(type(exc), exc, exc.__traceback__)
|
||||
elif error:
|
||||
msg = 'Exception in voice thread {}'.format(self.name)
|
||||
msg = f'Exception in voice thread {self.name}'
|
||||
log.exception(msg, exc_info=error)
|
||||
print(msg, file=sys.stderr)
|
||||
traceback.print_exception(type(error), error, error.__traceback__)
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,10 +22,19 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
'RawMessageDeleteEvent',
|
||||
'RawBulkMessageDeleteEvent',
|
||||
'RawMessageUpdateEvent',
|
||||
'RawReactionActionEvent',
|
||||
'RawReactionClearEvent',
|
||||
'RawReactionClearEmojiEvent',
|
||||
)
|
||||
|
||||
class _RawReprMixin:
|
||||
def __repr__(self):
|
||||
value = ' '.join('%s=%r' % (attr, getattr(self, attr)) for attr in self.__slots__)
|
||||
return '<%s %s>' % (self.__class__.__name__, value)
|
||||
value = ' '.join(f'{attr}={getattr(self, attr)!r}' for attr in self.__slots__)
|
||||
return f'<{self.__class__.__name__} {value}>'
|
||||
|
||||
class RawMessageDeleteEvent(_RawReprMixin):
|
||||
"""Represents the event payload for a :func:`on_raw_message_delete` event.
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -26,6 +24,10 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from .iterators import ReactionIterator
|
||||
|
||||
__all__ = (
|
||||
'Reaction',
|
||||
)
|
||||
|
||||
class Reaction:
|
||||
"""Represents a reaction to a message.
|
||||
|
||||
|
@ -1,87 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present 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 .enums import RelationshipType, try_enum
|
||||
from . import utils
|
||||
|
||||
class Relationship:
|
||||
"""Represents a relationship in Discord.
|
||||
|
||||
A relationship is like a friendship, a person who is blocked, etc.
|
||||
Only non-bot accounts can have relationships.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
user: :class:`User`
|
||||
The user you have the relationship with.
|
||||
type: :class:`RelationshipType`
|
||||
The type of relationship you have.
|
||||
"""
|
||||
|
||||
__slots__ = ('type', 'user', '_state')
|
||||
|
||||
def __init__(self, *, state, data):
|
||||
self._state = state
|
||||
self.type = try_enum(RelationshipType, data['type'])
|
||||
self.user = state.store_user(data['user'])
|
||||
|
||||
def __repr__(self):
|
||||
return '<Relationship user={0.user!r} type={0.type!r}>'.format(self)
|
||||
|
||||
@utils.deprecated()
|
||||
async def delete(self):
|
||||
"""|coro|
|
||||
|
||||
Deletes the relationship.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPException
|
||||
Deleting the relationship failed.
|
||||
"""
|
||||
|
||||
await self._state.http.remove_relationship(self.user.id)
|
||||
|
||||
@utils.deprecated()
|
||||
async def accept(self):
|
||||
"""|coro|
|
||||
|
||||
Accepts the relationship request. e.g. accepting a
|
||||
friend request.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
Raises
|
||||
-------
|
||||
HTTPException
|
||||
Accepting the relationship failed.
|
||||
"""
|
||||
|
||||
await self._state.http.add_relationship(self.user.id)
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -30,6 +28,11 @@ from .colour import Colour
|
||||
from .mixins import Hashable
|
||||
from .utils import snowflake_time, _get_as_snowflake
|
||||
|
||||
__all__ = (
|
||||
'RoleTags',
|
||||
'Role',
|
||||
)
|
||||
|
||||
class RoleTags:
|
||||
"""Represents tags on a role.
|
||||
|
||||
@ -251,7 +254,7 @@ class Role(Hashable):
|
||||
@property
|
||||
def mention(self):
|
||||
""":class:`str`: Returns a string that allows you to mention a role."""
|
||||
return '<@&%s>' % self.id
|
||||
return f'<@&{self.id}>'
|
||||
|
||||
@property
|
||||
def members(self):
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -46,6 +44,11 @@ from .errors import (
|
||||
from . import utils
|
||||
from .enums import Status
|
||||
|
||||
__all__ = (
|
||||
'AutoShardedClient',
|
||||
'ShardInfo',
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class EventType:
|
||||
@ -319,7 +322,7 @@ class AutoShardedClient(Client):
|
||||
|
||||
def _get_state(self, **options):
|
||||
return AutoShardedConnectionState(dispatch=self.dispatch,
|
||||
handlers=self._handlers, syncer=self._syncer,
|
||||
handlers=self._handlers,
|
||||
hooks=self._hooks, http=self.http, loop=self.loop, **options)
|
||||
|
||||
@property
|
||||
|
161
discord/state.py
161
discord/state.py
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -44,7 +42,6 @@ from .emoji import Emoji
|
||||
from .mentions import AllowedMentions
|
||||
from .partial_emoji import PartialEmoji
|
||||
from .message import Message
|
||||
from .relationship import Relationship
|
||||
from .channel import *
|
||||
from .raw_models import *
|
||||
from .member import Member
|
||||
@ -54,6 +51,7 @@ from . import utils
|
||||
from .flags import Intents, MemberCacheFlags
|
||||
from .object import Object
|
||||
from .invite import Invite
|
||||
from .interactions import Interaction
|
||||
|
||||
class ChunkRequest:
|
||||
def __init__(self, guild_id, loop, resolver, *, cache=True):
|
||||
@ -104,7 +102,7 @@ async def logging_coroutine(coroutine, *, info):
|
||||
log.exception('Exception occurred during %s', info)
|
||||
|
||||
class ConnectionState:
|
||||
def __init__(self, *, dispatch, handlers, hooks, syncer, http, loop, **options):
|
||||
def __init__(self, *, dispatch, handlers, hooks, http, loop, **options):
|
||||
self.loop = loop
|
||||
self.http = http
|
||||
self.max_messages = options.get('max_messages', 1000)
|
||||
@ -112,12 +110,11 @@ class ConnectionState:
|
||||
self.max_messages = 1000
|
||||
|
||||
self.dispatch = dispatch
|
||||
self.syncer = syncer
|
||||
self.is_bot = None
|
||||
self.handlers = handlers
|
||||
self.hooks = hooks
|
||||
self.shard_count = None
|
||||
self._ready_task = None
|
||||
self.application_id = utils._get_as_snowflake(options, 'application_id')
|
||||
self.heartbeat_timeout = options.get('heartbeat_timeout', 60.0)
|
||||
self.guild_ready_timeout = options.get('guild_ready_timeout', 2.0)
|
||||
if self.guild_ready_timeout < 0:
|
||||
@ -149,7 +146,7 @@ class ConnectionState:
|
||||
intents = options.get('intents', None)
|
||||
if intents is not None:
|
||||
if not isinstance(intents, Intents):
|
||||
raise TypeError('intents parameter must be Intent not %r' % type(intents))
|
||||
raise TypeError(f'intents parameter must be Intent not {type(intents)!r}')
|
||||
else:
|
||||
intents = Intents.default()
|
||||
|
||||
@ -175,7 +172,7 @@ class ConnectionState:
|
||||
cache_flags = MemberCacheFlags.from_intents(intents)
|
||||
else:
|
||||
if not isinstance(cache_flags, MemberCacheFlags):
|
||||
raise TypeError('member_cache_flags parameter must be MemberCacheFlags not %r' % type(cache_flags))
|
||||
raise TypeError(f'member_cache_flags parameter must be MemberCacheFlags not {type(cache_flags)!r}')
|
||||
|
||||
cache_flags._verify_intents(intents)
|
||||
|
||||
@ -198,7 +195,6 @@ class ConnectionState:
|
||||
self.user = None
|
||||
self._users = weakref.WeakValueDictionary()
|
||||
self._emojis = {}
|
||||
self._calls = {}
|
||||
self._guilds = {}
|
||||
self._voice_clients = {}
|
||||
|
||||
@ -340,7 +336,7 @@ class ConnectionState:
|
||||
channel_id = channel.id
|
||||
self._private_channels[channel_id] = channel
|
||||
|
||||
if self.is_bot and len(self._private_channels) > 128:
|
||||
if len(self._private_channels) > 128:
|
||||
_, to_remove = self._private_channels.popitem(last=False)
|
||||
if isinstance(to_remove, DMChannel):
|
||||
self._private_channels_by_user.pop(to_remove.recipient.id, None)
|
||||
@ -405,36 +401,34 @@ class ConnectionState:
|
||||
|
||||
async def _delay_ready(self):
|
||||
try:
|
||||
# only real bots wait for GUILD_CREATE streaming
|
||||
if self.is_bot:
|
||||
states = []
|
||||
while True:
|
||||
# this snippet of code is basically waiting N seconds
|
||||
# until the last GUILD_CREATE was sent
|
||||
try:
|
||||
guild = await asyncio.wait_for(self._ready_state.get(), timeout=self.guild_ready_timeout)
|
||||
except asyncio.TimeoutError:
|
||||
break
|
||||
states = []
|
||||
while True:
|
||||
# this snippet of code is basically waiting N seconds
|
||||
# until the last GUILD_CREATE was sent
|
||||
try:
|
||||
guild = await asyncio.wait_for(self._ready_state.get(), timeout=self.guild_ready_timeout)
|
||||
except asyncio.TimeoutError:
|
||||
break
|
||||
else:
|
||||
if self._guild_needs_chunking(guild):
|
||||
future = await self.chunk_guild(guild, wait=False)
|
||||
states.append((guild, future))
|
||||
else:
|
||||
if self._guild_needs_chunking(guild):
|
||||
future = await self.chunk_guild(guild, wait=False)
|
||||
states.append((guild, future))
|
||||
if guild.unavailable is False:
|
||||
self.dispatch('guild_available', guild)
|
||||
else:
|
||||
if guild.unavailable is False:
|
||||
self.dispatch('guild_available', guild)
|
||||
else:
|
||||
self.dispatch('guild_join', guild)
|
||||
self.dispatch('guild_join', guild)
|
||||
|
||||
for guild, future in states:
|
||||
try:
|
||||
await asyncio.wait_for(future, timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
log.warning('Shard ID %s timed out waiting for chunks for guild_id %s.', guild.shard_id, guild.id)
|
||||
for guild, future in states:
|
||||
try:
|
||||
await asyncio.wait_for(future, timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
log.warning('Shard ID %s timed out waiting for chunks for guild_id %s.', guild.shard_id, guild.id)
|
||||
|
||||
if guild.unavailable is False:
|
||||
self.dispatch('guild_available', guild)
|
||||
else:
|
||||
self.dispatch('guild_join', guild)
|
||||
if guild.unavailable is False:
|
||||
self.dispatch('guild_available', guild)
|
||||
else:
|
||||
self.dispatch('guild_join', guild)
|
||||
|
||||
# remove the state
|
||||
try:
|
||||
@ -442,10 +436,6 @@ class ConnectionState:
|
||||
except AttributeError:
|
||||
pass # already been deleted somehow
|
||||
|
||||
# call GUILD_SYNC after we're done chunking
|
||||
if not self.is_bot:
|
||||
log.info('Requesting GUILD_SYNC for %s guilds', len(self.guilds))
|
||||
await self.syncer([s.id for s in self.guilds])
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
else:
|
||||
@ -464,23 +454,19 @@ class ConnectionState:
|
||||
self.user = user = ClientUser(state=self, data=data['user'])
|
||||
self._users[user.id] = user
|
||||
|
||||
if self.application_id is None:
|
||||
try:
|
||||
application = data['application']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
self.application_id = utils._get_as_snowflake(application, 'id')
|
||||
|
||||
for guild_data in data['guilds']:
|
||||
self._add_guild_from_data(guild_data)
|
||||
|
||||
for relationship in data.get('relationships', []):
|
||||
try:
|
||||
r_id = int(relationship['id'])
|
||||
except KeyError:
|
||||
continue
|
||||
else:
|
||||
user._relationships[r_id] = Relationship(state=self, data=relationship)
|
||||
|
||||
for pm in data.get('private_channels', []):
|
||||
factory, _ = _channel_factory(pm['type'])
|
||||
self._add_private_channel(factory(me=user, data=pm, state=self))
|
||||
|
||||
self.dispatch('connect')
|
||||
self._ready_task = asyncio.ensure_future(self._delay_ready(), loop=self.loop)
|
||||
self._ready_task = asyncio.create_task(self._delay_ready())
|
||||
|
||||
def parse_resumed(self, data):
|
||||
self.dispatch('resumed')
|
||||
@ -601,6 +587,10 @@ class ConnectionState:
|
||||
if reaction:
|
||||
self.dispatch('reaction_clear_emoji', reaction)
|
||||
|
||||
def parse_interaction_create(self, data):
|
||||
interaction = Interaction(data=data, state=self)
|
||||
self.dispatch('interaction', interaction)
|
||||
|
||||
def parse_presence_update(self, data):
|
||||
guild_id = utils._get_as_snowflake(data, 'guild_id')
|
||||
guild = self._get_guild(guild_id)
|
||||
@ -724,22 +714,6 @@ class ConnectionState:
|
||||
else:
|
||||
self.dispatch('guild_channel_pins_update', channel, last_pin)
|
||||
|
||||
def parse_channel_recipient_add(self, data):
|
||||
channel = self._get_private_channel(int(data['channel_id']))
|
||||
user = self.store_user(data['user'])
|
||||
channel.recipients.append(user)
|
||||
self.dispatch('group_join', channel, user)
|
||||
|
||||
def parse_channel_recipient_remove(self, data):
|
||||
channel = self._get_private_channel(int(data['channel_id']))
|
||||
user = self.store_user(data['user'])
|
||||
try:
|
||||
channel.recipients.remove(user)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
self.dispatch('group_remove', channel, user)
|
||||
|
||||
def parse_guild_member_add(self, data):
|
||||
guild = self._get_guild(int(data['guild_id']))
|
||||
if guild is None:
|
||||
@ -871,7 +845,7 @@ class ConnectionState:
|
||||
|
||||
# check if it requires chunking
|
||||
if self._guild_needs_chunking(guild):
|
||||
asyncio.ensure_future(self._chunk_and_dispatch(guild, unavailable), loop=self.loop)
|
||||
asyncio.create_task(self._chunk_and_dispatch(guild, unavailable))
|
||||
return
|
||||
|
||||
# Dispatch available if newly available
|
||||
@ -880,10 +854,6 @@ class ConnectionState:
|
||||
else:
|
||||
self.dispatch('guild_join', guild)
|
||||
|
||||
def parse_guild_sync(self, data):
|
||||
guild = self._get_guild(int(data['id']))
|
||||
guild._sync(data)
|
||||
|
||||
def parse_guild_update(self, data):
|
||||
guild = self._get_guild(int(data['id']))
|
||||
if guild is not None:
|
||||
@ -1015,7 +985,7 @@ class ConnectionState:
|
||||
voice = self._get_voice_client(guild.id)
|
||||
if voice is not None:
|
||||
coro = voice.on_voice_state_update(data)
|
||||
asyncio.ensure_future(logging_coroutine(coro, info='Voice Protocol voice state update handler'))
|
||||
asyncio.create_task(logging_coroutine(coro, info='Voice Protocol voice state update handler'))
|
||||
|
||||
member, before, after = guild._update_voice_state(data, channel_id)
|
||||
if member is not None:
|
||||
@ -1029,11 +999,6 @@ class ConnectionState:
|
||||
self.dispatch('voice_state_update', member, before, after)
|
||||
else:
|
||||
log.debug('VOICE_STATE_UPDATE referencing an unknown member ID: %s. Discarding.', data['user_id'])
|
||||
else:
|
||||
# in here we're either at private or group calls
|
||||
call = self._calls.get(channel_id)
|
||||
if call is not None:
|
||||
call._update_voice_state(data)
|
||||
|
||||
def parse_voice_server_update(self, data):
|
||||
try:
|
||||
@ -1044,7 +1009,7 @@ class ConnectionState:
|
||||
vc = self._get_voice_client(key_id)
|
||||
if vc is not None:
|
||||
coro = vc.on_voice_server_update(data)
|
||||
asyncio.ensure_future(logging_coroutine(coro, info='Voice Protocol voice server update handler'))
|
||||
asyncio.create_task(logging_coroutine(coro, info='Voice Protocol voice server update handler'))
|
||||
|
||||
def parse_typing_start(self, data):
|
||||
channel, guild = self._get_guild_channel(data)
|
||||
@ -1065,27 +1030,9 @@ class ConnectionState:
|
||||
|
||||
if member is not None:
|
||||
timestamp = datetime.datetime.utcfromtimestamp(data.get('timestamp'))
|
||||
timestamp = timestamp.replace(tzinfo=datetime.timezone.utc)
|
||||
self.dispatch('typing', channel, member, timestamp)
|
||||
|
||||
def parse_relationship_add(self, data):
|
||||
key = int(data['id'])
|
||||
old = self.user.get_relationship(key)
|
||||
new = Relationship(state=self, data=data)
|
||||
self.user._relationships[key] = new
|
||||
if old is not None:
|
||||
self.dispatch('relationship_update', old, new)
|
||||
else:
|
||||
self.dispatch('relationship_add', new)
|
||||
|
||||
def parse_relationship_remove(self, data):
|
||||
key = int(data['id'])
|
||||
try:
|
||||
old = self.user._relationships.pop(key)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
self.dispatch('relationship_remove', old)
|
||||
|
||||
def _get_reaction_user(self, channel, user_id):
|
||||
if isinstance(channel, TextChannel):
|
||||
return channel.guild.get_member(user_id)
|
||||
@ -1223,16 +1170,20 @@ class AutoShardedConnectionState(ConnectionState):
|
||||
self.user = user = ClientUser(state=self, data=data['user'])
|
||||
self._users[user.id] = user
|
||||
|
||||
if self.application_id is None:
|
||||
try:
|
||||
application = data['application']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
self.application_id = utils._get_as_snowflake(application, 'id')
|
||||
|
||||
for guild_data in data['guilds']:
|
||||
self._add_guild_from_data(guild_data)
|
||||
|
||||
if self._messages:
|
||||
self._update_message_references()
|
||||
|
||||
for pm in data.get('private_channels', []):
|
||||
factory, _ = _channel_factory(pm['type'])
|
||||
self._add_private_channel(factory(me=user, data=pm, state=self))
|
||||
|
||||
self.dispatch('connect')
|
||||
self.dispatch('shard_connect', data['__shard_id__'])
|
||||
|
||||
@ -1245,7 +1196,7 @@ class AutoShardedConnectionState(ConnectionState):
|
||||
gc.collect()
|
||||
|
||||
if self._ready_task is None:
|
||||
self._ready_task = asyncio.ensure_future(self._delay_ready(), loop=self.loop)
|
||||
self._ready_task = asyncio.create_task(self._delay_ready())
|
||||
|
||||
def parse_resumed(self, data):
|
||||
self.dispatch('resumed')
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -29,6 +27,10 @@ from .asset import Asset
|
||||
from .utils import snowflake_time
|
||||
from .enums import StickerType, try_enum
|
||||
|
||||
__all__ = (
|
||||
'Sticker',
|
||||
)
|
||||
|
||||
class Sticker(Hashable):
|
||||
"""Represents a sticker.
|
||||
|
||||
@ -93,7 +95,7 @@ class Sticker(Hashable):
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
""":class:`datetime.datetime`: Returns the sticker's creation time in UTC as a naive datetime."""
|
||||
""":class:`datetime.datetime`: Returns the sticker's creation time in UTC."""
|
||||
return snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -43,10 +41,6 @@ class _PartialTemplateState:
|
||||
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
|
||||
@ -76,7 +70,7 @@ class _PartialTemplateState:
|
||||
return []
|
||||
|
||||
def __getattr__(self, attr):
|
||||
raise AttributeError('PartialTemplateState does not support {0!r}.'.format(attr))
|
||||
raise AttributeError(f'PartialTemplateState does not support {attr!r}.')
|
||||
|
||||
class Template:
|
||||
"""Represents a Discord template.
|
||||
@ -96,9 +90,10 @@ class Template:
|
||||
creator: :class:`User`
|
||||
The creator of the template.
|
||||
created_at: :class:`datetime.datetime`
|
||||
When the template was created.
|
||||
An aware datetime in UTC representing when the template was created.
|
||||
updated_at: :class:`datetime.datetime`
|
||||
When the template was last updated (referred to as "last synced" in the client).
|
||||
An aware datetime in UTC representing when the template was last updated.
|
||||
This is referred to as "last synced" in the official Discord client.
|
||||
source_guild: :class:`Guild`
|
||||
The source guild.
|
||||
"""
|
||||
@ -127,7 +122,7 @@ class Template:
|
||||
source_serialised['id'] = id
|
||||
state = _PartialTemplateState(state=self._state)
|
||||
guild = Guild(data=source_serialised, state=state)
|
||||
|
||||
|
||||
self.source_guild = guild
|
||||
|
||||
def __repr__(self):
|
||||
|
10
discord/types/__init__.py
Normal file
10
discord/types/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""
|
||||
discord.types
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Typings for the Discord API
|
||||
|
||||
:copyright: (c) 2015-present Rapptz
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
91
discord/types/channel.py
Normal file
91
discord/types/channel.py
Normal file
@ -0,0 +1,91 @@
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present 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 .user import PartialUser
|
||||
from .snowflake import Snowflake
|
||||
from typing import List, Literal, Optional, TypedDict
|
||||
|
||||
|
||||
class PermissionOverwrite(TypedDict):
|
||||
id: Snowflake
|
||||
type: Literal[0, 1]
|
||||
allow: str
|
||||
deny: str
|
||||
|
||||
|
||||
ChannelType = Literal[0, 1, 2, 3, 4, 5, 6, 13]
|
||||
|
||||
|
||||
class PartialChannel(TypedDict):
|
||||
id: str
|
||||
type: ChannelType
|
||||
name: str
|
||||
|
||||
|
||||
class _TextChannelOptional(PartialChannel, total=False):
|
||||
topic: str
|
||||
last_message_id: Optional[Snowflake]
|
||||
last_pin_timestamp: int
|
||||
rate_limit_per_user: int
|
||||
|
||||
|
||||
class _VoiceChannelOptional(PartialChannel, total=False):
|
||||
rtc_region: Optional[str]
|
||||
bitrate: int
|
||||
user_limit: int
|
||||
|
||||
|
||||
class _CategoryChannelOptional(PartialChannel, total=False):
|
||||
...
|
||||
|
||||
|
||||
class _StoreChannelOptional(PartialChannel, total=False):
|
||||
...
|
||||
|
||||
|
||||
class _StageChannelOptional(PartialChannel, total=False):
|
||||
rtc_region: Optional[str]
|
||||
bitrate: int
|
||||
user_limit: int
|
||||
topic: str
|
||||
|
||||
|
||||
class GuildChannel(
|
||||
_TextChannelOptional, _VoiceChannelOptional, _CategoryChannelOptional, _StoreChannelOptional, _StageChannelOptional
|
||||
):
|
||||
guild_id: Snowflake
|
||||
position: int
|
||||
permission_overwrites: List[PermissionOverwrite]
|
||||
nsfw: bool
|
||||
parent_id: Optional[Snowflake]
|
||||
|
||||
|
||||
class DMChannel(PartialChannel):
|
||||
last_message_id: Optional[Snowflake]
|
||||
recipients: List[PartialUser]
|
||||
|
||||
|
||||
class GroupDMChannel(DMChannel):
|
||||
icon: Optional[str]
|
||||
owner_id: Snowflake
|
84
discord/types/embed.py
Normal file
84
discord/types/embed.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present 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 typing import List, Literal, TypedDict
|
||||
|
||||
class _EmbedFooterOptional(TypedDict, total=False):
|
||||
icon_url: str
|
||||
proxy_icon_url: str
|
||||
|
||||
class EmbedFooter(_EmbedFooterOptional):
|
||||
text: str
|
||||
|
||||
class _EmbedFieldOptional(TypedDict, total=False):
|
||||
inline: bool
|
||||
|
||||
class EmbedField(_EmbedFieldOptional):
|
||||
name: str
|
||||
value: str
|
||||
|
||||
class EmbedThumbnail(TypedDict, total=False):
|
||||
url: str
|
||||
proxy_url: str
|
||||
height: int
|
||||
width: int
|
||||
|
||||
class EmbedVideo(TypedDict, total=False):
|
||||
url: str
|
||||
proxy_url: str
|
||||
height: int
|
||||
width: int
|
||||
|
||||
class EmbedImage(TypedDict, total=False):
|
||||
url: str
|
||||
proxy_url: str
|
||||
height: int
|
||||
width: int
|
||||
|
||||
class EmbedProvider(TypedDict, total=False):
|
||||
name: str
|
||||
url: str
|
||||
|
||||
class EmbedAuthor(TypedDict, total=False):
|
||||
name: str
|
||||
url: str
|
||||
icon_url: str
|
||||
proxy_icon_url: str
|
||||
|
||||
EmbedType = Literal['rich', 'image', 'video', 'gifv', 'article', 'link']
|
||||
|
||||
class Embed(TypedDict, total=False):
|
||||
title: str
|
||||
type: EmbedType
|
||||
description: str
|
||||
url: str
|
||||
timestamp: str
|
||||
color: int
|
||||
footer: EmbedFooter
|
||||
image: EmbedImage
|
||||
thumbnail: EmbedThumbnail
|
||||
video: EmbedVideo
|
||||
provider: EmbedProvider
|
||||
author: EmbedAuthor
|
||||
fields: List[EmbedField]
|
28
discord/types/snowflake.py
Normal file
28
discord/types/snowflake.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present 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 typing import List
|
||||
|
||||
Snowflake = str
|
||||
SnowflakeList = List[Snowflake]
|
33
discord/types/user.py
Normal file
33
discord/types/user.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present 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 .snowflake import Snowflake
|
||||
from typing import Optional, TypedDict
|
||||
|
||||
|
||||
class PartialUser(TypedDict):
|
||||
id: Snowflake
|
||||
username: str
|
||||
discriminator: str
|
||||
avatar: Optional[str]
|
569
discord/user.py
569
discord/user.py
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -24,62 +22,17 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
import discord.abc
|
||||
from .flags import PublicUserFlags
|
||||
from .utils import snowflake_time, _bytes_to_base64_data, parse_time
|
||||
from .enums import DefaultAvatar, RelationshipType, UserFlags, HypeSquadHouse, PremiumType, try_enum
|
||||
from .errors import ClientException
|
||||
from .utils import snowflake_time, _bytes_to_base64_data
|
||||
from .enums import DefaultAvatar, try_enum
|
||||
from .colour import Colour
|
||||
from .asset import Asset
|
||||
from .utils import deprecated
|
||||
|
||||
class Profile(namedtuple('Profile', 'flags user mutual_guilds connected_accounts premium_since')):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def nitro(self):
|
||||
return self.premium_since is not None
|
||||
|
||||
premium = nitro
|
||||
|
||||
def _has_flag(self, o):
|
||||
v = o.value
|
||||
return (self.flags & v) == v
|
||||
|
||||
@property
|
||||
def staff(self):
|
||||
return self._has_flag(UserFlags.staff)
|
||||
|
||||
@property
|
||||
def partner(self):
|
||||
return self._has_flag(UserFlags.partner)
|
||||
|
||||
@property
|
||||
def bug_hunter(self):
|
||||
return self._has_flag(UserFlags.bug_hunter)
|
||||
|
||||
@property
|
||||
def early_supporter(self):
|
||||
return self._has_flag(UserFlags.early_supporter)
|
||||
|
||||
@property
|
||||
def hypesquad(self):
|
||||
return self._has_flag(UserFlags.hypesquad)
|
||||
|
||||
@property
|
||||
def hypesquad_houses(self):
|
||||
flags = (UserFlags.hypesquad_bravery, UserFlags.hypesquad_brilliance, UserFlags.hypesquad_balance)
|
||||
return [house for house, flag in zip(HypeSquadHouse, flags) if self._has_flag(flag)]
|
||||
|
||||
@property
|
||||
def team_user(self):
|
||||
return self._has_flag(UserFlags.team_user)
|
||||
|
||||
@property
|
||||
def system(self):
|
||||
return self._has_flag(UserFlags.system)
|
||||
__all__ = (
|
||||
'User',
|
||||
'ClientUser',
|
||||
)
|
||||
|
||||
_BaseUser = discord.abc.User
|
||||
|
||||
@ -199,7 +152,7 @@ class BaseUser(_BaseUser):
|
||||
@property
|
||||
def default_avatar_url(self):
|
||||
""":class:`Asset`: Returns a URL for a user's default avatar."""
|
||||
return Asset(self._state, '/embed/avatars/{}.png'.format(self.default_avatar.value))
|
||||
return Asset(self._state, f'/embed/avatars/{self.default_avatar.value}.png')
|
||||
|
||||
@property
|
||||
def colour(self):
|
||||
@ -222,7 +175,7 @@ class BaseUser(_BaseUser):
|
||||
@property
|
||||
def mention(self):
|
||||
""":class:`str`: Returns a string that allows you to mention the given user."""
|
||||
return '<@{0.id}>'.format(self)
|
||||
return f'<@{self.id}>'
|
||||
|
||||
def permissions_in(self, channel):
|
||||
"""An alias for :meth:`abc.GuildChannel.permissions_for`.
|
||||
@ -244,7 +197,8 @@ class BaseUser(_BaseUser):
|
||||
def created_at(self):
|
||||
""":class:`datetime.datetime`: Returns the user's creation time in UTC.
|
||||
|
||||
This is when the user's Discord account was created."""
|
||||
This is when the user's Discord account was created.
|
||||
"""
|
||||
return snowflake_time(self.id)
|
||||
|
||||
@property
|
||||
@ -315,33 +269,26 @@ class ClientUser(BaseUser):
|
||||
.. versionadded:: 1.3
|
||||
|
||||
verified: :class:`bool`
|
||||
<<<<<<< HEAD
|
||||
Specifies if the user's email is verified.
|
||||
email: Optional[:class:`str`]
|
||||
The email the user used when registering.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
=======
|
||||
Specifies if the user is a verified account.
|
||||
>>>>>>> 523e35e4f3c3c49d4e471359f9fb559242bbecc8
|
||||
locale: Optional[:class:`str`]
|
||||
The IETF language tag used to identify the language the user is using.
|
||||
mfa_enabled: :class:`bool`
|
||||
Specifies if the user has MFA turned on and working.
|
||||
premium: :class:`bool`
|
||||
Specifies if the user is a premium user (e.g. has Discord Nitro).
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
premium_type: Optional[:class:`PremiumType`]
|
||||
Specifies the type of premium a user has (e.g. Nitro or Nitro Classic). Could be None if the user is not premium.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
"""
|
||||
__slots__ = BaseUser.__slots__ + \
|
||||
('email', 'locale', '_flags', 'verified', 'mfa_enabled',
|
||||
'premium', 'premium_type', '_relationships', '__weakref__')
|
||||
('locale', '_flags', 'verified', 'mfa_enabled', '__weakref__')
|
||||
|
||||
def __init__(self, *, state, data):
|
||||
super().__init__(state=state, data=data)
|
||||
self._relationships = {}
|
||||
|
||||
def __repr__(self):
|
||||
return '<ClientUser id={0.id} name={0.name!r} discriminator={0.discriminator!r}' \
|
||||
@ -351,83 +298,16 @@ class ClientUser(BaseUser):
|
||||
super()._update(data)
|
||||
# There's actually an Optional[str] phone field as well but I won't use it
|
||||
self.verified = data.get('verified', False)
|
||||
self.email = data.get('email')
|
||||
self.locale = data.get('locale')
|
||||
self._flags = data.get('flags', 0)
|
||||
self.mfa_enabled = data.get('mfa_enabled', False)
|
||||
self.premium = data.get('premium', False)
|
||||
self.premium_type = try_enum(PremiumType, data.get('premium_type', None))
|
||||
|
||||
@deprecated()
|
||||
def get_relationship(self, user_id):
|
||||
"""Retrieves the :class:`Relationship` if applicable.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
user_id: :class:`int`
|
||||
The user ID to check if we have a relationship with them.
|
||||
|
||||
Returns
|
||||
--------
|
||||
Optional[:class:`Relationship`]
|
||||
The relationship if available or ``None``.
|
||||
"""
|
||||
return self._relationships.get(user_id)
|
||||
|
||||
@property
|
||||
def relationships(self):
|
||||
"""List[:class:`User`]: Returns all the relationships that the user has.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
"""
|
||||
return list(self._relationships.values())
|
||||
|
||||
@property
|
||||
def friends(self):
|
||||
r"""List[:class:`User`]: Returns all the users that the user is friends with.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
"""
|
||||
return [r.user for r in self._relationships.values() if r.type is RelationshipType.friend]
|
||||
|
||||
@property
|
||||
def blocked(self):
|
||||
r"""List[:class:`User`]: Returns all the users that the user has blocked.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
"""
|
||||
return [r.user for r in self._relationships.values() if r.type is RelationshipType.blocked]
|
||||
|
||||
async def edit(self, **fields):
|
||||
async def edit(self, *, username=None, avatar=None):
|
||||
"""|coro|
|
||||
|
||||
Edits the current profile of the client.
|
||||
|
||||
If a bot account is used then a password field is optional,
|
||||
otherwise it is required.
|
||||
|
||||
.. warning::
|
||||
|
||||
The user account-only fields are deprecated.
|
||||
|
||||
.. note::
|
||||
|
||||
To upload an avatar, a :term:`py:bytes-like object` must be passed in that
|
||||
@ -439,19 +319,6 @@ class ClientUser(BaseUser):
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
password: :class:`str`
|
||||
The current password for the client's account.
|
||||
Only applicable to user accounts.
|
||||
new_password: :class:`str`
|
||||
The new password you wish to change to.
|
||||
Only applicable to user accounts.
|
||||
email: :class:`str`
|
||||
The new email you wish to change to.
|
||||
Only applicable to user accounts.
|
||||
house: Optional[:class:`HypeSquadHouse`]
|
||||
The hypesquad house you wish to change to.
|
||||
Could be ``None`` to leave the current house.
|
||||
Only applicable to user accounts.
|
||||
username: :class:`str`
|
||||
The new username you wish to change to.
|
||||
avatar: :class:`bytes`
|
||||
@ -464,218 +331,14 @@ class ClientUser(BaseUser):
|
||||
Editing your profile failed.
|
||||
InvalidArgument
|
||||
Wrong image format passed for ``avatar``.
|
||||
ClientException
|
||||
Password is required for non-bot accounts.
|
||||
House field was not a HypeSquadHouse.
|
||||
"""
|
||||
|
||||
try:
|
||||
avatar_bytes = fields['avatar']
|
||||
except KeyError:
|
||||
avatar = self.avatar
|
||||
else:
|
||||
if avatar_bytes is not None:
|
||||
avatar = _bytes_to_base64_data(avatar_bytes)
|
||||
else:
|
||||
avatar = None
|
||||
|
||||
not_bot_account = not self.bot
|
||||
password = fields.get('password')
|
||||
if not_bot_account and password is None:
|
||||
raise ClientException('Password is required for non-bot accounts.')
|
||||
|
||||
args = {
|
||||
'password': password,
|
||||
'username': fields.get('username', self.name),
|
||||
'avatar': avatar
|
||||
}
|
||||
|
||||
if not_bot_account:
|
||||
args['email'] = fields.get('email', self.email)
|
||||
|
||||
if 'new_password' in fields:
|
||||
args['new_password'] = fields['new_password']
|
||||
|
||||
http = self._state.http
|
||||
|
||||
if 'house' in fields:
|
||||
house = fields['house']
|
||||
if house is None:
|
||||
await http.leave_hypesquad_house()
|
||||
elif not isinstance(house, HypeSquadHouse):
|
||||
raise ClientException('`house` parameter was not a HypeSquadHouse')
|
||||
else:
|
||||
value = house.value
|
||||
|
||||
await http.change_hypesquad_house(value)
|
||||
|
||||
data = await http.edit_profile(**args)
|
||||
if not_bot_account:
|
||||
self.email = data['email']
|
||||
try:
|
||||
http._token(data['token'], bot=False)
|
||||
except KeyError:
|
||||
pass
|
||||
if avatar is not None:
|
||||
avatar = _bytes_to_base64_data(avatar)
|
||||
|
||||
data = await self._state.http.edit_profile(username=username, avatar=avatar)
|
||||
self._update(data)
|
||||
|
||||
@deprecated()
|
||||
async def create_group(self, *recipients):
|
||||
r"""|coro|
|
||||
|
||||
Creates a group direct message with the recipients
|
||||
provided. These recipients must be have a relationship
|
||||
of type :attr:`RelationshipType.friend`.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
\*recipients: :class:`User`
|
||||
An argument :class:`list` of :class:`User` to have in
|
||||
your group.
|
||||
|
||||
Raises
|
||||
-------
|
||||
HTTPException
|
||||
Failed to create the group direct message.
|
||||
ClientException
|
||||
Attempted to create a group with only one recipient.
|
||||
This does not include yourself.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`GroupChannel`
|
||||
The new group channel.
|
||||
"""
|
||||
|
||||
from .channel import GroupChannel
|
||||
|
||||
if len(recipients) < 2:
|
||||
raise ClientException('You must have two or more recipients to create a group.')
|
||||
|
||||
users = [str(u.id) for u in recipients]
|
||||
data = await self._state.http.start_group(self.id, users)
|
||||
return GroupChannel(me=self, data=data, state=self._state)
|
||||
|
||||
@deprecated()
|
||||
async def edit_settings(self, **kwargs):
|
||||
"""|coro|
|
||||
|
||||
Edits the client user's settings.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Parameters
|
||||
-------
|
||||
afk_timeout: :class:`int`
|
||||
How long (in seconds) the user needs to be AFK until Discord
|
||||
sends push notifications to your mobile device.
|
||||
animate_emojis: :class:`bool`
|
||||
Whether or not to animate emojis in the chat.
|
||||
convert_emoticons: :class:`bool`
|
||||
Whether or not to automatically convert emoticons into emojis.
|
||||
e.g. :-) -> 😃
|
||||
default_guilds_restricted: :class:`bool`
|
||||
Whether or not to automatically disable DMs between you and
|
||||
members of new guilds you join.
|
||||
detect_platform_accounts: :class:`bool`
|
||||
Whether or not to automatically detect accounts from services
|
||||
like Steam and Blizzard when you open the Discord client.
|
||||
developer_mode: :class:`bool`
|
||||
Whether or not to enable developer mode.
|
||||
disable_games_tab: :class:`bool`
|
||||
Whether or not to disable the showing of the Games tab.
|
||||
enable_tts_command: :class:`bool`
|
||||
Whether or not to allow tts messages to be played/sent.
|
||||
explicit_content_filter: :class:`UserContentFilter`
|
||||
The filter for explicit content in all messages.
|
||||
friend_source_flags: :class:`FriendFlags`
|
||||
Who can add you as a friend.
|
||||
gif_auto_play: :class:`bool`
|
||||
Whether or not to automatically play gifs that are in the chat.
|
||||
guild_positions: List[:class:`abc.Snowflake`]
|
||||
A list of guilds in order of the guild/guild icons that are on
|
||||
the left hand side of the UI.
|
||||
inline_attachment_media: :class:`bool`
|
||||
Whether or not to display attachments when they are uploaded in chat.
|
||||
inline_embed_media: :class:`bool`
|
||||
Whether or not to display videos and images from links posted in chat.
|
||||
locale: :class:`str`
|
||||
The :rfc:`3066` language identifier of the locale to use for the language
|
||||
of the Discord client.
|
||||
message_display_compact: :class:`bool`
|
||||
Whether or not to use the compact Discord display mode.
|
||||
render_embeds: :class:`bool`
|
||||
Whether or not to render embeds that are sent in the chat.
|
||||
render_reactions: :class:`bool`
|
||||
Whether or not to render reactions that are added to messages.
|
||||
restricted_guilds: List[:class:`abc.Snowflake`]
|
||||
A list of guilds that you will not receive DMs from.
|
||||
show_current_game: :class:`bool`
|
||||
Whether or not to display the game that you are currently playing.
|
||||
status: :class:`Status`
|
||||
The clients status that is shown to others.
|
||||
theme: :class:`Theme`
|
||||
The theme of the Discord UI.
|
||||
timezone_offset: :class:`int`
|
||||
The timezone offset to use.
|
||||
|
||||
Raises
|
||||
-------
|
||||
HTTPException
|
||||
Editing the settings failed.
|
||||
Forbidden
|
||||
The client is a bot user and not a user account.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`dict`
|
||||
The client user's updated settings.
|
||||
"""
|
||||
payload = {}
|
||||
|
||||
content_filter = kwargs.pop('explicit_content_filter', None)
|
||||
if content_filter:
|
||||
payload.update({'explicit_content_filter': content_filter.value})
|
||||
|
||||
friend_flags = kwargs.pop('friend_source_flags', None)
|
||||
if friend_flags:
|
||||
dicts = [{}, {'mutual_guilds': True}, {'mutual_friends': True},
|
||||
{'mutual_guilds': True, 'mutual_friends': True}, {'all': True}]
|
||||
payload.update({'friend_source_flags': dicts[friend_flags.value]})
|
||||
|
||||
guild_positions = kwargs.pop('guild_positions', None)
|
||||
if guild_positions:
|
||||
guild_positions = [str(x.id) for x in guild_positions]
|
||||
payload.update({'guild_positions': guild_positions})
|
||||
|
||||
restricted_guilds = kwargs.pop('restricted_guilds', None)
|
||||
if restricted_guilds:
|
||||
restricted_guilds = [str(x.id) for x in restricted_guilds]
|
||||
payload.update({'restricted_guilds': restricted_guilds})
|
||||
|
||||
status = kwargs.pop('status', None)
|
||||
if status:
|
||||
payload.update({'status': status.value})
|
||||
|
||||
theme = kwargs.pop('theme', None)
|
||||
if theme:
|
||||
payload.update({'theme': theme.value})
|
||||
|
||||
payload.update(kwargs)
|
||||
|
||||
data = await self._state.http.edit_settings(**payload)
|
||||
return data
|
||||
|
||||
class User(BaseUser, discord.abc.Messageable):
|
||||
"""Represents a Discord user.
|
||||
|
||||
@ -763,197 +426,3 @@ class User(BaseUser, discord.abc.Messageable):
|
||||
state = self._state
|
||||
data = await state.http.start_private_message(self.id)
|
||||
return state.add_dm_channel(data)
|
||||
|
||||
@property
|
||||
def relationship(self):
|
||||
"""Optional[:class:`Relationship`]: Returns the :class:`Relationship` with this user if applicable, ``None`` otherwise.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
"""
|
||||
return self._state.user.get_relationship(self.id)
|
||||
|
||||
@deprecated()
|
||||
async def mutual_friends(self):
|
||||
"""|coro|
|
||||
|
||||
Gets all mutual friends of this user.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Raises
|
||||
-------
|
||||
Forbidden
|
||||
Not allowed to get mutual friends of this user.
|
||||
HTTPException
|
||||
Getting mutual friends failed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[:class:`User`]
|
||||
The users that are mutual friends.
|
||||
"""
|
||||
state = self._state
|
||||
mutuals = await state.http.get_mutual_friends(self.id)
|
||||
return [User(state=state, data=friend) for friend in mutuals]
|
||||
|
||||
@deprecated()
|
||||
def is_friend(self):
|
||||
""":class:`bool`: Checks if the user is your friend.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
"""
|
||||
r = self.relationship
|
||||
if r is None:
|
||||
return False
|
||||
return r.type is RelationshipType.friend
|
||||
|
||||
@deprecated()
|
||||
def is_blocked(self):
|
||||
""":class:`bool`: Checks if the user is blocked.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
"""
|
||||
r = self.relationship
|
||||
if r is None:
|
||||
return False
|
||||
return r.type is RelationshipType.blocked
|
||||
|
||||
@deprecated()
|
||||
async def block(self):
|
||||
"""|coro|
|
||||
|
||||
Blocks the user.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Raises
|
||||
-------
|
||||
Forbidden
|
||||
Not allowed to block this user.
|
||||
HTTPException
|
||||
Blocking the user failed.
|
||||
"""
|
||||
|
||||
await self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value)
|
||||
|
||||
@deprecated()
|
||||
async def unblock(self):
|
||||
"""|coro|
|
||||
|
||||
Unblocks the user.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Raises
|
||||
-------
|
||||
Forbidden
|
||||
Not allowed to unblock this user.
|
||||
HTTPException
|
||||
Unblocking the user failed.
|
||||
"""
|
||||
await self._state.http.remove_relationship(self.id)
|
||||
|
||||
@deprecated()
|
||||
async def remove_friend(self):
|
||||
"""|coro|
|
||||
|
||||
Removes the user as a friend.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Raises
|
||||
-------
|
||||
Forbidden
|
||||
Not allowed to remove this user as a friend.
|
||||
HTTPException
|
||||
Removing the user as a friend failed.
|
||||
"""
|
||||
await self._state.http.remove_relationship(self.id)
|
||||
|
||||
@deprecated()
|
||||
async def send_friend_request(self):
|
||||
"""|coro|
|
||||
|
||||
Sends the user a friend request.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Raises
|
||||
-------
|
||||
Forbidden
|
||||
Not allowed to send a friend request to the user.
|
||||
HTTPException
|
||||
Sending the friend request failed.
|
||||
"""
|
||||
await self._state.http.send_friend_request(username=self.name, discriminator=self.discriminator)
|
||||
|
||||
@deprecated()
|
||||
async def profile(self):
|
||||
"""|coro|
|
||||
|
||||
Gets the user's profile.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This can only be used by non-bot accounts.
|
||||
|
||||
Raises
|
||||
-------
|
||||
Forbidden
|
||||
Not allowed to fetch profiles.
|
||||
HTTPException
|
||||
Fetching the profile failed.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`Profile`
|
||||
The profile of the user.
|
||||
"""
|
||||
|
||||
state = self._state
|
||||
data = await state.http.get_user_profile(self.id)
|
||||
|
||||
def transform(d):
|
||||
return state._get_guild(int(d['id']))
|
||||
|
||||
since = data.get('premium_since')
|
||||
mutual_guilds = list(filter(None, map(transform, data.get('mutual_guilds', []))))
|
||||
return Profile(flags=data['user'].get('flags', 0),
|
||||
premium_since=parse_time(since),
|
||||
mutual_guilds=mutual_guilds,
|
||||
user=self,
|
||||
connected_accounts=data['connected_accounts'])
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -27,6 +25,7 @@ DEALINGS IN THE SOFTWARE.
|
||||
import array
|
||||
import asyncio
|
||||
import collections.abc
|
||||
from typing import Optional, overload
|
||||
import unicodedata
|
||||
from base64 import b64encode
|
||||
from bisect import bisect_left
|
||||
@ -40,8 +39,19 @@ import warnings
|
||||
|
||||
from .errors import InvalidArgument
|
||||
|
||||
__all__ = (
|
||||
'oauth_uri',
|
||||
'snowflake_time',
|
||||
'time_snowflake',
|
||||
'find',
|
||||
'get',
|
||||
'sleep_until',
|
||||
'utcnow',
|
||||
'remove_markdown',
|
||||
'escape_markdown',
|
||||
'escape_mentions',
|
||||
)
|
||||
DISCORD_EPOCH = 1420070400000
|
||||
MAX_ASYNCIO_SECONDS = 3456000
|
||||
|
||||
class cached_property:
|
||||
def __init__(self, function):
|
||||
@ -105,9 +115,17 @@ class SequenceProxy(collections.abc.Sequence):
|
||||
def count(self, value):
|
||||
return self.__proxied.count(value)
|
||||
|
||||
def parse_time(timestamp):
|
||||
@overload
|
||||
def parse_time(timestamp: None) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def parse_time(timestamp: str) -> datetime.datetime:
|
||||
...
|
||||
|
||||
def parse_time(timestamp: Optional[str]) -> Optional[datetime.datetime]:
|
||||
if timestamp:
|
||||
return datetime.datetime(*map(int, re.split(r'[^\d]', timestamp.replace('+00:00', ''))))
|
||||
return datetime.datetime.fromisoformat(timestamp)
|
||||
return None
|
||||
|
||||
def copy_doc(original):
|
||||
@ -158,7 +176,7 @@ def oauth_url(client_id, permissions=None, guild=None, redirect_uri=None, scopes
|
||||
:class:`str`
|
||||
The OAuth2 URL for inviting the bot into guilds.
|
||||
"""
|
||||
url = 'https://discord.com/oauth2/authorize?client_id={}'.format(client_id)
|
||||
url = f'https://discord.com/oauth2/authorize?client_id={client_id}'
|
||||
url = url + '&scope=' + '+'.join(scopes or ('bot',))
|
||||
if permissions is not None:
|
||||
url = url + '&permissions=' + str(permissions.value)
|
||||
@ -170,7 +188,7 @@ def oauth_url(client_id, permissions=None, guild=None, redirect_uri=None, scopes
|
||||
return url
|
||||
|
||||
|
||||
def snowflake_time(id):
|
||||
def snowflake_time(id: int) -> datetime.datetime:
|
||||
"""
|
||||
Parameters
|
||||
-----------
|
||||
@ -180,25 +198,34 @@ def snowflake_time(id):
|
||||
Returns
|
||||
--------
|
||||
:class:`datetime.datetime`
|
||||
The creation date in UTC of a Discord snowflake ID."""
|
||||
return datetime.datetime.utcfromtimestamp(((id >> 22) + DISCORD_EPOCH) / 1000)
|
||||
An aware datetime in UTC representing the creation time of the snowflake.
|
||||
"""
|
||||
timestamp = ((id >> 22) + DISCORD_EPOCH) / 1000
|
||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
def time_snowflake(datetime_obj, high=False):
|
||||
def time_snowflake(dt: datetime.datetime, high: bool = False) -> int:
|
||||
"""Returns a numeric snowflake pretending to be created at the given date.
|
||||
|
||||
When using as the lower end of a range, use ``time_snowflake(high=False) - 1`` to be inclusive, ``high=True`` to be exclusive
|
||||
When using as the higher end of a range, use ``time_snowflake(high=True)`` + 1 to be inclusive, ``high=False`` to be exclusive
|
||||
When using as the lower end of a range, use ``time_snowflake(high=False) - 1``
|
||||
to be inclusive, ``high=True`` to be exclusive.
|
||||
|
||||
When using as the higher end of a range, use ``time_snowflake(high=True) + 1``
|
||||
to be inclusive, ``high=False`` to be exclusive
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
datetime_obj: :class:`datetime.datetime`
|
||||
A timezone-naive datetime object representing UTC time.
|
||||
dt: :class:`datetime.datetime`
|
||||
A datetime object to convert to a snowflake.
|
||||
If naive, the timezone is assumed to be local time.
|
||||
high: :class:`bool`
|
||||
Whether or not to set the lower 22 bit to high or low.
|
||||
"""
|
||||
unix_seconds = (datetime_obj - type(datetime_obj)(1970, 1, 1)).total_seconds()
|
||||
discord_millis = int(unix_seconds * 1000 - DISCORD_EPOCH)
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`int`
|
||||
The snowflake representing the time given.
|
||||
"""
|
||||
discord_millis = int(dt.timestamp() * 1000 - DISCORD_EPOCH)
|
||||
return (discord_millis << 22) + (2**22-1 if high else 0)
|
||||
|
||||
def find(predicate, seq):
|
||||
@ -376,19 +403,31 @@ async def sleep_until(when, result=None):
|
||||
-----------
|
||||
when: :class:`datetime.datetime`
|
||||
The timestamp in which to sleep until. If the datetime is naive then
|
||||
it is assumed to be in UTC.
|
||||
it is assumed to be local time.
|
||||
result: Any
|
||||
If provided is returned to the caller when the coroutine completes.
|
||||
"""
|
||||
if when.tzinfo is None:
|
||||
when = when.replace(tzinfo=datetime.timezone.utc)
|
||||
when = when.astimezone()
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
delta = (when - now).total_seconds()
|
||||
while delta > MAX_ASYNCIO_SECONDS:
|
||||
await asyncio.sleep(MAX_ASYNCIO_SECONDS)
|
||||
delta -= MAX_ASYNCIO_SECONDS
|
||||
return await asyncio.sleep(max(delta, 0), result)
|
||||
|
||||
def utcnow() -> datetime.datetime:
|
||||
"""A helper function to return an aware UTC datetime representing the current time.
|
||||
|
||||
This should be preferred to :func:`datetime.datetime.utcnow` since it is an aware
|
||||
datetime, compared to the naive datetime in the standard library.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`datetime.datetime`
|
||||
The current aware datetime in UTC.
|
||||
"""
|
||||
return datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
def valid_icon_size(size):
|
||||
"""Icons must be power of 2 within [16, 4096]."""
|
||||
return not size & (size - 1) and size in range(16, 4097)
|
||||
@ -489,21 +528,21 @@ _MARKDOWN_ESCAPE_SUBREGEX = '|'.join(r'\{0}(?=([\s\S]*((?<!\{0})\{0})))'.format(
|
||||
|
||||
_MARKDOWN_ESCAPE_COMMON = r'^>(?:>>)?\s|\[.+\]\(.+\)'
|
||||
|
||||
_MARKDOWN_ESCAPE_REGEX = re.compile(r'(?P<markdown>%s|%s)' % (_MARKDOWN_ESCAPE_SUBREGEX, _MARKDOWN_ESCAPE_COMMON), re.MULTILINE)
|
||||
_MARKDOWN_ESCAPE_REGEX = re.compile(fr'(?P<markdown>{_MARKDOWN_ESCAPE_SUBREGEX}|{_MARKDOWN_ESCAPE_COMMON})', re.MULTILINE)
|
||||
|
||||
_URL_REGEX = r'(?P<url><[^: >]+:\/[^ >]+>|(?:https?|steam):\/\/[^\s<]+[^<.,:;\"\'\]\s])'
|
||||
|
||||
_MARKDOWN_STOCK_REGEX = r'(?P<markdown>[_\\~|\*`]|%s)' % _MARKDOWN_ESCAPE_COMMON
|
||||
_MARKDOWN_STOCK_REGEX = fr'(?P<markdown>[_\\~|\*`]|{_MARKDOWN_ESCAPE_COMMON})'
|
||||
|
||||
def remove_markdown(text, *, ignore_links=True):
|
||||
"""A helper function that removes markdown characters.
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
|
||||
.. note::
|
||||
This function is not markdown aware and may remove meaning from the original text. For example,
|
||||
if the input contains ``10 * 5`` then it will be converted into ``10 5``.
|
||||
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
text: :class:`str`
|
||||
@ -525,7 +564,7 @@ def remove_markdown(text, *, ignore_links=True):
|
||||
|
||||
regex = _MARKDOWN_STOCK_REGEX
|
||||
if ignore_links:
|
||||
regex = '(?:%s|%s)' % (_URL_REGEX, regex)
|
||||
regex = f'(?:{_URL_REGEX}|{regex})'
|
||||
return re.sub(regex, replacement, text, 0, re.MULTILINE)
|
||||
|
||||
def escape_markdown(text, *, as_needed=False, ignore_links=True):
|
||||
@ -563,7 +602,7 @@ def escape_markdown(text, *, as_needed=False, ignore_links=True):
|
||||
|
||||
regex = _MARKDOWN_STOCK_REGEX
|
||||
if ignore_links:
|
||||
regex = '(?:%s|%s)' % (_URL_REGEX, regex)
|
||||
regex = f'(?:{_URL_REGEX}|{regex})'
|
||||
return re.sub(regex, replacement, text, 0, re.MULTILINE)
|
||||
else:
|
||||
text = re.sub(r'\\', r'\\\\', text)
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -57,6 +55,11 @@ try:
|
||||
except ImportError:
|
||||
has_nacl = False
|
||||
|
||||
__all__ = (
|
||||
'VoiceProtocol',
|
||||
'VoiceClient',
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class VoiceProtocol:
|
||||
@ -558,7 +561,7 @@ class VoiceClient(VoiceProtocol):
|
||||
raise ClientException('Already playing audio.')
|
||||
|
||||
if not isinstance(source, AudioSource):
|
||||
raise TypeError('source must an AudioSource not {0.__class__.__name__}'.format(source))
|
||||
raise TypeError(f'source must an AudioSource not {source.__class__.__name__}')
|
||||
|
||||
if not self.encoder and not source.is_opus():
|
||||
self.encoder = opus.Encoder()
|
||||
@ -601,7 +604,7 @@ class VoiceClient(VoiceProtocol):
|
||||
@source.setter
|
||||
def source(self, value):
|
||||
if not isinstance(value, AudioSource):
|
||||
raise TypeError('expected AudioSource not {0.__class__.__name__}.'.format(value))
|
||||
raise TypeError(f'expected AudioSource not {value.__class__.__name__}.')
|
||||
|
||||
if self._player is None:
|
||||
raise ValueError('Not playing anything.')
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -47,10 +45,104 @@ __all__ = (
|
||||
'RequestsWebhookAdapter',
|
||||
'Webhook',
|
||||
'WebhookMessage',
|
||||
'PartialWebhookChannel',
|
||||
'PartialWebhookGuild'
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class PartialWebhookChannel(Hashable):
|
||||
"""Represents a partial channel for webhooks.
|
||||
|
||||
These are typically given for channel follower webhooks.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
id: :class:`int`
|
||||
The partial channel's ID.
|
||||
name: :class:`str`
|
||||
The partial channel's name.
|
||||
"""
|
||||
|
||||
__slots__ = ('id', 'name')
|
||||
|
||||
def __init__(self, *, data):
|
||||
self.id = int(data['id'])
|
||||
self.name = data['name']
|
||||
|
||||
def __repr__(self):
|
||||
return f'<PartialWebhookChannel name={self.name!r} id={self.id}>'
|
||||
|
||||
class PartialWebhookGuild(Hashable):
|
||||
"""Represents a partial guild for webhooks.
|
||||
|
||||
These are typically given for channel follower webhooks.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
id: :class:`int`
|
||||
The partial guild's ID.
|
||||
name: :class:`str`
|
||||
The partial guild's name.
|
||||
icon: :class:`str`
|
||||
The partial guild's icon
|
||||
"""
|
||||
|
||||
__slots__ = ('id', 'name', 'icon', '_state')
|
||||
|
||||
def __init__(self, *, data, state):
|
||||
self._state = state
|
||||
self.id = int(data['id'])
|
||||
self.name = data['name']
|
||||
self.icon = data['icon']
|
||||
|
||||
def __repr__(self):
|
||||
return f'<PartialWebhookGuild name={self.name!r} id={self.id}>'
|
||||
|
||||
@property
|
||||
def icon_url(self):
|
||||
""":class:`Asset`: Returns the guild's icon asset."""
|
||||
return self.icon_url_as()
|
||||
|
||||
def is_icon_animated(self):
|
||||
""":class:`bool`: Returns True if the guild has an animated icon."""
|
||||
return bool(self.icon and self.icon.startswith('a_'))
|
||||
|
||||
def icon_url_as(self, *, format=None, static_format='webp', size=1024):
|
||||
"""Returns an :class:`Asset` for the guild's icon.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg', 'png' or 'gif', and
|
||||
'gif' is only valid for animated avatars. The size must be a power of 2
|
||||
between 16 and 4096.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
format: Optional[:class:`str`]
|
||||
The format to attempt to convert the icon to.
|
||||
If the format is ``None``, then it is automatically
|
||||
detected into either 'gif' or static_format depending on the
|
||||
icon being animated or not.
|
||||
static_format: Optional[:class:`str`]
|
||||
Format to attempt to convert only non-animated icons to.
|
||||
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_guild_icon(self._state, self, format=format, static_format=static_format, size=size)
|
||||
|
||||
class WebhookAdapter:
|
||||
"""Base class for all webhook adapters.
|
||||
|
||||
@ -65,7 +157,7 @@ class WebhookAdapter:
|
||||
def _prepare(self, webhook):
|
||||
self._webhook_id = webhook.id
|
||||
self._webhook_token = webhook.token
|
||||
self._request_url = '{0.BASE}/webhooks/{1}/{2}'.format(self, webhook.id, webhook.token)
|
||||
self._request_url = f'{self.BASE}/webhooks/{webhook.id}/{webhook.token}'
|
||||
self.webhook = webhook
|
||||
|
||||
def is_async(self):
|
||||
@ -100,10 +192,10 @@ class WebhookAdapter:
|
||||
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)
|
||||
return self.request('PATCH', f'{self._request_url}/messages/{message_id}', payload=payload)
|
||||
|
||||
def delete_webhook_message(self, message_id):
|
||||
return self.request('DELETE', '{}/messages/{}'.format(self._request_url, message_id))
|
||||
return self.request('DELETE', f'{self._request_url}/messages/{message_id}')
|
||||
|
||||
def handle_execution_response(self, data, *, wait):
|
||||
"""Transforms the webhook execution response into something
|
||||
@ -158,7 +250,7 @@ class WebhookAdapter:
|
||||
multipart = None
|
||||
files_to_pass = None
|
||||
|
||||
url = '%s?wait=%d' % (self._request_url, wait)
|
||||
url = f'{self._request_url}?wait={int(wait)}'
|
||||
maybe_coro = None
|
||||
try:
|
||||
maybe_coro = self.request('POST', url, multipart=multipart, payload=data, files=files_to_pass)
|
||||
@ -405,10 +497,6 @@ class _PartialWebhookState:
|
||||
def store_user(self, data):
|
||||
return BaseUser(state=self, data=data)
|
||||
|
||||
@property
|
||||
def is_bot(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def http(self):
|
||||
if self.parent is not None:
|
||||
@ -422,7 +510,7 @@ class _PartialWebhookState:
|
||||
if self.parent is not None:
|
||||
return getattr(self.parent, attr)
|
||||
|
||||
raise AttributeError('PartialWebhookState does not support {0!r}.'.format(attr))
|
||||
raise AttributeError(f'PartialWebhookState does not support {attr!r}.')
|
||||
|
||||
class WebhookMessage(Message):
|
||||
"""Represents a message sent from your webhook.
|
||||
@ -483,7 +571,7 @@ class WebhookMessage(Message):
|
||||
except HTTPException:
|
||||
pass
|
||||
|
||||
asyncio.ensure_future(inner_call(), loop=self._state.loop)
|
||||
asyncio.create_task(inner_call())
|
||||
return await asyncio.sleep(0)
|
||||
|
||||
def delete(self, *, delay=None):
|
||||
@ -597,10 +685,21 @@ class Webhook(Hashable):
|
||||
The default name of the webhook.
|
||||
avatar: Optional[:class:`str`]
|
||||
The default avatar of the webhook.
|
||||
source_guild: Optional[:class:`PartialWebhookGuild`]
|
||||
The guild of the channel that this webhook is following.
|
||||
Only given if :attr:`type` is :attr:`WebhookType.channel_follower`.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
source_channel: Optional[:class:`PartialWebhookChannel`]
|
||||
The channel that this webhook is following.
|
||||
Only given if :attr:`type` is :attr:`WebhookType.channel_follower`.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
__slots__ = ('id', 'type', 'guild_id', 'channel_id', 'user', 'name',
|
||||
'avatar', 'token', '_state', '_adapter')
|
||||
'avatar', 'token', '_state', '_adapter', 'source_channel', 'source_guild')
|
||||
|
||||
def __init__(self, data, *, adapter, state=None):
|
||||
self.id = int(data['id'])
|
||||
@ -622,13 +721,25 @@ class Webhook(Hashable):
|
||||
else:
|
||||
self.user = User(state=state, data=user)
|
||||
|
||||
source_channel = data.get('source_channel')
|
||||
if source_channel:
|
||||
source_channel = PartialWebhookChannel(data=source_channel)
|
||||
|
||||
self.source_channel = source_channel
|
||||
|
||||
source_guild = data.get('source_guild')
|
||||
if source_guild:
|
||||
source_guild = PartialWebhookGuild(data=source_guild, state=state)
|
||||
|
||||
self.source_guild = source_guild
|
||||
|
||||
def __repr__(self):
|
||||
return '<Webhook id=%r>' % self.id
|
||||
return f'<Webhook id={self.id!r}>'
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
""":class:`str` : Returns the webhook's url."""
|
||||
return 'https://discord.com/api/webhooks/{}/{}'.format(self.id, self.token)
|
||||
return f'https://discord.com/api/webhooks/{self.id}/{self.token}'
|
||||
|
||||
@classmethod
|
||||
def partial(cls, id, token, *, adapter):
|
||||
@ -697,7 +808,7 @@ class Webhook(Hashable):
|
||||
|
||||
@classmethod
|
||||
def _as_follower(cls, data, *, channel, user):
|
||||
name = "{} #{}".format(channel.guild, channel)
|
||||
name = f"{channel.guild} #{channel}"
|
||||
feed = {
|
||||
'id': data['webhook_id'],
|
||||
'type': 2,
|
||||
|
@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
@ -30,6 +28,12 @@ from .activity import create_activity
|
||||
from .invite import Invite
|
||||
from .enums import Status, try_enum
|
||||
|
||||
__all__ = (
|
||||
'WidgetChannel',
|
||||
'WidgetMember',
|
||||
'Widget',
|
||||
)
|
||||
|
||||
class WidgetChannel:
|
||||
"""Represents a "partial" widget channel.
|
||||
|
||||
@ -77,7 +81,7 @@ class WidgetChannel:
|
||||
@property
|
||||
def mention(self):
|
||||
""":class:`str`: The string that allows you to mention the channel."""
|
||||
return '<#%s>' % self.id
|
||||
return f'<#{self.id}>'
|
||||
|
||||
@property
|
||||
def created_at(self):
|
||||
@ -236,7 +240,7 @@ class Widget:
|
||||
@property
|
||||
def json_url(self):
|
||||
""":class:`str`: The JSON URL of the widget."""
|
||||
return "https://discord.com/api/guilds/{0.id}/widget.json".format(self)
|
||||
return f"https://discord.com/api/guilds/{self.id}/widget.json"
|
||||
|
||||
@property
|
||||
def invite_url(self):
|
||||
|
336
docs/api.rst
336
docs/api.rst
@ -340,7 +340,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
||||
:type channel: :class:`abc.Messageable`
|
||||
:param user: The user that started typing.
|
||||
:type user: Union[:class:`User`, :class:`Member`]
|
||||
:param when: When the typing started as a naive datetime in UTC.
|
||||
:param when: When the typing started as an aware datetime in UTC.
|
||||
:type when: :class:`datetime.datetime`
|
||||
|
||||
.. function:: on_message(message)
|
||||
@ -585,6 +585,17 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
||||
:param payload: The raw event payload data.
|
||||
:type payload: :class:`RawReactionClearEmojiEvent`
|
||||
|
||||
.. function:: on_interaction(interaction)
|
||||
|
||||
Called when an interaction happened.
|
||||
|
||||
This currently happens due to slash command invocations.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param interaction: The interaction data.
|
||||
:type interaction: :class:`Interaction`
|
||||
|
||||
.. function:: on_private_channel_delete(channel)
|
||||
on_private_channel_create(channel)
|
||||
|
||||
@ -612,7 +623,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
||||
|
||||
:param channel: The private channel that had its pins updated.
|
||||
:type channel: :class:`abc.PrivateChannel`
|
||||
:param last_pin: The latest message that was pinned as a naive datetime in UTC. Could be ``None``.
|
||||
:param last_pin: The latest message that was pinned as an aware datetime in UTC. Could be ``None``.
|
||||
:type last_pin: Optional[:class:`datetime.datetime`]
|
||||
|
||||
.. function:: on_guild_channel_delete(channel)
|
||||
@ -646,7 +657,7 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
||||
|
||||
:param channel: The guild channel that had its pins updated.
|
||||
:type channel: :class:`abc.GuildChannel`
|
||||
:param last_pin: The latest message that was pinned as a naive datetime in UTC. Could be ``None``.
|
||||
:param last_pin: The latest message that was pinned as an aware datetime in UTC. Could be ``None``.
|
||||
:type last_pin: Optional[:class:`datetime.datetime`]
|
||||
|
||||
.. function:: on_guild_integrations_update(guild)
|
||||
@ -897,29 +908,6 @@ to handle it, which defaults to print a traceback and ignoring the exception.
|
||||
:param user: The user that joined or left.
|
||||
:type user: :class:`User`
|
||||
|
||||
.. function:: on_relationship_add(relationship)
|
||||
on_relationship_remove(relationship)
|
||||
|
||||
Called when a :class:`Relationship` is added or removed from the
|
||||
:class:`ClientUser`.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
:param relationship: The relationship that was added or removed.
|
||||
:type relationship: :class:`Relationship`
|
||||
|
||||
.. function:: on_relationship_update(before, after)
|
||||
|
||||
Called when a :class:`Relationship` is updated, e.g. when you
|
||||
block a friend or a friendship is accepted.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
:param before: The previous relationship status.
|
||||
:type before: :class:`Relationship`
|
||||
:param after: The updated relationship status.
|
||||
:type after: :class:`Relationship`
|
||||
|
||||
.. _discord-api-utils:
|
||||
|
||||
Utility Functions
|
||||
@ -945,96 +933,7 @@ Utility Functions
|
||||
|
||||
.. autofunction:: discord.utils.sleep_until
|
||||
|
||||
Profile
|
||||
---------
|
||||
|
||||
.. class:: Profile
|
||||
|
||||
A namedtuple representing a user's Discord public profile.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. attribute:: user
|
||||
|
||||
The :class:`User` the profile belongs to.
|
||||
|
||||
:type: :class:`User`
|
||||
.. attribute:: premium
|
||||
|
||||
A boolean indicating if the user has premium (i.e. Discord Nitro).
|
||||
|
||||
:type: :class:`bool`
|
||||
.. attribute:: nitro
|
||||
|
||||
An alias for :attr:`premium`.
|
||||
.. attribute:: premium_since
|
||||
|
||||
A naive UTC datetime indicating how long the user has been premium since.
|
||||
This could be ``None`` if not applicable.
|
||||
|
||||
:type: :class:`datetime.datetime`
|
||||
.. attribute:: staff
|
||||
|
||||
A boolean indicating if the user is Discord Staff.
|
||||
|
||||
:type: :class:`bool`
|
||||
.. attribute:: partner
|
||||
|
||||
A boolean indicating if the user is a Discord Partner.
|
||||
|
||||
:type: :class:`bool`
|
||||
.. attribute:: bug_hunter
|
||||
|
||||
A boolean indicating if the user is a Bug Hunter.
|
||||
|
||||
:type: :class:`bool`
|
||||
.. attribute:: early_supporter
|
||||
|
||||
A boolean indicating if the user has had premium before 10 October, 2018.
|
||||
|
||||
:type: :class:`bool`
|
||||
.. attribute:: hypesquad
|
||||
|
||||
A boolean indicating if the user is in Discord HypeSquad.
|
||||
|
||||
:type: :class:`bool`
|
||||
.. attribute:: hypesquad_houses
|
||||
|
||||
A list of :class:`HypeSquadHouse` that the user is in.
|
||||
|
||||
:type: List[:class:`HypeSquadHouse`]
|
||||
.. attribute:: team_user
|
||||
|
||||
A boolean indicating if the user is in part of a team.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
:type: :class:`bool`
|
||||
|
||||
.. attribute:: system
|
||||
|
||||
A boolean indicating if the user is officially part of the Discord urgent message system.
|
||||
|
||||
.. versionadded:: 1.3
|
||||
|
||||
:type: :class:`bool`
|
||||
|
||||
.. attribute:: mutual_guilds
|
||||
|
||||
A list of :class:`Guild` that the :class:`ClientUser` shares with this
|
||||
user.
|
||||
|
||||
:type: List[:class:`Guild`]
|
||||
|
||||
.. attribute:: connected_accounts
|
||||
|
||||
A list of dict objects indicating the accounts the user has connected.
|
||||
|
||||
An example entry can be seen below: ::
|
||||
|
||||
{"type": "twitch", "id": "92473777", "name": "discordapp"}
|
||||
|
||||
:type: List[Dict[:class:`str`, :class:`str`]]
|
||||
.. autofunction:: discord.utils.utcnow
|
||||
|
||||
.. _discord-api-enums:
|
||||
|
||||
@ -1201,6 +1100,20 @@ of :class:`enum.Enum`.
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
.. class:: InteractionType
|
||||
|
||||
Specifies the type of :class:`Interaction`.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
.. attribute:: ping
|
||||
|
||||
Represents Discord pinging to see if the interaction response server is alive.
|
||||
|
||||
.. attribute:: application_command
|
||||
|
||||
Represents a slash command interaction.
|
||||
|
||||
.. class:: HypeSquadHouse
|
||||
|
||||
Specifies the HypeSquad house a user belongs to.
|
||||
@ -1940,127 +1853,6 @@ of :class:`enum.Enum`.
|
||||
|
||||
The action is the update of something.
|
||||
|
||||
.. class:: RelationshipType
|
||||
|
||||
Specifies the type of :class:`Relationship`.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This only applies to users, *not* bots.
|
||||
|
||||
.. attribute:: friend
|
||||
|
||||
You are friends with this user.
|
||||
|
||||
.. attribute:: blocked
|
||||
|
||||
You have blocked this user.
|
||||
|
||||
.. attribute:: incoming_request
|
||||
|
||||
The user has sent you a friend request.
|
||||
|
||||
.. attribute:: outgoing_request
|
||||
|
||||
You have sent a friend request to this user.
|
||||
|
||||
|
||||
.. class:: UserContentFilter
|
||||
|
||||
Represents the options found in ``Settings > Privacy & Safety > Safe Direct Messaging``
|
||||
in the Discord client.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This only applies to users, *not* bots.
|
||||
|
||||
.. attribute:: all_messages
|
||||
|
||||
Scan all direct messages from everyone.
|
||||
|
||||
.. attribute:: friends
|
||||
|
||||
Scan all direct messages that aren't from friends.
|
||||
|
||||
.. attribute:: disabled
|
||||
|
||||
Don't scan any direct messages.
|
||||
|
||||
|
||||
.. class:: FriendFlags
|
||||
|
||||
Represents the options found in ``Settings > Privacy & Safety > Who Can Add You As A Friend``
|
||||
in the Discord client.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This only applies to users, *not* bots.
|
||||
|
||||
.. attribute:: noone
|
||||
|
||||
This allows no-one to add you as a friend.
|
||||
|
||||
.. attribute:: mutual_guilds
|
||||
|
||||
This allows guild members to add you as a friend.
|
||||
|
||||
.. attribute:: mutual_friends
|
||||
|
||||
This allows friends of friends to add you as a friend.
|
||||
|
||||
.. attribute:: guild_and_friends
|
||||
|
||||
This is a superset of :attr:`mutual_guilds` and :attr:`mutual_friends`.
|
||||
|
||||
.. attribute:: everyone
|
||||
|
||||
This allows everyone to add you as a friend.
|
||||
|
||||
|
||||
.. class:: PremiumType
|
||||
|
||||
Represents the user's Discord Nitro subscription type.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This only applies to users, *not* bots.
|
||||
|
||||
.. attribute:: nitro
|
||||
|
||||
Represents the Discord Nitro with Nitro-exclusive games.
|
||||
|
||||
.. attribute:: nitro_classic
|
||||
|
||||
Represents the Discord Nitro with no Nitro-exclusive games.
|
||||
|
||||
|
||||
.. class:: Theme
|
||||
|
||||
Represents the theme synced across all Discord clients.
|
||||
|
||||
.. deprecated:: 1.7
|
||||
|
||||
.. note::
|
||||
|
||||
This only applies to users, *not* bots.
|
||||
|
||||
.. attribute:: light
|
||||
|
||||
Represents the Light theme on Discord.
|
||||
|
||||
.. attribute:: dark
|
||||
|
||||
Represents the Dark theme on Discord.
|
||||
|
||||
|
||||
.. class:: TeamMembershipState
|
||||
|
||||
Represents the membership state of a team member retrieved through :func:`Bot.application_info`.
|
||||
@ -2734,20 +2526,19 @@ interface, :meth:`WebhookAdapter.request`.
|
||||
Abstract Base Classes
|
||||
-----------------------
|
||||
|
||||
An :term:`py:abstract base class` (also known as an ``abc``) is a class that models can inherit
|
||||
to get their behaviour. The Python implementation of an :doc:`abc <py:library/abc>` is
|
||||
slightly different in that you can register them at run-time. **Abstract base classes cannot be instantiated**.
|
||||
They are mainly there for usage with :func:`py:isinstance` and :func:`py:issubclass`\.
|
||||
An :term:`abstract base class` (also known as an ``abc``) is a class that models can inherit
|
||||
to get their behaviour. **Abstract base classes should not be instantiated**.
|
||||
They are mainly there for usage with :func:`isinstance` and :func:`issubclass`\.
|
||||
|
||||
This library has a module related to abstract base classes, some of which are actually from the :doc:`abc <py:library/abc>` standard
|
||||
module, others which are not.
|
||||
This library has a module related to abstract base classes, in which all the ABCs are subclasses of
|
||||
:class:`typing.Protocol`.
|
||||
|
||||
Snowflake
|
||||
~~~~~~~~~~
|
||||
|
||||
.. attributetable:: discord.abc.Snowflake
|
||||
|
||||
.. autoclass:: discord.abc.Snowflake
|
||||
.. autoclass:: discord.abc.Snowflake()
|
||||
:members:
|
||||
|
||||
User
|
||||
@ -2755,7 +2546,7 @@ User
|
||||
|
||||
.. attributetable:: discord.abc.User
|
||||
|
||||
.. autoclass:: discord.abc.User
|
||||
.. autoclass:: discord.abc.User()
|
||||
:members:
|
||||
|
||||
PrivateChannel
|
||||
@ -2763,7 +2554,7 @@ PrivateChannel
|
||||
|
||||
.. attributetable:: discord.abc.PrivateChannel
|
||||
|
||||
.. autoclass:: discord.abc.PrivateChannel
|
||||
.. autoclass:: discord.abc.PrivateChannel()
|
||||
:members:
|
||||
|
||||
GuildChannel
|
||||
@ -2771,7 +2562,7 @@ GuildChannel
|
||||
|
||||
.. attributetable:: discord.abc.GuildChannel
|
||||
|
||||
.. autoclass:: discord.abc.GuildChannel
|
||||
.. autoclass:: discord.abc.GuildChannel()
|
||||
:members:
|
||||
|
||||
Messageable
|
||||
@ -2779,7 +2570,7 @@ Messageable
|
||||
|
||||
.. attributetable:: discord.abc.Messageable
|
||||
|
||||
.. autoclass:: discord.abc.Messageable
|
||||
.. autoclass:: discord.abc.Messageable()
|
||||
:members:
|
||||
:exclude-members: history, typing
|
||||
|
||||
@ -2794,7 +2585,7 @@ Connectable
|
||||
|
||||
.. attributetable:: discord.abc.Connectable
|
||||
|
||||
.. autoclass:: discord.abc.Connectable
|
||||
.. autoclass:: discord.abc.Connectable()
|
||||
|
||||
.. _discord_api_models:
|
||||
|
||||
@ -2832,14 +2623,6 @@ ClientUser
|
||||
:members:
|
||||
:inherited-members:
|
||||
|
||||
Relationship
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. attributetable:: Relationship
|
||||
|
||||
.. autoclass:: Relationship()
|
||||
:members:
|
||||
|
||||
User
|
||||
~~~~~
|
||||
|
||||
@ -2901,22 +2684,6 @@ Reaction
|
||||
.. automethod:: users
|
||||
:async-for:
|
||||
|
||||
CallMessage
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. attributetable:: CallMessage
|
||||
|
||||
.. autoclass:: CallMessage()
|
||||
:members:
|
||||
|
||||
GroupCall
|
||||
~~~~~~~~~~
|
||||
|
||||
.. attributetable:: GroupCall
|
||||
|
||||
.. autoclass:: GroupCall()
|
||||
:members:
|
||||
|
||||
Guild
|
||||
~~~~~~
|
||||
|
||||
@ -2957,6 +2724,14 @@ Integration
|
||||
.. autoclass:: IntegrationAccount()
|
||||
:members:
|
||||
|
||||
Interaction
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. attributetable:: Interaction
|
||||
|
||||
.. autoclass:: Interaction()
|
||||
:members:
|
||||
|
||||
Member
|
||||
~~~~~~
|
||||
|
||||
@ -3218,6 +2993,21 @@ RawReactionClearEmojiEvent
|
||||
.. autoclass:: RawReactionClearEmojiEvent()
|
||||
:members:
|
||||
|
||||
PartialWebhookGuild
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. attributetable:: PartialWebhookGuild
|
||||
|
||||
.. autoclass:: PartialWebhookGuild()
|
||||
:members:
|
||||
|
||||
PartialWebhookChannel
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. attributetable:: PartialWebhookChannel
|
||||
|
||||
.. autoclass:: PartialWebhookChannel()
|
||||
:members:
|
||||
|
||||
.. _discord_api_data:
|
||||
|
||||
|
20
docs/conf.py
20
docs/conf.py
@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# discord.py documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Aug 21 05:43:30 2015.
|
||||
@ -44,6 +43,7 @@ extensions = [
|
||||
]
|
||||
|
||||
autodoc_member_order = 'bysource'
|
||||
autodoc_typehints = 'none'
|
||||
|
||||
extlinks = {
|
||||
'issue': ('https://github.com/Rapptz/discord.py/issues/%s', 'GH-'),
|
||||
@ -76,8 +76,8 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'discord.py'
|
||||
copyright = u'2015-present, Rapptz'
|
||||
project = 'discord.py'
|
||||
copyright = '2015-present, Rapptz'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
@ -160,7 +160,7 @@ 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,
|
||||
'examples': f'https://github.com/Rapptz/discord.py/tree/{branch}/examples',
|
||||
}
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
@ -283,8 +283,8 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'discord.py.tex', u'discord.py Documentation',
|
||||
u'Rapptz', 'manual'),
|
||||
('index', 'discord.py.tex', 'discord.py Documentation',
|
||||
'Rapptz', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
@ -313,8 +313,8 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'discord.py', u'discord.py Documentation',
|
||||
[u'Rapptz'], 1)
|
||||
('index', 'discord.py', 'discord.py Documentation',
|
||||
['Rapptz'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
@ -327,8 +327,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'discord.py', u'discord.py Documentation',
|
||||
u'Rapptz', 'discord.py', 'One line description of project.',
|
||||
('index', 'discord.py', 'discord.py Documentation',
|
||||
'Rapptz', 'discord.py', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
@ -379,7 +379,10 @@ A lot of discord models work out of the gate as a parameter:
|
||||
- :class:`PartialMessage` (since v1.7)
|
||||
- :class:`TextChannel`
|
||||
- :class:`VoiceChannel`
|
||||
<<<<<<< HEAD
|
||||
- :class:`StageChannel` (since v1.7)
|
||||
=======
|
||||
>>>>>>> 523e35e4f3c3c49d4e471359f9fb559242bbecc8
|
||||
- :class:`StoreChannel` (since v1.7)
|
||||
- :class:`CategoryChannel`
|
||||
- :class:`Invite`
|
||||
@ -411,10 +414,15 @@ converter is given below:
|
||||
+--------------------------+-------------------------------------------------+
|
||||
| :class:`VoiceChannel` | :class:`~ext.commands.VoiceChannelConverter` |
|
||||
+--------------------------+-------------------------------------------------+
|
||||
<<<<<<< HEAD
|
||||
| :class:`StageChannel` | :class:`~ext.commands.StageChannelConverter` |
|
||||
+--------------------------+-------------------------------------------------+
|
||||
| :class:`StoreChannel` | :class:`~ext.commands.StoreChannelConverter` |
|
||||
+--------------------------+-------------------------------------------------+
|
||||
=======
|
||||
| :class:`StoreChannel` | :class:`~ext.commands.StoreChannelConverter` |
|
||||
+--------------------------+-------------------------------------------------+
|
||||
>>>>>>> 523e35e4f3c3c49d4e471359f9fb559242bbecc8
|
||||
| :class:`CategoryChannel` | :class:`~ext.commands.CategoryChannelConverter` |
|
||||
+--------------------------+-------------------------------------------------+
|
||||
| :class:`Invite` | :class:`~ext.commands.InviteConverter` |
|
||||
|
@ -32,6 +32,10 @@ Likewise, **this is the last version to support user bots**.
|
||||
|
||||
Development of v2.0 will have breaking changes and support for newer API features.
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
>>>>>>> 523e35e4f3c3c49d4e471359f9fb559242bbecc8
|
||||
- Add support for stage channels via :class:`StageChannel` (:issue:`6602`, :issue:`6608`)
|
||||
- Add support for :attr:`MessageReference.fail_if_not_exists` (:issue:`6484`)
|
||||
- By default, if the message you're replying to doesn't exist then the API errors out.
|
||||
|
@ -61,7 +61,7 @@ async def cool(ctx):
|
||||
In reality this just checks if a subcommand is being invoked.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send('No, {0.subcommand_passed} is not cool'.format(ctx))
|
||||
await ctx.send(f'No, {ctx.subcommand_passed} is not cool')
|
||||
|
||||
@cool.command(name='bot')
|
||||
async def _bot(ctx):
|
||||
|
@ -70,9 +70,9 @@ class Music(commands.Cog):
|
||||
"""Plays a file from the local filesystem"""
|
||||
|
||||
source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(query))
|
||||
ctx.voice_client.play(source, after=lambda e: print('Player error: %s' % e) if e else None)
|
||||
ctx.voice_client.play(source, after=lambda e: print(f'Player error: {e}') if e else None)
|
||||
|
||||
await ctx.send('Now playing: {}'.format(query))
|
||||
await ctx.send(f'Now playing: {query}')
|
||||
|
||||
@commands.command()
|
||||
async def yt(self, ctx, *, url):
|
||||
@ -80,9 +80,9 @@ class Music(commands.Cog):
|
||||
|
||||
async with ctx.typing():
|
||||
player = await YTDLSource.from_url(url, loop=self.bot.loop)
|
||||
ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None)
|
||||
ctx.voice_client.play(player, after=lambda e: print(f'Player error: {e}') if e else None)
|
||||
|
||||
await ctx.send('Now playing: {}'.format(player.title))
|
||||
await ctx.send(f'Now playing: {player.title}')
|
||||
|
||||
@commands.command()
|
||||
async def stream(self, ctx, *, url):
|
||||
@ -90,9 +90,9 @@ class Music(commands.Cog):
|
||||
|
||||
async with ctx.typing():
|
||||
player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
|
||||
ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None)
|
||||
ctx.voice_client.play(player, after=lambda e: print(f'Player error: {e}') if e else None)
|
||||
|
||||
await ctx.send('Now playing: {}'.format(player.title))
|
||||
await ctx.send(f'Now playing: {player.title}')
|
||||
|
||||
@commands.command()
|
||||
async def volume(self, ctx, volume: int):
|
||||
@ -102,7 +102,7 @@ class Music(commands.Cog):
|
||||
return await ctx.send("Not connected to a voice channel.")
|
||||
|
||||
ctx.voice_client.source.volume = volume / 100
|
||||
await ctx.send("Changed volume to {}%".format(volume))
|
||||
await ctx.send(f"Changed volume to {volume}%")
|
||||
|
||||
@commands.command()
|
||||
async def stop(self, ctx):
|
||||
|
@ -29,7 +29,7 @@ async def userinfo(ctx: commands.Context, user: discord.User):
|
||||
user_id = user.id
|
||||
username = user.name
|
||||
avatar = user.avatar_url
|
||||
await ctx.send('User found: {} -- {}\n{}'.format(user_id, username, avatar))
|
||||
await ctx.send(f'User found: {user_id} -- {username}\n{avatar}')
|
||||
|
||||
@userinfo.error
|
||||
async def userinfo_error(ctx: commands.Context, error: commands.CommandError):
|
||||
@ -70,7 +70,7 @@ class ChannelOrMemberConverter(commands.Converter):
|
||||
# If the value could not be converted we can raise an error
|
||||
# so our error handlers can deal with it in one place.
|
||||
# The error has to be CommandError derived, so BadArgument works fine here.
|
||||
raise commands.BadArgument('No Member or TextChannel could be converted from "{}"'.format(argument))
|
||||
raise commands.BadArgument(f'No Member or TextChannel could be converted from "{argument}"')
|
||||
|
||||
|
||||
|
||||
@ -81,7 +81,7 @@ async def notify(ctx: commands.Context, target: ChannelOrMemberConverter):
|
||||
# the `argument` parameter of the `ChannelOrMemberConverter.convert` method and
|
||||
# the conversion will go through the process defined there.
|
||||
|
||||
await target.send('Hello, {}!'.format(target.name))
|
||||
await target.send(f'Hello, {target.name}!')
|
||||
|
||||
@bot.command()
|
||||
async def ignore(ctx: commands.Context, target: typing.Union[discord.Member, discord.TextChannel]):
|
||||
@ -95,9 +95,9 @@ async def ignore(ctx: commands.Context, target: typing.Union[discord.Member, dis
|
||||
|
||||
# To check the resulting type, `isinstance` is used
|
||||
if isinstance(target, discord.Member):
|
||||
await ctx.send('Member found: {}, adding them to the ignore list.'.format(target.mention))
|
||||
await ctx.send(f'Member found: {target.mention}, adding them to the ignore list.')
|
||||
elif isinstance(target, discord.TextChannel): # this could be an `else` but for completeness' sake.
|
||||
await ctx.send('Channel found: {}, adding it to the ignore list.'.format(target.mention))
|
||||
await ctx.send(f'Channel found: {target.mention}, adding it to the ignore list.')
|
||||
|
||||
# Built-in type converters.
|
||||
@bot.command()
|
||||
|
@ -25,12 +25,12 @@ class MyClient(discord.Client):
|
||||
try:
|
||||
guess = await self.wait_for('message', check=is_correct, timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
return await message.channel.send('Sorry, you took too long it was {}.'.format(answer))
|
||||
return await message.channel.send(f'Sorry, you took too long it was {answer}.')
|
||||
|
||||
if int(guess.content) == answer:
|
||||
await message.channel.send('You are right!')
|
||||
else:
|
||||
await message.channel.send('Oops. It is actually {}.'.format(answer))
|
||||
await message.channel.send(f'Oops. It is actually {answer}.')
|
||||
|
||||
client = MyClient()
|
||||
client.run('token')
|
||||
|
@ -12,7 +12,7 @@ class MyClient(discord.Client):
|
||||
async def on_member_join(self, member):
|
||||
guild = member.guild
|
||||
if guild.system_channel is not None:
|
||||
to_send = 'Welcome {0.mention} to {1.name}!'.format(member, guild)
|
||||
to_send = f'Welcome {member.mention} to {guild.name}!'
|
||||
await guild.system_channel.send(to_send)
|
||||
|
||||
|
||||
|
8
setup.py
8
setup.py
@ -50,7 +50,7 @@ setup(name='enhanced-dpy',
|
||||
"Issue tracker": "https://github.com/iDutchy/discord.py/issues",
|
||||
},
|
||||
version=version,
|
||||
packages=['discord', 'discord.ext.commands', 'discord.ext.tasks'],
|
||||
packages=['discord', 'discord.types', 'discord.ext.commands', 'discord.ext.tasks'],
|
||||
license='MIT',
|
||||
description='A Python wrapper for the Discord API',
|
||||
long_description=readme,
|
||||
@ -58,17 +58,15 @@ setup(name='enhanced-dpy',
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
extras_require=extras_require,
|
||||
python_requires='>=3.5.3',
|
||||
python_requires='>=3.8.0',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Intended Audience :: Developers',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Internet',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
|
Loading…
x
Reference in New Issue
Block a user