Greasy Fork

Greasy Fork is available in English.

Yuketang Mess Helper

A note of exams haven't done

目前为 2024-12-17 提交的版本。查看 最新版本

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