- Include dist-release/ and release/ for direct colleague use - Add beginner-friendly installation guide - Update .gitignore to track distribution builds
584 lines
19 KiB
JavaScript
584 lines
19 KiB
JavaScript
"use strict";
|
|
(() => {
|
|
// src/shared/rate-normalizer.ts
|
|
function normalizeFractionRateDisplay(value) {
|
|
const numericValue = Number(value);
|
|
if (!Number.isFinite(numericValue)) {
|
|
return null;
|
|
}
|
|
const percentageValue = numericValue * 100;
|
|
return `${trimTrailingZeros(percentageValue.toFixed(6))}%`;
|
|
}
|
|
function trimTrailingZeros(value) {
|
|
return value.replace(/\.?0+$/, "");
|
|
}
|
|
|
|
// src/content/market/market-list-request-snapshot.ts
|
|
var MARKET_REQUEST_SNAPSHOT_ATTRIBUTE = "data-sces-market-request-snapshot";
|
|
function writeMarketListRequestSnapshot(document2, snapshot) {
|
|
document2.documentElement.setAttribute(
|
|
MARKET_REQUEST_SNAPSHOT_ATTRIBUTE,
|
|
JSON.stringify(snapshot)
|
|
);
|
|
}
|
|
|
|
// src/content/market/market-list-row.ts
|
|
var PAGE_NUMBER_KEYS = [
|
|
"currentPage",
|
|
"page",
|
|
"pageNo",
|
|
"pageNum",
|
|
"page_no",
|
|
"page_num"
|
|
];
|
|
var PAGE_SIZE_KEYS = [
|
|
"limit",
|
|
"pageSize",
|
|
"page_size",
|
|
"size"
|
|
];
|
|
var TOTAL_COUNT_KEYS = [
|
|
"total",
|
|
"totalCount",
|
|
"total_count"
|
|
];
|
|
var TOTAL_PAGE_KEYS = [
|
|
"pageCount",
|
|
"page_count",
|
|
"totalPage",
|
|
"totalPages",
|
|
"total_page",
|
|
"total_pages"
|
|
];
|
|
function mapMarketListRow(row) {
|
|
const attributeDatas = readMarketAttributeDatas(row);
|
|
const singleVideoAfterSearchRate = normalizeMarketListRate(
|
|
readMarketFieldValue(row, attributeDatas, "avg_search_after_view_rate_30d")
|
|
);
|
|
return {
|
|
authorId: readString(readMarketFieldValue(row, attributeDatas, "star_id")) ?? readString(readMarketFieldValue(row, attributeDatas, "id")) ?? "",
|
|
authorName: readString(readMarketFieldValue(row, attributeDatas, "nickname")) ?? readString(readMarketFieldValue(row, attributeDatas, "nick_name")) ?? "",
|
|
exportFields: buildMarketExportFieldFallbacks(row, attributeDatas),
|
|
hasDirectRatesSource: true,
|
|
location: readMarketLocation(row, attributeDatas),
|
|
price21To60s: readMarketPrice21To60s(row, attributeDatas),
|
|
rates: singleVideoAfterSearchRate ? {
|
|
singleVideoAfterSearchRate
|
|
} : void 0
|
|
};
|
|
}
|
|
function parseMarketListResponse(payload) {
|
|
const container = findMarketListContainer(payload);
|
|
if (!container) {
|
|
return null;
|
|
}
|
|
const marketList = readMarketListArray(container);
|
|
if (!marketList) {
|
|
return null;
|
|
}
|
|
return {
|
|
currentPage: readKnownNumberDeep(container, PAGE_NUMBER_KEYS) ?? void 0,
|
|
pageSize: readKnownNumberDeep(container, PAGE_SIZE_KEYS) ?? void 0,
|
|
records: marketList.map((row) => isRecord(row) ? mapMarketListRow(row) : null).filter(
|
|
(row) => row !== null && Boolean(row.authorId || row.authorName)
|
|
),
|
|
totalCount: readKnownNumberDeep(container, TOTAL_COUNT_KEYS) ?? void 0,
|
|
totalPages: readKnownNumberDeep(container, TOTAL_PAGE_KEYS) ?? void 0
|
|
};
|
|
}
|
|
function findMarketListContainer(value) {
|
|
const queue = [value];
|
|
while (queue.length > 0) {
|
|
const current = queue.shift();
|
|
if (!isRecord(current)) {
|
|
continue;
|
|
}
|
|
if (readMarketListArray(current)) {
|
|
return current;
|
|
}
|
|
Object.values(current).forEach((entry) => {
|
|
queue.push(unwrapVueRef(entry));
|
|
});
|
|
}
|
|
return null;
|
|
}
|
|
function readMarketListArray(record) {
|
|
const marketList = unwrapVueRef(record.marketList);
|
|
if (Array.isArray(marketList)) {
|
|
return marketList;
|
|
}
|
|
const authors = unwrapVueRef(record.authors);
|
|
if (Array.isArray(authors)) {
|
|
return authors;
|
|
}
|
|
return null;
|
|
}
|
|
function unwrapVueRef(value) {
|
|
if (isRecord(value) && "value" in value) {
|
|
return value.value;
|
|
}
|
|
return value;
|
|
}
|
|
function isRecord(value) {
|
|
return typeof value === "object" && value !== null;
|
|
}
|
|
function readMarketAttributeDatas(record) {
|
|
return isRecord(record.attribute_datas) ? record.attribute_datas : {};
|
|
}
|
|
function readMarketFieldValue(record, attributeDatas, field) {
|
|
return record[field] ?? attributeDatas[field];
|
|
}
|
|
function readString(value) {
|
|
return typeof value === "string" ? value : null;
|
|
}
|
|
function normalizeMarketListRate(value) {
|
|
if (typeof value === "number") {
|
|
return normalizeFractionRateDisplay(String(value));
|
|
}
|
|
return typeof value === "string" ? normalizeFractionRateDisplay(value) : null;
|
|
}
|
|
function normalizeExportCellText(value) {
|
|
return value?.replace(/\s+/g, " ").trim() ?? "";
|
|
}
|
|
function buildMarketExportFieldFallbacks(record, attributeDatas) {
|
|
const exportFields = {};
|
|
const authorInfo = buildMarketAuthorInfo(record, attributeDatas);
|
|
const authorType = buildMarketAuthorType(record, attributeDatas);
|
|
const contentTheme = buildMarketContentTheme(record, attributeDatas);
|
|
const connectedUsers = formatWanValue(
|
|
readNumericValue(readMarketFieldValue(record, attributeDatas, "link_link_cnt_by_industry"))
|
|
);
|
|
const followerCount = formatWanValue(
|
|
readNumericValue(readMarketFieldValue(record, attributeDatas, "follower"))
|
|
);
|
|
const expectedCpm = formatDecimalDisplay(
|
|
readNumericValue(readMarketFieldValue(record, attributeDatas, "prospective_20_60_cpm"))
|
|
);
|
|
const expectedPlayCount = formatWanValue(
|
|
readNumericValue(readMarketFieldValue(record, attributeDatas, "expected_play_num"))
|
|
);
|
|
const interactionRate = formatFractionPercent(
|
|
readNumericValue(readMarketFieldValue(record, attributeDatas, "interact_rate_within_30d"))
|
|
);
|
|
const finishRate = formatFractionPercent(
|
|
readNumericValue(readMarketFieldValue(record, attributeDatas, "play_over_rate_within_30d"))
|
|
);
|
|
const burstRate = readBurstRateDisplay(
|
|
readNumericValue(readMarketFieldValue(record, attributeDatas, "burst_text_rate"))
|
|
);
|
|
const price21To60s = readMarketPrice21To60s(record, attributeDatas);
|
|
const representativeVideo = readMarketRepresentativeVideo(record, attributeDatas);
|
|
assignExportField(exportFields, "\u8FBE\u4EBA\u4FE1\u606F", authorInfo);
|
|
assignExportField(exportFields, "\u4EE3\u8868\u89C6\u9891", representativeVideo);
|
|
assignExportField(exportFields, "\u8FBE\u4EBA\u7C7B\u578B", authorType);
|
|
assignExportField(exportFields, "\u5185\u5BB9\u4E3B\u9898", contentTheme);
|
|
assignExportField(exportFields, "\u8FDE\u63A5\u7528\u6237\u6570", connectedUsers);
|
|
assignExportField(exportFields, "\u7C89\u4E1D\u6570", followerCount);
|
|
assignExportField(exportFields, "\u9884\u671FCPM", expectedCpm);
|
|
assignExportField(exportFields, "\u9884\u671F\u64AD\u653E\u91CF", expectedPlayCount);
|
|
assignExportField(exportFields, "\u4E92\u52A8\u7387", interactionRate);
|
|
assignExportField(exportFields, "\u5B8C\u64AD\u7387", finishRate);
|
|
assignExportField(exportFields, "\u7206\u6587\u7387", burstRate);
|
|
assignExportField(exportFields, "21-60s\u62A5\u4EF7", price21To60s);
|
|
return Object.keys(exportFields).length > 0 ? exportFields : void 0;
|
|
}
|
|
function assignExportField(exportFields, key, value) {
|
|
if (hasTextValue(value)) {
|
|
exportFields[key] = value;
|
|
}
|
|
}
|
|
function hasTextValue(value) {
|
|
return Boolean(value && value.trim().length > 0);
|
|
}
|
|
function buildMarketAuthorInfo(record, attributeDatas) {
|
|
const nickname = readString(readMarketFieldValue(record, attributeDatas, "nickname")) ?? readString(readMarketFieldValue(record, attributeDatas, "nick_name")) ?? "";
|
|
const parts = [
|
|
nickname,
|
|
readMarketGenderLabel(readMarketFieldValue(record, attributeDatas, "gender")),
|
|
readString(readMarketFieldValue(record, attributeDatas, "city")) ?? ""
|
|
].filter((value) => Boolean(value));
|
|
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
}
|
|
function buildMarketAuthorType(record, attributeDatas) {
|
|
const tagsRelation = readRecordLike(
|
|
readMarketFieldValue(record, attributeDatas, "tags_relation")
|
|
);
|
|
if (tagsRelation) {
|
|
const primaryTag = Object.keys(tagsRelation)[0];
|
|
if (hasTextValue(primaryTag)) {
|
|
return primaryTag;
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
function buildMarketContentTheme(record, attributeDatas) {
|
|
const themes = readStringArray(
|
|
readMarketFieldValue(record, attributeDatas, "content_theme_labels_180d")
|
|
);
|
|
if (themes.length === 0) {
|
|
return void 0;
|
|
}
|
|
if (themes.length <= 2) {
|
|
return themes.join(" ");
|
|
}
|
|
return `${themes.slice(0, 2).join(" ")} ${themes.length - 2}+`;
|
|
}
|
|
function readMarketLocation(record, attributeDatas) {
|
|
return readString(readMarketFieldValue(record, attributeDatas, "city")) ?? void 0;
|
|
}
|
|
function readMarketPrice21To60s(record, attributeDatas) {
|
|
return formatCurrencyValue(
|
|
readNumericValue(readMarketFieldValue(record, attributeDatas, "price_20_60"))
|
|
);
|
|
}
|
|
function readMarketRepresentativeVideo(record, attributeDatas) {
|
|
const items = readArrayLike(readMarketFieldValue(record, attributeDatas, "items"));
|
|
for (const item of items) {
|
|
if (!isRecord(item)) {
|
|
continue;
|
|
}
|
|
const title = readString(item.title);
|
|
if (hasTextValue(title)) {
|
|
return normalizeExportCellText(title);
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
function readMarketGenderLabel(value) {
|
|
const rawValue = typeof value === "number" ? String(value) : readString(value);
|
|
if (rawValue === "1") {
|
|
return "\u7537";
|
|
}
|
|
if (rawValue === "2") {
|
|
return "\u5973";
|
|
}
|
|
return void 0;
|
|
}
|
|
function readBurstRateDisplay(value) {
|
|
if (value === null) {
|
|
return void 0;
|
|
}
|
|
if (value <= 0) {
|
|
return "-";
|
|
}
|
|
return formatFractionPercent(value);
|
|
}
|
|
function formatCurrencyValue(value) {
|
|
if (value === null) {
|
|
return void 0;
|
|
}
|
|
return `\xA5${value.toLocaleString("en-US", {
|
|
maximumFractionDigits: 0
|
|
})}`;
|
|
}
|
|
function formatWanValue(value) {
|
|
if (value === null) {
|
|
return void 0;
|
|
}
|
|
return `${formatDecimalWithGrouping(value / 1e4)}w`;
|
|
}
|
|
function formatFractionPercent(value) {
|
|
if (value === null) {
|
|
return void 0;
|
|
}
|
|
return `${formatDecimalDisplay(value * 100)}%`;
|
|
}
|
|
function formatDecimalDisplay(value) {
|
|
if (value === null) {
|
|
return void 0;
|
|
}
|
|
return value.toLocaleString("en-US", {
|
|
maximumFractionDigits: 1,
|
|
minimumFractionDigits: 0,
|
|
useGrouping: false
|
|
});
|
|
}
|
|
function formatDecimalWithGrouping(value) {
|
|
return value.toLocaleString("en-US", {
|
|
maximumFractionDigits: 1,
|
|
minimumFractionDigits: 0
|
|
});
|
|
}
|
|
function readNumericValue(value) {
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
return value;
|
|
}
|
|
if (typeof value === "string") {
|
|
const trimmedValue = value.trim();
|
|
if (!trimmedValue) {
|
|
return null;
|
|
}
|
|
const parsedValue = Number(trimmedValue);
|
|
return Number.isFinite(parsedValue) ? parsedValue : null;
|
|
}
|
|
return null;
|
|
}
|
|
function readStringArray(value) {
|
|
if (Array.isArray(value)) {
|
|
return value.filter((item) => typeof item === "string");
|
|
}
|
|
if (typeof value === "string") {
|
|
try {
|
|
const parsedValue = JSON.parse(value);
|
|
return Array.isArray(parsedValue) ? parsedValue.filter((item) => typeof item === "string") : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
function readArrayLike(value) {
|
|
if (Array.isArray(value)) {
|
|
return value;
|
|
}
|
|
if (typeof value === "string") {
|
|
try {
|
|
const parsedValue = JSON.parse(value);
|
|
return Array.isArray(parsedValue) ? parsedValue : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
function readRecordLike(value) {
|
|
if (isRecord(value)) {
|
|
return value;
|
|
}
|
|
if (typeof value === "string") {
|
|
try {
|
|
const parsedValue = JSON.parse(value);
|
|
return isRecord(parsedValue) ? parsedValue : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function readKnownNumber(record, keys) {
|
|
for (const key of keys) {
|
|
const value = readNumericValue(record[key]);
|
|
if (value !== null) {
|
|
return value;
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
function readKnownNumberDeep(value, keys) {
|
|
if (!isRecord(value)) {
|
|
return null;
|
|
}
|
|
const directValue = readKnownNumber(value, keys);
|
|
if (typeof directValue === "number") {
|
|
return directValue;
|
|
}
|
|
for (const nestedValue of Object.values(value)) {
|
|
const candidate = readKnownNumberDeep(unwrapVueRef(nestedValue), keys);
|
|
if (typeof candidate === "number") {
|
|
return candidate;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// src/content/market/page-bridge.ts
|
|
var BRIDGE_MARKER = "__SCES_MARKET_PAGE_BRIDGE_INSTALLED__";
|
|
var MARKET_SEARCH_REQUEST_PATH = "/gw/api/gsearch/search_for_author_square";
|
|
var SERIALIZED_MARKET_ROWS_ATTRIBUTE = "data-sces-market-rows";
|
|
installMarketPageBridge();
|
|
function installMarketPageBridge() {
|
|
if (window[BRIDGE_MARKER]) {
|
|
syncSerializedMarketRows();
|
|
return;
|
|
}
|
|
window[BRIDGE_MARKER] = true;
|
|
installMarketRequestSnapshotBridge();
|
|
syncSerializedMarketRows();
|
|
const observer = new MutationObserver(() => {
|
|
syncSerializedMarketRows();
|
|
});
|
|
observer.observe(document.documentElement, {
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
window.setInterval(() => {
|
|
syncSerializedMarketRows();
|
|
}, 1e3);
|
|
}
|
|
function installMarketRequestSnapshotBridge() {
|
|
installFetchSnapshotBridge();
|
|
installXmlHttpRequestSnapshotBridge();
|
|
}
|
|
function syncSerializedMarketRows() {
|
|
if (typeof document === "undefined") {
|
|
return;
|
|
}
|
|
const nextSerializedRows = JSON.stringify(readSerializedMarketRows());
|
|
if (document.documentElement.getAttribute(SERIALIZED_MARKET_ROWS_ATTRIBUTE) !== nextSerializedRows) {
|
|
document.documentElement.setAttribute(
|
|
SERIALIZED_MARKET_ROWS_ATTRIBUTE,
|
|
nextSerializedRows
|
|
);
|
|
}
|
|
}
|
|
function installFetchSnapshotBridge() {
|
|
if (typeof window.fetch !== "function") {
|
|
return;
|
|
}
|
|
const originalFetch = window.fetch.bind(window);
|
|
window.fetch = async (input, init) => {
|
|
const requestSnapshot = readFetchSnapshot(input, init);
|
|
const response = await originalFetch(input, init);
|
|
if (requestSnapshot) {
|
|
const clonedResponse = response.clone();
|
|
void captureMarketSnapshotFromResponse(
|
|
requestSnapshot,
|
|
() => clonedResponse.json()
|
|
);
|
|
}
|
|
return response;
|
|
};
|
|
}
|
|
function installXmlHttpRequestSnapshotBridge() {
|
|
const OriginalXmlHttpRequest = window.XMLHttpRequest;
|
|
if (!OriginalXmlHttpRequest) {
|
|
return;
|
|
}
|
|
const originalOpen = OriginalXmlHttpRequest.prototype.open;
|
|
const originalSend = OriginalXmlHttpRequest.prototype.send;
|
|
const originalSetRequestHeader = OriginalXmlHttpRequest.prototype.setRequestHeader;
|
|
OriginalXmlHttpRequest.prototype.open = function(method, url, ...rest) {
|
|
this.__scesMarketSnapshot = {
|
|
headers: {},
|
|
method,
|
|
url: String(url)
|
|
};
|
|
return originalOpen.call(this, method, url, ...rest);
|
|
};
|
|
OriginalXmlHttpRequest.prototype.setRequestHeader = function(name, value) {
|
|
this.__scesMarketSnapshot?.headers && (this.__scesMarketSnapshot.headers[name] = value);
|
|
return originalSetRequestHeader.call(this, name, value);
|
|
};
|
|
OriginalXmlHttpRequest.prototype.send = function(body) {
|
|
const snapshotState = this.__scesMarketSnapshot;
|
|
if (snapshotState) {
|
|
snapshotState.body = typeof body === "string" ? body : void 0;
|
|
this.addEventListener("load", () => {
|
|
if (this.status < 200 || this.status >= 300 || typeof this.responseText !== "string") {
|
|
return;
|
|
}
|
|
void captureMarketSnapshotFromResponse(
|
|
snapshotState,
|
|
async () => JSON.parse(this.responseText)
|
|
);
|
|
});
|
|
}
|
|
return originalSend.call(this, body);
|
|
};
|
|
}
|
|
async function captureMarketSnapshotFromResponse(snapshot, readPayload) {
|
|
if (!isMarketSearchRequest(snapshot.url)) {
|
|
return;
|
|
}
|
|
try {
|
|
const payload = await readPayload();
|
|
if (!parseMarketListResponse(payload)) {
|
|
return;
|
|
}
|
|
writeMarketListRequestSnapshot(document, {
|
|
body: snapshot.body,
|
|
headers: snapshot.headers,
|
|
method: snapshot.method,
|
|
url: snapshot.url
|
|
});
|
|
} catch {
|
|
}
|
|
}
|
|
function readSerializedMarketRows() {
|
|
const marketList = readMarketList();
|
|
return marketList.map((row) => {
|
|
const attributeDatas = isRecord2(row.attribute_datas) ? row.attribute_datas : {};
|
|
const singleVideoAfterSearchRate = readNormalizedFractionRate(
|
|
attributeDatas.avg_search_after_view_rate_30d
|
|
);
|
|
return {
|
|
authorId: readString2(row.star_id) ?? readString2(attributeDatas.id) ?? "",
|
|
authorName: readString2(attributeDatas.nickname) ?? readString2(row.nick_name) ?? "",
|
|
singleVideoAfterSearchRate
|
|
};
|
|
}).filter((row) => Boolean(row.authorId || row.authorName));
|
|
}
|
|
function readFetchSnapshot(input, init) {
|
|
const request = input instanceof Request ? input : null;
|
|
const method = init?.method ?? request?.method ?? "GET";
|
|
const url = request?.url ?? String(input);
|
|
const body = typeof init?.body === "string" ? init.body : typeof request?.bodyUsed === "boolean" && request.bodyUsed ? void 0 : void 0;
|
|
const headers = serializeHeaders(init?.headers ?? request?.headers);
|
|
return {
|
|
body,
|
|
headers,
|
|
method,
|
|
url
|
|
};
|
|
}
|
|
function serializeHeaders(headers) {
|
|
if (!headers) {
|
|
return void 0;
|
|
}
|
|
if (headers instanceof Headers) {
|
|
return Object.fromEntries(headers.entries());
|
|
}
|
|
if (Array.isArray(headers)) {
|
|
return Object.fromEntries(headers);
|
|
}
|
|
return Object.fromEntries(
|
|
Object.entries(headers).map(([key, value]) => [key, String(value)])
|
|
);
|
|
}
|
|
function readMarketList() {
|
|
if (typeof document === "undefined") {
|
|
return [];
|
|
}
|
|
const marketRoot = document.querySelector(".base-author-list");
|
|
const setupState = marketRoot?.__vue__?._setupState;
|
|
if (!setupState) {
|
|
return [];
|
|
}
|
|
for (const value of Object.values(setupState)) {
|
|
const candidate = unwrapVueRef2(value);
|
|
if (Array.isArray(candidate) && looksLikeMarketList(candidate)) {
|
|
return candidate;
|
|
}
|
|
if (!isRecord2(candidate) || !Array.isArray(candidate.marketList)) {
|
|
continue;
|
|
}
|
|
if (looksLikeMarketList(candidate.marketList)) {
|
|
return candidate.marketList;
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
function isMarketSearchRequest(url) {
|
|
return url === MARKET_SEARCH_REQUEST_PATH || url.startsWith(`${MARKET_SEARCH_REQUEST_PATH}?`) || url.includes(`${MARKET_SEARCH_REQUEST_PATH}?`) || url.endsWith(MARKET_SEARCH_REQUEST_PATH);
|
|
}
|
|
function looksLikeMarketList(value) {
|
|
const firstRow = value[0];
|
|
return isRecord2(firstRow) && ("star_id" in firstRow || "attribute_datas" in firstRow);
|
|
}
|
|
function unwrapVueRef2(value) {
|
|
if (isRecord2(value) && "value" in value) {
|
|
return value.value;
|
|
}
|
|
return value;
|
|
}
|
|
function isRecord2(value) {
|
|
return typeof value === "object" && value !== null;
|
|
}
|
|
function readString2(value) {
|
|
return typeof value === "string" ? value : null;
|
|
}
|
|
function readNormalizedFractionRate(value) {
|
|
return typeof value === "string" ? normalizeFractionRateDisplay(value) ?? void 0 : void 0;
|
|
}
|
|
})();
|