fix: Z-Library 按需获取下载地址并发布 v2.1.4
构建与发布 / 单测与集成测试 (push) Has been cancelled
构建与发布 / 打包 ${{ matrix.platform }} ${{ matrix.arch }} (arm64, linux, ubuntu-24.04-arm) (push) Has been cancelled
构建与发布 / 打包 ${{ matrix.platform }} ${{ matrix.arch }} (arm64, macos, macos-15) (push) Has been cancelled
构建与发布 / 打包 ${{ matrix.platform }} ${{ matrix.arch }} (x64, linux, ubuntu-24.04) (push) Has been cancelled
构建与发布 / 打包 ${{ matrix.platform }} ${{ matrix.arch }} (x64, windows, windows-2025) (push) Has been cancelled
构建与发布 / 发布 GitHub Release (push) Has been cancelled

This commit is contained in:
lofyer
2026-08-07 21:29:19 +08:00
parent 69c9ce1c2a
commit c2292da442
7 changed files with 81 additions and 6 deletions
+2 -2
View File
@@ -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",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "peoplelib",
"version": "2.1.3",
"version": "2.1.4",
"description": "多源开放文献、电子书与本地书库客户端",
"main": "main.js",
"author": "peoplelib",
+7
View File
@@ -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;
+22
View File
@@ -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');
+6 -1
View File
@@ -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) {
+1
View File
@@ -270,6 +270,7 @@ module.exports = {
id: 'zlib',
name: 'Z-Library',
supportsSearch: true,
downloadOnDemand: true,
// 无关键词时展示热门书目
async list(page) {
+42 -2
View File
@@ -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 = `<div class="dl-files"><div class="dl-file-row">
<span class="dl-file-name">每日免费下载额度有限,仅在点击后获取下载地址</span>
<button class="dl-btn" id="onDemandDownloadBtn">下载</button>
</div></div>`;
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', `<div class="dl-error">获取失败:${escapeHtml(res.error)}</div>`);
return;
}
renderDownloadResult(box, res.data);
const download = box.querySelector('.dl-btn[data-file]');
if (download) download.click();
};
}
// 当前条目的元数据,供下载时自动建库用
function entryMeta() {
const d = state.currentDetail || {};