From 12c20598120012eb914f9d8786dbdb1059ab01f0 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 9 Jan 2020 17:50:18 +0100 Subject: [PATCH] Split Message into Message/MessageData --- examples/interract.py | 8 +-- fbchat/_client.py | 18 +++---- fbchat/_message.py | 123 ++++++++++++++++++++++-------------------- fbchat/_thread.py | 5 +- tests/test_message.py | 13 ++--- 5 files changed, 87 insertions(+), 80 deletions(-) diff --git a/examples/interract.py b/examples/interract.py index 42d55b9..16c6f7a 100644 --- a/examples/interract.py +++ b/examples/interract.py @@ -60,7 +60,7 @@ thread.set_color(fbchat.ThreadColor.MESSENGER_BLUE) # Will change the thread emoji to `👍` thread.set_emoji("👍") -# message = fbchat.Message(session=session, id="") -# -# # Will react to a message with a 😍 emoji -# message.react(fbchat.MessageReaction.LOVE) +message = fbchat.Message(session=session, id="") + +# Will react to a message with a 😍 emoji +message.react(fbchat.MessageReaction.LOVE) diff --git a/fbchat/_client.py b/fbchat/_client.py index 581d762..26afd49 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -1362,18 +1362,15 @@ class Client: elif d.get("deltaMessageReply"): i = d["deltaMessageReply"] + thread = get_thread(metadata) metadata = i["message"]["messageMetadata"] - replied_to = Message._from_reply( - self.session, i["repliedToMessage"] - ) - message = Message._from_reply( - self.session, i["message"], replied_to - ) + replied_to = MessageData._from_reply(thread, i["repliedToMessage"]) + message = MessageData._from_reply(thread, i["message"], replied_to) self.on_message( mid=message.id, author_id=message.author, message_object=message, - thread=get_thread(metadata), + thread=thread, at=message.created_at, metadata=metadata, msg=m, @@ -1381,18 +1378,19 @@ class Client: # New message elif delta.get("class") == "NewMessage": + thread = get_thread(metadata) self.on_message( mid=mid, author_id=author_id, - message_object=Message._from_pull( - self.session, + message_object=MessageData._from_pull( + thread, delta, mid=mid, tags=metadata.get("tags"), author=author_id, created_at=at, ), - thread=get_thread(metadata), + thread=thread, at=at, metadata=metadata, msg=m, diff --git a/fbchat/_message.py b/fbchat/_message.py index bedbf2a..b43a54c 100644 --- a/fbchat/_message.py +++ b/fbchat/_message.py @@ -79,42 +79,15 @@ class Mention: class Message: """Represents a Facebook message.""" - # TODO: Make these fields required! - #: The session to use when making requests. - session = attr.ib(None, type=_session.Session) - #: The message ID - id = attr.ib(None, converter=str) + #: The thread that this message belongs to. + thread = attr.ib(type="_thread.ThreadABC") + #: The message ID. + id = attr.ib(converter=str) - #: The actual message - text = attr.ib(None) - #: A list of `Mention` objects - mentions = attr.ib(factory=list) - #: A `EmojiSize`. Size of a sent emoji - emoji_size = attr.ib(None) - #: ID of the sender - author = attr.ib(None) - #: Datetime of when the message was sent - created_at = attr.ib(None) - #: Whether the message is read - is_read = attr.ib(None) - #: A list of people IDs who read the message, works only with `Client.fetch_thread_messages` - read_by = attr.ib(factory=list) - #: A dictionary with user's IDs as keys, and their `MessageReaction` as values - reactions = attr.ib(factory=dict) - #: A `Sticker` - sticker = attr.ib(None) - #: A list of attachments - attachments = attr.ib(factory=list) - #: A list of `QuickReply` - quick_replies = attr.ib(factory=list) - #: Whether the message is unsent (deleted for everyone) - unsent = attr.ib(False) - #: Message ID you want to reply to - reply_to_id = attr.ib(None) - #: Replied message - replied_to = attr.ib(None) - #: Whether the message was forwarded - forwarded = attr.ib(False) + @property + def session(self): + """The session to use when making requests.""" + return self.thread.session def unsend(self): """Unsend the message (removes it for everyone).""" @@ -125,7 +98,7 @@ class Message: """React to the message, or removes reaction. Args: - reaction: Reaction emoji to use, if None removes reaction + reaction: Reaction emoji to use, if ``None`` removes reaction """ data = { "action": "ADD_REACTION" if reaction else "REMOVE_REACTION", @@ -138,27 +111,22 @@ class Message: j = self.session._payload_post("/webgraphql/mutation", data) _util.handle_graphql_errors(j) - @classmethod - def from_fetch(cls, thread, message_id: str) -> "Message": - """Fetch `Message` object from the given message id. + def fetch(self) -> "MessageData": + """Fetch fresh `MessageData` object.""" + message_info = self.thread._forced_fetch(self.id).get("message") + return MessageData._from_graphql(self.thread, message_info) - Args: - message_id: Message ID to fetch from - """ - message_info = thread._forced_fetch(message_id).get("message") - return Message._from_graphql(thread.session, message_info) - - @classmethod - def format_mentions(cls, text, *args, **kwargs): + @staticmethod + def format_mentions(text, *args, **kwargs): """Like `str.format`, but takes tuples with a thread id and text instead. - Return a `Message` object, with the formatted string and relevant mentions. + Return a tuple, with the formatted string and relevant mentions. >>> Message.format_mentions("Hey {!r}! My name is {}", ("1234", "Peter"), ("4321", "Michael")) - , ] emoji_size=None attachments=[]> + ("Hey 'Peter'! My name is Michael", [, ]) >>> Message.format_mentions("Hey {p}! My name is {}", ("1234", "Michael"), p=("4321", "Peter")) - , ] emoji_size=None attachments=[]> + ('Hey Peter! My name is Michael', [, ]) """ result = "" mentions = list() @@ -196,7 +164,46 @@ class Message: ) offset += len(name) - return cls(text=result, mentions=mentions) + return result, mentions + + +@attrs_default +class MessageData(Message): + """Represents data in a Facebook message. + + Inherits `Message`. + """ + + #: The actual message + text = attr.ib(None) + #: A list of `Mention` objects + mentions = attr.ib(factory=list) + #: A `EmojiSize`. Size of a sent emoji + emoji_size = attr.ib(None) + #: ID of the sender + author = attr.ib(None) + #: Datetime of when the message was sent + created_at = attr.ib(None) + #: Whether the message is read + is_read = attr.ib(None) + #: A list of people IDs who read the message, works only with `Client.fetch_thread_messages` + read_by = attr.ib(factory=list) + #: A dictionary with user's IDs as keys, and their `MessageReaction` as values + reactions = attr.ib(factory=dict) + #: A `Sticker` + sticker = attr.ib(None) + #: A list of attachments + attachments = attr.ib(factory=list) + #: A list of `QuickReply` + quick_replies = attr.ib(factory=list) + #: Whether the message is unsent (deleted for everyone) + unsent = attr.ib(False) + #: Message ID you want to reply to + reply_to_id = attr.ib(None) + #: Replied message + replied_to = attr.ib(None) + #: Whether the message was forwarded + forwarded = attr.ib(False) @staticmethod def _get_forwarded_from_tags(tags): @@ -215,7 +222,7 @@ class Message: return [] @classmethod - def _from_graphql(cls, session, data, read_receipts=None): + def _from_graphql(cls, thread, data, read_receipts=None): if data.get("message_sender") is None: data["message_sender"] = {} if data.get("message") is None: @@ -241,7 +248,7 @@ class Message: replied_to = cls._from_graphql(data["replied_to_message"]["message"]) return cls( - session=session, + thread=thread, id=str(data["message_id"]), text=data["message"].get("text"), mentions=[ @@ -270,7 +277,7 @@ class Message: ) @classmethod - def _from_reply(cls, session, data, replied_to=None): + def _from_reply(cls, thread, data, replied_to=None): tags = data["messageMetadata"].get("tags") metadata = data.get("messageMetadata", {}) @@ -297,7 +304,7 @@ class Message: ) return cls( - session=session, + thread=thread, id=metadata.get("messageId"), text=data.get("body"), mentions=[ @@ -317,9 +324,7 @@ class Message: ) @classmethod - def _from_pull( - cls, session, data, mid=None, tags=None, author=None, created_at=None - ): + def _from_pull(cls, thread, data, mid, tags, author, created_at): mentions = [] if data.get("data") and data["data"].get("prng"): try: @@ -366,7 +371,7 @@ class Message: ) return cls( - session=session, + thread=thread, id=mid, text=data.get("body"), mentions=mentions, diff --git a/fbchat/_thread.py b/fbchat/_thread.py index 957bd00..f6ec45f 100644 --- a/fbchat/_thread.py +++ b/fbchat/_thread.py @@ -318,8 +318,11 @@ class ThreadABC(metaclass=abc.ABCMeta): read_receipts = j["message_thread"]["read_receipts"]["nodes"] + # TODO: May or may not be a good idea to attach the current thread? + # For now, we just create a new thread: + thread = self.__class__(session=self.session, id=self.id) messages = [ - _message.Message._from_graphql(self.session, message, read_receipts) + _message.MessageData._from_graphql(thread, message, read_receipts) for message in j["message_thread"]["messages"]["nodes"] ] messages.reverse() diff --git a/tests/test_message.py b/tests/test_message.py index 19672ce..cc958fa 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -4,6 +4,7 @@ from fbchat._message import ( EmojiSize, Mention, Message, + MessageData, graphql_to_extensible_attachment, ) @@ -62,9 +63,9 @@ def test_mention_to_send_data(): def test_message_format_mentions(): - expected = Message( - text="Hey 'Peter'! My name is Michael", - mentions=[ + expected = ( + "Hey 'Peter'! My name is Michael", + [ Mention(thread_id="1234", offset=4, length=7), Mention(thread_id="4321", offset=24, length=7), ], @@ -78,9 +79,9 @@ def test_message_format_mentions(): def test_message_get_forwarded_from_tags(): - assert not Message._get_forwarded_from_tags(None) - assert not Message._get_forwarded_from_tags(["hot_emoji_size:unknown"]) - assert Message._get_forwarded_from_tags( + assert not MessageData._get_forwarded_from_tags(None) + assert not MessageData._get_forwarded_from_tags(["hot_emoji_size:unknown"]) + assert MessageData._get_forwarded_from_tags( ["attachment:photo", "inbox", "sent", "source:chat:forward", "tq"] )