From 6f03fb4d3545f02f8ccfeb14fa7b5d8169c15348 Mon Sep 17 00:00:00 2001 From: enncy <877526278@qq.com> Date: Thu, 10 Feb 2022 17:49:42 +0800 Subject: [PATCH] feat(scripts): add script package --- CHANGELOG.md | 16 ++ packages/app/index.js | 2 +- packages/scripts/index.js | 1 + packages/scripts/lib/src/index.js | 13 ++ .../scripts/lib/src/qa/handler/completion.js | 6 + packages/scripts/lib/src/qa/handler/index.js | 39 +++++ .../scripts/lib/src/qa/handler/judgment.js | 24 +++ .../lib/src/qa/handler/multiple.choice.js | 13 ++ .../lib/src/qa/handler/single.choice.js | 10 ++ packages/scripts/lib/src/qa/index.js | 153 ++++++++++++++++++ .../scripts/lib/src/qa/option.resolver.js | 15 ++ .../lib/src/qa/question.type.resolver.js | 57 +++++++ packages/scripts/lib/src/qa/types.js | 2 + packages/scripts/lib/test/cx.js | 99 ++++++++++++ packages/scripts/package.json | 15 +- packages/scripts/src/index.ts | 2 + packages/scripts/src/qa/handler/completion.ts | 4 + packages/scripts/src/qa/handler/index.ts | 39 +++++ packages/scripts/src/qa/handler/judgment.ts | 22 +++ .../scripts/src/qa/handler/multiple.choice.ts | 10 ++ .../scripts/src/qa/handler/single.choice.ts | 8 + packages/scripts/src/qa/index.ts | 152 +++++++++++++++++ packages/scripts/src/qa/option.resolver.ts | 12 ++ .../scripts/src/qa/question.type.resolver.ts | 51 ++++++ packages/scripts/src/qa/types.ts | 141 ++++++++++++++++ packages/scripts/test/cx.ts | 55 +++++++ packages/scripts/tsconfig.json | 11 ++ packages/scripts/webpack.config.js | 17 ++ 28 files changed, 986 insertions(+), 3 deletions(-) create mode 100644 packages/scripts/index.js create mode 100644 packages/scripts/lib/src/index.js create mode 100644 packages/scripts/lib/src/qa/handler/completion.js create mode 100644 packages/scripts/lib/src/qa/handler/index.js create mode 100644 packages/scripts/lib/src/qa/handler/judgment.js create mode 100644 packages/scripts/lib/src/qa/handler/multiple.choice.js create mode 100644 packages/scripts/lib/src/qa/handler/single.choice.js create mode 100644 packages/scripts/lib/src/qa/index.js create mode 100644 packages/scripts/lib/src/qa/option.resolver.js create mode 100644 packages/scripts/lib/src/qa/question.type.resolver.js create mode 100644 packages/scripts/lib/src/qa/types.js create mode 100644 packages/scripts/lib/test/cx.js create mode 100644 packages/scripts/src/index.ts create mode 100644 packages/scripts/src/qa/handler/completion.ts create mode 100644 packages/scripts/src/qa/handler/index.ts create mode 100644 packages/scripts/src/qa/handler/judgment.ts create mode 100644 packages/scripts/src/qa/handler/multiple.choice.ts create mode 100644 packages/scripts/src/qa/handler/single.choice.ts create mode 100644 packages/scripts/src/qa/index.ts create mode 100644 packages/scripts/src/qa/option.resolver.ts create mode 100644 packages/scripts/src/qa/question.type.resolver.ts create mode 100644 packages/scripts/src/qa/types.ts create mode 100644 packages/scripts/test/cx.ts create mode 100644 packages/scripts/tsconfig.json create mode 100644 packages/scripts/webpack.config.js diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29b..c4441ea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# 3.0.0-beta (2022-02-09) + + +### Bug Fixes + +* **package.json:** fix dependency security ([948662a](https://github.com/enncy/online-course-script/commit/948662a580aab60a3ff70c64111aa36c40b5a4ce)) + + +### Features + +* **app and web:** add elctron-builder in app , init page view in web ([fe60df6](https://github.com/enncy/online-course-script/commit/fe60df65dfeed1607ab89a941fc6b6eb627132fc)) +* **init:** init project ([0675ac6](https://github.com/enncy/online-course-script/commit/0675ac6a631e8946a52e7e4e655b28faee8248d4)) +* **packages:** init packages : web app scripts ([24e5386](https://github.com/enncy/online-course-script/commit/24e5386ec86dec33cc696fda6b5956785e2c1359)) + + + diff --git a/packages/app/index.js b/packages/app/index.js index 681cd831..0dd7f39d 100644 --- a/packages/app/index.js +++ b/packages/app/index.js @@ -1,4 +1,4 @@ -const { app, BrowserWindow, Menu, ipcMain } = require("electron"); +const { app, BrowserWindow, ipcMain } = require("electron"); const Store = require("electron-store"); app.commandLine.appendSwitch("enable-webgl"); diff --git a/packages/scripts/index.js b/packages/scripts/index.js new file mode 100644 index 00000000..baf59287 --- /dev/null +++ b/packages/scripts/index.js @@ -0,0 +1 @@ +var OCS;(()=>{var e={496:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__exportStar||function(e,t){for(var n in e)"default"===n||Object.prototype.hasOwnProperty.call(t,n)||r(t,e,n)};Object.defineProperty(t,"__esModule",{value:!0}),i(n(456),t)},62:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.completionHandler=void 0,t.completionHandler=function(e,t){}},274:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getOptionSimilarly=void 0;var i=r(n(386));t.getOptionSimilarly=function(e,t){return t.map((function(t){return t.textContent?i.default.findBestMatch(t.textContent,e).bestMatch.rating:0}))}},799:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.judgmentHandler=t.startWithRight=t.wrong=t.right=void 0;var r=n(386);t.right="是|对|正确|√|对的|是的|正确的|true|yes|YES|Yes",t.wrong="否|错|错误|x|错的|不正确的|不正确|不是|不是的|false|no|NO|No",t.startWithRight=!0,t.judgmentHandler=function(e,n){var i=r.findBestMatch(e[0],t.right.split("|").concat(t.wrong.split("|"))).bestMatch.target,o=1;t.startWithRight?RegExp(t.right).test(i)&&(o=0):RegExp(t.wrong).test(i)&&(o=0),n[o].click()}},682:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.multipleChoiceHandler=void 0;var r=n(274);t.multipleChoiceHandler=function(e,t){for(var n=(0,r.getOptionSimilarly)(e,t),i=0;i.6&&t[i].click()}},444:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.singleChoiceHandler=void 0;var r=n(274);t.singleChoiceHandler=function(e,t){var n=(0,r.getOptionSimilarly)(e,t),i=n.sort()[n.length-1];t[n.findIndex((function(e){return e===i}))].click()}},456:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.QA=t.fromElementLike=t.fromElementArrayLike=t.questionTypeWords=t.getQuestionType=t.getOptions=void 0;var r=n(62),i=n(799),o=n(682),l=n(444),u=n(65),s=n(732);Object.defineProperty(t,"getOptions",{enumerable:!0,get:function(){return s.getOptions}});var a=n(65);function c(e,t){if(void 0===t&&(t=document.body),"string"==typeof e){var n=t.querySelectorAll(e);return null===n?void 0:Array.from(n)}return"function"==typeof e?Reflect.apply(e,document,[t]):e}function d(e,t){if(void 0===t&&(t=document.body),"string"==typeof e){var n=t.querySelector(e);return null===n?void 0:n}return"function"==typeof e?Reflect.apply(e,document,[t]):e}Object.defineProperty(t,"getQuestionType",{enumerable:!0,get:function(){return a.getQuestionType}}),Object.defineProperty(t,"questionTypeWords",{enumerable:!0,get:function(){return a.questionTypeWords}}),t.fromElementArrayLike=c,t.fromElementLike=d;var p=function(){function e(t){this.questions=[],this.options=Object.assign(e.default,t),this.handleQuestionTemplate()}return e.prototype.handleQuestionTemplate=function(t){var n,r,i,o,l,s,a;void 0===t&&(t=e.default.question);for(var p=0,g=c(t.container)||[];p{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getOptions=void 0;var r=n(65);t.getOptions=function(e,t){var n=Array.from(e.querySelectorAll(t)),i=(0,r.getQuestionType)(e);return n.map((function(e){return"completion"===i?"":e.innerText}))}},65:(e,t)=>{"use strict";function n(e,t){return t.some((function(t){return RegExp(t).test(e)}))}Object.defineProperty(t,"__esModule",{value:!0}),t.questionTypeWords=t.getQuestionType=void 0,t.getQuestionType=function(e){var r=void 0;if("string"==typeof e)n(e,t.questionTypeWords.singleChoice)?r="single_choice":n(e,t.questionTypeWords.multipleChoice)?r="multiple_choice":n(e,t.questionTypeWords.judgment)?r="judgment":n(e,t.questionTypeWords.completion)&&(r="completion");else{var i=Array.from(e.querySelectorAll("input"));0!==i.length&&(2===i.length?i.every((function(e){return"radio"===e.type}))&&(r="judgment"):e.querySelector("textarea")?r="completion":i.every((function(e){return"radio"===e.type}))?r="single_choice":i.every((function(e){return"checkbox"===e.type}))&&(r="multiple_choice"))}return r},t.questionTypeWords={singleChoice:["单选","单选题","单项选择题"],multipleChoice:["多选","多选题","多项选择题"],judgment:["判断","判断题"],completion:["填空","填空题"]}},386:e=>{function t(e,t){if((e=e.replace(/\s+/g,""))===(t=t.replace(/\s+/g,"")))return 1;if(e.length<2||t.length<2)return 0;let n=new Map;for(let t=0;t0&&(n.set(i,o-1),r++)}return 2*r/(e.length+t.length-2)}e.exports={compareTwoStrings:t,findBestMatch:function(e,n){if(!function(e,t){return"string"==typeof e&&!!Array.isArray(t)&&!!t.length&&!t.find((function(e){return"string"!=typeof e}))}(e,n))throw new Error("Bad arguments: First argument should be a string, second should be an array of strings");const r=[];let i=0;for(let o=0;or[i].rating&&(i=o)}return{ratings:r,bestMatch:r[i],bestMatchIndex:i}}}}},t={},n=function n(r){var i=t[r];if(void 0!==i)return i.exports;var o=t[r]={exports:{}};return e[r].call(o.exports,o,o.exports,n),o.exports}(496);OCS=n})(); \ No newline at end of file diff --git a/packages/scripts/lib/src/index.js b/packages/scripts/lib/src/index.js new file mode 100644 index 00000000..70ad4435 --- /dev/null +++ b/packages/scripts/lib/src/index.js @@ -0,0 +1,13 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./qa"), exports); diff --git a/packages/scripts/lib/src/qa/handler/completion.js b/packages/scripts/lib/src/qa/handler/completion.js new file mode 100644 index 00000000..e7dbc720 --- /dev/null +++ b/packages/scripts/lib/src/qa/handler/completion.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.completionHandler = void 0; +function completionHandler(answers, target) { +} +exports.completionHandler = completionHandler; diff --git a/packages/scripts/lib/src/qa/handler/index.js b/packages/scripts/lib/src/qa/handler/index.js new file mode 100644 index 00000000..c4d89f2d --- /dev/null +++ b/packages/scripts/lib/src/qa/handler/index.js @@ -0,0 +1,39 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getOptionSimilarly = void 0; +var string_similarity_1 = __importDefault(require("string-similarity")); +// export async function handleQuestion(container: string | Element, answer: AnswerProvider, question: QuestionProvider) { +// const element = fromElementLike(container); +// const type = question.type(element); +// const title = question.title(element).textContent; +// if (title) { +// const options = question.options(element); +// const answers = await answer({ title, options: options.map((option) => option.textContent || ""), type }); +// if (type === "single_choice") { +// return singleChoiceHandler(answers, options); +// } +// if (type === 'multiple_choice') { +// return multipleChoiceHandler(answers, options); +// } +// if (type === 'judgment') { +// return judgmentHandler(answers, options); +// } +// if (type === 'completion') { +// return singleChoiceHandler(answers, options); +// } +// } +// } +function getOptionSimilarly(answers, options) { + return options.map(function (option) { + if (option.textContent) { + return string_similarity_1.default.findBestMatch(option.textContent, answers).bestMatch.rating; + } + else { + return 0; + } + }); +} +exports.getOptionSimilarly = getOptionSimilarly; diff --git a/packages/scripts/lib/src/qa/handler/judgment.js b/packages/scripts/lib/src/qa/handler/judgment.js new file mode 100644 index 00000000..140bc525 --- /dev/null +++ b/packages/scripts/lib/src/qa/handler/judgment.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.judgmentHandler = exports.startWithRight = exports.wrong = exports.right = void 0; +var similarity = require("string-similarity"); +exports.right = "是|对|正确|√|对的|是的|正确的|true|yes|YES|Yes"; +exports.wrong = "否|错|错误|x|错的|不正确的|不正确|不是|不是的|false|no|NO|No"; +exports.startWithRight = true; +function judgmentHandler(answers, options) { + var target = similarity.findBestMatch(answers[0], exports.right.split("|").concat(exports.wrong.split("|"))).bestMatch.target; + var index = 1; + // 开始选择 + if (exports.startWithRight) { + if (RegExp(exports.right).test(target)) { + index = 0; + } + } + else { + if (RegExp(exports.wrong).test(target)) { + index = 0; + } + } + options[index].click(); +} +exports.judgmentHandler = judgmentHandler; diff --git a/packages/scripts/lib/src/qa/handler/multiple.choice.js b/packages/scripts/lib/src/qa/handler/multiple.choice.js new file mode 100644 index 00000000..46565148 --- /dev/null +++ b/packages/scripts/lib/src/qa/handler/multiple.choice.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.multipleChoiceHandler = void 0; +var _1 = require("."); +function multipleChoiceHandler(answers, options) { + var ratings = (0, _1.getOptionSimilarly)(answers, options); + for (var i = 0; i < ratings.length; i++) { + if (ratings[i] > 0.6) { + options[i].click(); + } + } +} +exports.multipleChoiceHandler = multipleChoiceHandler; diff --git a/packages/scripts/lib/src/qa/handler/single.choice.js b/packages/scripts/lib/src/qa/handler/single.choice.js new file mode 100644 index 00000000..a8ba988a --- /dev/null +++ b/packages/scripts/lib/src/qa/handler/single.choice.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.singleChoiceHandler = void 0; +var _1 = require("."); +function singleChoiceHandler(answers, options) { + var ratings = (0, _1.getOptionSimilarly)(answers, options); + var max = ratings.sort()[ratings.length - 1]; + options[ratings.findIndex(function (r) { return r === max; })].click(); +} +exports.singleChoiceHandler = singleChoiceHandler; diff --git a/packages/scripts/lib/src/qa/index.js b/packages/scripts/lib/src/qa/index.js new file mode 100644 index 00000000..33cb37b1 --- /dev/null +++ b/packages/scripts/lib/src/qa/index.js @@ -0,0 +1,153 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.QA = exports.fromElementLike = exports.fromElementArrayLike = exports.questionTypeWords = exports.getQuestionType = exports.getOptions = void 0; +var completion_1 = require("./handler/completion"); +var judgment_1 = require("./handler/judgment"); +var multiple_choice_1 = require("./handler/multiple.choice"); +var single_choice_1 = require("./handler/single.choice"); +var question_type_resolver_1 = require("./question.type.resolver"); +var option_resolver_1 = require("./option.resolver"); +Object.defineProperty(exports, "getOptions", { enumerable: true, get: function () { return option_resolver_1.getOptions; } }); +var question_type_resolver_2 = require("./question.type.resolver"); +Object.defineProperty(exports, "getQuestionType", { enumerable: true, get: function () { return question_type_resolver_2.getQuestionType; } }); +Object.defineProperty(exports, "questionTypeWords", { enumerable: true, get: function () { return question_type_resolver_2.questionTypeWords; } }); +function fromElementArrayLike(target, container) { + if (container === void 0) { container = document.body; } + if (typeof target === "string") { + var el = container.querySelectorAll(target); + if (el === null) { + return undefined; + } + else { + return Array.from(el); + } + } + else if (typeof target === "function") { + return Reflect.apply(target, document, [container]); + } + else { + return target; + } +} +exports.fromElementArrayLike = fromElementArrayLike; +function fromElementLike(target, container) { + if (container === void 0) { container = document.body; } + if (typeof target === "string") { + var el = container.querySelector(target); + if (el === null) { + return undefined; + } + else { + return el; + } + } + else if (typeof target === "function") { + return Reflect.apply(target, document, [container]); + } + else { + return target; + } +} +exports.fromElementLike = fromElementLike; +var QA = /** @class */ (function () { + function QA(options) { + this.questions = []; + this.options = Object.assign(QA.default, options); + this.handleQuestionTemplate(); + } + /** + * resolve {@link QuestionTemplate} to {@link Question} + */ + QA.prototype.handleQuestionTemplate = function (question) { + var _a, _b, _c, _d, _e, _f, _g; + if (question === void 0) { question = QA.default.question; } + for (var _i = 0, _h = fromElementArrayLike(question.container) || []; _i < _h.length; _i++) { + var container = _h[_i]; + var el = container; + var title = fromElementLike(question.title, el); + var type = (0, question_type_resolver_1.getQuestionType)((title === null || title === void 0 ? void 0 : title.textContent) || "") || (0, question_type_resolver_1.getQuestionType)(el); + console.log({ + container: container, + title: title, + type: type, + }); + if (type) { + var qe = { + container: container, + type: type, + title: fromElementLike(question.title, el), + single: type === "single_choice" + ? { + text: fromElementLike((_a = question.single) === null || _a === void 0 ? void 0 : _a.text, el), + target: fromElementLike((_b = question.single) === null || _b === void 0 ? void 0 : _b.target, el), + } + : {}, + multiple: type === "multiple_choice" + ? { + text: fromElementLike((_c = question.multiple) === null || _c === void 0 ? void 0 : _c.text, el), + target: fromElementLike((_d = question.multiple) === null || _d === void 0 ? void 0 : _d.target, el), + } + : {}, + judgment: type === "judgment" + ? { + text: fromElementLike((_e = question.judgment) === null || _e === void 0 ? void 0 : _e.text, el), + target: fromElementLike((_f = question.judgment) === null || _f === void 0 ? void 0 : _f.target, el), + } + : {}, + completion: type === "completion" + ? { + target: fromElementLike((_g = question.completion) === null || _g === void 0 ? void 0 : _g.target, el), + } + : {}, + }; + this.questions.push(qe); + } + } + }; + QA.default = { + type: question_type_resolver_1.getQuestionType, + answer: function () { return ({ question: "", answers: [] }); }, + question: { + container: "ul", + title: "div span", + single: { + text: "li", + target: "li input", + handler: single_choice_1.singleChoiceHandler, + }, + multiple: { + text: "li", + target: "li input", + handler: multiple_choice_1.multipleChoiceHandler, + }, + judgment: { + text: "li", + target: "li input", + isRightInFirst: true, + right: ["是", "对", "正确", "√", "对的", "是的", "正确的", "true", "yes", "YES", "Yes"], + wrong: [ + "否", + "错", + "错误", + "x", + "错的", + "不正确的", + "不正确", + "不是", + "不是的", + "false", + "no", + "NO", + "No", + ], + handler: judgment_1.judgmentHandler, + }, + completion: { + target: "textarea", + handler: completion_1.completionHandler, + }, + }, + }; + return QA; +}()); +exports.QA = QA; diff --git a/packages/scripts/lib/src/qa/option.resolver.js b/packages/scripts/lib/src/qa/option.resolver.js new file mode 100644 index 00000000..b8991edd --- /dev/null +++ b/packages/scripts/lib/src/qa/option.resolver.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getOptions = void 0; +var question_type_resolver_1 = require("./question.type.resolver"); +/** + * resolve option string of specify element + */ +function getOptions(question, target) { + var options = Array.from(question.querySelectorAll(target)); + var questionType = (0, question_type_resolver_1.getQuestionType)(question); + return options.map(function (option) { + return questionType === "completion" ? "" : option.innerText; + }); +} +exports.getOptions = getOptions; diff --git a/packages/scripts/lib/src/qa/question.type.resolver.js b/packages/scripts/lib/src/qa/question.type.resolver.js new file mode 100644 index 00000000..749a978c --- /dev/null +++ b/packages/scripts/lib/src/qa/question.type.resolver.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.questionTypeWords = exports.getQuestionType = void 0; +/** + * 判断一个字符串或者元素的题目类型 {@link QuestionType} + * @param target + * @returns QuestionType + */ +function getQuestionType(target) { + var type = undefined; + if (typeof target === "string") { + if (match(target, exports.questionTypeWords.singleChoice)) { + type = "single_choice"; + } + else if (match(target, exports.questionTypeWords.multipleChoice)) { + type = "multiple_choice"; + } + else if (match(target, exports.questionTypeWords.judgment)) { + type = "judgment"; + } + else if (match(target, exports.questionTypeWords.completion)) { + type = "completion"; + } + } + else { + var inputs = Array.from(target.querySelectorAll("input")); + if (inputs.length !== 0) { + if (inputs.length === 2) { + if (inputs.every(function (input) { return input.type === "radio"; })) { + type = "judgment"; + } + } + else if (target.querySelector("textarea")) { + type = "completion"; + } + else { + if (inputs.every(function (input) { return input.type === "radio"; })) { + type = "single_choice"; + } + else if (inputs.every(function (input) { return input.type === "checkbox"; })) { + type = "multiple_choice"; + } + } + } + } + return type; +} +exports.getQuestionType = getQuestionType; +function match(target, arr) { + return arr.some(function (str) { return RegExp(str).test(target); }); +} +exports.questionTypeWords = { + singleChoice: ["单选", "单选题", "单项选择题"], + multipleChoice: ["多选", "多选题", "多项选择题"], + judgment: ["判断", "判断题"], + completion: ["填空", "填空题"], +}; diff --git a/packages/scripts/lib/src/qa/types.js b/packages/scripts/lib/src/qa/types.js new file mode 100644 index 00000000..ce03781e --- /dev/null +++ b/packages/scripts/lib/src/qa/types.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/packages/scripts/lib/test/cx.js b/packages/scripts/lib/test/cx.js new file mode 100644 index 00000000..3e4a9287 --- /dev/null +++ b/packages/scripts/lib/test/cx.js @@ -0,0 +1,99 @@ +"use strict"; +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 (_) 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 __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var qa_1 = require("../src/qa"); +var axios_1 = __importDefault(require("axios")); +var qa = new qa_1.QA({ + type: function (container) { + var typeTitle = container.querySelector(".type_tit"); + var text = typeTitle === null || typeTitle === void 0 ? void 0 : typeTitle.textContent; + if (text) { + return RegExp("单选题").test(text) + ? "single_choice" + : RegExp("多选题").test(text) + ? "multiple_choice" + : RegExp("判断题").test(text) + ? "judgment" + : RegExp("填空题").test(text) + ? "completion" + : undefined; + } + }, + answer: function (title, type) { + return __awaiter(this, void 0, void 0, function () { + var data; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, axios_1.default.get("https://wk.enncy.cn/chati/5ace3900615d11ec887935abef73b31b/0/" + + encodeURIComponent(title + .replace(/\d*\./, "") + .replace(/\(.*?题, .*?分\)/, "") + .replace(/\n/g, "")))]; + case 1: + data = (_a.sent()).data; + return [2 /*return*/, { + question: data.question, + answers: [data.answer], + }]; + } + }); + }); + }, + question: { + container: ".whiteDiv", + title: "h1,h2,h3,h4,h5,h6", + single: { + text: ".answer_p", + target: ".answerBg", + }, + multiple: { + text: ".answer_p", + target: ".answerBg", + }, + judgment: { + text: ".answer_p", + target: ".answerBg", + }, + completion: { + target: "textarea", + }, + }, +}); diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 9abe3a7c..20a30ebf 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -4,8 +4,19 @@ "description": "scripts package of ocs", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc && npm run pack", + "pack": "webpack --config webpack.config.js" }, "author": "enncy", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "@types/string-similarity": "^4.0.0", + "webpack": "^5.68.0", + "webpack-cli": "^4.9.2" + }, + "dependencies": { + "axios": "^0.25.0", + "string-similarity": "^4.0.4" + } } diff --git a/packages/scripts/src/index.ts b/packages/scripts/src/index.ts new file mode 100644 index 00000000..1f5279f2 --- /dev/null +++ b/packages/scripts/src/index.ts @@ -0,0 +1,2 @@ + +export * from "./qa" \ No newline at end of file diff --git a/packages/scripts/src/qa/handler/completion.ts b/packages/scripts/src/qa/handler/completion.ts new file mode 100644 index 00000000..55614b8a --- /dev/null +++ b/packages/scripts/src/qa/handler/completion.ts @@ -0,0 +1,4 @@ + +export function completionHandler(answers: string[], target: Element ) { + +} diff --git a/packages/scripts/src/qa/handler/index.ts b/packages/scripts/src/qa/handler/index.ts new file mode 100644 index 00000000..a531d9be --- /dev/null +++ b/packages/scripts/src/qa/handler/index.ts @@ -0,0 +1,39 @@ +import { fromElementLike } from ".."; + +import { singleChoiceHandler } from "./single.choice"; +import similarity from "string-similarity"; +import { multipleChoiceHandler } from "./multiple.choice"; +import { judgmentHandler } from "./judgment"; + +// export async function handleQuestion(container: string | Element, answer: AnswerProvider, question: QuestionProvider) { +// const element = fromElementLike(container); + // const type = question.type(element); + // const title = question.title(element).textContent; + // if (title) { + // const options = question.options(element); + // const answers = await answer({ title, options: options.map((option) => option.textContent || ""), type }); + // if (type === "single_choice") { + // return singleChoiceHandler(answers, options); + // } + // if (type === 'multiple_choice') { + // return multipleChoiceHandler(answers, options); + // } + // if (type === 'judgment') { + // return judgmentHandler(answers, options); + // } + // if (type === 'completion') { + // return singleChoiceHandler(answers, options); + // } + // } +// } + + +export function getOptionSimilarly(answers: string[], options: Element[]) { + return options.map((option) => { + if (option.textContent) { + return similarity.findBestMatch(option.textContent, answers).bestMatch.rating; + } else { + return 0; + } + }); +} \ No newline at end of file diff --git a/packages/scripts/src/qa/handler/judgment.ts b/packages/scripts/src/qa/handler/judgment.ts new file mode 100644 index 00000000..ec25bd05 --- /dev/null +++ b/packages/scripts/src/qa/handler/judgment.ts @@ -0,0 +1,22 @@ +import { getOptionSimilarly } from "."; +const similarity = require("string-similarity"); + +export const right = "是|对|正确|√|对的|是的|正确的|true|yes|YES|Yes"; +export const wrong = "否|错|错误|x|错的|不正确的|不正确|不是|不是的|false|no|NO|No"; +export const startWithRight = true; + +export function judgmentHandler(answers: string[], options: Element[]) { + const { target } = similarity.findBestMatch(answers[0], right.split("|").concat(wrong.split("|"))).bestMatch; + let index = 1; + // 开始选择 + if (startWithRight) { + if (RegExp(right).test(target)) { + index = 0; + } + } else { + if (RegExp(wrong).test(target)) { + index = 0; + } + } + (options[index] as HTMLElement).click(); +} diff --git a/packages/scripts/src/qa/handler/multiple.choice.ts b/packages/scripts/src/qa/handler/multiple.choice.ts new file mode 100644 index 00000000..3122aa55 --- /dev/null +++ b/packages/scripts/src/qa/handler/multiple.choice.ts @@ -0,0 +1,10 @@ +import { getOptionSimilarly } from "."; + +export function multipleChoiceHandler(answers: string[], options: Element[]) { + const ratings = getOptionSimilarly(answers, options); + for (let i = 0; i < ratings.length; i++) { + if (ratings[i] > 0.6) { + (options[i] as HTMLElement).click(); + } + } +} diff --git a/packages/scripts/src/qa/handler/single.choice.ts b/packages/scripts/src/qa/handler/single.choice.ts new file mode 100644 index 00000000..662a4c3d --- /dev/null +++ b/packages/scripts/src/qa/handler/single.choice.ts @@ -0,0 +1,8 @@ +import { getOptionSimilarly } from "."; + +export function singleChoiceHandler(answers: string[], options: Element[]) { + const ratings = getOptionSimilarly(answers, options); + const max = ratings.sort()[ratings.length - 1]; + + (options[ratings.findIndex((r) => r === max)] as HTMLElement).click(); +} diff --git a/packages/scripts/src/qa/index.ts b/packages/scripts/src/qa/index.ts new file mode 100644 index 00000000..827319b0 --- /dev/null +++ b/packages/scripts/src/qa/index.ts @@ -0,0 +1,152 @@ +import { completionHandler } from "./handler/completion"; +import { judgmentHandler } from "./handler/judgment"; +import { multipleChoiceHandler } from "./handler/multiple.choice"; +import { singleChoiceHandler } from "./handler/single.choice"; +import { getQuestionType } from "./question.type.resolver"; +import { ElementLike, QAOptions, QueryFunction, Question, QuestionTemplate } from "./types"; + +export { getOptions } from "./option.resolver"; +export { getQuestionType, questionTypeWords } from "./question.type.resolver"; +export { QuestionType } from "./types"; + +export function fromElementArrayLike( + target?: string | QueryFunction, + container: HTMLElement = document.body +): HTMLElement[] | undefined { + if (typeof target === "string") { + let el = container.querySelectorAll(target); + if (el === null) { + return undefined; + } else { + return Array.from(el) as HTMLElement[]; + } + } else if (typeof target === "function") { + return Reflect.apply(target, document, [container]); + } else { + return target; + } +} + +export function fromElementLike( + target?: ElementLike, + container: HTMLElement = document.body +): HTMLElement | undefined { + if (typeof target === "string") { + let el = container.querySelector(target); + if (el === null) { + return undefined; + } else { + return el as HTMLElement; + } + } else if (typeof target === "function") { + return Reflect.apply(target, document, [container]); + } else { + return target as HTMLElement; + } +} + +export class QA { + options: QAOptions; + questions: Question[] = []; + + public static default: QAOptions = { + type: getQuestionType, + answer: () => ({ question: "", answers: [] }), + question: { + container: "ul", + title: "div span", + single: { + text: "li", + target: "li input", + handler: singleChoiceHandler, + }, + multiple: { + text: "li", + target: "li input", + handler: multipleChoiceHandler, + }, + judgment: { + text: "li", + target: "li input", + isRightInFirst: true, + right: ["是", "对", "正确", "√", "对的", "是的", "正确的", "true", "yes", "YES", "Yes"], + wrong: [ + "否", + "错", + "错误", + "x", + "错的", + "不正确的", + "不正确", + "不是", + "不是的", + "false", + "no", + "NO", + "No", + ], + handler: judgmentHandler, + }, + completion: { + target: "textarea", + handler: completionHandler, + }, + }, + }; + + constructor(options: Partial) { + this.options = Object.assign(QA.default, options); + this.handleQuestionTemplate(); + } + + /** + * resolve {@link QuestionTemplate} to {@link Question} + */ + private handleQuestionTemplate(question: Partial> = QA.default.question) { + for (const container of fromElementArrayLike(question.container) || []) { + const el = container as HTMLElement; + const title = fromElementLike(question.title, el); + const type = getQuestionType(title?.textContent || "") || getQuestionType(el); + console.log({ + container, + title, + type, + }); + if (type) { + const qe: Question = { + container: container, + type, + title: fromElementLike(question.title, el), + single: + type === "single_choice" + ? { + text: fromElementLike(question.single?.text, el), + target: fromElementLike(question.single?.target, el), + } + : {}, + multiple: + type === "multiple_choice" + ? { + text: fromElementLike(question.multiple?.text, el), + target: fromElementLike(question.multiple?.target, el), + } + : {}, + judgment: + type === "judgment" + ? { + text: fromElementLike(question.judgment?.text, el), + target: fromElementLike(question.judgment?.target, el), + } + : {}, + completion: + type === "completion" + ? { + target: fromElementLike(question.completion?.target, el), + } + : {}, + }; + this.questions.push(qe); + } + } + } +} diff --git a/packages/scripts/src/qa/option.resolver.ts b/packages/scripts/src/qa/option.resolver.ts new file mode 100644 index 00000000..700c5028 --- /dev/null +++ b/packages/scripts/src/qa/option.resolver.ts @@ -0,0 +1,12 @@ +import { getQuestionType } from "./question.type.resolver"; + +/** + * resolve option string of specify element + */ +export function getOptions(question: HTMLElement, target: string) { + const options = Array.from(question.querySelectorAll(target)); + const questionType = getQuestionType(question); + return options.map((option) => { + return questionType === "completion" ? "" : (option as HTMLDivElement).innerText; + }); +} diff --git a/packages/scripts/src/qa/question.type.resolver.ts b/packages/scripts/src/qa/question.type.resolver.ts new file mode 100644 index 00000000..03f94599 --- /dev/null +++ b/packages/scripts/src/qa/question.type.resolver.ts @@ -0,0 +1,51 @@ +import { QuestionType } from "./types"; + +/** + * 判断一个字符串或者元素的题目类型 {@link QuestionType} + * @param target + * @returns QuestionType + */ +export function getQuestionType(target: string | HTMLElement): keyof QuestionType | undefined { + let type: keyof QuestionType | undefined = undefined; + if (typeof target === "string") { + if (match(target, questionTypeWords.singleChoice)) { + type = "single_choice"; + } else if (match(target, questionTypeWords.multipleChoice)) { + type = "multiple_choice"; + } else if (match(target, questionTypeWords.judgment)) { + type = "judgment"; + } else if (match(target, questionTypeWords.completion)) { + type = "completion"; + } + } else { + const inputs = Array.from(target.querySelectorAll("input")); + if (inputs.length !== 0) { + if (inputs.length === 2) { + if (inputs.every((input) => input.type === "radio")) { + type = "judgment"; + } + } else if (target.querySelector("textarea")) { + type = "completion"; + } else { + if (inputs.every((input) => input.type === "radio")) { + type = "single_choice"; + } else if (inputs.every((input) => input.type === "checkbox")) { + type = "multiple_choice"; + } + } + } + } + + return type; +} + +function match(target: string, arr: string[]) { + return arr.some((str) => RegExp(str).test(target)); +} + +export const questionTypeWords = { + singleChoice: ["单选", "单选题", "单项选择题"], + multipleChoice: ["多选", "多选题", "多项选择题"], + judgment: ["判断", "判断题"], + completion: ["填空", "填空题"], +}; diff --git a/packages/scripts/src/qa/types.ts b/packages/scripts/src/qa/types.ts new file mode 100644 index 00000000..0aac45ed --- /dev/null +++ b/packages/scripts/src/qa/types.ts @@ -0,0 +1,141 @@ +/** + * 题目类型 + */ +export interface QuestionType { + single_choice: string; + multiple_choice: string; + judgment: string; + completion: string; +} + +/** + * 题型提供器 + */ +export type QuestionTypeProvider = (container: HTMLElement) => keyof QuestionType | undefined; + +/** + * 题目处理器 + */ +export type QuestionHandler = (answers: string[], ...args: any) => void | Promise; + + + +/** + * 答案 + */ + export interface Answer { + question: string; + answers: string[]; +} + +/** + * 题目 + */ +export interface Question { + /** 类型提供器 */ + type: keyof QuestionType; + /** 题目所在的父元素 */ + container: T; + /** 题目标题元素 */ + title?: T; + /** 单选题选项 */ + single: Partial>; + /** 多选题选项 */ + multiple: Partial>; + /** 判断题选项 */ + judgment: Partial>; + /** 填空题选项 */ + completion: Partial>; +} + +/** + * 问题模板 + */ +export interface QuestionTemplate { + /** 题目所在的父元素 */ + container: string | QueryFunction; + /** 题目标题元素 */ + title?: T; + /** 单选题选项 */ + single: Partial>; + /** 多选题选项 */ + multiple: Partial>; + /** 判断题选项 */ + judgment: Partial>; + /** 填空题选项 */ + completion: Partial>; +} + +export type QueryFunction = (el: HTMLElement) => T; + +/** + * 类元素的表达 + */ +export type ElementLike = string | T | QueryFunction; + +/** + * 单选题选项 + */ +export interface SingleQuestionOptions { + /** 选项内容 */ + text: T; + /** 需点击的元素 */ + target: T; + handler: QuestionHandler; +} +export interface MultipleQuestionOptions { + /** 选项内容 */ + text: T; + /** 需点击的元素 */ + target: T; + handler: QuestionHandler; +} + +export interface JudgmentQuestionOptions { + /** + * 正确的同义词列表 + * + * default : {@link QA.default.judgment.right} + */ + right: string[]; + /** + * 错误的同义词列表 + * + * default : {@link QA.default.judgment.wrong} + */ + + wrong: string[]; + /** + * 正确选项是否在第一个元素 + * @example + * ```html + * + *
+ *
+ * + * + *
+ *
+ * ``` + */ + isRightInFirst: boolean; + /** 选项内容 */ + text: T; + /** 需点击的元素 */ + target: T; + handler: QuestionHandler; +} +export interface CompletionQuestionOptions { + /** 需填空的元素 */ + target: T; + handler: QuestionHandler; +} + +export interface QAOptions { + type: QuestionTypeProvider; + answer: (title: string, type: keyof QuestionType) => Promise | Answer; + question: Partial>; + onQuestion?: void; + onAnswer?: void; + onError?: void; +} diff --git a/packages/scripts/test/cx.ts b/packages/scripts/test/cx.ts new file mode 100644 index 00000000..d25e89e0 --- /dev/null +++ b/packages/scripts/test/cx.ts @@ -0,0 +1,55 @@ +import { QuestionType } from "./../src/qa/types"; +import { QA } from "../src/qa"; +import axios from "axios"; +const qa = new QA({ + type(container) { + const typeTitle = container.querySelector(".type_tit"); + const text = typeTitle?.textContent; + if (text) { + return RegExp("单选题").test(text) + ? "single_choice" + : RegExp("多选题").test(text) + ? "multiple_choice" + : RegExp("判断题").test(text) + ? "judgment" + : RegExp("填空题").test(text) + ? "completion" + : undefined; + } + }, + async answer(title, type) { + const { data } = await axios.get( + "https://wk.enncy.cn/chati/5ace3900615d11ec887935abef73b31b/0/" + + encodeURIComponent( + title + .replace(/\d*\./, "") + .replace(/\(.*?题, .*?分\)/, "") + .replace(/\n/g, "") + ) + ); + + return { + question: data.question, + answers: [data.answer], + }; + }, + question: { + container: ".whiteDiv", + title: "h1,h2,h3,h4,h5,h6", + single: { + text: ".answer_p", + target: ".answerBg", + }, + multiple: { + text: ".answer_p", + target: ".answerBg", + }, + judgment: { + text: ".answer_p", + target: ".answerBg", + }, + completion: { + target: "textarea", + }, + }, +}); diff --git a/packages/scripts/tsconfig.json b/packages/scripts/tsconfig.json new file mode 100644 index 00000000..fde7429e --- /dev/null +++ b/packages/scripts/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "outDir": "./lib", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/packages/scripts/webpack.config.js b/packages/scripts/webpack.config.js new file mode 100644 index 00000000..4206b634 --- /dev/null +++ b/packages/scripts/webpack.config.js @@ -0,0 +1,17 @@ +const path = require("path"); + +/** + * @type {import("webpack").Configuration} + */ +module.exports = { + entry: "./lib/src/index.js", + mode: "production", + output: { + filename: "index.js", + path: path.resolve(__dirname, "./"), + library: "OCS", + }, + optimization: { + minimize: true, + }, +};