@@ -2,7 +2,7 @@ import attr
|
||||
from . import _util
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class Attachment:
|
||||
"""Represents a Facebook attachment."""
|
||||
|
||||
@@ -10,12 +10,12 @@ class Attachment:
|
||||
uid = attr.ib(None)
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class UnsentMessage(Attachment):
|
||||
"""Represents an unsent message attachment."""
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class ShareAttachment(Attachment):
|
||||
"""Represents a shared item (e.g. URL) attachment."""
|
||||
|
||||
|
@@ -3,7 +3,7 @@ from . import _util
|
||||
from ._attachment import Attachment
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class FileAttachment(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):
|
||||
"""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):
|
||||
"""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):
|
||||
"""Represents a video that has been sent as a Facebook attachment."""
|
||||
|
||||
|
@@ -3,10 +3,12 @@ from . import _util, _plan
|
||||
from ._thread import ThreadType, Thread
|
||||
|
||||
|
||||
@attr.s(cmp=False, init=False)
|
||||
@attr.s
|
||||
class Group(Thread):
|
||||
"""Represents a Facebook group. Inherits `Thread`."""
|
||||
|
||||
type = ThreadType.GROUP
|
||||
|
||||
#: 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)
|
||||
#: A dictionary, containing user nicknames mapped to their IDs
|
||||
@@ -26,38 +28,6 @@ class Group(Thread):
|
||||
# Link for joining group
|
||||
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
|
||||
def _from_graphql(cls, data):
|
||||
if data.get("image") is None:
|
||||
|
@@ -3,7 +3,7 @@ from ._attachment import Attachment
|
||||
from . import _util
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class LocationAttachment(Attachment):
|
||||
"""Represents a user location.
|
||||
|
||||
@@ -53,7 +53,7 @@ class LocationAttachment(Attachment):
|
||||
return rtn
|
||||
|
||||
|
||||
@attr.s(cmp=False, init=False)
|
||||
@attr.s
|
||||
class LiveLocationAttachment(LocationAttachment):
|
||||
"""Represents a live user location."""
|
||||
|
||||
@@ -64,11 +64,6 @@ class LiveLocationAttachment(LocationAttachment):
|
||||
#: True if live location is expired
|
||||
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
|
||||
def _from_pull(cls, data):
|
||||
return cls(
|
||||
@@ -96,7 +91,7 @@ class LiveLocationAttachment(LocationAttachment):
|
||||
if target.get("coordinate")
|
||||
else None,
|
||||
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"),
|
||||
)
|
||||
media = data.get("media")
|
||||
|
@@ -42,7 +42,7 @@ class MessageReaction(Enum):
|
||||
NO = "👎"
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class Mention:
|
||||
"""Represents a ``@mention``."""
|
||||
|
||||
@@ -54,7 +54,7 @@ class Mention:
|
||||
length = attr.ib(10)
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class Message:
|
||||
"""Represents a Facebook message."""
|
||||
|
||||
|
@@ -3,10 +3,12 @@ from . import _plan
|
||||
from ._thread import ThreadType, Thread
|
||||
|
||||
|
||||
@attr.s(cmp=False, init=False)
|
||||
@attr.s
|
||||
class Page(Thread):
|
||||
"""Represents a Facebook page. Inherits `Thread`."""
|
||||
|
||||
type = ThreadType.PAGE
|
||||
|
||||
#: The page's custom URL
|
||||
url = attr.ib(None)
|
||||
#: The name of the page's location city
|
||||
@@ -18,23 +20,6 @@ class Page(Thread):
|
||||
#: The page's category
|
||||
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
|
||||
def _from_graphql(cls, data):
|
||||
if data.get("profile_picture") is None:
|
||||
|
@@ -10,7 +10,7 @@ class GuestStatus(Enum):
|
||||
DECLINED = 3
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class Plan:
|
||||
"""Represents a plan."""
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import attr
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class Poll:
|
||||
"""Represents a poll."""
|
||||
|
||||
@@ -24,7 +24,7 @@ class Poll:
|
||||
)
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class PollOption:
|
||||
"""Represents a poll option."""
|
||||
|
||||
|
@@ -2,7 +2,7 @@ import attr
|
||||
from ._attachment import Attachment
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class QuickReply:
|
||||
"""Represents a quick reply."""
|
||||
|
||||
@@ -16,7 +16,7 @@ class QuickReply:
|
||||
is_response = attr.ib(False)
|
||||
|
||||
|
||||
@attr.s(cmp=False, init=False)
|
||||
@attr.s
|
||||
class QuickReplyText(QuickReply):
|
||||
"""Represents a text quick reply."""
|
||||
|
||||
@@ -27,25 +27,16 @@ class QuickReplyText(QuickReply):
|
||||
#: Type of the quick reply
|
||||
_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(cmp=False, init=False)
|
||||
@attr.s
|
||||
class QuickReplyLocation(QuickReply):
|
||||
"""Represents a location quick reply (Doesn't work on mobile)."""
|
||||
|
||||
#: Type of the quick reply
|
||||
_type = "location"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(QuickReplyLocation, self).__init__(**kwargs)
|
||||
self.is_response = False
|
||||
|
||||
|
||||
@attr.s(cmp=False, init=False)
|
||||
@attr.s
|
||||
class QuickReplyPhoneNumber(QuickReply):
|
||||
"""Represents a phone number quick reply (Doesn't work on mobile)."""
|
||||
|
||||
@@ -54,12 +45,8 @@ class QuickReplyPhoneNumber(QuickReply):
|
||||
#: Type of the quick reply
|
||||
_type = "user_phone_number"
|
||||
|
||||
def __init__(self, image_url=None, **kwargs):
|
||||
super(QuickReplyPhoneNumber, self).__init__(**kwargs)
|
||||
self.image_url = image_url
|
||||
|
||||
|
||||
@attr.s(cmp=False, init=False)
|
||||
@attr.s
|
||||
class QuickReplyEmail(QuickReply):
|
||||
"""Represents an email quick reply (Doesn't work on mobile)."""
|
||||
|
||||
@@ -68,10 +55,6 @@ class QuickReplyEmail(QuickReply):
|
||||
#: Type of the quick reply
|
||||
_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):
|
||||
data = dict()
|
||||
|
@@ -2,7 +2,7 @@ import attr
|
||||
from ._attachment import Attachment
|
||||
|
||||
|
||||
@attr.s(cmp=False, init=False)
|
||||
@attr.s
|
||||
class Sticker(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
|
||||
label = attr.ib(None)
|
||||
|
||||
def __init__(self, uid=None):
|
||||
super(Sticker, self).__init__(uid=uid)
|
||||
|
||||
@classmethod
|
||||
def _from_graphql(cls, data):
|
||||
if not data:
|
||||
|
@@ -67,14 +67,14 @@ class ThreadColor(Enum):
|
||||
return cls._extend_if_invalid(value)
|
||||
|
||||
|
||||
@attr.s(cmp=False, init=False)
|
||||
@attr.s
|
||||
class Thread:
|
||||
"""Represents a Facebook thread."""
|
||||
|
||||
#: The unique identifier of the thread. Can be used a ``thread_id``. See :ref:`intro_threads` for more info
|
||||
uid = attr.ib(converter=str)
|
||||
#: 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
|
||||
photo = attr.ib(None)
|
||||
#: The name of the thread
|
||||
@@ -86,24 +86,6 @@ class Thread:
|
||||
#: Set :class:`Plan`
|
||||
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
|
||||
def _parse_customization_info(data):
|
||||
if data is None or data.get("customization_info") is None:
|
||||
|
@@ -41,10 +41,12 @@ class TypingStatus(Enum):
|
||||
TYPING = 1
|
||||
|
||||
|
||||
@attr.s(cmp=False, init=False)
|
||||
@attr.s
|
||||
class User(Thread):
|
||||
"""Represents a Facebook user. Inherits `Thread`."""
|
||||
|
||||
type = ThreadType.USER
|
||||
|
||||
#: The profile URL
|
||||
url = attr.ib(None)
|
||||
#: The users first name
|
||||
@@ -66,33 +68,6 @@ class User(Thread):
|
||||
#: The default emoji
|
||||
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
|
||||
def _from_graphql(cls, data):
|
||||
if data.get("profile_picture") is None:
|
||||
@@ -179,7 +154,7 @@ class User(Thread):
|
||||
)
|
||||
|
||||
|
||||
@attr.s(cmp=False)
|
||||
@attr.s
|
||||
class ActiveStatus:
|
||||
#: Whether the user is active now
|
||||
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