user-scanner 1.0.2.1__py3-none-any.whl → 1.0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- user_scanner/__init__.py +0 -1
- user_scanner/__main__.py +114 -61
- user_scanner/cli/banner.py +3 -8
- user_scanner/cli/printer.py +117 -0
- user_scanner/community/__init__.py +1 -0
- user_scanner/community/coderlegion.py +11 -31
- user_scanner/community/stackoverflow.py +35 -0
- user_scanner/core/orchestrator.py +199 -79
- user_scanner/core/result.py +128 -0
- user_scanner/core/utils.py +9 -0
- user_scanner/creator/devto.py +11 -29
- user_scanner/creator/hashnode.py +25 -28
- user_scanner/creator/itch_io.py +19 -0
- user_scanner/creator/kaggle.py +11 -29
- user_scanner/creator/medium.py +18 -22
- user_scanner/creator/patreon.py +12 -38
- user_scanner/creator/producthunt.py +47 -0
- user_scanner/dev/codeberg.py +11 -29
- user_scanner/dev/cratesio.py +11 -24
- user_scanner/dev/dockerhub.py +11 -27
- user_scanner/dev/github.py +43 -39
- user_scanner/dev/gitlab.py +21 -32
- user_scanner/dev/huggingface.py +19 -0
- user_scanner/dev/launchpad.py +11 -24
- user_scanner/dev/npmjs.py +21 -34
- user_scanner/dev/replit.py +11 -29
- user_scanner/donation/__init__.py +0 -0
- user_scanner/donation/buymeacoffee.py +19 -0
- user_scanner/donation/liberapay.py +36 -0
- user_scanner/gaming/chess_com.py +20 -36
- user_scanner/gaming/minecraft.py +19 -0
- user_scanner/gaming/monkeytype.py +8 -26
- user_scanner/gaming/osu.py +13 -38
- user_scanner/gaming/roblox.py +37 -42
- user_scanner/gaming/steam.py +29 -0
- user_scanner/social/bluesky.py +21 -38
- user_scanner/social/discord.py +17 -21
- user_scanner/social/instagram.py +11 -24
- user_scanner/social/mastodon.py +12 -38
- user_scanner/social/pinterest.py +18 -32
- user_scanner/social/reddit.py +19 -32
- user_scanner/social/snapchat.py +24 -37
- user_scanner/social/soundcloud.py +43 -0
- user_scanner/social/telegram.py +19 -24
- user_scanner/social/threads.py +11 -24
- user_scanner/social/x.py +20 -28
- user_scanner/social/youtube.py +41 -47
- user_scanner/utils/version.py +2 -0
- user_scanner/version.json +1 -1
- {user_scanner-1.0.2.1.dist-info → user_scanner-1.0.9.0.dist-info}/METADATA +62 -67
- user_scanner-1.0.9.0.dist-info/RECORD +61 -0
- user_scanner-1.0.2.1.dist-info/RECORD +0 -48
- {user_scanner-1.0.2.1.dist-info → user_scanner-1.0.9.0.dist-info}/WHEEL +0 -0
- {user_scanner-1.0.2.1.dist-info → user_scanner-1.0.9.0.dist-info}/entry_points.txt +0 -0
- {user_scanner-1.0.2.1.dist-info → user_scanner-1.0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,92 +1,212 @@
|
|
|
1
1
|
import importlib
|
|
2
|
-
import
|
|
2
|
+
import importlib.util
|
|
3
3
|
from colorama import Fore, Style
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
|
+
from itertools import permutations
|
|
6
|
+
import httpx
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from user_scanner.cli.printer import Printer
|
|
9
|
+
from user_scanner.core.result import Result, AnyResult
|
|
10
|
+
from typing import Callable, Dict, List
|
|
11
|
+
from user_scanner.core.utils import get_site_name, is_last_value
|
|
4
12
|
|
|
5
|
-
def load_modules(package):
|
|
6
13
|
|
|
14
|
+
def load_modules(category_path: Path):
|
|
7
15
|
modules = []
|
|
8
|
-
for
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
for file in category_path.glob("*.py"):
|
|
17
|
+
if file.name == "__init__.py":
|
|
18
|
+
continue
|
|
19
|
+
spec = importlib.util.spec_from_file_location(file.stem, str(file))
|
|
20
|
+
module = importlib.util.module_from_spec(spec)
|
|
21
|
+
spec.loader.exec_module(module)
|
|
22
|
+
|
|
23
|
+
modules.append(module)
|
|
14
24
|
return modules
|
|
15
25
|
|
|
16
|
-
def run_module_single(module, username):
|
|
17
26
|
|
|
27
|
+
def load_categories() -> Dict[str, Path]:
|
|
28
|
+
root = Path(__file__).resolve().parent.parent # Should be user_scanner
|
|
29
|
+
categories = {}
|
|
30
|
+
|
|
31
|
+
for subfolder in root.iterdir():
|
|
32
|
+
if subfolder.is_dir() and \
|
|
33
|
+
not subfolder.name.lower() in ["cli", "utils", "core"] and \
|
|
34
|
+
not "__" in subfolder.name: # Removes __pycache__
|
|
35
|
+
categories[subfolder.name] = subfolder.resolve()
|
|
36
|
+
|
|
37
|
+
return categories
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def find_module(name: str):
|
|
41
|
+
name = name.lower()
|
|
42
|
+
|
|
43
|
+
matches = [
|
|
44
|
+
module
|
|
45
|
+
for category_path in load_categories().values()
|
|
46
|
+
for module in load_modules(category_path)
|
|
47
|
+
if module.__name__.split(".")[-1].lower() == name
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
return matches
|
|
51
|
+
|
|
52
|
+
def find_category(module) -> str | None:
|
|
53
|
+
|
|
54
|
+
module_file = getattr(module, '__file__', None)
|
|
55
|
+
if not module_file:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
category = Path(module_file).parent.name.lower()
|
|
59
|
+
categories = load_categories()
|
|
60
|
+
if category in categories:
|
|
61
|
+
return category.capitalize()
|
|
62
|
+
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def worker_single(module, username: str) -> Result:
|
|
18
68
|
func = next((getattr(module, f) for f in dir(module)
|
|
19
69
|
if f.startswith("validate_") and callable(getattr(module, f))), None)
|
|
20
|
-
site_name = module.__name__.split('.')[-1].capitalize()
|
|
21
|
-
if site_name == "X":
|
|
22
|
-
site_name = "X (Twitter)"
|
|
23
|
-
|
|
24
|
-
if func:
|
|
25
|
-
try:
|
|
26
|
-
result = func(username)
|
|
27
|
-
if result == 1:
|
|
28
|
-
print(f" {Fore.GREEN}[✔] {site_name}: Available{Style.RESET_ALL}")
|
|
29
|
-
elif result == 0:
|
|
30
|
-
print(f" {Fore.RED}[✘] {site_name}: Taken{Style.RESET_ALL}")
|
|
31
|
-
else:
|
|
32
|
-
print(f" {Fore.YELLOW}[!] {site_name}: Error{Style.RESET_ALL}")
|
|
33
|
-
except Exception as e:
|
|
34
|
-
print(f" {Fore.YELLOW}[!] {site_name}: Exception - {e}{Style.RESET_ALL}")
|
|
35
|
-
else:
|
|
36
|
-
print(f" {Fore.YELLOW}[!] {site_name} has no validate_ function{Style.RESET_ALL}")
|
|
37
|
-
|
|
38
|
-
def run_checks_category(package, username, verbose=False):
|
|
39
|
-
modules = load_modules(package)
|
|
40
|
-
category_name = package.__name__.split('.')[-1].capitalize()
|
|
41
|
-
print(f"{Fore.MAGENTA}== {category_name} SITES =={Style.RESET_ALL}")
|
|
42
|
-
|
|
43
|
-
for module in modules:
|
|
44
|
-
run_module_single(module, username)
|
|
45
|
-
|
|
46
|
-
def run_checks(username):
|
|
47
|
-
|
|
48
|
-
from user_scanner import dev, social,creator, community, gaming
|
|
49
|
-
|
|
50
|
-
categories = [
|
|
51
|
-
("DEV", dev),
|
|
52
|
-
("SOCIAL", social),
|
|
53
|
-
("CREATOR", creator),
|
|
54
|
-
("COMMUNITY", community),
|
|
55
|
-
("GAMING", gaming)
|
|
56
|
-
]
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
site_name = get_site_name(module)
|
|
72
|
+
|
|
73
|
+
if not func:
|
|
74
|
+
return Result.error(f"{site_name} has no validate_ function", site_name=site_name, username=username)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
result: Result = func(username)
|
|
78
|
+
result.update(site_name=site_name, username=username)
|
|
79
|
+
return result
|
|
80
|
+
except Exception as e:
|
|
81
|
+
return Result.error(e, site_name=site_name, username=username)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def run_module_single(module, username: str, printer: Printer, last: bool = True) -> List[Result]:
|
|
85
|
+
result = worker_single(module, username)
|
|
86
|
+
|
|
87
|
+
category = find_category(module)
|
|
88
|
+
if category:
|
|
89
|
+
result.update(category=category)
|
|
90
|
+
|
|
91
|
+
site_name = get_site_name(module)
|
|
92
|
+
msg = printer.get_result_output(result)
|
|
93
|
+
if last == False and printer.is_json:
|
|
94
|
+
msg += ","
|
|
95
|
+
print(msg)
|
|
96
|
+
|
|
97
|
+
return [result]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def run_checks_category(category_path: Path, username: str, printer: Printer, last: bool = True) -> List[Result]:
|
|
102
|
+
modules = load_modules(category_path)
|
|
103
|
+
|
|
104
|
+
category_name = category_path.stem.capitalize()
|
|
105
|
+
if printer.is_console:
|
|
106
|
+
print(f"\n{Fore.MAGENTA}== {category_name} SITES =={Style.RESET_ALL}")
|
|
107
|
+
|
|
108
|
+
results = []
|
|
109
|
+
|
|
110
|
+
with ThreadPoolExecutor(max_workers=10) as executor:
|
|
111
|
+
exec_map = executor.map(lambda m: worker_single(m, username), modules)
|
|
112
|
+
for i, result in enumerate(exec_map):
|
|
113
|
+
result.update(category = category_name)
|
|
114
|
+
results.append(result)
|
|
115
|
+
|
|
116
|
+
is_last = last and is_last_value(modules, i)
|
|
117
|
+
site_name = get_site_name(modules[i])
|
|
118
|
+
msg = printer.get_result_output(result)
|
|
119
|
+
if is_last == False and printer.is_json:
|
|
120
|
+
msg += ","
|
|
121
|
+
print(msg)
|
|
122
|
+
|
|
123
|
+
return results
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def run_checks(username: str, printer: Printer, last: bool = True) -> List[Result]:
|
|
127
|
+
if printer.is_console:
|
|
128
|
+
print(f"\n{Fore.CYAN} Checking username: {username}{Style.RESET_ALL}")
|
|
129
|
+
|
|
130
|
+
results = []
|
|
131
|
+
|
|
132
|
+
categories = list(load_categories().values())
|
|
133
|
+
for i, category_path in enumerate(categories):
|
|
134
|
+
last_cat: int = last and (i == len(categories) - 1)
|
|
135
|
+
temp = run_checks_category(category_path, username, printer, last_cat)
|
|
136
|
+
results.extend(temp)
|
|
137
|
+
|
|
138
|
+
return results
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def make_get_request(url: str, **kwargs) -> httpx.Response:
|
|
142
|
+
"""Simple wrapper to **httpx.get** that predefines headers and timeout"""
|
|
143
|
+
if not "headers" in kwargs:
|
|
144
|
+
kwargs["headers"] = {
|
|
145
|
+
'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
|
|
146
|
+
'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
147
|
+
'Accept-Encoding': "gzip, deflate, br",
|
|
148
|
+
'Accept-Language': "en-US,en;q=0.9",
|
|
149
|
+
'sec-fetch-dest': "document",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if not "timeout" in kwargs:
|
|
153
|
+
kwargs["timeout"] = 5.0
|
|
154
|
+
|
|
155
|
+
return httpx.get(url, **kwargs)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kwargs) -> AnyResult:
|
|
159
|
+
"""
|
|
160
|
+
A generic validate function that makes a request and executes the provided function on the response.
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
response = make_get_request(url, **kwargs)
|
|
164
|
+
result = func(response)
|
|
165
|
+
result.url = url
|
|
166
|
+
return result
|
|
167
|
+
except Exception as e:
|
|
168
|
+
return Result.error(e, url=url)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def status_validate(url: str, available: int | List[int], taken: int | List[int], **kwargs) -> Result:
|
|
172
|
+
"""
|
|
173
|
+
Function that takes a **url** and **kwargs** for the request and
|
|
174
|
+
checks if the request status matches the availabe or taken.
|
|
175
|
+
**Available** and **Taken** must either be whole numbers or lists of whole numbers.
|
|
176
|
+
"""
|
|
177
|
+
def inner(response: httpx.Response):
|
|
178
|
+
# Checks if a number is equal or is contained inside
|
|
179
|
+
def contains(a, b): return (isinstance(a, list) and b in a) or (a == b)
|
|
180
|
+
status = response.status_code
|
|
181
|
+
available_value = contains(available, status)
|
|
182
|
+
taken_value = contains(taken, status)
|
|
183
|
+
|
|
184
|
+
if available_value and taken_value:
|
|
185
|
+
# Can't be both available and taken
|
|
186
|
+
return Result.error("Invalid status match. Report this on Github.")
|
|
187
|
+
elif available_value:
|
|
188
|
+
return Result.available()
|
|
189
|
+
elif taken_value:
|
|
190
|
+
return Result.taken()
|
|
191
|
+
return Result.error("Status didn't match. Report this on Github.")
|
|
192
|
+
|
|
193
|
+
return generic_validate(url, inner, **kwargs)
|
|
59
194
|
|
|
60
|
-
for cat_name, package in categories:
|
|
61
|
-
try:
|
|
62
|
-
modules = load_modules(package)
|
|
63
|
-
except ModuleNotFoundError:
|
|
64
|
-
continue
|
|
65
195
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if result == 1:
|
|
84
|
-
print(f" {Fore.GREEN}[✔] {site_name}: Available{Style.RESET_ALL}")
|
|
85
|
-
elif result == 0:
|
|
86
|
-
print(f" {Fore.RED}[✘] {site_name}: Taken{Style.RESET_ALL}")
|
|
87
|
-
else:
|
|
88
|
-
print(f" {Fore.YELLOW}[!] {site_name}: Error{Style.RESET_ALL}")
|
|
89
|
-
except Exception as e:
|
|
90
|
-
print(f" {Fore.YELLOW}[!] {site_name}: Exception - {e}{Style.RESET_ALL}")
|
|
91
|
-
|
|
92
|
-
print()
|
|
196
|
+
def generate_permutations(username, pattern, limit=None):
|
|
197
|
+
"""
|
|
198
|
+
Generate all order-based permutations of characters in `pattern`
|
|
199
|
+
appended after `username`.
|
|
200
|
+
"""
|
|
201
|
+
permutations_set = {username}
|
|
202
|
+
|
|
203
|
+
chars = list(pattern)
|
|
204
|
+
|
|
205
|
+
# generate permutations of length 1 → len(chars)
|
|
206
|
+
for r in range(1, len(chars) + 1):
|
|
207
|
+
for combo in permutations(chars, r):
|
|
208
|
+
permutations_set.add(username + ''.join(combo))
|
|
209
|
+
if limit and len(permutations_set) >= limit:
|
|
210
|
+
return list(permutations_set)[:limit]
|
|
211
|
+
|
|
212
|
+
return sorted(permutations_set)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
DEBUG_MSG = """Result {{
|
|
5
|
+
status: {status},
|
|
6
|
+
reason: "{reason}",
|
|
7
|
+
username: "{username}",
|
|
8
|
+
site_name: "{site_name}",
|
|
9
|
+
category: "{category}",
|
|
10
|
+
}}"""
|
|
11
|
+
|
|
12
|
+
JSON_TEMPLATE = """{{
|
|
13
|
+
\t"username": "{username}",
|
|
14
|
+
\t"category": "{category}",
|
|
15
|
+
\t"site_name": "{site_name}",
|
|
16
|
+
\t"status": "{status}",
|
|
17
|
+
\t"reason": "{reason}"
|
|
18
|
+
}}"""
|
|
19
|
+
|
|
20
|
+
CSV_TEMPLATE = "{username},{category},{site_name},{status},{reason}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def humanize_exception(e: Exception) -> str:
|
|
24
|
+
msg = str(e).lower()
|
|
25
|
+
|
|
26
|
+
if "10054" in msg:
|
|
27
|
+
return "Connection closed by remote server"
|
|
28
|
+
if "11001" in msg:
|
|
29
|
+
return "Could not resolve hostname"
|
|
30
|
+
|
|
31
|
+
return str(e)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Status(Enum):
|
|
35
|
+
TAKEN = 0
|
|
36
|
+
AVAILABLE = 1
|
|
37
|
+
ERROR = 2
|
|
38
|
+
|
|
39
|
+
def __str__(self):
|
|
40
|
+
return super().__str__().split(".")[1].capitalize()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Result:
|
|
44
|
+
def __init__(self, status: Status, reason: str | Exception | None = None, **kwargs):
|
|
45
|
+
self.status = status
|
|
46
|
+
self.reason = reason
|
|
47
|
+
|
|
48
|
+
self.username = None
|
|
49
|
+
self.site_name = None
|
|
50
|
+
self.category = None
|
|
51
|
+
self.update(**kwargs)
|
|
52
|
+
|
|
53
|
+
def update(self, **kwargs):
|
|
54
|
+
for field in ("username", "site_name", "category"):
|
|
55
|
+
if field in kwargs and kwargs[field] is not None:
|
|
56
|
+
setattr(self, field, kwargs[field])
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def taken(cls, **kwargs):
|
|
60
|
+
return cls(Status.TAKEN, **kwargs)
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def available(cls, **kwargs):
|
|
64
|
+
return cls(Status.AVAILABLE, **kwargs)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def error(cls, reason: str | Exception | None = None, **kwargs):
|
|
68
|
+
return cls(Status.ERROR, reason, **kwargs)
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_number(cls, i: int, reason: str | Exception | None = None):
|
|
72
|
+
try:
|
|
73
|
+
status = Status(i)
|
|
74
|
+
except ValueError:
|
|
75
|
+
return cls(Status.ERROR, "Invalid status. Please contact maintainers.")
|
|
76
|
+
|
|
77
|
+
return cls(status, reason if status == Status.ERROR else None)
|
|
78
|
+
|
|
79
|
+
def to_number(self) -> int:
|
|
80
|
+
return self.status.value
|
|
81
|
+
|
|
82
|
+
def has_reason(self) -> bool:
|
|
83
|
+
return self.reason != None
|
|
84
|
+
|
|
85
|
+
def get_reason(self) -> str:
|
|
86
|
+
if self.reason == None:
|
|
87
|
+
return ""
|
|
88
|
+
if isinstance(self.reason, str):
|
|
89
|
+
return self.reason
|
|
90
|
+
# Format the exception
|
|
91
|
+
msg = humanize_exception(self.reason)
|
|
92
|
+
return f"{type(self.reason).__name__}: {msg.capitalize()}"
|
|
93
|
+
|
|
94
|
+
def as_dict(self) -> dict:
|
|
95
|
+
return {
|
|
96
|
+
"status": self.status,
|
|
97
|
+
"reason": self.get_reason(),
|
|
98
|
+
"username": self.username,
|
|
99
|
+
"site_name": self.site_name,
|
|
100
|
+
"category": self.category
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def debug(self) -> str:
|
|
104
|
+
return DEBUG_MSG.format(**self.as_dict())
|
|
105
|
+
|
|
106
|
+
def to_json(self) -> str:
|
|
107
|
+
return JSON_TEMPLATE.format(**self.as_dict())
|
|
108
|
+
|
|
109
|
+
def to_csv(self) -> str:
|
|
110
|
+
return CSV_TEMPLATE.format(**self.as_dict())
|
|
111
|
+
|
|
112
|
+
def __str__(self):
|
|
113
|
+
return self.get_reason()
|
|
114
|
+
|
|
115
|
+
def __eq__(self, other):
|
|
116
|
+
if isinstance(other, Status):
|
|
117
|
+
return self.status == other
|
|
118
|
+
|
|
119
|
+
if isinstance(other, Result):
|
|
120
|
+
return self.status == other.status
|
|
121
|
+
|
|
122
|
+
if isinstance(other, int):
|
|
123
|
+
return self.to_number() == other
|
|
124
|
+
|
|
125
|
+
return NotImplemented
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
AnyResult = Literal[0, 1, 2] | Result
|
user_scanner/creator/devto.py
CHANGED
|
@@ -1,37 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from user_scanner.core.orchestrator import status_validate
|
|
2
|
+
|
|
3
3
|
|
|
4
4
|
def validate_devto(user):
|
|
5
5
|
url = f"https://dev.to/{user}"
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
|
|
9
|
-
'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
response = httpx.head(url, headers=headers, timeout=3.0, follow_redirects=True)
|
|
14
|
-
status = response.status_code
|
|
15
|
-
|
|
16
|
-
if status == 200:
|
|
17
|
-
return 0
|
|
18
|
-
elif status == 404:
|
|
19
|
-
return 1
|
|
20
|
-
else:
|
|
21
|
-
return 2
|
|
7
|
+
return status_validate(url, 404, 200, follow_redirects=True)
|
|
22
8
|
|
|
23
|
-
except (ConnectError, TimeoutException):
|
|
24
|
-
return 2
|
|
25
|
-
except Exception:
|
|
26
|
-
return 2
|
|
27
9
|
|
|
28
10
|
if __name__ == "__main__":
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
user = input("Username?: ").strip()
|
|
12
|
+
result = validate_devto(user)
|
|
31
13
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
14
|
+
if result == 1:
|
|
15
|
+
print("Available!")
|
|
16
|
+
elif result == 0:
|
|
17
|
+
print("Unavailable!")
|
|
18
|
+
else:
|
|
19
|
+
print("Error occurred!")
|
user_scanner/creator/hashnode.py
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import httpx
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
from user_scanner.core.result import Result
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
def validate_hashnode(user):
|
|
6
6
|
url = "https://hashnode.com/utility/ajax/check-username"
|
|
7
7
|
|
|
8
8
|
payload = {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
"username": user,
|
|
10
|
+
"name": "Dummy Dummy"
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
headers = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36",
|
|
15
|
+
'Accept': "application/json",
|
|
16
|
+
'Content-Type': "application/json",
|
|
17
|
+
'Origin': "https://hashnode.com",
|
|
18
|
+
'Referer': "https://hashnode.com/signup",
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
try:
|
|
@@ -26,29 +26,26 @@ def validate_hashnode(user):
|
|
|
26
26
|
|
|
27
27
|
if 'status' in data:
|
|
28
28
|
if data['status'] == 1:
|
|
29
|
-
return
|
|
29
|
+
return Result.available()
|
|
30
30
|
elif data['status'] == 0:
|
|
31
|
-
return
|
|
31
|
+
return Result.taken()
|
|
32
32
|
|
|
33
|
-
return
|
|
33
|
+
return Result.error("Status not found")
|
|
34
34
|
|
|
35
35
|
else:
|
|
36
|
-
|
|
36
|
+
return Result.error("Invalid status code")
|
|
37
|
+
|
|
38
|
+
except Exception as e:
|
|
39
|
+
return Result.error(e)
|
|
37
40
|
|
|
38
|
-
except (ConnectError, TimeoutException):
|
|
39
|
-
return 2
|
|
40
|
-
except json.JSONDecodeError:
|
|
41
|
-
return 2
|
|
42
|
-
except Exception:
|
|
43
|
-
return 2
|
|
44
41
|
|
|
45
42
|
if __name__ == "__main__":
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
user = input("Username?: ").strip()
|
|
44
|
+
result = validate_hashnode(user)
|
|
45
|
+
|
|
46
|
+
if result == 1:
|
|
47
|
+
print("Available!")
|
|
48
|
+
elif result == 0:
|
|
49
|
+
print("Unavailable!")
|
|
50
|
+
else:
|
|
51
|
+
print("Error occurred!")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from user_scanner.core.orchestrator import status_validate
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def validate_itch_io(user):
|
|
5
|
+
url = f"https://{user}.itch.io"
|
|
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_itch_io(user)
|
|
13
|
+
|
|
14
|
+
if result == 1:
|
|
15
|
+
print("Available!")
|
|
16
|
+
elif result == 0:
|
|
17
|
+
print("Unavailable!")
|
|
18
|
+
else:
|
|
19
|
+
print("Error occurred!")
|
user_scanner/creator/kaggle.py
CHANGED
|
@@ -1,37 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from user_scanner.core.orchestrator import status_validate
|
|
2
|
+
|
|
3
3
|
|
|
4
4
|
def validate_kaggle(user):
|
|
5
5
|
url = f"https://www.kaggle.com/{user}"
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36",
|
|
9
|
-
'Accept': "text/html",
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
response = httpx.get(url, headers=headers, timeout=3.0, follow_redirects=True)
|
|
14
|
-
status = response.status_code
|
|
15
|
-
|
|
16
|
-
if status == 200:
|
|
17
|
-
return 0
|
|
18
|
-
elif status == 404:
|
|
19
|
-
return 1
|
|
20
|
-
else:
|
|
21
|
-
return 2
|
|
7
|
+
return status_validate(url, 404, 200, follow_redirects=True)
|
|
22
8
|
|
|
23
|
-
except (ConnectError, TimeoutException):
|
|
24
|
-
return 2
|
|
25
|
-
except Exception:
|
|
26
|
-
return 2
|
|
27
9
|
|
|
28
10
|
if __name__ == "__main__":
|
|
29
|
-
|
|
30
|
-
|
|
11
|
+
user = input("Username?: ").strip()
|
|
12
|
+
result = validate_kaggle(user)
|
|
31
13
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
14
|
+
if result == 1:
|
|
15
|
+
print("Available!")
|
|
16
|
+
elif result == 0:
|
|
17
|
+
print("Unavailable!")
|
|
18
|
+
else:
|
|
19
|
+
print("Error occurred!")
|
user_scanner/creator/medium.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
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
|
def validate_medium(user):
|
|
5
6
|
url = f"https://medium.com/@{user}"
|
|
@@ -9,33 +10,28 @@ def validate_medium(user):
|
|
|
9
10
|
'Accept': "text/html",
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
response = httpx.get(url, headers=headers, timeout=3.0)
|
|
14
|
-
|
|
13
|
+
def process(response):
|
|
15
14
|
if response.status_code == 200:
|
|
16
15
|
html_text = response.text
|
|
17
16
|
|
|
18
|
-
|
|
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()
|
|
24
|
+
|
|
25
|
+
return generic_validate(url, process, headers=headers)
|
|
26
26
|
|
|
27
|
-
except (ConnectError, TimeoutException):
|
|
28
|
-
return 2
|
|
29
|
-
except Exception:
|
|
30
|
-
return 2
|
|
31
27
|
|
|
32
28
|
if __name__ == "__main__":
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
29
|
+
user = input("Username?: ").strip()
|
|
30
|
+
result = validate_medium(user)
|
|
31
|
+
|
|
32
|
+
if result == 1:
|
|
33
|
+
print("Available!")
|
|
34
|
+
elif result == 0:
|
|
35
|
+
print("Unavailable!")
|
|
36
|
+
else:
|
|
37
|
+
print("Error occurred!")
|