VoidVerified

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

当前为 2023-09-18 提交的版本,查看 最新版本

// ==UserScript==
// @name         VoidVerified
// @namespace    http://tampermonkey.net/
// @version      0.2.0
// @description  Display a verified sign next to user's name in AniList.
// @author       voidnyan
// @match        https://anilist.co/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';
    const version = "0.2.0";
    const localStorageConfigKey = "VoidVerificationConfig";
    const usernameSelector = ["a.name"];
    const displaySaveButton = false;

    const config = JSON.parse(window.localStorage.getItem(localStorageConfigKey));

    const verifiedDefaults = {
        enableForProfile: true,
        username: {
            enabled: true,
            enabledForReplies: true,
            color: "white",
            sign: "✔",
            title: "Verified",
        },
        highlight: {
            enabled: true,
            enabledForReplies: true,
            color: undefined,
            size: "5px",
        }
    };

    const verifiedUsersDefault = [
        {
            username: "voidnyan",
            color: "green",
            sign: "💻",
            title: "devnyan"
        },
//      "anotheruser"
    ].map(u => typeof u === "string" ? {username: u} : u);

    displaySaveButton && renderSaveButton();

    const verified = config?.verified ?? verifiedDefaults;
    const verifiedUsers = config?.verifiedUsers ?? verifiedUsersDefault;

    const verifiedUsersArray = [
        ...verifiedUsers.map(user => user.username)
    ];

    const observer = new MutationObserver(observeMutations);
    observer.observe(document.body, {childList: true, subtree: true});

    function observeMutations(mutations) {
        for (const mutation of mutations) {
            if (mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(handleVerified);
            }
        }
    }

    function handleVerified(node){
        if (!(node instanceof HTMLElement)) {
            return;
        }

        switch (true){
            case node.matches("div.reply"):
                handleReply(node);
                break;
            case node.matches("div.activity-anime_list"):
            case node.matches("div.activity-manga_list"):
            case node.matches("div.container"):
                handleListActivity(node);
                break;
            case node.matches("div.activity-text"):
            case node.matches("div.activity-message"):
                handleTextActivity(node);
                break;
            case node.matches("div.user"):
                handleProfile(node);
                break;
        }
    }

    function handleReply(node) {
        const username = node.querySelector(usernameSelector);
        addVerifiedMarkToReply(username);
        highlightReply(node, username);
    }

    function handleListActivity(node) {
        const isProfileActivity = node.querySelector(".small") !== null;

        if (isProfileActivity) {
            return;
        }

        const username = node.querySelector(usernameSelector);

        addVerifiedMark(username);
        highlightActivity(node, username);
    }

    function handleTextActivity(node) {
        const username = node.querySelector(usernameSelector);
        addVerifiedMark(username);
        highlightActivity(node, username);
    }

    function handleProfile(){
        if (!verified.enableForProfile){
            return;
        }

        const username = document.querySelector("h1.name");
        addVerifiedMarkToProfile(username);
    }

    function addVerifiedMark(username) {
        if (!isVerifiedUser(username) || !verified.username.enabled) {
            return;
        }
        const span = createMarkElement(username);
        span.style.width = "min-content";

        username.after(span);
    }

    function addVerifiedMarkToReply(username) {
        if (!isVerifiedUser(username) || !verified.username.enabledForReplies) {
            return;
        }

        const span = createMarkElement(username);
        span.style.display = "inline-block";
        span.style.verticalAlign = "top";
        span.style.lineHeight = "25px";
        span.style.height = "25px";

        username.after(span);
    }

    function addVerifiedMarkToProfile(username) {
        if (!isVerifiedUser(username)) {
            return;
        }

        const span = document.createElement("span");
        span.innerHTML = verified.username.sign;
        span.title = verified.username.title;
        span.style.marginLeft = "6px";

        username.after(span);
    }

    function getLinkColor(username){
        var linkColor = getComputedStyle(username).getPropertyValue("--color-blue");
        return `rgb(${linkColor})`;
    }

    function createMarkElement(username){
        const span = document.createElement("span");
        const dataset = username.dataset;
        const id = Object.keys(dataset)[0];
        span.setAttribute(`data-${id}`, "");

        span.style.color = verified.username.color ?? getLinkColor(username);
        span.style.marginLeft = "6px";
        span.innerHTML = getSign(username);
        span.title = getTitle(username);

        return span;
    }

    function highlightActivity(node, username) {
        if (!verified.highlight.enabled || !isVerifiedUser(username)) {
            return;
        };

        const wrapper = node.querySelector("div.wrap");
        wrapper.style.marginRight = `-${verified.highlight.size}`;
        wrapper.style.borderRight = `${verified.highlight.size} solid ${getColor(username)}`;
    }

    function highlightReply(node, username) {
        if (!verified.highlight.enabledForReplies || !isVerifiedUser(username)) {
            return;
        }

        node.style.marginRight = `-${verified.highlight.size}`;
        node.style.borderRight = `${verified.highlight.size} solid ${getColor(username)}`;
    }

    function isVerifiedUser(username) {
        const isVerifiedUser = verifiedUsersArray.includes(username?.innerHTML.trim());
        return isVerifiedUser;
    }

    function getColor(username) {
        const user = getUserObject(username);
        const color = user?.color ?? verified.highlight.color ?? getLinkColor(username);
        return color;
    }

    function getSign(username){
        const user = getUserObject(username);
        const sign = user?.sign ?? verified.username.sign;
        return sign;
    }

    function getTitle(username) {
        const user = getUserObject(username);
        const title = user?.title ?? verified.username.title;
        return title;
    }

    function getUserObject(username){
        var usernameAsString = username.innerHTML.trim();
        const user = verifiedUsers.find(u => u.username === usernameAsString);
        return user;
    }

    function overrideLocalStorageConfig(verifiedOverride, verifiedUsersOverride) {
        const config = {
            verified: verifiedOverride,
            verifiedUsers: verifiedUsersOverride
        }
        window.localStorage.setItem(localStorageConfigKey, JSON.stringify(config));
    }

    function saveConfigToLocalStorage(){
        const config = {
            verified,
            verifiedUsers
        };

        window.localStorage.setItem(localStorageConfigKey, JSON.stringify(config));
    }

    function renderSaveButton() {
        const footerLinks = document.querySelector("div.footer div.links");
        const button = document.createElement("button");
        button.onClick = overrideLocalStorageConfig(verifiedDefaults, verifiedUsersDefault);
        button.innerHTML = "Save Configuration";
        footerLinks.lastChild.append(button);
    }

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址