user-scanner 1.0.9.0__py3-none-any.whl → 1.0.10.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.
user_scanner/__main__.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import argparse
2
2
  import time
3
3
  import re
4
+ import sys
4
5
  from user_scanner.cli import printer
5
6
  from user_scanner.core.orchestrator import generate_permutations, load_categories
6
7
  from colorama import Fore, Style
@@ -8,10 +9,22 @@ from user_scanner.cli.banner import print_banner
8
9
  from typing import List
9
10
  from user_scanner.core.result import Result
10
11
  from user_scanner.core.utils import is_last_value
12
+ from user_scanner.utils.updater_logic import check_for_updates
13
+ from user_scanner.utils.update import update_self
14
+
15
+ # Color configs
16
+ R = Fore.RED
17
+ G = Fore.GREEN
18
+ C = Fore.CYAN
19
+ Y = Fore.YELLOW
20
+ X = Fore.RESET
21
+
11
22
 
12
23
  MAX_PERMUTATIONS_LIMIT = 100 # To prevent excessive generation
13
24
 
25
+
14
26
  def main():
27
+
15
28
  parser = argparse.ArgumentParser(
16
29
  prog="user-scanner",
17
30
  description="Scan usernames across multiple platforms."
@@ -51,15 +64,25 @@ def main():
51
64
  parser.add_argument(
52
65
  "-o", "--output", type=str, help="Specify the output file"
53
66
  )
67
+ parser.add_argument(
68
+ "-U", "--update", action="store_true", help="Update user-scanner to latest version"
69
+ )
54
70
 
55
71
  args = parser.parse_args()
56
72
 
57
73
  Printer = printer.Printer(args.format)
58
74
 
75
+ if args.update is True:
76
+ update_self()
77
+ print(f"[{G}+{X}] {G}Update successful. Please restart the tool.{X}")
78
+ sys.exit(0)
79
+
59
80
  if args.list:
60
81
  Printer.print_modules(args.category)
61
82
  return
62
83
 
84
+ check_for_updates()
85
+
63
86
  if not args.username:
64
87
  parser.print_help()
65
88
  return
@@ -70,19 +93,19 @@ def main():
70
93
 
71
94
  if args.permute and args.delay == 0 and Printer.is_console:
72
95
  print(
73
- Fore.YELLOW
96
+ Y
74
97
  + "[!] Warning: You're generating multiple usernames with NO delay between requests. "
75
98
  "This may trigger rate limits or IP bans. Use --delay 1 or higher. (Use only if the sites throw errors otherwise ignore)\n"
76
99
  + Style.RESET_ALL)
77
100
 
78
101
  usernames = [args.username] # Default single username list
79
102
 
80
- #Added permutation support , generate all possible permutation of given sequence.
103
+ # Added permutation support , generate all possible permutation of given sequence.
81
104
  if args.permute:
82
105
  usernames = generate_permutations(args.username, args.permute , args.stop)
83
106
  if Printer.is_console:
84
107
  print(
85
- Fore.CYAN + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
108
+ C + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
86
109
 
87
110
  if args.module and "." in args.module:
88
111
  args.module = args.module.replace(".", "_")
@@ -118,7 +141,7 @@ def main():
118
141
  results.extend(run_all_usernames(run_module_single, module))
119
142
  else:
120
143
  print(
121
- Fore.RED + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
144
+ R + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
122
145
 
123
146
  elif args.category:
124
147
  # Category-wise scan
@@ -139,7 +162,7 @@ def main():
139
162
  "\n[!] The console format cannot be "
140
163
  f"written to file: '{args.output}'."
141
164
  )
142
- print(Fore.RED + msg + Style.RESET_ALL)
165
+ print(R + msg + Style.RESET_ALL)
143
166
  return
144
167
 
145
168
  content = Printer.get_start()
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from colorama import Fore, Style, init
3
- from ..utils.version import load_local_version
3
+ from ..core.version import load_local_version
4
4
 
5
5
 
6
6
  version, version_type = load_local_version()
@@ -16,18 +16,9 @@ BANNER_ASCII = fr"""{C_CYAN} _ _ ___ ___ _ __ ___ ___ __ _ _ __ _
16
16
  \__,_|___/\___|_| |___/\___\__,_|_| |_|_| |_|\___|_| Version: {version}
17
17
  {Style.RESET_ALL}""".strip()
18
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
19
 
27
20
  def print_banner():
28
21
  print(BANNER_ASCII)
29
- print(INFO_BOX)
30
- print(" ")
31
22
 
32
23
 
33
24
  if __name__ == "__main__":
@@ -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}
@@ -138,7 +138,7 @@ def run_checks(username: str, printer: Printer, last: bool = True) -> List[Resul
138
138
  return results
139
139
 
140
140
 
141
- def make_get_request(url: str, **kwargs) -> httpx.Response:
141
+ def make_request(url: str, **kwargs) -> httpx.Response:
142
142
  """Simple wrapper to **httpx.get** that predefines headers and timeout"""
143
143
  if not "headers" in kwargs:
144
144
  kwargs["headers"] = {
@@ -152,7 +152,9 @@ def make_get_request(url: str, **kwargs) -> httpx.Response:
152
152
  if not "timeout" in kwargs:
153
153
  kwargs["timeout"] = 5.0
154
154
 
155
- return httpx.get(url, **kwargs)
155
+ method = kwargs.pop("method", "GET")
156
+
157
+ return httpx.request(method.upper(), url, **kwargs)
156
158
 
157
159
 
158
160
  def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kwargs) -> AnyResult:
@@ -160,7 +162,7 @@ def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kw
160
162
  A generic validate function that makes a request and executes the provided function on the response.
161
163
  """
162
164
  try:
163
- response = make_get_request(url, **kwargs)
165
+ response = make_request(url, **kwargs)
164
166
  result = func(response)
165
167
  result.url = url
166
168
  return 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,5 +1,6 @@
1
1
  import httpx
2
2
  from user_scanner.core.result import Result
3
+ from user_scanner.core.orchestrator import generic_validate
3
4
 
4
5
 
5
6
  def validate_hashnode(user):
@@ -18,9 +19,7 @@ def validate_hashnode(user):
18
19
  'Referer': "https://hashnode.com/signup",
19
20
  }
20
21
 
21
- try:
22
- response = httpx.post(url, json=payload, headers=headers, timeout=3.0)
23
-
22
+ def process(response):
24
23
  if response.status_code == 200:
25
24
  data = response.json()
26
25
 
@@ -35,9 +34,7 @@ def validate_hashnode(user):
35
34
  else:
36
35
  return Result.error("Invalid status code")
37
36
 
38
- except Exception as e:
39
- return Result.error(e)
40
-
37
+ return generic_validate(url, process, method="POST", json=payload, headers=headers, timeout=3.0)
41
38
 
42
39
  if __name__ == "__main__":
43
40
  user = input("Username?: ").strip()
@@ -1,8 +1,19 @@
1
- from user_scanner.core.orchestrator import status_validate
1
+ import re
2
+ from user_scanner.core.orchestrator import status_validate, Result
2
3
 
3
4
 
4
- def validate_itch_io(user):
5
- url = f"https://{user}.itch.io"
5
+ def validate_itch_io(user: str) -> Result:
6
+ if not (2 <= len(user) <= 25):
7
+ return Result.error("Length must be 2-25 characters.")
8
+
9
+ if not re.match(r'^[a-z0-9_-]+$', user):
10
+
11
+ if re.search(r'[A-Z]', user):
12
+ return Result.error("Use lowercase letters only.")
13
+
14
+ return Result.error("Only use lowercase letters, numbers, underscores, and hyphens.")
15
+
16
+ url = f"https://itch.io/profile/{user}"
6
17
 
7
18
  return status_validate(url, 404, 200, follow_redirects=True)
8
19
 
@@ -16,4 +27,4 @@ if __name__ == "__main__":
16
27
  elif result == 0:
17
28
  print("Unavailable!")
18
29
  else:
19
- print("Error occurred!")
30
+ 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,83 @@
1
+ import json
2
+ import re
3
+ import httpx
4
+ from user_scanner.core.orchestrator import generic_validate, Result
5
+
6
+
7
+ def validate_twitch(user: str) -> Result:
8
+ if not (4 <= len(user) <= 25):
9
+ return Result.error("Username must be between 4 and 25 characters long")
10
+
11
+ if not re.match(r"^[a-zA-Z0-9]+$", user):
12
+ return Result.error("Username can only contain alphanumeric characters (a-z, 0-9)")
13
+
14
+ url = "https://gql.twitch.tv/gql"
15
+
16
+ payload = [
17
+ {
18
+ "operationName": "ChannelLayout",
19
+ "variables": {
20
+ "channelLogin": user,
21
+ "includeIsDJ": True
22
+ },
23
+ "extensions": {
24
+ "persistedQuery": {
25
+ "version": 1,
26
+ "sha256Hash": "4c361fa1874dc8f6a49e62b56aa1032eccb31311bdb653918a924f96a8b2d1a6"
27
+ }
28
+ }
29
+ }
30
+ ]
31
+
32
+ headers = {
33
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
34
+ 'Accept-Encoding': "identity",
35
+ 'Content-Type': "application/json",
36
+ 'sec-ch-ua-platform': "\"Android\"",
37
+ 'accept-language': "en-US",
38
+ 'client-id': "kimne78kx3ncx6brgo4mv6wki5h1ko",
39
+ 'client-version': "7bb0442d-1175-4ab5-9d32-b1f370536cbf",
40
+ 'origin': "https://m.twitch.tv",
41
+ 'referer': "https://m.twitch.tv/",
42
+ }
43
+
44
+ def process(response: httpx.Response) -> Result:
45
+ if response.status_code != 200:
46
+ return Result.error(f"Unexpected status code: {response.status_code}")
47
+
48
+ try:
49
+ data = response.json()
50
+ except json.JSONDecodeError as e:
51
+ return Result.error(f"Failed to decode JSON response: {e}")
52
+
53
+ user_data = data[0].get('data', {}).get('user', {})
54
+ typename = user_data.get('__typename')
55
+
56
+ if typename == "User":
57
+ return Result.taken()
58
+ elif typename == "UserDoesNotExist":
59
+ return Result.available()
60
+ else:
61
+ return Result.error("Unexpected GraphQL response structure or type.")
62
+
63
+ return generic_validate(
64
+ url,
65
+ process,
66
+ headers=headers,
67
+ method='POST',
68
+ content=json.dumps(payload),
69
+ follow_redirects=False
70
+ )
71
+
72
+
73
+ if __name__ == "__main__":
74
+ user = input("Twitch Username?: ").strip()
75
+ result = validate_twitch(user)
76
+
77
+ if result == 1:
78
+ print("Available!")
79
+ elif result == 0:
80
+ print("Unavailable!")
81
+ else:
82
+ reason = result.get_reason()
83
+ print(f"Error occurred! Reason: {reason}")
@@ -0,0 +1,34 @@
1
+ import re
2
+ from user_scanner.core.orchestrator import status_validate, Result
3
+
4
+
5
+ def validate_bitbucket(user: str) -> Result:
6
+ if not (1 <= len(user) <= 30):
7
+ return Result.error("Length must be 1-30 characters.")
8
+
9
+ if not re.match(r'^[a-z0-9][a-z0-9_-]*$', user):
10
+
11
+ if re.search(r'[A-Z]', user):
12
+ return Result.error("Use lowercase letters only.")
13
+
14
+ return Result.error("Only use lowercase letters, numbers, hyphens, and underscores.")
15
+
16
+ url = f"https://bitbucket.org/{user}/"
17
+
18
+ return status_validate(url, 404, [200, 302], follow_redirects=True)
19
+
20
+
21
+ if __name__ == "__main__":
22
+ user = input("Username?: ").strip()
23
+ result = validate_bitbucket(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()}")
31
+
32
+
33
+
34
+
@@ -29,7 +29,7 @@ def validate_github(user):
29
29
 
30
30
  if response.status_code == 422:
31
31
  if GITHUB_INVALID_MSG in response.text:
32
- return Result.error("Cannot start/end with hyphen or use double hyphens")
32
+ return Result.error("Cannot start/end with hyphen or use double hyphens, underscores")
33
33
 
34
34
  return Result.taken()
35
35
 
@@ -0,0 +1,35 @@
1
+ import re
2
+ from user_scanner.core.orchestrator import status_validate, Result
3
+
4
+
5
+ def validate_leetcode(user: str) -> Result:
6
+ if not (3 <= len(user) <= 30):
7
+ return Result.error("Length must be between 3 and 30 characters")
8
+
9
+ if not re.match(r'^[a-zA-Z0-9._-]+$', user):
10
+ return Result.error("Can only use letters, numbers, underscores, periods, or hyphens")
11
+
12
+ url = f"https://leetcode.com/u/{user}/"
13
+
14
+ headers = {
15
+ 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
16
+ '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",
17
+ 'Accept-Encoding': "identity",
18
+ 'upgrade-insecure-requests': "1",
19
+ 'accept-language': "en-US,en;q=0.9",
20
+ 'priority': "u=0, i"
21
+ }
22
+
23
+ return status_validate(url, 404, 200, headers=headers, follow_redirects=True)
24
+
25
+
26
+ if __name__ == "__main__":
27
+ user = input("Username?: ").strip()
28
+ result = validate_leetcode(user)
29
+
30
+ if result == 1:
31
+ print("Available!")
32
+ elif result == 0:
33
+ print("Unavailable!")
34
+ else:
35
+ print(f"Error occurred! Reason: {result.get_reason()}")
@@ -0,0 +1,30 @@
1
+ import re
2
+ from user_scanner.core.orchestrator import status_validate, Result
3
+
4
+
5
+ def validate_sourceforge(user: str) -> Result:
6
+ if not (3 <= len(user) <= 30):
7
+ return Result.error("Length must be 3-30 characters.")
8
+
9
+ if not re.match(r'^[a-z0-9-]+$', user):
10
+
11
+ if re.search(r'[A-Z]', user):
12
+ return Result.error("Use lowercase letters only.")
13
+
14
+ return Result.error("Only use lowercase letters, numbers, and dashes.")
15
+
16
+ url = f"https://sourceforge.net/u/{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_sourceforge(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()}")
@@ -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,5 +1,6 @@
1
1
  import httpx
2
2
  from user_scanner.core.result import Result
3
+ from user_scanner.core.orchestrator import generic_validate
3
4
 
4
5
  def validate_discord(user):
5
6
  url = "https://discord.com/api/v9/unique-username/username-attempt-unauthed"
@@ -15,8 +16,7 @@ def validate_discord(user):
15
16
 
16
17
  data = {"username": user}
17
18
 
18
- try:
19
- response = httpx.post(url, headers=headers, json=data, timeout=3.0)
19
+ def process(response):
20
20
  if response.status_code == 200:
21
21
  status = response.json().get("taken")
22
22
  if status is True:
@@ -24,8 +24,8 @@ def validate_discord(user):
24
24
  elif status is False:
25
25
  return Result.available()
26
26
  return Result.error("Invalid status code")
27
- except Exception as e:
28
- return Result.error(e)
27
+
28
+ return generic_validate(url, process, method="POST", json=data, headers=headers, timeout=3.0)
29
29
 
30
30
 
31
31
  if __name__ == "__main__":
File without changes
@@ -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,62 @@
1
+ from pathlib import Path
2
+ from colorama import Fore
3
+ import sys
4
+ import json
5
+ from user_scanner.core.version import get_pypi_version, load_local_version
6
+ from user_scanner.utils.update import update_self
7
+
8
+ # Color configs
9
+ R = Fore.RED
10
+ G = Fore.GREEN
11
+ C = Fore.CYAN
12
+ Y = Fore.YELLOW
13
+ X = Fore.RESET
14
+
15
+ CONFIG_PATH = Path(__file__).parent.parent / "config.json"
16
+
17
+
18
+ def load_config() -> dict:
19
+ if CONFIG_PATH.exists():
20
+ return json.loads(CONFIG_PATH.read_text())
21
+
22
+ default = {"auto_update_status": True}
23
+ CONFIG_PATH.write_text(json.dumps(default, indent=2))
24
+ return default
25
+
26
+
27
+ def save_config_change(status: bool):
28
+ content = load_config()
29
+ content["auto_update_status"] = status
30
+ CONFIG_PATH.write_text(json.dumps(content, indent=2))
31
+
32
+
33
+ def check_for_updates():
34
+ if not load_config().get("auto_update_status", False):
35
+ return
36
+
37
+ try:
38
+ PYPI_URL = "https://pypi.org/pypi/user-scanner/json"
39
+ latest_ver = get_pypi_version(PYPI_URL)
40
+ current_ver, _ = load_local_version()
41
+
42
+ if current_ver != latest_ver:
43
+ print(
44
+ f"\n[!] New version available: "
45
+ f"{R}{current_ver}{X} -> {C}{latest_ver}{X}\n"
46
+ )
47
+
48
+ choice = input(
49
+ f"{Y}Do you want to update?{X} (y/n/d: {R}don't ask again{X}): "
50
+ ).strip().lower()
51
+
52
+ if choice == "y":
53
+ update_self()
54
+ print(f"[{G}+{X}] {G}Update successful. Please restart the tool.{X}")
55
+ sys.exit(0)
56
+
57
+ elif choice == "d":
58
+ save_config_change(False)
59
+ print(f"[{Y}*{X}] {R}Auto-update checks disabled.{X}")
60
+
61
+ except Exception as e:
62
+ print(f"[{Y}!{X}] {R}Update check failed{X}: {e}")
user_scanner/version.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "1.0.9.0",
2
+ "version": "1.0.10.0",
3
3
  "version_type": "pypi"
4
4
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: user-scanner
3
- Version: 1.0.9.0
3
+ Version: 1.0.10.0
4
4
  Summary: Check username availability across multiple popular platforms
5
5
  Keywords: username,checker,availability,social,tech,python,user-scanner
6
6
  Author-email: Kaif <kafcodec@gmail.com>
@@ -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.0-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
 
@@ -67,7 +68,7 @@ user-scanner -u <username> -m github
67
68
  ```
68
69
 
69
70
  Also, the output file and format can be specified: <br>
70
- \* Errors and warnings will only appear when the format is set to "console"
71
+
71
72
  ```bash
72
73
  user-scanner -u <username> -f console #Default format
73
74
  user-scanner -u <username> -f csv
@@ -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,39 +1,48 @@
1
1
  user_scanner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- user_scanner/__main__.py,sha256=jmfWuPNToix9UtLNh7IYrm-M22QBdSPtmMHv4ZY66Bc,5253
3
- user_scanner/version.json,sha256=dW2NfpLg6HpcgobM6wbpzkahpV6O6N2jXxTNSwuDsU4,49
2
+ user_scanner/__main__.py,sha256=v8yb4Zcc13GmsURMtCJQ30vceeMlC8brIGQe2y5nmVs,5737
3
+ user_scanner/config.json,sha256=WtVnrpPxhGUBmx_dBShO3R0NnipVBVz3BfzlEPO5Amc,28
4
+ user_scanner/version.json,sha256=H8P7qm9ofNSJWEHYWmaUFTjlhwTZUsGK3m-ONOBDRmg,50
4
5
  user_scanner/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- user_scanner/cli/banner.py,sha256=3t6owaDArERlvpcszA1Yi3dtksvh8a9tLyrxRowTC40,1499
6
+ user_scanner/cli/banner.py,sha256=z9gbSoJBrpGJZayJNCFzfUL-QvCZCUWyu2OEX7RZiTg,778
6
7
  user_scanner/cli/printer.py,sha256=H8rNw0ewG3G0JquKnMLW8PbHmFcALaEZZNUAsAUScHg,4027
7
8
  user_scanner/community/__init__.py,sha256=5EzlM991pJqvqIRc05_QV5BureJZ7wiCRm1AyEY6pms,12
8
9
  user_scanner/community/coderlegion.py,sha256=W_bdjzdFPRgUrNFFlylvToSJ4AzaFCtTsUy_MRVDdSo,451
10
+ user_scanner/community/hackernews.py,sha256=lKVuEVoGnXWYSANcuUyiSHzUr-VtcXHC7sEX1rxZi0Y,1068
9
11
  user_scanner/community/stackoverflow.py,sha256=MTL8O0TLHkjVbugBh1pLxELJLU3hkX_YEHjGjaKTJi4,1007
10
12
  user_scanner/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- user_scanner/core/orchestrator.py,sha256=BZ3IibDyT5itSdXmsTtS8SHHPT1bGwxjTwSk4xp86TU,7005
13
+ user_scanner/core/orchestrator.py,sha256=nfe0KEcT2U_MB48OgmuvQ0tHvQdnJm8VVi06QxiuJMU,7059
12
14
  user_scanner/core/result.py,sha256=8qrIXO5jg6OjWkLtEq25lx_b1hLgtDFugdDyrJX4vcU,3300
13
15
  user_scanner/core/utils.py,sha256=v3XLUXmknf9zl_JBOmnss3280SrEWBdPcz-zq3S8lak,249
16
+ user_scanner/core/version.py,sha256=k1_KTZdRLKBAxp8_PtOhTAtj8mBO_AUnUGdqI4epypY,855
14
17
  user_scanner/creator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
18
  user_scanner/creator/devto.py,sha256=mIACmG1a4eoctywxb5p04sI0YVi3dsjCRw9YVOFBEKQ,435
16
- user_scanner/creator/hashnode.py,sha256=vlXsJfIG_5ShEK2xPLDl2bi6X-d08iTeoqfKeQLaB1g,1363
17
- user_scanner/creator/itch_io.py,sha256=JsFkFzBfJI18DeuSYJIOPGEV_9v7t-jtwmBYCA9W2P8,440
19
+ user_scanner/creator/hashnode.py,sha256=NEIpSyf0zbcZ_QNjU3C7F5oApvVpUQOd_oQuughM-Qc,1403
20
+ user_scanner/creator/itch_io.py,sha256=2a8UVh-_OaWQPcSUHUuijDGpWDxsR8DoCcU1BdTRqqs,854
18
21
  user_scanner/creator/kaggle.py,sha256=QaXIG02OGxvQZEvwHm50RKNd7joxGOq0Ht3cFfrYEiU,445
19
22
  user_scanner/creator/medium.py,sha256=NIOYnk8_ASD0kYfKqs8t6uZZTV4D-5-ZxyHMzOMMOuI,1015
20
23
  user_scanner/creator/patreon.py,sha256=g-r85pxirf0ihK3STyGYPIzp59MB7JH64Opb4wq1fyU,461
21
24
  user_scanner/creator/producthunt.py,sha256=p0HoIIVhmv9bBkelhfzRYudUFoyk_qeT66-hPpHEFqk,1938
25
+ user_scanner/creator/substack.py,sha256=tisTUQmauteYZOZ0tULp9GGUuf4ZBEcpqv4ZmEvjyK0,1288
26
+ user_scanner/creator/twitch.py,sha256=blsh5sMT7miF5-xqVXYLieTILzkop2PsWqv9HhP8G40,2509
22
27
  user_scanner/dev/__init__.py,sha256=qUR0eLwN-gO6oKk-1cmCVT4G_AxUHHMgpV3wJ7URXi4,7
28
+ user_scanner/dev/bitbucket.py,sha256=qAIlFCmMaNTUx2-a5wJKHjbQjERcJt0zKHJmjLAeXr4,876
23
29
  user_scanner/dev/codeberg.py,sha256=Z6nV0_8xZhMiCcNn9Hn79VVh6y0ar9fqL7KS2b7IaDo,447
24
30
  user_scanner/dev/cratesio.py,sha256=mJnlLJoMLlQ7f_95QD7LgH1xCj-e6FooOFkpYypBfG4,724
25
31
  user_scanner/dev/dockerhub.py,sha256=sPEnomGiPM2mKv2HsA-9WxaXHjzz21A6ox3IXK1etLc,643
26
- user_scanner/dev/github.py,sha256=km0RMd4cS5sY8IUKDKoNC1oQeCj57ld4HAjOlo1w4ms,1689
32
+ user_scanner/dev/github.py,sha256=9Q4G84WTAeWfNliApKdRFl1MJLfHvDPJ09mwr8P1ePo,1702
27
33
  user_scanner/dev/gitlab.py,sha256=kMDSd74XbofmJocfS4Fd9DxPryIHBMek3N_5c7Z_AJQ,1351
28
34
  user_scanner/dev/huggingface.py,sha256=hDanOZ45LeUg3hrN0CYrBnBnLqHCYtOWS0_HCvAbmDw,454
29
35
  user_scanner/dev/launchpad.py,sha256=N58ioX_dEHq2uwyyGrWnDKWwbqK9_RiuBQ1uWR5cDfg,799
36
+ user_scanner/dev/leetcode.py,sha256=PTJcgp1W3fzLDK_Jy_VvRjKnLftLYMJaw3kfMjHqt9c,1246
30
37
  user_scanner/dev/npmjs.py,sha256=k-DhFqGJWDoQ79EzR8hmVrJk07AfJfPUWnIYuKc2G6w,713
31
38
  user_scanner/dev/replit.py,sha256=SI_i2l4w9tm2kBX4-cONBAT8dSynXoGEP4zcU8ngnh0,442
39
+ user_scanner/dev/sourceforge.py,sha256=Kt8MmpCgB1tNwYRI9PYOZzIrL1VfnpzeNC43DcbZlbI,850
32
40
  user_scanner/donation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
41
  user_scanner/donation/buymeacoffee.py,sha256=86LGyChv_UKQFp2D7nIoK1B-FCAAbbfabS8NA9yLp5k,459
34
42
  user_scanner/donation/liberapay.py,sha256=njClxpbRLZQ_L2-lUYCY6QFnF4IcwfCJPCIg1iEqo7M,1120
35
43
  user_scanner/gaming/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
44
  user_scanner/gaming/chess_com.py,sha256=74tMgukSUXwdmD9G7Jij_gudRlSfs46Xho5KNMVeyt4,1262
45
+ user_scanner/gaming/lichess.py,sha256=8b7DNRENh2UwjbsJNXRs2HIwC80OwGe5D0y-96_xjzs,1324
37
46
  user_scanner/gaming/minecraft.py,sha256=7a9H9ebLlRzGB0SjxLmzqLiDPDBZAuuNq3KKe2DZAvo,481
38
47
  user_scanner/gaming/monkeytype.py,sha256=n9KMBChs0ej7MgZqLGUDVz5CED70sQ3ksDF5pO0G05A,1380
39
48
  user_scanner/gaming/osu.py,sha256=2Xs1iM0CJ-3dNHu4tyF50_s0Ei_1mA5Zd6D6M5RmiVg,448
@@ -41,7 +50,7 @@ user_scanner/gaming/roblox.py,sha256=Qs51jLgKh-Ehqlco_j8CFtJ4CLVoZeBwPugDvAyLw3Q
41
50
  user_scanner/gaming/steam.py,sha256=l8xk_p9aiYQWCPoogQnO1iwkfojPhg6yd76OZHhKN50,740
42
51
  user_scanner/social/__init__.py,sha256=jaCkFwX1uYtF0ENifVwF8OfHrYYUTm64B9wlBq9BBfQ,9
43
52
  user_scanner/social/bluesky.py,sha256=11Y_vRj3txEDQqoD0iANgSWVSB8L87OotPQZquhneR0,1994
44
- user_scanner/social/discord.py,sha256=z-oIqT416ydnZUkq481rz6NTq5yc_BYu-P_Z79uR-Jw,1150
53
+ user_scanner/social/discord.py,sha256=S_6ItjRcxip_L60UJ2rdLRFf4eXT7fMN7roCKA-lDfc,1193
45
54
  user_scanner/social/instagram.py,sha256=GgmKGvi3meKdZ_nQJbJSBZDJTEKSoE6Cn4_VARmo62I,953
46
55
  user_scanner/social/mastodon.py,sha256=qISx-gUsddC8lFMcmERA4N0YAnXyS1Jq2Xgg7XE4sL4,450
47
56
  user_scanner/social/pinterest.py,sha256=JIJ-HPtMoGvxW7NQzm02lChFKMmE6k6GxFoUZ6OvCec,784
@@ -52,10 +61,11 @@ user_scanner/social/telegram.py,sha256=9IS-0pghMifNRmj62NcxCOvn23Hvg0AJJcuhCa_aX
52
61
  user_scanner/social/threads.py,sha256=rK8Gm_riDdr0djo23tk38fNVVEBuC6nj2iTXvWrqXeE,951
53
62
  user_scanner/social/x.py,sha256=sAnboHHZN2DWyKeds46GLZHxGG-G_bjzfVNIkblSHx8,1406
54
63
  user_scanner/social/youtube.py,sha256=UPu584teg75P7FT05RFG3nobbHgPmzjr-ZwyN2sw6gw,1980
55
- user_scanner/utils/update.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- user_scanner/utils/version.py,sha256=mPh24EwITyXgD3AMgbflRL180pS0JfrvuJdnoErOU34,623
57
- user_scanner-1.0.9.0.dist-info/entry_points.txt,sha256=XqU3kssYZ0vXaPy5qYUOTCu4u-48Xie7QWFpBCYc7Nc,59
58
- user_scanner-1.0.9.0.dist-info/licenses/LICENSE,sha256=XH1QyQG68zo1opDIZHTHcTAbe9XMzewvTaFTukcN9vc,1061
59
- user_scanner-1.0.9.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
60
- user_scanner-1.0.9.0.dist-info/METADATA,sha256=IeQ-358gvD2bIm2_HgkE7SR3AIS1JQ2XgvotvzEaqpA,5772
61
- user_scanner-1.0.9.0.dist-info/RECORD,,
64
+ user_scanner/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
+ user_scanner/utils/update.py,sha256=Rj3kLuUrQ-LlKGB7bkndqVjj0IUqugbDSj2SUrPRidE,936
66
+ user_scanner/utils/updater_logic.py,sha256=Gerj6pAuv32qcfhxshWlNISRtv1X-AHFnMyssyvRbPg,1746
67
+ user_scanner-1.0.10.0.dist-info/entry_points.txt,sha256=XqU3kssYZ0vXaPy5qYUOTCu4u-48Xie7QWFpBCYc7Nc,59
68
+ user_scanner-1.0.10.0.dist-info/licenses/LICENSE,sha256=XH1QyQG68zo1opDIZHTHcTAbe9XMzewvTaFTukcN9vc,1061
69
+ user_scanner-1.0.10.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
70
+ user_scanner-1.0.10.0.dist-info/METADATA,sha256=8CjDUuZLzm81re81Yqy3uTIFWKYoaQC7ehjPJ_KGQqU,5907
71
+ user_scanner-1.0.10.0.dist-info/RECORD,,