mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
1 parent
c2cef76663
commit
4881165730
8 files changed
+191
-8
No files matched your search
@@ -54,7 +54,10 @@ const problemConfigSchema = {
|
||||
def: problemConfigSchemaDef,
|
||||
properties: {
|
||||
key: { type: 'string', pattern: '[0-9a-f]{32}' },
|
||||
type: { enum: ['default', 'interactive', 'submit_answer', 'subjective'] },
|
||||
type: { enum: ['default', 'interactive', 'submit_answer', 'subjective', 'remote_judge'] },
|
||||
subType: { type: 'string' },
|
||||
langs: { type: 'array', items: { type: 'string' } },
|
||||
target: { type: 'string' },
|
||||
checker_type: { enum: ['default', 'lemon', 'syzoj', 'testlib', 'strict', 'qduoj'] },
|
||||
checker: { type: 'string', pattern: '\\.' },
|
||||
interactor: { type: 'string', pattern: '\\.' },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hydrooj/ui-default",
|
||||
"version": "4.12.3",
|
||||
"version": "4.12.4",
|
||||
"author": "undefined <i@undefined.moe>",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "hydro.js",
|
||||
|
||||
@@ -30,9 +30,8 @@ const page = new NamedPage(['problem_submit', 'contest_detail_problem_submit', '
|
||||
}
|
||||
}
|
||||
const main = {};
|
||||
const prefix = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0]));
|
||||
for (const key in window.LANGS) {
|
||||
if (UiContext.pdocConfig.langs && !prefix.has(key) && !UiContext.pdocConfig.langs.includes(key)) continue;
|
||||
if (UiContext.pdocConfig.langs && !UiContext.pdocConfig.langs.filter((i) => i === key || i.startsWith(`${key}.`)).length) continue;
|
||||
if (window.LANGS[key].domain && !window.LANGS[key].domain.includes(UiContext.domainId)) continue;
|
||||
if (UiContext.domain.langs && !`,${UiContext.domain.langs},`.includes(`,${key},`)) continue;
|
||||
if (!key.includes('.')) main[key] = window.LANGS[key].display;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hydrooj/vjudge",
|
||||
"version": "1.0.15",
|
||||
"version": "1.1.0",
|
||||
"description": "Submit problems to remote oj",
|
||||
"main": "package.json",
|
||||
"repository": "https://github.com/hydro-dev/Hydro.git",
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface IBasicProvider {
|
||||
content: string;
|
||||
}>;
|
||||
listProblem(page: number, resync: boolean): Promise<string[]>;
|
||||
submitProblem(id: string, lang: string, code: string, info): Promise<string>;
|
||||
submitProblem(id: string, lang: string, code: string, info: any, next: any, end: any): Promise<string>;
|
||||
waitForSubmission(id: string, next: any, end: any): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as Judge from 'hydrooj/dist/handler/judge';
|
||||
import TaskModel from 'hydrooj/dist/model/task';
|
||||
import DomainModel from 'hydrooj/dist/model/domain';
|
||||
import ProblemModel from 'hydrooj/dist/model/problem';
|
||||
import { STATUS } from 'hydrooj/dist/model/builtin';
|
||||
import { sleep } from '@hydrooj/utils/lib/utils';
|
||||
import providers from './providers/index';
|
||||
import { BasicProvider, IBasicProvider, RemoteAccount } from './interface';
|
||||
@@ -26,9 +27,12 @@ class Service {
|
||||
}
|
||||
|
||||
async judge(task) {
|
||||
const rid = await this.api.submitProblem(task.target, task.lang, task.code, task);
|
||||
const next = (payload) => Judge.next({ ...payload, rid: task.rid });
|
||||
const end = (payload) => Judge.end({ ...payload, rid: task.rid });
|
||||
await next({ status: STATUS.STATUS_FETCHED });
|
||||
const rid = await this.api.submitProblem(task.target, task.lang, task.code, task, next, end);
|
||||
if (!rid) return;
|
||||
await next({ status: STATUS.STATUS_JUDGING });
|
||||
await this.api.waitForSubmission(rid, next, end);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import codeforces from './codeforces';
|
||||
import spoj from './spoj';
|
||||
|
||||
export = { codeforces };
|
||||
export = { codeforces, spoj };
|
||||
@@ -0,0 +1,176 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import * as superagent from 'superagent';
|
||||
import proxy from 'superagent-proxy';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { PassThrough } from 'stream';
|
||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||
import { parseMemoryMB, sleep } from '@hydrooj/utils/lib/utils';
|
||||
import * as setting from 'hydrooj/dist/model/setting';
|
||||
import { Logger } from 'hydrooj/dist/logger';
|
||||
import { IBasicProvider, RemoteAccount } from '../interface';
|
||||
|
||||
proxy(superagent);
|
||||
const logger = new Logger('spoj');
|
||||
|
||||
const VERDICT = {
|
||||
11: STATUS.STATUS_COMPILE_ERROR,
|
||||
12: STATUS.STATUS_RUNTIME_ERROR,
|
||||
13: STATUS.STATUS_TIME_LIMIT_EXCEEDED,
|
||||
14: STATUS.STATUS_WRONG_ANSWER,
|
||||
15: STATUS.STATUS_ACCEPTED,
|
||||
};
|
||||
|
||||
export default class SPOJProvider 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 || 'https://www.spoj.com'}${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 || 'https://www.spoj.com'}${url}`;
|
||||
const req = superagent.post(url).type('form').set('Cookie', this.cookie);
|
||||
if (this.account.proxy) return req.proxy(this.account.proxy);
|
||||
return req;
|
||||
}
|
||||
|
||||
get loggedIn() {
|
||||
return this.get('/').then(({ text: html }) => {
|
||||
const $dom = new JSDOM(html);
|
||||
if ($dom.window.document.querySelectorAll('span.fa.fa-sign-out.fa-fw').length) return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
async ensureLogin() {
|
||||
if (await this.loggedIn) return true;
|
||||
logger.info('retry login');
|
||||
const res = await this.post('/login/').send({
|
||||
next_raw: '/',
|
||||
autologin: 1,
|
||||
login_user: this.account.handle,
|
||||
password: this.account.password,
|
||||
});
|
||||
console.log(res.body, res.header);
|
||||
const cookie = res.header['set-cookie'];
|
||||
if (cookie) {
|
||||
await this.save({ cookie });
|
||||
this.cookie = cookie;
|
||||
}
|
||||
if (await this.loggedIn) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
async getProblem(id: string) {
|
||||
logger.info(id);
|
||||
const { text } = await this.get(`/problems/${id}/`);
|
||||
const { window: { document } } = new JSDOM(text);
|
||||
const files = {};
|
||||
document.querySelector('#problem-body').querySelectorAll('img[src]').forEach((ele) => {
|
||||
const src = ele.getAttribute('src');
|
||||
if (!src.startsWith('http')) return;
|
||||
const file = new PassThrough();
|
||||
this.get(src).pipe(file);
|
||||
const fid = String.random(8);
|
||||
files[`${fid}.png`] = file;
|
||||
ele.setAttribute('src', `file://${fid}.png`);
|
||||
});
|
||||
const meta = document.querySelector('#problem-meta').children[1];
|
||||
const { text: submit } = await this.get(`/submit/${id}/`);
|
||||
const $dom = new JSDOM(submit);
|
||||
const langs = Array.from($dom.window.document.querySelector('#lang').querySelectorAll('option'))
|
||||
.map((i) => `spoj.${i.getAttribute('value')}`);
|
||||
let time = meta.children[2].children[1].innerHTML.trim().toLowerCase();
|
||||
if (time.includes('-')) time = time.split('-')[1];
|
||||
let memory = meta.children[4].children[1].innerHTML.trim().toLowerCase();
|
||||
if (memory.includes('-')) memory = memory.split('-')[1];
|
||||
return {
|
||||
title: document.querySelector('#problem-name').innerHTML.trim().split(`${id} - `)[1],
|
||||
data: {
|
||||
'config.yaml': Buffer.from(`time: ${time}
|
||||
memory: ${memory}
|
||||
type: remote_judge
|
||||
subType: spoj
|
||||
target: ${id}
|
||||
langs: ${JSON.stringify(langs)}`),
|
||||
},
|
||||
files,
|
||||
tag: Array.from(document.querySelectorAll('[data-tagid]')).map((i) => i.childNodes[0].nodeValue.substr(1, 999)),
|
||||
content: document.querySelector('#problem-body').innerHTML.trim(),
|
||||
};
|
||||
}
|
||||
|
||||
async listProblem(page: number, resync = false) {
|
||||
if (resync) return [];
|
||||
const res = await this.get(`/problems/classical/sort=0,start=${page * 50 - 50}`);
|
||||
const { window: { document } } = new JSDOM(res.text);
|
||||
const index = document.querySelector('ul.pagination').querySelector('li.active').children[0].innerHTML.trim();
|
||||
if (index !== page.toString()) return [];
|
||||
return Array.from(document.querySelectorAll('td[align=left]')).map((i) => i.children[0].getAttribute('href').split('/problems/')[1]);
|
||||
}
|
||||
|
||||
async submitProblem(problemcode: string, lang: string, code: string, info, next, end) {
|
||||
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}`;
|
||||
}
|
||||
// TODO check submit time to ensure submission
|
||||
const { text } = await this.post('/submit/complete/').send({
|
||||
submit: 'Submit!',
|
||||
lang: lang.split('spoj.')[1] || '44',
|
||||
problemcode,
|
||||
file: code,
|
||||
});
|
||||
if (text.includes('Wrong problem code!')) {
|
||||
await next({ message: 'Wrong problem code!' });
|
||||
await end({ status: STATUS.STATUS_COMPILE_ERROR });
|
||||
return null;
|
||||
}
|
||||
const { window } = new JSDOM(text);
|
||||
return window.document.querySelector('[name="newSubmissionId"]').getAttribute('value');
|
||||
}
|
||||
|
||||
async waitForSubmission(id: string, next, end) {
|
||||
logger.debug('Waiting for %s', id);
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
await sleep(3000);
|
||||
const { text } = await this.get(`/status/ns=${id}`);
|
||||
if (!text.includes(id)) {
|
||||
return await end({
|
||||
status: STATUS.STATUS_ETC,
|
||||
score: 0,
|
||||
time: 0,
|
||||
memory: 0,
|
||||
});
|
||||
}
|
||||
const { window: { document } } = new JSDOM(text);
|
||||
if (!document.querySelector(`#statusres_${id}[final='1']`)) continue;
|
||||
const status = VERDICT[document.querySelector(`#statusres_${id}`).getAttribute('status')] || STATUS.STATUS_WRONG_ANSWER;
|
||||
const timestr = document.querySelector(`#statustime_${id}`).children[0].innerHTML.trim();
|
||||
const time = timestr === '-' ? 0 : (+timestr) * 1000;
|
||||
const memorystr = document.querySelector(`#statusmem_${id}`).innerHTML.trim().toLowerCase();
|
||||
const memory = memorystr === '-' ? 0 : parseMemoryMB(memorystr) / 1024;
|
||||
await next({
|
||||
message: document.querySelector(`#statusres_${id}`).childNodes[0].nodeValue.trim(),
|
||||
});
|
||||
return await end({
|
||||
status,
|
||||
score: status === STATUS.STATUS_ACCEPTED ? 100 : 0,
|
||||
time,
|
||||
memory,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user