diff --git a/.github/workflows/task.yml b/.github/workflows/task.yml
index 336f0d6..2961378 100644
--- a/.github/workflows/task.yml
+++ b/.github/workflows/task.yml
@@ -13,7 +13,7 @@ on:
- favorites
- leaderboard
PICA_DL_SEARCH_KEYWORDS:
- description: '搜索关键字,多个用 # 隔开'
+ description: '搜索关键字或者漫画ID (多个用 # 隔开)'
type: string
OUTPUT_ZIP:
description: '每个章节打成独立压缩包'
diff --git a/package.json b/package.json
index 3b0c1b7..49a952b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "pica-cli",
"author": "justorez",
- "version": "1.2.0",
+ "version": "1.3.0",
"description": "😉 哔咔漫画下载器",
"packageManager": "pnpm@8.14.3",
"type": "module",
@@ -17,7 +17,7 @@
"start:zip": "node dist/zip.js",
"build": "rimraf dist && rollup --config rollup.config.js",
"test": "vitest",
- "pub": "pnpm build && pnpm publish",
+ "pub": "pnpm build && npm publish",
"check": "tsc --incremental --noEmit",
"lint": "eslint --cache --ext js,cjs,ts, --ignore-path .gitignore .",
"lint:fix": "eslint --cache --fix --ignore-path .gitignore .",
@@ -86,6 +86,7 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
+ "figures": "^6.0.1",
"lint-staged": "^15.2.0",
"mime": "^4.0.1",
"ora": "^8.0.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b09b908..c3578cc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -76,6 +76,9 @@ devDependencies:
eslint-plugin-prettier:
specifier: ^5.1.3
version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.4)
+ figures:
+ specifier: ^6.0.1
+ version: 6.0.1
lint-staged:
specifier: ^15.2.0
version: 15.2.0(supports-color@9.4.0)
@@ -1848,6 +1851,13 @@ packages:
escape-string-regexp: 1.0.5
dev: true
+ /figures@6.0.1:
+ resolution: {integrity: sha512-0oY/olScYD4IhQ8u//gCPA4F3mlTn2dacYmiDm/mbDQvpmLjV4uH+zhsQ5IyXRyvqkvtUkXkNdGvg5OFJTCsuQ==}
+ engines: {node: '>=18'}
+ dependencies:
+ is-unicode-supported: 2.0.0
+ dev: true
+
/file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
diff --git a/readme.md b/readme.md
index e9d93bf..ae0e203 100644
--- a/readme.md
+++ b/readme.md
@@ -4,18 +4,16 @@
😉 哔咔漫画下载器
-
+
- 交互式命令行
- 排行榜:下载当前排行榜的全部漫画
- 收藏夹:下载当前用户收藏夹的全部漫画
-- 关键词搜索:支持多选
+- 搜索:支持关键字和漫画ID (多个用 # 隔开)。访问哔咔电脑端网站,进入漫画详情,地址栏链接里的 `cid` 就是漫画ID
- 自动过滤已下载的章节和图片,不会重复下载
- 如果没有相关环境变量,则启动交互命令界面;若有则直接执行
-- 通过 `pica-zip` 命令分章节批量压缩,配合支持 zip 包的漫画阅读软件使用,比如 [Perfect Viewer](https://play.google.com/store/apps/details?id=com.rookiestudio.perfectviewer)。
- 不限于 `pica-cli` 下载的漫画使用,只要符合 [cmoics/漫画标题/漫画章节/漫画图片](#) 的目录结构即可。
-- 借助 github action 实现飞速下载,支持从 github artifact 和 file.io(无需注册和科学上网)两种方式下载完整漫画包。
- file.io 文件保存两周,单文件最大 2GB,注意链接只能下载**一次**,下载后文件会自动删除
+- 通过 `pica-zip` 命令分章节批量压缩,配合支持 zip 的漫画阅读软件使用,比如 [Perfect Viewer](https://play.google.com/store/apps/details?id=com.rookiestudio.perfectviewer)。不限于 `pica-cli` 下载的漫画,只要符合 [cmoics/漫画标题/漫画章节/漫画图片](#) 的目录结构即可。
+- 借助 github action 实现飞速下载,支持从 github artifact 和 file.io 两种方式下载完整漫画包。file.io 无需注册,无需科学上网,文件保存两周,单文件最大 2GB,注意链接只能下载**一次**,下载后文件会自动删除
如果用的开心,求个 star 支持一下,比心 ~ ❤️
@@ -35,11 +33,11 @@ PICA_ACCOUNT
PICA_PASSWORD
```
-fork 一份本仓库,将上面三个环境变量,设置为仓库密钥:
+fork 一份[本仓库](https://github.com/justorez/pica-cli),将上面三个环境变量,设置为仓库密钥:

-然后点击 Actions,再点击左侧的 `task` 工作流,再点击右侧的 `Run workflow`,输入相关的信息,点击运行即可。
+然后点击 Actions,再点击左侧的 `task` 工作流,再点击右侧的 `Run workflow`,输入相关的信息,点击运行。

@@ -51,7 +49,7 @@ fork 一份本仓库,将上面三个环境变量,设置为仓库密钥:

-如果你想自定义过程,请自行修改 [.github/workflows/task.yaml](.github/workflows/task.yaml)。
+如果你想自定义过程,请自行修改 [.github/workflows/task.yml](.github/workflows/task.yml)。
### 方式二:直接安装
@@ -110,6 +108,7 @@ pnpm dev:zip
## 更新日志
+- 2024/02/01 支持通过漫画ID精确下载
- 2024/01/31 github action 同时将漫画包上传到 file.io
- 2024/01/30 提供 github action 的下载方式
- 2024/01/29 下载完成后,提供命令把漫画按章节批量压缩
diff --git a/resource/1-1.gif b/resource/1-1.gif
new file mode 100644
index 0000000..66dfc39
Binary files /dev/null and b/resource/1-1.gif differ
diff --git a/resource/1.gif b/resource/1.gif
deleted file mode 100644
index cb67631..0000000
Binary files a/resource/1.gif and /dev/null differ
diff --git a/resource/2-6.png b/resource/2-6.png
new file mode 100644
index 0000000..0dbc464
Binary files /dev/null and b/resource/2-6.png differ
diff --git a/scripts/upload.js b/scripts/upload.js
index 6e2aad4..e96c5bb 100644
--- a/scripts/upload.js
+++ b/scripts/upload.js
@@ -6,10 +6,20 @@ import axios from 'axios'
import mime from 'mime'
import AdmZip from 'adm-zip'
import pico from 'picocolors'
+import figures from 'figures'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const MAX_SIZE = 2 * 1024 * 1024 * 1024 // 2GB
+const log = {
+ log: (...msg) => console.log(...msg),
+ info: (...msg) => console.log(pico.cyan('➡️'), ...msg),
+ warn: (...msg) =>
+ console.log(pico.yellow(`${figures.warning} ${msg.join(' ')}`)),
+ error: (...msg) =>
+ console.log(pico.red(`${figures.cross} ${msg.join(' ')}`))
+}
+
// https://file.io/
async function main() {
let root = path.resolve(__dirname, '../comics-zip')
@@ -17,7 +27,7 @@ async function main() {
if (!existsSync(root)) {
root = path.resolve(__dirname, '../comics')
if (!existsSync(root)) {
- console.log(pico.yellow('没有发现已下载的漫画'))
+ log.warn('没有发现已下载的漫画')
return
}
}
@@ -41,11 +51,11 @@ async function main() {
`https://file.io?title=${filename}`,
form
)
- console.log(
+ log.log(
`${pico.cyan(filename)} 已上传到 file.io. 下载地址:${pico.green(data.link)}`
)
} else {
- console.log(pico.yellow(`${filename} 大小超过了 2GB`))
+ log.warn(`${filename} 大小超过了 2GB`)
}
} catch (error) {
console.error(error)
diff --git a/src/index.ts b/src/index.ts
index 3ac35eb..240e1d0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,11 +1,18 @@
import { Pica } from './sdk'
-import { filterEpisodes, filterPictures, loadEnv, mark } from './utils'
+import {
+ log,
+ mark,
+ loadEnv,
+ filterEpisodes,
+ filterPictures,
+ isValidComicId
+} from './utils'
import ora from 'ora'
import { select, checkbox, input } from '@inquirer/prompts'
import ProgressBar from 'progress'
import { Comic } from './types'
import pLimit from 'p-limit'
-import picos from 'picocolors'
+import pico from 'picocolors'
loadEnv()
@@ -42,25 +49,42 @@ async function main() {
if (answer === 'search') {
if (PICA_IS_GITHUB && !PICA_DL_SEARCH_KEYWORDS) {
- console.log(picos.yellow('没有输入搜索关键字'))
+ log.warn('没有输入搜索关键字')
return
}
let searchRes: Comic[] = []
- const keywords =
+ const inputStr =
PICA_DL_SEARCH_KEYWORDS ||
(await input({
- message: '请输入关键字(多个用 # 隔开)',
+ message: '请输入关键字或者漫画ID (多个用 # 隔开)',
transformer: (val) => val.trim()
}))
- if (!keywords) {
- console.log(picos.yellow('没有输入搜索关键字'))
+ if (!inputStr) {
+ log.warn('没有输入搜索关键字')
return
}
- for (const keyword of keywords.split('#')) {
+ const inputKeys = inputStr.split('#')
+
+ // 根据漫画ID查询
+ const bookIds = inputKeys.filter((k: string) => isValidComicId(k))
+ for (const id of bookIds) {
+ try {
+ const info = await pica.comicInfo(id)
+ info.title = info.title.trim()
+ comics.push(info)
+ log.info(`${info.title} 已加入下载队列`)
+ } catch (error) {
+ log.error(`无效漫画ID ${id}`)
+ }
+ }
+
+ // 根据关键字查询
+ const keywords = inputKeys.filter((k: string) => !isValidComicId(k))
+ for (const keyword of keywords) {
spinner.start(`正在搜索 ${keyword}`)
searchRes = await pica.searchAll(keyword)
spinner.stop()
@@ -91,9 +115,7 @@ async function main() {
episodes = filterEpisodes(episodes, cid)
spinner.stop()
- console.log(
- `${picos.cyan('➡️')} ${title} 查询到 ${episodes.length} 个章节`
- )
+ log.info(`${title} 查询到 ${episodes.length} 个章节`)
for (const ep of episodes) {
spinner.start(`正在获取章节 ${ep.title} 的图片信息`)
@@ -102,7 +124,7 @@ async function main() {
spinner.stop()
const bar = new ProgressBar(
- `${picos.cyan('➡️')} ${title} ${ep.title} [:bar] :current/:total`,
+ `${pico.cyan('➡️')} ${title} ${ep.title} [:bar] :current/:total`,
{
incomplete: ' ',
width: 20,
@@ -129,12 +151,13 @@ async function main() {
mark(cid, ep.id)
}
- console.log(picos.green(`✓ ${picos.bold(title)} 下载完成`))
+ log.success(`${title} 下载完成`)
}
}
process.on('uncaughtException', (err) => {
- console.log(`\n${picos.red(err.message)}`)
+ console.log('\n')
+ log.error(`${err.message}`)
process.exit(0)
})
diff --git a/src/sdk.ts b/src/sdk.ts
index 697a008..b87a71d 100644
--- a/src/sdk.ts
+++ b/src/sdk.ts
@@ -148,7 +148,7 @@ export class Pica {
*/
async comicInfo(bookId: string) {
const url = `comics/${bookId}`
- const res = await this.request('get', url)
+ const res = await this.request('get', url)
return res.comic
}
diff --git a/src/utils.ts b/src/utils.ts
index 6d422f0..c3bdaf0 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -3,9 +3,15 @@ import dotenv from 'dotenv'
import fs from 'node:fs'
import { Episode, Picture } from './types'
import Debug from 'debug'
+import pico from 'picocolors'
+import figures from 'figures'
export const debug = Debug('pica')
+export function isValidComicId(cid: string) {
+ return /^[0-9a-zA-Z]{24}$/.test(cid)
+}
+
/**
* 标记某章节已下载完成,并记录到本地临时文件
*/
@@ -81,3 +87,20 @@ export function normalizeName(s: string) {
.replace(/>/g, '>')
.replace(/:/g, '-')
}
+
+type LogMsg = string[] | number[]
+// √ ✕
+export const log = {
+ log: (...msg: LogMsg) => console.log(...msg),
+ info: (...msg: LogMsg) => console.log(pico.cyan('➡️'), ...msg),
+ warn: (...msg: LogMsg) =>
+ console.log(pico.yellow(`${figures.warning} ${msg.join(' ')}`)),
+ error: (...msg: LogMsg) =>
+ console.log(pico.red(`${figures.cross} ${msg.join(' ')}`)),
+ success: (...msg: LogMsg) =>
+ console.log(pico.green(`${figures.tick} ${msg.join(' ')}`))
+}
+
+export function sleep(s: number) {
+ return new Promise((r) => setTimeout(r, s * 1000))
+}
diff --git a/src/zip.ts b/src/zip.ts
index d1e45b4..05d522d 100644
--- a/src/zip.ts
+++ b/src/zip.ts
@@ -1,15 +1,16 @@
import AdmZip from 'adm-zip'
import path from 'node:path'
import fs from 'node:fs'
-import pico from 'picocolors'
import ProgressBar from 'progress'
+import pico from 'picocolors'
+import { log } from './utils'
function main() {
const root = path.resolve(process.cwd(), 'comics')
const dest = path.resolve(process.cwd(), 'comics-zip')
if (!fs.existsSync(root)) {
- console.log(pico.yellow('没有发现已下载的漫画'))
+ log.warn('没有发现已下载的漫画')
return
}
@@ -18,11 +19,11 @@ function main() {
})
if (comics.length === 0) {
- console.log(pico.yellow('没有发现已下载的漫画'))
+ log.warn('没有发现已下载的漫画')
return
}
- console.log(
+ log.info(
`${pico.cyan(comics.length)} 本漫画等待打包:${pico.cyan(comics.join(', '))}`
)
@@ -50,8 +51,10 @@ function main() {
bar.tick()
}
- console.log(pico.green(`✓ ${comic} 打包完成`))
+ // log.success(`${comic} 打包完成`)
}
+
+ log.success(`打包完成`)
}
main()