import { normalizeFractionRateDisplay } from "../../shared/rate-normalizer"; const BRIDGE_MARKER = "__SCES_MARKET_PAGE_BRIDGE_INSTALLED__"; const SERIALIZED_MARKET_ROWS_ATTRIBUTE = "data-sces-market-rows"; type MarketRow = { attribute_datas?: Record; nick_name?: string; star_id?: string; }; declare global { interface Window { [BRIDGE_MARKER]?: boolean; } } installMarketPageBridge(); function installMarketPageBridge() { if (window[BRIDGE_MARKER]) { syncSerializedMarketRows(); return; } window[BRIDGE_MARKER] = true; syncSerializedMarketRows(); const observer = new MutationObserver(() => { syncSerializedMarketRows(); }); observer.observe(document.documentElement, { childList: true, subtree: true }); window.setInterval(() => { syncSerializedMarketRows(); }, 1000); } function syncSerializedMarketRows() { const nextSerializedRows = JSON.stringify(readSerializedMarketRows()); if ( document.documentElement.getAttribute(SERIALIZED_MARKET_ROWS_ATTRIBUTE) !== nextSerializedRows ) { document.documentElement.setAttribute( SERIALIZED_MARKET_ROWS_ATTRIBUTE, nextSerializedRows ); } } function readSerializedMarketRows() { const marketList = readMarketList(); return marketList .map((row) => { const attributeDatas = isRecord(row.attribute_datas) ? row.attribute_datas : {}; const singleVideoAfterSearchRate = readNormalizedFractionRate( attributeDatas.avg_search_after_view_rate_30d ); return { authorId: readString(row.star_id) ?? readString(attributeDatas.id) ?? "", authorName: readString(attributeDatas.nickname) ?? readString(row.nick_name) ?? "", singleVideoAfterSearchRate }; }) .filter((row) => Boolean(row.authorId || row.authorName)); } function readMarketList(): MarketRow[] { const marketRoot = document.querySelector(".base-author-list") as | (HTMLElement & { __vue__?: { _setupState?: Record; }; }) | null; const setupState = marketRoot?.__vue__?._setupState; if (!setupState) { return []; } for (const value of Object.values(setupState)) { const candidate = unwrapVueRef(value); if (Array.isArray(candidate) && looksLikeMarketList(candidate)) { return candidate as MarketRow[]; } if (!isRecord(candidate) || !Array.isArray(candidate.marketList)) { continue; } if (looksLikeMarketList(candidate.marketList)) { return candidate.marketList as MarketRow[]; } } return []; } function looksLikeMarketList(value: unknown[]): boolean { const firstRow = value[0]; return isRecord(firstRow) && ("star_id" in firstRow || "attribute_datas" in firstRow); } 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; } function readString(value: unknown): string | null { return typeof value === "string" ? value : null; } function readNormalizedFractionRate(value: unknown): string | undefined { return typeof value === "string" ? normalizeFractionRateDisplay(value) ?? undefined : undefined; }