user-scanner 1.0.6.0__py3-none-any.whl → 1.0.8.0__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 (35) hide show
  1. user_scanner/__main__.py +63 -44
  2. user_scanner/core/orchestrator.py +68 -36
  3. user_scanner/core/result.py +79 -0
  4. user_scanner/creator/hashnode.py +8 -13
  5. user_scanner/creator/itch_io.py +0 -5
  6. user_scanner/creator/medium.py +7 -12
  7. user_scanner/creator/patreon.py +0 -6
  8. user_scanner/creator/producthunt.py +2 -19
  9. user_scanner/dev/github.py +19 -2
  10. user_scanner/dev/gitlab.py +4 -3
  11. user_scanner/dev/huggingface.py +19 -0
  12. user_scanner/dev/npmjs.py +9 -25
  13. user_scanner/donation/buymeacoffee.py +0 -3
  14. user_scanner/gaming/chess_com.py +4 -9
  15. user_scanner/gaming/minecraft.py +0 -5
  16. user_scanner/gaming/monkeytype.py +4 -10
  17. user_scanner/gaming/osu.py +0 -5
  18. user_scanner/gaming/roblox.py +14 -9
  19. user_scanner/gaming/steam.py +6 -9
  20. user_scanner/social/bluesky.py +7 -13
  21. user_scanner/social/discord.py +6 -9
  22. user_scanner/social/mastodon.py +0 -6
  23. user_scanner/social/pinterest.py +4 -4
  24. user_scanner/social/reddit.py +4 -3
  25. user_scanner/social/soundcloud.py +43 -0
  26. user_scanner/social/telegram.py +6 -6
  27. user_scanner/social/x.py +10 -20
  28. user_scanner/social/youtube.py +2 -18
  29. user_scanner/version.json +1 -1
  30. {user_scanner-1.0.6.0.dist-info → user_scanner-1.0.8.0.dist-info}/METADATA +27 -6
  31. user_scanner-1.0.8.0.dist-info/RECORD +58 -0
  32. user_scanner-1.0.6.0.dist-info/RECORD +0 -55
  33. {user_scanner-1.0.6.0.dist-info → user_scanner-1.0.8.0.dist-info}/WHEEL +0 -0
  34. {user_scanner-1.0.6.0.dist-info → user_scanner-1.0.8.0.dist-info}/entry_points.txt +0 -0
  35. {user_scanner-1.0.6.0.dist-info → user_scanner-1.0.8.0.dist-info}/licenses/LICENSE +0 -0
user_scanner/__main__.py CHANGED
@@ -1,38 +1,22 @@
1
1
  import argparse
2
+ import time
2
3
  import re
3
- from user_scanner.core.orchestrator import run_checks, load_modules
4
+ from user_scanner.core.orchestrator import run_checks, load_modules , generate_permutations, load_categories
4
5
  from colorama import Fore, Style
5
6
  from .cli import banner
6
7
  from .cli.banner import print_banner
7
8
 
8
- CATEGORY_MAPPING = {
9
- "dev": "dev",
10
- "social": "social",
11
- "creator": "creator",
12
- "community": "community",
13
- "gaming": "gaming",
14
- "donation": "donation"
15
- }
16
-
9
+ MAX_PERMUTATIONS_LIMIT = 100 # To prevent excessive generation
17
10
 
18
11
  def list_modules(category=None):
19
- from user_scanner import dev, social, creator, community, gaming, donation
20
- packages = {
21
- "dev": dev,
22
- "social": social,
23
- "creator": creator,
24
- "community": community,
25
- "gaming": gaming,
26
- "donation": donation
27
- }
28
-
29
- categories_to_list = [category] if category else packages.keys()
12
+ categories = load_categories()
13
+ categories_to_list = [category] if category else categories.keys()
30
14
 
31
15
  for cat_name in categories_to_list:
32
- package = packages[cat_name]
33
- modules = load_modules(package)
16
+ path = categories[cat_name]
17
+ modules = load_modules(path)
34
18
  print(Fore.MAGENTA +
35
- f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
19
+ f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
36
20
  for module in modules:
37
21
  site_name = module.__name__.split(".")[-1]
38
22
  print(f" - {site_name}")
@@ -47,7 +31,7 @@ def main():
47
31
  "-u", "--username", help="Username to scan across platforms"
48
32
  )
49
33
  parser.add_argument(
50
- "-c", "--category", choices=CATEGORY_MAPPING.keys(),
34
+ "-c", "--category", choices=load_categories().keys(),
51
35
  help="Scan all platforms in a category"
52
36
  )
53
37
  parser.add_argument(
@@ -59,16 +43,28 @@ def main():
59
43
  parser.add_argument(
60
44
  "-v", "--verbose", action="store_true", help="Enable verbose output"
61
45
  )
62
-
46
+
47
+ parser.add_argument(
48
+ "-p", "--permute",type=str,help="Generate username permutations using a string pattern (e.g -p 234)"
49
+ )
50
+ parser.add_argument(
51
+ "-s", "--stop",type=int,default=MAX_PERMUTATIONS_LIMIT,help="Limit the number of username permutations generated"
52
+ )
53
+
54
+ parser.add_argument(
55
+ "-d", "--delay",type=float,default=0,help="Delay in seconds between requests (recommended: 1-2 seconds)"
56
+ )
57
+
63
58
  args = parser.parse_args()
64
-
65
- if args.module and "." in args.module:
66
- args.module = args.module.replace(".", "_")
67
-
59
+
68
60
  if args.list:
69
61
  list_modules(args.category)
70
62
  return
71
-
63
+
64
+ if not args.username:
65
+ parser.print_help()
66
+ return
67
+
72
68
  # Special username checks before run
73
69
  if (args.module == "x" or args.category == "social"):
74
70
  if re.search(r"[^a-zA-Z0-9._-]", args.username):
@@ -78,37 +74,60 @@ def main():
78
74
  if re.search(r"[^a-zA-Z0-9\.-]", args.username):
79
75
  print(
80
76
  Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. Bluesky will throw error. (Supported: only hyphens and digits)" + Style.RESET_ALL + "\n")
81
- if not args.username:
82
- parser.print_help()
83
- return
84
- else:
85
- print_banner()
77
+ print_banner()
78
+
79
+ if args.permute and args.delay == 0:
80
+ print(
81
+ Fore.YELLOW
82
+ + "[!] Warning: You're generating multiple usernames with NO delay between requests. "
83
+ "This may trigger rate limits or IP bans. Use --delay 1 or higher. (Use only if the sites throw errors otherwise ignore)\n"
84
+ + Style.RESET_ALL)
85
+
86
+ usernames = [args.username] # Default single username list
87
+
88
+ #Added permutation support , generate all possible permutation of given sequence.
89
+ if args.permute:
90
+ usernames = generate_permutations(args.username, args.permute , args.stop)
91
+ print(Fore.CYAN + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
92
+
93
+
94
+
95
+ if args.module and "." in args.module:
96
+ args.module = args.module.replace(".", "_")
86
97
 
87
- from user_scanner import dev, social, creator, community, gaming, donation
88
98
 
89
99
  if args.module:
90
100
  # Single module search across all categories
91
- packages = [dev, social, creator, community, gaming, donation]
92
101
  found = False
93
- for package in packages:
94
- modules = load_modules(package)
102
+ for cat_path in load_categories().values():
103
+ modules = load_modules(cat_path)
95
104
  for module in modules:
96
105
  site_name = module.__name__.split(".")[-1]
97
106
  if site_name.lower() == args.module.lower():
98
107
  from user_scanner.core.orchestrator import run_module_single
99
- run_module_single(module, args.username)
108
+ for name in usernames: # <-- permutation support here
109
+ run_module_single(module, name)
110
+ if args.delay > 0:
111
+ time.sleep(args.delay)
100
112
  found = True
101
113
  if not found:
102
114
  print(
103
115
  Fore.RED + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
104
116
  elif args.category:
105
117
  # Category-wise scan
106
- category_package = eval(CATEGORY_MAPPING[args.category])
118
+ category_package = load_categories().get(args.category)
107
119
  from user_scanner.core.orchestrator import run_checks_category
108
- run_checks_category(category_package, args.username, args.verbose)
120
+
121
+ for name in usernames: # <-- permutation support here
122
+ run_checks_category(category_package, name, args.verbose)
123
+ if args.delay > 0:
124
+ time.sleep(args.delay)
109
125
  else:
110
126
  # Full scan
111
- run_checks(args.username)
127
+ for name in usernames:
128
+ run_checks(name)
129
+ if args.delay > 0:
130
+ time.sleep(args.delay)
112
131
 
113
132
 
114
133
  if __name__ == "__main__":
@@ -1,28 +1,43 @@
1
1
  import importlib
2
- import pkgutil
3
2
  from colorama import Fore, Style
4
3
  import threading
5
-
4
+ from itertools import permutations
6
5
  import httpx
7
- from httpx import ConnectError, TimeoutException
6
+ from pathlib import Path
7
+ from user_scanner.core.result import Result, AnyResult
8
+ from typing import Callable, Dict, List
8
9
 
9
10
  lock = threading.Condition()
10
11
  # Basically which thread is the one to print
11
12
  print_queue = 0
12
13
 
13
14
 
14
- def load_modules(package):
15
-
15
+ def load_modules(category_path: Path):
16
16
  modules = []
17
- for _, name, _ in pkgutil.iter_modules(package.__path__, package.__name__ + "."):
18
- try:
19
- module = importlib.import_module(name)
20
- modules.append(module)
21
- except Exception as e:
22
- print(f"Failed to import {name}: {e}")
17
+ for file in category_path.glob("*.py"):
18
+ if file.name == "__init__.py":
19
+ continue
20
+ spec = importlib.util.spec_from_file_location(file.stem, str(file))
21
+ module = importlib.util.module_from_spec(spec)
22
+ spec.loader.exec_module(module)
23
+
24
+ modules.append(module)
23
25
  return modules
24
26
 
25
27
 
28
+ def load_categories() -> Dict[str, Path]:
29
+ root = Path(__file__).resolve().parent.parent # Should be user_scanner
30
+ categories = {}
31
+
32
+ for subfolder in root.iterdir():
33
+ if subfolder.is_dir() and \
34
+ not subfolder.name.lower() in ["cli", "utils", "core"] and \
35
+ not "__" in subfolder.name: # Removes __pycache__
36
+ categories[subfolder.name] = subfolder.resolve()
37
+
38
+ return categories
39
+
40
+
26
41
  def worker_single(module, username, i):
27
42
  global print_queue
28
43
 
@@ -36,12 +51,17 @@ def worker_single(module, username, i):
36
51
  if func:
37
52
  try:
38
53
  result = func(username)
54
+ reason = ""
55
+
56
+ if isinstance(result, Result) and result.has_reason():
57
+ reason = f" ({result.get_reason()})"
58
+
39
59
  if result == 1:
40
- output = f" {Fore.GREEN}[✔] {site_name}: Available{Style.RESET_ALL}"
60
+ output = f" {Fore.GREEN}[✔] {site_name} ({username}): Available{Style.RESET_ALL}"
41
61
  elif result == 0:
42
- output = f" {Fore.RED}[✘] {site_name}: Taken{Style.RESET_ALL}"
62
+ output = f" {Fore.RED}[✘] {site_name} ({username}): Taken{Style.RESET_ALL}"
43
63
  else:
44
- output = f" {Fore.YELLOW}[!] {site_name}: Error{Style.RESET_ALL}"
64
+ output = f" {Fore.YELLOW}[!] {site_name} ({username}): Error{reason}{Style.RESET_ALL}"
45
65
  except Exception as e:
46
66
  output = f" {Fore.YELLOW}[!] {site_name}: Exception - {e}{Style.RESET_ALL}"
47
67
  else:
@@ -62,11 +82,11 @@ def run_module_single(module, username):
62
82
  worker_single(module, username, print_queue)
63
83
 
64
84
 
65
- def run_checks_category(package, username, verbose=False):
85
+ def run_checks_category(category_path:Path, username:str, verbose=False):
66
86
  global print_queue
67
87
 
68
- modules = load_modules(package)
69
- category_name = package.__name__.split('.')[-1].capitalize()
88
+ modules = load_modules(category_path)
89
+ category_name = category_path.stem.capitalize()
70
90
  print(f"{Fore.MAGENTA}== {category_name} SITES =={Style.RESET_ALL}")
71
91
 
72
92
  print_queue = 0
@@ -82,18 +102,14 @@ def run_checks_category(package, username, verbose=False):
82
102
 
83
103
 
84
104
  def run_checks(username):
85
- from user_scanner import dev, social, creator, community, gaming, donation
86
-
87
- packages = [dev, social, creator, community, gaming, donation]
88
-
89
105
  print(f"\n{Fore.CYAN} Checking username: {username}{Style.RESET_ALL}\n")
90
106
 
91
- for package in packages:
92
- run_checks_category(package, username)
107
+ for category_path in load_categories().values():
108
+ run_checks_category(category_path, username)
93
109
  print()
94
110
 
95
111
 
96
- def make_get_request(url, **kwargs):
112
+ def make_get_request(url: str, **kwargs) -> httpx.Response:
97
113
  """Simple wrapper to **httpx.get** that predefines headers and timeout"""
98
114
  if not "headers" in kwargs:
99
115
  kwargs["headers"] = {
@@ -110,39 +126,55 @@ def make_get_request(url, **kwargs):
110
126
  return httpx.get(url, **kwargs)
111
127
 
112
128
 
113
- def generic_validate(url, func, **kwargs):
129
+ def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kwargs) -> AnyResult:
114
130
  """
115
131
  A generic validate function that makes a request and executes the provided function on the response.
116
132
  """
117
133
  try:
118
134
  response = make_get_request(url, **kwargs)
119
135
  return func(response)
120
- except (ConnectError, TimeoutException):
121
- return 2
122
- except Exception:
123
- return 2
136
+ except Exception as e:
137
+ return Result.error(e)
124
138
 
125
139
 
126
- def status_validate(url, available, taken, **kwargs):
140
+ def status_validate(url: str, available: int | List[int], taken: int | List[int], **kwargs) -> Result:
127
141
  """
128
142
  Function that takes a **url** and **kwargs** for the request and
129
143
  checks if the request status matches the availabe or taken.
130
144
  **Available** and **Taken** must either be whole numbers or lists of whole numbers.
131
145
  """
132
- def inner(response):
146
+ def inner(response: httpx.Response):
133
147
  # Checks if a number is equal or is contained inside
134
148
  def contains(a, b): return (isinstance(a, list) and b in a) or (a == b)
135
-
136
149
  status = response.status_code
137
150
  available_value = contains(available, status)
138
151
  taken_value = contains(taken, status)
139
152
 
140
153
  if available_value and taken_value:
141
- return 2 # Can't be both available and taken
154
+ # Can't be both available and taken
155
+ return Result.error("Invalid status match. Report this on Github.")
142
156
  elif available_value:
143
- return 1
157
+ return Result.available()
144
158
  elif taken_value:
145
- return 0
146
- return 2
159
+ return Result.taken()
160
+ return Result.error("Status didn't match. Report this on Github.")
147
161
 
148
162
  return generic_validate(url, inner, **kwargs)
163
+
164
+ def generate_permutations(username, pattern, limit=None):
165
+ """
166
+ Generate all order-based permutations of characters in `pattern`
167
+ appended after `username`.
168
+ """
169
+ permutations_set = {username}
170
+
171
+ chars = list(pattern)
172
+
173
+ # generate permutations of length 1 → len(chars)
174
+ for r in range(1, len(chars) + 1):
175
+ for combo in permutations(chars, r):
176
+ permutations_set.add(username + ''.join(combo))
177
+ if limit and len(permutations_set) >= limit:
178
+ return list(permutations_set)[:limit]
179
+
180
+ return sorted(permutations_set)
@@ -0,0 +1,79 @@
1
+ from enum import Enum
2
+ from typing import Literal
3
+
4
+
5
+ def humanize_exception(e: Exception) -> str:
6
+ msg = str(e).lower()
7
+
8
+ if "10054" in msg:
9
+ return "Connection closed by remote server"
10
+ if "11001" in msg:
11
+ return "Could not resolve hostname"
12
+
13
+ return str(e)
14
+
15
+
16
+ class Status(Enum):
17
+ TAKEN = 0
18
+ AVAILABLE = 1
19
+ ERROR = 2
20
+
21
+
22
+ class Result:
23
+ def __init__(self, status: Status, reason: str | Exception | None = None):
24
+ self.status = status
25
+ self.reason = reason
26
+
27
+ @classmethod
28
+ def taken(cls):
29
+ return cls(Status.TAKEN)
30
+
31
+ @classmethod
32
+ def available(cls):
33
+ return cls(Status.AVAILABLE)
34
+
35
+ @classmethod
36
+ def error(cls, reason: str | Exception | None = None):
37
+ return cls(Status.ERROR, reason)
38
+
39
+ @classmethod
40
+ def from_number(cls, i: int, reason: str | Exception | None = None):
41
+ try:
42
+ status = Status(i)
43
+ except ValueError:
44
+ return cls(Status.ERROR, "Invalid status. Please contact maintainers.")
45
+
46
+ return cls(status, reason if status == Status.ERROR else None)
47
+
48
+ def to_number(self) -> int:
49
+ return self.status.value
50
+
51
+ def has_reason(self) -> bool:
52
+ return self.reason != None
53
+
54
+ def get_reason(self) -> str:
55
+ if self.reason == None:
56
+ return ""
57
+ if isinstance(self.reason, str):
58
+ return self.reason
59
+ #Format the exception
60
+ msg = humanize_exception(self.reason)
61
+ return f"{type(self.reason).__name__}: {msg.capitalize()}"
62
+
63
+ def __str__(self):
64
+ return self.get_reason()
65
+
66
+ def __eq__(self, other):
67
+ if isinstance(other, Status):
68
+ return self.status == other
69
+
70
+ if isinstance(other, Result):
71
+ return self.status == other.status
72
+
73
+ if isinstance(other, int):
74
+ return self.to_number() == other
75
+
76
+ return NotImplemented
77
+
78
+
79
+ AnyResult = Literal[0, 1, 2] | Result
@@ -1,6 +1,5 @@
1
1
  import httpx
2
- import json
3
- from httpx import ConnectError, TimeoutException
2
+ from user_scanner.core.result import Result
4
3
 
5
4
 
6
5
  def validate_hashnode(user):
@@ -27,21 +26,17 @@ def validate_hashnode(user):
27
26
 
28
27
  if 'status' in data:
29
28
  if data['status'] == 1:
30
- return 1
29
+ return Result.available()
31
30
  elif data['status'] == 0:
32
- return 0
31
+ return Result.taken()
33
32
 
34
- return 2
33
+ return Result.error("Status not found")
35
34
 
36
35
  else:
37
- return 2
38
-
39
- except (ConnectError, TimeoutException):
40
- return 2
41
- except json.JSONDecodeError:
42
- return 2
43
- except Exception:
44
- return 2
36
+ return Result.error("Invalid status code")
37
+
38
+ except Exception as e:
39
+ return Result.error(e)
45
40
 
46
41
 
47
42
  if __name__ == "__main__":
@@ -2,11 +2,6 @@ from user_scanner.core.orchestrator import status_validate
2
2
 
3
3
 
4
4
  def validate_itch_io(user):
5
- """
6
- Checks if a itch.io username is available.
7
- Returns: 1 -> available, 0 -> taken, 2 -> error
8
- """
9
-
10
5
  url = f"https://{user}.itch.io"
11
6
 
12
7
  return status_validate(url, 404, 200, follow_redirects=True)
@@ -1,5 +1,5 @@
1
- import httpx
2
- from httpx import ConnectError, TimeoutException
1
+ from user_scanner.core.orchestrator import generic_validate
2
+ from user_scanner.core.result import Result
3
3
 
4
4
 
5
5
  def validate_medium(user):
@@ -10,24 +10,19 @@ def validate_medium(user):
10
10
  'Accept': "text/html",
11
11
  }
12
12
 
13
- try:
14
- response = httpx.get(url, headers=headers, timeout=3.0)
15
-
13
+ def process(response):
16
14
  if response.status_code == 200:
17
15
  html_text = response.text
18
16
 
19
17
  username_tag = f'property="profile:username" content="{user}"'
20
18
 
21
19
  if username_tag in html_text:
22
- return 0
20
+ return Result.taken()
23
21
  else:
24
- return 1
25
- return 2
22
+ return Result.available()
23
+ return Result.error()
26
24
 
27
- except (ConnectError, TimeoutException):
28
- return 2
29
- except Exception:
30
- return 2
25
+ return generic_validate(url, process, headers=headers)
31
26
 
32
27
 
33
28
  if __name__ == "__main__":
@@ -8,12 +8,6 @@ def validate_patreon(user):
8
8
 
9
9
 
10
10
  if __name__ == "__main__":
11
- try:
12
- import httpx
13
- except ImportError:
14
- print("Error: 'httpx' library is not installed.")
15
- exit()
16
-
17
11
  user = input("Username?: ").strip()
18
12
  result = validate_patreon(user)
19
13
 
@@ -1,5 +1,4 @@
1
- import httpx
2
- from httpx import ConnectError, TimeoutException
1
+ from user_scanner.core.orchestrator import status_validate
3
2
 
4
3
 
5
4
  def validate_producthunt(user):
@@ -12,23 +11,7 @@ def validate_producthunt(user):
12
11
  'Accept-Language': "en-US,en;q=0.9",
13
12
  }
14
13
 
15
- try:
16
- response = httpx.get(url, headers=headers,
17
- timeout=3.0, follow_redirects=True)
18
- status = response.status_code
19
-
20
- if status == 200:
21
- return 0
22
- elif status == 404:
23
- return 1
24
- else:
25
- return 2
26
-
27
- except (ConnectError, TimeoutException):
28
- return 2
29
- except Exception:
30
- return 2
31
-
14
+ status_validate(url, 404, 200, headers=headers, follow_redirects=True)
32
15
 
33
16
  if __name__ == "__main__":
34
17
  user = input("Username?: ").strip()
@@ -1,4 +1,4 @@
1
- from user_scanner.core.orchestrator import status_validate
1
+ from user_scanner.core.orchestrator import generic_validate, Result
2
2
 
3
3
 
4
4
  def validate_github(user):
@@ -18,7 +18,24 @@ def validate_github(user):
18
18
  'priority': "u=1, i"
19
19
  }
20
20
 
21
- return status_validate(url, 200, 422, headers=headers)
21
+ GITHUB_INVALID_MSG = (
22
+ "Username may only contain alphanumeric characters or single hyphens, "
23
+ "and cannot begin or end with a hyphen."
24
+ )
25
+
26
+ def process(response):
27
+ if response.status_code == 200:
28
+ return Result.available()
29
+
30
+ if response.status_code == 422:
31
+ if GITHUB_INVALID_MSG in response.text:
32
+ return Result.error("Cannot start/end with hyphen or use double hyphens")
33
+
34
+ return Result.taken()
35
+
36
+ return Result.error("Unexpected GitHub response report it via issues")
37
+
38
+ return generic_validate(url, process, headers=headers)
22
39
 
23
40
 
24
41
  if __name__ == "__main__":
@@ -1,4 +1,5 @@
1
1
  from user_scanner.core.orchestrator import generic_validate
2
+ from user_scanner.core.result import Result
2
3
 
3
4
 
4
5
  def validate_gitlab(user):
@@ -18,11 +19,11 @@ def validate_gitlab(user):
18
19
  # Corrected: Compare against Python boolean True/False
19
20
  # AVAILABLE (return 1) if "exists": true
20
21
  if data['exists'] is False:
21
- return 1
22
+ return Result.available()
22
23
  # UNAVAILABLE (return 0) if "exists": false
23
24
  elif data['exists'] is True:
24
- return 0
25
- return 2
25
+ return Result.taken()
26
+ return Result.error("Invalid status code")
26
27
 
27
28
  return generic_validate(url, process, headers=headers)
28
29
 
@@ -0,0 +1,19 @@
1
+ from user_scanner.core.orchestrator import status_validate
2
+
3
+
4
+ def validate_huggingface(user):
5
+ url = f"https://huggingface.co/{user}"
6
+
7
+ return status_validate(url, 404, 200, follow_redirects=True)
8
+
9
+
10
+ if __name__ == "__main__":
11
+ user = input("Username?: ").strip()
12
+ result = validate_huggingface(user)
13
+
14
+ if result == 1:
15
+ print("Available!")
16
+ elif result == 0:
17
+ print("Unavailable!")
18
+ else:
19
+ print("Error occurred!")
user_scanner/dev/npmjs.py CHANGED
@@ -1,34 +1,18 @@
1
- import httpx
2
- from httpx import ConnectError, TimeoutException
1
+ import re
2
+ from user_scanner.core.orchestrator import status_validate, Result
3
3
 
4
4
 
5
5
  def validate_npmjs(user):
6
- url = f"https://www.npmjs.com/~{user}"
6
+ if re.match(r'^[^a-zA-Z0-9_-]', user):
7
+ return Result.error("Username cannot start with a period")
7
8
 
8
- headers = {
9
- 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
10
- '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",
11
- 'Accept-Encoding': "gzip, deflate, br, zstd",
12
- 'accept-language': "en-US,en;q=0.9",
13
- 'priority': "u=0, i"
14
- }
9
+ if re.search(r'[A-Z]', user):
10
+ return Result.error("Username cannot contain uppercase letters.")
15
11
 
16
- try:
17
- response = httpx.head(url, headers=headers,
18
- timeout=3.0, follow_redirects=True)
19
- status = response.status_code
12
+ url = f"https://www.npmjs.com/~{user}"
20
13
 
21
- if status == 200:
22
- return 0
23
- elif status == 404:
24
- return 1
25
- else:
26
- return 2
27
14
 
28
- except (ConnectError, TimeoutException):
29
- return 2
30
- except Exception:
31
- return 2
15
+ return status_validate(url, 404, 200, timeout=3.0, follow_redirects=True)
32
16
 
33
17
 
34
18
  if __name__ == "__main__":
@@ -40,4 +24,4 @@ if __name__ == "__main__":
40
24
  elif result == 0:
41
25
  print("Unavailable!")
42
26
  else:
43
- print("Error occurred!")
27
+ print(f"Error occurred! Reason: {result}")