Greasy Fork

来自缓存

Greasy Fork is available in English.

PT 站点活跃度预警系统 (最终修复版)

基于剩余天数主动预警PT站点的活跃度,UI分级高亮,并在最后一天弹窗警告。目前已经在安全时间内减去了2天缓冲

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         PT 站点活跃度预警系统 (最终修复版)
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  基于剩余天数主动预警PT站点的活跃度,UI分级高亮,并在最后一天弹窗警告。目前已经在安全时间内减去了2天缓冲
// @author       Gemini & YourName
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_openInTab
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

// --- 1. 用户配置 ---
    const siteSettings = [
        // --- 重要的网站 ---
        { domain: 'pterclub.com', days: 58, name: '猫站', important: true },
        { domain: 'open.cd', days: 88, name: '皇后', important: true },
        { domain: 'ourbits.club', days: 148, name: '我堡', important: true },
        { domain: 'hdhome.org', days: 58, name: '家园', important: true },
        { domain: 'chdbits.xyz', days: 43, name: 'CHD', important: true },
        { domain: 'audiences.me', days: 98, name: '观众', important: true },   //根据用户等级修改!!!
        { domain: 'hhanclub.top', days: 28, name: '憨憨', important: true },
        { domain: 'pt.keepfrds.com', days: 33, name: '朋友', important: true },
        { domain: 'totheglory.im', days: 80, name: 'TTG', important: true },    //12周
        { domain: 'ob.m-team.cc', days: 38, name: '馒头', important: true },
        // --- 其他网站 ---
        { domain: 'kufei.org', days: 148, name: '库非' },
        { domain: 'hdtime.org', days: 148, name: 'HD时光' }, //暂时打不开网站
        { domain: '1ptba.com', days: 148, name: '1PT' },
        { domain: 'ptcafe.club', days: 178, name: '咖啡' },
        { domain: 'rousi.zip', days: 148, name: '肉丝' },
        { domain: 'ptlgs.org', days: 28, name: '劳改所' }, //超过30天不活跃的账号将被系统自动封禁。 活跃判定方法:当有未读公告时,需手动确认公告已读才算活跃;当没有未读公告时,需访问首页才算活跃。
        { domain: 'hdfans.org', days: 88, name: '红豆饭' },
        { domain: 'sunnypt.top', days: 148, name: 'Sunny' },
        { domain: 'www.agsvpt.com', days: 148, name: '末日' },
        { domain: 'qingwapt.com', days: 88, name: '青蛙' },
        { domain: 'xingtan.one', days: 58, name: '杏坛' },
        { domain: 'u2.dmhy.org', days: 88, name: 'U2动漫' },
        { domain: 'zmpt.cc', days: 43, name: '织梦' },
        { domain: 'hdkyl.in', days: 178, name: '麒麟' },
        { domain: 'ubits.club', days: 58, name: '你堡' }
    ];
    // --------------------


    // --- 2. 脚本核心逻辑 ---
    const STORAGE_KEY = 'pt_tracker_data';
    const GLOBAL_ALERT_KEY = 'pt_tracker_global_alert_date';

    async function loadData(key, defaultValue) { return await GM_getValue(key, defaultValue); }
    async function saveData(key, value) { await GM_setValue(key, value); }

    async function checkAndUpdateCurrentSite() {
        const storedData = await loadData(STORAGE_KEY, {});
        const currentHost = window.location.hostname;
        for (const site of siteSettings) {
            if (currentHost === site.domain || currentHost.endsWith('.' + site.domain)) {
                const now = new Date().toISOString();
                if (storedData[site.domain] !== now) {
                    storedData[site.domain] = now;
                    await saveData(STORAGE_KEY, storedData);
                    console.log(`[PT Tracker] 已刷新站点: ${site.name || site.domain}`);
                }
                return;
            }
        }
    }

    // --- 3. UI 创建与更新 (已修复) ---
    function createUI() {
        GM_addStyle(`
            #pt-tracker-container { position: fixed; top: 10px; right: 10px; z-index: 2147483647; }
            #pt-tracker-ui { padding: 2px 8px; min-width: 20px; height: 20px; background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 4px; display: flex; justify-content: center; align-items: center; font-size: 12px; font-family: monospace; cursor: pointer; box-shadow: 0 0 5px rgba(0,0,0,0.2); }
            #pt-tracker-tooltip { visibility: hidden; position: absolute; top: 100%; right: 0; padding-top: 5px; background-color: transparent; opacity: 0; transition: opacity 0.3s, visibility 0.3s; pointer-events: none; }
            #pt-tracker-tooltip-content { background-color: white; color: black; padding: 10px; border-radius: 4px; font-size: 12px; font-family: sans-serif; white-space: nowrap; text-align: left; border: 1px solid #ddd; max-height: 80vh; overflow-y: auto; pointer-events: auto; box-shadow: 0 2px 8px rgba(0,0,0,0.15); }
            #pt-tracker-tooltip-content a { text-decoration: none; }
            #pt-tracker-tooltip-content a:hover { text-decoration: underline; }
            #pt-tracker-container:hover #pt-tracker-tooltip { visibility: visible; opacity: 1; }
            #pt-tracker-open-all { display: block; margin-top: 8px; padding: 4px; background-color: #007bff; color: white; text-align: center; border-radius: 3px; cursor: pointer; font-weight: bold;}
            #pt-tracker-open-all:hover { background-color: #0056b3; }
        `);

        const container = document.createElement('div');
        container.id = 'pt-tracker-container';
        const uiCounter = document.createElement('div');
        uiCounter.id = 'pt-tracker-ui';
        const tooltip = document.createElement('div');
        tooltip.id = 'pt-tracker-tooltip';
        tooltip.innerHTML = `<div id="pt-tracker-tooltip-content"></div>`;
        container.appendChild(uiCounter);
        container.appendChild(tooltip);
        document.body.appendChild(container);

        return {
            uiCounter: uiCounter,
            tooltipContent: tooltip.querySelector('#pt-tracker-tooltip-content')
        };
    }

    async function updateUI({ uiCounter, tooltipContent }) {
        const storedData = await loadData(STORAGE_KEY, {});
        const now = new Date();
        const siteStates = [];

        for (const site of siteSettings) {
            const lastVisitStr = storedData[site.domain];
            let state = { ...site, tier: 'green', statusText: '', daysRemaining: Infinity };
            if (lastVisitStr) {
                const daysSinceVisit = Math.floor((now - new Date(lastVisitStr)) / (1000 * 60 * 60 * 24));
                const daysRemaining = site.days - daysSinceVisit;
                state.daysRemaining = daysRemaining;
                if (daysRemaining <= 3) state.tier = 'red';
                else if (daysRemaining <= 6) state.tier = 'pink';
                state.statusText = daysRemaining >= 0 ? `(剩余安全期: ${daysRemaining}天)` : `(已过期: ${-daysRemaining}天)`;
            } else {
                state.tier = 'red';
                state.statusText = `(从未访问过)`;
            }
            siteStates.push(state);
        }

        const greenSites = siteStates.filter(s => s.tier === 'green').sort((a, b) => b.daysRemaining - a.daysRemaining);
        const pinkSites = siteStates.filter(s => s.tier === 'pink').sort((a,b) => a.daysRemaining - b.daysRemaining);
        const redSites = siteStates.filter(s => s.tier === 'red').sort((a,b) => a.daysRemaining - b.daysRemaining);

        let contentHTML = '';
        const atRiskDomains = [];

        const renderSite = (site) => {
            const displayName = site.name || site.domain;
            const nameColor = site.important ? 'red' : 'black';
            let statusColor = 'grey';
            if (site.tier === 'red') statusColor = 'red';
            else if (site.tier === 'pink') statusColor = 'hotpink';
            else if (site.tier === 'green') statusColor = 'green';
            return `<div><a href="https://${site.domain}" target="_blank" style="color:${nameColor}; font-weight:${site.important ? 'bold' : 'normal'};">${displayName}</a> <span style="color:${statusColor};">${site.statusText}</span></div>`;
        };

        redSites.forEach(s => { contentHTML += renderSite(s); atRiskDomains.push(s.domain); });
        pinkSites.forEach(s => { contentHTML += renderSite(s); atRiskDomains.push(s.domain); });
        if ((redSites.length > 0 || pinkSites.length > 0) && greenSites.length > 0) {
            contentHTML += '<hr style="margin: 5px 0; border: none; border-top: 1px solid #eee;">';
        }
        greenSites.forEach(s => { contentHTML += renderSite(s); });
        if (atRiskDomains.length > 0) {
            contentHTML += `<div id="pt-tracker-open-all">一键打开 ${atRiskDomains.length} 个风险站点</div>`;
        }

        uiCounter.innerHTML = `
            <span style="color: green; font-weight: bold;">${greenSites.length}</span>
            <span style="margin: 0 2px;">/</span>
            <span style="color: hotpink; font-weight: bold;">${pinkSites.length}</span>
            <span style="margin: 0 2px;">/</span>
            <span style="color: red; font-weight: bold;">${redSites.length}</span>
        `;

        tooltipContent.innerHTML = contentHTML;

        const openAllButton = document.getElementById('pt-tracker-open-all');
        if (openAllButton) {
            openAllButton.addEventListener('click', () => {
                atRiskDomains.forEach(domain => GM_openInTab(`https://${domain}`, { active: false }));
            });
        }
    }

    async function checkAndShowAlerts() {
        const now = new Date();
        const todayStr = now.toISOString().split('T')[0];
        const lastAlertDate = await loadData(GLOBAL_ALERT_KEY, '');
        if (lastAlertDate === todayStr) { return; }
        const storedData = await loadData(STORAGE_KEY, {});
        let needsAlert = false;
        for (const site of siteSettings) {
            const lastVisitStr = storedData[site.domain];
            let daysRemaining = -1;
            if (lastVisitStr) {
                daysRemaining = site.days - Math.floor((now - new Date(lastVisitStr)) / (1000 * 60 * 60 * 24));
            }
            if (daysRemaining <= 1) {
                needsAlert = true;
                break;
            }
        }
        if (needsAlert) {
            alert('您有PT站点不活跃,即将封禁!');
            await saveData(GLOBAL_ALERT_KEY, todayStr);
        }
    }

    async function main() {
        const uiElements = createUI();
        await checkAndUpdateCurrentSite();
        await updateUI(uiElements);
        await checkAndShowAlerts();
    }

    if (document.readyState === 'complete') { main(); }
    else { window.addEventListener('load', main); }

})();