Reddit User/Subreddit/Domain Filter

Filter Reddit posts based on the user, subreddit, domain, or post flair name.

// ==UserScript==
// @name        Reddit User/Subreddit/Domain Filter
// @namespace   RedditUserSubDomainFilter
// @version     1.0.3
// @license     GNU AGPLv3
// @description Filter Reddit posts based on the user, subreddit, domain, or post flair name.
// @author      jcunews
// @include     https://*.reddit.com/*
// @grant       none
// ==/UserScript==

var filterFlair  = JSON.parse(localStorage.filterFlair || "[]"),
    filterUser   = JSON.parse(localStorage.filterUser || "[]"),
    filterSub    = JSON.parse(localStorage.filterSub || "[]"),
    filterDomain = JSON.parse(localStorage.filterDomain || "[]"),
    posts, flair, user, sub, domain, buttonTemplate, button, match, filtered;

function doFilter(dontHide) {
  posts = Array.prototype.slice.call(document.querySelectorAll(".thing"));
  posts.forEach(function(post,i) {
    filtered = false;
    flair = post.querySelector(".linkflairlabel");
    if (flair) {
      match = filterFlair.indexOf(flair.title) >= 0;
      filtered = filtered || !!match;
      button = flair.nextSibling;
      if (!button || !button.getAttribute || !button.getAttribute("type")) {
        button = buttonTemplate.cloneNode(true);
        button.title = "Filter posts with this flair (CTRL+Click to undo)";
        button.style.marginLeft = "";
        button.setAttribute("type", "flair");
        button.setAttribute("value", flair.title);
        button.onclick = setFilter;
        flair.parentNode.insertBefore(button, flair.nextSibling);
      }
      button.style.background = match ? "#b0b" : "#c00";
    }
    user = post.querySelector(".author");
    if (user) {
      match = filterUser.indexOf(user.textContent) >= 0;
      filtered = filtered || !!match;
      button = user.nextSibling;
      if (!button || !button.getAttribute || !button.getAttribute("type")) {
        button = buttonTemplate.cloneNode(true);
        button.title = "Filter posts from this user (CTRL+Click to undo)";
        button.style.marginLeft = "";
        button.setAttribute("type", "user");
        button.setAttribute("value", user.textContent);
        button.onclick = setFilter;
        user.parentNode.insertBefore(button, user.nextSibling);
      }
      button.style.background = match ? "#b0b" : "#c00";
    }
    sub = post.querySelector(".subreddit");
    if (sub) {
      match = filterSub.indexOf(sub.textContent.match(/\/?(.*)/)[1]) >= 0;
      filtered = filtered || !!match;
      button = sub.nextSibling;
      if (!button || !button.getAttribute || !button.getAttribute("type")) {
        button = buttonTemplate.cloneNode(true);
        button.title = "Filter posts from this subreddit (CTRL+Click to undo)";
        button.setAttribute("type", "sub");
        button.setAttribute("value", sub.textContent.match(/\/?(.*)/)[1]);
        button.onclick = setFilter;
        sub.parentNode.insertBefore(button, sub.nextSibling);
      }
      button.style.background = match ? "#b0b" : "#c00";
    }
    domain = post.querySelector(".domain");
    if (domain) {
      match = filterDomain.indexOf(domain.textContent.match(/\((.*)\)/)[1]) >= 0;
      filtered = filtered || !!match;
      button = domain.nextSibling;
      if (!button || !button.getAttribute || !button.getAttribute("type")) {
        button = buttonTemplate.cloneNode(true);
        button.title = "Filter posts from this domain (CTRL+Click to undo)";
        button.setAttribute("type", "domain");
        button.setAttribute("value", domain.textContent.match(/\((.*)\)/)[1]);
        button.onclick = setFilter;
        domain.parentNode.insertBefore(button, domain.nextSibling);
      }
      button.style.background = match ? "#b0b" : "#c00";
    }
    if (filtered) {
      if (!dontHide) {
        post.setAttribute("filtered", "1");
        post.style.display = "none";
      }
    } else {
      post.removeAttribute("filtered");
      post.style.display = "";
    }
  });
}

function setFilter(ev) {
  var value, i;
  button = ev.target;
  value = button.getAttribute("value");
  if (!ev.ctrlKey) {
    //add filter
    switch (button.getAttribute("type")) {
      case "flair":
        if (filterFlair.indexOf(value) < 0) {
          filterFlair.push(value);
          localStorage.filterFlair = JSON.stringify(filterFlair);
        }
        break;
      case "user":
        if (filterUser.indexOf(value) < 0) {
          filterUser.push(value);
          localStorage.filterUser = JSON.stringify(filterUser);
        }
        break;
      case "sub":
        if (filterSub.indexOf(value) < 0) {
          filterSub.push(value);
          localStorage.filterSub = JSON.stringify(filterSub);
        }
        break;
      default: //domain
        if (filterDomain.indexOf(value) < 0) {
          filterDomain.push(value);
          localStorage.filterDomain = JSON.stringify(filterDomain);
        }
    }
  } else {
    //remove filter
    switch (button.getAttribute("type")) {
      case "flair":
        i = filterFlair.indexOf(value);
        if (i >= 0) {
          filterFlair.splice(i, 1);
          localStorage.filterFlair = JSON.stringify(filterFlair);
        }
        break;
      case "user":
        i = filterUser.indexOf(value);
        if (i >= 0) {
          filterUser.splice(i, 1);
          localStorage.filterUser = JSON.stringify(filterUser);
        }
        break;
      case "sub":
        i = filterSub.indexOf(value);
        if (i >= 0) {
          filterSub.splice(i, 1);
          localStorage.filterSub = JSON.stringify(filterSub);
        }
        break;
      default: //domain
        i = filterDomain.indexOf(value);
        if (i >= 0) {
          filterDomain.splice(i, 1);
          localStorage.filterDomain = JSON.stringify(filterDomain);
        }
    }
  }
  doFilter(ev.ctrlKey);
}

function showFiltered() {
  posts = Array.prototype.slice.call(document.querySelectorAll(".thing[filtered]"));
  posts.forEach(function(post) {
    post.style.display = "";
  });
}

function addLink(ele) {
  ele.appendChild(button);
  //monitor dynamic post list
  if (window.siteTable_organic) {
    (new MutationObserver(function(records) {
      var newNodes = 0;
      records.forEach(function(record) {
        if (record.addedNodes) newNodes += record.addedNodes.length;
      });
      if (newNodes) doFilter();
    })).observe(window.siteTable_organic, { childList: true });
  }
}

if (!document.querySelector(".siteTable")) {
  //create button template
  buttonTemplate = document.createElement("DIV");
  buttonTemplate.textContent = "X";
  buttonTemplate.style.cssText = "\
display:inline-block; margin-left:1ex; border-radius:4px; padding:0 .7ex; \
background:#c00; text-align:center; line-height:normal; font-size: x-small; \
font-weight:bold; color:#fff; cursor:pointer";
  //filter posts
  doFilter();
  //add link to show all filtered posts
  button = document.createElement("A");
  button.textContent = "Show Filtered";
  button.title = "Temporarily show filtered posts";
  button.style.marginLeft = "2ex";
  button.href = "javascript:void(0)";
  button.onclick = showFiltered;
  if (match = document.querySelector(".tabmenu")) { //old layout
    addLink(match);
  } else if (match = document.querySelector(".gXMvOl")) { //new layout
    setTimeout(addLink, 50, match);
  }
}