"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, ...record.coreUserId ? { authorUid: record.coreUserId } : {} })), batchName, createdAt: options.createdAt, creatorName: options.authState.userInfo?.name ?? options.authState.userInfo?.username ?? logtoUserId, logtoUserId, resource }; } // src/content/market/market-list-row.ts var PAGE_NUMBER_KEYS = [ "currentPage", "page", "pageNo", "pageNum", "page_no", "page_num" ]; var PAGE_SIZE_KEYS = [ "limit", "pageSize", "page_size", "size" ]; var TOTAL_COUNT_KEYS = [ "total", "totalCount", "total_count" ]; var TOTAL_PAGE_KEYS = [ "pageCount", "page_count", "totalPage", "totalPages", "total_page", "total_pages" ]; function mapMarketListRow(row) { const attributeDatas = readMarketAttributeDatas(row); const singleVideoAfterSearchRate = normalizeMarketListRate( readMarketFieldValue(row, attributeDatas, "avg_search_after_view_rate_30d") ); return { authorId: readString(readMarketFieldValue(row, attributeDatas, "star_id")) ?? readString(readMarketFieldValue(row, attributeDatas, "id")) ?? "", authorName: readString(readMarketFieldValue(row, attributeDatas, "nickname")) ?? readString(readMarketFieldValue(row, attributeDatas, "nick_name")) ?? "", coreUserId: readString(readMarketFieldValue(row, attributeDatas, "core_user_id")) ?? void 0, exportFields: buildMarketExportFieldFallbacks(row, attributeDatas), hasDirectRatesSource: true, location: readMarketLocation(row, attributeDatas), price21To60s: readMarketPrice21To60s(row, attributeDatas), rates: singleVideoAfterSearchRate ? { singleVideoAfterSearchRate } : void 0 }; } function parseMarketListResponse(payload) { const container = findMarketListContainer(payload); if (!container) { return null; } const marketList = readMarketListArray(container); if (!marketList) { return null; } return { currentPage: readKnownNumberDeep(container, PAGE_NUMBER_KEYS) ?? void 0, pageSize: readKnownNumberDeep(container, PAGE_SIZE_KEYS) ?? void 0, records: marketList.map((row) => 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; const templateCell = templateCells[index] ?? templateCells[templateCells.length - 1] ?? null; if (existingCell) { existingCell.dataset.marketRowCell = field; applyPluginContentCellStyles(existingCell); syncContentCellHeight(existingCell, templateCell); continue; } const nextCell = field === SELECTION_COLUMN_KEY ? templateCell ? createSelectionContentCell(templateCell) : createBareContentCell(column.ownerDocument) : templateCell ? cloneElementShallow(templateCell) : createBareContentCell(column.ownerDocument); nextCell.dataset.marketRowCell = field; applyColumnWidth(nextCell, field); applyPluginContentCellStyles(nextCell); syncContentCellHeight(nextCell, templateCell); nextCell.textContent = ""; column.appendChild(nextCell); } } function syncContentCellHeight(cell, templateCell) { if (!templateCell) { return; } const measuredHeight = Math.round(templateCell.getBoundingClientRect().height); const nextHeight = measuredHeight > 0 ? `${measuredHeight}px` : templateCell.style.height; if (nextHeight) { cell.style.height = nextHeight; } else { cell.style.removeProperty("height"); } } function applyPluginHeaderCellStyles(cell) { cell.style.display = "flex"; cell.style.alignItems = "center"; cell.style.justifyContent = "normal"; cell.style.cursor = "pointer"; cell.style.whiteSpace = "nowrap"; } function applyPluginContentCellStyles(cell) { cell.style.display = "flex"; cell.style.alignItems = "center"; cell.style.justifyContent = "normal"; cell.style.paddingTop = "12px"; cell.style.paddingBottom = "12px"; cell.style.boxSizing = "border-box"; cell.style.whiteSpace = "nowrap"; } function ensureSelectionHeaderControl(cell) { cell.textContent = ""; cell.style.gap = "6px"; cell.style.justifyContent = "center"; const checkbox = ensureSelectionCheckbox(cell, "header"); const label = cell.querySelector( '[data-market-selection-label="header"]' ); if (label) { label.textContent = "\u5168\u9009"; return checkbox; } const nextLabel = cell.ownerDocument.createElement("span"); nextLabel.dataset.marketSelectionLabel = "header"; nextLabel.textContent = "\u5168\u9009"; nextLabel.style.fontSize = "12px"; cell.appendChild(nextLabel); return checkbox; } function ensureSelectionRowControl(cell) { cell.textContent = ""; cell.style.justifyContent = "center"; return ensureSelectionCheckbox(cell, "row"); } function ensureSelectionCheckbox(container, kind) { const existingCheckbox = container.querySelector( `[data-market-selection-checkbox="${kind}"]` ); if (existingCheckbox) { existingCheckbox.type = "checkbox"; return existingCheckbox; } const checkbox = container.ownerDocument.createElement("input"); checkbox.type = "checkbox"; checkbox.dataset.marketSelectionCheckbox = kind; checkbox.style.cursor = "pointer"; container.appendChild(checkbox); return checkbox; } function getOwnerDocument(root) { if ("ownerDocument" in root && root.ownerDocument) { return root.ownerDocument; } return "nodeType" in root && root.nodeType === 9 ? root : null; } function readSyntheticHeaderLabels(header) { return Array.from(header.querySelectorAll("[data-market-header-cell]")).reduce((labels, cell) => { if (!(cell instanceof header.ownerDocument.defaultView.HTMLElement)) { return labels; } const field = cell.dataset.marketHeaderCell; if (!field) { return labels; } labels[field] = normalizeExportCellText2(cell.textContent); return labels; }, {}); } function readSyntheticExportFields(row, headerLabelsByField) { const exportFields = {}; for (const cell of row.querySelectorAll("[data-market-field]")) { if (!(cell instanceof row.ownerDocument.defaultView.HTMLElement)) { continue; } const field = cell.dataset.marketField; const headerLabel = field ? headerLabelsByField[field] : ""; if (!shouldExportColumn(headerLabel)) { continue; } exportFields[headerLabel] = normalizeExportCellText2(cell.textContent); } return exportFields; } function readExportFieldsForDivGridRow(headerCells, rowCells) { const exportFields = {}; rowCells.forEach((cell, index) => { const headerLabel = normalizeExportCellText2(headerCells[index]?.textContent); if (!shouldExportColumn(headerLabel)) { return; } exportFields[headerLabel] = headerLabel === "21-60s\u62A5\u4EF7" ? readDivGridPriceDisplay(cell?.textContent) ?? "" : normalizeExportCellText2(cell?.textContent); }); return exportFields; } function findPreviousHeaderCell(cell) { let current = cell.previousElementSibling; while (current) { if (current instanceof cell.ownerDocument.defaultView.HTMLElement && current.classList.contains("header-cell")) { return current; } current = current.previousElementSibling; } return null; } function findPreviousColumn(column) { let current = column.previousElementSibling; while (current) { if (current instanceof column.ownerDocument.defaultView.HTMLElement && current.classList.contains("content-column")) { return current; } current = current.previousElementSibling; } return null; } function ensurePluginSection(rootSection, referenceSection, options) { const existingSection = rootSection.querySelector( `[data-market-plugin-section="${options.type}"]` ); if (existingSection) { existingSection.dataset.testid = options.testId; existingSection.setAttribute("data-testid", options.testId); return existingSection; } const templateSection = findPreviousSection(referenceSection) ?? referenceSection; const nextSection = cloneElementShallow(templateSection); nextSection.dataset.marketPluginSection = options.type; nextSection.dataset.testid = options.testId; nextSection.setAttribute("data-testid", options.testId); resetStickySectionStyles(nextSection); rootSection.insertBefore(nextSection, referenceSection); return nextSection; } function ensureVisibleHorizontalScroll(section) { ensureVisibleScrollbarStyles(section.ownerDocument); section.classList.remove("hide-scrollbar"); section.dataset.marketScrollbar = "visible"; section.style.overflowX = "auto"; section.style.scrollbarWidth = "thin"; section.style.scrollbarColor = "rgba(148, 163, 184, 0.95) rgba(226, 232, 240, 0.9)"; } function ensureVisibleScrollbarStyles(document2) { if (document2.getElementById(MARKET_SCROLLBAR_STYLE_ID)) { return; } const style = document2.createElement("style"); style.id = MARKET_SCROLLBAR_STYLE_ID; style.textContent = ` [data-market-scrollbar="visible"]::-webkit-scrollbar { display: block !important; height: 10px !important; } [data-market-scrollbar="visible"]::-webkit-scrollbar-track { background: rgba(226, 232, 240, 0.9) !important; border-radius: 999px; } [data-market-scrollbar="visible"]::-webkit-scrollbar-thumb { background: rgba(148, 163, 184, 0.95) !important; border: 2px solid rgba(226, 232, 240, 0.9); border-radius: 999px; } `; document2.head.appendChild(style); } function ensureScrollHint(root, headerSection) { const existingHint = root.querySelector( '[data-testid="market-scroll-hint"]' ); if (existingHint) { existingHint.textContent = MARKET_SCROLL_HINT_TEXT; return; } const hint = root.ownerDocument.createElement("div"); hint.dataset.testid = "market-scroll-hint"; hint.setAttribute("data-testid", "market-scroll-hint"); hint.textContent = MARKET_SCROLL_HINT_TEXT; hint.style.color = "#64748b"; hint.style.display = "flex"; hint.style.fontSize = "12px"; hint.style.justifyContent = "flex-end"; hint.style.lineHeight = "18px"; hint.style.padding = "0 12px 8px"; root.insertBefore(hint, headerSection); } function findPreviousSection(section) { let current = section.previousElementSibling; while (current) { if (current instanceof section.ownerDocument.defaultView.HTMLElement) { return current; } current = current.previousElementSibling; } return null; } function findPreviousNativeSection(section) { let current = section.previousElementSibling; while (current) { if (current instanceof section.ownerDocument.defaultView.HTMLElement && !current.hasAttribute("data-market-plugin-section")) { return current; } current = current.previousElementSibling; } return null; } function resetStickySectionStyles(section) { section.style.position = ""; section.style.left = ""; section.style.right = ""; section.style.zIndex = ""; section.style.width = ""; section.style.minWidth = ""; } function getActionColumn(bodySection) { const columns = getDirectContentColumns(bodySection); return columns[columns.length - 1] ?? null; } function getNativeAuthorColumn(authorSection) { return getDirectContentColumns(authorSection).find( (column) => !column.dataset.marketColumnGroup && getDirectContentCells(column).some( (cell) => cell.querySelector("a") || cell.querySelector(".author-nickname") || Boolean(cell.dataset.authorId) ) ) ?? null; } function getDirectHeaderCells(section) { return Array.from(section.querySelectorAll(".header-cell")).filter( (cell) => cell instanceof section.ownerDocument.defaultView.HTMLElement ); } function getDirectContentColumns(section) { return Array.from(section.children).filter( (child) => child instanceof section.ownerDocument.defaultView.HTMLElement && child.classList.contains("content-column") ); } function getDirectContentCells(column) { return Array.from(column.children).filter( (child) => child instanceof column.ownerDocument.defaultView.HTMLElement && child.classList.contains("content-cell") ); } function getDirectChildIndex(root, descendant) { const directChild = Array.from(root.children).find((child) => child.contains(descendant)); return directChild ? Array.from(root.children).indexOf(directChild) : -1; } function getIndexedChild(root, index) { if (index < 0) { return null; } const child = root.children[index] ?? null; return child instanceof root.ownerDocument.defaultView.HTMLElement ? child : null; } function findCellByText(cells, text) { return cells.find((cell) => cell.textContent?.trim() === text) ?? null; } function cloneElementShallow(reference) { const clone = reference.ownerDocument.createElement(reference.tagName); Array.from(reference.attributes).forEach((attribute) => { clone.setAttribute(attribute.name, attribute.value); }); return clone; } function createBareContentCell(document2) { const cell = document2.createElement("div"); cell.className = "content-cell"; return cell; } function createSelectionContentCell(templateCell) { const cell = cloneElementShallow(templateCell); cell.removeAttribute("data-testid"); cell.removeAttribute("data-author-id"); return cell; } function extractAuthorId(authorCell) { const explicitAuthorId = authorCell.dataset.authorId; if (explicitAuthorId) { return explicitAuthorId; } const linkedAuthorId = Array.from(authorCell.querySelectorAll("a")).map((link) => extractAuthorIdFromHref(link.href)).find((value) => Boolean(value)); if (linkedAuthorId) { return linkedAuthorId; } const fallbackAuthorId = authorCell.querySelector("[data-author-id]")?.getAttribute("data-author-id"); return fallbackAuthorId ?? ""; } function extractAuthorName(authorCell) { return authorCell.querySelector(".author-nickname")?.textContent?.trim() ?? authorCell.textContent?.trim() ?? ""; } function extractAuthorIdFromHref(href) { const match = href.match(/\/author-homepage\/[^/]+\/(\d+)/); return match?.[1] ?? null; } function readVueMarketRows(marketRoot) { const vueRoot = marketRoot.__vue__; const setupStates = collectVueSetupStates(vueRoot); for (const setupState of setupStates) { for (const value of Object.values(setupState)) { const candidate = unwrapVueRef2(value); if (!candidate || typeof candidate !== "object") { continue; } const marketList = unwrapVueRef2( candidate.marketList ); if (!Array.isArray(marketList)) { continue; } return marketList.map((row) => 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) ?? "", coreUserId: readString2(record.coreUserId) ?? void 0, 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, coreUserId: mergeNonEmptyString(baseRow.coreUserId, preferredRow.coreUserId), exportFields: mergeExportFieldMaps(baseRow.exportFields, preferredRow.exportFields), hasDirectRatesSource: preferredRow.hasDirectRatesSource || baseRow.hasDirectRatesSource, location: mergeNonEmptyString(baseRow.location, preferredRow.location), price21To60s: mergeNonEmptyString( baseRow.price21To60s, preferredRow.price21To60s ), rates: mergeRates(baseRow.rates, preferredRow.rates) }; } function takeMatchedMarketDataRow(remainingRows, authorId, authorName) { if (remainingRows.length === 0) { return null; } const matchedIndex = remainingRows.findIndex((row) => { if (authorId && row.authorId === authorId) { return true; } if (authorName && row.authorName === authorName) { return true; } return false; }); if (matchedIndex >= 0) { return remainingRows.splice(matchedIndex, 1)[0] ?? null; } if (!authorId && !authorName) { return remainingRows.shift() ?? null; } return null; } function mergeExportFieldMaps(current, fallback) { if (!current && !fallback) { return void 0; } const nextFields = { ...current ?? {} }; Object.entries(fallback ?? {}).forEach(([key, value]) => { if (!hasTextValue2(nextFields[key]) && hasTextValue2(value)) { nextFields[key] = value; } }); return nextFields; } function mergeRates(current, fallback) { if (!current && !fallback) { return void 0; } return { singleVideoAfterSearchRate: current?.singleVideoAfterSearchRate ?? fallback?.singleVideoAfterSearchRate, personalVideoAfterSearchRate: current?.personalVideoAfterSearchRate ?? fallback?.personalVideoAfterSearchRate }; } function mergeNonEmptyString(current, fallback) { return hasTextValue2(current) ? current : fallback; } function formatCurrencyValue2(value) { if (value === null) { return void 0; } return `\xA5${value.toLocaleString("en-US", { maximumFractionDigits: 0 })}`; } function readSerializedExportFields(record) { if (!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) ?? "", coreUserId: mergeStringValue(existingRecord.coreUserId, incomingRecord.coreUserId), exportFields: mergeFieldMap( existingRecord.exportFields, incomingRecord.exportFields ), failureReason: incomingRecord.failureReason ?? existingRecord.failureReason, hasDirectRatesSource: existingRecord.hasDirectRatesSource || incomingRecord.hasDirectRatesSource, location: mergeStringValue(existingRecord.location, incomingRecord.location), price21To60s: mergeStringValue( existingRecord.price21To60s, incomingRecord.price21To60s ), rates: mergeFieldMap(existingRecord.rates, incomingRecord.rates), status: mergeStatus(existingRecord.status, incomingRecord.status) }; } function mergeFieldMap(current, incoming) { if (!current && !incoming) { return void 0; } const merged = { ...current ?? {} }; Object.entries(incoming ?? {}).forEach(([key, value]) => { const currentValue = merged[key]; if (hasTextValue3(value) || !hasTextValue3(currentValue)) { merged[key] = value; } }); return merged; } function mergeStatus(current, incoming) { const priority = { failed: 1, idle: 0, loading: 2, missing: -1, success: 3 }; return priority[incoming] >= priority[current] ? incoming : current; } function mergeStringValue(current, incoming) { if (hasTextValue3(incoming) || !hasTextValue3(current)) { return incoming ?? current; } return current; } function hasTextValue3(value) { return typeof value === "string" && value.trim().length > 0; } // src/content/market/plugin-toolbar.ts var PLUGIN_ACTION_BUTTON_STYLE_ID = "sces-plugin-action-button-style"; function isPluginToolbarMounted(root, document2) { const actionRow = findNativeActionRow(document2); return Boolean(actionRow && root.parentElement === actionRow && !root.hidden); } function ensurePluginToolbar(document2, handlers) { ensurePluginActionButtonTheme(document2); const existingRoot = document2.querySelector( "[data-plugin-toolbar='root']" ); if (existingRoot) { 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) ?? "", coreUserId: mergeStringValue2(existingRecord.coreUserId, incomingRecord.coreUserId), exportFields: mergeFieldMap2( existingRecord.exportFields, incomingRecord.exportFields ), failureReason: incomingRecord.failureReason ?? existingRecord.failureReason, hasDirectRatesSource: existingRecord.hasDirectRatesSource || incomingRecord.hasDirectRatesSource, location: mergeStringValue2(existingRecord.location, incomingRecord.location), price21To60s: mergeStringValue2( existingRecord.price21To60s, incomingRecord.price21To60s ), rates: mergeFieldMap2(existingRecord.rates, incomingRecord.rates), status: incomingRecord.status ?? existingRecord.status }; } function mergeFieldMap2(current, incoming) { if (!current && !incoming) { return void 0; } const merged = { ...current ?? {} }; Object.entries(incoming ?? {}).forEach(([key, value]) => { const currentValue = merged[key]; if (hasTextValue4(value) || !hasTextValue4(currentValue)) { merged[key] = value; } }); return merged; } function mergeStringValue2(current, incoming) { return hasTextValue4(incoming) ? incoming : current; } function hasTextValue4(value) { return Boolean(value && value.trim().length > 0); } // src/content/market/result-store.ts function createMarketResultStore() { const records = /* @__PURE__ */ new Map(); return { getRecord(authorId) { return records.get(authorId) ?? null; }, listRecords() { return Array.from(records.values()); }, setAuthorFailed(authorId, reason) { const existingRecord = ensureRecord(authorId); existingRecord.status = "failed"; existingRecord.failureReason = reason; }, setAuthorLoading(authorId) { const existingRecord = ensureRecord(authorId); existingRecord.status = "loading"; delete existingRecord.failureReason; }, setBackendMetricsFailed(authorId) { const existingRecord = ensureRecord(authorId); existingRecord.backendMetricsStatus = "failed"; }, setBackendMetricsLoading(authorId) { const existingRecord = ensureRecord(authorId); existingRecord.backendMetricsStatus = "loading"; }, setBackendMetricsMissing(authorId) { const existingRecord = ensureRecord(authorId); existingRecord.backendMetricsStatus = "missing"; }, setBackendMetricsSuccess(authorId, backendMetrics) { const existingRecord = ensureRecord(authorId); existingRecord.backendMetricsStatus = "success"; existingRecord.backendMetrics = { ...existingRecord.backendMetrics, ...backendMetrics }; }, setAuthorSuccess(authorId, rates) { const existingRecord = ensureRecord(authorId); existingRecord.status = "success"; existingRecord.rates = { ...existingRecord.rates, ...rates }; delete existingRecord.failureReason; }, upsertMarketRow(row) { const existingRecord = records.get(row.authorId); if (existingRecord) { existingRecord.authorName = mergeStringValue3(existingRecord.authorName, row.authorName) ?? existingRecord.authorName; existingRecord.coreUserId = mergeStringValue3( existingRecord.coreUserId, row.coreUserId ); existingRecord.location = mergeStringValue3( existingRecord.location, row.location ); existingRecord.price21To60s = mergeStringValue3( existingRecord.price21To60s, row.price21To60s ); existingRecord.exportFields = mergeFieldMap3( existingRecord.exportFields, row.exportFields ); existingRecord.backendMetrics = mergeFieldMap3( existingRecord.backendMetrics, row.backendMetrics ); existingRecord.hasDirectRatesSource = existingRecord.hasDirectRatesSource || row.hasDirectRatesSource; existingRecord.rates = mergeFieldMap3(existingRecord.rates, row.rates); return existingRecord; } const nextRecord = { ...row, backendMetricsStatus: "idle", status: "idle" }; records.set(row.authorId, nextRecord); return nextRecord; } }; function ensureRecord(authorId) { const existingRecord = records.get(authorId); if (existingRecord) { return existingRecord; } const nextRecord = { authorId, authorName: authorId, backendMetricsStatus: "idle", status: "idle" }; records.set(authorId, nextRecord); return nextRecord; } } function mergeFieldMap3(current, incoming) { if (!current && !incoming) { return void 0; } const merged = { ...current ?? {} }; Object.entries(incoming ?? {}).forEach(([key, value]) => { const currentValue = merged[key]; if (!hasTextValue5(currentValue)) { merged[key] = value; } }); return merged; } function mergeStringValue3(current, incoming) { if (!hasTextValue5(current)) { return incoming ?? current; } return current; } function hasTextValue5(value) { return typeof value === "string" && value.trim().length > 0; } // src/shared/auth-messages.ts function isAuthResponseMessage(value) { if (!value || typeof value !== "object") { return false; } const candidate = value; if (candidate.ok === false) { return candidate.type === "auth:error" && typeof candidate.error === "string"; } if (candidate.ok !== true || typeof candidate.type !== "string") { return false; } if (candidate.type === "auth:ack") { return true; } if (candidate.type === "auth:token") { return Boolean( candidate.value && typeof candidate.value === "object" && typeof candidate.value.accessToken === "string" ); } if (candidate.type === "auth:state") { return Boolean( candidate.value && typeof candidate.value === "object" && typeof candidate.value.isAuthenticated === "boolean" ); } return false; } // src/shared/backend-metrics-messages.ts function isBackendMetricsResponseMessage(value) { if (!value || typeof value !== "object") { return false; } const candidate = value; if (candidate.ok === false) { return candidate.type === "backend-metrics:error" && typeof candidate.error === "string"; } return Boolean( candidate.ok === true && candidate.type === "backend-metrics:result" && candidate.value && typeof candidate.value === "object" && Array.isArray(candidate.value.rows) ); } // src/content/market/index.ts function createMarketController(options) { const marketApiClient = createMarketApiClient(); const 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 coreUserId = mergeStringValue4( existingRecord?.coreUserId, rowSnapshot.coreUserId ); const location2 = mergeStringValue4(existingRecord?.location, rowSnapshot.location); const price21To60s = mergeStringValue4( existingRecord?.price21To60s, rowSnapshot.price21To60s ); return { ...existingRecord, ...rowSnapshot, authorName, backendMetrics: mergeFieldMap4( existingRecord?.backendMetrics, rowSnapshot.backendMetrics ), backendMetricsStatus: existingRecord?.backendMetricsStatus ?? "idle", coreUserId, exportFields: withExportFieldFallbacks( mergeFieldMap4(existingRecord?.exportFields, rowSnapshot.exportFields), { authorName, location: location2, price21To60s } ), location: location2, price21To60s, rates: mergeFieldMap4(existingRecord?.rates, rowSnapshot.rates), status: existingRecord?.status ?? "idle" }; } function collectCurrentPageSnapshots() { readCurrentPageRows(options.document).forEach((rowSnapshot) => { resultStore.upsertMarketRow(rowSnapshot); }); } function findCurrentPageScrollContainer(table) { if (!table) { return null; } const candidateScores = /* @__PURE__ */ new Map(); const candidateRoots = table.rows.map((rowDom) => rowDom.row).filter((row) => row instanceof options.window.HTMLElement); for (const rootElement of candidateRoots) { let currentElement = rootElement.parentElement; let depth = 0; while (currentElement) { if (isScrollableContainer(currentElement)) { const scrollRange = currentElement.scrollHeight - currentElement.clientHeight; const existingScore = candidateScores.get(currentElement); if (!existingScore || depth < existingScore.depth) { candidateScores.set(currentElement, { depth, scrollRange }); } } depth += 1; currentElement = currentElement.parentElement; } } const rankedCandidates = Array.from(candidateScores.entries()).sort((left, right) => { const [, leftScore] = left; const [, rightScore] = right; if (rightScore.scrollRange !== leftScore.scrollRange) { return rightScore.scrollRange - leftScore.scrollRange; } return leftScore.depth - rightScore.depth; }); return rankedCandidates[0]?.[0] ?? null; } function isScrollableContainer(element) { const computedStyle = options.window.getComputedStyle(element); return /auto|scroll|overlay/.test(computedStyle.overflowY) && element.scrollHeight > element.clientHeight; } async function waitForDomSettled() { await new Promise((resolve) => { options.window.setTimeout(resolve, 0); }); await Promise.resolve(); } async function collectCurrentPageSnapshotsUntilSettled() { let previousFingerprint = ""; let stablePassCount = 0; let fingerprintStableSince = 0; let lastSnapshot = { blankExportFieldCount: 0, fingerprint: "", missingDefaultFieldCount: 0 }; for (let attempt = 0; attempt < 16; attempt += 1) { await waitForDomSettled(); if (attempt > 0) { await new Promise((resolve) => { options.window.setTimeout( resolve, previousFingerprint.includes("|missing:0") ? 25 : 50 ); }); await Promise.resolve(); } collectCurrentPageSnapshots(); const hydrationSnapshot = readVisibleRowHydrationSnapshot(); lastSnapshot = hydrationSnapshot; if (!hydrationSnapshot.fingerprint) { stablePassCount = 0; previousFingerprint = ""; continue; } if (hydrationSnapshot.fingerprint === previousFingerprint) { stablePassCount += 1; } else { previousFingerprint = hydrationSnapshot.fingerprint; stablePassCount = 1; fingerprintStableSince = options.window.Date.now(); } const stableForMs = options.window.Date.now() - fingerprintStableSince; if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount === 0 && stablePassCount >= 2) { return hydrationSnapshot; } if (hydrationSnapshot.missingDefaultFieldCount === 0 && hydrationSnapshot.blankExportFieldCount > 0 && stablePassCount >= 2 && stableForMs >= 500) { return hydrationSnapshot; } } return lastSnapshot; } function readVisibleRowHydrationSnapshot() { const table = syncMarketTable(options.document); if (!table || table.rows.length === 0) { return { blankExportFieldCount: 0, fingerprint: "", missingDefaultFieldCount: 0 }; } const parts = table.rows.map((rowDom) => { const rowSnapshot = readRowSnapshot(rowDom); const populatedFieldCount = Object.values(rowSnapshot.exportFields ?? {}).filter( (value) => typeof value === "string" && value.trim().length > 0 ).length; const blankExportFieldCount = Object.values(rowSnapshot.exportFields ?? {}).filter( (value) => typeof value !== "string" || value.trim().length === 0 ).length; const hasAuthorField = hasTextValue6(rowSnapshot.exportFields?.["\u8FBE\u4EBA\u4FE1\u606F"]); const hasRepresentativeVideo = hasTextValue6( rowSnapshot.exportFields?.["\u4EE3\u8868\u89C6\u9891"] ); const hasPriceField = hasTextValue6(rowSnapshot.price21To60s) || hasTextValue6(rowSnapshot.exportFields?.["21-60s\u62A5\u4EF7"]); const missingDefaultFieldCount = Number(!hasAuthorField) + Number(!hasRepresentativeVideo) + Number(!hasPriceField); return [ rowSnapshot.authorId, populatedFieldCount, `blank:${blankExportFieldCount}`, hasAuthorField ? "author" : "no-author", hasRepresentativeVideo ? "video" : "no-video", hasPriceField ? "price" : "no-price", `missing:${missingDefaultFieldCount}` ].join(":"); }); return { blankExportFieldCount: parts.reduce((count, part) => { const match = part.match(/:blank:(\d+):/); return count + Number(match?.[1] ?? 0); }, 0), fingerprint: parts.join("|"), missingDefaultFieldCount: parts.reduce((count, part) => { const match = part.match(/missing:(\d+)$/); return count + Number(match?.[1] ?? 0); }, 0) }; } function scheduleSync() { if (isDisposed) { return; } if (isSyncRunning) { needsResync = true; return; } if (isSyncScheduled) { return; } isSyncScheduled = true; scheduledSyncTimeoutId = options.window.setTimeout(() => { scheduledSyncTimeoutId = null; isSyncScheduled = false; if (isDisposed) { return; } void runSyncCycle(); }, 0); } function runWithoutMutationSync(callback) { if (isDisposed) { return; } observer.disconnect(); try { callback(); } finally { startObserving(); } } function startObserving() { if (isDisposed || !observationRoot) { return; } observer.observe(observationRoot, { childList: true, subtree: true }); } async function runSyncCycle() { if (isDisposed) { return; } if (isSyncRunning) { needsResync = true; return; } isSyncRunning = true; try { toolbar = ensurePluginToolbar(options.document, toolbarHandlers); await hydrateCurrentPage(); applyCurrentView(); lastKnownPageSignature = readMarketPageSignature(options.document); } finally { isSyncRunning = false; if (isDisposed) { return; } if (needsResync) { needsResync = false; scheduleSync(); } } } } function setScrollTop(element, top) { element.scrollTop = top; element.dispatchEvent(new Event("scroll")); } function readCurrentPageRows(document2) { const table = syncMarketTable(document2); if (!table) { return []; } return table.rows.map((rowDom) => readRowSnapshot(rowDom)).filter( (row) => Boolean(row.authorId) && hasTextValue6(row.authorName) ); } function countCurrentPageRows(document2) { const table = syncMarketTable(document2); if (!table) { return 0; } return table.rows.filter((rowDom) => Boolean(rowDom.authorId)).length; } function readRowSnapshot(rowDom) { return { authorId: rowDom.authorId, authorName: rowDom.authorName, exportFields: rowDom.exportFields, hasDirectRatesSource: rowDom.hasDirectRatesSource, location: rowDom.location, price21To60s: rowDom.price21To60s, rates: rowDom.rates }; } function getNextSortState(currentSort, field) { if (!currentSort || currentSort.field !== field) { return { direction: "desc", field }; } if (currentSort.direction === "desc") { return { direction: "asc", field }; } return void 0; } function hasCompleteRates(rates) { return Boolean( rates?.singleVideoAfterSearchRate && rates?.personalVideoAfterSearchRate ); } function hasSettledRateState(record) { if (!record) { return false; } return record.status === "failed" || hasCompleteRates(record.rates); } function hasSettledBackendMetricsState(record) { if (!record) { return false; } return record.backendMetricsStatus === "success" || record.backendMetricsStatus === "missing" || record.backendMetricsStatus === "failed"; } function mergeFieldMap4(current, incoming) { if (!current && !incoming) { return void 0; } const merged = { ...current ?? {} }; Object.entries(incoming ?? {}).forEach(([key, value]) => { const currentValue = merged[key]; if (hasTextValue6(value) || !hasTextValue6(currentValue)) { merged[key] = value; } }); return merged; } function createRuntimeMessageSender() { return (message) => Promise.resolve( globalThis.chrome?.runtime?.sendMessage?.(message) ); } async function readAuthState(sendMessage) { const response = await sendMessage({ type: "auth:get-state" }); if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") { throw new Error("\u8BF7\u5148\u767B\u5F55\u63D2\u4EF6"); } return response.value; } async function readBatchSubmitAck(sendMessage, payload) { const response = await sendMessage({ payload, type: "batch:submit" }); if (response && typeof response === "object" && response.ok === true) { return response.value; } if (response && typeof response === "object" && response.ok === false && typeof response.error === "string") { throw new Error(response.error); } throw new Error("\u6279\u6B21\u63D0\u4EA4\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"); } async function readBackendMetrics(sendMessage, starIds) { const response = await sendMessage({ type: "backend-metrics:search", value: { starIds } }); if (isBackendMetricsResponseMessage(response) && response.ok && response.type === "backend-metrics:result") { return response.value.rows; } throw new Error("\u540E\u7AEF\u6307\u6807\u52A0\u8F7D\u5931\u8D25"); } function mergeStringValue4(current, incoming) { if (hasTextValue6(incoming) || !hasTextValue6(current)) { return incoming ?? current; } return current; } function withExportFieldFallbacks(exportFields, fallbackValues) { if (!exportFields) { return void 0; } const nextExportFields = { ...exportFields }; if ("\u8FBE\u4EBA\u4FE1\u606F" in nextExportFields && !hasTextValue6(nextExportFields["\u8FBE\u4EBA\u4FE1\u606F"]) && hasTextValue6(fallbackValues.authorName)) { nextExportFields["\u8FBE\u4EBA\u4FE1\u606F"] = fallbackValues.authorName; } if ("\u5730\u533A" in nextExportFields && !hasTextValue6(nextExportFields["\u5730\u533A"]) && hasTextValue6(fallbackValues.location)) { nextExportFields["\u5730\u533A"] = fallbackValues.location; } if ("21-60s\u62A5\u4EF7" in nextExportFields && !hasTextValue6(nextExportFields["21-60s\u62A5\u4EF7"]) && hasTextValue6(fallbackValues.price21To60s)) { nextExportFields["21-60s\u62A5\u4EF7"] = fallbackValues.price21To60s; } return nextExportFields; } function hasTextValue6(value) { return typeof value === "string" && value.trim().length > 0; } function hasRuntimeMessageSender() { return Boolean( globalThis.chrome?.runtime?.sendMessage ); } // 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); } })();