feat: 支持自定义 Z-Library 镜像并发布 v2.1.5

在 Z-Library 账户设置内增加镜像站点编辑入口,一行一个 HTTPS 地址,
自定义地址经 origin 规范化后优先于内置列表。镜像配置与凭据分开处理,
退出登录后仍保留;移除当前会话依赖的镜像时清理旧会话和 Cookie。

版本更新到 2.1.5,并补充存储、校验、IPC、界面与真实 Electron 保存测试。
This commit is contained in:
lofyer
2026-08-09 15:25:32 +08:00
parent 7f0eea7464
commit 488de94289
13 changed files with 265 additions and 7 deletions
+49 -2
View File
@@ -43,13 +43,40 @@ function requestHeaders(mirror, includeForm = false) {
}
function getMirrors() {
const custom = (auth.read() || {}).customMirrors;
if (Array.isArray(custom) && custom.length) {
const custom = auth.getCustomMirrors();
if (custom.length) {
return custom.concat(DEFAULT_MIRRORS.filter((m) => !custom.includes(m)));
}
return DEFAULT_MIRRORS.slice();
}
const MAX_CUSTOM_MIRRORS = 20;
// 镜像地址来自用户输入,会被直接拼进请求 URL,因此必须按 origin 归一:
// 带路径、查询串或凭据的地址会让后续 apiUrl() 拼出错误甚至泄露凭据的 URL。
function normalizeMirror(value) {
const raw = String(value || '').trim();
if (!raw) return '';
let url;
try { url = new URL(raw); } catch (e) { throw new Error(`镜像地址无效:${raw}`); }
if (url.protocol !== 'https:') throw new Error(`镜像必须使用 HTTPS${raw}`);
if (url.username || url.password) throw new Error(`镜像地址不能包含账号信息:${raw}`);
return url.origin;
}
function normalizeMirrors(list) {
if (!Array.isArray(list)) throw new Error('镜像列表格式无效');
if (list.length > MAX_CUSTOM_MIRRORS) {
throw new Error(`最多只能保存 ${MAX_CUSTOM_MIRRORS} 个镜像`);
}
const out = [];
for (const item of list) {
const origin = normalizeMirror(item);
if (origin && !out.includes(origin)) out.push(origin);
}
return out;
}
function apiUrl(base, path, params = {}) {
const u = new URL(base + path);
for (const [k, v] of Object.entries(params)) {
@@ -379,5 +406,25 @@ module.exports = {
return auth.hasCreds();
},
// 默认域名变动频繁且会整批失效,用户需要能自己补上可用地址而不必等新版本
getMirrorConfig() {
return { custom: auth.getCustomMirrors(), defaults: DEFAULT_MIRRORS.slice() };
},
setMirrors(list) {
const next = normalizeMirrors(list);
const previous = auth.getCustomMirrors();
auth.setCustomMirrors(next);
// 换镜像后旧会话绑定的域名可能已不在列表里,留着会一直优先命中失效地址
const session = auth.getSession();
if (session && session.mirror && !getMirrors().includes(session.mirror)) {
auth.clearSession();
}
for (const mirror of previous) {
if (!next.includes(mirror)) clearCookies(mirror);
}
return { custom: next, defaults: DEFAULT_MIRRORS.slice() };
},
setLoginTransport
};