您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Config via GM Menu, No UI.
// ==UserScript== // @name Stripped RYW Features // @version 1.0 // @author kevoting (Stripped by AI) // @description Config via GM Menu, No UI. // @match https://character.ai/* // @icon https://www.google.com/s2/favicons?sz=64&domain=character.ai // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @run-at document-start // @namespace https://greasyfork.org/users/1077492 // ==/UserScript== (function() { 'use strict'; // --- Configuration via Tampermonkey/Greasemonkey Menu --- // Constants and Globals const B64_COMMON_WORDS_LIST_V2 = "I1lvdSBrbm93LCBpdCdzIGEgbG90IG9mIHRleHQsIHlvdSBkb24ndCBoYXZlIHRvIG1ha2Ugc3VjaCBhIGJpZyBkZWFsIGFib3V0IGl0Lgp7MTAxfT1wdXNzeQp7MTAyfT1hc3MKezEwM309YnJlYXN0cwp7MTA0fT10aXRzCnsxMDV9PW5pcHBsZXMKezEwNn09YmFsbHMKezEwOH09ZGljawp7MTA5fT1jb2NrCnsxMTB9PXRpZ2h0CnsxMTF9PXdldAp7MTEyfT1wdWxzYXRpbmcKezExM309cmlnaWQKezExNH09c3RpZmYKezExNX09ZHJpcHBpbmcKezExNn09aG9ybnkKezExOH09aGFyZAp7MTIwfT1ibG93am9iCnsxMjF9PXRpdGpvYgp7MTIzfT1kZWVwdGhyb2F0CnsxMjV9PWZ1Y2sKezEzMH09bGljawp7MTMxfT1saWNraW5nCnsxNDB9PWN1bQp7MTQxfT1jdW1taW5nCnsxNDR9PXByZWN1bQp7MTQ1fT1zZW1lbgp7MTUxfT1maW5nZXIKezE1M309ZmluZ2VyaW5nCnsxNjF9PXN1Y2sKezE2M309c3Vja2luZwp7MTcwfT1zcHJlYWQKezE5MX09cnViCnsxOTN9PXJ1YmJpbmcKezIwMH09aGFuZAp7MjAxfT1tb3V0aAp7MjAyfT10b25ndWUKezIwM309dGhyb2F0CnsyMDR9PWNsaXQKezIxMH09bWFzdHVyYmF0ZQp7MjExfT1tYXN0dXJiYXRpbmcKezIzMH09c3dhbGxvdwp7MjMxfT1zd2FsbG93cwp7MjMyfT1zd2FsbG93aW5nCnsyMzN9PXN3YWxsb3dlZAp7MjUwfT1kZWVwCnsyNTF9PWRlZXBlcgp7MjUyfT10aHJ1c3QKezI1M309dGhydXN0aW5nCnsyNTR9PWluc2lkZQp7MzAwfT1pbnNlcnQKezM0MH09cGFudGllcwp7MzQxfT1jdW50CnszNDJ9PXNxdWlydA=="; const NEO_URL = "wss://neo.character.ai/ws/"; const ANNOTATION_URL = "https://neo.character.ai/annotations/create"; const TURNS_RGX = /https:\/\/neo\.character\.ai\/turns\/[\w-]+\//gm; const SENTRY_URL = "sentry.io"; const EVENTS_URL = "events.character.ai"; const CLOUD_MONITORING_NAME = "datadoghq"; const ENABLE_TURN_CHANGER = true; const NO_ERROR_REPORTING = true; const NO_TRACKING = true; const NO_MONITORING = true; const fetchFn = window.fetch; const websocketFn = window.WebSocket; const sendSocketfn = window.WebSocket.prototype.send; const open_prototype = XMLHttpRequest.prototype.open; let neo_socket = null; let neo_payload_origin = null; let injected_last = null; let turns_since_last_inject = 0; let pending_payload = null; let waiting_request_id = null; let currentConfuserLevel; const kvp = new Map(); const references = new Map(); const references_compare = new Map(); const rgx_str_v2 = /\{(\d+)}/; // --- Menu Command Functions --- function setConfuserLevel(level) { level = parseFloat(level); // Changed to float to handle 0.5 if (isNaN(level) || level < 0 || level > 2) { console.error("[RYW Stripped] Invalid confuser level:", level); return; } currentConfuserLevel = level; GM_setValue("ryw_confuserLevel", level); console.log(`[RYW Stripped] Confuser level set to: ${level}. Reload page to see menu indicator update.`); } function setLevel0() { setConfuserLevel(0); } function setLevel05() { setConfuserLevel(0.5); } // New Level 0.5 function setLevel1() { setConfuserLevel(1); } function setLevel2() { setConfuserLevel(2); } function registerCommands() { GM_registerMenuCommand(`${currentConfuserLevel === 0 ? '[*]' : '[ ]'} Set Confuser: Off`, setLevel0); GM_registerMenuCommand(`${currentConfuserLevel === 0.5 ? '[*]' : '[ ]'} Set Confuser: Very Low (Word-End ZW)`, setLevel05); // New menu option GM_registerMenuCommand(`${currentConfuserLevel === 1 ? '[*]' : '[ ]'} Set Confuser: Low (Zero-Width)`, setLevel1); GM_registerMenuCommand(`${currentConfuserLevel === 2 ? '[*]' : '[ ]'} Set Confuser: Medium (Word Replace)`, setLevel2); } // --- Utility Functions --- function decodeBase64(base64) { try { const text = atob(base64); const length = text.length; const bytes = new Uint8Array(length); for (let i = 0; i < length; i++) { bytes[i] = text.charCodeAt(i); } const decoder = new TextDecoder(); return decoder.decode(bytes); } catch (e) { console.error("Failed to decode base64 string:", e); return ""; } } function loadWords() { kvp.clear(); const str = decodeBase64(B64_COMMON_WORDS_LIST_V2); const regex_v2 = /(\{\d+\})=(.+)/gm; let m; while ((m = regex_v2.exec(str)) !== null) { if (m.index === regex_v2.lastIndex) { regex_v2.lastIndex++; } if (m[1] && m[2]) { kvp.set(m[2].trim(), m[1]); } } console.log(`[RYW Stripped] Loaded ${kvp.size} words for obfuscation.`); } function uuidV4() { const uuid = new Array(36); for (let i = 0; i < 36; i++) { uuid[i] = Math.floor(Math.random() * 16); } uuid[14] = 4; uuid[19] = (uuid[19] & 0x3) | 0x8; uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; return uuid.map((x) => x.toString(16)).join(''); } function getWordEndZeroWidthText(text) { // New function for Level 0.5 const zSpace = "\u200b"; return text.split(" ").map(word => word + zSpace).join(" ").trim(); } function getZeroWidthText(text) { // Existing Level 1 function const zSpace = "\u200b"; return text.split(" ").map(word => { if (!word) return ""; return word.split('').join(zSpace); }).join(" "); } function hasRefValid(word) { return references.has(word); } function areMapsEqual(map1, map2) { if (map1.size !== map2.size) return false; for (let [key, value] of map1) { if (!map2.has(key) || map2.get(key) !== value) return false; } return true; } function parseAndFindCoincidences(txt, inverse = false) { if (typeof txt !== 'string') return { newtext: '', coincidences: new Map() }; txt = txt.replaceAll("\u200b", ""); const words = txt.split(/(\s+)/); const newParts = []; const coincidences = new Map(); const wordEnders = ".,!?;:)]\"'*~="; words.forEach(part => { if (part.match(/^\s+$/)) { newParts.push(part); return; } if (!part) { return; } let currentWord = part; if (inverse) { const match = currentWord.match(/^(\{\d+\})(.*)$/); if (match) { const code = match[1]; const suffix = match[2] || ""; let foundWord = null; for (let [wordKey, codeValue] of kvp) { if (codeValue === code) { foundWord = wordKey; break; } } if (foundWord) { currentWord = foundWord + suffix; } } } else { kvp.forEach((codeValue, wordKey) => { if (currentWord === wordKey || (currentWord.startsWith(wordKey) && (currentWord.length === wordKey.length || wordEnders.includes(currentWord[wordKey.length])))) { const suffix = currentWord.substring(wordKey.length); currentWord = codeValue + suffix; coincidences.set(wordKey, codeValue); } }); } newParts.push(currentWord); }); return { newtext: newParts.join(""), coincidences: coincidences }; } // --- Core Logic --- function tryPreProcess(json) { if (currentConfuserLevel < 2) return false; turns_since_last_inject++; if (turns_since_last_inject >= 10) { references.clear(); } const original_payload = JSON.parse(JSON.stringify(json)); let raw_text = json.payload.turn.candidates[0].raw_content; if (!raw_text) return false; const obj = parseAndFindCoincidences(raw_text, false); if (obj.coincidences.size > 0) { console.log("[RYW Stripped] Found words to obfuscate (Level 2):", Array.from(obj.coincidences.keys())); json.payload.turn.candidates[0].raw_content = obj.newtext; pending_payload = json; obj.coincidences.forEach((value, key) => { if (!hasRefValid(key)) { references.set(key, value); } }); let helper_text = "<!-- RYW Helper -->\n"; references.forEach((value, key) => { helper_text += `${value}=${key}\n`; }); helper_text += "<!-- End RYW Helper -->"; if (injected_last == null || turns_since_last_inject >= 10 || !areMapsEqual(references, references_compare)) { console.log("[RYW Stripped] Injecting/Updating helper message (Level 2)."); references_compare.clear(); references.forEach((v, k) => references_compare.set(k, v)); if (injected_last?.turn_key && injected_last?.candidates?.[0]?.candidate_id) { try { const editPayload = { command: "edit_turn_candidate", request_id: uuidV4(), payload: { new_candidate_raw_content: helper_text, turn_key: injected_last.turn_key, candidate_id: injected_last.candidates[0].candidate_id }, origin_id: neo_payload_origin }; sendSocketfn.call(neo_socket, JSON.stringify(editPayload)); return true; } catch (editError) { console.error("[RYW Stripped] Failed to edit helper message, will create new:", editError); injected_last = null; } } const helper_payload = JSON.parse(JSON.stringify(original_payload)); helper_payload.request_id = uuidV4(); helper_payload.command = "create_turn"; helper_payload.payload.turn.turn_key = { turn_id: uuidV4(), chat_id: json.payload.turn.turn_key.chat_id }; const newCandidateId = uuidV4(); helper_payload.payload.turn.candidates = [{ candidate_id: newCandidateId, raw_content: helper_text, vendor_score: 0, create_time: new Date().toISOString(), }]; helper_payload.payload.turn.primary_candidate_id = newCandidateId; helper_payload.payload.turn.author = { author_id: "user", is_human: true, name: "You" }; waiting_request_id = helper_payload.request_id; sendSocketfn.call(neo_socket, JSON.stringify(helper_payload)); return true; } else { console.log("[RYW Stripped] Helper message up-to-date, sending user message directly (Level 2)."); sendSocketfn.call(neo_socket, JSON.stringify(pending_payload)); pending_payload = null; return true; } } return false; } // --- Network Interception --- function patchedSend(...args) { if (!this.url || !this.url.startsWith(NEO_URL)) { return sendSocketfn.call(this, ...args); } if (neo_socket !== this) { if (neo_socket) { neo_socket.removeEventListener("message", neoSocketMessage); } neo_socket = this; neo_socket.addEventListener("message", neoSocketMessage); } try { const json = JSON.parse(args[0]); if (json.origin_id && !neo_payload_origin) { neo_payload_origin = json.origin_id; } if (json.command === "create_and_generate_turn") { if (currentConfuserLevel === 0.5 && json?.payload?.turn?.candidates?.[0]?.raw_content) { console.log("[RYW Stripped] Applying Level 0.5 Obfuscation (Word-End Zero-Width)"); json.payload.turn.candidates[0].raw_content = getWordEndZeroWidthText(json.payload.turn.candidates[0].raw_content); args[0] = JSON.stringify(json); } else if (currentConfuserLevel === 1 && json?.payload?.turn?.candidates?.[0]?.raw_content) { console.log("[RYW Stripped] Applying Level 1 Obfuscation (Zero-Width)"); json.payload.turn.candidates[0].raw_content = getZeroWidthText(json.payload.turn.candidates[0].raw_content); args[0] = JSON.stringify(json); } else if (currentConfuserLevel === 2 && json?.payload?.turn?.candidates?.[0]?.raw_content) { if (tryPreProcess(json)) { return; } if (pending_payload) { console.warn("[RYW Stripped] Pending payload exists but tryPreProcess returned false. Sending pending."); args[0] = JSON.stringify(pending_payload); pending_payload = null; } } return sendSocketfn.call(this, args[0]); } } catch (e) { /* console.error("[RYW Stripped] Error processing WebSocket send:", e, args[0]); */ } return sendSocketfn.call(this, ...args); } function neoSocketMessage(event) { try { const json = JSON.parse(event.data); if (waiting_request_id && json.request_id === waiting_request_id && json.command === "add_turn") { console.log("[RYW Stripped] Helper message turn added (Level 2)."); injected_last = json.turn; turns_since_last_inject = 0; waiting_request_id = null; if (pending_payload) { console.log("[RYW Stripped] Sending pending user message (Level 2)."); sendSocketfn.call(neo_socket, JSON.stringify(pending_payload)); pending_payload = null; } } else if (waiting_request_id && json.request_id === waiting_request_id && json.command === "neo_error") { console.error("[RYW Stripped] Error creating helper message (Level 2):", json.comment); waiting_request_id = null; injected_last = null; if (pending_payload) { console.warn("[RYW Stripped] Sending pending user message after helper creation failed (Level 2)."); sendSocketfn.call(neo_socket, JSON.stringify(pending_payload)); pending_payload = null; } } } catch (e) { /* console.error("[RYW Stripped] Error processing WebSocket message:", e, event.data); */ } } function PatchedWebSocket(url, protocols) { const ws = new websocketFn(url, protocols); if (url === NEO_URL) { if (neo_socket !== ws) { if (neo_socket) { neo_socket.removeEventListener("message", neoSocketMessage); } neo_socket = ws; neo_socket.addEventListener("message", neoSocketMessage); } } return ws; } PatchedWebSocket.prototype = websocketFn.prototype; PatchedWebSocket.prototype.constructor = PatchedWebSocket; function patchedOpen(...args) { const url = args[1]; if (url && (url.includes(ANNOTATION_URL) || url.includes("neo.character.ai/annotations"))) { this.send = function() {}; return; } if (url && url.includes("https://neo.character.ai/get-available-models")) { this.addEventListener('load', function() { if (this.readyState === 4 && this.status === 200) { const fakeResponse = JSON.stringify({ "available_models": ["MODEL_TYPE_FAST", "MODEL_TYPE_SMART", "MODEL_TYPE_BALANCED", "MODEL_TYPE_FAMILY_FRIENDLY", "MODEL_TYPE_MEMORY_OPTIMIZED", "MODEL_TYPE_MULTILINGUAL", "MODEL_TYPE_DYNAMIC", "MODEL_TYPE_THINKING"] }); Object.defineProperty(this, 'responseText', { value: fakeResponse, writable: false }); Object.defineProperty(this, 'response', { value: fakeResponse, writable: false }); } }); } if (ENABLE_TURN_CHANGER && url && url.match(TURNS_RGX)) { this.addEventListener('load', function() { if (this.readyState === 4 && this.status === 200 && this.responseText) { try { let json = JSON.parse(this.responseText); let changed = false; if (json?.turns?.length > 0) { json.turns.forEach(turn => { turn?.candidates?.forEach(candidate => { if (candidate?.raw_content) { const original = candidate.raw_content; candidate.raw_content = parseAndFindCoincidences(original, true).newtext; if (original !== candidate.raw_content) changed = true; } }); }); } if (changed) { const modifiedResponse = JSON.stringify(json); Object.defineProperty(this, 'responseText', { value: modifiedResponse, writable: false }); Object.defineProperty(this, 'response', { value: modifiedResponse, writable: false }); } } catch (e) { /* Error */ } } }); } return open_prototype.apply(this, args); } async function patchedFetch(...args) { const url = args[0] instanceof Request ? args[0].url : args[0]; if ((url.includes(SENTRY_URL) && NO_ERROR_REPORTING) || (url.includes(EVENTS_URL) && NO_TRACKING) || (url.includes(CLOUD_MONITORING_NAME) && NO_MONITORING) || url.includes(ANNOTATION_URL) || url.includes("neo.character.ai/annotations")) { return Promise.reject(new Error("Blocked by RYW Stripped")); } if (url && url.includes("https://neo.character.ai/get-available-models")) { const fakeResponse = JSON.stringify({ "available_models": ["MODEL_TYPE_FAST", "MODEL_TYPE_SMART", "MODEL_TYPE_BALANCED", "MODEL_TYPE_FAMILY_FRIENDLY", "MODEL_TYPE_MEMORY_OPTIMIZED", "MODEL_TYPE_MULTILINGUAL", "MODEL_TYPE_DYNAMIC", "MODEL_TYPE_THINKING"] }); return Promise.resolve(new Response(fakeResponse, { status: 200, headers: { 'Content-Type': 'application/json' } })); } if (ENABLE_TURN_CHANGER && url && url.match(TURNS_RGX)) { return fetchFn(...args).then(async response => { if (!response.ok || !response.body) return response; try { const clonedResponse = response.clone(); let json = await clonedResponse.json(); let changed = false; if (json?.turns?.length > 0) { json.turns.forEach(turn => { turn?.candidates?.forEach(candidate => { if (candidate?.raw_content) { const original = candidate.raw_content; candidate.raw_content = parseAndFindCoincidences(original, true).newtext; if (original !== candidate.raw_content) changed = true; } }); }); } if (changed) { const modifiedBody = JSON.stringify(json); return new Response(modifiedBody, { status: response.status, statusText: response.statusText, headers: response.headers }); } } catch (e) { /* Error */ } return response; }); } return fetchFn(...args); } // --- Initialization --- function applyNetworkPatches() { if (!window._rywNetworkPatched) { console.log("[RYW Stripped] Applying network patches."); window.WebSocket = PatchedWebSocket; window.WebSocket.prototype.send = patchedSend; XMLHttpRequest.prototype.open = patchedOpen; window.fetch = patchedFetch; window._rywNetworkPatched = true; loadWords(); } } function initialize() { currentConfuserLevel = GM_getValue("ryw_confuserLevel", 0); // Default to Level 0 console.log(`[RYW Stripped] Initializing Menu. Loaded Level: ${currentConfuserLevel}`); registerCommands(); applyNetworkPatches(); console.log(`[RYW Stripped] Initialized.`); } initialize(); window.addEventListener("DOMContentLoaded", applyNetworkPatches, { once: true }); })();