您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Bypass Drawaria.online censorship by converting text to bold Unicode characters.
// ==UserScript== // @name Drawaria No Censor Words // @namespace http://tampermonkey.net/ // @version 1.0 // @description Bypass Drawaria.online censorship by converting text to bold Unicode characters. // @author YouTubeDrawaria // @match https://drawaria.online/* // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // A simple utility to create DOM elements, ensuring the script is truly standalone. const domMake = { Tree: function(tagName, attributes = {}, children = []) { const element = document.createElement(tagName); for (const key in attributes) { if (attributes.hasOwnProperty(key)) { if (key === 'style' && typeof attributes[key] === 'string') { element.style.cssText = attributes[key]; } else if (key in element && tagName !== 'input') { // Avoid setting value attribute as property for inputs element[key] = attributes[key]; } else { element.setAttribute(key, attributes[key]); } } } children.forEach(child => { if (typeof child === 'string') { element.appendChild(document.createTextNode(child)); } else if (child instanceof Node) { element.appendChild(child); } }); return element; }, Button: function(text, attributes = {}) { const button = document.createElement('button'); button.textContent = text; for (const key in attributes) { if (attributes.hasOwnProperty(key)) { if (key === 'style' && typeof attributes[key] === 'string') { button.style.cssText = attributes[key]; } else { button.setAttribute(key, attributes[key]); } } } return button; } }; // --- Character map for Bold Text --- const BOLD_TEXT_MAP = { 'A': '𝗔', 'B': '𝗕', 'C': '𝗖', 'D': '𝗗', 'E': '𝗘', 'F': '𝗙', 'G': '𝗚', 'H': '𝗛', 'I': '𝗜', 'J': '𝗝', 'K': '𝗞', 'L': '𝗟', 'M': '𝗠', 'N': '𝗡', 'O': '𝗢', 'P': '𝗣', 'Q': '𝗤', 'R': '𝗥', 'S': '𝗦', 'T': '𝗧', 'U': '𝗨', 'V': '𝗩', 'W': '𝗪', 'X': '𝗫', 'Y': '𝗬', 'Z': '𝗭', 'a': '𝗮', 'b': '𝗯', 'c': '𝗰', 'd': '𝗱', 'e': '𝗲', 'f': '𝗳', 'g': '𝗴', 'h': '𝗵', 'i': '𝗶', 'j': '𝗷', 'k': '𝗸', 'l': '𝗹', 'm': '𝗺', 'n': '𝗻', 'o': '𝗼', 'p': '𝗽', 'q': '𝗾', 'r': '𝗿', 's': '𝘀', 't': '𝘁', 'u': '𝘂', 'v': '𝘃', 'w': '𝘄', 'x': '𝘅', 'y': '𝘆', 'z': '𝘇', '0': '𝟬', '1': '𝟭', '2': '𝟮', '3': '𝟯', '4': '𝟰', '5': '𝟱', '6': '𝟲', '7': '𝟳', '8': '𝟴', '9': '𝟵' }; class NoCensorWords { #panel = null; #toggleButton = null; #isCensorBypassActive = false; #chatInputObserver = null; #currentChatInput = null; // Reference to the currently active chat input element #inputListener = null; // Store the listener reference for proper removal #observerTarget = null; #_attachInputDebounceTimer = null; // Private property for the debounce timer constructor() { console.log("NoCensorWords: Initializing..."); this.#addStyles(); this.#createPanel(); this.#setupChatInputMonitoring(); console.log("NoCensorWords: Initialization complete."); } #addStyles() { const style = document.createElement('style'); style.textContent = ` .no-censor-button { background: #f0f0f0; /* Light background */ color: #333; /* Dark text */ border: 1px solid #ccc; border-radius: 5px; padding: 8px 12px; font-size: 1em; cursor: pointer; transition: background 0.16s ease-in-out, color 0.16s ease-in-out; width: 100%; /* Make button fill panel width */ box-sizing: border-box; /* Include padding and border in width */ } .no-censor-button:hover { background: #e0e0e0; /* Slightly darker on hover */ } .no-censor-button.active { background: #4CAF50; /* Green for active */ border-color: #4CAF50; color: #fff; /* White text for active */ font-weight: bold; } .no-censor-button.active:hover { background: #5cb85c; } .no-censor-panel { position: fixed; top: 100px; /* Default position */ right: 20px; width: 220px; /* Adjusted width to be more compact */ background: #ffffff; /* White background for the panel */ border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 100000; /* High z-index to be on top */ color: #333; /* Default text color for panel content */ font-family: Arial, sans-serif; padding: 10px; box-sizing: border-box; user-select: none; /* Prevent text selection on the panel itself */ } .no-censor-panel-header { cursor: grab; padding: 5px; background: #f0f0f0; /* Light header background */ border-bottom: 1px solid #e0e0e0; margin: -10px -10px 10px -10px; /* Negative margin to pull it to edges */ border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center; user-select: none; /* Prevent text selection on the header */ } .no-censor-panel-header span { font-weight: bold; font-size: 1.1em; margin-left: 10px; color: #333; /* Dark text for title */ } .no-censor-panel-close-btn { background: none; border: none; color: #666; /* Darker close button color */ font-size: 1.5em; cursor: pointer; padding: 0 5px; } .no-censor-panel-close-btn:hover { color: #ff0000; } `; document.head.appendChild(style); console.log("NoCensorWords: Styles added."); } #createPanel() { this.#panel = domMake.Tree('div', { class: 'no-censor-panel' }); const header = domMake.Tree('div', { class: 'no-censor-panel-header' }); const title = domMake.Tree('span', {}, ["No Censor Words"]); const closeButton = domMake.Button('×', { class: 'no-censor-panel-close-btn' }); closeButton.onclick = () => { this.#panel.remove(); if (this.#chatInputObserver) { this.#chatInputObserver.disconnect(); console.log("NoCensorWords: MutationObserver disconnected on panel close."); } if (this.#currentChatInput && this.#inputListener) { this.#currentChatInput.removeEventListener('input', this.#inputListener); console.log("NoCensorWords: Chat input listener removed on panel close."); } console.log("NoCensorWords: Panel removed."); }; // FIX: Use standard appendChild instead of custom appendAll header.appendChild(title); header.appendChild(closeButton); this.#panel.appendChild(header); this.#toggleButton = domMake.Button('Activar Bypass', { class: 'no-censor-button' }); this.#toggleButton.onclick = () => this.#toggleCensorBypass(); this.#panel.appendChild(this.#toggleButton); document.body.appendChild(this.#panel); this.#makeDraggable(this.#panel, header); console.log("NoCensorWords: Panel created and appended to body."); } #makeDraggable(element, handle) { let offsetX, offsetY; let isDragging = false; const startDrag = (e) => { // Ensure event target is the handle itself, not a child of the handle if (e.target !== handle && e.target !== handle.firstElementChild) { // Check if the target is the close button, don't drag if so if (e.target.classList.contains('no-censor-panel-close-btn')) { return; } } e.preventDefault(); // Prevent default browser drag behavior const rect = element.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; isDragging = true; handle.style.cursor = 'grabbing'; document.body.style.userSelect = 'none'; // Prevent text selection on body document.addEventListener('mousemove', doDrag); document.addEventListener('mouseup', stopDrag); }; const doDrag = (e) => { if (!isDragging) return; e.preventDefault(); let newLeft = e.clientX - offsetX; let newTop = e.clientY - offsetY; // Clamp to viewport boundaries newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - element.offsetWidth)); newTop = Math.max(0, Math.min(newTop, window.innerHeight - element.offsetHeight)); element.style.left = `${newLeft}px`; element.style.top = `${newTop}px`; }; const stopDrag = () => { isDragging = false; handle.style.cursor = 'grab'; document.body.style.userSelect = 'auto'; // Restore text selection document.removeEventListener('mousemove', doDrag); document.removeEventListener('mouseup', stopDrag); }; handle.addEventListener('mousedown', startDrag); handle.style.cursor = 'grab'; // Set default cursor for handle console.log("NoCensorWords: Panel made draggable."); } #toggleCensorBypass() { this.#isCensorBypassActive = !this.#isCensorBypassActive; if (this.#isCensorBypassActive) { this.#toggleButton.textContent = 'Desactivar Bypass'; this.#toggleButton.classList.add('active'); this.#applyConversionToCurrentInput(); // Apply to existing text if any console.log("NoCensorWords: Censura bypass ACTIVADO."); } else { this.#toggleButton.textContent = 'Activar Bypass'; this.#toggleButton.classList.remove('active'); // Reverting text is complex, simply stop future conversions. // The user would need to retype if they want original characters. console.log("NoCensorWords: Censura bypass DESACTIVADO."); } } #getChatInput() { // This attempts to get the chat input. It targets the id used by Drawaria, // which could be the original input or a textarea replaced by other scripts. return document.querySelector('#chatbox_textinput'); } #setupChatInputMonitoring() { // Find a stable parent element to observe for chat input changes this.#observerTarget = document.querySelector('#chatattop') || document.body; if (!this.#observerTarget) { console.error("NoCensorWords: Could not find chat container or body to observe for input changes."); return; } this.#chatInputObserver = new MutationObserver(() => { // Debounce the attachment to avoid multiple calls on rapid DOM changes clearTimeout(this.#_attachInputDebounceTimer); // Use the private property this.#_attachInputDebounceTimer = setTimeout(() => { // Assign to private property this.#attachInputListener(); }, 100); // Small delay to let DOM settle }); // Observe for changes in the chat container or body for the input element this.#chatInputObserver.observe(this.#observerTarget, { childList: true, subtree: true, attributes: false }); console.log("NoCensorWords: MutationObserver set up on", this.#observerTarget === document.body ? "body" : "#chatattop"); // Initial attachment in case input is already present when script loads this.#attachInputListener(); } #attachInputListener() { const newChatInput = this.#getChatInput(); if (newChatInput && newChatInput !== this.#currentChatInput) { // Remove old listener if it exists and is different from new input if (this.#currentChatInput && this.#inputListener) { this.#currentChatInput.removeEventListener('input', this.#inputListener); console.log("NoCensorWords: Removed old chat input listener."); } this.#currentChatInput = newChatInput; this.#inputListener = (event) => this.#handleChatInput(event); this.#currentChatInput.addEventListener('input', this.#inputListener); console.log("NoCensorWords: Attached new chat input listener to:", this.#currentChatInput); this.#applyConversionToCurrentInput(); // Apply conversion immediately upon attaching } else if (!newChatInput && this.#currentChatInput) { // Input disappeared this.#currentChatInput.removeEventListener('input', this.#inputListener); this.#currentChatInput = null; this.#inputListener = null; console.log("NoCensorWords: Chat input disappeared, listener removed."); } else if (newChatInput === this.#currentChatInput) { // console.log("NoCensorWords: Chat input is the same, no re-attachment needed."); } else { // console.log("NoCensorWords: Chat input not found yet."); } } #handleChatInput(event) { if (!this.#isCensorBypassActive || !this.#currentChatInput) { return; } const currentText = this.#currentChatInput.value; const originalSelectionStart = this.#currentChatInput.selectionStart; const originalSelectionEnd = this.#currentChatInput.selectionEnd; let convertedText = ''; let charOffset = 0; // Tracks the change in length due to conversion for (let i = 0; i < currentText.length; i++) { const originalChar = currentText[i]; // Convert uppercase and lowercase letters, and digits const convertedChar = BOLD_TEXT_MAP[originalChar] || BOLD_TEXT_MAP[originalChar.toUpperCase()] || originalChar; convertedText += convertedChar; // Adjust charOffset if character length changes if (convertedChar.length !== originalChar.length) { charOffset += (convertedChar.length - originalChar.length); } } // Only update if conversion actually changed the text to prevent infinite loops if (this.#currentChatInput.value !== convertedText) { // FIX: Directly assign the value. This is the safest way to update. this.#currentChatInput.value = convertedText; // Restore cursor and selection, adjusting for length changes this.#currentChatInput.selectionStart = originalSelectionStart + charOffset; this.#currentChatInput.selectionEnd = originalSelectionEnd + charOffset; // Optionally, dispatch an input event manually after setting value // if framework relies on it, though direct .value usually doesn't trigger 'input' // and we already have a wrapper dispatching in #applyConversionToCurrentInput. // No need to dispatch here unless required by a specific framework. } } // Applies conversion to the current input value immediately when toggled on #applyConversionToCurrentInput() { if (this.#currentChatInput && this.#isCensorBypassActive) { // Dispatch a synthetic 'input' event to trigger #handleChatInput // This is crucial because directly setting .value doesn't fire 'input'. const inputEvent = new Event('input', { bubbles: true }); this.#currentChatInput.dispatchEvent(inputEvent); } } } // Initialize the module function initNoCensorWordsScript() { console.log("NoCensorWords: DOMContentLoaded or script executed."); // Add a small delay to ensure the main Drawaria chat input is ready, // especially if other scripts (like AdvancedChatEnhancements) are also modifying it. setTimeout(() => { new NoCensorWords(); }, 1500); // 1.5 seconds delay } // Ensure the script runs after the DOM is fully loaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initNoCensorWordsScript); } else { initNoCensorWordsScript(); } })();