Add inline examples
This commit is contained in:
@@ -19,11 +19,14 @@ from typing import Sequence, Iterable, Tuple, Optional, Set
|
||||
|
||||
@attrs_default
|
||||
class Client:
|
||||
"""A client for the Facebook Chat (Messenger).
|
||||
"""A client for Facebook Messenger.
|
||||
|
||||
This contains all the methods you use to interact with Facebook. You can extend this
|
||||
class, and overwrite the ``on`` methods, to provide custom event handling (mainly
|
||||
useful while listening).
|
||||
This contains methods that are generally needed to interact with Facebook.
|
||||
|
||||
Example:
|
||||
Create a new client instance.
|
||||
|
||||
>>> client = fbchat.Client(session=session)
|
||||
"""
|
||||
|
||||
#: The session to use when making requests.
|
||||
@@ -39,6 +42,15 @@ class Client:
|
||||
|
||||
But does not include deactivated, deleted or memorialized users (logically,
|
||||
since you can't chat with those).
|
||||
|
||||
The order these are returned is arbitary.
|
||||
|
||||
Example:
|
||||
Get the name of an arbitary user that you're currently chatting with.
|
||||
|
||||
>>> users = client.fetch_users()
|
||||
>>> users[0].name
|
||||
"A user"
|
||||
"""
|
||||
data = {"viewer": self.session.user_id}
|
||||
j = self.session._payload_post("/chat/user_info_all", data)
|
||||
@@ -54,12 +66,18 @@ class Client:
|
||||
def search_for_users(self, name: str, limit: int) -> Iterable[_user.UserData]:
|
||||
"""Find and get users by their name.
|
||||
|
||||
The returned users are ordered by relevance.
|
||||
|
||||
Args:
|
||||
name: Name of the user
|
||||
limit: The max. amount of users to fetch
|
||||
|
||||
Returns:
|
||||
Users, ordered by relevance
|
||||
Example:
|
||||
Get the full name of the first found user.
|
||||
|
||||
>>> (user,) = client.search_for_users("user", limit=1)
|
||||
>>> user.name
|
||||
"A user"
|
||||
"""
|
||||
params = {"search": name, "limit": limit}
|
||||
(j,) = self.session._graphql_requests(
|
||||
@@ -74,9 +92,18 @@ class Client:
|
||||
def search_for_pages(self, name: str, limit: int) -> Iterable[_page.PageData]:
|
||||
"""Find and get pages by their name.
|
||||
|
||||
The returned pages are ordered by relevance.
|
||||
|
||||
Args:
|
||||
name: Name of the page
|
||||
limit: The max. amount of pages to fetch
|
||||
|
||||
Example:
|
||||
Get the full name of the first found page.
|
||||
|
||||
>>> (page,) = client.search_for_pages("page", limit=1)
|
||||
>>> page.name
|
||||
"A page"
|
||||
"""
|
||||
params = {"search": name, "limit": limit}
|
||||
(j,) = self.session._graphql_requests(
|
||||
@@ -91,9 +118,18 @@ class Client:
|
||||
def search_for_groups(self, name: str, limit: int) -> Iterable[_group.GroupData]:
|
||||
"""Find and get group threads by their name.
|
||||
|
||||
The returned groups are ordered by relevance.
|
||||
|
||||
Args:
|
||||
name: Name of the group thread
|
||||
limit: The max. amount of groups to fetch
|
||||
|
||||
Example:
|
||||
Get the full name of the first found group.
|
||||
|
||||
>>> (group,) = client.search_for_groups("group", limit=1)
|
||||
>>> group.name
|
||||
"A group"
|
||||
"""
|
||||
params = {"search": name, "limit": limit}
|
||||
(j,) = self.session._graphql_requests(
|
||||
@@ -108,9 +144,19 @@ class Client:
|
||||
def search_for_threads(self, name: str, limit: int) -> Iterable[_thread.ThreadABC]:
|
||||
"""Find and get threads by their name.
|
||||
|
||||
The returned threads are ordered by relevance.
|
||||
|
||||
Args:
|
||||
name: Name of the thread
|
||||
limit: The max. amount of threads to fetch
|
||||
|
||||
Example:
|
||||
Search for a user, and get the full name of the first found result.
|
||||
|
||||
>>> (user,) = client.search_for_threads("user", limit=1)
|
||||
>>> assert isinstance(user, fbchat.User)
|
||||
>>> user.name
|
||||
"A user"
|
||||
"""
|
||||
params = {"search": name, "limit": limit}
|
||||
(j,) = self.session._graphql_requests(
|
||||
@@ -173,16 +219,26 @@ class Client:
|
||||
Intended to be used alongside `ThreadABC.search_messages`
|
||||
|
||||
Warning! If someone send a message to a thread that matches the query, while
|
||||
we're searching, some snippets will get returned twice.
|
||||
we're searching, some snippets will get returned twice, and some will be lost.
|
||||
|
||||
Not sure if we should handle it, Facebook's implementation doesn't...
|
||||
This is fundamentally unfixable, it's just how the endpoint is implemented.
|
||||
|
||||
Args:
|
||||
query: Text to search for
|
||||
limit: Max. number of threads to retrieve. If ``None``, all threads will be
|
||||
retrieved.
|
||||
retrieved
|
||||
|
||||
Returns:
|
||||
Example:
|
||||
Search for messages, and print the amount of snippets in each thread.
|
||||
|
||||
>>> for thread, count in client.search_messages("abc", limit=3):
|
||||
... print(f"{thread.id} matched the search {count} time(s)")
|
||||
...
|
||||
1234 matched the search 2 time(s)
|
||||
2345 matched the search 1 time(s)
|
||||
3456 matched the search 100 time(s)
|
||||
|
||||
Return:
|
||||
Iterable with tuples of threads, and the total amount of matches.
|
||||
"""
|
||||
offset = 0
|
||||
@@ -237,6 +293,13 @@ class Client:
|
||||
|
||||
Args:
|
||||
ids: Thread ids to query
|
||||
|
||||
Example:
|
||||
Get data about the user with id "4".
|
||||
|
||||
>>> (user,) = client.fetch_thread_info(["4"])
|
||||
>>> user.name
|
||||
"Mark Zuckerberg"
|
||||
"""
|
||||
ids = list(ids)
|
||||
queries = []
|
||||
@@ -323,6 +386,16 @@ class Client:
|
||||
limit: Max. number of threads to retrieve. If ``None``, all threads will be
|
||||
retrieved.
|
||||
location: INBOX, PENDING, ARCHIVED or OTHER
|
||||
|
||||
Example:
|
||||
Fetch the last three threads that the user chatted with.
|
||||
|
||||
>>> for thread in client.fetch_threads(limit=3):
|
||||
... print(f"{thread.id}: {thread.name}")
|
||||
...
|
||||
1234: A user
|
||||
2345: A group
|
||||
3456: A page
|
||||
"""
|
||||
# This is measured empirically as 837, safe default chosen below
|
||||
MAX_BATCH_LIMIT = 100
|
||||
@@ -395,6 +468,10 @@ class Client:
|
||||
Args:
|
||||
image_id: The image you want to fetch
|
||||
|
||||
Example:
|
||||
>>> client.fetch_image_url("1234")
|
||||
"https://scontent-arn1-1.xx.fbcdn.net/v/t1.123-4/1_23_45_n.png?..."
|
||||
|
||||
Returns:
|
||||
An URL where you can download the original image
|
||||
"""
|
||||
@@ -513,7 +590,15 @@ class Client:
|
||||
j = self.session._payload_post("/ajax/mercury/move_thread.php", data)
|
||||
|
||||
def delete_threads(self, threads: Iterable[_thread.ThreadABC]):
|
||||
"""Delete threads."""
|
||||
"""Bulk delete threads.
|
||||
|
||||
Args:
|
||||
threads: Threads to delete
|
||||
|
||||
Example:
|
||||
>>> group = fbchat.Group(session=session, id="1234")
|
||||
>>> client.delete_threads([group])
|
||||
"""
|
||||
data_unpin = {}
|
||||
data_delete = {}
|
||||
for i, thread in enumerate(threads):
|
||||
@@ -527,10 +612,15 @@ class Client:
|
||||
)
|
||||
|
||||
def delete_messages(self, messages: Iterable[_message.Message]):
|
||||
"""Delete specified messages.
|
||||
"""Bulk delete specified messages.
|
||||
|
||||
Args:
|
||||
messages: Messages to delete
|
||||
|
||||
Example:
|
||||
>>> message1 = fbchat.Message(thread=thread, id="1234")
|
||||
>>> message2 = fbchat.Message(thread=thread, id="2345")
|
||||
>>> client.delete_threads([message1, message2])
|
||||
"""
|
||||
data = {}
|
||||
for i, message in enumerate(messages):
|
||||
|
@@ -7,7 +7,11 @@ from typing import Sequence, Iterable, Set, Mapping
|
||||
|
||||
@attrs_default
|
||||
class Group(_thread.ThreadABC):
|
||||
"""Represents a Facebook group. Implements `ThreadABC`."""
|
||||
"""Represents a Facebook group. Implements `ThreadABC`.
|
||||
|
||||
Example:
|
||||
>>> group = fbchat.Group(session=session, id="1234")
|
||||
"""
|
||||
|
||||
#: The session to use when making requests.
|
||||
session = attr.ib(type=_session.Session)
|
||||
@@ -22,6 +26,9 @@ class Group(_thread.ThreadABC):
|
||||
|
||||
Args:
|
||||
user_ids: One or more user IDs to add
|
||||
|
||||
Example:
|
||||
>>> group.add_participants(["1234", "2345"])
|
||||
"""
|
||||
data = self._to_send_data()
|
||||
|
||||
@@ -45,6 +52,9 @@ class Group(_thread.ThreadABC):
|
||||
|
||||
Args:
|
||||
user_id: User ID to remove
|
||||
|
||||
Example:
|
||||
>>> group.remove_participant("1234")
|
||||
"""
|
||||
data = {"uid": user_id, "tid": self.id}
|
||||
j = self.session._payload_post("/chat/remove_participants/", data)
|
||||
@@ -62,6 +72,9 @@ class Group(_thread.ThreadABC):
|
||||
|
||||
Args:
|
||||
user_ids: One or more user IDs to set admin
|
||||
|
||||
Example:
|
||||
>>> group.add_admins(["1234", "2345"])
|
||||
"""
|
||||
self._admin_status(user_ids, True)
|
||||
|
||||
@@ -70,6 +83,9 @@ class Group(_thread.ThreadABC):
|
||||
|
||||
Args:
|
||||
user_ids: One or more user IDs to remove admin
|
||||
|
||||
Example:
|
||||
>>> group.remove_admins(["1234", "2345"])
|
||||
"""
|
||||
self._admin_status(user_ids, False)
|
||||
|
||||
@@ -78,6 +94,9 @@ class Group(_thread.ThreadABC):
|
||||
|
||||
Args:
|
||||
title: New title
|
||||
|
||||
Example:
|
||||
>>> group.set_title("Abc")
|
||||
"""
|
||||
data = {"thread_name": title, "thread_id": self.id}
|
||||
j = self.session._payload_post("/messaging/set_thread_name/?dpr=1", data)
|
||||
@@ -87,6 +106,14 @@ class Group(_thread.ThreadABC):
|
||||
|
||||
Args:
|
||||
image_id: ID of uploaded image
|
||||
|
||||
Example:
|
||||
Upload an image, and use it as the group image.
|
||||
|
||||
>>> with open("image.png", "rb") as f:
|
||||
... (file,) = session._upload([("image.png", f, "image/png")])
|
||||
...
|
||||
>>> group.set_image(file[0])
|
||||
"""
|
||||
data = {"thread_image_id": image_id, "thread_id": self.id}
|
||||
j = self.session._payload_post("/messaging/set_thread_image/?dpr=1", data)
|
||||
@@ -96,6 +123,9 @@ class Group(_thread.ThreadABC):
|
||||
|
||||
Args:
|
||||
require_admin_approval: True or False
|
||||
|
||||
Example:
|
||||
>>> group.set_approval_mode(False)
|
||||
"""
|
||||
data = {"set_mode": int(require_admin_approval), "thread_fbid": self.id}
|
||||
j = self.session._payload_post("/messaging/set_approval_mode/?dpr=1", data)
|
||||
@@ -118,6 +148,9 @@ class Group(_thread.ThreadABC):
|
||||
|
||||
Args:
|
||||
user_ids: One or more user IDs to accept
|
||||
|
||||
Example:
|
||||
>>> group.accept_users(["1234", "2345"])
|
||||
"""
|
||||
self._users_approval(user_ids, True)
|
||||
|
||||
@@ -126,6 +159,9 @@ class Group(_thread.ThreadABC):
|
||||
|
||||
Args:
|
||||
user_ids: One or more user IDs to deny
|
||||
|
||||
Example:
|
||||
>>> group.deny_users(["1234", "2345"])
|
||||
"""
|
||||
self._users_approval(user_ids, False)
|
||||
|
||||
|
@@ -43,7 +43,11 @@ class EmojiSize(enum.Enum):
|
||||
|
||||
@attrs_default
|
||||
class Mention:
|
||||
"""Represents a ``@mention``."""
|
||||
"""Represents a ``@mention``.
|
||||
|
||||
>>> fbchat.Mention(thread_id="1234", offset=5, length=2)
|
||||
Mention(thread_id="1234", offset=5, length=2)
|
||||
"""
|
||||
|
||||
#: The thread ID the mention is pointing at
|
||||
thread_id = attr.ib(type=str)
|
||||
@@ -82,7 +86,12 @@ SENDABLE_REACTIONS = ("❤", "😍", "😆", "😮", "😢", "😠", "👍", "
|
||||
|
||||
@attrs_default
|
||||
class Message:
|
||||
"""Represents a Facebook message."""
|
||||
"""Represents a Facebook message.
|
||||
|
||||
Example:
|
||||
>>> thread = fbchat.User(session=session, id="1234")
|
||||
>>> message = fbchat.Message(thread=thread, id="mid.$XYZ")
|
||||
"""
|
||||
|
||||
#: The thread that this message belongs to.
|
||||
thread = attr.ib(type="_thread.ThreadABC")
|
||||
@@ -95,7 +104,11 @@ class Message:
|
||||
return self.thread.session
|
||||
|
||||
def unsend(self):
|
||||
"""Unsend the message (removes it for everyone)."""
|
||||
"""Unsend the message (removes it for everyone).
|
||||
|
||||
Example:
|
||||
>>> message.unsend()
|
||||
"""
|
||||
data = {"message_id": self.id}
|
||||
j = self.session._payload_post("/messaging/unsend_message/?dpr=1", data)
|
||||
|
||||
@@ -107,6 +120,9 @@ class Message:
|
||||
|
||||
Args:
|
||||
reaction: Reaction emoji to use, or if ``None``, removes reaction.
|
||||
|
||||
Example:
|
||||
>>> message.react("😍")
|
||||
"""
|
||||
if reaction and reaction not in SENDABLE_REACTIONS:
|
||||
raise ValueError(
|
||||
@@ -128,7 +144,13 @@ class Message:
|
||||
_exception.handle_graphql_errors(j)
|
||||
|
||||
def fetch(self) -> "MessageData":
|
||||
"""Fetch fresh `MessageData` object."""
|
||||
"""Fetch fresh `MessageData` object.
|
||||
|
||||
Example:
|
||||
>>> message = message.fetch()
|
||||
>>> message.text
|
||||
"The message text"
|
||||
"""
|
||||
message_info = self.thread._forced_fetch(self.id).get("message")
|
||||
return MessageData._from_graphql(self.thread, message_info)
|
||||
|
||||
@@ -139,10 +161,10 @@ class Message:
|
||||
Return a tuple, with the formatted string and relevant mentions.
|
||||
|
||||
>>> Message.format_mentions("Hey {!r}! My name is {}", ("1234", "Peter"), ("4321", "Michael"))
|
||||
("Hey 'Peter'! My name is Michael", [<Mention 1234: offset=4 length=7>, <Mention 4321: offset=24 length=7>])
|
||||
("Hey 'Peter'! My name is Michael", [Mention(thread_id=1234, offset=4, length=7), Mention(thread_id=4321, offset=24, length=7)])
|
||||
|
||||
>>> Message.format_mentions("Hey {p}! My name is {}", ("1234", "Michael"), p=("4321", "Peter"))
|
||||
('Hey Peter! My name is Michael', [<Mention 4321: offset=4 length=5>, <Mention 1234: offset=22 length=7>])
|
||||
('Hey Peter! My name is Michael', [Mention(thread_id=4321, offset=4, length=5), Mention(thread_id=1234, offset=22, length=7)])
|
||||
"""
|
||||
result = ""
|
||||
mentions = list()
|
||||
|
@@ -49,6 +49,9 @@ class Listener:
|
||||
session: The session to use when making requests.
|
||||
chat_on: Whether ...
|
||||
foreground: Whether ...
|
||||
|
||||
Example:
|
||||
>>> listener = fbchat.Listener.connect(session, chat_on=True, foreground=True)
|
||||
"""
|
||||
mqtt = paho.mqtt.client.Client(
|
||||
client_id="mqttwsclient",
|
||||
@@ -343,6 +346,12 @@ class Listener:
|
||||
Yields events when they arrive.
|
||||
|
||||
This will automatically reconnect on errors.
|
||||
|
||||
Example:
|
||||
Print events continually.
|
||||
|
||||
>>> for event in listener.listen():
|
||||
... print(event)
|
||||
"""
|
||||
while self._loop_once():
|
||||
if self._events:
|
||||
@@ -355,6 +364,14 @@ class Listener:
|
||||
Can be called while listening, which will stop the listening loop.
|
||||
|
||||
The `Listener` object should not be used after this is called!
|
||||
|
||||
Example:
|
||||
Stop the listener when recieving a message with the text "/stop"
|
||||
|
||||
>>> for event in listener.listen():
|
||||
... if isinstance(event, fbchat.MessageEvent):
|
||||
... if event.message.text == "/stop":
|
||||
... listener.disconnect() # Almost the same "break"
|
||||
"""
|
||||
self._mqtt.disconnect()
|
||||
|
||||
|
@@ -6,7 +6,11 @@ from . import _session, _plan, _thread
|
||||
|
||||
@attrs_default
|
||||
class Page(_thread.ThreadABC):
|
||||
"""Represents a Facebook page. Implements `ThreadABC`."""
|
||||
"""Represents a Facebook page. Implements `ThreadABC`.
|
||||
|
||||
Example:
|
||||
>>> page = fbchat.Page(session=session, id="1234")
|
||||
"""
|
||||
|
||||
# TODO: Implement pages properly, the implementation is lacking in a lot of places!
|
||||
|
||||
|
@@ -22,7 +22,11 @@ ACONTEXT = {
|
||||
|
||||
@attrs_default
|
||||
class Plan:
|
||||
"""Base model for plans."""
|
||||
"""Base model for plans.
|
||||
|
||||
Example:
|
||||
>>> plan = fbchat.Plan(session=session, id="1234")
|
||||
"""
|
||||
|
||||
#: The session to use when making requests.
|
||||
session = attr.ib(type=_session.Session)
|
||||
@@ -30,7 +34,13 @@ class Plan:
|
||||
id = attr.ib(converter=str, type=str)
|
||||
|
||||
def fetch(self) -> "PlanData":
|
||||
"""Fetch fresh `PlanData` object."""
|
||||
"""Fetch fresh `PlanData` object.
|
||||
|
||||
Example:
|
||||
>>> plan = plan.fetch()
|
||||
>>> plan.title
|
||||
"A plan"
|
||||
"""
|
||||
data = {"event_reminder_id": self.id}
|
||||
j = self.session._payload_post("/ajax/eventreminder", data)
|
||||
return PlanData._from_fetch(self.session, j)
|
||||
@@ -80,7 +90,11 @@ class Plan:
|
||||
j = self.session._payload_post("/ajax/eventreminder/submit", data)
|
||||
|
||||
def delete(self):
|
||||
"""Delete the plan."""
|
||||
"""Delete the plan.
|
||||
|
||||
Example:
|
||||
>>> plan.delete()
|
||||
"""
|
||||
data = {"event_reminder_id": self.id, "delete": "true", "acontext": ACONTEXT}
|
||||
j = self.session._payload_post("/ajax/eventreminder/submit", data)
|
||||
|
||||
@@ -93,11 +107,19 @@ class Plan:
|
||||
j = self.session._payload_post("/ajax/eventreminder/rsvp", data)
|
||||
|
||||
def participate(self):
|
||||
"""Set yourself as GOING/participating to the plan."""
|
||||
"""Set yourself as GOING/participating to the plan.
|
||||
|
||||
Example:
|
||||
>>> plan.participate()
|
||||
"""
|
||||
return self._change_participation(True)
|
||||
|
||||
def decline(self):
|
||||
"""Set yourself as having DECLINED the plan."""
|
||||
"""Set yourself as having DECLINED the plan.
|
||||
|
||||
Example:
|
||||
>>> plan.decline()
|
||||
"""
|
||||
return self._change_participation(False)
|
||||
|
||||
|
||||
|
@@ -70,7 +70,15 @@ class Poll:
|
||||
)
|
||||
|
||||
def fetch_options(self) -> Sequence[PollOption]:
|
||||
"""Fetch full list of `PollOption` objects on the poll."""
|
||||
"""Fetch all `PollOption` objects on the poll.
|
||||
|
||||
The result is ordered with options with the most votes first.
|
||||
|
||||
Example:
|
||||
>>> options = poll.fetch_options()
|
||||
>>> options[0].text
|
||||
"An option"
|
||||
"""
|
||||
data = {"question_id": self.id}
|
||||
j = self.session._payload_post("/ajax/mercury/get_poll_options", data)
|
||||
return [PollOption._from_graphql(m) for m in j]
|
||||
@@ -83,11 +91,11 @@ class Poll:
|
||||
new_options: New options to add
|
||||
|
||||
Example:
|
||||
options = poll.fetch_options()
|
||||
# Add option
|
||||
poll.set_votes([o.id for o in options], new_options=["New option"])
|
||||
# Remove vote from option
|
||||
poll.set_votes([o.id for o in options if o.text != "Option 1"])
|
||||
>>> options = poll.fetch_options()
|
||||
>>> # Add option
|
||||
>>> poll.set_votes([o.id for o in options], new_options=["New option"])
|
||||
>>> # Remove vote from option
|
||||
>>> poll.set_votes([o.id for o in options if o.text != "Option 1"])
|
||||
"""
|
||||
data = {"question_id": self.id}
|
||||
|
||||
|
@@ -155,10 +155,17 @@ class Session:
|
||||
"""Login the user, using ``email`` and ``password``.
|
||||
|
||||
Args:
|
||||
email: Facebook ``email`` or ``id`` or ``phone number``
|
||||
email: Facebook ``email``, ``id`` or ``phone number``
|
||||
password: Facebook account password
|
||||
on_2fa_callback: Function that will be called, in case a 2FA code is needed.
|
||||
This should return the requested 2FA code.
|
||||
|
||||
Example:
|
||||
>>> import getpass
|
||||
>>> import fbchat
|
||||
>>> session = fbchat.Session.login("<email or phone>", getpass.getpass())
|
||||
>>> session.user_id
|
||||
"1234"
|
||||
"""
|
||||
session = session_factory()
|
||||
|
||||
@@ -215,6 +222,9 @@ class Session:
|
||||
|
||||
Returns:
|
||||
Whether the user is still logged in
|
||||
|
||||
Example:
|
||||
>>> assert session.is_logged_in()
|
||||
"""
|
||||
# Send a request to the login url, to see if we're directed to the home page
|
||||
url = "https://m.facebook.com/login.php?login_attempt=1"
|
||||
@@ -228,6 +238,9 @@ class Session:
|
||||
"""Safely log out the user.
|
||||
|
||||
The session object must not be used after this action has been performed!
|
||||
|
||||
Example:
|
||||
>>> session.logout()
|
||||
"""
|
||||
logout_h = self._logout_h
|
||||
if not logout_h:
|
||||
@@ -285,6 +298,9 @@ class Session:
|
||||
|
||||
Returns:
|
||||
A dictionary containing session cookies
|
||||
|
||||
Example:
|
||||
>>> cookies = session.get_cookies()
|
||||
"""
|
||||
return self._session.cookies.get_dict()
|
||||
|
||||
@@ -294,6 +310,11 @@ class Session:
|
||||
|
||||
Args:
|
||||
cookies: A dictionary containing session cookies
|
||||
|
||||
Example:
|
||||
>>> cookies = session.get_cookies()
|
||||
>>> # Store cookies somewhere, and then subsequently
|
||||
>>> session = fbchat.Session.from_cookies(cookies)
|
||||
"""
|
||||
session = session_factory()
|
||||
session.cookies = requests.cookies.merge_cookies(session.cookies, cookies)
|
||||
@@ -346,7 +367,14 @@ class Session:
|
||||
`files` should be a list of files that requests can upload, see
|
||||
`requests.request <https://docs.python-requests.org/en/master/api/#requests.request>`_.
|
||||
|
||||
Return a list of tuples with a file's ID and mimetype.
|
||||
Example:
|
||||
>>> with open("file.txt", "rb") as f:
|
||||
... (file,) = session._upload([("file.txt", f, "text/plain")])
|
||||
...
|
||||
>>> file
|
||||
("1234", "text/plain")
|
||||
Return:
|
||||
Tuples with a file's ID and mimetype.
|
||||
"""
|
||||
file_dict = {"upload_{}".format(i): f for i, f in enumerate(files)}
|
||||
|
||||
|
@@ -84,6 +84,11 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
|
||||
Args:
|
||||
first: Whether to wave first or wave back
|
||||
|
||||
Example:
|
||||
Wave back to the thread.
|
||||
|
||||
>>> thread.wave(False)
|
||||
"""
|
||||
data = self._to_send_data()
|
||||
data["action_type"] = "ma-type:user-generated-message"
|
||||
@@ -109,6 +114,10 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
files: Optional tuples, each containing an uploaded file's ID and mimetype
|
||||
reply_to_id: Optional message to reply to
|
||||
|
||||
Example:
|
||||
>>> mention = fbchat.Mention(thread_id="1234", offset=5, length=2)
|
||||
>>> thread.send_text("A message", mentions=[mention])
|
||||
|
||||
Returns:
|
||||
The sent message
|
||||
"""
|
||||
@@ -138,6 +147,9 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
emoji: The emoji to send
|
||||
size: The size of the emoji
|
||||
|
||||
Example:
|
||||
>>> thread.send_emoji("😀", size=fbchat.EmojiSize.LARGE)
|
||||
|
||||
Returns:
|
||||
The sent message
|
||||
"""
|
||||
@@ -153,6 +165,11 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
Args:
|
||||
sticker_id: ID of the sticker to send
|
||||
|
||||
Example:
|
||||
Send a sticker with the id "1889713947839631"
|
||||
|
||||
>>> thread.send_sticker("1889713947839631")
|
||||
|
||||
Returns:
|
||||
The sent message
|
||||
"""
|
||||
@@ -175,6 +192,11 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
Args:
|
||||
latitude: The location latitude
|
||||
longitude: The location longitude
|
||||
|
||||
Example:
|
||||
Send a location in London, United Kingdom.
|
||||
|
||||
>>> thread.send_location(51.5287718, -0.2416815)
|
||||
"""
|
||||
self._send_location(True, latitude=latitude, longitude=longitude)
|
||||
|
||||
@@ -184,6 +206,11 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
Args:
|
||||
latitude: The location latitude
|
||||
longitude: The location longitude
|
||||
|
||||
Example:
|
||||
Send a pinned location in Beijing, China.
|
||||
|
||||
>>> thread.send_location(39.9390731, 116.117273)
|
||||
"""
|
||||
self._send_location(False, latitude=latitude, longitude=longitude)
|
||||
|
||||
@@ -191,6 +218,14 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
"""Send files from file IDs to a thread.
|
||||
|
||||
`files` should be a list of tuples, with a file's ID and mimetype.
|
||||
|
||||
Example:
|
||||
Upload and send a video to a thread.
|
||||
|
||||
>>> with open("video.mp4", "rb") as f:
|
||||
... files = session._upload([("video.mp4", f, "video/mp4")])
|
||||
>>>
|
||||
>>> thread.send_files(files=files)
|
||||
"""
|
||||
return self.send_text(text=None, files=files)
|
||||
|
||||
@@ -285,11 +320,20 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
Warning! If someone send a message to the thread that matches the query, while
|
||||
we're searching, some snippets will get returned twice.
|
||||
|
||||
Not sure if we should handle it, Facebook's implementation doesn't...
|
||||
This is fundamentally unfixable, it's just how the endpoint is implemented.
|
||||
|
||||
The returned message snippets are ordered by last sent.
|
||||
|
||||
Args:
|
||||
query: Text to search for
|
||||
limit: Max. number of message snippets to retrieve
|
||||
|
||||
Example:
|
||||
Fetch the latest message in the thread that matches the query.
|
||||
|
||||
>>> (message,) = thread.search_messages("abc", limit=1)
|
||||
>>> message.text
|
||||
"Some text and abc"
|
||||
"""
|
||||
offset = 0
|
||||
# The max limit is measured empirically to 420, safe default chosen below
|
||||
@@ -330,11 +374,22 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
]
|
||||
|
||||
def fetch_messages(self, limit: Optional[int]) -> Iterable["_message.Message"]:
|
||||
"""Fetch messages in a thread, with most recent messages first.
|
||||
"""Fetch messages in a thread.
|
||||
|
||||
The returned messages are ordered by most recent first.
|
||||
|
||||
Args:
|
||||
limit: Max. number of threads to retrieve. If ``None``, all threads will be
|
||||
retrieved.
|
||||
|
||||
Example:
|
||||
>>> for message in thread.fetch_messages(limit=5)
|
||||
... print(message.text)
|
||||
...
|
||||
A message
|
||||
Another message
|
||||
None
|
||||
A fourth message
|
||||
"""
|
||||
# This is measured empirically as 210 in extreme cases, fairly safe default
|
||||
# chosen below
|
||||
@@ -389,6 +444,13 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
Args:
|
||||
limit: Max. number of images to retrieve. If ``None``, all images will be
|
||||
retrieved.
|
||||
|
||||
Example:
|
||||
>>> for image in thread.fetch_messages(limit=3)
|
||||
... print(image.id)
|
||||
...
|
||||
1234
|
||||
2345
|
||||
"""
|
||||
cursor = None
|
||||
# The max limit on this request is unknown, so we set it reasonably high
|
||||
@@ -407,6 +469,9 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
Args:
|
||||
user_id: User that will have their nickname changed
|
||||
nickname: New nickname
|
||||
|
||||
Example:
|
||||
>>> thread.set_nickname("1234", "A nickname")
|
||||
"""
|
||||
data = {
|
||||
"nickname": nickname,
|
||||
@@ -422,16 +487,22 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
|
||||
The new color must be one of the following::
|
||||
|
||||
"#0084ff", "#44bec7", "#ffc300", "#fa3c4c", "#d696bb", "#6699cc", "#13cf13",
|
||||
"#ff7e29", "#e68585", "#7646ff", "#20cef5", "#67b868", "#d4a88c", "#ff5ca1",
|
||||
"#a695c7", "#ff7ca8", "#1adb5b", "#f01d6a", "#ff9c19" or "#0edcde".
|
||||
|
||||
The default is "#0084ff".
|
||||
"#0084ff", "#44bec7", "#ffc300", "#fa3c4c", "#d696bb", "#6699cc",
|
||||
"#13cf13", "#ff7e29", "#e68585", "#7646ff", "#20cef5", "#67b868",
|
||||
"#d4a88c", "#ff5ca1", "#a695c7", "#ff7ca8", "#1adb5b", "#f01d6a",
|
||||
"#ff9c19" or "#0edcde".
|
||||
|
||||
This list is subject to change in the future!
|
||||
|
||||
The default when creating a new thread is ``"#0084ff"``.
|
||||
|
||||
Args:
|
||||
color: New thread color
|
||||
|
||||
Example:
|
||||
Set the thread color to "Coral Pink".
|
||||
|
||||
>>> thread.set_color("#e68585")
|
||||
"""
|
||||
if color not in SETABLE_COLORS:
|
||||
raise ValueError(
|
||||
@@ -459,11 +530,16 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
# _graphql.from_doc_id("1768656253222505", {"data": data})
|
||||
# )
|
||||
|
||||
def set_emoji(self, emoji: str):
|
||||
def set_emoji(self, emoji: Optional[str]):
|
||||
"""Change thread emoji.
|
||||
|
||||
Args:
|
||||
emoji: New thread emoji
|
||||
emoji: New thread emoji. If ``None``, will be set to the default "LIKE" icon
|
||||
|
||||
Example:
|
||||
Set the thread emoji to "😊".
|
||||
|
||||
>>> thread.set_emoji("😊")
|
||||
"""
|
||||
data = {"emoji_choice": emoji, "thread_or_other_fbid": self.id}
|
||||
# While changing the emoji, the Facebook web client actually sends multiple
|
||||
@@ -477,6 +553,9 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
|
||||
Args:
|
||||
attachment_id: Attachment ID to forward
|
||||
|
||||
Example:
|
||||
>>> thread.forward_attachment("1234")
|
||||
"""
|
||||
data = {
|
||||
"attachment_id": attachment_id,
|
||||
@@ -497,11 +576,19 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
j = self.session._payload_post("/ajax/messaging/typ.php", data)
|
||||
|
||||
def start_typing(self):
|
||||
"""Set the current user to start typing in the thread."""
|
||||
"""Set the current user to start typing in the thread.
|
||||
|
||||
Example:
|
||||
>>> thread.start_typing()
|
||||
"""
|
||||
self._set_typing(True)
|
||||
|
||||
def stop_typing(self):
|
||||
"""Set the current user to stop typing in the thread."""
|
||||
"""Set the current user to stop typing in the thread.
|
||||
|
||||
Example:
|
||||
>>> thread.stop_typing()
|
||||
"""
|
||||
self._set_typing(False)
|
||||
|
||||
def create_plan(
|
||||
@@ -518,6 +605,9 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
Args:
|
||||
name: Name of the new plan
|
||||
at: When the plan is for
|
||||
|
||||
Example:
|
||||
>>> thread.create_plan(...)
|
||||
"""
|
||||
return _plan.Plan._create(self, name, at, location_name, location_id)
|
||||
|
||||
@@ -529,7 +619,7 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
options: Options and whether you want to select the option
|
||||
|
||||
Example:
|
||||
thread.create_poll("Test poll", {"Option 1": True, "Option 2": False})
|
||||
>>> thread.create_poll("Test poll", {"Option 1": True, "Option 2": False})
|
||||
"""
|
||||
# We're using ordered dictionaries, because the Facebook endpoint that parses
|
||||
# the POST parameters is badly implemented, and deals with ordering the options
|
||||
@@ -557,6 +647,10 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
|
||||
Args:
|
||||
duration: Time to mute, use ``None`` to mute forever
|
||||
|
||||
Example:
|
||||
>>> import datetime
|
||||
>>> thread.mute(datetime.timedelta(days=2))
|
||||
"""
|
||||
if duration is None:
|
||||
setting = "-1"
|
||||
@@ -568,7 +662,11 @@ class ThreadABC(metaclass=abc.ABCMeta):
|
||||
)
|
||||
|
||||
def unmute(self):
|
||||
"""Unmute the thread."""
|
||||
"""Unmute the thread.
|
||||
|
||||
Example:
|
||||
>>> thread.unmute()
|
||||
"""
|
||||
return self.mute(datetime.timedelta(0))
|
||||
|
||||
def _mute_reactions(self, mode: bool):
|
||||
|
@@ -36,7 +36,11 @@ GENDERS = {
|
||||
|
||||
@attrs_default
|
||||
class User(_thread.ThreadABC):
|
||||
"""Represents a Facebook user. Implements `ThreadABC`."""
|
||||
"""Represents a Facebook user. Implements `ThreadABC`.
|
||||
|
||||
Example:
|
||||
>>> user = fbchat.User(session=session, id="1234")
|
||||
"""
|
||||
|
||||
#: The session to use when making requests.
|
||||
session = attr.ib(type=_session.Session)
|
||||
@@ -51,22 +55,38 @@ class User(_thread.ThreadABC):
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Example:
|
||||
>>> user.confirm_friend_request()
|
||||
"""
|
||||
data = {"to_friend": self.id, "action": "confirm"}
|
||||
j = self.session._payload_post("/ajax/add_friend/action.php?dpr=1", data)
|
||||
|
||||
def remove_friend(self):
|
||||
"""Remove the user from the client's friend list."""
|
||||
"""Remove the user from the client's friend list.
|
||||
|
||||
Example:
|
||||
>>> user.remove_friend()
|
||||
"""
|
||||
data = {"uid": self.id}
|
||||
j = self.session._payload_post("/ajax/profile/removefriendconfirm.php", data)
|
||||
|
||||
def block(self):
|
||||
"""Block messages from the user."""
|
||||
"""Block messages from the user.
|
||||
|
||||
Example:
|
||||
>>> user.block()
|
||||
"""
|
||||
data = {"fbid": self.id}
|
||||
j = self.session._payload_post("/messaging/block_messages/?dpr=1", data)
|
||||
|
||||
def unblock(self):
|
||||
"""Unblock a previously blocked user."""
|
||||
"""Unblock a previously blocked user.
|
||||
|
||||
Example:
|
||||
>>> user.unblock()
|
||||
"""
|
||||
data = {"fbid": self.id}
|
||||
j = self.session._payload_post("/messaging/unblock_messages/?dpr=1", data)
|
||||
|
||||
|
Reference in New Issue
Block a user