VoidVerified

Display a verified sign next to user's name in AniList.

目前為 2023-11-18 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name          VoidVerified
// @version       1.1.1
// @namespace     http://tampermonkey.net/
// @author        voidnyan
// @description   Display a verified sign next to user's name in AniList.
// @homepageURL   https://github.com/voidnyan/void-verified#voidverified
// @supportURL    https://github.com/voidnyan/void-verified/issues
// @grant         none
// @match         https://anilist.co/*
// @license MIT
// ==/UserScript==

(function () {
	'use strict';

	class StyleHandler {
		Settings;
		usernameStyles = "";
		highlightStyles = "";
		otherStyles = "";

		profileLink = this.createStyleLink("", "profile");

		constructor(settings) {
			this.Settings = settings;
		}

		refreshStyles() {
			this.createStyles();
			this.createStyleLink(this.usernameStyles, "username");
			this.createStyleLink(this.highlightStyles, "highlight");
			this.createStyleLink(this.otherStyles, "other");
		}

		createStyles() {
			this.usernameStyles = "";
			this.otherStyles = `a[href="/settings/developer" i]::after{content: " & Void"}`;

			for (const user of this.Settings.VerifiedUsers) {
				if (
					this.Settings.getOptionValue(
						this.Settings.Options.enabledForUsername
					) ||
					user.enabledForUsername
				) {
					this.createUsernameCSS(user);
				}
			}

			if (
				this.Settings.getOptionValue(
					this.Settings.Options.moveSubscribeButtons
				)
			) {
				this.otherStyles += `
                .has-label::before {
                top: -30px !important;
                left: unset !important;
                right: -10px;
                }
    
                .has-label[label="Unsubscribe"],
                .has-label[label="Subscribe"] {
                font-size: 0.875em !important;
                }
    
                .has-label[label="Unsubscribe"] {
                color: rgba(var(--color-green),.8);
                }
                `;
			}

			this.createHighlightStyles();

			if (this.Settings.getOptionValue(this.Settings.Options.hideLikeCount)) {
				this.otherStyles += `
                    .like-wrap .count {
                        display: none;
                    }
                `;
			}
		}

		createHighlightStyles() {
			this.highlightStyles = "";
			for (const user of this.Settings.VerifiedUsers) {
				if (
					this.Settings.getOptionValue(
						this.Settings.Options.highlightEnabled
					) ||
					user.highlightEnabled
				) {
					this.createHighlightCSS(
						user,
						`div.wrap:has( div.header > a.name[href*="${user.username}" i] )`
					);
					this.createHighlightCSS(
						user,
						`div.wrap:has( div.details > a.name[href*="${user.username}" i] )`
					);
				}

				if (
					this.Settings.getOptionValue(
						this.Settings.Options.highlightEnabledForReplies
					) ||
					user.highlightEnabledForReplies
				) {
					this.createHighlightCSS(
						user,
						`div.reply:has( a.name[href*="${user.username}" i] )`
					);
				}
			}

			this.disableHighlightOnSmallCards();
		}

		createUsernameCSS(user) {
			this.usernameStyles += `
                a.name[href*="${user.username}" i]::after {
                    content: "${
						this.stringIsEmpty(user.sign) ??
						this.Settings.getOptionValue(
							this.Settings.Options.defaultSign
						)
					}";
                    color: ${
						this.getUserColor(user) ?? "rgb(var(--color-blue))"
					}
                }
                `;
		}

		createHighlightCSS(user, selector) {
			this.highlightStyles += `
                ${selector} {
                    margin-right: -${this.Settings.getOptionValue(
						this.Settings.Options.highlightSize
					)};
                    border-right: ${this.Settings.getOptionValue(
						this.Settings.Options.highlightSize
					)} solid ${
			this.getUserColor(user) ?? this.getDefaultHighlightColor()
		};
                    border-radius: 5px;
                }
                `;
		}

		disableHighlightOnSmallCards() {
			this.highlightStyles += `
                div.wrap:has(div.small) {
                margin-right: 0px !important;
                border-right: 0px solid black !important;
                }
                `;
		}

		refreshHomePage() {
			if (
				!this.Settings.getOptionValue(
					this.Settings.Options.highlightEnabled
				)
			) {
				return;
			}
			this.createHighlightStyles();
			this.createStyleLink(this.highlightStyles, "highlight");
		}

		clearProfileVerify() {
			this.profileLink.href =
				"data:text/css;charset=UTF-8," + encodeURIComponent("");
		}

		clearStyles(id) {
			const styles = document.getElementById(`void-verified-${id}-styles`);
			styles?.remove();
		}

		verifyProfile() {
			if (
				!this.Settings.getOptionValue(
					this.Settings.Options.enabledForProfileName
				)
			) {
				return;
			}

			const usernameHeader = document.querySelector("h1.name");
			const username = usernameHeader.innerHTML.trim();

			const user = this.Settings.VerifiedUsers.find(
				(u) => u.username === username
			);
			if (!user) {
				this.clearProfileVerify();
				return;
			}

			const profileStyle = `
                    h1.name::after {
                    content: "${
						this.stringIsEmpty(user.sign) ??
						this.Settings.getOptionValue(
							this.Settings.Options.defaultSign
						)
					}"
                    }
                `;
			this.profileLink.href =
				"data:text/css;charset=UTF-8," + encodeURIComponent(profileStyle);
		}

		copyUserColor() {
			const usernameHeader = document.querySelector("h1.name");
			const username = usernameHeader.innerHTML.trim();
			const user = this.Settings.VerifiedUsers.find(
				(u) => u.username === username
			);

			if (!user) {
				return;
			}

			if (
				!(
					user.copyColorFromProfile ||
					this.Settings.getOptionValue(
						this.Settings.Options.copyColorFromProfile
					)
				)
			) {
				return;
			}

			const color =
				getComputedStyle(usernameHeader).getPropertyValue("--color-blue");

			this.Settings.updateUserOption(user.username, "color", color);
		}

		getUserColor(user) {
			return (
				user.colorOverride ??
				(user.color &&
				(user.copyColorFromProfile ||
					this.Settings.getOptionValue(
						this.Settings.Options.copyColorFromProfile
					))
					? `rgb(${user.color})`
					: undefined)
			);
		}

		getDefaultHighlightColor() {
			if (
				this.Settings.getOptionValue(
					this.Settings.Options.useDefaultHighlightColor
				)
			) {
				return this.Settings.getOptionValue(
					this.Settings.Options.defaultHighlightColor
				);
			}
			return "rgb(var(--color-blue))";
		}

		createStyleLink(styles, id) {
			const oldLink = document.getElementById(`void-verified-${id}-styles`);
			const link = document.createElement("link");
			link.setAttribute("id", `void-verified-${id}-styles`);
			link.setAttribute("rel", "stylesheet");
			link.setAttribute("type", "text/css");
			link.setAttribute(
				"href",
				"data:text/css;charset=UTF-8," + encodeURIComponent(styles)
			);
			document.head?.append(link);
			oldLink?.remove();
			return link;
		}

		stringIsEmpty(string) {
			if (!string || string.length === 0) {
				return undefined;
			}
			return string;
		}
	}

	class GlobalCSS {
		settings;
		styleHandler;

		styleId = "global-css";
		isCleared = false;

		cssInLocalStorage = "void-verified-global-css";
		constructor(settings) {
			this.settings = settings;
			this.styleHandler = new StyleHandler(settings);

			this.css = localStorage.getItem(this.cssInLocalStorage) ?? "";
		}

		createCss() {
			if (
				!this.settings.getOptionValue(
					this.settings.Options.globalCssEnabled
				)
			) {
				this.styleHandler.clearStyles(this.styleId);
				return;
			}

			if (!this.shouldRender()) {
				return;
			}

			this.isCleared = false;
			this.styleHandler.createStyleLink(this.css, this.styleId);
		}

		updateCss(css) {
			this.css = css;
			this.createCss();
			localStorage.setItem(this.cssInLocalStorage, css);
		}

		clearCssForProfile() {
			if (this.isCleared) {
				return;
			}
			if (!this.shouldRender()) {
				this.styleHandler.clearStyles(this.styleId);
				this.isCleared = true;
			}
		}

		shouldRender() {
			if (window.location.pathname.startsWith("/settings/")) {
				return false;
			}

			if (
				!this.settings.getOptionValue(
					this.settings.Options.globalCssAutoDisable
				)
			) {
				return true;
			}

			const profileCustomCss = document.getElementById(
				"customCSS-automail-styles"
			);

			if (!profileCustomCss) {
				return true;
			}

			const shouldRender = profileCustomCss.innerHTML.trim().length === 0;
			return shouldRender;
		}
	}

	class Settings {
		LocalStorageUsers = "void-verified-users";
		LocalStorageSettings = "void-verified-settings";
		Version = "1.1.1";

		Options = {
			copyColorFromProfile: {
				defaultValue: true,
				description: "Copy user color from their profile when visited.",
			},
			moveSubscribeButtons: {
				defaultValue: false,
				description:
					"Move activity subscribe button next to comments and likes.",
			},
			hideLikeCount: {
				defaultValue: false,
				description: "Hide activity and reply like counts.",
			},
			enabledForUsername: {
				defaultValue: true,
				description: "Display a verified sign next to usernames.",
			},
			enabledForProfileName: {
				defaultValue: false,
				description: "Display a verified sign next to a profile name.",
			},
			defaultSign: {
				defaultValue: "✔",
				description: "The default sign displayed next to a username.",
			},
			highlightEnabled: {
				defaultValue: true,
				description: "Highlight user activity with a border.",
			},
			highlightEnabledForReplies: {
				defaultValue: true,
				description: "Highlight replies with a border.",
			},
			highlightSize: {
				defaultValue: "5px",
				description: "Width of the highlight border.",
			},
			useDefaultHighlightColor: {
				defaultValue: false,
				description:
					"Use fallback highlight color when user color is not specified.",
			},
			defaultHighlightColor: {
				defaultValue: "#FFFFFF",
				description: "Fallback highlight color.",
			},
			globalCssEnabled: {
				defaultValue: false,
				description: "Enable custom global CSS.",
			},
			globalCssAutoDisable: {
				defaultValue: true,
				description: "Disable global CSS when a profile has custom CSS.",
			},
		};

		VerifiedUsers = [];

		constructor() {
			this.VerifiedUsers =
				JSON.parse(localStorage.getItem(this.LocalStorageUsers)) ?? [];

			const settingsInLocalStorage =
				JSON.parse(localStorage.getItem(this.LocalStorageSettings)) ?? {};

			for (const [key, value] of Object.entries(settingsInLocalStorage)) {
				if (!this.Options[key]) {
					continue;
				}
				this.Options[key].value = value.value;
			}
		}

		getOptionValue(object) {
			if (object.value === "") {
				return object.defaultValue;
			}
			return object.value ?? object.defaultValue;
		}

		verifyUser(username) {
			if (this.VerifiedUsers.find((user) => user.username === username)) {
				return;
			}

			this.VerifiedUsers.push({ username });
			localStorage.setItem(
				this.LocalStorageUsers,
				JSON.stringify(this.VerifiedUsers)
			);
		}

		updateUserOption(username, key, value) {
			this.VerifiedUsers = this.VerifiedUsers.map((u) =>
				u.username === username
					? {
							...u,
							[key]: value,
					  }
					: u
			);
			localStorage.setItem(
				this.LocalStorageUsers,
				JSON.stringify(this.VerifiedUsers)
			);
		}

		removeUser(username) {
			this.VerifiedUsers = this.VerifiedUsers.filter(
				(user) => user.username !== username
			);
			localStorage.setItem(
				this.LocalStorageUsers,
				JSON.stringify(this.VerifiedUsers)
			);
		}

		saveSettingToLocalStorage(key, value) {
			let localSettings = JSON.parse(
				localStorage.getItem(this.LocalStorageSettings)
			);

			this.Options[key].value = value;

			if (localSettings === null) {
				const settings = {
					[key]: value,
				};
				localStorage.setItem(
					this.LocalStorageSettings,
					JSON.stringify(settings)
				);
				return;
			}

			localSettings[key] = { value };
			localStorage.setItem(
				this.LocalStorageSettings,
				JSON.stringify(localSettings)
			);
		}
	}

	class ActivityHandler {
		settings;
		constructor(settings) {
			this.settings = settings;
		}

		moveAndDisplaySubscribeButton() {
			if (
				!this.settings.getOptionValue(
					this.settings.Options.moveSubscribeButtons
				)
			) {
				return;
			}

			const subscribeButtons = document.querySelectorAll(
				"span[label='Unsubscribe'], span[label='Subscribe']"
			);
			for (const subscribeButton of subscribeButtons) {
				if (subscribeButton.parentNode.classList.contains("actions")) {
					continue;
				}

				const container = subscribeButton.parentNode.parentNode;
				const actions = container.querySelector(".actions");
				actions.append(subscribeButton);
			}
		}
	}

	class SettingsUserInterface {
		Settings;
		StyleHandler;
		globalCSS;
		AnilistBlue = "120, 180, 255";

		constructor(settings, styleHandler, globalCSS) {
			this.Settings = settings;
			this.StyleHandler = styleHandler;
			this.globalCSS = globalCSS;
		}

		renderSettingsUi() {
			const container = document.querySelector(
				".settings.container > .content"
			);
			const settingsContainer = document.createElement("div");
			settingsContainer.setAttribute("id", "voidverified-settings");
			this.renderSettingsHeader(settingsContainer);

			const settingsListContainer = document.createElement("div");
			settingsListContainer.style.display = "flex";
			settingsListContainer.style.flexDirection = "column";
			settingsListContainer.style.gap = "5px";
			for (const [key, setting] of Object.entries(this.Settings.Options)) {
				this.renderSetting(setting, settingsListContainer, key);
			}

			settingsContainer.append(settingsListContainer);

			this.renderUserTable(settingsContainer);

			this.renderCustomCssEditor(settingsContainer);

			container.append(settingsContainer);
		}

		removeSettingsUi() {
			const settings = document.querySelector("#voidverified-settings");
			settings?.remove();
		}

		renderSettingsHeader(settingsContainer) {
			const headerContainer = document.createElement("div");
			const header = document.createElement("h1");
			header.style.marginTop = "30px";
			header.innerText = "VoidVerified Settings";

			const versionInfo = document.createElement("p");
			versionInfo.append("Version: ");
			const versionNumber = document.createElement("span");
			versionNumber.style.color = `rgb(${this.AnilistBlue})`;
			versionNumber.append(this.Settings.Version);

			versionInfo.append(versionNumber);

			headerContainer.append(header);
			headerContainer.append(versionInfo);
			settingsContainer.append(headerContainer);
		}

		renderUserTable(settingsContainer) {
			const oldTableContainer = document.querySelector(
				"#void-verified-user-table"
			);
			const tableContainer =
				oldTableContainer ?? document.createElement("div");
			tableContainer.innerHTML = "";

			tableContainer.setAttribute("id", "void-verified-user-table");

			const table = document.createElement("table");
			const head = document.createElement("thead");
			const headrow = document.createElement("tr");
			headrow.append(this.createCell("Username", "th"));
			headrow.append(this.createCell("Sign", "th"));
			headrow.append(this.createCell("Color", "th"));

			head.append(headrow);

			const body = document.createElement("tbody");

			for (const user of this.Settings.VerifiedUsers) {
				body.append(this.createUserRow(user));
			}

			table.append(head);
			table.append(body);
			tableContainer.append(table);

			const inputForm = document.createElement("form");
			inputForm.addEventListener("submit", (event) =>
				this.handleVerifyUserForm(event, this.Settings)
			);
			const label = document.createElement("label");
			label.innerText = "Add user";
			inputForm.append(label);
			const textInput = document.createElement("input");
			textInput.setAttribute("id", "voidverified-add-user");

			inputForm.append(textInput);
			tableContainer.append(inputForm);

			oldTableContainer || settingsContainer.append(tableContainer);
		}

		createUserRow(user) {
			const row = document.createElement("tr");
			const userLink = document.createElement("a");
			userLink.innerText = user.username;
			userLink.setAttribute(
				"href",
				`https://anilist.co/user/${user.username}/`
			);
			userLink.setAttribute("target", "_blank");
			row.append(this.createCell(userLink));

			const signInput = document.createElement("input");
			signInput.value = user.sign ?? "";
			signInput.style.width = "100px";
			signInput.addEventListener("input", (event) =>
				this.updateUserOption(user.username, "sign", event.target.value)
			);
			const signCell = this.createCell(signInput);
			signCell.append(
				this.createUserCheckbox(
					user.enabledForUsername,
					user.username,
					"enabledForUsername",
					this.Settings.getOptionValue(
						this.Settings.Options.enabledForUsername
					)
				)
			);

			row.append(this.createCell(signCell));

			const colorInput = document.createElement("input");
			colorInput.setAttribute("type", "color");
			colorInput.style.border = "0";
			colorInput.style.height = "24px";
			colorInput.style.width = "40px";
			colorInput.style.padding = "0";
			colorInput.style.backgroundColor = "unset";
			colorInput.value = this.getUserColorPickerColor(user);
			colorInput.addEventListener(
				"change",
				(event) => this.handleUserColorChange(event, user.username),
				false
			);

			const colorInputContainer = document.createElement("span");

			const colorCell = this.createCell(colorInput);

			colorInputContainer.append(
				this.createUserCheckbox(
					user.copyColorFromProfile,
					user.username,
					"copyColorFromProfile",
					this.Settings.getOptionValue(
						this.Settings.Options.copyColorFromProfile
					)
				)
			);

			colorInputContainer.append(
				this.createUserCheckbox(
					user.highlightEnabled,
					user.username,
					"highlightEnabled",
					this.Settings.getOptionValue(
						this.Settings.Options.highlightEnabled
					)
				)
			);

			colorInputContainer.append(
				this.createUserCheckbox(
					user.highlightEnabledForReplies,
					user.username,
					"highlightEnabledForReplies",
					this.Settings.getOptionValue(
						this.Settings.Options.highlightEnabledForReplies
					)
				)
			);

			colorCell.append(colorInputContainer);

			const resetColorBtn = document.createElement("button");
			resetColorBtn.innerText = "🔄";
			resetColorBtn.addEventListener("click", () =>
				this.handleUserColorReset(user.username)
			);

			colorCell.append(resetColorBtn);
			row.append(colorCell);

			const deleteButton = document.createElement("button");
			deleteButton.innerText = "❌";
			deleteButton.addEventListener("click", () =>
				this.removeUser(user.username)
			);
			row.append(this.createCell(deleteButton));
			return row;
		}

		getUserColorPickerColor(user) {
			if (user.colorOverride) {
				return user.colorOverride;
			}

			if (
				user.color &&
				(user.copyColorFromProfile ||
					this.Settings.getOptionValue(
						this.Settings.Options.copyColorFromProfile
					))
			) {
				return this.rgbToHex(user.color);
			}

			if (
				this.Settings.getOptionValue(
					this.Settings.Options.useDefaultHighlightColor
				)
			) {
				return this.Settings.getOptionValue(
					this.Settings.Options.defaultHighlightColor
				);
			}

			return this.rgbToHex(this.AnilistBlue);
		}

		createUserCheckbox(isChecked, username, settingKey, disabled) {
			const checkbox = document.createElement("input");
			if (disabled) {
				checkbox.setAttribute("disabled", "");
			}

			checkbox.setAttribute("type", "checkbox");
			checkbox.checked = isChecked;
			checkbox.addEventListener("change", (event) => {
				this.updateUserOption(username, settingKey, event.target.checked);
				this.refreshUserTable();
			});

			checkbox.style.marginLeft = "5px";

			checkbox.title = this.Settings.Options[settingKey].description;
			return checkbox;
		}

		handleUserColorReset(username) {
			this.updateUserOption(username, "colorOverride", undefined);
			this.refreshUserTable();
		}

		handleUserColorChange(event, username) {
			const color = event.target.value;
			this.updateUserOption(username, "colorOverride", color);
		}

		handleVerifyUserForm(event, settings) {
			event.preventDefault();

			const usernameInput = document.getElementById("voidverified-add-user");
			const username = usernameInput.value;
			settings.verifyUser(username);
			usernameInput.value = "";
			this.refreshUserTable();
		}

		refreshUserTable() {
			const container = document.querySelector(
				".settings.container > .content"
			);
			this.renderUserTable(container);
		}

		updateUserOption(username, key, value) {
			this.Settings.updateUserOption(username, key, value);
			this.StyleHandler.refreshStyles();
		}

		removeUser(username) {
			this.Settings.removeUser(username);
			this.refreshUserTable();
			this.StyleHandler.refreshStyles();
		}

		verifyUser(username) {
			this.Settings.verifyUser(username);
			this.StyleHandler.refreshStyles();
		}

		createCell(content, elementType = "td") {
			const cell = document.createElement(elementType);
			cell.append(content);
			return cell;
		}

		renderSetting(setting, settingsContainer, settingKey, disabled = false) {
			const value = this.Settings.getOptionValue(setting);
			const type = typeof value;

			const container = document.createElement("div");
			const input = document.createElement("input");

			if (type === "boolean") {
				input.setAttribute("type", "checkbox");
			} else if (settingKey == "defaultHighlightColor") {
				input.setAttribute("type", "color");
				input.style.border = "0";
				input.style.height = "15px";
				input.style.width = "25px";
				input.style.padding = "0";
				input.style.backgroundColor = "unset";
			} else if (type === "string") {
				input.setAttribute("type", "text");
				input.style.width = "50px";
			}

			if (disabled) {
				input.setAttribute("disabled", "");
			}

			input.setAttribute("id", settingKey);
			input.addEventListener("change", (event) =>
				this.handleOption(event, settingKey, type)
			);

			if (type === "boolean" && value) {
				input.setAttribute("checked", true);
			} else if (type === "string") {
				input.value = value;
			}

			container.append(input);

			const label = document.createElement("label");
			label.setAttribute("for", settingKey);
			label.innerText = setting.description;
			label.style.marginLeft = "5px";
			container.append(label);
			settingsContainer.append(container);
		}

		handleOption(event, settingKey, type) {
			const value =
				type === "boolean" ? event.target.checked : event.target.value;
			this.Settings.saveSettingToLocalStorage(settingKey, value);
			this.StyleHandler.refreshStyles();
			this.refreshUserTable();
		}

		renderCustomCssEditor(settingsContainer) {
			const container = document.createElement("div");
			const label = document.createElement("label");
			label.innerText = "Custom Global CSS";
			label.setAttribute("for", "void-verified-global-css-editor");
			label.style.marginTop = "20px";
			label.style.fontSize = "2rem";
			label.style.display = "inline-block";
			container.append(label);

			const textarea = document.createElement("textarea");
			textarea.setAttribute("id", "void-verified-global-css-editor");

			textarea.value = this.globalCSS.css;
			textarea.style.width = "100%";
			textarea.style.height = "200px";
			textarea.style.resize = "vertical";
			textarea.style.background = "#14191f";
			textarea.style.color = "white";

			textarea.addEventListener("change", (event) => {
				this.handleCustomCssEditor(event, this);
			});

			container.append(textarea);

			const notice = document.createElement("div");
			notice.innerText =
				"Please note that Custom CSS is disabled in the settings. \nIn the event that you accidentally disable rendering of critical parts of AniList, navigate to the settings by URL";
			notice.style.fontSize = "11px";
			container.append(notice);

			settingsContainer.append(container);
		}

		handleCustomCssEditor(event, settingsUi) {
			const value = event.target.value;
			settingsUi.globalCSS.updateCss(value);
		}

		rgbToHex(rgb) {
			const [r, g, b] = rgb.split(",");
			const hex = this.generateHex(r, g, b);
			return hex;
		}

		generateHex(r, g, b) {
			return (
				"#" +
				[r, g, b]
					.map((x) => {
						const hex = Number(x).toString(16);
						return hex.length === 1 ? "0" + hex : hex;
					})
					.join("")
			);
		}
	}

	class IntervalScriptHandler {
		styleHandler;
		settingsUi;
		activityHandler;
		settings;
		globalCSS;
		constructor(settings) {
			this.settings = settings;

			this.styleHandler = new StyleHandler(settings);
			this.globalCSS = new GlobalCSS(settings);
			this.settingsUi = new SettingsUserInterface(
				settings,
				this.styleHandler,
				this.globalCSS
			);
			this.activityHandler = new ActivityHandler(settings);
		}

		currentPath = "";
		evaluationIntervalInSeconds = 1;
		hasPathChanged(path) {
			if (path === this.currentPath) {
				return false;
			}
			this.currentPath = path;
			return true;
		}

		handleIntervalScripts(intervalScriptHandler) {
			const path = window.location.pathname;

			intervalScriptHandler.activityHandler.moveAndDisplaySubscribeButton();
			intervalScriptHandler.globalCSS.clearCssForProfile();

			if (path === "/home") {
				intervalScriptHandler.styleHandler.refreshHomePage();
			}

			if (!path.startsWith("/settings/developer")) {
				intervalScriptHandler.settingsUi.removeSettingsUi();
			}

			if (!intervalScriptHandler.hasPathChanged(path)) {
				return;
			}

			intervalScriptHandler.styleHandler.clearProfileVerify();
			intervalScriptHandler.globalCSS.createCss();

			if (path.startsWith("/user/")) {
				intervalScriptHandler.styleHandler.verifyProfile();
				intervalScriptHandler.styleHandler.copyUserColor();
				return;
			}

			if (path.startsWith("/settings/developer")) {
				intervalScriptHandler.settingsUi.renderSettingsUi();
				return;
			}
		}

		enableScriptIntervalHandling() {
			setInterval(() => {
				this.handleIntervalScripts(this);
			}, this.evaluationIntervalInSeconds * 1000);
		}
	}

	const settings = new Settings();
	const styleHandler = new StyleHandler(settings);
	const intervalScriptHandler = new IntervalScriptHandler(settings);

	styleHandler.refreshStyles();
	intervalScriptHandler.enableScriptIntervalHandling();

	console.log(`VoidVerified ${settings.Version} loaded.`);

})();