Files
xueqiu_sync/chrome-extension/popup.js
T
2026-06-15 10:06:03 +08:00

318 lines
9.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// popup.js — 读 xueqiu.com 的 HttpOnly Cookie,弹窗展示 + 一键复制。
//
// 浏览器扩展能直接读 HttpOnly cookiechrome.cookies API),
// 但不能直连 PG/MySQL(沙箱限制)。所以这里只读 + 展示 + 复制。
// 复制完手动粘到 .env / dashboard / 任何需要的地方。
//
// v0.2 改动:
// 1) 尝试多种 chrome.cookies 查询模式(domain / url / 带 partitionKey
// 2) 同时从 content script 拉页面 document.cookie
// 3) 手动粘贴兜底(万一所有自动读都失败)
const $xq = document.getElementById("xq-val");
const $u = document.getElementById("u-val");
const $combined = document.getElementById("combined-val");
const $msg = document.getElementById("msg");
const $debug = document.getElementById("debug");
const $manual = document.getElementById("manual-textarea");
const $useManual = document.getElementById("use-manual");
let COOKIES = { xq_a_token: "", u: "" };
let ALL_COOKIES = [];
let PAGE_COOKIES = {};
function setMsg(text, level = "info") {
$msg.className = level;
$msg.textContent = text;
}
function clearMsg() {
$msg.className = "";
$msg.textContent = "";
}
function setVal(el, v, isEmpty) {
el.textContent = v || "未读取";
el.classList.toggle("empty", !!isEmpty);
}
function combinedString() {
const { xq_a_token, u } = COOKIES;
if (!xq_a_token || !u) return "";
return `xq_a_token=${xq_a_token};u=${u}`;
}
function mergeCookies(...lists) {
const seen = new Set();
const out = [];
for (const list of lists) {
for (const c of list || []) {
const key = `${c.domain}|${c.path}|${c.name}`;
if (!seen.has(key)) {
seen.add(key);
out.push(c);
}
}
}
return out;
}
function tryParseCookieString(s) {
// 接受 "a=1; b=2" 格式
const out = {};
if (!s) return out;
const norm = s.replace(/\n/g, ";").replace(/\r/g, ";");
for (const part of norm.split(";")) {
const idx = part.indexOf("=");
if (idx > 0) {
const k = part.slice(0, idx).trim();
const v = part.slice(idx + 1).trim();
if (k) out[k] = v;
}
}
return out;
}
async function readCookies() {
clearMsg();
ALL_COOKIES = [];
PAGE_COOKIES = {};
if (typeof chrome === "undefined" || !chrome.cookies) {
setMsg("❌ chrome.cookies API 不可用 — 扩展可能没正确加载", "err");
return;
}
const errors = [];
const queries = [];
// 模式 Adomain 模式
try {
const a = await chrome.cookies.getAll({ domain: ".xueqiu.com" });
queries.push({ mode: "domain=.xueqiu.com", n: a.length, list: a });
} catch (e) {
errors.push(`domain=.xueqiu.com: ${e.message || e}`);
}
try {
const a = await chrome.cookies.getAll({ domain: "xueqiu.com" });
queries.push({ mode: "domain=xueqiu.com (exact)", n: a.length, list: a });
} catch (e) {
errors.push(`domain=xueqiu.com: ${e.message || e}`);
}
// 模式 B:url 模式(多个变体)
const urls = [
"https://xueqiu.com/",
"https://www.xueqiu.com/",
"https://xueqiu.com",
"https://www.xueqiu.com",
];
for (const u of urls) {
try {
const r = await chrome.cookies.getAll({ url: u });
queries.push({ mode: `url=${u}`, n: r.length, list: r });
} catch (e) {
errors.push(`url=${u}: ${e.message || e}`);
}
}
// 模式 C:按 cookie 名精确查(不走 list,直接 single get
for (const u of urls) {
for (const name of ["xq_a_token", "u"]) {
try {
const c = await chrome.cookies.get({ url: u, name });
if (c) queries.push({ mode: `get ${name} @ ${u}`, n: 1, list: [c] });
} catch (e) {
// 静默:找不到是正常的
}
}
}
// 模式 D:分桶 cookieChrome 115+ 默认 partition 行为)
for (const u of urls) {
try {
const r = await chrome.cookies.getAll({
url: u,
partitionKey: { topLevelSite: "https://xueqiu.com" },
});
queries.push({ mode: `url=${u} (partitioned TLS=xueqiu.com)`, n: r.length, list: r });
} catch (e) {
// 忽略
}
}
// 模式 E:从 content script 读(page document.cookie
try {
const tabs = await chrome.tabs.query({
url: ["https://xueqiu.com/*", "https://*.xueqiu.com/*"],
});
for (const t of tabs) {
try {
const resp = await chrome.tabs.sendMessage(t.id, {
type: "xueqiu_sync/get_page_cookies",
});
if (resp && resp.cookies) {
PAGE_COOKIES = { ...PAGE_COOKIES, ...resp.cookies };
queries.push({ mode: `page @ ${t.url}`, n: Object.keys(resp.cookies).length, list: [] });
// 把 PAGE_COOKIES 转成伪 cookie 对象参与合并
for (const [k, v] of Object.entries(resp.cookies)) {
queries[queries.length - 1].list.push({
domain: "(page)",
path: "/",
name: k,
value: v,
});
}
}
} catch (_) {
// content script 没注入
}
}
} catch (e) {
errors.push(`tabs query: ${e.message || e}`);
}
// 合并所有模式的 cookie
const allLists = queries.map((q) => q.list);
ALL_COOKIES = mergeCookies(...allLists);
console.log("[xueqiu_sync] queries:", queries);
console.log("[xueqiu_sync] merged:", ALL_COOKIES);
const xq = ALL_COOKIES.find((c) => c.name === "xq_a_token" && c.value);
const u = ALL_COOKIES.find((c) => c.name === "u" && c.value);
COOKIES = {
xq_a_token: xq ? xq.value : "",
u: u ? u.value : "",
};
setVal($xq, COOKIES.xq_a_token, !COOKIES.xq_a_token);
setVal($u, COOKIES.u, !COOKIES.u);
$combined.value = combinedString() || "未读取";
// 扩展自身信息
let extInfo = null;
try {
extInfo = {
id: chrome.runtime.id,
manifest: chrome.runtime.getManifest(),
};
} catch (e) {
extInfo = { id: "(no chrome.runtime)", err: e.message };
}
renderDebug(queries, errors, extInfo);
// 状态判断
if (ALL_COOKIES.length === 0) {
setMsg(
"❌ 一个 xueqiu.com cookie 都没拿到。\n" +
"请展开下方「调试信息」看具体哪些查询返回 0 个。" +
"如果只有 page @ 拿到、其它都 0:可能是分桶 cookie,需要等 Chrome 改进。\n" +
"如果全部都 0:可能是 Chrome profile 不一致 / 扩展权限没生效。",
"err",
);
} else if (!COOKIES.xq_a_token) {
const names = ALL_COOKIES.map((c) => c.name).join(", ");
setMsg(
`❌ 拿到了 ${ALL_COOKIES.length} 个 cookie 但没有 xq_a_token。\n字段:${names}\n` +
"xq_a_token 是 HttpOnly。如果只能从 page 读到 u(无 xq_a_token):\n" +
"请展开调试区看「get xq_a_token @ ...」的查询结果。",
"err",
);
} else if (!COOKIES.u) {
setMsg("❌ 缺 u 字段 — Cookie 不完整", "err");
} else {
setMsg(`✅ 已读取 xq_a_token + u(共 ${ALL_COOKIES.length} 个 xueqiu.com cookie`, "ok");
}
}
function renderDebug(queries, errors, extInfo) {
const lines = [];
if (extInfo) {
lines.push(`[扩展] id=${extInfo.id}`);
if (extInfo.manifest) {
const perms = (extInfo.manifest.permissions || []).join(", ");
const hosts = (extInfo.manifest.host_permissions || []).join(", ");
lines.push(`[扩展] permissions=${perms}`);
lines.push(`[扩展] host_permissions=${hosts}`);
lines.push(`[扩展] manifest_version=${extInfo.manifest.manifest_version}`);
}
}
for (const q of queries) {
lines.push(`[查询] ${q.mode}${q.n}`);
}
if (errors && errors.length) {
lines.push("[错误](仅 show failure");
for (const e of errors) lines.push(" - " + e);
}
if (ALL_COOKIES.length) {
lines.push("[合并后] 列表:");
for (const c of ALL_COOKIES) {
const v = (c.value || "").slice(0, 40);
lines.push(` ${c.domain} ${c.path} ${c.name}=${v}${c.value && c.value.length > 40 ? "…" : ""}`);
}
}
$debug.textContent = lines.join("\n");
}
async function copyText(text, label) {
if (!text) {
setMsg("❌ 没有可复制的内容(Cookie 缺失)", "err");
return;
}
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
} else {
const ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
}
setMsg(`✅ 已复制:${label}`, "ok");
} catch (e) {
setMsg(`❌ 复制失败:${e.message || e}`, "err");
}
}
document.getElementById("copy-combined").addEventListener("click", () => {
copyText(combinedString(), "xq_a_token=...;u=...");
});
document.getElementById("copy-env").addEventListener("click", () => {
copyText(`XUEQIU_TOKEN=${combinedString()}`, "XUEQIU_TOKEN=...");
});
document.getElementById("copy-xq").addEventListener("click", () => {
copyText(COOKIES.xq_a_token, "xq_a_token (单独值)");
});
document.getElementById("reload-btn").addEventListener("click", () => {
readCookies();
});
// 手动粘贴兜底:用户从 DevTools Network tab 复制 Cookie header 粘到这里
$useManual.addEventListener("click", () => {
const text = $manual.value.trim();
if (!text) {
setMsg("❌ 粘贴框为空", "err");
return;
}
const pairs = tryParseCookieString(text);
const xq = pairs["xq_a_token"] || "";
const u = pairs["u"] || "";
if (!xq || !u) {
setMsg(`❌ 粘贴内容里找不到 xq_a_token 和/或 u(找到字段:${Object.keys(pairs).join(", ")}`, "err");
return;
}
COOKIES = { xq_a_token: xq, u };
setVal($xq, xq, false);
setVal($u, u, false);
$combined.value = combinedString();
setMsg("✅ 已从手动粘贴提取(仅当前会话有效)", "ok");
});
// 弹窗打开时立刻读一次
readCookies();