Refactor threads file structure

This commit is contained in:
Mads Marquart
2020-01-23 14:06:00 +01:00
parent c83836ceed
commit 2aea401c79
17 changed files with 138 additions and 116 deletions

View File

@@ -27,10 +27,18 @@ from ._exception import (
PleaseRefresh, PleaseRefresh,
) )
from ._session import Session from ._session import Session
from ._thread import ThreadLocation, ThreadABC, Thread from ._threads import (
from ._user import User, UserData, ActiveStatus ThreadLocation,
from ._group import Group, GroupData ThreadABC,
from ._page import Page, PageData Thread,
User,
UserData,
ActiveStatus,
Group,
GroupData,
Page,
PageData,
)
from ._message import EmojiSize, Mention, Message, MessageData from ._message import EmojiSize, Mention, Message, MessageData
from ._attachment import Attachment, UnsentMessage, ShareAttachment from ._attachment import Attachment, UnsentMessage, ShareAttachment
from ._sticker import Sticker from ._sticker import Sticker

View File

@@ -2,17 +2,7 @@ import attr
import datetime import datetime
from ._common import log, attrs_default from ._common import log, attrs_default
from . import ( from . import _exception, _util, _graphql, _session, _threads, _message
_exception,
_util,
_graphql,
_session,
_user,
_page,
_group,
_thread,
_message,
)
from typing import Sequence, Iterable, Tuple, Optional, Set from typing import Sequence, Iterable, Tuple, Optional, Set
@@ -32,7 +22,7 @@ class Client:
#: The session to use when making requests. #: The session to use when making requests.
session = attr.ib(type=_session.Session) session = attr.ib(type=_session.Session)
def fetch_users(self) -> Sequence[_user.UserData]: def fetch_users(self) -> Sequence[_threads.UserData]:
"""Fetch users the client is currently chatting with. """Fetch users the client is currently chatting with.
This is very close to your friend list, with the follow differences: This is very close to your friend list, with the follow differences:
@@ -60,10 +50,10 @@ class Client:
if data["type"] not in ["user", "friend"] or data["id"] in ["0", 0]: if data["type"] not in ["user", "friend"] or data["id"] in ["0", 0]:
log.warning("Invalid user data %s", data) log.warning("Invalid user data %s", data)
continue # Skip invalid users continue # Skip invalid users
users.append(_user.UserData._from_all_fetch(self.session, data)) users.append(_threads.UserData._from_all_fetch(self.session, data))
return users return users
def search_for_users(self, name: str, limit: int) -> Iterable[_user.UserData]: def search_for_users(self, name: str, limit: int) -> Iterable[_threads.UserData]:
"""Find and get users by their name. """Find and get users by their name.
The returned users are ordered by relevance. The returned users are ordered by relevance.
@@ -85,11 +75,11 @@ class Client:
) )
return ( return (
_user.UserData._from_graphql(self.session, node) _threads.UserData._from_graphql(self.session, node)
for node in j[name]["users"]["nodes"] for node in j[name]["users"]["nodes"]
) )
def search_for_pages(self, name: str, limit: int) -> Iterable[_page.PageData]: def search_for_pages(self, name: str, limit: int) -> Iterable[_threads.PageData]:
"""Find and get pages by their name. """Find and get pages by their name.
The returned pages are ordered by relevance. The returned pages are ordered by relevance.
@@ -111,11 +101,11 @@ class Client:
) )
return ( return (
_page.PageData._from_graphql(self.session, node) _threads.PageData._from_graphql(self.session, node)
for node in j[name]["pages"]["nodes"] for node in j[name]["pages"]["nodes"]
) )
def search_for_groups(self, name: str, limit: int) -> Iterable[_group.GroupData]: def search_for_groups(self, name: str, limit: int) -> Iterable[_threads.GroupData]:
"""Find and get group threads by their name. """Find and get group threads by their name.
The returned groups are ordered by relevance. The returned groups are ordered by relevance.
@@ -137,11 +127,11 @@ class Client:
) )
return ( return (
_group.GroupData._from_graphql(self.session, node) _threads.GroupData._from_graphql(self.session, node)
for node in j["viewer"]["groups"]["nodes"] for node in j["viewer"]["groups"]["nodes"]
) )
def search_for_threads(self, name: str, limit: int) -> Iterable[_thread.ThreadABC]: def search_for_threads(self, name: str, limit: int) -> Iterable[_threads.ThreadABC]:
"""Find and get threads by their name. """Find and get threads by their name.
The returned threads are ordered by relevance. The returned threads are ordered by relevance.
@@ -165,12 +155,12 @@ class Client:
for node in j[name]["threads"]["nodes"]: for node in j[name]["threads"]["nodes"]:
if node["__typename"] == "User": if node["__typename"] == "User":
yield _user.UserData._from_graphql(self.session, node) yield _threads.UserData._from_graphql(self.session, node)
elif node["__typename"] == "MessageThread": elif node["__typename"] == "MessageThread":
# MessageThread => Group thread # MessageThread => Group thread
yield _group.GroupData._from_graphql(self.session, node) yield _threads.GroupData._from_graphql(self.session, node)
elif node["__typename"] == "Page": elif node["__typename"] == "Page":
yield _page.PageData._from_graphql(self.session, node) yield _threads.PageData._from_graphql(self.session, node)
elif node["__typename"] == "Group": elif node["__typename"] == "Group":
# We don't handle Facebook "Groups" # We don't handle Facebook "Groups"
pass pass
@@ -189,17 +179,17 @@ class Client:
for node in j["graphql_payload"]["message_threads"]: for node in j["graphql_payload"]["message_threads"]:
type_ = node["thread_type"] type_ = node["thread_type"]
if type_ == "GROUP": if type_ == "GROUP":
thread = _group.Group( thread = _threads.Group(
session=self.session, id=node["thread_key"]["thread_fbid"] session=self.session, id=node["thread_key"]["thread_fbid"]
) )
elif type_ == "ONE_TO_ONE": elif type_ == "ONE_TO_ONE":
thread = _thread.Thread( thread = _threads.Thread(
session=self.session, id=node["thread_key"]["other_user_id"] session=self.session, id=node["thread_key"]["other_user_id"]
) )
# if True: # TODO: This check! # if True: # TODO: This check!
# thread = _user.UserData._from_graphql(self.session, node) # thread = _threads.UserData._from_graphql(self.session, node)
# else: # else:
# thread = _page.PageData._from_graphql(self.session, node) # thread = _threads.PageData._from_graphql(self.session, node)
else: else:
thread = None thread = None
log.warning("Unknown thread type %s, data: %s", type_, node) log.warning("Unknown thread type %s, data: %s", type_, node)
@@ -213,7 +203,7 @@ class Client:
def search_messages( def search_messages(
self, query: str, limit: Optional[int] self, query: str, limit: Optional[int]
) -> Iterable[Tuple[_thread.ThreadABC, int]]: ) -> Iterable[Tuple[_threads.ThreadABC, int]]:
"""Search for messages in all threads. """Search for messages in all threads.
Intended to be used alongside `ThreadABC.search_messages` Intended to be used alongside `ThreadABC.search_messages`
@@ -285,7 +275,7 @@ class Client:
log.debug(entries) log.debug(entries)
return entries return entries
def fetch_thread_info(self, ids: Iterable[str]) -> Iterable[_thread.ThreadABC]: def fetch_thread_info(self, ids: Iterable[str]) -> Iterable[_threads.ThreadABC]:
"""Fetch threads' info from IDs, unordered. """Fetch threads' info from IDs, unordered.
Warning: Warning:
@@ -336,7 +326,7 @@ class Client:
entry = entry["message_thread"] entry = entry["message_thread"]
if entry.get("thread_type") == "GROUP": if entry.get("thread_type") == "GROUP":
_id = entry["thread_key"]["thread_fbid"] _id = entry["thread_key"]["thread_fbid"]
yield _group.GroupData._from_graphql(self.session, entry) yield _threads.GroupData._from_graphql(self.session, entry)
elif entry.get("thread_type") == "ONE_TO_ONE": elif entry.get("thread_type") == "ONE_TO_ONE":
_id = entry["thread_key"]["other_user_id"] _id = entry["thread_key"]["other_user_id"]
if pages_and_users.get(_id) is None: if pages_and_users.get(_id) is None:
@@ -345,9 +335,9 @@ class Client:
) )
entry.update(pages_and_users[_id]) entry.update(pages_and_users[_id])
if "first_name" in entry: if "first_name" in entry:
yield _user.UserData._from_graphql(self.session, entry) yield _threads.UserData._from_graphql(self.session, entry)
else: else:
yield _page.PageData._from_graphql(self.session, entry) yield _threads.PageData._from_graphql(self.session, entry)
else: else:
raise _exception.ParseError("Unknown thread type", data=entry) raise _exception.ParseError("Unknown thread type", data=entry)
@@ -367,9 +357,9 @@ class Client:
for node in j["viewer"]["message_threads"]["nodes"]: for node in j["viewer"]["message_threads"]["nodes"]:
_type = node.get("thread_type") _type = node.get("thread_type")
if _type == "GROUP": if _type == "GROUP":
rtn.append(_group.GroupData._from_graphql(self.session, node)) rtn.append(_threads.GroupData._from_graphql(self.session, node))
elif _type == "ONE_TO_ONE": elif _type == "ONE_TO_ONE":
rtn.append(_user.UserData._from_thread_fetch(self.session, node)) rtn.append(_threads.UserData._from_thread_fetch(self.session, node))
else: else:
rtn.append(None) rtn.append(None)
log.warning("Unknown thread type: %s, data: %s", _type, node) log.warning("Unknown thread type: %s, data: %s", _type, node)
@@ -378,8 +368,8 @@ class Client:
def fetch_threads( def fetch_threads(
self, self,
limit: Optional[int], limit: Optional[int],
location: _thread.ThreadLocation = _thread.ThreadLocation.INBOX, location: _threads.ThreadLocation = _threads.ThreadLocation.INBOX,
) -> Iterable[_thread.ThreadABC]: ) -> Iterable[_threads.ThreadABC]:
"""Fetch the client's thread list. """Fetch the client's thread list.
Args: Args:
@@ -422,7 +412,7 @@ class Client:
if not before: if not before:
raise ValueError("Too many unknown threads.") raise ValueError("Too many unknown threads.")
def fetch_unread(self) -> Sequence[_thread.ThreadABC]: def fetch_unread(self) -> Sequence[_threads.ThreadABC]:
"""Fetch unread threads. """Fetch unread threads.
Warning: Warning:
@@ -439,13 +429,14 @@ class Client:
result = j["unread_thread_fbids"][0] result = j["unread_thread_fbids"][0]
# TODO: Parse Pages? # TODO: Parse Pages?
return [ return [
_group.Group(session=self.session, id=id_) for id_ in result["thread_fbids"] _threads.Group(session=self.session, id=id_)
for id_ in result["thread_fbids"]
] + [ ] + [
_user.User(session=self.session, id=id_) _threads.User(session=self.session, id=id_)
for id_ in result["other_user_fbids"] for id_ in result["other_user_fbids"]
] ]
def fetch_unseen(self) -> Sequence[_thread.ThreadABC]: def fetch_unseen(self) -> Sequence[_threads.ThreadABC]:
"""Fetch unseen / new threads. """Fetch unseen / new threads.
Warning: Warning:
@@ -456,9 +447,10 @@ class Client:
result = j["unseen_thread_fbids"][0] result = j["unseen_thread_fbids"][0]
# TODO: Parse Pages? # TODO: Parse Pages?
return [ return [
_group.Group(session=self.session, id=id_) for id_ in result["thread_fbids"] _threads.Group(session=self.session, id=id_)
for id_ in result["thread_fbids"]
] + [ ] + [
_user.User(session=self.session, id=id_) _threads.User(session=self.session, id=id_)
for id_ in result["other_user_fbids"] for id_ in result["other_user_fbids"]
] ]
@@ -529,7 +521,9 @@ class Client:
j = self.session._payload_post("/ajax/mercury/change_read_status.php", data) j = self.session._payload_post("/ajax/mercury/change_read_status.php", data)
def mark_as_read(self, threads: Iterable[_thread.ThreadABC], at: datetime.datetime): def mark_as_read(
self, threads: Iterable[_threads.ThreadABC], at: datetime.datetime
):
"""Mark threads as read. """Mark threads as read.
All messages inside the specified threads will be marked as read. All messages inside the specified threads will be marked as read.
@@ -541,7 +535,7 @@ class Client:
return self._read_status(True, threads, at) return self._read_status(True, threads, at)
def mark_as_unread( def mark_as_unread(
self, threads: Iterable[_thread.ThreadABC], at: datetime.datetime self, threads: Iterable[_threads.ThreadABC], at: datetime.datetime
): ):
"""Mark threads as unread. """Mark threads as unread.
@@ -560,7 +554,7 @@ class Client:
) )
def move_threads( def move_threads(
self, location: _thread.ThreadLocation, threads: Iterable[_thread.ThreadABC] self, location: _threads.ThreadLocation, threads: Iterable[_threads.ThreadABC]
): ):
"""Move threads to specified location. """Move threads to specified location.
@@ -568,10 +562,10 @@ class Client:
location: INBOX, PENDING, ARCHIVED or OTHER location: INBOX, PENDING, ARCHIVED or OTHER
threads: Threads to move threads: Threads to move
""" """
if location == _thread.ThreadLocation.PENDING: if location == _threads.ThreadLocation.PENDING:
location = _thread.ThreadLocation.OTHER location = _threads.ThreadLocation.OTHER
if location == _thread.ThreadLocation.ARCHIVED: if location == _threads.ThreadLocation.ARCHIVED:
data_archive = {} data_archive = {}
data_unpin = {} data_unpin = {}
for thread in threads: for thread in threads:
@@ -587,9 +581,9 @@ class Client:
data = {} data = {}
for i, thread in enumerate(threads): for i, thread in enumerate(threads):
data["{}[{}]".format(location.name.lower(), i)] = thread.id data["{}[{}]".format(location.name.lower(), i)] = thread.id
j = self.session._payload_post("/ajax/mercury/move_thread.php", data) j = self.session._payload_post("/ajax/mercury/move_threads.php", data)
def delete_threads(self, threads: Iterable[_thread.ThreadABC]): def delete_threads(self, threads: Iterable[_threads.ThreadABC]):
"""Bulk delete threads. """Bulk delete threads.
Args: Args:
@@ -608,7 +602,7 @@ class Client:
"/ajax/mercury/change_pinned_status.php?dpr=1", data_unpin "/ajax/mercury/change_pinned_status.php?dpr=1", data_unpin
) )
j_delete = self.session._payload_post( j_delete = self.session._payload_post(
"/ajax/mercury/delete_thread.php?dpr=1", data_delete "/ajax/mercury/delete_threads.php?dpr=1", data_delete
) )
def delete_messages(self, messages: Iterable[_message.Message]): def delete_messages(self, messages: Iterable[_message.Message]):

View File

@@ -6,7 +6,7 @@ from ._client_payload import *
from ._delta_class import * from ._delta_class import *
from ._delta_type import * from ._delta_type import *
from .. import _exception, _util, _user, _group, _thread from .. import _exception, _util, _threads
from typing import Mapping from typing import Mapping
@@ -20,15 +20,15 @@ class Typing(ThreadEvent):
@classmethod @classmethod
def _parse_orca(cls, session, data): def _parse_orca(cls, session, data):
author = _user.User(session=session, id=str(data["sender_fbid"])) author = _threads.User(session=session, id=str(data["sender_fbid"]))
status = data["state"] == 1 status = data["state"] == 1
return cls(author=author, thread=author, status=status) return cls(author=author, thread=author, status=status)
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
# TODO: Rename this method # TODO: Rename this method
author = _user.User(session=session, id=str(data["sender_fbid"])) author = _threads.User(session=session, id=str(data["sender_fbid"]))
thread = _group.Group(session=session, id=str(data["thread"])) thread = _threads.Group(session=session, id=str(data["thread"]))
status = data["state"] == 1 status = data["state"] == 1
return cls(author=author, thread=thread, status=status) return cls(author=author, thread=thread, status=status)
@@ -38,11 +38,11 @@ class FriendRequest(Event):
"""Somebody sent a friend request.""" """Somebody sent a friend request."""
#: The user that sent the request #: The user that sent the request
author = attr.ib(type=_user.User) author = attr.ib(type="_threads.User")
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
author = _user.User(session=session, id=str(data["from"])) author = _threads.User(session=session, id=str(data["from"]))
return cls(author=author) return cls(author=author)
@@ -56,14 +56,15 @@ class Presence(Event):
# TODO: Document this better! # TODO: Document this better!
#: User ids mapped to their active status #: User ids mapped to their active status
statuses = attr.ib(type=Mapping[str, _user.ActiveStatus]) statuses = attr.ib(type=Mapping[str, _threads.ActiveStatus])
#: ``True`` if the list is fully updated and ``False`` if it's partially updated #: ``True`` if the list is fully updated and ``False`` if it's partially updated
full = attr.ib(type=bool) full = attr.ib(type=bool)
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
statuses = { statuses = {
str(d["u"]): _user.ActiveStatus._from_orca_presence(d) for d in data["list"] str(d["u"]): _threads.ActiveStatus._from_orca_presence(d)
for d in data["list"]
} }
return cls(statuses=statuses, full=data["list_type"] == "full") return cls(statuses=statuses, full=data["list_type"] == "full")

View File

@@ -1,7 +1,7 @@
import attr import attr
import datetime import datetime
from ._common import attrs_event, UnknownEvent, ThreadEvent from ._common import attrs_event, UnknownEvent, ThreadEvent
from .. import _exception, _util, _user, _message from .. import _exception, _util, _threads, _message
from typing import Optional from typing import Optional
@@ -25,7 +25,7 @@ class ReactionEvent(ThreadEvent):
def _parse(cls, session, data): def _parse(cls, session, data):
thread = cls._get_thread(session, data) thread = cls._get_thread(session, data)
return cls( return cls(
author=_user.User(session=session, id=str(data["userId"])), author=_threads.User(session=session, id=str(data["userId"])),
thread=thread, thread=thread,
message=_message.Message(thread=thread, id=data["messageId"]), message=_message.Message(thread=thread, id=data["messageId"]),
reaction=data["reaction"] if data["action"] == 0 else None, reaction=data["reaction"] if data["action"] == 0 else None,
@@ -40,7 +40,7 @@ class UserStatusEvent(ThreadEvent):
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
return cls( return cls(
author=_user.User(session=session, id=str(data["actorFbid"])), author=_threads.User(session=session, id=str(data["actorFbid"])),
thread=cls._get_thread(session, data), thread=cls._get_thread(session, data),
blocked=not data["canViewerReply"], blocked=not data["canViewerReply"],
) )
@@ -59,7 +59,7 @@ class LiveLocationEvent(ThreadEvent):
thread = cls._get_thread(session, data) thread = cls._get_thread(session, data)
for location_data in data["messageLiveLocations"]: for location_data in data["messageLiveLocations"]:
message = _message.Message(thread=thread, id=data["messageId"]) message = _message.Message(thread=thread, id=data["messageId"])
author = _user.User(session=session, id=str(location_data["senderId"])) author = _threads.User(session=session, id=str(location_data["senderId"]))
location = _location.LiveLocationAttachment._from_pull(location_data) location = _location.LiveLocationAttachment._from_pull(location_data)
return None return None
@@ -78,7 +78,7 @@ class UnsendEvent(ThreadEvent):
def _parse(cls, session, data): def _parse(cls, session, data):
thread = cls._get_thread(session, data) thread = cls._get_thread(session, data)
return cls( return cls(
author=_user.User(session=session, id=str(data["senderID"])), author=_threads.User(session=session, id=str(data["senderID"])),
thread=thread, thread=thread,
message=_message.Message(thread=thread, id=data["messageID"]), message=_message.Message(thread=thread, id=data["messageID"]),
at=_util.millis_to_datetime(data["deletionTimestamp"]), at=_util.millis_to_datetime(data["deletionTimestamp"]),
@@ -99,7 +99,7 @@ class MessageReplyEvent(ThreadEvent):
metadata = data["message"]["messageMetadata"] metadata = data["message"]["messageMetadata"]
thread = cls._get_thread(session, metadata) thread = cls._get_thread(session, metadata)
return cls( return cls(
author=_user.User(session=session, id=str(metadata["actorFbId"])), author=_threads.User(session=session, id=str(metadata["actorFbId"])),
thread=thread, thread=thread,
message=_message.MessageData._from_reply(thread, data["message"]), message=_message.MessageData._from_reply(thread, data["message"]),
replied_to=_message.MessageData._from_reply( replied_to=_message.MessageData._from_reply(

View File

@@ -1,7 +1,7 @@
import attr import attr
import abc import abc
from .._common import kw_only from .._common import kw_only
from .. import _exception, _util, _thread, _group, _user, _message from .. import _exception, _util, _threads, _message
from typing import Any from typing import Any
@@ -38,9 +38,9 @@ class ThreadEvent(Event):
"""Represent an event that was done by a user/page in a thread.""" """Represent an event that was done by a user/page in a thread."""
#: The person who did the action #: The person who did the action
author = attr.ib(type=_user.User) # Or Union[User, Page]? author = attr.ib(type="_threads.User") # Or Union[User, Page]?
#: Thread that the action was done in #: Thread that the action was done in
thread = attr.ib(type=_thread.ThreadABC) thread = attr.ib(type="_threads.ThreadABC")
@staticmethod @staticmethod
def _get_thread(session, data): def _get_thread(session, data):
@@ -48,15 +48,15 @@ class ThreadEvent(Event):
key = data["threadKey"] key = data["threadKey"]
if "threadFbId" in key: if "threadFbId" in key:
return _group.Group(session=session, id=str(key["threadFbId"])) return _threads.Group(session=session, id=str(key["threadFbId"]))
elif "otherUserFbId" in key: elif "otherUserFbId" in key:
return _user.User(session=session, id=str(key["otherUserFbId"])) return _threads.User(session=session, id=str(key["otherUserFbId"]))
raise _exception.ParseError("Could not find thread data", data=data) raise _exception.ParseError("Could not find thread data", data=data)
@staticmethod @staticmethod
def _parse_metadata(session, data): def _parse_metadata(session, data):
metadata = data["messageMetadata"] metadata = data["messageMetadata"]
author = _user.User(session=session, id=metadata["actorFbId"]) author = _threads.User(session=session, id=metadata["actorFbId"])
thread = ThreadEvent._get_thread(session, metadata) thread = ThreadEvent._get_thread(session, metadata)
at = _util.millis_to_datetime(int(metadata["timestamp"])) at = _util.millis_to_datetime(int(metadata["timestamp"]))
return author, thread, at return author, thread, at

View File

@@ -1,7 +1,7 @@
import attr import attr
import datetime import datetime
from ._common import attrs_event, Event, UnknownEvent, ThreadEvent from ._common import attrs_event, Event, UnknownEvent, ThreadEvent
from .. import _util, _user, _group, _thread, _message from .. import _util, _threads, _message
from typing import Sequence, Optional from typing import Sequence, Optional
@@ -12,9 +12,9 @@ class PeopleAdded(ThreadEvent):
# TODO: Add message id # TODO: Add message id
thread = attr.ib(type=_group.Group) # Set the correct type thread = attr.ib(type="_threads.Group") # Set the correct type
#: The people who got added #: The people who got added
added = attr.ib(type=Sequence[_user.User]) added = attr.ib(type=Sequence["_threads.User"])
#: When the people were added #: When the people were added
at = attr.ib(type=datetime.datetime) at = attr.ib(type=datetime.datetime)
@@ -23,7 +23,7 @@ class PeopleAdded(ThreadEvent):
author, thread, at = cls._parse_metadata(session, data) author, thread, at = cls._parse_metadata(session, data)
added = [ added = [
# TODO: Parse user name # TODO: Parse user name
_user.User(session=session, id=x["userFbId"]) _threads.User(session=session, id=x["userFbId"])
for x in data["addedParticipants"] for x in data["addedParticipants"]
] ]
return cls(author=author, thread=thread, added=added, at=at) return cls(author=author, thread=thread, added=added, at=at)
@@ -35,7 +35,7 @@ class PersonRemoved(ThreadEvent):
# TODO: Add message id # TODO: Add message id
thread = attr.ib(type=_group.Group) # Set the correct type thread = attr.ib(type="_threads.Group") # Set the correct type
#: Person who got removed #: Person who got removed
removed = attr.ib(type=_message.Message) removed = attr.ib(type=_message.Message)
#: When the person were removed #: When the person were removed
@@ -44,7 +44,7 @@ class PersonRemoved(ThreadEvent):
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
author, thread, at = cls._parse_metadata(session, data) author, thread, at = cls._parse_metadata(session, data)
removed = _user.User(session=session, id=data["leftParticipantFbId"]) removed = _threads.User(session=session, id=data["leftParticipantFbId"])
return cls(author=author, thread=thread, removed=removed, at=at) return cls(author=author, thread=thread, removed=removed, at=at)
@@ -52,7 +52,7 @@ class PersonRemoved(ThreadEvent):
class TitleSet(ThreadEvent): class TitleSet(ThreadEvent):
"""Somebody changed a group's title.""" """Somebody changed a group's title."""
thread = attr.ib(type=_group.Group) # Set the correct type thread = attr.ib(type="_threads.Group") # Set the correct type
#: The new title #: The new title
title = attr.ib(type=str) title = attr.ib(type=str)
#: When the title was set #: When the title was set
@@ -77,7 +77,7 @@ class UnfetchedThreadEvent(Event):
# TODO: Present this in a way that users can fetch the changed group photo easily # TODO: Present this in a way that users can fetch the changed group photo easily
#: The thread the message was sent to #: The thread the message was sent to
thread = attr.ib(type=_thread.ThreadABC) thread = attr.ib(type="_threads.ThreadABC")
#: The message #: The message
message = attr.ib(type=Optional[_message.Message]) message = attr.ib(type=Optional[_message.Message])
@@ -103,7 +103,7 @@ class MessagesDelivered(ThreadEvent):
def _parse(cls, session, data): def _parse(cls, session, data):
thread = cls._get_thread(session, data) thread = cls._get_thread(session, data)
if "actorFbId" in data: if "actorFbId" in data:
author = _user.User(session=session, id=data["actorFbId"]) author = _threads.User(session=session, id=data["actorFbId"])
else: else:
author = thread author = thread
messages = [_message.Message(thread=thread, id=x) for x in data["messageIds"]] messages = [_message.Message(thread=thread, id=x) for x in data["messageIds"]]
@@ -116,22 +116,22 @@ class ThreadsRead(Event):
"""Somebody marked threads as read/seen.""" """Somebody marked threads as read/seen."""
#: The person who marked the threads as read #: The person who marked the threads as read
author = attr.ib(type=_thread.ThreadABC) author = attr.ib(type="_threads.ThreadABC")
#: The threads that were marked as read #: The threads that were marked as read
threads = attr.ib(type=Sequence[_thread.ThreadABC]) threads = attr.ib(type=Sequence["_threads.ThreadABC"])
#: When the threads were read #: When the threads were read
at = attr.ib(type=datetime.datetime) at = attr.ib(type=datetime.datetime)
@classmethod @classmethod
def _parse_read_receipt(cls, session, data): def _parse_read_receipt(cls, session, data):
author = _user.User(session=session, id=data["actorFbId"]) author = _threads.User(session=session, id=data["actorFbId"])
thread = ThreadEvent._get_thread(session, data) thread = ThreadEvent._get_thread(session, data)
at = _util.millis_to_datetime(int(data["actionTimestampMs"])) at = _util.millis_to_datetime(int(data["actionTimestampMs"]))
return cls(author=author, threads=[thread], at=at) return cls(author=author, threads=[thread], at=at)
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
author = _user.User(session=session, id=session.user_id) author = _threads.User(session=session, id=session.user_id)
threads = [ threads = [
ThreadEvent._get_thread(session, {"threadKey": x}) ThreadEvent._get_thread(session, {"threadKey": x})
for x in data["threadKeys"] for x in data["threadKeys"]
@@ -169,14 +169,14 @@ class ThreadFolder(Event):
# TODO: Finish this # TODO: Finish this
#: The created thread #: The created thread
thread = attr.ib(type=_thread.ThreadABC) thread = attr.ib(type="_threads.ThreadABC")
#: The folder/location #: The folder/location
folder = attr.ib(type=_thread.ThreadLocation) folder = attr.ib(type=_threads.ThreadLocation)
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
thread = ThreadEvent._get_thread(session, data) thread = ThreadEvent._get_thread(session, data)
folder = _thread.ThreadLocation._parse(data["folder"]) folder = _threads.ThreadLocation._parse(data["folder"])
return cls(thread=thread, folder=folder) return cls(thread=thread, folder=folder)
@@ -188,7 +188,7 @@ def parse_delta(session, data):
return PersonRemoved._parse(session, data) return PersonRemoved._parse(session, data)
elif class_ == "MarkFolderSeen": elif class_ == "MarkFolderSeen":
# TODO: Finish this # TODO: Finish this
folders = [_thread.ThreadLocation._parse(folder) for folder in data["folders"]] folders = [_threads.ThreadLocation._parse(folder) for folder in data["folders"]]
at = _util.millis_to_datetime(int(data["timestamp"])) at = _util.millis_to_datetime(int(data["timestamp"]))
return None return None
elif class_ == "ThreadName": elif class_ == "ThreadName":

View File

@@ -1,7 +1,7 @@
import attr import attr
import datetime import datetime
from ._common import attrs_event, Event, UnknownEvent, ThreadEvent from ._common import attrs_event, Event, UnknownEvent, ThreadEvent
from .. import _util, _user, _thread, _poll, _plan from .. import _util, _threads, _poll, _plan
from typing import Sequence, Optional from typing import Sequence, Optional
@@ -18,7 +18,7 @@ class ColorSet(ThreadEvent):
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
author, thread, at = cls._parse_metadata(session, data) author, thread, at = cls._parse_metadata(session, data)
color = _thread.ThreadABC._parse_color(data["untypedData"]["theme_color"]) color = _threads.ThreadABC._parse_color(data["untypedData"]["theme_color"])
return cls(author=author, thread=thread, color=color, at=at) return cls(author=author, thread=thread, color=color, at=at)
@@ -52,7 +52,9 @@ class NicknameSet(ThreadEvent):
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
author, thread, at = cls._parse_metadata(session, data) author, thread, at = cls._parse_metadata(session, data)
subject = _user.User(session=session, id=data["untypedData"]["participant_id"]) subject = _threads.User(
session=session, id=data["untypedData"]["participant_id"]
)
nickname = data["untypedData"]["nickname"] or None # None if "" nickname = data["untypedData"]["nickname"] or None # None if ""
return cls( return cls(
author=author, thread=thread, subject=subject, nickname=nickname, at=at author=author, thread=thread, subject=subject, nickname=nickname, at=at
@@ -64,14 +66,14 @@ class AdminsAdded(ThreadEvent):
"""Somebody added admins to a group.""" """Somebody added admins to a group."""
#: The people that were set as admins #: The people that were set as admins
added = attr.ib(type=Sequence[_user.User]) added = attr.ib(type=Sequence["_threads.User"])
#: When the admins were added #: When the admins were added
at = attr.ib(type=datetime.datetime) at = attr.ib(type=datetime.datetime)
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
author, thread, at = cls._parse_metadata(session, data) author, thread, at = cls._parse_metadata(session, data)
subject = _user.User(session=session, id=data["untypedData"]["TARGET_ID"]) subject = _threads.User(session=session, id=data["untypedData"]["TARGET_ID"])
return cls(author=author, thread=thread, added=[subject], at=at) return cls(author=author, thread=thread, added=[subject], at=at)
@@ -80,14 +82,14 @@ class AdminsRemoved(ThreadEvent):
"""Somebody removed admins from a group.""" """Somebody removed admins from a group."""
#: The people that were removed as admins #: The people that were removed as admins
removed = attr.ib(type=Sequence[_user.User]) removed = attr.ib(type=Sequence["_threads.User"])
#: When the admins were removed #: When the admins were removed
at = attr.ib(type=datetime.datetime) at = attr.ib(type=datetime.datetime)
@classmethod @classmethod
def _parse(cls, session, data): def _parse(cls, session, data):
author, thread, at = cls._parse_metadata(session, data) author, thread, at = cls._parse_metadata(session, data)
subject = _user.User(session=session, id=data["untypedData"]["TARGET_ID"]) subject = _threads.User(session=session, id=data["untypedData"]["TARGET_ID"])
return cls(author=author, thread=thread, removed=[subject], at=at) return cls(author=author, thread=thread, removed=[subject], at=at)

View File

@@ -12,7 +12,7 @@ from . import (
_file, _file,
_quick_reply, _quick_reply,
_sticker, _sticker,
_thread, _threads,
) )
from typing import Optional, Mapping, Sequence from typing import Optional, Mapping, Sequence
@@ -94,7 +94,7 @@ class Message:
""" """
#: The thread that this message belongs to. #: The thread that this message belongs to.
thread = attr.ib(type="_thread.ThreadABC") thread = attr.ib(type="_threads.ThreadABC")
#: The message ID. #: The message ID.
id = attr.ib(converter=str, type=str) id = attr.ib(converter=str, type=str)

View File

@@ -0,0 +1,4 @@
from ._abc import *
from ._group import *
from ._user import *
from ._page import *

View File

@@ -3,8 +3,17 @@ import attr
import collections import collections
import datetime import datetime
import enum import enum
from ._common import log, attrs_default, Image from .._common import log, attrs_default, Image
from . import _util, _exception, _session, _graphql, _attachment, _file, _plan, _message from .. import (
_util,
_exception,
_session,
_graphql,
_attachment,
_file,
_plan,
_message,
)
from typing import MutableMapping, Mapping, Any, Iterable, Tuple, Optional from typing import MutableMapping, Mapping, Any, Iterable, Tuple, Optional

View File

@@ -1,12 +1,14 @@
import attr import attr
import datetime import datetime
from ._common import attrs_default, Image from ._abc import ThreadABC
from . import _util, _session, _graphql, _plan, _thread, _user from . import _user
from .._common import attrs_default, Image
from .. import _util, _session, _graphql, _plan
from typing import Sequence, Iterable, Set, Mapping from typing import Sequence, Iterable, Set, Mapping
@attrs_default @attrs_default
class Group(_thread.ThreadABC): class Group(ThreadABC):
"""Represents a Facebook group. Implements `ThreadABC`. """Represents a Facebook group. Implements `ThreadABC`.
Example: Example:
@@ -245,7 +247,7 @@ class GroupData(Group):
@attrs_default @attrs_default
class NewGroup(_thread.ThreadABC): class NewGroup(ThreadABC):
"""Helper class to create new groups. """Helper class to create new groups.
TODO: Complete this! TODO: Complete this!
@@ -256,7 +258,7 @@ class NewGroup(_thread.ThreadABC):
#: The session to use when making requests. #: The session to use when making requests.
session = attr.ib(type=_session.Session) session = attr.ib(type=_session.Session)
#: The users that should be added to the group. #: The users that should be added to the group.
_users = attr.ib(type=Sequence[_user.User]) _users = attr.ib(type=Sequence["_user.User"])
@property @property
def id(self): def id(self):

View File

@@ -1,11 +1,12 @@
import attr import attr
import datetime import datetime
from ._common import attrs_default, Image from ._abc import ThreadABC
from . import _session, _plan, _thread from .._common import attrs_default, Image
from .. import _session, _plan
@attrs_default @attrs_default
class Page(_thread.ThreadABC): class Page(ThreadABC):
"""Represents a Facebook page. Implements `ThreadABC`. """Represents a Facebook page. Implements `ThreadABC`.
Example: Example:

View File

@@ -1,7 +1,8 @@
import attr import attr
import datetime import datetime
from ._common import log, attrs_default, Image from ._abc import ThreadABC
from . import _util, _session, _plan, _thread from .._common import log, attrs_default, Image
from .. import _util, _session, _plan
GENDERS = { GENDERS = {
@@ -35,7 +36,7 @@ GENDERS = {
@attrs_default @attrs_default
class User(_thread.ThreadABC): class User(ThreadABC):
"""Represents a Facebook user. Implements `ThreadABC`. """Represents a Facebook user. Implements `ThreadABC`.
Example: Example:

View File

@@ -1,5 +1,5 @@
import fbchat import fbchat
from fbchat._page import PageData from fbchat import PageData
def test_page_from_graphql(session): def test_page_from_graphql(session):

View File

@@ -1,7 +1,7 @@
import pytest import pytest
import datetime import datetime
import fbchat import fbchat
from fbchat._user import UserData, ActiveStatus from fbchat import UserData, ActiveStatus
def test_user_from_graphql(session): def test_user_from_graphql(session):