Advertisement
Guest User

Untitled

a guest
Apr 7th, 2016
43
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 88.59 KB | None | 0 0
  1. // ==UserScript==
  2. // @name parrot (color multichat for robin!)
  3. // @namespace http://tampermonkey.net/
  4. // @version 3.64
  5. // @description Recreate Slack on top of an 8 day Reddit project.
  6. // @author dashed, voltaek, daegalus, vvvv, orangeredstilton, lost_penguin, AviN456, Annon201, LTAcosta, mofosyne
  7. // @include https://www.reddit.com/robin*
  8. // @updateURL https://github.com/5a1t/parrot/raw/master/robin.user.js
  9. // @require http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
  10. // @require https://raw.githubusercontent.com/ricmoo/aes-js/master/index.js
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_addStyle
  14. // @grant GM_xmlhttpRequest
  15. // ==/UserScript==
  16. (function() {
  17. // hacky solutions
  18. var CURRENT_CHANNEL = "";
  19. var GOTO_BOTTOM = true;
  20. var robinChatWindow = $('#robinChatWindow');
  21.  
  22. String.prototype.lpad = function(padString, length) {
  23. var str = this;
  24. var prepend_str = "";
  25. for (var i = str.length; i < length; i++) {
  26. prepend_str = padString + prepend_str;
  27. }
  28. return prepend_str + str;
  29. };
  30.  
  31. String.prototype.rpad = function(padString, length) {
  32. var str = this;
  33. var prepend_str = "";
  34. for (var i = str.length; i < length; i++) {
  35. prepend_str = padString + prepend_str;
  36. }
  37. return str + prepend_str;
  38. };
  39.  
  40. function tryHide(){
  41. if(settings.hideVote){
  42. console.log("hiding vote buttons.");
  43. $('.robin-chat--buttons').hide();
  44. }
  45. else{
  46. $('.robin-chat--buttons').show();
  47. }
  48. }
  49.  
  50. // Channel selected in channel drop-down
  51. function dropdownChannel()
  52. {
  53. return $("#chat-prepend-select").val().trim();
  54. }
  55.  
  56. function buildDropdown()
  57. {
  58. split_channels = getChannelString().split(",");
  59. drop_html = "";
  60. for (var tag in split_channels) {
  61. var channel_name = split_channels[tag].trim();
  62. drop_html += '<option value="' + channel_name + '">' + channel_name + '</option>';
  63. }
  64.  
  65. $("#chat-prepend-select").html(drop_html);
  66. $("#chat-prepend-select").on("change", function() { updateTextCounter(); });
  67. }
  68.  
  69. function updateUserPanel(){
  70. var options = {
  71. month: "2-digit",
  72. day: "2-digit",
  73. hour: "2-digit",
  74. minute: "2-digit"
  75. };
  76.  
  77. $(".robin-room-participant").each( function(){
  78. lastseen = userExtra[$(this).find(".robin--username").text().trim()];
  79. if(lastseen){
  80. datestring = lastseen.toLocaleTimeString("en-us", options);
  81. $( this ).find(".robin--username").nextAll().remove();
  82. $( this ).find(".robin--username").after("<span class=\"robin-message--message\"style=\"font-size: 10px;\"> &nbsp;" + datestring + "</span>");
  83. }
  84. });
  85. }
  86.  
  87. // Utils
  88. function getChannelString() {
  89. return settings.filterChannel ? settings.channel : "," + settings.channel;
  90. }
  91.  
  92. function getChannelList()
  93. {
  94. var channels = String(getChannelString()).split(",");
  95. var channelArray = [];
  96.  
  97. for (i = 0; i < channels.length; i++)
  98. {
  99. var channel = channels[i].trim();
  100. if (channel.length > 0)
  101. channelArray.push(channel.toLowerCase());
  102. }
  103.  
  104. return channelArray;
  105. }
  106.  
  107. function hasChannel(source)
  108. {
  109. channel_array = getChannelList();
  110. source = String(source).toLowerCase();
  111.  
  112. return hasChannelFromList(source, channel_array, false);
  113. }
  114.  
  115. function hasChannelFromList(source, channels, shall_trim, ignore_empty)
  116. {
  117. channel_array = channels;
  118. source = shall_trim ? String(source).toLowerCase().trim() : String(source).toLowerCase();
  119.  
  120. for (idx = 0; idx < channel_array.length; idx++)
  121. {
  122. var current_chan = shall_trim ? channel_array[idx].trim() : channel_array[idx];
  123.  
  124. if(ignore_empty && current_chan.length <= 0) {
  125. continue;
  126. }
  127.  
  128. if(source.startsWith(current_chan.toLowerCase())) {
  129. return {
  130. name: current_chan,
  131. has: true,
  132. index: idx
  133. };
  134. }
  135. }
  136.  
  137. return {
  138. name: "",
  139. has: false,
  140. index: 0
  141. };
  142. }
  143.  
  144. function formatNumber(n) {
  145. var part = n.toString().split(".");
  146. part[0] = part[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  147. return part.join(".");
  148. }
  149.  
  150. function addMins(date, mins) {
  151. var newDateObj = new Date(date.getTime() + mins * 60000);
  152. return newDateObj;
  153. }
  154.  
  155. function howLongLeft(endTime) {
  156. if (endTime === null) {
  157. return 0;
  158. }
  159. try {
  160. return Math.floor((endTime - new Date()) / 60 / 1000 * 10) / 10;
  161. } catch (e) {
  162. return 0;
  163. }
  164. }
  165.  
  166. var UserExtra = {
  167. load: function loadSetting() {
  168. var userExtra = localStorage.getItem('parrot-user-extra');
  169. var users_version = localStorage.getItem('parrot-user-version');
  170.  
  171. try {
  172. userExtra = userExtra ? JSON.parse(userExtra) : {};
  173. } catch(e) { console.log("Error parsing userExtra..");}
  174.  
  175. userExtra = userExtra || {};
  176.  
  177. if(users_version == 12){
  178. console.log("found a good user list!");
  179. //return userExtra;
  180. //JSON.stringify is returning undefined.. Reset list each time for now. Will fix.
  181. return {};
  182. }
  183. else {
  184. console.log("found a bad user list, resetting!");
  185. localStorage.setItem('parrot-user-extra', JSON.stringify({}));
  186. return {};
  187. }
  188. },
  189.  
  190. save: function saveSetting(userExtra) {
  191. console.log("Saving");
  192. //console.log(JSON.stringify(userExtra));
  193. localStorage.setItem('parrot-user-extra', JSON.stringify(userExtra));
  194. localStorage.setItem('parrot-user-version', 12);
  195. }
  196. }
  197.  
  198. var Settings = {
  199. setupUI: function() {
  200. // Open Settings button
  201. $robinVoteWidget.prepend("<div class='addon'><div id='chatstats' class='robin-chat--vote info-box-only'></div></div>");
  202. $robinVoteWidget.prepend("<div class='addon'><div class='usercount robin-chat--vote info-box-only'></div></div>");
  203. $robinVoteWidget.prepend("<div class='addon'><div class='timeleft robin-chat--vote info-box-only'></div></div>");
  204. $robinVoteWidget.prepend('<div class="addon" id="openBtn_wrap"><div class="robin-chat--vote" id="openBtn">Open Settings</div></div>');
  205. $robinVoteWidget.append('<div class="addon"><div class="robin-chat--vote" id="standingsBtn">Show Standings</div></div>');
  206. $robinVoteWidget.append('<div class="addon"><div class="robin-chat--vote" id="channelsBtn">Show Most Active Channels</div></div>');
  207. $("#openBtn_wrap").prepend('<div class="robin-chat--sidebar-widget">' +
  208. '<a target="_blank" href="https://www.reddit.com/r/parrot_script/"><div class="robin-chat--vote">' +
  209. '<img src="https://i.imgur.com/ch75qF2.png">Parrot</div><p>soKukunelits fork ~ ' + versionString + '</p></a></div>');
  210.  
  211. // Setting container
  212. $(".robin-chat--sidebar").before(
  213. '<div class="robin-chat--sidebar" style="display:none;" id="settingContainer">' +
  214. '<div class="robin-chat--sidebar-widget robin-chat--vote-widget" id="settingContent">' +
  215. '<div class="robin-chat--vote" id="closeBtn">Close Settings</div>' +
  216. '</div>' +
  217. '</div>'
  218. );
  219.  
  220. // Standing container
  221. $("#settingContainer").before(
  222. '<div class="robin-chat--sidebar" style="display:none;" id="standingsContainer">' +
  223. '<div class="robin-chat--sidebar-widget robin-chat--vote-widget" id="standingsContent">' +
  224. '<div id="standingsTable">' +
  225. '<div>Reddit leaderboard</div><br/>' +
  226. '<div id="standingsTableReddit"></div><br/>' +
  227. '<div id="standingsTableMonstrous"></div>' +
  228. '</div>' +
  229. '<a href="https://www.reddit.com/r/robintracking/comments/4czzo2/robin_chatter_leader_board_official/" target="_blank">' +
  230. '<div class="robin-chat--vote">Full Leaderboard</div></a>' +
  231. '<div class="robin-chat--vote" id="closeStandingsBtn">Close Standings</div>' +
  232. '</div>' +
  233. '</div>'
  234. );
  235.  
  236. // Active channels container
  237. $("#settingContainer").before(
  238. '<div class="robin-chat--sidebar" style="display:none;" id="channelsContainer">' +
  239. '<div class="robin-chat--sidebar-widget robin-chat--vote-widget" id="channelsContent">' +
  240. '<div id="channelsTable">' +
  241. '<div>Most Active Channels (experimental)</div><br/>' +
  242. '<div id="activeChannelsTable"></div><br/>' +
  243. '</div>' +
  244. '<div class="robin-chat--vote" id="closeChannelsBtn">Close Channel List</div>' +
  245. '</div>' +
  246. '</div>'
  247. );
  248.  
  249. $("#settingContent").append('<div class="robin-chat--sidebar-widget robin-chat--notification-widget"><ul><li>Click on chat name to hide sidebar</li><li>Left click usernames to mute.</li>' +
  250. '<li>Right click usernames to copy to message.<li>Tab autocompletes usernames in the message box.</li><li>Ctrl+shift+left/right switches between channel tabs.</li>' +
  251. '<li>Up/down in the message box cycles through sent message history.</li><li>Report any bugs or issues <a href="https://www.reddit.com/r/parrot_script/" target="_blank"><strong>HERE<strong></a></li>' +
  252. '<li>Created for soKukuneli chat (T16)</li></ul></div>');
  253.  
  254. $("#robinDesktopNotifier").detach().appendTo("#settingContent");
  255.  
  256. $("#openBtn").on("click", function openSettings() {
  257. $(".robin-chat--sidebar").hide();
  258. $("#settingContainer").show();
  259. });
  260.  
  261. $("#closeBtn").on("click", function closeSettings() {
  262. $(".robin-chat--sidebar").show();
  263. $("#settingContainer").hide();
  264. $("#standingsContainer").hide();
  265. $("#channelsContainer").hide();
  266. tryHide();
  267. update();
  268. });
  269.  
  270. $("#standingsBtn").on("click", function openStandings() {
  271. $(".robin-chat--sidebar").hide();
  272. startStandings();
  273. $("#standingsContainer").show();
  274. });
  275.  
  276. $("#closeStandingsBtn").on("click", function closeStandings() {
  277. $(".robin-chat--sidebar").show();
  278. stopStandings();
  279. $("#standingsContainer").hide();
  280. $("#settingContainer").hide();
  281. $("#channelsContainer").hide();
  282. });
  283.  
  284. $("#channelsBtn").on("click", function openChannels() {
  285. $(".robin-chat--sidebar").hide();
  286. startChannels();
  287. $("#channelsContainer").show();
  288. });
  289.  
  290. $("#closeChannelsBtn").on("click", function closeChannels() {
  291. $(".robin-chat--sidebar").show();
  292. stopChannels();
  293. $("#channelsContainer").hide();
  294. $("#standingsContainer").hide();
  295. $("#settingContainer").hide();
  296. });
  297.  
  298. $("#robinSendMessage").prepend('<div id="chat-prepend-area"><label>Send chat to</span><select id="chat-prepend-select"></select></div>');
  299.  
  300. function setVote(vote) {
  301. return function() {
  302. settings.vote = vote;
  303. Settings.save(settings);
  304. };
  305. }
  306.  
  307. $(".robin-chat--vote.robin--vote-class--abandon").on("click", setVote("abandon"));
  308. $(".robin-chat--vote.robin--vote-class--continue").on("click", setVote("stay"));
  309. $(".robin-chat--vote.robin--vote-class--increase").on("click", setVote("grow"));
  310.  
  311. $('.robin-chat--buttons').prepend("<div class='robin-chat--vote robin--vote-class--novote'><span class='robin--icon'></span><div class='robin-chat--vote-label'></div></div>");
  312. },
  313.  
  314. load: function loadSetting() {
  315. var setting = localStorage.getItem('robin-grow-settings');
  316.  
  317. try {
  318. setting = setting ? JSON.parse(setting) : {};
  319. } catch(e) {}
  320.  
  321. setting = setting || {};
  322.  
  323. toggleSidebarPosition(setting);
  324. if (!setting.vote)
  325. setting.vote = "grow";
  326.  
  327. return setting;
  328. },
  329.  
  330. save: function saveSetting(settings) {
  331. localStorage.setItem('robin-grow-settings', JSON.stringify(settings));
  332. },
  333.  
  334. addBool: function addBoolSetting(name, description, defaultSetting, callback) {
  335. defaultSetting = settings[name] || defaultSetting;
  336.  
  337. $("#settingContent").append('<div class="robin-chat--sidebar-widget robin-chat--notification-widget"><label><input type="checkbox" name="setting-' + name + '"' + (defaultSetting ? ' checked' : '') + '>' + description + '</label></div>');
  338. $("input[name='setting-" + name + "']").change(function() {
  339. settings[name] = !settings[name];
  340. Settings.save(settings);
  341.  
  342. if(callback) {
  343. callback();
  344. }
  345. });
  346. if (settings[name] !== undefined) {
  347. $("input[name='setting-" + name + "']").prop("checked", settings[name]);
  348. } else {
  349. settings[name] = defaultSetting;
  350. }
  351. },
  352.  
  353. addRadio: function addRadioSetting(name, description, items, defaultSettings, callback) {
  354. //items JSON format:
  355. // {"id":[{"value":<string>,
  356. // "friendlyName":<string>}]};
  357.  
  358. defaultSettings = settings[name] || defaultSettings;
  359.  
  360. $("#settingContent").append('<div id="settingsContainer-' + name + '" class="robin-chat--sidebar-widget robin-chat--notification-widget"><span style="font-weight: 300; letter-spacing: 0.5px; line-height: 15px; font-size:' + settings.fontsize + 'px;">' + description + '</span><br><br>');
  361. for (i in items.id) {
  362. $("#settingsContainer-" + name).append('<label><input type="radio" name="settingsContainer-' + name + '" value="' + items.id[i].value + '"> ' + items.id[i].friendlyName + '</input></label><br>');
  363. }
  364. $("#settingsContainer-" + name).append('</div>');
  365.  
  366. if (settings[name] != undefined) {
  367. $("input:radio[name='setting-" + name + "'][value='" + settings[name] + "']").prop("checked", true);
  368. }
  369. else {
  370. $("input:radio[name='setting-" + name + "'][value='" + defaultSettings + "']").prop("checked", true);
  371. }
  372.  
  373. $("input:radio[name='setting-" + name + "']").on("click", function () {
  374. settings[name] = $("input:radio[name='setting-" + name + "']:checked").val();
  375. Settings.save(settings);
  376. });
  377.  
  378. if (callback) {
  379. callback();
  380. }
  381. },
  382.  
  383. addInput: function addInputSetting(name, description, defaultSetting, callback) {
  384. defaultSetting = settings[name] || defaultSetting;
  385.  
  386. $("#settingContent").append('<div id="robinDesktopNotifier" class="robin-chat--sidebar-widget robin-chat--notification-widget"><label>' + description + '</label><input type="text" name="setting-' + name + '"></div>');
  387. $("input[name='setting-" + name + "']").prop("defaultValue", defaultSetting)
  388. .on("change", function() {
  389.  
  390. settings[name] = String($(this).val());
  391. Settings.save(settings);
  392.  
  393. if(callback) {
  394. callback();
  395. }
  396. });
  397. settings[name] = defaultSetting;
  398. },
  399.  
  400. addButton: function(appendToID, newButtonID, description, callback, options) {
  401. options = options || {};
  402. $('#' + appendToID).append('<div class="addon"><div class="robin-chat--vote" style="font-weight: bold; padding: 5px;cursor: pointer;" id="' + newButtonID + '">' + description + '</div></div>');
  403. $('#' + newButtonID).on('click', function(e) { callback(e, options); });
  404. }
  405. };
  406.  
  407. function clearChat() {
  408. console.log("chat cleared!");
  409. getChannelMessageList(selectedChannel).empty();
  410. }
  411.  
  412. function setRobinMessageVisibility() {
  413. var prop = (settings.removeRobinMessages) ? "none" : "block";
  414.  
  415. $('#robinMessageVisiblity')
  416. .text('.robin-message.robin--flair-class--no-flair.robin--user-class--system {display: ' + prop + ';}');
  417. }
  418.  
  419. function toggleSidebarPosition(setting) {
  420. settings = settings || setting;
  421. var elements = {
  422. header: $('.robin-chat--header'),
  423. content: $('.content[role="main"]'),
  424. votePanel: $('.robin-chat--buttons'),
  425. sidebars: $('.robin-chat--sidebar'),
  426. chat: $('.robin-chat--main')
  427. };
  428. var sidebars = elements.sidebars.detach();
  429.  
  430. settings.sidebarPosition ? elements.chat.before(sidebars) : elements.chat.after(sidebars);
  431. }
  432.  
  433. function grabStandings() {
  434. var standings;
  435.  
  436. // Reddit leaderboard
  437. $.ajax({
  438. url: 'https://www.reddit.com/r/robintracking/comments/4czzo2/robin_chatter_leader_board_official/.rss?limit=1',
  439. data: {},
  440. success: function( data ) {
  441. var currentRoomName = $('.robin-chat--room-name').text();
  442. var standingsPost = $(data).find("entry > content").first();
  443. var decoded = $($('<div/>').html(standingsPost).text()).find('table').first();
  444.  
  445. decoded.find('tr').each(function(i) {
  446. var row = $(this).find('td,th');
  447. var nameColumn = $(row.get(2));
  448. nameColumn.find('a').prop('target','_blank');
  449. if (currentRoomName.startsWith(nameColumn.text().substring(0,6))) {
  450. var color = String(settings.leaderboard_current_color).length > 0 ? String(settings.leaderboard_current_color).trim() : '#22bb45';
  451. row.css('background-color', color);
  452. }
  453. row.each(function(j) {if (j == 3 || j == 4 || j > 5) {
  454. $(this).remove();
  455. }});
  456. });
  457.  
  458. $("#standingsTableReddit").html(decoded);
  459. },
  460. dataType: 'xml'
  461. });
  462.  
  463. // monstrouspeace.com tracker board
  464. $("#standingsTableMonstrous").html("");
  465.  
  466. if (settings.monstrousStats)
  467. {
  468. $.ajax({
  469. type: 'GET',
  470. url: 'https://monstrouspeace.com/robintracker/json.php',
  471. data: { get_param: 'value' },
  472. dataType: 'json',
  473. xhr: function() { return new GM_XHR(); },
  474. success: function(data) {
  475. var decoded =
  476. '<br/><div style="font-weight: bold; text-align: center;">MonstrousPeace.com tracking (experimental)</div><br/>' +
  477. "<table>\r\n" +
  478. "<thead>\r\n" +
  479. "<tr><th>#</th><th>Participants</th><th>Grow</th><th>Stay</th><th>Room Name</th><th>Tier</th></tr>\r\n" +
  480. "</thead>\r\n" +
  481. "<tbody>\r\n";
  482.  
  483. $.each(data, function(index, e) {
  484. decoded += "<tr><td>" + (index+1) + "</td><td>" + e.count + "</td><td>" + e.grow + "</td><td>" + e.stay + "</td><td>" + e.room + "</td><td>" + e.tier + "</td></tr>\r\n";
  485. });
  486. decoded +=
  487. "</tbody>\r\n" +
  488. "</table>\r\n" +
  489. '<br/>';
  490. $("#standingsTableMonstrous").html(decoded);
  491. }
  492. });
  493. }
  494. };
  495.  
  496. //
  497. // XHR that can cross same origin policy boundaries
  498. //
  499. function GM_XHR() {
  500. this.type = null;
  501. this.url = null;
  502. this.async = null;
  503. this.username = null;
  504. this.password = null;
  505. this.status = null;
  506. this.headers = {};
  507. this.readyState = null;
  508. this.abort = function() { this.readyState = 0; };
  509. this.getAllResponseHeaders = function(name) { return this.readyState != 4 ? "" : this.responseHeaders; };
  510. this.getResponseHeader = function(name) {
  511. var regexp = new RegExp('^'+name+': (.*)$','im');
  512. var match = regexp.exec(this.responseHeaders);
  513. if (match) { return match[1]; }
  514. return '';
  515. };
  516. this.open = function(type, url, async, username, password) {
  517. this.type = type ? type : null;
  518. this.url = url ? url : null;
  519. this.async = async ? async : null;
  520. this.username = username ? username : null;
  521. this.password = password ? password : null;
  522. this.readyState = 1;
  523. };
  524. this.setRequestHeader = function(name, value) { this.headers[name] = value; };
  525. this.send = function(data) {
  526. this.data = data;
  527. var that = this;
  528. GM_xmlhttpRequest({
  529. method: this.type,
  530. url: this.url,
  531. headers: this.headers,
  532. data: this.data,
  533. onload: function(rsp) { for (var k in rsp) { that[k] = rsp[k]; } that.onreadystatechange(); },
  534. onerror: function(rsp) { for (var k in rsp) { that[k] = rsp[k]; } }
  535. });
  536. };
  537. };
  538.  
  539. var standingsInterval = 0;
  540. function startStandings() {
  541. stopStandings();
  542. standingsInterval = setInterval(grabStandings, 120000);
  543. grabStandings();
  544. }
  545.  
  546. function stopStandings() {
  547. if (standingsInterval){
  548. clearInterval(standingsInterval);
  549. standingsInterval = 0;
  550. }
  551. }
  552.  
  553. function updateChannels()
  554. {
  555. // Sort the channels
  556. var channels = [];
  557. for(var channel in activeChannelsCounts){
  558. if (activeChannelsCounts[channel] > 1){
  559. channels.push(channel);
  560. }
  561. }
  562.  
  563. channels.sort(function(a,b) {return activeChannelsCounts[b] - activeChannelsCounts[a];});
  564.  
  565. // Build the table
  566. var html = "<table>\r\n" +
  567. "<thead>\r\n" +
  568. "<tr><th>#</th><th>Channel Name</th><th>Join Channel</th></tr>\r\n" +
  569. "</thead>\r\n" +
  570. "<tbody>\r\n";
  571.  
  572. var limit = 50;
  573. if (channels.length < limit)
  574. limit = channels.length;
  575.  
  576. for (var i = 0; i < limit; i++) {
  577. html += "<tr><td>" + (i+1) + "</td><td>" + channels[i] + "</td><td><div class=\"channelBtn robin-chat--vote\">Join Channel</div></td></tr>\r\n";
  578. }
  579.  
  580. html += "</tbody>\r\n" +
  581. "</table>\r\n" +
  582. '<br/>';
  583.  
  584. $("#activeChannelsTable").html(html);
  585.  
  586. $(".channelBtn").on("click", function joinChannel() {
  587. var channel = $(this).parent().prev().contents().text();
  588. var channels = getChannelList();
  589.  
  590. if (channel && $.inArray(channel, channels) < 0) {
  591. settings.channel += "," + channel;
  592. Settings.save(settings);
  593. buildDropdown();
  594. resetChannels();
  595. }
  596. });
  597. }
  598.  
  599. var channelsInterval = 0;
  600. function startChannels() {
  601. stopChannels();
  602. channelsInterval = setInterval(updateChannels, 30000);
  603. updateChannels();
  604. }
  605.  
  606. function stopChannels() {
  607. if (channelsInterval){
  608. clearInterval(channelsInterval);
  609. channelsInterval = 0;
  610. }
  611. }
  612.  
  613. var currentUsersName = $('div#header span.user a').html();
  614.  
  615. // Settings begin
  616. var $robinVoteWidget = $("#robinVoteWidget");
  617.  
  618. // IF the widget isn't there, we're probably on a reddit error page.
  619. if (!$robinVoteWidget.length) {
  620. // Don't overload reddit, wait a bit before reloading.
  621. setTimeout(function() {
  622. window.location.reload();
  623. }, 300000);
  624. return;
  625. }
  626.  
  627. // Get version string (if available from script engine)
  628. var versionString = "";
  629. if (typeof GM_info !== "undefined") {
  630. versionString = GM_info.script.version;
  631. }
  632.  
  633. Settings.setupUI($robinVoteWidget);
  634. var settings = Settings.load();
  635. var userExtra = UserExtra.load();
  636. startUserExtra();
  637.  
  638. function tryStoreUserExtra(){
  639. console.log("storing lastseens");
  640. UserExtra.save(userExtra);
  641. }
  642.  
  643. var userExtraInterval = 0;
  644.  
  645. function startUserExtra() {
  646. userExtraInterval = setInterval(listAllUsers, 10*1000);
  647. userExtraInterval = setInterval(tryStoreUserExtra, 20*1000);
  648. }
  649. // bootstrap
  650. tryHide();
  651.  
  652. // Options begin
  653. 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"); });
  654. Settings.addButton("robinChatInput", "clear-chat-button", "Clear Chat", clearChat);
  655. Settings.addBool("hideVote", "Hide voting panel", false, tryHide);
  656. Settings.addBool("removeSpam", "Remove bot spam", true);
  657. Settings.addInput("spamFilters", "<label>Custom Spam Filters<ul><li><b>Checkbox 'Remove bot spam' (above)</b></li><li>Comma-delimited</li><li>Spaces are NOT stripped</li></ul></label>", "spam example 1,John Madden");
  658. Settings.addBool("enableUnicode", "Allow unicode characters. Unicode is considered spam and thus are filtered out", false);
  659. Settings.addBool("sidebarPosition", "Left sidebar", false, toggleSidebarPosition);
  660. Settings.addBool("force_scroll", "Force scroll to bottom", false);
  661. Settings.addInput("cipherkey", "16 Character Cipher Key", "Example128BitKey");
  662. Settings.addInput("maxprune", "Max messages before pruning", "500");
  663. Settings.addInput("fontsize", "Chat font size", "12");
  664. Settings.addInput("fontstyle", "Font Style (default Consolas)", "");
  665. Settings.addBool("alignment", "Right align usernames", true);
  666. Settings.addInput("username_bg", "Custom background color on usernames", "");
  667.  
  668. Settings.addBool("removeRobinMessages", "Hide [robin] messages everywhere", false, setRobinMessageVisibility);
  669. Settings.addBool("removeChanMessageFromGlobal", "Hide channel messages in Global", false);
  670. Settings.addBool("filterChannel", "Hide non-channel messages in Global", false, function() { buildDropdown(); });
  671. Settings.addInput("channel", "<label>Channel Listing<ul><li>Multi-room-listening with comma-separated rooms</li><li>Names are case-insensitive</li><li>Spaces are NOT stripped</li></ul></label>", "%parrot", function() { buildDropdown(); resetChannels(); });
  672. Settings.addInput("channel_exclude", "<label>Channel Exclusion Filter<ul><li>Multi-room-listening with comma-separated rooms</li><li><strong>List of channels to exclude from Global channel (e.g. trivia channels)</strong></li><li>Names are case-insensitive</li><li>Spaces are NOT stripped</li></ul></label>", "");
  673.  
  674. Settings.addBool("tabChanColors", "Use color on channel tabs", true);
  675. Settings.addBool("twitchEmotes", "Twitch emotes (<a href='https://twitchemotes.com/filters/global' target='_blank'>Normal</a>, <a href='https://nightdev.com/betterttv/faces.php' target='_blank'>BTTV</a>)", false);
  676. Settings.addBool("youtubeVideo", "Inline youtube videos", false);
  677. Settings.addBool("timeoutEnabled", "Reload page after inactivity timeout", true);
  678. Settings.addInput("messageHistorySize", "Sent Message History Size", "50");
  679. Settings.addBool("monstrousStats", "Show automated leaderboard on standings page (asks for permission)</a>", false);
  680. Settings.addBool("reportStats", "Contribute statistics to the <a href='https://monstrouspeace.com/robintracker/'>Automated Leaderboard</a>.", true);
  681. Settings.addInput("statReportingInterval", "Report Statistics Interval (seconds) [above needs to be checked]", "300");
  682. Settings.addInput("leaderboard_current_color", "Highlight color of current chat room in leaderboard standings", '#22bb45');
  683.  
  684. Settings.addBool("enableTabComplete", "Tab Autocomplete usernames", true);
  685. Settings.addBool("enableQuickTabNavigation", "Keyboard channel-tabs navigation", true);
  686. Settings.addBool("enableAdvancedNaviOptions", "Keyboard navigation key remapping. Use custom settings below for switching channels instead:", false, function(){
  687. $('input[name^=setting-quickTabNavi]').prop('disabled',!$('input[name=setting-enableAdvancedNaviOptions]').prop('checked'));
  688. });
  689. Settings.addBool("quickTabNaviCtrlRequired", "Ctrl", true);
  690. Settings.addBool("quickTabNaviShiftRequired", "Shift", false);
  691. Settings.addBool("quickTabNaviAltRequired", "Alt", true);
  692. Settings.addBool("quickTabNaviMetaRequired", "Meta", false);
  693. Settings.addInput("quickTabNaviKeyLeft", "Key codes: Left = 37, Up = 38, Right = 39, Down = 40. See <a href='https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode' target='_blank'>Mozilla.org's documentation</a> for more key codes.<br><br>Navigate left tab final key code","37");
  694. Settings.addInput("quickTabNaviKeyRight", "Navigate right tab final key code","39");
  695. $('input[name^=setting-quickTabNavi]').prop('disabled',!settings.enableAdvancedNaviOptions);
  696.  
  697. $("#settingContent").append("<div class='robin-chat--sidebar-widget robin-chat--notification-widget'><label id='blockedUserContainer'>Muted Users (click to unmute)</label>");
  698. $("#blockedUserContainer").append("<div id='blockedUserList' class='robin-chat--sidebar-widget robin-chat--user-list-widget'></div>");
  699.  
  700. $("#settingContent").append('<div class="robin-chat--sidebar-widget robin-chat--report" style="text-align:center;"><a target="_blank" href="https://www.reddit.com/r/parrot_script/">parrot&nbsp;v' + versionString + '</a></div>');
  701.  
  702. $('head').append('<style id="robinMessageVisiblity"></style>');
  703. setRobinMessageVisibility();
  704. // Options end
  705. // Settings end
  706.  
  707. var timeStarted = new Date();
  708. var name = $(".robin-chat--room-name").text();
  709. 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);
  710. var youtubeRegex = new RegExp(/.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/);
  711.  
  712. var list = {};
  713.  
  714. buildDropdown();
  715.  
  716. var isEndingSoon = false;
  717. var endTime = null;
  718. var endTimeAttempts = 0;
  719.  
  720. // Grab the timestamp from the time remaining message and then calc the ending time using the estimate it gives you
  721. function getEndTime() { // mostly from /u/Yantrio, modified by /u/voltaek
  722. endTimeAttempts++;
  723. var remainingMessageContainer = $(".robin--user-class--system:contains('approx')");
  724. if (remainingMessageContainer.length === 0) {
  725. // for cases where it says "soon" instead of a time on page load
  726. var endingSoonMessageContainer = $(".robin--user-class--system:contains('soon')");
  727. if (endingSoonMessageContainer.length !== 0) {
  728. isEndingSoon = true;
  729. }
  730. return null;
  731. }
  732. var message = $(".robin-message--message", remainingMessageContainer).text();
  733. var time = new Date($(".robin-message--timestamp", remainingMessageContainer).attr("datetime"));
  734. try {
  735. return addMins(time, message.match(/\d+/)[0]);
  736. } catch (e) {
  737. return null;
  738. }
  739. }
  740.  
  741. endTime = getEndTime();
  742.  
  743. var lastStatisticsUpdate = 0;
  744. function update() {
  745. switch(settings.vote) {
  746. case "abandon":
  747. $(".robin-chat--vote.robin--vote-class--abandon:not('.robin--active')").click();
  748. break;
  749. case "stay":
  750. $(".robin-chat--vote.robin--vote-class--continue:not('.robin--active')").click();
  751. break;
  752. case "grow":
  753. $(".robin-chat--vote.robin--vote-class--increase:not('.robin--active')").click();
  754. break;
  755. default:
  756. $(".robin-chat--vote.robin--vote-class--increase:not('.robin--active')").click();
  757. break;
  758. }
  759. if (endTime !== null || isEndingSoon) {
  760. $(".timeleft").text(isEndingSoon ? "waiting to merge" : formatNumber(howLongLeft(endTime)) + " minutes remaining");
  761. }
  762. else if (endTimeAttempts <= 3 && endTime === null) {
  763. $("#robinVoteWidget .timeleft").parent().hide();
  764. endTime = getEndTime();
  765. if (endTime !== null || isEndingSoon) {
  766. $("#robinVoteWidget .timeleft").parent().show();
  767. }
  768. }
  769.  
  770. var users = 0;
  771. $.get("/robin/", function(a) {
  772. var START_TOKEN = "<script type=\"text/javascript\" id=\"config\">r.setup(";
  773. var END_TOKEN = ")</script>";
  774. var start = a.substring(a.indexOf(START_TOKEN)+START_TOKEN.length);
  775. var end = start.substring(0,start.indexOf(END_TOKEN));
  776. config = JSON.parse(end);
  777. list = config.robin_user_list;
  778.  
  779. var counts = list.reduce(function(counts, voter) {
  780. counts[voter.vote] += 1;
  781. return counts;
  782. }, {
  783. INCREASE: 0,
  784. ABANDON: 0,
  785. NOVOTE: 0,
  786. CONTINUE: 0
  787. });
  788.  
  789. var GROW_STR = formatNumber(counts.INCREASE);
  790. var ABANDON_STR = formatNumber(counts.ABANDON);
  791. var NOVOTE_STR = formatNumber(counts.NOVOTE);
  792. var STAY_STR = formatNumber(counts.CONTINUE);
  793.  
  794. $robinVoteWidget.find('.robin--vote-class--increase .robin-chat--vote-label').html('grow<br>(' + GROW_STR + ')');
  795. $robinVoteWidget.find('.robin--vote-class--abandon .robin-chat--vote-label').html('abandon<br>(' + ABANDON_STR + ')');
  796. $robinVoteWidget.find('.robin--vote-class--novote .robin-chat--vote-label').html('no vote<br>(' + NOVOTE_STR + ')');
  797. $robinVoteWidget.find('.robin--vote-class--continue .robin-chat--vote-label').html('stay<br>(' + STAY_STR + ')');
  798. users = list.length;
  799. $(".usercount").text(formatNumber(users) + " users in chat");
  800.  
  801. currentTime = Math.floor(Date.now()/1000);
  802.  
  803. // if(settings.reportStats && (currentTime-lastStatisticsUpdate)>=parseInt(settings.statReportingInterval))
  804.  
  805. // #yolo-robin till April 8th
  806. if((currentTime-lastStatisticsUpdate)>=parseInt(settings.statReportingInterval))
  807. {
  808. lastStatisticsUpdate = currentTime;
  809.  
  810. // Report statistics to the automated leaderboard
  811. trackers = [
  812. "https://monstrouspeace.com/robintracker/track.php"
  813. ];
  814.  
  815. queryString = "?id=" + config.robin_room_name.substr(0,10) +
  816. "&guid=" + config.robin_room_id +
  817. "&ab=" + counts.ABANDON +
  818. "&st=" + counts.CONTINUE +
  819. "&gr=" + counts.INCREASE +
  820. "&nv=" + counts.NOVOTE +
  821. "&count=" + users +
  822. "&ft=" + Math.floor(config.robin_room_date / 1000) +
  823. "&rt=" + Math.floor(config.robin_room_reap_time / 1000);
  824.  
  825. trackers.forEach(function(tracker){
  826. $.get(tracker + queryString);
  827. });
  828. }
  829.  
  830. var $chatstats = $("#chatstats");
  831.  
  832. if(settings.hideVote){
  833. $chatstats.text("GROW: " + GROW_STR + " (" + (counts.INCREASE / users * 100).toFixed(0) + "%) STAY: " + STAY_STR + " (" + (counts.CONTINUE / users * 100).toFixed(0) + "%)");
  834. $chatstats.show();
  835. } else {
  836. $chatstats.hide();
  837. }
  838. });
  839. var lastChatString = $(".robin-message--timestamp").last().attr("datetime");
  840. var timeSinceLastChat = new Date() - (new Date(lastChatString));
  841. var now = new Date();
  842. if (timeSinceLastChat !== undefined && (timeSinceLastChat > 600000 && now - timeStarted > 600000)) {
  843. if (settings.timeoutEnabled)
  844. window.location.reload(); // reload if we haven't seen any activity in a minute.
  845. }
  846.  
  847. // Try to join if not currently in a chat
  848. if ($("#joinRobinContainer").length) {
  849. $("#joinRobinContainer").click();
  850. setTimeout(function() {
  851. $("#joinRobin").click();
  852. }, 1000);
  853. }
  854. }
  855.  
  856. // hash string so finding spam doesn't take up too much memory
  857. function hashString(str) {
  858. var hash = 0;
  859.  
  860. if (str != 0) {
  861. for (i = 0; i < str.length; i++) {
  862. char = str.charCodeAt(i);
  863. if (str.charCodeAt(i) > 0x40) { // Let's try to not include the number in the hash in order to filter bots
  864. hash = ((hash << 5) - hash) + char;
  865. hash = hash & hash; // Convert to 32bit integer
  866. }
  867. }
  868. }
  869.  
  870. return hash;
  871. }
  872.  
  873. // Searches through all messages to find and hide spam
  874. var spamCounts = {};
  875.  
  876. function findAndHideSpam() {
  877.  
  878. // getChannelTab
  879. var len = channelList.length;
  880.  
  881. while(len-- > -1) {
  882.  
  883. //var $messages = getChannelTab(len).find(".robin-message");
  884.  
  885. var $messages = getChannelMessageList(len).find(".robin-message");
  886. var maxprune = parseInt(settings.maxprune || "1000", 10);
  887.  
  888. if (isNaN(maxprune)) {
  889. maxprune = 1000;
  890. }
  891. if ( maxprune <= 0) {
  892. maxprune = 1;
  893. }
  894.  
  895. //console.log("maxprune: " + maxprune + " Messages.length: " + $messages.length + " len: " + len) ;
  896.  
  897. if ($messages.length > maxprune) {
  898. $messages.slice(0, $messages.length - maxprune).remove();
  899. }
  900. }
  901.  
  902. if (false && settings.findAndHideSpam) {
  903. // skips over ones that have been hidden during this run of the loop
  904. $('.robin--user-class--user .robin-message--message:not(.addon--hide)').each(function() {
  905. var $this = $(this);
  906.  
  907. var hash = hashString($this.text());
  908. var user = $('.robin-message--from', $this.closest('.robin-message')).text();
  909.  
  910. if (!(user in spamCounts)) {
  911. spamCounts[user] = {};
  912. }
  913.  
  914. if (hash in spamCounts[user]) {
  915. spamCounts[user][hash].count++;
  916. spamCounts[user][hash].elements.push(this);
  917. } else {
  918. spamCounts[user][hash] = {
  919. count: 1,
  920. text: $this.text(),
  921. elements: [this]
  922. };
  923. }
  924. $this = null;
  925. });
  926.  
  927. $.each(spamCounts, function(user, messages) {
  928. $.each(messages, function(hash, message) {
  929. if (message.count >= 3) {
  930. $.each(message.elements, function(index, element) {
  931. $(element).closest('.robin-message').addClass('addon--hide').remove();
  932. });
  933. } else {
  934. message.count = 0;
  935. }
  936.  
  937. message.elements = [];
  938. });
  939. });
  940. }
  941. }
  942.  
  943. // faster to save this in memory
  944. /* Detects unicode spam - Credit to travelton
  945. * https://gist.github.com/travelton */
  946. var UNICODE_SPAM_RE = /[\u0080-\uFFFF]/;
  947. function isBotSpam(text) {
  948. // starts with a [, has "Autovoter", or is a vote
  949. var filter = text.indexOf("[") === 0 ||
  950. text == "voted to STAY" ||
  951. text == "voted to GROW" ||
  952. text == "voted to ABANDON" ||
  953. text.indexOf("Autovoter") > -1 ||
  954. (!settings['enableUnicode'] && UNICODE_SPAM_RE.test(text));
  955. var spamFilters = settings.spamFilters.split(",").map(function(filter) { return filter.trim().toLowerCase(); });
  956. spamFilters.forEach(function(filterVal) {
  957. filter = filter || filterVal.length > 0 && text.toLowerCase().indexOf(filterVal) >= 0;
  958. });
  959. // if(filter)console.log("removing "+text);
  960. return filter;
  961. }
  962.  
  963. // Individual mute button /u/verox-
  964. var mutedList = settings.mutedUsersList || [];
  965. $('body').on('click', ".robin--username", function() {
  966. var username = String($(this).text()).trim();
  967. var clickedUser = mutedList.indexOf(username);
  968.  
  969. var $userNames = $(".robin--username:contains(" + username + ")");
  970.  
  971. if (clickedUser == -1) {
  972. // Mute our user.
  973. mutedList.push(username);
  974. $userNames.css({textDecoration: "line-through"});
  975. } else {
  976. // Unmute our user.
  977. $userNames.css({textDecoration: "none"});
  978. mutedList.splice(clickedUser, 1);
  979. }
  980.  
  981. settings.mutedUsersList = mutedList;
  982. Settings.save(settings);
  983. listMutedUsers();
  984. });
  985.  
  986. // Copy cliked username into textarea /u/tW4r based on /u/verox-'s Individual mute button
  987. $('body').on('contextmenu', ".robin--username", function (event) {
  988. // Prevent context-menu from showing up
  989. event.preventDefault();
  990. // Get clicked username and previuos input source
  991. var username = String($(this).text()).trim();
  992. var source = String($("#robinMessageText").val());
  993. // Focus textarea and set the value of textarea
  994. $("#robinMessageText").focus().val("").val(source + " " + username + " ");
  995. });
  996.  
  997. function listMutedUsers() {
  998. $("#blockedUserList").html("");
  999.  
  1000. $.each(mutedList, function(index, value){
  1001.  
  1002. var mutedHere = "present";
  1003.  
  1004. var userInArray = $.grep(list, function(e) {
  1005. return e.name === value;
  1006. });
  1007.  
  1008. if (userInArray && userInArray.length > 0 && userInArray[0].present === true) {
  1009. mutedHere = "present";
  1010. } else {
  1011. mutedHere = "away";
  1012. }
  1013.  
  1014. var votestyle = userInArray && userInArray.length > 0 ?
  1015. " robin--vote-class--" + userInArray[0].vote.toLowerCase()
  1016. : "";
  1017.  
  1018. $("#blockedUserList").append(
  1019. $("<div class='robin-room-participant robin--user-class--user robin--presence-class--" + mutedHere + votestyle + "'></div>")
  1020. .append("<span class='robin--icon'></span><span class='robin--username' style='color:" + colorFromName(value) + "'>" + value + "</span>")
  1021. );
  1022. });
  1023. }
  1024. setTimeout(function() {
  1025. listMutedUsers();
  1026. }, 1500);
  1027.  
  1028. function listAllUsers() {
  1029. var actives = Object.keys(userExtra).map(function(key) {
  1030. //console.log("key: " + key + " val: " + userExtra[key]);
  1031. return [key, userExtra[key]];
  1032. });
  1033.  
  1034. // Sort the array based on the second element
  1035. actives = actives.sort(function(first, second) {
  1036. //console.log(first[1] + " is < " + second[1]);
  1037. //console.log(second[1] >= first[1]);
  1038. return second[1] - first[1];
  1039. });
  1040.  
  1041. var options = {
  1042. month: "2-digit",
  1043. day: "2-digit", hour: "2-digit", minute: "2-digit"
  1044. };
  1045.  
  1046. $("#robinUserList").html("<h4>Total Actives: " + actives.length + "<h4>");
  1047.  
  1048. $.each(actives, function(index,userpair){
  1049. var mutedHere = "present";
  1050.  
  1051. var userInArray = $.grep(list, function(e) {
  1052. return e.name === userpair[0];
  1053. });
  1054.  
  1055. if (userInArray && userInArray.length > 0 && userInArray[0].present === true) {
  1056. mutedHere = "present";
  1057. } else {
  1058. mutedHere = "away";
  1059. }
  1060.  
  1061. var votestyle = userInArray && userInArray.length > 0 ?
  1062. " robin--vote-class--" + userInArray[0].vote.toLowerCase()
  1063. : "";
  1064.  
  1065. var datestring = userpair[1].toLocaleTimeString("en-us", options);
  1066.  
  1067. name_area = $("<div class='robin-room-participant robin--user-class--user robin--presence-class--" + mutedHere + votestyle + "'></div>")
  1068. .hover(function(){$('#user-submenu-' + userpair[0]).slideDown('medium');},function(){$('#user-submenu-' + userpair[0]).slideUp('medium');})
  1069. .prepend('<ul class="parrot-user-submenu" id="user-submenu-' + userpair[0] + '" style="margin: 0px; padding: 0px; list-style: none; border: 1px solid rgb(171, 171, 171); display: none;" ><li><a target="_blank" href="https://www.reddit.com/message/compose/?to='+userpair[0]+'">Send '+ userpair[0] + ' a Message.</a></li> <li><a target="_blank" href="https://www.reddit.com/user/'+userpair[0]+'">View Comment History</a></li></ul>');
  1070. $("#robinUserList").append(
  1071. name_area
  1072. .append("<span class='robin--icon'></span><span class='robin--username' style='color:" + colorFromName(userpair[0]) + "'>" + userpair[0] + "</span>" + "<span class=\"robin-message--message\"style=\"font-size: 10px;\"> &nbsp;" + datestring + "</span>")
  1073. );
  1074. });
  1075.  
  1076. //updateUserPanel();
  1077. }
  1078.  
  1079. //colored text thanks to OrangeredStilton! https://gist.github.com/Two9A/3f33ee6f6daf6a14c1cc3f18f276dacd
  1080. 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)'];
  1081.  
  1082. //Emotes by ande_
  1083. //Normal Twitch emotes
  1084. var emotes = {};
  1085. $.getJSON("https://twitchemotes.com/api_cache/v2/global.json", function(data) {
  1086. emotes = data.emotes;
  1087. for(var prop in emotes){
  1088. emotes[prop.toLowerCase()] = emotes[prop];
  1089. }
  1090. });
  1091.  
  1092. //BetterTwitchTV emotes
  1093. var bttvEmotes = {};
  1094. $.getJSON("https://api.betterttv.net/2/emotes", function(data) {
  1095. data.emotes.forEach(function(emote){
  1096. bttvEmotes[emote.code.toLowerCase()] = emote.id;
  1097. });
  1098. });
  1099.  
  1100. // credit to wwwroth for idea (notification audio)
  1101. // i think this method is better
  1102. var notifAudio = new Audio("https://slack.global.ssl.fastly.net/dfc0/sounds/push/knock_brush.mp3");
  1103.  
  1104. //
  1105. // Tabbed channel windows by /u/lost_penguin
  1106. //
  1107. var channelList = [];
  1108. var selectedChannel = -1;
  1109.  
  1110. function setupMultiChannel()
  1111. {
  1112. // Add div to hold tabs
  1113. $("#robinChatWindow").before("<div id=\"robinChannelDiv\" class=\"robin-chat--message-list\"><ul id=\"robinChannelList\"></ul></div>");
  1114.  
  1115. // Add tab for all other messages
  1116. $("#robinChannelList").append("<li id=\"robinChannelTab\"><a id=\"robinChannelLink\" href=\"#robinCh\">Global</a></li>");
  1117.  
  1118. // Room tab events
  1119. var tab = $("#robinChannelLink");
  1120. tab.on("click", function() { selectChannel(""); });
  1121.  
  1122. // Add rooms
  1123. resetChannels();
  1124.  
  1125. // Restore the selected channel in the url
  1126. if (channelList[window.location.hash.charAt(8)]) selectChannel(window.location.hash);
  1127. }
  1128.  
  1129. function resetChannels()
  1130. {
  1131. channelList = getChannelList();
  1132.  
  1133. var chatBox = $("#robinChatWindow");
  1134. var tabBar = $("#robinChannelList");
  1135.  
  1136. // Remove all existing rooms
  1137. chatBox.children().each(function() { if (this.id.startsWith("robinChatMessageList-ch")) this.remove(); });
  1138. tabBar.children().each(function() { if (this.id.startsWith("robinChannelTab-ch")) this.remove(); });
  1139.  
  1140. // Create fresh rooms
  1141. for (i = 0; i < channelList.length; i++)
  1142. {
  1143. // Room message window
  1144. chatBox.append("<div id=\"robinChatMessageList-ch" + i + "\" class=\"robin-chat--message-list\">");
  1145.  
  1146. // Room tab
  1147. tabBar.append("<li id=\"robinChannelTab-ch" + i + "\"><a id=\"robinChannelLink-ch" + i + "\" href=\"#robinCh" + i + "\">" + channelList[i] + "</a></li>");
  1148.  
  1149. // Room tab event
  1150. var tab = $("#robinChannelLink-ch" + i);
  1151. tab.on("click", function() { selectChannel($(this).attr("href")); });
  1152. }
  1153.  
  1154. selectChannel("");
  1155. }
  1156.  
  1157. function selectChannel(channelLinkId)
  1158. {
  1159.  
  1160. // Get channel index
  1161. var channelIndex = -1;
  1162. if ((typeof channelLinkId) == 'string' && channelLinkId.length > 8) {
  1163. channelIndex = channelLinkId.substring(8);
  1164. }
  1165. if((typeof channelLinkId) == 'number') {
  1166. channelIndex = channelLinkId;
  1167. }
  1168.  
  1169.  
  1170. $("#chat-prepend-select").val($("#robinChannelLink-ch" + (channelIndex >= 0 ? channelIndex : "") ).html());
  1171.  
  1172. // Remember selection
  1173. selectedChannel = channelIndex;
  1174.  
  1175. // Show/hide channel drop-down
  1176. if (channelIndex >= 0)
  1177. $("#chat-prepend-area").css("display", "none");
  1178. else
  1179. $("#chat-prepend-area").css("display", "");
  1180.  
  1181. // Update tab selection
  1182. for (i = -1; i < channelList.length; i++)
  1183. setChannelSelected(getChannelTab(i), getChannelMessageList(i), channelIndex == i);
  1184.  
  1185. updateTextCounter();
  1186. }
  1187.  
  1188. function markChannelChanged(index)
  1189. {
  1190. if (index != selectedChannel)
  1191. getChannelTab(index).attr("class", "robin-chan-tab-changed");
  1192. }
  1193.  
  1194. function setChannelSelected(tab, box, select)
  1195. {
  1196.  
  1197. if (select)
  1198. {
  1199. tab.attr("class", "robin-chan-tab-selected");
  1200. box.css("display", "");
  1201.  
  1202. doScroll();
  1203. }
  1204. else
  1205. {
  1206. if (tab.attr("class") == "robin-chan-tab-selected")
  1207. tab.attr("class", "");
  1208.  
  1209. box.css("display", "none");
  1210. }
  1211. }
  1212.  
  1213. function getChannelTab(index)
  1214. {
  1215. if (index == -1) return $("#robinChannelLink");
  1216. return $("#robinChannelLink-ch" + index);
  1217. }
  1218.  
  1219. function getChannelMessageList(index)
  1220. {
  1221. if (index == -1) return $("#robinChatMessageList");
  1222. return $("#robinChatMessageList-ch" + index);
  1223. }
  1224.  
  1225. function convertTextToSpecial(messageText, elem)
  1226. {
  1227. urlRegex.lastIndex = 0;
  1228. if (urlRegex.test(messageText)) {
  1229. urlRegex.lastIndex = 0;
  1230. var url = encodeURI(urlRegex.exec(messageText)[0]);
  1231. var parsedUrl = url.replace(/^/, "<a target=\"_blank\" href=\"").replace(/$/, "\">"+url+"</a>");
  1232. var oldHTML = $(elem).find('.robin-message--message').html();
  1233. var newHTML = oldHTML.replace(url, parsedUrl);
  1234. $(elem).find('.robin-message--message').html(newHTML);
  1235. }
  1236. if (settings.twitchEmotes){
  1237. var split = messageText.split(' ');
  1238. var changes = false;
  1239. for (var i=0; i < split.length; i++) {
  1240. var key = (split[i]).toLowerCase();
  1241. if(emotes.hasOwnProperty(key)){
  1242. split[i] = "<img src=\"https://static-cdn.jtvnw.net/emoticons/v1/"+emotes[key].image_id+"/1.0\">";
  1243. changes = true;
  1244. }
  1245. if(bttvEmotes.hasOwnProperty(key)){
  1246. split[i] = "<img src=\"https://cdn.betterttv.net/emote/"+bttvEmotes[key]+"/1x\">";
  1247. changes = true;
  1248. }
  1249. }
  1250. if (changes) {
  1251. $(elem).find('.robin-message--message').html(split.join(' '));
  1252. }
  1253. }
  1254.  
  1255. // TODO this can support vine videos too
  1256. if (settings.youtubeVideo) {
  1257. var matches = messageText.match(youtubeRegex);
  1258. if (!matches || matches[1].length !== 11) return;
  1259.  
  1260. var youtubeId = matches[1];
  1261. var youtubeURL = "//www.youtube.com/embed/" + youtubeId + "?autoplay=1&autohide=1&enablejsapi=1";
  1262. $videoContainer = $("<div class='video-container' style='width:400px;height:300px;background-color: #000;display:inline-block;position:relative;'><button class='press-play robin-chat--vote' style='margin:auto;position:absolute;top:0px;bottom:0px;left:0px;right:0px;width:100px;height:30px;'>Play Video</button><img style='width:400px;height:300px;' src='" + "//img.youtube.com/vi/" + youtubeId + "/hqdefault.jpg" + "' /></div>");
  1263.  
  1264. $(elem).find('.robin-message--message').append($videoContainer);
  1265.  
  1266. var iframe = "<iframe class='media-embed' type='text/html' width=400 height=300 src='"+ youtubeURL + "' frameBorder=0 allowFullScreen />";
  1267. $videoContainer.find(".press-play").on("click", function() {
  1268. $(this).off();
  1269. $videoContainer.html(iframe);
  1270. });
  1271. }
  1272. }
  1273.  
  1274. function moveChannelMessage(channelIndex, message, overrideBGColor, isChanMessage)
  1275. {
  1276. var channel = getChannelMessageList(channelIndex);
  1277. var messageCopy = message;
  1278.  
  1279. if (isChanMessage && !settings.removeChanMessageFromGlobal)
  1280. messageCopy = messageCopy.cloneNode(true);
  1281.  
  1282. var messageElem = $(messageCopy.children && messageCopy.children[2]);
  1283. var messageText = messageElem.text();
  1284.  
  1285. // Remove channel name from channel messages
  1286. if (messageText.startsWith(channelList[channelIndex]))
  1287. {
  1288. messageText = messageText.substring(channelList[channelIndex].length).trim();
  1289. messageElem.text(messageText);
  1290. }
  1291.  
  1292. // Remove channel colour from channel messages
  1293. if (!overrideBGColor) {
  1294. if (!settings.tabChanColors) {
  1295. messageElem.parent().css("background", "");
  1296. }
  1297. }
  1298.  
  1299. convertTextToSpecial(messageText, messageCopy);
  1300.  
  1301. channel.append(messageCopy);
  1302.  
  1303. markChannelChanged(channelIndex);
  1304. }
  1305.  
  1306. function doScroll()
  1307. {
  1308. if (GOTO_BOTTOM || settings.force_scroll) {
  1309. robinChatWindow.scrollTop(robinChatWindow[0].scrollHeight);
  1310. }
  1311. }
  1312.  
  1313. //
  1314. // Get selected destination channel for messages
  1315. //
  1316. function selChanName()
  1317. {
  1318. if (selectedChannel >= 0)
  1319. return channelList[selectedChannel];
  1320. return dropdownChannel();
  1321. }
  1322.  
  1323. function updateTextCounter()
  1324. {
  1325. var chanPrefix = selChanName();
  1326. if (chanPrefix.length > 0)
  1327. chanPrefix += " ";
  1328.  
  1329. var maxLength = 140 - chanPrefix.length;
  1330.  
  1331. var $robinMessageText = $("#robinMessageText");
  1332. var message = $robinMessageText.val();
  1333. var messageLength = message.length;
  1334.  
  1335. // small channel names with a full message body switched to a long channel name
  1336. // cause robinMessageText to overflow out of bounds
  1337. // this truncates the message to the limit
  1338. if (messageLength > maxLength) {
  1339. message = message.substr(0, maxLength);
  1340. $robinMessageText.val(message);
  1341. }
  1342.  
  1343. // subtract the channel prefix from the maxLength
  1344. $("#robinMessageText").attr('maxLength', maxLength);
  1345. $("#textCounterDisplayAlt").text(String(Math.max(maxLength - message.length), 0));
  1346. }
  1347.  
  1348. var pastMessageQueue = [];
  1349. var pastMessageQueueIndex = 0;
  1350. var pastMessageTemp = "";
  1351. function updatePastMessageQueue(message)
  1352. {
  1353. pastMessageQueueIndex = 0;
  1354. pastMessageTemp = "";
  1355. var value = message;
  1356.  
  1357. if (!value || (pastMessageQueue.length > 0 && value == pastMessageQueue[0]))
  1358. return;
  1359.  
  1360. pastMessageQueue.unshift(value);
  1361.  
  1362. var maxhistorysize = parseInt(settings.messageHistorySize || "50", 10);
  1363. if (maxhistorysize < 0 || isNaN(maxhistorysize)) {
  1364. maxhistorysize = 50;
  1365. }
  1366.  
  1367. while (pastMessageQueue.length > maxhistorysize) {
  1368. pastMessageQueue.pop();
  1369. }
  1370. }
  1371.  
  1372. function onMessageBoxSubmit()
  1373. {
  1374. var chanName = selChanName();
  1375. var message = $("#robinMessageText").val();
  1376. var dest = $("#robinMessageText");
  1377.  
  1378. if(message.indexOf("@cipher") == 0 || message.indexOf("@c") == 0) {
  1379. var encryption_cue = message.indexOf("@cipher") == 0 ? "@cipher" : "@c";
  1380. var mes2 = "88z48" + $.trim(message.substr(encryption_cue.length));
  1381. var key = aesjs.util.convertStringToBytes(String(settings['cipherkey']));
  1382. var textBytes = aesjs.util.convertStringToBytes(mes2);
  1383. var aesCtr = new aesjs.ModeOfOperation.ctr(key);
  1384. var encryptedBytes = aesCtr.encrypt(textBytes);
  1385. var result = "";
  1386.  
  1387. for (index = 0; index <encryptedBytes.length; index++)
  1388. {
  1389. var c = encryptedBytes[index].toString(36);
  1390. if (c.length == 1) c += "A";
  1391. result += c;
  1392. }
  1393. dest.val(chanName + "em:" + result);
  1394. updateTextCounter();
  1395. }
  1396. else {
  1397. var chanPrefix = selChanName();
  1398.  
  1399. if (chanPrefix.length > 0)
  1400. chanPrefix += " ";
  1401.  
  1402. if (message.startsWith("/me "))
  1403. dest.val("/me " + chanPrefix + message.substring(4));
  1404. else if (message.startsWith("/"))
  1405. dest.val(message);
  1406. else
  1407. dest.val(chanPrefix + message);
  1408.  
  1409. updateTextCounter();
  1410. }
  1411.  
  1412. updatePastMessageQueue(message);
  1413. setTimeout(updateTextCounter, 50);
  1414. }
  1415.  
  1416. function onMessageBoxKeyUp(e)
  1417. {
  1418. var key = e.keyCode ? e.keyCode : e.charCode;
  1419. key = key || e.which;
  1420.  
  1421. if (key != 38 && key != 40)
  1422. return;
  1423.  
  1424. e.preventDefault();
  1425. e.stopPropagation();
  1426. e.stopImmediatePropagation();
  1427.  
  1428. var source = $("#robinMessageText").val();
  1429. var chanName = selChanName();
  1430. var namePart = "";
  1431.  
  1432. // Up Arrow - Message History
  1433. if (key == 38 && pastMessageQueue.length > pastMessageQueueIndex) {
  1434. if (pastMessageQueueIndex === 0) {
  1435. pastMessageTemp = source;
  1436. }
  1437.  
  1438. source = pastMessageQueue[pastMessageQueueIndex++];
  1439. }
  1440. // Down Arrow - Message History
  1441. else if (key == 40 && pastMessageQueueIndex > 0) {
  1442. pastMessageQueueIndex--;
  1443.  
  1444. if (pastMessageQueueIndex === 0) {
  1445. source = pastMessageTemp;
  1446. } else {
  1447. source = pastMessageQueue[pastMessageQueueIndex - 1];
  1448. }
  1449. }
  1450.  
  1451. $("#robinMessageText").val(source);
  1452. updateTextCounter();
  1453. }
  1454.  
  1455. // Monitor the most active channels.
  1456. var activeChannelsQueue = [];
  1457. var activeChannelsCounts = {};
  1458. function updateMostActiveChannels(messageText)
  1459. {
  1460. var chanName = messageText;
  1461.  
  1462. if (!chanName)
  1463. return;
  1464.  
  1465. // To simplify things, we're going to start by only handling channels that start with punctuation.
  1466. if (!chanName.match(/^[!"#$%&\\'()\*+,\-\.\/:;<=>?@\[\]\^_`{|}~]/))
  1467. return;
  1468.  
  1469. var index = chanName.indexOf("<Crypto>");
  1470. if (index >= 0)
  1471. chanName = chanName.substring(0, index);
  1472.  
  1473. index = chanName.indexOf(" ");
  1474. if (index >= 0)
  1475. chanName = chanName.substring(0, index);
  1476.  
  1477. if (!chanName || chanName == messageText)
  1478. return;
  1479.  
  1480. chanName = chanName.toLowerCase();
  1481. activeChannelsQueue.unshift(chanName);
  1482.  
  1483. if (!activeChannelsCounts[chanName]) {
  1484. activeChannelsCounts[chanName] = 0;
  1485. }
  1486. activeChannelsCounts[chanName]++;
  1487.  
  1488. if (activeChannelsQueue.length > 2000){
  1489. var oldChanName = activeChannelsQueue.pop();
  1490. activeChannelsCounts[oldChanName]--;
  1491. }
  1492. }
  1493.  
  1494. $('.robin-chat--header').click(function() {
  1495. $(".robin-chat--sidebar").toggleClass("sidebarminimized");
  1496. });
  1497.  
  1498. var myObserver = new MutationObserver(mutationHandler);
  1499. //--- Add a target node to the observer. Can only add one node at a time.
  1500. // XXX Shou: we should only need to watch childList, more can slow it down.
  1501. $("#robinChatMessageList").each(function() {
  1502. myObserver.observe(this, { childList: true });
  1503. });
  1504.  
  1505. var counter=0.0;
  1506. var countdown=0;
  1507. function mutationHandler(mutationRecords) {
  1508. mutationRecords.forEach(function(mutation) {
  1509. var jq = $(mutation.addedNodes);
  1510. // There are nodes added
  1511. if (jq.length > 0) {
  1512. var colors_match = {};
  1513. split_channels = getChannelString().toLowerCase().split(",");
  1514.  
  1515. for(i = 0; i < split_channels.length; i++){
  1516. colors_match[split_channels[i].trim()] = colors[i];
  1517. }
  1518.  
  1519. // cool we have a message.
  1520. var $timestamp = $(jq[0]).find('.robin-message--timestamp');
  1521.  
  1522. var $user = $(jq[0]).find('.robin--username');
  1523. if(! $user.length) {
  1524. $timestamp.after('<span class="robin-message--from robin--username"></span>');
  1525. $user = $(jq[0]).find('.robin--username');
  1526. }
  1527. var thisUser = $user.text();
  1528. var $message = $(jq[0]).find('.robin-message--message');
  1529. var messageText = $message.text();
  1530.  
  1531. // Decryption
  1532. var chanName = hasChannel(messageText).name;
  1533. if (messageText.indexOf(chanName + "em:") == 0) {
  1534. var plainMessage = "";
  1535. for (index = (chanName + "em:").length; index < messageText.length; index += 2)
  1536. plainMessage += ((plainMessage.length > 0 ? "," : "") + messageText.substring(index, index + 2).replace("A", ""));
  1537.  
  1538. var key = aesjs.util.convertStringToBytes(String(settings['cipherkey']));
  1539. var aesCtr = new aesjs.ModeOfOperation.ctr(key);
  1540. var hexList = plainMessage.split(",");
  1541. var textBytes = hexList.map(function (x) {
  1542. return parseInt(x, 36);
  1543. });
  1544. console.log("textbytes: " + textBytes.toString());
  1545. var decryptedBytes = aesCtr.decrypt(textBytes);
  1546. // Convert our bytes back into text
  1547. var decryptedText = aesjs.util.convertBytesToString(decryptedBytes);
  1548. messageText = messageText.replace(textBytes, decryptedText);
  1549.  
  1550. var special = "88z48";
  1551.  
  1552. if(decryptedText.indexOf(special) == -1){
  1553. $message = null;
  1554. $(jq[0]).remove();
  1555. }
  1556. else {
  1557. $(jq[0]).find('.robin-message--message').text(chanName+"<Crypto> "+decryptedText.substring(5));
  1558. messageText = $message.text();
  1559. }
  1560. }
  1561.  
  1562. var is_muted = (thisUser && mutedList.indexOf(thisUser) >= 0);
  1563. var is_spam = (settings.removeSpam && isBotSpam(messageText));
  1564. var remove_message = is_muted || is_spam;
  1565.  
  1566. if (!remove_message) {
  1567. updateMostActiveChannels(messageText);
  1568. }
  1569.  
  1570. datenow = new Date();
  1571. userExtra[$user.text()] = datenow;
  1572. //updateUserPanel();
  1573.  
  1574. var exclude_list = String(settings.channel_exclude).split(",");
  1575. var results_chan_exclusion = hasChannelFromList(messageText, exclude_list, true, true);
  1576.  
  1577. if (exclude_list.length > 0, String(settings.channel_exclude).trim().length > 0 && results_chan_exclusion.has) {
  1578. $message = null;
  1579. $(jq[0]).remove();
  1580. return;
  1581. }
  1582.  
  1583. if (String(settings['username_bg']).length > 0) {
  1584. $user.css("background", String(settings['username_bg']));
  1585. }
  1586.  
  1587. var alignedUser = settings['alignment'] ? $user.html().lpad('&nbsp;', 20) : $user.html().rpad('&nbsp;', 20);
  1588.  
  1589. $user.html(alignedUser);
  1590. var stylecalc = "";
  1591. if (settings.fontstyle !== ""){
  1592. stylecalc = '"'+settings.fontstyle.trim()+'"' + ",";
  1593. }
  1594. stylecalc = stylecalc + 'Consolas, "Lucida Console", Monaco, monospace';
  1595. $user.css("font-family", stylecalc).css("font-size", settings.fontsize+"px");
  1596. $message.css("font-family", stylecalc).css("font-size", settings.fontsize+"px");
  1597.  
  1598. var results_chan = hasChannel(messageText);
  1599.  
  1600. var nextIsRepeat = jq.hasClass('robin--user-class--system') && messageText.indexOf("try again") >= 0;
  1601. if (nextIsRepeat) {
  1602. var messageText = jq.next().find(".robin-message--message").text();
  1603. var chanName = selChanName();
  1604. if (messageText.toLowerCase().startsWith(chanName.toLowerCase()))
  1605. messageText = messageText.substring(chanName.length).trim();
  1606.  
  1607. $("#robinMessageText").val(messageText);
  1608. updateTextCounter();
  1609. }
  1610.  
  1611. remove_message = remove_message && !jq.hasClass("robin--user-class--system");
  1612. if (remove_message) {
  1613. $message = null;
  1614. $(jq[0]).remove();
  1615.  
  1616. return;
  1617. }
  1618.  
  1619. var userIsMentioned = false;
  1620. if (messageText.toLowerCase().indexOf(currentUsersName.toLowerCase()) !== -1) {
  1621. $message.parent().css("background","#FFA27F");
  1622. notifAudio.play();
  1623. userIsMentioned = true;
  1624. } else {
  1625.  
  1626. //still show mentions in highlight color.
  1627.  
  1628. var result = hasChannel(messageText);
  1629.  
  1630. if(result.has) {
  1631. $message.parent().css("background", colors_match[result.name]);
  1632. } else {
  1633.  
  1634. var is_not_in_channels = (settings.filterChannel &&
  1635. !jq.hasClass('robin--user-class--system') &&
  1636. String(getChannelString()).length > 0 &&
  1637. !results_chan.has);
  1638.  
  1639. if (is_not_in_channels) {
  1640. $message = null;
  1641. $(jq[0]).remove();
  1642.  
  1643. return;
  1644. }
  1645. }
  1646. }
  1647. if (thisUser.indexOf("[robin]") !=-1){
  1648. if ($message.text().indexOf("RATELIMIT") != -1){
  1649. var rltime = $.trim($message.text().substr(54));
  1650. rltime = parseInt(rltime.substring(0, rltime.indexOf(' ')))+1;
  1651. if (rltime>10)rltime=1;
  1652. console.log(rltime.toString());
  1653. countdown=rltime;
  1654. }
  1655. }
  1656.  
  1657. // Move channel messages to channel tabs
  1658. if (results_chan.has)
  1659. moveChannelMessage(results_chan.index, jq[0], userIsMentioned, true);
  1660.  
  1661. if (selectedChannel >= 0 && thisUser.trim() == '[robin]')
  1662. moveChannelMessage(selectedChannel, jq[0], false, false);
  1663.  
  1664. if (!results_chan.has || !settings.removeChanMessageFromGlobal)
  1665. markChannelChanged(-1);
  1666.  
  1667. if (!settings.removeChanMessageFromGlobal)
  1668. {
  1669. if (results_chan.has) {
  1670. messageText = messageText.substring(results_chan.name.length).trim();
  1671. $message.text(messageText);
  1672. }
  1673.  
  1674. // This needs to be done after any changes to the $message.text() since they will overwrite $message.html() changes
  1675. convertTextToSpecial(messageText, jq[0]);
  1676.  
  1677. $("<span class='robin-message--from'><strong>" + results_chan.name.lpad("&nbsp", 6) + "</strong></span>").css("font-family", '"Lucida Console", Monaco, monospace')
  1678. .css("font-size", "12px")
  1679. .insertAfter($timestamp);
  1680. }
  1681.  
  1682. findAndHideSpam();
  1683. doScroll();
  1684. }
  1685. });
  1686. }
  1687. function countTimer() {
  1688. counter += 0.5;
  1689. if (countdown % 1 == 0 && countdown != 0) {
  1690. $('#sendBtn').css('background-color', '#FF5555');
  1691. } else {
  1692. $('#sendBtn').css('background-color', '');
  1693. }
  1694. if (countdown > 1 ) {
  1695. countdown -= 0.5;
  1696. $('#sendBtn').html("Chat in: " + parseInt(countdown));
  1697. } else if (countdown == 1) {
  1698. $('#sendBtn').html("Send Message");
  1699. countdown = 0;
  1700. }
  1701.  
  1702. }
  1703.  
  1704. setInterval(update, 10000);
  1705. update();
  1706.  
  1707. setInterval(countTimer, 500);
  1708.  
  1709. var flairColor = [
  1710. '#e50000', // red
  1711. '#db8e00', // orange
  1712. '#ccc100', // yellow
  1713. '#02be01', // green
  1714. '#0083c7', // blue
  1715. '#820080' // purple
  1716. ];
  1717.  
  1718. function colorFromName(name) {
  1719. sanitizedName = name.toLowerCase().replace(/[^a-z0-9]/g, "");
  1720. flairNum = parseInt(sanitizedName, 36) % 6;
  1721. return flairColor[flairNum];
  1722. }
  1723.  
  1724. // Initial pass to color names in user list
  1725. $('#robinUserList').find('.robin--username').each(function(){
  1726. this.style.color = colorFromName(this.textContent);
  1727. });
  1728.  
  1729. // When a user's status changes, they are removed from the user list and re-added with new status classes,
  1730. // so here we watch for names being added to the user list to re-color
  1731. var myUserListObserver = new MutationObserver(function(mutations) {
  1732. mutations.forEach(function(mutation) {
  1733. if (mutation.addedNodes.length > 0) {
  1734. var usernameSpan = mutation.addedNodes[0].children[1];
  1735. // This may need to be fixed. Until then, I'm adding this if statement to prevent errors flooding the console.
  1736. if (usernameSpan) {
  1737. usernameSpan.style.color = colorFromName(usernameSpan.innerHTML);
  1738. }
  1739. }
  1740. });
  1741. });
  1742. myUserListObserver.observe(document.getElementById("robinUserList"), { childList: true });
  1743.  
  1744. // Color current user's name in chat and darken post backgrounds
  1745. var currentUserColor = colorFromName(currentUsersName);
  1746. $('<style>.robin--user-class--self .robin--username { color: ' + currentUserColor + ' !important; }</style>').appendTo('body');
  1747.  
  1748. $(".text-counter-input").attr("id", "robinMessageText");
  1749. $("#robinMessageText").on("input", function() { updateTextCounter(); });
  1750. $("#robinMessageText").on("keyup", function(e) { onMessageBoxKeyUp(e); });
  1751.  
  1752. $(".text-counter-display")
  1753. .css("display", "none")
  1754. .after('<span id="textCounterDisplayAlt">140</span>');
  1755.  
  1756. // Send message button
  1757. $("#robinSendMessage").append('<div id="sendBtn" class="robin-chat--vote">Send Message</div>'); // Send message
  1758.  
  1759. // Submit message by pressing enter
  1760. $("#robinSendMessage").on("submit", function() { onMessageBoxSubmit(); } );
  1761.  
  1762. // Submit message by clicking button
  1763. $("#robinSendMessage").append('<input id="sendBtnHidden" type="button" onclick=\'$(".text-counter-input").submit()\' style="display:none;"></input>');
  1764. $("#sendBtn").on("click", function() { onMessageBoxSubmit(); $("#sendBtnHidden").trigger('click'); } );
  1765.  
  1766. // Setup page for tabbed channels
  1767. setupMultiChannel();
  1768.  
  1769. $('#robinChatWindow').scroll(function() {
  1770. if(robinChatWindow.scrollTop() < robinChatWindow[0].scrollHeight - robinChatWindow.height()) {
  1771. GOTO_BOTTOM = false;
  1772. return;
  1773. }
  1774. GOTO_BOTTOM = true;
  1775. });
  1776.  
  1777. $(document).keydown(function(e) {
  1778. if (!settings.enableQuickTabNavigation) return; // Is quicknav enabled
  1779.  
  1780. //console.log(e.keyCode);
  1781.  
  1782. var lKeycode = 37;
  1783. var rKeycode = 39; // set the keycodes to default
  1784.  
  1785. if (settings.enableAdvancedNaviOptions) { // are we using advanced settings
  1786. if (( settings.quickTabNaviCtrlRequired && !e.ctrlKey) ||
  1787. (!settings.quickTabNaviCtrlRequired && e.ctrlKey))
  1788. return;
  1789. if (( settings.quickTabNaviAltRequired && !e.altKey) ||
  1790. (!settings.quickTabNaviAltRequired && e.altKey))
  1791. return;
  1792. if (( settings.quickTabNaviShiftRequired && !e.shiftKey) ||
  1793. (!settings.quickTabNaviShiftRequired && e.shiftKey))
  1794. return;
  1795. if (( settings.quickTabNaviMetaRequired && !e.metaKey) ||
  1796. (!settings.quickTabNaviMetaRequired && e.metaKey))
  1797. return;
  1798.  
  1799. lKeycode = settings.quickTabNaviKeyLeft; // if we made it this far set the new keycodes
  1800. rKeycode = settings.quickTabNaviKeyRight;
  1801. }
  1802. else { // using original keycodes
  1803. if (!((e.metaKey || e.ctrlKey) && e.shiftKey)) {
  1804. return;
  1805. }
  1806. }
  1807.  
  1808. var key = e.keyCode ? e.keyCode : e.charCode
  1809. key = key || e.which;
  1810. if (key == lKeycode) {
  1811. var newChanIdx = selectedChannel - 1;
  1812.  
  1813. if (newChanIdx <= -2) {
  1814. newChanIdx = channelList.length - 1;
  1815. }
  1816. selectChannel(newChanIdx);
  1817. }
  1818.  
  1819. if (key == rKeycode) {
  1820. var newChanIdx = selectedChannel + 1;
  1821.  
  1822. if (newChanIdx == channelList.length) {
  1823. newChanIdx = -1;
  1824. }
  1825. selectChannel(newChanIdx);
  1826. }
  1827. });
  1828.  
  1829. //merge easter egg
  1830. (function(){
  1831. var easterEgg_partyNoMore = localStorage.getItem('easterEgg_partyNoMore'),
  1832. easterEgg_assetsCached = localStorage.getItem('easterEgg_assetsCached');
  1833.  
  1834. //chance to pre-cache assets
  1835. if(!easterEgg_assetsCached) {
  1836. var shouldCache = Math.floor(Math.random() * (20 - 1 + 1)) + 1;
  1837. if(shouldCache == 20) {
  1838. localStorage.setItem('easterEgg_assetsCached', true);
  1839. var easterEgg_cachedAirhorn = new Audio("https://www.myinstants.com/media/sounds/air-horn-club-sample_1.mp3");
  1840. var easterEgg_cachedCheer = new Audio("https://www.myinstants.com/media/sounds/cheering.mp3");
  1841. var easterEgg_cachedFireworks=new Image();
  1842. easterEgg_cachedFireworks.src='https://media.giphy.com/media/araoLWtAIZKzS/giphy.gif';
  1843. var easterEgg_cachedParrot=new Image();
  1844. easterEgg_cachedParrot.src='https://media.giphy.com/media/10v0l8aVLyLJ5e/giphy.gif';
  1845. }
  1846. }
  1847.  
  1848. //only play it once
  1849. if(!easterEgg_partyNoMore){
  1850. var easterEgg_robinTier,
  1851. easterEgg_airHorn = [],
  1852. easterEgg_airHorn_interval = [],
  1853. easterEgg_airHorn_timeOut = [
  1854. 300,
  1855. 800,
  1856. 1200,
  1857. 500
  1858. ],
  1859. easterEgg_cheer,
  1860. easterEgg_cheer_interval,
  1861. easterEgg_cheer_timeOut = 600,
  1862. easterEgg_scriptString = $('script#config')[0].innerHTML;//script object containing complete user list
  1863. easterEgg_scriptString_startIndex = easterEgg_scriptString.indexOf('"robin_user_list": '),
  1864. easterEgg_users_length = $.parseJSON('{'+easterEgg_scriptString.substr(easterEgg_scriptString_startIndex, easterEgg_scriptString.length).split(']')[0]+']}').robin_user_list.length,//script object parsed into user length
  1865. easterEgg_fireWorks_URL = 'https://media.giphy.com/media/araoLWtAIZKzS/giphy.gif',
  1866. easterEgg_fireWorks = [],
  1867. easterEgg_fireWorks_BUFFER = 100,
  1868. easterEgg_windowHeight = $( window ).height(),
  1869. easterEgg_windowWidth = $( window ).width();
  1870.  
  1871. if(easterEgg_users_length>4410){//the number to beat (or close to it)
  1872. easterEgg_robinTier=17;
  1873. }else{
  1874. easterEgg_robinTier=16;
  1875. }
  1876. // uncomment to enable (requires clearing of local storage for multiple views)
  1877. // easterEgg_robinTier=17;
  1878.  
  1879. //if we're tier 17
  1880. if(easterEgg_robinTier == 17){
  1881. //set local storage so it doesn't happen again
  1882. localStorage.setItem('easterEgg_partyNoMore', true);
  1883.  
  1884. //create cheer loop
  1885. easterEgg_cheer = new Audio("https://www.myinstants.com/media/sounds/cheering.mp3");
  1886. easterEgg_cheer_interval = setInterval(function(){
  1887. easterEgg_cheer.play();
  1888. }, easterEgg_cheer_timeOut);
  1889.  
  1890. //create fireworks
  1891. for (var i = 0; i < 7; i++) {
  1892. var easterEgg_fireWorks_image = $('<img>');
  1893. easterEgg_fireWorks_image.attr('src', easterEgg_fireWorks_URL);
  1894. easterEgg_fireWorks_image.css('position', 'absolute');
  1895. easterEgg_fireWorks_image.css('zIndex', '99');
  1896. easterEgg_fireWorks_image.css('top', Math.random() * ((easterEgg_windowHeight-easterEgg_fireWorks_BUFFER) - easterEgg_fireWorks_BUFFER));
  1897. easterEgg_fireWorks_image.css('left', Math.random() * ((easterEgg_windowWidth-easterEgg_fireWorks_BUFFER) - easterEgg_fireWorks_BUFFER));
  1898. easterEgg_fireWorks.push(easterEgg_fireWorks_image);
  1899.  
  1900. $('body').append(easterEgg_fireWorks_image);
  1901. }
  1902.  
  1903. //create airhorn loops
  1904. for (var i = 0; i < 4; i++) {
  1905. (function(){
  1906. var y = i;
  1907. easterEgg_airHorn[y] = new Audio("https://www.myinstants.com/media/sounds/air-horn-club-sample_1.mp3");
  1908. easterEgg_airHorn_interval[y] = setInterval(function(){
  1909. (function(){
  1910. var x = y;
  1911. // easterEgg_airHorn[x].play();
  1912. })();
  1913. }, easterEgg_airHorn_timeOut[y]);
  1914. })();
  1915. }
  1916. //dancing parrot
  1917. $('body').append('<div id="easterEgg_parrot" style="position:absolute; z-index:100; top:0; right:0; width:360px; height:203px"><img src="https://media.giphy.com/media/10v0l8aVLyLJ5e/giphy.gif"></div>');
  1918.  
  1919. //"we did it" banner
  1920. $('body').append('<div id="easterEgg_weDidItReddit" style="position:absolute; z-index:101; top:0; left:0; color:red; font-size: 100px;">WE DID IT REDDIT!!!111!</div>');
  1921.  
  1922. //remove everything @30s
  1923. setTimeout(function(){
  1924. $('#easterEgg_parrot').remove();
  1925. $('#easterEgg_weDidItReddit').remove();
  1926. clearTimeout(easterEgg_cheer_interval);
  1927. for (var i = 0; i < 4; i++) {
  1928. clearTimeout(easterEgg_airHorn_interval[i]);
  1929. }
  1930. for (var i = 0; i < easterEgg_fireWorks.length; i++) {
  1931. easterEgg_fireWorks[i].remove();
  1932. }
  1933. }, 15000);
  1934. }
  1935. }
  1936. })();
  1937.  
  1938. GM_addStyle(" \
  1939. .robin--username { \
  1940. cursor: pointer \
  1941. } \
  1942. #settingContent { \
  1943. overflow-y: scroll; \
  1944. } \
  1945. #robinVoteWidget, \
  1946. #closeBtn, \
  1947. #sendBtn { \
  1948. font-weight: bold; \
  1949. } \
  1950. .robin-chat--sidebar .robin-chat--vote, \
  1951. #sendBtn { \
  1952. margin-left: 0; \
  1953. } \
  1954. .info-box-only, \
  1955. .robin--vote-class--novote { \
  1956. pointer-events: none; \
  1957. } \
  1958. #openBtn, \
  1959. #closeBtn, \
  1960. #sendBtn, \
  1961. #standingsBtn, \
  1962. #channelsBtn { \
  1963. cursor: pointer; \
  1964. } \
  1965. #openBtn_wrap { \
  1966. text-align: center; \
  1967. } \
  1968. #openBtn_wrap > div:first-child { \
  1969. padding-top: 0; \
  1970. } \
  1971. #openBtn_wrap p { \
  1972. font-size: 12px; \
  1973. } \
  1974. #openBtn_wrap img { \
  1975. display:inline-block; \
  1976. vertical-align:middle; \
  1977. width:15px; \
  1978. height:15px; \
  1979. } \
  1980. #standingsContent .robin-chat--vote { \
  1981. cursor: pointer; \
  1982. font-weight: bold; \
  1983. margin-left: 0; \
  1984. } \
  1985. #channelsContent .robin-chat--vote { \
  1986. cursor: pointer; \
  1987. font-weight: bold; \
  1988. margin-left: 0; \
  1989. } \
  1990. #channelsContent th { \
  1991. text-align: center !important; \
  1992. } \
  1993. #channelsContent td { \
  1994. text-align: center !important; \
  1995. } \
  1996. .channelBtn {\
  1997. padding: 0 !important; \
  1998. font-size: x-small !important; \
  1999. }\
  2000. .robin--user-class--self { \
  2001. background: #F5F5F5; \
  2002. font-weight: bold; \
  2003. } \
  2004. .robin--user-class--self .robin--username { \
  2005. font-weight: bold; \
  2006. } \
  2007. #robinChatInput { \
  2008. background: #EFEFED; \
  2009. } \
  2010. #chat-prepend-area { \
  2011. font-size: 11px; \
  2012. margin: 0 0 3px 20px; \
  2013. display: inline-block; \
  2014. } \
  2015. #chat-prepend-area label { \
  2016. margin-right: 0; \
  2017. display: inline-block; \
  2018. } \
  2019. #chat-prepend-select { \
  2020. margin: 0 10px; \
  2021. } \
  2022. #robinSendMessage .text-counter { \
  2023. float: right; \
  2024. } \
  2025. #chatstats { \
  2026. font-weight:bold; \
  2027. } \
  2028. \
  2029. /* Change font to fixed-width */ \
  2030. #robinChatWindow { \
  2031. font-family: Consolas, 'Lucida Console', Monaco, monospace; \
  2032. } \
  2033. \
  2034. /* Full Height Chat */ \
  2035. @media(min-width:769px) { \
  2036. .content { \
  2037. border: none; \
  2038. } \
  2039. .footer-parent { \
  2040. margin-top: 0; \
  2041. font-size: inherit; \
  2042. } \
  2043. .debuginfo { \
  2044. display: none; \
  2045. } \
  2046. .bottommenu { \
  2047. padding: 0 3px; \
  2048. display: inline-block; \
  2049. } \
  2050. #robinChatInput { \
  2051. padding: 2px; \
  2052. } \
  2053. #sendBtn, #clear-chat-button { \
  2054. margin-bottom: 0; \
  2055. margin-right: 0; \
  2056. } \
  2057. .robin-chat .robin-chat--body { \
  2058. /* 130 is height of reddit header, chat header, and remaining footer */ \
  2059. height: calc(100vh - 130px) \
  2060. } \
  2061. } \
  2062. \
  2063. /* Settings Panel */ \
  2064. #settingContent .robin-chat--sidebar-widget { \
  2065. padding: 6px 0; \
  2066. } \
  2067. #settingContent .robin-chat--sidebar-widget ul { \
  2068. list-style-type: circle; \
  2069. font-size: 12px; \
  2070. padding-left: 40px; \
  2071. font-weight: normal; \
  2072. } \
  2073. #settingContent .robin-chat--sidebar-widget label { \
  2074. font-weight: bold; \
  2075. } \
  2076. #settingContent .robin-chat--sidebar-widget input[type='text'] { \
  2077. width: 100%; \
  2078. padding: 2px; \
  2079. } \
  2080. #settingContent .robin-chat--sidebar-widget input[type='checkbox'] { \
  2081. vertical-align: middle; \
  2082. margin-right: 0; \
  2083. } \
  2084. \
  2085. /* RES Night Mode Support */ \
  2086. .res-nightmode .robin-message, \
  2087. .res-nightmode .robin--user-class--self .robin--username, \
  2088. .res-nightmode .robin-room-participant .robin--username, \
  2089. .res-nightmode:not([class*=flair]) > .robin--username, \
  2090. .res-nightmode .robin-chat .robin-chat--vote, \
  2091. .res-nightmode .robin-message[style*='color: white'] { \
  2092. color: #DDD; \
  2093. } \
  2094. .res-nightmode .robin-chat .robin-chat--sidebar, \
  2095. .res-nightmode .robin-chat .robin-chat--vote { \
  2096. background-color: #262626; \
  2097. } \
  2098. .res-nightmode #robinChatInput { \
  2099. background-color: #262626 !important; \
  2100. } \
  2101. .res-nightmode .robin-chat .robin-chat--vote { \
  2102. box-shadow: 0px 0px 2px 1px #888; \
  2103. } \
  2104. .res-nightmode .robin-chat .robin-chat--vote.robin--active { \
  2105. background-color: #444444; \
  2106. box-shadow: 1px 1px 5px 1px black inset; \
  2107. } \
  2108. .res-nightmode .robin-chat .robin-chat--vote:focus { \
  2109. background-color: #848484; \
  2110. outline: 1px solid #9A9A9A; \
  2111. } \
  2112. .res-nightmode .robin--user-class--self { \
  2113. background-color: #424242; \
  2114. } \
  2115. .res-nightmode .robin-message[style*='background: rgb(255, 162, 127)'] { \
  2116. background-color: #520000 !important; \
  2117. } \
  2118. .res-nightmode .robin-chat .robin-chat--user-list-widget { \
  2119. overflow-x: hidden; \
  2120. } \
  2121. .res-nightmode .robin-chat .robin-chat--sidebar-widget { \
  2122. border-bottom: none; \
  2123. } \
  2124. \
  2125. #standingsTable table {width: 100%} \
  2126. #standingsTable table th { \
  2127. font-weight: bold; \
  2128. } \
  2129. #standingsTable > div:first-child { \
  2130. font-weight: bold; \
  2131. text-align: center; \
  2132. } \
  2133. #channelsTable table {width: 100%} \
  2134. #channelsTable table th { \
  2135. font-weight: bold; \
  2136. } \
  2137. #channelsTable > div:first-child { \
  2138. font-weight: bold; \
  2139. text-align: center; \
  2140. } \
  2141. .robin-chat--sidebar.sidebarminimized { \
  2142. display: none; \
  2143. } \
  2144. \
  2145. /* Styles for tab bar */ \
  2146. [id^='robinChannelLink'] { \
  2147. width:10%; \
  2148. display:inline-block; \
  2149. } \
  2150. #robinChannelList { \
  2151. width: 72%!important; \
  2152. top: 105px!important; \
  2153. } \
  2154. ul#robinChannelList { \
  2155. list-style-type: none; \
  2156. margin: 0px; \
  2157. padding:0.3em 0; \
  2158. position:absolute; \
  2159. top:95px; \
  2160. width:85%; \
  2161. } \
  2162. ul#robinChannelList li { \
  2163. display: inline; \
  2164. } \
  2165. ul#robinChannelList li a { \
  2166. width:auto!important; \
  2167. color: #42454a; \
  2168. background-color: #dedbde; \
  2169. border: 1px solid #c9c3ba; \
  2170. border-bottom: none; \
  2171. padding:2px 30px!important; \
  2172. text-decoration: none; \
  2173. font-size:1em; \
  2174. } \
  2175. ul#robinChannelList li a:hover { \
  2176. background-color: #f1f0ee; \
  2177. } \
  2178. ul#robinChannelList li a.robin-chan-tab-changed { \
  2179. color: red; \
  2180. font-weight: bold; \
  2181. } \
  2182. ul#robinChannelList li a.robin-chan-tab-selected { \
  2183. color: blue; \
  2184. background-color: white; \
  2185. font-weight: bold; \
  2186. padding: 0.7em 0.3em 0.38em 0.3em; \
  2187. } \
  2188. ");
  2189. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement