@a9i5k4/dsh-literature 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -134,7 +134,12 @@ var init_config = __esm({
134
134
  entryMode: "auto",
135
135
  /** Watched folder: new PDFs here are auto-imported into the built-in library. */
136
136
  importDir: "",
137
- watchImport: false
137
+ watchImport: false,
138
+ /** User-configured custom download sources (mirrors / institutional proxies).
139
+ * Each: { id, label, urlTemplate, headers?, enabled, order }. URL templates
140
+ * may use {doi} {arxiv} {isbn} {title} {url}. These are the user's own
141
+ * configuration — the plugin never ships any such source itself. */
142
+ customSources: []
138
143
  };
139
144
  cached = null;
140
145
  }
@@ -1184,11 +1189,31 @@ function looksLikePdf(buffer) {
1184
1189
  if (!buffer || buffer.length < 8) return false;
1185
1190
  return buffer.subarray(0, 5).equals(PDF_MAGIC);
1186
1191
  }
1187
- async function candidates(record, { unpaywallEmail } = {}) {
1192
+ function renderSourceTemplate(template, record) {
1193
+ if (!template || !/^https?:\/\//i.test(String(template))) return "";
1194
+ const vars = {
1195
+ doi: record.doi ? encodeURIComponent(record.doi) : "",
1196
+ arxiv: record.arxiv ? encodeURIComponent(record.arxiv) : "",
1197
+ isbn: record.isbn ? encodeURIComponent(record.isbn) : "",
1198
+ title: record.title ? encodeURIComponent(record.title) : "",
1199
+ url: record.url ? encodeURIComponent(record.url) : ""
1200
+ };
1201
+ for (const [k, v] of Object.entries(vars)) {
1202
+ if (!v && String(template).includes(`{${k}}`)) return "";
1203
+ }
1204
+ for (const [k, v] of Object.entries(vars)) {
1205
+ if (!v && String(template).includes(`{${k}}`)) return "";
1206
+ }
1207
+ let out = String(template);
1208
+ for (const [k, v] of Object.entries(vars)) out = out.split(`{${k}}`).join(v);
1209
+ if (out.includes("{") || !/^https?:\/\//i.test(out)) return "";
1210
+ return out;
1211
+ }
1212
+ async function candidates(record, { unpaywallEmail, customSources = [] } = {}) {
1188
1213
  const out = [];
1189
- const push2 = (url, source, kind = "pdf") => {
1214
+ const push2 = (url, source, kind = "pdf", headers) => {
1190
1215
  if (!url) return;
1191
- out.push({ url: String(url), source, kind });
1216
+ out.push({ url: String(url), source, kind, headers });
1192
1217
  };
1193
1218
  if (record.arxiv) {
1194
1219
  const base = String(record.arxiv).replace(/v\d+$/, "");
@@ -1231,6 +1256,10 @@ async function candidates(record, { unpaywallEmail } = {}) {
1231
1256
  warn("crossref link lookup failed:", e.message);
1232
1257
  }
1233
1258
  }
1259
+ for (const src of [...customSources].filter((x) => x && x.enabled).sort((a, b) => (a.order ?? 100) - (b.order ?? 100))) {
1260
+ const url = renderSourceTemplate(src.urlTemplate, record);
1261
+ if (url) push2(url, src.label || src.id || "custom", "pdf", src.headers);
1262
+ }
1234
1263
  const seen = /* @__PURE__ */ new Set();
1235
1264
  return out.filter((c) => {
1236
1265
  const k = c.url.toLowerCase();
@@ -1256,8 +1285,8 @@ function extractPdfUrlFromHtml(html, baseUrl2) {
1256
1285
  }
1257
1286
  return null;
1258
1287
  }
1259
- async function fetchPdf(record, { timeoutMs = 3e4, unpaywallEmail = "" } = {}) {
1260
- const list = await candidates(record, { unpaywallEmail });
1288
+ async function fetchPdf(record, { timeoutMs = 3e4, unpaywallEmail = "", customSources = [] } = {}) {
1289
+ const list = await candidates(record, { unpaywallEmail, customSources });
1261
1290
  if (!list.length) {
1262
1291
  const bookLike = ["book", "bookSection", "report", "thesis"].includes(record.itemType);
1263
1292
  throw new PdfFailure(
@@ -1273,7 +1302,8 @@ async function fetchPdf(record, { timeoutMs = 3e4, unpaywallEmail = "" } = {}) {
1273
1302
  try {
1274
1303
  const { buffer, contentType, finalUrl } = await httpGetBuffer(cand.url, {
1275
1304
  timeoutMs,
1276
- accept: "application/pdf,*/*;q=0.8"
1305
+ accept: "application/pdf,*/*;q=0.8",
1306
+ ...cand.headers && typeof cand.headers === "object" ? { headers: cand.headers } : {}
1277
1307
  });
1278
1308
  if (looksLikePdf(buffer)) {
1279
1309
  log(`pdf ok: ${cand.url} (${(buffer.length / 1024).toFixed(0)} KB via ${cand.source})`);
@@ -1966,7 +1996,7 @@ async function fetchItemPdf(key) {
1966
1996
  const result = await withRetry(
1967
1997
  (attempt) => {
1968
1998
  if (attempt > 1) emitTask({ id: task.id, state: "running", progress: 10, message: `\u7B2C ${attempt} \u6B21\u5C1D\u8BD5`, updatedAt: Date.now() });
1969
- return fetchPdf(current.record, { timeoutMs: config.fetchTimeoutMs, unpaywallEmail: config.unpaywallEmail });
1999
+ return fetchPdf(current.record, { timeoutMs: config.fetchTimeoutMs, unpaywallEmail: config.unpaywallEmail, customSources: config.customSources });
1970
2000
  },
1971
2001
  { ...config.retry, label: `fetch ${key}` }
1972
2002
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a9i5k4/dsh-literature",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "DSH Literature 文献侧窗:在 DeepSeek Harness 侧边栏识别 DOI/arXiv/标题、抓取元数据与全文、写入本地文献库或导出目录,并提供内置 PDF 阅读器(缩放/翻页/目录/搜索/高亮/笔记)。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -13,6 +13,7 @@
13
13
  "lib",
14
14
  "cordis.patch.yml",
15
15
  "README.md",
16
+ "README_EN.md",
16
17
  "LICENSE"
17
18
  ],
18
19
  "scripts": {