milenium 0.5.7__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. {milenium-0.5.7 → milenium-1.0.2}/PKG-INFO +10 -3
  2. milenium-1.0.2/milenium/__init__.py +53 -0
  3. milenium-1.0.2/milenium/cli.py +180 -0
  4. milenium-1.0.2/milenium/modules/__init__.py +9 -0
  5. milenium-1.0.2/milenium/modules/abuseipdb_search.py +30 -0
  6. milenium-1.0.2/milenium/modules/aggregator.py +43 -0
  7. milenium-1.0.2/milenium/modules/censys_search.py +30 -0
  8. milenium-1.0.2/milenium/modules/holehe_search.py +17 -0
  9. milenium-1.0.2/milenium/modules/image_search.py +24 -0
  10. milenium-1.0.2/milenium/modules/ipinfo_search.py +28 -0
  11. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/logger.py +1 -1
  12. milenium-1.0.2/milenium/modules/maigret_search.py +17 -0
  13. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/neon_db.py +40 -30
  14. milenium-1.0.2/milenium/modules/sherlock_search.py +16 -0
  15. milenium-1.0.2/milenium/modules/shodan_search.py +31 -0
  16. milenium-1.0.2/milenium/modules/telegram_analysis.py +36 -0
  17. milenium-1.0.2/milenium/modules/urlscan_search.py +26 -0
  18. milenium-1.0.2/milenium/modules/virustotal_search.py +50 -0
  19. {milenium-0.5.7 → milenium-1.0.2}/milenium/tui.py +155 -31
  20. {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/PKG-INFO +10 -3
  21. {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/SOURCES.txt +11 -0
  22. {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/requires.txt +6 -0
  23. {milenium-0.5.7 → milenium-1.0.2}/pyproject.toml +10 -3
  24. milenium-0.5.7/milenium/__init__.py +0 -23
  25. milenium-0.5.7/milenium/cli.py +0 -328
  26. milenium-0.5.7/milenium/modules/__init__.py +0 -0
  27. milenium-0.5.7/milenium/modules/aggregator.py +0 -41
  28. {milenium-0.5.7 → milenium-1.0.2}/README.md +0 -0
  29. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/breach.py +0 -0
  30. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/breach_local.py +0 -0
  31. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/dehashed.py +0 -0
  32. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/dns_history.py +0 -0
  33. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/domain.py +0 -0
  34. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/dorker.py +0 -0
  35. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/email.py +0 -0
  36. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/email_validator.py +0 -0
  37. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/exif_extractor.py +0 -0
  38. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/github_search.py +0 -0
  39. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/gravatar.py +0 -0
  40. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/hibp.py +0 -0
  41. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/instagram_osint.py +0 -0
  42. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/ip.py +0 -0
  43. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/ip_geo_advanced.py +0 -0
  44. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/ip_reputation.py +0 -0
  45. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/leakcheck.py +0 -0
  46. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/password_pattern.py +0 -0
  47. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/permutator.py +0 -0
  48. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/pgp_lookup.py +0 -0
  49. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/phone.py +0 -0
  50. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/pwned_passwords.py +0 -0
  51. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/report.py +0 -0
  52. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/reverse_whois.py +0 -0
  53. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/shodan_lite.py +0 -0
  54. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/social.py +0 -0
  55. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/telegram_osint.py +0 -0
  56. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/tg_parser.py +0 -0
  57. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/twitter_osint.py +0 -0
  58. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/username.py +0 -0
  59. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/vk_osint.py +0 -0
  60. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/wayback.py +0 -0
  61. {milenium-0.5.7 → milenium-1.0.2}/milenium/modules/wifi_geolocation.py +0 -0
  62. {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/dependency_links.txt +0 -0
  63. {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/entry_points.txt +0 -0
  64. {milenium-0.5.7 → milenium-1.0.2}/milenium.egg-info/top_level.txt +0 -0
  65. {milenium-0.5.7 → milenium-1.0.2}/setup.cfg +0 -0
@@ -1,10 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: milenium
3
- Version: 0.5.7
4
- Summary: OSINT toolkit
5
- Requires-Python: >=3.9
3
+ Version: 1.0.2
4
+ Summary: The most powerful OSINT aggregator
5
+ Author: VexxDev200
6
+ Requires-Python: >=3.10
6
7
  Description-Content-Type: text/markdown
7
8
  Requires-Dist: requests
9
+ Requires-Dist: aiohttp
8
10
  Requires-Dist: dnspython
9
11
  Requires-Dist: phonenumbers
10
12
  Requires-Dist: beautifulsoup4
@@ -12,6 +14,11 @@ Requires-Dist: rich
12
14
  Requires-Dist: click
13
15
  Requires-Dist: python-whois
14
16
  Requires-Dist: psycopg2-binary
17
+ Requires-Dist: maigret
18
+ Requires-Dist: sherlock-project
19
+ Requires-Dist: holehe
20
+ Requires-Dist: telethon
21
+ Requires-Dist: PicImageSearch
15
22
 
16
23
  # milenium
17
24
  # exiftool
@@ -0,0 +1,53 @@
1
+ import subprocess
2
+ import sys
3
+ import importlib
4
+
5
+
6
+ def _ensure_deps():
7
+ """Автоустановка зависимостей при первом импорте."""
8
+ deps = {
9
+ "requests": "requests",
10
+ "aiohttp": "aiohttp",
11
+ "dns": "dnspython",
12
+ "phonenumbers": "phonenumbers",
13
+ "bs4": "beautifulsoup4",
14
+ "rich": "rich",
15
+ "click": "click",
16
+ "whois": "python-whois",
17
+ "psycopg2": "psycopg2-binary",
18
+ "telethon": "telethon",
19
+ "PicImageSearch": "PicImageSearch",
20
+ }
21
+ def _ensure_deps():
22
+ deps = {
23
+ "requests": "requests",
24
+ "aiohttp": "aiohttp",
25
+ "dns": "dnspython",
26
+ "phonenumbers": "phonenumbers",
27
+ "bs4": "beautifulsoup4",
28
+ "rich": "rich",
29
+ "click": "click",
30
+ "whois": "python-whois",
31
+ "psycopg2": "psycopg2-binary",
32
+ "telethon": "telethon",
33
+ "PicImageSearch": "PicImageSearch",
34
+ }
35
+ for mod, pkg in deps.items():
36
+ try:
37
+ importlib.import_module(mod)
38
+ except ImportError:
39
+ try:
40
+ subprocess.check_call([sys.executable, "-m", "pip", "install", pkg, "--quiet"])
41
+ except Exception:
42
+ pass
43
+ # Прокидываем конфиг в окружение
44
+ try:
45
+ from milenium.modules import config
46
+ config.apply_to_env()
47
+ except Exception:
48
+ pass
49
+
50
+
51
+ _ensure_deps()
52
+
53
+ __version__ = "1.0.0"
@@ -0,0 +1,180 @@
1
+ import sys
2
+ import json
3
+ import time
4
+ import asyncio
5
+ import click
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+ from milenium.modules.logger import TeeLogger, progress
9
+ from milenium.modules import (
10
+ neon_db, aggregator,
11
+ shodan_search, censys_search, virustotal_search,
12
+ abuseipdb_search, ipinfo_search, urlscan_search,
13
+ maigret_search, sherlock_search, holehe_search,
14
+ telegram_analysis, image_search,
15
+ )
16
+
17
+ console = Console()
18
+
19
+
20
+ def _init_logger():
21
+ logger = TeeLogger("milenium.log", mode="a")
22
+ sys.stdout = logger
23
+ return logger
24
+
25
+
26
+ def _save(command, target, module, data, target_type=None):
27
+ try:
28
+ neon_db.init()
29
+ neon_db.save(command, target, module, data, target_type=target_type)
30
+ progress("NeonDB: записано")
31
+ except Exception as e:
32
+ progress(f"NeonDB error: {e}")
33
+
34
+
35
+ @click.group()
36
+ def main():
37
+ """milenium — самый мощный OSINT агрегатор"""
38
+ pass
39
+
40
+
41
+ @main.command()
42
+ @click.argument("username")
43
+ def user(username):
44
+ """Maigret + Sherlock + Holehe"""
45
+ logger = _init_logger()
46
+ t0 = time.time()
47
+ progress(f"старт: user {username}")
48
+ res = aggregator.run(username=username)
49
+ _save("user", username, "username", res, "username")
50
+ console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
51
+ progress(f"готово за {time.time() - t0:.1f}s")
52
+ logger.close()
53
+
54
+
55
+ @main.command()
56
+ @click.argument("email")
57
+ def email_cmd(email):
58
+ """Holehe + VirusTotal domain"""
59
+ logger = _init_logger()
60
+ t0 = time.time()
61
+ progress(f"старт: email {email}")
62
+ res = aggregator.run(email=email)
63
+ _save("email", email, "email", res, "email")
64
+ console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
65
+ progress(f"готово за {time.time() - t0:.1f}s")
66
+ logger.close()
67
+
68
+
69
+ @main.command()
70
+ @click.argument("ip")
71
+ def ip_cmd(ip):
72
+ """Shodan + Censys + VirusTotal + AbuseIPDB + ipinfo"""
73
+ logger = _init_logger()
74
+ t0 = time.time()
75
+ progress(f"старт: ip {ip}")
76
+ res = aggregator.run(ip=ip)
77
+ _save("ip", ip, "ip", res, "ip")
78
+ console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
79
+ progress(f"готово за {time.time() - t0:.1f}s")
80
+ logger.close()
81
+
82
+
83
+ @main.command()
84
+ @click.argument("url")
85
+ def url_cmd(url):
86
+ """urlscan.io анализ"""
87
+ logger = _init_logger()
88
+ t0 = time.time()
89
+ progress(f"старт: url {url}")
90
+ res = aggregator.run(url=url)
91
+ _save("url", url, "url", res, "url")
92
+ console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
93
+ progress(f"готово за {time.time() - t0:.1f}s")
94
+ logger.close()
95
+
96
+
97
+ @main.command()
98
+ @click.argument("username")
99
+ def tg(username):
100
+ """Telethon: инфа по TG-аккаунту"""
101
+ logger = _init_logger()
102
+ t0 = time.time()
103
+ progress(f"старт: telegram {username}")
104
+ res = asyncio.run(telegram_analysis.check_user(username))
105
+ _save("telegram", username, "telegram", res, "username")
106
+ for k, v in res.items():
107
+ console.print(f"[bold]{k}:[/bold] {v}")
108
+ progress(f"готово за {time.time() - t0:.1f}s")
109
+ logger.close()
110
+
111
+
112
+ @main.command()
113
+ @click.argument("image_path")
114
+ @click.option("--engine", default="yandex")
115
+ def img(image_path, engine):
116
+ """PicImageSearch: обратный поиск по фото"""
117
+ logger = _init_logger()
118
+ t0 = time.time()
119
+ progress(f"старт: image search {engine}")
120
+ res = asyncio.run(image_search.search(image_path, engine))
121
+ _save("image", image_path, "image", res, "file")
122
+ console.print(json.dumps(res, indent=2, ensure_ascii=False, default=str))
123
+ progress(f"готово за {time.time() - t0:.1f}s")
124
+ logger.close()
125
+
126
+
127
+ @main.command()
128
+ @click.option("--key", default=None, help="Какой ключ задать")
129
+ @click.option("--value", default=None, help="Значение")
130
+ def config_cmd(key, value):
131
+ """Просмотр и настройка конфига"""
132
+ from milenium.modules import config
133
+ if key and value:
134
+ config.set_key(key, value)
135
+ console.print(f"[green]Сохранено:[/green] {key}")
136
+ return
137
+ cfg = config.all_keys()
138
+ console.print(f"[bold]Конфиг:[/bold] {config.config_path()}")
139
+ for k, v in cfg.items():
140
+ shown = v if not v else (v[:8] + "..." if len(v) > 12 else v)
141
+ console.print(f" [red]{k}[/red] = {shown or '[empty]'}")
142
+
143
+
144
+ @main.command()
145
+ @click.option("--target", default=None)
146
+ @click.option("--stats", is_flag=True)
147
+ def db_neon(target, stats):
148
+ """NeonDB статистика и поиск"""
149
+ logger = _init_logger()
150
+ try:
151
+ neon_db.init()
152
+ if stats:
153
+ s = neon_db.stats()
154
+ console.print(f"[bold]Всего:[/bold] {s['total']}")
155
+ console.print(f"[bold]Findings:[/bold] {s['findings']}")
156
+ console.print(f"[bold]Leaks:[/bold] {s['leaks']}")
157
+ else:
158
+ rows = neon_db.query(target=target, limit=50)
159
+ table = Table(title="NeonDB")
160
+ table.add_column("Время")
161
+ table.add_column("Команда")
162
+ table.add_column("Цель")
163
+ table.add_column("Модуль")
164
+ for r in rows:
165
+ table.add_row(str(r["ts"])[:19], r["command"], r["target"], r["module"])
166
+ console.print(table)
167
+ except Exception as e:
168
+ console.print(f"[bold red]Ошибка Neon:[/bold red] {e}")
169
+ logger.close()
170
+
171
+
172
+ @main.command()
173
+ def tui():
174
+ """TUI интерфейс"""
175
+ from milenium.tui import interactive
176
+ interactive()
177
+
178
+
179
+ if __name__ == "__main__":
180
+ main()
@@ -0,0 +1,9 @@
1
+ __all__ = [
2
+ "logger", "neon_db", "aggregator", "dorker",
3
+ "username", "social", "email", "phone", "ip", "domain", "breach", "report",
4
+ "pwned_passwords", "breach_local", "hibp", "dehashed", "leakcheck",
5
+ "shodan_lite", "shodan_search", "censys_search", "virustotal_search",
6
+ "abuseipdb_search", "ipinfo_search", "urlscan_search",
7
+ "telegram_osint", "telegram_analysis", "image_search",
8
+ "maigret_search", "sherlock_search", "holehe_search",
9
+ ]
@@ -0,0 +1,30 @@
1
+ import os
2
+ import aiohttp
3
+
4
+ from milenium.modules import config
5
+ ABUSE_KEY = config.get("ABUSEIPDB_KEY", "")
6
+
7
+
8
+ async def check(ip: str) -> dict:
9
+ """AbuseIPDB: жалобы на IP."""
10
+ if not ABUSE_KEY:
11
+ return {"error": "ABUSEIPDB_KEY not set"}
12
+ url = "https://api.abuseipdb.com/api/v2/check"
13
+ params = {"ipAddress": ip, "maxAgeInDays": 90}
14
+ headers = {"Key": ABUSE_KEY, "Accept": "application/json"}
15
+ try:
16
+ async with aiohttp.ClientSession() as session:
17
+ async with session.get(url, params=params, headers=headers, timeout=15) as resp:
18
+ if resp.status != 200:
19
+ return {"error": f"HTTP {resp.status}"}
20
+ data = await resp.json()
21
+ d = data.get("data", {})
22
+ return {
23
+ "abuse_score": d.get("abuseConfidenceScore"),
24
+ "total_reports": d.get("totalReports"),
25
+ "country": d.get("countryCode"),
26
+ "isp": d.get("isp"),
27
+ "domain": d.get("domain"),
28
+ }
29
+ except Exception as e:
30
+ return {"error": str(e)}
@@ -0,0 +1,43 @@
1
+ import asyncio
2
+ from milenium.modules import (
3
+ shodan_search, censys_search, virustotal_search,
4
+ abuseipdb_search, ipinfo_search, urlscan_search,
5
+ maigret_search, sherlock_search, holehe_search,
6
+ )
7
+
8
+
9
+ async def run_async(email=None, username=None, phone=None, ip=None, url=None):
10
+ tasks = {}
11
+
12
+ if ip:
13
+ tasks["shodan"] = shodan_search.check_ip(ip)
14
+ tasks["censys"] = censys_search.check_host(ip)
15
+ tasks["virustotal"] = virustotal_search.check_ip(ip)
16
+ tasks["abuseipdb"] = abuseipdb_search.check(ip)
17
+ tasks["ipinfo"] = ipinfo_search.check(ip)
18
+
19
+ if username:
20
+ tasks["maigret"] = maigret_search.check(username)
21
+ tasks["sherlock"] = sherlock_search.check(username)
22
+
23
+ if email:
24
+ tasks["holehe"] = holehe_search.check(email)
25
+ tasks["virustotal_domain"] = virustotal_search.check_domain(email.split("@")[-1])
26
+
27
+ if url:
28
+ tasks["urlscan"] = urlscan_search.check(url)
29
+
30
+ results = {}
31
+ if tasks:
32
+ keys = list(tasks.keys())
33
+ values = await asyncio.gather(*tasks.values(), return_exceptions=True)
34
+ for k, v in zip(keys, values):
35
+ if isinstance(v, Exception):
36
+ results[k] = {"error": str(v)}
37
+ else:
38
+ results[k] = v
39
+ return results
40
+
41
+
42
+ def run(**kwargs):
43
+ return asyncio.run(run_async(**kwargs))
@@ -0,0 +1,30 @@
1
+ import os
2
+ import aiohttp
3
+
4
+ CENSYS_TOKEN = os.environ.get("CENSYS_TOKEN", "censys_UJptWhGQ_L57vdqJiF1FQEd1VtTVArqsX")
5
+
6
+
7
+ async def check_host(ip: str) -> dict:
8
+ """Censys API: хосты, сервисы, TLS."""
9
+ if not CENSYS_TOKEN:
10
+ return {"error": "CENSYS_TOKEN not set"}
11
+ url = f"https://search.censys.io/api/v2/hosts/{ip}"
12
+ headers = {"Authorization": f"Bearer {CENSYS_TOKEN}"}
13
+ try:
14
+ async with aiohttp.ClientSession() as session:
15
+ async with session.get(url, headers=headers, timeout=15) as resp:
16
+ if resp.status == 404:
17
+ return {"found": False}
18
+ if resp.status != 200:
19
+ return {"error": f"HTTP {resp.status}"}
20
+ data = await resp.json()
21
+ result = data.get("result", {})
22
+ services = result.get("services", [])
23
+ return {
24
+ "services": [s.get("service_name") for s in services],
25
+ "ports": [s.get("port") for s in services],
26
+ "os": result.get("operating_system", {}).get("product"),
27
+ "country": result.get("location", {}).get("country"),
28
+ }
29
+ except Exception as e:
30
+ return {"error": str(e)}
@@ -0,0 +1,17 @@
1
+ import asyncio
2
+ import json
3
+
4
+
5
+ async def check(email: str) -> dict:
6
+ """Holehe: проверка email по 120+ платформам."""
7
+ try:
8
+ proc = await asyncio.create_subprocess_exec(
9
+ "holehe", email, "--only-used", "--no-color",
10
+ stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
11
+ )
12
+ stdout, _ = await proc.communicate()
13
+ lines = stdout.decode("utf-8", errors="ignore").splitlines()
14
+ found = [l for l in lines if "[+]" in l]
15
+ return {"found": len(found), "sites": found[:100]}
16
+ except Exception as e:
17
+ return {"error": str(e)}
@@ -0,0 +1,24 @@
1
+ import asyncio
2
+ from PicImageSearch import Network, Yandex, Google, Bing, Tineye
3
+
4
+
5
+ async def search(image_path: str, engine: str = "yandex") -> dict:
6
+ """PicImageSearch: обратный поиск по фото."""
7
+ engines = {"yandex": Yandex, "google": Google, "bing": Bing, "tineye": Tineye}
8
+ cls = engines.get(engine.lower())
9
+ if not cls:
10
+ return {"error": f"unknown engine: {engine}"}
11
+ try:
12
+ async with Network() as client:
13
+ searcher = cls(client=client)
14
+ resp = await searcher.search(file=image_path)
15
+ results = []
16
+ for item in resp.raw[:10]:
17
+ results.append({
18
+ "title": getattr(item, "title", None),
19
+ "url": getattr(item, "url", None),
20
+ "thumbnail": getattr(item, "thumbnail", None),
21
+ })
22
+ return {"engine": engine, "results": results}
23
+ except Exception as e:
24
+ return {"error": str(e)}
@@ -0,0 +1,28 @@
1
+ import os
2
+ import aiohttp
3
+
4
+ from milenium.modules import config
5
+ IPINFO_TOKEN = config.get("IPINFO_TOKEN", "")
6
+
7
+
8
+ async def check(ip: str) -> dict:
9
+ """ipinfo.io: гео, ASN, компания."""
10
+ if not IPINFO_TOKEN:
11
+ return {"error": "IPINFO_TOKEN not set"}
12
+ url = f"https://ipinfo.io/{ip}/json?token={IPINFO_TOKEN}"
13
+ try:
14
+ async with aiohttp.ClientSession() as session:
15
+ async with session.get(url, timeout=15) as resp:
16
+ if resp.status != 200:
17
+ return {"error": f"HTTP {resp.status}"}
18
+ data = await resp.json()
19
+ return {
20
+ "city": data.get("city"),
21
+ "region": data.get("region"),
22
+ "country": data.get("country"),
23
+ "org": data.get("org"),
24
+ "timezone": data.get("timezone"),
25
+ "loc": data.get("loc"),
26
+ }
27
+ except Exception as e:
28
+ return {"error": str(e)}
@@ -32,7 +32,7 @@ class TeeLogger:
32
32
 
33
33
  def _write_header(self):
34
34
  ts = datetime.now().isoformat()
35
- self.file.write(f"\n{'='*60}\n[SESSION START] {ts}\n{'='*60}\n")
35
+ self.file.write(f"\n{'=' * 60}\n[SESSION START] {ts}\n{'=' * 60}\n")
36
36
  self.file.flush()
37
37
 
38
38
  def write(self, message):
@@ -0,0 +1,17 @@
1
+ import asyncio
2
+ import json
3
+
4
+
5
+ async def check(username: str) -> dict:
6
+ """Maigret: поиск по 3000+ сайтам."""
7
+ try:
8
+ proc = await asyncio.create_subprocess_exec(
9
+ "maigret", username, "--json", "simple", "--no-progressbar",
10
+ stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
11
+ )
12
+ stdout, _ = await proc.communicate()
13
+ data = json.loads(stdout.decode("utf-8", errors="ignore"))
14
+ found = [k for k, v in data.items() if v.get("status") == "claimed"]
15
+ return {"found": len(found), "sites": found[:100]}
16
+ except Exception as e:
17
+ return {"error": str(e)}
@@ -2,21 +2,28 @@ import os
2
2
  import json
3
3
  import psycopg2
4
4
  import psycopg2.extras
5
+ from milenium.modules import config
5
6
 
6
- DATABASE_URL = os.environ.get("DATABASE_URL", "postgresql://neondb_owner:npg_JNAjnTaz0UK5@ep-twilight-bar-b1ofdyot-pooler.c-5.eu-central-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require")
7
+ DATABASE_URL = ""
7
8
 
8
9
 
9
- def _conn():
10
+ def _get_url():
11
+ global DATABASE_URL
10
12
  if not DATABASE_URL:
13
+ DATABASE_URL = config.get("DATABASE_URL", "")
14
+ return DATABASE_URL
15
+
16
+
17
+ def _conn():
18
+ url = _get_url()
19
+ if not url:
11
20
  raise RuntimeError("DATABASE_URL not set")
12
- return psycopg2.connect(DATABASE_URL, sslmode="require")
21
+ return psycopg2.connect(url, sslmode="require")
13
22
 
14
23
 
15
24
  def init():
16
- """Создаёт расширенные таблицы."""
17
25
  with _conn() as conn:
18
26
  with conn.cursor() as cur:
19
- # Основная таблица результатов
20
27
  cur.execute("""
21
28
  CREATE TABLE IF NOT EXISTS results (
22
29
  id SERIAL PRIMARY KEY,
@@ -29,7 +36,6 @@ def init():
29
36
  data JSONB
30
37
  )
31
38
  """)
32
- # Отдельная таблица для найденных URL/ссылок
33
39
  cur.execute("""
34
40
  CREATE TABLE IF NOT EXISTS findings (
35
41
  id SERIAL PRIMARY KEY,
@@ -41,7 +47,6 @@ def init():
41
47
  meta JSONB
42
48
  )
43
49
  """)
44
- # Таблица для утечек
45
50
  cur.execute("""
46
51
  CREATE TABLE IF NOT EXISTS leaks (
47
52
  id SERIAL PRIMARY KEY,
@@ -53,7 +58,6 @@ def init():
53
58
  breach TEXT
54
59
  )
55
60
  """)
56
- # Таблица сессий
57
61
  cur.execute("""
58
62
  CREATE TABLE IF NOT EXISTS sessions (
59
63
  id SERIAL PRIMARY KEY,
@@ -66,7 +70,6 @@ def init():
66
70
 
67
71
 
68
72
  def save(command, target, module, data, target_type=None):
69
- """Сохраняет результат + разбирает его на findings/leaks."""
70
73
  found_count = 0
71
74
  if isinstance(data, dict):
72
75
  for k, v in data.items():
@@ -79,10 +82,9 @@ def save(command, target, module, data, target_type=None):
79
82
  """INSERT INTO results (command, target, target_type, module, found_count, data)
80
83
  VALUES (%s, %s, %s, %s, %s, %s)""",
81
84
  (command, target, target_type, module, found_count,
82
- json.dumps(data, ensure_ascii=False))
85
+ json.dumps(data, ensure_ascii=False, default=str))
83
86
  )
84
87
 
85
- # Разбираем findings
86
88
  if isinstance(data, dict):
87
89
  for key, val in data.items():
88
90
  if isinstance(val, dict) and "url" in val:
@@ -90,7 +92,7 @@ def save(command, target, module, data, target_type=None):
90
92
  """INSERT INTO findings (target, source, url, found, meta)
91
93
  VALUES (%s, %s, %s, %s, %s)""",
92
94
  (target, key, val.get("url"), val.get("found", False),
93
- json.dumps(val, ensure_ascii=False))
95
+ json.dumps(val, ensure_ascii=False, default=str))
94
96
  )
95
97
  elif isinstance(val, list):
96
98
  for item in val:
@@ -99,10 +101,15 @@ def save(command, target, module, data, target_type=None):
99
101
  """INSERT INTO findings (target, source, url, found, meta)
100
102
  VALUES (%s, %s, %s, %s, %s)""",
101
103
  (target, key, item.get("url"), True,
102
- json.dumps(item, ensure_ascii=False))
104
+ json.dumps(item, ensure_ascii=False, default=str))
105
+ )
106
+ elif isinstance(item, str) and item.startswith("http"):
107
+ cur.execute(
108
+ """INSERT INTO findings (target, source, url, found, meta)
109
+ VALUES (%s, %s, %s, %s, %s)""",
110
+ (target, key, item, True, json.dumps({"url": item}))
103
111
  )
104
112
 
105
- # Разбираем утечки
106
113
  if isinstance(data, dict) and "breaches" in data:
107
114
  for b in data.get("breaches", []):
108
115
  cur.execute(
@@ -163,19 +170,22 @@ def leaks(target=None, limit=100):
163
170
 
164
171
 
165
172
  def stats():
166
- with _conn() as conn:
167
- with conn.cursor() as cur:
168
- cur.execute("SELECT COUNT(*) FROM results")
169
- total = cur.fetchone()[0]
170
- cur.execute("SELECT COUNT(*) FROM findings WHERE found = TRUE")
171
- findings_count = cur.fetchone()[0]
172
- cur.execute("SELECT COUNT(*) FROM leaks")
173
- leaks_count = cur.fetchone()[0]
174
- cur.execute("SELECT module, COUNT(*) FROM results GROUP BY module")
175
- by_module = dict(cur.fetchall())
176
- return {
177
- "total": total,
178
- "findings": findings_count,
179
- "leaks": leaks_count,
180
- "by_module": by_module,
181
- }
173
+ try:
174
+ with _conn() as conn:
175
+ with conn.cursor() as cur:
176
+ cur.execute("SELECT COUNT(*) FROM results")
177
+ total = cur.fetchone()[0]
178
+ cur.execute("SELECT COUNT(*) FROM findings WHERE found = TRUE")
179
+ findings_count = cur.fetchone()[0]
180
+ cur.execute("SELECT COUNT(*) FROM leaks")
181
+ leaks_count = cur.fetchone()[0]
182
+ cur.execute("SELECT module, COUNT(*) FROM results GROUP BY module")
183
+ by_module = dict(cur.fetchall())
184
+ return {
185
+ "total": total,
186
+ "findings": findings_count,
187
+ "leaks": leaks_count,
188
+ "by_module": by_module,
189
+ }
190
+ except Exception as e:
191
+ return {"total": 0, "findings": 0, "leaks": 0, "by_module": {}, "error": str(e)}
@@ -0,0 +1,16 @@
1
+ import asyncio
2
+ import json
3
+
4
+
5
+ async def check(username: str) -> dict:
6
+ """Sherlock: поиск по 3000+ сетям."""
7
+ try:
8
+ proc = await asyncio.create_subprocess_exec(
9
+ "sherlock", username, "--json",
10
+ stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
11
+ )
12
+ stdout, _ = await proc.communicate()
13
+ data = json.loads(stdout.decode("utf-8", errors="ignore"))
14
+ return {"found": len(data), "sites": list(data.keys())[:100]}
15
+ except Exception as e:
16
+ return {"error": str(e)}
@@ -0,0 +1,31 @@
1
+ import os
2
+ import aiohttp
3
+
4
+ from milenium.modules import config
5
+ SHODAN_KEY = config.get("SHODAN_API_KEY", "")
6
+
7
+
8
+ async def check_ip(ip: str) -> dict:
9
+ """Shodan API: порты, баннеры, CVE, гео."""
10
+ if not SHODAN_KEY:
11
+ return {"error": "SHODAN_API_KEY not set"}
12
+ url = f"https://api.shodan.io/shodan/host/{ip}?key={SHODAN_KEY}&minify=true"
13
+ try:
14
+ async with aiohttp.ClientSession() as session:
15
+ async with session.get(url, timeout=15) as resp:
16
+ if resp.status == 404:
17
+ return {"found": False}
18
+ if resp.status != 200:
19
+ return {"error": f"HTTP {resp.status}"}
20
+ data = await resp.json()
21
+ return {
22
+ "org": data.get("org"),
23
+ "os": data.get("os"),
24
+ "ports": data.get("ports", []),
25
+ "hostnames": data.get("hostnames", []),
26
+ "vulns": list(data.get("vulns", {}).keys()),
27
+ "country": data.get("country_name"),
28
+ "city": data.get("city"),
29
+ }
30
+ except Exception as e:
31
+ return {"error": str(e)}