// ==UserScript== // @name Yuketang Mess Helper // @namespace http://tampermonkey.net/ // @version 1.01 // @description A note of exams haven't done // @author Linx // @match https://www.yuketang.cn/v2/web/index // @match https://www.yuketang.cn/v2/web/index/* // @icon none // @grant GM_addStyle // @connect yuketang.cn // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; console.log('Yuketang Homework Helper is running.'); async function fetchClassroom() { return fetch("https://www.yuketang.cn/v2/api/web/courses/list?identity=2", { headers: { "accept": "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "priority": "u=1, i", "sec-ch-ua": "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "xt-agent": "web", "xtbz": "ykt" }, referrer: "https://www.yuketang.cn/v2/web/index", referrerPolicy: "strict-origin-when-cross-origin", body: null, method: "GET", mode: "cors", credentials: "include" }) .then(response => response.json()) .then(data => { if (data.errcode === 0 && data.data && Array.isArray(data.data.list)) { const currentDate = new Date(); const currentYear = currentDate.getFullYear(); const currentMonth = currentDate.getMonth() + 1; // getMonth() 返回从 0 开始的月份 // 筛选 term 距今不超过 13 个月的 item const filteredItems = data.data.list.filter(item => { const termYear = Math.floor(item.term / 100); // term 的前 4 位表示年份 const termMonth = item.term % 100; // term 的后 2 位表示月份 const monthsDifference = (currentYear - termYear) * 12 + (currentMonth - termMonth); return monthsDifference <= 13; }); const classrooms = filteredItems.map(item => { return { classroom_id: item.classroom_id, course_name: item.course.name, teacher_name: item.teacher.name } }); return classrooms; } else { throw new Error("Unexpected response format"); } }) .catch(error => { console.error("Error fetching classroom data:", error); throw error; }); } async function fetchExam() { return await fetchClassroom() .then(classrooms => { const page = 0; const offset = 20; const sort = -1; // 创建针对每个 classroom 的 fetch 请求 const fetchPromises = classrooms.map(classroom => { const url = `https://www.yuketang.cn/v2/api/web/logs/learn/${classroom.classroom_id}?actype=5&page=${page}&offset=${offset}&sort=${sort}`; return fetch(url) .then(response => response.json()) .then(data => { if (data && data.data && Array.isArray(data.data.activities)) { return data.data.activities.map(activity => { return { ...activity, classroom_id: classroom.classroom_id, course_name: classroom.course_name, teacher_name: classroom.teacher_name }; }); } else { console.warn(`Unexpected response for classroom ${classroom}:`, data); return []; // 如果格式不符合预期,返回空数组 } }) .catch(error => { console.error(`Error fetching activities for classroom ${classroom}:`, error); return []; // 返回空数组以避免 undefined }); }); // 等待所有 fetch 请求完成 return Promise.all(fetchPromises).then(activities => { console.log(`Fetched ${activities.flat().length} activities`); return activities.flat(); }); }); } async function buildAcitivities() { return await fetchExam().then(activities => { const now = Date.now(); const validActivities = activities.filter(item => item.deadline > now); validActivities.sort((a, b) => a.deadline - b.deadline); const container = document.createElement('div'); for (const activity of validActivities) { container.appendChild(renderActivity(activity)); } return container; }) }; function renderActivity(activity) { let link = `https://www.yuketang.cn/v2/web/exam/${activity.classroom_id}/${activity.courseware_id}`; if (activity.type === 4) { link = `https://www.yuketang.cn/v2/web/studentQuiz/${activity.courseware_id}/1`; } const linkBox = document.createElement('a'); linkBox.href = link; linkBox.className = 'custom-link'; linkBox.target = '_blank'; const contentBox = document.createElement('div'); contentBox.className = 'content-box'; contentBox.style.padding = '15px'; contentBox.style.margin = '10px 0'; contentBox.style.border = '1px solid #e0e0e0'; contentBox.style.borderRadius = '8px'; contentBox.style.backgroundColor = '#f9f9f9'; contentBox.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)'; contentBox.style.transition = 'transform 0.2s, box-shadow 0.2s'; contentBox.addEventListener('mouseover', () => { contentBox.style.transform = 'translateY(-2px)'; contentBox.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.15)'; }); contentBox.addEventListener('mouseout', () => { contentBox.style.transform = 'none'; contentBox.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)'; }); const title = document.createElement('h2'); title.style.fontSize = '18px'; title.style.margin = '0 0 8px'; title.style.color = '#333'; title.textContent = `${activity.title} - ${activity.course_name} (${activity.teacher_name})`; const subInfo = document.createElement('div'); subInfo.style.fontSize = '14px'; subInfo.style.color = '#666'; subInfo.innerHTML = ` 满分:${activity.total_score}分 | 共${activity.problem_count}题 | ${activity.limit ? `限时:${activity.limit / 60}分钟 |` : ''} 截止时间:${formatDeadline(activity.deadline)} `; contentBox.appendChild(title); contentBox.appendChild(subInfo); linkBox.appendChild(contentBox); return linkBox; } // 格式化截止时间 function formatDeadline(deadlineTimestamp) { const date = new Date(deadlineTimestamp); const options = { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', weekday: 'short' }; return date.toLocaleDateString('zh-CN', options).replace(/\//g, '/'); } function buildIframe(activities) { // 插入一个触发按钮到页面 const triggerButton = document.createElement('button'); triggerButton.style.borderRadius = '50px'; triggerButton.style.padding = '12px 24px'; triggerButton.style.transition = 'all 0.3s'; triggerButton.textContent = '查看作业'; triggerButton.style.position = 'fixed'; triggerButton.style.bottom = '40px'; triggerButton.style.right = '40px'; triggerButton.style.padding = '10px 20px'; triggerButton.style.fontSize = '16px'; triggerButton.style.cursor = 'pointer'; triggerButton.style.zIndex = '10000'; triggerButton.style.backgroundColor = '#007bff'; triggerButton.style.color = '#fff'; triggerButton.style.border = 'none'; triggerButton.style.borderRadius = '5px'; triggerButton.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; triggerButton.addEventListener('mouseover', () => { triggerButton.style.backgroundColor = '#0078d7'; triggerButton.style.transform = 'scale(1.05)'; }); triggerButton.addEventListener('mouseout', () => { triggerButton.style.backgroundColor = '#007bff'; triggerButton.style.transform = 'scale(1)'; }); document.body.appendChild(triggerButton); // 创建 div 元素 const hwDiv = document.createElement('div'); hwDiv.id = 'hwIframe'; hwDiv.style.position = 'fixed'; hwDiv.style.top = '10%'; hwDiv.style.left = '10%'; hwDiv.style.width = '80%'; hwDiv.style.height = '80%'; hwDiv.style.border = '2px solid #ccc'; hwDiv.style.borderRadius = '10px'; hwDiv.style.zIndex = '10001'; hwDiv.style.backgroundColor = '#fff'; hwDiv.style.padding = '20px'; hwDiv.style.boxSizing = 'border-box'; hwDiv.style.overflowY = 'auto'; hwDiv.style.maxHeight = '80%'; hwDiv.style.boxShadow = '0 8px 16px rgba(0, 0, 0, 0.2)'; hwDiv.style.transition = 'transform 0.3s'; // hwDiv.addEventListener('mouseover', () => { // hwDiv.style.transform = 'scale(1.01)'; // }); // hwDiv.addEventListener('mouseout', () => { // hwDiv.style.transform = 'scale(1)'; // }); // 创建关闭按钮 const closeBtn = document.createElement('button'); closeBtn.textContent = '×'; closeBtn.style.position = 'absolute'; closeBtn.style.top = '10px'; closeBtn.style.right = '10px'; closeBtn.style.width = '30px'; closeBtn.style.height = '30px'; closeBtn.style.padding = '0'; closeBtn.style.cursor = 'pointer'; closeBtn.style.backgroundColor = '#ff5c5c'; closeBtn.style.color = '#fff'; closeBtn.style.border = 'none'; closeBtn.style.borderRadius = '50%'; closeBtn.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)'; closeBtn.style.fontSize = '18px'; closeBtn.style.display = 'flex'; closeBtn.style.alignItems = 'center'; closeBtn.style.justifyContent = 'center'; closeBtn.style.transition = 'all 0.2s'; // 添加鼠标悬停效果 closeBtn.addEventListener('mouseover', () => { closeBtn.style.backgroundColor = '#ff3b3b'; closeBtn.style.transform = 'scale(1.1)'; }); closeBtn.addEventListener('mouseout', () => { closeBtn.style.backgroundColor = '#ff5c5c'; closeBtn.style.transform = 'scale(1)'; }); // 获取activities hwDiv.appendChild(activities); hwDiv.appendChild(closeBtn); // 点击关闭按钮时移除 div 和关闭按钮 closeBtn.addEventListener('click', () => { hwDiv.remove(); }); triggerButton.addEventListener('click', () => { // 检查是否已经存在 div,避免重复添加 if (document.getElementById('hwIframe')) { return; } // 将 div 和关闭按钮插入页面 document.body.appendChild(hwDiv); }); } buildAcitivities().then(activities => { buildIframe(activities); }); })();