twitch spam filter

this script reduces twitch's chat spam

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
};