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
This commit is contained in:
Kacper Ziubryniewicz
2019-04-25 22:03:03 +02:00
committed by Mads Marquart
parent 61502ed32a
commit 70faa86e34
3 changed files with 60 additions and 23 deletions

View File

@@ -1707,6 +1707,23 @@ class Client(object):
thread_type=thread_type, 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): def createGroup(self, message, user_ids):
""" """
Creates a group with the given ids Creates a group with the given ids

View File

@@ -90,6 +90,8 @@ class Message(object):
reply_to_id = attr.ib(None) reply_to_id = attr.ib(None)
#: Replied message #: Replied message
replied_to = attr.ib(None, init=False) replied_to = attr.ib(None, init=False)
#: Whether the message was forwarded
forwarded = attr.ib(False, init=False)
@classmethod @classmethod
def formatMentions(cls, text, *args, **kwargs): def formatMentions(cls, text, *args, **kwargs):
@@ -144,12 +146,19 @@ class Message(object):
message = cls(text=result, mentions=mentions) message = cls(text=result, mentions=mentions)
return message 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 @classmethod
def _from_graphql(cls, data): def _from_graphql(cls, data):
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:
data["message"] = {} data["message"] = {}
tags = data.get("tags_list")
rtn = cls( rtn = cls(
text=data["message"].get("text"), text=data["message"].get("text"),
mentions=[ mentions=[
@@ -160,7 +169,8 @@ class Message(object):
) )
for m in data["message"].get("ranges") or () 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")), sticker=_sticker.Sticker._from_graphql(data.get("sticker")),
) )
rtn.uid = str(data["message_id"]) rtn.uid = str(data["message_id"])
@@ -203,13 +213,15 @@ class Message(object):
@classmethod @classmethod
def _from_reply(cls, data): def _from_reply(cls, data):
tags = data["messageMetadata"].get("tags")
rtn = cls( rtn = cls(
text=data.get("body"), text=data.get("body"),
mentions=[ mentions=[
Mention(m.get("i"), offset=m.get("o"), length=m.get("l")) Mention(m.get("i"), offset=m.get("o"), length=m.get("l"))
for m in json.loads(data.get("data", {}).get("prng", "[]")) 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", {}) metadata = data.get("messageMetadata", {})
rtn.uid = metadata.get("messageId") rtn.uid = metadata.get("messageId")
@@ -311,6 +323,7 @@ class Message(object):
) )
rtn.emoji_size = EmojiSize._from_tags(tags) rtn.emoji_size = EmojiSize._from_tags(tags)
rtn.forwarded = cls._get_forwarded_from_tags(tags)
return rtn return rtn

View File

@@ -114,6 +114,7 @@ class ReqUrl(object):
SEARCH_MESSAGES = "https://www.facebook.com/ajax/mercury/search_snippets.php?dpr=1" 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" MARK_SPAM = "https://www.facebook.com/ajax/mercury/mark_spam.php?dpr=1"
UNSEND = "https://www.facebook.com/messaging/unsend_message/?dpr=1" UNSEND = "https://www.facebook.com/messaging/unsend_message/?dpr=1"
FORWARD_ATTACHMENT = "https://www.facebook.com/mercury/attachments/forward/"
pull_channel = 0 pull_channel = 0
@@ -192,8 +193,13 @@ def generateOfflineThreadingID():
def check_json(j): def check_json(j):
if j.get("error") is None: if j.get("payload") and j["payload"].get("error"):
return raise FBchatFacebookError(
"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: if "errorDescription" in j:
# 'errorDescription' is in the users own language! # 'errorDescription' is in the users own language!
raise FBchatFacebookError( raise FBchatFacebookError(
@@ -213,7 +219,8 @@ def check_json(j):
) )
else: else:
raise FBchatFacebookError( raise FBchatFacebookError(
"Error {} when sending request".format(j["error"]), fb_error_code=j["error"] "Error {} when sending request".format(j["error"]),
fb_error_code=j["error"],
) )