fix conflicts

This commit is contained in:
iDutchy 2020-12-04 19:05:58 -06:00
commit 0354036451
22 changed files with 564 additions and 157 deletions

View File

@ -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())

View File

@ -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:

View File

@ -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':

View File

@ -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."""

View File

@ -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

View File

@ -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

View File

@ -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:
self.cog_unload()
try:
self.cog_unload()
except Exception:
pass

View File

@ -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__

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
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:
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 = allowed_mentions.to_dict()
fields['allowed_mentions'] = allowed_mentions
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)
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

View File

@ -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."""

View File

@ -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())

View File

@ -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

View File

@ -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'.

View File

@ -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

View File

@ -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
~~~~~~~~~~

View 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")

View File

@ -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')