Move /t_ms delta class parsing to separate file and add tests
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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
187
fbchat/_delta_class.py
Normal 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)
|
@@ -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
|
||||
|
@@ -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"),
|
||||
|
@@ -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
275
tests/test_delta_class.py
Normal 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})
|
@@ -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})
|
||||
|
Reference in New Issue
Block a user