user-scanner 1.0.9.1__tar.gz → 1.0.10.1__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 (73) hide show
  1. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/PKG-INFO +18 -6
  2. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/README.md +16 -4
  3. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/pyproject.toml +6 -2
  4. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/__main__.py +30 -8
  5. user_scanner-1.0.10.1/user_scanner/cli/banner.py +24 -0
  6. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/cli/printer.py +2 -2
  7. user_scanner-1.0.10.1/user_scanner/community/hackernews.py +33 -0
  8. user_scanner-1.0.10.1/user_scanner/config.json +1 -0
  9. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/core/orchestrator.py +14 -13
  10. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/core/result.py +2 -5
  11. {user_scanner-1.0.9.1/user_scanner/utils → user_scanner-1.0.10.1/user_scanner/core}/version.py +12 -3
  12. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/creator/hashnode.py +0 -1
  13. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/creator/medium.py +14 -3
  14. user_scanner-1.0.10.1/user_scanner/creator/substack.py +36 -0
  15. user_scanner-1.0.10.1/user_scanner/gaming/lichess.py +40 -0
  16. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/gaming/monkeytype.py +15 -4
  17. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/discord.py +0 -1
  18. user_scanner-1.0.10.1/user_scanner/social/mastodon.py +30 -0
  19. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/soundcloud.py +1 -6
  20. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/telegram.py +2 -2
  21. user_scanner-1.0.10.1/user_scanner/social/tiktok.py +50 -0
  22. user_scanner-1.0.10.1/user_scanner/utils/update.py +34 -0
  23. user_scanner-1.0.10.1/user_scanner/utils/updater_logic.py +83 -0
  24. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/version.json +1 -1
  25. user_scanner-1.0.9.1/user_scanner/cli/banner.py +0 -34
  26. user_scanner-1.0.9.1/user_scanner/social/mastodon.py +0 -19
  27. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/LICENSE +0 -0
  28. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/__init__.py +0 -0
  29. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/cli/__init__.py +0 -0
  30. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/community/__init__.py +0 -0
  31. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/community/coderlegion.py +0 -0
  32. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/community/stackoverflow.py +0 -0
  33. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/core/__init__.py +0 -0
  34. /user_scanner-1.0.9.1/user_scanner/core/utils.py → /user_scanner-1.0.10.1/user_scanner/core/helpers.py +0 -0
  35. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/creator/__init__.py +0 -0
  36. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/creator/devto.py +0 -0
  37. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/creator/itch_io.py +0 -0
  38. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/creator/kaggle.py +0 -0
  39. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/creator/patreon.py +0 -0
  40. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/creator/producthunt.py +0 -0
  41. {user_scanner-1.0.9.1/user_scanner/social → user_scanner-1.0.10.1/user_scanner/creator}/twitch.py +0 -0
  42. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/__init__.py +0 -0
  43. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/bitbucket.py +0 -0
  44. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/codeberg.py +0 -0
  45. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/cratesio.py +0 -0
  46. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/dockerhub.py +0 -0
  47. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/github.py +0 -0
  48. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/gitlab.py +0 -0
  49. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/huggingface.py +0 -0
  50. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/launchpad.py +0 -0
  51. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/leetcode.py +0 -0
  52. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/npmjs.py +0 -0
  53. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/replit.py +0 -0
  54. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/dev/sourceforge.py +0 -0
  55. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/donation/__init__.py +0 -0
  56. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/donation/buymeacoffee.py +0 -0
  57. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/donation/liberapay.py +0 -0
  58. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/gaming/__init__.py +0 -0
  59. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/gaming/chess_com.py +0 -0
  60. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/gaming/minecraft.py +0 -0
  61. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/gaming/osu.py +0 -0
  62. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/gaming/roblox.py +0 -0
  63. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/gaming/steam.py +0 -0
  64. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/__init__.py +0 -0
  65. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/bluesky.py +0 -0
  66. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/instagram.py +0 -0
  67. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/pinterest.py +0 -0
  68. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/reddit.py +0 -0
  69. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/snapchat.py +0 -0
  70. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/threads.py +0 -0
  71. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/x.py +0 -0
  72. {user_scanner-1.0.9.1 → user_scanner-1.0.10.1}/user_scanner/social/youtube.py +0 -0
  73. /user_scanner-1.0.9.1/user_scanner/utils/update.py → /user_scanner-1.0.10.1/user_scanner/utils/__init__.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: user-scanner
3
- Version: 1.0.9.1
3
+ Version: 1.0.10.1
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>
7
- Requires-Python: >=3.7
7
+ Requires-Python: >=3.10
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
10
  Requires-Dist: httpx
@@ -13,9 +13,9 @@ Project-URL: Homepage, https://github.com/kaifcodec/user-scanner
13
13
 
14
14
  # User Scanner
15
15
 
16
- ![1000136215](https://github.com/user-attachments/assets/49ec8d24-665b-4115-8525-01a8d0ca2ef4)
16
+ ![User Scanner Logo](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.9.1-blueviolet?style=for-the-badge&logo=github" />
18
+ <img src="https://img.shields.io/badge/Version-1.0.10.1-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" />
@@ -32,6 +32,8 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
32
32
  ### Features
33
33
 
34
34
  - ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**
35
+ - ✅ Can be used as a username OSINT tool
36
+ - ✅ Smart auto-update system, Detects new releases on PyPI and interactively prompts the user to upgrade.
35
37
  - ✅ Clear **Available / Taken / Error** output for each platform
36
38
  - ✅ Robust error handling: It prints the exact reason (e.g. Cannot use underscores, hyphens at the start/end)
37
39
  - ✅ Fully modular: add new platform modules easily
@@ -39,7 +41,6 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
39
41
  - ✅ Selection of results format (e.g. json, csv, console (default))
40
42
  - ✅ Get the scanning results in preferred format (json/csv) in specified output file (suitable for power users)
41
43
  - ✅ Command-line interface ready: works directly after `pip install`
42
- - ✅ Can be used as username OSINT tool
43
44
  - ✅ Very low and lightweight dependencies, can be run on any machine
44
45
  ---
45
46
 
@@ -78,7 +79,7 @@ user-scanner -u <username> -f <format> -o <output-file>
78
79
  Generate multiple username variations by appending a suffix:
79
80
 
80
81
  ```bash
81
- user-scanner -u <username> -p <suffix>
82
+ user-scanner -u <username> -p <suffix>
82
83
  ```
83
84
  Optionally, scan a specific category or single module with limit:
84
85
 
@@ -87,9 +88,20 @@ user-scanner -u <username> -p <suffix> -c dev
87
88
  user-scanner -u <username> -p <suffix> -m github
88
89
  user-scanner -u <username> -p <suffix> -s <number> # limit generation of usernames
89
90
  user-scanner -u <username> -p <suffix> -d <seconds> # delay to avoid rate-limits (can be 0s-1s)
91
+ ```
92
+ ---
93
+
94
+ ### Update
95
+
96
+ Update the tool to the latest PyPI version:
97
+
98
+ ```bash
99
+ user-scanner -U
100
+
90
101
  ```
91
102
 
92
103
  ---
104
+
93
105
  ### Screenshot:
94
106
 
95
107
  - Note*: New modules are constantly getting added so this might have only limited, outdated output:
@@ -1,8 +1,8 @@
1
1
  # User Scanner
2
2
 
3
- ![1000136215](https://github.com/user-attachments/assets/49ec8d24-665b-4115-8525-01a8d0ca2ef4)
3
+ ![User Scanner Logo](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.9.1-blueviolet?style=for-the-badge&logo=github" />
5
+ <img src="https://img.shields.io/badge/Version-1.0.10.1-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" />
@@ -19,6 +19,8 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
19
19
  ### Features
20
20
 
21
21
  - ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**
22
+ - ✅ Can be used as a username OSINT tool
23
+ - ✅ Smart auto-update system, Detects new releases on PyPI and interactively prompts the user to upgrade.
22
24
  - ✅ Clear **Available / Taken / Error** output for each platform
23
25
  - ✅ Robust error handling: It prints the exact reason (e.g. Cannot use underscores, hyphens at the start/end)
24
26
  - ✅ Fully modular: add new platform modules easily
@@ -26,7 +28,6 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
26
28
  - ✅ Selection of results format (e.g. json, csv, console (default))
27
29
  - ✅ Get the scanning results in preferred format (json/csv) in specified output file (suitable for power users)
28
30
  - ✅ Command-line interface ready: works directly after `pip install`
29
- - ✅ Can be used as username OSINT tool
30
31
  - ✅ Very low and lightweight dependencies, can be run on any machine
31
32
  ---
32
33
 
@@ -65,7 +66,7 @@ user-scanner -u <username> -f <format> -o <output-file>
65
66
  Generate multiple username variations by appending a suffix:
66
67
 
67
68
  ```bash
68
- user-scanner -u <username> -p <suffix>
69
+ user-scanner -u <username> -p <suffix>
69
70
  ```
70
71
  Optionally, scan a specific category or single module with limit:
71
72
 
@@ -74,9 +75,20 @@ user-scanner -u <username> -p <suffix> -c dev
74
75
  user-scanner -u <username> -p <suffix> -m github
75
76
  user-scanner -u <username> -p <suffix> -s <number> # limit generation of usernames
76
77
  user-scanner -u <username> -p <suffix> -d <seconds> # delay to avoid rate-limits (can be 0s-1s)
78
+ ```
79
+ ---
80
+
81
+ ### Update
82
+
83
+ Update the tool to the latest PyPI version:
84
+
85
+ ```bash
86
+ user-scanner -U
87
+
77
88
  ```
78
89
 
79
90
  ---
91
+
80
92
  ### Screenshot:
81
93
 
82
94
  - Note*: New modules are constantly getting added so this might have only limited, outdated output:
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "user-scanner"
7
- version = "1.0.9.1"
7
+ version = "1.0.10.1"
8
8
  description = "Check username availability across multiple popular platforms"
9
9
  readme = "README.md"
10
10
  license = {file = "LICENSE"}
@@ -16,7 +16,7 @@ dependencies = [
16
16
  "colorama"
17
17
  ]
18
18
 
19
- requires-python = ">=3.7"
19
+ requires-python = ">=3.10"
20
20
  keywords = ["username", "checker", "availability", "social", "tech", "python", "user-scanner"]
21
21
 
22
22
  [project.urls]
@@ -27,3 +27,7 @@ user-scanner = "user_scanner.__main__:main"
27
27
 
28
28
  [tool.flit.sdist]
29
29
  exclude = ["tests/", "docs/", ".github/", ".git", "rm_pycache.py", "che.py", ".gitignore"]
30
+
31
+ [tool.pytest.ini_options]
32
+ pythonpath = "."
33
+ testpaths = ["tests"]
@@ -1,17 +1,29 @@
1
1
  import argparse
2
2
  import time
3
- import re
3
+ import sys
4
4
  from user_scanner.cli import printer
5
5
  from user_scanner.core.orchestrator import generate_permutations, load_categories
6
6
  from colorama import Fore, Style
7
7
  from user_scanner.cli.banner import print_banner
8
8
  from typing import List
9
9
  from user_scanner.core.result import Result
10
- from user_scanner.core.utils import is_last_value
10
+ from user_scanner.core.helpers import is_last_value
11
+ from user_scanner.utils.updater_logic import check_for_updates
12
+ from user_scanner.utils.update import update_self
13
+
14
+ # Color configs
15
+ R = Fore.RED
16
+ G = Fore.GREEN
17
+ C = Fore.CYAN
18
+ Y = Fore.YELLOW
19
+ X = Fore.RESET
20
+
11
21
 
12
22
  MAX_PERMUTATIONS_LIMIT = 100 # To prevent excessive generation
13
23
 
24
+
14
25
  def main():
26
+
15
27
  parser = argparse.ArgumentParser(
16
28
  prog="user-scanner",
17
29
  description="Scan usernames across multiple platforms."
@@ -51,15 +63,25 @@ def main():
51
63
  parser.add_argument(
52
64
  "-o", "--output", type=str, help="Specify the output file"
53
65
  )
66
+ parser.add_argument(
67
+ "-U", "--update", action="store_true", help="Update user-scanner to latest version"
68
+ )
54
69
 
55
70
  args = parser.parse_args()
56
71
 
57
72
  Printer = printer.Printer(args.format)
58
73
 
74
+ if args.update is True:
75
+ update_self()
76
+ print(f"[{G}+{X}] {G}Update successful. Please restart the tool.{X}")
77
+ sys.exit(0)
78
+
59
79
  if args.list:
60
80
  Printer.print_modules(args.category)
61
81
  return
62
82
 
83
+ check_for_updates()
84
+
63
85
  if not args.username:
64
86
  parser.print_help()
65
87
  return
@@ -70,19 +92,19 @@ def main():
70
92
 
71
93
  if args.permute and args.delay == 0 and Printer.is_console:
72
94
  print(
73
- Fore.YELLOW
95
+ Y
74
96
  + "[!] Warning: You're generating multiple usernames with NO delay between requests. "
75
97
  "This may trigger rate limits or IP bans. Use --delay 1 or higher. (Use only if the sites throw errors otherwise ignore)\n"
76
98
  + Style.RESET_ALL)
77
99
 
78
100
  usernames = [args.username] # Default single username list
79
101
 
80
- #Added permutation support , generate all possible permutation of given sequence.
102
+ # Added permutation support , generate all possible permutation of given sequence.
81
103
  if args.permute:
82
104
  usernames = generate_permutations(args.username, args.permute , args.stop)
83
105
  if Printer.is_console:
84
106
  print(
85
- Fore.CYAN + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
107
+ C + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
86
108
 
87
109
  if args.module and "." in args.module:
88
110
  args.module = args.module.replace(".", "_")
@@ -96,7 +118,7 @@ def main():
96
118
  print(Printer.get_start())
97
119
  for i, name in enumerate(usernames):
98
120
  is_last = i == len(usernames) - 1
99
- if arg == None:
121
+ if arg is None:
100
122
  results.extend(func(name, Printer, is_last))
101
123
  else:
102
124
  results.extend(func(arg, name, Printer, is_last))
@@ -118,7 +140,7 @@ def main():
118
140
  results.extend(run_all_usernames(run_module_single, module))
119
141
  else:
120
142
  print(
121
- Fore.RED + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
143
+ R + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
122
144
 
123
145
  elif args.category:
124
146
  # Category-wise scan
@@ -139,7 +161,7 @@ def main():
139
161
  "\n[!] The console format cannot be "
140
162
  f"written to file: '{args.output}'."
141
163
  )
142
- print(Fore.RED + msg + Style.RESET_ALL)
164
+ print(R + msg + Style.RESET_ALL)
143
165
  return
144
166
 
145
167
  content = Printer.get_start()
@@ -0,0 +1,24 @@
1
+ from colorama import Fore, Style, init
2
+ from ..core.version import load_local_version
3
+
4
+
5
+ version, version_type = load_local_version()
6
+ init(autoreset=True)
7
+ C_RED = Fore.RED + Style.BRIGHT
8
+ C_CYAN = Fore.CYAN + Style.BRIGHT
9
+ C_GREEN = Fore.GREEN + Style.BRIGHT
10
+ C_WHITE = Fore.WHITE + Style.BRIGHT
11
+ C_MAGENTA = Fore.MAGENTA + Style.BRIGHT
12
+ BANNER_ASCII = fr"""{C_CYAN} _ _ ___ ___ _ __ ___ ___ __ _ _ __ _ __ ___ _ __
13
+ | | | / __|/ _ \ '__|____/ __|/ __/ _` | '_ \| '_ \ / _ \ '__|
14
+ | |_| \__ \ __/ | |_____\__ \ (_| (_| | | | | | | | __/ |
15
+ \__,_|___/\___|_| |___/\___\__,_|_| |_|_| |_|\___|_| Version: {version}
16
+ {Style.RESET_ALL}""".strip()
17
+
18
+
19
+ def print_banner():
20
+ print(BANNER_ASCII)
21
+
22
+
23
+ if __name__ == "__main__":
24
+ print_banner()
@@ -15,7 +15,7 @@ def indentate(msg: str, indent: int):
15
15
 
16
16
  class Printer:
17
17
  def __init__(self, format: Literal["console", "csv", "json"]) -> None:
18
- if not format in ["console", "csv", "json"]:
18
+ if format not in ["console", "csv", "json"]:
19
19
  raise ValueError(f"Invalid output-format: {format}")
20
20
  self.mode: str = format
21
21
  self.indent: int = 0
@@ -42,7 +42,7 @@ class Printer:
42
42
 
43
43
  def get_end(self, json_char: str = "]") -> str:
44
44
  if not self.is_json:
45
- return
45
+ return ""
46
46
  self.indent = max(self.indent - 1, 0)
47
47
  return indentate(json_char, self.indent)
48
48
 
@@ -0,0 +1,33 @@
1
+ import re
2
+ from user_scanner.core.orchestrator import generic_validate, Result
3
+
4
+
5
+ def validate_hackernews(user: str) -> Result:
6
+ if not (2 <= len(user) <= 15):
7
+ return Result.error("Length must be 2-15 characters")
8
+
9
+ if not re.match(r'^[a-zA-Z0-9_-]+$', user):
10
+ return Result.error("Only use letters, numbers, underscores, and hyphens")
11
+
12
+ url = f"https://news.ycombinator.com/user?id={user}"
13
+
14
+ def process(response):
15
+ if "No such user." in response.text:
16
+ return Result.available()
17
+ if f"profile: {user}" in response.text.lower() or "created:" in response.text:
18
+ return Result.taken()
19
+ return Result.error("Unexpected response structure")
20
+
21
+ return generic_validate(url, process, timeout=3.0, follow_redirects=True)
22
+
23
+
24
+ if __name__ == "__main__":
25
+ user = input("Username?: ").strip()
26
+ result = validate_hackernews(user)
27
+
28
+ if result == 1:
29
+ print("Available!")
30
+ elif result == 0:
31
+ print("Unavailable!")
32
+ else:
33
+ print(f"Error occurred! Reason: {result.get_reason()}")
@@ -0,0 +1 @@
1
+ {"auto_update_status": true}
@@ -6,9 +6,9 @@ from itertools import permutations
6
6
  import httpx
7
7
  from pathlib import Path
8
8
  from user_scanner.cli.printer import Printer
9
- from user_scanner.core.result import Result, AnyResult
9
+ from user_scanner.core.result import Result
10
10
  from typing import Callable, Dict, List
11
- from user_scanner.core.utils import get_site_name, is_last_value
11
+ from user_scanner.core.helpers import get_site_name, is_last_value
12
12
 
13
13
 
14
14
  def load_modules(category_path: Path):
@@ -17,6 +17,8 @@ def load_modules(category_path: Path):
17
17
  if file.name == "__init__.py":
18
18
  continue
19
19
  spec = importlib.util.spec_from_file_location(file.stem, str(file))
20
+ if spec is None or spec.loader is None:
21
+ continue
20
22
  module = importlib.util.module_from_spec(spec)
21
23
  spec.loader.exec_module(module)
22
24
 
@@ -30,8 +32,8 @@ def load_categories() -> Dict[str, Path]:
30
32
 
31
33
  for subfolder in root.iterdir():
32
34
  if subfolder.is_dir() and \
33
- not subfolder.name.lower() in ["cli", "utils", "core"] and \
34
- not "__" in subfolder.name: # Removes __pycache__
35
+ subfolder.name.lower() not in ["cli", "utils", "core"] and \
36
+ "__" not in subfolder.name: # Removes __pycache__
35
37
  categories[subfolder.name] = subfolder.resolve()
36
38
 
37
39
  return categories
@@ -88,9 +90,9 @@ def run_module_single(module, username: str, printer: Printer, last: bool = True
88
90
  if category:
89
91
  result.update(category=category)
90
92
 
91
- site_name = get_site_name(module)
93
+ get_site_name(module)
92
94
  msg = printer.get_result_output(result)
93
- if last == False and printer.is_json:
95
+ if not last and printer.is_json:
94
96
  msg += ","
95
97
  print(msg)
96
98
 
@@ -114,9 +116,9 @@ def run_checks_category(category_path: Path, username: str, printer: Printer, la
114
116
  results.append(result)
115
117
 
116
118
  is_last = last and is_last_value(modules, i)
117
- site_name = get_site_name(modules[i])
119
+ get_site_name(modules[i])
118
120
  msg = printer.get_result_output(result)
119
- if is_last == False and printer.is_json:
121
+ if not is_last and printer.is_json:
120
122
  msg += ","
121
123
  print(msg)
122
124
 
@@ -131,7 +133,7 @@ def run_checks(username: str, printer: Printer, last: bool = True) -> List[Resul
131
133
 
132
134
  categories = list(load_categories().values())
133
135
  for i, category_path in enumerate(categories):
134
- last_cat: int = last and (i == len(categories) - 1)
136
+ last_cat = last and (i == len(categories) - 1)
135
137
  temp = run_checks_category(category_path, username, printer, last_cat)
136
138
  results.extend(temp)
137
139
 
@@ -140,7 +142,7 @@ def run_checks(username: str, printer: Printer, last: bool = True) -> List[Resul
140
142
 
141
143
  def make_request(url: str, **kwargs) -> httpx.Response:
142
144
  """Simple wrapper to **httpx.get** that predefines headers and timeout"""
143
- if not "headers" in kwargs:
145
+ if "headers" not in kwargs:
144
146
  kwargs["headers"] = {
145
147
  'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
146
148
  'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
@@ -149,7 +151,7 @@ def make_request(url: str, **kwargs) -> httpx.Response:
149
151
  'sec-fetch-dest': "document",
150
152
  }
151
153
 
152
- if not "timeout" in kwargs:
154
+ if "timeout" not in kwargs:
153
155
  kwargs["timeout"] = 5.0
154
156
 
155
157
  method = kwargs.pop("method", "GET")
@@ -157,14 +159,13 @@ def make_request(url: str, **kwargs) -> httpx.Response:
157
159
  return httpx.request(method.upper(), url, **kwargs)
158
160
 
159
161
 
160
- def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kwargs) -> AnyResult:
162
+ def generic_validate(url: str, func: Callable[[httpx.Response], Result], **kwargs) -> Result:
161
163
  """
162
164
  A generic validate function that makes a request and executes the provided function on the response.
163
165
  """
164
166
  try:
165
167
  response = make_request(url, **kwargs)
166
168
  result = func(response)
167
- result.url = url
168
169
  return result
169
170
  except Exception as e:
170
171
  return Result.error(e, url=url)
@@ -1,5 +1,4 @@
1
1
  from enum import Enum
2
- from typing import Literal
3
2
 
4
3
  DEBUG_MSG = """Result {{
5
4
  status: {status},
@@ -80,10 +79,10 @@ class Result:
80
79
  return self.status.value
81
80
 
82
81
  def has_reason(self) -> bool:
83
- return self.reason != None
82
+ return self.reason is not None
84
83
 
85
84
  def get_reason(self) -> str:
86
- if self.reason == None:
85
+ if self.reason is None:
87
86
  return ""
88
87
  if isinstance(self.reason, str):
89
88
  return self.reason
@@ -124,5 +123,3 @@ class Result:
124
123
 
125
124
  return NotImplemented
126
125
 
127
-
128
- AnyResult = Literal[0, 1, 2] | Result
@@ -1,14 +1,15 @@
1
1
  import json
2
+ import httpx
2
3
  from pathlib import Path
3
4
 
4
- _SCRIPT_DIR = Path(__file__).resolve().parent
5
- VERSION_FILE = _SCRIPT_DIR.parent / "version.json"
5
+ SCRIPT_DIR = Path(__file__).parent
6
+ VERSION_FILE = SCRIPT_DIR.parent / "version.json"
6
7
 
7
8
 
8
9
  def load_local_version():
9
10
  try:
10
11
  data = json.loads(VERSION_FILE.read_text())
11
- return data.get("version", "0.0.0"), data.get("version_type", "local")
12
+ return data.get("version", "error_report_via_gh_issues"), data.get("version_type", "local")
12
13
  except FileNotFoundError:
13
14
  return "N/A", "file_missing"
14
15
  except json.JSONDecodeError:
@@ -17,6 +18,14 @@ def load_local_version():
17
18
  return "N/A", "error"
18
19
 
19
20
 
21
+ def get_pypi_version(pypi_url):
22
+ try:
23
+ pypi_version = httpx.get(pypi_url, timeout=7).json()["info"]["version"]
24
+ except Exception as e:
25
+ print(e)
26
+ return None
27
+ return pypi_version
28
+
20
29
  if __name__ == "__main__":
21
30
  version, version_type = load_local_version()
22
31
  print(f"Version: {version}, Type: {version_type}")
@@ -1,4 +1,3 @@
1
- import httpx
2
1
  from user_scanner.core.result import Result
3
2
  from user_scanner.core.orchestrator import generic_validate
4
3
 
@@ -6,8 +6,19 @@ def validate_medium(user):
6
6
  url = f"https://medium.com/@{user}"
7
7
 
8
8
  headers = {
9
- 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
10
- 'Accept': "text/html",
9
+ 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
10
+ 'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
11
+ 'Accept-Encoding': "identity",
12
+ 'upgrade-insecure-requests': "1",
13
+ 'sec-fetch-site': "none",
14
+ 'sec-fetch-mode': "navigate",
15
+ 'sec-fetch-user': "?1",
16
+ 'sec-fetch-dest': "document",
17
+ 'sec-ch-ua': "\"Google Chrome\";v=\"143\", \"Chromium\";v=\"143\", \"Not A(Brand\";v=\"24\"",
18
+ 'sec-ch-ua-mobile': "?0",
19
+ 'sec-ch-ua-platform': "\"Linux\"",
20
+ 'accept-language': "en-US,en;q=0.9",
21
+ 'priority': "u=0, i"
11
22
  }
12
23
 
13
24
  def process(response):
@@ -34,4 +45,4 @@ if __name__ == "__main__":
34
45
  elif result == 0:
35
46
  print("Unavailable!")
36
47
  else:
37
- print("Error occurred!")
48
+ print(f"Error occurred! Reason: {result.get_reason()}")
@@ -0,0 +1,36 @@
1
+ import re
2
+ from user_scanner.core.orchestrator import status_validate, Result
3
+
4
+
5
+ def validate_substack(user: str) -> Result:
6
+ if not (4 <= len(user) <= 32):
7
+ return Result.error("Length must be 4-32 characters")
8
+
9
+ if not re.match(r'^[a-z0-9]+$', user):
10
+ if re.search(r'[A-Z]', user):
11
+ return Result.error("Use lowercase letters only")
12
+ return Result.error("Usernames can only contain lowercase letters and numbers")
13
+
14
+ url = f"https://{user}.substack.com"
15
+
16
+ headers = {
17
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
18
+ '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",
19
+ 'Accept-Encoding': "identity",
20
+ 'accept-language': "en-US,en;q=0.9",
21
+ 'priority': "u=0, i"
22
+ }
23
+
24
+ return status_validate(url, 404, 200, headers=headers, follow_redirects=True)
25
+
26
+
27
+ if __name__ == "__main__":
28
+ user = input("Username?: ").strip()
29
+ result = validate_substack(user)
30
+
31
+ if result == 1:
32
+ print("Available!")
33
+ elif result == 0:
34
+ print("Unavailable!")
35
+ else:
36
+ print(f"Error occurred! Reason: {result.get_reason()}")
@@ -0,0 +1,40 @@
1
+ import re
2
+ from user_scanner.core.orchestrator import generic_validate, Result
3
+
4
+
5
+ def validate_lichess(user: str) -> Result:
6
+ if not (2 <= len(user) <= 20):
7
+ return Result.error("Length must be 2-20 characters")
8
+
9
+ if not re.match(r'^[a-zA-Z0-9_-]+$', user):
10
+ return Result.error("Usernames can only contain letters, numbers, underscores, and hyphens")
11
+
12
+ if re.search(r'[_-]{2,}', user):
13
+ return Result.error("Username cannot contain consecutive underscores or hyphens")
14
+
15
+ if not re.match(r'.*[a-zA-Z0-9]$', user):
16
+ return Result.error("Username must end with a letter or a number")
17
+
18
+ url = f"https://lichess.org/api/player/autocomplete?term={user}&exists=1"
19
+
20
+ def process(response):
21
+ res_text = response.text.strip().lower()
22
+ if res_text == "true":
23
+ return Result.taken()
24
+ if res_text == "false":
25
+ return Result.available()
26
+ return Result.error("Unexpected error, report it via github issues")
27
+
28
+ return generic_validate(url, process, timeout=3.0)
29
+
30
+
31
+ if __name__ == "__main__":
32
+ user = input("Username?: ").strip()
33
+ result = validate_lichess(user)
34
+
35
+ if result == 1:
36
+ print("Available!")
37
+ elif result == 0:
38
+ print("Unavailable!")
39
+ else:
40
+ print(f"Error occurred! Reason: {result.get_reason()}")
@@ -1,9 +1,10 @@
1
1
  from user_scanner.core.orchestrator import generic_validate
2
2
  from user_scanner.core.result import Result
3
+ from urllib.parse import quote
3
4
 
4
- def validate_monkeytype(user: str) -> int:
5
-
6
- url = f"https://api.monkeytype.com/users/checkName/{user}"
5
+ def validate_monkeytype(user: str) -> Result:
6
+ safe_user = quote(user, safe="")
7
+ url = f"https://api.monkeytype.com/users/checkName/{safe_user}"
7
8
 
8
9
  headers = {
9
10
  "User-Agent": (
@@ -28,6 +29,16 @@ def validate_monkeytype(user: str) -> int:
28
29
  return Result.available()
29
30
  elif available is False:
30
31
  return Result.taken()
32
+
33
+ # Surface Monkeytype validation errors (e.g. special characters)
34
+ try:
35
+ data = response.json()
36
+ errors = data.get("validationErrors")
37
+ if errors:
38
+ return Result.error("; ".join(errors))
39
+ except Exception:
40
+ pass
41
+
31
42
  return Result.error("Invalid status code")
32
43
 
33
44
  return generic_validate(url, process, headers=headers)
@@ -42,4 +53,4 @@ if __name__ == "__main__":
42
53
  elif result == 0:
43
54
  print("Unavailable!")
44
55
  else:
45
- print("Error occurred!")
56
+ print("Error occured!")
@@ -1,4 +1,3 @@
1
- import httpx
2
1
  from user_scanner.core.result import Result
3
2
  from user_scanner.core.orchestrator import generic_validate
4
3
 
@@ -0,0 +1,30 @@
1
+ import re
2
+ from user_scanner.core.orchestrator import status_validate
3
+ from user_scanner.core.result import Result
4
+
5
+
6
+ def validate_mastodon(user: str) -> Result:
7
+ if not (3 <= len(user) <= 30):
8
+ return Result.error("Length must be 3-30 characters")
9
+
10
+ if not re.match(r'^[a-zA-Z0-9_-]+$', user):
11
+ return Result.error("Usernames can only contain letters, numbers, underscores and hyphens")
12
+
13
+ if not re.match(r'^[a-zA-Z0-9].*[a-zA-Z0-9]$', user):
14
+ return Result.error("Username must start and end with a letter or number")
15
+
16
+ url = f"https://mastodon.social/api/v1/accounts/lookup?acct={user}"
17
+
18
+ return status_validate(url, 404, 200, follow_redirects=True)
19
+
20
+
21
+ if __name__ == "__main__":
22
+ user = input("Username?: ").strip()
23
+ result = validate_mastodon(user)
24
+
25
+ if result == 1:
26
+ print("Available!")
27
+ elif result == 0:
28
+ print("Unavailable!")
29
+ else:
30
+ print(f"Error occurred! Reason: {result.get_reason()}")
@@ -5,11 +5,6 @@ from user_scanner.core.result import Result
5
5
  def validate_soundcloud(user):
6
6
  url = f"https://soundcloud.com/{user}"
7
7
 
8
- headers = {
9
- 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
10
- 'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
11
- }
12
-
13
8
  def process(response):
14
9
  if response.status_code == 404:
15
10
  return Result.available()
@@ -28,7 +23,7 @@ def validate_soundcloud(user):
28
23
 
29
24
  return Result.error()
30
25
 
31
- return generic_validate(url, process, headers=headers, follow_redirects=True)
26
+ return generic_validate(url, process, follow_redirects=True)
32
27
 
33
28
 
34
29
  if __name__ == "__main__":
@@ -3,13 +3,13 @@ from user_scanner.core.orchestrator import generic_validate
3
3
  from user_scanner.core.result import Result
4
4
 
5
5
 
6
- def validate_telegram(user: str) -> int:
6
+ def validate_telegram(user: str) -> Result:
7
7
  url = f"https://t.me/{user}"
8
8
 
9
9
  def process(r):
10
10
  if r.status_code == 200:
11
11
  if re.search(r'<div[^>]*class="tgme_page_extra"[^>]*>', r.text):
12
- return Result.taken()
12
+ return Result.taken()
13
13
  else:
14
14
  return Result.available()
15
15
  return Result.error()
@@ -0,0 +1,50 @@
1
+ import re
2
+ from user_scanner.core.orchestrator import generic_validate
3
+ from user_scanner.core.result import Result
4
+
5
+
6
+ def validate_tiktok(user: str) -> Result:
7
+ if not (2 <= len(user) <= 24):
8
+ return Result.error("Length must be 2-24 characters")
9
+
10
+ if user.isdigit():
11
+ return Result.error("Usernames cannot contain numbers only")
12
+
13
+ if not re.match(r'^[a-zA-Z0-9_.]+$', user):
14
+ return Result.error("Usernames can only contain letters, numbers, underscores and periods")
15
+
16
+ if user.startswith(".") or user.endswith("."):
17
+ return Result.error("Username cannot start nor end with a period")
18
+
19
+ headers = {
20
+ 'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
21
+ 'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
22
+ 'Accept-Encoding': "identity",
23
+ 'Accept-Language': "en-US,en;q=0.9",
24
+ 'sec-fetch-dest': "document",
25
+ 'Connection': "keep-alive"
26
+ }
27
+
28
+ url = f"https://www.tiktok.com/@{user}"
29
+
30
+ def process(response) -> Result:
31
+ if response.status_code == 200:
32
+ if "statuscode\":10221" in response.text.lower():
33
+ return Result.available()
34
+ else:
35
+ return Result.taken()
36
+ return Result.error("Unable to load tiktok")
37
+
38
+ return generic_validate(url, process, headers=headers)
39
+
40
+
41
+ if __name__ == "__main__":
42
+ user = input("Username?: ").strip()
43
+ result = validate_tiktok(user)
44
+
45
+ if result == 1:
46
+ print("Available!")
47
+ elif result == 0:
48
+ print("Unavailable!")
49
+ else:
50
+ print(f"Error occurred! Reason: {result.get_reason()}")
@@ -0,0 +1,34 @@
1
+ import subprocess
2
+ import sys
3
+ from colorama import Fore
4
+
5
+ def get_version(package_name):
6
+ try:
7
+ from importlib.metadata import version # Python 3.8+
8
+ return version(package_name)
9
+ except Exception:
10
+ return "Unknown"
11
+
12
+ def update_self():
13
+ print("Updating user-scanner using pip...\n")
14
+ try:
15
+ subprocess.check_call([
16
+ sys.executable, "-m", "pip", "uninstall", "user-scanner", "-y"
17
+ ])
18
+ subprocess.check_call([
19
+ sys.executable, "-m", "pip", "install", "user-scanner"
20
+ ])
21
+ except subprocess.CalledProcessError as e:
22
+ print(f"{Fore.RED}Failed to update user-scanner: {e}{Fore.reset}")
23
+ return
24
+
25
+
26
+ user_scanner_ver = get_version("user-scanner")
27
+
28
+ print("\nInstalled Version:")
29
+ print(f" • user-scanner: {user_scanner_ver}")
30
+
31
+ if __name__ == "__main__":
32
+ user_scanner_ver = get_version("user-scanner")
33
+ print(user_scanner_ver)
34
+
@@ -0,0 +1,83 @@
1
+ from pathlib import Path
2
+ from colorama import Fore
3
+ import sys
4
+ import json
5
+ import os
6
+ from user_scanner.core.version import get_pypi_version, load_local_version
7
+ from user_scanner.utils.update import update_self
8
+
9
+ # Color configs
10
+ R = Fore.RED
11
+ G = Fore.GREEN
12
+ C = Fore.CYAN
13
+ Y = Fore.YELLOW
14
+ X = Fore.RESET
15
+
16
+ CONFIG_PATH = Path(__file__).parent.parent / "config.json"
17
+
18
+
19
+ def _get_config_path(path: str | Path | None = None) -> Path:
20
+ """
21
+ Determine the config path in this order:
22
+ 1. explicit path argument (if provided)
23
+ 2. environment variable USER_SCANNER_CONFIG (if set)
24
+ 3. default CONFIG_PATH
25
+ """
26
+ if path:
27
+ return Path(path)
28
+ env = os.environ.get("USER_SCANNER_CONFIG")
29
+ if env:
30
+ return Path(env)
31
+ return CONFIG_PATH
32
+
33
+
34
+ def load_config(path: str | Path | None = None) -> dict:
35
+ cp = _get_config_path(path)
36
+ if cp.exists():
37
+ return json.loads(cp.read_text())
38
+
39
+ default = {"auto_update_status": True}
40
+ # Ensure parent exists
41
+ cp.parent.mkdir(parents=True, exist_ok=True)
42
+ cp.write_text(json.dumps(default, indent=2))
43
+ return default
44
+
45
+
46
+ def save_config_change(status: bool, path: str | Path | None = None):
47
+ cp = _get_config_path(path)
48
+ content = load_config(path)
49
+ content["auto_update_status"] = status
50
+ cp.parent.mkdir(parents=True, exist_ok=True)
51
+ cp.write_text(json.dumps(content, indent=2))
52
+
53
+
54
+ def check_for_updates():
55
+ if not load_config().get("auto_update_status", False):
56
+ return
57
+
58
+ try:
59
+ PYPI_URL = "https://pypi.org/pypi/user-scanner/json"
60
+ latest_ver = get_pypi_version(PYPI_URL)
61
+ current_ver, _ = load_local_version()
62
+
63
+ if current_ver != latest_ver:
64
+ print(
65
+ f"\n[!] New version available: "
66
+ f"{R}{current_ver}{X} -> {C}{latest_ver}{X}\n"
67
+ )
68
+
69
+ choice = input(
70
+ f"{Y}Do you want to update?{X} (y/n/d: {R}don't ask again{X}): "
71
+ ).strip().lower()
72
+
73
+ if choice == "y":
74
+ update_self()
75
+ print(f"[{G}+{X}] {G}Update successful. Please restart the tool.{X}")
76
+ sys.exit(0)
77
+
78
+ elif choice == "d":
79
+ save_config_change(False)
80
+ print(f"[{Y}*{X}] {R}Auto-update checks disabled.{X}")
81
+
82
+ except Exception as e:
83
+ print(f"[{Y}!{X}] {R}Update check failed{X}: {e}")
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.0.9.1",
2
+ "version": "1.0.10.1",
3
3
  "version_type": "pypi"
4
4
  }
@@ -1,34 +0,0 @@
1
- import json
2
- from colorama import Fore, Style, init
3
- from ..utils.version import load_local_version
4
-
5
-
6
- version, version_type = load_local_version()
7
- init(autoreset=True)
8
- C_RED = Fore.RED + Style.BRIGHT
9
- C_CYAN = Fore.CYAN + Style.BRIGHT
10
- C_GREEN = Fore.GREEN + Style.BRIGHT
11
- C_WHITE = Fore.WHITE + Style.BRIGHT
12
- C_MAGENTA = Fore.MAGENTA + Style.BRIGHT
13
- BANNER_ASCII = fr"""{C_CYAN} _ _ ___ ___ _ __ ___ ___ __ _ _ __ _ __ ___ _ __
14
- | | | / __|/ _ \ '__|____/ __|/ __/ _` | '_ \| '_ \ / _ \ '__|
15
- | |_| \__ \ __/ | |_____\__ \ (_| (_| | | | | | | | __/ |
16
- \__,_|___/\___|_| |___/\___\__,_|_| |_|_| |_|\___|_| Version: {version}
17
- {Style.RESET_ALL}""".strip()
18
-
19
- INFO_BOX = f"""{C_MAGENTA} ╔════════════════════════════════════════╗
20
- ║ {C_RED}♚ {C_GREEN}Project Name{C_WHITE} : UserScanner {C_MAGENTA}║
21
- ║ {C_RED}♚ {C_GREEN}Author{C_WHITE} : Kaif {C_MAGENTA}║
22
- ║ {C_RED}♚ {C_GREEN}Github{C_WHITE} : github.com/kaifcodec {C_MAGENTA}║
23
- ║ {C_RED}♚ {C_GREEN}Email{C_WHITE} : kaifcodec@gmail.com {C_MAGENTA}║
24
- ══════════════════════════════════════════{Style.RESET_ALL}""".strip()
25
-
26
-
27
- def print_banner():
28
- print(BANNER_ASCII)
29
- print(INFO_BOX)
30
- print(" ")
31
-
32
-
33
- if __name__ == "__main__":
34
- print_banner()
@@ -1,19 +0,0 @@
1
- from user_scanner.core.orchestrator import status_validate
2
-
3
-
4
- def validate_mastodon(user):
5
- url = f"https://mastodon.social/@{user}"
6
-
7
- return status_validate(url, 404, 200, follow_redirects=True)
8
-
9
-
10
- if __name__ == "__main__":
11
- user = input("Username?: ").strip()
12
- result = validate_mastodon(user)
13
-
14
- if result == 1:
15
- print("Available!")
16
- elif result == 0:
17
- print("Unavailable!")
18
- else:
19
- print("Error occured!")
File without changes