vjudge: add poj support

This commit is contained in:
undefined committed 2021-10-09 22:03:34 +08:00
1 parent 554287d5a6
commit a3c729cebd
7 files changed
+289 -21

No files matched your search

+11 -2
View File
@@ -171,14 +171,14 @@ export function parseTimeMS(str: string | number) {
if (typeof str === 'number') return str;
const match = TIME_RE.exec(str);
if (!match) throw new Error(`${str} error parsing time`);
return Math.floor(parseFloat(match[1]) * TIME_UNITS[match[2]]);
return Math.floor(parseFloat(match[1]) * TIME_UNITS[match[2].toLowerCase()]);
}
export function parseMemoryMB(str: string | number) {
if (typeof str === 'number') return str;
const match = MEMORY_RE.exec(str);
if (!match) throw new Error(`${str} error parsing memory`);
return Math.ceil(parseFloat(match[1]) * MEMORY_UNITS[match[2]]);
return Math.ceil(parseFloat(match[1]) * MEMORY_UNITS[match[2].toLowerCase()]);
}
export function isClass(obj: any, strict = false) {
@@ -432,3 +432,12 @@ export function sortFiles(files: { _id: string }[] | string[]) {
});
return isString ? result.map((x) => x.name) : result;
}
export const htmlEncode = (str) => str.replace(/[&<>'"]/g,
(tag) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;',
}[tag]));
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/utils",
"version": "1.1.4",
"version": "1.1.5",
"description": "hydrooj utils",
"main": "package.json",
"repository": "https://github.com/hydro-dev/Hydro.git",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/vjudge",
"version": "1.3.11",
"version": "1.4.0",
"description": "Submit problems to remote oj",
"main": "package.json",
"repository": "https://github.com/hydro-dev/Hydro.git",
+2 -1
View File
@@ -1,8 +1,9 @@
import codeforces from './codeforces';
import luogu from './luogu';
import poj from './poj';
import spoj from './spoj';
import uoj from './uoj';
export = {
codeforces, luogu, spoj, uoj,
codeforces, luogu, poj, spoj, uoj,
};
+254
View File
@@ -0,0 +1,254 @@
/* eslint-disable no-await-in-loop */
import { PassThrough } from 'stream';
import { JSDOM } from 'jsdom';
import * as superagent from 'superagent';
import proxy from 'superagent-proxy';
import { STATUS } from '@hydrooj/utils/lib/status';
import {
htmlEncode, parseMemoryMB, parseTimeMS, 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';
import { VERDICT } from '../verdict';
proxy(superagent as any);
const logger = new Logger('remote/poj');
/* langs
poj:
display: POJ
execute: /bin/echo Invalid
domain:
- poj
poj.0:
display: G++
monaco: cpp
highlight: cpp astyle-c
comment: //
poj.1:
display: GCC
monaco: c
highlight: c astyle-c
comment: //
poj.2:
display: Java
monaco: java
highlight: java astyle-java
comment: //
poj.3:
display: Pascal
monaco: pascal
highlight: pascal
comment: //
poj.4:
display: C++
monaco: cpp
highlight: cpp astyle-c
comment: //
poj.5:
display: C
monaco: c
highlight: c astyle-c
comment: //
poj.6:
display: Fortran
monaco: plain
highlight: plain
*/
const langs = {
default: 'en',
'zh-CN': 'zh_CN',
es: 'es',
ja: 'ja',
};
export default class POJProvider implements IBasicProvider {
constructor(public account: RemoteAccount, private save: (data: any) => Promise<void>) {
if (account.cookie) this.cookie = account.cookie;
}
cookie: string[] = [];
get(url: string) {
logger.debug('get', url);
if (!url.includes('//')) url = `${this.account.endpoint || 'http://poj.org'}${url}`;
const req = superagent.get(url).set('Cookie', this.cookie);
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 || 'http://poj.org'}${url}`;
const req = superagent.post(url).set('Cookie', this.cookie).type('form');
if (this.account.proxy) return req.proxy(this.account.proxy);
return req;
}
async getCsrfToken(url: string) {
const { header } = await this.get(url);
if (header['set-cookie']) {
await this.save({ cookie: header['set-cookie'] });
this.cookie = header['set-cookie'];
}
return '';
}
get loggedIn() {
return this.get('/submit?problem_id=1000').then(({ text: html }) => !html.includes('<form method=POST action=login>'));
}
async ensureLogin() {
if (await this.loggedIn) return true;
logger.info('retry login');
await this.getCsrfToken('/');
const res = await this.post('/login')
.set('referer', 'http://poj.org/')
.send({
user_id1: this.account.handle,
password1: this.account.password,
B1: 'login',
url: '/',
});
return this.loggedIn;
}
async getProblem(id: string) {
logger.info(id);
const res = await this.get(`/problem?id=${id.split('P')[1]}`);
const { window: { document } } = new JSDOM(res.text);
const files = {};
const main = document.querySelector('[background="images/table_back.jpg"]>tbody>tr>td');
const languages = [...main.children[0].children[0].children]
.map((i) => i.getAttribute('value'));
const info = main.getElementsByClassName('plm')[0]
.children[0].children[0].children[0];
const time = info.children[0].innerHTML.split('</b> ')[1].toLowerCase().trim();
const memory = info.children[2].innerHTML.split('</b> ')[1].toLowerCase().trim();
const contents = {};
const images = {};
for (const lang of languages) {
await sleep(1000);
const { text } = await this.get(`/problem?id=${id.split('P')[1]}&lang=${lang}&change=true`);
const { window: { document: page } } = new JSDOM(text);
const content = page.querySelector('[background="images/table_back.jpg"]>tbody>tr>td');
content.children[0].remove();
content.children[0].remove();
content.children[0].remove();
content.querySelectorAll('img[src]').forEach((ele) => {
const src = ele.getAttribute('src');
if (!src.startsWith('http')) return;
if (images[src]) {
ele.setAttribute('src', `file://${images[src]}.png`);
return;
}
const file = new PassThrough();
this.get(src).pipe(file);
const fid = String.random(8);
images[src] = fid;
files[`${fid}.png`] = file;
ele.setAttribute('src', `file://${fid}.png`);
});
let lastId = 0;
let markNext = '';
let html = '';
for (const node of content.children) {
if (node.className.includes('pst')) {
if (!node.innerHTML.startsWith('Sample ')) {
html += `<h2>${htmlEncode(node.innerHTML)}</h2>`;
} else if (node.innerHTML.startsWith('Sample Input')) {
lastId++;
markNext = 'input';
} else {
markNext = 'output';
}
} else if (node.className.includes('sio')) {
html += `<pre><code class="language-${markNext}${lastId}">${htmlEncode(node.innerHTML)}</code></pre>`;
} else if (node.className.includes('ptx')) {
for (const item of node.childNodes) {
if (item.nodeType === 3) {
const p = page.createElement('p');
p.innerHTML = htmlEncode(item.textContent);
item.replaceWith(p);
}
if (item.nodeName.includes('BR')) item.remove();
}
html += node.innerHTML;
} else html += node.innerHTML;
}
contents[langs[lang]] = html;
}
return {
title: main.getElementsByClassName('ptt')[0].innerHTML,
data: {
'config.yaml': Buffer.from(`time: ${time}\nmemory: ${memory}\ntype: remote_judge\nsubType: poj\ntarget: ${id}`),
},
files,
tag: [],
content: JSON.stringify(contents),
};
}
async listProblem(page: number, resync = false) {
if (resync && page > 1) return [];
const { text } = await this.get(`/problemlist?volume=${page}`);
const $dom = new JSDOM(text);
return Array.from($dom.window.document.querySelectorAll('.a>tbody>tr[align="center"]'))
.map((i) => `P${+i.children[0].innerHTML ? i.children[0].innerHTML : i.children[1].innerHTML}`);
}
async submitProblem(id: string, lang: string, code: string, info) {
const language = lang.includes('poj.') ? lang.split('poj.')[1] : '0';
const comment = setting.langs[lang].comment;
if (comment) {
const msg = `Hydro submission #${info.rid}@${new Date().getTime()}`;
if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`;
else if (comment instanceof Array) code = `${comment[0]} ${msg} ${comment[1]}\n${code}`;
}
code = Buffer.from(code).toString('base64');
const { text } = await this.post('/submit').send({
problem_id: id.split('P')[1],
language,
source: code,
submit: 'Submit',
encoded: 1,
});
if (text.includes('Error Occurred')) {
throw new Error(text.split('<li>')[1].split('</li>')[0]);
}
const { text: status } = await this.get(`/status?problem_id=${id.split('P')[1]}&user_id=${this.account.handle}&result=&language=${language}`);
const $dom = new JSDOM(status);
return $dom.window.document.querySelector('.a>tbody>tr[align="center"]>td').innerHTML;
}
// eslint-disable-next-line consistent-return
async waitForSubmission(id: string, next, end) {
let count = 0;
// eslint-disable-next-line no-constant-condition
while (count < 60) {
count++;
await sleep(3000);
const { text } = await this.get(`/status?top=${+id + 1}`);
const { window: { document } } = new JSDOM(text);
const submission = document.querySelector('.a>tbody>tr[align="center"]');
const status = VERDICT[submission.children[3].children[0].innerHTML.toUpperCase()]
|| STATUS.STATUS_SYSTEM_ERROR;
if (status === STATUS.STATUS_JUDGING) continue;
if (status === STATUS.STATUS_COMPILE_ERROR) {
const { text: info } = await this.get(`/showcompileinfo?solution_id=${id}`);
const ceInfo = new JSDOM(info);
await next({ compilerText: ceInfo.window.document.querySelector('pre>font').innerHTML });
}
const memory = parseMemoryMB(submission.children[4].innerHTML.trim() || 0) * 1024;
const time = parseTimeMS(submission.children[5].innerHTML.trim() || 0);
return await end({
status,
score: status === STATUS.STATUS_ACCEPTED ? 100 : 0,
time,
memory,
});
}
}
}
+1 -16
View File
@@ -8,6 +8,7 @@ import { parseMemoryMB, parseTimeMS, 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';
import { VERDICT } from '../verdict';
proxy(superagent as any);
const logger = new Logger('remote/uoj');
@@ -24,22 +25,6 @@ const MAPPING = {
十: 10,
};
const VERDICT = {
RUNTIME_ERROR: STATUS.STATUS_RUNTIME_ERROR,
WRONG_ANSWER: STATUS.STATUS_WRONG_ANSWER,
OK: STATUS.STATUS_ACCEPTED,
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,
'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,
'EXTRA TEST PASSED': STATUS.STATUS_ACCEPTED,
};
export default class UOJProvider implements IBasicProvider {
constructor(public account: RemoteAccount, private save: (data: any) => Promise<void>) {
if (account.cookie) this.cookie = account.cookie;
+19
View File
@@ -0,0 +1,19 @@
import { STATUS } from '@hydrooj/utils/lib/status';
export const VERDICT = {
RUNTIME_ERROR: STATUS.STATUS_RUNTIME_ERROR,
WRONG_ANSWER: STATUS.STATUS_WRONG_ANSWER,
OK: STATUS.STATUS_ACCEPTED,
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,
'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,
'EXTRA TEST PASSED': STATUS.STATUS_ACCEPTED,
'COMPILE ERROR': STATUS.STATUS_COMPILE_ERROR,
'RUNNING & JUDGING': STATUS.STATUS_JUDGING,
};