DanielBlaze / Reddit Base64 Decoder

// ==UserScript==
// @namespace   https://openuserjs.org/users/DanielBlaze
// @name        Reddit Base64 Decoder
// @description Automatically base64 decode links in reddit posts
// @copyright   2018, DanielBlaze (https://openuserjs.org/users/DanielBlaze)
// @updateURL   https://openuserjs.org/meta/DanielBlaze/Reddit_Base64_Decoder.meta.js
// @license     MIT
// @version     1.1.0
// @author      DanielBlaze
// @grant       none
// @match       https://*.reddit.com/r/*
// ==/UserScript==

// ==OpenUserJS==
// @author DanielBlaze
// ==/OpenUserJS==

const DEBUG = false;

/**
 * Create a log group if debug is enabled
 * @param {string} name - the group name
 */
function logGroup(name) {
  if (DEBUG) console.group(name);
}

/**
 * End a log group if debug is enabled
 * @param {string} name - the group name
 */
function logGroupEnd(name) {
  if (DEBUG) console.groupEnd(name);
}

/**
 * Log a message is debug is enabled
 * @param {string} msg
 */
function log(msg) {
  if (DEBUG) console.log(msg);
}

/**
 * Attempts to decode an array of elements. Styling will be lost on any element which is decoded
 * @param {HTMLElement[]} domElements
 */
function decode(domElements) {
  // for all pararaph tags
  domElements.forEach(domElement => {
    // split on the words we find, and make sure we have something before continuing
    const words = domElement.innerText.split(' ');
    if (words.length === 0) return;

    // decode the string
    let changed = false;
    const transformed = words.map(word => {
      try {
        const decoded = atob(word);

        // if it's valid, change it, and underline it. Secure links = underline in blue. Insecure links = warning image + underlined in red
        if (decoded.startsWith('https://')) {
          changed = true;
          return `<a style="border-bottom: 1px dotted #036;" target="_blank" href="${decoded}">${decoded}</a>`;
        } else if (decoded.startsWith('http://')) {
          changed = true;
          return `&nbsp;<img style="vertical-align: middle;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAbwAAAG8B8aLcQwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIVSURBVDiNhZNLaBNRFIb/OzNJmzakqQhqjFptrK1BIfRBJ1GDjzQ0kZiImdDSIqmKC1clQiDFR6QqSJaCC0UXUhdZim7UUgoiuBQ3GomaVMVHA2LRJE5mjgtpMZlRz+7+5z/fOfdwLyMi6EUknrZW5Wq09rOy22DgztyfyRT0fEwPEBhLW0DVJ4M9n50OyyJuzndUiTd3zman3zd6Bd32VE0eEovOk8EXwGtgY3u56cJdZxaAp9HKNQpDI0lba5M8ObI3v6J57CWsav3h9h1JuP4LEBh/Pup9a7K0yLj3dAMuPuwBU4GzoZcA6M4/Af7RVPdqa2UiLP7e1+1HDsy9agcAbLcuYeuar91DsUTgrwAe7NL4/jxvNKgAAJXYH3sBpgI5kKpe1wUMj6bEjrVLkQOuD41TrsT65go8ji82XyxxQgNgjF2Z8OfAmP67WI7JfXlAUTJ1gOHxVGjnltKuvq5FnRJWd2rjZBzue2fxSYlpAOAkSeI5wuVj/pymNDiwAOe67xo9PlAEU5XTkiQZ+U2ucNy74+PxkFjUGLvs3+C2ltBiVOp0AQRzmyw8LnTaORCdiu55o3vf5I1+HJ3p1c0Ft30C1WoxAQw2U0OH5Qi7C3j+zKyb4xiBQM0CVFybutWbDolFGA1aUKR/AYWyqU6TFQ5X5zaDMX6eERGCY6mDBDYI0n4uWS57dQZQGLjZB9nMuV9Nv60tCtUXugAAAABJRU5ErkJggg==" />&nbsp; <a style="border-bottom: 1px dotted #f00;" target="_blank" href="${decoded}">${decoded}</a>`;
        }
      } catch (_) {
      }

      return word;
    });

    // only if we changed something should we update the HTML - otherwise we lose the original formatting
    if (changed) domElement.innerHTML = transformed.join(' ');
  });
}

(function () {
  'use strict';

  // check we have observers available to us
  if (!MutationObserver) return;

  // observer config - only interested in tree modifications
  const config = { childList: true, subtree: true };

  // the callback to decode the string
  const callback = function (mutationsList, observer) {
    logGroup('mutations');
    log(`Observing ${mutationsList.length} mutations`);

    let count = 0;

    mutationsList.forEach(mutation => {
      if (mutation.target.nodeName === 'DIV' && mutation.addedNodes.length > 0) {
        // check we can find the post contents
        const postContents = mutation.target.querySelectorAll('p');
        if (!postContents) return;

        // decode that bad boy
        decode(postContents);
        count++;
      }
    });

    log(`Tried decoding ${count} divs`);
    logGroupEnd('mutations');
  };

  // create an observer instance with the callback
  const observer = new MutationObserver(callback);

  // Start observing the target node for mutations
  observer.observe(document, config);

  // run on page load incase no mutations occur, but only on old reddit
  const postContainer = document.querySelector('.expando');
  if (postContainer) {
    const postContents = postContainer.querySelectorAll('p');
    if (postContents) {
      decode(postContents);
    }
  }
})();