user-scanner 1.0.8.0__tar.gz → 1.0.9.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 (62) hide show
  1. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/PKG-INFO +33 -18
  2. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/README.md +32 -17
  3. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/pyproject.toml +1 -1
  4. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/__main__.py +86 -61
  5. user_scanner-1.0.9.0/user_scanner/cli/printer.py +117 -0
  6. user_scanner-1.0.9.0/user_scanner/community/__init__.py +1 -0
  7. user_scanner-1.0.9.0/user_scanner/community/stackoverflow.py +35 -0
  8. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/core/orchestrator.py +87 -55
  9. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/core/result.py +57 -8
  10. user_scanner-1.0.9.0/user_scanner/core/utils.py +9 -0
  11. user_scanner-1.0.8.0/user_scanner/social/youtube.py → user_scanner-1.0.9.0/user_scanner/creator/producthunt.py +15 -11
  12. user_scanner-1.0.9.0/user_scanner/social/youtube.py +50 -0
  13. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/version.json +1 -1
  14. user_scanner-1.0.8.0/user_scanner/creator/producthunt.py +0 -25
  15. user_scanner-1.0.8.0/user_scanner/gaming/__init__.py +0 -0
  16. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/LICENSE +0 -0
  17. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/__init__.py +0 -0
  18. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/cli/__init__.py +0 -0
  19. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/cli/banner.py +0 -0
  20. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/community/coderlegion.py +0 -0
  21. {user_scanner-1.0.8.0/user_scanner/community → user_scanner-1.0.9.0/user_scanner/core}/__init__.py +0 -0
  22. {user_scanner-1.0.8.0/user_scanner/core → user_scanner-1.0.9.0/user_scanner/creator}/__init__.py +0 -0
  23. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/devto.py +0 -0
  24. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/hashnode.py +0 -0
  25. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/itch_io.py +0 -0
  26. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/kaggle.py +0 -0
  27. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/medium.py +0 -0
  28. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/patreon.py +0 -0
  29. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/__init__.py +0 -0
  30. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/codeberg.py +0 -0
  31. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/cratesio.py +0 -0
  32. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/dockerhub.py +0 -0
  33. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/github.py +0 -0
  34. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/gitlab.py +0 -0
  35. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/huggingface.py +0 -0
  36. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/launchpad.py +0 -0
  37. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/npmjs.py +0 -0
  38. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/replit.py +0 -0
  39. {user_scanner-1.0.8.0/user_scanner/creator → user_scanner-1.0.9.0/user_scanner/donation}/__init__.py +0 -0
  40. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/donation/buymeacoffee.py +0 -0
  41. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/donation/liberapay.py +0 -0
  42. {user_scanner-1.0.8.0/user_scanner/donation → user_scanner-1.0.9.0/user_scanner/gaming}/__init__.py +0 -0
  43. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/chess_com.py +0 -0
  44. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/minecraft.py +0 -0
  45. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/monkeytype.py +0 -0
  46. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/osu.py +0 -0
  47. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/roblox.py +0 -0
  48. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/steam.py +0 -0
  49. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/__init__.py +0 -0
  50. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/bluesky.py +0 -0
  51. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/discord.py +0 -0
  52. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/instagram.py +0 -0
  53. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/mastodon.py +0 -0
  54. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/pinterest.py +0 -0
  55. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/reddit.py +0 -0
  56. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/snapchat.py +0 -0
  57. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/soundcloud.py +0 -0
  58. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/telegram.py +0 -0
  59. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/threads.py +0 -0
  60. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/x.py +0 -0
  61. {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/utils/update.py +0 -0
  62. {user_scanner-1.0.8.0 → user_scanner-1.0.9.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.8.0
3
+ Version: 1.0.9.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.8.0-blueviolet?style=for-the-badge&logo=github" />
18
+ <img src="https://img.shields.io/badge/Version-1.0.9.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" />
@@ -31,13 +31,16 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
31
31
 
32
32
  ### Features
33
33
 
34
- - ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**.
35
- - ✅ Clear **Available / Taken / Error** output for each platform.
36
- - ✅ Fully modular: add new platform modules easily.
37
- - ✅ Wildcard-based username permutations for automatic variation generation
38
- - ✅ Command-line interface ready: works directly after `pip install`.
39
- - ✅ Can be used as username OSINT tool.
40
- - ✅ Very low and lightweight dependencies, can be run on any machine.
34
+ - ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**
35
+ - ✅ Clear **Available / Taken / Error** output for each platform
36
+ - ✅ Robust error handling: It prints the exact reason (e.g. Cannot use underscores, hyphens at the start/end)
37
+ - ✅ Fully modular: add new platform modules easily
38
+ - ✅ Wildcard-based username permutations for automatic variation generation using provided suffix
39
+ - ✅ Selection of results format (e.g. json, csv, console (default))
40
+ - ✅ Get the scanning results in preferred format (json/csv) in specified output file (suitable for power users)
41
+ - ✅ Command-line interface ready: works directly after `pip install`
42
+ - ✅ Can be used as username OSINT tool
43
+ - ✅ Very low and lightweight dependencies, can be run on any machine
41
44
  ---
42
45
 
43
46
  ### Installation
@@ -61,15 +64,21 @@ Optionally, scan a specific category or single module:
61
64
  user-scanner -u <username> -c dev
62
65
  user-scanner -l # Lists all available modules
63
66
  user-scanner -u <username> -m github
64
- user-scanner -u <username> -p <suffix>
67
+ ```
65
68
 
69
+ Also, the output file and format can be specified: <br>
70
+ \* Errors and warnings will only appear when the format is set to "console"
71
+ ```bash
72
+ user-scanner -u <username> -f console #Default format
73
+ user-scanner -u <username> -f csv
74
+ user-scanner -u <username> -f json
75
+ user-scanner -u <username> -f <format> -o <output-file>
66
76
  ```
67
77
 
68
78
  Generate multiple username variations by appending a suffix:
69
79
 
70
80
  ```bash
71
81
  user-scanner -u <username> -p <suffix>
72
-
73
82
  ```
74
83
  Optionally, scan a specific category or single module with limit:
75
84
 
@@ -77,7 +86,7 @@ Optionally, scan a specific category or single module with limit:
77
86
  user-scanner -u <username> -p <suffix> -c dev
78
87
  user-scanner -u <username> -p <suffix> -m github
79
88
  user-scanner -u <username> -p <suffix> -s <number> # limit generation of usernames
80
- user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
89
+ user-scanner -u <username> -p <suffix> -d <seconds> # delay to avoid rate-limits (can be 0s-1s)
81
90
  ```
82
91
 
83
92
  ---
@@ -93,6 +102,10 @@ user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
93
102
 
94
103
  <img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
95
104
 
105
+ ---
106
+
107
+ <img width="992" height="556" alt="1000141265" src="https://github.com/user-attachments/assets/9babb19f-bc87-4e7b-abe5-c52b8b1b672c" />
108
+
96
109
 
97
110
  ### Contributing:
98
111
 
@@ -109,12 +122,14 @@ user_scanner/
109
122
  ```
110
123
 
111
124
  **Module guidelines:**
112
- - Each module must define a `validate_<site>()` function that takes a `username` and returns:
113
- - `1` → Available
114
- - `0` → Taken
115
- - `2`Error / Could not check
116
- - Use `httpx` for requests, `colorama` for colored output.
117
- - Optional: modules can define a CLI parser if they support custom arguments.
125
+ This project contains small "validator" modules that check whether a username exists on a given platform. Each validator is a single function that returns a Result object (see `core/orchestrator.py`).
126
+
127
+ Result semantics:
128
+ - Result.available()`available`
129
+ - Result.taken() `taken`
130
+ - Result.error(message: Optional[str]) `error`, blocked, unknown, or request failure (include short diagnostic message when helpful)
131
+
132
+ Follow this document when adding or updating validators.
118
133
 
119
134
  See [CONTRIBUTING.md](CONTRIBUTING.md) for examples.
120
135
 
@@ -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.8.0-blueviolet?style=for-the-badge&logo=github" />
5
+ <img src="https://img.shields.io/badge/Version-1.0.9.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" />
@@ -18,13 +18,16 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
18
18
 
19
19
  ### Features
20
20
 
21
- - ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**.
22
- - ✅ Clear **Available / Taken / Error** output for each platform.
23
- - ✅ Fully modular: add new platform modules easily.
24
- - ✅ Wildcard-based username permutations for automatic variation generation
25
- - ✅ Command-line interface ready: works directly after `pip install`.
26
- - ✅ Can be used as username OSINT tool.
27
- - ✅ Very low and lightweight dependencies, can be run on any machine.
21
+ - ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**
22
+ - ✅ Clear **Available / Taken / Error** output for each platform
23
+ - ✅ Robust error handling: It prints the exact reason (e.g. Cannot use underscores, hyphens at the start/end)
24
+ - ✅ Fully modular: add new platform modules easily
25
+ - ✅ Wildcard-based username permutations for automatic variation generation using provided suffix
26
+ - ✅ Selection of results format (e.g. json, csv, console (default))
27
+ - ✅ Get the scanning results in preferred format (json/csv) in specified output file (suitable for power users)
28
+ - ✅ Command-line interface ready: works directly after `pip install`
29
+ - ✅ Can be used as username OSINT tool
30
+ - ✅ Very low and lightweight dependencies, can be run on any machine
28
31
  ---
29
32
 
30
33
  ### Installation
@@ -48,15 +51,21 @@ Optionally, scan a specific category or single module:
48
51
  user-scanner -u <username> -c dev
49
52
  user-scanner -l # Lists all available modules
50
53
  user-scanner -u <username> -m github
51
- user-scanner -u <username> -p <suffix>
54
+ ```
52
55
 
56
+ Also, the output file and format can be specified: <br>
57
+ \* Errors and warnings will only appear when the format is set to "console"
58
+ ```bash
59
+ user-scanner -u <username> -f console #Default format
60
+ user-scanner -u <username> -f csv
61
+ user-scanner -u <username> -f json
62
+ user-scanner -u <username> -f <format> -o <output-file>
53
63
  ```
54
64
 
55
65
  Generate multiple username variations by appending a suffix:
56
66
 
57
67
  ```bash
58
68
  user-scanner -u <username> -p <suffix>
59
-
60
69
  ```
61
70
  Optionally, scan a specific category or single module with limit:
62
71
 
@@ -64,7 +73,7 @@ Optionally, scan a specific category or single module with limit:
64
73
  user-scanner -u <username> -p <suffix> -c dev
65
74
  user-scanner -u <username> -p <suffix> -m github
66
75
  user-scanner -u <username> -p <suffix> -s <number> # limit generation of usernames
67
- user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
76
+ user-scanner -u <username> -p <suffix> -d <seconds> # delay to avoid rate-limits (can be 0s-1s)
68
77
  ```
69
78
 
70
79
  ---
@@ -80,6 +89,10 @@ user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
80
89
 
81
90
  <img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
82
91
 
92
+ ---
93
+
94
+ <img width="992" height="556" alt="1000141265" src="https://github.com/user-attachments/assets/9babb19f-bc87-4e7b-abe5-c52b8b1b672c" />
95
+
83
96
 
84
97
  ### Contributing:
85
98
 
@@ -96,12 +109,14 @@ user_scanner/
96
109
  ```
97
110
 
98
111
  **Module guidelines:**
99
- - Each module must define a `validate_<site>()` function that takes a `username` and returns:
100
- - `1` → Available
101
- - `0` → Taken
102
- - `2`Error / Could not check
103
- - Use `httpx` for requests, `colorama` for colored output.
104
- - Optional: modules can define a CLI parser if they support custom arguments.
112
+ This project contains small "validator" modules that check whether a username exists on a given platform. Each validator is a single function that returns a Result object (see `core/orchestrator.py`).
113
+
114
+ Result semantics:
115
+ - Result.available()`available`
116
+ - Result.taken() `taken`
117
+ - Result.error(message: Optional[str]) `error`, blocked, unknown, or request failure (include short diagnostic message when helpful)
118
+
119
+ Follow this document when adding or updating validators.
105
120
 
106
121
  See [CONTRIBUTING.md](CONTRIBUTING.md) for examples.
107
122
 
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "user-scanner"
7
- version = "1.0.8.0"
7
+ version = "1.0.9.0"
8
8
  description = "Check username availability across multiple popular platforms"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
@@ -1,27 +1,16 @@
1
1
  import argparse
2
2
  import time
3
3
  import re
4
- from user_scanner.core.orchestrator import run_checks, load_modules , generate_permutations, load_categories
4
+ from user_scanner.cli import printer
5
+ from user_scanner.core.orchestrator import generate_permutations, load_categories
5
6
  from colorama import Fore, Style
6
- from .cli import banner
7
- from .cli.banner import print_banner
7
+ from user_scanner.cli.banner import print_banner
8
+ from typing import List
9
+ from user_scanner.core.result import Result
10
+ from user_scanner.core.utils import is_last_value
8
11
 
9
12
  MAX_PERMUTATIONS_LIMIT = 100 # To prevent excessive generation
10
13
 
11
- def list_modules(category=None):
12
- categories = load_categories()
13
- categories_to_list = [category] if category else categories.keys()
14
-
15
- for cat_name in categories_to_list:
16
- path = categories[cat_name]
17
- modules = load_modules(path)
18
- print(Fore.MAGENTA +
19
- f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
20
- for module in modules:
21
- site_name = module.__name__.split(".")[-1]
22
- print(f" - {site_name}")
23
-
24
-
25
14
  def main():
26
15
  parser = argparse.ArgumentParser(
27
16
  prog="user-scanner",
@@ -43,91 +32,127 @@ def main():
43
32
  parser.add_argument(
44
33
  "-v", "--verbose", action="store_true", help="Enable verbose output"
45
34
  )
46
-
35
+
47
36
  parser.add_argument(
48
37
  "-p", "--permute",type=str,help="Generate username permutations using a string pattern (e.g -p 234)"
49
38
  )
50
39
  parser.add_argument(
51
40
  "-s", "--stop",type=int,default=MAX_PERMUTATIONS_LIMIT,help="Limit the number of username permutations generated"
52
41
  )
53
-
42
+
54
43
  parser.add_argument(
55
44
  "-d", "--delay",type=float,default=0,help="Delay in seconds between requests (recommended: 1-2 seconds)"
56
45
  )
57
-
46
+
47
+ parser.add_argument(
48
+ "-f", "--format", choices=["console", "csv", "json"], default="console", help="Specify the output format (default: console)"
49
+ )
50
+
51
+ parser.add_argument(
52
+ "-o", "--output", type=str, help="Specify the output file"
53
+ )
54
+
58
55
  args = parser.parse_args()
59
-
56
+
57
+ Printer = printer.Printer(args.format)
58
+
60
59
  if args.list:
61
- list_modules(args.category)
60
+ Printer.print_modules(args.category)
62
61
  return
63
-
62
+
64
63
  if not args.username:
65
64
  parser.print_help()
66
65
  return
67
-
68
- # Special username checks before run
69
- if (args.module == "x" or args.category == "social"):
70
- if re.search(r"[^a-zA-Z0-9._-]", args.username):
71
- print(
72
- Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. X (Twitter) doesn't support these." + Style.RESET_ALL)
73
- if (args.module == "bluesky" or args.category == "social"):
74
- if re.search(r"[^a-zA-Z0-9\.-]", args.username):
75
- print(
76
- Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. Bluesky will throw error. (Supported: only hyphens and digits)" + Style.RESET_ALL + "\n")
77
- print_banner()
78
66
 
79
- if args.permute and args.delay == 0:
67
+
68
+ if Printer.is_console:
69
+ print_banner()
70
+
71
+ if args.permute and args.delay == 0 and Printer.is_console:
80
72
  print(
81
73
  Fore.YELLOW
82
74
  + "[!] Warning: You're generating multiple usernames with NO delay between requests. "
83
75
  "This may trigger rate limits or IP bans. Use --delay 1 or higher. (Use only if the sites throw errors otherwise ignore)\n"
84
76
  + Style.RESET_ALL)
85
-
77
+
86
78
  usernames = [args.username] # Default single username list
87
-
79
+
88
80
  #Added permutation support , generate all possible permutation of given sequence.
89
81
  if args.permute:
90
82
  usernames = generate_permutations(args.username, args.permute , args.stop)
91
- print(Fore.CYAN + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
83
+ if Printer.is_console:
84
+ print(
85
+ Fore.CYAN + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
92
86
 
93
-
94
-
95
87
  if args.module and "." in args.module:
96
88
  args.module = args.module.replace(".", "_")
97
89
 
90
+ def run_all_usernames(func, arg = None) -> List[Result]:
91
+ """
92
+ Executes a function for all given usernames.
93
+ Made in order to simplify main()
94
+ """
95
+ results = []
96
+ print(Printer.get_start())
97
+ for i, name in enumerate(usernames):
98
+ is_last = i == len(usernames) - 1
99
+ if arg == None:
100
+ results.extend(func(name, Printer, is_last))
101
+ else:
102
+ results.extend(func(arg, name, Printer, is_last))
103
+ if args.delay > 0 and not is_last:
104
+ time.sleep(args.delay)
105
+ if Printer.is_json:
106
+ print(Printer.get_end())
107
+ return results
108
+
109
+ results = []
98
110
 
99
111
  if args.module:
100
112
  # Single module search across all categories
101
- found = False
102
- for cat_path in load_categories().values():
103
- modules = load_modules(cat_path)
113
+ from user_scanner.core.orchestrator import run_module_single, find_module
114
+ modules = find_module(args.module)
115
+
116
+ if len(modules) > 0:
104
117
  for module in modules:
105
- site_name = module.__name__.split(".")[-1]
106
- if site_name.lower() == args.module.lower():
107
- from user_scanner.core.orchestrator import run_module_single
108
- for name in usernames: # <-- permutation support here
109
- run_module_single(module, name)
110
- if args.delay > 0:
111
- time.sleep(args.delay)
112
- found = True
113
- if not found:
118
+ results.extend(run_all_usernames(run_module_single, module))
119
+ else:
114
120
  print(
115
121
  Fore.RED + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
122
+
116
123
  elif args.category:
117
124
  # Category-wise scan
118
125
  category_package = load_categories().get(args.category)
119
126
  from user_scanner.core.orchestrator import run_checks_category
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)
127
+ results = run_all_usernames(run_checks_category, category_package)
128
+
125
129
  else:
126
130
  # Full scan
127
- for name in usernames:
128
- run_checks(name)
129
- if args.delay > 0:
130
- time.sleep(args.delay)
131
+ from user_scanner.core.orchestrator import run_checks
132
+ results = run_all_usernames(run_checks)
133
+
134
+ if not args.output:
135
+ return
136
+
137
+ if args.output and Printer.is_console:
138
+ msg = (
139
+ "\n[!] The console format cannot be "
140
+ f"written to file: '{args.output}'."
141
+ )
142
+ print(Fore.RED + msg + Style.RESET_ALL)
143
+ return
144
+
145
+ content = Printer.get_start()
146
+
147
+ for i,result in enumerate(results):
148
+ char = "" if Printer.is_csv or is_last_value(results, i) else ","
149
+ content += "\n" + Printer.get_result_output(result) + char
150
+
151
+ if Printer.is_json:
152
+ content += "\n" + Printer.get_end()
153
+
154
+ with open(args.output, "a", encoding="utf-8") as f:
155
+ f.write(content)
131
156
 
132
157
 
133
158
  if __name__ == "__main__":
@@ -0,0 +1,117 @@
1
+ from colorama import Fore, Style
2
+ from typing import Literal
3
+ from user_scanner.core.result import Result, Status
4
+
5
+ INDENT = " "
6
+ CSV_HEADER = "username,category,site_name,status,url,reason"
7
+
8
+
9
+ def indentate(msg: str, indent: int):
10
+ if indent <= 0:
11
+ return msg
12
+ tabs = INDENT * indent
13
+ return "\n".join([f"{tabs}{line}" for line in msg.split("\n")])
14
+
15
+
16
+ class Printer:
17
+ def __init__(self, format: Literal["console", "csv", "json"]) -> None:
18
+ if not format in ["console", "csv", "json"]:
19
+ raise ValueError(f"Invalid output-format: {format}")
20
+ self.mode: str = format
21
+ self.indent: int = 0
22
+
23
+ @property
24
+ def is_console(self) -> bool:
25
+ return self.mode == "console"
26
+
27
+ @property
28
+ def is_csv(self) -> bool:
29
+ return self.mode == "csv"
30
+
31
+ @property
32
+ def is_json(self) -> bool:
33
+ return self.mode == "json"
34
+
35
+ def get_start(self, json_char: str = "[") -> str:
36
+ if self.is_json:
37
+ self.indent += 1
38
+ return indentate(json_char, self.indent - 1)
39
+ elif self.is_csv:
40
+ return CSV_HEADER
41
+ return ""
42
+
43
+ def get_end(self, json_char: str = "]") -> str:
44
+ if not self.is_json:
45
+ return
46
+ self.indent = max(self.indent - 1, 0)
47
+ return indentate(json_char, self.indent)
48
+
49
+ def get_result_output(self, result: Result) -> str:
50
+ #In principle result should always have this
51
+ site_name = result.site_name
52
+ username = result.username
53
+
54
+ match (result.status, self.mode):
55
+ case (Status.AVAILABLE, "console"):
56
+ return f"{INDENT}{Fore.GREEN}[✔] {site_name} ({username}): Available{Style.RESET_ALL}"
57
+
58
+ case (Status.TAKEN, "console"):
59
+ return f"{INDENT}{Fore.RED}[✘] {site_name} ({username}): Taken{Style.RESET_ALL}"
60
+
61
+ case (Status.ERROR, "console"):
62
+ reason = ""
63
+ if isinstance(result, Result) and result.has_reason():
64
+ reason = f" ({result.get_reason()})"
65
+ return f"{INDENT}{Fore.YELLOW}[!] {site_name} ({username}): Error{reason}{Style.RESET_ALL}"
66
+
67
+ case (_, "json"):
68
+ return indentate(result.to_json().replace("\t", INDENT), self.indent)
69
+
70
+ case (_, "csv"):
71
+ return result.to_csv()
72
+
73
+ return ""
74
+
75
+ def print_modules(self, category: str | None = None):
76
+ from user_scanner.core.orchestrator import load_categories, load_modules
77
+ categories = load_categories()
78
+ categories_to_list = [category] if category else categories.keys()
79
+
80
+ # Print the start
81
+ if self.is_json:
82
+ print(self.get_start("{"))
83
+ elif self.is_csv:
84
+ print("category,site_name")
85
+
86
+ for i, cat_name in enumerate(categories_to_list):
87
+ path = categories[cat_name]
88
+ modules = load_modules(path)
89
+
90
+ # Print for each category
91
+ match self.mode:
92
+ case "console":
93
+ print(Fore.MAGENTA +
94
+ f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
95
+ case "json":
96
+ print(self.get_start(f"\"{cat_name}\": ["))
97
+
98
+ for j, module in enumerate(modules):
99
+ is_last = j == len(modules) - 1
100
+ site_name = module.__name__.split(".")[-1].capitalize()
101
+
102
+ # Print for each site name
103
+ match self.mode:
104
+ case "console":
105
+ print(f"{INDENT}- {site_name}")
106
+ case "json":
107
+ msg = f"\"{site_name}\"" + ("" if is_last else ",")
108
+ print(indentate(msg, self.indent))
109
+ case "csv":
110
+ print(f"{cat_name},{site_name}")
111
+
112
+ if self.is_json:
113
+ is_last = i == len(categories_to_list) - 1
114
+ print(self.get_end("]" if is_last else "],"))
115
+
116
+ if self.is_json:
117
+ print(self.get_end("}"))
@@ -0,0 +1 @@
1
+ # community
@@ -0,0 +1,35 @@
1
+ from user_scanner.core.orchestrator import generic_validate
2
+ from user_scanner.core.result import Result
3
+
4
+ def validate_stackoverflow(user: str) -> Result:
5
+ url = f"https://stackoverflow.com/users/filter?search={user}"
6
+
7
+ def process(response):
8
+ if response.status_code == 200:
9
+ text = response.text
10
+
11
+ if "No users matched your search." in text:
12
+ return Result.available()
13
+
14
+ pattern = f'>{user}<'
15
+ if pattern in text:
16
+ return Result.taken()
17
+
18
+ return Result.available()
19
+
20
+ return Result.error("Unexpected status code from Stack Overflow")
21
+
22
+ return generic_validate(url, process)
23
+
24
+
25
+ if __name__ == "__main__":
26
+ user = input("Username?: ").strip()
27
+ result = validate_stackoverflow(user)
28
+
29
+ if result == Result.available():
30
+ print("Available!")
31
+ elif result == Result.taken():
32
+ print("Unavailable!")
33
+ else:
34
+ msg = result.get_reason()
35
+ print("Error occurred!" + msg)
@@ -1,15 +1,14 @@
1
1
  import importlib
2
+ import importlib.util
2
3
  from colorama import Fore, Style
3
- import threading
4
+ from concurrent.futures import ThreadPoolExecutor
4
5
  from itertools import permutations
5
6
  import httpx
6
7
  from pathlib import Path
8
+ from user_scanner.cli.printer import Printer
7
9
  from user_scanner.core.result import Result, AnyResult
8
10
  from typing import Callable, Dict, List
9
-
10
- lock = threading.Condition()
11
- # Basically which thread is the one to print
12
- print_queue = 0
11
+ from user_scanner.core.utils import get_site_name, is_last_value
13
12
 
14
13
 
15
14
  def load_modules(category_path: Path):
@@ -38,75 +37,105 @@ def load_categories() -> Dict[str, Path]:
38
37
  return categories
39
38
 
40
39
 
41
- def worker_single(module, username, i):
42
- global print_queue
40
+ def find_module(name: str):
41
+ name = name.lower()
42
+
43
+ matches = [
44
+ module
45
+ for category_path in load_categories().values()
46
+ for module in load_modules(category_path)
47
+ if module.__name__.split(".")[-1].lower() == name
48
+ ]
49
+
50
+ return matches
51
+
52
+ def find_category(module) -> str | None:
53
+
54
+ module_file = getattr(module, '__file__', None)
55
+ if not module_file:
56
+ return None
57
+
58
+ category = Path(module_file).parent.name.lower()
59
+ categories = load_categories()
60
+ if category in categories:
61
+ return category.capitalize()
62
+
63
+ return None
64
+
65
+
43
66
 
67
+ def worker_single(module, username: str) -> Result:
44
68
  func = next((getattr(module, f) for f in dir(module)
45
69
  if f.startswith("validate_") and callable(getattr(module, f))), None)
46
- site_name = module.__name__.split('.')[-1].capitalize().replace("_", ".")
47
- if site_name == "X":
48
- site_name = "X (Twitter)"
49
70
 
50
- output = ""
51
- if func:
52
- try:
53
- result = func(username)
54
- reason = ""
71
+ site_name = get_site_name(module)
55
72
 
56
- if isinstance(result, Result) and result.has_reason():
57
- reason = f" ({result.get_reason()})"
73
+ if not func:
74
+ return Result.error(f"{site_name} has no validate_ function", site_name=site_name, username=username)
58
75
 
59
- if result == 1:
60
- output = f" {Fore.GREEN}[✔] {site_name} ({username}): Available{Style.RESET_ALL}"
61
- elif result == 0:
62
- output = f" {Fore.RED}[✘] {site_name} ({username}): Taken{Style.RESET_ALL}"
63
- else:
64
- output = f" {Fore.YELLOW}[!] {site_name} ({username}): Error{reason}{Style.RESET_ALL}"
65
- except Exception as e:
66
- output = f" {Fore.YELLOW}[!] {site_name}: Exception - {e}{Style.RESET_ALL}"
67
- else:
68
- output = f" {Fore.YELLOW}[!] {site_name} has no validate_ function{Style.RESET_ALL}"
76
+ try:
77
+ result: Result = func(username)
78
+ result.update(site_name=site_name, username=username)
79
+ return result
80
+ except Exception as e:
81
+ return Result.error(e, site_name=site_name, username=username)
69
82
 
70
- with lock:
71
- # Waits for in-order printing
72
- while i != print_queue:
73
- lock.wait()
74
83
 
75
- print(output)
76
- print_queue += 1
77
- lock.notify_all()
84
+ def run_module_single(module, username: str, printer: Printer, last: bool = True) -> List[Result]:
85
+ result = worker_single(module, username)
78
86
 
87
+ category = find_category(module)
88
+ if category:
89
+ result.update(category=category)
79
90
 
80
- def run_module_single(module, username):
81
- # Just executes as if it was a thread
82
- worker_single(module, username, print_queue)
91
+ site_name = get_site_name(module)
92
+ msg = printer.get_result_output(result)
93
+ if last == False and printer.is_json:
94
+ msg += ","
95
+ print(msg)
83
96
 
97
+ return [result]
84
98
 
85
- def run_checks_category(category_path:Path, username:str, verbose=False):
86
- global print_queue
87
99
 
100
+
101
+ def run_checks_category(category_path: Path, username: str, printer: Printer, last: bool = True) -> List[Result]:
88
102
  modules = load_modules(category_path)
103
+
89
104
  category_name = category_path.stem.capitalize()
90
- print(f"{Fore.MAGENTA}== {category_name} SITES =={Style.RESET_ALL}")
105
+ if printer.is_console:
106
+ print(f"\n{Fore.MAGENTA}== {category_name} SITES =={Style.RESET_ALL}")
91
107
 
92
- print_queue = 0
108
+ results = []
93
109
 
94
- threads = []
95
- for i, module in enumerate(modules):
96
- t = threading.Thread(target=worker_single, args=(module, username, i))
97
- threads.append(t)
98
- t.start()
110
+ with ThreadPoolExecutor(max_workers=10) as executor:
111
+ exec_map = executor.map(lambda m: worker_single(m, username), modules)
112
+ for i, result in enumerate(exec_map):
113
+ result.update(category = category_name)
114
+ results.append(result)
99
115
 
100
- for t in threads:
101
- t.join()
116
+ is_last = last and is_last_value(modules, i)
117
+ site_name = get_site_name(modules[i])
118
+ msg = printer.get_result_output(result)
119
+ if is_last == False and printer.is_json:
120
+ msg += ","
121
+ print(msg)
102
122
 
123
+ return results
103
124
 
104
- def run_checks(username):
105
- print(f"\n{Fore.CYAN} Checking username: {username}{Style.RESET_ALL}\n")
106
125
 
107
- for category_path in load_categories().values():
108
- run_checks_category(category_path, username)
109
- print()
126
+ def run_checks(username: str, printer: Printer, last: bool = True) -> List[Result]:
127
+ if printer.is_console:
128
+ print(f"\n{Fore.CYAN} Checking username: {username}{Style.RESET_ALL}")
129
+
130
+ results = []
131
+
132
+ categories = list(load_categories().values())
133
+ for i, category_path in enumerate(categories):
134
+ last_cat: int = last and (i == len(categories) - 1)
135
+ temp = run_checks_category(category_path, username, printer, last_cat)
136
+ results.extend(temp)
137
+
138
+ return results
110
139
 
111
140
 
112
141
  def make_get_request(url: str, **kwargs) -> httpx.Response:
@@ -132,9 +161,11 @@ def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kw
132
161
  """
133
162
  try:
134
163
  response = make_get_request(url, **kwargs)
135
- return func(response)
164
+ result = func(response)
165
+ result.url = url
166
+ return result
136
167
  except Exception as e:
137
- return Result.error(e)
168
+ return Result.error(e, url=url)
138
169
 
139
170
 
140
171
  def status_validate(url: str, available: int | List[int], taken: int | List[int], **kwargs) -> Result:
@@ -161,6 +192,7 @@ def status_validate(url: str, available: int | List[int], taken: int | List[int]
161
192
 
162
193
  return generic_validate(url, inner, **kwargs)
163
194
 
195
+
164
196
  def generate_permutations(username, pattern, limit=None):
165
197
  """
166
198
  Generate all order-based permutations of characters in `pattern`
@@ -1,6 +1,24 @@
1
1
  from enum import Enum
2
2
  from typing import Literal
3
3
 
4
+ DEBUG_MSG = """Result {{
5
+ status: {status},
6
+ reason: "{reason}",
7
+ username: "{username}",
8
+ site_name: "{site_name}",
9
+ category: "{category}",
10
+ }}"""
11
+
12
+ JSON_TEMPLATE = """{{
13
+ \t"username": "{username}",
14
+ \t"category": "{category}",
15
+ \t"site_name": "{site_name}",
16
+ \t"status": "{status}",
17
+ \t"reason": "{reason}"
18
+ }}"""
19
+
20
+ CSV_TEMPLATE = "{username},{category},{site_name},{status},{reason}"
21
+
4
22
 
5
23
  def humanize_exception(e: Exception) -> str:
6
24
  msg = str(e).lower()
@@ -18,23 +36,36 @@ class Status(Enum):
18
36
  AVAILABLE = 1
19
37
  ERROR = 2
20
38
 
39
+ def __str__(self):
40
+ return super().__str__().split(".")[1].capitalize()
41
+
21
42
 
22
43
  class Result:
23
- def __init__(self, status: Status, reason: str | Exception | None = None):
44
+ def __init__(self, status: Status, reason: str | Exception | None = None, **kwargs):
24
45
  self.status = status
25
46
  self.reason = reason
26
47
 
48
+ self.username = None
49
+ self.site_name = None
50
+ self.category = None
51
+ self.update(**kwargs)
52
+
53
+ def update(self, **kwargs):
54
+ for field in ("username", "site_name", "category"):
55
+ if field in kwargs and kwargs[field] is not None:
56
+ setattr(self, field, kwargs[field])
57
+
27
58
  @classmethod
28
- def taken(cls):
29
- return cls(Status.TAKEN)
59
+ def taken(cls, **kwargs):
60
+ return cls(Status.TAKEN, **kwargs)
30
61
 
31
62
  @classmethod
32
- def available(cls):
33
- return cls(Status.AVAILABLE)
63
+ def available(cls, **kwargs):
64
+ return cls(Status.AVAILABLE, **kwargs)
34
65
 
35
66
  @classmethod
36
- def error(cls, reason: str | Exception | None = None):
37
- return cls(Status.ERROR, reason)
67
+ def error(cls, reason: str | Exception | None = None, **kwargs):
68
+ return cls(Status.ERROR, reason, **kwargs)
38
69
 
39
70
  @classmethod
40
71
  def from_number(cls, i: int, reason: str | Exception | None = None):
@@ -56,10 +87,28 @@ class Result:
56
87
  return ""
57
88
  if isinstance(self.reason, str):
58
89
  return self.reason
59
- #Format the exception
90
+ # Format the exception
60
91
  msg = humanize_exception(self.reason)
61
92
  return f"{type(self.reason).__name__}: {msg.capitalize()}"
62
93
 
94
+ def as_dict(self) -> dict:
95
+ return {
96
+ "status": self.status,
97
+ "reason": self.get_reason(),
98
+ "username": self.username,
99
+ "site_name": self.site_name,
100
+ "category": self.category
101
+ }
102
+
103
+ def debug(self) -> str:
104
+ return DEBUG_MSG.format(**self.as_dict())
105
+
106
+ def to_json(self) -> str:
107
+ return JSON_TEMPLATE.format(**self.as_dict())
108
+
109
+ def to_csv(self) -> str:
110
+ return CSV_TEMPLATE.format(**self.as_dict())
111
+
63
112
  def __str__(self):
64
113
  return self.get_reason()
65
114
 
@@ -0,0 +1,9 @@
1
+ def get_site_name(module) -> str:
2
+ name = module.__name__.split('.')[-1].capitalize().replace("_", ".")
3
+ if name == "X":
4
+ return "X (Twitter)"
5
+ return name
6
+
7
+
8
+ def is_last_value(values, i: int) -> bool:
9
+ return i == len(values) - 1
@@ -1,25 +1,28 @@
1
- from user_scanner.core.orchestrator import status_validate
1
+ from user_scanner.core.orchestrator import status_validate, Result
2
2
 
3
3
 
4
- def validate_youtube(user):
4
+ def validate_youtube(user) -> Result:
5
5
  url = f"https://m.youtube.com/@{user}"
6
-
7
6
  headers = {
8
- 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36",
7
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
9
8
  '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",
10
- 'Accept-Encoding': "gzip, deflate, br, zstd",
11
- 'device-memory': "4",
12
- 'sec-ch-ua': "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
9
+ 'Accept-Encoding': "identity",
10
+ 'sec-ch-dpr': "2.75",
11
+ 'sec-ch-viewport-width': "980",
12
+ 'sec-ch-ua': "\"Google Chrome\";v=\"143\", \"Chromium\";v=\"143\", \"Not A(Brand\";v=\"24\"",
13
13
  'sec-ch-ua-mobile': "?1",
14
- 'sec-ch-ua-full-version': "\"141.0.7390.111\"",
14
+ 'sec-ch-ua-full-version': "\"143.0.7499.52\"",
15
15
  'sec-ch-ua-arch': "\"\"",
16
16
  'sec-ch-ua-platform': "\"Android\"",
17
17
  'sec-ch-ua-platform-version': "\"15.0.0\"",
18
18
  'sec-ch-ua-bitness': "\"\"",
19
19
  'sec-ch-ua-wow64': "?0",
20
- 'sec-ch-ua-full-version-list': "\"Google Chrome\";v=\"141.0.7390.111\", \"Not?A_Brand\";v=\"8.0.0.0\", \"Chromium\";v=\"141.0.7390.111\"",
20
+ 'sec-ch-ua-full-version-list': "\"Google Chrome\";v=\"143.0.7499.52\", \"Chromium\";v=\"143.0.7499.52\", \"Not A(Brand\";v=\"24.0.0.0\"",
21
21
  'sec-ch-ua-form-factors': "\"Mobile\"",
22
22
  'upgrade-insecure-requests': "1",
23
+ 'x-browser-channel': "stable",
24
+ 'x-browser-year': "2025",
25
+ 'x-browser-copyright': "Copyright 2025 Google LLC. All Rights reserved.",
23
26
  'sec-fetch-site': "none",
24
27
  'sec-fetch-mode': "navigate",
25
28
  'sec-fetch-user': "?1",
@@ -28,7 +31,7 @@ def validate_youtube(user):
28
31
  'priority': "u=0, i"
29
32
  }
30
33
 
31
- status_validate(url, 404, 200, headers=headers, follow_redirects=True)
34
+ return status_validate(url, 404, 200, headers=headers)
32
35
 
33
36
 
34
37
  if __name__ == "__main__":
@@ -40,4 +43,5 @@ if __name__ == "__main__":
40
43
  elif result == 0:
41
44
  print("Unavailable!")
42
45
  else:
43
- print("Error occured!")
46
+ reason = result.get_reason()
47
+ print(f"Error occurred! Reason: {reason}")
@@ -0,0 +1,50 @@
1
+ from user_scanner.core.orchestrator import status_validate, Result
2
+
3
+
4
+ def validate_youtube(user) -> Result:
5
+ url = f"https://m.youtube.com/@{user}"
6
+ headers = {
7
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
8
+ '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",
9
+ 'Accept-Encoding': "identity",
10
+ 'sec-ch-dpr': "2.75",
11
+ 'sec-ch-viewport-width': "980",
12
+ 'sec-ch-ua': "\"Google Chrome\";v=\"143\", \"Chromium\";v=\"143\", \"Not A(Brand\";v=\"24\"",
13
+ 'sec-ch-ua-mobile': "?1",
14
+ 'sec-ch-ua-full-version': "\"143.0.7499.52\"",
15
+ 'sec-ch-ua-arch': "\"\"",
16
+ 'sec-ch-ua-platform': "\"Android\"",
17
+ 'sec-ch-ua-platform-version': "\"15.0.0\"",
18
+ 'sec-ch-ua-model': "\"I2404\"",
19
+ 'sec-ch-ua-bitness': "\"\"",
20
+ 'sec-ch-ua-wow64': "?0",
21
+ 'sec-ch-ua-full-version-list': "\"Google Chrome\";v=\"143.0.7499.52\", \"Chromium\";v=\"143.0.7499.52\", \"Not A(Brand\";v=\"24.0.0.0\"",
22
+ 'sec-ch-ua-form-factors': "\"Mobile\"",
23
+ 'upgrade-insecure-requests': "1",
24
+ 'x-browser-channel': "stable",
25
+ 'x-browser-year': "2025",
26
+ 'x-browser-copyright': "Copyright 2025 Google LLC. All Rights reserved.",
27
+ 'sec-fetch-site': "none",
28
+ 'sec-fetch-mode': "navigate",
29
+ 'sec-fetch-user': "?1",
30
+ 'sec-fetch-dest': "document",
31
+ 'accept-language': "en-US,en;q=0.9",
32
+ 'priority': "u=0, i"
33
+ }
34
+
35
+
36
+ return status_validate(url, 404, 200, headers=headers)
37
+
38
+
39
+ if __name__ == "__main__":
40
+ user = input("Username?: ").strip()
41
+ result = validate_youtube(user)
42
+
43
+ if result == 1:
44
+ print("Available!")
45
+ elif result == 0:
46
+ print("Unavailable!")
47
+ else:
48
+ reason = result.get_reason()
49
+ print(f"Error occurred! Reason: {reason}")
50
+
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.0.8.0",
2
+ "version": "1.0.9.0",
3
3
  "version_type": "pypi"
4
4
  }
@@ -1,25 +0,0 @@
1
- from user_scanner.core.orchestrator import status_validate
2
-
3
-
4
- def validate_producthunt(user):
5
- url = f"https://www.producthunt.com/@{user}"
6
-
7
- headers = {
8
- '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",
9
- '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",
10
- 'Accept-Encoding': "gzip, deflate, br",
11
- 'Accept-Language': "en-US,en;q=0.9",
12
- }
13
-
14
- status_validate(url, 404, 200, headers=headers, follow_redirects=True)
15
-
16
- if __name__ == "__main__":
17
- user = input("Username?: ").strip()
18
- result = validate_producthunt(user)
19
-
20
- if result == 1:
21
- print("Available!")
22
- elif result == 0:
23
- print("Unavailable!")
24
- else:
25
- print("Error occured!")
File without changes
File without changes