// ==UserScript== // @name parrot (color multichat for robin!) // @namespace http://tampermonkey.net/ // @version 3.65 // @description Recreate Slack on top of an 8 day Reddit project. // @author dashed, voltaek, daegalus, vvvv, orangeredstilton, lost_penguin, AviN456, Annon201, LTAcosta, mofosyne // @include https://www.reddit.com/robin* // @updateURL https://github.com/5a1t/parrot/raw/master/robin.user.js // @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js // @require https://raw.githubusercontent.com/ricmoo/aes-js/master/index.js // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // ==/UserScript== (function() { // hacky solutions var CURRENT_CHANNEL = ""; var GOTO_BOTTOM = true; var robinChatWindow = $('#robinChatWindow'); String.prototype.lpad = function(padString, length) { var str = this; var prepend_str = ""; for (var i = str.length; i < length; i++) { prepend_str = padString + prepend_str; } return prepend_str + str; }; String.prototype.rpad = function(padString, length) { var str = this; var prepend_str = ""; for (var i = str.length; i < length; i++) { prepend_str = padString + prepend_str; } return str + prepend_str; }; function tryHide(){ if(settings.hideVote){ console.log("hiding vote buttons."); $('.robin-chat--buttons').hide(); } else{ $('.robin-chat--buttons').show(); } } // Channel selected in channel drop-down function dropdownChannel() { return $("#chat-prepend-select").val().trim(); } function buildDropdown() { split_channels = getChannelString().split(","); drop_html = ""; for (var tag in split_channels) { var channel_name = split_channels[tag].trim(); drop_html += ''; } $("#chat-prepend-select").html(drop_html); $("#chat-prepend-select").on("change", function() { updateTextCounter(); }); } function updateUserPanel(){ var options = { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }; $(".robin-room-participant").each( function(){ lastseen = userExtra[$(this).find(".robin--username").text().trim()]; if(lastseen){ datestring = lastseen.toLocaleTimeString("en-us", options); $( this ).find(".robin--username").nextAll().remove(); $( this ).find(".robin--username").after("  " + datestring + ""); } }); } // Utils function getChannelString() { return settings.filterChannel ? settings.channel : "," + settings.channel; } function getChannelList() { var channels = String(getChannelString()).split(","); var channelArray = []; for (i = 0; i < channels.length; i++) { var channel = channels[i].trim(); if (channel.length > 0) channelArray.push(channel.toLowerCase()); } return channelArray; } function hasChannel(source) { channel_array = getChannelList(); source = String(source).toLowerCase(); return hasChannelFromList(source, channel_array, false); } function hasChannelFromList(source, channels, shall_trim, ignore_empty) { channel_array = channels; source = shall_trim ? String(source).toLowerCase().trim() : String(source).toLowerCase(); for (idx = 0; idx < channel_array.length; idx++) { var current_chan = shall_trim ? channel_array[idx].trim() : channel_array[idx]; if(ignore_empty && current_chan.length <= 0) { continue; } if(source.startsWith(current_chan.toLowerCase())) { return { name: current_chan, has: true, index: idx }; } } return { name: "", has: false, index: 0 }; } 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 howLongLeft(endTime) { if (endTime === null) { return 0; } try { return Math.floor((endTime - new Date()) / 60 / 1000 * 10) / 10; } catch (e) { return 0; } } var UserExtra = { load: function loadSetting() { var userExtra = localStorage.getItem('parrot-user-extra'); var users_version = localStorage.getItem('parrot-user-version'); try { userExtra = userExtra ? JSON.parse(userExtra) : {}; } catch(e) { console.log("Error parsing userExtra..");} userExtra = userExtra || {}; if(users_version == 12){ console.log("found a good user list!"); //return userExtra; //JSON.stringify is returning undefined.. Reset list each time for now. Will fix. return {}; } else { console.log("found a bad user list, resetting!"); localStorage.setItem('parrot-user-extra', JSON.stringify({})); return {}; } }, save: function saveSetting(userExtra) { console.log("Saving"); //console.log(JSON.stringify(userExtra)); localStorage.setItem('parrot-user-extra', JSON.stringify(userExtra)); localStorage.setItem('parrot-user-version', 12); } } var Settings = { setupUI: function() { // Open Settings button $robinVoteWidget.prepend("
"); $robinVoteWidget.prepend("
"); $robinVoteWidget.prepend("
"); $robinVoteWidget.prepend('
Open Settings
'); $robinVoteWidget.append('
Show Standings
'); $robinVoteWidget.append('
Show Most Active Channels
'); $("#openBtn_wrap").prepend('
' + '
' + 'Parrot

soKukunelits fork ~ ' + versionString + '

'); // Setting container $(".robin-chat--sidebar").before( '' ); // Standing container $("#settingContainer").before( '' ); // Active channels container $("#settingContainer").before( '' ); $("#settingContent").append('
'); $("#robinDesktopNotifier").detach().appendTo("#settingContent"); $("#openBtn").on("click", function openSettings() { $(".robin-chat--sidebar").hide(); $("#settingContainer").show(); }); $("#closeBtn").on("click", function closeSettings() { $(".robin-chat--sidebar").show(); $("#settingContainer").hide(); $("#standingsContainer").hide(); $("#channelsContainer").hide(); tryHide(); update(); }); $("#standingsBtn").on("click", function openStandings() { $(".robin-chat--sidebar").hide(); startStandings(); $("#standingsContainer").show(); }); $("#closeStandingsBtn").on("click", function closeStandings() { $(".robin-chat--sidebar").show(); stopStandings(); $("#standingsContainer").hide(); $("#settingContainer").hide(); $("#channelsContainer").hide(); }); $("#channelsBtn").on("click", function openChannels() { $(".robin-chat--sidebar").hide(); startChannels(); $("#channelsContainer").show(); }); $("#closeChannelsBtn").on("click", function closeChannels() { $(".robin-chat--sidebar").show(); stopChannels(); $("#channelsContainer").hide(); $("#standingsContainer").hide(); $("#settingContainer").hide(); }); $("#robinSendMessage").prepend('
'); 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("
"); }, load: function loadSetting() { var setting = localStorage.getItem('robin-grow-settings'); try { setting = setting ? JSON.parse(setting) : {}; } catch(e) {} setting = setting || {}; toggleSidebarPosition(setting); if (!setting.vote) setting.vote = "grow"; return setting; }, save: function saveSetting(settings) { localStorage.setItem('robin-grow-settings', JSON.stringify(settings)); }, addBool: function addBoolSetting(name, description, defaultSetting, callback) { defaultSetting = settings[name] || defaultSetting; $("#settingContent").append('
'); $("input[name='setting-" + name + "']").change(function() { settings[name] = !settings[name]; Settings.save(settings); if(callback) { callback(); } }); if (settings[name] !== undefined) { $("input[name='setting-" + name + "']").prop("checked", settings[name]); } else { settings[name] = defaultSetting; } }, addRadio: function addRadioSetting(name, description, items, defaultSettings, callback) { //items JSON format: // {"id":[{"value":, // "friendlyName":}]}; defaultSettings = settings[name] || defaultSettings; $("#settingContent").append('
' + description + '

'); for (i in items.id) { $("#settingsContainer-" + name).append('
'); } $("#settingsContainer-" + name).append('
'); if (settings[name] != undefined) { $("input:radio[name='setting-" + name + "'][value='" + settings[name] + "']").prop("checked", true); } else { $("input:radio[name='setting-" + name + "'][value='" + defaultSettings + "']").prop("checked", true); } $("input:radio[name='setting-" + name + "']").on("click", function () { settings[name] = $("input:radio[name='setting-" + name + "']:checked").val(); Settings.save(settings); }); if (callback) { callback(); } }, addInput: function addInputSetting(name, description, defaultSetting, callback) { defaultSetting = settings[name] || defaultSetting; $("#settingContent").append('
'); $("input[name='setting-" + name + "']").prop("defaultValue", defaultSetting) .on("change", function() { settings[name] = String($(this).val()); Settings.save(settings); if(callback) { callback(); } }); settings[name] = defaultSetting; }, addButton: function(appendToID, newButtonID, description, callback, options) { options = options || {}; $('#' + appendToID).append('
' + description + '
'); $('#' + newButtonID).on('click', function(e) { callback(e, options); }); } }; function clearChat() { console.log("chat cleared!"); getChannelMessageList(selectedChannel).empty(); } function setRobinMessageVisibility() { var prop = (settings.removeRobinMessages) ? "none" : "block"; $('#robinMessageVisiblity') .text('.robin-message.robin--flair-class--no-flair.robin--user-class--system {display: ' + prop + ';}'); } function toggleSidebarPosition(setting) { settings = settings || setting; var elements = { header: $('.robin-chat--header'), content: $('.content[role="main"]'), votePanel: $('.robin-chat--buttons'), sidebars: $('.robin-chat--sidebar'), chat: $('.robin-chat--main') }; var sidebars = elements.sidebars.detach(); settings.sidebarPosition ? elements.chat.before(sidebars) : elements.chat.after(sidebars); } function toggleTableMode(setting) { settings = settings || setting; if (settings.tableMode) $('.robin-chat--message-list').addClass('robin-chat--message-list-table-mode'); else $('.robin-chat--message-list').removeClass('robin-chat--message-list-table-mode'); } function grabStandings() { var standings; // Reddit leaderboard $.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))) { var color = String(settings.leaderboard_current_color).length > 0 ? String(settings.leaderboard_current_color).trim() : '#22bb45'; row.css('background-color', color); } row.each(function(j) {if (j == 3 || j == 4 || j > 5) { $(this).remove(); }}); }); $("#standingsTableReddit").html(decoded); }, dataType: 'xml' }); // monstrouspeace.com tracker board $("#standingsTableMonstrous").html(""); if (settings.monstrousStats) { $.ajax({ type: 'GET', url: 'https://monstrouspeace.com/robintracker/json.php', data: { get_param: 'value' }, dataType: 'json', xhr: function() { return new GM_XHR(); }, success: function(data) { var decoded = '
MonstrousPeace.com tracking (experimental)

' + "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n"; $.each(data, function(index, e) { decoded += "\r\n"; }); decoded += "\r\n" + "
#ParticipantsGrowStayRoom NameTier
" + (index+1) + "" + e.count + "" + e.grow + "" + e.stay + "" + e.room + "" + e.tier + "
\r\n" + '
'; $("#standingsTableMonstrous").html(decoded); } }); } }; // // XHR that can cross same origin policy boundaries // function GM_XHR() { this.type = null; this.url = null; this.async = null; this.username = null; this.password = null; this.status = null; this.headers = {}; this.readyState = null; this.abort = function() { this.readyState = 0; }; this.getAllResponseHeaders = function(name) { return this.readyState != 4 ? "" : this.responseHeaders; }; this.getResponseHeader = function(name) { var regexp = new RegExp('^'+name+': (.*)$','im'); var match = regexp.exec(this.responseHeaders); if (match) { return match[1]; } return ''; }; this.open = function(type, url, async, username, password) { this.type = type ? type : null; this.url = url ? url : null; this.async = async ? async : null; this.username = username ? username : null; this.password = password ? password : null; this.readyState = 1; }; this.setRequestHeader = function(name, value) { this.headers[name] = value; }; this.send = function(data) { this.data = data; var that = this; GM_xmlhttpRequest({ method: this.type, url: this.url, headers: this.headers, data: this.data, onload: function(rsp) { for (var k in rsp) { that[k] = rsp[k]; } that.onreadystatechange(); }, onerror: function(rsp) { for (var k in rsp) { that[k] = rsp[k]; } } }); }; }; var standingsInterval = 0; function startStandings() { stopStandings(); standingsInterval = setInterval(grabStandings, 120000); grabStandings(); } function stopStandings() { if (standingsInterval){ clearInterval(standingsInterval); standingsInterval = 0; } } function updateChannels() { // Sort the channels var channels = []; for(var channel in activeChannelsCounts){ if (activeChannelsCounts[channel] > 1){ channels.push(channel); } } channels.sort(function(a,b) {return activeChannelsCounts[b] - activeChannelsCounts[a];}); // Build the table var html = "\r\n" + "\r\n" + "\r\n" + "\r\n" + "\r\n"; var limit = 50; if (channels.length < limit) limit = channels.length; for (var i = 0; i < limit; i++) { html += "\r\n"; } html += "\r\n" + "
#Channel NameJoin Channel
" + (i+1) + "" + channels[i] + "
Join Channel
\r\n" + '
'; $("#activeChannelsTable").html(html); $(".channelBtn").on("click", function joinChannel() { var channel = $(this).parent().prev().contents().text(); var channels = getChannelList(); if (channel && $.inArray(channel, channels) < 0) { settings.channel += "," + channel; Settings.save(settings); buildDropdown(); resetChannels(); } }); } var channelsInterval = 0; function startChannels() { stopChannels(); channelsInterval = setInterval(updateChannels, 30000); updateChannels(); } function stopChannels() { if (channelsInterval){ clearInterval(channelsInterval); channelsInterval = 0; } } var currentUsersName = $('div#header span.user a').html(); // Settings begin 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(); }, 300000); return; } // Get version string (if available from script engine) var versionString = ""; if (typeof GM_info !== "undefined") { versionString = GM_info.script.version; } Settings.setupUI($robinVoteWidget); var settings = Settings.load(); var userExtra = UserExtra.load(); startUserExtra(); function tryStoreUserExtra(){ console.log("storing lastseens"); UserExtra.save(userExtra); } var userExtraInterval = 0; function startUserExtra() { userExtraInterval = setInterval(listAllUsers, 10*1000); userExtraInterval = setInterval(tryStoreUserExtra, 20*1000); } // bootstrap tryHide(); // Options begin Settings.addButton("settingContent", "update-script-button", "Update Parrot", function(){ window.open("https://github.com/5a1t/parrot/raw/master/robin.user.js?t=" + (+ new Date()), "_blank"); }); Settings.addButton("robinChatInput", "clear-chat-button", "Clear Chat", clearChat); Settings.addBool("hideVote", "Hide voting panel", false, tryHide); Settings.addBool("removeSpam", "Remove bot spam", true); Settings.addInput("spamFilters", "", "spam example 1,John Madden"); Settings.addBool("enableUnicode", "Allow unicode characters. Unicode is considered spam and thus are filtered out", false); Settings.addBool("sidebarPosition", "Left sidebar", false, toggleSidebarPosition); Settings.addBool("force_scroll", "Force scroll to bottom", false); Settings.addInput("cipherkey", "16 Character Cipher Key", "Example128BitKey"); Settings.addInput("maxprune", "Max messages before pruning", "500"); Settings.addBool("tableMode", "Toggle table-mode for the message list", false, toggleTableMode); Settings.addInput("fontsize", "Chat font size", "12"); Settings.addInput("fontstyle", "Font Style (default Consolas)", ""); Settings.addBool("alignment", "Right align usernames", true); Settings.addInput("username_bg", "Custom background color on usernames", ""); Settings.addBool("removeRobinMessages", "Hide [robin] messages everywhere", false, setRobinMessageVisibility); Settings.addBool("removeChanMessageFromGlobal", "Hide channel messages in Global", false); Settings.addBool("filterChannel", "Hide non-channel messages in Global", false, function() { buildDropdown(); }); Settings.addInput("channel", "", "%parrot", function() { buildDropdown(); resetChannels(); }); Settings.addInput("channel_exclude", "", ""); Settings.addBool("tabChanColors", "Use color on channel tabs", true); Settings.addBool("twitchEmotes", "Twitch emotes (Normal, BTTV)", false); Settings.addBool("youtubeVideo", "Inline youtube videos", false); Settings.addBool("timeoutEnabled", "Reload page after inactivity timeout", true); Settings.addInput("messageHistorySize", "Sent Message History Size", "50"); Settings.addBool("monstrousStats", "Show automated leaderboard on standings page (asks for permission)", false); Settings.addBool("reportStats", "Contribute statistics to the Automated Leaderboard.", true); Settings.addInput("statReportingInterval", "Report Statistics Interval (seconds) [above needs to be checked]", "300"); Settings.addInput("leaderboard_current_color", "Highlight color of current chat room in leaderboard standings", '#22bb45'); Settings.addBool("enableTabComplete", "Tab Autocomplete usernames", true); Settings.addBool("enableQuickTabNavigation", "Keyboard channel-tabs navigation", true); Settings.addBool("enableAdvancedNaviOptions", "Keyboard navigation key remapping. Use custom settings below for switching channels instead:", false, function(){ $('input[name^=setting-quickTabNavi]').prop('disabled',!$('input[name=setting-enableAdvancedNaviOptions]').prop('checked')); }); Settings.addBool("quickTabNaviCtrlRequired", "Ctrl", true); Settings.addBool("quickTabNaviShiftRequired", "Shift", false); Settings.addBool("quickTabNaviAltRequired", "Alt", true); Settings.addBool("quickTabNaviMetaRequired", "Meta", false); Settings.addInput("quickTabNaviKeyLeft", "Key codes: Left = 37, Up = 38, Right = 39, Down = 40. See Mozilla.org's documentation for more key codes.

Navigate left tab final key code","37"); Settings.addInput("quickTabNaviKeyRight", "Navigate right tab final key code","39"); $('input[name^=setting-quickTabNavi]').prop('disabled',!settings.enableAdvancedNaviOptions); $("#settingContent").append("
"); $("#blockedUserContainer").append("
"); $("#settingContent").append(''); $('head').append(''); setRobinMessageVisibility(); // Options end // 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 youtubeRegex = new RegExp(/.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/); var list = {}; buildDropdown(); var isEndingSoon = false; var endTime = null; var endTimeAttempts = 0; // 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 endTimeAttempts++; 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": $(".robin-chat--vote.robin--vote-class--increase:not('.robin--active')").click(); break; default: $(".robin-chat--vote.robin--vote-class--increase:not('.robin--active')").click(); break; } if (endTime !== null || isEndingSoon) { $(".timeleft").text(isEndingSoon ? "waiting to merge" : formatNumber(howLongLeft(endTime)) + " minutes remaining"); } else if (endTimeAttempts <= 3 && endTime === null) { $("#robinVoteWidget .timeleft").parent().hide(); endTime = getEndTime(); if (endTime !== null || isEndingSoon) { $("#robinVoteWidget .timeleft").parent().show(); } } 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 }); var GROW_STR = formatNumber(counts.INCREASE); var ABANDON_STR = formatNumber(counts.ABANDON); var NOVOTE_STR = formatNumber(counts.NOVOTE); var STAY_STR = formatNumber(counts.CONTINUE); $robinVoteWidget.find('.robin--vote-class--increase .robin-chat--vote-label').html('grow
(' + GROW_STR + ')'); $robinVoteWidget.find('.robin--vote-class--abandon .robin-chat--vote-label').html('abandon
(' + ABANDON_STR + ')'); $robinVoteWidget.find('.robin--vote-class--novote .robin-chat--vote-label').html('no vote
(' + NOVOTE_STR + ')'); $robinVoteWidget.find('.robin--vote-class--continue .robin-chat--vote-label').html('stay
(' + STAY_STR + ')'); users = list.length; $(".usercount").text(formatNumber(users) + " users in chat"); currentTime = Math.floor(Date.now()/1000); // if(settings.reportStats && (currentTime-lastStatisticsUpdate)>=parseInt(settings.statReportingInterval)) // #yolo-robin till April 8th if((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 $chatstats = $("#chatstats"); if(settings.hideVote){ $chatstats.text("GROW: " + GROW_STR + " (" + (counts.INCREASE / users * 100).toFixed(0) + "%) STAY: " + STAY_STR + " (" + (counts.CONTINUE / users * 100).toFixed(0) + "%)"); $chatstats.show(); } else { $chatstats.hide(); } }); var lastChatString = $(".robin-message--timestamp").last().attr("datetime"); var timeSinceLastChat = new Date() - (new Date(lastChatString)); var now = new Date(); if (timeSinceLastChat !== undefined && (timeSinceLastChat > 600000 && now - timeStarted > 600000)) { if (settings.timeoutEnabled) 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); } } // 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() { // getChannelTab var len = channelList.length; while(len-- > -1) { //var $messages = getChannelTab(len).find(".robin-message"); var $messages = getChannelMessageList(len).find(".robin-message"); var maxprune = parseInt(settings.maxprune || "1000", 10); if (isNaN(maxprune)) { maxprune = 1000; } if ( maxprune <= 0) { maxprune = 1; } //console.log("maxprune: " + maxprune + " Messages.length: " + $messages.length + " len: " + len) ; if ($messages.length > maxprune) { $messages.slice(0, $messages.length - maxprune).remove(); } } if (false && 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) { $(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 || (!settings['enableUnicode'] && 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 = String($(this).text()).trim(); var clickedUser = mutedList.indexOf(username); var $userNames = $(".robin--username:contains(" + username + ")"); if (clickedUser == -1) { // Mute our user. mutedList.push(username); $userNames.css({textDecoration: "line-through"}); } else { // Unmute our user. $userNames.css({textDecoration: "none"}); mutedList.splice(clickedUser, 1); } settings.mutedUsersList = mutedList; Settings.save(settings); listMutedUsers(); }); // Copy cliked username into textarea /u/tW4r based on /u/verox-'s Individual mute button $('body').on('contextmenu', ".robin--username", function (event) { // Prevent context-menu from showing up event.preventDefault(); // Get clicked username and previuos input source var username = String($(this).text()).trim(); var source = String($("#robinMessageText").val()); // Focus textarea and set the value of textarea $("#robinMessageText").focus().val("").val(source + " " + username + " "); }); function listMutedUsers() { $("#blockedUserList").html(""); $.each(mutedList, function(index, value){ var mutedHere = "present"; var userInArray = $.grep(list, function(e) { return e.name === value; }); if (userInArray && userInArray.length > 0 && userInArray[0].present === true) { mutedHere = "present"; } else { mutedHere = "away"; } var votestyle = userInArray && userInArray.length > 0 ? " robin--vote-class--" + userInArray[0].vote.toLowerCase() : ""; $("#blockedUserList").append( $("
") .append("" + value + "") ); }); } setTimeout(function() { listMutedUsers(); }, 1500); function listAllUsers() { var actives = Object.keys(userExtra).map(function(key) { //console.log("key: " + key + " val: " + userExtra[key]); return [key, userExtra[key]]; }); // Sort the array based on the second element actives = actives.sort(function(first, second) { //console.log(first[1] + " is < " + second[1]); //console.log(second[1] >= first[1]); return second[1] - first[1]; }); var options = { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }; $("#robinUserList").html("

Total Actives: " + actives.length + "

"); $.each(actives, function(index,userpair){ var mutedHere = "present"; var userInArray = $.grep(list, function(e) { return e.name === userpair[0]; }); if (userInArray && userInArray.length > 0 && userInArray[0].present === true) { mutedHere = "present"; } else { mutedHere = "away"; } var votestyle = userInArray && userInArray.length > 0 ? " robin--vote-class--" + userInArray[0].vote.toLowerCase() : ""; var datestring = userpair[1].toLocaleTimeString("en-us", options); name_area = $("
") .hover(function(){$('#user-submenu-' + userpair[0]).slideDown('medium');},function(){$('#user-submenu-' + userpair[0]).slideUp('medium');}) .prepend(''); $("#robinUserList").append( name_area .append("" + userpair[0] + "" + "  " + datestring + "") ); }); //updateUserPanel(); } //colored text thanks to OrangeredStilton! https://gist.github.com/Two9A/3f33ee6f6daf6a14c1cc3f18f276dacd var colors = ['rgba(255,0,0,0.1)','rgba(0,255,0,0.1)','rgba(0,0,255,0.1)', 'rgba(0,255,255,0.1)','rgba(255,0,255,0.1)', 'rgba(255,255,0,0.1)']; //Emotes by ande_ //Normal Twitch emotes var emotes = {}; $.getJSON("https://twitchemotes.com/api_cache/v2/global.json", function(data) { emotes = data.emotes; for(var prop in emotes){ emotes[prop.toLowerCase()] = emotes[prop]; } }); //BetterTwitchTV emotes var bttvEmotes = {}; $.getJSON("https://api.betterttv.net/2/emotes", function(data) { data.emotes.forEach(function(emote){ bttvEmotes[emote.code.toLowerCase()] = emote.id; }); }); // 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"); // // Tabbed channel windows by /u/lost_penguin // var channelList = []; var selectedChannel = -1; function setupMultiChannel() { // Add div to hold tabs $("#robinChatWindow").before("
    "); // Add tab for all other messages $("#robinChannelList").append("
  • Global
  • "); // Room tab events var tab = $("#robinChannelLink"); tab.on("click", function() { selectChannel(""); }); // Add rooms resetChannels(); // Restore the selected channel in the url if (channelList[window.location.hash.charAt(8)]) selectChannel(window.location.hash); } function resetChannels() { channelList = getChannelList(); var chatBox = $("#robinChatWindow"); var tabBar = $("#robinChannelList"); // Remove all existing rooms chatBox.children().each(function() { if (this.id.startsWith("robinChatMessageList-ch")) this.remove(); }); tabBar.children().each(function() { if (this.id.startsWith("robinChannelTab-ch")) this.remove(); }); // Create fresh rooms for (i = 0; i < channelList.length; i++) { // Room message window chatBox.append("
    "); // Room tab tabBar.append("
  • " + channelList[i] + "
  • "); // Room tab event var tab = $("#robinChannelLink-ch" + i); tab.on("click", function() { selectChannel($(this).attr("href")); }); } toggleTableMode(); selectChannel(""); } function selectChannel(channelLinkId) { // Get channel index var channelIndex = -1; if ((typeof channelLinkId) == 'string' && channelLinkId.length > 8) { channelIndex = channelLinkId.substring(8); } if((typeof channelLinkId) == 'number') { channelIndex = channelLinkId; } $("#chat-prepend-select").val($("#robinChannelLink-ch" + (channelIndex >= 0 ? channelIndex : "") ).html()); // Remember selection selectedChannel = channelIndex; // Show/hide channel drop-down if (channelIndex >= 0) $("#chat-prepend-area").css("display", "none"); else $("#chat-prepend-area").css("display", ""); // Update tab selection for (i = -1; i < channelList.length; i++) setChannelSelected(getChannelTab(i), getChannelMessageList(i), channelIndex == i); updateTextCounter(); } function markChannelChanged(index) { if (index != selectedChannel) getChannelTab(index).attr("class", "robin-chan-tab-changed"); } function setChannelSelected(tab, box, select) { if (select) { tab.attr("class", "robin-chan-tab-selected"); box.css("display", ""); doScroll(); } else { if (tab.attr("class") == "robin-chan-tab-selected") tab.attr("class", ""); box.css("display", "none"); } } function getChannelTab(index) { if (index == -1) return $("#robinChannelLink"); return $("#robinChannelLink-ch" + index); } function getChannelMessageList(index) { if (index == -1) return $("#robinChatMessageList"); return $("#robinChatMessageList-ch" + index); } function convertTextToSpecial(messageText, elem) { urlRegex.lastIndex = 0; if (urlRegex.test(messageText)) { urlRegex.lastIndex = 0; var url = encodeURI(urlRegex.exec(messageText)[0]); var parsedUrl = url.replace(/^/, ""+url+""); var oldHTML = $(elem).find('.robin-message--message').html(); var newHTML = oldHTML.replace(url, parsedUrl); $(elem).find('.robin-message--message').html(newHTML); } if (settings.twitchEmotes){ var split = messageText.split(' '); var changes = false; for (var i=0; i < split.length; i++) { var key = (split[i]).toLowerCase(); if(emotes.hasOwnProperty(key)){ split[i] = ""; changes = true; } if(bttvEmotes.hasOwnProperty(key)){ split[i] = ""; changes = true; } } if (changes) { $(elem).find('.robin-message--message').html(split.join(' ')); } } // TODO this can support vine videos too if (settings.youtubeVideo) { var matches = messageText.match(youtubeRegex); if (!matches || matches[1].length !== 11) return; var youtubeId = matches[1]; $videoContainer = $("
    "); $(elem).find('.robin-message--message').append($videoContainer); $videoContainer.find(".press-play").on("click", function() { $(this).off(); var youtubeId = $(this).val(); var youtubeURL = "//www.youtube.com/embed/" + youtubeId + "?autoplay=1&autohide=1&enablejsapi=1"; var iframe = "