user-scanner 1.0.8.1__tar.gz → 1.0.9.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.8.1 → user_scanner-1.0.9.0}/PKG-INFO +22 -10
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/README.md +21 -9
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/pyproject.toml +1 -1
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/__main__.py +86 -61
- user_scanner-1.0.9.0/user_scanner/cli/printer.py +117 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/core/orchestrator.py +86 -55
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/core/result.py +57 -8
- user_scanner-1.0.9.0/user_scanner/core/utils.py +9 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/version.json +1 -1
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/LICENSE +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/__init__.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/cli/__init__.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/cli/banner.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/community/__init__.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/community/coderlegion.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/community/stackoverflow.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/core/__init__.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/creator/__init__.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/creator/devto.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/creator/hashnode.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/creator/itch_io.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/creator/kaggle.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/creator/medium.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/creator/patreon.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/creator/producthunt.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/__init__.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/codeberg.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/cratesio.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/dockerhub.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/github.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/gitlab.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/huggingface.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/launchpad.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/npmjs.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/dev/replit.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/donation/__init__.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/donation/buymeacoffee.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/donation/liberapay.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/gaming/__init__.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/gaming/chess_com.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/gaming/minecraft.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/gaming/monkeytype.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/gaming/osu.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/gaming/roblox.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/gaming/steam.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/__init__.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/bluesky.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/discord.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/instagram.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/mastodon.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/pinterest.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/reddit.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/snapchat.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/soundcloud.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/telegram.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/threads.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/x.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/social/youtube.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.0}/user_scanner/utils/update.py +0 -0
- {user_scanner-1.0.8.1 → user_scanner-1.0.9.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.9.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.9.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" />
|
|
@@ -31,14 +31,16 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
|
|
|
31
31
|
|
|
32
32
|
### Features
|
|
33
33
|
|
|
34
|
-
- ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities
|
|
35
|
-
- ✅ Clear **Available / Taken / Error** output for each platform
|
|
34
|
+
- ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**
|
|
35
|
+
- ✅ Clear **Available / Taken / Error** output for each platform
|
|
36
36
|
- ✅ Robust error handling: It prints the exact reason (e.g. Cannot use underscores, hyphens at the start/end)
|
|
37
|
-
- ✅ Fully modular: add new platform modules easily
|
|
37
|
+
- ✅ Fully modular: add new platform modules easily
|
|
38
38
|
- ✅ Wildcard-based username permutations for automatic variation generation using provided suffix
|
|
39
|
+
- ✅ Selection of results format (e.g. json, csv, console (default))
|
|
40
|
+
- ✅ Get the scanning results in preferred format (json/csv) in specified output file (suitable for power users)
|
|
39
41
|
- ✅ Command-line interface ready: works directly after `pip install`
|
|
40
|
-
- ✅ Can be used as username OSINT tool
|
|
41
|
-
- ✅ Very low and lightweight dependencies, can be run on any machine
|
|
42
|
+
- ✅ Can be used as username OSINT tool
|
|
43
|
+
- ✅ Very low and lightweight dependencies, can be run on any machine
|
|
42
44
|
---
|
|
43
45
|
|
|
44
46
|
### Installation
|
|
@@ -62,15 +64,21 @@ Optionally, scan a specific category or single module:
|
|
|
62
64
|
user-scanner -u <username> -c dev
|
|
63
65
|
user-scanner -l # Lists all available modules
|
|
64
66
|
user-scanner -u <username> -m github
|
|
65
|
-
|
|
67
|
+
```
|
|
66
68
|
|
|
69
|
+
Also, the output file and format can be specified: <br>
|
|
70
|
+
\* Errors and warnings will only appear when the format is set to "console"
|
|
71
|
+
```bash
|
|
72
|
+
user-scanner -u <username> -f console #Default format
|
|
73
|
+
user-scanner -u <username> -f csv
|
|
74
|
+
user-scanner -u <username> -f json
|
|
75
|
+
user-scanner -u <username> -f <format> -o <output-file>
|
|
67
76
|
```
|
|
68
77
|
|
|
69
78
|
Generate multiple username variations by appending a suffix:
|
|
70
79
|
|
|
71
80
|
```bash
|
|
72
81
|
user-scanner -u <username> -p <suffix>
|
|
73
|
-
|
|
74
82
|
```
|
|
75
83
|
Optionally, scan a specific category or single module with limit:
|
|
76
84
|
|
|
@@ -78,7 +86,7 @@ Optionally, scan a specific category or single module with limit:
|
|
|
78
86
|
user-scanner -u <username> -p <suffix> -c dev
|
|
79
87
|
user-scanner -u <username> -p <suffix> -m github
|
|
80
88
|
user-scanner -u <username> -p <suffix> -s <number> # limit generation of usernames
|
|
81
|
-
user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
|
|
89
|
+
user-scanner -u <username> -p <suffix> -d <seconds> # delay to avoid rate-limits (can be 0s-1s)
|
|
82
90
|
```
|
|
83
91
|
|
|
84
92
|
---
|
|
@@ -94,6 +102,10 @@ user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
|
|
|
94
102
|
|
|
95
103
|
<img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
|
|
96
104
|
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
<img width="992" height="556" alt="1000141265" src="https://github.com/user-attachments/assets/9babb19f-bc87-4e7b-abe5-c52b8b1b672c" />
|
|
108
|
+
|
|
97
109
|
|
|
98
110
|
### Contributing:
|
|
99
111
|
|
|
@@ -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.9.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" />
|
|
@@ -18,14 +18,16 @@ Perfect for finding a **unique username** across GitHub, Twitter, Reddit, Instag
|
|
|
18
18
|
|
|
19
19
|
### Features
|
|
20
20
|
|
|
21
|
-
- ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities
|
|
22
|
-
- ✅ Clear **Available / Taken / Error** output for each platform
|
|
21
|
+
- ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**
|
|
22
|
+
- ✅ Clear **Available / Taken / Error** output for each platform
|
|
23
23
|
- ✅ Robust error handling: It prints the exact reason (e.g. Cannot use underscores, hyphens at the start/end)
|
|
24
|
-
- ✅ Fully modular: add new platform modules easily
|
|
24
|
+
- ✅ Fully modular: add new platform modules easily
|
|
25
25
|
- ✅ Wildcard-based username permutations for automatic variation generation using provided suffix
|
|
26
|
+
- ✅ Selection of results format (e.g. json, csv, console (default))
|
|
27
|
+
- ✅ Get the scanning results in preferred format (json/csv) in specified output file (suitable for power users)
|
|
26
28
|
- ✅ Command-line interface ready: works directly after `pip install`
|
|
27
|
-
- ✅ Can be used as username OSINT tool
|
|
28
|
-
- ✅ Very low and lightweight dependencies, can be run on any machine
|
|
29
|
+
- ✅ Can be used as username OSINT tool
|
|
30
|
+
- ✅ Very low and lightweight dependencies, can be run on any machine
|
|
29
31
|
---
|
|
30
32
|
|
|
31
33
|
### Installation
|
|
@@ -49,15 +51,21 @@ Optionally, scan a specific category or single module:
|
|
|
49
51
|
user-scanner -u <username> -c dev
|
|
50
52
|
user-scanner -l # Lists all available modules
|
|
51
53
|
user-scanner -u <username> -m github
|
|
52
|
-
|
|
54
|
+
```
|
|
53
55
|
|
|
56
|
+
Also, the output file and format can be specified: <br>
|
|
57
|
+
\* Errors and warnings will only appear when the format is set to "console"
|
|
58
|
+
```bash
|
|
59
|
+
user-scanner -u <username> -f console #Default format
|
|
60
|
+
user-scanner -u <username> -f csv
|
|
61
|
+
user-scanner -u <username> -f json
|
|
62
|
+
user-scanner -u <username> -f <format> -o <output-file>
|
|
54
63
|
```
|
|
55
64
|
|
|
56
65
|
Generate multiple username variations by appending a suffix:
|
|
57
66
|
|
|
58
67
|
```bash
|
|
59
68
|
user-scanner -u <username> -p <suffix>
|
|
60
|
-
|
|
61
69
|
```
|
|
62
70
|
Optionally, scan a specific category or single module with limit:
|
|
63
71
|
|
|
@@ -65,7 +73,7 @@ Optionally, scan a specific category or single module with limit:
|
|
|
65
73
|
user-scanner -u <username> -p <suffix> -c dev
|
|
66
74
|
user-scanner -u <username> -p <suffix> -m github
|
|
67
75
|
user-scanner -u <username> -p <suffix> -s <number> # limit generation of usernames
|
|
68
|
-
user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
|
|
76
|
+
user-scanner -u <username> -p <suffix> -d <seconds> # delay to avoid rate-limits (can be 0s-1s)
|
|
69
77
|
```
|
|
70
78
|
|
|
71
79
|
---
|
|
@@ -81,6 +89,10 @@ user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
|
|
|
81
89
|
|
|
82
90
|
<img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
|
|
83
91
|
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
<img width="992" height="556" alt="1000141265" src="https://github.com/user-attachments/assets/9babb19f-bc87-4e7b-abe5-c52b8b1b672c" />
|
|
95
|
+
|
|
84
96
|
|
|
85
97
|
### Contributing:
|
|
86
98
|
|
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import time
|
|
3
3
|
import re
|
|
4
|
-
from user_scanner.
|
|
4
|
+
from user_scanner.cli import printer
|
|
5
|
+
from user_scanner.core.orchestrator import generate_permutations, load_categories
|
|
5
6
|
from colorama import Fore, Style
|
|
6
|
-
from .cli import
|
|
7
|
-
from
|
|
7
|
+
from user_scanner.cli.banner import print_banner
|
|
8
|
+
from typing import List
|
|
9
|
+
from user_scanner.core.result import Result
|
|
10
|
+
from user_scanner.core.utils import is_last_value
|
|
8
11
|
|
|
9
12
|
MAX_PERMUTATIONS_LIMIT = 100 # To prevent excessive generation
|
|
10
13
|
|
|
11
|
-
def list_modules(category=None):
|
|
12
|
-
categories = load_categories()
|
|
13
|
-
categories_to_list = [category] if category else categories.keys()
|
|
14
|
-
|
|
15
|
-
for cat_name in categories_to_list:
|
|
16
|
-
path = categories[cat_name]
|
|
17
|
-
modules = load_modules(path)
|
|
18
|
-
print(Fore.MAGENTA +
|
|
19
|
-
f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
|
|
20
|
-
for module in modules:
|
|
21
|
-
site_name = module.__name__.split(".")[-1]
|
|
22
|
-
print(f" - {site_name}")
|
|
23
|
-
|
|
24
|
-
|
|
25
14
|
def main():
|
|
26
15
|
parser = argparse.ArgumentParser(
|
|
27
16
|
prog="user-scanner",
|
|
@@ -43,91 +32,127 @@ def main():
|
|
|
43
32
|
parser.add_argument(
|
|
44
33
|
"-v", "--verbose", action="store_true", help="Enable verbose output"
|
|
45
34
|
)
|
|
46
|
-
|
|
35
|
+
|
|
47
36
|
parser.add_argument(
|
|
48
37
|
"-p", "--permute",type=str,help="Generate username permutations using a string pattern (e.g -p 234)"
|
|
49
38
|
)
|
|
50
39
|
parser.add_argument(
|
|
51
40
|
"-s", "--stop",type=int,default=MAX_PERMUTATIONS_LIMIT,help="Limit the number of username permutations generated"
|
|
52
41
|
)
|
|
53
|
-
|
|
42
|
+
|
|
54
43
|
parser.add_argument(
|
|
55
44
|
"-d", "--delay",type=float,default=0,help="Delay in seconds between requests (recommended: 1-2 seconds)"
|
|
56
45
|
)
|
|
57
|
-
|
|
46
|
+
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"-f", "--format", choices=["console", "csv", "json"], default="console", help="Specify the output format (default: console)"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"-o", "--output", type=str, help="Specify the output file"
|
|
53
|
+
)
|
|
54
|
+
|
|
58
55
|
args = parser.parse_args()
|
|
59
|
-
|
|
56
|
+
|
|
57
|
+
Printer = printer.Printer(args.format)
|
|
58
|
+
|
|
60
59
|
if args.list:
|
|
61
|
-
|
|
60
|
+
Printer.print_modules(args.category)
|
|
62
61
|
return
|
|
63
|
-
|
|
62
|
+
|
|
64
63
|
if not args.username:
|
|
65
64
|
parser.print_help()
|
|
66
65
|
return
|
|
67
|
-
|
|
68
|
-
# Special username checks before run
|
|
69
|
-
if (args.module == "x" or args.category == "social"):
|
|
70
|
-
if re.search(r"[^a-zA-Z0-9._-]", args.username):
|
|
71
|
-
print(
|
|
72
|
-
Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. X (Twitter) doesn't support these." + Style.RESET_ALL)
|
|
73
|
-
if (args.module == "bluesky" or args.category == "social"):
|
|
74
|
-
if re.search(r"[^a-zA-Z0-9\.-]", args.username):
|
|
75
|
-
print(
|
|
76
|
-
Fore.RED + f"[!] Username '{args.username}' contains unsupported special characters. Bluesky will throw error. (Supported: only hyphens and digits)" + Style.RESET_ALL + "\n")
|
|
77
|
-
print_banner()
|
|
78
66
|
|
|
79
|
-
|
|
67
|
+
|
|
68
|
+
if Printer.is_console:
|
|
69
|
+
print_banner()
|
|
70
|
+
|
|
71
|
+
if args.permute and args.delay == 0 and Printer.is_console:
|
|
80
72
|
print(
|
|
81
73
|
Fore.YELLOW
|
|
82
74
|
+ "[!] Warning: You're generating multiple usernames with NO delay between requests. "
|
|
83
75
|
"This may trigger rate limits or IP bans. Use --delay 1 or higher. (Use only if the sites throw errors otherwise ignore)\n"
|
|
84
76
|
+ Style.RESET_ALL)
|
|
85
|
-
|
|
77
|
+
|
|
86
78
|
usernames = [args.username] # Default single username list
|
|
87
|
-
|
|
79
|
+
|
|
88
80
|
#Added permutation support , generate all possible permutation of given sequence.
|
|
89
81
|
if args.permute:
|
|
90
82
|
usernames = generate_permutations(args.username, args.permute , args.stop)
|
|
91
|
-
|
|
83
|
+
if Printer.is_console:
|
|
84
|
+
print(
|
|
85
|
+
Fore.CYAN + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)
|
|
92
86
|
|
|
93
|
-
|
|
94
|
-
|
|
95
87
|
if args.module and "." in args.module:
|
|
96
88
|
args.module = args.module.replace(".", "_")
|
|
97
89
|
|
|
90
|
+
def run_all_usernames(func, arg = None) -> List[Result]:
|
|
91
|
+
"""
|
|
92
|
+
Executes a function for all given usernames.
|
|
93
|
+
Made in order to simplify main()
|
|
94
|
+
"""
|
|
95
|
+
results = []
|
|
96
|
+
print(Printer.get_start())
|
|
97
|
+
for i, name in enumerate(usernames):
|
|
98
|
+
is_last = i == len(usernames) - 1
|
|
99
|
+
if arg == None:
|
|
100
|
+
results.extend(func(name, Printer, is_last))
|
|
101
|
+
else:
|
|
102
|
+
results.extend(func(arg, name, Printer, is_last))
|
|
103
|
+
if args.delay > 0 and not is_last:
|
|
104
|
+
time.sleep(args.delay)
|
|
105
|
+
if Printer.is_json:
|
|
106
|
+
print(Printer.get_end())
|
|
107
|
+
return results
|
|
108
|
+
|
|
109
|
+
results = []
|
|
98
110
|
|
|
99
111
|
if args.module:
|
|
100
112
|
# Single module search across all categories
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
from user_scanner.core.orchestrator import run_module_single, find_module
|
|
114
|
+
modules = find_module(args.module)
|
|
115
|
+
|
|
116
|
+
if len(modules) > 0:
|
|
104
117
|
for module in modules:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
from user_scanner.core.orchestrator import run_module_single
|
|
108
|
-
for name in usernames: # <-- permutation support here
|
|
109
|
-
run_module_single(module, name)
|
|
110
|
-
if args.delay > 0:
|
|
111
|
-
time.sleep(args.delay)
|
|
112
|
-
found = True
|
|
113
|
-
if not found:
|
|
118
|
+
results.extend(run_all_usernames(run_module_single, module))
|
|
119
|
+
else:
|
|
114
120
|
print(
|
|
115
121
|
Fore.RED + f"[!] Module '{args.module}' not found in any category." + Style.RESET_ALL)
|
|
122
|
+
|
|
116
123
|
elif args.category:
|
|
117
124
|
# Category-wise scan
|
|
118
125
|
category_package = load_categories().get(args.category)
|
|
119
126
|
from user_scanner.core.orchestrator import run_checks_category
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
run_checks_category(category_package, name, args.verbose)
|
|
123
|
-
if args.delay > 0:
|
|
124
|
-
time.sleep(args.delay)
|
|
127
|
+
results = run_all_usernames(run_checks_category, category_package)
|
|
128
|
+
|
|
125
129
|
else:
|
|
126
130
|
# Full scan
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
from user_scanner.core.orchestrator import run_checks
|
|
132
|
+
results = run_all_usernames(run_checks)
|
|
133
|
+
|
|
134
|
+
if not args.output:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
if args.output and Printer.is_console:
|
|
138
|
+
msg = (
|
|
139
|
+
"\n[!] The console format cannot be "
|
|
140
|
+
f"written to file: '{args.output}'."
|
|
141
|
+
)
|
|
142
|
+
print(Fore.RED + msg + Style.RESET_ALL)
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
content = Printer.get_start()
|
|
146
|
+
|
|
147
|
+
for i,result in enumerate(results):
|
|
148
|
+
char = "" if Printer.is_csv or is_last_value(results, i) else ","
|
|
149
|
+
content += "\n" + Printer.get_result_output(result) + char
|
|
150
|
+
|
|
151
|
+
if Printer.is_json:
|
|
152
|
+
content += "\n" + Printer.get_end()
|
|
153
|
+
|
|
154
|
+
with open(args.output, "a", encoding="utf-8") as f:
|
|
155
|
+
f.write(content)
|
|
131
156
|
|
|
132
157
|
|
|
133
158
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from colorama import Fore, Style
|
|
2
|
+
from typing import Literal
|
|
3
|
+
from user_scanner.core.result import Result, Status
|
|
4
|
+
|
|
5
|
+
INDENT = " "
|
|
6
|
+
CSV_HEADER = "username,category,site_name,status,url,reason"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def indentate(msg: str, indent: int):
|
|
10
|
+
if indent <= 0:
|
|
11
|
+
return msg
|
|
12
|
+
tabs = INDENT * indent
|
|
13
|
+
return "\n".join([f"{tabs}{line}" for line in msg.split("\n")])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Printer:
|
|
17
|
+
def __init__(self, format: Literal["console", "csv", "json"]) -> None:
|
|
18
|
+
if not format in ["console", "csv", "json"]:
|
|
19
|
+
raise ValueError(f"Invalid output-format: {format}")
|
|
20
|
+
self.mode: str = format
|
|
21
|
+
self.indent: int = 0
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def is_console(self) -> bool:
|
|
25
|
+
return self.mode == "console"
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def is_csv(self) -> bool:
|
|
29
|
+
return self.mode == "csv"
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def is_json(self) -> bool:
|
|
33
|
+
return self.mode == "json"
|
|
34
|
+
|
|
35
|
+
def get_start(self, json_char: str = "[") -> str:
|
|
36
|
+
if self.is_json:
|
|
37
|
+
self.indent += 1
|
|
38
|
+
return indentate(json_char, self.indent - 1)
|
|
39
|
+
elif self.is_csv:
|
|
40
|
+
return CSV_HEADER
|
|
41
|
+
return ""
|
|
42
|
+
|
|
43
|
+
def get_end(self, json_char: str = "]") -> str:
|
|
44
|
+
if not self.is_json:
|
|
45
|
+
return
|
|
46
|
+
self.indent = max(self.indent - 1, 0)
|
|
47
|
+
return indentate(json_char, self.indent)
|
|
48
|
+
|
|
49
|
+
def get_result_output(self, result: Result) -> str:
|
|
50
|
+
#In principle result should always have this
|
|
51
|
+
site_name = result.site_name
|
|
52
|
+
username = result.username
|
|
53
|
+
|
|
54
|
+
match (result.status, self.mode):
|
|
55
|
+
case (Status.AVAILABLE, "console"):
|
|
56
|
+
return f"{INDENT}{Fore.GREEN}[✔] {site_name} ({username}): Available{Style.RESET_ALL}"
|
|
57
|
+
|
|
58
|
+
case (Status.TAKEN, "console"):
|
|
59
|
+
return f"{INDENT}{Fore.RED}[✘] {site_name} ({username}): Taken{Style.RESET_ALL}"
|
|
60
|
+
|
|
61
|
+
case (Status.ERROR, "console"):
|
|
62
|
+
reason = ""
|
|
63
|
+
if isinstance(result, Result) and result.has_reason():
|
|
64
|
+
reason = f" ({result.get_reason()})"
|
|
65
|
+
return f"{INDENT}{Fore.YELLOW}[!] {site_name} ({username}): Error{reason}{Style.RESET_ALL}"
|
|
66
|
+
|
|
67
|
+
case (_, "json"):
|
|
68
|
+
return indentate(result.to_json().replace("\t", INDENT), self.indent)
|
|
69
|
+
|
|
70
|
+
case (_, "csv"):
|
|
71
|
+
return result.to_csv()
|
|
72
|
+
|
|
73
|
+
return ""
|
|
74
|
+
|
|
75
|
+
def print_modules(self, category: str | None = None):
|
|
76
|
+
from user_scanner.core.orchestrator import load_categories, load_modules
|
|
77
|
+
categories = load_categories()
|
|
78
|
+
categories_to_list = [category] if category else categories.keys()
|
|
79
|
+
|
|
80
|
+
# Print the start
|
|
81
|
+
if self.is_json:
|
|
82
|
+
print(self.get_start("{"))
|
|
83
|
+
elif self.is_csv:
|
|
84
|
+
print("category,site_name")
|
|
85
|
+
|
|
86
|
+
for i, cat_name in enumerate(categories_to_list):
|
|
87
|
+
path = categories[cat_name]
|
|
88
|
+
modules = load_modules(path)
|
|
89
|
+
|
|
90
|
+
# Print for each category
|
|
91
|
+
match self.mode:
|
|
92
|
+
case "console":
|
|
93
|
+
print(Fore.MAGENTA +
|
|
94
|
+
f"\n== {cat_name.upper()} SITES =={Style.RESET_ALL}")
|
|
95
|
+
case "json":
|
|
96
|
+
print(self.get_start(f"\"{cat_name}\": ["))
|
|
97
|
+
|
|
98
|
+
for j, module in enumerate(modules):
|
|
99
|
+
is_last = j == len(modules) - 1
|
|
100
|
+
site_name = module.__name__.split(".")[-1].capitalize()
|
|
101
|
+
|
|
102
|
+
# Print for each site name
|
|
103
|
+
match self.mode:
|
|
104
|
+
case "console":
|
|
105
|
+
print(f"{INDENT}- {site_name}")
|
|
106
|
+
case "json":
|
|
107
|
+
msg = f"\"{site_name}\"" + ("" if is_last else ",")
|
|
108
|
+
print(indentate(msg, self.indent))
|
|
109
|
+
case "csv":
|
|
110
|
+
print(f"{cat_name},{site_name}")
|
|
111
|
+
|
|
112
|
+
if self.is_json:
|
|
113
|
+
is_last = i == len(categories_to_list) - 1
|
|
114
|
+
print(self.get_end("]" if is_last else "],"))
|
|
115
|
+
|
|
116
|
+
if self.is_json:
|
|
117
|
+
print(self.get_end("}"))
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import importlib.util
|
|
3
3
|
from colorama import Fore, Style
|
|
4
|
-
import
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
5
|
from itertools import permutations
|
|
6
6
|
import httpx
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from user_scanner.cli.printer import Printer
|
|
8
9
|
from user_scanner.core.result import Result, AnyResult
|
|
9
10
|
from typing import Callable, Dict, List
|
|
10
|
-
|
|
11
|
-
lock = threading.Condition()
|
|
12
|
-
# Basically which thread is the one to print
|
|
13
|
-
print_queue = 0
|
|
11
|
+
from user_scanner.core.utils import get_site_name, is_last_value
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
def load_modules(category_path: Path):
|
|
@@ -39,75 +37,105 @@ def load_categories() -> Dict[str, Path]:
|
|
|
39
37
|
return categories
|
|
40
38
|
|
|
41
39
|
|
|
42
|
-
def
|
|
43
|
-
|
|
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
|
+
|
|
44
66
|
|
|
67
|
+
def worker_single(module, username: str) -> Result:
|
|
45
68
|
func = next((getattr(module, f) for f in dir(module)
|
|
46
69
|
if f.startswith("validate_") and callable(getattr(module, f))), None)
|
|
47
|
-
site_name = module.__name__.split('.')[-1].capitalize().replace("_", ".")
|
|
48
|
-
if site_name == "X":
|
|
49
|
-
site_name = "X (Twitter)"
|
|
50
70
|
|
|
51
|
-
|
|
52
|
-
if func:
|
|
53
|
-
try:
|
|
54
|
-
result = func(username)
|
|
55
|
-
reason = ""
|
|
71
|
+
site_name = get_site_name(module)
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
if not func:
|
|
74
|
+
return Result.error(f"{site_name} has no validate_ function", site_name=site_name, username=username)
|
|
59
75
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
except Exception as e:
|
|
67
|
-
output = f" {Fore.YELLOW}[!] {site_name}: Exception - {e}{Style.RESET_ALL}"
|
|
68
|
-
else:
|
|
69
|
-
output = f" {Fore.YELLOW}[!] {site_name} has no validate_ function{Style.RESET_ALL}"
|
|
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)
|
|
70
82
|
|
|
71
|
-
with lock:
|
|
72
|
-
# Waits for in-order printing
|
|
73
|
-
while i != print_queue:
|
|
74
|
-
lock.wait()
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
lock.notify_all()
|
|
84
|
+
def run_module_single(module, username: str, printer: Printer, last: bool = True) -> List[Result]:
|
|
85
|
+
result = worker_single(module, username)
|
|
79
86
|
|
|
87
|
+
category = find_category(module)
|
|
88
|
+
if category:
|
|
89
|
+
result.update(category=category)
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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)
|
|
84
96
|
|
|
97
|
+
return [result]
|
|
85
98
|
|
|
86
|
-
def run_checks_category(category_path:Path, username:str, verbose=False):
|
|
87
|
-
global print_queue
|
|
88
99
|
|
|
100
|
+
|
|
101
|
+
def run_checks_category(category_path: Path, username: str, printer: Printer, last: bool = True) -> List[Result]:
|
|
89
102
|
modules = load_modules(category_path)
|
|
103
|
+
|
|
90
104
|
category_name = category_path.stem.capitalize()
|
|
91
|
-
|
|
105
|
+
if printer.is_console:
|
|
106
|
+
print(f"\n{Fore.MAGENTA}== {category_name} SITES =={Style.RESET_ALL}")
|
|
92
107
|
|
|
93
|
-
|
|
108
|
+
results = []
|
|
94
109
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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)
|
|
100
115
|
|
|
101
|
-
|
|
102
|
-
|
|
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)
|
|
103
122
|
|
|
123
|
+
return results
|
|
104
124
|
|
|
105
|
-
def run_checks(username):
|
|
106
|
-
print(f"\n{Fore.CYAN} Checking username: {username}{Style.RESET_ALL}\n")
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
print()
|
|
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
|
|
111
139
|
|
|
112
140
|
|
|
113
141
|
def make_get_request(url: str, **kwargs) -> httpx.Response:
|
|
@@ -133,9 +161,11 @@ def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kw
|
|
|
133
161
|
"""
|
|
134
162
|
try:
|
|
135
163
|
response = make_get_request(url, **kwargs)
|
|
136
|
-
|
|
164
|
+
result = func(response)
|
|
165
|
+
result.url = url
|
|
166
|
+
return result
|
|
137
167
|
except Exception as e:
|
|
138
|
-
return Result.error(e)
|
|
168
|
+
return Result.error(e, url=url)
|
|
139
169
|
|
|
140
170
|
|
|
141
171
|
def status_validate(url: str, available: int | List[int], taken: int | List[int], **kwargs) -> Result:
|
|
@@ -162,6 +192,7 @@ def status_validate(url: str, available: int | List[int], taken: int | List[int]
|
|
|
162
192
|
|
|
163
193
|
return generic_validate(url, inner, **kwargs)
|
|
164
194
|
|
|
195
|
+
|
|
165
196
|
def generate_permutations(username, pattern, limit=None):
|
|
166
197
|
"""
|
|
167
198
|
Generate all order-based permutations of characters in `pattern`
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
from typing import Literal
|
|
3
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
|
+
|
|
4
22
|
|
|
5
23
|
def humanize_exception(e: Exception) -> str:
|
|
6
24
|
msg = str(e).lower()
|
|
@@ -18,23 +36,36 @@ class Status(Enum):
|
|
|
18
36
|
AVAILABLE = 1
|
|
19
37
|
ERROR = 2
|
|
20
38
|
|
|
39
|
+
def __str__(self):
|
|
40
|
+
return super().__str__().split(".")[1].capitalize()
|
|
41
|
+
|
|
21
42
|
|
|
22
43
|
class Result:
|
|
23
|
-
def __init__(self, status: Status, reason: str | Exception | None = None):
|
|
44
|
+
def __init__(self, status: Status, reason: str | Exception | None = None, **kwargs):
|
|
24
45
|
self.status = status
|
|
25
46
|
self.reason = reason
|
|
26
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
|
+
|
|
27
58
|
@classmethod
|
|
28
|
-
def taken(cls):
|
|
29
|
-
return cls(Status.TAKEN)
|
|
59
|
+
def taken(cls, **kwargs):
|
|
60
|
+
return cls(Status.TAKEN, **kwargs)
|
|
30
61
|
|
|
31
62
|
@classmethod
|
|
32
|
-
def available(cls):
|
|
33
|
-
return cls(Status.AVAILABLE)
|
|
63
|
+
def available(cls, **kwargs):
|
|
64
|
+
return cls(Status.AVAILABLE, **kwargs)
|
|
34
65
|
|
|
35
66
|
@classmethod
|
|
36
|
-
def error(cls, reason: str | Exception | None = None):
|
|
37
|
-
return cls(Status.ERROR, reason)
|
|
67
|
+
def error(cls, reason: str | Exception | None = None, **kwargs):
|
|
68
|
+
return cls(Status.ERROR, reason, **kwargs)
|
|
38
69
|
|
|
39
70
|
@classmethod
|
|
40
71
|
def from_number(cls, i: int, reason: str | Exception | None = None):
|
|
@@ -56,10 +87,28 @@ class Result:
|
|
|
56
87
|
return ""
|
|
57
88
|
if isinstance(self.reason, str):
|
|
58
89
|
return self.reason
|
|
59
|
-
#Format the exception
|
|
90
|
+
# Format the exception
|
|
60
91
|
msg = humanize_exception(self.reason)
|
|
61
92
|
return f"{type(self.reason).__name__}: {msg.capitalize()}"
|
|
62
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
|
+
|
|
63
112
|
def __str__(self):
|
|
64
113
|
return self.get_reason()
|
|
65
114
|
|
|
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
|