邀请资格
(async () => {
if (location.hostname !== "chatgpt.com") {
throw new Error("请在 https://chatgpt.com 页面执行");
}
const referralKey = "codex_referral_persistent_invite";
const sessionRes = await fetch("/api/auth/session", {
credentials: "include",
headers: { accept: "application/json" },
});
const session = await sessionRes.json();
const token = session.accessToken;
if (!token) {
throw new Error("无法获取 accessToken,请确认已登录 ChatGPT");
}
async function api(path) {
const res = await fetch(path, {
method: "GET",
credentials: "include",
headers: {
accept: "application/json",
authorization: `Bearer ${token}`,
},
});
const text = await res.text();
let data;
try {
data = JSON.parse(text);
} catch {
data = text.slice(0, 300);
}
return { status: res.status, ok: res.ok, data };
}
console.clear();
console.log("%c正在检测 Codex 邀请资格...", "font-size:14px;font-weight:700;color:#2563eb;");
const [account, usage, rules, eligibility, resetCredits] = await Promise.all([
api("/backend-api/wham/accounts/check"),
api("/backend-api/wham/usage"),
api(`/backend-api/wham/referrals/eligibility_rules?referral_key=${encodeURIComponent(referralKey)}`),
api(`/backend-api/referrals/invite/eligibility?referral_key=${encodeURIComponent(referralKey)}`),
api("/backend-api/wham/rate-limit-reset-credits"),
]);
const acct = account.data?.accounts?.[0] ?? {};
const plan = usage.data?.plan_type ?? acct.plan_type ?? "unknown";
const timeRule = rules.data?.time_frame_rules?.[0] ?? {};
const sent = Number(timeRule.invites_sent ?? 0);
const total = Number(timeRule.invites_total ?? 0);
const remaining = Math.max(total - sent, 0);
const shouldShow = eligibility.data?.should_show;
let verdict;
let verdictColor;
if (remaining <= 0) {
verdict = "当前周期邀请额度已用完。";
verdictColor = "#dc2626";
} else if (plan === "free") {
verdict = "当前有邀请规则额度,但 Free 账号发送可能被 plan 拦截;升级 Plus/Pro 后可能解锁,但不能保证。";
verdictColor = "#b45309";
} else if (shouldShow === false) {
verdict = "当前有额度,但前端 eligibility 不显示;可能被服务端活动/实验开关拦截。";
verdictColor = "#b45309";
} else {
verdict = "当前账号大概率有邀请资格,可以尝试发送邀请。";
verdictColor = "#047857";
}
const summary = {
plan_type: plan,
reset_cards_available:
usage.data?.rate_limit_reset_credits?.available_count ??
resetCredits.data?.available_count ??
"unknown",
invites_sent: timeRule.invites_sent ?? "unknown",
invites_total: timeRule.invites_total ?? "unknown",
invite_remaining: remaining,
time_frame: timeRule.time_frame ?? "unknown",
eligibility_http: eligibility.status,
should_show: typeof shouldShow === "boolean" ? shouldShow : "unknown",
verdict,
};
const rawSanitized = JSON.parse(JSON.stringify({
account,
usage,
rules,
eligibility,
resetCredits,
}, (k, v) => /token|secret|authorization|cookie|email|url|link|id/i.test(k) ? "[redacted]" : v));
const css = {
title: "font-size:18px;font-weight:800;color:#111827;margin:6px 0;",
verdict: `font-size:15px;font-weight:800;color:${verdictColor};`,
label: "color:#6b7280;font-weight:700;",
value: "color:#111827;font-weight:800;",
muted: "color:#6b7280;",
line: "color:#d1d5db;",
};
console.log("%cCodex 邀请资格检测结果", css.title);
console.log("%c%s", css.verdict, verdict);
console.log("%c────────────────────────────────────", css.line);
console.log("%c当前套餐 %c%s", css.label, css.value, summary.plan_type);
console.log("%c重置卡数量 %c%s", css.label, css.value, summary.reset_cards_available);
console.log(
"%c邀请额度 %c已用 %s / 总计 %s / 剩余 %s",
css.label,
css.value,
summary.invites_sent,
summary.invites_total,
summary.invite_remaining
);
console.log("%c统计周期 %c%s", css.label, css.value, summary.time_frame);
console.log("%cEligibility %cHTTP %s", css.label, css.value, summary.eligibility_http);
console.log("%c前端 should_show %c%s", css.label, css.value, summary.should_show);
console.log("%c────────────────────────────────────", css.line);
console.log("%c结论说明", css.label);
console.log(
"%c%s",
css.muted,
"invite_remaining > 0 只代表规则额度未用完;真实能否发送还取决于 plan 和服务端 eligibility。"
);
console.groupCollapsed("完整响应,已脱敏");
console.dir(rawSanitized);
console.groupEnd();
return summary;
})();
邀请详情
(async () => {
if (location.hostname !== "chatgpt.com") throw new Error("请在 https://chatgpt.com 执行");
const referralKey = "codex_referral_persistent_invite";
const timeZone = "Asia/Shanghai";
const privacyMode = true;
const session = await (await fetch("/api/auth/session", {
credentials: "include",
headers: { accept: "application/json" },
})).json();
const token = session.accessToken;
if (!token) throw new Error("无法获取 accessToken,请确认已登录 ChatGPT");
async function get(path) {
const res = await fetch(path, {
credentials: "include",
headers: { accept: "application/json", authorization: `Bearer ${token}` },
});
const text = await res.text();
let data;
try { data = JSON.parse(text); } catch { data = text.slice(0, 300); }
return { status: res.status, ok: res.ok, data };
}
const [account, usage, rules, eligibility, resetCredits] = await Promise.all([
get("/backend-api/wham/accounts/check"),
get("/backend-api/wham/usage"),
get(`/backend-api/wham/referrals/eligibility_rules?referral_key=${encodeURIComponent(referralKey)}`),
get(`/backend-api/referrals/invite/eligibility?referral_key=${encodeURIComponent(referralKey)}`),
get("/backend-api/wham/rate-limit-reset-credits"),
]);
const esc = (v) => String(v ?? "").replace(/[&<>"']/g, m => ({
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
}[m]));
const mask = (s) => {
s = String(s ?? "");
if (!privacyMode) return s;
return s
.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[email]")
.replace(/\b\d{7,}\b/g, v => `尾号 ${v.slice(-4)}`);
};
const fmt = (iso) => iso ? new Intl.DateTimeFormat("zh-CN", {
timeZone,
year: "numeric", month: "2-digit", day: "2-digit",
hour: "2-digit", minute: "2-digit", second: "2-digit",
hour12: false,
}).format(new Date(iso)).replace(/\//g, "-") : "";
const sourceOf = (c) => {
const text = `${c.title ?? ""} ${c.description ?? ""}`.toLowerCase();
if (/invite|referral|邀请/.test(text)) return mask(c.description || "邀请奖励");
if (/codex team|free|official|官方/.test(text)) return "官方免费赠送";
return mask(c.description || c.title || c.reset_type || "未知来源");
};
const acct = account.data?.accounts?.[0] ?? {};
const plan = usage.data?.plan_type ?? acct.plan_type ?? "unknown";
const rule = rules.data?.time_frame_rules?.[0] ?? {};
const sent = Number(rule.invites_sent ?? 0);
const total = Number(rule.invites_total ?? 0);
const remaining = Math.max(total - sent, 0);
const shouldShow = eligibility.data?.should_show;
const summary = {
plan,
resetCards: usage.data?.rate_limit_reset_credits?.available_count ?? resetCredits.data?.available_count ?? "unknown",
invitesSent: rule.invites_sent ?? "unknown",
invitesTotal: rule.invites_total ?? "unknown",
inviteRemaining: remaining,
timeFrame: rule.time_frame ?? "unknown",
eligibilityHttp: eligibility.status,
shouldShow: typeof shouldShow === "boolean" ? shouldShow : "unknown",
};
const rows = (resetCredits.data?.credits ?? []).map((c, i) => ({
序号: i + 1,
来源: sourceOf(c),
状态: c.status ?? "",
领取时间: fmt(c.granted_at),
过期时间: fmt(c.expires_at),
}));
const verdict =
remaining <= 0 ? "当前周期邀请额度已用完"
: plan === "free" ? "有规则额度,但 Free 可能被套餐拦截"
: shouldShow === false ? "有规则额度,但前端 eligibility 不显示"
: "大概率具备邀请资格";
document.getElementById("codex-invite-report")?.remove();
const box = document.createElement("div");
box.id = "codex-invite-report";
box.style = "position:fixed;right:20px;top:20px;z-index:999999;width:760px;max-height:80vh;overflow:auto;background:#111827;color:#f9fafb;border:1px solid #374151;border-radius:10px;box-shadow:0 20px 50px #0008;font:14px system-ui;padding:18px;";
box.innerHTML = `
<div style="display:flex;justify-content:space-between;gap:12px;align-items:center">
<div style="font-size:18px;font-weight:800">Codex 邀请资格详情</div>
<button onclick="this.closest('#codex-invite-report').remove()" style="background:#374151;color:white;border:0;border-radius:6px;padding:6px 10px;cursor:pointer">关闭</button>
</div>
<div style="margin:10px 0 16px;color:${remaining > 0 && plan !== "free" && shouldShow !== false ? "#34d399" : "#fbbf24"};font-weight:800">${esc(verdict)}</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:16px">
${[
["套餐", summary.plan],
["重置卡", summary.resetCards],
["邀请额度", `${summary.invitesSent}/${summary.invitesTotal}`],
["剩余邀请", summary.inviteRemaining],
["周期", summary.timeFrame],
["Eligibility", `HTTP ${summary.eligibilityHttp}`],
["should_show", summary.shouldShow],
].map(([k,v]) => `<div style="background:#1f2937;border-radius:8px;padding:10px"><div style="color:#9ca3af;font-size:12px">${esc(k)}</div><div style="font-weight:800;margin-top:4px">${esc(v)}</div></div>`).join("")}
</div>
<table style="width:100%;border-collapse:collapse">
<thead><tr>${["序号","来源","状态","领取时间","过期时间"].map(h => `<th style="text-align:left;border-bottom:1px solid #374151;padding:8px;color:#d1d5db">${h}</th>`).join("")}</tr></thead>
<tbody>${rows.map(r => `<tr>${Object.values(r).map(v => `<td style="border-bottom:1px solid #1f2937;padding:8px">${esc(v)}</td>`).join("")}</tr>`).join("")}</tbody>
</table>
`;
document.body.appendChild(box);
console.log("Codex 邀请资格摘要:", summary);
console.table(rows);
})();

网页端直接邀请不需要打开 Codex 的方法
(async () => {
const PANEL_ID = "codex-referral-panel";
const REFERRAL_KEY = "codex_referral_persistent_invite";
const API_BASE = "/backend-api";
document.getElementById(PANEL_ID)?.remove();
if (location.hostname !== "chatgpt.com") {
throw new Error("请先打开 https://chatgpt.com,再在该页面的 F12 Console 中运行。");
}
const panel = document.createElement("section");
panel.id = PANEL_ID;
panel.innerHTML = `
<style>
#${PANEL_ID} {
position: fixed;
z-index: 2147483647;
right: 20px;
bottom: 20px;
width: min(420px, calc(100vw - 32px));
color: #171717;
background: #fff;
border: 1px solid #d4d4d4;
border-radius: 8px;
box-shadow: 0 16px 48px rgba(0, 0, 0, .22);
font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
overflow: hidden;
}
#${PANEL_ID} * { box-sizing: border-box; }
#${PANEL_ID} header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
color: #fff;
background: #171717;
}
#${PANEL_ID} h2 {
margin: 0;
font-size: 15px;
font-weight: 650;
letter-spacing: 0;
}
#${PANEL_ID} .close {
width: 28px;
height: 28px;
padding: 0;
color: #fff;
background: transparent;
border: 0;
font-size: 20px;
cursor: pointer;
}
#${PANEL_ID} .body { padding: 16px; }
#${PANEL_ID} .summary {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 14px;
}
#${PANEL_ID} .metric {
padding: 9px 10px;
background: #f5f5f5;
border: 1px solid #e5e5e5;
border-radius: 6px;
}
#${PANEL_ID} .metric span {
display: block;
color: #737373;
font-size: 12px;
}
#${PANEL_ID} .metric strong {
display: block;
margin-top: 2px;
font-size: 14px;
overflow-wrap: anywhere;
}
#${PANEL_ID} label {
display: block;
margin-bottom: 6px;
font-weight: 600;
}
#${PANEL_ID} input {
width: 100%;
height: 40px;
padding: 0 11px;
color: #171717;
background: #fff;
border: 1px solid #a3a3a3;
border-radius: 6px;
outline: none;
}
#${PANEL_ID} input:focus {
border-color: #171717;
box-shadow: 0 0 0 2px rgba(23, 23, 23, .12);
}
#${PANEL_ID} .actions {
display: flex;
gap: 8px;
margin-top: 10px;
}
#${PANEL_ID} button.action {
height: 38px;
padding: 0 13px;
border: 1px solid #a3a3a3;
border-radius: 6px;
background: #fff;
cursor: pointer;
font-weight: 600;
}
#${PANEL_ID} button.primary {
flex: 1;
color: #fff;
background: #171717;
border-color: #171717;
}
#${PANEL_ID} button:disabled {
cursor: not-allowed;
opacity: .5;
}
#${PANEL_ID} .status {
min-height: 82px;
max-height: 220px;
margin: 14px 0 0;
padding: 10px;
overflow: auto;
white-space: pre-wrap;
overflow-wrap: anywhere;
color: #262626;
background: #fafafa;
border: 1px solid #e5e5e5;
border-radius: 6px;
font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
#${PANEL_ID} .ok { color: #166534; }
#${PANEL_ID} .error { color: #b91c1c; }
</style>
<header>
<h2>Codex 邀请检查与发送</h2>
<button class="close" title="关闭" aria-label="关闭">×</button>
</header>
<div class="body">
<div class="summary">
<div class="metric"><span>网页当前套餐</span><strong data-field="plan">读取中</strong></div>
<div class="metric"><span>本月邀请</span><strong data-field="quota">读取中</strong></div>
<div class="metric"><span>重置卡</span><strong data-field="credits">读取中</strong></div>
<div class="metric"><span>账号 ID</span><strong data-field="account">读取中</strong></div>
</div>
<label for="codex-referral-email">受邀邮箱</label>
<input id="codex-referral-email" type="email" autocomplete="off"
placeholder="[email protected]">
<div class="actions">
<button class="action" data-action="refresh">刷新状态</button>
<button class="action primary" data-action="send" disabled>发送邀请</button>
</div>
<pre class="status">正在读取当前网页会话…</pre>
</div>
`;
document.body.appendChild(panel);
const $ = (selector) => panel.querySelector(selector);
const planEl = $('[data-field="plan"]');
const quotaEl = $('[data-field="quota"]');
const creditsEl = $('[data-field="credits"]');
const accountEl = $('[data-field="account"]');
const emailEl = $("#codex-referral-email");
const refreshButton = $('[data-action="refresh"]');
const sendButton = $('[data-action="send"]');
const statusEl = $(".status");
let token = "";
let accountId = "";
let busy = false;
const setStatus = (message, type = "") => {
statusEl.className = `status ${type}`.trim();
statusEl.textContent = message;
};
const setBusy = (value) => {
busy = value;
refreshButton.disabled = value;
emailEl.disabled = value;
sendButton.disabled = value || !token || !accountId;
};
const parseResponse = async (response) => {
const contentType = response.headers.get("content-type") || "";
const text = await response.text();
if (contentType.includes("application/json")) {
try {
return text ? JSON.parse(text) : null;
} catch {
return { detail: "服务器返回了无法解析的 JSON", raw: text.slice(0, 500) };
}
}
return {
detail: text
? `服务器返回非 JSON 内容:${text.slice(0, 300)}`
: "服务器没有返回内容",
};
};
const api = async (path, options = {}, useAccount = true) => {
const response = await fetch(`${API_BASE}${path}`, {
...options,
credentials: "include",
headers: {
accept: "application/json",
authorization: `Bearer ${token}`,
...(useAccount && accountId ? { "chatgpt-account-id": accountId } : {}),
...(options.body ? { "content-type": "application/json" } : {}),
...(options.headers || {}),
},
});
return {
ok: response.ok,
status: response.status,
data: await parseResponse(response),
};
};
const getSession = async () => {
const response = await fetch("/api/auth/session", {
credentials: "include",
headers: { accept: "application/json" },
});
const session = await parseResponse(response);
token = session?.accessToken || session?.access_token || "";
if (!response.ok || !token) {
throw new Error("网页会话中没有 Access Token。请确认当前 chatgpt.com 页面已登录,然后刷新页面重试。");
}
};
const selectAccount = async () => {
const result = await api("/wham/accounts/check", {}, false);
if (!result.ok) {
throw new Error(`读取账号失败:HTTP ${result.status} ${result.data?.detail || ""}`.trim());
}
accountId =
result.data?.default_account_id ||
result.data?.account_ordering?.[0] ||
result.data?.accounts?.[0]?.id ||
"";
if (!accountId) {
throw new Error("服务器没有返回可用的 ChatGPT 账号 ID。");
}
};
const refresh = async (message = "正在刷新当前网页账号状态…") => {
setBusy(true);
setStatus(message);
try {
await getSession();
await selectAccount();
const [usage, rules, frontendEligibility] = await Promise.all([
api("/wham/usage"),
api(`/wham/referrals/eligibility_rules?referral_key=${encodeURIComponent(REFERRAL_KEY)}`),
api(`/referrals/invite/eligibility?referral_key=${encodeURIComponent(REFERRAL_KEY)}`),
]);
if (!usage.ok) {
throw new Error(`读取套餐失败:HTTP ${usage.status} ${usage.data?.detail || ""}`.trim());
}
if (!rules.ok) {
throw new Error(`读取邀请额度失败:HTTP ${rules.status} ${rules.data?.detail || ""}`.trim());
}
const frame = rules.data?.time_frame_rules?.[0] || {};
const sent = Number(frame.invites_sent);
const total = Number(frame.invites_total);
const remaining =
Number.isFinite(sent) && Number.isFinite(total) ? Math.max(0, total - sent) : null;
planEl.textContent = usage.data?.plan_type || "未知";
quotaEl.textContent =
remaining === null ? "未知" : `${sent}/${total},剩余 ${remaining}`;
creditsEl.textContent =
usage.data?.rate_limit_reset_credits?.available_count ?? "未知";
accountEl.textContent = accountId;
const eligibilityLine = frontendEligibility.ok
? "网页 eligibility:HTTP 200"
: `网页 eligibility:HTTP ${frontendEligibility.status}(仅供诊断,不阻止发送)`;
setStatus(
[
"状态读取完成。",
`套餐:${planEl.textContent}`,
`邀请:${quotaEl.textContent}`,
`重置卡:${creditsEl.textContent}`,
eligibilityLine,
"",
"最终资格以发送接口 /wham/referrals/invite 的实际响应为准。",
].join("\n"),
"ok",
);
} catch (error) {
token = "";
accountId = "";
planEl.textContent = "读取失败";
quotaEl.textContent = "读取失败";
creditsEl.textContent = "读取失败";
accountEl.textContent = "读取失败";
setStatus(error instanceof Error ? error.message : String(error), "error");
} finally {
setBusy(false);
}
};
const sendInvite = async () => {
if (busy) return;
const email = emailEl.value.trim();
if (!emailEl.checkValidity() || !email) {
emailEl.reportValidity();
return;
}
const confirmed = window.confirm(
`确认使用当前网页账号发送 1 次 Codex 邀请?\n\n收件邮箱:${email}\n账号 ID:${accountId}`,
);
if (!confirmed) return;
setBusy(true);
setStatus(`正在向 ${email} 发送邀请…`);
try {
// 发送前重新读取网页会话,避免使用已经过期的 Token。
await getSession();
await selectAccount();
const result = await api("/wham/referrals/invite", {
method: "POST",
body: JSON.stringify({
referral_key: REFERRAL_KEY,
emails: [email],
}),
});
const detail =
result.data?.detail ||
result.data?.message ||
(result.ok ? "服务器已接受邀请请求" : "服务器拒绝了邀请请求");
setStatus(
[
result.ok ? "邀请发送成功。" : "邀请发送失败。",
`HTTP:${result.status}`,
`结果:${detail}`,
result.data?.email ? `服务器识别邮箱:${result.data.email}` : "",
result.data?.invites?.[0]?.invite_url
? `邀请链接:${result.data.invites[0].invite_url}`
: "",
"",
"完整响应已打印到 Console,但不包含 Access Token。",
]
.filter(Boolean)
.join("\n"),
result.ok ? "ok" : "error",
);
console.log("Codex 邀请响应:", {
http_status: result.status,
ok: result.ok,
response: result.data,
});
if (result.ok) {
await refresh("发送成功,正在重新读取邀请额度…");
}
} catch (error) {
setStatus(error instanceof Error ? error.message : String(error), "error");
} finally {
setBusy(false);
}
};
$(".close").addEventListener("click", () => panel.remove());
refreshButton.addEventListener("click", () => refresh());
sendButton.addEventListener("click", sendInvite);
await refresh();
})();
运行后你的网页应该有一个窗口
