Inverter

Inverts webpages with a hotkey

  1. // ==UserScript==
  2. // @name Inverter
  3. // @icon http://i.imgur.com/wBrRGXc.png
  4. // @namespace skoshy.com
  5. // @version 0.2.41
  6. // @description Inverts webpages with a hotkey
  7. // @author Stefan Koshy
  8. // @run-at document-start
  9. // @match *://*/*
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_deleteValue
  13. // ==/UserScript==
  14.  
  15. var DEBUG_MODE = false;
  16. var SCRIPT_ID = 'inverter';
  17. var CURRENT_SITE = getCurrentSite();
  18.  
  19. // From https://gist.github.com/arantius/3123124
  20. // These are replacement functions for GreaseMonkey scripts, but the only work on a single domain instead of being cross domain
  21. // Todo: Implement solution that works cross domain
  22.  
  23. if (typeof GM_getValue == 'undefined') {
  24. function GM_getValue(aKey, aDefault) {
  25. 'use strict';
  26. let val = localStorage.getItem(SCRIPT_ID + aKey)
  27. if (null === val && 'undefined' != typeof aDefault) return aDefault;
  28. return val;
  29. }
  30. }
  31.  
  32. if (typeof GM_setValue == 'undefined') {
  33. function GM_setValue(aKey, aVal) {
  34. 'use strict';
  35. localStorage.setItem(SCRIPT_ID + aKey, aVal);
  36. }
  37. }
  38.  
  39. var timers = {};
  40. timers.lastToggle = 0; // default the last toggle to nothing
  41.  
  42. var options = {};
  43. options.toggleDelayMs = 200; // number of milliseconds delay there needs to be to switch from toggle mode to save inversion mode
  44.  
  45. var css = {};
  46. css.defaults = {};
  47. css.overrides = {};
  48.  
  49. css.common = {};
  50. css.common.css = `
  51. html {
  52. filter: invert(1);
  53. min-height: 100%;
  54. background-color: black;
  55. }
  56.  
  57. img, figure, video, picture {
  58. filter: invert(1);
  59. }
  60.  
  61. figure img { /* no need to invert imgs in figures */
  62. filter: invert(0);
  63. }
  64.  
  65. *[style*="url(/"],
  66. *[style*="blob:"],
  67. *[style*="url('https:"],
  68. *[style*="url('http:"],
  69. *[style*="url('://"],
  70. *[style*="url('blob:"],
  71. *[style*='url("https:'],
  72. *[style*='url("https:'],
  73. *[style*='url("http:'],
  74. *[style*='url("://'],
  75. *[style*='url("blob:']
  76. { filter: invert(1); }
  77.  
  78. body, body > div {
  79. background-color: white;
  80. }
  81.  
  82. iframe[src*="youtube.com"], iframe[src*="vimeo.com"] {
  83. filter: invert(1);
  84. }
  85.  
  86. twitterwidget::shadow .MediaCard-media {
  87. filter: invert(1);
  88. }
  89.  
  90. twitterwidget::shadow .Avatar {
  91. filter: invert(1);
  92. }
  93.  
  94. .gbii /* This is for multiple Google Domains to reinvert the profile icon in the top right */
  95. {filter: invert(1);}
  96. `;
  97. css.messenger = {};
  98. css.messenger.includeCommon = false;
  99. css.messenger.javascriptOnce = function() {
  100. var chatColor = '';
  101.  
  102. setInterval(function() {
  103. var chatColorEl = document.querySelector('._fl2 [data-testid="info_panel_button"] svg polygon, ._fl2 [data-testid="info_panel_button"] svg path'); /* Two elements, depends on if the info button is pressed or not */
  104. if (!chatColorEl || chatColorEl.style.fill == chatColor) return;
  105.  
  106. chatColor = chatColorEl.style.fill;
  107.  
  108. var newCss = `
  109. ._43by
  110. { background: `+chatColor.replace('rgb', 'rgba').replace(')', ', .3)')+` !important; }
  111. `;
  112.  
  113. addGlobalStyle(newCss, SCRIPT_ID+'-css', isInverterEnabled(), SCRIPT_ID+'-messengerSpecialCss');
  114. }, 500);
  115. };
  116. css.messenger.css = `
  117. body
  118. ,._5i_d * /* Text from YouTube/video share */
  119. { color: white; }
  120.  
  121. /* Bug Fixes */
  122. ._kmc /* message box, min height fix */
  123. { min-height: 26px !important; }
  124.  
  125. ._4sp8 {
  126. background: black !important;
  127. }
  128.  
  129. ._4rv3 /* Message box container */
  130. ,._5743 * /* Top name */
  131. ,._1ht6 /* Names in the sidebar */
  132. ,._1ht3 ._1htf /* Unread message in sidebar */
  133. ,._1tqi ._4qba /* App Name */
  134. ,._5swm * /* Text from link shares */
  135. ,._hh7 a /* Link from received message */
  136. ,._55r /* video chat notices */
  137. ,._1enh ._364g /* from searching, contact names */
  138. ,._225b /* from searching, header */
  139. ,._lll * /* Intro message text for first time chatting someone */
  140. ,._3szq /* Info Box text */
  141. ,._58ak input /* Composing new message text box */
  142. ,._1jt6 ._3oh- /* Info box contact name */
  143. ,._4rph ._4rpj /* Info box group chat, add people */
  144. ,._4_j5 ._364g /* Info box group chat, additional people */
  145. ,._2jnv /* Info box group chat, chat name */
  146. { background: transparent !important; color: white !important; }
  147.  
  148. ._1ht3._1ht1 /* unread message in sidebar */
  149. { background-color: rgba(0, 132, 255, .5) !important; }
  150.  
  151. ._1ht1._1ht2 /* current message in the sidebar */
  152. { background-color: rgba(255, 255, 255, .15) !important; }
  153.  
  154. ._2y8_ * /* Popup boxes text */
  155. { color: black; }
  156.  
  157. ._29_7 ._hh7 /* receiving message boxes */
  158. { background: rgba(255,255,255,.12) !important; color: white; }
  159.  
  160. ._43by /* sent message boxes */
  161. { background: rgba(0, 132, 255,.45) !important; transition: background 0.3s ease; }
  162.  
  163. ._497p /* Timestamps and joining video chat messages in the chat */
  164. ,._1ht7 /* timestamps in sidebar */
  165. ,._ih3 /* Names in group chat */
  166. ,._5iwm ._58al::placeholder /* placeholder text for search */
  167. ,._1lj0 /* Info Box Headers */
  168. ,._3eus, ._5uh /* Info Box Person Description */
  169. ,._2y8z /* Composing new message "To" field" */
  170. ,._58ak input::placeholder /* Composing new message text box, placeholder */
  171. ,._5i_d .__6m /* Link share URL */
  172. ,._3x6v /* Info box, side text next to notifications if notifications are muted */
  173. { color: rgba(255,255,255,.6) !important; }
  174.  
  175. ._29_7 ._52mr /* Borders of message bubble stuff, like video chat notifications, links, etc, received */
  176. ,._nd_ ._52mr /* Same as above, but sent */
  177. { border-color: rgba(255,255,255,.2); }
  178.  
  179. .sp_1g5zN81j-1P /* tiny icons, like when a video chat ends */
  180. ,._5iwm ._58ak::before /* search icon */
  181. ,.sp_6SI1R2TSgtb /* more tiny icons */
  182. ,._2t5t /* more icons, like the forward icon */
  183. { filter: invert(1); }
  184.  
  185. a._4ce_ /* games icon */
  186. ,._4rv6 /* stickers icon */
  187. { opacity: .7 !important; filter: invert(1) !important; }
  188.  
  189. ._5iwm ._58al /* search box */
  190. { background: rgba(255,255,255,.3) !important; color: white !important; }
  191.  
  192. ::-webkit-scrollbar-track
  193. {
  194. -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3) !important;
  195. border-radius: 10px !important;
  196. background-color: #333 !important;
  197. }
  198.  
  199. ::-webkit-scrollbar
  200. {
  201. width: 12px !important;
  202. background-color: transparent !important;
  203. }
  204.  
  205. ::-webkit-scrollbar-thumb
  206. {
  207. border-radius: 10px !important;
  208. -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3) !important;
  209. background-color: #555 !important;
  210. }
  211. `;
  212. css.gmail = {};
  213. css.gmail.css = `
  214. iframe[src*="hangouts.google.com"]
  215. { filter: invert(1); }
  216. `;
  217. css.inbox = {};
  218. css.inbox.css = `
  219. img[src*="ssl.gstatic.com"], img[src*="www.gstatic.com"]
  220. { filter: invert(0); }
  221. nav#GH > .b4 /* Header bar, kepp the same color */
  222. { filter: invert(1); }
  223. iframe[src*="hangouts.google.com"]
  224. { filter: invert(1); }
  225. .cf /* Shipping notification headers */
  226. { filter: invert(1); }
  227. .xW /* Shipping icon */
  228. { filter: invert(1) !important; }
  229. `;
  230. css.hangouts = {};
  231. css.hangouts.enableInIframe = true;
  232. css.hangouts.css = `
  233. .Ik:not(.uB) /* Chat Headers */
  234. { filter: invert(1); }
  235. `;
  236. css.youtube = {};
  237. css.youtube.css = `
  238. .player-api video
  239. {filter: inherit;}
  240.  
  241. #theater-background
  242. {background: white !important;}
  243.  
  244. #player-playlist,
  245. .player-api,
  246. #c4-header-bg-container /* Banner on YouTube channel page */
  247. {filter: invert(1);}
  248.  
  249. #player-playlist img,
  250. #c4-header-bg-container img /* Banner on YouTube channel page */
  251. {filter: invert(0);}
  252.  
  253. `;
  254. css.facebook = {};
  255. css.facebook.css = `
  256. i /* Emoji and other small icons */
  257. {filter: invert(1);}
  258. `;
  259. css.twitter = {};
  260. css.twitter.css = `
  261. .PermalinkOverlay-with-background /* overlay when clicking on a tweet */
  262. {background: rgba(255,255,255,.55) !important;}
  263.  
  264. iframe
  265. {background-color: white; filter: invert(1);}
  266.  
  267. .is-generic-video {
  268. filter: invert(1);
  269. }
  270. `;
  271. css.soundcloud = {};
  272. css.soundcloud.css = `
  273. span[style]
  274. `;
  275. css.pocketcasts = {};
  276. css.pocketcasts.css = `
  277. #header {filter: invert(1);}
  278. #audio_player {filter: invert(1); background-color: black;}
  279.  
  280. #header img {filter: invert(0);}
  281. #audio_player img {filter: invert(0);}
  282.  
  283. #audio_player #audio_player_wrapper .player_top,
  284. #audio_player #audio_player_wrapper .player_bottom
  285. {background-color: transparent;}
  286. `;
  287. css.slack = {};
  288. css.slack.includeCommon = false;
  289. css.slack.css = `
  290. body #loading_welcome,
  291. .loading #loading-zone,
  292. #loading_message p,
  293. body #client_body,
  294. body .end_div_msg_lim,
  295. #client_body h1,
  296. #client_body h2,
  297. #client_body h3,
  298. #client_body h4,
  299. body #footer,
  300. body #msg_input,
  301. #msgs_overlay_div,
  302. body .file_container:not(.snippet_container),
  303. #messages_container.has_top_messages_banner:before, /* The background of the X New Message since blah thing in the message window */
  304.  
  305. /* Flexible column, aka threaded messages */
  306. body #col_flex,
  307. #flex_contents .heading,
  308.  
  309. /* About channel column */
  310. body #details_tab .channel_page_section,
  311. body #details_tab .channel_page_section div,
  312. #details_tab .channel_page_section .section_title,
  313. .c-channel_insights__message ts-message.standalone:not(.for_mention_display):not(.for_search_display):not(.for_top_results_search_display):not(.for_star_display),
  314. .c-channel_insights__date_heading span,
  315.  
  316. /* All threads view */
  317. #threads_msgs_scroller_div,
  318. body ts-thread,
  319. body ts-thread .thread_messages,
  320. #convo_container .convo_flexpane_divider .reply_count, #file_preview_scroller .convo_flexpane_divider .reply_count,
  321. body .reply_input_container .ql-container,
  322.  
  323. /* Top Header */
  324. body #client_header,
  325. body .channel_header,
  326. body .ql-container.texty_single_line_input .ql-editor, /* Search input */
  327. .feature_name_tagging_client #edit_topic_inner:before, /* Topic box */
  328. .day_divider .day_divider_label, /* Day divider */
  329. #client_body:not(.onboarding):before, /* Top day divider */
  330.  
  331. /* New Direct Message modal */
  332. #fs_modal.active,
  333. .plastic_select, input[type=url], input[type=text], input[type=tel], input[type=number], input[type=email], input[type=password], select, textarea,
  334.  
  335. /* Unreads Tab */
  336. body .unread_msgs_loading,
  337.  
  338. /* Image viewer */
  339. .fs_modal_file_viewer_content .viewer
  340. { background: rgb(27, 27, 27) !important; color: white !important; -webkit-text-stroke-width: 1px; -webkit-text-stroke-color: rgba(101, 101, 101, 0.1); }
  341.  
  342. .sli_briefing, /* New Unreads Banner */
  343. #msgs_div .unread_divider.no_unreads .divider_label /* New Message line indicator text */
  344. { background: rgb(27, 27, 27) !important; }
  345.  
  346. body .channel_title .channel_name,
  347. body #archives_end_div_msg_lim h1,
  348. body #end_display_msg_lim h1,
  349. body ts-message,
  350. body #client_body ts-message .message_content .message_sender,
  351. body .c-message__sender,
  352. body .c-message--light .c-message__sender .c-message__sender_link,
  353. body .c-message__body,
  354. body ts-thread .thread_header .thread_channel_name a,
  355. .c-member__display-name, .c-team__display-name, .c-usergroup__handle,
  356.  
  357. /* Unread Tab */
  358. body .unread_empty_state,
  359.  
  360. /* Convo Tab */
  361. body #convo_tab .message_input, body #convo_tab textarea#msg_text,
  362.  
  363. /* New Direct Message Modal */
  364. #fs_modal h1, #fs_modal h2, #fs_modal h3, #fs_modal h4, #fs_modal h5,
  365. .c-member__secondary-name--medium,
  366. #im_browser .im_browser_row.multiparty,
  367. body .flexpane_file_title .member, body .flexpane_file_title .service_link, .flexpane_file_title .title a, /* files pane */
  368. body .comment .member, .comment_body, /* file comments */
  369. #details_tab .feature_sli_channel_insights .channel_created_section .creator_link, #details_tab .feature_sli_channel_insights .channel_purpose_section .channel_purpose_text
  370. { color: white !important; -webkit-text-stroke-width: 1px; -webkit-text-stroke-color: rgba(101, 101, 101, 0.1); }
  371.  
  372. /* Hover Message */
  373. ts-message.active:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
  374. ts-message.message--focus:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
  375. ts-message:hover:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
  376. #msg_input+#primary_file_button:not(:hover):not(.active),
  377. ts-message .reply_bar:hover, /* Hovering over the reply bar in a message */
  378. ts-message.selected:not(.delete_mode),
  379. ts-thread ts-message.new_reply, /* when a thread has a new message */
  380. #im_browser #im_list_container:not(.keyboard_active).not_scrolling .im_browser_row:not(.disabled_dm):hover, #im_browser .im_browser_row.highlighted, /* New direct message modal, hovering over row */
  381.  
  382. /* Unreads Tab */
  383. body .unread_group_header, body .unread_group_header_name a,
  384.  
  385. .msg_inline_attachment_column.column_border
  386. { background: rgb(40, 40, 40) !important; color: white !important; }
  387.  
  388. /* Hover - Border */
  389. #client_body:not(.onboarding):before, /* Top day divider */
  390. #convo_container .convo_flexpane_divider,
  391. #file_preview_scroller .convo_flexpane_divider,
  392. #flex_contents .heading,
  393. .flex_pane_showing #col_flex,
  394. .day_container .day_msgs,
  395. .c-channel_insights__message ts-message.standalone:not(.for_mention_display):not(.for_search_display):not(.for_top_results_search_display):not(.for_star_display)
  396. { border-color: rgb(40, 40, 40) !important; }
  397.  
  398. /* Placeholder text */
  399. body .ql-placeholder
  400. { color: rgb(200,200,200); }
  401.  
  402. /* Override text with black text */
  403. .feature_name_tagging_client ts-message .mention, /* channel mention */
  404. body .comment .mention, body .ql-editor .mention, body ts-message .mention /* more mentions */
  405. { color: black !important; }
  406.  
  407. /* Remove the white border between channel switcher and messages */
  408. body #col_messages,
  409. #client_body:not(.onboarding):before,
  410. .channel_header,
  411. #footer,
  412. body ts-message.active:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
  413. body ts-message.message--focus:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
  414. body ts-message:hover:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply)
  415. { box-shadow: none !important; }
  416.  
  417. /* Links */
  418. ts-message .message_body a,
  419. ts-message .message_body a:link,
  420. ts-message .message_body a:visited,
  421. ts-message .message_body .file_preview_link
  422. { color: #66baec !important; }
  423.  
  424. /* Links - Hover */
  425. ts-message .message_body a:hover,
  426. ts-message .message_body a:link:hover,
  427. ts-message .message_body a:visited:hover,
  428. ts-message .message_body .file_preview_link:hover
  429. { color: #569eca !important; }
  430. `;
  431. css.zohonotebook = {};
  432. css.zohonotebook.css = `
  433. #wholeContainer,
  434. .versionsContainer
  435. { background: rgb(27, 27, 27) !important; color: white !important; }
  436.  
  437. .noteCardDiv:before
  438. { border-color: rgb(27, 27, 27) !important; }
  439.  
  440. .navBarIcon
  441. { filter: invert(1); }
  442.  
  443. #createWrapper
  444. { background: transparent !important; }
  445. `;
  446. css.zohonotebook.includeCommon = false;
  447. css.none = {};
  448. css.none.css = ``;
  449.  
  450.  
  451. function addGlobalStyle(css, className, enabled, id) {
  452. var head, style;
  453. head = document.getElementsByTagName('head')[0];
  454. if (!head) { return; }
  455.  
  456. // check to see if this element already exists, if it does override it
  457. var oldEl = document.getElementById(id);
  458.  
  459. style = document.createElement('style');
  460. style.type = 'text/css';
  461. style.innerHTML = css;
  462. style.id = id;
  463. style.className = className;
  464. head.appendChild(style);
  465. style.disabled = !enabled;
  466.  
  467. // delete old element if it exists
  468. if (oldEl) {
  469. oldEl.parentNode.removeChild(oldEl);
  470. }
  471. }
  472.  
  473. function parseCSS(parsed) {
  474. for (attribute in css.defaults) {
  475. exceptionToReplace = new RegExp('{{'+attribute+'}}', 'g');
  476. parsed = parsed.replace(exceptionToReplace, css['defaults'][attribute]);
  477. }
  478.  
  479. return parsed;
  480. }
  481.  
  482. document.addEventListener("keydown", function(e) {
  483. if (e.altKey === true && e.shiftKey === false && e.ctrlKey === true && e.metaKey === false && e.code == 'KeyI') {
  484. var timestamp = new Date();
  485. timestamp = timestamp.getTime();
  486.  
  487. if (timers.lastToggle > timestamp-options.toggleDelayMs) {
  488. if (isInverterEnabled()) {
  489. GM_setValue('enabled_'+document.domain, true);
  490. alert('Saved inversion for '+document.domain);
  491. } else {
  492. GM_deleteValue('enabled_'+document.domain);
  493. alert('Deleted inversion setting for '+document.domain);
  494. }
  495. } else {
  496. // toggle style
  497.  
  498. if (isInverterEnabled()) {
  499. disableStyle();
  500. } else {
  501. enableStyle();
  502. }
  503. }
  504.  
  505. timers.lastToggle = timestamp;
  506. }
  507. });
  508.  
  509. function getCssStyleElements() {
  510. return document.getElementsByClassName(SCRIPT_ID+'-css');
  511. }
  512.  
  513. function enableStyle() {
  514. var cssToInclude = '';
  515.  
  516. if (css[CURRENT_SITE].includeCommon === false) {}
  517. else { cssToInclude += css.common.css; }
  518.  
  519. cssToInclude += css[CURRENT_SITE].css;
  520.  
  521. addGlobalStyle(parseCSS(
  522. cssToInclude
  523. ), SCRIPT_ID+'-css', true, SCRIPT_ID+'-css');
  524. }
  525.  
  526. function disableStyle() {
  527. var cssEls = getCssStyleElements();
  528. for (let i = 0; i < cssEls.length; i++) {
  529. cssEls[i].parentNode.removeChild(cssEls[i]); // remove the element
  530. }
  531. }
  532.  
  533. function isInverterEnabled() {
  534. var cssEl = document.getElementById(SCRIPT_ID+'-css');
  535.  
  536. return isTruthy(cssEl);
  537. }
  538.  
  539. function getCurrentSite() {
  540. var url = document.documentURI;
  541. var toReturn = 'none';
  542.  
  543. if (url.indexOf('messenger.com') != -1) toReturn = 'messenger';
  544. if (url.indexOf('youtube.com') != -1) toReturn = 'youtube';
  545. if (url.indexOf('twitter.com') != -1) toReturn = 'twitter';
  546. if (url.indexOf('inbox.google.com') != -1) toReturn = 'inbox';
  547. if (url.indexOf('hangouts.google.com') != -1) toReturn = 'hangouts';
  548. if (url.indexOf('mail.google.com') != -1) toReturn = 'gmail';
  549. if (url.indexOf('facebook.com') != -1) toReturn = 'facebook';
  550. if (url.indexOf('play.pocketcasts.com') != -1) toReturn = 'pocketcasts';
  551. if (url.indexOf('notebook.zoho.com') != -1) toReturn = 'zohonotebook';
  552. if (url.indexOf('slack.com/messages') != -1 || url.indexOf('slack.com/threads') != -1 || url.indexOf('slack.com/unreads') != -1) toReturn = 'slack';
  553.  
  554. return toReturn;
  555. }
  556.  
  557. function init() {
  558. var styleEnabled = GM_getValue( 'enabled_'+document.domain , false );
  559.  
  560. if (DEBUG_MODE) {
  561. console.log('Inversion Enabled for site ('+CURRENT_SITE+'): '+styleEnabled);
  562. }
  563.  
  564. if (inIframe() && isFalsy(css[CURRENT_SITE].enableInIframe)) { styleEnabled = false; }
  565.  
  566. if (css[CURRENT_SITE].javascriptOnce) { css[CURRENT_SITE].javascriptOnce(); }
  567.  
  568. if (styleEnabled) {
  569. enableStyle();
  570. }
  571. }
  572.  
  573. init();
  574.  
  575. /*
  576. * Utility functions
  577. */
  578.  
  579. function isTruthy(item) {
  580. return !isFalsy(item);
  581. }
  582.  
  583. // from https://gist.github.com/skoshy/69a7951b3070c2e2496d8257e16d7981
  584. function isFalsy(item) {
  585. if (
  586. !item
  587. || (typeof item == "object" && (
  588. Object.keys(item).length == 0 // for empty objects, like {}, []
  589. && !(typeof item.addEventListener == "function") // omit webpage elements
  590. ))
  591. )
  592. return true;
  593. else
  594. return false;
  595. }
  596.  
  597. function addEvent(obj, evt, fn) {
  598. if (obj.addEventListener) {
  599. obj.addEventListener(evt, fn, false);
  600. }
  601. else if (obj.attachEvent) {
  602. obj.attachEvent("on" + evt, fn);
  603. }
  604. }
  605.  
  606. function inIframe () {
  607. try {
  608. return window.self !== window.top;
  609. } catch (e) {
  610. return true;
  611. }
  612. }