// ==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 });
})();