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