mirror of
				https://github.com/Rapptz/discord.py.git
				synced 2025-10-31 21:43:01 +00:00 
			
		
		
		
	Implement discord.MessageFlags
Refactor flags placement and use it for suppression.
This commit is contained in:
		| @@ -27,7 +27,8 @@ from .emoji import Emoji | ||||
| from .partial_emoji import PartialEmoji | ||||
| from .activity import * | ||||
| from .channel import * | ||||
| from .guild import Guild, SystemChannelFlags | ||||
| from .guild import Guild | ||||
| from .flags import SystemChannelFlags, MessageFlags | ||||
| from .relationship import Relationship | ||||
| from .member import Member, VoiceState | ||||
| from .message import Message, Attachment | ||||
|   | ||||
							
								
								
									
										237
									
								
								discord/flags.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								discord/flags.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,237 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2015-2019 Rapptz | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a | ||||
| copy of this software and associated documentation files (the "Software"), | ||||
| to deal in the Software without restriction, including without limitation | ||||
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||||
| and/or sell copies of the Software, and to permit persons to whom the | ||||
| Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | ||||
| OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||||
| DEALINGS IN THE SOFTWARE. | ||||
| """ | ||||
|  | ||||
| __all__ = ( | ||||
|     'SystemChannelFlags', | ||||
|     'MessageFlags', | ||||
| ) | ||||
|  | ||||
| class _flag_descriptor: | ||||
|     def __init__(self, func): | ||||
|         self.flag = func(None) | ||||
|         self.__doc__ = func.__doc__ | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         return instance._has_flag(self.flag) | ||||
|  | ||||
|     def __set__(self, instance, value): | ||||
|         instance._set_flag(self.flag, value) | ||||
|  | ||||
| def fill_with_flags(cls): | ||||
|     cls.VALID_FLAGS = { | ||||
|         name: value.flag | ||||
|         for name, value in cls.__dict__.items() | ||||
|         if isinstance(value, _flag_descriptor) | ||||
|     } | ||||
|  | ||||
|     max_bits = max(cls.VALID_FLAGS.values()).bit_length() | ||||
|     cls.ALL_OFF_VALUE = -1 + (2 ** max_bits) | ||||
|     return cls | ||||
|  | ||||
| @fill_with_flags | ||||
| class SystemChannelFlags: | ||||
|     r"""Wraps up a Discord system channel flag value. | ||||
|  | ||||
|     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. This allows you to edit the system flags easily. | ||||
|  | ||||
|     To construct an object you can pass keyword arguments denoting the flags | ||||
|     to enable or disable. | ||||
|  | ||||
|     .. 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. This value is a bit array field of a 53-bit integer | ||||
|         representing the currently available flags. You should query | ||||
|         flags via the properties rather than using this raw value. | ||||
|     """ | ||||
|     __slots__ = ('value',) | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         self.value = self.ALL_OFF_VALUE | ||||
|         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 _from_value(cls, value): | ||||
|         self = cls.__new__(cls) | ||||
|         self.value = value | ||||
|         return self | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         return isinstance(other, SystemChannelFlags) and self.value == other.value | ||||
|  | ||||
|     def __ne__(self, other): | ||||
|         return not self.__eq__(other) | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return hash(self.value) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return '<SystemChannelFlags value=%s>' % self.value | ||||
|  | ||||
|     def __iter__(self): | ||||
|         for name, value in self.__class__.__dict__.items(): | ||||
|             if isinstance(value, _flag_descriptor): | ||||
|                 yield (name, self._has_flag(value.flag)) | ||||
|  | ||||
|     # For some reason the flags for system channels are "inverted" | ||||
|     # ergo, if they're set then it means "suppress" (off in the GUI toggle) | ||||
|     # Since this is counter-intuitive from an API perspective and annoying | ||||
|     # these will be inverted automatically | ||||
|  | ||||
|     def _has_flag(self, o): | ||||
|         return (self.value & o) != o | ||||
|  | ||||
|     def _set_flag(self, o, toggle): | ||||
|         if toggle is True: | ||||
|             self.value &= ~o | ||||
|         elif toggle is False: | ||||
|             self.value |= o | ||||
|         else: | ||||
|             raise TypeError('Value to set for SystemChannelFlags must be a bool.') | ||||
|  | ||||
|     @_flag_descriptor | ||||
|     def join_notifications(self): | ||||
|         """:class:`bool`: Returns ``True`` if the system channel is used for member join notifications.""" | ||||
|         return 1 | ||||
|  | ||||
|     @_flag_descriptor | ||||
|     def premium_subscriptions(self): | ||||
|         """:class:`bool`: Returns ``True`` if the system channel is used for Nitro boosting notifications.""" | ||||
|         return 2 | ||||
|  | ||||
|  | ||||
| @fill_with_flags | ||||
| class MessageFlags: | ||||
|     r"""Wraps up a Discord Message flag value. | ||||
|  | ||||
|     See :class:`SystemChannelFlags`. | ||||
|  | ||||
|     .. 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. This value is a bit array field of a 53-bit integer | ||||
|         representing the currently available flags. You should query | ||||
|         flags via the properties rather than using this raw value. | ||||
|     """ | ||||
|     __slots__ = ('value',) | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         self.value = 0 | ||||
|         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 _from_value(cls, value): | ||||
|         self = cls.__new__(cls) | ||||
|         self.value = value | ||||
|         return self | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         return isinstance(other, MessageFlags) and self.value == other.value | ||||
|  | ||||
|     def __ne__(self, other): | ||||
|         return not self.__eq__(other) | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return hash(self.value) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return '<MessageFlags value=%s>' % self.value | ||||
|  | ||||
|     def __iter__(self): | ||||
|         for name, value in self.__class__.__dict__.items(): | ||||
|             if isinstance(value, _flag_descriptor): | ||||
|                 yield (name, self._has_flag(value.flag)) | ||||
|  | ||||
|     def _has_flag(self, o): | ||||
|         return (self.value & o) == o | ||||
|  | ||||
|     def _set_flag(self, o, toggle): | ||||
|         if toggle is True: | ||||
|             self.value |= o | ||||
|         elif toggle is False: | ||||
|             self.value &= o | ||||
|         else: | ||||
|             raise TypeError('Value to set for MessageFlags must be a bool.') | ||||
|  | ||||
|     @_flag_descriptor | ||||
|     def crossposted(self): | ||||
|         """:class:`bool`: Returns ``True`` if the message is the original crossposted message.""" | ||||
|         return 1 | ||||
|  | ||||
|     @_flag_descriptor | ||||
|     def is_crossposted(self): | ||||
|         """:class:`bool`: Returns ``True`` if the message was crossposted from another channel.""" | ||||
|         return 2 | ||||
|  | ||||
|     @_flag_descriptor | ||||
|     def suppress_embeds(self): | ||||
|         """:class:`bool`: Returns ``True`` if the message's embeds have been suppressed.""" | ||||
|         return 4 | ||||
|      | ||||
|     @_flag_descriptor | ||||
|     def source_message_deleted(self): | ||||
|         """:class:`bool`: Returns ``True`` if the source message for this crosspost has been deleted.""" | ||||
|         return 8 | ||||
							
								
								
									
										115
									
								
								discord/guild.py
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								discord/guild.py
									
									
									
									
									
								
							| @@ -45,124 +45,11 @@ from .iterators import AuditLogIterator, MemberIterator | ||||
| from .webhook import Webhook | ||||
| from .widget import Widget | ||||
| from .asset import Asset | ||||
| from .flags import SystemChannelFlags | ||||
|  | ||||
| BanEntry = namedtuple('BanEntry', 'reason user') | ||||
| _GuildLimit = namedtuple('_GuildLimit', 'emoji bitrate filesize') | ||||
|  | ||||
| class _flag_descriptor: | ||||
|     def __init__(self, func): | ||||
|         self.flag = func(None) | ||||
|         self.__doc__ = func.__doc__ | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         return instance._has_flag(self.flag) | ||||
|  | ||||
|     def __set__(self, instance, value): | ||||
|         instance._set_flag(self.flag, value) | ||||
|  | ||||
| def fill_with_flags(cls): | ||||
|     cls.VALID_FLAGS = { | ||||
|         name: value.flag | ||||
|         for name, value in cls.__dict__.items() | ||||
|         if isinstance(value, _flag_descriptor) | ||||
|     } | ||||
|  | ||||
|     max_bits = max(cls.VALID_FLAGS.values()).bit_length() | ||||
|     cls.ALL_OFF_VALUE = -1 + (2 ** max_bits) | ||||
|     return cls | ||||
|  | ||||
| @fill_with_flags | ||||
| class SystemChannelFlags: | ||||
|     r"""Wraps up a Discord system channel flag value. | ||||
|  | ||||
|     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. This allows you to edit the system flags easily. | ||||
|  | ||||
|     To construct an object you can pass keyword arguments denoting the flags | ||||
|     to enable or disable. | ||||
|  | ||||
|     .. 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. This value is a bit array field of a 53-bit integer | ||||
|         representing the currently available flags. You should query | ||||
|         flags via the properties rather than using this raw value. | ||||
|     """ | ||||
|     __slots__ = ('value',) | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         self.value = self.ALL_OFF_VALUE | ||||
|         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 _from_value(cls, value): | ||||
|         self = cls.__new__(cls) | ||||
|         self.value = value | ||||
|         return self | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         return isinstance(other, SystemChannelFlags) and self.value == other.value | ||||
|  | ||||
|     def __ne__(self, other): | ||||
|         return not self.__eq__(other) | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return hash(self.value) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return '<SystemChannelFlags value=%s>' % self.value | ||||
|  | ||||
|     def __iter__(self): | ||||
|         for name, value in self.__class__.__dict__.items(): | ||||
|             if isinstance(value, _flag_descriptor): | ||||
|                 yield (name, self._has_flag(value.flag)) | ||||
|  | ||||
|     # For some reason the flags in the Discord API are "inverted" | ||||
|     # ergo, if they're set then it means "suppress" (off in the GUI toggle) | ||||
|     # Since this is counter-intuitive from an API perspective and annoying | ||||
|     # these will be inverted automatically | ||||
|  | ||||
|     def _has_flag(self, o): | ||||
|         return (self.value & o) != o | ||||
|  | ||||
|     def _set_flag(self, o, toggle): | ||||
|         if toggle is True: | ||||
|             self.value &= ~o | ||||
|         elif toggle is False: | ||||
|             self.value |= o | ||||
|         else: | ||||
|             raise TypeError('Value to set for SystemChannelFlags must be a bool.') | ||||
|  | ||||
|     @_flag_descriptor | ||||
|     def join_notifications(self): | ||||
|         """:class:`bool`: Returns True if the system channel is used for member join notifications.""" | ||||
|         return 1 | ||||
|  | ||||
|     @_flag_descriptor | ||||
|     def premium_subscriptions(self): | ||||
|         """:class:`bool`: Returns True if the system channel is used for Nitro boosting notifications.""" | ||||
|         return 2 | ||||
|  | ||||
|  | ||||
| class Guild(Hashable): | ||||
|     """Represents a Discord guild. | ||||
|   | ||||
| @@ -372,12 +372,6 @@ class HTTPClient: | ||||
|         r = Route('PATCH', '/channels/{channel_id}/messages/{message_id}', channel_id=channel_id, message_id=message_id) | ||||
|         return self.request(r, json=fields) | ||||
|  | ||||
|     def suppress_message_embeds(self, channel_id, message_id, *, suppress): | ||||
|         payload = { 'suppress': suppress } | ||||
|         r = Route('POST', '/channels/{channel_id}/messages/{message_id}/suppress-embeds', | ||||
|                   channel_id=channel_id, message_id=message_id) | ||||
|         return self.request(r, json=payload) | ||||
|  | ||||
|     def add_reaction(self, channel_id, message_id, emoji): | ||||
|         r = Route('PUT', '/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me', | ||||
|                   channel_id=channel_id, message_id=message_id, emoji=emoji) | ||||
| @@ -430,6 +424,10 @@ class HTTPClient: | ||||
|  | ||||
|         return self.request(Route('GET', '/channels/{channel_id}/messages', channel_id=channel_id), params=params) | ||||
|  | ||||
|     def publish_message(self, channel_id, message_id): | ||||
|         return self.request(Route('POST', '/channels/{channel_id}/messages/{message_id}/crosspost',  | ||||
|                                   channel_id=channel_id, message_id=message_id)) | ||||
|  | ||||
|     def pin_message(self, channel_id, message_id): | ||||
|         return self.request(Route('PUT', '/channels/{channel_id}/pins/{message_id}', | ||||
|                                   channel_id=channel_id, message_id=message_id)) | ||||
|   | ||||
| @@ -38,6 +38,7 @@ from .enums import MessageType, try_enum | ||||
| from .errors import InvalidArgument, ClientException, HTTPException | ||||
| from .embeds import Embed | ||||
| from .member import Member | ||||
| from .flags import MessageFlags | ||||
|  | ||||
| class Attachment: | ||||
|     """Represents an attachment from Discord. | ||||
| @@ -237,6 +238,8 @@ class Message: | ||||
|         A list of attachments given to a message. | ||||
|     pinned: :class:`bool` | ||||
|         Specifies if the message is currently pinned. | ||||
|     flags: :class:`MessageFlags` | ||||
|         Extra features of the message. | ||||
|     reactions : List[:class:`Reaction`] | ||||
|         Reactions to a message. Reactions can be either custom emoji or standard unicode emoji. | ||||
|     activity: Optional[:class:`dict`] | ||||
| @@ -263,7 +266,7 @@ class Message: | ||||
|                  'mention_everyone', 'embeds', 'id', 'mentions', 'author', | ||||
|                  '_cs_channel_mentions', '_cs_raw_mentions', 'attachments', | ||||
|                  '_cs_clean_content', '_cs_raw_channel_mentions', 'nonce', 'pinned', | ||||
|                  'role_mentions', '_cs_raw_role_mentions', 'type', 'call', | ||||
|                  'role_mentions', '_cs_raw_role_mentions', 'type', 'call', 'flags', | ||||
|                  '_cs_system_content', '_cs_guild', '_state', 'reactions', | ||||
|                  'application', 'activity') | ||||
|  | ||||
| @@ -280,19 +283,20 @@ class Message: | ||||
|         self._edited_timestamp = utils.parse_time(data['edited_timestamp']) | ||||
|         self.type = try_enum(MessageType, data['type']) | ||||
|         self.pinned = data['pinned'] | ||||
|         self.flags = MessageFlags._from_value(data.get('flags', 0)) | ||||
|         self.mention_everyone = data['mention_everyone'] | ||||
|         self.tts = data['tts'] | ||||
|         self.content = data['content'] | ||||
|         self.nonce = data.get('nonce') | ||||
|  | ||||
|         for handler in ('author', 'member', 'mentions', 'mention_roles', 'call'): | ||||
|         for handler in ('author', 'member', 'mentions', 'mention_roles', 'call', 'flags'): | ||||
|             try: | ||||
|                 getattr(self, '_handle_%s' % handler)(data[handler]) | ||||
|             except KeyError: | ||||
|                 continue | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return '<Message id={0.id} channel={0.channel!r} type={0.type!r} author={0.author!r}>'.format(self) | ||||
|         return '<Message id={0.id} channel={0.channel!r} type={0.type!r} author={0.author!r} flags={0.flags!r}>'.format(self) | ||||
|  | ||||
|     def _try_patch(self, data, key, transform=None): | ||||
|         try: | ||||
| @@ -361,6 +365,9 @@ class Message: | ||||
|     def _handle_pinned(self, value): | ||||
|         self.pinned = value | ||||
|  | ||||
|     def _handle_flags(self, value): | ||||
|         self.flags = MessageFlags._from_value(value) | ||||
|  | ||||
|     def _handle_application(self, value): | ||||
|         self.application = value | ||||
|  | ||||
| @@ -772,7 +779,9 @@ class Message: | ||||
|         except KeyError: | ||||
|             pass | ||||
|         else: | ||||
|             await self._state.http.suppress_message_embeds(self.channel.id, self.id, suppress=suppress) | ||||
|              flags = MessageFlags._from_value(self.flags.value) | ||||
|              flags.suppress_embeds = suppress | ||||
|              fields['flags'] = flags.value | ||||
|  | ||||
|         delete_after = fields.pop('delete_after', None) | ||||
|  | ||||
| @@ -783,6 +792,27 @@ class Message: | ||||
|         if delete_after is not None: | ||||
|             await self.delete(delay=delete_after) | ||||
|  | ||||
|     async def publish(self): | ||||
|         """|coro| | ||||
|  | ||||
|         Publishes this message to your announcement channel. | ||||
|  | ||||
|         You must have the :attr:`~Permissions.manage_messages` permission to use this. | ||||
|  | ||||
|         .. note:: | ||||
|  | ||||
|             This can only be used by non-bot accounts. | ||||
|  | ||||
|         Raises | ||||
|         ------- | ||||
|         Forbidden | ||||
|             You do not have the proper permissions to publish this message. | ||||
|         HTTPException | ||||
|             Publishing the message failed. | ||||
|         """ | ||||
|  | ||||
|         await self._state.http.publish_message(self.channel.id, self.id) | ||||
|  | ||||
|     async def pin(self): | ||||
|         """|coro| | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user