diff --git a/user_scanner/user_scan/social/facebook.py b/user_scanner/user_scan/social/facebook.py new file mode 100644 index 0000000..71b2067 --- /dev/null +++ b/user_scanner/user_scan/social/facebook.py @@ -0,0 +1,117 @@ +import re + +import httpx +from user_scanner.core.result import Result + + +def _check(user: str) -> Result: + show_url = "https://facebook.com" + + if not (1 <= len(user) <= 50): + return Result.error("Length must be 1-50 characters") + + if not re.match(r"^[a-zA-Z0-9.]+$", user): + return Result.error("Only letters, numbers and periods allowed") + + if user.isdigit(): + return Result.error("Username cannot be numbers only") + + if user.startswith(".") or user.endswith("."): + return Result.error("Username cannot start or end with a period") + + with httpx.Client(http2=True, follow_redirects=False, timeout=10.0) as client: + try: + headers1 = { + 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 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.7", + 'Accept-Encoding': "identity", + 'sec-ch-ua': '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"', + } + client.get("https://m.facebook.com/login/", headers=headers1) + + headers2 = { + 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 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.7", + 'Accept-Encoding': "identity", + 'upgrade-insecure-requests': "1", + 'sec-fetch-site': "cross-site", + 'sec-fetch-mode': "navigate", + 'sec-fetch-user': "?1", + 'sec-fetch-dest': "document", + 'sec-ch-ua': '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"', + 'sec-ch-ua-mobile': "?0", + 'sec-ch-ua-platform': '"Linux"', + 'referer': "https://www.google.com/", + 'accept-language': "en-US,en;q=0.9", + 'priority': "u=0, i" + } + res = client.get("https://www.facebook.com", params={'_rdr': ""}, headers=headers2) + html = res.text + + lsd_match = re.search(r'\["LSD",\[\],\{"token":"([^"]+)"\}', html) or \ + re.search(r'name="lsd"\s+value="([^"]+)"', html) or \ + re.search(r'"lsd":"([^"]+)"', html) + + j_match = re.search(r'jazoest=(\d+)', html) or \ + re.search(r'name="jazoest"\s+value="(\d+)"', html) + + lsd = lsd_match.group(1) if lsd_match else None + jazoest = j_match.group(1) if j_match else None + + if not lsd or not jazoest: + return Result.error(f"Token extraction failed (LSD: {bool(lsd)}, Jazoest: {bool(jazoest)})") + + headers3 = { + 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36", + 'Accept-Encoding': "identity", + 'sec-ch-ua-full-version-list': '"Google Chrome";v="143.0.7499.192", "Chromium";v="143.0.7499.192", "Not A(Brand";v="24.0.0.0"', + 'sec-ch-ua-platform': '"Linux"', + 'sec-ch-ua': '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"', + 'sec-ch-ua-model': '""', + 'sec-ch-ua-mobile': "?0", + 'x-asbd-id': "359341", + 'x-fb-lsd': lsd, + 'sec-ch-prefers-color-scheme': "dark", + 'sec-ch-ua-platform-version': '""', + 'origin': "https://www.facebook.com", + 'sec-fetch-site': "same-origin", + 'sec-fetch-mode': "cors", + 'sec-fetch-dest': "empty", + 'referer': "https://www.facebook.com/login/identify/?ctx=recover&ars=facebook_login&from_login_screen=0", + 'accept-language': "en-US,en;q=0.9", + 'priority': "u=1, i" + } + + payload = { + 'jazoest': jazoest, + 'lsd': lsd, + 'email': user, + 'did_submit': "1", + '__user': "0", + '__a': "1", + '__req': "7" + } + + response = client.post( + "https://www.facebook.com/ajax/login/help/identify.php", + params={'ctx': "recover"}, + data=payload, + headers=headers3 + ) + body = response.text + + if "These accounts matched your search" in body or "redirectPageTo" in body: + return Result.taken(url=show_url) + elif "No search results" in body or "Your search did not return any results." in body: + return Result.available(url=show_url) + else: + return Result.error("Unexpected response, report it via GitHub issues") + + except httpx.TimeoutException: + return Result.error("Connection timed out") + except Exception as e: + return Result.error(f"Unexpected exception: {e}") + + +def validate_facebook(user: str) -> Result: + return _check(user)