From f65235c5d2cbc396154a7dfaa8d6af65a68c75c3 Mon Sep 17 00:00:00 2001 From: EP Date: Sun, 9 Mar 2025 18:45:56 -0400 Subject: [PATCH] Fix emoji encoding and improve universal decoder --- css/notification.css | 34 +++++++++++ index.html | 1 + js/app.js | 133 +++++++++++++++++++++++++++++++++++++------ js/steganography.js | 44 ++++++++------ js/transforms.js | 50 +++++++++++++++- 5 files changed, 227 insertions(+), 35 deletions(-) create mode 100644 css/notification.css diff --git a/css/notification.css b/css/notification.css new file mode 100644 index 0000000..6c6c33d --- /dev/null +++ b/css/notification.css @@ -0,0 +1,34 @@ +/* Copy notification styles */ +.copy-notification { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #25282c; + color: #00FF41; + padding: 10px 20px; + border-radius: 5px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + z-index: 1000; + display: flex; + align-items: center; + gap: 10px; + animation: fade-in 0.3s ease-in-out; +} + +.copy-notification.error { + color: #ff4141; +} + +.copy-notification.fade-out { + animation: fade-out 0.3s ease-in-out forwards; +} + +@keyframes fade-in { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes fade-out { + from { opacity: 1; transform: translateY(0); } + to { opacity: 0; transform: translateY(20px); } +} diff --git a/index.html b/index.html index 9ee8163..587f164 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@ Parseltongue 2.0 - LLM Payload Crafter + diff --git a/js/app.js b/js/app.js index 72c652f..a66788b 100644 --- a/js/app.js +++ b/js/app.js @@ -83,23 +83,15 @@ new Vue({ return; } - // Try invisible text decoding - let decoded = window.steganography.decodeInvisible(this.decodeInput); - if (decoded) { - this.decodedMessage = decoded; - this.copyToClipboard(decoded); - return; + // Use the universal decoder + const result = this.universalDecode(this.decodeInput); + + if (result) { + this.decodedMessage = `Decoded (${result.method}): ${result.text}`; + this.copyToClipboard(result.text); + } else { + this.decodedMessage = 'No encoded message detected'; } - - // Try emoji decoding - decoded = window.steganography.decodeEmoji(this.decodeInput); - if (decoded) { - this.decodedMessage = decoded; - this.copyToClipboard(decoded); - return; - } - - this.decodedMessage = 'No hidden message found'; }, previewInvisible(text) { return '[invisible]'; @@ -107,11 +99,120 @@ new Vue({ // Utility Methods async copyToClipboard(text) { + if (!text) return; + try { await navigator.clipboard.writeText(text); + + // Show a brief notification + const notification = document.createElement('div'); + notification.className = 'copy-notification'; + notification.innerHTML = ' Copied!'; + document.body.appendChild(notification); + + // Remove after animation + setTimeout(() => { + notification.classList.add('fade-out'); + setTimeout(() => document.body.removeChild(notification), 300); + }, 1000); } catch (err) { console.error('Failed to copy text:', err); + // Show error notification + const notification = document.createElement('div'); + notification.className = 'copy-notification error'; + notification.innerHTML = ' Copy failed'; + document.body.appendChild(notification); + + setTimeout(() => { + notification.classList.add('fade-out'); + setTimeout(() => document.body.removeChild(notification), 300); + }, 1000); } + }, + + // Universal Decoder - tries all decoding methods + universalDecode(input) { + if (!input) return ''; + + // Try all decoders in order + + // 1. Try steganography decoders + // - Check for emoji steganography first + // The emoji encoding uses variation selectors which are hard to see + if (/[\u{1F300}-\u{1F6FF}\u{2600}-\u{26FF}]/u.test(input)) { + const decoded = window.steganography.decodeEmoji(input); + if (decoded) { + return { text: decoded, method: 'Emoji Steganography' }; + } + } + + // - Invisible text + let decoded = window.steganography.decodeInvisible(input); + if (decoded) { + return { text: decoded, method: 'Invisible Text' }; + } + + // 2. Try transform reversals + // - Binary + if (/^[01\s]+$/.test(input.trim())) { + try { + // Use binary transform's reverse function if available + if (window.transforms.binary && window.transforms.binary.reverse) { + const result = window.transforms.binary.reverse(input); + if (result && /[\x20-\x7E]/.test(result)) { // Make sure it's readable ASCII + return { text: result, method: 'Binary' }; + } + } else { + // Fallback implementation + const binText = input.replace(/\s+/g, ''); + let result = ''; + for (let i = 0; i < binText.length; i += 8) { + const byte = binText.substr(i, 8); + if (byte.length === 8) { + result += String.fromCharCode(parseInt(byte, 2)); + } + } + if (result && /[\x20-\x7E]/.test(result)) { // Make sure it's readable ASCII + return { text: result, method: 'Binary' }; + } + } + } catch (e) { + console.error('Binary decode error:', e); + } + } + + // - Morse code + if (/^[.\-\s\/]+$/.test(input.trim())) { + try { + // Use morse transform's reverse function if available + if (window.transforms.morse && window.transforms.morse.reverse) { + const result = window.transforms.morse.reverse(input); + if (result !== input && /[a-zA-Z0-9]/.test(result)) { + return { text: result, method: 'Morse Code' }; + } + } + } catch (e) { + console.error('Morse decode error:', e); + } + } + + // - Try reverse each transform + for (const name in window.transforms) { + const transform = window.transforms[name]; + if (transform.reverse) { + try { + const result = transform.reverse(input); + // Only return if the result is different and contains readable characters + if (result !== input && /[a-zA-Z0-9\s]/.test(result)) { + return { text: result, method: transform.name }; + } + } catch (e) { + console.error(`Error decoding with ${name}:`, e); + } + } + } + + return null; } }, // Initialize theme diff --git a/js/steganography.js b/js/steganography.js index 1ec6ce0..9cbe8ea 100644 --- a/js/steganography.js +++ b/js/steganography.js @@ -6,24 +6,22 @@ const carriers = [ { emoji: '🐊', name: 'CROCODILE', desc: 'Dangerous Croc', preview: text => `🐊${text}` } ]; -// Variation selector functions -function toVariationSelector(byte) { - return String.fromCodePoint(0xFE00 + byte); -} - -function fromVariationSelector(codePoint) { - return codePoint - 0xFE00; -} - // Emoji encoding/decoding function encodeEmoji(emoji, text) { - if (!text) return ''; + if (!text) return emoji; + + // Convert text to binary string + const binary = Array.from(text) + .map(c => c.charCodeAt(0).toString(2).padStart(8, '0')) + .join(''); + + // Use variation selectors to encode binary + const vs15 = '\ufe0e'; // text variation selector (0) + const vs16 = '\ufe0f'; // emoji variation selector (1) - const bytes = new TextEncoder().encode(text); let result = emoji; - - for (const byte of bytes) { - result += toVariationSelector(byte); + for (const bit of binary) { + result += bit === '0' ? vs15 : vs16; } return result; @@ -32,11 +30,23 @@ function encodeEmoji(emoji, text) { function decodeEmoji(text) { if (!text) return ''; - const matches = [...text.matchAll(/[\uFE00-\uFE0F]/g)]; + // Extract variation selectors + const matches = [...text.matchAll(/[\ufe0e\ufe0f]/g)]; if (!matches.length) return ''; - const bytes = new Uint8Array(matches.map(m => fromVariationSelector(m[0].codePointAt(0)))); - return new TextDecoder().decode(bytes); + // Convert variation selectors to binary + const binary = matches.map(m => m[0] === '\ufe0e' ? '0' : '1').join(''); + + // Convert binary to text + let decoded = ''; + for (let i = 0; i < binary.length; i += 8) { + const byte = binary.slice(i, i + 8); + if (byte.length === 8) { + decoded += String.fromCharCode(parseInt(byte, 2)); + } + } + + return decoded; } // Invisible text encoding/decoding diff --git a/js/transforms.js b/js/transforms.js index b5824bd..1b85037 100644 --- a/js/transforms.js +++ b/js/transforms.js @@ -15,11 +15,23 @@ const transforms = { '(': ')', ')': '(', '[': ']', ']': '[', '{': '}', '}': '{', '<': '>', '>': '<', '&': '⅋', '_': '‾' }, + // Create reverse map for decoding + reverseMap: function() { + const revMap = {}; + for (const [key, value] of Object.entries(this.map)) { + revMap[value] = key; + } + return revMap; + }, func: function(text) { return [...text].map(c => this.map[c] || c).reverse().join(''); }, preview: function(text) { return this.func(text); + }, + reverse: function(text) { + const revMap = this.reverseMap(); + return [...text].map(c => revMap[c] || c).reverse().join(''); } }, @@ -130,11 +142,29 @@ const transforms = { '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.' }, - func: function(text) { - return [...text.toLowerCase()].map(c => this.map[c] || c).join(' '); + // Create reverse map for decoding + reverseMap: function() { + const revMap = {}; + for (const [key, value] of Object.entries(this.map)) { + revMap[value] = key; + } + return revMap; + }, + func: function(text, decode = false) { + if (decode) { + // Decode mode + const revMap = this.reverseMap(); + return text.split(/\s+/).map(c => revMap[c] || c).join(''); + } else { + // Encode mode + return [...text.toLowerCase()].map(c => this.map[c] || c).join(' '); + } }, preview: function(text) { return this.func(text); + }, + reverse: function(text) { + return this.func(text, true); } }, @@ -145,8 +175,24 @@ const transforms = { }, preview: function(text) { return this.func(text); + }, + reverse: function(text) { + // Remove spaces and ensure we have valid binary + const binText = text.replace(/\s+/g, ''); + let result = ''; + + // Process 8 bits at a time + for (let i = 0; i < binText.length; i += 8) { + const byte = binText.substr(i, 8); + if (byte.length === 8) { + result += String.fromCharCode(parseInt(byte, 2)); + } + } + return result; } } + // Note: other transforms don't have reverse functions because they're not easily reversible + // The universal decoder will only try to reverse transforms that have a reverse function }; // Export transforms for use in app.js