user-scanner 1.1.0__py3-none-any.whl → 1.1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. user_scanner/__main__.py +2 -2
  2. user_scanner/config.json +1 -0
  3. user_scanner/core/email_orchestrator.py +17 -17
  4. user_scanner/core/orchestrator.py +6 -3
  5. user_scanner/core/result.py +37 -21
  6. user_scanner/email_scan/adult/pornhub.py +1 -1
  7. user_scanner/email_scan/adult/sexvid.py +43 -0
  8. user_scanner/email_scan/community/__init__.py +0 -0
  9. user_scanner/email_scan/community/stackoverflow.py +40 -0
  10. user_scanner/email_scan/creator/__init__.py +0 -0
  11. user_scanner/email_scan/creator/gumroad.py +82 -0
  12. user_scanner/email_scan/creator/patreon.py +58 -0
  13. user_scanner/email_scan/dev/__init__.py +1 -0
  14. user_scanner/email_scan/dev/replit.py +51 -0
  15. user_scanner/email_scan/entertainment/.ruff_cache/0.14.10/14677874048998292530 +0 -0
  16. user_scanner/email_scan/entertainment/__init__.py +0 -0
  17. user_scanner/email_scan/entertainment/spotify.py +87 -0
  18. user_scanner/email_scan/gaming/__init__.py +0 -0
  19. user_scanner/email_scan/gaming/chess_com.py +47 -0
  20. user_scanner/email_scan/social/mastodon.py +5 -3
  21. user_scanner/user_scan/shopping/__init__.py +0 -0
  22. user_scanner/user_scan/shopping/vinted.py +42 -0
  23. user_scanner/version.json +1 -1
  24. {user_scanner-1.1.0.dist-info → user_scanner-1.1.0.2.dist-info}/METADATA +3 -5
  25. {user_scanner-1.1.0.dist-info → user_scanner-1.1.0.2.dist-info}/RECORD +30 -17
  26. user_scanner/email_scan/dev/.ruff_cache/0.14.10/10328336453267387919 +0 -0
  27. user_scanner/email_scan/shopping/ebay.py.lock +0 -97
  28. /user_scanner/email_scan/{dev → entertainment}/.ruff_cache/.gitignore +0 -0
  29. /user_scanner/email_scan/{dev → entertainment}/.ruff_cache/CACHEDIR.TAG +0 -0
  30. {user_scanner-1.1.0.dist-info → user_scanner-1.1.0.2.dist-info}/WHEEL +0 -0
  31. {user_scanner-1.1.0.dist-info → user_scanner-1.1.0.2.dist-info}/entry_points.txt +0 -0
  32. {user_scanner-1.1.0.dist-info → user_scanner-1.1.0.2.dist-info}/licenses/LICENSE +0 -0
user_scanner/__main__.py CHANGED
@@ -196,7 +196,7 @@ def main():
196
196
  print(f"{R}[✘] Error: No valid emails found in {args.email_file}{X}")
197
197
  sys.exit(1)
198
198
 
199
- print(f"{C}[+] Loaded {len(valid_emails)} emails from {args.email_file}{X}")
199
+ print(f"{C}[+] Loaded {len(valid_emails)} {'email' if len(valid_emails) == 1 else 'emails'} from {args.email_file}{X}")
200
200
  is_email = True
201
201
  targets = valid_emails
202
202
  except FileNotFoundError:
@@ -213,7 +213,7 @@ def main():
213
213
  if not usernames:
214
214
  print(f"{R}[✘] Error: No valid usernames found in {args.username_file}{X}")
215
215
  sys.exit(1)
216
- print(f"{C}[+] Loaded {len(usernames)} usernames from {args.username_file}{X}")
216
+ print(f"{C}[+] Loaded {len(usernames)} {'username' if len(usernames) == 1 else 'usernames'} from {args.username_file}{X}")
217
217
  is_email = False
218
218
  targets = usernames
219
219
  except FileNotFoundError:
@@ -0,0 +1 @@
1
+ {"auto_update_status": true}
@@ -1,10 +1,11 @@
1
1
  import asyncio
2
2
  from pathlib import Path
3
- from typing import List
4
3
  from types import ModuleType
4
+ from typing import List
5
+
5
6
  from colorama import Fore, Style
6
7
 
7
- from user_scanner.core.helpers import load_categories, load_modules, find_category
8
+ from user_scanner.core.helpers import find_category, load_categories, load_modules
8
9
  from user_scanner.core.result import Result
9
10
 
10
11
  # Concurrency control
@@ -13,11 +14,21 @@ MAX_CONCURRENT_REQUESTS = 15
13
14
 
14
15
  async def _async_worker(module: ModuleType, email: str, sem: asyncio.Semaphore) -> Result:
15
16
  async with sem:
16
- module_name = module.__name__.split('.')[-1]
17
+ module_name = module.__name__.split(".")[-1]
17
18
  func_name = f"validate_{module_name}"
19
+ actual_cat = find_category(module) or "Email"
20
+
21
+ params = {
22
+ "site_name": module_name.capitalize(),
23
+ "username": email,
24
+ "category": actual_cat,
25
+ "is_email": True,
26
+ }
18
27
 
19
28
  if not hasattr(module, func_name):
20
- return Result.error(f"Function {func_name} not found")
29
+ return (
30
+ Result.error(f"Function {func_name} not found").update(**params).show()
31
+ )
21
32
 
22
33
  func = getattr(module, func_name)
23
34
 
@@ -27,21 +38,10 @@ async def _async_worker(module: ModuleType, email: str, sem: asyncio.Semaphore)
27
38
  except Exception as e:
28
39
  result = Result.error(e)
29
40
 
30
- # Use helper to get actual dir name for the Result object
31
- actual_cat = find_category(module) or "Email"
32
-
33
- result.update(
34
- site_name=module_name.capitalize(),
35
- username=email,
36
- category=actual_cat,
37
- is_email=True
38
- )
39
-
40
- print(result.get_console_output())
41
- return result
41
+ return result.update(**params).show()
42
42
 
43
43
 
44
- async def _run_batch(modules: List[ModuleType], email:str) -> List[Result]:
44
+ async def _run_batch(modules: List[ModuleType], email: str) -> List[Result]:
45
45
  sem = asyncio.Semaphore(MAX_CONCURRENT_REQUESTS)
46
46
  tasks = []
47
47
  for module in modules:
@@ -15,7 +15,11 @@ def _worker_single(module: ModuleType, username: str) -> Result:
15
15
  site_name = get_site_name(module)
16
16
 
17
17
  if not func:
18
- return Result.error(f"{site_name} has no validate_ function", site_name=site_name, username=username)
18
+ return Result.error(
19
+ f"{site_name} has no validate_ function",
20
+ site_name=site_name,
21
+ username=username,
22
+ )
19
23
 
20
24
  try:
21
25
  result: Result = func(username)
@@ -48,8 +52,7 @@ def run_user_category(category_path: Path, username: str) -> List[Result]:
48
52
  for result in exec_map:
49
53
  result.update(category=category_name)
50
54
  results.append(result)
51
-
52
- print(result.get_console_output())
55
+ result.show()
53
56
 
54
57
  return results
55
58
 
@@ -1,4 +1,5 @@
1
1
  from enum import Enum
2
+
2
3
  from colorama import Fore, Style
3
4
 
4
5
  DEBUG_MSG = """Result {{
@@ -53,21 +54,22 @@ class Result:
53
54
  self.username = None
54
55
  self.site_name = None
55
56
  self.category = None
56
- self.is_email = False
57
+ self.is_email = False
57
58
  self.update(**kwargs)
58
59
 
59
60
  def update(self, **kwargs):
60
61
  for field in ("username", "site_name", "category", "is_email"):
61
62
  if field in kwargs and kwargs[field] is not None:
62
63
  setattr(self, field, kwargs[field])
64
+ return self
63
65
 
64
66
  @classmethod
65
- def taken(cls, **kwargs):
66
- return cls(Status.TAKEN, **kwargs)
67
+ def taken(cls, reason: str | Exception | None = None, **kwargs):
68
+ return cls(Status.TAKEN, reason, **kwargs)
67
69
 
68
70
  @classmethod
69
- def available(cls, **kwargs):
70
- return cls(Status.AVAILABLE, **kwargs)
71
+ def available(cls, reason: str | Exception | None = None, **kwargs):
72
+ return cls(Status.AVAILABLE, reason, **kwargs)
71
73
 
72
74
  @classmethod
73
75
  def error(cls, reason: str | Exception | None = None, **kwargs):
@@ -79,7 +81,7 @@ class Result:
79
81
  status = Status(i)
80
82
  except ValueError:
81
83
  return cls(Status.ERROR, "Invalid status. Please contact maintainers.")
82
- return cls(status, reason if status == Status.ERROR else None)
84
+ return cls(status, reason)
83
85
 
84
86
  def to_number(self) -> int:
85
87
  return self.status.value
@@ -102,7 +104,7 @@ class Result:
102
104
  "username": self.username,
103
105
  "site_name": self.site_name,
104
106
  "category": self.category,
105
- "is_email": self.is_email
107
+ "is_email": self.is_email,
106
108
  }
107
109
 
108
110
  def debug(self) -> str:
@@ -111,7 +113,7 @@ class Result:
111
113
  def to_json(self) -> str:
112
114
  msg = JSON_TEMPLATE.format(**self.as_dict())
113
115
  if self.is_email:
114
- msg = msg.replace("\t\"username\":", "\t\"email\":")
116
+ msg = msg.replace('\t"username":', '\t"email":')
115
117
  return msg
116
118
 
117
119
  def to_csv(self) -> str:
@@ -129,22 +131,36 @@ class Result:
129
131
  return self.to_number() == other
130
132
  return NotImplemented
131
133
 
134
+ def get_output_color(self) -> str:
135
+ if self == Status.ERROR:
136
+ return Fore.YELLOW
137
+ elif self.is_email:
138
+ return Fore.GREEN if self == Status.TAKEN else Fore.RED
139
+ else:
140
+ return Fore.GREEN if self == Status.AVAILABLE else Fore.RED
141
+
142
+ def get_output_icon(self) -> str:
143
+ if self == Status.ERROR:
144
+ return "[!]"
145
+ elif self.is_email:
146
+ return "[✔]" if self == Status.TAKEN else "[✘]"
147
+ else:
148
+ return "[✔]" if self == Status.AVAILABLE else "[✘]"
149
+
132
150
  def get_console_output(self) -> str:
133
151
  site_name = self.site_name
134
- username = self.username
135
152
  status_text = self.status.to_label(self.is_email)
153
+ username = ""
154
+ if self.username:
155
+ username = f"({self.username})"
136
156
 
137
- if self.is_email:
138
- color = Fore.GREEN if self == Status.TAKEN else Fore.RED
139
- icon = "[✔]" if self == Status.TAKEN else "[✘]"
140
- else:
141
- color = Fore.GREEN if self == Status.AVAILABLE else Fore.RED
142
- icon = "[✔]" if self == Status.AVAILABLE else "[✘]"
157
+ color = self.get_output_color()
158
+ icon = self.get_output_icon()
143
159
 
144
- if self == Status.AVAILABLE or self == Status.TAKEN:
145
- return f" {color}{icon} {site_name} ({username}): {status_text}{Style.RESET_ALL}"
146
- elif self == Status.ERROR:
147
- reason = f" ({self.get_reason()})" if self.has_reason() else ""
148
- return f" {Fore.YELLOW}[!] {site_name} ({username}): {status_text}{reason}{Style.RESET_ALL}"
160
+ reason = f" ({self.get_reason()})" if self.has_reason() else ""
161
+ return f" {color}{icon} {site_name} {username}: {status_text}{reason}{Style.RESET_ALL}"
149
162
 
150
- return ""
163
+ def show(self):
164
+ """Prints the console output and returns itself for chaining"""
165
+ print(self.get_console_output())
166
+ return self
@@ -14,7 +14,7 @@ async def _check(email: str) -> Result:
14
14
  "content-type": "application/x-www-form-urlencoded; charset=UTF-8"
15
15
  }
16
16
 
17
- async with httpx.AsyncClient(http2=True, follow_redirects=True, timeout=10) as client:
17
+ async with httpx.AsyncClient(http2=True, follow_redirects=True, timeout=3) as client:
18
18
  try:
19
19
  landing_resp = await client.get(base_url, headers=headers)
20
20
  token_match = re.search(r'var\s+token\s*=\s*"([^"]+)"', landing_resp.text)
@@ -0,0 +1,43 @@
1
+ import httpx
2
+ from user_scanner.core.result import Result
3
+
4
+ async def _check(email: str) -> Result:
5
+ url = "https://www.sexvid.pro/reset-password/"
6
+
7
+ payload = {
8
+ 'action': "restore_password",
9
+ 'mode': "async",
10
+ 'format': "json",
11
+ 'email_link': "https://www.sexvid.pro/signup.php",
12
+ 'email': email,
13
+ 'code': "xxxxx"
14
+ }
15
+
16
+ headers = {
17
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36",
18
+ 'Accept': "*/*",
19
+ 'X-Requested-With': "XMLHttpRequest",
20
+ 'Origin': "https://www.sexvid.pro",
21
+ 'Referer': "https://www.sexvid.pro/reset-password/",
22
+ 'Content-Type': "application/x-www-form-urlencoded"
23
+ }
24
+
25
+ async with httpx.AsyncClient(http2=False, timeout=5.0) as client:
26
+ try:
27
+ response = await client.post(url, data=payload, headers=headers)
28
+ res_text = response.text
29
+
30
+ if "doesnt_exist" in res_text or "No user with such email exists" in res_text:
31
+ return Result.available()
32
+ elif "A new generated password has been sent" in res_text or "status\":\"success" in res_text:
33
+ return Result.taken()
34
+ elif response.status_code == 429:
35
+ return Result.error("Rate-limited")
36
+ else:
37
+ return Result.error(f"[{response.status_code}] Unexpected response body, report it via GitHub issues")
38
+
39
+ except Exception as e:
40
+ return Result.error(e)
41
+
42
+ async def validate_sexvid(email: str) -> Result:
43
+ return await _check(email)
File without changes
@@ -0,0 +1,40 @@
1
+ import httpx
2
+ from user_scanner.core.result import Result
3
+
4
+ async def _check(email: str) -> Result:
5
+ async with httpx.AsyncClient(http2=False, follow_redirects=True) as client:
6
+ try:
7
+ url = "https://stackoverflow.com/users/login"
8
+
9
+ payload = {
10
+ 'ssrc': "login",
11
+ 'email': email,
12
+ 'password': "Password109-grt",
13
+ 'oauth_version': "",
14
+ 'oauth_server': ""
15
+ }
16
+
17
+ headers = {
18
+ 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
19
+ '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",
20
+ 'Accept-Encoding': "identity",
21
+ 'sec-ch-ua-platform': '"Linux"',
22
+ 'origin': "https://stackoverflow.com",
23
+ 'referer': "https://stackoverflow.com/users/login"
24
+ }
25
+
26
+ response = await client.post(url, data=payload, headers=headers)
27
+ body = response.text
28
+
29
+ if "No user found with matching email" in body:
30
+ return Result.available()
31
+ elif "The email or password is incorrect" in body:
32
+ return Result.taken()
33
+ else:
34
+ return Result.error("Unexpected response body")
35
+
36
+ except Exception as e:
37
+ return Result.error(f"unexpected exception: {e}")
38
+
39
+ async def validate_stackoverflow(email: str) -> Result:
40
+ return await _check(email)
File without changes
@@ -0,0 +1,82 @@
1
+ import httpx
2
+ import re
3
+ from user_scanner.core.result import Result
4
+
5
+
6
+ async def _check(email: str) -> Result:
7
+ async with httpx.AsyncClient(http2=False, follow_redirects=True) as client:
8
+ try:
9
+ url1 = "https://gumroad.com/users/forgot_password/new"
10
+ headers1 = {
11
+ 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
12
+ 'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
13
+ 'Accept-Encoding': "gzip, deflate, br, zstd",
14
+ 'sec-ch-ua': '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"',
15
+ 'sec-ch-ua-mobile': "?0",
16
+ 'sec-ch-ua-platform': '"Linux"',
17
+ 'upgrade-insecure-requests': "1",
18
+ 'referer': "https://www.google.com/",
19
+ 'accept-language': "en-US,en;q=0.9"
20
+ }
21
+
22
+ res1 = await client.get(url1, headers=headers1)
23
+ html = res1.text
24
+
25
+ csrf_match = re.search(
26
+ r'authenticity_token":"([^&]+)"', html)
27
+ if not csrf_match:
28
+ csrf_match = re.search(
29
+ r'name="csrf-token" content="([^"]+)"', html)
30
+
31
+ if not csrf_match:
32
+ return Result.error("Failed to extract CSRF token")
33
+
34
+ csrf_token = csrf_match.group(1)
35
+
36
+ url2 = "https://gumroad.com/users/forgot_password"
37
+
38
+ payload = {
39
+ "user": {
40
+ "email": email
41
+ }
42
+ }
43
+
44
+ headers2 = {
45
+ 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
46
+ 'Accept': "text/html, application/xhtml+xml",
47
+ 'Accept-Encoding': "gzip, deflate, br, zstd",
48
+ 'Content-Type': "application/json",
49
+ 'sec-ch-ua-platform': '"Linux"',
50
+ 'x-csrf-token': csrf_token,
51
+ 'sec-ch-ua': '"Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"',
52
+ 'x-inertia': "true",
53
+ 'sec-ch-ua-mobile': "?0",
54
+ 'x-requested-with': "XMLHttpRequest",
55
+ 'origin': "https://gumroad.com",
56
+ 'sec-fetch-site': "same-origin",
57
+ 'sec-fetch-mode': "cors",
58
+ 'sec-fetch-dest': "empty",
59
+ 'referer': "https://gumroad.com/users/forgot_password/new",
60
+ 'accept-language': "en-US,en;q=0.9",
61
+ 'priority': "u=1, i"
62
+ }
63
+
64
+ response = await client.post(url2, json=payload, headers=headers2)
65
+
66
+ data = response.json()
67
+ flash_msg = data.get("props", {}).get(
68
+ "flash", {}).get("message", "")
69
+
70
+ if "An account does not exist" in flash_msg:
71
+ return Result.available()
72
+ elif "An account does not exist" not in flash_msg:
73
+ return Result.taken()
74
+ else:
75
+ return Result.error(f"Unexpected status: {response.status_code}")
76
+
77
+ except Exception as e:
78
+ return Result.error(f"unexpected exception: {e}")
79
+
80
+
81
+ async def validate_gumroad(email: str) -> Result:
82
+ return await _check(email)
@@ -0,0 +1,58 @@
1
+ import httpx
2
+ from user_scanner.core.result import Result
3
+
4
+
5
+ async def _check(email: str) -> Result:
6
+ async with httpx.AsyncClient(http2=False) as client:
7
+ try:
8
+ url = "https://www.patreon.com/api/auth"
9
+
10
+ params = {
11
+ 'include': "user.null",
12
+ 'fields[user]': "[]",
13
+ 'json-api-version': "1.0",
14
+ 'json-api-use-default-includes': "false"
15
+ }
16
+
17
+ payload = "{\"data\":{\"type\":\"genericPatreonApi\",\"attributes\":{\"patreon_auth\":{\"email\":\"" + email + \
18
+ "\",\"allow_account_creation\":false},\"auth_context\":\"auth\",\"ru\":\"https://www.patreon.com/home\"},\"relationships\":{}}}"
19
+
20
+ headers = {
21
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36",
22
+ 'Accept-Encoding': "identity",
23
+ 'content-type': "application/vnd.api+json",
24
+ 'origin': "https://www.patreon.com"
25
+ }
26
+
27
+ response = await client.post(
28
+ url,
29
+ params=params,
30
+ content=payload,
31
+ headers=headers
32
+ )
33
+
34
+ if response.status_code != 200:
35
+ return Result.error(f"Status {response.status_code}")
36
+
37
+ data = response.json()
38
+ next_step = data.get("data", {}).get(
39
+ "attributes", {}).get("next_auth_step")
40
+
41
+ if next_step == "password":
42
+ return Result.taken()
43
+ elif next_step == "signup":
44
+ return Result.available()
45
+ else:
46
+ return Result.error("Unexpected auth step")
47
+
48
+ except Exception as e:
49
+ return Result.error(f"unexpected exception: {e}")
50
+
51
+
52
+ async def validate_patreon(email: str) -> Result:
53
+ return await _check(email)
54
+
55
+
56
+
57
+
58
+
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,51 @@
1
+ import httpx
2
+ import json
3
+ from user_scanner.core.result import Result
4
+
5
+ async def _check(email: str) -> Result:
6
+ url = "https://replit.com/data/user/exists"
7
+
8
+ payload = {
9
+ "email": email
10
+ }
11
+
12
+ headers = {
13
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36",
14
+ 'Accept': "application/json",
15
+ 'Accept-Encoding': "identity",
16
+ 'Content-Type': "application/json",
17
+ 'sec-ch-ua-platform': "\"Android\"",
18
+ 'sec-ch-ua': "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\"",
19
+ 'sec-ch-ua-mobile': "?1",
20
+ 'x-requested-with': "XMLHttpRequest",
21
+ 'origin': "https://replit.com",
22
+ 'sec-fetch-site': "same-origin",
23
+ 'sec-fetch-mode': "cors",
24
+ 'sec-fetch-dest': "empty",
25
+ 'referer': "https://replit.com/signup",
26
+ 'accept-language': "en-US,en;q=0.9",
27
+ 'priority': "u=1, i"
28
+ }
29
+
30
+ async with httpx.AsyncClient(http2=False, timeout=5.0) as client:
31
+ try:
32
+ response = await client.post(url, content=json.dumps(payload), headers=headers)
33
+
34
+ if response.status_code == 403:
35
+ return Result.error("403 Forbidden")
36
+
37
+ data = response.json()
38
+ exists = data.get("exists")
39
+
40
+ if exists is True:
41
+ return Result.taken()
42
+ if exists is False:
43
+ return Result.available()
44
+
45
+ return Result.error("Unexpected response format")
46
+
47
+ except Exception as e:
48
+ return Result.error(str(e))
49
+
50
+ async def validate_replit(email: str) -> Result:
51
+ return await _check(email)
File without changes
@@ -0,0 +1,87 @@
1
+ import httpx
2
+ import json
3
+ from user_scanner.core.result import Result
4
+
5
+
6
+ async def _check(email: str) -> Result:
7
+ async with httpx.AsyncClient(http2=False, follow_redirects=True) as client:
8
+ try:
9
+ get_url = "https://www.spotify.com/in-en/signup"
10
+ get_headers = {
11
+ 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
12
+ '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",
13
+ 'Accept-Encoding': "identity",
14
+ 'sec-ch-ua': "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\"",
15
+ 'sec-ch-ua-mobile': "?0",
16
+ 'sec-ch-ua-platform': "\"Linux\"",
17
+ 'upgrade-insecure-requests': "1",
18
+ 'sec-fetch-site': "same-origin",
19
+ 'sec-fetch-mode': "navigate",
20
+ 'sec-fetch-user': "?1",
21
+ 'sec-fetch-dest': "document",
22
+ 'referer': "https://www.spotify.com/us/signup",
23
+ 'accept-language': "en-US,en;q=0.9",
24
+ 'priority': "u=0, i"
25
+ }
26
+
27
+ await client.get(get_url, headers=get_headers)
28
+
29
+ post_url = "https://spclient.wg.spotify.com/signup/public/v2/account/validate"
30
+
31
+ payload = {
32
+ "fields": [
33
+ {
34
+ "field": "FIELD_EMAIL",
35
+ "value": email
36
+ }
37
+ ],
38
+ "client_info": {
39
+ "api_key": "a1e486e2729f46d6bb368d6b2bcda326",
40
+ "app_version": "v2",
41
+ "capabilities": [1],
42
+ "installation_id": "3740cfb5-c76f-4ae9-9a94-f0989d7ae5a4",
43
+ "platform": "www",
44
+ "client_id": ""
45
+ },
46
+ "tracking": {
47
+ "creation_flow": "",
48
+ "creation_point": "https://www.spotify.com/us/signup",
49
+ "referrer": "",
50
+ "origin_vertical": "",
51
+ "origin_surface": ""
52
+ }
53
+ }
54
+
55
+ post_headers = {
56
+ 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
57
+ 'Accept-Encoding': "identity",
58
+ 'Content-Type': "application/json",
59
+ 'sec-ch-ua-platform': "\"Linux\"",
60
+ 'sec-ch-ua': "\"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"144\", \"Google Chrome\";v=\"144\"",
61
+ 'sec-ch-ua-mobile': "?0",
62
+ 'origin': "https://www.spotify.com",
63
+ 'sec-fetch-site': "same-site",
64
+ 'sec-fetch-mode': "cors",
65
+ 'sec-fetch-dest': "empty",
66
+ 'referer': "https://www.spotify.com/",
67
+ 'accept-language': "en-US,en;q=0.9",
68
+ 'priority': "u=1, i"
69
+ }
70
+
71
+ response = await client.post(post_url, content=json.dumps(payload), headers=post_headers)
72
+
73
+ data = response.json()
74
+
75
+ if "error" in data and "already_exists" in data["error"]:
76
+ return Result.taken()
77
+ elif "success" in data:
78
+ return Result.available()
79
+
80
+ return Result.error(f"Unexpected error [{response.status_code}], report it via GitHub issues")
81
+
82
+ except Exception as e:
83
+ return Result.error(f"Exception: {e}")
84
+
85
+
86
+ async def validate_spotify(email: str) -> Result:
87
+ return await _check(email)
File without changes
@@ -0,0 +1,47 @@
1
+ import httpx
2
+ from user_scanner.core.result import Result
3
+
4
+ async def _check(email: str) -> Result:
5
+ async with httpx.AsyncClient(http2=True) as client:
6
+ try:
7
+ url = "https://www.chess.com/rpc/chesscom.authentication.v1.EmailValidationService/Validate"
8
+
9
+ payload = {
10
+ "email": email
11
+ }
12
+
13
+ headers = {
14
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Mobile Safari/537.36",
15
+ 'Accept': "application/json, text/plain, */*",
16
+ 'Accept-Encoding': "identity",
17
+ 'Content-Type': "application/json",
18
+ 'sec-ch-ua-platform': '"Android"',
19
+ 'accept-language': "en_US",
20
+ 'connect-protocol-version': "1",
21
+ 'origin': "https://www.chess.com",
22
+ 'sec-fetch-site': "same-origin",
23
+ 'sec-fetch-mode': "cors",
24
+ 'referer': "https://www.chess.com/register",
25
+ 'priority': "u=1, i"
26
+ }
27
+
28
+ response = await client.post(url, json=payload, headers=headers)
29
+
30
+ if response.status_code != 200:
31
+ return Result.error(f"Status {response.status_code}, report is via GitHub issues")
32
+
33
+ data = response.json()
34
+ status = data.get("status")
35
+
36
+ if status == "EMAIL_STATUS_TAKEN":
37
+ return Result.taken()
38
+ elif status == "EMAIL_STATUS_AVAILABLE":
39
+ return Result.available()
40
+ else:
41
+ return Result.error(f"Unknown status: {status}, report is via GitHub issues")
42
+
43
+ except Exception as e:
44
+ return Result.error(f"unexpected exception: {e}")
45
+
46
+ async def validate_chess_com(email: str) -> Result:
47
+ return await _check(email)
@@ -15,10 +15,10 @@ async def _check(email):
15
15
  "origin": "https://mastodon.social"
16
16
  }
17
17
 
18
- async with httpx.AsyncClient(http2=True, headers=headers, follow_redirects=False) as client:
18
+ async with httpx.AsyncClient(http2=True, headers=headers, follow_redirects=True) as client:
19
19
  try:
20
20
  initial_resp = await client.get(signup_url)
21
- if initial_resp.status_code != 200:
21
+ if initial_resp.status_code not in [200, 302]:
22
22
  return Result.error(f"Failed to access signup page: {initial_resp.status_code}")
23
23
 
24
24
  token_match = re.search(
@@ -43,7 +43,9 @@ async def _check(email):
43
43
  res_status = response.status_code
44
44
  if "has already been taken" in res_text:
45
45
  return Result.taken()
46
- elif "has already been taken" not in res_text and res_status == 200:
46
+ elif "registration attempt has been blocked" in res_text:
47
+ return Result.error("Your IP has been flagged by mastodon, try after some time")
48
+ elif "has already been taken" not in res_text and res_status in [200, 302]:
47
49
  return Result.available()
48
50
  elif res_status == 429:
49
51
  return Result.error("Rate limited, use '-d' flag to avoid bot detection")
File without changes
@@ -0,0 +1,42 @@
1
+ import difflib
2
+ import re
3
+
4
+ from user_scanner.core.orchestrator import generic_validate
5
+ from user_scanner.core.result import Result
6
+
7
+
8
+ def validate_vinted(user: str):
9
+ user = user.lower().strip()
10
+
11
+ url = f"https://www.vinted.pt/member/general/search?search_text={user}"
12
+
13
+ if not re.match(r"^[a-zA-Z0-9_.-]+$", user):
14
+ return Result.error(
15
+ "Usernames can only contain letters, numbers, underscores, periods and dashes"
16
+ )
17
+
18
+ if user.startswith(("_", "-", ".")) or user.endswith(("_", "-", ".")):
19
+ return Result.error("Cannot start/end with a special character")
20
+
21
+ def process(response):
22
+ if response.status_code != 200:
23
+ return Result.error("Invalid status code")
24
+
25
+ pattern = r"\"login\\\":\\\"([A-Za-z0-9_.-]+)"
26
+ search = re.findall(pattern, response.text)
27
+
28
+ if len(search) == 0:
29
+ return Result.available()
30
+ elif user not in search:
31
+ closest = difflib.get_close_matches(user, search, n=1)
32
+ msg = f"closest: {closest[0]}" if closest else None
33
+ return Result.available(msg)
34
+ else:
35
+ return Result.taken()
36
+
37
+ return generic_validate(url, process)
38
+
39
+
40
+ if __name__ == "__main__":
41
+ user = input("Username?: ").strip()
42
+ validate_vinted(user).show()
user_scanner/version.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.1.0",
2
+ "version": "1.1.0.2",
3
3
  "version_type": "pypi"
4
4
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: user-scanner
3
- Version: 1.1.0
3
+ Version: 1.1.0.2
4
4
  Summary: Check username availability across multiple popular platforms
5
5
  Keywords: username,checker,availability,social,tech,python,user-scanner
6
6
  Author-email: Kaif <kafcodec@gmail.com>
@@ -16,7 +16,7 @@ Project-URL: Homepage, https://github.com/kaifcodec/user-scanner
16
16
 
17
17
  ![User Scanner Logo](https://github.com/user-attachments/assets/49ec8d24-665b-4115-8525-01a8d0ca2ef4)
18
18
  <p align="center">
19
- <img src="https://img.shields.io/badge/Version-1.0.10.1-blueviolet?style=for-the-badge&logo=github" />
19
+ <img src="https://img.shields.io/badge/Version-1.1.0.2-blueviolet?style=for-the-badge&logo=github" />
20
20
  <img src="https://img.shields.io/github/issues/kaifcodec/user-scanner?style=for-the-badge&logo=github" />
21
21
  <img src="https://img.shields.io/badge/Tested%20on-Termux-black?style=for-the-badge&logo=termux" />
22
22
  <img src="https://img.shields.io/badge/Tested%20on-Windows-cyan?style=for-the-badge&logo=Windows" />
@@ -26,9 +26,7 @@ Project-URL: Homepage, https://github.com/kaifcodec/user-scanner
26
26
 
27
27
  ---
28
28
 
29
- ### ⚠️ Email OSINT mode had not been implemented yet, still in progress
30
-
31
- A powerful *Email OSINT tool* that checks if a specific email is registered on various sites, combined with *username scanning* — 2-in-1 solution.
29
+ A powerful *Email OSINT tool* that checks if a specific email is registered on various sites, combined with *username scanning* for branding or OSINT — 2-in-1 tool.
32
30
 
33
31
  Perfect for fast, accurate and lightweight email OSINT
34
32
 
@@ -1,34 +1,45 @@
1
1
  user_scanner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- user_scanner/__main__.py,sha256=upuAt-VZO08YzaA4paD7ArUy2S2K12wdTqlNxn107eY,10762
3
- user_scanner/version.json,sha256=oTUn4qlwwbH4RAqsIExmaGV6igJwBW8xmvRpphKh6Bc,47
2
+ user_scanner/__main__.py,sha256=6ecuWBMKZQLwmz7hSYcOk2uwSxj3jpYqYVI-2ti0zmA,10848
3
+ user_scanner/config.json,sha256=WtVnrpPxhGUBmx_dBShO3R0NnipVBVz3BfzlEPO5Amc,28
4
+ user_scanner/version.json,sha256=AraEBiS8nb-YghE3619KvezE-vSVK9A4TJRdwF7v92o,49
4
5
  user_scanner/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
6
  user_scanner/cli/banner.py,sha256=3b4PIggnJrmxF4DfbuPMqSavpwNl0m5uedaOL2SXN3o,766
6
7
  user_scanner/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- user_scanner/core/email_orchestrator.py,sha256=EMQGqvk4GqaXQqCSTOYdveRuu63bLf71r6TTrfSrups,2368
8
+ user_scanner/core/email_orchestrator.py,sha256=E813-i_acOEEb9Q_n1aQANBkDrlB6dglUjzVrC0-L-g,2349
8
9
  user_scanner/core/formatter.py,sha256=CMwyR6PuP15HqyS6oWe40ye_4dne14lrIj7_5T7rla4,725
9
10
  user_scanner/core/helpers.py,sha256=g_WMbEJvT98TA4Db9c-LPIDl6ExjLQ1dSE3cIyKRUos,6406
10
- user_scanner/core/orchestrator.py,sha256=OLC4JmBJm1ZzwsDUt6Kqtcc2BENs3fP0iqbTT1nS3KU,4460
11
- user_scanner/core/result.py,sha256=HxzsaQrfvZ-PxDJknAENIaF-BekfyAAV2Mf8J382zjg,4597
11
+ user_scanner/core/orchestrator.py,sha256=uYs6WBUNPo8fFGZzenwCc9DB246zNVEC_DrlZgVxqNo,4485
12
+ user_scanner/core/result.py,sha256=VCIJvte_SXCq59j2o3wQsQYpmXjHTrGKy8Ayn211Rkc,4982
12
13
  user_scanner/core/version.py,sha256=k1_KTZdRLKBAxp8_PtOhTAtj8mBO_AUnUGdqI4epypY,855
13
14
  user_scanner/email_scan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
15
  user_scanner/email_scan/adult/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- user_scanner/email_scan/adult/pornhub.py,sha256=cc_G5bclFl8QWU43ohM7SgilWk0RYmpUC7NHsw9Z0Bs,2135
16
+ user_scanner/email_scan/adult/pornhub.py,sha256=8OF-0h_FntUv1Pi0eT801Bhn6Uhd1dh_fD4lfbKV6OY,2134
17
+ user_scanner/email_scan/adult/sexvid.py,sha256=Hn-C1kelSHaeh6f6q6xzy8lm0PNs-7saLNsBaD-TJmc,1624
16
18
  user_scanner/email_scan/adult/xnxx.py,sha256=WiKn4Vkc5FC1HXry9N8SN-dsTdm1qq0NS6KUcPRmMMY,1728
17
19
  user_scanner/email_scan/adult/xvideos.py,sha256=tx0PZOZ66KHDmrnvd1RABCnqCUIdKfCPgAbogBtX69A,1769
18
- user_scanner/email_scan/dev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ user_scanner/email_scan/community/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ user_scanner/email_scan/community/stackoverflow.py,sha256=Eq3kTXohmp2XodVLaR8ujWWCTjkTm1HXy-Yew4LzOGA,1608
22
+ user_scanner/email_scan/creator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ user_scanner/email_scan/creator/gumroad.py,sha256=wJfY_CPT2aiGShuL73XYPw_Oni7sEYn8nQ7avc0mrJg,3354
24
+ user_scanner/email_scan/creator/patreon.py,sha256=1A3tPNAyQ6WDLU_XY6PJUYe4FIVWzlALyEgOWzlZ-bE,1904
25
+ user_scanner/email_scan/dev/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
19
26
  user_scanner/email_scan/dev/bitbucket.py,sha256=oR_fSug_Aft4fy8Lu8r0VO737b8ZlfcC_D3GMbcUF0k,1352
20
27
  user_scanner/email_scan/dev/github.py,sha256=JBSSN9kv3IQxKG0tmemUdSHwfQDu53dHC4i9oCwDqow,3160
21
28
  user_scanner/email_scan/dev/huggingface.py,sha256=GjFNkuVZ_8eFgs9OrFakhiEb8pVRwEJO-tyx32IQMns,1229
22
- user_scanner/email_scan/dev/.ruff_cache/.gitignore,sha256=njpg8ebsSuYCFcEdVLFxOSdF7CXp3e1DPVvZITY68xY,35
23
- user_scanner/email_scan/dev/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
24
- user_scanner/email_scan/dev/.ruff_cache/0.14.10/10328336453267387919,sha256=LEcpRIkQ-C6pcMhwLtJIv41263y2EjXHzr0r530xCtc,250
29
+ user_scanner/email_scan/dev/replit.py,sha256=Jj4YA1OIibpZ17rvJbccGsJDDnOXCoiH3ybhIwj5d3E,1697
30
+ user_scanner/email_scan/entertainment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ user_scanner/email_scan/entertainment/spotify.py,sha256=jzFa9p1IWnAYDK7k7NTJ7ipmrGqmK_PVrUQ4otjldkU,3659
32
+ user_scanner/email_scan/entertainment/.ruff_cache/.gitignore,sha256=njpg8ebsSuYCFcEdVLFxOSdF7CXp3e1DPVvZITY68xY,35
33
+ user_scanner/email_scan/entertainment/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
34
+ user_scanner/email_scan/entertainment/.ruff_cache/0.14.10/14677874048998292530,sha256=cIbkvYriz3xNK--m83EvzAesaxgXZBfKUoxnx9O6cuo,173
35
+ user_scanner/email_scan/gaming/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ user_scanner/email_scan/gaming/chess_com.py,sha256=EJF3OTxYtzDC-F4OAVfT0mRWUopmNZdwdYChw97VpE4,1841
25
37
  user_scanner/email_scan/shopping/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- user_scanner/email_scan/shopping/ebay.py.lock,sha256=UTvqqwdIPPp3Pt8NCwNwHC3xNghexQc1HcNLXf9s4wQ,4326
27
38
  user_scanner/email_scan/shopping/flipkart.py,sha256=wMQJ1VIawhM6W0UQCThcIUtaYN7QeexvJSRSeXS4l04,1879
28
39
  user_scanner/email_scan/social/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
40
  user_scanner/email_scan/social/facebook.py,sha256=dnCDZfqRYLXDT7GjJQ-bSqGfC-rO8G9O7pu8dUse7Nw,4475
30
41
  user_scanner/email_scan/social/instagram.py,sha256=qGDub3d4pSY_KW4aNYDQOGVNSrmWQkZWMHadCFkDa64,2022
31
- user_scanner/email_scan/social/mastodon.py,sha256=F4E1kJ6148N3C66uFvJu_fhx7r7LcMau6wDIkdRbxlM,2294
42
+ user_scanner/email_scan/social/mastodon.py,sha256=Qm13Nl_9j_7sHlc4wN6QF9jgfLlaxbOVgacvXK3hLRY,2478
32
43
  user_scanner/email_scan/social/x.py,sha256=WoHaecbR1qGte-mwyODsY0YGNf-iRZyGS7s9fw0SQgg,1475
33
44
  user_scanner/user_scan/community/__init__.py,sha256=5EzlM991pJqvqIRc05_QV5BureJZ7wiCRm1AyEY6pms,12
34
45
  user_scanner/user_scan/community/coderlegion.py,sha256=W_bdjzdFPRgUrNFFlylvToSJ4AzaFCtTsUy_MRVDdSo,451
@@ -70,6 +81,8 @@ user_scanner/user_scan/gaming/monkeytype.py,sha256=IWt_0sXPaiTfKVpYVNW9KLMGtDzU5
70
81
  user_scanner/user_scan/gaming/osu.py,sha256=2Xs1iM0CJ-3dNHu4tyF50_s0Ei_1mA5Zd6D6M5RmiVg,448
71
82
  user_scanner/user_scan/gaming/roblox.py,sha256=5q8vWlO5mdUZpQg_rx3ewBrDOHnLprSHJj7uEJ2S934,1813
72
83
  user_scanner/user_scan/gaming/steam.py,sha256=l8xk_p9aiYQWCPoogQnO1iwkfojPhg6yd76OZHhKN50,740
84
+ user_scanner/user_scan/shopping/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
+ user_scanner/user_scan/shopping/vinted.py,sha256=YwuHl1LFJHdqmQ2OEjFHjh2Bt8Ce5m4pq67aDGTWk_U,1299
73
86
  user_scanner/user_scan/social/__init__.py,sha256=jaCkFwX1uYtF0ENifVwF8OfHrYYUTm64B9wlBq9BBfQ,9
74
87
  user_scanner/user_scan/social/bluesky.py,sha256=11Y_vRj3txEDQqoD0iANgSWVSB8L87OotPQZquhneR0,1994
75
88
  user_scanner/user_scan/social/discord.py,sha256=KA7Uw8RBuid-YZZglIKQwWbg8PIKdrMwXP3fKH3c-go,1180
@@ -87,8 +100,8 @@ user_scanner/user_scan/social/youtube.py,sha256=UPu584teg75P7FT05RFG3nobbHgPmzjr
87
100
  user_scanner/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
101
  user_scanner/utils/update.py,sha256=Rj3kLuUrQ-LlKGB7bkndqVjj0IUqugbDSj2SUrPRidE,936
89
102
  user_scanner/utils/updater_logic.py,sha256=tl6kbKL02DrP-R1dkQWhHr12juVDgkJZZvKAfbI1ruU,2381
90
- user_scanner-1.1.0.dist-info/entry_points.txt,sha256=XqU3kssYZ0vXaPy5qYUOTCu4u-48Xie7QWFpBCYc7Nc,59
91
- user_scanner-1.1.0.dist-info/licenses/LICENSE,sha256=XH1QyQG68zo1opDIZHTHcTAbe9XMzewvTaFTukcN9vc,1061
92
- user_scanner-1.1.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
93
- user_scanner-1.1.0.dist-info/METADATA,sha256=eHPKnr6zczLO7pagkzEm0LFxyveyfSR3a9RVbIULioA,8601
94
- user_scanner-1.1.0.dist-info/RECORD,,
103
+ user_scanner-1.1.0.2.dist-info/entry_points.txt,sha256=XqU3kssYZ0vXaPy5qYUOTCu4u-48Xie7QWFpBCYc7Nc,59
104
+ user_scanner-1.1.0.2.dist-info/licenses/LICENSE,sha256=XH1QyQG68zo1opDIZHTHcTAbe9XMzewvTaFTukcN9vc,1061
105
+ user_scanner-1.1.0.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
106
+ user_scanner-1.1.0.2.dist-info/METADATA,sha256=IlmLoZS92pZRb_kL0-oDnr2Diyd8aTjpLg3Da0D6V_E,8542
107
+ user_scanner-1.1.0.2.dist-info/RECORD,,
@@ -1,97 +0,0 @@
1
- import httpx
2
- import re
3
- import asyncio
4
- from user_scanner.core.result import Result
5
-
6
- async def _check(email: str) -> Result:
7
- async with httpx.AsyncClient(http2=True, follow_redirects=False) as client:
8
- try:
9
- # --- Step 1: GET landing page ---
10
- url1 = "https://signin.ebay.com/signin/"
11
- headers1 = {
12
- 'host': 'signin.ebay.com',
13
- 'sec-ch-ua': '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
14
- 'sec-ch-ua-mobile': '?1',
15
- 'sec-ch-ua-platform': '"Android"',
16
- 'upgrade-insecure-requests': '1',
17
- 'user-agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36',
18
- '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',
19
- 'sec-fetch-site': 'cross-site',
20
- 'sec-fetch-mode': 'navigate',
21
- 'sec-fetch-user': '?1',
22
- 'sec-fetch-dest': 'document',
23
- 'referer': 'https://www.google.com/',
24
- 'accept-encoding': 'gzip, deflate, br, zstd',
25
- 'accept-language': 'en-US,en;q=0.9',
26
- 'priority': 'u=0, i'
27
- }
28
-
29
- res1 = await client.get(url1, headers=headers1)
30
- html = res1.text
31
-
32
- # Type-safe extraction for mypy
33
- srt_match = re.search(r'name=srt\s+id=srt\s+value=([^\s>]+)', html)
34
- if not srt_match:
35
- # Quoted fallback
36
- srt_match = re.search(r'name="srt".*?value="([^"]+)"', html)
37
-
38
- if not srt_match:
39
- return Result.error("Failed to extract srt")
40
-
41
- srt = srt_match.group(1)
42
-
43
- # --- Step 2: Identification POST ---
44
- url2 = "https://signin.ebay.com/signin/srv/identifer"
45
-
46
- payload = {
47
- 'identifier': email,
48
- 'srt': srt,
49
- 'webAuthNOptedInKeys': "",
50
- 'webAuthNCredCheck': "true",
51
- 'useCase': "AUTH",
52
- 'flowType': "PSI"
53
- }
54
-
55
- headers2 = {
56
- 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
57
- 'Accept-Encoding': "gzip, deflate, br, zstd",
58
- 'x-ebay-requested-with': "XMLHttpRequest",
59
- 'sec-ch-ua-platform': '"Android"',
60
- 'x-ebay-c-trackable-id': "01KFK31DZY0KKB2Y0HA1B00DWH",
61
- 'sec-ch-ua': '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
62
- 'sec-ch-ua-model': '"I2404"',
63
- 'sec-ch-ua-mobile': "?1",
64
- 'x-requested-with': "XMLHttpRequest",
65
- 'sec-ch-ua-full-version': '"143.0.7499.192"',
66
- 'sec-ch-ua-platform-version': '"15.0.0"',
67
- 'origin': "https://signin.ebay.com",
68
- 'sec-fetch-site': "same-origin",
69
- 'sec-fetch-mode': "cors",
70
- 'sec-fetch-dest': "empty",
71
- 'referer': "https://signin.ebay.com/signin/",
72
- 'accept-language': "en-US,en;q=0.9",
73
- 'priority': "u=1, i"
74
- }
75
-
76
- # Optional: Short sleep to mimic human timing before POST
77
- await asyncio.sleep(1)
78
-
79
- response = await client.post(url2, data=payload, headers=headers2)
80
-
81
- if response.status_code == 483:
82
- return Result.error("Security challenge (483)")
83
-
84
- data = response.json()
85
-
86
- if data.get("signinErrorUpdate") is True and "couldn't find this eBay account" in str(data.get("errorMsg", "")):
87
- return Result.available()
88
- elif data.get("signinErrorUpdate") is False or "publicUserId" in data:
89
- return Result.taken()
90
- else:
91
- return Result.error("unexpected.... Report it via github issues...")
92
-
93
- except Exception as e:
94
- return Result.error(f"unexpected exception: {e}")
95
-
96
- async def validate_ebay(email: str) -> Result:
97
- return await _check(email)