多页搜索

This commit is contained in:
mtvpls
2026-05-19 19:41:40 +08:00
parent 87c3da046b
commit d7543e74c1
+18 -3
View File
@@ -28,6 +28,7 @@ interface ResolvedLegadoConfig {
const DEFAULT_TIMEOUT_MS = Number(process.env.LEGADO_TIMEOUT_MS || process.env.OPDS_TIMEOUT_MS || 20000); const DEFAULT_TIMEOUT_MS = Number(process.env.LEGADO_TIMEOUT_MS || process.env.OPDS_TIMEOUT_MS || 20000);
const MAX_TEXT_BYTES = Number(process.env.LEGADO_MAX_TEXT_BYTES || 3 * 1024 * 1024); const MAX_TEXT_BYTES = Number(process.env.LEGADO_MAX_TEXT_BYTES || 3 * 1024 * 1024);
const LEGADO_CACHE_VERSION = 'v5'; const LEGADO_CACHE_VERSION = 'v5';
const DEFAULT_LEGADO_SEARCH_PAGES = Number(process.env.LEGADO_SEARCH_PAGES || 5);
const textCache = new Map<string, { expiresAt: number; data: string }>(); const textCache = new Map<string, { expiresAt: number; data: string }>();
const searchCache = new Map<string, { expiresAt: number; data: BookListItem[] }>(); const searchCache = new Map<string, { expiresAt: number; data: BookListItem[] }>();
const detailCache = new Map<string, { expiresAt: number; data: BookDetail }>(); const detailCache = new Map<string, { expiresAt: number; data: BookDetail }>();
@@ -453,19 +454,27 @@ export class LegadoClient {
const cached = searchCache.get(cacheKey); const cached = searchCache.get(cacheKey);
if (cached && cached.expiresAt > Date.now()) return { source, results: cached.data }; if (cached && cached.expiresAt > Date.now()) return { source, results: cached.data };
const targetUrl = buildUrlFromTemplate(rule.searchUrl, source, q, 1);
const html = await fetchText(source, targetUrl);
const results: BookListItem[] = []; const results: BookListItem[] = [];
const seen = new Set<string>();
for (let page = 1; page <= DEFAULT_LEGADO_SEARCH_PAGES; page += 1) {
const targetUrl = buildUrlFromTemplate(rule.searchUrl, source, q, page);
const html = await fetchText(source, targetUrl);
let pageCount = 0;
const json = parseJsonMaybe(html); const json = parseJsonMaybe(html);
if (json && ruleIsJson(rule.ruleSearch.bookList)) { if (json && ruleIsJson(rule.ruleSearch.bookList)) {
const items = selectJsonItems(json, rule.ruleSearch.bookList); const items = selectJsonItems(json, rule.ruleSearch.bookList);
pageCount = items.length;
items.forEach((item) => { items.forEach((item) => {
const detailHref = readJsonRule(item, rule.ruleSearch?.bookUrl, source, targetUrl); const detailHref = readJsonRule(item, rule.ruleSearch?.bookUrl, source, targetUrl);
const title = readJsonRule(item, rule.ruleSearch?.name, source, targetUrl); const title = readJsonRule(item, rule.ruleSearch?.name, source, targetUrl);
if (!title && !detailHref) return; if (!title && !detailHref) return;
const cover = readJsonRule(item, rule.ruleSearch?.coverUrl, source, targetUrl); const cover = readJsonRule(item, rule.ruleSearch?.coverUrl, source, targetUrl);
const itemId = jsonPrimitiveToString(item?.id) || undefined;
const dedupeKey = itemId || detailHref || `${title}|${cover}`;
if (dedupeKey && seen.has(dedupeKey)) return;
if (dedupeKey) seen.add(dedupeKey);
results.push(makeItem(source, { results.push(makeItem(source, {
id: jsonPrimitiveToString(item?.id) || undefined, id: itemId,
title, title,
author: readJsonRule(item, rule.ruleSearch?.author, source, targetUrl), author: readJsonRule(item, rule.ruleSearch?.author, source, targetUrl),
summary: readJsonRule(item, rule.ruleSearch?.intro, source, targetUrl), summary: readJsonRule(item, rule.ruleSearch?.intro, source, targetUrl),
@@ -477,12 +486,16 @@ export class LegadoClient {
} else { } else {
const $ = cheerio.load(html); const $ = cheerio.load(html);
const items = selectElements($, $.root(), rule.ruleSearch.bookList); const items = selectElements($, $.root(), rule.ruleSearch.bookList);
pageCount = items.length;
items.each((_, element) => { items.each((_, element) => {
const root = $(element); const root = $(element);
const detailHref = readValue($, root, rule.ruleSearch?.bookUrl, targetUrl); const detailHref = readValue($, root, rule.ruleSearch?.bookUrl, targetUrl);
const title = readValue($, root, rule.ruleSearch?.name, targetUrl); const title = readValue($, root, rule.ruleSearch?.name, targetUrl);
if (!title && !detailHref) return; if (!title && !detailHref) return;
const cover = readValue($, root, rule.ruleSearch?.coverUrl, targetUrl); const cover = readValue($, root, rule.ruleSearch?.coverUrl, targetUrl);
const dedupeKey = detailHref || `${title}|${cover}`;
if (dedupeKey && seen.has(dedupeKey)) return;
if (dedupeKey) seen.add(dedupeKey);
results.push(makeItem(source, { results.push(makeItem(source, {
title, title,
author: readValue($, root, rule.ruleSearch?.author, targetUrl), author: readValue($, root, rule.ruleSearch?.author, targetUrl),
@@ -493,6 +506,8 @@ export class LegadoClient {
})); }));
}); });
} }
if (pageCount === 0) break;
}
searchCache.set(cacheKey, { data: results, expiresAt: Date.now() + cacheTTL }); searchCache.set(cacheKey, { data: results, expiresAt: Date.now() + cacheTTL });
return { source, results }; return { source, results };
} }