From c2292da442de1f200707fec543dd0a704d71c551 Mon Sep 17 00:00:00 2001 From: lofyer Date: Fri, 7 Aug 2026 21:29:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Z-Library=20=E6=8C=89=E9=9C=80=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E4=B8=8B=E8=BD=BD=E5=9C=B0=E5=9D=80=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E5=B8=83=20v2.1.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 4 ++-- package.json | 2 +- src/_test/sources.test.js | 7 +++++++ src/_test/ui.test.js | 22 ++++++++++++++++++++ src/sources/index.js | 7 ++++++- src/sources/zlib.js | 1 + src/ui/views/browse.js | 44 +++++++++++++++++++++++++++++++++++++-- 7 files changed, 81 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index d980ead..dc5e2c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "peoplelib", - "version": "2.1.3", + "version": "2.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "peoplelib", - "version": "2.1.3", + "version": "2.1.4", "license": "MIT", "dependencies": { "foliate-js": "1.0.1", diff --git a/package.json b/package.json index a488a5d..3397a38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "peoplelib", - "version": "2.1.3", + "version": "2.1.4", "description": "多源开放文献、电子书与本地书库客户端", "main": "main.js", "author": "peoplelib", diff --git a/src/_test/sources.test.js b/src/_test/sources.test.js index 08733a8..e8905b3 100644 --- a/src/_test/sources.test.js +++ b/src/_test/sources.test.js @@ -16,6 +16,11 @@ test('注册表:每个源都实现完整接口', () => { } assert.ok(s.name, `${s.id} 缺 name`); } + assert.strictEqual(list.find((source) => source.id === 'zlib').downloadOnDemand, true); + assert.ok( + list.filter((source) => source.id !== 'zlib').every((source) => source.downloadOnDemand === false), + '只有会消耗下载额度的 Z-Library 应按点击解析' + ); }); test('注册表:开放教材与中英文维基文库已启用', () => { @@ -527,7 +532,9 @@ test('zlib: postId 缺 hash 时详情仍可用', async () => { }); const d = await zlib.detail('123/'); assert.strictEqual(d.title, 'B'); + assert.strictEqual(urls.length, 1, '打开详情不应顺带请求下载接口'); assert.ok(urls[0].includes('/eapi/book/123'), urls[0]); + assert.ok(!urls[0].includes('/file'), '打开详情消耗了下载额度: ' + urls[0]); assert.ok(!urls[0].includes('/eapi/book/123/?'), '缺 hash 时不该留下尾斜杠: ' + urls[0]); } finally { auth.getSession = origSession; diff --git a/src/_test/ui.test.js b/src/_test/ui.test.js index f2f4a54..5c4ddf5 100644 --- a/src/_test/ui.test.js +++ b/src/_test/ui.test.js @@ -218,6 +218,28 @@ test('下载区接入全局任务中心且完成按钮使用高对比绿色底 assert.match(rule[1], /color:\s*#07130b/); }); +test('Z-Library 进入详情不消耗下载额度,点击下载后才解析并开始任务', () => { + const browse = fs.readFileSync(path.join(__dirname, '..', 'ui', 'views', 'browse.js'), 'utf8'); + const openDetail = browse.slice( + browse.indexOf('async function openDetail'), + browse.indexOf('function sourceNameOf') + ); + assert.match(openDetail, /if \(sourceDownloadsOnDemand\(activeSourceId\)\)/); + assert.match(openDetail, /renderOnDemandDownload\(postId, activeSourceId, token\)/); + assert.match(openDetail, /else\s*\{\s*loadDownload\(postId, activeSourceId, token\)/); + + const onDemand = browse.slice( + browse.indexOf('function renderOnDemandDownload'), + browse.indexOf('// 当前条目的元数据') + ); + const clickAt = onDemand.indexOf('btn.onclick = async'); + const resolveAt = onDemand.indexOf('window.api.sources.download', clickAt); + const startAt = onDemand.indexOf('download.click()', resolveAt); + assert.ok(clickAt >= 0 && resolveAt > clickAt, '下载地址在用户点击前被获取'); + assert.ok(startAt > resolveAt, '解析下载地址后没有立即开始真实下载'); + assert.match(onDemand, /每日免费下载额度有限,仅在点击后获取下载地址/); +}); + test('主窗口在设置旁提供持久化明暗主题切换', () => { const html = fs.readFileSync(path.join(__dirname, '..', 'ui', 'index.html'), 'utf8'); const app = fs.readFileSync(path.join(__dirname, '..', 'ui', 'app.js'), 'utf8'); diff --git a/src/sources/index.js b/src/sources/index.js index e1aa8b0..12f19a7 100644 --- a/src/sources/index.js +++ b/src/sources/index.js @@ -36,7 +36,12 @@ const sources = [ const byId = new Map(sources.map((s) => [s.id, s])); function listSources() { - return sources.map((s) => ({ id: s.id, name: s.name, supportsSearch: s.supportsSearch !== false })); + return sources.map((s) => ({ + id: s.id, + name: s.name, + supportsSearch: s.supportsSearch !== false, + downloadOnDemand: s.downloadOnDemand === true + })); } function getSource(id) { diff --git a/src/sources/zlib.js b/src/sources/zlib.js index a508c2a..8c6e60f 100644 --- a/src/sources/zlib.js +++ b/src/sources/zlib.js @@ -270,6 +270,7 @@ module.exports = { id: 'zlib', name: 'Z-Library', supportsSearch: true, + downloadOnDemand: true, // 无关键词时展示热门书目 async list(page) { diff --git a/src/ui/views/browse.js b/src/ui/views/browse.js index bb57871..cba19ae 100644 --- a/src/ui/views/browse.js +++ b/src/ui/views/browse.js @@ -314,7 +314,11 @@ const Browse = (() => { } state.currentDetail = res.data; renderDetail(res.data); - loadDownload(postId, activeSourceId, token); + if (sourceDownloadsOnDemand(activeSourceId)) { + renderOnDemandDownload(postId, activeSourceId, token); + } else { + loadDownload(postId, activeSourceId, token); + } refreshAddButton(); } @@ -323,6 +327,11 @@ const Browse = (() => { return s ? s.name : id; } + function sourceDownloadsOnDemand(id) { + const source = state.sourceList.find((item) => item.id === id); + return !!(source && source.downloadOnDemand); + } + function renderDetail(d) { const tagsHtml = (d.tags || []).map((t) => { const idx = t.indexOf(':'); @@ -391,7 +400,10 @@ const Browse = (() => { $('dlRetry').onclick = () => loadDownload(postId, sourceId, token); return; } - const d = res.data; + renderDownloadResult(box, res.data); + } + + function renderDownloadResult(box, d) { let html = ''; const files = d.files || []; if (files.length) { @@ -412,6 +424,34 @@ const Browse = (() => { box.querySelectorAll('[data-open]').forEach((a) => { a.onclick = (e) => { e.preventDefault(); window.api.openExternal(a.dataset.open); }; }); } + function renderOnDemandDownload(postId, sourceId, token) { + const box = $('downloadBox'); + if (!box) return; + box.innerHTML = `
+ 每日免费下载额度有限,仅在点击后获取下载地址 + +
`; + const btn = $('onDemandDownloadBtn'); + btn.onclick = async () => { + const oldError = box.querySelector('.dl-error'); + if (oldError) oldError.remove(); + btn.disabled = true; + btn.textContent = '获取中...'; + const res = await window.api.sources.download(sourceId, postId); + if (!box.isConnected || token !== state.detailToken) return; + if (!res.ok) { + btn.disabled = false; + btn.textContent = '重试'; + btn.title = res.error || '获取下载地址失败'; + box.insertAdjacentHTML('beforeend', `
获取失败:${escapeHtml(res.error)}
`); + return; + } + renderDownloadResult(box, res.data); + const download = box.querySelector('.dl-btn[data-file]'); + if (download) download.click(); + }; + } + // 当前条目的元数据,供下载时自动建库用 function entryMeta() { const d = state.currentDetail || {};