您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
注入自定义模型到 Google AI Studio。拦截 XHR/Fetch 请求处理模型列表,并拦截 CountTokens 请求以使用最新的 Gemini 2.5 Pro 06-05 模型进行计算。
// ==UserScript== // @name Google AI Studio 模型注入器(多模型版)- 修复Token计算 // @namespace http://tampermonkey.net/ // @version 1.6.8 // @description 注入自定义模型到 Google AI Studio。拦截 XHR/Fetch 请求处理模型列表,并拦截 CountTokens 请求以使用最新的 Gemini 2.5 Pro 06-05 模型进行计算。 // @author Generated by AI / HCPTangHY / Mozi / wisdgod / UserModified / Z_06 (Token Fix) / AI (Emoji Update) // @match https://aistudio.google.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=aistudio.google.com // @grant none // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; // ==================== 配置区域 ==================== const SCRIPT_VERSION = "v1.6.8"; const LOG_PREFIX = `[AI Studio 注入器 ${SCRIPT_VERSION}]`; const ANTI_HIJACK_PREFIX = ")]}'\n"; // --- Token 计算备用模型配置 --- // 已更新为最新的 Gemini 2.5 Pro 06-05 版本 const TOKEN_COUNT_FALLBACK_MODEL = 'models/gemini-2.5-pro-preview-06-05'; // 模型配置列表 const MODELS_TO_INJECT = [ // --- 乱码模型组 (按性能排序) --- { name: 'models/jfdksal98a', displayName: `💎 jfdksal98a (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/68zkqbz8vs', displayName: `🔮 68zkqbz8vs (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/a24bo28u1a', displayName: `⚡ a24bo28u1a (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/2vmc1bo4ri', displayName: `🚀 2vmc1bo4ri (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/42fc3y4xfsz', displayName: `🤖 42fc3y4xfsz (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/ixqzem8yj4j', displayName: `⚙️ ixqzem8yj4j (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/oiy9yghoam', displayName: `🔧 oiy9yghoam (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, // --- 其他已知模型 --- { name: 'models/blacktooth-ab-test', displayName: `🏴☠️ Blacktooth (AB-Test) (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/gemini-2.5-pro-preview-03-25', displayName: `✨ Gemini 2.5 Pro 03-25 (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/goldmane-ab-test', displayName: `🦁 Goldmane (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/claybrook-ab-test', displayName: `💧 Claybrook (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/frostwind-ab-test', displayName: `❄️ Frostwind (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` }, { name: 'models/calmriver-ab-test', displayName: `🌊 Calmriver (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 注入的模型` } ]; // JSON 结构中的字段索引 const MODEL_FIELDS = { NAME: 0, DISPLAY_NAME: 3, DESCRIPTION: 4, METHODS: 7 }; // ==================== 工具函数 ==================== /** * 检查 URL 是否为 ListModels API 端点 * @param {string} url - 要检查的 URL * @returns {boolean} */ function isListModelsURL(url) { return url && typeof url === 'string' && url.includes('alkalimakersuite') && url.includes('/ListModels'); } /** * 检查 URL 是否为 CountTokens API 端点 * @param {string} url - 要检查的 URL * @returns {boolean} */ function isCountTokensURL(url) { return url && typeof url === 'string' && url.includes('alkalimakersuite') && url.includes('/CountTokens'); } /** * 递归查找模型列表数组 * @param {any} obj - 要搜索的对象 * @returns {Array|null} 找到的模型数组或 null */ function findModelListArray(obj) { if (!obj) return null; // 检查是否为目标模型数组 if (Array.isArray(obj) && obj.length > 0 && obj.every( item => Array.isArray(item) && typeof item[MODEL_FIELDS.NAME] === 'string' && String(item[MODEL_FIELDS.NAME]).startsWith('models/') )) { return obj; } // 递归搜索子对象 if (typeof obj === 'object') { for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key) && typeof obj[key] === 'object' && obj[key] !== null) { const result = findModelListArray(obj[key]); if (result) return result; } } } return null; } /** * 查找合适的模板模型 * @param {Array} modelsArray - 模型数组 * @returns {Array|null} 模板模型或 null */ function findTemplateModel(modelsArray) { // 优先查找包含特定关键词的模型 const templateModel = modelsArray.find(m => Array.isArray(m) && m[MODEL_FIELDS.NAME] && String(m[MODEL_FIELDS.NAME]).includes('pro') && Array.isArray(m[MODEL_FIELDS.METHODS])) || modelsArray.find(m => Array.isArray(m) && m[MODEL_FIELDS.NAME] && String(m[MODEL_FIELDS.NAME]).includes('flash') && Array.isArray(m[MODEL_FIELDS.METHODS])) || modelsArray.find(m => Array.isArray(m) && m[MODEL_FIELDS.NAME] && Array.isArray(m[MODEL_FIELDS.METHODS])); return templateModel; } /** * 更新已存在模型的显示名称 * @param {Array} existingModel - 现有模型 * @param {Object} modelToInject - 要注入的模型配置 * @returns {boolean} 是否进行了更新 */ function updateExistingModel(existingModel, modelToInject) { if (!existingModel || existingModel[MODEL_FIELDS.DISPLAY_NAME] === modelToInject.displayName) { return false; } // 提取基础名称(去除版本号和表情) const cleanName = (name) => String(name) .replace(/ \(脚本 v\d+\.\d+(\.\d+)?(-beta\d*)?\)/, '') // 包含所有当前使用的表情,包括新增的 🔮💎⚡🚀🤖⚙️🔧 .replace(/^[✨🦁💧❄️🌊🐉🏴☠️🔮💎⚡🚀🤖⚙️🔧]\s*/, '') .trim(); const baseExistingName = cleanName(existingModel[MODEL_FIELDS.DISPLAY_NAME]); const baseInjectName = cleanName(modelToInject.displayName); if (baseExistingName === baseInjectName) { // 仅更新版本号和表情 existingModel[MODEL_FIELDS.DISPLAY_NAME] = modelToInject.displayName; console.log(LOG_PREFIX, `已更新表情/版本号: ${modelToInject.displayName}`); return true; } // 如果是官方模型和注入模型名称不一致但模型ID相同的情况,可以考虑保留官方名称或标记 // 但对于乱码模型,通常它们不会出现在官方列表中,所以这部分逻辑可以简化或忽略 return false; } /** * 创建新模型 * @param {Array} templateModel - 模板模型 * @param {Object} modelToInject - 要注入的模型配置 * @param {string} templateName - 模板名称 * @returns {Array} 新模型数组 */ function createNewModel(templateModel, modelToInject, templateName) { const newModel = structuredClone(templateModel); newModel[MODEL_FIELDS.NAME] = modelToInject.name; newModel[MODEL_FIELDS.DISPLAY_NAME] = modelToInject.displayName; newModel[MODEL_FIELDS.DESCRIPTION] = `${modelToInject.description} (基于 ${templateName} 结构)`; // 确保 methods 存在,这让 UI 认为它可以调用 countTokens if (!Array.isArray(newModel[MODEL_FIELDS.METHODS])) { newModel[MODEL_FIELDS.METHODS] = [ "generateContent", "countTokens", // 即使后端不支持,我们也在这里声明支持,以便后续拦截 "createCachedContent", "batchGenerateContent" ]; } else if (!newModel[MODEL_FIELDS.METHODS].includes("countTokens")) { newModel[MODEL_FIELDS.METHODS].push("countTokens"); } return newModel; } // ==================== 响应处理函数 (用于 ListModels) ==================== /** * 处理并修改 ListModels 返回的 JSON 数据 * @param {Object} jsonData - 原始 JSON 数据 * @param {string} url - 请求 URL * @returns {Object} 包含处理后数据和修改标志的对象 */ function processListModelsResponse(jsonData, url) { let modificationMade = false; const modelsArray = findModelListArray(jsonData); if (!modelsArray || !Array.isArray(modelsArray)) { console.warn(LOG_PREFIX, '在 JSON 中未找到有效的模型列表结构:', url); return { data: jsonData, modified: false }; } // 查找模板模型 const templateModel = findTemplateModel(modelsArray); const templateName = templateModel?.[MODEL_FIELDS.NAME] || 'unknown'; if (!templateModel) { console.warn(LOG_PREFIX, '未找到合适的模板模型,无法注入新模型'); } // 反向遍历以保持显示顺序 (配置中靠前的模型显示在最上面) [...MODELS_TO_INJECT].reverse().forEach(modelToInject => { const existingModel = modelsArray.find( model => Array.isArray(model) && model[MODEL_FIELDS.NAME] === modelToInject.name ); if (!existingModel) { // 注入新模型 if (!templateModel) { console.warn(LOG_PREFIX, `无法注入 ${modelToInject.name}:缺少模板`); return; } const newModel = createNewModel(templateModel, modelToInject, templateName); modelsArray.unshift(newModel); // unshift 将模型添加到数组开头 modificationMade = true; console.log(LOG_PREFIX, `成功注入: ${modelToInject.displayName}`); } else { // 更新现有模型 if (updateExistingModel(existingModel, modelToInject)) { modificationMade = true; } } }); return { data: jsonData, modified: modificationMade }; } /** * 修改 ListModels 响应体 * @param {string} originalText - 原始响应文本 * @param {string} url - 请求 URL * @returns {string} 修改后的响应文本 */ function modifyListModelsResponseBody(originalText, url) { if (!originalText || typeof originalText !== 'string') { return originalText; } try { let textBody = originalText; let hasPrefix = false; // 处理反劫持前缀 if (textBody.startsWith(ANTI_HIJACK_PREFIX)) { textBody = textBody.substring(ANTI_HIJACK_PREFIX.length); hasPrefix = true; } if (!textBody.trim()) return originalText; const jsonData = JSON.parse(textBody); const result = processListModelsResponse(jsonData, url); if (result.modified) { let newBody = JSON.stringify(result.data); if (hasPrefix) { newBody = ANTI_HIJACK_PREFIX + newBody; } return newBody; } } catch (error) { console.error(LOG_PREFIX, '处理 ListModels 响应体时出错:', url, error); } return originalText; } // ==================== 请求处理函数 (用于 CountTokens) ==================== /** * 修改 CountTokens 请求体,替换模型名称 * @param {string} originalBody - 原始请求体 (JSON 字符串) * @returns {Object} 包含修改后请求体和修改标志的对象 */ function modifyCountTokensRequestBody(originalBody) { try { const payload = JSON.parse(originalBody); // AI Studio 的请求负载结构通常是一个数组: [ "models/the-model-name", { ...content... } ] if (Array.isArray(payload) && typeof payload[0] === 'string' && payload[0].startsWith('models/')) { const requestedModel = payload[0]; // 检查请求的模型是否是我们注入的隐藏模型之一 const isHiddenModel = MODELS_TO_INJECT.some(m => m.name === requestedModel); if (isHiddenModel && requestedModel !== TOKEN_COUNT_FALLBACK_MODEL) { console.log(LOG_PREFIX, `[Token Count 拦截] 将请求模型从 ${requestedModel} 重定向到 ${TOKEN_COUNT_FALLBACK_MODEL}`); payload[0] = TOKEN_COUNT_FALLBACK_MODEL; // 替换为备用模型 return { body: JSON.stringify(payload), modified: true }; } } } catch (e) { console.error(LOG_PREFIX, "解析或修改 CountTokens 请求体时出错:", e); } return { body: originalBody, modified: false }; } // ==================== 请求/响应拦截 ==================== // 拦截 Fetch API const originalFetch = window.fetch; window.fetch = async function(...args) { const resource = args[0]; const url = (resource instanceof Request) ? resource.url : String(resource); // --- 拦截 1: 出站请求 (CountTokens) --- // AI Studio 主要使用 XHR,Fetch 拦截作为备用 // 执行原始 Fetch const response = await originalFetch.apply(this, args); // --- 拦截 2: 入站响应 (ListModels) --- if (isListModelsURL(url) && response.ok) { console.log(LOG_PREFIX, '[Fetch] 拦截到 ListModels 响应:', url); try { const cloneResponse = response.clone(); const originalText = await cloneResponse.text(); const newBody = modifyListModelsResponseBody(originalText, url); if (newBody !== originalText) { return new Response(newBody, { status: response.status, statusText: response.statusText, headers: response.headers }); } } catch (e) { console.error(LOG_PREFIX, '[Fetch] 处理 ListModels 响应错误:', e); } } return response; }; // 拦截 XMLHttpRequest const xhrProto = XMLHttpRequest.prototype; const originalOpen = xhrProto.open; const originalSend = xhrProto.send; // 保存原始 send 方法 const originalResponseTextDescriptor = Object.getOwnPropertyDescriptor(xhrProto, 'responseText'); const originalResponseDescriptor = Object.getOwnPropertyDescriptor(xhrProto, 'response'); let listModelsInterceptionCount = 0; // 重写 open 方法 (记录请求类型) xhrProto.open = function(method, url) { this._interceptorUrl = url; this._isListModelsXHR = isListModelsURL(url); // 标记是否为 ListModels 请求 this._isCountTokensXHR = isCountTokensURL(url); // 标记是否为 CountTokens 请求 if (this._isListModelsXHR) { listModelsInterceptionCount++; console.log(LOG_PREFIX, `[XHR Open] 检测到 ListModels 请求 (${listModelsInterceptionCount}):`, url); } return originalOpen.apply(this, arguments); }; // 重写 send 方法 (修改 CountTokens 请求) xhrProto.send = function(data) { // 检查是否为 CountTokens 请求,并且有数据体 if (this._isCountTokensXHR && data && typeof data === 'string') { const { body: modifiedData, modified } = modifyCountTokensRequestBody(data); if (modified) { // Log已经在modifyCountTokensRequestBody中打印 } return originalSend.call(this, modifiedData); } // 对于其他请求,正常发送 return originalSend.call(this, data); }; /** * 处理 XHR 响应 (修改 ListModels 响应) * @param {XMLHttpRequest} xhr - XHR 对象 * @param {any} originalValue - 原始响应值 * @param {string} type - 响应类型 * @returns {any} 处理后的响应值 */ const handleXHRResponse = (xhr, originalValue, type = 'text') => { // 只处理 ListModels 的响应 if (!xhr._isListModelsXHR || xhr.readyState !== 4 || xhr.status !== 200) { return originalValue; } const cacheKey = '_modifiedResponseCache_' + type; if (xhr[cacheKey] === undefined) { const originalText = (type === 'text' || typeof originalValue !== 'object' || originalValue === null) ? String(originalValue || '') : JSON.stringify(originalValue); // 使用修改 ListModels 响应的函数 xhr[cacheKey] = modifyListModelsResponseBody(originalText, xhr._interceptorUrl); } const cachedResponse = xhr[cacheKey]; try { if (type === 'json' && typeof cachedResponse === 'string') { const textToParse = cachedResponse.replace(ANTI_HIJACK_PREFIX, ''); return textToParse ? JSON.parse(textToParse) : null; } } catch (e) { console.error(LOG_PREFIX, '[XHR] 解析缓存的 JSON 时出错:', e); return originalValue; } return cachedResponse; }; // 重写 responseText 属性 if (originalResponseTextDescriptor?.get) { Object.defineProperty(xhrProto, 'responseText', { get: function() { const originalText = originalResponseTextDescriptor.get.call(this); if (this.responseType && this.responseType !== 'text' && this.responseType !== "") { return originalText; } return handleXHRResponse(this, originalText, 'text'); }, configurable: true }); } // 重写 response 属性 if (originalResponseDescriptor?.get) { Object.defineProperty(xhrProto, 'response', { get: function() { const originalResponse = originalResponseDescriptor.get.call(this); if (this.responseType === 'json') { return handleXHRResponse(this, originalResponse, 'json'); } if (!this.responseType || this.responseType === 'text' || this.responseType === "") { return handleXHRResponse(this, originalResponse, 'text'); } return originalResponse; }, configurable: true }); } console.log(LOG_PREFIX, '脚本已激活。ListModels 响应拦截和 CountTokens 请求拦截已启用。'); })();