// ==UserScript== // @name ChatGPT请求计数器 // @namespace http://tampermonkey.net/ // @version 0.7 // @description 记录ChatGPT各个模型的调用情况 // @author muzi // @icon https://chat.openai.com/favicon.ico // @match https://chat.openai.com/ // @match https://chat.openai.com/?model=* // @match https://chat.openai.com/c/* // @match https://chat.openai.com/g/* // @match https://chat.openai.com/gpts/* // @match https://chatgpt.com/ // @match https://chatgpt.com/?model=* // @match https://chatgpt.com/c/* // @match https://chatgpt.com/g/* // @match https://chatgpt.com/gpts // @match https://chatgpt.com/gpts/* // @match https://chatgpt.com/share/* // @match https://chatgpt.com/share/*/continue // @grant none // @run-at document-end // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/494383/ChatGPT%E8%AF%B7%E6%B1%82%E8%AE%A1%E6%95%B0%E5%99%A8.user.js // @updateURL https://update.greasyfork.icu/scripts/494383/ChatGPT%E8%AF%B7%E6%B1%82%E8%AE%A1%E6%95%B0%E5%99%A8.meta.js // ==/UserScript== /* 1. 监听 fetch 函数调用 当 request_url=https://chat.openai.com/backend-api/conversation && request_method=POST 时,解析 post body 并记录其中的 model 字段 a. 从 localStorage 中读取 model_request_mapping,如果没有则创建一个空的 model_mapping b. 写入数据 key 为 model,value 为每次调用的时间戳列表,存储到 localStorage 中 2. 记录模型调用次数 - 当天所有模型调用次数 - 当天 GPT-4 调用次数 - 剩余 GPT-4 调用次数(比如每3小时50条) 3. 支持导出数据 - 以 json 格式导出 model_request_mapping 数据 4. 支持自定义配置 a. 缓存时间,超过缓存时间的数据自动清理,避免 localStorage 过大 5. 支持记录模型生成的 token 数量 */ /* 2024-04-16 ChangeLog 增加对 auto 模式的统计 */ (function () { "use strict"; const LIMIT_HOURS = 3; // 自定义配置:缓存时间 const GPT4_LIMIT = 40; // 自定义配置:GPT-4 调用限制 const panelHTML = `
`; var originalFetch = window.fetch; console.info("ChatGPTRequestCounter loaded"); window.fetch = function (url, options) { // 重构:将条件逻辑提取到单独的函数中 processRequest(url, options); // 调用原始的fetch函数 return originalFetch.apply(this, arguments); }; function processRequest(url, options) { if ( url.includes("/backend-api/conversation") && options && options.method === "POST" ) { logRequest(options); } else if ( url.includes("discovery.json") || url.includes("gizmos/g-") || url.includes("_next/data") || url.includes("backend-api/conversation") ) { safeUpdateCount(); } else if ( url.endsWith("lat/r") && options && options.method === "POST" ) { logTokenNum(options); } } function logRequest(options) { processData(options, "model_request_mapping"); } function logTokenNum(options) { processData(options, "tokenStatistics", (data) => ({ time: Date.now(), response_tokens: data.count_tokens, })); } function processData( options, storageKey, dataMappingFn = (data) => Date.now() ) { try { const requestData = JSON.parse(options.body); if (requestData.model) { let storageData = JSON.parse( localStorage.getItem(storageKey) || "{}" ); if (!storageData[requestData.model]) { storageData[requestData.model] = []; } storageData[requestData.model].push(dataMappingFn(requestData)); localStorage.setItem(storageKey, JSON.stringify(storageData)); safeUpdateCount(); } } catch (err) { console.error("Error processing request:", err); } } function safeUpdateCount() { try { updateCount(); } catch (err) { console.error("Error logging ChatGPT request:", err); } } function logDailyTokenCount() { const today = new Date().toDateString(); // 获取当天日期字符串 let tokenStatistics = JSON.parse( localStorage.getItem("tokenStatistics") || "{}" ); let dailyTokenCount = {}; for (let model in tokenStatistics) { dailyTokenCount[model] = tokenStatistics[model] .filter( (entry) => new Date(entry.time).toDateString() === today ) // 筛选当天的数据 .reduce((total, entry) => total + entry.response_tokens, 0); // 累加token数量 } return dailyTokenCount; } function countRequests() { let gpt4_recent = 0; let gpt4_today = 0; let all_models_today = 0; let dynamic_today = 0; let dynamic_recent = 0; const cutoffTime = Date.now() - LIMIT_HOURS * 60 * 60 * 1000; const todayCutOffTime = new Date().setHours(0, 0, 0, 0); const originalMapping = JSON.parse( localStorage.getItem("model_request_mapping") || "{}" ); const model_request_mapping = JSON.parse( JSON.stringify(originalMapping) ); // deep copy for (let model in model_request_mapping) { const todayRequests = model_request_mapping[model].filter( (timestamp) => timestamp > todayCutOffTime ); all_models_today += todayRequests.length; if (model.startsWith("gpt-4")) { gpt4_today += todayRequests.length; const recentRequests = todayRequests.filter( (timestamp) => timestamp > cutoffTime ); gpt4_recent += recentRequests.length; } // 新增处理 auto 开头的模型 if (model.startsWith("auto")) { dynamic_today += todayRequests.length; const recentRequests = todayRequests.filter( (timestamp) => timestamp > cutoffTime ); dynamic_recent += recentRequests.length; } } return { all_models_today, gpt4_today, gpt4_recent, dynamic_today, // 新增 dynamic_recent, // 新增 }; } // 插入展示面板 function insertPanel(panelHTML, exportData) { // 避免重复插入 if (document.querySelector("#chatgpt-counter-panel")) { console.log("Panel already inserted."); return; } // 目标选择器 const targetSelector = ".flex.flex-col.pt-2.empty\\:hidden.dark\\:border-white\\/20"; console.log("Looking for profileDiv at:", targetSelector); // 使用 MutationObserver 观察 DOM 变化 const observer = new MutationObserver((mutations, obs) => { const profileDiv = document.querySelector(targetSelector); if (profileDiv) { // 避免重复插入 if (document.querySelector("#chatgpt-counter-panel")) { console.log("Panel already inserted."); obs.disconnect(); // 停止观察 return; } // 插入面板的 HTML console.log("Inserting panel at:", targetSelector); profileDiv.insertAdjacentHTML("afterbegin", panelHTML); // 添加导出按钮的事件监听器 const exportButton = document.getElementById( "chatgpt-counter-panel-export" ); if (exportButton) { exportButton.removeEventListener("click", exportData); exportButton.addEventListener("click", exportData); } // 停止观察 obs.disconnect(); // 延迟更新计数,确保面板已插入 setTimeout(updateCount, 500); } else { console.warn( "Target profileDiv not found. Waiting for further mutations..." ); } }); // 观察整个文档的子节点变化 observer.observe(document, { childList: true, subtree: true, }); } // 更新展示面板数值 function updateCount() { const counts = countRequests(); const all_models_today = counts.all_models_today; const gpt4_today = counts.gpt4_today; const dynamic_today = counts.dynamic_today; const remaining = GPT4_LIMIT - counts.gpt4_recent - counts.dynamic_recent; // 如果面板不存在,插入面板 insertPanel(panelHTML, exportData); // 安全地更新元素的文本内容 const updateTextContent = (id, text) => { const element = document.getElementById(id); if (element) { element.textContent = text; } else { console.error(`Element with ID '${id}' not found. waiting...`); } }; updateTextContent("total", all_models_today); updateTextContent("gpt-4", gpt4_today); updateTextContent("dynamic", dynamic_today); updateTextContent("remain", remaining); } // 创建导出功能 function exportData() { // 获取当天的Token计数 const dailyTokenCount = logDailyTokenCount(); const tokenCountMessage = "今日模型输出统计:\n" + JSON.stringify(dailyTokenCount, null, 2); // 询问用户是否下载请求记录,同时展示Token计数 const userConfirmed = confirm( tokenCountMessage + "\n\n是否下载请求记录?" ); if (userConfirmed) { // 用户选择“确定”,触发下载 const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent( localStorage.getItem("model_request_mapping") ); const downloadAnchorNode = document.createElement("a"); downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute( "download", "model_request_mapping.json" ); document.body.appendChild(downloadAnchorNode); downloadAnchorNode.click(); downloadAnchorNode.remove(); } } // every 5 minutes update count setInterval(() => { updateCount(); }, 5 * 60 * 1000); updateCount(); })();