Yuketang Mess Helper

A note of exams haven't done

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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
// ==/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 = `
            <span>满分:${activity.total_score}分</span> |
            <span>共${activity.problem_count}题</span> |
            ${activity.limit ? `<span>限时:${activity.limit / 60}分钟</span> |` : ''}
            <span style="color: #007bff;">截止时间:${formatDeadline(activity.deadline)}</span>
        `;

        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);
    });
})();