Reddit Content Filter

By pabs Last update May 5, 2009 — Installed 1,511 times.

There are 1 previous version of this script.

// ==UserScript==
// @name           Reddit Content Filter
// @description    Hide reddit articles by author, domain, or title.
// @include        http://reddit.com/*
// @include        http://*.reddit.com/*
// ==/UserScript==

(function () {
  var keys = ['user', 'site', 'word'], sum = 0, show_btn;

  /*
   * key-name map
   */
  var names = {
    user: 'Author',
    site: 'Domain',
    word: 'Title'
  };

  /*
   * tidy - convert string into list of trimmed, comma-separated strings.
   */
  var tidy = (function() {
    var re = {
      trim:   /^\s*|\s*$/g, 
      split:  /\s*,\s*/
    };

    return function (str) {
      return str.replace(re.trim, '').split(re.split).filter(function(str) {
        return str && str.length > 0;
      });
    };
  })();

  var get = function(key) {
    return GM_getValue(key + '_filters') || '';
  };

  /*
   * get_config - get filter words as hash.
   */
  var get_config = function() { 
    var ret = {};

    // map keys to hash of array of regular expressions
    keys.forEach(function(key) {
      ret[key] = get(key);
    });

    // return results
    return ret;
  };

  /*
   * get_res - get filter words as hash of array of regexes.
   */
  var get_res = function() {
    var ret = {};

    keys.forEach(function(key) {
      ret[key] = tidy(get(key)).map(function (str) {
        return new RegExp(str, 'i');
      });
    });

    // return results
    return ret;
  };

  /*
   * get_nodes - get a list of matching nodes.
   */
  var get_nodes = (function() {
    var result_type = XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, 
        prefix = "//div[@id='siteTable']//div[@class='entry']";

    // relevant nodes' paths
    var paths = [
      // match <a class='title' href='{site}'>{title}</a> 
      // (used for word and site filters)
      prefix + "/p[@class='title']/a[contains(@class, 'title')]",

      // match <a class='author' href='...'>{user}</a> 
      // (used for user filters)
      prefix + "/p[@class='tagline']/a[@class='author']",

      prefix + "/p[@class='title']/span[@class='domain']/a"
    ].join('|');

    return function() {
      return document.evaluate(paths, document, null, result_type, null);
    };
  })();

  /*
   * find - find matching article elements and pass to callback.
   */
  var find = function(fn) {
    var i, l, el, e, k, cls, str, els = get_nodes(), res = get_res();
    var match_debug = 0;
  
    for (i = 0, l = els.snapshotLength; i < l; i++) {
      if (el = els.snapshotItem(i)) {
        match_debug++;

        for (k in res) {
          str = null;

          if (cls = el.className) {
            if (k == 'word' && cls.indexOf('title') != -1) {
              // title word match
              str = el.innerHTML;
            } else if (k == 'user' && cls.indexOf('author') != -1) {
              // user name match
              str = el.innerHTML;
            }
          } else {
            if (k == 'site' && el.tagName == 'A')
              // url word match
              str = el.href;
          }

          // if we have a match, then walk up to the containing div
          if (str && res[k].some(function(re) { return str.match(re); })) {
            e = el;

            // walk up to containing div.entry and pass it to callback
            while (e && (e = e.parentNode)) {
              if (e.tagName == 'DIV' && e.className.indexOf('thing') != -1) {
                fn(e);
                e = null;
              }
            }
          }
        }
      }
    }

    // alert('match_debug = ' + match_debug);
  };

  /*
   * show - show all matching articles
   */
  var show = function() {
    // show hidden articles
    find(function(el) { 
      el.style.display = 'block'; 
    });

    // reset sum
    sum = 0;

    // disable show btn
    show_btn.style.display = 'none';
    show_btn.innerHTML = 'Show All (0)';
  };

  /*
   * hide - hide all matching articles 
   */
  var hide = function() {
    var els = {};

    // hide matching items
    find(function(el) {
      var c = el.className.toLowerCase().split(/\s+/).sort().join('');
      if (!els[c]) {
        els[c] = true;

        el.style.display = 'none';
        sum++;
      }
    });

    // enable show button if we hid anything
    if (sum > 0) {
      show_btn.style.display = 'inline';
      show_btn.innerHTML = 'Show Hidden (' + sum + ')';
    }
  };

  var edit = function(key) {
    var val, res = get_config(),
        msg = 'Edit ' + names[key] + ' Filters ' + 
              '(list of comma-separated phrases):';

    // prompt for and save filters
    if ((val = prompt(msg, res[key])) !== null) {
      // show everything that was hidden
      show();

      // apply new value to config
      GM_setValue(key + '_filters', val);

      // reset sum and hide all matching items
      hide();
    }
  };

  /*
   * init - add config buttons to top-right corner 
   */
  var init = function() {
    var btn, div = document.createElement('div');

    // move div to top-right corner
    div = document.createElement('div');
    div.style.zIndex = 9999;
    div.style.position = 'absolute';
    div.style.top = '20px';
    div.style.right = '8px';

    // create show button
    btn = show_btn = document.createElement('button');
    btn.style.display = 'none';
    btn.style.fontSize = '9pt';
    btn.innerHTML = 'Show All (0)';
    div.appendChild(btn);

    // add show btn handler
    btn.addEventListener('click', function(ev) {
      // show hidden articles and stop event
      show();
      return false;
    }, false);

    // create edit buttons
    keys.forEach(function(key) {
      var name = names[key] + ' Filters';

      // create edit button
      btn = document.createElement('button');
      btn.innerHTML = name;
      btn.style.marginLeft = '5px';
      btn.style.fontSize = '9pt';

      // add button click handler
      btn.addEventListener('click', function() { 
        edit(key);
        return false;
      }, false);

      // add button to dom
      div.appendChild(btn);
    });

    // add buttons to screen
    document.body.appendChild(div);
  };

  // add configuration menu items
  keys.forEach(function(key) {
    var name = 'Edit ' + names[key] + ' Filters...';

    GM_registerMenuCommand(name, function() {
      edit(key);
    });
  });

  // add window load handler
  window.addEventListener('load', function() {
    // add config buttons
    init();

    // hide matching articles
    hide();
  }, false);
})();