您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
向 Google AI Studio 注入自定义模型,支持在模型列表中手动添加ID(无需输入 models/)。拦截 XHR/Fetch 请求。
// ==UserScript== // @name Google AI Studio 模型注入器 // @namespace http://tampermonkey.net/ // @version 1.7.0 // @description 向 Google AI Studio 注入自定义模型,支持在模型列表中手动添加ID(无需输入 models/)。拦截 XHR/Fetch 请求。 // @author Generated by AI / HCPTangHY / Mozi / wisdgod / UserModified // @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.7.0"; // 更新版本号 const LOG_PREFIX = `[AI Studio 注入器 ${SCRIPT_VERSION}]`; const ANTI_HIJACK_PREFIX = ")]}'\n"; const CUSTOM_MODELS_STORAGE_KEY = 'AI_STUDIO_INJECTOR_CUSTOM_MODELS'; // 特殊“动作”模型定义 const ACTION_ADD_MODEL = { name: 'models/---script-action-add-custom---', displayName: `➕ 添加自定义模型 (点击此处)`, description: '点击此项以手动输入新的模型 ID 并保存。' }; const ACTION_CLEAR_MODELS = { name: 'models/---script-action-clear-custom---', displayName: `🗑️ 清除手动添加的模型 (点击此处)`, description: '点击此项以清除所有您手动添加的模型。' }; // 预设模型配置列表 const PREDEFINED_MODELS = [ { name: 'models/blacktooth-ab-test', displayName: `🏴☠️ Blacktooth (脚本 ${SCRIPT_VERSION})`, description: `由脚本 ${SCRIPT_VERSION} 预设注入的模型` }, { name: 'models/jfdksal98a', displayName: `🪐 jfdksal98a (脚本 ${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 }; // ==================== 自定义模型管理 (UI 逻辑) ==================== function loadCustomModels() { try { const storedModels = localStorage.getItem(CUSTOM_MODELS_STORAGE_KEY); if (storedModels) { const models = JSON.parse(storedModels); return models.map(model => ({ ...model, displayName: model.displayName.replace(/ \(脚本 v[\d.]+\)$/, '') + ` (脚本 ${SCRIPT_VERSION})`, description: model.description.replace(/脚本 v[\d.]+/, `脚本 ${SCRIPT_VERSION}`) })); } } catch (e) { console.error(LOG_PREFIX, '加载自定义模型时出错:', e); } return []; } function saveCustomModels(models) { try { localStorage.setItem(CUSTOM_MODELS_STORAGE_KEY, JSON.stringify(models)); } catch (e) { console.error(LOG_PREFIX, '保存自定义模型时出错:', e); } } /** * 提示用户输入新的模型 ID (已修改:自动添加 'models/') */ function promptForNewModel() { // 修改提示语,告诉用户不需要输入 'models/' const rawInputId = prompt("【添加自定义模型】\n请输入新的模型ID (例如: my-custom-model):\n(会自动添加 'models/' 前缀)"); if (!rawInputId) return; // 用户取消 const trimmedId = rawInputId.trim(); if (!trimmedId) { alert("错误:模型ID不能为空。"); return; } // 自动添加 'models/' 前缀,如果用户没加的话 let fullModelId; if (trimmedId.startsWith('models/')) { fullModelId = trimmedId; // 如果用户还是输入了,也接受 } else { fullModelId = 'models/' + trimmedId; } // 使用 fullModelId 生成默认名称 const defaultName = fullModelId.split('/').pop(); const displayNameInput = prompt("请输入该模型的显示名称 (例如: 🤖 我的模型):", `🤖 ${defaultName}`); if (!displayNameInput) return; // 用户取消 const newModel = { name: fullModelId, // 使用完整的 ID displayName: `${displayNameInput} (脚本 ${SCRIPT_VERSION})`, description: `由用户手动添加并通过脚本 ${SCRIPT_VERSION} 注入的模型` }; const customModels = loadCustomModels(); const allCurrentModels = [...PREDEFINED_MODELS, ...customModels]; // 检查完整 ID 是否已存在 if (allCurrentModels.some(m => m.name === fullModelId)) { alert(`错误:模型ID ${fullModelId} 已存在。`); return; } customModels.push(newModel); saveCustomModels(customModels); alert(`模型 ${fullModelId} 添加成功!\n\n页面将自动刷新以应用更改。`); window.location.reload(); } /** * 清除所有用户自定义的模型 */ function clearAllCustomModels() { if (confirm("⚠️ 确定要清除所有您手动添加的自定义模型吗?\n\n(脚本预设的模型不会被删除)")) { saveCustomModels([]); alert("所有手动添加的自定义模型已清除。页面将自动刷新。"); window.location.reload(); } } /** * 拦截列表点击事件 */ 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)) { console.log(LOG_PREFIX, "拦截到 '添加模型' 点击事件。"); actionTaken = true; setTimeout(promptForNewModel, 50); } else if (text.includes(ACTION_CLEAR_MODELS.displayName)) { console.log(LOG_PREFIX, "拦截到 '清除模型' 点击事件。"); actionTaken = true; setTimeout(clearAllCustomModels, 50); } if (actionTaken) { event.preventDefault(); event.stopPropagation(); if (document.activeElement) { document.activeElement.blur(); } } } }, true); // true 表示在捕获阶段处理 console.log(LOG_PREFIX, '模型选择点击拦截器已设置。'); } // ==================== 初始化 ==================== const customModels = loadCustomModels(); const ALL_MODELS_TO_INJECT = [...PREDEFINED_MODELS, ...customModels]; console.log(LOG_PREFIX, `预设模型: ${PREDEFINED_MODELS.length} 个, 用户自定义模型: ${customModels.length} 个`); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', setupModelSelectionInterceptor); } else { setupModelSelectionInterceptor(); } // ==================== 数据处理工具函数 ==================== function isTargetURL(url) { return url && typeof url === 'string' && url.includes('alkalimakersuite') && url.includes('/ListModels'); } 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; } function findTemplateModel(modelsArray) { return 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])); } 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+)?\)/, '') .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; return true; // 已更新,不需要再注入 } else { if (!String(existingModel[MODEL_FIELDS.DISPLAY_NAME]).includes("(原始)")) { existingModel[MODEL_FIELDS.DISPLAY_NAME] += " (原始)"; } return false; // 标记为原始,仍需注入脚本版本 } } 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} 结构)`; if (!Array.isArray(newModel[MODEL_FIELDS.METHODS])) { newModel[MODEL_FIELDS.METHODS] = [ "generateContent", "countTokens", "createCachedContent", "batchGenerateContent" ]; } return newModel; } // ==================== 核心处理函数 ==================== function processJsonData(jsonData, url) { let modificationMade = false; const modelsArray = findModelListArray(jsonData); if (!modelsArray) return { data: jsonData, modified: false }; const templateModel = findTemplateModel(modelsArray); const templateName = templateModel?.[MODEL_FIELDS.NAME] || 'unknown'; if (!templateModel) { console.warn(LOG_PREFIX, '未找到模板模型'); return { data: jsonData, modified: false }; } // 1. 注入正常的自定义和预设模型 [...ALL_MODELS_TO_INJECT].reverse().forEach(modelToInject => { const existingModel = modelsArray.find( model => Array.isArray(model) && model[MODEL_FIELDS.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[MODEL_FIELDS.NAME] === modelToInject.name && String(m[MODEL_FIELDS.DISPLAY_NAME]).includes('(脚本 v')); if (!alreadyInjected) { const newModel = createNewModel(templateModel, modelToInject, templateName); modelsArray.unshift(newModel); modificationMade = true; console.log(LOG_PREFIX, `成功注入: ${modelToInject.displayName}`); } else { if (alreadyInjected[MODEL_FIELDS.DISPLAY_NAME] !== modelToInject.displayName) { alreadyInjected[MODEL_FIELDS.DISPLAY_NAME] = modelToInject.displayName; alreadyInjected[MODEL_FIELDS.DESCRIPTION] = `${modelToInject.description} (基于 ${templateName} 结构)`; modificationMade = true; } } } }); // 2. 注入“动作”条目 (添加和清除) const addActionModel = createNewModel(templateModel, ACTION_ADD_MODEL, templateName); addActionModel[MODEL_FIELDS.METHODS] = []; // 清空方法列表,让它看起来更像个按钮 if (customModels.length > 0) { const clearActionModel = createNewModel(templateModel, ACTION_CLEAR_MODELS, templateName); clearActionModel[MODEL_FIELDS.METHODS] = []; modelsArray.unshift(clearActionModel); } modelsArray.unshift(addActionModel); modificationMade = true; console.log(LOG_PREFIX, '已注入“添加/清除自定义模型”动作条目。'); return { data: jsonData, modified: modificationMade }; } function modifyResponseBody(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 = processJsonData(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, '处理响应体时出错:', 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 (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) { console.error(LOG_PREFIX, '[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'); xhrProto.open = function(method, url) { this._interceptorUrl = url; this._isTargetXHR = isTargetURL(url); return originalOpen.apply(this, arguments); }; 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(ANTI_HIJACK_PREFIX, ''); return textToParse ? JSON.parse(textToParse) : null; } } catch (e) { console.error(LOG_PREFIX, '[XHR] 解析 JSON 时出错:', e); return originalValue; } return cachedResponse; }; 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 }); } 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, '脚本 v1.7.0 已激活。Fetch 和 XHR 拦截已启用。'); })();