131 lines
3.3 KiB
TypeScript
131 lines
3.3 KiB
TypeScript
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<string, unknown>;
|
|
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<string, unknown>;
|
|
};
|
|
})
|
|
| 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<string, unknown> {
|
|
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;
|
|
}
|