Google AI Studio 模型注入器(多模型版)

向 Google AI Studio 注入自定义模型,支持主题表情图标。拦截 XHR/Fetch 请求,处理数组结构的 JSON 数据

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Google AI Studio 模型注入器(多模型版)
// @namespace    http://tampermonkey.net/
// @version      1.6.1
// @description  向 Google AI Studio 注入自定义模型,支持主题表情图标。拦截 XHR/Fetch 请求,处理数组结构的 JSON 数据
// @author       Generated by AI / HCPTangHY / Mozi / wisdgod
// @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.1";
    const LOG_PREFIX = `[AI Studio 注入器 ${SCRIPT_VERSION}]`;
    const ANTI_HIJACK_PREFIX = ")]}'\n";

    // 模型配置列表
    const MODELS_TO_INJECT = [
        {
            name: 'models/kingfall-ab-test',
            displayName: `👑 Kingfall (脚本 ${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 是否为目标 API 端点
     * @param {string} url - 要检查的 URL
     * @returns {boolean}
     */
    function isTargetURL(url) {
        return url && typeof url === 'string' &&
               url.includes('alkalimakersuite') &&
               url.includes('/ListModels');
    }

    /**
     * 递归查找模型列表数组
     * @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+(-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}`);
        } else {
            // 标记为原始模型
            existingModel[MODEL_FIELDS.DISPLAY_NAME] = modelToInject.displayName + " (原始)";
            console.log(LOG_PREFIX, `已更新官方模型 ${modelToInject.name} 的显示名称`);
        }
        return true;
    }

    /**
     * 创建新模型
     * @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} 结构)`;

        if (!Array.isArray(newModel[MODEL_FIELDS.METHODS])) {
            newModel[MODEL_FIELDS.METHODS] = [
                "generateContent",
                "countTokens",
                "createCachedContent",
                "batchGenerateContent"
            ];
        }

        return newModel;
    }

    // ==================== 核心处理函数 ====================

    /**
     * 处理并修改 JSON 数据
     * @param {Object} jsonData - 原始 JSON 数据
     * @param {string} url - 请求 URL
     * @returns {Object} 包含处理后数据和修改标志的对象
     */
    function processJsonData(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);
                modificationMade = true;
                console.log(LOG_PREFIX, `成功注入: ${modelToInject.displayName}`);
            } else {
                // 更新现有模型
                if (updateExistingModel(existingModel, modelToInject)) {
                    modificationMade = true;
                }
            }
        });

        return { data: jsonData, modified: modificationMade };
    }

    /**
     * 修改响应体
     * @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;

            // 处理反劫持前缀
            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 resource = args[0];
        const url = (resource instanceof Request) ? resource.url : String(resource);
        const response = await originalFetch.apply(this, args);

        if (isTargetURL(url) && response.ok) {
            console.log(LOG_PREFIX, '[Fetch] 拦截到目标请求:', url);
            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');
    let interceptionCount = 0;

    // 重写 open 方法
    xhrProto.open = function(method, url) {
        this._interceptorUrl = url;
        this._isTargetXHR = isTargetURL(url);

        if (this._isTargetXHR) {
            interceptionCount++;
            console.log(LOG_PREFIX, `[XHR] 检测到目标请求 (${interceptionCount}):`, url);
        }

        return originalOpen.apply(this, arguments);
    };

    /**
     * 处理 XHR 响应
     * @param {XMLHttpRequest} xhr - XHR 对象
     * @param {any} originalValue - 原始响应值
     * @param {string} type - 响应类型
     * @returns {any} 处理后的响应值
     */
    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;
    };

    // 重写 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, '脚本已激活,Fetch 和 XHR 拦截已启用');
})();