From 152f20027a74fc20c4dde904476faaf7534d9747 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 9 Jan 2020 00:35:43 +0100 Subject: [PATCH] Add ThreadABC helper, that'll contain functions that threads can call --- fbchat/__init__.py | 2 +- fbchat/_client.py | 8 ++---- fbchat/_group.py | 7 +++-- fbchat/_page.py | 10 ++++--- fbchat/_thread.py | 62 +++++++++++++++++++++++++++++++++++++------- fbchat/_user.py | 10 ++++--- tests/test_thread.py | 15 +++++++---- 7 files changed, 80 insertions(+), 34 deletions(-) diff --git a/fbchat/__init__.py b/fbchat/__init__.py index 4f88896..01b27c6 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -14,7 +14,7 @@ from . import _core, _util from ._core import Image from ._exception import FBchatException, FBchatFacebookError from ._session import Session -from ._thread import ThreadType, ThreadLocation, ThreadColor, Thread +from ._thread import ThreadType, ThreadLocation, ThreadColor, ThreadABC, Thread from ._user import TypingStatus, User, ActiveStatus from ._group import Group from ._page import Page diff --git a/fbchat/_client.py b/fbchat/_client.py index ff2d043..2a01d97 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -108,11 +108,6 @@ class Client: FETCH METHODS """ - def _forced_fetch(self, thread_id, mid): - params = {"thread_and_message_id": {"thread_id": thread_id, "message_id": mid}} - (j,) = self.graphql_requests(_graphql.from_doc_id("1768656253222505", params)) - return j - def fetch_threads(self, thread_location, before=None, after=None, limit=None): """Fetch all threads in ``thread_location``. @@ -1820,7 +1815,8 @@ class Client: self.on_unknown_messsage_type(msg=m) else: thread_id = str(delta["threadKey"]["threadFbId"]) - fetch_info = self._forced_fetch(thread_id, mid) + thread = Thread(session=self.session, id=thread_id) + fetch_info = thread._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"])) diff --git a/fbchat/_group.py b/fbchat/_group.py index d206259..d72eb04 100644 --- a/fbchat/_group.py +++ b/fbchat/_group.py @@ -1,13 +1,12 @@ import attr from ._core import attrs_default, Image -from . import _util, _session, _plan -from ._thread import Thread +from . import _util, _session, _plan, _thread from typing import Iterable @attrs_default -class Group(Thread): - """Represents a Facebook group. Inherits `Thread`.""" +class Group(_thread.ThreadABC): + """Represents a Facebook group. Implements `ThreadABC`.""" #: The session to use when making requests. session = attr.ib(type=_session.Session) diff --git a/fbchat/_page.py b/fbchat/_page.py index 9310df4..32d97a1 100644 --- a/fbchat/_page.py +++ b/fbchat/_page.py @@ -1,12 +1,11 @@ import attr from ._core import attrs_default, Image -from . import _session, _plan -from ._thread import Thread +from . import _session, _plan, _thread @attrs_default -class Page(Thread): - """Represents a Facebook page. Inherits `Thread`.""" +class Page(_thread.ThreadABC): + """Represents a Facebook page. Implements `ThreadABC`.""" #: The session to use when making requests. session = attr.ib(type=_session.Session) @@ -33,6 +32,9 @@ class Page(Thread): #: The page's category category = attr.ib(None) + def _to_send_data(self): + return {"other_user_fbid": self.id} + @classmethod def _from_graphql(cls, session, data): if data.get("profile_picture") is None: diff --git a/fbchat/_thread.py b/fbchat/_thread.py index 643a979..87cd1cb 100644 --- a/fbchat/_thread.py +++ b/fbchat/_thread.py @@ -1,6 +1,8 @@ +import abc import attr from ._core import attrs_default, Enum, Image from . import _session +from typing import MutableMapping, Any class ThreadType(Enum): @@ -68,17 +70,39 @@ class ThreadColor(Enum): return cls._extend_if_invalid(value) -@attrs_default -class Thread: - """Represents a Facebook thread.""" +class ThreadABC(metaclass=abc.ABCMeta): + """Implemented by thread-like classes. - #: The session to use when making requests. - session = attr.ib(type=_session.Session) - #: The unique identifier of the thread. - id = attr.ib(converter=str) + This is private to implement. + """ + + @property + @abc.abstractmethod + def session(self) -> _session.Session: + """The session to use when making requests.""" + raise NotImplementedError + + @property + @abc.abstractmethod + def id(self) -> str: + """The unique identifier of the thread.""" + raise NotImplementedError + + @abc.abstractmethod + def _to_send_data(self) -> MutableMapping[str, str]: + raise NotImplementedError + + def _forced_fetch(self, message_id: str) -> dict: + params = { + "thread_and_message_id": {"thread_id": self.id, "message_id": message_id} + } + (j,) = self.session._graphql_requests( + _graphql.from_doc_id("1768656253222505", params) + ) + return j @staticmethod - def _parse_customization_info(data): + def _parse_customization_info(data: Any) -> MutableMapping[str, Any]: if data is None or data.get("customization_info") is None: return {} info = data["customization_info"] @@ -110,6 +134,24 @@ class Thread: rtn["own_nickname"] = pc[1].get("nickname") return rtn + +@attrs_default +class Thread(ThreadABC): + """Represents a Facebook thread, where the actual type is unknown. + + Implements parts of `ThreadABC`, call the method to figure out if your use case is + supported. Otherwise, you'll have to use an `User`/`Group`/`Page` object. + + Note: This list may change in minor versions! + """ + + #: The session to use when making requests. + session = attr.ib(type=_session.Session) + #: The unique identifier of the thread. + id = attr.ib(converter=str) + def _to_send_data(self): - # TODO: Only implement this in subclasses - return {"other_user_fbid": self.id} + raise NotImplementedError( + "The method you called is not supported on raw Thread objects." + " Please use an appropriate User/Group/Page object instead!" + ) diff --git a/fbchat/_user.py b/fbchat/_user.py index 93ea274..d1afd60 100644 --- a/fbchat/_user.py +++ b/fbchat/_user.py @@ -1,7 +1,6 @@ import attr from ._core import attrs_default, Enum, Image -from . import _util, _session, _plan -from ._thread import Thread +from . import _util, _session, _plan, _thread GENDERS = { @@ -42,8 +41,8 @@ class TypingStatus(Enum): @attrs_default -class User(Thread): - """Represents a Facebook user. Inherits `Thread`.""" +class User(_thread.ThreadABC): + """Represents a Facebook user. Implements `ThreadABC`.""" #: The session to use when making requests. session = attr.ib(type=_session.Session) @@ -80,6 +79,9 @@ class User(Thread): #: The default emoji emoji = attr.ib(None) + def _to_send_data(self): + return {"other_user_fbid": self.id} + def confirm_friend_request(self): """Confirm a friend request, adding the user to your friend list.""" data = {"to_friend": self.id, "action": "confirm"} diff --git a/tests/test_thread.py b/tests/test_thread.py index baea7ad..53cf7b5 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -1,6 +1,6 @@ import pytest import fbchat -from fbchat._thread import ThreadType, ThreadColor, Thread +from fbchat import ThreadType, ThreadColor, ThreadABC, Thread def test_thread_type_to_class(): @@ -19,8 +19,8 @@ def test_thread_color_from_graphql(): def test_thread_parse_customization_info_empty(): - assert {} == Thread._parse_customization_info(None) - assert {} == Thread._parse_customization_info({"customization_info": None}) + assert {} == ThreadABC._parse_customization_info(None) + assert {} == ThreadABC._parse_customization_info({"customization_info": None}) def test_thread_parse_customization_info_group(): @@ -43,7 +43,7 @@ def test_thread_parse_customization_info_group(): "color": ThreadColor.BRILLIANT_ROSE, "nicknames": {"123456789": "A", "987654321": "B"}, } - assert expected == Thread._parse_customization_info(data) + assert expected == ThreadABC._parse_customization_info(data) def test_thread_parse_customization_info_user(): @@ -62,4 +62,9 @@ def test_thread_parse_customization_info_user(): # ... Other irrelevant fields } expected = {"emoji": None, "color": None, "own_nickname": "A", "nickname": "B"} - assert expected == Thread._parse_customization_info(data) + assert expected == ThreadABC._parse_customization_info(data) + + +def test_thread_create_and_implements_thread_abc(session): + thread = Thread(session=session, id="123") + assert thread._parse_customization_info