Filtering "Incomplete" for PterClub

支持“非完结”种子主副标题过滤 + 精确识别制作组(仅匹配以 - 开头并出现在标题结尾的情况)

// ==UserScript==
// @name         Filtering "Incomplete" for PterClub
// @namespace    http://tampermonkey.net/
// @version      3.2
// @description  支持“非完结”种子主副标题过滤 + 精确识别制作组(仅匹配以 - 开头并出现在标题结尾的情况)
// @author       @Zuoans
// @match        https://pterclub.com/torrents.php* 
// @license      MIT
// @grant        GM_addStyle
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    // ====================== 样式定义 ======================
    GM_addStyle(`
        :root {
            --blue-start: rgba(51, 160, 200, 0.6);
            --blue-mid: rgba(51, 160, 200, 0.1);
            --purple-start: rgba(147, 112, 219, 0.5);
            --purple-mid: rgba(147, 112, 219, 0.1);
        }

        /* incomplete样式 */
        table.torrentname > tbody > tr.tm-incomplete:not(.tm-group) {
            background: linear-gradient(to right, var(--blue-start) 0%, var(--blue-mid) 50%, rgba(255,255,255,0) 90%) !important;
        }

        /* 制作组样式 */
        table.torrentname > tbody > tr.tm-group:not(.tm-incomplete) {
            background: linear-gradient(to right, var(--purple-start) 0%, var(--purple-mid) 50%, rgba(255,255,255,0) 90%) !important;
        }

        /* incomplete&制作组双重标记样式 */
        table.torrentname > tbody > tr.tm-incomplete.tm-group {
            background: linear-gradient(to right, var(--purple-start) 0%, var(--purple-mid) 50%, rgba(255,255,255,0) 90%) !important;
        }

        /* 文字保护 */
        table.torrentname [class*="tm-"] td {
            text-shadow: 0 1px 1px rgba(255,255,255,0.1),
                         0 -1px 1px rgba(0,0,0,0.1) !important;
        }
    `);

    // 目标制作组列表(新增 PTerWEB)
    const targetGroups = [
        'AdBlue', 'AREY', 'BdC', 'BMDru', 'CatEDU', 'c0kE',
        'doraemon', 'JKCT', 'KMX', 'Lislander', 'RO',
        'Telesto', 'XPcl', 'ZTR', 'PTerWEB'
    ];

    // 辅助函数:转义正则特殊字符
    function escapeRegExp(string) {
        return string.replace(/[.*+?^${}()|[$$\$$]/g, '\\$&');
    }

    // ✅ 制作组正则:只匹配以 - 开头,并出现在标题结尾的组名(如 -ZTR)
    const groupRegex = new RegExp(
        `-(?:${targetGroups.map(g => escapeRegExp(g)).join('|')})(?=[\\s\\W]|$)`, 
        'i'
    );

    // 排除关键词正则(包含 Complete / Fin / FIN+ 的行不能被标记为 non-complete)
    const excludeKeywordRegex = /\b(?:Complete|Fin)(?!\w)|FIN\+/gi;

    // 新增:incomplete 正则(仅检测 SxxExx 或 第X集,不考虑 EP)
    const incompleteRegex = /(?:^|\s)(S\d+E\d+|第\d+集)(?=\s|$)/i;

    function safeHighlight() {
        const rows = document.querySelectorAll('table.torrentname > tbody > tr');

        rows.forEach(tr => {
            tr.classList.remove('tm-incomplete', 'tm-group');

            // 获取主标题
            const titleLink = tr.querySelector('.torrentname a');
            if (!titleLink) return;

            let mainTitle = titleLink.innerText.trim();

            // 获取副标题
            let subTitleEl = tr.querySelector('.torrentname div[style*="margin-top"] span');
            let subTitle = subTitleEl ? subTitleEl.innerText.trim() : '';

            let fullTitle = (mainTitle + ' ' + subTitle).trim();

            // 检查是否包含排除关键词
            const hasExclusion = excludeKeywordRegex.test(fullTitle);

            // 判断是否为非完结(主副标题中任意一个满足即可)
            const isIncomplete = !hasExclusion && incompleteRegex.test(fullTitle);

            // 判断是否为目标制作组(✅ 仅匹配以 - 开头并出现在结尾的组名)
            const isTargetGroup = groupRegex.test(fullTitle);

            if (isIncomplete) tr.classList.add('tm-incomplete');
            if (isTargetGroup) tr.classList.add('tm-group');
        });
    }

    // MutationObserver 设置
    const observer = new MutationObserver(mutations => {
        if (mutations.some(mut => mut.addedNodes.length > 0)) {
            safeHighlight();
        }
    });

    document.addEventListener('DOMContentLoaded', () => {
        safeHighlight();
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });

    // 页面卸载时清理
    window.addEventListener('unload', () => {
        document.querySelectorAll('tr').forEach(el => {
            el.className = el.className.replace(/\btm-\w+\b/g, '');
        });
    });

})();