Darkify

Selectively toggle dark mode for any website

当前为 2024-02-27 提交的版本,查看 最新版本

// ==UserScript==
// @name        Darkify
// @namespace   http://yifangu.com
// @license     MIT
// @version     2024-02-27
// @description Selectively toggle dark mode for any website
// @author      You
// @match       *://*/*
// @run-at      document-start
// @inject-into content
// @grant       GM.setValue
// @grant       GM.getValue
// @grant       GM.info
// ==/UserScript==

(async function () {
	"use strict";
	if (window.top !== window.self) {
		return;
	}
	const ver = GM.info.script.version;
	const firstRunReadme = [
		"This is your first time running Darkify",
		"Press CTRL+SHIFT+D to toggle on/off dark mode at a given site.",
		"For mobile devices, hold down three fingers for 3 seconds to toggle on/off dark mode.",
		`Version ${ver}`,
	].join("\n");

	const upgradeReadme = [
		`Darkify has been upgraded to version ${ver}`
	].join("\n");

	const savedVer = await GM.getValue("version");
	if (savedVer === undefined) {
		// first run
		alert(firstRunReadme);
		await GM.setValue("version", ver);
	} else if (savedVer !== ver) {
		alert(upgradeReadme);
		await GM.setValue("version", ver);
	}

	const siteKey = "_" + location.hostname.toLowerCase().replaceAll(/[^a-z0-9]/g, "_");
	const enabled = await GM.getValue(`sites.${siteKey}`, false);

	const toggle = async () => {
		await GM.setValue(`sites.${siteKey}`, !enabled);
		location.reload();
	};

	window.addEventListener("keydown", async (e) => {
		// ctrl + shift + d
		if (e.ctrlKey && e.shiftKey && e.key === "D") {
			e.preventDefault();
			await toggle();
		}
	});

	let toggleTimeoutHandle;
	document.addEventListener("touchmove", (e) => {
		const num = e.touches.length;
		if (num === 3) {
			if (toggleTimeoutHandle === undefined) {
				toggleTimeoutHandle = setTimeout(async () => {
					await toggle();
					toggleTimeoutHandle = undefined;
				}, 3000);
			}
		} else {
			if (toggleTimeoutHandle !== undefined) {
				clearTimeout(toggleTimeoutHandle);
				toggleTimeoutHandle = undefined;
			}
		}
	});

	document.addEventListener("touchend", () => {
		if (toggleTimeoutHandle !== undefined) {
			clearTimeout(toggleTimeoutHandle);
			toggleTimeoutHandle = undefined;
		}
	});

	if (!enabled) {
		return;
	}

	const css = `
		:root {
			color-scheme: dark;
			background-color: white;
			filter: invert(100%) hue-rotate(180deg) !important;
			-webkit-font-smoothing: antialiased;
			color: black;
		}
		body {
			color-scheme: light;
			background-color: #000 !important;
			background: linear-gradient(#fff, #fff);
		}
		img:not([src*="svg"]),video,iframe,embed,object,canvas,picture,.logo,.darkify-ignore {
			filter: invert(100%) hue-rotate(180deg) !important;
		}
		img[src*="svg"]:hover {
			background-color: #aaa !important;
		}
	`;


	function darkify() {
		const divs = document.querySelectorAll("div");
		for (const div of divs) {
			if (!div.classList.contains("darkify-ignore")) {
				continue;
			}
			const cs = getComputedStyle(div);
			const bg = cs.backgroundImage;
			const bs = cs.backgroundSize;
			if (bg === "none") {
				continue;
			}
			if (!["cover", "contain"].includes(bs)) {
				continue;
			}
			if (bg.includes(".svg") || bg.includes(".gif")) {
				continue;
			}
			div.classList.add("darkify-ignore");
		}
		if (document.getElementById("darkify")) {
			return;
		}
		const style = document.createElement("style");
		style.id = "darkify";
		style.innerHTML = css;

		const themeColorMeta = document.createElement("meta");
		themeColorMeta.name = "theme-color";
		themeColorMeta.content = "#000000";

		document.head.appendChild(style);
		document.head.appendChild(themeColorMeta);
	}

	darkify();

	document.addEventListener("DOMContentLoaded", darkify);
})();