Split Message into Message/MessageData

This commit is contained in:
Mads Marquart
2020-01-09 17:50:18 +01:00
parent a1b3fd3ffa
commit 12c2059812
5 changed files with 87 additions and 80 deletions

View File

@@ -60,7 +60,7 @@ thread.set_color(fbchat.ThreadColor.MESSENGER_BLUE)
# Will change the thread emoji to `👍` # Will change the thread emoji to `👍`
thread.set_emoji("👍") thread.set_emoji("👍")
# message = fbchat.Message(session=session, id="<message id>") message = fbchat.Message(session=session, id="<message id>")
#
# # Will react to a message with a 😍 emoji # Will react to a message with a 😍 emoji
# message.react(fbchat.MessageReaction.LOVE) message.react(fbchat.MessageReaction.LOVE)

View File

@@ -1362,18 +1362,15 @@ class Client:
elif d.get("deltaMessageReply"): elif d.get("deltaMessageReply"):
i = d["deltaMessageReply"] i = d["deltaMessageReply"]
thread = get_thread(metadata)
metadata = i["message"]["messageMetadata"] metadata = i["message"]["messageMetadata"]
replied_to = Message._from_reply( replied_to = MessageData._from_reply(thread, i["repliedToMessage"])
self.session, i["repliedToMessage"] message = MessageData._from_reply(thread, i["message"], replied_to)
)
message = Message._from_reply(
self.session, i["message"], replied_to
)
self.on_message( self.on_message(
mid=message.id, mid=message.id,
author_id=message.author, author_id=message.author,
message_object=message, message_object=message,
thread=get_thread(metadata), thread=thread,
at=message.created_at, at=message.created_at,
metadata=metadata, metadata=metadata,
msg=m, msg=m,
@@ -1381,18 +1378,19 @@ class Client:
# New message # New message
elif delta.get("class") == "NewMessage": elif delta.get("class") == "NewMessage":
thread = get_thread(metadata)
self.on_message( self.on_message(
mid=mid, mid=mid,
author_id=author_id, author_id=author_id,
message_object=Message._from_pull( message_object=MessageData._from_pull(
self.session, thread,
delta, delta,
mid=mid, mid=mid,
tags=metadata.get("tags"), tags=metadata.get("tags"),
author=author_id, author=author_id,
created_at=at, created_at=at,
), ),
thread=get_thread(metadata), thread=thread,
at=at, at=at,
metadata=metadata, metadata=metadata,
msg=m, msg=m,

View File

@@ -79,42 +79,15 @@ class Mention:
class Message: class Message:
"""Represents a Facebook message.""" """Represents a Facebook message."""
# TODO: Make these fields required! #: The thread that this message belongs to.
#: The session to use when making requests. thread = attr.ib(type="_thread.ThreadABC")
session = attr.ib(None, type=_session.Session) #: The message ID.
#: The message ID id = attr.ib(converter=str)
id = attr.ib(None, converter=str)
#: The actual message @property
text = attr.ib(None) def session(self):
#: A list of `Mention` objects """The session to use when making requests."""
mentions = attr.ib(factory=list) return self.thread.session
#: 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)
def unsend(self): def unsend(self):
"""Unsend the message (removes it for everyone).""" """Unsend the message (removes it for everyone)."""
@@ -125,7 +98,7 @@ class Message:
"""React to the message, or removes reaction. """React to the message, or removes reaction.
Args: Args:
reaction: Reaction emoji to use, if None removes reaction reaction: Reaction emoji to use, if ``None`` removes reaction
""" """
data = { data = {
"action": "ADD_REACTION" if reaction else "REMOVE_REACTION", "action": "ADD_REACTION" if reaction else "REMOVE_REACTION",
@@ -138,27 +111,22 @@ class Message:
j = self.session._payload_post("/webgraphql/mutation", data) j = self.session._payload_post("/webgraphql/mutation", data)
_util.handle_graphql_errors(j) _util.handle_graphql_errors(j)
@classmethod def fetch(self) -> "MessageData":
def from_fetch(cls, thread, message_id: str) -> "Message": """Fetch fresh `MessageData` object."""
"""Fetch `Message` object from the given message id. message_info = self.thread._forced_fetch(self.id).get("message")
return MessageData._from_graphql(self.thread, message_info)
Args: @staticmethod
message_id: Message ID to fetch from def format_mentions(text, *args, **kwargs):
"""
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):
"""Like `str.format`, but takes tuples with a thread id and text instead. """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")) >>> Message.format_mentions("Hey {!r}! My name is {}", ("1234", "Peter"), ("4321", "Michael"))
<Message (None): "Hey 'Peter'! My name is Michael", mentions=[<Mention 1234: offset=4 length=7>, <Mention 4321: offset=24 length=7>] emoji_size=None attachments=[]> ("Hey 'Peter'! My name is Michael", [<Mention 1234: offset=4 length=7>, <Mention 4321: offset=24 length=7>])
>>> Message.format_mentions("Hey {p}! My name is {}", ("1234", "Michael"), p=("4321", "Peter")) >>> Message.format_mentions("Hey {p}! My name is {}", ("1234", "Michael"), p=("4321", "Peter"))
<Message (None): 'Hey Peter! My name is Michael', mentions=[<Mention 4321: offset=4 length=5>, <Mention 1234: offset=22 length=7>] emoji_size=None attachments=[]> ('Hey Peter! My name is Michael', [<Mention 4321: offset=4 length=5>, <Mention 1234: offset=22 length=7>])
""" """
result = "" result = ""
mentions = list() mentions = list()
@@ -196,7 +164,46 @@ class Message:
) )
offset += len(name) 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 @staticmethod
def _get_forwarded_from_tags(tags): def _get_forwarded_from_tags(tags):
@@ -215,7 +222,7 @@ class Message:
return [] return []
@classmethod @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: if data.get("message_sender") is None:
data["message_sender"] = {} data["message_sender"] = {}
if data.get("message") is None: if data.get("message") is None:
@@ -241,7 +248,7 @@ class Message:
replied_to = cls._from_graphql(data["replied_to_message"]["message"]) replied_to = cls._from_graphql(data["replied_to_message"]["message"])
return cls( return cls(
session=session, thread=thread,
id=str(data["message_id"]), id=str(data["message_id"]),
text=data["message"].get("text"), text=data["message"].get("text"),
mentions=[ mentions=[
@@ -270,7 +277,7 @@ class Message:
) )
@classmethod @classmethod
def _from_reply(cls, session, data, replied_to=None): def _from_reply(cls, thread, data, replied_to=None):
tags = data["messageMetadata"].get("tags") tags = data["messageMetadata"].get("tags")
metadata = data.get("messageMetadata", {}) metadata = data.get("messageMetadata", {})
@@ -297,7 +304,7 @@ class Message:
) )
return cls( return cls(
session=session, thread=thread,
id=metadata.get("messageId"), id=metadata.get("messageId"),
text=data.get("body"), text=data.get("body"),
mentions=[ mentions=[
@@ -317,9 +324,7 @@ class Message:
) )
@classmethod @classmethod
def _from_pull( def _from_pull(cls, thread, data, mid, tags, author, created_at):
cls, session, data, mid=None, tags=None, author=None, created_at=None
):
mentions = [] mentions = []
if data.get("data") and data["data"].get("prng"): if data.get("data") and data["data"].get("prng"):
try: try:
@@ -366,7 +371,7 @@ class Message:
) )
return cls( return cls(
session=session, thread=thread,
id=mid, id=mid,
text=data.get("body"), text=data.get("body"),
mentions=mentions, mentions=mentions,

View File

@@ -318,8 +318,11 @@ class ThreadABC(metaclass=abc.ABCMeta):
read_receipts = j["message_thread"]["read_receipts"]["nodes"] 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 = [ 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"] for message in j["message_thread"]["messages"]["nodes"]
] ]
messages.reverse() messages.reverse()

View File

@@ -4,6 +4,7 @@ from fbchat._message import (
EmojiSize, EmojiSize,
Mention, Mention,
Message, Message,
MessageData,
graphql_to_extensible_attachment, graphql_to_extensible_attachment,
) )
@@ -62,9 +63,9 @@ def test_mention_to_send_data():
def test_message_format_mentions(): def test_message_format_mentions():
expected = Message( expected = (
text="Hey 'Peter'! My name is Michael", "Hey 'Peter'! My name is Michael",
mentions=[ [
Mention(thread_id="1234", offset=4, length=7), Mention(thread_id="1234", offset=4, length=7),
Mention(thread_id="4321", offset=24, 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(): def test_message_get_forwarded_from_tags():
assert not Message._get_forwarded_from_tags(None) assert not MessageData._get_forwarded_from_tags(None)
assert not Message._get_forwarded_from_tags(["hot_emoji_size:unknown"]) assert not MessageData._get_forwarded_from_tags(["hot_emoji_size:unknown"])
assert Message._get_forwarded_from_tags( assert MessageData._get_forwarded_from_tags(
["attachment:photo", "inbox", "sent", "source:chat:forward", "tq"] ["attachment:photo", "inbox", "sent", "source:chat:forward", "tq"]
) )