From 70faa86e3495d205c627578e2f4048cbf45ce335 Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Thu, 25 Apr 2019 22:03:03 +0200 Subject: [PATCH] Add forwarding attachments (#420) - Add ability to forward attachments and `Message.forwarded` attribute - Improve error handling for a lot of client methods, including, but not limited to: - `fetchAllUsers` - `searchForMessageIDs` - `search` - `fetchThreadInfo` and siblings - `fetchUnread` - `fetchUnseen` - `fetchPollOptions` - `fetchPlanInfo` - `send` and siblings - File uploads --- fbchat/_client.py | 17 ++++++++++++++++ fbchat/_message.py | 17 ++++++++++++++-- fbchat/_util.py | 49 ++++++++++++++++++++++++++-------------------- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/fbchat/_client.py b/fbchat/_client.py index bb8347b0..a739956d 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -1707,6 +1707,23 @@ def sendLocalImage( thread_type=thread_type, ) + def forwardAttachment(self, attachment_id, thread_id=None): + """ + Forwards an attachment + + :param attachment_id: Attachment ID to forward + :param thread_id: User/Group ID to send to. See :ref:`intro_threads` + :raises: FBchatException if request failed + """ + thread_id, thread_type = self._getThread(thread_id, None) + data = { + "attachment_id": attachment_id, + "recipient_map[{}]".format(generateOfflineThreadingID()): thread_id, + } + j = self._post( + self.req_url.FORWARD_ATTACHMENT, data, fix_request=True, as_json=True + ) + def createGroup(self, message, user_ids): """ Creates a group with the given ids diff --git a/fbchat/_message.py b/fbchat/_message.py index f99626b9..c9593d70 100644 --- a/fbchat/_message.py +++ b/fbchat/_message.py @@ -90,6 +90,8 @@ class Message(object): reply_to_id = attr.ib(None) #: Replied message replied_to = attr.ib(None, init=False) + #: Whether the message was forwarded + forwarded = attr.ib(False, init=False) @classmethod def formatMentions(cls, text, *args, **kwargs): @@ -144,12 +146,19 @@ def formatMentions(cls, text, *args, **kwargs): message = cls(text=result, mentions=mentions) return message + @staticmethod + def _get_forwarded_from_tags(tags): + if tags is None: + return False + return any(map(lambda tag: "forward" in tag or "copy" in tag, tags)) + @classmethod def _from_graphql(cls, data): if data.get("message_sender") is None: data["message_sender"] = {} if data.get("message") is None: data["message"] = {} + tags = data.get("tags_list") rtn = cls( text=data["message"].get("text"), mentions=[ @@ -160,7 +169,8 @@ def _from_graphql(cls, data): ) for m in data["message"].get("ranges") or () ], - emoji_size=EmojiSize._from_tags(data.get("tags_list")), + emoji_size=EmojiSize._from_tags(tags), + forwarded=cls._get_forwarded_from_tags(tags), sticker=_sticker.Sticker._from_graphql(data.get("sticker")), ) rtn.uid = str(data["message_id"]) @@ -203,13 +213,15 @@ def _from_graphql(cls, data): @classmethod def _from_reply(cls, data): + tags = data["messageMetadata"].get("tags") rtn = cls( text=data.get("body"), mentions=[ Mention(m.get("i"), offset=m.get("o"), length=m.get("l")) for m in json.loads(data.get("data", {}).get("prng", "[]")) ], - emoji_size=EmojiSize._from_tags(data["messageMetadata"].get("tags")), + emoji_size=EmojiSize._from_tags(tags), + forwarded=cls._get_forwarded_from_tags(tags), ) metadata = data.get("messageMetadata", {}) rtn.uid = metadata.get("messageId") @@ -311,6 +323,7 @@ def _from_pull(cls, data, mid=None, tags=None, author=None, timestamp=None): ) rtn.emoji_size = EmojiSize._from_tags(tags) + rtn.forwarded = cls._get_forwarded_from_tags(tags) return rtn diff --git a/fbchat/_util.py b/fbchat/_util.py index 6639b3e4..4e943d51 100644 --- a/fbchat/_util.py +++ b/fbchat/_util.py @@ -114,6 +114,7 @@ class ReqUrl(object): SEARCH_MESSAGES = "https://www.facebook.com/ajax/mercury/search_snippets.php?dpr=1" MARK_SPAM = "https://www.facebook.com/ajax/mercury/mark_spam.php?dpr=1" UNSEND = "https://www.facebook.com/messaging/unsend_message/?dpr=1" + FORWARD_ATTACHMENT = "https://www.facebook.com/mercury/attachments/forward/" pull_channel = 0 @@ -192,29 +193,35 @@ def generateOfflineThreadingID(): def check_json(j): - if j.get("error") is None: - return - if "errorDescription" in j: - # 'errorDescription' is in the users own language! + if j.get("payload") and j["payload"].get("error"): raise FBchatFacebookError( - "Error #{} when sending request: {}".format( - j["error"], j["errorDescription"] - ), - fb_error_code=j["error"], - fb_error_message=j["errorDescription"], - ) - elif "debug_info" in j["error"] and "code" in j["error"]: - raise FBchatFacebookError( - "Error #{} when sending request: {}".format( - j["error"]["code"], repr(j["error"]["debug_info"]) - ), - fb_error_code=j["error"]["code"], - fb_error_message=j["error"]["debug_info"], - ) - else: - raise FBchatFacebookError( - "Error {} when sending request".format(j["error"]), fb_error_code=j["error"] + "Error when sending request: {}".format(j["payload"]["error"]), + fb_error_code=None, + fb_error_message=j["payload"]["error"], ) + elif j.get("error"): + if "errorDescription" in j: + # 'errorDescription' is in the users own language! + raise FBchatFacebookError( + "Error #{} when sending request: {}".format( + j["error"], j["errorDescription"] + ), + fb_error_code=j["error"], + fb_error_message=j["errorDescription"], + ) + elif "debug_info" in j["error"] and "code" in j["error"]: + raise FBchatFacebookError( + "Error #{} when sending request: {}".format( + j["error"]["code"], repr(j["error"]["debug_info"]) + ), + fb_error_code=j["error"]["code"], + fb_error_message=j["error"]["debug_info"], + ) + else: + raise FBchatFacebookError( + "Error {} when sending request".format(j["error"]), + fb_error_code=j["error"], + ) def check_request(r, as_json=True):