Inverter

Inverts webpages with a hotkey

当前为 2018-02-03 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Inverter
// @icon         http://i.imgur.com/wBrRGXc.png
// @namespace    skoshy.com
// @version      0.2.41
// @description  Inverts webpages with a hotkey
// @author       Stefan Koshy
// @run-at       document-start
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// ==/UserScript==

var DEBUG_MODE = false;
var SCRIPT_ID = 'inverter';
var CURRENT_SITE = getCurrentSite();

// From https://gist.github.com/arantius/3123124
// These are replacement functions for GreaseMonkey scripts, but the only work on a single domain instead of being cross domain
// Todo: Implement solution that works cross domain

if (typeof GM_getValue == 'undefined') {
	function GM_getValue(aKey, aDefault) {
		'use strict';
		let val = localStorage.getItem(SCRIPT_ID + aKey)
		if (null === val && 'undefined' != typeof aDefault) return aDefault;
		return val;
	}
}

if (typeof GM_setValue == 'undefined') {
	function GM_setValue(aKey, aVal) {
		'use strict';
		localStorage.setItem(SCRIPT_ID + aKey, aVal);
	}
}

var timers = {};
timers.lastToggle = 0; // default the last toggle to nothing

var options = {};
options.toggleDelayMs = 200; // number of milliseconds delay there needs to be to switch from toggle mode to save inversion mode

var css = {};
css.defaults = {};
css.overrides = {};

css.common = {};
css.common.css = `
html {
	filter: invert(1);
	min-height: 100%;
	background-color: black;
}

img, figure, video, picture {
	filter: invert(1);
}

figure img { /* no need to invert imgs in figures */
	filter: invert(0);
}

*[style*="url(/"],
*[style*="blob:"],
*[style*="url('https:"],
*[style*="url('http:"],
*[style*="url('://"],
*[style*="url('blob:"],
*[style*='url("https:'],
*[style*='url("https:'],
*[style*='url("http:'],
*[style*='url("://'],
*[style*='url("blob:']
{ filter: invert(1); }

body, body > div {
	background-color: white;
}

iframe[src*="youtube.com"], iframe[src*="vimeo.com"] {
	filter: invert(1);
}

twitterwidget::shadow .MediaCard-media {
	filter: invert(1);
}

twitterwidget::shadow .Avatar {
	filter: invert(1);
}

.gbii /* This is for multiple Google Domains to reinvert the profile icon in the top right */
{filter: invert(1);}
`;
css.messenger = {};
css.messenger.includeCommon = false;
css.messenger.javascriptOnce = function() {
	var chatColor = '';

	setInterval(function() {
		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 */
		if (!chatColorEl || chatColorEl.style.fill == chatColor) return;

		chatColor = chatColorEl.style.fill;

		var newCss = `
			._43by
			{ background: `+chatColor.replace('rgb', 'rgba').replace(')', ', .3)')+` !important; }
		`;

		addGlobalStyle(newCss, SCRIPT_ID+'-css', isInverterEnabled(), SCRIPT_ID+'-messengerSpecialCss');
	}, 500);
};
css.messenger.css = `
body
,._5i_d * /* Text from YouTube/video share */
{ color: white; }

/* Bug Fixes */
._kmc /* message box, min height fix */
{ min-height: 26px !important; }

._4sp8 {
background: black !important;
}

._4rv3 /* Message box container */
,._5743 * /* Top name */
,._1ht6 /* Names in the sidebar */
,._1ht3 ._1htf /* Unread message in sidebar */
,._1tqi ._4qba /* App Name */
,._5swm * /* Text from link shares */
,._hh7 a /* Link from received message */
,._55r /* video chat notices */
,._1enh ._364g /* from searching, contact names */
,._225b /* from searching, header */
,._lll * /* Intro message text for first time chatting someone */
,._3szq /* Info Box text */
,._58ak input /* Composing new message text box */
,._1jt6 ._3oh- /* Info box contact name */
,._4rph ._4rpj /* Info box group chat, add people */
,._4_j5 ._364g /* Info box group chat, additional people */
,._2jnv /* Info box group chat, chat name */
{ background: transparent !important; color: white !important; }

._1ht3._1ht1 /* unread message in sidebar */
{ background-color: rgba(0, 132, 255, .5) !important; }

._1ht1._1ht2 /* current message in the sidebar */
{ background-color: rgba(255, 255, 255, .15) !important; }

._2y8_ * /* Popup boxes text */
{ color: black; }

._29_7 ._hh7 /* receiving message boxes */
{ background: rgba(255,255,255,.12) !important; color: white; }

._43by /* sent message boxes */
{ background: rgba(0, 132, 255,.45) !important; transition: background 0.3s ease; }

._497p /* Timestamps and joining video chat messages in the chat */
,._1ht7 /* timestamps in sidebar */
,._ih3 /* Names in group chat */
,._5iwm ._58al::placeholder /* placeholder text for search */
,._1lj0 /* Info Box Headers */
,._3eus, ._5uh /* Info Box Person Description */
,._2y8z /* Composing new message "To" field" */
,._58ak input::placeholder /* Composing new message text box, placeholder */
,._5i_d .__6m /* Link share URL */
,._3x6v /* Info box, side text next to notifications if notifications are muted */
{ color: rgba(255,255,255,.6) !important; }

._29_7 ._52mr /* Borders of message bubble stuff, like video chat notifications, links, etc, received */
,._nd_ ._52mr /* Same as above, but sent */
{ border-color: rgba(255,255,255,.2); }

.sp_1g5zN81j-1P /* tiny icons, like when a video chat ends */
,._5iwm ._58ak::before /* search icon */
,.sp_6SI1R2TSgtb /* more tiny icons */
,._2t5t /* more icons, like the forward icon */
{ filter: invert(1); }

a._4ce_ /* games icon */
,._4rv6 /* stickers icon */
{ opacity: .7 !important; filter: invert(1) !important; }

._5iwm ._58al /* search box */
{ background: rgba(255,255,255,.3) !important; color: white !important; }

::-webkit-scrollbar-track
{
	-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3) !important;
	border-radius: 10px !important;
	background-color: #333 !important;
}

::-webkit-scrollbar
{
	width: 12px !important;
	background-color: transparent !important;
}

::-webkit-scrollbar-thumb
{
	border-radius: 10px !important;
	-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3) !important;
	background-color: #555 !important;
}
`;
css.gmail = {};
css.gmail.css = `
iframe[src*="hangouts.google.com"]
{ filter: invert(1); }
`;
css.inbox = {};
css.inbox.css = `
img[src*="ssl.gstatic.com"], img[src*="www.gstatic.com"]
{ filter: invert(0); }
nav#GH > .b4 /* Header bar, kepp the same color */
{ filter: invert(1); }
iframe[src*="hangouts.google.com"]
{ filter: invert(1); }
.cf /* Shipping notification headers */
{ filter: invert(1); }
.xW /* Shipping icon */
{ filter: invert(1) !important; }
`;
css.hangouts = {};
css.hangouts.enableInIframe = true;
css.hangouts.css = `
.Ik:not(.uB) /* Chat Headers */
{ filter: invert(1); }
`;
css.youtube = {};
css.youtube.css = `
.player-api video
{filter: inherit;}

#theater-background
{background: white !important;}

#player-playlist,
.player-api,
#c4-header-bg-container /* Banner on YouTube channel page */
{filter: invert(1);}

#player-playlist img,
#c4-header-bg-container img /* Banner on YouTube channel page */
{filter: invert(0);}

`;
css.facebook = {};
css.facebook.css = `
i /* Emoji and other small icons */
{filter: invert(1);}
`;
css.twitter = {};
css.twitter.css = `
.PermalinkOverlay-with-background /* overlay when clicking on a tweet */
{background: rgba(255,255,255,.55) !important;}

iframe
{background-color: white; filter: invert(1);}

.is-generic-video {
	filter: invert(1);
}
`;
css.soundcloud = {};
css.soundcloud.css = `
span[style]
`;
css.pocketcasts = {};
css.pocketcasts.css = `
#header {filter: invert(1);}
#audio_player {filter: invert(1); background-color: black;}

#header img {filter: invert(0);}
#audio_player img {filter: invert(0);}

#audio_player #audio_player_wrapper .player_top,
#audio_player #audio_player_wrapper .player_bottom
{background-color: transparent;}
`;
css.slack = {};
css.slack.includeCommon = false;
css.slack.css = `
body #loading_welcome,
.loading #loading-zone,
#loading_message p,
body #client_body,
body .end_div_msg_lim,
#client_body h1,
#client_body h2,
#client_body h3,
#client_body h4,
body #footer,
body #msg_input,
#msgs_overlay_div,
body .file_container:not(.snippet_container),
#messages_container.has_top_messages_banner:before, /* The background of the X New Message since blah thing in the message window */

/* Flexible column, aka threaded messages */
body #col_flex,
#flex_contents .heading,

/* About channel column */
body #details_tab .channel_page_section,
body #details_tab .channel_page_section div,
#details_tab .channel_page_section .section_title,
.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),
.c-channel_insights__date_heading span,

/* All threads view */
#threads_msgs_scroller_div,
body ts-thread,
body ts-thread .thread_messages,
#convo_container .convo_flexpane_divider .reply_count, #file_preview_scroller .convo_flexpane_divider .reply_count,
body .reply_input_container .ql-container,

/* Top Header */
body #client_header,
body .channel_header,
body .ql-container.texty_single_line_input .ql-editor, /* Search input */
.feature_name_tagging_client #edit_topic_inner:before, /* Topic box */
.day_divider .day_divider_label, /* Day divider */
#client_body:not(.onboarding):before, /* Top day divider */

/* New Direct Message modal */
#fs_modal.active,
.plastic_select, input[type=url], input[type=text], input[type=tel], input[type=number], input[type=email], input[type=password], select, textarea,

/* Unreads Tab */
body .unread_msgs_loading,

/* Image viewer */
.fs_modal_file_viewer_content .viewer
  { background: rgb(27, 27, 27) !important; color: white !important; -webkit-text-stroke-width: 1px; -webkit-text-stroke-color: rgba(101, 101, 101, 0.1); }

.sli_briefing, /* New Unreads Banner */
#msgs_div .unread_divider.no_unreads .divider_label /* New Message line indicator text */
  { background: rgb(27, 27, 27) !important; }

body .channel_title .channel_name,
body #archives_end_div_msg_lim h1,
body #end_display_msg_lim h1,
body ts-message,
body #client_body ts-message .message_content .message_sender,
body .c-message__sender,
body .c-message--light .c-message__sender .c-message__sender_link,
body .c-message__body,
body ts-thread .thread_header .thread_channel_name a,
.c-member__display-name, .c-team__display-name, .c-usergroup__handle,

/* Unread Tab */
body .unread_empty_state,

/* Convo Tab */
body #convo_tab .message_input, body #convo_tab textarea#msg_text,

/* New Direct Message Modal */
#fs_modal h1, #fs_modal h2, #fs_modal h3, #fs_modal h4, #fs_modal h5,
.c-member__secondary-name--medium,
#im_browser .im_browser_row.multiparty,
body .flexpane_file_title .member, body .flexpane_file_title .service_link, .flexpane_file_title .title a, /* files pane */
body .comment .member, .comment_body, /* file comments */
#details_tab .feature_sli_channel_insights .channel_created_section .creator_link, #details_tab .feature_sli_channel_insights .channel_purpose_section .channel_purpose_text
  { color: white !important; -webkit-text-stroke-width: 1px; -webkit-text-stroke-color: rgba(101, 101, 101, 0.1); }

/* Hover Message */
ts-message.active:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
ts-message.message--focus:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
ts-message:hover:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
#msg_input+#primary_file_button:not(:hover):not(.active),
ts-message .reply_bar:hover, /* Hovering over the reply bar in a message */
ts-message.selected:not(.delete_mode),
ts-thread ts-message.new_reply, /* when a thread has a new message */
#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 */

/* Unreads Tab */
body .unread_group_header, body .unread_group_header_name a,

.msg_inline_attachment_column.column_border
  { background: rgb(40, 40, 40) !important; color: white !important;  }

/* Hover - Border */
#client_body:not(.onboarding):before, /* Top day divider */
#convo_container .convo_flexpane_divider,
#file_preview_scroller .convo_flexpane_divider,
#flex_contents .heading,
.flex_pane_showing #col_flex,
.day_container .day_msgs,
.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)
  { border-color: rgb(40, 40, 40) !important; }

/* Placeholder text */
body .ql-placeholder
  { color: rgb(200,200,200); }

/* Override text with black text */
.feature_name_tagging_client ts-message .mention, /* channel mention */
body .comment .mention, body .ql-editor .mention, body ts-message .mention /* more mentions */
  { color: black !important; }

/* Remove the white border between channel switcher and messages */
body #col_messages,
#client_body:not(.onboarding):before,
.channel_header,
#footer,
body ts-message.active:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
body ts-message.message--focus:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply),
body ts-message:hover:not(.standalone):not(.multi_delete_mode):not(.highlight):not(.new_reply)
  { box-shadow: none !important; }

/* Links */
ts-message .message_body a,
ts-message .message_body a:link,
ts-message .message_body a:visited,
ts-message .message_body .file_preview_link
  { color: #66baec !important; }

/* Links - Hover */
ts-message .message_body a:hover,
ts-message .message_body a:link:hover,
ts-message .message_body a:visited:hover,
ts-message .message_body .file_preview_link:hover
  { color: #569eca !important; }
`;
css.zohonotebook = {};
css.zohonotebook.css = `
#wholeContainer,
.versionsContainer
{ background: rgb(27, 27, 27) !important; color: white !important; }

.noteCardDiv:before
{ border-color: rgb(27, 27, 27) !important; }

.navBarIcon
{ filter: invert(1); }

#createWrapper
{ background: transparent !important; }
`;
css.zohonotebook.includeCommon = false;
css.none = {};
css.none.css = ``;


function addGlobalStyle(css, className, enabled, id) {
	var head, style;
	head = document.getElementsByTagName('head')[0];
	if (!head) { return; }

	// check to see if this element already exists, if it does override it
	var oldEl = document.getElementById(id);

	style = document.createElement('style');
	style.type = 'text/css';
	style.innerHTML = css;
	style.id = id;
	style.className = className;
	head.appendChild(style);
	style.disabled = !enabled;

	// delete old element if it exists
	if (oldEl) {
		oldEl.parentNode.removeChild(oldEl);
	}
}

function parseCSS(parsed) {
	for (attribute in css.defaults) {
		exceptionToReplace = new RegExp('{{'+attribute+'}}', 'g');
		parsed = parsed.replace(exceptionToReplace, css['defaults'][attribute]);
	}

	return parsed;
}

document.addEventListener("keydown", function(e) {
	if (e.altKey === true && e.shiftKey === false && e.ctrlKey === true && e.metaKey === false && e.code == 'KeyI') {
		var timestamp = new Date();
		timestamp = timestamp.getTime();

		if (timers.lastToggle > timestamp-options.toggleDelayMs) {
			if (isInverterEnabled()) {
			GM_setValue('enabled_'+document.domain, true);
			alert('Saved inversion for '+document.domain);
			} else {
			GM_deleteValue('enabled_'+document.domain);
			alert('Deleted inversion setting for '+document.domain);
			}
		} else {
			// toggle style

			if (isInverterEnabled()) {
				disableStyle();
			} else {
				enableStyle();
			}
		}

		timers.lastToggle = timestamp;
	}
});

function getCssStyleElements() {
	return document.getElementsByClassName(SCRIPT_ID+'-css');
}

function enableStyle() {
	var cssToInclude = '';

	if (css[CURRENT_SITE].includeCommon === false) {}
	else { cssToInclude += css.common.css; }

	cssToInclude += css[CURRENT_SITE].css;

	addGlobalStyle(parseCSS(
		cssToInclude
	), SCRIPT_ID+'-css', true, SCRIPT_ID+'-css');
}

function disableStyle() {
	var cssEls = getCssStyleElements();
	for (let i = 0; i < cssEls.length; i++) {
		cssEls[i].parentNode.removeChild(cssEls[i]); // remove the element
	}
}

function isInverterEnabled() {
	var cssEl = document.getElementById(SCRIPT_ID+'-css');

	return isTruthy(cssEl);
}

function getCurrentSite() {
	var url = document.documentURI;
	var toReturn = 'none';

	if (url.indexOf('messenger.com') != -1) toReturn = 'messenger';
	if (url.indexOf('youtube.com') != -1) toReturn = 'youtube';
	if (url.indexOf('twitter.com') != -1) toReturn = 'twitter';
	if (url.indexOf('inbox.google.com') != -1) toReturn = 'inbox';
	if (url.indexOf('hangouts.google.com') != -1) toReturn = 'hangouts';
	if (url.indexOf('mail.google.com') != -1) toReturn = 'gmail';
	if (url.indexOf('facebook.com') != -1) toReturn = 'facebook';
	if (url.indexOf('play.pocketcasts.com') != -1) toReturn = 'pocketcasts';
	if (url.indexOf('notebook.zoho.com') != -1) toReturn = 'zohonotebook';
	if (url.indexOf('slack.com/messages') != -1 || url.indexOf('slack.com/threads') != -1 || url.indexOf('slack.com/unreads') != -1) toReturn = 'slack';

	return toReturn;
}

function init() {
	var styleEnabled = GM_getValue( 'enabled_'+document.domain , false );

	if (DEBUG_MODE) {
		console.log('Inversion Enabled for site ('+CURRENT_SITE+'): '+styleEnabled);
	}

	if (inIframe() && isFalsy(css[CURRENT_SITE].enableInIframe)) { styleEnabled = false; }

	if (css[CURRENT_SITE].javascriptOnce) { css[CURRENT_SITE].javascriptOnce(); }

	if (styleEnabled) {
		enableStyle();
	}
}

init();

/*
* Utility functions
*/

function isTruthy(item) {
	return !isFalsy(item);
}

// from https://gist.github.com/skoshy/69a7951b3070c2e2496d8257e16d7981
function isFalsy(item) {
	if (
		!item
		|| (typeof item == "object" && (
			Object.keys(item).length == 0 // for empty objects, like {}, []
			&& !(typeof item.addEventListener == "function") // omit webpage elements
		))
	)
		return true;
	else
		return false;
}

function addEvent(obj, evt, fn) {
	if (obj.addEventListener) {
		obj.addEventListener(evt, fn, false);
	}
	else if (obj.attachEvent) {
		obj.attachEvent("on" + evt, fn);
	}
}

function inIframe () {
	try {
		return window.self !== window.top;
	} catch (e) {
		return true;
	}
}