Fix silent market CSV export
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user