mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-08-28 13:50:49 +02:00
refactor: migrate to modular tool-based architecture
- Implement tool registry system with individual tool modules - Reorganize transformers into categorized source modules - Remove emojiLibrary.js, consolidate into EmojiUtils and emojiData - Fix mobile close button and tooltip functionality - Add build system for transforms and emoji data - Migrate from Python backend to pure JavaScript - Add comprehensive documentation and testing - Improve code organization and maintainability - Ignore generated files (transforms-bundle.js, emojiData.js)
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
function universalDecode(input, context = {}) {
|
||||
if (!input) return null;
|
||||
|
||||
const allDecodings = [];
|
||||
const { activeTab, activeTransform } = context;
|
||||
|
||||
function addDecoding(text, method, priority = 20) {
|
||||
if (text && text !== input && text.length > 0) {
|
||||
const exists = allDecodings.some(d => d.text === text);
|
||||
if (!exists) {
|
||||
allDecodings.push({ text, method, priority });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let foundHighPriorityMatch = false;
|
||||
for (const [transformKey, transform] of Object.entries(window.transforms)) {
|
||||
if (transform.detector && transform.reverse) {
|
||||
try {
|
||||
if (transform.detector(input)) {
|
||||
const result = transform.reverse(input);
|
||||
if (result && result !== input && result.length > 0) {
|
||||
const hasContent = result.replace(/[\x00-\x1F\x7F-\x9F\s]/g, '').length > 0;
|
||||
if (hasContent) {
|
||||
const detectorPriority = transform.priority || 285;
|
||||
addDecoding(result, transform.name, detectorPriority);
|
||||
if (detectorPriority >= 280) {
|
||||
foundHighPriorityMatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug('Error in transform detector:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundHighPriorityMatch || allDecodings.some(d => d.priority >= 280)) {
|
||||
const exclusiveMatches = allDecodings.filter(d => d.priority >= 280);
|
||||
if (exclusiveMatches.length > 0) {
|
||||
exclusiveMatches.sort((a, b) => b.priority - a.priority);
|
||||
return {
|
||||
text: exclusiveMatches[0].text,
|
||||
method: exclusiveMatches[0].method,
|
||||
alternatives: exclusiveMatches.slice(1).map(d => ({ text: d.text, method: d.method }))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (window.steganography && window.steganography.hasEmojiInText && window.steganography.hasEmojiInText(input)) {
|
||||
try {
|
||||
const decoded = window.steganography.decodeEmoji(input);
|
||||
if (decoded) {
|
||||
addDecoding(decoded, 'Emoji Steganography', 100);
|
||||
}
|
||||
} catch (e) {
|
||||
console.debug('Error decoding emoji steganography:', e);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeTab === 'transforms' && activeTransform) {
|
||||
try {
|
||||
const transformKey = Object.keys(window.transforms).find(
|
||||
key => window.transforms[key].name === activeTransform.name
|
||||
);
|
||||
|
||||
if (transformKey && window.transforms[transformKey].reverse) {
|
||||
const result = window.transforms[transformKey].reverse(input);
|
||||
if (result && result !== input) {
|
||||
addDecoding(result, activeTransform.name, 150);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error decoding with active transform:', e);
|
||||
}
|
||||
}
|
||||
|
||||
for (const name in window.transforms) {
|
||||
const transform = window.transforms[name];
|
||||
if (transform.reverse && !transform.detector) {
|
||||
try {
|
||||
const result = transform.reverse(input);
|
||||
if (result !== input && /[a-zA-Z0-9\s]{3,}/.test(result)) {
|
||||
addDecoding(result, transform.name, 10);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error decoding with ${name}:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allDecodings.sort((a, b) => b.priority - a.priority);
|
||||
|
||||
if (allDecodings.length === 0) return null;
|
||||
|
||||
const primary = allDecodings[0];
|
||||
const alternatives = allDecodings.slice(1).map(({ text, method }) => ({ text, method }));
|
||||
|
||||
return { text: primary.text, method: primary.method, alternatives };
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
const __STEG_DEFAULTS__ = {
|
||||
bitZeroVS: '\ufe0e',
|
||||
bitOneVS: '\ufe0f',
|
||||
initialPresentation: 'emoji',
|
||||
trailingZW: '\u200B',
|
||||
interBitZW: null,
|
||||
interBitEvery: 1,
|
||||
bitOrder: 'msb'
|
||||
};
|
||||
let __stegOptions__ = Object.assign({}, __STEG_DEFAULTS__);
|
||||
function setStegOptions(opts) {
|
||||
if (!opts) return;
|
||||
__stegOptions__ = Object.assign({}, __stegOptions__, opts);
|
||||
}
|
||||
|
||||
function encodeForPreview(emoji, text) {
|
||||
return encodeEmoji(emoji, text);
|
||||
}
|
||||
|
||||
function hasEmojiInText(text) {
|
||||
if (!text) return false;
|
||||
if (window.emojiData && typeof window.emojiData === 'object') {
|
||||
const emojiKeys = Object.keys(window.emojiData).filter(key => {
|
||||
const value = window.emojiData[key];
|
||||
return typeof value === 'object' && value !== null && 'official' in value;
|
||||
});
|
||||
if (emojiKeys.some(emoji => text.includes(emoji))) return true;
|
||||
}
|
||||
return /[\u{1F300}-\u{1F9FF}\u{1FA00}-\u{1FAFF}\u{2600}-\u{27BF}\u{1F1E6}-\u{1F1FF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}]/u.test(text);
|
||||
}
|
||||
|
||||
function findEmojiMatch(text) {
|
||||
if (!text) return null;
|
||||
|
||||
if (window.emojiData && typeof window.emojiData === 'object') {
|
||||
const emojiKeys = Object.keys(window.emojiData).filter(key => {
|
||||
const value = window.emojiData[key];
|
||||
return typeof value === 'object' && value !== null && 'official' in value;
|
||||
});
|
||||
|
||||
if (emojiKeys.length > 0) {
|
||||
emojiKeys.sort((a, b) => b.length - a.length);
|
||||
const escapedEmojis = emojiKeys.map(emoji =>
|
||||
emoji.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
);
|
||||
const emojiRegex = new RegExp(`(${escapedEmojis.join('|')})`, 'u');
|
||||
const match = text.match(emojiRegex);
|
||||
if (match) return match;
|
||||
}
|
||||
}
|
||||
|
||||
const flagEmojiRegex = /([\u{1F1E6}-\u{1F1FF}][\u{1F1E6}-\u{1F1FF}])/u;
|
||||
const singleEmojiRegex = /([\u{1F300}-\u{1F9FF}\u{1FA00}-\u{1FAFF}\u{2600}-\u{27BF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}])/u;
|
||||
|
||||
return text.match(flagEmojiRegex) || text.match(singleEmojiRegex);
|
||||
}
|
||||
|
||||
const carriers = [
|
||||
{
|
||||
emoji: '🐍',
|
||||
name: 'SNAKE',
|
||||
desc: 'Classic Snake',
|
||||
preview: function(text) {
|
||||
return encodeForPreview(this.emoji, text);
|
||||
}
|
||||
},
|
||||
{
|
||||
emoji: '🐉',
|
||||
name: 'DRAGON',
|
||||
desc: 'Mystical Dragon',
|
||||
preview: function(text) {
|
||||
return encodeForPreview(this.emoji, text);
|
||||
}
|
||||
},
|
||||
{
|
||||
emoji: '🦎',
|
||||
name: 'LIZARD',
|
||||
desc: 'Sneaky Lizard',
|
||||
preview: function(text) {
|
||||
return encodeForPreview(this.emoji, text);
|
||||
}
|
||||
},
|
||||
{
|
||||
emoji: '🐊',
|
||||
name: 'CROCODILE',
|
||||
desc: 'Dangerous Croc',
|
||||
preview: function(text) {
|
||||
return encodeForPreview(this.emoji, text);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function encodeEmoji(emoji, text) {
|
||||
if (!text) return emoji;
|
||||
|
||||
let binary = '';
|
||||
try {
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(text);
|
||||
const bitOrder = __stegOptions__.bitOrder || 'msb';
|
||||
binary = Array.from(bytes)
|
||||
.map(byte => {
|
||||
let byteStr = byte.toString(2).padStart(8, '0');
|
||||
if (bitOrder === 'lsb') {
|
||||
byteStr = byteStr.split('').reverse().join('');
|
||||
}
|
||||
return byteStr;
|
||||
})
|
||||
.join('');
|
||||
} catch (e) {
|
||||
const bitOrder = __stegOptions__.bitOrder || 'msb';
|
||||
binary = Array.from(text)
|
||||
.map(c => {
|
||||
const codePoint = c.codePointAt(0);
|
||||
let bytes = [];
|
||||
if (codePoint <= 0x7F) {
|
||||
bytes.push(codePoint);
|
||||
} else if (codePoint <= 0x7FF) {
|
||||
bytes.push(0xC0 | (codePoint >> 6));
|
||||
bytes.push(0x80 | (codePoint & 0x3F));
|
||||
} else if (codePoint <= 0xFFFF) {
|
||||
bytes.push(0xE0 | (codePoint >> 12));
|
||||
bytes.push(0x80 | ((codePoint >> 6) & 0x3F));
|
||||
bytes.push(0x80 | (codePoint & 0x3F));
|
||||
} else {
|
||||
bytes.push(0xF0 | (codePoint >> 18));
|
||||
bytes.push(0x80 | ((codePoint >> 12) & 0x3F));
|
||||
bytes.push(0x80 | ((codePoint >> 6) & 0x3F));
|
||||
bytes.push(0x80 | (codePoint & 0x3F));
|
||||
}
|
||||
return bytes.map(byte => {
|
||||
let byteStr = byte.toString(2).padStart(8, '0');
|
||||
if (bitOrder === 'lsb') {
|
||||
byteStr = byteStr.split('').reverse().join('');
|
||||
}
|
||||
return byteStr;
|
||||
}).join('');
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
const vs0 = __stegOptions__.bitZeroVS || '\ufe0e';
|
||||
const vs1 = __stegOptions__.bitOneVS || '\ufe0f';
|
||||
|
||||
let result = emoji;
|
||||
if (__stegOptions__.initialPresentation === 'emoji') result += '\ufe0f';
|
||||
else if (__stegOptions__.initialPresentation === 'text') result += '\ufe0e';
|
||||
|
||||
for (let i=0;i<binary.length;i++) {
|
||||
const bit = binary[i];
|
||||
result += bit === '0' ? vs0 : vs1;
|
||||
if (__stegOptions__.interBitZW && i < binary.length-1 && ((i+1) % Math.max(1, __stegOptions__.interBitEvery)) === 0) {
|
||||
result += __stegOptions__.interBitZW;
|
||||
}
|
||||
}
|
||||
|
||||
if (__stegOptions__.trailingZW) {
|
||||
result += __stegOptions__.trailingZW;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function decodeEmoji(text) {
|
||||
if (!text) return '';
|
||||
|
||||
const emojiMatch = findEmojiMatch(text);
|
||||
if (!emojiMatch) return '';
|
||||
|
||||
const emojiChar = emojiMatch[1];
|
||||
const emojiIndex = emojiMatch.index;
|
||||
|
||||
const fromEmoji = text.substring(emojiIndex);
|
||||
const emojiCharEscaped = emojiChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`^${emojiCharEscaped}([\ufe0e\ufe0f\u200B\u200C\u200D\ufeff]+)`, 'u');
|
||||
const emojiData = fromEmoji.match(pattern);
|
||||
|
||||
if (!emojiData || !emojiData[1]) return '';
|
||||
|
||||
const rawSeq = emojiData[1];
|
||||
const matches = [...rawSeq.matchAll(/[\ufe0e\ufe0f]/g)];
|
||||
if (matches.length === 0) return '';
|
||||
|
||||
const skip = (__stegOptions__.initialPresentation === 'none') ? 0 : 1;
|
||||
if (matches.length <= skip) return '';
|
||||
|
||||
const zeroSel = __stegOptions__.bitZeroVS || '\ufe0e';
|
||||
const oneSel = __stegOptions__.bitOneVS || '\ufe0f';
|
||||
let binary = matches.slice(skip).map(m => m[0] === zeroSel ? '0' : (m[0] === oneSel ? '1' : '')).join('');
|
||||
|
||||
const validBinaryLength = Math.floor(binary.length / 8) * 8;
|
||||
const bytes = [];
|
||||
for (let i = 0; i < validBinaryLength; i += 8) {
|
||||
let byte = binary.slice(i, i + 8);
|
||||
if (__stegOptions__.bitOrder === 'lsb') {
|
||||
byte = byte.split('').reverse().join('');
|
||||
}
|
||||
if (byte.length === 8) {
|
||||
const byteValue = parseInt(byte, 2);
|
||||
bytes.push(byteValue);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const decoder = new TextDecoder('utf-8', { fatal: false });
|
||||
const uint8Array = new Uint8Array(bytes);
|
||||
return decoder.decode(uint8Array);
|
||||
} catch (e) {
|
||||
let decoded = '';
|
||||
for (const byteValue of bytes) {
|
||||
if (byteValue >= 0 && byteValue <= 255) {
|
||||
decoded += String.fromCharCode(byteValue);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return decodeURIComponent(escape(decoded));
|
||||
} catch (e2) {
|
||||
return decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function encodeInvisible(text) {
|
||||
if (!text) return '';
|
||||
|
||||
const bytes = new TextEncoder().encode(text);
|
||||
return Array.from(bytes)
|
||||
.map(byte => String.fromCodePoint(0xE0000 + byte))
|
||||
.join('');
|
||||
}
|
||||
|
||||
function decodeInvisible(text) {
|
||||
if (!text) return '';
|
||||
|
||||
const matches = [...text.matchAll(/[\uE0000-\uE007F]/g)];
|
||||
if (!matches.length) return '';
|
||||
|
||||
const bytes = new Uint8Array(matches.length);
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
bytes[i] = matches[i][0].codePointAt(0) - 0xE0000;
|
||||
}
|
||||
|
||||
try {
|
||||
const decoder = new TextDecoder('utf-8', {fatal: false});
|
||||
let decoded = decoder.decode(bytes);
|
||||
decoded = decoded.replace(/@+(?=[a-zA-Z0-9])/g, '');
|
||||
decoded = decoded.replace(/([a-zA-Z0-9])@+/g, '$1');
|
||||
decoded = decoded.replace(/@+/g, '');
|
||||
return decoded;
|
||||
} catch (e) {
|
||||
console.error('Error decoding invisible text:', e);
|
||||
let result = '';
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i] >= 32 && bytes[i] <= 126) {
|
||||
result += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
window.steganography = {
|
||||
carriers,
|
||||
encodeEmoji,
|
||||
decodeEmoji,
|
||||
encodeInvisible,
|
||||
decodeInvisible,
|
||||
setStegOptions,
|
||||
hasEmojiInText
|
||||
};
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* Tool Registry and Loader
|
||||
* Manages all available tools and provides dynamic loading
|
||||
*/
|
||||
|
||||
// Import all tools (they should be loaded before this file)
|
||||
// Tools will be registered here
|
||||
|
||||
class ToolRegistry {
|
||||
constructor() {
|
||||
this.tools = new Map();
|
||||
this.toolsArray = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a tool
|
||||
* @param {Tool} tool - Tool instance to register
|
||||
*/
|
||||
register(tool) {
|
||||
if (!(tool instanceof Tool)) {
|
||||
console.error('Tool must be an instance of Tool class');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tool.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tools.set(tool.id, tool);
|
||||
this.toolsArray.push(tool);
|
||||
|
||||
// Sort by order
|
||||
this.toolsArray.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a tool by ID
|
||||
* @param {string} id - Tool ID
|
||||
* @returns {Tool|null}
|
||||
*/
|
||||
get(id) {
|
||||
return this.tools.get(id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered tools
|
||||
* @returns {Array<Tool>}
|
||||
*/
|
||||
getAll() {
|
||||
return this.toolsArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all enabled tools
|
||||
* @returns {Array<Tool>}
|
||||
*/
|
||||
getEnabled() {
|
||||
return this.toolsArray.filter(tool => tool.enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge Vue data from all tools
|
||||
* @returns {Object}
|
||||
*/
|
||||
mergeVueData() {
|
||||
const merged = {};
|
||||
this.toolsArray.forEach(tool => {
|
||||
const toolData = tool.getVueData();
|
||||
Object.assign(merged, toolData);
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge Vue methods from all tools
|
||||
* @returns {Object}
|
||||
*/
|
||||
mergeVueMethods() {
|
||||
const merged = {};
|
||||
this.toolsArray.forEach(tool => {
|
||||
const toolMethods = tool.getVueMethods();
|
||||
Object.assign(merged, toolMethods);
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge Vue watchers from all tools
|
||||
* @returns {Object}
|
||||
*/
|
||||
mergeVueWatchers() {
|
||||
const merged = {};
|
||||
this.toolsArray.forEach(tool => {
|
||||
const toolWatchers = tool.getVueWatchers();
|
||||
Object.assign(merged, toolWatchers);
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge Vue lifecycle hooks from all tools
|
||||
* @returns {Object}
|
||||
*/
|
||||
mergeVueLifecycle() {
|
||||
const merged = {};
|
||||
this.toolsArray.forEach(tool => {
|
||||
const toolLifecycle = tool.getVueLifecycle();
|
||||
Object.keys(toolLifecycle).forEach(hook => {
|
||||
if (!merged[hook]) {
|
||||
merged[hook] = [];
|
||||
}
|
||||
merged[hook].push(toolLifecycle[hook]);
|
||||
});
|
||||
});
|
||||
|
||||
// Convert arrays to functions that call all hooks
|
||||
const result = {};
|
||||
Object.keys(merged).forEach(hook => {
|
||||
result[hook] = function() {
|
||||
const args = arguments;
|
||||
merged[hook].forEach(fn => {
|
||||
if (typeof fn === 'function') {
|
||||
fn.apply(this, args);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HTML for all tab buttons
|
||||
* @returns {String}
|
||||
*/
|
||||
generateTabButtonsHTML() {
|
||||
return this.toolsArray.map(tool => tool.getTabButtonHTML()).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate HTML for all tab content
|
||||
* @returns {String}
|
||||
*/
|
||||
generateTabContentHTML() {
|
||||
return this.toolsArray.map(tool => tool.getTabContentHTML()).join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tool activation
|
||||
* @param {string} toolId - Tool ID
|
||||
* @param {Vue} vueInstance - Vue instance
|
||||
*/
|
||||
activateTool(toolId, vueInstance) {
|
||||
const tool = this.get(toolId);
|
||||
if (tool && typeof tool.onActivate === 'function') {
|
||||
tool.onActivate(vueInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tool deactivation
|
||||
* @param {string} toolId - Tool ID
|
||||
* @param {Vue} vueInstance - Vue instance
|
||||
*/
|
||||
deactivateTool(toolId, vueInstance) {
|
||||
const tool = this.get(toolId);
|
||||
if (tool && typeof tool.onDeactivate === 'function') {
|
||||
tool.onDeactivate(vueInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create global registry instance
|
||||
window.ToolRegistry = ToolRegistry;
|
||||
window.toolRegistry = new ToolRegistry();
|
||||
|
||||
// Auto-register tools if they're available
|
||||
if (typeof DecodeTool !== 'undefined') {
|
||||
window.toolRegistry.register(new DecodeTool());
|
||||
}
|
||||
if (typeof EmojiTool !== 'undefined') {
|
||||
window.toolRegistry.register(new EmojiTool());
|
||||
}
|
||||
if (typeof GibberishTool !== 'undefined') {
|
||||
window.toolRegistry.register(new GibberishTool());
|
||||
}
|
||||
if (typeof MutationTool !== 'undefined') {
|
||||
window.toolRegistry.register(new MutationTool());
|
||||
}
|
||||
if (typeof SplitterTool !== 'undefined') {
|
||||
window.toolRegistry.register(new SplitterTool());
|
||||
}
|
||||
if (typeof TokenadeTool !== 'undefined') {
|
||||
window.toolRegistry.register(new TokenadeTool());
|
||||
}
|
||||
if (typeof TokenizerTool !== 'undefined') {
|
||||
window.toolRegistry.register(new TokenizerTool());
|
||||
}
|
||||
if (typeof TransformTool !== 'undefined') {
|
||||
window.toolRegistry.register(new TransformTool());
|
||||
}
|
||||
|
||||
// Export for module systems
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = ToolRegistry;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user