export const MARKET_REQUEST_SNAPSHOT_ATTRIBUTE = "data-sces-market-request-snapshot"; const MARKET_SEARCH_ENDPOINT_PATH = "/gw/api/gsearch/search_for_author_square"; export interface MarketListRequestSnapshot { body?: string; headers?: Record; method: string; url: string; } export function readMarketListRequestSnapshot( document: Document ): MarketListRequestSnapshot | null { const serializedSnapshot = document.documentElement.getAttribute( MARKET_REQUEST_SNAPSHOT_ATTRIBUTE ); if (!serializedSnapshot) { return readMarketListRequestSnapshotFromPageState(document); } try { const parsedSnapshot = normalizeMarketListRequestSnapshot( JSON.parse(serializedSnapshot) ); if (!parsedSnapshot) { return readMarketListRequestSnapshotFromPageState(document); } return parsedSnapshot; } catch { return readMarketListRequestSnapshotFromPageState(document); } } export function writeMarketListRequestSnapshot( document: Document, snapshot: MarketListRequestSnapshot ): void { document.documentElement.setAttribute( MARKET_REQUEST_SNAPSHOT_ATTRIBUTE, JSON.stringify(snapshot) ); } function isMarketListRequestSnapshot( value: unknown ): value is MarketListRequestSnapshot { if (!value || typeof value !== "object") { return false; } const candidate = value as Partial; return ( typeof candidate.method === "string" && typeof candidate.url === "string" && (!("body" in candidate) || typeof candidate.body === "string") && (!("headers" in candidate) || isStringRecord(candidate.headers)) ); } function normalizeMarketListRequestSnapshot( value: unknown ): MarketListRequestSnapshot | null { if (!value || typeof value !== "object") { return null; } const candidate = value as Partial & { headers?: Record; }; const normalizedSnapshot: Partial = { body: typeof candidate.body === "string" ? candidate.body : undefined, method: typeof candidate.method === "string" ? candidate.method : undefined, url: typeof candidate.url === "string" ? candidate.url : undefined }; if (candidate.headers && typeof candidate.headers === "object") { normalizedSnapshot.headers = Object.fromEntries( Object.entries(candidate.headers) .filter(([, entry]) => ["string", "number", "boolean"].includes(typeof entry) ) .map(([key, entry]) => [key, String(entry)]) ); } return isMarketListRequestSnapshot(normalizedSnapshot) ? normalizedSnapshot : null; } function isStringRecord(value: unknown): value is Record { if (!value || typeof value !== "object") { return false; } return Object.values(value).every((entry) => typeof entry === "string"); } function readMarketListRequestSnapshotFromPageState( document: Document ): MarketListRequestSnapshot | null { const reqParams = findMarketReqParams(document); if (!reqParams) { return null; } return { body: JSON.stringify(reqParams), method: "POST", url: buildMarketSearchUrl(document) }; } function findMarketReqParams(document: Document): Record | null { const marketRoot = document.querySelector(".base-author-list") as | (Element & { __vue__?: { _setupState?: Record; }; }) | null; const setupState = marketRoot?.__vue__?._setupState; if (!setupState) { return null; } const queue: unknown[] = Object.values(setupState); while (queue.length > 0) { const current = unwrapVueRef(queue.shift()); if (!isRecord(current)) { continue; } const reqParams = unwrapVueRef(current.reqParams); if (isRecord(reqParams)) { return reqParams; } Object.values(current).forEach((value) => { queue.push(value); }); } return null; } function buildMarketSearchUrl(document: Document): string { if ( document.location?.origin && document.location.origin !== "null" && document.location.origin !== "about:blank" ) { return document.location.origin.includes("xingtu.cn") ? MARKET_SEARCH_ENDPOINT_PATH : new URL(MARKET_SEARCH_ENDPOINT_PATH, document.location.origin).toString(); } return MARKET_SEARCH_ENDPOINT_PATH; } function unwrapVueRef(value: unknown): unknown { if (isRecord(value) && "value" in value) { return value.value; } return value; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; }