// ==UserScript== // @name VoidVerified // @version 1.2.0 // @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 // @downloadURL none // ==/UserScript== (function () { 'use strict'; const defaultSettings = { copyColorFromProfile: { defaultValue: true, description: "Copy user color from their profile.", }, 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.", }, quickAccessEnabled: { defaultValue: false, description: "Display quick access of users in home page.", }, }; class ColorFunctions { static hexToRgb(hex) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `${r}, ${g}, ${b}`; } static rgbToHex(rgb) { const [r, g, b] = rgb.split(","); const hex = this.generateHex(r, g, b); return hex; } static 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 AnilistAPI { apiQueryTimeoutInMinutes = 30; apiQueryTimeout = this.apiQueryTimeoutInMinutes * 60 * 1000; settings; constructor(settings) { this.settings = settings; } queryUserData() { this.#createUserQuery(); } async #createUserQuery() { let stopQueries = false; for (const user of this.#getUsersToQuery()) { if (stopQueries) { break; } stopQueries = this.#queryUser(user); } } #userQuery = ` query ($username: String) { User(name: $username) { name avatar { large } options { profileColor } } } `; #queryUser(user) { const variables = { username: user.username, }; const url = "https://graphql.anilist.co"; const options = { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify({ query: this.#userQuery, variables, }), }; let stopQueries = false; fetch(url, options) .then(this.#handleResponse) .then((data) => { const resultUser = data.User; this.settings.updateUserFromApi(user, resultUser); }) .catch((err) => { console.error(err); stopQueries = true; }); return stopQueries; } #getUsersToQuery() { if ( this.settings.getOptionValue( this.settings.options.copyColorFromProfile ) || this.settings.getOptionValue( this.settings.options.quickAccessEnabled ) ) { return this.#filterUsersByLastFetch(); } const users = this.settings.verifiedUsers.filter( (user) => user.copyColorFromProfile || user.quickAccessEnabled ); return this.#filterUsersByLastFetch(users); } #handleResponse(response) { return response.json().then((json) => { return response.ok ? json.data : Promise.reject(json); }); } #filterUsersByLastFetch(users = null) { const currentDate = new Date(); if (users) { return users.filter( (user) => !user.lastFetch || currentDate - new Date(user.lastFetch) > this.apiQueryTimeout ); } return this.settings.verifiedUsers.filter( (user) => !user.lastFetch || currentDate - new Date(user.lastFetch) > this.apiQueryTimeout ); } } class Settings { localStorageUsers = "void-verified-users"; localStorageSettings = "void-verified-settings"; version = "1.2.0"; verifiedUsers = []; options = defaultSettings; 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.toLowerCase() === username.toLowerCase() ) ) { return; } this.verifiedUsers.push({ username }); localStorage.setItem( this.localStorageUsers, JSON.stringify(this.verifiedUsers) ); const anilistAPI = new AnilistAPI(this); anilistAPI.queryUserData(); } 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) ); } updateUserFromApi(user, apiUser) { const newUser = this.#mapApiUser(user, apiUser); this.verifiedUsers = this.verifiedUsers.map((u) => u.username.toLowerCase() === user.username.toLowerCase() ? newUser : u ); localStorage.setItem( this.localStorageUsers, JSON.stringify(this.verifiedUsers) ); } #mapApiUser(user, apiUser) { let userObject = { ...user }; userObject.color = this.#handleAnilistColor( apiUser.options.profileColor ); userObject.username = apiUser.name; userObject.avatar = apiUser.avatar.large; userObject.lastFetch = new Date(); return userObject; } 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) ); } #defaultColors = [ "gray", "blue", "purple", "green", "orange", "red", "pink", ]; #defaultColorRgb = { gray: "103, 123, 148", blue: "61, 180, 242", purple: "192, 99, 255", green: "76, 202, 81", orange: "239, 136, 26", red: "225, 51, 51", pink: "252, 157, 214", }; #handleAnilistColor(color) { if (this.#defaultColors.includes(color)) { return this.#defaultColorRgb[color]; } return ColorFunctions.hexToRgb(color); } } 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 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")); headrow.append(this.#createCell("Other", "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 quickAccessCheckbox = this.#createUserCheckbox( user.quickAccessEnabled, user.username, "quickAccessEnabled", this.settings.getOptionValue( this.settings.options.quickAccessEnabled ) ); row.append(this.#createCell(quickAccessCheckbox)); 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 ColorFunctions.rgbToHex(user.color); } if ( this.settings.getOptionValue( this.settings.options.useDefaultHighlightColor ) ) { return this.settings.getOptionValue( this.settings.options.defaultHighlightColor ); } return ColorFunctions.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(); } #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); } } class QuickAccess { settings; #quickAccessId = "void-verified-quick-access"; constructor(settings) { this.settings = settings; } renderQuickAccess() { if (this.#quickAccessRendered()) { return; } if ( !this.settings.getOptionValue( this.settings.options.quickAccessEnabled ) && !this.settings.verifiedUsers.some((user) => user.quickAccessEnabled) ) { return; } const quickAccessContainer = document.createElement("div"); quickAccessContainer.setAttribute("id", this.#quickAccessId); const sectionHeader = document.createElement("div"); sectionHeader.setAttribute("class", "section-header"); const title = document.createElement("h2"); title.append("Quick Access"); sectionHeader.append(title); quickAccessContainer.append(sectionHeader); const quickAccessBody = document.createElement("div"); quickAccessBody.style = ` background: rgb(var(--color-foreground)); display: grid; grid-template-columns: repeat(auto-fill, 60px); grid-template-rows: repeat(auto-fill, 80px); gap: 15px; padding: 20px; margin-bottom: 25px; `; for (const user of this.#getQuickAccessUsers()) { quickAccessBody.append(this.#createQuickAccessLink(user)); } quickAccessContainer.append(quickAccessBody); const section = document.querySelector( ".container > .home > div:nth-child(2)" ); section.insertBefore(quickAccessContainer, section.firstChild); } #createQuickAccessLink(user) { const container = document.createElement("a"); container.style.display = "inline-block"; const link = document.createElement("a"); container.setAttribute( "href", `https://anilist.co/user/${user.username}/` ); const image = document.createElement("div"); image.style = ` background-image: url(${user.avatar}); display: flex; background-size: contain; background-repeat: no-repeat; height: 60px; width: 60px; `; container.append(image); const username = document.createElement("div"); username.append(user.username); username.style = ` display: inline-block; text-align: center; bottom: -20px; width: 100%; word-break: break-all; font-size: 1.2rem; `; container.append(username); container.append(link); return container; } #quickAccessRendered() { const quickAccess = document.getElementById(this.#quickAccessId); return quickAccess !== null; } #getQuickAccessUsers() { if ( this.settings.getOptionValue( this.settings.options.quickAccessEnabled ) ) { return this.settings.verifiedUsers; } return this.settings.verifiedUsers.filter( (user) => user.quickAccessEnabled ); } } class IntervalScriptHandler { styleHandler; settingsUi; activityHandler; settings; globalCSS; quickAccess; 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); this.quickAccess = new QuickAccess(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(); intervalScriptHandler.quickAccess.renderQuickAccess(); } 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(); const anilistAPI = new AnilistAPI(settings); anilistAPI.queryUserData(); console.log(`VoidVerified ${settings.version} loaded.`); })();