您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Reverse every selected line (Full) or (Smart). Alt+R or Tampermonkey menu. Debug mode copies result to clipboard for doing it manually if a certain site doesn't work (string looks normal but bypasses some filters)
// ==UserScript== // @name Universal Text Reversal bypass // @namespace http://tampermonkey.net/ // @version 1.3 // @license MIT // @description Reverse every selected line (Full) or (Smart). Alt+R or Tampermonkey menu. Debug mode copies result to clipboard for doing it manually if a certain site doesn't work (string looks normal but bypasses some filters) // @author AnnaRoblox // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setClipboard // ==/UserScript== (function () { 'use strict'; /* ---------- CONFIG ---------- */ const STORAGE_KEY = 'utr_mode'; // localStorage key const RTL_MAGIC = "\u202E"; // RIGHT-TO-LEFT OVERRIDE const PDF = "\u202C"; // POP DIRECTIONAL FORMATTING const RLO = "\u202E"; // Right-TO-RIGHT OVERRIDE let isDebugMode = false; // debug: copy result to clipboard let mode = localStorage.getItem(STORAGE_KEY) || 'smart'; // 'full' | 'smart' /* ---------- CORE TEXT TRANSFORM ---------- */ // FULL mode – reverse every line const reverseLines = text => text.split('\n') .map(l => RTL_MAGIC + [...l].reverse().join('')) .join('\n'); // SMART pattern – cycles through: // RTL → reverse next 2 → PDF → next 1 → RLO → reverse next 2 → PDF → next 1 → … const smartReverse = text => text .split('\n') .map(line => { /* ---------- 1. split into runs of non-white / white ---------- */ const tokens = line.match(/(\s+|\S+)/g) || []; let out = ''; /* ---------- 2. state that survives across words -------------- */ let buf = ''; let dir = 'ltr'; // direction we are *about* to emit const push = (segment, newDir) => { if (newDir === 'rtl') { out += RTL_MAGIC + [...segment].reverse().join('') + PDF; } else { out += segment; } dir = newDir; buf = ''; }; /* ---------- 3. process every token --------------------------- */ for (const tk of tokens) { if (/^\s+$/.test(tk)) { // whitespace – flush first, then inject if (buf) push(buf, dir); // flush leftover chars out += tk; continue; } /* ----- non-white: feed chars into the global cycle --------- */ for (const ch of tk) { buf += ch; if (dir === 'ltr' && buf.length === 2) push(buf, 'rtl'); else if (dir === 'rtl' && buf.length === 1) push(buf, 'ltr'); } } /* ---------- 4. flush anything still in buffer ---------------- */ if (buf) { if (dir === 'rtl') { out += RTL_MAGIC + [...buf].reverse().join('') + PDF; } else { out += buf; } } return out; }) .join('\n'); const transform = txt => mode === 'full' ? reverseLines(txt) : smartReverse(txt); /* ---------- UI / MENU ---------- */ function buildMenu() { GM_registerMenuCommand('Reverse selected lines', processSelection); GM_registerMenuCommand( `Mode: ${mode.toUpperCase()} (click to toggle)`, () => { mode = mode === 'full' ? 'smart' : 'full'; localStorage.setItem(STORAGE_KEY, mode); buildMenu(); // refresh label } ); GM_registerMenuCommand( `DEBUG: ${isDebugMode ? 'ON' : 'OFF'} (click to toggle)`, () => { isDebugMode = !isDebugMode; buildMenu(); } ); } /* ---------- CLIPBOARD HELPERS ---------- */ 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); } } /* ---------- SELECTION / INPUT LOGIC ---------- */ function locateRealInput(node) { let cur = node; while (cur && cur !== document.documentElement) { if (cur.nodeType !== 1) { cur = cur.parentNode; continue; } if (cur.tagName === 'INPUT' || cur.tagName === 'TEXTAREA') return { element: cur, type: cur.tagName.toLowerCase() }; if (cur.contentEditable === 'true') return { element: cur, type: 'contenteditable' }; if (cur.shadowRoot) { const sr = cur.shadowRoot; const active = sr.activeElement || sr.querySelector('input, textarea, [contenteditable="true"]'); if (active) return locateRealInput(active); } cur = cur.parentNode || cur.host; } return null; } function processSelection() { const sel = window.getSelection(); const inputInfo = locateRealInput(sel.focusNode); if (!inputInfo) { const selected = sel.toString(); if (selected) { const reversed = transform(selected); if (isDebugMode) copyToClipboard(reversed); document.execCommand('insertText', false, reversed); } return; } const { element: el, type } = inputInfo; let original, start, end; if (type === 'input' || type === 'textarea') { original = el.value; start = el.selectionStart; end = el.selectionEnd; } 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); const reversed = transform(chunk); const replacement = start === end ? reversed : original.slice(0, start) + reversed + original.slice(end); if (isDebugMode) copyToClipboard(reversed); if (type === 'input' || type === 'textarea') { el.value = replacement; el.setSelectionRange(start, start + reversed.length); } else { el.textContent = replacement; const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null); let node, offset = 0, startNode; while (node = walker.nextNode()) { const len = node.textContent.length; if (!startNode && offset + len >= start) { startNode = node; const r = new Range(); r.setStart(startNode, start - offset); r.setEnd(startNode, start - offset + reversed.length); sel.removeAllRanges(); sel.addRange(r); break; } offset += len; } } } /* ---------- KEYBOARD SHORTCUT ---------- */ document.addEventListener('keydown', e => { if (e.altKey && e.key.toLowerCase() === 'r') { e.preventDefault(); processSelection(); } }); buildMenu(); })();