Advertisement
Guest User

Untitled

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