Robin Enhancement Script

Highlight mentions, make links clickable, add tabbed channels & automatically remove spam

目前为 2016-04-07 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Robin Enhancement Script
  3. // @namespace https://www.reddit.com/
  4. // @version 3.2.0
  5. // @description Highlight mentions, make links clickable, add tabbed channels & automatically remove spam
  6. // @author Bag, netnerd01
  7. // @match https://www.reddit.com/robin*
  8. // @grant none
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // ==/UserScript==
  12. (function() {
  13.  
  14. // Grab users username + play nice with RES
  15. var robin_user = $("#header-bottom-right .user a").first().text();
  16. var ignored_users = {};
  17.  
  18. // for spam counter - very important i know :P
  19. var blocked_spam_el = null;
  20. var blocked_spam = 0;
  21. var user_last_message = '';
  22. //
  23. var _robin_grow_detected = false;
  24.  
  25. var colors = [
  26. 'rgba(255,0,0,0.1)',
  27. 'rgba(0,255,0,0.1)',
  28. 'rgba(0,0,255,0.1)',
  29. 'rgba(0,255,255,0.1)',
  30. 'rgba(255,0,255,0.1)',
  31. 'rgba(255,255,0,0.1)',
  32. 'rgba(211,211,211, .1)',
  33. 'rgba(0,100,0, .1)',
  34. 'rgba(255,20,147, .1)',
  35. 'rgba(184,134,11, .1)',
  36. ];
  37.  
  38.  
  39. // Play nice with Greasemonkey
  40. if(typeof GM_getValue === "undefined") GM_getValue = function(){return false;};
  41. if(typeof GM_setValue === "undefined") GM_setValue = function(){return false;};
  42.  
  43. /**
  44. * Pull tabber out in to semi-stand alone module
  45. * Big thanks to netnerd01 for his pre-work on this
  46. *
  47. * Basic usage - tabbedChannels.init( dom_node_to_add_tabs_to );
  48. * and hook up tabbedChannels.proccessLine(lower_case_text, jquery_of_line_container); to each line detected by the system
  49. */
  50. var tabbedChannels = new function(){
  51. var _self = this;
  52.  
  53. // Default options
  54. this.channels = ["~","*",".","%","$","#",";","^","<3",":gov","#rpg","@"];
  55. this.mode = 'single';
  56.  
  57. // internals
  58. this.unread_counts = {};
  59. this.$el = null;
  60. this.$opt = null;
  61. this.defaultRoomClasses = '';
  62. this.channelMatchingCache = [];
  63.  
  64. //channels user is in currently
  65. this.currentRooms = 0;
  66.  
  67. // When channel is clicked, toggle it on or off
  68. this.toggle_channel = function(e){
  69. var channel = $(e.target).data("filter");
  70. if(channel===null)return; // no a channel
  71.  
  72. if(!$("#robinChatWindow").hasClass("robin-filter-" + channel)){
  73. _self.enable_channel(channel);
  74. $(e.target).addClass("selected");
  75. // clear unread counter
  76. $(e.target).find("span").text(0);
  77. _self.unread_counts[channel] = 0;
  78. }else{
  79. _self.disable_channel(channel);
  80. $(e.target).removeClass("selected");
  81. }
  82.  
  83. // scroll everything correctly
  84. _scroll_to_bottom();
  85. };
  86.  
  87. // Enable a channel
  88. this.enable_channel = function(channel_id){
  89.  
  90. // if using room type "single", deslect other rooms on change
  91. if(this.mode == "single"){
  92. this.disable_all_channels();
  93. }
  94.  
  95. $("#robinChatWindow").addClass("robin-filter robin-filter-" + channel_id);
  96. $("#robinChatWindow").attr("data-channel-key", this.channels[channel_id]);
  97. this.currentRooms++;
  98. // unselect show all
  99. _self.$el.find("span.all").removeClass("selected");
  100. };
  101.  
  102. // disable a channel
  103. this.disable_channel = function(channel_id){
  104. $("#robinChatWindow").removeClass("robin-filter-" + channel_id);
  105. this.currentRooms--;
  106.  
  107. // no rooms selcted, run "show all"
  108. if(this.currentRooms == 0){
  109. this.disable_all_channels();
  110. }else{
  111. // Grab next channel name if u leave a room in multi mode
  112. $("#robinChatWindow").attr("data-channel-key", $(".robin-filters span.selected").first().data("filter-name"));
  113. }
  114. };
  115.  
  116. // turn all channels off
  117. this.disable_all_channels = function(e){
  118. $("#robinChatWindow").attr("class", _self.defaultRoomClasses).attr("data-channel-key","");
  119. _self.$el.find(".robin-filters > span").removeClass("selected");
  120. this.currentRooms = 0;
  121.  
  122. _self.$el.find("span.all").addClass("selected");
  123. _scroll_to_bottom();
  124. };
  125.  
  126. // render tabs
  127. this.drawTabs = function(){
  128. html = '';
  129. for(var i in this.channels){
  130. if(typeof this.channels[i] === 'undefined') continue;
  131. html += '<span data-filter="' + i + '" data-filter-name="'+ this.channels[i] +'">' + this.channels[i] + ' (<span>0</span>)</span> ';
  132. }
  133. this.$el.find(".robin-filters").html(html);
  134. };
  135.  
  136. // After creation of a new channel, go find if any content (not matched by a channel already) is relevant
  137. this.reScanChannels = function(new_channel){
  138. $("#robinChatWindow").find("div.robin-message").each(function(idx,item){
  139. var line = $(item).find(".robin-message--message").text().toLowerCase();
  140. tabbedChannels.proccessLine(line, $(item), true);
  141. });
  142. }
  143.  
  144. // Add new channel
  145. this.addChannel = function(new_channel){
  146. if(this.channels.indexOf(new_channel) === -1){
  147. this.channels.push(new_channel);
  148. this.unread_counts[this.channels.length-1] = 0;
  149. this.updateChannelMatchCache();
  150. this.saveChannelList();
  151. this.drawTabs();
  152.  
  153. // Populate content for channel
  154. this.reScanChannels();
  155.  
  156. // refresh everything after redraw
  157. this.disable_all_channels();
  158. }
  159. };
  160.  
  161. // remove existing channel
  162. this.removeChannel = function(channel){
  163. if(confirm("are you sure you wish to remove the " + channel + " channel?")){
  164. var idx = this.channels.indexOf(channel);
  165. delete this.channels[idx];
  166. this.updateChannelMatchCache();
  167. this.saveChannelList();
  168. this.drawTabs();
  169.  
  170. // sub channels, will fall back to existing channels
  171. this.reScanChannels();
  172.  
  173. // refresh everything after redraw
  174. this.disable_all_channels();
  175. }
  176. };
  177.  
  178.  
  179. // save channel list
  180. this.saveChannelList = function(){
  181. // clean array before save
  182. var channels = this.channels.filter(function (item) { return item != undefined });
  183. GM_setValue("robin-enhance-channels", channels);
  184. };
  185.  
  186. // Change chat mode
  187. this.changeChannelMode = function(e){
  188. _self.mode = $(this).data("type");
  189.  
  190. // swicth bolding
  191. $(this).parent().find("span").css("font-weight","normal");
  192. $(this).css("font-weight","bold");
  193. _self.disable_all_channels();
  194.  
  195. // Update mode setting
  196. GM_setValue("robin-enhance-mode", _self.mode);
  197. };
  198.  
  199. this.updateChannelMatchCache = function(){
  200. var order = this.channels.slice(0);
  201. order.sort(function(a, b){
  202. return b.length - a.length; // ASC -> a - b; DESC -> b - a
  203. });
  204. for(var i in order){
  205. order[i] = this.channels.indexOf(order[i]);
  206. }
  207. // sorted array of channel name indexs
  208.  
  209. this.channelMatchingCache = order;
  210. }
  211.  
  212. // Procces each chat line to create text
  213. this.proccessLine = function(text, $element, rescan){
  214. var i, idx, channel;
  215.  
  216. // If rescanning, clear any existing "channel" classes
  217. if(typeof rescan !== 'undefined' && rescan === true){
  218. $element.removeClass("in-channel");
  219.  
  220. for(i=0; i <= this.channels.length; i++){
  221. $element.removeClass("robin-filter-" + i);
  222. }
  223. }
  224.  
  225. // Scann for channel identifiers
  226. for(i=0; i< this.channelMatchingCache.length; i++){ // sorted so longer get picked out before shorter ones (sub channel matching)
  227. idx = this.channelMatchingCache[i];
  228. channel = this.channels[idx];
  229.  
  230. if(typeof channel === 'undefined') continue;
  231.  
  232. if(text.indexOf(channel) === 0){
  233. $element.addClass("robin-filter-" + idx +" in-channel");
  234. this.unread_counts[idx]++;
  235. return;
  236. }
  237. }
  238. };
  239.  
  240. // If in one channel, auto add channel keys
  241. this.submit_helper = function(){
  242. if($("#robinChatWindow").hasClass("robin-filter")){
  243. // auto add channel key
  244. var channel_key = $("#robinChatWindow").attr("data-channel-key");
  245.  
  246. if($(".text-counter-input").val().indexOf("/me") === 0){
  247. $(".text-counter-input").val("/me " + channel_key + " " + $(".text-counter-input").val().substr(3));
  248. }else if($(".text-counter-input").val().indexOf("/") !== 0){
  249. // if its not a "/" command, add channel
  250. $(".text-counter-input").val(channel_key + " " + $(".text-counter-input").val());
  251. }
  252. }
  253. };
  254.  
  255. // Update everuything
  256. this.tick = function(){
  257. _self.$el.find(".robin-filters span").each(function(){
  258. if($(this).hasClass("selected")) return;
  259. $(this).find("span").text(_self.unread_counts[$(this).data("filter")]);
  260. });
  261. };
  262.  
  263. // Init tab zone
  264. this.init = function($el){
  265. // Load channels
  266. if(GM_getValue("robin-enhance-channels")){
  267. this.channels = GM_getValue("robin-enhance-channels");
  268. }
  269. if(GM_getValue("robin-enhance-mode")){
  270. this.mode = GM_getValue("robin-enhance-mode");
  271. }
  272.  
  273. // init counters
  274. for(var i in this.channels){
  275. this.unread_counts[i] = 0;
  276. }
  277.  
  278. // update channel cache
  279. this.updateChannelMatchCache();
  280.  
  281. // set up el
  282. this.$el = $el;
  283.  
  284. // Create inital markup
  285. this.$el.html("<span class='all selected'>Everything</span><span><div class='robin-filters'></div></span><span class='more'>[Options]</span>");
  286. this.$opt = $("<div class='robin-channel-add' style='display:none'><input name='add-channel'><button>Add channel</button> <span class='channel-mode'>Channel Mode: <span title='View one channel at a time' data-type='single'>Single</span> | <span title='View many channels at once' data-type='multi'>Multi</span></span></div>").insertAfter(this.$el);
  287.  
  288. // Attach events
  289. this.$el.find(".robin-filters").click(this.toggle_channel);
  290. this.$el.find("span.all").click(this.disable_all_channels);
  291. this.$el.find("span.more").click(function(){ $(".robin-channel-add").slideToggle(); });
  292. this.$el.find(".robin-filters").bind("contextmenu", function(e){
  293. e.preventDefault();
  294. e.stopPropagation();
  295. var chan_id = $(e.target).data("filter");
  296. if(chan_id===null)return; // no a channel
  297. _self.removeChannel(_self.channels[chan_id]);
  298. });
  299. // Form events
  300. this.$opt.find(".channel-mode span").click(this.changeChannelMode);
  301. this.$opt.find("button").click(function(){
  302. var new_chan = _self.$opt.find("input[name='add-channel']").val();
  303. if(new_chan != '') _self.addChannel(new_chan);
  304. _self.$opt.find("input[name='add-channel']").val('');
  305. });
  306.  
  307. $("#robinSendMessage").submit(this.submit_helper);
  308. // store default room class
  309. this.defaultRoomClasses = $("#robinChatWindow").attr("class");
  310.  
  311. // redraw tabs
  312. this.drawTabs();
  313.  
  314. // start ticker
  315. setInterval(this.tick, 1000);
  316. }
  317. };
  318.  
  319. /**
  320. * Check if a message is "spam"
  321. */
  322. var is_spam = function(line){
  323. return (
  324. // Hide auto vote messages
  325. (/^voted to (grow|stay|abandon)/.test(line)) ||
  326. // random unicode?
  327. (/[\u0080-\uFFFF]/.test(line)) ||
  328. // hide any auto voter messages
  329. (/\[.*autovoter.*\]/.test(line)) ||
  330. // Common bots
  331. (/^(\[binbot\]|\[robin-grow\])/.test(line)) ||
  332. // repeating chars in line (more than 5). e.g. aaaaaaa !!!!!!!!
  333. (/(.)\1{5,}/.test(line)) ||
  334. // Some common messages
  335. (/(voting will end in approximately|\[i spam the most used phrase\]|\[message from creator\]|\[.*bot.*\])/.test(line)) ||
  336. // no spaces = spam if its longer than 25 chars (dont filter links)
  337. (line.indexOf(" ") === -1 && line.length > 25 && line.indexOf("http") === -1) ||
  338. // repeating same word
  339. /(\b\S+\b)\s+\b\1\b/i.test(line)
  340. );
  341. };
  342.  
  343. /**
  344. * Check if a message is from an ignored user
  345. *
  346. */
  347. var is_ignored = function($usr, $ele){
  348. // no user name, go looking for when said it
  349. if($usr.length === 0){
  350. while($usr.length === 0){
  351. $ele = $ele.prev();
  352. $usr = $ele.find(".robin--username");
  353. }
  354. }
  355. // are they ignored?
  356. return (ignored_users[$usr.text()]);
  357. };
  358.  
  359. /**
  360. * Make links clickable
  361. *
  362. */
  363. var auto_link = function($msg){
  364. var text = $msg.html(); // read as html so stuff stays escaped
  365. // normal links
  366. text = text.replace(/\b(?:https?|ftp):\/\/[a-z0-9-+&@#\/%?=~_|!:,.;]*[a-z0-9-+&@#\/%=~_|]/gim, '<a target="blank" href="$&">$&</a>');
  367. // reddit subreddit links
  368. text = text.replace(/ \/r\/(\w+)/gim, ' <a target="blank" href="https://reddit.com/r/$1">/r/$1</a>');
  369. // update text
  370. $msg.html(text);
  371. };
  372.  
  373. /**
  374. * Mute a user
  375. */
  376. var _mute_user = function(usr){
  377. // Add to ignore list
  378. ignored_users[usr] = true;
  379. _render_muted_list();
  380. };
  381.  
  382. /**
  383. * un-mute a user
  384. */
  385. var _unmute_user = function(usr){
  386. // Add to ignore list
  387. delete ignored_users[usr];
  388. _render_muted_list();
  389. };
  390.  
  391. // Render list of ignored users
  392. var _render_muted_list = function(){
  393. var html = "<strong>Ignored users</strong><br>";
  394. for(var u in ignored_users){
  395. html += "<div data-usr='"+ u + "'>" + u + " - [unmute]</div>";
  396. }
  397. $("#muted_users").html(html);
  398. };
  399.  
  400. // Scroll chat back to bottom
  401. var _scroll_to_bottom = function(){
  402. $("#robinChatWindow").scrollTop($("#robinChatMessageList").height());
  403. };
  404.  
  405. // create persistant option
  406. function createOption(name, click_action, default_state){
  407. var checked_markup;
  408. var key = "robin-enhance-" + name.replace(/\W/g, '');
  409. console.log(key);
  410. var state = (typeof default_state !== "undefined") ? default_state : false;
  411.  
  412. // try and state if setting is defined
  413. if(GM_getValue(key)){
  414. console.log("state loaded");
  415. state = (GM_getValue(key) === 'true') ? true : false;
  416. }
  417. // markup for state
  418. checked_markup = (state === true) ? "checked='checked'" : "";
  419. // render option
  420. var $option = $("<label><input type='checkbox' "+checked_markup+">"+name+"</label>").click(function(){
  421. var checked = $(this).find("input").is(':checked');
  422.  
  423. // persist state
  424. if(checked != state){
  425. console.log("state saved", checked);
  426. GM_setValue(key, checked ? 'true' : 'false'); // true/false stored as strings, to avoid unset matching
  427. state = checked;
  428. }
  429.  
  430. click_action(checked, $(this));
  431. });
  432. // add to dom
  433. $("#robinDesktopNotifier").append($option);
  434. // init
  435. click_action(default_state, $option)
  436. };
  437.  
  438. // update spam count
  439. var update_spam_count = function(){
  440. blocked_spam++;
  441. blocked_spam_el.innerHTML = blocked_spam;
  442. };
  443.  
  444. // when name is clicked, fill it into the chat box
  445. var fill_name = function(e){
  446. e.preventDefault();
  447. e.stopPropagation();
  448.  
  449. // if text area blank, prefill name. if not, stick it on the end
  450. if($(".text-counter-input").val() === ''){
  451. $(".text-counter-input").val($(this).text() + ' ').focus();
  452. }else{
  453. $(".text-counter-input").val($(".text-counter-input").val() + ' ' + $(this).text()).focus();
  454. }
  455. };
  456.  
  457. /**
  458. * Parse a link and apply changes
  459. */
  460. var parse_line = function($ele){
  461. var $msg = $ele.find(".robin-message--message");
  462. var $usr = $ele.find(".robin--username");
  463. var line = $msg.text().toLowerCase();
  464.  
  465. // dont parse system messages
  466. if($ele.hasClass("robin--user-class--system")){
  467. if(line.indexOf("ratelimit | you are doing that too much") !== -1){
  468. // channel key length + a space length
  469. var offset = $("#robinChatWindow").attr("data-channel-key").length + 1;
  470. $(".text-counter-input").val(user_last_message.slice(offset));
  471. }
  472. return;
  473. }
  474.  
  475. // If user is ignored or message looks like "Spam". hide it
  476. if (is_ignored($usr, $ele) || is_spam(line)) {
  477. $ele.addClass("spam-hidden");
  478. update_spam_count();
  479. }
  480.  
  481. // Highlight mentions
  482. if(line.indexOf(robin_user) !== -1){
  483. $ele.addClass("user-mention");
  484. }
  485.  
  486. // Make links clickable
  487. if(!_robin_grow_detected && line.indexOf("http") !== -1){
  488. auto_link($msg);
  489. }
  490.  
  491. // Add mute button to users
  492. if(!$ele.hasClass("robin--user-class--system") && $usr.text() != robin_user){
  493. $("<span style='font-size:.8em;cursor:pointer'> [mute] </span>").insertBefore($usr).click(function(){
  494. _mute_user($usr.text());
  495. });
  496. }
  497.  
  498. // Track channels
  499. tabbedChannels.proccessLine(line, $ele);
  500.  
  501. // bind click to use (override other click events if we can)
  502. $usr.bindFirst("click", fill_name);
  503. };
  504.  
  505.  
  506. // Detect changes, are parse the new message
  507. $("#robinChatWindow").on('DOMNodeInserted', function(e) {
  508. if ($(e.target).is('div.robin-message')) {
  509. // Apply changes to line
  510. parse_line($(e.target));
  511. }
  512. });
  513.  
  514. // When everything is ready
  515. $(document).ready(function(){
  516.  
  517. // Set default spam filter type
  518. $("#robinChatWindow").addClass("hide-spam");
  519.  
  520. createOption("Hide spam completely (<span id='spamcount'>0</span> removed)", function(checked, ele){
  521. if(checked){
  522. $("#robinChat").removeClass("mute-spam").addClass("hide-spam");
  523. }else{
  524. $("#robinChat").removeClass("hide-spam").addClass("mute-spam");
  525. }
  526. // correct scroll after spam filter change
  527. _scroll_to_bottom();
  528. },true);
  529.  
  530. createOption("Use channel colors", function(checked, ele){
  531. if(checked){
  532. $("#robinChat").addClass("show-colors");
  533. }else{
  534. $("#robinChat").removeClass("show-colors");
  535. }
  536. // correct scroll after spam filter change
  537. _scroll_to_bottom();
  538. },false);
  539.  
  540.  
  541.  
  542. blocked_spam_el = $("#spamcount")[0];
  543.  
  544. // Add Muted list & hook up unmute logic
  545. $('<div id="muted_users" class="robin-chat--sidebar-widget robin-chat--notification-widget"><strong>Ignored users</strong></div>').insertAfter($("#robinDesktopNotifier"));
  546. $('#muted_users').click(function(e){
  547. var user = $(e.target).data("usr");
  548. if(user) _unmute_user(user);
  549. });
  550.  
  551. // Init tabbed channels
  552. tabbedChannels.init($('<div id="filter_tabs"></div>').insertAfter("#robinChatWindow"));
  553.  
  554. // store i copy of last message, in case somthing goes wrong (rate limit)
  555. $("#robinSendMessage").submit(function(){
  556. user_last_message = $(".text-counter-input").val();
  557. });
  558. });
  559.  
  560. // fix by netnerd01
  561. var stylesheet = document.createElement('style');
  562. document.head.appendChild(stylesheet);
  563. stylesheet = stylesheet.sheet;
  564.  
  565. // filter for channel
  566. stylesheet.insertRule("#robinChatWindow.robin-filter div.robin-message { display:none; }", 0);
  567. stylesheet.insertRule("#robinChatWindow.robin-filter div.robin-message.robin--user-class--system { display:block; }", 0);
  568. var color;
  569. for(var c=0;c<35;c++){
  570. color = colors[(c % (colors.length))];
  571.  
  572. stylesheet.insertRule("#robinChat.show-colors #robinChatWindow div.robin-message.robin-filter-"+c+" { background: "+color+";}", 0);
  573. stylesheet.insertRule("#robinChatWindow.robin-filter.robin-filter-"+c+" div.robin-message.robin-filter-"+c+" { display:block;}", 0);
  574. }
  575.  
  576. // Styles for filter tabs
  577. stylesheet.insertRule("#filter_tabs {width:100%; display: table; table-layout: fixed; background:#d7d7d2; border-bottom:1px solid #efefed;}",0);
  578. stylesheet.insertRule("#filter_tabs > span {width:90%; display: table-cell;}",0);
  579. stylesheet.insertRule("#filter_tabs > span.all, #filter_tabs > span.more {width:60px; text-align:center; vertical-align:middle; cursor:pointer;}",0);
  580. stylesheet.insertRule("#filter_tabs > span.all.selected, #filter_tabs > span.all.selected:hover {background: #fff;}", 0);
  581. stylesheet.insertRule("#filter_tabs .robin-filters { display: table; width:100%;table-layout: fixed; '}", 0);
  582. stylesheet.insertRule("#filter_tabs .robin-filters > span { padding: 5px 2px;text-align: center; display: table-cell; cursor: pointer;width:2%; vertical-align: middle; font-size: 1.1em;}", 0);
  583. stylesheet.insertRule("#filter_tabs .robin-filters > span.selected, #filter_tabs .robin-filters > span:hover { background: #fff;}", 0);
  584. stylesheet.insertRule("#filter_tabs .robin-filters > span > span {pointer-events: none;}", 0);
  585.  
  586. stylesheet.insertRule(".robin-channel-add {padding:5px; display:none;}", 0);
  587. stylesheet.insertRule(".robin-channel-add input {padding: 2.5px; }", 0);
  588. stylesheet.insertRule(".robin-channel-add .channel-mode {float:right; font-size:1.2em;padding:5px;}", 0);
  589. stylesheet.insertRule(".robin-channel-add .channel-mode span {cursor:pointer}", 0);
  590. //mentions should show even in filter view
  591. stylesheet.insertRule("#robinChat #robinChatWindow div.robin-message.user-mention { display:block; font-weight:bold; }", 0);
  592.  
  593. // Add initial styles for "spam" messages
  594. stylesheet.insertRule("#robinChat.hide-spam #robinChatWindow div.robin-message.spam-hidden { display:none; }", 0);
  595. stylesheet.insertRule("#robinChat.mute-spam #robinChatWindow div.robin-message.spam-hidden { opacity:0.3; font-size:1.2em; }", 0);
  596. stylesheet.insertRule("#robinChat.show-colors #robinChatWindow div.robin-message.spam-hidden { opacity:0.3; font-size:1.2em; }", 0);
  597. // muted user box
  598. stylesheet.insertRule("#muted_users { font-size:1.2em; }", 0);
  599. stylesheet.insertRule("#muted_users div { padding: 2px 0; }", 0);
  600. stylesheet.insertRule("#muted_users strong { font-weight:bold; }", 0);
  601.  
  602. // FIX RES nightmode (ish) [ by Kei ]
  603. stylesheet.insertRule(".res-nightmode #robinChatWindow div.robin-message { color: #ccc; }", 0);
  604. stylesheet.insertRule(".res-nightmode .robin-chat--sidebar-widget { background: #222; color: #ccc;}", 0);
  605. stylesheet.insertRule(".res-nightmode .robin-room-participant { background: #222; color: #999;}", 0);
  606. stylesheet.insertRule(".res-nightmode #filter_tabs {background: rgb(51, 51, 51);}", 0);
  607. stylesheet.insertRule(".res-nightmode #filter_tabs .robin-filters > span.selected,.res-nightmode #filter_tabs .robin-filters > span:hover,.res-nightmode #filter_tabs > span.all.selected,.res-nightmode #filter_tabs > span.all:hover {background: rgb(34, 34, 34)}", 0);
  608. stylesheet.insertRule(".res-nightmode .robin-chat--input { background: #222 }", 0);
  609. stylesheet.insertRule(".res-nightmode .robin--presence-class--away .robin--username {color: #999;}", 0);
  610. stylesheet.insertRule(".res-nightmode .robin--presence-class--present .robin--username {color: #ccc;}", 0);
  611. stylesheet.insertRule(".res-nightmode #robinChat .robin--user-class--self .robin--username { color: #999; }", 0);
  612. stylesheet.insertRule(".res-nightmode .robin-chat--vote { background: #777; color: #ccc;}", 0);
  613. stylesheet.insertRule(".res-nightmode .robin-chat--buttons button.robin-chat--vote.robin--active { background: #ccc; color:#999; }", 0);
  614.  
  615. $(document).ready(function(){
  616. setTimeout(function(){
  617. // Play nice with robin grow (makes room for tab bar we insert)
  618. if($(".usercount.robin-chat--vote").length !== 0){
  619. _robin_grow_detected = true;
  620. stylesheet.insertRule("#robinChat.robin-chat .robin-chat--body { height: calc(100vh - 150px); }", 0);
  621. }
  622. },500);
  623. });
  624.  
  625. // Allow me to sneek functions in front of other libaries - used when working with robin grow >.< sorry guys
  626. //http://stackoverflow.com/questions/2360655/jquery-event-handlers-always-execute-in-order-they-were-bound-any-way-around-t
  627. $.fn.bindFirst = function(name, fn) {
  628. // bind as you normally would
  629. // don't want to miss out on any jQuery magic
  630. this.on(name, fn);
  631.  
  632. // Thanks to a comment by @Martin, adding support for
  633. // namespaced events too.
  634. this.each(function() {
  635. var handlers = $._data(this, 'events')[name.split('.')[0]];
  636. // take out the handler we just inserted from the end
  637. var handler = handlers.pop();
  638. // move it at the beginning
  639. handlers.splice(0, 0, handler);
  640. });
  641. };
  642.  
  643. })();