-
-
- The password you entered is incorrect. Did you forget your password?
+
+
+ |
diff --git a/fbchat/_session.py b/fbchat/_session.py index 6bf8f7d..092ba2e 100644 --- a/fbchat/_session.py +++ b/fbchat/_session.py @@ -4,7 +4,10 @@ import requests import random import re import json -import urllib.parse + +# TODO: Only import when required +# Or maybe just replace usage with `html.parser`? +import bs4 from ._common import log, kw_only from . import _graphql, _util, _exception @@ -27,6 +30,10 @@ def parse_server_js_define(html: str) -> Mapping[str, Any]: rtn = [] if not define_splits: 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: @@ -94,16 +101,7 @@ def client_id_factory() -> str: return hex(int(random.random() * 2 ** 31))[2:] -def get_next_url(url: str) -> Optional[str]: - parsed_url = urllib.parse.urlparse(url) - query = urllib.parse.parse_qs(parsed_url.query) - return query.get("next", [None])[0] - - def find_form_request(html: str): - # Only import when required - import bs4 - soup = bs4.BeautifulSoup(html, "html.parser", parse_only=bs4.SoupStrainer("form")) form = soup.form @@ -114,6 +112,7 @@ def find_form_request(html: str): if not url: raise _exception.ParseError("Could not find url to submit to", data=form) + # From what I've seen, it'll always do this! if url.startswith("/"): url = "https://www.facebook.com" + url @@ -168,15 +167,12 @@ def two_factor_helper(session: requests.Session, r, on_2fa_callback): def get_error_data(html: str) -> Optional[str]: """Get error message from a request.""" - # Only import when required - import bs4 - soup = bs4.BeautifulSoup( html, "html.parser", parse_only=bs4.SoupStrainer("form", id="login_form") ) # Attempt to extract and format the error string # The error message is in the user's own language! - return ". ".join(list(soup.stripped_strings)[:2]) or None + return " ".join(list(soup.stripped_strings)[1:3]) or None def get_fb_dtsg(define) -> Optional[str]: @@ -298,7 +294,7 @@ class Session: ) # Get a facebook.com url that handles the 2FA flow # This probably works differently for Messenger-only accounts - url = get_next_url(url) + url = _util.get_url_parameter(url, "next") # Explicitly allow redirects r = session.get(url, allow_redirects=True) url = two_factor_helper(session, r, on_2fa_callback) diff --git a/tests/test_session.py b/tests/test_session.py index 7bf56f3..1332581 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,15 +1,47 @@ import datetime import pytest +from fbchat import ParseError from fbchat._session import ( + parse_server_js_define, base36encode, prefix_url, generate_message_id, + session_factory, client_id_factory, - is_home, + find_form_request, get_error_data, ) +def test_parse_server_js_define(): + html = """ + some data;require("TimeSliceImpl").guard(function(){(require("ServerJSDefine")).handleDefines([["DTSGInitialData",[],{"token":"123"},100]]) + + + other irrelevant data + """ + define = parse_server_js_define(html) + assert define == { + "DTSGInitialData": {"token": "123"}, + "DTSGInitData": {"async_get_token": "12345", "token": "123"}, + } + + +def test_parse_server_js_define_error(): + with pytest.raises(ParseError, match="Could not find any"): + parse_server_js_define("") + + html = 'function(){(require("ServerJSDefine")).handleDefines([{"a": function(){}}])' + with pytest.raises(ParseError, match="Invalid"): + parse_server_js_define(html + html) + + html = 'function(){require("ServerJSDefine").handleDefines({"a": "b"})' + with pytest.raises(ParseError, match="Invalid"): + parse_server_js_define(html + html) + + @pytest.mark.parametrize( "number,expected", [(1, "1"), (10, "a"), (123, "3f"), (1000, "rs"), (123456789, "21i3v9")], @@ -19,8 +51,10 @@ def test_base36encode(number, expected): def test_prefix_url(): - assert prefix_url("/") == "https://www.facebook.com/" - assert prefix_url("/abc") == "https://www.facebook.com/abc" + static_url = "https://upload.messenger.com/" + assert prefix_url(static_url) == static_url + assert prefix_url("/") == "https://www.messenger.com/" + assert prefix_url("/abc") == "https://www.messenger.com/abc" def test_generate_message_id(): @@ -28,46 +62,106 @@ def test_generate_message_id(): assert generate_message_id(datetime.datetime.utcnow(), "def") +def test_session_factory(): + session = session_factory() + assert session.headers + + def test_client_id_factory(): # Returns random output, so hard to test more thoroughly assert client_id_factory() -def test_is_home(): - assert not is_home("https://m.facebook.com/login/?...") - assert is_home("https://m.facebook.com/home.php?refsrc=...") +def test_find_form_request(): + html = """ +
-
-
- The password you entered is incorrect. Did you forget your password?
+
+
+ |