chore: remove dist-release and keep dist only

This commit is contained in:
admin123 2026-05-25 14:42:17 +08:00
parent d7b35d6149
commit 6e06a67bde
12 changed files with 0 additions and 10403 deletions

2
.gitignore vendored
View File

@ -2,12 +2,10 @@
.old-reference/
.local/
dist/
# dist-release/
# release/
node_modules/
# Build artifacts
dist-release/
dist-release.pem
dist-release.crx

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1,13 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" fill="none">
<defs>
<linearGradient id="bg" x1="16" y1="12" x2="114" y2="116" gradientUnits="userSpaceOnUse">
<stop stop-color="#9F1239"/>
<stop offset="1" stop-color="#4C0519"/>
</linearGradient>
</defs>
<rect x="8" y="8" width="112" height="112" rx="28" fill="url(#bg)"/>
<path d="M34 80L50 62L64 70L83 46" stroke="#FFF7ED" stroke-linecap="round" stroke-linejoin="round" stroke-width="10"/>
<circle cx="86" cy="44" r="8" fill="#FFF7ED"/>
<path d="M79 80C79 71.7157 85.7157 65 94 65C102.284 65 109 71.7157 109 80C109 88.2843 102.284 95 94 95C85.7157 95 79 88.2843 79 80Z" stroke="#FFF7ED" stroke-width="8"/>
<path d="M104 91L114 101" stroke="#FFF7ED" stroke-linecap="round" stroke-width="8"/>
</svg>

Before

Width:  |  Height:  |  Size: 822 B

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,59 +0,0 @@
{
"action": {
"default_icon": {
"16": "assets/icons/icon-16.png",
"32": "assets/icons/icon-32.png"
},
"default_popup": "popup/index.html"
},
"background": {
"service_worker": "background/index.js"
},
"content_scripts": [
{
"js": [
"content/index.js"
],
"matches": [
"https://xingtu.cn/ad/creator/market*",
"https://*.xingtu.cn/ad/creator/market*"
],
"run_at": "document_start"
}
],
"description": "Bootstraps the Xingtu creator market content script.",
"icons": {
"16": "assets/icons/icon-16.png",
"32": "assets/icons/icon-32.png",
"48": "assets/icons/icon-48.png",
"128": "assets/icons/icon-128.png"
},
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0CaZJxcX97TbRXCR08L10t9EZFV31+wPnUgDf21j2f0qYaWdblzWXfVkeU9jGb2Hr2Etpp7F/XuBa6pcipUXkzMMBkJ42KOkciAwbuzTBoAtGB8o9aoWigtax+gGfSz+T3BjqxKBJtXqeqbIAKCDIlxRKIrY+KcY1Z+mD5BKcBHKsUDQPlHsrjc1g0wIBD5doz9LoOk1Wso6gK5cSeOp9lw5YHcu4TImR4yqxGiL6pZwnpciuX/g7qjWBZXn5gf0YBlDsBDDTt5upbP3NguUKgO2qA9M77LyeUwXl3aqbIxYi/VwsQ2t5w9PGWtnOUQQDWUcEg/9dfTb89esZXKATwIDAQAB",
"manifest_version": 3,
"name": "Star Chart Search Enhancer",
"permissions": [
"downloads",
"identity",
"storage"
],
"web_accessible_resources": [
{
"matches": [
"https://xingtu.cn/*",
"https://*.xingtu.cn/*"
],
"resources": [
"content/market-page-bridge.js"
]
}
],
"version": "0.0525.6",
"host_permissions": [
"https://xingtu.cn/ad/creator/market*",
"https://*.xingtu.cn/ad/creator/market*",
"https://login-api.intelligrow.cn/*",
"https://talent-search.intelligrow.cn/*",
"http://192.168.31.21:8083/*",
"https://*/*"
]
}

View File

@ -1,12 +0,0 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Star Chart Search Enhancer</title>
</head>
<body>
<main id="app"></main>
<script src="./index.js"></script>
</body>
</html>

View File

@ -1,422 +0,0 @@
"use strict";
(() => {
// src/popup/view.ts
function renderLoggedOut(root, error) {
root.innerHTML = `
<section data-popup-state="logged-out">
<h1>Star Chart Search Enhancer</h1>
<p>\u767B\u5F55\u540E\u624D\u80FD\u4F7F\u7528\u661F\u56FE\u589E\u5F3A\u529F\u80FD</p>
${error ? `<p data-popup-error="true">${error}</p>` : ""}
<button type="button" data-popup-sign-in="button">\u767B\u5F55 Logto</button>
</section>
`;
}
function renderLoggedIn(root, authState) {
const userInfo = authState.userInfo;
root.innerHTML = `
<section data-popup-state="logged-in">
<h1>Star Chart Search Enhancer</h1>
<p>\u5DF2\u767B\u5F55</p>
<p>${userInfo?.name ?? userInfo?.username ?? "\u672A\u77E5\u7528\u6237"}</p>
<p>${userInfo?.email ?? ""}</p>
<section data-popup-update="root">
<h2>\u7248\u672C\u66F4\u65B0</h2>
<p data-popup-update-status="text">\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...</p>
</section>
<button type="button" data-popup-sign-out="button">\u9000\u51FA\u767B\u5F55</button>
</section>
`;
}
function renderUpdateStatus(root, options) {
const container = root.querySelector('[data-popup-update="root"]');
if (!container) {
return;
}
if (options.status === "checking") {
container.innerHTML = `
<h2>\u7248\u672C\u66F4\u65B0</h2>
<p data-popup-update-status="text">\u5F53\u524D\u7248\u672C\uFF1A${options.currentVersion}</p>
<p>\u6B63\u5728\u68C0\u67E5\u66F4\u65B0...</p>
`;
return;
}
if (options.status === "error") {
container.innerHTML = `
<h2>\u7248\u672C\u66F4\u65B0</h2>
<p data-popup-update-status="text">\u5F53\u524D\u7248\u672C\uFF1A${options.currentVersion}</p>
<p>\u6682\u65F6\u65E0\u6CD5\u68C0\u67E5\u66F4\u65B0</p>
<p>\u5982\u679C\u9700\u8981\u65B0\u7248\uFF0C\u8BF7\u8054\u7CFB\u7EF4\u62A4\u540C\u4E8B\u83B7\u53D6\u66F4\u65B0\u5305\u3002</p>
`;
return;
}
if (options.status === "latest" || !options.manifest) {
container.innerHTML = `
<h2>\u7248\u672C\u66F4\u65B0</h2>
<p data-popup-update-status="text">\u5F53\u524D\u7248\u672C\uFF1A${options.currentVersion}</p>
<p>\u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C</p>
`;
return;
}
container.innerHTML = `
<h2>\u7248\u672C\u66F4\u65B0</h2>
<p data-popup-update-status="text">\u5F53\u524D\u7248\u672C\uFF1A${options.currentVersion}</p>
<p>\u53D1\u73B0\u65B0\u7248\u672C\uFF1A${options.manifest.latestVersion}</p>
${renderReleaseNotes(options.manifest.releaseNotes)}
<button type="button" data-popup-download-update="button">\u4E0B\u8F7D\u66F4\u65B0\u5305</button>
<button type="button" data-popup-download-guide="button">\u4E0B\u8F7D\u4F7F\u7528\u8BF4\u660E</button>
<p data-popup-update-download-status="text">\u4E0B\u8F7D\u540E\u8BF7\u89E3\u538B\u65B0\u7248 zip\uFF0C\u5E76\u5728 chrome://extensions \u91CC\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u3002</p>
`;
}
function setUpdateDownloadStatus(root, value) {
const output = root.querySelector('[data-popup-update-download-status="text"]');
if (!output) {
return;
}
output.textContent = value;
}
function renderReleaseNotes(releaseNotes) {
if (releaseNotes.length === 0) {
return "";
}
return `
<ul>
${releaseNotes.map((note) => `<li>${escapeHtml(note)}</li>`).join("")}
</ul>
`;
}
function escapeHtml(value) {
return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function renderDevPanel(root, authState) {
const panel = root.ownerDocument.createElement("section");
panel.dataset.popupDevPanel = "root";
panel.innerHTML = `
<h2>dev auth panel</h2>
<p>resource: ${authState.resource ?? ""}</p>
<p>scopes: ${(authState.scopes ?? []).join(", ")}</p>
<p>token: ${authState.tokenAvailable ? "available" : "missing"}</p>
<p>expires: ${authState.accessTokenExpiresAt ?? "unknown"}</p>
<p>error: ${authState.lastError ?? ""}</p>
<button type="button" data-popup-test-protected-api="button">\u6D4B\u8BD5\u53D7\u4FDD\u62A4\u63A5\u53E3</button>
<pre data-popup-protected-api-result="output"></pre>
`;
root.appendChild(panel);
}
function setProtectedApiResult(root, value) {
const output = root.querySelector(
'[data-popup-protected-api-result="output"]'
);
if (!output) {
return;
}
output.textContent = value;
}
// src/shared/auth-config.ts
var defaultAuthConfig = {
apiResource: "https://talent-search.intelligrow.cn",
appId: "i4jkllbvih0554r4n0fd3",
enableDevAuthPanel: false,
logtoEndpoint: "https://login-api.intelligrow.cn",
scopes: ["openid", "profile", "offline_access", "talent-search:read"]
};
function readAuthConfig(overrides = {}) {
const nextConfig = {
...defaultAuthConfig,
...overrides
};
if (!nextConfig.logtoEndpoint.trim()) {
throw new Error("auth config logtoEndpoint is required");
}
if (!nextConfig.appId.trim()) {
throw new Error("auth config appId is required");
}
if (!nextConfig.apiResource.trim()) {
throw new Error("auth config apiResource is required");
}
return nextConfig;
}
// src/shared/auth-messages.ts
function isAuthResponseMessage(value) {
if (!value || typeof value !== "object") {
return false;
}
const candidate = value;
if (candidate.ok === false) {
return candidate.type === "auth:error" && typeof candidate.error === "string";
}
if (candidate.ok !== true || typeof candidate.type !== "string") {
return false;
}
if (candidate.type === "auth:ack") {
return true;
}
if (candidate.type === "auth:token") {
return Boolean(
candidate.value && typeof candidate.value === "object" && typeof candidate.value.accessToken === "string"
);
}
if (candidate.type === "auth:state") {
return Boolean(
candidate.value && typeof candidate.value === "object" && typeof candidate.value.isAuthenticated === "boolean"
);
}
return false;
}
// src/shared/protected-api-client.ts
function createProtectedApiClient(options) {
const fetchImpl = options.fetchImpl ?? fetch;
return {
async loadProtectedMockData() {
const token = await readAccessToken(options.sendMessage);
const response = await fetchImpl(
new URL("/api/mock/protected", options.baseUrl).toString(),
{
headers: {
Authorization: `Bearer ${token}`
},
method: "GET"
}
);
if (response.status === 401 || response.status === 403) {
throw new Error("protected api unauthorized");
}
if (!response.ok) {
throw new Error(`protected api request failed: ${response.status}`);
}
return response.json();
}
};
}
async function readAccessToken(sendMessage) {
const response = await sendMessage({ type: "auth:get-access-token" });
if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:token" || !response.value.accessToken.trim()) {
throw new Error("protected api token unavailable");
}
return response.value.accessToken;
}
// src/shared/update-check.ts
function compareExtensionVersions(left, right) {
const leftParts = parseVersionParts(left);
const rightParts = parseVersionParts(right);
const maxLength = Math.max(leftParts.length, rightParts.length);
for (let index = 0; index < maxLength; index += 1) {
const leftValue = leftParts[index] ?? 0;
const rightValue = rightParts[index] ?? 0;
if (leftValue !== rightValue) {
return leftValue - rightValue;
}
}
return 0;
}
function parseUpdateManifest(value) {
if (!value || typeof value !== "object") {
return null;
}
const candidate = value;
if (!isVersionString(candidate.latestVersion) || !isVersionString(candidate.minSupportedVersion) || !isHttpsUrl(candidate.zipUrl) || !isHttpsUrl(candidate.guideUrl) || typeof candidate.publishedAt !== "string" || !Array.isArray(candidate.releaseNotes) || !candidate.releaseNotes.every((note) => typeof note === "string")) {
return null;
}
return {
guideUrl: candidate.guideUrl,
latestVersion: candidate.latestVersion,
minSupportedVersion: candidate.minSupportedVersion,
publishedAt: candidate.publishedAt,
releaseNotes: candidate.releaseNotes,
zipUrl: candidate.zipUrl
};
}
async function fetchUpdateManifest(manifestUrl, fetchImpl = fetch) {
const response = await fetchImpl(manifestUrl, {
cache: "no-store"
});
if (!response.ok) {
throw new Error(`update manifest request failed: ${response.status}`);
}
const manifest = parseUpdateManifest(await response.json());
if (!manifest) {
throw new Error("update manifest is invalid");
}
return manifest;
}
function parseVersionParts(value) {
return value.split(".").map((part) => {
const parsed = Number.parseInt(part, 10);
return Number.isFinite(parsed) ? parsed : 0;
});
}
function isVersionString(value) {
return typeof value === "string" && /^\d+(?:\.\d+)*$/.test(value);
}
function isHttpsUrl(value) {
if (typeof value !== "string") {
return false;
}
try {
return new URL(value).protocol === "https:";
} catch {
return false;
}
}
// src/shared/update-config.ts
var UPDATE_MANIFEST_URL = "https://wksgx-1343191620.cos.ap-nanjing.myqcloud.com/star-chart-search-enhancer/latest.json";
// src/popup/index.ts
async function bootPopup(options = {}) {
const currentDocument = options.document ?? document;
const popupConfig = readAuthConfig(options.config);
const currentVersion = options.currentVersion ?? readCurrentVersion();
const root = currentDocument.querySelector("#app");
const HTMLElementCtor = currentDocument.defaultView?.HTMLElement;
if (!root || HTMLElementCtor && !(root instanceof HTMLElementCtor)) {
throw new Error("popup root #app is required");
}
const sendMessage = options.sendMessage ?? ((message) => Promise.resolve(
globalThis.chrome?.runtime?.sendMessage?.(message)
));
const fetchProtectedApi = options.fetchProtectedApi ?? createProtectedApiClient({
baseUrl: "http://127.0.0.1:4319",
sendMessage
}).loadProtectedMockData;
const fetchUpdateManifest2 = options.fetchUpdateManifest ?? (() => fetchUpdateManifest(
options.updateManifestUrl ?? UPDATE_MANIFEST_URL
));
await renderCurrentAuthState(root, popupConfig, sendMessage, fetchProtectedApi, {
currentVersion,
fetchUpdateManifest: fetchUpdateManifest2
});
}
async function renderCurrentAuthState(root, popupConfig, sendMessage, fetchProtectedApi, updateOptions) {
const response = await sendMessage({ type: "auth:get-state" });
if (!isAuthResponseMessage(response) || !response.ok || response.type !== "auth:state") {
renderLoggedOut(root, "\u8BA4\u8BC1\u72B6\u6001\u8BFB\u53D6\u5931\u8D25");
return;
}
if (!response.value.isAuthenticated) {
renderLoggedOut(root, response.value.lastError);
root.querySelector('[data-popup-sign-in="button"]')?.addEventListener("click", () => {
void runAuthAction(root, popupConfig, sendMessage, {
actionMessage: { type: "auth:sign-in" },
fetchProtectedApi,
updateOptions
});
});
return;
}
renderLoggedIn(root, response.value);
void runUpdateCheck(root, sendMessage, updateOptions);
root.querySelector('[data-popup-sign-out="button"]')?.addEventListener("click", () => {
void runAuthAction(root, popupConfig, sendMessage, {
actionMessage: { type: "auth:sign-out" },
fetchProtectedApi,
updateOptions
});
});
if (popupConfig.enableDevAuthPanel) {
renderDevPanel(root, response.value);
root.querySelector('[data-popup-test-protected-api="button"]')?.addEventListener("click", () => {
void runProtectedApiProbe(root, fetchProtectedApi);
});
}
}
async function runAuthAction(root, popupConfig, sendMessage, options) {
const response = await sendMessage(options.actionMessage);
if (isActionError(response)) {
renderLoggedOut(root, response.error);
root.querySelector('[data-popup-sign-in="button"]')?.addEventListener("click", () => {
void runAuthAction(root, popupConfig, sendMessage, options);
});
return;
}
await renderCurrentAuthState(
root,
popupConfig,
sendMessage,
options.fetchProtectedApi,
options.updateOptions
);
}
function isActionError(response) {
return isAuthResponseMessage(response) && !response.ok && response.type === "auth:error";
}
async function runUpdateCheck(root, sendMessage, options) {
renderUpdateStatus(root, {
currentVersion: options.currentVersion,
status: "checking"
});
try {
const manifest = await options.fetchUpdateManifest();
if (compareExtensionVersions(manifest.latestVersion, options.currentVersion) <= 0) {
renderUpdateStatus(root, {
currentVersion: options.currentVersion,
status: "latest"
});
return;
}
renderUpdateStatus(root, {
currentVersion: options.currentVersion,
manifest,
status: "available"
});
bindUpdateDownloadButtons(root, sendMessage, manifest);
} catch {
renderUpdateStatus(root, {
currentVersion: options.currentVersion,
status: "error"
});
}
}
function bindUpdateDownloadButtons(root, sendMessage, manifest) {
root.querySelector('[data-popup-download-update="button"]')?.addEventListener("click", () => {
void downloadUpdateAsset(root, sendMessage, {
filename: "star-chart-search-enhancer-internal.zip",
url: manifest.zipUrl
});
});
root.querySelector('[data-popup-download-guide="button"]')?.addEventListener("click", () => {
void downloadUpdateAsset(root, sendMessage, {
filename: "\u661F\u56FE\u589E\u5F3A\u63D2\u4EF6-\u8D85\u7B80\u5355\u5B89\u88C5\u4F7F\u7528\u6307\u5357.pdf",
url: manifest.guideUrl
});
});
}
async function downloadUpdateAsset(root, sendMessage, options) {
setUpdateDownloadStatus(root, "\u6B63\u5728\u4E0B\u8F7D...");
try {
await sendMessage({
filename: options.filename,
type: "update:download",
url: options.url
});
setUpdateDownloadStatus(root, "\u5DF2\u89E6\u53D1\u4E0B\u8F7D\u3002\u4E0B\u8F7D\u540E\u8BF7\u89E3\u538B\u65B0\u7248 zip\uFF0C\u5E76\u5728 chrome://extensions \u91CC\u91CD\u65B0\u52A0\u8F7D\u63D2\u4EF6\u3002");
} catch (error) {
setUpdateDownloadStatus(
root,
error instanceof Error ? error.message : "\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
);
}
}
function readCurrentVersion() {
const runtime = globalThis.chrome?.runtime;
return runtime?.getManifest?.().version ?? "0.0.0";
}
async function runProtectedApiProbe(root, fetchProtectedApi) {
setProtectedApiResult(root, "\u8BF7\u6C42\u4E2D...");
try {
const result = await fetchProtectedApi();
setProtectedApiResult(root, JSON.stringify(result, null, 2));
} catch (error) {
setProtectedApiResult(
root,
error instanceof Error ? error.message : String(error)
);
}
}
if (typeof document !== "undefined") {
void bootPopup();
}
})();