您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
V2.7: 修正了因网站增加Referer等头部校验而导致无法获取作业的问题。V2.6: 新增 "Star本项目" 按钮。
// ==UserScript== // @name 新海天帮你列智慧教学平台作业清单 // @namespace http://tampermonkey.net/ // @version 2.8 // @description V2.7: 修正了因网站增加Referer等头部校验而导致无法获取作业的问题。V2.6: 新增 "Star本项目" 按钮。 // @author 上条当咩&&Gemini // @match http://123.121.147.7:88/* // @license MIT // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect 123.121.147.7 // ==/UserScript== (function() { 'use strict'; // API 基础 URL const BASE_URL = 'http://123.121.147.7:88/ve'; // 全局变量 let allHomeworkData = []; let sortOrder = 'desc'; // 'asc' 或 'desc' let currentFilter = 'all'; // --- 1. 创建 UI 元素 --- // 注入CSS样式 GM_addStyle(` #homework-checker-btn { position: fixed; top: 70px; right: 150px; z-index: 9999; padding: 8px 15px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); } #homework-checker-btn:hover { background-color: #0056b3; } #homework-modal { display: none; position: fixed; z-index: 10000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.5); font-family: Arial, sans-serif; } .modal-content { position: relative; /* 为绝对定位的子元素提供上下文 */ background-color: #fefefe; margin: 5% auto; padding: 20px; border: 1px solid #888; width: 80%; max-width: 1200px; border-radius: 8px; } .modal-close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; } /* 新增的小人图像样式 */ #mascot-img { position: absolute; top: 5px; right: 60px; /* 调整位置,使其在关闭按钮旁边 */ max-height: 100px; /* 控制图像大小 */ user-select: none; /* 防止用户选中图像 */ } #homework-status { font-size: 16px; margin-bottom: 10px; } #homework-filters { margin-bottom: 15px; display: flex; align-items: center; } .filter-tag { display: inline-block; padding: 5px 12px; margin-right: 8px; border: 1px solid #ccc; border-radius: 15px; cursor: pointer; font-size: 13px; } .filter-tag:hover { background-color: #f0f0f0; } .filter-tag.active { background-color: #007bff; color: white; border-color: #007bff; } /* “Star本项目”按钮的样式 */ #star-repo-btn { display: inline-block; padding: 5px 12px; margin-left: 15px; /* 与左侧标签的间距 */ background-color: #e53935; /* 红色背景 */ color: white; text-decoration: none; border-radius: 15px; font-size: 13px; font-weight: bold; transition: background-color 0.2s ease-in-out; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } #star-repo-btn:hover { background-color: #c62828; /* 鼠标悬停时颜色变深 */ } #homework-list { margin-top: 10px; max-height: 65vh; overflow-y: auto; } #homework-list table { width: 100%; border-collapse: collapse; } #homework-list th, #homework-list td { border: 1px solid #ddd; padding: 10px; text-align: left; font-size: 14px; } #homework-list th { background-color: #f2f2f2; position: sticky; top: -1px; } .sortable { cursor: pointer; } .sortable:hover { background-color: #e8e8e8; } .sort-asc::after { content: ' ▲'; font-size: 10px; } .sort-desc::after { content: ' ▼'; font-size: 10px; } .status-pending { color: red; font-weight: bold; } .status-submitted { color: grey; } .status-graded { color: green; } .past-due { background-color: #fbecec !important; } .urgency-high { background-color: #ffebee !important; } .urgency-medium { background-color: #fffde7 !important; } .urgency-low { background-color: #e3f2fd !important; } `); const button = document.createElement('button'); button.id = 'homework-checker-btn'; button.innerHTML = '一键获取作业'; document.body.appendChild(button); const modal = document.createElement('div'); modal.id = 'homework-modal'; // 在弹窗HTML中添加img元素,并设置onerror事件 modal.innerHTML = ` <div class="modal-content"> <span class="modal-close">×</span> <img id="mascot-img" src="https://love.nimisora.icu/homework-notify/nimisora.png" alt="Mascot" onerror="this.style.display='none'"> <h2>作业清单</h2> <div id="homework-status">点击按钮开始获取...</div> <div id="homework-filters"></div> <div id="homework-list"></div> </div> `; document.body.appendChild(modal); // --- 2. 核心逻辑 --- function gmFetch(url) { return new Promise((resolve, reject) => { // 定义需要伪造的请求头 const headers = { // 关键 #1: 模仿成功请求的 Accept 类型 'Accept': '*/*', // 关键 #2: 表明这是一个 AJAX 请求 'X-Requested-With': 'XMLHttpRequest', // 关键 #3: 请求的来源页面,这是最重要的部分 'Referer': 'http://123.121.147.7:88/ve/back/coursePlatform/coursePlatform.shtml?method=toCoursePlatform&courseToPage=10460&courseId=C108005B&dataSource=1&cId=117416&xkhId=2024-2025-2-2C108005B03&xqCode=2024202502&teacherId=9944' }; GM_xmlhttpRequest({ method: 'GET', url: url, headers: headers, // <-- 将伪造的请求头附加到请求上 onload: (response) => { if (response.status >= 200 && response.status < 400) { // 增强:如果响应体为空(例如没有作业),则直接返回一个可处理的空数据结构,避免JSON解析错误 if (!response.responseText || !response.responseText.trim()) { // 返回一个有 courseNoteList 属性的空对象,以适配后续代码 resolve({ courseNoteList: [] }); return; } try { const jsonData = JSON.parse(response.responseText); if (typeof jsonData === 'string' && jsonData.includes("您还未登录")) { reject(new Error("会话已过期或未登录,请重新登录。")); return; } resolve(jsonData); } catch (e) { reject(new Error(`响应内容不是有效的JSON格式。原始文本: "${response.responseText}"`)); } } else { reject(new Error(`请求失败,HTTP状态码: ${response.status}`)); } }, onerror: (error) => reject(new Error(`网络请求错误: ${error}`)) }); }); } async function fetchSemesterInfo() { const url = `${BASE_URL}/back/rp/common/teachCalendar.shtml?method=queryCurrentXq`; const data = await gmFetch(url); if (data.result && data.result.length > 0) return data.result[0].xqCode; throw new Error("获取学期信息失败。"); } async function fetchCourseList(xqCode) { const url = `${BASE_URL}/back/coursePlatform/course.shtml?method=getCourseList&pagesize=100&page=1&xqCode=${xqCode}`; const data = await gmFetch(url); if (data.courseList) return data.courseList; throw new Error("获取课程列表失败。"); } async function fetchAllHomeworkForCourse(course) { const homeworkTypes = [ { subType: 0, name: "普通作业" }, { subType: 1, name: "课程报告" }, { subType: 2, name: "实验作业" } ]; const promises = homeworkTypes.map(async (type) => { const url = `${BASE_URL}/back/coursePlatform/homeWork.shtml?method=getHomeWorkList&cId=${course.id}&subType=${type.subType}&page=1&pagesize=100`; try { const data = await gmFetch(url); if (data.courseNoteList && data.courseNoteList.length > 0) { return data.courseNoteList.map(hw => ({ ...hw, courseName: course.name, homeworkType: type.name })); } } catch (error) { console.error(`获取 [${course.name}] 的 [${type.name}] 失败:`, error); } return []; }); const results = await Promise.all(promises); return results.flat(); } function getUrgencyScore(homework) { const now = new Date(); const deadline = new Date(homework.end_time); const diffHours = (deadline - now) / (1000 * 60 * 60); if (diffHours < 0) return 4; if (diffHours < 24) return 1; if (diffHours < 48) return 2; return 3; } function sortHomeworkInPlace() { allHomeworkData.sort((a, b) => { const isAUnsubmitted = a.subStatus === '未提交'; const isBUnsubmitted = b.subStatus === '未提交'; if (isAUnsubmitted !== isBUnsubmitted) { return isBUnsubmitted - isAUnsubmitted; } if (isAUnsubmitted && isBUnsubmitted) { const urgencyA = getUrgencyScore(a); const urgencyB = getUrgencyScore(b); if (urgencyA !== urgencyB) { return urgencyA - urgencyB; } } const dateA = new Date(a.end_time); const dateB = new Date(b.end_time); return sortOrder === 'asc' ? dateA - dateB : dateB - dateA; }); } function formatDeadline(dateString) { if (!dateString) return 'N/A'; let date = new Date(dateString); if (date.getHours() === 0 && date.getMinutes() === 0 && date.getSeconds() === 0) { date.setSeconds(date.getSeconds() - 1); } return date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2) + ' ' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2); } function renderHomeworkTable(homeworks) { const listDiv = document.getElementById('homework-list'); if (homeworks.length === 0) { listDiv.innerHTML = '<p style="font-size: 16px; color: green;">当前筛选条件下没有作业!</p>'; return; } let tableHTML = `<table><thead><tr> <th>课程名称</th><th>作业标题</th><th>类型</th> <th class="sortable" id="deadline-header">截止时间</th> <th>剩余时间</th><th>状态</th><th>分数</th> </tr></thead><tbody>`; const now = new Date(); homeworks.forEach(hw => { let statusText, statusClass, rowClass = ''; const isGraded = hw.stu_score !== null && hw.stu_score !== undefined && hw.stu_score !== '未公布成绩'; const isSubmitted = hw.subStatus === '已提交'; let remainingTimeText = ' - '; if (!isSubmitted && !isGraded) { const deadline = new Date(hw.end_time); const diffMillis = deadline - now; const diffHours = diffMillis / (1000 * 60 * 60); if (diffHours < 0) { remainingTimeText = '<span style="color:red;">已截止</span>'; rowClass = 'past-due'; } else { remainingTimeText = `${Math.floor(diffHours)} 小时`; if (diffHours < 24) rowClass = 'urgency-high'; else if (diffHours < 48) rowClass = 'urgency-medium'; else rowClass = 'urgency-low'; } } if (isGraded) { statusText = '已批阅'; statusClass = 'status-graded'; } else if (isSubmitted) { statusText = '已提交'; statusClass = 'status-submitted'; } else { statusText = '未提交'; statusClass = 'status-pending'; } tableHTML += `<tr class="${rowClass}"> <td>${hw.courseName || 'N/A'}</td><td>${hw.title || 'N/A'}</td> <td>${hw.homeworkType || 'N/A'}</td><td>${formatDeadline(hw.end_time)}</td> <td>${remainingTimeText}</td><td class="${statusClass}">${statusText}</td> <td>${isGraded ? hw.stu_score : ' - '}</td></tr>`; }); tableHTML += '</tbody></table>'; listDiv.innerHTML = tableHTML; addSortingFunctionality(); } function addSortingFunctionality() { const deadlineHeader = document.getElementById('deadline-header'); if (deadlineHeader) { deadlineHeader.classList.remove('sort-asc', 'sort-desc'); deadlineHeader.classList.add(sortOrder === 'asc' ? 'sort-asc' : 'sort-desc'); deadlineHeader.onclick = () => { sortOrder = sortOrder === 'desc' ? 'asc' : 'desc'; sortHomeworkInPlace(); filterAndRender(currentFilter); }; } } function setupFilters() { const filters = [ { id: 'all', text: '全部作业' }, { id: 'pending', text: '未提交' }, { id: 'submitted', text: '待批改' }, { id: 'graded', text: '已批阅' }, { id: 'overdue', text: '已截止(未交)' }, ]; const filtersContainer = document.getElementById('homework-filters'); // 生成过滤器标签的 HTML const filtersHTML = filters.map(f => `<span class="filter-tag" data-filter="${f.id}">${f.text}</span>`).join(''); // 创建 "Star本项目" 按钮的 HTML const starButtonHTML = ` <a href="https://github.com/10086mea/make-bjtu-homework-greater/" target="_blank" id="star-repo-btn"> ❤️ Star本项目 </a> `; // 将过滤器和按钮一起放入容器 filtersContainer.innerHTML = filtersHTML + starButtonHTML; // 为过滤器标签添加点击事件监听 filtersContainer.addEventListener('click', (e) => { if (e.target.classList.contains('filter-tag')) { currentFilter = e.target.dataset.filter; filtersContainer.querySelectorAll('.filter-tag').forEach(tag => tag.classList.remove('active')); e.target.classList.add('active'); filterAndRender(currentFilter); } }); // 默认激活 "全部作业" 过滤器 filtersContainer.querySelector('.filter-tag[data-filter="all"]').classList.add('active'); } function filterAndRender(filterType) { let filteredData = []; const now = new Date(); const dataToFilter = [...allHomeworkData]; switch (filterType) { case 'pending': filteredData = dataToFilter.filter(hw => hw.subStatus === '未提交'); break; case 'submitted': filteredData = dataToFilter.filter(hw => hw.subStatus === '已提交' && hw.stu_score === '未公布成绩'); break; case 'graded': filteredData = dataToFilter.filter(hw => hw.stu_score !== null && hw.stu_score !== undefined && hw.stu_score !== '未公布成绩'); break; case 'overdue': filteredData = dataToFilter.filter(hw => new Date(hw.end_time) < now && hw.subStatus === '未提交'); break; case 'all': default: filteredData = dataToFilter; break; } renderHomeworkTable(filteredData); } // --- 3. 事件绑定 --- button.onclick = async () => { modal.style.display = 'block'; const statusDiv = document.getElementById('homework-status'); const listDiv = document.getElementById('homework-list'); statusDiv.innerHTML = '🚀 新海天正在获取作业信息...'; listDiv.innerHTML = ''; document.getElementById('homework-filters').innerHTML = ''; try { const xqCode = await fetchSemesterInfo(); const courses = await fetchCourseList(xqCode); if (!courses || courses.length === 0) { statusDiv.textContent = '✅ 当前学期没有课程。'; return; } statusDiv.innerHTML = `新海天正在从 ${courses.length} 门课程中获取作业...`; const homeworkByCourse = await Promise.all(courses.map(c => fetchAllHomeworkForCourse(c))); allHomeworkData = homeworkByCourse.flat(); sortHomeworkInPlace(); setupFilters(); currentFilter = 'all'; filterAndRender(currentFilter); statusDiv.textContent = `🎉 获取完成!新海天共找到 ${allHomeworkData.length} 项作业。`; } catch (error) { console.error("获取作业失败:", error); statusDiv.innerHTML = `<span style="color:red;">❌ 获取作业失败: ${error.message}</span>`; } }; modal.querySelector('.modal-close').onclick = () => { modal.style.display = 'none'; }; window.onclick = (event) => { if (event.target == modal) { modal.style.display = 'none'; } }; })();