fix conflicts
This commit is contained in:
commit
0354036451
@ -33,12 +33,12 @@ from .guild import Guild
|
|||||||
from .flags import *
|
from .flags import *
|
||||||
from .relationship import Relationship
|
from .relationship import Relationship
|
||||||
from .member import Member, VoiceState
|
from .member import Member, VoiceState
|
||||||
from .message import Message, MessageReference, Attachment
|
from .message import *
|
||||||
from .asset import Asset
|
from .asset import Asset
|
||||||
from .errors import *
|
from .errors import *
|
||||||
from .calls import CallMessage, GroupCall
|
from .calls import CallMessage, GroupCall
|
||||||
from .permissions import Permissions, PermissionOverwrite
|
from .permissions import Permissions, PermissionOverwrite
|
||||||
from .role import Role
|
from .role import Role, RoleTags
|
||||||
from .file import File
|
from .file import File
|
||||||
from .colour import Color, Colour
|
from .colour import Color, Colour
|
||||||
from .integrations import Integration, IntegrationAccount
|
from .integrations import Integration, IntegrationAccount
|
||||||
@ -64,11 +64,4 @@ VersionInfo = namedtuple('VersionInfo', 'major minor micro enhanced releaselevel
|
|||||||
|
|
||||||
version_info = VersionInfo(major=1, minor=6, micro=0, enhanced=6, releaselevel='alpha', serial=0)
|
version_info = VersionInfo(major=1, minor=6, micro=0, enhanced=6, releaselevel='alpha', serial=0)
|
||||||
|
|
||||||
try:
|
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||||
from logging import NullHandler
|
|
||||||
except ImportError:
|
|
||||||
class NullHandler(logging.Handler):
|
|
||||||
def emit(self, record):
|
|
||||||
pass
|
|
||||||
|
|
||||||
logging.getLogger(__name__).addHandler(NullHandler())
|
|
||||||
|
@ -32,7 +32,7 @@ import asyncio
|
|||||||
from .iterators import HistoryIterator
|
from .iterators import HistoryIterator
|
||||||
from .context_managers import Typing
|
from .context_managers import Typing
|
||||||
from .enums import ChannelType
|
from .enums import ChannelType
|
||||||
from .errors import InvalidArgument, ClientException, HTTPException
|
from .errors import InvalidArgument, ClientException
|
||||||
from .permissions import PermissionOverwrite, Permissions
|
from .permissions import PermissionOverwrite, Permissions
|
||||||
from .role import Role
|
from .role import Role
|
||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
@ -805,7 +805,8 @@ class Messageable(metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
async def send(self, content=None, *, tts=False, embed=None, file=None,
|
async def send(self, content=None, *, tts=False, embed=None, file=None,
|
||||||
files=None, delete_after=None, nonce=None,
|
files=None, delete_after=None, nonce=None,
|
||||||
allowed_mentions=None, message_reference=None):
|
allowed_mentions=None, reference=None,
|
||||||
|
mention_author=None):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
Sends a message to the destination with the content given.
|
Sends a message to the destination with the content given.
|
||||||
@ -857,6 +858,19 @@ class Messageable(metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
.. versionadded:: 1.5.1.5
|
.. versionadded:: 1.5.1.5
|
||||||
|
|
||||||
|
reference: Union[:class:`~discord.Message`, :class:`~discord.MessageReference`]
|
||||||
|
A reference to the :class:`~discord.Message` to which you are replying, this can be created using
|
||||||
|
:meth:`~discord.Message.to_reference` or passed directly as a :class:`~discord.Message`. You can control
|
||||||
|
whether this mentions the author of the referenced message using the :attr:`~discord.AllowedMentions.replied_user`
|
||||||
|
attribute of ``allowed_mentions`` or by setting ``mention_author``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
mention_author: Optional[:class:`bool`]
|
||||||
|
If set, overrides the :attr:`~discord.AllowedMentions.replied_user` attribute of ``allowed_mentions``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
--------
|
--------
|
||||||
~discord.HTTPException
|
~discord.HTTPException
|
||||||
@ -864,8 +878,10 @@ class Messageable(metaclass=abc.ABCMeta):
|
|||||||
~discord.Forbidden
|
~discord.Forbidden
|
||||||
You do not have the proper permissions to send the message.
|
You do not have the proper permissions to send the message.
|
||||||
~discord.InvalidArgument
|
~discord.InvalidArgument
|
||||||
The ``files`` list is not of the appropriate size or
|
The ``files`` list is not of the appropriate size,
|
||||||
you specified both ``file`` and ``files``.
|
you specified both ``file`` and ``files``,
|
||||||
|
or the ``reference`` object is not a :class:`~discord.Message`
|
||||||
|
or :class:`~discord.MessageReference`.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
---------
|
---------
|
||||||
@ -887,8 +903,15 @@ class Messageable(metaclass=abc.ABCMeta):
|
|||||||
else:
|
else:
|
||||||
allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict()
|
allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict()
|
||||||
|
|
||||||
if message_reference is not None:
|
if mention_author is not None:
|
||||||
message_reference = message_reference.to_dict()
|
allowed_mentions = allowed_mentions or {}
|
||||||
|
allowed_mentions['replied_user'] = bool(mention_author)
|
||||||
|
|
||||||
|
if reference is not None:
|
||||||
|
try:
|
||||||
|
reference = reference.to_message_reference_dict()
|
||||||
|
except AttributeError:
|
||||||
|
raise InvalidArgument('reference parameter must be Message or MessageReference') from None
|
||||||
|
|
||||||
if file is not None and files is not None:
|
if file is not None and files is not None:
|
||||||
raise InvalidArgument('cannot pass both file and files parameter to send()')
|
raise InvalidArgument('cannot pass both file and files parameter to send()')
|
||||||
@ -899,8 +922,8 @@ class Messageable(metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
data = await state.http.send_files(channel.id, files=[file], allowed_mentions=allowed_mentions,
|
data = await state.http.send_files(channel.id, files=[file], allowed_mentions=allowed_mentions,
|
||||||
content=content, tts=tts, embed=embed, nonce=nonce,
|
content=content, tts=tts, embed=embed, nonce=nonce,
|
||||||
message_reference=message_reference)
|
message_reference=reference)
|
||||||
finally:
|
finally:
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
@ -913,14 +936,14 @@ class Messageable(metaclass=abc.ABCMeta):
|
|||||||
try:
|
try:
|
||||||
data = await state.http.send_files(channel.id, files=files, content=content, tts=tts,
|
data = await state.http.send_files(channel.id, files=files, content=content, tts=tts,
|
||||||
embed=embed, nonce=nonce, allowed_mentions=allowed_mentions,
|
embed=embed, nonce=nonce, allowed_mentions=allowed_mentions,
|
||||||
message_reference=message_reference)
|
message_reference=reference)
|
||||||
finally:
|
finally:
|
||||||
for f in files:
|
for f in files:
|
||||||
f.close()
|
f.close()
|
||||||
else:
|
else:
|
||||||
data = await state.http.send_message(channel.id, content, tts=tts, embed=embed,
|
data = await state.http.send_message(channel.id, content, tts=tts, embed=embed,
|
||||||
nonce=nonce, allowed_mentions=allowed_mentions,
|
nonce=nonce, allowed_mentions=allowed_mentions,
|
||||||
message_reference=message_reference)
|
message_reference=reference)
|
||||||
|
|
||||||
ret = state.create_message(channel=channel, data=data)
|
ret = state.create_message(channel=channel, data=data)
|
||||||
if delete_after is not None:
|
if delete_after is not None:
|
||||||
|
@ -685,6 +685,7 @@ class CustomActivity(BaseActivity):
|
|||||||
__slots__ = ('name', 'emoji', 'state')
|
__slots__ = ('name', 'emoji', 'state')
|
||||||
|
|
||||||
def __init__(self, name, *, emoji=None, **extra):
|
def __init__(self, name, *, emoji=None, **extra):
|
||||||
|
super().__init__(**extra)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.state = extra.pop('state', None)
|
self.state = extra.pop('state', None)
|
||||||
if self.name == 'Custom Status':
|
if self.name == 'Custom Status':
|
||||||
|
@ -35,14 +35,12 @@ import re
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .user import User, Profile
|
from .user import User, Profile
|
||||||
from .asset import Asset
|
|
||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
from .template import Template
|
from .template import Template
|
||||||
from .widget import Widget
|
from .widget import Widget
|
||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
from .channel import _channel_factory
|
from .channel import _channel_factory
|
||||||
from .enums import ChannelType
|
from .enums import ChannelType
|
||||||
from .member import Member
|
|
||||||
from .mentions import AllowedMentions
|
from .mentions import AllowedMentions
|
||||||
from .errors import *
|
from .errors import *
|
||||||
from .enums import Status, VoiceRegion
|
from .enums import Status, VoiceRegion
|
||||||
@ -347,6 +345,18 @@ class Client:
|
|||||||
ws = self.ws
|
ws = self.ws
|
||||||
return float('nan') if not ws else ws.latency
|
return float('nan') if not ws else ws.latency
|
||||||
|
|
||||||
|
def is_ws_ratelimited(self):
|
||||||
|
""":class:`bool`: Whether the websocket is currently rate limited.
|
||||||
|
|
||||||
|
This can be useful to know when deciding whether you should query members
|
||||||
|
using HTTP or via the gateway.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
if self.ws:
|
||||||
|
return self.ws.is_ratelimited()
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user(self):
|
def user(self):
|
||||||
"""Optional[:class:`.ClientUser`]: Represents the connected client. ``None`` if not logged in."""
|
"""Optional[:class:`.ClientUser`]: Represents the connected client. ``None`` if not logged in."""
|
||||||
|
@ -446,6 +446,11 @@ class ExpireBehaviour(Enum):
|
|||||||
|
|
||||||
ExpireBehavior = ExpireBehaviour
|
ExpireBehavior = ExpireBehaviour
|
||||||
|
|
||||||
|
class StickerType(Enum):
|
||||||
|
png = 1
|
||||||
|
apng = 2
|
||||||
|
lottie = 3
|
||||||
|
|
||||||
def try_enum(cls, val):
|
def try_enum(cls, val):
|
||||||
"""A function that tries to turn the value into enum ``cls``.
|
"""A function that tries to turn the value into enum ``cls``.
|
||||||
|
|
||||||
@ -456,8 +461,3 @@ def try_enum(cls, val):
|
|||||||
return cls._enum_value_map_[val]
|
return cls._enum_value_map_[val]
|
||||||
except (KeyError, TypeError, AttributeError):
|
except (KeyError, TypeError, AttributeError):
|
||||||
return val
|
return val
|
||||||
|
|
||||||
class StickerType(Enum):
|
|
||||||
png = 1
|
|
||||||
apng = 2
|
|
||||||
lottie = 3
|
|
||||||
|
@ -30,12 +30,11 @@ import inspect
|
|||||||
import importlib.util
|
import importlib.util
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import re
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from .core import GroupMixin, Command
|
from .core import GroupMixin
|
||||||
from .view import StringView
|
from .view import StringView
|
||||||
from .context import Context
|
from .context import Context
|
||||||
from . import errors
|
from . import errors
|
||||||
|
@ -70,6 +70,11 @@ class CogMeta(type):
|
|||||||
-----------
|
-----------
|
||||||
name: :class:`str`
|
name: :class:`str`
|
||||||
The cog name. By default, it is the name of the class with no modification.
|
The cog name. By default, it is the name of the class with no modification.
|
||||||
|
description: :class:`str`
|
||||||
|
The cog description. By default, it is the cleaned docstring of the class.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
command_attrs: :class:`dict`
|
command_attrs: :class:`dict`
|
||||||
A list of attributes to apply to every command inside this cog. The dictionary
|
A list of attributes to apply to every command inside this cog. The dictionary
|
||||||
is passed into the :class:`Command` options at ``__init__``.
|
is passed into the :class:`Command` options at ``__init__``.
|
||||||
@ -93,6 +98,11 @@ class CogMeta(type):
|
|||||||
attrs['__cog_name__'] = kwargs.pop('name', name)
|
attrs['__cog_name__'] = kwargs.pop('name', name)
|
||||||
attrs['__cog_settings__'] = command_attrs = kwargs.pop('command_attrs', {})
|
attrs['__cog_settings__'] = command_attrs = kwargs.pop('command_attrs', {})
|
||||||
|
|
||||||
|
description = kwargs.pop('description', None)
|
||||||
|
if description is None:
|
||||||
|
description = inspect.cleandoc(attrs.get('__doc__', ''))
|
||||||
|
attrs['__cog_description__'] = description
|
||||||
|
|
||||||
commands = {}
|
commands = {}
|
||||||
listeners = {}
|
listeners = {}
|
||||||
no_bot_cog = 'Commands or listeners must not start with cog_ or bot_ (in method {0.__name__}.{1})'
|
no_bot_cog = 'Commands or listeners must not start with cog_ or bot_ (in method {0.__name__}.{1})'
|
||||||
@ -209,11 +219,11 @@ class Cog(metaclass=CogMeta):
|
|||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
""":class:`str`: Returns the cog's description, typically the cleaned docstring."""
|
""":class:`str`: Returns the cog's description, typically the cleaned docstring."""
|
||||||
try:
|
return self.__cog_description__
|
||||||
return self.__cog_cleaned_doc__
|
|
||||||
except AttributeError:
|
@description.setter
|
||||||
self.__cog_cleaned_doc__ = cleaned = inspect.getdoc(self)
|
def description(self, description):
|
||||||
return cleaned
|
self.__cog_description__ = description
|
||||||
|
|
||||||
def walk_commands(self):
|
def walk_commands(self):
|
||||||
"""An iterator that recursively walks through this cog's commands and subcommands.
|
"""An iterator that recursively walks through this cog's commands and subcommands.
|
||||||
@ -427,4 +437,7 @@ class Cog(metaclass=CogMeta):
|
|||||||
if cls.bot_check_once is not Cog.bot_check_once:
|
if cls.bot_check_once is not Cog.bot_check_once:
|
||||||
bot.remove_check(self.bot_check_once, call_once=True)
|
bot.remove_check(self.bot_check_once, call_once=True)
|
||||||
finally:
|
finally:
|
||||||
self.cog_unload()
|
try:
|
||||||
|
self.cog_unload()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
@ -333,27 +333,7 @@ class Context(discord.abc.Messageable):
|
|||||||
except CommandError as e:
|
except CommandError as e:
|
||||||
await cmd.on_help_command_error(self, e)
|
await cmd.on_help_command_error(self, e)
|
||||||
|
|
||||||
async def reply(self, content=None, **kwargs):
|
async def reply(self, content=None, **kwargs):
|
||||||
"""|coro|
|
return await self.message.reply(content, **kwargs)
|
||||||
|
|
||||||
A shortcut method to :meth:`~discord.abc.Messageable.send` to reply to the
|
reply.__doc__ = discord.Message.reply.__doc__
|
||||||
:class:`~discord.Message` that invoked the command.
|
|
||||||
|
|
||||||
.. versionadded:: 1.5.1.5
|
|
||||||
|
|
||||||
Raises
|
|
||||||
--------
|
|
||||||
~discord.HTTPException
|
|
||||||
Sending the message failed.
|
|
||||||
~discord.Forbidden
|
|
||||||
You do not have the proper permissions to send the message.
|
|
||||||
~discord.InvalidArgument
|
|
||||||
The ``files`` list is not of the appropriate size or
|
|
||||||
you specified both ``file`` and ``files``.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
---------
|
|
||||||
:class:`~discord.Message`
|
|
||||||
The message that was sent.
|
|
||||||
"""
|
|
||||||
return await self.message.reply(content, **kwargs)
|
|
||||||
|
@ -30,14 +30,13 @@ from collections import namedtuple
|
|||||||
from . import utils
|
from . import utils
|
||||||
from .role import Role
|
from .role import Role
|
||||||
from .member import Member, VoiceState
|
from .member import Member, VoiceState
|
||||||
from .activity import create_activity
|
|
||||||
from .emoji import Emoji
|
from .emoji import Emoji
|
||||||
from .errors import InvalidData
|
from .errors import InvalidData
|
||||||
from .permissions import PermissionOverwrite
|
from .permissions import PermissionOverwrite
|
||||||
from .colour import Colour
|
from .colour import Colour
|
||||||
from .errors import InvalidArgument, ClientException
|
from .errors import InvalidArgument, ClientException
|
||||||
from .channel import *
|
from .channel import *
|
||||||
from .enums import VoiceRegion, Status, ChannelType, try_enum, VerificationLevel, ContentFilter, NotificationLevel
|
from .enums import VoiceRegion, ChannelType, try_enum, VerificationLevel, ContentFilter, NotificationLevel
|
||||||
from .mixins import Hashable
|
from .mixins import Hashable
|
||||||
from .user import User
|
from .user import User
|
||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
@ -474,7 +473,7 @@ class Guild(Hashable):
|
|||||||
@property
|
@property
|
||||||
def rules_channel(self):
|
def rules_channel(self):
|
||||||
"""Optional[:class:`TextChannel`]: Return's the guild's channel used for the rules.
|
"""Optional[:class:`TextChannel`]: Return's the guild's channel used for the rules.
|
||||||
Must be a discoverable guild.
|
The guild must be a Community guild.
|
||||||
|
|
||||||
If no channel is set, then this returns ``None``.
|
If no channel is set, then this returns ``None``.
|
||||||
|
|
||||||
@ -486,8 +485,8 @@ class Guild(Hashable):
|
|||||||
@property
|
@property
|
||||||
def public_updates_channel(self):
|
def public_updates_channel(self):
|
||||||
"""Optional[:class:`TextChannel`]: Return's the guild's channel where admins and
|
"""Optional[:class:`TextChannel`]: Return's the guild's channel where admins and
|
||||||
moderators of the guilds receive notices from Discord. This is only available to
|
moderators of the guilds receive notices from Discord. The guild must be a
|
||||||
guilds that contain ``PUBLIC`` in :attr:`Guild.features`.
|
Community guild.
|
||||||
|
|
||||||
If no channel is set, then this returns ``None``.
|
If no channel is set, then this returns ``None``.
|
||||||
|
|
||||||
@ -581,6 +580,30 @@ class Guild(Hashable):
|
|||||||
""":class:`Role`: Gets the @everyone role that all members have by default."""
|
""":class:`Role`: Gets the @everyone role that all members have by default."""
|
||||||
return self.get_role(self.id)
|
return self.get_role(self.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def premium_subscriber_role(self):
|
||||||
|
"""Optional[:class:`Role`]: Gets the premium subscriber role, AKA "boost" role, in this guild.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
for role in self._roles.values():
|
||||||
|
if role.is_premium_subscriber():
|
||||||
|
return role
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def self_role(self):
|
||||||
|
"""Optional[:class:`Role`]: Gets the role associated with this client's user, if any.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
self_id = self._state.self_id
|
||||||
|
for role in self._roles.values():
|
||||||
|
tags = role.tags
|
||||||
|
if tags and tags.bot_id == self_id:
|
||||||
|
return role
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def owner(self):
|
def owner(self):
|
||||||
"""Optional[:class:`Member`]: The member that owns the guild."""
|
"""Optional[:class:`Member`]: The member that owns the guild."""
|
||||||
@ -1209,10 +1232,10 @@ class Guild(Hashable):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if rules_channel is None:
|
if public_updates_channel is None:
|
||||||
fields['public_updates_channel_id'] = rules_channel
|
fields['public_updates_channel_id'] = public_updates_channel
|
||||||
else:
|
else:
|
||||||
fields['public_updates_channel_id'] = rules_channel.id
|
fields['public_updates_channel_id'] = public_updates_channel.id
|
||||||
await http.edit_guild(self.id, reason=reason, **fields)
|
await http.edit_guild(self.id, reason=reason, **fields)
|
||||||
|
|
||||||
async def fetch_channels(self):
|
async def fetch_channels(self):
|
||||||
|
@ -361,7 +361,7 @@ class HTTPClient:
|
|||||||
if allowed_mentions:
|
if allowed_mentions:
|
||||||
payload['allowed_mentions'] = allowed_mentions
|
payload['allowed_mentions'] = allowed_mentions
|
||||||
|
|
||||||
if message_reference:
|
if message_reference:
|
||||||
payload['message_reference'] = message_reference
|
payload['message_reference'] = message_reference
|
||||||
|
|
||||||
return self.request(r, json=payload)
|
return self.request(r, json=payload)
|
||||||
@ -382,7 +382,7 @@ class HTTPClient:
|
|||||||
payload['nonce'] = nonce
|
payload['nonce'] = nonce
|
||||||
if allowed_mentions:
|
if allowed_mentions:
|
||||||
payload['allowed_mentions'] = allowed_mentions
|
payload['allowed_mentions'] = allowed_mentions
|
||||||
if message_reference:
|
if message_reference:
|
||||||
payload['message_reference'] = message_reference
|
payload['message_reference'] = message_reference
|
||||||
|
|
||||||
form.add_field('payload_json', utils.to_json(payload))
|
form.add_field('payload_json', utils.to_json(payload))
|
||||||
|
@ -28,7 +28,7 @@ import asyncio
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from .errors import NoMoreItems
|
from .errors import NoMoreItems
|
||||||
from .utils import DISCORD_EPOCH, time_snowflake, maybe_coroutine
|
from .utils import time_snowflake, maybe_coroutine
|
||||||
from .object import Object
|
from .object import Object
|
||||||
from .audit_logs import AuditLogEntry
|
from .audit_logs import AuditLogEntry
|
||||||
|
|
||||||
@ -62,6 +62,11 @@ class _AsyncIterator:
|
|||||||
if ret:
|
if ret:
|
||||||
return elem
|
return elem
|
||||||
|
|
||||||
|
def chunk(self, max_size):
|
||||||
|
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):
|
||||||
return _MappedAsyncIterator(self, func)
|
return _MappedAsyncIterator(self, func)
|
||||||
|
|
||||||
@ -92,6 +97,26 @@ class _AsyncIterator:
|
|||||||
def _identity(x):
|
def _identity(x):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
class _ChunkedAsyncIterator(_AsyncIterator):
|
||||||
|
def __init__(self, iterator, max_size):
|
||||||
|
self.iterator = iterator
|
||||||
|
self.max_size = max_size
|
||||||
|
|
||||||
|
async def next(self):
|
||||||
|
ret = []
|
||||||
|
n = 0
|
||||||
|
while n < self.max_size:
|
||||||
|
try:
|
||||||
|
item = await self.iterator.next()
|
||||||
|
except NoMoreItems:
|
||||||
|
if ret:
|
||||||
|
return ret
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
ret.append(item)
|
||||||
|
n += 1
|
||||||
|
return ret
|
||||||
|
|
||||||
class _MappedAsyncIterator(_AsyncIterator):
|
class _MappedAsyncIterator(_AsyncIterator):
|
||||||
def __init__(self, iterator, func):
|
def __init__(self, iterator, func):
|
||||||
self.iterator = iterator
|
self.iterator = iterator
|
||||||
|
@ -34,7 +34,7 @@ from . import utils
|
|||||||
from .user import BaseUser, User
|
from .user import BaseUser, User
|
||||||
from .activity import create_activity
|
from .activity import create_activity
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
from .enums import Status, try_enum, UserFlags, HypeSquadHouse
|
from .enums import Status, try_enum
|
||||||
from .colour import Colour
|
from .colour import Colour
|
||||||
from .object import Object
|
from .object import Object
|
||||||
|
|
||||||
|
@ -59,11 +59,11 @@ class AllowedMentions:
|
|||||||
roles are not mentioned at all. If a list of :class:`abc.Snowflake`
|
roles are not mentioned at all. If a list of :class:`abc.Snowflake`
|
||||||
is given then only the roles provided will be mentioned, provided those
|
is given then only the roles provided will be mentioned, provided those
|
||||||
roles are in the message content.
|
roles are in the message content.
|
||||||
replied_user: :class:`bool`
|
replied_user: :class:`bool`
|
||||||
Whether to mention the author of the message being replied to. Defaults
|
Whether to mention the author of the message being replied to. Defaults
|
||||||
to ``True``.
|
to ``True``.
|
||||||
|
|
||||||
.. versionadded:: 1.5.1.5
|
.. versionadded:: 1.6
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('everyone', 'users', 'roles', 'replied_user')
|
__slots__ = ('everyone', 'users', 'roles', 'replied_user')
|
||||||
@ -107,7 +107,7 @@ class AllowedMentions:
|
|||||||
elif self.roles != False:
|
elif self.roles != False:
|
||||||
data['roles'] = [x.id for x in self.roles]
|
data['roles'] = [x.id for x in self.roles]
|
||||||
|
|
||||||
if self.replied_user == True:
|
if self.replied_user:
|
||||||
data['replied_user'] = True
|
data['replied_user'] = True
|
||||||
|
|
||||||
data['parse'] = parse
|
data['parse'] = parse
|
||||||
|
@ -45,6 +45,12 @@ from .guild import Guild
|
|||||||
from .mixins import Hashable
|
from .mixins import Hashable
|
||||||
from .sticker import Sticker
|
from .sticker import Sticker
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'Attachment',
|
||||||
|
'Message',
|
||||||
|
'MessageReference',
|
||||||
|
'DeletedReferencedMessage',
|
||||||
|
)
|
||||||
|
|
||||||
class Attachment:
|
class Attachment:
|
||||||
"""Represents an attachment from Discord.
|
"""Represents an attachment from Discord.
|
||||||
@ -213,11 +219,45 @@ class Attachment:
|
|||||||
data = await self.read(use_cached=use_cached)
|
data = await self.read(use_cached=use_cached)
|
||||||
return File(io.BytesIO(data), filename=self.filename, spoiler=spoiler)
|
return File(io.BytesIO(data), filename=self.filename, spoiler=spoiler)
|
||||||
|
|
||||||
|
class DeletedReferencedMessage:
|
||||||
|
"""A special sentinel type that denotes whether the
|
||||||
|
resolved message referenced message had since been deleted.
|
||||||
|
|
||||||
|
The purpose of this class is to separate referenced messages that could not be
|
||||||
|
fetched and those that were previously fetched but have since been deleted.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('_parent')
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
""":class:`int`: The message ID of the deleted referenced message."""
|
||||||
|
return self._parent.message_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def channel_id(self):
|
||||||
|
""":class:`int`: The channel ID of the deleted referenced message."""
|
||||||
|
return self._parent.channel_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def guild_id(self):
|
||||||
|
"""Optional[:class:`int`]: The guild ID of the deleted referenced message."""
|
||||||
|
return self._parent.guild_id
|
||||||
|
|
||||||
|
|
||||||
class MessageReference:
|
class MessageReference:
|
||||||
"""Represents a reference to a :class:`Message`.
|
"""Represents a reference to a :class:`~discord.Message`.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
|
.. versionchanged:: 1.6
|
||||||
|
This class can now be constructed by users.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
message_id: Optional[:class:`int`]
|
message_id: Optional[:class:`int`]
|
||||||
@ -226,15 +266,56 @@ class MessageReference:
|
|||||||
The channel id of the message referenced.
|
The channel id of the message referenced.
|
||||||
guild_id: Optional[:class:`int`]
|
guild_id: Optional[:class:`int`]
|
||||||
The guild id of the message referenced.
|
The guild id of the message referenced.
|
||||||
|
resolved: Optional[Union[:class:`Message`, :class:`DeletedReferencedMessage`]]
|
||||||
|
The message that this reference resolved to. If this is ``None``
|
||||||
|
then the original message was not fetched either due to the discord API
|
||||||
|
not attempting to resolve it or it not being available at the time of creation.
|
||||||
|
If the message was resolved at a prior point but has since been deleted then
|
||||||
|
this will be of type :class:`DeletedReferencedMessage`.
|
||||||
|
|
||||||
|
Currently, this is mainly the replied to message when a user replies to a message.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('message_id', 'channel_id', 'guild_id', '_state')
|
__slots__ = ('message_id', 'channel_id', 'guild_id', 'resolved', '_state')
|
||||||
|
|
||||||
def __init__(self, state, **kwargs):
|
def __init__(self, *, message_id, channel_id, guild_id=None):
|
||||||
self.message_id = utils._get_as_snowflake(kwargs, 'message_id')
|
self._state = None
|
||||||
self.channel_id = int(kwargs.pop('channel_id'))
|
self.resolved = None
|
||||||
self.guild_id = utils._get_as_snowflake(kwargs, 'guild_id')
|
self.message_id = message_id
|
||||||
|
self.channel_id = channel_id
|
||||||
|
self.guild_id = guild_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def with_state(cls, state, data):
|
||||||
|
self = cls.__new__(cls)
|
||||||
|
self.message_id = utils._get_as_snowflake(data, 'message_id')
|
||||||
|
self.channel_id = int(data.pop('channel_id'))
|
||||||
|
self.guild_id = utils._get_as_snowflake(data, 'guild_id')
|
||||||
self._state = state
|
self._state = state
|
||||||
|
self.resolved = None
|
||||||
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_message(cls, message):
|
||||||
|
"""Creates a :class:`MessageReference` from an existing :class:`~discord.Message`.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
message: :class:`~discord.Message`
|
||||||
|
The message to be converted into a reference.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`MessageReference`
|
||||||
|
A reference to the message.
|
||||||
|
"""
|
||||||
|
self = cls(message_id=message.id, channel_id=message.channel.id, guild_id=getattr(message.guild, 'id', None))
|
||||||
|
self._state = message._state
|
||||||
|
return self
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_message(cls, message):
|
def from_message(cls, message):
|
||||||
@ -256,35 +337,21 @@ class MessageReference:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def cached_message(self):
|
def cached_message(self):
|
||||||
"""Optional[:class:`Message`]: The cached message, if found in the internal message cache."""
|
"""Optional[:class:`~discord.Message`]: The cached message, if found in the internal message cache."""
|
||||||
return self._state._get_message(self.message_id)
|
return self._state._get_message(self.message_id)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<MessageReference message_id={0.message_id!r} channel_id={0.channel_id!r} guild_id={0.guild_id!r}>'.format(self)
|
return '<MessageReference message_id={0.message_id!r} channel_id={0.channel_id!r} guild_id={0.guild_id!r}>'.format(self)
|
||||||
|
|
||||||
def to_dict(self, specify_channel=False):
|
def to_dict(self):
|
||||||
"""Converts the message reference to a dict, for transmission via the gateway.
|
result = {'message_id': self.message_id} if self.message_id is not None else {}
|
||||||
|
result['channel_id'] = self.channel_id
|
||||||
.. versionadded:: 1.5.1.5
|
if self.guild_id is not None:
|
||||||
|
result['guild_id'] = self.guild_id
|
||||||
Parameters
|
|
||||||
-------
|
|
||||||
specify_channel: Optional[:class:`bool`]
|
|
||||||
Whether to include the channel ID in the returned object.
|
|
||||||
Defaults to False.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
:class:`dict`
|
|
||||||
The reference as a dict.
|
|
||||||
"""
|
|
||||||
result = {'message_id': self.message_id} if self.message_id is not None else {}
|
|
||||||
if specify_channel:
|
|
||||||
result['channel_id'] = self.channel_id
|
|
||||||
if self.guild_id is not None:
|
|
||||||
result['guild_id'] = self.guild_id
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
to_message_reference_dict = to_dict
|
||||||
|
|
||||||
def flatten_handlers(cls):
|
def flatten_handlers(cls):
|
||||||
prefix = len('_handle_')
|
prefix = len('_handle_')
|
||||||
handlers = [
|
handlers = [
|
||||||
@ -332,10 +399,10 @@ class Message(Hashable):
|
|||||||
call: Optional[:class:`CallMessage`]
|
call: Optional[:class:`CallMessage`]
|
||||||
The call that the message refers to. This is only applicable to messages of type
|
The call that the message refers to. This is only applicable to messages of type
|
||||||
:attr:`MessageType.call`.
|
:attr:`MessageType.call`.
|
||||||
reference: Optional[:class:`MessageReference`]
|
reference: Optional[:class:`~discord.MessageReference`]
|
||||||
The message that this message references. This is only applicable to messages of
|
The message that this message references. This is only applicable to messages of
|
||||||
type :attr:`MessageType.pins_add`, crossposted messages created by a
|
type :attr:`MessageType.pins_add`, crossposted messages created by a
|
||||||
followed channel integration or message replies.
|
followed channel integration, or message replies.
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
@ -431,8 +498,27 @@ class Message(Hashable):
|
|||||||
self.nonce = data.get('nonce')
|
self.nonce = data.get('nonce')
|
||||||
self.stickers = [Sticker(data=data, state=state) for data in data.get('stickers', [])]
|
self.stickers = [Sticker(data=data, state=state) for data in data.get('stickers', [])]
|
||||||
|
|
||||||
ref = data.get('message_reference')
|
try:
|
||||||
self.reference = MessageReference(state, **ref) if ref is not None else None
|
ref = data['message_reference']
|
||||||
|
except KeyError:
|
||||||
|
self.reference = None
|
||||||
|
else:
|
||||||
|
self.reference = ref = MessageReference.with_state(state, ref)
|
||||||
|
try:
|
||||||
|
resolved = data['referenced_message']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if resolved is None:
|
||||||
|
ref.resolved = DeletedReferencedMessage(ref)
|
||||||
|
else:
|
||||||
|
# Right now the channel IDs match but maybe in the future they won't.
|
||||||
|
if ref.channel_id == channel.id:
|
||||||
|
chan = channel
|
||||||
|
else:
|
||||||
|
chan, _ = state._get_guild_channel(resolved)
|
||||||
|
|
||||||
|
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', 'call', 'flags'):
|
||||||
try:
|
try:
|
||||||
@ -891,9 +977,22 @@ class Message(Hashable):
|
|||||||
before deleting the message we just edited. If the deletion fails,
|
before deleting the message we just edited. If the deletion fails,
|
||||||
then it is silently ignored.
|
then it is silently ignored.
|
||||||
allowed_mentions: Optional[:class:`~discord.AllowedMentions`]
|
allowed_mentions: Optional[:class:`~discord.AllowedMentions`]
|
||||||
Controls the mentions being processed in this message.
|
Controls the mentions being processed in this message. If this is
|
||||||
|
passed, then the object is merged with :attr:`~discord.Client.allowed_mentions`.
|
||||||
|
The merging behaviour only overrides attributes that have been explicitly passed
|
||||||
|
to the object, otherwise it uses the attributes set in :attr:`~discord.Client.allowed_mentions`.
|
||||||
|
If no object is passed at all then the defaults given by :attr:`~discord.Client.allowed_mentions`
|
||||||
|
are used instead.
|
||||||
|
|
||||||
.. versionadded:: 1.4
|
.. versionadded:: 1.4
|
||||||
|
.. versionchanged:: 1.6
|
||||||
|
:attr:`~discord.Client.allowed_mentions` serves as defaults unconditionally.
|
||||||
|
|
||||||
|
mention_author: Optional[:class:`bool`]
|
||||||
|
Overrides the :attr:`~discord.AllowedMentions.replied_user` attribute
|
||||||
|
of ``allowed_mentions``.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
@ -931,17 +1030,24 @@ class Message(Hashable):
|
|||||||
|
|
||||||
delete_after = fields.pop('delete_after', None)
|
delete_after = fields.pop('delete_after', None)
|
||||||
|
|
||||||
try:
|
mention_author = fields.pop('mention_author', None)
|
||||||
allowed_mentions = fields.pop('allowed_mentions')
|
allowed_mentions = fields.pop('allowed_mentions', None)
|
||||||
except KeyError:
|
if allowed_mentions is not None:
|
||||||
pass
|
if self._state.allowed_mentions is not None:
|
||||||
else:
|
allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions)
|
||||||
if allowed_mentions is not None:
|
allowed_mentions = allowed_mentions.to_dict()
|
||||||
if self._state.allowed_mentions is not None:
|
if mention_author is not None:
|
||||||
allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions).to_dict()
|
allowed_mentions['replied_user'] = mention_author
|
||||||
else:
|
fields['allowed_mentions'] = allowed_mentions
|
||||||
allowed_mentions = allowed_mentions.to_dict()
|
elif mention_author is not None:
|
||||||
fields['allowed_mentions'] = allowed_mentions
|
if self._state.allowed_mentions is not None:
|
||||||
|
allowed_mentions = self._state.allowed_mentions.to_dict()
|
||||||
|
allowed_mentions['replied_user'] = mention_author
|
||||||
|
else:
|
||||||
|
allowed_mentions = {'replied_user': mention_author}
|
||||||
|
fields['allowed_mentions'] = allowed_mentions
|
||||||
|
elif self._state.allowed_mentions is not None:
|
||||||
|
fields['allowed_mentions'] = self._state.allowed_mentions.to_dict()
|
||||||
|
|
||||||
if fields:
|
if fields:
|
||||||
data = await self._state.http.edit_message(self.channel.id, self.id, **fields)
|
data = await self._state.http.edit_message(self.channel.id, self.id, **fields)
|
||||||
@ -1180,26 +1286,50 @@ class Message(Hashable):
|
|||||||
|
|
||||||
async def reply(self, content=None, **kwargs):
|
async def reply(self, content=None, **kwargs):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
A shortcut method to :meth:`abc.Messageable.send` to reply to the
|
|
||||||
:class:`Message`.
|
|
||||||
|
|
||||||
.. versionadded:: 1.5.1.5
|
|
||||||
|
|
||||||
Raises
|
|
||||||
--------
|
|
||||||
~discord.HTTPException
|
|
||||||
Sending the message failed.
|
|
||||||
~discord.Forbidden
|
|
||||||
You do not have the proper permissions to send the message.
|
|
||||||
~discord.InvalidArgument
|
|
||||||
The ``files`` list is not of the appropriate size or
|
|
||||||
you specified both ``file`` and ``files``.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
---------
|
|
||||||
:class:`Message`
|
|
||||||
The message that was sent.
|
|
||||||
"""
|
|
||||||
|
|
||||||
reference = MessageReference.from_message(self)
|
A shortcut method to :meth:`abc.Messageable.send` to reply to the
|
||||||
return await self.channel.send(content, message_reference=reference, **kwargs)
|
:class:`Message`.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Raises
|
||||||
|
--------
|
||||||
|
~discord.HTTPException
|
||||||
|
Sending the message failed.
|
||||||
|
~discord.Forbidden
|
||||||
|
You do not have the proper permissions to send the message.
|
||||||
|
~discord.InvalidArgument
|
||||||
|
The ``files`` list is not of the appropriate size or
|
||||||
|
you specified both ``file`` and ``files``.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
:class:`Message`
|
||||||
|
The message that was sent.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await self.channel.send(content, reference=self, **kwargs)
|
||||||
|
|
||||||
|
def to_reference(self):
|
||||||
|
"""Creates a :class:`~discord.MessageReference` from the current message.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
:class:`~discord.MessageReference`
|
||||||
|
The reference to this message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return MessageReference.from_message(self)
|
||||||
|
|
||||||
|
def to_message_reference_dict(self):
|
||||||
|
data = {
|
||||||
|
'message_id': self.id,
|
||||||
|
'channel_id': self.channel.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.guild is not None:
|
||||||
|
data['guild_id'] = self.guild.id
|
||||||
|
|
||||||
|
return data
|
||||||
|
@ -28,7 +28,53 @@ from .permissions import Permissions
|
|||||||
from .errors import InvalidArgument
|
from .errors import InvalidArgument
|
||||||
from .colour import Colour
|
from .colour import Colour
|
||||||
from .mixins import Hashable
|
from .mixins import Hashable
|
||||||
from .utils import snowflake_time
|
from .utils import snowflake_time, _get_as_snowflake
|
||||||
|
|
||||||
|
class RoleTags:
|
||||||
|
"""Represents tags on a role.
|
||||||
|
|
||||||
|
A role tag is a piece of extra information attached to a managed role
|
||||||
|
that gives it context for the reason the role is managed.
|
||||||
|
|
||||||
|
While this can be accessed, a useful interface is also provided in the
|
||||||
|
:class:`Role` and :class:`Guild` classes as well.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
------------
|
||||||
|
bot_id: Optional[:class:`int`]
|
||||||
|
The bot's user ID that manages this role.
|
||||||
|
integration_id: Optional[:class:`int`]
|
||||||
|
The integration ID that manages the role.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('bot_id', 'integration_id', '_premium_subscriber',)
|
||||||
|
|
||||||
|
def __init__(self, data):
|
||||||
|
self.bot_id = _get_as_snowflake(data, 'bot_id')
|
||||||
|
self.integration_id = _get_as_snowflake(data, 'integration_id')
|
||||||
|
# NOTE: The API returns "null" for this if it's valid, which corresponds to None.
|
||||||
|
# This is different from other fields where "null" means "not there".
|
||||||
|
# So in this case, a value of None is the same as True.
|
||||||
|
# Which means we would need a different sentinel. For this purpose I used ellipsis.
|
||||||
|
self._premium_subscriber = data.get('premium_subscriber', ...)
|
||||||
|
|
||||||
|
def is_bot_managed(self):
|
||||||
|
""":class:`bool`: Whether the role is associated with a bot."""
|
||||||
|
return self.bot_id is not None
|
||||||
|
|
||||||
|
def is_premium_subscriber(self):
|
||||||
|
""":class:`bool`: Whether the role is the premium subscriber, AKA "boost", role for the guild."""
|
||||||
|
return self._premium_subscriber is None
|
||||||
|
|
||||||
|
def is_integration(self):
|
||||||
|
""":class:`bool`: Whether the role is managed by an integration."""
|
||||||
|
return self.integration_id is not None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<RoleTags bot_id={0.bot_id} integration_id={0.integration_id} ' \
|
||||||
|
'premium_subscriber={1}>'.format(self, self.is_premium_subscriber())
|
||||||
|
|
||||||
class Role(Hashable):
|
class Role(Hashable):
|
||||||
"""Represents a Discord role in a :class:`Guild`.
|
"""Represents a Discord role in a :class:`Guild`.
|
||||||
@ -85,10 +131,12 @@ class Role(Hashable):
|
|||||||
integrations such as Twitch.
|
integrations such as Twitch.
|
||||||
mentionable: :class:`bool`
|
mentionable: :class:`bool`
|
||||||
Indicates if the role can be mentioned by users.
|
Indicates if the role can be mentioned by users.
|
||||||
|
tags: Optional[:class:`RoleTags`]
|
||||||
|
The role tags associated with this role.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('id', 'name', '_permissions', '_colour', 'position',
|
__slots__ = ('id', 'name', '_permissions', '_colour', 'position',
|
||||||
'managed', 'mentionable', 'hoist', 'guild', '_state')
|
'managed', 'mentionable', 'hoist', 'guild', 'tags', '_state')
|
||||||
|
|
||||||
def __init__(self, *, guild, state, data):
|
def __init__(self, *, guild, state, data):
|
||||||
self.guild = guild
|
self.guild = guild
|
||||||
@ -150,10 +198,36 @@ class Role(Hashable):
|
|||||||
self.managed = data.get('managed', False)
|
self.managed = data.get('managed', False)
|
||||||
self.mentionable = data.get('mentionable', False)
|
self.mentionable = data.get('mentionable', False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.tags = RoleTags(data['tags'])
|
||||||
|
except KeyError:
|
||||||
|
self.tags = None
|
||||||
|
|
||||||
def is_default(self):
|
def is_default(self):
|
||||||
""":class:`bool`: Checks if the role is the default role."""
|
""":class:`bool`: Checks if the role is the default role."""
|
||||||
return self.guild.id == self.id
|
return self.guild.id == self.id
|
||||||
|
|
||||||
|
def is_bot_managed(self):
|
||||||
|
""":class:`bool`: Whether the role is associated with a bot.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
return self.tags is not None and self.tags.is_bot_managed()
|
||||||
|
|
||||||
|
def is_premium_subscriber(self):
|
||||||
|
""":class:`bool`: Whether the role is the premium subscriber, AKA "boost", role for the guild.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
return self.tags is not None and self.tags.is_premium_subscriber()
|
||||||
|
|
||||||
|
def is_integration(self):
|
||||||
|
""":class:`bool`: Whether the role is managed by an integration.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
return self.tags is not None and self.tags.is_integration()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def permissions(self):
|
def permissions(self):
|
||||||
""":class:`Permissions`: Returns the role's permissions."""
|
""":class:`Permissions`: Returns the role's permissions."""
|
||||||
@ -221,7 +295,7 @@ class Role(Hashable):
|
|||||||
use this.
|
use this.
|
||||||
|
|
||||||
All fields are optional.
|
All fields are optional.
|
||||||
|
|
||||||
.. versionchanged:: 1.4
|
.. versionchanged:: 1.4
|
||||||
Can now pass ``int`` to ``colour`` keyword-only parameter.
|
Can now pass ``int`` to ``colour`` keyword-only parameter.
|
||||||
|
|
||||||
@ -263,7 +337,7 @@ class Role(Hashable):
|
|||||||
colour = fields['colour']
|
colour = fields['colour']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
colour = fields.get('color', self.colour)
|
colour = fields.get('color', self.colour)
|
||||||
|
|
||||||
if isinstance(colour, int):
|
if isinstance(colour, int):
|
||||||
colour = Colour(value=colour)
|
colour = Colour(value=colour)
|
||||||
|
|
||||||
|
@ -258,6 +258,16 @@ class ShardInfo:
|
|||||||
""":class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds for this shard."""
|
""":class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds for this shard."""
|
||||||
return self._parent.ws.latency
|
return self._parent.ws.latency
|
||||||
|
|
||||||
|
def is_ws_ratelimited(self):
|
||||||
|
""":class:`bool`: Whether the websocket is currently rate limited.
|
||||||
|
|
||||||
|
This can be useful to know when deciding whether you should query members
|
||||||
|
using HTTP or via the gateway.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
return self._parent.ws.is_ratelimited()
|
||||||
|
|
||||||
class AutoShardedClient(Client):
|
class AutoShardedClient(Client):
|
||||||
"""A client similar to :class:`Client` except it handles the complications
|
"""A client similar to :class:`Client` except it handles the complications
|
||||||
of sharding for the user into a more manageable and transparent single
|
of sharding for the user into a more manageable and transparent single
|
||||||
@ -519,3 +529,16 @@ class AutoShardedClient(Client):
|
|||||||
|
|
||||||
me.activities = activities
|
me.activities = activities
|
||||||
me.status = status_enum
|
me.status = status_enum
|
||||||
|
|
||||||
|
def is_ws_ratelimited(self):
|
||||||
|
""":class:`bool`: Whether the websocket is currently rate limited.
|
||||||
|
|
||||||
|
This can be useful to know when deciding whether you should query members
|
||||||
|
using HTTP or via the gateway.
|
||||||
|
|
||||||
|
This implementation checks if any of the shards are rate limited.
|
||||||
|
For more granular control, consider :meth:`ShardInfo.is_ws_ratelimited`.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
"""
|
||||||
|
return any(shard.ws.is_ratelimited() for shard in self.__shards.values())
|
||||||
|
@ -30,7 +30,6 @@ import copy
|
|||||||
import datetime
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import math
|
|
||||||
import weakref
|
import weakref
|
||||||
import warnings
|
import warnings
|
||||||
import inspect
|
import inspect
|
||||||
@ -53,7 +52,6 @@ from .role import Role
|
|||||||
from .enums import ChannelType, try_enum, Status
|
from .enums import ChannelType, try_enum, Status
|
||||||
from . import utils
|
from . import utils
|
||||||
from .flags import Intents, MemberCacheFlags
|
from .flags import Intents, MemberCacheFlags
|
||||||
from .embeds import Embed
|
|
||||||
from .object import Object
|
from .object import Object
|
||||||
from .invite import Invite
|
from .invite import Invite
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class Team:
|
|||||||
"""
|
"""
|
||||||
return self.icon_url_as()
|
return self.icon_url_as()
|
||||||
|
|
||||||
def icon_url_as(self, *, format='None', size=1024):
|
def icon_url_as(self, *, format='webp', size=1024):
|
||||||
"""Returns an :class:`Asset` for the icon the team has.
|
"""Returns an :class:`Asset` for the icon the team has.
|
||||||
|
|
||||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||||
|
@ -31,7 +31,6 @@ import unicodedata
|
|||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from bisect import bisect_left
|
from bisect import bisect_left
|
||||||
import datetime
|
import datetime
|
||||||
from email.utils import parsedate_to_datetime
|
|
||||||
import functools
|
import functools
|
||||||
from inspect import isawaitable as _isawaitable
|
from inspect import isawaitable as _isawaitable
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
@ -40,7 +39,6 @@ import re
|
|||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from .errors import InvalidArgument
|
from .errors import InvalidArgument
|
||||||
from .object import Object
|
|
||||||
|
|
||||||
DISCORD_EPOCH = 1420070400000
|
DISCORD_EPOCH = 1420070400000
|
||||||
MAX_ASYNCIO_SECONDS = 3456000
|
MAX_ASYNCIO_SECONDS = 3456000
|
||||||
|
44
docs/api.rst
44
docs/api.rst
@ -2145,6 +2145,26 @@ Certain utilities make working with async iterators easier, detailed below.
|
|||||||
:return: A list of every element in the async iterator.
|
:return: A list of every element in the async iterator.
|
||||||
:rtype: list
|
:rtype: list
|
||||||
|
|
||||||
|
.. method:: chunk(max_size)
|
||||||
|
|
||||||
|
Collects items into chunks of up to a given maximum size.
|
||||||
|
Another :class:`AsyncIterator` is returned which collects items into
|
||||||
|
:class:`list`\s of a given size. The maximum chunk size must be a positive integer.
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Collecting groups of users: ::
|
||||||
|
|
||||||
|
async for leader, *users in reaction.users().chunk(3):
|
||||||
|
...
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The last chunk collected may not be as large as ``max_size``.
|
||||||
|
|
||||||
|
:param max_size: The size of individual chunks.
|
||||||
|
:rtype: :class:`AsyncIterator`
|
||||||
|
|
||||||
.. method:: map(func)
|
.. method:: map(func)
|
||||||
|
|
||||||
This is similar to the built-in :func:`map <py:map>` function. Another
|
This is similar to the built-in :func:`map <py:map>` function. Another
|
||||||
@ -2767,6 +2787,13 @@ Message
|
|||||||
.. autoclass:: Message()
|
.. autoclass:: Message()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
DeletedReferencedMessage
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. autoclass:: DeletedReferencedMessage()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Reaction
|
Reaction
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
@ -2872,6 +2899,12 @@ Role
|
|||||||
.. autoclass:: Role()
|
.. autoclass:: Role()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
RoleTags
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
.. autoclass:: RoleTags()
|
||||||
|
:members:
|
||||||
|
|
||||||
TextChannel
|
TextChannel
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -2999,11 +3032,6 @@ Sticker
|
|||||||
.. autoclass:: Sticker()
|
.. autoclass:: Sticker()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
MessageReference
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
.. autoclass:: MessageReference()
|
|
||||||
:members:
|
|
||||||
|
|
||||||
RawMessageDeleteEvent
|
RawMessageDeleteEvent
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -3092,6 +3120,12 @@ AllowedMentions
|
|||||||
.. autoclass:: AllowedMentions
|
.. autoclass:: AllowedMentions
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
MessageReference
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. autoclass:: MessageReference
|
||||||
|
:members:
|
||||||
|
|
||||||
Intents
|
Intents
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
83
examples/reaction_roles.py
Normal file
83
examples/reaction_roles.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
"""Uses a messages to add and remove roles through reactions."""
|
||||||
|
|
||||||
|
import discord
|
||||||
|
|
||||||
|
class RoleReactClient(discord.Client):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.role_message_id = 0 # ID of message that can be reacted to to add role
|
||||||
|
self.emoji_to_role = {
|
||||||
|
partial_emoji_1: 0, # ID of role associated with partial emoji object 'partial_emoji_1'
|
||||||
|
partial_emoji_2: 0 # ID of role associated with partial emoji object 'partial_emoji_2'
|
||||||
|
}
|
||||||
|
|
||||||
|
async def on_raw_reaction_add(self, payload):
|
||||||
|
"""Gives a role based on a reaction emoji."""
|
||||||
|
# Make sure that the message the user is reacting to is the one we care about
|
||||||
|
if payload.message_id != self.role_message_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
role_id = self.emoji_to_role[payload.emoji]
|
||||||
|
except KeyError:
|
||||||
|
# If the emoji isn't the one we care about then exit as well.
|
||||||
|
return
|
||||||
|
|
||||||
|
guild = self.get_guild(payload.guild_id)
|
||||||
|
if guild is None:
|
||||||
|
# Check if we're still in the guild and it's cached.
|
||||||
|
return
|
||||||
|
|
||||||
|
role = guild.get_role(role_id)
|
||||||
|
if role is None:
|
||||||
|
# Make sure the role still exists and is valid.
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Finally add the role
|
||||||
|
await payload.member.add_roles(role)
|
||||||
|
except discord.HTTPException:
|
||||||
|
# If we want to do something in case of errors we'd do it here.
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def on_raw_reaction_remove(self, payload):
|
||||||
|
"""Removes a role based on a reaction emoji."""
|
||||||
|
# Make sure that the message the user is reacting to is the one we care about
|
||||||
|
if payload.message_id == self.role_message_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
role_id = self.emoji_to_role[payload.emoji]
|
||||||
|
except KeyError:
|
||||||
|
# If the emoji isn't the one we care about then exit as well.
|
||||||
|
return
|
||||||
|
|
||||||
|
guild = self.get_guild(payload.guild_id)
|
||||||
|
if guild is None:
|
||||||
|
# Check if we're still in the guild and it's cached.
|
||||||
|
return
|
||||||
|
|
||||||
|
role = guild.get_role(role_id)
|
||||||
|
if role is None:
|
||||||
|
# Make sure the role still exists and is valid.
|
||||||
|
return
|
||||||
|
|
||||||
|
member = guild.get_member(payload.user_id)
|
||||||
|
if member is None:
|
||||||
|
# Makes sure the member still exists and is valid
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Finally, remove the role
|
||||||
|
await member.remove_roles(role)
|
||||||
|
except discord.HTTPException:
|
||||||
|
# If we want to do something in case of errors we'd do it here.
|
||||||
|
pass
|
||||||
|
|
||||||
|
# This bot requires the members and reactions intents.
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.members = True
|
||||||
|
|
||||||
|
client = RoleReactClient(intents=intents)
|
||||||
|
client.run("token")
|
@ -13,7 +13,7 @@ class MyClient(discord.Client):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if message.content.startswith('!hello'):
|
if message.content.startswith('!hello'):
|
||||||
await message.channel.send('Hello {0.author.mention}'.format(message))
|
await message.reply('Hello!', mention_author=True)
|
||||||
|
|
||||||
client = MyClient()
|
client = MyClient()
|
||||||
client.run('token')
|
client.run('token')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user