user-scanner 1.0.6.0__py3-none-any.whl → 1.0.8.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 +63 -44
- user_scanner/core/orchestrator.py +68 -36
- user_scanner/core/result.py +79 -0
- user_scanner/creator/hashnode.py +8 -13
- user_scanner/creator/itch_io.py +0 -5
- user_scanner/creator/medium.py +7 -12
- user_scanner/creator/patreon.py +0 -6
- user_scanner/creator/producthunt.py +2 -19
- user_scanner/dev/github.py +19 -2
- user_scanner/dev/gitlab.py +4 -3
- user_scanner/dev/huggingface.py +19 -0
- user_scanner/dev/npmjs.py +9 -25
- user_scanner/donation/buymeacoffee.py +0 -3
- user_scanner/gaming/chess_com.py +4 -9
- user_scanner/gaming/minecraft.py +0 -5
- user_scanner/gaming/monkeytype.py +4 -10
- user_scanner/gaming/osu.py +0 -5
- user_scanner/gaming/roblox.py +14 -9
- user_scanner/gaming/steam.py +6 -9
- user_scanner/social/bluesky.py +7 -13
- user_scanner/social/discord.py +6 -9
- user_scanner/social/mastodon.py +0 -6
- user_scanner/social/pinterest.py +4 -4
- user_scanner/social/reddit.py +4 -3
- user_scanner/social/soundcloud.py +43 -0
- user_scanner/social/telegram.py +6 -6
- user_scanner/social/x.py +10 -20
- user_scanner/social/youtube.py +2 -18
- user_scanner/version.json +1 -1
- {user_scanner-1.0.6.0.dist-info → user_scanner-1.0.8.0.dist-info}/METADATA +27 -6
- user_scanner-1.0.8.0.dist-info/RECORD +58 -0
- user_scanner-1.0.6.0.dist-info/RECORD +0 -55
- {user_scanner-1.0.6.0.dist-info → user_scanner-1.0.8.0.dist-info}/WHEEL +0 -0
- {user_scanner-1.0.6.0.dist-info → user_scanner-1.0.8.0.dist-info}/entry_points.txt +0 -0
- {user_scanner-1.0.6.0.dist-info → user_scanner-1.0.8.0.dist-info}/licenses/LICENSE +0 -0
user_scanner/__main__.py
CHANGED
|
@@ -1,38 +1,22 @@
|
|
|
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.core.orchestrator import run_checks, load_modules , generate_permutations, load_categories
|
|
4
5
|
from colorama import Fore, Style
|
|
5
6
|
from .cli import banner
|
|
6
7
|
from .cli.banner import print_banner
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
"dev": "dev",
|
|
10
|
-
"social": "social",
|
|
11
|
-
"creator": "creator",
|
|
12
|
-
"community": "community",
|
|
13
|
-
"gaming": "gaming",
|
|
14
|
-
"donation": "donation"
|
|
15
|
-
}
|
|
16
|
-
|
|
9
|
+
MAX_PERMUTATIONS_LIMIT = 100 # To prevent excessive generation
|
|
17
10
|
|
|
18
11
|
def list_modules(category=None):
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"dev": dev,
|
|
22
|
-
"social": social,
|
|
23
|
-
"creator": creator,
|
|
24
|
-
"community": community,
|
|
25
|
-
"gaming": gaming,
|
|
26
|
-
"donation": donation
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
categories_to_list = [category] if category else packages.keys()
|
|
12
|
+
categories = load_categories()
|
|
13
|
+
categories_to_list = [category] if category else categories.keys()
|
|
30
14
|
|
|
31
15
|
for cat_name in categories_to_list:
|
|
32
|
-
|
|
33
|
-
modules = load_modules(
|
|
16
|
+
path = categories[cat_name]
|
|
17
|
+
modules = load_modules(path)
|
|
34
18
|
print(Fore.MAGENTA +
|
|
35
|
-
|
|
19
|
+
f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
|
|
36
20
|
for module in modules:
|
|
37
21
|
site_name = module.__name__.split(".")[-1]
|
|
38
22
|
print(f" - {site_name}")
|
|
@@ -47,7 +31,7 @@ def main():
|
|
|
47
31
|
"-u", "--username", help="Username to scan across platforms"
|
|
48
32
|
)
|
|
49
33
|
parser.add_argument(
|
|
50
|
-
"-c", "--category", choices=
|
|
34
|
+
"-c", "--category", choices=load_categories().keys(),
|
|
51
35
|
help="Scan all platforms in a category"
|
|
52
36
|
)
|
|
53
37
|
parser.add_argument(
|
|
@@ -59,16 +43,28 @@ def main():
|
|
|
59
43
|
parser.add_argument(
|
|
60
44
|
"-v", "--verbose", action="store_true", help="Enable verbose output"
|
|
61
45
|
)
|
|
62
|
-
|
|
46
|
+
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"-p", "--permute",type=str,help="Generate username permutations using a string pattern (e.g -p 234)"
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"-s", "--stop",type=int,default=MAX_PERMUTATIONS_LIMIT,help="Limit the number of username permutations generated"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"-d", "--delay",type=float,default=0,help="Delay in seconds between requests (recommended: 1-2 seconds)"
|
|
56
|
+
)
|
|
57
|
+
|
|
63
58
|
args = parser.parse_args()
|
|
64
|
-
|
|
65
|
-
if args.module and "." in args.module:
|
|
66
|
-
args.module = args.module.replace(".", "_")
|
|
67
|
-
|
|
59
|
+
|
|
68
60
|
if args.list:
|
|
69
61
|
list_modules(args.category)
|
|
70
62
|
return
|
|
71
|
-
|
|
63
|
+
|
|
64
|
+
if not args.username:
|
|
65
|
+
parser.print_help()
|
|
66
|
+
return
|
|
67
|
+
|
|
72
68
|
# Special username checks before run
|
|
73
69
|
if (args.module == "x" or args.category == "social"):
|
|
74
70
|
if re.search(r"[^a-zA-Z0-9._-]", args.username):
|
|
@@ -78,37 +74,60 @@ def main():
|
|
|
78
74
|
if re.search(r"[^a-zA-Z0-9\.-]", args.username):
|
|
79
75
|
print(
|
|
80
76
|
Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. Bluesky will throw error. (Supported: only hyphens and digits)" + Style.RESET_ALL + "\n")
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
print_banner()
|
|
78
|
+
|
|
79
|
+
if args.permute and args.delay == 0:
|
|
80
|
+
print(
|
|
81
|
+
Fore.YELLOW
|
|
82
|
+
+ "[!] Warning: You're generating multiple usernames with NO delay between requests. "
|
|
83
|
+
"This may trigger rate limits or IP bans. Use --delay 1 or higher. (Use only if the sites throw errors otherwise ignore)\n"
|
|
84
|
+
+ Style.RESET_ALL)
|
|
85
|
+
|
|
86
|
+
usernames = [args.username] # Default single username list
|
|
87
|
+
|
|
88
|
+
#Added permutation support , generate all possible permutation of given sequence.
|
|
89
|
+
if args.permute:
|
|
90
|
+
usernames = generate_permutations(args.username, args.permute , args.stop)
|
|
91
|
+
print(Fore.CYAN + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if args.module and "." in args.module:
|
|
96
|
+
args.module = args.module.replace(".", "_")
|
|
86
97
|
|
|
87
|
-
from user_scanner import dev, social, creator, community, gaming, donation
|
|
88
98
|
|
|
89
99
|
if args.module:
|
|
90
100
|
# Single module search across all categories
|
|
91
|
-
packages = [dev, social, creator, community, gaming, donation]
|
|
92
101
|
found = False
|
|
93
|
-
for
|
|
94
|
-
modules = load_modules(
|
|
102
|
+
for cat_path in load_categories().values():
|
|
103
|
+
modules = load_modules(cat_path)
|
|
95
104
|
for module in modules:
|
|
96
105
|
site_name = module.__name__.split(".")[-1]
|
|
97
106
|
if site_name.lower() == args.module.lower():
|
|
98
107
|
from user_scanner.core.orchestrator import run_module_single
|
|
99
|
-
|
|
108
|
+
for name in usernames: # <-- permutation support here
|
|
109
|
+
run_module_single(module, name)
|
|
110
|
+
if args.delay > 0:
|
|
111
|
+
time.sleep(args.delay)
|
|
100
112
|
found = True
|
|
101
113
|
if not found:
|
|
102
114
|
print(
|
|
103
115
|
Fore.RED + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
|
|
104
116
|
elif args.category:
|
|
105
117
|
# Category-wise scan
|
|
106
|
-
category_package =
|
|
118
|
+
category_package = load_categories().get(args.category)
|
|
107
119
|
from user_scanner.core.orchestrator import run_checks_category
|
|
108
|
-
|
|
120
|
+
|
|
121
|
+
for name in usernames: # <-- permutation support here
|
|
122
|
+
run_checks_category(category_package, name, args.verbose)
|
|
123
|
+
if args.delay > 0:
|
|
124
|
+
time.sleep(args.delay)
|
|
109
125
|
else:
|
|
110
126
|
# Full scan
|
|
111
|
-
|
|
127
|
+
for name in usernames:
|
|
128
|
+
run_checks(name)
|
|
129
|
+
if args.delay > 0:
|
|
130
|
+
time.sleep(args.delay)
|
|
112
131
|
|
|
113
132
|
|
|
114
133
|
if __name__ == "__main__":
|
|
@@ -1,28 +1,43 @@
|
|
|
1
1
|
import importlib
|
|
2
|
-
import pkgutil
|
|
3
2
|
from colorama import Fore, Style
|
|
4
3
|
import threading
|
|
5
|
-
|
|
4
|
+
from itertools import permutations
|
|
6
5
|
import httpx
|
|
7
|
-
from
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from user_scanner.core.result import Result, AnyResult
|
|
8
|
+
from typing import Callable, Dict, List
|
|
8
9
|
|
|
9
10
|
lock = threading.Condition()
|
|
10
11
|
# Basically which thread is the one to print
|
|
11
12
|
print_queue = 0
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
def load_modules(
|
|
15
|
-
|
|
15
|
+
def load_modules(category_path: Path):
|
|
16
16
|
modules = []
|
|
17
|
-
for
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
for file in category_path.glob("*.py"):
|
|
18
|
+
if file.name == "__init__.py":
|
|
19
|
+
continue
|
|
20
|
+
spec = importlib.util.spec_from_file_location(file.stem, str(file))
|
|
21
|
+
module = importlib.util.module_from_spec(spec)
|
|
22
|
+
spec.loader.exec_module(module)
|
|
23
|
+
|
|
24
|
+
modules.append(module)
|
|
23
25
|
return modules
|
|
24
26
|
|
|
25
27
|
|
|
28
|
+
def load_categories() -> Dict[str, Path]:
|
|
29
|
+
root = Path(__file__).resolve().parent.parent # Should be user_scanner
|
|
30
|
+
categories = {}
|
|
31
|
+
|
|
32
|
+
for subfolder in root.iterdir():
|
|
33
|
+
if subfolder.is_dir() and \
|
|
34
|
+
not subfolder.name.lower() in ["cli", "utils", "core"] and \
|
|
35
|
+
not "__" in subfolder.name: # Removes __pycache__
|
|
36
|
+
categories[subfolder.name] = subfolder.resolve()
|
|
37
|
+
|
|
38
|
+
return categories
|
|
39
|
+
|
|
40
|
+
|
|
26
41
|
def worker_single(module, username, i):
|
|
27
42
|
global print_queue
|
|
28
43
|
|
|
@@ -36,12 +51,17 @@ def worker_single(module, username, i):
|
|
|
36
51
|
if func:
|
|
37
52
|
try:
|
|
38
53
|
result = func(username)
|
|
54
|
+
reason = ""
|
|
55
|
+
|
|
56
|
+
if isinstance(result, Result) and result.has_reason():
|
|
57
|
+
reason = f" ({result.get_reason()})"
|
|
58
|
+
|
|
39
59
|
if result == 1:
|
|
40
|
-
output = f" {Fore.GREEN}[✔] {site_name}: Available{Style.RESET_ALL}"
|
|
60
|
+
output = f" {Fore.GREEN}[✔] {site_name} ({username}): Available{Style.RESET_ALL}"
|
|
41
61
|
elif result == 0:
|
|
42
|
-
output = f" {Fore.RED}[✘] {site_name}: Taken{Style.RESET_ALL}"
|
|
62
|
+
output = f" {Fore.RED}[✘] {site_name} ({username}): Taken{Style.RESET_ALL}"
|
|
43
63
|
else:
|
|
44
|
-
output = f" {Fore.YELLOW}[!] {site_name}: Error{Style.RESET_ALL}"
|
|
64
|
+
output = f" {Fore.YELLOW}[!] {site_name} ({username}): Error{reason}{Style.RESET_ALL}"
|
|
45
65
|
except Exception as e:
|
|
46
66
|
output = f" {Fore.YELLOW}[!] {site_name}: Exception - {e}{Style.RESET_ALL}"
|
|
47
67
|
else:
|
|
@@ -62,11 +82,11 @@ def run_module_single(module, username):
|
|
|
62
82
|
worker_single(module, username, print_queue)
|
|
63
83
|
|
|
64
84
|
|
|
65
|
-
def run_checks_category(
|
|
85
|
+
def run_checks_category(category_path:Path, username:str, verbose=False):
|
|
66
86
|
global print_queue
|
|
67
87
|
|
|
68
|
-
modules = load_modules(
|
|
69
|
-
category_name =
|
|
88
|
+
modules = load_modules(category_path)
|
|
89
|
+
category_name = category_path.stem.capitalize()
|
|
70
90
|
print(f"{Fore.MAGENTA}== {category_name} SITES =={Style.RESET_ALL}")
|
|
71
91
|
|
|
72
92
|
print_queue = 0
|
|
@@ -82,18 +102,14 @@ def run_checks_category(package, username, verbose=False):
|
|
|
82
102
|
|
|
83
103
|
|
|
84
104
|
def run_checks(username):
|
|
85
|
-
from user_scanner import dev, social, creator, community, gaming, donation
|
|
86
|
-
|
|
87
|
-
packages = [dev, social, creator, community, gaming, donation]
|
|
88
|
-
|
|
89
105
|
print(f"\n{Fore.CYAN} Checking username: {username}{Style.RESET_ALL}\n")
|
|
90
106
|
|
|
91
|
-
for
|
|
92
|
-
run_checks_category(
|
|
107
|
+
for category_path in load_categories().values():
|
|
108
|
+
run_checks_category(category_path, username)
|
|
93
109
|
print()
|
|
94
110
|
|
|
95
111
|
|
|
96
|
-
def make_get_request(url, **kwargs):
|
|
112
|
+
def make_get_request(url: str, **kwargs) -> httpx.Response:
|
|
97
113
|
"""Simple wrapper to **httpx.get** that predefines headers and timeout"""
|
|
98
114
|
if not "headers" in kwargs:
|
|
99
115
|
kwargs["headers"] = {
|
|
@@ -110,39 +126,55 @@ def make_get_request(url, **kwargs):
|
|
|
110
126
|
return httpx.get(url, **kwargs)
|
|
111
127
|
|
|
112
128
|
|
|
113
|
-
def generic_validate(url, func, **kwargs):
|
|
129
|
+
def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kwargs) -> AnyResult:
|
|
114
130
|
"""
|
|
115
131
|
A generic validate function that makes a request and executes the provided function on the response.
|
|
116
132
|
"""
|
|
117
133
|
try:
|
|
118
134
|
response = make_get_request(url, **kwargs)
|
|
119
135
|
return func(response)
|
|
120
|
-
except
|
|
121
|
-
return
|
|
122
|
-
except Exception:
|
|
123
|
-
return 2
|
|
136
|
+
except Exception as e:
|
|
137
|
+
return Result.error(e)
|
|
124
138
|
|
|
125
139
|
|
|
126
|
-
def status_validate(url, available, taken, **kwargs):
|
|
140
|
+
def status_validate(url: str, available: int | List[int], taken: int | List[int], **kwargs) -> Result:
|
|
127
141
|
"""
|
|
128
142
|
Function that takes a **url** and **kwargs** for the request and
|
|
129
143
|
checks if the request status matches the availabe or taken.
|
|
130
144
|
**Available** and **Taken** must either be whole numbers or lists of whole numbers.
|
|
131
145
|
"""
|
|
132
|
-
def inner(response):
|
|
146
|
+
def inner(response: httpx.Response):
|
|
133
147
|
# Checks if a number is equal or is contained inside
|
|
134
148
|
def contains(a, b): return (isinstance(a, list) and b in a) or (a == b)
|
|
135
|
-
|
|
136
149
|
status = response.status_code
|
|
137
150
|
available_value = contains(available, status)
|
|
138
151
|
taken_value = contains(taken, status)
|
|
139
152
|
|
|
140
153
|
if available_value and taken_value:
|
|
141
|
-
|
|
154
|
+
# Can't be both available and taken
|
|
155
|
+
return Result.error("Invalid status match. Report this on Github.")
|
|
142
156
|
elif available_value:
|
|
143
|
-
return
|
|
157
|
+
return Result.available()
|
|
144
158
|
elif taken_value:
|
|
145
|
-
return
|
|
146
|
-
return
|
|
159
|
+
return Result.taken()
|
|
160
|
+
return Result.error("Status didn't match. Report this on Github.")
|
|
147
161
|
|
|
148
162
|
return generic_validate(url, inner, **kwargs)
|
|
163
|
+
|
|
164
|
+
def generate_permutations(username, pattern, limit=None):
|
|
165
|
+
"""
|
|
166
|
+
Generate all order-based permutations of characters in `pattern`
|
|
167
|
+
appended after `username`.
|
|
168
|
+
"""
|
|
169
|
+
permutations_set = {username}
|
|
170
|
+
|
|
171
|
+
chars = list(pattern)
|
|
172
|
+
|
|
173
|
+
# generate permutations of length 1 → len(chars)
|
|
174
|
+
for r in range(1, len(chars) + 1):
|
|
175
|
+
for combo in permutations(chars, r):
|
|
176
|
+
permutations_set.add(username + ''.join(combo))
|
|
177
|
+
if limit and len(permutations_set) >= limit:
|
|
178
|
+
return list(permutations_set)[:limit]
|
|
179
|
+
|
|
180
|
+
return sorted(permutations_set)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def humanize_exception(e: Exception) -> str:
|
|
6
|
+
msg = str(e).lower()
|
|
7
|
+
|
|
8
|
+
if "10054" in msg:
|
|
9
|
+
return "Connection closed by remote server"
|
|
10
|
+
if "11001" in msg:
|
|
11
|
+
return "Could not resolve hostname"
|
|
12
|
+
|
|
13
|
+
return str(e)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Status(Enum):
|
|
17
|
+
TAKEN = 0
|
|
18
|
+
AVAILABLE = 1
|
|
19
|
+
ERROR = 2
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Result:
|
|
23
|
+
def __init__(self, status: Status, reason: str | Exception | None = None):
|
|
24
|
+
self.status = status
|
|
25
|
+
self.reason = reason
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def taken(cls):
|
|
29
|
+
return cls(Status.TAKEN)
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def available(cls):
|
|
33
|
+
return cls(Status.AVAILABLE)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def error(cls, reason: str | Exception | None = None):
|
|
37
|
+
return cls(Status.ERROR, reason)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_number(cls, i: int, reason: str | Exception | None = None):
|
|
41
|
+
try:
|
|
42
|
+
status = Status(i)
|
|
43
|
+
except ValueError:
|
|
44
|
+
return cls(Status.ERROR, "Invalid status. Please contact maintainers.")
|
|
45
|
+
|
|
46
|
+
return cls(status, reason if status == Status.ERROR else None)
|
|
47
|
+
|
|
48
|
+
def to_number(self) -> int:
|
|
49
|
+
return self.status.value
|
|
50
|
+
|
|
51
|
+
def has_reason(self) -> bool:
|
|
52
|
+
return self.reason != None
|
|
53
|
+
|
|
54
|
+
def get_reason(self) -> str:
|
|
55
|
+
if self.reason == None:
|
|
56
|
+
return ""
|
|
57
|
+
if isinstance(self.reason, str):
|
|
58
|
+
return self.reason
|
|
59
|
+
#Format the exception
|
|
60
|
+
msg = humanize_exception(self.reason)
|
|
61
|
+
return f"{type(self.reason).__name__}: {msg.capitalize()}"
|
|
62
|
+
|
|
63
|
+
def __str__(self):
|
|
64
|
+
return self.get_reason()
|
|
65
|
+
|
|
66
|
+
def __eq__(self, other):
|
|
67
|
+
if isinstance(other, Status):
|
|
68
|
+
return self.status == other
|
|
69
|
+
|
|
70
|
+
if isinstance(other, Result):
|
|
71
|
+
return self.status == other.status
|
|
72
|
+
|
|
73
|
+
if isinstance(other, int):
|
|
74
|
+
return self.to_number() == other
|
|
75
|
+
|
|
76
|
+
return NotImplemented
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
AnyResult = Literal[0, 1, 2] | Result
|
user_scanner/creator/hashnode.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import httpx
|
|
2
|
-
import
|
|
3
|
-
from httpx import ConnectError, TimeoutException
|
|
2
|
+
from user_scanner.core.result import Result
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
def validate_hashnode(user):
|
|
@@ -27,21 +26,17 @@ def validate_hashnode(user):
|
|
|
27
26
|
|
|
28
27
|
if 'status' in data:
|
|
29
28
|
if data['status'] == 1:
|
|
30
|
-
return
|
|
29
|
+
return Result.available()
|
|
31
30
|
elif data['status'] == 0:
|
|
32
|
-
return
|
|
31
|
+
return Result.taken()
|
|
33
32
|
|
|
34
|
-
return
|
|
33
|
+
return Result.error("Status not found")
|
|
35
34
|
|
|
36
35
|
else:
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
except
|
|
40
|
-
return
|
|
41
|
-
except json.JSONDecodeError:
|
|
42
|
-
return 2
|
|
43
|
-
except Exception:
|
|
44
|
-
return 2
|
|
36
|
+
return Result.error("Invalid status code")
|
|
37
|
+
|
|
38
|
+
except Exception as e:
|
|
39
|
+
return Result.error(e)
|
|
45
40
|
|
|
46
41
|
|
|
47
42
|
if __name__ == "__main__":
|
user_scanner/creator/itch_io.py
CHANGED
|
@@ -2,11 +2,6 @@ from user_scanner.core.orchestrator import status_validate
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def validate_itch_io(user):
|
|
5
|
-
"""
|
|
6
|
-
Checks if a itch.io username is available.
|
|
7
|
-
Returns: 1 -> available, 0 -> taken, 2 -> error
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
5
|
url = f"https://{user}.itch.io"
|
|
11
6
|
|
|
12
7
|
return status_validate(url, 404, 200, follow_redirects=True)
|
user_scanner/creator/medium.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
from
|
|
1
|
+
from user_scanner.core.orchestrator import generic_validate
|
|
2
|
+
from user_scanner.core.result import Result
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def validate_medium(user):
|
|
@@ -10,24 +10,19 @@ def validate_medium(user):
|
|
|
10
10
|
'Accept': "text/html",
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
response = httpx.get(url, headers=headers, timeout=3.0)
|
|
15
|
-
|
|
13
|
+
def process(response):
|
|
16
14
|
if response.status_code == 200:
|
|
17
15
|
html_text = response.text
|
|
18
16
|
|
|
19
17
|
username_tag = f'property="profile:username" content="{user}"'
|
|
20
18
|
|
|
21
19
|
if username_tag in html_text:
|
|
22
|
-
return
|
|
20
|
+
return Result.taken()
|
|
23
21
|
else:
|
|
24
|
-
return
|
|
25
|
-
return
|
|
22
|
+
return Result.available()
|
|
23
|
+
return Result.error()
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
return 2
|
|
29
|
-
except Exception:
|
|
30
|
-
return 2
|
|
25
|
+
return generic_validate(url, process, headers=headers)
|
|
31
26
|
|
|
32
27
|
|
|
33
28
|
if __name__ == "__main__":
|
user_scanner/creator/patreon.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
from httpx import ConnectError, TimeoutException
|
|
1
|
+
from user_scanner.core.orchestrator import status_validate
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
def validate_producthunt(user):
|
|
@@ -12,23 +11,7 @@ def validate_producthunt(user):
|
|
|
12
11
|
'Accept-Language': "en-US,en;q=0.9",
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
response = httpx.get(url, headers=headers,
|
|
17
|
-
timeout=3.0, follow_redirects=True)
|
|
18
|
-
status = response.status_code
|
|
19
|
-
|
|
20
|
-
if status == 200:
|
|
21
|
-
return 0
|
|
22
|
-
elif status == 404:
|
|
23
|
-
return 1
|
|
24
|
-
else:
|
|
25
|
-
return 2
|
|
26
|
-
|
|
27
|
-
except (ConnectError, TimeoutException):
|
|
28
|
-
return 2
|
|
29
|
-
except Exception:
|
|
30
|
-
return 2
|
|
31
|
-
|
|
14
|
+
status_validate(url, 404, 200, headers=headers, follow_redirects=True)
|
|
32
15
|
|
|
33
16
|
if __name__ == "__main__":
|
|
34
17
|
user = input("Username?: ").strip()
|
user_scanner/dev/github.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from user_scanner.core.orchestrator import
|
|
1
|
+
from user_scanner.core.orchestrator import generic_validate, Result
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def validate_github(user):
|
|
@@ -18,7 +18,24 @@ def validate_github(user):
|
|
|
18
18
|
'priority': "u=1, i"
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
GITHUB_INVALID_MSG = (
|
|
22
|
+
"Username may only contain alphanumeric characters or single hyphens, "
|
|
23
|
+
"and cannot begin or end with a hyphen."
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def process(response):
|
|
27
|
+
if response.status_code == 200:
|
|
28
|
+
return Result.available()
|
|
29
|
+
|
|
30
|
+
if response.status_code == 422:
|
|
31
|
+
if GITHUB_INVALID_MSG in response.text:
|
|
32
|
+
return Result.error("Cannot start/end with hyphen or use double hyphens")
|
|
33
|
+
|
|
34
|
+
return Result.taken()
|
|
35
|
+
|
|
36
|
+
return Result.error("Unexpected GitHub response report it via issues")
|
|
37
|
+
|
|
38
|
+
return generic_validate(url, process, headers=headers)
|
|
22
39
|
|
|
23
40
|
|
|
24
41
|
if __name__ == "__main__":
|
user_scanner/dev/gitlab.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from user_scanner.core.orchestrator import generic_validate
|
|
2
|
+
from user_scanner.core.result import Result
|
|
2
3
|
|
|
3
4
|
|
|
4
5
|
def validate_gitlab(user):
|
|
@@ -18,11 +19,11 @@ def validate_gitlab(user):
|
|
|
18
19
|
# Corrected: Compare against Python boolean True/False
|
|
19
20
|
# AVAILABLE (return 1) if "exists": true
|
|
20
21
|
if data['exists'] is False:
|
|
21
|
-
return
|
|
22
|
+
return Result.available()
|
|
22
23
|
# UNAVAILABLE (return 0) if "exists": false
|
|
23
24
|
elif data['exists'] is True:
|
|
24
|
-
return
|
|
25
|
-
return
|
|
25
|
+
return Result.taken()
|
|
26
|
+
return Result.error("Invalid status code")
|
|
26
27
|
|
|
27
28
|
return generic_validate(url, process, headers=headers)
|
|
28
29
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from user_scanner.core.orchestrator import status_validate
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def validate_huggingface(user):
|
|
5
|
+
url = f"https://huggingface.co/{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_huggingface(user)
|
|
13
|
+
|
|
14
|
+
if result == 1:
|
|
15
|
+
print("Available!")
|
|
16
|
+
elif result == 0:
|
|
17
|
+
print("Unavailable!")
|
|
18
|
+
else:
|
|
19
|
+
print("Error occurred!")
|
user_scanner/dev/npmjs.py
CHANGED
|
@@ -1,34 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
from
|
|
1
|
+
import re
|
|
2
|
+
from user_scanner.core.orchestrator import status_validate, Result
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def validate_npmjs(user):
|
|
6
|
-
|
|
6
|
+
if re.match(r'^[^a-zA-Z0-9_-]', user):
|
|
7
|
+
return Result.error("Username cannot start with a period")
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
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': "gzip, deflate, br, zstd",
|
|
12
|
-
'accept-language': "en-US,en;q=0.9",
|
|
13
|
-
'priority': "u=0, i"
|
|
14
|
-
}
|
|
9
|
+
if re.search(r'[A-Z]', user):
|
|
10
|
+
return Result.error("Username cannot contain uppercase letters.")
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
response = httpx.head(url, headers=headers,
|
|
18
|
-
timeout=3.0, follow_redirects=True)
|
|
19
|
-
status = response.status_code
|
|
12
|
+
url = f"https://www.npmjs.com/~{user}"
|
|
20
13
|
|
|
21
|
-
if status == 200:
|
|
22
|
-
return 0
|
|
23
|
-
elif status == 404:
|
|
24
|
-
return 1
|
|
25
|
-
else:
|
|
26
|
-
return 2
|
|
27
14
|
|
|
28
|
-
|
|
29
|
-
return 2
|
|
30
|
-
except Exception:
|
|
31
|
-
return 2
|
|
15
|
+
return status_validate(url, 404, 200, timeout=3.0, follow_redirects=True)
|
|
32
16
|
|
|
33
17
|
|
|
34
18
|
if __name__ == "__main__":
|
|
@@ -40,4 +24,4 @@ if __name__ == "__main__":
|
|
|
40
24
|
elif result == 0:
|
|
41
25
|
print("Unavailable!")
|
|
42
26
|
else:
|
|
43
|
-
print("Error occurred!")
|
|
27
|
+
print(f"Error occurred! Reason: {result}")
|