mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-09-04 09:10:58 +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,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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user