青骄第二课堂小助手: 2024 知识竞赛 | 跳过视频 | 自动完成所有课程 | 领取每日学分 | 课程自动填充答案
- // ==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/toastify-js@1.12.0/src/toastify.min.js
- // @require https://update.greasyfork.org/scripts/453791/lib2class.js
- // @require https://fastly.jsdelivr.net/npm/axios@1.3.6/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";
- }