From 23df64d1d77a9556a1d105d72bc9810fb4c06c18 Mon Sep 17 00:00:00 2001 From: enncy <877526278@qq.com> Date: Sun, 19 Mar 2023 19:05:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20=E4=BC=98=E5=8C=96=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E7=AD=94=E9=A2=98=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=9C=89=E7=AD=94=E6=A1=88=E4=B8=8D=E9=80=89=E7=9A=84?= =?UTF-8?q?BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复多选题只进行全匹配的BUG,导致有时候有答案但是不会选择 - 删除答题器的自定义重试,以及超时时间,而是写死,防止用户不清楚随意修改。 - 修复搜题结果不显示图片的BUG --- .../answer-wrapper/answer.wrapper.handler.ts | 22 +-- .../answer-wrapper/answer.wrapper.parser.ts | 2 +- .../core/src/core/answer-wrapper/interface.ts | 8 +- packages/core/src/core/utils/request.ts | 21 ++- packages/core/src/core/utils/string.ts | 14 +- packages/core/src/core/worker/interface.ts | 22 +-- .../core/src/core/worker/question.resolver.ts | 177 ++++++++++-------- packages/core/src/core/worker/utils.ts | 1 + packages/core/src/core/worker/worker.ts | 153 +++++++-------- packages/core/src/elements/index.ts | 6 +- packages/core/src/elements/interface.ts | 4 +- .../{search.results.ts => search.infos.ts} | 23 ++- packages/scripts/src/utils/work.ts | 38 +++- 13 files changed, 275 insertions(+), 216 deletions(-) rename packages/core/src/elements/{search.results.ts => search.infos.ts} (68%) diff --git a/packages/core/src/core/answer-wrapper/answer.wrapper.handler.ts b/packages/core/src/core/answer-wrapper/answer.wrapper.handler.ts index e326ebbd..e3c52bf6 100644 --- a/packages/core/src/core/answer-wrapper/answer.wrapper.handler.ts +++ b/packages/core/src/core/answer-wrapper/answer.wrapper.handler.ts @@ -1,4 +1,4 @@ -import { AnswererWrapper, SearchResult, Answer } from './interface'; +import { AnswererWrapper, SearchInformation, Result } from './interface'; import { request } from '../utils/request'; /** @@ -38,8 +38,8 @@ export async function defaultAnswerWrapperHandler( answererWrappers: AnswererWrapper[], // 上下文解析环境 env: any -): Promise { - const searchResults: SearchResult[] = []; +): Promise { + const searchInfos: SearchInformation[] = []; const temp: AnswererWrapper[] = JSON.parse(JSON.stringify(answererWrappers)); // 多线程请求 await Promise.all( @@ -57,7 +57,7 @@ export async function defaultAnswerWrapperHandler( } = wrapper; try { // 答案列表 - let answers: Answer[] = []; + let results: Result[] = []; // 构造请求数据 const data: Record = Object.create({}); /** 构造一个请求数据 */ @@ -86,34 +86,34 @@ export async function defaultAnswerWrapperHandler( if (info && Array.isArray(info)) { /** 如果返回一个二维数组 */ if (info.every((item: any) => Array.isArray(item))) { - answers = answers.concat( + results = results.concat( info.map((item: any) => ({ question: item[0], answer: item[1] })) ); } else { - answers.push({ + results.push({ question: info[0], answer: info[1] }); } } - searchResults.push({ + searchInfos.push({ url: wrapper.url, name, homepage, - answers, + results, response: responseData, data: requestData }); } catch (error) { - searchResults.push({ + searchInfos.push({ url: wrapper.url, name, homepage, - answers: [], + results: [], response: undefined, data: undefined, error: error as any @@ -136,5 +136,5 @@ export async function defaultAnswerWrapperHandler( return str; } - return searchResults; + return searchInfos; } diff --git a/packages/core/src/core/answer-wrapper/answer.wrapper.parser.ts b/packages/core/src/core/answer-wrapper/answer.wrapper.parser.ts index e050170c..a9dc3b38 100644 --- a/packages/core/src/core/answer-wrapper/answer.wrapper.parser.ts +++ b/packages/core/src/core/answer-wrapper/answer.wrapper.parser.ts @@ -62,7 +62,7 @@ export class AnswerWrapperParser { /** 从 url 中解析 */ static async fromURL(url: string) { const text = await request(url, { - contentType: 'text', + responseType: 'text', method: 'get', type: 'fetch' }); diff --git a/packages/core/src/core/answer-wrapper/interface.ts b/packages/core/src/core/answer-wrapper/interface.ts index 5678296f..5170b474 100644 --- a/packages/core/src/core/answer-wrapper/interface.ts +++ b/packages/core/src/core/answer-wrapper/interface.ts @@ -1,17 +1,17 @@ /** 题目答案 */ -export interface Answer { +export interface Result { question: string; answer: string; } -/** 题库查询结果 */ -export interface SearchResult { +/** 题库查询信息 */ +export interface SearchInformation { url: string; name: string; /** 主页 */ homepage?: string; /** 题目答案 */ - answers: Answer[]; + results: Result[]; /** 请求响应内容 */ response: any; /** 请求发起内容 */ diff --git a/packages/core/src/core/utils/request.ts b/packages/core/src/core/utils/request.ts index d0225492..f41d0815 100644 --- a/packages/core/src/core/utils/request.ts +++ b/packages/core/src/core/utils/request.ts @@ -10,7 +10,7 @@ export function request( opts: { type: 'fetch' | 'GM_xmlhttpRequest'; method?: 'get' | 'post'; - contentType?: T; + responseType?: T; headers?: Record; data?: Record; } @@ -18,7 +18,7 @@ export function request( return new Promise((resolve, reject) => { try { /** 默认参数 */ - const { contentType = 'json', method = 'get', type = 'fetch', data = {}, headers = {} } = opts || {}; + const { responseType = 'json', method = 'get', type = 'fetch', data = {}, headers = {} } = opts || {}; /** 环境变量 */ const env = $.isInBrowser() ? 'browser' : 'node'; @@ -29,12 +29,12 @@ export function request( GM_xmlhttpRequest({ url, method: method === 'get' ? 'GET' : 'POST', - data: new URLSearchParams(data).toString(), - headers: headers, - responseType: 'json', + data: Object.keys(data).length ? new URLSearchParams(data).toString() : undefined, + headers: Object.keys(headers).length ? headers : undefined, + responseType: responseType === 'json' ? 'json' : undefined, onload: (response) => { if (response.status === 200) { - if (contentType === 'json') { + if (responseType === 'json') { try { resolve(JSON.parse(response.responseText)); } catch (error) { @@ -47,7 +47,10 @@ export function request( reject(response.responseText); } }, - onerror: reject + onerror: (err) => { + console.error('GM_xmlhttpRequest error', err); + reject(err); + } }); } else { reject(new Error('GM_xmlhttpRequest is not defined')); @@ -55,9 +58,9 @@ export function request( } else { const fet: (...args: any[]) => Promise = env === 'node' ? require('node-fetch').default : fetch; - fet(url, { contentType, body: method === 'post' ? JSON.stringify(data) : undefined, method, headers }) + fet(url, { body: method === 'post' ? JSON.stringify(data) : undefined, method, headers }) .then((response) => { - if (contentType === 'json') { + if (responseType === 'json') { response.json().then(resolve).catch(reject); } else { // @ts-ignore diff --git a/packages/core/src/core/utils/string.ts b/packages/core/src/core/utils/string.ts index 912e28fa..9a4f9b0e 100644 --- a/packages/core/src/core/utils/string.ts +++ b/packages/core/src/core/utils/string.ts @@ -25,21 +25,21 @@ export function clearString(str: string, ...exclude: string[]) { * * ```js * - * answerSimilar( ['3'], ['1+2','3','4','错误的例子'] ) // [0, 1, 0, 0] + * answerSimilar( ['3'], ['1+2','3','4','错误的选项'] ) // [0, 1, 0, 0] * - * answerSimilar( ['hello world','console.log("hello world")'], ['console.log("hello world")','hello world','1','错误的例子'] ) // [1, 1, 0, 0] + * answerSimilar( ['hello world','console.log("hello world")'], ['console.log("hello world")','hello world','1','错误的选项'] ) // [1, 1, 0, 0] * * ``` * */ export function answerSimilar(answers: string[], options: string[]): Rating[] { - answers = answers.map(removeRedundant); - options = options.map(removeRedundant); + const _answers = answers.map(removeRedundant); + const _options = options.map(removeRedundant); const similar = - answers.length !== 0 - ? options.map((option) => findBestMatch(option, answers).bestMatch) - : options.map((opt) => ({ rating: 0, target: '' } as Rating)); + _answers.length !== 0 + ? _options.map((option) => findBestMatch(option, _answers).bestMatch) + : _options.map(() => ({ rating: 0, target: '' } as Rating)); return similar; } diff --git a/packages/core/src/core/worker/interface.ts b/packages/core/src/core/worker/interface.ts index 06515526..1a5d2982 100644 --- a/packages/core/src/core/worker/interface.ts +++ b/packages/core/src/core/worker/interface.ts @@ -1,4 +1,4 @@ -import { SearchResult } from '../answer-wrapper/interface'; +import { SearchInformation } from '../answer-wrapper/interface'; export type ElementResolver = (root: HTMLElement | Document) => R; export type RawElements = Record< @@ -22,7 +22,7 @@ export type SearchedElements = Record & { export interface WorkContext { root: HTMLElement; elements: SearchedElements; - searchResults: SearchResult[]; + searchInfos: SearchInformation[]; } /** 题目类型 */ @@ -62,12 +62,12 @@ export interface SimplifyWorkResult { requesting: boolean; /** 正在等待 答题 线程处理 */ resolving: boolean; - /** 搜索结果 */ - searchResults: { + /** 查题信息 */ + searchInfos: { /** 题目名 */ - name: SearchResult['name']; + name: SearchInformation['name']; /** 题库链接 */ - homepage?: SearchResult['homepage']; + homepage?: SearchInformation['homepage']; /** 题库搜索错误信息 */ error?: string; /** 搜索结果 [题目,答案] */ @@ -77,8 +77,8 @@ export interface SimplifyWorkResult { /** 答案题目处理器 */ export type QuestionResolver = ( - /** 答案 */ - searchResults: SearchResult[], + /** 查题信息 */ + searchInfos: SearchInformation[], /** 选项 */ options: HTMLElement[], handler: ( @@ -177,7 +177,7 @@ export type AnswererType = ( elements: SearchedElements, type: string | undefined, ctx: WorkContext> -) => SearchResult[] | Promise; +) => SearchInformation[] | Promise; /** * 答题器参数 @@ -195,10 +195,6 @@ export type WorkOptions = { requestPeriod?: number; /** 答题间隔(秒), 如果过快可能导致保存答案失败啊,或者被检测到 */ resolvePeriod?: number; - /** 回答器请求超时时间(秒) */ - timeout?: number; - /** 回答器请求重试次数 */ - retry?: number; /** 多线程数量(个) */ thread?: number; /** 当元素被搜索到 */ diff --git a/packages/core/src/core/worker/question.resolver.ts b/packages/core/src/core/worker/question.resolver.ts index 4fc04315..03e5ab0f 100644 --- a/packages/core/src/core/worker/question.resolver.ts +++ b/packages/core/src/core/worker/question.resolver.ts @@ -14,23 +14,10 @@ export function defaultQuestionResolve( * * 在多个题库给出的答案中,找出最相似的答案 */ - async single(results, options, handler) { - // 是否为纯ABCD答案 - for (const result of results) { - for (const answer of result.answers) { - const ans = StringUtils.nowrap(answer.answer).trim(); - if (ans.length === 1 && isPlainAnswer(ans)) { - const index = ans.charCodeAt(0) - 65; - await handler('single', options[index].innerText, options[index], ctx); - await $.sleep(500); - return { finish: true, option: options[index] }; - } - } - } - + async single(infos, options, handler) { /** 配对选项的相似度 */ const ratings = answerSimilar( - results.map((res) => res.answers.map((ans) => ans.answer)).flat(), + infos.map((res) => res.results.map((res) => res.answer)).flat(), options.map((el) => el.innerText) ); /** 找出最相似的选项 */ @@ -52,6 +39,20 @@ export function defaultQuestionResolve( ratings: ratings.map((r) => r.rating) }; } + + // 是否为纯ABCD答案 + for (const info of infos) { + for (const res of info.results) { + const ans = StringUtils.nowrap(res.answer).trim(); + if (ans.length === 1 && isPlainAnswer(ans)) { + const index = ans.charCodeAt(0) - 65; + await handler('single', options[index].innerText, options[index], ctx); + await $.sleep(500); + return { finish: true, option: options[index] }; + } + } + } + return { finish: false }; }, /** @@ -59,88 +60,102 @@ export function defaultQuestionResolve( * * 匹配每个题库的答案,找出匹配数量最多的题库,并且选择 */ - async multiple(results, options, handler) { + async multiple(infos, options, handler) { /** 最终的回答列表 */ const targetAnswers: string[][] = []; /** 最终的选项 */ const targetOptions: HTMLElement[][] = []; - // 各个题库的序号 - let count = 0; - for (const answers of results.map((res) => res.answers.map((ans) => ans.answer))) { - targetAnswers[count] = []; - targetOptions[count] = []; - - // 判断选项是否完全存在于答案里面 - options.forEach((el, i) => { - if (answers.some((answer) => answer.includes(removeRedundant(el.innerText)))) { - targetAnswers[count][i] = el.innerText; - targetOptions[count][i] = el; - } - }); - - // 判断是否为纯ABCD答案 - for (const answer of answers) { - const ans = StringUtils.nowrap(answer).trim(); - if (isPlainAnswer(ans)) { - for (let i = 0; i < ans.length; i++) { - const index = ans.charCodeAt(i) - 65; - targetAnswers[count][i] = options[index].innerText; - targetOptions[count][i] = options[index]; - } - } - } - - if (targetAnswers[count].length === 0) { - const ratings = answerSimilar( - answers, - options.map((el) => el.innerText) - ).sort((a, b) => b.rating - a.rating); - - // 匹配相似率 - if (ratings.some((rating) => rating.rating > 0.6)) { - options.forEach((el, i) => { - if (ratings[i].rating > 0.6) { - targetAnswers[count][i] = el.innerText; - targetOptions[count][i] = el; - } - }); + const list: { + /** 匹配的选项 */ + options: HTMLElement[]; + /** 匹配的答案 */ + answers: string[]; + ratings: number[]; + /** 总匹配度 */ + similarSum: number; + /** 匹配数量 */ + similarCount: number; + }[] = []; + + const results = infos.map((info) => info.results).flat(); + + /** + * 遍历题库结果 + * 选出结果中包含答案最多的一个 + */ + for (let i = 0; i < results.length; i++) { + const result = results[i]; + list[i] = { options: [], answers: [], ratings: [], similarSum: 0, similarCount: 0 }; + + // 每个答案可能存在多个选项需要分割 + const answers = splitAnswer(result.answer); + console.log('answers', { answer: result.answer, answers }); + + const ratings = answerSimilar( + answers, + options.map((o) => removeRedundant(o.innerText)) + ); + for (let j = 0; j < ratings.length; j++) { + const rating = ratings[j]; + if (rating.rating > 0.6) { + list[i].options.push(options[j]); + list[i].answers.push(ratings[j].target); + list[i].ratings.push(ratings[j].rating); + list[i].similarSum += rating.rating; + list[i].similarCount += 1; } } - - count++; } - /** 查找每个题库里面是否存在答案 , 并且找到答案数量较多的一个 */ - let max = 0; - let index = -1; - for (let i = 0; i < targetOptions.length; i++) { - const len = targetAnswers[i].filter((ans) => ans !== undefined).length; - if (len > max) { - max = len; - index = i; - } - } + const match = list + .filter((i) => i.similarCount !== 0) + .sort((a, b) => { + const bsc = b.similarCount * 100; + const asc = a.similarCount * 100; + const bss = b.similarSum; + const ass = a.similarSum; - /** 如果答案不存在 */ - if (index === -1) { - return { finish: false }; - } else { - targetAnswers[index] = targetAnswers[index].filter((ans) => ans !== undefined); - targetOptions[index] = targetOptions[index].filter((ans) => ans !== undefined); + // similarCount 由于是匹配的数量,其结果决定排序, + // similarSum 是匹配精度,其结果决定同样数量的情况下,哪一个的精度更高 - for (let i = 0; i < targetOptions[index].length; i++) { - await handler('multiple', targetAnswers[index][i], targetOptions[index][i], ctx); + // 高到低排序 + return bsc + bss - asc + ass; + }); + + if (match[0]) { + for (let i = 0; i < match[0].options.length; i++) { + await handler('multiple', match[0].answers[i], match[0].options[i], ctx); // 暂停一会防止点击过快 await $.sleep(500); } - return { finish: true, targetOptions, targetAnswers }; + return { finish: true, match, targetOptions, targetAnswers }; + } else { + const plainOptions = []; + // 纯ABCD答案 + for (const result of results) { + const ans = StringUtils.nowrap(result.answer).trim(); + if (isPlainAnswer(ans)) { + for (const char of ans) { + const index = char.charCodeAt(0) - 65; + await handler('single', options[index].innerText, options[index], ctx); + await $.sleep(500); + plainOptions.push(options[index]); + } + } + } + + if (plainOptions.length) { + return { finish: true, plainOptions }; + } else { + return { finish: false }; + } } }, /** 判断题处理器 */ - async judgement(results, options, handler) { - for (const answers of results.map((res) => res.answers.map((ans) => ans.answer))) { + async judgement(infos, options, handler) { + for (const answers of infos.map((info) => info.results.map((res) => res.answer))) { const correctWords = ['是', '对', '正确', '√', '对的', '是的', '正确的', 'true', 'yes', '1']; const incorrectWords = [ '非', @@ -197,8 +212,8 @@ export function defaultQuestionResolve( return { finish: false }; }, /** 填空题处理器 */ - async completion(results, options, handler) { - for (const answers of results.map((res) => res.answers.map((ans) => ans.answer))) { + async completion(infos, options, handler) { + for (const answers of infos.map((info) => info.results.map((res) => res.answer))) { // 排除空答案 let ans = answers.filter((ans) => ans); if (ans.length === 1) { diff --git a/packages/core/src/core/worker/utils.ts b/packages/core/src/core/worker/utils.ts index e316891e..26a2aca8 100644 --- a/packages/core/src/core/worker/utils.ts +++ b/packages/core/src/core/worker/utils.ts @@ -24,6 +24,7 @@ export function defaultWorkTypeResolver(ctx: WorkContext): QuestionTypes | /** 判断答案是否为A-Z的文本, 并且字符序号依次递增, 并且 每个字符是否都只出现了一次 */ export function isPlainAnswer(answer: string) { + answer = answer.trim(); if (answer.length > 8 || !/[A-Z]/.test(answer)) { return false; } diff --git a/packages/core/src/core/worker/worker.ts b/packages/core/src/core/worker/worker.ts index 4882b66d..15dc4cb3 100644 --- a/packages/core/src/core/worker/worker.ts +++ b/packages/core/src/core/worker/worker.ts @@ -81,7 +81,7 @@ export class OCSWorker extends CommonEventE for (const q of questionRoot) { const ctx: WorkContext = { - searchResults: [], + searchInfos: [], root: q, elements: domSearchAll(this.opts.elements, q) }; @@ -121,72 +121,93 @@ export class OCSWorker extends CommonEventE let type; let error: string | undefined; - /** 获取题目类型 */ - if (typeof this.opts.work === 'object') { - type = - this.opts.work.type === undefined - ? // 使用默认解析器 - defaultWorkTypeResolver(ctx) - : // 自定义解析器 - typeof this.opts.work.type === 'string' - ? this.opts.work.type - : this.opts.work.type(ctx); - } + try { + /** 获取题目类型 */ + if (typeof this.opts.work === 'object') { + type = + this.opts.work.type === undefined + ? // 使用默认解析器 + defaultWorkTypeResolver(ctx) + : // 自定义解析器 + typeof this.opts.work.type === 'string' + ? this.opts.work.type + : this.opts.work.type(ctx); + } - /** 检查是否暂停中 */ - if (this.isStop) { - await new Promise((resolve, reject) => { - const interval = setInterval(() => { - if (this.isStop === false) { - clearInterval(interval); - resolve(); - } - }, 200); - }); - } + /** 检查是否暂停中 */ + if (this.isStop) { + await new Promise((resolve, reject) => { + const interval = setInterval(() => { + if (this.isStop === false) { + clearInterval(interval); + resolve(); + } + }, 200); + }); + } - /** 查找题目 */ - const searchResults = await this.doAnswer(ctx.elements, type, ctx); + /** 查找题目 */ + const searchInfos = await this.doAnswer(ctx.elements, type, ctx); - let resultPromise: { (): Promise } | undefined; + let resultPromise: { (): Promise } | undefined; - if (!searchResults) { - error = '答案获取失败, 请重新运行, 或者忽略此题。'; - } else { - // 答案为 undefined 的情况, 需要赋值给一个空字符串,因为可能传回的题目中带有其他提示信息,或者题目里包含答案。 - searchResults.forEach((res) => { - res.answers = res.answers.map((ans) => { - ans.answer = ans.answer ? ans.answer : ''; - return ans; + if (!searchInfos) { + error = '答案获取失败, 请重新运行, 或者忽略此题。'; + } else { + // 答案为 undefined 的情况, 需要赋值给一个空字符串,因为可能传回的题目中带有其他提示信息,或者题目里包含答案。 + searchInfos.forEach((info) => { + info.results = info.results.map((ans) => { + ans.answer = ans.answer ? ans.answer : ''; + return ans; + }); }); - }); - ctx.searchResults = searchResults; + ctx.searchInfos = searchInfos; - if (searchResults.length === 0) { - error = '搜索不到答案, 请重新运行, 或者忽略此题。'; - } + if (searchInfos.length === 0) { + error = '搜索不到答案, 请重新运行, 或者忽略此题。'; + } - /** 开始处理 */ - if (typeof this.opts.work === 'object') { - if (ctx.elements.options) { - /** 使用默认处理器 */ - - if (type) { - const resolver = defaultQuestionResolve(ctx)[type]; - const handler = this.opts.work.handler; - resultPromise = async () => - await resolver(searchResults, ctx.elements.options as HTMLElement[], handler); + /** 开始处理 */ + if (typeof this.opts.work === 'object') { + if (ctx.elements.options) { + /** 使用默认处理器 */ + + if (type) { + const resolver = defaultQuestionResolve(ctx)[type]; + const handler = this.opts.work.handler; + resultPromise = async () => + await resolver(searchInfos, ctx.elements.options as HTMLElement[], handler); + } else { + error = '题目类型解析失败, 请自行提供解析器, 或者忽略此题。'; + } } else { - error = '题目类型解析失败, 请自行提供解析器, 或者忽略此题。'; + error = 'elements.options 为空 ! 使用默认处理器, 必须提供题目选项的选择器。'; } } else { - error = 'elements.options 为空 ! 使用默认处理器, 必须提供题目选项的选择器。'; + /** 使用自定义处理器 */ + const work = this.opts.work; + resultPromise = async () => await work(ctx); } + } + + if (resultPromise) { + resolvers.push({ + func: resultPromise, + index: i + }); } else { - /** 使用自定义处理器 */ - const work = this.opts.work; - resultPromise = async () => await work(ctx); + resolvers.push({ + func: async () => ({ finish: false }), + index: i + }); + } + } catch (err) { + console.error(err); + if (err instanceof Error) { + error = err.message; + } else { + error = String(err); } } @@ -200,18 +221,6 @@ export class OCSWorker extends CommonEventE results[i] = currentResult; - if (resultPromise) { - resolvers.push({ - func: resultPromise, - index: i - }); - } else { - resolvers.push({ - func: async () => ({ finish: false }), - index: i - }); - } - await this.opts.onResultsUpdate?.(results, currentResult); /** 间隔 */ @@ -278,9 +287,7 @@ export class OCSWorker extends CommonEventE /** 获取答案 */ private async doAnswer(elements: WorkContext['elements'], type: string | undefined, ctx: WorkContext) { - const { timeout: _t, retry: _r } = this.opts; - const timeout = _t ?? 60; - let retry = _r ?? 2; + const timeout = 30; /** 解析选项,可以自定义查题器 */ const answer = async () => { @@ -292,12 +299,10 @@ export class OCSWorker extends CommonEventE }; let answers = await answer(); + await $.sleep(3000); if (!answers) { - /** 重试获取答案 */ - while (retry && !answers) { - answers = await answer(); - retry--; - } + /** 重试一次获取答案 */ + answers = await answer(); } return answers; diff --git a/packages/core/src/elements/index.ts b/packages/core/src/elements/index.ts index 622309c5..839a109b 100644 --- a/packages/core/src/elements/index.ts +++ b/packages/core/src/elements/index.ts @@ -5,7 +5,7 @@ import { HeaderElement } from './header'; import { MessageElement } from './message'; import { ModelElement } from './model'; import { ScriptPanelElement } from './script.panel'; -import { SearchResultsElement } from './search.results'; +import { SearchInfosElement } from './search.infos'; export { ConfigElement } from './config'; export { ContainerElement } from './container'; @@ -13,7 +13,7 @@ export { HeaderElement } from './header'; export { MessageElement } from './message'; export { ModelElement } from './model'; export { ScriptPanelElement } from './script.panel'; -export { SearchResultsElement } from './search.results'; +export { SearchInfosElement } from './search.infos'; export const definedCustomElements = [ ConfigElement, @@ -22,6 +22,6 @@ export const definedCustomElements = [ ModelElement, MessageElement, ScriptPanelElement, - SearchResultsElement, + SearchInfosElement, DropdownElement ]; diff --git a/packages/core/src/elements/interface.ts b/packages/core/src/elements/interface.ts index 7944e976..5b7c2a97 100644 --- a/packages/core/src/elements/interface.ts +++ b/packages/core/src/elements/interface.ts @@ -5,7 +5,7 @@ import { HeaderElement } from './header'; import { MessageElement } from './message'; import { ModelElement } from './model'; import { ScriptPanelElement } from './script.panel'; -import { SearchResultsElement } from './search.results'; +import { SearchInfosElement } from './search.infos'; export class IElement extends HTMLElement {} @@ -16,6 +16,6 @@ export interface CustomElementTagMap { 'message-element': MessageElement; 'script-panel-element': ScriptPanelElement; 'header-element': HeaderElement; - 'search-results-element': SearchResultsElement; + 'search-infos-element': SearchInfosElement; 'dropdown-element': DropdownElement; } diff --git a/packages/core/src/elements/search.results.ts b/packages/core/src/elements/search.infos.ts similarity index 68% rename from packages/core/src/elements/search.results.ts rename to packages/core/src/elements/search.infos.ts index e2099134..a05ce904 100644 --- a/packages/core/src/elements/search.results.ts +++ b/packages/core/src/elements/search.infos.ts @@ -7,14 +7,14 @@ import { IElement } from './interface'; /** 判断是否有网络图片格式的文本,有则替换成 img 标签 */ const transformImgLink = (str: string) => - str.replace(/https?:\/\/.*?\.(png|jpg|jpeg|gif)/g, (match) => ``); + str.replace(/https?:\/\/.*?ananas.*?\.(png|jpg|jpeg|gif)/g, (match) => ``); /** * 搜索结果元素 */ -export class SearchResultsElement extends IElement { +export class SearchInfosElement extends IElement { /** 搜索结果 [题目,答案] */ - results: SimplifyWorkResult['searchResults'] = []; + infos: SimplifyWorkResult['searchInfos'] = []; /** 当前的题目 */ question: string = ''; @@ -22,28 +22,31 @@ export class SearchResultsElement extends IElement { const question = transformImgLink(this.question || '无'); this.append( - el('div', [question, $creator.copy('复制', question)], (div) => { + el('div', [el('span', { innerHTML: question }), $creator.copy('复制', question)], (div) => { div.style.padding = '4px'; }), el('hr') ); this.append( - ...this.results.map((res) => { + ...this.infos.map((info) => { return el('details', { open: true }, [ - el('summary', [el('a', { href: res.homepage, innerText: res.name })]), - ...(res.error + el('summary', [el('a', { href: info.homepage, innerText: info.name })]), + ...(info.error ? /** 显示错误信息 */ - [el('span', { className: 'error' }, ['此题库搜题时发生错误:', res.error || '网络错误或者未知错误'])] + [el('span', { className: 'error' }, ['此题库搜题时发生错误:', info.error || '网络错误或者未知错误'])] : /** 显示结果列表 */ [ - ...res.results.map((ans) => { + ...info.results.map((ans) => { const title = transformImgLink(ans[0] || this.question || '无'); const answer = transformImgLink(ans[1] || '无'); return el('div', { className: 'search-result' }, [ /** 题目 */ - el('div', { className: 'question' }, [el('span', title), $creator.copy('复制', title)]), + el('div', { className: 'question' }, [ + el('span', { innerHTML: title }), + $creator.copy('复制', title) + ]), /** 答案 */ el('div', { className: 'answer' }, [ el('span', '答案:'), diff --git a/packages/scripts/src/utils/work.ts b/packages/scripts/src/utils/work.ts index ed84fa6f..7c98bc89 100644 --- a/packages/scripts/src/utils/work.ts +++ b/packages/scripts/src/utils/work.ts @@ -1,4 +1,4 @@ -import { $creator, $message, OCSWorker, Script, el } from '@ocsjs/core'; +import { $creator, $message, OCSWorker, Script, SimplifyWorkResult, StringUtils, WorkResult, el } from '@ocsjs/core'; import { workConfigs } from './configs'; /** @@ -46,11 +46,47 @@ export function createWorkerControl( export function optimizationTextWithImage(root: HTMLElement) { if (root) { const el = root.cloneNode(true) as HTMLElement; + for (const img of Array.from(el.querySelectorAll('img'))) { img.after(img.src); } + return el.innerText; } else { return ''; } } + +/** 将 {@link WorkResult} 转换成 {@link SimplifyWorkResult} */ +export function simplifyWorkResult(results: WorkResult[]): SimplifyWorkResult[] { + const res: SimplifyWorkResult[] = []; + + for (const wr of results) { + res.push({ + requesting: wr.requesting, + resolving: wr.resolving, + error: wr.error, + question: StringUtils.of( + wr.ctx?.elements.title + ?.filter(Boolean) + ?.map((t) => optimizationTextWithImage(t as HTMLElement)) + .join(',') || '' + ) + .toString() + /** cx新版题目冗余 */ + .replace(/\d+\.\s*\((.+题|名词解释|完形填空|阅读理解), .+分\)/, '') + /** cx旧版题目冗余 */ + .replace(/[[|(|【|(]..题[\]|)|】|)]/, ''), + finish: wr.result?.finish, + searchInfos: + wr.ctx?.searchInfos.map((sr) => ({ + error: sr.error?.message, + name: sr.name, + homepage: sr.homepage, + results: sr.results.map((ans) => [ans.question, ans.answer]) + })) || [] + }); + } + + return res; +}