atdork 1.3.3__py3-none-any.whl

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.
@@ -0,0 +1,288 @@
1
+ Metadata-Version: 2.4
2
+ Name: atdork
3
+ Version: 1.3.3
4
+ Summary: Professional dorking ddgs metasearch OSINT tool
5
+ Author: alzzmarket
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/amnottdevv/atdork
8
+ Project-URL: Bug Reports, https://github.com/amnottdevv/atdork/issues
9
+ Project-URL: Source Code, https://github.com/amnottdevv/atdork
10
+ Keywords: osint,dork,pentest,security,duckduckgo,metasearch
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Information Technology
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Security
19
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: ddgs>=7.0
24
+ Requires-Dist: rich>=13.0
25
+ Requires-Dist: pyfiglet>=0.8
26
+ Requires-Dist: pyyaml>=6.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8.0; extra == "dev"
29
+ Requires-Dist: flake8>=7.0; extra == "dev"
30
+ Requires-Dist: bandit>=1.7; extra == "dev"
31
+ Requires-Dist: safety>=3.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+
35
+ # Atdork
36
+
37
+ ![Version](https://img.shields.io/badge/version-1.3-blue.svg)
38
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
39
+ ![License](https://img.shields.io/badge/license-MIT-green)
40
+ ![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey)
41
+ [![CI](https://github.com/amnottdevv/atdork/actions/workflows/ci.yml/badge.svg)](https://github.com/amnottdevv/atdork/actions/workflows/ci.yml)
42
+ [![Tests](https://img.shields.io/badge/tests-114%20passed-brightgreen)](https://github.com/amnottdevv/atdork/actions)
43
+ ![GitHub Clones](https://img.shields.io/badge/clones-985%2F14d-blue)
44
+ ![GitHub Visitors](https://img.shields.io/badge/visitors-519%2F14d-brightgreen)
45
+
46
+ A lightweight, ethical DuckDuckGo-based OSINT tool for running advanced search queries (dorks) from the command line.
47
+ Atdork helps security researchers, penetration testers, and OSINT analysts quickly discover publicly available information across multiple search engines.
48
+
49
+ **v1.3 introduces built‑in resilience, adaptive rate limiting, SQLite storage, and comprehensive logging — making it production‑ready.**
50
+
51
+ ---
52
+
53
+ ## Features
54
+
55
+ - **Interactive & CLI modes** – use an interactive prompt or pass arguments directly.
56
+ - **Multi‑engine support** – choose backend search engines (Google, Bing, DuckDuckGo, Startpage, Yandex, etc.).
57
+ - **Batch processing** – run dozens of dorks from a text file or inline string, now with **multi‑threaded execution** for speed.
58
+ - **Resilience engine** (`--resilient`) – circuit breaker, automatic backend fallback, and intelligent retry handling.
59
+ - **Adaptive rate limiter** (`--adaptive-delay`) – dynamic per‑backend delay that responds to rate‑limits and recovers automatically.
60
+ - **Output validation** – built‑in filters to **remove spam, invalid URLs, and low‑quality results**; optional strict mode.
61
+ - **Vulnerability filter** (`--filter-vuln`) – identify results matching platform‑specific signatures (e.g. WordPress, Joomla).
62
+ - **Proxy rotation** – load proxies from a file, comma‑separated list, or Tor fallback (now with `user:pass@host:port` support).
63
+ - **Strict proxy mode** – prevent leaking your real IP if all proxies fail.
64
+ - **Intelligent proxy manager** – validates format, auto‑removes dead proxies, tracks statistics.
65
+ - **SQLite database** – persistent storage of all queries and results with resume, history, deduplication, and export.
66
+ - **Rotating file logs** – timestamped, module‑aware log files with automatic rotation.
67
+ - **User‑Agent rotation** – built‑in pool of modern User‑Agent strings, automatically rotated.
68
+ - **Flexible output** – save results as TXT, JSON, or CSV; store batch results per query or in a single file.
69
+ - **YAML configuration** – store your favourite settings in `atdork.yaml` for reproducibility.
70
+ - **CI/CD pipeline** – automated tests, linting, and security scanning on every commit.
71
+
72
+ ---
73
+
74
+ ## Installation
75
+
76
+ 1. **Clone the repository**
77
+ ```bash
78
+ git clone https://github.com/amnottdevv/atdork.git
79
+ cd atdork
80
+ ```
81
+
82
+ 2. **Install dependencies**
83
+ ```bash
84
+ pip install -r requirements.txt
85
+ ```
86
+
87
+ Requirements:
88
+ - `duckduckgo-search>=7.0`
89
+ - `rich>=13.0`
90
+ - `pyfiglet>=0.8`
91
+ - `pyyaml>=6.0`
92
+
93
+ 3. **(Optional) Tor**
94
+ Install Tor if you plan to use the `--tor` flag. Atdork will automatically connect to `127.0.0.1:9050`.
95
+
96
+ ---
97
+
98
+ ## Quick Start
99
+
100
+ ### Interactive mode (guided prompts)
101
+ ```bash
102
+ python main.py --interactive
103
+ ```
104
+
105
+ ### Command‑line mode
106
+ ```bash
107
+ python main.py -q "site:gov filetype:pdf" -r 10
108
+ ```
109
+
110
+ ### Batch from file (with resilience and rate limiter)
111
+ ```bash
112
+ python main.py --batch-file dorks.txt --resilient --adaptive-delay -r 20 --format json -o results.json
113
+ ```
114
+
115
+ ### Proxy with strict mode (now with authentication)
116
+ ```bash
117
+ python main.py -q "admin login" --proxy "http://user:pass@proxy:8080" --strict
118
+ ```
119
+
120
+ ### Filter vulnerable WordPress results
121
+ ```bash
122
+ python main.py -q "inurl:wp-content" -r 30 --filter-vuln wordpress
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Command-Line Arguments
128
+
129
+ | Argument | Description | Default |
130
+ |----------|-------------|---------|
131
+ | `--interactive` | Launch interactive mode | (off) |
132
+ | `-q`, `--query` | Search query / dork | |
133
+ | `-r`, `--max-results` | Maximum number of results (1‑100) | 20 |
134
+ | `--region` | Search region (e.g. `us-en`, `uk-en`) | `us-en` |
135
+ | `--safesearch` | `on`, `moderate`, `off` | `moderate` |
136
+ | `--timelimit` | `d` (day), `w` (week), `m` (month), `y` (year) | (none) |
137
+ | `--backend` | Backend engine(s) – comma‑separated | `auto` |
138
+ | `--user-agent` | Custom User‑Agent (auto‑rotate if empty) | (auto) |
139
+ | `--timeout` | Request timeout (seconds) | 10 |
140
+ | `--retries` | Number of retry attempts on failure | 2 |
141
+ | `--delay` | Delay between requests (seconds) | 0 |
142
+ | `--proxy` | One or more proxy URLs (comma‑separated) | |
143
+ | `--proxy-file` | File containing proxy URLs | |
144
+ | `--tor` | Use Tor SOCKS5 proxy | |
145
+ | `--strict` | Fail instead of falling back to direct connection | `False` |
146
+ | `--proxy-cooldown` | Cooldown after a proxy failure (seconds) | 60 |
147
+ | `--max-failures` | Remove proxy after N consecutive failures | 3 |
148
+ | `--resilient` | Enable resilience mode (circuit breaker + fallback) | `False` |
149
+ | `--adaptive-delay` | Enable adaptive rate limiting per backend | `False` |
150
+ | `--concurrency` | Number of parallel threads for batch | 1 |
151
+ | `--max-fallback-failures` | Consecutive failures before fallback to sequential | 3 |
152
+ | `--batch-file` | File with one query per line | |
153
+ | `--batch-separator` | Separator for inline multiple queries | `;` |
154
+ | `-o`, `--output` | Save results to file | |
155
+ | `--output-dir` | Save each query result as a separate file | |
156
+ | `--format` | Output format: `txt`, `json`, `csv` | `txt` |
157
+ | `--no-snippet` | Hide snippet text in terminal | |
158
+ | `--no-validate` | Disable spam/invalid result filtering | |
159
+ | `--strict-filter` | Stricter filter (require non‑empty snippet) | |
160
+ | `--filter-vuln` | Filter results by vulnerability platform (e.g. `wordpress`) | |
161
+ | `--db-path` | SQLite database path | `atdork.db` |
162
+ | `--resume` | Resume pending queries from the database | |
163
+ | `--history` | Show search history | |
164
+ | `--no-dedup` | Disable global URL deduplication | |
165
+ | `--export-db` | Export database to JSON/CSV | |
166
+ | `--log-file` | Log file path | `atdork.log` |
167
+ | `--no-fallback-backends` | Disable backend fallback | |
168
+ | `--no-verify` | Disable SSL verification (not recommended) | |
169
+ | `--debug` | Enable debug logging | |
170
+ | `--version` | Show version and exit | |
171
+
172
+ **Available backends:** `auto`, `bing`, `brave`, `duckduckgo`, `google`, `grokipedia`, `mojeek`, `startpage`, `yandex`, `yahoo`, `wikipedia`.
173
+
174
+ ---
175
+
176
+ ## Examples
177
+
178
+ ### 1. Basic OSINT search with validation
179
+ ```bash
180
+ python main.py -q "intitle:index.of mp3" -r 30 --backend google --safesearch off
181
+ ```
182
+
183
+ ### 2. High‑anonymity scan with Tor and strict proxy rules
184
+ ```bash
185
+ python main.py -q "confidential filetype:xlsx" --tor --strict --delay 2 -r 50 -o secret.json
186
+ ```
187
+
188
+ ### 3. Batch processing with resilience and rate limiter
189
+ ```bash
190
+ python main.py --batch-file pentest_dorks.txt --concurrency 5 --resilient --adaptive-delay --proxy-file proxies.txt --output-dir results --format csv
191
+ ```
192
+
193
+ ### 4. Resume an interrupted batch
194
+ ```bash
195
+ python main.py --resume
196
+ ```
197
+
198
+ ### 5. Export all stored results
199
+ ```bash
200
+ python main.py --export-db all_results.json
201
+ ```
202
+
203
+ ### 6. Debug run to inspect proxy and thread behaviour
204
+ ```bash
205
+ python main.py -q "test" --proxy "http://user:pass@proxy:8080" --debug
206
+ ```
207
+
208
+ ### 7. Strict filtering for high‑quality OSINT reports
209
+ ```bash
210
+ python main.py -q "financial report filetype:pdf" --strict-filter -o clean_results.json
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Project Structure
216
+
217
+ ```
218
+ atdork/
219
+ ├── main.py # Entry point, CLI argument parser, orchestration
220
+ ├── core/
221
+ │ ├── case/
222
+ │ │ ├── resilience.py # Circuit breaker, backend fallback
223
+ │ │ └── rate_limiter.py # Adaptive per‑backend rate limiting
224
+ │ ├── scanner.py # Search logic, retry, proxy/UA integration
225
+ │ ├── batch_runner.py # Batch execution (sequential/parallel)
226
+ │ ├── proxy_manager.py # Proxy validation, rotation, statistics
227
+ │ ├── database.py # SQLite storage and export
228
+ │ ├── logger.py # Rotating file + console logging
229
+ │ ├── filter_vuln.py # Vulnerability signature filtering
230
+ │ └── config.py # YAML configuration loader
231
+ ├── lib/
232
+ │ ├── display.py # Terminal output, banner
233
+ │ ├── storage.py # Save results as TXT / JSON / CSV
234
+ │ └── validator.py # Output filtering (spam, URL validation)
235
+ ├── tests/ # 114 unit tests (pytest)
236
+ ├── wordlists/ # Vulnerability signature files
237
+ ├── presets/ # Dork templates (YAML)
238
+ ├── .github/workflows/ci.yml # CI pipeline
239
+ ├── atdork.yaml # User configuration file
240
+ ├── requirements.txt
241
+ └── README.md
242
+ ```
243
+ ## activitie graph
244
+ ![Alt](https://repobeats.axiom.co/api/embed/34d4bf05a783d8e3ea0762148747c10ed8f53e9f.svg "Repobeats analytics image")
245
+ ---
246
+
247
+ ## Ethical Use & Disclaimer
248
+
249
+ Atdork is intended for **ethical and legal purposes only**, such as:
250
+ - Authorised penetration testing
251
+ - Security research
252
+ - OSINT investigations with proper consent
253
+ - Educational use
254
+
255
+ **Do not use this tool for:**
256
+ - Unauthorised access to systems or data
257
+ - Harvesting information in violation of laws or regulations
258
+ - Any activity that infringes on privacy or intellectual property rights
259
+
260
+ Always ensure you comply with applicable local and international laws. The developer assumes no liability for misuse of this software.
261
+
262
+ ---
263
+
264
+ ## Contributing
265
+
266
+ Pull requests, issues, and feature suggestions are welcome.
267
+ Please open an issue first to discuss what you would like to change.
268
+
269
+ 1. Fork the repository
270
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
271
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
272
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
273
+ 5. Open a Pull Request
274
+
275
+ ---
276
+
277
+ ## License
278
+
279
+ Distributed under the MIT License. See `LICENSE` for more information.
280
+
281
+ ---
282
+
283
+ ## Contact
284
+
285
+ **alzzmarket**
286
+ GitHub: [github.com/amnottdevv/atdork](https://github.com/amnottdevv/atdork)
287
+
288
+ If you find this tool useful, consider leaving a ⭐ on the repository.
@@ -0,0 +1,7 @@
1
+ atdork.py,sha256=dHYeUZcnQaFfEn1NISBK2zbNIvtweFAH_XW7RCldl0g,24396
2
+ atdork-1.3.3.dist-info/licenses/LICENSE,sha256=Uu5tWkgHCW13P16ZqYewtz1n_zs27UN8bqmk7iZQc4A,1085
3
+ atdork-1.3.3.dist-info/METADATA,sha256=kpc2NHDv80Sdlm2D1WJTcF9rsnP0y6UGuYgxk6rOrr8,12117
4
+ atdork-1.3.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
5
+ atdork-1.3.3.dist-info/entry_points.txt,sha256=8fQ5kC0C53G8UhqY5gKbQzxc5Dh-7q3l9nYWaJkt_IE,39
6
+ atdork-1.3.3.dist-info/top_level.txt,sha256=nI0prIJBY05vBGNS5ZAiw5a-8F9r6MewqVD0BB5NYrw,7
7
+ atdork-1.3.3.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ atdork = atdork:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 alzzdev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ atdork
atdork.py ADDED
@@ -0,0 +1,580 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Atdork - Professional OSINT Tool
4
+ Version : 1.3.3
5
+ Author : alzzmarket
6
+ GitHub : github.com/amnottdevv/atdork
7
+ """
8
+
9
+ import argparse
10
+ import sys
11
+ import os
12
+ import json
13
+ import logging
14
+ from datetime import datetime
15
+ from typing import Optional, Dict, Any, List
16
+
17
+ from rich.console import Console
18
+ from rich.prompt import Prompt
19
+ from rich.progress import Progress, SpinnerColumn, TextColumn
20
+ from pyfiglet import Figlet
21
+
22
+ from core.config import load_config
23
+ from core.scanner import search_dork, SearchError
24
+ from core.batch_runner import load_queries_from_file, parse_query_string, run_batch
25
+ from core.proxy_manager import create_proxy_manager
26
+ from core.filter_vuln import filter_vulnerable
27
+ from core.template_dork import load_template_dorks, list_available_templates
28
+ from lib.display import show_banner, display_results
29
+ from lib.storage import save_results
30
+ from lib.validator import filter_results, get_filter_stats
31
+ from core.database import Database
32
+ from core.logger import setup_logging
33
+
34
+ # ── Resilience & Rate Limiter ──────────────────────────────────────────
35
+ from core.case.resilience import ResilienceHandler
36
+ from core.case.rate_limiter import RateLimiter
37
+
38
+ console = Console()
39
+
40
+
41
+ def _show_ascii_banner():
42
+ """Tampilkan ASCII art header."""
43
+ f = Figlet(font='slant')
44
+ banner = f.renderText('Atdork')
45
+ console.print(f"[bold green]{banner}[/bold green]")
46
+ console.print("[bold cyan]Professional OSINT Tool[/bold cyan]")
47
+ console.print(f"[dim]v1.3.3 - {datetime.now().strftime('%Y-%m-%d %H:%M')}[/dim]")
48
+ console.print()
49
+
50
+
51
+ def build_parser():
52
+ parser = argparse.ArgumentParser(
53
+ prog="atdork",
54
+ description="Atdork – Professional DuckDuckGo metasearch OSINT tool.",
55
+ epilog='Contoh: atdork -q "site:gov filetype:pdf" -r 10 -o hasil.json'
56
+ )
57
+ # Konfigurasi
58
+ parser.add_argument("--config", type=str, help="Path to YAML config file.")
59
+ parser.add_argument("--interactive", action="store_true", help="Jalankan mode interaktif.")
60
+
61
+ # Opsi utama
62
+ parser.add_argument("-q", "--query", type=str, help="Kata kunci / dork.")
63
+ parser.add_argument("-r", "--max-results", type=int, default=20, help="Jumlah hasil maksimum (1-100).")
64
+
65
+ # Parameter pencarian
66
+ parser.add_argument("--region", type=str, default="us-en", help="Region pencarian.")
67
+ parser.add_argument("--safesearch", type=str, default="moderate", choices=["on","moderate","off"])
68
+ parser.add_argument("--timelimit", type=str, default=None, choices=["d","w","m","y"])
69
+ parser.add_argument("--backend", type=str, default="auto", help="Backend mesin pencari.")
70
+
71
+ # Request tuning
72
+ parser.add_argument("--user-agent", type=str, help="Custom User-Agent.")
73
+ parser.add_argument("--timeout", type=int, default=10)
74
+ parser.add_argument("--retries", type=int, default=2)
75
+ parser.add_argument("--delay", type=float, default=0)
76
+
77
+ # Proxy
78
+ parser.add_argument("--proxy", type=str, help="Proxy URL (comma-separated).")
79
+ parser.add_argument("--proxy-file", type=str)
80
+ parser.add_argument("--tor", action="store_true", help="Gunakan Tor SOCKS5.")
81
+ parser.add_argument("--proxy-cooldown", type=int, default=60)
82
+ parser.add_argument("--strict", action="store_true", help="Jangan fallback ke direct.")
83
+ parser.add_argument("--max-failures", type=int, default=3)
84
+
85
+ # Multi-threading
86
+ parser.add_argument("--concurrency", type=int, default=1, help="Jumlah thread paralel untuk batch (1 = sekuensial).")
87
+ parser.add_argument("--max-fallback-failures", type=int, default=3, help="Batas kegagalan berturut-turut sebelum fallback ke sekuensial")
88
+
89
+ # Batch
90
+ parser.add_argument("--batch-file", type=str)
91
+ parser.add_argument("--batch-separator", type=str, default=";")
92
+
93
+ # Output
94
+ parser.add_argument("-o", "--output", type=str, help="Simpan hasil ke file.")
95
+ parser.add_argument("--output-dir", type=str)
96
+ parser.add_argument("--format", type=str, choices=["txt","json","csv"], default="txt")
97
+ parser.add_argument("--no-snippet", action="store_true")
98
+ parser.add_argument("-v", "--verbose", action="store_true", help="Tampilkan hasil pencarian ke layar dalam mode batch.")
99
+
100
+ # Validasi (legacy)
101
+ parser.add_argument("--no-validate", action="store_true", help="Matikan semua filter validasi.")
102
+ parser.add_argument("--strict-filter", action="store_true", help="Filter ketat (title≥5, desc≥10, spam on, url all).")
103
+
104
+ # Validasi granular (v1.3.1)
105
+ parser.add_argument("--validate-url", type=str, default="all",
106
+ choices=["only", "path", "params", "all", "false"],
107
+ help="Mode validasi URL: only (domain), path (domain+path), params (domain+params), all (lengkap), false (mati).")
108
+ parser.add_argument("--validate-title", type=str, default="5",
109
+ help="Panjang minimal judul (integer) atau 'false' untuk matikan (default: 5).")
110
+ parser.add_argument("--validate-desc", type=str, default="10",
111
+ help="Panjang minimal deskripsi (integer) atau 'false' untuk matikan (default: 10).")
112
+ parser.add_argument("--validate-spam", type=str, default="true",
113
+ choices=["true", "false"],
114
+ help="Aktifkan/matikan filter spam (default: true).")
115
+
116
+ # Filter kerentanan
117
+ parser.add_argument("--filter-vuln", type=str, help="Filter platform (e.g., wordpress).")
118
+
119
+ # Scanner tambahan
120
+ parser.add_argument("--no-fallback-backends", action="store_true")
121
+ parser.add_argument("--no-verify", action="store_true")
122
+
123
+ # Logging
124
+ parser.add_argument("--log-file", type=str, default="atdork.log", help="Path file log.")
125
+
126
+ # Database
127
+ parser.add_argument("--db-path", type=str, default="atdork.db", help="Path SQLite database.")
128
+ parser.add_argument("--resume", action="store_true", help="Resume batch yang tertunda.")
129
+ parser.add_argument("--history", action="store_true", help="Tampilkan riwayat pencarian.")
130
+ parser.add_argument("--no-dedup", action="store_true", help="Nonaktifkan deduplikasi global.")
131
+ parser.add_argument("--export-db", type=str, help="Export database ke file (json/csv).")
132
+
133
+ # Resilience & Rate Limiter (v1.3)
134
+ parser.add_argument("--resilient", action="store_true",
135
+ help="Aktifkan mode tahan banting (circuit breaker, backend fallback).")
136
+ parser.add_argument("--adaptive-delay", action="store_true",
137
+ help="Gunakan delay adaptif berdasarkan respons backend.")
138
+
139
+ # Template Dork (v1.3.2)
140
+ parser.add_argument("--template", type=str, help="Nama template dork (tanpa ekstensi) atau path ke file YAML. Bisa multiple (pisahkan dengan koma).")
141
+ parser.add_argument("--target", type=str, help="Target domain untuk substitusi {target} di template.")
142
+ parser.add_argument("--select", type=str, help="Pilih dork tertentu dari template (contoh: 1,3,5).")
143
+ parser.add_argument("--list-templates", action="store_true", help="Tampilkan daftar template yang tersedia.")
144
+ parser.add_argument("--template-path", type=str, help="Path ke folder template (default: wordlists/templates/).")
145
+ parser.add_argument("--preview", action="store_true", help="Pratinjau isi template tanpa menjalankannya.")
146
+
147
+ parser.add_argument("--debug", action="store_true")
148
+ parser.add_argument("--version", action="version", version="%(prog)s 1.3.3")
149
+ return parser
150
+
151
+
152
+ def _parse_validation_args(args) -> dict:
153
+ """Konversi argumen validasi ke dictionary untuk filter_results."""
154
+ if args.no_validate:
155
+ return {"strict": False}
156
+ if args.strict_filter:
157
+ return {"strict": True}
158
+
159
+ min_title = None if args.validate_title == "false" else int(args.validate_title)
160
+ min_desc = None if args.validate_desc == "false" else int(args.validate_desc)
161
+ check_spam = args.validate_spam == "true"
162
+ url_mode = args.validate_url
163
+
164
+ return {
165
+ "strict": None,
166
+ "min_title": min_title,
167
+ "min_desc": min_desc,
168
+ "check_spam": check_spam,
169
+ "url_mode": url_mode,
170
+ }
171
+
172
+
173
+ def _create_resilience_handler(args, proxy_manager=None) -> Optional[ResilienceHandler]:
174
+ if not args.resilient:
175
+ return None
176
+ return ResilienceHandler(
177
+ active=True,
178
+ max_retries=args.retries,
179
+ backoff_base=2.0,
180
+ max_backoff=30.0,
181
+ circuit_threshold=3,
182
+ circuit_cooldown=120.0,
183
+ proxy_manager=proxy_manager,
184
+ )
185
+
186
+
187
+ def _create_rate_limiter(args) -> Optional[RateLimiter]:
188
+ if not args.adaptive_delay:
189
+ return None
190
+ return RateLimiter(
191
+ base_delay=1.0,
192
+ max_delay=60.0,
193
+ backoff_factor=2.0,
194
+ recovery_factor=0.9,
195
+ min_delay=0.1,
196
+ )
197
+
198
+
199
+ def interactive_mode(db: Database):
200
+ """Alur interaktif seperti skrip awal."""
201
+ show_banner()
202
+ query = Prompt.ask("[bold yellow]Masukkan keyword/dork[/bold yellow]").strip()
203
+ if not query:
204
+ console.print("[red]Query kosong. Keluar.[/red]")
205
+ return
206
+
207
+ max_res_str = Prompt.ask("[bold yellow]Jumlah maksimal hasil[/bold yellow]", default="20")
208
+ try:
209
+ max_results = int(max_res_str)
210
+ max_results = max(1, min(100, max_results))
211
+ except ValueError:
212
+ max_results = 20
213
+
214
+ console.print("\n[bold cyan]🔍 Memulai pencarian...[/bold cyan]")
215
+ try:
216
+ results = search_dork(query, max_results=max_results)
217
+ except SearchError as e:
218
+ console.print(f"[red]Gagal: {e}[/red]")
219
+ return
220
+
221
+ if results:
222
+ original = len(results)
223
+ results = filter_results(results)
224
+ stats = get_filter_stats(original, len(results))
225
+ if stats["removed"] > 0:
226
+ console.print(f"[dim]Filter: {stats['removed']} hasil spam/invalid dihapus.[/dim]")
227
+
228
+ if db:
229
+ qid = db.add_query(query, "completed")
230
+ db.add_results_batch(qid, results)
231
+
232
+ display_results(results, query)
233
+
234
+ if results and Prompt.ask("[yellow]Simpan hasil ke file? (y/n)[/yellow]", choices=["y","n"], default="n") == "y":
235
+ path = save_results(results, query, output_format="txt")
236
+ console.print(f"[green]✅ Disimpan ke: {path}[/green]")
237
+
238
+
239
+ def cli_mode(args):
240
+ """Mode command line utama yang sudah terintegrasi penuh."""
241
+ setup_logging(debug=args.debug, log_file=args.log_file)
242
+ logger = logging.getLogger(__name__)
243
+
244
+ _show_ascii_banner()
245
+
246
+ # Validasi max_results
247
+ args.max_results = max(1, min(100, args.max_results))
248
+
249
+ # Database connection
250
+ db = Database(args.db_path) if (args.resume or args.history or args.export_db or not args.no_dedup) else None
251
+
252
+ # Handle --list-templates
253
+ if args.list_templates:
254
+ templates = list_available_templates(args.template_path or "wordlists/templates")
255
+ if not templates:
256
+ console.print("[yellow]Tidak ada template ditemukan.[/yellow]")
257
+ else:
258
+ console.print("[bold cyan]Template Dorks Tersedia:[/bold cyan]")
259
+ for t in templates:
260
+ console.print(f" [green]{t['name']}[/green] - {t['description']}")
261
+ if db: db.close()
262
+ return
263
+
264
+ # Handle --preview
265
+ if args.preview and args.template:
266
+ try:
267
+ for tname in args.template.split(","):
268
+ tname = tname.strip()
269
+ if not tname:
270
+ continue
271
+ dorks = load_template_dorks(
272
+ tname,
273
+ target=args.target,
274
+ select=args.select,
275
+ template_path=args.template_path
276
+ )
277
+ console.print(f"[bold cyan]Preview template '{tname}':[/bold cyan]")
278
+ for i, d in enumerate(dorks, 1):
279
+ console.print(f" {i}. {d}")
280
+ except Exception as e:
281
+ console.print(f"[red]Error: {e}[/red]")
282
+ if db: db.close()
283
+ return
284
+
285
+ # Handle --history
286
+ if args.history and db:
287
+ rows = db.get_all_queries()
288
+ if not rows:
289
+ console.print("[yellow]Belum ada riwayat pencarian.[/yellow]")
290
+ else:
291
+ console.print("[bold cyan]Riwayat Pencarian:[/bold cyan]")
292
+ for qid, text, status in rows:
293
+ console.print(f" [green]#{qid}[/green] [{status}] {text[:80]}")
294
+ if db: db.close()
295
+ return
296
+
297
+ # Handle --export-db
298
+ if args.export_db and db:
299
+ out = args.export_db
300
+ if out.endswith('.json'):
301
+ db.export_to_json(out)
302
+ else:
303
+ db.export_to_csv(out)
304
+ console.print(f"[green]✅ Database diekspor ke: {out}[/green]")
305
+ if db: db.close()
306
+ return
307
+
308
+ # Handle --resume
309
+ if args.resume and db:
310
+ pending = db.get_pending_queries()
311
+ if not pending:
312
+ console.print("[yellow]Tidak ada query yang tertunda.[/yellow]")
313
+ if db: db.close()
314
+ return
315
+ queries = [q[1] for q in pending]
316
+ console.print(f"[bold cyan]Resume mode: {len(queries)} query tertunda[/bold cyan]")
317
+ else:
318
+ queries = []
319
+
320
+ # Kumpulkan query dari berbagai sumber
321
+ if not queries:
322
+ # Template dork (dukung multiple dengan koma)
323
+ if args.template:
324
+ for tname in args.template.split(","):
325
+ tname = tname.strip()
326
+ if not tname:
327
+ continue
328
+ try:
329
+ template_dorks = load_template_dorks(
330
+ tname,
331
+ target=args.target,
332
+ select=args.select,
333
+ template_path=args.template_path
334
+ )
335
+ queries.extend(template_dorks)
336
+ except Exception as e:
337
+ console.print(f"[red]Error loading template '{tname}': {e}[/red]")
338
+ sys.exit(1)
339
+
340
+ # Query dari -q (bisa digabung)
341
+ if args.query:
342
+ if args.batch_separator in args.query:
343
+ custom_queries = parse_query_string(args.query, args.batch_separator)
344
+ else:
345
+ custom_queries = [args.query]
346
+ queries.extend(custom_queries)
347
+
348
+ # Batch file
349
+ if args.batch_file:
350
+ try:
351
+ file_queries = load_queries_from_file(args.batch_file)
352
+ queries.extend(file_queries)
353
+ except Exception as e:
354
+ console.print(f"[red]Gagal membaca file batch: {e}[/red]")
355
+ sys.exit(1)
356
+
357
+ if not queries:
358
+ console.print("[red]Error: Tidak ada query yang diberikan. Gunakan -q, --template, atau --batch-file.[/red]")
359
+ sys.exit(1)
360
+
361
+ # Proxy manager
362
+ proxy_manager = None
363
+ if args.proxy or args.proxy_file or args.tor:
364
+ try:
365
+ proxy_manager = create_proxy_manager(
366
+ proxy_arg=args.proxy,
367
+ proxy_file=args.proxy_file,
368
+ enable_tor=args.tor,
369
+ cooldown=args.proxy_cooldown,
370
+ strict=args.strict,
371
+ max_failures=args.max_failures,
372
+ )
373
+ console.print("[dim]Proxy manager diinisialisasi.[/dim]")
374
+ except ValueError as e:
375
+ console.print(f"[red]Proxy manager error: {e}[/red]")
376
+ sys.exit(1)
377
+
378
+ # Resilience & Rate limiter
379
+ resilience_handler = _create_resilience_handler(args, proxy_manager)
380
+ rate_limiter = _create_rate_limiter(args)
381
+
382
+ # Parameter scanner yang akan diteruskan
383
+ scanner_kwargs = {
384
+ "max_results": args.max_results,
385
+ "timeout": args.timeout,
386
+ "retries": args.retries,
387
+ "delay": args.delay,
388
+ "proxy_manager": proxy_manager,
389
+ "region": args.region,
390
+ "safesearch": args.safesearch,
391
+ "timelimit": args.timelimit,
392
+ "backend": args.backend,
393
+ "user_agent": args.user_agent,
394
+ "verify": not args.no_verify,
395
+ "fallback_backends": not args.no_fallback_backends,
396
+ }
397
+
398
+ # Parameter validasi
399
+ val_kwargs = _parse_validation_args(args)
400
+
401
+ # Jalankan batch atau single query
402
+ if len(queries) > 1 or args.resume:
403
+ console.print(f"[bold cyan]Batch mode: {len(queries)} query[/bold cyan]")
404
+ batch_results = run_batch(
405
+ queries=queries,
406
+ resilience_handler=resilience_handler,
407
+ rate_limiter=rate_limiter,
408
+ concurrency=args.concurrency,
409
+ **scanner_kwargs
410
+ )
411
+
412
+ # Tampilkan hasil jika --verbose
413
+ if args.verbose:
414
+ for q, res in batch_results.items():
415
+ console.print(f"\n[bold yellow]━━━ {q} ━━━[/bold yellow]")
416
+ display_results(res, q, no_snippet=args.no_snippet)
417
+
418
+ # Filter validasi
419
+ total_removed = 0
420
+ for q in batch_results:
421
+ old = len(batch_results[q])
422
+ batch_results[q] = filter_results(batch_results[q], **val_kwargs)
423
+ total_removed += old - len(batch_results[q])
424
+ if total_removed:
425
+ console.print(f"[dim]Filter: {total_removed} hasil dihapus.[/dim]")
426
+
427
+ # Filter kerentanan (v1.3.3: gunakan parameter baru dan unpack 3 nilai)
428
+ if args.filter_vuln:
429
+ total_vuln = 0
430
+ for q in batch_results:
431
+ vuln, safe, _ = filter_vulnerable(batch_results[q], filter_arg=args.filter_vuln)
432
+ total_vuln += len(vuln)
433
+ batch_results[q] = vuln
434
+ console.print(f"[bold red]🔴 {total_vuln} hasil berpotensi rentan ({args.filter_vuln}).[/bold red]")
435
+
436
+ # Simpan ke database
437
+ if db:
438
+ for q, results in batch_results.items():
439
+ qid = db.add_query(q, "completed")
440
+ db.add_results_batch(qid, results)
441
+
442
+ total_hits = sum(len(v) for v in batch_results.values())
443
+ console.print(f"\n[green]Batch selesai. Total {total_hits} hasil.[/green]")
444
+
445
+ # Output file
446
+ if args.output:
447
+ with open(args.output, "w", encoding="utf-8") as f:
448
+ json.dump(batch_results, f, indent=2, ensure_ascii=False)
449
+ console.print(f"[green]✅ Disimpan ke: {args.output}[/green]")
450
+ elif args.output_dir:
451
+ os.makedirs(args.output_dir, exist_ok=True)
452
+ for q, res in batch_results.items():
453
+ safe_name = "".join(c if c.isalnum() or c in " _-()" else "_" for c in q)[:50]
454
+ fname = f"{safe_name}.{args.format}"
455
+ fpath = os.path.join(args.output_dir, fname)
456
+ if args.format == "json":
457
+ with open(fpath, "w", encoding="utf-8") as f:
458
+ json.dump(res, f, indent=2, ensure_ascii=False)
459
+ elif args.format == "csv":
460
+ import csv
461
+ with open(fpath, "w", newline="", encoding="utf-8") as f:
462
+ writer = csv.DictWriter(f, fieldnames=["title","href","body"])
463
+ writer.writeheader()
464
+ for row in res:
465
+ writer.writerow(row)
466
+ else:
467
+ with open(fpath, "w", encoding="utf-8") as f:
468
+ f.write(f"Query: {q}\n\n")
469
+ for i, r in enumerate(res, 1):
470
+ f.write(f"[{i}] {r.get('title','')}\n{r.get('href','')}\n{r.get('body','')}\n\n")
471
+ console.print(f"[green]✅ Batch disimpan di folder: {args.output_dir}[/green]")
472
+
473
+ # Rate limiter recommendations
474
+ if rate_limiter:
475
+ console.print("\n[bold cyan]Rate Limiter Recommendations:[/bold cyan]")
476
+ for backend, rec in rate_limiter.all_recommendations().items():
477
+ console.print(f" [yellow]{backend}[/yellow]: {rec}")
478
+
479
+ else:
480
+ # ── Single query ──────────────────────────────────────────────
481
+ q = queries[0]
482
+ console.print(f"[bold cyan]🔍 Searching for:[/bold cyan] {q}")
483
+
484
+ backend_for_rate = scanner_kwargs.get("backend", "auto")
485
+ if rate_limiter:
486
+ rate_limiter.wait(backend_for_rate)
487
+
488
+ try:
489
+ if resilience_handler:
490
+ results, err = resilience_handler.execute(q, **scanner_kwargs)
491
+ if err:
492
+ raise SearchError(err)
493
+ else:
494
+ results = search_dork(query=q, **scanner_kwargs)
495
+
496
+ if rate_limiter:
497
+ rate_limiter.report_response(backend_for_rate, 200, len(results) > 0)
498
+ except Exception as e:
499
+ if rate_limiter:
500
+ if "429" in str(e):
501
+ rate_limiter.report_response(backend_for_rate, 429, False)
502
+ else:
503
+ rate_limiter.report_response(backend_for_rate, 500, False)
504
+ console.print(f"[red]Search failed: {e}[/red]")
505
+ sys.exit(1)
506
+
507
+ # Tampilkan hasil (single query selalu tampil)
508
+ display_results(results, q, no_snippet=args.no_snippet)
509
+
510
+ # Filter validasi
511
+ original = len(results)
512
+ results = filter_results(results, **val_kwargs)
513
+ stats = get_filter_stats(original, len(results))
514
+ if stats["removed"]:
515
+ console.print(f"[dim]Filter: {stats['removed']} hasil dihapus.[/dim]")
516
+
517
+ # Filter kerentanan (v1.3.3: gunakan parameter baru dan unpack 3 nilai)
518
+ if args.filter_vuln:
519
+ vuln, safe, _ = filter_vulnerable(results, filter_arg=args.filter_vuln)
520
+ console.print(f"[bold red]🔴 Rentan: {len(vuln)}[/bold red] | [green]🟢 Aman: {len(safe)}[/green]")
521
+ results = vuln
522
+
523
+ # Database insert
524
+ if db and not args.no_dedup:
525
+ original_len = len(results)
526
+ unique_results = []
527
+ for r in results:
528
+ if not db.is_duplicate(r.get("href","")):
529
+ unique_results.append(r)
530
+ console.print(f"[dim]Deduplikasi: {original_len - len(unique_results)} dihapus.[/dim]")
531
+ results = unique_results
532
+ qid = db.add_query(q, "completed")
533
+ db.add_results_batch(qid, results)
534
+ elif db:
535
+ qid = db.add_query(q, "completed")
536
+ db.add_results_batch(qid, results)
537
+
538
+ if args.output:
539
+ save_results(results, q, output_path=args.output, output_format=args.format)
540
+ console.print(f"[green]✅ Disimpan ke: {args.output}[/green]")
541
+ elif args.output_dir:
542
+ os.makedirs(args.output_dir, exist_ok=True)
543
+ path = save_results(results, q, output_dir=args.output_dir, output_format=args.format)
544
+ console.print(f"[green]✅ Disimpan ke: {path}[/green]")
545
+
546
+ if rate_limiter:
547
+ console.print("\n[bold cyan]Rate Limiter Recommendations:[/bold cyan]")
548
+ for backend, rec in rate_limiter.all_recommendations().items():
549
+ console.print(f" [yellow]{backend}[/yellow]: {rec}")
550
+
551
+ if db:
552
+ db.close()
553
+
554
+
555
+ def main():
556
+ parser = build_parser()
557
+ args, remaining = parser.parse_known_args()
558
+ config = load_config(args.config)
559
+ parser.set_defaults(**config)
560
+ args = parser.parse_args(remaining, namespace=args)
561
+
562
+ setup_logging(debug=args.debug, log_file=args.log_file)
563
+
564
+ if args.interactive or (not args.query and not args.batch_file and not args.resume and not args.history and not args.export_db and not args.template and not args.list_templates and not args.preview):
565
+ db = Database(args.db_path)
566
+ interactive_mode(db)
567
+ db.close()
568
+ else:
569
+ cli_mode(args)
570
+
571
+
572
+ if __name__ == "__main__":
573
+ try:
574
+ main()
575
+ except KeyboardInterrupt:
576
+ console.print("\n[yellow]⚠️ Dibatalkan[/yellow]")
577
+ sys.exit(0)
578
+ except Exception as e:
579
+ console.print(f"[red]Unexpected error: {e}[/red]")
580
+ sys.exit(1)