174 lines
4.6 KiB
TypeScript
174 lines
4.6 KiB
TypeScript
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<string, string>;
|
|
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<MarketListRequestSnapshot>;
|
|
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<MarketListRequestSnapshot> & {
|
|
headers?: Record<string, unknown>;
|
|
};
|
|
const normalizedSnapshot: Partial<MarketListRequestSnapshot> = {
|
|
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<string, string> {
|
|
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<string, unknown> | null {
|
|
const marketRoot = document.querySelector(".base-author-list") as
|
|
| (Element & {
|
|
__vue__?: {
|
|
_setupState?: Record<string, unknown>;
|
|
};
|
|
})
|
|
| 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<string, unknown> {
|
|
return typeof value === "object" && value !== null;
|
|
}
|