// ==UserScript== // @name 青梨派助手:一键完成浏览任务 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 在青梨派(理工易班)页面添加按钮,一键完成所有未读的文章浏览任务以获取积分。 // @author 毫厘 // @match https://qlp.whut.edu.cn/mp-pc/home* // @connect qlpoa.whut.edu.cn // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== (function () { 'use strict'; // ==================== 全局状态管理 ==================== let taskState = { isRunning: false, isPaused: false, shouldStop: false, currentIndex: 0, totalTasks: 0, completedCount: 0, failedCount: 0 }; // ==================== 工具函数 ==================== function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 检查是否应该暂停 async function checkPauseState() { while (taskState.isPaused && !taskState.shouldStop) { await sleep(100); } } // 显示通知 function showNotification(message, type = 'info', duration = 3000) { const notification = document.createElement('div'); notification.className = `lgeb-notification lgeb-notification-${type}`; notification.innerHTML = `
${getIcon(type)} ${message}
`; document.body.appendChild(notification); setTimeout(() => notification.classList.add('lgeb-notification-show'), 10); if (duration > 0) { setTimeout(() => { notification.classList.remove('lgeb-notification-show'); setTimeout(() => notification.remove(), 300); }, duration); } return notification; } function getIcon(type) { const icons = { success: '✓', error: '✕', warning: '⚠', info: 'ℹ' }; return icons[type] || icons.info; } // 更新进度面板 function updateProgressPanel(current, total, status) { const panel = document.getElementById('lgeb-progress-panel'); if (!panel) return; const percentage = total > 0 ? Math.round((current / total) * 100) : 0; panel.querySelector('.lgeb-progress-bar-fill').style.width = `${percentage}%`; panel.querySelector('.lgeb-progress-text').textContent = `${current} / ${total}`; panel.querySelector('.lgeb-progress-percentage').textContent = `${percentage}%`; panel.querySelector('.lgeb-progress-status').textContent = status; } // 显示进度面板 function showProgressPanel(total) { let panel = document.getElementById('lgeb-progress-panel'); if (!panel) { panel = document.createElement('div'); panel.id = 'lgeb-progress-panel'; panel.innerHTML = `

任务处理中

0 / ${total} 0%
准备开始...
`; document.body.appendChild(panel); // 绑定控制按钮事件 document.getElementById('lgeb-pause-btn').addEventListener('click', togglePause); document.getElementById('lgeb-stop-btn').addEventListener('click', stopTasks); } setTimeout(() => panel.classList.add('lgeb-progress-panel-show'), 10); return panel; } // 切换暂停状态 function togglePause() { const btn = document.getElementById('lgeb-pause-btn'); if (!btn) return; taskState.isPaused = !taskState.isPaused; if (taskState.isPaused) { btn.innerHTML = '▶ 继续'; btn.classList.add('lgeb-paused'); updateProgressPanel(taskState.currentIndex, taskState.totalTasks, '已暂停'); showNotification('任务已暂停', 'warning', 2000); } else { btn.innerHTML = '⏸ 暂停'; btn.classList.remove('lgeb-paused'); showNotification('继续处理任务...', 'info', 2000); } } // 停止任务 function stopTasks() { taskState.shouldStop = true; taskState.isPaused = false; showNotification('正在停止任务...', 'warning', 2000); updateProgressPanel(taskState.currentIndex, taskState.totalTasks, '正在停止...'); } // 隐藏进度面板 function hideProgressPanel() { const panel = document.getElementById('lgeb-progress-panel'); if (panel) { panel.classList.remove('lgeb-progress-panel-show'); setTimeout(() => panel.remove(), 300); } } // 显示结果面板 function showResultPanel(success, failed, total) { const panel = document.createElement('div'); panel.className = 'lgeb-result-panel'; panel.innerHTML = `

任务完成

${total}
总任务数
${success}
成功
${failed}
失败
${failed > 0 ? '
失败的任务可能是特殊类型或已过期
' : ''}
`; document.body.appendChild(panel); setTimeout(() => panel.classList.add('lgeb-result-panel-show'), 10); } // ==================== 主要逻辑 ==================== async function completeTasks() { const btn = document.getElementById('lgeb-main-btn'); // 防止重复点击 if (taskState.isRunning) { showNotification('任务正在运行中', 'warning', 2000); return; } // 重置状态 taskState = { isRunning: true, isPaused: false, shouldStop: false, currentIndex: 0, totalTasks: 0, completedCount: 0, failedCount: 0 }; btn.disabled = true; btn.classList.add('lgeb-btn-loading'); const token = localStorage.getItem('token'); if (!token) { showNotification('获取登录凭证失败!请确认已登录并刷新页面', 'error', 4000); resetTaskState(btn); return; } console.log("【青梨派助手】成功获取登录凭证, 开始获取文章列表..."); showNotification('正在获取任务列表...', 'info', 2000); // 使用Promise包装GM_xmlhttpRequest const fetchArticles = () => new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: "https://qlpoa.whut.edu.cn/mp-api/auth/user/getArticleList?pageNum=1&pageSize=500&taskType=1,2,3", headers: { "Accept": "application/json, text/plain, */*", "Authorization": token, "Origin": "https://qlp.whut.edu.cn", "Referer": "https://qlp.whut.edu.cn/" }, timeout: 15000, // 增加超时时间 onload: (response) => resolve(response), onerror: (error) => reject(error), ontimeout: () => reject(new Error('请求超时')) }); }); try { const response = await fetchArticles(); if (response.status !== 200) { throw new Error(`服务器响应异常, 状态码: ${response.status}`); } const data = JSON.parse(response.responseText); if (data.code !== 200 || !data.rows) { throw new Error(data.msg || "返回的数据格式不正确"); } const articles = data.rows; const unreadArticles = articles.filter(article => article.isView === false); if (unreadArticles.length === 0) { showNotification('太棒了,所有任务都已完成!', 'success', 3000); resetTaskState(btn); btn.textContent = '✓ 任务已完成'; setTimeout(() => { btn.textContent = '一键完成任务'; }, 3000); return; } console.log(`【青梨派助手】发现 ${unreadArticles.length} 个未完成的任务,开始处理...`); showNotification(`发现 ${unreadArticles.length} 个未完成任务,开始处理`, 'info', 2000); taskState.totalTasks = unreadArticles.length; showProgressPanel(unreadArticles.length); // 批量处理任务(优化效率) await processTasks(unreadArticles, token); } catch (e) { showNotification(`获取列表失败: ${e.message}`, 'error', 4000); console.error("【青梨派助手】解析文章列表失败:", e); resetTaskState(btn); } } // 批量处理任务(优化后的逻辑) async function processTasks(articles, token) { const btn = document.getElementById('lgeb-main-btn'); for (let i = 0; i < articles.length; i++) { // 检查是否需要停止 if (taskState.shouldStop) { showNotification('任务已停止', 'warning', 3000); break; } // 检查暂停状态 await checkPauseState(); if (taskState.shouldStop) break; taskState.currentIndex = i + 1; const article = articles[i]; const articleId = article.id; updateProgressPanel(i, articles.length, `正在处理第 ${i + 1} 个任务...`); // 执行单个任务 await processArticle(articleId, token); // 动态延迟(优化效率:减少等待时间) await sleep(200 + Math.random() * 200); } // 任务完成或停止 updateProgressPanel(taskState.currentIndex, articles.length, taskState.shouldStop ? '已停止' : '处理完成!'); setTimeout(() => { hideProgressPanel(); showResultPanel(taskState.completedCount, taskState.failedCount, taskState.currentIndex); resetTaskState(btn); }, 1000); } // 处理单篇文章(Promise化) function processArticle(articleId, token) { return new Promise((resolve) => { const viewUrl = `https://qlpoa.whut.edu.cn/mp-api/auth/user/viewArticle?articleId=${articleId}&viewType=2`; GM_xmlhttpRequest({ method: "GET", url: viewUrl, headers: { "Authorization": token }, timeout: 10000, onload: function (viewResponse) { try { const result = JSON.parse(viewResponse.responseText); if (result.code === 200) { console.log(`%c✓ [ID: ${articleId}] 完成`, "color: green; font-weight: bold;"); taskState.completedCount++; } else { taskState.failedCount++; let errorMsg = result.msg?.includes("Exception") ? "服务器内部错误" : result.msg; console.warn(`%c✗ [ID: ${articleId}] ${errorMsg}`, "color: orange;"); } } catch (e) { taskState.failedCount++; console.error(`%c✗ [ID: ${articleId}] 解析失败`, "color: red;"); } resolve(); }, onerror: function () { taskState.failedCount++; console.error(`%c✗ [ID: ${articleId}] 网络错误`, "color: red;"); resolve(); }, ontimeout: function () { taskState.failedCount++; console.error(`%c✗ [ID: ${articleId}] 请求超时`, "color: red;"); resolve(); } }); }); } // 重置任务状态 function resetTaskState(btn) { taskState.isRunning = false; taskState.isPaused = false; taskState.shouldStop = false; btn.disabled = false; btn.classList.remove('lgeb-btn-loading'); } // ==================== UI创建 ==================== function addButton() { const btn = document.createElement("button"); btn.textContent = "一键完成任务"; btn.id = "lgeb-main-btn"; btn.onclick = completeTasks; document.body.appendChild(btn); } function addStyles() { const style = document.createElement('style'); style.textContent = ` /* 主按钮样式 */ #lgeb-main-btn { position: fixed; top: 150px; right: 20px; z-index: 99999; padding: 14px 28px; font-size: 15px; font-weight: 600; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 12px; cursor: pointer; box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } #lgeb-main-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 12px 24px rgba(102, 126, 234, 0.4); } #lgeb-main-btn:active:not(:disabled) { transform: translateY(0); } #lgeb-main-btn:disabled { opacity: 0.7; cursor: not-allowed; } #lgeb-main-btn.lgeb-btn-loading::after { content: ''; position: absolute; top: 50%; right: 12px; width: 16px; height: 16px; margin-top: -8px; border: 2px solid rgba(255, 255, 255, 0.3); border-top-color: white; border-radius: 50%; animation: lgeb-spin 0.6s linear infinite; } @keyframes lgeb-spin { to { transform: rotate(360deg); } } /* 通知样式 */ .lgeb-notification { position: fixed; top: 20px; right: 20px; z-index: 100000; min-width: 300px; max-width: 400px; background: white; border-radius: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); opacity: 0; transform: translateX(400px); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .lgeb-notification-show { opacity: 1; transform: translateX(0); } .lgeb-notification-content { display: flex; align-items: center; padding: 16px 20px; gap: 12px; } .lgeb-notification-icon { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-weight: bold; font-size: 14px; } .lgeb-notification-success .lgeb-notification-icon { background: #10b981; color: white; } .lgeb-notification-error .lgeb-notification-icon { background: #ef4444; color: white; } .lgeb-notification-warning .lgeb-notification-icon { background: #f59e0b; color: white; } .lgeb-notification-info .lgeb-notification-icon { background: #3b82f6; color: white; } .lgeb-notification-message { flex: 1; color: #1f2937; font-size: 14px; line-height: 1.5; } /* 进度面板样式 */ #lgeb-progress-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.9); z-index: 100001; width: 400px; background: white; border-radius: 16px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); opacity: 0; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } #lgeb-progress-panel.lgeb-progress-panel-show { opacity: 1; transform: translate(-50%, -50%) scale(1); } .lgeb-progress-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 24px; border-bottom: 1px solid #e5e7eb; } .lgeb-progress-header h3 { margin: 0; font-size: 18px; font-weight: 600; color: #1f2937; } .lgeb-progress-close { width: 28px; height: 28px; border: none; background: #f3f4f6; border-radius: 6px; cursor: pointer; font-size: 16px; color: #6b7280; transition: all 0.2s; } .lgeb-progress-close:hover { background: #e5e7eb; color: #1f2937; } .lgeb-progress-body { padding: 24px; } .lgeb-progress-info { display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 14px; color: #6b7280; } .lgeb-progress-percentage { font-weight: 600; color: #667eea; } .lgeb-progress-bar { height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden; margin-bottom: 16px; } .lgeb-progress-bar-fill { height: 100%; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); border-radius: 4px; transition: width 0.3s ease; width: 0%; } .lgeb-progress-status { text-align: center; font-size: 14px; color: #6b7280; } /* 控制按钮样式 */ .lgeb-progress-controls { display: flex; gap: 12px; margin-top: 16px; } .lgeb-control-btn { flex: 1; padding: 10px; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; } .lgeb-pause-btn { background: #f59e0b; color: white; } .lgeb-pause-btn:hover { background: #d97706; } .lgeb-pause-btn.lgeb-paused { background: #10b981; } .lgeb-pause-btn.lgeb-paused:hover { background: #059669; } .lgeb-stop-btn { background: #ef4444; color: white; } .lgeb-stop-btn:hover { background: #dc2626; } /* 结果面板样式 */ .lgeb-result-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.9); z-index: 100002; width: 450px; background: white; border-radius: 16px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); opacity: 0; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .lgeb-result-panel-show { opacity: 1; transform: translate(-50%, -50%) scale(1); } .lgeb-result-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 24px; border-bottom: 1px solid #e5e7eb; } .lgeb-result-header h3 { margin: 0; font-size: 18px; font-weight: 600; color: #1f2937; } .lgeb-result-close { width: 28px; height: 28px; border: none; background: #f3f4f6; border-radius: 6px; cursor: pointer; font-size: 16px; color: #6b7280; transition: all 0.2s; } .lgeb-result-close:hover { background: #e5e7eb; color: #1f2937; } .lgeb-result-body { padding: 24px; } .lgeb-result-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 20px; } .lgeb-stat-item { text-align: center; padding: 20px; border-radius: 12px; background: #f9fafb; } .lgeb-stat-total { background: linear-gradient(135deg, #667eea15, #764ba215); } .lgeb-stat-success { background: linear-gradient(135deg, #10b98115, #10b98115); } .lgeb-stat-failed { background: linear-gradient(135deg, #ef444415, #ef444415); } .lgeb-stat-number { font-size: 32px; font-weight: 700; margin-bottom: 8px; } .lgeb-stat-total .lgeb-stat-number { color: #667eea; } .lgeb-stat-success .lgeb-stat-number { color: #10b981; } .lgeb-stat-failed .lgeb-stat-number { color: #ef4444; } .lgeb-stat-label { font-size: 14px; color: #6b7280; } .lgeb-result-note { text-align: center; font-size: 13px; color: #9ca3af; margin-bottom: 20px; padding: 12px; background: #fef3c7; border-radius: 8px; color: #92400e; } .lgeb-result-refresh { width: 100%; padding: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.3s; } .lgeb-result-refresh:hover { transform: translateY(-2px); box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3); } `; document.head.appendChild(style); } // ==================== 初始化 ==================== function init() { addStyles(); addButton(); console.log('%c【青梨派助手】已加载 v1.0', 'color: #667eea; font-size: 14px; font-weight: bold;'); console.log('%c功能: 一键完成任务 | 支持暂停/恢复/停止 | 优化效率', 'color: #6b7280; font-size: 12px;'); } if (document.readyState === 'complete') { init(); } else { window.addEventListener('load', init); } })();