From 5f3e9a96242473338391fccbf82395476e83fa46 Mon Sep 17 00:00:00 2001 From: Izai Alejandro Zalles Merino Date: Mon, 13 Oct 2025 15:13:17 -0400 Subject: [PATCH] feat(ciphers): add Dancing Men Encode/Decode ops (token-based char(n) format) with Magic detection; add to Encryption/Encoding category --- src/core/config/Categories.json | 2 + src/core/operations/DancingMenDecode.mjs | 76 +++++++++++++++++++ src/core/operations/DancingMenEncode.mjs | 97 ++++++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 src/core/operations/DancingMenDecode.mjs create mode 100644 src/core/operations/DancingMenEncode.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 61b6d64d..f6b5896b 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -130,6 +130,8 @@ "From Morse Code", "Bacon Cipher Encode", "Bacon Cipher Decode", + "Dancing Men Encode", + "Dancing Men Decode", "Bifid Cipher Encode", "Bifid Cipher Decode", "Caesar Box Cipher", diff --git a/src/core/operations/DancingMenDecode.mjs b/src/core/operations/DancingMenDecode.mjs new file mode 100644 index 00000000..258694c6 --- /dev/null +++ b/src/core/operations/DancingMenDecode.mjs @@ -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; diff --git a/src/core/operations/DancingMenEncode.mjs b/src/core/operations/DancingMenEncode.mjs new file mode 100644 index 00000000..b5346adb --- /dev/null +++ b/src/core/operations/DancingMenEncode.mjs @@ -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;