您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Encrypts/decrypts text using session-based password & markers. Provides input area for encryption.
// ==UserScript== // @name Ephemeral Encryptor // @namespace https://gf.qytechs.cn/en/users/1465519-user89109 // @version 1.0 // @description Encrypts/decrypts text using session-based password & markers. Provides input area for encryption. // @author UwU7.com // @license All Rights Reserved // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setClipboard // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // --- Constants and Defaults --- const DEFAULT_PASSWORD = "OMkxyLK8tjuvdxbQwG7A"; const DEFAULT_START_MARKER = "[UwU7]"; const DEFAULT_END_MARKER = "[/UwU7]"; const SCRIPT_NAME = "Ephemeral Encryptor"; // --- Session State Variables --- let currentPassword = DEFAULT_PASSWORD; let currentStartMarker = DEFAULT_START_MARKER; let currentEndMarker = DEFAULT_END_MARKER; // --- Crypto Helper Functions --- function arrayBufferToBase64(buffer) { let binary = ''; const bytes = new Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); } function base64ToArrayBuffer(base64) { const binary_string = window.atob(base64); const len = binary_string.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binary_string.charCodeAt(i); } return bytes.buffer; } async function getKey(password, salt) { const enc = new TextEncoder(); const keyMaterial = await window.crypto.subtle.importKey( "raw", enc.encode(password), { name: "PBKDF2" }, false, ["deriveKey"] ); return await window.crypto.subtle.deriveKey( { name: "PBKDF2", salt: salt, iterations: 100000, hash: "SHA-256" }, keyMaterial, { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); } async function encryptText(plainText, password) { try { const salt = window.crypto.getRandomValues(new Uint8Array(16)); const iv = window.crypto.getRandomValues(new Uint8Array(12)); const key = await getKey(password, salt); const enc = new TextEncoder(); const encodedText = enc.encode(plainText); const ciphertext = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: iv }, key, encodedText ); return JSON.stringify({ s: arrayBufferToBase64(salt), i: arrayBufferToBase64(iv), c: arrayBufferToBase64(ciphertext) }); } catch (error) { console.error(`${SCRIPT_NAME} Encryption Error:`, error); throw error; } } async function decryptText(encryptedJsonString, password) { try { const encryptedData = JSON.parse(encryptedJsonString); if (!encryptedData.s || !encryptedData.i || !encryptedData.c) { throw new Error("Invalid encrypted data format."); } const salt = base64ToArrayBuffer(encryptedData.s); const iv = base64ToArrayBuffer(encryptedData.i); const ciphertext = base64ToArrayBuffer(encryptedData.c); const key = await getKey(password, salt); const decryptedBuffer = await window.crypto.subtle.decrypt( { name: "AES-GCM", iv: iv }, key, ciphertext ); const dec = new TextDecoder(); return dec.decode(decryptedBuffer); } catch (error) { throw error; } } // --- UI Functions --- /** * Creates and displays a modal for text input and encryption. */ function showEncryptionInputModal() { const modalId = 'ephemeral-encryptor-input-modal'; const existingModal = document.getElementById(modalId); if (existingModal) { existingModal.remove(); } const modalOverlay = document.createElement('div'); modalOverlay.id = modalId; modalOverlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483646; `; const modalContent = document.createElement('div'); modalContent.style.cssText = ` background-color: #fff; padding: 25px; border-radius: 8px; box-shadow: 0 5px 20px rgba(0,0,0,0.2); width: 90%; max-width: 600px; display: flex; flex-direction: column; gap: 15px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; `; const title = document.createElement('h3'); title.textContent = 'Encrypt Text'; title.style.cssText = "margin: 0 0 5px 0; text-align: center; font-size: 1.4em; color: #333;"; const textarea = document.createElement('textarea'); textarea.rows = 10; textarea.placeholder = "Enter text to encrypt here..."; textarea.style.cssText = ` width: calc(100% - 20px); padding: 10px; border-radius: 5px; border: 1px solid #ccc; font-size: 1em; resize: vertical; min-height: 100px; `; const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = "display: flex; justify-content: space-between; gap: 10px;"; const encryptButton = document.createElement('button'); encryptButton.textContent = 'Encrypt & Copy to Clipboard'; encryptButton.style.cssText = ` padding: 10px 15px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; flex-grow: 1; transition: background-color 0.2s; `; encryptButton.onmouseover = () => encryptButton.style.backgroundColor = '#0056b3'; encryptButton.onmouseout = () => encryptButton.style.backgroundColor = '#007bff'; const closeButton = document.createElement('button'); closeButton.textContent = 'Close'; closeButton.style.cssText = ` padding: 10px 15px; background-color: #6c757d; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; transition: background-color 0.2s; `; closeButton.onmouseover = () => closeButton.style.backgroundColor = '#545b62'; closeButton.onmouseout = () => closeButton.style.backgroundColor = '#6c757d'; encryptButton.addEventListener('click', async () => { const plainText = textarea.value; if (plainText.trim() === "") { alert("Textarea is empty. Nothing to encrypt."); return; } try { const encryptedPayload = await encryptText(plainText, currentPassword); const fullEncryptedBlock = currentStartMarker + encryptedPayload + currentEndMarker; GM_setClipboard(fullEncryptedBlock); encryptButton.textContent = 'Copied!'; textarea.value = ''; // Clear textarea after copying setTimeout(() => { encryptButton.textContent = 'Encrypt & Copy to Clipboard'; }, 2000); } catch (error) { alert("Encryption failed. See console for details."); console.error(`${SCRIPT_NAME} Modal Encryption Error:`, error); } }); closeButton.addEventListener('click', () => { modalOverlay.remove(); }); const escapeKeyListener = (event) => { if (event.key === "Escape") { modalOverlay.remove(); document.removeEventListener('keydown', escapeKeyListener); } }; document.addEventListener('keydown', escapeKeyListener); modalContent.addEventListener('click', (event) => event.stopPropagation()); modalOverlay.addEventListener('click', () => { modalOverlay.remove(); document.removeEventListener('keydown', escapeKeyListener); }); buttonContainer.appendChild(encryptButton); buttonContainer.appendChild(closeButton); modalContent.appendChild(title); modalContent.appendChild(textarea); modalContent.appendChild(buttonContainer); modalOverlay.appendChild(modalContent); document.body.appendChild(modalOverlay); textarea.focus(); } // --- Core Logic for Finding and Processing Encrypted Blocks --- async function processDecryptionInTextNode(node) { let nodeChanged = false; let content = node.nodeValue; let searchStartIndex = 0; while (searchStartIndex < content.length) { const startIndexFull = content.indexOf(currentStartMarker, searchStartIndex); if (startIndexFull === -1) break; const endIndexFull = content.indexOf(currentEndMarker, startIndexFull + currentStartMarker.length); if (endIndexFull === -1) break; const payloadStartIndex = startIndexFull + currentStartMarker.length; const potentialPayload = content.substring(payloadStartIndex, endIndexFull); if (potentialPayload.trim() === "") { searchStartIndex = endIndexFull + currentEndMarker.length; continue; } try { const decryptedText = await decryptText(potentialPayload, currentPassword); const before = content.substring(0, startIndexFull); const after = content.substring(endIndexFull + currentEndMarker.length); content = before + decryptedText + after; nodeChanged = true; searchStartIndex = (before + decryptedText).length; } catch (error) { searchStartIndex = endIndexFull + currentEndMarker.length; } } if (nodeChanged) { node.nodeValue = content; } return nodeChanged; } async function scanAndDecrypt(rootElement = document.body) { const textNodesToProcess = []; const treeWalker = document.createTreeWalker(rootElement, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { if (node.parentElement && (node.parentElement.closest('script, style, textarea, input, [contenteditable="true"], #ephemeral-encryptor-input-modal'))) { return NodeFilter.FILTER_REJECT; } if (!node.nodeValue || node.nodeValue.trim() === '') { return NodeFilter.FILTER_REJECT; } if (node.nodeValue.includes(currentStartMarker) && node.nodeValue.includes(currentEndMarker)) { return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_REJECT; } }); let currentNode; while (currentNode = treeWalker.nextNode()) { textNodesToProcess.push(currentNode); } for (const textNode of textNodesToProcess) { await processDecryptionInTextNode(textNode); } } // encryptAndReplaceSelection function is removed as per user request. // --- MutationObserver for Dynamic Content --- const observer = new MutationObserver(mutationsList => { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(newNode => { if (newNode.nodeType === Node.ELEMENT_NODE) { if (newNode.id !== 'ephemeral-encryptor-input-modal' && !newNode.closest('#ephemeral-encryptor-input-modal')) { scanAndDecrypt(newNode); } } }); } else if (mutation.type === 'characterData') { if (mutation.target && mutation.target.nodeType === Node.TEXT_NODE) { const parent = mutation.target.parentElement; if (parent && parent.id !== 'ephemeral-encryptor-input-modal' && !parent.closest('#ephemeral-encryptor-input-modal')) { if (mutation.target.nodeValue.includes(currentStartMarker) && mutation.target.nodeValue.includes(currentEndMarker)) { processDecryptionInTextNode(mutation.target); } } } } } }); // --- Violentmonkey Menu Commands --- GM_registerMenuCommand('Set Session Password', () => { const newPassword = prompt(`${SCRIPT_NAME}: Enter session password:`, ""); if (newPassword !== null) { currentPassword = newPassword || DEFAULT_PASSWORD; alert(`${SCRIPT_NAME}: Session password ${currentPassword === DEFAULT_PASSWORD ? 'reverted to default' : 'updated'}.`); scanAndDecrypt(); } }); GM_registerMenuCommand('Set Session Markers', () => { const newStartMarker = prompt(`${SCRIPT_NAME}: Enter START marker:`, currentStartMarker); if (newStartMarker === null) return; const newEndMarker = prompt(`${SCRIPT_NAME}: Enter END marker:`, currentEndMarker); if (newEndMarker === null) return; currentStartMarker = newStartMarker.trim() || DEFAULT_START_MARKER; currentEndMarker = newEndMarker.trim() || DEFAULT_END_MARKER; alert(`${SCRIPT_NAME}: Session markers updated.`); scanAndDecrypt(); }); GM_registerMenuCommand('View Current Settings', () => { alert( `${SCRIPT_NAME} - Session Settings:\n\n` + `Password Type: ${currentPassword === DEFAULT_PASSWORD ? 'Default' : 'Custom'}\n` + `Start Marker: ${currentStartMarker}\n` + `End Marker: ${currentEndMarker}\n\n` + `(Settings are for the current session only.)` ); }); // GM_registerMenuCommand('Encrypt Selection (on-page)', encryptAndReplaceSelection); // Removed GM_registerMenuCommand('Encrypt Text (Input & Copy)', showEncryptionInputModal); GM_registerMenuCommand('Manually Re-scan Page', () => { scanAndDecrypt(); alert(`${SCRIPT_NAME}: Page re-scan complete.`); }); // --- Initialization --- function initialize() { scanAndDecrypt(); observer.observe(document.body, { childList: true, subtree: true, characterData: true }); console.log(`${SCRIPT_NAME} initialized. Using ${currentPassword === DEFAULT_PASSWORD ? 'default' : 'custom session'} password.`); } if (document.readyState === 'complete' || document.readyState === 'interactive') { initialize(); } else { document.addEventListener('DOMContentLoaded', initialize, { once: true }); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址