-
-
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
105 changed files
with
4,993 additions
and
1,581 deletions.
There are no files selected for viewing
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 |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
pnpm-lock.yaml | ||
package-lock.json | ||
public/ | ||
bin/ | ||
|
||
# rollup-plugin-visualizer | ||
stats.html | ||
|
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 |
---|---|---|
@@ -1,29 +1,35 @@ | ||
{ | ||
"$schema": "https://json.schemastore.org/electron-builder.json", | ||
"appId": "ocs.enncy.cn", | ||
"productName": "ocs", | ||
"extraMetadata": { | ||
"name": "OCS Desktop", | ||
"description": "OCS 浏览器自动化神器,一键浏览器多开,用户脚本环境一键配置。" | ||
}, | ||
"productName": "OCS Desktop", | ||
"asar": false, | ||
"copyright": "Copyright © 2021 ${author}", | ||
"nsis": { | ||
"oneClick": false, | ||
"allowToChangeInstallationDirectory": true | ||
}, | ||
|
||
"win": { | ||
"icon": "public/favicon.png", | ||
"artifactName": "${productName}-${version}-setup-${os}-${arch}.${ext}", | ||
"artifactName": "ocs-${version}-setup-${os}-${arch}.${ext}", | ||
"target": [ | ||
{ | ||
"target": "nsis", | ||
"arch": ["x64"] | ||
}, | ||
{ | ||
"target": "zip" | ||
"target": "nsis", | ||
"arch": ["ia32"] | ||
} | ||
] | ||
}, | ||
"mac": { | ||
"icon": "public/favicon.icns", | ||
"artifactName": "${productName}-${version}-setup-${os}-${arch}.${ext}", | ||
"artifactName": "ocs-${version}-setup-${os}-${arch}.${ext}", | ||
"target": "default" | ||
} | ||
} |
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
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 @@ | ||
export * from './wk/index'; |
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,42 @@ | ||
import { Page } from 'playwright-core'; | ||
import { EventsRecord } from '../interface'; | ||
import { AutomationScript, RunFunction, ScriptOptions } from '../script'; | ||
|
||
export type BaseAutomationEvents = { | ||
'script-error': (...msg: string[]) => void; | ||
'script-data': (...msg: string[]) => void; | ||
}; | ||
|
||
export interface Config { | ||
label: string; | ||
value: any; | ||
hide?: boolean; | ||
} | ||
|
||
export type ConfigValueRecord<C extends Record<string, Config>> = { | ||
[K in keyof C]: C[K]['value']; | ||
}; | ||
|
||
export class ConfigsRequiredAutomationScript< | ||
C extends Record<string, Config> = Record<string, Config>, | ||
RF extends RunFunction = RunFunction, | ||
E extends EventsRecord = EventsRecord | ||
> extends AutomationScript<RF, E> { | ||
name: string; | ||
configs: C; | ||
|
||
constructor(configs: C, options: ScriptOptions<RF>) { | ||
super(options); | ||
this.name = options.name; | ||
this.run = options.run; | ||
this.configs = configs; | ||
} | ||
} | ||
|
||
export class PlaywrightScript< | ||
C extends Record<string, Config> = Record<string, Config> | ||
> extends ConfigsRequiredAutomationScript< | ||
C, | ||
(page: Page, configs: ConfigValueRecord<C>) => any, | ||
BaseAutomationEvents | ||
> {} |
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,176 @@ | ||
import type { Page } from 'playwright-core'; | ||
import { BaseAutomationEvents, PlaywrightScript } from '../interface'; | ||
import { slowType } from '../../utils'; | ||
import axios from 'axios'; | ||
import { TypedEventEmitter } from '../../interface'; | ||
|
||
export const CXUnitLoginScript = new PlaywrightScript( | ||
{ | ||
unit: { | ||
label: '学校/单位', | ||
value: '' | ||
}, | ||
id: { | ||
label: '学号/工号', | ||
value: '' | ||
}, | ||
password: { | ||
label: '密码', | ||
value: '' | ||
} | ||
}, | ||
{ | ||
name: '超星-学校机构登录', | ||
async run(page, configs) { | ||
try { | ||
if (await isNotLogin(page)) { | ||
/** 其他登录 */ | ||
await Promise.all([page.waitForLoadState('networkidle'), page.click('#otherlogin')]); | ||
await page.waitForTimeout(3000); | ||
/** 输入机构名, 并等待搜索结果 */ | ||
await slowType(page, '#inputunitname', configs.unit); | ||
await page.waitForTimeout(2000); | ||
/** 点击第一个结果 */ | ||
await page.click('.filter-list > ul > li'); | ||
await page.type('#uname', configs.id); | ||
await page.type('#password', configs.password); | ||
|
||
await login(page, CXUnitLoginScript, { | ||
ocrApiUrl: 'http://localhost:15319/ocr', | ||
ocrApiImageKey: 'ocr' | ||
}); | ||
} | ||
} catch (err) { | ||
CXUnitLoginScript.emit('script-error', String(err)); | ||
} | ||
} | ||
} | ||
); | ||
|
||
export const CXPhoneLoginScript = new PlaywrightScript( | ||
{ | ||
phone: { | ||
label: '手机号', | ||
value: '' | ||
}, | ||
password: { | ||
label: '密码', | ||
value: '' | ||
} | ||
}, | ||
{ | ||
name: '超星-手机密码登录', | ||
async run(page, configs) { | ||
try { | ||
if (await isNotLogin(page)) { | ||
await page.type('#phone', configs.phone); | ||
await page.type('#pwd', configs.password); | ||
|
||
await login(page, CXPhoneLoginScript); | ||
} | ||
} catch (err) { | ||
CXPhoneLoginScript.emit('script-error', String(err)); | ||
} | ||
} | ||
} | ||
); | ||
|
||
function login( | ||
page: Page, | ||
events: TypedEventEmitter<BaseAutomationEvents>, | ||
opts?: { | ||
ocrApiUrl?: string; | ||
ocrApiImageKey?: string; | ||
} | ||
) { | ||
return new Promise<void>((resolve, reject) => { | ||
// 监听登录状态 | ||
const listenerLogin = (page: Page) => { | ||
// 登录成功 | ||
if (page.url().includes('/space/index') || page.url().includes('/base')) { | ||
page.off('load', listenerLogin); | ||
resolve(); | ||
} | ||
}; | ||
|
||
page.on('load', listenerLogin); | ||
// 1分钟登录超时 | ||
const timeout = setTimeout(() => { | ||
events.emit('script-error', '登录超时,请重试。'); | ||
resolve(); | ||
}, 60 * 1000); | ||
|
||
// 重试5次 | ||
let tryCount = 5; | ||
// 尝试登录 | ||
const tryLogin = async () => { | ||
tryCount--; | ||
const area = await page.$('#numVerCode'); | ||
if (area !== null) { | ||
/** 破解验证码 */ | ||
if (opts?.ocrApiUrl && opts?.ocrApiImageKey && area) { | ||
/** 每次都点击保证是最新图片 */ | ||
await Promise.all([page.waitForLoadState('networkidle'), await area.click()]); | ||
const box = await area.boundingBox(); | ||
if (box) { | ||
/** 请求验证码破解接口 */ | ||
const body = Object.create([]); | ||
const buffer = await page.screenshot({ clip: box }); | ||
Reflect.set(body, opts.ocrApiImageKey, buffer.toString('base64')); | ||
const { | ||
data: { ocr: code, canOCR } | ||
} = await axios.post(opts.ocrApiUrl, body); | ||
if (canOCR) { | ||
/** 破解验证码 */ | ||
if (code) { | ||
await page.fill('#vercode', code); | ||
} | ||
} else { | ||
throw new Error('未检测到图片验证码识别模块, 请手动输入验证码。'); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// 点击登录 | ||
await page.click('#loginBtn'); | ||
await page.waitForTimeout(3000); | ||
|
||
// 检测错误 | ||
const vercodeMsg = await page.evaluate(() => | ||
Array.from(document.querySelectorAll('#vercodeMsg.err-txt')) | ||
.map((e) => e.textContent?.trim() || '') | ||
.filter(Boolean) | ||
); | ||
|
||
const errors = await page.evaluate(() => | ||
Array.from(document.querySelectorAll('.err-txt,.err-tip')) | ||
.map((e) => e.textContent?.trim() || '') | ||
.filter(Boolean) | ||
); | ||
|
||
if (vercodeMsg.concat(errors).some((str) => str.includes('验证码'))) { | ||
if (tryCount === 0) { | ||
clearTimeout(timeout); | ||
events.emit('script-error', vercodeMsg.join('\n').trim()); | ||
} else { | ||
await tryLogin(); | ||
} | ||
} else if (errors.length) { | ||
clearTimeout(timeout); | ||
events.emit('script-error', errors.join('\n').trim()); | ||
} else { | ||
clearTimeout(timeout); | ||
} | ||
}; | ||
|
||
tryLogin(); | ||
}); | ||
} | ||
|
||
async function isNotLogin(page: Page) { | ||
await page.goto('https://i.chaoxing.com'); | ||
await page.waitForTimeout(2000); | ||
|
||
return page.url().includes('passport2.chaoxing.com'); | ||
} |
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,2 @@ | ||
export * from './cx'; | ||
export * from './zhs'; |
Oops, something went wrong.