5554 lines
195 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
(() => {
// src/shared/rate-normalizer.ts
function normalizeRateDisplay(value) {
const trimmedValue = value.trim();
const rangeMatch = trimmedValue.match(
/^([0-9]+(?:\.[0-9]+)?)\s*%?\s*-\s*([0-9]+(?:\.[0-9]+)?)\s*%$/
);
if (rangeMatch) {
const [, lowerBound, upperBound] = rangeMatch;
return `${lowerBound}% - ${upperBound}%`;
}
return trimmedValue.replace(/\s+/g, "");
}
function normalizeFractionRateDisplay(value) {
const numericValue = Number(value);
if (!Number.isFinite(numericValue)) {
return null;
}
const percentageValue = numericValue * 100;
return `${trimTrailingZeros(percentageValue.toFixed(6))}%`;
}
function parseRateLowerBound(value) {
const comparableRate = toComparableRate(value);
return comparableRate?.numeric ?? null;
}
function compareRateValues(leftValue, rightValue) {
const leftComparable = toComparableRate(leftValue);
const rightComparable = toComparableRate(rightValue);
if (!leftComparable && !rightComparable) {
return 0;
}
if (!leftComparable) {
return 1;
}
if (!rightComparable) {
return -1;
}
if (leftComparable.numeric !== rightComparable.numeric) {
return leftComparable.numeric - rightComparable.numeric;
}
if (leftComparable.isLessThan === rightComparable.isLessThan) {
return 0;
}
return leftComparable.isLessThan ? -1 : 1;
}
function toComparableRate(value) {
if (!value) {
return null;
}
const normalizedValue = normalizeRateDisplay(value);
const lessThanMatch = normalizedValue.match(/^<\s*([0-9]+(?:\.[0-9]+)?)%$/);
if (lessThanMatch) {
return {
isLessThan: true,
numeric: Number(lessThanMatch[1])
};
}
const rangeMatch = normalizedValue.match(
/^([0-9]+(?:\.[0-9]+)?)%\s*-\s*([0-9]+(?:\.[0-9]+)?)%$/
);
if (rangeMatch) {
return {
isLessThan: false,
numeric: Number(rangeMatch[1])
};
}
const exactMatch = normalizedValue.match(/^([0-9]+(?:\.[0-9]+)?)%$/);
if (exactMatch) {
return {
isLessThan: false,
numeric: Number(exactMatch[1])
};
}
return null;
}
function trimTrailingZeros(value) {
return value.replace(/\.?0+$/, "");
}
// src/shared/csv.ts
function escapeCsvCell(value) {
if (/[",\n]/.test(value)) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
}
// src/content/market/csv-exporter.ts
var FALLBACK_BASE_COLUMNS = [
{
header: "\u8FBE\u4EBAID",
readValue: (record) => record.authorId
},
{
header: "\u8FBE\u4EBA\u540D\u79F0",
readValue: (record) => record.authorName
},
{
header: "\u5730\u533A",
readValue: (record) => record.location ?? ""
},
{
header: "21-60s\u62A5\u4EF7",
readValue: (record) => record.price21To60s ?? ""
}
];
var RATE_COLUMNS = [
{
header: "\u5355\u89C6\u9891\u770B\u540E\u641C\u7387",
readValue: (record) => record.rates?.singleVideoAfterSearchRate ? normalizeRateDisplay(record.rates.singleVideoAfterSearchRate) : ""
},
{
header: "\u4E2A\u4EBA\u89C6\u9891\u770B\u540E\u641C\u7387",
readValue: (record) => record.rates?.personalVideoAfterSearchRate ? normalizeRateDisplay(record.rates.personalVideoAfterSearchRate) : ""
}
];
var BACKEND_METRIC_COLUMNS = [
{
header: "\u79D2\u601Dapi-\u770B\u540E\u641C\u7387",
readValue: (record) => record.backendMetrics?.afterViewSearchRate ?? ""
},
{
header: "\u79D2\u601Dapi-\u770B\u540E\u641C\u6570",
readValue: (record) => record.backendMetrics?.afterViewSearchCount ?? ""
},
{
header: "\u79D2\u601Dapi-\u65B0\u589EA3\u6570",
readValue: (record) => record.backendMetrics?.a3IncreaseCount ?? ""
},
{
header: "\u79D2\u601Dapi-\u65B0\u589EA3\u7387",
readValue: (record) => record.backendMetrics?.newA3Rate ?? ""
},
{
header: "\u79D2\u601Dapi-CPA3",
readValue: (record) => record.backendMetrics?.cpa3 ?? ""
},
{
header: "\u79D2\u601Dapi-cp_search",
readValue: (record) => record.backendMetrics?.cpSearch ?? ""
}
];
function buildMarketCsv(records) {
const csvColumns = buildMarketCsvColumns(records);
const headerLine = csvColumns.map((column) => column.header).join(",");
const rowLines = records.map(
(record) => csvColumns.map((column) => escapeCsvCell(column.readValue(record))).join(",")
);
return [headerLine, ...rowLines].join("\n");
}
function buildMarketCsvColumns(records) {
const baseColumns = buildBaseColumns(records);
return [...baseColumns, ...RATE_COLUMNS, ...BACKEND_METRIC_COLUMNS];
}
function buildBaseColumns(records) {
const orderedHeaders = [];
const seenHeaders = /* @__PURE__ */ new Set();
const excludedHeaders = /* @__PURE__ */ new Set(["\u4EE3\u8868\u89C6\u9891"]);
records.forEach((record) => {
Object.keys(record.exportFields ?? {}).forEach((header) => {
if (seenHeaders.has(header) || excludedHeaders.has(header)) {
return;
}
seenHeaders.add(header);
orderedHeaders.push(header);
});
});
if (orderedHeaders.length === 0) {
return FALLBACK_BASE_COLUMNS;
}
return orderedHeaders.map((header) => ({
header,
readValue: (record) => record.exportFields?.[header] ?? ""
}));
}
// src/content/market/audience-profile-csv.ts
var PROFILE_LAYOUTS = [
{ includeGender: true, kind: "audience", label: "\u89C2\u4F17\u753B\u50CF" },
{ includeGender: true, kind: "fans", label: "\u7C89\u4E1D\u753B\u50CF" },
{ includeGender: false, kind: "longtimeFans", label: "\u94C1\u7C89\u753B\u50CF" }
];
var GENDER_LABELS = ["\u7537\u6027", "\u5973\u6027"];
var AGE_LABELS = ["18-23", "24-30", "31-40", "41-50", "50+"];
var CITY_TIER_LABELS = [
"\u4E00\u7EBF\u57CE\u5E02",
"\u4E8C\u7EBF\u57CE\u5E02",
"\u4E09\u7EBF\u57CE\u5E02",
"\u56DB\u7EBF\u57CE\u5E02",
"\u4E94\u7EBF\u57CE\u5E02"
];
var CROWD_LABELS = [
"\u7CBE\u81F4\u5988\u5988",
"\u90FD\u5E02\u94F6\u53D1",
"\u65B0\u9510\u767D\u9886",
"\u8D44\u6DF1\u4E2D\u4EA7",
"\u90FD\u5E02\u84DD\u9886",
"Z\u4E16\u4EE3",
"\u5C0F\u9547\u4E2D\u8001\u5E74",
"\u5C0F\u9547\u9752\u5E74"
];
var BUSINESS_VIDEO_LAYOUTS = [
{ key: "personalVideo", label: "\u4E2A\u4EBA\u89C6\u9891" },
{ key: "xingtuVideo", label: "\u661F\u56FE\u89C6\u9891" }
];
var BUSINESS_VIDEO_METRIC_LAYOUTS = [
{ key: "medianPlay", label: "\u64AD\u653E\u91CF\u4E2D\u4F4D\u6570" },
{ key: "finishRate", label: "\u5B8C\u64AD\u7387" },
{ key: "interactionRate", label: "\u4E92\u52A8\u7387" },
{ key: "publishedItems", label: "\u53D1\u5E03\u4F5C\u54C1" },
{ key: "averageDuration", label: "\u5E73\u5747\u65F6\u957F" },
{ key: "averageLike", label: "\u5E73\u5747\u70B9\u8D5E" },
{ key: "averageComment", label: "\u5E73\u5747\u8BC4\u8BBA" },
{ key: "averageShare", label: "\u5E73\u5747\u8F6C\u53D1" }
];
var BUSINESS_VIDEO_SECTION_LABEL = "\u5185\u5BB9\u6570\u636E";
var BUSINESS_ESTIMATE_SECTION_LABEL = "\u6548\u679C\u9884\u4F30";
var BUSINESS_ESTIMATE_LAYOUTS = [
{ key: "oneToTwenty", label: "1-20s\u89C6\u9891" },
{ key: "twentyToSixty", label: "20-60s\u89C6\u9891" },
{ key: "overSixty", label: "60s\u4EE5\u4E0A\u89C6\u9891" }
];
var BUSINESS_ESTIMATE_METRIC_LAYOUTS = [
{ key: "expectedCpm", label: "\u9884\u671FCPM" },
{ key: "expectedCpe", label: "\u9884\u671FCPE" },
{ key: "expectedPlay", label: "\u9884\u671F\u64AD\u653E\u91CF" },
{ key: "hotRate", label: "\u7206\u6587\u7387" }
];
function buildAudienceProfileCsv(rows) {
const marketColumns = buildMarketCsvColumns(rows.map((row) => row.record));
const csvColumns = [
...marketColumns.map(toMarketColumn),
...buildBusinessAbilityColumns(),
...PROFILE_LAYOUTS.flatMap((layout) => buildProfileColumns(layout))
];
const headerLine = csvColumns.map((column) => column.header).join(",");
const rowLines = rows.map(
(row) => csvColumns.map((column) => escapeCsvCell(column.readValue(row))).join(",")
);
return [headerLine, ...rowLines].join("\n");
}
function buildBusinessAbilityColumns() {
return [
...BUSINESS_VIDEO_LAYOUTS.flatMap(
(videoLayout) => BUSINESS_VIDEO_METRIC_LAYOUTS.map((metricLayout) => ({
header: `${BUSINESS_VIDEO_SECTION_LABEL}-${videoLayout.label}-${metricLayout.label}`,
readValue: (row) => readBusinessVideoValue(row, videoLayout.key, metricLayout.key)
}))
),
...BUSINESS_ESTIMATE_LAYOUTS.flatMap(
(durationLayout) => BUSINESS_ESTIMATE_METRIC_LAYOUTS.map((metricLayout) => ({
header: `${BUSINESS_ESTIMATE_SECTION_LABEL}-${durationLayout.label}-${metricLayout.label}`,
readValue: (row) => readBusinessEstimateValue(row, durationLayout.key, metricLayout.key)
}))
)
];
}
function readBusinessVideoValue(row, videoKey, metricKey) {
const businessAbility = row.businessAbility;
if (!businessAbility || businessAbility.status !== "success") {
return "";
}
return businessAbility.videos[videoKey]?.[metricKey] ?? "";
}
function readBusinessEstimateValue(row, durationKey, metricKey) {
const businessAbility = row.businessAbility;
if (!businessAbility || businessAbility.status !== "success") {
return "";
}
return businessAbility.estimates[durationKey]?.[metricKey] ?? "";
}
function toMarketColumn(column) {
return {
header: column.header,
readValue: (row) => column.readValue(row.record)
};
}
function buildProfileColumns(layout) {
const columns = [];
if (layout.includeGender) {
columns.push(
...buildFixedDistributionColumns(
layout.label,
layout.kind,
"gender",
GENDER_LABELS
)
);
}
columns.push(
...buildFixedDistributionColumns(layout.label, layout.kind, "age", AGE_LABELS),
...buildFixedDistributionColumns(
layout.label,
layout.kind,
"cityTier",
CITY_TIER_LABELS
),
...buildFixedDistributionColumns(layout.label, layout.kind, "crowd", CROWD_LABELS)
);
return columns;
}
function buildFixedDistributionColumns(prefix, kind, key, labels) {
return labels.map((label) => ({
header: `${prefix}-${label}\u5360\u6BD4`,
readValue: (row) => readDistributionValue(row.profiles[kind], key, label)
}));
}
function readDistributionValue(profile, key, label) {
if (profile.status !== "success") {
return "";
}
return readProfileDistributionItems(profile, key).find(
(candidate) => candidate.label === label
)?.value ?? "0%";
}
function readProfileDistributionItems(profile, key) {
return profile.status === "success" ? profile[key] ?? [] : [];
}
// src/content/market/audience-profile-client.ts
var SECTION_BY_DISPLAY = [
[/性别/, "gender"],
[/年龄/, "age"],
[/省份|全国省份/, "province"],
[/城市分布|地域/, "cityTop"],
[/城市等级/, "cityTier"],
[/兴趣/, "interest"],
[/八大人群/, "crowd"]
];
var GENDER_LABELS2 = {
female: "\u5973\u6027",
male: "\u7537\u6027"
};
var AGE_ORDER = ["18-23", "24-30", "31-40", "41-50", "50+"];
var CITY_TIER_ORDER = ["\u4E00\u7EBF", "\u65B0\u4E00\u7EBF", "\u4E8C\u7EBF", "\u4E09\u7EBF", "\u56DB\u7EBF", "\u4E94\u7EBF"];
var AUDIENCE_PROFILE_TARGETS = {
audience: { linkType: 5, source: "audienceDistribution" },
fans: { authorType: 1, source: "fansDistribution" },
longtimeFans: { authorType: 5, source: "fansDistribution" }
};
function createAudienceProfileClient(options = {}) {
const baseUrl = options.baseUrl ?? resolveBaseUrl();
const fetchImpl = options.fetchImpl ?? defaultFetch;
const timeoutMs = options.timeoutMs ?? 8e3;
return {
async loadAudienceProfile(record, target) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetchImpl(
buildAudienceProfileUrl(record.authorId, baseUrl, target),
{
credentials: "include",
method: "GET",
signal: controller.signal
}
);
if (!response.ok) {
return {
failureReason: "request-failed",
status: "failed"
};
}
return mapAudienceProfileResponse(await response.json());
} catch (error) {
return {
failureReason: error instanceof Error && error.name === "AbortError" ? "timeout" : "request-failed",
status: "failed"
};
} finally {
clearTimeout(timeoutId);
}
}
};
}
function buildAudienceProfileUrl(authorId, baseUrl, target) {
const url = new URL(
target.source === "audienceDistribution" ? "/gw/api/data_sp/author_audience_distribution" : "/gw/api/data_sp/get_author_fans_distribution",
baseUrl
);
url.searchParams.set("o_author_id", authorId);
url.searchParams.set("platform_source", "1");
if (target.source === "audienceDistribution") {
url.searchParams.set("platform_channel", "1");
url.searchParams.set("link_type", String(target.linkType));
} else {
url.searchParams.set("author_type", String(target.authorType));
}
return url.toString();
}
function mapAudienceProfileResponse(payload) {
if (!isRecord(payload) || !Array.isArray(payload.distributions)) {
return {
failureReason: "bad-response",
status: "failed"
};
}
const profile = {
status: "success"
};
payload.distributions.forEach((section) => {
if (!isRecord(section)) {
return;
}
const display = readString(section.type_display);
const sectionName = resolveSection(display);
if (!sectionName || !Array.isArray(section.distribution_list)) {
return;
}
profile[sectionName] = normalizeDistributionItems(
section.distribution_list,
sectionName
);
});
if (Object.keys(profile).length === 1) {
return {
failureReason: "missing-profile",
status: "failed"
};
}
return profile;
}
function normalizeDistributionItems(rawItems, sectionName) {
const parsedItems = rawItems.map((item) => {
if (!isRecord(item)) {
return null;
}
const key = readString(item.distribution_key);
const value = readNumber(item.distribution_value);
if (!key || value === null) {
return null;
}
return {
label: normalizeLabel(key, sectionName),
rawLabel: key,
value
};
}).filter(
(item) => Boolean(item)
);
const total = parsedItems.reduce((sum, item) => sum + item.value, 0);
if (total <= 0) {
return [];
}
return parsedItems.sort((left, right) => compareDistributionItems(left, right, sectionName)).map((item) => ({
label: item.label,
value: formatPercent(item.value / total)
}));
}
function compareDistributionItems(left, right, sectionName) {
if (sectionName === "age") {
return orderIndex(AGE_ORDER, left.rawLabel) - orderIndex(AGE_ORDER, right.rawLabel);
}
if (sectionName === "cityTier") {
return orderIndex(CITY_TIER_ORDER, left.rawLabel) - orderIndex(CITY_TIER_ORDER, right.rawLabel);
}
return right.value - left.value;
}
function orderIndex(order, value) {
const index = order.indexOf(value);
return index === -1 ? order.length : index;
}
function normalizeLabel(label, sectionName) {
if (sectionName === "gender") {
return GENDER_LABELS2[label] ?? label;
}
if (sectionName === "cityTier" && !label.endsWith("\u57CE\u5E02")) {
return `${label}\u57CE\u5E02`;
}
return label;
}
function resolveSection(display) {
if (!display) {
return null;
}
return SECTION_BY_DISPLAY.find(([pattern]) => pattern.test(display))?.[1] ?? null;
}
function formatPercent(value) {
const percent = Math.round(value * 1e3) / 10;
return `${Number.isInteger(percent) ? percent.toFixed(0) : percent.toFixed(1)}%`;
}
function readString(value) {
return typeof value === "string" && value.trim() ? value.trim() : null;
}
function readNumber(value) {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string" && value.trim()) {
const numericValue = Number(value);
return Number.isFinite(numericValue) ? numericValue : null;
}
return null;
}
function resolveBaseUrl() {
if (typeof location !== "undefined" && location.origin) {
return location.origin;
}
return "https://xingtu.cn";
}
async function defaultFetch(input, init) {
return fetch(input, init);
}
function isRecord(value) {
return typeof value === "object" && value !== null;
}
// src/content/market/author-base-client.ts
function createAuthorBaseClient(options = {}) {
const baseUrl = options.baseUrl ?? resolveBaseUrl2();
const fetchImpl = options.fetchImpl ?? defaultFetch2;
const timeoutMs = options.timeoutMs ?? 8e3;
return {
async loadAuthorBaseInfo(authorId) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetchImpl(
buildAuthorBaseInfoUrl(authorId, baseUrl),
{
credentials: "include",
method: "GET",
signal: controller.signal
}
);
if (!response.ok) {
return buildFailedRecord(authorId, "request-failed");
}
return mapAuthorBaseInfoResponse(authorId, await response.json());
} catch (error) {
return buildFailedRecord(
authorId,
error instanceof Error && error.name === "AbortError" ? "timeout" : "request-failed"
);
} finally {
clearTimeout(timeoutId);
}
}
};
}
function buildAuthorBaseInfoUrl(authorId, baseUrl) {
const url = new URL("/gw/api/author/get_author_base_info", baseUrl);
url.searchParams.set("o_author_id", authorId);
url.searchParams.set("platform_source", "1");
url.searchParams.set("platform_channel", "1");
url.searchParams.set("recommend", "true");
url.searchParams.set("need_sec_uid", "true");
url.searchParams.set("need_linkage_info", "true");
return url.toString();
}
function mapAuthorBaseInfoResponse(authorId, payload) {
if (!isRecord2(payload)) {
return buildFailedRecord(authorId, "bad-response");
}
const authorName = readString2(payload.nick_name);
if (!authorName) {
return buildFailedRecord(authorId, "missing-rate");
}
return {
authorId,
authorName,
status: "success"
};
}
function buildFailedRecord(authorId, failureReason) {
return {
authorId,
authorName: "",
failureReason,
status: "failed"
};
}
function readString2(value) {
return typeof value === "string" && value.trim() ? value.trim() : null;
}
function resolveBaseUrl2() {
if (typeof location !== "undefined" && location.origin) {
return location.origin;
}
return "https://xingtu.cn";
}
async function defaultFetch2(input, init) {
return fetch(input, init);
}
function isRecord2(value) {
return typeof value === "object" && value !== null;
}
// src/content/market/author-id-input.ts
var AUTHOR_ID_PATTERN = /^\d{16,20}$/;
function parseAuthorIds(input) {
const ids = [];
const duplicates = [];
const invalidTokens = [];
const seen = /* @__PURE__ */ new Set();
input.split(/[\s,;]+/).map((token) => token.trim()).filter(Boolean).forEach((token) => {
if (!/^\d+$/.test(token) || !AUTHOR_ID_PATTERN.test(token)) {
invalidTokens.push(token);
return;
}
if (seen.has(token)) {
duplicates.push(token);
return;
}
seen.add(token);
ids.push(token);
});
return {
duplicates,
ids,
invalidTokens
};
}
// src/content/market/business-ability-client.ts
var VIDEO_TYPES = {
personalVideo: 1,
xingtuVideo: 2
};
function createBusinessAbilityClient(options = {}) {
const baseUrl = options.baseUrl ?? resolveBaseUrl3();
const fetchImpl = options.fetchImpl ?? defaultFetch3;
const timeoutMs = options.timeoutMs ?? 8e3;
return {
async loadBusinessAbility(record) {
const personalVideo = await loadJson(
buildBusinessAbilityVideoUrl(record.authorId, baseUrl, VIDEO_TYPES.personalVideo)
);
const xingtuVideo = await loadJson(
buildBusinessAbilityVideoUrl(record.authorId, baseUrl, VIDEO_TYPES.xingtuVideo)
);
const estimates = await loadJson(
buildBusinessAbilityEstimateUrl(record.authorId, baseUrl)
);
if (!personalVideo.ok || !xingtuVideo.ok || !estimates.ok) {
return {
failureReason: personalVideo.failureReason ?? xingtuVideo.failureReason ?? estimates.failureReason,
status: "failed"
};
}
return {
estimates: mapBusinessAbilityEstimateResponse(estimates.payload),
status: "success",
videos: {
personalVideo: mapBusinessAbilityVideoResponse(personalVideo.payload),
xingtuVideo: mapBusinessAbilityVideoResponse(xingtuVideo.payload)
}
};
}
};
async function loadJson(url) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetchImpl(url, {
credentials: "include",
method: "GET",
signal: controller.signal
});
if (!response.ok) {
return { failureReason: "request-failed", ok: false };
}
return { ok: true, payload: await response.json() };
} catch (error) {
return {
failureReason: error instanceof Error && error.name === "AbortError" ? "timeout" : "request-failed",
ok: false
};
} finally {
clearTimeout(timeoutId);
}
}
}
function buildBusinessAbilityVideoUrl(authorId, baseUrl, videoType) {
const url = new URL("/gw/api/data_sp/get_author_spread_info", baseUrl);
url.searchParams.set("o_author_id", authorId);
url.searchParams.set("platform_source", "1");
url.searchParams.set("platform_channel", "1");
url.searchParams.set("type", String(videoType));
url.searchParams.set("flow_type", "0");
url.searchParams.set("only_assign", "true");
url.searchParams.set("range", "2");
return url.toString();
}
function buildBusinessAbilityEstimateUrl(authorId, baseUrl) {
const url = new URL(
"/gw/api/aggregator/get_author_commerce_spread_info",
baseUrl
);
url.searchParams.set("o_author_id", authorId);
return url.toString();
}
function mapBusinessAbilityVideoResponse(payload) {
const data = getPayloadData(payload);
return {
averageComment: formatWan(readNumber2(data?.comment_avg)),
averageDuration: formatDuration(readNumber2(data?.avg_duration)),
averageLike: formatWan(readNumber2(data?.like_avg)),
averageShare: formatWan(readNumber2(data?.share_avg)),
finishRate: formatBasisPointRate(readNestedNumber(data, "play_over_rate", "value")),
interactionRate: formatBasisPointRate(
readNestedNumber(data, "interact_rate", "value")
),
medianPlay: formatWan(readNumber2(data?.play_mid)),
publishedItems: formatPublishedItems(readNumber2(data?.item_num))
};
}
function mapBusinessAbilityEstimateResponse(payload) {
const data = getPayloadData(payload);
const expectedPlay = formatWan(readNumber2(data?.vv));
const hotRate = formatDecimalRate(readNumber2(data?.platform_hot_rate));
return {
oneToTwenty: {
expectedCpe: formatDecimal(readNumber2(data?.cpe_1_20), 1),
expectedCpm: formatFixedDecimal(readNumber2(data?.cpm_1_20), 1),
expectedPlay,
hotRate
},
overSixty: {
expectedCpe: formatDecimal(readNumber2(data?.cpe_60), 1),
expectedCpm: formatFixedDecimal(readNumber2(data?.cpm_60), 1),
expectedPlay,
hotRate
},
twentyToSixty: {
expectedCpe: formatDecimal(readNumber2(data?.cpe_20_60), 1),
expectedCpm: formatFixedDecimal(readNumber2(data?.cpm_20_60), 1),
expectedPlay,
hotRate
}
};
}
function formatPublishedItems(value) {
if (value === null) {
return "";
}
return value > 0 && value < 5 ? "<5" : formatDecimal(value, 0);
}
function formatDuration(value) {
if (value === null) {
return "";
}
return `${formatDecimal(value / 100, 0)}s`;
}
function formatBasisPointRate(value) {
if (value === null) {
return "";
}
return `${formatDecimal(value / 100, 1)}%`;
}
function formatDecimalRate(value) {
if (value === null) {
return "\u7F3A\u5931";
}
return `${formatDecimal(value * 100, 0)}%`;
}
function formatWan(value) {
if (value === null) {
return "";
}
if (Math.abs(value) >= 1e4) {
return `${formatDecimal(value / 1e4, 1)}w`;
}
return formatDecimal(value, 0);
}
function formatDecimal(value, digits) {
if (value === null || !Number.isFinite(value)) {
return "";
}
const fixed = value.toFixed(digits);
return fixed.replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
}
function formatFixedDecimal(value, digits) {
if (value === null || !Number.isFinite(value)) {
return "";
}
return value.toFixed(digits);
}
function readNestedNumber(data, objectKey, valueKey) {
const objectValue = data?.[objectKey];
if (!isRecord3(objectValue)) {
return null;
}
return readNumber2(objectValue[valueKey]);
}
function readNumber2(value) {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string" && value.trim()) {
const numericValue = Number(value);
return Number.isFinite(numericValue) ? numericValue : null;
}
return null;
}
function getPayloadData(payload) {
if (!isRecord3(payload)) {
return null;
}
return isRecord3(payload.data) ? payload.data : payload;
}
function resolveBaseUrl3() {
if (typeof location !== "undefined" && location.origin) {
return location.origin;
}
return "https://xingtu.cn";
}
async function defaultFetch3(input, init) {
return fetch(input, init);
}
function isRecord3(value) {
return typeof value === "object" && value !== null;
}
// src/content/market/author-id-dialog.ts
function promptForAuthorIds(document2) {
return new Promise((resolve) => {
const overlay = document2.createElement("div");
overlay.dataset.authorIdDialog = "overlay";
applyOverlayStyles(overlay);
const dialog = document2.createElement("section");
applyDialogStyles(dialog);
const title = document2.createElement("h2");
title.textContent = "\u6309\u661F\u56FEID\u5BFC\u51FA\u753B\u50CFCSV";
applyTitleStyles(title);
const textarea = document2.createElement("textarea");
textarea.dataset.authorIdDialogInput = "textarea";
textarea.placeholder = "\u6BCF\u884C\u4E00\u4E2A\u661F\u56FEID\uFF0C\u4E5F\u652F\u6301\u9017\u53F7\u3001\u7A7A\u683C\u5206\u9694";
applyTextareaStyles(textarea);
const hint = document2.createElement("p");
hint.textContent = "\u7C98\u8D34\u5BA2\u6237\u63D0\u4F9B\u7684\u8FBE\u4EBA\u661F\u56FEID\uFF0C\u786E\u8BA4\u540E\u5C06\u6279\u91CF\u5BFC\u51FA\u753B\u50CF\u548C\u5546\u4E1A\u80FD\u529B\u6570\u636E\u3002";
applyHintStyles(hint);
const actions = document2.createElement("div");
applyActionsStyles(actions);
const cancelButton = document2.createElement("button");
cancelButton.type = "button";
cancelButton.textContent = "\u53D6\u6D88";
applySecondaryButtonStyles(cancelButton);
const confirmButton = document2.createElement("button");
confirmButton.type = "button";
confirmButton.textContent = "\u5F00\u59CB\u5BFC\u51FA";
applyPrimaryButtonStyles(confirmButton);
actions.append(cancelButton, confirmButton);
dialog.append(title, hint, textarea, actions);
overlay.append(dialog);
document2.body.appendChild(overlay);
const close = (value) => {
overlay.remove();
resolve(value);
};
cancelButton.addEventListener("click", () => close(null));
confirmButton.addEventListener("click", () => close(textarea.value));
overlay.addEventListener("click", (event) => {
if (event.target === overlay) {
close(null);
}
});
textarea.focus();
});
}
function applyOverlayStyles(overlay) {
overlay.style.position = "fixed";
overlay.style.inset = "0";
overlay.style.zIndex = "2147483647";
overlay.style.display = "flex";
overlay.style.alignItems = "center";
overlay.style.justifyContent = "center";
overlay.style.background = "rgba(15, 23, 42, 0.38)";
}
function applyDialogStyles(dialog) {
dialog.style.width = "520px";
dialog.style.maxWidth = "calc(100vw - 32px)";
dialog.style.background = "#ffffff";
dialog.style.borderRadius = "8px";
dialog.style.boxShadow = "0 18px 45px rgba(15, 23, 42, 0.22)";
dialog.style.padding = "20px";
dialog.style.boxSizing = "border-box";
}
function applyTitleStyles(title) {
title.style.margin = "0 0 8px";
title.style.fontSize = "18px";
title.style.fontWeight = "700";
title.style.color = "#1f2329";
}
function applyHintStyles(hint) {
hint.style.margin = "0 0 12px";
hint.style.fontSize = "13px";
hint.style.lineHeight = "20px";
hint.style.color = "#64748b";
}
function applyTextareaStyles(textarea) {
textarea.style.width = "100%";
textarea.style.height = "220px";
textarea.style.resize = "vertical";
textarea.style.border = "1px solid #d0d7de";
textarea.style.borderRadius = "6px";
textarea.style.padding = "10px";
textarea.style.boxSizing = "border-box";
textarea.style.fontSize = "13px";
textarea.style.lineHeight = "20px";
textarea.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, monospace";
textarea.style.color = "#1f2329";
}
function applyActionsStyles(actions) {
actions.style.display = "flex";
actions.style.justifyContent = "flex-end";
actions.style.columnGap = "8px";
actions.style.marginTop = "14px";
}
function applyPrimaryButtonStyles(button) {
button.style.height = "32px";
button.style.padding = "0 15px";
button.style.border = "1px solid #7f1d2d";
button.style.borderRadius = "8px";
button.style.background = "#7f1d2d";
button.style.color = "#ffffff";
button.style.fontWeight = "600";
}
function applySecondaryButtonStyles(button) {
button.style.height = "32px";
button.style.padding = "0 15px";
button.style.border = "1px solid #d0d7de";
button.style.borderRadius = "8px";
button.style.background = "#ffffff";
button.style.color = "#1f2329";
button.style.fontWeight = "600";
}
// src/content/market/batch-name-dialog.ts
var DIALOG_STYLE_ID = "sces-batch-name-dialog-style";
var activeDialogs = /* @__PURE__ */ new WeakMap();
function promptForBatchName(document2) {
const existingDialog = activeDialogs.get(document2);
if (existingDialog) {
existingDialog.input.focus();
existingDialog.input.select();
return existingDialog.promise;
}
ensureDialogStyles(document2);
const dialogRoot = document2.createElement("div");
dialogRoot.dataset.pluginBatchNameDialog = "root";
dialogRoot.setAttribute("role", "dialog");
dialogRoot.setAttribute("aria-modal", "true");
dialogRoot.setAttribute("aria-labelledby", "sces-batch-name-title");
applyOverlayStyles2(dialogRoot);
const dialogPanel = document2.createElement("div");
applyPanelStyles(dialogPanel);
const title = document2.createElement("h2");
title.id = "sces-batch-name-title";
title.textContent = "\u63D0\u4EA4\u6279\u6B21";
applyTitleStyles2(title);
const description = document2.createElement("p");
description.textContent = "\u8BF7\u8F93\u5165\u6279\u6B21\u540D\u79F0\uFF0C\u4FBF\u4E8E\u540E\u7EED\u5728\u7CFB\u7EDF\u4E2D\u8BC6\u522B\u548C\u8FFD\u8E2A\u3002";
applyDescriptionStyles(description);
const input = document2.createElement("input");
input.type = "text";
input.dataset.pluginBatchNameInput = "input";
input.placeholder = "\u4F8B\u5982\uFF1A618\u8FBE\u4EBA\u7B5B\u9009\u7B2C\u4E00\u6279";
input.maxLength = 60;
applyInputStyles(input);
const errorText = document2.createElement("p");
errorText.dataset.pluginBatchNameError = "text";
applyErrorStyles(errorText);
const buttonRow = document2.createElement("div");
applyButtonRowStyles(buttonRow);
const cancelButton = document2.createElement("button");
cancelButton.type = "button";
cancelButton.dataset.pluginBatchNameCancel = "button";
cancelButton.textContent = "\u53D6\u6D88";
applySecondaryButtonStyles2(cancelButton);
const confirmButton = document2.createElement("button");
confirmButton.type = "button";
confirmButton.dataset.pluginBatchNameConfirm = "button";
confirmButton.textContent = "\u786E\u8BA4\u63D0\u4EA4";
applyPrimaryButtonStyles2(confirmButton);
buttonRow.append(cancelButton, confirmButton);
dialogPanel.append(title, description, input, errorText, buttonRow);
dialogRoot.appendChild(dialogPanel);
document2.body.appendChild(dialogRoot);
const dialogPromise = new Promise((resolve) => {
const closeDialog = (value) => {
activeDialogs.delete(document2);
dialogRoot.remove();
document2.removeEventListener("keydown", handleDocumentKeydown, true);
resolve(value);
};
const submitValue = () => {
const value = input.value.trim();
if (!value) {
errorText.textContent = "\u8BF7\u8F93\u5165\u6279\u6B21\u540D\u79F0";
input.setAttribute("aria-invalid", "true");
input.focus();
return;
}
closeDialog(value);
};
const handleDocumentKeydown = (event) => {
if (event.key === "Escape") {
event.preventDefault();
closeDialog(null);
return;
}
if (event.key === "Enter") {
event.preventDefault();
submitValue();
}
};
input.addEventListener("input", () => {
if (!input.value.trim()) {
return;
}
errorText.textContent = "";
input.removeAttribute("aria-invalid");
});
cancelButton.addEventListener("click", () => {
closeDialog(null);
});
confirmButton.addEventListener("click", () => {
submitValue();
});
dialogRoot.addEventListener("click", (event) => {
if (event.target === dialogRoot) {
closeDialog(null);
}
});
document2.addEventListener("keydown", handleDocumentKeydown, true);
});
activeDialogs.set(document2, {
input,
promise: dialogPromise
});
input.focus();
return dialogPromise;
}
function ensureDialogStyles(document2) {
if (document2.getElementById(DIALOG_STYLE_ID)) {
return;
}
const style = document2.createElement("style");
style.id = DIALOG_STYLE_ID;
style.textContent = `
[data-plugin-batch-name-dialog="root"] {
animation: sces-batch-name-fade-in 0.16s ease;
}
@keyframes sces-batch-name-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
`;
document2.head.appendChild(style);
}
function applyOverlayStyles2(root) {
root.style.position = "fixed";
root.style.inset = "0";
root.style.background = "rgba(15, 23, 42, 0.38)";
root.style.display = "flex";
root.style.alignItems = "center";
root.style.justifyContent = "center";
root.style.padding = "24px";
root.style.zIndex = "2147483647";
}
function applyPanelStyles(panel) {
panel.style.width = "min(420px, calc(100vw - 32px))";
panel.style.background = "#fffaf9";
panel.style.border = "1px solid rgba(127, 29, 45, 0.14)";
panel.style.borderRadius = "18px";
panel.style.boxShadow = "0 28px 70px rgba(15, 23, 42, 0.22)";
panel.style.padding = "24px";
panel.style.boxSizing = "border-box";
}
function applyTitleStyles2(title) {
title.style.margin = "0";
title.style.color = "#4c0519";
title.style.fontSize = "20px";
title.style.fontWeight = "700";
title.style.lineHeight = "28px";
}
function applyDescriptionStyles(description) {
description.style.margin = "10px 0 0";
description.style.color = "#64748b";
description.style.fontSize = "13px";
description.style.lineHeight = "20px";
}
function applyInputStyles(input) {
input.style.width = "100%";
input.style.height = "42px";
input.style.marginTop = "18px";
input.style.padding = "0 14px";
input.style.boxSizing = "border-box";
input.style.border = "1px solid #d8c1c6";
input.style.borderRadius = "12px";
input.style.background = "#ffffff";
input.style.color = "#1f2937";
input.style.fontSize = "14px";
input.style.outline = "none";
}
function applyErrorStyles(errorText) {
errorText.style.minHeight = "20px";
errorText.style.margin = "8px 0 0";
errorText.style.color = "#b91c1c";
errorText.style.fontSize = "12px";
errorText.style.lineHeight = "18px";
}
function applyButtonRowStyles(buttonRow) {
buttonRow.style.display = "flex";
buttonRow.style.justifyContent = "flex-end";
buttonRow.style.gap = "10px";
buttonRow.style.marginTop = "18px";
}
function applySecondaryButtonStyles2(button) {
button.style.height = "36px";
button.style.padding = "0 16px";
button.style.border = "1px solid #d7dde6";
button.style.borderRadius = "10px";
button.style.background = "#ffffff";
button.style.color = "#334155";
button.style.fontWeight = "600";
button.style.cursor = "pointer";
}
function applyPrimaryButtonStyles2(button) {
button.style.height = "36px";
button.style.padding = "0 16px";
button.style.border = "1px solid #7f1d2d";
button.style.borderRadius = "10px";
button.style.background = "#7f1d2d";
button.style.color = "#ffffff";
button.style.fontWeight = "600";
button.style.cursor = "pointer";
}
// src/content/market/batch-payload.ts
function createBatchPayload(options) {
const logtoUserId = options.authState.userInfo?.sub?.trim();
if (!logtoUserId) {
throw new Error("batch submit user id unavailable");
}
const resource = options.authState.resource?.trim();
if (!resource) {
throw new Error("batch submit resource unavailable");
}
const batchName = options.batchName.trim();
if (!batchName) {
throw new Error("batch submit batch name is required");
}
return {
authors: options.records.map((record) => ({
authorId: record.authorId,
authorName: record.authorName,
...record.coreUserId ? { authorUid: record.coreUserId } : {}
})),
batchName,
createdAt: options.createdAt,
creatorName: options.authState.userInfo?.name ?? options.authState.userInfo?.username ?? logtoUserId,
logtoUserId,
resource
};
}
// 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: readString3(readMarketFieldValue(row, attributeDatas, "star_id")) ?? readString3(readMarketFieldValue(row, attributeDatas, "id")) ?? "",
authorName: readString3(readMarketFieldValue(row, attributeDatas, "nickname")) ?? readString3(readMarketFieldValue(row, attributeDatas, "nick_name")) ?? "",
coreUserId: readString3(readMarketFieldValue(row, attributeDatas, "core_user_id")) ?? void 0,
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) => isRecord4(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 readKnownPaginationNumber(value, kind) {
if (!isRecord4(value)) {
return null;
}
return readKnownNumberDeep(value, kind === "page" ? PAGE_NUMBER_KEYS : PAGE_SIZE_KEYS);
}
function findMarketListContainer(value) {
const queue = [value];
while (queue.length > 0) {
const current = queue.shift();
if (!isRecord4(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 (isRecord4(value) && "value" in value) {
return value.value;
}
return value;
}
function isRecord4(value) {
return typeof value === "object" && value !== null;
}
function readMarketAttributeDatas(record) {
return isRecord4(record.attribute_datas) ? record.attribute_datas : {};
}
function readMarketFieldValue(record, attributeDatas, field) {
return record[field] ?? attributeDatas[field];
}
function readString3(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 = readString3(readMarketFieldValue(record, attributeDatas, "nickname")) ?? readString3(readMarketFieldValue(record, attributeDatas, "nick_name")) ?? "";
const parts = [
nickname,
readMarketGenderLabel(readMarketFieldValue(record, attributeDatas, "gender")),
readString3(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 readString3(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 (!isRecord4(item)) {
continue;
}
const title = readString3(item.title);
if (hasTextValue(title)) {
return normalizeExportCellText(title);
}
}
return void 0;
}
function readMarketGenderLabel(value) {
const rawValue = typeof value === "number" ? String(value) : readString3(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 (isRecord4(value)) {
return value;
}
if (typeof value === "string") {
try {
const parsedValue = JSON.parse(value);
return isRecord4(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 (!isRecord4(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/dom-sync.ts
var BACKEND_COLUMN_KEY = "backendMetrics";
var SELECTION_COLUMN_KEY = "selection";
var SINGLE_COLUMN_KEY = "singleVideoAfterSearchRate";
var PERSONAL_COLUMN_KEY = "personalVideoAfterSearchRate";
var ACTION_HEADER_TEXT = "\u64CD\u4F5C";
var AUTHOR_HEADER_TEXT = "\u8FBE\u4EBA\u4FE1\u606F";
var BACKEND_HEADER_TEXT = "\u79D2\u63A2\u6307\u6807";
var MARKET_SCROLL_HINT_TEXT = "\u6A2A\u5411\u6EDA\u52A8\u53EF\u67E5\u770B\u770B\u540E\u641C\u7387\u3001\u79D2\u63A2\u6307\u6807";
var MARKET_SCROLLBAR_STYLE_ID = "sces-market-scrollbar-style";
var UNAVAILABLE_RATE_TEXT = "\u6682\u65E0\u6765\u6E90";
var UNAVAILABLE_BACKEND_METRICS_TEXT = "\u6682\u65E0\u6570\u636E";
var SERIALIZED_MARKET_ROWS_ATTRIBUTE = "data-sces-market-rows";
var SORTABLE_RATE_FIELDS = [SINGLE_COLUMN_KEY, PERSONAL_COLUMN_KEY];
var BACKEND_METRIC_COLUMNS2 = [
{
field: "afterViewSearchRate",
label: "\u770B\u540E\u641C\u7387"
},
{
field: "afterViewSearchCount",
label: "\u770B\u540E\u641C\u6570"
},
{
field: "a3IncreaseCount",
label: "\u65B0\u589EA3\u6570"
},
{
field: "newA3Rate",
label: "\u65B0\u589EA3\u7387"
},
{
field: "cpa3",
label: "CPA3"
},
{
field: "cpSearch",
label: "cp_search"
}
];
var SORTABLE_MARKET_FIELDS = [
...SORTABLE_RATE_FIELDS,
...BACKEND_METRIC_COLUMNS2.map((column) => column.field)
];
function syncMarketTable(root) {
return syncSyntheticMarketTable(root) ?? syncDivGridMarketTable(root);
}
function readMarketPageSignature(root) {
const document2 = getOwnerDocument(root);
const explicitPageIndex = document2?.documentElement.getAttribute("data-test-page-index") ?? "";
const activePageIndex = document2?.querySelector(".el-pagination .number.active, .xt-pagination .number.active")?.textContent?.trim() ?? "";
const authorIds = readRawAuthorIds(root).join("|");
return `${explicitPageIndex || activePageIndex}::${authorIds}`;
}
function findNextPageControl(root) {
const document2 = getOwnerDocument(root);
if (!document2) {
return null;
}
const explicitControl = document2.querySelector('[data-testid="next-page"]');
if (explicitControl instanceof document2.defaultView.HTMLElement) {
return explicitControl;
}
const paginationNextControl = document2.querySelector(
".el-pagination .btn-next, .xt-pagination .btn-next"
);
if (paginationNextControl instanceof document2.defaultView.HTMLElement) {
return paginationNextControl;
}
const candidates = Array.from(
document2.querySelectorAll("button, a, [role='button']")
).filter(
(element) => element instanceof document2.defaultView.HTMLElement
);
return candidates.find(
(element) => /下一页|next/i.test(normalizeExportCellText2(element.textContent))
) ?? null;
}
function isPageControlDisabled(control) {
if (!control) {
return true;
}
if (control instanceof HTMLButtonElement) {
return control.disabled;
}
return control.getAttribute("aria-disabled") === "true";
}
function renderMarketRowState(rowDom, record) {
renderBackendMetricsCells(rowDom.backendMetricsCells, record);
if (record.status === "success" && record.rates) {
rowDom.singleCell.textContent = readRateCellText(
record.rates.singleVideoAfterSearchRate
);
rowDom.personalCell.textContent = readRateCellText(
record.rates.personalVideoAfterSearchRate
);
return;
}
if (record.status === "loading") {
rowDom.singleCell.textContent = "\u52A0\u8F7D\u4E2D...";
rowDom.personalCell.textContent = "\u52A0\u8F7D\u4E2D...";
return;
}
if (record.status === "failed") {
rowDom.singleCell.textContent = "\u52A0\u8F7D\u5931\u8D25";
rowDom.personalCell.textContent = "\u52A0\u8F7D\u5931\u8D25";
return;
}
rowDom.singleCell.textContent = "";
rowDom.personalCell.textContent = "";
}
function applyRowVisibility(table, visibleAuthorIds) {
table.rows.forEach((rowDom) => {
const isVisible = visibleAuthorIds.has(rowDom.authorId);
rowDom.visibilityTargets.forEach((target) => {
target.hidden = !isVisible;
});
});
}
function applyRowOrder(table, orderedAuthorIds) {
const rowById = new Map(table.rows.map((rowDom) => [rowDom.authorId, rowDom]));
const orderByAuthorId = new Map(
orderedAuthorIds.map((authorId, index) => [authorId, index])
);
orderedAuthorIds.forEach((authorId) => {
const rowDom = rowById.get(authorId);
if (!rowDom) {
return;
}
rowDom.orderTargets.forEach(({ container, mode, node }) => {
const visualOrder = orderByAuthorId.get(authorId) ?? orderedAuthorIds.length;
if (mode === "css") {
container.dataset.marketOrderMode = "css";
container.style.display = "flex";
container.style.flexDirection = "column";
node.style.order = String(visualOrder);
return;
}
container.dataset.marketOrderMode = "dom";
container.appendChild(node);
});
});
}
function syncPluginSortHeaders(root, options) {
SORTABLE_MARKET_FIELDS.forEach((field) => {
const cell = root.querySelector(
`[data-market-header-cell="${field}"]`
);
if (!cell) {
return;
}
syncSortableHeaderCell(cell, {
direction: options.activeSort?.field === field ? options.activeSort.direction : "none",
field,
onToggleSort: options.onToggleSort
});
});
}
function syncMarketSelectionState(table, selectedAuthorIds) {
table.rows.forEach((rowDom) => {
rowDom.selectionCheckbox.dataset.marketSelectionAuthorId = rowDom.authorId;
rowDom.selectionCheckbox.checked = selectedAuthorIds.has(rowDom.authorId);
});
if (!table.headerSelectionCheckbox) {
return;
}
const visibleRows = table.rows.filter(
(rowDom) => rowDom.visibilityTargets.some((target) => !target.hidden)
);
const scopedRows = visibleRows.length > 0 ? visibleRows : table.rows;
const selectedCount = scopedRows.filter(
(rowDom) => selectedAuthorIds.has(rowDom.authorId)
).length;
table.headerSelectionCheckbox.indeterminate = selectedCount > 0 && selectedCount < scopedRows.length;
table.headerSelectionCheckbox.checked = scopedRows.length > 0 && selectedCount === scopedRows.length;
table.headerSelectionCheckbox.disabled = scopedRows.length === 0;
}
function syncSyntheticMarketTable(root) {
const header = root.querySelector("[data-market-header]");
const body = root.querySelector("[data-market-body]");
if (!header || !body) {
return null;
}
const selectionHeader = ensureSyntheticHeaderCell(header, SELECTION_COLUMN_KEY, "");
const headerSelectionCheckbox = ensureSelectionHeaderControl(selectionHeader);
ensureSyntheticHeaderCell(header, SINGLE_COLUMN_KEY, "\u5355\u89C6\u9891\u770B\u540E\u641C\u7387");
ensureSyntheticHeaderCell(header, PERSONAL_COLUMN_KEY, "\u4E2A\u4EBA\u89C6\u9891\u770B\u540E\u641C\u7387");
BACKEND_METRIC_COLUMNS2.forEach(({ field, label }) => {
ensureSyntheticHeaderCell(header, field, label);
});
const headerLabelsByField = readSyntheticHeaderLabels(header);
const rows = Array.from(body.querySelectorAll("[data-market-row]")).map(
(rowElement) => {
const row = rowElement;
const selectionCell = ensureSyntheticRowCell(row, SELECTION_COLUMN_KEY);
const selectionCheckbox = ensureSelectionRowControl(selectionCell);
const singleCell = ensureSyntheticRowCell(row, SINGLE_COLUMN_KEY);
const personalCell = ensureSyntheticRowCell(row, PERSONAL_COLUMN_KEY);
const backendMetricsCells = Object.fromEntries(
BACKEND_METRIC_COLUMNS2.map(({ field }) => [field, ensureSyntheticRowCell(row, field)])
);
const authorId = row.dataset.authorId ?? "";
selectionCheckbox.dataset.marketSelectionAuthorId = authorId;
return {
authorId,
authorName: row.querySelector('[data-market-field="authorName"]')?.textContent?.trim() ?? "",
backendMetricsCells,
exportFields: readSyntheticExportFields(row, headerLabelsByField),
hasDirectRatesSource: false,
location: row.querySelector('[data-market-field="location"]')?.textContent?.trim() ?? "",
orderTargets: [
{
container: body,
mode: "dom",
node: row
}
],
personalCell,
price21To60s: row.querySelector('[data-market-field="price21To60s"]')?.textContent?.trim() ?? "",
rates: void 0,
row,
selectionCheckbox,
singleCell,
visibilityTargets: [row]
};
}
);
return {
headerSelectionCheckbox,
rows
};
}
function syncDivGridMarketTable(root) {
const document2 = getOwnerDocument(root);
if (!document2) {
return null;
}
for (const marketRoot of document2.querySelectorAll(".base-author-list")) {
if (!(marketRoot instanceof document2.defaultView.HTMLElement)) {
continue;
}
const syncedTable = syncDivGridRoot(marketRoot);
if (syncedTable) {
return syncedTable;
}
}
return null;
}
function readRawAuthorIds(root) {
const document2 = getOwnerDocument(root);
const syntheticAuthorIds = readSyntheticAuthorIds(root);
if (syntheticAuthorIds && syntheticAuthorIds.length > 0) {
return syntheticAuthorIds;
}
const divGridAuthorIds = readDivGridAuthorIds(root);
if (divGridAuthorIds && divGridAuthorIds.length > 0) {
return divGridAuthorIds;
}
if (!document2) {
return [];
}
return readSerializedMarketRows(document2).map((row) => row.authorId).filter((authorId) => Boolean(authorId));
}
function readSyntheticAuthorIds(root) {
const body = root.querySelector("[data-market-body]");
if (!body) {
return null;
}
return Array.from(body.querySelectorAll("[data-market-row]")).map(
(row) => row instanceof HTMLElement ? row.dataset.authorId ?? "" : ""
).filter((authorId) => Boolean(authorId));
}
function readDivGridAuthorIds(root) {
const document2 = getOwnerDocument(root);
if (!document2) {
return null;
}
const marketRoot = document2.querySelector(".base-author-list");
if (!(marketRoot instanceof document2.defaultView.HTMLElement)) {
return null;
}
const bodySection = Array.from(marketRoot.querySelectorAll(".section-wrapper")).find(
(section) => section instanceof document2.defaultView.HTMLElement && !section.classList.contains("sticky-header")
);
const authorSection = bodySection ? Array.from(bodySection.children).find(
(child) => child instanceof document2.defaultView.HTMLElement && child.querySelector(".content-column .content-cell")
) ?? null : null;
const authorColumn = authorSection ? getNativeAuthorColumn(authorSection) : null;
if (!authorColumn) {
return null;
}
return getDirectContentCells(authorColumn).map((cell) => extractAuthorId(cell)).filter((authorId) => Boolean(authorId));
}
function syncDivGridRoot(root) {
const headerSection = root.querySelector(
".section-wrapper.sticky-header"
);
const bodySection = Array.from(root.querySelectorAll(".section-wrapper")).find(
(section) => section instanceof root.ownerDocument.defaultView.HTMLElement && !section.classList.contains("sticky-header")
);
if (!headerSection || !bodySection) {
return null;
}
const authorHeader = findCellByText(getDirectHeaderCells(headerSection), AUTHOR_HEADER_TEXT);
const actionHeader = findCellByText(getDirectHeaderCells(headerSection), ACTION_HEADER_TEXT);
if (!authorHeader || !actionHeader) {
return null;
}
const rightHeaderSection = actionHeader.parentElement;
if (!(rightHeaderSection instanceof root.ownerDocument.defaultView.HTMLElement)) {
return null;
}
const middleHeaderSection = findPreviousNativeSection(rightHeaderSection) ?? rightHeaderSection;
const authorSection = getIndexedChild(
bodySection,
getDirectChildIndex(headerSection, authorHeader)
);
const authorHeaderSection = getIndexedChild(
headerSection,
getDirectChildIndex(headerSection, authorHeader)
);
const rightSection = getIndexedChild(
bodySection,
getDirectChildIndex(headerSection, actionHeader)
);
if (!authorSection || !authorHeaderSection || !rightSection) {
return null;
}
const middleBodySection = findPreviousNativeSection(rightSection) ?? rightSection;
const pluginHeaderSection = ensurePluginSection(headerSection, rightHeaderSection, {
testId: "plugin-header",
type: "header"
});
const pluginBodySection = ensurePluginSection(bodySection, rightSection, {
testId: "plugin-section",
type: "body"
});
const authorColumn = getNativeAuthorColumn(authorSection);
const actionColumn = getActionColumn(rightSection);
if (!authorColumn || !actionColumn) {
return null;
}
const rowCount = getDirectContentCells(authorColumn).length;
const selectionHeaderCell = ensureDivHeaderCell(
authorHeaderSection,
authorHeader,
SELECTION_COLUMN_KEY,
""
);
const headerSelectionCheckbox = ensureSelectionHeaderControl(selectionHeaderCell);
const selectionColumn = ensureDivBodyColumn(
authorSection,
authorColumn,
SELECTION_COLUMN_KEY,
rowCount
);
const headerTemplateCell = getDirectHeaderCells(middleHeaderSection).at(-1) ?? findPreviousHeaderCell(actionHeader) ?? actionHeader;
const bodyTemplateColumn = getDirectContentColumns(middleBodySection).at(-1) ?? findPreviousColumn(actionColumn) ?? actionColumn;
ensureDivHeaderCell(
pluginHeaderSection,
headerTemplateCell,
SINGLE_COLUMN_KEY,
"\u5355\u89C6\u9891\u770B\u540E\u641C\u7387"
);
ensureDivHeaderCell(
pluginHeaderSection,
headerTemplateCell,
PERSONAL_COLUMN_KEY,
"\u4E2A\u4EBA\u89C6\u9891\u770B\u540E\u641C\u7387"
);
const singleColumn = ensureDivBodyColumn(
pluginBodySection,
bodyTemplateColumn,
SINGLE_COLUMN_KEY,
rowCount
);
const personalColumn = ensureDivBodyColumn(
pluginBodySection,
bodyTemplateColumn,
PERSONAL_COLUMN_KEY,
rowCount
);
const backendMetricColumns = Object.fromEntries(
BACKEND_METRIC_COLUMNS2.map(({ field, label }) => {
ensureDivHeaderCell(pluginHeaderSection, headerTemplateCell, field, label);
return [
field,
ensureDivBodyColumn(
pluginBodySection,
bodyTemplateColumn,
field,
rowCount
)
];
})
);
syncContainerWidth(pluginHeaderSection);
syncContainerWidth(pluginBodySection);
syncContainerWidth(authorHeaderSection);
syncContainerWidth(authorSection);
ensureVisibleHorizontalScroll(headerSection);
ensureVisibleHorizontalScroll(bodySection);
ensureScrollHint(root, headerSection);
const allBodyColumns = Array.from(bodySection.children).flatMap(
(section) => section instanceof root.ownerDocument.defaultView.HTMLElement ? getDirectContentColumns(section) : []
);
const allHeaderCells = Array.from(headerSection.children).flatMap(
(section) => section instanceof root.ownerDocument.defaultView.HTMLElement ? getDirectHeaderCells(section) : []
);
const authorCells = getDirectContentCells(authorColumn);
const selectionCells = getDirectContentCells(selectionColumn);
const singleCells = getDirectContentCells(singleColumn);
const personalCells = getDirectContentCells(personalColumn);
const backendMetricCellsByField = Object.fromEntries(
BACKEND_METRIC_COLUMNS2.map(({ field }) => [
field,
getDirectContentCells(backendMetricColumns[field])
])
);
const priceColumn = findPreviousColumn(actionColumn);
const priceCells = priceColumn ? getDirectContentCells(priceColumn) : [];
const remainingVueMarketRows = [...readVueMarketRows(root)];
const remainingSerializedMarketRows = [...readSerializedMarketRows(root.ownerDocument)];
const rows = authorCells.flatMap((authorCell, index) => {
const selectionCell = selectionCells[index] ?? null;
const singleCell = singleCells[index] ?? null;
const personalCell = personalCells[index] ?? null;
const backendMetricsCells = Object.fromEntries(
BACKEND_METRIC_COLUMNS2.map(({ field }) => [
field,
backendMetricCellsByField[field][index] ?? null
])
);
if (!selectionCell || !singleCell || !personalCell || Object.values(backendMetricsCells).some((cell) => cell === null)) {
return [];
}
const selectionCheckbox = ensureSelectionRowControl(selectionCell);
const alignedRowCells = allBodyColumns.map(
(column) => getDirectContentCells(column)[index] ?? null
);
const rowCells = alignedRowCells.filter(
(cell) => cell !== null
);
const directAuthorId = extractAuthorId(authorCell) || "";
const directAuthorName = extractAuthorName(authorCell) || "";
const vueMarketRow = takeMatchedMarketDataRow(
remainingVueMarketRows,
directAuthorId,
directAuthorName
);
const serializedMarketRow = takeMatchedMarketDataRow(
remainingSerializedMarketRows,
directAuthorId,
directAuthorName
);
const fallbackMarketRow = mergeMarketDataRows(serializedMarketRow, vueMarketRow);
const exportFields = mergeExportFieldMaps(
readExportFieldsForDivGridRow(allHeaderCells, alignedRowCells),
fallbackMarketRow?.exportFields
);
const authorId = directAuthorId || fallbackMarketRow?.authorId || "";
const authorName = directAuthorName || fallbackMarketRow?.authorName || "";
const price21To60s = mergeNonEmptyString(
readDivGridPriceDisplay(priceCells[index]?.textContent),
fallbackMarketRow?.price21To60s
);
selectionCheckbox.dataset.marketSelectionAuthorId = authorId;
return [
{
authorId,
authorName,
backendMetricsCells,
exportFields,
hasDirectRatesSource: fallbackMarketRow?.hasDirectRatesSource ?? false,
location: fallbackMarketRow?.location,
orderTargets: rowCells.map((cell) => {
const container = cell.parentElement;
if (!(container instanceof root.ownerDocument.defaultView.HTMLElement)) {
return null;
}
return {
container,
mode: "css",
node: cell
};
}).filter((target) => target !== null),
personalCell,
price21To60s,
rates: fallbackMarketRow?.rates,
row: authorCell,
selectionCheckbox,
singleCell,
visibilityTargets: rowCells
}
];
});
return {
headerSelectionCheckbox,
rows
};
}
function ensureSyntheticHeaderCell(header, field, label) {
const existingCell = header.querySelector(
`[data-market-header-cell="${field}"]`
);
if (existingCell) {
existingCell.textContent = label;
return existingCell;
}
const nextCell = header.ownerDocument.createElement("div");
nextCell.dataset.marketHeaderCell = field;
nextCell.textContent = label;
if (field === SELECTION_COLUMN_KEY) {
header.insertBefore(nextCell, header.firstChild);
} else {
header.appendChild(nextCell);
}
return nextCell;
}
function ensureSyntheticRowCell(row, field) {
const existingCell = row.querySelector(
`[data-market-row-cell="${field}"]`
);
if (existingCell) {
return existingCell;
}
const nextCell = row.ownerDocument.createElement(field === BACKEND_COLUMN_KEY ? "div" : "span");
nextCell.dataset.marketRowCell = field;
if (field === SELECTION_COLUMN_KEY) {
row.insertBefore(nextCell, row.firstChild);
} else {
row.appendChild(nextCell);
}
return nextCell;
}
function ensureDivHeaderCell(container, templateCell, field, label) {
const existingCell = container.querySelector(
`[data-market-header-cell="${field}"]`
);
if (existingCell) {
existingCell.textContent = label;
applyPluginHeaderCellStyles(existingCell);
return existingCell;
}
const nextCell = cloneElementShallow(templateCell);
nextCell.dataset.marketHeaderCell = field;
nextCell.textContent = label;
applyColumnWidth(nextCell, field);
applyPluginHeaderCellStyles(nextCell);
if (field === SELECTION_COLUMN_KEY) {
container.insertBefore(nextCell, templateCell);
} else {
container.appendChild(nextCell);
}
return nextCell;
}
function ensureDivBodyColumn(container, templateColumn, field, rowCount) {
const existingColumn = container.querySelector(
`[data-market-column-group="${field}"]`
);
if (existingColumn) {
syncDivColumnCells(existingColumn, templateColumn, field, rowCount);
return existingColumn;
}
const nextColumn = cloneElementShallow(templateColumn);
nextColumn.dataset.marketColumnGroup = field;
applyColumnWidth(nextColumn, field);
syncDivColumnCells(nextColumn, templateColumn, field, rowCount);
if (field === SELECTION_COLUMN_KEY) {
container.insertBefore(nextColumn, templateColumn);
} else {
container.appendChild(nextColumn);
}
return nextColumn;
}
function syncDivColumnCells(column, templateColumn, field, rowCount) {
const currentCells = getDirectContentCells(column);
while (currentCells.length > rowCount) {
currentCells.pop()?.remove();
}
const templateCells = getDirectContentCells(templateColumn);
for (let index = 0; index < rowCount; index += 1) {
const existingCell = getDirectContentCells(column)[index] ?? null;
const templateCell = templateCells[index] ?? templateCells[templateCells.length - 1] ?? null;
if (existingCell) {
existingCell.dataset.marketRowCell = field;
applyPluginContentCellStyles(existingCell);
syncContentCellHeight(existingCell, templateCell);
continue;
}
const nextCell = field === SELECTION_COLUMN_KEY ? templateCell ? createSelectionContentCell(templateCell) : createBareContentCell(column.ownerDocument) : templateCell ? cloneElementShallow(templateCell) : createBareContentCell(column.ownerDocument);
nextCell.dataset.marketRowCell = field;
applyColumnWidth(nextCell, field);
applyPluginContentCellStyles(nextCell);
syncContentCellHeight(nextCell, templateCell);
nextCell.textContent = "";
column.appendChild(nextCell);
}
}
function syncContentCellHeight(cell, templateCell) {
if (!templateCell) {
return;
}
const measuredHeight = Math.round(templateCell.getBoundingClientRect().height);
const nextHeight = measuredHeight > 0 ? `${measuredHeight}px` : templateCell.style.height;
if (nextHeight) {
cell.style.height = nextHeight;
} else {
cell.style.removeProperty("height");
}
}
function applyPluginHeaderCellStyles(cell) {
cell.style.display = "flex";
cell.style.alignItems = "center";
cell.style.justifyContent = "normal";
cell.style.cursor = "pointer";
cell.style.whiteSpace = "nowrap";
}
function applyPluginContentCellStyles(cell) {
cell.style.display = "flex";
cell.style.alignItems = "center";
cell.style.justifyContent = "normal";
cell.style.paddingTop = "12px";
cell.style.paddingBottom = "12px";
cell.style.boxSizing = "border-box";
cell.style.whiteSpace = "nowrap";
}
function ensureSelectionHeaderControl(cell) {
cell.textContent = "";
cell.style.gap = "6px";
cell.style.justifyContent = "center";
const checkbox = ensureSelectionCheckbox(cell, "header");
const label = cell.querySelector(
'[data-market-selection-label="header"]'
);
if (label) {
label.textContent = "\u5168\u9009";
return checkbox;
}
const nextLabel = cell.ownerDocument.createElement("span");
nextLabel.dataset.marketSelectionLabel = "header";
nextLabel.textContent = "\u5168\u9009";
nextLabel.style.fontSize = "12px";
cell.appendChild(nextLabel);
return checkbox;
}
function ensureSelectionRowControl(cell) {
cell.textContent = "";
cell.style.justifyContent = "center";
return ensureSelectionCheckbox(cell, "row");
}
function ensureSelectionCheckbox(container, kind) {
const existingCheckbox = container.querySelector(
`[data-market-selection-checkbox="${kind}"]`
);
if (existingCheckbox) {
existingCheckbox.type = "checkbox";
return existingCheckbox;
}
const checkbox = container.ownerDocument.createElement("input");
checkbox.type = "checkbox";
checkbox.dataset.marketSelectionCheckbox = kind;
checkbox.style.cursor = "pointer";
container.appendChild(checkbox);
return checkbox;
}
function getOwnerDocument(root) {
if ("ownerDocument" in root && root.ownerDocument) {
return root.ownerDocument;
}
return "nodeType" in root && root.nodeType === 9 ? root : null;
}
function readSyntheticHeaderLabels(header) {
return Array.from(header.querySelectorAll("[data-market-header-cell]")).reduce((labels, cell) => {
if (!(cell instanceof header.ownerDocument.defaultView.HTMLElement)) {
return labels;
}
const field = cell.dataset.marketHeaderCell;
if (!field) {
return labels;
}
labels[field] = normalizeExportCellText2(cell.textContent);
return labels;
}, {});
}
function readSyntheticExportFields(row, headerLabelsByField) {
const exportFields = {};
for (const cell of row.querySelectorAll("[data-market-field]")) {
if (!(cell instanceof row.ownerDocument.defaultView.HTMLElement)) {
continue;
}
const field = cell.dataset.marketField;
const headerLabel = field ? headerLabelsByField[field] : "";
if (!shouldExportColumn(headerLabel)) {
continue;
}
exportFields[headerLabel] = normalizeExportCellText2(cell.textContent);
}
return exportFields;
}
function readExportFieldsForDivGridRow(headerCells, rowCells) {
const exportFields = {};
rowCells.forEach((cell, index) => {
const headerLabel = normalizeExportCellText2(headerCells[index]?.textContent);
if (!shouldExportColumn(headerLabel)) {
return;
}
exportFields[headerLabel] = headerLabel === "21-60s\u62A5\u4EF7" ? readDivGridPriceDisplay(cell?.textContent) ?? "" : normalizeExportCellText2(cell?.textContent);
});
return exportFields;
}
function findPreviousHeaderCell(cell) {
let current = cell.previousElementSibling;
while (current) {
if (current instanceof cell.ownerDocument.defaultView.HTMLElement && current.classList.contains("header-cell")) {
return current;
}
current = current.previousElementSibling;
}
return null;
}
function findPreviousColumn(column) {
let current = column.previousElementSibling;
while (current) {
if (current instanceof column.ownerDocument.defaultView.HTMLElement && current.classList.contains("content-column")) {
return current;
}
current = current.previousElementSibling;
}
return null;
}
function ensurePluginSection(rootSection, referenceSection, options) {
const existingSection = rootSection.querySelector(
`[data-market-plugin-section="${options.type}"]`
);
if (existingSection) {
existingSection.dataset.testid = options.testId;
existingSection.setAttribute("data-testid", options.testId);
return existingSection;
}
const templateSection = findPreviousSection(referenceSection) ?? referenceSection;
const nextSection = cloneElementShallow(templateSection);
nextSection.dataset.marketPluginSection = options.type;
nextSection.dataset.testid = options.testId;
nextSection.setAttribute("data-testid", options.testId);
resetStickySectionStyles(nextSection);
rootSection.insertBefore(nextSection, referenceSection);
return nextSection;
}
function ensureVisibleHorizontalScroll(section) {
ensureVisibleScrollbarStyles(section.ownerDocument);
section.classList.remove("hide-scrollbar");
section.dataset.marketScrollbar = "visible";
section.style.overflowX = "auto";
section.style.scrollbarWidth = "thin";
section.style.scrollbarColor = "rgba(148, 163, 184, 0.95) rgba(226, 232, 240, 0.9)";
}
function ensureVisibleScrollbarStyles(document2) {
if (document2.getElementById(MARKET_SCROLLBAR_STYLE_ID)) {
return;
}
const style = document2.createElement("style");
style.id = MARKET_SCROLLBAR_STYLE_ID;
style.textContent = `
[data-market-scrollbar="visible"]::-webkit-scrollbar {
display: block !important;
height: 10px !important;
}
[data-market-scrollbar="visible"]::-webkit-scrollbar-track {
background: rgba(226, 232, 240, 0.9) !important;
border-radius: 999px;
}
[data-market-scrollbar="visible"]::-webkit-scrollbar-thumb {
background: rgba(148, 163, 184, 0.95) !important;
border: 2px solid rgba(226, 232, 240, 0.9);
border-radius: 999px;
}
`;
document2.head.appendChild(style);
}
function ensureScrollHint(root, headerSection) {
const existingHint = root.querySelector(
'[data-testid="market-scroll-hint"]'
);
if (existingHint) {
existingHint.textContent = MARKET_SCROLL_HINT_TEXT;
return;
}
const hint = root.ownerDocument.createElement("div");
hint.dataset.testid = "market-scroll-hint";
hint.setAttribute("data-testid", "market-scroll-hint");
hint.textContent = MARKET_SCROLL_HINT_TEXT;
hint.style.color = "#64748b";
hint.style.display = "flex";
hint.style.fontSize = "12px";
hint.style.justifyContent = "flex-end";
hint.style.lineHeight = "18px";
hint.style.padding = "0 12px 8px";
root.insertBefore(hint, headerSection);
}
function findPreviousSection(section) {
let current = section.previousElementSibling;
while (current) {
if (current instanceof section.ownerDocument.defaultView.HTMLElement) {
return current;
}
current = current.previousElementSibling;
}
return null;
}
function findPreviousNativeSection(section) {
let current = section.previousElementSibling;
while (current) {
if (current instanceof section.ownerDocument.defaultView.HTMLElement && !current.hasAttribute("data-market-plugin-section")) {
return current;
}
current = current.previousElementSibling;
}
return null;
}
function resetStickySectionStyles(section) {
section.style.position = "";
section.style.left = "";
section.style.right = "";
section.style.zIndex = "";
section.style.width = "";
section.style.minWidth = "";
}
function getActionColumn(bodySection) {
const columns = getDirectContentColumns(bodySection);
return columns[columns.length - 1] ?? null;
}
function getNativeAuthorColumn(authorSection) {
return getDirectContentColumns(authorSection).find(
(column) => !column.dataset.marketColumnGroup && getDirectContentCells(column).some(
(cell) => cell.querySelector("a") || cell.querySelector(".author-nickname") || Boolean(cell.dataset.authorId)
)
) ?? null;
}
function getDirectHeaderCells(section) {
return Array.from(section.querySelectorAll(".header-cell")).filter(
(cell) => cell instanceof section.ownerDocument.defaultView.HTMLElement
);
}
function getDirectContentColumns(section) {
return Array.from(section.children).filter(
(child) => child instanceof section.ownerDocument.defaultView.HTMLElement && child.classList.contains("content-column")
);
}
function getDirectContentCells(column) {
return Array.from(column.children).filter(
(child) => child instanceof column.ownerDocument.defaultView.HTMLElement && child.classList.contains("content-cell")
);
}
function getDirectChildIndex(root, descendant) {
const directChild = Array.from(root.children).find((child) => child.contains(descendant));
return directChild ? Array.from(root.children).indexOf(directChild) : -1;
}
function getIndexedChild(root, index) {
if (index < 0) {
return null;
}
const child = root.children[index] ?? null;
return child instanceof root.ownerDocument.defaultView.HTMLElement ? child : null;
}
function findCellByText(cells, text) {
return cells.find((cell) => cell.textContent?.trim() === text) ?? null;
}
function cloneElementShallow(reference) {
const clone = reference.ownerDocument.createElement(reference.tagName);
Array.from(reference.attributes).forEach((attribute) => {
clone.setAttribute(attribute.name, attribute.value);
});
return clone;
}
function createBareContentCell(document2) {
const cell = document2.createElement("div");
cell.className = "content-cell";
return cell;
}
function createSelectionContentCell(templateCell) {
const cell = cloneElementShallow(templateCell);
cell.removeAttribute("data-testid");
cell.removeAttribute("data-author-id");
return cell;
}
function extractAuthorId(authorCell) {
const explicitAuthorId = authorCell.dataset.authorId;
if (explicitAuthorId) {
return explicitAuthorId;
}
const linkedAuthorId = Array.from(authorCell.querySelectorAll("a")).map((link) => extractAuthorIdFromHref(link.href)).find((value) => Boolean(value));
if (linkedAuthorId) {
return linkedAuthorId;
}
const fallbackAuthorId = authorCell.querySelector("[data-author-id]")?.getAttribute("data-author-id");
return fallbackAuthorId ?? "";
}
function extractAuthorName(authorCell) {
return authorCell.querySelector(".author-nickname")?.textContent?.trim() ?? authorCell.textContent?.trim() ?? "";
}
function extractAuthorIdFromHref(href) {
const match = href.match(/\/author-homepage\/[^/]+\/(\d+)/);
return match?.[1] ?? null;
}
function readVueMarketRows(marketRoot) {
const vueRoot = marketRoot.__vue__;
const setupStates = collectVueSetupStates(vueRoot);
for (const setupState of setupStates) {
for (const value of Object.values(setupState)) {
const candidate = unwrapVueRef2(value);
if (!candidate || typeof candidate !== "object") {
continue;
}
const marketList = unwrapVueRef2(
candidate.marketList
);
if (!Array.isArray(marketList)) {
continue;
}
return marketList.map((row) => isRecord5(row) ? mapMarketListRow(row) : null).filter((row) => row !== null);
}
}
return [];
}
function collectVueSetupStates(vueRoot) {
if (!vueRoot) {
return [];
}
const queue = [vueRoot];
const setupStates = [];
while (queue.length > 0) {
const current = queue.shift();
if (!isRecord5(current)) {
continue;
}
if (isRecord5(current._setupState)) {
setupStates.push(current._setupState);
}
const children = Array.isArray(current.$children) ? current.$children : [];
queue.push(...children);
}
return setupStates;
}
function readSerializedMarketRows(document2) {
const serializedRows = document2.documentElement.getAttribute(
SERIALIZED_MARKET_ROWS_ATTRIBUTE
);
if (!serializedRows) {
return [];
}
try {
const parsedRows = JSON.parse(serializedRows);
if (!Array.isArray(parsedRows)) {
return [];
}
return parsedRows.map((row) => {
const record = isRecord5(row) ? row : {};
const singleVideoAfterSearchRate = readString4(
record.singleVideoAfterSearchRate
);
return {
authorId: readString4(record.authorId) ?? "",
authorName: readString4(record.authorName) ?? "",
coreUserId: readString4(record.coreUserId) ?? void 0,
exportFields: readSerializedExportFields(record),
hasDirectRatesSource: Boolean(singleVideoAfterSearchRate),
location: readString4(record.location) ?? void 0,
price21To60s: readString4(record.price21To60s) ?? void 0,
rates: singleVideoAfterSearchRate ? {
singleVideoAfterSearchRate
} : void 0
};
}).filter((row) => Boolean(row.authorId || row.authorName));
} catch {
return [];
}
}
function unwrapVueRef2(value) {
if (isRecord5(value) && "value" in value) {
return value.value;
}
return value;
}
function isRecord5(value) {
return typeof value === "object" && value !== null;
}
function readString4(value) {
return typeof value === "string" ? value : null;
}
function normalizeExportCellText2(value) {
return value?.replace(/\s+/g, " ").trim() ?? "";
}
function readDivGridPriceDisplay(value) {
const normalizedValue = normalizeExportCellText2(value);
if (!normalizedValue) {
return void 0;
}
const match = normalizedValue.match(/^¥?\s*([\d,]+(?:\.\d+)?)$/);
if (!match) {
return void 0;
}
const numericValue = Number(match[1].replace(/,/g, ""));
if (!Number.isFinite(numericValue)) {
return void 0;
}
return formatCurrencyValue2(numericValue);
}
function shouldExportColumn(label) {
const excludedBackendLabels = new Set(BACKEND_METRIC_COLUMNS2.map((column) => column.label));
return Boolean(
label && label !== ACTION_HEADER_TEXT && label !== BACKEND_HEADER_TEXT && !excludedBackendLabels.has(label) && label !== "\u5355\u89C6\u9891\u770B\u540E\u641C\u7387" && label !== "\u4E2A\u4EBA\u89C6\u9891\u770B\u540E\u641C\u7387"
);
}
function syncSortableHeaderCell(cell, options) {
const label = readSortableHeaderLabel(cell);
const sorterRoot = ensureHeaderSorterRoot(cell);
const text = ensureHeaderSorterText(sorterRoot);
const icon = ensureHeaderSorterIcon(sorterRoot);
const upTriangle = ensureHeaderTriangle(icon, "up");
const downTriangle = ensureHeaderTriangle(icon, "down");
text.textContent = label;
cell.dataset.marketSortField = options.field;
cell.dataset.marketSortDirection = options.direction;
cell.setAttribute("role", "button");
cell.tabIndex = 0;
cell.onclick = () => {
options.onToggleSort(options.field);
};
cell.onkeydown = (event) => {
if (event.key !== "Enter" && event.key !== " ") {
return;
}
event.preventDefault();
options.onToggleSort(options.field);
};
syncTriangleStyles(upTriangle, {
active: options.direction === "asc",
direction: "up"
});
syncTriangleStyles(downTriangle, {
active: options.direction === "desc",
direction: "down"
});
}
function readSortableHeaderLabel(cell) {
return cell.dataset.marketHeaderLabel ?? normalizeExportCellText2(cell.textContent) ?? "";
}
function ensureHeaderSorterRoot(cell) {
const existingRoot = cell.querySelector(
'[data-market-sorter="root"]'
);
if (existingRoot) {
return existingRoot;
}
cell.dataset.marketHeaderLabel = normalizeExportCellText2(cell.textContent);
cell.replaceChildren();
const root = cell.ownerDocument.createElement("span");
root.dataset.marketSorter = "root";
root.style.alignItems = "center";
root.style.display = "inline-flex";
root.style.gap = "4px";
root.style.maxWidth = "100%";
cell.appendChild(root);
return root;
}
function ensureHeaderSorterText(sorterRoot) {
const existingText = sorterRoot.querySelector(
'[data-market-sorter="text"]'
);
if (existingText) {
return existingText;
}
const text = sorterRoot.ownerDocument.createElement("span");
text.dataset.marketSorter = "text";
text.style.display = "inline-block";
text.style.lineHeight = "20px";
text.style.whiteSpace = "nowrap";
sorterRoot.appendChild(text);
return text;
}
function ensureHeaderSorterIcon(sorterRoot) {
const existingIcon = sorterRoot.querySelector(
'[data-market-sorter="icon"]'
);
if (existingIcon) {
return existingIcon;
}
const icon = sorterRoot.ownerDocument.createElement("span");
icon.dataset.marketSorter = "icon";
icon.style.display = "inline-flex";
icon.style.flexDirection = "column";
icon.style.gap = "2px";
icon.style.justifyContent = "center";
icon.style.minWidth = "8px";
sorterRoot.appendChild(icon);
return icon;
}
function ensureHeaderTriangle(iconRoot, direction) {
const existingTriangle = iconRoot.querySelector(
`[data-market-sorter-triangle="${direction}"]`
);
if (existingTriangle) {
return existingTriangle;
}
const triangle = iconRoot.ownerDocument.createElement("span");
triangle.dataset.marketSorterTriangle = direction;
triangle.style.display = "block";
triangle.style.height = "0";
triangle.style.width = "0";
triangle.style.borderLeft = "4px solid transparent";
triangle.style.borderRight = "4px solid transparent";
iconRoot.appendChild(triangle);
return triangle;
}
function syncTriangleStyles(triangle, options) {
const activeColor = "#1f2329";
const inactiveColor = "#c9cdd4";
if (options.direction === "up") {
triangle.style.borderBottom = `5px solid ${options.active ? activeColor : inactiveColor}`;
triangle.style.borderTop = "0 solid transparent";
} else {
triangle.style.borderTop = `5px solid ${options.active ? activeColor : inactiveColor}`;
triangle.style.borderBottom = "0 solid transparent";
}
}
function readRateCellText(value) {
return value ? normalizeRateDisplay(value) : UNAVAILABLE_RATE_TEXT;
}
function applyColumnWidth(element, field) {
if (field === SELECTION_COLUMN_KEY) {
element.style.minWidth = "56px";
element.style.width = "56px";
}
if (field === BACKEND_COLUMN_KEY) {
element.style.minWidth = "240px";
element.style.width = "240px";
}
if (field === SINGLE_COLUMN_KEY || field === PERSONAL_COLUMN_KEY) {
element.style.minWidth = "160px";
element.style.width = "160px";
}
if (BACKEND_METRIC_COLUMNS2.some((column) => column.field === field)) {
element.style.minWidth = "120px";
element.style.width = "120px";
}
}
function syncContainerWidth(container) {
if (!(container instanceof HTMLElement)) {
return;
}
const directChildren = Array.from(container.children).filter(
(child) => child instanceof HTMLElement
);
const totalWidth = directChildren.reduce((sum, child) => {
return sum + readElementWidth(child);
}, 0);
if (totalWidth <= 0) {
return;
}
container.style.width = `${totalWidth}px`;
container.style.minWidth = `${totalWidth}px`;
}
function readElementWidth(element) {
const styleWidth = Number.parseFloat(element.style.width || "");
if (Number.isFinite(styleWidth) && styleWidth > 0) {
return styleWidth;
}
const minWidth = Number.parseFloat(element.style.minWidth || "");
if (Number.isFinite(minWidth) && minWidth > 0) {
return minWidth;
}
return 0;
}
function mergeMarketDataRows(baseRow, preferredRow) {
if (!baseRow && !preferredRow) {
return null;
}
if (!baseRow) {
return preferredRow;
}
if (!preferredRow) {
return baseRow;
}
return {
authorId: preferredRow.authorId || baseRow.authorId,
authorName: preferredRow.authorName || baseRow.authorName,
coreUserId: mergeNonEmptyString(baseRow.coreUserId, preferredRow.coreUserId),
exportFields: mergeExportFieldMaps(baseRow.exportFields, preferredRow.exportFields),
hasDirectRatesSource: preferredRow.hasDirectRatesSource || baseRow.hasDirectRatesSource,
location: mergeNonEmptyString(baseRow.location, preferredRow.location),
price21To60s: mergeNonEmptyString(
baseRow.price21To60s,
preferredRow.price21To60s
),
rates: mergeRates(baseRow.rates, preferredRow.rates)
};
}
function takeMatchedMarketDataRow(remainingRows, authorId, authorName) {
if (remainingRows.length === 0) {
return null;
}
const matchedIndex = remainingRows.findIndex((row) => {
if (authorId && row.authorId === authorId) {
return true;
}
if (authorName && row.authorName === authorName) {
return true;
}
return false;
});
if (matchedIndex >= 0) {
return remainingRows.splice(matchedIndex, 1)[0] ?? null;
}
if (!authorId && !authorName) {
return remainingRows.shift() ?? null;
}
return null;
}
function mergeExportFieldMaps(current, fallback) {
if (!current && !fallback) {
return void 0;
}
const nextFields = {
...current ?? {}
};
Object.entries(fallback ?? {}).forEach(([key, value]) => {
if (!hasTextValue2(nextFields[key]) && hasTextValue2(value)) {
nextFields[key] = value;
}
});
return nextFields;
}
function mergeRates(current, fallback) {
if (!current && !fallback) {
return void 0;
}
return {
singleVideoAfterSearchRate: current?.singleVideoAfterSearchRate ?? fallback?.singleVideoAfterSearchRate,
personalVideoAfterSearchRate: current?.personalVideoAfterSearchRate ?? fallback?.personalVideoAfterSearchRate
};
}
function mergeNonEmptyString(current, fallback) {
return hasTextValue2(current) ? current : fallback;
}
function formatCurrencyValue2(value) {
if (value === null) {
return void 0;
}
return `\xA5${value.toLocaleString("en-US", {
maximumFractionDigits: 0
})}`;
}
function readSerializedExportFields(record) {
if (!isRecord5(record.exportFields)) {
return void 0;
}
const entries = Object.entries(record.exportFields).flatMap(
([key, value]) => typeof value === "string" ? [[key, value]] : []
);
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
}
function hasTextValue2(value) {
return typeof value === "string" && value.trim().length > 0;
}
function renderBackendMetricsCells(cells, record) {
if (record.backendMetricsStatus === "loading" || record.status === "loading" && !record.backendMetricsStatus) {
fillBackendMetricCells(cells, "\u52A0\u8F7D\u4E2D...");
return;
}
if (record.backendMetricsStatus === "failed") {
fillBackendMetricCells(cells, "\u52A0\u8F7D\u5931\u8D25");
return;
}
if (record.backendMetricsStatus === "missing") {
fillBackendMetricCells(cells, UNAVAILABLE_BACKEND_METRICS_TEXT);
return;
}
if (record.backendMetricsStatus !== "success" || !record.backendMetrics) {
fillBackendMetricCells(cells, "");
return;
}
BACKEND_METRIC_COLUMNS2.forEach(({ field }) => {
cells[field].textContent = record.backendMetrics?.[field] ?? "";
});
}
function fillBackendMetricCells(cells, value) {
BACKEND_METRIC_COLUMNS2.forEach(({ field }) => {
cells[field].textContent = value;
});
}
// src/content/market/filter-sort-controller.ts
function applyFilterAndSort(records, options = {}) {
const filteredRecords = records.filter(
(record) => matchesFilters(record, options.filters)
);
if (!options.sort) {
return filteredRecords;
}
return [...filteredRecords].sort(
(leftRecord, rightRecord) => compareRecords(leftRecord, rightRecord, options.sort)
);
}
function matchesFilters(record, filters) {
if (!filters) {
return true;
}
return meetsThreshold(
record.rates?.singleVideoAfterSearchRate,
filters.singleVideoAfterSearchRateMin
) && meetsThreshold(
record.rates?.personalVideoAfterSearchRate,
filters.personalVideoAfterSearchRateMin
);
}
function meetsThreshold(rateValue, minValue) {
if (minValue == null) {
return true;
}
const lowerBound = parseRateLowerBound(rateValue ?? null);
return lowerBound != null && lowerBound >= minValue;
}
function compareRecords(leftRecord, rightRecord, sort) {
if (isRateSortField(sort.field)) {
return compareRateSortRecords(leftRecord, rightRecord, sort);
}
return compareBackendMetricRecords(leftRecord, rightRecord, sort);
}
function compareRateSortRecords(leftRecord, rightRecord, sort) {
const field = sort.field;
const leftValue = leftRecord.rates?.[field];
const rightValue = rightRecord.rates?.[field];
const leftLowerBound = parseRateLowerBound(leftValue ?? null);
const rightLowerBound = parseRateLowerBound(rightValue ?? null);
if (leftLowerBound == null && rightLowerBound == null) {
return compareRecordIdentity(leftRecord, rightRecord);
}
if (leftLowerBound == null) {
return 1;
}
if (rightLowerBound == null) {
return -1;
}
if (leftLowerBound !== rightLowerBound) {
return sort.direction === "asc" ? leftLowerBound - rightLowerBound : rightLowerBound - leftLowerBound;
}
const tieBreak = compareRateValues(leftValue, rightValue);
if (tieBreak !== 0) {
return sort.direction === "asc" ? tieBreak : -tieBreak;
}
return compareRecordIdentity(leftRecord, rightRecord);
}
function compareBackendMetricRecords(leftRecord, rightRecord, sort) {
const field = sort.field;
const leftValue = parseBackendMetricValue(leftRecord.backendMetrics?.[field]);
const rightValue = parseBackendMetricValue(rightRecord.backendMetrics?.[field]);
if (leftValue == null && rightValue == null) {
return compareRecordIdentity(leftRecord, rightRecord);
}
if (leftValue == null) {
return 1;
}
if (rightValue == null) {
return -1;
}
if (leftValue !== rightValue) {
return sort.direction === "asc" ? leftValue - rightValue : rightValue - leftValue;
}
return compareRecordIdentity(leftRecord, rightRecord);
}
function parseBackendMetricValue(value) {
if (!value) {
return null;
}
const normalizedValue = value.replace(/,/g, "").replace(/%/g, "").trim();
if (!normalizedValue) {
return null;
}
const numericValue = Number(normalizedValue);
return Number.isFinite(numericValue) ? numericValue : null;
}
function isRateSortField(field) {
return field === "singleVideoAfterSearchRate" || field === "personalVideoAfterSearchRate";
}
function compareRecordIdentity(leftRecord, rightRecord) {
const authorIdCompare = leftRecord.authorId.localeCompare(rightRecord.authorId);
if (authorIdCompare !== 0) {
return authorIdCompare;
}
return leftRecord.authorName.localeCompare(rightRecord.authorName);
}
// src/content/market/api-client.ts
function createMarketApiClient(options = {}) {
const baseUrl = options.baseUrl ?? resolveBaseUrl4();
const fetchImpl = options.fetchImpl ?? defaultFetch4;
const timeoutMs = options.timeoutMs ?? 8e3;
return {
async loadAuthorAseInfo(authorId) {
const primaryResult = await loadAuthorMetricsFromUrl(
buildAuthorCommerceSeedBaseInfoUrl(authorId, baseUrl)
);
if (primaryResult.success || primaryResult.reason === "timeout") {
return primaryResult;
}
return loadAuthorMetricsFromUrl(buildAuthorAseInfoUrl(authorId, baseUrl));
}
};
async function loadAuthorMetricsFromUrl(url) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetchImpl(url, {
credentials: "include",
method: "GET",
signal: controller.signal
});
if (!response.ok) {
return {
success: false,
reason: "request-failed"
};
}
return mapAuthorAseInfoResponse(await response.json());
} catch (error) {
if (isAbortError(error) || controller.signal.aborted) {
return {
success: false,
reason: "timeout"
};
}
return {
success: false,
reason: "request-failed"
};
} finally {
clearTimeout(timeoutId);
}
}
}
function buildAuthorAseInfoUrl(authorId, baseUrl) {
const url = new URL("/gw/api/aggregator/get_author_ase_info", baseUrl);
url.searchParams.set("author_id", authorId);
url.searchParams.set("range", "30");
return url.toString();
}
function buildAuthorCommerceSeedBaseInfoUrl(authorId, baseUrl) {
const url = new URL(
"/gw/api/aggregator/get_author_commerce_seed_base_info",
baseUrl
);
url.searchParams.set("o_author_id", authorId);
url.searchParams.set("range", "90");
return url.toString();
}
function mapAuthorAseInfoResponse(payload) {
const data = getPayloadData2(payload);
if (!data) {
return {
success: false,
reason: "bad-response"
};
}
const singleVideoAfterSearchRate = readNormalizedRate(
data.avg_search_after_view_rate
);
const personalVideoAfterSearchRate = readNormalizedRate(
data.personal_avg_search_after_view_rate
);
if (!singleVideoAfterSearchRate && !personalVideoAfterSearchRate) {
return {
success: false,
reason: "missing-rate"
};
}
return {
success: true,
rates: {
...singleVideoAfterSearchRate ? { singleVideoAfterSearchRate } : {},
...personalVideoAfterSearchRate ? { personalVideoAfterSearchRate } : {}
}
};
}
function getPayloadData2(payload) {
if (!isRecord6(payload)) {
return null;
}
return isRecord6(payload.data) ? payload.data : payload;
}
function readNormalizedRate(value) {
return typeof value === "string" ? normalizeRateDisplay(value) : null;
}
function resolveBaseUrl4() {
if (typeof location !== "undefined" && location.origin) {
return location.origin;
}
return "https://xingtu.cn";
}
async function defaultFetch4(input, init) {
return fetch(input, init);
}
function isAbortError(error) {
return error instanceof Error && error.name === "AbortError";
}
function isRecord6(value) {
return typeof value === "object" && value !== null;
}
// src/content/market/export-range-controller.ts
function createExportRangeController(options) {
return {
async exportRecords(target) {
const mergedRecords = /* @__PURE__ */ new Map();
let currentPage = 0;
let expectedMinimumRowCount;
while (true) {
currentPage += 1;
options.onProgress?.({
currentPage,
totalPages: target.mode === "count" ? target.pageCount : void 0
});
const currentPageRecords = await preparePageRecords(expectedMinimumRowCount);
if (!currentPageRecords) {
throw new Error(`\u7B2C ${currentPage} \u9875\u52A0\u8F7D\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`);
}
currentPageRecords.forEach((record) => {
const existingRecord = mergedRecords.get(record.authorId);
mergedRecords.set(record.authorId, mergeMarketRecord(existingRecord, record));
});
expectedMinimumRowCount = Math.max(
expectedMinimumRowCount ?? 0,
currentPageRecords.length
);
if (target.mode === "count" && currentPage >= target.pageCount) {
break;
}
const previousSignature = readMarketPageSignature(options.document);
const nextPageControl = findNextPageControl(options.document);
if (!nextPageControl || isPageControlDisabled(nextPageControl)) {
break;
}
nextPageControl.click();
const pageChanged = await waitForPageChange(previousSignature);
if (!pageChanged) {
throw new Error(`\u7B2C ${currentPage + 1} \u9875\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`);
}
}
return Array.from(mergedRecords.values());
}
};
async function preparePageRecords(expectedMinimumRowCount) {
for (let attempt = 0; attempt < 4; attempt += 1) {
const currentPageReady = await waitForCurrentPageReady();
if (!currentPageReady) {
return null;
}
await options.prepareCurrentPageForExport();
const currentPageRecords = options.readCurrentPageRecords();
if (currentPageRecords.length > 0 && (typeof expectedMinimumRowCount !== "number" || expectedMinimumRowCount <= 0 || isCurrentPageTerminal() || currentPageRecords.length >= expectedMinimumRowCount)) {
return currentPageRecords;
}
}
return null;
}
async function waitForPageChange(previousSignature) {
const previousPageState = parsePageSignature(previousSignature);
for (let attempt = 0; attempt < 60; attempt += 1) {
await new Promise((resolve) => {
options.window.setTimeout(resolve, 50);
});
await Promise.resolve();
const nextSignature = readMarketPageSignature(options.document);
const nextPageState = parsePageSignature(nextSignature);
if (hasLoadedNextPage(previousPageState, nextPageState)) {
return true;
}
}
return false;
}
async function waitForCurrentPageReady() {
let stableAttemptCount = 0;
let lastReadyFingerprint = "";
for (let attempt = 0; attempt < 80; attempt += 1) {
await new Promise((resolve) => {
options.window.setTimeout(resolve, 150);
});
await Promise.resolve();
const pageState = readCurrentPageState();
if (!pageState.authorIds || pageState.rowCount <= 0) {
stableAttemptCount = 0;
lastReadyFingerprint = "";
continue;
}
const readyFingerprint = [
pageState.pageToken,
pageState.authorIds,
String(pageState.rowCount),
pageState.isTerminalPage ? "terminal" : "paged"
].join("::");
if (readyFingerprint === lastReadyFingerprint) {
stableAttemptCount += 1;
} else {
lastReadyFingerprint = readyFingerprint;
stableAttemptCount = 1;
}
if (stableAttemptCount >= 6) {
return true;
}
}
return false;
}
function readCurrentPageState() {
const pageSignature = parsePageSignature(readMarketPageSignature(options.document));
const nextPageControl = findNextPageControl(options.document);
return {
authorIds: pageSignature.authorIds,
isTerminalPage: isPageControlDisabled(nextPageControl),
pageToken: pageSignature.pageToken,
rowCount: options.readCurrentPageRowCount()
};
}
function isCurrentPageTerminal() {
return isPageControlDisabled(findNextPageControl(options.document));
}
}
function parsePageSignature(signature) {
const separatorIndex = signature.indexOf("::");
if (separatorIndex < 0) {
return {
authorIds: "",
pageToken: signature.trim()
};
}
return {
authorIds: signature.slice(separatorIndex + 2).trim(),
pageToken: signature.slice(0, separatorIndex).trim()
};
}
function hasLoadedNextPage(previousPageState, nextPageState) {
if (!nextPageState.authorIds) {
return false;
}
if (nextPageState.pageToken || previousPageState.pageToken) {
return nextPageState.pageToken !== previousPageState.pageToken;
}
return nextPageState.authorIds !== previousPageState.authorIds;
}
function mergeMarketRecord(existingRecord, incomingRecord) {
if (!existingRecord) {
return {
...incomingRecord,
exportFields: mergeFieldMap(void 0, incomingRecord.exportFields),
rates: mergeFieldMap(void 0, incomingRecord.rates)
};
}
return {
...existingRecord,
...incomingRecord,
authorName: mergeStringValue(existingRecord.authorName, incomingRecord.authorName) ?? "",
coreUserId: mergeStringValue(existingRecord.coreUserId, incomingRecord.coreUserId),
exportFields: mergeFieldMap(
existingRecord.exportFields,
incomingRecord.exportFields
),
failureReason: incomingRecord.failureReason ?? existingRecord.failureReason,
hasDirectRatesSource: existingRecord.hasDirectRatesSource || incomingRecord.hasDirectRatesSource,
location: mergeStringValue(existingRecord.location, incomingRecord.location),
price21To60s: mergeStringValue(
existingRecord.price21To60s,
incomingRecord.price21To60s
),
rates: mergeFieldMap(existingRecord.rates, incomingRecord.rates),
status: mergeStatus(existingRecord.status, incomingRecord.status)
};
}
function mergeFieldMap(current, incoming) {
if (!current && !incoming) {
return void 0;
}
const merged = {
...current ?? {}
};
Object.entries(incoming ?? {}).forEach(([key, value]) => {
const currentValue = merged[key];
if (hasTextValue3(value) || !hasTextValue3(currentValue)) {
merged[key] = value;
}
});
return merged;
}
function mergeStatus(current, incoming) {
const priority = {
failed: 1,
idle: 0,
loading: 2,
missing: -1,
success: 3
};
return priority[incoming] >= priority[current] ? incoming : current;
}
function mergeStringValue(current, incoming) {
if (hasTextValue3(incoming) || !hasTextValue3(current)) {
return incoming ?? current;
}
return current;
}
function hasTextValue3(value) {
return typeof value === "string" && value.trim().length > 0;
}
// src/content/market/plugin-toolbar.ts
var PLUGIN_ACTION_BUTTON_STYLE_ID = "sces-plugin-action-button-style";
function isPluginToolbarMounted(root, document2) {
const actionRow = findNativeActionRow(document2);
return Boolean(actionRow && root.parentElement === actionRow && !root.hidden);
}
function ensurePluginToolbar(document2, handlers) {
ensurePluginActionButtonTheme(document2);
const existingRoot = document2.querySelector(
"[data-plugin-toolbar='root']"
);
if (existingRoot) {
if (existingRoot.querySelector(
'[data-plugin-export-audience-profile-by-id="button"]'
)) {
ensureToolbarMounted(existingRoot, document2);
return readToolbarDom(existingRoot);
}
existingRoot.remove();
}
const root = document2.createElement("section");
root.dataset.pluginToolbar = "root";
applyToolbarRootStyles(root);
const exportRangeSelect = document2.createElement("select");
exportRangeSelect.dataset.pluginExportRange = "select";
appendOption(exportRangeSelect, "current", "\u5F53\u524D\u9875");
appendOption(exportRangeSelect, "first-5", "\u524D5\u9875");
appendOption(exportRangeSelect, "first-10", "\u524D10\u9875");
appendOption(exportRangeSelect, "all", "\u5168\u90E8");
appendOption(exportRangeSelect, "custom", "\u81EA\u5B9A\u4E49");
exportRangeSelect.value = "first-5";
const exportCustomPagesInput = document2.createElement("input");
exportCustomPagesInput.type = "number";
exportCustomPagesInput.min = "1";
exportCustomPagesInput.step = "1";
exportCustomPagesInput.hidden = true;
exportCustomPagesInput.placeholder = "\u9875\u6570";
exportCustomPagesInput.dataset.pluginExportCustomPages = "input";
const exportButton = document2.createElement("button");
exportButton.type = "button";
exportButton.dataset.pluginExport = "button";
exportButton.textContent = "\u5BFC\u51FACSV";
const audienceProfileExportButton = document2.createElement("button");
audienceProfileExportButton.type = "button";
audienceProfileExportButton.dataset.pluginExportAudienceProfile = "button";
audienceProfileExportButton.textContent = "\u5BFC\u51FA\u753B\u50CFCSV";
const audienceProfileByIdExportButton = document2.createElement("button");
audienceProfileByIdExportButton.type = "button";
audienceProfileByIdExportButton.dataset.pluginExportAudienceProfileById = "button";
audienceProfileByIdExportButton.textContent = "\u6309ID\u5BFC\u51FA\u753B\u50CFCSV";
const batchSubmitButton = document2.createElement("button");
batchSubmitButton.type = "button";
batchSubmitButton.dataset.pluginBatchSubmit = "button";
batchSubmitButton.textContent = "\u63D0\u4EA4\u6279\u6B21";
const exportStatusText = document2.createElement("span");
exportStatusText.dataset.pluginExportStatus = "text";
applyStatusStyles(exportStatusText);
root.append(
exportRangeSelect,
exportCustomPagesInput,
exportButton,
audienceProfileExportButton,
audienceProfileByIdExportButton,
batchSubmitButton,
exportStatusText
);
document2.body.appendChild(root);
applyNativeControlStyles(document2, {
audienceProfileExportButton,
audienceProfileByIdExportButton,
batchSubmitButton,
exportButton,
exportCustomPagesInput,
exportRangeSelect
});
ensureToolbarMounted(root, document2);
exportButton.addEventListener("click", () => {
void handlers.onExport();
});
audienceProfileExportButton.addEventListener("click", () => {
void handlers.onExportAudienceProfile();
});
audienceProfileByIdExportButton.addEventListener("click", () => {
void handlers.onExportAudienceProfileByIds();
});
batchSubmitButton.addEventListener("click", () => {
void handlers.onSubmitBatch();
});
exportRangeSelect.addEventListener("change", () => {
syncCustomPagesInputVisibility({
batchSubmitButton,
audienceProfileByIdExportButton,
audienceProfileExportButton,
exportButton,
exportCustomPagesInput,
exportRangeSelect,
exportStatusText,
root
});
});
const toolbarDom = {
audienceProfileExportButton,
audienceProfileByIdExportButton,
batchSubmitButton,
exportButton,
exportCustomPagesInput,
exportRangeSelect,
exportStatusText,
root
};
syncCustomPagesInputVisibility(toolbarDom);
return toolbarDom;
}
function appendOption(select, value, label) {
const option = select.ownerDocument.createElement("option");
option.value = value;
option.textContent = label;
select.appendChild(option);
}
function readToolbarDom(root) {
const toolbarDom = {
audienceProfileByIdExportButton: root.querySelector(
'[data-plugin-export-audience-profile-by-id="button"]'
),
audienceProfileExportButton: root.querySelector(
'[data-plugin-export-audience-profile="button"]'
),
batchSubmitButton: root.querySelector(
'[data-plugin-batch-submit="button"]'
),
exportButton: root.querySelector(
'[data-plugin-export="button"]'
),
exportCustomPagesInput: root.querySelector(
'[data-plugin-export-custom-pages="input"]'
),
exportRangeSelect: root.querySelector(
'[data-plugin-export-range="select"]'
),
exportStatusText: root.querySelector(
'[data-plugin-export-status="text"]'
),
root
};
syncCustomPagesInputVisibility(toolbarDom);
return toolbarDom;
}
function readToolbarExportTarget(toolbar) {
const scope = toolbar.exportRangeSelect.value;
if (scope === "all") {
return {
target: {
mode: "all"
}
};
}
if (scope === "current") {
return {
target: {
mode: "count",
pageCount: 1
}
};
}
if (scope === "first-5") {
return {
target: {
mode: "count",
pageCount: 5
}
};
}
if (scope === "first-10") {
return {
target: {
mode: "count",
pageCount: 10
}
};
}
const pageCount = Number(toolbar.exportCustomPagesInput.value);
if (!Number.isInteger(pageCount) || pageCount < 1) {
return {
error: "\u8BF7\u8F93\u5165\u6709\u6548\u9875\u6570"
};
}
return {
target: {
mode: "count",
pageCount
}
};
}
function setToolbarBusyState(toolbar, isBusy) {
[
toolbar.batchSubmitButton,
toolbar.audienceProfileByIdExportButton,
toolbar.audienceProfileExportButton,
toolbar.exportButton,
toolbar.exportRangeSelect,
toolbar.exportCustomPagesInput
].forEach((element) => {
element.disabled = isBusy;
});
}
function setToolbarExportStatus(toolbar, text) {
toolbar.exportStatusText.textContent = text;
}
function syncCustomPagesInputVisibility(toolbar) {
toolbar.exportCustomPagesInput.hidden = toolbar.exportRangeSelect.value !== "custom";
}
function ensureToolbarMounted(root, document2) {
const actionRow = findNativeActionRow(document2);
if (!actionRow) {
root.hidden = true;
return;
}
const customizeButton = findNativeActionButton(actionRow, "\u81EA\u5B9A\u4E49\u6307\u6807");
const insertionAnchor = customizeButton ? findDirectChildAnchor(actionRow, customizeButton) : null;
if (insertionAnchor) {
actionRow.insertBefore(root, insertionAnchor);
} else if (root.parentElement !== actionRow) {
actionRow.prepend(root);
}
root.hidden = false;
}
function findNativeActionRow(document2) {
const customizeButton = findNativeActionButton(document2, "\u81EA\u5B9A\u4E49\u6307\u6807");
const exportButton = findNativeActionButton(document2, "\u5BFC\u51FA");
const header = findHeaderContainer(customizeButton, exportButton);
const sharedActionRow = customizeButton && exportButton ? findSmallestSharedActionRow(customizeButton, exportButton, header) : null;
if (sharedActionRow) {
return sharedActionRow;
}
const scope = header ?? document2;
const candidates = Array.from(
scope.querySelectorAll(".xt-space.xt-space--medium, .search-content--header")
).filter(
(element) => element instanceof document2.defaultView.HTMLElement
);
const rankedCandidates = candidates.filter(
(candidate) => isNativeActionRowCandidate(candidate, customizeButton, exportButton)
).sort((left, right) => {
const depthDelta = getDepthWithinAncestor(right, header) - getDepthWithinAncestor(left, header);
if (depthDelta !== 0) {
return depthDelta;
}
return normalizeText(left.textContent).length - normalizeText(right.textContent).length;
});
return rankedCandidates[0] ?? null;
}
function findHeaderContainer(customizeButton, exportButton) {
return customizeButton?.closest(".search-content--header") ?? exportButton?.closest(".search-content--header");
}
function findSmallestSharedActionRow(customizeButton, exportButton, boundary) {
const exportAncestors = new Set(collectAncestorChain(exportButton, boundary));
for (const candidate of collectAncestorChain(customizeButton, boundary)) {
if (exportAncestors.has(candidate) && isNativeActionRowCandidate(candidate, customizeButton, exportButton)) {
return candidate;
}
}
return null;
}
function collectAncestorChain(element, boundary) {
const ancestors = [];
let current = element.parentElement;
while (current) {
ancestors.push(current);
if (current === boundary) {
break;
}
current = current.parentElement;
}
return ancestors;
}
function isNativeActionRowCandidate(candidate, customizeButton, exportButton) {
if (customizeButton && !candidate.contains(customizeButton)) {
return false;
}
if (exportButton && !candidate.contains(exportButton)) {
return false;
}
const directChildLabels = Array.from(candidate.children).flatMap((child) => {
const buttons = [];
if (child instanceof candidate.ownerDocument.defaultView.HTMLButtonElement) {
buttons.push(child);
}
buttons.push(...Array.from(child.querySelectorAll("button")));
return buttons;
}).map((button) => normalizeText(button.textContent));
return directChildLabels.includes("\u5BFC\u51FA") && (directChildLabels.includes("\u81EA\u5B9A\u4E49\u6307\u6807") || Boolean(customizeButton));
}
function getDepthWithinAncestor(element, boundary) {
let depth = 0;
let current = element.parentElement;
while (current && current !== boundary) {
depth += 1;
current = current.parentElement;
}
return depth;
}
function findNativeActionButton(root, text) {
const document2 = root instanceof Document ? root : root.ownerDocument;
if (!document2) {
return null;
}
const candidates = Array.from(root.querySelectorAll("button")).filter(
(element) => element instanceof document2.defaultView.HTMLElement
);
return candidates.find((element) => normalizeText(element.textContent) === text) ?? null;
}
function applyToolbarRootStyles(root) {
root.style.display = "inline-flex";
root.style.alignItems = "center";
root.style.columnGap = "8px";
root.style.flexWrap = "wrap";
}
function applyNativeControlStyles(document2, controls) {
const primaryButton = findButtonContainingText(document2, "\u53D1\u5E03\u4EFB\u52A1") ?? findButtonContainingText(document2, "+\u53D1\u5E03\u4EFB\u52A1");
const nativeButton = primaryButton ?? findNativeActionButton(document2, "\u81EA\u5B9A\u4E49\u6307\u6807") ?? findNativeActionButton(document2, "\u5BFC\u51FA");
if (nativeButton) {
controls.exportButton.className = nativeButton.className;
controls.audienceProfileExportButton.className = nativeButton.className;
controls.audienceProfileByIdExportButton.className = nativeButton.className;
controls.batchSubmitButton.className = nativeButton.className;
}
[
controls.exportButton,
controls.audienceProfileExportButton,
controls.audienceProfileByIdExportButton,
controls.batchSubmitButton
].forEach((button) => {
applyPrimaryButtonStyles3(button);
button.style.whiteSpace = "nowrap";
});
[controls.exportRangeSelect, controls.exportCustomPagesInput].forEach((element) => {
element.style.height = "32px";
element.style.border = "1px solid #d0d7de";
element.style.borderRadius = "6px";
element.style.padding = "0 10px";
element.style.background = "#fff";
element.style.color = "#1f2329";
element.style.boxSizing = "border-box";
});
controls.exportRangeSelect.style.minWidth = "104px";
controls.exportCustomPagesInput.style.width = "72px";
}
function applyPrimaryButtonStyles3(button) {
button.style.backgroundColor = "#7f1d2d";
button.style.border = "1px solid #7f1d2d";
button.style.borderRadius = "8px";
button.style.color = "#ffffff";
button.style.height = "32px";
button.style.padding = "0 15px";
button.style.boxSizing = "border-box";
button.style.fontWeight = "600";
button.style.transition = "background-color 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease";
}
function applyStatusStyles(statusText) {
statusText.style.color = "#64748b";
statusText.style.fontSize = "12px";
statusText.style.lineHeight = "20px";
statusText.style.marginLeft = "4px";
statusText.style.whiteSpace = "nowrap";
}
function ensurePluginActionButtonTheme(document2) {
if (document2.getElementById(PLUGIN_ACTION_BUTTON_STYLE_ID)) {
return;
}
const style = document2.createElement("style");
style.id = PLUGIN_ACTION_BUTTON_STYLE_ID;
style.textContent = `
[data-plugin-export="button"]:hover:not(:disabled),
[data-plugin-export-audience-profile="button"]:hover:not(:disabled),
[data-plugin-export-audience-profile-by-id="button"]:hover:not(:disabled),
[data-plugin-batch-submit="button"]:hover:not(:disabled) {
background-color: #6d1627 !important;
border-color: #6d1627 !important;
}
[data-plugin-export="button"]:active:not(:disabled),
[data-plugin-export-audience-profile="button"]:active:not(:disabled),
[data-plugin-export-audience-profile-by-id="button"]:active:not(:disabled),
[data-plugin-batch-submit="button"]:active:not(:disabled) {
background-color: #58111f !important;
border-color: #58111f !important;
transform: translateY(1px);
}
[data-plugin-export="button"]:focus-visible,
[data-plugin-export-audience-profile="button"]:focus-visible,
[data-plugin-export-audience-profile-by-id="button"]:focus-visible,
[data-plugin-batch-submit="button"]:focus-visible {
outline: none !important;
box-shadow: 0 0 0 3px rgba(127, 29, 45, 0.2) !important;
}
[data-plugin-export="button"]:disabled,
[data-plugin-export-audience-profile="button"]:disabled,
[data-plugin-export-audience-profile-by-id="button"]:disabled,
[data-plugin-batch-submit="button"]:disabled {
background-color: #c89ca4 !important;
border-color: #c89ca4 !important;
color: rgba(255, 255, 255, 0.95) !important;
cursor: not-allowed !important;
opacity: 1 !important;
transform: none !important;
box-shadow: none !important;
}
`;
document2.head.appendChild(style);
}
function normalizeText(value) {
return value?.replace(/\s+/g, " ").trim() ?? "";
}
function findButtonContainingText(root, text) {
const document2 = root instanceof Document ? root : root.ownerDocument;
if (!document2) {
return null;
}
const candidates = Array.from(root.querySelectorAll("button")).filter(
(element) => element instanceof document2.defaultView.HTMLElement
);
return candidates.find((element) => normalizeText(element.textContent).includes(text)) ?? null;
}
function findDirectChildAnchor(ancestor, descendant) {
let current = descendant;
let previous = null;
while (current && current !== ancestor) {
previous = current;
current = current.parentElement;
}
return current === ancestor ? previous : null;
}
// src/content/market/market-list-request-snapshot.ts
var MARKET_REQUEST_SNAPSHOT_ATTRIBUTE = "data-sces-market-request-snapshot";
var MARKET_SEARCH_ENDPOINT_PATH = "/gw/api/gsearch/search_for_author_square";
function readMarketListRequestSnapshot(document2) {
const serializedSnapshot = document2.documentElement.getAttribute(
MARKET_REQUEST_SNAPSHOT_ATTRIBUTE
);
if (!serializedSnapshot) {
return readMarketListRequestSnapshotFromPageState(document2);
}
try {
const parsedSnapshot = normalizeMarketListRequestSnapshot(
JSON.parse(serializedSnapshot)
);
if (!parsedSnapshot) {
return readMarketListRequestSnapshotFromPageState(document2);
}
return parsedSnapshot;
} catch {
return readMarketListRequestSnapshotFromPageState(document2);
}
}
function isMarketListRequestSnapshot(value) {
if (!value || typeof value !== "object") {
return false;
}
const candidate = value;
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) {
if (!value || typeof value !== "object") {
return null;
}
const candidate = value;
const normalizedSnapshot = {
body: typeof candidate.body === "string" ? candidate.body : void 0,
method: typeof candidate.method === "string" ? candidate.method : void 0,
url: typeof candidate.url === "string" ? candidate.url : void 0
};
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) {
if (!value || typeof value !== "object") {
return false;
}
return Object.values(value).every((entry) => typeof entry === "string");
}
function readMarketListRequestSnapshotFromPageState(document2) {
const reqParams = findMarketReqParams(document2);
if (!reqParams) {
return null;
}
return {
body: JSON.stringify(reqParams),
method: "POST",
url: buildMarketSearchUrl(document2)
};
}
function findMarketReqParams(document2) {
const marketRoot = document2.querySelector(".base-author-list");
const setupState = marketRoot?.__vue__?._setupState;
if (!setupState) {
return null;
}
const queue = Object.values(setupState);
while (queue.length > 0) {
const current = unwrapVueRef3(queue.shift());
if (!isRecord7(current)) {
continue;
}
const reqParams = unwrapVueRef3(current.reqParams);
if (isRecord7(reqParams)) {
return reqParams;
}
Object.values(current).forEach((value) => {
queue.push(value);
});
}
return null;
}
function buildMarketSearchUrl(document2) {
if (document2.location?.origin && document2.location.origin !== "null" && document2.location.origin !== "about:blank") {
return document2.location.origin.includes("xingtu.cn") ? MARKET_SEARCH_ENDPOINT_PATH : new URL(MARKET_SEARCH_ENDPOINT_PATH, document2.location.origin).toString();
}
return MARKET_SEARCH_ENDPOINT_PATH;
}
function unwrapVueRef3(value) {
if (isRecord7(value) && "value" in value) {
return value.value;
}
return value;
}
function isRecord7(value) {
return typeof value === "object" && value !== null;
}
// src/content/market/silent-export-controller.ts
var PAGE_NUMBER_KEYS2 = [
"currentPage",
"page",
"pageNo",
"pageNum",
"page_no",
"page_num"
];
function createSilentExportController(options) {
const fetchImpl = options.fetchImpl ?? defaultFetch5;
return {
async exportRecords(target) {
const snapshot = readMarketListRequestSnapshot(options.document);
if (!snapshot) {
return null;
}
const baseRequest = createPagedRequest(snapshot);
if (!baseRequest) {
return null;
}
const mergedRecords = /* @__PURE__ */ new Map();
const maxPageCount = target.mode === "count" ? target.pageCount : 200;
let totalPagesHint;
for (let offset = 0; offset < maxPageCount; offset += 1) {
const pageNumber = baseRequest.initialPage + offset;
options.onProgress?.({
currentPage: offset + 1,
totalPages: target.mode === "count" ? target.pageCount : totalPagesHint
});
const payload = await fetchPagePayload(fetchImpl, baseRequest, pageNumber);
const parsedResponse = parseMarketListResponse(payload);
if (!parsedResponse) {
return null;
}
totalPagesHint = parsedResponse.totalPages ?? totalPagesHint;
if (parsedResponse.records.length === 0) {
break;
}
parsedResponse.records.forEach((record) => {
const existingRecord = mergedRecords.get(record.authorId);
mergedRecords.set(record.authorId, mergeMarketRecord2(existingRecord, record));
});
if (target.mode === "count" && offset + 1 >= target.pageCount) {
break;
}
if (target.mode === "all") {
if (typeof parsedResponse.totalPages === "number" && pageNumber >= parsedResponse.totalPages) {
break;
}
if (typeof parsedResponse.pageSize === "number" && parsedResponse.records.length < parsedResponse.pageSize) {
break;
}
}
}
return Array.from(mergedRecords.values());
}
};
}
function createPagedRequest(snapshot) {
const bodyPage = readPageFromBody(snapshot.body);
if (bodyPage !== null) {
return {
initialPage: bodyPage,
pageSource: "body",
snapshot
};
}
const urlPage = readPageFromUrl(snapshot.url);
if (urlPage !== null) {
return {
initialPage: urlPage,
pageSource: "url",
snapshot
};
}
return {
initialPage: 1,
pageSource: "none",
snapshot
};
}
async function fetchPagePayload(fetchImpl, request, pageNumber) {
const nextUrl = request.pageSource === "url" ? mutateUrlPage(request.snapshot.url, pageNumber) : request.snapshot.url;
const nextBody = mutateBodyPage(request.snapshot.body, pageNumber);
const response = await fetchImpl(nextUrl, {
body: nextBody,
credentials: "include",
headers: filterReplayHeaders(request.snapshot.headers, nextBody),
method: request.snapshot.method
});
if (!response.ok) {
throw new Error("\u9759\u9ED8\u5BFC\u51FA\u8BF7\u6C42\u5931\u8D25");
}
return response.json();
}
function readPageFromUrl(url) {
try {
const parsedUrl = new URL(url);
for (const key of PAGE_NUMBER_KEYS2) {
const value = readNumericString(parsedUrl.searchParams.get(key));
if (value !== null) {
return value;
}
}
} catch {
return null;
}
return null;
}
function mutateUrlPage(url, pageNumber) {
try {
const parsedUrl = new URL(url);
for (const key of PAGE_NUMBER_KEYS2) {
if (!parsedUrl.searchParams.has(key)) {
continue;
}
parsedUrl.searchParams.set(key, String(pageNumber));
return parsedUrl.toString();
}
parsedUrl.searchParams.set("page", String(pageNumber));
return parsedUrl.toString();
} catch {
return url;
}
}
function readPageFromBody(body) {
const parsedBody = parseBody(body);
if (!parsedBody) {
return null;
}
return readKnownPaginationNumber(parsedBody, "page");
}
function mutateBodyPage(body, pageNumber) {
if (!body) {
return body;
}
const trimmedBody = body.trim();
if (!trimmedBody) {
return body;
}
try {
const parsedJson = JSON.parse(trimmedBody);
if (!replacePageNumberInValue(parsedJson, pageNumber) && isRecord8(parsedJson)) {
parsedJson.page = pageNumber;
}
return JSON.stringify(parsedJson);
} catch {
const searchParams = new URLSearchParams(trimmedBody);
for (const key of PAGE_NUMBER_KEYS2) {
if (!searchParams.has(key)) {
continue;
}
searchParams.set(key, String(pageNumber));
return searchParams.toString();
}
searchParams.set("page", String(pageNumber));
return searchParams.toString();
}
}
function parseBody(body) {
if (!body) {
return null;
}
const trimmedBody = body.trim();
if (!trimmedBody) {
return null;
}
try {
const parsedBody = JSON.parse(trimmedBody);
return isRecord8(parsedBody) ? parsedBody : null;
} catch {
const searchParams = new URLSearchParams(trimmedBody);
const payload = {};
searchParams.forEach((value, key) => {
payload[key] = value;
});
return payload;
}
}
function replacePageNumberInValue(value, pageNumber) {
if (!isRecord8(value)) {
return false;
}
let replaced = false;
PAGE_NUMBER_KEYS2.forEach((key) => {
if (!(key in value)) {
return;
}
value[key] = pageNumber;
replaced = true;
});
if (replaced) {
return true;
}
return Object.values(value).some((entry) => replacePageNumberInValue(entry, pageNumber));
}
function filterReplayHeaders(headers, body) {
const filteredHeaders = Object.fromEntries(
Object.entries(headers ?? {}).filter(([key]) => {
const normalizedKey = key.toLowerCase();
return normalizedKey !== "content-length" && normalizedKey !== "host";
})
);
if (body) {
if (!hasHeader(filteredHeaders, "accept")) {
filteredHeaders.Accept = "application/json, text/plain, */*";
}
if (!hasHeader(filteredHeaders, "content-type")) {
filteredHeaders["Content-Type"] = "application/json";
}
if (!hasHeader(filteredHeaders, "x-login-source")) {
filteredHeaders["x-login-source"] = "1";
}
if (!hasHeader(filteredHeaders, "agw-js-conv")) {
filteredHeaders["Agw-Js-Conv"] = "str";
}
}
return Object.keys(filteredHeaders).length > 0 ? filteredHeaders : void 0;
}
function hasHeader(headers, key) {
return Object.keys(headers).some((headerKey) => headerKey.toLowerCase() === key);
}
function readNumericString(value) {
if (!value) {
return null;
}
const parsedValue = Number(value);
return Number.isFinite(parsedValue) ? parsedValue : null;
}
async function defaultFetch5(input, init) {
return fetch(input, init);
}
function isRecord8(value) {
return typeof value === "object" && value !== null;
}
function mergeMarketRecord2(existingRecord, incomingRecord) {
if (!existingRecord) {
return {
...incomingRecord,
exportFields: mergeFieldMap2(void 0, incomingRecord.exportFields),
rates: mergeFieldMap2(void 0, incomingRecord.rates),
status: incomingRecord.status ?? "idle"
};
}
return {
...existingRecord,
...incomingRecord,
authorName: mergeStringValue2(existingRecord.authorName, incomingRecord.authorName) ?? "",
coreUserId: mergeStringValue2(existingRecord.coreUserId, incomingRecord.coreUserId),
exportFields: mergeFieldMap2(
existingRecord.exportFields,
incomingRecord.exportFields
),
failureReason: incomingRecord.failureReason ?? existingRecord.failureReason,
hasDirectRatesSource: existingRecord.hasDirectRatesSource || incomingRecord.hasDirectRatesSource,
location: mergeStringValue2(existingRecord.location, incomingRecord.location),
price21To60s: mergeStringValue2(
existingRecord.price21To60s,
incomingRecord.price21To60s
),
rates: mergeFieldMap2(existingRecord.rates, incomingRecord.rates),
status: incomingRecord.status ?? existingRecord.status
};
}
function mergeFieldMap2(current, incoming) {
if (!current && !incoming) {
return void 0;
}
const merged = {
...current ?? {}
};
Object.entries(incoming ?? {}).forEach(([key, value]) => {
const currentValue = merged[key];
if (hasTextValue4(value) || !hasTextValue4(currentValue)) {
merged[key] = value;
}
});
return merged;
}
function mergeStringValue2(current, incoming) {
return hasTextValue4(incoming) ? incoming : current;
}
function hasTextValue4(value) {
return Boolean(value && value.trim().length > 0);
}
// src/content/market/result-store.ts
function createMarketResultStore() {
const records = /* @__PURE__ */ new Map();
return {
getRecord(authorId) {
return records.get(authorId) ?? null;
},
listRecords() {
return Array.from(records.values());
},
setAuthorFailed(authorId, reason) {
const existingRecord = ensureRecord(authorId);
existingRecord.status = "failed";
existingRecord.failureReason = reason;
},
setAuthorLoading(authorId) {
const existingRecord = ensureRecord(authorId);
existingRecord.status = "loading";
delete existingRecord.failureReason;
},
setBackendMetricsFailed(authorId) {
const existingRecord = ensureRecord(authorId);
existingRecord.backendMetricsStatus = "failed";
},
setBackendMetricsLoading(authorId) {
const existingRecord = ensureRecord(authorId);
existingRecord.backendMetricsStatus = "loading";
},
setBackendMetricsMissing(authorId) {
const existingRecord = ensureRecord(authorId);
existingRecord.backendMetricsStatus = "missing";
},
setBackendMetricsSuccess(authorId, backendMetrics) {
const existingRecord = ensureRecord(authorId);
existingRecord.backendMetricsStatus = "success";
existingRecord.backendMetrics = {
...existingRecord.backendMetrics,
...backendMetrics
};
},
setAuthorSuccess(authorId, rates) {
const existingRecord = ensureRecord(authorId);
existingRecord.status = "success";
existingRecord.rates = {
...existingRecord.rates,
...rates
};
delete existingRecord.failureReason;
},
upsertMarketRow(row) {
const existingRecord = records.get(row.authorId);
if (existingRecord) {
existingRecord.authorName = mergeStringValue3(existingRecord.authorName, row.authorName) ?? existingRecord.authorName;
existingRecord.coreUserId = mergeStringValue3(
existingRecord.coreUserId,
row.coreUserId
);
existingRecord.location = mergeStringValue3(
existingRecord.location,
row.location
);
existingRecord.price21To60s = mergeStringValue3(
existingRecord.price21To60s,
row.price21To60s
);
existingRecord.exportFields = mergeFieldMap3(
existingRecord.exportFields,
row.exportFields
);
existingRecord.backendMetrics = mergeFieldMap3(
existingRecord.backendMetrics,
row.backendMetrics
);
existingRecord.hasDirectRatesSource = existingRecord.hasDirectRatesSource || row.hasDirectRatesSource;
existingRecord.rates = mergeFieldMap3(existingRecord.rates, row.rates);
return existingRecord;
}
const nextRecord = {
...row,
backendMetricsStatus: "idle",
status: "idle"
};
records.set(row.authorId, nextRecord);
return nextRecord;
}
};
function ensureRecord(authorId) {
const existingRecord = records.get(authorId);
if (existingRecord) {
return existingRecord;
}
const nextRecord = {
authorId,
authorName: authorId,
backendMetricsStatus: "idle",
status: "idle"
};
records.set(authorId, nextRecord);
return nextRecord;
}
}
function mergeFieldMap3(current, incoming) {
if (!current && !incoming) {
return void 0;
}
const merged = {
...current ?? {}
};
Object.entries(incoming ?? {}).forEach(([key, value]) => {
const currentValue = merged[key];
if (!hasTextValue5(currentValue)) {
merged[key] = value;
}
});
return merged;
}
function mergeStringValue3(current, incoming) {
if (!hasTextValue5(current)) {
return incoming ?? current;
}
return current;
}
function hasTextValue5(value) {
return typeof value === "string" && value.trim().length > 0;
}
// src/shared/auth-messages.ts
function isAuthResponseMessage(value) {
if (!value || typeof value !== "object") {
return false;
}
const candidate = value;
if (candidate.ok === false) {
return candidate.type === "auth:error" && typeof candidate.error === "string";
}
if (candidate.ok !== true || typeof candidate.type !== "string") {
return false;
}
if (candidate.type === "auth:ack") {
return true;
}
if (candidate.type === "auth:token") {
return Boolean(
candidate.value && typeof candidate.value === "object" && typeof candidate.value.accessToken === "string"
);
}
if (candidate.type === "auth:state") {
return Boolean(
candidate.value && typeof candidate.value === "object" && typeof candidate.value.isAuthenticated === "boolean"
);
}
return false;
}
// src/shared/backend-metrics-messages.ts
function isBackendMetricsResponseMessage(value) {
if (!value || typeof value !== "object") {
return false;
}
const candidate = value;
if (candidate.ok === false) {
return candidate.type === "backend-metrics:error" && typeof candidate.error === "string";
}
return Boolean(
candidate.ok === true && candidate.type === "backend-metrics:result" && candidate.value && typeof candidate.value === "object" && Array.isArray(candidate.value.rows)
);
}
// src/content/market/index.ts
function createMarketController(options) {
const marketApiClient = createMarketApiClient();
const audienceProfileClient = createAudienceProfileClient();
const authorBaseClient = createAuthorBaseClient();
const businessAbilityClient = createBusinessAbilityClient();
const sendRuntimeMessage = createRuntimeMessageSender();
const resultStore = options.resultStore ?? createMarketResultStore();
const loadAuthorMetrics = options.loadAuthorMetrics ?? marketApiClient.loadAuthorAseInfo;
const searchBackendMetrics = options.searchBackendMetrics ?? (hasRuntimeMessageSender() ? (starIds) => readBackendMetrics(sendRuntimeMessage, starIds) : null);
const buildCsv = options.buildCsv ?? buildMarketCsv;
const buildAudienceCsv = options.buildAudienceProfileCsv ?? buildAudienceProfileCsv;
const loadAudienceProfile = options.loadAudienceProfile ?? audienceProfileClient.loadAudienceProfile;
const loadAuthorBaseInfo = options.loadAuthorBaseInfo ?? authorBaseClient.loadAuthorBaseInfo;
const loadBusinessAbility = options.loadBusinessAbility ?? businessAbilityClient.loadBusinessAbility;
const getAuthState = options.getAuthState ?? (() => readAuthState(sendRuntimeMessage));
const mutationObserverFactory = options.mutationObserverFactory ?? ((callback) => new MutationObserver(callback));
const promptBatchName = options.promptBatchName ?? (() => promptForBatchName(options.document));
const promptAuthorIds = options.promptAuthorIds ?? (() => promptForAuthorIds(options.document));
const submitBatch = options.submitBatch ?? ((payload) => readBatchSubmitAck(sendRuntimeMessage, payload));
const audienceProfileTargets = [
{ kind: "audience", target: AUDIENCE_PROFILE_TARGETS.audience },
{ kind: "fans", target: AUDIENCE_PROFILE_TARGETS.fans },
{
kind: "longtimeFans",
target: AUDIENCE_PROFILE_TARGETS.longtimeFans
}
];
let activeProgressLabel = "\u5BFC\u51FA\u4E2D";
let shouldShowDetailedProgress = true;
const exportRangeController = createExportRangeController({
document: options.document,
onProgress: ({ currentPage, totalPages }) => {
updateToolbarProgress(currentPage, totalPages);
},
prepareCurrentPageForExport,
readCurrentPageRecords: () => getVisibleOrderedRecords(),
readCurrentPageRowCount: () => countCurrentPageRows(options.document),
window: options.window
});
const silentExportController = createSilentExportController({
document: options.document,
onProgress: ({ currentPage, totalPages }) => {
updateToolbarProgress(currentPage, totalPages);
}
});
let activeSort;
let isDisposed = false;
let isSyncRunning = false;
let isSyncScheduled = false;
let lastKnownPageSignature = "";
let needsResync = false;
let scheduledSyncTimeoutId = null;
const selectedAuthorIds = /* @__PURE__ */ new Set();
let toolbar;
const observer = mutationObserverFactory(() => {
if (isDisposed) {
return;
}
let nextPageSignature = lastKnownPageSignature;
try {
nextPageSignature = readMarketPageSignature(options.document);
} catch {
return;
}
const toolbarNeedsRemount = !toolbar || !isPluginToolbarMounted(toolbar.root, options.document);
const selectionControlsMissing = !options.document.querySelector('[data-market-selection-checkbox="row"]') || !options.document.querySelector('[data-market-selection-checkbox="header"]');
if (nextPageSignature === lastKnownPageSignature && !toolbarNeedsRemount && !selectionControlsMissing) {
return;
}
scheduleSync();
});
const observationRoot = options.document.body ?? options.document.documentElement;
startObserving();
const toolbarHandlers = {
onExport: async () => {
syncSelectionStateFromDom();
const exportTarget = readToolbarExportTarget(toolbar);
if (!exportTarget.target) {
setToolbarExportStatus(toolbar, exportTarget.error ?? "\u5BFC\u51FA\u914D\u7F6E\u65E0\u6548");
return;
}
setToolbarBusyState(toolbar, true);
try {
const records = filterRecordsBySelection(
await exportRecords(exportTarget.target, "\u5BFC\u51FA\u4E2D", {
showDetailedProgress: selectedAuthorIds.size === 0
})
);
options.onCsvReady?.(buildCsv(records));
setToolbarExportStatus(toolbar, "");
} catch (error) {
setToolbarExportStatus(
toolbar,
error instanceof Error ? error.message : "\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
);
} finally {
setToolbarBusyState(toolbar, false);
}
},
onExportAudienceProfile: async () => {
syncSelectionStateFromDom();
if (selectedAuthorIds.size === 0) {
setToolbarExportStatus(toolbar, "\u8BF7\u5148\u52FE\u9009\u9700\u8981\u5BFC\u51FA\u753B\u50CF\u7684\u8FBE\u4EBA");
return;
}
const exportTarget = readToolbarExportTarget(toolbar);
if (!exportTarget.target) {
setToolbarExportStatus(toolbar, exportTarget.error ?? "\u5BFC\u51FA\u914D\u7F6E\u65E0\u6548");
return;
}
setToolbarBusyState(toolbar, true);
try {
const selectedRecords = filterRecordsBySelectionStrict(
await exportRecords(exportTarget.target, "\u753B\u50CF\u5BFC\u51FA\u4E2D", {
showDetailedProgress: false
})
);
if (selectedRecords.length === 0) {
setToolbarExportStatus(toolbar, "\u5F53\u524D\u5BFC\u51FA\u8303\u56F4\u5185\u6CA1\u6709\u9009\u4E2D\u7684\u8FBE\u4EBA");
return;
}
const rows = [];
for (let index = 0; index < selectedRecords.length; index += 1) {
const record = selectedRecords[index];
setToolbarExportStatus(
toolbar,
`\u753B\u50CF\u5BFC\u51FA\u4E2D ${index + 1}/${selectedRecords.length}...`
);
const [profiles, businessAbility] = await Promise.all([
loadAudienceProfileSet(record),
loadBusinessAbilitySafe(record)
]);
rows.push({
businessAbility,
profiles,
record
});
}
if (rows.every(
(row) => Object.values(row.profiles).every((profile) => profile.status === "failed")
)) {
setToolbarExportStatus(toolbar, "\u753B\u50CF\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
return;
}
options.onCsvReady?.(buildAudienceCsv(rows), buildAudienceProfileFilename());
setToolbarExportStatus(toolbar, "");
} catch (error) {
setToolbarExportStatus(
toolbar,
error instanceof Error ? error.message : "\u753B\u50CF\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
);
} finally {
setToolbarBusyState(toolbar, false);
}
},
onExportAudienceProfileByIds: async () => {
const input = await promptAuthorIds();
if (input === null) {
return;
}
const parsed = parseAuthorIds(input);
if (parsed.ids.length === 0) {
setToolbarExportStatus(toolbar, "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u8FBE\u4EBA\u661F\u56FEID");
return;
}
setToolbarBusyState(toolbar, true);
try {
setToolbarExportStatus(
toolbar,
`\u8BC6\u522B ${parsed.ids.length + parsed.duplicates.length + parsed.invalidTokens.length} \u4E2A\uFF0C\u53BB\u91CD\u540E ${parsed.ids.length} \u4E2A\uFF0C\u975E\u6CD5 ${parsed.invalidTokens.length} \u4E2A`
);
const backendMetricsByAuthorId = await loadBackendMetricsMap(parsed.ids);
const rows = [];
for (let index = 0; index < parsed.ids.length; index += 1) {
const authorId = parsed.ids[index];
setToolbarExportStatus(
toolbar,
`\u6309ID\u753B\u50CF\u5BFC\u51FA\u4E2D ${index + 1}/${parsed.ids.length}...`
);
rows.push(
await loadAudienceProfileRowById(
authorId,
backendMetricsByAuthorId.get(authorId)
)
);
}
options.onCsvReady?.(
buildAudienceCsv(rows),
buildAudienceProfileFilename(/* @__PURE__ */ new Date(), "\u6309ID\u5BFC\u51FA")
);
setToolbarExportStatus(toolbar, "");
} catch (error) {
setToolbarExportStatus(
toolbar,
error instanceof Error ? error.message : "\u6309ID\u5BFC\u51FA\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
);
} finally {
setToolbarBusyState(toolbar, false);
}
},
onSubmitBatch: async () => {
syncSelectionStateFromDom();
const exportTarget = readToolbarExportTarget(toolbar);
if (!exportTarget.target) {
setToolbarExportStatus(toolbar, exportTarget.error ?? "\u5BFC\u51FA\u914D\u7F6E\u65E0\u6548");
return;
}
const batchName = await promptBatchName();
if (batchName === null) {
return;
}
if (!batchName.trim()) {
setToolbarExportStatus(toolbar, "\u8BF7\u8F93\u5165\u6279\u6B21\u540D\u79F0");
return;
}
setToolbarBusyState(toolbar, true);
try {
const hasSelectedAuthors = selectedAuthorIds.size > 0;
const records = filterRecordsBySelection(
await exportRecords(
exportTarget.target,
hasSelectedAuthors ? "\u63D0\u4EA4\u5DF2\u9009\u8FBE\u4EBA\u4E2D" : "\u63D0\u4EA4\u4E2D",
{
showDetailedProgress: !hasSelectedAuthors
}
)
);
const authState = await getAuthState();
if (!authState.isAuthenticated) {
throw new Error("\u8BF7\u5148\u767B\u5F55\u63D2\u4EF6");
}
const payload = createBatchPayload({
authState,
batchName,
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
records
});
await submitBatch(payload);
setToolbarExportStatus(toolbar, "\u6279\u6B21\u63D0\u4EA4\u6210\u529F");
} catch (error) {
setToolbarExportStatus(
toolbar,
error instanceof Error ? error.message : "\u6279\u6B21\u63D0\u4EA4\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
);
} finally {
setToolbarBusyState(toolbar, false);
}
}
};
toolbar = ensurePluginToolbar(options.document, toolbarHandlers);
const ready = runSyncCycle();
return {
dispose() {
isDisposed = true;
observer.disconnect();
if (scheduledSyncTimeoutId !== null) {
options.window.clearTimeout(scheduledSyncTimeoutId);
scheduledSyncTimeoutId = null;
}
},
ready
};
async function hydrateCurrentPage() {
const table = syncMarketTable(options.document);
if (!table) {
return;
}
const pageRows = [];
for (const rowDom of table.rows) {
const rowSnapshot = readRowSnapshot(rowDom);
if (!rowSnapshot.authorId || !hasTextValue6(rowSnapshot.authorName)) {
continue;
}
pageRows.push({
rowDom,
rowSnapshot
});
resultStore.upsertMarketRow(rowSnapshot);
}
const pendingRateRows = [];
const rowsNeedingBackendMetrics = [];
pageRows.forEach(({ rowDom, rowSnapshot }) => {
if (rowSnapshot.hasDirectRatesSource) {
resultStore.setAuthorSuccess(rowSnapshot.authorId, rowSnapshot.rates ?? {});
}
const existingRecord = resultStore.getRecord(rowSnapshot.authorId);
const needsRateFetch = !hasSettledRateState(existingRecord) && !hasCompleteRates(existingRecord?.rates);
const needsBackendMetrics = Boolean(searchBackendMetrics) && !hasSettledBackendMetricsState(existingRecord);
if (needsRateFetch) {
resultStore.setAuthorLoading(rowSnapshot.authorId);
pendingRateRows.push({
rowDom,
rowSnapshot
});
}
if (needsBackendMetrics) {
resultStore.setBackendMetricsLoading(rowSnapshot.authorId);
rowsNeedingBackendMetrics.push({
rowDom,
rowSnapshot
});
}
if (needsRateFetch || needsBackendMetrics) {
renderMarketRowState(rowDom, {
...existingRecord ?? {
authorId: rowSnapshot.authorId,
authorName: rowSnapshot.authorName,
status: "idle"
},
...rowSnapshot,
backendMetricsStatus: needsBackendMetrics ? "loading" : existingRecord?.backendMetricsStatus,
rates: existingRecord?.rates,
status: needsRateFetch || needsBackendMetrics ? "loading" : existingRecord?.status ?? "idle"
});
return;
}
if (existingRecord) {
renderMarketRowState(rowDom, existingRecord);
}
});
await Promise.all([
hydrateRatesForRows(pendingRateRows),
hydrateBackendMetricsForPage(rowsNeedingBackendMetrics)
]);
pageRows.forEach(({ rowDom, rowSnapshot }) => {
const record = resultStore.getRecord(rowSnapshot.authorId);
if (!record) {
return;
}
renderMarketRowState(rowDom, record);
});
}
async function hydrateRatesForRows(pageRows) {
if (pageRows.length === 0) {
return;
}
await Promise.all(
pageRows.map(async ({ rowSnapshot }) => {
const metricsResult = await loadAuthorMetrics(rowSnapshot.authorId);
if (metricsResult.success) {
resultStore.setAuthorSuccess(rowSnapshot.authorId, metricsResult.rates);
return;
}
resultStore.setAuthorFailed(rowSnapshot.authorId, metricsResult.reason);
})
);
}
async function hydrateBackendMetricsForPage(pageRows) {
if (!searchBackendMetrics || pageRows.length === 0) {
return;
}
try {
const rows = await searchBackendMetrics(
pageRows.map(({ rowSnapshot }) => rowSnapshot.authorId)
);
const rowMap = new Map(rows.map((row) => [row.starId, row]));
pageRows.forEach(({ rowSnapshot }) => {
const backendMetrics = rowMap.get(rowSnapshot.authorId);
if (backendMetrics) {
resultStore.setBackendMetricsSuccess(rowSnapshot.authorId, backendMetrics);
} else {
resultStore.setBackendMetricsMissing(rowSnapshot.authorId);
}
});
} catch {
pageRows.forEach(({ rowSnapshot }) => {
resultStore.setBackendMetricsFailed(rowSnapshot.authorId);
});
}
}
function applyCurrentView() {
runWithoutMutationSync(() => {
toolbar = ensurePluginToolbar(options.document, toolbarHandlers);
const table = syncMarketTable(options.document);
if (!table) {
return;
}
syncPluginSortHeaders(options.document, {
activeSort,
onToggleSort: toggleSortFromHeader
});
const records = getVisibleOrderedRecords(table);
applyRowVisibility(table, new Set(records.map((record) => record.authorId)));
applyRowOrder(table, records.map((record) => record.authorId));
bindSelectionControls(table);
syncMarketSelectionState(table, selectedAuthorIds);
lastKnownPageSignature = readMarketPageSignature(options.document);
});
}
function bindSelectionControls(table) {
if (!table) {
return;
}
table.rows.forEach((rowDom) => {
rowDom.selectionCheckbox.dataset.marketSelectionAuthorId = rowDom.authorId;
if (rowDom.selectionCheckbox.dataset.marketSelectionBound === "true") {
return;
}
rowDom.selectionCheckbox.dataset.marketSelectionBound = "true";
rowDom.selectionCheckbox.addEventListener("change", () => {
if (rowDom.selectionCheckbox.checked) {
selectedAuthorIds.add(rowDom.authorId);
} else {
selectedAuthorIds.delete(rowDom.authorId);
}
refreshSelectionControls();
});
});
if (!table.headerSelectionCheckbox) {
return;
}
if (table.headerSelectionCheckbox.dataset.marketSelectionBound === "true") {
return;
}
table.headerSelectionCheckbox.dataset.marketSelectionBound = "true";
table.headerSelectionCheckbox.addEventListener("change", () => {
const currentTable = syncMarketTable(options.document);
if (!currentTable) {
return;
}
const visibleRows = currentTable.rows.filter(
(rowDom) => rowDom.visibilityTargets.some((target) => !target.hidden)
);
const scopedRows = visibleRows.length > 0 ? visibleRows : currentTable.rows;
if (table.headerSelectionCheckbox?.checked) {
scopedRows.forEach((rowDom) => {
selectedAuthorIds.add(rowDom.authorId);
});
} else {
scopedRows.forEach((rowDom) => {
selectedAuthorIds.delete(rowDom.authorId);
});
}
refreshSelectionControls();
});
}
function refreshSelectionControls() {
const table = syncMarketTable(options.document);
if (!table) {
return;
}
bindSelectionControls(table);
syncMarketSelectionState(table, selectedAuthorIds);
}
function syncSelectionStateFromDom() {
const rowSelectionCheckboxes = Array.from(
options.document.querySelectorAll('[data-market-selection-checkbox="row"]')
).filter(
(element) => element instanceof HTMLInputElement
);
if (rowSelectionCheckboxes.length === 0) {
return;
}
rowSelectionCheckboxes.forEach((checkbox) => {
const authorId = checkbox.dataset.marketSelectionAuthorId?.trim();
if (!authorId) {
return;
}
if (checkbox.checked) {
selectedAuthorIds.add(authorId);
} else {
selectedAuthorIds.delete(authorId);
}
});
refreshSelectionControls();
}
function toggleSortFromHeader(field) {
activeSort = getNextSortState(activeSort, field);
applyCurrentView();
}
function getVisibleOrderedRecords(table = syncMarketTable(options.document)) {
const currentPageRecords = readCurrentPageRecords(table);
return applyFilterAndSort(currentPageRecords, {
sort: activeSort
});
}
async function exportRecords(target, inProgressLabel = "\u5BFC\u51FA\u4E2D", progressOptions = {}) {
activeProgressLabel = inProgressLabel;
shouldShowDetailedProgress = progressOptions.showDetailedProgress ?? true;
setToolbarExportStatus(toolbar, `${inProgressLabel}...`);
if (target.mode === "count" && target.pageCount <= 1) {
await prepareCurrentPageForExport();
return getVisibleOrderedRecords();
}
const silentExportRecords = await silentExportController.exportRecords(target);
if (silentExportRecords) {
return hydrateExportRecords(
silentExportRecords.map((record) => ({
...record,
status: record.status ?? "idle"
}))
);
}
return exportRangeController.exportRecords(target);
}
function updateToolbarProgress(currentPage, totalPages) {
if (!shouldShowDetailedProgress) {
setToolbarExportStatus(toolbar, `${activeProgressLabel}...`);
return;
}
setToolbarExportStatus(
toolbar,
totalPages ? `${activeProgressLabel} ${currentPage}/${totalPages} \u9875...` : `${activeProgressLabel} \u7B2C${currentPage}\u9875...`
);
}
function filterRecordsBySelection(records) {
if (selectedAuthorIds.size === 0) {
return records;
}
const selectedRecords = records.filter(
(record) => selectedAuthorIds.has(record.authorId)
);
return selectedRecords.length > 0 ? selectedRecords : records;
}
function filterRecordsBySelectionStrict(records) {
if (selectedAuthorIds.size === 0) {
return [];
}
return records.filter((record) => selectedAuthorIds.has(record.authorId));
}
async function loadAudienceProfileSet(record) {
const profiles = {};
for (const { kind, target } of audienceProfileTargets) {
try {
profiles[kind] = await loadAudienceProfile(record, target);
} catch (error) {
profiles[kind] = {
failureReason: error instanceof Error ? error.message : "request-failed",
status: "failed"
};
}
}
return profiles;
}
async function loadBusinessAbilitySafe(record) {
try {
return await loadBusinessAbility(record);
} catch (error) {
return {
failureReason: error instanceof Error ? error.message : "request-failed",
status: "failed"
};
}
}
async function loadAudienceProfileRowById(authorId, backendMetrics) {
const [baseRecord, metricsResult] = await Promise.all([
loadAuthorBaseInfoSafe(authorId),
loadAuthorMetricsSafe(authorId)
]);
const recordForRequests = {
...baseRecord,
authorName: baseRecord.authorName || authorId,
...metricsResult.success ? { rates: metricsResult.rates } : {},
...backendMetrics ? { backendMetrics, backendMetricsStatus: "success" } : {}
};
const [profiles, businessAbility] = await Promise.all([
loadAudienceProfileSet(recordForRequests),
loadBusinessAbilitySafe(recordForRequests)
]);
const failureReasons = collectAudienceProfileRowFailures(
baseRecord,
profiles,
businessAbility
);
const rowStatus = failureReasons.length === 0 ? "\u6210\u529F" : hasAudienceProfileRowSuccess(baseRecord, profiles, businessAbility) ? "\u90E8\u5206\u6210\u529F" : "\u5931\u8D25";
const authorName = baseRecord.authorName || "";
return {
businessAbility,
profiles,
record: {
...recordForRequests,
exportFields: {
\u8FBE\u4EBAID: authorId,
\u8FBE\u4EBA\u540D\u79F0: authorName,
\u5BFC\u51FA\u72B6\u6001: rowStatus,
\u5931\u8D25\u539F\u56E0: failureReasons.join("; ")
}
}
};
}
async function loadAuthorBaseInfoSafe(authorId) {
try {
return await loadAuthorBaseInfo(authorId);
} catch (error) {
return {
authorId,
authorName: "",
failureReason: error instanceof Error ? "request-failed" : "request-failed",
status: "failed"
};
}
}
async function loadAuthorMetricsSafe(authorId) {
try {
return await loadAuthorMetrics(authorId);
} catch {
return {
reason: "request-failed",
success: false
};
}
}
async function loadBackendMetricsMap(authorIds) {
const metricsMap = /* @__PURE__ */ new Map();
if (!searchBackendMetrics || authorIds.length === 0) {
return metricsMap;
}
try {
const rows = await searchBackendMetrics(authorIds);
rows.forEach((row) => {
const { starId, ...backendMetrics } = row;
metricsMap.set(starId, backendMetrics);
});
} catch {
return metricsMap;
}
return metricsMap;
}
function collectAudienceProfileRowFailures(baseRecord, profiles, businessAbility) {
const failures = [];
if (baseRecord.status === "failed") {
failures.push(`\u57FA\u7840\u4FE1\u606F:${baseRecord.failureReason ?? "request-failed"}`);
}
Object.entries(profiles).forEach(([kind, profile]) => {
if (profile.status === "failed") {
failures.push(`${readAudienceProfileKindLabel(kind)}:${profile.failureReason ?? "request-failed"}`);
}
});
if (businessAbility.status === "failed") {
failures.push(`\u5546\u4E1A\u80FD\u529B:${businessAbility.failureReason ?? "request-failed"}`);
}
return failures;
}
function hasAudienceProfileRowSuccess(baseRecord, profiles, businessAbility) {
return baseRecord.status === "success" || businessAbility.status === "success" || Object.values(profiles).some((profile) => profile.status === "success");
}
function readAudienceProfileKindLabel(kind) {
if (kind === "audience") {
return "\u89C2\u4F17\u753B\u50CF";
}
if (kind === "fans") {
return "\u7C89\u4E1D\u753B\u50CF";
}
return "\u94C1\u7C89\u753B\u50CF";
}
async function prepareCurrentPageForExport() {
await runSyncCycle();
await harvestCurrentPageForExport();
await runSyncCycle();
}
async function hydrateExportRecords(records) {
for (const record of records) {
resultStore.upsertMarketRow(record);
const existingRecord = resultStore.getRecord(record.authorId);
if (existingRecord?.status === "success" && existingRecord.rates) {
continue;
}
if (record.hasDirectRatesSource) {
const directRates = record.rates ?? {};
const hasAllRates = Boolean(directRates.singleVideoAfterSearchRate) && Boolean(directRates.personalVideoAfterSearchRate);
resultStore.setAuthorSuccess(record.authorId, directRates);
if (hasAllRates) {
continue;
}
} else {
resultStore.setAuthorLoading(record.authorId);
}
const metricsResult = await loadAuthorMetrics(record.authorId);
if (metricsResult.success) {
resultStore.setAuthorSuccess(record.authorId, metricsResult.rates);
} else {
resultStore.setAuthorFailed(record.authorId, metricsResult.reason);
}
}
if (searchBackendMetrics) {
const backendTargetRecords = records.filter((record) => {
const existingRecord = resultStore.getRecord(record.authorId);
return !(existingRecord?.backendMetricsStatus === "success" || existingRecord?.backendMetricsStatus === "missing");
});
if (backendTargetRecords.length > 0) {
backendTargetRecords.forEach((record) => {
resultStore.setBackendMetricsLoading(record.authorId);
});
try {
const backendRows = await searchBackendMetrics(
backendTargetRecords.map((record) => record.authorId)
);
const backendRowMap = new Map(backendRows.map((row) => [row.starId, row]));
backendTargetRecords.forEach((record) => {
const backendMetrics = backendRowMap.get(record.authorId);
if (backendMetrics) {
resultStore.setBackendMetricsSuccess(record.authorId, backendMetrics);
} else {
resultStore.setBackendMetricsMissing(record.authorId);
}
});
} catch {
backendTargetRecords.forEach((record) => {
resultStore.setBackendMetricsFailed(record.authorId);
});
}
}
}
return records.map((record) => toMarketRecord(record));
}
async function harvestCurrentPageForExport() {
let hydrationSnapshot = await collectCurrentPageSnapshotsUntilSettled();
if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount === 0) {
return;
}
const table = syncMarketTable(options.document);
const scrollContainer = findCurrentPageScrollContainer(table);
if (!scrollContainer) {
return;
}
const originalScrollTop = scrollContainer.scrollTop;
const maxScrollTop = Math.max(
0,
scrollContainer.scrollHeight - scrollContainer.clientHeight
);
if (maxScrollTop <= 0) {
return;
}
const step = Math.max(scrollContainer.clientHeight, 240);
for (let nextScrollTop = Math.min(originalScrollTop + step, maxScrollTop); nextScrollTop > originalScrollTop && nextScrollTop <= maxScrollTop; nextScrollTop = Math.min(nextScrollTop + step, maxScrollTop)) {
setScrollTop(scrollContainer, nextScrollTop);
hydrationSnapshot = await collectCurrentPageSnapshotsUntilSettled();
if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount === 0) {
break;
}
if (nextScrollTop === maxScrollTop) {
break;
}
}
if (scrollContainer.scrollTop !== originalScrollTop) {
setScrollTop(scrollContainer, originalScrollTop);
}
}
function readCurrentPageRecords(table) {
if (!table) {
return [];
}
return table.rows.map((rowDom) => {
const rowSnapshot = readRowSnapshot(rowDom);
if (!rowSnapshot.authorId || !hasTextValue6(rowSnapshot.authorName)) {
return null;
}
return toMarketRecord(rowSnapshot);
}).filter((record) => record !== null);
}
function toMarketRecord(rowSnapshot) {
const existingRecord = resultStore.getRecord(rowSnapshot.authorId);
const authorName = mergeStringValue4(existingRecord?.authorName, rowSnapshot.authorName) ?? "";
const coreUserId = mergeStringValue4(
existingRecord?.coreUserId,
rowSnapshot.coreUserId
);
const location2 = mergeStringValue4(existingRecord?.location, rowSnapshot.location);
const price21To60s = mergeStringValue4(
existingRecord?.price21To60s,
rowSnapshot.price21To60s
);
return {
...existingRecord,
...rowSnapshot,
authorName,
backendMetrics: mergeFieldMap4(
existingRecord?.backendMetrics,
rowSnapshot.backendMetrics
),
backendMetricsStatus: existingRecord?.backendMetricsStatus ?? "idle",
coreUserId,
exportFields: withExportFieldFallbacks(
mergeFieldMap4(existingRecord?.exportFields, rowSnapshot.exportFields),
{
authorName,
location: location2,
price21To60s
}
),
location: location2,
price21To60s,
rates: mergeFieldMap4(existingRecord?.rates, rowSnapshot.rates),
status: existingRecord?.status ?? "idle"
};
}
function collectCurrentPageSnapshots() {
readCurrentPageRows(options.document).forEach((rowSnapshot) => {
resultStore.upsertMarketRow(rowSnapshot);
});
}
function findCurrentPageScrollContainer(table) {
if (!table) {
return null;
}
const candidateScores = /* @__PURE__ */ new Map();
const candidateRoots = table.rows.map((rowDom) => rowDom.row).filter((row) => row instanceof options.window.HTMLElement);
for (const rootElement of candidateRoots) {
let currentElement = rootElement.parentElement;
let depth = 0;
while (currentElement) {
if (isScrollableContainer(currentElement)) {
const scrollRange = currentElement.scrollHeight - currentElement.clientHeight;
const existingScore = candidateScores.get(currentElement);
if (!existingScore || depth < existingScore.depth) {
candidateScores.set(currentElement, {
depth,
scrollRange
});
}
}
depth += 1;
currentElement = currentElement.parentElement;
}
}
const rankedCandidates = Array.from(candidateScores.entries()).sort((left, right) => {
const [, leftScore] = left;
const [, rightScore] = right;
if (rightScore.scrollRange !== leftScore.scrollRange) {
return rightScore.scrollRange - leftScore.scrollRange;
}
return leftScore.depth - rightScore.depth;
});
return rankedCandidates[0]?.[0] ?? null;
}
function isScrollableContainer(element) {
const computedStyle = options.window.getComputedStyle(element);
return /auto|scroll|overlay/.test(computedStyle.overflowY) && element.scrollHeight > element.clientHeight;
}
async function waitForDomSettled() {
await new Promise((resolve) => {
options.window.setTimeout(resolve, 0);
});
await Promise.resolve();
}
async function collectCurrentPageSnapshotsUntilSettled() {
let previousFingerprint = "";
let stablePassCount = 0;
let fingerprintStableSince = 0;
let lastSnapshot = {
blankExportFieldCount: 0,
fingerprint: "",
missingDefaultFieldCount: 0
};
for (let attempt = 0; attempt < 16; attempt += 1) {
await waitForDomSettled();
if (attempt > 0) {
await new Promise((resolve) => {
options.window.setTimeout(
resolve,
previousFingerprint.includes("|missing:0") ? 25 : 50
);
});
await Promise.resolve();
}
collectCurrentPageSnapshots();
const hydrationSnapshot = readVisibleRowHydrationSnapshot();
lastSnapshot = hydrationSnapshot;
if (!hydrationSnapshot.fingerprint) {
stablePassCount = 0;
previousFingerprint = "";
continue;
}
if (hydrationSnapshot.fingerprint === previousFingerprint) {
stablePassCount += 1;
} else {
previousFingerprint = hydrationSnapshot.fingerprint;
stablePassCount = 1;
fingerprintStableSince = options.window.Date.now();
}
const stableForMs = options.window.Date.now() - fingerprintStableSince;
if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount === 0 && stablePassCount >= 2) {
return hydrationSnapshot;
}
if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount > 0 && stablePassCount >= 2 && stableForMs >= 500) {
return hydrationSnapshot;
}
}
return lastSnapshot;
}
function readVisibleRowHydrationSnapshot() {
const table = syncMarketTable(options.document);
if (!table || table.rows.length === 0) {
return {
blankExportFieldCount: 0,
fingerprint: "",
missingDefaultFieldCount: 0
};
}
const parts = table.rows.map((rowDom) => {
const rowSnapshot = readRowSnapshot(rowDom);
const populatedFieldCount = Object.values(rowSnapshot.exportFields ?? {}).filter(
(value) => typeof value === "string" && value.trim().length > 0
).length;
const blankExportFieldCount = Object.values(rowSnapshot.exportFields ?? {}).filter(
(value) => typeof value !== "string" || value.trim().length === 0
).length;
const hasAuthorField = hasTextValue6(rowSnapshot.exportFields?.["\u8FBE\u4EBA\u4FE1\u606F"]);
const hasRepresentativeVideo = hasTextValue6(
rowSnapshot.exportFields?.["\u4EE3\u8868\u89C6\u9891"]
);
const hasPriceField = hasTextValue6(rowSnapshot.price21To60s) || hasTextValue6(rowSnapshot.exportFields?.["21-60s\u62A5\u4EF7"]);
const missingDefaultFieldCount = Number(!hasAuthorField) + Number(!hasRepresentativeVideo) + Number(!hasPriceField);
return [
rowSnapshot.authorId,
populatedFieldCount,
`blank:${blankExportFieldCount}`,
hasAuthorField ? "author" : "no-author",
hasRepresentativeVideo ? "video" : "no-video",
hasPriceField ? "price" : "no-price",
`missing:${missingDefaultFieldCount}`
].join(":");
});
return {
blankExportFieldCount: parts.reduce((count, part) => {
const match = part.match(/:blank:(\d+):/);
return count + Number(match?.[1] ?? 0);
}, 0),
fingerprint: parts.join("|"),
missingDefaultFieldCount: parts.reduce((count, part) => {
const match = part.match(/missing:(\d+)$/);
return count + Number(match?.[1] ?? 0);
}, 0)
};
}
function scheduleSync() {
if (isDisposed) {
return;
}
if (isSyncRunning) {
needsResync = true;
return;
}
if (isSyncScheduled) {
return;
}
isSyncScheduled = true;
scheduledSyncTimeoutId = options.window.setTimeout(() => {
scheduledSyncTimeoutId = null;
isSyncScheduled = false;
if (isDisposed) {
return;
}
void runSyncCycle();
}, 0);
}
function runWithoutMutationSync(callback) {
if (isDisposed) {
return;
}
observer.disconnect();
try {
callback();
} finally {
startObserving();
}
}
function startObserving() {
if (isDisposed || !observationRoot) {
return;
}
observer.observe(observationRoot, {
childList: true,
subtree: true
});
}
async function runSyncCycle() {
if (isDisposed) {
return;
}
if (isSyncRunning) {
needsResync = true;
return;
}
isSyncRunning = true;
try {
toolbar = ensurePluginToolbar(options.document, toolbarHandlers);
await hydrateCurrentPage();
applyCurrentView();
lastKnownPageSignature = readMarketPageSignature(options.document);
} finally {
isSyncRunning = false;
if (isDisposed) {
return;
}
if (needsResync) {
needsResync = false;
scheduleSync();
}
}
}
}
function setScrollTop(element, top) {
element.scrollTop = top;
element.dispatchEvent(new Event("scroll"));
}
function readCurrentPageRows(document2) {
const table = syncMarketTable(document2);
if (!table) {
return [];
}
return table.rows.map((rowDom) => readRowSnapshot(rowDom)).filter(
(row) => Boolean(row.authorId) && hasTextValue6(row.authorName)
);
}
function countCurrentPageRows(document2) {
const table = syncMarketTable(document2);
if (!table) {
return 0;
}
return table.rows.filter((rowDom) => Boolean(rowDom.authorId)).length;
}
function readRowSnapshot(rowDom) {
return {
authorId: rowDom.authorId,
authorName: rowDom.authorName,
exportFields: rowDom.exportFields,
hasDirectRatesSource: rowDom.hasDirectRatesSource,
location: rowDom.location,
price21To60s: rowDom.price21To60s,
rates: rowDom.rates
};
}
function getNextSortState(currentSort, field) {
if (!currentSort || currentSort.field !== field) {
return {
direction: "desc",
field
};
}
if (currentSort.direction === "desc") {
return {
direction: "asc",
field
};
}
return void 0;
}
function hasCompleteRates(rates) {
return Boolean(
rates?.singleVideoAfterSearchRate && rates?.personalVideoAfterSearchRate
);
}
function hasSettledRateState(record) {
if (!record) {
return false;
}
return record.status === "failed" || hasCompleteRates(record.rates);
}
function hasSettledBackendMetricsState(record) {
if (!record) {
return false;
}
return record.backendMetricsStatus === "success" || record.backendMetricsStatus === "missing" || record.backendMetricsStatus === "failed";
}
function mergeFieldMap4(current, incoming) {
if (!current && !incoming) {
return void 0;
}
const merged = {
...current ?? {}
};
Object.entries(incoming ?? {}).forEach(([key, value]) => {
const currentValue = merged[key];
if (hasTextValue6(value) || !hasTextValue6(currentValue)) {
merged[key] = value;
}
});
return merged;
}
function createRuntimeMessageSender() {
return (message) => Promise.resolve(
globalThis.chrome?.runtime?.sendMessage?.(message)
);
}
async function readAuthState(sendMessage) {
const response = await sendMessage({ type: "auth:get-state" });
if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") {
throw new Error("\u8BF7\u5148\u767B\u5F55\u63D2\u4EF6");
}
return response.value;
}
async function readBatchSubmitAck(sendMessage, payload) {
const response = await sendMessage({
payload,
type: "batch:submit"
});
if (response && typeof response === "object" && response.ok === true) {
return response.value;
}
if (response && typeof response === "object" && response.ok === false && typeof response.error === "string") {
throw new Error(response.error);
}
throw new Error("\u6279\u6B21\u63D0\u4EA4\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
}
async function readBackendMetrics(sendMessage, starIds) {
const response = await sendMessage({
type: "backend-metrics:search",
value: {
starIds
}
});
if (isBackendMetricsResponseMessage(response) && response.ok && response.type === "backend-metrics:result") {
return response.value.rows;
}
throw new Error("\u540E\u7AEF\u6307\u6807\u52A0\u8F7D\u5931\u8D25");
}
function mergeStringValue4(current, incoming) {
if (hasTextValue6(incoming) || !hasTextValue6(current)) {
return incoming ?? current;
}
return current;
}
function withExportFieldFallbacks(exportFields, fallbackValues) {
if (!exportFields) {
return void 0;
}
const nextExportFields = {
...exportFields
};
if ("\u8FBE\u4EBA\u4FE1\u606F" in nextExportFields && !hasTextValue6(nextExportFields["\u8FBE\u4EBA\u4FE1\u606F"]) && hasTextValue6(fallbackValues.authorName)) {
nextExportFields["\u8FBE\u4EBA\u4FE1\u606F"] = fallbackValues.authorName;
}
if ("\u5730\u533A" in nextExportFields && !hasTextValue6(nextExportFields["\u5730\u533A"]) && hasTextValue6(fallbackValues.location)) {
nextExportFields["\u5730\u533A"] = fallbackValues.location;
}
if ("21-60s\u62A5\u4EF7" in nextExportFields && !hasTextValue6(nextExportFields["21-60s\u62A5\u4EF7"]) && hasTextValue6(fallbackValues.price21To60s)) {
nextExportFields["21-60s\u62A5\u4EF7"] = fallbackValues.price21To60s;
}
return nextExportFields;
}
function hasTextValue6(value) {
return typeof value === "string" && value.trim().length > 0;
}
function hasRuntimeMessageSender() {
return Boolean(
globalThis.chrome?.runtime?.sendMessage
);
}
function buildAudienceProfileFilename(date = /* @__PURE__ */ new Date(), label) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hour = String(date.getHours()).padStart(2, "0");
const minute = String(date.getMinutes()).padStart(2, "0");
const labelPart = label ? `_${label}` : "";
return `\u8FBE\u4EBA\u8FDE\u63A5\u7528\u6237\u753B\u50CF${labelPart}_${year}${month}${day}_${hour}${minute}.csv`;
}
// src/content/market/auth-gate.ts
function renderMarketAuthGate(document2, currentWindow, message = "\u8BF7\u5148\u767B\u5F55\u63D2\u4EF6") {
const existingGate = document2.querySelector(
'[data-market-auth-gate="root"]'
);
if (existingGate) {
return existingGate;
}
const root = document2.createElement("section");
root.dataset.marketAuthGate = "root";
root.innerHTML = `
<strong></strong>
<p>\u6253\u5F00\u6269\u5C55\u5F39\u7A97\u5B8C\u6210\u767B\u5F55\u540E\u5237\u65B0\u672C\u9875</p>
<button type="button" data-market-auth-help="button">\u53BB\u767B\u5F55</button>
`;
const title = root.querySelector("strong");
if (title) {
title.textContent = message;
}
root.querySelector('[data-market-auth-help="button"]')?.addEventListener("click", () => {
currentWindow.alert("\u8BF7\u70B9\u51FB\u6D4F\u89C8\u5668\u5DE5\u5177\u680F\u4E2D\u7684\u6269\u5C55\u56FE\u6807\u5B8C\u6210\u767B\u5F55");
});
document2.body.prepend(root);
return root;
}
// src/content/index.ts
var DOWNLOAD_MARKET_CSV_MESSAGE = "download-market-csv";
async function bootContentScript(options = {}) {
const currentWindow = options.window ?? window;
const currentDocument = options.document ?? document;
const controllerFactory = options.createMarketController ?? createMarketController;
const sendAuthMessage = options.sendAuthMessage ?? createRuntimeMessageSender2();
if (!isMarketPage(currentWindow.location.href)) {
return null;
}
installMarketPageBridge(currentDocument);
const authState = await readAuthState2(sendAuthMessage);
if (!authState?.isAuthenticated) {
await waitForBodyReady(currentDocument, currentWindow);
renderMarketAuthGate(
currentDocument,
currentWindow,
isExpiredAuthState(authState) ? "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" : void 0
);
return {
ready: Promise.resolve()
};
}
await waitForBodyReady(currentDocument, currentWindow);
return controllerFactory({
document: currentDocument,
onCsvReady: (csv, filename) => {
if (filename) {
downloadCsv(currentDocument, currentWindow, csv, filename);
return;
}
if (requestCsvDownload(csv)) {
return;
}
downloadCsv(currentDocument, currentWindow, csv, filename);
},
window: currentWindow
});
}
async function readAuthState2(sendMessage) {
const response = await sendMessage({ type: "auth:get-state" });
if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") {
return null;
}
return response.value;
}
function isMarketPage(url) {
const parsedUrl = new URL(url);
const isXingtuHost = parsedUrl.hostname === "xingtu.cn" || parsedUrl.hostname.endsWith(".xingtu.cn");
return isXingtuHost && parsedUrl.pathname.startsWith("/ad/creator/market");
}
function bootstrapContentScript() {
const runtime = globalThis.chrome?.runtime;
if (!runtime || typeof window === "undefined" || typeof document === "undefined") {
return;
}
const marker = "__starChartSearchEnhancerContentController";
const scopedWindow = window;
if (scopedWindow[marker]) {
return;
}
scopedWindow[marker] = true;
void bootContentScript().then((controller) => {
scopedWindow[marker] = controller;
});
}
bootstrapContentScript();
function requestCsvDownload(csv, filename) {
const runtime = globalThis.chrome?.runtime;
if (!runtime?.id || typeof runtime.sendMessage !== "function") {
return false;
}
runtime.sendMessage({
csv,
filename: filename ?? `star-chart-search-enhancer-${formatTimestampForFilename()}.csv`,
type: DOWNLOAD_MARKET_CSV_MESSAGE
});
return true;
}
function createRuntimeMessageSender2() {
return async (message) => {
const runtime = globalThis.chrome?.runtime;
if (typeof runtime?.sendMessage !== "function") {
return null;
}
return runtime.sendMessage(message);
};
}
async function waitForBodyReady(document2, currentWindow) {
if (document2.body) {
return;
}
await new Promise((resolve) => {
const handleReady = () => {
if (document2.body) {
document2.removeEventListener("DOMContentLoaded", handleReady);
resolve();
}
};
document2.addEventListener("DOMContentLoaded", handleReady);
currentWindow.setTimeout(handleReady, 0);
});
}
function downloadCsv(document2, window2, csv, filename) {
const blob = new Blob(["\uFEFF", csv], {
type: "text/csv;charset=utf-8"
});
const objectUrl = window2.URL.createObjectURL(blob);
const link = document2.createElement("a");
link.href = objectUrl;
link.download = filename ?? `star-chart-search-enhancer-${formatTimestampForFilename()}.csv`;
document2.body.appendChild(link);
link.click();
link.remove();
window2.URL.revokeObjectURL(objectUrl);
}
function formatTimestampForFilename() {
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
}
function isExpiredAuthState(authState) {
const lastError = authState?.lastError;
return typeof lastError === "string" && (/token/i.test(lastError) || lastError.includes("\u8FC7\u671F"));
}
function installMarketPageBridge(document2) {
if (document2.documentElement.querySelector(
'[data-sces-market-bridge="script"]'
)) {
return;
}
const script = document2.createElement("script");
script.dataset.scesMarketBridge = "script";
const runtime = globalThis.chrome?.runtime;
const bridgeUrl = runtime?.getURL?.("content/market-page-bridge.js");
if (bridgeUrl) {
script.src = bridgeUrl;
} else {
script.textContent = "";
}
(document2.head ?? document2.documentElement).appendChild(script);
}
})();