升级要求查看

最近1级升2级的要求脚本用不了 在佬的基础上更改了下 可以正常使用了 原贴
改了一下佬友的等级信息浮窗脚本,适配L站和IF站 - 教程 - IDC Flare
不知道能不能发 各位佬手下留情


在篡改猴中添加新脚本 复制粘贴保存即可

// ==UserScript==
// @name         L站/IF站 等级监控浮窗
// @namespace    http://tampermonkey.net/
// @version      3.3
// @description  在 linux.do 或 idcflare.com 显示等级浮窗,支持0-3级用户
// @author       佬友
// @match        https://linux.do/*
// @match        https://idcflare.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_log
// @connect      connect.linux.do
// @connect      linux.do
// @connect      connect.idcflare.com
// @connect      idcflare.com
// @connect      *
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 网站配置
    const siteConfigs = {
        'linux.do': {
            connectHost: 'https://connect.linux.do/',
            mainHost: 'https://linux.do',
            storagePrefix: 'linux_do'
        },
        'idcflare.com': {
            connectHost: 'https://connect.idcflare.com/',
            mainHost: 'https://idcflare.com',
            storagePrefix: 'idcflare_com'
        }
    };

    const hostname = window.location.hostname;
    const currentConfig = siteConfigs[hostname];

    if (!currentConfig) {
        console.log(`[等级监控脚本] 不支持当前域名: ${hostname}`);
        return;
    }

    const STORAGE_KEY = `${currentConfig.storagePrefix}_user_trust_level_data_v3`;
    const LAST_CHECK_KEY = `${currentConfig.storagePrefix}_last_check_v3`;
    const POSITION_KEY = `${currentConfig.storagePrefix}_window_position_v3`;

    // 0级和1级用户升级要求
    const LEVEL_REQUIREMENTS = {
        0: {
            topics_entered: 5,
            posts_read_count: 30,
            time_read: 600
        },
        1: {
            days_visited: 15,
            likes_given: 1,
            likes_received: 1,
            replies_to_different_topics: 3,
            topics_entered: 20,
            posts_read_count: 100,
            time_read: 3600
        }
    };

    const debugDiv = document.createElement('div');
    debugDiv.style.position = 'fixed';
    debugDiv.style.bottom = '10px';
    debugDiv.style.right = '10px';
    debugDiv.style.width = '300px';
    debugDiv.style.maxHeight = '200px';
    debugDiv.style.overflow = 'auto';
    debugDiv.style.background = 'rgba(0,0,0,0.8)';
    debugDiv.style.color = '#0f0';
    debugDiv.style.padding = '10px';
    debugDiv.style.borderRadius = '5px';
    debugDiv.style.zIndex = '10000';
    debugDiv.style.fontFamily = 'monospace';
    debugDiv.style.fontSize = '12px';
    debugDiv.style.display = 'none';
    document.body.appendChild(debugDiv);

    function debugLog(message) {
        const time = new Date().toLocaleTimeString();
        const logMessage = `[${hostname}脚本] ${message}`;
        console.log(logMessage);
        GM_log(logMessage);

        const logLine = document.createElement('div');
        logLine.textContent = `${time}: ${message}`;
        debugDiv.appendChild(logLine);
        debugDiv.scrollTop = debugDiv.scrollHeight;
    }

    document.addEventListener('keydown', function(e) {
        if (e.altKey && e.key === 'd') {
            debugDiv.style.display = debugDiv.style.display === 'none' ? 'block' : 'none';
        }
    });

    debugLog('脚本开始执行');

    function isDiscourseDarkMode() {
        const themeButton = document.querySelector('button[data-identifier="interface-color-selector"]');
        if (themeButton) {
            const useElement = themeButton.querySelector('svg use');
            if (useElement) {
                const href = useElement.getAttribute('href');
                if (href === '#moon') {
                    return true;
                }
                if (href === '#sun') {
                    return false;
                }
                if (href === '#circle-half-stroke') {
                    const isSystemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
                    return isSystemDark;
                }
            }
        }
        return false;
    }

    GM_addStyle(`
        :root {
            --ld-bg-primary: white;
            --ld-bg-secondary: #f9fafb;
            --ld-bg-tertiary: #f3f4f6;
            --ld-bg-disabled: #e5e7eb;
            --ld-text-primary: #1f2937;
            --ld-text-secondary: #374151;
            --ld-text-tertiary: #4b5563;
            --ld-text-muted: #6b7280;
            --ld-text-disabled: #9ca3af;
            --ld-border-primary: #e5e7eb;
            --ld-border-secondary: #f3f4f6;
            --ld-shadow-color: rgba(0, 0, 0, 0.1);
            --ld-success-color: #16a34a;
            --ld-success-bg: #f0fdf4;
            --ld-error-color: #dc2626;
            --ld-error-bg: #fef2f2;
            --ld-accent-color: #ea580c;
            --ld-accent-color-darker: #c2410c;
            --ld-accent-bg: #fed7aa;
            --ld-progress-bar-bg: linear-gradient(90deg, #fb923c, #ea580c);
        }
        .ld-dark-mode {
            --ld-bg-primary: #2d2d2d;
            --ld-bg-secondary: #252525;
            --ld-bg-tertiary: #3a3a3a;
            --ld-bg-disabled: #4a4a4a;
            --ld-text-primary: #e0e0e0;
            --ld-text-secondary: #c7c7c7;
            --ld-text-tertiary: #b0b0b0;
            --ld-text-muted: #8e8e8e;
            --ld-text-disabled: #6e6e6e;
            --ld-border-primary: #444444;
            --ld-border-secondary: #383838;
            --ld-shadow-color: rgba(0, 0, 0, 0.3);
            --ld-success-color: #5eead4;
            --ld-success-bg: #064e3b;
            --ld-error-color: #fb7185;
            --ld-error-bg: #4c0519;
        }
        .ld-floating-container {
            position: fixed;
            top: 50%;
            right: 0;
            transform: translateY(-50%);
            z-index: 9999;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }
        .ld-floating-btn {
            background: var(--ld-bg-primary);
            box-shadow: 0 4px 12px var(--ld-shadow-color);
            border: 1px solid var(--ld-border-primary);
            border-radius: 8px 0 0 8px;
            border-right: none;
            transition: all 0.3s ease;
            cursor: move;
            width: 48px;
            padding: 12px 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 4px;
            user-select: none;
        }
        .ld-floating-btn:hover {
            width: 64px;
            box-shadow: 0 8px 24px var(--ld-shadow-color);
        }
        .ld-btn-icon {
            width: 16px;
            height: 16px;
            color: var(--ld-text-muted);
        }
        .ld-btn-level {
            font-size: 12px;
            font-weight: bold;
            color: var(--ld-accent-color);
        }
        .ld-btn-progress-bar {
            width: 32px;
            height: 4px;
            background: var(--ld-border-primary);
            border-radius: 2px;
            overflow: hidden;
        }
        .ld-btn-progress-fill {
            height: 100%;
            background: var(--ld-accent-color);
            border-radius: 2px;
            transition: width 0.3s ease;
        }
        .ld-btn-stats {
            font-size: 10px;
            color: var(--ld-text-muted);
        }
        .ld-btn-chevron {
            width: 12px;
            height: 12px;
            color: var(--ld-text-disabled);
            opacity: 0;
            transition: opacity 0.3s ease;
        }
        .ld-floating-btn:hover .ld-btn-chevron {
            opacity: 1;
            animation: pulse 1s infinite;
        }
        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }
        .ld-popup {
            position: absolute;
            top: 50%;
            right: 100%;
            margin-right: 8px;
            width: 384px;
            max-height: calc(100vh - 20px);
            background: var(--ld-bg-primary);
            border-radius: 12px;
            box-shadow: 0 20px 25px -5px var(--ld-shadow-color), 0 10px 10px -5px var(--ld-shadow-color);
            border: 1px solid var(--ld-border-primary);
            opacity: 0;
            transform: translate(20px, -50%);
            transition: all 0.2s ease;
            pointer-events: none;
            overflow: hidden;
            overflow-y: auto;
            display: flex;
            flex-direction: column;
        }
        .ld-popup.show {
            opacity: 1;
            transform: translate(0, -50%);
            pointer-events: auto;
        }
        .ld-popup-header {
            padding: 16px;
            border-bottom: 1px solid var(--ld-border-secondary);
        }
        .ld-header-top {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 8px;
        }
        .ld-user-info {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .ld-user-dot {
            width: 12px;
            height: 12px;
            background: #ea580c;
            border-radius: 50%;
        }
        .ld-user-name {
            font-size: 14px;
            font-weight: 500;
            color: var(--ld-text-secondary);
        }
        .ld-level-badge {
            font-size: 12px;
            background: var(--ld-accent-bg);
            color: var(--ld-accent-color-darker);
            padding: 4px 8px;
            border-radius: 9999px;
        }
        .ld-progress-section {
            margin-top: 12px;
        }
        .ld-progress-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 4px;
        }
        .ld-progress-label {
            font-size: 12px;
            color: var(--ld-text-muted);
        }
        .ld-progress-stats {
            font-size: 12px;
            color: var(--ld-text-tertiary);
        }
        .ld-progress-bar-container {
            width: 100%;
            height: 8px;
            background: var(--ld-border-primary);
            border-radius: 4px;
            overflow: hidden;
        }
        .ld-progress-bar {
            height: 100%;
            background: var(--ld-progress-bar-bg);
            border-radius: 4px;
            transition: width 0.3s ease;
        }
        .ld-status-cards {
            padding: 16px;
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 12px;
        }
        .ld-status-card {
            border-radius: 8px;
            padding: 8px;
        }
        .ld-status-card.failed {
            background: var(--ld-error-bg);
        }
        .ld-status-card.passed {
            background: var(--ld-success-bg);
        }
        .ld-card-header {
            display: flex;
            align-items: center;
            gap: 4px;
            margin-bottom: 4px;
        }
        .ld-card-icon {
            width: 12px;
            height: 12px;
        }
        .ld-card-header.failed {
            color: var(--ld-error-color);
        }
        .ld-card-header.passed {
            color: var(--ld-success-color);
        }
        .ld-card-title {
            font-size: 12px;
            font-weight: 500;
        }
        .ld-card-label {
            font-size: 12px;
            color: var(--ld-text-tertiary);
        }
        .ld-card-value {
            font-size: 14px;
            font-weight: 500;
            color: var(--ld-text-primary);
        }
        .ld-card-subtitle {
            font-size: 12px;
            margin-top: 2px;
        }
        .ld-card-subtitle.failed {
            color: var(--ld-error-color);
        }
        .ld-card-subtitle.passed {
            color: var(--ld-success-color);
        }
        .ld-details-section {
            border-top: 1px solid var(--ld-border-secondary);
            flex-grow: 1;
            min-height: 0;
            display: flex;
            flex-direction: column;
        }
        .ld-details-list {
            padding: 12px;
            overflow-y: auto;
            flex-grow: 1;
        }
        .ld-detail-item {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 4px 8px;
            border-radius: 4px;
            transition: background 0.2s ease;
        }
        .ld-detail-item:hover {
            background: var(--ld-bg-secondary);
        }
        .ld-detail-left {
            display: flex;
            align-items: center;
            gap: 8px;
            flex: 1;
            min-width: 0;
        }
        .ld-detail-icon {
            width: 12px;
            height: 12px;
            color: var(--ld-text-disabled);
            flex-shrink: 0;
        }
        .ld-detail-label {
            font-size: 12px;
            color: var(--ld-text-tertiary);
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .ld-detail-right {
            display: flex;
            align-items: center;
            gap: 12px;
            flex-shrink: 0;
        }
        .ld-detail-current {
            font-size: 12px;
            font-weight: 500;
            text-align: right;
        }
        .ld-detail-current.passed {
            color: var(--ld-success-color);
        }
        .ld-detail-current.failed {
            color: var(--ld-error-color);
        }
        .ld-detail-target {
            font-size: 12px;
            color: var(--ld-text-disabled);
            text-align: right;
        }
        .ld-detail-status {
            width: 12px;
            height: 12px;
        }
        .ld-detail-status.passed {
            color: var(--ld-success-color);
        }
        .ld-detail-status.failed {
            color: var(--ld-error-color);
        }
        .ld-popup-footer {
            padding: 12px;
            background: var(--ld-bg-secondary);
            border-top: 1px solid var(--ld-border-secondary);
            text-align: center;
        }
        .ld-footer-message {
            font-size: 12px;
            font-weight: 500;
            margin-bottom: 4px;
        }
        .ld-footer-message.failed {
            color: var(--ld-error-color);
        }
        .ld-footer-message.passed {
            color: var(--ld-success-color);
        }
        .ld-footer-time {
            font-size: 12px;
            color: var(--ld-text-muted);
        }
        .ld-reload-btn {
            display: block;
            width: calc(100% - 24px);
            margin: 0 12px 12px;
            padding: 8px;
            background: var(--ld-bg-tertiary);
            color: var(--ld-text-secondary);
            border: none;
            border-radius: 6px;
            font-weight: 500;
            cursor: pointer;
            transition: background 0.2s;
            font-size: 12px;
        }
        .ld-reload-btn:hover {
            background: var(--ld-bg-disabled);
        }
        .ld-reload-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        .ld-error-container {
            padding: 24px;
            text-align: center;
            color: var(--ld-text-muted);
        }
        .ld-error-icon {
            font-size: 24px;
            color: var(--ld-error-color);
            margin-bottom: 12px;
        }
        .ld-error-title {
            font-weight: 500;
            margin-bottom: 8px;
            color: var(--ld-error-color);
            font-size: 14px;
        }
        .ld-error-message {
            margin-bottom: 16px;
            font-size: 12px;
            line-height: 1.5;
        }
        .ld-hidden-iframe {
            position: absolute;
            width: 0;
            height: 0;
            border: 0;
            visibility: hidden;
        }
        @media (max-height: 600px) {
            .ld-details-list {
                max-height: 200px;
                flex-grow: 0;
            }
        }
        .ld-floating-container.ld-left-aligned .ld-floating-btn {
            border-radius: 0 8px 8px 0;
            border-left: none;
            border-right: 1px solid var(--ld-border-primary);
        }
        .ld-floating-container.ld-left-aligned .ld-btn-chevron {
            transform: rotate(180deg);
        }
        .ld-floating-container.ld-left-aligned .ld-popup {
            left: 100%;
            right: auto;
            margin-left: 8px;
            margin-right: 0;
            transform: translate(-20px, -50%);
        }
        .ld-floating-container.ld-left-aligned .ld-popup.show {
            transform: translate(0, -50%);
        }
    `);

    function getElementByXpath(xpath) {
        return document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    }

    const loginBtnXpath = '//*[@id="ember3"]/div[2]/header/div/div/div[3]/span/span';
    const loginBtn = getElementByXpath(loginBtnXpath);

    debugLog('检查登录按钮: ' + (loginBtn ? '存在' : '不存在'));

    if (loginBtn) {
        debugLog('已检测到登录按钮,不显示等级浮窗');
        return;
    }

    const cachedData = GM_getValue(STORAGE_KEY);
    const lastCheck = GM_getValue(LAST_CHECK_KEY, 0);
    const now = Date.now();
    const oneHourMs = 60 * 60 * 1000;

    debugLog(`上次检查时间: ${new Date(lastCheck).toLocaleString()}`);

    const container = document.createElement('div');
    container.className = 'ld-floating-container';

    const savedPosition = GM_getValue(POSITION_KEY);
    if (savedPosition && savedPosition.top) {
        Object.assign(container.style, {
            top: savedPosition.top,
            left: savedPosition.left || 'auto',
            right: savedPosition.right || 'auto',
            transform: 'none'
        });

        if (savedPosition.left === '0px') {
            container.classList.add('ld-left-aligned');
        }
    }

    const btn = document.createElement('div');
    btn.className = 'ld-floating-btn';
    btn.innerHTML = `
        <svg class="ld-btn-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
        </svg>
        <div class="ld-btn-level">L?</div>
        <div class="ld-btn-progress-bar">
            <div class="ld-btn-progress-fill" style="width: 0%;"></div>
        </div>
        <div class="ld-btn-stats">0/0</div>
        <svg class="ld-btn-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
        </svg>
    `;

    const popup = document.createElement('div');
    popup.className = 'ld-popup';

    popup.innerHTML = `
        <div class="ld-popup-header">
            <div class="ld-header-top">
                <div class="ld-user-info">
                    <div class="ld-user-dot"></div>
                    <span class="ld-user-name">加载中...</span>
                </div>
                <span class="ld-level-badge">升级到等级?</span>
            </div>
            <div class="ld-progress-section">
                <div class="ld-progress-header">
                    <span class="ld-progress-label">完成进度</span>
                    <span class="ld-progress-stats">0/0</span>
                </div>
                <div class="ld-progress-bar-container">
                    <div class="ld-progress-bar" style="width: 0%;"></div>
                </div>
            </div>
        </div>
        <div class="ld-popup-content">
            <div class="ld-status-cards">
                <div class="ld-status-card failed">
                    <div class="ld-card-header failed">
                        <svg class="ld-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
                        </svg>
                        <span class="ld-card-title">未达标</span>
                    </div>
                    <div class="ld-card-label">正在加载...</div>
                    <div class="ld-card-value">-</div>
                </div>
                <div class="ld-status-card passed">
                    <div class="ld-card-header passed">
                        <svg class="ld-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
                        </svg>
                        <span class="ld-card-title">已完成</span>
                    </div>
                    <div class="ld-card-label">其他要求</div>
                    <div class="ld-card-value">0 / 0</div>
                </div>
            </div>
        </div>
    `;

    container.appendChild(btn);
    container.appendChild(popup);

    let isHovered = false;
    let hoverTimeout = null;
    let darkModeMediaQuery = null;
    let observerDebounceTimeout = null;
    let isDragging = false;

    function applyDarkModeAndSetupListeners() {
        const isDark = isDiscourseDarkMode();
        const wasDark = container.classList.contains('ld-dark-mode');

        if (isDark !== wasDark) {
            if (isDark) {
                container.classList.add('ld-dark-mode');
                debugLog('切换为暗黑模式');
            } else {
                container.classList.remove('ld-dark-mode');
                debugLog('切换为亮色模式');
            }
        }

        setupMediaQueryListener();
    }

    function mediaQueryChangedCallback(event) {
        debugLog(`系统颜色偏好改变: ${event.matches ? '暗色' : '亮色'}`);
        const themeButton = document.querySelector('button[data-identifier="interface-color-selector"]');
        if (themeButton) {
            const useElement = themeButton.querySelector('svg use');
            if (useElement && useElement.getAttribute('href') === '#circle-half-stroke') {
                applyDarkModeAndSetupListeners();
            }
        }
    }

    function setupMediaQueryListener() {
        if (darkModeMediaQuery) {
            darkModeMediaQuery.removeEventListener('change', mediaQueryChangedCallback);
            darkModeMediaQuery = null;
        }

        const themeButton = document.querySelector('button[data-identifier="interface-color-selector"]');
        if (themeButton) {
            const useElement = themeButton.querySelector('svg use');
            if (useElement && useElement.getAttribute('href') === '#circle-half-stroke') {
                if (window.matchMedia) {
                    darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
                    darkModeMediaQuery.addEventListener('change', mediaQueryChangedCallback);
                }
            }
        }
    }

    const observer = new MutationObserver(mutations => {
        clearTimeout(observerDebounceTimeout);
        observerDebounceTimeout = setTimeout(() => {
            applyDarkModeAndSetupListeners();
        }, 300);
    });

    applyDarkModeAndSetupListeners();

    observer.observe(document.body, {
        childList: true,
        subtree: true,
    });

    // 智能调整弹出窗口位置,防止超出屏幕
    function adjustPopupPosition() {
        popup.style.transform = 'translate(0, -50%)';
        popup.offsetHeight;

        const containerRect = container.getBoundingClientRect();
        const viewportHeight = window.innerHeight;
        const popupHeight = popup.scrollHeight;
        const margin = 10;

        const popupTop = containerRect.top + (containerRect.height / 2) - (popupHeight / 2);
        const popupBottom = popupTop + popupHeight;

        let translateYCorrection = 0;

        if (popupTop < margin) {
            translateYCorrection = margin - popupTop;
            debugLog(`浮窗超出顶部,向下修正 ${translateYCorrection.toFixed(2)}px`);
        } else if (popupBottom > viewportHeight - margin) {
            translateYCorrection = (viewportHeight - margin) - popupBottom;
            debugLog(`浮窗超出底部,向上修正 ${translateYCorrection.toFixed(2)}px`);
        } else {
            debugLog('浮窗在可视范围内,无需修正');
        }

        const finalTranslateY = `calc(-50% + ${translateYCorrection}px)`;
        const isLeftAligned = container.classList.contains('ld-left-aligned');
        const finalTranslateX = popup.classList.contains('show') ? '0' : (isLeftAligned ? '-20px' : '20px');

        popup.style.transform = `translate(${finalTranslateX}, ${finalTranslateY})`;
    }


    container.addEventListener('mouseenter', () => {
        if (isDragging) return;
        clearTimeout(hoverTimeout);
        isHovered = true;
        hoverTimeout = setTimeout(() => {
            if (isHovered) {
                adjustPopupPosition();
                popup.classList.add('show');
            }
        }, 150);
    });

    container.addEventListener('mouseleave', () => {
        if (isDragging) return;
        clearTimeout(hoverTimeout);
        isHovered = false;
        hoverTimeout = setTimeout(() => {
            if (!isHovered) {
                popup.classList.remove('show');
            }
        }, 100);
    });

    let dragStartX, dragStartY, dragStartTop, dragStartLeft;

    function onDragMove(e) {
        if (!isDragging) return;

        const dx = e.clientX - dragStartX;
        const dy = e.clientY - dragStartY;
        let newTop = dragStartTop + dy;
        let newLeft = dragStartLeft + dx;

        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;
        const containerWidth = container.offsetWidth;
        const containerHeight = container.offsetHeight;

        if (newTop < 0) newTop = 0;
        if (newLeft < 0) newLeft = 0;
        if (newTop + containerHeight > viewportHeight) newTop = viewportHeight - containerHeight;
        if (newLeft + containerWidth > viewportWidth) newLeft = viewportWidth - containerWidth;

        container.style.top = `${newTop}px`;
        container.style.left = `${newLeft}px`;
    }

    function onDragEnd() {
        if (!isDragging) return;
        isDragging = false;

        btn.style.cursor = 'move';
        document.body.style.userSelect = 'auto';

        document.removeEventListener('mousemove', onDragMove);
        document.removeEventListener('mouseup', onDragEnd);

        const viewportWidth = window.innerWidth;
        const containerRect = container.getBoundingClientRect();
        const containerCenter = containerRect.left + containerRect.width / 2;

        let finalPosition;

        if (containerCenter < viewportWidth / 2) {
            container.style.left = '0px';
            container.style.right = 'auto';
            container.classList.add('ld-left-aligned');
            finalPosition = { top: container.style.top, left: '0px', right: 'auto' };
        } else {
            container.style.left = 'auto';
            container.style.right = '0px';
            container.classList.remove('ld-left-aligned');
            finalPosition = { top: container.style.top, left: 'auto', right: '0px' };
        }

        GM_setValue(POSITION_KEY, finalPosition);
    }

    btn.addEventListener('mousedown', (e) => {
        if (e.button !== 0) return;
        isDragging = true;
        e.preventDefault();

        dragStartX = e.clientX;
        dragStartY = e.clientY;

        const rect = container.getBoundingClientRect();
        container.style.right = 'auto';
        container.style.transform = 'none';
        container.style.top = `${rect.top}px`;
        container.style.left = `${rect.left}px`;

        dragStartTop = rect.top;
        dragStartLeft = rect.left;

        btn.style.cursor = 'grabbing';
        document.body.style.userSelect = 'none';

        document.addEventListener('mousemove', onDragMove);
        document.addEventListener('mouseup', onDragEnd);
    });

    window.addEventListener('resize', () => {
        if (popup.classList.contains('show')) {
            adjustPopupPosition();
        }
    });

    document.body.appendChild(container);

    debugLog('新版按钮和浮窗已添加到页面');

    if (cachedData && (now - lastCheck < oneHourMs)) {
        debugLog('使用缓存数据');
        updateInfo(
            cachedData.username,
            cachedData.currentLevel,
            cachedData.targetLevel,
            cachedData.trustLevelDetails,
            new Date(lastCheck),
            cachedData.originalHtml || '',
            true
        );
    } else {
        debugLog('缓存过期或不存在,准备安排获取新数据');
        const delay = 3000;
        debugLog(`将在 ${delay / 1000} 秒后尝试获取数据...`);
        setTimeout(() => {
            debugLog('Timeout结束,准备调用 fetchDataWithGM');
            fetchDataWithGM();
        }, delay);
    }

    function parseTrustLevelDetails(targetInfoDivElement) {
        const details = {
            items: [],
            summaryText: '',
            achievedCount: 0,
            totalCount: 0,
            targetLevelInSummary: null
        };

        if (!targetInfoDivElement) {
            debugLog('parseTrustLevelDetails: targetInfoDivElement为空');
            return details;
        }

        const table = targetInfoDivElement.querySelector('table');
        if (table) {
            const rows = table.querySelectorAll('tbody tr');
            rows.forEach((row, index) => {
                if (index === 0) return;

                const cells = row.querySelectorAll('td');
                if (cells.length >= 3) {
                    const label = cells[0].textContent.trim();
                    const currentText = cells[1].textContent.trim();
                    const requiredText = cells[2].textContent.trim();
                    const isMet = cells[1].classList.contains('text-green-500');

                    details.items.push({
                        label: label,
                        current: currentText,
                        required: requiredText,
                        isMet: isMet
                    });

                    if (isMet) {
                        details.achievedCount++;
                    }
                }
            });
            details.totalCount = details.items.length;
        } else {
            debugLog('parseTrustLevelDetails: 未找到表格');
        }

        const paragraphs = targetInfoDivElement.querySelectorAll('p');
        paragraphs.forEach(p => {
            const text = p.textContent.trim();
            if (text.includes('要求') || text.includes('已满足') || text.includes('信任级别')) {
                details.summaryText = text;
                const levelMatch = text.match(/信任级别\s*(\d+)/);
                if (levelMatch) {
                    details.targetLevelInSummary = levelMatch[1];
                }
            }
        });
        if (!details.summaryText) {
            debugLog('parseTrustLevelDetails: 未找到总结文本段落');
        }

        debugLog(`parseTrustLevelDetails: 解析完成, ${details.achievedCount}/${details.totalCount} 项达标. 总结: ${details.summaryText}. 目标等级从总结文本: ${details.targetLevelInSummary}`);
        return details;
    }

    // 核心数据获取函数
    function fetchDataWithGM() {
        debugLog('进入 fetchDataWithGM 函数');

        // 优先尝试从页面环境中直接获取用户信息 (0网络请求,无视Cloudflare)
        let currentUser = null;
        try {
            // 尝试读取 Discourse 全局对象
            if (typeof unsafeWindow !== 'undefined' && unsafeWindow.Discourse && unsafeWindow.Discourse.User) {
                currentUser = unsafeWindow.Discourse.User.current();
            } else if (window.Discourse && window.Discourse.User) {
                currentUser = window.Discourse.User.current();
            }
        } catch (e) {
            debugLog('尝试读取页面JS对象失败: ' + e);
        }

        if (currentUser) {
            const username = currentUser.username;
            const userLevel = currentUser.trust_level;
            debugLog(`从页面环境获取成功: 用户=${username}, 等级=${userLevel}`);

            if (userLevel === 0 || userLevel === 1) {
                debugLog(`检测到${userLevel}级用户,使用summary.json获取数据`);
                fetchLowLevelUserData(username, userLevel);
                return; // 直接返回,不请求 connect
            } else {
                debugLog(`检测到${userLevel}级用户,需要详情数据`);
                // 2级以上仍需 connect 页面数据,继续执行下方逻辑,或者尝试 fetch connect
            }
        }

        debugLog('页面环境获取未命中或用户等级较高,准备发起网络请求');

        // 尝试使用 fetch 请求 connect 页面 (携带凭证,期望解决 CF 拦截)
        // 注意:connect.linux.do 可能需要 CORS 支持,如果失败则回退 GM
        fetch(currentConfig.connectHost, {
            method: 'GET',
            credentials: 'include'
        })
        .then(response => {
            debugLog(`fetch(connect) status: ${response.status}`);
            if (response.ok) {
                return response.text().then(responseText => {
                    handleConnectResponse(responseText);
                });
            } else {
                 throw new Error(`Status ${response.status}`);
            }
        })
        .catch(err => {
            debugLog(`fetch(connect) 失败: ${err.message},尝试 GM_xmlhttpRequest`);
            // Fallback to GM if fetch fails (e.g. CORS or Network)
            performGMRequest();
        });
    }

    function performGMRequest() {
        GM_xmlhttpRequest({
            method: "GET",
            url: currentConfig.connectHost,
            timeout: 15000,
            onload: function(response) {
                debugLog(`GM_xmlhttpRequest 成功: status ${response.status}`);
                if (response.status === 200) {
                   handleConnectResponse(response.responseText);
                } else {
                    debugLog(`请求失败,状态码: ${response.status}`);
                    handleRequestError(response);
                }
            },
            onerror: function(error) {
                debugLog(`GM_xmlhttpRequest 错误: ${JSON.stringify(error)}`);
                showError('网络请求错误');
            },
            ontimeout: function() {
                debugLog('GM_xmlhttpRequest 超时');
                showError('请求超时');
            }
        });
    }

    function handleConnectResponse(responseText) {
        debugLog(`解析 connect 响应,长度: ${responseText.length}`);
        const tempDiv = document.createElement('div');
        tempDiv.innerHTML = responseText;

        let globalUsername = '用户';
        let currentLevel = '未知';
        const h1 = tempDiv.querySelector('h1');
        if (h1) {
            const h1Text = h1.textContent.trim();
            const welcomeMatch = h1Text.match(/你好,\s*([^(\s]*)\s*\(?([^)]*)\)?\s*(\d+)级用户/i);
            if (welcomeMatch) {
                globalUsername = welcomeMatch[2] || welcomeMatch[1] || '用户';
                currentLevel = welcomeMatch[3];
                debugLog(`从<h1>解析: 用户='${globalUsername}', 等级='${currentLevel}'`);
            }
        }

        const userLevel = parseInt(currentLevel);
        if (isNaN(userLevel)) {
             showError('无法解析用户等级');
             return;
        }

        if (userLevel === 0 || userLevel === 1) {
            fetchLowLevelUserData(globalUsername, userLevel);
        } else if (userLevel >= 2) {
            processHighLevelUserData(tempDiv, globalUsername, currentLevel);
        }
    }

    function saveDataToCache(username, currentLevel, targetLevel, trustLevelDetails, originalHtml) {
        debugLog('保存数据到缓存');
        const dataToCache = {
            username,
            currentLevel,
            targetLevel,
            trustLevelDetails,
            originalHtml,
            cacheTimestamp: Date.now()
        };
        GM_setValue(STORAGE_KEY, dataToCache);
        GM_setValue(LAST_CHECK_KEY, Date.now());
    }

    function updateInfo(username, currentLevel, targetLevel, trustLevelDetails, updateTime, originalHtml, isFromCache = false) {
        debugLog(`更新信息: 用户='${username}', 当前L=${currentLevel}, 目标L=${targetLevel}, 详情获取=${trustLevelDetails && trustLevelDetails.items.length > 0}, 更新时间=${updateTime.toLocaleString()}`);

        const achievedCount = trustLevelDetails ? trustLevelDetails.achievedCount : 0;
        const totalCount = trustLevelDetails ? trustLevelDetails.totalCount : 0;
        const progressPercent = totalCount > 0 ? Math.round((achievedCount / totalCount) * 100) : 0;

        const levelElement = btn.querySelector('.ld-btn-level');
        const progressFill = btn.querySelector('.ld-btn-progress-fill');
        const statsElement = btn.querySelector('.ld-btn-stats');

        if (levelElement) levelElement.textContent = `L${currentLevel || '?'}`;
        if (progressFill) progressFill.style.width = `${progressPercent}%`;
        if (statsElement) statsElement.textContent = `${achievedCount}/${totalCount}`;

        updatePopupContent(username, currentLevel, targetLevel, trustLevelDetails, updateTime, originalHtml, isFromCache);
    }

    function updatePopupContent(username, currentLevel, targetLevel, trustLevelDetails, updateTime, originalHtml, isFromCache = false) {
        if (!trustLevelDetails || !trustLevelDetails.items || trustLevelDetails.items.length === 0) {
            showPopupError('无法加载数据', '未能获取到信任级别详情数据,请刷新重试。', updateTime);
            return;
        }

        const achievedCount = trustLevelDetails.achievedCount;
        const totalCount = trustLevelDetails.totalCount;
        const progressPercent = Math.round((achievedCount / totalCount) * 100);

        const failedItems = trustLevelDetails.items.filter(item => !item.isMet);
        const failedItem = failedItems.length > 0 ? failedItems[0] : null;

        function getIconSvg(type) {
            const icons = {
                user: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>',
                message: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-3.582 8-8 8a8.991 8.991 0 01-4.92-1.487L3 21l2.513-5.08A8.991 8.991 0 013 12c0-4.418 3.582-8 8-8s8 3.582 8 8z"></path>',
                eye: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>',
                thumbsUp: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"></path>',
                warning: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>',
                shield: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>'
            };
            return icons[type] || icons.user;
        }

        function getItemIcon(label) {
            if (label.includes('访问次数')) return 'user';
            if (label.includes('回复') || label.includes('话题')) return 'message';
            if (label.includes('浏览') || label.includes('已读')) return 'eye';
            if (label.includes('举报')) return 'warning';
            if (label.includes('点赞') || label.includes('获赞')) return 'thumbsUp';
            if (label.includes('禁言') || label.includes('封禁')) return 'shield';
            return 'user';
        }

        let html = `
            <div class="ld-popup-header">
                <div class="ld-header-top">
                    <div class="ld-user-info">
                        <div class="ld-user-dot"></div>
                        <span class="ld-user-name">${username || '用户'}</span>
                    </div>
                    <span class="ld-level-badge">升级到等级${targetLevel}</span>
                </div>
                <div class="ld-progress-section">
                    <div class="ld-progress-header">
                        <span class="ld-progress-label">完成进度</span>
                        <span class="ld-progress-stats">${achievedCount}/${totalCount}</span>
                    </div>
                    <div class="ld-progress-bar-container">
                        <div class="ld-progress-bar" style="width: ${progressPercent}%;"></div>
                    </div>
                </div>
            </div>
            <div class="ld-status-cards">`;

        if (failedItems.length > 0) {
            html += `
                <div class="ld-status-card failed">
                    <div class="ld-card-header failed">
                        <svg class="ld-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
                        </svg>
                        <span class="ld-card-title">未达标</span>
                    </div>
                    <div class="ld-card-label">${failedItem ? failedItem.label : '无'}</div>
                    <div class="ld-card-value">${failedItem ? failedItem.current : '所有要求均已满足'}</div>
                    ${failedItem ? `<div class="ld-card-subtitle failed">需要 ${failedItem.required}</div>` : ''}
                </div>
                <div class="ld-status-card passed">
                    <div class="ld-card-header passed">
                        <svg class="ld-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
                        </svg>
                        <span class="ld-card-title">已完成</span>
                    </div>
                    <div class="ld-card-label">其他要求</div>
                    <div class="ld-card-value">${achievedCount} / ${totalCount}</div>
                </div>`;
        } else {
            html += `
                <div class="ld-status-card passed" style="grid-column: span 2;">
                    <div class="ld-card-header passed">
                        <svg class="ld-card-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
                        </svg>
                        <span class="ld-card-title">全部达标!</span>
                    </div>
                    <div class="ld-card-value" style="font-size: 16px; margin-top: 8px;">🎉 恭喜!你已满足所有升级要求</div>
                </div>`;
        }

        html += `
            </div>
            <div class="ld-details-section">
                <div class="ld-details-list">`;

        trustLevelDetails.items.forEach(item => {
            const iconType = getItemIcon(item.label);
            html += `
                <div class="ld-detail-item">
                    <div class="ld-detail-left">
                        <svg class="ld-detail-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            ${getIconSvg(iconType)}
                        </svg>
                        <span class="ld-detail-label">${item.label}</span>
                    </div>
                    <div class="ld-detail-right">
                        <span class="ld-detail-current ${item.isMet ? 'passed' : 'failed'}">${item.current}</span>
                        <span class="ld-detail-target">/${item.required}</span>
                        <svg class="ld-detail-status ${item.isMet ? 'passed' : 'failed'}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            ${item.isMet ?
                                '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>' :
                                '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>'
                            }
                        </svg>
                    </div>
                </div>`;
        });

        html += `
                </div>
            </div>
            <div class="ld-popup-footer">
                <div class="ld-footer-message ${failedItems.length === 0 ? 'passed' : 'failed'}">
                    ${trustLevelDetails.summaryText || (failedItems.length === 0 ? '已满足信任级别要求' : '不符合信任级别要求,继续加油')}
                </div>
                <div class="ld-footer-time">更新于 ${updateTime.toLocaleString()}</div>
            </div>
            <button class="ld-reload-btn">刷新数据</button>`;

        popup.innerHTML = html;

        setTimeout(() => {
            const reloadBtn = popup.querySelector('.ld-reload-btn');
            if (reloadBtn) {
                reloadBtn.addEventListener('click', function() {
                    this.textContent = '加载中...';
                    this.disabled = true;
                    fetchDataWithGM();
                    setTimeout(() => {
                        if (!this.isConnected) return;
                        this.textContent = '刷新数据';
                        this.disabled = false;
                    }, 3000);
                });
            }
        }, 100);

        window.addEventListener('unload', () => {
            if (observer) {
                observer.disconnect();
                debugLog('MutationObserver已停止');
            }
            if (darkModeMediaQuery) {
                darkModeMediaQuery.removeEventListener('change', mediaQueryChangedCallback);
                debugLog('已移除 prefers-color-scheme 监听器 (卸载时)');
            }
            clearTimeout(observerDebounceTimeout);
            clearTimeout(hoverTimeout);
        });
    }

    function showPopupError(title, message, updateTime) {
        popup.innerHTML = `
            <div class="ld-error-container">
                <div class="ld-error-icon">❌</div>
                <div class="ld-error-title">${title}</div>
                <div class="ld-error-message">${message}</div>
                <div class="ld-footer-time">尝试时间: ${updateTime ? updateTime.toLocaleString() : '未知'}</div>
            </div>
            <button class="ld-reload-btn">重试</button>
        `;

        setTimeout(() => {
            const retryBtn = popup.querySelector('.ld-reload-btn');
            if (retryBtn) {
                retryBtn.addEventListener('click', function() {
                    this.textContent = '加载中...';
                    this.disabled = true;
                    fetchDataWithGM();
                    setTimeout(() => {
                        if (!this.isConnected) return;
                        this.textContent = '重试';
                        this.disabled = false;
                    }, 3000);
                });
            }
        }, 100);
    }

    function showError(message) {
        debugLog(`显示错误: ${message}`);
        showPopupError('出错了', message, new Date());
    }

    function handleRequestError(response, customVerifyUrl) {
        let responseBody = response.responseText || "";
        debugLog(`响应内容 (前500字符): ${responseBody.substring(0, 500)}`);

        const verifyUrl = customVerifyUrl || currentConfig.connectHost;

        if (response.status === 429) {
            showError('请求过于频繁 (429),请稍后重试。Cloudflare可能暂时限制了访问。');
        } else if (responseBody.includes('Cloudflare') || responseBody.includes('challenge-platform') || responseBody.includes('Just a moment')) {
             showError(`Cloudflare拦截或验证页面。请等待或手动访问 <a href="${verifyUrl}" target="_blank" style="color:white;text-decoration:underline;">${verifyUrl}</a> 完成验证。`);
        } else if (responseBody.includes('登录') || responseBody.includes('注册')) {
            showError(`获取数据失败,可能是需要登录 ${verifyUrl}。`);
        } else {
             showError(`获取数据失败 (状态: ${response.status})`);
        }
    }

    // 处理2级及以上用户数据
    function processHighLevelUserData(tempDiv, globalUsername, currentLevel) {
        let targetInfoDiv = null;
        const potentialDivs = tempDiv.querySelectorAll('div.bg-white.p-6.rounded-lg.mb-4.shadow');
        debugLog(`找到了 ${potentialDivs.length} 个潜在的 'div.bg-white.p-6.rounded-lg.mb-4.shadow' 元素。`);

        for (let i = 0; i < potentialDivs.length; i++) {
            const div = potentialDivs[i];
            const h2 = div.querySelector('h2.text-xl.mb-4.font-bold');
            if (h2 && h2.textContent.includes('信任级别')) {
                targetInfoDiv = div;
                debugLog(`找到包含"信任级别"标题的目标div,其innerHTML (前200字符): ${targetInfoDiv.innerHTML.substring(0,200)}`);
                break;
            }
        }

        if (!targetInfoDiv) {
            debugLog('通过遍历和内容检查,未找到包含"信任级别"标题的目标div。');
            showError('未找到包含等级信息的数据块。请检查控制台日志 (Alt+D) 中的HTML内容,并提供一个准确的选择器。');
            return;
        }

        debugLog('通过内容匹配,在响应中找到目标信息div。');
        const originalHtml = targetInfoDiv.innerHTML;

        let specificUsername = globalUsername;
        let targetLevel = '未知';
        const h2InDiv = targetInfoDiv.querySelector('h2.text-xl.mb-4.font-bold');
        if (h2InDiv) {
            const h2Text = h2InDiv.textContent.trim();
            const titleMatch = h2Text.match(/^(.+?)\s*-\s*信任级别\s*(\d+)\s*的要求/i);
            if (titleMatch) {
                specificUsername = titleMatch[1].trim();
                targetLevel = titleMatch[2];
                debugLog(`从<h2>解析: 特定用户名='${specificUsername}', 目标等级='${targetLevel}'`);
            } else {
                 debugLog(`从<h2>解析: 未匹配到标题格式: "${h2Text}"`);
            }
        } else {
            debugLog('目标div中未找到<h2>标签');
        }

        const trustLevelDetails = parseTrustLevelDetails(targetInfoDiv);

        debugLog(`最终提取信息: 用户名='${specificUsername}', 当前等级='${currentLevel}', 目标等级='${targetLevel}'`);
        updateInfo(specificUsername, currentLevel, targetLevel, trustLevelDetails, new Date(), originalHtml);
        saveDataToCache(specificUsername, currentLevel, targetLevel, trustLevelDetails, originalHtml);
    }

    function getCommonHeaders() {
        const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
        return {
            "X-CSRF-Token": csrfToken,
            "X-Requested-With": "XMLHttpRequest"
        };
    }

    // 处理0级和1级用户数据 (阶段1: 获取summary.json)
    function fetchLowLevelUserData(username, currentLevel) {
        debugLog(`开始获取${currentLevel}级用户 ${username} 的数据`);

        fetch(`${currentConfig.mainHost}/u/${username}/summary.json`, {
            method: 'GET',
            headers: getCommonHeaders(),
            credentials: 'include'
        })
        .then(response => {
            debugLog(`summary.json请求成功: status ${response.status}`);
            if (response.ok) {
                return response.json().then(data => {
                    const userSummary = data.user_summary;
                    debugLog(`获取到用户摘要数据: ${JSON.stringify(userSummary)}`);

                    if (currentLevel === 1) {
                        fetchUserRepliesData(username, currentLevel, userSummary);
                    } else {
                        processLowLevelUserData(username, currentLevel, userSummary, null);
                    }
                });
            } else {
                return response.text().then(text => {
                    debugLog(`summary.json请求失败: ${response.status}`);
                    handleRequestError({
                        status: response.status,
                        responseText: text
                    }, currentConfig.mainHost);
                });
            }
        })
        .catch(error => {
            debugLog(`summary.json请求错误: ${error}`);
            showError('获取用户数据时网络错误');
        });
    }

    // 处理1级用户数据 (阶段2: 获取回复数)
    function fetchUserRepliesData(username, currentLevel, userSummary) {
        debugLog(`获取用户 ${username} 的回复数据`);

        fetch(`${currentConfig.mainHost}/u/${username}/activity/replies`, {
            method: 'GET',
            headers: getCommonHeaders(),
            credentials: 'include'
        })
        .then(response => {
            debugLog(`replies页面请求成功: status ${response.status}`);
            if (response.ok) {
                return response.text().then(responseText => {
                    const tempDiv = document.createElement('div');
                    tempDiv.innerHTML = responseText;

                    const replyContainer = tempDiv.querySelector('#main-outlet div:nth-child(3) section div');
                    let repliesCount = 0;

                    if (replyContainer) {
                        const replyItems = replyContainer.querySelectorAll('#user-content > div > div:nth-child(1) > div');
                        repliesCount = Math.min(replyItems.length, 3);
                        debugLog(`找到 ${replyItems.length} 个回复项,统计 ${repliesCount} 个`);
                    } else {
                        debugLog('未找到回复容器');
                    }

                    processLowLevelUserData(username, currentLevel, userSummary, repliesCount);
                });
            } else {
                debugLog(`replies页面请求失败: ${response.status}`);
                processLowLevelUserData(username, currentLevel, userSummary, 0);
            }
        })
        .catch(error => {
            debugLog(`replies页面请求错误: ${error}`);
            processLowLevelUserData(username, currentLevel, userSummary, 0);
        });
    }

    // 根据获取的数据构建0/1级用户的升级详情
    function processLowLevelUserData(username, currentLevel, userSummary, repliesCount) {
        debugLog(`处理${currentLevel}级用户数据: ${username}`);

        const targetLevel = currentLevel + 1;
        const requirements = LEVEL_REQUIREMENTS[currentLevel];

        if (!requirements) {
            showError(`未找到等级${currentLevel}的升级要求配置`);
            return;
        }

        const trustLevelDetails = {
            items: [],
            summaryText: '',
            achievedCount: 0,
            totalCount: 0,
            targetLevelInSummary: targetLevel.toString()
        };

        Object.entries(requirements).forEach(([key, requiredValue]) => {
            let currentValue = 0;
            let label = '';
            let isMet = false;

            switch (key) {
                case 'topics_entered':
                    currentValue = userSummary.topics_entered || 0;
                    label = '浏览的话题';
                    isMet = currentValue >= requiredValue;
                    break;
                case 'posts_read_count':
                    currentValue = userSummary.posts_read_count || 0;
                    label = '已读帖子';
                    isMet = currentValue >= requiredValue;
                    break;
                case 'time_read':
                    currentValue = Math.floor((userSummary.time_read || 0) / 60);
                    label = '阅读时间(分钟)';
                    isMet = (userSummary.time_read || 0) >= requiredValue;
                    break;
                case 'days_visited':
                    currentValue = userSummary.days_visited || 0;
                    label = '访问天数';
                    isMet = currentValue >= requiredValue;
                    break;
                case 'likes_given':
                    currentValue = userSummary.likes_given || 0;
                    label = '给出的赞';
                    isMet = currentValue >= requiredValue;
                    break;
                case 'likes_received':
                    currentValue = userSummary.likes_received || 0;
                    label = '收到的赞';
                    isMet = currentValue >= requiredValue;
                    break;
                case 'replies_to_different_topics':
                    currentValue = repliesCount || 0;
                    label = '回复不同话题';
                    isMet = currentValue >= requiredValue;
                    break;
            }

            if (label) {
                trustLevelDetails.items.push({
                    label: label,
                    current: currentValue.toString(),
                    required: key === 'time_read' ? Math.floor(requiredValue / 60).toString() : requiredValue.toString(),
                    isMet: isMet
                });

                if (isMet) {
                    trustLevelDetails.achievedCount++;
                }
                trustLevelDetails.totalCount++;
            }
        });

        if (trustLevelDetails.achievedCount === trustLevelDetails.totalCount) {
            trustLevelDetails.summaryText = `已满足信任级别 ${targetLevel} 要求`;
        } else {
            trustLevelDetails.summaryText = `不符合信任级别 ${targetLevel} 要求,继续加油`;
        }

        debugLog(`${currentLevel}级用户数据处理完成: ${trustLevelDetails.achievedCount}/${trustLevelDetails.totalCount} 项达标`);

        updateInfo(username, currentLevel.toString(), targetLevel.toString(), trustLevelDetails, new Date(), '', false);
        saveDataToCache(username, currentLevel.toString(), targetLevel.toString(), trustLevelDetails, '');
    }
})();
152 个赞

发代码块格式的,就可以直接复制了

上下三点这样子

image

14 个赞

感谢佬斧正 已修改

11 个赞

感谢大佬

8 个赞

非常感谢

2 个赞

感谢分享,我觉得脚本如果直接作用到connect那边过去就最好了

8 个赞

感谢大佬

3 个赞

感谢大佬

2 个赞

感谢大佬!

4 个赞

佬,手机有没有正确的打开方式

3 个赞

感谢大佬的脚本!!!

1 个赞

这个“回复不同的话题”是指啥,为什么会是0

2 个赞

感谢大佬

1 个赞

感谢佬友 :face_blowing_a_kiss:

1 个赞

感谢佬友

7 个赞

感谢佬友

1 个赞

同问,我也是

image
有佬解答一下这个是啥意思吗,是要回复什么特别的吗

2 个赞

在L站,话题指的是主贴本身,例如“升级要求查看”就是一个话题,而点进主贴(话题)后你可以看到很多回复,每一条回复就被称之为一个“帖子”。
希望这个解释可以让你清晰一些,帮助你对升级的要求有一些概念。

本文借鉴了fanite用户的解释,感谢他。

8 个赞

在L站,话题指的是主贴本身,例如“升级要求查看”就是一个话题,你在这个话题下进行了回复,就会增加1,回复内容是不限制的,当然前提是遵循我们社区的规则,所以说,你回复了这个话题就会增加1,变成1/3;

1级用户只能用脚本进行查看自己差哪些条件才能升级;

关于如何使用脚本的问题,因为可能是大家默认的前置知识,所以我分享一下细节,若你是用chrome浏览器则需要在扩展程序里搜索篡改猴(也被大家称为油猴),4.7星,7.3w个评分的那个就没有错,下载完之后打开更多选项里的选项,找到添加新脚本页面,把所有内容删除后,复制粘贴版主给的内容,再找到文件下的保存选项,你就可以发现在已安装脚本里有一个名字为“L站/IF站 等级监控浮窗”的脚本,这时候打开L站,启用脚本就可以看到1级升2级的所有要求,按照这个要求去升级就好了

1 个赞