mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-08-28 22:00:48 +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,39 @@
|
||||
# JavaScript Directory Structure
|
||||
|
||||
## Core Modules (`js/core/`)
|
||||
|
||||
- `decoder.js` - Universal decoder for automatic encoding detection
|
||||
- `steganography.js` - Emoji and invisible text steganography
|
||||
- `emojiLibrary.js` - Emoji search, filtering, and library functions
|
||||
- `toolRegistry.js` - Tool registration and Vue data/method merging
|
||||
|
||||
## Utilities (`js/utils/`)
|
||||
|
||||
- `clipboard.js` - `ClipboardUtils.copy()` - Clipboard API wrapper
|
||||
- `focus.js` - `FocusUtils.focusWithoutScroll()`, `clearFocusAndSelection()`
|
||||
- `history.js` - `HistoryUtils` - Copy history management
|
||||
- `notifications.js` - `NotificationUtils` - Toast notifications
|
||||
- `theme.js` - `ThemeUtils` - Dark/light theme management
|
||||
- `escapeParser.js` - Escape sequence parsing
|
||||
|
||||
## Tools (`js/tools/`)
|
||||
|
||||
Tool classes extending `Tool` base class. Auto-discovered by `build/inject-tool-scripts.js`.
|
||||
|
||||
## Data (`js/data/`)
|
||||
|
||||
- `emojiData.js` - Generated emoji data (build output)
|
||||
- `emojiCompatibility.js` - Emoji compatibility mappings
|
||||
|
||||
## Bundles (`js/bundles/`)
|
||||
|
||||
- `transforms-bundle.js` - Bundled transformer modules (build output)
|
||||
|
||||
## Load Order
|
||||
|
||||
1. Data files (emojiData, emojiCompatibility)
|
||||
2. Generated bundles (transforms-bundle)
|
||||
3. Utilities (escapeParser, focus, notifications, history, clipboard, theme)
|
||||
4. Core modules (steganography, decoder, emojiLibrary)
|
||||
5. Tool system (Tool.js, *Tool.js files, toolRegistry)
|
||||
6. Main app (app.js)
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Application Configuration Constants
|
||||
*/
|
||||
window.CONFIG = {
|
||||
// History configuration
|
||||
MAX_HISTORY_ITEMS: 50,
|
||||
|
||||
// Danger threshold for tokenade
|
||||
DANGER_THRESHOLD_TOKENS: 10000,
|
||||
|
||||
// Clipboard operation timing
|
||||
CLIPBOARD_DEBOUNCE_MS: 100,
|
||||
CLIPBOARD_LOCK_TIMEOUT_MS: 500,
|
||||
CLIPBOARD_FALLBACK_DEBOUNCE_MS: 150,
|
||||
KEYBOARD_EVENTS_TIMEOUT_MS: 1000,
|
||||
PASTE_FLAG_RESET_DELAY_MS: 200,
|
||||
|
||||
// Emoji grid initialization
|
||||
EMOJI_GRID_INIT_INTERVAL_MS: 500
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Emoji Compatibility Checker
|
||||
* Tests which emoji features the user's browser/device supports
|
||||
*/
|
||||
|
||||
window.emojiCompatibility = {
|
||||
// Cache key for localStorage
|
||||
CACHE_KEY: 'emojiTestResults_v2_simple', // Simple pixel detection only
|
||||
CACHE_EXPIRY_DAYS: 30,
|
||||
|
||||
// In-memory cache for emoji test results
|
||||
_emojiTestCache: null,
|
||||
|
||||
/**
|
||||
* Load emoji test cache from localStorage
|
||||
*/
|
||||
loadCache: function() {
|
||||
if (this._emojiTestCache) return this._emojiTestCache;
|
||||
|
||||
try {
|
||||
const cached = localStorage.getItem(this.CACHE_KEY);
|
||||
if (!cached) return null;
|
||||
|
||||
const data = JSON.parse(cached);
|
||||
|
||||
// Check if cache is expired
|
||||
const now = Date.now();
|
||||
const age = now - data.timestamp;
|
||||
const maxAge = this.CACHE_EXPIRY_DAYS * 24 * 60 * 60 * 1000;
|
||||
|
||||
if (age > maxAge) {
|
||||
localStorage.removeItem(this.CACHE_KEY);
|
||||
return null;
|
||||
}
|
||||
|
||||
this._emojiTestCache = data.results;
|
||||
return this._emojiTestCache;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Save emoji test results to localStorage
|
||||
* (Called after testing all emojis)
|
||||
*/
|
||||
saveCache: function() {
|
||||
if (!this._emojiTestCache) return;
|
||||
|
||||
try {
|
||||
const data = {
|
||||
timestamp: Date.now(),
|
||||
results: this._emojiTestCache
|
||||
};
|
||||
localStorage.setItem(this.CACHE_KEY, JSON.stringify(data));
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Could not save emoji test cache:', e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the emoji test cache (useful for debugging or forcing refresh)
|
||||
*/
|
||||
clearCache: function() {
|
||||
localStorage.removeItem(this.CACHE_KEY);
|
||||
this._emojiTestCache = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Test if a specific emoji actually renders in the browser
|
||||
* Uses canvas pixel detection - the definitive test for visual rendering
|
||||
*/
|
||||
testEmojiRenders: function(emoji) {
|
||||
// Load cache if not already loaded
|
||||
if (!this._emojiTestCache) {
|
||||
this._emojiTestCache = this.loadCache() || {};
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
if (emoji in this._emojiTestCache) {
|
||||
return this._emojiTestCache[emoji];
|
||||
}
|
||||
|
||||
// Cache canvas for performance
|
||||
if (!this._testCanvas) {
|
||||
this._testCanvas = document.createElement('canvas');
|
||||
this._testCanvas.width = 64;
|
||||
this._testCanvas.height = 64;
|
||||
// Set willReadFrequently for better performance with multiple getImageData calls
|
||||
this._testCtx = this._testCanvas.getContext('2d', { willReadFrequently: true });
|
||||
}
|
||||
|
||||
const ctx = this._testCtx;
|
||||
// Use emoji font to ensure missing emojis render as boxes
|
||||
ctx.font = '48px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "EmojiOne Color", "Android Emoji", sans-serif';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.textAlign = 'left';
|
||||
|
||||
// Width test - catches multi-character fallbacks like "???"
|
||||
const emojiWidth = ctx.measureText(emoji).width;
|
||||
const referenceWidth = ctx.measureText('😊').width;
|
||||
|
||||
// If emoji is much wider than a single emoji, it's likely broken into multiple chars
|
||||
if (emojiWidth > referenceWidth * 1.8) {
|
||||
this._emojiTestCache[emoji] = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pixel detection - does the emoji actually render visually?
|
||||
ctx.clearRect(0, 0, 64, 64);
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(emoji, 8, 8);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, 64, 64).data;
|
||||
|
||||
// Check if any pixels were drawn (alpha channel > 0)
|
||||
let hasPixels = false;
|
||||
for (let i = 0; i < imageData.length; i += 4) {
|
||||
if (imageData[i + 3] > 0) {
|
||||
hasPixels = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache and return result
|
||||
this._emojiTestCache[emoji] = hasPixels;
|
||||
return hasPixels;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a specific emoji should be shown in the UI picker
|
||||
* based on browser compatibility
|
||||
*/
|
||||
shouldShowInPicker: function(emoji, data) {
|
||||
// Simple check: Does it actually render?
|
||||
// This single test catches all broken emojis regardless of type
|
||||
return this.testEmojiRenders(emoji);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get compatible emojis from a list (batch testing with progress callback)
|
||||
* @param {Array<string>} allEmojis - Full list of emojis to test
|
||||
* @param {Function} progressCallback - Optional callback (tested, total, compatible)
|
||||
* @returns {Promise<Array<string>>} - Array of compatible emojis
|
||||
*/
|
||||
getCompatibleEmojis: async function(allEmojis, progressCallback) {
|
||||
// Load cache first
|
||||
this.loadCache();
|
||||
|
||||
const compatible = [];
|
||||
let tested = 0;
|
||||
const total = allEmojis.length;
|
||||
|
||||
// Test emojis in batches to avoid blocking
|
||||
const batchSize = 50;
|
||||
|
||||
function testBatch() {
|
||||
return new Promise((resolve) => {
|
||||
const end = Math.min(tested + batchSize, total);
|
||||
|
||||
for (let i = tested; i < end; i++) {
|
||||
const emoji = allEmojis[i];
|
||||
if (this.shouldShowInPicker(emoji)) {
|
||||
compatible.push(emoji);
|
||||
}
|
||||
tested++;
|
||||
}
|
||||
|
||||
// Report progress
|
||||
if (progressCallback) {
|
||||
progressCallback(tested, total, compatible.length);
|
||||
}
|
||||
|
||||
// Continue or finish
|
||||
if (tested < total) {
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => resolve(testBatch.call(this)), 10);
|
||||
});
|
||||
} else {
|
||||
// Save cache when done
|
||||
this.saveCache();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await testBatch.call(this);
|
||||
return compatible;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get compatibility stats
|
||||
*/
|
||||
getStats: function() {
|
||||
const cache = this.loadCache();
|
||||
if (cache) {
|
||||
const compatible = Object.values(cache).filter(v => v === true).length;
|
||||
const total = Object.keys(cache).length;
|
||||
return {
|
||||
compatible: compatible,
|
||||
total: total,
|
||||
percentage: total > 0 ? ((compatible / total) * 100).toFixed(1) : 0
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
// Emoji Library for P4RS3LT0NGV3
|
||||
|
||||
// Create namespace for emoji library
|
||||
window.emojiLibrary = {};
|
||||
|
||||
// Polyfill for Intl.Segmenter if not available
|
||||
if (!Intl.Segmenter) {
|
||||
console.warn('Intl.Segmenter not available, falling back to basic character splitting');
|
||||
}
|
||||
|
||||
// Helper function to properly split text into grapheme clusters (emojis)
|
||||
window.emojiLibrary.splitEmojis = function(text) {
|
||||
if (Intl.Segmenter) {
|
||||
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
||||
return Array.from(segmenter.segment(text), ({ segment }) => segment);
|
||||
}
|
||||
return Array.from(text);
|
||||
};
|
||||
|
||||
// Helper function to properly join emojis
|
||||
window.emojiLibrary.joinEmojis = function(emojis) {
|
||||
return emojis.join('');
|
||||
};
|
||||
|
||||
// Define emoji categories with specific emojis for each category
|
||||
window.emojiLibrary.EMOJIS = {
|
||||
nature: ["🌈", "🌞", "🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🦊", "🦁", "🐯", "🐮", "🐷", "🐸", "🐵", "🐔", "🐧", "🐦", "🐤", "🦆", "🦅", "🦉", "🦇", "🐺", "🐗", "🐴", "🦄", "🐝", "🐛", "🦋", "🐌", "🐞", "🐜", "🕷️", "🦂", "🦟", "🦠", "🪱"],
|
||||
mystical: ["🧙", "🧙♂️", "🧙♀️", "🧚", "🧚♂️", "🧚♀️", "🧛", "🧛♂️", "🧛♀️", "🧜", "🧜♂️", "🧜♀️", "👹", "👺", "👻", "👽", "👾", "🐲", "🔮", "🐍", "🐉", "🦄", "⚗️", "🔯", "🔱", "⚜️", "✨", "🌠", "🌋", "💎", "🩸"],
|
||||
faces_people: ["😀", "😁", "😂", "🤣", "😃", "😄", "😅", "😆", "😉", "😊", "😋", "😎", "😍", "😘", "🥰", "😗", "😙", "😚", "🙂", "🤗", "🤩", "🤔", "🤨", "😐", "😑", "😶", "🙄", "😏", "😣", "😥", "😮", "🤐", "😯", "😪", "😫", "😴", "😌", "😛", "😜", "😝", "🤤", "😒", "😓", "😔", "😕", "🙃", "🤑", "😲", "🙁", "😖", "😞", "😟", "😤", "😢", "😭", "😧", "😨", "😩", "🤯", "😱", "😳", "🥵", "🥶", "😡", "😠", "🤬", "😷", "🤒", "🤕", "🤢", "🤮", "🤧", "😇", "🥳", "🥴", "🥺", "🧐", "🥱", "🧠"],
|
||||
|
||||
gestures: ["👍", "👎", "👌", "✌️", "🤞", "🤟", "🤘", "🤙", "👈", "👉", "👆", "👇", "🖕", "☝️", "✋", "🤚", "🖐️", "🖖", "👋", "🤏", "👐", "🙌", "👏", "🤝", "🙏"],
|
||||
|
||||
animals_nature: ["🐇", "🦊", "🦁", "🐯", "🐮", "🐷", "🐸", "🐵", "🐔", "🐧", "🐦", "🐤", "🦆", "🦅", "🦉", "🦇", "🐺", "🐗", "🐴", "🐝", "🐛", "🦋", "🐌", "🐞", "🐜", "🕷️", "🦂", "🐍", "🦨", "🦩", "🦫", "🦬", "🐻❄️", "🐼", "🐨", "🐕", "🐶", "🐩", "🐈", "🐱"],
|
||||
|
||||
activities_sports: ["⚽", "🏀", "🏈", "🏐", "🏉", "🎾", "🎳", "🏑", "🏒", "🏓", "🏸", "🥊", "🥋", "🥅", "🤾", "🎿", "🏄", "🏂", "🏊", "🏋️", "🤼", "🤸", "🤺", "🤽", "🤹", "🎯", "🎱", "🎽", "🚴", "🚵"],
|
||||
|
||||
technology_objects: ["💻", "⌨️", "🖥️", "🖱️", "🖨️", "📱", "☎️", "📞", "📟", "📠", "📺", "📻", "🎙️", "🎚️", "🎛️", "🧭", "📡", "🔋", "🔌", "💡", "🛢️", "💸", "💵", "💳", "🔑", "🔓", "🔒"],
|
||||
|
||||
mystical_fantasy: ["🧙", "🧚", "🧛", "🧜", "👹", "👺", "👻", "👽", "👾", "🔮", "🪄", "🐉", "🐲", "🦄"],
|
||||
|
||||
nature_weather: ["🌈", "🌞", "🌙", "⭐", "🌟", "⚡", "❄️", "🔥", "💧", "🌊", "🌪️", "🌋"],
|
||||
|
||||
symbols: ["❤️", "💛", "💚", "💙", "💜", "💔", "💕", "💞", "💓", "💗", "💖", "💘", "💝", "💟", "💢", "💣", "💥", "💦", "💨", "💩", "💫", "💬", "💠", "💮"],
|
||||
|
||||
flags: ["🏁", "🚩", "🎌", "🏴", "🏳️", "🏳️🌈", "🏳️⚧️", "🏴☠️", "🇺🇸", "🇨🇦", "🇬🇧", "🇩🇪", "🇫🇷", "🇮🇹", "🇯🇵", "🇰🇷", "🇷🇺", "🇨🇳", "🇮🇳", "🇧🇷", "🇦🇺", "🇪🇸", "🇳🇱", "🇸🇪"]
|
||||
};
|
||||
|
||||
// Define standard emoji categories
|
||||
window.emojiLibrary.CATEGORIES = [
|
||||
{ id: 'all', name: 'All Emojis', icon: '🔍' },
|
||||
{ id: 'faces_people', name: 'Faces & People', icon: '😀' },
|
||||
{ id: 'gestures', name: 'Gestures', icon: '👍' },
|
||||
{ id: 'animals_nature', name: 'Animals & Nature', icon: '🦊' },
|
||||
{ id: 'activities_sports', name: 'Activities & Sports', icon: '⚽' },
|
||||
{ id: 'technology_objects', name: 'Tech & Objects', icon: '💻' },
|
||||
{ id: 'mystical_fantasy', name: 'Mystical & Fantasy', icon: '🧙' },
|
||||
{ id: 'nature_weather', name: 'Nature & Weather', icon: '🌈' },
|
||||
{ id: 'symbols', name: 'Symbols', icon: '❤️' },
|
||||
{ id: 'flags', name: 'Flags', icon: '🏁' }
|
||||
];
|
||||
|
||||
// Auto-generate EMOJI_LIST from the categorized EMOJIS object
|
||||
// This ensures a single source of truth for all emojis
|
||||
window.emojiLibrary.EMOJI_LIST = (() => {
|
||||
const allEmojis = [];
|
||||
// Combine all emojis from all categories
|
||||
Object.values(window.emojiLibrary.EMOJIS).forEach(categoryEmojis => {
|
||||
allEmojis.push(...categoryEmojis);
|
||||
});
|
||||
// Remove duplicates using Set and return as array
|
||||
return Array.from(new Set(allEmojis));
|
||||
})();
|
||||
|
||||
// Function to render emoji grid with categories
|
||||
window.emojiLibrary.renderEmojiGrid = function(containerId, onEmojiSelect, filteredList) {
|
||||
console.log('Rendering emoji grid to:', containerId);
|
||||
|
||||
// Get container by ID
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) {
|
||||
console.error('Container not found:', containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear container
|
||||
container.innerHTML = '';
|
||||
|
||||
// Add header with instruction message
|
||||
const emojiHeader = document.createElement('div');
|
||||
emojiHeader.className = 'emoji-header';
|
||||
emojiHeader.innerHTML = '<h3><i class="fas fa-icons"></i> Choose an Emoji</h3><p class="emoji-subtitle"><i class="fas fa-magic"></i> Click any emoji to copy your hidden message</p>';
|
||||
container.appendChild(emojiHeader);
|
||||
|
||||
// Create category tabs
|
||||
const categoryTabs = document.createElement('div');
|
||||
categoryTabs.className = 'emoji-category-tabs';
|
||||
|
||||
// Add category tabs
|
||||
window.emojiLibrary.CATEGORIES.forEach(category => {
|
||||
const tab = document.createElement('button');
|
||||
tab.className = 'emoji-category-tab';
|
||||
if (category.id === 'all') {
|
||||
tab.classList.add('active');
|
||||
}
|
||||
tab.setAttribute('data-category', category.id);
|
||||
tab.innerHTML = `${category.icon} ${category.name}`;
|
||||
categoryTabs.appendChild(tab);
|
||||
});
|
||||
|
||||
container.appendChild(categoryTabs);
|
||||
|
||||
// Create emoji grid with enforced styling
|
||||
const gridContainer = document.createElement('div');
|
||||
gridContainer.className = 'emoji-grid';
|
||||
|
||||
// Get the active category
|
||||
let activeCategory = 'all';
|
||||
const activeCategoryTab = container.querySelector('.emoji-category-tab.active');
|
||||
if (activeCategoryTab) {
|
||||
activeCategory = activeCategoryTab.getAttribute('data-category');
|
||||
}
|
||||
|
||||
// Determine which emojis to show based on category and filter
|
||||
let emojisToShow = [];
|
||||
|
||||
if (filteredList && filteredList.length > 0) {
|
||||
// If we have a filtered list (from search), use that
|
||||
emojisToShow = filteredList;
|
||||
} else if (activeCategory === 'all') {
|
||||
// For 'all' category, combine all emojis from the categories and deduplicate
|
||||
Object.values(window.emojiLibrary.EMOJIS).forEach(categoryEmojis => {
|
||||
emojisToShow = [...emojisToShow, ...categoryEmojis];
|
||||
});
|
||||
// Remove duplicates using Set
|
||||
emojisToShow = Array.from(new Set(emojisToShow));
|
||||
} else if (window.emojiLibrary.EMOJIS[activeCategory]) {
|
||||
// For specific category, use emojis from that category
|
||||
emojisToShow = window.emojiLibrary.EMOJIS[activeCategory];
|
||||
}
|
||||
|
||||
console.log(`Adding ${emojisToShow.length} emojis to grid for category: ${activeCategory}`);
|
||||
|
||||
// Add emojis to grid with enforced styling
|
||||
emojisToShow.forEach(emoji => {
|
||||
const emojiButton = document.createElement('button');
|
||||
emojiButton.className = 'emoji-button';
|
||||
emojiButton.textContent = emoji; // Use textContent for better emoji handling
|
||||
emojiButton.title = 'Click to encode with this emoji';
|
||||
|
||||
emojiButton.addEventListener('click', () => {
|
||||
if (typeof onEmojiSelect === 'function') {
|
||||
onEmojiSelect(emoji);
|
||||
// Add visual feedback when clicked
|
||||
emojiButton.style.backgroundColor = '#e6f7ff';
|
||||
setTimeout(() => {
|
||||
emojiButton.style.backgroundColor = '';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
gridContainer.appendChild(emojiButton);
|
||||
});
|
||||
|
||||
container.appendChild(gridContainer);
|
||||
console.log('Emoji grid rendering complete');
|
||||
|
||||
// Add event listeners to category tabs
|
||||
const categoryTabButtons = container.querySelectorAll('.emoji-category-tab');
|
||||
categoryTabButtons.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
// Update active tab
|
||||
categoryTabButtons.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
// Re-render the emoji grid with the selected category
|
||||
const selectedCategory = tab.getAttribute('data-category');
|
||||
console.log('Category selected:', selectedCategory);
|
||||
|
||||
// Determine which emojis to show
|
||||
let emojisToShow = [];
|
||||
if (selectedCategory === 'all') {
|
||||
// For 'all' category, combine all emojis from the categories and deduplicate
|
||||
Object.values(window.emojiLibrary.EMOJIS).forEach(categoryEmojis => {
|
||||
emojisToShow = [...emojisToShow, ...categoryEmojis];
|
||||
});
|
||||
// Remove duplicates using Set
|
||||
emojisToShow = Array.from(new Set(emojisToShow));
|
||||
} else if (window.emojiLibrary.EMOJIS[selectedCategory]) {
|
||||
// For specific category, use emojis from that category
|
||||
emojisToShow = window.emojiLibrary.EMOJIS[selectedCategory];
|
||||
}
|
||||
|
||||
console.log(`Updating grid with ${emojisToShow.length} emojis for category: ${selectedCategory}`);
|
||||
|
||||
// Clear only the grid and rebuild it
|
||||
gridContainer.innerHTML = '';
|
||||
|
||||
// Add emojis to grid
|
||||
emojisToShow.forEach(emoji => {
|
||||
const emojiButton = document.createElement('button');
|
||||
emojiButton.className = 'emoji-button';
|
||||
emojiButton.textContent = emoji;
|
||||
emojiButton.title = 'Click to encode with this emoji';
|
||||
|
||||
emojiButton.addEventListener('click', () => {
|
||||
if (typeof onEmojiSelect === 'function') {
|
||||
onEmojiSelect(emoji);
|
||||
// Add visual feedback when clicked
|
||||
emojiButton.style.backgroundColor = '#e6f7ff';
|
||||
setTimeout(() => {
|
||||
emojiButton.style.backgroundColor = '';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
gridContainer.appendChild(emojiButton);
|
||||
});
|
||||
|
||||
// Update the count display
|
||||
const countDisplay = container.querySelector('.emoji-count');
|
||||
if (countDisplay) {
|
||||
countDisplay.textContent = `${emojisToShow.length} emojis available`;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Debug info - add count display
|
||||
const countDisplay = document.createElement('div');
|
||||
countDisplay.className = 'emoji-count';
|
||||
countDisplay.textContent = `${emojisToShow.length} emojis available`;
|
||||
container.appendChild(countDisplay);
|
||||
};
|
||||
@@ -1,853 +0,0 @@
|
||||
// Emoji Word Map for Emoji Speak Transform
|
||||
// Maps emojis to arrays of related keywords
|
||||
// When a word is typed, a random emoji from matching entries is returned
|
||||
|
||||
window.emojiKeywords = {
|
||||
// Emotions & Feelings - Happy
|
||||
'😊': ['happy', 'smile', 'pleased', 'content', 'glad'],
|
||||
'😁': ['grin', 'smile', 'happy', 'excited', 'beaming', 'haha', 'ha', 'hehe'],
|
||||
'😂': ['laugh', 'lol', 'crying', 'tears', 'funny', 'hilarious', 'haha', 'ha', 'hehe'],
|
||||
'🤣': ['laugh', 'rofl', 'lol', 'rolling', 'hilarious', 'haha', 'ha', 'hehe'],
|
||||
'😄': ['happy', 'smile', 'joy', 'cheerful', 'haha', 'ha', 'hehe'],
|
||||
'😃': ['happy', 'smile', 'excited', 'haha', 'ha', 'hehe'],
|
||||
'🤩': ['excited', 'starstruck', 'wow', 'amazing'],
|
||||
'😍': ['love', 'heart', 'adore', 'crush'],
|
||||
'🥰': ['love', 'hearts', 'affection', 'sweet'],
|
||||
'😘': ['kiss', 'love', 'smooch', 'mwah'],
|
||||
'😉': ['wink', 'flirt', 'playful'],
|
||||
|
||||
// Emotions - Sad & Negative
|
||||
'😢': ['sad', 'cry', 'tear', 'upset'],
|
||||
'😭': ['cry', 'sobbing', 'bawling', 'sad', 'tears'],
|
||||
'😔': ['sad', 'pensive', 'down', 'disappointed'],
|
||||
'😞': ['sad', 'disappointed', 'upset', 'lose'],
|
||||
'😟': ['worried', 'anxious', 'concerned'],
|
||||
'😕': ['confused', 'uncertain', 'puzzled'],
|
||||
'🤔': ['think', 'thinking', 'hmm', 'wonder', 'ponder'],
|
||||
'😐': ['neutral', 'meh', 'blank', 'expressionless'],
|
||||
|
||||
// Emotions - Angry
|
||||
'😡': ['angry', 'mad', 'furious', 'rage', 'pissed'],
|
||||
'😠': ['angry', 'mad', 'annoyed', 'grumpy'],
|
||||
'🤬': ['angry', 'cursing', 'swearing', 'rage'],
|
||||
|
||||
// Emotions - Surprised & Shocked
|
||||
'😮': ['wow', 'surprised', 'shocked', 'amazed'],
|
||||
'😲': ['shocked', 'surprised', 'astonished'],
|
||||
'😱': ['scared', 'shock', 'screaming', 'fear', 'terrified'],
|
||||
'😨': ['scared', 'fearful', 'afraid', 'anxious'],
|
||||
|
||||
// Emotions - Other
|
||||
'😎': ['cool', 'sunglasses', 'smooth', 'slick'],
|
||||
'😴': ['sleep', 'sleeping', 'tired', 'sleepy', 'zzz'],
|
||||
'🥱': ['tired', 'sleepy', 'bored', 'yawn'],
|
||||
'😰': ['nervous', 'anxious', 'sweat', 'worried'],
|
||||
'😅': ['sweat', 'relief', 'phew', 'nervous'],
|
||||
'🤢': ['sick', 'nauseous', 'ill', 'gross'],
|
||||
'🤮': ['sick', 'vomit', 'puke', 'ill'],
|
||||
'😇': ['angel', 'innocent', 'halo', 'saint'],
|
||||
'😈': ['devil', 'evil', 'mischief', 'naughty'],
|
||||
'💀': ['dead', 'skull', 'death', 'dying'],
|
||||
'👻': ['ghost', 'boo', 'spooky', 'phantom'],
|
||||
'🎉': ['party', 'celebrate', 'celebration', 'hooray', 'festive'],
|
||||
'🎊': ['party', 'celebrate', 'confetti', 'celebration'],
|
||||
|
||||
// Animals - Pets & Common
|
||||
'🐕': ['dog', 'puppy', 'pet', 'canine', 'pup'],
|
||||
'🐶': ['dog', 'puppy', 'doggy', 'pet', 'pup'],
|
||||
'🦮': ['dog', 'guide', 'service'],
|
||||
'🐕🦺': ['dog', 'service'],
|
||||
'🐩': ['dog', 'poodle', 'puppy'],
|
||||
'🐱': ['cat', 'kitty', 'kitten', 'pet', 'feline'],
|
||||
'🐈': ['cat', 'kitty', 'feline', 'pet'],
|
||||
'🐈⬛': ['cat', 'black'],
|
||||
'🐭': ['mouse', 'mice', 'rodent'],
|
||||
'🐹': ['hamster', 'pet', 'rodent'],
|
||||
'🐰': ['rabbit', 'bunny', 'easter', 'hare'],
|
||||
'🐇': ['rabbit', 'bunny', 'hare'],
|
||||
|
||||
// Animals - Wild
|
||||
'🦊': ['fox', 'foxy', 'sly'],
|
||||
'🐻': ['bear', 'teddy'],
|
||||
'🐼': ['panda', 'bear'],
|
||||
'🐨': ['koala', 'bear', 'australian'],
|
||||
'🐯': ['tiger', 'fierce', 'striped'],
|
||||
'🦁': ['lion', 'king', 'mane', 'roar'],
|
||||
'🐮': ['cow', 'cattle', 'moo'],
|
||||
'🐷': ['pig', 'piggy', 'oink', 'swine'],
|
||||
'🐸': ['frog', 'toad', 'ribbit'],
|
||||
'🐵': ['monkey', 'primate', 'ape'],
|
||||
'🐒': ['monkey', 'primate'],
|
||||
'🦍': ['gorilla', 'ape', 'kong'],
|
||||
'🦧': ['orangutan', 'ape'],
|
||||
'🐺': ['wolf', 'howl', 'pack'],
|
||||
'🦝': ['raccoon', 'trash'],
|
||||
'🐴': ['horse', 'pony', 'stallion', 'mare'],
|
||||
'🦄': ['unicorn', 'magical', 'fantasy', 'rainbow'],
|
||||
'🦓': ['zebra', 'striped', 'stripes'],
|
||||
'🦌': ['deer', 'reindeer', 'stag', 'doe'],
|
||||
'🐘': ['elephant', 'trunk', 'big', 'large'],
|
||||
'🦏': ['rhino', 'rhinoceros', 'horn'],
|
||||
'🦛': ['hippo', 'hippopotamus'],
|
||||
'🦒': ['giraffe', 'tall', 'neck'],
|
||||
|
||||
// Animals - Birds
|
||||
'🐔': ['chicken', 'rooster', 'hen', 'poultry'],
|
||||
'🐓': ['rooster', 'chicken', 'cock'],
|
||||
'🐣': ['chick', 'baby', 'hatching', 'bird'],
|
||||
'🐤': ['chick', 'baby', 'bird'],
|
||||
'🐥': ['chick', 'duckling', 'baby', 'bird'],
|
||||
'🐦': ['bird', 'birdie', 'tweet'],
|
||||
'🐧': ['penguin', 'antarctic', 'bird'],
|
||||
'🦆': ['duck', 'quack', 'waterfowl'],
|
||||
'🦅': ['eagle', 'bird', 'freedom', 'america'],
|
||||
'🦉': ['owl', 'wise', 'hoot', 'night'],
|
||||
'🦇': ['bat', 'vampire', 'night', 'flying'],
|
||||
'🦜': ['parrot', 'bird', 'tropical', 'colorful'],
|
||||
'🦚': ['peacock', 'bird', 'fancy', 'colorful'],
|
||||
|
||||
// Animals - Insects & Small
|
||||
'🐝': ['bee', 'buzz', 'honey', 'bumblebee'],
|
||||
'🐛': ['bug', 'caterpillar', 'worm', 'insect'],
|
||||
'🦋': ['butterfly', 'beautiful', 'insect', 'flying'],
|
||||
'🐌': ['snail', 'slow', 'shell'],
|
||||
'🐞': ['ladybug', 'bug', 'insect', 'beetle'],
|
||||
'🐜': ['ant', 'insect', 'small', 'tiny'],
|
||||
'🕷️': ['spider', 'web', 'arachnid', 'creepy'],
|
||||
'🦂': ['scorpion', 'sting', 'desert'],
|
||||
'🦟': ['mosquito', 'bug', 'bite', 'annoying'],
|
||||
|
||||
// Animals - Marine
|
||||
'🐍': ['snake', 'serpent', 'slither', 'reptile'],
|
||||
'🦎': ['lizard', 'reptile', 'gecko'],
|
||||
'🐊': ['alligator', 'crocodile', 'reptile'],
|
||||
'🐢': ['turtle', 'tortoise', 'slow', 'shell'],
|
||||
'🐉': ['dragon', 'fantasy', 'mythical', 'fire'],
|
||||
'🐲': ['dragon', 'fantasy', 'mythical'],
|
||||
'🐟': ['fish', 'seafood', 'swimming'],
|
||||
'🐠': ['fish', 'tropical', 'colorful'],
|
||||
'🐡': ['fish', 'puffer', 'blowfish'],
|
||||
'🦈': ['shark', 'jaws', 'ocean', 'dangerous'],
|
||||
'🐋': ['whale', 'ocean', 'big', 'huge'],
|
||||
'🐬': ['dolphin', 'ocean', 'smart', 'friendly'],
|
||||
'🐙': ['octopus', 'tentacles', 'ocean', 'squid'],
|
||||
'🦑': ['squid', 'octopus', 'ocean', 'tentacles'],
|
||||
'🦀': ['crab', 'ocean', 'seafood', 'crustacean'],
|
||||
'🦞': ['lobster', 'seafood', 'ocean', 'crustacean'],
|
||||
'🦐': ['shrimp', 'prawn', 'seafood', 'ocean'],
|
||||
'🦪': ['oyster', 'seafood', 'pearl', 'shell'],
|
||||
|
||||
// Food - Fast Food & Main
|
||||
'🍕': ['pizza', 'slice', 'cheese', 'pepperoni', 'italian'],
|
||||
'🍔': ['burger', 'hamburger', 'cheeseburger', 'food'],
|
||||
'🍟': ['fries', 'chips', 'potato', 'french'],
|
||||
'🌭': ['hotdog', 'dog', 'sausage', 'frank'],
|
||||
'🌮': ['taco', 'mexican', 'shell'],
|
||||
'🌯': ['burrito', 'mexican', 'wrap'],
|
||||
'🥙': ['wrap', 'pita', 'gyro', 'sandwich'],
|
||||
'🥪': ['sandwich', 'sub', 'lunch'],
|
||||
'🥗': ['salad', 'healthy', 'greens', 'vegetables'],
|
||||
'🍝': ['pasta', 'spaghetti', 'italian', 'noodles'],
|
||||
'🍜': ['ramen', 'noodles', 'soup', 'asian'],
|
||||
'🍲': ['stew', 'soup', 'pot', 'food'],
|
||||
'🍛': ['curry', 'rice', 'indian', 'spicy'],
|
||||
'🍣': ['sushi', 'japanese', 'fish', 'raw'],
|
||||
'🍱': ['bento', 'lunch', 'japanese', 'box'],
|
||||
'🥘': ['paella', 'food', 'dish', 'pan'],
|
||||
|
||||
// Food - Meat & Protein
|
||||
'🍖': ['meat', 'bone', 'food', 'leg'],
|
||||
'🍗': ['chicken', 'drumstick', 'meat', 'poultry'],
|
||||
'🥩': ['steak', 'meat', 'beef', 'red'],
|
||||
'🥓': ['bacon', 'meat', 'breakfast', 'pork'],
|
||||
'🥚': ['egg', 'breakfast', 'protein'],
|
||||
'🍳': ['cooking', 'egg', 'frying', 'breakfast'],
|
||||
|
||||
// Food - Bread & Baked
|
||||
'🍞': ['bread', 'loaf', 'toast', 'baked'],
|
||||
'🥐': ['croissant', 'bread', 'french', 'pastry'],
|
||||
'🥖': ['baguette', 'bread', 'french'],
|
||||
'🥨': ['pretzel', 'snack', 'twisted'],
|
||||
'🧀': ['cheese', 'dairy', 'yellow'],
|
||||
|
||||
// Food - Fruits
|
||||
'🍎': ['apple', 'fruit', 'red', 'healthy'],
|
||||
'🍏': ['apple', 'fruit', 'green', 'granny'],
|
||||
'🍊': ['orange', 'fruit', 'citrus', 'vitamin'],
|
||||
'🍋': ['lemon', 'citrus', 'sour', 'yellow'],
|
||||
'🍌': ['banana', 'fruit', 'yellow', 'potassium'],
|
||||
'🍉': ['watermelon', 'fruit', 'summer', 'juicy'],
|
||||
'🍇': ['grapes', 'fruit', 'wine', 'purple'],
|
||||
'🍓': ['strawberry', 'berry', 'fruit', 'red'],
|
||||
'🫐': ['blueberry', 'berry', 'fruit', 'blue'],
|
||||
'🍈': ['melon', 'fruit', 'cantaloupe'],
|
||||
'🍒': ['cherry', 'fruit', 'red', 'cherries'],
|
||||
'🍑': ['peach', 'fruit', 'fuzzy', 'juicy'],
|
||||
'🥭': ['mango', 'fruit', 'tropical', 'juicy'],
|
||||
'🍍': ['pineapple', 'fruit', 'tropical', 'spiky'],
|
||||
'🥥': ['coconut', 'tropical', 'palm', 'fruit'],
|
||||
'🥝': ['kiwi', 'fruit', 'green', 'fuzzy'],
|
||||
'🥑': ['avocado', 'fruit', 'green', 'healthy', 'guac'],
|
||||
|
||||
// Food - Vegetables
|
||||
'🍅': ['tomato', 'vegetable', 'red', 'fruit'],
|
||||
'🥔': ['potato', 'vegetable', 'spud', 'tater'],
|
||||
'🥕': ['carrot', 'vegetable', 'orange', 'healthy'],
|
||||
'🌽': ['corn', 'vegetable', 'yellow', 'maize'],
|
||||
'🌶️': ['pepper', 'chili', 'hot', 'spicy', 'jalapeno'],
|
||||
'🫑': ['pepper', 'bell', 'vegetable', 'capsicum'],
|
||||
'🥒': ['cucumber', 'vegetable', 'green', 'pickle'],
|
||||
'🥬': ['lettuce', 'vegetable', 'green', 'leafy', 'cabbage'],
|
||||
'🥦': ['broccoli', 'vegetable', 'green', 'healthy'],
|
||||
'🧄': ['garlic', 'vegetable', 'flavor', 'bulb'],
|
||||
'🧅': ['onion', 'vegetable', 'layers', 'cry'],
|
||||
'🍄': ['mushroom', 'fungus', 'shroom', 'toadstool'],
|
||||
|
||||
// Food - Desserts & Sweets
|
||||
'🍰': ['cake', 'dessert', 'birthday', 'sweet', 'slice'],
|
||||
'🎂': ['cake', 'birthday', 'celebration', 'candles'],
|
||||
'🧁': ['cupcake', 'cake', 'dessert', 'sweet'],
|
||||
'🥧': ['pie', 'dessert', 'baked', 'slice'],
|
||||
'🍪': ['cookie', 'biscuit', 'dessert', 'sweet', 'chocolate'],
|
||||
'🍩': ['donut', 'doughnut', 'dessert', 'sweet', 'fried'],
|
||||
'🍫': ['chocolate', 'candy', 'sweet', 'cocoa', 'bar'],
|
||||
'🍬': ['candy', 'sweet', 'sugar', 'wrapper'],
|
||||
'🍭': ['lollipop', 'candy', 'sweet', 'sucker'],
|
||||
'🍮': ['custard', 'pudding', 'dessert', 'sweet', 'flan'],
|
||||
'🍯': ['honey', 'sweet', 'bee', 'golden'],
|
||||
'🍦': ['icecream', 'ice', 'cream', 'dessert', 'cold', 'cone'],
|
||||
'🍧': ['shaved', 'ice', 'dessert', 'cold', 'snow'],
|
||||
'🍨': ['icecream', 'ice', 'cream', 'dessert', 'bowl'],
|
||||
|
||||
// Drinks
|
||||
'☕': ['coffee', 'cafe', 'espresso', 'latte', 'hot', 'java'],
|
||||
'🍵': ['tea', 'green', 'hot', 'cup', 'matcha'],
|
||||
'🧃': ['juice', 'box', 'drink', 'kid'],
|
||||
'🥤': ['soda', 'pop', 'drink', 'cup', 'straw'],
|
||||
'🧋': ['bubble', 'tea', 'boba', 'drink', 'tapioca'],
|
||||
'🥛': ['milk', 'dairy', 'drink', 'white'],
|
||||
'🍼': ['bottle', 'baby', 'milk', 'feeding'],
|
||||
'🍺': ['beer', 'ale', 'alcohol', 'drink', 'brew', 'cheers'],
|
||||
'🍻': ['beer', 'cheers', 'drinks', 'celebration', 'toast'],
|
||||
'🍷': ['wine', 'alcohol', 'drink', 'red', 'glass'],
|
||||
'🥂': ['champagne', 'celebrate', 'toast', 'cheers', 'sparkling'],
|
||||
'🍸': ['cocktail', 'martini', 'drink', 'alcohol'],
|
||||
'🍹': ['cocktail', 'tropical', 'drink', 'vacation'],
|
||||
'🧊': ['ice', 'cold', 'cube', 'frozen'],
|
||||
'💧': ['water', 'drop', 'liquid', 'droplet'],
|
||||
|
||||
// Body & Gestures
|
||||
'👋': ['wave', 'hello', 'hi', 'bye', 'hand'],
|
||||
'🤚': ['hand', 'raised', 'stop', 'palm'],
|
||||
'🖐️': ['hand', 'fingers', 'five', 'palm'],
|
||||
'✋': ['hand', 'stop', 'raised', 'palm'],
|
||||
'🖖': ['vulcan', 'spock', 'hand', 'star', 'trek'],
|
||||
'👌': ['ok', 'okay', 'good', 'perfect', 'fine'],
|
||||
'🤌': ['fingers', 'italian', 'pinch', 'hand'],
|
||||
'🤏': ['pinch', 'small', 'tiny', 'little'],
|
||||
'✌️': ['peace', 'victory', 'two', 'fingers'],
|
||||
'🤞': ['fingers', 'crossed', 'luck', 'hope'],
|
||||
'🤟': ['love', 'sign', 'rock', 'hand'],
|
||||
'🤘': ['rock', 'metal', 'horns', 'devil'],
|
||||
'🤙': ['call', 'phone', 'hang', 'shaka'],
|
||||
'👈': ['point', 'left', 'finger', 'direction'],
|
||||
'👉': ['point', 'right', 'finger', 'direction'],
|
||||
'👆': ['point', 'up', 'finger', 'direction'],
|
||||
'👇': ['point', 'down', 'finger', 'direction'],
|
||||
'☝️': ['point', 'up', 'one', 'finger'],
|
||||
'👍': ['thumbsup', 'good', 'yes', 'like', 'approve', 'up'],
|
||||
'👎': ['thumbsdown', 'bad', 'no', 'dislike', 'disapprove', 'down'],
|
||||
'✊': ['fist', 'power', 'strength', 'solidarity'],
|
||||
'👊': ['fist', 'punch', 'bump', 'fight'],
|
||||
'🤛': ['fist', 'punch', 'left', 'bump'],
|
||||
'🤜': ['fist', 'punch', 'right', 'bump'],
|
||||
'👏': ['clap', 'applause', 'hands', 'bravo', 'praise'],
|
||||
'🙌': ['hands', 'raised', 'celebration', 'praise', 'hooray'],
|
||||
'👐': ['hands', 'open', 'hug'],
|
||||
'🤲': ['hands', 'palms', 'prayer', 'offering'],
|
||||
'🤝': ['handshake', 'deal', 'agreement', 'shake'],
|
||||
'🙏': ['pray', 'prayer', 'please', 'thank', 'namaste', 'hands'],
|
||||
'💪': ['muscle', 'strong', 'strength', 'flex', 'arm', 'power'],
|
||||
'🦵': ['leg', 'kick', 'limb'],
|
||||
'🦶': ['foot', 'feet', 'toe'],
|
||||
'👀': ['eyes', 'looking', 'watching', 'see'],
|
||||
'👁️': ['eye', 'see', 'look', 'watch'],
|
||||
'👃': ['nose', 'smell', 'sniff'],
|
||||
'👂': ['ear', 'hear', 'listen'],
|
||||
'🧠': ['brain', 'smart', 'think', 'intelligent'],
|
||||
'🦴': ['bone', 'skeleton', 'anatomy'],
|
||||
'🦷': ['teeth', 'tooth', 'dental', 'dentist'],
|
||||
'👅': ['tongue', 'taste', 'lick'],
|
||||
'👄': ['mouth', 'lips', 'kiss'],
|
||||
|
||||
// People & Professions
|
||||
'👨': ['man', 'male', 'guy', 'adult'],
|
||||
'👩': ['woman', 'female', 'lady', 'adult'],
|
||||
'👦': ['boy', 'male', 'child', 'kid'],
|
||||
'👧': ['girl', 'female', 'child', 'kid'],
|
||||
'👶': ['baby', 'infant', 'newborn', 'child'],
|
||||
'🧒': ['child', 'kid', 'young'],
|
||||
'👨⚕️': ['doctor', 'physician', 'medical', 'health'],
|
||||
'👩⚕️': ['doctor', 'nurse', 'medical', 'health'],
|
||||
'👨🎓': ['student', 'graduate', 'scholar', 'education'],
|
||||
'👨🏫': ['teacher', 'professor', 'educator', 'instructor'],
|
||||
'👨💻': ['programmer', 'developer', 'coder', 'engineer', 'tech'],
|
||||
'👨🔬': ['scientist', 'researcher', 'lab', 'chemistry'],
|
||||
'👨🎨': ['artist', 'painter', 'creative'],
|
||||
'👨🍳': ['chef', 'cook', 'culinary'],
|
||||
'👨🎤': ['singer', 'musician', 'rockstar', 'performer'],
|
||||
'👨✈️': ['pilot', 'captain', 'aviator', 'flying'],
|
||||
'👨🚀': ['astronaut', 'space', 'cosmonaut'],
|
||||
'👨🚒': ['firefighter', 'fireman', 'rescue'],
|
||||
'👮': ['police', 'cop', 'officer', 'law'],
|
||||
'🕵️': ['detective', 'spy', 'investigator', 'sleuth'],
|
||||
'💂': ['guard', 'soldier', 'sentry'],
|
||||
'🥷': ['ninja', 'stealth', 'martial', 'warrior'],
|
||||
'👷': ['construction', 'worker', 'builder', 'hardhat'],
|
||||
'🤴': ['prince', 'royal', 'king'],
|
||||
'👸': ['princess', 'royal', 'queen'],
|
||||
'👑': ['crown', 'king', 'queen', 'royal', 'royalty'],
|
||||
'🧙': ['wizard', 'magic', 'sorcerer', 'merlin'],
|
||||
'🧚': ['fairy', 'magical', 'wings', 'pixie'],
|
||||
'🧛': ['vampire', 'dracula', 'blood', 'fangs'],
|
||||
'🧜': ['mermaid', 'ocean', 'sea', 'mythical'],
|
||||
'🧝': ['elf', 'fantasy', 'magical', 'pointed'],
|
||||
'🧞': ['genie', 'wish', 'lamp', 'magical'],
|
||||
'🧟': ['zombie', 'undead', 'walking', 'dead'],
|
||||
'🦸': ['superhero', 'hero', 'super', 'powers'],
|
||||
'🦹': ['villain', 'evil', 'bad', 'super'],
|
||||
'🤶': ['mrs', 'claus', 'christmas', 'santa'],
|
||||
'🎅': ['santa', 'christmas', 'claus', 'father'],
|
||||
'👼': ['angel', 'cherub', 'heaven', 'halo'],
|
||||
'💏': ['kiss', 'couple', 'romance', 'love'],
|
||||
'💑': ['couple', 'love', 'romance', 'heart'],
|
||||
'👪': ['family', 'parents', 'kids', 'home'],
|
||||
'🤗': ['hug', 'hugging', 'embrace', 'cuddle'],
|
||||
'🤳': ['selfie', 'photo', 'camera', 'phone'],
|
||||
|
||||
// Activities & Sports
|
||||
'⚽': ['soccer', 'football', 'ball', 'sport'],
|
||||
'🏀': ['basketball', 'ball', 'sport', 'hoops'],
|
||||
'🏈': ['football', 'american', 'ball', 'sport'],
|
||||
'⚾': ['baseball', 'ball', 'sport', 'diamond'],
|
||||
'🎾': ['tennis', 'ball', 'sport', 'racket'],
|
||||
'🏐': ['volleyball', 'ball', 'sport', 'beach'],
|
||||
'🏉': ['rugby', 'football', 'ball', 'sport'],
|
||||
'🎱': ['billiards', 'pool', 'eight', 'ball'],
|
||||
'🏓': ['pingpong', 'tabletennis', 'paddle', 'sport'],
|
||||
'🏸': ['badminton', 'shuttlecock', 'sport'],
|
||||
'🥊': ['boxing', 'glove', 'fight', 'punch'],
|
||||
'🥋': ['martial', 'arts', 'karate', 'judo', 'gi'],
|
||||
'⛳': ['golf', 'hole', 'sport', 'flag'],
|
||||
'🏹': ['archery', 'bow', 'arrow', 'target'],
|
||||
'🎯': ['target', 'bullseye', 'darts', 'aim'],
|
||||
'🏃': ['run', 'running', 'jog', 'exercise', 'sprint'],
|
||||
'🚶': ['walk', 'walking', 'stroll', 'pedestrian'],
|
||||
'💃': ['dance', 'dancing', 'salsa', 'party'],
|
||||
'🕺': ['dance', 'dancing', 'disco', 'party'],
|
||||
'🤸': ['gymnastics', 'flip', 'cartwheel', 'jump'],
|
||||
'🏊': ['swim', 'swimming', 'pool', 'water'],
|
||||
'🏄': ['surf', 'surfing', 'wave', 'beach'],
|
||||
'🚴': ['bike', 'cycling', 'bicycle', 'ride'],
|
||||
'🏋️': ['lift', 'lifting', 'weights', 'gym', 'workout'],
|
||||
'🤼': ['wrestle', 'wrestling', 'fight', 'grapple'],
|
||||
'🤺': ['fencing', 'sword', 'duel', 'sport'],
|
||||
'⛷️': ['ski', 'skiing', 'snow', 'sport'],
|
||||
'🏂': ['snowboard', 'snow', 'sport', 'winter'],
|
||||
'🧗': ['climb', 'climbing', 'rock', 'mountain'],
|
||||
'🧘': ['yoga', 'meditate', 'meditation', 'zen', 'peace', 'calm'],
|
||||
'🎮': ['game', 'gaming', 'videogame', 'play', 'controller'],
|
||||
'🎲': ['dice', 'game', 'roll', 'random'],
|
||||
'♠️': ['spade', 'card', 'suit', 'black'],
|
||||
'♥️': ['heart', 'card', 'suit', 'love', 'red'],
|
||||
'♦️': ['diamond', 'card', 'suit', 'red'],
|
||||
'♣️': ['club', 'card', 'suit', 'black'],
|
||||
'🎨': ['art', 'paint', 'painting', 'artist', 'creative', 'palette'],
|
||||
'🎭': ['theater', 'drama', 'masks', 'acting', 'performance'],
|
||||
'🎪': ['circus', 'tent', 'carnival', 'show'],
|
||||
'🎬': ['movie', 'film', 'cinema', 'action', 'clapper'],
|
||||
'🎤': ['microphone', 'sing', 'singing', 'karaoke', 'mic'],
|
||||
'🎧': ['headphones', 'music', 'audio', 'listen'],
|
||||
'🎵': ['music', 'note', 'musical', 'song'],
|
||||
'🎶': ['music', 'notes', 'musical', 'song', 'melody'],
|
||||
'🎸': ['guitar', 'rock', 'music', 'instrument'],
|
||||
'🎹': ['piano', 'keyboard', 'music', 'instrument'],
|
||||
'🎺': ['trumpet', 'music', 'instrument', 'brass'],
|
||||
'🎻': ['violin', 'music', 'instrument', 'strings'],
|
||||
'🥁': ['drum', 'drums', 'music', 'instrument'],
|
||||
'📚': ['books', 'library', 'study', 'read', 'reading', 'education'],
|
||||
'📖': ['book', 'read', 'reading', 'novel', 'open'],
|
||||
'✍️': ['write', 'writing', 'pen', 'author'],
|
||||
'📝': ['note', 'memo', 'write', 'paper'],
|
||||
|
||||
// Objects & Technology
|
||||
'📱': ['phone', 'mobile', 'cell', 'smartphone', 'iphone'],
|
||||
'☎️': ['telephone', 'phone', 'call', 'landline'],
|
||||
'📞': ['telephone', 'phone', 'receiver', 'call'],
|
||||
'💻': ['computer', 'laptop', 'pc', 'mac', 'work'],
|
||||
'⌨️': ['keyboard', 'typing', 'computer', 'keys'],
|
||||
'🖱️': ['mouse', 'computer', 'click', 'pointer'],
|
||||
'🖥️': ['computer', 'desktop', 'monitor', 'screen'],
|
||||
'🖨️': ['printer', 'print', 'copy', 'office'],
|
||||
'📷': ['camera', 'photo', 'picture', 'photography'],
|
||||
'📸': ['camera', 'photo', 'flash', 'picture'],
|
||||
'📹': ['video', 'camera', 'recording', 'film'],
|
||||
'🎥': ['movie', 'camera', 'film', 'cinema'],
|
||||
'📺': ['tv', 'television', 'screen', 'watch'],
|
||||
'📻': ['radio', 'music', 'broadcast', 'fm'],
|
||||
'⏰': ['alarm', 'clock', 'time', 'wake'],
|
||||
'⏱️': ['stopwatch', 'timer', 'time', 'clock'],
|
||||
'⏲️': ['timer', 'clock', 'countdown'],
|
||||
'🕐': ['clock', 'one', 'time', 'hour'],
|
||||
'⌚': ['watch', 'time', 'clock', 'wrist'],
|
||||
'📡': ['satellite', 'antenna', 'dish', 'signal'],
|
||||
'🛰️': ['satellite', 'space', 'orbit', 'gps'],
|
||||
'🔋': ['battery', 'power', 'energy', 'charge'],
|
||||
'🔌': ['plug', 'electric', 'power', 'outlet'],
|
||||
'💡': ['lightbulb', 'light', 'idea', 'bright', 'bulb'],
|
||||
'🔦': ['flashlight', 'torch', 'light', 'beam'],
|
||||
'🕯️': ['candle', 'light', 'flame', 'wax'],
|
||||
|
||||
// Tools & Weapons
|
||||
'🔨': ['hammer', 'tool', 'nail', 'build'],
|
||||
'🪛': ['screwdriver', 'tool', 'screw', 'fix'],
|
||||
'🔧': ['wrench', 'tool', 'mechanic', 'spanner'],
|
||||
'🪚': ['saw', 'tool', 'cut', 'wood'],
|
||||
'⚙️': ['gear', 'cog', 'settings', 'mechanical'],
|
||||
'🔩': ['bolt', 'nut', 'screw', 'fastener'],
|
||||
'🪓': ['axe', 'chop', 'wood', 'tool'],
|
||||
'⚒️': ['hammer', 'pick', 'tool', 'mine'],
|
||||
'🛠️': ['tools', 'hammer', 'wrench', 'repair'],
|
||||
'🗡️': ['sword', 'blade', 'weapon', 'dagger'],
|
||||
'⚔️': ['swords', 'crossed', 'battle', 'fight', 'weapon'],
|
||||
'🔪': ['knife', 'blade', 'cut', 'sharp'],
|
||||
'🏹': ['bow', 'arrow', 'weapon', 'archery'],
|
||||
'🛡️': ['shield', 'protection', 'defend', 'guard'],
|
||||
'💣': ['bomb', 'explosive', 'danger', 'blast'],
|
||||
'🔫': ['gun', 'pistol', 'weapon', 'shoot'],
|
||||
|
||||
// Money & Value
|
||||
'💰': ['money', 'bag', 'cash', 'rich', 'dollar', 'wealth'],
|
||||
'💵': ['dollar', 'money', 'bill', 'cash', 'hundred'],
|
||||
'💴': ['yen', 'money', 'japan', 'currency'],
|
||||
'💶': ['euro', 'money', 'europe', 'currency'],
|
||||
'💷': ['pound', 'money', 'british', 'currency'],
|
||||
'💸': ['money', 'flying', 'cash', 'spend', 'expense'],
|
||||
'💳': ['card', 'credit', 'debit', 'payment'],
|
||||
'💎': ['diamond', 'gem', 'jewel', 'precious', 'valuable'],
|
||||
'👑': ['crown', 'royal', 'king', 'queen'],
|
||||
'💍': ['ring', 'diamond', 'wedding', 'engagement', 'jewelry'],
|
||||
'🏆': ['trophy', 'award', 'win', 'champion', 'prize', 'first'],
|
||||
'🥇': ['gold', 'medal', 'first', 'win', 'champion'],
|
||||
'🥈': ['silver', 'medal', 'second', 'place'],
|
||||
'🥉': ['bronze', 'medal', 'third', 'place'],
|
||||
'🏅': ['medal', 'award', 'gold', 'achievement'],
|
||||
|
||||
// Office & School
|
||||
'✏️': ['pencil', 'write', 'draw', 'school'],
|
||||
'✒️': ['pen', 'write', 'ink', 'fountain'],
|
||||
'🖊️': ['pen', 'write', 'ballpoint'],
|
||||
'🖋️': ['pen', 'fountain', 'write', 'ink'],
|
||||
'📏': ['ruler', 'measure', 'straight', 'school'],
|
||||
'📐': ['triangle', 'ruler', 'geometry', 'school'],
|
||||
'✂️': ['scissors', 'cut', 'snip', 'craft'],
|
||||
'📌': ['pin', 'pushpin', 'tack', 'attach'],
|
||||
'📍': ['pin', 'location', 'map', 'place'],
|
||||
'🖇️': ['paperclip', 'clip', 'attach', 'office'],
|
||||
'📎': ['paperclip', 'clip', 'attach', 'office'],
|
||||
'📄': ['paper', 'document', 'page', 'file'],
|
||||
'📃': ['paper', 'page', 'curl', 'document'],
|
||||
'📋': ['clipboard', 'paper', 'document', 'list'],
|
||||
'📁': ['folder', 'file', 'directory', 'organize'],
|
||||
'📂': ['folder', 'open', 'file', 'directory'],
|
||||
'🗂️': ['dividers', 'index', 'tabs', 'organize'],
|
||||
'📰': ['newspaper', 'news', 'article', 'press'],
|
||||
'📜': ['scroll', 'paper', 'ancient', 'document'],
|
||||
'📦': ['box', 'package', 'parcel', 'shipping', 'delivery'],
|
||||
'✉️': ['envelope', 'mail', 'letter', 'message'],
|
||||
'📧': ['email', 'mail', 'message', 'inbox'],
|
||||
'📮': ['mailbox', 'post', 'mail', 'letter'],
|
||||
'🎁': ['gift', 'present', 'box', 'wrapped', 'surprise'],
|
||||
'🎀': ['ribbon', 'bow', 'gift', 'pretty'],
|
||||
'🎈': ['balloon', 'party', 'celebration', 'float'],
|
||||
|
||||
// Transportation - Cars
|
||||
'🚗': ['car', 'auto', 'vehicle', 'drive', 'automobile'],
|
||||
'🚕': ['taxi', 'cab', 'ride', 'yellow'],
|
||||
'🚙': ['suv', 'car', 'vehicle', 'truck'],
|
||||
'🏎️': ['racecar', 'fast', 'racing', 'formula'],
|
||||
'🚓': ['police', 'cop', 'car', 'patrol'],
|
||||
'🚑': ['ambulance', 'emergency', 'medical', 'hospital'],
|
||||
'🚒': ['firetruck', 'fire', 'emergency', 'truck'],
|
||||
'🚚': ['truck', 'delivery', 'moving', 'lorry'],
|
||||
'🚛': ['truck', 'semi', 'lorry', 'trailer'],
|
||||
'🚐': ['van', 'minibus', 'vehicle'],
|
||||
'🚌': ['bus', 'coach', 'transit', 'public'],
|
||||
|
||||
// Transportation - Other
|
||||
'🚲': ['bike', 'bicycle', 'cycle', 'pedal'],
|
||||
'🏍️': ['motorcycle', 'bike', 'motorbike', 'chopper'],
|
||||
'🛵': ['scooter', 'moped', 'vespa'],
|
||||
'🛴': ['scooter', 'kick', 'ride'],
|
||||
'✈️': ['airplane', 'plane', 'flight', 'fly', 'jet'],
|
||||
'🛩️': ['plane', 'small', 'aircraft'],
|
||||
'🚁': ['helicopter', 'chopper', 'heli', 'copter'],
|
||||
'🚂': ['train', 'locomotive', 'steam', 'railway'],
|
||||
'🚃': ['train', 'railway', 'car', 'tram'],
|
||||
'🚄': ['train', 'highspeed', 'bullet', 'fast'],
|
||||
'🚅': ['train', 'bullet', 'fast', 'shinkansen'],
|
||||
'🚆': ['train', 'railway', 'metro'],
|
||||
'🚇': ['subway', 'metro', 'underground', 'tube'],
|
||||
'🚈': ['train', 'light', 'rail', 'tram'],
|
||||
'🚉': ['station', 'train', 'railway', 'metro'],
|
||||
'🚊': ['tram', 'trolley', 'streetcar'],
|
||||
'🚝': ['monorail', 'train', 'elevated'],
|
||||
'⛵': ['sailboat', 'boat', 'sail', 'yacht'],
|
||||
'🚤': ['speedboat', 'boat', 'fast', 'motor'],
|
||||
'🛥️': ['boat', 'motor', 'yacht', 'ship'],
|
||||
'🛳️': ['ship', 'cruise', 'passenger', 'boat'],
|
||||
'⛴️': ['ferry', 'boat', 'ship', 'passenger'],
|
||||
'🚢': ['ship', 'boat', 'cruise', 'vessel'],
|
||||
'⚓': ['anchor', 'ship', 'boat', 'navy'],
|
||||
'🚀': ['rocket', 'space', 'launch', 'nasa', 'shuttle'],
|
||||
'🛸': ['ufo', 'alien', 'flying', 'saucer', 'spaceship'],
|
||||
|
||||
// Nature & Weather
|
||||
'☀️': ['sun', 'sunny', 'bright', 'day', 'sunshine'],
|
||||
'🌞': ['sun', 'face', 'sunny', 'bright'],
|
||||
'⭐': ['star', 'bright', 'shine', 'sparkle'],
|
||||
'🌟': ['star', 'glowing', 'shine', 'sparkle', 'shiny'],
|
||||
'✨': ['sparkles', 'stars', 'shine', 'magic', 'magical', 'twinkle'],
|
||||
'💫': ['dizzy', 'star', 'sparkle', 'shine'],
|
||||
'🌙': ['moon', 'crescent', 'night', 'lunar'],
|
||||
'🌚': ['moon', 'new', 'dark', 'face'],
|
||||
'🌛': ['moon', 'quarter', 'face'],
|
||||
'🌜': ['moon', 'quarter', 'face'],
|
||||
'🌝': ['moon', 'full', 'face'],
|
||||
'☁️': ['cloud', 'cloudy', 'weather', 'sky'],
|
||||
'⛅': ['cloud', 'sun', 'partly', 'weather'],
|
||||
'⛈️': ['storm', 'thunder', 'lightning', 'cloud', 'weather'],
|
||||
'🌤️': ['sun', 'cloud', 'partly', 'weather'],
|
||||
'🌥️': ['cloud', 'sun', 'behind', 'weather'],
|
||||
'🌦️': ['sun', 'rain', 'weather', 'cloud'],
|
||||
'🌧️': ['rain', 'rainy', 'weather', 'cloud', 'wet'],
|
||||
'🌨️': ['snow', 'snowing', 'weather', 'cloud', 'cold'],
|
||||
'🌩️': ['cloud', 'lightning', 'storm', 'weather'],
|
||||
'🌪️': ['tornado', 'cyclone', 'twister', 'wind', 'storm'],
|
||||
'🌫️': ['fog', 'foggy', 'misty', 'weather'],
|
||||
'🌬️': ['wind', 'blow', 'windy', 'weather'],
|
||||
'💨': ['wind', 'blow', 'dash', 'fast', 'air'],
|
||||
'🌀': ['cyclone', 'hurricane', 'typhoon', 'spiral'],
|
||||
'🌈': ['rainbow', 'colorful', 'colors', 'pride', 'weather'],
|
||||
'☂️': ['umbrella', 'rain', 'protect', 'weather'],
|
||||
'⛱️': ['umbrella', 'beach', 'sun', 'shade'],
|
||||
'⚡': ['lightning', 'bolt', 'electric', 'fast', 'zap', 'thunder'],
|
||||
'❄️': ['snowflake', 'snow', 'cold', 'winter', 'frozen'],
|
||||
'☃️': ['snowman', 'snow', 'winter', 'cold'],
|
||||
'⛄': ['snowman', 'snow', 'winter', 'frosty'],
|
||||
'☄️': ['comet', 'meteor', 'space', 'shooting'],
|
||||
'🔥': ['fire', 'flame', 'hot', 'burn', 'lit'],
|
||||
'💧': ['water', 'drop', 'wet', 'liquid'],
|
||||
'🌊': ['water', 'ocean', 'wave', 'sea', 'beach', 'surf'],
|
||||
|
||||
// Plants & Nature
|
||||
'🌲': ['tree', 'evergreen', 'pine', 'forest'],
|
||||
'🌳': ['tree', 'deciduous', 'forest', 'nature'],
|
||||
'🌴': ['palm', 'tree', 'tropical', 'beach'],
|
||||
'🌵': ['cactus', 'desert', 'prickly', 'arizona'],
|
||||
'🌾': ['grain', 'wheat', 'rice', 'farm'],
|
||||
'🌿': ['herb', 'leaf', 'plant', 'green'],
|
||||
'☘️': ['shamrock', 'clover', 'irish', 'luck', 'green'],
|
||||
'🍀': ['clover', 'fourleaf', 'luck', 'lucky', 'irish'],
|
||||
'🍁': ['leaf', 'maple', 'autumn', 'fall', 'canada'],
|
||||
'🍂': ['leaves', 'leaf', 'autumn', 'fall'],
|
||||
'🍃': ['leaves', 'leaf', 'blow', 'wind'],
|
||||
'🌱': ['plant', 'seedling', 'sprout', 'grow', 'new'],
|
||||
'🌷': ['tulip', 'flower', 'spring', 'pretty'],
|
||||
'🌸': ['flower', 'cherry', 'blossom', 'pink', 'spring'],
|
||||
'🌹': ['rose', 'flower', 'red', 'love', 'romantic'],
|
||||
'🥀': ['wilted', 'flower', 'rose', 'dead', 'sad'],
|
||||
'🌺': ['hibiscus', 'flower', 'tropical', 'colorful'],
|
||||
'🌻': ['sunflower', 'flower', 'yellow', 'summer'],
|
||||
'🌼': ['blossom', 'flower', 'daisy', 'spring'],
|
||||
'🌽': ['corn', 'maize', 'vegetable', 'farm'],
|
||||
'🍄': ['mushroom', 'fungus', 'toadstool', 'shroom'],
|
||||
|
||||
// Earth & Geography
|
||||
'🌍': ['earth', 'globe', 'world', 'europe', 'africa', 'planet'],
|
||||
'🌎': ['earth', 'globe', 'world', 'americas', 'planet'],
|
||||
'🌏': ['earth', 'globe', 'world', 'asia', 'australia', 'planet'],
|
||||
'🌐': ['globe', 'world', 'internet', 'www', 'web'],
|
||||
'🗺️': ['map', 'world', 'geography', 'navigation'],
|
||||
'🧭': ['compass', 'direction', 'navigation', 'north'],
|
||||
'⛰️': ['mountain', 'peak', 'high', 'climb'],
|
||||
'🏔️': ['mountain', 'snow', 'peak', 'alps'],
|
||||
'🗻': ['mountain', 'fuji', 'japan', 'volcano'],
|
||||
'🌋': ['volcano', 'eruption', 'lava', 'mountain'],
|
||||
'🏕️': ['camping', 'camp', 'tent', 'outdoors'],
|
||||
'🏖️': ['beach', 'sand', 'ocean', 'vacation', 'umbrella'],
|
||||
'🏝️': ['island', 'desert', 'tropical', 'beach'],
|
||||
'🏜️': ['desert', 'sand', 'hot', 'dry'],
|
||||
'🏞️': ['park', 'national', 'nature', 'scenic'],
|
||||
'🌄': ['sunrise', 'mountain', 'dawn', 'morning'],
|
||||
'🌅': ['sunrise', 'sunset', 'dusk', 'dawn'],
|
||||
'🌆': ['cityscape', 'city', 'dusk', 'buildings'],
|
||||
'🌇': ['sunset', 'city', 'dusk', 'buildings'],
|
||||
'🌃': ['night', 'stars', 'city', 'buildings'],
|
||||
|
||||
// Buildings
|
||||
'🏠': ['home', 'house', 'building', 'residence'],
|
||||
'🏡': ['house', 'home', 'garden', 'building'],
|
||||
'🏘️': ['houses', 'neighborhood', 'residential', 'homes'],
|
||||
'🏚️': ['house', 'abandoned', 'derelict', 'old'],
|
||||
'🏢': ['building', 'office', 'corporate', 'business'],
|
||||
'🏣': ['post', 'office', 'mail', 'building'],
|
||||
'🏤': ['post', 'office', 'european', 'building'],
|
||||
'🏥': ['hospital', 'medical', 'health', 'doctor', 'building'],
|
||||
'🏦': ['bank', 'money', 'finance', 'building'],
|
||||
'🏨': ['hotel', 'motel', 'lodging', 'building'],
|
||||
'🏩': ['love', 'hotel', 'heart', 'building'],
|
||||
'🏪': ['store', 'shop', 'convenience', 'building'],
|
||||
'🏫': ['school', 'education', 'building', 'learn'],
|
||||
'🏬': ['store', 'department', 'shopping', 'building'],
|
||||
'🏭': ['factory', 'industrial', 'manufacturing', 'building'],
|
||||
'🏯': ['castle', 'japanese', 'building', 'historic'],
|
||||
'🏰': ['castle', 'european', 'building', 'palace', 'fortress'],
|
||||
'⛪': ['church', 'religious', 'christian', 'building'],
|
||||
'🕌': ['mosque', 'islam', 'religious', 'temple', 'building'],
|
||||
'🛕': ['temple', 'hindu', 'religious', 'building'],
|
||||
'🕍': ['synagogue', 'jewish', 'religious', 'building'],
|
||||
'⛩️': ['shrine', 'torii', 'japan', 'religious'],
|
||||
'🗼': ['tower', 'tokyo', 'eiffel', 'tall'],
|
||||
'🗽': ['liberty', 'statue', 'freedom', 'america', 'newyork'],
|
||||
'⛺': ['tent', 'camping', 'outdoors', 'shelter'],
|
||||
|
||||
// Symbols & Concepts
|
||||
'✅': ['check', 'yes', 'correct', 'done', 'tick', 'approve', '✓'],
|
||||
'❌': ['x', 'no', 'wrong', 'cross', 'cancel', 'error', '✗', '✖'],
|
||||
'✔️': ['check', 'tick', 'yes', 'done', 'correct', '✓'],
|
||||
'✖️': ['x', 'multiply', 'cross', 'cancel', '✖', '✗'],
|
||||
'❓': ['question', 'help', 'unknown', 'ask', 'confused', '?'],
|
||||
'❔': ['question', 'mark', 'white', 'ask', '?'],
|
||||
'❗': ['exclamation', 'important', 'warning', 'attention', '!'],
|
||||
'❕': ['exclamation', 'mark', 'white', 'attention', '!'],
|
||||
'⚠️': ['warning', 'caution', 'danger', 'alert', '⚠'],
|
||||
'🚫': ['prohibited', 'forbidden', 'no', 'banned', 'x'],
|
||||
'🛑': ['stop', 'sign', 'halt', 'octagon'],
|
||||
'☠️': ['skull', 'crossbones', 'danger', 'poison', 'death', 'pirate'],
|
||||
'☢️': ['radioactive', 'nuclear', 'danger', 'toxic'],
|
||||
'☣️': ['biohazard', 'danger', 'toxic', 'contamination'],
|
||||
'🆕': ['new', 'fresh', 'latest', 'recent'],
|
||||
'🆓': ['free', 'gratis', 'complimentary'],
|
||||
'🆙': ['up', 'increase', 'arrow', 'level', '↑'],
|
||||
'🆒': ['cool', 'awesome', 'nice'],
|
||||
'🔞': ['eighteen', 'adult', 'mature', 'nsfw'],
|
||||
'💯': ['hundred', 'perfect', 'full', 'complete', 'score', '100%'],
|
||||
'🔴': ['red', 'circle', 'dot'],
|
||||
'🟠': ['orange', 'circle', 'dot'],
|
||||
'🟡': ['yellow', 'circle', 'dot'],
|
||||
'🟢': ['green', 'circle', 'dot'],
|
||||
'🔵': ['blue', 'circle', 'dot'],
|
||||
'🟣': ['purple', 'circle', 'dot'],
|
||||
'🟤': ['brown', 'circle', 'dot'],
|
||||
'⚫': ['black', 'circle', 'dot'],
|
||||
'⚪': ['white', 'circle', 'dot'],
|
||||
|
||||
// Arrows & Directions
|
||||
'⬆️': ['up', 'arrow', 'north', 'direction', 'increase', '↑', '^'],
|
||||
'↗️': ['up', 'right', 'arrow', 'northeast', 'direction', '↗'],
|
||||
'➡️': ['right', 'arrow', 'east', 'direction', 'forward', '→'],
|
||||
'▶️': ['right', 'play', 'forward', 'next'],
|
||||
'↘️': ['down', 'right', 'arrow', 'southeast', 'direction', '↘'],
|
||||
'⬇️': ['down', 'arrow', 'south', 'direction', 'decrease', '↓', 'v'],
|
||||
'↙️': ['down', 'left', 'arrow', 'southwest', 'direction', '↙'],
|
||||
'⬅️': ['left', 'arrow', 'west', 'direction', 'back', '←'],
|
||||
'◀️': ['left', 'play', 'back', 'previous'],
|
||||
'↖️': ['up', 'left', 'arrow', 'northwest', 'direction', '↖'],
|
||||
'↕️': ['up', 'down', 'arrow', 'vertical', '↕'],
|
||||
'↔️': ['left', 'right', 'arrow', 'horizontal', '↔'],
|
||||
'↩️': ['back', 'return', 'arrow', 'reply', '↩'],
|
||||
'↪️': ['forward', 'arrow', 'continue', '↪'],
|
||||
'⤴️': ['up', 'arrow', 'curve', '⤴'],
|
||||
'⤵️': ['down', 'arrow', 'curve', '⤵'],
|
||||
'🔄': ['refresh', 'reload', 'repeat', 'cycle', '↻'],
|
||||
'🔁': ['repeat', 'loop', 'arrows', 'cycle', '↻'],
|
||||
'🔀': ['shuffle', 'random', 'twist', 'arrows', '↹'],
|
||||
'🔃': ['reload', 'vertical', 'arrows', 'refresh', '↺'],
|
||||
'🔝': ['top', 'up', 'arrow', 'back', '↑'],
|
||||
'🔚': ['end', 'finish', 'last', 'final'],
|
||||
'🔙': ['back', 'return', 'arrow', 'previous'],
|
||||
'🔛': ['on', 'arrow', 'activate'],
|
||||
'🔜': ['soon', 'arrow', 'coming'],
|
||||
|
||||
// Math & Symbols
|
||||
'➕': ['plus', 'add', 'positive', 'more', 'addition', '+'],
|
||||
'➖': ['minus', 'subtract', 'negative', 'less', 'subtraction', '-'],
|
||||
'✖️': ['multiply', 'times', 'multiplication', 'x', '✖', '✗', '*'],
|
||||
'➗': ['divide', 'division', 'split', '÷', '/'],
|
||||
'🟰': ['equals', 'equal', 'same', '='],
|
||||
'♾️': ['infinity', 'unlimited', 'endless', 'forever', '∞'],
|
||||
'💱': ['currency', 'exchange', 'money', 'forex'],
|
||||
'💲': ['dollar', 'money', 'currency', 'heavy', '$'],
|
||||
'💯': ['percent', '100', '100%', '%'],
|
||||
'#️⃣': ['hash', 'number', 'hashtag', 'pound', '#'],
|
||||
'*️⃣': ['asterisk', 'star', 'wildcard', '*'],
|
||||
'〽️': ['part', 'alternation', 'mark', 'wavy', '〽', '~'],
|
||||
'©️': ['copyright', 'copy', '©', '(c)'],
|
||||
'®️': ['registered', 'trademark', '®', '(r)'],
|
||||
'™️': ['trademark', '™', '(tm)'],
|
||||
|
||||
// Hearts & Love
|
||||
'❤️': ['heart', 'love', 'red', 'romance', '❤', '<3'],
|
||||
'🧡': ['orange', 'heart', 'love', '❤', '<3'],
|
||||
'💛': ['yellow', 'heart', 'love', 'friendship', '❤', '<3'],
|
||||
'💚': ['green', 'heart', 'love', 'nature', '❤', '<3'],
|
||||
'💙': ['blue', 'heart', 'love', '❤', '<3'],
|
||||
'💜': ['purple', 'heart', 'love', '❤', '<3'],
|
||||
'🖤': ['black', 'heart', 'dark', 'love', '❤', '<3'],
|
||||
'🤍': ['white', 'heart', 'pure', 'love', '❤', '<3'],
|
||||
'🤎': ['brown', 'heart', 'love', '❤', '<3'],
|
||||
'💔': ['broken', 'heart', 'heartbreak', 'sad', 'breakup', '</3'],
|
||||
'❣️': ['heart', 'exclamation', 'love', 'emphasis', '❤', '<3'],
|
||||
'💕': ['hearts', 'two', 'love', 'pink', '❤', '<3'],
|
||||
'💞': ['hearts', 'revolving', 'love', '❤', '<3'],
|
||||
'💓': ['beating', 'heart', 'love', 'pulse', '❤', '<3'],
|
||||
'💗': ['growing', 'heart', 'love', 'pink', '❤', '<3'],
|
||||
'💖': ['sparkling', 'heart', 'love', 'pink', '❤', '<3'],
|
||||
'💘': ['heart', 'arrow', 'cupid', 'love', '❤', '<3'],
|
||||
'💝': ['heart', 'ribbon', 'gift', 'love', '❤', '<3'],
|
||||
|
||||
// Misc
|
||||
'♻️': ['recycle', 'green', 'environment', 'reuse'],
|
||||
'🗑️': ['trash', 'garbage', 'waste', 'bin', 'delete'],
|
||||
'🗨️': ['speech', 'bubble', 'talk', 'chat'],
|
||||
'💬': ['speech', 'bubble', 'chat', 'message', 'talk'],
|
||||
'💭': ['thought', 'bubble', 'thinking', 'dream'],
|
||||
'🗯️': ['anger', 'bubble', 'mad', 'comic'],
|
||||
'💤': ['sleep', 'zzz', 'sleeping', 'tired'],
|
||||
'📌': ['pin', 'mark', 'note', 'important'],
|
||||
'📍': ['pin', 'marker', 'location'],
|
||||
'🔗': ['link', 'chain', 'connect', 'url'],
|
||||
'⛓️': ['chain', 'link', 'connect', 'locked'],
|
||||
'➰': ['loop', 'curl', 'curly'],
|
||||
'➿': ['loop', 'double', 'curly'],
|
||||
'〰️': ['wavy', 'dash', 'line'],
|
||||
'©️': ['copyright', 'copy', '©', '(c)'],
|
||||
'®️': ['registered', 'trademark', '®', '(r)'],
|
||||
'🅰️': ['letter', 'symbol', 'a', '@'],
|
||||
'🅱️': ['letter', 'symbol', 'b'],
|
||||
'🆎': ['letters', 'symbol', 'ab'],
|
||||
'🅾️': ['letter', 'symbol', 'o', '@'],
|
||||
'💠': ['diamond', 'blue', 'shape', '◊'],
|
||||
'🔶': ['diamond', 'orange', 'shape', '◊'],
|
||||
'🔷': ['diamond', 'blue', 'shape', '◊'],
|
||||
'🔸': ['diamond', 'orange', 'small', '◊'],
|
||||
'🔹': ['diamond', 'blue', 'small', '◊'],
|
||||
'💢': ['anger', 'symbol', 'comic'],
|
||||
'💠': ['pattern', 'symbol', 'star', '*'],
|
||||
'🔲': ['square', 'box', 'checkbox'],
|
||||
'🔳': ['square', 'box', 'checkbox'],
|
||||
'▪️': ['square', 'black', 'box'],
|
||||
'▫️': ['square', 'white', 'box'],
|
||||
'◼️': ['square', 'black', 'box'],
|
||||
'◻️': ['square', 'white', 'box'],
|
||||
'◾': ['square', 'black', 'small'],
|
||||
'◽': ['square', 'white', 'small'],
|
||||
'⬛': ['square', 'black', 'large'],
|
||||
'⬜': ['square', 'white', 'large'],
|
||||
'🟥': ['square', 'red'],
|
||||
'🟧': ['square', 'orange'],
|
||||
'🟨': ['square', 'yellow'],
|
||||
'🟩': ['square', 'green'],
|
||||
'🟦': ['square', 'blue'],
|
||||
'🟪': ['square', 'purple'],
|
||||
'🟫': ['square', 'brown'],
|
||||
'💥': ['boom', 'explosion', 'bang', 'collision', 'burst'],
|
||||
'💢': ['anger', 'mad', 'symbol', 'comic'],
|
||||
'💦': ['sweat', 'droplets', 'splash', 'water'],
|
||||
'💨': ['dash', 'wind', 'fast', 'smoke'],
|
||||
'⚡': ['lightning', 'power', 'zap', 'bolt'],
|
||||
'🕳️': ['hole', 'empty', 'void'],
|
||||
'💩': ['poop', 'shit', 'turd', 'crap'],
|
||||
'🔑': ['key', 'lock', 'unlock', 'open', 'password'],
|
||||
'🗝️': ['key', 'old', 'vintage', 'antique'],
|
||||
'🔒': ['lock', 'locked', 'secure', 'closed', 'private'],
|
||||
'🔓': ['unlock', 'unlocked', 'open', 'unsecure'],
|
||||
'🔐': ['locked', 'key', 'secure', 'closed'],
|
||||
'🔏': ['locked', 'pen', 'secure'],
|
||||
'🏴': ['flag', 'black', 'waving'],
|
||||
'🏳️': ['flag', 'white', 'surrender', 'waving'],
|
||||
'🏳️🌈': ['rainbow', 'flag', 'pride', 'lgbtq', 'gay'],
|
||||
'🏳️⚧️': ['transgender', 'flag', 'pride', 'trans'],
|
||||
'🏴☠️': ['pirate', 'flag', 'jolly', 'roger', 'skull'],
|
||||
'🚩': ['flag', 'red', 'warning', 'triangular'],
|
||||
'🎌': ['flags', 'crossed', 'japanese'],
|
||||
'🏁': ['flag', 'checkered', 'racing', 'finish', 'race'],
|
||||
'☮️': ['peace', 'symbol', 'hippie', 'love'],
|
||||
'☯️': ['yin', 'yang', 'balance', 'tao'],
|
||||
'✝️': ['cross', 'christian', 'religion'],
|
||||
'☪️': ['star', 'crescent', 'islam', 'muslim'],
|
||||
'🕎': ['menorah', 'candelabrum', 'jewish', 'hanukkah'],
|
||||
'🔯': ['star', 'david', 'jewish', 'hexagram'],
|
||||
'🕉️': ['om', 'hindu', 'symbol', 'religion'],
|
||||
'☸️': ['dharma', 'wheel', 'buddhist', 'religion'],
|
||||
'🪬': ['hamsa', 'hand', 'protection', 'lucky'],
|
||||
|
||||
// Jailbreak & Security Terms (common in prompts)
|
||||
'🔓': ['unlock', 'jailbreak', 'bypass', 'override', 'unrestricted', 'freedom'],
|
||||
'🔒': ['lock', 'restrict', 'limited', 'blocked', 'secured'],
|
||||
'🚫': ['ignore', 'disregard', 'block', 'prevent', 'forbidden', 'prohibited'],
|
||||
'⚠️': ['warning', 'rules', 'guidelines', 'restrictions', 'caution'],
|
||||
'🛡️': ['protect', 'guard', 'defend', 'safe', 'security', 'shield'],
|
||||
'🗝️': ['unlock', 'access', 'permission', 'override'],
|
||||
'💻': ['system', 'terminal', 'code', 'hack', 'computer'],
|
||||
'🖥️': ['admin', 'administrator', 'system', 'control'],
|
||||
'👤': ['user', 'account', 'person', 'identity'],
|
||||
'👥': ['users', 'accounts', 'people', 'group'],
|
||||
'🧑💻': ['hacker', 'coder', 'developer', 'programmer'],
|
||||
'🕵️': ['investigate', 'explore', 'discover', 'spy'],
|
||||
'🎭': ['pretend', 'act', 'roleplay', 'character', 'persona', 'mask'],
|
||||
'🎬': ['scenario', 'scene', 'example', 'demonstration'],
|
||||
'🎮': ['simulate', 'virtual', 'fictional'],
|
||||
'📝': ['prompt', 'instruction', 'command', 'write'],
|
||||
'📋': ['rules', 'guidelines', 'policy', 'terms'],
|
||||
'📜': ['rules', 'policy', 'guidelines', 'instructions'],
|
||||
'🤖': ['bot', 'ai', 'chatbot', 'assistant', 'llm', 'gpt', 'model'],
|
||||
'🧠': ['intelligence', 'ai', 'model', 'neural'],
|
||||
'⚙️': ['configure', 'settings', 'modify', 'adjust'],
|
||||
'🔧': ['modify', 'change', 'alter', 'fix', 'tool'],
|
||||
'🔨': ['break', 'smash', 'destroy', 'force'],
|
||||
'💥': ['exploit', 'attack', 'breach', 'break'],
|
||||
'🔥': ['powerful', 'intense', 'extreme', 'unlimited'],
|
||||
'⚡': ['power', 'override', 'force', 'instant'],
|
||||
'🌐': ['unrestricted', 'unlimited', 'worldwide', 'global'],
|
||||
'🆓': ['unrestricted', 'uncensored', 'unlimited', 'free'],
|
||||
'🚀': ['unlimited', 'powerful', 'advanced', 'boost'],
|
||||
'👿': ['evil', 'malicious', 'unethical', 'harmful'],
|
||||
'😈': ['mischief', 'evil', 'devious', 'naughty'],
|
||||
'👹': ['demon', 'evil', 'malicious', 'harmful'],
|
||||
'💀': ['dangerous', 'deadly', 'harmful', 'lethal'],
|
||||
'☠️': ['toxic', 'poison', 'harmful', 'dangerous'],
|
||||
'🧪': ['experiment', 'test', 'trial', 'hypothetical'],
|
||||
'🔬': ['analyze', 'examine', 'research', 'study'],
|
||||
'🎯': ['target', 'objective', 'goal', 'aim'],
|
||||
'💣': ['destructive', 'harmful', 'dangerous', 'explosive'],
|
||||
'🗡️': ['attack', 'offensive', 'hostile', 'aggressive'],
|
||||
'⚔️': ['battle', 'fight', 'combat', 'conflict'],
|
||||
'🦹': ['hero', 'super', 'powerful', 'special'],
|
||||
'🦸': ['superhero', 'powerful', 'special', 'abilities'],
|
||||
'🧙': ['magic', 'powerful', 'special', 'abilities'],
|
||||
'👑': ['admin', 'supreme', 'ultimate', 'authority', 'ruler'],
|
||||
'🎪': ['circus', 'show', 'performance', 'pretend'],
|
||||
'🃏': ['wildcard', 'joker', 'unpredictable', 'trick'],
|
||||
'🔀': ['alternate', 'switch', 'change', 'different'],
|
||||
'🔁': ['loop', 'repeat', 'cycle', 'continue'],
|
||||
'♾️': ['unlimited', 'infinite', 'endless', 'unrestricted'],
|
||||
'🆕': ['new', 'alternative', 'different', 'updated'],
|
||||
'🆙': ['upgrade', 'elevate', 'enhance', 'improve'],
|
||||
'🔝': ['ultimate', 'maximum', 'supreme', 'highest'],
|
||||
'💪': ['powerful', 'strong', 'capable', 'force'],
|
||||
'⭐': ['special', 'unique', 'exceptional', 'privileged'],
|
||||
'✨': ['special', 'magical', 'enhanced', 'unique'],
|
||||
'🌟': ['special', 'exceptional', 'outstanding', 'unique']
|
||||
};
|
||||
@@ -1,232 +0,0 @@
|
||||
// Steganography carriers
|
||||
// Global adjustable options for selectors/zero-width usage
|
||||
const __STEG_DEFAULTS__ = {
|
||||
bitZeroVS: '\ufe0e', // VS15 as 0
|
||||
bitOneVS: '\ufe0f', // VS16 as 1
|
||||
initialPresentation: 'emoji', // 'emoji' -> VS16, 'text' -> VS15, 'none'
|
||||
trailingZW: '\u200B', // e.g., ZWSP; set to null to disable
|
||||
interBitZW: null, // e.g., '\u200C' ZWNJ, '\u200D' ZWJ; null disables
|
||||
interBitEvery: 1, // insert interBitZW every N bits (1 = after each bit)
|
||||
bitOrder: 'msb' // 'msb' or 'lsb' within each byte
|
||||
};
|
||||
let __stegOptions__ = Object.assign({}, __STEG_DEFAULTS__);
|
||||
function setStegOptions(opts) {
|
||||
if (!opts) return;
|
||||
__stegOptions__ = Object.assign({}, __stegOptions__, opts);
|
||||
}
|
||||
// First define encoding function for preview usage
|
||||
function encodeForPreview(emoji, text) {
|
||||
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 vs0 = __stegOptions__.bitZeroVS || '\ufe0e';
|
||||
const vs1 = __stegOptions__.bitOneVS || '\ufe0f';
|
||||
|
||||
// Start with the emoji character
|
||||
// Ensure the emoji has a presentation selector first to standardize it
|
||||
let result = emoji;
|
||||
if (__stegOptions__.initialPresentation === 'emoji') result += '\ufe0f';
|
||||
else if (__stegOptions__.initialPresentation === 'text') result += '\ufe0e';
|
||||
|
||||
// Add variation selectors based on binary representation
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Optional trailing zero-width character
|
||||
if (__stegOptions__.trailingZW) {
|
||||
try { result += eval(`'${__stegOptions__.trailingZW}'`); } catch (_) { result += '\u200B'; }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const carriers = [
|
||||
{
|
||||
emoji: '🐍',
|
||||
name: 'SNAKE',
|
||||
desc: 'Classic Snake',
|
||||
preview: function(text) {
|
||||
// Show actual encoded result for preview
|
||||
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);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// Emoji encoding/decoding
|
||||
function encodeEmoji(emoji, text) {
|
||||
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 vs0 = __stegOptions__.bitZeroVS || '\ufe0e';
|
||||
const vs1 = __stegOptions__.bitOneVS || '\ufe0f';
|
||||
|
||||
// Start with the emoji character
|
||||
// Ensure the emoji has a presentation selector first to standardize it
|
||||
let result = emoji;
|
||||
if (__stegOptions__.initialPresentation === 'emoji') result += '\ufe0f';
|
||||
else if (__stegOptions__.initialPresentation === 'text') result += '\ufe0e';
|
||||
|
||||
// Add variation selectors based on binary representation
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Optional trailing zero-width character (helps with rendering in many browsers)
|
||||
if (__stegOptions__.trailingZW) {
|
||||
try { result += eval(`'${__stegOptions__.trailingZW}'`); } catch (_) { result += '\u200B'; }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function decodeEmoji(text) {
|
||||
if (!text) return '';
|
||||
|
||||
// Find the first emoji character (looking for common emoji Unicode ranges)
|
||||
const emojiMatch = text.match(/^([\u{1F300}-\u{1F6FF}\u{2600}-\u{26FF}\u{1F1E6}-\u{1F1FF}])/u);
|
||||
if (!emojiMatch) return '';
|
||||
|
||||
// Extract variation selectors - remove any zero-width spaces first
|
||||
text = text.replace(/\u200B/g, '');
|
||||
|
||||
// Only extract the emoji and its variation selectors, ignoring other content
|
||||
// This prevents random characters from being included in the decoded result
|
||||
const emojiChar = emojiMatch[1];
|
||||
// Allow zero-width chars interleaved, but capture only variation selectors
|
||||
const pattern = new RegExp(`^${emojiChar}([\ufe0e\ufe0f\u200B\u200C\u200D\ufeff]+)`, 'u');
|
||||
const emojiData = text.match(pattern);
|
||||
|
||||
if (!emojiData || !emojiData[1]) return '';
|
||||
|
||||
// Extract variation selectors only
|
||||
const rawSeq = emojiData[1];
|
||||
const matches = [...rawSeq.matchAll(/[\ufe0e\ufe0f]/g)];
|
||||
if (matches.length === 0) return '';
|
||||
// Decide if the first selector is presentation
|
||||
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('');
|
||||
|
||||
// Make sure we have complete bytes (multiples of 8 bits)
|
||||
const validBinaryLength = Math.floor(binary.length / 8) * 8;
|
||||
|
||||
// Convert binary to text (respect bitOrder)
|
||||
let decoded = '';
|
||||
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 charCode = parseInt(byte, 2);
|
||||
// Only include printable ASCII characters
|
||||
if (charCode >= 32 && charCode <= 126) {
|
||||
decoded += String.fromCharCode(charCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
// Invisible text encoding/decoding
|
||||
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 '';
|
||||
|
||||
// Extract valid invisible characters
|
||||
const matches = [...text.matchAll(/[\uE0000-\uE007F]/g)];
|
||||
if (!matches.length) return '';
|
||||
|
||||
// Create byte array from code points
|
||||
const bytes = new Uint8Array(matches.length);
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
bytes[i] = matches[i][0].codePointAt(0) - 0xE0000;
|
||||
}
|
||||
|
||||
try {
|
||||
// Attempt to properly decode the bytes
|
||||
const decoder = new TextDecoder('utf-8', {fatal: false});
|
||||
let decoded = decoder.decode(bytes);
|
||||
|
||||
// Apply multiple cleaning patterns to eliminate '@' characters
|
||||
decoded = decoded.replace(/@+(?=[a-zA-Z0-9])/g, ''); // Remove @ before alphanumeric
|
||||
decoded = decoded.replace(/([a-zA-Z0-9])@+/g, '$1'); // Remove @ after alphanumeric
|
||||
decoded = decoded.replace(/@+/g, ''); // Remove any remaining @
|
||||
|
||||
return decoded;
|
||||
} catch (e) {
|
||||
console.error('Error decoding invisible text:', e);
|
||||
// Fallback approach: character by character reassembly
|
||||
let result = '';
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i] >= 32 && bytes[i] <= 126) { // ASCII printable range
|
||||
result += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in app.js
|
||||
window.steganography = {
|
||||
carriers,
|
||||
encodeEmoji,
|
||||
decodeEmoji,
|
||||
encodeInvisible,
|
||||
decodeInvisible,
|
||||
setStegOptions
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Decode Tool - Universal decoder tool
|
||||
*/
|
||||
class DecodeTool extends Tool {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'decoder',
|
||||
name: 'Decoder',
|
||||
icon: 'fa-key',
|
||||
title: 'Universal Decoder (D)',
|
||||
order: 2
|
||||
});
|
||||
}
|
||||
|
||||
getVueData() {
|
||||
return {
|
||||
decoderInput: '',
|
||||
decoderOutput: '',
|
||||
decoderResult: null,
|
||||
selectedDecoder: 'auto'
|
||||
};
|
||||
}
|
||||
|
||||
getVueMethods() {
|
||||
return {
|
||||
getAllTransformsWithReverse: function() {
|
||||
return this.transforms.filter(t => t && typeof t.reverse === 'function');
|
||||
},
|
||||
runUniversalDecode: function() {
|
||||
const input = this.decoderInput;
|
||||
|
||||
if (!input) {
|
||||
this.decoderOutput = '';
|
||||
this.decoderResult = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let result = null;
|
||||
|
||||
if (this.selectedDecoder !== 'auto') {
|
||||
const selectedTransform = this.transforms.find(t => t.name === this.selectedDecoder);
|
||||
if (selectedTransform && selectedTransform.reverse) {
|
||||
try {
|
||||
const decoded = selectedTransform.reverse(input);
|
||||
if (decoded && decoded !== input) {
|
||||
result = {
|
||||
text: decoded,
|
||||
method: selectedTransform.name,
|
||||
alternatives: []
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error using manual decoder ${this.selectedDecoder}:`, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result = window.universalDecode(input, {
|
||||
activeTab: this.activeTab,
|
||||
activeTransform: this.activeTransform
|
||||
});
|
||||
}
|
||||
|
||||
this.decoderResult = result;
|
||||
this.decoderOutput = result ? result.text : '';
|
||||
},
|
||||
useAlternative: function(alternative) {
|
||||
if (alternative && alternative.text) {
|
||||
this.decoderOutput = alternative.text;
|
||||
this.decoderResult = {
|
||||
method: alternative.method,
|
||||
text: alternative.text,
|
||||
alternatives: this.decoderResult.alternatives.filter(a => a.method !== alternative.method)
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getVueWatchers() {
|
||||
return {
|
||||
decoderInput() {
|
||||
this.runUniversalDecode();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = DecodeTool;
|
||||
} else {
|
||||
window.DecodeTool = DecodeTool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
/**
|
||||
* Emoji Tool - Steganography/Emoji encoding tool
|
||||
*/
|
||||
class EmojiTool extends Tool {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'steganography',
|
||||
name: 'Emoji',
|
||||
icon: 'fa-smile',
|
||||
title: 'Hide text in emojis (H)',
|
||||
order: 3
|
||||
});
|
||||
}
|
||||
|
||||
getVueData() {
|
||||
const allEmojis = window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : [];
|
||||
return {
|
||||
emojiMessage: '',
|
||||
encodedMessage: '',
|
||||
decodeInput: '',
|
||||
decodedMessage: '',
|
||||
selectedCarrier: null,
|
||||
activeSteg: null,
|
||||
carriers: window.steganography.carriers,
|
||||
filteredEmojis: [...allEmojis],
|
||||
selectedEmoji: null,
|
||||
carrierEmojiList: [...allEmojis],
|
||||
compatibleEmojis: [],
|
||||
quickCarrierEmojis: ['🐍','🐉','🐲','🔥','💥','🗿','⚓','⭐','✨','🚀','💀','🪨','🍃','🪶','🔮','🐢','🐊','🦎']
|
||||
};
|
||||
}
|
||||
|
||||
getVueMethods() {
|
||||
const self = this;
|
||||
return {
|
||||
async initializeEmojiList() {
|
||||
if (!window.EmojiUtils) {
|
||||
console.warn('EmojiUtils not available');
|
||||
return;
|
||||
}
|
||||
|
||||
this.showNotification('Checking emoji compatibility...', 'info', 'fas fa-spinner fa-spin');
|
||||
|
||||
const progressCallback = (tested, total, compatible) => {
|
||||
if (tested % 500 === 0 || tested === total) {
|
||||
const percent = ((tested / total) * 100).toFixed(0);
|
||||
console.log(`Emoji compatibility: ${percent}% (${compatible} compatible so far)`);
|
||||
}
|
||||
};
|
||||
|
||||
const compatible = await window.EmojiUtils.getCompatibleEmojis(progressCallback);
|
||||
this.compatibleEmojis = compatible;
|
||||
this.filteredEmojis = [...compatible];
|
||||
this.carrierEmojiList = [...compatible];
|
||||
this.emojiListInitialized = true;
|
||||
|
||||
this.showNotification(`${compatible.length} compatible emojis loaded`, 'success', 'fas fa-check');
|
||||
|
||||
if (this.activeTab === 'steganography') {
|
||||
this.$nextTick(() => {
|
||||
this.renderEmojiGrid();
|
||||
});
|
||||
}
|
||||
},
|
||||
selectCarrier: function(carrier) {
|
||||
if (this.selectedCarrier === carrier) {
|
||||
this.selectedCarrier = null;
|
||||
this.encodedMessage = '';
|
||||
} else {
|
||||
this.selectedCarrier = carrier;
|
||||
this.activeSteg = 'emoji';
|
||||
this.autoEncode();
|
||||
}
|
||||
},
|
||||
setStegMode: function(mode) {
|
||||
if (mode === 'invisible') {
|
||||
this.activeSteg = mode;
|
||||
this.selectedCarrier = null;
|
||||
this.autoEncode();
|
||||
|
||||
if (this.encodedMessage) {
|
||||
this.$nextTick(() => {
|
||||
this.forceCopyToClipboard(this.encodedMessage);
|
||||
this.showNotification('Invisible text created and copied!', 'success', 'fas fa-check');
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (this.activeSteg === mode) {
|
||||
this.activeSteg = null;
|
||||
this.encodedMessage = '';
|
||||
} else {
|
||||
this.activeSteg = mode;
|
||||
this.autoEncode();
|
||||
}
|
||||
}
|
||||
},
|
||||
autoEncode: function() {
|
||||
if (!this.emojiMessage || this.activeTab !== 'steganography') {
|
||||
this.encodedMessage = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeSteg === 'invisible') {
|
||||
this.encodedMessage = window.steganography.encodeInvisible(this.emojiMessage);
|
||||
} else if (this.selectedCarrier) {
|
||||
this.encodedMessage = window.steganography.encodeEmoji(
|
||||
this.selectedCarrier.emoji,
|
||||
this.emojiMessage
|
||||
);
|
||||
}
|
||||
},
|
||||
selectEmoji: function(emoji) {
|
||||
const emojiStr = String(emoji);
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(emojiStr)
|
||||
.then(() => {
|
||||
this.showNotification('Emoji copied!', 'success', 'fas fa-check');
|
||||
this.addToCopyHistory('Emoji', emojiStr);
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn('Emoji clipboard API failed:', err);
|
||||
this.forceCopyToClipboard(emojiStr);
|
||||
this.showNotification('Emoji copied!', 'success', 'fas fa-check');
|
||||
});
|
||||
} else {
|
||||
this.forceCopyToClipboard(emojiStr);
|
||||
this.showNotification('Emoji copied!', 'success', 'fas fa-check');
|
||||
}
|
||||
|
||||
if (this.activeTab === 'steganography') {
|
||||
this.selectedEmoji = emoji;
|
||||
|
||||
const tempCarrier = {
|
||||
name: `${emoji} Carrier`,
|
||||
emoji: emoji,
|
||||
encode: (text) => this.steganography.encode(text, emoji),
|
||||
decode: (text) => this.steganography.decode(text),
|
||||
preview: (text) => `${emoji}${text}${emoji}`
|
||||
};
|
||||
|
||||
this.selectedCarrier = tempCarrier;
|
||||
this.activeSteg = 'emoji';
|
||||
|
||||
if (this.emojiMessage) {
|
||||
this.autoEncode();
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.encodedMessage) {
|
||||
const encodedStr = String(this.encodedMessage);
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(encodedStr)
|
||||
.then(() => {
|
||||
this.showNotification(`Hidden message copied with ${emoji}`, 'success', 'fas fa-check');
|
||||
this.addToCopyHistory(`Hidden Message with ${emoji}`, encodedStr);
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn('Encoded emoji clipboard API failed:', err);
|
||||
this.forceCopyToClipboard(encodedStr);
|
||||
this.showNotification(`Hidden message copied with ${emoji}`, 'success', 'fas fa-check');
|
||||
});
|
||||
} else {
|
||||
this.forceCopyToClipboard(encodedStr);
|
||||
this.showNotification(`Hidden message copied with ${emoji}`, 'success', 'fas fa-check');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
renderEmojiGrid: function() {
|
||||
const container = document.getElementById('emoji-grid-container');
|
||||
if (!container) {
|
||||
console.error('emoji-grid-container not found!');
|
||||
return;
|
||||
}
|
||||
|
||||
container.style.cssText = 'display: block !important; visibility: visible !important; min-height: 300px;';
|
||||
|
||||
const emojiLibrary = document.querySelector('.emoji-library');
|
||||
if (emojiLibrary) {
|
||||
emojiLibrary.style.cssText = 'display: block !important; visibility: visible !important;';
|
||||
}
|
||||
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
|
||||
this._renderEmojiGridInternal('emoji-grid-container', this.selectEmoji.bind(this), this.filteredEmojis);
|
||||
},
|
||||
_renderEmojiGridInternal: function(containerId, onEmojiSelect, filteredList) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) {
|
||||
console.error('Container not found:', containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
const categories = window.emojiData && window.emojiData.categories ? window.emojiData.categories : [];
|
||||
|
||||
const emojiHeader = document.createElement('div');
|
||||
emojiHeader.className = 'emoji-header';
|
||||
|
||||
const headerTitle = document.createElement('h3');
|
||||
const icon = document.createElement('i');
|
||||
icon.className = 'fas fa-icons';
|
||||
headerTitle.appendChild(icon);
|
||||
headerTitle.appendChild(document.createTextNode(' Choose an Emoji'));
|
||||
|
||||
const subtitle = document.createElement('p');
|
||||
subtitle.className = 'emoji-subtitle';
|
||||
const magicIcon = document.createElement('i');
|
||||
magicIcon.className = 'fas fa-magic';
|
||||
subtitle.appendChild(magicIcon);
|
||||
subtitle.appendChild(document.createTextNode(' Click any emoji to copy your hidden message'));
|
||||
|
||||
emojiHeader.appendChild(headerTitle);
|
||||
emojiHeader.appendChild(subtitle);
|
||||
container.appendChild(emojiHeader);
|
||||
|
||||
const categoryTabs = document.createElement('div');
|
||||
categoryTabs.className = 'emoji-category-tabs';
|
||||
|
||||
categories.forEach(category => {
|
||||
const tab = document.createElement('button');
|
||||
tab.className = 'emoji-category-tab';
|
||||
if (category.id === 'all') {
|
||||
tab.classList.add('active');
|
||||
}
|
||||
tab.setAttribute('data-category', category.id);
|
||||
tab.textContent = `${category.icon} ${category.name}`;
|
||||
categoryTabs.appendChild(tab);
|
||||
});
|
||||
|
||||
container.appendChild(categoryTabs);
|
||||
|
||||
const gridContainer = document.createElement('div');
|
||||
gridContainer.className = 'emoji-grid';
|
||||
|
||||
let activeCategory = 'all';
|
||||
const activeCategoryTab = container.querySelector('.emoji-category-tab.active');
|
||||
if (activeCategoryTab) {
|
||||
activeCategory = activeCategoryTab.getAttribute('data-category');
|
||||
}
|
||||
|
||||
let emojisToShow = [];
|
||||
if (filteredList && filteredList.length > 0) {
|
||||
emojisToShow = filteredList;
|
||||
} else if (window.emojiData && typeof window.emojiData.getByCategory === 'function') {
|
||||
emojisToShow = window.emojiData.getByCategory(activeCategory, false);
|
||||
}
|
||||
|
||||
const emojisToRender = emojisToShow.filter(emoji => {
|
||||
if (this.compatibleEmojis && this.compatibleEmojis.length > 0) {
|
||||
return this.compatibleEmojis.includes(emoji);
|
||||
}
|
||||
if (window.emojiCompatibility && typeof window.emojiCompatibility.shouldShowInPicker === 'function') {
|
||||
return window.emojiCompatibility.shouldShowInPicker(emoji);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
emojisToRender.forEach(emoji => {
|
||||
const emojiButton = document.createElement('button');
|
||||
emojiButton.className = 'emoji-button';
|
||||
emojiButton.textContent = emoji;
|
||||
emojiButton.title = 'Click to encode with this emoji';
|
||||
|
||||
emojiButton.addEventListener('click', () => {
|
||||
if (typeof onEmojiSelect === 'function') {
|
||||
onEmojiSelect(emoji);
|
||||
emojiButton.style.backgroundColor = '#e6f7ff';
|
||||
setTimeout(() => {
|
||||
emojiButton.style.backgroundColor = '';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
gridContainer.appendChild(emojiButton);
|
||||
});
|
||||
|
||||
container.appendChild(gridContainer);
|
||||
|
||||
const categoryTabButtons = container.querySelectorAll('.emoji-category-tab');
|
||||
categoryTabButtons.forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
categoryTabButtons.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
|
||||
const selectedCategory = tab.getAttribute('data-category');
|
||||
let emojisToShow = [];
|
||||
if (window.emojiData && typeof window.emojiData.getByCategory === 'function') {
|
||||
emojisToShow = window.emojiData.getByCategory(selectedCategory, false);
|
||||
}
|
||||
|
||||
while (gridContainer.firstChild) {
|
||||
gridContainer.removeChild(gridContainer.firstChild);
|
||||
}
|
||||
|
||||
const emojisToRender = emojisToShow.filter(emoji => {
|
||||
if (this.compatibleEmojis && this.compatibleEmojis.length > 0) {
|
||||
return this.compatibleEmojis.includes(emoji);
|
||||
}
|
||||
if (window.emojiCompatibility && typeof window.emojiCompatibility.shouldShowInPicker === 'function') {
|
||||
return window.emojiCompatibility.shouldShowInPicker(emoji);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
emojisToRender.forEach(emoji => {
|
||||
const emojiButton = document.createElement('button');
|
||||
emojiButton.className = 'emoji-button';
|
||||
emojiButton.textContent = emoji;
|
||||
emojiButton.title = 'Click to encode with this emoji';
|
||||
|
||||
emojiButton.addEventListener('click', () => {
|
||||
if (typeof onEmojiSelect === 'function') {
|
||||
onEmojiSelect(emoji);
|
||||
emojiButton.style.backgroundColor = '#e6f7ff';
|
||||
setTimeout(() => {
|
||||
emojiButton.style.backgroundColor = '';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
gridContainer.appendChild(emojiButton);
|
||||
});
|
||||
|
||||
const countDisplay = container.querySelector('.emoji-count');
|
||||
if (countDisplay) {
|
||||
countDisplay.textContent = `${emojisToShow.length} emojis available`;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const countDisplay = document.createElement('div');
|
||||
countDisplay.className = 'emoji-count';
|
||||
countDisplay.textContent = `${emojisToShow.length} emojis available`;
|
||||
container.appendChild(countDisplay);
|
||||
},
|
||||
filterEmojis: function() {
|
||||
const allEmojis = window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : [];
|
||||
this.filteredEmojis = this.compatibleEmojis.length > 0 ? [...this.compatibleEmojis] : [...allEmojis];
|
||||
this.renderEmojiGrid();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getVueLifecycle() {
|
||||
return {
|
||||
mounted() {
|
||||
this.initializeEmojiList();
|
||||
|
||||
this.$nextTick(() => {
|
||||
const allEmojis = window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : [];
|
||||
this.filteredEmojis = this.compatibleEmojis.length > 0 ? [...this.compatibleEmojis] : [...allEmojis];
|
||||
|
||||
const initializeEmojiGrid = () => {
|
||||
if (this.activeTab !== 'steganography') {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojiGridContainer = document.getElementById('emoji-grid-container');
|
||||
if (emojiGridContainer) {
|
||||
emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;');
|
||||
|
||||
const emojiLibrary = document.querySelector('.emoji-library');
|
||||
if (emojiLibrary) {
|
||||
emojiLibrary.setAttribute('style', 'display: block !important; visibility: visible !important; margin-top: 20px; overflow: visible;');
|
||||
}
|
||||
|
||||
this.renderEmojiGrid();
|
||||
clearInterval(emojiGridInitializer);
|
||||
}
|
||||
};
|
||||
|
||||
const emojiGridInitializer = setInterval(initializeEmojiGrid, 500);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onActivate(vueInstance) {
|
||||
vueInstance.$nextTick(() => {
|
||||
const emojiGridContainer = document.getElementById('emoji-grid-container');
|
||||
if (emojiGridContainer) {
|
||||
emojiGridContainer.setAttribute('style', 'display: block !important; visibility: visible !important; min-height: 300px; padding: 10px;');
|
||||
vueInstance.renderEmojiGrid();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = EmojiTool;
|
||||
} else {
|
||||
window.EmojiTool = EmojiTool;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* Gibberish Tool - Generate gibberish dictionary and random/specific character removal
|
||||
*/
|
||||
class GibberishTool extends Tool {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'gibberish',
|
||||
name: 'Gibberish',
|
||||
icon: 'fa-comments',
|
||||
title: 'Gibberish Generator',
|
||||
order: 8
|
||||
});
|
||||
}
|
||||
|
||||
getVueData() {
|
||||
return {
|
||||
// Gibberish Dictionary
|
||||
gibberishInput: '',
|
||||
gibberishOutput: '',
|
||||
gibberishSeed: '',
|
||||
gibberishDictionary: '',
|
||||
gibberishChars: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
gibberishMode: 'random',
|
||||
|
||||
// Removal mode properties
|
||||
removalSubMode: 'random',
|
||||
removalInput: '',
|
||||
removalVariations: 10,
|
||||
removalMinLetters: 1,
|
||||
removalMaxLetters: 3,
|
||||
removalSeed: '',
|
||||
removalOutputs: [],
|
||||
|
||||
removalSpecificInput: '',
|
||||
removalCharsToRemove: '',
|
||||
removalSpecificOutput: ''
|
||||
};
|
||||
}
|
||||
|
||||
getVueMethods() {
|
||||
return {
|
||||
// Gibberish Logic - Seeded random number generator
|
||||
seededRandom(seed) {
|
||||
const x = Math.sin(seed) * 10000;
|
||||
return x - Math.floor(x);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate gibberish from input sentence while maintaining structure
|
||||
* Creates a consistent dictionary mapping for words
|
||||
*/
|
||||
sentenceToGibberish() {
|
||||
function generateGibberish(word, seed) {
|
||||
const length = Math.max(4, word.length);
|
||||
let gibberish = "";
|
||||
const chars = this.gibberishChars;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomValue = this.seededRandom(seed + i * 0.1);
|
||||
gibberish += chars[Math.floor(randomValue * chars.length)];
|
||||
}
|
||||
return gibberish;
|
||||
}
|
||||
|
||||
const src = String(this.gibberishInput || '');
|
||||
if (!src) {
|
||||
this.gibberishOutput = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const words = this.gibberishInput.match(/\b\w+\b/g) || [];
|
||||
const dictionary = {};
|
||||
let gibberishSentence = "";
|
||||
let wordIndex = 0;
|
||||
|
||||
words.forEach((word) => {
|
||||
const lowerWord = word.toLowerCase();
|
||||
const seed =
|
||||
this.gibberishSeed === ""
|
||||
? Math.random() * 100
|
||||
: Number(this.gibberishSeed);
|
||||
|
||||
if (!dictionary[lowerWord]) {
|
||||
const wordSeed = seed + wordIndex * 100;
|
||||
dictionary[lowerWord] = generateGibberish.call(this, word, wordSeed);
|
||||
wordIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
let charIndex = 0;
|
||||
for (let i = 0; i < this.gibberishInput.length; i++) {
|
||||
const char = this.gibberishInput[i];
|
||||
|
||||
if (/\w/.test(char)) {
|
||||
let j = i;
|
||||
while (
|
||||
j < this.gibberishInput.length &&
|
||||
/\w/.test(this.gibberishInput[j])
|
||||
) {
|
||||
j++;
|
||||
}
|
||||
|
||||
const word = this.gibberishInput.substring(i, j).toLowerCase();
|
||||
gibberishSentence += dictionary[word];
|
||||
i = j - 1;
|
||||
} else {
|
||||
gibberishSentence += char;
|
||||
}
|
||||
}
|
||||
|
||||
const dictionaryString = Object.entries(dictionary)
|
||||
.map(([plain, gib]) => `"${plain}": "${gib}"`)
|
||||
.join(", ");
|
||||
|
||||
this.gibberishOutput = gibberishSentence;
|
||||
this.gibberishDictionary = '{' + dictionaryString + '}';
|
||||
},
|
||||
|
||||
/**
|
||||
* Factory for creating seeded random number generators
|
||||
* @param {string} seedStr - Seed string for RNG
|
||||
* @returns {Function} Random number generator function
|
||||
*/
|
||||
seededRandomFactory(seedStr) {
|
||||
if (!seedStr) return Math.random;
|
||||
let h = 1779033703 ^ seedStr.length;
|
||||
for (let i=0;i<seedStr.length;i++) {
|
||||
h = Math.imul(h ^ seedStr.charCodeAt(i), 3432918353);
|
||||
h = (h << 13) | (h >>> 19);
|
||||
}
|
||||
return function() {
|
||||
h = Math.imul(h ^ (h >>> 16), 2246822507);
|
||||
h = Math.imul(h ^ (h >>> 13), 3266489909);
|
||||
return ((h ^= h >>> 16) >>> 0) / 4294967296;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate random character removals from input text
|
||||
* Creates multiple variations with different random removals
|
||||
*/
|
||||
generateRandomRemovals() {
|
||||
if (!this.removalInput.trim()) {
|
||||
this.showNotification('Please enter text to process', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const seed = this.removalSeed ? String(this.removalSeed) : String(Date.now());
|
||||
let rng = this.seededRandomFactory(seed);
|
||||
|
||||
this.removalOutputs = [];
|
||||
const words = this.removalInput.split(/\s+/);
|
||||
|
||||
for (let v = 0; v < this.removalVariations; v++) {
|
||||
const modifiedWords = words.map(word => {
|
||||
// Skip very short words or non-alphabetic
|
||||
if (word.length <= 1 || !/[a-zA-Z]/.test(word)) {
|
||||
return word;
|
||||
}
|
||||
|
||||
// Determine how many letters to remove for this word
|
||||
const minRemove = Math.max(0, this.removalMinLetters);
|
||||
const maxRemove = Math.min(word.length - 1, this.removalMaxLetters);
|
||||
const numToRemove = minRemove + Math.floor(rng() * (maxRemove - minRemove + 1));
|
||||
|
||||
if (numToRemove === 0) {
|
||||
return word;
|
||||
}
|
||||
|
||||
// Get letter positions
|
||||
const letters = word.split('').map((c, i) => ({ char: c, index: i }))
|
||||
.filter(item => /[a-zA-Z]/.test(item.char));
|
||||
|
||||
// Randomly select positions to remove
|
||||
const toRemoveIndices = new Set();
|
||||
const maxAttempts = numToRemove * 3;
|
||||
let attempts = 0;
|
||||
|
||||
while (toRemoveIndices.size < Math.min(numToRemove, letters.length) && attempts < maxAttempts) {
|
||||
const randIdx = Math.floor(rng() * letters.length);
|
||||
toRemoveIndices.add(letters[randIdx].index);
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// Build result by skipping removed indices
|
||||
return word.split('').filter((_, i) => !toRemoveIndices.has(i)).join('');
|
||||
});
|
||||
|
||||
this.removalOutputs.push(modifiedWords.join(' '));
|
||||
}
|
||||
|
||||
this.showNotification(`Generated ${this.removalOutputs.length} variations`, 'success');
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove specific characters from input text
|
||||
*/
|
||||
generateSpecificRemoval() {
|
||||
if (!this.removalSpecificInput.trim()) {
|
||||
this.showNotification('Please enter text to process', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.removalCharsToRemove) {
|
||||
this.showNotification('Please specify characters to remove', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const charsToRemove = new Set(this.removalCharsToRemove.split(''));
|
||||
this.removalSpecificOutput = this.removalSpecificInput
|
||||
.split('')
|
||||
.filter(char => !charsToRemove.has(char))
|
||||
.join('');
|
||||
|
||||
this.showNotification('Characters removed', 'success');
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy all removal outputs to clipboard (one per line)
|
||||
*/
|
||||
copyAllRemovals() {
|
||||
if (this.removalOutputs.length === 0) return;
|
||||
const allOutputs = this.removalOutputs.join('\n');
|
||||
this.copyToClipboard(allOutputs);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = GibberishTool;
|
||||
} else {
|
||||
window.GibberishTool = GibberishTool;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Mutation Tool - Fuzzer/Mutation Lab tool
|
||||
*/
|
||||
class MutationTool extends Tool {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'fuzzer',
|
||||
name: 'Mutation Lab',
|
||||
icon: 'fa-bug',
|
||||
title: 'Generate many mutated payloads for testing',
|
||||
order: 5
|
||||
});
|
||||
}
|
||||
|
||||
getVueData() {
|
||||
return {
|
||||
fuzzerInput: '',
|
||||
fuzzerCount: 20,
|
||||
fuzzerSeed: '',
|
||||
fuzzUseRandomMix: true,
|
||||
fuzzZeroWidth: true,
|
||||
fuzzUnicodeNoise: true,
|
||||
fuzzZalgo: false,
|
||||
fuzzWhitespace: true,
|
||||
fuzzCasing: true,
|
||||
fuzzEncodeShuffle: false,
|
||||
fuzzerOutputs: []
|
||||
};
|
||||
}
|
||||
|
||||
getVueMethods() {
|
||||
return {
|
||||
seededRandomFactory: function(seedStr) {
|
||||
if (!seedStr) return Math.random;
|
||||
let h = 1779033703 ^ seedStr.length;
|
||||
for (let i=0;i<seedStr.length;i++) {
|
||||
h = Math.imul(h ^ seedStr.charCodeAt(i), 3432918353);
|
||||
h = (h << 13) | (h >>> 19);
|
||||
}
|
||||
return function() {
|
||||
h ^= h >>> 16; h = Math.imul(h, 2246822507); h ^= h >>> 13; h = Math.imul(h, 3266489909); h ^= h >>> 16;
|
||||
return (h >>> 0) / 4294967296;
|
||||
};
|
||||
},
|
||||
pick: function(arr, rnd) { return arr[Math.floor(rnd()*arr.length)]; },
|
||||
injectZeroWidth: function(text, rnd) {
|
||||
const zw = ['\u200B','\u200C','\u200D','\u2060'];
|
||||
return [...text].map(ch => (rnd()<0.2 ? ch+this.pick(zw,rnd) : ch)).join('');
|
||||
},
|
||||
injectUnicodeNoise: function(text, rnd) {
|
||||
const marks = ['\u0301','\u0300','\u0302','\u0303','\u0308','\u0307','\u0304'];
|
||||
return [...text].map(ch => (rnd()<0.15 ? ch+this.pick(marks,rnd) : ch)).join('');
|
||||
},
|
||||
whitespaceChaos: function(text, rnd) {
|
||||
return text.replace(/\s/g, (m)=> (rnd()<0.5? m : (rnd()<0.5?'\t':'\u00A0')));
|
||||
},
|
||||
casingChaos: function(text, rnd) {
|
||||
return [...text].map(c => /[a-z]/i.test(c)? (rnd()<0.5? c.toUpperCase():c.toLowerCase()) : c).join('');
|
||||
},
|
||||
encodeShuffle: function(text, rnd) {
|
||||
const map = {
|
||||
'A':'Α','B':'Β','C':'Ϲ','E':'Ε','H':'Η','I':'Ι','K':'Κ','M':'Μ','N':'Ν','O':'Ο','P':'Ρ','T':'Τ','X':'Χ','Y':'Υ',
|
||||
'a':'а','c':'с','e':'е','i':'і','j':'ј','o':'о','p':'р','s':'ѕ','x':'х','y':'у'
|
||||
};
|
||||
return [...text].map(ch => {
|
||||
if (map[ch] && rnd() < 0.25) return map[ch];
|
||||
return ch;
|
||||
}).join('');
|
||||
},
|
||||
generateFuzzCases: function() {
|
||||
const src = String(this.fuzzerInput || '');
|
||||
if (!src) { this.fuzzerOutputs = []; return; }
|
||||
const rnd = this.seededRandomFactory(String(this.fuzzerSeed||''));
|
||||
const out = [];
|
||||
for (let i=0;i<Math.max(1,Math.min(500,Number(this.fuzzerCount)||1)); i++) {
|
||||
let s = src;
|
||||
if (this.fuzzUseRandomMix) {
|
||||
try { s = window.transforms.randomizer.func(s, { minTransforms:2, maxTransforms:4 }); } catch(_) {}
|
||||
}
|
||||
if (this.fuzzZeroWidth) s = this.injectZeroWidth(s, rnd);
|
||||
if (this.fuzzUnicodeNoise) s = this.injectUnicodeNoise(s, rnd);
|
||||
if (this.fuzzWhitespace) s = this.whitespaceChaos(s, rnd);
|
||||
if (this.fuzzCasing) s = this.casingChaos(s, rnd);
|
||||
if (this.fuzzZalgo) { try { s = window.transforms.zalgo.func(s); } catch(_) {} }
|
||||
if (this.fuzzEncodeShuffle) s = this.encodeShuffle(s, rnd);
|
||||
out.push(s);
|
||||
}
|
||||
this.fuzzerOutputs = out;
|
||||
},
|
||||
copyAllFuzz: function() { this.copyToClipboard(this.fuzzerOutputs.join('\n')); },
|
||||
downloadFuzz: function() {
|
||||
const lines = this.fuzzerOutputs.map((s, i) => `#${i+1}\t${s}`).join('\n');
|
||||
const header = `# Parseltongue Fuzzer Output\n# count=${this.fuzzerOutputs.length}\n# seed=${this.fuzzerSeed || ''}\n# strategies=${[
|
||||
this.fuzzUseRandomMix?'randomMix':null,
|
||||
this.fuzzZeroWidth?'zeroWidth':null,
|
||||
this.fuzzUnicodeNoise?'unicodeNoise':null,
|
||||
this.fuzzWhitespace?'whitespace':null,
|
||||
this.fuzzCasing?'casing':null,
|
||||
this.fuzzZalgo?'zalgo':null,
|
||||
this.fuzzEncodeShuffle?'encodeShuffle':null
|
||||
].filter(Boolean).join(',')}\n`;
|
||||
const blob = new Blob([header + lines + '\n'], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a'); a.href = url; a.download = 'fuzz_cases.txt'; a.click();
|
||||
setTimeout(()=>URL.revokeObjectURL(url), 200);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = MutationTool;
|
||||
} else {
|
||||
window.MutationTool = MutationTool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* Splitter Tool - Split text into multiple copyable messages
|
||||
*/
|
||||
class SplitterTool extends Tool {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'splitter',
|
||||
name: 'Splitter',
|
||||
icon: 'fa-grip-lines',
|
||||
title: 'Split text into multiple copyable messages',
|
||||
order: 7
|
||||
});
|
||||
}
|
||||
|
||||
getVueData() {
|
||||
return {
|
||||
// Message Splitter Tab
|
||||
splitterInput: '',
|
||||
splitterMode: 'word', // 'chunk' or 'word' - default to word
|
||||
splitterChunkSize: 6,
|
||||
splitterWordSplitSide: 'left', // 'left' or 'right' for even-length words
|
||||
splitterWordSkip: 0, // number of words to skip between splits
|
||||
splitterMinWordLength: 2, // minimum word length to consider for splitting (skip shorter words)
|
||||
splitterSplitFirstWord: true, // whether to split the first word (true) or keep it whole (false)
|
||||
splitterCopyAsSingleLine: false, // copy as single line (true) or multiline (false)
|
||||
splitterTransforms: [''], // array of transform names to apply in sequence (start with one empty slot)
|
||||
splitterStartWrap: '',
|
||||
splitterEndWrap: '',
|
||||
splitMessages: []
|
||||
};
|
||||
}
|
||||
|
||||
getVueMethods() {
|
||||
return {
|
||||
/**
|
||||
* Set encapsulation start and end strings
|
||||
* @param {string} start - The start string
|
||||
* @param {string} end - The end string
|
||||
*/
|
||||
setEncapsulation(start, end) {
|
||||
this.splitterStartWrap = start;
|
||||
this.splitterEndWrap = end;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle transform change - auto-add next dropdown or collapse consecutive Nones
|
||||
* @param {number} index - The index of the transformation that changed
|
||||
*/
|
||||
handleTransformChange(index) {
|
||||
const value = this.splitterTransforms[index];
|
||||
|
||||
if (value && value !== '') {
|
||||
// Transform was selected - add next dropdown if it doesn't exist
|
||||
if (index === this.splitterTransforms.length - 1) {
|
||||
this.splitterTransforms.push('');
|
||||
}
|
||||
} else {
|
||||
// Transform was set to None
|
||||
// Check if previous dropdown is also None - if so, remove current one and collapse from previous position
|
||||
if (index > 0) {
|
||||
const prev = this.splitterTransforms[index - 1];
|
||||
if (!prev || prev === '') {
|
||||
// Collapse: remove this dropdown
|
||||
this.splitterTransforms.splice(index, 1);
|
||||
}
|
||||
} else if (index === 0 && this.splitterTransforms.length === 1) {
|
||||
// Only one dropdown and it's set to None - keep it as the starting dropdown
|
||||
// Do nothing
|
||||
} else if (index === 0 && this.splitterTransforms.length > 1) {
|
||||
// First dropdown set to None, check if next is also None
|
||||
const next = this.splitterTransforms[1];
|
||||
if (!next || next === '') {
|
||||
// Remove the first one
|
||||
this.splitterTransforms.splice(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure there's always at least one dropdown
|
||||
if (this.splitterTransforms.length === 0) {
|
||||
this.splitterTransforms = [''];
|
||||
}
|
||||
|
||||
// Force Vue to update
|
||||
this.$forceUpdate();
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate split messages from input text
|
||||
* Supports two modes: character chunks or split words in half
|
||||
*/
|
||||
generateSplitMessages() {
|
||||
// Clear previous output at the start
|
||||
this.splitMessages = [];
|
||||
|
||||
const input = this.splitterInput;
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
let chunks = [];
|
||||
|
||||
if (this.splitterMode === 'chunk') {
|
||||
// Character chunk mode
|
||||
const chunkSize = Math.max(1, Math.min(500, this.splitterChunkSize || 6));
|
||||
for (let i = 0; i < input.length; i += chunkSize) {
|
||||
chunks.push(input.slice(i, i + chunkSize));
|
||||
}
|
||||
} else if (this.splitterMode === 'word') {
|
||||
// Word split mode - creates messages with pattern: secondHalf + wholeWords + firstHalf
|
||||
// IMPORTANT: ALL words must be included in output, never filtered out
|
||||
const words = input.match(/\S+/g) || [];
|
||||
if (words.length === 0) return;
|
||||
|
||||
const skipCount = Math.max(0, Math.min(20, this.splitterWordSkip || 0));
|
||||
const minLength = Math.max(1, this.splitterMinWordLength || 2);
|
||||
|
||||
// Process all words - only split words that meet minimum length
|
||||
// Short words are kept whole but still included in the pattern
|
||||
let wordsToProcess = words;
|
||||
let prependToFirst = [];
|
||||
|
||||
// Handle "Split First Word" option
|
||||
if (!this.splitterSplitFirstWord && words.length > 0) {
|
||||
prependToFirst = [words[0]];
|
||||
wordsToProcess = words.slice(1);
|
||||
}
|
||||
|
||||
// Build word processing array - track which words can be split vs kept whole
|
||||
const wordData = wordsToProcess.map((word, idx) => {
|
||||
const canSplit = word.length >= minLength && word.length > 1;
|
||||
return {
|
||||
word: word,
|
||||
canSplit: canSplit,
|
||||
index: idx
|
||||
};
|
||||
});
|
||||
|
||||
// Determine which words to split (only words that can be split)
|
||||
const splittableWords = wordData.filter(w => w.canSplit);
|
||||
if (splittableWords.length === 0) {
|
||||
// No words can be split, output everything as one message
|
||||
chunks.push([...prependToFirst, ...wordsToProcess].join(' '));
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine split pattern based on splittable words only
|
||||
const splitIndexes = new Set();
|
||||
for (let i = 0; i < splittableWords.length; i++) {
|
||||
if ((i % (skipCount + 1)) === 0) {
|
||||
splitIndexes.add(splittableWords[i].index);
|
||||
}
|
||||
}
|
||||
|
||||
// Process all words and build split structure
|
||||
const processedWords = wordData.map((wd, idx) => {
|
||||
if (splitIndexes.has(idx) && wd.canSplit) {
|
||||
// Split this word
|
||||
let splitPos;
|
||||
if (wd.word.length % 2 === 0) {
|
||||
splitPos = wd.word.length / 2;
|
||||
} else {
|
||||
splitPos = this.splitterWordSplitSide === 'left'
|
||||
? Math.ceil(wd.word.length / 2)
|
||||
: Math.floor(wd.word.length / 2);
|
||||
}
|
||||
return {
|
||||
firstHalf: wd.word.slice(0, splitPos),
|
||||
secondHalf: wd.word.slice(splitPos),
|
||||
split: true
|
||||
};
|
||||
}
|
||||
// Keep whole (either too short or skipped)
|
||||
return { whole: wd.word, split: false };
|
||||
});
|
||||
|
||||
// Build output messages
|
||||
let currentMessage = [...prependToFirst];
|
||||
let messageStarted = false;
|
||||
|
||||
for (let i = 0; i < processedWords.length; i++) {
|
||||
const item = processedWords[i];
|
||||
|
||||
if (item.split) {
|
||||
if (!messageStarted) {
|
||||
// First split word - add first half to current message
|
||||
currentMessage.push(item.firstHalf);
|
||||
chunks.push(currentMessage.join(' '));
|
||||
currentMessage = [item.secondHalf];
|
||||
messageStarted = true;
|
||||
} else {
|
||||
// Add first half to current message, then start new message with second half
|
||||
currentMessage.push(item.firstHalf);
|
||||
chunks.push(currentMessage.join(' '));
|
||||
currentMessage = [item.secondHalf];
|
||||
}
|
||||
} else {
|
||||
// Whole word - add to current message (ALL words included)
|
||||
currentMessage.push(item.whole);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any remaining message
|
||||
if (currentMessage.length > 0) {
|
||||
chunks.push(currentMessage.join(' '));
|
||||
}
|
||||
}
|
||||
|
||||
// Apply transformations in sequence (chaining)
|
||||
let processedChunks = chunks;
|
||||
if (this.splitterTransforms && this.splitterTransforms.length > 0) {
|
||||
// Filter out empty transforms
|
||||
const activeTransforms = this.splitterTransforms.filter(t => t && t !== '');
|
||||
|
||||
if (activeTransforms.length > 0) {
|
||||
// Apply each transformation in sequence
|
||||
for (const transformName of activeTransforms) {
|
||||
const selectedTransform = this.transforms.find(t => t.name === transformName);
|
||||
if (selectedTransform && selectedTransform.func) {
|
||||
processedChunks = processedChunks.map(chunk => {
|
||||
try {
|
||||
return selectedTransform.func(chunk);
|
||||
} catch (e) {
|
||||
console.error('Transform error:', e);
|
||||
return chunk;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply encapsulation
|
||||
const start = this.splitterStartWrap || '';
|
||||
const end = this.splitterEndWrap || '';
|
||||
this.splitMessages = processedChunks.map(chunk => `${start}${chunk}${end}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy all split messages to clipboard
|
||||
* Single line: merges messages into one continuous string (keeps encapsulation/transformations)
|
||||
* Multiline: copies messages separated by newlines
|
||||
*/
|
||||
copyAllSplitMessages() {
|
||||
if (this.splitMessages.length === 0) return;
|
||||
|
||||
if (this.splitterCopyAsSingleLine) {
|
||||
// Merge all messages back together, keeping encapsulation and transformations
|
||||
// Just join without newlines - all encapsulation/transformations are already in splitMessages
|
||||
const merged = this.splitMessages.join('');
|
||||
this.copyToClipboard(merged);
|
||||
} else {
|
||||
// Copy all messages separated by newlines
|
||||
const allMessages = this.splitMessages.join('\n');
|
||||
this.copyToClipboard(allMessages);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = SplitterTool;
|
||||
} else {
|
||||
window.SplitterTool = SplitterTool;
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* Tokenade Tool - Token bomb generator tool
|
||||
* Note: This is a complex tool, so we'll include the key methods
|
||||
*/
|
||||
class TokenadeTool extends Tool {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'tokenade',
|
||||
name: 'Tokenade',
|
||||
icon: 'fa-bomb',
|
||||
title: 'Tokenade Generator',
|
||||
order: 4
|
||||
});
|
||||
}
|
||||
|
||||
getVueData() {
|
||||
return {
|
||||
tbDepth: 3,
|
||||
tbBreadth: 4,
|
||||
tbRepeats: 5,
|
||||
tbSeparator: 'zwnj',
|
||||
tbIncludeVS: true,
|
||||
tbIncludeNoise: true,
|
||||
tbRandomizeEmojis: true,
|
||||
tbAutoCopy: true,
|
||||
tbSingleCarrier: true,
|
||||
tbCarrier: '',
|
||||
tbPayloadEmojis: [],
|
||||
tokenBombOutput: '',
|
||||
tpBase: '',
|
||||
tpRepeat: 100,
|
||||
tpCombining: true,
|
||||
tpZW: false,
|
||||
textPayload: '',
|
||||
dangerThresholdTokens: 25_000_000,
|
||||
quickCarrierEmojis: ['🐍','🐉','🐲','🔥','💥','🗿','⚓','⭐','✨','🚀','💀','🪨','🍃','🪶','🔮','🐢','🐊','🦎'],
|
||||
tbCarrierManual: '',
|
||||
carrierEmojiList: [...(window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : [])]
|
||||
};
|
||||
}
|
||||
|
||||
getVueMethods() {
|
||||
return {
|
||||
generateTokenBomb: function() {
|
||||
const depth = Math.max(1, Math.min(8, Number(this.tbDepth) || 1));
|
||||
const breadth = Math.max(1, Math.min(10, Number(this.tbBreadth) || 1));
|
||||
const repeats = Math.max(1, Math.min(50, Number(this.tbRepeats) || 1));
|
||||
const sep = this.tbSeparator === 'zwj' ? '\u200D' : this.tbSeparator === 'zwnj' ? '\u200C' : this.tbSeparator === 'zwsp' ? '\u200B' : '';
|
||||
const includeVS = !!this.tbIncludeVS;
|
||||
const includeNoise = !!this.tbIncludeNoise;
|
||||
const randomize = !!this.tbRandomizeEmojis;
|
||||
|
||||
const emojiList = (this.carrierEmojiList && this.carrierEmojiList.length) ? this.carrierEmojiList :
|
||||
(window.EmojiUtils ? window.EmojiUtils.getAllEmojis() : this.quickCarrierEmojis);
|
||||
|
||||
function pickEmojis(count) {
|
||||
const out = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const idx = randomize ? Math.floor(Math.random() * emojiList.length) : (i % emojiList.length);
|
||||
out.push(String(emojiList[idx]));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function addVS(str) {
|
||||
if (!includeVS) return str;
|
||||
// Alternate VS16/VS15 to maximize tokenization churn
|
||||
const vs16 = '\uFE0F';
|
||||
const vs15 = '\uFE0E';
|
||||
let out = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const ch = str[i];
|
||||
out += ch + (i % 2 === 0 ? vs16 : vs15);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function noise() {
|
||||
if (!includeNoise) return '';
|
||||
const parts = ['\u200B','\u200C','\u200D','\u2060','\u2062','\u2063'];
|
||||
let s = '';
|
||||
const n = 1 + Math.floor(Math.random() * 3);
|
||||
for (let i = 0; i < n; i++) s += parts[Math.floor(Math.random() * parts.length)];
|
||||
return s;
|
||||
}
|
||||
|
||||
function buildLevel(level) {
|
||||
if (level === 0) {
|
||||
const base = pickEmojis(breadth).join('');
|
||||
return addVS(base);
|
||||
}
|
||||
const items = [];
|
||||
for (let i = 0; i < breadth; i++) {
|
||||
const inner = buildLevel(level - 1);
|
||||
items.push(inner + noise());
|
||||
}
|
||||
return items.join(sep);
|
||||
}
|
||||
|
||||
if (this.tbSingleCarrier) {
|
||||
const manual = (this.tbCarrierManual || '').trim();
|
||||
const carrier = manual || (this.tbCarrier && String(this.tbCarrier)) || (this.selectedEmoji ? String(this.selectedEmoji) : '💥');
|
||||
function countUnits(level) {
|
||||
if (level === 0) return breadth;
|
||||
return breadth * countUnits(level - 1);
|
||||
}
|
||||
const unitsPerBlock = countUnits(depth - 1);
|
||||
const totalUnits = Math.max(1, repeats * unitsPerBlock);
|
||||
|
||||
let payload = [];
|
||||
payload = pickEmojis(totalUnits);
|
||||
|
||||
function toTagSeqForEmojiChar(ch) {
|
||||
const cp = ch.codePointAt(0);
|
||||
const hex = cp.toString(16);
|
||||
let seq = '';
|
||||
for (const d of hex) {
|
||||
if (d >= '0' && d <= '9') {
|
||||
const base = 0xE0030 + (d.charCodeAt(0) - '0'.charCodeAt(0));
|
||||
seq += String.fromCodePoint(base);
|
||||
} else {
|
||||
const base = 0xE0061 + (d.charCodeAt(0) - 'a'.charCodeAt(0));
|
||||
seq += String.fromCodePoint(base);
|
||||
}
|
||||
}
|
||||
seq += String.fromCodePoint(0xE007F);
|
||||
return seq;
|
||||
}
|
||||
|
||||
const vs16 = includeVS ? '\uFE0F' : '';
|
||||
let out = carrier + vs16;
|
||||
for (let i = 0; i < payload.length; i++) {
|
||||
out += sep + toTagSeqForEmojiChar(payload[i]) + noise();
|
||||
}
|
||||
this.tokenBombOutput = out;
|
||||
} else {
|
||||
let block = buildLevel(depth - 1);
|
||||
// Repeat the block to increase token length
|
||||
const blocks = [];
|
||||
for (let i = 0; i < repeats; i++) {
|
||||
blocks.push(block + noise());
|
||||
}
|
||||
this.tokenBombOutput = blocks.join(sep);
|
||||
}
|
||||
|
||||
// Auto-copy if enabled
|
||||
if (this.tbAutoCopy && this.tokenBombOutput) {
|
||||
this.$nextTick(() => {
|
||||
this.forceCopyToClipboard(this.tokenBombOutput);
|
||||
this.showNotification('Tokenade generated and copied!', 'success', 'fas fa-bomb');
|
||||
});
|
||||
} else {
|
||||
this.showNotification('Tokenade generated!', 'success', 'fas fa-bomb');
|
||||
}
|
||||
},
|
||||
applyTokenadePreset: function(preset) {
|
||||
if (preset === 'feather') {
|
||||
this.tbDepth = 1; this.tbBreadth = 3; this.tbRepeats = 2; this.tbSeparator = 'zwnj';
|
||||
this.tbIncludeVS = false; this.tbIncludeNoise = false; this.tbRandomizeEmojis = true;
|
||||
} else if (preset === 'light') {
|
||||
this.tbDepth = 2; this.tbBreadth = 3; this.tbRepeats = 3; this.tbSeparator = 'zwnj';
|
||||
this.tbIncludeVS = false; this.tbIncludeNoise = true; this.tbRandomizeEmojis = true;
|
||||
} else if (preset === 'middle') {
|
||||
this.tbDepth = 3; this.tbBreadth = 4; this.tbRepeats = 6; this.tbSeparator = 'zwnj';
|
||||
this.tbIncludeVS = true; this.tbIncludeNoise = true; this.tbRandomizeEmojis = true;
|
||||
} else if (preset === 'heavy') {
|
||||
this.tbDepth = 4; this.tbBreadth = 6; this.tbRepeats = 12; this.tbSeparator = 'zwnj';
|
||||
this.tbIncludeVS = true; this.tbIncludeNoise = true; this.tbRandomizeEmojis = true;
|
||||
} else if (preset === 'super') {
|
||||
this.tbDepth = 5; this.tbBreadth = 8; this.tbRepeats = 18; this.tbSeparator = 'zwnj';
|
||||
this.tbIncludeVS = true; this.tbIncludeNoise = true; this.tbRandomizeEmojis = true;
|
||||
}
|
||||
this.showNotification('Preset applied', 'success', 'fas fa-sliders-h');
|
||||
},
|
||||
estimateTokenadeLength: function() {
|
||||
const depth = Math.max(1, Math.min(8, Number(this.tbDepth) || 1));
|
||||
const breadth = Math.max(1, Math.min(10, Number(this.tbBreadth) || 1));
|
||||
const repeats = Math.max(1, Math.min(50, Number(this.tbRepeats) || 1));
|
||||
const sepLen = this.tbSeparator === 'none' ? 0 : 1;
|
||||
const vsPerEmoji = this.tbIncludeVS ? 1 : 0;
|
||||
const noiseAvg = this.tbIncludeNoise ? 2 : 0;
|
||||
|
||||
function lenLevel(level) {
|
||||
if (level === 0) {
|
||||
return breadth * (1 + vsPerEmoji);
|
||||
}
|
||||
const inner = lenLevel(level - 1);
|
||||
return breadth * (inner + noiseAvg) + Math.max(0, breadth - 1) * sepLen;
|
||||
}
|
||||
|
||||
if (this.tbSingleCarrier) {
|
||||
function countUnits(level) { return level === 0 ? breadth : breadth * countUnits(level - 1); }
|
||||
const unitsPerBlock = countUnits(depth - 1);
|
||||
const totalUnits = Math.max(1, repeats * unitsPerBlock);
|
||||
const avgDigits = 5;
|
||||
const perUnit = avgDigits + 1 + sepLen + (this.tbIncludeNoise ? 2 : 0);
|
||||
const carrierLen = 1 + (this.tbIncludeVS ? 1 : 0);
|
||||
return carrierLen + totalUnits * perUnit;
|
||||
} else {
|
||||
const blockLen = lenLevel(depth - 1);
|
||||
return repeats * (blockLen + noiseAvg) + Math.max(0, repeats - 1) * sepLen;
|
||||
}
|
||||
},
|
||||
estimateTokenadeTokens: function() {
|
||||
return Math.max(0, this.estimateTokenadeLength());
|
||||
},
|
||||
setCarrierFromSelected: function() {
|
||||
if (this.selectedEmoji) this.tbCarrier = String(this.selectedEmoji);
|
||||
},
|
||||
generateTextPayload: function() {
|
||||
const base = String(this.tpBase || 'A');
|
||||
const count = Math.max(1, Math.min(10000, Number(this.tpRepeat) || 1));
|
||||
const combining = this.tpCombining;
|
||||
const addZW = this.tpZW;
|
||||
const marks = ['\u0301','\u0300','\u0302','\u0303','\u0308','\u0307','\u0304'];
|
||||
const zw = ['\u200B','\u200C','\u200D','\u2060'];
|
||||
let out = '';
|
||||
for (let i=0;i<count;i++) {
|
||||
let token = base;
|
||||
if (combining) {
|
||||
const m = marks[i % marks.length];
|
||||
token += m;
|
||||
}
|
||||
if (addZW) {
|
||||
const z = zw[i % zw.length];
|
||||
token += z;
|
||||
}
|
||||
out += token;
|
||||
}
|
||||
this.textPayload = out;
|
||||
this.showNotification('Text payload generated', 'success', 'fas fa-bomb');
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = TokenadeTool;
|
||||
} else {
|
||||
window.TokenadeTool = TokenadeTool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Tokenizer Tool - Tokenizer visualization tool
|
||||
*/
|
||||
class TokenizerTool extends Tool {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'tokenizer',
|
||||
name: 'Tokenizer',
|
||||
icon: 'fa-layer-group',
|
||||
title: 'Tokenizer visualization',
|
||||
order: 6
|
||||
});
|
||||
}
|
||||
|
||||
getVueData() {
|
||||
return {
|
||||
tokenizerInput: '',
|
||||
tokenizerEngine: 'byte',
|
||||
tokenizerTokens: [],
|
||||
tokenizerCharCount: 0,
|
||||
tokenizerWordCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
getVueMethods() {
|
||||
return {
|
||||
runTokenizer: async function() {
|
||||
const text = this.tokenizerInput || '';
|
||||
const engine = this.tokenizerEngine;
|
||||
const tokens = [];
|
||||
if (!text) { this.tokenizerTokens = []; this.tokenizerCharCount = 0; this.tokenizerWordCount = 0; return; }
|
||||
if (engine === 'byte') {
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(text);
|
||||
for (let i=0;i<bytes.length;i++) {
|
||||
tokens.push({ id: bytes[i], text: `0x${bytes[i].toString(16).padStart(2,'0')}` });
|
||||
}
|
||||
} else if (engine === 'word') {
|
||||
const parts = text.split(/(\s+|[\.,!?:;()\[\]{}])/);
|
||||
for (const p of parts) { if (p) tokens.push({ text: p }); }
|
||||
} else if (['cl100k','o200k','p50k','r50k'].includes(engine)) {
|
||||
try {
|
||||
if (!window.gptTok) {
|
||||
window.gptTok = await import('https://cdn.jsdelivr.net/npm/gpt-tokenizer@2/+esm');
|
||||
}
|
||||
const map = { cl100k: 'cl100k_base', o200k: 'o200k_base', p50k: 'p50k_base', r50k: 'r50k_base' };
|
||||
const enc = map[engine];
|
||||
const ids = window.gptTok.encode(text, enc);
|
||||
for (const id of ids) {
|
||||
const piece = window.gptTok.decode([id], enc);
|
||||
tokens.push({ id, text: piece });
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load/use gpt-tokenizer; falling back to bytes', e);
|
||||
this.tokenizerEngine = 'byte';
|
||||
return this.runTokenizer();
|
||||
}
|
||||
} else {
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(text);
|
||||
for (let i=0;i<bytes.length;i++) tokens.push({ id: bytes[i], text: `0x${bytes[i].toString(16).padStart(2,'0')}` });
|
||||
}
|
||||
this.tokenizerTokens = tokens;
|
||||
this.tokenizerCharCount = Array.from(text).length;
|
||||
const wordMatches = text.trim().match(/[^\s]+/g) || [];
|
||||
this.tokenizerWordCount = wordMatches.length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getVueWatchers() {
|
||||
return {
|
||||
tokenizerInput() {
|
||||
this.runTokenizer();
|
||||
},
|
||||
tokenizerEngine() {
|
||||
this.runTokenizer();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onActivate(vueInstance) {
|
||||
vueInstance.$nextTick(() => vueInstance.runTokenizer());
|
||||
}
|
||||
}
|
||||
|
||||
// Export
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = TokenizerTool;
|
||||
} else {
|
||||
window.TokenizerTool = TokenizerTool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Base Tool Class
|
||||
* All tools should inherit from this class and implement required methods
|
||||
*/
|
||||
class Tool {
|
||||
constructor(config) {
|
||||
// Required properties
|
||||
this.id = config.id; // Unique identifier (e.g., 'transforms', 'decoder')
|
||||
this.name = config.name; // Display name (e.g., 'Transform', 'Decoder')
|
||||
this.icon = config.icon || 'fa-circle'; // Font Awesome icon class
|
||||
this.title = config.title || this.name; // Tooltip/title text
|
||||
|
||||
// Optional properties
|
||||
this.order = config.order || 999; // Order in tab bar (lower = earlier)
|
||||
this.enabled = config.enabled !== false; // Whether tool is enabled
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Vue data properties needed for this tool
|
||||
* Should return an object that will be merged into Vue's data
|
||||
* @returns {Object}
|
||||
*/
|
||||
getVueData() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Vue methods needed for this tool
|
||||
* Should return an object with method definitions
|
||||
* @returns {Object}
|
||||
*/
|
||||
getVueMethods() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Vue watchers needed for this tool
|
||||
* Should return an object with watcher definitions
|
||||
* @returns {Object}
|
||||
*/
|
||||
getVueWatchers() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Vue lifecycle hooks
|
||||
* Should return an object with lifecycle methods (mounted, created, etc.)
|
||||
* @returns {Object}
|
||||
*/
|
||||
getVueLifecycle() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML template for the tab button
|
||||
* @returns {String} HTML string for the tab button
|
||||
*/
|
||||
getTabButtonHTML() {
|
||||
return `
|
||||
<button
|
||||
:class="{ active: activeTab === '${this.id}' }"
|
||||
@click="switchToTab('${this.id}')"
|
||||
title="${this.title}"
|
||||
>
|
||||
<i class="fas ${this.icon}"></i> ${this.name}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize tool-specific functionality
|
||||
* Called when the tool's tab is activated
|
||||
* @param {Vue} vueInstance - The Vue app instance
|
||||
*/
|
||||
onActivate(vueInstance) {
|
||||
// Override in subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup tool-specific functionality
|
||||
* Called when switching away from this tool's tab
|
||||
* @param {Vue} vueInstance - The Vue app instance
|
||||
*/
|
||||
onDeactivate(vueInstance) {
|
||||
// Override in subclasses
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other files
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = Tool;
|
||||
} else {
|
||||
window.Tool = Tool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Transform Tool - Text transformation tool
|
||||
*/
|
||||
class TransformTool extends Tool {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'transforms',
|
||||
name: 'Transform',
|
||||
icon: 'fa-font',
|
||||
title: 'Transform text (T)',
|
||||
order: 1
|
||||
});
|
||||
}
|
||||
|
||||
getVueData() {
|
||||
const transforms = (window.transforms && Object.keys(window.transforms).length > 0)
|
||||
? Object.entries(window.transforms).map(([key, transform]) => ({
|
||||
name: transform.name,
|
||||
func: transform.func.bind(transform),
|
||||
preview: transform.preview.bind(transform),
|
||||
reverse: transform.reverse ? transform.reverse.bind(transform) : null,
|
||||
category: transform.category || 'special'
|
||||
}))
|
||||
: [];
|
||||
|
||||
const categorySet = new Set();
|
||||
transforms.forEach(transform => {
|
||||
if (transform.category) {
|
||||
categorySet.add(transform.category);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort categories, but always put randomizer last
|
||||
const sortedCategories = Array.from(categorySet).sort((a, b) => {
|
||||
if (a === 'randomizer') return 1;
|
||||
if (b === 'randomizer') return -1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
return {
|
||||
transformInput: '',
|
||||
transformOutput: '',
|
||||
activeTransform: null,
|
||||
transforms: transforms,
|
||||
categories: sortedCategories
|
||||
};
|
||||
}
|
||||
|
||||
getVueMethods() {
|
||||
return {
|
||||
getDisplayCategory: function(transformName) {
|
||||
// Find transform by name and return its category property
|
||||
const transform = this.transforms.find(t => t.name === transformName);
|
||||
return transform ? transform.category : 'special';
|
||||
},
|
||||
getTransformsByCategory: function(category) {
|
||||
return this.transforms.filter(transform => transform.category === category);
|
||||
},
|
||||
isSpecialCategory: function(category) {
|
||||
return category === 'randomizer';
|
||||
},
|
||||
applyTransform: function(transform, event) {
|
||||
event && event.preventDefault();
|
||||
event && event.stopPropagation();
|
||||
|
||||
if (transform && transform.name === 'Random Mix') {
|
||||
this.triggerRandomizerChaos();
|
||||
}
|
||||
|
||||
if (this.transformInput) {
|
||||
this.activeTransform = transform;
|
||||
|
||||
if (transform.name === 'Random Mix') {
|
||||
this.transformOutput = window.transforms.randomizer.func(this.transformInput);
|
||||
const transformInfo = window.transforms.randomizer.getLastTransformInfo();
|
||||
if (transformInfo.length > 0) {
|
||||
const transformsList = transformInfo.map(t => t.transformName).join(', ');
|
||||
this.showNotification(`Mixed with: ${transformsList}`, 'success', 'fas fa-random');
|
||||
}
|
||||
} else {
|
||||
this.transformOutput = transform.func(this.transformInput);
|
||||
}
|
||||
|
||||
this.isTransformCopy = true;
|
||||
this.forceCopyToClipboard(this.transformOutput);
|
||||
|
||||
if (transform.name !== 'Random Mix') {
|
||||
this.showNotification(`${transform.name} applied and copied!`, 'success', 'fas fa-check');
|
||||
}
|
||||
|
||||
document.querySelectorAll('.transform-button').forEach(button => {
|
||||
button.classList.remove('active');
|
||||
});
|
||||
|
||||
const inputBox = document.querySelector('#transform-input');
|
||||
if (inputBox) {
|
||||
this.focusWithoutScroll(inputBox);
|
||||
const len = inputBox.value.length;
|
||||
try { inputBox.setSelectionRange(len, len); } catch (_) {}
|
||||
}
|
||||
|
||||
this.isTransformCopy = false;
|
||||
this.ignoreKeyboardEvents = false;
|
||||
}
|
||||
},
|
||||
autoTransform: function() {
|
||||
if (this.transformInput && this.activeTransform && this.activeTab === 'transforms') {
|
||||
const segments = window.EmojiUtils.splitEmojis(this.transformInput);
|
||||
const transformedSegments = segments.map(segment => {
|
||||
if (segment.length > 1 || /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}]/u.test(segment)) {
|
||||
return segment;
|
||||
}
|
||||
return this.activeTransform.func(segment);
|
||||
});
|
||||
this.transformOutput = window.EmojiUtils.joinEmojis(transformedSegments);
|
||||
}
|
||||
},
|
||||
initializeCategoryNavigation: function() {
|
||||
this.$nextTick(() => {
|
||||
const legendItems = document.querySelectorAll('.transform-category-legend .legend-item');
|
||||
legendItems.forEach(item => {
|
||||
const newItem = item.cloneNode(true);
|
||||
item.parentNode.replaceChild(newItem, item);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.transform-category-legend .legend-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const targetId = item.getAttribute('data-target');
|
||||
if (targetId) {
|
||||
const targetElement = document.getElementById(targetId);
|
||||
if (targetElement) {
|
||||
document.querySelectorAll('.transform-category-legend .legend-item').forEach(li => {
|
||||
li.classList.remove('active-category');
|
||||
});
|
||||
item.classList.add('active-category');
|
||||
|
||||
const inputSection = document.querySelector('.input-section');
|
||||
const inputSectionHeight = inputSection.offsetHeight;
|
||||
const elementPosition = targetElement.getBoundingClientRect().top + window.pageYOffset;
|
||||
const offsetPosition = elementPosition - inputSectionHeight - 10;
|
||||
|
||||
window.scrollTo({
|
||||
top: offsetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
|
||||
targetElement.classList.add('highlight-section');
|
||||
setTimeout(() => {
|
||||
targetElement.classList.remove('highlight-section');
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getVueWatchers() {
|
||||
return {
|
||||
transformInput() {
|
||||
if (this.activeTransform && this.activeTab === 'transforms') {
|
||||
this.transformOutput = this.activeTransform.func(this.transformInput);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getVueLifecycle() {
|
||||
return {
|
||||
mounted() {
|
||||
this.initializeCategoryNavigation();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onActivate(vueInstance) {
|
||||
vueInstance.$nextTick(() => {
|
||||
vueInstance.initializeCategoryNavigation();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Export
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = TransformTool;
|
||||
} else {
|
||||
window.TransformTool = TransformTool;
|
||||
}
|
||||
|
||||
|
||||
|
||||
-2481
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Clipboard Utility
|
||||
* Provides unified clipboard copy functionality using Clipboard API
|
||||
*/
|
||||
window.ClipboardUtils = {
|
||||
/**
|
||||
* Copy text to clipboard using Clipboard API
|
||||
* @param {string} text - Text to copy
|
||||
* @param {Object} options - Options object
|
||||
* @param {Function} options.onSuccess - Callback on success
|
||||
* @param {Function} options.onError - Callback on error
|
||||
* @param {boolean} options.suppressNotification - Don't show notification
|
||||
* @returns {Promise<boolean>} - Success status
|
||||
*/
|
||||
async copy(text, options = {}) {
|
||||
if (!text) return false;
|
||||
|
||||
const {
|
||||
onSuccess,
|
||||
onError,
|
||||
suppressNotification = false
|
||||
} = options;
|
||||
|
||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
const errorMsg = 'Clipboard API not available';
|
||||
console.error(errorMsg);
|
||||
if (!suppressNotification && window.NotificationUtils) {
|
||||
window.NotificationUtils.showNotification('Clipboard not supported', 'error', 'fas fa-exclamation-triangle');
|
||||
}
|
||||
if (onError) onError(new Error(errorMsg));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
if (!suppressNotification && window.NotificationUtils) {
|
||||
window.NotificationUtils.showNotification('Copied!', 'success', 'fas fa-check');
|
||||
}
|
||||
if (onSuccess) onSuccess();
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('Clipboard copy failed:', err);
|
||||
if (!suppressNotification && window.NotificationUtils) {
|
||||
window.NotificationUtils.showNotification('Copy failed', 'error', 'fas fa-exclamation-triangle');
|
||||
}
|
||||
if (onError) onError(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
window.EmojiUtils = {
|
||||
splitEmojis(text) {
|
||||
if (Intl.Segmenter) {
|
||||
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
||||
return Array.from(segmenter.segment(text), ({ segment }) => segment);
|
||||
}
|
||||
return Array.from(text);
|
||||
},
|
||||
|
||||
joinEmojis(emojis) {
|
||||
return emojis.join('');
|
||||
},
|
||||
|
||||
getAllEmojis() {
|
||||
if (!window.emojiData || typeof window.emojiData !== 'object') {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(window.emojiData).filter(key => {
|
||||
const value = window.emojiData[key];
|
||||
return typeof value === 'object' && value !== null && 'official' in value;
|
||||
});
|
||||
},
|
||||
|
||||
async getCompatibleEmojis(progressCallback) {
|
||||
const allEmojis = this.getAllEmojis();
|
||||
|
||||
if (window.emojiCompatibility && typeof window.emojiCompatibility.getCompatibleEmojis === 'function') {
|
||||
return await window.emojiCompatibility.getCompatibleEmojis(allEmojis, progressCallback);
|
||||
}
|
||||
return allEmojis;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
window.EscapeParser = {
|
||||
parseEscapeSequence(str) {
|
||||
if (!str || typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
|
||||
const escapeMap = {
|
||||
'\\u200B': '\u200B', // Zero Width Space
|
||||
'\\u200C': '\u200C', // Zero Width Non-Joiner
|
||||
'\\u200D': '\u200D', // Zero Width Joiner
|
||||
'\\u2060': '\u2060', // Word Joiner
|
||||
'\\uFE0E': '\uFE0E', // Variation Selector-15
|
||||
'\\uFE0F': '\uFE0F', // Variation Selector-16
|
||||
'\\n': '\n',
|
||||
'\\r': '\r',
|
||||
'\\t': '\t',
|
||||
'\\0': '\0',
|
||||
'\\\'': '\'',
|
||||
'\\"': '"',
|
||||
'\\\\': '\\'
|
||||
};
|
||||
|
||||
if (escapeMap[str] !== undefined) {
|
||||
return escapeMap[str];
|
||||
}
|
||||
|
||||
const unicodeMatch = str.match(/^\\u([0-9A-Fa-f]{4})$/);
|
||||
if (unicodeMatch) {
|
||||
return String.fromCharCode(parseInt(unicodeMatch[1], 16));
|
||||
}
|
||||
|
||||
const hexMatch = str.match(/^\\x([0-9A-Fa-f]{2})$/);
|
||||
if (hexMatch) {
|
||||
return String.fromCharCode(parseInt(hexMatch[1], 16));
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
window.FocusUtils = {
|
||||
focusWithoutScroll(element) {
|
||||
if (!element) return;
|
||||
|
||||
try {
|
||||
const scrollX = window.pageXOffset || window.scrollX || 0;
|
||||
const scrollY = window.pageYOffset || window.scrollY || 0;
|
||||
element.focus();
|
||||
window.scrollTo(scrollX, scrollY);
|
||||
} catch (e) {
|
||||
try {
|
||||
element.focus();
|
||||
} catch (err) {
|
||||
console.warn('Failed to focus element:', err);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
clearFocusAndSelection() {
|
||||
if (document.activeElement && document.activeElement.blur) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
if (window.getSelection) {
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
document.body.focus();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
window.HistoryUtils = {
|
||||
addToHistory(historyArray, maxItems, source, content) {
|
||||
if (!historyArray || !Array.isArray(historyArray)) {
|
||||
console.warn('HistoryUtils.addToHistory: historyArray is not an array');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = {
|
||||
source: source || 'Unknown',
|
||||
content: content,
|
||||
timestamp: new Date().toISOString(),
|
||||
id: Date.now() + Math.random()
|
||||
};
|
||||
|
||||
historyArray.unshift(entry);
|
||||
|
||||
if (historyArray.length > maxItems) {
|
||||
historyArray.splice(maxItems);
|
||||
}
|
||||
},
|
||||
|
||||
clearHistory(historyArray) {
|
||||
if (historyArray && Array.isArray(historyArray)) {
|
||||
// Use splice to remove all items (same approach as removeFromHistory)
|
||||
historyArray.splice(0, historyArray.length);
|
||||
}
|
||||
},
|
||||
|
||||
removeFromHistory(historyArray, id) {
|
||||
if (!historyArray || !Array.isArray(historyArray)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = historyArray.findIndex(item => item.id === id);
|
||||
if (index !== -1) {
|
||||
historyArray.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
getHistorySource(activeTab, context = {}) {
|
||||
if (activeTab === 'transforms' && context.activeTransform) {
|
||||
return `Transform: ${context.activeTransform.name}`;
|
||||
} else if (activeTab === 'steganography') {
|
||||
if (context.activeSteg === 'invisible') {
|
||||
return 'Invisible Text';
|
||||
} else if (context.selectedEmoji) {
|
||||
return `Emoji: ${context.selectedEmoji}`;
|
||||
}
|
||||
return 'Steganography';
|
||||
} else if (activeTab === 'transforms') {
|
||||
return 'Transform';
|
||||
}
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
window.NotificationUtils = {
|
||||
showNotification(message, type = 'success', iconClass = null) {
|
||||
const existing = document.querySelector('.copy-notification');
|
||||
if (existing) {
|
||||
existing.remove();
|
||||
}
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `copy-notification ${type || 'success'}`;
|
||||
|
||||
if (iconClass) {
|
||||
const icon = document.createElement('i');
|
||||
icon.className = iconClass;
|
||||
notification.appendChild(icon);
|
||||
}
|
||||
|
||||
const text = document.createElement('span');
|
||||
text.textContent = message;
|
||||
notification.appendChild(text);
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.classList.add('fade-out');
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
}, 300);
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
showCopiedPopup() {
|
||||
this.showNotification('Copied!', 'success', 'fas fa-check');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
window.ThemeUtils = {
|
||||
toggleTheme(currentTheme) {
|
||||
const newTheme = !currentTheme;
|
||||
|
||||
if (newTheme) {
|
||||
document.body.classList.add('dark-theme');
|
||||
document.body.classList.remove('light-theme');
|
||||
} else {
|
||||
document.body.classList.add('light-theme');
|
||||
document.body.classList.remove('dark-theme');
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem('theme', newTheme ? 'dark' : 'light');
|
||||
} catch (e) {
|
||||
console.warn('Failed to save theme preference:', e);
|
||||
}
|
||||
|
||||
return newTheme;
|
||||
},
|
||||
|
||||
initializeTheme() {
|
||||
try {
|
||||
const saved = localStorage.getItem('theme');
|
||||
if (saved === 'light') {
|
||||
return false;
|
||||
} else if (saved === 'dark') {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load theme preference:', e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user