您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动刷新AutoDL平台长时间关机的实例,防止被平台回收。支持自定义天数阈值和批量操作。
// ==UserScript== // @name AutoDL 自动刷新实例 - 防止回收 // @namespace http://tampermonkey.net/ // @version 1.0.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: 350px; 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.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-content { padding: 15px; cursor: default; } .autodl-refresh-input-group { margin-bottom: 12px; } .autodl-refresh-input-group label { display: block; margin-bottom: 5px; font-weight: bold; color: #333; } .autodl-refresh-input-group input { width: 100%; padding: 8px 10px; border: 1px solid #ddd; border-radius: 5px; box-sizing: border-box; font-size: 14px; cursor: text; } .autodl-refresh-btn { width: 100%; padding: 10px; background: linear-gradient(135deg, #28a745, #20c997); color: white; border: none; border-radius: 5px; font-size: 14px; font-weight: bold; cursor: pointer; margin-bottom: 8px; 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-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") ); } // 主执行函数 async function executeAutoRefresh() { debugLog('开始执行自动刷新流程'); console.log('🚀 ===== AutoDL 自动刷新开始 ====='); 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); // 检查是否有无卡模式实例运行 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; 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)); debugLog(`实例 ${inst.uuid} 关机天数: ${daysDiff} 天`); if (daysDiff >= daysThreshold) { uuidsToRefresh.push(inst.uuid); addLog(`📌 发现符合条件的实例: ${inst.uuid} (关机${daysDiff}天)`, "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) { addLog("❌ 请填写完整的手机号、密码和天数阈值", "error"); return; } 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 = '9'; } } // 测试连接函数 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) { addLog("❌ 请填写完整的手机号、密码和天数阈值", "error"); return; } 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', '9'); panel.innerHTML = ` <div class="autodl-refresh-header"> <span>🤖 AutoDL 自动刷新</span> <button class="autodl-refresh-close">×</button> </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="如: 9" value="${savedDays}" min="1" max="365"> </div> <div style="display: flex; gap: 8px; margin-bottom: 8px;"> <button id="autodl-start-btn" class="autodl-refresh-btn" style="flex: 1;">开始执行</button> <button id="autodl-save-btn" class="autodl-refresh-btn" style="flex: 1; background: linear-gradient(135deg, #17a2b8, #138496);">保存设置</button> </div> <div style="display: flex; gap: 8px; margin-bottom: 8px;"> <button id="autodl-clear-btn" class="autodl-refresh-btn" style="flex: 1; background: linear-gradient(135deg, #dc3545, #c82333); font-size: 12px;">清除保存信息</button> <button id="autodl-test-btn" class="autodl-refresh-btn" style="flex: 1; background: linear-gradient(135deg, #ffc107, #e0a800); font-size: 12px;">测试连接</button> </div> <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> `; document.body.appendChild(panel); // 添加拖拽功能 let isDragging = false; let currentX; let currentY; let initialX; let initialY; let xOffset = 0; let yOffset = 0; 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'); } 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(); }); // 自动保存设置(当输入框失去焦点时) 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); addLog("✅ 设置已自动保存", "success"); }; // 绑定事件监听器 document.getElementById('autodl-phone').addEventListener('blur', saveSettings); document.getElementById('autodl-password').addEventListener('blur', saveSettings); document.getElementById('autodl-days').addEventListener('blur', saveSettings); // 显示调试信息 showDebugInfo(); // 如果已有保存的账号密码,显示提示 if (savedPhone && savedPassword) { addLog("✅ 已加载保存的账户信息", "success"); addLog(`📱 手机号: ${savedPhone}`, "info"); addLog("🔐 密码已保存,可直接执行", "info"); } else { addLog("💡 首次使用,请填写账户信息并点击'保存设置'", "info"); } } // 等待页面加载完成后创建控制面板 function init() { console.log('🚀 AutoDL 自动刷新插件正在初始化...'); // 检查是否在AutoDL网站 if (window.location.hostname.includes('autodl.com')) { debugLog('检测到AutoDL网站,准备创建控制面板'); // 延迟创建面板,确保页面完全加载 setTimeout(() => { createControlPanel(); addLog("🚀 AutoDL自动刷新插件已加载", "success"); addLog("💡 功能说明:自动刷新关机超过指定天数的实例,防止被平台回收", "info"); addLog("🔍 调试模式已开启,详细日志请查看浏览器控制台", "debug"); console.log('✅ AutoDL 自动刷新插件初始化完成'); }, 2000); } else { console.log('❌ 当前网站不是AutoDL,插件不会运行'); } } // 启动插件 init(); })();