From 45a71fd1a3122bf49ddea4cb719476fa927475fd Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 23 Jan 2020 12:07:40 +0100 Subject: [PATCH] Add inline examples --- fbchat/_client.py | 114 ++++++++++++++++++++++++++++++++++++----- fbchat/_group.py | 38 +++++++++++++- fbchat/_message.py | 34 ++++++++++--- fbchat/_mqtt.py | 17 +++++++ fbchat/_page.py | 6 ++- fbchat/_plan.py | 32 ++++++++++-- fbchat/_poll.py | 20 +++++--- fbchat/_session.py | 32 +++++++++++- fbchat/_thread.py | 124 ++++++++++++++++++++++++++++++++++++++++----- fbchat/_user.py | 30 +++++++++-- 10 files changed, 396 insertions(+), 51 deletions(-) diff --git a/fbchat/_client.py b/fbchat/_client.py index 157218e..1f01625 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -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): diff --git a/fbchat/_group.py b/fbchat/_group.py index 18ec6bf..5b7dab6 100644 --- a/fbchat/_group.py +++ b/fbchat/_group.py @@ -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) diff --git a/fbchat/_message.py b/fbchat/_message.py index 80fae2b..6db2f8e 100644 --- a/fbchat/_message.py +++ b/fbchat/_message.py @@ -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", [, ]) + ("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', [, ]) + ('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() diff --git a/fbchat/_mqtt.py b/fbchat/_mqtt.py index 39b7d0a..040f6b4 100644 --- a/fbchat/_mqtt.py +++ b/fbchat/_mqtt.py @@ -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() diff --git a/fbchat/_page.py b/fbchat/_page.py index ff89bfe..0532b93 100644 --- a/fbchat/_page.py +++ b/fbchat/_page.py @@ -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! diff --git a/fbchat/_plan.py b/fbchat/_plan.py index 129fbec..622c93b 100644 --- a/fbchat/_plan.py +++ b/fbchat/_plan.py @@ -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) diff --git a/fbchat/_poll.py b/fbchat/_poll.py index 32a3af5..519cd3a 100644 --- a/fbchat/_poll.py +++ b/fbchat/_poll.py @@ -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} diff --git a/fbchat/_session.py b/fbchat/_session.py index c0eb3d4..5d25e7e 100644 --- a/fbchat/_session.py +++ b/fbchat/_session.py @@ -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("", 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 `_. - 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)} diff --git a/fbchat/_thread.py b/fbchat/_thread.py index 0d8a0f0..d444d03 100644 --- a/fbchat/_thread.py +++ b/fbchat/_thread.py @@ -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): diff --git a/fbchat/_user.py b/fbchat/_user.py index 4ab8286..3d62ded 100644 --- a/fbchat/_user.py +++ b/fbchat/_user.py @@ -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)