UNPKG

rsshub

Version:
176 lines (175 loc) • 5.93 kB
import { t as rofetch } from "./ofetch-U0CdpE2g.mjs"; import { t as config } from "./config-BfTrYkQS.mjs"; import { n as generateHeaders } from "./header-generator-DACPoxdp.mjs"; import { t as cache_default } from "./cache-C1Y-9qDV.mjs"; import { t as md5 } from "./md5-CpIHIxCO.mjs"; import { t as getSignedHeaders } from "./sign-JzOJfAzW.mjs"; import { VM } from "vm2"; import { JSDOM, VirtualConsole } from "jsdom"; //#region lib/routes/zhihu/jsdom.ts let isUnreachableRuntimeErrorGuarded = false; const pendingZseCredentials = /* @__PURE__ */ new Map(); const preventUnreachableRuntimeError = () => { if (isUnreachableRuntimeErrorGuarded) return; isUnreachableRuntimeErrorGuarded = true; process.on("unhandledRejection", (reason) => { const error = reason; if (error?.name === "RuntimeError" && error.message === "unreachable") return; throw reason; }); }; const generateZseCk = async (url, apiPath, configuredDc0) => { preventUnreachableRuntimeError(); const ua = generateHeaders()["user-agent"]; const headers = { "user-agent": ua }; let dc0 = configuredDc0; if (!dc0) dc0 = ((await rofetch.raw("https://www.zhihu.com/explore", { headers, redirect: "manual", ignoreResponseError: true })).headers.getSetCookie?.() ?? []).find((line) => line.startsWith("d_c0="))?.split(";", 1)[0].slice(5).split("|", 1)[0].replace(/=+$/, "") || ""; if (!dc0) throw new Error("zhihu: failed to obtain a guest d_c0 cookie"); const html = (await rofetch.raw(`https://www.zhihu.com${apiPath}`, { headers: { ...headers, cookie: `d_c0=${dc0}; __zse_ck=005_x-x`, referer: url, "x-requested-with": "fetch" }, ignoreResponseError: true }))._data ?? ""; const meta = html.match(/id="zh-zse-ck"[^>]*content="([^"]*)"/)?.[1]; const hash = html.match(/zse-ck\/v4\/([a-f0-9]+)\.js/)?.[1]; if (!meta || !hash) throw new Error("zhihu: challenge page did not contain an URL to __zse_ck meta/script"); const vmScript = await rofetch(`https://static.zhihu.com/zse-ck/v4/${hash}.js`, { headers, parseResponse: (text) => text }); const { window } = new JSDOM(`<!doctype html><html><head><meta id="zh-zse-ck" content="${meta}"><script data-assets-tracker-config='{"appName":"zse_ck"}'><\/script></head><body></body></html>`, { url, referrer: "https://www.zhihu.com/", runScripts: "outside-only", pretendToBeVisual: true, virtualConsole: new VirtualConsole() }); Object.defineProperties(window.navigator, { userAgent: { value: ua, configurable: true }, webdriver: { value: false, configurable: true } }); window.TextEncoder = TextEncoder; window.TextDecoder = TextDecoder; window.atob = (value) => Buffer.from(value, "base64").toString("binary"); window.btoa = (value) => Buffer.from(value, "binary").toString("base64"); Object.assign(window, { __g: {} }); const cookieDescriptor = Object.getOwnPropertyDescriptor(window.Document.prototype, "cookie"); const setCookie = cookieDescriptor?.set; if (!cookieDescriptor?.get || !setCookie) { window.close(); throw new Error("zhihu: JSDOM did not provide document.cookie accessors"); } const tokenPromise = new Promise((resolve) => { Object.defineProperty(window.document, "cookie", { configurable: true, get: cookieDescriptor.get, set(value) { setCookie.call(window.document, value); const token = value.match(/__zse_ck=([^;]+)/)?.[1]; if (token?.includes("-")) resolve(token); } }); }); let zseCk; let timeoutId; try { new VM({ timeout: 3e3, sandbox: window }).run(vmScript); const timeout = new Promise((resolve) => { timeoutId = setTimeout(resolve, 3e3); }); zseCk = await Promise.race([tokenPromise, timeout]); } finally { clearTimeout(timeoutId); window.close(); } if (!zseCk) throw new Error("zhihu: WASM VM did not produce a __zse_ck"); return { dc0, zseCk, ua }; }; const getGeneratedZseCredentials = (url, apiPath, configuredDc0) => { const cacheKey = `zhihu:zse-ck:v4:${configuredDc0 ? md5(configuredDc0) : "guest"}`; const pending = pendingZseCredentials.get(cacheKey); if (pending) return pending; const created = (async () => { try { return await cache_default.tryGet(cacheKey, () => generateZseCk(url, apiPath, configuredDc0), config.cache.contentExpire, false); } finally { pendingZseCredentials.delete(cacheKey); } })(); pendingZseCredentials.set(cacheKey, created); return created; }; const mergeGeneratedCookies = (configured, dc0, zseCk) => { const remaining = configured.split(";").map((pair) => pair.trim()).filter((pair) => { const name = pair.split("=", 1)[0]; return name && name !== "d_c0" && name !== "__zse_ck"; }); return [ `__zse_ck=${zseCk}`, `d_c0=${dc0}`, ...remaining ].join("; "); }; const createJSDOMClient = (pageUrl, configured, configuredDc0) => { let sessionPromise; const startSession = async (apiPath) => { try { const { dc0, zseCk, ua } = await getGeneratedZseCredentials(pageUrl, apiPath, configuredDc0); return { dc0, headers: { cookie: configuredDc0 ? mergeGeneratedCookies(configured, dc0, zseCk) : `__zse_ck=${zseCk}; d_c0=${dc0}`, "user-agent": ua } }; } catch (error) { sessionPromise = void 0; throw error; } }; const getSession = (apiPath = new URL(pageUrl).pathname + new URL(pageUrl).search) => sessionPromise ??= startSession(apiPath); return { get: async (apiPath) => { if (!apiPath.startsWith("/api/")) throw new Error("zhihu: expected an API path"); const { dc0, headers } = await getSession(apiPath); return rofetch(`https://www.zhihu.com${apiPath}`, { headers: { ...getSignedHeaders(apiPath, dc0), ...headers, Referer: pageUrl } }); }, getPage: async () => { const { headers } = await getSession(); return rofetch(pageUrl, { headers: { ...headers, Referer: pageUrl }, parseResponse: (text) => text }); } }; }; //#endregion export { createJSDOMClient };