text: /**
   * Termy.js - An easily customisable web terminal interface.
   * ==========================================================
   * Copyright (c) 2017 - 2018 TheDragonRing <thedragonring.bod@gmail.com>, under the MIT License.
   */
  
  // Custom commands
  const commands = {
    // command name - STRING - e.g. hello
    time: {
      // command description - STRING - e.g. 'says hello.'
      description: 'displays the current time.',
      // run - FUNCTION - e.g. (args) => { $(`${container} .${commandCount - 1}`).append('Hello!') }
      run: (args) => {
        // Get what hour of the day it is.
        const date = new Date(),
          // Get what minute of the hour it is.
          m = (date.getMinutes() < 10) ? '0' + date.getMinutes() : date.getMinutes(),
          // Get what second of the minute it is.
          s = (date.getSeconds() < 10) ? '0' + date.getSeconds() : date.getSeconds(),
          // Get what meridiem of the day it is.
          meridiem = (date.getHours() > 11) ? 'PM' : 'AM',
          // Get the the date in dd/mm/yyyy format.
          dd = (date.getDate() < 10) ? '0' + date.getDate() : date.getDate(),
          mm = (date.getMonth() + 1 < 10) ? '0' + (date.getMonth() + 1) : date.getMonth() + 1,
          yyyy = date.getFullYear(),
          // Get the user's timezone.
          timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
          // Get the user's timezone's offset from GMT.
          hourOffset = (parseInt(Math.abs(date.getTimezoneOffset() / 60)) < 10)
            ? '0' + parseInt(Math.abs(date.getTimezoneOffset() / 60))
            : parseInt(Math.abs(date.getTimezoneOffset() / 60)),
          minOffset = (Math.abs(date.getTimezoneOffset() % 60) < 10)
            ? '0' + Math.abs(date.getTimezoneOffset() % 60)
            : Math.abs(date.getTimezoneOffset() % 60),
          gmt = (date.getTimezoneOffset() < 0)
            ? '+' + hourOffset + ':' + minOffset
            : ((date.getTimezoneOffset() > 0) ? '-' + hourOffset + ':' + minOffset : '00:00');
        // Get what hour of the day it is.
        h = (date.getHours() > 12) ? date.getHours() - 12 : date.getHours();
        if (h === 0) h = 12;
        // Display time details to user.
        $(`${container} .${commandCount - 1}`).append(`${h}:${m}:${s} ${meridiem},
          ${dd}/${mm}/${yyyy}, ${timezone} (GMT${gmt})`);
      }
    },
    // command name - STRING - e.g. hello
    '8ball': {
      // command description - STRING - e.g. 'says hello.'
      description: 'gives a random response to the input',
      // arguments to show as usage - STRING - e.g. '<required> [optional]'
      usage: '&lt;input&gt;',
      // run - FUNCTION - e.g. (args) => { $(`${ container }.${ commandCount - 1 }`).append('Hello!') }
      run: (args) => {
        if (args[0]) {
          let answers = [
            'Who cares?',
            'I\'m not sure... try asking again later!',
            'I\'m not telling! Ask me again and I might consider it...',
            'Uhhh... do you really want to know?',
            'No. Definitely not.',
            'Dot. Definitely dot.',
            'There\'s an easy answer to that - nope!',
            'I doubt it.',
            'Of course!',
            'Yeah...',
            'It is most likely that that is so.',
            'Is fire hot?'
          ],
            // reply with random answer
            x = Math.floor(Math.random() * answers.length);
          $(`${container} .${commandCount - 1}`)
            .append(`<strong>Q:</strong> ${$.makeArray(args).join(' ')}<br><strong>A:</strong> ${answers[x]}`);
        } else {
          $(`${container} .${commandCount - 1}`)
            .append('<span style="color: #f00">ERROR</span>: this command\
            should be executed as <i>8ball &lt;input&gt;</i>');
          command = '';
        }
      }
    },
    // command name - STRING - e.g. hello
    google: {
      // command description - STRING - e.g. 'says hello.'
      description: 'googles the command arguments.',
      // arguments to show as usage - STRING - e.g. '<required> [optional]'
      usage: '&lt;query&gt;',
      // whether or not the command uses Typed.js and prepares the prompt for
      // the next command itself - BOOLEEN - e.g. true
      typed: true,
      // run - FUNCTION - e.g. (args) => { $(`${ container }.${ commandCount - 1 }`).append('Hello!') }
      run: (args) => {
        if (args[0]) {
          exit(`https://google.com/search?q=${args.join('+')}`);
        } else {
          $(`${container} .${commandCount - 1}`)
            .append('<span style="color: #f00">ERROR</span>: this command\
            should be executed as <i>google &lt;query&gt;</i>');
          command = '';
        }
      }
    },
    // command name - STRING - e.g. hello
    sudo: {
      // whether or not to hide the command in the help menu - BOOLEEN - e.g. true
      hidden: true,
      // run - FUNCTION - e.g. (args) => { $(`${ container }.${ commandCount - 1 }`).append('Hello!') }
      run: (args) => {
        if (args.join(' ') === 'make me a sandwich') {
          // make a sandwhich
          $(`${container} .${commandCount - 1}`)
            .append(`Here you go: <i class="em em-sandwich"></i>`);
        } else {
          // pretend the command doesn't exist
          $(`${container} .${commandCount - 1}`)
            .append(`<span style="color: #f00">ERROR</span>: ${command}: command not found`);
        }
      }
    },
    // command name - STRING - e.g. hello
    make: {
      // whether or not to hide the command in the help menu - BOOLEEN - e.g. true
      hidden: true,
      // run - FUNCTION - e.g. (args) => { $(`${ container }.${ commandCount - 1 }`).append('Hello!') }
      run: (args) => {
        if (args.join(' ') === 'me a sandwich') {
          // refuse to make a sandwich
          $(`${container} .${commandCount - 1}`)
            .append(`No way. Do it yourself!`);
        } else {
          // pretend the command doesn't exist
          $(`${container} .${commandCount - 1}`)
            .append(`<span style="color: #f00">ERROR</span>: ${command}: command not found`);
        }
      }
    },
  };
  
  // Minified Termy
  var termy = (container, host, user, promptColour) => {
    if (!container) return;
    // Variables for later use
    let command,
      commandCount = 0,
      loggedIn = true;
    const data = {},
      // Default commands and settings
      defaults = {
        settings: {
          // user and host names
          // below settings will set it to visitor@site.com
          // if the window hostname is undefined, it'll show as visitor@example.domain
          user: 'visitor',
          host: window.location.hostname || 'example.domain',
          // colour of the ~$
          promptColour: '#00f'
        },
        // if setting is not neccessary set to false or remove
        commands: {
          // command name - STRING - e.g. hello
          help: {
            // command description - STRING - e.g. 'says hello.'
            description: 'displays all available commands.',
            // command aliases - ARRAY - e.g. ['hello', 'hi']
            aliases: ['?'],
            // run - FUNCTION - e.g. (args) => { $(`${container} .${commandCount - 1}`).append('Hello!') }
            run: (args) => {
              let help = underline('Available Commands:', '-');
              Object.getOwnPropertyNames(data.commands).forEach((val, idx, array) => {
                if (!data.commands[val].hidden)
                  help += `<br>${val}: ${data.commands[val].description}`;
              });
              $(`${container} .${commandCount - 1}`).append(help);
            }
          },
          // command name - STRING - e.g. hello
          man: {
            // command description - STRING - e.g. 'says hello.'
            description: 'shows detailed information about commands.',
            // arguments to show as usage - STRING - e.g. '<required> [optional]'
            usage: '&lt;command - run <i>help</i> to see all available&gt;',
            // run - FUNCTION - e.g. (args) => { $(`${container} .${commandCount - 1}`).append('Hello!') }
            runn: (args) => {
              let manual = false;
              Object.getOwnPropertyNames(data.commands).forEach((val, idx, array) => {
                if (!data.commands[val].hidden && val === args[0]) {
                  manual = underline('Manual: ' + val, '-');
                  if (data.commands[val]['description'])
                    manual += `<br> DESCRIPTION: ${data.commands[val]['description']}`;
                  if (data.commands[val]['usage'])
                    manual += `<br> USAGE: ${data.commands[val]['usage']}`;
                  if (data.commands[val]['info'])
                    manual += `<br> INFO: ${data.commands[val]['info']}`;
                }
              });
              // If a valid command was inputted, display manual page. Otherwise show error.
              if (manual) {
                $(`${container} .${commandCount - 1}`).append(manual);
              } else {
                $(`${container} .${commandCount - 1}`).append('<span style="color: #f00">ERROR</span>: this\
            command should be executed as <i>man &lt;command&gt;</i>: to see available commands run <i>help</i>');
              }
            }
          },
          // command name - STRING - e.g. hello
          clear: {
            // command description - STRING - e.g. 'says hello.'
            description: 'removes all previously run commands from the terminal.',
            // run - FUNCTION - e.g. (args) => { $(`${container} .${commandCount - 1}`).append('Hello!') }
            run: (args) => {
              $(`${container} .terminal`).empty();
            }
          },
          // command name - STRING - e.g. hello
          exit: {
            // command description - STRING - e.g. 'says hello.'
            description: 'logs out, to execute commands once again the page must be reloaded.',
            // arguments to show as usage - STRING - e.g. '<required> [optional]'
            usage: 'exit',
            // whether or not the command uses Typed.js and prepares the prompt for
            // the next command itself - BOOLEEN - e.g. true
            typed: true,
            // run - FUNCTION - e.g. (args) => { $(`${container} .${commandCount - 1}`).append('Hello!') }
            run: (args) => {
              exit();
            }
          }
        }
      };
  
    // Load Termy
    const load = () => {
      // Add neccessary CSS
      const styles = document.createElement('style');
      styles.setAttribute('id', `termyStyles-${container}`);
      styles.setAttribute('type', 'text/css');
      document.head.appendChild(styles);
      document.getElementById(`termyStyles-${container}`).innerHTML = `@import url(https://afeld.github.io/emoji-css/emoji.css);@import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono);${container}{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:#000;font-family:"Ubuntu Mono",monospace;padding:1rem;max-width:100%;overflow-wrap:break-word;overflow-x:hidden;font-size:1.1em}${container},${container} a{color:#fff}${container} a{text-decoration:none;font-weight:700}${container} button{font-family:"Ubuntu Mono",monospace;background:none;border:none;padding:0;margin:0;color:#fff;font-weight:700;font-size:inherit}${container} button:hover{cursor:pointer}${container} p{margin:0}${container} .cmd-input{font-size:inherit;font-family:"Ubuntu Mono",monospace;color:#fff;background:none;border:none;outline:none;padding:none;margin:none;width:100%;resize:none}`;
  
      // Add neccessary <div> elements
      if (container === 'body' || container === 'html')
        document.body.innerHTML = '<div class="init"></div><div class="terminal"></div>';
      if (container.charAt(0) === '.') {
        const elements = document.getElementsByClassName(container.slice(1));
        for (let i = 0; i < elements.length; i++) {
          elements[i].innerHTML = '<div class="init"></div><div class="terminal"></div>';
        }
      }
      if (container.charAt(0) === '#')
        document.getElementsById(container.slice(1)).innerHTML =
          '<div class="init"></div><div class="terminal"></div>';
      // Check for dependencies (jQuery, Typed.js, autosize)
      try {
        $;
      } catch (e) {
        if (container === 'body' || container === 'html')
          document.body.innerHTML =
            '<span style="color: #f00">ERROR</span>: missing dependency: <a href="http://jquery.com/">jQuery</a><br>';
        if (container.charAt(0) === '.') {
          const elements = document.getElementsByClassName(container.slice(1));
          for (let i = 0; i < elements.length; i++) {
            elements[i].innerHTML =
              '<span style="color: #f00">ERROR</span>: missing dependency: <a href="http://jquery.com/">jQuery</a><br>';
          }
        }
        if (container.charAt(0) === '#')
          document.getElementsById(container.slice(1)).innerHTML =
            '<span style="color: #f00">ERROR</span>: missing dependency: <a href="http://jquery.com/">jQuery</a><br>';
      }
      try {
        Typed;
      } catch (e) {
        if (container === 'body' || container === 'html')
          document.body.innerHTML = `${document.body.innerHTML} <span style="color: #f00">ERROR</span>: missing dependency: <a href="https://mattboldt.com/typed.js/">Typed.js</a><br>`;
        if (container.charAt(0) === '.') {
          const elements = document.getElementsByClassName(container.slice(1));
          for (let i = 0; i < elements.length; i++) {
            elements[i].innerHTML = `${elements[i].innerHTML} <span style="color: #f00">ERROR</span>: missing dependency: <a href="https://mattboldt.com/typed.js/">Typed.js</a><br>`;
          }
        }
        if (container.charAt(0) === '#')
          document.getElementsById(container.slice(1)).innerHTML = `${document.getElementsById(container.slice(1)).innerHTML} <span style="color: #f00">ERROR</span>: missing dependency: <a href="https://mattboldt.com/typed.js/">Typed.js</a><br>`;
      }
      try {
        autosize;
      } catch (e) {
        if (container === 'body' || container === 'html')
          document.body.innerHTML = `${document.body.innerHTML} <span style="color: #f00">ERROR</span>: missing dependency: <a href="http://www.jacklmoore.com/autosize/">Autosize</a><br>`;
        if (container.charAt(0) === '.') {
          const elements = document.getElementsByClassName(container.slice(1));
          for (let i = 0; i < elements.length; i++) {
            elements[i].innerHTML = `${elements[i].innerHTML} <span style="color: #f00">ERROR</span>: missing dependency:
            <a href="http://www.jacklmoore.com/autosize/">Autosize</a><br>`;
          }
        }
        if (container.charAt(0) === '#')
          document.getElementsById(container.slice(1)).innerHTML =
            `${document.getElementsById(container.slice(1)).innerHTML} <span style="color: #f00">ERROR</span>: missing dependency: <a href="http://www.jacklmoore.com/autosize/">Autosize</a><br>`;
      }
      try {
        // Save custom data to object
        const custom = { settings: {}, commands: {} };
        if (typeof host === "string")
          $.extend(custom.settings, { host: host });
        if (typeof user === "string")
          $.extend(custom.settings, { user: user });
        if (typeof promptColour === "string")
          $.extend(custom.settings, { promptColour: promptColour });
        if (typeof commands === "object")
          $.extend(custom.commands, commands);
        $.extend(true, data, defaults, custom);
        // If everything is loaded, then start Termy
        if ($ && Typed && autosize) {
          initialize();
        }
      } catch (e) { }
    },
  
      // Initialize Termy
      initialize = () => {
        // Display init text
        const init = `<span style="color: #0f0">Welcome to ${data.settings.host}!</span><br><br>>> Scanning for data...<br>>> Loading and configuring Termy...<br><span style="margin-left:28px">==================================</span><br>>> Done!<br><br>This webpage is running <a href="https://github.com/TheDragonRing/termy">Termy v1.0</span>,<br>by <a href="https://thedragonring.me">TheDragonRing</a>, under the <a href="https://opensource.org/licenses/MIT">MIT License</a>.<br><br>Run <i>help</i> to see available commands.<br>`;
        new Typed(`${container} .init`, {
          strings: [init],
          typeSpeed: -100,
          showCursor: false,
          onComplete: () => {
            shell();
          }
        });
  
        // Manage commands
        const shell = () => {
          displayPrefix();
          $(container).keyup((e) => {
            if (loggedIn) {
              const key = e.which;
              // Input text.
              if (key === 13) {
                e.preventDefault();
                command = $(`${container} .cmd-input`).val().trim();
                if (command) {
                  $(`${container} .cmd-input`).replaceWith(`<span>${command}</span><br>`);
                  // $('.caret').remove();
                  // Turn input into command.
                  let args = command.split(' ');
                  args = args.filter((a) => a !== '');
                  command = args[0].toLowerCase();
                  // Error message if unregistered command.
                  let registered = [];
                  Object.getOwnPropertyNames(data.commands).forEach((val) => {
                    registered.push(val);
                  });
                  if (registered.indexOf(command) === -1) {
                    $(`${container} .${commandCount - 1}`)
                      .append(`<span style="color: #f00">ERROR</span>: ${command}: command not found`);
                  } else {
                    args.shift();
                    try {
                      // Execute command.
                      data.commands[command].run(args);
                    } catch (err) {
                      // Error message if error recieved when attempting to run command.
                      $(`${container} .${commandCount - 1}`).append(`<span style="color: #f00">ERROR</span>: ${command}: an error occured while running this command: ${err}`);
                    }
                    $(container).scrollTop($(container).height());
                  }
                  // Prepare to recieve next command.
                  const typedCommands = [];
                  Object.getOwnPropertyNames(data.commands).forEach((val, idx, array) => {
                    if (data.commands[val].typed) {
                      typedCommands.push(val);
                    }
                  });
                  if (typedCommands.indexOf(command) === -1) {
                    command = '';
                    displayPrefix();
                  }
                }
                return false;
              }
            }
          });
        };
  
        // Focuses input on click
        $(container).click(() => {
          $(`${container} .cmd-input`).focus();
        });
      },
  
      // Add prefix for each command
      displayPrefix = () => {
        // Textarea has Grammarly disabled
        $(`${container} .terminal`).append(`<p class="${commandCount}"><span class="host"><span style="color: #0f0">${data.settings.user}@${data.settings.host}</span>:<span style="color: ${data.settings.promptColour}">~$</span></span> <textarea class="cmd-input" data-gramm="false"></textarea></p>`);
        commandCount++;
        // Fix sizing and positioning
        $(`${container} .cmd-input`).css('textIndent', '6.5px');
        autosize($(`${container} .cmd-input`));
        $(`${container} .cmd-input`).focus();
        $(container).scrollTop($(container).height());
      },
      // Animate flashing caret
      // Disabled until I figure out how to replace the default textarea caret...
      /* flashingCaret = () => {
        $('.caret').animate({
          opacity: 0
        }, 600).animate({
          opacity: 1
        }, 600);
        setTimeout(flashingCaret(), 1200);
      }, */
      // Add an underline to a string
      underline = (str, char) => {
        let line = '';
        for (let i = 0; i < str.length; i++) {
          line += char;
        }
        return `${str}<br>${line}`;
      },
      // Logs out of the terminal
      // To execute commands once again the page must be reloaded
      // Parameter is optional, if used it will send the user to that URL after logging them out
      exit = (url) => {
        loggedIn = false;
        $(container).unbind('keyup');
        $(container).append('<div class="exit"></div>');
        let logout = `<br>>> Logged out<br>
        >> Closed connection to ${data.settings.host}<br>${url ? 'Goodbye. Thank you for using Termy.' : `To use Termy once again, <button onclick="termy('${container}'${host ? `, '${host}'` : ''}${user ? `, '${user}'` : ''}${promptColour ? `, '${promptColour}'` : ''});">reload the terminal</button>.`}`;
        $(container).scrollTop($(container).height());
        new Typed(`${container} .exit`, {
          strings: [logout],
          typeSpeed: -100,
          showCursor: false,
          onComplete: function (self) {
            if (url)
              window.location.href = url;
          }
        });
      };
  
    load();
  
  };