fix conflicts
This commit is contained in:
commit
0354036451
@ -33,12 +33,12 @@ from .guild import Guild
|
||||
from .flags import *
|
||||
from .relationship import Relationship
|
||||
from .member import Member, VoiceState
|
||||
from .message import Message, MessageReference, Attachment
|
||||
from .message import *
|
||||
from .asset import Asset
|
||||
from .errors import *
|
||||
from .calls import CallMessage, GroupCall
|
||||
from .permissions import Permissions, PermissionOverwrite
|
||||
from .role import Role
|
||||
from .role import Role, RoleTags
|
||||
from .file import File
|
||||
from .colour import Color, Colour
|
||||
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)
|
||||
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
||||
|
@ -32,7 +32,7 @@ import asyncio
|
||||
from .iterators import HistoryIterator
|
||||
from .context_managers import Typing
|
||||
from .enums import ChannelType
|
||||
from .errors import InvalidArgument, ClientException, HTTPException
|
||||
from .errors import InvalidArgument, ClientException
|
||||
from .permissions import PermissionOverwrite, Permissions
|
||||
from .role import Role
|
||||
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,
|
||||
files=None, delete_after=None, nonce=None,
|
||||
allowed_mentions=None, message_reference=None):
|
||||
allowed_mentions=None, reference=None,
|
||||
mention_author=None):
|
||||
"""|coro|
|
||||
|
||||
Sends a message to the destination with the content given.
|
||||
@ -857,6 +858,19 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
|
||||
.. 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
|
||||
--------
|
||||
~discord.HTTPException
|
||||
@ -864,8 +878,10 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
~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``.
|
||||
The ``files`` list is not of the appropriate size,
|
||||
you specified both ``file`` and ``files``,
|
||||
or the ``reference`` object is not a :class:`~discord.Message`
|
||||
or :class:`~discord.MessageReference`.
|
||||
|
||||
Returns
|
||||
---------
|
||||
@ -887,8 +903,15 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
else:
|
||||
allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict()
|
||||
|
||||
if message_reference is not None:
|
||||
message_reference = message_reference.to_dict()
|
||||
if mention_author is not None:
|
||||
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:
|
||||
raise InvalidArgument('cannot pass both file and files parameter to send()')
|
||||
@ -900,7 +923,7 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
try:
|
||||
data = await state.http.send_files(channel.id, files=[file], allowed_mentions=allowed_mentions,
|
||||
content=content, tts=tts, embed=embed, nonce=nonce,
|
||||
message_reference=message_reference)
|
||||
message_reference=reference)
|
||||
finally:
|
||||
file.close()
|
||||
|
||||
@ -913,14 +936,14 @@ class Messageable(metaclass=abc.ABCMeta):
|
||||
try:
|
||||
data = await state.http.send_files(channel.id, files=files, content=content, tts=tts,
|
||||
embed=embed, nonce=nonce, allowed_mentions=allowed_mentions,
|
||||
message_reference=message_reference)
|
||||
message_reference=reference)
|
||||
finally:
|
||||
for f in files:
|
||||
f.close()
|
||||
else:
|
||||
data = await state.http.send_message(channel.id, content, tts=tts, embed=embed,
|
||||
nonce=nonce, allowed_mentions=allowed_mentions,
|
||||
message_reference=message_reference)
|
||||
message_reference=reference)
|
||||
|
||||
ret = state.create_message(channel=channel, data=data)
|
||||
if delete_after is not None:
|
||||
|
@ -685,6 +685,7 @@ class CustomActivity(BaseActivity):
|
||||
__slots__ = ('name', 'emoji', 'state')
|
||||
|
||||
def __init__(self, name, *, emoji=None, **extra):
|
||||
super().__init__(**extra)
|
||||
self.name = name
|
||||
self.state = extra.pop('state', None)
|
||||
if self.name == 'Custom Status':
|
||||
|
@ -35,14 +35,12 @@ import re
|
||||
import aiohttp
|
||||
|
||||
from .user import User, Profile
|
||||
from .asset import Asset
|
||||
from .invite import Invite
|
||||
from .template import Template
|
||||
from .widget import Widget
|
||||
from .guild import Guild
|
||||
from .channel import _channel_factory
|
||||
from .enums import ChannelType
|
||||
from .member import Member
|
||||
from .mentions import AllowedMentions
|
||||
from .errors import *
|
||||
from .enums import Status, VoiceRegion
|
||||
@ -347,6 +345,18 @@ class Client:
|
||||
ws = self.ws
|
||||
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
|
||||
def user(self):
|
||||
"""Optional[:class:`.ClientUser`]: Represents the connected client. ``None`` if not logged in."""
|
||||
|
@ -446,6 +446,11 @@ class ExpireBehaviour(Enum):
|
||||
|
||||
ExpireBehavior = ExpireBehaviour
|
||||
|
||||
class StickerType(Enum):
|
||||
png = 1
|
||||
apng = 2
|
||||
lottie = 3
|
||||
|
||||
def try_enum(cls, val):
|
||||
"""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]
|
||||
except (KeyError, TypeError, AttributeError):
|
||||
return val
|
||||
|
||||
class StickerType(Enum):
|
||||
png = 1
|
||||
apng = 2
|
||||
lottie = 3
|
||||
|
@ -30,12 +30,11 @@ import inspect
|
||||
import importlib.util
|
||||
import sys
|
||||
import traceback
|
||||
import re
|
||||
import types
|
||||
|
||||
import discord
|
||||
|
||||
from .core import GroupMixin, Command
|
||||
from .core import GroupMixin
|
||||
from .view import StringView
|
||||
from .context import Context
|
||||
from . import errors
|
||||
|
@ -70,6 +70,11 @@ class CogMeta(type):
|
||||
-----------
|
||||
name: :class:`str`
|
||||
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`
|
||||
A list of attributes to apply to every command inside this cog. The dictionary
|
||||
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_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 = {}
|
||||
listeners = {}
|
||||
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
|
||||
def description(self):
|
||||
""":class:`str`: Returns the cog's description, typically the cleaned docstring."""
|
||||
try:
|
||||
return self.__cog_cleaned_doc__
|
||||
except AttributeError:
|
||||
self.__cog_cleaned_doc__ = cleaned = inspect.getdoc(self)
|
||||
return cleaned
|
||||
return self.__cog_description__
|
||||
|
||||
@description.setter
|
||||
def description(self, description):
|
||||
self.__cog_description__ = description
|
||||
|
||||
def walk_commands(self):
|
||||
"""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:
|
||||
bot.remove_check(self.bot_check_once, call_once=True)
|
||||
finally:
|
||||
try:
|
||||
self.cog_unload()
|
||||
except Exception:
|
||||
pass
|
||||
|
@ -334,26 +334,6 @@ class Context(discord.abc.Messageable):
|
||||
await cmd.on_help_command_error(self, e)
|
||||
|
||||
async def reply(self, content=None, **kwargs):
|
||||
"""|coro|
|
||||
|
||||
A shortcut method to :meth:`~discord.abc.Messageable.send` to reply to the
|
||||
: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)
|
||||
|
||||
reply.__doc__ = discord.Message.reply.__doc__
|
||||
|
@ -30,14 +30,13 @@ from collections import namedtuple
|
||||
from . import utils
|
||||
from .role import Role
|
||||
from .member import Member, VoiceState
|
||||
from .activity import create_activity
|
||||
from .emoji import Emoji
|
||||
from .errors import InvalidData
|
||||
from .permissions import PermissionOverwrite
|
||||
from .colour import Colour
|
||||
from .errors import InvalidArgument, ClientException
|
||||
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 .user import User
|
||||
from .invite import Invite
|
||||
@ -474,7 +473,7 @@ class Guild(Hashable):
|
||||
@property
|
||||
def rules_channel(self):
|
||||
"""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``.
|
||||
|
||||
@ -486,8 +485,8 @@ class Guild(Hashable):
|
||||
@property
|
||||
def public_updates_channel(self):
|
||||
"""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
|
||||
guilds that contain ``PUBLIC`` in :attr:`Guild.features`.
|
||||
moderators of the guilds receive notices from Discord. The guild must be a
|
||||
Community guild.
|
||||
|
||||
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."""
|
||||
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
|
||||
def owner(self):
|
||||
"""Optional[:class:`Member`]: The member that owns the guild."""
|
||||
@ -1209,10 +1232,10 @@ class Guild(Hashable):
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if rules_channel is None:
|
||||
fields['public_updates_channel_id'] = rules_channel
|
||||
if public_updates_channel is None:
|
||||
fields['public_updates_channel_id'] = public_updates_channel
|
||||
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)
|
||||
|
||||
async def fetch_channels(self):
|
||||
|
@ -28,7 +28,7 @@ import asyncio
|
||||
import datetime
|
||||
|
||||
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 .audit_logs import AuditLogEntry
|
||||
|
||||
@ -62,6 +62,11 @@ class _AsyncIterator:
|
||||
if ret:
|
||||
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):
|
||||
return _MappedAsyncIterator(self, func)
|
||||
|
||||
@ -92,6 +97,26 @@ class _AsyncIterator:
|
||||
def _identity(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):
|
||||
def __init__(self, iterator, func):
|
||||
self.iterator = iterator
|
||||
|
@ -34,7 +34,7 @@ from . import utils
|
||||
from .user import BaseUser, User
|
||||
from .activity import create_activity
|
||||
from .permissions import Permissions
|
||||
from .enums import Status, try_enum, UserFlags, HypeSquadHouse
|
||||
from .enums import Status, try_enum
|
||||
from .colour import Colour
|
||||
from .object import Object
|
||||
|
||||
|
@ -63,7 +63,7 @@ class AllowedMentions:
|
||||
Whether to mention the author of the message being replied to. Defaults
|
||||
to ``True``.
|
||||
|
||||
.. versionadded:: 1.5.1.5
|
||||
.. versionadded:: 1.6
|
||||
"""
|
||||
|
||||
__slots__ = ('everyone', 'users', 'roles', 'replied_user')
|
||||
@ -107,7 +107,7 @@ class AllowedMentions:
|
||||
elif self.roles != False:
|
||||
data['roles'] = [x.id for x in self.roles]
|
||||
|
||||
if self.replied_user == True:
|
||||
if self.replied_user:
|
||||
data['replied_user'] = True
|
||||
|
||||
data['parse'] = parse
|
||||
|
@ -45,6 +45,12 @@ from .guild import Guild
|
||||
from .mixins import Hashable
|
||||
from .sticker import Sticker
|
||||
|
||||
__all__ = (
|
||||
'Attachment',
|
||||
'Message',
|
||||
'MessageReference',
|
||||
'DeletedReferencedMessage',
|
||||
)
|
||||
|
||||
class Attachment:
|
||||
"""Represents an attachment from Discord.
|
||||
@ -213,11 +219,45 @@ class Attachment:
|
||||
data = await self.read(use_cached=use_cached)
|
||||
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:
|
||||
"""Represents a reference to a :class:`Message`.
|
||||
"""Represents a reference to a :class:`~discord.Message`.
|
||||
|
||||
.. versionadded:: 1.5
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
This class can now be constructed by users.
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
message_id: Optional[:class:`int`]
|
||||
@ -226,15 +266,56 @@ class MessageReference:
|
||||
The channel id of the message referenced.
|
||||
guild_id: Optional[:class:`int`]
|
||||
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):
|
||||
self.message_id = utils._get_as_snowflake(kwargs, 'message_id')
|
||||
self.channel_id = int(kwargs.pop('channel_id'))
|
||||
self.guild_id = utils._get_as_snowflake(kwargs, 'guild_id')
|
||||
def __init__(self, *, message_id, channel_id, guild_id=None):
|
||||
self._state = None
|
||||
self.resolved = None
|
||||
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.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
|
||||
def from_message(cls, message):
|
||||
@ -256,35 +337,21 @@ class MessageReference:
|
||||
|
||||
@property
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def to_dict(self, specify_channel=False):
|
||||
"""Converts the message reference to a dict, for transmission via the gateway.
|
||||
|
||||
.. versionadded:: 1.5.1.5
|
||||
|
||||
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.
|
||||
"""
|
||||
def to_dict(self):
|
||||
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
|
||||
|
||||
to_message_reference_dict = to_dict
|
||||
|
||||
def flatten_handlers(cls):
|
||||
prefix = len('_handle_')
|
||||
handlers = [
|
||||
@ -332,10 +399,10 @@ class Message(Hashable):
|
||||
call: Optional[:class:`CallMessage`]
|
||||
The call that the message refers to. This is only applicable to messages of type
|
||||
: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
|
||||
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
|
||||
|
||||
@ -431,8 +498,27 @@ class Message(Hashable):
|
||||
self.nonce = data.get('nonce')
|
||||
self.stickers = [Sticker(data=data, state=state) for data in data.get('stickers', [])]
|
||||
|
||||
ref = data.get('message_reference')
|
||||
self.reference = MessageReference(state, **ref) if ref is not None else None
|
||||
try:
|
||||
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'):
|
||||
try:
|
||||
@ -891,9 +977,22 @@ class Message(Hashable):
|
||||
before deleting the message we just edited. If the deletion fails,
|
||||
then it is silently ignored.
|
||||
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
|
||||
.. 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
|
||||
-------
|
||||
@ -931,17 +1030,24 @@ class Message(Hashable):
|
||||
|
||||
delete_after = fields.pop('delete_after', None)
|
||||
|
||||
try:
|
||||
allowed_mentions = fields.pop('allowed_mentions')
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
mention_author = fields.pop('mention_author', None)
|
||||
allowed_mentions = fields.pop('allowed_mentions', None)
|
||||
if allowed_mentions is not None:
|
||||
if self._state.allowed_mentions is not None:
|
||||
allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions).to_dict()
|
||||
else:
|
||||
allowed_mentions = self._state.allowed_mentions.merge(allowed_mentions)
|
||||
allowed_mentions = allowed_mentions.to_dict()
|
||||
if mention_author is not None:
|
||||
allowed_mentions['replied_user'] = mention_author
|
||||
fields['allowed_mentions'] = allowed_mentions
|
||||
elif mention_author is not None:
|
||||
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:
|
||||
data = await self._state.http.edit_message(self.channel.id, self.id, **fields)
|
||||
@ -1180,10 +1286,11 @@ class Message(Hashable):
|
||||
|
||||
async def reply(self, content=None, **kwargs):
|
||||
"""|coro|
|
||||
|
||||
A shortcut method to :meth:`abc.Messageable.send` to reply to the
|
||||
:class:`Message`.
|
||||
|
||||
.. versionadded:: 1.5.1.5
|
||||
.. versionadded:: 1.6
|
||||
|
||||
Raises
|
||||
--------
|
||||
@ -1201,5 +1308,28 @@ class Message(Hashable):
|
||||
The message that was sent.
|
||||
"""
|
||||
|
||||
reference = MessageReference.from_message(self)
|
||||
return await self.channel.send(content, message_reference=reference, **kwargs)
|
||||
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 .colour import Colour
|
||||
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):
|
||||
"""Represents a Discord role in a :class:`Guild`.
|
||||
@ -85,10 +131,12 @@ class Role(Hashable):
|
||||
integrations such as Twitch.
|
||||
mentionable: :class:`bool`
|
||||
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',
|
||||
'managed', 'mentionable', 'hoist', 'guild', '_state')
|
||||
'managed', 'mentionable', 'hoist', 'guild', 'tags', '_state')
|
||||
|
||||
def __init__(self, *, guild, state, data):
|
||||
self.guild = guild
|
||||
@ -150,10 +198,36 @@ class Role(Hashable):
|
||||
self.managed = data.get('managed', False)
|
||||
self.mentionable = data.get('mentionable', False)
|
||||
|
||||
try:
|
||||
self.tags = RoleTags(data['tags'])
|
||||
except KeyError:
|
||||
self.tags = None
|
||||
|
||||
def is_default(self):
|
||||
""":class:`bool`: Checks if the role is the default role."""
|
||||
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
|
||||
def permissions(self):
|
||||
""":class:`Permissions`: Returns the role's permissions."""
|
||||
|
@ -258,6 +258,16 @@ class ShardInfo:
|
||||
""":class:`float`: Measures latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds for this shard."""
|
||||
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):
|
||||
"""A client similar to :class:`Client` except it handles the complications
|
||||
of sharding for the user into a more manageable and transparent single
|
||||
@ -519,3 +529,16 @@ class AutoShardedClient(Client):
|
||||
|
||||
me.activities = activities
|
||||
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 itertools
|
||||
import logging
|
||||
import math
|
||||
import weakref
|
||||
import warnings
|
||||
import inspect
|
||||
@ -53,7 +52,6 @@ from .role import Role
|
||||
from .enums import ChannelType, try_enum, Status
|
||||
from . import utils
|
||||
from .flags import Intents, MemberCacheFlags
|
||||
from .embeds import Embed
|
||||
from .object import Object
|
||||
from .invite import Invite
|
||||
|
||||
|
@ -75,7 +75,7 @@ class Team:
|
||||
"""
|
||||
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.
|
||||
|
||||
The format must be one of 'webp', 'jpeg', 'jpg' or 'png'.
|
||||
|
@ -31,7 +31,6 @@ import unicodedata
|
||||
from base64 import b64encode
|
||||
from bisect import bisect_left
|
||||
import datetime
|
||||
from email.utils import parsedate_to_datetime
|
||||
import functools
|
||||
from inspect import isawaitable as _isawaitable
|
||||
from operator import attrgetter
|
||||
@ -40,7 +39,6 @@ import re
|
||||
import warnings
|
||||
|
||||
from .errors import InvalidArgument
|
||||
from .object import Object
|
||||
|
||||
DISCORD_EPOCH = 1420070400000
|
||||
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.
|
||||
: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)
|
||||
|
||||
This is similar to the built-in :func:`map <py:map>` function. Another
|
||||
@ -2767,6 +2787,13 @@ Message
|
||||
.. autoclass:: Message()
|
||||
:members:
|
||||
|
||||
DeletedReferencedMessage
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: DeletedReferencedMessage()
|
||||
:members:
|
||||
|
||||
|
||||
Reaction
|
||||
~~~~~~~~~
|
||||
|
||||
@ -2872,6 +2899,12 @@ Role
|
||||
.. autoclass:: Role()
|
||||
:members:
|
||||
|
||||
RoleTags
|
||||
~~~~~~~~~~
|
||||
|
||||
.. autoclass:: RoleTags()
|
||||
:members:
|
||||
|
||||
TextChannel
|
||||
~~~~~~~~~~~~
|
||||
|
||||
@ -2999,11 +3032,6 @@ Sticker
|
||||
.. autoclass:: Sticker()
|
||||
:members:
|
||||
|
||||
MessageReference
|
||||
~~~~~~~~~~~~~~~~~
|
||||
.. autoclass:: MessageReference()
|
||||
:members:
|
||||
|
||||
RawMessageDeleteEvent
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -3092,6 +3120,12 @@ AllowedMentions
|
||||
.. autoclass:: AllowedMentions
|
||||
:members:
|
||||
|
||||
MessageReference
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: MessageReference
|
||||
:members:
|
||||
|
||||
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
|
||||
|
||||
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.run('token')
|
||||
|
Loading…
x
Reference in New Issue
Block a user