// ==UserScript==
// @name Robin Grow
// @namespace http://tampermonkey.net/
// @version 2.1.2
// @description Try to take over the world!
// @author /u/mvartan
// @include https://www.reddit.com/robin*
// @updateURL https://github.com/vartan/robin-grow/raw/master/robin.user.js
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// ==/UserScript==
(function() {
if (!window.GM_addStyle) {
window.GM_addStyle = function(styles) {
$("body").append($(document.createElement("style")).attr("type", "text/css").text(styles));
};
}
// Styles
GM_addStyle('.robin--username {cursor: pointer} #robin-grow-tabbar {padding-left:10px;} .robin-grow-tab {cursor:pointer; display: inline-block !important;width: auto;padding: 7px;font-size: 16pt !important;text-transform:none !important;}');
GM_addStyle('.robin--username {cursor: pointer}');
GM_addStyle('#standingsTable table {width: 100%}');
GM_addStyle('#standingsTable table th {font-weight: bold}');
var currentChannelTab = "";
function getChannelPrefix() {
var channels = settings.channel && settings.channel.split(",") || "";
return currentChannelTab || settings.filterChannel && settings.channel && settings.channel[0] || "";
}
// Utils
function hasChannel(source, channels) {
var channelParts = channels.split(",");
for(var ci in channelParts) {
var channel = channelParts[ci];
console.log("checking "+channel)
if(String(source).toLowerCase().startsWith(channel))
return true;
}
return false;
}
function formatNumber(n) {
var part = n.toString().split(".");
part[0] = part[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return part.join(".");
}
function addMins(date, mins) {
var newDateObj = new Date(date.getTime() + mins * 60000);
return newDateObj;
}
function grabStandings() {
var standings;
$.ajax({
url: 'https://www.reddit.com/r/robintracking/comments/4czzo2/robin_chatter_leader_board_official/.rss?limit=1',
data: {},
success: function( data ) {
var currentRoomName = $('.robin-chat--room-name').text();
var standingsPost = $(data).find("entry > content").first();
var decoded = $($('
').html(standingsPost).text()).find('table').first();
decoded.find('tr').each(function(i) { var row = $(this).find('td,th');
var nameColumn = $(row.get(2));
nameColumn.find('a').prop('target','_blank');
if (currentRoomName.startsWith(nameColumn.text().substring(0,6))) {
row.css('background-color', '#22bb45');
}
row.each(function(j) {if (j == 3 || j == 4 || j > 5) {
$(this).remove();
}});
});
$("#standingsTable").html(decoded);
},
dataType: 'xml'
});
}
var standingsInterval = 0;
function startStandings() {
stopStandings();
standingsInterval = setInterval(grabStandings, 120000);
grabStandings();
}
function stopStandings() {
if (standingsInterval){
clearInterval(standingsInterval);
standingsInterval = 0;
}
}
function howLongLeft(endTime) {
if (endTime === null) {
return 0;
}
try {
return Math.floor((endTime - new Date()) / 60 / 1000 * 10) / 10;
} catch (e) {
return 0;
}
}
function filterChannelMessage(message) {
console.log(message);
var messageTextNode = (message && message.childNodes && message.childNodes[5]);
var messageText = (messageTextNode && messageTextNode.innerText) || "";
if(messageText.indexOf(currentChannelTab) !== 0) {
$(message).hide();
console.log("Should filter "+messageText);
}
}
function configureTabs() {
}
function filterChannelAllMessages() {
$(".robin-message.robin--user-class--user, .robin-message.robin--user-class--self").each(function(i, el) {
$(el).show();
filterChannelMessage(el);
});
}
function clearChat() {
$("#robinChatMessageList").text("");
}
function chooseChannel(el) {
el = el.target || el;
$(".robin-grow-tab").removeClass("robin--active");
$(el).addClass("robin--active");
if(el.innerText == "All") {
currentChannelTab = "";
} else {
currentChannelTab = el.innerText;
}
filterChannelAllMessages();
}
function setupTabs() {
$("#robin-grow-tabbar").html("Channels: ");
$("#robin-grow-tabbar").append("All
");
var channels = settings.channel.split(",").map(function(filter) { return filter.trim().toLowerCase() });
channels.forEach(function(channel) {
if(channel.length > 0)
$("#robin-grow-tabbar").append(""+channel+"
");
})
var foundChannel = false;
$(".robin-grow-tab").each(function(i, el) {
if(el.innerText == currentChannelTab) {
foundChannel = true;
chooseChannel(el);
}
})
if(!foundChannel) {
currentChannelTab = "";
}
$(".robin-grow-tab").click(chooseChannel);
}
var Settings = {
setupUI: function() {
$robinVoteWidget.prepend("");
$robinVoteWidget.prepend("");
// Open Settings button
$robinVoteWidget.append('');
$(".robin-chat--main").prepend("")
$robinVoteWidget.append('');
// Setting container
$(".robin-chat--sidebar").before(
''
);
// Standing container
$("#settingContainer").before(
''
);
$("#robinDesktopNotifier").detach().appendTo("#settingContent");
$("#openBtn").on("click", function openSettings() {
$(".robin-chat--sidebar").hide();
$("#settingContainer").show();
});
$("#standingsBtn").on("click", function openStandings() {
$(".robin-chat--sidebar").hide();
startStandings();
$("#standingsContainer").show();
});
$("#closeBtn").on("click", function closeSettings() {
$(".robin-chat--sidebar").show();
$("#settingContainer").hide();
$("#standingsContainer").hide();
});
$("#closeStandingsBtn").on("click", function closeStandings() {
$(".robin-chat--sidebar").show();
stopStandings();
$("#standingsContainer").hide();
$("#settingContainer").hide();
});
function setVote(vote) {
return function() {
settings.vote = vote;
Settings.save(settings);
};
}
$(".robin-chat--vote.robin--vote-class--abandon").on("click", setVote("abandon"));
$(".robin-chat--vote.robin--vote-class--continue").on("click", setVote("stay"));
$(".robin-chat--vote.robin--vote-class--increase").on("click", setVote("grow"));
$('.robin-chat--buttons').prepend("");
$robinVoteWidget.find('.robin-chat--vote').css('padding', '5px');
if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) $('.robin-chat--user-list-widget').css('margin-top', '122px');
$('.robin--vote-class--novote').css('pointer-events', 'none');
},
load: function loadSetting() {
var setting = localStorage["robin-grow-settings"];
try {
setting = setting ? JSON.parse(setting) : {};
} catch(e) {}
setting = setting || {};
if (!setting.vote)
setting.vote = "grow";
return setting;
},
save: function saveSetting(settings) {
localStorage["robin-grow-settings"] = JSON.stringify(settings);
},
addBool: function addBoolSetting(name, description, defaultSetting) {
defaultSetting = settings[name] || defaultSetting;
$("#settingContent").append('');
$("input[name='setting-" + name + "']").on("click", function() {
settings[name] = !settings[name];
Settings.save(settings);
});
if (settings[name] !== undefined) {
$("input[name='setting-" + name + "']").prop("checked", settings[name]);
} else {
settings[name] = defaultSetting;
}
},
addInput: function addInputSetting(name, description, defaultSetting) {
defaultSetting = settings[name] || defaultSetting;
$("#settingContent").append('');
$("input[name='setting-" + name + "']").prop("defaultValue", defaultSetting)
.on("change", function() {
settings[name] = $(this).val();
Settings.save(settings);
setupTabs();
});
settings[name] = defaultSetting;
},
addButton: function(id, description, callback, options) {
options = options || {};
$("#settingContent").append('');
$('#' + id).on('click', function(e) { callback(e, options); });
}
};
var currentUsersName = $('div#header span.user a').html();
// Settings
var $robinVoteWidget = $("#robinVoteWidget");
// IF the widget isn't there, we're probably on a reddit error page.
if (!$robinVoteWidget.length) {
// Don't overload reddit, wait a bit before reloading.
setTimeout(function() {
window.location.reload();
}, 15000);
return;
}
Settings.setupUI($robinVoteWidget);
var settings = Settings.load();
// Options begin
Settings.addButton("clearChat", "Clear Chat", clearChat);
Settings.addBool("removeSpam", "Remove bot spam", true);
Settings.addBool("findAndHideSpam", "Remove messages that have been sent more than 3 times", true);
Settings.addInput("maxprune", "Max messages before pruning", "500");
Settings.addInput("spamFilters", "Custom spam filters, comma delimited.", "spam example 1, spam example 2");
Settings.addInput("channel", "Channel filter", "");
Settings.addBool("filterChannel", "Filter by channel", false);
Settings.addBool("showtrivia", "Username Highlighing", false);
Settings.addInput("triviahosts", "Usernames to highlight, comma delimited.", "dthunder,nbagf");
Settings.addBool("reportStats", "Contribute statistics to the Automated Leaderboard.", false);
Settings.addInput("statReportingInterval", "Report Statistics Interval (seconds)", "60");
// Options end
// Add version at the end (if available from script engine)
var versionString = "";
if (typeof GM_info !== "undefined") {
versionString = " - v" + GM_info.script.version;
}
$("#settingContent").append('');
// Settings end
var timeStarted = new Date();
var name = $(".robin-chat--room-name").text();
var urlRegex = new RegExp(/(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?/ig);
var list = {};
// Instead of forcing the channel filter into the box all the time, lets hook this more intuitively
var targetTextBox = $("#robinSendMessage").find("input[type='text']");
targetTextBox.next().on('click', function ()
{
if( settings.filterChannel && String(settings.channel).length > 0 )
{
var sendingMessage = targetTextBox.val();
if( sendingMessage.length <= 0 ) return false;
if( sendingMessage.startsWith("/") ) return true; // this is a command, we dont need to do anything
if( sendingMessage.startsWith("-") )
{
targetTextBox.val(sendingMessage.substring(1)); // Remove the - character from the beginning of the string
return true; // this prefix means we should not touch output (raw)
}
// Append our chat prefix to the outgoing message
if( sendingMessage.indexOf(currentChannelTab) != 0 ) {
console.log("adding channel to message");
targetTextBox.val(currentChannelTab + " " + sendingMessage);
}
}
});
/*
$(".text-counter-input").val(settings.filterChannel? settings.channel+" " :"")
$(".text-counter-input").keyup(function(e) {
if(settings.filterChannel && $(".text-counter-input").val().indexOf(settings.channel) != 0) {
$(".text-counter-input").val(settings.channel+" "+$(".text-counter-input").val())
}
});
*/
function fixMessage() {
var messageText = $(".text-counter-input").val();
if(messageText.indexOf(getChannelPrefix()) != 0) {
$(".text-counter-input").val(getChannelPrefix()+" "+messageText);
}
if(messageText.indexOf("/me") == 0) {
$(".text-counter-input").val("/me "+getChannelPrefix()+" " + messageText.substring(currentChannelTab.length+3));
}
}
$("#robinSendMessage").submit(fixMessage);
var isEndingSoon = false;
var endTime = null;
// Grab the timestamp from the time remaining message and then calc the ending time using the estimate it gives you
function getEndTime() { // mostly from /u/Yantrio, modified by /u/voltaek
var remainingMessageContainer = $(".robin--user-class--system:contains('approx')");
if (remainingMessageContainer.length === 0) {
// for cases where it says "soon" instead of a time on page load
var endingSoonMessageContainer = $(".robin--user-class--system:contains('soon')");
if (endingSoonMessageContainer.length !== 0) {
isEndingSoon = true;
}
return null;
}
var message = $(".robin-message--message", remainingMessageContainer).text();
var time = new Date($(".robin-message--timestamp", remainingMessageContainer).attr("datetime"));
try {
return addMins(time, message.match(/\d+/)[0]);
} catch (e) {
return null;
}
}
endTime = getEndTime();
var lastStatisticsUpdate = 0;
function update() {
switch(settings.vote) {
case "abandon":
$(".robin-chat--vote.robin--vote-class--abandon:not('.robin--active')").click();
break;
case "stay":
$(".robin-chat--vote.robin--vote-class--continue:not('.robin--active')").click();
break;
case "grow":
default:
$(".robin-chat--vote.robin--vote-class--increase:not('.robin--active')").click();
break;
}
if (endTime === null && !isEndingSoon) {
$(".timeleft").hide();
endTime = getEndTime();
} else {
$(".timeleft").show().text(isEndingSoon ? "ending soon" : formatNumber(howLongLeft(endTime)) + " minutes remaining");
}
var users = 0;
$.get("/robin/", function(a) {
var START_TOKEN = "";
var start = a.substring(a.indexOf(START_TOKEN)+START_TOKEN.length);
var end = start.substring(0,start.indexOf(END_TOKEN));
config = JSON.parse(end);
list = config.robin_user_list;
var counts = list.reduce(function(counts, voter) {
counts[voter.vote] += 1;
return counts;
}, {
INCREASE: 0,
ABANDON: 0,
NOVOTE: 0,
CONTINUE: 0
});
$robinVoteWidget.find('.robin--vote-class--increase .robin-chat--vote-label').html('grow
(' + formatNumber(counts.INCREASE) + ')');
$robinVoteWidget.find('.robin--vote-class--abandon .robin-chat--vote-label').html('abandon
(' + formatNumber(counts.ABANDON) + ')');
$robinVoteWidget.find('.robin--vote-class--novote .robin-chat--vote-label').html('no vote
(' + formatNumber(counts.NOVOTE) + ')');
$robinVoteWidget.find('.robin--vote-class--continue .robin-chat--vote-label').html('stay
(' + formatNumber(counts.CONTINUE) + ')');
users = list.length;
$(".usercount").text(formatNumber(users) + " users in chat");
currentTime = Math.floor(Date.now()/1000);
if(settings.reportStats && (currentTime-lastStatisticsUpdate)>=parseInt(settings.statReportingInterval))
{
lastStatisticsUpdate = currentTime;
// Report statistics to the automated leaderboard
trackers = [
"https://monstrouspeace.com/robintracker/track.php"
];
queryString = "?id=" + config.robin_room_name.substr(0,10) +
"&guid=" + config.robin_room_id +
"&ab=" + counts.ABANDON +
"&st=" + counts.CONTINUE +
"&gr=" + counts.INCREASE +
"&nv=" + counts.NOVOTE +
"&count=" + users +
"&ft=" + Math.floor(config.robin_room_date / 1000) +
"&rt=" + Math.floor(config.robin_room_reap_time / 1000);
trackers.forEach(function(tracker){
$.get(tracker + queryString);
});
}
});
var lastChatString = $(".robin-message--timestamp").last().attr("datetime");
var timeSinceLastChat = new Date() - (new Date(lastChatString));
var now = new Date();
if (timeSinceLastChat !== undefined && (timeSinceLastChat > 5*60000 && now - timeStarted > 5*60000)) {
window.location.reload(); // reload if we haven't seen any activity in a minute.
}
// Try to join if not currently in a chat
if ($("#joinRobinContainer").length) {
$("#joinRobinContainer").click();
setTimeout(function() {
$("#joinRobin").click();
}, 1000);
}
}
// if (GM_getValue("chatName") != name) {
// GM_setValue("chatName", name);
// setTimeout(function() {
// var oldVal = $(".text-counter-input").val();
// $(".text-counter-input").val("[Robin-Grow] I automatically voted to grow, and so can you! http://redd.it/4cwk2s !");
// $("#sendBtn").click();
// $(".text-counter-input").val(oldVal);
//
// }, 10000);
// }
// hash string so finding spam doesn't take up too much memory
function hashString(str) {
var hash = 0;
if (str != 0) {
for (i = 0; i < str.length; i++) {
char = str.charCodeAt(i);
if (str.charCodeAt(i) > 0x40) { // Let's try to not include the number in the hash in order to filter bots
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
}
}
return hash;
}
// Searches through all messages to find and hide spam
var spamCounts = {};
function findAndHideSpam() {
var $messages = $(".robin-message");
var maxprune = parseInt(settings.maxprune || "1000", 10);
if (maxprune < 10 || isNaN(maxprune)) {
maxprune = 1000;
}
if ($messages.length > maxprune) {
$messages.slice(0, $messages.length - maxprune).remove();
}
if (settings.findAndHideSpam) {
// skips over ones that have been hidden during this run of the loop
$('.robin--user-class--user .robin-message--message:not(.addon--hide)').each(function() {
var $this = $(this);
var hash = hashString($this.text());
var user = $('.robin-message--from', $this.closest('.robin-message')).text();
if (!(user in spamCounts)) {
spamCounts[user] = {};
}
if (hash in spamCounts[user]) {
spamCounts[user][hash].count++;
spamCounts[user][hash].elements.push(this);
} else {
spamCounts[user][hash] = {
count: 1,
text: $this.text(),
elements: [this]
};
}
$this = null;
});
$.each(spamCounts, function(user, messages) {
$.each(messages, function(hash, message) {
if (message.count >= 3) {
$.each(message.elements, function(index, element) {
//console.log("SPAM REMOVE: "+$(element).closest('.robin-message').text())
$(element).closest('.robin-message').addClass('addon--hide').remove();
});
} else {
message.count = 0;
}
message.elements = [];
});
});
}
}
// faster to save this in memory
/* Detects unicode spam - Credit to travelton
* https://gist.github.com/travelton */
var UNICODE_SPAM_RE = /[\u0080-\uFFFF]/;
function isBotSpam(text) {
// starts with a [, has "Autovoter", or is a vote
var filter = text.indexOf("[") === 0 ||
text == "voted to STAY" ||
text == "voted to GROW" ||
text == "voted to ABANDON" ||
text.indexOf("Autovoter") > -1 ||
(UNICODE_SPAM_RE.test(text));
var spamFilters = settings.spamFilters.split(",").map(function(filter) { return filter.trim().toLowerCase() });
spamFilters.forEach(function(filterVal) {
filter = filter || filterVal.length > 0 && text.toLowerCase().indexOf(filterVal) >= 0
})
// if(filter)console.log("removing "+text);
return filter;
}
// Individual mute button /u/verox-
var mutedList = settings.mutedUsersList || [];
$('body').on('click', ".robin--username", function() {
var username = $(this).text();
var clickedUser = mutedList.indexOf(username);
if (clickedUser == -1) {
// Mute our user.
mutedList.push(username);
this.style.textDecoration = "line-through";
} else {
// Unmute our user.
this.style.textDecoration = "none";
mutedList.splice(clickedUser, 1);
}
settings.mutedUsersList = mutedList;
Settings.save(settings);
listMutedUsers();
});
$("#settingContent").append("Muted Users");
$("#settingContent").append("");
function listMutedUsers() {
$("#blockedUserList").html("");
$.each(mutedList, function(index, value){
var mutedHere = "present";
var userInArray = $.grep(list, function(e) {
return e.name === value;
});
if (userInArray[0].present === true) {
mutedHere = "present";
} else {
mutedHere = "away";
}
$("#blockedUserList").append(
$("")
.append("" + value + "")
);
});
}
setTimeout(function() {
listMutedUsers();
}, 1500);
// credit to wwwroth for idea (notification audio)
// i think this method is better
var notifAudio = new Audio("https://slack.global.ssl.fastly.net/dfc0/sounds/push/knock_brush.mp3");
var myObserver = new MutationObserver(mutationHandler);
//--- Add a target node to the observer. Can only add one node at a time.
// XXX Shou: we should only need to watch childList, more can slow it down.
$("#robinChatMessageList").each(function() {
myObserver.observe(this, { childList: true });
});
function mutationHandler(mutationRecords) {
mutationRecords.forEach(function(mutation) {
var jq = $(mutation.addedNodes);
// There are nodes added
if (jq.length > 0) {
// cool we have a message.
var thisUser = $(jq[0].children && jq[0].children[1]).text();
var $message = $(jq[0].children && jq[0].children[2]);
var messageText = $message.text();
if($message[0])
filterChannelMessage(jq[0]);
var remove_message =
(mutedList.indexOf(thisUser) >= 0) ||
(settings.removeSpam && isBotSpam(messageText)) ||
(settings.filterChannel &&
!jq.hasClass('robin--user-class--system') &&
String(settings.channel).length > 0 &&
!hasChannel(messageText, settings.channel));
// Trivia bot highlighting
if( settings.showtrivia ) {
$.each(settings.triviahosts.split(','), function(key, value) {
if( value.toLowerCase() == thisUser.toLowerCase() ) {
$(jq[0]).css("background", "rgba(107, 207, 95, 0.8)").css("color", "white").css("font-weight", "bold");
remove_message = false;
return;
}
});
}
if(nextIsRepeat && jq.hasClass('robin--user-class--system')) {
}
var nextIsRepeat = jq.hasClass('robin--user-class--system') && messageText.indexOf("try again") >= 0;
if(nextIsRepeat) {
$(".text-counter-input").val(jq.next().find(".robin-message--message").text());
}
remove_message = remove_message && !jq.hasClass("robin--user-class--system");
if (remove_message) {
$message = null;
$(jq[0]).remove();
} else {
if(settings.filterChannel) {
if(messageText.indexOf(settings.channel) == 0) {
$message.text(messageText.substring(settings.channel.length).trim());
}
}
if (messageText.toLowerCase().indexOf(currentUsersName.toLowerCase()) !== -1) {
$message.parent().css("background","#FFA27F").css("color","white");
notifAudio.play();
console.log("got new mention");
}
if(urlRegex.test(messageText)) {
urlRegex.lastIndex = 0;
var url = encodeURI(urlRegex.exec(messageText)[0]);
var parsedUrl = url.replace(/^/, ""+url+"");
var oldHTML = $(jq[0]).find('.robin-message--message').html();
var newHTML = oldHTML.replace(url, parsedUrl);
$(jq[0]).find('.robin-message--message').html(newHTML);
}
findAndHideSpam();
}
}
});
}
setInterval(update, 10000);
update();
var flairColor = [
'#e50000', // red
'#db8e00', // orange
'#ccc100', // yellow
'#02be01', // green
'#0083c7', // blue
'#820080' // purple
];
function colorFromName(name) {
sanitizedName = name.toLowerCase().replace(/[^a-z0-9]/g, "");
flairNum = parseInt(sanitizedName, 36) % 6;
return flairColor[flairNum];
}
// Initial pass to color names in user list
$('#robinUserList').find('.robin--username').each(function(){
this.style.color = colorFromName(this.textContent);
});
// When a user's status changes, they are removed from the user list and re-added with new status classes,
// so here we watch for names being added to the user list to re-color
var myUserListObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length > 0) {
var usernameSpan = mutation.addedNodes[0].children[1];
usernameSpan.style.color = colorFromName(usernameSpan.innerHTML);
}
});
});
myUserListObserver.observe(document.getElementById("robinUserList"), { childList: true });
// Color current user's name in chat and darken post backgrounds
var currentUserColor = colorFromName(currentUsersName);
$('').appendTo('body');
// Send message button
$("#robinSendMessage").append('Send Message
'); // Send message
$("#sendBtn").bind("mousedown touchstart", function(e) {
fixMessage();
});
$('#robinChatInput').css('background', '#EFEFED');
// Full-screen-height chat
$('').appendTo('body');
// RES Night Mode support
if ($("body").hasClass("res")) {
$('').appendTo('body');
}
setupTabs();
})();