13:37

Written by Vincent Bruijn

I’m not from the generation that really wrote leet online, nor can’t I say I master it. But nevertheless, as an alternative script for latin characters, 1337 or leet or eleet or 31337 speak opens up a lot of nice alternative ways of writing. With the wide support of UTF-8, the “traditional” ASCII-based leetspeak can be extended with UTF-8 quite easily.

My first 1337 PHP class dates from about 2008, which is 10 years ago. Since then I’ve learned a lot, and not only in programming of course. Let’s bring the old code to 2018, let’s JavaScriptify it!

How should you approach this endeavour? Let us first define the core target we want as a result. In other words, what would be the manual for the code we want to deliver?

A module that returns a leetified variant of a given string, with,
optionally, different results for identical givens, using different
alternatives per character.

I will start by running npm init from an empty directory named leet. The resulting package.json looks like this:

{
  "name": "leet",
  "version": "0.0.1",
  "description": "Leetify a given string",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Vincent Bruijn",
  "license": "MIT"
}

The basic directory layout will contain a index.js and a simple test stored in ./test.

$ touch index.js && mkdir test && touch test/main.test.js

Usually I don’t start with a test. I even have to admit that I write too few tests. But I also have to admit that I try to be better in life, so I will start with a test here. What we expect our module to do is about this:

const leet = require('../index');

const result = leet.leetify('abc', { predictive: true });

if (!(result === '@|3(')) {
  console.log('test fails');
} else {
  console.log('test passes');
}

On line 1, we require our newly created module. On the next line of code, we assume a function from our module to be named leetify which accepts a string and a configuration object. On line 5 we are checking wether the result of leet.leetify is equal to an outcome we expect. If not, we log a failure, else the test passes.

Note the second parameter. I just defined it to have a property called predictive, to make a distinction between a continuous random result and a predictive result. The latter is easier to test, so I will stick to that at the moment.

This test defines what the basic layout of our module should be in index.js. I think it is this:

module.exports.leetify = function (stringToLeet, config) {
  return 'abc';
};

Now we can run our first test:

$ node ./test/main.test.js
test fails

Well that was to be expected, since 'abc' !== '@|3('. So next step is to set up a dictionary to which each given character can be matched against, and let us then pick a leet alternative. The dictionary can look something like this, most substitutions are directly taken from my old school PHP class from 2008.

const dictionary = {
  a: ['@', '/-\\', '/\\', '4'],
  b: ['|3', '13', 'I3', '6', ']3', '!3', '(3', '/3', ')3', '8'],
  c: ['(', '[', '©', '<'],
  d: ['|)', '])', '[)', 'I>', '|>'],
  e: ['3'],
  f: ['|=', '/=', 'ph', 'PH'],
  g: ['6', '9', '(_-', '&', '(_+', 'C-'],
  h: ['|-|', ']-[', '[-]', '|~|', '#', ')-('],
  i: ['1', '|', '!'],
  j: ['_|', '_/'],
  k: ['|<', '|{'],
  l: ['|_', '1', '1_'],
  m: [
    '|/|',
    '//\\\\//\\\\',
    '|v|',
    ']V[',
    '//\\',
    '/|\\',
    '|V|',
    '//.',
    '.\\\\\\',
  ],
  n: ['||', '][', '//'],
  o: ['0', '()'],
  p: ['|?', '|>', '9', 'q'],
  q: ['(_),', '()_', 'O,', '0_'],
  r: ['2', '|~', '|2', '®'],
  s: ['5', '$', 'Z', 'z'],
  t: ['+', '7'],
  u: ['|_|', '(_)'],
  v: ['/', '\\\\\\//'],
  w: ['\\\\\\//\\\\\\//', '`//', '\\/\\/', "\\\\'", 'V/'],
  x: ['}{', '><'],
  y: ['`/'],
  z: ['2', '7_', '~/_'],
};

We define a constant named dictionary that contains 26 properties, one for every lower case letter of the alphabet, which has an array as value, containing one or more alternatives for the property name.

Next step is to extend our function to return a leetified string, paying respect to our configuration.

module.exports.leetify = function (stringToLeet, config) {
  let stringAsArray = stringToLeet.split('');

  stringAsArray = stringAsArray.map((element) => {
    const dictEntry = dictionary[element.toLowerCase()];
    if (!dictEntry) {
      return element;
    }
    const index =
      config && config.predictive
        ? 0
        : Math.floor(Math.random() * dictEntry.length);
    return dictEntry[index];
  });

  return stringAsArray.join('');
};

The first line of the above code is identical to what we’ve been setting up earlier on. The second line splits the given String into an Array of single characters. This makes it easy to map a function on each of them, and that’s exactly what we do the following lines.

Within the mapping function, I first force the character to lower case, making it easier to look up its alternatives in the dictionary. If no alternatives are found, we just return that exact character, think of space characters for example.

After retrieving the leet alternatives, we need to create either a random index, or a precise index, depending on our configuration. I decide here to always return the first, i.e. 0, index for the predictive configuration, as I know for sure that within the dictionary, all characters have at least one alternative. If there is no configuration object or the predicitive value is false, we create a random index based upon the length of the list of alternatives.

The mapping function will return one alternative from the character’s alternatives from the dictionary. Just befor returning the new string, we need to create on from the array of characters, by joining it with an empty string.

When you now run the test, it should pass!

$ node ./test/main.test.js
test passes

For the fun of it, add a last set of lines to your test to see some alternative outcomes.

for (let i = 0; i < 10; i++) {
  console.log(leet.leetify('Lorem ipsum dolor sit amet'));
}

This initiates a loop that wil run 10 times and will output a leetified version of the line Lorem ipsum dolor sit amet, which will probably be something similar to 1()|~3.\\\ 1q5(_).\\\ |>0|_()2 5!7 @|/|3+.

Rounding up, this is a simple first take on creating a NodeJS module with a test. In a next post, I will extend the dictionary with more character alternatives. Also I will show you how to prepare the module for reuse, as mature as an NPM package. Until then, I can only say: Bye bye!

8`/3 8`/3!