Inverter

Inverts webpages with a hotkey

// ==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;
	}
}