E-Hentai Uploader & Artist Blocker

One-click to block uploaders and artist tags with separate export functions.

// ==UserScript==
// @name         E-Hentai Uploader & Artist Blocker
// @name:zh-CN   E-Hentai 上传者与画师屏蔽助手
// @namespace    http://tampermonkey.net/
// @version      3.3
// @description  One-click to block uploaders and artist tags with separate export functions.
// @description:zh-CN 一键将上传者和画师Tag添加到本地屏蔽名单,并提供独立的导出功能。
// @author       Vesper233
// @license      CC BY-NC-ND 4.0
// @match        https://e-hentai.org/
// @match        https://exhentai.org/
// @match        https://e-hentai.org/?*
// @match        https://exhentai.org/?*
// @match        https://e-hentai.org/watched*
// @match        https://exhentai.org/watched*
// @match        https://e-hentai.org/g/*
// @match        https://exhentai.org/g/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_setClipboard
// @grant        GM_notification
// @grant        window.open
// ==/UserScript==

(function() {
    'use strict';

    // --- 配置 (两个独立的存储键) ---
    const UPLOADER_LIST_KEY = 'eh_blocker_uploader_list_v3'; // 上传者屏蔽名单
    const ARTIST_TAG_LIST_KEY = 'eh_blocker_artist_tag_list_v3'; // 画师Tag屏蔽名单
    const UCONFIG_URL = 'https://e-hentai.org/uconfig.php'; // 上传者设置页
    const MYTAGS_URL = 'https://e-hentai.org/mytags'; // Tag设置页

    // --- 主函数入口 ---
    (async function main() {
        if (document.getElementById('gdn')) {
            await processUploaderOnGalleryPage();
            await processArtistTags();
        } else if (document.querySelector('.itg.gltc')) {
            await processUploaderOnListPage();
        }
    })();


    // --- 页面处理函数 (与之前版本相同) ---

    async function processUploaderOnGalleryPage() {
        const uploaderDiv = document.getElementById('gdn');
        if (!uploaderDiv) return;
        const uploaderLink = uploaderDiv.querySelector('a');
        if (!uploaderLink) return;
        const uploaderName = uploaderLink.textContent.trim();
        if (!uploaderName) return;

        const blocklist = await getList(UPLOADER_LIST_KEY);
        const button = createBlockButton(uploaderName, blocklist, UPLOADER_LIST_KEY, '屏蔽上传者', '上传者');
        uploaderDiv.appendChild(button);
    }

    async function processUploaderOnListPage() {
        const blocklist = await getList(UPLOADER_LIST_KEY);
        const galleryRows = document.querySelectorAll('.itg.gltc tr[class^="gtr"]');

        galleryRows.forEach(row => {
            const uploaderCell = row.querySelector('div.glcu a');
            if (!uploaderCell) return;
            const uploaderName = uploaderCell.textContent.trim();
            if (!uploaderName) return;

            const button = createBlockButton(uploaderName, blocklist, UPLOADER_LIST_KEY, '屏蔽上传者', '上传者');
            button.style.marginLeft = '5px';
            uploaderCell.parentElement.appendChild(button);
        });
    }

    async function processArtistTags() {
        const tagTable = document.querySelector('#taglist table');
        if (!tagTable) return;

        const allRows = tagTable.querySelectorAll('tr');
        let artistTagContainer = null;
        for (const row of allRows) {
            const firstCell = row.querySelector('td:first-child');
            if (firstCell && firstCell.textContent.trim() === 'artist:') {
                artistTagContainer = row.querySelector('td:nth-child(2)');
                break;
            }
        }

        if (!artistTagContainer) return;
        const artistLinks = artistTagContainer.querySelectorAll('a');
        if (artistLinks.length === 0) return;
        const blocklist = await getList(ARTIST_TAG_LIST_KEY);

        artistLinks.forEach(link => {
            let fullTag;
            try {
                fullTag = decodeURIComponent(link.href.split('/tag/')[1].replace(/\+/g, ' '));
            } catch(e) { return; }

            const button = createBlockButton(fullTag, blocklist, ARTIST_TAG_LIST_KEY, '屏蔽画师', '画师Tag');
            button.style.marginLeft = '5px';
            link.after(button);
        });
    }

    // --- 核心功能与辅助函数 (与之前版本相同) ---

    function createBlockButton(name, blocklist, storageKey, tooltip, itemType) {
        const button = document.createElement('a');
        button.style.cssText = 'cursor:pointer; font-weight:bold;';
        button.title = tooltip;

        if (blocklist.includes(name)) {
            button.textContent = `[已屏蔽]`;
            button.style.color = '#757575';
        } else {
            button.textContent = `[屏蔽]`;
            button.style.color = '#D32F2F';
            button.addEventListener('click', async (event) => {
                event.preventDefault();
                const currentList = await getList(storageKey);
                if (currentList.includes(name)) return;
                currentList.push(name);
                await GM_setValue(storageKey, JSON.stringify(currentList));
                button.textContent = `[已添加]`;
                button.style.color = '#4CAF50';
                GM_notification({
                    title: '添加成功',
                    text: `"${name}" 已加入您的本地${itemType}屏蔽名单。`,
                    timeout: 4000
                });
            }, { once: true });
        }
        return button;
    }

    async function getList(key) {
        const storedValue = await GM_getValue(key, '[]');
        try { return JSON.parse(storedValue); } catch (e) { return []; }
    }
    
    // 通用的导出函数
    async function exportListAndJump(listKey, targetUrl, itemType) {
        const list = await getList(listKey);
        if (list.length === 0) {
            GM_notification({ title: '提示', text: `您的${itemType}屏蔽名单是空的。`, timeout: 3000 });
            return;
        }
        const sortedList = [...new Set(list)].sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
        const listString = sortedList.join('\n');
        try {
            await GM_setClipboard(listString, 'text');
            GM_notification({
                title: '复制成功',
                text: `已将 ${sortedList.length} 个${itemType}复制到剪贴板,即将打开设置页。`,
                timeout: 7000,
            });
            window.open(targetUrl, '_blank');
        } catch (error) {
            GM_notification({
                title: '自动复制失败!',
                text: `请从新弹出的窗口中手动复制您的${itemType}名单。`,
                timeout: 10000
            });
            showManualCopyWindow(listString, itemType);
        }
    }
    
    function showManualCopyWindow(text, itemType) {
        // ... (此处省略和之前版本相同的函数代码)
        const container = document.createElement('div');
        container.style.cssText = 'position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background-color:#333; color:#fff; padding:20px; border:2px solid #555; border-radius:8px; z-index:99999; box-shadow:0 0 15px rgba(0,0,0,0.7);';
        container.innerHTML = `<h3 style="margin-top:0; border-bottom:1px solid #555; padding-bottom:10px;">请手动复制${itemType}名单</h3><textarea readonly style="width:400px; height:300px; background-color:#222; color:#ddd; border:1px solid #444;">${text}</textarea><br><button style="margin-top:10px; padding:5px 10px;">关闭</button>`;
        document.body.appendChild(container);
        container.querySelector('textarea').select();
        container.querySelector('button').onclick = () => container.remove();
    }

    // --- Tampermonkey 菜单命令 (已修正) ---

    GM_registerMenuCommand('⬆️ 导出屏蔽上传者名单', () => {
        exportListAndJump(UPLOADER_LIST_KEY, UCONFIG_URL, '上传者');
    });

    GM_registerMenuCommand('🎨 导出屏蔽画师Tag', () => {
        exportListAndJump(ARTIST_TAG_LIST_KEY, MYTAGS_URL, '画师Tag');
    });

    GM_registerMenuCommand('🗑️ 清空本地所有记录', async () => {
        if (confirm('【高危操作】您确定要永久删除本地记录的所有【上传者】和【画师Tag】吗?此操作不可撤销!')) {
            await GM_setValue(UPLOADER_LIST_KEY, JSON.stringify([]));
            await GM_setValue(ARTIST_TAG_LIST_KEY, JSON.stringify([]));
            GM_notification({ title: '操作成功', text: '本地所有屏蔽记录已清空。', timeout: 4000 });
            location.reload();
        }
    });

})();