mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-08 13:38:41 +00:00
feat(remote_judger): add luogu
This commit is contained in:
parent
7bc29d60c9
commit
5cfd8d5846
@ -1,8 +1,8 @@
|
|||||||
export interface RemoteAccount {
|
export interface RemoteAccount {
|
||||||
type: string;
|
type: string;
|
||||||
cookie?: string[];
|
cookie?: string[];
|
||||||
handle: string;
|
handle?: string;
|
||||||
password: string;
|
password?: string;
|
||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
proxy?: string;
|
proxy?: string;
|
||||||
}
|
}
|
||||||
|
281
remote_judger/src/providers/luogu.ts
Normal file
281
remote_judger/src/providers/luogu.ts
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
import { JSDOM } from 'jsdom';
|
||||||
|
import superagent from 'superagent';
|
||||||
|
import proxy from 'superagent-proxy';
|
||||||
|
import Logger from '../utils/logger';
|
||||||
|
import { IBasicProvider, RemoteAccount, USER_AGENT } from '../interface';
|
||||||
|
import sleep from '../utils/sleep';
|
||||||
|
import flattenDeep from '../utils/flattenDeep';
|
||||||
|
|
||||||
|
proxy(superagent);
|
||||||
|
const logger = new Logger('remote/luogu');
|
||||||
|
|
||||||
|
const STATUS_MAP = [
|
||||||
|
'Waiting', // WAITING,
|
||||||
|
'Judging', // JUDGING,
|
||||||
|
'Compile Error', // CE
|
||||||
|
'Output Limit Exceeded', // OLE
|
||||||
|
'Memory Limit Exceeded', // MLE
|
||||||
|
'Time Limit Exceeded', // TLE
|
||||||
|
'Wrong Answer', // WA
|
||||||
|
'Runtime Error', // RE
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
'Judgment Failed', // UKE
|
||||||
|
'Accepted', // AC
|
||||||
|
0,
|
||||||
|
'Wrong Answer', // WA
|
||||||
|
];
|
||||||
|
|
||||||
|
const LANGS_MAP = {
|
||||||
|
C: {
|
||||||
|
id: 2,
|
||||||
|
name: 'C',
|
||||||
|
comment: '//',
|
||||||
|
},
|
||||||
|
'C++98': {
|
||||||
|
id: 3,
|
||||||
|
name: 'C++98',
|
||||||
|
comment: '//',
|
||||||
|
},
|
||||||
|
'C++11': {
|
||||||
|
id: 4,
|
||||||
|
name: 'C++11',
|
||||||
|
comment: '//',
|
||||||
|
},
|
||||||
|
'C++': {
|
||||||
|
id: 11,
|
||||||
|
name: 'C++14',
|
||||||
|
comment: '//',
|
||||||
|
},
|
||||||
|
'C++17': {
|
||||||
|
id: 12,
|
||||||
|
name: 'C++17',
|
||||||
|
comment: '//',
|
||||||
|
},
|
||||||
|
'C++20': {
|
||||||
|
id: 27,
|
||||||
|
name: 'C++20',
|
||||||
|
comment: '//',
|
||||||
|
},
|
||||||
|
Python3: {
|
||||||
|
id: 7,
|
||||||
|
name: 'Python 3',
|
||||||
|
comment: '#',
|
||||||
|
},
|
||||||
|
Java8: {
|
||||||
|
id: 8,
|
||||||
|
name: 'Java 8',
|
||||||
|
comment: '//',
|
||||||
|
},
|
||||||
|
Pascal: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Pascal',
|
||||||
|
comment: '//',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class LuoguProvider implements IBasicProvider {
|
||||||
|
constructor(public account: RemoteAccount) {
|
||||||
|
if (account.cookie) this.cookie = account.cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constructFromAccountData(data) {
|
||||||
|
return new this({
|
||||||
|
type: 'luogu',
|
||||||
|
cookie: Object.entries(data).map(([key, value]) => `${key}=${value}`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie: string[] = [];
|
||||||
|
csrf: string;
|
||||||
|
|
||||||
|
get(url: string) {
|
||||||
|
logger.debug('get', url, this.cookie);
|
||||||
|
|
||||||
|
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', USER_AGENT);
|
||||||
|
|
||||||
|
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', USER_AGENT)
|
||||||
|
.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) {
|
||||||
|
await this.getCsrfToken('/user/setting');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('retry login');
|
||||||
|
|
||||||
|
// TODO login;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async submitProblem(
|
||||||
|
id: string,
|
||||||
|
lang: string,
|
||||||
|
code: string,
|
||||||
|
submissionId: number,
|
||||||
|
next,
|
||||||
|
end
|
||||||
|
) {
|
||||||
|
if (!(await this.ensureLogin())) {
|
||||||
|
end({
|
||||||
|
error: true,
|
||||||
|
status: 'Judgment Failed',
|
||||||
|
message: 'Login failed',
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code.length < 10) {
|
||||||
|
end({
|
||||||
|
error: true,
|
||||||
|
status: 'Compile Error',
|
||||||
|
message: 'Code too short',
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const programType = LANGS_MAP[lang] || LANGS_MAP['C++'];
|
||||||
|
const comment = programType.comment;
|
||||||
|
|
||||||
|
if (comment) {
|
||||||
|
const msg = `S2OJ Submission #${submissionId} @ ${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}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.post(`/fe/api/problem/submit/${id}`)
|
||||||
|
.set('referer', `https://www.luogu.com.cn/problem/${id}`)
|
||||||
|
.send({
|
||||||
|
code,
|
||||||
|
lang: programType.id,
|
||||||
|
enableO2: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('RecordID:', result.body.rid);
|
||||||
|
|
||||||
|
return result.body.rid;
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitForSubmission(problem_id: string, id: string, next, end) {
|
||||||
|
const done = {};
|
||||||
|
let fail = 0;
|
||||||
|
let count = 0;
|
||||||
|
let finished = 0;
|
||||||
|
|
||||||
|
while (count < 120 && 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
|
||||||
|
) {
|
||||||
|
return await end({
|
||||||
|
error: true,
|
||||||
|
id,
|
||||||
|
status: 'Compile Error',
|
||||||
|
message: data.detail.compileResult.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('Fetched with length', JSON.stringify(body).length);
|
||||||
|
const total = flattenDeep(body.currentData.testCaseGroup).length;
|
||||||
|
|
||||||
|
// TODO sorted
|
||||||
|
|
||||||
|
if (!data.detail.judgeResult?.subtasks) continue;
|
||||||
|
|
||||||
|
for (const key in data.detail.judgeResult.subtasks) {
|
||||||
|
const subtask = data.detail.judgeResult.subtasks[key];
|
||||||
|
for (const cid in subtask.testCases || {}) {
|
||||||
|
if (done[`${subtask.id}.${cid}`]) continue;
|
||||||
|
finished++;
|
||||||
|
done[`${subtask.id}.${cid}`] = true;
|
||||||
|
await next({
|
||||||
|
status: `Judging (${(finished / total) * 100})`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.status < 2) continue;
|
||||||
|
|
||||||
|
logger.info('RecordID:', id, 'done');
|
||||||
|
|
||||||
|
// TODO calc total status
|
||||||
|
|
||||||
|
return await end({
|
||||||
|
id: 'R' + id,
|
||||||
|
status: STATUS_MAP[data.status],
|
||||||
|
score: data.score,
|
||||||
|
time: data.time,
|
||||||
|
memory: data.memory,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
|
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await end({
|
||||||
|
error: true,
|
||||||
|
status: 'Judgment Failed',
|
||||||
|
message: 'Failed to fetch submission details.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
6
remote_judger/src/utils/flattenDeep.ts
Normal file
6
remote_judger/src/utils/flattenDeep.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const flattenDeep = arr =>
|
||||||
|
Array.isArray(arr)
|
||||||
|
? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
|
||||||
|
: [arr];
|
||||||
|
|
||||||
|
export default flattenDeep;
|
@ -139,8 +139,9 @@ class VJudge {
|
|||||||
details:
|
details:
|
||||||
payload.details ||
|
payload.details ||
|
||||||
'<div>' +
|
'<div>' +
|
||||||
`<info-block>ID = ${payload.id || 'None'}</info-block>` +
|
`<info-block>REMOTE_SUBMISSION_ID = ${
|
||||||
`<info-block>VERDICT = ${payload.status}</info-block>` +
|
payload.id || 'None'
|
||||||
|
}\nVERDICT = ${payload.status}</info-block>` +
|
||||||
'</div>',
|
'</div>',
|
||||||
}),
|
}),
|
||||||
judge_time,
|
judge_time,
|
||||||
@ -187,13 +188,13 @@ class VJudge {
|
|||||||
message: e.message,
|
message: e.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Unsupported remote submit type: ' + config.remote_submit_type
|
'Unsupported remote submit type: ' + config.remote_submit_type
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function apply(request: any) {
|
export async function apply(request: any) {
|
||||||
const vjudge = new VJudge(request);
|
const vjudge = new VJudge(request);
|
||||||
@ -202,6 +203,7 @@ export async function apply(request: any) {
|
|||||||
await vjudge.addProvider('atcoder');
|
await vjudge.addProvider('atcoder');
|
||||||
await vjudge.addProvider('uoj');
|
await vjudge.addProvider('uoj');
|
||||||
await vjudge.addProvider('loj');
|
await vjudge.addProvider('loj');
|
||||||
|
await vjudge.importProvider('luogu');
|
||||||
|
|
||||||
return vjudge;
|
return vjudge;
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,9 @@ function handleUpload($zip_file_name, $content, $tot_size) {
|
|||||||
|
|
||||||
if (UOJProblem::info('type') == 'remote') {
|
if (UOJProblem::info('type') == 'remote') {
|
||||||
$submit_type = in_array($_POST['answer_remote_submit_type'], $remote_provider['submit_type']) ? $_POST['answer_remote_submit_type'] : $remote_provider['submit_type'][0];
|
$submit_type = in_array($_POST['answer_remote_submit_type'], $remote_provider['submit_type']) ? $_POST['answer_remote_submit_type'] : $remote_provider['submit_type'][0];
|
||||||
|
if ($submit_type != 'bot') {
|
||||||
$content['no_rejudge'] = true;
|
$content['no_rejudge'] = true;
|
||||||
|
}
|
||||||
$content['config'][] = ['remote_submit_type', $submit_type];
|
$content['config'][] = ['remote_submit_type', $submit_type];
|
||||||
$content['config'][] = ['remote_account_data', $_POST['answer_remote_account_data']];
|
$content['config'][] = ['remote_account_data', $_POST['answer_remote_account_data']];
|
||||||
}
|
}
|
||||||
|
@ -989,9 +989,9 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (oj == 'luogu') {
|
if (oj == 'luogu') {
|
||||||
var luogu_account_data = {"_uid": "", "__clientid": ""};
|
var luogu_account_data = {"_uid": "", "__client_id": ""};
|
||||||
var input_luogu_uid = $('<input class="form-control" type="text" name="luogu_uid" id="input-luogu_uid" />');
|
var input_luogu_uid = $('<input class="form-control" type="text" name="luogu_uid" id="input-luogu_uid" />');
|
||||||
var input_luogu_clientid = $('<input class="form-control" type="text" name="luogu_clientid" id="input-luogu_clientid" />');
|
var input_luogu_client_id = $('<input class="form-control" type="text" name="luogu_client_id" id="input-luogu_client_id" />');
|
||||||
|
|
||||||
if ('localStorage' in window) {
|
if ('localStorage' in window) {
|
||||||
try {
|
try {
|
||||||
@ -1014,13 +1014,15 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
|
|||||||
save_luogu_account_data();
|
save_luogu_account_data();
|
||||||
});
|
});
|
||||||
|
|
||||||
input_luogu_clientid.change(function() {
|
input_luogu_client_id.change(function() {
|
||||||
luogu_account_data.__clientid = $(this).val();
|
luogu_account_data.__client_id = $(this).val();
|
||||||
input_my_account_data.val(JSON.stringify(luogu_account_data));
|
input_my_account_data.val(JSON.stringify(luogu_account_data));
|
||||||
save_luogu_account_data();
|
save_luogu_account_data();
|
||||||
});
|
});
|
||||||
|
|
||||||
input_my_account_data.val(JSON.stringify(luogu_account_data));
|
input_my_account_data.val(JSON.stringify(luogu_account_data));
|
||||||
|
input_luogu_uid.val(luogu_account_data._uid);
|
||||||
|
input_luogu_client_id.val(luogu_account_data.__client_id);
|
||||||
|
|
||||||
div_submit_type_my.append(
|
div_submit_type_my.append(
|
||||||
$('<div class="row mt-3" />')
|
$('<div class="row mt-3" />')
|
||||||
@ -1029,9 +1031,9 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
|
|||||||
.append($('<div class="col-sm-6" />').append($('<div class="form-text" />').append('请填入 Cookie 中的 <code>_uid</code>。')))
|
.append($('<div class="col-sm-6" />').append($('<div class="form-text" />').append('请填入 Cookie 中的 <code>_uid</code>。')))
|
||||||
).append(
|
).append(
|
||||||
$('<div class="row mt-3" />')
|
$('<div class="row mt-3" />')
|
||||||
.append($('<div class="col-sm-2" />').append('<label for="input-luogu_clientid" class="form-col-label">__clientid</label>'))
|
.append($('<div class="col-sm-2" />').append('<label for="input-luogu_client_id" class="form-col-label">__client_id</label>'))
|
||||||
.append($('<div class="col-sm-4" />').append(input_luogu_clientid))
|
.append($('<div class="col-sm-4" />').append(input_luogu_client_id))
|
||||||
.append($('<div class="col-sm-6" />').append($('<div class="form-text" />').append('请填入 Cookie 中的 <code>__clientid</code>。')))
|
.append($('<div class="col-sm-6" />').append($('<div class="form-text" />').append('请填入 Cookie 中的 <code>__client_id</code>。')))
|
||||||
).append(input_my_account_data);
|
).append(input_my_account_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user