PornHub | Hide Block List Models

This script will hide models that you add to block list

// ==UserScript==
// @name            PornHub | Hide Block List Models
// @namespace       http://tampermonkey.net/
// @version         1.0.3
// @description     This script will hide models that you add to block list
// @author          extra.lewd
// @match           https://*.pornhub.com/*
// @exclude         https://*.pornhub.com/model/*
// @exclude         https://*.pornhub.com/channels/*
// @exclude         https://*.pornhub.com/categories
// @icon            https://pornhub.com/favicon.ico
// @run-at          document-end
// @license         CC BY-NC-ND 4.0
// @license-url     https://creativecommons.org/licenses/by-nc-nd/4.0/
// @homepage        https://discord.gg/TcWrM6pXWD
// @homepageURL     https://discord.gg/TcWrM6pXWD
// @website         https://discord.gg/TcWrM6pXWD
// @source          https://discord.gg/TcWrM6pXWD
// @compatible      firefox
// @compatible      chrome
// @compatible      opera
// @compatible      safari
// @compatible      edge
// @grant           GM_addStyle
// @grant           GM_registerMenuCommand
// @grant           GM.getValue
// @grant           GM.setValue
// ==/UserScript==
(function () {
    'use strict';
    class PCache {
        cache;
        storageKey = "blocked_phub";
        constructor(cache = new Map()) {
            this.cache = cache;
            this.load();
        }
        has(key) {
            return this.cache.has(key);
        }
        get(key) {
            const cache = this.cache.get(key);
            if (!!cache && cache.ttl > Date.now()) {
                return cache.value;
            }
            this.del(key);
        }
        set(key, value, ttl) {
            this.cache.set(key, {
                value,
                ttl: Date.now() + ttl
            });
            this.save();
        }
        del(key) {
            this.cache.delete(key);
            this.save();
        }
        clear() {
            this.cache.clear();
            this.save();
        }
        async wrap(key, callback, ttl) {
            const cache = this.cache.get(key);
            if (cache) {
                return cache.value;
            }
            const value = await callback();
            this.set(key, value, ttl);
            return value;
        }
        save() {
            localStorage.setItem(this.storageKey, JSON.stringify([...this.cache.entries()]));
        }
        load() {
            const cache = localStorage.getItem(this.storageKey);
            if (cache) {
                try {
                    this.cache = new Map(JSON.parse(cache));
                }
                catch (error) {
                }
            }
        }
    }
    GM_addStyle(`
            .phub-blur {
                filter: blur(10px);
            }

            .phub-hide {
                display: none !important;
            }

            .phub-blur:hover,.phub-blur:active,.phub-blur:focus {
            filter: none;
            }
            `);
    const cache = new PCache();
    let busy = false;
    const P_CLEAR = "phub-clear";
    const P_SKIP = "phub-skip";
    const P_BLUR = "phub-blur";
    const P_HIDE = "phub-hide";
    scan();
    // todo: add also hide by .languageType span, but it require phub spoken tag on videos
    setInterval(scan, 10_000);
    async function scan() {
        if (!checkAuth())
            return; // we don't have block list, cuz we don't logined yet
        if (busy)
            return;
        try {
            busy = true;
            const videos = document.querySelectorAll(`li.videoblock:not(.${P_CLEAR}):not(.${P_HIDE}):not(.${P_BLUR}):not(.${P_SKIP}),li[data-video-id]:not(.${P_CLEAR}):not(.${P_HIDE}):not(.${P_BLUR}):not(.${P_SKIP})`);
            if (videos.length < 1) {
                return;
            }
            const mode = await GM.getValue("phub_mode", 0);
            for (const video of videos) {
                try {
                    // note: some (as germancouple1) has only "span.username" without href / url instead of "a". Devs?
                    const path = video.querySelector(".usernameWrap")?.querySelector("a")?.getAttribute("href") || video.querySelector(".uploaderLink")?.getAttribute("href");
                    if (!path) {
                        console.warn("Skip", video);
                        video.classList.add(P_SKIP);
                        continue;
                    }
                    ;
                    const url = new URL(path, window.location.href);
                    const blocked = await cache.wrap(url.href, async () => {
                        return fetch(url, {
                            credentials: "include"
                        })
                            .then(r => r.text())
                            .then(isBlocked);
                    }, 1000 * 60 * 60); // 1 hour
                    video.classList.add(blocked
                        ? mode
                            ? P_HIDE
                            : P_BLUR
                        : P_CLEAR);
                }
                catch (error) {
                    console.error(error, video);
                }
            }
        }
        catch (error) {
            console.error(error);
        }
        finally {
            busy = false;
        }
    }
    // note: phub developers clowns a bit, so...
    function isBlocked(html) {
        const p = new DOMParser();
        const doc = p.parseFromString(html, "text/html");
        // Currently, support mobiles only on eng lang
        return !doc.querySelector(".addFriendButton:not(.disabled)") && !!doc.querySelector(".addFriendButton") || doc.querySelector(".blockUserBtn .textContent")?.textContent === "Unblock User";
    }
    function checkAuth() {
        return !document.querySelector("#headerLoginLink");
    }
    GM_registerMenuCommand("Blur Blocked (Default)", () => {
        GM.setValue("phub_mode", 0);
    });
    GM_registerMenuCommand("Hide Blocked", () => {
        GM.setValue("phub_mode", 1);
    });
    GM_registerMenuCommand("Clear Cache", () => {
        cache.clear();
    });
    // GM_registerMenuCommand("Check Block (Dev only)", () => {
    //     const isBlocked = !document.querySelector(".addFriendButton:not(.disabled)") && !!document.querySelector(".addFriendButton");
    //     GM.notification({
    //         text: `Target on current page ${isBlocked ? "IN" : "NOT in"} block list.`,
    //         title: "Debug",
    //     })
    // })
})();