diff --git a/user_scanner/core/helpers.py b/user_scanner/core/helpers.py index 9f4dfdaf..c558040a 100644 --- a/user_scanner/core/helpers.py +++ b/user_scanner/core/helpers.py @@ -50,6 +50,8 @@ "dragongroot", "hoichoi", "fantasia", + "couplejoy", + "asafeer", ], } diff --git a/user_scanner/email_scan/learning/asafeer.py b/user_scanner/email_scan/learning/asafeer.py new file mode 100644 index 00000000..9a3dbca0 --- /dev/null +++ b/user_scanner/email_scan/learning/asafeer.py @@ -0,0 +1,52 @@ +import httpx +from user_scanner.core.result import Result + + +async def _check(email: str) -> Result: + url = "https://3asafeer.com/caller.php" + show_url = "https://3asafeer.com" + + params = { + "page": "user", + "task": "forgotPass", + "eml": email, + "format": "json", + } + + headers = { + "User-Agent": "okhttp/5.3.2", + "Accept-Encoding": "gzip", + } + + async with httpx.AsyncClient(http2=True) as client: + try: + response = await client.get(url, params=params, headers=headers, timeout=6.0) + + if response.status_code == 429: + return Result.error("Rate limited", url=show_url) + + if response.status_code == 200: + data = response.json() + result_val = data.get("result") + if result_val == "success": + return Result.taken(url=show_url) + elif result_val == "failed": + return Result.available(url=show_url) + + return Result.error("Unexpected response body, report it via GitHub issues", url=show_url) + + return Result.error( + f"Unexpected response status: {response.status_code}, report it via GitHub issues", + url=show_url, + ) + + except Exception as e: + return Result.error(e, url=show_url) + + +async def validate_asafeer(email: str) -> Result: + """ + 3Asafeer (مدرسة عصافير) email validator. Checks forgotPass endpoint. + Loud module because it sends a password reset email when account exists. + """ + return await _check(email) diff --git a/user_scanner/email_scan/shopping/tatacliq.py b/user_scanner/email_scan/shopping/tatacliq.py new file mode 100644 index 00000000..fe51abd7 --- /dev/null +++ b/user_scanner/email_scan/shopping/tatacliq.py @@ -0,0 +1,66 @@ +import httpx +from user_scanner.core.result import Result + + +async def _check(email: str) -> Result: + url = "https://api.tatadigital.com/api/v2/sso/check-email" + show_url = "https://tatadigital.com" + + payload = { + "email": email, + "sendOtp": False, + } + + headers = { + "User-Agent": "okhttp/5.2.0", + "Accept-Encoding": "gzip", + "client_id": "TATACLIQ-ANDROID-APP", + "appversion": "103.22.0", + "appplatform": "android", + "content-type": "application/json; charset=UTF-8", + } + + async with httpx.AsyncClient(http2=True) as client: + try: + response = await client.post(url, json=payload, headers=headers, timeout=6.0) + + if response.status_code == 429: + return Result.error("Rate limited", url=show_url) + + if response.status_code == 200: + data = response.json() + user_type = data.get("userType") + email_enrolled = data.get("emailEnrolled") + + if user_type in ["existing", "migrated"] or email_enrolled is True: + extra = {} + if user_type: + extra["user_type"] = user_type + if email_enrolled is not None: + extra["email_enrolled"] = email_enrolled + if data.get("emailVerified") is not None: + extra["email_verified"] = data["emailVerified"] + if data.get("phoneMasked"): + extra["phone_masked"] = data["phoneMasked"] + return Result.taken(extra=extra, url=show_url) + + if user_type == "new" or data.get("message") == "User is not found": + return Result.available(url=show_url) + + return Result.error("Unexpected response body, report it via GitHub issues", url=show_url) + + return Result.error( + f"Unexpected response status: {response.status_code}, report it via GitHub issues", + url=show_url, + ) + + except Exception as e: + return Result.error(e, url=show_url) + + +async def validate_tatacliq(email: str) -> Result: + """ + Tata CLiQ (Tata Neu / Tata Digital SSO) email validator. + Checks check-email endpoint without sending OTP (sendOtp=False). + """ + return await _check(email) diff --git a/user_scanner/email_scan/social/couplejoy.py b/user_scanner/email_scan/social/couplejoy.py new file mode 100644 index 00000000..b866e3bd --- /dev/null +++ b/user_scanner/email_scan/social/couplejoy.py @@ -0,0 +1,58 @@ +import httpx +from user_scanner.core.result import Result + + +async def _check(email: str) -> Result: + url = "https://api.couplejoyapp.com/v1/reset-password" + show_url = "https://couplejoyapp.com" + + payload = { + "email": email + } + + headers = { + "User-Agent": "okhttp/4.12.0", + "Accept": "application/json", + "Accept-Encoding": "gzip", + "Content-Type": "application/json", + "app-language": "en", + "app-timezone": "UTC", + "app-platform": "android", + "app-version": "4.28.0", + "app-locale": "en-US", + "app-os-version": "11", + "authorization": "Bearer null", + } + + async with httpx.AsyncClient(http2=True) as client: + try: + response = await client.post(url, json=payload, headers=headers, timeout=6.0) + + if response.status_code == 429: + return Result.error("Rate limited", url=show_url) + + if response.status_code == 200: + data = response.json() + if data.get("success") is True: + return Result.taken(url=show_url) + + if response.status_code == 400: + data = response.json() + if data.get("code") == "EMAIL_NOT_FOUND": + return Result.available(url=show_url) + + return Result.error( + f"Unexpected response status: {response.status_code}, report it via GitHub issues", + url=show_url, + ) + + except Exception as e: + return Result.error(e, url=show_url) + + +async def validate_couplejoy(email: str) -> Result: + """ + Couplejoy email validator. Checks reset-password endpoint. + Loud module because it sends password reset email when account exists. + """ + return await _check(email) diff --git a/user_scanner/email_scan/social/lovenudge.py b/user_scanner/email_scan/social/lovenudge.py new file mode 100644 index 00000000..e068e203 --- /dev/null +++ b/user_scanner/email_scan/social/lovenudge.py @@ -0,0 +1,52 @@ +import httpx +from user_scanner.core.result import Result + + +async def _check(email: str) -> Result: + url = "https://api.lovenudgeapp.com/api/register/check" + show_url = "https://lovenudgeapp.com" + + params = { + "email": email, + "time_zone": "UTC", + "app_version": "5.2.10", + } + + headers = { + "User-Agent": "okhttp/4.11.0", + "Accept": "application/json", + "Accept-Encoding": "gzip", + "Content-Type": "application/json", + } + + async with httpx.AsyncClient(http2=True) as client: + try: + response = await client.get(url, params=params, headers=headers, timeout=6.0) + + if response.status_code == 429: + return Result.error("Rate limited", url=show_url) + + if response.status_code == 200: + data = response.json() + if "data" in data: + if data["data"] is True: + return Result.taken(url=show_url) + elif data["data"] is False: + return Result.available(url=show_url) + + return Result.error("Unexpected response body, report it via GitHub issues", url=show_url) + + return Result.error( + f"Unexpected response status: {response.status_code}, report it via GitHub issues", + url=show_url, + ) + + except Exception as e: + return Result.error(e, url=show_url) + + +async def validate_lovenudge(email: str) -> Result: + """ + Love Nudge email validator. Checks register/check endpoint. + """ + return await _check(email)