Robin Enhancement Script

Highlight mentions, make link clickable, use channels & automatically remove spam

Verze ze dne 04. 04. 2016. Zobrazit nejnovější verzi.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         Robin Enhancement Script
// @namespace    https://www.reddit.com/
// @version      2.2.0
// @description  Highlight mentions, make link clickable, use channels & automatically remove spam
// @author       mr_bag
// @match        https://www.reddit.com/robin*
// @grant        none
// ==/UserScript==
(function() {

	// Grab users username + play nice with RES
	var robin_user = $("#header-bottom-right .user a").first().text();
	var ignored_users = {};

	// for spam counter - very important i know :P
	var blocked_spam_el = null;
	var blocked_spam = 0;

	var _robin_grow_detected = false;

	// Supported channels
	var channels = {
		"%": "percent",
		"$" : "dollar",
		"#" : "hash",
		"~" : "tilde",
		"^" : "hat",
		"+" : "plus",
		"&" : "and",
		"*" : "star",
		"." : "dot",
		"!" : "exclaim",
		"?" : "question"
	};

	var channel_unread_counts = {};

	// Init unread count
	for(var i in channels){
		channel_unread_counts[i] = 0;
	}

	/**
	 * Check if a message is "spam"
	 */
	var is_spam = function(line){
		return (
			// Hide auto vote messages
			(/^voted to (grow|stay|abandon)/.test(line)) ||
			// random unicode?
			(/[\u0080-\uFFFF]/.test(line)) ||
			// hide any auto voter messages
			(/\[.*autovoter.*\]/.test(line)) ||
			// Common bots
			(/^(\[binbot\]|\[robin-grow\])/.test(line)) ||
			// repeating chars in line (more than 5). e.g. aaaaaaa !!!!!!!!
			(/(.)\1{5,}/.test(line)) ||
			// Some common messages
			(/(voting will end in approximately|\[i spam the most used phrase\]|\[message from creator\]|\[.*bot.*\])/.test(line)) ||
			// no spaces = spam if its longer than 25 chars (dont filter links)
			(line.indexOf(" ") === -1 && line.length > 25 && line.indexOf("http") === -1) ||
			// repeating same word
			/(\b\S+\b)\s+\b\1\b/i.test(line)
		);
	};

	/**
	 * Check if a message is from an ignored user
	 *
	 */
	var is_ignored = function($usr, $ele){
		// no user name, go looking for when said it
		if($usr.length === 0){
			while($usr.length === 0){
				$ele = $ele.prev();
				$usr = $ele.find(".robin--username");
			}
		}
		// are they ignored?
		return (ignored_users[$usr.text()]);
	};

	/**
	 * Make links clickable
	 *
	 */
	var auto_link = function($msg){
		var text = $msg.html(); // read as html so stuff stays escaped
		// normal links
		text = text.replace(/\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim, '<a target="blank" href="$&">$&</a>');
		// reddit subreddit links
		text = text.replace(/ \/r\/(\w+)/gim, ' <a target="blank" href="https://reddit.com/r/$1">/r/$1</a>');
		// update text
		$msg.html(text);
	};

	/**
	 * Mute a user
	 */
	var _mute_user = function(usr){
		// Add to ignore list
		ignored_users[usr] = true;
		_render_muted_list();
	};

	/**
	 * un-mute a user
	 */
	var _unmute_user = function(usr){
		// Add to ignore list
		delete ignored_users[usr];
		_render_muted_list();
	};

	// Render list of ignored users
	var _render_muted_list = function(){
		var html = "<strong>Ignored users</strong><br>";
		for(var u in ignored_users){
			html += "<div data-usr='"+ u + "'>" + u + " - [unmute]</div>";
		}
		$("#muted_users").html(html);
	};

	// Scroll chat back to bottom
	var _scroll_to_bottom = function(){
		$("#robinChatWindow").scrollTop($("#robinChatMessageList").height());
	};

	var update_spam_count = function(){
		blocked_spam++;
		blocked_spam_el.innerHTML = blocked_spam;
	};

	var prefill_name = function(e){
		e.preventDefault();
		e.stopPropagation();

		// if text area blank, prefill name.
		if($(".text-counter-input").val() === ''){
			$(".text-counter-input").val($(this).text() + ' ').focus();
		}
	};

	/**
	 * Parse a link and apply changes
	 */
	var parse_line = function($ele){
		var $msg = $ele.find(".robin-message--message");
		var $usr = $ele.find(".robin--username");

		var line = $msg.text().toLowerCase();

		// If user is ignored or message looks like "Spam". hide it
		if (is_ignored($usr, $ele) || is_spam(line)) {
			$ele.addClass("spam-hidden");
			update_spam_count();
		}

		// Highlight mentions
		if(line.indexOf(robin_user) !== -1){
			$ele.addClass("user-mention");
		}

		// Make links clickable
		if(!_robin_grow_detected && line.indexOf("http") !== -1){
			auto_link($msg);
		}

		// Add mute button to users
		if(!$ele.hasClass("robin--user-class--system") && $usr.text() != robin_user){
			$("<span style='font-size:.8em;cursor:pointer'> [mute] </span>").insertBefore($usr).click(function(){
				_mute_user($usr.text());
			});
		}

		// Track channels
		for(var i in channels){
			if(line.indexOf(i) === 0){
				$ele.addClass("filter-" + channels[i] +" in-channel");
				channel_unread_counts[i]++;
			}
		}

		// bind click to use (override other click events if we can)
		$usr.bindFirst("click", prefill_name);
	};

	var _apply_updates = function(){

		// Update unread counts
		$("#filter_tabs span").each(function(){
			// dont update selected tab
			if($(this).hasClass("selected")) return;

			var type = $(this).attr("data-filter");
			$(this).find("span").text(channel_unread_counts[type]);
		});
	};

	// Detect changes, are parse the new message
	$("#robinChatWindow").on('DOMNodeInserted', function(e) {
		if ($(e.target).is('div.robin-message')) {
			// Apply changes to line
			parse_line($(e.target));
		}
	});

	// When everything is ready
	$(document).ready(function(){

		// Set default spam filter type
		$("#robinChatWindow").addClass("hide-spam");

		// Add checkbox to toggle "hide" behaviors
		$("#robinDesktopNotifier").append("<label><input type='checkbox' checked='checked'>Hide spam completely (<span id='spamcount'>0</span> removed)</label>").click(function(){
			if($(this).find("input").is(':checked')){
				$("#robinChatWindow").removeClass("mute-spam").addClass("hide-spam");
			}else{
				$("#robinChatWindow").removeClass("hide-spam").addClass("mute-spam");
			}
			// correct scroll after spam filter change
			_scroll_to_bottom();
		});

		blocked_spam_el = $("#spamcount")[0];

		// Add Muted list & hook up unmute logic
		$('<div id="muted_users" class="robin-chat--sidebar-widget robin-chat--notification-widget"><strong>Ignored users</strong></div>').insertAfter($("#robinDesktopNotifier"));
		$('#muted_users').click(function(e){
			var user = $(e.target).data("usr");
			if(user) _unmute_user(user);
		});

		// Hook up toggles for filters
		$('<div id="filter_tabs"><span data-action="unfilter" class="selected">Everything</span> <span data-filter="%">%</span> <span data-filter="~">~</span> <span data-filter="$">$</span> <span data-filter="#">#</span>  <span data-filter=".">.</span> <span data-filter="?">?</span> <span data-filter="^">^</span> <span data-filter="&">&amp;</span> <span data-filter="+">+</span>  <span data-filter="!">!</span> <span data-filter="*">*</span> <span data-action="channels">All channels</span> <span data-action="hide">No channels</span></div>').insertAfter("#robinChatWindow").click(function(e){
			var filter = $(e.target).data("filter");
			var action = $(e.target).data("action");
			// filter was toggled?
			if(!filter && !action) return;

			var robin_window = $("#robinChatWindow");
			// Toggle selected "tab"
			$(e.target).parent().find("span").removeClass("selected");
			$(e.target).addClass("selected");

			// remove filters
			robin_window.removeAttr("data-filter").removeClass("filter_on").removeClass("filter_all_channels").removeClass("filter_only_channels");

			if(action === 'unfilter'){
				// Show all	
			}else if(action === 'hide'){
				// Hide any channel conversations
				robin_window.addClass("filter_all_channels");
			}else if(action === 'channels'){
				// Hide any channel conversations
				robin_window.addClass("filter_only_channels");
			}else{
				// apply a filter
				robin_window.addClass("filter_on").attr("data-filter", filter);

				// blank count on view
				channel_unread_counts[filter] = 0;
				$(e.target).find("span").text("0");
			}

			// correct scroll after filter change
			_scroll_to_bottom();
		});
		// add counter
		$("#filter_tabs span[data-filter]").append(" (<span>0</span>)");


		// Auto append % when in filtered mode
		$("#robinSendMessage").submit(function(){
			if($("#robinChatWindow").hasClass("filter_on")){

				// if "/me" cut off chars, and rebuild with "channel"
				if($(".text-counter-input").val().indexOf("/me") === 0){
					$(".text-counter-input").val("/me " + $("#robinChatWindow").attr("data-filter") + " " + $(".text-counter-input").val().substr(3));
				}else if($(".text-counter-input").val().indexOf("/") !== 0){
					// if its not a "/" command, add channel
					$(".text-counter-input").val($("#robinChatWindow").attr("data-filter") + " " + $(".text-counter-input").val());
				}
				// other /commands should work as normal ( no channel ) so leave em by
			}
		});

		// apply updates to counts every 5 seconds
		setInterval(_apply_updates, 1000);
	});

	// fix by netnerd01
	var stylesheet = document.createElement('style');
	document.head.appendChild(stylesheet);
	stylesheet = stylesheet.sheet;

	// filter for channel
	stylesheet.insertRule("#robinChatWindow.filter_on div.robin-message { display:none; }", 0);
	stylesheet.insertRule("#robinChatWindow.filter_on[data-filter='$'] div.robin-message.filter-dollar,#robinChatWindow.filter_on[data-filter='*'] div.robin-message.filter-star,#robinChatWindow.filter_on[data-filter='.'] div.robin-message.filter-dot, #robinChatWindow.filter_on[data-filter='!'] div.robin-message.filter-exclaim,#robinChatWindow.filter_on[data-filter='?'] div.robin-message.filter-question,#robinChatWindow.filter_on[data-filter='&'] div.robin-message.filter-and, #robinChatWindow.filter_on[data-filter='%'] div.robin-message.filter-percent,#robinChatWindow.filter_on[data-filter='#'] div.robin-message.filter-hash,#robinChatWindow.filter_on[data-filter='~'] div.robin-message.filter-tilde,#robinChatWindow.filter_on[data-filter='^'] div.robin-message.filter-hat,#robinChatWindow.filter_on[data-filter='+'] div.robin-message.filter-plus,#robinChatWindow.filter_on div.robin-message.robin--user-class--system  { display:block; }", 0);

	// filter, hide all channels
	stylesheet.insertRule("#robinChatWindow.filter_all_channels div.robin-message.in-channel{ display:none; }", 0);

	// filter show all channels
	stylesheet.insertRule("#robinChatWindow.filter_only_channels div.robin-message {display:none; }", 0);
	stylesheet.insertRule("#robinChatWindow.filter_only_channels div.robin-message.in-channel, #robinChatWindow.filter_only_channels div.robin-message.robin--user-class--system  { display:block; }", 0);

	// Styles for filter tabs
	stylesheet.insertRule("#filter_tabs { display: table; width:100%;table-layout: fixed; background:#d7d7d2; border-bottom:1px solid #efefed;'}", 0);
	stylesheet.insertRule("#filter_tabs > span { padding: 5px 2px;text-align: center; display: table-cell; cursor: pointer;width:2%; vertical-align: middle; font-size: 1.1em;}", 0);
	stylesheet.insertRule("#filter_tabs > span.selected, #filter_tabs span:hover { background: #fff;}", 0);
	stylesheet.insertRule("#filter_tabs > span > span {pointer-events: none;}", 0);

	//mentions should show even in filter view
	stylesheet.insertRule("#robinChat #robinChatWindow div.robin-message.user-mention { display:block; font-weight:bold; }", 0);

	// Add initial styles for "spam" messages
	stylesheet.insertRule("#robinChat #robinChatWindow.hide-spam div.robin-message.spam-hidden { display:none; }", 0);
	stylesheet.insertRule("#robinChat #robinChatWindow.mute-spam div.robin-message.spam-hidden { opacity:0.3; font-size:1.2em; }", 0);

	// muted user box
	stylesheet.insertRule("#muted_users { font-size:1.2em; }", 0);
	stylesheet.insertRule("#muted_users div { padding: 2px 0; }", 0);
	stylesheet.insertRule("#muted_users strong { font-weight:bold; }", 0);

	// FIX RES nightmode (ish) [ by Kei ]
	stylesheet.insertRule(".res-nightmode #robinChatWindow div.robin-message { color: #ccc; }", 0);
	stylesheet.insertRule(".res-nightmode .robin-chat--sidebar-widget { background: #222; color: #ccc;}", 0);
	stylesheet.insertRule(".res-nightmode .robin-room-participant { background: #222; color: #999;}", 0);
	stylesheet.insertRule(".res-nightmode #filter_tabs {background: rgb(51, 51, 51);}", 0);
	stylesheet.insertRule(".res-nightmode #filter_tabs > span.selected,.res-nightmode #filter_tabs > span:hover {background: rgb(34, 34, 34)}", 0);
	stylesheet.insertRule(".res-nightmode .robin-chat--input { background: #222 }", 0);
	stylesheet.insertRule(".res-nightmode .robin--presence-class--away .robin--username {color: #999;}", 0);
	stylesheet.insertRule(".res-nightmode .robin--presence-class--present .robin--username {color: #ccc;}", 0);
	stylesheet.insertRule(".res-nightmode #robinChat .robin--user-class--self .robin--username { color: #999; }", 0);
	stylesheet.insertRule(".res-nightmode .robin-chat--vote { background: #777; color: #ccc;}", 0);
	stylesheet.insertRule(".res-nightmode .robin-chat--buttons button.robin-chat--vote.robin--active { background: #ccc; color:#999; }", 0);

	$(document).ready(function(){
		setTimeout(function(){
			// Play nice with robin grow (makes room for tab bar we insert)
			if($(".usercount.robin-chat--vote").length !== 0){
				_robin_grow_detected = true;
				stylesheet.insertRule("#robinChat.robin-chat .robin-chat--body { height: calc(100vh - 150px); }", 0);
			}
		},500);
	});

	// Allow me to sneek functions in front of other libaries - used when working with robin grow >.< sorry guys
	//http://stackoverflow.com/questions/2360655/jquery-event-handlers-always-execute-in-order-they-were-bound-any-way-around-t
	$.fn.bindFirst = function(name, fn) {
		// bind as you normally would
		// don't want to miss out on any jQuery magic
		this.on(name, fn);

		// Thanks to a comment by @Martin, adding support for
		// namespaced events too.
		this.each(function() {
			var handlers = $._data(this, 'events')[name.split('.')[0]];
			// take out the handler we just inserted from the end
			var handler = handlers.pop();
			// move it at the beginning
			handlers.splice(0, 0, handler);
		});
	};

})();