Merge pull request #501 from carpedm20/split-models
Split User, Group, Page, Plan and Message classes
This commit is contained in:
@@ -9,7 +9,7 @@ print("Own id: {}".format(session.user_id))
|
|||||||
user = fbchat.Thread(session=session, id=session.user_id)
|
user = fbchat.Thread(session=session, id=session.user_id)
|
||||||
|
|
||||||
# Send a message to yourself
|
# Send a message to yourself
|
||||||
user.send(fbchat.Message(text="Hi me!"))
|
user.send_text("Hi me!")
|
||||||
|
|
||||||
# Log the user out
|
# Log the user out
|
||||||
session.logout()
|
session.logout()
|
||||||
|
@@ -10,7 +10,7 @@ class EchoBot(fbchat.Client):
|
|||||||
|
|
||||||
# If you're not the author, echo
|
# If you're not the author, echo
|
||||||
if author_id != self.session.user_id:
|
if author_id != self.session.user_id:
|
||||||
thread.send(message_object)
|
thread.send_text(message_object.text)
|
||||||
|
|
||||||
|
|
||||||
session = fbchat.Session.login("<email>", "<password>")
|
session = fbchat.Session.login("<email>", "<password>")
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import fbchat
|
import fbchat
|
||||||
|
import requests
|
||||||
|
|
||||||
session = fbchat.Session.login("<email>", "<password>")
|
session = fbchat.Session.login("<email>", "<password>")
|
||||||
|
|
||||||
@@ -9,34 +10,32 @@ thread = fbchat.User(session=session, id=session.user_id)
|
|||||||
# thread = fbchat.Group(session=session, id="1234567890")
|
# thread = fbchat.Group(session=session, id="1234567890")
|
||||||
|
|
||||||
# Will send a message to the thread
|
# Will send a message to the thread
|
||||||
thread.send(fbchat.Message(text="<message>"))
|
thread.send_text("<message>")
|
||||||
|
|
||||||
# Will send the default `like` emoji
|
# Will send the default `like` emoji
|
||||||
thread.send(fbchat.Message(emoji_size=fbchat.EmojiSize.LARGE))
|
thread.send_sticker(fbchat.EmojiSize.LARGE.value)
|
||||||
|
|
||||||
# Will send the emoji `👍`
|
# Will send the emoji `👍`
|
||||||
thread.send(fbchat.Message(text="👍", emoji_size=fbchat.EmojiSize.LARGE))
|
thread.send_emoji("👍", size=fbchat.EmojiSize.LARGE)
|
||||||
|
|
||||||
# Will send the sticker with ID `767334476626295`
|
# Will send the sticker with ID `767334476626295`
|
||||||
thread.send(fbchat.Message(sticker=fbchat.Sticker("767334476626295")))
|
thread.send_sticker("767334476626295")
|
||||||
|
|
||||||
# Will send a message with a mention
|
# Will send a message with a mention
|
||||||
thread.send(
|
thread.send_text(
|
||||||
fbchat.Message(
|
|
||||||
text="This is a @mention",
|
text="This is a @mention",
|
||||||
mentions=[fbchat.Mention(thread.id, offset=10, length=8)],
|
mentions=[fbchat.Mention(thread.id, offset=10, length=8)],
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
# Will send the image located at `<image path>`
|
# Will send the image located at `<image path>`
|
||||||
thread.send_local_image(
|
with open("<image path>", "rb") as f:
|
||||||
"<image path>", message=fbchat.Message(text="This is a local image")
|
files = session._upload([("image_name.png", f, "image/png")])
|
||||||
)
|
thread.send_text(text="This is a local image", files=files)
|
||||||
|
|
||||||
# Will download the image at the URL `<image url>`, and then send it
|
# Will download the image at the URL `<image url>`, and then send it
|
||||||
thread.send_remote_image(
|
r = requests.get("<image url>")
|
||||||
"<image url>", message=fbchat.Message(text="This is a remote image")
|
files = session._upload([("image_name.png", r.content, "image/png")])
|
||||||
)
|
thread.send_files(files) # Alternative to .send_text
|
||||||
|
|
||||||
|
|
||||||
# Only do these actions if the thread is a group
|
# Only do these actions if the thread is a group
|
||||||
@@ -46,7 +45,7 @@ if isinstance(thread, fbchat.Group):
|
|||||||
# Will add the users with IDs `<1st user id>`, `<2nd user id>` and `<3th user id>` to the group
|
# Will add the users with IDs `<1st user id>`, `<2nd user id>` and `<3th user id>` to the group
|
||||||
thread.add_participants(["<1st user id>", "<2nd user id>", "<3rd user id>"])
|
thread.add_participants(["<1st user id>", "<2nd user id>", "<3rd user id>"])
|
||||||
# Will change the title of the group to `<title>`
|
# Will change the title of the group to `<title>`
|
||||||
thread.change_title("<title>")
|
thread.set_title("<title>")
|
||||||
|
|
||||||
|
|
||||||
# Will change the nickname of the user `<user_id>` to `<new nickname>`
|
# Will change the nickname of the user `<user_id>` to `<new nickname>`
|
||||||
@@ -61,7 +60,7 @@ thread.set_color(fbchat.ThreadColor.MESSENGER_BLUE)
|
|||||||
# Will change the thread emoji to `👍`
|
# Will change the thread emoji to `👍`
|
||||||
thread.set_emoji("👍")
|
thread.set_emoji("👍")
|
||||||
|
|
||||||
# message = fbchat.Message(session=session, id="<message id>")
|
message = fbchat.Message(session=session, id="<message id>")
|
||||||
#
|
|
||||||
# # Will react to a message with a 😍 emoji
|
# Will react to a message with a 😍 emoji
|
||||||
# message.react(fbchat.MessageReaction.LOVE)
|
message.react(fbchat.MessageReaction.LOVE)
|
||||||
|
@@ -15,9 +15,9 @@ from ._core import Image
|
|||||||
from ._exception import FBchatException, FBchatFacebookError
|
from ._exception import FBchatException, FBchatFacebookError
|
||||||
from ._session import Session
|
from ._session import Session
|
||||||
from ._thread import ThreadLocation, ThreadColor, ThreadABC, Thread
|
from ._thread import ThreadLocation, ThreadColor, ThreadABC, Thread
|
||||||
from ._user import TypingStatus, User, ActiveStatus
|
from ._user import TypingStatus, User, UserData, ActiveStatus
|
||||||
from ._group import Group
|
from ._group import Group, GroupData
|
||||||
from ._page import Page
|
from ._page import Page, PageData
|
||||||
from ._message import EmojiSize, MessageReaction, Mention, Message
|
from ._message import EmojiSize, MessageReaction, Mention, Message
|
||||||
from ._attachment import Attachment, UnsentMessage, ShareAttachment
|
from ._attachment import Attachment, UnsentMessage, ShareAttachment
|
||||||
from ._sticker import Sticker
|
from ._sticker import Sticker
|
||||||
@@ -31,7 +31,7 @@ from ._quick_reply import (
|
|||||||
QuickReplyEmail,
|
QuickReplyEmail,
|
||||||
)
|
)
|
||||||
from ._poll import Poll, PollOption
|
from ._poll import Poll, PollOption
|
||||||
from ._plan import GuestStatus, Plan
|
from ._plan import GuestStatus, Plan, PlanData
|
||||||
|
|
||||||
from ._client import Client
|
from ._client import Client
|
||||||
|
|
||||||
|
@@ -8,9 +8,9 @@ from . import _util, _graphql, _session
|
|||||||
|
|
||||||
from ._exception import FBchatException, FBchatFacebookError
|
from ._exception import FBchatException, FBchatFacebookError
|
||||||
from ._thread import ThreadLocation, ThreadColor
|
from ._thread import ThreadLocation, ThreadColor
|
||||||
from ._user import TypingStatus, User, ActiveStatus
|
from ._user import TypingStatus, User, UserData, ActiveStatus
|
||||||
from ._group import Group
|
from ._group import Group, GroupData
|
||||||
from ._page import Page
|
from ._page import Page, PageData
|
||||||
from ._message import EmojiSize, MessageReaction, Mention, Message
|
from ._message import EmojiSize, MessageReaction, Mention, Message
|
||||||
from ._attachment import Attachment
|
from ._attachment import Attachment
|
||||||
from ._sticker import Sticker
|
from ._sticker import Sticker
|
||||||
@@ -24,7 +24,7 @@ from ._quick_reply import (
|
|||||||
QuickReplyEmail,
|
QuickReplyEmail,
|
||||||
)
|
)
|
||||||
from ._poll import Poll, PollOption
|
from ._poll import Poll, PollOption
|
||||||
from ._plan import ACONTEXT, Plan
|
from ._plan import PlanData
|
||||||
|
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
@@ -204,7 +204,7 @@ class Client:
|
|||||||
if data["id"] in ["0", 0]:
|
if data["id"] in ["0", 0]:
|
||||||
# Skip invalid users
|
# Skip invalid users
|
||||||
continue
|
continue
|
||||||
users.append(User._from_all_fetch(self.session, data))
|
users.append(UserData._from_all_fetch(self.session, data))
|
||||||
return users
|
return users
|
||||||
|
|
||||||
def search_for_users(self, name, limit=10):
|
def search_for_users(self, name, limit=10):
|
||||||
@@ -224,7 +224,8 @@ class Client:
|
|||||||
(j,) = self.graphql_requests(_graphql.from_query(_graphql.SEARCH_USER, params))
|
(j,) = self.graphql_requests(_graphql.from_query(_graphql.SEARCH_USER, params))
|
||||||
|
|
||||||
return [
|
return [
|
||||||
User._from_graphql(self.session, node) for node in j[name]["users"]["nodes"]
|
UserData._from_graphql(self.session, node)
|
||||||
|
for node in j[name]["users"]["nodes"]
|
||||||
]
|
]
|
||||||
|
|
||||||
def search_for_pages(self, name, limit=10):
|
def search_for_pages(self, name, limit=10):
|
||||||
@@ -243,7 +244,8 @@ class Client:
|
|||||||
(j,) = self.graphql_requests(_graphql.from_query(_graphql.SEARCH_PAGE, params))
|
(j,) = self.graphql_requests(_graphql.from_query(_graphql.SEARCH_PAGE, params))
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Page._from_graphql(self.session, node) for node in j[name]["pages"]["nodes"]
|
PageData._from_graphql(self.session, node)
|
||||||
|
for node in j[name]["pages"]["nodes"]
|
||||||
]
|
]
|
||||||
|
|
||||||
def search_for_groups(self, name, limit=10):
|
def search_for_groups(self, name, limit=10):
|
||||||
@@ -263,7 +265,7 @@ class Client:
|
|||||||
(j,) = self.graphql_requests(_graphql.from_query(_graphql.SEARCH_GROUP, params))
|
(j,) = self.graphql_requests(_graphql.from_query(_graphql.SEARCH_GROUP, params))
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Group._from_graphql(self.session, node)
|
GroupData._from_graphql(self.session, node)
|
||||||
for node in j["viewer"]["groups"]["nodes"]
|
for node in j["viewer"]["groups"]["nodes"]
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -288,12 +290,12 @@ class Client:
|
|||||||
rtn = []
|
rtn = []
|
||||||
for node in j[name]["threads"]["nodes"]:
|
for node in j[name]["threads"]["nodes"]:
|
||||||
if node["__typename"] == "User":
|
if node["__typename"] == "User":
|
||||||
rtn.append(User._from_graphql(self.session, node))
|
rtn.append(UserData._from_graphql(self.session, node))
|
||||||
elif node["__typename"] == "MessageThread":
|
elif node["__typename"] == "MessageThread":
|
||||||
# MessageThread => Group thread
|
# MessageThread => Group thread
|
||||||
rtn.append(Group._from_graphql(self.session, node))
|
rtn.append(GroupData._from_graphql(self.session, node))
|
||||||
elif node["__typename"] == "Page":
|
elif node["__typename"] == "Page":
|
||||||
rtn.append(Page._from_graphql(self.session, node))
|
rtn.append(PageData._from_graphql(self.session, node))
|
||||||
elif node["__typename"] == "Group":
|
elif node["__typename"] == "Group":
|
||||||
# We don't handle Facebook "Groups"
|
# We don't handle Facebook "Groups"
|
||||||
pass
|
pass
|
||||||
@@ -493,16 +495,16 @@ class Client:
|
|||||||
entry = entry["message_thread"]
|
entry = entry["message_thread"]
|
||||||
if entry.get("thread_type") == "GROUP":
|
if entry.get("thread_type") == "GROUP":
|
||||||
_id = entry["thread_key"]["thread_fbid"]
|
_id = entry["thread_key"]["thread_fbid"]
|
||||||
rtn[_id] = Group._from_graphql(self.session, entry)
|
rtn[_id] = GroupData._from_graphql(self.session, entry)
|
||||||
elif entry.get("thread_type") == "ONE_TO_ONE":
|
elif entry.get("thread_type") == "ONE_TO_ONE":
|
||||||
_id = entry["thread_key"]["other_user_id"]
|
_id = entry["thread_key"]["other_user_id"]
|
||||||
if pages_and_users.get(_id) is None:
|
if pages_and_users.get(_id) is None:
|
||||||
raise FBchatException("Could not fetch thread {}".format(_id))
|
raise FBchatException("Could not fetch thread {}".format(_id))
|
||||||
entry.update(pages_and_users[_id])
|
entry.update(pages_and_users[_id])
|
||||||
if "first_name" in entry["type"]:
|
if "first_name" in entry:
|
||||||
rtn[_id] = User._from_graphql(self.session, entry)
|
rtn[_id] = UserData._from_graphql(self.session, entry)
|
||||||
else:
|
else:
|
||||||
rtn[_id] = Page._from_graphql(self.session, entry)
|
rtn[_id] = PageData._from_graphql(self.session, entry)
|
||||||
else:
|
else:
|
||||||
raise FBchatException(
|
raise FBchatException(
|
||||||
"{} had an unknown thread type: {}".format(thread_ids[i], entry)
|
"{} had an unknown thread type: {}".format(thread_ids[i], entry)
|
||||||
@@ -547,9 +549,11 @@ class Client:
|
|||||||
for node in j["viewer"]["message_threads"]["nodes"]:
|
for node in j["viewer"]["message_threads"]["nodes"]:
|
||||||
_type = node.get("thread_type")
|
_type = node.get("thread_type")
|
||||||
if _type == "GROUP":
|
if _type == "GROUP":
|
||||||
rtn.append(Group._from_graphql(self.session, node))
|
rtn.append(GroupData._from_graphql(self.session, node))
|
||||||
elif _type == "ONE_TO_ONE":
|
elif _type == "ONE_TO_ONE":
|
||||||
rtn.append(User._from_thread_fetch(self.session, node))
|
user = UserData._from_thread_fetch(self.session, node)
|
||||||
|
if user:
|
||||||
|
rtn.append(user)
|
||||||
else:
|
else:
|
||||||
raise FBchatException(
|
raise FBchatException(
|
||||||
"Unknown thread type: {}, with data: {}".format(_type, node)
|
"Unknown thread type: {}, with data: {}".format(_type, node)
|
||||||
@@ -628,22 +632,6 @@ class Client:
|
|||||||
j = self._payload_post("/ajax/mercury/get_poll_options", data)
|
j = self._payload_post("/ajax/mercury/get_poll_options", data)
|
||||||
return [PollOption._from_graphql(m) for m in j]
|
return [PollOption._from_graphql(m) for m in j]
|
||||||
|
|
||||||
def fetch_plan_info(self, plan_id):
|
|
||||||
"""Fetch `Plan` object from the plan id.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plan_id: Plan ID to fetch from
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Plan: `Plan` object
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
FBchatException: If request failed
|
|
||||||
"""
|
|
||||||
data = {"event_reminder_id": plan_id}
|
|
||||||
j = self._payload_post("/ajax/eventreminder", data)
|
|
||||||
return Plan._from_fetch(j)
|
|
||||||
|
|
||||||
def _get_private_data(self):
|
def _get_private_data(self):
|
||||||
(j,) = self.graphql_requests(_graphql.from_doc_id("1868889766468115", {}))
|
(j,) = self.graphql_requests(_graphql.from_doc_id("1868889766468115", {}))
|
||||||
return j["viewer"]
|
return j["viewer"]
|
||||||
@@ -692,56 +680,6 @@ class Client:
|
|||||||
SEND METHODS
|
SEND METHODS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def edit_plan(self, plan, new_plan):
|
|
||||||
"""Edit a plan.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plan (Plan): Plan to edit
|
|
||||||
new_plan: New plan
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
FBchatException: If request failed
|
|
||||||
"""
|
|
||||||
data = {
|
|
||||||
"event_reminder_id": plan.id,
|
|
||||||
"delete": "false",
|
|
||||||
"date": _util.datetime_to_seconds(new_plan.time),
|
|
||||||
"location_name": new_plan.location or "",
|
|
||||||
"location_id": new_plan.location_id or "",
|
|
||||||
"title": new_plan.title,
|
|
||||||
"acontext": ACONTEXT,
|
|
||||||
}
|
|
||||||
j = self._payload_post("/ajax/eventreminder/submit", data)
|
|
||||||
|
|
||||||
def delete_plan(self, plan):
|
|
||||||
"""Delete a plan.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plan: Plan to delete
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
FBchatException: If request failed
|
|
||||||
"""
|
|
||||||
data = {"event_reminder_id": plan.id, "delete": "true", "acontext": ACONTEXT}
|
|
||||||
j = self._payload_post("/ajax/eventreminder/submit", data)
|
|
||||||
|
|
||||||
def change_plan_participation(self, plan, take_part=True):
|
|
||||||
"""Change participation in a plan.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plan: Plan to take part in or not
|
|
||||||
take_part: Whether to take part in the plan
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
FBchatException: If request failed
|
|
||||||
"""
|
|
||||||
data = {
|
|
||||||
"event_reminder_id": plan.id,
|
|
||||||
"guest_state": "GOING" if take_part else "DECLINED",
|
|
||||||
"acontext": ACONTEXT,
|
|
||||||
}
|
|
||||||
j = self._payload_post("/ajax/eventreminder/rsvp", data)
|
|
||||||
|
|
||||||
def update_poll_vote(self, poll_id, option_ids=[], new_options=[]):
|
def update_poll_vote(self, poll_id, option_ids=[], new_options=[]):
|
||||||
"""Update a poll vote.
|
"""Update a poll vote.
|
||||||
|
|
||||||
@@ -1280,7 +1218,7 @@ class Client:
|
|||||||
elif delta_type == "lightweight_event_create":
|
elif delta_type == "lightweight_event_create":
|
||||||
self.on_plan_created(
|
self.on_plan_created(
|
||||||
mid=mid,
|
mid=mid,
|
||||||
plan=Plan._from_pull(delta["untypedData"]),
|
plan=PlanData._from_pull(self.session, delta["untypedData"]),
|
||||||
author_id=author_id,
|
author_id=author_id,
|
||||||
thread=get_thread(metadata),
|
thread=get_thread(metadata),
|
||||||
at=at,
|
at=at,
|
||||||
@@ -1292,7 +1230,7 @@ class Client:
|
|||||||
elif delta_type == "lightweight_event_notify":
|
elif delta_type == "lightweight_event_notify":
|
||||||
self.on_plan_ended(
|
self.on_plan_ended(
|
||||||
mid=mid,
|
mid=mid,
|
||||||
plan=Plan._from_pull(delta["untypedData"]),
|
plan=PlanData._from_pull(self.session, delta["untypedData"]),
|
||||||
thread=get_thread(metadata),
|
thread=get_thread(metadata),
|
||||||
at=at,
|
at=at,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
@@ -1303,7 +1241,7 @@ class Client:
|
|||||||
elif delta_type == "lightweight_event_update":
|
elif delta_type == "lightweight_event_update":
|
||||||
self.on_plan_edited(
|
self.on_plan_edited(
|
||||||
mid=mid,
|
mid=mid,
|
||||||
plan=Plan._from_pull(delta["untypedData"]),
|
plan=PlanData._from_pull(self.session, delta["untypedData"]),
|
||||||
author_id=author_id,
|
author_id=author_id,
|
||||||
thread=get_thread(metadata),
|
thread=get_thread(metadata),
|
||||||
at=at,
|
at=at,
|
||||||
@@ -1315,7 +1253,7 @@ class Client:
|
|||||||
elif delta_type == "lightweight_event_delete":
|
elif delta_type == "lightweight_event_delete":
|
||||||
self.on_plan_deleted(
|
self.on_plan_deleted(
|
||||||
mid=mid,
|
mid=mid,
|
||||||
plan=Plan._from_pull(delta["untypedData"]),
|
plan=PlanData._from_pull(self.session, delta["untypedData"]),
|
||||||
author_id=author_id,
|
author_id=author_id,
|
||||||
thread=get_thread(metadata),
|
thread=get_thread(metadata),
|
||||||
at=at,
|
at=at,
|
||||||
@@ -1328,7 +1266,7 @@ class Client:
|
|||||||
take_part = delta["untypedData"]["guest_status"] == "GOING"
|
take_part = delta["untypedData"]["guest_status"] == "GOING"
|
||||||
self.on_plan_participation(
|
self.on_plan_participation(
|
||||||
mid=mid,
|
mid=mid,
|
||||||
plan=Plan._from_pull(delta["untypedData"]),
|
plan=PlanData._from_pull(self.session, delta["untypedData"]),
|
||||||
take_part=take_part,
|
take_part=take_part,
|
||||||
author_id=author_id,
|
author_id=author_id,
|
||||||
thread=get_thread(metadata),
|
thread=get_thread(metadata),
|
||||||
@@ -1424,18 +1362,15 @@ class Client:
|
|||||||
|
|
||||||
elif d.get("deltaMessageReply"):
|
elif d.get("deltaMessageReply"):
|
||||||
i = d["deltaMessageReply"]
|
i = d["deltaMessageReply"]
|
||||||
|
thread = get_thread(metadata)
|
||||||
metadata = i["message"]["messageMetadata"]
|
metadata = i["message"]["messageMetadata"]
|
||||||
replied_to = Message._from_reply(
|
replied_to = MessageData._from_reply(thread, i["repliedToMessage"])
|
||||||
self.session, i["repliedToMessage"]
|
message = MessageData._from_reply(thread, i["message"], replied_to)
|
||||||
)
|
|
||||||
message = Message._from_reply(
|
|
||||||
self.session, i["message"], replied_to
|
|
||||||
)
|
|
||||||
self.on_message(
|
self.on_message(
|
||||||
mid=message.id,
|
mid=message.id,
|
||||||
author_id=message.author,
|
author_id=message.author,
|
||||||
message_object=message,
|
message_object=message,
|
||||||
thread=get_thread(metadata),
|
thread=thread,
|
||||||
at=message.created_at,
|
at=message.created_at,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
msg=m,
|
msg=m,
|
||||||
@@ -1443,18 +1378,19 @@ class Client:
|
|||||||
|
|
||||||
# New message
|
# New message
|
||||||
elif delta.get("class") == "NewMessage":
|
elif delta.get("class") == "NewMessage":
|
||||||
|
thread = get_thread(metadata)
|
||||||
self.on_message(
|
self.on_message(
|
||||||
mid=mid,
|
mid=mid,
|
||||||
author_id=author_id,
|
author_id=author_id,
|
||||||
message_object=Message._from_pull(
|
message_object=MessageData._from_pull(
|
||||||
self.session,
|
thread,
|
||||||
delta,
|
delta,
|
||||||
mid=mid,
|
mid=mid,
|
||||||
tags=metadata.get("tags"),
|
tags=metadata.get("tags"),
|
||||||
author=author_id,
|
author=author_id,
|
||||||
created_at=at,
|
created_at=at,
|
||||||
),
|
),
|
||||||
thread=get_thread(metadata),
|
thread=thread,
|
||||||
at=at,
|
at=at,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
msg=m,
|
msg=m,
|
||||||
|
@@ -12,32 +12,9 @@ class Group(_thread.ThreadABC):
|
|||||||
session = attr.ib(type=_session.Session)
|
session = attr.ib(type=_session.Session)
|
||||||
#: The group's unique identifier.
|
#: The group's unique identifier.
|
||||||
id = attr.ib(converter=str)
|
id = attr.ib(converter=str)
|
||||||
#: The group's picture
|
|
||||||
photo = attr.ib(None)
|
def _to_send_data(self):
|
||||||
#: The name of the group
|
return {"thread_fbid": self.id}
|
||||||
name = attr.ib(None)
|
|
||||||
#: Datetime when the group was last active / when the last message was sent
|
|
||||||
last_active = attr.ib(None)
|
|
||||||
#: Number of messages in the group
|
|
||||||
message_count = attr.ib(None)
|
|
||||||
#: Set `Plan`
|
|
||||||
plan = attr.ib(None)
|
|
||||||
#: Unique list (set) of the group thread's participant user IDs
|
|
||||||
participants = attr.ib(factory=set)
|
|
||||||
#: A dictionary, containing user nicknames mapped to their IDs
|
|
||||||
nicknames = attr.ib(factory=dict)
|
|
||||||
#: A `ThreadColor`. The groups's message color
|
|
||||||
color = attr.ib(None)
|
|
||||||
#: The groups's default emoji
|
|
||||||
emoji = attr.ib(None)
|
|
||||||
# Set containing user IDs of thread admins
|
|
||||||
admins = attr.ib(factory=set)
|
|
||||||
# True if users need approval to join
|
|
||||||
approval_mode = attr.ib(None)
|
|
||||||
# Set containing user IDs requesting to join
|
|
||||||
approval_requests = attr.ib(factory=set)
|
|
||||||
# Link for joining group
|
|
||||||
join_link = attr.ib(None)
|
|
||||||
|
|
||||||
def add_participants(self, user_ids: Iterable[str]):
|
def add_participants(self, user_ids: Iterable[str]):
|
||||||
"""Add users to the group.
|
"""Add users to the group.
|
||||||
@@ -151,6 +128,41 @@ class Group(_thread.ThreadABC):
|
|||||||
"""
|
"""
|
||||||
self._users_approval(user_ids, False)
|
self._users_approval(user_ids, False)
|
||||||
|
|
||||||
|
|
||||||
|
@attrs_default
|
||||||
|
class GroupData(Group):
|
||||||
|
"""Represents data about a Facebook group.
|
||||||
|
|
||||||
|
Inherits `Group`, and implements `ThreadABC`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: The group's picture
|
||||||
|
photo = attr.ib(None)
|
||||||
|
#: The name of the group
|
||||||
|
name = attr.ib(None)
|
||||||
|
#: Datetime when the group was last active / when the last message was sent
|
||||||
|
last_active = attr.ib(None)
|
||||||
|
#: Number of messages in the group
|
||||||
|
message_count = attr.ib(None)
|
||||||
|
#: Set `Plan`
|
||||||
|
plan = attr.ib(None)
|
||||||
|
#: Unique list (set) of the group thread's participant user IDs
|
||||||
|
participants = attr.ib(factory=set)
|
||||||
|
#: A dictionary, containing user nicknames mapped to their IDs
|
||||||
|
nicknames = attr.ib(factory=dict)
|
||||||
|
#: A `ThreadColor`. The groups's message color
|
||||||
|
color = attr.ib(None)
|
||||||
|
#: The groups's default emoji
|
||||||
|
emoji = attr.ib(None)
|
||||||
|
# Set containing user IDs of thread admins
|
||||||
|
admins = attr.ib(factory=set)
|
||||||
|
# True if users need approval to join
|
||||||
|
approval_mode = attr.ib(None)
|
||||||
|
# Set containing user IDs requesting to join
|
||||||
|
approval_requests = attr.ib(factory=set)
|
||||||
|
# Link for joining group
|
||||||
|
join_link = attr.ib(None)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, session, data):
|
def _from_graphql(cls, session, data):
|
||||||
if data.get("image") is None:
|
if data.get("image") is None:
|
||||||
@@ -163,7 +175,9 @@ class Group(_thread.ThreadABC):
|
|||||||
)
|
)
|
||||||
plan = None
|
plan = None
|
||||||
if data.get("event_reminders") and data["event_reminders"].get("nodes"):
|
if data.get("event_reminders") and data["event_reminders"].get("nodes"):
|
||||||
plan = _plan.Plan._from_graphql(data["event_reminders"]["nodes"][0])
|
plan = _plan.PlanData._from_graphql(
|
||||||
|
session, data["event_reminders"]["nodes"][0]
|
||||||
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
session=session,
|
session=session,
|
||||||
@@ -195,9 +209,6 @@ class Group(_thread.ThreadABC):
|
|||||||
plan=plan,
|
plan=plan,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _to_send_data(self):
|
|
||||||
return {"thread_fbid": self.id}
|
|
||||||
|
|
||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
class NewGroup(_thread.ThreadABC):
|
class NewGroup(_thread.ThreadABC):
|
||||||
|
@@ -50,21 +50,21 @@ class Mention:
|
|||||||
#: The thread ID the mention is pointing at
|
#: The thread ID the mention is pointing at
|
||||||
thread_id = attr.ib()
|
thread_id = attr.ib()
|
||||||
#: The character where the mention starts
|
#: The character where the mention starts
|
||||||
offset = attr.ib(0)
|
offset = attr.ib()
|
||||||
#: The length of the mention
|
#: The length of the mention
|
||||||
length = attr.ib(10)
|
length = attr.ib()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_range(cls, data):
|
def _from_range(cls, data):
|
||||||
return cls(
|
return cls(
|
||||||
thread_id=data.get("entity", {}).get("id"),
|
thread_id=data["entity"]["id"],
|
||||||
offset=data.get("offset"),
|
offset=data["offset"],
|
||||||
length=data.get("length"),
|
length=data["length"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_prng(cls, data):
|
def _from_prng(cls, data):
|
||||||
return cls(thread_id=data.get("i"), offset=data.get("o"), length=data.get("l"))
|
return cls(thread_id=data["i"], offset=data["o"], length=data["l"])
|
||||||
|
|
||||||
def _to_send_data(self, i):
|
def _to_send_data(self, i):
|
||||||
return {
|
return {
|
||||||
@@ -79,42 +79,15 @@ class Mention:
|
|||||||
class Message:
|
class Message:
|
||||||
"""Represents a Facebook message."""
|
"""Represents a Facebook message."""
|
||||||
|
|
||||||
# TODO: Make these fields required!
|
#: The thread that this message belongs to.
|
||||||
#: The session to use when making requests.
|
thread = attr.ib(type="_thread.ThreadABC")
|
||||||
session = attr.ib(None, type=_session.Session)
|
#: The message ID.
|
||||||
#: The message ID
|
id = attr.ib(converter=str)
|
||||||
id = attr.ib(None, converter=str)
|
|
||||||
|
|
||||||
#: The actual message
|
@property
|
||||||
text = attr.ib(None)
|
def session(self):
|
||||||
#: A list of `Mention` objects
|
"""The session to use when making requests."""
|
||||||
mentions = attr.ib(factory=list)
|
return self.thread.session
|
||||||
#: A `EmojiSize`. Size of a sent emoji
|
|
||||||
emoji_size = attr.ib(None)
|
|
||||||
#: ID of the sender
|
|
||||||
author = attr.ib(None)
|
|
||||||
#: Datetime of when the message was sent
|
|
||||||
created_at = attr.ib(None)
|
|
||||||
#: Whether the message is read
|
|
||||||
is_read = attr.ib(None)
|
|
||||||
#: A list of people IDs who read the message, works only with `Client.fetch_thread_messages`
|
|
||||||
read_by = attr.ib(factory=list)
|
|
||||||
#: A dictionary with user's IDs as keys, and their `MessageReaction` as values
|
|
||||||
reactions = attr.ib(factory=dict)
|
|
||||||
#: A `Sticker`
|
|
||||||
sticker = attr.ib(None)
|
|
||||||
#: A list of attachments
|
|
||||||
attachments = attr.ib(factory=list)
|
|
||||||
#: A list of `QuickReply`
|
|
||||||
quick_replies = attr.ib(factory=list)
|
|
||||||
#: Whether the message is unsent (deleted for everyone)
|
|
||||||
unsent = attr.ib(False)
|
|
||||||
#: Message ID you want to reply to
|
|
||||||
reply_to_id = attr.ib(None)
|
|
||||||
#: Replied message
|
|
||||||
replied_to = attr.ib(None)
|
|
||||||
#: Whether the message was forwarded
|
|
||||||
forwarded = attr.ib(False)
|
|
||||||
|
|
||||||
def unsend(self):
|
def unsend(self):
|
||||||
"""Unsend the message (removes it for everyone)."""
|
"""Unsend the message (removes it for everyone)."""
|
||||||
@@ -125,7 +98,7 @@ class Message:
|
|||||||
"""React to the message, or removes reaction.
|
"""React to the message, or removes reaction.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reaction: Reaction emoji to use, if None removes reaction
|
reaction: Reaction emoji to use, if ``None`` removes reaction
|
||||||
"""
|
"""
|
||||||
data = {
|
data = {
|
||||||
"action": "ADD_REACTION" if reaction else "REMOVE_REACTION",
|
"action": "ADD_REACTION" if reaction else "REMOVE_REACTION",
|
||||||
@@ -138,27 +111,22 @@ class Message:
|
|||||||
j = self.session._payload_post("/webgraphql/mutation", data)
|
j = self.session._payload_post("/webgraphql/mutation", data)
|
||||||
_util.handle_graphql_errors(j)
|
_util.handle_graphql_errors(j)
|
||||||
|
|
||||||
@classmethod
|
def fetch(self) -> "MessageData":
|
||||||
def from_fetch(cls, thread, message_id: str) -> "Message":
|
"""Fetch fresh `MessageData` object."""
|
||||||
"""Fetch `Message` object from the given message id.
|
message_info = self.thread._forced_fetch(self.id).get("message")
|
||||||
|
return MessageData._from_graphql(self.thread, message_info)
|
||||||
|
|
||||||
Args:
|
@staticmethod
|
||||||
message_id: Message ID to fetch from
|
def format_mentions(text, *args, **kwargs):
|
||||||
"""
|
|
||||||
message_info = thread._forced_fetch(message_id).get("message")
|
|
||||||
return Message._from_graphql(thread.session, message_info)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def format_mentions(cls, text, *args, **kwargs):
|
|
||||||
"""Like `str.format`, but takes tuples with a thread id and text instead.
|
"""Like `str.format`, but takes tuples with a thread id and text instead.
|
||||||
|
|
||||||
Return a `Message` object, with the formatted string and relevant mentions.
|
Return a tuple, with the formatted string and relevant mentions.
|
||||||
|
|
||||||
>>> Message.format_mentions("Hey {!r}! My name is {}", ("1234", "Peter"), ("4321", "Michael"))
|
>>> Message.format_mentions("Hey {!r}! My name is {}", ("1234", "Peter"), ("4321", "Michael"))
|
||||||
<Message (None): "Hey 'Peter'! My name is Michael", mentions=[<Mention 1234: offset=4 length=7>, <Mention 4321: offset=24 length=7>] emoji_size=None attachments=[]>
|
("Hey 'Peter'! My name is Michael", [<Mention 1234: offset=4 length=7>, <Mention 4321: offset=24 length=7>])
|
||||||
|
|
||||||
>>> Message.format_mentions("Hey {p}! My name is {}", ("1234", "Michael"), p=("4321", "Peter"))
|
>>> Message.format_mentions("Hey {p}! My name is {}", ("1234", "Michael"), p=("4321", "Peter"))
|
||||||
<Message (None): 'Hey Peter! My name is Michael', mentions=[<Mention 4321: offset=4 length=5>, <Mention 1234: offset=22 length=7>] emoji_size=None attachments=[]>
|
('Hey Peter! My name is Michael', [<Mention 4321: offset=4 length=5>, <Mention 1234: offset=22 length=7>])
|
||||||
"""
|
"""
|
||||||
result = ""
|
result = ""
|
||||||
mentions = list()
|
mentions = list()
|
||||||
@@ -196,7 +164,46 @@ class Message:
|
|||||||
)
|
)
|
||||||
offset += len(name)
|
offset += len(name)
|
||||||
|
|
||||||
return cls(text=result, mentions=mentions)
|
return result, mentions
|
||||||
|
|
||||||
|
|
||||||
|
@attrs_default
|
||||||
|
class MessageData(Message):
|
||||||
|
"""Represents data in a Facebook message.
|
||||||
|
|
||||||
|
Inherits `Message`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: ID of the sender
|
||||||
|
author = attr.ib()
|
||||||
|
#: Datetime of when the message was sent
|
||||||
|
created_at = attr.ib()
|
||||||
|
#: The actual message
|
||||||
|
text = attr.ib(None)
|
||||||
|
#: A list of `Mention` objects
|
||||||
|
mentions = attr.ib(factory=list)
|
||||||
|
#: A `EmojiSize`. Size of a sent emoji
|
||||||
|
emoji_size = attr.ib(None)
|
||||||
|
#: Whether the message is read
|
||||||
|
is_read = attr.ib(None)
|
||||||
|
#: A list of people IDs who read the message, works only with `Client.fetch_thread_messages`
|
||||||
|
read_by = attr.ib(factory=list)
|
||||||
|
#: A dictionary with user's IDs as keys, and their `MessageReaction` as values
|
||||||
|
reactions = attr.ib(factory=dict)
|
||||||
|
#: A `Sticker`
|
||||||
|
sticker = attr.ib(None)
|
||||||
|
#: A list of attachments
|
||||||
|
attachments = attr.ib(factory=list)
|
||||||
|
#: A list of `QuickReply`
|
||||||
|
quick_replies = attr.ib(factory=list)
|
||||||
|
#: Whether the message is unsent (deleted for everyone)
|
||||||
|
unsent = attr.ib(False)
|
||||||
|
#: Message ID you want to reply to
|
||||||
|
reply_to_id = attr.ib(None)
|
||||||
|
#: Replied message
|
||||||
|
replied_to = attr.ib(None)
|
||||||
|
#: Whether the message was forwarded
|
||||||
|
forwarded = attr.ib(False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_forwarded_from_tags(tags):
|
def _get_forwarded_from_tags(tags):
|
||||||
@@ -204,52 +211,6 @@ class Message:
|
|||||||
return False
|
return False
|
||||||
return any(map(lambda tag: "forward" in tag or "copy" in tag, tags))
|
return any(map(lambda tag: "forward" in tag or "copy" in tag, tags))
|
||||||
|
|
||||||
def _to_send_data(self):
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
if self.text or self.sticker or self.emoji_size:
|
|
||||||
data["action_type"] = "ma-type:user-generated-message"
|
|
||||||
|
|
||||||
if self.text:
|
|
||||||
data["body"] = self.text
|
|
||||||
|
|
||||||
for i, mention in enumerate(self.mentions):
|
|
||||||
data.update(mention._to_send_data(i))
|
|
||||||
|
|
||||||
if self.emoji_size:
|
|
||||||
if self.text:
|
|
||||||
data["tags[0]"] = "hot_emoji_size:" + self.emoji_size.name.lower()
|
|
||||||
else:
|
|
||||||
data["sticker_id"] = self.emoji_size.value
|
|
||||||
|
|
||||||
if self.sticker:
|
|
||||||
data["sticker_id"] = self.sticker.id
|
|
||||||
|
|
||||||
if self.quick_replies:
|
|
||||||
xmd = {"quick_replies": []}
|
|
||||||
for quick_reply in self.quick_replies:
|
|
||||||
# TODO: Move this to `_quick_reply.py`
|
|
||||||
q = dict()
|
|
||||||
q["content_type"] = quick_reply._type
|
|
||||||
q["payload"] = quick_reply.payload
|
|
||||||
q["external_payload"] = quick_reply.external_payload
|
|
||||||
q["data"] = quick_reply.data
|
|
||||||
if quick_reply.is_response:
|
|
||||||
q["ignore_for_webhook"] = False
|
|
||||||
if isinstance(quick_reply, _quick_reply.QuickReplyText):
|
|
||||||
q["title"] = quick_reply.title
|
|
||||||
if not isinstance(quick_reply, _quick_reply.QuickReplyLocation):
|
|
||||||
q["image_url"] = quick_reply.image_url
|
|
||||||
xmd["quick_replies"].append(q)
|
|
||||||
if len(self.quick_replies) == 1 and self.quick_replies[0].is_response:
|
|
||||||
xmd["quick_replies"] = xmd["quick_replies"][0]
|
|
||||||
data["platform_xmd"] = json.dumps(xmd)
|
|
||||||
|
|
||||||
if self.reply_to_id:
|
|
||||||
data["replied_to_message_id"] = self.reply_to_id
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_quick_replies(data):
|
def _parse_quick_replies(data):
|
||||||
if data:
|
if data:
|
||||||
@@ -261,7 +222,7 @@ class Message:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, session, data, read_receipts=None):
|
def _from_graphql(cls, thread, data, read_receipts=None):
|
||||||
if data.get("message_sender") is None:
|
if data.get("message_sender") is None:
|
||||||
data["message_sender"] = {}
|
data["message_sender"] = {}
|
||||||
if data.get("message") is None:
|
if data.get("message") is None:
|
||||||
@@ -287,15 +248,15 @@ class Message:
|
|||||||
replied_to = cls._from_graphql(data["replied_to_message"]["message"])
|
replied_to = cls._from_graphql(data["replied_to_message"]["message"])
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
session=session,
|
thread=thread,
|
||||||
id=str(data["message_id"]),
|
id=str(data["message_id"]),
|
||||||
|
author=str(data["message_sender"]["id"]),
|
||||||
|
created_at=created_at,
|
||||||
text=data["message"].get("text"),
|
text=data["message"].get("text"),
|
||||||
mentions=[
|
mentions=[
|
||||||
Mention._from_range(m) for m in data["message"].get("ranges") or ()
|
Mention._from_range(m) for m in data["message"].get("ranges") or ()
|
||||||
],
|
],
|
||||||
emoji_size=EmojiSize._from_tags(tags),
|
emoji_size=EmojiSize._from_tags(tags),
|
||||||
author=str(data["message_sender"]["id"]),
|
|
||||||
created_at=created_at,
|
|
||||||
is_read=not data["unread"] if data.get("unread") is not None else None,
|
is_read=not data["unread"] if data.get("unread") is not None else None,
|
||||||
read_by=[
|
read_by=[
|
||||||
receipt["actor"]["id"]
|
receipt["actor"]["id"]
|
||||||
@@ -316,7 +277,7 @@ class Message:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_reply(cls, session, data, replied_to=None):
|
def _from_reply(cls, thread, data, replied_to=None):
|
||||||
tags = data["messageMetadata"].get("tags")
|
tags = data["messageMetadata"].get("tags")
|
||||||
metadata = data.get("messageMetadata", {})
|
metadata = data.get("messageMetadata", {})
|
||||||
|
|
||||||
@@ -343,16 +304,16 @@ class Message:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
session=session,
|
thread=thread,
|
||||||
id=metadata.get("messageId"),
|
id=metadata.get("messageId"),
|
||||||
|
author=str(metadata["actorFbId"]),
|
||||||
|
created_at=_util.millis_to_datetime(metadata["timestamp"]),
|
||||||
text=data.get("body"),
|
text=data.get("body"),
|
||||||
mentions=[
|
mentions=[
|
||||||
Mention._from_prng(m)
|
Mention._from_prng(m)
|
||||||
for m in _util.parse_json(data.get("data", {}).get("prng", "[]"))
|
for m in _util.parse_json(data.get("data", {}).get("prng", "[]"))
|
||||||
],
|
],
|
||||||
emoji_size=EmojiSize._from_tags(tags),
|
emoji_size=EmojiSize._from_tags(tags),
|
||||||
author=str(metadata.get("actorFbId")),
|
|
||||||
created_at=_util.millis_to_datetime(metadata.get("timestamp")),
|
|
||||||
sticker=sticker,
|
sticker=sticker,
|
||||||
attachments=attachments,
|
attachments=attachments,
|
||||||
quick_replies=cls._parse_quick_replies(data.get("platform_xmd_encoded")),
|
quick_replies=cls._parse_quick_replies(data.get("platform_xmd_encoded")),
|
||||||
@@ -363,9 +324,7 @@ class Message:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_pull(
|
def _from_pull(cls, thread, data, mid, tags, author, created_at):
|
||||||
cls, session, data, mid=None, tags=None, author=None, created_at=None
|
|
||||||
):
|
|
||||||
mentions = []
|
mentions = []
|
||||||
if data.get("data") and data["data"].get("prng"):
|
if data.get("data") and data["data"].get("prng"):
|
||||||
try:
|
try:
|
||||||
@@ -412,13 +371,13 @@ class Message:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
session=session,
|
thread=thread,
|
||||||
id=mid,
|
id=mid,
|
||||||
|
author=author,
|
||||||
|
created_at=created_at,
|
||||||
text=data.get("body"),
|
text=data.get("body"),
|
||||||
mentions=mentions,
|
mentions=mentions,
|
||||||
emoji_size=EmojiSize._from_tags(tags),
|
emoji_size=EmojiSize._from_tags(tags),
|
||||||
author=author,
|
|
||||||
created_at=created_at,
|
|
||||||
sticker=sticker,
|
sticker=sticker,
|
||||||
attachments=attachments,
|
attachments=attachments,
|
||||||
unsent=unsent,
|
unsent=unsent,
|
||||||
|
@@ -11,10 +11,22 @@ class Page(_thread.ThreadABC):
|
|||||||
session = attr.ib(type=_session.Session)
|
session = attr.ib(type=_session.Session)
|
||||||
#: The unique identifier of the page.
|
#: The unique identifier of the page.
|
||||||
id = attr.ib(converter=str)
|
id = attr.ib(converter=str)
|
||||||
|
|
||||||
|
def _to_send_data(self):
|
||||||
|
return {"other_user_fbid": self.id}
|
||||||
|
|
||||||
|
|
||||||
|
@attrs_default
|
||||||
|
class PageData(Page):
|
||||||
|
"""Represents data about a Facebook page.
|
||||||
|
|
||||||
|
Inherits `Page`, and implements `ThreadABC`.
|
||||||
|
"""
|
||||||
|
|
||||||
#: The page's picture
|
#: The page's picture
|
||||||
photo = attr.ib(None)
|
photo = attr.ib()
|
||||||
#: The name of the page
|
#: The name of the page
|
||||||
name = attr.ib(None)
|
name = attr.ib()
|
||||||
#: Datetime when the thread was last active / when the last message was sent
|
#: Datetime when the thread was last active / when the last message was sent
|
||||||
last_active = attr.ib(None)
|
last_active = attr.ib(None)
|
||||||
#: Number of messages in the thread
|
#: Number of messages in the thread
|
||||||
@@ -32,9 +44,6 @@ class Page(_thread.ThreadABC):
|
|||||||
#: The page's category
|
#: The page's category
|
||||||
category = attr.ib(None)
|
category = attr.ib(None)
|
||||||
|
|
||||||
def _to_send_data(self):
|
|
||||||
return {"other_user_fbid": self.id}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, session, data):
|
def _from_graphql(cls, session, data):
|
||||||
if data.get("profile_picture") is None:
|
if data.get("profile_picture") is None:
|
||||||
@@ -43,7 +52,9 @@ class Page(_thread.ThreadABC):
|
|||||||
data["city"] = {}
|
data["city"] = {}
|
||||||
plan = None
|
plan = None
|
||||||
if data.get("event_reminders") and data["event_reminders"].get("nodes"):
|
if data.get("event_reminders") and data["event_reminders"].get("nodes"):
|
||||||
plan = _plan.Plan._from_graphql(data["event_reminders"]["nodes"][0])
|
plan = _plan.PlanData._from_graphql(
|
||||||
|
session, data["event_reminders"]["nodes"][0]
|
||||||
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
session=session,
|
session=session,
|
||||||
@@ -52,7 +63,7 @@ class Page(_thread.ThreadABC):
|
|||||||
city=data.get("city").get("name"),
|
city=data.get("city").get("name"),
|
||||||
category=data.get("category_type"),
|
category=data.get("category_type"),
|
||||||
photo=Image._from_uri(data["profile_picture"]),
|
photo=Image._from_uri(data["profile_picture"]),
|
||||||
name=data.get("name"),
|
name=data["name"],
|
||||||
message_count=data.get("messages_count"),
|
message_count=data.get("messages_count"),
|
||||||
plan=plan,
|
plan=plan,
|
||||||
)
|
)
|
||||||
|
100
fbchat/_plan.py
100
fbchat/_plan.py
@@ -1,7 +1,8 @@
|
|||||||
import attr
|
import attr
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
from ._core import attrs_default, Enum
|
from ._core import attrs_default, Enum
|
||||||
from . import _util
|
from . import _exception, _util, _session
|
||||||
|
|
||||||
|
|
||||||
class GuestStatus(Enum):
|
class GuestStatus(Enum):
|
||||||
@@ -19,14 +20,96 @@ ACONTEXT = {
|
|||||||
|
|
||||||
@attrs_default
|
@attrs_default
|
||||||
class Plan:
|
class Plan:
|
||||||
"""Represents a plan."""
|
"""Base model for plans."""
|
||||||
|
|
||||||
|
#: The session to use when making requests.
|
||||||
|
session = attr.ib(type=_session.Session)
|
||||||
|
#: The plan's unique identifier.
|
||||||
|
id = attr.ib(converter=str)
|
||||||
|
|
||||||
|
def fetch(self) -> "PlanData":
|
||||||
|
"""Fetch fresh `PlanData` object."""
|
||||||
|
data = {"event_reminder_id": self.id}
|
||||||
|
j = self.session._payload_post("/ajax/eventreminder", data)
|
||||||
|
return PlanData._from_fetch(self.session, j)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create(
|
||||||
|
cls,
|
||||||
|
thread,
|
||||||
|
name: str,
|
||||||
|
at: datetime.datetime,
|
||||||
|
location_name: str = None,
|
||||||
|
location_id: str = None,
|
||||||
|
):
|
||||||
|
data = {
|
||||||
|
"event_type": "EVENT",
|
||||||
|
"event_time": _util.datetime_to_seconds(at),
|
||||||
|
"title": name,
|
||||||
|
"thread_id": thread.id,
|
||||||
|
"location_id": location_id or "",
|
||||||
|
"location_name": location_name or "",
|
||||||
|
"acontext": ACONTEXT,
|
||||||
|
}
|
||||||
|
j = thread.session._payload_post("/ajax/eventreminder/create", data)
|
||||||
|
if "error" in j:
|
||||||
|
raise _exception.FBchatFacebookError(
|
||||||
|
"Failed creating plan: {}".format(j["error"]),
|
||||||
|
fb_error_message=j["error"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def edit(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
at: datetime.datetime,
|
||||||
|
location_name: str = None,
|
||||||
|
location_id: str = None,
|
||||||
|
):
|
||||||
|
"""Edit the plan.
|
||||||
|
|
||||||
|
# TODO: Arguments
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"event_reminder_id": self.id,
|
||||||
|
"delete": "false",
|
||||||
|
"date": _util.datetime_to_seconds(at),
|
||||||
|
"location_name": location_name or "",
|
||||||
|
"location_id": location_id or "",
|
||||||
|
"title": name,
|
||||||
|
"acontext": ACONTEXT,
|
||||||
|
}
|
||||||
|
j = self.session._payload_post("/ajax/eventreminder/submit", data)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
"""Delete the plan."""
|
||||||
|
data = {"event_reminder_id": self.id, "delete": "true", "acontext": ACONTEXT}
|
||||||
|
j = self.session._payload_post("/ajax/eventreminder/submit", data)
|
||||||
|
|
||||||
|
def _change_participation(self):
|
||||||
|
data = {
|
||||||
|
"event_reminder_id": self.id,
|
||||||
|
"guest_state": "GOING" if take_part else "DECLINED",
|
||||||
|
"acontext": ACONTEXT,
|
||||||
|
}
|
||||||
|
j = self.session._payload_post("/ajax/eventreminder/rsvp", data)
|
||||||
|
|
||||||
|
def participate(self):
|
||||||
|
"""Set yourself as GOING/participating to the plan."""
|
||||||
|
self._change_participation(True)
|
||||||
|
|
||||||
|
def decline(self):
|
||||||
|
"""Set yourself as having DECLINED the plan."""
|
||||||
|
self._change_participation(False)
|
||||||
|
|
||||||
|
|
||||||
|
@attrs_default
|
||||||
|
class PlanData(Plan):
|
||||||
|
"""Represents data about a plan."""
|
||||||
|
|
||||||
#: Plan time (datetime), only precise down to the minute
|
#: Plan time (datetime), only precise down to the minute
|
||||||
time = attr.ib()
|
time = attr.ib()
|
||||||
#: Plan title
|
#: Plan title
|
||||||
title = attr.ib()
|
title = attr.ib()
|
||||||
#: ID of the plan
|
|
||||||
id = attr.ib(None)
|
|
||||||
#: Plan location name
|
#: Plan location name
|
||||||
location = attr.ib(None, converter=lambda x: x or "")
|
location = attr.ib(None, converter=lambda x: x or "")
|
||||||
#: Plan location ID
|
#: Plan location ID
|
||||||
@@ -64,8 +147,9 @@ class Plan:
|
|||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_pull(cls, data):
|
def _from_pull(cls, session, data):
|
||||||
return cls(
|
return cls(
|
||||||
|
session=session,
|
||||||
id=data.get("event_id"),
|
id=data.get("event_id"),
|
||||||
time=_util.seconds_to_datetime(int(data.get("event_time"))),
|
time=_util.seconds_to_datetime(int(data.get("event_time"))),
|
||||||
title=data.get("event_title"),
|
title=data.get("event_title"),
|
||||||
@@ -79,8 +163,9 @@ class Plan:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_fetch(cls, data):
|
def _from_fetch(cls, session, data):
|
||||||
return cls(
|
return cls(
|
||||||
|
session=session,
|
||||||
id=data.get("oid"),
|
id=data.get("oid"),
|
||||||
time=_util.seconds_to_datetime(data.get("event_time")),
|
time=_util.seconds_to_datetime(data.get("event_time")),
|
||||||
title=data.get("title"),
|
title=data.get("title"),
|
||||||
@@ -91,8 +176,9 @@ class Plan:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, data):
|
def _from_graphql(cls, session, data):
|
||||||
return cls(
|
return cls(
|
||||||
|
session=session,
|
||||||
id=data.get("id"),
|
id=data.get("id"),
|
||||||
time=_util.seconds_to_datetime(data.get("time")),
|
time=_util.seconds_to_datetime(data.get("time")),
|
||||||
title=data.get("event_title"),
|
title=data.get("event_title"),
|
||||||
|
@@ -73,6 +73,17 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
|||||||
def _to_send_data(self) -> MutableMapping[str, str]:
|
def _to_send_data(self) -> MutableMapping[str, str]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# Note:
|
||||||
|
# You can go out of Facebook's spec with `self.session._do_send_request`!
|
||||||
|
#
|
||||||
|
# A few examples:
|
||||||
|
# - You can send a sticker and an emoji at the same time
|
||||||
|
# - You can wave, send a sticker and text at the same time
|
||||||
|
# - You can reply to a message with a sticker
|
||||||
|
#
|
||||||
|
# We won't support those use cases, it'll make for a confusing API!
|
||||||
|
# If we absolutely need to in the future, we can always add extra functionality
|
||||||
|
|
||||||
def wave(self, first: bool = True) -> str:
|
def wave(self, first: bool = True) -> str:
|
||||||
"""Wave hello to the thread.
|
"""Wave hello to the thread.
|
||||||
|
|
||||||
@@ -85,73 +96,127 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
|||||||
"INITIATED" if first else "RECIPROCATED"
|
"INITIATED" if first else "RECIPROCATED"
|
||||||
)
|
)
|
||||||
data["lightweight_action_attachment[lwa_type]"] = "WAVE"
|
data["lightweight_action_attachment[lwa_type]"] = "WAVE"
|
||||||
# TODO: This!
|
|
||||||
# if isinstance(self, _user.User):
|
|
||||||
# data["specific_to_list[0]"] = "fbid:{}".format(thread_id)
|
|
||||||
message_id, thread_id = self.session._do_send_request(data)
|
message_id, thread_id = self.session._do_send_request(data)
|
||||||
return message_id
|
return message_id
|
||||||
|
|
||||||
def send(self, message) -> str:
|
def send_text(
|
||||||
"""Send message to the thread.
|
self,
|
||||||
|
text: str,
|
||||||
|
mentions: Iterable["_message.Mention"] = None,
|
||||||
|
files: Iterable[Tuple[str, str]] = None,
|
||||||
|
reply_to_id: str = None,
|
||||||
|
) -> str:
|
||||||
|
"""Send a message to the thread.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
message (Message): Message to send
|
text: Text to send
|
||||||
|
mentions: Optional mentions
|
||||||
|
files: Optional tuples, each containing an uploaded file's ID and mimetype
|
||||||
|
reply_to_id: Optional message to reply to
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:ref:`Message ID <intro_message_ids>` of the sent message
|
:ref:`Message ID <intro_message_ids>` of the sent message
|
||||||
"""
|
"""
|
||||||
data = self._to_send_data()
|
data = self._to_send_data()
|
||||||
data.update(message._to_send_data())
|
data["action_type"] = "ma-type:user-generated-message"
|
||||||
|
if text is None: # To support `send_files`
|
||||||
|
data["body"] = text
|
||||||
|
|
||||||
|
for i, mention in enumerate(mentions or ()):
|
||||||
|
data.update(mention._to_send_data(i))
|
||||||
|
|
||||||
|
if files:
|
||||||
|
data["has_attachment"] = True
|
||||||
|
|
||||||
|
for i, (file_id, mimetype) in enumerate(files or ()):
|
||||||
|
data["{}s[{}]".format(_util.mimetype_to_key(mimetype), i)] = file_id
|
||||||
|
|
||||||
|
if reply_to_id:
|
||||||
|
data["replied_to_message_id"] = reply_to_id
|
||||||
|
|
||||||
return self.session._do_send_request(data)
|
return self.session._do_send_request(data)
|
||||||
|
|
||||||
def _send_location(self, current, latitude, longitude, message=None) -> str:
|
def send_emoji(self, emoji: str, size: "_message.EmojiSize") -> str:
|
||||||
|
"""Send an emoji to the thread.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
emoji: The emoji to send
|
||||||
|
size: The size of the emoji
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
:ref:`Message ID <intro_message_ids>` of the sent message
|
||||||
|
"""
|
||||||
|
data = self._to_send_data()
|
||||||
|
data["action_type"] = "ma-type:user-generated-message"
|
||||||
|
data["body"] = emoji
|
||||||
|
data["tags[0]"] = "hot_emoji_size:{}".format(size.name.lower())
|
||||||
|
return self.session._do_send_request(data)
|
||||||
|
|
||||||
|
def send_sticker(self, sticker_id: str) -> str:
|
||||||
|
"""Send a sticker to the thread.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sticker_id: ID of the sticker to send
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
:ref:`Message ID <intro_message_ids>` of the sent message
|
||||||
|
"""
|
||||||
|
data = self._to_send_data()
|
||||||
|
data["action_type"] = "ma-type:user-generated-message"
|
||||||
|
data["sticker_id"] = sticker_id
|
||||||
|
return self.session._do_send_request(data)
|
||||||
|
|
||||||
|
def _send_location(self, current, latitude, longitude) -> str:
|
||||||
data = self._to_send_data()
|
data = self._to_send_data()
|
||||||
if message is not None:
|
|
||||||
data.update(message._to_send_data())
|
|
||||||
data["action_type"] = "ma-type:user-generated-message"
|
data["action_type"] = "ma-type:user-generated-message"
|
||||||
data["location_attachment[coordinates][latitude]"] = latitude
|
data["location_attachment[coordinates][latitude]"] = latitude
|
||||||
data["location_attachment[coordinates][longitude]"] = longitude
|
data["location_attachment[coordinates][longitude]"] = longitude
|
||||||
data["location_attachment[is_current_location]"] = current
|
data["location_attachment[is_current_location]"] = current
|
||||||
return self.session._do_send_request(data)
|
return self.session._do_send_request(data)
|
||||||
|
|
||||||
def send_location(self, latitude: float, longitude: float, message=None):
|
def send_location(self, latitude: float, longitude: float):
|
||||||
"""Send a given location to a thread as the user's current location.
|
"""Send a given location to a thread as the user's current location.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
latitude: The location latitude
|
latitude: The location latitude
|
||||||
longitude: The location longitude
|
longitude: The location longitude
|
||||||
message: Additional message
|
|
||||||
"""
|
"""
|
||||||
self._send_location(
|
self._send_location(True, latitude=latitude, longitude=longitude)
|
||||||
True, latitude=latitude, longitude=longitude, message=message,
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_pinned_location(self, latitude: float, longitude: float, message=None):
|
def send_pinned_location(self, latitude: float, longitude: float):
|
||||||
"""Send a given location to a thread as a pinned location.
|
"""Send a given location to a thread as a pinned location.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
latitude: The location latitude
|
latitude: The location latitude
|
||||||
longitude: The location longitude
|
longitude: The location longitude
|
||||||
message: Additional message
|
|
||||||
"""
|
"""
|
||||||
self._send_location(
|
self._send_location(False, latitude=latitude, longitude=longitude)
|
||||||
False, latitude=latitude, longitude=longitude, message=message,
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_files(self, files: Iterable[Tuple[str, str]], message):
|
def send_files(self, files: Iterable[Tuple[str, str]]):
|
||||||
"""Send files from file IDs to a thread.
|
"""Send files from file IDs to a thread.
|
||||||
|
|
||||||
`files` should be a list of tuples, with a file's ID and mimetype.
|
`files` should be a list of tuples, with a file's ID and mimetype.
|
||||||
"""
|
"""
|
||||||
data = self._to_send_data()
|
return self.send_text(text=None, files=files)
|
||||||
data.update(message._to_send_data())
|
|
||||||
data["action_type"] = "ma-type:user-generated-message"
|
|
||||||
data["has_attachment"] = True
|
|
||||||
|
|
||||||
for i, (file_id, mimetype) in enumerate(files):
|
# xmd = {"quick_replies": []}
|
||||||
data["{}s[{}]".format(_util.mimetype_to_key(mimetype), i)] = file_id
|
# for quick_reply in quick_replies:
|
||||||
|
# # TODO: Move this to `_quick_reply.py`
|
||||||
return self.session._do_send_request(data)
|
# q = dict()
|
||||||
|
# q["content_type"] = quick_reply._type
|
||||||
|
# q["payload"] = quick_reply.payload
|
||||||
|
# q["external_payload"] = quick_reply.external_payload
|
||||||
|
# q["data"] = quick_reply.data
|
||||||
|
# if quick_reply.is_response:
|
||||||
|
# q["ignore_for_webhook"] = False
|
||||||
|
# if isinstance(quick_reply, _quick_reply.QuickReplyText):
|
||||||
|
# q["title"] = quick_reply.title
|
||||||
|
# if not isinstance(quick_reply, _quick_reply.QuickReplyLocation):
|
||||||
|
# q["image_url"] = quick_reply.image_url
|
||||||
|
# xmd["quick_replies"].append(q)
|
||||||
|
# if len(quick_replies) == 1 and quick_replies[0].is_response:
|
||||||
|
# xmd["quick_replies"] = xmd["quick_replies"][0]
|
||||||
|
# data["platform_xmd"] = json.dumps(xmd)
|
||||||
|
|
||||||
# TODO: This!
|
# TODO: This!
|
||||||
# def quick_reply(self, quick_reply, payload=None):
|
# def quick_reply(self, quick_reply, payload=None):
|
||||||
@@ -253,8 +318,11 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
read_receipts = j["message_thread"]["read_receipts"]["nodes"]
|
read_receipts = j["message_thread"]["read_receipts"]["nodes"]
|
||||||
|
|
||||||
|
# TODO: May or may not be a good idea to attach the current thread?
|
||||||
|
# For now, we just create a new thread:
|
||||||
|
thread = self.__class__(session=self.session, id=self.id)
|
||||||
messages = [
|
messages = [
|
||||||
_message.Message._from_graphql(self.session, message, read_receipts)
|
_message.MessageData._from_graphql(thread, message, read_receipts)
|
||||||
for message in j["message_thread"]["messages"]["nodes"]
|
for message in j["message_thread"]["messages"]["nodes"]
|
||||||
]
|
]
|
||||||
messages.reverse()
|
messages.reverse()
|
||||||
@@ -381,24 +449,10 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
|||||||
# TODO: Arguments
|
# TODO: Arguments
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
title: Name of the new plan
|
name: Name of the new plan
|
||||||
at: When the plan is for
|
at: When the plan is for
|
||||||
"""
|
"""
|
||||||
data = {
|
return _plan.Plan._create(self, name, at, location_name, location_id)
|
||||||
"event_type": "EVENT",
|
|
||||||
"event_time": _util.datetime_to_seconds(at),
|
|
||||||
"title": name,
|
|
||||||
"thread_id": self.id,
|
|
||||||
"location_id": location_id or "",
|
|
||||||
"location_name": location_name or "",
|
|
||||||
"acontext": _plan.ACONTEXT,
|
|
||||||
}
|
|
||||||
j = self.session._payload_post("/ajax/eventreminder/create", data)
|
|
||||||
if "error" in j:
|
|
||||||
raise _exception.FBchatFacebookError(
|
|
||||||
"Failed creating plan: {}".format(j["error"]),
|
|
||||||
fb_error_message=j["error"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_poll(self, question: str, options=Iterable[Tuple[str, bool]]):
|
def create_poll(self, question: str, options=Iterable[Tuple[str, bool]]):
|
||||||
"""Create poll in a thread.
|
"""Create poll in a thread.
|
||||||
|
128
fbchat/_user.py
128
fbchat/_user.py
@@ -1,5 +1,5 @@
|
|||||||
import attr
|
import attr
|
||||||
from ._core import attrs_default, Enum, Image
|
from ._core import log, attrs_default, Enum, Image
|
||||||
from . import _util, _session, _plan, _thread
|
from . import _util, _session, _plan, _thread
|
||||||
|
|
||||||
|
|
||||||
@@ -48,39 +48,13 @@ class User(_thread.ThreadABC):
|
|||||||
session = attr.ib(type=_session.Session)
|
session = attr.ib(type=_session.Session)
|
||||||
#: The user's unique identifier.
|
#: The user's unique identifier.
|
||||||
id = attr.ib(converter=str)
|
id = attr.ib(converter=str)
|
||||||
#: The user's picture
|
|
||||||
photo = attr.ib(None)
|
|
||||||
#: The name of the user
|
|
||||||
name = attr.ib(None)
|
|
||||||
#: Datetime when the thread was last active / when the last message was sent
|
|
||||||
last_active = attr.ib(None)
|
|
||||||
#: Number of messages in the thread
|
|
||||||
message_count = attr.ib(None)
|
|
||||||
#: Set `Plan`
|
|
||||||
plan = attr.ib(None)
|
|
||||||
#: The profile URL
|
|
||||||
url = attr.ib(None)
|
|
||||||
#: The users first name
|
|
||||||
first_name = attr.ib(None)
|
|
||||||
#: The users last name
|
|
||||||
last_name = attr.ib(None)
|
|
||||||
#: Whether the user and the client are friends
|
|
||||||
is_friend = attr.ib(None)
|
|
||||||
#: The user's gender
|
|
||||||
gender = attr.ib(None)
|
|
||||||
#: From 0 to 1. How close the client is to the user
|
|
||||||
affinity = attr.ib(None)
|
|
||||||
#: The user's nickname
|
|
||||||
nickname = attr.ib(None)
|
|
||||||
#: The clients nickname, as seen by the user
|
|
||||||
own_nickname = attr.ib(None)
|
|
||||||
#: A `ThreadColor`. The message color
|
|
||||||
color = attr.ib(None)
|
|
||||||
#: The default emoji
|
|
||||||
emoji = attr.ib(None)
|
|
||||||
|
|
||||||
def _to_send_data(self):
|
def _to_send_data(self):
|
||||||
return {"other_user_fbid": self.id}
|
return {
|
||||||
|
"other_user_fbid": self.id,
|
||||||
|
# The entry below is to support .wave
|
||||||
|
"specific_to_list[0]": "fbid:{}".format(self.id),
|
||||||
|
}
|
||||||
|
|
||||||
def confirm_friend_request(self):
|
def confirm_friend_request(self):
|
||||||
"""Confirm a friend request, adding the user to your friend list."""
|
"""Confirm a friend request, adding the user to your friend list."""
|
||||||
@@ -102,6 +76,45 @@ class User(_thread.ThreadABC):
|
|||||||
data = {"fbid": self.id}
|
data = {"fbid": self.id}
|
||||||
j = self.session._payload_post("/messaging/unblock_messages/?dpr=1", data)
|
j = self.session._payload_post("/messaging/unblock_messages/?dpr=1", data)
|
||||||
|
|
||||||
|
|
||||||
|
@attrs_default
|
||||||
|
class UserData(User):
|
||||||
|
"""Represents data about a Facebook user.
|
||||||
|
|
||||||
|
Inherits `User`, and implements `ThreadABC`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: The user's picture
|
||||||
|
photo = attr.ib()
|
||||||
|
#: The name of the user
|
||||||
|
name = attr.ib()
|
||||||
|
#: Whether the user and the client are friends
|
||||||
|
is_friend = attr.ib()
|
||||||
|
#: The users first name
|
||||||
|
first_name = attr.ib()
|
||||||
|
#: The users last name
|
||||||
|
last_name = attr.ib(None)
|
||||||
|
#: Datetime when the thread was last active / when the last message was sent
|
||||||
|
last_active = attr.ib(None)
|
||||||
|
#: Number of messages in the thread
|
||||||
|
message_count = attr.ib(None)
|
||||||
|
#: Set `Plan`
|
||||||
|
plan = attr.ib(None)
|
||||||
|
#: The profile URL. ``None`` for Messenger-only users
|
||||||
|
url = attr.ib(None)
|
||||||
|
#: The user's gender
|
||||||
|
gender = attr.ib(None)
|
||||||
|
#: From 0 to 1. How close the client is to the user
|
||||||
|
affinity = attr.ib(None)
|
||||||
|
#: The user's nickname
|
||||||
|
nickname = attr.ib(None)
|
||||||
|
#: The clients nickname, as seen by the user
|
||||||
|
own_nickname = attr.ib(None)
|
||||||
|
#: A `ThreadColor`. The message color
|
||||||
|
color = attr.ib(None)
|
||||||
|
#: The default emoji
|
||||||
|
emoji = attr.ib(None)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _from_graphql(cls, session, data):
|
def _from_graphql(cls, session, data):
|
||||||
if data.get("profile_picture") is None:
|
if data.get("profile_picture") is None:
|
||||||
@@ -109,23 +122,25 @@ class User(_thread.ThreadABC):
|
|||||||
c_info = cls._parse_customization_info(data)
|
c_info = cls._parse_customization_info(data)
|
||||||
plan = None
|
plan = None
|
||||||
if data.get("event_reminders") and data["event_reminders"].get("nodes"):
|
if data.get("event_reminders") and data["event_reminders"].get("nodes"):
|
||||||
plan = _plan.Plan._from_graphql(data["event_reminders"]["nodes"][0])
|
plan = _plan.PlanData._from_graphql(
|
||||||
|
session, data["event_reminders"]["nodes"][0]
|
||||||
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
session=session,
|
session=session,
|
||||||
id=data["id"],
|
id=data["id"],
|
||||||
url=data.get("url"),
|
url=data["url"],
|
||||||
first_name=data.get("first_name"),
|
first_name=data["first_name"],
|
||||||
last_name=data.get("last_name"),
|
last_name=data.get("last_name"),
|
||||||
is_friend=data.get("is_viewer_friend"),
|
is_friend=data["is_viewer_friend"],
|
||||||
gender=GENDERS.get(data.get("gender")),
|
gender=GENDERS.get(data["gender"]),
|
||||||
affinity=data.get("viewer_affinity"),
|
affinity=data.get("viewer_affinity"),
|
||||||
nickname=c_info.get("nickname"),
|
nickname=c_info.get("nickname"),
|
||||||
color=c_info.get("color"),
|
color=c_info.get("color"),
|
||||||
emoji=c_info.get("emoji"),
|
emoji=c_info.get("emoji"),
|
||||||
own_nickname=c_info.get("own_nickname"),
|
own_nickname=c_info.get("own_nickname"),
|
||||||
photo=Image._from_uri(data["profile_picture"]),
|
photo=Image._from_uri(data["profile_picture"]),
|
||||||
name=data.get("name"),
|
name=data["name"],
|
||||||
message_count=data.get("messages_count"),
|
message_count=data.get("messages_count"),
|
||||||
plan=plan,
|
plan=plan,
|
||||||
)
|
)
|
||||||
@@ -141,38 +156,41 @@ class User(_thread.ThreadABC):
|
|||||||
user = next(
|
user = next(
|
||||||
p for p in participants if p["id"] == data["thread_key"]["other_user_id"]
|
p for p in participants if p["id"] == data["thread_key"]["other_user_id"]
|
||||||
)
|
)
|
||||||
|
if user["__typename"] != "User":
|
||||||
|
# TODO: Add Page._from_thread_fetch, and parse it there
|
||||||
|
log.warning("Tried to parse %s as a user.", user["__typename"])
|
||||||
|
return None
|
||||||
|
|
||||||
last_active = None
|
last_active = None
|
||||||
if "last_message" in data:
|
if "last_message" in data:
|
||||||
last_active = _util.millis_to_datetime(
|
last_active = _util.millis_to_datetime(
|
||||||
int(data["last_message"]["nodes"][0]["timestamp_precise"])
|
int(data["last_message"]["nodes"][0]["timestamp_precise"])
|
||||||
)
|
)
|
||||||
|
|
||||||
first_name = user.get("short_name")
|
first_name = user["short_name"]
|
||||||
if first_name is None:
|
|
||||||
last_name = None
|
|
||||||
else:
|
|
||||||
last_name = user.get("name").split(first_name, 1).pop().strip()
|
last_name = user.get("name").split(first_name, 1).pop().strip()
|
||||||
|
|
||||||
plan = None
|
plan = None
|
||||||
if data.get("event_reminders") and data["event_reminders"].get("nodes"):
|
if data.get("event_reminders") and data["event_reminders"].get("nodes"):
|
||||||
plan = _plan.Plan._from_graphql(data["event_reminders"]["nodes"][0])
|
plan = _plan.PlanData._from_graphql(
|
||||||
|
session, data["event_reminders"]["nodes"][0]
|
||||||
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
session=session,
|
session=session,
|
||||||
id=user["id"],
|
id=user["id"],
|
||||||
url=user.get("url"),
|
url=user["url"],
|
||||||
name=user.get("name"),
|
name=user["name"],
|
||||||
first_name=first_name,
|
first_name=first_name,
|
||||||
last_name=last_name,
|
last_name=last_name,
|
||||||
is_friend=user.get("is_viewer_friend"),
|
is_friend=user["is_viewer_friend"],
|
||||||
gender=GENDERS.get(user.get("gender")),
|
gender=GENDERS.get(user["gender"]),
|
||||||
affinity=user.get("affinity"),
|
|
||||||
nickname=c_info.get("nickname"),
|
nickname=c_info.get("nickname"),
|
||||||
color=c_info.get("color"),
|
color=c_info.get("color"),
|
||||||
emoji=c_info.get("emoji"),
|
emoji=c_info.get("emoji"),
|
||||||
own_nickname=c_info.get("own_nickname"),
|
own_nickname=c_info.get("own_nickname"),
|
||||||
photo=Image._from_uri(user["big_image_src"]),
|
photo=Image._from_uri(user["big_image_src"]),
|
||||||
message_count=data.get("messages_count"),
|
message_count=data["messages_count"],
|
||||||
last_active=last_active,
|
last_active=last_active,
|
||||||
plan=plan,
|
plan=plan,
|
||||||
)
|
)
|
||||||
@@ -182,12 +200,12 @@ class User(_thread.ThreadABC):
|
|||||||
return cls(
|
return cls(
|
||||||
session=session,
|
session=session,
|
||||||
id=data["id"],
|
id=data["id"],
|
||||||
first_name=data.get("firstName"),
|
first_name=data["firstName"],
|
||||||
url=data.get("uri"),
|
url=data["uri"],
|
||||||
photo=Image(url=data.get("thumbSrc")),
|
photo=Image(url=data["thumbSrc"]),
|
||||||
name=data.get("name"),
|
name=data["name"],
|
||||||
is_friend=data.get("is_friend"),
|
is_friend=data["is_friend"],
|
||||||
gender=GENDERS.get(data.get("gender")),
|
gender=GENDERS.get(data["gender"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -2,11 +2,7 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
import contextlib
|
|
||||||
import mimetypes
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import requests
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
from ._core import log
|
from ._core import log
|
||||||
from ._exception import (
|
from ._exception import (
|
||||||
@@ -188,39 +184,6 @@ def mimetype_to_key(mimetype):
|
|||||||
return "file_id"
|
return "file_id"
|
||||||
|
|
||||||
|
|
||||||
def get_files_from_urls(file_urls):
|
|
||||||
files = []
|
|
||||||
for file_url in file_urls:
|
|
||||||
r = requests.get(file_url)
|
|
||||||
# We could possibly use r.headers.get('Content-Disposition'), see
|
|
||||||
# https://stackoverflow.com/a/37060758
|
|
||||||
file_name = path.basename(file_url).split("?")[0].split("#")[0]
|
|
||||||
files.append(
|
|
||||||
(
|
|
||||||
file_name,
|
|
||||||
r.content,
|
|
||||||
r.headers.get("Content-Type") or mimetypes.guess_type(file_name)[0],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return files
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def get_files_from_paths(filenames):
|
|
||||||
files = []
|
|
||||||
for filename in filenames:
|
|
||||||
files.append(
|
|
||||||
(
|
|
||||||
path.basename(filename),
|
|
||||||
open(filename, "rb"),
|
|
||||||
mimetypes.guess_type(filename)[0],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
yield files
|
|
||||||
for fn, fp, ft in files:
|
|
||||||
fp.close()
|
|
||||||
|
|
||||||
|
|
||||||
def get_url_parameters(url, *args):
|
def get_url_parameters(url, *args):
|
||||||
params = urllib.parse.parse_qs(urllib.parse.urlparse(url).query)
|
params = urllib.parse.parse_qs(urllib.parse.urlparse(url).query)
|
||||||
return [params[arg][0] for arg in args if params.get(arg)]
|
return [params[arg][0] for arg in args if params.get(arg)]
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from fbchat._group import Group
|
from fbchat._group import GroupData
|
||||||
|
|
||||||
|
|
||||||
def test_group_from_graphql(session):
|
def test_group_from_graphql(session):
|
||||||
@@ -25,7 +25,7 @@ def test_group_from_graphql(session):
|
|||||||
"joinable_mode": {"mode": "0", "link": ""},
|
"joinable_mode": {"mode": "0", "link": ""},
|
||||||
"event_reminders": {"nodes": []},
|
"event_reminders": {"nodes": []},
|
||||||
}
|
}
|
||||||
assert Group(
|
assert GroupData(
|
||||||
session=session,
|
session=session,
|
||||||
id="11223344",
|
id="11223344",
|
||||||
photo=None,
|
photo=None,
|
||||||
@@ -41,4 +41,4 @@ def test_group_from_graphql(session):
|
|||||||
approval_mode=False,
|
approval_mode=False,
|
||||||
approval_requests=set(),
|
approval_requests=set(),
|
||||||
join_link="",
|
join_link="",
|
||||||
) == Group._from_graphql(session, data)
|
) == GroupData._from_graphql(session, data)
|
||||||
|
@@ -4,6 +4,7 @@ from fbchat._message import (
|
|||||||
EmojiSize,
|
EmojiSize,
|
||||||
Mention,
|
Mention,
|
||||||
Message,
|
Message,
|
||||||
|
MessageData,
|
||||||
graphql_to_extensible_attachment,
|
graphql_to_extensible_attachment,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,10 +47,25 @@ def test_graphql_to_extensible_attachment_dispatch(monkeypatch, obj, type_):
|
|||||||
assert graphql_to_extensible_attachment(data)
|
assert graphql_to_extensible_attachment(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mention_to_send_data():
|
||||||
|
assert {
|
||||||
|
"profile_xmd[0][id]": "1234",
|
||||||
|
"profile_xmd[0][length]": 7,
|
||||||
|
"profile_xmd[0][offset]": 4,
|
||||||
|
"profile_xmd[0][type]": "p",
|
||||||
|
} == Mention(thread_id="1234", offset=4, length=7)._to_send_data(0)
|
||||||
|
assert {
|
||||||
|
"profile_xmd[1][id]": "4321",
|
||||||
|
"profile_xmd[1][length]": 7,
|
||||||
|
"profile_xmd[1][offset]": 24,
|
||||||
|
"profile_xmd[1][type]": "p",
|
||||||
|
} == Mention(thread_id="4321", offset=24, length=7)._to_send_data(1)
|
||||||
|
|
||||||
|
|
||||||
def test_message_format_mentions():
|
def test_message_format_mentions():
|
||||||
expected = Message(
|
expected = (
|
||||||
text="Hey 'Peter'! My name is Michael",
|
"Hey 'Peter'! My name is Michael",
|
||||||
mentions=[
|
[
|
||||||
Mention(thread_id="1234", offset=4, length=7),
|
Mention(thread_id="1234", offset=4, length=7),
|
||||||
Mention(thread_id="4321", offset=24, length=7),
|
Mention(thread_id="4321", offset=24, length=7),
|
||||||
],
|
],
|
||||||
@@ -63,63 +79,13 @@ def test_message_format_mentions():
|
|||||||
|
|
||||||
|
|
||||||
def test_message_get_forwarded_from_tags():
|
def test_message_get_forwarded_from_tags():
|
||||||
assert not Message._get_forwarded_from_tags(None)
|
assert not MessageData._get_forwarded_from_tags(None)
|
||||||
assert not Message._get_forwarded_from_tags(["hot_emoji_size:unknown"])
|
assert not MessageData._get_forwarded_from_tags(["hot_emoji_size:unknown"])
|
||||||
assert Message._get_forwarded_from_tags(
|
assert MessageData._get_forwarded_from_tags(
|
||||||
["attachment:photo", "inbox", "sent", "source:chat:forward", "tq"]
|
["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(id="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")
|
@pytest.mark.skip(reason="need to be added")
|
||||||
def test_message_to_send_data_quick_replies():
|
def test_message_to_send_data_quick_replies():
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import fbchat
|
import fbchat
|
||||||
from fbchat._page import Page
|
from fbchat._page import PageData
|
||||||
|
|
||||||
|
|
||||||
def test_page_from_graphql(session):
|
def test_page_from_graphql(session):
|
||||||
@@ -11,7 +11,7 @@ def test_page_from_graphql(session):
|
|||||||
"category_type": "SCHOOL",
|
"category_type": "SCHOOL",
|
||||||
"city": None,
|
"city": None,
|
||||||
}
|
}
|
||||||
assert Page(
|
assert PageData(
|
||||||
session=session,
|
session=session,
|
||||||
id="123456",
|
id="123456",
|
||||||
photo=fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/..."),
|
photo=fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/..."),
|
||||||
@@ -19,4 +19,4 @@ def test_page_from_graphql(session):
|
|||||||
url="https://www.facebook.com/some-school/",
|
url="https://www.facebook.com/some-school/",
|
||||||
city=None,
|
city=None,
|
||||||
category="SCHOOL",
|
category="SCHOOL",
|
||||||
) == Page._from_graphql(session, data)
|
) == PageData._from_graphql(session, data)
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from fbchat._plan import GuestStatus, Plan
|
from fbchat._plan import GuestStatus, PlanData
|
||||||
|
|
||||||
|
|
||||||
def test_plan_properties():
|
def test_plan_properties(session):
|
||||||
plan = Plan(
|
plan = PlanData(
|
||||||
|
session=session,
|
||||||
|
id="1234567890",
|
||||||
time=...,
|
time=...,
|
||||||
title=...,
|
title=...,
|
||||||
guests={
|
guests={
|
||||||
@@ -18,7 +20,7 @@ def test_plan_properties():
|
|||||||
assert plan.declined == ["4567"]
|
assert plan.declined == ["4567"]
|
||||||
|
|
||||||
|
|
||||||
def test_plan_from_pull():
|
def test_plan_from_pull(session):
|
||||||
data = {
|
data = {
|
||||||
"event_timezone": "",
|
"event_timezone": "",
|
||||||
"event_creator_id": "1234",
|
"event_creator_id": "1234",
|
||||||
@@ -35,7 +37,8 @@ def test_plan_from_pull():
|
|||||||
'{"guest_list_state":"GOING","node":{"id":"4567"}}]'
|
'{"guest_list_state":"GOING","node":{"id":"4567"}}]'
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
assert Plan(
|
assert PlanData(
|
||||||
|
session=session,
|
||||||
id="1111",
|
id="1111",
|
||||||
time=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
time=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
||||||
title="abc",
|
title="abc",
|
||||||
@@ -46,10 +49,10 @@ def test_plan_from_pull():
|
|||||||
"3456": GuestStatus.DECLINED,
|
"3456": GuestStatus.DECLINED,
|
||||||
"4567": GuestStatus.GOING,
|
"4567": GuestStatus.GOING,
|
||||||
},
|
},
|
||||||
) == Plan._from_pull(data)
|
) == PlanData._from_pull(session, data)
|
||||||
|
|
||||||
|
|
||||||
def test_plan_from_fetch():
|
def test_plan_from_fetch(session):
|
||||||
data = {
|
data = {
|
||||||
"message_thread_id": 123456789,
|
"message_thread_id": 123456789,
|
||||||
"event_time": 1500000000,
|
"event_time": 1500000000,
|
||||||
@@ -92,7 +95,8 @@ def test_plan_from_fetch():
|
|||||||
"4567": "GOING",
|
"4567": "GOING",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert Plan(
|
assert PlanData(
|
||||||
|
session=session,
|
||||||
id=1111,
|
id=1111,
|
||||||
time=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
time=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
||||||
title="abc",
|
title="abc",
|
||||||
@@ -105,10 +109,10 @@ def test_plan_from_fetch():
|
|||||||
"3456": GuestStatus.DECLINED,
|
"3456": GuestStatus.DECLINED,
|
||||||
"4567": GuestStatus.GOING,
|
"4567": GuestStatus.GOING,
|
||||||
},
|
},
|
||||||
) == Plan._from_fetch(data)
|
) == PlanData._from_fetch(session, data)
|
||||||
|
|
||||||
|
|
||||||
def test_plan_from_graphql():
|
def test_plan_from_graphql(session):
|
||||||
data = {
|
data = {
|
||||||
"id": "1111",
|
"id": "1111",
|
||||||
"lightweight_event_creator": {"id": "1234"},
|
"lightweight_event_creator": {"id": "1234"},
|
||||||
@@ -134,7 +138,8 @@ def test_plan_from_graphql():
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert Plan(
|
assert PlanData(
|
||||||
|
session=session,
|
||||||
time=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
time=datetime.datetime(2017, 7, 14, 2, 40, tzinfo=datetime.timezone.utc),
|
||||||
title="abc",
|
title="abc",
|
||||||
location="",
|
location="",
|
||||||
@@ -147,4 +152,4 @@ def test_plan_from_graphql():
|
|||||||
"3456": GuestStatus.DECLINED,
|
"3456": GuestStatus.DECLINED,
|
||||||
"4567": GuestStatus.GOING,
|
"4567": GuestStatus.GOING,
|
||||||
},
|
},
|
||||||
) == Plan._from_graphql(data)
|
) == PlanData._from_graphql(session, data)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from fbchat import Plan, FBchatFacebookError
|
from fbchat import PlanData, FBchatFacebookError
|
||||||
from utils import random_hex, subset
|
from utils import random_hex, subset
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
@@ -10,12 +10,12 @@ pytestmark = pytest.mark.online
|
|||||||
@pytest.fixture(
|
@pytest.fixture(
|
||||||
scope="module",
|
scope="module",
|
||||||
params=[
|
params=[
|
||||||
Plan(time=int(time()) + 100, title=random_hex()),
|
# PlanData(time=int(time()) + 100, title=random_hex()),
|
||||||
pytest.param(
|
# pytest.param(
|
||||||
Plan(time=int(time()), title=random_hex()),
|
# PlanData(time=int(time()), title=random_hex()),
|
||||||
marks=[pytest.mark.xfail(raises=FBchatFacebookError)],
|
# marks=[pytest.mark.xfail(raises=FBchatFacebookError)],
|
||||||
),
|
# ),
|
||||||
pytest.param(Plan(time=0, title=None), marks=[pytest.mark.xfail()]),
|
# pytest.param(PlanData(time=0, title=None), marks=[pytest.mark.xfail()]),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def plan_data(request, client, user, thread, catch_event, compare):
|
def plan_data(request, client, user, thread, catch_event, compare):
|
||||||
@@ -73,7 +73,7 @@ def test_change_plan_participation(
|
|||||||
@pytest.mark.trylast
|
@pytest.mark.trylast
|
||||||
def test_edit_plan(client, thread, catch_event, compare, plan_data):
|
def test_edit_plan(client, thread, catch_event, compare, plan_data):
|
||||||
event, plan = plan_data
|
event, plan = plan_data
|
||||||
new_plan = Plan(plan.time + 100, random_hex())
|
new_plan = PlanData(plan.time + 100, random_hex())
|
||||||
with catch_event("on_plan_edited") as x:
|
with catch_event("on_plan_edited") as x:
|
||||||
client.edit_plan(plan, new_plan)
|
client.edit_plan(plan, new_plan)
|
||||||
assert compare(x)
|
assert compare(x)
|
||||||
@@ -89,7 +89,7 @@ def test_edit_plan(client, thread, catch_event, compare, plan_data):
|
|||||||
@pytest.mark.skip
|
@pytest.mark.skip
|
||||||
def test_on_plan_ended(client, thread, catch_event, compare):
|
def test_on_plan_ended(client, thread, catch_event, compare):
|
||||||
with catch_event("on_plan_ended") as x:
|
with catch_event("on_plan_ended") as x:
|
||||||
client.create_plan(Plan(int(time()) + 120, "Wait for ending"))
|
client.create_plan(PlanData(int(time()) + 120, "Wait for ending"))
|
||||||
x.wait(180)
|
x.wait(180)
|
||||||
assert subset(
|
assert subset(
|
||||||
x.res,
|
x.res,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import datetime
|
import datetime
|
||||||
import fbchat
|
import fbchat
|
||||||
from fbchat._user import User, ActiveStatus
|
from fbchat._user import UserData, ActiveStatus
|
||||||
|
|
||||||
|
|
||||||
def test_user_from_graphql(session):
|
def test_user_from_graphql(session):
|
||||||
@@ -16,7 +16,7 @@ def test_user_from_graphql(session):
|
|||||||
"gender": "FEMALE",
|
"gender": "FEMALE",
|
||||||
"viewer_affinity": 0.4560002,
|
"viewer_affinity": 0.4560002,
|
||||||
}
|
}
|
||||||
assert User(
|
assert UserData(
|
||||||
session=session,
|
session=session,
|
||||||
id="1234",
|
id="1234",
|
||||||
photo=fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/..."),
|
photo=fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/..."),
|
||||||
@@ -27,7 +27,7 @@ def test_user_from_graphql(session):
|
|||||||
is_friend=True,
|
is_friend=True,
|
||||||
gender="female_singular",
|
gender="female_singular",
|
||||||
affinity=0.4560002,
|
affinity=0.4560002,
|
||||||
) == User._from_graphql(session, data)
|
) == UserData._from_graphql(session, data)
|
||||||
|
|
||||||
|
|
||||||
def test_user_from_thread_fetch(session):
|
def test_user_from_thread_fetch(session):
|
||||||
@@ -138,7 +138,7 @@ def test_user_from_thread_fetch(session):
|
|||||||
"read_receipts": ...,
|
"read_receipts": ...,
|
||||||
"delivery_receipts": ...,
|
"delivery_receipts": ...,
|
||||||
}
|
}
|
||||||
assert User(
|
assert UserData(
|
||||||
session=session,
|
session=session,
|
||||||
id="1234",
|
id="1234",
|
||||||
photo=fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/..."),
|
photo=fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/..."),
|
||||||
@@ -154,7 +154,7 @@ def test_user_from_thread_fetch(session):
|
|||||||
own_nickname="B",
|
own_nickname="B",
|
||||||
color=None,
|
color=None,
|
||||||
emoji=None,
|
emoji=None,
|
||||||
) == User._from_thread_fetch(session, data)
|
) == UserData._from_thread_fetch(session, data)
|
||||||
|
|
||||||
|
|
||||||
def test_user_from_all_fetch(session):
|
def test_user_from_all_fetch(session):
|
||||||
@@ -177,7 +177,7 @@ def test_user_from_all_fetch(session):
|
|||||||
"is_nonfriend_messenger_contact": False,
|
"is_nonfriend_messenger_contact": False,
|
||||||
"is_blocked": False,
|
"is_blocked": False,
|
||||||
}
|
}
|
||||||
assert User(
|
assert UserData(
|
||||||
session=session,
|
session=session,
|
||||||
id="1234",
|
id="1234",
|
||||||
photo=fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/..."),
|
photo=fbchat.Image(url="https://scontent-arn2-1.xx.fbcdn.net/v/..."),
|
||||||
@@ -186,7 +186,7 @@ def test_user_from_all_fetch(session):
|
|||||||
first_name="Abc",
|
first_name="Abc",
|
||||||
is_friend=True,
|
is_friend=True,
|
||||||
gender="female_singular",
|
gender="female_singular",
|
||||||
) == User._from_all_fetch(session, data)
|
) == UserData._from_all_fetch(session, data)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="can't gather test data, the pulling is broken")
|
@pytest.mark.skip(reason="can't gather test data, the pulling is broken")
|
||||||
|
Reference in New Issue
Block a user