const { fetchJson, clampPage, isRetryable } = require('./http'); const PAGE_SIZE = 30; const CATALOG_START = '2000-01-01'; const SNAPSHOT_TTL = 5 * 60 * 1000; let catalogSnapshot = null; function toItem(r, server) { const authors = String(r.authors || '').split(';').map((s) => s.trim()).filter(Boolean).slice(0, 3).join(', '); return { postId: r.doi, title: r.title || '(无标题)', cover: '', date: r.date || '', url: `https://www.${server}.org/content/${r.doi}v${r.version || 1}`, subtitle: [authors, r.category].filter(Boolean).join(' · '), _server: server }; } function dateStr(d) { return d.toISOString().slice(0, 10); } async function fetchWindow(server, from, to, cursor, tries = 3) { let last; for (let i = 0; i < tries; i++) { try { const j = await fetchJson(`https://api.biorxiv.org/details/${server}/${from}/${to}/${cursor}`, { retries: 0 }); const total = parseInt(j.messages && j.messages[0] && j.messages[0].total, 10) || 0; return { total, collection: j.collection || [] }; } catch (e) { last = e; // 超时与网络抖动是这里最常见的瞬时故障,必须一并重试 if (!isRetryable(e) || i === tries - 1) throw e; await new Promise((r) => setTimeout(r, 1500 * (i + 1))); } } throw last; } module.exports = { id: 'biorxiv', name: 'bioRxiv 预印本', supportsSearch: false, async list(page) { page = clampPage(page); const server = 'biorxiv'; const to = dateStr(new Date()); if (!catalogSnapshot || catalogSnapshot.to !== to || catalogSnapshot.expiresAt <= Date.now()) { const first = await fetchWindow(server, CATALOG_START, to, 0); catalogSnapshot = { to, total: first.total, expiresAt: Date.now() + SNAPSHOT_TTL }; } const total = catalogSnapshot.total; const end = Math.max(0, total - (page - 1) * PAGE_SIZE); const cursor = Math.max(0, end - PAGE_SIZE); const count = end - cursor; if (!count) return { items: [], maxPage: Math.max(1, Math.ceil(total / PAGE_SIZE)), page }; const { collection } = await fetchWindow(server, CATALOG_START, to, cursor); const items = collection.slice(0, count).reverse().map((r) => toItem(r, server)); return { items, maxPage: Math.max(1, Math.ceil(total / PAGE_SIZE)), page }; }, async search() { throw new Error('bioRxiv 暂不支持搜索,请翻页浏览'); }, async detail(postId) { const j = await fetchJson(`https://api.biorxiv.org/details/biorxiv/${postId}`); const c = (j.collection || [])[0]; if (!c) throw new Error('未找到该预印本'); return { postId, title: c.title, cover: '', authors: String(c.authors || '').split(';').map((s) => s.trim()).filter(Boolean), date: c.date || '', tags: [c.category ? `分类:${c.category}` : '', c.license ? `许可:${c.license}` : ''].filter(Boolean), brief: c.abstract || '', url: `https://www.biorxiv.org/content/${c.doi}v${c.version || 1}`, links: [{ name: 'bioRxiv 页', url: `https://www.biorxiv.org/content/${c.doi}v${c.version || 1}` }] }; }, async download(postId) { const j = await fetchJson(`https://api.biorxiv.org/details/biorxiv/${postId}`); const c = (j.collection || [])[0]; const v = (c && c.version) || 1; return { files: [{ name: `${String(postId).replace(/\//g, '_')}.pdf`, link: `https://www.biorxiv.org/content/${postId}v${v}.full.pdf`, format: 'PDF' }], links: [{ name: 'bioRxiv 页', url: `https://www.biorxiv.org/content/${postId}v${v}` }] }; } };