user-scanner 1.0.6.0__tar.gz → 1.0.8.0__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.6.0 → user_scanner-1.0.8.0}/PKG-INFO +27 -6
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/README.md +26 -5
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/pyproject.toml +2 -2
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/__main__.py +63 -44
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/core/orchestrator.py +68 -36
- user_scanner-1.0.8.0/user_scanner/core/result.py +79 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/creator/hashnode.py +8 -13
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/creator/itch_io.py +0 -5
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/creator/medium.py +7 -12
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/creator/patreon.py +0 -6
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/creator/producthunt.py +2 -19
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/dev/github.py +19 -2
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/dev/gitlab.py +4 -3
- user_scanner-1.0.8.0/user_scanner/dev/huggingface.py +19 -0
- user_scanner-1.0.8.0/user_scanner/dev/npmjs.py +27 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/donation/buymeacoffee.py +0 -3
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/gaming/chess_com.py +4 -9
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/gaming/minecraft.py +0 -5
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/gaming/monkeytype.py +4 -10
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/gaming/osu.py +0 -5
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/gaming/roblox.py +14 -9
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/gaming/steam.py +6 -9
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/bluesky.py +7 -13
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/discord.py +6 -9
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/mastodon.py +0 -6
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/pinterest.py +4 -4
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/reddit.py +4 -3
- user_scanner-1.0.8.0/user_scanner/social/soundcloud.py +43 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/telegram.py +6 -6
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/x.py +10 -20
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/youtube.py +2 -18
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/version.json +1 -1
- user_scanner-1.0.6.0/user_scanner/dev/npmjs.py +0 -43
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/LICENSE +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/__init__.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/cli/__init__.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/cli/banner.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/community/__init__.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/community/coderlegion.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/core/__init__.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/creator/__init__.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/creator/devto.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/creator/kaggle.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/dev/__init__.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/dev/codeberg.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/dev/cratesio.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/dev/dockerhub.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/dev/launchpad.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/dev/replit.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/donation/__init__.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/donation/liberapay.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/gaming/__init__.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/__init__.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/instagram.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/snapchat.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/social/threads.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/utils/update.py +0 -0
- {user_scanner-1.0.6.0 → user_scanner-1.0.8.0}/user_scanner/utils/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: user-scanner
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.8.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>
|
|
@@ -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.
|
|
18
|
+
<img src="https://img.shields.io/badge/Version-1.0.8.0-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" />
|
|
@@ -34,6 +34,7 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
|
|
|
34
34
|
- ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**.
|
|
35
35
|
- ✅ Clear **Available / Taken / Error** output for each platform.
|
|
36
36
|
- ✅ Fully modular: add new platform modules easily.
|
|
37
|
+
- ✅ Wildcard-based username permutations for automatic variation generation
|
|
37
38
|
- ✅ Command-line interface ready: works directly after `pip install`.
|
|
38
39
|
- ✅ Can be used as username OSINT tool.
|
|
39
40
|
- ✅ Very low and lightweight dependencies, can be run on any machine.
|
|
@@ -60,14 +61,37 @@ Optionally, scan a specific category or single module:
|
|
|
60
61
|
user-scanner -u <username> -c dev
|
|
61
62
|
user-scanner -l # Lists all available modules
|
|
62
63
|
user-scanner -u <username> -m github
|
|
64
|
+
user-scanner -u <username> -p <suffix>
|
|
63
65
|
|
|
64
66
|
```
|
|
67
|
+
|
|
68
|
+
Generate multiple username variations by appending a suffix:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
user-scanner -u <username> -p <suffix>
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Optionally, scan a specific category or single module with limit:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
user-scanner -u <username> -p <suffix> -c dev
|
|
78
|
+
user-scanner -u <username> -p <suffix> -m github
|
|
79
|
+
user-scanner -u <username> -p <suffix> -s <number> # limit generation of usernames
|
|
80
|
+
user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
|
|
81
|
+
```
|
|
82
|
+
|
|
65
83
|
---
|
|
66
84
|
### Screenshot:
|
|
67
85
|
|
|
68
86
|
- Note*: New modules are constantly getting added so this might have only limited, outdated output:
|
|
69
87
|
|
|
70
|
-
|
|
88
|
+
|
|
89
|
+
<img width="1080" height="770" alt="1000140392" src="https://github.com/user-attachments/assets/4638c8f6-40c6-46f8-ae17-ac65cd199d81" />
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
<img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
|
|
71
95
|
|
|
72
96
|
|
|
73
97
|
### Contributing:
|
|
@@ -94,9 +118,6 @@ user_scanner/
|
|
|
94
118
|
|
|
95
119
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for examples.
|
|
96
120
|
|
|
97
|
-
### 📧 Contact:
|
|
98
|
-
- [Email](kaifcodec@gmail.com)
|
|
99
|
-
|
|
100
121
|
---
|
|
101
122
|
|
|
102
123
|
### Dependencies:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
<p align="center">
|
|
5
|
-
<img src="https://img.shields.io/badge/Version-1.0.
|
|
5
|
+
<img src="https://img.shields.io/badge/Version-1.0.8.0-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" />
|
|
@@ -21,6 +21,7 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
|
|
|
21
21
|
- ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**.
|
|
22
22
|
- ✅ Clear **Available / Taken / Error** output for each platform.
|
|
23
23
|
- ✅ Fully modular: add new platform modules easily.
|
|
24
|
+
- ✅ Wildcard-based username permutations for automatic variation generation
|
|
24
25
|
- ✅ Command-line interface ready: works directly after `pip install`.
|
|
25
26
|
- ✅ Can be used as username OSINT tool.
|
|
26
27
|
- ✅ Very low and lightweight dependencies, can be run on any machine.
|
|
@@ -47,14 +48,37 @@ Optionally, scan a specific category or single module:
|
|
|
47
48
|
user-scanner -u <username> -c dev
|
|
48
49
|
user-scanner -l # Lists all available modules
|
|
49
50
|
user-scanner -u <username> -m github
|
|
51
|
+
user-scanner -u <username> -p <suffix>
|
|
50
52
|
|
|
51
53
|
```
|
|
54
|
+
|
|
55
|
+
Generate multiple username variations by appending a suffix:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
user-scanner -u <username> -p <suffix>
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Optionally, scan a specific category or single module with limit:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
user-scanner -u <username> -p <suffix> -c dev
|
|
65
|
+
user-scanner -u <username> -p <suffix> -m github
|
|
66
|
+
user-scanner -u <username> -p <suffix> -s <number> # limit generation of usernames
|
|
67
|
+
user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
|
|
68
|
+
```
|
|
69
|
+
|
|
52
70
|
---
|
|
53
71
|
### Screenshot:
|
|
54
72
|
|
|
55
73
|
- Note*: New modules are constantly getting added so this might have only limited, outdated output:
|
|
56
74
|
|
|
57
|
-
|
|
75
|
+
|
|
76
|
+
<img width="1080" height="770" alt="1000140392" src="https://github.com/user-attachments/assets/4638c8f6-40c6-46f8-ae17-ac65cd199d81" />
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
<img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
|
|
58
82
|
|
|
59
83
|
|
|
60
84
|
### Contributing:
|
|
@@ -81,9 +105,6 @@ user_scanner/
|
|
|
81
105
|
|
|
82
106
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for examples.
|
|
83
107
|
|
|
84
|
-
### 📧 Contact:
|
|
85
|
-
- [Email](kaifcodec@gmail.com)
|
|
86
|
-
|
|
87
108
|
---
|
|
88
109
|
|
|
89
110
|
### Dependencies:
|
|
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "user-scanner"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.8.0"
|
|
8
8
|
description = "Check username availability across multiple popular platforms"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -26,4 +26,4 @@ Homepage = "https://github.com/kaifcodec/user-scanner"
|
|
|
26
26
|
user-scanner = "user_scanner.__main__:main"
|
|
27
27
|
|
|
28
28
|
[tool.flit.sdist]
|
|
29
|
-
exclude = ["tests/", "docs/", ".github/", ".git", "rm_pycache", "che.py", ".gitignore"]
|
|
29
|
+
exclude = ["tests/", "docs/", ".github/", ".git", "rm_pycache.py", "che.py", ".gitignore"]
|
|
@@ -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
|
|
@@ -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__":
|
|
@@ -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)
|
|
@@ -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__":
|