From 4015bed474fb644edf56faf6ad1b635ee80c2a39 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 23 Jan 2020 15:15:09 +0100 Subject: [PATCH] Move ThreadLocation, ActiveStatus and Image to _models/ folder --- fbchat/__init__.py | 6 +-- fbchat/_client.py | 10 ++--- fbchat/_common.py | 42 ------------------ fbchat/_events/__init__.py | 6 +-- fbchat/_events/_delta_class.py | 6 +-- fbchat/_models/__init__.py | 1 + fbchat/_models/_attachment.py | 3 +- fbchat/_models/_common.py | 79 +++++++++++++++++++++++++++++++++ fbchat/_models/_file.py | 4 +- fbchat/_models/_location.py | 4 +- fbchat/_models/_quick_reply.py | 2 +- fbchat/_models/_sticker.py | 4 +- fbchat/_threads/_abc.py | 16 +------ fbchat/_threads/_group.py | 6 +-- fbchat/_threads/_page.py | 6 +-- fbchat/_threads/_user.py | 29 +++--------- tests/models/test_attachment.py | 12 ++--- tests/models/test_file.py | 36 ++++++++------- tests/models/test_location.py | 4 +- tests/models/test_sticker.py | 6 +-- 20 files changed, 146 insertions(+), 136 deletions(-) create mode 100644 fbchat/_models/_common.py diff --git a/fbchat/__init__.py b/fbchat/__init__.py index c290856..9d5e8bb 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -15,7 +15,6 @@ _logging.getLogger(__name__).addHandler(_logging.NullHandler()) # The order of these is somewhat significant, e.g. User has to be imported after Thread! from . import _common, _util -from ._common import Image from ._exception import ( FacebookError, HTTPError, @@ -28,12 +27,10 @@ from ._exception import ( ) from ._session import Session from ._threads import ( - ThreadLocation, ThreadABC, Thread, User, UserData, - ActiveStatus, Group, GroupData, Page, @@ -42,6 +39,9 @@ from ._threads import ( # Models from ._models import ( + Image, + ThreadLocation, + ActiveStatus, Attachment, UnsentMessage, ShareAttachment, diff --git a/fbchat/_client.py b/fbchat/_client.py index 466406b..838e004 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -368,7 +368,7 @@ class Client: def fetch_threads( self, limit: Optional[int], - location: _threads.ThreadLocation = _threads.ThreadLocation.INBOX, + location: _models.ThreadLocation = _models.ThreadLocation.INBOX, ) -> Iterable[_threads.ThreadABC]: """Fetch the client's thread list. @@ -554,7 +554,7 @@ class Client: ) def move_threads( - self, location: _threads.ThreadLocation, threads: Iterable[_threads.ThreadABC] + self, location: _models.ThreadLocation, threads: Iterable[_threads.ThreadABC] ): """Move threads to specified location. @@ -562,10 +562,10 @@ class Client: location: INBOX, PENDING, ARCHIVED or OTHER threads: Threads to move """ - if location == _threads.ThreadLocation.PENDING: - location = _threads.ThreadLocation.OTHER + if location == _models.ThreadLocation.PENDING: + location = _models.ThreadLocation.OTHER - if location == _threads.ThreadLocation.ARCHIVED: + if location == _models.ThreadLocation.ARCHIVED: data_archive = {} data_unpin = {} for thread in threads: diff --git a/fbchat/_common.py b/fbchat/_common.py index 772e9a0..7a9b943 100644 --- a/fbchat/_common.py +++ b/fbchat/_common.py @@ -9,45 +9,3 @@ kw_only = sys.version_info[:2] > (3, 5) #: Default attrs settings for classes attrs_default = attr.s(frozen=True, slots=True, kw_only=kw_only) - - -@attrs_default -class Image: - #: URL to the image - url = attr.ib(type=str) - #: Width of the image - width = attr.ib(None, type=int) - #: Height of the image - height = attr.ib(None, type=int) - - @classmethod - def _from_uri(cls, data): - return cls( - url=data["uri"], - width=int(data["width"]) if data.get("width") else None, - height=int(data["height"]) if data.get("height") else None, - ) - - @classmethod - def _from_url(cls, data): - return cls( - url=data["url"], - width=int(data["width"]) if data.get("width") else None, - height=int(data["height"]) if data.get("height") else None, - ) - - @classmethod - def _from_uri_or_none(cls, data): - if data is None: - return None - if data.get("uri") is None: - return None - return cls._from_uri(data) - - @classmethod - def _from_url_or_none(cls, data): - if data is None: - return None - if data.get("url") is None: - return None - return cls._from_url(data) diff --git a/fbchat/_events/__init__.py b/fbchat/_events/__init__.py index 09da365..dbb1cbb 100644 --- a/fbchat/_events/__init__.py +++ b/fbchat/_events/__init__.py @@ -5,7 +5,7 @@ from ._client_payload import * from ._delta_class import * from ._delta_type import * -from .. import _exception, _threads +from .. import _exception, _threads, _models from typing import Mapping @@ -55,14 +55,14 @@ class Presence(Event): # TODO: Document this better! #: User ids mapped to their active status - statuses = attr.ib(type=Mapping[str, _threads.ActiveStatus]) + statuses = attr.ib(type=Mapping[str, "_models.ActiveStatus"]) #: ``True`` if the list is fully updated and ``False`` if it's partially updated full = attr.ib(type=bool) @classmethod def _parse(cls, session, data): statuses = { - str(d["u"]): _threads.ActiveStatus._from_orca_presence(d) + str(d["u"]): _models.ActiveStatus._from_orca_presence(d) for d in data["list"] } return cls(statuses=statuses, full=data["list_type"] == "full") diff --git a/fbchat/_events/_delta_class.py b/fbchat/_events/_delta_class.py index 94ed8f8..704caf0 100644 --- a/fbchat/_events/_delta_class.py +++ b/fbchat/_events/_delta_class.py @@ -171,12 +171,12 @@ class ThreadFolder(Event): #: The created thread thread = attr.ib(type="_threads.ThreadABC") #: The folder/location - folder = attr.ib(type=_threads.ThreadLocation) + folder = attr.ib(type="_models.ThreadLocation") @classmethod def _parse(cls, session, data): thread = cls._get_thread(session, data) - folder = _threads.ThreadLocation._parse(data["folder"]) + folder = _models.ThreadLocation._parse(data["folder"]) return cls(thread=thread, folder=folder) @@ -190,7 +190,7 @@ def parse_delta(session, data): return PersonRemoved._parse(session, data) elif class_ == "MarkFolderSeen": # TODO: Finish this - folders = [_threads.ThreadLocation._parse(folder) for folder in data["folders"]] + folders = [_models.ThreadLocation._parse(folder) for folder in data["folders"]] at = _util.millis_to_datetime(int(data["timestamp"])) return None elif class_ == "ThreadName": diff --git a/fbchat/_models/__init__.py b/fbchat/_models/__init__.py index de13669..d8e5044 100644 --- a/fbchat/_models/__init__.py +++ b/fbchat/_models/__init__.py @@ -1,3 +1,4 @@ +from ._common import * from ._attachment import * from ._file import * from ._location import * diff --git a/fbchat/_models/_attachment.py b/fbchat/_models/_attachment.py index c68fa64..315771f 100644 --- a/fbchat/_models/_attachment.py +++ b/fbchat/_models/_attachment.py @@ -1,5 +1,6 @@ import attr -from .._common import attrs_default, Image +from . import Image +from .._common import attrs_default from .. import _util from typing import Sequence diff --git a/fbchat/_models/_common.py b/fbchat/_models/_common.py new file mode 100644 index 0000000..a99e011 --- /dev/null +++ b/fbchat/_models/_common.py @@ -0,0 +1,79 @@ +import attr +import datetime +import enum +from .._common import attrs_default +from .. import _util + + +class ThreadLocation(enum.Enum): + """Used to specify where a thread is located (inbox, pending, archived, other).""" + + INBOX = "INBOX" + PENDING = "PENDING" + ARCHIVED = "ARCHIVED" + OTHER = "OTHER" + + @classmethod + def _parse(cls, value: str): + return cls(value.lstrip("FOLDER_")) + + +@attrs_default +class ActiveStatus: + #: Whether the user is active now + active = attr.ib(None, type=bool) + #: Datetime when the user was last active + last_active = attr.ib(None, type=datetime.datetime) + #: Whether the user is playing Messenger game now + in_game = attr.ib(None, type=bool) + + @classmethod + def _from_orca_presence(cls, data): + # TODO: Handle `c` and `vc` keys (Probably some binary data) + return cls( + active=data["p"] in [2, 3], + last_active=_util.seconds_to_datetime(data["l"]) if "l" in data else None, + in_game=None, + ) + + +@attrs_default +class Image: + #: URL to the image + url = attr.ib(type=str) + #: Width of the image + width = attr.ib(None, type=int) + #: Height of the image + height = attr.ib(None, type=int) + + @classmethod + def _from_uri(cls, data): + return cls( + url=data["uri"], + width=int(data["width"]) if data.get("width") else None, + height=int(data["height"]) if data.get("height") else None, + ) + + @classmethod + def _from_url(cls, data): + return cls( + url=data["url"], + width=int(data["width"]) if data.get("width") else None, + height=int(data["height"]) if data.get("height") else None, + ) + + @classmethod + def _from_uri_or_none(cls, data): + if data is None: + return None + if data.get("uri") is None: + return None + return cls._from_uri(data) + + @classmethod + def _from_url_or_none(cls, data): + if data is None: + return None + if data.get("url") is None: + return None + return cls._from_url(data) diff --git a/fbchat/_models/_file.py b/fbchat/_models/_file.py index 03243e7..0aec26c 100644 --- a/fbchat/_models/_file.py +++ b/fbchat/_models/_file.py @@ -1,7 +1,7 @@ import attr import datetime -from ._attachment import Attachment -from .._common import attrs_default, Image +from . import Image, Attachment +from .._common import attrs_default from .. import _util from typing import Set diff --git a/fbchat/_models/_location.py b/fbchat/_models/_location.py index 7172819..a4d46b2 100644 --- a/fbchat/_models/_location.py +++ b/fbchat/_models/_location.py @@ -1,6 +1,6 @@ import attr -from ._attachment import Attachment -from .._common import attrs_default, Image +from . import Image, Attachment +from .._common import attrs_default from .. import _util diff --git a/fbchat/_models/_quick_reply.py b/fbchat/_models/_quick_reply.py index a833b28..8d41db7 100644 --- a/fbchat/_models/_quick_reply.py +++ b/fbchat/_models/_quick_reply.py @@ -1,5 +1,5 @@ import attr -from ._attachment import Attachment +from . import Attachment from .._common import attrs_default from typing import Any diff --git a/fbchat/_models/_sticker.py b/fbchat/_models/_sticker.py index 6505b51..e6fa829 100644 --- a/fbchat/_models/_sticker.py +++ b/fbchat/_models/_sticker.py @@ -1,6 +1,6 @@ import attr -from ._attachment import Attachment -from .._common import attrs_default, Image +from . import Image, Attachment +from .._common import attrs_default @attrs_default diff --git a/fbchat/_threads/_abc.py b/fbchat/_threads/_abc.py index 8adb500..6dd6f56 100644 --- a/fbchat/_threads/_abc.py +++ b/fbchat/_threads/_abc.py @@ -2,25 +2,11 @@ import abc import attr import collections import datetime -import enum -from .._common import log, attrs_default, Image +from .._common import log, attrs_default from .. import _util, _exception, _session, _graphql, _models from typing import MutableMapping, Mapping, Any, Iterable, Tuple, Optional -class ThreadLocation(enum.Enum): - """Used to specify where a thread is located (inbox, pending, archived, other).""" - - INBOX = "INBOX" - PENDING = "PENDING" - ARCHIVED = "ARCHIVED" - OTHER = "OTHER" - - @classmethod - def _parse(cls, value: str): - return cls(value.lstrip("FOLDER_")) - - DEFAULT_COLOR = "#0084ff" SETABLE_COLORS = ( DEFAULT_COLOR, diff --git a/fbchat/_threads/_group.py b/fbchat/_threads/_group.py index 3877a8d..e8d0ec3 100644 --- a/fbchat/_threads/_group.py +++ b/fbchat/_threads/_group.py @@ -2,7 +2,7 @@ import attr import datetime from ._abc import ThreadABC from . import _user -from .._common import attrs_default, Image +from .._common import attrs_default from .. import _util, _session, _graphql, _models from typing import Sequence, Iterable, Set, Mapping @@ -176,7 +176,7 @@ class GroupData(Group): """ #: The group's picture - photo = attr.ib(None, type=Image) + photo = attr.ib(None, type="_models.Image") #: The name of the group name = attr.ib(None, type=str) #: When the group was last active / when the last message was sent @@ -238,7 +238,7 @@ class GroupData(Group): if data.get("group_approval_queue") else None, join_link=data["joinable_mode"].get("link"), - photo=Image._from_uri_or_none(data["image"]), + photo=_models.Image._from_uri_or_none(data["image"]), name=data.get("name"), message_count=data.get("messages_count"), last_active=last_active, diff --git a/fbchat/_threads/_page.py b/fbchat/_threads/_page.py index d0feeee..a2c7429 100644 --- a/fbchat/_threads/_page.py +++ b/fbchat/_threads/_page.py @@ -1,7 +1,7 @@ import attr import datetime from ._abc import ThreadABC -from .._common import attrs_default, Image +from .._common import attrs_default from .. import _session, _models @@ -32,7 +32,7 @@ class PageData(Page): """ #: The page's picture - photo = attr.ib(type=Image) + photo = attr.ib(type="_models.Image") #: The name of the page name = attr.ib(type=str) #: When the thread was last active / when the last message was sent @@ -70,7 +70,7 @@ class PageData(Page): url=data.get("url"), city=data.get("city").get("name"), category=data.get("category_type"), - photo=Image._from_uri(data["profile_picture"]), + photo=_models.Image._from_uri(data["profile_picture"]), name=data["name"], message_count=data.get("messages_count"), plan=plan, diff --git a/fbchat/_threads/_user.py b/fbchat/_threads/_user.py index 0295d16..297b890 100644 --- a/fbchat/_threads/_user.py +++ b/fbchat/_threads/_user.py @@ -1,7 +1,7 @@ import attr import datetime from ._abc import ThreadABC -from .._common import log, attrs_default, Image +from .._common import log, attrs_default from .. import _util, _session, _models @@ -100,7 +100,7 @@ class UserData(User): """ #: The user's picture - photo = attr.ib(type=Image) + photo = attr.ib(type="_models.Image") #: The name of the user name = attr.ib(type=str) #: Whether the user and the client are friends @@ -162,7 +162,7 @@ class UserData(User): color=c_info["color"], emoji=c_info["emoji"], own_nickname=c_info.get("own_nickname"), - photo=Image._from_uri(data["profile_picture"]), + photo=_models.Image._from_uri(data["profile_picture"]), name=data["name"], message_count=data.get("messages_count"), plan=plan, @@ -196,7 +196,7 @@ class UserData(User): color=c_info["color"], emoji=c_info["emoji"], own_nickname=c_info.get("own_nickname"), - photo=Image._from_uri(user["big_image_src"]), + photo=_models.Image._from_uri(user["big_image_src"]), message_count=data["messages_count"], last_active=_util.millis_to_datetime(int(data["updated_time_precise"])), plan=plan, @@ -209,27 +209,8 @@ class UserData(User): id=data["id"], first_name=data["firstName"], url=data["uri"], - photo=Image(url=data["thumbSrc"]), + photo=_models.Image(url=data["thumbSrc"]), name=data["name"], is_friend=data["is_friend"], gender=GENDERS.get(data["gender"]), ) - - -@attrs_default -class ActiveStatus: - #: Whether the user is active now - active = attr.ib(None, type=bool) - #: Datetime when the user was last active - last_active = attr.ib(None, type=datetime.datetime) - #: Whether the user is playing Messenger game now - in_game = attr.ib(None, type=bool) - - @classmethod - def _from_orca_presence(cls, data): - # TODO: Handle `c` and `vc` keys (Probably some binary data) - return cls( - active=data["p"] in [2, 3], - last_active=_util.seconds_to_datetime(data["l"]) if "l" in data else None, - in_game=None, - ) diff --git a/tests/models/test_attachment.py b/tests/models/test_attachment.py index ae3206e..d736d24 100644 --- a/tests/models/test_attachment.py +++ b/tests/models/test_attachment.py @@ -1,7 +1,7 @@ import pytest import datetime import fbchat -from fbchat import UnsentMessage, ShareAttachment +from fbchat import Image, UnsentMessage, ShareAttachment from fbchat._models._message import graphql_to_extensible_attachment @@ -122,7 +122,7 @@ def test_share_from_graphql_link_with_image(): " Share photos and videos, send messages and get updates." ), source=None, - image=fbchat.Image( + image=Image( url="https://www.facebook.com/rsrc.php/v3/x.png", width=325, height=325 ), original_image_url="https://www.facebook.com/rsrc.php/v3/x.png", @@ -184,7 +184,7 @@ def test_share_from_graphql_video(): " Subscribe to the official Rick As..." ), source="youtube.com", - image=fbchat.Image( + image=Image( url="https://external-arn2-1.xx.fbcdn.net/safe_image.php?d=xyz123" "&w=960&h=540&url=https%3A%2F%2Fi.ytimg.com%2Fvi%2FdQw4w9WgXcQ" "%2Fmaxresdefault.jpg&sx=0&sy=0&sw=1280&sh=720&_nc_hash=abc123", @@ -307,7 +307,7 @@ def test_share_with_image_subattachment(): title="", description="Abc", source="Def", - image=fbchat.Image( + image=Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/t1.0-9/1.jpg", width=720, height=960, @@ -435,7 +435,7 @@ def test_share_with_video_subattachment(): title="", description="Abc", source="Def", - image=fbchat.Image( + image=Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg", width=960, height=540, @@ -447,7 +447,7 @@ def test_share_with_video_subattachment(): duration=datetime.timedelta(seconds=24, microseconds=469000), preview_url="https://video-arn2-1.xx.fbcdn.net/v/t42.9040-2/vid.mp4", previews={ - fbchat.Image( + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg", width=960, height=540, diff --git a/tests/models/test_file.py b/tests/models/test_file.py index 26df1c7..a15a6c5 100644 --- a/tests/models/test_file.py +++ b/tests/models/test_file.py @@ -1,6 +1,12 @@ import datetime import fbchat -from fbchat import FileAttachment, AudioAttachment, ImageAttachment, VideoAttachment +from fbchat import ( + Image, + FileAttachment, + AudioAttachment, + ImageAttachment, + VideoAttachment, +) from fbchat._models._file import graphql_to_attachment, graphql_to_subattachment @@ -28,13 +34,13 @@ def test_imageattachment_from_list(): width=2833, height=1367, previews={ - fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/s261x260/1.jpg"), - fbchat.Image( + Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/s261x260/1.jpg"), + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/2.jpg", width=960, height=463, ), - fbchat.Image( + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/s2048x2048/3.jpg", width=2048, height=988, @@ -68,15 +74,15 @@ def test_videoattachment_from_list(): width=640, height=368, previews={ - fbchat.Image( + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/t15.3394-10/p261x260/1.jpg" ), - fbchat.Image( + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/t15.3394-10/2.jpg", width=640, height=368, ), - fbchat.Image( + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/t15.3394-10/3.jpg", width=640, height=368, @@ -169,8 +175,8 @@ def test_graphql_to_attachment_image1(): height=None, is_animated=False, previews={ - fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/p50x50/2.png"), - fbchat.Image( + Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/p50x50/2.png"), + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/1.png", width=128, height=128, @@ -204,9 +210,7 @@ def test_graphql_to_attachment_image2(): width=None, height=None, is_animated=True, - previews={ - fbchat.Image(url="https://cdn.fbsbx.com/v/1.gif", width=128, height=128) - }, + previews={Image(url="https://cdn.fbsbx.com/v/1.gif", width=128, height=128)}, ) == graphql_to_attachment(data) @@ -244,17 +248,17 @@ def test_graphql_to_attachment_video(): duration=datetime.timedelta(seconds=6), preview_url="https://video-arn2-1.xx.fbcdn.net/v/video-4321.mp4", previews={ - fbchat.Image( + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/s168x128/1.jpg", width=168, height=96, ), - fbchat.Image( + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/p261x260/3.jpg", width=452, height=260, ), - fbchat.Image( + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/2.jpg", width=640, height=368, @@ -345,7 +349,7 @@ def test_graphql_to_subattachment_video(): duration=datetime.timedelta(seconds=24, microseconds=469000), preview_url="https://video-arn2-1.xx.fbcdn.net/v/t42.9040-2/vid.mp4", previews={ - fbchat.Image( + Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg", width=960, height=540, diff --git a/tests/models/test_location.py b/tests/models/test_location.py index c8454b2..b49aa5e 100644 --- a/tests/models/test_location.py +++ b/tests/models/test_location.py @@ -1,7 +1,7 @@ import pytest import datetime import fbchat -from fbchat import LocationAttachment, LiveLocationAttachment +from fbchat import Image, LocationAttachment, LiveLocationAttachment def test_location_attachment_from_graphql(): @@ -38,7 +38,7 @@ def test_location_attachment_from_graphql(): id=400828513928715, latitude=55.4, longitude=12.4322, - image=fbchat.Image( + image=Image( url="https://external-arn2-1.xx.fbcdn.net/static_map.php?v=1020&osm_provider=2&size=545x280&zoom=15&markers=55.40000000%2C12.43220000&language=en", width=545, height=280, diff --git a/tests/models/test_sticker.py b/tests/models/test_sticker.py index 90de3a2..20b8906 100644 --- a/tests/models/test_sticker.py +++ b/tests/models/test_sticker.py @@ -1,6 +1,6 @@ import pytest import fbchat -from fbchat import Sticker +from fbchat import Image, Sticker def test_from_graphql_none(): @@ -20,7 +20,7 @@ def test_from_graphql_normal(): frames_per_col=1, frame_count=1, frame_rate=83, - image=fbchat.Image( + image=Image( url="https://scontent-arn2-1.xx.fbcdn.net/v/redacted.png", width=274, height=274, @@ -57,7 +57,7 @@ def test_from_graphql_animated(): frames_per_col=2, frame_count=4, frame_rate=142, - image=fbchat.Image( + image=Image( url="https://scontent-arn2-1.fbcdn.net/v/redacted1.png", width=240, height=293,