From bd2b39c27a8dea9388f6dbc541caf17f2f43e837 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 9 Jan 2020 01:13:17 +0100 Subject: [PATCH] Add thread actions to ThreadABC --- fbchat/_client.py | 193 ---------------------------------------------- fbchat/_thread.py | 141 ++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 194 deletions(-) diff --git a/fbchat/_client.py b/fbchat/_client.py index e446027..c517e3f 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -728,112 +728,6 @@ class Client: data = {"message_id": mid} j = self._payload_post("/messaging/unsend_message/?dpr=1", data) - def forward_attachment(self, attachment_id, thread_id=None): - """Forward an attachment. - - Args: - attachment_id: Attachment ID to forward - thread_id: User/Group ID to send to. See :ref:`intro_threads` - - Raises: - FBchatException: If request failed - """ - data = { - "attachment_id": attachment_id, - "recipient_map[{}]".format( - _util.generate_offline_threading_id() - ): thread_id, - } - j = self._payload_post("/mercury/attachments/forward/", data) - if not j.get("success"): - raise FBchatFacebookError( - "Failed forwarding attachment: {}".format(j["error"]), - fb_error_message=j["error"], - ) - - def change_thread_title(self, title, thread_id=None, thread_type=ThreadType.USER): - """Change title of a thread. - - If this is executed on a user thread, this will change the nickname of that - user, effectively changing the title. - - Args: - title: New group thread title - thread_id: Group ID to change title of. See :ref:`intro_threads` - thread_type (ThreadType): See :ref:`intro_threads` - - Raises: - FBchatException: If request failed - """ - if thread_type == ThreadType.USER: - # The thread is a user, so we change the user's nickname - return self.change_nickname( - title, thread_id, thread_id=thread_id, thread_type=thread_type - ) - - data = {"thread_name": title, "thread_id": thread_id} - j = self._payload_post("/messaging/set_thread_name/?dpr=1", data) - - def change_nickname( - self, nickname, user_id, thread_id=None, thread_type=ThreadType.USER - ): - """Change the nickname of a user in a thread. - - Args: - nickname: New nickname - user_id: User that will have their nickname changed - thread_id: User/Group ID to change color of. See :ref:`intro_threads` - thread_type (ThreadType): See :ref:`intro_threads` - - Raises: - FBchatException: If request failed - """ - data = { - "nickname": nickname, - "participant_id": user_id, - "thread_or_other_fbid": thread_id, - } - j = self._payload_post( - "/messaging/save_thread_nickname/?source=thread_settings&dpr=1", data - ) - - def change_thread_color(self, color, thread_id=None): - """Change thread color. - - Args: - color (ThreadColor): New thread color - thread_id: User/Group ID to change color of. See :ref:`intro_threads` - - Raises: - FBchatException: If request failed - """ - data = { - "color_choice": color.value if color != ThreadColor.MESSENGER_BLUE else "", - "thread_or_other_fbid": thread_id, - } - j = self._payload_post( - "/messaging/save_thread_color/?source=thread_settings&dpr=1", data - ) - - def change_thread_emoji(self, emoji, thread_id=None): - """Change thread color. - - Note: - While changing the emoji, the Facebook web client actually sends multiple - different requests, though only this one is required to make the change. - - Args: - color: New thread emoji - thread_id: User/Group ID to change emoji of. See :ref:`intro_threads` - - Raises: - FBchatException: If request failed - """ - data = {"emoji_choice": emoji, "thread_or_other_fbid": thread_id} - j = self._payload_post( - "/messaging/save_thread_emoji/?source=thread_settings&dpr=1", data - ) - def react_to_message(self, message_id, reaction): """React to a message, or removes reaction. @@ -855,32 +749,6 @@ class Client: j = self._payload_post("/webgraphql/mutation", data) _util.handle_graphql_errors(j) - def create_plan(self, plan, thread_id=None): - """Set a plan. - - Args: - plan (Plan): Plan to set - thread_id: User/Group ID to send plan to. See :ref:`intro_threads` - - Raises: - FBchatException: If request failed - """ - data = { - "event_type": "EVENT", - "event_time": _util.datetime_to_seconds(plan.time), - "title": plan.title, - "thread_id": thread_id, - "location_id": plan.location_id or "", - "location_name": plan.location or "", - "acontext": ACONTEXT, - } - j = self._payload_post("/ajax/eventreminder/create", data) - if "error" in j: - raise FBchatFacebookError( - "Failed creating plan: {}".format(j["error"]), - fb_error_message=j["error"], - ) - def edit_plan(self, plan, new_plan): """Edit a plan. @@ -931,33 +799,6 @@ class Client: } j = self._payload_post("/ajax/eventreminder/rsvp", data) - def create_poll(self, poll, thread_id=None): - """Create poll in a group thread. - - Args: - poll (Poll): Poll to create - thread_id: User/Group ID to create poll in. See :ref:`intro_threads` - - Raises: - FBchatException: If request failed - """ - # We're using ordered dictionaries, because the Facebook endpoint that parses - # the POST parameters is badly implemented, and deals with ordering the options - # wrongly. If you can find a way to fix this for the endpoint, or if you find - # another endpoint, please do suggest it ;) - data = OrderedDict([("question_text", poll.title), ("target_id", thread_id)]) - - for i, option in enumerate(poll.options): - data["option_text_array[{}]".format(i)] = option.text - data["option_is_selected_array[{}]".format(i)] = str(int(option.vote)) - - j = self._payload_post("/messaging/group_polling/create_poll/?dpr=1", data) - if j.get("status") != "success": - raise FBchatFacebookError( - "Failed creating poll: {}".format(j.get("errorTitle")), - fb_error_message=j.get("errorMessage"), - ) - def update_poll_vote(self, poll_id, option_ids=[], new_options=[]): """Update a poll vote. @@ -986,25 +827,6 @@ class Client: fb_error_message=j.get("errorMessage"), ) - def set_typing_status(self, status, thread_id=None, thread_type=None): - """Set users typing status in a thread. - - Args: - status (TypingStatus): Specify the typing status - thread_id: User/Group ID to change status in. See :ref:`intro_threads` - thread_type (ThreadType): See :ref:`intro_threads` - - Raises: - FBchatException: If request failed - """ - data = { - "typ": status.value, - "thread": thread_id, - "to": thread_id if thread_type == ThreadType.USER else "", - "source": "mercury-chat", - } - j = self._payload_post("/ajax/messaging/typ.php", data) - """ END SEND METHODS """ @@ -1146,21 +968,6 @@ class Client: ) return True - def mark_as_spam(self, thread_id=None): - """Mark a thread as spam, and delete it. - - Args: - thread_id: User/Group ID to mark as spam. See :ref:`intro_threads` - - Returns: - True - - Raises: - FBchatException: If request failed - """ - j = self._payload_post("/ajax/mercury/mark_spam.php?dpr=1", {"id": thread_id}) - return True - def delete_messages(self, message_ids): """Delete specified messages. diff --git a/fbchat/_thread.py b/fbchat/_thread.py index 86cef6d..d303160 100644 --- a/fbchat/_thread.py +++ b/fbchat/_thread.py @@ -1,7 +1,8 @@ import abc import attr +import datetime from ._core import attrs_default, Enum, Image -from . import _util, _session +from . import _util, _exception, _session from typing import MutableMapping, Any, Iterable, Tuple @@ -306,6 +307,144 @@ class ThreadABC(metaclass=abc.ABCMeta): yield Attachment(id=i["node"].get("legacy_attachment_id")) del j[self.id]["message_shared_media"]["edges"][0] + def set_nickname(self, user_id: str, nickname: str): + """Change the nickname of a user in the thread. + + Args: + user_id: User that will have their nickname changed + nickname: New nickname + """ + data = { + "nickname": nickname, + "participant_id": user_id, + "thread_or_other_fbid": self.id, + } + j = self.session._payload_post( + "/messaging/save_thread_nickname/?source=thread_settings&dpr=1", data + ) + + def set_color(self, color: ThreadColor): + """Change thread color. + + Args: + color: New thread color + """ + data = { + "color_choice": color.value if color != ThreadColor.MESSENGER_BLUE else "", + "thread_or_other_fbid": self.id, + } + j = self.session._payload_post( + "/messaging/save_thread_color/?source=thread_settings&dpr=1", data + ) + + def set_emoji(self, emoji: str): + """Change thread color. + + Args: + emoji: New thread emoji + """ + data = {"emoji_choice": emoji, "thread_or_other_fbid": self.id} + # While changing the emoji, the Facebook web client actually sends multiple + # different requests, though only this one is required to make the change. + j = self.session._payload_post( + "/messaging/save_thread_emoji/?source=thread_settings&dpr=1", data + ) + + def forward_attachment(self, attachment_id): + """Forward an attachment. + + Args: + attachment_id: Attachment ID to forward + """ + data = { + "attachment_id": attachment_id, + "recipient_map[{}]".format(_util.generate_offline_threading_id()): self.id, + } + j = self.session._payload_post("/mercury/attachments/forward/", data) + if not j.get("success"): + raise _exception.FBchatFacebookError( + "Failed forwarding attachment: {}".format(j["error"]), + fb_error_message=j["error"], + ) + + def _set_typing(self, typing): + data = { + "typ": "1" if typing else "0", + "thread": self.id, + # TODO: This + "to": self.id if thread_type == ThreadType.USER else "", + "source": "mercury-chat", + } + j = self.session._payload_post("/ajax/messaging/typ.php", data) + + def start_typing(self): + """Set the current user to start typing in the thread.""" + self._set_typing(True) + + def stop_typing(self): + """Set the current user to stop typing in the thread.""" + self._set_typing(False) + + def create_plan( + self, + name: str, + at: datetime.datetime, + location_name: str = None, + location_id: str = None, + ): + """Create a new plan. + + # TODO: Arguments + + Args: + title: Name of the new plan + at: When the plan is for + """ + data = { + "event_type": "EVENT", + "event_time": _util.datetime_to_seconds(at), + "title": name, + "thread_id": self.id, + "location_id": location_id or "", + "location_name": location or "", + "acontext": 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]]): + """Create poll in a thread. + + # TODO: Arguments + """ + # We're using ordered dictionaries, because the Facebook endpoint that parses + # the POST parameters is badly implemented, and deals with ordering the options + # wrongly. If you can find a way to fix this for the endpoint, or if you find + # another endpoint, please do suggest it ;) + data = OrderedDict([("question_text", question), ("target_id", self.id)]) + + for i, (text, vote) in enumerate(options): + data["option_text_array[{}]".format(i)] = text + data["option_is_selected_array[{}]".format(i)] = str(int(vote)) + + j = self.session._payload_post( + "/messaging/group_polling/create_poll/?dpr=1", data + ) + if j.get("status") != "success": + raise _exception.FBchatFacebookError( + "Failed creating poll: {}".format(j.get("errorTitle")), + fb_error_message=j.get("errorMessage"), + ) + + def mark_as_spam(self): + """Mark the thread as spam, and delete it.""" + data = {"id": self.id} + j = self.session._payload_post("/ajax/mercury/mark_spam.php?dpr=1", data) + def _forced_fetch(self, message_id: str) -> dict: params = { "thread_and_message_id": {"thread_id": self.id, "message_id": message_id}