Universal Text Reversal bypass

Bypass filters by reversing text structure. Modes: (Full) character reversal, (Smart) character-pair swap, (Word Swap) logical word reordering, or (Smart-Swap) combined word & character swap. Visually, the text looks normal.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Universal Text Reversal bypass
// @namespace    github.com/annaroblox
// @version      2.3
// @license      MIT
// @description  Bypass filters by reversing text structure. Modes: (Full) character reversal, (Smart) character-pair swap, (Word Swap) logical word reordering, or (Smart-Swap) combined word & character swap. Visually, the text looks normal.
// @author       AnnaRoblox
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setClipboard
// ==/UserScript==

(function () {
    'use strict';

    /* ---------- CONFIG ---------- */
    const STORAGE_KEY = 'utr_mode';
    const RLO = "\u202E";  // RIGHT-TO-LEFT OVERRIDE
    const PDF = "\u202C";  // POP DIRECTIONAL FORMATTING
    const RLI = "\u2067";  // RIGHT-TO-LEFT ISOLATE
    const LRI = "\u2066";  // LEFT-TO-RIGHT ISOLATE
    const PDI = "\u2069";  // POP DIRECTIONAL ISOLATE

    let isDebugMode = false;
    let mode        = localStorage.getItem(STORAGE_KEY) || 'smart-swap';
    let menuCommandIds = []; // Array to store menu command IDs for unregistering

    /* ---------- CORE TEXT TRANSFORM ---------- */

    const reverseLines = text =>
        text.split('\n')
            .map(l => RLO + [...l].reverse().join('') + PDF)
            .join('\n');

    /**
     * wordIsolateSwap function
     * How it works:
     * This method swaps adjacent pairs of words but preserves the visual appearance.
     * e.g., "hello how are you" becomes "how hello you are" (stored) but displays as "hello how are you"
     * 1. Adjacent word pairs are swapped: (hello,how) -> (how,hello), (are,you) -> (you,are)
     * 2. Each swapped pair is wrapped in RLI...PDI, creating a Right-to-Left context that reverses them back.
     * 3. Each individual word is wrapped in LRI...PDI to force Left-to-Right rendering.
     * 4. The browser renders each RTL pair, swapping them back to appear normal.
     */
    const wordIsolateSwap = text =>
      text
        .split('\n')
        .map(line => {
          const words = line.match(/\S+/g) || [];
          if (words.length === 0) return line;

          // Swap adjacent pairs of words
          const swappedWords = [];
          for (let i = 0; i < words.length; i += 2) {
            if (i + 1 < words.length) {
              // Swap the pair
              swappedWords.push(words[i + 1]);
              swappedWords.push(words[i]);
            } else {
              // Odd word out, keep it in place
              swappedWords.push(words[i]);
            }
          }

          // Wrap each pair in RLI...PDI to reverse it back for display
          let result = '';
          for (let i = 0; i < swappedWords.length; i += 2) {
            if (i + 1 < swappedWords.length) {
              // Wrap the swapped pair to reverse it for display
              result += RLI + LRI + swappedWords[i] + PDI + ' ' + LRI + swappedWords[i + 1] + PDI + PDI;
              // Add space between pairs
              if (i + 2 < swappedWords.length) result += ' ';
            } else {
              // Odd word out, just wrap it normally
              result += LRI + swappedWords[i] + PDI;
            }
          }

          return result;
        })
        .join('\n');

   /**
 * smartSwap function - combines word swapping with character pair swapping
 * Structure: RLI + [LRI + char-swapped-word + PDI] + SPACE + [LRI + char-swapped-word + PDI] + PDI
 * - Character pairs within each word are swapped using RLI+RLO+chars+PDI
 * - Each complete word is wrapped in LRI...PDI
 * - Adjacent word pairs are physically swapped in storage
 * - The outer RLI...PDI reverses the word order for correct display
 */
const smartSwap = text =>
  text
    .split('\n')
    .map(line => {
      const words = line.match(/\S+/g) || [];
      if (words.length === 0) return line;

      // Apply character pair swapping to each word
      const processedWords = words.map(word => {
        const chars = [...word];
        let charSwapped = '';
        for (let i = 0; i < chars.length; i += 2) {
          if (i + 1 < chars.length) {
            // Swap the pair: chars[i+1] + chars[i]
            const swappedPair = chars[i + 1] + chars[i];
            charSwapped += RLI + RLO + swappedPair + PDI;
          } else {
            charSwapped += chars[i];
          }
        }
        // Wrap entire word in LRI...PDI
        return LRI + charSwapped + PDI;
      });

      // Swap adjacent pairs of words
      const swappedWords = [];
      for (let i = 0; i < processedWords.length; i += 2) {
        if (i + 1 < processedWords.length) {
          swappedWords.push(processedWords[i + 1]);
          swappedWords.push(processedWords[i]);
        } else {
          swappedWords.push(processedWords[i]);
        }
      }

      // Wrap all pairs in RLI...PDI to reverse word order for display
      let result = '';
      for (let i = 0; i < swappedWords.length; i += 2) {
        if (i + 1 < swappedWords.length) {
          // Wrap the swapped pair in RLI...PDI
          result += RLI + swappedWords[i] + ' ' + swappedWords[i + 1] + PDI;
          if (i + 2 < swappedWords.length) result += ' ';
        } else {
          // Odd word out
          result += swappedWords[i];
        }
      }

      return result;
    })
    .join('\n');

    /**
     * Selective word swap - swaps any specified word pairs
     * For each pair:
     * - If one word is the last word: first word output normally, then RLI block with rest reversed
     * - Otherwise: reverse the entire range between them
     */
    const selectiveWordSwap = (text, indicesToSwap) => {
      return text
        .split('\n')
        .map(line => {
          const words = line.match(/\S+/g) || [];
          if (words.length === 0) return line;

          // Create swap pairs (ensure min, max order)
          const swapPairs = [];
          for (let i = 0; i < indicesToSwap.length; i += 2) {
            if (i + 1 < indicesToSwap.length) {
              const idx1 = indicesToSwap[i];
              const idx2 = indicesToSwap[i + 1];
              swapPairs.push([Math.min(idx1, idx2), Math.max(idx1, idx2)]);
            }
          }

          // Build result
          let result = '';
          const processed = new Set();

          for (let i = 0; i < words.length; i++) {
            if (processed.has(i)) continue;

            // Check if this index starts a swap pair
            const pair = swapPairs.find(p => p[0] === i);

            if (pair) {
              const [startIdx, endIdx] = pair;

              // Special case: if swapping with the last word
              if (endIdx === words.length - 1) {
                // Output the first swapped word normally
                result += LRI + words[startIdx] + PDI;
                processed.add(startIdx);

                // Add space before RLI block if there are words in between
                if (startIdx + 1 <= endIdx) result += ' ';

                // Start RLI block
                result += RLI;

                // Add words from startIdx+1 to endIdx in REVERSE order
                for (let j = endIdx; j >= startIdx + 1; j--) {
                  result += LRI + words[j] + PDI;
                  if (j > startIdx + 1) result += ' ';
                  processed.add(j);
                }

                // End RLI block
                result += PDI;
              } else {
                // Normal case: reverse the entire range
                // Start RLI block
                result += RLI;

                // Add words in REVERSE order (from end to start)
                for (let j = endIdx; j >= startIdx; j--) {
                  result += LRI + words[j] + PDI;
                  if (j > startIdx) result += ' ';
                  processed.add(j);
                }

                // End RLI block
                result += PDI;
              }

              // Add space after the block if not at the end
              if (endIdx + 1 < words.length) result += ' ';
            } else {
              // Not part of a swap, check if already processed
              if (!processed.has(i)) {
                result += LRI + words[i] + PDI;
                processed.add(i);
                // Add space after the word if not at the end
                if (i + 1 < words.length) result += ' ';
              }
            }
          }

          return result;
        })
        .join('\n');
    };

    /**
     * smartReverse function
     * How it works: Swaps pairs of characters within each word.
     */
    const smartReverse = text =>
      text
        .split('\n')
        .map(line => {
          const tokens = line.match(/(\s+|\S+)/g) || [];
          const transformedTokens = tokens.map(tk => {
            if (/^\s+$/.test(tk)) return tk;
            const chars = [...tk];
            let processedWord = '';
            for (let i = 0; i < chars.length; i += 2) {
              if (i + 1 < chars.length) {
                const swappedPair = chars[i + 1] + chars[i];
                processedWord += RLI + RLO + swappedPair + PDI;
              } else {
                processedWord += chars[i];
              }
            }
            return processedWord;
          });
          return transformedTokens.join('');
        })
        .join('\n');

    // Dispatcher function to select the correct transformation
    const transform = txt => {
        switch (mode) {
            case 'full':
                return reverseLines(txt);
            case 'wordswap':
                return wordIsolateSwap(txt);
            case 'smart-swap':
                return smartSwap(txt);
            case 'smart':
            default:
                return smartReverse(txt);
        }
    };

    /* ---------- WORD SELECTION UI ---------- */

    function createWordSelectionModal(text) {
        // Create modal overlay
        const overlay = document.createElement('div');
        overlay.id = 'utr-word-selector-overlay';
        overlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7);
            z-index: 999999;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: Arial, sans-serif;
        `;

        // Create modal container
        const modal = document.createElement('div');
        modal.style.cssText = `
            background: white;
            border-radius: 8px;
            padding: 20px;
            max-width: 600px;
            max-height: 80vh;
            overflow-y: auto;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
        `;

        // Title
        const title = document.createElement('h3');
        title.textContent = 'Select Words to Swap';
        title.style.cssText = 'margin: 0 0 15px 0; color: #333;';
        modal.appendChild(title);

        // Instructions
        const instructions = document.createElement('p');
        instructions.textContent = 'Click words in pairs to swap them. When swapping with the last word, middle words stay in place. Otherwise, the entire range is reversed.';
        instructions.style.cssText = 'margin: 0 0 15px 0; color: #666; font-size: 14px;';
        modal.appendChild(instructions);

        // Word container
        const wordContainer = document.createElement('div');
        wordContainer.style.cssText = `
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            margin-bottom: 20px;
            padding: 15px;
            background: #f5f5f5;
            border-radius: 4px;
            min-height: 60px;
        `;

        const words = text.match(/\S+/g) || [];
        const selectedIndices = [];
        const wordElements = [];

        words.forEach((word, index) => {
            const wordSpan = document.createElement('span');
            wordSpan.textContent = word;
            wordSpan.dataset.index = index;
            wordSpan.style.cssText = `
                padding: 6px 12px;
                background: white;
                border: 2px solid #ddd;
                border-radius: 4px;
                cursor: pointer;
                transition: all 0.2s;
                user-select: none;
            `;

            wordSpan.addEventListener('click', () => {
                const idx = parseInt(wordSpan.dataset.index);
                const selectedIdx = selectedIndices.indexOf(idx);

                if (selectedIdx > -1) {
                    // Deselect
                    selectedIndices.splice(selectedIdx, 1);
                    wordSpan.style.background = 'white';
                    wordSpan.style.borderColor = '#ddd';
                    wordSpan.style.color = 'black';
                } else {
                    // Select
                    selectedIndices.push(idx);
                    const pairIndex = selectedIndices.length - 1;
                    const color = pairIndex % 2 === 0 ? '#4CAF50' : '#2196F3';
                    wordSpan.style.background = color;
                    wordSpan.style.borderColor = color;
                    wordSpan.style.color = 'white';
                }
            });

            wordElements.push(wordSpan);
            wordContainer.appendChild(wordSpan);
        });

        modal.appendChild(wordContainer);

        // Button container
        const buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = 'display: flex; gap: 10px; justify-content: flex-end;';

        // Apply button
        const applyBtn = document.createElement('button');
        applyBtn.textContent = 'Apply Swap';
        applyBtn.style.cssText = `
            padding: 10px 20px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            font-weight: bold;
        `;
        applyBtn.addEventListener('click', () => {
            if (selectedIndices.length >= 2) {
                const transformed = selectiveWordSwap(text, selectedIndices);
                applyTransformedText(transformed);
            }
            document.body.removeChild(overlay);
        });

        // Cancel button
        const cancelBtn = document.createElement('button');
        cancelBtn.textContent = 'Cancel';
        cancelBtn.style.cssText = `
            padding: 10px 20px;
            background: #f44336;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            font-weight: bold;
        `;
        cancelBtn.addEventListener('click', () => {
            document.body.removeChild(overlay);
        });

        buttonContainer.appendChild(cancelBtn);
        buttonContainer.appendChild(applyBtn);
        modal.appendChild(buttonContainer);

        overlay.appendChild(modal);
        document.body.appendChild(overlay);

        // Close on overlay click
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                document.body.removeChild(overlay);
            }
        });

        // Close on Escape key
        const escapeHandler = (e) => {
            if (e.key === 'Escape') {
                document.body.removeChild(overlay);
                document.removeEventListener('keydown', escapeHandler);
            }
        };
        document.addEventListener('keydown', escapeHandler);
    }

    function applyTransformedText(transformed) {
        const sel = window.getSelection();
        const inputInfo = locateRealInput(sel.focusNode || document.activeElement);

        if (!inputInfo) {
            copyToClipboard(transformed);
            try { document.execCommand('insertText', false, transformed); }
            catch (e) { copyToClipboard(transformed); }
            return;
        }

        const { element: el, type } = inputInfo;
        let start, end;

        if (type === 'input' || type === 'textarea') {
            start = el.selectionStart ?? 0;
            end = el.selectionEnd ?? 0;
        } else {
            const range = sel.rangeCount ? sel.getRangeAt(0) : null;
            if (!range) { start = end = 0; }
            else {
                const pre = range.cloneRange();
                pre.selectNodeContents(el);
                pre.setEnd(range.startContainer, range.startOffset);
                start = pre.toString().length;
                end = start + range.toString().length;
            }
        }

        replaceTextInInput(el, type, transformed, start, end);
    }

    /* ---------- UI / MENU ---------- */
    function buildMenu() {
        menuCommandIds.forEach(id => GM_unregisterMenuCommand(id));
        menuCommandIds = [];

        menuCommandIds.push(
            GM_registerMenuCommand('Process selected text', processSelection)
        );

        menuCommandIds.push(
            GM_registerMenuCommand(
                `Mode: ${mode.toUpperCase()} (click to toggle)`,
                () => {
                    const modes = ['full', 'smart', 'wordswap', 'smart-swap'];
                    const currentIndex = modes.indexOf(mode);
                    mode = modes[(currentIndex + 1) % modes.length]; // Cycle through the modes
                    localStorage.setItem(STORAGE_KEY, mode);
                    buildMenu();
                },
                'm'
            )
        );

        menuCommandIds.push(
            GM_registerMenuCommand(
                `DEBUG: ${isDebugMode ? 'ON' : 'OFF'} (click to toggle)`,
                () => { isDebugMode = !isDebugMode; buildMenu(); },
                'd'
            )
        );
    }

    /* ---------- CLIPBOARD & INPUT HANDLING ---------- */

    function copyToClipboard(textToCopy) {
        if (typeof GM_setClipboard !== 'undefined') {
            GM_setClipboard(textToCopy);
        } else if (navigator.clipboard) {
            navigator.clipboard.writeText(textToCopy).catch(console.error);
        } else {
            const ta = document.createElement('textarea');
            ta.value = textToCopy;
            ta.style.position = 'fixed'; ta.style.left = '-9999px';
            document.body.appendChild(ta);
            ta.select();
            try { document.execCommand('copy'); } catch (e) {}
            document.body.removeChild(ta);
        }
    }

    function isEditableElement(el) {
        if (!el || !el.nodeType || el.nodeType !== 1) return false;
        const tag = el.tagName?.toLowerCase();
        if (tag === 'input' || tag === 'textarea') return true;
        if (el.contentEditable === 'true' || el.isContentEditable || el.designMode === 'on') return true;
        const role = el.getAttribute('role');
        if (role === 'textbox' || role === 'searchbox') return true;
        return false;
    }

    function findEditableInShadowDOM(root) {
        if (!root) return null;
        if (root.activeElement && isEditableElement(root.activeElement)) return root.activeElement;
        if (root.activeElement?.shadowRoot) {
            const found = findEditableInShadowDOM(root.activeElement.shadowRoot);
            if (found) return found;
        }
        const selectors = ['input:not([type="hidden"])', 'textarea', '[contenteditable="true"]', '[role="textbox"]', '[role="searchbox"]'];
        for (const selector of selectors) {
            const el = root.querySelector(selector);
            if (el && isEditableElement(el)) return el;
        }
        return null;
    }

    function locateRealInput(node) {
        let cur = node;
        while (cur) {
            if (cur.nodeType === 1 && isEditableElement(cur)) {
                const tag = cur.tagName?.toLowerCase();
                return { element: cur, type: (tag === 'input' || tag === 'textarea') ? tag : 'contenteditable' };
            }
            if (cur.shadowRoot) {
                const shadowEditable = findEditableInShadowDOM(cur.shadowRoot);
                if (shadowEditable) {
                    const tag = shadowEditable.tagName?.toLowerCase();
                    return { element: shadowEditable, type: (tag === 'input' || tag === 'textarea') ? tag : 'contenteditable' };
                }
            }
            cur = cur.parentNode || cur.host;
            if (!cur && node.getRootNode) {
                const root = node.getRootNode();
                if (root?.host) cur = root.host;
            }
        }
        let active = document.activeElement;
        while (active) {
            if (isEditableElement(active)) {
                const tag = active.tagName?.toLowerCase();
                return { element: active, type: (tag === 'input' || tag === 'textarea') ? tag : 'contenteditable' };
            }
            if (active.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
            else if (active.shadowRoot) {
                const shadowEditable = findEditableInShadowDOM(active.shadowRoot);
                if (shadowEditable) {
                    const tag = shadowEditable.tagName?.toLowerCase();
                    return { element: shadowEditable, type: (tag === 'input' || tag === 'textarea') ? tag : 'contenteditable' };
                }
                break;
            } else break;
        }
        try {
            for (const frame of document.querySelectorAll('iframe')) {
                try {
                    const frameDoc = frame.contentDocument || frame.contentWindow?.document;
                    if (frameDoc?.activeElement && isEditableElement(frameDoc.activeElement)) {
                        const tag = frameDoc.activeElement.tagName?.toLowerCase();
                        return { element: frameDoc.activeElement, type: (tag === 'input' || tag === 'textarea') ? tag : 'contenteditable' };
                    }
                } catch (e) { /* Cross-origin */ }
            }
        } catch (e) {}
        return null;
    }

    function replaceTextInInput(el, type, reversed, start, end) {
        if (type === 'input' || type === 'textarea') {
            const original = el.value;
            const replacement = original.slice(0, start) + reversed + original.slice(end);
            const scrollTop = el.scrollTop;
            el.value = replacement;
            el.setSelectionRange(start, start + reversed.length);
            el.scrollTop = scrollTop;
            el.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
            el.dispatchEvent(new Event('change', { bubbles: true }));
        } else {
            const sel = window.getSelection();
            if (document.queryCommandSupported('insertText')) {
                try {
                    if (sel.rangeCount > 0) {
                        const range = sel.getRangeAt(0);
                        if (start !== end) range.deleteContents();
                    }
                    document.execCommand('insertText', false, reversed);
                    el.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
                    return;
                } catch (e) {}
            }
            if (sel.rangeCount > 0) {
                const range = sel.getRangeAt(0);
                range.deleteContents();
                const textNode = document.createTextNode(reversed);
                range.insertNode(textNode);
                range.setStartAfter(textNode);
                range.setEndAfter(textNode);
                sel.removeAllRanges();
                sel.addRange(range);
                el.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
            }
        }
    }

    function processSelection() {
        const sel = window.getSelection();
        const inputInfo = locateRealInput(sel.focusNode || document.activeElement);

        if (!inputInfo) {
            const selected = sel.toString();
            if (selected) {
                const reversed = transform(selected);
                if (isDebugMode) copyToClipboard(reversed);
                try { document.execCommand('insertText', false, reversed); }
                catch (e) { copyToClipboard(reversed); }
            }
            return;
        }

        const { element: el, type } = inputInfo;
        let original, start, end;

        if (type === 'input' || type === 'textarea') {
            original = el.value;
            start = el.selectionStart ?? 0;
            end   = el.selectionEnd ?? 0;
        } else {
            original = el.textContent || '';
            const range = sel.rangeCount ? sel.getRangeAt(0) : null;
            if (!range) { start = end = 0; }
            else {
                const pre = range.cloneRange();
                pre.selectNodeContents(el);
                pre.setEnd(range.startContainer, range.startOffset);
                start = pre.toString().length;
                end   = start + range.toString().length;
            }
        }

        const chunk = (start === end) ? original : original.slice(start, end);
        if (!chunk) return;

        const reversed = transform(chunk);
        if (isDebugMode) copyToClipboard(reversed);
        replaceTextInInput(el, type, reversed, start, end);
    }

    function openWordSelectorForSelection() {
        const sel = window.getSelection();
        const inputInfo = locateRealInput(sel.focusNode || document.activeElement);

        let selectedText = '';

        if (!inputInfo) {
            selectedText = sel.toString();
        } else {
            const { element: el, type } = inputInfo;
            if (type === 'input' || type === 'textarea') {
                const start = el.selectionStart ?? 0;
                const end = el.selectionEnd ?? 0;
                selectedText = el.value.slice(start, end);
            } else {
                const range = sel.rangeCount ? sel.getRangeAt(0) : null;
                if (range) {
                    selectedText = range.toString();
                }
            }
        }

        if (selectedText && selectedText.trim()) {
            createWordSelectionModal(selectedText);
        }
    }

    // Alt + R for normal processing
    document.addEventListener('keydown', e => {
        if (e.altKey && !e.ctrlKey && e.key.toLowerCase() === 'r') {
            e.preventDefault();
            processSelection();
        }
    }, true);

    // Ctrl + Alt + R for word selector
    document.addEventListener('keydown', e => {
        if (e.ctrlKey && e.altKey && e.key.toLowerCase() === 'r') {
            e.preventDefault();
            openWordSelectorForSelection();
        }
    }, true);

    buildMenu();
})();