diff --git a/fbchat/_client.py b/fbchat/_client.py index 30279449..3e9c853d 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -68,7 +68,7 @@ def __init__( self, email, password, - user_agent=None, + user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36", max_tries=5, session_cookies=None, logging_level=logging.INFO, @@ -312,7 +312,9 @@ def fetchThreads(self, thread_location, before=None, after=None, limit=None): before=last_thread_timestamp, thread_location=thread_location ) - if len(candidates) > 1: + if last_thread_timestamp == None and len(candidates) >= 1: + threads += candidates + elif len(candidates) > 1: threads += candidates[1:] else: # End of threads break @@ -1714,7 +1716,7 @@ def reactToMessage(self, message_id, reaction): "client_mutation_id": "1", "actor_id": self._uid, "message_id": str(message_id), - "reaction": reaction.value if reaction else None, + "reaction": reaction if reaction else None } data = {"doc_id": 1491398900900362, "variables": json.dumps({"data": data})} j = self._payload_post("/webgraphql/mutation", data) @@ -2614,14 +2616,18 @@ def getThreadIdAndThreadType(msg_metadata): thread_id, thread_type = getThreadIdAndThreadType(i) mid = i["messageId"] author_id = str(i["userId"]) - reaction = ( - MessageReaction(i["reaction"]) if i.get("reaction") else None - ) + + # Commented as the statement below invalidates other reaction emoji + + # reaction = (reaction = ( + # MessageReaction(i["reaction"]) if i.get("reaction") else None + # ) + add_reaction = not bool(i["action"]) if add_reaction: self.onReactionAdded( mid=mid, - reaction=reaction, + reaction=i.get("reaction"), author_id=author_id, thread_id=thread_id, thread_type=thread_type, diff --git a/fbchat/_state.py b/fbchat/_state.py index 3aebcecf..52fb1833 100644 --- a/fbchat/_state.py +++ b/fbchat/_state.py @@ -1,5 +1,6 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +from pprint import pprint import attr import bs4 @@ -15,22 +16,26 @@ def get_user_id(session): # TODO: Optimize this `.get_dict()` call! rtn = session.cookies.get_dict().get("c_user") + # Sometimes c_user is missing if logged in from another location + if rtn is None: + rtn = str(input("c_user is blank, enter manually from browser cookies: ")) if rtn is None: raise _exception.FBchatException("Could not find user id") return str(rtn) def find_input_fields(html): - return bs4.BeautifulSoup(html, "html.parser", parse_only=bs4.SoupStrainer("input")) + # return bs4.BeautifulSoup(html, "html.parser", parse_only=bs4.SoupStrainer("input")) + return bs4.BeautifulSoup(html, "html.parser") def session_factory(user_agent=None): - session = requests.session() - session.headers["Referer"] = "https://www.facebook.com" - session.headers["Accept"] = "text/html" + session = requests.Session() + # session.headers["Referer"] = "https://www.facebook.com" + # session.headers["Accept"] = "text/html" # TODO: Deprecate setting the user agent manually - session.headers["User-Agent"] = user_agent or random.choice(_util.USER_AGENTS) + # session.headers["User-Agent"] = user_agent or random.choice(_util.USER_AGENTS) return session @@ -123,20 +128,29 @@ def get_params(self): } @classmethod - def login(cls, email, password, on_2fa_callback, user_agent=None): + def login(cls, email, password, on_2fa_callback, user_agent=_util.USER_AGENTS): session = session_factory(user_agent=user_agent) soup = find_input_fields(session.get("https://m.facebook.com/").text) + + soup = soup.find_all("input") + data = dict( (elem["name"], elem["value"]) for elem in soup if elem.has_attr("value") and elem.has_attr("name") ) + data["email"] = email data["pass"] = password data["login"] = "Log In" - r = session.post("https://m.facebook.com/login.php?login_attempt=1", data=data) + # pprint(data) + r = session.post("https://m.facebook.com/login/device-based/regular/login/?refsrc=deprecated&lwv=100&refid=8", data=data) + + # print(r.status_code) + # print(r.url) + # print(r.content) # Usually, 'Checkpoint' will refer to 2FA if "checkpoint" in r.url and ('id="approvals_code"' in r.text.lower()): @@ -147,6 +161,10 @@ def login(cls, email, password, on_2fa_callback, user_agent=None): if "save-device" in r.url: r = session.get("https://m.facebook.com/login/save-device/cancel/") + # Sometimes facebook redirects to facebook.com/cookie/consent-page/*[...more directories]. So, go to homepage + if "cookie" in r.url: + r = session.get("https://m.facebook.com/", allow_redirects=False) + if is_home(r.url): return cls.from_session(session=session) else: @@ -177,20 +195,53 @@ def from_session(cls, session): user_id = get_user_id(session) r = session.get(_util.prefix_url("/")) - soup = find_input_fields(r.text) - fb_dtsg_element = soup.find("input", {"name": "fb_dtsg"}) - if fb_dtsg_element: - fb_dtsg = fb_dtsg_element["value"] - else: + fb_dtsg = '' + + if fb_dtsg == None or fb_dtsg == "": + FB_DTSG_REGEX = re.compile(r'"[a-zA-Z0-9-_:]+:+[a-zA-Z0-9-_:]*"') + fb_dtsg = FB_DTSG_REGEX.search(r.text).group(0) + fb_dtsg = fb_dtsg.replace('"', "") + fb_dtsg = fb_dtsg.replace("'", '') + # print("\n\n[latest] fb_dtsg:\n") + # print(fb_dtsg) + + + if fb_dtsg == None or fb_dtsg == "": + fb_dtsg_element = soup.find("input", {"name": "fb_dtsg"}) + print("\n\n[1]fb_dtsg_element:\n") + print(fb_dtsg_element) + if fb_dtsg_element: + fb_dtsg = fb_dtsg_element["value"] + # print("\n\n[1.1]fb_dtsg_element:\n") + # print(fb_dtsg) + + if fb_dtsg == None or fb_dtsg == "": # Fall back to searching with a regex - fb_dtsg = FB_DTSG_REGEX.search(r.text).group(1) - - revision = int(r.text.split('"client_revision":', 1)[1].split(",", 1)[0]) + fb_dtsg = '' + try: + fb_dtsg = FB_DTSG_REGEX.search(r.text).group(1) + # print("\n\n[2]fb_dtsg_element:\n") + # print(fb_dtsg) + except: + pass + + + if fb_dtsg == None or fb_dtsg == "": + FB_DTSG_REGEX = re.compile(r'"[a-zA-Z0-9_.-]*:[a-zA-Z0-9_.-]*"\)') + fb_dtsg = FB_DTSG_REGEX.search(r.text).group(0) + fb_dtsg = fb_dtsg.replace(")", "") + fb_dtsg = fb_dtsg.replace('"', '') + # print("\n\n[3]fb_dtsg_element:\n") + # print(fb_dtsg) + + revision = int(r.text.split('"client_revision":')[1].split(",", 1)[0]) logout_h_element = soup.find("input", {"name": "h"}) logout_h = logout_h_element["value"] if logout_h_element else None + + print(user_id, fb_dtsg, revision, logout_h) return cls( user_id=user_id, diff --git a/fbchat/_util.py b/fbchat/_util.py index 920e3749..fd6da1ab 100644 --- a/fbchat/_util.py +++ b/fbchat/_util.py @@ -43,14 +43,8 @@ log.addHandler(handler) #: Default list of user agents -USER_AGENTS = [ - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/601.1.10 (KHTML, like Gecko) Version/8.0.5 Safari/601.1.10", - "Mozilla/5.0 (Windows NT 6.3; WOW64; ; NCT50_AAP285C84A1328) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", - "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", -] +# Changing to this user-agent solved login issue for some people. +USER_AGENTS = ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36"] def now():