diff --git a/packages/vjudge/package.json b/packages/vjudge/package.json index fe5231d31..281973b77 100644 --- a/packages/vjudge/package.json +++ b/packages/vjudge/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/vjudge", - "version": "1.8.6", + "version": "1.8.7", "description": "Submit problems to remote oj", "main": "src/model.ts", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/vjudge/src/fetch.ts b/packages/vjudge/src/fetch.ts index 470948230..d8dc884f1 100644 --- a/packages/vjudge/src/fetch.ts +++ b/packages/vjudge/src/fetch.ts @@ -53,4 +53,8 @@ export class BasicFetcher { if (typeof cookie === 'string') this.cookie = [cookie]; else this.cookie = cookie; } + + setEndpoint(endpoint: string) { + this.defaultEndpoint = endpoint; + } } diff --git a/packages/vjudge/src/providers/hustoj.ts b/packages/vjudge/src/providers/hustoj.ts index 701a06a5c..c71a58e17 100644 --- a/packages/vjudge/src/providers/hustoj.ts +++ b/packages/vjudge/src/providers/hustoj.ts @@ -19,6 +19,7 @@ interface HustOJRemoteConfig { usernameField?: string; passwordField?: string; extra?: Record | (() => Promise>); + check?: string; }; // NOTE: CAPTCHAS ARE NOT SUPPORTED submit?: { @@ -29,6 +30,7 @@ interface HustOJRemoteConfig { extra?: Record; tooFrequent?: string; rid?: RegExp; + noRefetch?: boolean; }; monit?: { endpoint?: string; @@ -36,6 +38,7 @@ interface HustOJRemoteConfig { ceInfo?: { endpoint?: string; matcher?: RegExp; + selector?: string; }, server?: string; } @@ -67,6 +70,7 @@ const defaultConfig: HustOJRemoteConfig = { usernameField: 'username', passwordField: 'password', extra: {}, + check: '', }, submit: { endpoint: '/submit.php', @@ -84,7 +88,7 @@ function isProcessing(t: string) { return [STATUS.STATUS_WAITING, STATUS.STATUS_COMPILING, STATUS.STATUS_JUDGING].includes(VERDICT[t]); } // TODO -async function parseCaptcha(image: Buffer) { // eslint-disable-line +async function _parseCaptcha(image: Buffer) { // eslint-disable-line return ''; } @@ -108,6 +112,7 @@ export class HUSTOJ extends BasicFetcher implements IBasicProvider { }); this.config = config; this.updateConfig(); + if (this.config.server) this.setEndpoint(this.config.server); } updateConfig() { } @@ -120,25 +125,40 @@ export class HUSTOJ extends BasicFetcher implements IBasicProvider { return []; } + get loggedIn() { + if (!this.config.login.check) return !!this.cookie?.length; + return this.get('/').then(({ text }) => !!text.includes(this.config.login.check)); + } + async login(username: string, password: string) { this.cookie = []; const c = this.config.login; + const ex = typeof c.extra === 'function' ? await c.extra() : c.extra; const res = await this.post(c.endpoint).send({ [c.usernameField]: username, [c.passwordField]: password, - ...(typeof c.extra === 'function' ? await c.extra() : c.extra), + ...ex, }); + console.log(res.text); this.state.username = username; - this.cookie = res.headers['set-cookie'].join('\n'); + if (res.headers['set-cookie']) this.cookie = res.headers['set-cookie']; + this.save({ cookie: this.cookie }); } async ensureLogin() { if (!this.account.handle || !this.account.password) return false; + if (await this.loggedIn) return true; await this.login(this.account.handle, this.account.password); - return true; + if (!await this.loggedIn) { + // assume rate limit triggered or incorrect captcha + await this.login(this.account.handle, this.account.password); + } + return this.loggedIn; } - async submitProblem(id: string, lang: string, code: string, info, next) { + async submitProblem(id: string, lang: string, code: string) { + await this.ensureLogin(); + if (id.startsWith('P')) id = id.substring(1); const res = await this.post(this.config.submit.endpoint).send({ [this.config.submit.idField]: id, [this.config.submit.langField]: lang.split('.')[1], @@ -147,10 +167,12 @@ export class HUSTOJ extends BasicFetcher implements IBasicProvider { }); // if (res.text.includes(this.config.submit.tooFrequent)) throw new TooFrequentError(); console.log(res.text); + if (this.config.submit.noRefetch) { + return this.config.submit.rid.exec(res.text)[1]; + } const url = this.config.monit.endpoint.replace('{uid}', this.state.username).replace('{pid}', id); this.state.pid = id; const r = await this.get(url); - next({ message: `ID: ${this.config.submit.rid.exec(r.text)[1]}` }); return this.config.submit.rid.exec(r.text)[1]; } @@ -240,9 +262,16 @@ export class YBT extends HUSTOJ { } if (VERDICT[staText[4]] === STATUS.STATUS_COMPILE_ERROR) { const ceInfoUrl = this.config.ceInfo.endpoint.replace('{rid}', rid); - const resp = await this.get(ceInfoUrl); - const compilerText = decodeURIComponent(this.config.ceInfo.matcher.exec(resp.text)[1] - .replace('\n', '').replace('
', '\n').replace('\n\n', '\n')); + let compilerText = 'Cannot get compiler text'; + if (this.config.ceInfo.matcher) { + const resp = await this.get(ceInfoUrl); + const match = this.config.ceInfo.matcher.exec(resp.text); + if (match) compilerText = decodeURIComponent(match[1]).replace(/\n/g, '').replace(//g, '\n').replace(/\n\n/g, '\n'); + } else if (this.config.ceInfo.selector) { + const { document } = await this.html(ceInfoUrl); + const match = document.querySelector(this.config.ceInfo.selector)?.innerHTML; + if (match) compilerText = decodeURIComponent(match).replace(/\n/g, '').replace(//g, '\n').replace(/\n\n/g, '\n'); + } end({ status: STATUS.STATUS_COMPILE_ERROR, score: 0, @@ -294,10 +323,52 @@ export class YBT extends HUSTOJ { } } -export class YBTBAS extends HUSTOJ { +export class YBTBAS extends YBT { + static langs = { + ybtbas: { + display: 'YBTBAS', + hidden: true, + remote: 'ybtbas', + }, + 'ybtbas.7': { + display: 'G++14', + monaco: 'cpp', + highlight: 'cpp', + }, + 'ybtbas.1': { + display: 'G++', + monaco: 'cpp', + highlight: 'cpp', + }, + 'ybtbas.2': { + display: 'GCC', + monaco: 'cpp', + highlight: 'cpp', + }, + 'ybtbas.3': { + display: 'Java', + monaco: 'java', + highlight: 'java', + }, + 'ybtbas.4': { + display: 'Pascal', + monaco: 'pas', + highlight: 'pascal', + }, + 'ybtbas.5': { + display: 'Python', + monaco: 'python', + highlight: 'python', + }, + }; + updateConfig() { + this.config.login.check = '退出登录'; this.config.login.extra = async () => { const captcha = await this.get('/login_xx.php').responseType('arraybuffer'); + if (captcha.headers['set-cookie']) this.cookie = captcha.headers['set-cookie']; + if (!global.parseCaptcha) await sleep(30000); + const parseCaptcha = global.parseCaptcha || _parseCaptcha; return { login: '登录', auth: (await parseCaptcha(captcha.body)).toLowerCase(), @@ -310,10 +381,12 @@ export class YBTBAS extends HUSTOJ { codeField: 'source', extra: { submit: '提交', user_id: this.account.handle }, tooFrequent: '提交频繁啦!', + rid: /runidx=([0-9]+)/gmi, + noRefetch: true, }; this.config.ceInfo = { endpoint: '/show_ce_info.php?runid={rid}', - matcher: /(.*?)<\/td>/gmi, + selector: 'table[width="900px"]>tbody>tr>td>div>center>table>tbody>tr>td', }; this.config.server = 'http://bas.ssoier.cn:8086/'; } diff --git a/packages/vjudge/src/providers/index.ts b/packages/vjudge/src/providers/index.ts index 8834fb0a1..c9689004a 100644 --- a/packages/vjudge/src/providers/index.ts +++ b/packages/vjudge/src/providers/index.ts @@ -2,6 +2,7 @@ import codeforces from './codeforces'; import csgoj from './csgoj'; import { BZOJ as bzoj, HUSTOJ as hustoj, XJOI as xjoi, YBT as ybt, + YBTBAS as ybtbas, } from './hustoj'; import luogu from './luogu'; import poj from './poj'; @@ -9,6 +10,16 @@ import spoj from './spoj'; import uoj from './uoj'; const vjudge: Record = { - codeforces, csgoj, 'luogu.legacy': luogu, poj, spoj, uoj, hustoj, bzoj, xjoi, ybt, + codeforces, + csgoj, + 'luogu.legacy': luogu, + poj, + spoj, + uoj, + hustoj, + bzoj, + xjoi, + ybt, + ybtbas, }; export default vjudge;