Skip to content

Commit 4fe8c44

Browse files
committed
create: new email_scan category fitness and add 2 new modules
1 parent 40c588d commit 4fe8c44

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

user_scanner/email_scan/fitness/__init__.py

Whitespace-only changes.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import httpx
2+
import json
3+
import re
4+
from user_scanner.core.result import Result
5+
6+
async def _check(email: str) -> Result:
7+
base_url = "https://www.fitnessblender.com"
8+
api_url = "https://www.fitnessblender.com/api/v1/validate/unique-email"
9+
show_url = "https://www.fitnessblender.com"
10+
11+
headers = {
12+
'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Mobile Safari/537.36",
13+
'Accept': "application/json, text/plain, */*",
14+
'Accept-Encoding': "identity",
15+
'sec-ch-ua-platform': '"Android"',
16+
'Origin': "https://www.fitnessblender.com",
17+
'Referer': "https://www.fitnessblender.com/join",
18+
'X-Requested-With': "XMLHttpRequest",
19+
'Priority': "u=1, i"
20+
}
21+
22+
try:
23+
async with httpx.AsyncClient(timeout=20.0, follow_redirects=True) as client:
24+
# Step 1: Visit homepage to get cookies (XSRF-TOKEN and FB_SESSION)
25+
# Laravel sets these in the Set-Cookie header
26+
r_init = await client.get(base_url, headers=headers)
27+
28+
if r_init.status_code != 200:
29+
return Result.error(f"Handshake failed: {r_init.status_code}")
30+
31+
# Get the CSRF token from the cookies
32+
# Laravel allows using the XSRF-TOKEN cookie value as the x-csrf-token header
33+
csrf_token = client.cookies.get("XSRF-TOKEN")
34+
35+
# If not in cookies, try to extract from the HTML script tag as a backup
36+
if not csrf_token:
37+
match = re.search(r'csrfToken:\s*"([^"]+)"', r_init.text)
38+
if match:
39+
csrf_token = match.group(1)
40+
41+
if not csrf_token:
42+
return Result.error("CSRF token not found")
43+
44+
# Step 2: Post to the validation API
45+
headers['x-csrf-token'] = csrf_token
46+
headers['Content-Type'] = "application/json"
47+
48+
payload = {
49+
"email": email,
50+
"force": 0
51+
}
52+
53+
response = await client.post(
54+
api_url,
55+
content=json.dumps(payload),
56+
headers=headers
57+
)
58+
59+
if response.status_code == 403:
60+
return Result.error("Caught by WAF (403)")
61+
62+
if response.status_code == 419:
63+
return Result.error("CSRF Mismatch/Expired (419)")
64+
65+
data = response.json()
66+
status = data.get("status")
67+
message = data.get("message", "").lower()
68+
69+
# Logic: error + "already registered" = TAKEN
70+
if status == "error" and "already registered" in message:
71+
return Result.taken(url=show_url)
72+
73+
# Logic: success + "ok" = AVAILABLE
74+
elif status == "success" and message == "ok":
75+
return Result.available(url=show_url)
76+
77+
return Result.error(f"Unexpected response: {message}")
78+
79+
except Exception as e:
80+
return Result.error(e)
81+
82+
async def validate_fitnessblender(email: str) -> Result:
83+
return await _check(email)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import httpx
2+
from user_scanner.core.result import Result
3+
4+
5+
async def _check(email: str) -> Result:
6+
url = "https://www.myfitnesspal.com/api/idm/user-exists"
7+
show_url = "https://www.myfitnesspal.com"
8+
9+
headers = {
10+
'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Mobile Safari/537.36",
11+
'Accept': "application/json, text/plain, */*",
12+
'Accept-Encoding': "identity",
13+
'sec-ch-ua-platform': '"Android"',
14+
'sec-ch-ua': '"Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145"',
15+
'Referer': "https://www.myfitnesspal.com/account/create",
16+
'Accept-Language': "en-US,en;q=0.9",
17+
'Priority': "u=1, i"
18+
}
19+
20+
params = {
21+
'email': email
22+
}
23+
24+
try:
25+
async with httpx.AsyncClient(timeout=10.0) as client:
26+
response = await client.get(url, params=params, headers=headers)
27+
28+
if response.status_code == 403:
29+
return Result.error("Caught by WAF or IP Block (403)")
30+
31+
if response.status_code == 429:
32+
return Result.error("Rate limited by MyFitnessPal (429)")
33+
34+
if response.status_code != 200:
35+
return Result.error(f"HTTP Error: {response.status_code}")
36+
37+
data = response.json()
38+
39+
# Use the emailExists key for validation
40+
if data.get("emailExists") is True:
41+
return Result.taken(url=show_url)
42+
43+
elif data.get("emailExists") is False:
44+
return Result.available(url=show_url)
45+
46+
return Result.error("Unexpected response body structure")
47+
48+
except httpx.ConnectTimeout:
49+
return Result.error("Connection timed out! maybe region blocks")
50+
except httpx.ReadTimeout:
51+
return Result.error("Server took too long to respond (Read Timeout)")
52+
except Exception as e:
53+
return Result.error(e)
54+
55+
56+
async def validate_myfitnesspal(email: str) -> Result:
57+
return await _check(email)

0 commit comments

Comments
 (0)