web-snapshot-cli 0.2.0__tar.gz → 1.1.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.
- web_snapshot_cli-1.1.0/.github/workflows/pages.yml +33 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/PKG-INFO +17 -2
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/README.md +15 -0
- web_snapshot_cli-1.1.0/docs/CNAME +1 -0
- web_snapshot_cli-1.1.0/docs/app.js +221 -0
- web_snapshot_cli-1.1.0/docs/index.html +200 -0
- web_snapshot_cli-1.1.0/docs/style.css +289 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/pyproject.toml +6 -2
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/src/snapshot/__init__.py +1 -1
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/src/snapshot/cli.py +41 -2
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/src/snapshot/downloader.py +73 -1
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/src/snapshot/utils.py +50 -0
- web_snapshot_cli-1.1.0/src/snapshot/wordlist.py +172 -0
- web_snapshot_cli-1.1.0/src/snapshot/wordlists/__init__.py +0 -0
- web_snapshot_cli-1.1.0/src/snapshot/wordlists/common.txt +132 -0
- web_snapshot_cli-1.1.0/src/snapshot/wordlists/large.txt +18637 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/tests/test_crawler.py +27 -0
- web_snapshot_cli-1.1.0/tests/test_utils.py +56 -0
- web_snapshot_cli-1.1.0/tests/test_wordlist.py +69 -0
- web_snapshot_cli-0.2.0/tests/test_utils.py +0 -26
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/.github/workflows/ci.yml +0 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/.github/workflows/publish.yml +0 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/.gitignore +0 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/LICENSE +0 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/src/snapshot/__main__.py +0 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/src/snapshot/crawl_policy.py +0 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/src/snapshot/manifest.py +0 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/src/snapshot/restore.py +0 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/tests/test_crawl_policy.py +0 -0
- {web_snapshot_cli-0.2.0 → web_snapshot_cli-1.1.0}/tests/test_features.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Deploy GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "docs/**"
|
|
8
|
+
- ".github/workflows/pages.yml"
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
pages: write
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: pages
|
|
18
|
+
cancel-in-progress: false
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
deploy:
|
|
22
|
+
environment:
|
|
23
|
+
name: github-pages
|
|
24
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: actions/configure-pages@v5
|
|
29
|
+
- uses: actions/upload-pages-artifact@v3
|
|
30
|
+
with:
|
|
31
|
+
path: docs
|
|
32
|
+
- uses: actions/deploy-pages@v4
|
|
33
|
+
id: deployment
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: web-snapshot-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Take offline snapshots of any website and restore them locally
|
|
5
5
|
Project-URL: Homepage, https://github.com/codingsushi79/Snapshot
|
|
6
6
|
Project-URL: Repository, https://github.com/codingsushi79/Snapshot
|
|
@@ -8,7 +8,7 @@ Author: snapshot contributors
|
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
License-File: LICENSE
|
|
10
10
|
Keywords: cli,crawler,offline,snapshot,web-archive
|
|
11
|
-
Classifier: Development Status ::
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Environment :: Console
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -45,6 +45,8 @@ Then run:
|
|
|
45
45
|
snapshot https://example.com ./mirror
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
**Command generator:** [snapshot.sushii.dev](https://snapshot.sushii.dev/)
|
|
49
|
+
|
|
48
50
|
## Usage
|
|
49
51
|
|
|
50
52
|
### Snapshot a single page
|
|
@@ -85,6 +87,16 @@ snapshot --cookie session=abc123 --header "Authorization: Bearer TOKEN" \
|
|
|
85
87
|
snapshot --crawl --sitemap https://example.com ./mirror --max-pages 500
|
|
86
88
|
```
|
|
87
89
|
|
|
90
|
+
### Gobuster-style path discovery
|
|
91
|
+
|
|
92
|
+
Brute-force common paths (including `robots.txt`, `sitemap.xml`, admin panels, etc.) using bundled wordlists (`common` + ~18k `large` entries). Add your own SecLists/gobuster wordlists with `-w`:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
snapshot --crawl --gobuster https://example.com ./mirror
|
|
96
|
+
snapshot --crawl -w common -w large -w /path/to/SecLists/Discovery/Web-Content/common.txt URL ./out
|
|
97
|
+
snapshot --crawl --gobuster --wordlist-ext html,php,asp URL ./out
|
|
98
|
+
```
|
|
99
|
+
|
|
88
100
|
### Resume an interrupted snapshot
|
|
89
101
|
|
|
90
102
|
```bash
|
|
@@ -137,6 +149,9 @@ snapshot -restore ./mirror --port 3000 --no-open
|
|
|
137
149
|
| `--robots` / `--no-robots` | Respect robots.txt (default: on) |
|
|
138
150
|
| `--crawl-delay` | Seconds to wait after each request (default: 0) |
|
|
139
151
|
| `--sitemap` | Seed crawl from sitemap.xml |
|
|
152
|
+
| `--gobuster`, `-g` | Discover paths with bundled wordlists |
|
|
153
|
+
| `--wordlist`, `-w` | Custom or builtin wordlist (`common`, `large`) |
|
|
154
|
+
| `--wordlist-ext` | Extensions to append per word (`html,php`) |
|
|
140
155
|
| `--resume` | Skip pages/assets already saved |
|
|
141
156
|
| `--verbose`, `-v` | Detailed log output |
|
|
142
157
|
| `--dry-run` | Fetch without writing files |
|
|
@@ -16,6 +16,8 @@ Then run:
|
|
|
16
16
|
snapshot https://example.com ./mirror
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
**Command generator:** [snapshot.sushii.dev](https://snapshot.sushii.dev/)
|
|
20
|
+
|
|
19
21
|
## Usage
|
|
20
22
|
|
|
21
23
|
### Snapshot a single page
|
|
@@ -56,6 +58,16 @@ snapshot --cookie session=abc123 --header "Authorization: Bearer TOKEN" \
|
|
|
56
58
|
snapshot --crawl --sitemap https://example.com ./mirror --max-pages 500
|
|
57
59
|
```
|
|
58
60
|
|
|
61
|
+
### Gobuster-style path discovery
|
|
62
|
+
|
|
63
|
+
Brute-force common paths (including `robots.txt`, `sitemap.xml`, admin panels, etc.) using bundled wordlists (`common` + ~18k `large` entries). Add your own SecLists/gobuster wordlists with `-w`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
snapshot --crawl --gobuster https://example.com ./mirror
|
|
67
|
+
snapshot --crawl -w common -w large -w /path/to/SecLists/Discovery/Web-Content/common.txt URL ./out
|
|
68
|
+
snapshot --crawl --gobuster --wordlist-ext html,php,asp URL ./out
|
|
69
|
+
```
|
|
70
|
+
|
|
59
71
|
### Resume an interrupted snapshot
|
|
60
72
|
|
|
61
73
|
```bash
|
|
@@ -108,6 +120,9 @@ snapshot -restore ./mirror --port 3000 --no-open
|
|
|
108
120
|
| `--robots` / `--no-robots` | Respect robots.txt (default: on) |
|
|
109
121
|
| `--crawl-delay` | Seconds to wait after each request (default: 0) |
|
|
110
122
|
| `--sitemap` | Seed crawl from sitemap.xml |
|
|
123
|
+
| `--gobuster`, `-g` | Discover paths with bundled wordlists |
|
|
124
|
+
| `--wordlist`, `-w` | Custom or builtin wordlist (`common`, `large`) |
|
|
125
|
+
| `--wordlist-ext` | Extensions to append per word (`html,php`) |
|
|
111
126
|
| `--resume` | Skip pages/assets already saved |
|
|
112
127
|
| `--verbose`, `-v` | Detailed log output |
|
|
113
128
|
| `--dry-run` | Fetch without writing files |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
snapshot.sushii.dev
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
const $ = (sel) => document.querySelector(sel);
|
|
2
|
+
const $$ = (sel) => document.querySelectorAll(sel);
|
|
3
|
+
|
|
4
|
+
const PRESETS = {
|
|
5
|
+
quick: {
|
|
6
|
+
crawl: false,
|
|
7
|
+
gobuster: false,
|
|
8
|
+
sitemap: false,
|
|
9
|
+
maxPages: 50,
|
|
10
|
+
depth: 3,
|
|
11
|
+
crawlDelay: 0,
|
|
12
|
+
},
|
|
13
|
+
crawl: {
|
|
14
|
+
crawl: true,
|
|
15
|
+
gobuster: false,
|
|
16
|
+
sitemap: true,
|
|
17
|
+
maxPages: 200,
|
|
18
|
+
depth: 5,
|
|
19
|
+
crawlDelay: 0,
|
|
20
|
+
resume: true,
|
|
21
|
+
verbose: true,
|
|
22
|
+
},
|
|
23
|
+
full: {
|
|
24
|
+
crawl: true,
|
|
25
|
+
gobuster: true,
|
|
26
|
+
sitemap: true,
|
|
27
|
+
maxPages: 5000,
|
|
28
|
+
depth: 10,
|
|
29
|
+
crawlDelay: 0.5,
|
|
30
|
+
wordlistExt: "html,php,asp,aspx",
|
|
31
|
+
resume: true,
|
|
32
|
+
verbose: true,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function shellQuote(value) {
|
|
37
|
+
if (!value) return '""';
|
|
38
|
+
if (/^[A-Za-z0-9_./~-]+$/.test(value)) return value;
|
|
39
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function linesFromTextarea(id) {
|
|
43
|
+
const el = $(id);
|
|
44
|
+
if (!el || !el.value.trim()) return [];
|
|
45
|
+
return el.value
|
|
46
|
+
.split("\n")
|
|
47
|
+
.map((l) => l.trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isRestoreMode() {
|
|
52
|
+
return document.querySelector(".mode-tab.active")?.dataset.mode === "restore";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildCommand() {
|
|
56
|
+
if (isRestoreMode()) {
|
|
57
|
+
return buildRestoreCommand();
|
|
58
|
+
}
|
|
59
|
+
return buildSnapshotCommand();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildRestoreCommand() {
|
|
63
|
+
const parts = ["snapshot", "-restore"];
|
|
64
|
+
const dir = $("#outputDir").value.trim() || "./mirror";
|
|
65
|
+
const port = $("#port").value;
|
|
66
|
+
const host = $("#host").value.trim();
|
|
67
|
+
const noOpen = $("#noOpen").checked;
|
|
68
|
+
|
|
69
|
+
parts.push(shellQuote(dir));
|
|
70
|
+
if (port && port !== "8080") parts.push("--port", port);
|
|
71
|
+
if (host && host !== "127.0.0.1") parts.push("--host", shellQuote(host));
|
|
72
|
+
if (noOpen) parts.push("--no-open");
|
|
73
|
+
|
|
74
|
+
return parts.join(" ");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function buildSnapshotCommand() {
|
|
78
|
+
const parts = ["snapshot"];
|
|
79
|
+
const flags = [];
|
|
80
|
+
|
|
81
|
+
if ($("#crawl").checked) flags.push("--crawl");
|
|
82
|
+
if ($("#gobuster").checked) flags.push("--gobuster");
|
|
83
|
+
if ($("#sitemap").checked) flags.push("--sitemap");
|
|
84
|
+
if ($("#resume").checked) flags.push("--resume");
|
|
85
|
+
if ($("#verbose").checked) flags.push("--verbose");
|
|
86
|
+
if ($("#dryRun").checked) flags.push("--dry-run");
|
|
87
|
+
if ($("#noAssets").checked) flags.push("--no-assets");
|
|
88
|
+
if (!$("#robots").checked) flags.push("--no-robots");
|
|
89
|
+
if (!$("#sameOrigin").checked) flags.push("--no-same-origin");
|
|
90
|
+
|
|
91
|
+
const lang = $("#lang").value;
|
|
92
|
+
if (lang && lang !== "html") flags.push("--lang", lang);
|
|
93
|
+
|
|
94
|
+
const maxPages = $("#maxPages").value;
|
|
95
|
+
if (maxPages && maxPages !== "50") flags.push("--max-pages", maxPages);
|
|
96
|
+
|
|
97
|
+
const depth = $("#depth").value;
|
|
98
|
+
if (depth && depth !== "3") flags.push("--depth", depth);
|
|
99
|
+
|
|
100
|
+
const timeout = $("#timeout").value;
|
|
101
|
+
if (timeout && timeout !== "15") flags.push("--timeout", timeout);
|
|
102
|
+
|
|
103
|
+
const concurrency = $("#concurrency").value;
|
|
104
|
+
if (concurrency && concurrency !== "16") flags.push("--concurrency", concurrency);
|
|
105
|
+
|
|
106
|
+
const crawlDelay = $("#crawlDelay").value;
|
|
107
|
+
if (crawlDelay && crawlDelay !== "0" && crawlDelay !== "0.0") {
|
|
108
|
+
flags.push("--crawl-delay", crawlDelay);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const userAgent = $("#userAgent").value.trim();
|
|
112
|
+
if (userAgent) flags.push("--user-agent", shellQuote(userAgent));
|
|
113
|
+
|
|
114
|
+
const wordlistExt = $("#wordlistExt").value.trim();
|
|
115
|
+
if (wordlistExt) flags.push("--wordlist-ext", shellQuote(wordlistExt));
|
|
116
|
+
|
|
117
|
+
for (const cookie of linesFromTextarea("#cookies")) {
|
|
118
|
+
flags.push("--cookie", shellQuote(cookie));
|
|
119
|
+
}
|
|
120
|
+
for (const header of linesFromTextarea("#headers")) {
|
|
121
|
+
flags.push("--header", shellQuote(header));
|
|
122
|
+
}
|
|
123
|
+
for (const inc of linesFromTextarea("#includes")) {
|
|
124
|
+
flags.push("--include", shellQuote(inc));
|
|
125
|
+
}
|
|
126
|
+
for (const exc of linesFromTextarea("#excludes")) {
|
|
127
|
+
flags.push("--exclude", shellQuote(exc));
|
|
128
|
+
}
|
|
129
|
+
for (const wl of linesFromTextarea("#wordlists")) {
|
|
130
|
+
flags.push("--wordlist", shellQuote(wl));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
parts.push(...flags);
|
|
134
|
+
|
|
135
|
+
let url = $("#url").value.trim();
|
|
136
|
+
if (url && !/^https?:\/\//i.test(url)) url = `https://${url}`;
|
|
137
|
+
const out = $("#outputDir").value.trim() || "./mirror";
|
|
138
|
+
|
|
139
|
+
parts.push(shellQuote(url || "https://example.com"));
|
|
140
|
+
parts.push(shellQuote(out));
|
|
141
|
+
|
|
142
|
+
return parts.join(" ");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function updateCommand() {
|
|
146
|
+
const cmd = buildCommand();
|
|
147
|
+
$("#command").textContent = cmd;
|
|
148
|
+
|
|
149
|
+
const restoreHint = $("#restoreHint");
|
|
150
|
+
if (isRestoreMode()) {
|
|
151
|
+
restoreHint.classList.add("hidden");
|
|
152
|
+
} else {
|
|
153
|
+
restoreHint.classList.remove("hidden");
|
|
154
|
+
const dir = $("#outputDir").value.trim() || "./mirror";
|
|
155
|
+
$("#restoreCommand").textContent = `snapshot -restore ${shellQuote(dir)}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function applyPreset(name) {
|
|
160
|
+
const p = PRESETS[name];
|
|
161
|
+
if (!p) return;
|
|
162
|
+
|
|
163
|
+
$("#crawl").checked = !!p.crawl;
|
|
164
|
+
$("#gobuster").checked = !!p.gobuster;
|
|
165
|
+
$("#sitemap").checked = !!p.sitemap;
|
|
166
|
+
$("#resume").checked = !!p.resume;
|
|
167
|
+
$("#verbose").checked = !!p.verbose;
|
|
168
|
+
if (p.maxPages != null) $("#maxPages").value = p.maxPages;
|
|
169
|
+
if (p.depth != null) $("#depth").value = p.depth;
|
|
170
|
+
if (p.crawlDelay != null) $("#crawlDelay").value = p.crawlDelay;
|
|
171
|
+
if (p.wordlistExt != null) $("#wordlistExt").value = p.wordlistExt;
|
|
172
|
+
|
|
173
|
+
setMode("snapshot");
|
|
174
|
+
updateCommand();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function setMode(mode) {
|
|
178
|
+
$$(".mode-tab").forEach((tab) => {
|
|
179
|
+
tab.classList.toggle("active", tab.dataset.mode === mode);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const snapshotOnly = $$(".snapshot-only");
|
|
183
|
+
const restoreOnly = $$(".restore-only");
|
|
184
|
+
|
|
185
|
+
snapshotOnly.forEach((el) => el.classList.toggle("hidden", mode === "restore"));
|
|
186
|
+
restoreOnly.forEach((el) => el.classList.toggle("hidden", mode !== "restore"));
|
|
187
|
+
|
|
188
|
+
updateCommand();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function copyCommand() {
|
|
192
|
+
const text = $("#command").textContent;
|
|
193
|
+
await navigator.clipboard.writeText(text);
|
|
194
|
+
const btn = $("#copyBtn");
|
|
195
|
+
btn.textContent = "Copied!";
|
|
196
|
+
btn.classList.add("copied");
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
btn.textContent = "Copy";
|
|
199
|
+
btn.classList.remove("copied");
|
|
200
|
+
}, 1500);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function init() {
|
|
204
|
+
$$(".mode-tab").forEach((tab) => {
|
|
205
|
+
tab.addEventListener("click", () => setMode(tab.dataset.mode));
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
$$(".preset-btn").forEach((btn) => {
|
|
209
|
+
btn.addEventListener("click", () => applyPreset(btn.dataset.preset));
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
$("#copyBtn").addEventListener("click", copyCommand);
|
|
213
|
+
|
|
214
|
+
document.addEventListener("input", updateCommand);
|
|
215
|
+
document.addEventListener("change", updateCommand);
|
|
216
|
+
|
|
217
|
+
setMode("snapshot");
|
|
218
|
+
updateCommand();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>snapshot — command generator</title>
|
|
7
|
+
<meta name="description" content="Build snapshot CLI commands for offline website mirrors." />
|
|
8
|
+
<link rel="stylesheet" href="style.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<header>
|
|
12
|
+
<h1>snapshot command generator</h1>
|
|
13
|
+
<p>
|
|
14
|
+
Build install and run commands for
|
|
15
|
+
<a href="https://github.com/codingsushi79/Snapshot">web-snapshot-cli</a>
|
|
16
|
+
</p>
|
|
17
|
+
</header>
|
|
18
|
+
|
|
19
|
+
<div class="layout">
|
|
20
|
+
<div class="panel">
|
|
21
|
+
<div class="mode-tabs">
|
|
22
|
+
<button type="button" class="mode-tab active" data-mode="snapshot">Snapshot</button>
|
|
23
|
+
<button type="button" class="mode-tab" data-mode="restore">Restore</button>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="presets snapshot-only">
|
|
27
|
+
<button type="button" class="preset-btn" data-preset="quick">Single page</button>
|
|
28
|
+
<button type="button" class="preset-btn" data-preset="crawl">Crawl</button>
|
|
29
|
+
<button type="button" class="preset-btn" data-preset="full">Full + gobuster</button>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<h2>Target</h2>
|
|
33
|
+
<div class="field snapshot-only">
|
|
34
|
+
<label for="url">Website URL</label>
|
|
35
|
+
<input type="url" id="url" placeholder="https://example.com" value="https://example.com" />
|
|
36
|
+
</div>
|
|
37
|
+
<div class="field">
|
|
38
|
+
<label for="outputDir">Output directory</label>
|
|
39
|
+
<input type="text" id="outputDir" placeholder="./mirror or ~/sites/example" value="./mirror" />
|
|
40
|
+
<div class="hint">Where files are saved (snapshot) or served from (restore)</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<hr class="section-divider snapshot-only" />
|
|
44
|
+
|
|
45
|
+
<h2 class="snapshot-only">Discovery</h2>
|
|
46
|
+
<div class="check-grid snapshot-only">
|
|
47
|
+
<label class="check-item">
|
|
48
|
+
<input type="checkbox" id="crawl" />
|
|
49
|
+
<div>--crawl<span>Follow same-origin links</span></div>
|
|
50
|
+
</label>
|
|
51
|
+
<label class="check-item">
|
|
52
|
+
<input type="checkbox" id="gobuster" />
|
|
53
|
+
<div>--gobuster<span>Bundled wordlists; soft 404 pages are filtered out</span></div>
|
|
54
|
+
</label>
|
|
55
|
+
<label class="check-item">
|
|
56
|
+
<input type="checkbox" id="sitemap" />
|
|
57
|
+
<div>--sitemap<span>Seed from sitemap.xml / robots.txt</span></div>
|
|
58
|
+
</label>
|
|
59
|
+
<label class="check-item">
|
|
60
|
+
<input type="checkbox" id="resume" />
|
|
61
|
+
<div>--resume<span>Skip already-downloaded files</span></div>
|
|
62
|
+
</label>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="field snapshot-only">
|
|
66
|
+
<label for="wordlists">Extra wordlists (one per line)</label>
|
|
67
|
+
<textarea id="wordlists" placeholder="common large /path/to/SecLists/Discovery/Web-Content/common.txt"></textarea>
|
|
68
|
+
<div class="hint">Builtin names: <code>common</code>, <code>large</code>. --gobuster uses both by default. Pages that return 200 but show 404/not-found content are skipped.</div>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="field snapshot-only">
|
|
71
|
+
<label for="wordlistExt">Wordlist extensions</label>
|
|
72
|
+
<input type="text" id="wordlistExt" placeholder="html,php,asp,aspx" />
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<hr class="section-divider snapshot-only" />
|
|
76
|
+
|
|
77
|
+
<h2 class="snapshot-only">Crawl limits</h2>
|
|
78
|
+
<div class="row snapshot-only">
|
|
79
|
+
<div class="field">
|
|
80
|
+
<label for="maxPages">Max pages</label>
|
|
81
|
+
<input type="number" id="maxPages" value="50" min="1" />
|
|
82
|
+
</div>
|
|
83
|
+
<div class="field">
|
|
84
|
+
<label for="depth">Depth</label>
|
|
85
|
+
<input type="number" id="depth" value="3" min="1" />
|
|
86
|
+
</div>
|
|
87
|
+
<div class="field">
|
|
88
|
+
<label for="concurrency">Concurrency</label>
|
|
89
|
+
<input type="number" id="concurrency" value="16" min="1" />
|
|
90
|
+
</div>
|
|
91
|
+
<div class="field">
|
|
92
|
+
<label for="crawlDelay">Crawl delay (sec)</label>
|
|
93
|
+
<input type="number" id="crawlDelay" value="0" min="0" step="0.1" />
|
|
94
|
+
</div>
|
|
95
|
+
<div class="field">
|
|
96
|
+
<label for="timeout">Timeout (sec)</label>
|
|
97
|
+
<input type="number" id="timeout" value="15" min="1" step="0.5" />
|
|
98
|
+
</div>
|
|
99
|
+
<div class="field">
|
|
100
|
+
<label for="lang">Output format</label>
|
|
101
|
+
<select id="lang">
|
|
102
|
+
<option value="html">html</option>
|
|
103
|
+
<option value="md">md</option>
|
|
104
|
+
</select>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<hr class="section-divider snapshot-only" />
|
|
109
|
+
|
|
110
|
+
<h2 class="snapshot-only">Filters & auth</h2>
|
|
111
|
+
<div class="field snapshot-only">
|
|
112
|
+
<label for="includes">Include globs (one per line)</label>
|
|
113
|
+
<textarea id="includes" placeholder="/docs/* /blog/*"></textarea>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="field snapshot-only">
|
|
116
|
+
<label for="excludes">Exclude globs (one per line)</label>
|
|
117
|
+
<textarea id="excludes" placeholder="/admin/* /drafts/*"></textarea>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="field snapshot-only">
|
|
120
|
+
<label for="cookies">Cookies (name=value, one per line)</label>
|
|
121
|
+
<textarea id="cookies" placeholder="session=abc123"></textarea>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="field snapshot-only">
|
|
124
|
+
<label for="headers">Headers (Name: Value, one per line)</label>
|
|
125
|
+
<textarea id="headers" placeholder="Authorization: Bearer TOKEN"></textarea>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="field snapshot-only">
|
|
128
|
+
<label for="userAgent">Custom User-Agent</label>
|
|
129
|
+
<input type="text" id="userAgent" placeholder="Leave blank for default" />
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<hr class="section-divider" />
|
|
133
|
+
|
|
134
|
+
<h2>Options</h2>
|
|
135
|
+
<div class="check-grid snapshot-only">
|
|
136
|
+
<label class="check-item">
|
|
137
|
+
<input type="checkbox" id="robots" checked />
|
|
138
|
+
<div>--robots<span>Respect robots.txt</span></div>
|
|
139
|
+
</label>
|
|
140
|
+
<label class="check-item">
|
|
141
|
+
<input type="checkbox" id="sameOrigin" checked />
|
|
142
|
+
<div>--same-origin<span>Same origin only</span></div>
|
|
143
|
+
</label>
|
|
144
|
+
<label class="check-item">
|
|
145
|
+
<input type="checkbox" id="noAssets" />
|
|
146
|
+
<div>--no-assets<span>Skip CSS/JS/images</span></div>
|
|
147
|
+
</label>
|
|
148
|
+
<label class="check-item">
|
|
149
|
+
<input type="checkbox" id="verbose" />
|
|
150
|
+
<div>--verbose<span>Detailed logs</span></div>
|
|
151
|
+
</label>
|
|
152
|
+
<label class="check-item">
|
|
153
|
+
<input type="checkbox" id="dryRun" />
|
|
154
|
+
<div>--dry-run<span>Don't write files</span></div>
|
|
155
|
+
</label>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="row restore-only hidden">
|
|
158
|
+
<div class="field">
|
|
159
|
+
<label for="port">Port</label>
|
|
160
|
+
<input type="number" id="port" value="8080" min="1" />
|
|
161
|
+
</div>
|
|
162
|
+
<div class="field">
|
|
163
|
+
<label for="host">Host</label>
|
|
164
|
+
<input type="text" id="host" value="127.0.0.1" />
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
<label class="check-item restore-only hidden">
|
|
168
|
+
<input type="checkbox" id="noOpen" />
|
|
169
|
+
<div>--no-open<span>Don't open browser</span></div>
|
|
170
|
+
</label>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<div class="panel output-panel">
|
|
174
|
+
<h2>Generated command</h2>
|
|
175
|
+
<div class="command-box">
|
|
176
|
+
<code id="command">snapshot https://example.com ./mirror</code>
|
|
177
|
+
<button type="button" class="copy-btn" id="copyBtn">Copy</button>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div class="install-line">
|
|
181
|
+
Install first:
|
|
182
|
+
<code>pip install web-snapshot-cli</code>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="install-line" id="restoreHint">
|
|
186
|
+
After snapshot, restore locally:
|
|
187
|
+
<code id="restoreCommand">snapshot -restore ./mirror</code>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<footer>
|
|
193
|
+
<a href="https://github.com/codingsushi79/Snapshot">GitHub</a>
|
|
194
|
+
·
|
|
195
|
+
<a href="https://pypi.org/project/web-snapshot-cli/">PyPI</a>
|
|
196
|
+
</footer>
|
|
197
|
+
|
|
198
|
+
<script src="app.js"></script>
|
|
199
|
+
</body>
|
|
200
|
+
</html>
|