feat(ciphers): add Dancing Men Encode/Decode ops (token-based char(n) format) with Magic detection; add to Encryption/Encoding category

This commit is contained in:
Izai Alejandro Zalles Merino 2025-10-13 15:13:17 -04:00
parent eb684745fe
commit 5f3e9a9624
3 changed files with 175 additions and 0 deletions

View File

@ -130,6 +130,8 @@
"From Morse Code", "From Morse Code",
"Bacon Cipher Encode", "Bacon Cipher Encode",
"Bacon Cipher Decode", "Bacon Cipher Decode",
"Dancing Men Encode",
"Dancing Men Decode",
"Bifid Cipher Encode", "Bifid Cipher Encode",
"Bifid Cipher Decode", "Bifid Cipher Decode",
"Caesar Box Cipher", "Caesar Box Cipher",

View File

@ -0,0 +1,76 @@
/**
* @author Agent Mode
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
/**
* Dancing Men Decode operation
*
* Decodes textual Dancing Men tokens like char(97)..char(122) back to letters a-z.
* If a token is suffixed with '!' (flag), it can be interpreted as a word separator.
*/
class DancingMenDecode extends Operation {
/**
* DancingMenDecode constructor
*/
constructor() {
super();
this.name = "Dancing Men Decode";
this.module = "Ciphers";
this.description = "Decode Dancing Men token format (char(97)..char(122), optional ! for flags) back to text.";
this.infoURL = "https://www.dcode.fr/dancing-men-cipher";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Flags indicate spaces",
type: "boolean",
value: false
}
];
// Magic detection: sequence of 3+ char(ddd) tokens optionally with trailing '!'
this.checks = [
{
pattern: "^(?:\\s*char\\(\\d{2,3}\\)!?\\s*){3,}$",
args: [false],
useful: true
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [flagsAsSpaces] = args;
const tokenRe = /char\((\d{2,3})\)(!?)/g;
let out = "";
let lastIndex = 0;
let m;
while ((m = tokenRe.exec(input)) !== null) {
// Append any intermediary non-token text unchanged
if (m.index > lastIndex) {
out += input.slice(lastIndex, m.index);
}
const code = parseInt(m[1], 10);
let ch = "";
if (code >= 97 && code <= 122) ch = String.fromCharCode(code);
else if (code >= 65 && code <= 90) ch = String.fromCharCode(code).toLowerCase();
else ch = ""; // Unknown token range -> drop
out += ch;
if (flagsAsSpaces && m[2] === "!") out += " ";
lastIndex = tokenRe.lastIndex;
}
// Append any remainder
if (lastIndex < input.length) out += input.slice(lastIndex);
return out;
}
}
export default DancingMenDecode;

View File

@ -0,0 +1,97 @@
/**
* @author Agent Mode
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
/**
* Dancing Men Encode operation
*
* Encodes Latin letters a-z into textual Dancing Men tokens of the form char(97)..char(122).
* Optionally, spaces can be represented by a flag marker appended to the previous token ("!")
* to mimic the word-separator flag described in Conan Doyle's short story.
*/
class DancingMenEncode extends Operation {
/**
* DancingMenEncode constructor
*/
constructor() {
super();
this.name = "Dancing Men Encode";
this.module = "Ciphers";
this.description = "Encode plaintext to Dancing Men token format using tokens like char(97)..char(122). Optionally mark word boundaries with a flag (!).";
this.infoURL = "https://www.dcode.fr/dancing-men-cipher";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Use flags as word separators",
type: "boolean",
value: false
},
{
name: "Separator between tokens",
type: "option",
value: ["Space", "None"],
defaultIndex: 0
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [useFlags, sepChoice] = args;
const sep = sepChoice === "None" ? "" : " ";
const out = [];
let prevIdx = -1;
for (let i = 0; i < input.length; i++) {
const ch = input[i];
const code = ch.toLowerCase().charCodeAt(0);
if (code >= 97 && code <= 122) {
out.push(`char(${code})`);
prevIdx = out.length - 1;
} else if (ch === " ") {
if (useFlags && prevIdx >= 0) {
// Append a flag marker to the previous token to denote word boundary
out[prevIdx] = out[prevIdx] + "!";
} else {
// Represent space explicitly in the stream
out.push(" ");
prevIdx = -1;
}
} else if (ch === "\n" || ch === "\r" || ch === "\t") {
out.push(ch);
prevIdx = -1;
} else {
// Pass-through other characters as-is
out.push(ch);
prevIdx = -1;
}
}
// Join but preserve already injected spaces/newlines
// We only join char(...) tokens using the chosen separator
// Build final by inserting sep between adjacent char(...) tokens (and their optional !)
const tokens = [];
for (let i = 0; i < out.length; i++) {
const cur = out[i];
tokens.push(cur);
const curIsToken = /^char\(\d{2,3}\)!?$/.test(cur);
const next = out[i + 1];
const nextIsToken = typeof next === "string" && /^char\(\d{2,3}\)!?$/.test(next);
if (sep && curIsToken && nextIsToken) tokens.push(sep);
}
return tokens.join("");
}
}
export default DancingMenEncode;