user-scanner 1.0.10.0__tar.gz → 1.0.10.2__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.
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/PKG-INFO +3 -3
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/README.md +1 -1
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/pyproject.toml +6 -2
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/__main__.py +2 -3
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/cli/banner.py +0 -1
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/cli/printer.py +2 -2
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/core/orchestrator.py +14 -13
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/core/result.py +2 -5
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/creator/hashnode.py +0 -1
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/creator/medium.py +14 -3
- user_scanner-1.0.10.2/user_scanner/creator/producthunt.py +25 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/gaming/monkeytype.py +15 -4
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/discord.py +0 -1
- user_scanner-1.0.10.2/user_scanner/social/mastodon.py +30 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/soundcloud.py +1 -6
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/telegram.py +2 -2
- user_scanner-1.0.10.2/user_scanner/social/tiktok.py +50 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/utils/updater_logic.py +28 -7
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/version.json +1 -1
- user_scanner-1.0.10.0/user_scanner/creator/producthunt.py +0 -47
- user_scanner-1.0.10.0/user_scanner/social/mastodon.py +0 -19
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/LICENSE +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/__init__.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/cli/__init__.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/community/__init__.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/community/coderlegion.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/community/hackernews.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/community/stackoverflow.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/config.json +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/core/__init__.py +0 -0
- /user_scanner-1.0.10.0/user_scanner/core/utils.py → /user_scanner-1.0.10.2/user_scanner/core/helpers.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/core/version.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/creator/__init__.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/creator/devto.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/creator/itch_io.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/creator/kaggle.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/creator/patreon.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/creator/substack.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/creator/twitch.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/__init__.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/bitbucket.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/codeberg.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/cratesio.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/dockerhub.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/github.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/gitlab.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/huggingface.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/launchpad.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/leetcode.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/npmjs.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/replit.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/dev/sourceforge.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/donation/__init__.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/donation/buymeacoffee.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/donation/liberapay.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/gaming/__init__.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/gaming/chess_com.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/gaming/lichess.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/gaming/minecraft.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/gaming/osu.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/gaming/roblox.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/gaming/steam.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/__init__.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/bluesky.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/instagram.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/pinterest.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/reddit.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/snapchat.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/threads.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/x.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/social/youtube.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/utils/__init__.py +0 -0
- {user_scanner-1.0.10.0 → user_scanner-1.0.10.2}/user_scanner/utils/update.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: user-scanner
|
|
3
|
-
Version: 1.0.10.
|
|
3
|
+
Version: 1.0.10.2
|
|
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
|
+
Requires-Python: >=3.10
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Requires-Dist: httpx
|
|
@@ -15,7 +15,7 @@ Project-URL: Homepage, https://github.com/kaifcodec/user-scanner
|
|
|
15
15
|
|
|
16
16
|

|
|
17
17
|
<p align="center">
|
|
18
|
-
<img src="https://img.shields.io/badge/Version-1.0.10.
|
|
18
|
+
<img src="https://img.shields.io/badge/Version-1.0.10.2-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" />
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
<p align="center">
|
|
5
|
-
<img src="https://img.shields.io/badge/Version-1.0.10.
|
|
5
|
+
<img src="https://img.shields.io/badge/Version-1.0.10.2-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" />
|
|
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "user-scanner"
|
|
7
|
-
version = "1.0.10.
|
|
7
|
+
version = "1.0.10.2"
|
|
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.
|
|
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,6 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import time
|
|
3
|
-
import re
|
|
4
3
|
import sys
|
|
5
4
|
from user_scanner.cli import printer
|
|
6
5
|
from user_scanner.core.orchestrator import generate_permutations, load_categories
|
|
@@ -8,7 +7,7 @@ from colorama import Fore, Style
|
|
|
8
7
|
from user_scanner.cli.banner import print_banner
|
|
9
8
|
from typing import List
|
|
10
9
|
from user_scanner.core.result import Result
|
|
11
|
-
from user_scanner.core.
|
|
10
|
+
from user_scanner.core.helpers import is_last_value
|
|
12
11
|
from user_scanner.utils.updater_logic import check_for_updates
|
|
13
12
|
from user_scanner.utils.update import update_self
|
|
14
13
|
|
|
@@ -119,7 +118,7 @@ def main():
|
|
|
119
118
|
print(Printer.get_start())
|
|
120
119
|
for i, name in enumerate(usernames):
|
|
121
120
|
is_last = i == len(usernames) - 1
|
|
122
|
-
if arg
|
|
121
|
+
if arg is None:
|
|
123
122
|
results.extend(func(name, Printer, is_last))
|
|
124
123
|
else:
|
|
125
124
|
results.extend(func(arg, name, Printer, is_last))
|
|
@@ -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
|
|
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
|
|
|
@@ -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
|
|
9
|
+
from user_scanner.core.result import Result
|
|
10
10
|
from typing import Callable, Dict, List
|
|
11
|
-
from user_scanner.core.
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
93
|
+
get_site_name(module)
|
|
92
94
|
msg = printer.get_result_output(result)
|
|
93
|
-
if last
|
|
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
|
-
|
|
119
|
+
get_site_name(modules[i])
|
|
118
120
|
msg = printer.get_result_output(result)
|
|
119
|
-
if is_last
|
|
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
|
|
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
|
|
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
|
|
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],
|
|
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
|
|
82
|
+
return self.reason is not None
|
|
84
83
|
|
|
85
84
|
def get_reason(self) -> str:
|
|
86
|
-
if self.reason
|
|
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
|
|
@@ -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/
|
|
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,25 @@
|
|
|
1
|
+
from user_scanner.core.orchestrator import status_validate
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def validate_producthunt(user):
|
|
5
|
+
url = f"https://www.producthunt.com/@{user}"
|
|
6
|
+
|
|
7
|
+
headers = {
|
|
8
|
+
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
|
|
9
|
+
'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
|
10
|
+
'Accept-Encoding': "gzip, deflate, br",
|
|
11
|
+
'Accept-Language': "en-US,en;q=0.9",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
status_validate(url, 404, 200, headers=headers, follow_redirects=True)
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
user = input("Username?: ").strip()
|
|
18
|
+
result = validate_producthunt(user)
|
|
19
|
+
|
|
20
|
+
if result == 1:
|
|
21
|
+
print("Available!")
|
|
22
|
+
elif result == 0:
|
|
23
|
+
print("Unavailable!")
|
|
24
|
+
else:
|
|
25
|
+
print("Error occured!")
|
|
@@ -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) ->
|
|
5
|
-
|
|
6
|
-
url = f"https://api.monkeytype.com/users/checkName/{
|
|
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
|
|
56
|
+
print("Error occured!")
|
|
@@ -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,
|
|
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) ->
|
|
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()}")
|
|
@@ -2,6 +2,7 @@ from pathlib import Path
|
|
|
2
2
|
from colorama import Fore
|
|
3
3
|
import sys
|
|
4
4
|
import json
|
|
5
|
+
import os
|
|
5
6
|
from user_scanner.core.version import get_pypi_version, load_local_version
|
|
6
7
|
from user_scanner.utils.update import update_self
|
|
7
8
|
|
|
@@ -15,19 +16,39 @@ X = Fore.RESET
|
|
|
15
16
|
CONFIG_PATH = Path(__file__).parent.parent / "config.json"
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
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())
|
|
21
38
|
|
|
22
39
|
default = {"auto_update_status": True}
|
|
23
|
-
|
|
40
|
+
# Ensure parent exists
|
|
41
|
+
cp.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
cp.write_text(json.dumps(default, indent=2))
|
|
24
43
|
return default
|
|
25
44
|
|
|
26
45
|
|
|
27
|
-
def save_config_change(status: bool):
|
|
28
|
-
|
|
46
|
+
def save_config_change(status: bool, path: str | Path | None = None):
|
|
47
|
+
cp = _get_config_path(path)
|
|
48
|
+
content = load_config(path)
|
|
29
49
|
content["auto_update_status"] = status
|
|
30
|
-
|
|
50
|
+
cp.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
cp.write_text(json.dumps(content, indent=2))
|
|
31
52
|
|
|
32
53
|
|
|
33
54
|
def check_for_updates():
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
from user_scanner.core.orchestrator import status_validate, Result
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def validate_youtube(user) -> Result:
|
|
5
|
-
url = f"https://m.youtube.com/@{user}"
|
|
6
|
-
headers = {
|
|
7
|
-
'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
|
|
8
|
-
'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",
|
|
9
|
-
'Accept-Encoding': "identity",
|
|
10
|
-
'sec-ch-dpr': "2.75",
|
|
11
|
-
'sec-ch-viewport-width': "980",
|
|
12
|
-
'sec-ch-ua': "\"Google Chrome\";v=\"143\", \"Chromium\";v=\"143\", \"Not A(Brand\";v=\"24\"",
|
|
13
|
-
'sec-ch-ua-mobile': "?1",
|
|
14
|
-
'sec-ch-ua-full-version': "\"143.0.7499.52\"",
|
|
15
|
-
'sec-ch-ua-arch': "\"\"",
|
|
16
|
-
'sec-ch-ua-platform': "\"Android\"",
|
|
17
|
-
'sec-ch-ua-platform-version': "\"15.0.0\"",
|
|
18
|
-
'sec-ch-ua-bitness': "\"\"",
|
|
19
|
-
'sec-ch-ua-wow64': "?0",
|
|
20
|
-
'sec-ch-ua-full-version-list': "\"Google Chrome\";v=\"143.0.7499.52\", \"Chromium\";v=\"143.0.7499.52\", \"Not A(Brand\";v=\"24.0.0.0\"",
|
|
21
|
-
'sec-ch-ua-form-factors': "\"Mobile\"",
|
|
22
|
-
'upgrade-insecure-requests': "1",
|
|
23
|
-
'x-browser-channel': "stable",
|
|
24
|
-
'x-browser-year': "2025",
|
|
25
|
-
'x-browser-copyright': "Copyright 2025 Google LLC. All Rights reserved.",
|
|
26
|
-
'sec-fetch-site': "none",
|
|
27
|
-
'sec-fetch-mode': "navigate",
|
|
28
|
-
'sec-fetch-user': "?1",
|
|
29
|
-
'sec-fetch-dest': "document",
|
|
30
|
-
'accept-language': "en-US,en;q=0.9",
|
|
31
|
-
'priority': "u=0, i"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return status_validate(url, 404, 200, headers=headers)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if __name__ == "__main__":
|
|
38
|
-
user = input("Username?: ").strip()
|
|
39
|
-
result = validate_youtube(user)
|
|
40
|
-
|
|
41
|
-
if result == 1:
|
|
42
|
-
print("Available!")
|
|
43
|
-
elif result == 0:
|
|
44
|
-
print("Unavailable!")
|
|
45
|
-
else:
|
|
46
|
-
reason = result.get_reason()
|
|
47
|
-
print(f"Error occurred! Reason: {reason}")
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|