vjudge: add yacs (#692)

Co-authored-by: undefined <i@undefined.moe>
This commit is contained in:
Milmonandundefined authored and GitHub committed 2023-12-01 16:49:58 +08:00
1 parent e0db6c6d47
commit b7dc35d795
6 files changed
+177 -3

No files matched your search

+2
View File
@@ -81,8 +81,10 @@ Hydro 同时支持 VJudge,这意味着你可以直接在系统内导入其他
- [UOJ](https://uoj.ac):国内知名 OJ,国家集训队常用;
- [SPOJ](https://www.spoj.com):国内连接很不稳定,不推荐;
- [洛谷](https://www.luogu.com.cn):使用此功能需要向洛谷购买授权;
- [HDUOJ](https://acm.hdu.edu.cn):杭州电子科技大学在线评测系统,其中包含多校训练题;
- [CSGOJ](https://cpc.csgrandeur.cn):广东省赛与湖南省赛赛题评测平台;
- [POJ](https://poj.org):较为古董,服务器稳定性差;
- [YACS](https://iai.sh.cn)上海市计算机学会竞赛平台;
- HUSTOJ:理论上支持所有 HUSTOJ 驱动的系统,但由于各个系统中 UI 有差异,通常需要手动适配。
### 多赛制支持
+1 -1
View File
@@ -73,7 +73,7 @@ export async function next(body: Partial<JudgeResultBody> & { rdoc: RecordDoc })
if (!rdoc) return null;
}
const { $set, $push } = processPayload(rdoc, body);
rdoc = await record.update(body.rdoc.domainId, body.rid, $set, $push, {}, body.addProgress ? { progress: body.addProgress } : {});
rdoc = await record.update(rdoc.domainId, body.rid, $set, $push, {}, body.addProgress ? { progress: body.addProgress } : {});
bus.broadcast('record/change', rdoc, $set, $push, body);
return rdoc;
}
+2 -2
View File
@@ -42,8 +42,8 @@ class AccountService {
async judge(task) {
const rdoc = await RecordModel.get(task.domainId, task.rid);
task = Object.assign(rdoc, task);
const next = (payload) => JudgeHandler.next({ ...payload, rid: task.rid });
const end = (payload) => JudgeHandler.end({ ...payload, rid: task.rid });
const next = (payload) => JudgeHandler.next({ ...payload, rid: task.rid, rdoc });
const end = (payload) => JudgeHandler.end({ ...payload, rid: task.rid, rdoc });
await next({ status: STATUS.STATUS_FETCHED });
try {
const langConfig = SettingModel.langs[task.lang];
+2
View File
@@ -9,6 +9,7 @@ import luogu from './luogu';
import poj from './poj';
import spoj from './spoj';
import uoj from './uoj';
import yacs from './yacs';
const vjudge: Record<string, any> = {
codeforces,
@@ -23,5 +24,6 @@ const vjudge: Record<string, any> = {
ybt,
ybtbas,
hduoj,
yacs,
};
export default vjudge;
+160
View File
@@ -0,0 +1,160 @@
/* eslint-disable no-await-in-loop */
import { JSDOM } from 'jsdom';
import {
Logger, md5, sleep, STATUS, yaml,
} from 'hydrooj';
import { BasicFetcher } from '../fetch';
import { IBasicProvider, RemoteAccount } from '../interface';
import { VERDICT } from '../verdict';
const logger = new Logger('remote/yacs');
export default class YACSProvider extends BasicFetcher implements IBasicProvider {
constructor(public account: RemoteAccount, private save: (data: any) => Promise<void>) {
super(account, 'https://api.iai.sh.cn', 'form', logger);
}
async getData(url: string) {
const { text } = await this.get(new URL(url, 'https://iai.sh.cn').toString());
const { window: { document } } = new JSDOM(text);
return JSON.parse(document.querySelector('script#__NEXT_DATA__').innerHTML).props.pageProps;
}
token: string;
get loggedIn(): Promise<boolean> {
return new Promise((resolve) => {
this.get('/user/fetchByToken').query({ token: this.token })
.end((err, { status }) => resolve(status === 200));
});
}
async ensureLogin() {
if (await this.loggedIn) return true;
logger.info('retry login');
try {
const { body: { token } } = await this.post('/user/login')
.send({
username: this.account.handle,
password: /^[a-f0-9]{32}$/.test(this.account.password)
? this.account.password
: md5(`${this.account.password}yacs`),
});
this.token = token;
} catch (e) { }
return await this.loggedIn;
}
async getProblem(id: string) {
logger.info(id);
const { problem } = await this.getData(`/problem/${id.split('P')[1]}`);
const tag = [];
if (problem.level) tag.push(problem.level);
if (problem.contest) tag.push(problem.contest.name);
let content = '';
if (problem.background.trim()) content += `## 题目背景\n\n${problem.background.trim()}\n\n`;
content += `## 题目描述\n\n${problem.description.trim()}\n\n`;
content += `## 输入格式\n\n${problem.inputFormat.trim()}\n\n`;
content += `## 输出格式\n\n${problem.outputFormat.trim()}\n\n`;
content += problem.exampleList.map((sample, index) => {
const sampleId = index + 1;
let ret = '';
ret += `\`\`\`input${sampleId}\n${sample.input.trim()}\n\`\`\`\n\n`;
ret += `\`\`\`output${sampleId}\n${sample.output.trim()}\n\`\`\`\n\n`;
if (sample.note && sample.note.trim()) ret += `## 样例解释 ${sampleId}\n\n${sample.note.trim()}\n\n`;
return ret;
}).join('');
content += `## 数据范围\n\n${problem.dataRange.trim()}\n\n`;
if (problem.source.trim()) content += `## 来源\n\n${problem.source.trim()}\n\n`;
return {
title: problem.title,
data: {
'config.yaml': Buffer.from(yaml.dump({
time: `${problem.limitTime}ms`,
memory: `${problem.limitMemory}m`,
type: 'remote_judge',
subType: 'yacs',
target: id,
})),
},
files: {},
tag,
content,
};
}
async listProblem(page: number, resync = false) {
if (resync && page > 1) return [];
const data = await this.getData(`/problem?pi=${page}`);
return data.problemList
.filter((problem) => problem.contest.status === '榜单已公布')
.map((problem) => `P${problem.id}`);
}
async submitProblem(id: string, lang: string, code: string, info, next, end) {
if (!['C++', 'Python 3.6'].includes(lang)) {
end({ status: STATUS.STATUS_COMPILE_ERROR, message: `Language not supported: ${lang}` });
return null;
}
const { body } = await this.post('/submission/submit')
.set('Yacs-Token', this.token)
.send({
code,
language: lang,
problemId: +id.split('P')[1],
});
return `${body.id}`;
}
async waitForSubmission(id: string, next, end) {
const done = {};
// eslint-disable-next-line no-constant-condition
while (true) {
await sleep(3000);
const { submission } = await this.getData(`/submission/${id}`);
const status = VERDICT[submission.finalStatus];
if (status === STATUS.STATUS_COMPILE_ERROR) {
return await end({
status,
compilerText: submission.finalErrorInfo,
score: 0,
time: 0,
memory: 0,
});
}
const cases = [];
let taskId = 0;
for (const task of submission.finalTaskList) {
taskId++;
let pointId = 0;
for (const point of (task as any).dataPointList) {
pointId++;
if (done[`${taskId}.${pointId}`]) continue;
if (VERDICT[point.status] === STATUS.STATUS_JUDGING) continue;
done[`${taskId}.${pointId}`] = true;
cases.push({
id: pointId,
subtaskId: taskId,
score: point.scoreGet,
time: 0,
memory: 0,
status: VERDICT[point.status],
});
}
}
await next({
status,
score: submission.finalScoreGet,
cases,
});
if (status !== STATUS.STATUS_JUDGING) {
return await end({
status,
score: submission.finalScoreGet,
time: submission.finalTimeUsage,
memory: submission.finalMemoryUsage / 1024,
});
}
}
}
}
+10
View File
@@ -39,6 +39,16 @@ export const VERDICT = new Proxy<Record<string, STATUS>>({
SUBMIT_FAILED_PERM: STATUS.STATUS_SYSTEM_ERROR,
SUBMIT_FAILED_TEMP: STATUS.STATUS_SYSTEM_ERROR,
// YACS
正在评测: STATUS.STATUS_JUDGING,
答案正确: STATUS.STATUS_ACCEPTED,
编译失败: STATUS.STATUS_COMPILE_ERROR,
部分正确: STATUS.STATUS_WRONG_ANSWER,
运行超时: STATUS.STATUS_TIME_LIMIT_EXCEEDED,
内存超出: STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
暂未公布: STATUS.STATUS_SYSTEM_ERROR,
评测机故障: STATUS.STATUS_SYSTEM_ERROR,
// HustOJ
WAITING: STATUS.STATUS_WAITING,
等待: STATUS.STATUS_WAITING,