user-scanner 1.0.2.1__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 +114 -61
  3. user_scanner/cli/banner.py +3 -8
  4. user_scanner/cli/printer.py +117 -0
  5. user_scanner/community/__init__.py +1 -0
  6. user_scanner/community/coderlegion.py +11 -31
  7. user_scanner/community/stackoverflow.py +35 -0
  8. user_scanner/core/orchestrator.py +199 -79
  9. user_scanner/core/result.py +128 -0
  10. user_scanner/core/utils.py +9 -0
  11. user_scanner/creator/devto.py +11 -29
  12. user_scanner/creator/hashnode.py +25 -28
  13. user_scanner/creator/itch_io.py +19 -0
  14. user_scanner/creator/kaggle.py +11 -29
  15. user_scanner/creator/medium.py +18 -22
  16. user_scanner/creator/patreon.py +12 -38
  17. user_scanner/creator/producthunt.py +47 -0
  18. user_scanner/dev/codeberg.py +11 -29
  19. user_scanner/dev/cratesio.py +11 -24
  20. user_scanner/dev/dockerhub.py +11 -27
  21. user_scanner/dev/github.py +43 -39
  22. user_scanner/dev/gitlab.py +21 -32
  23. user_scanner/dev/huggingface.py +19 -0
  24. user_scanner/dev/launchpad.py +11 -24
  25. user_scanner/dev/npmjs.py +21 -34
  26. user_scanner/dev/replit.py +11 -29
  27. user_scanner/donation/__init__.py +0 -0
  28. user_scanner/donation/buymeacoffee.py +19 -0
  29. user_scanner/donation/liberapay.py +36 -0
  30. user_scanner/gaming/chess_com.py +20 -36
  31. user_scanner/gaming/minecraft.py +19 -0
  32. user_scanner/gaming/monkeytype.py +8 -26
  33. user_scanner/gaming/osu.py +13 -38
  34. user_scanner/gaming/roblox.py +37 -42
  35. user_scanner/gaming/steam.py +29 -0
  36. user_scanner/social/bluesky.py +21 -38
  37. user_scanner/social/discord.py +17 -21
  38. user_scanner/social/instagram.py +11 -24
  39. user_scanner/social/mastodon.py +12 -38
  40. user_scanner/social/pinterest.py +18 -32
  41. user_scanner/social/reddit.py +19 -32
  42. user_scanner/social/snapchat.py +24 -37
  43. user_scanner/social/soundcloud.py +43 -0
  44. user_scanner/social/telegram.py +19 -24
  45. user_scanner/social/threads.py +11 -24
  46. user_scanner/social/x.py +20 -28
  47. user_scanner/social/youtube.py +41 -47
  48. user_scanner/utils/version.py +2 -0
  49. user_scanner/version.json +1 -1
  50. {user_scanner-1.0.2.1.dist-info → user_scanner-1.0.9.0.dist-info}/METADATA +62 -67
  51. user_scanner-1.0.9.0.dist-info/RECORD +61 -0
  52. user_scanner-1.0.2.1.dist-info/RECORD +0 -48
  53. {user_scanner-1.0.2.1.dist-info → user_scanner-1.0.9.0.dist-info}/WHEEL +0 -0
  54. {user_scanner-1.0.2.1.dist-info → user_scanner-1.0.9.0.dist-info}/entry_points.txt +0 -0
  55. {user_scanner-1.0.2.1.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,38 +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
-
9
- CATEGORY_MAPPING = {
10
- "dev": "dev",
11
- "social": "social",
12
- "creator": "creator",
13
- "community": "community",
14
- "gaming": "gaming"
15
- }
16
-
17
- def list_modules(category=None):
18
- from user_scanner import dev, social, creator, community, gaming
19
- packages = {
20
- "dev": dev,
21
- "social": social,
22
- "creator": creator,
23
- "community": community,
24
- "gaming": gaming
25
- }
26
-
27
- categories_to_list = [category] if category else packages.keys()
28
-
29
- for cat_name in categories_to_list:
30
- package = packages[cat_name]
31
- modules = load_modules(package)
32
- print(Fore.MAGENTA + f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
33
- for module in modules:
34
- site_name = module.__name__.split(".")[-1]
35
- 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
36
13
 
37
14
  def main():
38
15
  parser = argparse.ArgumentParser(
@@ -43,7 +20,7 @@ def main():
43
20
  "-u", "--username", help="Username to scan across platforms"
44
21
  )
45
22
  parser.add_argument(
46
- "-c", "--category", choices=CATEGORY_MAPPING.keys(),
23
+ "-c", "--category", choices=load_categories().keys(),
47
24
  help="Scan all platforms in a category"
48
25
  )
49
26
  parser.add_argument(
@@ -56,51 +33,127 @@ def main():
56
33
  "-v", "--verbose", action="store_true", help="Enable verbose output"
57
34
  )
58
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
+
59
55
  args = parser.parse_args()
60
56
 
57
+ Printer = printer.Printer(args.format)
58
+
61
59
  if args.list:
62
- list_modules(args.category)
60
+ Printer.print_modules(args.category)
63
61
  return
64
62
 
65
-
66
- # Special username checks before run
67
- if (args.module == "x" or args.category == "social"):
68
- if re.search(r"[^a-zA-Z0-9._-]", args.username):
69
- print(Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. X (Twitter) doesn't support these." + Style.RESET_ALL)
70
- if (args.module == "bluesky" or args.category == "social"):
71
- if re.search(r"[^a-zA-Z0-9\.-]", args.username):
72
- print(Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. Bluesky will throw error. (Supported: only hyphens and digits)" + Style.RESET_ALL +"\n")
73
63
  if not args.username:
74
- parser.print_help()
75
- return
76
- else:
77
- print_banner()
64
+ parser.print_help()
65
+ return
78
66
 
79
67
 
80
- from user_scanner import dev, social, creator, community, gaming
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 = []
81
110
 
82
111
  if args.module:
83
112
  # Single module search across all categories
84
- packages = [dev, social, creator, community, gaming]
85
- found = False
86
- for package in packages:
87
- 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:
88
117
  for module in modules:
89
- site_name = module.__name__.split(".")[-1]
90
- if site_name.lower() == args.module.lower():
91
- from user_scanner.core.orchestrator import run_module_single
92
- run_module_single(module, args.username)
93
- found = True
94
- if not found:
95
- 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
+
96
123
  elif args.category:
97
124
  # Category-wise scan
98
- category_package = eval(CATEGORY_MAPPING[args.category])
125
+ category_package = load_categories().get(args.category)
99
126
  from user_scanner.core.orchestrator import run_checks_category
100
- run_checks_category(category_package, args.username, args.verbose)
127
+ results = run_all_usernames(run_checks_category, category_package)
128
+
101
129
  else:
102
130
  # Full scan
103
- 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
+
104
157
 
105
158
  if __name__ == "__main__":
106
159
  main()
@@ -23,17 +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
- if __name__ == "__main__":
31
- print_banner()
32
-
33
-
34
-
35
-
36
-
37
-
38
31
 
39
32
 
33
+ if __name__ == "__main__":
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,39 +1,19 @@
1
- import httpx
2
- from httpx import ConnectError, TimeoutException
1
+ from user_scanner.core.orchestrator import status_validate
2
+
3
3
 
4
4
  def validate_coderlegion(user):
5
5
  url = f"https://coderlegion.com/user/{user}"
6
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
- try:
14
- response = httpx.get(url, headers=headers, timeout = 15.0)
15
- status = response.status_code
16
-
17
- if status == 200:
18
- return 0
19
- elif status == 404:
20
- return 1
21
- else:
22
- return 2
7
+ return status_validate(url, 404, 200, timeout=15.0)
23
8
 
24
- except (ConnectError, TimeoutException):
25
- return 2
26
- except Exception:
27
- return 2
28
9
 
29
10
  if __name__ == "__main__":
30
- user = input ("Username?: ").strip()
31
- result = validate_coderlegion(user)
32
-
33
- if result == 1:
34
- print("Available!")
35
- elif result == 0:
36
- print("Unavailable!")
37
- else:
38
- print("Error occured!")
11
+ user = input("Username?: ").strip()
12
+ result = validate_coderlegion(user)
39
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)