user-scanner 1.0.10.3__py3-none-any.whl → 1.1.0.1__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 (92) hide show
  1. user_scanner/__main__.py +241 -129
  2. user_scanner/core/email_orchestrator.py +78 -0
  3. user_scanner/core/formatter.py +27 -0
  4. user_scanner/core/helpers.py +194 -2
  5. user_scanner/core/orchestrator.py +24 -112
  6. user_scanner/core/result.py +51 -18
  7. user_scanner/email_scan/adult/pornhub.py +62 -0
  8. user_scanner/email_scan/adult/xnxx.py +46 -0
  9. user_scanner/email_scan/adult/xvideos.py +50 -0
  10. user_scanner/email_scan/community/stackoverflow.py +40 -0
  11. user_scanner/email_scan/creator/__init__.py +0 -0
  12. user_scanner/email_scan/creator/gumroad.py +82 -0
  13. user_scanner/email_scan/creator/patreon.py +58 -0
  14. user_scanner/email_scan/dev/__init__.py +0 -0
  15. user_scanner/email_scan/dev/bitbucket.py +33 -0
  16. user_scanner/email_scan/dev/github.py +72 -0
  17. user_scanner/email_scan/dev/huggingface.py +37 -0
  18. user_scanner/email_scan/gaming/__init__.py +0 -0
  19. user_scanner/email_scan/gaming/chess_com.py +47 -0
  20. user_scanner/email_scan/shopping/__init__.py +0 -0
  21. user_scanner/email_scan/shopping/flipkart.py +52 -0
  22. user_scanner/email_scan/social/__init__.py +0 -0
  23. user_scanner/email_scan/social/facebook.py +96 -0
  24. user_scanner/email_scan/social/instagram.py +48 -0
  25. user_scanner/email_scan/social/mastodon.py +57 -0
  26. user_scanner/email_scan/social/x.py +41 -0
  27. user_scanner/user_scan/community/lemmy.py +30 -0
  28. user_scanner/user_scan/creator/__init__.py +0 -0
  29. user_scanner/user_scan/creator/gumroad.py +22 -0
  30. user_scanner/user_scan/donation/__init__.py +0 -0
  31. user_scanner/user_scan/gaming/__init__.py +0 -0
  32. user_scanner/{gaming → user_scan/gaming}/roblox.py +15 -5
  33. user_scanner/version.json +1 -1
  34. user_scanner-1.1.0.1.dist-info/METADATA +239 -0
  35. user_scanner-1.1.0.1.dist-info/RECORD +98 -0
  36. user_scanner/cli/printer.py +0 -117
  37. user_scanner-1.0.10.3.dist-info/METADATA +0 -172
  38. user_scanner-1.0.10.3.dist-info/RECORD +0 -72
  39. /user_scanner/{creator → email_scan}/__init__.py +0 -0
  40. /user_scanner/{donation → email_scan/adult}/__init__.py +0 -0
  41. /user_scanner/{gaming → email_scan/community}/__init__.py +0 -0
  42. /user_scanner/{community → user_scan/community}/__init__.py +0 -0
  43. /user_scanner/{community → user_scan/community}/coderlegion.py +0 -0
  44. /user_scanner/{community → user_scan/community}/hackernews.py +0 -0
  45. /user_scanner/{community → user_scan/community}/stackoverflow.py +0 -0
  46. /user_scanner/{creator → user_scan/creator}/devto.py +0 -0
  47. /user_scanner/{creator → user_scan/creator}/hashnode.py +0 -0
  48. /user_scanner/{creator → user_scan/creator}/itch_io.py +0 -0
  49. /user_scanner/{creator → user_scan/creator}/kaggle.py +0 -0
  50. /user_scanner/{creator → user_scan/creator}/medium.py +0 -0
  51. /user_scanner/{creator → user_scan/creator}/patreon.py +0 -0
  52. /user_scanner/{creator → user_scan/creator}/producthunt.py +0 -0
  53. /user_scanner/{creator → user_scan/creator}/substack.py +0 -0
  54. /user_scanner/{creator → user_scan/creator}/twitch.py +0 -0
  55. /user_scanner/{dev → user_scan/dev}/__init__.py +0 -0
  56. /user_scanner/{dev → user_scan/dev}/bitbucket.py +0 -0
  57. /user_scanner/{dev → user_scan/dev}/codeberg.py +0 -0
  58. /user_scanner/{dev → user_scan/dev}/cratesio.py +0 -0
  59. /user_scanner/{dev → user_scan/dev}/dockerhub.py +0 -0
  60. /user_scanner/{dev → user_scan/dev}/github.py +0 -0
  61. /user_scanner/{dev → user_scan/dev}/gitlab.py +0 -0
  62. /user_scanner/{dev → user_scan/dev}/huggingface.py +0 -0
  63. /user_scanner/{dev → user_scan/dev}/launchpad.py +0 -0
  64. /user_scanner/{dev → user_scan/dev}/leetcode.py +0 -0
  65. /user_scanner/{dev → user_scan/dev}/npmjs.py +0 -0
  66. /user_scanner/{dev → user_scan/dev}/replit.py +0 -0
  67. /user_scanner/{dev → user_scan/dev}/sourceforge.py +0 -0
  68. /user_scanner/{donation → user_scan/donation}/buymeacoffee.py +0 -0
  69. /user_scanner/{donation → user_scan/donation}/liberapay.py +0 -0
  70. /user_scanner/{gaming → user_scan/gaming}/chess_com.py +0 -0
  71. /user_scanner/{gaming → user_scan/gaming}/lichess.py +0 -0
  72. /user_scanner/{gaming → user_scan/gaming}/minecraft.py +0 -0
  73. /user_scanner/{gaming → user_scan/gaming}/monkeytype.py +0 -0
  74. /user_scanner/{gaming → user_scan/gaming}/osu.py +0 -0
  75. /user_scanner/{gaming → user_scan/gaming}/steam.py +0 -0
  76. /user_scanner/{social → user_scan/social}/__init__.py +0 -0
  77. /user_scanner/{social → user_scan/social}/bluesky.py +0 -0
  78. /user_scanner/{social → user_scan/social}/discord.py +0 -0
  79. /user_scanner/{social → user_scan/social}/instagram.py +0 -0
  80. /user_scanner/{social → user_scan/social}/mastodon.py +0 -0
  81. /user_scanner/{social → user_scan/social}/pinterest.py +0 -0
  82. /user_scanner/{social → user_scan/social}/reddit.py +0 -0
  83. /user_scanner/{social → user_scan/social}/snapchat.py +0 -0
  84. /user_scanner/{social → user_scan/social}/soundcloud.py +0 -0
  85. /user_scanner/{social → user_scan/social}/telegram.py +0 -0
  86. /user_scanner/{social → user_scan/social}/threads.py +0 -0
  87. /user_scanner/{social → user_scan/social}/tiktok.py +0 -0
  88. /user_scanner/{social → user_scan/social}/x.py +0 -0
  89. /user_scanner/{social → user_scan/social}/youtube.py +0 -0
  90. {user_scanner-1.0.10.3.dist-info → user_scanner-1.1.0.1.dist-info}/WHEEL +0 -0
  91. {user_scanner-1.0.10.3.dist-info → user_scanner-1.1.0.1.dist-info}/entry_points.txt +0 -0
  92. {user_scanner-1.0.10.3.dist-info → user_scanner-1.1.0.1.dist-info}/licenses/LICENSE +0 -0
user_scanner/__main__.py CHANGED
@@ -1,16 +1,37 @@
1
1
  import argparse
2
2
  import time
3
3
  import sys
4
- from user_scanner.cli import printer
5
- from user_scanner.core.orchestrator import generate_permutations, load_categories
4
+ import re
6
5
  from colorama import Fore, Style
6
+
7
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.helpers import is_last_value
8
+ from user_scanner.core.version import load_local_version
9
+ from user_scanner.core import formatter
11
10
  from user_scanner.utils.updater_logic import check_for_updates
12
11
  from user_scanner.utils.update import update_self
13
12
 
13
+ from user_scanner.core.helpers import (
14
+ load_categories,
15
+ load_modules,
16
+ find_module,
17
+ get_site_name,
18
+ generate_permutations,
19
+ set_proxy_manager,
20
+ get_proxy_count
21
+ )
22
+
23
+ from user_scanner.core.orchestrator import (
24
+ run_user_full,
25
+ run_user_category,
26
+ run_user_module
27
+ )
28
+
29
+ from user_scanner.core.email_orchestrator import (
30
+ run_email_full_batch,
31
+ run_email_category_batch,
32
+ run_email_module_batch
33
+ )
34
+
14
35
  # Color configs
15
36
  R = Fore.RED
16
37
  G = Fore.GREEN
@@ -18,163 +39,254 @@ C = Fore.CYAN
18
39
  Y = Fore.YELLOW
19
40
  X = Fore.RESET
20
41
 
21
-
22
- MAX_PERMUTATIONS_LIMIT = 100 # To prevent excessive generation
42
+ MAX_PERMUTATIONS_LIMIT = 100
23
43
 
24
44
 
25
45
  def main():
26
-
27
46
  parser = argparse.ArgumentParser(
28
47
  prog="user-scanner",
29
- description="Scan usernames across multiple platforms."
30
- )
31
- parser.add_argument(
32
- "-u", "--username", help="Username to scan across platforms"
33
- )
34
- parser.add_argument(
35
- "-c", "--category", choices=load_categories().keys(),
36
- help="Scan all platforms in a category"
37
- )
38
- parser.add_argument(
39
- "-m", "--module", help="Scan a single specific module across all categories"
40
- )
41
- parser.add_argument(
42
- "-l", "--list", action="store_true", help="List all available modules by category"
43
- )
44
- parser.add_argument(
45
- "-v", "--verbose", action="store_true", help="Enable verbose output"
48
+ description="Scan usernames or emails across multiple platforms."
46
49
  )
47
50
 
48
- parser.add_argument(
49
- "-p", "--permute",type=str,help="Generate username permutations using a string pattern (e.g -p 234)"
50
- )
51
- parser.add_argument(
52
- "-s", "--stop",type=int,default=MAX_PERMUTATIONS_LIMIT,help="Limit the number of username permutations generated"
53
- )
51
+ group = parser.add_mutually_exclusive_group(required=False)
52
+
53
+ group.add_argument("-u", "--username",
54
+ help="Username to scan across platforms")
55
+ group.add_argument("-e", "--email", help="Email to scan across platforms")
56
+
57
+ group.add_argument("-uf", "--username-file",
58
+ help="File containing usernames (one per line)")
59
+ group.add_argument("-ef", "--email-file",
60
+ help="File containing emails (one per line)")
61
+
62
+ parser.add_argument("-c", "--category",
63
+ help="Scan all platforms in a category")
64
+
65
+ parser.add_argument("-m", "--module", help="Scan a single specific module")
66
+
67
+
68
+ parser.add_argument("-lu", "--list-user", action="store_true",
69
+ help="List all available modules for username scanning")
70
+
71
+ parser.add_argument("-le", "--list-email", action="store_true",
72
+ help="List all available modules for email scanning")
73
+
74
+ parser.add_argument("-v", "--verbose", action="store_true",
75
+ help="Enable verbose output")
76
+
77
+ parser.add_argument("-p", "--permute", type=str,
78
+ help="Generate permutations using a pattern")
79
+
80
+ parser.add_argument("-s", "--stop", type=int,
81
+ default=MAX_PERMUTATIONS_LIMIT, help="Limit permutations")
82
+
83
+ parser.add_argument("-d", "--delay", type=float,
84
+ default=0, help="Delay between requests")
54
85
 
55
86
  parser.add_argument(
56
- "-d", "--delay",type=float,default=0,help="Delay in seconds between requests (recommended: 1-2 seconds)"
57
- )
87
+ "-f", "--format", choices=["csv", "json"], help="Output format")
88
+
89
+ parser.add_argument("-o", "--output", type=str, help="Output file path")
58
90
 
59
91
  parser.add_argument(
60
- "-f", "--format", choices=["console", "csv", "json"], default="console", help="Specify the output format (default: console)"
61
- )
92
+ "-P", "--proxy-file", type=str, help="Path to proxy list file (one proxy per line)")
62
93
 
63
94
  parser.add_argument(
64
- "-o", "--output", type=str, help="Specify the output file"
65
- )
95
+ "--validate-proxies", action="store_true",
96
+ help="Validate proxies before scanning (tests against google.com)")
97
+
66
98
  parser.add_argument(
67
- "-U", "--update", action="store_true", help="Update user-scanner to latest version"
68
- )
99
+ "-U", "--update", action="store_true", help="Update the tool")
69
100
 
70
- args = parser.parse_args()
101
+ parser.add_argument("--version", action="store_true", help="Print version")
71
102
 
72
- Printer = printer.Printer(args.format)
103
+ args = parser.parse_args()
73
104
 
74
- if args.update is True:
105
+ if args.update:
75
106
  update_self()
76
107
  print(f"[{G}+{X}] {G}Update successful. Please restart the tool.{X}")
77
108
  sys.exit(0)
78
109
 
79
- if args.list:
80
- Printer.print_modules(args.category)
110
+ if args.version:
111
+ version, _ = load_local_version()
112
+ print(f"user-scanner current version -> {G}{version}{X}")
113
+ sys.exit(0)
114
+
115
+ if args.list_user:
116
+ categories = load_categories()
117
+ for cat_name, cat_path in categories.items():
118
+ modules = load_modules(cat_path)
119
+ print(Fore.MAGENTA +
120
+ f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
121
+ for module in modules:
122
+ print(f" - {get_site_name(module)}")
81
123
  return
82
124
 
83
- check_for_updates()
125
+ if args.list_email:
126
+ categories = load_categories(is_email=True)
127
+ for cat_name, cat_path in categories.items():
128
+ modules = load_modules(cat_path)
129
+ print(Fore.MAGENTA +
130
+ f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
131
+ for module in modules:
132
+ print(f" - {get_site_name(module)}")
133
+ return
84
134
 
85
- if not args.username:
135
+ if not (args.username or args.email or args.username_file or args.email_file):
86
136
  parser.print_help()
87
137
  return
88
138
 
89
-
90
- if Printer.is_console:
91
- print_banner()
92
-
93
- if args.permute and args.delay == 0 and Printer.is_console:
94
- print(
95
- Y
96
- + "[!] Warning: You're generating multiple usernames with NO delay between requests. "
97
- "This may trigger rate limits or IP bans. Use --delay 1 or higher. (Use only if the sites throw errors otherwise ignore)\n"
98
- + Style.RESET_ALL)
99
-
100
- usernames = [args.username] # Default single username list
101
-
102
- # Added permutation support , generate all possible permutation of given sequence.
103
- if args.permute:
104
- usernames = generate_permutations(args.username, args.permute , args.stop)
105
- if Printer.is_console:
106
- print(
107
- C + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
108
-
109
- if args.module and "." in args.module:
110
- args.module = args.module.replace(".", "_")
111
-
112
- def run_all_usernames(func, arg = None) -> List[Result]:
113
- """
114
- Executes a function for all given usernames.
115
- Made in order to simplify main()
116
- """
117
- results = []
118
- print(Printer.get_start())
119
- for i, name in enumerate(usernames):
120
- is_last = i == len(usernames) - 1
121
- if arg is None:
122
- results.extend(func(name, Printer, is_last))
139
+ # Initialize proxy manager if proxy file is provided
140
+ if args.proxy_file:
141
+ try:
142
+ # Validate proxies if flag is set
143
+ if args.validate_proxies:
144
+ print(f"{C}[*] Validating proxies from {args.proxy_file}...{X}")
145
+ from user_scanner.core.helpers import validate_proxies, ProxyManager
146
+
147
+ # Load proxies first
148
+ temp_manager = ProxyManager(args.proxy_file)
149
+ all_proxies = temp_manager.proxies
150
+ print(f"{C}[*] Testing {len(all_proxies)} proxies...{X}")
151
+
152
+ # Validate them
153
+ working_proxies = validate_proxies(all_proxies)
154
+
155
+ if not working_proxies:
156
+ print(f"{R}[✘] No working proxies found{X}")
157
+ sys.exit(1)
158
+
159
+ print(f"{G}[+] Found {len(working_proxies)} working proxies out of {len(all_proxies)}{X}")
160
+
161
+ # Save working proxies to temp file
162
+ temp_proxy_file = "validated_proxies.txt"
163
+ with open(temp_proxy_file, 'w', encoding='utf-8') as f:
164
+ for proxy in working_proxies:
165
+ f.write(proxy + '\n')
166
+
167
+ set_proxy_manager(temp_proxy_file)
168
+ proxy_count = get_proxy_count()
169
+ print(f"{G}[+] Using {proxy_count} validated proxies{X}")
123
170
  else:
124
- results.extend(func(arg, name, Printer, is_last))
125
- if args.delay > 0 and not is_last:
126
- time.sleep(args.delay)
127
- if Printer.is_json:
128
- print(Printer.get_end())
129
- return results
130
-
131
- results = []
132
-
133
- if args.module:
134
- # Single module search across all categories
135
- from user_scanner.core.orchestrator import run_module_single, find_module
136
- modules = find_module(args.module)
137
-
138
- if len(modules) > 0:
139
- for module in modules:
140
- results.extend(run_all_usernames(run_module_single, module))
141
- else:
142
- print(
143
- R + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
144
-
145
- elif args.category:
146
- # Category-wise scan
147
- category_package = load_categories().get(args.category)
148
- from user_scanner.core.orchestrator import run_checks_category
149
- results = run_all_usernames(run_checks_category, category_package)
171
+ set_proxy_manager(args.proxy_file)
172
+ proxy_count = get_proxy_count()
173
+ print(f"{G}[+] Loaded {proxy_count} proxies from {args.proxy_file}{X}")
174
+ except Exception as e:
175
+ print(f"{R}[✘] Error loading proxies: {e}{X}")
176
+ sys.exit(1)
150
177
 
178
+ check_for_updates()
179
+ print_banner()
180
+
181
+ # Handle bulk email file
182
+ if args.email_file:
183
+ try:
184
+ with open(args.email_file, 'r', encoding='utf-8') as f:
185
+ emails = [line.strip() for line in f if line.strip() and not line.startswith('#')]
186
+
187
+ # Validate email formats
188
+ valid_emails = []
189
+ for email in emails:
190
+ if re.findall(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", email):
191
+ valid_emails.append(email)
192
+ else:
193
+ print(f"{Y}[!] Skipping invalid email format: {email}{X}")
194
+
195
+ if not valid_emails:
196
+ print(f"{R}[✘] Error: No valid emails found in {args.email_file}{X}")
197
+ sys.exit(1)
198
+
199
+ print(f"{C}[+] Loaded {len(valid_emails)} emails from {args.email_file}{X}")
200
+ is_email = True
201
+ targets = valid_emails
202
+ except FileNotFoundError:
203
+ print(f"{R}[✘] Error: File not found: {args.email_file}{X}")
204
+ sys.exit(1)
205
+ except Exception as e:
206
+ print(f"{R}[✘] Error reading email file: {e}{X}")
207
+ sys.exit(1)
208
+ # Handle bulk username file
209
+ elif args.username_file:
210
+ try:
211
+ with open(args.username_file, 'r', encoding='utf-8') as f:
212
+ usernames = [line.strip() for line in f if line.strip() and not line.startswith('#')]
213
+ if not usernames:
214
+ print(f"{R}[✘] Error: No valid usernames found in {args.username_file}{X}")
215
+ sys.exit(1)
216
+ print(f"{C}[+] Loaded {len(usernames)} usernames from {args.username_file}{X}")
217
+ is_email = False
218
+ targets = usernames
219
+ except FileNotFoundError:
220
+ print(f"{R}[✘] Error: File not found: {args.username_file}{X}")
221
+ sys.exit(1)
222
+ except Exception as e:
223
+ print(f"{R}[✘] Error reading username file: {e}{X}")
224
+ sys.exit(1)
151
225
  else:
152
- # Full scan
153
- from user_scanner.core.orchestrator import run_checks
154
- results = run_all_usernames(run_checks)
155
-
156
- if not args.output:
157
- return
158
-
159
- if args.output and Printer.is_console:
160
- msg = (
161
- "\n[!] The console format cannot be "
162
- f"written to file: '{args.output}'."
163
- )
164
- print(R + msg + Style.RESET_ALL)
165
- return
166
-
167
- content = Printer.get_start()
226
+ is_email = args.email is not None
227
+ if is_email and not re.findall(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", args.email):
228
+ print(R + "[✘] Error: Invalid email format." + X)
229
+ sys.exit(1)
230
+
231
+ target_name = args.username or args.email
232
+ targets = [target_name]
233
+
234
+ # Handle permutations (only for single username/email)
235
+ if args.permute and not (args.username_file or args.email_file):
236
+ target_name = args.username or args.email
237
+ targets = generate_permutations(
238
+ target_name, args.permute, args.stop, is_email)
239
+ print(
240
+ C + f"[+] Generated {len(targets)} permutations" + Style.RESET_ALL)
241
+ elif args.permute and (args.username_file or args.email_file):
242
+ print(f"{R}[✘] Error: Permutations not supported with file-based scanning{X}")
243
+ sys.exit(1)
168
244
 
169
- for i,result in enumerate(results):
170
- char = "" if Printer.is_csv or is_last_value(results, i) else ","
171
- content += "\n" + Printer.get_result_output(result) + char
245
+ results = []
172
246
 
173
- if Printer.is_json:
174
- content += "\n" + Printer.get_end()
247
+ for i, target in enumerate(targets):
248
+ if i != 0 and args.delay:
249
+ time.sleep(args.delay)
175
250
 
176
- with open(args.output, "a", encoding="utf-8") as f:
177
- f.write(content)
251
+ if is_email:
252
+ print(f"\n{Fore.CYAN} Checking email: {target}{Style.RESET_ALL}")
253
+ else:
254
+ print(f"\n{Fore.CYAN} Checking username: {target}{Style.RESET_ALL}")
255
+
256
+ if args.module:
257
+ modules = find_module(args.module, is_email)
258
+ fn = run_email_module_batch if is_email else run_user_module
259
+ if modules:
260
+ for module in modules:
261
+ results.extend(fn(module, target))
262
+ else:
263
+ print(
264
+ R +
265
+ f"[!] {'Email' if is_email else 'User'} module '{args.module}' not found." +
266
+ Style.RESET_ALL
267
+ )
268
+
269
+ elif args.category:
270
+ cat_path = load_categories(is_email).get(args.category)
271
+ fn = run_email_category_batch if is_email else run_user_category
272
+ if cat_path:
273
+ results.extend(fn(cat_path, target))
274
+ else:
275
+ print(
276
+ R +
277
+ f"[!] {'Email' if is_email else 'User'} category '{args.module}' not found." +
278
+ Style.RESET_ALL
279
+ )
280
+ else:
281
+ fn = run_email_full_batch if is_email else run_user_full
282
+ results.extend(fn(target))
283
+
284
+ if args.output:
285
+ content = formatter.into_csv(
286
+ results) if args.format == "csv" else formatter.into_json(results)
287
+ with open(args.output, "a", encoding="utf-8") as f:
288
+ f.write(content)
289
+ print(G + f"\n[+] Results saved to {args.output}" + Style.RESET_ALL)
178
290
 
179
291
 
180
292
  if __name__ == "__main__":
@@ -0,0 +1,78 @@
1
+ import asyncio
2
+ from pathlib import Path
3
+ from typing import List
4
+ from types import ModuleType
5
+ from colorama import Fore, Style
6
+
7
+ from user_scanner.core.helpers import load_categories, load_modules, find_category
8
+ from user_scanner.core.result import Result
9
+
10
+ # Concurrency control
11
+ MAX_CONCURRENT_REQUESTS = 15
12
+
13
+
14
+ async def _async_worker(module: ModuleType, email: str, sem: asyncio.Semaphore) -> Result:
15
+ async with sem:
16
+ module_name = module.__name__.split('.')[-1]
17
+ func_name = f"validate_{module_name}"
18
+
19
+ if not hasattr(module, func_name):
20
+ return Result.error(f"Function {func_name} not found")
21
+
22
+ func = getattr(module, func_name)
23
+
24
+ try:
25
+ res = func(email)
26
+ result = await res if asyncio.iscoroutine(res) else res
27
+ except Exception as e:
28
+ result = Result.error(e)
29
+
30
+ # Use helper to get actual dir name for the Result object
31
+ actual_cat = find_category(module) or "Email"
32
+
33
+ result.update(
34
+ site_name=module_name.capitalize(),
35
+ username=email,
36
+ category=actual_cat,
37
+ is_email=True
38
+ )
39
+
40
+ print(result.get_console_output())
41
+ return result
42
+
43
+
44
+ async def _run_batch(modules: List[ModuleType], email:str) -> List[Result]:
45
+ sem = asyncio.Semaphore(MAX_CONCURRENT_REQUESTS)
46
+ tasks = []
47
+ for module in modules:
48
+ tasks.append(_async_worker(module, email, sem))
49
+
50
+ if not tasks:
51
+ return []
52
+ return list(await asyncio.gather(*tasks))
53
+
54
+
55
+ def run_email_module_batch(module: ModuleType, email: str) -> List[Result]:
56
+ return asyncio.run(_run_batch([module], email))
57
+
58
+
59
+ def run_email_category_batch(category_path: Path, email: str) -> List[Result]:
60
+ cat_name = category_path.stem.capitalize()
61
+ print(f"\n{Fore.MAGENTA}== {cat_name} SITES =={Style.RESET_ALL}")
62
+
63
+ modules = load_modules(category_path)
64
+ return asyncio.run(_run_batch(modules, email))
65
+
66
+
67
+ def run_email_full_batch(email: str) -> List[Result]:
68
+ categories = load_categories(is_email=True)
69
+ all_results = []
70
+
71
+ for cat_name, cat_path in categories.items():
72
+ print(f"\n{Fore.MAGENTA}== {cat_name.upper()} SITES =={Style.RESET_ALL}")
73
+
74
+ modules = load_modules(cat_path)
75
+ cat_results = asyncio.run(_run_batch(modules, email))
76
+ all_results.extend(cat_results)
77
+
78
+ return all_results
@@ -0,0 +1,27 @@
1
+ from user_scanner.core.result import Result
2
+ from typing import List
3
+
4
+ INDENT = " "
5
+ CSV_HEADER = "username,category,site_name,status,reason"
6
+
7
+
8
+ def indentate(msg: str, indent: int):
9
+ if indent <= 0:
10
+ return msg
11
+ tabs = INDENT * indent
12
+ return "\n".join([f"{tabs}{line}" for line in msg.split("\n")])
13
+
14
+
15
+ def into_json(results: List[Result]) -> str:
16
+ res = "[\n"
17
+
18
+ for i, result in enumerate(results):
19
+ is_last = i == len(results) - 1
20
+ end = "" if is_last else ","
21
+ res += indentate(result.to_json().replace("\t", INDENT), 1) + end + "\n"
22
+
23
+ return res + "]"
24
+
25
+
26
+ def into_csv(results: List[Result]) -> str:
27
+ return CSV_HEADER + "\n" + "\n".join(result.to_csv() for result in results)