Inverter

Inverts webpages with a hotkey

目前为 2017-08-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Inverter
  3. // @icon http://i.imgur.com/wBrRGXc.png
  4. // @namespace skoshy.com
  5. // @version 0.2.31
  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.none = {};
  288. css.none.css = ``;
  289.  
  290.  
  291. function addGlobalStyle(css, className, enabled, id) {
  292. var head, style;
  293. head = document.getElementsByTagName('head')[0];
  294. if (!head) { return; }
  295.  
  296. // check to see if this element already exists, if it does override it
  297. var oldEl = document.getElementById(id);
  298.  
  299. style = document.createElement('style');
  300. style.type = 'text/css';
  301. style.innerHTML = css;
  302. style.id = id;
  303. style.className = className;
  304. head.appendChild(style);
  305. style.disabled = !enabled;
  306.  
  307. // delete old element if it exists
  308. if (oldEl) {
  309. oldEl.parentNode.removeChild(oldEl);
  310. }
  311. }
  312.  
  313. function parseCSS(parsed) {
  314. for (attribute in css.defaults) {
  315. exceptionToReplace = new RegExp('{{'+attribute+'}}', 'g');
  316. parsed = parsed.replace(exceptionToReplace, css['defaults'][attribute]);
  317. }
  318.  
  319. return parsed;
  320. }
  321.  
  322. document.addEventListener("keydown", function(e) {
  323. if (e.altKey === true && e.shiftKey === false && e.ctrlKey === true && e.metaKey === false && e.code == 'KeyI') {
  324. var timestamp = new Date();
  325. timestamp = timestamp.getTime();
  326.  
  327. if (timers.lastToggle > timestamp-options.toggleDelayMs) {
  328. if (isInverterEnabled()) {
  329. GM_setValue('enabled_'+document.domain, true);
  330. alert('Saved inversion for '+document.domain);
  331. } else {
  332. GM_deleteValue('enabled_'+document.domain);
  333. alert('Deleted inversion setting for '+document.domain);
  334. }
  335. } else {
  336. // toggle style
  337.  
  338. if (isInverterEnabled()) {
  339. disableStyle();
  340. } else {
  341. enableStyle();
  342. }
  343. }
  344.  
  345. timers.lastToggle = timestamp;
  346. }
  347. });
  348.  
  349. function getCssStyleElements() {
  350. return document.getElementsByClassName(SCRIPT_ID+'-css');
  351. }
  352.  
  353. function enableStyle() {
  354. var cssToInclude = '';
  355.  
  356. if (css[CURRENT_SITE].includeCommon === false) {}
  357. else { cssToInclude += css.common.css; }
  358.  
  359. cssToInclude += css[CURRENT_SITE].css;
  360.  
  361. addGlobalStyle(parseCSS(
  362. cssToInclude
  363. ), SCRIPT_ID+'-css', true, SCRIPT_ID+'-css');
  364. }
  365.  
  366. function disableStyle() {
  367. var cssEls = getCssStyleElements();
  368. for (let i = 0; i < cssEls.length; i++) {
  369. cssEls[i].parentNode.removeChild(cssEls[i]); // remove the element
  370. }
  371. }
  372.  
  373. function isInverterEnabled() {
  374. var cssEl = document.getElementById(SCRIPT_ID+'-css');
  375.  
  376. return isTruthy(cssEl);
  377. }
  378.  
  379. function getCurrentSite() {
  380. var url = document.documentURI;
  381. var toReturn = 'none';
  382.  
  383. if (url.indexOf('messenger.com') != -1) toReturn = 'messenger';
  384. if (url.indexOf('youtube.com') != -1) toReturn = 'youtube';
  385. if (url.indexOf('twitter.com') != -1) toReturn = 'twitter';
  386. if (url.indexOf('inbox.google.com') != -1) toReturn = 'inbox';
  387. if (url.indexOf('hangouts.google.com') != -1) toReturn = 'hangouts';
  388. if (url.indexOf('mail.google.com') != -1) toReturn = 'gmail';
  389. if (url.indexOf('facebook.com') != -1) toReturn = 'facebook';
  390. if (url.indexOf('play.pocketcasts.com') != -1) toReturn = 'pocketcasts';
  391.  
  392. return toReturn;
  393. }
  394.  
  395. function init() {
  396. var styleEnabled = GM_getValue( 'enabled_'+document.domain , false );
  397.  
  398. if (DEBUG_MODE) {
  399. console.log('Inversion Enabled for site ('+CURRENT_SITE+'): '+styleEnabled);
  400. }
  401.  
  402. if (inIframe() && isFalsy(css[CURRENT_SITE].enableInIframe)) { styleEnabled = false; }
  403.  
  404. if (css[CURRENT_SITE].javascriptOnce) { css[CURRENT_SITE].javascriptOnce(); }
  405.  
  406. if (styleEnabled) {
  407. enableStyle();
  408. }
  409. }
  410.  
  411. init();
  412.  
  413. /*
  414. * Utility functions
  415. */
  416.  
  417. function isTruthy(item) {
  418. return !isFalsy(item);
  419. }
  420.  
  421. // from https://gist.github.com/skoshy/69a7951b3070c2e2496d8257e16d7981
  422. function isFalsy(item) {
  423. if (
  424. !item
  425. || (typeof item == "object" && (
  426. Object.keys(item).length == 0 // for empty objects, like {}, []
  427. && !(typeof item.addEventListener == "function") // omit webpage elements
  428. ))
  429. )
  430. return true;
  431. else
  432. return false;
  433. }
  434.  
  435. function addEvent(obj, evt, fn) {
  436. if (obj.addEventListener) {
  437. obj.addEventListener(evt, fn, false);
  438. }
  439. else if (obj.attachEvent) {
  440. obj.attachEvent("on" + evt, fn);
  441. }
  442. }
  443.  
  444. function inIframe () {
  445. try {
  446. return window.self !== window.top;
  447. } catch (e) {
  448. return true;
  449. }
  450. }