+ *
+ * Copyright (c) 2014-2017, Jon Schlinkert.
+ * Released under the MIT License.
+ *)
+*/
diff --git a/dist-release/content/index.js b/dist-release/content/index.js
new file mode 100644
index 0000000..db7cf3a
--- /dev/null
+++ b/dist-release/content/index.js
@@ -0,0 +1,4446 @@
+"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: "\u770B\u540E\u641C\u7387",
+ readValue: (record) => record.backendMetrics?.afterViewSearchRate ?? ""
+ },
+ {
+ header: "\u770B\u540E\u641C\u6570",
+ readValue: (record) => record.backendMetrics?.afterViewSearchCount ?? ""
+ },
+ {
+ header: "\u65B0\u589EA3\u6570",
+ readValue: (record) => record.backendMetrics?.a3IncreaseCount ?? ""
+ },
+ {
+ header: "\u65B0\u589EA3\u7387",
+ readValue: (record) => record.backendMetrics?.newA3Rate ?? ""
+ },
+ {
+ header: "CPA3",
+ readValue: (record) => record.backendMetrics?.cpa3 ?? ""
+ },
+ {
+ header: "cp_search",
+ readValue: (record) => record.backendMetrics?.cpSearch ?? ""
+ }
+ ];
+ function buildMarketCsv(records) {
+ const baseColumns = buildBaseColumns(records);
+ const csvColumns = [...baseColumns, ...RATE_COLUMNS, ...BACKEND_METRIC_COLUMNS];
+ 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 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/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");
+ applyOverlayStyles(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";
+ applyTitleStyles(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";
+ applySecondaryButtonStyles(cancelButton);
+ const confirmButton = document2.createElement("button");
+ confirmButton.type = "button";
+ confirmButton.dataset.pluginBatchNameConfirm = "button";
+ confirmButton.textContent = "\u786E\u8BA4\u63D0\u4EA4";
+ applyPrimaryButtonStyles(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 applyOverlayStyles(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 applyTitleStyles(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 applySecondaryButtonStyles(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 applyPrimaryButtonStyles(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
+ })),
+ 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: readString(readMarketFieldValue(row, attributeDatas, "star_id")) ?? readString(readMarketFieldValue(row, attributeDatas, "id")) ?? "",
+ authorName: readString(readMarketFieldValue(row, attributeDatas, "nickname")) ?? readString(readMarketFieldValue(row, attributeDatas, "nick_name")) ?? "",
+ exportFields: buildMarketExportFieldFallbacks(row, attributeDatas),
+ hasDirectRatesSource: true,
+ location: readMarketLocation(row, attributeDatas),
+ price21To60s: readMarketPrice21To60s(row, attributeDatas),
+ rates: singleVideoAfterSearchRate ? {
+ singleVideoAfterSearchRate
+ } : void 0
+ };
+ }
+ function parseMarketListResponse(payload) {
+ const container = findMarketListContainer(payload);
+ if (!container) {
+ return null;
+ }
+ const marketList = readMarketListArray(container);
+ if (!marketList) {
+ return null;
+ }
+ return {
+ currentPage: readKnownNumberDeep(container, PAGE_NUMBER_KEYS) ?? void 0,
+ pageSize: readKnownNumberDeep(container, PAGE_SIZE_KEYS) ?? void 0,
+ records: marketList.map((row) => isRecord(row) ? mapMarketListRow(row) : null).filter(
+ (row) => row !== null && Boolean(row.authorId || row.authorName)
+ ),
+ totalCount: readKnownNumberDeep(container, TOTAL_COUNT_KEYS) ?? void 0,
+ totalPages: readKnownNumberDeep(container, TOTAL_PAGE_KEYS) ?? void 0
+ };
+ }
+ function readKnownPaginationNumber(value, kind) {
+ if (!isRecord(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 (!isRecord(current)) {
+ continue;
+ }
+ if (readMarketListArray(current)) {
+ return current;
+ }
+ Object.values(current).forEach((entry) => {
+ queue.push(unwrapVueRef(entry));
+ });
+ }
+ return null;
+ }
+ function readMarketListArray(record) {
+ const marketList = unwrapVueRef(record.marketList);
+ if (Array.isArray(marketList)) {
+ return marketList;
+ }
+ const authors = unwrapVueRef(record.authors);
+ if (Array.isArray(authors)) {
+ return authors;
+ }
+ return null;
+ }
+ function unwrapVueRef(value) {
+ if (isRecord(value) && "value" in value) {
+ return value.value;
+ }
+ return value;
+ }
+ function isRecord(value) {
+ return typeof value === "object" && value !== null;
+ }
+ function readMarketAttributeDatas(record) {
+ return isRecord(record.attribute_datas) ? record.attribute_datas : {};
+ }
+ function readMarketFieldValue(record, attributeDatas, field) {
+ return record[field] ?? attributeDatas[field];
+ }
+ function readString(value) {
+ return typeof value === "string" ? value : null;
+ }
+ function normalizeMarketListRate(value) {
+ if (typeof value === "number") {
+ return normalizeFractionRateDisplay(String(value));
+ }
+ return typeof value === "string" ? normalizeFractionRateDisplay(value) : null;
+ }
+ function normalizeExportCellText(value) {
+ return value?.replace(/\s+/g, " ").trim() ?? "";
+ }
+ function buildMarketExportFieldFallbacks(record, attributeDatas) {
+ const exportFields = {};
+ const authorInfo = buildMarketAuthorInfo(record, attributeDatas);
+ const authorType = buildMarketAuthorType(record, attributeDatas);
+ const contentTheme = buildMarketContentTheme(record, attributeDatas);
+ const connectedUsers = formatWanValue(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "link_link_cnt_by_industry"))
+ );
+ const followerCount = formatWanValue(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "follower"))
+ );
+ const expectedCpm = formatDecimalDisplay(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "prospective_20_60_cpm"))
+ );
+ const expectedPlayCount = formatWanValue(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "expected_play_num"))
+ );
+ const interactionRate = formatFractionPercent(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "interact_rate_within_30d"))
+ );
+ const finishRate = formatFractionPercent(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "play_over_rate_within_30d"))
+ );
+ const burstRate = readBurstRateDisplay(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "burst_text_rate"))
+ );
+ const price21To60s = readMarketPrice21To60s(record, attributeDatas);
+ const representativeVideo = readMarketRepresentativeVideo(record, attributeDatas);
+ assignExportField(exportFields, "\u8FBE\u4EBA\u4FE1\u606F", authorInfo);
+ assignExportField(exportFields, "\u4EE3\u8868\u89C6\u9891", representativeVideo);
+ assignExportField(exportFields, "\u8FBE\u4EBA\u7C7B\u578B", authorType);
+ assignExportField(exportFields, "\u5185\u5BB9\u4E3B\u9898", contentTheme);
+ assignExportField(exportFields, "\u8FDE\u63A5\u7528\u6237\u6570", connectedUsers);
+ assignExportField(exportFields, "\u7C89\u4E1D\u6570", followerCount);
+ assignExportField(exportFields, "\u9884\u671FCPM", expectedCpm);
+ assignExportField(exportFields, "\u9884\u671F\u64AD\u653E\u91CF", expectedPlayCount);
+ assignExportField(exportFields, "\u4E92\u52A8\u7387", interactionRate);
+ assignExportField(exportFields, "\u5B8C\u64AD\u7387", finishRate);
+ assignExportField(exportFields, "\u7206\u6587\u7387", burstRate);
+ assignExportField(exportFields, "21-60s\u62A5\u4EF7", price21To60s);
+ return Object.keys(exportFields).length > 0 ? exportFields : void 0;
+ }
+ function assignExportField(exportFields, key, value) {
+ if (hasTextValue(value)) {
+ exportFields[key] = value;
+ }
+ }
+ function hasTextValue(value) {
+ return Boolean(value && value.trim().length > 0);
+ }
+ function buildMarketAuthorInfo(record, attributeDatas) {
+ const nickname = readString(readMarketFieldValue(record, attributeDatas, "nickname")) ?? readString(readMarketFieldValue(record, attributeDatas, "nick_name")) ?? "";
+ const parts = [
+ nickname,
+ readMarketGenderLabel(readMarketFieldValue(record, attributeDatas, "gender")),
+ readString(readMarketFieldValue(record, attributeDatas, "city")) ?? ""
+ ].filter((value) => Boolean(value));
+ return parts.length > 0 ? parts.join(" ") : void 0;
+ }
+ function buildMarketAuthorType(record, attributeDatas) {
+ const tagsRelation = readRecordLike(
+ readMarketFieldValue(record, attributeDatas, "tags_relation")
+ );
+ if (tagsRelation) {
+ const primaryTag = Object.keys(tagsRelation)[0];
+ if (hasTextValue(primaryTag)) {
+ return primaryTag;
+ }
+ }
+ return void 0;
+ }
+ function buildMarketContentTheme(record, attributeDatas) {
+ const themes = readStringArray(
+ readMarketFieldValue(record, attributeDatas, "content_theme_labels_180d")
+ );
+ if (themes.length === 0) {
+ return void 0;
+ }
+ if (themes.length <= 2) {
+ return themes.join(" ");
+ }
+ return `${themes.slice(0, 2).join(" ")} ${themes.length - 2}+`;
+ }
+ function readMarketLocation(record, attributeDatas) {
+ return readString(readMarketFieldValue(record, attributeDatas, "city")) ?? void 0;
+ }
+ function readMarketPrice21To60s(record, attributeDatas) {
+ return formatCurrencyValue(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "price_20_60"))
+ );
+ }
+ function readMarketRepresentativeVideo(record, attributeDatas) {
+ const items = readArrayLike(readMarketFieldValue(record, attributeDatas, "items"));
+ for (const item of items) {
+ if (!isRecord(item)) {
+ continue;
+ }
+ const title = readString(item.title);
+ if (hasTextValue(title)) {
+ return normalizeExportCellText(title);
+ }
+ }
+ return void 0;
+ }
+ function readMarketGenderLabel(value) {
+ const rawValue = typeof value === "number" ? String(value) : readString(value);
+ if (rawValue === "1") {
+ return "\u7537";
+ }
+ if (rawValue === "2") {
+ return "\u5973";
+ }
+ return void 0;
+ }
+ function readBurstRateDisplay(value) {
+ if (value === null) {
+ return void 0;
+ }
+ if (value <= 0) {
+ return "-";
+ }
+ return formatFractionPercent(value);
+ }
+ function formatCurrencyValue(value) {
+ if (value === null) {
+ return void 0;
+ }
+ return `\xA5${value.toLocaleString("en-US", {
+ maximumFractionDigits: 0
+ })}`;
+ }
+ function formatWanValue(value) {
+ if (value === null) {
+ return void 0;
+ }
+ return `${formatDecimalWithGrouping(value / 1e4)}w`;
+ }
+ function formatFractionPercent(value) {
+ if (value === null) {
+ return void 0;
+ }
+ return `${formatDecimalDisplay(value * 100)}%`;
+ }
+ function formatDecimalDisplay(value) {
+ if (value === null) {
+ return void 0;
+ }
+ return value.toLocaleString("en-US", {
+ maximumFractionDigits: 1,
+ minimumFractionDigits: 0,
+ useGrouping: false
+ });
+ }
+ function formatDecimalWithGrouping(value) {
+ return value.toLocaleString("en-US", {
+ maximumFractionDigits: 1,
+ minimumFractionDigits: 0
+ });
+ }
+ function readNumericValue(value) {
+ if (typeof value === "number" && Number.isFinite(value)) {
+ return value;
+ }
+ if (typeof value === "string") {
+ const trimmedValue = value.trim();
+ if (!trimmedValue) {
+ return null;
+ }
+ const parsedValue = Number(trimmedValue);
+ return Number.isFinite(parsedValue) ? parsedValue : null;
+ }
+ return null;
+ }
+ function readStringArray(value) {
+ if (Array.isArray(value)) {
+ return value.filter((item) => typeof item === "string");
+ }
+ if (typeof value === "string") {
+ try {
+ const parsedValue = JSON.parse(value);
+ return Array.isArray(parsedValue) ? parsedValue.filter((item) => typeof item === "string") : [];
+ } catch {
+ return [];
+ }
+ }
+ return [];
+ }
+ function readArrayLike(value) {
+ if (Array.isArray(value)) {
+ return value;
+ }
+ if (typeof value === "string") {
+ try {
+ const parsedValue = JSON.parse(value);
+ return Array.isArray(parsedValue) ? parsedValue : [];
+ } catch {
+ return [];
+ }
+ }
+ return [];
+ }
+ function readRecordLike(value) {
+ if (isRecord(value)) {
+ return value;
+ }
+ if (typeof value === "string") {
+ try {
+ const parsedValue = JSON.parse(value);
+ return isRecord(parsedValue) ? parsedValue : null;
+ } catch {
+ return null;
+ }
+ }
+ return null;
+ }
+ function readKnownNumber(record, keys) {
+ for (const key of keys) {
+ const value = readNumericValue(record[key]);
+ if (value !== null) {
+ return value;
+ }
+ }
+ return void 0;
+ }
+ function readKnownNumberDeep(value, keys) {
+ if (!isRecord(value)) {
+ return null;
+ }
+ const directValue = readKnownNumber(value, keys);
+ if (typeof directValue === "number") {
+ return directValue;
+ }
+ for (const nestedValue of Object.values(value)) {
+ const candidate = readKnownNumberDeep(unwrapVueRef(nestedValue), keys);
+ if (typeof candidate === "number") {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
+ // src/content/market/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;
+ if (existingCell) {
+ existingCell.dataset.marketRowCell = field;
+ applyPluginContentCellStyles(existingCell);
+ continue;
+ }
+ const templateCell = templateCells[index] ?? templateCells[templateCells.length - 1] ?? null;
+ 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);
+ nextCell.textContent = "";
+ column.appendChild(nextCell);
+ }
+ }
+ 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) => isRecord2(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 (!isRecord2(current)) {
+ continue;
+ }
+ if (isRecord2(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 = isRecord2(row) ? row : {};
+ const singleVideoAfterSearchRate = readString2(
+ record.singleVideoAfterSearchRate
+ );
+ return {
+ authorId: readString2(record.authorId) ?? "",
+ authorName: readString2(record.authorName) ?? "",
+ exportFields: readSerializedExportFields(record),
+ hasDirectRatesSource: Boolean(singleVideoAfterSearchRate),
+ location: readString2(record.location) ?? void 0,
+ price21To60s: readString2(record.price21To60s) ?? void 0,
+ rates: singleVideoAfterSearchRate ? {
+ singleVideoAfterSearchRate
+ } : void 0
+ };
+ }).filter((row) => Boolean(row.authorId || row.authorName));
+ } catch {
+ return [];
+ }
+ }
+ function unwrapVueRef2(value) {
+ if (isRecord2(value) && "value" in value) {
+ return value.value;
+ }
+ return value;
+ }
+ function isRecord2(value) {
+ return typeof value === "object" && value !== null;
+ }
+ function readString2(value) {
+ return typeof value === "string" ? value : null;
+ }
+ function 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,
+ 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 (!isRecord2(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 ?? resolveBaseUrl();
+ const fetchImpl = options.fetchImpl ?? defaultFetch;
+ 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 = getPayloadData(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 getPayloadData(payload) {
+ if (!isRecord3(payload)) {
+ return null;
+ }
+ return isRecord3(payload.data) ? payload.data : payload;
+ }
+ function readNormalizedRate(value) {
+ return typeof value === "string" ? normalizeRateDisplay(value) : 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 isAbortError(error) {
+ return error instanceof Error && error.name === "AbortError";
+ }
+ function isRecord3(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) ?? "",
+ 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) {
+ ensureToolbarMounted(existingRoot, document2);
+ return readToolbarDom(existingRoot);
+ }
+ 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 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,
+ batchSubmitButton,
+ exportStatusText
+ );
+ document2.body.appendChild(root);
+ applyNativeControlStyles(document2, {
+ batchSubmitButton,
+ exportButton,
+ exportCustomPagesInput,
+ exportRangeSelect
+ });
+ ensureToolbarMounted(root, document2);
+ exportButton.addEventListener("click", () => {
+ void handlers.onExport();
+ });
+ batchSubmitButton.addEventListener("click", () => {
+ void handlers.onSubmitBatch();
+ });
+ exportRangeSelect.addEventListener("change", () => {
+ syncCustomPagesInputVisibility({
+ batchSubmitButton,
+ exportButton,
+ exportCustomPagesInput,
+ exportRangeSelect,
+ exportStatusText,
+ root
+ });
+ });
+ const toolbarDom = {
+ 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 = {
+ 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.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.batchSubmitButton.className = nativeButton.className;
+ }
+ [controls.exportButton, controls.batchSubmitButton].forEach((button) => {
+ applyPrimaryButtonStyles2(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 applyPrimaryButtonStyles2(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-batch-submit="button"]:hover:not(:disabled) {
+ background-color: #6d1627 !important;
+ border-color: #6d1627 !important;
+ }
+
+ [data-plugin-export="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-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-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 (!isRecord4(current)) {
+ continue;
+ }
+ const reqParams = unwrapVueRef3(current.reqParams);
+ if (isRecord4(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 (isRecord4(value) && "value" in value) {
+ return value.value;
+ }
+ return value;
+ }
+ function isRecord4(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 ?? defaultFetch2;
+ 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) && isRecord5(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 isRecord5(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 (!isRecord5(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 defaultFetch2(input, init) {
+ return fetch(input, init);
+ }
+ function isRecord5(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) ?? "",
+ 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.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 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 getAuthState = options.getAuthState ?? (() => readAuthState(sendRuntimeMessage));
+ const mutationObserverFactory = options.mutationObserverFactory ?? ((callback) => new MutationObserver(callback));
+ const promptBatchName = options.promptBatchName ?? (() => promptForBatchName(options.document));
+ const submitBatch = options.submitBatch ?? ((payload) => readBatchSubmitAck(sendRuntimeMessage, payload));
+ 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);
+ }
+ },
+ 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;
+ }
+ 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 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",
+ 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
+ );
+ }
+
+ // src/content/market/auth-gate.ts
+ function renderMarketAuthGate(document2, currentWindow) {
+ const existingGate = document2.querySelector(
+ '[data-market-auth-gate="root"]'
+ );
+ if (existingGate) {
+ return existingGate;
+ }
+ const root = document2.createElement("section");
+ root.dataset.marketAuthGate = "root";
+ root.innerHTML = `
+ \u8BF7\u5148\u767B\u5F55\u63D2\u4EF6
+ \u6253\u5F00\u6269\u5C55\u5F39\u7A97\u5B8C\u6210\u767B\u5F55\u540E\u5237\u65B0\u672C\u9875
+
+ `;
+ 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);
+ return {
+ ready: Promise.resolve()
+ };
+ }
+ await waitForBodyReady(currentDocument, currentWindow);
+ return controllerFactory({
+ document: currentDocument,
+ onCsvReady: (csv) => {
+ if (requestCsvDownload(csv)) {
+ return;
+ }
+ downloadCsv(currentDocument, currentWindow, csv);
+ },
+ 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) {
+ const runtime = globalThis.chrome?.runtime;
+ if (!runtime?.id || typeof runtime.sendMessage !== "function") {
+ return false;
+ }
+ runtime.sendMessage({
+ csv,
+ 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) {
+ 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 = `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 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);
+ }
+})();
diff --git a/dist-release/content/market-page-bridge.js b/dist-release/content/market-page-bridge.js
new file mode 100644
index 0000000..f09d29c
--- /dev/null
+++ b/dist-release/content/market-page-bridge.js
@@ -0,0 +1,583 @@
+"use strict";
+(() => {
+ // src/shared/rate-normalizer.ts
+ function normalizeFractionRateDisplay(value) {
+ const numericValue = Number(value);
+ if (!Number.isFinite(numericValue)) {
+ return null;
+ }
+ const percentageValue = numericValue * 100;
+ return `${trimTrailingZeros(percentageValue.toFixed(6))}%`;
+ }
+ function trimTrailingZeros(value) {
+ return value.replace(/\.?0+$/, "");
+ }
+
+ // src/content/market/market-list-request-snapshot.ts
+ var MARKET_REQUEST_SNAPSHOT_ATTRIBUTE = "data-sces-market-request-snapshot";
+ function writeMarketListRequestSnapshot(document2, snapshot) {
+ document2.documentElement.setAttribute(
+ MARKET_REQUEST_SNAPSHOT_ATTRIBUTE,
+ JSON.stringify(snapshot)
+ );
+ }
+
+ // src/content/market/market-list-row.ts
+ var PAGE_NUMBER_KEYS = [
+ "currentPage",
+ "page",
+ "pageNo",
+ "pageNum",
+ "page_no",
+ "page_num"
+ ];
+ var PAGE_SIZE_KEYS = [
+ "limit",
+ "pageSize",
+ "page_size",
+ "size"
+ ];
+ var TOTAL_COUNT_KEYS = [
+ "total",
+ "totalCount",
+ "total_count"
+ ];
+ var TOTAL_PAGE_KEYS = [
+ "pageCount",
+ "page_count",
+ "totalPage",
+ "totalPages",
+ "total_page",
+ "total_pages"
+ ];
+ function mapMarketListRow(row) {
+ const attributeDatas = readMarketAttributeDatas(row);
+ const singleVideoAfterSearchRate = normalizeMarketListRate(
+ readMarketFieldValue(row, attributeDatas, "avg_search_after_view_rate_30d")
+ );
+ return {
+ authorId: readString(readMarketFieldValue(row, attributeDatas, "star_id")) ?? readString(readMarketFieldValue(row, attributeDatas, "id")) ?? "",
+ authorName: readString(readMarketFieldValue(row, attributeDatas, "nickname")) ?? readString(readMarketFieldValue(row, attributeDatas, "nick_name")) ?? "",
+ exportFields: buildMarketExportFieldFallbacks(row, attributeDatas),
+ hasDirectRatesSource: true,
+ location: readMarketLocation(row, attributeDatas),
+ price21To60s: readMarketPrice21To60s(row, attributeDatas),
+ rates: singleVideoAfterSearchRate ? {
+ singleVideoAfterSearchRate
+ } : void 0
+ };
+ }
+ function parseMarketListResponse(payload) {
+ const container = findMarketListContainer(payload);
+ if (!container) {
+ return null;
+ }
+ const marketList = readMarketListArray(container);
+ if (!marketList) {
+ return null;
+ }
+ return {
+ currentPage: readKnownNumberDeep(container, PAGE_NUMBER_KEYS) ?? void 0,
+ pageSize: readKnownNumberDeep(container, PAGE_SIZE_KEYS) ?? void 0,
+ records: marketList.map((row) => isRecord(row) ? mapMarketListRow(row) : null).filter(
+ (row) => row !== null && Boolean(row.authorId || row.authorName)
+ ),
+ totalCount: readKnownNumberDeep(container, TOTAL_COUNT_KEYS) ?? void 0,
+ totalPages: readKnownNumberDeep(container, TOTAL_PAGE_KEYS) ?? void 0
+ };
+ }
+ function findMarketListContainer(value) {
+ const queue = [value];
+ while (queue.length > 0) {
+ const current = queue.shift();
+ if (!isRecord(current)) {
+ continue;
+ }
+ if (readMarketListArray(current)) {
+ return current;
+ }
+ Object.values(current).forEach((entry) => {
+ queue.push(unwrapVueRef(entry));
+ });
+ }
+ return null;
+ }
+ function readMarketListArray(record) {
+ const marketList = unwrapVueRef(record.marketList);
+ if (Array.isArray(marketList)) {
+ return marketList;
+ }
+ const authors = unwrapVueRef(record.authors);
+ if (Array.isArray(authors)) {
+ return authors;
+ }
+ return null;
+ }
+ function unwrapVueRef(value) {
+ if (isRecord(value) && "value" in value) {
+ return value.value;
+ }
+ return value;
+ }
+ function isRecord(value) {
+ return typeof value === "object" && value !== null;
+ }
+ function readMarketAttributeDatas(record) {
+ return isRecord(record.attribute_datas) ? record.attribute_datas : {};
+ }
+ function readMarketFieldValue(record, attributeDatas, field) {
+ return record[field] ?? attributeDatas[field];
+ }
+ function readString(value) {
+ return typeof value === "string" ? value : null;
+ }
+ function normalizeMarketListRate(value) {
+ if (typeof value === "number") {
+ return normalizeFractionRateDisplay(String(value));
+ }
+ return typeof value === "string" ? normalizeFractionRateDisplay(value) : null;
+ }
+ function normalizeExportCellText(value) {
+ return value?.replace(/\s+/g, " ").trim() ?? "";
+ }
+ function buildMarketExportFieldFallbacks(record, attributeDatas) {
+ const exportFields = {};
+ const authorInfo = buildMarketAuthorInfo(record, attributeDatas);
+ const authorType = buildMarketAuthorType(record, attributeDatas);
+ const contentTheme = buildMarketContentTheme(record, attributeDatas);
+ const connectedUsers = formatWanValue(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "link_link_cnt_by_industry"))
+ );
+ const followerCount = formatWanValue(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "follower"))
+ );
+ const expectedCpm = formatDecimalDisplay(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "prospective_20_60_cpm"))
+ );
+ const expectedPlayCount = formatWanValue(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "expected_play_num"))
+ );
+ const interactionRate = formatFractionPercent(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "interact_rate_within_30d"))
+ );
+ const finishRate = formatFractionPercent(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "play_over_rate_within_30d"))
+ );
+ const burstRate = readBurstRateDisplay(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "burst_text_rate"))
+ );
+ const price21To60s = readMarketPrice21To60s(record, attributeDatas);
+ const representativeVideo = readMarketRepresentativeVideo(record, attributeDatas);
+ assignExportField(exportFields, "\u8FBE\u4EBA\u4FE1\u606F", authorInfo);
+ assignExportField(exportFields, "\u4EE3\u8868\u89C6\u9891", representativeVideo);
+ assignExportField(exportFields, "\u8FBE\u4EBA\u7C7B\u578B", authorType);
+ assignExportField(exportFields, "\u5185\u5BB9\u4E3B\u9898", contentTheme);
+ assignExportField(exportFields, "\u8FDE\u63A5\u7528\u6237\u6570", connectedUsers);
+ assignExportField(exportFields, "\u7C89\u4E1D\u6570", followerCount);
+ assignExportField(exportFields, "\u9884\u671FCPM", expectedCpm);
+ assignExportField(exportFields, "\u9884\u671F\u64AD\u653E\u91CF", expectedPlayCount);
+ assignExportField(exportFields, "\u4E92\u52A8\u7387", interactionRate);
+ assignExportField(exportFields, "\u5B8C\u64AD\u7387", finishRate);
+ assignExportField(exportFields, "\u7206\u6587\u7387", burstRate);
+ assignExportField(exportFields, "21-60s\u62A5\u4EF7", price21To60s);
+ return Object.keys(exportFields).length > 0 ? exportFields : void 0;
+ }
+ function assignExportField(exportFields, key, value) {
+ if (hasTextValue(value)) {
+ exportFields[key] = value;
+ }
+ }
+ function hasTextValue(value) {
+ return Boolean(value && value.trim().length > 0);
+ }
+ function buildMarketAuthorInfo(record, attributeDatas) {
+ const nickname = readString(readMarketFieldValue(record, attributeDatas, "nickname")) ?? readString(readMarketFieldValue(record, attributeDatas, "nick_name")) ?? "";
+ const parts = [
+ nickname,
+ readMarketGenderLabel(readMarketFieldValue(record, attributeDatas, "gender")),
+ readString(readMarketFieldValue(record, attributeDatas, "city")) ?? ""
+ ].filter((value) => Boolean(value));
+ return parts.length > 0 ? parts.join(" ") : void 0;
+ }
+ function buildMarketAuthorType(record, attributeDatas) {
+ const tagsRelation = readRecordLike(
+ readMarketFieldValue(record, attributeDatas, "tags_relation")
+ );
+ if (tagsRelation) {
+ const primaryTag = Object.keys(tagsRelation)[0];
+ if (hasTextValue(primaryTag)) {
+ return primaryTag;
+ }
+ }
+ return void 0;
+ }
+ function buildMarketContentTheme(record, attributeDatas) {
+ const themes = readStringArray(
+ readMarketFieldValue(record, attributeDatas, "content_theme_labels_180d")
+ );
+ if (themes.length === 0) {
+ return void 0;
+ }
+ if (themes.length <= 2) {
+ return themes.join(" ");
+ }
+ return `${themes.slice(0, 2).join(" ")} ${themes.length - 2}+`;
+ }
+ function readMarketLocation(record, attributeDatas) {
+ return readString(readMarketFieldValue(record, attributeDatas, "city")) ?? void 0;
+ }
+ function readMarketPrice21To60s(record, attributeDatas) {
+ return formatCurrencyValue(
+ readNumericValue(readMarketFieldValue(record, attributeDatas, "price_20_60"))
+ );
+ }
+ function readMarketRepresentativeVideo(record, attributeDatas) {
+ const items = readArrayLike(readMarketFieldValue(record, attributeDatas, "items"));
+ for (const item of items) {
+ if (!isRecord(item)) {
+ continue;
+ }
+ const title = readString(item.title);
+ if (hasTextValue(title)) {
+ return normalizeExportCellText(title);
+ }
+ }
+ return void 0;
+ }
+ function readMarketGenderLabel(value) {
+ const rawValue = typeof value === "number" ? String(value) : readString(value);
+ if (rawValue === "1") {
+ return "\u7537";
+ }
+ if (rawValue === "2") {
+ return "\u5973";
+ }
+ return void 0;
+ }
+ function readBurstRateDisplay(value) {
+ if (value === null) {
+ return void 0;
+ }
+ if (value <= 0) {
+ return "-";
+ }
+ return formatFractionPercent(value);
+ }
+ function formatCurrencyValue(value) {
+ if (value === null) {
+ return void 0;
+ }
+ return `\xA5${value.toLocaleString("en-US", {
+ maximumFractionDigits: 0
+ })}`;
+ }
+ function formatWanValue(value) {
+ if (value === null) {
+ return void 0;
+ }
+ return `${formatDecimalWithGrouping(value / 1e4)}w`;
+ }
+ function formatFractionPercent(value) {
+ if (value === null) {
+ return void 0;
+ }
+ return `${formatDecimalDisplay(value * 100)}%`;
+ }
+ function formatDecimalDisplay(value) {
+ if (value === null) {
+ return void 0;
+ }
+ return value.toLocaleString("en-US", {
+ maximumFractionDigits: 1,
+ minimumFractionDigits: 0,
+ useGrouping: false
+ });
+ }
+ function formatDecimalWithGrouping(value) {
+ return value.toLocaleString("en-US", {
+ maximumFractionDigits: 1,
+ minimumFractionDigits: 0
+ });
+ }
+ function readNumericValue(value) {
+ if (typeof value === "number" && Number.isFinite(value)) {
+ return value;
+ }
+ if (typeof value === "string") {
+ const trimmedValue = value.trim();
+ if (!trimmedValue) {
+ return null;
+ }
+ const parsedValue = Number(trimmedValue);
+ return Number.isFinite(parsedValue) ? parsedValue : null;
+ }
+ return null;
+ }
+ function readStringArray(value) {
+ if (Array.isArray(value)) {
+ return value.filter((item) => typeof item === "string");
+ }
+ if (typeof value === "string") {
+ try {
+ const parsedValue = JSON.parse(value);
+ return Array.isArray(parsedValue) ? parsedValue.filter((item) => typeof item === "string") : [];
+ } catch {
+ return [];
+ }
+ }
+ return [];
+ }
+ function readArrayLike(value) {
+ if (Array.isArray(value)) {
+ return value;
+ }
+ if (typeof value === "string") {
+ try {
+ const parsedValue = JSON.parse(value);
+ return Array.isArray(parsedValue) ? parsedValue : [];
+ } catch {
+ return [];
+ }
+ }
+ return [];
+ }
+ function readRecordLike(value) {
+ if (isRecord(value)) {
+ return value;
+ }
+ if (typeof value === "string") {
+ try {
+ const parsedValue = JSON.parse(value);
+ return isRecord(parsedValue) ? parsedValue : null;
+ } catch {
+ return null;
+ }
+ }
+ return null;
+ }
+ function readKnownNumber(record, keys) {
+ for (const key of keys) {
+ const value = readNumericValue(record[key]);
+ if (value !== null) {
+ return value;
+ }
+ }
+ return void 0;
+ }
+ function readKnownNumberDeep(value, keys) {
+ if (!isRecord(value)) {
+ return null;
+ }
+ const directValue = readKnownNumber(value, keys);
+ if (typeof directValue === "number") {
+ return directValue;
+ }
+ for (const nestedValue of Object.values(value)) {
+ const candidate = readKnownNumberDeep(unwrapVueRef(nestedValue), keys);
+ if (typeof candidate === "number") {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
+ // src/content/market/page-bridge.ts
+ var BRIDGE_MARKER = "__SCES_MARKET_PAGE_BRIDGE_INSTALLED__";
+ var MARKET_SEARCH_REQUEST_PATH = "/gw/api/gsearch/search_for_author_square";
+ var SERIALIZED_MARKET_ROWS_ATTRIBUTE = "data-sces-market-rows";
+ installMarketPageBridge();
+ function installMarketPageBridge() {
+ if (window[BRIDGE_MARKER]) {
+ syncSerializedMarketRows();
+ return;
+ }
+ window[BRIDGE_MARKER] = true;
+ installMarketRequestSnapshotBridge();
+ syncSerializedMarketRows();
+ const observer = new MutationObserver(() => {
+ syncSerializedMarketRows();
+ });
+ observer.observe(document.documentElement, {
+ childList: true,
+ subtree: true
+ });
+ window.setInterval(() => {
+ syncSerializedMarketRows();
+ }, 1e3);
+ }
+ function installMarketRequestSnapshotBridge() {
+ installFetchSnapshotBridge();
+ installXmlHttpRequestSnapshotBridge();
+ }
+ function syncSerializedMarketRows() {
+ if (typeof document === "undefined") {
+ return;
+ }
+ const nextSerializedRows = JSON.stringify(readSerializedMarketRows());
+ if (document.documentElement.getAttribute(SERIALIZED_MARKET_ROWS_ATTRIBUTE) !== nextSerializedRows) {
+ document.documentElement.setAttribute(
+ SERIALIZED_MARKET_ROWS_ATTRIBUTE,
+ nextSerializedRows
+ );
+ }
+ }
+ function installFetchSnapshotBridge() {
+ if (typeof window.fetch !== "function") {
+ return;
+ }
+ const originalFetch = window.fetch.bind(window);
+ window.fetch = async (input, init) => {
+ const requestSnapshot = readFetchSnapshot(input, init);
+ const response = await originalFetch(input, init);
+ if (requestSnapshot) {
+ const clonedResponse = response.clone();
+ void captureMarketSnapshotFromResponse(
+ requestSnapshot,
+ () => clonedResponse.json()
+ );
+ }
+ return response;
+ };
+ }
+ function installXmlHttpRequestSnapshotBridge() {
+ const OriginalXmlHttpRequest = window.XMLHttpRequest;
+ if (!OriginalXmlHttpRequest) {
+ return;
+ }
+ const originalOpen = OriginalXmlHttpRequest.prototype.open;
+ const originalSend = OriginalXmlHttpRequest.prototype.send;
+ const originalSetRequestHeader = OriginalXmlHttpRequest.prototype.setRequestHeader;
+ OriginalXmlHttpRequest.prototype.open = function(method, url, ...rest) {
+ this.__scesMarketSnapshot = {
+ headers: {},
+ method,
+ url: String(url)
+ };
+ return originalOpen.call(this, method, url, ...rest);
+ };
+ OriginalXmlHttpRequest.prototype.setRequestHeader = function(name, value) {
+ this.__scesMarketSnapshot?.headers && (this.__scesMarketSnapshot.headers[name] = value);
+ return originalSetRequestHeader.call(this, name, value);
+ };
+ OriginalXmlHttpRequest.prototype.send = function(body) {
+ const snapshotState = this.__scesMarketSnapshot;
+ if (snapshotState) {
+ snapshotState.body = typeof body === "string" ? body : void 0;
+ this.addEventListener("load", () => {
+ if (this.status < 200 || this.status >= 300 || typeof this.responseText !== "string") {
+ return;
+ }
+ void captureMarketSnapshotFromResponse(
+ snapshotState,
+ async () => JSON.parse(this.responseText)
+ );
+ });
+ }
+ return originalSend.call(this, body);
+ };
+ }
+ async function captureMarketSnapshotFromResponse(snapshot, readPayload) {
+ if (!isMarketSearchRequest(snapshot.url)) {
+ return;
+ }
+ try {
+ const payload = await readPayload();
+ if (!parseMarketListResponse(payload)) {
+ return;
+ }
+ writeMarketListRequestSnapshot(document, {
+ body: snapshot.body,
+ headers: snapshot.headers,
+ method: snapshot.method,
+ url: snapshot.url
+ });
+ } catch {
+ }
+ }
+ function readSerializedMarketRows() {
+ const marketList = readMarketList();
+ return marketList.map((row) => {
+ const attributeDatas = isRecord2(row.attribute_datas) ? row.attribute_datas : {};
+ const singleVideoAfterSearchRate = readNormalizedFractionRate(
+ attributeDatas.avg_search_after_view_rate_30d
+ );
+ return {
+ authorId: readString2(row.star_id) ?? readString2(attributeDatas.id) ?? "",
+ authorName: readString2(attributeDatas.nickname) ?? readString2(row.nick_name) ?? "",
+ singleVideoAfterSearchRate
+ };
+ }).filter((row) => Boolean(row.authorId || row.authorName));
+ }
+ function readFetchSnapshot(input, init) {
+ const request = input instanceof Request ? input : null;
+ const method = init?.method ?? request?.method ?? "GET";
+ const url = request?.url ?? String(input);
+ const body = typeof init?.body === "string" ? init.body : typeof request?.bodyUsed === "boolean" && request.bodyUsed ? void 0 : void 0;
+ const headers = serializeHeaders(init?.headers ?? request?.headers);
+ return {
+ body,
+ headers,
+ method,
+ url
+ };
+ }
+ function serializeHeaders(headers) {
+ if (!headers) {
+ return void 0;
+ }
+ if (headers instanceof Headers) {
+ return Object.fromEntries(headers.entries());
+ }
+ if (Array.isArray(headers)) {
+ return Object.fromEntries(headers);
+ }
+ return Object.fromEntries(
+ Object.entries(headers).map(([key, value]) => [key, String(value)])
+ );
+ }
+ function readMarketList() {
+ if (typeof document === "undefined") {
+ return [];
+ }
+ const marketRoot = document.querySelector(".base-author-list");
+ const setupState = marketRoot?.__vue__?._setupState;
+ if (!setupState) {
+ return [];
+ }
+ for (const value of Object.values(setupState)) {
+ const candidate = unwrapVueRef2(value);
+ if (Array.isArray(candidate) && looksLikeMarketList(candidate)) {
+ return candidate;
+ }
+ if (!isRecord2(candidate) || !Array.isArray(candidate.marketList)) {
+ continue;
+ }
+ if (looksLikeMarketList(candidate.marketList)) {
+ return candidate.marketList;
+ }
+ }
+ return [];
+ }
+ function isMarketSearchRequest(url) {
+ return url === MARKET_SEARCH_REQUEST_PATH || url.startsWith(`${MARKET_SEARCH_REQUEST_PATH}?`) || url.includes(`${MARKET_SEARCH_REQUEST_PATH}?`) || url.endsWith(MARKET_SEARCH_REQUEST_PATH);
+ }
+ function looksLikeMarketList(value) {
+ const firstRow = value[0];
+ return isRecord2(firstRow) && ("star_id" in firstRow || "attribute_datas" in firstRow);
+ }
+ function unwrapVueRef2(value) {
+ if (isRecord2(value) && "value" in value) {
+ return value.value;
+ }
+ return value;
+ }
+ function isRecord2(value) {
+ return typeof value === "object" && value !== null;
+ }
+ function readString2(value) {
+ return typeof value === "string" ? value : null;
+ }
+ function readNormalizedFractionRate(value) {
+ return typeof value === "string" ? normalizeFractionRateDisplay(value) ?? void 0 : void 0;
+ }
+})();
diff --git a/dist-release/manifest.json b/dist-release/manifest.json
new file mode 100644
index 0000000..9df1c42
--- /dev/null
+++ b/dist-release/manifest.json
@@ -0,0 +1,58 @@
+{
+ "action": {
+ "default_icon": {
+ "16": "assets/icons/icon-16.png",
+ "32": "assets/icons/icon-32.png"
+ },
+ "default_popup": "popup/index.html"
+ },
+ "background": {
+ "service_worker": "background/index.js"
+ },
+ "content_scripts": [
+ {
+ "js": [
+ "content/index.js"
+ ],
+ "matches": [
+ "https://xingtu.cn/ad/creator/market*",
+ "https://*.xingtu.cn/ad/creator/market*"
+ ],
+ "run_at": "document_start"
+ }
+ ],
+ "description": "Bootstraps the Xingtu creator market content script.",
+ "icons": {
+ "16": "assets/icons/icon-16.png",
+ "32": "assets/icons/icon-32.png",
+ "48": "assets/icons/icon-48.png",
+ "128": "assets/icons/icon-128.png"
+ },
+ "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0CaZJxcX97TbRXCR08L10t9EZFV31+wPnUgDf21j2f0qYaWdblzWXfVkeU9jGb2Hr2Etpp7F/XuBa6pcipUXkzMMBkJ42KOkciAwbuzTBoAtGB8o9aoWigtax+gGfSz+T3BjqxKBJtXqeqbIAKCDIlxRKIrY+KcY1Z+mD5BKcBHKsUDQPlHsrjc1g0wIBD5doz9LoOk1Wso6gK5cSeOp9lw5YHcu4TImR4yqxGiL6pZwnpciuX/g7qjWBZXn5gf0YBlDsBDDTt5upbP3NguUKgO2qA9M77LyeUwXl3aqbIxYi/VwsQ2t5w9PGWtnOUQQDWUcEg/9dfTb89esZXKATwIDAQAB",
+ "manifest_version": 3,
+ "name": "Star Chart Search Enhancer",
+ "permissions": [
+ "downloads",
+ "identity",
+ "storage"
+ ],
+ "version": "0.2.0421.2",
+ "web_accessible_resources": [
+ {
+ "matches": [
+ "https://xingtu.cn/*",
+ "https://*.xingtu.cn/*"
+ ],
+ "resources": [
+ "content/market-page-bridge.js"
+ ]
+ }
+ ],
+ "host_permissions": [
+ "https://xingtu.cn/ad/creator/market*",
+ "https://*.xingtu.cn/ad/creator/market*",
+ "https://login-api.intelligrow.cn/*",
+ "https://talent-search.intelligrow.cn/*",
+ "http://192.168.31.21:8083/*"
+ ]
+}
diff --git a/dist-release/popup/index.html b/dist-release/popup/index.html
new file mode 100644
index 0000000..6e17f31
--- /dev/null
+++ b/dist-release/popup/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Star Chart Search Enhancer
+
+
+
+
+
+
diff --git a/dist-release/popup/index.js b/dist-release/popup/index.js
new file mode 100644
index 0000000..2003be9
--- /dev/null
+++ b/dist-release/popup/index.js
@@ -0,0 +1,219 @@
+"use strict";
+(() => {
+ // src/popup/view.ts
+ function renderLoggedOut(root, error) {
+ root.innerHTML = `
+
+ Star Chart Search Enhancer
+ \u767B\u5F55\u540E\u624D\u80FD\u4F7F\u7528\u661F\u56FE\u589E\u5F3A\u529F\u80FD
+ ${error ? `${error}
` : ""}
+
+
+ `;
+ }
+ function renderLoggedIn(root, authState) {
+ const userInfo = authState.userInfo;
+ root.innerHTML = `
+
+ Star Chart Search Enhancer
+ \u5DF2\u767B\u5F55
+ ${userInfo?.name ?? userInfo?.username ?? "\u672A\u77E5\u7528\u6237"}
+ ${userInfo?.email ?? ""}
+
+
+ `;
+ }
+ function renderDevPanel(root, authState) {
+ const panel = root.ownerDocument.createElement("section");
+ panel.dataset.popupDevPanel = "root";
+ panel.innerHTML = `
+ dev auth panel
+ resource: ${authState.resource ?? ""}
+ scopes: ${(authState.scopes ?? []).join(", ")}
+ token: ${authState.tokenAvailable ? "available" : "missing"}
+ expires: ${authState.accessTokenExpiresAt ?? "unknown"}
+ error: ${authState.lastError ?? ""}
+
+
+ `;
+ root.appendChild(panel);
+ }
+ function setProtectedApiResult(root, value) {
+ const output = root.querySelector(
+ '[data-popup-protected-api-result="output"]'
+ );
+ if (!output) {
+ return;
+ }
+ output.textContent = value;
+ }
+
+ // src/shared/auth-config.ts
+ var defaultAuthConfig = {
+ apiResource: "https://talent-search.intelligrow.cn",
+ appId: "i4jkllbvih0554r4n0fd3",
+ enableDevAuthPanel: false,
+ logtoEndpoint: "https://login-api.intelligrow.cn",
+ scopes: ["openid", "profile", "offline_access", "talent-search:read"]
+ };
+ function readAuthConfig(overrides = {}) {
+ const nextConfig = {
+ ...defaultAuthConfig,
+ ...overrides
+ };
+ if (!nextConfig.logtoEndpoint.trim()) {
+ throw new Error("auth config logtoEndpoint is required");
+ }
+ if (!nextConfig.appId.trim()) {
+ throw new Error("auth config appId is required");
+ }
+ if (!nextConfig.apiResource.trim()) {
+ throw new Error("auth config apiResource is required");
+ }
+ return nextConfig;
+ }
+
+ // 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/protected-api-client.ts
+ function createProtectedApiClient(options) {
+ const fetchImpl = options.fetchImpl ?? fetch;
+ return {
+ async loadProtectedMockData() {
+ const token = await readAccessToken(options.sendMessage);
+ const response = await fetchImpl(
+ new URL("/api/mock/protected", options.baseUrl).toString(),
+ {
+ headers: {
+ Authorization: `Bearer ${token}`
+ },
+ method: "GET"
+ }
+ );
+ if (response.status === 401 || response.status === 403) {
+ throw new Error("protected api unauthorized");
+ }
+ if (!response.ok) {
+ throw new Error(`protected api request failed: ${response.status}`);
+ }
+ return response.json();
+ }
+ };
+ }
+ async function readAccessToken(sendMessage) {
+ const response = await sendMessage({ type: "auth:get-access-token" });
+ if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:token" || !response.value.accessToken.trim()) {
+ throw new Error("protected api token unavailable");
+ }
+ return response.value.accessToken;
+ }
+
+ // src/popup/index.ts
+ async function bootPopup(options = {}) {
+ const currentDocument = options.document ?? document;
+ const popupConfig = readAuthConfig(options.config);
+ const root = currentDocument.querySelector("#app");
+ const HTMLElementCtor = currentDocument.defaultView?.HTMLElement;
+ if (!root || HTMLElementCtor && !(root instanceof HTMLElementCtor)) {
+ throw new Error("popup root #app is required");
+ }
+ const sendMessage = options.sendMessage ?? ((message) => Promise.resolve(
+ globalThis.chrome?.runtime?.sendMessage?.(message)
+ ));
+ const fetchProtectedApi = options.fetchProtectedApi ?? createProtectedApiClient({
+ baseUrl: "http://127.0.0.1:4319",
+ sendMessage
+ }).loadProtectedMockData;
+ await renderCurrentAuthState(root, popupConfig, sendMessage, fetchProtectedApi);
+ }
+ async function renderCurrentAuthState(root, popupConfig, sendMessage, fetchProtectedApi) {
+ const response = await sendMessage({ type: "auth:get-state" });
+ if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") {
+ renderLoggedOut(root, "\u8BA4\u8BC1\u72B6\u6001\u8BFB\u53D6\u5931\u8D25");
+ return;
+ }
+ if (!response.value.isAuthenticated) {
+ renderLoggedOut(root, response.value.lastError);
+ root.querySelector('[data-popup-sign-in="button"]')?.addEventListener("click", () => {
+ void runAuthAction(root, popupConfig, sendMessage, {
+ actionMessage: { type: "auth:sign-in" },
+ fetchProtectedApi
+ });
+ });
+ return;
+ }
+ renderLoggedIn(root, response.value);
+ root.querySelector('[data-popup-sign-out="button"]')?.addEventListener("click", () => {
+ void runAuthAction(root, popupConfig, sendMessage, {
+ actionMessage: { type: "auth:sign-out" },
+ fetchProtectedApi
+ });
+ });
+ if (popupConfig.enableDevAuthPanel) {
+ renderDevPanel(root, response.value);
+ root.querySelector('[data-popup-test-protected-api="button"]')?.addEventListener("click", () => {
+ void runProtectedApiProbe(root, fetchProtectedApi);
+ });
+ }
+ }
+ async function runAuthAction(root, popupConfig, sendMessage, options) {
+ const response = await sendMessage(options.actionMessage);
+ if (isActionError(response)) {
+ renderLoggedOut(root, response.error);
+ root.querySelector('[data-popup-sign-in="button"]')?.addEventListener("click", () => {
+ void runAuthAction(root, popupConfig, sendMessage, options);
+ });
+ return;
+ }
+ await renderCurrentAuthState(
+ root,
+ popupConfig,
+ sendMessage,
+ options.fetchProtectedApi
+ );
+ }
+ function isActionError(response) {
+ return isAuthResponseMessage(response) && !response.ok && response.type === "auth:error";
+ }
+ async function runProtectedApiProbe(root, fetchProtectedApi) {
+ setProtectedApiResult(root, "\u8BF7\u6C42\u4E2D...");
+ try {
+ const result = await fetchProtectedApi();
+ setProtectedApiResult(root, JSON.stringify(result, null, 2));
+ } catch (error) {
+ setProtectedApiResult(
+ root,
+ error instanceof Error ? error.message : String(error)
+ );
+ }
+ }
+ if (typeof document !== "undefined") {
+ void bootPopup();
+ }
+})();
diff --git a/docs/【超简单版】插件安装使用指南.md b/docs/【超简单版】插件安装使用指南.md
new file mode 100644
index 0000000..52634a3
--- /dev/null
+++ b/docs/【超简单版】插件安装使用指南.md
@@ -0,0 +1,132 @@
+# 🌟 星图增强插件 - 超简单使用指南
+
+> 适合:完全没用过插件的新手 | 阅读时间:3分钟
+
+---
+
+## 📦 第一步:拿到压缩包
+
+你会从同事那里收到一个文件:
+
+**`star-chart-search-enhancer-internal.zip`**
+
+把它保存在桌面上,不要删掉。
+
+---
+
+## 📂 第二步:解压(右键就行)
+
+1. 在桌面上找到这个压缩包
+2. **右键** → 选择"解压到当前文件夹"(或"Extract Here")
+3. 会多出一个文件夹,名字类似 `star-chart-search-enhancer-internal`
+
+⚠️ **重要**:这个文件夹要一直放在桌面,不要删、不要改名
+
+---
+
+## 🔧 第三步:安装到 Chrome(只需做一次)
+
+1. 打开 **Chrome 浏览器**
+2. 在地址栏输入:
+ ```
+ chrome://extensions
+ ```
+ 然后按回车
+
+3. 右上角找到 **"开发者模式"** → 打开开关(点一下变蓝色)
+
+4. 点击左上角出现的 **"加载已解压的扩展程序"**
+
+5. 选择刚才解压出来的那个文件夹
+
+6. 看到绿色的插件卡片出现,就装好了!
+
+✅ **检查**:点击"详情",确认 ID 是 `pkjopdibdnomhogjheclhnknmejccffg`
+
+---
+
+## 🔑 第四步:登录(只用登录一次)
+
+1. 点击 Chrome 右上角的 **拼图图标** 🧩
+2. 找到 **Star Chart Search Enhancer**
+3. 点击 **图钉** 📌 把它固定到工具栏
+4. 点击插件图标,然后点 **"登录"**
+5. 按提示完成公司账号登录
+
+---
+
+## 🚀 第五步:开始使用
+
+### 打开星图页面
+
+访问:
+```
+https://xingtu.cn/ad/creator/market
+```
+
+等待页面加载,你会看到页面上多了一排新按钮。
+
+---
+
+## 📝 主要功能
+
+### 1️⃣ 导出 Excel 表格
+
+- 勾选你想导出的达人(不勾就选全部)
+- 选择范围:当前页 / 前5页 / 全部
+- 点击 **"导出CSV"**
+- 文件自动下载到电脑的"下载"文件夹
+
+### 2️⃣ 提交批次
+
+- 勾选你想提交的达人
+- 点击 **"提交批次"**
+- 输入批次名称(例如:`5月母婴达人第一批`)
+- 点击确认
+
+---
+
+## 🔄 如何更新插件
+
+收到新版本压缩包时:
+
+1. 删掉桌面上的旧文件夹
+2. 解压新的压缩包
+3. 打开 `chrome://extensions`
+4. 找到插件卡片,点击 **"重新加载"** 🔄
+
+---
+
+## ❓ 常见问题
+
+### Q: 页面没有多出按钮?
+A: 先点击插件图标确认已登录,然后刷新页面(按 F5)
+
+### Q: 提示登录失败?
+A: 关闭弹窗再试一次,或检查网络连接
+
+### Q: 导出没反应?
+A: 检查浏览器的下载列表,文件可能已经下好了
+
+### Q: 不小心把文件夹删了?
+A: 重新解压压缩包,然后到 `chrome://extensions` 点"重新加载"
+
+---
+
+## ✅ 每日使用 checklist
+
+- [ ] 打开 Chrome,确认插件图标在
+- [ ] 点击图标,确认显示"已登录"
+- [ ] 打开星图页面
+- [ ] 正常使用导出/提交功能
+
+---
+
+## 🆘 还是不行?
+
+把下面信息发给同事:
+1. 你在哪一步卡住了
+2. 页面截图
+3. 扩展 ID(从 chrome://extensions 里看)
+
+**记住正确的 ID:`**pkjopdibdnomhogjheclhnknmejccffg**`
diff --git a/release/star-chart-search-enhancer-chrome-web-store.zip b/release/star-chart-search-enhancer-chrome-web-store.zip
new file mode 100644
index 0000000..6397d89
Binary files /dev/null and b/release/star-chart-search-enhancer-chrome-web-store.zip differ
diff --git a/release/star-chart-search-enhancer-internal.zip b/release/star-chart-search-enhancer-internal.zip
new file mode 100644
index 0000000..f4df8ec
Binary files /dev/null and b/release/star-chart-search-enhancer-internal.zip differ