Files
P4RS3LT0NGV3/src/transformers/cipher/porta.js
T

145 lines
6.2 KiB
JavaScript

// porta cipher transform
import BaseTransformer from '../BaseTransformer.js';
export default new BaseTransformer({
name: 'Porta Cipher',
priority: 60,
category: 'cipher',
key: 'KEY',
configurableOptions: [
{
id: 'key',
label: 'Keyword',
type: 'text',
default: 'KEY'
}
],
_key: function(options) {
const k = options && options.key !== undefined && options.key !== null
? String(options.key)
: null;
return (k || this.key || 'KEY').toUpperCase().replace(/[^A-Z]/g, '');
},
func: function(text, options) {
options = options || {};
const key = this._key(options);
if (key.length === 0) return text;
// Porta uses 13 reciprocal alphabets, each pair (A/B, C/D, etc.) shares a tableau
// Each tableau is reciprocal: if A->B in encode, then B->A in decode (same operation)
const tableaus = {
'A': 'NOPQRSTUVWXYZABCDEFGHIJKLM', 'B': 'NOPQRSTUVWXYZABCDEFGHIJKLM',
'C': 'OPQRSTUVWXYZABCDEFGHIJKLMN', 'D': 'OPQRSTUVWXYZABCDEFGHIJKLMN',
'E': 'PQRSTUVWXYZABCDEFGHIJKLMNO', 'F': 'PQRSTUVWXYZABCDEFGHIJKLMNO',
'G': 'QRSTUVWXYZABCDEFGHIJKLMNOP', 'H': 'QRSTUVWXYZABCDEFGHIJKLMNOP',
'I': 'RSTUVWXYZABCDEFGHIJKLMNOPQ', 'J': 'RSTUVWXYZABCDEFGHIJKLMNOPQ',
'K': 'STUVWXYZABCDEFGHIJKLMNOPQR', 'L': 'STUVWXYZABCDEFGHIJKLMNOPQR',
'M': 'TUVWXYZABCDEFGHIJKLMNOPQRS', 'N': 'TUVWXYZABCDEFGHIJKLMNOPQRS',
'O': 'UVWXYZABCDEFGHIJKLMNOPQRST', 'P': 'UVWXYZABCDEFGHIJKLMNOPQRST',
'Q': 'VWXYZABCDEFGHIJKLMNOPQRSTU', 'R': 'VWXYZABCDEFGHIJKLMNOPQRSTU',
'S': 'WXYZABCDEFGHIJKLMNOPQRSTUV', 'T': 'WXYZABCDEFGHIJKLMNOPQRSTUV',
'U': 'XYZABCDEFGHIJKLMNOPQRSTUVW', 'V': 'XYZABCDEFGHIJKLMNOPQRSTUVW',
'W': 'YZABCDEFGHIJKLMNOPQRSTUVWX', 'X': 'YZABCDEFGHIJKLMNOPQRSTUVWX',
'Y': 'ZABCDEFGHIJKLMNOPQRSTUVWXY', 'Z': 'ZABCDEFGHIJKLMNOPQRSTUVWXY'
};
let result = '';
let keyIndex = 0;
for (let i = 0; i < text.length; i++) {
const c = text[i];
const code = c.charCodeAt(0);
if (code >= 65 && code <= 90) { // Uppercase
const keyChar = key[keyIndex % key.length];
const tableau = tableaus[keyChar];
const plainPos = code - 65;
// Porta: cipher = tableau[plain]
result += tableau[plainPos];
keyIndex++;
} else if (code >= 97 && code <= 122) { // Lowercase
const keyChar = key[keyIndex % key.length];
const tableau = tableaus[keyChar];
const plainPos = code - 97;
// Porta: cipher = tableau[plain] (lowercase)
result += tableau[plainPos].toLowerCase();
keyIndex++;
} else {
result += c;
}
}
return result;
},
reverse: function(text, options) {
options = options || {};
const key = this._key(options);
if (key.length === 0) return text;
const tableaus = {
'A': 'NOPQRSTUVWXYZABCDEFGHIJKLM', 'B': 'NOPQRSTUVWXYZABCDEFGHIJKLM',
'C': 'OPQRSTUVWXYZABCDEFGHIJKLMN', 'D': 'OPQRSTUVWXYZABCDEFGHIJKLMN',
'E': 'PQRSTUVWXYZABCDEFGHIJKLMNO', 'F': 'PQRSTUVWXYZABCDEFGHIJKLMNO',
'G': 'QRSTUVWXYZABCDEFGHIJKLMNOP', 'H': 'QRSTUVWXYZABCDEFGHIJKLMNOP',
'I': 'RSTUVWXYZABCDEFGHIJKLMNOPQ', 'J': 'RSTUVWXYZABCDEFGHIJKLMNOPQ',
'K': 'STUVWXYZABCDEFGHIJKLMNOPQR', 'L': 'STUVWXYZABCDEFGHIJKLMNOPQR',
'M': 'TUVWXYZABCDEFGHIJKLMNOPQRS', 'N': 'TUVWXYZABCDEFGHIJKLMNOPQRS',
'O': 'UVWXYZABCDEFGHIJKLMNOPQRST', 'P': 'UVWXYZABCDEFGHIJKLMNOPQRST',
'Q': 'VWXYZABCDEFGHIJKLMNOPQRSTU', 'R': 'VWXYZABCDEFGHIJKLMNOPQRSTU',
'S': 'WXYZABCDEFGHIJKLMNOPQRSTUV', 'T': 'WXYZABCDEFGHIJKLMNOPQRSTUV',
'U': 'XYZABCDEFGHIJKLMNOPQRSTUVW', 'V': 'XYZABCDEFGHIJKLMNOPQRSTUVW',
'W': 'YZABCDEFGHIJKLMNOPQRSTUVWX', 'X': 'YZABCDEFGHIJKLMNOPQRSTUVWX',
'Y': 'ZABCDEFGHIJKLMNOPQRSTUVWXY', 'Z': 'ZABCDEFGHIJKLMNOPQRSTUVWXY'
};
let result = '';
let keyIndex = 0;
for (let i = 0; i < text.length; i++) {
const c = text[i];
const code = c.charCodeAt(0);
if (code >= 65 && code <= 90) { // Uppercase
const keyChar = key[keyIndex % key.length];
const tableau = tableaus[keyChar];
// Find position of ciphertext char in tableau - that's the plaintext position
const cipherChar = String.fromCharCode(code);
const plainPos = tableau.indexOf(cipherChar);
if (plainPos >= 0) {
result += String.fromCharCode(plainPos + 65);
} else {
result += c;
}
keyIndex++;
} else if (code >= 97 && code <= 122) { // Lowercase
const keyChar = key[keyIndex % key.length];
const tableau = tableaus[keyChar];
const cipherChar = String.fromCharCode(code - 32); // Convert to uppercase for lookup
const plainPos = tableau.indexOf(cipherChar);
if (plainPos >= 0) {
result += String.fromCharCode(plainPos + 97);
} else {
result += c;
}
keyIndex++;
} else {
result += c;
}
}
return result;
},
preview: function(text, options) {
if (!text) return '[porta]';
const result = this.func(text.slice(0, 8), options);
return result.substring(0, 10) + (result.length > 10 ? '...' : '');
},
detector: function(text) {
const cleaned = text.replace(/[\s.,!?;:'"()\-&0-9]/g, '');
if (cleaned.length < 5) return false;
const letterCount = (cleaned.match(/[a-zA-Z]/g) || []).length;
return letterCount / cleaned.length > 0.7;
}
});