From 9fabb42c2e6fcca70b9252de14f63b5af31422bd Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 25 Nov 2022 14:50:49 +0800 Subject: [PATCH] vjudge: update VERDICT and hustoj support --- packages/vjudge/package.json | 2 +- packages/vjudge/src/providers/codeforces.ts | 4 +- packages/vjudge/src/providers/hustoj.ts | 378 ++++++++++++++++++++ packages/vjudge/src/providers/index.ts | 5 +- packages/vjudge/src/verdict.ts | 65 ++-- 5 files changed, 430 insertions(+), 24 deletions(-) create mode 100644 packages/vjudge/src/providers/hustoj.ts diff --git a/packages/vjudge/package.json b/packages/vjudge/package.json index 5903cc06d..34028c468 100644 --- a/packages/vjudge/package.json +++ b/packages/vjudge/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/vjudge", - "version": "1.6.5", + "version": "1.7.0", "description": "Submit problems to remote oj", "main": "package.json", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/vjudge/src/providers/codeforces.ts b/packages/vjudge/src/providers/codeforces.ts index c78a206ff..b39a827ae 100644 --- a/packages/vjudge/src/providers/codeforces.ts +++ b/packages/vjudge/src/providers/codeforces.ts @@ -13,7 +13,7 @@ import { buildContent, Logger, SettingModel, sleep, STATUS, Time, } from 'hydrooj'; import { IBasicProvider, RemoteAccount } from '../interface'; -import { VERDICT } from '../verdict'; +import { normalize, VERDICT } from '../verdict'; proxy(superagent); const logger = new Logger('remote/codeforces'); @@ -465,7 +465,7 @@ export default class CodeforcesProvider implements IBasicProvider { }); } if (body.waiting === 'true') continue; - const status = VERDICT[Object.keys(VERDICT).filter((k) => body.verdict.includes(k))[0]]; + const status = VERDICT[Object.keys(VERDICT).find((k) => normalize(body.verdict).includes(k))]; return await end({ status, score: status === STATUS.STATUS_ACCEPTED ? 100 : 0, diff --git a/packages/vjudge/src/providers/hustoj.ts b/packages/vjudge/src/providers/hustoj.ts new file mode 100644 index 000000000..729b32631 --- /dev/null +++ b/packages/vjudge/src/providers/hustoj.ts @@ -0,0 +1,378 @@ +import { defaultsDeep } from 'lodash'; +import { + SettingModel, sleep, STATUS, superagent, +} from 'hydrooj'; +import { IBasicProvider, RemoteAccount } from '../interface'; +import { VERDICT } from '../verdict'; + +// NOTE: This is no longer under maintenance. +// You may find it just works, or you may not. +// Feel free to open a pull request if you want to fix any issue. + +/* eslint-disable no-await-in-loop */ +const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36'; + +interface HustOJRemoteConfig { + login?: { + endpoint?: string; + usernameField?: string; + passwordField?: string; + extra?: Record; + }; + // NOTE: CAPTCHAS ARE NOT SUPPORTED + submit?: { + endpoint?: string; + idField?: string; + langField?: string; + codeField?: string; + extra?: Record; + tooFrequent?: string; + rid?: RegExp; + }; + monit?: { + endpoint?: string; + }; + ceInfo?: { + endpoint?: string; + matcher?: RegExp; + }, + server?: string; +} + +const defaultConfig: HustOJRemoteConfig = { + // languages: { + // c: 0, + // cc: 1, + // pas: 2, + // java: 3, + // rb: 4, + // sh: 5, + // py: 6, + // php: 7, + // pl: 8, + // cs: 9, + // js: 16, + // go: 17, + // }, + ceInfo: { + endpoint: '/ceinfo.php?sid={rid}', + matcher: /
(.*?)<\/pre>/gmi,
+    },
+    monit: {
+        endpoint: '/status.php?pid={pid}&user_id={uid}',
+    },
+    login: {
+        endpoint: '/login.php',
+        usernameField: 'username',
+        passwordField: 'password',
+        extra: {},
+    },
+    submit: {
+        endpoint: '/submit.php',
+        idField: 'id',
+        langField: 'language',
+        codeField: 'source',
+        extra: {},
+        tooFrequent: 'UNKNOWN',
+        rid: /\n([0-9]+)<\/td>/gmi,
+    },
+    server: 'https://acm.hust.edu.cn',
+};
+
+function isProcessing(t: string) {
+    return [STATUS.STATUS_WAITING, STATUS.STATUS_COMPILING, STATUS.STATUS_JUDGING].includes(VERDICT[t]);
+}
+
+export class HUSTOJ implements IBasicProvider {
+    cookie: string[] = [];
+    config = { ...defaultConfig };
+    state = {
+        username: '',
+        pid: '',
+    };
+
+    constructor(public account: RemoteAccount, private save: (data: any) => Promise) {
+        defaultsDeep(this.config, account);
+        this.updateConfig();
+    }
+
+    updateConfig() { }
+
+    get(url: string) {
+        return superagent.get(url).set('Cookie', this.cookie);
+    }
+
+    post(url: string, data: Record) {
+        return superagent.post(url)
+            .set('Cookie', this.cookie).send(data).type('form')
+            .set('Referer', this.config.server)
+            .set('User-Agent', UA)
+            .set('Accept', 'application/json');
+    }
+
+    getProblem() {
+        return null;
+    }
+
+    async listProblem() {
+        return [];
+    }
+
+    async login(username: string, password: string) {
+        this.cookie = [];
+        const res = await this.post(this.config.login[0], {
+            [this.config.login.usernameField]: username,
+            [this.config.login.passwordField]: password,
+            ...this.config.login.extra,
+        });
+        this.state.username = username;
+        this.cookie = res.headers['set-cookie'].join('\n');
+    }
+
+    async ensureLogin() {
+        if (!this.account.handle || !this.account.password) return false;
+        await this.login(this.account.handle, this.account.password);
+        return true;
+    }
+
+    async submitProblem(id: string, lang: string, code: string, info, next) {
+        const langId = lang.split('.')[1];
+        const comment = SettingModel.langs[lang].comment;
+        if (comment) {
+            const msg = `Hydro submission #${info.rid}@${Date.now()}`;
+            if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`;
+            else if (comment instanceof Array) code = `${comment[0]} ${msg} ${comment[1]}\n${code}`;
+        }
+        const res = await this.post(this.config.submit.endpoint, {
+            [this.config.submit.idField]: id,
+            [this.config.submit.langField]: langId,
+            [this.config.submit.codeField]: code,
+            ...this.config.submit.extra,
+        });
+        // if (res.text.includes(this.config.submit.tooFrequent)) throw new TooFrequentError();
+        console.log(res.text);
+        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];
+    }
+
+    async waitForSubmission(rid, next, end) {
+        let url = this.config.monit.endpoint.replace('{uid}', this.state.username).replace('{pid}', this.state.pid);
+        // eslint-disable-next-line max-len
+        const RE = new RegExp(`${rid}.*?.*?(.*?)(.*?)kb(.*?)ms`, 'gmi');
+        const res = await this.get(url);
+        let [, status, time, memory] = RE.exec(res.text);
+        while (isProcessing(status)) {
+            next({ status: STATUS.STATUS_JUDGING, progress: 0 });
+            await sleep(1000);
+            const resp = await this.get(url);
+            [, status, time, memory] = RE.exec(resp.text);
+        }
+        if (VERDICT[status] === STATUS.STATUS_COMPILE_ERROR) {
+            url = this.config.ceInfo.endpoint.replace('{rid}', rid);
+            const resp = await this.get(url);
+            const compilerText = decodeURIComponent(this.config.ceInfo.matcher.exec(resp.text)[1]
+                ?.replace(/\n/g, '')?.replace(//g, '\n')?.replace(/\n\n/g, '\n'));
+            end({
+                status: STATUS.STATUS_COMPILE_ERROR,
+                score: 0,
+                time: 0,
+                memory: 0,
+                compilerText,
+            });
+            return;
+        }
+        const score = VERDICT[status] === STATUS.STATUS_ACCEPTED ? 100 : 0;
+        next({
+            status: STATUS.STATUS_JUDGING,
+            case: {
+                status: VERDICT[status],
+                score,
+                time: +time,
+                memory: +memory,
+                message: status,
+            },
+            progress: 99,
+        });
+        end({
+            status: VERDICT[status],
+            score,
+            time: +time,
+            memory: +memory,
+            message: status,
+        });
+    }
+}
+
+export class YBT extends HUSTOJ {
+    updateConfig() {
+        // this.LANGUAGES = {
+        //     cc: 1,
+        //     c: 2,
+        //     java: 3,
+        //     pas: 4,
+        //     py: 5,
+        //     py3: 5,
+        // };
+        this.config.login.extra = { login: '登录' };
+        this.config.submit = {
+            endpoint: '/action.php',
+            idField: 'problem_id',
+            langField: 'language',
+            codeField: 'source',
+            extra: { submit: '提交', user_id: this.account.handle },
+            tooFrequent: '提交频繁啦!',
+        };
+        this.config.ceInfo = {
+            endpoint: '/show_ce_info.php?runid={rid}',
+            matcher: /(.*?)<\/td>/gmi,
+        };
+        this.config.server = 'http://ybt.ssoier.cn:8088/';
+    }
+
+    async waitForSubmission(rid, next, end) {
+        const url = `/statusx1.php?runidx=${rid}`;
+        let res = await this.get(url);
+        let staText = res.text.split(':');
+        while (isProcessing(staText[4])) {
+            next({ status: STATUS.STATUS_JUDGING, progress: 0 });
+            await sleep(1000);
+            res = await this.get(url);
+            staText = res.text.split(':');
+        }
+        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')); + end({ + status: STATUS.STATUS_COMPILE_ERROR, + score: 0, + time: 0, + memory: 0, + compilerText, + }); + return; + } + const staText4 = staText[4].split('|'); + const staText5 = staText[5].split(','); + let totalTime = 0; + let totalMem = 0; + let totalScore = 0; + let totalStatus = STATUS.STATUS_WRONG_ANSWER; + for (const i in staText5) { + if (staText5[i]) { + let score; + const staText5i = staText5[i].split('|'); + if (VERDICT[staText5i[0]] === STATUS.STATUS_ACCEPTED) { + score = 100; + totalScore += score; + } else score = 0; + const staText5i1 = staText5i[1].split('_'); + totalMem += +staText5i1[0]; + totalTime += +staText5i1[1]; + next({ + status: STATUS.STATUS_JUDGING, + case: { + status: VERDICT[staText5i[0]], + score, + time: +staText5i1[1], + memory: +staText5i1[0], + }, + progress: 99, + }); + } + } + if (VERDICT[staText4[0]] === STATUS.STATUS_ACCEPTED) { + totalScore = 100; + totalStatus = STATUS.STATUS_ACCEPTED; + } + end({ + status: totalStatus, + score: totalScore, + time: totalTime, + memory: totalMem, + }); + } +} + +export class BZOJ extends HUSTOJ { + updateConfig() { + this.config.login = { + endpoint: '/login.php', + usernameField: 'user_id', + passwordField: 'password', + extra: { submit: 'Submit' }, + }; + this.config.submit.tooFrequent = 'You should not submit more than twice in 10 seconds.....'; + this.config.submit.rid = /Submit_Time<\/td><\/tr>\n([0-9]+)/igm; + this.config.ceInfo.matcher = /
([\s\S]*?)<\/pre>/igm;
+    }
+}
+
+export class XJOI extends HUSTOJ {
+    updateConfig() {
+        // this.LANGUAGES = { cc: 'g++', c: 'gcc', pas: 'fpc' };
+        this.config.login = {
+            endpoint: '/',
+            usernameField: 'user',
+            passwordField: 'pass',
+            extra: { remember: 'on' },
+        };
+        this.config.submit = {
+            endpoint: '/submit',
+            idField: 'proid',
+            langField: 'language',
+            codeField: 'source',
+            tooFrequent: '请稍后再提交',
+            extra: {},
+            rid: / ([\s\S]*?)<\/textarea>/igm,
+            /time: ([0-9]+)ms, memory: ([0-9]+)kb, points: ([0-9]+), status: (.*?)/gmi,
+        ];
+        const url = `/detail${rid}`;
+        let res = await this.get(url);
+        let msg = SUPERMONIT[0].exec(res.text)[1].split('\n');
+        while (isProcessing(msg[0])) {
+            await sleep(1000);
+            res = await this.get(url);
+            msg = SUPERMONIT[1].exec(res.text)[1].split('\n');
+        }
+        if (VERDICT[msg[0]] === STATUS.STATUS_COMPILE_ERROR) {
+            end({
+                status: STATUS.STATUS_COMPILE_ERROR,
+                score: 0,
+                time: 0,
+                memory: 0,
+                compilerText: msg.join('\n'),
+            });
+            return;
+        }
+        for (let i = 2; i < msg.length - 1; i++) {
+            const [, time, memory, score, status] = SUPERMONIT[1].exec(msg[i]);
+            next({
+                status: STATUS.STATUS_JUDGING,
+                case: {
+                    time: +time,
+                    memory: +memory,
+                    score: +score,
+                    status: VERDICT[status],
+                },
+            });
+        }
+        const [, time, memory, score, status] = SUPERMONIT[1].exec(msg[1]);
+        end({
+            time: +time,
+            memory: +memory,
+            score: +score,
+            status: VERDICT[status],
+        });
+    }
+}
diff --git a/packages/vjudge/src/providers/index.ts b/packages/vjudge/src/providers/index.ts
index 4e175cad4..de792aa2a 100644
--- a/packages/vjudge/src/providers/index.ts
+++ b/packages/vjudge/src/providers/index.ts
@@ -1,11 +1,14 @@
 import codeforces from './codeforces';
 import csgoj from './csgoj';
+import {
+    BZOJ as bzoj, HUSTOJ as hustoj, XJOI as xjoi, YBT as ybt,
+} from './hustoj';
 import luogu from './luogu';
 import poj from './poj';
 import spoj from './spoj';
 import uoj from './uoj';
 
 const vjudge: Record = {
-    codeforces, csgoj, luogu, poj, spoj, uoj,
+    codeforces, csgoj, luogu, poj, spoj, uoj, hustoj, bzoj, xjoi, ybt,
 };
 export default vjudge;
diff --git a/packages/vjudge/src/verdict.ts b/packages/vjudge/src/verdict.ts
index 00f593b62..a938a2a58 100644
--- a/packages/vjudge/src/verdict.ts
+++ b/packages/vjudge/src/verdict.ts
@@ -1,32 +1,26 @@
 import { STATUS } from '@hydrooj/utils/lib/status';
 
-export const VERDICT = {
+export function normalize(key: string) {
+    return key.toUpperCase().replace(/ /g, '_');
+}
+
+export const VERDICT = new Proxy>({
     RUNTIME_ERROR: STATUS.STATUS_RUNTIME_ERROR,
     WRONG_ANSWER: STATUS.STATUS_WRONG_ANSWER,
     OK: STATUS.STATUS_ACCEPTED,
+    COMPILING: STATUS.STATUS_COMPILING,
     TIME_LIMIT_EXCEEDED: STATUS.STATUS_TIME_LIMIT_EXCEEDED,
     MEMORY_LIMIT_EXCEEDED: STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
     IDLENESS_LIMIT_EXCEEDED: STATUS.STATUS_TIME_LIMIT_EXCEEDED,
     ACCEPTED: STATUS.STATUS_ACCEPTED,
-    'PRESENTATION ERROR': STATUS.STATUS_WRONG_ANSWER,
-    'WRONG ANSWER': STATUS.STATUS_WRONG_ANSWER,
-    'RUNTIME ERROR': STATUS.STATUS_RUNTIME_ERROR,
-    'TIME LIMIT EXCEEDED': STATUS.STATUS_TIME_LIMIT_EXCEEDED,
-    'MEMORY LIMIT EXCEEDED': STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
-    'OUTPUT LIMIT EXCEEDED': STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED,
-    'IDLENESS LIMIT EXCEEDED': STATUS.STATUS_TIME_LIMIT_EXCEEDED,
-    'EXTRA TEST PASSED': STATUS.STATUS_ACCEPTED,
-    'COMPILE ERROR': STATUS.STATUS_COMPILE_ERROR,
-    'RUNNING & JUDGING': STATUS.STATUS_JUDGING,
+    PRESENTATION_ERROR: STATUS.STATUS_WRONG_ANSWER,
+    OUTPUT_LIMIT_EXCEEDED: STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED,
+    EXTRA_TEST_PASSED: STATUS.STATUS_ACCEPTED,
+    COMPILE_ERROR: STATUS.STATUS_COMPILE_ERROR,
+    'RUNNING_&_JUDGING': STATUS.STATUS_JUDGING,
 
-    Accepted: STATUS.STATUS_ACCEPTED,
-    'Wrong answer': STATUS.STATUS_WRONG_ANSWER,
-    'Runtime error': STATUS.STATUS_RUNTIME_ERROR,
-    'Time limit exceeded': STATUS.STATUS_TIME_LIMIT_EXCEEDED,
-    'Memory limit exceeded': STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
-    'Idleness limit exceeded': STATUS.STATUS_TIME_LIMIT_EXCEEDED,
-
-    'Happy New Year!': STATUS.STATUS_ACCEPTED, // Codeforces
+    // Codeforces
+    'HAPPY_NEW_YEAR!': STATUS.STATUS_ACCEPTED,
 
     // Vjudge
     QUEUEING: STATUS.STATUS_COMPILING,
@@ -44,4 +38,35 @@ export const VERDICT = {
     FAILED_OTHER: STATUS.STATUS_SYSTEM_ERROR,
     SUBMIT_FAILED_PERM: STATUS.STATUS_SYSTEM_ERROR,
     SUBMIT_FAILED_TEMP: STATUS.STATUS_SYSTEM_ERROR,
-};
+
+    // HustOJ
+    等待: STATUS.STATUS_WAITING,
+    运行并评判: STATUS.STATUS_JUDGING,
+    编译成功: STATUS.STATUS_JUDGING,
+    RUNNING_JUDGING: STATUS.STATUS_JUDGING,
+    正确: STATUS.STATUS_ACCEPTED,
+    格式错误: STATUS.STATUS_WRONG_ANSWER,
+    答案错误: STATUS.STATUS_WRONG_ANSWER,
+    OUTPUT_LIMIT_EXCEED: STATUS.STATUS_WRONG_ANSWER,
+    输出超限: STATUS.STATUS_WRONG_ANSWER,
+    运行时错误: STATUS.STATUS_RUNTIME_ERROR,
+    DANGEROUS_SYSCALL: STATUS.STATUS_RUNTIME_ERROR,
+    TIME_LIMIT_EXCEED: STATUS.STATUS_TIME_LIMIT_EXCEEDED,
+    时间超限: STATUS.STATUS_TIME_LIMIT_EXCEEDED,
+    MEMORY_LIMIT_EXCEED: STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
+    内存超限: STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
+    COMPILATION_ERROR: STATUS.STATUS_COMPILE_ERROR,
+    编译错误: STATUS.STATUS_COMPILE_ERROR,
+    RF: STATUS.STATUS_SYSTEM_ERROR,
+    SYSTEM_ERROR: STATUS.STATUS_SYSTEM_ERROR,
+    SE: STATUS.STATUS_SYSTEM_ERROR,
+    未知错误: STATUS.STATUS_SYSTEM_ERROR,
+}, {
+    get(self, key) {
+        if (typeof key === 'symbol') return null;
+        key = normalize(key);
+        if (STATUS[key]) return STATUS[key];
+        if (self[key]) return self[key];
+        return null;
+    },
+});