Inverter

Inverts webpages with a hotkey

目前為 2017-06-21 提交的版本,檢視 最新版本

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