Factorio: Block MODs and MOD authors

This script provides buttons to block MODs and MOD authors on the MOD portal

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Factorio: Block MODs and MOD authors
// @name:ja      Factirio: MODおよびMOD作者をブロック
// @namespace    https://2238.club/
// @version      1.0.1
// @description  This script provides buttons to block MODs and MOD authors on the MOD portal
// @description:ja MODポータルに個々のMODや特定の作者のMODを一式ブロックするボタンを追加します
// @author       sakuro
// @match        https://mods.factorio.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=factorio.com
// @license    MIT
// ==/UserScript==

(function() {
    'use strict';

    const uniq = (array) => [...new Set(array)];

    class BlockList {
        constructor(key) {
            this.key = key;
            this.load();
        }

        load() {
            const rawData = localStorage.getItem(this.key);
            this.list = rawData ? JSON.parse(rawData) : [];
        }

        save() {
            localStorage.setItem(this.key, JSON.stringify(this.list));
        }

        add(item) {
            this.list = uniq([item, ...this.list]);
            this.save();
        }

        isBlocked(item) {
            return this.list.includes(item);
        }

        clear() {
            this.list = [];
            localStorage.removeItem(this.key);
        }
    };

    const getNames = (anchor, prefix) => {
        try {
            if (!(anchor instanceof HTMLAnchorElement)) {
                throw new Error('anchor must be an HTMLAnchorElement');
            }
            if (!prefix.startsWith('/') || !prefix.endsWith('/')) {
                throw new Error('prefix must start and end with /');
            }

            const textContent = anchor.textContent.trim() || '';
            const href = anchor.getAttribute('href') || '';
            const pathname = new URL(href, document.location).pathname;
            const strippedHref = pathname.startsWith(prefix) ? pathname.substring(prefix.length) : pathname;

            return [textContent, strippedHref];
        } catch (error) {
            console.error({anchor, prefix, error});
            return [];
        }
    }


    const getMod = (mod) => getNames(mod.querySelector('a[href^="/mod/"]'), '/mod/');
    const getAuthor = (mod) => getNames(mod.querySelector('a[href^="/user/"]'), '/user/');

    const blockedMods = new BlockList('blocked mods');
    const blockedAuthors = new BlockList('blocked authors');

    const modList = document.querySelector('.mod-list');

    const onBlockMod = (mod) => {
        return (evt) => {
            const [name, id] = getMod(mod, '/mod/');
            blockedMods.add(id);
            refresh(modList);
        };
    };

    const onBlockAuthor = (mod) => {
        return (evt) => {
            const [name, id] = getAuthor(mod, '/user/');
            blockedAuthors.add(id);
            refresh(modList);
        };
    };


    const onUnblockAll = (evt) => {
        blockedAuthors.clear();
        blockedMods.clear();
        refresh(modList);
    };

    const createButton = (iconName, tooltip, handler) => {
        const div = document.createElement('div');
        div.classList.add('mr12');

        const button = document.createElement('button');
        button.classList.add('button');
        button.style = 'min-width: 44px; line-height: 1; text-align: center;';
        button.title = tooltip;
        button.ariaLabel = tooltip;
        button.addEventListener('click', handler);

        const icon = document.createElement('i');
        icon.classList.add('fa');
        icon.classList.add(iconName);

        button.append(icon);
        div.append(button);

        return div;
    };

    const refresh = (list) => {
        list.querySelectorAll('.panel-inset-lighter.flex-column.p0').forEach((e) => {
            const [authorName, authorId] = getAuthor(e);
            if (! authorId) {
                console.log("author not found");
                return;
            }

            const [modName, modId] = getMod(e);
            if (! modId) {
                console.log("mod not found");
                return;
            }

            if (blockedAuthors.isBlocked(authorId)) {
                e.style.display = 'none';
                console.log(`Blocked: ${modId} by ${authorId}`);
            } else if (blockedMods.isBlocked(modId)) {
                e.style.display = 'none';
                console.log(`Blocked: ${modId}`);
            } else {
                e.style.display = 'block';
                const section = e.querySelector('.mod-download-section');
                const lastElement = section.lastElementChild;
                if (section.querySelectorAll('.mr12').length < 4) {
                    const blockModButton = createButton('fa-circle-xmark', 'Block this mod', onBlockMod(e));
                    lastElement.insertAdjacentElement('beforebegin', blockModButton);
                    const blockAuthorButton = createButton('fa-person-circle-xmark', 'Block this author', onBlockAuthor(e));
                    lastElement.insertAdjacentElement('beforebegin', blockAuthorButton);
                }
            }
        });
    };

    const unblockAllButton = createButton('fa-recycle', 'Unblock all blocked MODs and MOD authors', onUnblockAll);
    document.querySelector('.header-links').appendChild(unblockAllButton);

    refresh(modList);
}());