-
-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
28 changed files
with
2,095 additions
and
457 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { AnswererWrapper } from '../worker/answer.wrapper.handler'; | ||
import { request } from './request'; | ||
|
||
/** | ||
* 解析题库配置, 有误会抛出异常 | ||
*/ | ||
export async function parseAnswererWrappers(value: string): Promise<AnswererWrapper[]> { | ||
let raw = value.toString(); | ||
let aw: AnswererWrapper[] = []; | ||
|
||
// 解析 http 链接 | ||
if (value.startsWith('http')) { | ||
raw = await request(value, { | ||
contentType: 'text', | ||
method: 'get', | ||
type: 'fetch' | ||
}); | ||
} | ||
|
||
try { | ||
aw = JSON.parse(raw); | ||
} catch { | ||
throw new Error(`格式错误,必须为:json字符串 或 题库配置链接`); | ||
} | ||
|
||
if (aw && Array.isArray(aw)) { | ||
if (aw.length) { | ||
for (let i = 0; i < aw.length; i++) { | ||
const item = aw[i]; | ||
if (typeof item.name !== 'string') { | ||
throw new Error(`第 ${i + 1} 个题库的 名字(name) 为空`); | ||
} | ||
if (typeof item.url !== 'string') { | ||
throw new Error(`第 ${i + 1} 个题库的 接口地址(url) 为空`); | ||
} | ||
if (typeof item.name !== 'string') { | ||
throw new Error(`第 ${i + 1} 个题库的 解析器(handler) 为空`); | ||
} | ||
if (item.headers && typeof item.headers !== 'object') { | ||
throw new Error(`第 ${i + 1} 个题库的 头部信息(header) 应为 对象 格式`); | ||
} | ||
if (item.data && typeof item.data !== 'object') { | ||
throw new Error(`第 ${i + 1} 个题库的 提交数据(data) 应为 对象 格式`); | ||
} | ||
const contentTypes = ['json', 'text'] as AnswererWrapper['contentType'][]; | ||
if (item.contentType && contentTypes.every((i) => i !== item.contentType)) { | ||
throw new Error(`第 ${i + 1} 个题库的 contentType 必须为以下选项中的一个 ${contentTypes.join(', ')}`); | ||
} | ||
const methods = ['post', 'get'] as AnswererWrapper['method'][]; | ||
if (item.method && methods.every((i) => i !== item.method)) { | ||
throw new Error(`第 ${i + 1} 个题库的 method 必须为以下选项中的一个 ${methods.join(', ')}`); | ||
} | ||
const types = ['fetch', 'GM_xmlhttpRequest'] as AnswererWrapper['type'][]; | ||
if (item.type && types.every((i) => i !== item.type)) { | ||
throw new Error(`第 ${i + 1} 个题库的 type 必须为以下选项中的一个 ${types.join(', ')}`); | ||
} | ||
} | ||
return aw; | ||
} else { | ||
throw new Error('题库为空!'); | ||
} | ||
} else { | ||
throw new Error('题库配置格式错误!'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { RawElements, SearchedElements } from '../worker/interface'; | ||
|
||
/** | ||
* 与 {@link domSearchAll } 相同,区别是这个只返回单个元素,而不是一个元素数组 | ||
* @param root | ||
* @param wrapper | ||
* @returns | ||
*/ | ||
export function domSearch<E extends RawElements>( | ||
/** 搜索构造器 */ | ||
wrapper: E, | ||
root: HTMLElement | Document = window.document | ||
): SearchedElements<E, HTMLElement | null> { | ||
const obj = Object.create({}); | ||
Reflect.ownKeys(wrapper).forEach((key) => { | ||
Reflect.set(obj, key, root.querySelector(wrapper[key.toString()])); | ||
}); | ||
return obj; | ||
} | ||
|
||
/** | ||
* 元素搜索 | ||
* | ||
* @example | ||
* | ||
* const { title , btn , arr } = domSearch(document.body,{ | ||
* title: '.title' | ||
* btn: ()=> '.btn', | ||
* arr: ()=> Array.from(document.body.querySelectorAll('.function-arr')) | ||
* }) | ||
* | ||
* console.log(title) // 等价于 Array.from(document.body.querySelectorAll('.title')) | ||
* console.log(btn)// 等价于 Array.from(document.body.querySelectorAll('.btn')) | ||
*/ | ||
export function domSearchAll<E extends RawElements>( | ||
/** 搜索构造器 */ | ||
wrapper: E, | ||
root: HTMLElement | Document = window.document | ||
): SearchedElements<E, HTMLElement[]> { | ||
const obj = Object.create({}); | ||
Reflect.ownKeys(wrapper).forEach((key) => { | ||
Reflect.set(obj, key, Array.from(root.querySelectorAll(wrapper[key.toString()]))); | ||
}); | ||
return obj; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { isInBrowser } from '../../utils/common'; | ||
|
||
/** | ||
* 发起请求 | ||
* @param url 请求地址 | ||
* @param opts 请求参数 | ||
*/ | ||
export function request<T extends 'json' | 'text'>( | ||
url: string, | ||
opts: { | ||
type: 'fetch' | 'GM_xmlhttpRequest'; | ||
method?: 'get' | 'post'; | ||
contentType?: T; | ||
headers?: Record<string, string>; | ||
data?: Record<string, string>; | ||
} | ||
): Promise<T extends 'json' ? Record<string, any> : string> { | ||
return new Promise((resolve, reject) => { | ||
try { | ||
/** 默认参数 */ | ||
const { contentType = 'json', method = 'get', type = 'fetch', data = {}, headers = {} } = opts || {}; | ||
/** 环境变量 */ | ||
const env = isInBrowser() ? 'browser' : 'node'; | ||
|
||
/** 如果是跨域模式并且是浏览器环境 */ | ||
if (type === 'GM_xmlhttpRequest' && env === 'browser') { | ||
if (typeof GM_xmlhttpRequest !== 'undefined') { | ||
// eslint-disable-next-line no-undef | ||
GM_xmlhttpRequest({ | ||
url, | ||
method: method === 'get' ? 'GET' : 'POST', | ||
data: new URLSearchParams(data).toString(), | ||
headers: headers, | ||
responseType: 'json', | ||
onload: (response) => { | ||
if (response.status === 200) { | ||
if (contentType === 'json') { | ||
try { | ||
resolve(JSON.parse(response.responseText)); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
} else { | ||
resolve(response.responseText as any); | ||
} | ||
} else { | ||
reject(response.responseText); | ||
} | ||
}, | ||
onerror: reject | ||
}); | ||
} else { | ||
reject(new Error('GM_xmlhttpRequest is not defined')); | ||
} | ||
} else { | ||
const fet: (...args: any[]) => Promise<Response> = env === 'node' ? require('node-fetch').default : fetch; | ||
|
||
fet(url, { contentType, body: method === 'post' ? data : undefined, method, headers }) | ||
.then((response) => { | ||
if (contentType === 'json') { | ||
response.json().then(resolve).catch(reject); | ||
} else { | ||
// @ts-ignore | ||
response.text().then(resolve).catch(reject); | ||
} | ||
}) | ||
.catch((error) => { | ||
reject(new Error(error)); | ||
}); | ||
} | ||
} catch (error) { | ||
reject(error); | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { findBestMatch, Rating } from 'string-similarity'; | ||
|
||
/** | ||
* 删除特殊字符, 并且全部转小写,只保留英文,数字,中文 | ||
* @param str | ||
* @returns | ||
*/ | ||
export function clearString(str: string, ...exclude: string[]) { | ||
return str | ||
.trim() | ||
.toLocaleLowerCase() | ||
.replace(RegExp(`[^\\u4e00-\\u9fa5A-Za-z0-9${exclude.join('')}]*`, 'g'), ''); | ||
} | ||
|
||
/** | ||
* 答案相似度匹配 , 返回相似度对象列表 Array<{@link Rating}> | ||
* | ||
* 相似度计算算法 : https://www.npmjs.com/package/string-similarity | ||
* | ||
* @param answers 答案列表 | ||
* @param options 选项列表 | ||
* | ||
* | ||
* @example | ||
* | ||
* ```js | ||
* | ||
* 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] | ||
* | ||
* ``` | ||
* | ||
*/ | ||
export function answerSimilar(answers: string[], options: string[]): Rating[] { | ||
answers = answers.map(removeRedundant); | ||
options = options.map(removeRedundant); | ||
|
||
const similar = | ||
answers.length !== 0 | ||
? options.map((option) => findBestMatch(option, answers).bestMatch) | ||
: options.map((opt) => ({ rating: 0, target: '' } as Rating)); | ||
|
||
return similar; | ||
} | ||
|
||
/** | ||
* 删除题目选项中开头的冗余字符串 | ||
*/ | ||
export function removeRedundant(str: string) { | ||
return str?.trim().replace(/[A-Z]{1}[^A-Za-z0-9\u4e00-\u9fa5]+([A-Za-z0-9\u4e00-\u9fa5]+)/, '$1') || ''; | ||
} |
Oops, something went wrong.