vjudge: draft: luogu

This commit is contained in:
undefined committed 2021-09-16 17:59:39 +08:00
1 parent 2bed36b012
commit d48adda992
4 files changed
+181 -2

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/vjudge",
"version": "1.3.1",
"version": "1.3.2",
"description": "Submit problems to remote oj",
"main": "package.json",
"repository": "https://github.com/hydro-dev/Hydro.git",
+1
View File
@@ -6,6 +6,7 @@ export interface RemoteAccount {
password: string;
endpoint?: string;
proxy?: string;
query?: string;
}
declare module 'hydrooj/src/interface' {
interface Collections {
+2 -1
View File
@@ -1,4 +1,5 @@
import codeforces from './codeforces';
import luogu from './luogu';
import spoj from './spoj';
export = { codeforces, spoj };
export = { codeforces, luogu, spoj };
+177
View File
@@ -0,0 +1,177 @@
/* eslint-disable no-await-in-loop */
import { JSDOM } from 'jsdom';
import { flattenDeep } from 'lodash';
import * as superagent from 'superagent';
import proxy from 'superagent-proxy';
import { STATUS } from '@hydrooj/utils/lib/status';
import { sleep } from '@hydrooj/utils/lib/utils';
import { Logger } from 'hydrooj/src/logger';
import * as setting from 'hydrooj/src/model/setting';
import { IBasicProvider, RemoteAccount } from '../interface';
proxy(superagent as any);
const logger = new Logger('remote/luogu');
const STATUS_MAP = [
STATUS.STATUS_WAITING,
STATUS.STATUS_JUDGING,
STATUS.STATUS_COMPILE_ERROR,
STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED,
STATUS.STATUS_MEMORY_LIMIT_EXCEEDED,
STATUS.STATUS_TIME_LIMIT_EXCEEDED,
STATUS.STATUS_WRONG_ANSWER,
STATUS.STATUS_RUNTIME_ERROR,
0,
0,
0,
STATUS.STATUS_SYSTEM_ERROR,
STATUS.STATUS_ACCEPTED,
0,
STATUS.STATUS_WRONG_ANSWER,
];
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36';
export default class LuoguProvider implements IBasicProvider {
constructor(public account: RemoteAccount, private save: (data: any) => Promise<void>) {
if (account.cookie) this.cookie = account.cookie;
}
cookie: string[] = [];
csrf: string;
get(url: string) {
logger.debug('get', url);
if (!url.includes('//')) url = `${this.account.endpoint || 'https://www.luogu.com.cn'}${url}`;
const req = superagent.get(url)
.set('Cookie', this.cookie)
.set('User-Agent', UA);
if (this.account.proxy) return req.proxy(this.account.proxy);
return req;
}
post(url: string) {
logger.debug('post', url, this.cookie);
if (!url.includes('//')) url = `${this.account.endpoint || 'https://www.luogu.com.cn'}${url}`;
const req = superagent.post(url)
.set('Cookie', this.cookie)
.set('x-csrf-token', this.csrf)
.set('User-Agent', UA)
.set('x-requested-with', 'XMLHttpRequest')
.set('origin', 'https://www.luogu.com.cn');
if (this.account.proxy) return req.proxy(this.account.proxy);
return req;
}
async getCsrfToken(url: string) {
const { text: html } = await this.get(url);
const $dom = new JSDOM(html);
this.csrf = $dom.window.document.querySelector('meta[name="csrf-token"]').getAttribute('content');
logger.info('csrf-token=', this.csrf);
}
get loggedIn() {
return this.get('/user/setting?_contentOnly=1').then(({ body }) => body.currentTemplate !== 'AuthLogin');
}
async ensureLogin() {
if (await this.loggedIn) {
this.getCsrfToken('/');
setInterval(() => this.getCsrfToken('/'), 5 * 60 * 1000);
return true;
}
logger.info('retry login');
// TODO login;
return false;
}
async getProblem(id: string) {
logger.info(id);
// TODO
return {
title: id,
data: {},
files: {},
tag: [],
content: '',
};
}
async listProblem() {
return [];
}
async submitProblem(id: string, lang: string, code: string, info) {
const comment = setting.langs[lang]?.comment;
if (comment) {
const msg = `Hydro submission #${info.rid}@${new Date().toLocaleString()}`;
if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`;
else if (comment instanceof Array) code = `${comment[0]} ${msg} ${comment[1]}\n${code}`;
}
lang = lang.includes('luogu.') ? lang.split('luogu.')[1] : '0';
const result = await this.post(`/fe/api/problem/submit/${id}${this.account.query || ''}`)
.set('referer', `https://www.luogu.com.cn/problem/${id}`)
// TODO O2
.send({ code, lang: +lang, enableO2: 0 });
logger.info('RecordID:', result.body.rid);
return result.body.rid;
}
async waitForSubmission(id: string, next, end) {
const done = {};
let fail = 0;
let count = 0;
while (count < 60 && fail < 5) {
await sleep(1500);
count++;
try {
const { body } = await this.get(`/record/${id}?_contentOnly=1`);
const data = body.currentData.record;
if (data.detail.compileResult && data.detail.compileResult.success === false) {
await next({ compilerText: data.detail.compileResult.message });
return await end({
status: STATUS.STATUS_COMPILE_ERROR, score: 0, time: 0, memory: 0,
});
}
logger.info('Fetched with length', JSON.stringify(body).length);
const total = flattenDeep(body.currentData.testCaseGroup).length;
// TODO sorted
for (const subtask of data.detail?.judgeResult?.subtasks || []) {
for (const cid of Object.keys(subtask.testCases)) {
if (done[`${subtask.id}.${cid}`]) continue;
done[`${subtask.id}.${cid}`] = true;
const testcase = subtask.testCases[cid];
await next({
status: STATUS.STATUS_JUDGING,
case: {
status: STATUS_MAP[testcase.status],
time: testcase.time,
memory: testcase.memory,
message: testcase.description,
},
progress: (Object.keys(done).length / total) * 100,
});
}
}
if (data.status < 2) continue;
logger.info('RecordID:', id, 'done');
// TODO calc total status
return await end({
status: STATUS_MAP[data.status],
score: STATUS_MAP[data.status] === STATUS.STATUS_ACCEPTED ? 100 : 0,
time: data.time,
memory: data.memory,
});
} catch (e) {
logger.error(e);
fail++;
}
}
return await end({
status: STATUS.STATUS_SYSTEM_ERROR,
score: 0,
time: 0,
memory: 0,
});
}
}