@@ -2,7 +2,7 @@ import attr
|
|||||||
from . import _util
|
from . import _util
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class Attachment:
|
class Attachment:
|
||||||
"""Represents a Facebook attachment."""
|
"""Represents a Facebook attachment."""
|
||||||
|
|
||||||
@@ -10,12 +10,12 @@ class Attachment:
|
|||||||
uid = attr.ib(None)
|
uid = attr.ib(None)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class UnsentMessage(Attachment):
|
class UnsentMessage(Attachment):
|
||||||
"""Represents an unsent message attachment."""
|
"""Represents an unsent message attachment."""
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class ShareAttachment(Attachment):
|
class ShareAttachment(Attachment):
|
||||||
"""Represents a shared item (e.g. URL) attachment."""
|
"""Represents a shared item (e.g. URL) attachment."""
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ from . import _util
|
|||||||
from ._attachment import Attachment
|
from ._attachment import Attachment
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class FileAttachment(Attachment):
|
class FileAttachment(Attachment):
|
||||||
"""Represents a file that has been sent as a Facebook attachment."""
|
"""Represents a file that has been sent as a Facebook attachment."""
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ class FileAttachment(Attachment):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class AudioAttachment(Attachment):
|
class AudioAttachment(Attachment):
|
||||||
"""Represents an audio file that has been sent as a Facebook attachment."""
|
"""Represents an audio file that has been sent as a Facebook attachment."""
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class AudioAttachment(Attachment):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, init=False)
|
@attr.s(init=False)
|
||||||
class ImageAttachment(Attachment):
|
class ImageAttachment(Attachment):
|
||||||
"""Represents an image that has been sent as a Facebook attachment.
|
"""Represents an image that has been sent as a Facebook attachment.
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ class ImageAttachment(Attachment):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, init=False)
|
@attr.s(init=False)
|
||||||
class VideoAttachment(Attachment):
|
class VideoAttachment(Attachment):
|
||||||
"""Represents a video that has been sent as a Facebook attachment."""
|
"""Represents a video that has been sent as a Facebook attachment."""
|
||||||
|
|
||||||
|
@@ -3,10 +3,12 @@ from . import _util, _plan
|
|||||||
from ._thread import ThreadType, Thread
|
from ._thread import ThreadType, Thread
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, init=False)
|
@attr.s
|
||||||
class Group(Thread):
|
class Group(Thread):
|
||||||
"""Represents a Facebook group. Inherits `Thread`."""
|
"""Represents a Facebook group. Inherits `Thread`."""
|
||||||
|
|
||||||
|
type = ThreadType.GROUP
|
||||||
|
|
||||||
#: Unique list (set) of the group thread's participant user IDs
|
#: Unique list (set) of the group thread's participant user IDs
|
||||||
participants = attr.ib(factory=set, converter=lambda x: set() if x is None else x)
|
participants = attr.ib(factory=set, converter=lambda x: set() if x is None else x)
|
||||||
#: A dictionary, containing user nicknames mapped to their IDs
|
#: A dictionary, containing user nicknames mapped to their IDs
|
||||||
@@ -26,38 +28,6 @@ class Group(Thread):
|
|||||||
# Link for joining group
|
# Link for joining group
|
||||||
join_link = attr.ib(None)
|
join_link = attr.ib(None)
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
uid,
|
|
||||||
participants=None,
|
|
||||||
nicknames=None,
|
|
||||||
color=None,
|
|
||||||
emoji=None,
|
|
||||||
admins=None,
|
|
||||||
approval_mode=None,
|
|
||||||
approval_requests=None,
|
|
||||||
join_link=None,
|
|
||||||
privacy_mode=None,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
super(Group, self).__init__(ThreadType.GROUP, uid, **kwargs)
|
|
||||||
if participants is None:
|
|
||||||
participants = set()
|
|
||||||
self.participants = participants
|
|
||||||
if nicknames is None:
|
|
||||||
nicknames = []
|
|
||||||
self.nicknames = nicknames
|
|
||||||
self.color = color
|
|
||||||
self.emoji = emoji
|
|
||||||
if admins is None:
|
|
||||||
admins = set()
|
|
||||||
self.admins = admins
|
|
||||||
self.approval_mode = approval_mode
|
|
||||||
if approval_requests is None:
|
|
||||||
approval_requests = set()
|
|
||||||
self.approval_requests = approval_requests
|
|
||||||
self.join_link = join_link
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, data):
|
def _from_graphql(cls, data):
|
||||||
if data.get("image") is None:
|
if data.get("image") is None:
|
||||||
|
@@ -3,7 +3,7 @@ from ._attachment import Attachment
|
|||||||
from . import _util
|
from . import _util
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class LocationAttachment(Attachment):
|
class LocationAttachment(Attachment):
|
||||||
"""Represents a user location.
|
"""Represents a user location.
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class LocationAttachment(Attachment):
|
|||||||
return rtn
|
return rtn
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, init=False)
|
@attr.s
|
||||||
class LiveLocationAttachment(LocationAttachment):
|
class LiveLocationAttachment(LocationAttachment):
|
||||||
"""Represents a live user location."""
|
"""Represents a live user location."""
|
||||||
|
|
||||||
@@ -64,11 +64,6 @@ class LiveLocationAttachment(LocationAttachment):
|
|||||||
#: True if live location is expired
|
#: True if live location is expired
|
||||||
is_expired = attr.ib(None)
|
is_expired = attr.ib(None)
|
||||||
|
|
||||||
def __init__(self, name=None, expires_at=None, is_expired=None, **kwargs):
|
|
||||||
super(LiveLocationAttachment, self).__init__(**kwargs)
|
|
||||||
self.expires_at = expires_at
|
|
||||||
self.is_expired = is_expired
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_pull(cls, data):
|
def _from_pull(cls, data):
|
||||||
return cls(
|
return cls(
|
||||||
@@ -96,7 +91,7 @@ class LiveLocationAttachment(LocationAttachment):
|
|||||||
if target.get("coordinate")
|
if target.get("coordinate")
|
||||||
else None,
|
else None,
|
||||||
name=data["title_with_entities"]["text"],
|
name=data["title_with_entities"]["text"],
|
||||||
expires_at=_util.millis_to_datetime(target.get("expiration_time")),
|
expires_at=_util.seconds_to_datetime(target.get("expiration_time")),
|
||||||
is_expired=target.get("is_expired"),
|
is_expired=target.get("is_expired"),
|
||||||
)
|
)
|
||||||
media = data.get("media")
|
media = data.get("media")
|
||||||
|
@@ -42,7 +42,7 @@ class MessageReaction(Enum):
|
|||||||
NO = "👎"
|
NO = "👎"
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class Mention:
|
class Mention:
|
||||||
"""Represents a ``@mention``."""
|
"""Represents a ``@mention``."""
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class Mention:
|
|||||||
length = attr.ib(10)
|
length = attr.ib(10)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class Message:
|
class Message:
|
||||||
"""Represents a Facebook message."""
|
"""Represents a Facebook message."""
|
||||||
|
|
||||||
|
@@ -3,10 +3,12 @@ from . import _plan
|
|||||||
from ._thread import ThreadType, Thread
|
from ._thread import ThreadType, Thread
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, init=False)
|
@attr.s
|
||||||
class Page(Thread):
|
class Page(Thread):
|
||||||
"""Represents a Facebook page. Inherits `Thread`."""
|
"""Represents a Facebook page. Inherits `Thread`."""
|
||||||
|
|
||||||
|
type = ThreadType.PAGE
|
||||||
|
|
||||||
#: The page's custom URL
|
#: The page's custom URL
|
||||||
url = attr.ib(None)
|
url = attr.ib(None)
|
||||||
#: The name of the page's location city
|
#: The name of the page's location city
|
||||||
@@ -18,23 +20,6 @@ class Page(Thread):
|
|||||||
#: The page's category
|
#: The page's category
|
||||||
category = attr.ib(None)
|
category = attr.ib(None)
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
uid,
|
|
||||||
url=None,
|
|
||||||
city=None,
|
|
||||||
likes=None,
|
|
||||||
sub_title=None,
|
|
||||||
category=None,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
super(Page, self).__init__(ThreadType.PAGE, uid, **kwargs)
|
|
||||||
self.url = url
|
|
||||||
self.city = city
|
|
||||||
self.likes = likes
|
|
||||||
self.sub_title = sub_title
|
|
||||||
self.category = category
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, data):
|
def _from_graphql(cls, data):
|
||||||
if data.get("profile_picture") is None:
|
if data.get("profile_picture") is None:
|
||||||
|
@@ -10,7 +10,7 @@ class GuestStatus(Enum):
|
|||||||
DECLINED = 3
|
DECLINED = 3
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class Plan:
|
class Plan:
|
||||||
"""Represents a plan."""
|
"""Represents a plan."""
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import attr
|
import attr
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class Poll:
|
class Poll:
|
||||||
"""Represents a poll."""
|
"""Represents a poll."""
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ class Poll:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class PollOption:
|
class PollOption:
|
||||||
"""Represents a poll option."""
|
"""Represents a poll option."""
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ import attr
|
|||||||
from ._attachment import Attachment
|
from ._attachment import Attachment
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class QuickReply:
|
class QuickReply:
|
||||||
"""Represents a quick reply."""
|
"""Represents a quick reply."""
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class QuickReply:
|
|||||||
is_response = attr.ib(False)
|
is_response = attr.ib(False)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, init=False)
|
@attr.s
|
||||||
class QuickReplyText(QuickReply):
|
class QuickReplyText(QuickReply):
|
||||||
"""Represents a text quick reply."""
|
"""Represents a text quick reply."""
|
||||||
|
|
||||||
@@ -27,25 +27,16 @@ class QuickReplyText(QuickReply):
|
|||||||
#: Type of the quick reply
|
#: Type of the quick reply
|
||||||
_type = "text"
|
_type = "text"
|
||||||
|
|
||||||
def __init__(self, title=None, image_url=None, **kwargs):
|
|
||||||
super(QuickReplyText, self).__init__(**kwargs)
|
|
||||||
self.title = title
|
|
||||||
self.image_url = image_url
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
@attr.s(cmp=False, init=False)
|
|
||||||
class QuickReplyLocation(QuickReply):
|
class QuickReplyLocation(QuickReply):
|
||||||
"""Represents a location quick reply (Doesn't work on mobile)."""
|
"""Represents a location quick reply (Doesn't work on mobile)."""
|
||||||
|
|
||||||
#: Type of the quick reply
|
#: Type of the quick reply
|
||||||
_type = "location"
|
_type = "location"
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super(QuickReplyLocation, self).__init__(**kwargs)
|
|
||||||
self.is_response = False
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
@attr.s(cmp=False, init=False)
|
|
||||||
class QuickReplyPhoneNumber(QuickReply):
|
class QuickReplyPhoneNumber(QuickReply):
|
||||||
"""Represents a phone number quick reply (Doesn't work on mobile)."""
|
"""Represents a phone number quick reply (Doesn't work on mobile)."""
|
||||||
|
|
||||||
@@ -54,12 +45,8 @@ class QuickReplyPhoneNumber(QuickReply):
|
|||||||
#: Type of the quick reply
|
#: Type of the quick reply
|
||||||
_type = "user_phone_number"
|
_type = "user_phone_number"
|
||||||
|
|
||||||
def __init__(self, image_url=None, **kwargs):
|
|
||||||
super(QuickReplyPhoneNumber, self).__init__(**kwargs)
|
|
||||||
self.image_url = image_url
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
@attr.s(cmp=False, init=False)
|
|
||||||
class QuickReplyEmail(QuickReply):
|
class QuickReplyEmail(QuickReply):
|
||||||
"""Represents an email quick reply (Doesn't work on mobile)."""
|
"""Represents an email quick reply (Doesn't work on mobile)."""
|
||||||
|
|
||||||
@@ -68,10 +55,6 @@ class QuickReplyEmail(QuickReply):
|
|||||||
#: Type of the quick reply
|
#: Type of the quick reply
|
||||||
_type = "user_email"
|
_type = "user_email"
|
||||||
|
|
||||||
def __init__(self, image_url=None, **kwargs):
|
|
||||||
super(QuickReplyEmail, self).__init__(**kwargs)
|
|
||||||
self.image_url = image_url
|
|
||||||
|
|
||||||
|
|
||||||
def graphql_to_quick_reply(q, is_response=False):
|
def graphql_to_quick_reply(q, is_response=False):
|
||||||
data = dict()
|
data = dict()
|
||||||
|
@@ -2,7 +2,7 @@ import attr
|
|||||||
from ._attachment import Attachment
|
from ._attachment import Attachment
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, init=False)
|
@attr.s
|
||||||
class Sticker(Attachment):
|
class Sticker(Attachment):
|
||||||
"""Represents a Facebook sticker that has been sent to a thread as an attachment."""
|
"""Represents a Facebook sticker that has been sent to a thread as an attachment."""
|
||||||
|
|
||||||
@@ -32,9 +32,6 @@ class Sticker(Attachment):
|
|||||||
#: The sticker's label/name
|
#: The sticker's label/name
|
||||||
label = attr.ib(None)
|
label = attr.ib(None)
|
||||||
|
|
||||||
def __init__(self, uid=None):
|
|
||||||
super(Sticker, self).__init__(uid=uid)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, data):
|
def _from_graphql(cls, data):
|
||||||
if not data:
|
if not data:
|
||||||
|
@@ -67,14 +67,14 @@ class ThreadColor(Enum):
|
|||||||
return cls._extend_if_invalid(value)
|
return cls._extend_if_invalid(value)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, init=False)
|
@attr.s
|
||||||
class Thread:
|
class Thread:
|
||||||
"""Represents a Facebook thread."""
|
"""Represents a Facebook thread."""
|
||||||
|
|
||||||
#: The unique identifier of the thread. Can be used a ``thread_id``. See :ref:`intro_threads` for more info
|
#: The unique identifier of the thread. Can be used a ``thread_id``. See :ref:`intro_threads` for more info
|
||||||
uid = attr.ib(converter=str)
|
uid = attr.ib(converter=str)
|
||||||
#: Specifies the type of thread. Can be used a ``thread_type``. See :ref:`intro_threads` for more info
|
#: Specifies the type of thread. Can be used a ``thread_type``. See :ref:`intro_threads` for more info
|
||||||
type = attr.ib()
|
type = None
|
||||||
#: A URL to the thread's picture
|
#: A URL to the thread's picture
|
||||||
photo = attr.ib(None)
|
photo = attr.ib(None)
|
||||||
#: The name of the thread
|
#: The name of the thread
|
||||||
@@ -86,24 +86,6 @@ class Thread:
|
|||||||
#: Set :class:`Plan`
|
#: Set :class:`Plan`
|
||||||
plan = attr.ib(None)
|
plan = attr.ib(None)
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
_type,
|
|
||||||
uid,
|
|
||||||
photo=None,
|
|
||||||
name=None,
|
|
||||||
last_active=None,
|
|
||||||
message_count=None,
|
|
||||||
plan=None,
|
|
||||||
):
|
|
||||||
self.uid = str(uid)
|
|
||||||
self.type = _type
|
|
||||||
self.photo = photo
|
|
||||||
self.name = name
|
|
||||||
self.last_active = last_active
|
|
||||||
self.message_count = message_count
|
|
||||||
self.plan = plan
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_customization_info(data):
|
def _parse_customization_info(data):
|
||||||
if data is None or data.get("customization_info") is None:
|
if data is None or data.get("customization_info") is None:
|
||||||
|
@@ -41,10 +41,12 @@ class TypingStatus(Enum):
|
|||||||
TYPING = 1
|
TYPING = 1
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False, init=False)
|
@attr.s
|
||||||
class User(Thread):
|
class User(Thread):
|
||||||
"""Represents a Facebook user. Inherits `Thread`."""
|
"""Represents a Facebook user. Inherits `Thread`."""
|
||||||
|
|
||||||
|
type = ThreadType.USER
|
||||||
|
|
||||||
#: The profile URL
|
#: The profile URL
|
||||||
url = attr.ib(None)
|
url = attr.ib(None)
|
||||||
#: The users first name
|
#: The users first name
|
||||||
@@ -66,33 +68,6 @@ class User(Thread):
|
|||||||
#: The default emoji
|
#: The default emoji
|
||||||
emoji = attr.ib(None)
|
emoji = attr.ib(None)
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
uid,
|
|
||||||
url=None,
|
|
||||||
first_name=None,
|
|
||||||
last_name=None,
|
|
||||||
is_friend=None,
|
|
||||||
gender=None,
|
|
||||||
affinity=None,
|
|
||||||
nickname=None,
|
|
||||||
own_nickname=None,
|
|
||||||
color=None,
|
|
||||||
emoji=None,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
super(User, self).__init__(ThreadType.USER, uid, **kwargs)
|
|
||||||
self.url = url
|
|
||||||
self.first_name = first_name
|
|
||||||
self.last_name = last_name
|
|
||||||
self.is_friend = is_friend
|
|
||||||
self.gender = gender
|
|
||||||
self.affinity = affinity
|
|
||||||
self.nickname = nickname
|
|
||||||
self.own_nickname = own_nickname
|
|
||||||
self.color = color
|
|
||||||
self.emoji = emoji
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, data):
|
def _from_graphql(cls, data):
|
||||||
if data.get("profile_picture") is None:
|
if data.get("profile_picture") is None:
|
||||||
@@ -179,7 +154,7 @@ class User(Thread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@attr.s(cmp=False)
|
@attr.s
|
||||||
class ActiveStatus:
|
class ActiveStatus:
|
||||||
#: Whether the user is active now
|
#: Whether the user is active now
|
||||||
active = attr.ib(None)
|
active = attr.ib(None)
|
||||||
|
456
tests/test_attachment.py
Normal file
456
tests/test_attachment.py
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
import pytest
|
||||||
|
import datetime
|
||||||
|
import fbchat
|
||||||
|
from fbchat._attachment import UnsentMessage, ShareAttachment
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_unsent_message():
|
||||||
|
data = {
|
||||||
|
"legacy_attachment_id": "ee.mid.$xyz",
|
||||||
|
"story_attachment": {
|
||||||
|
"description": {"text": "You removed a message"},
|
||||||
|
"media": None,
|
||||||
|
"source": None,
|
||||||
|
"style_list": ["globally_deleted_message_placeholder", "fallback"],
|
||||||
|
"title_with_entities": {"text": ""},
|
||||||
|
"properties": [],
|
||||||
|
"url": None,
|
||||||
|
"deduplication_key": "deadbeef123",
|
||||||
|
"action_links": [],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": None,
|
||||||
|
"subattachments": [],
|
||||||
|
},
|
||||||
|
"genie_attachment": {"genie_message": None},
|
||||||
|
}
|
||||||
|
assert UnsentMessage(
|
||||||
|
uid="ee.mid.$xyz"
|
||||||
|
) == fbchat._message.graphql_to_extensible_attachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_share_from_graphql_minimal():
|
||||||
|
data = {
|
||||||
|
"target": {},
|
||||||
|
"url": "a.com",
|
||||||
|
"title_with_entities": {"text": "a.com"},
|
||||||
|
"subattachments": [],
|
||||||
|
}
|
||||||
|
assert ShareAttachment(
|
||||||
|
url="a.com", original_url="a.com", title="a.com"
|
||||||
|
) == ShareAttachment._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_share_from_graphql_link():
|
||||||
|
data = {
|
||||||
|
"description": {"text": ""},
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": None,
|
||||||
|
"playable_duration_in_ms": 0,
|
||||||
|
"is_playable": False,
|
||||||
|
"playable_url": None,
|
||||||
|
},
|
||||||
|
"source": {"text": "a.com"},
|
||||||
|
"style_list": ["share", "fallback"],
|
||||||
|
"title_with_entities": {"text": "a.com"},
|
||||||
|
"properties": [],
|
||||||
|
"url": "http://l.facebook.com/l.php?u=http%3A%2F%2Fa.com%2F&h=def&s=1",
|
||||||
|
"deduplication_key": "ee.mid.$xyz",
|
||||||
|
"action_links": [{"title": "About this website", "url": None}],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {"__typename": "ExternalUrl"},
|
||||||
|
"subattachments": [],
|
||||||
|
}
|
||||||
|
assert ShareAttachment(
|
||||||
|
author=None,
|
||||||
|
url="http://l.facebook.com/l.php?u=http%3A%2F%2Fa.com%2F&h=def&s=1",
|
||||||
|
original_url="http://a.com/",
|
||||||
|
title="a.com",
|
||||||
|
description="",
|
||||||
|
source="a.com",
|
||||||
|
image_url=None,
|
||||||
|
original_image_url=None,
|
||||||
|
image_width=None,
|
||||||
|
image_height=None,
|
||||||
|
attachments=[],
|
||||||
|
uid="ee.mid.$xyz",
|
||||||
|
) == ShareAttachment._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_share_from_graphql_link_with_image():
|
||||||
|
data = {
|
||||||
|
"description": {
|
||||||
|
"text": (
|
||||||
|
"Create an account or log in to Facebook."
|
||||||
|
" Connect with friends, family and other people you know."
|
||||||
|
" Share photos and videos, send messages and get updates."
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": {
|
||||||
|
"uri": "https://www.facebook.com/rsrc.php/v3/x.png",
|
||||||
|
"height": 325,
|
||||||
|
"width": 325,
|
||||||
|
},
|
||||||
|
"playable_duration_in_ms": 0,
|
||||||
|
"is_playable": False,
|
||||||
|
"playable_url": None,
|
||||||
|
},
|
||||||
|
"source": None,
|
||||||
|
"style_list": ["share", "fallback"],
|
||||||
|
"title_with_entities": {"text": "Facebook – log in or sign up"},
|
||||||
|
"properties": [],
|
||||||
|
"url": "http://facebook.com/",
|
||||||
|
"deduplication_key": "deadbeef123",
|
||||||
|
"action_links": [],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {"__typename": "ExternalUrl"},
|
||||||
|
"subattachments": [],
|
||||||
|
}
|
||||||
|
assert ShareAttachment(
|
||||||
|
author=None,
|
||||||
|
url="http://facebook.com/",
|
||||||
|
original_url="http://facebook.com/",
|
||||||
|
title="Facebook – log in or sign up",
|
||||||
|
description=(
|
||||||
|
"Create an account or log in to Facebook."
|
||||||
|
" Connect with friends, family and other people you know."
|
||||||
|
" Share photos and videos, send messages and get updates."
|
||||||
|
),
|
||||||
|
source=None,
|
||||||
|
image_url="https://www.facebook.com/rsrc.php/v3/x.png",
|
||||||
|
original_image_url="https://www.facebook.com/rsrc.php/v3/x.png",
|
||||||
|
image_width=325,
|
||||||
|
image_height=325,
|
||||||
|
attachments=[],
|
||||||
|
uid="deadbeef123",
|
||||||
|
) == ShareAttachment._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_share_from_graphql_video():
|
||||||
|
data = {
|
||||||
|
"description": {
|
||||||
|
"text": (
|
||||||
|
"Rick Astley's official music video for “Never Gonna Give You Up”"
|
||||||
|
" Listen to Rick Astley: https://RickAstley.lnk.to/_listenYD"
|
||||||
|
" Subscribe to the official Rick As..."
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": {
|
||||||
|
"uri": (
|
||||||
|
"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"
|
||||||
|
),
|
||||||
|
"height": 540,
|
||||||
|
"width": 960,
|
||||||
|
},
|
||||||
|
"playable_duration_in_ms": 0,
|
||||||
|
"is_playable": True,
|
||||||
|
"playable_url": "https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=1",
|
||||||
|
},
|
||||||
|
"source": {"text": "youtube.com"},
|
||||||
|
"style_list": ["share", "fallback"],
|
||||||
|
"title_with_entities": {
|
||||||
|
"text": "Rick Astley - Never Gonna Give You Up (Video)"
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{"key": "width", "value": {"text": "1280"}},
|
||||||
|
{"key": "height", "value": {"text": "720"}},
|
||||||
|
],
|
||||||
|
"url": "https://l.facebook.com/l.php?u=https%3A%2F%2Fyoutu.be%2FdQw4w9WgXcQ",
|
||||||
|
"deduplication_key": "ee.mid.$gAAT4Sw1WSGhzQ9uRWVtEpZHZ8ZPV",
|
||||||
|
"action_links": [{"title": "About this website", "url": None}],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {"__typename": "ExternalUrl"},
|
||||||
|
"subattachments": [],
|
||||||
|
}
|
||||||
|
assert ShareAttachment(
|
||||||
|
author=None,
|
||||||
|
url="https://l.facebook.com/l.php?u=https%3A%2F%2Fyoutu.be%2FdQw4w9WgXcQ",
|
||||||
|
original_url="https://youtu.be/dQw4w9WgXcQ",
|
||||||
|
title="Rick Astley - Never Gonna Give You Up (Video)",
|
||||||
|
description=(
|
||||||
|
"Rick Astley's official music video for “Never Gonna Give You Up”"
|
||||||
|
" Listen to Rick Astley: https://RickAstley.lnk.to/_listenYD"
|
||||||
|
" Subscribe to the official Rick As..."
|
||||||
|
),
|
||||||
|
source="youtube.com",
|
||||||
|
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"
|
||||||
|
),
|
||||||
|
original_image_url="https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
|
||||||
|
image_width=960,
|
||||||
|
image_height=540,
|
||||||
|
attachments=[],
|
||||||
|
uid="ee.mid.$gAAT4Sw1WSGhzQ9uRWVtEpZHZ8ZPV",
|
||||||
|
) == ShareAttachment._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_share_with_image_subattachment():
|
||||||
|
data = {
|
||||||
|
"description": {"text": "Abc"},
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t1.0-9/1.jpg",
|
||||||
|
"height": 960,
|
||||||
|
"width": 720,
|
||||||
|
},
|
||||||
|
"playable_duration_in_ms": 0,
|
||||||
|
"is_playable": False,
|
||||||
|
"playable_url": None,
|
||||||
|
},
|
||||||
|
"source": {"text": "Def"},
|
||||||
|
"style_list": ["attached_story", "fallback"],
|
||||||
|
"title_with_entities": {"text": ""},
|
||||||
|
"properties": [],
|
||||||
|
"url": "https://www.facebook.com/groups/11223344/permalink/1234/",
|
||||||
|
"deduplication_key": "deadbeef123",
|
||||||
|
"action_links": [
|
||||||
|
{"title": None, "url": None},
|
||||||
|
{"title": None, "url": "https://www.facebook.com/groups/11223344/"},
|
||||||
|
{
|
||||||
|
"title": "Report Post to Admin",
|
||||||
|
"url": "https://www.facebook.com/groups/11223344/members/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {
|
||||||
|
"__typename": "Story",
|
||||||
|
"title": None,
|
||||||
|
"description": {"text": "Abc"},
|
||||||
|
"actors": [
|
||||||
|
{
|
||||||
|
"__typename": "User",
|
||||||
|
"name": "Def",
|
||||||
|
"id": "1111",
|
||||||
|
"short_name": "Def",
|
||||||
|
"url": "https://www.facebook.com/some-user",
|
||||||
|
"profile_picture": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t1.0-1/c123.123.123.123a/s50x50/img.jpg",
|
||||||
|
"height": 50,
|
||||||
|
"width": 50,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"to": {
|
||||||
|
"__typename": "Group",
|
||||||
|
"name": "Some group",
|
||||||
|
"url": "https://www.facebook.com/groups/11223344/",
|
||||||
|
},
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"url": "https://www.facebook.com/photo.php?fbid=4321&set=gm.1234&type=3",
|
||||||
|
"media": {
|
||||||
|
"is_playable": False,
|
||||||
|
"image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t1.0-9/1.jpg",
|
||||||
|
"height": 960,
|
||||||
|
"width": 720,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attached_story": None,
|
||||||
|
},
|
||||||
|
"subattachments": [
|
||||||
|
{
|
||||||
|
"description": {"text": "Abc"},
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t1.0-9/1.jpg",
|
||||||
|
"height": 960,
|
||||||
|
"width": 720,
|
||||||
|
},
|
||||||
|
"playable_duration_in_ms": 0,
|
||||||
|
"is_playable": False,
|
||||||
|
"playable_url": None,
|
||||||
|
},
|
||||||
|
"source": None,
|
||||||
|
"style_list": ["photo", "games_app", "fallback"],
|
||||||
|
"title_with_entities": {"text": ""},
|
||||||
|
"properties": [
|
||||||
|
{"key": "photoset_reference_token", "value": {"text": "gm.1234"}},
|
||||||
|
{"key": "layout_x", "value": {"text": "0"}},
|
||||||
|
{"key": "layout_y", "value": {"text": "0"}},
|
||||||
|
{"key": "layout_w", "value": {"text": "0"}},
|
||||||
|
{"key": "layout_h", "value": {"text": "0"}},
|
||||||
|
],
|
||||||
|
"url": "https://www.facebook.com/photo.php?fbid=4321&set=gm.1234&type=3",
|
||||||
|
"deduplication_key": "deadbeef456",
|
||||||
|
"action_links": [],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {"__typename": "Photo"},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
assert ShareAttachment(
|
||||||
|
author="1111",
|
||||||
|
url="https://www.facebook.com/groups/11223344/permalink/1234/",
|
||||||
|
original_url="https://www.facebook.com/groups/11223344/permalink/1234/",
|
||||||
|
title="",
|
||||||
|
description="Abc",
|
||||||
|
source="Def",
|
||||||
|
image_url="https://scontent-arn2-1.xx.fbcdn.net/v/t1.0-9/1.jpg",
|
||||||
|
original_image_url="https://scontent-arn2-1.xx.fbcdn.net/v/t1.0-9/1.jpg",
|
||||||
|
image_width=720,
|
||||||
|
image_height=960,
|
||||||
|
attachments=[None],
|
||||||
|
uid="deadbeef123",
|
||||||
|
) == ShareAttachment._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_share_with_video_subattachment():
|
||||||
|
data = {
|
||||||
|
"description": {"text": "Abc"},
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg",
|
||||||
|
"height": 540,
|
||||||
|
"width": 960,
|
||||||
|
},
|
||||||
|
"playable_duration_in_ms": 24469,
|
||||||
|
"is_playable": True,
|
||||||
|
"playable_url": "https://video-arn2-1.xx.fbcdn.net/v/t42.9040-2/vid.mp4",
|
||||||
|
},
|
||||||
|
"source": {"text": "Def"},
|
||||||
|
"style_list": ["attached_story", "fallback"],
|
||||||
|
"title_with_entities": {"text": ""},
|
||||||
|
"properties": [],
|
||||||
|
"url": "https://www.facebook.com/groups/11223344/permalink/1234/",
|
||||||
|
"deduplication_key": "deadbeef123",
|
||||||
|
"action_links": [
|
||||||
|
{"title": None, "url": None},
|
||||||
|
{"title": None, "url": "https://www.facebook.com/groups/11223344/"},
|
||||||
|
{"title": None, "url": None},
|
||||||
|
{"title": "A watch party is currently playing this video.", "url": None},
|
||||||
|
],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {
|
||||||
|
"__typename": "Story",
|
||||||
|
"title": None,
|
||||||
|
"description": {"text": "Abc"},
|
||||||
|
"actors": [
|
||||||
|
{
|
||||||
|
"__typename": "User",
|
||||||
|
"name": "Def",
|
||||||
|
"id": "1111",
|
||||||
|
"short_name": "Def",
|
||||||
|
"url": "https://www.facebook.com/some-user",
|
||||||
|
"profile_picture": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t1.0-1/c1.0.50.50a/p50x50/profile.jpg",
|
||||||
|
"height": 50,
|
||||||
|
"width": 50,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"to": {
|
||||||
|
"__typename": "Group",
|
||||||
|
"name": "Some group",
|
||||||
|
"url": "https://www.facebook.com/groups/11223344/",
|
||||||
|
},
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"url": "https://www.facebook.com/some-user/videos/2222/",
|
||||||
|
"media": {
|
||||||
|
"is_playable": True,
|
||||||
|
"image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg",
|
||||||
|
"height": 540,
|
||||||
|
"width": 960,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attached_story": None,
|
||||||
|
},
|
||||||
|
"subattachments": [
|
||||||
|
{
|
||||||
|
"description": None,
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg",
|
||||||
|
"height": 540,
|
||||||
|
"width": 960,
|
||||||
|
},
|
||||||
|
"playable_duration_in_ms": 24469,
|
||||||
|
"is_playable": True,
|
||||||
|
"playable_url": "https://video-arn2-1.xx.fbcdn.net/v/t42.9040-2/vid.mp4",
|
||||||
|
},
|
||||||
|
"source": None,
|
||||||
|
"style_list": [
|
||||||
|
"video_autoplay",
|
||||||
|
"video_inline",
|
||||||
|
"video",
|
||||||
|
"games_app",
|
||||||
|
"fallback",
|
||||||
|
],
|
||||||
|
"title_with_entities": {"text": ""},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"key": "can_autoplay_result",
|
||||||
|
"value": {"text": "ugc_default_allowed"},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": "https://www.facebook.com/some-user/videos/2222/",
|
||||||
|
"deduplication_key": "deadbeef456",
|
||||||
|
"action_links": [],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {
|
||||||
|
"__typename": "Video",
|
||||||
|
"video_id": "2222",
|
||||||
|
"video_messenger_cta_payload": None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
assert ShareAttachment(
|
||||||
|
author="1111",
|
||||||
|
url="https://www.facebook.com/groups/11223344/permalink/1234/",
|
||||||
|
original_url="https://www.facebook.com/groups/11223344/permalink/1234/",
|
||||||
|
title="",
|
||||||
|
description="Abc",
|
||||||
|
source="Def",
|
||||||
|
image_url="https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg",
|
||||||
|
original_image_url="https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg",
|
||||||
|
image_width=960,
|
||||||
|
image_height=540,
|
||||||
|
attachments=[
|
||||||
|
fbchat.VideoAttachment(
|
||||||
|
uid="2222",
|
||||||
|
duration=datetime.timedelta(seconds=24, microseconds=469000),
|
||||||
|
preview_url="https://video-arn2-1.xx.fbcdn.net/v/t42.9040-2/vid.mp4",
|
||||||
|
medium_image={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg",
|
||||||
|
"width": 960,
|
||||||
|
"height": 540,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
uid="deadbeef123",
|
||||||
|
) == ShareAttachment._from_graphql(data)
|
14
tests/test_core.py
Normal file
14
tests/test_core.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import pytest
|
||||||
|
from fbchat._core import Enum
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||||
|
def test_enum_extend_if_invalid():
|
||||||
|
class TestEnum(Enum):
|
||||||
|
A = 1
|
||||||
|
B = 2
|
||||||
|
|
||||||
|
assert TestEnum._extend_if_invalid(1) == TestEnum.A
|
||||||
|
assert TestEnum._extend_if_invalid(3) == TestEnum.UNKNOWN_3
|
||||||
|
assert TestEnum._extend_if_invalid(3) == TestEnum.UNKNOWN_3
|
||||||
|
assert TestEnum(3) == TestEnum.UNKNOWN_3
|
358
tests/test_file.py
Normal file
358
tests/test_file.py
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
import datetime
|
||||||
|
import fbchat
|
||||||
|
from fbchat._file import (
|
||||||
|
FileAttachment,
|
||||||
|
AudioAttachment,
|
||||||
|
ImageAttachment,
|
||||||
|
VideoAttachment,
|
||||||
|
graphql_to_attachment,
|
||||||
|
graphql_to_subattachment,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_imageattachment_from_list():
|
||||||
|
data = {
|
||||||
|
"__typename": "MessageImage",
|
||||||
|
"id": "bWVzc2...",
|
||||||
|
"legacy_attachment_id": "1234",
|
||||||
|
"image": {"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/s261x260/1.jpg"},
|
||||||
|
"image1": {
|
||||||
|
"height": 463,
|
||||||
|
"width": 960,
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/2.jpg",
|
||||||
|
},
|
||||||
|
"image2": {
|
||||||
|
"height": 988,
|
||||||
|
"width": 2048,
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/s2048x2048/3.jpg",
|
||||||
|
},
|
||||||
|
"original_dimensions": {"x": 2833, "y": 1367},
|
||||||
|
"photo_encodings": [],
|
||||||
|
}
|
||||||
|
assert ImageAttachment(
|
||||||
|
uid="1234",
|
||||||
|
width=2833,
|
||||||
|
height=1367,
|
||||||
|
thumbnail_url="https://scontent-arn2-1.xx.fbcdn.net/v/s261x260/1.jpg",
|
||||||
|
preview={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/2.jpg",
|
||||||
|
"width": 960,
|
||||||
|
"height": 463,
|
||||||
|
},
|
||||||
|
large_preview={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/s2048x2048/3.jpg",
|
||||||
|
"width": 2048,
|
||||||
|
"height": 988,
|
||||||
|
},
|
||||||
|
) == ImageAttachment._from_list({"node": data})
|
||||||
|
|
||||||
|
|
||||||
|
def test_videoattachment_from_list():
|
||||||
|
data = {
|
||||||
|
"__typename": "MessageVideo",
|
||||||
|
"id": "bWVzc2...",
|
||||||
|
"legacy_attachment_id": "1234",
|
||||||
|
"image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.3394-10/p261x260/1.jpg"
|
||||||
|
},
|
||||||
|
"image1": {
|
||||||
|
"height": 368,
|
||||||
|
"width": 640,
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.3394-10/2.jpg",
|
||||||
|
},
|
||||||
|
"image2": {
|
||||||
|
"height": 368,
|
||||||
|
"width": 640,
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.3394-10/3.jpg",
|
||||||
|
},
|
||||||
|
"original_dimensions": {"x": 640, "y": 368},
|
||||||
|
}
|
||||||
|
assert VideoAttachment(
|
||||||
|
uid="1234",
|
||||||
|
width=640,
|
||||||
|
height=368,
|
||||||
|
small_image={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.3394-10/p261x260/1.jpg"
|
||||||
|
},
|
||||||
|
medium_image={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.3394-10/2.jpg",
|
||||||
|
"width": 640,
|
||||||
|
"height": 368,
|
||||||
|
},
|
||||||
|
large_image={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.3394-10/3.jpg",
|
||||||
|
"width": 640,
|
||||||
|
"height": 368,
|
||||||
|
},
|
||||||
|
) == VideoAttachment._from_list({"node": data})
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_attachment_empty():
|
||||||
|
assert fbchat.Attachment() == graphql_to_attachment({"__typename": "Unknown"})
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_attachment_simple():
|
||||||
|
data = {"__typename": "Unknown", "legacy_attachment_id": "1234"}
|
||||||
|
assert fbchat.Attachment(uid="1234") == graphql_to_attachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_attachment_file():
|
||||||
|
data = {
|
||||||
|
"__typename": "MessageFile",
|
||||||
|
"attribution_app": None,
|
||||||
|
"attribution_metadata": None,
|
||||||
|
"filename": "file.txt",
|
||||||
|
"url": "https://l.facebook.com/l.php?u=https%3A%2F%2Fcdn.fbsbx.com%2Fv%2Ffile.txt&h=AT1...&s=1",
|
||||||
|
"content_type": "attach:text",
|
||||||
|
"is_malicious": False,
|
||||||
|
"message_file_fbid": "1234",
|
||||||
|
"url_shimhash": "AT0...",
|
||||||
|
"url_skipshim": True,
|
||||||
|
}
|
||||||
|
assert FileAttachment(
|
||||||
|
uid="1234",
|
||||||
|
url="https://l.facebook.com/l.php?u=https%3A%2F%2Fcdn.fbsbx.com%2Fv%2Ffile.txt&h=AT1...&s=1",
|
||||||
|
size=None,
|
||||||
|
name="file.txt",
|
||||||
|
is_malicious=False,
|
||||||
|
) == graphql_to_attachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_attachment_audio():
|
||||||
|
data = {
|
||||||
|
"__typename": "MessageAudio",
|
||||||
|
"attribution_app": None,
|
||||||
|
"attribution_metadata": None,
|
||||||
|
"filename": "audio.mp3",
|
||||||
|
"playable_url": "https://cdn.fbsbx.com/v/audio.mp3?dl=1",
|
||||||
|
"playable_duration_in_ms": 27745,
|
||||||
|
"is_voicemail": False,
|
||||||
|
"audio_type": "FILE_ATTACHMENT",
|
||||||
|
"url_shimhash": "AT0...",
|
||||||
|
"url_skipshim": True,
|
||||||
|
}
|
||||||
|
assert AudioAttachment(
|
||||||
|
uid=None,
|
||||||
|
filename="audio.mp3",
|
||||||
|
url="https://cdn.fbsbx.com/v/audio.mp3?dl=1",
|
||||||
|
duration=datetime.timedelta(seconds=27, microseconds=745000),
|
||||||
|
audio_type="FILE_ATTACHMENT",
|
||||||
|
) == graphql_to_attachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_attachment_image1():
|
||||||
|
data = {
|
||||||
|
"__typename": "MessageImage",
|
||||||
|
"attribution_app": None,
|
||||||
|
"attribution_metadata": None,
|
||||||
|
"filename": "image-1234",
|
||||||
|
"preview": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/1.png",
|
||||||
|
"height": 128,
|
||||||
|
"width": 128,
|
||||||
|
},
|
||||||
|
"large_preview": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/2.png",
|
||||||
|
"height": 128,
|
||||||
|
"width": 128,
|
||||||
|
},
|
||||||
|
"thumbnail": {"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/p50x50/3.png"},
|
||||||
|
"photo_encodings": [],
|
||||||
|
"legacy_attachment_id": "1234",
|
||||||
|
"original_dimensions": {"x": 128, "y": 128},
|
||||||
|
"original_extension": "png",
|
||||||
|
"render_as_sticker": False,
|
||||||
|
"blurred_image_uri": None,
|
||||||
|
}
|
||||||
|
assert ImageAttachment(
|
||||||
|
uid="1234",
|
||||||
|
original_extension="png",
|
||||||
|
width=None,
|
||||||
|
height=None,
|
||||||
|
is_animated=False,
|
||||||
|
thumbnail_url="https://scontent-arn2-1.xx.fbcdn.net/v/p50x50/3.png",
|
||||||
|
preview={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/1.png",
|
||||||
|
"width": 128,
|
||||||
|
"height": 128,
|
||||||
|
},
|
||||||
|
large_preview={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/2.png",
|
||||||
|
"width": 128,
|
||||||
|
"height": 128,
|
||||||
|
},
|
||||||
|
) == graphql_to_attachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_attachment_image2():
|
||||||
|
data = {
|
||||||
|
"__typename": "MessageAnimatedImage",
|
||||||
|
"attribution_app": None,
|
||||||
|
"attribution_metadata": None,
|
||||||
|
"filename": "gif-1234",
|
||||||
|
"animated_image": {
|
||||||
|
"uri": "https://cdn.fbsbx.com/v/1.gif",
|
||||||
|
"height": 128,
|
||||||
|
"width": 128,
|
||||||
|
},
|
||||||
|
"legacy_attachment_id": "1234",
|
||||||
|
"preview_image": {
|
||||||
|
"uri": "https://cdn.fbsbx.com/v/1.gif",
|
||||||
|
"height": 128,
|
||||||
|
"width": 128,
|
||||||
|
},
|
||||||
|
"original_dimensions": {"x": 128, "y": 128},
|
||||||
|
}
|
||||||
|
assert ImageAttachment(
|
||||||
|
uid="1234",
|
||||||
|
original_extension="gif",
|
||||||
|
width=None,
|
||||||
|
height=None,
|
||||||
|
is_animated=True,
|
||||||
|
preview={"uri": "https://cdn.fbsbx.com/v/1.gif", "width": 128, "height": 128},
|
||||||
|
animated_preview={
|
||||||
|
"uri": "https://cdn.fbsbx.com/v/1.gif",
|
||||||
|
"width": 128,
|
||||||
|
"height": 128,
|
||||||
|
},
|
||||||
|
) == graphql_to_attachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_attachment_video():
|
||||||
|
data = {
|
||||||
|
"__typename": "MessageVideo",
|
||||||
|
"attribution_app": None,
|
||||||
|
"attribution_metadata": None,
|
||||||
|
"filename": "video-4321.mp4",
|
||||||
|
"playable_url": "https://video-arn2-1.xx.fbcdn.net/v/video-4321.mp4",
|
||||||
|
"chat_image": {
|
||||||
|
"height": 96,
|
||||||
|
"width": 168,
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/s168x128/1.jpg",
|
||||||
|
},
|
||||||
|
"legacy_attachment_id": "1234",
|
||||||
|
"video_type": "FILE_ATTACHMENT",
|
||||||
|
"original_dimensions": {"x": 640, "y": 368},
|
||||||
|
"playable_duration_in_ms": 6000,
|
||||||
|
"large_image": {
|
||||||
|
"height": 368,
|
||||||
|
"width": 640,
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/2.jpg",
|
||||||
|
},
|
||||||
|
"inbox_image": {
|
||||||
|
"height": 260,
|
||||||
|
"width": 452,
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/p261x260/3.jpg",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert VideoAttachment(
|
||||||
|
uid="1234",
|
||||||
|
width=None,
|
||||||
|
height=None,
|
||||||
|
duration=datetime.timedelta(seconds=6),
|
||||||
|
preview_url="https://video-arn2-1.xx.fbcdn.net/v/video-4321.mp4",
|
||||||
|
small_image={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/s168x128/1.jpg",
|
||||||
|
"width": 168,
|
||||||
|
"height": 96,
|
||||||
|
},
|
||||||
|
medium_image={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/p261x260/3.jpg",
|
||||||
|
"width": 452,
|
||||||
|
"height": 260,
|
||||||
|
},
|
||||||
|
large_image={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/2.jpg",
|
||||||
|
"width": 640,
|
||||||
|
"height": 368,
|
||||||
|
},
|
||||||
|
) == graphql_to_attachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_subattachment_empty():
|
||||||
|
assert None is graphql_to_subattachment({})
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_subattachment_image():
|
||||||
|
data = {
|
||||||
|
"description": {"text": "Abc"},
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t1.0-9/1.jpg",
|
||||||
|
"height": 960,
|
||||||
|
"width": 720,
|
||||||
|
},
|
||||||
|
"playable_duration_in_ms": 0,
|
||||||
|
"is_playable": False,
|
||||||
|
"playable_url": None,
|
||||||
|
},
|
||||||
|
"source": None,
|
||||||
|
"style_list": ["photo", "games_app", "fallback"],
|
||||||
|
"title_with_entities": {"text": ""},
|
||||||
|
"properties": [
|
||||||
|
{"key": "photoset_reference_token", "value": {"text": "gm.4321"}},
|
||||||
|
{"key": "layout_x", "value": {"text": "0"}},
|
||||||
|
{"key": "layout_y", "value": {"text": "0"}},
|
||||||
|
{"key": "layout_w", "value": {"text": "0"}},
|
||||||
|
{"key": "layout_h", "value": {"text": "0"}},
|
||||||
|
],
|
||||||
|
"url": "https://www.facebook.com/photo.php?fbid=1234&set=gm.4321&type=3",
|
||||||
|
"deduplication_key": "8334...",
|
||||||
|
"action_links": [],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {"__typename": "Photo"},
|
||||||
|
}
|
||||||
|
assert None is graphql_to_subattachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_subattachment_video():
|
||||||
|
data = {
|
||||||
|
"description": None,
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg",
|
||||||
|
"height": 540,
|
||||||
|
"width": 960,
|
||||||
|
},
|
||||||
|
"playable_duration_in_ms": 24469,
|
||||||
|
"is_playable": True,
|
||||||
|
"playable_url": "https://video-arn2-1.xx.fbcdn.net/v/t42.9040-2/vid.mp4",
|
||||||
|
},
|
||||||
|
"source": None,
|
||||||
|
"style_list": [
|
||||||
|
"video_autoplay",
|
||||||
|
"video_inline",
|
||||||
|
"video",
|
||||||
|
"games_app",
|
||||||
|
"fallback",
|
||||||
|
],
|
||||||
|
"title_with_entities": {"text": ""},
|
||||||
|
"properties": [
|
||||||
|
{"key": "can_autoplay_result", "value": {"text": "ugc_default_allowed"}}
|
||||||
|
],
|
||||||
|
"url": "https://www.facebook.com/some-username/videos/1234/",
|
||||||
|
"deduplication_key": "ddb7...",
|
||||||
|
"action_links": [],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {
|
||||||
|
"__typename": "Video",
|
||||||
|
"video_id": "1234",
|
||||||
|
"video_messenger_cta_payload": None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert VideoAttachment(
|
||||||
|
uid="1234",
|
||||||
|
duration=datetime.timedelta(seconds=24, microseconds=469000),
|
||||||
|
preview_url="https://video-arn2-1.xx.fbcdn.net/v/t42.9040-2/vid.mp4",
|
||||||
|
medium_image={
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/t15.5256-10/p180x540/1.jpg",
|
||||||
|
"width": 960,
|
||||||
|
"height": 540,
|
||||||
|
},
|
||||||
|
) == graphql_to_subattachment(data)
|
35
tests/test_graphql.py
Normal file
35
tests/test_graphql.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import pytest
|
||||||
|
import json
|
||||||
|
from fbchat._graphql import ConcatJSONDecoder, queries_to_json, response_to_json
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"content,result",
|
||||||
|
[
|
||||||
|
("", []),
|
||||||
|
('{"a":"b"}', [{"a": "b"}]),
|
||||||
|
('{"a":"b"}{"b":"c"}', [{"a": "b"}, {"b": "c"}]),
|
||||||
|
(' \n{"a": "b" } \n { "b" \n\n : "c" }', [{"a": "b"}, {"b": "c"}]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_concat_json_decoder(content, result):
|
||||||
|
assert result == json.loads(content, cls=ConcatJSONDecoder)
|
||||||
|
|
||||||
|
|
||||||
|
def test_queries_to_json():
|
||||||
|
assert {"q0": "A", "q1": "B", "q2": "C"} == json.loads(
|
||||||
|
queries_to_json("A", "B", "C")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_response_to_json():
|
||||||
|
data = (
|
||||||
|
'{"q1":{"data":{"b":"c"}}}\r\n'
|
||||||
|
'{"q0":{"response":[1,2]}}\r\n'
|
||||||
|
"{\n"
|
||||||
|
' "successful_results": 2,\n'
|
||||||
|
' "error_results": 0,\n'
|
||||||
|
' "skipped_results": 0\n'
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
assert [[1, 2], {"b": "c"}] == response_to_json(data)
|
43
tests/test_group.py
Normal file
43
tests/test_group.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from fbchat._group import Group
|
||||||
|
|
||||||
|
|
||||||
|
def test_group_from_graphql():
|
||||||
|
data = {
|
||||||
|
"name": "Group ABC",
|
||||||
|
"thread_key": {"thread_fbid": "11223344"},
|
||||||
|
"image": None,
|
||||||
|
"is_group_thread": True,
|
||||||
|
"all_participants": {
|
||||||
|
"nodes": [
|
||||||
|
{"messaging_actor": {"id": "1234"}},
|
||||||
|
{"messaging_actor": {"id": "2345"}},
|
||||||
|
{"messaging_actor": {"id": "3456"}},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"customization_info": {
|
||||||
|
"participant_customizations": [],
|
||||||
|
"outgoing_bubble_color": None,
|
||||||
|
"emoji": "😀",
|
||||||
|
},
|
||||||
|
"thread_admins": [{"id": "1234"}],
|
||||||
|
"group_approval_queue": {"nodes": []},
|
||||||
|
"approval_mode": 0,
|
||||||
|
"joinable_mode": {"mode": "0", "link": ""},
|
||||||
|
"event_reminders": {"nodes": []},
|
||||||
|
}
|
||||||
|
assert Group(
|
||||||
|
uid="11223344",
|
||||||
|
photo=None,
|
||||||
|
name="Group ABC",
|
||||||
|
last_active=None,
|
||||||
|
message_count=None,
|
||||||
|
plan=None,
|
||||||
|
participants={"1234", "2345", "3456"},
|
||||||
|
nicknames={},
|
||||||
|
color=None,
|
||||||
|
emoji="😀",
|
||||||
|
admins={"1234"},
|
||||||
|
approval_mode=False,
|
||||||
|
approval_requests=set(),
|
||||||
|
join_link="",
|
||||||
|
) == Group._from_graphql(data)
|
91
tests/test_location.py
Normal file
91
tests/test_location.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import pytest
|
||||||
|
import datetime
|
||||||
|
from fbchat._location import LocationAttachment, LiveLocationAttachment
|
||||||
|
|
||||||
|
|
||||||
|
def test_location_attachment_from_graphql():
|
||||||
|
data = {
|
||||||
|
"description": {"text": ""},
|
||||||
|
"media": {
|
||||||
|
"animated_image": None,
|
||||||
|
"image": {
|
||||||
|
"uri": "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",
|
||||||
|
"height": 280,
|
||||||
|
"width": 545,
|
||||||
|
},
|
||||||
|
"playable_duration_in_ms": 0,
|
||||||
|
"is_playable": False,
|
||||||
|
"playable_url": None,
|
||||||
|
},
|
||||||
|
"source": None,
|
||||||
|
"style_list": ["message_location", "fallback"],
|
||||||
|
"title_with_entities": {"text": "Your location"},
|
||||||
|
"properties": [
|
||||||
|
{"key": "width", "value": {"text": "545"}},
|
||||||
|
{"key": "height", "value": {"text": "280"}},
|
||||||
|
],
|
||||||
|
"url": "https://l.facebook.com/l.php?u=https%3A%2F%2Fwww.bing.com%2Fmaps%2Fdefault.aspx%3Fv%3D2%26pc%3DFACEBK%26mid%3D8100%26where1%3D55.4%252C%2B12.4322%26FORM%3DFBKPL1%26mkt%3Den-GB&h=a&s=1",
|
||||||
|
"deduplication_key": "400828513928715",
|
||||||
|
"action_links": [],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"xma_layout_info": None,
|
||||||
|
"target": {"__typename": "MessageLocation"},
|
||||||
|
"subattachments": [],
|
||||||
|
}
|
||||||
|
expected = LocationAttachment(latitude=55.4, longitude=12.4322, uid=400828513928715)
|
||||||
|
expected.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"
|
||||||
|
expected.image_width = 545
|
||||||
|
expected.image_height = 280
|
||||||
|
expected.url = "https://l.facebook.com/l.php?u=https%3A%2F%2Fwww.bing.com%2Fmaps%2Fdefault.aspx%3Fv%3D2%26pc%3DFACEBK%26mid%3D8100%26where1%3D55.4%252C%2B12.4322%26FORM%3DFBKPL1%26mkt%3Den-GB&h=a&s=1"
|
||||||
|
assert expected == LocationAttachment._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="need to gather test data")
|
||||||
|
def test_live_location_from_pull():
|
||||||
|
data = ...
|
||||||
|
assert LiveLocationAttachment(...) == LiveLocationAttachment._from_pull(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_live_location_from_graphql_expired():
|
||||||
|
data = {
|
||||||
|
"description": {"text": "Last update 4 Jan"},
|
||||||
|
"media": None,
|
||||||
|
"source": None,
|
||||||
|
"style_list": ["message_live_location", "fallback"],
|
||||||
|
"title_with_entities": {"text": "Location-sharing ended"},
|
||||||
|
"properties": [],
|
||||||
|
"url": "https://www.facebook.com/",
|
||||||
|
"deduplication_key": "2254535444791641",
|
||||||
|
"action_links": [],
|
||||||
|
"messaging_attribution": None,
|
||||||
|
"messenger_call_to_actions": [],
|
||||||
|
"target": {
|
||||||
|
"__typename": "MessageLiveLocation",
|
||||||
|
"live_location_id": "2254535444791641",
|
||||||
|
"is_expired": True,
|
||||||
|
"expiration_time": 1546626345,
|
||||||
|
"sender": {"id": "100007056224713"},
|
||||||
|
"coordinate": None,
|
||||||
|
"location_title": None,
|
||||||
|
"sender_destination": None,
|
||||||
|
"stop_reason": "CANCELED",
|
||||||
|
},
|
||||||
|
"subattachments": [],
|
||||||
|
}
|
||||||
|
expected = LiveLocationAttachment(
|
||||||
|
uid=2254535444791641,
|
||||||
|
name="Location-sharing ended",
|
||||||
|
expires_at=datetime.datetime(
|
||||||
|
2019, 1, 4, 18, 25, 45, tzinfo=datetime.timezone.utc
|
||||||
|
),
|
||||||
|
is_expired=True,
|
||||||
|
)
|
||||||
|
expected.url = "https://www.facebook.com/"
|
||||||
|
assert expected == LiveLocationAttachment._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="need to gather test data")
|
||||||
|
def test_live_location_from_graphql():
|
||||||
|
data = ...
|
||||||
|
assert LiveLocationAttachment(...) == LiveLocationAttachment._from_graphql(data)
|
140
tests/test_message.py
Normal file
140
tests/test_message.py
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import pytest
|
||||||
|
import fbchat
|
||||||
|
from fbchat._message import (
|
||||||
|
EmojiSize,
|
||||||
|
Mention,
|
||||||
|
Message,
|
||||||
|
graphql_to_extensible_attachment,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"tags,size",
|
||||||
|
[
|
||||||
|
(None, None),
|
||||||
|
(["hot_emoji_size:unknown"], None),
|
||||||
|
(["bunch", "of:different", "tags:large", "hot_emoji_size:s"], EmojiSize.SMALL),
|
||||||
|
(["hot_emoji_size:s"], EmojiSize.SMALL),
|
||||||
|
(["hot_emoji_size:m"], EmojiSize.MEDIUM),
|
||||||
|
(["hot_emoji_size:l"], EmojiSize.LARGE),
|
||||||
|
(["hot_emoji_size:small"], EmojiSize.SMALL),
|
||||||
|
(["hot_emoji_size:medium"], EmojiSize.MEDIUM),
|
||||||
|
(["hot_emoji_size:large"], EmojiSize.LARGE),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_emojisize_from_tags(tags, size):
|
||||||
|
assert size is EmojiSize._from_tags(tags)
|
||||||
|
|
||||||
|
|
||||||
|
def test_graphql_to_extensible_attachment_empty():
|
||||||
|
assert None is graphql_to_extensible_attachment({})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"obj,type_",
|
||||||
|
[
|
||||||
|
# UnsentMessage testing is done in test_attachment.py
|
||||||
|
(fbchat.LocationAttachment, "MessageLocation"),
|
||||||
|
(fbchat.LiveLocationAttachment, "MessageLiveLocation"),
|
||||||
|
(fbchat.ShareAttachment, "ExternalUrl"),
|
||||||
|
(fbchat.ShareAttachment, "Story"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_graphql_to_extensible_attachment_dispatch(monkeypatch, obj, type_):
|
||||||
|
monkeypatch.setattr(obj, "_from_graphql", lambda data: True)
|
||||||
|
data = {"story_attachment": {"target": {"__typename": type_}}}
|
||||||
|
assert graphql_to_extensible_attachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_message_format_mentions():
|
||||||
|
expected = Message(
|
||||||
|
text="Hey 'Peter'! My name is Michael",
|
||||||
|
mentions=[
|
||||||
|
Mention(thread_id="1234", offset=4, length=7),
|
||||||
|
Mention(thread_id="4321", offset=24, length=7),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert expected == Message.format_mentions(
|
||||||
|
"Hey {!r}! My name is {}", ("1234", "Peter"), ("4321", "Michael")
|
||||||
|
)
|
||||||
|
assert expected == Message.format_mentions(
|
||||||
|
"Hey {p!r}! My name is {}", ("4321", "Michael"), p=("1234", "Peter")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_message_get_forwarded_from_tags():
|
||||||
|
assert not Message._get_forwarded_from_tags(None)
|
||||||
|
assert not Message._get_forwarded_from_tags(["hot_emoji_size:unknown"])
|
||||||
|
assert Message._get_forwarded_from_tags(
|
||||||
|
["attachment:photo", "inbox", "sent", "source:chat:forward", "tq"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_message_to_send_data_minimal():
|
||||||
|
assert {"action_type": "ma-type:user-generated-message", "body": "Hey"} == Message(
|
||||||
|
text="Hey"
|
||||||
|
)._to_send_data()
|
||||||
|
|
||||||
|
|
||||||
|
def test_message_to_send_data_mentions():
|
||||||
|
msg = Message(
|
||||||
|
text="Hey 'Peter'! My name is Michael",
|
||||||
|
mentions=[
|
||||||
|
Mention(thread_id="1234", offset=4, length=7),
|
||||||
|
Mention(thread_id="4321", offset=24, length=7),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert {
|
||||||
|
"action_type": "ma-type:user-generated-message",
|
||||||
|
"body": "Hey 'Peter'! My name is Michael",
|
||||||
|
"profile_xmd[0][id]": "1234",
|
||||||
|
"profile_xmd[0][length]": 7,
|
||||||
|
"profile_xmd[0][offset]": 4,
|
||||||
|
"profile_xmd[0][type]": "p",
|
||||||
|
"profile_xmd[1][id]": "4321",
|
||||||
|
"profile_xmd[1][length]": 7,
|
||||||
|
"profile_xmd[1][offset]": 24,
|
||||||
|
"profile_xmd[1][type]": "p",
|
||||||
|
} == msg._to_send_data()
|
||||||
|
|
||||||
|
|
||||||
|
def test_message_to_send_data_sticker():
|
||||||
|
msg = Message(sticker=fbchat.Sticker(uid="123"))
|
||||||
|
assert {
|
||||||
|
"action_type": "ma-type:user-generated-message",
|
||||||
|
"sticker_id": "123",
|
||||||
|
} == msg._to_send_data()
|
||||||
|
|
||||||
|
|
||||||
|
def test_message_to_send_data_emoji():
|
||||||
|
msg = Message(text="😀", emoji_size=EmojiSize.LARGE)
|
||||||
|
assert {
|
||||||
|
"action_type": "ma-type:user-generated-message",
|
||||||
|
"body": "😀",
|
||||||
|
"tags[0]": "hot_emoji_size:large",
|
||||||
|
} == msg._to_send_data()
|
||||||
|
msg = Message(emoji_size=EmojiSize.LARGE)
|
||||||
|
assert {
|
||||||
|
"action_type": "ma-type:user-generated-message",
|
||||||
|
"sticker_id": "369239383222810",
|
||||||
|
} == msg._to_send_data()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="need to be added")
|
||||||
|
def test_message_to_send_data_quick_replies():
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="need to gather test data")
|
||||||
|
def test_message_from_graphql():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="need to gather test data")
|
||||||
|
def test_message_from_reply():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="need to gather test data")
|
||||||
|
def test_message_from_pull():
|
||||||
|
pass
|
20
tests/test_page.py
Normal file
20
tests/test_page.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from fbchat._page import Page
|
||||||
|
|
||||||
|
|
||||||
|
def test_page_from_graphql():
|
||||||
|
data = {
|
||||||
|
"id": "123456",
|
||||||
|
"name": "Some school",
|
||||||
|
"profile_picture": {"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/..."},
|
||||||
|
"url": "https://www.facebook.com/some-school/",
|
||||||
|
"category_type": "SCHOOL",
|
||||||
|
"city": None,
|
||||||
|
}
|
||||||
|
assert Page(
|
||||||
|
uid="123456",
|
||||||
|
photo="https://scontent-arn2-1.xx.fbcdn.net/v/...",
|
||||||
|
name="Some school",
|
||||||
|
url="https://www.facebook.com/some-school/",
|
||||||
|
city=None,
|
||||||
|
category="SCHOOL",
|
||||||
|
) == Page._from_graphql(data)
|
150
tests/test_plan.py
Normal file
150
tests/test_plan.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import datetime
|
||||||
|
from fbchat._plan import GuestStatus, Plan
|
||||||
|
|
||||||
|
|
||||||
|
def test_plan_properties():
|
||||||
|
plan = Plan(time=..., title=...)
|
||||||
|
plan.guests = {
|
||||||
|
"1234": GuestStatus.INVITED,
|
||||||
|
"2345": GuestStatus.INVITED,
|
||||||
|
"3456": GuestStatus.GOING,
|
||||||
|
"4567": GuestStatus.DECLINED,
|
||||||
|
}
|
||||||
|
assert set(plan.invited) == {"1234", "2345"}
|
||||||
|
assert plan.going == ["3456"]
|
||||||
|
assert plan.declined == ["4567"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_plan_from_pull():
|
||||||
|
data = {
|
||||||
|
"event_timezone": "",
|
||||||
|
"event_creator_id": "1234",
|
||||||
|
"event_id": "1111",
|
||||||
|
"event_type": "EVENT",
|
||||||
|
"event_track_rsvp": "1",
|
||||||
|
"event_title": "abc",
|
||||||
|
"event_time": "1500000000",
|
||||||
|
"event_seconds_to_notify_before": "3600",
|
||||||
|
"guest_state_list": (
|
||||||
|
'[{"guest_list_state":"INVITED","node":{"id":"1234"}},'
|
||||||
|
'{"guest_list_state":"INVITED","node":{"id":"2356"}},'
|
||||||
|
'{"guest_list_state":"DECLINED","node":{"id":"3456"}},'
|
||||||
|
'{"guest_list_state":"GOING","node":{"id":"4567"}}]'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
plan = Plan(
|
||||||
|
time=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
||||||
|
title="abc",
|
||||||
|
)
|
||||||
|
plan.uid = "1111"
|
||||||
|
plan.author_id = "1234"
|
||||||
|
plan.guests = {
|
||||||
|
"1234": GuestStatus.INVITED,
|
||||||
|
"2356": GuestStatus.INVITED,
|
||||||
|
"3456": GuestStatus.DECLINED,
|
||||||
|
"4567": GuestStatus.GOING,
|
||||||
|
}
|
||||||
|
assert plan == Plan._from_pull(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_plan_from_fetch():
|
||||||
|
data = {
|
||||||
|
"message_thread_id": 123456789,
|
||||||
|
"event_time": 1500000000,
|
||||||
|
"creator_id": 1234,
|
||||||
|
"event_time_updated_time": 1450000000,
|
||||||
|
"title": "abc",
|
||||||
|
"track_rsvp": 1,
|
||||||
|
"event_type": "EVENT",
|
||||||
|
"status": "created",
|
||||||
|
"message_id": "mid.xyz",
|
||||||
|
"seconds_to_notify_before": 3600,
|
||||||
|
"event_time_source": "user",
|
||||||
|
"repeat_mode": "once",
|
||||||
|
"creation_time": 1400000000,
|
||||||
|
"location_id": 0,
|
||||||
|
"location_name": None,
|
||||||
|
"latitude": "",
|
||||||
|
"longitude": "",
|
||||||
|
"event_id": 0,
|
||||||
|
"trigger_message_id": "",
|
||||||
|
"note": "",
|
||||||
|
"timezone_id": 0,
|
||||||
|
"end_time": 0,
|
||||||
|
"list_id": 0,
|
||||||
|
"payload_id": 0,
|
||||||
|
"cu_app": "",
|
||||||
|
"location_sharing_subtype": "",
|
||||||
|
"reminder_notif_param": [],
|
||||||
|
"workplace_meeting_id": "",
|
||||||
|
"genie_fbid": 0,
|
||||||
|
"galaxy": "",
|
||||||
|
"oid": 1111,
|
||||||
|
"type": 8128,
|
||||||
|
"is_active": True,
|
||||||
|
"location_address": None,
|
||||||
|
"event_members": {
|
||||||
|
"1234": "INVITED",
|
||||||
|
"2356": "INVITED",
|
||||||
|
"3456": "DECLINED",
|
||||||
|
"4567": "GOING",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
plan = Plan(
|
||||||
|
time=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
||||||
|
title="abc",
|
||||||
|
location="",
|
||||||
|
location_id="",
|
||||||
|
)
|
||||||
|
plan.uid = 1111
|
||||||
|
plan.author_id = 1234
|
||||||
|
plan.guests = {
|
||||||
|
"1234": GuestStatus.INVITED,
|
||||||
|
"2356": GuestStatus.INVITED,
|
||||||
|
"3456": GuestStatus.DECLINED,
|
||||||
|
"4567": GuestStatus.GOING,
|
||||||
|
}
|
||||||
|
assert plan == Plan._from_fetch(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_plan_from_graphql():
|
||||||
|
data = {
|
||||||
|
"id": "1111",
|
||||||
|
"lightweight_event_creator": {"id": "1234"},
|
||||||
|
"time": 1500000000,
|
||||||
|
"lightweight_event_type": "EVENT",
|
||||||
|
"location_name": None,
|
||||||
|
"location_coordinates": None,
|
||||||
|
"location_page": None,
|
||||||
|
"lightweight_event_status": "CREATED",
|
||||||
|
"note": "",
|
||||||
|
"repeat_mode": "ONCE",
|
||||||
|
"event_title": "abc",
|
||||||
|
"trigger_message": None,
|
||||||
|
"seconds_to_notify_before": 3600,
|
||||||
|
"allows_rsvp": True,
|
||||||
|
"related_event": None,
|
||||||
|
"event_reminder_members": {
|
||||||
|
"edges": [
|
||||||
|
{"node": {"id": "1234"}, "guest_list_state": "INVITED"},
|
||||||
|
{"node": {"id": "2356"}, "guest_list_state": "INVITED"},
|
||||||
|
{"node": {"id": "3456"}, "guest_list_state": "DECLINED"},
|
||||||
|
{"node": {"id": "4567"}, "guest_list_state": "GOING"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
plan = Plan(
|
||||||
|
time=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
||||||
|
title="abc",
|
||||||
|
location="",
|
||||||
|
location_id="",
|
||||||
|
)
|
||||||
|
plan.uid = "1111"
|
||||||
|
plan.author_id = "1234"
|
||||||
|
plan.guests = {
|
||||||
|
"1234": GuestStatus.INVITED,
|
||||||
|
"2356": GuestStatus.INVITED,
|
||||||
|
"3456": GuestStatus.DECLINED,
|
||||||
|
"4567": GuestStatus.GOING,
|
||||||
|
}
|
||||||
|
assert plan == Plan._from_graphql(data)
|
87
tests/test_poll.py
Normal file
87
tests/test_poll.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
from fbchat._poll import Poll, PollOption
|
||||||
|
|
||||||
|
|
||||||
|
def test_poll_option_from_graphql_unvoted():
|
||||||
|
data = {
|
||||||
|
"id": "123456789",
|
||||||
|
"text": "abc",
|
||||||
|
"total_count": 0,
|
||||||
|
"viewer_has_voted": "false",
|
||||||
|
"voters": [],
|
||||||
|
}
|
||||||
|
assert PollOption(
|
||||||
|
text="abc", vote=False, voters=[], votes_count=0, uid=123456789
|
||||||
|
) == PollOption._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_poll_option_from_graphql_voted():
|
||||||
|
data = {
|
||||||
|
"id": "123456789",
|
||||||
|
"text": "abc",
|
||||||
|
"total_count": 2,
|
||||||
|
"viewer_has_voted": "true",
|
||||||
|
"voters": ["1234", "2345"],
|
||||||
|
}
|
||||||
|
assert PollOption(
|
||||||
|
text="abc", vote=True, voters=["1234", "2345"], votes_count=2, uid=123456789
|
||||||
|
) == PollOption._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_poll_option_from_graphql_alternate_format():
|
||||||
|
# Format received when fetching poll options
|
||||||
|
data = {
|
||||||
|
"id": "123456789",
|
||||||
|
"text": "abc",
|
||||||
|
"viewer_has_voted": True,
|
||||||
|
"voters": {
|
||||||
|
"count": 2,
|
||||||
|
"edges": [{"node": {"id": "1234"}}, {"node": {"id": "2345"}}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert PollOption(
|
||||||
|
text="abc", vote=True, voters=["1234", "2345"], votes_count=2, uid=123456789
|
||||||
|
) == PollOption._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_poll_from_graphql():
|
||||||
|
data = {
|
||||||
|
"id": "123456789",
|
||||||
|
"text": "Some poll",
|
||||||
|
"total_count": 5,
|
||||||
|
"viewer_has_voted": "true",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"id": "1111",
|
||||||
|
"text": "Abc",
|
||||||
|
"total_count": 1,
|
||||||
|
"viewer_has_voted": "true",
|
||||||
|
"voters": ["1234"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2222",
|
||||||
|
"text": "Def",
|
||||||
|
"total_count": 2,
|
||||||
|
"viewer_has_voted": "false",
|
||||||
|
"voters": ["2345", "3456"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3333",
|
||||||
|
"text": "Ghi",
|
||||||
|
"total_count": 0,
|
||||||
|
"viewer_has_voted": "false",
|
||||||
|
"voters": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
assert Poll(
|
||||||
|
title="Some poll",
|
||||||
|
options=[
|
||||||
|
PollOption(text="Abc", vote=True, voters=["1234"], votes_count=1, uid=1111),
|
||||||
|
PollOption(
|
||||||
|
text="Def", vote=False, voters=["2345", "3456"], votes_count=2, uid=2222
|
||||||
|
),
|
||||||
|
PollOption(text="Ghi", vote=False, voters=[], votes_count=0, uid=3333),
|
||||||
|
],
|
||||||
|
options_count=5,
|
||||||
|
uid=123456789,
|
||||||
|
) == Poll._from_graphql(data)
|
49
tests/test_quick_reply.py
Normal file
49
tests/test_quick_reply.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from fbchat._quick_reply import (
|
||||||
|
QuickReplyText,
|
||||||
|
QuickReplyLocation,
|
||||||
|
QuickReplyPhoneNumber,
|
||||||
|
QuickReplyEmail,
|
||||||
|
graphql_to_quick_reply,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_minimal():
|
||||||
|
data = {
|
||||||
|
"content_type": "text",
|
||||||
|
"payload": None,
|
||||||
|
"external_payload": None,
|
||||||
|
"data": None,
|
||||||
|
"title": "A",
|
||||||
|
"image_url": None,
|
||||||
|
}
|
||||||
|
assert QuickReplyText(title="A") == graphql_to_quick_reply(data)
|
||||||
|
data = {"content_type": "location"}
|
||||||
|
assert QuickReplyLocation() == graphql_to_quick_reply(data)
|
||||||
|
data = {"content_type": "user_phone_number"}
|
||||||
|
assert QuickReplyPhoneNumber() == graphql_to_quick_reply(data)
|
||||||
|
data = {"content_type": "user_email"}
|
||||||
|
assert QuickReplyEmail() == graphql_to_quick_reply(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_text_full():
|
||||||
|
data = {
|
||||||
|
"content_type": "text",
|
||||||
|
"title": "A",
|
||||||
|
"payload": "Some payload",
|
||||||
|
"image_url": "https://example.com/image.jpg",
|
||||||
|
"data": None,
|
||||||
|
}
|
||||||
|
assert QuickReplyText(
|
||||||
|
payload="Some payload",
|
||||||
|
data=None,
|
||||||
|
is_response=False,
|
||||||
|
title="A",
|
||||||
|
image_url="https://example.com/image.jpg",
|
||||||
|
) == graphql_to_quick_reply(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_with_is_response():
|
||||||
|
data = {"content_type": "text"}
|
||||||
|
assert QuickReplyText(is_response=True) == graphql_to_quick_reply(
|
||||||
|
data, is_response=True
|
||||||
|
)
|
86
tests/test_sticker.py
Normal file
86
tests/test_sticker.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import pytest
|
||||||
|
from fbchat._sticker import Sticker
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_graphql_none():
|
||||||
|
assert None == Sticker._from_graphql(None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_graphql_minimal():
|
||||||
|
assert Sticker(uid=1) == Sticker._from_graphql({"id": 1})
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_graphql_normal():
|
||||||
|
assert Sticker(
|
||||||
|
uid="369239383222810",
|
||||||
|
pack="227877430692340",
|
||||||
|
is_animated=False,
|
||||||
|
medium_sprite_image=None,
|
||||||
|
large_sprite_image=None,
|
||||||
|
frames_per_row=None,
|
||||||
|
frames_per_col=None,
|
||||||
|
frame_rate=None,
|
||||||
|
url="https://scontent-arn2-1.xx.fbcdn.net/v/redacted.png",
|
||||||
|
width=274,
|
||||||
|
height=274,
|
||||||
|
label="Like, thumbs up",
|
||||||
|
) == Sticker._from_graphql(
|
||||||
|
{
|
||||||
|
"id": "369239383222810",
|
||||||
|
"pack": {"id": "227877430692340"},
|
||||||
|
"label": "Like, thumbs up",
|
||||||
|
"frame_count": 1,
|
||||||
|
"frame_rate": 83,
|
||||||
|
"frames_per_row": 1,
|
||||||
|
"frames_per_column": 1,
|
||||||
|
"sprite_image_2x": None,
|
||||||
|
"sprite_image": None,
|
||||||
|
"padded_sprite_image": None,
|
||||||
|
"padded_sprite_image_2x": None,
|
||||||
|
"url": "https://scontent-arn2-1.xx.fbcdn.net/v/redacted.png",
|
||||||
|
"height": 274,
|
||||||
|
"width": 274,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_graphql_animated():
|
||||||
|
assert Sticker(
|
||||||
|
uid="144885035685763",
|
||||||
|
pack="350357561732812",
|
||||||
|
is_animated=True,
|
||||||
|
medium_sprite_image="https://scontent-arn2-1.xx.fbcdn.net/v/redacted2.png",
|
||||||
|
large_sprite_image="https://scontent-arn2-1.fbcdn.net/v/redacted3.png",
|
||||||
|
frames_per_row=2,
|
||||||
|
frames_per_col=2,
|
||||||
|
frame_rate=142,
|
||||||
|
url="https://scontent-arn2-1.fbcdn.net/v/redacted1.png",
|
||||||
|
width=240,
|
||||||
|
height=293,
|
||||||
|
label="Love, cat with heart",
|
||||||
|
) == Sticker._from_graphql(
|
||||||
|
{
|
||||||
|
"id": "144885035685763",
|
||||||
|
"pack": {"id": "350357561732812"},
|
||||||
|
"label": "Love, cat with heart",
|
||||||
|
"frame_count": 4,
|
||||||
|
"frame_rate": 142,
|
||||||
|
"frames_per_row": 2,
|
||||||
|
"frames_per_column": 2,
|
||||||
|
"sprite_image_2x": {
|
||||||
|
"uri": "https://scontent-arn2-1.fbcdn.net/v/redacted3.png"
|
||||||
|
},
|
||||||
|
"sprite_image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/redacted2.png"
|
||||||
|
},
|
||||||
|
"padded_sprite_image": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/unused1.png"
|
||||||
|
},
|
||||||
|
"padded_sprite_image_2x": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/unused2.png"
|
||||||
|
},
|
||||||
|
"url": "https://scontent-arn2-1.fbcdn.net/v/redacted1.png",
|
||||||
|
"height": 293,
|
||||||
|
"width": 240,
|
||||||
|
}
|
||||||
|
)
|
65
tests/test_thread.py
Normal file
65
tests/test_thread.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import pytest
|
||||||
|
import fbchat
|
||||||
|
from fbchat._thread import ThreadType, ThreadColor, Thread
|
||||||
|
|
||||||
|
|
||||||
|
def test_thread_type_to_class():
|
||||||
|
assert fbchat.User == ThreadType.USER._to_class()
|
||||||
|
assert fbchat.Group == ThreadType.GROUP._to_class()
|
||||||
|
assert fbchat.Page == ThreadType.PAGE._to_class()
|
||||||
|
|
||||||
|
|
||||||
|
def test_thread_color_from_graphql():
|
||||||
|
assert None is ThreadColor._from_graphql(None)
|
||||||
|
assert ThreadColor.MESSENGER_BLUE is ThreadColor._from_graphql("")
|
||||||
|
assert ThreadColor.VIKING is ThreadColor._from_graphql("FF44BEC7")
|
||||||
|
assert ThreadColor._from_graphql("DEADBEEF") is getattr(
|
||||||
|
ThreadColor, "UNKNOWN_#ADBEEF"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_thread_parse_customization_info_empty():
|
||||||
|
assert {} == Thread._parse_customization_info(None)
|
||||||
|
assert {} == Thread._parse_customization_info({"customization_info": None})
|
||||||
|
|
||||||
|
|
||||||
|
def test_thread_parse_customization_info_group():
|
||||||
|
data = {
|
||||||
|
"thread_key": {"thread_fbid": "11111", "other_user_id": None},
|
||||||
|
"customization_info": {
|
||||||
|
"emoji": "🎉",
|
||||||
|
"participant_customizations": [
|
||||||
|
{"participant_id": "123456789", "nickname": "A"},
|
||||||
|
{"participant_id": "987654321", "nickname": "B"},
|
||||||
|
],
|
||||||
|
"outgoing_bubble_color": "FFFF5CA1",
|
||||||
|
},
|
||||||
|
"customization_enabled": True,
|
||||||
|
"thread_type": "GROUP",
|
||||||
|
# ... Other irrelevant fields
|
||||||
|
}
|
||||||
|
expected = {
|
||||||
|
"emoji": "🎉",
|
||||||
|
"color": ThreadColor.BRILLIANT_ROSE,
|
||||||
|
"nicknames": {"123456789": "A", "987654321": "B"},
|
||||||
|
}
|
||||||
|
assert expected == Thread._parse_customization_info(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_thread_parse_customization_info_user():
|
||||||
|
data = {
|
||||||
|
"thread_key": {"thread_fbid": None, "other_user_id": "987654321"},
|
||||||
|
"customization_info": {
|
||||||
|
"emoji": None,
|
||||||
|
"participant_customizations": [
|
||||||
|
{"participant_id": "123456789", "nickname": "A"},
|
||||||
|
{"participant_id": "987654321", "nickname": "B"},
|
||||||
|
],
|
||||||
|
"outgoing_bubble_color": None,
|
||||||
|
},
|
||||||
|
"customization_enabled": True,
|
||||||
|
"thread_type": "ONE_TO_ONE",
|
||||||
|
# ... Other irrelevant fields
|
||||||
|
}
|
||||||
|
expected = {"emoji": None, "color": None, "own_nickname": "A", "nickname": "B"}
|
||||||
|
assert expected == Thread._parse_customization_info(data)
|
194
tests/test_user.py
Normal file
194
tests/test_user.py
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import pytest
|
||||||
|
import datetime
|
||||||
|
from fbchat._user import User, ActiveStatus
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_from_graphql():
|
||||||
|
data = {
|
||||||
|
"id": "1234",
|
||||||
|
"name": "Abc Def Ghi",
|
||||||
|
"first_name": "Abc",
|
||||||
|
"last_name": "Ghi",
|
||||||
|
"profile_picture": {"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/..."},
|
||||||
|
"is_viewer_friend": True,
|
||||||
|
"url": "https://www.facebook.com/profile.php?id=1234",
|
||||||
|
"gender": "FEMALE",
|
||||||
|
"viewer_affinity": 0.4560002,
|
||||||
|
}
|
||||||
|
assert User(
|
||||||
|
uid="1234",
|
||||||
|
photo="https://scontent-arn2-1.xx.fbcdn.net/v/...",
|
||||||
|
name="Abc Def Ghi",
|
||||||
|
url="https://www.facebook.com/profile.php?id=1234",
|
||||||
|
first_name="Abc",
|
||||||
|
last_name="Ghi",
|
||||||
|
is_friend=True,
|
||||||
|
gender="female_singular",
|
||||||
|
) == User._from_graphql(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_from_thread_fetch():
|
||||||
|
data = {
|
||||||
|
"thread_key": {"thread_fbid": None, "other_user_id": "1234"},
|
||||||
|
"name": None,
|
||||||
|
"last_message": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"snippet": "aaa",
|
||||||
|
"message_sender": {"messaging_actor": {"id": "1234"}},
|
||||||
|
"timestamp_precise": "1500000000000",
|
||||||
|
"commerce_message_type": None,
|
||||||
|
"extensible_attachment": None,
|
||||||
|
"sticker": None,
|
||||||
|
"blob_attachments": [],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unread_count": 0,
|
||||||
|
"messages_count": 1111,
|
||||||
|
"image": None,
|
||||||
|
"updated_time_precise": "1500000000000",
|
||||||
|
"mute_until": None,
|
||||||
|
"is_pin_protected": False,
|
||||||
|
"is_viewer_subscribed": True,
|
||||||
|
"thread_queue_enabled": False,
|
||||||
|
"folder": "INBOX",
|
||||||
|
"has_viewer_archived": False,
|
||||||
|
"is_page_follow_up": False,
|
||||||
|
"cannot_reply_reason": None,
|
||||||
|
"ephemeral_ttl_mode": 0,
|
||||||
|
"customization_info": {
|
||||||
|
"emoji": None,
|
||||||
|
"participant_customizations": [
|
||||||
|
{"participant_id": "4321", "nickname": "B"},
|
||||||
|
{"participant_id": "1234", "nickname": "A"},
|
||||||
|
],
|
||||||
|
"outgoing_bubble_color": None,
|
||||||
|
},
|
||||||
|
"thread_admins": [],
|
||||||
|
"approval_mode": None,
|
||||||
|
"joinable_mode": {"mode": "0", "link": ""},
|
||||||
|
"thread_queue_metadata": None,
|
||||||
|
"event_reminders": {"nodes": []},
|
||||||
|
"montage_thread": None,
|
||||||
|
"last_read_receipt": {"nodes": [{"timestamp_precise": "1500000050000"}]},
|
||||||
|
"related_page_thread": None,
|
||||||
|
"rtc_call_data": {
|
||||||
|
"call_state": "NO_ONGOING_CALL",
|
||||||
|
"server_info_data": "",
|
||||||
|
"initiator": None,
|
||||||
|
},
|
||||||
|
"associated_object": None,
|
||||||
|
"privacy_mode": 1,
|
||||||
|
"reactions_mute_mode": "REACTIONS_NOT_MUTED",
|
||||||
|
"mentions_mute_mode": "MENTIONS_NOT_MUTED",
|
||||||
|
"customization_enabled": True,
|
||||||
|
"thread_type": "ONE_TO_ONE",
|
||||||
|
"participant_add_mode_as_string": None,
|
||||||
|
"is_canonical_neo_user": False,
|
||||||
|
"participants_event_status": [],
|
||||||
|
"page_comm_item": None,
|
||||||
|
"all_participants": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"messaging_actor": {
|
||||||
|
"id": "1234",
|
||||||
|
"__typename": "User",
|
||||||
|
"name": "Abc Def Ghi",
|
||||||
|
"gender": "FEMALE",
|
||||||
|
"url": "https://www.facebook.com/profile.php?id=1234",
|
||||||
|
"big_image_src": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/..."
|
||||||
|
},
|
||||||
|
"short_name": "Abc",
|
||||||
|
"username": "",
|
||||||
|
"is_viewer_friend": True,
|
||||||
|
"is_messenger_user": True,
|
||||||
|
"is_verified": False,
|
||||||
|
"is_message_blocked_by_viewer": False,
|
||||||
|
"is_viewer_coworker": False,
|
||||||
|
"is_employee": None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"messaging_actor": {
|
||||||
|
"id": "4321",
|
||||||
|
"__typename": "User",
|
||||||
|
"name": "Aaa Bbb Ccc",
|
||||||
|
"gender": "NEUTER",
|
||||||
|
"url": "https://www.facebook.com/aaabbbccc",
|
||||||
|
"big_image_src": {
|
||||||
|
"uri": "https://scontent-arn2-1.xx.fbcdn.net/v/..."
|
||||||
|
},
|
||||||
|
"short_name": "Aaa",
|
||||||
|
"username": "aaabbbccc",
|
||||||
|
"is_viewer_friend": False,
|
||||||
|
"is_messenger_user": True,
|
||||||
|
"is_verified": False,
|
||||||
|
"is_message_blocked_by_viewer": False,
|
||||||
|
"is_viewer_coworker": False,
|
||||||
|
"is_employee": None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"read_receipts": ...,
|
||||||
|
"delivery_receipts": ...,
|
||||||
|
}
|
||||||
|
assert User(
|
||||||
|
uid="1234",
|
||||||
|
photo="https://scontent-arn2-1.xx.fbcdn.net/v/...",
|
||||||
|
name="Abc Def Ghi",
|
||||||
|
last_active=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
||||||
|
message_count=1111,
|
||||||
|
url="https://www.facebook.com/profile.php?id=1234",
|
||||||
|
first_name="Abc",
|
||||||
|
last_name="Def Ghi",
|
||||||
|
is_friend=True,
|
||||||
|
gender="female_singular",
|
||||||
|
nickname="A",
|
||||||
|
own_nickname="B",
|
||||||
|
color=None,
|
||||||
|
emoji=None,
|
||||||
|
) == User._from_thread_fetch(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_from_all_fetch():
|
||||||
|
data = {
|
||||||
|
"id": "1234",
|
||||||
|
"name": "Abc Def Ghi",
|
||||||
|
"firstName": "Abc",
|
||||||
|
"vanity": "",
|
||||||
|
"thumbSrc": "https://scontent-arn2-1.xx.fbcdn.net/v/...",
|
||||||
|
"uri": "https://www.facebook.com/profile.php?id=1234",
|
||||||
|
"gender": 1,
|
||||||
|
"i18nGender": 2,
|
||||||
|
"type": "friend",
|
||||||
|
"is_friend": True,
|
||||||
|
"mThumbSrcSmall": None,
|
||||||
|
"mThumbSrcLarge": None,
|
||||||
|
"dir": None,
|
||||||
|
"searchTokens": ["Abc", "Ghi"],
|
||||||
|
"alternateName": "",
|
||||||
|
"is_nonfriend_messenger_contact": False,
|
||||||
|
"is_blocked": False,
|
||||||
|
}
|
||||||
|
assert User(
|
||||||
|
uid="1234",
|
||||||
|
photo="https://scontent-arn2-1.xx.fbcdn.net/v/...",
|
||||||
|
name="Abc Def Ghi",
|
||||||
|
url="https://www.facebook.com/profile.php?id=1234",
|
||||||
|
first_name="Abc",
|
||||||
|
is_friend=True,
|
||||||
|
gender="female_singular",
|
||||||
|
) == User._from_all_fetch(data)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="can't gather test data, the pulling is broken")
|
||||||
|
def test_active_status_from_chatproxy_presence():
|
||||||
|
assert ActiveStatus() == ActiveStatus._from_chatproxy_presence(data)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="can't gather test data, the pulling is broken")
|
||||||
|
def test_active_status_from_buddylist_overlay():
|
||||||
|
assert ActiveStatus() == ActiveStatus._from_buddylist_overlay(data)
|
309
tests/test_util.py
Normal file
309
tests/test_util.py
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
import pytest
|
||||||
|
import fbchat
|
||||||
|
import datetime
|
||||||
|
from fbchat._util import (
|
||||||
|
strip_json_cruft,
|
||||||
|
parse_json,
|
||||||
|
str_base,
|
||||||
|
generate_message_id,
|
||||||
|
get_signature_id,
|
||||||
|
handle_payload_error,
|
||||||
|
handle_graphql_errors,
|
||||||
|
check_http_code,
|
||||||
|
get_jsmods_require,
|
||||||
|
require_list,
|
||||||
|
mimetype_to_key,
|
||||||
|
get_url_parameter,
|
||||||
|
prefix_url,
|
||||||
|
seconds_to_datetime,
|
||||||
|
millis_to_datetime,
|
||||||
|
datetime_to_seconds,
|
||||||
|
datetime_to_millis,
|
||||||
|
seconds_to_timedelta,
|
||||||
|
millis_to_timedelta,
|
||||||
|
timedelta_to_seconds,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_strip_json_cruft():
|
||||||
|
assert strip_json_cruft('for(;;);{"abc": "def"}') == '{"abc": "def"}'
|
||||||
|
assert strip_json_cruft('{"abc": "def"}') == '{"abc": "def"}'
|
||||||
|
|
||||||
|
|
||||||
|
def test_strip_json_cruft_invalid():
|
||||||
|
with pytest.raises(AttributeError):
|
||||||
|
strip_json_cruft(None)
|
||||||
|
with pytest.raises(fbchat.FBchatException, match="No JSON object found"):
|
||||||
|
strip_json_cruft("No JSON object here!")
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_json():
|
||||||
|
assert parse_json('{"a":"b"}') == {"a": "b"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_json_invalid():
|
||||||
|
with pytest.raises(fbchat.FBchatFacebookError, match="Error while parsing JSON"):
|
||||||
|
parse_json("No JSON object here!")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"number,base,expected",
|
||||||
|
[
|
||||||
|
(123, 10, "123"),
|
||||||
|
(1, 36, "1"),
|
||||||
|
(10, 36, "a"),
|
||||||
|
(123, 36, "3f"),
|
||||||
|
(1000, 36, "rs"),
|
||||||
|
(123456789, 36, "21i3v9"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_str_base(number, base, expected):
|
||||||
|
assert str_base(number, base) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_message_id():
|
||||||
|
# Returns random output, so hard to test more thoroughly
|
||||||
|
generate_message_id("abc")
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_signature_id():
|
||||||
|
# Returns random output, so hard to test more thoroughly
|
||||||
|
get_signature_id()
|
||||||
|
|
||||||
|
|
||||||
|
ERROR_DATA = [
|
||||||
|
(
|
||||||
|
fbchat._exception.FBchatNotLoggedIn,
|
||||||
|
1357001,
|
||||||
|
"Not logged in",
|
||||||
|
"Please log in to continue.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
fbchat._exception.FBchatPleaseRefresh,
|
||||||
|
1357004,
|
||||||
|
"Sorry, something went wrong",
|
||||||
|
"Please try closing and re-opening your browser window.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
fbchat._exception.FBchatInvalidParameters,
|
||||||
|
1357031,
|
||||||
|
"This content is no longer available",
|
||||||
|
(
|
||||||
|
"The content you requested cannot be displayed at the moment. It may be"
|
||||||
|
" temporarily unavailable, the link you clicked on may have expired or you"
|
||||||
|
" may not have permission to view this page."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
fbchat._exception.FBchatInvalidParameters,
|
||||||
|
1545010,
|
||||||
|
"Messages Unavailable",
|
||||||
|
(
|
||||||
|
"Sorry, messages are temporarily unavailable."
|
||||||
|
" Please try again in a few minutes."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
fbchat.FBchatFacebookError,
|
||||||
|
1545026,
|
||||||
|
"Unable to Attach File",
|
||||||
|
(
|
||||||
|
"The type of file you're trying to attach isn't allowed."
|
||||||
|
" Please try again with a different format."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
fbchat._exception.FBchatInvalidParameters,
|
||||||
|
1545003,
|
||||||
|
"Invalid action",
|
||||||
|
"You cannot perform that action.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
fbchat.FBchatFacebookError,
|
||||||
|
1545012,
|
||||||
|
"Temporary Failure",
|
||||||
|
"There was a temporary error, please try again.",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("exception,code,description,summary", ERROR_DATA)
|
||||||
|
def test_handle_payload_error(exception, code, summary, description):
|
||||||
|
data = {"error": code, "errorSummary": summary, "errorDescription": description}
|
||||||
|
with pytest.raises(exception, match=r"Error #\d+ when sending request"):
|
||||||
|
handle_payload_error(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_payload_error_no_error():
|
||||||
|
assert handle_payload_error({}) is None
|
||||||
|
assert handle_payload_error({"payload": {"abc": ["Something", "else"]}}) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_graphql_errors():
|
||||||
|
error = {
|
||||||
|
"allow_user_retry": False,
|
||||||
|
"api_error_code": -1,
|
||||||
|
"code": 1675030,
|
||||||
|
"debug_info": None,
|
||||||
|
"description": "Error performing query.",
|
||||||
|
"fbtrace_id": "CLkuLR752sB",
|
||||||
|
"is_silent": False,
|
||||||
|
"is_transient": False,
|
||||||
|
"message": (
|
||||||
|
'Errors while executing operation "MessengerThreadSharedLinks":'
|
||||||
|
" At Query.message_thread: Field implementation threw an exception."
|
||||||
|
" Check your server logs for more information."
|
||||||
|
),
|
||||||
|
"path": ["message_thread"],
|
||||||
|
"query_path": None,
|
||||||
|
"requires_reauth": False,
|
||||||
|
"severity": "CRITICAL",
|
||||||
|
"summary": "Query error",
|
||||||
|
}
|
||||||
|
with pytest.raises(fbchat.FBchatFacebookError, match="GraphQL error"):
|
||||||
|
handle_graphql_errors({"data": {"message_thread": None}, "errors": [error]})
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_graphql_errors_singular_error_key():
|
||||||
|
with pytest.raises(fbchat.FBchatFacebookError, match="GraphQL error #123"):
|
||||||
|
handle_graphql_errors({"error": {"code": 123}})
|
||||||
|
|
||||||
|
|
||||||
|
def test_handle_graphql_errors_no_error():
|
||||||
|
assert handle_graphql_errors({"data": {"message_thread": None}}) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_http_code():
|
||||||
|
with pytest.raises(fbchat.FBchatFacebookError):
|
||||||
|
check_http_code(400)
|
||||||
|
with pytest.raises(fbchat.FBchatFacebookError):
|
||||||
|
check_http_code(500)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_http_code_404_handling():
|
||||||
|
with pytest.raises(fbchat.FBchatFacebookError, match="invalid id"):
|
||||||
|
check_http_code(404)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_http_code_no_error():
|
||||||
|
assert check_http_code(200) is None
|
||||||
|
assert check_http_code(302) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_jsmods_require_get_image_url():
|
||||||
|
data = {
|
||||||
|
"__ar": 1,
|
||||||
|
"payload": None,
|
||||||
|
"jsmods": {
|
||||||
|
"require": [
|
||||||
|
[
|
||||||
|
"ServerRedirect",
|
||||||
|
"redirectPageTo",
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
"https://scontent-arn2-1.xx.fbcdn.net/v/image.png&dl=1",
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
["TuringClientSignalCollectionTrigger", ..., [], ...],
|
||||||
|
["TuringClientSignalCollectionTrigger", "retrieveSignals", [], ...],
|
||||||
|
["BanzaiODS"],
|
||||||
|
["BanzaiScuba"],
|
||||||
|
],
|
||||||
|
"define": ...,
|
||||||
|
},
|
||||||
|
"js": ...,
|
||||||
|
"css": ...,
|
||||||
|
"bootloadable": ...,
|
||||||
|
"resource_map": ...,
|
||||||
|
"ixData": {},
|
||||||
|
"bxData": {},
|
||||||
|
"gkxData": ...,
|
||||||
|
"qexData": {},
|
||||||
|
"lid": "123",
|
||||||
|
}
|
||||||
|
url = "https://scontent-arn2-1.xx.fbcdn.net/v/image.png&dl=1"
|
||||||
|
assert get_jsmods_require(data, 3) == url
|
||||||
|
|
||||||
|
|
||||||
|
def test_require_list():
|
||||||
|
assert require_list([]) == set()
|
||||||
|
assert require_list([1, 2, 2]) == {1, 2}
|
||||||
|
assert require_list(1) == {1}
|
||||||
|
assert require_list("abc") == {"abc"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_mimetype_to_key():
|
||||||
|
assert mimetype_to_key(None) == "file_id"
|
||||||
|
assert mimetype_to_key("image/gif") == "gif_id"
|
||||||
|
assert mimetype_to_key("video/mp4") == "video_id"
|
||||||
|
assert mimetype_to_key("video/quicktime") == "video_id"
|
||||||
|
assert mimetype_to_key("image/png") == "image_id"
|
||||||
|
assert mimetype_to_key("image/jpeg") == "image_id"
|
||||||
|
assert mimetype_to_key("audio/mpeg") == "audio_id"
|
||||||
|
assert mimetype_to_key("application/json") == "file_id"
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_url_parameter():
|
||||||
|
assert get_url_parameter("http://example.com?a=b&c=d", "c") == "d"
|
||||||
|
assert get_url_parameter("http://example.com?a=b&a=c", "a") == "b"
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
get_url_parameter("http://example.com", "a")
|
||||||
|
|
||||||
|
|
||||||
|
def test_prefix_url():
|
||||||
|
assert prefix_url("/") == "https://www.facebook.com/"
|
||||||
|
assert prefix_url("/abc") == "https://www.facebook.com/abc"
|
||||||
|
assert prefix_url("abc") == "abc"
|
||||||
|
assert prefix_url("https://m.facebook.com/abc") == "https://m.facebook.com/abc"
|
||||||
|
|
||||||
|
|
||||||
|
DT_0 = datetime.datetime(1970, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
|
||||||
|
DT = datetime.datetime(2018, 11, 16, 1, 51, 4, 162000, tzinfo=datetime.timezone.utc)
|
||||||
|
DT_NO_TIMEZONE = datetime.datetime(2018, 11, 16, 1, 51, 4, 162000)
|
||||||
|
|
||||||
|
|
||||||
|
def test_seconds_to_datetime():
|
||||||
|
assert seconds_to_datetime(0) == DT_0
|
||||||
|
assert seconds_to_datetime(1542333064.162) == DT
|
||||||
|
assert seconds_to_datetime(1542333064.162) != DT_NO_TIMEZONE
|
||||||
|
|
||||||
|
|
||||||
|
def test_millis_to_datetime():
|
||||||
|
assert millis_to_datetime(0) == DT_0
|
||||||
|
assert millis_to_datetime(1542333064162) == DT
|
||||||
|
assert millis_to_datetime(1542333064162) != DT_NO_TIMEZONE
|
||||||
|
|
||||||
|
|
||||||
|
def test_datetime_to_seconds():
|
||||||
|
assert datetime_to_seconds(DT_0) == 0
|
||||||
|
assert datetime_to_seconds(DT) == 1542333064 # Rounded
|
||||||
|
datetime_to_seconds(DT_NO_TIMEZONE) # Depends on system timezone
|
||||||
|
|
||||||
|
|
||||||
|
def test_datetime_to_millis():
|
||||||
|
assert datetime_to_millis(DT_0) == 0
|
||||||
|
assert datetime_to_millis(DT) == 1542333064162
|
||||||
|
datetime_to_millis(DT_NO_TIMEZONE) # Depends on system timezone
|
||||||
|
|
||||||
|
|
||||||
|
def test_seconds_to_timedelta():
|
||||||
|
assert seconds_to_timedelta(0.001) == datetime.timedelta(microseconds=1000)
|
||||||
|
assert seconds_to_timedelta(1) == datetime.timedelta(seconds=1)
|
||||||
|
assert seconds_to_timedelta(3600) == datetime.timedelta(hours=1)
|
||||||
|
assert seconds_to_timedelta(86400) == datetime.timedelta(days=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_millis_to_timedelta():
|
||||||
|
assert millis_to_timedelta(1) == datetime.timedelta(microseconds=1000)
|
||||||
|
assert millis_to_timedelta(1000) == datetime.timedelta(seconds=1)
|
||||||
|
assert millis_to_timedelta(3600000) == datetime.timedelta(hours=1)
|
||||||
|
assert millis_to_timedelta(86400000) == datetime.timedelta(days=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_timedelta_to_seconds():
|
||||||
|
assert timedelta_to_seconds(datetime.timedelta(microseconds=1000)) == 0 # Rounded
|
||||||
|
assert timedelta_to_seconds(datetime.timedelta(seconds=1)) == 1
|
||||||
|
assert timedelta_to_seconds(datetime.timedelta(hours=1)) == 3600
|
||||||
|
assert timedelta_to_seconds(datetime.timedelta(days=1)) == 86400
|
Reference in New Issue
Block a user