rsshub
Version:
Make RSS Great Again!
176 lines (175 loc) • 5.93 kB
JavaScript
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 };