// ==UserScript== // @name QingJiaoHelper // @namespace http://tampermonkey.net/ // @version 0.3.0 // @description 青骄第二课堂小助手: 长期更新 | 2022 知识竞赛 | 跳过视频 | 自动完成所有课程 | 每日领取学分 | 课程自动填充答案 // @author WindLeaf233 // @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/WindLeaf233/QingJiaoHelper // @require https://cdn.bootcdn.net/ajax/libs/toastify-js/1.12.0/toastify.min.js // @require https://greasyfork.org/scripts/453791-lib2class/code/lib2class.js?version=1258326 // @require https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.min.js // @resource toastifycss https://cdn.bootcdn.net/ajax/libs/toastify-js/1.12.0/toastify.min.css // @resource spectrecss https://cdn.jsdelivr.net/gh/WindLeaf233/QingJiaoHelper/spectre.css // @downloadURL none // ==/UserScript== var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var _this = this; /* ------------ API ------------ */ var apiGetGradeLevels = { method: "GET", api: "/course/getHomepageGrade", }; var apiGetCoursesByGradeLevel = { method: "GET", api: "/course/getHomepageCourseList?grade=${grade}&pageSize=50&pageNo=1", }; var apiGetSelfCoursesByGradeLevel = { method: "GET", api: "/course/getHomepageCourseList?grade=自学&pageNo=1&pageSize=500&sort=&type=${grade}", }; var apiGetTestPaperList = { method: "GET", api: "/exam/getTestPaperList?courseId=${courseId}", }; var apiCommitExam = { method: "POST", api: "/exam/commit", }; var apiAddMedal = { method: "GET", api: "/medal/addMedal", }; var apiGetBeforeResourcesByCategoryName = { method: "POST", api: "/resource/getBeforeResourcesByCategoryName", }; var apiAddPCPlayPV = { method: "POST", api: "/resource/addPCPlayPV", }; var apiLikePC = { method: "POST", api: "/resource/likePC", }; /* ------------ API 调用函数 ------------ */ /** * 获取 API * @param api API * @param params 插入参数 * @param data 附带数据(POST 方法) * @returns 获取到的 API 返回数据 */ function requestAPI(api, params, data) { return __awaiter(this, void 0, void 0, function () { var method, origin, url, key; return __generator(this, function (_a) { switch (_a.label) { case 0: method = api.method; origin = "https://www.2-class.com"; url = "".concat(origin, "/api").concat(api.api); for (key in params) { url = url.replaceAll("${" + key + "}", params[key]); } if (!(method === "GET")) return [3 /*break*/, 2]; return [4 /*yield*/, axios({ method: "GET", url: url }) .then(function (response) { var rdata = response.data; console.debug("[".concat(method, "] ").concat(url), data, rdata); if (rdata.success === false || rdata.data === null) { var errorMessage = rdata.errorMsg; var errorCode = rdata.errorCode; console.error("API \u8FD4\u56DE\u9519\u8BEF [".concat(errorCode, "]\uFF1A").concat(errorMessage, "\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u91CD\u8BD5\uFF01")); return null; } else { return rdata; } }) .catch(function (reason) { showMessage("\u8BF7\u6C42 API \u5931\u8D25\uFF08".concat(reason.code, "\uFF09\uFF1A").concat(reason.message, "\n\u8BF7\u5C06\u63A7\u5236\u53F0\u4E2D\u7684\u5177\u4F53\u62A5\u9519\u63D0\u4EA4\uFF01"), "red"); console.error("\u8BF7\u6C42\u5931\u8D25\uFF08".concat(reason.status, "/").concat(reason.code, "\uFF09\u2192").concat(reason.message, "\u2192"), reason.toJSON(), reason.response, reason.stack); })]; case 1: return [2 /*return*/, _a.sent()]; case 2: return [4 /*yield*/, axios({ method: "POST", url: url, headers: { "Content-Type": "application/json;charset=UTF-8", }, data: data, }).then(function (response) { var rdata = response.data; console.debug("[".concat(method, "] ").concat(url), data, rdata); if (rdata.success === false || rdata.data === null) { var errorMessage = rdata.errorMsg; var errorCode = rdata.errorCode; console.error("API \u8FD4\u56DE\u9519\u8BEF [".concat(errorCode, "]\uFF1A").concat(errorMessage, "\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u91CD\u8BD5\uFF01")); return null; } else { return rdata; } })]; case 3: return [2 /*return*/, _a.sent()]; } }); }); } /** * 获取所有的年级名,如五年级 * @returns 所有年级名 */ function getAvailableGradeLevels() { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, requestAPI(apiGetGradeLevels).then(function (data) { return data ? data.data.map(function (it) { return it.value; }) : null; })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /** * 获取指定年级名可用的课程列表 * @param gradeLevel 年级名 * @returns 所有可用的课程列表 */ function getCoursesByGradeLevel(gradeLevel) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, requestAPI(apiGetCoursesByGradeLevel, { grade: gradeLevel, }).then(function (data) { return data ? data.data.list : null; })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /** * 获取指定年级名可用的自学课程列表 * @param gradeLevel 年级名 * @returns 所有可用的自学课程列表 */ function getSelfCoursesByGradeLevel(gradeLevel) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, requestAPI(apiGetSelfCoursesByGradeLevel, { grade: gradeLevel, }).then(function (data) { return data ? data.data.list : null; })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /** * 获取指定课程 ID 的考试题列表 * @param courseId 课程 ID * @returns 考试题列表 */ function getTestPaperList(courseId) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, requestAPI(apiGetTestPaperList, { courseId: courseId }).then(function (data) { return data ? data.data.testPaperList : null; })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /** * 获取指定课程 ID 的考试题答案列表 * @param courseId 课程 ID * @returns 答案列表,如 ["1,2", "0,1", "2,3"] */ function getCourseAnswers(courseId) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getTestPaperList(courseId).then(function (testPaperList) { if (!isNone(testPaperList)) { var answers = testPaperList.map(function (column) { return column.answer; }); console.debug("\u6210\u529F\u83B7\u53D6\u8BFE\u7A0B [".concat(courseId, "] \u7684\u7B54\u6848"), answers); return answers.map(function (it) { return it.split("").join(","); }); } else { console.error("\u65E0\u6CD5\u83B7\u53D6\u8BFE\u7A0B [".concat(courseId, "] \u7B54\u6848\uFF01")); return null; } })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /** * 提交考试 * @param data 考试数据 * @returns 请求后的 API 返回数据 */ function commitExam(data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, requestAPI(apiCommitExam, {}, data)]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /** * 领取禁毒学子勋章 * @returns 如果获取成功,返回徽章的序号 */ function addMedal() { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, requestAPI(apiAddMedal).then(function (data) { if (isNone(data)) { return null; } else { var flag = data.flag; var num = data.medalNum; if (flag) { return num; } else { return undefined; } } })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /** * 获取所有的资源 * @returns 资源对象 */ function getBeforeResourcesByCategoryName(data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, requestAPI(apiGetBeforeResourcesByCategoryName, {}, data).then(function (data) { return data ? data.data.list.map(function (it) { return { title: it.briefTitle, resourceId: it.resourceId, }; }) : null; })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /** * 添加资源假播放 * @param data 资源数据 * @returns 是否成功 */ function addPCPlayPV(data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, requestAPI(apiAddPCPlayPV, {}, data).then(function (data) { return data ? data.data.result : null; })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /** * 给资源点赞 * @param data 点赞数据 * @returns 是否点赞成功 */ function likePC(data) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, requestAPI(apiLikePC, {}, data).then(function (data) { if (isNone(data)) { return null; } else { var rdata = data.data; return !Number.isNaN(Number(rdata)) || rdata.errorCode === "ALREADY_like"; } })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } /* ------------ 脚本定义 ------------ */ var scriptName = "QingJiaoHelper"; var scriptVersion = "v0.3.0"; var toastifyDuration = 3 * 1000; var toastifyGravity = "top"; var toastifyPosition = "left"; /* ------------ 青骄第二课堂数据 ------------ */ // 这几个都需要懒加载 var __DATA__ = function () { return window["__DATA__"]; }; var reqtoken = function () { return __DATA__().reqtoken; }; var userInfo = function () { return __DATA__().userInfo; }; var isLogined = function () { return JSON.stringify(userInfo()) !== "{}"; }; var accountGradeLevel = function () { return isLogined() ? userInfo().department.gradeName : "未登录"; }; // TODO 优化获取方式 // 似乎无法通过 API 获取自学年级名列表,目前只使用了手动列举 var coursesGradeLevels = function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getAvailableGradeLevels()]; case 1: return [2 /*return*/, _a.sent()]; } }); }); }; var selfCoursesGradeLevels = function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, [ "小学", "初中", "高中", "中职", "通用", ]]; }); }); }; ("use strict"); /* ------------ 动态值 ------------ */ var isTaskCoursesEnabled = function () { return getGMValue("qjh_isTaskCoursesEnabled", false); }; var isTaskSelfCourseEnabled = function () { return getGMValue("qjh_isTaskSelfCourseEnabled", false); }; var isTaskGetCreditEnabled = function () { return getGMValue("qjh_isTaskGetCreditEnabled", false); }; var isTaskSingleCourseEnabled = function () { return getGMValue("qjh_isTaskSingleCourseEnabled", true); }; var isTaskSkipEnabled = function () { return getGMValue("qjh_isTaskSkipEnabled", true); }; var isTaskFinalExaminationEnabled = function () { return getGMValue("qjh_isTaskFinalExaminationEnabled", false); }; var isFullAutomaticEmulationEnabled = function () { return getGMValue("qjh_isFullAutomaticEmulationEnabled", false); }; /* ------------ 自动完成的一些值 ------------ */ var autoComplete = function () { return featureNotAvailable("自动完成"); }; var autoCompleteCreditsDone = function () { return getGMValue("qjh_autoCompleteCreditsDone", false); }; var features = [ { key: "courses", title: "自动完成所有课程(不包括考试)", matcher: ["/courses", "/drugControlClassroom/courses"], task: function () { return taskCourses(false); }, enabled: isTaskCoursesEnabled, }, { key: "selfCourse", title: "自动完成所有自学课程(不包括考试)", matcher: ["/selfCourse", "/drugControlClassroom/selfCourse"], task: function () { return taskCourses(true); }, enabled: isTaskSelfCourseEnabled, }, { key: "credit", title: "自动获取每日学分(会花费一段时间,请耐心等待)", matcher: ["/admin/creditCenter"], task: taskGetCredit, enabled: isTaskGetCreditEnabled, }, { key: "singleCourse", title: "单个课程自动完成", matcher: /\/courses\/exams\/(\d+)/, task: taskSingleCourse, enabled: isTaskSingleCourseEnabled, }, // { // title: "知识竞赛", // matcher: ["/competition"], // task: taskCompetition, // enabled: true, // }, { key: "finalExamination", title: "期末考试", matcher: ["/courses/exams/finalExam"], task: taskFinalExamination, enabled: isTaskFinalExaminationEnabled, }, { key: "skip", title: "显示课程视频跳过按钮", matcher: /\/courses\/(\d+)/, task: taskSkip, enabled: isTaskSkipEnabled, }, ]; /** * 触发功能 */ function triggerFeatures() { // 匹配当前的地址,自动执行对应的功能 if (location.pathname === "/") { showMessage("".concat(scriptName, "\n\u7248\u672C\uFF1A").concat(scriptVersion), "green"); } features.forEach(function (feature) { var matcher = feature.matcher; var isMatched = matcher instanceof RegExp ? location.pathname.match(matcher) : matcher.indexOf(location.pathname) !== -1; if (isMatched && feature.enabled()) { showMessage("\u6FC0\u6D3B\u529F\u80FD\uFF1A".concat(feature.title), "green"); feature.task(); } }); } // 脚本主函数,注册一些东西,并执行功能 (function () { // 加载 `__DATA__` for (var _i = 0, _a = document.getElementsByTagName("script"); _i < _a.length; _i++) { var script = _a[_i]; if (script.innerText.indexOf("window.__DATA__") !== -1) { eval(script.innerText); } } // 脚本初加载 // 应用 `toastifycss` 样式 GM_addStyle(GM_getResourceText("toastifycss")); GM_addStyle(GM_getResourceText("spectrecss")); // 注册菜单 GM_registerMenuCommand("菜单", showMenu); prepareMenu(); // 检测地址改变,并触发对应的功能 var pathname = location.pathname; setInterval(function () { var newPathName = location.pathname; if (newPathName !== pathname) { console.debug("\u5730\u5740\u6539\u53D8", pathname, newPathName); pathname = newPathName; triggerFeatures(); } }); // 默认触发一次 triggerFeatures(); // 如果 `自动完成` 功能启用,每次刷新页面就会执行以下函数,即依次开启所有功能 // TODO // autoComplete = () => // features.forEach((feature: feature) => { // showMessage(`自动激活功能:${feature.title}`, "green"); // feature.task(); // }); })(); /* ------------ 渲染菜单 ------------ */ var customGradeLevels = function () { return getGMValue("qjh_customGradeLevels", []); }; var customSelfGradeLevels = function () { return getGMValue("qjh_customSelfGradeLevels", []); }; /** * 解析菜单 HTML */ function prepareMenu() { return __awaiter(this, void 0, void 0, function () { var menuElement, coursesGradeLevelsList, selfCoursesGradeLevelsList, titleElement, _loop_1, _i, _a, _b, selector, gradeLevels, customGradeLevelsList, customGradeLevelsListChangeHandler, closeButton, toggleInputs, _loop_2, _c, toggleInputs_1, toggleInput, featButtons, _loop_3, _d, featButtons_1, featButton; return __generator(this, function (_e) { switch (_e.label) { case 0: return [4 /*yield*/, waitForElementLoaded("#qjh-menu")]; case 1: menuElement = _e.sent(); return [4 /*yield*/, coursesGradeLevels()]; case 2: coursesGradeLevelsList = _e.sent(); return [4 /*yield*/, selfCoursesGradeLevels()]; case 3: selfCoursesGradeLevelsList = _e.sent(); if (coursesGradeLevels === null || selfCoursesGradeLevelsList === null) { showMessage("\u8BFE\u7A0B\u5E74\u7EA7\u5217\u8868\u6216\u81EA\u5B66\u8BFE\u7A0B\u5E74\u7EA7\u5217\u8868\u83B7\u53D6\u5931\u8D25\uFF01", "red"); } return [4 /*yield*/, waitForElementLoaded("#qjh-menu-title")]; case 4: titleElement = _e.sent(); titleElement.append(scriptVersion); _loop_1 = function (selector, gradeLevels, customGradeLevelsList, customGradeLevelsListChangeHandler) { var element, _loop_4, _f, gradeLevels_1, gradeLevel; return __generator(this, function (_g) { switch (_g.label) { case 0: return [4 /*yield*/, waitForElementLoaded(selector)]; case 1: element = _g.sent(); if (gradeLevels === null) { return [2 /*return*/, "continue"]; } _loop_4 = function (gradeLevel) { var label = document.createElement("label"); label.className = "form-checkbox form-inline"; var input = document.createElement("input"); input.type = "checkbox"; input.checked = // gradeLevel === converToGenericGradeLevel(accountGradeLevel()) || customGradeLevelsList().indexOf(gradeLevel) !== -1; input.onchange = function () { if (input.checked) { customGradeLevelsListChangeHandler(Array.of.apply(Array, __spreadArray(__spreadArray([], customGradeLevelsList(), false), [gradeLevel], false))); } else { customGradeLevelsListChangeHandler(customGradeLevelsList().filter(function (it) { return it !== gradeLevel; })); } }; var i = document.createElement("i"); i.className = "form-icon"; label.appendChild(input); label.appendChild(i); label.append(gradeLevel); element.appendChild(label); }; for (_f = 0, gradeLevels_1 = gradeLevels; _f < gradeLevels_1.length; _f++) { gradeLevel = gradeLevels_1[_f]; _loop_4(gradeLevel); } return [2 /*return*/]; } }); }; _i = 0, _a = [ { selector: "#qjh-menu-feat-courses", gradeLevels: coursesGradeLevelsList, customGradeLevelsList: customGradeLevels, customGradeLevelsListChangeHandler: function (value) { return GM_setValue("qjh_customGradeLevels", value); }, }, { selector: "#qjh-menu-feat-self-courses", gradeLevels: selfCoursesGradeLevelsList, customGradeLevelsList: customSelfGradeLevels, customGradeLevelsListChangeHandler: function (value) { return GM_setValue("qjh_customSelfGradeLevels", value); }, }, ]; _e.label = 5; case 5: if (!(_i < _a.length)) return [3 /*break*/, 8]; _b = _a[_i], selector = _b.selector, gradeLevels = _b.gradeLevels, customGradeLevelsList = _b.customGradeLevelsList, customGradeLevelsListChangeHandler = _b.customGradeLevelsListChangeHandler; return [5 /*yield**/, _loop_1(selector, gradeLevels, customGradeLevelsList, customGradeLevelsListChangeHandler)]; case 6: _e.sent(); _e.label = 7; case 7: _i++; return [3 /*break*/, 5]; case 8: return [4 /*yield*/, waitForElementLoaded("#qjh-menu-close-button")]; case 9: closeButton = _e.sent(); closeButton.onclick = function () { menuElement.style.display = "none"; }; toggleInputs = nodeListToArray(document.querySelectorAll("input")).filter(function (element) { return element.getAttribute("qjh-type") === "toggle"; }); _loop_2 = function (toggleInput) { var key = toggleInput.getAttribute("qjh-key"); toggleInput.checked = GM_getValue(key); toggleInput.onchange = function () { GM_setValue(key, toggleInput.checked); }; }; for (_c = 0, toggleInputs_1 = toggleInputs; _c < toggleInputs_1.length; _c++) { toggleInput = toggleInputs_1[_c]; _loop_2(toggleInput); } featButtons = nodeListToArray(document.querySelectorAll("button")).filter(function (element) { return element.getAttribute("qjh-feat-key") !== null; }); _loop_3 = function (featButton) { var key = featButton.getAttribute("qjh-feat-key"); var feature = features.find(function (feature) { return feature.key === key; }); featButton.onclick = function () { if (feature.enabled()) { showMessage("\u624B\u52A8\u6FC0\u6D3B\u529F\u80FD\uFF1A".concat(feature.title), "green"); feature.task(); } else { showMessage("\u529F\u80FD ".concat(feature.title, " \u672A\u88AB\u542F\u7528\uFF01"), "red"); } }; }; for (_d = 0, featButtons_1 = featButtons; _d < featButtons_1.length; _d++) { featButton = featButtons_1[_d]; _loop_3(featButton); } return [2 /*return*/]; } }); }); } /* ------------ 功能函数 ------------ */ /** * 开始课程学习,获取并自动提交答案 * @param courseId 课程 ID * @returns 是否提交成功(未测试) */ function startCourse(courseId) { return __awaiter(this, void 0, void 0, function () { var answers, data, response; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getCourseAnswers(courseId)]; case 1: answers = _a.sent(); if (!(answers === null)) return [3 /*break*/, 2]; showMessage("[".concat(courseId, "] \u65E0\u6CD5\u83B7\u53D6\u5F53\u524D\u8BFE\u7A0B\u7684\u7B54\u6848\uFF01"), "red"); return [2 /*return*/, false]; case 2: console.debug("\u6B63\u5728\u63D0\u4EA4\u8BFE\u7A0B [".concat(courseId, "] \u7B54\u6848...")); data = { courseId: courseId, examCommitReqDataList: answers.map(function (answer, index) { return { examId: index + 1, answer: Number(answer) || answer, // 如果是单选,则必须要为数字 }; }), reqtoken: reqtoken(), }; return [4 /*yield*/, commitExam(data)]; case 3: response = _a.sent(); console.debug("\u63D0\u4EA4\u8BFE\u7A0B [".concat(data.courseId, "] \u7B54\u6848"), response); return [2 /*return*/, !isNone(response)]; } }); }); } /** * 开始 `课程中心` 或 `自学课堂` 的学习 * * @param isSelfCourses 是否为自学 */ function taskCourses(isSelfCourses) { return __awaiter(this, void 0, void 0, function () { var gradeLevels, _i, gradeLevels_2, gradeLevel, coursesList, _a, courseIds, committed, _b, courseIds_1, courseId, result; return __generator(this, function (_c) { switch (_c.label) { case 0: if (!isLogined()) { showMessage("你还没有登录!", "red"); return [2 /*return*/]; } return [4 /*yield*/, (isSelfCourses ? selfCoursesGradeLevels : coursesGradeLevels)()]; case 1: gradeLevels = _c.sent(); if (gradeLevels === null) { showMessage("\u83B7\u53D6\u5E74\u7EA7\u540D\u5217\u8868\u5931\u8D25\uFF0C\u529F\u80FD\u5DF2\u4E2D\u6B62\uFF01", "red"); return [2 /*return*/]; } console.debug("获取总年级名列表", gradeLevels); gradeLevels = isSelfCourses ? customSelfGradeLevels() : customGradeLevels(); console.debug("已选择的年级列表", gradeLevels); _i = 0, gradeLevels_2 = gradeLevels; _c.label = 2; case 2: if (!(_i < gradeLevels_2.length)) return [3 /*break*/, 13]; gradeLevel = gradeLevels_2[_i]; if (!isSelfCourses) return [3 /*break*/, 4]; return [4 /*yield*/, getSelfCoursesByGradeLevel(gradeLevel)]; case 3: _a = _c.sent(); return [3 /*break*/, 6]; case 4: return [4 /*yield*/, getCoursesByGradeLevel(gradeLevel)]; case 5: _a = _c.sent(); _c.label = 6; case 6: coursesList = _a; if (coursesList === null) { showMessage("[".concat(gradeLevel, "] \u83B7\u53D6\u5F53\u524D\u5E74\u7EA7\u7684\u8BFE\u7A0B\u5217\u8868\u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7\u5F53\u524D\u5E74\u7EA7\uFF01"), "red"); } courseIds = coursesList .filter(function (it) { return !it.isFinish && it.title !== "期末考试"; }) .map(function (it) { return it.courseId; }); if (courseIds.length === 0) { console.debug("[".concat(gradeLevel, "] \u6240\u6709").concat(isSelfCourses ? "自学" : "", "\u8BFE\u7A0B\u90FD\u662F\u5B8C\u6210\u72B6\u6001\uFF0C\u5DF2\u8DF3\u8FC7\uFF01")); return [2 /*return*/]; } console.debug("[".concat(gradeLevel, "] \u672A\u5B8C\u6210\u7684").concat(isSelfCourses ? "自学" : "", "\u8BFE\u7A0B"), courseIds); committed = 0; _b = 0, courseIds_1 = courseIds; _c.label = 7; case 7: if (!(_b < courseIds_1.length)) return [3 /*break*/, 11]; courseId = courseIds_1[_b]; if (courseId === "finalExam") { return [2 /*return*/]; } if (!!isNone(courseId)) return [3 /*break*/, 9]; return [4 /*yield*/, startCourse(courseId)]; case 8: result = _c.sent(); if (result) { committed++; } else { console.error("[".concat(courseId, "] \u65E0\u6CD5\u63D0\u4EA4\u5F53\u524D\u8BFE\u7A0B\uFF0C\u5DF2\u8DF3\u8FC7\uFF01")); } return [3 /*break*/, 10]; case 9: console.error("[".concat(gradeLevel, "] \u65E0\u6CD5\u627E\u5230 courseId\uFF0C\u5DF2\u8DF3\u8FC7\uFF01")); _c.label = 10; case 10: _b++; return [3 /*break*/, 7]; case 11: // TODO 暂时还没完成 autoComplete showMessage("\u6210\u529F\u5B8C\u6210\u4E86 ".concat(committed, " \u4E2A").concat(isSelfCourses ? "自学" : "", "\u8BFE\u7A0B\uFF01"), "green"); _c.label = 12; case 12: _i++; return [3 /*break*/, 2]; case 13: return [2 /*return*/]; } }); }); } /** * 开始手动单个课程自动完成 */ function taskSingleCourse() { return __awaiter(this, void 0, void 0, function () { var courseId, answers; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!isLogined()) { showMessage("你还没有登录!", "red"); return [2 /*return*/]; } courseId = location.pathname.match(/(\d+)/g)[0]; return [4 /*yield*/, getCourseAnswers(courseId)]; case 1: answers = _a.sent(); return [4 /*yield*/, emulateExamination(answers, "#app > div > div.home-container > div > div > div > div > div > button", "#app > div > div.home-container > div > div > div > div > div > div.exam-content-btnbox > button", "#app > div > div.home-container > div > div > div > div > div > div.exam-content-btnbox > div > button.ant-btn-primary", function (answers, _) { var firstAnswer = answers.shift().toString(); return { answer: firstAnswer, matchedQuestion: null, }; }, "\u7B54\u9898 [".concat(courseId, "]"), answers.length)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); } /** * 考试全自动完成模拟 * @param answers 答案列表 * @param startButtonSelector 开始按钮选择器 * @param primaryNextButtonSelector 初下一题按钮选择器 * @param secondaryNextButtonSelector 次下一题按钮选择器 * @param answerHandler 答案处理器,传入答案和问题并由该处理器处理完毕后返回答案和匹配到的问题至本函数 * @param examinationName 答题名称 * @param size */ function emulateExamination(answers, startButtonSelector, primaryNextButtonSelector, secondaryNextButtonSelector, answerHandler, examinationName, size) { if (size === void 0) { size = 100; } return __awaiter(this, void 0, void 0, function () { var isExaminationStarted, count, next, startButton; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: isExaminationStarted = false; count = 0; next = function (nextAnswers, nextButton) { if (nextButton === void 0) { nextButton = null; } return __awaiter(_this, void 0, void 0, function () { var questionElement, questionText, _firstNextButton, _a, answer, matchedQuestion, selections, displayAnswer, finalQuestion, _i, _b, answerIndex, selectionElement; return __generator(this, function (_c) { switch (_c.label) { case 0: return [4 /*yield*/, waitForElementLoaded(".exam-content-question")]; case 1: questionElement = _c.sent(); questionText = removeSpaces(questionElement.innerText.split("\n")[0] // 获取第一行(题目都是在第一行) ); if (!!isExaminationStarted) return [3 /*break*/, 4]; return [4 /*yield*/, waitForElementLoaded(primaryNextButtonSelector)]; case 2: _firstNextButton = _c.sent(); isExaminationStarted = true; return [4 /*yield*/, next(nextAnswers, _firstNextButton)]; case 3: _c.sent(); return [3 /*break*/, 5]; case 4: // 如果已经开始过,那么 `count` 必定大于 0 // 此时,会把下一步按钮从 `初下一题` 按钮更换为 `次下一题` 按钮 if (count > 0) { nextButton = document.querySelector(secondaryNextButtonSelector); } // 根据题量大小 `size` 开始答题 if (!isNone(size) && count < size) { // 用户点击 `下一步` 按钮会继续触发本函数,传入下一题的答案和下一题的按钮 // * 延时为 200ms nextButton.onclick = function () { setTimeout(function () { return next(nextAnswers, nextButton); }, 200); return; }; _a = answerHandler(answers, questionText), answer = _a.answer, matchedQuestion = _a.matchedQuestion; selections = document.getElementsByClassName("exam-single-content-box"); console.debug("选择", answer, selections); displayAnswer = toDisplayAnswer(answer); finalQuestion = matchedQuestion || questionText; if (!isFullAutomaticEmulationEnabled()) { showMessage("".concat(finalQuestion ? finalQuestion + "\n" : "", "\u7B2C ").concat(count + 1, " \u9898\u7B54\u6848\uFF1A").concat(displayAnswer), "green"); } // 自动选择答案 for (_i = 0, _b = answer.split(",").map(function (it) { return Number(it); }); _i < _b.length; _i++) { answerIndex = _b[_i]; selectionElement = selections[answerIndex]; // 模拟点击 selectionElement.click(); } // 如果是全自动,会自动点击下一题的按钮 if (isFullAutomaticEmulationEnabled()) { nextButton.click(); } count++; } _c.label = 5; case 5: return [2 /*return*/]; } }); }); }; return [4 /*yield*/, waitForElementLoaded(startButtonSelector)]; case 1: startButton = _a.sent(); startButton.onclick = function () { showMessage("\u5F00\u59CB ".concat(examinationName, "\uFF01"), "blue"); next(answers, null); }; return [2 /*return*/]; } }); }); } /** * 自动在课程视频页面添加 `跳过` 按钮 */ function taskSkip() { return __awaiter(this, void 0, void 0, function () { var courseId, span, skipButton, skipSpan; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!isLogined()) { showMessage("你还没有登录!", "red"); return [2 /*return*/]; } courseId = location.pathname.match(/(\d+)/g)[0]; return [4 /*yield*/, waitForElementLoaded("#app > div > div.home-container > div > div > div.course-title-box > div > a > span")]; case 1: span = _a.sent(); span.style.display = "inline-flex"; skipButton = document.createElement("button"); skipButton.type = "button"; // 和青骄第二课堂的按钮用同样的样式 skipButton.className = "ant-btn ant-btn-danger ant-btn-lg"; skipSpan = document.createElement("span"); skipSpan.innerText = "跳过"; skipButton.appendChild(skipSpan); skipButton.onclick = function () { location.href = "/courses/exams/".concat(courseId); }; span.appendChild(skipButton); return [2 /*return*/]; } }); }); } /** * 自动获取学分 */ function taskGetCredit() { return __awaiter(this, void 0, void 0, function () { var num, categories, done, failed, liked, _i, categories_1, category, data, resources, _a, resources_1, resource, resourceId, resourceData, result, likeResult, beforeDone, checkSuccess; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!isLogined()) { showMessage("你还没有登录!", "red"); return [2 /*return*/]; } return [4 /*yield*/, addMedal()]; case 1: num = _b.sent(); if (num !== undefined) { showMessage("\u6210\u529F\u9886\u53D6\u7981\u6BD2\u5FBD\u7AE0 [".concat(num, "]!"), "green"); } else if (num === null) { showMessage("领取徽章失败!", "red"); } else { console.warn("无法领取徽章(可能已领取过),已跳过!"); } categories = [ { name: "public_good", tag: "read" }, { name: "ma_yun_recommend", tag: "labour" }, { 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" }, ]; done = 0; failed = 0; liked = 0; _i = 0, categories_1 = categories; _b.label = 2; case 2: if (!(_i < categories_1.length)) return [3 /*break*/, 9]; category = categories_1[_i]; data = { categoryName: category.name, pageNo: 1, pageSize: 100, reqtoken: reqtoken(), tag: category.tag, }; return [4 /*yield*/, getBeforeResourcesByCategoryName(data)]; case 3: resources = _b.sent(); if (resources === null) { console.error("\u65E0\u6CD5\u83B7\u53D6\u5206\u7C7B ".concat(category.name, " \u7684\u8D44\u6E90\uFF0C\u5DF2\u8DF3\u8FC7\uFF01")); return [3 /*break*/, 8]; } console.debug("\u83B7\u53D6\u5206\u7C7B ".concat(category.name, " \u7684\u8D44\u6E90"), resources); _a = 0, resources_1 = resources; _b.label = 4; case 4: if (!(_a < resources_1.length)) return [3 /*break*/, 8]; resource = resources_1[_a]; resourceId = resource.resourceId; resourceData = { resourceId: resourceId, reqtoken: reqtoken() }; return [4 /*yield*/, addPCPlayPV(resourceData)]; case 5: result = _b.sent(); if (result) { console.debug("\u6210\u529F\u5B8C\u6210\u8D44\u6E90 [".concat(resourceId, "]\uFF1A").concat(resource.title)); done++; } else { console.error("\u65E0\u6CD5\u5B8C\u6210\u8D44\u6E90 ".concat(resourceId, "\uFF0C\u5DF2\u8DF3\u8FC7\uFF01")); failed++; } return [4 /*yield*/, likePC(resourceData)]; case 6: likeResult = _b.sent(); if (likeResult) { console.debug("\u6210\u529F\u70B9\u8D5E\u8D44\u6E90 [".concat(resourceId, "]\uFF01")); liked++; } else { console.error("\u8D44\u6E90\u70B9\u8D5E\u5931\u8D25 [".concat(resourceId, "]\uFF0C\u5DF2\u8DF3\u8FC7\uFF01")); } _b.label = 7; case 7: _a++; return [3 /*break*/, 4]; case 8: _i++; return [3 /*break*/, 2]; case 9: beforeDone = done; checkSuccess = setInterval(function () { if (done !== 0) { if (done === beforeDone) { showMessage("\u6210\u529F\u5B8C\u6210 ".concat(done, "/").concat(failed, " \u4E2A\u8D44\u6E90\uFF0C\u70B9\u8D5E ").concat(liked, " \u4E2A\uFF01"), "green"); // TODO 自动完成 // autoCompleteCreditsDone = true; // GM_setValue('qjh_autoCompleteCreditsDone', true); clearInterval(checkSuccess); } else { beforeDone = done; } } }, 500); return [2 /*return*/]; } }); }); } /** * 开始完成期末考试 */ function taskFinalExamination() { return __awaiter(this, void 0, void 0, function () { var supportedFinal, gradeLevel, paperName, papers_1; return __generator(this, function (_a) { switch (_a.label) { case 0: supportedFinal = libs.supportedFinal; gradeLevel = accountGradeLevel(); if (!supportedFinal.hasOwnProperty(gradeLevel)) return [3 /*break*/, 2]; paperName = supportedFinal[gradeLevel]; papers_1 = libs[paperName]; papers_1 = papers_1.map(function (it) { // it.answer -> ABC return { question: it.question, answer: toAnswer(it.answer) }; }); return [4 /*yield*/, emulateExamination(papers_1.map(function (it) { return it.answer; }), "#app > div > div.home-container > div > div > div > div > div > button", "#app > div > div.home-container > div > div > div > div > div > div.exam-content-btnbox > button", "#app > div > div.home-container > div > div > div > div > div > div.exam-content-btnbox > div > button.ant-btn.ant-btn-primary", function (_, question) { var _a = accurateFind(papers_1, question) || fuzzyFind(papers_1, question), answer = _a.answer, realQuestion = _a.realQuestion; return { answer: answer, matchedQuestion: realQuestion, }; }, "期末考试", 10 // TODO 这个 10 是干什么的我还没搞清楚,之后再说 )]; case 1: _a.sent(); return [3 /*break*/, 3]; case 2: showMessage("\u4F60\u7684\u5E74\u7EA7 [".concat(gradeLevel, "] \u6682\u672A\u652F\u6301\u671F\u672B\u8003\u8BD5\uFF01"), "red"); return [2 /*return*/]; case 3: return [2 /*return*/]; } }); }); } /* ------------ 实用函数 ------------ */ /** * 展示消息实用函数 * @param text 消息文本 * @param color 颜色 */ function showMessage(text, color) { Toastify({ text: text, duration: toastifyDuration, newWindow: true, gravity: toastifyGravity, position: toastifyPosition, stopOnFocus: true, style: { background: color }, }).showToast(); } /** * 提示功能暂时不可用 * * 如果有些功能需要等待脚本加载完全完成之后才能使用,在加载完全前调用功能就会触发此函数 * * 因此如果多次见到这个提示,很可能是脚本 bug * @param name 功能名 */ function featureNotAvailable(name) { if (name === void 0) { name = "(未知)"; } showMessage("".concat(name, " \u529F\u80FD\u5F53\u524D\u4E0D\u53EF\u7528\uFF0C\u8BF7\u5C1D\u8BD5\u5237\u65B0\u9875\u9762\u3002\u5982\u679C\u95EE\u9898\u4F9D\u65E7\u8BF7\u4E0A\u62A5\u8FD9\u4E2A bug\uFF01"), "red"); } /** * 判断一个对象是否是无效对象 * @param obj 对象 * @returns 是否是无效对象 */ function isNone(obj) { return obj == undefined || obj == null; } /** * 获取油猴本地变量 * @param name 本地变量名 * @param defaultValue 默认值 * @returns 值 */ function getGMValue(name, defaultValue) { var value = GM_getValue(name); if (isNone(value)) { value = defaultValue; GM_setValue(name, defaultValue); } return value; } /** * 等待页面某个元素完全加载完成并获取这个元素对象 * @param querySelector 选择器 */ function waitForElementLoaded(querySelector) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve, reject) { var attempts = 0; var tryFind = function () { var element = document.querySelector(querySelector); if (element) { resolve(element); } else { attempts++; if (attempts >= 30) { console.error("\u65E0\u6CD5\u627E\u5230\u5143\u7D20 [".concat(querySelector, "]\uFF0C\u5DF2\u653E\u5F03\uFF01")); reject(); } else { setTimeout(tryFind, 250 * Math.pow(1.1, attempts)); } } }; tryFind(); })]; }); }); } /** * 删除文本里的所有空格 * @param string 输入文本 * @returns 删除空格后的文本 */ function removeSpaces(string) { return string.replace(/\s*/g, ""); } /** * 把通用答案转为显示友好答案 * @param answers 通用答案,如 `0,1,2` * @returns 显示友好答案,如 `ABC` */ function toDisplayAnswer(answer) { var alphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); var result = ""; for (var _i = 0, _a = answer.split(","); _i < _a.length; _i++) { var singleAnswer = _a[_i]; var index = Number(singleAnswer); result = result + alphas[index]; } return result; } /** * 把显示友好答案转为通用答案 * @param answers 显示友好答案,如 `ABC` * @returns 通用答案,如 `0,1,2` */ function toAnswer(answers) { var alphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""); var result = ""; for (var _i = 0, answers_1 = answers; _i < answers_1.length; _i++) { var answer = answers_1[_i]; result = result + alphas.indexOf(answer); } return result; } /** * 把元素节点列表转换为元素数组 * @param nodeList 元素节点列表 * @returns 元素数组 */ function nodeListToArray(nodeList) { return Array.prototype.slice.call(nodeList); } /** * 把原年级名转为通用年级名 * @param gradeLevel 原年级名,如 `八年级` * @returns 通用年级名,如 `初二` */ function converToGenericGradeLevel(gradeLevel) { var mapping = { 七年级: "初一", 八年级: "初二", 九年级: "初三", }; return mapping[gradeLevel]; } /** * 比较两个数组之间的差异并返回结果 * @param array1 第一个数组 * @param array2 第二个数组 * @returns 两个数组之间的差异的数组 */ function arrayDiff(array1, array2) { return array1.concat(array2).filter(function (v, _, array) { return array.indexOf(v) === array.lastIndexOf(v); }); } /** * 在题库中精确匹配问题 * @param papers 待查找的问题列表 * @param question 网页显示的问题文本 * @returns 匹配出来的答案和真正的问题文本,匹配不到会返回 `null` */ function accurateFind(papers, question) { var result = papers.find(function (it) { return removeSpaces(it.question) === question; }); if (!isNone(result)) { console.debug("\u7CBE\u786E\u5339\u914D\u95EE\u9898\uFF1A".concat(question, " \u2192 ").concat(result.question)); return { answer: result.answer, realQuestion: question }; } else { return null; } } /** * 在题库中模糊匹配问题 * @param papers 待查找的问题列表 * @param question 网页显示的问题文本 * @returns 匹配出来的答案和真正的问题文本 */ function fuzzyFind(papers, question) { // 先把问题文本转为字符列表 var chars = question.split(""); // 取它的长度(即文本的长度) var length = chars.length; // 临时存储 var percentages = []; for (var _i = 0, papers_2 = papers; _i < papers_2.length; _i++) { var paper = papers_2[_i]; // 把题库中的问题文本转为字符列表 var questionChars = paper.question.split(""); // 比较原文本和题库的题,并拿到不重复的部分 var diff = arrayDiff(chars, questionChars); // 取它的长度(即和当前问题文本不匹配的字符数量) var diffLength = diff.length; // 将不匹配的字符数量与原文本字符数量相除,得到不匹配度 var percentage = diffLength / length; percentages.push({ question: paper.question, answer: paper.answer, unconfidence: percentage, }); } // 通过排序,获得不匹配度最低的(即匹配度最高的) var theMostConfident = percentages.sort(function (a, b) { return a.unconfidence - b.unconfidence; })[0]; // 获得匹配度最高的问题的问题文本和答案,返回 var theMostConfidentQuestion = theMostConfident.question; var confidence = 1 - theMostConfident.unconfidence; console.debug("\u6A21\u7CCA\u5339\u914D\u95EE\u9898\uFF08".concat(confidence, "\uFF09\uFF1A").concat(question, " \u2192 ").concat(theMostConfidentQuestion)); return { answer: theMostConfident.answer, realQuestion: theMostConfidentQuestion, }; } var container = document.createElement("div"); container.setAttribute("id", "qjh-menu"); container.innerHTML = "\n\n