From aa8a328f0ad0591724358e230ac76187c24b016e Mon Sep 17 00:00:00 2001 From: Rapptz Date: Sun, 9 Oct 2016 19:23:49 -0400 Subject: [PATCH] Remove Message.timestamp and make Message.channel_mentions lazy. Message.timestamp is replaced with Message.created_at. This commit also affects how MESSAGE_UPDATE is handled by only assuming that two keys are provided rather than overwriting data with missing keys. --- discord/message.py | 106 +++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 46 deletions(-) diff --git a/discord/message.py b/discord/message.py index c50f8ab5a..9497c2b2c 100644 --- a/discord/message.py +++ b/discord/message.py @@ -39,11 +39,9 @@ class Message: Attributes ----------- - edited_timestamp : Optional[datetime.datetime] + edited_timestamp: Optional[datetime.datetime] A naive UTC datetime object containing the edited time of the message. - timestamp : datetime.datetime - A naive UTC datetime object containing the time the message was created. - tts : bool + tts: bool Specifies if the message was done with text-to-speech. type: :class:`MessageType` The type of message. In most cases this should not be checked, but it is helpful @@ -51,12 +49,12 @@ class Message: author A :class:`Member` that sent the message. If :attr:`channel` is a private channel, then it is a :class:`User` instead. - content : str + content: str The actual contents of the message. nonce The value used by the discord server and the client to verify that the message is successfully sent. This is typically non-important. - embeds : list + embeds: list A list of embedded objects. The elements are objects that meet oEmbed's specification_. .. _specification: http://oembed.com/ @@ -66,12 +64,12 @@ class Message: In :issue:`very rare cases <21>` this could be a :class:`Object` instead. For the sake of convenience, this :class:`Object` instance has an attribute ``is_private`` set to ``True``. - server : Optional[:class:`Server`] + server: Optional[:class:`Server`] The server that the message belongs to. If not applicable (i.e. a PM) then it's None instead. call: Optional[:class:`CallMessage`] The call that the message refers to. This is only applicable to messages of type :attr:`MessageType.call`. - mention_everyone : bool + mention_everyone: bool Specifies if the message mentions everyone. .. note:: @@ -91,15 +89,18 @@ class Message: The order of the mentions list is not in any particular order so you should not rely on it. This is a discord limitation, not one with the library. - channel_mentions : list + channel_mentions: list A list of :class:`Channel` that were mentioned. If the message is in a private message then the list is always empty. - role_mentions : list + role_mentions: list A list of :class:`Role` that were mentioned. If the message is in a private message then the list is always empty. - id : str + id: str The message ID. - attachments : list + webhook_id: Optional[str] + If this message was sent by a webhook, then this is the webhook ID's that sent this + message. + attachments: list A list of attachments given to a message. pinned: bool Specifies if the message is currently pinned. @@ -107,9 +108,9 @@ class Message: Reactions to a message. Reactions can be either custom emoji or standard unicode emoji. """ - __slots__ = ( 'edited_timestamp', 'timestamp', 'tts', 'content', 'channel', + __slots__ = ( 'edited_timestamp', 'tts', 'content', 'channel', 'webhook_id', 'mention_everyone', 'embeds', 'id', 'mentions', 'author', - 'channel_mentions', 'server', '_cs_raw_mentions', 'attachments', + '_cs_channel_mentions', 'server', '_cs_raw_mentions', 'attachments', '_cs_clean_content', '_cs_raw_channel_mentions', 'nonce', 'pinned', 'role_mentions', '_cs_raw_role_mentions', 'type', 'call', '_cs_system_content', '_state', 'reactions' ) @@ -121,27 +122,31 @@ class Message: reaction.message = self self._update(channel, data) + def _try_patch(self, data, key, transform): + try: + value = data[key] + except KeyError: + pass + else: + setattr(self, key, transform(value)) + def _update(self, channel, data): - # at the moment, the timestamps seem to be naive so they have no time zone and operate on UTC time. - # we can use this to our advantage to use strptime instead of a complicated parsing routine. - # example timestamp: 2015-08-21T12:03:45.782000+00:00 - # sometimes the .%f modifier is missing - self.edited_timestamp = utils.parse_time(data['edited_timestamp']) - self.timestamp = utils.parse_time(data['timestamp']) - self.tts = data.get('tts', False) - self.pinned = data.get('pinned', False) - self.content = data['content'] - self.mention_everyone = data['mention_everyone'] - self.embeds = data['embeds'] - self.id = data['id'] self.channel = channel - self.author = self._state.try_insert_user(data['author']) - self.nonce = data.get('nonce') - self.attachments = data['attachments'] - self.type = try_enum(MessageType, data.get('type')) - self._handle_upgrades(data['channel_id']) - self._handle_mentions(data.get('mentions', []), data.get('mention_roles', [])) - self._handle_call(data.get('call')) + for handler in ('mentions', 'mention_roles', 'call'): + try: + getattr(self, '_handle_%s' % handler)(data[handler]) + except KeyError: + continue + + self._try_patch(data, 'edited_timestamp', utils.parse_time) + self._try_patch(data, 'author', self._state.try_insert_user) + self._try_patch(data, 'pinned', bool) + self._try_patch(data, 'mention_everyone', bool) + self._try_patch(data, 'tts', bool) + self._try_patch(data, 'content', str) + self._try_patch(data, 'attachments', lambda x: x) + self._try_patch(data, 'embeds', lambda x: x) + self._try_patch(data, 'nonce', lambda x: x) # clear the cached properties cached = filter(lambda attr: attr.startswith('_cs_'), self.__slots__) @@ -151,24 +156,21 @@ class Message: except AttributeError: pass - def _handle_mentions(self, mentions, role_mentions): + def _handle_mentions(self, mentions): self.mentions = [] - self.channel_mentions = [] - self.role_mentions = [] - if getattr(self.channel, 'is_private', True): + if self.server is None: self.mentions = [self._state.try_insert_user(m) for m in mentions] return - if self.server is not None: - for mention in mentions: - id_search = mention.get('id') - member = self.server.get_member(id_search) - if member is not None: - self.mentions.append(member) - - it = filter(None, map(lambda m: self.server.get_channel(m), self.raw_channel_mentions)) - self.channel_mentions = utils._unique(it) + for mention in mentions: + id_search = mention['id'] + member = self.server.get_member(id_search) + if member is not None: + self.mentions.append(member) + def _handle_mention_roles(self, role_mentions): + self.role_mentions = [] + if self.server is not None: for role_id in role_mentions: role = utils.get(self.server.roles, id=role_id) if role is not None: @@ -218,6 +220,13 @@ class Message: """ return re.findall(r'<@&([0-9]+)>', self.content) + @utils.cached_slot_property('_cs_channel_mentions') + def channel_mentions(self): + if self.server is None: + return [] + it = filter(None, map(lambda m: self.server.get_channel(m), self.raw_channel_mentions)) + return utils._unique(it) + @utils.cached_slot_property('_cs_clean_content') def clean_content(self): """A property that returns the content in a "cleaned up" @@ -289,6 +298,11 @@ class Message: if found is not None: self.author = found + @property + def created_at(self): + """Returns the message's creation time in UTC.""" + return utils.snowflake_time(self.id) + @utils.cached_slot_property('_cs_system_content') def system_content(self): """A property that returns the content that is rendered