twitch spam filter

this script reduces twitch's chat spam

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name        twitch spam filter
// @description this script reduces twitch's chat spam
// @namespace   twitch
// @include     /^https?://www\.twitch\.tv/[a-zA-Z0-9_]+$/
// @version     0.1
// @grant       none
// ==/UserScript==

/* known issues :
** - the last chat message is sometimes hidden, you need to scroll a bit,
**   or wait for the next message to see it.
** - if you naviguate to another stream through the website,
**   the spam count will not show. You need to refresh the page to 
**   reset it.
*/

/* todo :
** - filter 'weird' text, which is most likely copypasta
*/

window.onload = function() {
    /* helper functions */
    var uniq = function(str) {
	var seen = {};
	return str.filter(function(item) {
            return seen.hasOwnProperty(item) ? false : (seen[item] = true);
	});
    };
    /* objects */
    /* Bucket */
    var Bucket = function(size) {
	this.maxSize = size;
	this.currentSize = 0;
	this.array = [];
    };
    Bucket.prototype.add = function(elem) {
	if (this.currentSize < this.maxSize) {
	    this.array.push(elem);
	    this.currentSize++;
	    return ;
	}
	this.array.shift();
	this.array.push(elem);
    };
    Bucket.prototype.contains = function(elem) {
	return this.array.indexOf(elem) != -1;
    };
    /* * Message */
    var Message = function(object) {
	this.object = object;
	this.parse(object);
    };
    Message.prototype.parse = function(object) {
	this.from = $(".from", object).text();
	this.content = $(".message", object).text().trim();
	this.emoticons = this.parseEmoticons($(".emoticon", object));
	this.badges = this.parseBadges($(".badge", object));
    };
    Message.prototype.parseEmoticons = function(object) {
	var parsed = [];
	object.each(function() {
	    parsed.push($(this).attr("alt"));
	});
	return parsed;
    };
    Message.prototype.parseBadges = function(object) {
	var parsed = [];
	object.each(function() {
	    parsed.push($(this).attr("class").split(" ").slice(3)[0]);
	});
	return parsed;
    };
    Message.prototype.show = function() {
	$(this.object).addClass("filtered");
	return $(this.object).show();
    };
    Message.prototype.remove = function() {
	return $(this.object).remove();
    };
    /* * MessageHandler */
    var MessageHandler = function() {
	this.filtered = 0;
	this.filteredBox = null;
	this.filterBoxActive = false
	this.whiteHandlers = []; // the message is shown if one hadnler returns true
	this.blackHandlers = []; // the message is filtered if one handler returns false
    };
    MessageHandler.prototype.init = function() {

    };
    MessageHandler.prototype.handleMessage = function(message) {
	for (var i = 0, max = this.whiteHandlers.length; i < max; i++) {
	    if (this.whiteHandlers[i](message) === true) {
		return true;
	    }
	}
	for (var i = 0, max = this.blackHandlers.length; i < max; i++) {
	    if (this.blackHandlers[i](message) === false) {
		return false;
	    }
	}
	return true;
    };
    MessageHandler.prototype.registerHandler = function(type, handler) {
	if (! this.hasOwnProperty(type)) {
	    return false;
	}
	this[type].push(handler);
	return true;
    };
    MessageHandler.prototype.createFilterBox = function() {
	this.filteredBox = $("<p class='filterbox' title='spam count' style='position:relative;top:-2px;left:8px;color: rgb(166, 142, 210)'></p>");
	if ($(".chat-buttons-container").append(this.filteredBox)) {
	    $(this.filteredBox).text(this.filtered);
	    this.filterBoxActive = true;
	}
    };
    MessageHandler.prototype.incFilterCount = function() {
	if (! this.filterBoxActive) {
	    this.createFilterBox();
	}
	this.filtered++;
	$(this.filteredBox).text(this.filtered);
    };
    /* whiteHandlers */
    var whitelist = [
	$(".username").text()
    ];
    var whitelistHandler = function(message) {
	for (var i = 0, max = whitelist.length; i < max; i++) {
	    if (message.from == whitelist[i]) {
		return true;
	    }
	}
	if (message.from == "") { // admin messages leave username blank
	    return true;
	}
	return false;
    };
    /* blackHandlers */
    var emoteHandler = function(message) {
	/* if the message is empty, it only contains emotes */
	if (! message.content.length) {
	    return false;
	}
	/* that's too much */
	if (message.emoticons.length >= 5) {
	    return false;
	}
	return true;
    };
    var numericHandler = function(message) {
	/* Kappa 123 */
	return isNaN(message.content);
    };
    var oneWordHandler = function(message) {
	/* who cares of false positives */
	return message.content.indexOf(" ") != -1;
    };
    var capsLockPleaseHandler = function(message) {
	var caps = 0;
	var alphaNum = 0;
	for (var i = 0, max = message.content.length; i < max; i++) {
	    if (message.content[i] >= 'A' && message.content[i] <= 'Z') {
		caps++;
		alphaNum++;
	    }
	    else if (message.content[i] != " ") {
		alphaNum++;
	    }
	}
	if (alphaNum / caps <= 2.0) {
	    return false;
	}
	return true;
    };
    var sameSameHandler = function(message) {
	var msg = message.content.split(" ");
	var uniqMsg = uniq(msg);
	if (msg.length / uniqMsg.length >= 1.7) {
	    return false;
	}
	return true;
    };
    var latestMessages = new Bucket(30);
    /* r9kHandler should be executed last, as storing a filtered
    ** message is useless */
    var r9kHandler = function(message) {
	var ret = true;
	if (latestMessages.contains(message.content)) {
	    ret = false;
	}
	latestMessages.add(message.content);
	return ret;
    };
    /* code */
    var hideMessages = function() {
	$("head").append("<style type='text/css'>.chat-line { display: none; }</style>");
    };
    var messageHandler = new MessageHandler();
    var whiteHandlers = [
	whitelistHandler
    ];
    var blackHandlers = [
	emoteHandler,
	numericHandler,
	oneWordHandler,
	capsLockPleaseHandler,
	sameSameHandler,
	r9kHandler
    ];
    messageHandler.init();
    for (var i = 0, max = whiteHandlers.length; i < max; i++) {
	messageHandler.registerHandler("whiteHandlers", whiteHandlers[i]);
    }
    for (var i = 0, max = blackHandlers.length; i < max; i++) {
	messageHandler.registerHandler("blackHandlers", blackHandlers[i]);
    }
    hideMessages();
    setInterval(function() {
	var messages = $(".chat-line").not(".filtered");
	messages.each(function() {
	    var message = new Message(this);
	    if (messageHandler.handleMessage(message)) {
		message.show();
	    }
	    else {
		message.remove();
		messageHandler.incFilterCount();
	    }
	});
    }, 200);
};