From a6381dcf77f773316ea59767a06d06cfcc57e4d9 Mon Sep 17 00:00:00 2001 From: Rapptz Date: Fri, 20 Dec 2019 23:10:46 -0500 Subject: [PATCH] Add support for guild intents --- discord/client.py | 4 + discord/flags.py | 317 ++++++++++++++++++++++++++++++++++++++++++++- discord/gateway.py | 3 + discord/state.py | 8 ++ 4 files changed, 331 insertions(+), 1 deletion(-) diff --git a/discord/client.py b/discord/client.py index 407fd47f9..91d83da46 100644 --- a/discord/client.py +++ b/discord/client.py @@ -140,6 +140,10 @@ class Client: Integer starting at ``0`` and less than :attr:`.shard_count`. shard_count: Optional[:class:`int`] The total number of shards. + intents: :class:`Intents` + A list of intents that you want to enable for the session. This is a way of + disabling and enabling certain gateway events from triggering and being sent. + Currently, if no intents are passed then you will receive all data. fetch_offline_members: :class:`bool` Indicates if :func:`.on_ready` should be delayed to fetch all offline members from the guilds the client belongs to. If this is ``False``\, then diff --git a/discord/flags.py b/discord/flags.py index 448bced2f..4f5f197e0 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -29,7 +29,8 @@ from .enums import UserFlags __all__ = ( 'SystemChannelFlags', 'MessageFlags', - 'PublicUserFlags' + 'PublicUserFlags', + 'Intents', ) class flag_value: @@ -327,3 +328,317 @@ class PublicUserFlags(BaseFlags): def all(self): """List[:class:`UserFlags`]: Returns all public flags the user has.""" return [public_flag for public_flag in UserFlags if self._has_flag(public_flag.value)] + + +@fill_with_flags() +class Intents(BaseFlags): + r"""Wraps up a Discord gateway intent flag. + + Similar to :class:`Permissions`\, the properties provided are two way. + You can set and retrieve individual bits using the properties as if they + were regular bools. + + To construct an object you can pass keyword arguments denoting the flags + to enable or disable. + + This is used to disable certain gateway features that are unnecessary to + run your bot. To make use of this, it is passed to the ``intents`` keyword + argument of :class:`Client`. + + A default instance of this class has everything enabled except :attr:`presences`. + + .. container:: operations + + .. describe:: x == y + + Checks if two flags are equal. + .. describe:: x != y + + Checks if two flags are not equal. + .. describe:: hash(x) + + Return the flag's hash. + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + + Attributes + ----------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + __slots__ = () + + def __init__(self, **kwargs): + # Change the default value to everything being enabled + # except presences + bits = max(self.VALID_FLAGS.values()).bit_length() + self.value = (1 << bits) - 1 + self.presences = False + for key, value in kwargs.items(): + if key not in self.VALID_FLAGS: + raise TypeError('%r is not a valid flag name.' % key) + setattr(self, key, value) + + @classmethod + def all(cls): + """A factory method that creates a :class:`Intents` with everything enabled.""" + bits = max(cls.VALID_FLAGS.values()).bit_length() + value = (1 << bits) - 1 + self = cls.__new__(cls) + self.value = value + return self + + @classmethod + def none(cls): + """A factory method that creates a :class:`Intents` with everything disabled.""" + self = cls.__new__(cls) + self.value = self.DEFAULT_VALUE + return self + + @flag_value + def guilds(self): + """:class:`bool`: Whether guild related events are enabled. + + This corresponds to the following events: + + - :func:`on_guild_join` + - :func:`on_guild_remove` + - :func:`on_guild_available` + - :func:`on_guild_unavailable` + - :func:`on_guild_channel_update` + - :func:`on_guild_channel_create` + - :func:`on_guild_channel_delete` + - :func:`on_guild_channel_pins_update` + """ + return 1 << 0 + + @flag_value + def members(self): + """:class:`bool`: Whether guild member related events are enabled. + + This corresponds to the following events: + + - :func:`on_member_join` + - :func:`on_member_remove` + - :func:`on_member_update` (nickname, roles) + """ + return 1 << 1 + + @flag_value + def bans(self): + """:class:`bool`: Whether guild ban related events are enabled. + + This corresponds to the following events: + + - :func:`on_member_ban` + - :func:`on_member_unban` + """ + return 1 << 2 + + @flag_value + def emojis(self): + """:class:`bool`: Whether guild emoji related events are enabled. + + This corresponds to the following events: + + - :func:`on_guild_emojis_update` + """ + return 1 << 3 + + @flag_value + def integrations(self): + """:class:`bool`: Whether guild integration related events are enabled. + + This corresponds to the following events: + + - :func:`on_guild_integrations_update` + """ + return 1 << 4 + + @flag_value + def webhooks(self): + """:class:`bool`: Whether guild webhook related events are enabled. + + This corresponds to the following events: + + - :func:`on_webhooks_update` + """ + return 1 << 5 + + @flag_value + def invites(self): + """:class:`bool`: Whether guild invite related events are enabled. + + This corresponds to the following events: + + - :func:`on_invite_create` + - :func:`on_invite_delete` + """ + return 1 << 6 + + @flag_value + def voice_states(self): + """:class:`bool`: Whether guild voice state related events are enabled. + + This corresponds to the following events: + + - :func:`on_voice_state_update` + """ + return 1 << 7 + + @flag_value + def presences(self): + """:class:`bool`: Whether guild voice state related events are enabled. + + This corresponds to the following events: + + - :func:`on_member_update` (activities, status) + - :func:`on_user_update` + + .. note:: + + Currently, this requires opting in explicitly via the dev portal as well. + Bots in over 100 guilds will need to apply to Discord for approval. + """ + return 1 << 8 + + @flag_value + def messages(self): + """:class:`bool`: Whether guild and direct message related events are enabled. + + This is a shortcut to set or get both :attr:`guild_messages` and :attr:`dm_messages`. + + This corresponds to the following events: + + - :func:`on_message` (both guilds and DMs) + - :func:`on_message_update` (both guilds and DMs) + - :func:`on_message_delete` (both guilds and DMs) + - :func:`on_raw_message_delete` (both guilds and DMs) + - :func:`on_raw_message_update` (both guilds and DMs) + - :func:`on_private_channel_create` + """ + return (1 << 9) | (1 << 12) + + @flag_value + def guild_messages(self): + """:class:`bool`: Whether guild message related events are enabled. + + See also :attr:`dm_messages` for DMs or :attr:`messages` for both. + + This corresponds to the following events: + + - :func:`on_message` (only for guilds) + - :func:`on_message_update` (only for guilds) + - :func:`on_message_delete` (only for guilds) + - :func:`on_raw_message_delete` (only for guilds) + - :func:`on_raw_message_update` (only for guilds) + """ + return 1 << 9 + + @flag_value + def dm_messages(self): + """:class:`bool`: Whether direct message related events are enabled. + + See also :attr:`guild_messages` for guilds or :attr:`messages` for both. + + This corresponds to the following events: + + - :func:`on_message` (only for DMs) + - :func:`on_message_update` (only for DMs) + - :func:`on_message_delete` (only for DMs) + - :func:`on_raw_message_delete` (only for DMs) + - :func:`on_raw_message_update` (only for DMs) + - :func:`on_private_channel_create` + """ + return 1 << 12 + + @flag_value + def reactions(self): + """:class:`bool`: Whether guild and direct message reaction related events are enabled. + + This is a shortcut to set or get both :attr:`guild_reactions` and :attr:`dm_reactions`. + + This corresponds to the following events: + + - :func:`on_reaction_add` (both guilds and DMs) + - :func:`on_reaction_remove` (both guilds and DMs) + - :func:`on_reaction_clear` (both guilds and DMs) + - :func:`on_raw_reaction_add` (both guilds and DMs) + - :func:`on_raw_reaction_remove` (both guilds and DMs) + - :func:`on_raw_reaction_clear` (both guilds and DMs) + """ + return (1 << 10) | (1 << 13) + + @flag_value + def guild_reactions(self): + """:class:`bool`: Whether guild message reaction related events are enabled. + + See also :attr:`dm_reactions` for DMs or :attr:`reactions` for both. + + This corresponds to the following events: + + - :func:`on_reaction_add` (only for guilds) + - :func:`on_reaction_remove` (only for guilds) + - :func:`on_reaction_clear` (only for guilds) + - :func:`on_raw_reaction_add` (only for guilds) + - :func:`on_raw_reaction_remove` (only for guilds) + - :func:`on_raw_reaction_clear` (only for guilds) + """ + return 1 << 10 + + @flag_value + def dm_reactions(self): + """:class:`bool`: Whether direct message reaction related events are enabled. + + See also :attr:`guild_reactions` for guilds or :attr:`reactions` for both. + + This corresponds to the following events: + + - :func:`on_reaction_add` (only for DMs) + - :func:`on_reaction_remove` (only for DMs) + - :func:`on_reaction_clear` (only for DMs) + - :func:`on_raw_reaction_add` (only for DMs) + - :func:`on_raw_reaction_remove` (only for DMs) + - :func:`on_raw_reaction_clear` (only for DMs) + """ + return 1 << 13 + + @flag_value + def typing(self): + """:class:`bool`: Whether guild and direct message typing related events are enabled. + + This is a shortcut to set or get both :attr:`guild_typing` and :attr:`dm_typing`. + + This corresponds to the following events: + + - :func:`on_typing` (both guilds and DMs) + """ + return (1 << 11) | (1 << 14) + + @flag_value + def guild_typing(self): + """:class:`bool`: Whether guild and direct message typing related events are enabled. + + See also :attr:`dm_typing` for DMs or :attr:`typing` for both. + + This corresponds to the following events: + + - :func:`on_typing` (only for guilds) + """ + return 1 << 11 + + @flag_value + def dm_typing(self): + """:class:`bool`: Whether guild and direct message typing related events are enabled. + + See also :attr:`guild_typing` for guilds or :attr:`typing` for both. + + This corresponds to the following events: + + - :func:`on_typing` (only for DMs) + """ + return 1 << 14 diff --git a/discord/gateway.py b/discord/gateway.py index 382d60f1b..ab5955661 100644 --- a/discord/gateway.py +++ b/discord/gateway.py @@ -343,6 +343,9 @@ class DiscordWebSocket: 'afk': False } + if state._intents is not None: + payload['d']['intents'] = state._intents + await self.call_hooks('before_identify', self.shard_id, initial=self._initial_identify) await self.send_as_json(payload) log.info('Shard ID %s has sent the IDENTIFY payload.', self.shard_id) diff --git a/discord/state.py b/discord/state.py index fc297d035..6c0f1e89a 100644 --- a/discord/state.py +++ b/discord/state.py @@ -51,6 +51,7 @@ from .member import Member from .role import Role from .enums import ChannelType, try_enum, Status, Enum from . import utils +from .flags import Intents from .embeds import Embed from .object import Object from .invite import Invite @@ -115,8 +116,15 @@ class ConnectionState: else: status = str(status) + intents = options.get('intents', None) + if intents is not None: + if not isinstance(intents, Intents): + raise TypeError('intents parameter must be Intent not %r' % type(intents)) + intents = intents.value + self._activity = activity self._status = status + self._intents = intents self.parsers = parsers = {} for attr, func in inspect.getmembers(self):