// ==UserScript== // @name ChatGPT 降智检测 // @namespace https://github.com/EpochTX/OpenAIsm // @version 1.1 // @description ChatGPT 降智一键检测脚本 // @author epochtx // @match https://chatgpt.com/* // @match https://chat.openai.com/* // @grant none // ==/UserScript== (() => { "use strict"; const VERSION = "1.1"; if (window.__CHATGPT_ROUTE_CHECKER_V6__) { return; } window.__CHATGPT_ROUTE_CHECKER_V6__ = true; const nativeFetch = window.fetch.bind(window); const NativeXHR = window.XMLHttpRequest; const nativeXHROpen = NativeXHR.prototype.open; const nativeXHRSend = NativeXHR.prototype.send; const nativeBeacon = navigator.sendBeacon ? navigator.sendBeacon.bind(navigator) : null; const state = { active: false, turnKey: null, requestModel: null, thinkingEffort: null, serverModel: null, isCollapsed: false }; const oldPanel = document.getElementById("__chatgpt_route_checker_panel__"); if (oldPanel) oldPanel.remove(); const oldStyle = document.getElementById("__chatgpt_route_checker_style__"); if (oldStyle) oldStyle.remove(); const styleEl = document.createElement("style"); styleEl.id = "__chatgpt_route_checker_style__"; styleEl.textContent = ` #__chatgpt_route_checker_panel__ { position: fixed; right: 20px; bottom: 20px; z-index: 2147483647; width: 320px; background: rgba(20, 20, 25, 0.85); backdrop-filter: blur(16px) saturate(180%); -webkit-backdrop-filter: blur(16px) saturate(180%); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 16px; color: #f4f4f5; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.04); transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1); overflow: hidden; user-select: none; } #__chatgpt_route_checker_panel__.collapsed { width: auto; border-radius: 28px; cursor: pointer; } .cg-rc-body { padding: 14px; } .cg-rc-verdict { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; border-radius: 10px; font-size: 13px; font-weight: 600; margin-bottom: 12px; } .cg-rc-collapse-mini { background: transparent; border: none; color: inherit; cursor: pointer; font-size: 12px; opacity: 0.7; padding: 0; line-height: 1; transition: opacity 0.15s; } .cg-rc-collapse-mini:hover { opacity: 1; } .cg-rc-card { border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; background: rgba(255, 255, 255, 0.025); border: 1px solid rgba(255, 255, 255, 0.05); } .cg-rc-label { font-size: 10.5px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; } .cg-rc-main-model { font-size: 15px; font-weight: 700; color: #fff; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; word-break: break-all; } .cg-rc-list { display: flex; flex-direction: column; gap: 6px; } .cg-rc-row { display: flex; justify-content: space-between; align-items: center; padding: 4px 8px; background: rgba(255, 255, 255, 0.015); border-radius: 6px; } .cg-rc-row-key { color: #9ca3af; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11px; } .cg-rc-row-val { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11.5px; color: #f3f4f6; max-width: 170px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; } .cg-rc-status-pill { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; font-size: 12px; font-weight: 600; } `; document.head.appendChild(styleEl); const panel = document.createElement("div"); panel.id = "__chatgpt_route_checker_panel__"; document.documentElement.appendChild(panel); function esc(val) { return String(val).replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]); } function pending(val) { return val ? `${esc(val)}` : '-'; } function modelFamily(slug) { const m = String(slug || "").match(/^gpt-(\d+)-(\d+)/i); return m ? `gpt-${m[1]}-${m[2]}` : null; } function verdictInfo() { if (!state.active) { return { text: "等待发送新消息…", icon: "⏳", color: "#9ca3af", bg: "rgba(156,163,175,0.08)", border: "rgba(156,163,175,0.2)" }; } if (!state.requestModel) { return { text: "读取请求中…", icon: "🔎", color: "#38bdf8", bg: "rgba(56,189,248,0.08)", border: "rgba(56,189,248,0.2)" }; } if (!state.serverModel) { return { text: "等待服务端响应…", icon: "🧠", color: "#fbbf24", bg: "rgba(251,191,36,0.08)", border: "rgba(251,191,36,0.2)" }; } if (state.requestModel === state.serverModel) { return { text: "模型一致 (未降智)", icon: "✅", color: "#34d399", bg: "rgba(52,211,153,0.08)", border: "rgba(52,211,153,0.2)" }; } const reqFam = modelFamily(state.requestModel); const srvFam = modelFamily(state.serverModel); if (reqFam && srvFam && reqFam === srvFam) { return { text: "同系列模型 (细分)", icon: "✅", color: "#34d399", bg: "rgba(52,211,153,0.08)", border: "rgba(52,211,153,0.2)" }; } return { text: "模型不一致 (疑似降智)", icon: "🚨", color: "#f87171", bg: "rgba(248,113,113,0.12)", border: "rgba(248,113,113,0.3)" }; } function render() { const v = verdictInfo(); if (state.isCollapsed) { panel.className = "collapsed"; panel.innerHTML = `
${v.icon} ${esc(state.serverModel || state.requestModel || v.text)}
`; panel.onclick = () => { state.isCollapsed = false; render(); }; return; } panel.className = ""; panel.onclick = null; panel.innerHTML = `
${v.icon} ${v.text}
server_ste_metadata.model_slug
${state.serverModel ? `${esc(state.serverModel)}` : '等待捕获…'}
request.model ${pending(state.requestModel)}
request.thinking_effort ${pending(state.thinkingEffort)}
`; const btn = panel.querySelector("#__cg_rc_collapse_btn__"); if (btn) { btn.onclick = (e) => { e.stopPropagation(); state.isCollapsed = true; render(); }; } } function extractTurnKey(req) { try { const msgs = Array.isArray(req?.messages) ? req.messages : []; for (let i = msgs.length - 1; i >= 0; i--) { if (msgs[i]?.author?.role === "user" && msgs[i]?.id) return msgs[i].id; } if (req?.message?.author?.role === "user") return req.message.id || null; } catch {} return null; } function resetTurn(key) { state.active = true; state.turnKey = key || `turn-${Date.now()}`; state.serverModel = null; } function beginOrUpdateTurn(req) { const newKey = extractTurnKey(req); if (!state.active || (newKey && newKey !== state.turnKey)) { resetTurn(newKey); } if (typeof req?.model === "string") state.requestModel = req.model; if (typeof req?.thinking_effort === "string") state.thinkingEffort = req.thinking_effort; render(); } function applyServerMetadata(meta) { if (!meta || typeof meta !== "object") return; if (typeof meta.model_slug === "string") { state.serverModel = meta.model_slug; render(); } } function scanObject(obj, depth = 0, seen = new WeakSet()) { if (obj == null || depth > 16) return; if (typeof obj === "string") { const t = obj.trim(); if ((t.startsWith("{") || t.startsWith("[")) && (t.includes("model_slug") || t.includes("server_ste_metadata"))) { try { scanObject(JSON.parse(t), depth + 1, seen); } catch {} } return; } if (typeof obj !== "object" || seen.has(obj)) return; seen.add(obj); if (obj.type === "server_ste_metadata" && obj.metadata) applyServerMetadata(obj.metadata); if (obj.server_ste_metadata && typeof obj.server_ste_metadata === "object") applyServerMetadata(obj.server_ste_metadata); if (obj.turn_analytics?.server_ste_metadata) applyServerMetadata(obj.turn_analytics.server_ste_metadata); for (const val of Object.values(obj)) { scanObject(val, depth + 1, seen); } } function parseJSONCandidate(text) { if (!text) return; const val = text.trim(); if (!val || val === "[DONE]") return; try { scanObject(JSON.parse(val)); } catch {} } function scanWholeText(text) { if (!text || typeof text !== "string") return; try { scanObject(JSON.parse(text)); } catch {} for (let line of text.split(/\r?\n/)) { line = line.trim(); if (line.startsWith("data:")) line = line.slice(5).trim(); parseJSONCandidate(line); } } async function watchStream(response) { if (!response?.body) { try { scanWholeText(await response.text()); } catch {} return; } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ""; let eventData = []; try { while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); let idx; while ((idx = buffer.indexOf("\n")) !== -1) { let line = buffer.slice(0, idx); buffer = buffer.slice(idx + 1); if (line.endsWith("\r")) line = line.slice(0, -1); if (line === "") { if (eventData.length) { parseJSONCandidate(eventData.join("\n")); eventData = []; } continue; } if (line.startsWith("data:")) { eventData.push(line.slice(5).trimStart()); continue; } const trimmed = line.trim(); if (trimmed.startsWith("{") || trimmed.startsWith("[")) { parseJSONCandidate(trimmed); } } } } catch {} } async function bodyToText(body) { try { if (body == null) return ""; if (typeof body === "string") return body; if (body instanceof Blob) return await body.text(); if (body instanceof URLSearchParams) return body.toString(); if (body instanceof ArrayBuffer) return new TextDecoder().decode(body); if (ArrayBuffer.isView(body)) return new TextDecoder().decode(body); } catch {} return ""; } async function getRequestText(input, init) { if (init?.body != null) return await bodyToText(init.body); if (input instanceof Request) { try { return await input.clone().text(); } catch {} } return ""; } const isConversation = (u, m) => m === "POST" && /\/backend-api\/f\/conversation(?:\?|$)/.test(u); const isTelemetry = (u, m) => m === "POST" && /\/ces\/v1\/telemetry\/intake(?:\?|$)/.test(u); window.fetch = async function(input, init = {}) { const url = typeof input === "string" ? input : input?.url || ""; const method = String(init.method || input?.method || "GET").toUpperCase(); if (isConversation(url, method) || isTelemetry(url, method)) { const text = await getRequestText(input, init); if (isConversation(url, method)) { try { beginOrUpdateTurn(JSON.parse(text)); } catch {} } else { scanWholeText(text); } } const response = await nativeFetch(input, init); if (isConversation(url, method)) { try { watchStream(response.clone()); } catch {} } return response; }; NativeXHR.prototype.open = function(method, url, ...rest) { this.__rcMethod = String(method || "GET").toUpperCase(); this.__rcUrl = String(url || ""); return nativeXHROpen.call(this, method, url, ...rest); }; NativeXHR.prototype.send = function(body) { const url = this.__rcUrl || ""; const method = this.__rcMethod || "GET"; bodyToText(body).then(text => { if (isConversation(url, method)) { try { beginOrUpdateTurn(JSON.parse(text)); } catch {} } else if (isTelemetry(url, method)) { scanWholeText(text); } }); return nativeXHRSend.call(this, body); }; render(); console.log(`ChatGPT Route Checker v${VERSION} 已启动`); })();