您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Detects obfuscated text in Reddit comments as: binary and NATO (usually in NSFW); convert it back to human readable string. Implement a text selection popup for Google search.
// ==UserScript== // @name Reddit Multi Code Decoder for obfuscated content // @namespace [email protected] // @version 0.3.2 // @description Detects obfuscated text in Reddit comments as: binary and NATO (usually in NSFW); convert it back to human readable string. Implement a text selection popup for Google search. // @author [email protected] // @license MIT // @match https://www.reddit.com/* // @match https://old.reddit.com/* // @grant none // ==/UserScript== (function() { 'use strict'; // ===== COMMON ===== // Centralized comment selectors for both selection popup and binary decoder const COMMENT_SELECTORS = [ // New Reddit '[data-testid="comment"]', 'shreddit-comment', '.Comment', '[data-click-id="text"]', // Old Reddit '.usertext-body', '.md', // Generic fallbacks '.comment', '[class*="comment"]' ]; // Centralized search provider configuration const SEARCH_CONFIG = { // Default search provider templates providers: { google: { name: "Google", webSearch: "https://www.google.com/search?q={{QUERY}}", imageSearch: "https://www.google.com/search?q={{QUERY}}&tbm=isch", videoSearch: "https://www.google.com/search?q={{QUERY}}&tbm=vid" }, bing: { name: "Bing", webSearch: "https://www.bing.com/search?q={{QUERY}}", imageSearch: "https://www.bing.com/images/search?q={{QUERY}}" }, duckduckgo: { name: "DuckDuckGo", webSearch: "https://duckduckgo.com/?q={{QUERY}}", imageSearch: "https://duckduckgo.com/?q={{QUERY}}&iax=images&ia=images" } }, // Active configuration active: { provider: "google", defaultSearchType: "imageSearch" // Default to images as specified } }; // Search utility functions function buildSearchURL(query, searchType = null) { try { const provider = SEARCH_CONFIG.providers[SEARCH_CONFIG.active.provider]; if (!provider) { // Fallback to Google if current provider is invalid SEARCH_CONFIG.active.provider = "google"; const fallbackProvider = SEARCH_CONFIG.providers.google; const type = searchType || SEARCH_CONFIG.active.defaultSearchType; const template = fallbackProvider[type] || fallbackProvider.webSearch; return template.replace('{{QUERY}}', encodeURIComponent(query)); } const type = searchType || SEARCH_CONFIG.active.defaultSearchType; const template = provider[type] || provider.webSearch; return template.replace('{{QUERY}}', encodeURIComponent(query)); } catch (error) { // Ultimate fallback to Google web search return `https://www.google.com/search?q=${encodeURIComponent(query)}`; } } function validateProvider(providerName) { return SEARCH_CONFIG.providers.hasOwnProperty(providerName); } function setSearchProvider(providerName) { if (validateProvider(providerName)) { SEARCH_CONFIG.active.provider = providerName; return true; } return false; } // NATO phonetic alphabet mapping const NATO_ALPHABET = { 'apple': 'A', 'alpha': 'A', 'alfa': 'A', 'bravo': 'B', 'charlie': 'C', 'delta': 'D', 'echo': 'E', 'foxtrot': 'F', 'golf': 'G', 'hotel': 'H', 'india': 'I', 'juliet': 'J', 'kilo': 'K', 'lima': 'L', 'mike': 'M', 'mira': 'M', 'november': 'N', 'oscar': 'O', 'papa': 'P', 'quebec': 'Q', 'romeo': 'R', 'sierra': 'S', 'tango': 'T', 'uniform': 'U', 'victor': 'V', 'whiskey': 'W', 'xray': 'X', 'x-ray': 'X', 'yankee': 'Y', 'zulu': 'Z', 'space': ' ' }; // NATO phonetic pattern: sequences of NATO words with optional separators const natoPattern = new RegExp( `\\b(?:${Object.keys(NATO_ALPHABET).join('|')})(?:[\\s,.-]+(?:${Object.keys(NATO_ALPHABET).join('|')}))*\\b`, 'gi' ); // Function to convert NATO phonetic string to text function natoToText(natoString) { try { // First, normalize the string to handle x-ray properly let normalizedString = natoString.toLowerCase(); // Create a list of NATO words sorted by length (descending) to match longer words first const natoWords = Object.keys(NATO_ALPHABET).sort((a, b) => b.length - a.length); let result = ''; let position = 0; let validWordsCount = 0; while (position < normalizedString.length) { // Skip whitespace and separators while (position < normalizedString.length && /[\s,.-]/.test(normalizedString[position])) { position++; } if (position >= normalizedString.length) break; let matched = false; // Try to match each NATO word starting from current position for (const word of natoWords) { if (normalizedString.substr(position, word.length) === word) { // Check if this is a complete word (not part of a larger word) const nextChar = normalizedString[position + word.length]; if (!nextChar || /[\s,.-]/.test(nextChar)) { result += NATO_ALPHABET[word]; position += word.length; validWordsCount++; matched = true; break; } } } if (!matched) { // Skip the current character if no NATO word was found position++; } } // Only return result if we have at least 2 valid NATO words if (validWordsCount >= 2) { return result; } return null; } catch (e) { return null; } } // ===== TEXT SELECTION POPUP FEATURE ===== let selectionPopup = null; function createSelectionPopup() { const popup = document.createElement('div'); popup.id = 'text-selection-popup'; popup.style.cssText = ` position: fixed; background: #ffffff; border: 1px solid #e0e0e0; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); padding: 8px; z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; display: none; max-width: 200px; `; const searchButton = document.createElement('button'); searchButton.textContent = '🔍 Search on Google'; searchButton.style.cssText = ` background: #4285f4; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 13px; width: 100%; transition: background-color 0.2s; display: flex; align-items: center; justify-content: center; line-height: 1; `; searchButton.onmouseover = () => searchButton.style.backgroundColor = '#3367d6'; searchButton.onmouseout = () => searchButton.style.backgroundColor = '#4285f4'; popup.appendChild(searchButton); document.body.appendChild(popup); return { popup, searchButton }; } function hideSelectionPopup() { if (selectionPopup && selectionPopup.popup) { selectionPopup.popup.style.display = 'none'; } } function showSelectionPopup(x, y, selectedText) { if (!selectionPopup) { selectionPopup = createSelectionPopup(); } const { popup, searchButton } = selectionPopup; // Update button click handler with current selected text searchButton.onclick = () => { const searchURL = buildSearchURL(selectedText.trim()); window.open(searchURL, '_blank'); hideSelectionPopup(); }; // Position popup near mouse cursor (using clientX/clientY with fixed positioning) popup.style.left = `${x + 10}px`; popup.style.top = `${y + 10}px`; popup.style.display = 'block'; // Adjust position if popup goes off screen const rect = popup.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; if (rect.right > viewportWidth) { popup.style.left = `${x - rect.width - 10}px`; } if (rect.bottom > viewportHeight) { popup.style.top = `${y - rect.height - 10}px`; } } // Check if the target element is within a comment function isWithinComment(element) { return COMMENT_SELECTORS.some(selector => element.closest(selector)); } // Handle text selection events document.addEventListener('mouseup', (e) => { setTimeout(() => { const selection = window.getSelection(); const selectedText = selection.toString().trim(); if (selectedText.length > 0 && isWithinComment(e.target)) { // Use clientX/clientY with fixed positioning for proper viewport positioning showSelectionPopup(e.clientX, e.clientY, selectedText); } else { hideSelectionPopup(); } }, 10); }); // Hide popup when clicking elsewhere or on escape document.addEventListener('mousedown', (e) => { if (selectionPopup && selectionPopup.popup && !selectionPopup.popup.contains(e.target)) { hideSelectionPopup(); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { hideSelectionPopup(); } }); // ===== BINARY DECODER FUNCTIONALITY ===== // Binary pattern: sequences of 0s and 1s, typically in groups of 8 (bytes) // Matches patterns like: 01001000 01100101 01101100 01101100 01101111 const binaryPattern = /(?:\b[01]{8}\b[\s]*){2,}/g; // Function to convert binary string to text function binaryToText(binaryString) { try { // Remove all whitespace and split into 8-bit chunks const cleanBinary = binaryString.replace(/\s/g, ''); if (cleanBinary.length % 8 !== 0) { return null; // Invalid binary string } let result = ''; for (let i = 0; i < cleanBinary.length; i += 8) { const byte = cleanBinary.substr(i, 8); const charCode = parseInt(byte, 2); // Only convert printable ASCII characters (32-126) and common whitespace if ((charCode >= 32 && charCode <= 126) || charCode === 9 || charCode === 10 || charCode === 13) { result += String.fromCharCode(charCode); } else { return null; // Contains non-printable characters, probably not text } } return result; } catch (e) { return null; } } // Function to create translation component for multiple decoder types function createTranslationComponent(type, originalText, translatedText) { const container = document.createElement('div'); container.style.cssText = ` margin: 8px 0; padding: 12px; background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 8px; font-family: monospace; font-size: 14px; position: relative; display: flex; align-items: center; gap: 12px; `; const header = document.createElement('div'); header.style.cssText = ` font-weight: bold; color: #1a73e8; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; flex-shrink: 0; `; // Set header text based on decoder type if (type === 'nato') { header.textContent = '📻 NATO:'; } else { header.textContent = '🔢 Binary:'; } const translationDiv = document.createElement('div'); translationDiv.style.cssText = ` background: #e8f5e8; padding: 8px; border-radius: 4px; font-weight: bold; color: #2d5a2d; flex-grow: 1; `; translationDiv.textContent = `${translatedText}`; const searchLink = document.createElement('a'); searchLink.href = buildSearchURL(translatedText); searchLink.target = '_blank'; searchLink.rel = 'noopener noreferrer'; searchLink.style.cssText = ` background: #4285f4; color: white; padding: 6px 8px; border-radius: 4px; text-decoration: none; display: flex; align-items: center; gap: 4px; font-size: 12px; flex-shrink: 0; transition: background-color 0.2s; `; searchLink.onmouseover = () => searchLink.style.backgroundColor = '#3367d6'; searchLink.onmouseout = () => searchLink.style.backgroundColor = '#4285f4'; // Search icon (SVG) const searchIcon = document.createElement('span'); searchIcon.innerHTML = ` <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"> <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/> </svg> `; searchLink.appendChild(searchIcon); searchLink.appendChild(document.createTextNode('Search')); container.appendChild(header); container.appendChild(translationDiv); container.appendChild(searchLink); return container; } // Function to process text nodes and detect binary and NATO patterns function processTextNode(textNode) { const text = textNode.textContent; let hasTranslations = false; // Check for binary patterns const binaryMatches = text.match(binaryPattern); if (binaryMatches) { binaryMatches.forEach(match => { const translated = binaryToText(match); if (translated && translated.trim().length > 0) { if (!hasTranslations) { // Create a wrapper for the original text node const wrapper = document.createElement('span'); textNode.parentNode.insertBefore(wrapper, textNode); wrapper.appendChild(textNode); hasTranslations = true; } // Add binary translation component after the text const translationComponent = createTranslationComponent('binary', match, translated); textNode.parentNode.insertBefore(translationComponent, textNode.nextSibling); } }); } // Check for NATO phonetic patterns and combine consecutive sequences const natoMatches = text.match(natoPattern); if (natoMatches) { // Combine all NATO matches into a single sequence const allNatoWords = natoMatches.join(' '); const combinedTranslated = natoToText(allNatoWords); if (combinedTranslated && combinedTranslated.trim().length > 0) { if (!hasTranslations) { // Create a wrapper for the original text node const wrapper = document.createElement('span'); textNode.parentNode.insertBefore(wrapper, textNode); wrapper.appendChild(textNode); hasTranslations = true; } // Add single NATO translation component for all matches const translationComponent = createTranslationComponent('nato', allNatoWords, combinedTranslated); textNode.parentNode.insertBefore(translationComponent, textNode.nextSibling); } } } // Function to scan for decodable patterns in comments function scanForPatterns() { // Create selectors for paragraph elements within comments const commentParagraphSelectors = COMMENT_SELECTORS.map(selector => `${selector} p`); commentParagraphSelectors.forEach(selector => { const elements = document.querySelectorAll(selector); elements.forEach(element => { // Skip if already processed if (element.hasAttribute('data-processed')) return; element.setAttribute('data-processed', 'true'); // Process all text nodes in the element const walker = document.createTreeWalker( element, NodeFilter.SHOW_TEXT, null, false ); const textNodes = []; let node; while (node = walker.nextNode()) { textNodes.push(node); } textNodes.forEach(processTextNode); }); }); } // Initial scan setTimeout(scanForPatterns, 1000); // Watch for dynamically loaded content const observer = new MutationObserver((mutations) => { let shouldScan = false; mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { // Check if the added node contains comments using centralized selectors if (node.querySelector && COMMENT_SELECTORS.some(selector => node.querySelector(selector) || node.matches(selector) )) { shouldScan = true; } } }); }); if (shouldScan) { setTimeout(scanForPatterns, 500); } }); observer.observe(document.body, { childList: true, subtree: true }); // Scan when comments are expanded or loaded document.addEventListener('click', (e) => { // Check for comment expansion buttons if (e.target.matches('[aria-expanded]') || e.target.closest('[aria-expanded]') || e.target.textContent.includes('more replies') || e.target.textContent.includes('comments')) { setTimeout(scanForPatterns, 1000); } }); console.log('Reddit Binary Decoder v0.3.1 loaded'); })();