Inverter

Inverts webpages with a hotkey

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

  1. // ==UserScript==
  2. // @name Inverter
  3. // @icon http://i.imgur.com/wBrRGXc.png
  4. // @namespace skoshy.com
  5. // @version 0.2.25
  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 currentSite = '';
  16. var scriptId = 'inverter';
  17.  
  18. var timers = {};
  19. timers.lastToggle = 0; // default the last toggle to nothing
  20.  
  21. var options = {};
  22. options.toggleDelayMs = 200; // number of milliseconds delay there needs to be to switch from toggle mode to save inversion mode
  23.  
  24. var css = {};
  25. css.defaults = {};
  26. css.overrides = {};
  27.  
  28. css.common = {};
  29. css.common.css = `
  30. html {
  31. filter: invert(1);
  32. min-height: 100%;
  33. background-color: black;
  34. }
  35.  
  36. img, figure, video, picture {
  37. filter: invert(1);
  38. }
  39.  
  40. figure img { /* no need to invert imgs in figures */
  41. filter: invert(0);
  42. }
  43.  
  44. *[style*="url(/"],
  45. *[style*="blob:"],
  46. *[style*="url('https:"],
  47. *[style*="url('http:"],
  48. *[style*="url('://"],
  49. *[style*="url('blob:"],
  50. *[style*='url("https:'],
  51. *[style*='url("https:'],
  52. *[style*='url("http:'],
  53. *[style*='url("://'],
  54. *[style*='url("blob:']
  55. { filter: invert(1); }
  56.  
  57. body, body > div {
  58. background-color: white;
  59. }
  60.  
  61. iframe[src*="youtube.com"], iframe[src*="vimeo.com"] {
  62. filter: invert(1);
  63. }
  64.  
  65. twitterwidget::shadow .MediaCard-media {
  66. filter: invert(1);
  67. }
  68.  
  69. twitterwidget::shadow .Avatar {
  70. filter: invert(1);
  71. }
  72.  
  73. .gbii /* This is for multiple Google Domains to reinvert the profile icon in the top right */
  74. {filter: invert(1);}
  75. `;
  76. css.messenger = {};
  77. css.messenger.includeCommon = false;
  78. css.messenger.javascriptOnce = function() {
  79. var chatColor = '';
  80. setInterval(function() {
  81. 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 */
  82. if (!chatColorEl || chatColorEl.style.fill == chatColor) return;
  83. chatColor = chatColorEl.style.fill;
  84. var newCss = `
  85. ._43by
  86. { background: `+chatColor.replace('rgb', 'rgba').replace(')', ', .3)')+` !important; }
  87. `;
  88. addGlobalStyle(newCss, scriptId+'-css', isInverterEnabled(), scriptId+'-messengerSpecialCss');
  89. }, 500);
  90. };
  91. css.messenger.css = `
  92. body
  93. ,._5i_d * /* Text from YouTube/video share */
  94. { color: white; }
  95.  
  96. /* Bug Fixes */
  97. ._kmc /* message box, min height fix */
  98. { min-height: 26px !important; }
  99.  
  100. ._4sp8 {
  101. background: black !important;
  102. }
  103.  
  104. ._4rv3 /* Message box container */
  105. ,._5743 * /* Top name */
  106. ,._1ht6 /* Names in the sidebar */
  107. ,._1ht3 ._1htf /* Unread message in sidebar */
  108. ,._1tqi ._4qba /* App Name */
  109. ,._5swm * /* Text from link shares */
  110. ,._hh7 a /* Link from received message */
  111. ,._55r /* video chat notices */
  112. ,._1enh ._364g /* from searching, contact names */
  113. ,._225b /* from searching, header */
  114. ,._lll * /* Intro message text for first time chatting someone */
  115. ,._3szq /* Info Box text */
  116. ,._58ak input /* Composing new message text box */
  117. ,._1jt6 ._3oh- /* Info box contact name */
  118. ,._4rph ._4rpj /* Info box group chat, add people */
  119. ,._4_j5 ._364g /* Info box group chat, additional people */
  120. ,._2jnv /* Info box group chat, chat name */
  121. { background: transparent !important; color: white !important; }
  122.  
  123. ._2y8_ * /* Popup boxes text */
  124. { color: black; }
  125.  
  126. ._29_7 ._hh7 /* receiving message boxes */
  127. { background: rgba(255,255,255,.12) !important; color: white; }
  128.  
  129. ._43by /* sent message boxes */
  130. { background: rgba(0, 132, 255,.45) !important; transition: background 0.3s ease; }
  131.  
  132. ._497p /* Timestamps and joining video chat messages in the chat */
  133. ,._1ht7 /* timestamps in sidebar */
  134. ,._ih3 /* Names in group chat */
  135. ,._5iwm ._58al::placeholder /* placeholder text for search */
  136. ,._1lj0 /* Info Box Headers */
  137. ,._3eus, ._5uh /* Info Box Person Description */
  138. ,._2y8z /* Composing new message "To" field" */
  139. ,._58ak input::placeholder /* Composing new message text box, placeholder */
  140. ,._5i_d .__6m /* Link share URL */
  141. ,._3x6v /* Info box, side text next to notifications if notifications are muted */
  142. { color: rgba(255,255,255,.6) !important; }
  143.  
  144. ._29_7 ._52mr /* Borders of message bubble stuff, like video chat notifications, links, etc, received */
  145. ,._nd_ ._52mr /* Same as above, but sent */
  146. { border-color: rgba(255,255,255,.2); }
  147.  
  148. .sp_1g5zN81j-1P /* tiny icons, like when a video chat ends */
  149. ,._5iwm ._58ak::before /* search icon */
  150. ,.sp_6SI1R2TSgtb /* more tiny icons */
  151. ,._2t5t /* more icons, like the forward icon */
  152. { filter: invert(1); }
  153.  
  154. a._4ce_ /* games icon */
  155. ,._4rv6 /* stickers icon */
  156. { opacity: .7 !important; filter: invert(1) !important; }
  157.  
  158. ._5iwm ._58al /* search box */
  159. { background: rgba(255,255,255,.3) !important; color: white !important; }
  160.  
  161. ::-webkit-scrollbar-track
  162. {
  163. -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3) !important;
  164. border-radius: 10px !important;
  165. background-color: #333 !important;
  166. }
  167.  
  168. ::-webkit-scrollbar
  169. {
  170. width: 12px !important;
  171. background-color: transparent !important;
  172. }
  173.  
  174. ::-webkit-scrollbar-thumb
  175. {
  176. border-radius: 10px !important;
  177. -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3) !important;
  178. background-color: #555 !important;
  179. }
  180. `;
  181. css.gmail = {};
  182. css.gmail.css = `
  183. iframe[src*="hangouts.google.com"]
  184. { filter: invert(1); }
  185. `;
  186. css.inbox = {};
  187. css.inbox.css = `
  188. img[src*="ssl.gstatic.com"], img[src*="www.gstatic.com"]
  189. { filter: invert(0); }
  190. nav#GH > .b4 /* Header bar, kepp the same color */
  191. { filter: invert(1); }
  192. iframe[src*="hangouts.google.com"]
  193. { filter: invert(1); }
  194. .cf /* Shipping notification headers */
  195. { filter: invert(1); }
  196. .xW /* Shipping icon */
  197. { filter: invert(1) !important; }
  198. `;
  199. css.hangouts = {};
  200. css.hangouts.enableInIframe = true;
  201. css.hangouts.css = `
  202. .Ik:not(.uB) /* Chat Headers */
  203. { filter: invert(1); }
  204. `;
  205. css.youtube = {};
  206. css.youtube.css = `
  207. .player-api video
  208. {filter: inherit;}
  209.  
  210. #theater-background
  211. {background: white !important;}
  212.  
  213. #player-playlist,
  214. .player-api,
  215. #c4-header-bg-container /* Banner on YouTube channel page */
  216. {filter: invert(1);}
  217.  
  218. #player-playlist img,
  219. #c4-header-bg-container img /* Banner on YouTube channel page */
  220. {filter: invert(0);}
  221.  
  222. `;
  223. css.facebook = {};
  224. css.facebook.css = `
  225. i /* Emoji and other small icons */
  226. {filter: invert(1);}
  227. `;
  228. css.twitter = {};
  229. css.twitter.css = `
  230. .PermalinkOverlay-with-background /* overlay when clicking on a tweet */
  231. {background: rgba(255,255,255,.55) !important;}
  232.  
  233. iframe
  234. {background-color: white; filter: invert(1);}
  235.  
  236. .is-generic-video {
  237. filter: invert(1);
  238. }
  239. `;
  240. css.soundcloud = {};
  241. css.soundcloud.css = `
  242. span[style]
  243. `;
  244. css.pocketcasts = {};
  245. css.pocketcasts.css = `
  246. #header {filter: invert(1);}
  247. #audio_player {filter: invert(1); background-color: black;}
  248.  
  249. #header img {filter: invert(0);}
  250. #audio_player img {filter: invert(0);}
  251.  
  252. #audio_player #audio_player_wrapper .player_top,
  253. #audio_player #audio_player_wrapper .player_bottom
  254. {background-color: transparent;}
  255. `;
  256. css.none = {};
  257. css.none.css = ``;
  258.  
  259.  
  260. function addGlobalStyle(css, className, enabled, id) {
  261. var head, style;
  262. head = document.getElementsByTagName('head')[0];
  263. if (!head) { return; }
  264. // check to see if this element already exists, if it does override it
  265. var oldEl = document.getElementById(id);
  266. style = document.createElement('style');
  267. style.type = 'text/css';
  268. style.innerHTML = css;
  269. style.id = id;
  270. style.className = className;
  271. head.appendChild(style);
  272. style.disabled = !enabled;
  273. // delete old element if it exists
  274. if (oldEl) {
  275. oldEl.parentNode.removeChild(oldEl);
  276. }
  277. }
  278.  
  279. function parseCSS(parsed) {
  280. for (attribute in css.defaults) {
  281. exceptionToReplace = new RegExp('{{'+attribute+'}}', 'g');
  282. parsed = parsed.replace(exceptionToReplace, css['defaults'][attribute]);
  283. }
  284.  
  285. return parsed;
  286. }
  287.  
  288. document.addEventListener("keydown", function(e) {
  289. if (e.altKey === true && e.shiftKey === false && e.ctrlKey === true && e.metaKey === false && e.code == 'KeyI') {
  290. var timestamp = new Date();
  291. timestamp = timestamp.getTime();
  292. if (timers.lastToggle > timestamp-options.toggleDelayMs) {
  293. if (isInverterEnabled()) {
  294. GM_setValue('enabled_'+document.domain, true);
  295. alert('Saved inversion for '+document.domain);
  296. } else {
  297. GM_deleteValue('enabled_'+document.domain);
  298. alert('Deleted inversion setting for '+document.domain);
  299. }
  300. } else {
  301. // toggle style
  302. var cssEls = document.getElementsByClassName(scriptId+'-css');
  303.  
  304. if (isInverterEnabled()) {
  305. for (let i = 0; i < cssEls.length; i++) {
  306. cssEls[i].disabled = true;
  307. }
  308. } else {
  309. for (let i = 0; i < cssEls.length; i++) {
  310. cssEls[i].disabled = false;
  311. }
  312. }
  313. }
  314. timers.lastToggle = timestamp;
  315. }
  316. });
  317.  
  318. function isInverterEnabled() {
  319. var cssEl = document.getElementById(scriptId+'-css');
  320. return cssEl.disabled === false;
  321. }
  322.  
  323. function getSetCurrentSite() {
  324. var url = document.documentURI;
  325. currentSite = 'none'
  326.  
  327. if (url.indexOf('messenger.com') != -1) currentSite = 'messenger';
  328. if (url.indexOf('youtube.com') != -1) currentSite = 'youtube';
  329. if (url.indexOf('twitter.com') != -1) currentSite = 'twitter';
  330. if (url.indexOf('inbox.google.com') != -1) currentSite = 'inbox';
  331. if (url.indexOf('hangouts.google.com') != -1) currentSite = 'hangouts';
  332. if (url.indexOf('mail.google.com') != -1) currentSite = 'gmail';
  333. if (url.indexOf('facebook.com') != -1) currentSite = 'facebook';
  334. if (url.indexOf('play.pocketcasts.com') != -1) currentSite = 'pocketcasts';
  335.  
  336. console.log('Detected site for '+scriptId+' script: '+currentSite);
  337. return currentSite;
  338. }
  339.  
  340. function init() {
  341. getSetCurrentSite();
  342.  
  343. var styleEnabled = GM_getValue( 'enabled_'+document.domain , false );
  344. console.log('Inversion Enabled for site ('+currentSite+'): '+styleEnabled);
  345. if (inIframe() && isFalsy(css[currentSite].enableInIframe)) { styleEnabled = false; }
  346. var cssToInclude = '';
  347. if (css[currentSite].includeCommon === false) {}
  348. else { cssToInclude += css.common.css; }
  349. if (css[currentSite].javascriptOnce) { css[currentSite].javascriptOnce(); }
  350. cssToInclude += css[currentSite].css;
  351.  
  352. addGlobalStyle(parseCSS(
  353. cssToInclude
  354. ), scriptId+'-css', styleEnabled, scriptId+'-css');
  355. }
  356.  
  357. init();
  358.  
  359. /*
  360. * Utility functions
  361. */
  362.  
  363. function isTruthy(item) {
  364. return !isFalsy(item);
  365. }
  366.  
  367. function isFalsy(item) {
  368. if (
  369. !item
  370. || (typeof item == "object" && Object.keys(item).length == 0) // for empty objects, like {}, []
  371. )
  372. return true;
  373. else
  374. return false;
  375. }
  376.  
  377. function addEvent(obj, evt, fn) {
  378. if (obj.addEventListener) {
  379. obj.addEventListener(evt, fn, false);
  380. }
  381. else if (obj.attachEvent) {
  382. obj.attachEvent("on" + evt, fn);
  383. }
  384. }
  385.  
  386. function inIframe () {
  387. try {
  388. return window.self !== window.top;
  389. } catch (e) {
  390. return true;
  391. }
  392. }