您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
监控 ChatGPT 服务状态、IP 质量和 PoW 难度
当前为
// ==UserScript== // @name ChatGPT Degraded // @name:zh-CN ChatGPT 服务降级监控 // @namespace https://github.com/lroolle/chatgpt-degraded // @version 0.2.1 // @description Monitor ChatGPT service level, IP quality and PoW difficulty // @description:zh-CN 监控 ChatGPT 服务状态、IP 质量和 PoW 难度 // @author lroolle // @license AGPL-3.0 // @match *://chatgpt.com/* // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDY0IDY0Ij4KICA8ZGVmcz4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojMmE5ZDhmO3N0b3Atb3BhY2l0eToxIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3R5bGU9InN0b3AtY29sb3I6IzJhOWQ4ZjtzdG9wLW9wYWNpdHk6MC44Ii8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8Zz4KICAgIDxjaXJjbGUgY3g9IjMyIiBjeT0iMzIiIHI9IjI4IiBmaWxsPSJ1cmwoI2dyYWRpZW50KSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEiLz4KPCEtLU91dGVyIGNpcmNsZSBtb2RpZmllZCB0byBsb29rIGxpa2UgIkMiLS0+CiAgICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjMyIiByPSIyMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1kYXNoYXJyYXk9IjEyNSA1NSIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjIwIi8+CiAgICA8Y2lyY2xlIGN4PSIzMiIgY3k9IjMyIiByPSIxMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjEiLz4KICAgIDxjaXJjbGUgY3g9IjMyIiBjeT0iMzIiIHI9IjQiIGZpbGw9IiNmZmYiLz4KICA8L2c+Cjwvc3ZnPg== // @grant none // @homepageURL https://github.com/lroolle/chatgpt-degraded // @supportURL https://github.com/lroolle/chatgpt-degraded/issues // ==/UserScript== // Reference: https://github.com/KoriIku/chatgpt-degrade-checker (by KoriIku) (function () { "use strict"; const displayBox = document.createElement("div"); displayBox.style.position = "fixed"; displayBox.style.bottom = "10px"; displayBox.style.right = "80px"; displayBox.style.transform = "none"; displayBox.style.width = "320px"; displayBox.style.padding = "24px"; displayBox.style.backgroundColor = "var(--surface-primary, rgb(255, 255, 255))"; displayBox.style.color = "var(--text-primary, #374151)"; displayBox.style.fontSize = "14px"; displayBox.style.borderRadius = "16px"; displayBox.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)"; displayBox.style.zIndex = "10000"; displayBox.style.transition = "opacity 0.15s ease, transform 0.15s ease"; displayBox.style.display = "none"; displayBox.style.opacity = "0"; displayBox.style.transform = "translateX(10px)"; displayBox.style.border = "1px solid var(--border-light, rgba(0, 0, 0, 0.05))"; displayBox.innerHTML = ` <div id="content"> <div class="monitor-item"> <div class="monitor-row"> <span class="label" title="ChatGPT Account Type">ChatGPT</span> <span id="user-type" class="value"></span> </div> </div> <div class="monitor-item"> <div class="monitor-row"> <span class="label" title="Your IP Address">IP</span> <div class="ip-container"> <span id="ip-address" class="value monospace" title="Click to copy"></span> <span id="warp-badge" class="warp-badge" title=""></span> <span id="ip-quality" class="value-tag"></span> </div> </div> <div class="progress-wrapper" data-tooltip="Risk Level: Indicates the risk associated with your connection. Lower is better, higher may indicate VPN/proxy detection."> <div class="progress-container"> <div id="ip-quality-bar" class="progress-bar"></div> </div> <div class="progress-background"></div> </div> </div> <div class="monitor-item"> <div class="monitor-row"> <span class="label" title="Proof of Work Difficulty">PoW</span> <div class="pow-container"> <span id="difficulty" class="value monospace"></span> <span id="pow-level" class="value-tag"></span> </div> </div> <div class="progress-wrapper" data-tooltip="PoW Difficulty: Required computational work before sending messages. Lower (green) means faster responses."> <div class="progress-container"> <div id="pow-bar" class="progress-bar"></div> </div> <div class="progress-background"></div> </div> </div> <div class="monitor-item"> <div class="monitor-row"> <span class="label" title="OpenAI System Status">Status</span> <a id="status-description" href="https://status.openai.com" target="_blank" class="value"> Checking status... </a> </div> </div> </div> <style> .monitor-item { margin-bottom: 16px; } .monitor-item:last-child { margin-bottom: 0; } .monitor-row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; } .label { font-size: 14px; color: var(--text-secondary, #6B7280); flex-shrink: 0; min-width: 40px; font-weight: 400; } .value { font-size: 14px; color: var(--text-primary, #374151); flex: 1; } .monospace { font-family: 'Menlo', 'Monaco', 'Courier New', monospace; font-size: 14px; } .value-tag { font-size: 14px; color: var(--success-color, #10a37f); white-space: nowrap; font-weight: 500; } .progress-wrapper { position: relative; margin-left: 40px; } .progress-container { position: relative; height: 4px; background: transparent; border-radius: 2px; overflow: hidden; z-index: 1; } .progress-background { position: absolute; top: 0; left: 0; right: 0; height: 4px; background: var(--surface-secondary, rgba(0, 0, 0, 0.08)); border-radius: 2px; } .progress-bar { height: 100%; width: 0%; transition: all 0.3s ease; background: var(--success-color, #10a37f); } #status-description { text-decoration: none; color: inherit; } #status-description:hover { text-decoration: underline; } #ip-address { cursor: pointer; } #ip-address:hover { opacity: 0.7; } #user-type { font-weight: 500; } .ip-container, .pow-container { display: flex; align-items: center; gap: 6px; flex: 1; } .warp-badge { font-size: 12px; color: var(--success-color, #10a37f); background-color: var(--surface-secondary, rgba(16, 163, 127, 0.1)); padding: 2px 4px; border-radius: 4px; font-weight: 500; cursor: help; display: none; } </style>`; document.body.appendChild(displayBox); const collapsedIndicator = document.createElement("div"); collapsedIndicator.style.position = "fixed"; collapsedIndicator.style.bottom = "10px"; collapsedIndicator.style.right = "40px"; collapsedIndicator.style.transform = "none"; collapsedIndicator.style.width = "24px"; collapsedIndicator.style.height = "24px"; collapsedIndicator.style.backgroundColor = "transparent"; collapsedIndicator.style.border = "1px solid var(--token-border-light, rgba(0, 0, 0, 0.1))"; collapsedIndicator.style.borderRadius = "50%"; collapsedIndicator.style.cursor = "pointer"; collapsedIndicator.style.zIndex = "10000"; collapsedIndicator.style.display = "flex"; collapsedIndicator.style.alignItems = "center"; collapsedIndicator.style.justifyContent = "center"; collapsedIndicator.style.transition = "all 0.3s ease"; collapsedIndicator.innerHTML = ` <svg width="24" height="24" viewBox="0 0 64 64"> <defs> <linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%"> <stop offset="0%" style="stop-color:var(--token-text-secondary, #666);stop-opacity:1" /> <stop offset="100%" style="stop-color:var(--token-text-secondary, #666);stop-opacity:0.8" /> </linearGradient> <filter id="glow"> <feGaussianBlur stdDeviation="1" result="coloredBlur"/> <feMerge> <feMergeNode in="coloredBlur"/> <feMergeNode in="SourceGraphic"/> </feMerge> </filter> </defs> <g id="icon-group" filter="url(#glow)" transform="rotate(165, 32, 32)"> <circle cx="32" cy="32" r="28" fill="url(#gradient)" stroke="#fff" stroke-width="1"/> <circle cx="32" cy="32" r="20" fill="none" stroke="#fff" stroke-width="1" stroke-dasharray="80 40" transform="rotate(-90, 32, 32)"> <animate attributeName="r" values="20;22;20" dur="4s" repeatCount="indefinite"/> </circle> <circle cx="32" cy="32" r="12" fill="none" stroke="#fff" stroke-width="1"> <animate attributeName="r" values="12;14;12" dur="4s" repeatCount="indefinite"/> </circle> <circle id="center-dot" cx="32" cy="32" r="4" fill="#fff"> <animate attributeName="r" values="4;6;4" dur="4s" repeatCount="indefinite"/> </circle> </g> </svg>`; document.body.appendChild(collapsedIndicator); collapsedIndicator.addEventListener("mouseenter", () => { displayBox.style.display = "block"; requestAnimationFrame(() => { displayBox.style.opacity = "1"; displayBox.style.transform = "translateX(0)"; }); }); displayBox.addEventListener("mouseleave", () => { displayBox.style.opacity = "0"; displayBox.style.transform = "translateX(10px)"; setTimeout(() => { displayBox.style.display = "none"; }, 150); }); // Add IP masking function function maskIP(ip) { if (!ip || ip === "Unknown") return ip; // For IPv4: 192.168.1.1 -> 192.*.*.1 if (ip.includes(".")) { const parts = ip.split("."); return `${parts[0]}.*.*.${parts[3]}`; } // For IPv6: 2a09:bac5:6248:183c::26a:1a -> 2a09:*:*:183c if (ip.includes(":")) { const parts = ip.split(":"); return `${parts[0]}:*:*:${parts[3]}`; } return ip; } async function fetchIPInfo() { try { const response = await fetch("https://chatgpt.com/cdn-cgi/trace"); const text = await response.text(); const data = text.split("\n").reduce((obj, line) => { const [key, value] = line.split("="); if (key && value) obj[key.trim()] = value.trim(); return obj; }, {}); const ipElement = document.getElementById("ip-address"); const warpBadge = document.getElementById("warp-badge"); const maskedIP = maskIP(data.ip); const fullIP = data.ip || "Unknown"; const warpStatus = data.warp || "off"; ipElement.innerText = maskedIP; // Update WARP badge visibility and content if (warpStatus === "on") { warpBadge.style.display = "inline-flex"; warpBadge.innerText = "warp"; warpBadge.title = "Protected by Cloudflare WARP"; } else if (warpStatus === "plus") { warpBadge.style.display = "inline-flex"; warpBadge.innerText = "warp+"; warpBadge.title = "Protected by Cloudflare WARP+"; } else { warpBadge.style.display = "none"; } // Add tooltips ipElement.title = "Click to copy full IP address"; // Add click to copy functionality const copyHandler = async () => { try { await navigator.clipboard.writeText(fullIP); const originalText = ipElement.innerText; ipElement.innerText = "Copied!"; setTimeout(() => { ipElement.innerText = originalText; }, 1000); } catch (err) { console.error("Failed to copy:", err); } }; ipElement.removeEventListener("click", copyHandler); ipElement.addEventListener("click", copyHandler); } catch (error) { document.getElementById("ip-address").innerText = "Failed to fetch"; document.getElementById("warp-status").innerText = "unknown"; } } function getRiskColorAndLevel(difficulty) { if (!difficulty || difficulty === "N/A") { return { color: "#e63946", level: "Unknown", ipQuality: "Unknown", percentage: 0, }; } // Clean the difficulty hex string and get its length const cleanDifficulty = difficulty.replace("0x", "").replace(/^0+/, ""); const hexLength = cleanDifficulty.length; const numericDifficulty = parseInt(difficulty.replace("0x", ""), 16); // Define risk levels based on hex length // Shorter hex = higher numeric value = lower difficulty = higher risk if (hexLength <= 2) { // 0x00 to 0xFF return { color: "#e63946", // Red level: "Critical", ipQuality: "Very High Risk", percentage: 100, }; } else if (hexLength <= 3) { // 0x100 to 0xFFF return { color: "#FAB12F", // Orange level: "Hard", ipQuality: "High Risk", percentage: 75, }; } else if (hexLength <= 4) { // 0x1000 to 0xFFFF return { color: "#859F3D", // Light Green level: "Medium", ipQuality: "Medium Risk", percentage: 50, }; } else if (hexLength <= 5) { // 0x10000 to 0xFFFFF return { color: "#2a9d8f", // Teal level: "Easy", ipQuality: "Low Risk", percentage: 25, }; } else { // 0x100000 and above return { color: "#4CAF50", // Green level: "Very Easy", ipQuality: "Minimal Risk", percentage: 0, }; } } function updateProgressBars(difficulty) { const powBar = document.getElementById("pow-bar"); const powLevel = document.getElementById("pow-level"); const ipQualityBar = document.getElementById("ip-quality-bar"); const ipQuality = document.getElementById("ip-quality"); const difficultyElement = document.getElementById("difficulty"); const ipAddressElement = document.getElementById("ip-address"); const { color, level, ipQuality: quality, percentage, } = getRiskColorAndLevel(difficulty); // Create gradient based on risk level const gradient = `linear-gradient(90deg, ${color} ${percentage}%, rgba(255, 255, 255, 0.1) ${percentage}% )`; // Update progress bars with title for explanation setProgressBar( powBar, powLevel, percentage, level, gradient, "PoW Difficulty: Required computational work before sending messages. Lower (green) means faster responses.", ); setProgressBar( ipQualityBar, ipQuality, percentage, quality, gradient, "IP Quality: Indicates the risk associated with your IP as assessed by ChatGPT. Lower(green) is better.", ); // Sync colors with text difficultyElement.style.color = color; ipAddressElement.style.color = color; ipQuality.style.color = color; powLevel.style.color = color; // Update the icon color const gradientStops = collapsedIndicator.querySelector("#gradient"); if (gradientStops) { gradientStops.innerHTML = ` <stop offset="0%" style="stop-color:${color};stop-opacity:1" /> <stop offset="100%" style="stop-color:${color};stop-opacity:0.8" /> `; } } function setProgressBar(bar, label, percentage, text, gradient, title) { bar.style.width = "100%"; bar.style.background = gradient; bar.title = title; // Set title for explanation label.innerText = text; label.style.color = "var(--success-color, #10a37f)"; } async function fetchChatGPTStatus() { try { // Create an AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 3000); const response = await fetch( "https://status.openai.com/api/v2/status.json", { signal: controller.signal, }, ); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); const status = data.status; const statusDescription = document.getElementById("status-description"); const statusMonitorItem = statusDescription.closest(".monitor-item"); statusMonitorItem.style.display = "block"; if (status) { const indicator = status.indicator.toLowerCase(); const description = status.description || "All Systems Operational"; const indicatorColors = { none: "#4CAF50", minor: "#FAB12F", major: "#FFA500", critical: "#e63946", }; statusDescription.style.color = indicatorColors[indicator] || "#aaa"; statusDescription.textContent = description; } } catch (error) { console.error("Error fetching ChatGPT status:", error); const statusDescription = document.getElementById("status-description"); const statusMonitorItem = statusDescription.closest(".monitor-item"); statusMonitorItem.style.display = "none"; } } // Update user type display with colors function updateUserType(type) { const userTypeElement = document.getElementById("user-type"); if (type === "plus" || type === "chatgpt-paid" || type.includes("paid")) { userTypeElement.textContent = "Paid"; userTypeElement.style.color = "var(--success-color, #10a37f)"; } else { userTypeElement.textContent = "Free"; userTypeElement.style.color = "var(--success-color, #10a37f)"; } } // Modify the fetch interceptor to update user type const originalFetch = window.fetch; window.fetch = async function (resource, options) { const response = await originalFetch(resource, options); if ( (resource.includes("/backend-api/sentinel/chat-requirements") || resource.includes("backend-anon/sentinel/chat-requirements")) && options.method === "POST" ) { try { const clonedResponse = response.clone(); const data = await clonedResponse.json(); const difficulty = data.proofofwork?.difficulty || "N/A"; const userType = data.persona || "free"; document.getElementById("difficulty").innerText = difficulty; updateUserType(userType); updateProgressBars(difficulty); } catch (error) { console.error("Error processing response:", error); } } return response; }; function updateTheme() { const isDark = document.documentElement.classList.contains("dark") || localStorage.getItem("theme") === "dark"; displayBox.style.backgroundColor = isDark ? "rgba(0, 0, 0, 0.8)" : "rgba(255, 255, 255, 0.9)"; displayBox.style.color = isDark ? "#fff" : "#000"; const labels = displayBox.querySelectorAll(".label"); labels.forEach((label) => { label.style.color = isDark ? "#aaa" : "#666"; }); } // Initialize fetchIPInfo(); fetchChatGPTStatus(); updateTheme(); const statusCheckInterval = 60 * 60 * 1000; let statusCheckTimer = setInterval(fetchChatGPTStatus, statusCheckInterval); document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") { clearInterval(statusCheckTimer); fetchChatGPTStatus(); statusCheckTimer = setInterval(fetchChatGPTStatus, statusCheckInterval); } }); const observer = new MutationObserver(updateTheme); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"], }); document.querySelectorAll("[title]").forEach((element) => { element.addEventListener("mouseenter", () => { element.style.transitionDelay = "0s"; // Remove delay }); }); })();