|
1 | | -import httpx |
2 | 1 | import re |
3 | | -from user_scanner.core.result import Result |
4 | | - |
5 | 2 |
|
6 | | -async def _check(email: str) -> Result: |
7 | | - show_url = "https://gumroad.com" |
8 | | - async with httpx.AsyncClient(timeout=15.0, http2=False, follow_redirects=True) as client: |
9 | | - try: |
10 | | - url1 = "https://gumroad.com/users/forgot_password/new" |
11 | | - headers1 = { |
12 | | - 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", |
13 | | - 'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", |
14 | | - 'Accept-Encoding': "identity", |
15 | | - 'sec-ch-ua': '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"', |
16 | | - 'sec-ch-ua-mobile': "?0", |
17 | | - 'sec-ch-ua-platform': '"Linux"', |
18 | | - 'upgrade-insecure-requests': "1", |
19 | | - 'referer': "https://www.google.com/", |
20 | | - 'accept-language': "en-US,en;q=0.9" |
21 | | - } |
| 3 | +from user_scanner.core.impersonate import impersonate_request_async |
| 4 | +from user_scanner.core.result import Result |
22 | 5 |
|
23 | | - res1 = await client.get(url1, headers=headers1) |
24 | | - html = res1.text |
| 6 | +SIGNUP_URL = "https://gumroad.com/signup" |
| 7 | +CSRF_RE = re.compile(r'<meta name="csrf-token" content="([^"]+)"') |
25 | 8 |
|
26 | | - csrf_match = re.search( |
27 | | - r'authenticity_token":"([^&]+)"', html) |
28 | | - if not csrf_match: |
29 | | - csrf_match = re.search( |
30 | | - r'name="csrf-token" content="([^"]+)"', html) |
31 | 9 |
|
32 | | - if not csrf_match: |
33 | | - return Result.error("Failed to extract CSRF token") |
| 10 | +async def validate_gumroad(email: str) -> Result: |
| 11 | + show_url = "https://gumroad.com" |
34 | 12 |
|
35 | | - csrf_token = csrf_match.group(1) |
| 13 | + try: |
| 14 | + page = await impersonate_request_async(SIGNUP_URL, allow_redirects=True) |
| 15 | + if page.status_code != 200: |
| 16 | + return Result.error( |
| 17 | + f"Unexpected Gumroad signup response: {page.status_code}", |
| 18 | + url=show_url, |
| 19 | + ) |
36 | 20 |
|
37 | | - url2 = "https://gumroad.com/users/forgot_password" |
| 21 | + token = CSRF_RE.search(page.text) |
| 22 | + if not token or "Signup/New" not in page.text: |
| 23 | + return Result.error("Could not read Gumroad signup form", url=show_url) |
38 | 24 |
|
39 | | - payload = { |
| 25 | + response = await impersonate_request_async( |
| 26 | + SIGNUP_URL, |
| 27 | + "POST", |
| 28 | + json={ |
40 | 29 | "user": { |
41 | | - "email": email |
42 | | - } |
43 | | - } |
| 30 | + "email": email, |
| 31 | + # Gumroad rejects this three-character password after checking |
| 32 | + # whether the email is already registered. |
| 33 | + "password": "Q7~", |
| 34 | + }, |
| 35 | + }, |
| 36 | + headers={ |
| 37 | + "accept": "text/html, application/xhtml+xml", |
| 38 | + "origin": "https://gumroad.com", |
| 39 | + "referer": SIGNUP_URL, |
| 40 | + "x-csrf-token": token.group(1), |
| 41 | + "x-inertia": "true", |
| 42 | + "x-requested-with": "XMLHttpRequest", |
| 43 | + }, |
| 44 | + allow_redirects=True, |
| 45 | + ) |
| 46 | + if response.status_code != 200: |
| 47 | + return Result.error( |
| 48 | + f"Unexpected Gumroad signup response: {response.status_code}", |
| 49 | + url=show_url, |
| 50 | + ) |
44 | 51 |
|
45 | | - headers2 = { |
46 | | - 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", |
47 | | - 'Accept': "text/html, application/xhtml+xml", |
48 | | - 'Accept-Encoding': "identity", |
49 | | - 'Content-Type': "application/json", |
50 | | - 'sec-ch-ua-platform': '"Linux"', |
51 | | - 'x-csrf-token': csrf_token, |
52 | | - 'sec-ch-ua': '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"', |
53 | | - 'x-inertia': "true", |
54 | | - 'sec-ch-ua-mobile': "?0", |
55 | | - 'x-requested-with': "XMLHttpRequest", |
56 | | - 'origin': "https://gumroad.com", |
57 | | - 'sec-fetch-site': "same-origin", |
58 | | - 'sec-fetch-mode': "cors", |
59 | | - 'sec-fetch-dest': "empty", |
60 | | - 'referer': "https://gumroad.com/users/forgot_password/new", |
61 | | - 'accept-language': "en-US,en;q=0.9", |
62 | | - 'priority': "u=1, i" |
63 | | - } |
| 52 | + data = response.json() |
| 53 | + if data.get("component") != "Signup/New": |
| 54 | + return Result.error("Unexpected Gumroad signup page", url=show_url) |
64 | 55 |
|
65 | | - response = await client.post(url2, json=payload, headers=headers2) |
66 | | - |
67 | | - data = response.json() |
68 | | - flash_msg = data.get("props", {}).get( |
69 | | - "flash", {}).get("message", "") |
70 | | - |
71 | | - if "An account does not exist" in flash_msg: |
72 | | - return Result.available(url=show_url) |
73 | | - elif "An account does not exist" not in flash_msg: |
74 | | - return Result.taken(url=show_url) |
75 | | - else: |
76 | | - return Result.error(f"Unexpected status: {response.status_code}") |
77 | | - |
78 | | - except Exception as e: |
79 | | - return Result.error(f"unexpected exception: {e}") |
80 | | - |
81 | | - |
82 | | -async def validate_gumroad(email: str) -> Result: |
83 | | - return await _check(email) |
| 56 | + flash = data.get("props", {}).get("flash") or {} |
| 57 | + message = flash.get("message") |
| 58 | + if message == "An account already exists with this email.": |
| 59 | + return Result.taken(url=show_url) |
| 60 | + if message == "Password is too short (minimum is 4 characters)": |
| 61 | + return Result.available(url=show_url) |
| 62 | + return Result.error("Unexpected Gumroad signup result", url=show_url) |
| 63 | + except Exception as exc: |
| 64 | + return Result.error(exc, url=show_url) |
0 commit comments