// ==UserScript== // @name Google AI Studio 模型注入器 // @namespace http://tampermonkey.net/ // @version 1.7.1 // @description 向 Google AI Studio 注入自定义模型,支持在模型列表中手动添加ID(无需输入 models/)。拦截 XHR/Fetch 请求。 // @author Generated by AI / HCPTangHY / Mozi / wisdgod / UserModified / Yefori // @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 // @downloadURL https://update.greasyfork.icu/scripts/539399/Google%20AI%20Studio%20%E6%A8%A1%E5%9E%8B%E6%B3%A8%E5%85%A5%E5%99%A8.user.js // @updateURL https://update.greasyfork.icu/scripts/539399/Google%20AI%20Studio%20%E6%A8%A1%E5%9E%8B%E6%B3%A8%E5%85%A5%E5%99%A8.meta.js // ==/UserScript== (function() { 'use strict'; // ==================== 配置管理 ==================== /** * 脚本全局配置 * @const */ const CONFIG = { VERSION: "1.7.1", STORAGE_KEY: 'AI_STUDIO_INJECTOR_CUSTOM_MODELS', API: { ANTI_HIJACK_PREFIX: ")]}'\n", TARGET_PATH: '/ListModels', TARGET_HOST: 'alkalimakersuite' }, MODEL_PREFIX: 'models/', FIELD_INDEX: { NAME: 0, DISPLAY_NAME: 3, DESCRIPTION: 4, METHODS: 7 } }; // ==================== 日志管理 ==================== /** * 统一的日志管理器 * @namespace Logger */ const Logger = { prefix: `[AI Studio 注入器 v${CONFIG.VERSION}]`, /** * 输出常规日志 * @param {...any} args - 日志内容 */ log(...args) { console.log(this.prefix, ...args); }, /** * 输出错误日志 * @param {...any} args - 错误内容 */ error(...args) { console.error(this.prefix, ...args); }, /** * 输出警告日志 * @param {...any} args - 警告内容 */ warn(...args) { console.warn(this.prefix, ...args); } }; // ==================== 存储管理 ==================== /** * 本地存储管理器,处理自定义模型的持久化 * @namespace Storage */ const Storage = { /** * 从本地存储加载自定义模型列表 * @returns {Array} 自定义模型数组,包含name、displayName和description字段 */ load() { try { const data = localStorage.getItem(CONFIG.STORAGE_KEY); if (data) { const models = JSON.parse(data); // 自动更新模型的版本标记以匹配当前脚本版本 return models.map(model => ({ ...model, displayName: model.displayName.replace(/ \(脚本 v[\d.]+\)$/, '') + ` (脚本 v${CONFIG.VERSION})`, description: model.description.replace(/脚本 v[\d.]+/, `脚本 v${CONFIG.VERSION}`) })); } } catch (e) { Logger.error('加载自定义模型时出错:', e); } return []; }, /** * 保存自定义模型列表到本地存储 * @param {Array} models - 要保存的模型数组 * @returns {boolean} 保存是否成功 */ save(models) { try { localStorage.setItem(CONFIG.STORAGE_KEY, JSON.stringify(models)); return true; } catch (e) { Logger.error('保存自定义模型时出错:', e); return false; } } }; // ==================== 工具函数 ==================== /** * 通用工具函数集合 * @namespace Utils */ const Utils = { /** * 清理模型名称,移除版本标记和emoji前缀 * @param {string} name - 原始模型名称 * @returns {string} 清理后的名称 */ cleanModelName(name) { return String(name) .replace(/ \(脚本 v\d+\.\d+(\.\d+)?\)/, '') .replace(/^[✨🦁💧❄️🌊🐉🏴‍☠️🤖🪐]\s*/, '') .trim(); }, /** * 格式化模型显示名称,添加版本标记 * @param {string} displayName - 原始显示名称 * @returns {string} 带版本标记的显示名称 */ formatDisplayName(displayName) { const cleanName = displayName.replace(/ \(脚本 v[\d.]+\)$/, ''); return `${cleanName} (脚本 v${CONFIG.VERSION})`; }, /** * 格式化模型描述,更新版本信息 * @param {string} description - 原始描述 * @returns {string} 更新版本后的描述 */ formatDescription(description) { return description.replace(/脚本 v[\d.]+/, `脚本 v${CONFIG.VERSION}`); }, /** * 检查URL是否匹配需要拦截的模型列表API * @param {string} url - 要检查的URL * @returns {boolean} 是否为目标URL */ isTargetURL(url) { return url && typeof url === 'string' && url.includes(CONFIG.API.TARGET_HOST) && url.includes(CONFIG.API.TARGET_PATH); }, /** * 验证模型ID是否有效 * @param {string} id - 模型ID * @returns {boolean} ID是否有效 */ validateModelId(id) { if (!id || typeof id !== 'string') return false; const trimmed = id.trim(); return trimmed.length > 0; }, /** * 确保模型ID具有正确的前缀 * @param {string} id - 原始模型ID * @returns {string} 带正确前缀的模型ID */ ensureModelPrefix(id) { const trimmed = id.trim(); return trimmed.startsWith(CONFIG.MODEL_PREFIX) ? trimmed : CONFIG.MODEL_PREFIX + trimmed; } }; // ==================== 模型定义 ==================== /** * 特殊动作模型:添加自定义模型 * @const */ const ACTION_ADD_MODEL = { name: 'models/---script-action-add-custom---', displayName: `➕ 添加自定义模型 (点击此处)`, description: '点击此项以手动输入新的模型 ID 并保存。' }; /** * 特殊动作模型:清除所有自定义模型 * @const */ const ACTION_CLEAR_MODELS = { name: 'models/---script-action-clear-custom---', displayName: `🗑️ 清除手动添加的模型 (点击此处)`, description: '点击此项以清除所有您手动添加的模型。' }; /** * 预设模型列表 * @const */ const PREDEFINED_MODELS = [ { name: "models/blacktooth-ab-test", displayName: `🏴‍☠️ Blacktooth (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, { name: "models/jfdksal98a", displayName: `🪐 jfdksal98a (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, // --- 新添加的模型开始 --- { name: "models/68zkqbz8vs", displayName: `🤖 68zkqbz8vs (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, { name: "models/a24bo28u1a", displayName: `🚀 a24bo28u1a (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, { name: "models/2vmc1bo4ri", displayName: `💡 2vmc1bo4ri (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, { name: "models/42fc3y4xfsz", displayName: `🔧 42fc3y4xfsz (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, templateModelName: `models/gemini-2.5-flash-preview-05-20`, }, { name: "models/ixqzem8yj4j", displayName: `🔮 ixqzem8yj4j (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, templateModelName: `models/gemini-2.5-flash-preview-05-20`, }, // --- 新添加的模型结束 --- { name: "models/gemini-2.5-pro-preview-03-25", displayName: `✨ Gemini 2.5 Pro 03-25 (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, { name: "models/goldmane-ab-test", displayName: `🦁 Goldmane (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, { name: "models/claybrook-ab-test", displayName: `💧 Claybrook (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, { name: "models/frostwind-ab-test", displayName: `❄️ Frostwind (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, { name: "models/calmriver-ab-test", displayName: `🌊 Calmriver (脚本 v${CONFIG.VERSION})`, description: `由脚本 v${CONFIG.VERSION} 预设注入的模型`, }, ]; // ==================== 自定义模型管理 (UI 逻辑) ==================== /** * 通过对话框收集用户输入并创建新的自定义模型 */ function promptForNewModel() { // 提示用户输入模型ID,会自动添加 'models/' 前缀 const rawInputId = prompt("【添加自定义模型】\n请输入新的模型ID (例如: my-custom-model):\n(会自动添加 'models/' 前缀)"); if (!rawInputId) return; if (!Utils.validateModelId(rawInputId)) { alert("错误:模型ID不能为空。"); return; } // 自动添加 'models/' 前缀 const fullModelId = Utils.ensureModelPrefix(rawInputId); // 使用模型ID生成默认显示名称 const defaultName = fullModelId.split('/').pop(); const displayNameInput = prompt("请输入该模型的显示名称 (例如: 🤖 我的模型):", `🤖 ${defaultName}`); if (!displayNameInput) return; const newModel = { name: fullModelId, displayName: Utils.formatDisplayName(displayNameInput), description: `由用户手动添加并通过脚本 v${CONFIG.VERSION} 注入的模型` }; const customModels = Storage.load(); const allCurrentModels = [...PREDEFINED_MODELS, ...customModels]; // 检查模型ID是否已存在 if (allCurrentModels.some(m => m.name === fullModelId)) { alert(`错误:模型ID ${fullModelId} 已存在。`); return; } customModels.push(newModel); if (Storage.save(customModels)) { alert(`模型 ${fullModelId} 添加成功!\n\n页面将自动刷新以应用更改。`); window.location.reload(); } else { alert("错误:无法保存模型。"); } } /** * 清除所有用户自定义的模型(不影响预设模型) */ function clearAllCustomModels() { if (confirm("⚠️ 确定要清除所有您手动添加的自定义模型吗?\n\n(脚本预设的模型不会被删除)")) { if (Storage.save([])) { alert("所有手动添加的自定义模型已清除。页面将自动刷新。"); window.location.reload(); } else { alert("错误:无法清除模型。"); } } } /** * 设置模型选择器的点击事件拦截器,用于处理动作模型的点击 */ function setupModelSelectionInterceptor() { document.body.addEventListener('click', (event) => { const optionElement = event.target.closest('[role="option"], mat-option'); if (optionElement && optionElement.textContent) { const text = optionElement.textContent; let actionTaken = false; if (text.includes(ACTION_ADD_MODEL.displayName)) { Logger.log("拦截到 '添加模型' 点击事件。"); actionTaken = true; setTimeout(promptForNewModel, 50); } else if (text.includes(ACTION_CLEAR_MODELS.displayName)) { Logger.log("拦截到 '清除模型' 点击事件。"); actionTaken = true; setTimeout(clearAllCustomModels, 50); } if (actionTaken) { // 阻止默认行为和事件冒泡 event.preventDefault(); event.stopPropagation(); // 移除焦点以关闭下拉菜单 if (document.activeElement) { document.activeElement.blur(); } } } }, true); // 在捕获阶段处理事件 Logger.log('模型选择点击拦截器已设置。'); } // ==================== 初始化 ==================== const customModels = Storage.load(); const ALL_MODELS_TO_INJECT = [...PREDEFINED_MODELS, ...customModels]; Logger.log(`预设模型: ${PREDEFINED_MODELS.length} 个, 用户自定义模型: ${customModels.length} 个`); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', setupModelSelectionInterceptor); } else { setupModelSelectionInterceptor(); } // ==================== 数据处理工具函数 ==================== /** * 递归查找响应数据中的模型列表数组 * @param {Object} 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[CONFIG.FIELD_INDEX.NAME] === 'string' && String(item[CONFIG.FIELD_INDEX.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|undefined} 找到的模板模型 */ function findTemplateModel(modelsArray) { // 优先选择包含 'pro' 的模型作为模板 return modelsArray.find(m => Array.isArray(m) && m[CONFIG.FIELD_INDEX.NAME] && String(m[CONFIG.FIELD_INDEX.NAME]).includes('pro') && Array.isArray(m[CONFIG.FIELD_INDEX.METHODS])) || // 次选包含 'flash' 的模型 modelsArray.find(m => Array.isArray(m) && m[CONFIG.FIELD_INDEX.NAME] && String(m[CONFIG.FIELD_INDEX.NAME]).includes('flash') && Array.isArray(m[CONFIG.FIELD_INDEX.METHODS])) || // 最后选择任意有方法列表的模型 modelsArray.find(m => Array.isArray(m) && m[CONFIG.FIELD_INDEX.NAME] && Array.isArray(m[CONFIG.FIELD_INDEX.METHODS])); } /** * 根据模型名称查找合适的模板模型用于创建新模型条目 * @param {Array} modelsArray - 模型数组 * @param {String} modelName - 模板模型名称 * @returns {Array|undefined} 找到的模板模型 */ function findTemplateModelByModelName(modelsArray, modelName) { return modelsArray.find(m => Array.isArray(m) && m[CONFIG.FIELD_INDEX.NAME] && String(m[CONFIG.FIELD_INDEX.NAME]) === modelName && Array.isArray(m[CONFIG.FIELD_INDEX.METHODS])); } /** * 更新已存在的模型条目 * @param {Array} existingModel - 现有模型数组 * @param {Object} modelToInject - 要注入的模型配置 * @returns {boolean} 是否成功更新(true表示已更新,无需再注入新条目) */ function updateExistingModel(existingModel, modelToInject) { if (!existingModel || existingModel[CONFIG.FIELD_INDEX.DISPLAY_NAME] === modelToInject.displayName) { return false; } const baseExistingName = Utils.cleanModelName(existingModel[CONFIG.FIELD_INDEX.DISPLAY_NAME]); const baseInjectName = Utils.cleanModelName(modelToInject.displayName); if (baseExistingName === baseInjectName) { // 基础名称相同,更新版本标记 existingModel[CONFIG.FIELD_INDEX.DISPLAY_NAME] = modelToInject.displayName; return true; } else { // 基础名称不同,标记为原始版本 if (!String(existingModel[CONFIG.FIELD_INDEX.DISPLAY_NAME]).includes("(原始)")) { existingModel[CONFIG.FIELD_INDEX.DISPLAY_NAME] += " (原始)"; } return false; } } /** * 基于模板创建新的模型条目 * @param {Array} templateModel - 模板模型 * @param {Object} modelToInject - 要注入的模型配置 * @param {string} templateName - 模板模型名称 * @returns {Array} 新创建的模型条目 */ function createNewModel(templateModel, modelToInject, templateName) { const newModel = structuredClone(templateModel); newModel[CONFIG.FIELD_INDEX.NAME] = modelToInject.name; newModel[CONFIG.FIELD_INDEX.DISPLAY_NAME] = modelToInject.displayName; newModel[CONFIG.FIELD_INDEX.DESCRIPTION] = `${modelToInject.description} (基于 ${templateName} 结构)`; // 确保有方法列表 if (!Array.isArray(newModel[CONFIG.FIELD_INDEX.METHODS])) { newModel[CONFIG.FIELD_INDEX.METHODS] = [ "generateContent", "countTokens", "createCachedContent", "batchGenerateContent" ]; } return newModel; } // ==================== 核心处理函数 ==================== /** * 处理API响应数据,注入自定义模型 * @param {Object} jsonData - 原始JSON响应数据 * @param {string} url - 请求URL * @returns {{data: Object, modified: boolean}} 处理结果 */ function processJsonData(jsonData, url) { let modificationMade = false; const modelsArray = findModelListArray(jsonData); if (!modelsArray) return { data: jsonData, modified: false }; const generalTemplateModel = findTemplateModel(modelsArray); const generalTemplateName = generalTemplateModel?.[CONFIG.FIELD_INDEX.NAME] || 'unknown'; if (!generalTemplateModel) { Logger.warn('未找到模板模型'); return { data: jsonData, modified: false }; } // 注入自定义和预设模型 [...ALL_MODELS_TO_INJECT].reverse().forEach(modelToInject => { const existingModel = modelsArray.find( model => Array.isArray(model) && model[CONFIG.FIELD_INDEX.NAME] === modelToInject.name ); let shouldInjectNew = true; if (existingModel) { const updated = updateExistingModel(existingModel, modelToInject); modificationMade = modificationMade || updated; shouldInjectNew = !updated; } if (shouldInjectNew) { // 检查是否已经注入过脚本版本 const alreadyInjected = modelsArray.find(m => Array.isArray(m) && m[CONFIG.FIELD_INDEX.NAME] === modelToInject.name && String(m[CONFIG.FIELD_INDEX.DISPLAY_NAME]).includes('(脚本 v') ); if (!alreadyInjected) { let templateModel = generalTemplateModel; let templateName = generalTemplateName; if(modelToInject.templateModelName){ templateModel = findTemplateModelByModelName(modelsArray, modelToInject.templateModelName) } let newModel = createNewModel(templateModel, modelToInject, templateName); modelsArray.unshift(newModel); modificationMade = true; Logger.log(`成功注入: ${modelToInject.displayName} 到模板 "${templateName}"`); } else { // 更新已注入模型的版本信息 if (alreadyInjected[CONFIG.FIELD_INDEX.DISPLAY_NAME] !== modelToInject.displayName) { alreadyInjected[CONFIG.FIELD_INDEX.DISPLAY_NAME] = modelToInject.displayName; alreadyInjected[CONFIG.FIELD_INDEX.DESCRIPTION] = `${modelToInject.description} (基于 ${generalTemplateName} 结构)`; modificationMade = true; } } } }); // 注入动作模型(添加和清除按钮) const addActionModel = createNewModel(generalTemplateModel, ACTION_ADD_MODEL, generalTemplateName); addActionModel[CONFIG.FIELD_INDEX.METHODS] = []; // 清空方法列表 if (customModels.length > 0) { const clearActionModel = createNewModel(generalTemplateModel, ACTION_CLEAR_MODELS, generalTemplateName); clearActionModel[CONFIG.FIELD_INDEX.METHODS] = []; modelsArray.unshift(clearActionModel); } modelsArray.unshift(addActionModel); modificationMade = true; Logger.log('已注入"添加/清除自定义模型"动作条目。'); return { data: jsonData, modified: modificationMade }; } /** * 修改HTTP响应体,注入自定义模型数据 * @param {string} originalText - 原始响应文本 * @param {string} url - 请求URL * @returns {string} 修改后的响应文本 */ function modifyResponseBody(originalText, url) { if (!originalText || typeof originalText !== 'string') return originalText; try { let textBody = originalText; let hasPrefix = false; // 处理Google的反劫持前缀 if (textBody.startsWith(CONFIG.API.ANTI_HIJACK_PREFIX)) { textBody = textBody.substring(CONFIG.API.ANTI_HIJACK_PREFIX.length); hasPrefix = true; } if (!textBody.trim()) return originalText; const jsonData = JSON.parse(textBody); const result = processJsonData(jsonData, url); if (result.modified) { let newBody = JSON.stringify(result.data); if (hasPrefix) newBody = CONFIG.API.ANTI_HIJACK_PREFIX + newBody; return newBody; } } catch (error) { Logger.error('处理响应体时出错:', url, error); } return originalText; } // ==================== 请求拦截 ==================== // 拦截 Fetch API const originalFetch = window.fetch; window.fetch = async function(...args) { const url = (args[0] instanceof Request) ? args[0].url : String(args[0]); const response = await originalFetch.apply(this, args); if (Utils.isTargetURL(url) && response.ok) { try { const cloneResponse = response.clone(); const originalText = await cloneResponse.text(); const newBody = modifyResponseBody(originalText, url); if (newBody !== originalText) { return new Response(newBody, { status: response.status, statusText: response.statusText, headers: response.headers }); } } catch (e) { Logger.error('[Fetch] 处理错误:', e); } } return response; }; // 拦截 XMLHttpRequest const xhrProto = XMLHttpRequest.prototype; const originalOpen = xhrProto.open; const originalResponseTextDescriptor = Object.getOwnPropertyDescriptor(xhrProto, 'responseText'); const originalResponseDescriptor = Object.getOwnPropertyDescriptor(xhrProto, 'response'); /** * 重写XHR的open方法以记录请求URL */ xhrProto.open = function(method, url) { this._interceptorUrl = url; this._isTargetXHR = Utils.isTargetURL(url); return originalOpen.apply(this, arguments); }; /** * 处理XHR响应,根据需要修改内容 * @param {XMLHttpRequest} xhr - XHR对象 * @param {*} originalValue - 原始响应值 * @param {string} type - 响应类型 ('text' 或 'json') * @returns {*} 处理后的响应值 */ const handleXHRResponse = (xhr, originalValue, type = 'text') => { if (!xhr._isTargetXHR || 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); xhr[cacheKey] = modifyResponseBody(originalText, xhr._interceptorUrl); } const cachedResponse = xhr[cacheKey]; try { if (type === 'json' && typeof cachedResponse === 'string') { const textToParse = cachedResponse.replace(CONFIG.API.ANTI_HIJACK_PREFIX, ''); return textToParse ? JSON.parse(textToParse) : null; } } catch (e) { Logger.error('[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 }); } Logger.log(`脚本 v${CONFIG.VERSION} 已激活。Fetch 和 XHR 拦截已启用。`); })();