Skip to content

Commit 71da2f8

Browse files
committed
start: adding support for email
1 parent e04cbde commit 71da2f8

File tree

3 files changed

+51
-29
lines changed

3 files changed

+51
-29
lines changed

tests/test_orchestrator.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ def test_generate_permutations():
2525

2626
assert len(perms) > 1
2727

28+
def test_generate_permutations_email():
29+
perms = orchestrator.generate_permutations("[email protected]", "abc", limit=None, is_email=True)
30+
assert "[email protected]" in perms
31+
assert all(
32+
33+
(p.startswith("john") and len(p) > len("[email protected]") and p.endswith("@email.com"))
34+
for p in perms
35+
)
36+
assert len(perms) > 1
37+
2838
def test_run_module_single_prints_json_and_csv(capsys):
2939
module = types.ModuleType("fake.testsite")
3040
module.__file__ = "<in-memory>/fake/testsite.py"

user_scanner/__main__.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import argparse
22
import time
33
import sys
4+
import re
45
from user_scanner.cli import printer
56
from user_scanner.core.orchestrator import generate_permutations, load_categories
67
from colorama import Fore, Style
@@ -28,9 +29,15 @@ def main():
2829
prog="user-scanner",
2930
description="Scan usernames across multiple platforms."
3031
)
31-
parser.add_argument(
32+
33+
group = parser.add_mutually_exclusive_group(required=True)
34+
35+
group.add_argument(
3236
"-u", "--username", help="Username to scan across platforms"
3337
)
38+
group.add_argument(
39+
"-e", "--email", help="Email to scan across platforms"
40+
)
3441
parser.add_argument(
3542
"-c", "--category", choices=load_categories().keys(),
3643
help="Scan all platforms in a category"
@@ -44,22 +51,18 @@ def main():
4451
parser.add_argument(
4552
"-v", "--verbose", action="store_true", help="Enable verbose output"
4653
)
47-
4854
parser.add_argument(
4955
"-p", "--permute",type=str,help="Generate username permutations using a string pattern (e.g -p 234)"
5056
)
5157
parser.add_argument(
5258
"-s", "--stop",type=int,default=MAX_PERMUTATIONS_LIMIT,help="Limit the number of username permutations generated"
5359
)
54-
5560
parser.add_argument(
5661
"-d", "--delay",type=float,default=0,help="Delay in seconds between requests (recommended: 1-2 seconds)"
5762
)
58-
5963
parser.add_argument(
6064
"-f", "--format", choices=["console", "csv", "json"], default="console", help="Specify the output format (default: console)"
6165
)
62-
6366
parser.add_argument(
6467
"-o", "--output", type=str, help="Specify the output file"
6568
)
@@ -82,26 +85,27 @@ def main():
8285

8386
check_for_updates()
8487

85-
if not args.username:
86-
parser.print_help()
87-
return
88-
89-
9088
if Printer.is_console:
9189
print_banner()
9290

91+
is_email = args.email is not None
92+
if is_email and not re.findall(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", args.email):
93+
print(R + "[✘] Error: Invalid email." + X)
94+
sys.exit(1)
95+
9396
if args.permute and args.delay == 0 and Printer.is_console:
9497
print(
9598
Y
9699
+ "[!] Warning: You're generating multiple usernames with NO delay between requests. "
97100
"This may trigger rate limits or IP bans. Use --delay 1 or higher. (Use only if the sites throw errors otherwise ignore)\n"
98101
+ Style.RESET_ALL)
99102

100-
usernames = [args.username] # Default single username list
103+
name = args.username or args.email #Username or email
104+
usernames = [name] # Default single username list
101105

102106
# Added permutation support , generate all possible permutation of given sequence.
103107
if args.permute:
104-
usernames = generate_permutations(args.username, args.permute , args.stop)
108+
usernames = generate_permutations(name, args.permute , args.stop, is_email)
105109
if Printer.is_console:
106110
print(
107111
C + f"[+] Generated {len(usernames)} username permutations" + Style.RESET_ALL)

user_scanner/core/orchestrator.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
from pathlib import Path
88
from user_scanner.cli.printer import Printer
99
from user_scanner.core.result import Result
10+
from types import ModuleType
1011
from typing import Callable, Dict, List
1112
from user_scanner.core.helpers import get_site_name, is_last_value
1213

1314

14-
def load_modules(category_path: Path):
15+
def load_modules(category_path: Path) -> List[ModuleType]:
1516
modules = []
1617
for file in category_path.glob("*.py"):
1718
if file.name == "__init__.py":
@@ -26,8 +27,9 @@ def load_modules(category_path: Path):
2627
return modules
2728

2829

29-
def load_categories() -> Dict[str, Path]:
30-
root = Path(__file__).resolve().parent.parent / "user_scan"
30+
def load_categories(is_email: bool = False) -> Dict[str, Path]:
31+
folder_name = "email_scan" if is_email else "user_scan"
32+
root = Path(__file__).resolve().parent.parent / folder_name
3133
categories = {}
3234

3335
for subfolder in root.iterdir():
@@ -39,33 +41,30 @@ def load_categories() -> Dict[str, Path]:
3941
return categories
4042

4143

42-
def find_module(name: str):
44+
def find_module(name: str, is_email: bool = False) -> List[ModuleType]:
4345
name = name.lower()
4446

45-
matches = [
47+
return [
4648
module
47-
for category_path in load_categories().values()
49+
for category_path in load_categories(is_email).values()
4850
for module in load_modules(category_path)
4951
if module.__name__.split(".")[-1].lower() == name
5052
]
5153

52-
return matches
5354

54-
def find_category(module) -> str | None:
55+
def find_category(module: ModuleType) -> str | None:
5556

5657
module_file = getattr(module, '__file__', None)
5758
if not module_file:
5859
return None
5960

6061
category = Path(module_file).parent.name.lower()
61-
categories = load_categories()
62-
if category in categories:
62+
if category in load_categories(False) or category in load_categories(True):
6363
return category.capitalize()
6464

6565
return None
6666

6767

68-
6968
def worker_single(module, username: str) -> Result:
7069
func = next((getattr(module, f) for f in dir(module)
7170
if f.startswith("validate_") and callable(getattr(module, f))), None)
@@ -99,7 +98,6 @@ def run_module_single(module, username: str, printer: Printer, last: bool = True
9998
return [result]
10099

101100

102-
103101
def run_checks_category(category_path: Path, username: str, printer: Printer, last: bool = True) -> List[Result]:
104102
modules = load_modules(category_path)
105103

@@ -196,20 +194,30 @@ def contains(a, b): return (isinstance(a, list) and b in a) or (a == b)
196194
return generic_validate(url, inner, **kwargs)
197195

198196

199-
def generate_permutations(username, pattern, limit=None):
197+
def generate_permutations(username: str, pattern: str, limit: int | None = None, is_email: bool = False) -> List[str]:
200198
"""
201199
Generate all order-based permutations of characters in `pattern`
202200
appended after `username`.
203201
"""
204-
permutations_set = {username}
205202

203+
if limit and limit <= 0:
204+
return []
205+
206+
permutations_set = {username}
206207
chars = list(pattern)
207208

209+
domain = ""
210+
if is_email:
211+
username, domain = username.strip().split("@")
212+
208213
# generate permutations of length 1 → len(chars)
209-
for r in range(1, len(chars) + 1):
214+
for r in range(len(chars)):
210215
for combo in permutations(chars, r):
211-
permutations_set.add(username + ''.join(combo))
216+
new = username + ''.join(combo)
217+
if is_email:
218+
new += "@" + domain
219+
permutations_set.add(new)
212220
if limit and len(permutations_set) >= limit:
213-
return list(permutations_set)[:limit]
221+
return sorted(permutations_set)
214222

215223
return sorted(permutations_set)

0 commit comments

Comments
 (0)