Some checks failed
CI / skinny-install (aco) (push) Successful in 2m19s
CI / lint-test (push) Failing after 3m13s
CI / skinny-install (api) (push) Successful in 1m45s
CI / skinny-install (bcda) (push) Successful in 1m41s
CI / skinny-install (bib) (push) Successful in 2m2s
CI / skinny-install (bls) (push) Successful in 2m1s
CI / skinny-install (ccw) (push) Successful in 1m37s
CI / skinny-install (cli) (push) Successful in 2m7s
CI / skinny-install (cms) (push) Successful in 1m40s
CI / skinny-install (conf) (push) Successful in 1m57s
CI / skinny-install (perf) (push) Successful in 1m41s
CI / skinny-install (rex) (push) Successful in 1m46s
CI / skinny-install (pfs) (push) Successful in 2m37s
Deploy / build-scan-report (push) Failing after 2m34s
Cloudflare Worker (pdf-proxy) deployed to bypass ISP-level SciHub blocking: - Worker fetches PDFs from SciHub mirrors on Cloudflare's edge network - Authenticated via X-Proxy-Key header - Routes: /pdf?doi=, /fetch?url=, /health - Deployed at pdf-proxy.lite7889.workers.dev fetch_pdfs.py updated with 5-phase waterfall: 1. Unpaywall (legal OA, ~4% for this corpus) 2. PMC (free, PMCID-based) 3. Semantic Scholar (batch 500) 4. CF Worker → SciHub (72% hit rate on first 50, running full batch) 5. SciHub direct (fallback with proxy) Also: cloudflared tunnel (stack-proxy) with WARP routing in compose.yml, CoreDNS sci-hub domain routing via unfiltered DNS. Test: 36/50 PDFs (72%) retrieved in first batch via Worker.
140 lines
3.9 KiB
JavaScript
140 lines
3.9 KiB
JavaScript
/**
|
|
* PDF Fetch Proxy — Cloudflare Worker
|
|
*
|
|
* Relays HTTP requests through Cloudflare's edge network,
|
|
* bypassing ISP-level DNS/IP blocking of academic sources.
|
|
*
|
|
* Usage:
|
|
* GET https://<worker>/fetch?url=https://sci-hub.st/10.1234/example
|
|
* GET https://<worker>/pdf?doi=10.1234/example
|
|
*
|
|
* Security: requires X-Proxy-Key header matching the SECRET binding.
|
|
*/
|
|
|
|
const SCIHUB_MIRRORS = [
|
|
"https://sci-hub.st",
|
|
"https://sci-hub.ru",
|
|
"https://sci-hub.se",
|
|
"https://sci-hub.ren",
|
|
"https://sci-hub.ee",
|
|
];
|
|
|
|
export default {
|
|
async fetch(request, env) {
|
|
// Auth check
|
|
const key = request.headers.get("X-Proxy-Key");
|
|
if (!key || key !== env.SECRET) {
|
|
return new Response("Unauthorized", { status: 401 });
|
|
}
|
|
|
|
const url = new URL(request.url);
|
|
|
|
// Route: /fetch?url=<encoded_url> — generic proxy
|
|
if (url.pathname === "/fetch") {
|
|
const targetUrl = url.searchParams.get("url");
|
|
if (!targetUrl) {
|
|
return new Response("Missing url parameter", { status: 400 });
|
|
}
|
|
return proxyFetch(targetUrl);
|
|
}
|
|
|
|
// Route: /pdf?doi=<doi> — SciHub PDF resolver
|
|
if (url.pathname === "/pdf") {
|
|
const doi = url.searchParams.get("doi");
|
|
if (!doi) {
|
|
return new Response("Missing doi parameter", { status: 400 });
|
|
}
|
|
return fetchPdfFromScihub(doi);
|
|
}
|
|
|
|
// Route: /health
|
|
if (url.pathname === "/health") {
|
|
return new Response(JSON.stringify({ status: "ok", ts: Date.now() }), {
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
return new Response("Not found. Use /fetch?url=, /pdf?doi=, or /health", {
|
|
status: 404,
|
|
});
|
|
},
|
|
};
|
|
|
|
async function proxyFetch(targetUrl) {
|
|
try {
|
|
const resp = await fetch(targetUrl, {
|
|
headers: { "User-Agent": "Mozilla/5.0 (compatible; stack-proxy/1.0)" },
|
|
redirect: "follow",
|
|
});
|
|
// Stream the response back with original headers
|
|
const headers = new Headers(resp.headers);
|
|
headers.set("X-Proxy-Source", "cloudflare-worker");
|
|
return new Response(resp.body, {
|
|
status: resp.status,
|
|
headers,
|
|
});
|
|
} catch (e) {
|
|
return new Response(JSON.stringify({ error: e.message }), {
|
|
status: 502,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
}
|
|
|
|
async function fetchPdfFromScihub(doi) {
|
|
for (const mirror of SCIHUB_MIRRORS) {
|
|
try {
|
|
const pageUrl = `${mirror}/${doi}`;
|
|
const resp = await fetch(pageUrl, {
|
|
headers: { "User-Agent": "Mozilla/5.0 (compatible; stack-proxy/1.0)" },
|
|
redirect: "follow",
|
|
});
|
|
if (resp.status !== 200) continue;
|
|
|
|
const html = await resp.text();
|
|
|
|
// Extract PDF URL from SciHub page
|
|
let pdfUrl = null;
|
|
const patterns = [
|
|
/id="pdf"[^>]*src="([^"]+)"/,
|
|
/<iframe[^>]*src="([^"]*\.pdf[^"]*)"/,
|
|
/<embed[^>]*src="([^"]*\.pdf[^"]*)"/,
|
|
];
|
|
for (const pat of patterns) {
|
|
const m = html.match(pat);
|
|
if (m) {
|
|
pdfUrl = m[1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!pdfUrl) continue;
|
|
|
|
// Normalize URL
|
|
if (pdfUrl.startsWith("//")) pdfUrl = "https:" + pdfUrl;
|
|
else if (pdfUrl.startsWith("/")) pdfUrl = mirror + pdfUrl;
|
|
|
|
// Fetch the actual PDF
|
|
const pdfResp = await fetch(pdfUrl, {
|
|
headers: { "User-Agent": "Mozilla/5.0" },
|
|
redirect: "follow",
|
|
});
|
|
|
|
if (pdfResp.status === 200) {
|
|
const headers = new Headers(pdfResp.headers);
|
|
headers.set("Content-Type", "application/pdf");
|
|
headers.set("X-Proxy-Source", "cloudflare-worker");
|
|
headers.set("X-Proxy-Mirror", mirror);
|
|
return new Response(pdfResp.body, { status: 200, headers });
|
|
}
|
|
} catch (e) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return new Response(
|
|
JSON.stringify({ error: "PDF not found on any mirror", doi }),
|
|
{ status: 404, headers: { "Content-Type": "application/json" } }
|
|
);
|
|
}
|