Inverter

Inverts webpages with a hotkey

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

  1. // ==UserScript==
  2. // @name Inverter
  3. // @icon http://i.imgur.com/wBrRGXc.png
  4. // @namespace skoshy.com
  5. // @version 0.2.30
  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. ._2y8_ * /* Popup boxes text */
  149. { color: black; }
  150.  
  151. ._29_7 ._hh7 /* receiving message boxes */
  152. { background: rgba(255,255,255,.12) !important; color: white; }
  153.  
  154. ._43by /* sent message boxes */
  155. { background: rgba(0, 132, 255,.45) !important; transition: background 0.3s ease; }
  156.  
  157. ._497p /* Timestamps and joining video chat messages in the chat */
  158. ,._1ht7 /* timestamps in sidebar */
  159. ,._ih3 /* Names in group chat */
  160. ,._5iwm ._58al::placeholder /* placeholder text for search */
  161. ,._1lj0 /* Info Box Headers */
  162. ,._3eus, ._5uh /* Info Box Person Description */
  163. ,._2y8z /* Composing new message "To" field" */
  164. ,._58ak input::placeholder /* Composing new message text box, placeholder */
  165. ,._5i_d .__6m /* Link share URL */
  166. ,._3x6v /* Info box, side text next to notifications if notifications are muted */
  167. { color: rgba(255,255,255,.6) !important; }
  168.  
  169. ._29_7 ._52mr /* Borders of message bubble stuff, like video chat notifications, links, etc, received */
  170. ,._nd_ ._52mr /* Same as above, but sent */
  171. { border-color: rgba(255,255,255,.2); }
  172.  
  173. .sp_1g5zN81j-1P /* tiny icons, like when a video chat ends */
  174. ,._5iwm ._58ak::before /* search icon */
  175. ,.sp_6SI1R2TSgtb /* more tiny icons */
  176. ,._2t5t /* more icons, like the forward icon */
  177. { filter: invert(1); }
  178.  
  179. a._4ce_ /* games icon */
  180. ,._4rv6 /* stickers icon */
  181. { opacity: .7 !important; filter: invert(1) !important; }
  182.  
  183. ._5iwm ._58al /* search box */
  184. { background: rgba(255,255,255,.3) !important; color: white !important; }
  185.  
  186. ::-webkit-scrollbar-track
  187. {
  188. -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3) !important;
  189. border-radius: 10px !important;
  190. background-color: #333 !important;
  191. }
  192.  
  193. ::-webkit-scrollbar
  194. {
  195. width: 12px !important;
  196. background-color: transparent !important;
  197. }
  198.  
  199. ::-webkit-scrollbar-thumb
  200. {
  201. border-radius: 10px !important;
  202. -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3) !important;
  203. background-color: #555 !important;
  204. }
  205. `;
  206. css.gmail = {};
  207. css.gmail.css = `
  208. iframe[src*="hangouts.google.com"]
  209. { filter: invert(1); }
  210. `;
  211. css.inbox = {};
  212. css.inbox.css = `
  213. img[src*="ssl.gstatic.com"], img[src*="www.gstatic.com"]
  214. { filter: invert(0); }
  215. nav#GH > .b4 /* Header bar, kepp the same color */
  216. { filter: invert(1); }
  217. iframe[src*="hangouts.google.com"]
  218. { filter: invert(1); }
  219. .cf /* Shipping notification headers */
  220. { filter: invert(1); }
  221. .xW /* Shipping icon */
  222. { filter: invert(1) !important; }
  223. `;
  224. css.hangouts = {};
  225. css.hangouts.enableInIframe = true;
  226. css.hangouts.css = `
  227. .Ik:not(.uB) /* Chat Headers */
  228. { filter: invert(1); }
  229. `;
  230. css.youtube = {};
  231. css.youtube.css = `
  232. .player-api video
  233. {filter: inherit;}
  234.  
  235. #theater-background
  236. {background: white !important;}
  237.  
  238. #player-playlist,
  239. .player-api,
  240. #c4-header-bg-container /* Banner on YouTube channel page */
  241. {filter: invert(1);}
  242.  
  243. #player-playlist img,
  244. #c4-header-bg-container img /* Banner on YouTube channel page */
  245. {filter: invert(0);}
  246.  
  247. `;
  248. css.facebook = {};
  249. css.facebook.css = `
  250. i /* Emoji and other small icons */
  251. {filter: invert(1);}
  252. `;
  253. css.twitter = {};
  254. css.twitter.css = `
  255. .PermalinkOverlay-with-background /* overlay when clicking on a tweet */
  256. {background: rgba(255,255,255,.55) !important;}
  257.  
  258. iframe
  259. {background-color: white; filter: invert(1);}
  260.  
  261. .is-generic-video {
  262. filter: invert(1);
  263. }
  264. `;
  265. css.soundcloud = {};
  266. css.soundcloud.css = `
  267. span[style]
  268. `;
  269. css.pocketcasts = {};
  270. css.pocketcasts.css = `
  271. #header {filter: invert(1);}
  272. #audio_player {filter: invert(1); background-color: black;}
  273.  
  274. #header img {filter: invert(0);}
  275. #audio_player img {filter: invert(0);}
  276.  
  277. #audio_player #audio_player_wrapper .player_top,
  278. #audio_player #audio_player_wrapper .player_bottom
  279. {background-color: transparent;}
  280. `;
  281. css.none = {};
  282. css.none.css = ``;
  283.  
  284.  
  285. function addGlobalStyle(css, className, enabled, id) {
  286. var head, style;
  287. head = document.getElementsByTagName('head')[0];
  288. if (!head) { return; }
  289.  
  290. // check to see if this element already exists, if it does override it
  291. var oldEl = document.getElementById(id);
  292.  
  293. style = document.createElement('style');
  294. style.type = 'text/css';
  295. style.innerHTML = css;
  296. style.id = id;
  297. style.className = className;
  298. head.appendChild(style);
  299. style.disabled = !enabled;
  300.  
  301. // delete old element if it exists
  302. if (oldEl) {
  303. oldEl.parentNode.removeChild(oldEl);
  304. }
  305. }
  306.  
  307. function parseCSS(parsed) {
  308. for (attribute in css.defaults) {
  309. exceptionToReplace = new RegExp('{{'+attribute+'}}', 'g');
  310. parsed = parsed.replace(exceptionToReplace, css['defaults'][attribute]);
  311. }
  312.  
  313. return parsed;
  314. }
  315.  
  316. document.addEventListener("keydown", function(e) {
  317. if (e.altKey === true && e.shiftKey === false && e.ctrlKey === true && e.metaKey === false && e.code == 'KeyI') {
  318. var timestamp = new Date();
  319. timestamp = timestamp.getTime();
  320.  
  321. if (timers.lastToggle > timestamp-options.toggleDelayMs) {
  322. if (isInverterEnabled()) {
  323. GM_setValue('enabled_'+document.domain, true);
  324. alert('Saved inversion for '+document.domain);
  325. } else {
  326. GM_deleteValue('enabled_'+document.domain);
  327. alert('Deleted inversion setting for '+document.domain);
  328. }
  329. } else {
  330. // toggle style
  331.  
  332. if (isInverterEnabled()) {
  333. disableStyle();
  334. } else {
  335. enableStyle();
  336. }
  337. }
  338.  
  339. timers.lastToggle = timestamp;
  340. }
  341. });
  342.  
  343. function getCssStyleElements() {
  344. return document.getElementsByClassName(SCRIPT_ID+'-css');
  345. }
  346.  
  347. function enableStyle() {
  348. var cssToInclude = '';
  349.  
  350. if (css[CURRENT_SITE].includeCommon === false) {}
  351. else { cssToInclude += css.common.css; }
  352.  
  353. cssToInclude += css[CURRENT_SITE].css;
  354.  
  355. addGlobalStyle(parseCSS(
  356. cssToInclude
  357. ), SCRIPT_ID+'-css', true, SCRIPT_ID+'-css');
  358. }
  359.  
  360. function disableStyle() {
  361. var cssEls = getCssStyleElements();
  362. for (let i = 0; i < cssEls.length; i++) {
  363. cssEls[i].parentNode.removeChild(cssEls[i]); // remove the element
  364. }
  365. }
  366.  
  367. function isInverterEnabled() {
  368. var cssEl = document.getElementById(SCRIPT_ID+'-css');
  369.  
  370. return isTruthy(cssEl);
  371. }
  372.  
  373. function getCurrentSite() {
  374. var url = document.documentURI;
  375. var toReturn = 'none';
  376.  
  377. if (url.indexOf('messenger.com') != -1) toReturn = 'messenger';
  378. if (url.indexOf('youtube.com') != -1) toReturn = 'youtube';
  379. if (url.indexOf('twitter.com') != -1) toReturn = 'twitter';
  380. if (url.indexOf('inbox.google.com') != -1) toReturn = 'inbox';
  381. if (url.indexOf('hangouts.google.com') != -1) toReturn = 'hangouts';
  382. if (url.indexOf('mail.google.com') != -1) toReturn = 'gmail';
  383. if (url.indexOf('facebook.com') != -1) toReturn = 'facebook';
  384. if (url.indexOf('play.pocketcasts.com') != -1) toReturn = 'pocketcasts';
  385.  
  386. return toReturn;
  387. }
  388.  
  389. function init() {
  390. var styleEnabled = GM_getValue( 'enabled_'+document.domain , false );
  391.  
  392. if (DEBUG_MODE) {
  393. console.log('Inversion Enabled for site ('+CURRENT_SITE+'): '+styleEnabled);
  394. }
  395.  
  396. if (inIframe() && isFalsy(css[CURRENT_SITE].enableInIframe)) { styleEnabled = false; }
  397.  
  398. if (css[CURRENT_SITE].javascriptOnce) { css[CURRENT_SITE].javascriptOnce(); }
  399.  
  400. if (styleEnabled) {
  401. enableStyle();
  402. }
  403. }
  404.  
  405. init();
  406.  
  407. /*
  408. * Utility functions
  409. */
  410.  
  411. function isTruthy(item) {
  412. return !isFalsy(item);
  413. }
  414.  
  415. // from https://gist.github.com/skoshy/69a7951b3070c2e2496d8257e16d7981
  416. function isFalsy(item) {
  417. if (
  418. !item
  419. || (typeof item == "object" && (
  420. Object.keys(item).length == 0 // for empty objects, like {}, []
  421. && !(typeof item.addEventListener == "function") // omit webpage elements
  422. ))
  423. )
  424. return true;
  425. else
  426. return false;
  427. }
  428.  
  429. function addEvent(obj, evt, fn) {
  430. if (obj.addEventListener) {
  431. obj.addEventListener(evt, fn, false);
  432. }
  433. else if (obj.attachEvent) {
  434. obj.attachEvent("on" + evt, fn);
  435. }
  436. }
  437.  
  438. function inIframe () {
  439. try {
  440. return window.self !== window.top;
  441. } catch (e) {
  442. return true;
  443. }
  444. }