manhuagui-cli 1.0.0
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.
- package/LICENSE +9 -0
- package/README.md +131 -0
- package/README_EN.md +131 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/chapter.d.ts +6 -0
- package/dist/lib/chapter.d.ts.map +1 -0
- package/dist/lib/chapter.js +142 -0
- package/dist/lib/chapter.js.map +1 -0
- package/dist/lib/cli.d.ts +30 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +272 -0
- package/dist/lib/cli.js.map +1 -0
- package/dist/lib/comic.d.ts +6 -0
- package/dist/lib/comic.d.ts.map +1 -0
- package/dist/lib/comic.js +61 -0
- package/dist/lib/comic.js.map +1 -0
- package/dist/lib/config.d.ts +21 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +42 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/download.d.ts +2 -0
- package/dist/lib/download.d.ts.map +1 -0
- package/dist/lib/download.js +14 -0
- package/dist/lib/download.js.map +1 -0
- package/dist/lib/errors.d.ts +4 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +7 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/logger.d.ts +7 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +17 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/progress.d.ts +22 -0
- package/dist/lib/progress.d.ts.map +1 -0
- package/dist/lib/progress.js +51 -0
- package/dist/lib/progress.js.map +1 -0
- package/dist/lib/prompts.d.ts +6 -0
- package/dist/lib/prompts.d.ts.map +1 -0
- package/dist/lib/prompts.js +56 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/types.d.ts +15 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.d.ts +8 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +31 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wooloo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# manhuagui-cli
|
|
2
|
+
|
|
3
|
+
> 漫画柜 (manhuagui.com) 漫画下载命令行工具
|
|
4
|
+
|
|
5
|
+
[English](README_EN.md)
|
|
6
|
+
|
|
7
|
+
## 功能特性
|
|
8
|
+
|
|
9
|
+
- 交互式命令行界面,也支持命令行参数直接运行
|
|
10
|
+
- 自动解析漫画页面,列出所有章节与卷
|
|
11
|
+
- 使用 Playwright 模拟浏览器请求,绕过反爬检测
|
|
12
|
+
- 随机 User-Agent 和视口大小,模拟真实用户行为
|
|
13
|
+
- CDN 节点自动轮换,图片下载失败时自动重试(最多 3 次)
|
|
14
|
+
- 章节内图片并发下载,章节之间顺序处理并添加随机延迟
|
|
15
|
+
- 断点续传:进度保存到 `progress.json`,中断后可通过 `--resume` / `-r` 继续
|
|
16
|
+
- 下载目录结构清晰:`output/<漫画名>/<章节组>/<章节>/001.webp`
|
|
17
|
+
|
|
18
|
+
## 系统要求
|
|
19
|
+
|
|
20
|
+
- **Node.js** >= 22
|
|
21
|
+
- **pnpm**(推荐)或 npm
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
### npm(推荐)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g manhuagui-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
安装后即可直接使用 `manhuagui-cli` 命令。
|
|
32
|
+
|
|
33
|
+
### 从源码安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/wooloo26/manhuagui-cli.git
|
|
37
|
+
cd manhuagui-cli
|
|
38
|
+
pnpm install
|
|
39
|
+
pnpm build
|
|
40
|
+
pnpm link --global
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 使用方法
|
|
44
|
+
|
|
45
|
+
### 交互模式
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm dev
|
|
49
|
+
# 或编译后:
|
|
50
|
+
pnpm build
|
|
51
|
+
node dist/index.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
按提示输入漫画 URL,选择要下载的章节组,确认后开始下载。
|
|
55
|
+
|
|
56
|
+
### 命令行模式
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
node dist/index.js <URL> [选项]
|
|
60
|
+
# 或全局安装后:
|
|
61
|
+
manhuagui-cli <URL> [选项]
|
|
62
|
+
|
|
63
|
+
# 查看帮助
|
|
64
|
+
node dist/index.js --help
|
|
65
|
+
|
|
66
|
+
# 示例:下载指定章节组
|
|
67
|
+
node dist/index.js https://www.manhuagui.com/comic/12345/ -s "单行本"
|
|
68
|
+
|
|
69
|
+
# 示例:下载指定章节
|
|
70
|
+
node dist/index.js https://www.manhuagui.com/comic/12345/ -s "单话" -c "第01话"
|
|
71
|
+
|
|
72
|
+
# 示例:断点续传
|
|
73
|
+
node dist/index.js https://www.manhuagui.com/comic/12345/ --resume
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 选项
|
|
77
|
+
|
|
78
|
+
| 选项 | 简写 | 说明 |
|
|
79
|
+
| ------------------ | ---- | ------------------------------------ |
|
|
80
|
+
| `--section <name>` | `-s` | 指定要下载的章节组名称(默认:全部) |
|
|
81
|
+
| `--chapter <name>` | `-c` | 指定要下载的章节名称 |
|
|
82
|
+
| `--resume` | `-r` | 断点续传模式 |
|
|
83
|
+
| `--help` | `-h` | 显示帮助信息 |
|
|
84
|
+
| `--version` | `-v` | 显示版本号 |
|
|
85
|
+
|
|
86
|
+
## 环境变量
|
|
87
|
+
|
|
88
|
+
复制 `.env.example` 为 `.env`,根据需要修改:
|
|
89
|
+
|
|
90
|
+
| 变量 | 默认值 | 说明 |
|
|
91
|
+
| ------------------- | ---------- | ---------------------------------- |
|
|
92
|
+
| `OUTPUT_BASE` | `./output` | 下载输出目录 |
|
|
93
|
+
| `IMAGE_CONCURRENCY` | `2` | 章节内图片并发下载数 |
|
|
94
|
+
| `DOWNLOAD_DELAY` | `3000` | 图片批次之间延迟(毫秒,0 禁用) |
|
|
95
|
+
| `CHAPTER_DELAY_MIN` | `5000` | 章节间最小延迟(毫秒) |
|
|
96
|
+
| `CHAPTER_DELAY_MAX` | `15000` | 章节间最大延迟(毫秒) |
|
|
97
|
+
| `USER_AGENTS` | — | 自定义 User-Agent 列表(每行一个) |
|
|
98
|
+
|
|
99
|
+
## 输出目录结构
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
output/
|
|
103
|
+
└── <漫画名>/
|
|
104
|
+
├── urls.json # 章节图片 URL 清单
|
|
105
|
+
├── progress.json # 下载进度(断点续传用)
|
|
106
|
+
└── <章节组>/
|
|
107
|
+
└── <章节>/
|
|
108
|
+
├── 001.webp
|
|
109
|
+
├── 002.webp
|
|
110
|
+
└── ...
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 开发
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pnpm install # 安装依赖
|
|
117
|
+
pnpm dev # 开发模式运行(直接运行 TypeScript)
|
|
118
|
+
pnpm build # 编译 TypeScript
|
|
119
|
+
pnpm typecheck # 类型检查
|
|
120
|
+
pnpm test # 运行测试
|
|
121
|
+
pnpm check # 代码检查和格式化检查
|
|
122
|
+
pnpm format # 自动格式化
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
[MIT](LICENSE)
|
|
128
|
+
|
|
129
|
+
## 免责声明
|
|
130
|
+
|
|
131
|
+
本工具仅供学习和个人使用。请遵守目标网站的服务条款,尊重内容创作者的知识产权。请勿将本工具用于商业用途或在未经许可的情况下分发受版权保护的内容。
|
package/README_EN.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# manhuagui-cli
|
|
2
|
+
|
|
3
|
+
> CLI tool for downloading manhua from manhuagui.com
|
|
4
|
+
|
|
5
|
+
[中文](README.md)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Interactive CLI with prompts, also supports direct CLI args
|
|
10
|
+
- Auto-parses comic pages, lists all sections and chapters
|
|
11
|
+
- Uses Playwright headless browser to bypass anti-bot measures
|
|
12
|
+
- Random User-Agent and viewport rotation to simulate real users
|
|
13
|
+
- CDN host rotation with automatic retry (up to 3 attempts) on image download failure
|
|
14
|
+
- Concurrent image downloads within a chapter, sequential chapters with human-like delays
|
|
15
|
+
- Resumable downloads: progress saved to `progress.json`, resume with `--resume` / `-r`
|
|
16
|
+
- Clean output structure: `output/<comic-title>/<section>/<chapter>/001.webp`
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- **Node.js** >= 22
|
|
21
|
+
- **pnpm** (recommended) or npm
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
### npm (Recommended)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g manhuagui-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
After installation, use the `manhuagui-cli` command directly.
|
|
32
|
+
|
|
33
|
+
### From Source
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/wooloo26/manhuagui-cli.git
|
|
37
|
+
cd manhuagui-cli
|
|
38
|
+
pnpm install
|
|
39
|
+
pnpm build
|
|
40
|
+
pnpm link --global
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Interactive Mode
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm dev
|
|
49
|
+
# or build first:
|
|
50
|
+
pnpm build
|
|
51
|
+
node dist/index.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Follow prompts to enter comic URL, select sections, and confirm download.
|
|
55
|
+
|
|
56
|
+
### CLI Mode
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
node dist/index.js <URL> [options]
|
|
60
|
+
# or if linked globally:
|
|
61
|
+
manhuagui-cli <URL> [options]
|
|
62
|
+
|
|
63
|
+
# Show help
|
|
64
|
+
node dist/index.js --help
|
|
65
|
+
|
|
66
|
+
# Example: download specific section
|
|
67
|
+
node dist/index.js https://www.manhuagui.com/comic/12345/ -s "单行本"
|
|
68
|
+
|
|
69
|
+
# Example: download specific chapter
|
|
70
|
+
node dist/index.js https://www.manhuagui.com/comic/12345/ -s "单话" -c "第01话"
|
|
71
|
+
|
|
72
|
+
# Example: resume interrupted download
|
|
73
|
+
node dist/index.js https://www.manhuagui.com/comic/12345/ --resume
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Options
|
|
77
|
+
|
|
78
|
+
| Option | Alias | Description |
|
|
79
|
+
| ------------------ | ----- | ---------------------------------------------- |
|
|
80
|
+
| `--section <name>` | `-s` | Download only the named section (default: all) |
|
|
81
|
+
| `--chapter <name>` | `-c` | Download only the named chapter |
|
|
82
|
+
| `--resume` | `-r` | Resume from previous interrupted download |
|
|
83
|
+
| `--help` | `-h` | Show help |
|
|
84
|
+
| `--version` | `-v` | Show version |
|
|
85
|
+
|
|
86
|
+
## Environment Variables
|
|
87
|
+
|
|
88
|
+
Copy `.env.example` to `.env` and modify as needed:
|
|
89
|
+
|
|
90
|
+
| Variable | Default | Description |
|
|
91
|
+
| ------------------- | ---------- | -------------------------------------- |
|
|
92
|
+
| `OUTPUT_BASE` | `./output` | Download output directory |
|
|
93
|
+
| `IMAGE_CONCURRENCY` | `2` | Concurrent image downloads per chapter |
|
|
94
|
+
| `DOWNLOAD_DELAY` | `3000` | Delay between image batches (ms) |
|
|
95
|
+
| `CHAPTER_DELAY_MIN` | `5000` | Min delay between chapters (ms) |
|
|
96
|
+
| `CHAPTER_DELAY_MAX` | `15000` | Max delay between chapters (ms) |
|
|
97
|
+
| `USER_AGENTS` | — | Custom User-Agent pool, one per line |
|
|
98
|
+
|
|
99
|
+
## Output Structure
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
output/
|
|
103
|
+
└── <comic-title>/
|
|
104
|
+
├── urls.json # Chapter image URL manifest
|
|
105
|
+
├── progress.json # Download progress (for resume)
|
|
106
|
+
└── <section>/
|
|
107
|
+
└── <chapter>/
|
|
108
|
+
├── 001.webp
|
|
109
|
+
├── 002.webp
|
|
110
|
+
└── ...
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pnpm install # Install dependencies
|
|
117
|
+
pnpm dev # Run in dev mode (TypeScript directly)
|
|
118
|
+
pnpm build # Compile TypeScript
|
|
119
|
+
pnpm typecheck # Type check
|
|
120
|
+
pnpm test # Run tests
|
|
121
|
+
pnpm check # Lint and format check
|
|
122
|
+
pnpm format # Auto-fix formatting
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
[MIT](LICENSE)
|
|
128
|
+
|
|
129
|
+
## Disclaimer
|
|
130
|
+
|
|
131
|
+
This tool is for educational and personal use only. Please respect the terms of service of the target website and the intellectual property rights of content creators. Do not use this tool for commercial purposes or to distribute copyrighted content without permission.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,CAAC,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Browser } from "playwright";
|
|
2
|
+
export declare function computePadLength(count: number): number;
|
|
3
|
+
export declare function extractExtension(url: string): string;
|
|
4
|
+
export declare function buildFilePath(outputDir: string, index: number, padLen: number, ext: string): string;
|
|
5
|
+
export declare function extractChapterImages(chapterUrl: string, browser: Browser, outputDir: string): Promise<string[]>;
|
|
6
|
+
//# sourceMappingURL=chapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chapter.d.ts","sourceRoot":"","sources":["../../src/lib/chapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAA0C,MAAM,YAAY,CAAC;AAqBlF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpD;AAED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,MAAM,CAGR;AAmID,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAgCnB"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { CHAPTER_SELECTOR_TIMEOUT, DOWNLOAD_DELAY, IMAGE_CONCURRENCY, IMAGE_LOAD_DELAY, NEXT_PAGE_TIMEOUT, PAD_MIN_LENGTH, PAGE_LOAD_TIMEOUT, pickUserAgent, RETRY_BACKOFF_BASE, RETRY_COUNT, VIEWPORT_MAX_HEIGHT, VIEWPORT_MAX_WIDTH, VIEWPORT_MIN_HEIGHT, VIEWPORT_MIN_WIDTH, } from "./config.js";
|
|
4
|
+
import { rotateHost } from "./download.js";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
import { ensureDir, randInt, sleep } from "./utils.js";
|
|
7
|
+
export function computePadLength(count) {
|
|
8
|
+
return Math.max(PAD_MIN_LENGTH, String(count).length);
|
|
9
|
+
}
|
|
10
|
+
export function extractExtension(url) {
|
|
11
|
+
const match = url.match(/\.(\w{3,4})(?:\?|$)/);
|
|
12
|
+
return match?.[1] ?? "webp";
|
|
13
|
+
}
|
|
14
|
+
export function buildFilePath(outputDir, index, padLen, ext) {
|
|
15
|
+
const padNum = String(index + 1).padStart(padLen, "0");
|
|
16
|
+
return join(outputDir, `${padNum}.${ext}`);
|
|
17
|
+
}
|
|
18
|
+
async function getPageCount(page) {
|
|
19
|
+
return page.evaluate(() => {
|
|
20
|
+
const pageSpan = document.querySelector("#page");
|
|
21
|
+
if (pageSpan?.parentElement) {
|
|
22
|
+
const match = pageSpan.parentElement.textContent?.match(/\/(\d+)/);
|
|
23
|
+
if (match)
|
|
24
|
+
return parseInt(match[1], 10);
|
|
25
|
+
}
|
|
26
|
+
const select = document.querySelector("#pageSelect");
|
|
27
|
+
if (select?.options.length)
|
|
28
|
+
return select.options.length;
|
|
29
|
+
return 0;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function collectImageUrls(page, pageCount) {
|
|
33
|
+
const urls = [];
|
|
34
|
+
let currentUrl = await page.$eval("#mangaFile", (img) => img.src);
|
|
35
|
+
urls.push(currentUrl);
|
|
36
|
+
for (let i = 1; i < pageCount; i++) {
|
|
37
|
+
const prevUrl = currentUrl;
|
|
38
|
+
const nextBtn = await page.$("#next");
|
|
39
|
+
if (!nextBtn)
|
|
40
|
+
break;
|
|
41
|
+
await nextBtn.click();
|
|
42
|
+
await page.waitForFunction((prev) => {
|
|
43
|
+
const img = document.querySelector("#mangaFile");
|
|
44
|
+
return img !== null && img.src !== "" && img.src !== prev;
|
|
45
|
+
}, prevUrl, { timeout: NEXT_PAGE_TIMEOUT });
|
|
46
|
+
await page.waitForTimeout(IMAGE_LOAD_DELAY);
|
|
47
|
+
currentUrl = await page.$eval("#mangaFile", (img) => img.src);
|
|
48
|
+
urls.push(currentUrl);
|
|
49
|
+
}
|
|
50
|
+
return urls;
|
|
51
|
+
}
|
|
52
|
+
async function downloadImage(dlPage, chapterUrl, url, outputDir, imageIndex, padLen) {
|
|
53
|
+
const ext = extractExtension(url);
|
|
54
|
+
const filePath = buildFilePath(outputDir, imageIndex, padLen, ext);
|
|
55
|
+
let downloadUrl = url;
|
|
56
|
+
for (let attempt = 0; attempt < RETRY_COUNT; attempt++) {
|
|
57
|
+
try {
|
|
58
|
+
const response = await dlPage.goto(downloadUrl, {
|
|
59
|
+
referer: chapterUrl,
|
|
60
|
+
waitUntil: "load",
|
|
61
|
+
timeout: PAGE_LOAD_TIMEOUT,
|
|
62
|
+
});
|
|
63
|
+
if (response?.status() !== 200) {
|
|
64
|
+
throw new Error(`HTTP ${response?.status() ?? "no response"}`);
|
|
65
|
+
}
|
|
66
|
+
const contentType = response?.headers()?.["content-type"] ?? "";
|
|
67
|
+
if (contentType && !contentType.startsWith("image/")) {
|
|
68
|
+
throw new Error(`Unexpected content type: ${contentType}`);
|
|
69
|
+
}
|
|
70
|
+
const base64 = await dlPage.evaluate(async () => {
|
|
71
|
+
const res = await fetch(window.location.href);
|
|
72
|
+
if (!res.ok)
|
|
73
|
+
throw new Error(`Fetch failed: ${res.status}`);
|
|
74
|
+
const buf = await res.arrayBuffer();
|
|
75
|
+
const bytes = new Uint8Array(buf);
|
|
76
|
+
let binary = "";
|
|
77
|
+
const step = 0x8000;
|
|
78
|
+
for (let j = 0; j < bytes.length; j += step) {
|
|
79
|
+
binary += String.fromCharCode.apply(null, Array.from(bytes.subarray(j, j + step)));
|
|
80
|
+
}
|
|
81
|
+
return btoa(binary);
|
|
82
|
+
});
|
|
83
|
+
writeFileSync(filePath, Buffer.from(base64, "base64"));
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
if (attempt < RETRY_COUNT - 1) {
|
|
88
|
+
downloadUrl = rotateHost(downloadUrl);
|
|
89
|
+
await sleep(RETRY_BACKOFF_BASE * (attempt + 1));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
logger.warn(`Failed to download after ${RETRY_COUNT} retries: ${url}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
async function downloadImages(context, chapterUrl, outputDir, urls, padLen) {
|
|
97
|
+
const concurrency = Math.min(IMAGE_CONCURRENCY, urls.length);
|
|
98
|
+
const downloadPages = await Promise.all(Array.from({ length: concurrency }, () => context.newPage()));
|
|
99
|
+
try {
|
|
100
|
+
for (let i = 0; i < urls.length; i += concurrency) {
|
|
101
|
+
const batch = urls.slice(i, i + concurrency);
|
|
102
|
+
const results = await Promise.all(batch.map((url, idx) => downloadImage(downloadPages[idx], chapterUrl, url, outputDir, i + idx, padLen)));
|
|
103
|
+
if (results.some((r) => !r))
|
|
104
|
+
return;
|
|
105
|
+
if (DOWNLOAD_DELAY > 0) {
|
|
106
|
+
await sleep(Math.round(DOWNLOAD_DELAY * (0.5 + Math.random())));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
await Promise.all(downloadPages.map((p) => p.close()));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export async function extractChapterImages(chapterUrl, browser, outputDir) {
|
|
115
|
+
ensureDir(outputDir);
|
|
116
|
+
const context = await browser.newContext({
|
|
117
|
+
userAgent: pickUserAgent(),
|
|
118
|
+
viewport: {
|
|
119
|
+
width: randInt(VIEWPORT_MIN_WIDTH, VIEWPORT_MAX_WIDTH),
|
|
120
|
+
height: randInt(VIEWPORT_MIN_HEIGHT, VIEWPORT_MAX_HEIGHT),
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
const page = await context.newPage();
|
|
124
|
+
try {
|
|
125
|
+
await page.goto(chapterUrl, {
|
|
126
|
+
waitUntil: "domcontentloaded",
|
|
127
|
+
timeout: PAGE_LOAD_TIMEOUT,
|
|
128
|
+
});
|
|
129
|
+
await page.waitForSelector("#mangaFile", { timeout: CHAPTER_SELECTOR_TIMEOUT });
|
|
130
|
+
const pageCount = await getPageCount(page);
|
|
131
|
+
if (pageCount <= 0)
|
|
132
|
+
return [];
|
|
133
|
+
const urls = await collectImageUrls(page, pageCount);
|
|
134
|
+
const padLen = computePadLength(urls.length);
|
|
135
|
+
await downloadImages(context, chapterUrl, outputDir, urls, padLen);
|
|
136
|
+
return urls;
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
await context.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=chapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chapter.js","sourceRoot":"","sources":["../../src/lib/chapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const command: import("citty").CommandDef<{
|
|
2
|
+
readonly url: {
|
|
3
|
+
readonly type: "positional";
|
|
4
|
+
readonly description: "漫画 URL / Comic URL";
|
|
5
|
+
readonly required: false;
|
|
6
|
+
};
|
|
7
|
+
readonly section: {
|
|
8
|
+
readonly type: "string";
|
|
9
|
+
readonly description: "指定章节组名称 / Section name to download";
|
|
10
|
+
readonly alias: "s";
|
|
11
|
+
};
|
|
12
|
+
readonly chapter: {
|
|
13
|
+
readonly type: "string";
|
|
14
|
+
readonly description: "指定章节名称 / Chapter name to download";
|
|
15
|
+
readonly alias: "c";
|
|
16
|
+
};
|
|
17
|
+
readonly resume: {
|
|
18
|
+
readonly type: "boolean";
|
|
19
|
+
readonly description: "断点续传 / Resume from previous download";
|
|
20
|
+
readonly alias: "r";
|
|
21
|
+
readonly default: false;
|
|
22
|
+
};
|
|
23
|
+
readonly "dry-run": {
|
|
24
|
+
readonly type: "boolean";
|
|
25
|
+
readonly description: "预览模式 / Preview without downloading";
|
|
26
|
+
readonly alias: "d";
|
|
27
|
+
readonly default: false;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/lib/cli.ts"],"names":[],"mappings":"AA2SA,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmDlB,CAAC"}
|