您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a button to uwuify ChatGPT responses, preserving formatting.
// ==UserScript== // @name ChatGPT UwUifier // @namespace https://github.com/rastr1sr // @version 1.0 // @description Adds a button to uwuify ChatGPT responses, preserving formatting. // @author Rastrisr // @match *://chat.openai.com/* // @match *://chatgpt.com/* // @grant none // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- Configuration --- const UWU_BUTTON_ID = 'uwuifier-button'; const ORIGINAL_CONTENT_ATTR = 'data-original-content-uwu'; const ASSISTANT_MESSAGE_SELECTOR = '[data-message-author-role="assistant"]'; const CONTENT_SELECTORS = [ '.markdown', '[class*="prose"]', 'div.text-message', 'div > p' ]; const DEBOUNCE_DELAY = 300; const TAGS_TO_SKIP_UWUIFY = new Set(['PRE', 'CODE', 'SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'BUTTON']); // --- State --- let isUwuActive = false; let uwuButton = null; let modifiedElements = new Set(); let initTimeoutId = null; let observerDebounceTimeout = null; // --- Core UwUify Logic (Text Only) --- function uwuifyText(text) { return text .replace(/(?:[rl])(?![aeiou])/gi, (match) => (match === 'r' || match === 'l' ? 'w' : 'W')) .replace(/[rl](?=[aeiou])/gi, 'w') .replace(/[RL](?=[AEIOU])/g, 'W') .replace(/ove/gi, 'uv') .replace(/O([^a-zA-Z]|$)/g, 'OwO$1') .replace(/n([aeiou])/gi, (match, p1) => Math.random() > 0.4 ? `ny${p1}` : match) .replace(/\b(Y)ou\b/g, 'Yuw').replace(/\b(y)ou\b/g, 'yuw') .replace(/\b(T)he\b/g, 'Da').replace(/\b(t)he\b/g, 'da') .replace(/\b(Y)ou're\b/g, "Yuw'we").replace(/\b(y)ou're\b/g, "yuw'we") // ... More text replacement rules here, reply in comments if you have any suggestions ... .replace(/([.!?])\s+/g, (match, p1) => { const random = Math.random(); if (random < 0.05) return `${p1} uwu `; if (random < 0.10) return `${p1} owo `; if (random < 0.15) return `${p1} >w< `; return match; }) .replace(/(!+)/g, (match) => Math.random() < 0.7 ? `${match}~` : match) .replace(/([.!?])($|\n)/gm, (match, p1, p2) => { const random = Math.random(); let suffix = ''; if (random < 0.08) suffix = ` (✿◠‿◠)`; else if (random < 0.16) suffix = ` (◕ᴗ◕✿)`; else if (random < 0.24) suffix = ` (。◕‿◕。)`; return `${p1}${suffix}${p2}`; }); } // --- DOM Processing --- function traverseAndUwuify(node) { if (node.nodeType === Node.TEXT_NODE) { if (node.parentNode && TAGS_TO_SKIP_UWUIFY.has(node.parentNode.tagName)) { return; } const trimmedText = node.nodeValue.trim(); if (trimmedText.length > 0) { // Check for our placeholders - DO NOT uwuify them if (!trimmedText.startsWith('[[CODE_BLOCK_') && !trimmedText.startsWith('[[INLINE_CODE_')) { node.nodeValue = uwuifyText(node.nodeValue); } } } else if (node.nodeType === Node.ELEMENT_NODE) { if (TAGS_TO_SKIP_UWUIFY.has(node.tagName)) { return; } node.childNodes.forEach(traverseAndUwuify); } } function uwuifyHtmlContent(htmlString) { // Protect code blocks and inline code with placeholders const codeBlocks = []; let processedHtml = htmlString.replace(/```([\s\S]*?)```/g, (match, codeContent) => { const placeholder = `<pre class="uwu-placeholder-block">[[CODE_BLOCK_${codeBlocks.length}]]</pre>`; codeBlocks.push(match); return placeholder; }); const inlineCode = []; processedHtml = processedHtml.replace(/`([^`]+?)`/g, (match, codeContent) => { const placeholder = `<code class="uwu-placeholder-inline">[[INLINE_CODE_${inlineCode.length}]]</code>`; inlineCode.push(match); return placeholder; }); // Parse the HTML with placeholders into a DOM fragment const tempDiv = document.createElement('div'); tempDiv.innerHTML = processedHtml; // Traverse the DOM fragment and apply uwuifyText to safe text nodes traverseAndUwuify(tempDiv); // Serialize the modified DOM fragment back to an HTML string let uwuifiedHtml = tempDiv.innerHTML; // Restore original code blocks and inline code uwuifiedHtml = uwuifiedHtml.replace(/<pre class="uwu-placeholder-block">\[\[CODE_BLOCK_(\d+)\]\]<\/pre>/g, (match, index) => { return codeBlocks[parseInt(index, 10)] || match; }); uwuifiedHtml = uwuifiedHtml.replace(/<code class="uwu-placeholder-inline">\[\[INLINE_CODE_(\d+)\]\]<\/code>/g, (match, index) => { return inlineCode[parseInt(index, 10)] || match; }); return uwuifiedHtml; } // --- DOM Manipulation --- function findContentElements(parentElement) { for (const selector of CONTENT_SELECTORS) { const elements = parentElement.querySelectorAll(selector); if (elements.length > 0) { const filtered = Array.from(elements).filter(el => el.textContent.trim().length > 0 && !el.querySelector(CONTENT_SELECTORS.join(','))); if (filtered.length > 0) return filtered; return Array.from(elements); } } // Fallback: Look for direct div children that aren't just simple containers const directDivs = parentElement.querySelectorAll(':scope > div:not(:has(button):only-child)'); if (directDivs.length > 0) return Array.from(directDivs).filter(el => el.textContent.trim().length > 10); // Fallback: Use the parent itself if it has significant text and few children if (parentElement.textContent.trim().length > 50 && parentElement.childElementCount < 5) { return [parentElement]; } return []; } function applyUwU() { modifiedElements.clear(); const assistantMessages = document.querySelectorAll(ASSISTANT_MESSAGE_SELECTOR); assistantMessages.forEach(messageContainer => { const contentElements = findContentElements(messageContainer); contentElements.forEach(element => { if (element.closest(ASSISTANT_MESSAGE_SELECTOR) !== messageContainer || modifiedElements.has(element)) { return; } if (!element.hasAttribute(ORIGINAL_CONTENT_ATTR)) { element.setAttribute(ORIGINAL_CONTENT_ATTR, element.innerHTML); } const originalContent = element.getAttribute(ORIGINAL_CONTENT_ATTR); if (originalContent !== null) { element.innerHTML = uwuifyHtmlContent(originalContent); modifiedElements.add(element); } }); }); } function revertUwU() { modifiedElements.forEach(element => { if (element.hasAttribute(ORIGINAL_CONTENT_ATTR)) { element.innerHTML = element.getAttribute(ORIGINAL_CONTENT_ATTR); } }); } function toggleUwU() { isUwuActive = !isUwuActive; if (isUwuActive) { applyUwU(); uwuButton.textContent = 'De-UwUify'; uwuButton.setAttribute('data-active', 'true'); } else { revertUwU(); uwuButton.textContent = 'UwUify'; uwuButton.setAttribute('data-active', 'false'); } } // --- Button Creation and Injection --- function createUwUButton() { const button = document.createElement('button'); button.id = UWU_BUTTON_ID; button.className = 'uwu-button flex items-center justify-center rounded-md border h-9 w-auto px-3 py-2 text-sm font-medium transition-colors'; button.textContent = 'UwUify'; button.setAttribute('data-active', 'false'); button.addEventListener('click', toggleUwU); return button; } function addStyles() { const styleId = 'uwuifier-styles'; if (document.getElementById(styleId)) return; const style = document.createElement('style'); style.id = styleId; style.textContent = ` #${UWU_BUTTON_ID} { position: fixed; top: 60px; right: 20px; z-index: 9999; cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.2); font-family: inherit; background-color: var(--button-secondary-background, #ffffff); color: var(--button-secondary-color, #374151); border-color: var(--button-secondary-border-color, #d1d5db); transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease, border-color 0.2s ease; } #${UWU_BUTTON_ID}:hover { background-color: var(--button-secondary-background-hover, #f3f4f6); transform: scale(1.03); } #${UWU_BUTTON_ID}[data-active="true"] { background-color: #d8b4fe; border-color: #c084fc; color: #3b0764; } #${UWU_BUTTON_ID}[data-active="true"]:hover { background-color: #c084fc; } @media (prefers-color-scheme: dark) { #${UWU_BUTTON_ID} { background-color: #374151; color: #d1d5db; border-color: #4b5563; } #${UWU_BUTTON_ID}:hover { background-color: #4b5563; color: #f9fafb; } #${UWU_BUTTON_ID}[data-active="true"] { background-color: #a855f7; border-color: #9333ea; color: #ffffff; } #${UWU_BUTTON_ID}[data-active="true"]:hover { background-color: #9333ea; } } `; document.head.appendChild(style); } function initialize() { if (initTimeoutId) clearTimeout(initTimeoutId); if (document.getElementById(UWU_BUTTON_ID)) { if (!uwuButton) uwuButton = document.getElementById(UWU_BUTTON_ID); if (uwuButton && uwuButton.getAttribute('data-active') !== String(isUwuActive)) { uwuButton.setAttribute('data-active', String(isUwuActive)); uwuButton.textContent = isUwuActive ? 'De-UwUify' : 'UwUify'; } return; } const chatContainer = document.querySelector('main') || document.body; if (chatContainer) { addStyles(); uwuButton = createUwUButton(); document.body.appendChild(uwuButton); if (isUwuActive) { applyUwU(); } } else { initTimeoutId = setTimeout(initialize, 1000); } } // --- Mutation Observer --- const observer = new MutationObserver((mutations) => { let potentiallyNewContent = false; let needsButtonCheck = !document.getElementById(UWU_BUTTON_ID); for (const mutation of mutations) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { if (needsButtonCheck) { potentiallyNewContent = true; break; } for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { if ((node.matches && node.matches(ASSISTANT_MESSAGE_SELECTOR)) || (node.querySelector && node.querySelector(ASSISTANT_MESSAGE_SELECTOR))) { potentiallyNewContent = true; break; } } } } if (potentiallyNewContent && !needsButtonCheck) break; } if (potentiallyNewContent || needsButtonCheck) { clearTimeout(observerDebounceTimeout); observerDebounceTimeout = setTimeout(() => { initialize(); if (isUwuActive && potentiallyNewContent) { applyUwU(); } }, DEBOUNCE_DELAY); } }); // --- Start Script --- observer.observe(document.body, { childList: true, subtree: true }); initialize(); })();