From 356db553b7cc002b1ff506d1453588f81dd92927 Mon Sep 17 00:00:00 2001 From: Nikolaos Karaolidis Date: Tue, 28 Mar 2023 11:49:48 +0300 Subject: [PATCH] Sync to maraid/fbchat Signed-off-by: Nikolaos Karaolidis --- fbchat/_models/_message.py | 8 ------ fbchat/_session.py | 32 ++++++++++++++++++++---- fbchat/_threads/_abc.py | 51 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/fbchat/_models/_message.py b/fbchat/_models/_message.py index b90037c..0683209 100644 --- a/fbchat/_models/_message.py +++ b/fbchat/_models/_message.py @@ -125,20 +125,12 @@ class Message: def react(self, reaction: Optional[str]): """React to the message, or removes reaction. - Currently, you can use "❤", "😍", "😆", "😮", "😢", "😠", "👍" or "👎". It - should be possible to add support for more, but we haven't figured that out yet. - Args: reaction: Reaction emoji to use, or if ``None``, removes reaction. Example: >>> message.react("😍") """ - if reaction and reaction not in SENDABLE_REACTIONS: - raise ValueError( - "Invalid reaction! Please use one of: {}".format(SENDABLE_REACTIONS) - ) - data = { "action": "ADD_REACTION" if reaction else "REMOVE_REACTION", "client_mutation_id": "1", diff --git a/fbchat/_session.py b/fbchat/_session.py index 4e4a7df..cb688db 100644 --- a/fbchat/_session.py +++ b/fbchat/_session.py @@ -16,8 +16,10 @@ from typing import Optional, Mapping, Callable, Any SERVER_JS_DEFINE_REGEX = re.compile( - r'(?:"ServerJS".{,100}\.handle\({.*"define":)|(?:require\("ServerJSDefine"\)\)?\.handleDefines\()' -) + r'(?:"ServerJS".{,100}\.handle\({.*"define":)' + r'|(?:ServerJS.{,100}\.handleWithCustomApplyEach\(ScheduledApplyEach,{.*"define":)' + r'|(?:require\("ServerJSDefine"\)\)?\.handleDefines\()' + r'|(?:"require":\[\["ScheduledServerJS".{,100}"define":)') SERVER_JS_DEFINE_JSON_DECODER = json.JSONDecoder() @@ -36,8 +38,6 @@ def parse_server_js_define(html: str) -> Mapping[str, Any]: raise _exception.ParseError("Could not find any ServerJSDefine", data=html) if len(define_splits) < 2: raise _exception.ParseError("Could not find enough ServerJSDefine", data=html) - if len(define_splits) > 2: - raise _exception.ParseError("Found too many ServerJSDefine", data=define_splits) # Parse entries (should be two) for entry in define_splits: try: @@ -311,6 +311,21 @@ class Session: data=data, allow_redirects=False, cookies=login_cookies(_util.now()), + headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.106 Safari/537.36", + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "accept-language": "en-HU,en;q=0.9,hu-HU;q=0.8,hu;q=0.7,en-US;q=0.6", + "cache-control": "max-age=0", + "origin": "https://www.messenger.com", + "referer": "https://www.messenger.com/login/", + "sec-ch-ua": '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"', + "sec-ch-ua-mobile": "?0", + "sec-fetch-dest": "document", + "sec-fetch-mode": "navigate", + "sec-fetch-site": "same-origin", + "sec-fetch-user": "?1", + "upgrade-insecure-requests": "1" + } ) except requests.RequestException as e: _exception.handle_requests_error(e) @@ -411,7 +426,7 @@ class Session: # Make a request to the main page to retrieve ServerJSDefine entries try: - r = session.get(prefix_url("/"), allow_redirects=False) + r = session.get(prefix_url("/"), allow_redirects=True) except requests.RequestException as e: _exception.handle_requests_error(e) _exception.handle_http_error(r.status_code) @@ -532,3 +547,10 @@ class Session: return message_ids[0] except (KeyError, IndexError, TypeError) as e: raise _exception.ParseError("No message IDs could be found", data=j) from e + + def _uri_share_data(self, data): + data["image_height"] = 960 + data["image_width"] = 960 + data["__user"] = self.user.id + j = self._post("/message_share_attachment/fromURI/", data) + return j["payload"]["share_data"] diff --git a/fbchat/_threads/_abc.py b/fbchat/_threads/_abc.py index 8fb4266..a52765a 100644 --- a/fbchat/_threads/_abc.py +++ b/fbchat/_threads/_abc.py @@ -105,6 +105,7 @@ class ThreadABC(metaclass=abc.ABCMeta): mentions: Iterable["_models.Mention"] = None, files: Iterable[Tuple[str, str]] = None, reply_to_id: str = None, + uri: str = None ) -> str: """Send a message to the thread. @@ -114,6 +115,7 @@ class ThreadABC(metaclass=abc.ABCMeta): files: Optional tuples, each containing an uploaded file's ID and mimetype. See `ThreadABC.send_files` for an example. reply_to_id: Optional message to reply to + uri: Uri to formulate a sharable attachment with Example: Send a message with a mention to a thread. @@ -138,6 +140,9 @@ class ThreadABC(metaclass=abc.ABCMeta): if files: data["has_attachment"] = True + + if uri: + data.update(self._generate_shareable_attachment(uri)) for i, (file_id, mimetype) in enumerate(files or ()): data["{}s[{}]".format(_util.mimetype_to_key(mimetype), i)] = file_id @@ -235,7 +240,53 @@ class ThreadABC(metaclass=abc.ABCMeta): >>> thread.send_files(files) """ return self.send_text(text=None, files=files) + + def send_uri(self, uri: str, **kwargs): + """Send a uri preview to a thread. + Args: + uri: uri to preview + """ + if kwargs.get('text') is None: + kwargs['text'] = None + self.send_text(uri=uri, **kwargs) + def _generate_shareable_attachment(self, uri): + """Send a uri preview to a thread. + Args: + uri: uri to preview + Returns: + :ref:`Message ID ` of the sent message + Raises: + FBchatException: If request failed + """ + url_data = self.session._uri_share_data({"uri": uri}) + data = self._to_send_data() + data["action_type"] = "ma-type:user-generated-message" + data["shareable_attachment[share_type]"] = url_data["share_type"] + + # Most uri params will come back as dict + if isinstance(url_data["share_params"], dict): + data["has_attachment"] = True + for key in url_data["share_params"]: + if isinstance(url_data["share_params"][key], dict): + for key2 in url_data["share_params"][key]: + data[ + "shareable_attachment[share_params][{}][{}]".format( + key, key2 + ) + ] = url_data["share_params"][key][key2] + else: + data[ + "shareable_attachment[share_params][{}]".format(key) + ] = url_data["share_params"][key] + + # Some (such as facebook profile pages) will just be a list + else: + data["has_attachment"] = False + for index, val in enumerate(url_data["share_params"]): + data["shareable_attachment[share_params][{}]".format(index)] = val + return data + # xmd = {"quick_replies": []} # for quick_reply in quick_replies: # # TODO: Move this to `_quick_reply.py`