Move /t_ms delta class parsing to separate file and add tests

This commit is contained in:
Mads Marquart
2020-01-20 18:02:55 +01:00
parent 0696ff9f4b
commit 4abe5659ae
8 changed files with 522 additions and 281 deletions

View File

@@ -50,6 +50,15 @@ from ._client_payload import (
UnsendEvent,
MessageReplyEvent,
)
from ._delta_class import (
PeopleAdded,
PersonRemoved,
TitleSet,
UnfetchedThreadEvent,
MessagesDelivered,
ThreadsRead,
MessageEvent,
)
from ._client import Client

View File

@@ -15,6 +15,7 @@ from . import (
_message,
_event_common,
_client_payload,
_delta_class,
)
from ._thread import ThreadLocation
@@ -618,7 +619,6 @@ class Client:
return None
delta_type = delta.get("type")
delta_class = delta.get("class")
metadata = delta.get("messageMetadata")
if metadata:
@@ -626,30 +626,8 @@ class Client:
author_id = str(metadata["actorFbId"])
at = _util.millis_to_datetime(int(metadata.get("timestamp")))
# Added participants
if "addedParticipants" in delta:
added_ids = [str(x["userFbId"]) for x in delta["addedParticipants"]]
self.on_people_added(
mid=mid,
added_ids=added_ids,
author_id=author_id,
group=get_thread(metadata),
at=at,
)
# Left/removed participants
elif "leftParticipantFbId" in delta:
removed_id = str(delta["leftParticipantFbId"])
self.on_person_removed(
mid=mid,
removed_id=removed_id,
author_id=author_id,
group=get_thread(metadata),
at=at,
)
# Color change
elif delta_type == "change_thread_theme":
if delta_type == "change_thread_theme":
thread = get_thread(metadata)
self.on_color_change(
mid=mid,
@@ -662,13 +640,6 @@ class Client:
metadata=metadata,
)
elif delta_class == "MarkFolderSeen":
locations = [
ThreadLocation(folder.lstrip("FOLDER_")) for folder in delta["folders"]
]
at = _util.millis_to_datetime(int(delta["timestamp"]))
self._on_seen(locations=locations, at=at)
# Emoji change
elif delta_type == "change_thread_icon":
new_emoji = delta["untypedData"]["thread_icon"]
@@ -681,45 +652,6 @@ class Client:
metadata=metadata,
)
# Thread title change
elif delta_class == "ThreadName":
new_title = delta["name"]
self.on_title_change(
mid=mid,
author_id=author_id,
new_title=new_title,
group=get_thread(metadata),
at=at,
metadata=metadata,
)
# Forced fetch
elif delta_class == "ForcedFetch":
mid = delta.get("messageId")
if mid is None:
self.on_unknown_messsage_type(msg=delta)
else:
group = get_thread(delta)
fetch_info = group._forced_fetch(mid)
fetch_data = fetch_info["message"]
author_id = fetch_data["message_sender"]["id"]
at = _util.millis_to_datetime(int(fetch_data["timestamp_precise"]))
if fetch_data.get("__typename") == "ThreadImageMessage":
# Thread image change
image_metadata = fetch_data.get("image_with_metadata")
image_id = (
int(image_metadata["legacy_attachment_id"])
if image_metadata
else None
)
self.on_image_change(
mid=mid,
author_id=author_id,
new_image=image_id,
group=group,
at=at,
)
# Nickname change
elif delta_type == "change_thread_nickname":
changed_for = str(delta["untypedData"]["participant_id"])
@@ -766,52 +698,6 @@ class Client:
at=at,
)
# Message delivered
elif delta_class == "DeliveryReceipt":
message_ids = delta["messageIds"]
delivered_for = str(
delta.get("actorFbId") or delta["threadKey"]["otherUserFbId"]
)
at = _util.millis_to_datetime(int(delta["deliveredWatermarkTimestampMs"]))
self.on_message_delivered(
msg_ids=message_ids,
delivered_for=delivered_for,
thread=get_thread(delta),
at=at,
metadata=metadata,
)
# Message seen
elif delta_class == "ReadReceipt":
seen_by = str(delta.get("actorFbId") or delta["threadKey"]["otherUserFbId"])
seen_at = _util.millis_to_datetime(int(delta["actionTimestampMs"]))
at = _util.millis_to_datetime(int(delta["watermarkTimestampMs"]))
self.on_message_seen(
seen_by=seen_by,
thread=get_thread(delta),
seen_at=seen_at,
at=at,
metadata=metadata,
)
# Messages marked as seen
elif delta_class == "MarkRead":
seen_at = _util.millis_to_datetime(
int(delta.get("actionTimestampMs") or delta.get("actionTimestamp"))
)
watermark_ts = delta.get("watermarkTimestampMs") or delta.get(
"watermarkTimestamp"
)
at = _util.millis_to_datetime(int(watermark_ts))
threads = []
if "folders" not in delta:
threads = [
get_thread({"threadKey": thr}) for thr in delta.get("threadKeys")
]
self.on_marked_seen(threads=threads, seen_at=seen_at, at=at, metadata=delta)
# Game played
elif delta_type == "instant_game_update":
game_id = delta["untypedData"]["game_id"]
@@ -834,10 +720,6 @@ class Client:
metadata=metadata,
)
# Skip "no operation" events
elif delta_class == "NoOp":
pass
# Group call started/ended
elif delta_type == "rtc_call_log":
call_status = delta["untypedData"]["event"]
@@ -964,28 +846,14 @@ class Client:
)
# Client payload (that weird numbers)
elif delta_class == "ClientPayload":
elif delta.get("class") == "ClientPayload":
for event in _client_payload.parse_client_payloads(self.session, delta):
self.on_event(event)
# New message
elif delta.get("class") == "NewMessage":
thread = get_thread(metadata)
self.on_message(
mid=mid,
author_id=author_id,
message_object=_message.MessageData._from_pull(
thread,
delta,
mid=mid,
tags=metadata.get("tags"),
author=author_id,
created_at=at,
),
thread=thread,
at=at,
metadata=metadata,
)
elif delta.get("class"):
event = _delta_class.parse_delta(self.session, delta)
if event:
self.on_event(event)
# Unknown message type
else:
@@ -1116,27 +984,6 @@ class Client:
"""Called when the client is listening, and an event happens."""
log.info("Got event: %s", event)
def on_message(
self,
mid=None,
author_id=None,
message_object=None,
thread=None,
at=None,
metadata=None,
):
"""Called when the client is listening, and somebody sends a message.
Args:
mid: The message ID
author_id: The ID of the author
message_object (Message): The message (As a `Message` object)
thread: Thread that the message was sent to. See :ref:`intro_threads`
at (datetime.datetime): When the message was sent
metadata: Extra metadata about the message
"""
log.info("{} from {} in {}".format(message_object, author_id, thread))
def on_color_change(
self,
mid=None,
@@ -1179,41 +1026,6 @@ class Client:
"""
log.info("Emoji change from {} in {}: {}".format(author_id, thread, new_emoji))
def on_title_change(
self,
mid=None,
author_id=None,
new_title=None,
group=None,
at=None,
metadata=None,
):
"""Called when the client is listening, and somebody changes a thread's title.
Args:
mid: The action ID
author_id: The ID of the person who changed the title
new_title: The new title
group: Group that the action was sent to. See :ref:`intro_threads`
at (datetime.datetime): When the action was executed
metadata: Extra metadata about the action
"""
log.info("Title change from {} in {}: {}".format(author_id, group, new_title))
def on_image_change(
self, mid=None, author_id=None, new_image=None, group=None, at=None
):
"""Called when the client is listening, and somebody changes a thread's image.
Args:
mid: The action ID
author_id: The ID of the person who changed the image
new_image: The ID of the new image
group: Group that the action was sent to. See :ref:`intro_threads`
at (datetime.datetime): When the action was executed
"""
log.info("{} changed group image in {}".format(author_id, group))
def on_nickname_change(
self,
mid=None,
@@ -1286,78 +1098,6 @@ class Client:
else:
log.info("{} disabled approval mode in {}".format(author_id, group))
def on_message_seen(
self, seen_by=None, thread=None, seen_at=None, at=None, metadata=None
):
"""Called when the client is listening, and somebody marks a message as seen.
Args:
seen_by: The ID of the person who marked the message as seen
thread: Thread that the action was sent to. See :ref:`intro_threads`
seen_at (datetime.datetime): When the person saw the message
at (datetime.datetime): When the action was executed
metadata: Extra metadata about the action
"""
log.info("Messages seen by {} in {} at {}".format(seen_by, thread, seen_at))
def on_message_delivered(
self, msg_ids=None, delivered_for=None, thread=None, at=None, metadata=None,
):
"""Called when the client is listening, and somebody marks messages as delivered.
Args:
msg_ids: The messages that are marked as delivered
delivered_for: The person that marked the messages as delivered
thread: Thread that the action was sent to. See :ref:`intro_threads`
at (datetime.datetime): When the action was executed
metadata: Extra metadata about the action
"""
log.info(
"Messages {} delivered to {} in {} at {}".format(
msg_ids, delivered_for, thread, at
)
)
def on_marked_seen(self, threads=None, seen_at=None, at=None, metadata=None):
"""Called when the client is listening, and the client has successfully marked threads as seen.
Args:
threads: The threads that were marked
author_id: The ID of the person who changed the emoji
seen_at (datetime.datetime): When the threads were seen
at (datetime.datetime): When the action was executed
metadata: Extra metadata about the action
"""
log.info("Marked messages as seen in threads {} at {}".format(threads, seen_at))
def on_people_added(
self, mid=None, added_ids=None, author_id=None, group=None, at=None
):
"""Called when the client is listening, and somebody adds people to a group thread.
Args:
mid: The action ID
added_ids: The IDs of the people who got added
author_id: The ID of the person who added the people
group: Group that the action was sent to. See :ref:`intro_threads`
at (datetime.datetime): When the action was executed
"""
log.info("{} added: {} in {}".format(author_id, ", ".join(added_ids), group))
def on_person_removed(
self, mid=None, removed_id=None, author_id=None, group=None, at=None
):
"""Called when the client is listening, and somebody removes a person from a group thread.
Args:
mid: The action ID
removed_id: The ID of the person who got removed
author_id: The ID of the person who removed the person
group: Group that the action was sent to. See :ref:`intro_threads`
at (datetime.datetime): When the action was executed
"""
log.info("{} removed: {} in {}".format(author_id, removed_id, group))
def on_friend_request(self, from_id=None):
"""Called when the client is listening, and somebody sends a friend request.
@@ -1366,16 +1106,6 @@ class Client:
"""
log.info("Friend request from {}".format(from_id))
def _on_seen(self, locations=None, at=None):
"""
Todo:
Document this, and make it public
Args:
locations: ---
at: A timestamp of the action
"""
def on_inbox(self, unseen=None, unread=None, recent_unread=None):
"""
Todo:

187
fbchat/_delta_class.py Normal file
View File

@@ -0,0 +1,187 @@
import attr
import datetime
from ._event_common import attrs_event, Event, UnknownEvent, ThreadEvent
from . import _util, _user, _group, _thread, _message
from typing import Sequence
@attrs_event
class PeopleAdded(ThreadEvent):
"""somebody added people to a group thread."""
# TODO: Add message id
thread = attr.ib(type=_group.Group) # Set the correct type
#: The people who got added
added = attr.ib(type=Sequence[_user.User])
#: When the people were added
at = attr.ib(type=datetime.datetime)
@classmethod
def _parse(cls, session, data):
author, thread, at = cls._parse_metadata(session, data)
added = [
# TODO: Parse user name
_user.User(session=session, id=x["userFbId"])
for x in data["addedParticipants"]
]
return cls(author=author, thread=thread, added=added, at=at)
@attrs_event
class PersonRemoved(ThreadEvent):
"""Somebody removed a person from a group thread."""
# TODO: Add message id
thread = attr.ib(type=_group.Group) # Set the correct type
#: Person who got removed
removed = attr.ib(type=_message.Message)
#: When the person were removed
at = attr.ib(type=datetime.datetime)
@classmethod
def _parse(cls, session, data):
author, thread, at = cls._parse_metadata(session, data)
removed = _user.User(session=session, id=data["leftParticipantFbId"])
return cls(author=author, thread=thread, removed=removed, at=at)
@attrs_event
class TitleSet(ThreadEvent):
"""Somebody changed a group's title."""
thread = attr.ib(type=_group.Group) # Set the correct type
#: The new title
title = attr.ib(type=str)
#: When the title was set
at = attr.ib(type=datetime.datetime)
@classmethod
def _parse(cls, session, data):
author, thread, at = cls._parse_metadata(session, data)
return cls(author=author, thread=thread, title=data["name"], at=at)
@attrs_event
class UnfetchedThreadEvent(Event):
"""A message was received, but the data must be fetched manually.
Use `Message.fetch` to retrieve the message data.
This is usually used when somebody changes the group's photo, or when a new pending
group is created.
"""
# TODO: Present this in a way that users can fetch the changed group photo easily
#: The thread the message was sent to
thread = attr.ib(type=_thread.ThreadABC)
#: The message
message = attr.ib(type=_message.Message)
@classmethod
def _parse(cls, session, data):
thread = ThreadEvent._get_thread(session, data)
message = _message.Message(thread=thread, id=data["messageId"])
return cls(thread=thread, message=message)
@attrs_event
class MessagesDelivered(ThreadEvent):
"""Somebody marked messages as delivered in a thread."""
#: The messages that were marked as delivered
messages = attr.ib(type=Sequence[_message.Message])
#: When the messages were delivered
at = attr.ib(type=datetime.datetime)
@classmethod
def _parse(cls, session, data):
author = _user.User(session=session, id=data["actorFbId"])
thread = cls._get_thread(session, data)
messages = [_message.Message(thread=thread, id=x) for x in data["messageIds"]]
at = _util.millis_to_datetime(int(data["deliveredWatermarkTimestampMs"]))
return cls(author=author, thread=thread, messages=messages, at=at)
@attrs_event
class ThreadsRead(Event):
"""Somebody marked threads as read/seen."""
#: The person who marked the threads as read
author = attr.ib(type=_thread.ThreadABC)
#: The threads that were marked as read
threads = attr.ib(type=Sequence[_thread.ThreadABC])
#: When the threads were read
at = attr.ib(type=datetime.datetime)
@classmethod
def _parse_read_receipt(cls, session, data):
author = _user.User(session=session, id=data["actorFbId"])
thread = ThreadEvent._get_thread(session, data)
at = _util.millis_to_datetime(int(data["actionTimestampMs"]))
return cls(author=author, threads=[thread], at=at)
@classmethod
def _parse(cls, session, data):
author = _user.User(session=session, id=session.user_id)
threads = [
ThreadEvent._get_thread(session, {"threadKey": x})
for x in data["threadKeys"]
]
at = _util.millis_to_datetime(int(data["actionTimestamp"]))
return cls(author=author, threads=threads, at=at)
@attrs_event
class MessageEvent(ThreadEvent):
"""Somebody sent a message to a thread."""
#: The sent message
message = attr.ib(type=_message.Message)
#: When the threads were read
at = attr.ib(type=datetime.datetime)
@classmethod
def _parse(cls, session, data):
author, thread, at = cls._parse_metadata(session, data)
message = _message.MessageData._from_pull(
thread, data, author=author.id, created_at=at,
)
return cls(author=author, thread=thread, message=message, at=at)
def parse_delta(session, data):
class_ = data.get("class")
if class_ == "ParticipantsAddedToGroupThread":
return PeopleAdded._parse(session, data)
elif class_ == "ParticipantLeftGroupThread":
return PersonRemoved._parse(session, data)
elif class_ == "MarkFolderSeen":
# TODO: Finish this
folders = [
_thread.ThreadLocation(folder.lstrip("FOLDER_"))
for folder in data["folders"]
]
at = _util.millis_to_datetime(int(data["timestamp"]))
return None
elif class_ == "ThreadName":
return TitleSet._parse(session, data)
elif class_ == "ForcedFetch":
return UnfetchedThreadEvent._parse(session, data)
elif class_ == "DeliveryReceipt":
return MessagesDelivered._parse(session, data)
elif class_ == "ReadReceipt":
return ThreadsRead._parse_read_receipt(session, data)
elif class_ == "MarkRead":
return ThreadsRead._parse(session, data)
elif class_ == "NoOp":
# Skip "no operation" events
return None
elif class_ == "ClientPayload":
return X._parse(session, data)
elif class_ == "NewMessage":
return MessageEvent._parse(session, data)
return UnknownEvent(data=data)

View File

@@ -1,7 +1,7 @@
import attr
import abc
from ._core import kw_only
from . import _exception, _thread, _group, _user, _message
from . import _exception, _util, _thread, _group, _user, _message
#: Default attrs settings for events
attrs_event = attr.s(slots=True, kw_only=kw_only, frozen=True)
@@ -48,3 +48,11 @@ class ThreadEvent(Event):
elif "otherUserFbId" in key:
return _user.User(session=session, id=str(key["otherUserFbId"]))
raise _exception.ParseError("Could not find thread data", data=data)
@staticmethod
def _parse_metadata(session, data):
metadata = data["messageMetadata"]
author = _user.User(session=session, id=metadata["actorFbId"])
thread = ThreadEvent._get_thread(session, metadata)
at = _util.millis_to_datetime(int(metadata["timestamp"]))
return author, thread, at

View File

@@ -367,7 +367,11 @@ class MessageData(Message):
)
@classmethod
def _from_pull(cls, thread, data, mid, tags, author, created_at):
def _from_pull(cls, thread, data, author, created_at):
metadata = data["messageMetadata"]
tags = metadata.get("tags")
mentions = []
if data.get("data") and data["data"].get("prng"):
try:
@@ -415,7 +419,7 @@ class MessageData(Message):
return cls(
thread=thread,
id=mid,
id=metadata["messageId"],
author=author,
created_at=created_at,
text=data.get("body"),

View File

@@ -8,7 +8,14 @@ from fbchat import Message, Mention
@pytest.fixture(scope="session")
def session():
return object() # TODO: Add a mocked session
class FakeSession:
# TODO: Add a further mocked session
user_id = "31415926536"
def __repr__(self):
return "<FakeSession>"
return FakeSession()
@pytest.fixture(scope="session")

275
tests/test_delta_class.py Normal file
View File

@@ -0,0 +1,275 @@
import datetime
import pytest
from fbchat import (
ParseError,
User,
Group,
UnknownEvent,
PeopleAdded,
PersonRemoved,
TitleSet,
UnfetchedThreadEvent,
MessagesDelivered,
ThreadsRead,
MessageEvent,
)
from fbchat._message import Message, MessageData
from fbchat._delta_class import parse_delta
def test_people_added(session):
data = {
"addedParticipants": [
{
"fanoutPolicy": "IRIS_MESSAGE_QUEUE",
"firstName": "Abc",
"fullName": "Abc Def",
"initialFolder": "FOLDER_INBOX",
"initialFolderId": {"systemFolderId": "INBOX"},
"isMessengerUser": False,
"userFbId": "1234",
}
],
"irisSeqId": "11223344",
"irisTags": ["DeltaParticipantsAddedToGroupThread", "is_from_iris_fanout"],
"messageMetadata": {
"actorFbId": "3456",
"adminText": "You added Abc Def to the group.",
"folderId": {"systemFolderId": "INBOX"},
"messageId": "mid.$XYZ",
"offlineThreadingId": "1122334455",
"skipBumpThread": False,
"tags": [],
"threadKey": {"threadFbId": "4321"},
"threadReadStateEffect": "KEEP_AS_IS",
"timestamp": "1500000000000",
"unsendType": "deny_log_message",
},
"participants": ["1234", "2345", "3456", "4567"],
"requestContext": {"apiArgs": {}},
"tqSeqId": "1111",
"class": "ParticipantsAddedToGroupThread",
}
assert PeopleAdded(
author=User(session=session, id="3456"),
thread=Group(session=session, id="4321"),
added=[User(session=session, id="1234")],
at=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
) == parse_delta(session, data)
def test_person_removed(session):
data = {
"irisSeqId": "11223344",
"irisTags": ["DeltaParticipantLeftGroupThread", "is_from_iris_fanout"],
"leftParticipantFbId": "1234",
"messageMetadata": {
"actorFbId": "3456",
"adminText": "You removed Abc Def from the group.",
"folderId": {"systemFolderId": "INBOX"},
"messageId": "mid.$XYZ",
"offlineThreadingId": "1122334455",
"skipBumpThread": True,
"tags": [],
"threadKey": {"threadFbId": "4321"},
"threadReadStateEffect": "KEEP_AS_IS",
"timestamp": "1500000000000",
"unsendType": "deny_log_message",
},
"participants": ["1234", "2345", "3456", "4567"],
"requestContext": {"apiArgs": {}},
"tqSeqId": "1111",
"class": "ParticipantLeftGroupThread",
}
assert PersonRemoved(
author=User(session=session, id="3456"),
thread=Group(session=session, id="4321"),
removed=User(session=session, id="1234"),
at=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
) == parse_delta(session, data)
def test_title_set(session):
data = {
"irisSeqId": "11223344",
"irisTags": ["DeltaThreadName", "is_from_iris_fanout"],
"messageMetadata": {
"actorFbId": "3456",
"adminText": "You named the group abc.",
"folderId": {"systemFolderId": "INBOX"},
"messageId": "mid.$XYZ",
"offlineThreadingId": "1122334455",
"skipBumpThread": False,
"tags": [],
"threadKey": {"threadFbId": "4321"},
"threadReadStateEffect": "KEEP_AS_IS",
"timestamp": "1500000000000",
"unsendType": "deny_log_message",
},
"name": "abc",
"participants": ["1234", "2345", "3456", "4567"],
"requestContext": {"apiArgs": {}},
"tqSeqId": "1111",
"class": "ThreadName",
}
assert TitleSet(
author=User(session=session, id="3456"),
thread=Group(session=session, id="4321"),
title="abc",
at=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
) == parse_delta(session, data)
def test_forced_fetch(session):
data = {
"forceInsert": False,
"messageId": "mid.$XYZ",
"threadKey": {"threadFbId": "1234"},
"class": "ForcedFetch",
}
thread = Group(session=session, id="1234")
assert UnfetchedThreadEvent(
thread=thread, message=Message(thread=thread, id="mid.$XYZ")
) == parse_delta(session, data)
def test_delivery_receipt(session):
data = {
"actorFbId": "1234",
"deliveredWatermarkTimestampMs": "1500000000000",
"irisSeqId": "1111111",
"irisTags": ["DeltaDeliveryReceipt"],
"messageIds": ["mid.$XYZ", "mid.$ABC"],
"requestContext": {"apiArgs": {}},
"threadKey": {"threadFbId": "4321"},
"class": "DeliveryReceipt",
}
thread = Group(session=session, id="4321")
assert MessagesDelivered(
author=User(session=session, id="1234"),
thread=thread,
messages=[
Message(thread=thread, id="mid.$XYZ"),
Message(thread=thread, id="mid.$ABC"),
],
at=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
) == parse_delta(session, data)
def test_read_receipt(session):
data = {
"actionTimestampMs": "1600000000000",
"actorFbId": "1234",
"irisSeqId": "1111111",
"irisTags": ["DeltaReadReceipt", "is_from_iris_fanout"],
"requestContext": {"apiArgs": {}},
"threadKey": {"threadFbId": "4321"},
"tqSeqId": "1111",
"watermarkTimestampMs": "1500000000000",
"class": "ReadReceipt",
}
assert ThreadsRead(
author=User(session=session, id="1234"),
threads=[Group(session=session, id="4321")],
at=datetime.datetime(2020, 9, 13, 12, 26, 40, tzinfo=datetime.timezone.utc),
) == parse_delta(session, data)
def test_mark_read(session):
data = {
"actionTimestamp": "1600000000000",
"irisSeqId": "1111111",
"irisTags": ["DeltaMarkRead", "is_from_iris_fanout"],
"threadKeys": [{"threadFbId": "1234"}, {"otherUserFbId": "2345"}],
"tqSeqId": "1111",
"watermarkTimestamp": "1500000000000",
"class": "MarkRead",
}
assert ThreadsRead(
author=User(session=session, id=session.user_id),
threads=[Group(session=session, id="1234"), User(session=session, id="2345")],
at=datetime.datetime(2020, 9, 13, 12, 26, 40, tzinfo=datetime.timezone.utc),
) == parse_delta(session, data)
def test_new_message_user(session):
data = {
"attachments": [],
"body": "test",
"irisSeqId": "1111111",
"irisTags": ["DeltaNewMessage"],
"messageMetadata": {
"actorFbId": "1234",
"folderId": {"systemFolderId": "INBOX"},
"messageId": "mid.$XYZ",
"offlineThreadingId": "11223344556677889900",
"skipBumpThread": False,
"skipSnippetUpdate": False,
"tags": ["source:messenger:web"],
"threadKey": {"otherUserFbId": "1234"},
"threadReadStateEffect": "KEEP_AS_IS",
"timestamp": "1600000000000",
},
"requestContext": {"apiArgs": {}},
"class": "NewMessage",
}
assert MessageEvent(
author=User(session=session, id="1234"),
thread=User(session=session, id="1234"),
message=MessageData(
thread=User(session=session, id="1234"),
id="mid.$XYZ",
author="1234",
text="test",
created_at=datetime.datetime(
2020, 9, 13, 12, 26, 40, tzinfo=datetime.timezone.utc
),
),
at=datetime.datetime(2020, 9, 13, 12, 26, 40, tzinfo=datetime.timezone.utc),
) == parse_delta(session, data)
def test_new_message_group(session):
data = {
"attachments": [],
"body": "test",
"irisSeqId": "1111111",
"irisTags": ["DeltaNewMessage", "is_from_iris_fanout"],
"messageMetadata": {
"actorFbId": "4321",
"folderId": {"systemFolderId": "INBOX"},
"messageId": "mid.$XYZ",
"offlineThreadingId": "11223344556677889900",
"skipBumpThread": False,
"tags": ["source:messenger:web"],
"threadKey": {"threadFbId": "1234"},
"threadReadStateEffect": "KEEP_AS_IS",
"timestamp": "1600000000000",
},
"participants": ["4321", "5432", "6543"],
"requestContext": {"apiArgs": {}},
"tqSeqId": "1111",
"class": "NewMessage",
}
assert MessageEvent(
author=User(session=session, id="4321"),
thread=Group(session=session, id="1234"),
message=MessageData(
thread=Group(session=session, id="1234"),
id="mid.$XYZ",
author="4321",
text="test",
created_at=datetime.datetime(
2020, 9, 13, 12, 26, 40, tzinfo=datetime.timezone.utc
),
),
at=datetime.datetime(2020, 9, 13, 12, 26, 40, tzinfo=datetime.timezone.utc),
) == parse_delta(session, data)
def test_noop(session):
assert parse_delta(session, {"class": "NoOp"}) is None
def test_parse_delta_unknown(session):
assert UnknownEvent(data={"abc": 10}) == parse_delta(session, {"abc": 10})

View File

@@ -1,4 +1,5 @@
import pytest
import datetime
from fbchat import Group, User, ParseError, ThreadEvent
@@ -55,3 +56,23 @@ def test_thread_event_get_thread_unknown(session):
data = {"threadKey": {"abc": "1234"}}
with pytest.raises(ParseError, match="Could not find thread data"):
ThreadEvent._get_thread(session, data)
def test_thread_event_parse_metadata(session):
data = {
"actorFbId": "4321",
"folderId": {"systemFolderId": "INBOX"},
"messageId": "mid.$XYZ",
"offlineThreadingId": "112233445566",
"skipBumpThread": False,
"skipSnippetUpdate": False,
"tags": ["source:messenger:web"],
"threadKey": {"otherUserFbId": "1234"},
"threadReadStateEffect": "KEEP_AS_IS",
"timestamp": "1500000000000",
}
assert (
User(session=session, id="4321"),
User(session=session, id="1234"),
datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
) == ThreadEvent._parse_metadata(session, {"messageMetadata": data})