@ncukondo/reference-manager 0.27.1 → 0.28.0
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/dist/chunks/{SearchableMultiSelect-CixI0oIE.js → SearchableMultiSelect-LIMpJm8A.js} +2 -2
- package/dist/chunks/{SearchableMultiSelect-CixI0oIE.js.map → SearchableMultiSelect-LIMpJm8A.js.map} +1 -1
- package/dist/chunks/{action-menu-DKfv85rC.js → action-menu-CdJjcmdk.js} +3 -3
- package/dist/chunks/{action-menu-DKfv85rC.js.map → action-menu-CdJjcmdk.js.map} +1 -1
- package/dist/chunks/{checker-B-SL7krG.js → checker-CP8vSC-S.js} +5 -5
- package/dist/chunks/{checker-B-SL7krG.js.map → checker-CP8vSC-S.js.map} +1 -1
- package/dist/chunks/{crossref-client-D6g3pLUI.js → crossref-client-C1HVx8LA.js} +2 -2
- package/dist/chunks/{crossref-client-D6g3pLUI.js.map → crossref-client-C1HVx8LA.js.map} +1 -1
- package/dist/chunks/{file-watcher-Dlx0PolG.js → file-watcher-CWHg1yol.js} +48 -2
- package/dist/chunks/file-watcher-CWHg1yol.js.map +1 -0
- package/dist/chunks/{fix-interaction-CAIeqdx5.js → fix-interaction-BR3VVyHq.js} +5 -5
- package/dist/chunks/{fix-interaction-CAIeqdx5.js.map → fix-interaction-BR3VVyHq.js.map} +1 -1
- package/dist/chunks/{index-DS3BUDRy.js → index-BP_TPa_d.js} +4 -4
- package/dist/chunks/{index-DS3BUDRy.js.map → index-BP_TPa_d.js.map} +1 -1
- package/dist/chunks/{index-B1pKaejY.js → index-C9Gc8dcm.js} +98 -43
- package/dist/chunks/index-C9Gc8dcm.js.map +1 -0
- package/dist/chunks/{index-D6flikkH.js → index-CHe855EM.js} +3 -3
- package/dist/chunks/index-CHe855EM.js.map +1 -0
- package/dist/chunks/{index-DUpYvm-W.js → index-T-edKSzd.js} +364 -50
- package/dist/chunks/index-T-edKSzd.js.map +1 -0
- package/dist/chunks/{loader-B-fte1uv.js → loader-B6sytmQd.js} +2 -2
- package/dist/chunks/{loader-B-fte1uv.js.map → loader-B6sytmQd.js.map} +1 -1
- package/dist/chunks/{metadata-comparator-C5zfoYdK.js → metadata-comparator-DvqzC5tX.js} +5 -3
- package/dist/chunks/metadata-comparator-DvqzC5tX.js.map +1 -0
- package/dist/chunks/{pubmed-client-mGn5jDIc.js → pubmed-client-DEo6eaH7.js} +2 -2
- package/dist/chunks/{pubmed-client-mGn5jDIc.js.map → pubmed-client-DEo6eaH7.js.map} +1 -1
- package/dist/chunks/{reference-select-BpTCTnrO.js → reference-select-B54Upu7p.js} +4 -4
- package/dist/chunks/{reference-select-BpTCTnrO.js.map → reference-select-B54Upu7p.js.map} +1 -1
- package/dist/chunks/{style-select-RS-lt5lQ.js → style-select-BUwM-Azt.js} +3 -3
- package/dist/chunks/{style-select-RS-lt5lQ.js.map → style-select-BUwM-Azt.js.map} +1 -1
- package/dist/cli/commands/fulltext.d.ts.map +1 -1
- package/dist/cli/completion.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/core/csl-json/types.d.ts +67 -0
- package/dist/core/csl-json/types.d.ts.map +1 -1
- package/dist/features/attachments/types.d.ts +8 -16
- package/dist/features/attachments/types.d.ts.map +1 -1
- package/dist/features/check/metadata-similarity.d.ts +1 -0
- package/dist/features/check/metadata-similarity.d.ts.map +1 -1
- package/dist/features/duplicate/detector.d.ts.map +1 -1
- package/dist/features/duplicate/types.d.ts +2 -1
- package/dist/features/duplicate/types.d.ts.map +1 -1
- package/dist/features/format/citation-fallback.d.ts.map +1 -1
- package/dist/features/format/pretty.d.ts.map +1 -1
- package/dist/features/format/resource-indicators.d.ts.map +1 -1
- package/dist/features/import/cache.d.ts +7 -1
- package/dist/features/import/cache.d.ts.map +1 -1
- package/dist/features/import/detector.d.ts +15 -2
- package/dist/features/import/detector.d.ts.map +1 -1
- package/dist/features/import/fetcher.d.ts +7 -0
- package/dist/features/import/fetcher.d.ts.map +1 -1
- package/dist/features/import/importer.d.ts.map +1 -1
- package/dist/features/import/normalizer.d.ts +16 -0
- package/dist/features/import/normalizer.d.ts.map +1 -1
- package/dist/features/import/rate-limiter.d.ts +1 -1
- package/dist/features/import/rate-limiter.d.ts.map +1 -1
- package/dist/features/operations/attachments/get.d.ts.map +1 -1
- package/dist/features/operations/attachments/list.d.ts.map +1 -1
- package/dist/features/operations/attachments/open.d.ts.map +1 -1
- package/dist/features/operations/fulltext/convert.d.ts.map +1 -1
- package/dist/features/operations/fulltext/detach.d.ts.map +1 -1
- package/dist/features/operations/fulltext/discover.d.ts.map +1 -1
- package/dist/features/operations/fulltext/fetch.d.ts +27 -4
- package/dist/features/operations/fulltext/fetch.d.ts.map +1 -1
- package/dist/features/operations/fulltext/get.d.ts.map +1 -1
- package/dist/features/operations/fulltext/open.d.ts.map +1 -1
- package/dist/features/operations/remove.d.ts.map +1 -1
- package/dist/features/search/matcher.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/mcp/tools/fulltext.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -2
- package/dist/chunks/file-watcher-Dlx0PolG.js.map +0 -1
- package/dist/chunks/index-B1pKaejY.js.map +0 -1
- package/dist/chunks/index-D6flikkH.js.map +0 -1
- package/dist/chunks/index-DUpYvm-W.js.map +0 -1
- package/dist/chunks/metadata-comparator-C5zfoYdK.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { h as CslItemSchema, g as detectDuplicate, m as generateId, a as sortOrderSchema, b as sortFieldSchema, p as pickDefined, t as tokenize, s as search$1, f as sortResults, y as searchSortFieldSchema, L as Library, F as FileWatcher } from "./file-watcher-
|
|
2
|
+
import { h as CslItemSchema, g as detectDuplicate, m as generateId, a as sortOrderSchema, b as sortFieldSchema, p as pickDefined, t as tokenize, s as search$1, f as sortResults, y as searchSortFieldSchema, L as Library, F as FileWatcher } from "./file-watcher-CWHg1yol.js";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { mkdtempSync, writeFileSync, existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { Cite, plugins } from "@citation-js/core";
|
|
@@ -770,12 +770,14 @@ async function checkUnpaywallDetailed(doi, email) {
|
|
|
770
770
|
const DEFAULT_SOURCE_ORDER = ["pmc", "arxiv", "unpaywall", "core"];
|
|
771
771
|
function checkArxivSource(article) {
|
|
772
772
|
if (!article.arxivId)
|
|
773
|
-
return
|
|
773
|
+
return { skipped: "no arXiv ID available" };
|
|
774
774
|
return checkArxiv(article.arxivId);
|
|
775
775
|
}
|
|
776
776
|
async function checkCoreSource(article, config) {
|
|
777
|
-
if (!config.coreApiKey
|
|
778
|
-
return
|
|
777
|
+
if (!config.coreApiKey)
|
|
778
|
+
return { skipped: "coreApiKey not configured" };
|
|
779
|
+
if (!article.doi)
|
|
780
|
+
return { skipped: "no DOI available" };
|
|
779
781
|
return await checkCore(article.doi, config.coreApiKey);
|
|
780
782
|
}
|
|
781
783
|
const sourceCheckers = {
|
|
@@ -819,9 +821,16 @@ async function enrichArticleIds(article, config) {
|
|
|
819
821
|
return { enriched: article, discoveredIds };
|
|
820
822
|
}
|
|
821
823
|
async function checkUnpaywallSource(enriched, config, state) {
|
|
822
|
-
if (!config.unpaywallEmail
|
|
824
|
+
if (!config.unpaywallEmail) {
|
|
825
|
+
state.skipped.push({ source: "unpaywall", reason: "unpaywallEmail not configured" });
|
|
823
826
|
return;
|
|
827
|
+
}
|
|
828
|
+
if (!enriched.doi) {
|
|
829
|
+
state.skipped.push({ source: "unpaywall", reason: "no DOI available" });
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
824
832
|
state.sourcesChecked++;
|
|
833
|
+
state.checkedSources.push("unpaywall");
|
|
825
834
|
try {
|
|
826
835
|
const detailed = await checkUnpaywallDetailed(enriched.doi, config.unpaywallEmail);
|
|
827
836
|
if (!detailed)
|
|
@@ -835,14 +844,17 @@ async function checkUnpaywallSource(enriched, config, state) {
|
|
|
835
844
|
}
|
|
836
845
|
}
|
|
837
846
|
async function checkPmcSourceWithIds(enriched, state, discoveredIds) {
|
|
838
|
-
if (!enriched.pmid && !enriched.pmcid)
|
|
847
|
+
if (!enriched.pmid && !enriched.pmcid) {
|
|
848
|
+
state.skipped.push({ source: "pmc", reason: "no PMCID or PMID available" });
|
|
839
849
|
return;
|
|
850
|
+
}
|
|
840
851
|
const ids = {};
|
|
841
852
|
if (enriched.pmid)
|
|
842
853
|
ids.pmid = enriched.pmid;
|
|
843
854
|
if (enriched.pmcid)
|
|
844
855
|
ids.pmcid = enriched.pmcid;
|
|
845
856
|
state.sourcesChecked++;
|
|
857
|
+
state.checkedSources.push("pmc");
|
|
846
858
|
try {
|
|
847
859
|
const result = await checkPmc(ids);
|
|
848
860
|
if (!result)
|
|
@@ -860,9 +872,12 @@ async function checkGenericSource(source, enriched, config, state) {
|
|
|
860
872
|
if (!checker)
|
|
861
873
|
return;
|
|
862
874
|
const result = await runSourceChecker(checker, enriched, config);
|
|
863
|
-
if (result.
|
|
875
|
+
if (result.skipReason) {
|
|
876
|
+
state.skipped.push({ source, reason: result.skipReason });
|
|
864
877
|
return;
|
|
878
|
+
}
|
|
865
879
|
state.sourcesChecked++;
|
|
880
|
+
state.checkedSources.push(source);
|
|
866
881
|
if (result.error) {
|
|
867
882
|
state.errors.push({ source, error: result.error });
|
|
868
883
|
} else if (result.locations) {
|
|
@@ -873,6 +888,7 @@ async function lazyPmcCheck(enriched, state, discoveredIds) {
|
|
|
873
888
|
if (!state.unpaywallPmcid || enriched.pmcid)
|
|
874
889
|
return;
|
|
875
890
|
discoveredIds.pmcid = discoveredIds.pmcid ?? state.unpaywallPmcid;
|
|
891
|
+
state.checkedSources.push("pmc-lazy");
|
|
876
892
|
try {
|
|
877
893
|
const pmcResult = await checkPmc({ pmcid: state.unpaywallPmcid });
|
|
878
894
|
if (pmcResult) {
|
|
@@ -884,7 +900,13 @@ async function lazyPmcCheck(enriched, state, discoveredIds) {
|
|
|
884
900
|
}
|
|
885
901
|
async function discoverOA(article, config) {
|
|
886
902
|
const { enriched, discoveredIds } = await enrichArticleIds(article, config);
|
|
887
|
-
const state = {
|
|
903
|
+
const state = {
|
|
904
|
+
locations: [],
|
|
905
|
+
errors: [],
|
|
906
|
+
skipped: [],
|
|
907
|
+
checkedSources: [],
|
|
908
|
+
sourcesChecked: 0
|
|
909
|
+
};
|
|
888
910
|
const sourceOrder = config.preferSources.length > 0 ? config.preferSources : DEFAULT_SOURCE_ORDER;
|
|
889
911
|
for (const source of sourceOrder) {
|
|
890
912
|
if (source === "unpaywall") {
|
|
@@ -897,16 +919,24 @@ async function discoverOA(article, config) {
|
|
|
897
919
|
}
|
|
898
920
|
await lazyPmcCheck(enriched, state, discoveredIds);
|
|
899
921
|
const oaStatus = determineOAStatus(state.locations, state.errors, state.sourcesChecked);
|
|
900
|
-
return {
|
|
922
|
+
return {
|
|
923
|
+
oaStatus,
|
|
924
|
+
locations: state.locations,
|
|
925
|
+
errors: state.errors,
|
|
926
|
+
skipped: state.skipped,
|
|
927
|
+
checkedSources: state.checkedSources,
|
|
928
|
+
discoveredIds
|
|
929
|
+
};
|
|
901
930
|
}
|
|
902
931
|
async function runSourceChecker(checker, article, config) {
|
|
903
932
|
try {
|
|
904
933
|
const result = await checker(article, config);
|
|
905
|
-
if (result ===
|
|
906
|
-
return {
|
|
907
|
-
|
|
934
|
+
if (result !== null && typeof result === "object" && "skipped" in result) {
|
|
935
|
+
return { skipReason: result.skipped };
|
|
936
|
+
}
|
|
937
|
+
return { locations: result ?? [] };
|
|
908
938
|
} catch (err) {
|
|
909
|
-
return {
|
|
939
|
+
return { error: String(err) };
|
|
910
940
|
}
|
|
911
941
|
}
|
|
912
942
|
const NON_RETRYABLE_STATUSES = /* @__PURE__ */ new Set([400, 401, 403, 404, 405, 410]);
|
|
@@ -10240,6 +10270,7 @@ async function fulltextDiscover(library, options) {
|
|
|
10240
10270
|
if (doi) article.doi = doi;
|
|
10241
10271
|
if (pmid) article.pmid = pmid;
|
|
10242
10272
|
if (pmcid) article.pmcid = pmcid;
|
|
10273
|
+
if (item.custom?.arxiv_id) article.arxivId = item.custom.arxiv_id;
|
|
10243
10274
|
const config = {
|
|
10244
10275
|
unpaywallEmail: fulltextConfig.sources.unpaywallEmail ?? "",
|
|
10245
10276
|
coreApiKey: fulltextConfig.sources.coreApiKey ?? "",
|
|
@@ -10275,6 +10306,7 @@ function buildDiscoveryArticle(item) {
|
|
|
10275
10306
|
if (item.DOI) article.doi = item.DOI;
|
|
10276
10307
|
if (item.PMID) article.pmid = item.PMID;
|
|
10277
10308
|
if (item.PMCID) article.pmcid = item.PMCID;
|
|
10309
|
+
if (item.custom?.arxiv_id) article.arxivId = item.custom.arxiv_id;
|
|
10278
10310
|
return article;
|
|
10279
10311
|
}
|
|
10280
10312
|
function buildDiscoveryConfig(fulltextConfig) {
|
|
@@ -10287,13 +10319,22 @@ function buildDiscoveryConfig(fulltextConfig) {
|
|
|
10287
10319
|
if (fulltextConfig.sources.ncbiTool) config.ncbiTool = fulltextConfig.sources.ncbiTool;
|
|
10288
10320
|
return config;
|
|
10289
10321
|
}
|
|
10290
|
-
async function tryDownloadPdf(locations, tempDir, ctx) {
|
|
10322
|
+
async function tryDownloadPdf(locations, tempDir, ctx, attempts) {
|
|
10291
10323
|
const pdfLocations = locations.filter((loc) => loc.urlType === "pdf");
|
|
10292
10324
|
if (pdfLocations.length === 0) return { attached: false, source: "" };
|
|
10293
10325
|
const pdfPath = join(tempDir, "fulltext.pdf");
|
|
10294
10326
|
for (const pdfLocation of pdfLocations) {
|
|
10295
10327
|
const pdfResult = await downloadPdf(pdfLocation.url, pdfPath);
|
|
10296
|
-
if (!pdfResult.success)
|
|
10328
|
+
if (!pdfResult.success) {
|
|
10329
|
+
attempts.push({
|
|
10330
|
+
source: pdfLocation.source,
|
|
10331
|
+
phase: "download",
|
|
10332
|
+
url: pdfLocation.url,
|
|
10333
|
+
fileType: "pdf",
|
|
10334
|
+
error: pdfResult.error ?? "Download failed"
|
|
10335
|
+
});
|
|
10336
|
+
continue;
|
|
10337
|
+
}
|
|
10297
10338
|
const attachResult = await fulltextAttach(ctx.library, {
|
|
10298
10339
|
identifier: ctx.identifier,
|
|
10299
10340
|
idType: ctx.idType,
|
|
@@ -10306,16 +10347,39 @@ async function tryDownloadPdf(locations, tempDir, ctx) {
|
|
|
10306
10347
|
if (attachResult.success) {
|
|
10307
10348
|
return { attached: true, source: pdfLocation.source };
|
|
10308
10349
|
}
|
|
10350
|
+
attempts.push({
|
|
10351
|
+
source: pdfLocation.source,
|
|
10352
|
+
phase: "attach",
|
|
10353
|
+
url: pdfLocation.url,
|
|
10354
|
+
fileType: "pdf",
|
|
10355
|
+
error: "Failed to attach file"
|
|
10356
|
+
});
|
|
10309
10357
|
}
|
|
10310
10358
|
return { attached: false, source: pdfLocations[0]?.source ?? "" };
|
|
10311
10359
|
}
|
|
10312
|
-
async function tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx) {
|
|
10360
|
+
async function tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx, attempts) {
|
|
10313
10361
|
const xmlPath = join(tempDir, "fulltext.xml");
|
|
10314
10362
|
const xmlResult = await downloadPmcXml(pmcid, xmlPath);
|
|
10315
|
-
if (!xmlResult.success)
|
|
10363
|
+
if (!xmlResult.success) {
|
|
10364
|
+
attempts.push({
|
|
10365
|
+
source: "pmc",
|
|
10366
|
+
phase: "download",
|
|
10367
|
+
fileType: "xml",
|
|
10368
|
+
error: xmlResult.error ?? "Download failed"
|
|
10369
|
+
});
|
|
10370
|
+
return false;
|
|
10371
|
+
}
|
|
10316
10372
|
const mdPath = join(tempDir, "fulltext.md");
|
|
10317
10373
|
const convertResult2 = await convertPmcXmlToMarkdown(xmlPath, mdPath);
|
|
10318
|
-
if (!convertResult2.success)
|
|
10374
|
+
if (!convertResult2.success) {
|
|
10375
|
+
attempts.push({
|
|
10376
|
+
source: "pmc",
|
|
10377
|
+
phase: "convert",
|
|
10378
|
+
fileType: "xml",
|
|
10379
|
+
error: convertResult2.error ?? "Conversion failed"
|
|
10380
|
+
});
|
|
10381
|
+
return false;
|
|
10382
|
+
}
|
|
10319
10383
|
const attachResult = await fulltextAttach(ctx.library, {
|
|
10320
10384
|
identifier: ctx.identifier,
|
|
10321
10385
|
idType: ctx.idType,
|
|
@@ -10325,19 +10389,43 @@ async function tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx) {
|
|
|
10325
10389
|
move: true,
|
|
10326
10390
|
fulltextDirectory: ctx.fulltextDirectory
|
|
10327
10391
|
});
|
|
10392
|
+
if (!attachResult.success) {
|
|
10393
|
+
attempts.push({
|
|
10394
|
+
source: "pmc",
|
|
10395
|
+
phase: "attach",
|
|
10396
|
+
fileType: "markdown",
|
|
10397
|
+
error: "Failed to attach file"
|
|
10398
|
+
});
|
|
10399
|
+
}
|
|
10328
10400
|
return attachResult.success;
|
|
10329
10401
|
}
|
|
10330
10402
|
function extractArxivId(url) {
|
|
10331
10403
|
const match = url.match(/arxiv\.org\/(?:abs|html|pdf)\/(\d{4}\.\d{4,5}(?:v\d+)?)/);
|
|
10332
10404
|
return match?.[1];
|
|
10333
10405
|
}
|
|
10334
|
-
async function tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx) {
|
|
10406
|
+
async function tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx, attempts) {
|
|
10335
10407
|
const htmlPath = join(tempDir, "fulltext.html");
|
|
10336
10408
|
const htmlResult = await downloadArxivHtml(arxivId, htmlPath);
|
|
10337
|
-
if (!htmlResult.success)
|
|
10409
|
+
if (!htmlResult.success) {
|
|
10410
|
+
attempts.push({
|
|
10411
|
+
source: "arxiv",
|
|
10412
|
+
phase: "download",
|
|
10413
|
+
fileType: "html",
|
|
10414
|
+
error: htmlResult.error ?? "Download failed"
|
|
10415
|
+
});
|
|
10416
|
+
return false;
|
|
10417
|
+
}
|
|
10338
10418
|
const mdPath = join(tempDir, "fulltext.md");
|
|
10339
10419
|
const convertResult2 = await convertArxivHtmlToMarkdown(htmlPath, mdPath);
|
|
10340
|
-
if (!convertResult2.success)
|
|
10420
|
+
if (!convertResult2.success) {
|
|
10421
|
+
attempts.push({
|
|
10422
|
+
source: "arxiv",
|
|
10423
|
+
phase: "convert",
|
|
10424
|
+
fileType: "html",
|
|
10425
|
+
error: convertResult2.error ?? "Conversion failed"
|
|
10426
|
+
});
|
|
10427
|
+
return false;
|
|
10428
|
+
}
|
|
10341
10429
|
const attachResult = await fulltextAttach(ctx.library, {
|
|
10342
10430
|
identifier: ctx.identifier,
|
|
10343
10431
|
idType: ctx.idType,
|
|
@@ -10347,12 +10435,36 @@ async function tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx) {
|
|
|
10347
10435
|
move: true,
|
|
10348
10436
|
fulltextDirectory: ctx.fulltextDirectory
|
|
10349
10437
|
});
|
|
10438
|
+
if (!attachResult.success) {
|
|
10439
|
+
attempts.push({
|
|
10440
|
+
source: "arxiv",
|
|
10441
|
+
phase: "attach",
|
|
10442
|
+
fileType: "markdown",
|
|
10443
|
+
error: "Failed to attach file"
|
|
10444
|
+
});
|
|
10445
|
+
}
|
|
10350
10446
|
return attachResult.success;
|
|
10351
10447
|
}
|
|
10352
10448
|
async function checkExistingFulltext(library, identifier, idType, fulltextDirectory) {
|
|
10353
10449
|
const existing = await fulltextGet(library, { identifier, idType, fulltextDirectory });
|
|
10354
10450
|
return existing.success && existing.paths !== void 0;
|
|
10355
10451
|
}
|
|
10452
|
+
function buildHintUrls(item) {
|
|
10453
|
+
const urls = [];
|
|
10454
|
+
if (item.DOI) urls.push(`https://doi.org/${item.DOI}`);
|
|
10455
|
+
if (item.PMID) urls.push(`https://pubmed.ncbi.nlm.nih.gov/${item.PMID}/`);
|
|
10456
|
+
return urls;
|
|
10457
|
+
}
|
|
10458
|
+
function formatHint(prefix, urls) {
|
|
10459
|
+
if (urls.length === 0) return prefix;
|
|
10460
|
+
if (urls.length === 1) return `${prefix}: ${urls[0]}`;
|
|
10461
|
+
return `${prefix}:
|
|
10462
|
+
${urls.map((u) => ` ${u}`).join("\n")}`;
|
|
10463
|
+
}
|
|
10464
|
+
function buildNoSourcesHint(item) {
|
|
10465
|
+
const urls = buildHintUrls(item);
|
|
10466
|
+
return urls.length > 0 ? formatHint("open to download manually", urls) : void 0;
|
|
10467
|
+
}
|
|
10356
10468
|
async function fulltextFetch(library, options) {
|
|
10357
10469
|
const { identifier, idType = "id", fulltextConfig, fulltextDirectory, source, force } = options;
|
|
10358
10470
|
const item = await library.find(identifier, { idType });
|
|
@@ -10375,12 +10487,22 @@ async function fulltextFetch(library, options) {
|
|
|
10375
10487
|
buildDiscoveryArticle(item),
|
|
10376
10488
|
buildDiscoveryConfig(fulltextConfig)
|
|
10377
10489
|
);
|
|
10490
|
+
const discoveryErrors = discovery.errors.length > 0 ? discovery.errors : void 0;
|
|
10491
|
+
const skipped = discovery.skipped.length > 0 ? discovery.skipped : void 0;
|
|
10492
|
+
const checkedSources = discovery.checkedSources.length > 0 ? discovery.checkedSources : void 0;
|
|
10378
10493
|
let locations = discovery.locations;
|
|
10379
10494
|
if (source) {
|
|
10380
10495
|
locations = locations.filter((loc) => loc.source === source);
|
|
10381
10496
|
}
|
|
10382
10497
|
if (locations.length === 0) {
|
|
10383
|
-
return {
|
|
10498
|
+
return {
|
|
10499
|
+
success: false,
|
|
10500
|
+
error: `No OA sources found for ${identifier}`,
|
|
10501
|
+
discoveryErrors,
|
|
10502
|
+
checkedSources,
|
|
10503
|
+
skipped,
|
|
10504
|
+
hint: buildNoSourcesHint(item)
|
|
10505
|
+
};
|
|
10384
10506
|
}
|
|
10385
10507
|
const effectivePmcid = item.PMCID ?? discovery.discoveredIds?.pmcid ?? extractPmcidFromLocations(locations);
|
|
10386
10508
|
const tempDir = await mkdtemp(join(tmpdir(), "ref-fulltext-"));
|
|
@@ -10392,49 +10514,61 @@ async function fulltextFetch(library, options) {
|
|
|
10392
10514
|
force: force ?? false
|
|
10393
10515
|
};
|
|
10394
10516
|
try {
|
|
10395
|
-
|
|
10517
|
+
const result = await downloadAndAttach(
|
|
10518
|
+
locations,
|
|
10519
|
+
effectivePmcid,
|
|
10520
|
+
tempDir,
|
|
10521
|
+
ctx,
|
|
10522
|
+
item.id,
|
|
10523
|
+
identifier
|
|
10524
|
+
);
|
|
10525
|
+
return { ...result, discoveryErrors, checkedSources, skipped };
|
|
10396
10526
|
} finally {
|
|
10397
10527
|
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
10398
10528
|
});
|
|
10399
10529
|
}
|
|
10400
10530
|
}
|
|
10401
|
-
async function tryArxivHtmlFromLocations(locations, tempDir, ctx) {
|
|
10531
|
+
async function tryArxivHtmlFromLocations(locations, tempDir, ctx, attempts) {
|
|
10402
10532
|
const arxivHtmlLocation = locations.find(
|
|
10403
10533
|
(loc) => loc.source === "arxiv" && loc.urlType === "html"
|
|
10404
10534
|
);
|
|
10405
10535
|
if (!arxivHtmlLocation) return { attached: false, source: "" };
|
|
10406
10536
|
const arxivId = extractArxivId(arxivHtmlLocation.url);
|
|
10407
10537
|
if (!arxivId) return { attached: false, source: "arxiv" };
|
|
10408
|
-
const mdAttached = await tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx);
|
|
10538
|
+
const mdAttached = await tryDownloadArxivHtmlAndConvert(arxivId, tempDir, ctx, attempts);
|
|
10409
10539
|
return { attached: mdAttached, source: "arxiv" };
|
|
10410
10540
|
}
|
|
10411
|
-
function buildDownloadError(locations, identifier) {
|
|
10541
|
+
function buildDownloadError(locations, identifier, attempts) {
|
|
10542
|
+
const attemptUrls = attempts.filter((a) => a.url).map((a) => a.url);
|
|
10543
|
+
const hint = attemptUrls.length > 0 ? formatHint("open to download manually (may require institutional access)", attemptUrls) : void 0;
|
|
10412
10544
|
const pdfLocation = locations.find((loc) => loc.urlType === "pdf");
|
|
10413
10545
|
if (pdfLocation) {
|
|
10414
10546
|
return {
|
|
10415
10547
|
success: false,
|
|
10416
|
-
error: `Failed to download from ${pdfLocation.source}: download failed
|
|
10548
|
+
error: `Failed to download from ${pdfLocation.source}: download failed`,
|
|
10549
|
+
hint
|
|
10417
10550
|
};
|
|
10418
10551
|
}
|
|
10419
|
-
return { success: false, error: `Failed to download fulltext for ${identifier}
|
|
10552
|
+
return { success: false, error: `Failed to download fulltext for ${identifier}`, hint };
|
|
10420
10553
|
}
|
|
10421
10554
|
async function downloadAndAttach(locations, pmcid, tempDir, ctx, referenceId, identifier) {
|
|
10422
10555
|
const attachedFiles = [];
|
|
10423
10556
|
let usedSource = "";
|
|
10424
|
-
const
|
|
10557
|
+
const attempts = [];
|
|
10558
|
+
const pdfResult = await tryDownloadPdf(locations, tempDir, ctx, attempts);
|
|
10425
10559
|
if (pdfResult.attached) {
|
|
10426
10560
|
attachedFiles.push("pdf");
|
|
10427
10561
|
usedSource = pdfResult.source;
|
|
10428
10562
|
}
|
|
10429
10563
|
if (pmcid) {
|
|
10430
|
-
const mdAttached = await tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx);
|
|
10564
|
+
const mdAttached = await tryDownloadPmcXmlAndConvert(pmcid, tempDir, ctx, attempts);
|
|
10431
10565
|
if (mdAttached) {
|
|
10432
10566
|
attachedFiles.push("markdown");
|
|
10433
10567
|
if (!usedSource) usedSource = "pmc";
|
|
10434
10568
|
}
|
|
10435
10569
|
}
|
|
10436
10570
|
if (!attachedFiles.includes("markdown")) {
|
|
10437
|
-
const arxivResult = await tryArxivHtmlFromLocations(locations, tempDir, ctx);
|
|
10571
|
+
const arxivResult = await tryArxivHtmlFromLocations(locations, tempDir, ctx, attempts);
|
|
10438
10572
|
if (arxivResult.attached) {
|
|
10439
10573
|
attachedFiles.push("markdown");
|
|
10440
10574
|
if (!usedSource) usedSource = arxivResult.source;
|
|
@@ -10443,7 +10577,10 @@ async function downloadAndAttach(locations, pmcid, tempDir, ctx, referenceId, id
|
|
|
10443
10577
|
if (attachedFiles.length > 0) {
|
|
10444
10578
|
return { success: true, referenceId, source: usedSource, attachedFiles };
|
|
10445
10579
|
}
|
|
10446
|
-
return
|
|
10580
|
+
return {
|
|
10581
|
+
...buildDownloadError(locations, identifier, attempts),
|
|
10582
|
+
attempts: attempts.length > 0 ? attempts : void 0
|
|
10583
|
+
};
|
|
10447
10584
|
}
|
|
10448
10585
|
const fetch$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
10449
10586
|
__proto__: null,
|
|
@@ -10511,6 +10648,7 @@ function formatFirstAuthor(item) {
|
|
|
10511
10648
|
if (!firstAuthor) {
|
|
10512
10649
|
return "Unknown";
|
|
10513
10650
|
}
|
|
10651
|
+
if (firstAuthor.literal) return firstAuthor.literal;
|
|
10514
10652
|
const family = firstAuthor.family || "Unknown";
|
|
10515
10653
|
const givenInitial = firstAuthor.given ? firstAuthor.given[0] : "";
|
|
10516
10654
|
if (givenInitial) {
|
|
@@ -10600,6 +10738,7 @@ function getFirstAuthorFamilyName(item) {
|
|
|
10600
10738
|
if (!firstAuthor) {
|
|
10601
10739
|
return "Unknown";
|
|
10602
10740
|
}
|
|
10741
|
+
if (firstAuthor.literal) return firstAuthor.literal;
|
|
10603
10742
|
return firstAuthor.family || "Unknown";
|
|
10604
10743
|
}
|
|
10605
10744
|
function formatInTextEntry(item) {
|
|
@@ -10950,6 +11089,13 @@ function getIsbnFromCache(isbn) {
|
|
|
10950
11089
|
function cacheIsbnResult(isbn, item, config) {
|
|
10951
11090
|
storeInCache(isbnCache, isbn, item);
|
|
10952
11091
|
}
|
|
11092
|
+
const arxivCache = /* @__PURE__ */ new Map();
|
|
11093
|
+
function getArxivFromCache(arxivId) {
|
|
11094
|
+
return getFromCache(arxivCache, arxivId);
|
|
11095
|
+
}
|
|
11096
|
+
function cacheArxivResult(arxivId, item, config) {
|
|
11097
|
+
storeInCache(arxivCache, arxivId, item);
|
|
11098
|
+
}
|
|
10953
11099
|
const DOI_URL_PREFIXES$1 = [
|
|
10954
11100
|
"https://doi.org/",
|
|
10955
11101
|
"http://doi.org/",
|
|
@@ -10990,6 +11136,28 @@ function normalizeIsbn(isbn) {
|
|
|
10990
11136
|
normalized = normalized.toUpperCase();
|
|
10991
11137
|
return normalized;
|
|
10992
11138
|
}
|
|
11139
|
+
const ARXIV_URL_PREFIXES = [
|
|
11140
|
+
"https://arxiv.org/abs/",
|
|
11141
|
+
"http://arxiv.org/abs/",
|
|
11142
|
+
"https://arxiv.org/pdf/",
|
|
11143
|
+
"http://arxiv.org/pdf/",
|
|
11144
|
+
"https://arxiv.org/html/",
|
|
11145
|
+
"http://arxiv.org/html/"
|
|
11146
|
+
];
|
|
11147
|
+
function normalizeArxiv(arxiv) {
|
|
11148
|
+
const trimmed = arxiv.trim();
|
|
11149
|
+
if (!trimmed) {
|
|
11150
|
+
return "";
|
|
11151
|
+
}
|
|
11152
|
+
const lowerInput = trimmed.toLowerCase();
|
|
11153
|
+
for (const prefix of ARXIV_URL_PREFIXES) {
|
|
11154
|
+
if (lowerInput.startsWith(prefix.toLowerCase())) {
|
|
11155
|
+
return trimmed.slice(prefix.length);
|
|
11156
|
+
}
|
|
11157
|
+
}
|
|
11158
|
+
const withoutPrefix = trimmed.replace(/^arxiv:\s*/i, "");
|
|
11159
|
+
return withoutPrefix;
|
|
11160
|
+
}
|
|
10993
11161
|
const EXTENSION_MAP = {
|
|
10994
11162
|
".json": "json",
|
|
10995
11163
|
".bib": "bibtex",
|
|
@@ -11050,6 +11218,9 @@ function detectSingleIdentifier(input) {
|
|
|
11050
11218
|
if (isDoi(input)) {
|
|
11051
11219
|
return "doi";
|
|
11052
11220
|
}
|
|
11221
|
+
if (isArxiv(input)) {
|
|
11222
|
+
return "arxiv";
|
|
11223
|
+
}
|
|
11053
11224
|
if (isIsbn(input)) {
|
|
11054
11225
|
return "isbn";
|
|
11055
11226
|
}
|
|
@@ -11112,6 +11283,17 @@ function isIsbn(input) {
|
|
|
11112
11283
|
}
|
|
11113
11284
|
return true;
|
|
11114
11285
|
}
|
|
11286
|
+
const ARXIV_ID_PATTERN = /^\d{4}\.\d{4,5}(v\d+)?$/;
|
|
11287
|
+
function isArxiv(input) {
|
|
11288
|
+
if (!input || input.length === 0) {
|
|
11289
|
+
return false;
|
|
11290
|
+
}
|
|
11291
|
+
const normalized = normalizeArxiv(input);
|
|
11292
|
+
if (!normalized) {
|
|
11293
|
+
return false;
|
|
11294
|
+
}
|
|
11295
|
+
return ARXIV_ID_PATTERN.test(normalized);
|
|
11296
|
+
}
|
|
11115
11297
|
const RATE_LIMITS = {
|
|
11116
11298
|
pubmed: {
|
|
11117
11299
|
withoutApiKey: 3,
|
|
@@ -11121,8 +11303,10 @@ const RATE_LIMITS = {
|
|
|
11121
11303
|
},
|
|
11122
11304
|
crossref: 50,
|
|
11123
11305
|
// 50 req/sec
|
|
11124
|
-
isbn: 10
|
|
11306
|
+
isbn: 10,
|
|
11125
11307
|
// 10 req/sec (conservative for Google Books API daily limit)
|
|
11308
|
+
arxiv: 1
|
|
11309
|
+
// 1 req/sec (arXiv API is strict about rate limiting)
|
|
11126
11310
|
};
|
|
11127
11311
|
class RateLimiterImpl {
|
|
11128
11312
|
requestsPerSecond;
|
|
@@ -11175,6 +11359,8 @@ function getRequestsPerSecond(api, config) {
|
|
|
11175
11359
|
return RATE_LIMITS.crossref;
|
|
11176
11360
|
case "isbn":
|
|
11177
11361
|
return RATE_LIMITS.isbn;
|
|
11362
|
+
case "arxiv":
|
|
11363
|
+
return RATE_LIMITS.arxiv;
|
|
11178
11364
|
}
|
|
11179
11365
|
}
|
|
11180
11366
|
const PMC_API_BASE = "https://pmc.ncbi.nlm.nih.gov/api/ctxp/v1/pubmed/";
|
|
@@ -11354,8 +11540,108 @@ async function fetchIsbn(isbn) {
|
|
|
11354
11540
|
};
|
|
11355
11541
|
}
|
|
11356
11542
|
}
|
|
11543
|
+
const ARXIV_API_BASE = "https://export.arxiv.org/api/query";
|
|
11544
|
+
function extractXmlText(xml, tagName) {
|
|
11545
|
+
const regex = new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)</${tagName}>`);
|
|
11546
|
+
const match = regex.exec(xml);
|
|
11547
|
+
return match?.[1]?.trim() ?? "";
|
|
11548
|
+
}
|
|
11549
|
+
function extractAuthors(entryXml) {
|
|
11550
|
+
const authors = [];
|
|
11551
|
+
const matches = entryXml.matchAll(/<author>[\s\S]*?<name>([^<]+)<\/name>[\s\S]*?<\/author>/g);
|
|
11552
|
+
for (const match of matches) {
|
|
11553
|
+
const name = match[1]?.trim();
|
|
11554
|
+
if (name) {
|
|
11555
|
+
authors.push({ literal: name });
|
|
11556
|
+
}
|
|
11557
|
+
}
|
|
11558
|
+
return authors;
|
|
11559
|
+
}
|
|
11560
|
+
function extractJournalDoi(entryXml) {
|
|
11561
|
+
const match = /<arxiv:doi[^>]*>([^<]+)<\/arxiv:doi>/.exec(entryXml);
|
|
11562
|
+
return match?.[1]?.trim();
|
|
11563
|
+
}
|
|
11564
|
+
function parseIssuedDate(dateStr) {
|
|
11565
|
+
if (!dateStr) return void 0;
|
|
11566
|
+
const date = new Date(dateStr);
|
|
11567
|
+
if (Number.isNaN(date.getTime())) return void 0;
|
|
11568
|
+
return {
|
|
11569
|
+
"date-parts": [[date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()]]
|
|
11570
|
+
};
|
|
11571
|
+
}
|
|
11572
|
+
async function fetchArxiv(arxivId) {
|
|
11573
|
+
if (!ARXIV_ID_PATTERN.test(arxivId)) {
|
|
11574
|
+
return {
|
|
11575
|
+
success: false,
|
|
11576
|
+
error: `Invalid arXiv ID format: ${arxivId}`,
|
|
11577
|
+
reason: "validation_error"
|
|
11578
|
+
};
|
|
11579
|
+
}
|
|
11580
|
+
const rateLimiter = getRateLimiter("arxiv", {});
|
|
11581
|
+
await rateLimiter.acquire();
|
|
11582
|
+
try {
|
|
11583
|
+
const url = `${ARXIV_API_BASE}?id_list=${encodeURIComponent(arxivId)}`;
|
|
11584
|
+
const response = await fetch(url, {
|
|
11585
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
|
|
11586
|
+
});
|
|
11587
|
+
if (!response.ok) {
|
|
11588
|
+
return {
|
|
11589
|
+
success: false,
|
|
11590
|
+
error: `arXiv API returned status ${response.status}`,
|
|
11591
|
+
reason: "fetch_error"
|
|
11592
|
+
};
|
|
11593
|
+
}
|
|
11594
|
+
const xml = await response.text();
|
|
11595
|
+
const entryMatch = /<entry>([\s\S]*?)<\/entry>/.exec(xml);
|
|
11596
|
+
if (!entryMatch) {
|
|
11597
|
+
return {
|
|
11598
|
+
success: false,
|
|
11599
|
+
error: `No results found for arXiv ID ${arxivId}`,
|
|
11600
|
+
reason: "not_found"
|
|
11601
|
+
};
|
|
11602
|
+
}
|
|
11603
|
+
const entryXml = entryMatch[1] ?? "";
|
|
11604
|
+
const title = extractXmlText(entryXml, "title");
|
|
11605
|
+
const summary = extractXmlText(entryXml, "summary");
|
|
11606
|
+
const published = extractXmlText(entryXml, "published");
|
|
11607
|
+
const authors = extractAuthors(entryXml);
|
|
11608
|
+
const journalDoi = extractJournalDoi(entryXml);
|
|
11609
|
+
const baseId = arxivId.replace(/v\d+$/, "");
|
|
11610
|
+
const doi = journalDoi ?? `10.48550/arXiv.${baseId}`;
|
|
11611
|
+
const item = {
|
|
11612
|
+
id: "",
|
|
11613
|
+
type: "article",
|
|
11614
|
+
title,
|
|
11615
|
+
author: authors,
|
|
11616
|
+
abstract: summary || void 0,
|
|
11617
|
+
issued: parseIssuedDate(published),
|
|
11618
|
+
DOI: doi,
|
|
11619
|
+
URL: `https://arxiv.org/abs/${arxivId}`,
|
|
11620
|
+
custom: {
|
|
11621
|
+
arxiv_id: arxivId
|
|
11622
|
+
}
|
|
11623
|
+
};
|
|
11624
|
+
const parseResult = CslItemSchema.safeParse(item);
|
|
11625
|
+
if (!parseResult.success) {
|
|
11626
|
+
return {
|
|
11627
|
+
success: false,
|
|
11628
|
+
error: `Invalid CSL-JSON data for arXiv ${arxivId}: ${parseResult.error.message}`,
|
|
11629
|
+
reason: "validation_error"
|
|
11630
|
+
};
|
|
11631
|
+
}
|
|
11632
|
+
return { success: true, item: parseResult.data };
|
|
11633
|
+
} catch (error) {
|
|
11634
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
11635
|
+
return {
|
|
11636
|
+
success: false,
|
|
11637
|
+
error: errorMsg,
|
|
11638
|
+
reason: "fetch_error"
|
|
11639
|
+
};
|
|
11640
|
+
}
|
|
11641
|
+
}
|
|
11357
11642
|
const fetcher = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
11358
11643
|
__proto__: null,
|
|
11644
|
+
fetchArxiv,
|
|
11359
11645
|
fetchDoi,
|
|
11360
11646
|
fetchIsbn,
|
|
11361
11647
|
fetchPmids
|
|
@@ -11550,19 +11836,22 @@ function classifyIdentifiers(identifiers) {
|
|
|
11550
11836
|
const pmids = [];
|
|
11551
11837
|
const dois = [];
|
|
11552
11838
|
const isbns = [];
|
|
11839
|
+
const arxivs = [];
|
|
11553
11840
|
const unknowns = [];
|
|
11554
11841
|
for (const id of identifiers) {
|
|
11555
|
-
if (
|
|
11556
|
-
pmids.push(normalizePmid(id));
|
|
11557
|
-
} else if (isDoi(id)) {
|
|
11842
|
+
if (isDoi(id)) {
|
|
11558
11843
|
dois.push(normalizeDoi(id));
|
|
11844
|
+
} else if (isArxiv(id)) {
|
|
11845
|
+
arxivs.push(normalizeArxiv(id));
|
|
11559
11846
|
} else if (isIsbn(id)) {
|
|
11560
11847
|
isbns.push(normalizeIsbn(id));
|
|
11848
|
+
} else if (isPmid(id)) {
|
|
11849
|
+
pmids.push(normalizePmid(id));
|
|
11561
11850
|
} else {
|
|
11562
11851
|
unknowns.push(id);
|
|
11563
11852
|
}
|
|
11564
11853
|
}
|
|
11565
|
-
return { pmids, dois, isbns, unknowns };
|
|
11854
|
+
return { pmids, dois, isbns, arxivs, unknowns };
|
|
11566
11855
|
}
|
|
11567
11856
|
function buildUnknownResults(unknowns) {
|
|
11568
11857
|
return unknowns.map((unknown) => ({
|
|
@@ -11655,6 +11944,29 @@ async function fetchIsbnsWithCache(isbns) {
|
|
|
11655
11944
|
}
|
|
11656
11945
|
return results;
|
|
11657
11946
|
}
|
|
11947
|
+
async function fetchArxivsWithCache(arxivIds) {
|
|
11948
|
+
const results = [];
|
|
11949
|
+
for (const arxivId of arxivIds) {
|
|
11950
|
+
const cached = getArxivFromCache(arxivId);
|
|
11951
|
+
if (cached) {
|
|
11952
|
+
results.push({ success: true, item: clearItemId(cached), source: arxivId });
|
|
11953
|
+
continue;
|
|
11954
|
+
}
|
|
11955
|
+
const fetchResult = await fetchArxiv(arxivId);
|
|
11956
|
+
if (fetchResult.success) {
|
|
11957
|
+
cacheArxivResult(arxivId, fetchResult.item);
|
|
11958
|
+
results.push({ success: true, item: clearItemId(fetchResult.item), source: arxivId });
|
|
11959
|
+
} else {
|
|
11960
|
+
results.push({
|
|
11961
|
+
success: false,
|
|
11962
|
+
error: fetchResult.error,
|
|
11963
|
+
source: arxivId,
|
|
11964
|
+
reason: fetchResult.reason
|
|
11965
|
+
});
|
|
11966
|
+
}
|
|
11967
|
+
}
|
|
11968
|
+
return results;
|
|
11969
|
+
}
|
|
11658
11970
|
function parseJsonContent(content) {
|
|
11659
11971
|
try {
|
|
11660
11972
|
const parsed = JSON.parse(content);
|
|
@@ -11811,7 +12123,7 @@ async function importFromIdentifiers(identifiers, options) {
|
|
|
11811
12123
|
if (identifiers.length === 0) {
|
|
11812
12124
|
return { results: [] };
|
|
11813
12125
|
}
|
|
11814
|
-
const { pmids, dois, isbns, unknowns } = classifyIdentifiers(identifiers);
|
|
12126
|
+
const { pmids, dois, isbns, arxivs, unknowns } = classifyIdentifiers(identifiers);
|
|
11815
12127
|
const results = [];
|
|
11816
12128
|
results.push(...buildUnknownResults(unknowns));
|
|
11817
12129
|
const pmidResults = await fetchPmidsWithCache(pmids, options.pubmedConfig ?? {});
|
|
@@ -11820,6 +12132,8 @@ async function importFromIdentifiers(identifiers, options) {
|
|
|
11820
12132
|
results.push(...doiResults);
|
|
11821
12133
|
const isbnResults = await fetchIsbnsWithCache(isbns);
|
|
11822
12134
|
results.push(...isbnResults);
|
|
12135
|
+
const arxivResults = await fetchArxivsWithCache(arxivs);
|
|
12136
|
+
results.push(...arxivResults);
|
|
11823
12137
|
return { results };
|
|
11824
12138
|
}
|
|
11825
12139
|
function looksLikeFilePath(input) {
|
|
@@ -11861,13 +12175,14 @@ async function processIdentifiers(inputs, options) {
|
|
|
11861
12175
|
const isValidPmid = isPmid(input);
|
|
11862
12176
|
const isValidDoi = isDoi(input);
|
|
11863
12177
|
const isValidIsbn = isIsbn(input);
|
|
11864
|
-
|
|
12178
|
+
const isValidArxiv = isArxiv(input);
|
|
12179
|
+
if (isValidPmid || isValidDoi || isValidIsbn || isValidArxiv) {
|
|
11865
12180
|
validIdentifiers.push(input);
|
|
11866
12181
|
} else {
|
|
11867
12182
|
const hint = looksLikeFilePath(input) ? " Hint: If this is a file path, check that the file exists." : "";
|
|
11868
12183
|
results.push({
|
|
11869
12184
|
success: false,
|
|
11870
|
-
error: `Cannot interpret '${input}' as identifier (not a valid PMID, DOI, or
|
|
12185
|
+
error: `Cannot interpret '${input}' as identifier (not a valid PMID, DOI, ISBN, or arXiv ID).${hint}`,
|
|
11871
12186
|
source: input,
|
|
11872
12187
|
reason: "validation_error"
|
|
11873
12188
|
});
|
|
@@ -11911,7 +12226,7 @@ async function processStdinContent(content, options) {
|
|
|
11911
12226
|
source: r.source === "content" ? "stdin" : r.source
|
|
11912
12227
|
}));
|
|
11913
12228
|
}
|
|
11914
|
-
if (format === "pmid" || format === "doi" || format === "isbn") {
|
|
12229
|
+
if (format === "pmid" || format === "doi" || format === "isbn" || format === "arxiv") {
|
|
11915
12230
|
const identifiers2 = content.split(/\s+/).filter((s) => s.length > 0);
|
|
11916
12231
|
return processIdentifiers(identifiers2, options);
|
|
11917
12232
|
}
|
|
@@ -12084,7 +12399,7 @@ function createAddRoute(library, config) {
|
|
|
12084
12399
|
}
|
|
12085
12400
|
const CHECK_CONCURRENCY = 5;
|
|
12086
12401
|
async function checkReferences(library, options) {
|
|
12087
|
-
const { checkReference } = await import("./checker-
|
|
12402
|
+
const { checkReference } = await import("./checker-CP8vSC-S.js");
|
|
12088
12403
|
const save = options.save !== false;
|
|
12089
12404
|
const skipDays = options.skipDays ?? 7;
|
|
12090
12405
|
const items = await resolveItems(library, options);
|
|
@@ -12114,7 +12429,7 @@ function fillSkippedResults(tasks, results) {
|
|
|
12114
12429
|
uuid: task.item.custom?.uuid ?? "",
|
|
12115
12430
|
status: "skipped",
|
|
12116
12431
|
findings: [],
|
|
12117
|
-
checkedAt: task.item.custom?.check?.checked_at,
|
|
12432
|
+
checkedAt: task.item.custom?.check?.checked_at ?? "",
|
|
12118
12433
|
checkedSources: []
|
|
12119
12434
|
};
|
|
12120
12435
|
}
|
|
@@ -12176,11 +12491,11 @@ async function resolveItems(library, options) {
|
|
|
12176
12491
|
}
|
|
12177
12492
|
function shouldSkipRecentCheck(item, skipDays) {
|
|
12178
12493
|
if (skipDays <= 0) return false;
|
|
12179
|
-
const
|
|
12180
|
-
if (!
|
|
12181
|
-
const
|
|
12494
|
+
const checkedAt = item.custom?.check?.checked_at;
|
|
12495
|
+
if (!checkedAt) return false;
|
|
12496
|
+
const checkedAtDate = new Date(checkedAt);
|
|
12182
12497
|
const now = /* @__PURE__ */ new Date();
|
|
12183
|
-
const daysSince = (now.getTime() -
|
|
12498
|
+
const daysSince = (now.getTime() - checkedAtDate.getTime()) / (1e3 * 60 * 60 * 24);
|
|
12184
12499
|
return daysSince < skipDays;
|
|
12185
12500
|
}
|
|
12186
12501
|
async function saveCheckResult(library, item, result) {
|
|
@@ -12193,9 +12508,8 @@ async function saveCheckResult(library, item, result) {
|
|
|
12193
12508
|
...f.details ? { details: snakeCaseKeys(f.details) } : {}
|
|
12194
12509
|
}))
|
|
12195
12510
|
};
|
|
12196
|
-
const existingCustom = item.custom ?? {};
|
|
12197
12511
|
await library.update(item.id, {
|
|
12198
|
-
custom: { ...
|
|
12512
|
+
custom: { ...item.custom, check: checkData }
|
|
12199
12513
|
});
|
|
12200
12514
|
}
|
|
12201
12515
|
function snakeCaseKeys(obj) {
|
|
@@ -12764,4 +13078,4 @@ export {
|
|
|
12764
13078
|
fetcher as y,
|
|
12765
13079
|
add as z
|
|
12766
13080
|
};
|
|
12767
|
-
//# sourceMappingURL=index-
|
|
13081
|
+
//# sourceMappingURL=index-T-edKSzd.js.map
|