AutoDL 自动刷新实例 - 防止回收

自动刷新AutoDL平台长时间关机的实例,防止被平台回收。支持自定义天数阈值和批量操作。

// ==UserScript==
// @name         AutoDL 自动刷新实例 - 防止回收
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  自动刷新AutoDL平台长时间关机的实例,防止被平台回收。支持自定义天数阈值和批量操作。
// @author       AutoDL助手
// @match        https://www.autodl.com/*
// @icon         https://www.autodl.com/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 添加样式
    GM_addStyle(`
        .autodl-refresh-panel {
            position: fixed;
            top: 20px;
            right: 20px;
            width: 280px;
            background: white;
            border: 2px solid #007bff;
            border-radius: 10px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 10000;
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            cursor: move;
            user-select: none;
        }

        .autodl-refresh-panel.expanded {
            width: 350px;
        }

        .autodl-refresh-panel.dragging {
            opacity: 0.8;
            box-shadow: 0 8px 24px rgba(0,0,0,0.25);
        }

        .autodl-refresh-header {
            background: linear-gradient(135deg, #007bff, #0056b3);
            color: white;
            padding: 12px 15px;
            border-radius: 8px 8px 0 0;
            font-weight: bold;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: move;
        }

        .autodl-refresh-close {
            background: none;
            border: none;
            color: white;
            font-size: 18px;
            cursor: pointer;
            padding: 0;
            width: 20px;
            height: 20px;
            cursor: pointer;
        }

        .autodl-refresh-expand {
            background: none;
            border: none;
            color: white;
            font-size: 20px;
            cursor: pointer;
            padding: 0;
            width: 24px;
            height: 24px;
            cursor: pointer;
            margin-right: 8px;
        }

        .autodl-refresh-expand:hover {
            opacity: 0.8;
        }

        .autodl-expand-hint {
            font-size: 10px;
            color:rgb(255, 0, 0);
            text-align: center;
            margin-top: 2px;
            margin-bottom: 0;
            line-height: 1.2;
        }

        .autodl-warning-counter {
            background: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 5px;
            padding: 6px 8px;
            margin: 6px 0;
            text-align: center;
            font-size: 12px;
            font-weight: bold;
        }

        .autodl-warning-counter.safe {
            color: #28a745;
            border-color: #28a745;
            background: #d4f5d4;
        }

        .autodl-warning-counter.warning {
            color: #dc3545;
            border-color: #dc3545;
            background: #f8d7da;
        }

        .autodl-refresh-content {
            padding: 10px;
            cursor: default;
        }

        .autodl-refresh-input-group {
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .autodl-refresh-input-group label {
            display: block;
            margin-bottom: 0;
            font-weight: bold;
            color: #333;
            font-size: 13px;
            white-space: nowrap;
            min-width: 60px;
        }

        .autodl-refresh-input-group input {
            flex: 1;
            padding: 6px 8px;
            border: 1px solid #ddd;
            border-radius: 5px;
            box-sizing: border-box;
            font-size: 13px;
            cursor: text;
        }

        .autodl-refresh-btn {
            width: 100%;
            padding: 8px;
            background: linear-gradient(135deg, #28a745, #20c997);
            color: white;
            border: none;
            border-radius: 5px;
            font-size: 13px;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 6px;
            transition: all 0.3s ease;
        }

        .autodl-refresh-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0,0,0,0.2);
        }

        .autodl-refresh-btn:disabled {
            background: #6c757d;
            cursor: not-allowed;
            transform: none;
        }

        .autodl-refresh-log {
            max-height: 200px;
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 10px;
            background: #f8f9fa;
            font-size: 12px;
            line-height: 1.4;
        }

        .autodl-refresh-log-item {
            margin-bottom: 5px;
            padding: 3px 0;
        }

        .autodl-refresh-log-success {
            color: #28a745;
        }

        .autodl-refresh-log-error {
            color: #dc3545;
        }

        .autodl-refresh-log-warning {
            color: #ffc107;
        }

        .autodl-refresh-log-info {
            color: #17a2b8;
        }

        .autodl-refresh-log-debug {
            color: #6c757d;
            font-style: italic;
        }

        .autodl-refresh-status {
            text-align: center;
            padding: 8px;
            margin: 8px 0;
            border-radius: 5px;
            font-weight: bold;
        }

        .autodl-refresh-status.running {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }

        .autodl-refresh-status.completed {
            background: #d1ecf1;
            color: #0c5460;
            border: 1px solid #bee5eb;
        }

        .autodl-refresh-status.error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }

        .autodl-refresh-debug-info {
            background: #e9ecef;
            border: 1px solid #dee2e6;
            border-radius: 5px;
            padding: 8px;
            margin: 8px 0;
            font-size: 11px;
            color: #495057;
        }

        .autodl-expanded-content {
            display: none;
        }

        .autodl-expanded-content.visible {
            display: block;
        }

        /* 展开模式下恢复垂直布局 */
        .autodl-refresh-panel.expanded .autodl-refresh-input-group {
            display: block;
        }

        .autodl-refresh-panel.expanded .autodl-refresh-input-group label {
            margin-bottom: 3px;
            min-width: auto;
        }

        .autodl-refresh-panel.expanded .autodl-refresh-input-group input {
            width: 100%;
            flex: none;
        }

        .autodl-confirm-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.5);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 20000;
        }

        .autodl-confirm-content {
            background: white;
            border-radius: 10px;
            padding: 25px;
            max-width: 500px;
            width: 90%;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            text-align: center;
        }

        .autodl-confirm-title {
            font-size: 18px;
            font-weight: bold;
            color: #dc3545;
            margin-bottom: 15px;
        }

        .autodl-confirm-message {
            font-size: 14px;
            line-height: 1.6;
            color: #333;
            margin-bottom: 20px;
            text-align: left;
        }

        .autodl-confirm-instances {
            background: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 5px;
            padding: 10px;
            margin: 10px 0;
            font-family: monospace;
            font-size: 12px;
            color: #495057;
        }

        .autodl-confirm-note {
            font-size: 12px;
            color: #6c757d;
            font-style: italic;
            margin-top: 10px;
        }

        .autodl-confirm-buttons {
            display: flex;
            gap: 15px;
            justify-content: center;
            margin-top: 20px;
        }

        .autodl-confirm-btn {
            padding: 10px 25px;
            border: none;
            border-radius: 5px;
            font-size: 14px;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.3s ease;
        }

        .autodl-confirm-btn.confirm {
            background: linear-gradient(135deg, #dc3545, #c82333);
            color: white;
        }

        .autodl-confirm-btn.confirm:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
        }

        .autodl-confirm-btn.cancel {
            background: linear-gradient(135deg, #6c757d, #5a6268);
            color: white;
        }

        .autodl-confirm-btn.cancel:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(108, 117, 125, 0.3);
        }
    `);

    // AutoDL API 基础URL
    const BASE_URL = "https://www.autodl.com/api/v1";

    // API 请求头
    const HEADERS = {
        "Content-Type": "application/json;charset=UTF-8",
        "Accept": "*/*",
        "appversion": "v5.95.2",
        "Origin": "https://www.autodl.com",
        "Referer": "https://www.autodl.com/login"
    };

    // 调试模式开关
    const DEBUG_MODE = true;

    // 调试日志函数
    function debugLog(message, data = null) {
        if (DEBUG_MODE) {
            const timestamp = new Date().toLocaleTimeString();
            console.log(`[AutoDL Debug ${timestamp}] ${message}`);
            if (data) {
                console.log('Debug Data:', data);
            }
        }
    }

    // SHA1 哈希函数
    async function sha1Hash(text) {
        try {
            debugLog(`开始对密码进行SHA1加密...`);
            const encoder = new TextEncoder();
            const data = encoder.encode(text);
            const hashBuffer = await crypto.subtle.digest('SHA-1', data);
            const hashArray = Array.from(new Uint8Array(hashBuffer));
            const hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
            debugLog(`密码SHA1加密完成: ${hash.substring(0, 8)}...`);
            return hash;
        } catch (error) {
            debugLog(`SHA1加密失败: ${error.message}`, error);
            throw error;
        }
    }

    // 登录并获取token
    async function loginAndGetToken(phone, plaintextPassword) {
        try {
            debugLog(`开始登录流程,手机号: ${phone}`);

            // 对明文密码进行SHA1加密
            const encryptedPassword = await sha1Hash(plaintextPassword);

            // 步骤1: 登录获取 ticket
            addLog("正在进行登录...", "info");
            debugLog(`发送登录请求到: ${BASE_URL}/new_login`);

            const loginPayload = {
                phone: phone,
                password: encryptedPassword,
                v_code: "",
                phone_area: "+86",
                picture_id: null
            };
            debugLog('登录请求参数:', loginPayload);

            const loginResponse = await fetch(`${BASE_URL}/new_login`, {
                method: 'POST',
                headers: HEADERS,
                body: JSON.stringify(loginPayload)
            });

            debugLog(`登录响应状态: ${loginResponse.status}`);
            const loginData = await loginResponse.json();
            debugLog('登录响应数据:', loginData);

            if (loginData.code !== "Success") {
                throw new Error(`登录失败: ${loginData.msg || '未知错误'}`);
            }

            const ticket = loginData.data.ticket;
            addLog(`✅ 成功获取到 Ticket: ${ticket}`, "success");
            debugLog(`获取到Ticket: ${ticket}`);

            // 步骤2: 使用 ticket 换取 token
            addLog("正在换取Token...", "info");
            debugLog(`发送passport请求到: ${BASE_URL}/passport`);

            const passportPayload = { ticket: ticket };
            debugLog('Passport请求参数:', passportPayload);

            const passportResponse = await fetch(`${BASE_URL}/passport`, {
                method: 'POST',
                headers: HEADERS,
                body: JSON.stringify(passportPayload)
            });

            debugLog(`Passport响应状态: ${passportResponse.status}`);
            const passportData = await passportResponse.json();
            debugLog('Passport响应数据:', passportData);

            if (passportData.code !== "Success") {
                throw new Error(`换取 Token 失败: ${passportData.msg || '未知错误'}`);
            }

            const token = passportData.data.token;
            addLog("🎉 成功获取到 Token!", "success");
            debugLog(`获取到Token: ${token.substring(0, 20)}...`);
            return token;

        } catch (error) {
            debugLog(`登录过程出错: ${error.message}`, error);
            addLog(`❌ 登录失败: ${error.message}`, "error");
            throw error;
        }
    }

    // 获取所有计算实例的列表
    async function getInstances(headers) {
        try {
            debugLog(`开始获取实例列表...`);
            addLog("正在获取实例列表...", "info");

            const response = await fetch(`${BASE_URL}/instance`, {
                method: 'POST',
                headers: headers,
                body: JSON.stringify({})
            });

            debugLog(`获取实例响应状态: ${response.status}`);
            const data = await response.json();
            debugLog('实例列表响应数据:', data);

            if (data.code === "Success") {
                const instances = data.data.list;
                debugLog(`成功获取到 ${instances.length} 个实例`);
                addLog(`✅ 成功获取到 ${instances.length} 个实例`, "success");
                return instances;
            } else {
                throw new Error(`获取实例列表失败: ${JSON.stringify(data)}`);
            }
        } catch (error) {
            debugLog(`获取实例列表失败: ${error.message}`, error);
            addLog(`❌ 获取实例列表失败: ${error.message}`, "error");
            throw error;
        }
    }

    // 开启指定的计算实例
    async function powerOnInstance(headers, instanceUuid) {
        try {
            debugLog(`开始开机实例: ${instanceUuid}`);
            addLog(`正在开机实例 ${instanceUuid}...`, "info");

            const payload = {
                instance_uuid: instanceUuid,
                payload: "non_gpu"
            };
            debugLog('开机请求参数:', payload);

            const response = await fetch(`${BASE_URL}/instance/power_on`, {
                method: 'POST',
                headers: headers,
                body: JSON.stringify(payload)
            });

            debugLog(`开机响应状态: ${response.status}`);
            const data = await response.json();
            debugLog('开机响应数据:', data);

            if (data.code === "Success") {
                addLog(`实例 ${instanceUuid} 成功开机。`, "success");
                debugLog(`实例 ${instanceUuid} 开机成功`);
            } else {
                throw new Error(`开机失败: ${JSON.stringify(data)}`);
            }
        } catch (error) {
            debugLog(`开机失败 ${instanceUuid}: ${error.message}`, error);
            addLog(`❌ 开机失败 ${instanceUuid}: ${error.message}`, "error");
            throw error;
        }
    }

    // 关闭指定的计算实例
    async function powerOffInstance(headers, instanceUuid) {
        try {
            debugLog(`开始关机实例: ${instanceUuid}`);
            addLog(`正在关机实例 ${instanceUuid}...`, "info");

            const payload = {
                instance_uuid: instanceUuid
            };
            debugLog('关机请求参数:', payload);

            const response = await fetch(`${BASE_URL}/instance/power_off`, {
                method: 'POST',
                headers: headers,
                body: JSON.stringify(payload)
            });

            debugLog(`关机响应状态: ${response.status}`);
            const data = await response.json();
            debugLog('关机响应数据:', data);

            if (data.code === "Success") {
                addLog(`实例 ${instanceUuid} 成功关机。`, "success");
                debugLog(`实例 ${instanceUuid} 关机成功`);
            } else {
                throw new Error(`关机失败: ${JSON.stringify(data)}`);
            }
        } catch (error) {
            debugLog(`关机失败 ${instanceUuid}: ${error.message}`, error);
            addLog(`❌ 关机失败 ${instanceUuid}: ${error.message}`, "error");
            throw error;
        }
    }

    // 等待函数
    function sleep(ms) {
        debugLog(`等待 ${ms} 毫秒...`);
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 添加日志
    function addLog(message, type = "info") {
        const timestamp = new Date().toLocaleTimeString();
        const logMessage = `[${timestamp}] ${message}`;

        // 添加到控制面板
        const logContainer = document.getElementById('autodl-refresh-log');
        if (logContainer) {
            const logItem = document.createElement('div');
            logItem.className = `autodl-refresh-log-item autodl-refresh-log-${type}`;
            logItem.textContent = logMessage;
            logContainer.appendChild(logItem);
            logContainer.scrollTop = logContainer.scrollHeight;
        }

        // 同时输出到控制台
        console.log(`[AutoDL ${type.toUpperCase()}] ${message}`);
    }

    // 更新状态
    function updateStatus(message, type = "info") {
        debugLog(`更新状态: ${message} (${type})`);
        const statusElement = document.getElementById('autodl-refresh-status');
        if (statusElement) {
            statusElement.textContent = message;
            statusElement.className = `autodl-refresh-status ${type}`;
        }
    }

    // 显示调试信息
    function showDebugInfo() {
        const debugContainer = document.getElementById('autodl-refresh-debug');
        if (debugContainer) {
            debugContainer.innerHTML = `
                <div class="autodl-refresh-debug-info">
                    <strong>调试信息:</strong><br>
                    - 脚本版本: 1.0.0<br>
                    - 调试模式: ${DEBUG_MODE ? '开启' : '关闭'}<br>
                    - 当前时间: ${new Date().toLocaleString()}<br>
                    - 页面URL: ${window.location.href}<br>
                    - 用户代理: ${navigator.userAgent.substring(0, 50)}...
                </div>
            `;
        }
    }

    // 显示确认弹框
    function showConfirmModal(runningInstances, callback) {
        const modal = document.createElement('div');
        modal.className = 'autodl-confirm-modal';

        const instanceList = runningInstances.map(inst => inst.uuid).join('\n');

        modal.innerHTML = `
            <div class="autodl-confirm-content">
                <div class="autodl-confirm-title">⚠️ 发现无卡模式实例冲突</div>
                <div class="autodl-confirm-message">
                    发现 ${runningInstances.length} 个无卡模式实例正在运行:
                    <div class="autodl-confirm-instances">${instanceList}</div>
                    是否关闭这些实例并执行其他符合条件的开关机操作?
                </div>
                <div class="autodl-confirm-note">
                    注:刚关闭的实例不会参与后续的开关机操作
                </div>
                <div class="autodl-confirm-buttons">
                    <button class="autodl-confirm-btn confirm">确认关闭并执行</button>
                    <button class="autodl-confirm-btn cancel">取消操作</button>
                </div>
            </div>
        `;

        document.body.appendChild(modal);

        // 绑定按钮事件
        modal.querySelector('.autodl-confirm-btn.confirm').addEventListener('click', () => {
            modal.remove();
            callback(true); // 用户确认
        });

        modal.querySelector('.autodl-confirm-btn.cancel').addEventListener('click', () => {
            modal.remove();
            callback(false); // 用户取消
        });

        // 点击背景关闭
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                modal.remove();
                callback(false);
            }
        });
    }

    // 检查是否有无卡模式实例运行
    function checkRunningNonGpuInstances(instances) {
        // 这里需要根据实际的API响应结构调整
        // 假设实例对象中有 start_mode 字段表示启动模式
        return instances.filter(inst =>
            inst.status === "running" &&
            (inst.start_mode === "non_gpu" || inst.payload === "non_gpu")
        );
    }

    // 检查即将被释放的实例数量
    function checkAndUpdateWarningCounter(instances) {
        try {
            debugLog('开始检查即将被释放的实例...');
            let warningCount = 0;
            const today = new Date();

            for (const inst of instances) {
                if (inst.status === "shutdown") {
                    const stoppedInfo = inst.stopped_at;

                    if (stoppedInfo && stoppedInfo.Valid && stoppedInfo.Time) {
                        try {
                            const stoppedTime = new Date(stoppedInfo.Time);
                            const daysDiff = Math.floor((today - stoppedTime) / (1000 * 60 * 60 * 24));

                            // 检查是否关机了9天或更多(还有5天或更少会被释放)
                            if (daysDiff >= 9) {
                                warningCount++;
                                debugLog(`发现即将被释放的实例: ${inst.uuid}, 关机天数: ${daysDiff}`);
                            }
                        } catch (error) {
                            debugLog(`解析实例 ${inst.uuid} 时间出错: ${error.message}`);
                        }
                    }
                }
            }

            // 更新前端显示
            updateWarningCounter(warningCount);
            debugLog(`检查完成,发现 ${warningCount} 个即将被释放的实例`);

        } catch (error) {
            debugLog(`检查即将被释放实例时出错: ${error.message}`, error);
            updateWarningCounter(-1); // 显示错误状态
        }
    }

    // 更新警告计数器显示
    function updateWarningCounter(count) {
        const counterElement = document.getElementById('autodl-warning-counter');
        const countElement = document.getElementById('autodl-warning-count');

        if (!counterElement || !countElement) return;

        if (count === -1) {
            // 错误状态
            countElement.textContent = '检查失败';
            counterElement.className = 'autodl-warning-counter warning';
        } else if (count === 0) {
            // 安全状态
            countElement.textContent = '0';
            counterElement.className = 'autodl-warning-counter safe';
        } else {
            // 警告状态
            countElement.textContent = count.toString();
            counterElement.className = 'autodl-warning-counter warning';
        }
    }

    // 独立检查实例状态(不执行刷新任务)
    async function checkInstanceStatus() {
        const phone = document.getElementById('autodl-phone').value.trim();
        const password = document.getElementById('autodl-password').value.trim();

        if (!phone || !password) {
            updateWarningCounter(-1);
            return;
        }

        try {
            updateWarningCounter(-1); // 显示检查中状态
            document.getElementById('autodl-warning-count').textContent = '检查中...';

            // 登录并获取实例列表
            const token = await loginAndGetToken(phone, password);
            const authHeaders = { ...HEADERS, "Authorization": token };
            const instances = await getInstances(authHeaders);

            // 检查即将被释放的实例数量
            checkAndUpdateWarningCounter(instances);

        } catch (error) {
            debugLog(`独立检查实例状态失败: ${error.message}`, error);
            updateWarningCounter(-1);
        }
    }

    // 初始化展开内容
    function initializeExpandedContent() {
        // 显示调试信息
        showDebugInfo();

        // 从存储中获取保存的值
        const savedPhone = GM_getValue('phone', '');
        const savedPassword = GM_getValue('password', '');

        // 如果已有保存的账号密码,显示提示
        if (savedPhone && savedPassword) {
            addLog("✅ 已加载保存的账户信息", "success");
            addLog(`📱 手机号: ${savedPhone}`, "info");
            addLog("🔐 密码已保存,可直接执行", "info");
        } else {
            addLog("💡 首次使用,请填写账户信息并点击'保存设置'", "info");
        }

        addLog("🚀 AutoDL自动刷新插件已加载", "success");
        addLog("💡 功能说明:自动刷新关机超过指定天数的实例,防止被平台回收", "info");
        addLog("🔍 调试模式已开启,详细日志请查看浏览器控制台", "debug");
    }

    // 主执行函数
    async function executeAutoRefresh() {
        debugLog('开始执行自动刷新流程');
        console.log('🚀 ===== AutoDL 自动刷新开始 =====');

        // 展开面板显示完整内容
        const panel = document.querySelector('.autodl-refresh-panel');
        const expandedContent = document.querySelector('.autodl-expanded-content');
        const expandBtn = document.getElementById('autodl-expand-btn');
        if (panel && expandedContent) {
            panel.classList.add('expanded');
            expandedContent.classList.add('visible');
            if (expandBtn) expandBtn.textContent = '⬆️';
            document.getElementById('autodl-expand-hint').style.display = 'none'; // 隐藏提示
            // 初始化展开内容
            initializeExpandedContent();
        }

        const phone = document.getElementById('autodl-phone').value.trim();
        const password = document.getElementById('autodl-password').value.trim();
        const daysThreshold = parseInt(document.getElementById('autodl-days').value.trim());

        debugLog(`输入参数 - 手机号: ${phone}, 天数阈值: ${daysThreshold}`);

        if (!phone || !password || isNaN(daysThreshold)) {
            addLog("❌ 请填写完整的手机号、密码和天数阈值", "error");
            console.error("❌ 参数验证失败");
            return;
        }

        const startBtn = document.getElementById('autodl-start-btn');
        startBtn.disabled = true;
        startBtn.textContent = '执行中...';

        try {
            updateStatus("正在登录...", "running");
            console.log('📱 开始登录流程...');

            // 登录并获取 token
            const token = await loginAndGetToken(phone, password);

            // 使用 token 更新请求头
            const authHeaders = { ...HEADERS, "Authorization": token };
            debugLog('已更新请求头,包含Authorization token');

            updateStatus("正在获取实例列表...", "running");
            console.log('📋 开始获取实例列表...');

            // 获取实例列表
            const instances = await getInstances(authHeaders);

            // 检查即将被释放的实例数量
            checkAndUpdateWarningCounter(instances);

            // 检查是否有无卡模式实例运行
            const runningNonGpu = checkRunningNonGpuInstances(instances);
            const excludedInstances = []; // 记录要排除的实例

            if (runningNonGpu.length > 0) {
                addLog(`⚠️ 发现 ${runningNonGpu.length} 个无卡模式实例正在运行`, "warning");
                console.log('⚠️ 发现无卡模式实例冲突:', runningNonGpu);

                // 显示确认弹框
                return new Promise((resolve) => {
                    showConfirmModal(runningNonGpu, async (confirmed) => {
                        if (!confirmed) {
                            addLog("❌ 用户取消了操作", "warning");
                            updateStatus("操作已取消", "error");
                            startBtn.disabled = false;
                            startBtn.textContent = '开始执行';
                            resolve();
                            return;
                        }

                        // 用户确认,继续执行
                        addLog("✅ 用户确认关闭冲突实例", "success");

                        // 关闭冲突实例
                        for (const inst of runningNonGpu) {
                            try {
                                addLog(`正在关闭冲突实例: ${inst.uuid}`, "info");
                                await powerOffInstance(authHeaders, inst.uuid);
                                excludedInstances.push(inst.uuid);
                                await sleep(5000); // 等待5秒
                            } catch (error) {
                                addLog(`❌ 关闭冲突实例失败: ${inst.uuid}`, "error");
                            }
                        }

                        addLog(`✅ 已关闭 ${excludedInstances.length} 个冲突实例`, "success");

                        // 继续执行原有的开关机逻辑
                        await continueRefreshProcess(instances, excludedInstances, daysThreshold, authHeaders, startBtn);
                        resolve();
                    });
                });
            } else {
                // 没有冲突,直接执行
                await continueRefreshProcess(instances, excludedInstances, daysThreshold, authHeaders, startBtn);
            }

        } catch (error) {
            console.error('❌ 程序运行出错:', error);
            addLog(`❌ 程序运行出错: ${error.message}`, "error");
            updateStatus("执行失败", "error");
            startBtn.disabled = false;
            startBtn.textContent = '开始执行';
        }
    }

    // 继续执行刷新流程
    async function continueRefreshProcess(instances, excludedInstances, daysThreshold, authHeaders, startBtn) {
        try {
            // 筛选关机时长 >= 阈值的实例(排除冲突实例)
            console.log('🔍 开始筛选符合条件的实例...');
            const uuidsToRefresh = [];
            let totalShutdown = 0;

            // 将用户输入的"还有几天被释放"转换为"已关机几天"
            // AutoDL规则:关机14天后被释放,所以实际检查天数 = 14 - 用户输入的剩余天数
            const actualDaysThreshold = 14 - daysThreshold;
            addLog(`用户设置:还有${daysThreshold}天被释放,对应检查关机${actualDaysThreshold}天的实例`, "info");

            for (const inst of instances) {
                // 排除冲突实例
                if (excludedInstances.includes(inst.uuid)) {
                    debugLog(`跳过冲突实例: ${inst.uuid}`);
                    continue;
                }

                debugLog(`检查实例: ${inst.uuid}, 状态: ${inst.status}`);

                if (inst.status === "shutdown") {
                    totalShutdown++;
                    const stoppedInfo = inst.stopped_at;
                    debugLog(`实例 ${inst.uuid} 关机信息:`, stoppedInfo);

                    if (stoppedInfo && stoppedInfo.Valid && stoppedInfo.Time) {
                        try {
                            const stoppedTime = new Date(stoppedInfo.Time);
                            const now = new Date();
                            const daysDiff = Math.floor((now - stoppedTime) / (1000 * 60 * 60 * 24));
                            const remainingDays = 14 - daysDiff;

                            debugLog(`实例 ${inst.uuid} 关机天数: ${daysDiff} 天,还有 ${remainingDays} 天被释放`);

                            if (daysDiff >= actualDaysThreshold) {
                                uuidsToRefresh.push(inst.uuid);
                                addLog(`�� 发现符合条件的实例: ${inst.uuid} (关机${daysDiff}天,还有${remainingDays}天被释放)`, "info");
                            }
                        } catch (error) {
                            debugLog(`解析实例 ${inst.uuid} 关机时间出错:${error.message}`, error);
                            addLog(`⚠️ 解析实例 ${inst.uuid} 关机时间出错:${error.message}`, "warning");
                        }
                    }
                }
            }

            console.log(`📊 统计信息: 总实例 ${instances.length}, 关机实例 ${totalShutdown}, 冲突实例 ${excludedInstances.length}, 需要刷新 ${uuidsToRefresh.length}`);

            if (uuidsToRefresh.length === 0) {
                addLog(`🎉 没有找到还有${daysThreshold}天或更少会被释放的实例,无需刷新。`, "success");
                updateStatus("无需刷新", "completed");
                console.log('✅ 无需刷新任何实例');
                return;
            }

            addLog(`共找到 ${uuidsToRefresh.length} 个还有${daysThreshold}天或更少会被释放的实例,即将执行"五开模式"...`, "info");
            updateStatus(`正在处理 ${uuidsToRefresh.length} 个实例...`, "running");
            console.log(`🔄 开始处理 ${uuidsToRefresh.length} 个实例...`);

            // 执行开关机操作
            let successCount = 0;
            let errorCount = 0;

            for (let i = 0; i < uuidsToRefresh.length; i++) {
                const uuid = uuidsToRefresh[i];
                try {
                    console.log(`\n🔄 处理实例 ${i + 1}/${uuidsToRefresh.length}: ${uuid}`);
                    addLog(`\n--- 正在处理实例 ${uuid} (${i + 1}/${uuidsToRefresh.length}) ---`, "info");

                    // 开机
                    await powerOnInstance(authHeaders, uuid);
                    addLog("等待 10 秒...", "info");
                    await sleep(10000);

                    // 关机
                    await powerOffInstance(authHeaders, uuid);
                    addLog("等待 10 秒...", "info");
                    await sleep(10000);

                    successCount++;
                    console.log(`✅ 实例 ${uuid} 处理完成`);

                } catch (error) {
                    errorCount++;
                    console.error(`❌ 实例 ${uuid} 处理失败:`, error);
                    addLog(`❌ 操作实例 ${uuid} 时出现错误: ${error.message}`, "error");
                }
            }

            console.log(`\n📈 执行结果统计: 成功 ${successCount}, 失败 ${errorCount}`);
            addLog(`\n🎉 所有符合条件的实例已完成开关机操作。成功: ${successCount}, 失败: ${errorCount}`, "success");
            updateStatus("执行完成", "completed");

            // 发送通知
            GM_notification({
                text: `AutoDL自动刷新完成!处理了 ${uuidsToRefresh.length} 个实例,成功: ${successCount}, 失败: ${errorCount}`,
                title: "AutoDL助手",
                timeout: 5000
            });

        } catch (error) {
            console.error('❌ 刷新流程出错:', error);
            addLog(`❌ 刷新流程出错: ${error.message}`, "error");
            updateStatus("执行失败", "error");
        } finally {
            startBtn.disabled = false;
            startBtn.textContent = '开始执行';
            console.log('🏁 ===== AutoDL 自动刷新结束 =====');
        }
    }

    // 手动保存设置函数
    function saveCredentials() {
        const phone = document.getElementById('autodl-phone').value.trim();
        const password = document.getElementById('autodl-password').value.trim();
        const days = document.getElementById('autodl-days').value.trim();

        if (!phone || !password || !days) {
            // 展开面板以显示错误信息
            const panel = document.querySelector('.autodl-refresh-panel');
            const expandedContent = document.querySelector('.autodl-expanded-content');
            const expandBtn = document.getElementById('autodl-expand-btn');
            if (panel && expandedContent && !expandedContent.classList.contains('visible')) {
                panel.classList.add('expanded');
                expandedContent.classList.add('visible');
                if (expandBtn) expandBtn.textContent = '⬆️';
                document.getElementById('autodl-expand-hint').style.display = 'none'; // 隐藏提示
                initializeExpandedContent();
            }
            addLog("❌ 请填写完整的手机号、密码和天数阈值", "error");
            return;
        }

        // 展开面板以显示保存确认信息
        const panel = document.querySelector('.autodl-refresh-panel');
        const expandedContent = document.querySelector('.autodl-expanded-content');
        const expandBtn = document.getElementById('autodl-expand-btn');
        if (panel && expandedContent && !expandedContent.classList.contains('visible')) {
            panel.classList.add('expanded');
            expandedContent.classList.add('visible');
            if (expandBtn) expandBtn.textContent = '⬆️';
            document.getElementById('autodl-expand-hint').style.display = 'none'; // 隐藏提示
            initializeExpandedContent();
        }

        GM_setValue('phone', phone);
        GM_setValue('password', password);
        GM_setValue('days', days);

        addLog("✅ 账户信息已保存!下次使用时将自动加载", "success");
        addLog("🔒 密码已加密存储在本地,请放心使用", "info");

        // 更新按钮状态
        const saveBtn = document.getElementById('autodl-save-btn');
        saveBtn.textContent = '已保存';
        saveBtn.style.background = 'linear-gradient(135deg, #28a745, #20c997)';

        setTimeout(() => {
            saveBtn.textContent = '保存设置';
            saveBtn.style.background = 'linear-gradient(135deg, #17a2b8, #138496)';
        }, 2000);
    }

    // 清除保存的账户信息
    function clearCredentials() {
        if (confirm('确定要清除保存的账户信息吗?这将删除您之前输入的手机号和密码。')) {
            GM_deleteValue('phone');
            GM_deleteValue('password');
            GM_deleteValue('days');
            addLog("✅ 账户信息已清除!", "success");
            addLog("💡 请重新填写账户信息以继续使用。", "info");

            // 清空输入框
            document.getElementById('autodl-phone').value = '';
            document.getElementById('autodl-password').value = '';
            document.getElementById('autodl-days').value = '5';
        }
    }

    // 测试连接函数
    async function testConnection() {
        const phone = document.getElementById('autodl-phone').value.trim();
        const password = document.getElementById('autodl-password').value.trim();
        const days = document.getElementById('autodl-days').value.trim();

        if (!phone || !password || !days) {
            // 展开面板以显示错误信息
            const panel = document.querySelector('.autodl-refresh-panel');
            const expandedContent = document.querySelector('.autodl-expanded-content');
            const expandBtn = document.getElementById('autodl-expand-btn');
            if (panel && expandedContent && !expandedContent.classList.contains('visible')) {
                panel.classList.add('expanded');
                expandedContent.classList.add('visible');
                if (expandBtn) expandBtn.textContent = '⬆️';
                document.getElementById('autodl-expand-hint').style.display = 'none'; // 隐藏提示
                initializeExpandedContent();
            }
            addLog("❌ 请填写完整的手机号、密码和天数阈值", "error");
            return;
        }

        // 展开面板以显示测试过程
        const panel = document.querySelector('.autodl-refresh-panel');
        const expandedContent = document.querySelector('.autodl-expanded-content');
        const expandBtn = document.getElementById('autodl-expand-btn');
        if (panel && expandedContent && !expandedContent.classList.contains('visible')) {
            panel.classList.add('expanded');
            expandedContent.classList.add('visible');
            if (expandBtn) expandBtn.textContent = '⬆️';
            document.getElementById('autodl-expand-hint').style.display = 'none'; // 隐藏提示
            initializeExpandedContent();
        }

        try {
            updateStatus("正在测试连接...", "running");
            console.log('🔗 开始测试连接...');

            // 尝试登录并获取实例列表
            const token = await loginAndGetToken(phone, password);
            const authHeaders = { ...HEADERS, "Authorization": token };
            const instances = await getInstances(authHeaders);

            addLog(`✅ 连接测试成功!可以正常登录和获取实例。`, "success");
            updateStatus("连接成功", "completed");
            GM_notification({
                text: "AutoDL连接测试成功!",
                title: "AutoDL助手",
                timeout: 3000
            });

        } catch (error) {
            addLog(`❌ 连接测试失败: ${error.message}`, "error");
            updateStatus("连接失败", "error");
            GM_notification({
                text: `AutoDL连接测试失败: ${error.message}`,
                title: "AutoDL助手",
                timeout: 5000
            });
        }
    }

    // 创建控制面板
    function createControlPanel() {
        const panel = document.createElement('div');
        panel.className = 'autodl-refresh-panel';

        // 从存储中获取保存的值
        const savedPhone = GM_getValue('phone', '');
        const savedPassword = GM_getValue('password', '');
        const savedDays = GM_getValue('days', '5');

        panel.innerHTML = `
            <div class="autodl-refresh-header">
                <span>🤖 AutoDL 自动刷新</span>
                <div>
                    <button class="autodl-refresh-expand" id="autodl-expand-btn">⬇️</button>
                    <button class="autodl-refresh-close">×</button>
                </div>
            </div>
            <div class="autodl-refresh-content">
                <div class="autodl-refresh-input-group">
                    <label for="autodl-phone">手机号:</label>
                    <input type="text" id="autodl-phone" placeholder="请输入手机号" value="${savedPhone}">
                </div>
                <div class="autodl-refresh-input-group">
                    <label for="autodl-password">密码:</label>
                    <input type="password" id="autodl-password" placeholder="请输入密码" value="${savedPassword}">
                </div>
                <div class="autodl-refresh-input-group">
                    <label for="autodl-days">天数阈值:</label>
                    <input type="number" id="autodl-days" placeholder="如: 5" value="${savedDays}" min="1" max="13">
                </div>
                <div style="font-size: 11px; color: #6c757d; margin-top: -6px; margin-bottom: 8px; text-align: center;">
                    输入还有几天会被释放(如输入5=找还有5天被释放的实例)
                </div>
                <button id="autodl-start-btn" class="autodl-refresh-btn">开始执行</button>

                <div class="autodl-warning-counter safe" id="autodl-warning-counter">
                    5天后被释放的数量: <span id="autodl-warning-count">检查中...</span>
                </div>

                <div class="autodl-expand-hint" id="autodl-expand-hint">点击 ⬇️ 显示全部页面</div>

                <div class="autodl-expanded-content">
                    <div style="display: flex; gap: 8px; margin-bottom: 8px;">
                        <button id="autodl-save-btn" class="autodl-refresh-btn" style="flex: 1; background: linear-gradient(135deg, #17a2b8, #138496);">保存设置</button>
                        <button id="autodl-test-btn" class="autodl-refresh-btn" style="flex: 1; background: linear-gradient(135deg, #ffc107, #e0a800);">测试连接</button>
                    </div>
                    <button id="autodl-clear-btn" class="autodl-refresh-btn" style="background: linear-gradient(135deg, #dc3545, #c82333); font-size: 12px;">清除保存信息</button>
                    <div id="autodl-refresh-status" class="autodl-refresh-status">准备就绪</div>
                    <div class="autodl-refresh-log" id="autodl-refresh-log"></div>
                    <div id="autodl-refresh-debug"></div>
                </div>
            </div>
        `;

        document.body.appendChild(panel);

        // 从存储中获取保存的位置
        const savedX = GM_getValue('panel_x', 0);
        const savedY = GM_getValue('panel_y', 0);

        // 添加拖拽功能
        let isDragging = false;
        let currentX;
        let currentY;
        let initialX;
        let initialY;
        let xOffset = savedX;
        let yOffset = savedY;

        // 恢复保存的位置
        if (savedX !== 0 || savedY !== 0) {
            setTranslate(savedX, savedY, panel);
        }

        function dragStart(e) {
            if (e.target.closest('.autodl-refresh-close') ||
                e.target.closest('input') ||
                e.target.closest('button') ||
                e.target.closest('.autodl-refresh-log')) {
                return;
            }

            initialX = e.clientX - xOffset;
            initialY = e.clientY - yOffset;

            if (e.target === panel || e.target.closest('.autodl-refresh-header')) {
                isDragging = true;
                panel.classList.add('dragging');
            }
        }

        function dragEnd(e) {
            initialX = currentX;
            initialY = currentY;
            isDragging = false;
            panel.classList.remove('dragging');
            
            // 保存当前位置到存储
            GM_setValue('panel_x', xOffset);
            GM_setValue('panel_y', yOffset);
        }

        function drag(e) {
            if (isDragging) {
                e.preventDefault();
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;
                xOffset = currentX;
                yOffset = currentY;

                setTranslate(currentX, currentY, panel);
            }
        }

        function setTranslate(xPos, yPos, el) {
            el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
        }

        panel.addEventListener('mousedown', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);

        // 绑定按钮事件
        document.getElementById('autodl-start-btn').addEventListener('click', executeAutoRefresh);
        document.getElementById('autodl-save-btn').addEventListener('click', saveCredentials);
        document.getElementById('autodl-clear-btn').addEventListener('click', clearCredentials);
        document.getElementById('autodl-test-btn').addEventListener('click', testConnection);
        document.querySelector('.autodl-refresh-close').addEventListener('click', () => {
            panel.remove();
        });

        // 展开/收起切换功能
        document.getElementById('autodl-expand-btn').addEventListener('click', () => {
            const expandedContent = document.querySelector('.autodl-expanded-content');
            const expandBtn = document.getElementById('autodl-expand-btn');

            if (expandedContent.classList.contains('visible')) {
                // 当前是展开状态,执行收起
                panel.classList.remove('expanded');
                expandedContent.classList.remove('visible');
                expandBtn.textContent = '⬇️';
                document.getElementById('autodl-expand-hint').style.display = 'block'; // 显示提示
            } else {
                // 当前是收起状态,执行展开
                panel.classList.add('expanded');
                expandedContent.classList.add('visible');
                expandBtn.textContent = '⬆️';
                document.getElementById('autodl-expand-hint').style.display = 'none'; // 隐藏提示
                // 初始化展开内容
                initializeExpandedContent();
            }
        });

        // 自动保存设置(当输入框失去焦点时)
        const saveSettings = () => {
            const phone = document.getElementById('autodl-phone').value.trim();
            const password = document.getElementById('autodl-password').value.trim();
            const days = document.getElementById('autodl-days').value.trim();

            if (phone) GM_setValue('phone', phone);
            if (password) GM_setValue('password', password);
            if (days) GM_setValue('days', days);

            // 如果手机号和密码都有值,自动检查实例状态
            if (phone && password) {
                checkInstanceStatus();
            }
        };

        // 绑定事件监听器
        document.getElementById('autodl-phone').addEventListener('blur', saveSettings);
        document.getElementById('autodl-password').addEventListener('blur', saveSettings);
        document.getElementById('autodl-days').addEventListener('blur', saveSettings);

        // 不在初始状态显示调试信息和日志
        // 只有在点击开始执行后才会显示

        // 如果已有保存的账号密码,自动检查一次实例状态
        const checkSavedPhone = GM_getValue('phone', '');
        const checkSavedPassword = GM_getValue('password', '');
        if (checkSavedPhone && checkSavedPassword) {
            setTimeout(() => {
                checkInstanceStatus();
            }, 1000); // 延迟1秒后检查,确保页面完全加载
        }
    }

    // 等待页面加载完成后创建控制面板
    function init() {
        console.log('🚀 AutoDL 自动刷新插件正在初始化...');

        // 检查是否在AutoDL网站
        if (window.location.hostname.includes('autodl.com')) {
            debugLog('检测到AutoDL网站,准备创建控制面板');

            // 延迟创建面板,确保页面完全加载
            setTimeout(() => {
                createControlPanel();
                console.log('✅ AutoDL 自动刷新插件初始化完成');
            }, 2000);
        } else {
            console.log('❌ 当前网站不是AutoDL,插件不会运行');
        }
    }

    // 启动插件
    init();
})();