您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Select AI model, recalculates auth headers after modification. Uses unsafeWindow.
当前为
// ==UserScript== // @name Model Selector for AI Uncensored (v2.2 - Header Recalc) // @namespace http://tampermonkey.net/ // @version 2.2 // @description Select AI model, recalculates auth headers after modification. Uses unsafeWindow. // @author saros // @match https://www.aiuncensored.info/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @run-at document-start // ==/UserScript== (function() { 'use strict'; console.log('[Model Selector] Script starting (v2.2)...'); // --- Configuration --- const AVAILABLE_MODELS = [ "deepseek-ai/DeepSeek-V3-0324", // Default from the snippet "hermes3-405b", "hermes3-8b", "hermes3-70b", "deepseek-r1-671b", // Beta model from snippet // Add more model identifiers as needed ]; const STORAGE_KEY = 'selectedChatModel_AIUncensored_v2'; const API_ENDPOINT_PATH = '/api/chat'; // --- Get the initially selected model (or default) --- let selectedModel = GM_getValue(STORAGE_KEY, AVAILABLE_MODELS[0]); // --- Replicated Header Generation Function (based on site's tF) --- const generateAuthHeaders = async (requestBodyObject) => { const timestamp = Math.floor(Date.now() / 1e3).toString(); const bodyString = JSON.stringify(requestBodyObject); const payload = `${timestamp}${bodyString}`; const encoder = new TextEncoder(); // WARNING: Using potentially hardcoded key found in client-side script const secretKey = encoder.encode("your-super-secret-key-replace-in-production"); const dataToSign = encoder.encode(payload); try { const importedKey = await crypto.subtle.importKey( "raw", secretKey, { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ); const signatureBuffer = await crypto.subtle.sign("HMAC", importedKey, dataToSign); const signatureHex = Array.from(new Uint8Array(signatureBuffer)) .map(b => b.toString(16).padStart(2, "0")).join(""); // WARNING: Using potentially hardcoded API key found in client-side script return { "X-API-Key": "62852b00cb9e44bca86f0ec7e7455dc6", "X-Timestamp": timestamp, "X-Signature": signatureHex, "Content-Type": "application/json", // Add other necessary headers from original init if needed (accept, etc.) // It's often safer to merge than replace completely if other headers matter "accept": "*/*", "accept-language": "en-US,en;q=0.9", // Example, might be needed }; } catch (error) { console.error("[Model Selector] Error generating signature:", error); throw error; // Re-throw to indicate failure } }; // --- Intercept Fetch using unsafeWindow --- const originalFetch = unsafeWindow.fetch; unsafeWindow.fetch = async function(input, init) { const url = (input instanceof Request) ? input.url : input; const method = ((init && init.method) || (input instanceof Request && input.method) || 'GET').toUpperCase(); if (url.endsWith(API_ENDPOINT_PATH) && method === 'POST' && init && init.body) { try { // 1. Parse the original body let bodyData = JSON.parse(init.body); // 2. Check if modification is needed if (bodyData && typeof bodyData === 'object' && bodyData.hasOwnProperty('model') && bodyData.model !== selectedModel) { console.log(`[Model Selector] Intercepted ${API_ENDPOINT_PATH}. Original model: ${bodyData.model}`); // 3. Modify the model in the JS object bodyData.model = selectedModel; console.log(`[Model Selector] Modified model to: ${selectedModel}`); // 4. Regenerate Auth Headers using the MODIFIED body data console.log('[Model Selector] Regenerating auth headers...'); const newHeaders = await generateAuthHeaders(bodyData); // 5. Prepare the new init object for fetch const newInit = { ...init, // Copy original init options (method, credentials, etc.) headers: newHeaders, // Use the NEW headers body: JSON.stringify(bodyData) // Use the MODIFIED body string }; console.log('[Model Selector] Sending fetch with new headers and modified body.'); // 6. Call the ORIGINAL fetch with the NEW init data return originalFetch(input, newInit); } else if (bodyData && bodyData.model === selectedModel) { console.log(`[Model Selector] Intercepted ${API_ENDPOINT_PATH}. Model already matches selection (${selectedModel}). Passing through.`); } else { console.warn('[Model Selector] Fetch body did not contain expected "model" property or modification not needed:', bodyData); } } catch (e) { console.error('[Model Selector] Error processing fetch interceptor:', e); // Fall through to original fetch if error occurs } } // For non-matching requests or if modification wasn't needed/failed, call the original fetch return originalFetch(input, init); }; // --- Create Improved UI (No changes needed here) --- function createEnhancedUI() { console.log('[Model Selector] createEnhancedUI called'); // ... (Keep the UI code from version 2.1) ... // Inject Styles GM_addStyle(` /* Toggle Button (Top Left Area - Shifted Right) */ #modelSelectorToggleBtn { position: fixed; top: 16px; /* Align with header buttons vertically */ left: 60px; /* MOVED right - Adjust value if needed */ z-index: 9998; background-color: #2a2a2a; color: #ffffff; border: 1px solid #c15a17; border-radius: 50%; width: 38px; height: 38px; font-size: 20px; cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.3); transition: background-color 0.2s, transform 0.2s; display: flex; align-items: center; justify-content: center; line-height: 1; } #modelSelectorToggleBtn:hover { background-color: #3a3a3a; transform: translateY(-1px); } /* Main Container Panel (Initially Hidden) */ #modelSelectorContainer { position: fixed; top: 65px; /* Position below header */ left: 15px; /* CHANGED FROM right to left */ z-index: 9999; background-color: #1a1a1a; border: 1px solid #c15a17; padding: 0; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.5); font-family: 'Avenir', 'Arial', sans-serif; color: #ffffff; min-width: 280px; display: none; flex-direction: column; } #modelSelectorContainer.visible { display: flex; } /* Panel Header */ #modelSelectorHeader { background-color: #2a2a2a; padding: 8px 12px; border-bottom: 1px solid #c15a17; border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center; font-weight: bold; font-size: 14px; } #modelSelectorHeader span { color: #c15a17; } /* Panel Close Button */ #modelSelectorCloseBtn { background: none; border: none; color: #ffffff; font-size: 22px; font-weight: bold; cursor: pointer; padding: 0 4px; line-height: 1; opacity: 0.7; } #modelSelectorCloseBtn:hover { opacity: 1; } /* Panel Body */ #modelSelectorBody { padding: 15px; display: flex; flex-direction: column; gap: 8px; } /* Label */ #modelSelectorContainer label { margin: 0; font-weight: normal; color: #dddddd; font-size: 13px; } /* Dropdown */ #modelSelectorDropdown { width: 100%; padding: 8px 10px; background-color: #333333; color: #ffffff; border: 1px solid #c15a17; border-radius: 5px; font-size: 14px; box-sizing: border-box; } #modelSelectorDropdown option { background-color: #333333; color: #ffffff; padding: 5px 8px; } `); // Create Toggle Button const toggleBtn = document.createElement('button'); toggleBtn.id = 'modelSelectorToggleBtn'; toggleBtn.innerHTML = '⚙️'; toggleBtn.title = 'Toggle Model Selector'; // Create Panel Container const container = document.createElement('div'); container.id = 'modelSelectorContainer'; // Create Panel Header const header = document.createElement('div'); header.id = 'modelSelectorHeader'; const title = document.createElement('span'); title.textContent = 'Model Settings'; const closeBtn = document.createElement('button'); closeBtn.id = 'modelSelectorCloseBtn'; closeBtn.innerHTML = '×'; closeBtn.title = 'Close'; header.appendChild(title); header.appendChild(closeBtn); // Create Panel Body const body = document.createElement('div'); body.id = 'modelSelectorBody'; // Create Label const label = document.createElement('label'); label.htmlFor = 'modelSelectorDropdown'; label.textContent = 'Active Model:'; // Create Dropdown const select = document.createElement('select'); select.id = 'modelSelectorDropdown'; // Populate Dropdown AVAILABLE_MODELS.forEach(model => { const option = document.createElement('option'); option.value = model; option.textContent = model.includes('/') ? model.split('/')[1] : model; option.title = model; if (model === selectedModel) { option.selected = true; } select.appendChild(option); }); // Assemble Panel Body body.appendChild(label); body.appendChild(select); // Assemble Panel Container container.appendChild(header); container.appendChild(body); // Append elements to the actual page body document.body.appendChild(toggleBtn); document.body.appendChild(container); // --- Add Event Listeners --- toggleBtn.addEventListener('click', () => { container.classList.toggle('visible'); }); closeBtn.addEventListener('click', () => { container.classList.remove('visible'); }); select.addEventListener('change', (event) => { selectedModel = event.target.value; GM_setValue(STORAGE_KEY, selectedModel); console.log(`[Model Selector] Model selection changed to: ${selectedModel}`); toggleBtn.style.borderColor = '#4caf50'; setTimeout(() => { toggleBtn.style.borderColor = '#c15a17'; }, 500); }); } // --- Initialize UI --- if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createEnhancedUI); } else { createEnhancedUI(); } })();