Greasy Fork

Greasy Fork is available in English.

IP纯净度实时监测

IP纯净度实时监测,支持自由拖拽、折叠、右下角手动缩放窗口大小,并记忆所有状态。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         IP纯净度实时监测
// @name:zh-CN   IP纯净度实时监测
// @name:en      IP Pure Monitor
// @namespace    http://tampermonkey.net/
// @version      1.4.1
// @description  IP纯净度实时监测,支持自由拖拽、折叠、右下角手动缩放窗口大小,并记忆所有状态。
// @description:zh-CN IP纯净度实时监测,支持自由拖拽、折叠、右下角手动缩放窗口大小,并记忆所有状态。
// @description:en A Tampermonkey userscript for real-time IP purity and fraud score monitoring.
// @author       lwow
// @license      MIT
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      my.ippure.com
// ==/UserScript==

(function() {
    'use strict';

    const CACHE_MINUTES = 5;
    const API_URL = "https://my.ippure.com/v1/info";

    let savedLeft = GM_getValue('ip_monitor_left', '');
    let savedTop = GM_getValue('ip_monitor_top', '');
    let isMinimized = GM_getValue('ip_monitor_minimized', false);
    let savedWidth = GM_getValue('ip_monitor_width', '');
    let savedHeight = GM_getValue('ip_monitor_height', '');

    const host = document.createElement('div');
    host.id = 'ip-pure-monitor-host';
    host.style.cssText = 'position: fixed; z-index: 2147483647; top: 0; left: 0; width: 0; height: 0; overflow: visible; pointer-events: none;';
    document.documentElement.appendChild(host);

    const shadow = host.attachShadow({mode: 'open'});

    const style = document.createElement('style');
    style.textContent = `
        #ip-pure-monitor {
            all: initial;
            position: absolute;
            ${savedLeft && savedTop ? `left: ${savedLeft}; top: ${savedTop};` : 'bottom: 20px; right: 20px;'}
            background: rgba(0, 0, 0, 0.85) !important;
            color: white !important;
            border-radius: 8px !important;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
            font-size: 13px !important;
            line-height: 1.5 !important;
            box-shadow: 0 4px 10px rgba(0,0,0,0.3) !important;
            user-select: none !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
            pointer-events: auto;
            min-width: 160px;
            z-index: 2147483647;
            display: flex !important;
            flex-direction: column !important;
            overflow: hidden !important; 
        }
        .header {
            display: flex !important;
            justify-content: space-between !important;
            align-items: center !important;
            padding: 6px 12px !important;
            background: rgba(255, 255, 255, 0.1) !important;
            cursor: move !important;
            font-size: 12px !important;
            color: #ddd !important;
            flex-shrink: 0 !important;
        }
        .content {
            padding: 10px 15px !important;
            cursor: pointer;
            display: ${isMinimized ? 'none' : 'block'} !important;
            box-sizing: border-box !important;
            resize: both;
            overflow: auto;
            min-width: 180px;
            min-height: 95px;
        }
        .content::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }
        .content::-webkit-scrollbar-thumb {
            background: rgba(255, 255, 255, 0.3);
            border-radius: 4px;
        }
        .content::-webkit-scrollbar-corner {
            background: transparent;
        }
        .content::-webkit-resizer {
            background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12"><path d="M12 0L0 12h12V0z" fill="rgba(255,255,255,0.6)"/></svg>');
            background-repeat: no-repeat;
            background-position: bottom right;
        }
    `;
    shadow.appendChild(style);

    // anti dark reader
    const preventDarkReaderObserver = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                
                if (node.tagName === 'STYLE' && node !== style) {
                    node.remove();
                }
            }
        }
    });

    preventDarkReaderObserver.observe(shadow, { childList: true });

    const monitorDiv = document.createElement('div');
    monitorDiv.id = 'ip-pure-monitor';

    const headerDiv = document.createElement('div');
    headerDiv.className = 'header';

    const titleSpan = document.createElement('span');
    titleSpan.innerText = '🛡️ IP 监测';
    titleSpan.style.fontWeight = 'bold';

    const minBtn = document.createElement('span');
    minBtn.innerText = isMinimized ? '[+]' : '[-]';
    minBtn.style.cssText = 'cursor: pointer; font-weight: bold; padding-left: 10px;';

    headerDiv.appendChild(titleSpan);
    headerDiv.appendChild(minBtn);

    const contentDiv = document.createElement('div');
    contentDiv.className = 'content';

    if (savedWidth) contentDiv.style.width = savedWidth;
    if (savedHeight) contentDiv.style.height = savedHeight;

    monitorDiv.appendChild(headerDiv);
    monitorDiv.appendChild(contentDiv);
    shadow.appendChild(monitorDiv);

    function updateUI(data, isError = false) {
        contentDiv.replaceChildren(); 

        if (isError) {
            monitorDiv.style.borderLeft = "4px solid #ff4444";
            
            const errTitle = document.createElement('div');
            errTitle.textContent = '❌ 检测失败';
            
            const errDesc = document.createElement('div');
            errDesc.style.fontSize = '11px';
            errDesc.style.color = '#aaa';
            errDesc.textContent = '点击此处重试';
            
            contentDiv.appendChild(errTitle);
            contentDiv.appendChild(errDesc);
            return;
        }

        const score = data.fraudScore;
        let statusColor = "#00C851";
        let statusText = "极佳";

        if (score >= 75) {
            statusColor = "#ff4444";
            statusText = "高危";
        } else if (score >= 40) {
            statusColor = "#ffbb33";
            statusText = "中等";
        }

        monitorDiv.style.borderLeft = `4px solid ${statusColor}`;

        if (isMinimized) {
            titleSpan.replaceChildren(); // 清空 titleSpan
            
            const dotSpan = document.createElement('span');
            dotSpan.style.color = statusColor;
            dotSpan.textContent = '● ';
            
            titleSpan.appendChild(dotSpan);
            titleSpan.appendChild(document.createTextNode(`${data.ip} (${score})`));
        } else {
            titleSpan.innerText = '🛡️ IP 监测';
        }

        const line1 = document.createElement('div');
        line1.style.fontWeight = 'bold';
        line1.style.marginBottom = '4px';
        
        const dotSpan2 = document.createElement('span');
        dotSpan2.style.color = statusColor;
        dotSpan2.textContent = '● ';
        
        line1.appendChild(dotSpan2);
        line1.appendChild(document.createTextNode(`IP: ${data.ip}`));

        const line2 = document.createElement('div');
        line2.style.color = '#ddd';
        line2.appendChild(document.createTextNode(`${data.countryCode} - ${data.city} | 分数: `));
        
        const scoreSpan = document.createElement('span');
        scoreSpan.style.color = statusColor;
        scoreSpan.style.fontWeight = 'bold';
        scoreSpan.textContent = score;
        
        line2.appendChild(scoreSpan);
        line2.appendChild(document.createTextNode(` (${statusText})`));

        const line3 = document.createElement('div');
        line3.style.fontSize = '11px';
        line3.style.color = '#aaa';
        line3.style.marginTop = '4px';
        line3.textContent = `${data.isResidential ? '🏠 住宅' : '🏢 机房'} | ${data.asOrganization || '未知ISP'}`;

        contentDiv.appendChild(line1);
        contentDiv.appendChild(line2);
        contentDiv.appendChild(line3);
    }

    function fetchIPInfo() {
        contentDiv.replaceChildren(); // 清空并显示加载状态
        const loadingDiv = document.createElement('div');
        loadingDiv.style.color = '#aaa';
        loadingDiv.textContent = '🔄 检测中...';
        contentDiv.appendChild(loadingDiv);

        GM_xmlhttpRequest({
            method: "GET",
            url: API_URL,
            timeout: 10000,
            headers: { "Accept": "application/json", "Cache-Control": "no-cache" },
            onload: function(response) {
                if (response.status === 200) {
                    try {
                        const data = JSON.parse(response.responseText);
                        GM_setValue('ip_cache_data', JSON.stringify(data));
                        GM_setValue('ip_cache_time', Date.now());
                        updateUI(data);
                    } catch (e) { updateUI(null, true); }
                } else { updateUI(null, true); }
            },
            onerror: () => updateUI(null, true),
            ontimeout: () => updateUI(null, true)
        });
    }

    minBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        isMinimized = !isMinimized;
        GM_setValue('ip_monitor_minimized', isMinimized);

        if (isMinimized) {
            contentDiv.style.setProperty('display', 'none', 'important');
            minBtn.innerText = '[+]';
            try {
                const cachedData = JSON.parse(GM_getValue('ip_cache_data'));
                if(cachedData) updateUI(cachedData);
            } catch(e) {}
        } else {
            contentDiv.style.setProperty('display', 'block', 'important');
            minBtn.innerText = '[-]';
            titleSpan.innerText = '🛡️ IP 监测';
        }
    });

    contentDiv.addEventListener('click', (e) => {
        const rect = contentDiv.getBoundingClientRect();

        const isClickOnResizer = (e.clientX > rect.right - 15) && (e.clientY > rect.bottom - 15);
        if (!isClickOnResizer) {
            fetchIPInfo();
        }
    });

    let isDragging = false;
    let offsetX, offsetY;

    headerDiv.addEventListener('mousedown', (e) => {
        if (e.target === minBtn) return;
        isDragging = true;
        const rect = monitorDiv.getBoundingClientRect();
        offsetX = e.clientX - rect.left;
        offsetY = e.clientY - rect.top;
        monitorDiv.style.transition = 'none';
    });

    document.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
        e.stopPropagation();

        monitorDiv.style.bottom = 'auto';
        monitorDiv.style.right = 'auto';

        let newX = e.clientX - offsetX;
        let newY = e.clientY - offsetY;

        const maxX = window.innerWidth - monitorDiv.offsetWidth;
        const maxY = window.innerHeight - monitorDiv.offsetHeight;

        newX = Math.max(0, Math.min(newX, maxX));
        newY = Math.max(0, Math.min(newY, maxY));

        monitorDiv.style.left = newX + 'px';
        monitorDiv.style.top = newY + 'px';
    }, true);

    document.addEventListener('mouseup', (e) => {
        if (isDragging) {
            isDragging = false;
            e.stopPropagation();
            monitorDiv.style.transition = 'all 0.3s ease';
            GM_setValue('ip_monitor_left', monitorDiv.style.left);
            GM_setValue('ip_monitor_top', monitorDiv.style.top);
        }
    }, true);

    let resizeTimeout;
    const resizeObserver = new ResizeObserver(() => {
        if (isMinimized) return; 
        clearTimeout(resizeTimeout);
        resizeTimeout = setTimeout(() => {
            if (contentDiv.style.width) GM_setValue('ip_monitor_width', contentDiv.style.width);
            if (contentDiv.style.height) GM_setValue('ip_monitor_height', contentDiv.style.height);
        }, 300); 
    });
    resizeObserver.observe(contentDiv);

    const cachedDataStr = GM_getValue('ip_cache_data');
    const cachedTime = GM_getValue('ip_cache_time');
    const now = Date.now();

    if (cachedDataStr && cachedTime && (now - cachedTime < CACHE_MINUTES * 60 * 1000)) {
        try { updateUI(JSON.parse(cachedDataStr)); } catch (e) { fetchIPInfo(); }
    } else {
        fetchIPInfo();
    }
})();