您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Inject multiple custom models with themed emojis into Google AI Studio model list. Intercepts XHR/fetch, handles array-of-arrays JSON structures.
// ==UserScript== // @name Google AI Studio Model kingfall // @namespace http://tampermonkey.net/ // @version 1.7 // @description Inject multiple custom models with themed emojis into Google AI Studio model list. Intercepts XHR/fetch, handles array-of-arrays JSON structures. // @author Updated by everyone // @match https://aistudio.google.com/* // @match https://google.com/app/prompts/new_chat* // @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'; // ============== Configuration =============== const SCRIPT_VERSION = "v1.7"; const DEBUG = false; // set true for verbose logging const LOG_PREFIX = `[Injector ${SCRIPT_VERSION}]`; const MODELS_TO_INJECT = [ { name: 'models/kingfall-ab-test', displayName: `👑 Kingfall (${SCRIPT_VERSION})`, description: `Injected by script ${SCRIPT_VERSION}` }, { name: 'models/gemini-2.5-pro-preview-03-25', displayName: `✨ Gemini 2.5 Pro 03-25 (${SCRIPT_VERSION})`, description: `Injected by script ${SCRIPT_VERSION}` }, { name: 'models/goldmane-ab-test', displayName: `🦁 Goldmane (${SCRIPT_VERSION})`, description: `Injected by script ${SCRIPT_VERSION}` }, { name: 'models/claybrook-ab-test', displayName: `💧 Claybrook (${SCRIPT_VERSION})`, description: `Injected by script ${SCRIPT_VERSION}` }, { name: 'models/frostwind-ab-test', displayName: `❄️ Frostwind (${SCRIPT_VERSION})`, description: `Injected by script ${SCRIPT_VERSION}` }, { name: 'models/calmriver-ab-test', displayName: `🌊 Calmriver (${SCRIPT_VERSION})`, description: `Injected by script ${SCRIPT_VERSION}` } ]; // ============== Helpers =============== function log(...args) { if (DEBUG) console.log(LOG_PREFIX, ...args); } function isTargetURL(url) { return typeof url === 'string' && url.includes('ListModels'); } // Recursively find the array-of-arrays model list function findModelListArray(obj) { if (Array.isArray(obj) && obj.length > 0 && obj.every( item => Array.isArray(item) && typeof item[0] === 'string' && item[0].startsWith('models/') )) { return obj; } if (obj && typeof obj === 'object') { for (const key in obj) { if (obj.hasOwnProperty(key)) { const res = findModelListArray(obj[key]); if (res) return res; } } } return null; } function processJsonData(data) { const NAME_IDX = 0, DISPLAY_IDX = 3, DESC_IDX = 4, METHODS_IDX = 7; let modified = false; const modelsArray = findModelListArray(data); if (!modelsArray) { log('No model list found, skipping'); return { data, modified }; } // find a template model that has methods const template = modelsArray.find(m => Array.isArray(m) && Array.isArray(m[METHODS_IDX])) || null; if (!template) { log('Template model not found, cannot inject new models'); } MODELS_TO_INJECT.slice().reverse().forEach(model => { const exists = modelsArray.some(m => Array.isArray(m) && m[NAME_IDX] === model.name); if (!exists && template) { const clone = JSON.parse(JSON.stringify(template)); clone[NAME_IDX] = model.name; clone[DISPLAY_IDX] = model.displayName; clone[DESC_IDX] = `${model.description} (based on ${template[NAME_IDX]})`; if (!Array.isArray(clone[METHODS_IDX])) { clone[METHODS_IDX] = ['generateContent','countTokens','createCachedContent','batchGenerateContent']; } modelsArray.unshift(clone); modified = true; log('Injected', model.name); } else if (exists) { // update display name if changed const existing = modelsArray.find(m => m[NAME_IDX] === model.name); if (existing && existing[DISPLAY_IDX] !== model.displayName) { existing[DISPLAY_IDX] = model.displayName; modified = true; log('Updated displayName for', model.name); } } }); return { data, modified }; } function modifyResponseBody(text, url) { if (typeof text !== 'string') return text; const ANTI_HIJACK = ")]}'\n"; let body = text, hasPrefix = false; if (body.startsWith(ANTI_HIJACK)) { body = body.slice(ANTI_HIJACK.length); hasPrefix = true; } if (!body.trim()) return text; try { const json = JSON.parse(body); const result = processJsonData(json); if (result.modified) { let out = JSON.stringify(result.data); if (hasPrefix) out = ANTI_HIJACK + out; return out; } } catch (e) { console.error(LOG_PREFIX, 'JSON parse error for', url, e); } return text; } // ============== Fetch Interceptor =============== const origFetch = window.fetch; window.fetch = async function(input, init) { const url = input instanceof Request ? input.url : String(input); const response = await origFetch(input, init); if (response.ok && isTargetURL(url)) { try { const clone = response.clone(); const text = await clone.text(); const mod = modifyResponseBody(text, url); if (mod !== text) { return new Response(mod, { status: response.status, statusText: response.statusText, headers: response.headers }); } } catch (e) { console.error(LOG_PREFIX, 'Fetch intercept error', e); } } return response; }; log('Fetch patch applied'); // ============== XHR Interceptor =============== const xhrProto = XMLHttpRequest.prototype; const origOpen = xhrProto.open; const txtDesc = Object.getOwnPropertyDescriptor(xhrProto, 'responseText'); const resDesc = Object.getOwnPropertyDescriptor(xhrProto, 'response'); xhrProto.open = function(method, url) { this._isTarget = isTargetURL(url); return origOpen.apply(this, arguments); }; function wrapResponse(xhr, origVal, type) { if (xhr._isTarget && xhr.readyState === 4 && xhr.status === 200) { const cacheKey = '_cached' + type; if (xhr[cacheKey] === undefined) { const txt = type === 'json' && typeof origVal === 'object' ? JSON.stringify(origVal) : String(origVal); xhr[cacheKey] = modifyResponseBody(txt, xhr._interceptorUrl); } const val = xhr[cacheKey]; if (type === 'json' && typeof val === 'string') { try { return JSON.parse(val); } catch { return origVal; } } return val; } return origVal; } if (txtDesc && txtDesc.get) { Object.defineProperty(xhrProto, 'responseText', { get: function() { return wrapResponse(this, txtDesc.get.call(this), 'text'); }, configurable: true }); } if (resDesc && resDesc.get) { Object.defineProperty(xhrProto, 'response', { get: function() { const rt = resDesc.get.call(this); if (this.responseType === 'json') return wrapResponse(this, rt, 'json'); if (!this.responseType || this.responseType === 'text') return wrapResponse(this, rt, 'text'); return rt; }, configurable: true }); } log('XHR patch applied'); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址