- // ==UserScript==
- // @name Robin Enhancement Script
- // @namespace https://www.reddit.com/
- // @version 2.0.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;
- }
-
- /**
- * 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]++;
- }
- }
- };
-
- var _apply_updates = function(){
-
- // Update unread counts
- $("#filter_tabs span").each(function(){
- 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="&">&</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($(".text-counter-input").val().indexOf("/") !== 0){ // /commands should work as normal
- $(".text-counter-input").val($("#robinChatWindow").attr("data-filter") + " " + $(".text-counter-input").val());
- }
- }
- });
-
- // apply updates to counts every 5 seconds
- setInterval(_apply_updates, 1000);
- });
-
-
-
-
- // filter for channel
- document.styleSheets[0].insertRule("#robinChatWindow.filter_on div.robin-message { display:none; }", 0);
- document.styleSheets[0].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
- document.styleSheets[0].insertRule("#robinChatWindow.filter_all_channels div.robin-message.in-channel{ display:none; }", 0);
-
- // filter show all channels
- document.styleSheets[0].insertRule("#robinChatWindow.filter_only_channels div.robin-message {display:none; }", 0);
- document.styleSheets[0].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
- document.styleSheets[0].insertRule("#filter_tabs { display: table; width:100%;table-layout: fixed; background:#d7d7d2; border-bottom:1px solid #efefed;'}", 0);
- document.styleSheets[0].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);
- document.styleSheets[0].insertRule("#filter_tabs > span.selected, #filter_tabs span:hover { background: #fff;}", 0);
-
- //mentions should show even in filter view
- document.styleSheets[0].insertRule("#robinChat #robinChatWindow div.robin-message.user-mention { display:block; font-weight:bold; }", 0);
-
- // Add initial styles for "spam" messages
- document.styleSheets[0].insertRule("#robinChat #robinChatWindow.hide-spam div.robin-message.spam-hidden { display:none; }", 0);
- document.styleSheets[0].insertRule("#robinChat #robinChatWindow.mute-spam div.robin-message.spam-hidden { opacity:0.3; font-size:1.2em; }", 0);
-
- // muted user box
- document.styleSheets[0].insertRule("#muted_users { font-size:1.2em; }", 0);
- document.styleSheets[0].insertRule("#muted_users div { padding: 2px 0; }", 0);
- document.styleSheets[0].insertRule("#muted_users strong { font-weight:bold; }", 0);
-
- // FIX RES nightmode (ish)
- document.styleSheets[0].insertRule(".res-nightmode #robinChatWindow div.robin-message { color: #ccc; }", 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;
- document.styleSheets[0].insertRule("#robinChat.robin-chat .robin-chat--body { height: calc(100vh - 150px); }", 0);
- }
- },500);
- });
- })();