QingJiaoHelper

青骄第二课堂小助手: 2024 知识竞赛 | 跳过视频 | 自动完成所有课程 | 领取每日学分 | 课程自动填充答案

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name                 QingJiaoHelper
// @namespace            http://tampermonkey.net/
// @version              0.3.5.3
// @description          青骄第二课堂小助手: 2024 知识竞赛 | 跳过视频 | 自动完成所有课程 | 领取每日学分 | 课程自动填充答案
// @author               FoliageOwO
// @match                *://www.2-class.com/*
// @match                *://2-class.com/*
// @grant                GM_addStyle
// @grant                GM_getResourceText
// @grant                GM_registerMenuCommand
// @grant                GM_getValue
// @grant                GM_setValue
// @license              GPL-3.0
// @supportURL           https://github.com/FoliageOwO/QingJiaoHelper
// @require              https://fastly.jsdelivr.net/npm/[email protected]/src/toastify.min.js
// @require              https://update.greasyfork.org/scripts/453791/lib2class.js
// @require              https://fastly.jsdelivr.net/npm/[email protected]/dist/axios.min.js
// @resource toastifycss https://fastly.jsdelivr.net/npm/toastify-js/src/toastify.css
// @resource spectrecss  https://fastly.jsdelivr.net/gh/FoliageOwO/QingJiaoHelper/spectre.css
// ==/UserScript==
const apiGetGradeLevels = {
    method: "GET",
    api: "/course/getHomepageGrade",
};
const apiGetCoursesByGradeLevel = {
    method: "GET",
    api: "/course/getHomepageCourseList?grade=${grade}&pageSize=50&pageNo=1",
};
const apiGetSelfCoursesByGradeLevel = {
    method: "GET",
    api: "/course/getHomepageCourseList?grade=自学&pageNo=1&pageSize=500&sort=&type=${grade}",
};
const apiGetTestPaperList = {
    method: "GET",
    api: "/exam/getTestPaperList?courseId=${courseId}",
};
const apiCommitExam = {
    method: "POST",
    api: "/exam/commit",
};
const apiAddMedal = {
    method: "GET",
    api: "/medal/addMedal",
};
const apiGetBeforeResourcesByCategoryName = {
    method: "POST",
    api: "/resource/getBeforeResourcesByCategoryName",
};
const apiAddPCPlayPV = {
    method: "POST",
    api: "/resource/addPCPlayPV",
};
const apiLikePC = {
    method: "POST",
    api: "/resource/likePC",
};
async function requestAPI(api, params, data) {
    const method = api.method;
    const origin = "https://www.2-class.com";
    let url = `${origin}/api${api.api}`;
    for (const key in params) {
        url = url.replaceAll("${" + key + "}", params[key]);
    }
    if (method === "GET") {
        return await axios({
            method: "GET",
            url,
        })
            .then((response) => {
            const rdata = response.data;
            console.debug(`[${method}] ${url}`, data, rdata);
            if (rdata.success === false || rdata.data === null) {
                const errorMessage = rdata.errorMsg;
                const errorCode = rdata.errorCode;
                console.error(`API 返回错误 [${errorCode}]:${errorMessage},请刷新页面重试!`);
                return null;
            }
            else {
                return rdata;
            }
        })
            .catch((reason) => {
            showMessage(`请求 API 失败(${reason.code}):${reason.message}\n请将控制台中的具体报错提交!`, "red");
            console.error(`请求失败(${reason.status}/${reason.code})→${reason.message}→`, reason.toJSON(), reason.response, reason.stack);
        });
    }
    else {
        return await axios({
            method: "POST",
            url,
            withCredentials: true,
            headers: {
                "Content-Type": "application/json;charset=UTF-8",
            },
            data,
        }).then((response) => {
            const rdata = response.data;
            console.debug(`[${method}] ${url}`, data, rdata);
            if (rdata.success === false || rdata.data === null) {
                const errorMessage = rdata.errorMsg;
                const errorCode = rdata.errorCode;
                console.error(`API 返回错误 [${errorCode}]:${errorMessage},请刷新页面重试!`);
                return null;
            }
            else {
                return rdata;
            }
        });
    }
}
async function getAvailableGradeLevels() {
    return await requestAPI(apiGetGradeLevels).then((data) => {
        return data ? data.data.map((it) => it.value) : null;
    });
}
async function getCoursesByGradeLevel(gradeLevel) {
    return await requestAPI(apiGetCoursesByGradeLevel, {
        grade: gradeLevel,
    }).then((data) => {
        return data ? data.data.list : null;
    });
}
async function getSelfCoursesByGradeLevel(gradeLevel) {
    return await requestAPI(apiGetSelfCoursesByGradeLevel, {
        grade: gradeLevel,
    }).then((data) => {
        return data ? data.data.list : null;
    });
}
async function getTestPaperList(courseId) {
    return await requestAPI(apiGetTestPaperList, { courseId }).then((data) => {
        return data ? data.data.testPaperList : null;
    });
}
async function getCourseAnswers(courseId) {
    return await getTestPaperList(courseId).then((testPaperList) => {
        if (!isNone(testPaperList)) {
            const answers = testPaperList.map((column) => column.answer);
            console.debug(`成功获取课程 [${courseId}] 的答案`, answers);
            return answers.map((it) => it.split("").join(","));
        }
        else {
            console.error(`无法获取课程 [${courseId}] 答案!`);
            return null;
        }
    });
}
async function commitExam(data) {
    return await requestAPI(apiCommitExam, {}, data);
}
async function addMedal() {
    return await requestAPI(apiAddMedal).then((data) => {
        if (isNone(data)) {
            return null;
        }
        else {
            const flag = data.flag;
            const num = data.medalNum;
            if (flag) {
                return num;
            }
            else {
                return undefined;
            }
        }
    });
}
async function getBeforeResourcesByCategoryName(data) {
    return await requestAPI(apiGetBeforeResourcesByCategoryName, {}, data).then((data) => data
        ? data.data.list.map((it) => {
            return {
                title: it.briefTitle,
                resourceId: it.resourceId,
            };
        })
        : null);
}
async function addPCPlayPV(data) {
    return await requestAPI(apiAddPCPlayPV, {}, data).then((data) => {
        return data ? data.data.result : null;
    });
}
async function likePC(data) {
    return await requestAPI(apiLikePC, {}, data).then((data) => {
        if (isNone(data)) {
            return null;
        }
        else {
            const rdata = data.data;
            return !Number.isNaN(Number(rdata)) || rdata.errorCode === "ALREADY_like";
        }
    });
}
const scriptName = "QingJiaoHelper";
const scriptVersion = "v0.3.5.3";
const toastifyDuration = 3 * 1000;
const toastifyGravity = "top";
const toastifyPosition = "left";
const fuzzyFindConfidenceTreshold = 0.8;
const __DATA__ = () => window["__DATA__"];
const reqtoken = () => __DATA__().reqtoken;
const userInfo = () => __DATA__().userInfo;
const isLogined = () => JSON.stringify(userInfo()) !== "{}";
const accountGradeLevel = () => isLogined() ? userInfo().department.gradeName : "未登录";
const coursesGradeLevels = async () => await getAvailableGradeLevels();
const selfCoursesGradeLevels = async () => [
    "小学",
    "初中",
    "高中",
    "中职",
    "通用",
];
("use strict");
const isTaskCoursesEnabled = () => getGMValue("qjh_isTaskCoursesEnabled", false);
const isTaskSelfCourseEnabled = () => getGMValue("qjh_isTaskSelfCourseEnabled", false);
const isTaskGetCreditEnabled = () => getGMValue("qjh_isTaskGetCreditEnabled", false);
const isTaskFinalExaminationEnabled = () => getGMValue("qjh_isTaskFinalExaminationEnabled", false);
const isFullAutomaticEmulationEnabled = () => getGMValue("qjh_isFullAutomaticEmulationEnabled", false);
const isTaskCompetitionEnabled = () => getGMValue("qjh_isTaskCompetitionEnabled", true);
let autoComplete = () => featureNotAvailable("自动完成");
let autoCompleteCreditsDone = () => getGMValue("qjh_autoCompleteCreditsDone", false);
const features = [
    {
        key: "courses",
        title: "自动完成所有课程(不包括考试)",
        matcher: ["/courses", "/drugControlClassroom/courses"],
        task: () => taskCourses(false),
        enabled: isTaskCoursesEnabled,
    },
    {
        key: "selfCourse",
        title: "自动完成所有自学课程(不包括考试)",
        matcher: ["/selfCourse", "/drugControlClassroom/selfCourse"],
        task: () => taskCourses(true),
        enabled: isTaskSelfCourseEnabled,
    },
    {
        key: "credit",
        title: "自动获取每日学分(会花费一段时间,请耐心等待)",
        matcher: ["/admin/creditCenter"],
        task: taskGetCredit,
        enabled: isTaskGetCreditEnabled,
    },
    {
        key: "singleCourse",
        title: "单个课程自动填充答案",
        matcher: /\/courses\/exams\/(\d+)/,
        task: taskSingleCourse,
        enabled: () => true,
    },
    {
        key: "competition",
        title: "知识竞赛",
        matcher: ["/competition"],
        task: taskCompetition,
        enabled: isTaskCompetitionEnabled,
    },
    {
        key: "finalExamination",
        title: "期末考试",
        matcher: ["/courses/exams/finalExam"],
        task: taskFinalExamination,
        enabled: isTaskFinalExaminationEnabled,
    },
    {
        key: "skip",
        title: "跳过课程视频",
        matcher: /\/courses\/(\d+)/,
        task: taskSkip,
        enabled: () => true,
    },
];
function triggerFeatures() {
    if (location.pathname === "/") {
        showMessage(`${scriptName}\n版本:${scriptVersion}`, "green");
    }
    features.forEach((feature) => {
        let matcher = feature.matcher;
        let isMatched = matcher instanceof RegExp
            ? location.pathname.match(matcher)
            : matcher.indexOf(location.pathname) !== -1;
        if (isMatched && feature.enabled()) {
            showMessage(`激活功能:${feature.title}`, "green");
            feature.task();
        }
    });
}
(function () {
    for (let script of document.getElementsByTagName("script")) {
        if (script.innerText.indexOf("window.__DATA__") !== -1) {
            eval(script.innerText);
        }
    }
    GM_addStyle(GM_getResourceText("toastifycss"));
    GM_addStyle(GM_getResourceText("spectrecss"));
    GM_registerMenuCommand("菜单", showMenu);
    prepareMenu();
    let pathname = location.pathname;
    setInterval(() => {
        const newPathName = location.pathname;
        if (newPathName !== pathname) {
            console.debug(`地址改变`, pathname, newPathName);
            pathname = newPathName;
            triggerFeatures();
        }
    });
    triggerFeatures();
})();
const customGradeLevels = () => getGMValue("qjh_customGradeLevels", []);
const customSelfGradeLevels = () => getGMValue("qjh_customSelfGradeLevels", []);
async function prepareMenu() {
    const menuElement = await waitForElementLoaded("#qjh-menu");
    const coursesGradeLevelsList = await coursesGradeLevels();
    const selfCoursesGradeLevelsList = await selfCoursesGradeLevels();
    if (coursesGradeLevels === null || selfCoursesGradeLevelsList === null) {
        showMessage(`课程年级列表或自学课程年级列表获取失败!`, "red");
    }
    const titleElement = await waitForElementLoaded("#qjh-menu-title");
    titleElement.append(scriptVersion);
    for (const { selector, gradeLevels, customGradeLevelsList, customGradeLevelsListChangeHandler, } of [
        {
            selector: "#qjh-menu-feat-courses",
            gradeLevels: coursesGradeLevelsList,
            customGradeLevelsList: customGradeLevels,
            customGradeLevelsListChangeHandler: (value) => GM_setValue("qjh_customGradeLevels", value),
        },
        {
            selector: "#qjh-menu-feat-self-courses",
            gradeLevels: selfCoursesGradeLevelsList,
            customGradeLevelsList: customSelfGradeLevels,
            customGradeLevelsListChangeHandler: (value) => GM_setValue("qjh_customSelfGradeLevels", value),
        },
    ]) {
        const element = await waitForElementLoaded(selector);
        if (gradeLevels === null) {
            continue;
        }
        for (const gradeLevel of gradeLevels) {
            const label = document.createElement("label");
            label.className = "form-checkbox form-inline";
            const input = document.createElement("input");
            input.type = "checkbox";
            input.checked =
                customGradeLevelsList().indexOf(gradeLevel) !== -1;
            input.onchange = () => {
                if (input.checked) {
                    customGradeLevelsListChangeHandler(Array.of(...customGradeLevelsList(), gradeLevel));
                }
                else {
                    customGradeLevelsListChangeHandler(customGradeLevelsList().filter((it) => it !== gradeLevel));
                }
            };
            const i = document.createElement("i");
            i.className = "form-icon";
            label.appendChild(input);
            label.appendChild(i);
            label.append(gradeLevel);
            element.appendChild(label);
        }
    }
    const closeButton = await waitForElementLoaded("#qjh-menu-close-button");
    closeButton.onclick = () => {
        menuElement.style.display = "none";
    };
    const toggleInputs = nodeListToArray(document.querySelectorAll("input")).filter((element) => element.getAttribute("qjh-type") === "toggle");
    for (const toggleInput of toggleInputs) {
        const key = toggleInput.getAttribute("qjh-key");
        toggleInput.checked = GM_getValue(key);
        toggleInput.onchange = () => {
            GM_setValue(key, toggleInput.checked);
        };
    }
    const featButtons = nodeListToArray(document.querySelectorAll("button")).filter((element) => element.getAttribute("qjh-feat-key") !== null);
    for (const featButton of featButtons) {
        const key = featButton.getAttribute("qjh-feat-key");
        const feature = features.find((feature) => feature.key === key);
        featButton.onclick = () => {
            if (feature.enabled()) {
                showMessage(`手动激活功能:${feature.title}`, "green");
                feature.task();
            }
            else {
                showMessage(`功能 ${feature.title} 未被启用!`, "red");
            }
        };
    }
}
async function startCourse(courseId) {
    const answers = await getCourseAnswers(courseId);
    if (answers === null) {
        showMessage(`[${courseId}] 无法获取当前课程的答案!`, "red");
        return false;
    }
    else {
        location.href = `https://www.2-class.com/courses/exams/${courseId}`;
    }
}
async function taskCourses(isSelfCourses) {
    if (!isLogined()) {
        showMessage("你还没有登录!", "red");
        return;
    }
    let gradeLevels = await (isSelfCourses
        ? selfCoursesGradeLevels
        : coursesGradeLevels)();
    if (gradeLevels === null) {
        showMessage(`获取年级名列表失败,功能已中止!`, "red");
        return;
    }
    console.debug("获取总年级名列表", gradeLevels);
    gradeLevels = isSelfCourses ? customSelfGradeLevels() : customGradeLevels();
    console.debug("已选择的年级列表", gradeLevels);
    for (const gradeLevel of gradeLevels) {
        const coursesList = isSelfCourses
            ? await getSelfCoursesByGradeLevel(gradeLevel)
            : await getCoursesByGradeLevel(gradeLevel);
        if (coursesList === null) {
            showMessage(`[${gradeLevel}] 获取当前年级的课程列表失败,已跳过当前年级!`, "red");
        }
        const courseIds = coursesList
            .filter((it) => !it.isFinish && it.title !== "期末考试")
            .map((it) => it.courseId);
        if (courseIds.length === 0) {
            console.debug(`[${gradeLevel}] 所有${isSelfCourses ? "自学" : ""}课程都是完成状态,已跳过!`);
            return;
        }
        console.debug(`[${gradeLevel}] 未完成的${isSelfCourses ? "自学" : ""}课程`, courseIds);
        let committed = 0;
        for (const courseId of courseIds) {
            if (courseId === "finalExam") {
                return;
            }
            if (!isNone(courseId)) {
                const result = await startCourse(courseId);
                if (result) {
                    committed++;
                }
                else {
                    console.error(`[${courseId}] 无法提交当前课程,已跳过!`);
                }
            }
            else {
                console.error(`[${gradeLevel}] 无法找到 courseId,已跳过!`);
            }
        }
        showMessage(`成功完成了 ${committed} 个${isSelfCourses ? "自学" : ""}课程!`, "green");
    }
}
async function taskSingleCourse() {
    if (!isLogined()) {
        showMessage("你还没有登录!", "red");
        return;
    }
    const courseId = location.pathname.match(/(\d+)/g)[0];
    const answers = await getCourseAnswers(courseId);
    await emulateExamination(answers, "#app > div > div.home-container > div > div > div > div:nth-child(1) > div > button", "#app > div > div.home-container > div > div > div > div:nth-child(1) > div > div.exam-content-btnbox > button", "#app > div > div.home-container > div > div > div > div:nth-child(1) > div > div.exam-content-btnbox > div > button:nth-child(2)", (answers, _) => {
        const firstAnswer = answers.shift().toString();
        return {
            type: "index",
            answer: firstAnswer,
            matchedQuestion: null,
        };
    }, `答题 [${courseId}]`, answers.length, 50);
    const passText = await waitForElementLoaded("#app > div > div.home-container > div > div > div > div.exam-box > div > div > p.exam-pass-title");
    if (passText) {
        const courses = [];
        const courseLevels = customGradeLevels();
        for (const courseLevel of courseLevels) {
            const result = await getCoursesByGradeLevel(courseLevel);
            for (const course of result) {
                courses.push(course);
            }
        }
        const courseIds = courses
            .filter((it) => !it.isFinish && it.title !== "期末考试")
            .map((it) => it.courseId);
        if (courseIds.length === 0) {
            showMessage("所有的课程已全部自动完成!", "green");
            location.href = "https://www.2-class.com/courses/";
        }
        else {
            location.href = `https://www.2-class.com/courses/exams/${courseIds[0]}`;
        }
    }
}
async function emulateExamination(answers, startButtonSelector, primaryNextButtonSelector, secondaryNextButtonSelector, answerHandler, examinationName, size = 100, interval = 3000, afterStart = async () => { }) {
    let isExaminationStarted = false;
    let count = 0;
    const next = async (nextAnswers, nextButton = null) => {
        const questionElement = await waitForElementLoaded(".exam-content-question");
        const questionText = removeStuffs(questionElement.innerText.split("\n")[0]);
        if (!isExaminationStarted) {
            const primaryNextButton = await waitForElementLoaded(primaryNextButtonSelector);
            isExaminationStarted = true;
            await next(nextAnswers, primaryNextButton);
        }
        else {
            let nextSecButton = nextButton;
            if (count > 0) {
                nextSecButton = await waitForElementLoaded(secondaryNextButtonSelector);
            }
            if (!isNone(size) && count < size) {
                nextSecButton.onclick = async () => {
                    setTimeout(async () => await next(nextAnswers, nextSecButton), 0);
                };
                let { type, answer, matchedQuestion } = answerHandler(answers, questionText);
                if (isNone(answer)) {
                    showMessage(`未找到此题的答案,请手动回答,或等待题库更新:${questionText}`, "red");
                    count++;
                    return;
                }
                else {
                    const selections = document.getElementsByClassName("exam-single-content-box");
                    console.debug("选择", answer, selections);
                    const finalQuestion = matchedQuestion || questionText;
                    if (!isFullAutomaticEmulationEnabled()) {
                        showMessage(`${finalQuestion ? finalQuestion + "\n" : ""}第 ${count + 1} 题答案:${type === "index" ? toDisplayAnswer(answer) : answer}`, "green");
                    }
                    if (type === "text") {
                        for (let answerText of answer.split("||")) {
                            answerText = removeStuffs(answerText);
                            const selectionElements = htmlCollectionToArray(selections).filter((it) => {
                                const match = it.innerText.match(/^([A-Z])([.。,,、.])(.*)/);
                                const answerContent = removeStuffs(match[1 + 2]);
                                return (!isNone(answerContent) &&
                                    (answerContent === answerText ||
                                        fuzzyMatch(answerContent, answerText).matched));
                            });
                            selectionElements.map((it) => it.click());
                        }
                    }
                    else {
                        for (const answerIndex of answer
                            .split(",")
                            .filter((it) => it !== "")
                            .map((it) => Number(it))) {
                            const selectionElement = selections[answerIndex];
                            selectionElement.click();
                        }
                    }
                    if (isFullAutomaticEmulationEnabled()) {
                        setTimeout(() => nextSecButton.click(), interval);
                    }
                    count++;
                }
            }
        }
    };
    const startButton = await waitForElementLoaded(startButtonSelector);
    if (isFullAutomaticEmulationEnabled()) {
        showMessage(`自动开始 ${examinationName}!`, "blue");
        startButton.click();
        await afterStart();
        next(answers, null);
    }
    else {
        startButton.onclick = async () => {
            showMessage(`开始 ${examinationName}!`, "blue");
            await afterStart();
            next(answers, null);
        };
    }
}
async function taskSkip() {
    if (!isLogined()) {
        showMessage("你还没有登录!", "red");
        return;
    }
    const courseId = location.pathname.match(/(\d+)/g)[0];
    const video = (await waitForElementLoaded("#app > div > div.home-container > div > div > div:nth-child(2) > div > div > div > div > div > video"));
    const videoControlButton = await waitForElementLoaded("#app > div > div.home-container > div > div > div:nth-child(2) > div > div > div > div > div > .prism-controlbar > .prism-play-btn");
    videoControlButton.onclick = () => {
        const endTime = video.seekable.end(0);
        video.currentTime = endTime;
    };
}
async function taskGetCredit() {
    if (!isLogined()) {
        showMessage("你还没有登录!", "red");
        return;
    }
    const length = 5;
    const num = await addMedal();
    if (num !== undefined) {
        showMessage(`成功领取禁毒徽章 [${num}]!`, "green");
    }
    else if (num === null) {
        showMessage("领取徽章失败!", "red");
    }
    else {
        showMessage("无法领取徽章(可能已领取过),已跳过!", "yellow");
        console.warn("无法领取徽章(可能已领取过),已跳过!");
    }
    const categories = [
        { name: "public_good", tag: "read" },
        { name: "ma_yun_recommend", tag: "labour" }, // the `ma_yun_recommend` has lots of sub-categorys
        { name: "ma_yun_recommend", tag: "movie" },
        { name: "ma_yun_recommend", tag: "music" },
        { name: "ma_yun_recommend", tag: "physicalEducation" },
        { name: "ma_yun_recommend", tag: "arts" },
        { name: "ma_yun_recommend", tag: "natural" },
        { name: "ma_yun_recommend", tag: "publicWelfareFoundation" },
        { name: "school_safe", tag: "safeVolunteer" },
    ];
    let done = 0;
    let failed = 0;
    let liked = 0;
    for (const category of categories) {
        const data = {
            categoryName: category.name,
            pageNo: 1,
            pageSize: 100,
            reqtoken: reqtoken(),
            tag: category.tag,
        };
        const resources = await getBeforeResourcesByCategoryName(data);
        if (resources === null) {
            console.error(`无法获取分类 ${category.name} 的资源,已跳过!`);
            continue;
        }
        console.debug(`获取分类 ${category.name} 的资源`, resources);
        for (const resource of resources) {
            if (done >= length)
                break;
            const resourceId = resource.resourceId;
            const resourceData = { resourceId, reqtoken: reqtoken() };
            const result = await addPCPlayPV(resourceData);
            if (result) {
                console.debug(`成功完成资源 [${resourceId}]:${resource.title}`);
                done++;
            }
            else {
                console.error(`无法完成资源 ${resourceId},已跳过!`);
                failed++;
            }
            const likeResult = await likePC(resourceData);
            if (likeResult) {
                console.debug(`成功点赞资源 [${resourceId}]!`);
                liked++;
            }
            else {
                console.error(`资源点赞失败 [${resourceId}],已跳过!`);
            }
        }
    }
    let beforeDone = done;
    const checkSuccess = setInterval(() => {
        if (done !== 0) {
            if (done === beforeDone) {
                showMessage(`成功完成 ${done}/${done + failed} 个资源,点赞 ${liked} 个!`, "green");
                clearInterval(checkSuccess);
            }
            else {
                beforeDone = done;
            }
        }
    }, 500);
}
async function taskFinalExamination() {
    const supportedFinal = libs.supportedFinal;
    const gradeLevel = accountGradeLevel();
    if (supportedFinal.hasOwnProperty(gradeLevel)) {
        const paperName = supportedFinal[gradeLevel];
        let papers = libs[paperName];
        await emulateExamination(papers.map((it) => it.answer), "#app > div > div.home-container > div > div > div > div:nth-child(1) > div > button", "#app > div > div.home-container > div > div > div > div:nth-child(1) > div > div.exam-content-btnbox > button", "#app > div > div.home-container > div > div > div > div:nth-child(1) > div > div.exam-content-btnbox > div > button:nth-child(2)", (_, question) => {
            const [answerList, n] = accurateFind(papers, question) ||
                fuzzyFind(papers, question) || [[], 0];
            return {
                type: "text",
                answer: n > 0 ? answerList.map((it) => it.answer).join("||") : null,
                matchedQuestion: n > 0 ? answerList.map((it) => it.realQuestion).join("||") : null,
            };
        }, "期末考试", 10, // 一共 10 道题
        3000 // 默认题目间隔 3s
        );
    }
    else {
        showMessage(`你的年级 [${gradeLevel}] 暂未支持期末考试!`, "red");
        return;
    }
}
async function taskMultiComplete() {
}
async function taskCompetition() {
    const supportedCompetition = libs.supportedCompetition;
    const gradeLevel = accountGradeLevel();
    let gradeGroup;
    const gradesPrimary = {
        一年级: 1,
        二年级: 2,
        三年级: 3,
        四年级: 4,
        五年级: 5,
        六年级: 6,
    };
    if (gradeLevel in gradesPrimary) {
        gradeGroup = "小学组";
    }
    else {
        gradeGroup = "中学组";
    }
    if (supportedCompetition.hasOwnProperty(gradeGroup)) {
        showMessage(`已自动选择 [${gradeGroup}] 知识竞赛题库`, "cornflowerblue");
        const paperName = supportedCompetition[gradeGroup];
        const papers = libs[paperName];
        if (!Array.isArray(papers)) {
            showMessage(`[${gradeGroup}] 暂不支持知识竞赛!`, "red");
            return;
        }
        await emulateExamination(papers.map((it) => it.answer), "#app > div > div.home-container > div > div > div.competiotion-exam-box-all > div.exam-box > div > div.exam_content_bottom_btn > button", "#app > div > div.home-container > div > div > div.competiotion-exam-box-all > div.exam-box > div.competition-sub > button", "#app > div > div.home-container > div > div > div.competiotion-exam-box-all > div.exam-box > div.competition-sub > button.ant-btn.ant-btn-primary", (_, question) => {
            const [answerList, n] = accurateFind(papers, question) ||
                fuzzyFind(papers, question) || [[], 0];
            return {
                type: "text",
                answer: n > 0 ? answerList.map((it) => it.answer).join("||") : null,
                matchedQuestion: n > 0 ? answerList.map((it) => it.realQuestion).join("||") : null,
            };
        }, "知识竞赛", 20, // 最大题目数,竞赛只有 20 道题目,如果未定义并打开了 `自动下一题并提交` 会导致循环提示最后一题 80 次
        3000, // 与下一题的间隔时间,单位毫秒,默认 3 秒
        async () => {
            const gradeGroupDialog = await waitForElementLoaded("#app > div > div.home-container > div > div > div.competiotion-exam-box-all > div.dialog-mask > div");
            const options = nodeListToArray(gradeGroupDialog.querySelectorAll(".option"));
            const filteredOptions = options.filter((it) => it.innerHTML === gradeGroup);
            const resultOption = filteredOptions[0];
            if (filteredOptions.length < 1 || isNone(resultOption)) {
                showMessage(`[${gradeGroup}] 暂不支持知识竞赛!`, "red");
                return;
            }
            else {
                resultOption.click();
            }
        });
    }
    else {
        showMessage(`你的年级 [${gradeLevel}] 暂未支持知识竞赛!`, "red");
        return;
    }
}
function showMessage(text, color) {
    Toastify({
        text,
        duration: toastifyDuration,
        newWindow: true,
        gravity: toastifyGravity,
        position: toastifyPosition,
        stopOnFocus: true,
        style: { background: color },
    }).showToast();
}
function featureNotAvailable(name = "(未知)") {
    showMessage(`${name} 功能当前不可用,请尝试刷新页面。如果问题依旧请上报这个 bug!`, "red");
}
function isNone(obj) {
    return obj == undefined || obj == null;
}
function getGMValue(name, defaultValue) {
    let value = GM_getValue(name);
    if (isNone(value)) {
        value = defaultValue;
        GM_setValue(name, defaultValue);
    }
    return value;
}
async function waitForElementLoaded(querySelector) {
    return new Promise((resolve, reject) => {
        let attempts = 0;
        const tryFind = () => {
            const element = document.querySelector(querySelector);
            if (element) {
                resolve(element);
            }
            else {
                attempts++;
                if (attempts >= 30) {
                    console.error(`无法找到元素 [${querySelector}],已放弃!`);
                    reject();
                }
                else {
                    setTimeout(tryFind, 250 * Math.pow(1.1, attempts));
                }
            }
        };
        tryFind();
    });
}
function removeStuffs(string) {
    return isNone(string)
        ? null
        : string
            .replace(/\s*/g, "")
            .replace(/[,。?!;:—【】(),.?!;:-\[\]\(\)]/g, "");
}
function toDisplayAnswer(answer) {
    const alphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
    let result = "";
    for (const singleAnswer of answer.split(",")) {
        const index = Number(singleAnswer);
        result = result + alphas[index];
    }
    return result;
}
function nodeListToArray(nodeList) {
    return Array.prototype.slice.call(nodeList);
}
function htmlCollectionToArray(htmlCollection) {
    const result = [];
    for (const element of htmlCollection)
        result.push(element);
    return result;
}
function arrayDiff(array1, array2) {
    return array1.concat(array2).filter((v, _, array) => {
        return array.indexOf(v) === array.lastIndexOf(v);
    });
}
function fuzzyMatch(a, b) {
    const aChars = a.split("");
    const bChars = b.split("");
    const length = aChars.length > bChars.length ? aChars.length : bChars.length;
    const diff = arrayDiff(aChars, bChars);
    const diffLength = diff.length;
    const unconfidence = diffLength / length;
    return {
        matched: 1 - unconfidence >= fuzzyFindConfidenceTreshold,
        confidence: 1 - unconfidence,
    };
}
function accurateFind(papers, question) {
    const results = papers.filter((it) => removeStuffs(it.question) === removeStuffs(question));
    if (results.length > 0) {
        console.debug(`精确匹配问题:${question} → ${question}`);
        return [
            results.map((it) => {
                return { answer: it.answer, realQuestion: it.question };
            }),
            results.length,
        ];
    }
    else {
        return null;
    }
}
function fuzzyFind(papers, question) {
    const chars = question.split("");
    const length = chars.length;
    const percentages = [];
    for (const paper of papers) {
        const { matched, confidence } = fuzzyMatch(question, paper.question);
        if (matched) {
            percentages.push({
                question: paper.question,
                answer: paper.answer,
                confidence,
            });
        }
    }
    const theMostConfidents = percentages
        .filter((it) => it.confidence > 0)
        .sort((a, b) => a.confidence - b.confidence);
    if (theMostConfidents.length <= 0) {
        console.error(`模糊匹配未找到高度匹配的结果:${question}`);
        return null;
    }
    console.debug(`模糊匹配问题:${question} → ${theMostConfidents
        .map((it) => `(${it.confidence})${it.question}`)
        .join("||")}`);
    return [
        theMostConfidents.map((it) => {
            return { answer: it.answer, realQuestion: it.question };
        }),
        theMostConfidents.length,
    ];
}
async function insertValue(input, value) {
    input.value = value;
    const event = new Event("input", {
        bubbles: true,
    });
    const tracker = input._valueTracker;
    event.simulated = true;
    if (tracker) {
        tracker.setValue(value);
    }
    input.dispatchEvent(event);
}
async function login(account, password) {
    const loginButton = await waitForElementLoaded("#app > div > div.home-container > div > div > main > div.white-bg-panel > div.login_home > div > div.padding-panel.btn-panel > div > button");
    loginButton.click();
    const accountInput = (await waitForElementLoaded("#account"));
    const passwordInput = (await waitForElementLoaded("#password"));
    passwordInput.type = "text";
    const submitButton = await waitForElementLoaded("body > div:nth-child(14) > div > div.ant-modal-wrap > div > div.ant-modal-content > div > form > div > div > div > button");
    await new Promise((resolve) => setTimeout(resolve, 500));
    await insertValue(accountInput, account);
    await insertValue(passwordInput, password);
    submitButton.click();
    waitForElementLoaded("#login_nc")
        .then(async () => {
        showMessage("正在进行模拟滑块验证,请稍等...", "green");
        await mockVerify();
        waitForElementLoaded("div > div > div > div.ant-notification-notice-description").then(() => {
            showMessage("检测到滑块验证登入失败,请重新刷新网页并确保开发者工具处于开启状态!", "red");
        });
    })
        .catch(() => {
        console.log("无滑块验证出现,已直接登入");
    });
}
async function mockVerify() {
    const mockDistance = 394; // 滑块验证的长度
    const mockInterval = 20; // 滑动间隔
    const mockButtonId = "nc_1_n1z"; // 滑块验证的可交互按钮 ID
    const verifyButton = document.getElementById(mockButtonId);
    const clientRect = verifyButton.getBoundingClientRect();
    const x = clientRect.x;
    const y = clientRect.y;
    const mousedown = new MouseEvent("mousedown", {
        bubbles: true,
        cancelable: true,
        clientX: x,
        clientY: y,
    });
    verifyButton.dispatchEvent(mousedown);
    let dx = 0;
    let dy = 0;
    const timer = setInterval(function () {
        const _x = x + dx;
        const _y = y + dy;
        const mousemoveEvent = new MouseEvent("mousemove", {
            bubbles: true,
            cancelable: true,
            clientX: _x,
            clientY: _y,
        });
        verifyButton.dispatchEvent(mousemoveEvent);
        if (_x - x >= mockDistance) {
            clearInterval(timer);
            const mouseupEvent = new MouseEvent("mouseup", {
                bubbles: true,
                cancelable: true,
                clientX: _x,
                clientY: _y,
            });
            verifyButton.dispatchEvent(mouseupEvent);
        }
        else {
            dx += Math.ceil(Math.random() * 50);
        }
    }, mockInterval);
}
const container = document.createElement("div");
container.setAttribute("id", "qjh-menu");
container.innerHTML = `<style>
  .qjh-menu {
    height: max-content;
    box-shadow: 1px 1px 10px #909090;
    padding: 1em;
    position: fixed;
    z-index: 999;
    right: 1%;
    top: 3%;
    width: 25%;
    -webkit-border-radius: 10px;
    -moz-border-radius: 10px;
    border-radius: 10px;
  }

  .form-inline {
    display: inline-block;
  }
</style>

<div class="card container qjh-menu">
  <div class="card-header">
    <div class="card-title text-bold h5" id="qjh-menu-title">
      QingJiaoHelper
      <button
        class="btn btn-link float-right"
        type="button"
        id="qjh-menu-close-button"
      >
        ❌
      </button>
    </div>
  </div>

  <div class="card-body">
    <div class="toast toast-warning">
      ⚠注意:勾选的功能会在下一次刷新页面时<mark><b>自动激活</b></mark
      >,未勾选的功能只能手动启用!点击<b>一键完成</b>按钮可以在这个菜单中直接完成,而不用手动跳转到对应页面。
    </div>

    <div class="divider text-center" data-content="考试"></div>

    <div class="form-group">
      <label class="form-switch">
        <b>期末考试</b>
        <input
          type="checkbox"
          qjh-type="toggle"
          qjh-key="qjh_isTaskFinalExaminationEnabled"
        />
        <i class="form-icon"></i>
        <button class="btn btn-sm mx-2" type="button">
          <a href="/courses/exams/finalExam">点击跳转</a>
        </button>
      </label>
    </div>

    <div class="form-group">
      <label class="form-switch">
        <b>知识竞赛</b>
        <input
          type="checkbox"
          qjh-type="toggle"
          qjh-key="qjh_isTaskCompetitionEnabled"
        />
        <i class="form-icon"></i>
        <button class="btn btn-sm mx-2" type="button">
          <a href="/competition">点击跳转</a>
        </button>
      </label>
    </div>

    <div class="divider text-center" data-content="课程"></div>

    <div>
      <div class="form-group" id="qjh-menu-feat-courses">
        <label class="form-switch">
          <b>完成所选年级的课程</b>
          <input
            type="checkbox"
            qjh-type="toggle"
            qjh-key="qjh_isTaskCoursesEnabled"
          />
          <i class="form-icon"></i>
          <button class="btn btn-sm mx-2" type="button" qjh-feat-key="courses">
            一键完成👉
          </button>
        </label>
      </div>

      <div class="form-group" id="qjh-menu-feat-self-courses">
        <label class="form-switch">
          <b>完成所选年级的自学课程</b>
          <input
            type="checkbox"
            qjh-type="toggle"
            qjh-key="qjh_isTaskSelfCourseEnabled"
          />
          <i class="form-icon"></i>
          <button
            class="btn btn-sm mx-2"
            type="button"
            qjh-feat-key="selfCourse"
          >
            一键完成👉
          </button>
        </label>
      </div>

      <div class="divider text-center" data-content="其他"></div>

      <div class="form-group"></div>
        <label class="form-switch">
          <b>获取每日学分(点赞视频和领取徽章)</b>
          <input
            type="checkbox"
            qjh-type="toggle"
            qjh-key="qjh_isTaskGetCreditEnabled"
          />
          <i class="form-icon"></i>
          <button
            class="btn btn-sm mx-2"
            type="button"
            onclick="taskGetCredit"
            qjh-feat-key="credit"
          >
            一键完成👉
          </button>
        </label>
      </div>

      <div class="form-group">
        <label class="form-switch">
          <b>自动开始作答、下一题和提交</b>
          <input
            type="checkbox"
            qjh-type="toggle"
            qjh-key="qjh_isFullAutomaticEmulationEnabled"
          />
          <i class="form-icon"></i>
        </label>
      </div>

      
    </div>

    <div class="divider"></div>

    <div class="card-footer text-gray">
      本脚本由 FoliageOwO 以
      <b><a href="https://www.gnu.org/licenses/gpl-3.0.en.html">GPL-3.0</a></b>
      开源许可在 GitHub 开源,脚本地址:<a
        href="https://github.com/FoliageOwO/QingJiaoHelper"
        target="_blank"
        >GitHub</a
      >、<a
        href="https://greasyfork.org/zh-CN/scripts/452984-qingjiaohelper"
        target="_blank"
        >GreasyFork</a
      >。
    </div>
  </div>
</div>
`;
container.style.display = "none";
document.body.appendChild(container);
function showMenu() {
    container.style.display = "unset";
}