user-scanner 1.0.8.0__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.0 → user_scanner-1.0.9.0}/PKG-INFO +33 -18
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/README.md +32 -17
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/pyproject.toml +1 -1
- {user_scanner-1.0.8.0 → 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.9.0/user_scanner/community/__init__.py +1 -0
- user_scanner-1.0.9.0/user_scanner/community/stackoverflow.py +35 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/core/orchestrator.py +87 -55
- {user_scanner-1.0.8.0 → 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.0/user_scanner/social/youtube.py → user_scanner-1.0.9.0/user_scanner/creator/producthunt.py +15 -11
- user_scanner-1.0.9.0/user_scanner/social/youtube.py +50 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/version.json +1 -1
- user_scanner-1.0.8.0/user_scanner/creator/producthunt.py +0 -25
- user_scanner-1.0.8.0/user_scanner/gaming/__init__.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/LICENSE +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/__init__.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/cli/__init__.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/cli/banner.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/community/coderlegion.py +0 -0
- {user_scanner-1.0.8.0/user_scanner/community → user_scanner-1.0.9.0/user_scanner/core}/__init__.py +0 -0
- {user_scanner-1.0.8.0/user_scanner/core → user_scanner-1.0.9.0/user_scanner/creator}/__init__.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/devto.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/hashnode.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/itch_io.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/kaggle.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/medium.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/creator/patreon.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/__init__.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/codeberg.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/cratesio.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/dockerhub.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/github.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/gitlab.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/huggingface.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/launchpad.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/npmjs.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/dev/replit.py +0 -0
- {user_scanner-1.0.8.0/user_scanner/creator → user_scanner-1.0.9.0/user_scanner/donation}/__init__.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/donation/buymeacoffee.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/donation/liberapay.py +0 -0
- {user_scanner-1.0.8.0/user_scanner/donation → user_scanner-1.0.9.0/user_scanner/gaming}/__init__.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/chess_com.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/minecraft.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/monkeytype.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/osu.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/roblox.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/gaming/steam.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/__init__.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/bluesky.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/discord.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/instagram.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/mastodon.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/pinterest.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/reddit.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/snapchat.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/soundcloud.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/telegram.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/threads.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/social/x.py +0 -0
- {user_scanner-1.0.8.0 → user_scanner-1.0.9.0}/user_scanner/utils/update.py +0 -0
- {user_scanner-1.0.8.0 → 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,13 +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
|
|
36
|
-
- ✅
|
|
37
|
-
- ✅
|
|
38
|
-
- ✅
|
|
39
|
-
- ✅
|
|
40
|
-
- ✅
|
|
34
|
+
- ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**
|
|
35
|
+
- ✅ Clear **Available / Taken / Error** output for each platform
|
|
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
|
|
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)
|
|
41
|
+
- ✅ Command-line interface ready: works directly after `pip install`
|
|
42
|
+
- ✅ Can be used as username OSINT tool
|
|
43
|
+
- ✅ Very low and lightweight dependencies, can be run on any machine
|
|
41
44
|
---
|
|
42
45
|
|
|
43
46
|
### Installation
|
|
@@ -61,15 +64,21 @@ Optionally, scan a specific category or single module:
|
|
|
61
64
|
user-scanner -u <username> -c dev
|
|
62
65
|
user-scanner -l # Lists all available modules
|
|
63
66
|
user-scanner -u <username> -m github
|
|
64
|
-
|
|
67
|
+
```
|
|
65
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>
|
|
66
76
|
```
|
|
67
77
|
|
|
68
78
|
Generate multiple username variations by appending a suffix:
|
|
69
79
|
|
|
70
80
|
```bash
|
|
71
81
|
user-scanner -u <username> -p <suffix>
|
|
72
|
-
|
|
73
82
|
```
|
|
74
83
|
Optionally, scan a specific category or single module with limit:
|
|
75
84
|
|
|
@@ -77,7 +86,7 @@ Optionally, scan a specific category or single module with limit:
|
|
|
77
86
|
user-scanner -u <username> -p <suffix> -c dev
|
|
78
87
|
user-scanner -u <username> -p <suffix> -m github
|
|
79
88
|
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
|
|
89
|
+
user-scanner -u <username> -p <suffix> -d <seconds> # delay to avoid rate-limits (can be 0s-1s)
|
|
81
90
|
```
|
|
82
91
|
|
|
83
92
|
---
|
|
@@ -93,6 +102,10 @@ user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
|
|
|
93
102
|
|
|
94
103
|
<img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
|
|
95
104
|
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
<img width="992" height="556" alt="1000141265" src="https://github.com/user-attachments/assets/9babb19f-bc87-4e7b-abe5-c52b8b1b672c" />
|
|
108
|
+
|
|
96
109
|
|
|
97
110
|
### Contributing:
|
|
98
111
|
|
|
@@ -109,12 +122,14 @@ user_scanner/
|
|
|
109
122
|
```
|
|
110
123
|
|
|
111
124
|
**Module guidelines:**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
125
|
+
This project contains small "validator" modules that check whether a username exists on a given platform. Each validator is a single function that returns a Result object (see `core/orchestrator.py`).
|
|
126
|
+
|
|
127
|
+
Result semantics:
|
|
128
|
+
- Result.available() → `available`
|
|
129
|
+
- Result.taken() → `taken`
|
|
130
|
+
- Result.error(message: Optional[str]) → `error`, blocked, unknown, or request failure (include short diagnostic message when helpful)
|
|
131
|
+
|
|
132
|
+
Follow this document when adding or updating validators.
|
|
118
133
|
|
|
119
134
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for examples.
|
|
120
135
|
|
|
@@ -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,13 +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
|
|
23
|
-
- ✅
|
|
24
|
-
- ✅
|
|
25
|
-
- ✅
|
|
26
|
-
- ✅
|
|
27
|
-
- ✅
|
|
21
|
+
- ✅ Check usernames across **social networks**, **developer platforms**, and **creator communities**
|
|
22
|
+
- ✅ Clear **Available / Taken / Error** output for each platform
|
|
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
|
|
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)
|
|
28
|
+
- ✅ Command-line interface ready: works directly after `pip install`
|
|
29
|
+
- ✅ Can be used as username OSINT tool
|
|
30
|
+
- ✅ Very low and lightweight dependencies, can be run on any machine
|
|
28
31
|
---
|
|
29
32
|
|
|
30
33
|
### Installation
|
|
@@ -48,15 +51,21 @@ Optionally, scan a specific category or single module:
|
|
|
48
51
|
user-scanner -u <username> -c dev
|
|
49
52
|
user-scanner -l # Lists all available modules
|
|
50
53
|
user-scanner -u <username> -m github
|
|
51
|
-
|
|
54
|
+
```
|
|
52
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>
|
|
53
63
|
```
|
|
54
64
|
|
|
55
65
|
Generate multiple username variations by appending a suffix:
|
|
56
66
|
|
|
57
67
|
```bash
|
|
58
68
|
user-scanner -u <username> -p <suffix>
|
|
59
|
-
|
|
60
69
|
```
|
|
61
70
|
Optionally, scan a specific category or single module with limit:
|
|
62
71
|
|
|
@@ -64,7 +73,7 @@ Optionally, scan a specific category or single module with limit:
|
|
|
64
73
|
user-scanner -u <username> -p <suffix> -c dev
|
|
65
74
|
user-scanner -u <username> -p <suffix> -m github
|
|
66
75
|
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
|
|
76
|
+
user-scanner -u <username> -p <suffix> -d <seconds> # delay to avoid rate-limits (can be 0s-1s)
|
|
68
77
|
```
|
|
69
78
|
|
|
70
79
|
---
|
|
@@ -80,6 +89,10 @@ user-scanner -u <username> -p <suffix> -d <seconds> #delay to avoid rate-limits
|
|
|
80
89
|
|
|
81
90
|
<img width="1080" height="352" alt="1000140393" src="https://github.com/user-attachments/assets/578b248c-2a05-4917-aab3-6372a7c28045" />
|
|
82
91
|
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
<img width="992" height="556" alt="1000141265" src="https://github.com/user-attachments/assets/9babb19f-bc87-4e7b-abe5-c52b8b1b672c" />
|
|
95
|
+
|
|
83
96
|
|
|
84
97
|
### Contributing:
|
|
85
98
|
|
|
@@ -96,12 +109,14 @@ user_scanner/
|
|
|
96
109
|
```
|
|
97
110
|
|
|
98
111
|
**Module guidelines:**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
112
|
+
This project contains small "validator" modules that check whether a username exists on a given platform. Each validator is a single function that returns a Result object (see `core/orchestrator.py`).
|
|
113
|
+
|
|
114
|
+
Result semantics:
|
|
115
|
+
- Result.available() → `available`
|
|
116
|
+
- Result.taken() → `taken`
|
|
117
|
+
- Result.error(message: Optional[str]) → `error`, blocked, unknown, or request failure (include short diagnostic message when helpful)
|
|
118
|
+
|
|
119
|
+
Follow this document when adding or updating validators.
|
|
105
120
|
|
|
106
121
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for examples.
|
|
107
122
|
|
|
@@ -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("}"))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# community
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from user_scanner.core.orchestrator import generic_validate
|
|
2
|
+
from user_scanner.core.result import Result
|
|
3
|
+
|
|
4
|
+
def validate_stackoverflow(user: str) -> Result:
|
|
5
|
+
url = f"https://stackoverflow.com/users/filter?search={user}"
|
|
6
|
+
|
|
7
|
+
def process(response):
|
|
8
|
+
if response.status_code == 200:
|
|
9
|
+
text = response.text
|
|
10
|
+
|
|
11
|
+
if "No users matched your search." in text:
|
|
12
|
+
return Result.available()
|
|
13
|
+
|
|
14
|
+
pattern = f'>{user}<'
|
|
15
|
+
if pattern in text:
|
|
16
|
+
return Result.taken()
|
|
17
|
+
|
|
18
|
+
return Result.available()
|
|
19
|
+
|
|
20
|
+
return Result.error("Unexpected status code from Stack Overflow")
|
|
21
|
+
|
|
22
|
+
return generic_validate(url, process)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
user = input("Username?: ").strip()
|
|
27
|
+
result = validate_stackoverflow(user)
|
|
28
|
+
|
|
29
|
+
if result == Result.available():
|
|
30
|
+
print("Available!")
|
|
31
|
+
elif result == Result.taken():
|
|
32
|
+
print("Unavailable!")
|
|
33
|
+
else:
|
|
34
|
+
msg = result.get_reason()
|
|
35
|
+
print("Error occurred!" + msg)
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import importlib
|
|
2
|
+
import importlib.util
|
|
2
3
|
from colorama import Fore, Style
|
|
3
|
-
import
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
5
|
from itertools import permutations
|
|
5
6
|
import httpx
|
|
6
7
|
from pathlib import Path
|
|
8
|
+
from user_scanner.cli.printer import Printer
|
|
7
9
|
from user_scanner.core.result import Result, AnyResult
|
|
8
10
|
from typing import Callable, Dict, List
|
|
9
|
-
|
|
10
|
-
lock = threading.Condition()
|
|
11
|
-
# Basically which thread is the one to print
|
|
12
|
-
print_queue = 0
|
|
11
|
+
from user_scanner.core.utils import get_site_name, is_last_value
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
def load_modules(category_path: Path):
|
|
@@ -38,75 +37,105 @@ def load_categories() -> Dict[str, Path]:
|
|
|
38
37
|
return categories
|
|
39
38
|
|
|
40
39
|
|
|
41
|
-
def
|
|
42
|
-
|
|
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
|
+
|
|
43
66
|
|
|
67
|
+
def worker_single(module, username: str) -> Result:
|
|
44
68
|
func = next((getattr(module, f) for f in dir(module)
|
|
45
69
|
if f.startswith("validate_") and callable(getattr(module, f))), None)
|
|
46
|
-
site_name = module.__name__.split('.')[-1].capitalize().replace("_", ".")
|
|
47
|
-
if site_name == "X":
|
|
48
|
-
site_name = "X (Twitter)"
|
|
49
70
|
|
|
50
|
-
|
|
51
|
-
if func:
|
|
52
|
-
try:
|
|
53
|
-
result = func(username)
|
|
54
|
-
reason = ""
|
|
71
|
+
site_name = get_site_name(module)
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
if not func:
|
|
74
|
+
return Result.error(f"{site_name} has no validate_ function", site_name=site_name, username=username)
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
except Exception as e:
|
|
66
|
-
output = f" {Fore.YELLOW}[!] {site_name}: Exception - {e}{Style.RESET_ALL}"
|
|
67
|
-
else:
|
|
68
|
-
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)
|
|
69
82
|
|
|
70
|
-
with lock:
|
|
71
|
-
# Waits for in-order printing
|
|
72
|
-
while i != print_queue:
|
|
73
|
-
lock.wait()
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
lock.notify_all()
|
|
84
|
+
def run_module_single(module, username: str, printer: Printer, last: bool = True) -> List[Result]:
|
|
85
|
+
result = worker_single(module, username)
|
|
78
86
|
|
|
87
|
+
category = find_category(module)
|
|
88
|
+
if category:
|
|
89
|
+
result.update(category=category)
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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)
|
|
83
96
|
|
|
97
|
+
return [result]
|
|
84
98
|
|
|
85
|
-
def run_checks_category(category_path:Path, username:str, verbose=False):
|
|
86
|
-
global print_queue
|
|
87
99
|
|
|
100
|
+
|
|
101
|
+
def run_checks_category(category_path: Path, username: str, printer: Printer, last: bool = True) -> List[Result]:
|
|
88
102
|
modules = load_modules(category_path)
|
|
103
|
+
|
|
89
104
|
category_name = category_path.stem.capitalize()
|
|
90
|
-
|
|
105
|
+
if printer.is_console:
|
|
106
|
+
print(f"\n{Fore.MAGENTA}== {category_name} SITES =={Style.RESET_ALL}")
|
|
91
107
|
|
|
92
|
-
|
|
108
|
+
results = []
|
|
93
109
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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)
|
|
99
115
|
|
|
100
|
-
|
|
101
|
-
|
|
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)
|
|
102
122
|
|
|
123
|
+
return results
|
|
103
124
|
|
|
104
|
-
def run_checks(username):
|
|
105
|
-
print(f"\n{Fore.CYAN} Checking username: {username}{Style.RESET_ALL}\n")
|
|
106
125
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
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
|
|
110
139
|
|
|
111
140
|
|
|
112
141
|
def make_get_request(url: str, **kwargs) -> httpx.Response:
|
|
@@ -132,9 +161,11 @@ def generic_validate(url: str, func: Callable[[httpx.Response], AnyResult], **kw
|
|
|
132
161
|
"""
|
|
133
162
|
try:
|
|
134
163
|
response = make_get_request(url, **kwargs)
|
|
135
|
-
|
|
164
|
+
result = func(response)
|
|
165
|
+
result.url = url
|
|
166
|
+
return result
|
|
136
167
|
except Exception as e:
|
|
137
|
-
return Result.error(e)
|
|
168
|
+
return Result.error(e, url=url)
|
|
138
169
|
|
|
139
170
|
|
|
140
171
|
def status_validate(url: str, available: int | List[int], taken: int | List[int], **kwargs) -> Result:
|
|
@@ -161,6 +192,7 @@ def status_validate(url: str, available: int | List[int], taken: int | List[int]
|
|
|
161
192
|
|
|
162
193
|
return generic_validate(url, inner, **kwargs)
|
|
163
194
|
|
|
195
|
+
|
|
164
196
|
def generate_permutations(username, pattern, limit=None):
|
|
165
197
|
"""
|
|
166
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
|
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
from user_scanner.core.orchestrator import status_validate
|
|
1
|
+
from user_scanner.core.orchestrator import status_validate, Result
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
def validate_youtube(user):
|
|
4
|
+
def validate_youtube(user) -> Result:
|
|
5
5
|
url = f"https://m.youtube.com/@{user}"
|
|
6
|
-
|
|
7
6
|
headers = {
|
|
8
|
-
'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
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",
|
|
9
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",
|
|
10
|
-
'Accept-Encoding': "
|
|
11
|
-
'
|
|
12
|
-
'sec-ch-
|
|
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
13
|
'sec-ch-ua-mobile': "?1",
|
|
14
|
-
'sec-ch-ua-full-version': "\"
|
|
14
|
+
'sec-ch-ua-full-version': "\"143.0.7499.52\"",
|
|
15
15
|
'sec-ch-ua-arch': "\"\"",
|
|
16
16
|
'sec-ch-ua-platform': "\"Android\"",
|
|
17
17
|
'sec-ch-ua-platform-version': "\"15.0.0\"",
|
|
18
18
|
'sec-ch-ua-bitness': "\"\"",
|
|
19
19
|
'sec-ch-ua-wow64': "?0",
|
|
20
|
-
'sec-ch-ua-full-version-list': "\"Google Chrome\";v=\"
|
|
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
21
|
'sec-ch-ua-form-factors': "\"Mobile\"",
|
|
22
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.",
|
|
23
26
|
'sec-fetch-site': "none",
|
|
24
27
|
'sec-fetch-mode': "navigate",
|
|
25
28
|
'sec-fetch-user': "?1",
|
|
@@ -28,7 +31,7 @@ def validate_youtube(user):
|
|
|
28
31
|
'priority': "u=0, i"
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
status_validate(url, 404, 200, headers=headers
|
|
34
|
+
return status_validate(url, 404, 200, headers=headers)
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
if __name__ == "__main__":
|
|
@@ -40,4 +43,5 @@ if __name__ == "__main__":
|
|
|
40
43
|
elif result == 0:
|
|
41
44
|
print("Unavailable!")
|
|
42
45
|
else:
|
|
43
|
-
|
|
46
|
+
reason = result.get_reason()
|
|
47
|
+
print(f"Error occurred! Reason: {reason}")
|
|
@@ -0,0 +1,50 @@
|
|
|
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-model': "\"I2404\"",
|
|
19
|
+
'sec-ch-ua-bitness': "\"\"",
|
|
20
|
+
'sec-ch-ua-wow64': "?0",
|
|
21
|
+
'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\"",
|
|
22
|
+
'sec-ch-ua-form-factors': "\"Mobile\"",
|
|
23
|
+
'upgrade-insecure-requests': "1",
|
|
24
|
+
'x-browser-channel': "stable",
|
|
25
|
+
'x-browser-year': "2025",
|
|
26
|
+
'x-browser-copyright': "Copyright 2025 Google LLC. All Rights reserved.",
|
|
27
|
+
'sec-fetch-site': "none",
|
|
28
|
+
'sec-fetch-mode': "navigate",
|
|
29
|
+
'sec-fetch-user': "?1",
|
|
30
|
+
'sec-fetch-dest': "document",
|
|
31
|
+
'accept-language': "en-US,en;q=0.9",
|
|
32
|
+
'priority': "u=0, i"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
return status_validate(url, 404, 200, headers=headers)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
user = input("Username?: ").strip()
|
|
41
|
+
result = validate_youtube(user)
|
|
42
|
+
|
|
43
|
+
if result == 1:
|
|
44
|
+
print("Available!")
|
|
45
|
+
elif result == 0:
|
|
46
|
+
print("Unavailable!")
|
|
47
|
+
else:
|
|
48
|
+
reason = result.get_reason()
|
|
49
|
+
print(f"Error occurred! Reason: {reason}")
|
|
50
|
+
|
|
@@ -1,25 +0,0 @@
|
|
|
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!")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{user_scanner-1.0.8.0/user_scanner/community → user_scanner-1.0.9.0/user_scanner/core}/__init__.py
RENAMED
|
File without changes
|
{user_scanner-1.0.8.0/user_scanner/core → user_scanner-1.0.9.0/user_scanner/creator}/__init__.py
RENAMED
|
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
|
{user_scanner-1.0.8.0/user_scanner/creator → user_scanner-1.0.9.0/user_scanner/donation}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{user_scanner-1.0.8.0/user_scanner/donation → user_scanner-1.0.9.0/user_scanner/gaming}/__init__.py
RENAMED
|
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
|