Reverse every selected line (Full) or only the last half of every word (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.2
// @license MIT
// @description Reverse every selected line (Full) or only the last half of every word (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
let isDebugMode = false; // debug: copy result to clipboard
let mode = localStorage.getItem(STORAGE_KEY) || 'full'; // '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 mode – reverse only last half of every word
const smartReverse = text =>
text.split('\n')
.map(line =>
line.split(/(\s+)/) // keep whitespace
.map(part => {
if (!/\S/.test(part)) return part; // whitespace only
const mid = Math.ceil(part.length / 2);
const left = part.slice(0, mid);
const right = part.slice(mid);
return left + RTL_MAGIC + [...right].reverse().join('') + PDF;
})
.join('')
)
.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();
})();