🏠 Home 

Reddit Flair Ban

Remove topics by flair. Only works with Classic Reddit

// ==UserScript==
// @name         Reddit Flair Ban
// @namespace    RedditUsercript
// @version      1
// @description  Remove topics by flair. Only works with Classic Reddit
// @author       John Doe
// @match        https://www.reddit.com/r/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery.serializeJSON/2.9.0/jquery.serializejson.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/noty/3.1.4/noty.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/alasql/0.5.1/alasql.min.js
// @require      https://greasyfork.org/scripts/390426-bulma-css-framework-0-7-5/code/bulma-css-framework-075.js?version=735315
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// ==/UserScript==
/* global $:false, jQuery:false, debug:false, alasql:false, Noty:false */
((() => {
const VERSION = 1;
const DEBUG = 0;
const cssFiles = [
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/fontawesome.min.css",
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/solid.min.css",
"https://cdnjs.cloudflare.com/ajax/libs/noty/3.1.4/noty.min.css"
];
const htmlStyles = `
<style type="text/css">
.is-clipped {
overflow: hidden !important;
}
#flair_list {
font-size: 12px;
font-weight: bold;
}
/* ################################  */
.layout-css-ban-flair-icon {
font-size: 10px;
color: black;
cursor:pointer;
padding-bottom: 2px;
}
.layout-unban-flair-icon {
color:green;
cursor:pointer;
}
.js-banned-flair-list-opener .fa-stack { font-size: 0.7em; }
.js-banned-flair-list-opener i { vertical-align: middle; }
</style>
`;
// <i class="fas fa-cogs"></i>
const htmlCPbutton = `
<span>
<button class="js-banned-flair-list-opener">
<span class="fa-stack fa-2x">
<i class="fas fa-hammer fa-stack-1x"></i>
<i class="fas fa-ban fa-stack-2x" style="color:Tomato"></i>
</span>
</button>
<span class="separator">|</span>
</span>
`;
const htmlPageAppend = `
<div class="bulma">
<!-- Banned Flair List -->
<div id="id-card-banned-flairs" class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">
<span class="js-uscr-modal-draggable-handler cursor--move">Banned Flairs</span>
</p>
<button class="delete js-uscr-card-button-close" aria-label="close"></button>
</header>
<form id="id-form-banned-flairs">
<section class="modal-card-body">
<!-- Content ... -->
<div class="columns">
<div class="column">
The following flairs are banned. To unban click <i class="fas fa-plus-circle"></i></span> icon.
</div>
</div>
<div class="columns">
<div class="column">
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth" id="flair_list">
</table>
</div>
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-primary js-uscr-card-button-close">Close</button>
</footer>
</form>
</div>
</div>
<!-- /CP -->
</div>
`;
// functions
let storage = {
version: 1,
// compress: false,
options: {
prefix: ''
},
// Greasemonkey storage API
read: function (key, defaultValue) {
const raw = GM_getValue(this._prefix(key), defaultValue);
// let str = (this.compress) ? LZString.decompressFromUTF16(raw) : raw;
return this._parse(raw);
},
write: function (key, value) {
const raw = this._stringify(value);
// let str = (this.compress) ? LZString.compressToUTF16(raw) : raw;
return GM_setValue(this._prefix(key), raw);
},
delete: function (key) {
return GM_deleteValue(this._prefix(key));
},
readKeys: function () {
return GM_listValues();
},
// browser localstorage
// read: function (key, defaultValue) {
//     const raw = localStorage.getItem(this._prefix(key), defaultValue);
//     const val = raw || defaultValue;
//     // const str = (this.compress) ? LZString.decompressFromUTF16(val) : val;
//     // return this._parse(str);
//     return this._parse(val);
// },
// write: function (key, value) {
//     const raw = this._stringify(value);
//     // let str = (this.compress) ? LZString.compressToUTF16(raw) : raw;
//     localStorage.setItem(this._prefix(key), raw);
//     return;
// },
// delete: function (key) {
//     return localStorage.removeItem(this._prefix(key));
// },
// readKeys: function () {
//     let keys = [];
//     for(let i=0, l=localStorage.length; i < l; i++){
//        keys.push( localStorage.getItem(localStorage.key(i)) );
//     }
//     return keys;
// },
// "Set" means "add if absent, replace if present."
set: function (key, value) {
let savedVal = this.read(key);
if (typeof savedVal === 'undefined' || !savedVal) {
// add if absent
return this.add(key, value);
} else {
// replace if present
this.write(key, value);
return true;
}
},
// "Add" means "add if absent, do nothing if present" (if a uniquing collection).
add: function (key, value) {
let savedVal = this.read(key, false);
if (typeof savedVal === 'undefined' || !savedVal) {
this.write(key, value);
return true;
} else {
if (this._isArray(savedVal)) { // is array
let index = savedVal.indexOf(value);
if (index !== -1) {
// do nothing if present
return false;
} else {
// add if absent
savedVal.push(value);
this.write(key, savedVal);
return true;
}
} else if (this._isObject(savedVal)) { // is object
// merge obj value on obj
let r###lt, objToMerge = value;
r###lt = Object.assign(savedVal, objToMerge);
this.write(key, r###lt);
return false;
}
return false;
}
},
// "Replace" means "replace if present, do nothing if absent."
replace: function (key, itemFind, itemReplacement) {
let savedVal = this.read(key, false);
if (typeof savedVal === 'undefined' || !savedVal) {
// do nothing if absent
return false;
} else {
if (this._isArray(savedVal)) { // is Array
let index = savedVal.indexOf(itemFind);
if (index !== -1) {
// replace if present
savedVal[index] = itemReplacement;
this.write(key, savedVal);
return true;
} else {
// do nothing if absent
return false;
}
} else if (this._isObject(savedVal)) {
// is Object
// replace property's value
savedVal[itemFind] = itemReplacement;
this.write(key, savedVal);
return true;
}
return false;
}
},
// "Remove" means "remove if present, do nothing if absent."
remove: function (key, value) {
if (typeof value === 'undefined') { // remove key
this.delete(key);
return true;
} else { // value present
let savedVal = this.read(key);
if (typeof savedVal === 'undefined' || !savedVal) {
return true;
} else {
if (this._isArray(savedVal)) { // is Array
let index = savedVal.indexOf(value);
if (index !== -1) {
// remove if present
savedVal.splice(index, 1);
this.write(key, savedVal);
return true;
} else {
// do nothing if absent
return false;
}
} else if (this._isObject(savedVal)) { // is Object
let property = value;
delete savedVal[property];
this.write(key, savedVal);
return true;
}
return false;
}
}
},
get: function (key, defaultValue) {
return this.read(key, defaultValue);
},
getAll: function () {
const keys = this._listKeys();
let obj = {};
for (let i = 0, len = keys.length; i < len; i++) {
obj[keys[i]] = this.read(keys[i]);
}
return obj;
},
getKeys: function () {
return this._listKeys();
},
getPrefix: function () {
return this.options.prefix;
},
empty: function () {
const keys = this._listKeys();
for (let i = 0, len = keys.lenght; i < len; i++) {
this.delete(keys[i]);
}
},
has: function (key) {
return this.get(key) !== null;
},
forEach: function (callbackFunc) {
const allContent = this.getAll();
for (let prop in allContent) {
callbackFunc(prop, allContent[prop]);
}
},
_parse: function (value) {
if (this._isJson(value)) {
return JSON.parse(value);
}
return value;
},
_stringify: function (value) {
if (this._isJson(value)) {
return value;
}
return JSON.stringify(value);
},
_listKeys: function (usePrefix = false) {
const prefixed = this.readKeys();
let unprefixed = [];
if (usePrefix) {
return prefixed;
} else {
for (let i = 0, len = prefixed.length; i < len; i++) {
unprefixed[i] = this._unprefix(prefixed[i]);
}
return unprefixed;
}
},
_prefix: function (key) {
return this.options.prefix + key;
},
_unprefix: function (key) {
return key.substring(this.options.prefix.length);
},
_isJson: function (item) {
try {
JSON.parse(item);
} catch (e) {
return false;
}
return true;
},
_isObject: function (a) {
return (!!a) && (a.constructor === Object);
},
_isArray: function (a) {
return (!!a) && (a.constructor === Array);
}
};
function isObject(val) {
if (val === null) {
return false;
}
return ((typeof val === "function") || (typeof val === "object"));
}
function setDebug(isDebug = false) {
if (isDebug) {
window.debug = window.console.log.bind(window.console, "%s: %s");
} else {
window.debug = function () {};
window.console.log = function () {};
}
}
function appendFilesToHead(arr = [], forceExt = false) {
for (let i = 0; i < arr.length; i++) {
let urlStr = arr[i];
let ext = (forceExt) ? forceExt : urlStr.slice((Math.max(0, urlStr.lastIndexOf(".")) || Infinity) + 1);
let ele = null;
switch (ext) {
case "js":
ele = document.createElement("script");
ele.type = "text/javascript";
ele.src = urlStr;
break;
case "css":
ele = document.createElement("link");
ele.rel = "stylesheet";
ele.type = "text/css";
ele.href = urlStr;
break;
default:
ele = document.createElement("script");
ele.type = "text/javascript";
ele.src = urlStr;
}
document.getElementsByTagName("head")[0].appendChild(ele);
}
}
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
function decodeHtml(html) {
let txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
function db_init() {
CURRENT_LOCALSTORAGE_DB = storage.get("flairs", []);
mybase = new alasql.Database("mybase");
mybase.exec("CREATE TABLE flairs (subname STRING, flair STRING)");
debug("localstorage db.lenght", CURRENT_LOCALSTORAGE_DB.length);
if (CURRENT_LOCALSTORAGE_DB.length >= 1) {
alasql.databases.mybase.tables.flairs.data = CURRENT_LOCALSTORAGE_DB;
}
}
function save_db() {
console.log(alasql.databases.mybase.tables.flairs.data);
storage.set("flairs", alasql.databases.mybase.tables.flairs.data);
}
// https://stackoverflow.com/questions/7298364/using-jquery-and-json-to-populate-forms
function populateForm($form, data) {
$.each(data, (key, value) => { // all json fields ordered by name
let $ctrls, $ctrl;
if (value instanceof Array) {
$ctrls = $form.find("[name='" + key + "[]']"); //all form elements for a name. Multiple checkboxes can have the same name, but different values
} else {
$ctrls = $form.find("[name='" + key + "']");
}
if ($ctrls.is("select")) { //special form types
$("option", $ctrls).each(function () {
if (this.value == value) {
this.selected = true;
}
});
} else if ($ctrls.is("textarea")) {
$ctrls.val(value);
} else {
switch ($ctrls.attr("type")) { //input type
case "text":
case "hidden":
$ctrls.val(value);
break;
case "radio":
if ($ctrls.length >= 1) {
$.each($ctrls, function (index) { // every individual element
let elemValue = $(this).attr("value");
let singleVal = value;
let elemValueInData = singleVal;
if (elemValue == value) {
$(this).prop("checked", true);
} else {
$(this).prop("checked", false);
}
});
}
break;
case "checkbox":
if ($ctrls.length > 1) {
$.each($ctrls, function (index) { // every individual element
let elemValue = $(this).attr("value");
let elemValueInData;
let singleVal;
for (let i = 0; i < value.length; i++) {
singleVal = value[i];
debug("singleVal", singleVal, "/value[i][1]", value[i][1]);
if (singleVal == elemValue) {
elemValueInData = singleVal;
}
}
if (elemValueInData) {
$(this).prop("checked", true);
} else {
$(this).prop("checked", false);
}
});
} else if ($ctrls.length == 1) {
$ctrl = $ctrls;
if (value) {
$ctrl.prop("checked", true);
} else {
$ctrl.prop("checked", false);
}
}
break;
} //switch input type
} // if/else
}); // all json fields
} // populate form
function getSubName() {
let url = window.location.href;
let regex = /^https?:\/\/(?:www\.)?reddit\.com\/r\/([^\/?\s]+)\/?/i;
let match = url.match(regex);
return match[1];
}
function getSubBannedFlairs() {
return mybase.exec("SELECT * FROM flairs WHERE subname=?", [CURRENT_SUB_NAME]);
}
// end functions
setDebug(DEBUG);
let mybase = null;
let CURRENT_SUB_NAME = null;
let CURRENT_SUB_FLAIRS = [];
let CURRENT_LOCALSTORAGE_DB = null;
let CURRENT_USERNAME = $("#header .user A").text();
db_init();
CURRENT_SUB_NAME = getSubName();
CURRENT_SUB_FLAIRS = getSubBannedFlairs();
console.log('CURRENT_SUB_NAME', CURRENT_SUB_NAME);
console.log('CURRENT_SUB_FLAIRS', CURRENT_SUB_FLAIRS);
appendFilesToHead(cssFiles, "css");
$("head").append(htmlStyles);
$("body").append(htmlPageAppend);
$("#header-bottom-right").prepend(htmlCPbutton);
setTimeout(function(){
let c = 0;
CURRENT_SUB_FLAIRS.forEach(function(item){
$(`.linkflairlabel[title="${item.flair}"]`).each(function(){
console.log('remove thread', item.flair);
$(this).closest("DIV[data-fullname^='t3_']").remove();
c++;
});
});
console.log('counter : removed threads', c );
$("p.title > .linkflairlabel ").each(function (index, value) {
let html = "";
let name = $(this).text();
html = `<span class="js-flair-ban" data-name="${name}" title="Filter : Remove '${name}' from listing"><span class="layout-css-ban-flair-icon"><i class="fas fa-minus-circle"></i></span></span>`;
let append = `<span class="layout-after-flair" data-name="${name}">${html} </span>`;
$(this).after(append);
});
// CARD > BUTTON CLOSE
$(".js-uscr-card-button-close").click(function () {
event.preventDefault();
let card_id = $(this).closest(".modal").attr("id");
$(`#${card_id}`).removeClass('is-active');
$('html').removeClass('is-clipped');
return false;
});
// OPEN
$(".js-banned-flair-list-opener").click(function () {
let html_flairs = '';
let r###lt = mybase.exec("SELECT * FROM flairs WHERE subname = ?", [CURRENT_SUB_NAME]);
let counter = 0;
console.log(r###lt);
r###lt.forEach(function(item){
counter++;
html_flairs += `
<tr id="row_${counter}">
<td>${item.flair} </td>
<td><span class="js-flair-unban layout-unban-flair-icon" data-name="${item.flair}" data-row="${counter}"><i class="fas fa-plus-circle"></i></span></td>
</tr>
`;
});
$("#flair_list").html(html_flairs);
$('html').addClass('is-clipped');
$("#id-card-banned-flairs").addClass("is-active");
});
$(".js-flair-ban").click(function () {
let name = $(this).attr("data-name");
console.log('name', name);
mybase.exec("DELETE FROM flairs WHERE subname=? and flair = ?", [CURRENT_SUB_NAME, name]);
mybase.exec("INSERT INTO flairs (?,?)", [CURRENT_SUB_NAME, name]);
new Noty({
type: 'success',
text: 'Flair banned: ' + name + '',
}).show();
save_db();
});
$(document).on('click', '.js-flair-unban', function(e) {
let name = $(this).attr("data-name");
let row = $(this).attr("data-row");
console.log('name', name);
mybase.exec("DELETE FROM flairs WHERE subname=? and flair = ?", [CURRENT_SUB_NAME, name]);
$("#row_"+row).remove();
new Noty({
type: 'success',
text: 'Flair unbanned: ' + name + '',
}).show();
save_db();
});
}, 200);
Noty.overrideDefaults({
layout   : 'topRight',
closeWith: ['click', 'button'],
progressBar: false,
timeout: 2000,
closeWith: ['click'],
});
}))();