user-scanner 1.0.7.0__tar.gz → 1.0.8.0__tar.gz

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 (58) hide show
  1. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/PKG-INFO +9 -6
  2. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/README.md +8 -5
  3. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/pyproject.toml +1 -1
  4. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/core/orchestrator.py +32 -30
  5. user_scanner-1.0.8.0/user_scanner/core/result.py +79 -0
  6. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/creator/hashnode.py +8 -13
  7. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/creator/itch_io.py +0 -5
  8. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/creator/medium.py +7 -12
  9. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/creator/patreon.py +0 -6
  10. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/creator/producthunt.py +2 -19
  11. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/dev/github.py +19 -2
  12. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/dev/gitlab.py +4 -3
  13. user_scanner-1.0.8.0/user_scanner/dev/huggingface.py +19 -0
  14. user_scanner-1.0.8.0/user_scanner/dev/npmjs.py +27 -0
  15. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/donation/buymeacoffee.py +0 -3
  16. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/gaming/chess_com.py +4 -9
  17. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/gaming/minecraft.py +0 -5
  18. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/gaming/monkeytype.py +4 -10
  19. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/gaming/osu.py +0 -5
  20. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/gaming/roblox.py +14 -9
  21. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/gaming/steam.py +6 -9
  22. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/bluesky.py +7 -13
  23. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/discord.py +6 -9
  24. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/mastodon.py +0 -6
  25. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/pinterest.py +4 -4
  26. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/reddit.py +4 -3
  27. user_scanner-1.0.8.0/user_scanner/social/soundcloud.py +43 -0
  28. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/telegram.py +6 -6
  29. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/x.py +10 -20
  30. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/youtube.py +2 -18
  31. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/version.json +1 -1
  32. user_scanner-1.0.7.0/user_scanner/dev/npmjs.py +0 -43
  33. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/LICENSE +0 -0
  34. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/__init__.py +0 -0
  35. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/__main__.py +0 -0
  36. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/cli/__init__.py +0 -0
  37. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/cli/banner.py +0 -0
  38. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/community/__init__.py +0 -0
  39. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/community/coderlegion.py +0 -0
  40. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/core/__init__.py +0 -0
  41. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/creator/__init__.py +0 -0
  42. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/creator/devto.py +0 -0
  43. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/creator/kaggle.py +0 -0
  44. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/dev/__init__.py +0 -0
  45. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/dev/codeberg.py +0 -0
  46. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/dev/cratesio.py +0 -0
  47. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/dev/dockerhub.py +0 -0
  48. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/dev/launchpad.py +0 -0
  49. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/dev/replit.py +0 -0
  50. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/donation/__init__.py +0 -0
  51. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/donation/liberapay.py +0 -0
  52. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/gaming/__init__.py +0 -0
  53. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/__init__.py +0 -0
  54. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/instagram.py +0 -0
  55. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/snapchat.py +0 -0
  56. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/social/threads.py +0 -0
  57. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/utils/update.py +0 -0
  58. {user_scanner-1.0.7.0 → user_scanner-1.0.8.0}/user_scanner/utils/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: user-scanner
3
- Version: 1.0.7.0
3
+ Version: 1.0.8.0
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>
@@ -15,7 +15,7 @@ Project-URL: Homepage, https://github.com/kaifcodec/user-scanner
15
15
 
16
16
  ![1000136215](https://github.com/user-attachments/assets/49ec8d24-665b-4115-8525-01a8d0ca2ef4)
17
17
  <p align="center">
18
- <img src="https://img.shields.io/badge/Version-1.0.7.0-blueviolet?style=for-the-badge&logo=github" />
18
+ <img src="https://img.shields.io/badge/Version-1.0.8.0-blueviolet?style=for-the-badge&logo=github" />
19
19
  <img src="https://img.shields.io/github/issues/kaifcodec/user-scanner?style=for-the-badge&logo=github" />
20
20
  <img src="https://img.shields.io/badge/Tested%20on-Termux-black?style=for-the-badge&logo=termux" />
21
21
  <img src="https://img.shields.io/badge/Tested%20on-Windows-cyan?style=for-the-badge&logo=Windows" />
@@ -85,7 +85,13 @@ user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
85
85
 
86
86
  - Note*: New modules are constantly getting added so this might have only limited, outdated output:
87
87
 
88
- <img width="1008" height="568" alt="1000139959" src="https://github.com/user-attachments/assets/467a4aa0-238d-4110-b9a6-d4b96c244432" />
88
+
89
+ <img width="1080" height="770" alt="1000140392" src="https://github.com/user-attachments/assets/4638c8f6-40c6-46f8-ae17-ac65cd199d81" />
90
+
91
+
92
+ ---
93
+
94
+ <img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
89
95
 
90
96
 
91
97
  ### Contributing:
@@ -112,9 +118,6 @@ user_scanner/
112
118
 
113
119
  See [CONTRIBUTING.md](CONTRIBUTING.md) for examples.
114
120
 
115
- ### 📧 Contact:
116
- - [Email](kaifcodec@gmail.com)
117
-
118
121
  ---
119
122
 
120
123
  ### Dependencies:
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![1000136215](https://github.com/user-attachments/assets/49ec8d24-665b-4115-8525-01a8d0ca2ef4)
4
4
  <p align="center">
5
- <img src="https://img.shields.io/badge/Version-1.0.7.0-blueviolet?style=for-the-badge&logo=github" />
5
+ <img src="https://img.shields.io/badge/Version-1.0.8.0-blueviolet?style=for-the-badge&logo=github" />
6
6
  <img src="https://img.shields.io/github/issues/kaifcodec/user-scanner?style=for-the-badge&logo=github" />
7
7
  <img src="https://img.shields.io/badge/Tested%20on-Termux-black?style=for-the-badge&logo=termux" />
8
8
  <img src="https://img.shields.io/badge/Tested%20on-Windows-cyan?style=for-the-badge&logo=Windows" />
@@ -72,7 +72,13 @@ user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
72
72
 
73
73
  - Note*: New modules are constantly getting added so this might have only limited, outdated output:
74
74
 
75
- <img width="1008" height="568" alt="1000139959" src="https://github.com/user-attachments/assets/467a4aa0-238d-4110-b9a6-d4b96c244432" />
75
+
76
+ <img width="1080" height="770" alt="1000140392" src="https://github.com/user-attachments/assets/4638c8f6-40c6-46f8-ae17-ac65cd199d81" />
77
+
78
+
79
+ ---
80
+
81
+ <img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
76
82
 
77
83
 
78
84
  ### Contributing:
@@ -99,9 +105,6 @@ user_scanner/
99
105
 
100
106
  See [CONTRIBUTING.md](CONTRIBUTING.md) for examples.
101
107
 
102
- ### 📧 Contact:
103
- - [Email](kaifcodec@gmail.com)
104
-
105
108
  ---
106
109
 
107
110
  ### Dependencies:
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "user-scanner"
7
- version = "1.0.7.0"
7
+ version = "1.0.8.0"
8
8
  description = "Check username availability across multiple popular platforms"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
@@ -1,33 +1,18 @@
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
8
6
  from pathlib import Path
9
- from typing import Dict
7
+ from user_scanner.core.result import Result, AnyResult
8
+ from typing import Callable, Dict, List
10
9
 
11
10
  lock = threading.Condition()
12
11
  # Basically which thread is the one to print
13
12
  print_queue = 0
14
13
 
15
14
 
16
- def load_categories() -> Dict[str, Path]:
17
- root = Path(__file__).resolve().parent.parent # Should be user_scanner
18
- categories = {}
19
-
20
- for subfolder in root.iterdir():
21
- if subfolder.is_dir() and \
22
- not subfolder.name.lower() in ["cli", "utils", "core"] and \
23
- not "__" in subfolder.name: # Removes __pycache__
24
- categories[subfolder.name] = subfolder.resolve()
25
-
26
- return categories
27
-
28
-
29
15
  def load_modules(category_path: Path):
30
-
31
16
  modules = []
32
17
  for file in category_path.glob("*.py"):
33
18
  if file.name == "__init__.py":
@@ -40,6 +25,19 @@ def load_modules(category_path: Path):
40
25
  return modules
41
26
 
42
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
+
43
41
  def worker_single(module, username, i):
44
42
  global print_queue
45
43
 
@@ -53,12 +51,17 @@ def worker_single(module, username, i):
53
51
  if func:
54
52
  try:
55
53
  result = func(username)
54
+ reason = ""
55
+
56
+ if isinstance(result, Result) and result.has_reason():
57
+ reason = f" ({result.get_reason()})"
58
+
56
59
  if result == 1:
57
60
  output = f" {Fore.GREEN}[✔] {site_name} ({username}): Available{Style.RESET_ALL}"
58
61
  elif result == 0:
59
62
  output = f" {Fore.RED}[✘] {site_name} ({username}): Taken{Style.RESET_ALL}"
60
63
  else:
61
- output = f" {Fore.YELLOW}[!] {site_name} ({username}): Error{Style.RESET_ALL}"
64
+ output = f" {Fore.YELLOW}[!] {site_name} ({username}): Error{reason}{Style.RESET_ALL}"
62
65
  except Exception as e:
63
66
  output = f" {Fore.YELLOW}[!] {site_name}: Exception - {e}{Style.RESET_ALL}"
64
67
  else:
@@ -106,7 +109,7 @@ def run_checks(username):
106
109
  print()
107
110
 
108
111
 
109
- def make_get_request(url, **kwargs):
112
+ def make_get_request(url: str, **kwargs) -> httpx.Response:
110
113
  """Simple wrapper to **httpx.get** that predefines headers and timeout"""
111
114
  if not "headers" in kwargs:
112
115
  kwargs["headers"] = {
@@ -123,26 +126,24 @@ def make_get_request(url, **kwargs):
123
126
  return httpx.get(url, **kwargs)
124
127
 
125
128
 
126
- def generic_validate(url, func, **kwargs):
129
+ def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kwargs) -> AnyResult:
127
130
  """
128
131
  A generic validate function that makes a request and executes the provided function on the response.
129
132
  """
130
133
  try:
131
134
  response = make_get_request(url, **kwargs)
132
135
  return func(response)
133
- except (ConnectError, TimeoutException):
134
- return 2
135
- except Exception:
136
- return 2
136
+ except Exception as e:
137
+ return Result.error(e)
137
138
 
138
139
 
139
- def status_validate(url, available, taken, **kwargs):
140
+ def status_validate(url: str, available: int | List[int], taken: int | List[int], **kwargs) -> Result:
140
141
  """
141
142
  Function that takes a **url** and **kwargs** for the request and
142
143
  checks if the request status matches the availabe or taken.
143
144
  **Available** and **Taken** must either be whole numbers or lists of whole numbers.
144
145
  """
145
- def inner(response):
146
+ def inner(response: httpx.Response):
146
147
  # Checks if a number is equal or is contained inside
147
148
  def contains(a, b): return (isinstance(a, list) and b in a) or (a == b)
148
149
  status = response.status_code
@@ -150,12 +151,13 @@ def status_validate(url, available, taken, **kwargs):
150
151
  taken_value = contains(taken, status)
151
152
 
152
153
  if available_value and taken_value:
153
- 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.")
154
156
  elif available_value:
155
- return 1
157
+ return Result.available()
156
158
  elif taken_value:
157
- return 0
158
- return 2
159
+ return Result.taken()
160
+ return Result.error("Status didn't match. Report this on Github.")
159
161
 
160
162
  return generic_validate(url, inner, **kwargs)
161
163
 
@@ -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!")
@@ -0,0 +1,27 @@
1
+ import re
2
+ from user_scanner.core.orchestrator import status_validate, Result
3
+
4
+
5
+ def validate_npmjs(user):
6
+ if re.match(r'^[^a-zA-Z0-9_-]', user):
7
+ return Result.error("Username cannot start with a period")
8
+
9
+ if re.search(r'[A-Z]', user):
10
+ return Result.error("Username cannot contain uppercase letters.")
11
+
12
+ url = f"https://www.npmjs.com/~{user}"
13
+
14
+
15
+ return status_validate(url, 404, 200, timeout=3.0, follow_redirects=True)
16
+
17
+
18
+ if __name__ == "__main__":
19
+ user = input("Username?: ").strip()
20
+ result = validate_npmjs(user)
21
+
22
+ if result == 1:
23
+ print("Available!")
24
+ elif result == 0:
25
+ print("Unavailable!")
26
+ else:
27
+ print(f"Error occurred! Reason: {result}")
@@ -1,6 +1,3 @@
1
- import httpx
2
- from httpx import ConnectError, TimeoutException
3
-
4
1
  from user_scanner.core.orchestrator import status_validate
5
2
 
6
3
 
@@ -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_chess_com(user):
@@ -16,22 +17,16 @@ def validate_chess_com(user):
16
17
  data = response.json()
17
18
  if data.get('valid') is True:
18
19
  # 'valid': true means the username is NOT taken
19
- return 1
20
+ return Result.available()
20
21
  elif data.get('valid') is False:
21
22
  # 'valid': false means the username IS taken
22
- return 0
23
- return 2
23
+ return Result.taken()
24
+ return Result.error("Invalid status code")
24
25
 
25
26
  return generic_validate(url, process, headers=headers)
26
27
 
27
28
 
28
29
  if __name__ == "__main__":
29
- try:
30
- import httpx
31
- except ImportError:
32
- print("Error: 'httpx' library is not installed.")
33
- exit()
34
-
35
30
  user = input("Username?: ").strip()
36
31
  result = validate_chess_com(user)
37
32
 
@@ -2,11 +2,6 @@ from user_scanner.core.orchestrator import status_validate
2
2
 
3
3
 
4
4
  def validate_minecraft(user):
5
- """
6
- Checks for minecraft username with mojang api.
7
- Returns: 1 -> available, 0 -> taken, 2 -> error
8
- """
9
-
10
5
  url = f"https://api.mojang.com/minecraft/profile/lookup/name/{user}"
11
6
 
12
7
  return status_validate(url, 404, 200, follow_redirects=True)
@@ -1,5 +1,5 @@
1
1
  from user_scanner.core.orchestrator import generic_validate
2
-
2
+ from user_scanner.core.result import Result
3
3
 
4
4
  def validate_monkeytype(user: str) -> int:
5
5
 
@@ -25,21 +25,15 @@ def validate_monkeytype(user: str) -> int:
25
25
  available = payload.get("available")
26
26
 
27
27
  if available is True:
28
- return 1
28
+ return Result.available()
29
29
  elif available is False:
30
- return 0
31
- return 2
30
+ return Result.taken()
31
+ return Result.error("Invalid status code")
32
32
 
33
33
  return generic_validate(url, process, headers=headers)
34
34
 
35
35
 
36
36
  if __name__ == "__main__":
37
- try:
38
- import httpx # noqa: F401
39
- except ImportError:
40
- print("Error: 'httpx' library is not installed.")
41
- raise SystemExit(1)
42
-
43
37
  user = input("Username?: ").strip()
44
38
  result = validate_monkeytype(user)
45
39
 
@@ -2,11 +2,6 @@ from user_scanner.core.orchestrator import status_validate
2
2
 
3
3
 
4
4
  def validate_osu(user):
5
- """
6
- Checks if a Osu username is available.
7
- Returns: 1 -> available, 0 -> taken, 2 -> error
8
- """
9
-
10
5
  url = f"https://osu.ppy.sh/users/{user}"
11
6
 
12
7
  return status_validate(url, 404, [200, 302], follow_redirects=True)
@@ -1,27 +1,32 @@
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_roblox(user):
5
- """
6
- Checks if a roblox username is available.
7
- Returns: 1 -> available, 0 -> taken, 2 -> error
8
- """
9
-
10
6
  # official api
11
7
  url = f"https://users.roblox.com/v1/users/search?keyword={user}&limit=10"
12
8
 
13
9
  def process(response):
14
10
  search_results = response.json() # api response
15
11
 
16
- if "errors" in search_results: # this usually triggers when timeout or ratelimit
17
- return 2
12
+ if response.status_code == 429:
13
+ return Result.error("Too many requests")
14
+
15
+ if response.status_code == 400:
16
+ error = search_results["errors"][0] #Api states theres always an error
17
+ if error["code"] == 6:
18
+ return Result.error("Username is too short")
19
+ if error["code"] == 5:
20
+ return Result.error("Username was filtered")
21
+ #Shouldn't be able to reach this
22
+ return Result.error("Invalid username")
18
23
 
19
24
  # iterates through the entries in the search results
20
25
  for entry in search_results["data"]:
21
26
  # .lower() so casing from the API doesn't matter
22
27
  if entry["name"].lower() == user.lower(): # if a username matches the user
23
- return 0
24
- return 1
28
+ return Result.taken()
29
+ return Result.available()
25
30
 
26
31
  return generic_validate(url, process, follow_redirects=True)
27
32
 
@@ -1,21 +1,18 @@
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_steam(user):
5
- """
6
- Checks if a steam username is available.
7
- Returns: 1 -> available, 0 -> taken, 2 -> error
8
- """
9
-
10
6
  url = f"https://steamcommunity.com/id/{user}/"
11
7
 
12
8
  def process(response):
13
9
  if response.status_code == 200:
14
- if response.text.find("Error</title>") != -1:
15
- return 1
10
+ if "Error</title>" in response.text:
11
+ return Result.available()
16
12
  else:
17
- return 0
18
- return 2
13
+ return Result.taken()
14
+
15
+ return Result.error("Invalid status code")
19
16
 
20
17
  return generic_validate(url, process)
21
18
 
@@ -1,5 +1,5 @@
1
- import re
2
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_bluesky(user):
@@ -25,30 +25,24 @@ def validate_bluesky(user):
25
25
  'handle': handle,
26
26
  }
27
27
 
28
- if not re.fullmatch(r"^[a-zA-Z0-9\.-]{1,64}$", user):
29
- return 2
30
-
31
28
  def process(response):
32
29
  if response.status_code == 200:
33
30
  data = response.json()
34
31
  result_type = data.get('result', {}).get('$type')
35
32
 
36
33
  if result_type == "com.atproto.temp.checkHandleAvailability#resultAvailable":
37
- return 1
34
+ return Result.available()
38
35
  elif result_type == "com.atproto.temp.checkHandleAvailability#resultUnavailable":
39
- return 0
40
- return 2
36
+ return Result.taken()
37
+ elif response.status_code == 400:
38
+ return Result.error("Username can only contain letters, numbers, hyphens (no leading/trailing)")
39
+
40
+ return Result.error("Invalid status code!")
41
41
 
42
42
  return generic_validate(url, process, headers=headers, params=params, timeout=15.0)
43
43
 
44
44
 
45
45
  if __name__ == "__main__":
46
- try:
47
- import httpx
48
- except ImportError:
49
- print("Error: 'httpx' library is not installed.")
50
- exit()
51
-
52
46
  user = input("Username?: ").strip()
53
47
  result = validate_bluesky(user)
54
48
 
@@ -1,6 +1,5 @@
1
1
  import httpx
2
- from httpx import ConnectError, TimeoutException
3
-
2
+ from user_scanner.core.result import Result
4
3
 
5
4
  def validate_discord(user):
6
5
  url = "https://discord.com/api/v9/unique-username/username-attempt-unauthed"
@@ -21,14 +20,12 @@ def validate_discord(user):
21
20
  if response.status_code == 200:
22
21
  status = response.json().get("taken")
23
22
  if status is True:
24
- return 0
23
+ return Result.taken()
25
24
  elif status is False:
26
- return 1
27
- return 2
28
- except (ConnectError, TimeoutException):
29
- return 2
30
- except Exception:
31
- return 2
25
+ return Result.available()
26
+ return Result.error("Invalid status code")
27
+ except Exception as e:
28
+ return Result.error(e)
32
29
 
33
30
 
34
31
  if __name__ == "__main__":
@@ -8,12 +8,6 @@ def validate_mastodon(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_mastodon(user)
19
13
 
@@ -1,5 +1,5 @@
1
1
  from user_scanner.core.orchestrator import generic_validate
2
-
2
+ from user_scanner.core.result import Result
3
3
 
4
4
  def validate_pinterest(user):
5
5
  url = f"https://www.pinterest.com/{user}/"
@@ -7,11 +7,11 @@ def validate_pinterest(user):
7
7
  def process(response):
8
8
  if response.status_code == 200:
9
9
  if "User not found." in response.text:
10
- return 1
10
+ return Result.available()
11
11
  else:
12
- return 0
12
+ return Result.taken()
13
13
  else:
14
- return 2
14
+ return Result.error("Invalid status code")
15
15
 
16
16
  return generic_validate(url, process, follow_redirects=True)
17
17
 
@@ -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_reddit(user):
@@ -7,11 +8,11 @@ def validate_reddit(user):
7
8
  def process(response):
8
9
  if response.status_code == 200:
9
10
  if "Sorry, nobody on Reddit goes by that name." in response.text:
10
- return 1
11
+ return Result.available()
11
12
  else:
12
- return 0
13
+ return Result.taken()
13
14
  else:
14
- return 2
15
+ return Result.error()
15
16
 
16
17
  return generic_validate(url, process, follow_redirects=True)
17
18
 
@@ -0,0 +1,43 @@
1
+ from user_scanner.core.orchestrator import generic_validate
2
+ from user_scanner.core.result import Result
3
+
4
+
5
+ def validate_soundcloud(user):
6
+ url = f"https://soundcloud.com/{user}"
7
+
8
+ headers = {
9
+ 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
10
+ 'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
11
+ }
12
+
13
+ def process(response):
14
+ if response.status_code == 404:
15
+ return Result.available()
16
+
17
+ if response.status_code == 200:
18
+ text = response.text
19
+
20
+ if f'soundcloud://users:{user}' in text:
21
+ return Result.taken()
22
+ if f'"username":"{user}"' in text:
23
+ return Result.taken()
24
+ if 'soundcloud://users:' in text and '"username":"' in text:
25
+ return Result.taken()
26
+
27
+ return Result.available()
28
+
29
+ return Result.error()
30
+
31
+ return generic_validate(url, process, headers=headers, follow_redirects=True)
32
+
33
+
34
+ if __name__ == "__main__":
35
+ user = input("Username?: ").strip()
36
+ result = validate_soundcloud(user)
37
+
38
+ if result == 1:
39
+ print("Available!")
40
+ elif result == 0:
41
+ print("Unavailable!")
42
+ else:
43
+ print("Error occured!")
@@ -1,18 +1,18 @@
1
1
  import re
2
2
  from user_scanner.core.orchestrator import generic_validate
3
+ from user_scanner.core.result import Result
3
4
 
4
5
 
5
6
  def validate_telegram(user: str) -> int:
6
- """
7
- Checks if a Telegram username is available.
8
- Returns: 1 -> available, 0 -> taken, 2 -> error
9
- """
10
7
  url = f"https://t.me/{user}"
11
8
 
12
9
  def process(r):
13
10
  if r.status_code == 200:
14
- return 0 if re.search(r'<div[^>]*class="tgme_page_extra"[^>]*>', r.text) else 1
15
- return 2
11
+ if re.search(r'<div[^>]*class="tgme_page_extra"[^>]*>', r.text):
12
+ return Result.taken()
13
+ else:
14
+ return Result.available()
15
+ return Result.error()
16
16
 
17
17
  return generic_validate(url, process, follow_redirects=True)
18
18
 
@@ -1,8 +1,5 @@
1
- import httpx
2
- import json
3
- from colorama import Fore, Style
4
- from httpx import ConnectError, TimeoutException
5
-
1
+ from user_scanner.core.result import Result
2
+ from user_scanner.core.orchestrator import generic_validate
6
3
 
7
4
  def validate_x(user):
8
5
  url = "https://api.twitter.com/i/users/username_available.json"
@@ -18,31 +15,24 @@ def validate_x(user):
18
15
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
19
16
  }
20
17
 
21
- try:
22
- response = httpx.get(url, params=params, headers=headers, timeout=3.0)
18
+ def process(response):
23
19
  status = response.status_code
24
- # print(response.text)
20
+
25
21
  if status in [401, 403, 429]:
26
- return 2
22
+ return Result.error()
27
23
 
28
24
  elif status == 200:
29
25
  data = response.json()
30
26
  if data.get('valid') is True:
31
- return 1
27
+ return Result.available()
32
28
  elif data.get('reason') == 'taken':
33
- return 0
29
+ return Result.taken()
34
30
  elif (data.get('reason') == "improper_format" or data.get('reason') == "invalid_username"):
35
- print(
36
- "\n" + " "+f"{Fore.CYAN}X says: {data.get('desc')}{Style.RESET_ALL}")
37
- return 2
38
- else:
39
- return 2
31
+ return Result.error(f"X says: {data.get('desc')}")
40
32
 
41
- except (ConnectError, TimeoutException, json.JSONDecodeError):
42
- return 2
43
- except Exception:
44
- return 2
33
+ return Result.error()
45
34
 
35
+ return generic_validate(url, process, params=params, headers=headers)
46
36
 
47
37
  if __name__ == "__main__":
48
38
  user = input("Username?: ").strip()
@@ -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_youtube(user):
@@ -29,22 +28,7 @@ def validate_youtube(user):
29
28
  'priority': "u=0, i"
30
29
  }
31
30
 
32
- try:
33
- response = httpx.get(url, headers=headers,
34
- follow_redirects=True, timeout=3.0)
35
- status = response.status_code
36
-
37
- if status == 200:
38
- return 0
39
- elif status == 404:
40
- return 1
41
- else:
42
- return 2
43
-
44
- except (ConnectError, TimeoutException):
45
- return 2
46
- except Exception:
47
- return 2
31
+ status_validate(url, 404, 200, headers=headers, follow_redirects=True)
48
32
 
49
33
 
50
34
  if __name__ == "__main__":
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.0.7.0",
2
+ "version": "1.0.8.0",
3
3
  "version_type": "pypi"
4
4
  }
@@ -1,43 +0,0 @@
1
- import httpx
2
- from httpx import ConnectError, TimeoutException
3
-
4
-
5
- def validate_npmjs(user):
6
- url = f"https://www.npmjs.com/~{user}"
7
-
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
- }
15
-
16
- try:
17
- response = httpx.head(url, headers=headers,
18
- timeout=3.0, follow_redirects=True)
19
- status = response.status_code
20
-
21
- if status == 200:
22
- return 0
23
- elif status == 404:
24
- return 1
25
- else:
26
- return 2
27
-
28
- except (ConnectError, TimeoutException):
29
- return 2
30
- except Exception:
31
- return 2
32
-
33
-
34
- if __name__ == "__main__":
35
- user = input("Username?: ").strip()
36
- result = validate_npmjs(user)
37
-
38
- if result == 1:
39
- print("Available!")
40
- elif result == 0:
41
- print("Unavailable!")
42
- else:
43
- print("Error occurred!")
File without changes