user-scanner 1.0.5.0__py3-none-any.whl → 1.0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. user_scanner/__init__.py +0 -1
  2. user_scanner/__main__.py +113 -64
  3. user_scanner/cli/banner.py +2 -0
  4. user_scanner/cli/printer.py +117 -0
  5. user_scanner/community/__init__.py +1 -0
  6. user_scanner/community/coderlegion.py +15 -14
  7. user_scanner/community/stackoverflow.py +35 -0
  8. user_scanner/core/orchestrator.py +167 -95
  9. user_scanner/core/result.py +128 -0
  10. user_scanner/core/utils.py +9 -0
  11. user_scanner/creator/devto.py +13 -11
  12. user_scanner/creator/hashnode.py +25 -28
  13. user_scanner/creator/itch_io.py +12 -15
  14. user_scanner/creator/kaggle.py +12 -11
  15. user_scanner/creator/medium.py +18 -22
  16. user_scanner/creator/patreon.py +12 -16
  17. user_scanner/creator/producthunt.py +38 -31
  18. user_scanner/dev/codeberg.py +13 -11
  19. user_scanner/dev/cratesio.py +19 -17
  20. user_scanner/dev/dockerhub.py +17 -15
  21. user_scanner/dev/github.py +46 -27
  22. user_scanner/dev/gitlab.py +18 -15
  23. user_scanner/dev/huggingface.py +19 -0
  24. user_scanner/dev/launchpad.py +19 -17
  25. user_scanner/dev/npmjs.py +21 -34
  26. user_scanner/dev/replit.py +13 -11
  27. user_scanner/donation/buymeacoffee.py +10 -12
  28. user_scanner/donation/liberapay.py +36 -0
  29. user_scanner/gaming/chess_com.py +17 -20
  30. user_scanner/gaming/minecraft.py +19 -0
  31. user_scanner/gaming/monkeytype.py +7 -12
  32. user_scanner/gaming/osu.py +13 -16
  33. user_scanner/gaming/roblox.py +35 -26
  34. user_scanner/gaming/steam.py +18 -19
  35. user_scanner/social/bluesky.py +20 -24
  36. user_scanner/social/discord.py +17 -21
  37. user_scanner/social/instagram.py +22 -20
  38. user_scanner/social/mastodon.py +12 -16
  39. user_scanner/social/pinterest.py +15 -13
  40. user_scanner/social/reddit.py +16 -13
  41. user_scanner/social/snapchat.py +28 -26
  42. user_scanner/social/soundcloud.py +43 -0
  43. user_scanner/social/telegram.py +19 -17
  44. user_scanner/social/threads.py +23 -21
  45. user_scanner/social/x.py +20 -28
  46. user_scanner/social/youtube.py +41 -47
  47. user_scanner/utils/update.py +0 -0
  48. user_scanner/utils/version.py +2 -0
  49. user_scanner/version.json +1 -1
  50. {user_scanner-1.0.5.0.dist-info → user_scanner-1.0.9.0.dist-info}/METADATA +58 -23
  51. user_scanner-1.0.9.0.dist-info/RECORD +61 -0
  52. user_scanner-1.0.5.0.dist-info/RECORD +0 -52
  53. {user_scanner-1.0.5.0.dist-info → user_scanner-1.0.9.0.dist-info}/WHEEL +0 -0
  54. {user_scanner-1.0.5.0.dist-info → user_scanner-1.0.9.0.dist-info}/entry_points.txt +0 -0
  55. {user_scanner-1.0.5.0.dist-info → user_scanner-1.0.9.0.dist-info}/licenses/LICENSE +0 -0
user_scanner/__init__.py CHANGED
@@ -1 +0,0 @@
1
-
user_scanner/__main__.py CHANGED
@@ -1,39 +1,15 @@
1
1
  import argparse
2
+ import time
2
3
  import re
3
- from user_scanner.core.orchestrator import run_checks, load_modules
4
+ from user_scanner.cli import printer
5
+ from user_scanner.core.orchestrator import generate_permutations, load_categories
4
6
  from colorama import Fore, Style
5
- from .cli import banner
6
- from .cli.banner import print_banner
7
-
8
- CATEGORY_MAPPING = {
9
- "dev": "dev",
10
- "social": "social",
11
- "creator": "creator",
12
- "community": "community",
13
- "gaming": "gaming",
14
- "donation": "donation"
15
- }
16
-
17
- def list_modules(category=None):
18
- from user_scanner import dev, social, creator, community, gaming, donation
19
- packages = {
20
- "dev": dev,
21
- "social": social,
22
- "creator": creator,
23
- "community": community,
24
- "gaming": gaming,
25
- "donation": donation
26
- }
27
-
28
- categories_to_list = [category] if category else packages.keys()
29
-
30
- for cat_name in categories_to_list:
31
- package = packages[cat_name]
32
- modules = load_modules(package)
33
- print(Fore.MAGENTA + f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
34
- for module in modules:
35
- site_name = module.__name__.split(".")[-1]
36
- print(f" - {site_name}")
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
11
+
12
+ MAX_PERMUTATIONS_LIMIT = 100 # To prevent excessive generation
37
13
 
38
14
  def main():
39
15
  parser = argparse.ArgumentParser(
@@ -44,7 +20,7 @@ def main():
44
20
  "-u", "--username", help="Username to scan across platforms"
45
21
  )
46
22
  parser.add_argument(
47
- "-c", "--category", choices=CATEGORY_MAPPING.keys(),
23
+ "-c", "--category", choices=load_categories().keys(),
48
24
  help="Scan all platforms in a category"
49
25
  )
50
26
  parser.add_argument(
@@ -57,54 +33,127 @@ def main():
57
33
  "-v", "--verbose", action="store_true", help="Enable verbose output"
58
34
  )
59
35
 
36
+ parser.add_argument(
37
+ "-p", "--permute",type=str,help="Generate username permutations using a string pattern (e.g -p 234)"
38
+ )
39
+ parser.add_argument(
40
+ "-s", "--stop",type=int,default=MAX_PERMUTATIONS_LIMIT,help="Limit the number of username permutations generated"
41
+ )
42
+
43
+ parser.add_argument(
44
+ "-d", "--delay",type=float,default=0,help="Delay in seconds between requests (recommended: 1-2 seconds)"
45
+ )
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
+
60
55
  args = parser.parse_args()
61
56
 
62
- if args.module and "." in args.module:
63
- args.module = args.module.replace(".", "_")
57
+ Printer = printer.Printer(args.format)
64
58
 
65
59
  if args.list:
66
- list_modules(args.category)
60
+ Printer.print_modules(args.category)
67
61
  return
68
62
 
69
-
70
- # Special username checks before run
71
- if (args.module == "x" or args.category == "social"):
72
- if re.search(r"[^a-zA-Z0-9._-]", args.username):
73
- print(Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. X (Twitter) doesn't support these." + Style.RESET_ALL)
74
- if (args.module == "bluesky" or args.category == "social"):
75
- if re.search(r"[^a-zA-Z0-9\.-]", args.username):
76
- print(Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. Bluesky will throw error. (Supported: only hyphens and digits)" + Style.RESET_ALL +"\n")
77
63
  if not args.username:
78
- parser.print_help()
79
- return
80
- else:
81
- print_banner()
64
+ parser.print_help()
65
+ return
82
66
 
83
67
 
84
- from user_scanner import dev, social, creator, community, gaming, donation
68
+ if Printer.is_console:
69
+ print_banner()
70
+
71
+ if args.permute and args.delay == 0 and Printer.is_console:
72
+ print(
73
+ Fore.YELLOW
74
+ + "[!] Warning: You're generating multiple usernames with NO delay between requests. "
75
+ "This may trigger rate limits or IP bans. Use --delay 1 or higher. (Use only if the sites throw errors otherwise ignore)\n"
76
+ + Style.RESET_ALL)
77
+
78
+ usernames = [args.username] # Default single username list
79
+
80
+ #Added permutation support , generate all possible permutation of given sequence.
81
+ if args.permute:
82
+ usernames = generate_permutations(args.username, args.permute , args.stop)
83
+ if Printer.is_console:
84
+ print(
85
+ Fore.CYAN + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
86
+
87
+ if args.module and "." in args.module:
88
+ args.module = args.module.replace(".", "_")
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 = []
85
110
 
86
111
  if args.module:
87
112
  # Single module search across all categories
88
- packages = [dev, social, creator, community, gaming, donation]
89
- found = False
90
- for package in packages:
91
- modules = load_modules(package)
113
+ from user_scanner.core.orchestrator import run_module_single, find_module
114
+ modules = find_module(args.module)
115
+
116
+ if len(modules) > 0:
92
117
  for module in modules:
93
- site_name = module.__name__.split(".")[-1]
94
- if site_name.lower() == args.module.lower():
95
- from user_scanner.core.orchestrator import run_module_single
96
- run_module_single(module, args.username)
97
- found = True
98
- if not found:
99
- print(Fore.RED + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
118
+ results.extend(run_all_usernames(run_module_single, module))
119
+ else:
120
+ print(
121
+ Fore.RED + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
122
+
100
123
  elif args.category:
101
124
  # Category-wise scan
102
- category_package = eval(CATEGORY_MAPPING[args.category])
125
+ category_package = load_categories().get(args.category)
103
126
  from user_scanner.core.orchestrator import run_checks_category
104
- run_checks_category(category_package, args.username, args.verbose)
127
+ results = run_all_usernames(run_checks_category, category_package)
128
+
105
129
  else:
106
130
  # Full scan
107
- run_checks(args.username)
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)
156
+
108
157
 
109
158
  if __name__ == "__main__":
110
159
  main()
@@ -23,10 +23,12 @@ INFO_BOX = f"""{C_MAGENTA} ╔═══════════════
23
23
  ║ {C_RED}♚ {C_GREEN}Email{C_WHITE} : kaifcodec@gmail.com {C_MAGENTA}║
24
24
  ══════════════════════════════════════════{Style.RESET_ALL}""".strip()
25
25
 
26
+
26
27
  def print_banner():
27
28
  print(BANNER_ASCII)
28
29
  print(INFO_BOX)
29
30
  print(" ")
30
31
 
32
+
31
33
  if __name__ == "__main__":
32
34
  print_banner()
@@ -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
@@ -1,18 +1,19 @@
1
- from ..core.orchestrator import status_validate
1
+ from user_scanner.core.orchestrator import status_validate
2
2
 
3
- def validate_coderlegion(user):
4
- url = f"https://coderlegion.com/user/{user}"
5
-
6
- return status_validate(url, 404, 200, timeout = 15.0)
7
3
 
8
- if __name__ == "__main__":
9
- user = input ("Username?: ").strip()
10
- result = validate_coderlegion(user)
4
+ def validate_coderlegion(user):
5
+ url = f"https://coderlegion.com/user/{user}"
6
+
7
+ return status_validate(url, 404, 200, timeout=15.0)
11
8
 
12
- if result == 1:
13
- print("Available!")
14
- elif result == 0:
15
- print("Unavailable!")
16
- else:
17
- print("Error occured!")
18
9
 
10
+ if __name__ == "__main__":
11
+ user = input("Username?: ").strip()
12
+ result = validate_coderlegion(user)
13
+
14
+ if result == 1:
15
+ print("Available!")
16
+ elif result == 0:
17
+ print("Unavailable!")
18
+ else:
19
+ print("Error occured!")
@@ -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)