feat(remote_judger/loj): fetch submission from archive

This commit is contained in:
Baoshuo Ren 2023-02-06 09:30:21 +08:00
parent 170120157d
commit 7794800603
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
8 changed files with 300 additions and 30 deletions

View File

@ -10,7 +10,7 @@ import { crlf, LF } from 'crlf-normalize';
proxy(superagent);
const logger = new Logger('remote/loj');
const langs_map = {
const LANGS_MAP = {
C: {
name: 'C (gcc, c11, O2, m64)',
info: {
@ -157,7 +157,11 @@ export default class LibreojProvider implements IBasicProvider {
}
static constructFromAccountData(data) {
throw new Error('Method not implemented.');
return new this({
type: 'loj',
handle: data.username,
password: data.token,
});
}
get(url: string) {
@ -186,8 +190,7 @@ export default class LibreojProvider implements IBasicProvider {
get loggedIn() {
return this.get('/auth/getSessionInfo?token=' + this.account.password).then(
res =>
res.body.userMeta && res.body.userMeta.username === this.account.handle
res => res.body.userMeta && res.body.userMeta.id
);
}
@ -212,7 +215,17 @@ export default class LibreojProvider implements IBasicProvider {
next,
end
) {
const programType = langs_map[lang] || langs_map['C++'];
if (!(await this.ensureLogin())) {
await end({
error: true,
status: 'Judgment Failed',
message: 'Login failed',
});
return null;
}
const programType = LANGS_MAP[lang] || LANGS_MAP['C++'];
const comment = programType.comment;
if (comment) {
@ -255,7 +268,34 @@ export default class LibreojProvider implements IBasicProvider {
return body.submissionId;
}
async ensureIsOwnSubmission(id: string) {
const user_id = await this.get(
'/auth/getSessionInfo?token=' + this.account.password
).then(res => res.body.userMeta?.id);
if (!user_id) return false;
const submission_user_id = await this.post(
'/submission/getSubmissionDetail'
)
.send({ submissionId: String(id), locale: 'zh_CN' })
.retry(3)
.then(res => res.body.meta?.submitter.id);
return user_id === submission_user_id;
}
async waitForSubmission(problem_id: string, id: string, next, end) {
if (!(await this.ensureLogin())) {
await end({
error: true,
status: 'Judgment Failed',
message: 'Login failed',
});
return null;
}
let i = 0;
while (true) {
@ -275,6 +315,15 @@ export default class LibreojProvider implements IBasicProvider {
if (error) continue;
if (body.meta.problem.displayId != problem_id) {
return await end({
id,
error: true,
status: 'Judgment Failed',
message: 'Submission does not match current problem.',
});
}
if (!body.progress) {
await next({ status: 'Waiting for Remote Judge' });

View File

@ -182,6 +182,43 @@ class VJudge {
} catch (e) {
logger.error(e);
await end({
error: true,
status: 'Judgment Failed',
message: e.message,
});
}
} else if (config.remote_submit_type == 'archive') {
try {
const provider = this.p_imports[type].constructFromAccountData(
JSON.parse(config.remote_account_data)
);
if (!config.remote_submission_id) {
return await end({
error: true,
status: 'Judgment Failed',
message: 'REMOTE_SUBMISSION_ID is not set.',
});
}
if (await provider.ensureIsOwnSubmission(config.remote_submission_id)) {
await provider.waitForSubmission(
problem_id,
config.remote_submission_id,
next,
end
);
} else {
return await end({
error: true,
status: 'Judgment Failed',
message: 'Remote submission does not belongs to current user.',
});
}
} catch (e) {
logger.error(e);
await end({
error: true,
status: 'Judgment Failed',

View File

@ -109,13 +109,30 @@ function handleUpload($zip_file_name, $content, $tot_size) {
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];
$content['config'][] = ['remote_submit_type', $submit_type];
if ($submit_type != 'bot') {
$content['no_rejudge'] = true;
$content['config'][] = ['remote_account_data', $_POST['answer_remote_account_data']];
}
$content['config'][] = ['remote_submit_type', $submit_type];
if ($submit_type == 'archive') {
$content['remote_submission_id'] = $_POST['answer_remote_submission_id'];
$content['config'][] = ['remote_submission_id', $_POST['answer_remote_submission_id']];
$content['config'] = array_filter(
$content['config'],
function ($key) {
return !strEndWith($key, '_language');
},
ARRAY_FILTER_USE_KEY
);
$zip_file = new ZipArchive();
$zip_file->open(UOJContext::storagePath() . $zip_file_name, ZipArchive::CREATE);
$zip_file->addFromString('answer.code', '');
$zip_file->close();
}
}
UOJSubmission::onUpload($zip_file_name, $content, $tot_size, $is_participating);
@ -217,10 +234,18 @@ if ($pre_submit_check_ret === true && !$no_more_submission) {
$remote_oj = UOJProblem::cur()->getExtraConfig('remote_online_judge');
$remote_pid = UOJProblem::cur()->getExtraConfig('remote_problem_id');
$remote_url = UOJRemoteProblem::getProblemRemoteUrl($remote_oj, $remote_pid);
$submit_type = json_encode(UOJRemoteProblem::$providers[$remote_oj]['submit_type']);
$remote_provider = UOJRemoteProblem::$providers[$remote_oj];
$submit_type = json_encode($remote_provider['submit_type']);
$answer_form->addNoVal('answer_remote_submit_type', '');
$answer_form->addNoVal('answer_remote_account_data', '');
$answer_form->add('answer_remote_submit_type', '', function ($opt) use ($remote_provider) {
return in_array($opt, $remote_provider['submit_type']) ? '' : '无效选项';
}, null);
$answer_form->add('answer_remote_account_data', '', function ($data) {
return json_decode($data) !== null ? '' : '无效数据';
}, null);
$answer_form->add('answer_remote_submission_id', '', function ($id) {
return validateUInt($id) ? '' : '无效 ID';
}, null);
$answer_form->appendHTML(<<<EOD
<h5>Remote Judge 配置</h5>
<div class="" id="answer-remote_submit_group"></div>

View File

@ -25,14 +25,14 @@ if ($type == 'luogu') {
return false;
}
if ($curl->responseHeaders['Content-Type'] == 'text/html') {
if (strStartWith($curl->responseHeaders['Content-Type'], 'text/html')) {
$sec = $curl->getResponseCookie('sec');
if ($sec) {
$curl->setCookie('sec', $sec);
$curl->get(UOJRemoteProblem::$providers['luogu']['url'] . '/user/setting?_contentOnly=1');
if ($curl->responseHeaders['Content-Type'] == 'application/json') {
if (strStartWith($curl->responseHeaders['Content-Type'], 'application/json')) {
$res = validateLuogu($curl->response);
return true;
@ -42,7 +42,7 @@ if ($type == 'luogu') {
}
return false;
} else if ($curl->responseHeaders['Content-Type'] == 'application/json') {
} else if (strStartWith($curl->responseHeaders['Content-Type'], 'application/json')) {
$res = validateLuogu($curl->response);
return true;
@ -50,8 +50,6 @@ if ($type == 'luogu') {
return false;
}, 3);
die(json_encode(['ok' => $res === true]));
} else if ($type == 'codeforces') {
$curl->setFollowLocation();
$curl->setCookie('JSESSIONID', UOJRequest::post('JSESSIONID', 'is_string', ''));
@ -63,7 +61,7 @@ if ($type == 'luogu') {
return false;
}
if (str_starts_with($curl->responseHeaders['Content-Type'], 'text/html')) {
if (strStartWith($curl->responseHeaders['Content-Type'], 'text/html')) {
if (str_contains($curl->response, 'Login into Codeforces')) {
return false;
}
@ -77,6 +75,28 @@ if ($type == 'luogu') {
return true;
}
return false;
}, 3);
} else if ($type == 'loj') {
retry_loop(function () use (&$curl, &$res) {
$curl->get('https://api.loj.ac.cn/api/auth/getSessionInfo?token=' . UOJRequest::post('token', 'is_string', ''));
if ($curl->error) {
return false;
}
if (strStartWith($curl->responseHeaders['Content-Type'], 'application/json')) {
$response = json_decode(json_encode($curl->response), true);
if (isset($response['userMeta']) && isset($response['userMeta']['id'])) {
$res = true;
return true;
}
return true;
}
return false;
}, 3);
} else {

View File

@ -168,7 +168,7 @@ if ($perm['manager_view']) {
?>
<?php if ($perm['content'] || $perm['manager_view']) : ?>
<div class="copy-button-container">
<div>
<?php UOJSubmission::cur()->echoContent() ?>
</div>

View File

@ -42,7 +42,7 @@ class UOJRemoteProblem {
'short_name' => 'LOJ',
'url' => 'https://loj.ac',
'languages' => ['C', 'C++03', 'C++11', 'C++', 'C++17', 'C++20', 'Python3', 'Python2.7', 'Java17', 'Pascal'],
'submit_type' => ['bot'],
'submit_type' => ['bot', 'archive'],
],
'luogu' => [
'name' => '洛谷',

View File

@ -142,18 +142,33 @@ trait UOJSubmissionLikeTrait {
return false;
}
if ($content['remote_submission_id']) {
echo <<<EOD
<div class="card mb-3">
<div class="card-header fw-bold">
远程提交
</div>
<div class="card-body">
远程提交 ID{$content['remote_submission_id']}
</div>
</div>
EOD;
return true;
}
$zip_file = new ZipArchive();
if ($zip_file->open(UOJContext::storagePath() . $content['file_name'], ZipArchive::RDONLY) !== true) {
echo <<<EOD
<div class="card mb-3">
<div class="card-header text-bg-danger fw-bold">
提交内容
</div>
<div class="card-body">
木有
</div>
</div>
EOD;
<div class="card mb-3">
<div class="card-header text-bg-danger fw-bold">
提交内容
</div>
<div class="card-body">
木有
</div>
</div>
EOD;
return false;
}

View File

@ -932,11 +932,14 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
return this.each(function() {
var input_submit_type_bot_id = 'input-submit_type_bot';
var input_submit_type_my_id = 'input-submit_type_my';
var input_submit_type_archive_id = 'input-submit_type_archive';
var div_submit_type_bot_id = 'div-submit_type_bot';
var div_submit_type_my_id = 'div-submit_type_my';
var div_submit_type_archive_id = 'div-submit_type_archive';
var input_submit_type_bot = $('<input class="form-check-input" type="radio" name="answer_remote_submit_type" id="' + input_submit_type_bot_id + '" value="bot" />');
var input_submit_type_my = $('<input class="form-check-input" type="radio" name="answer_remote_submit_type" id="' + input_submit_type_my_id + '" value="my" />');
var input_submit_type_archive = $('<input class="form-check-input" type="radio" name="answer_remote_submit_type" id="' + input_submit_type_archive_id + '" value="archive" />');
var input_my_account_data = $('<input type="hidden" name="answer_remote_account_data" value="" />');
var my_account_validation_status = $('<span />').append('<span class="text-secondary">待验证</span>');
@ -977,10 +980,24 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
.append($('<div class="mt-3" />')
.append('<span>将使用您的账号提交本题。</span>')
.append('<span>配置方法请查阅 <a href="https://sjzezoj.com/blog/baoshuo/post/717" target="_blank">使用教程</a>。</span>')
);
var div_submit_type_archive = $('<div id="' + div_submit_type_archive_id + '" />')
.append($('<div class="mt-3" />')
.append('<span>将从您给定的提交记录中抓取评测结果。</span>')
.append('<span>配置方法请查阅 <a href="https://sjzezoj.com/blog/baoshuo/post/717" target="_blank">使用教程</a>。</span>')
).append(
$('<div class="row mt-3 align-items-center" />')
.append($('<div class="col-sm-2" />').append('<label for="input-answer_remote_submission_id" class="col-form-label">提交记录 ID</label>'))
.append($('<div class="col-sm-4" />').append('<input id="input-answer_remote_submission_id" name="answer_remote_submission_id" class="form-control font-monospace" autocomplete="off" />'))
.append($('<div class="col-sm-6" />').append($('<div class="form-text mt-0" />').append('请填入远程 OJ 上的提交记录 ID。')))
);
var div_account_data = $('<div class="border px-3 py-2 mt-3" />')
.append($('<div class="mt-2" />').append('<span class="fs-6 fw-bold">远程账号信息</span>'))
.append($('<div class="mt-3" />')
.append('<span>账号状态:</span>')
.append(my_account_validation_status)
.append(my_account_validation_btn)
);
);
if ('localStorage' in window) {
var prefer_submit_type = localStorage.getItem('uoj_remote_judge_save_prefer_submit_type__' + oj) || null;
@ -993,31 +1010,64 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
}
input_submit_type_bot.click(function() {
div_account_data.hide('fast');
div_submit_type_my.hide('fast');
div_submit_type_archive.hide('fast');
div_submit_type_bot.show('fast');
$('#form-group-answer_answer').show('fast');
save_prefer_submit_type('bot');
});
input_submit_type_my.click(function() {
div_submit_type_bot.hide('fast');
div_submit_type_archive.hide('fast');
div_submit_type_my.show('fast');
div_account_data.show('fast');
$('#form-group-answer_answer').show('fast');
save_prefer_submit_type('my');
});
input_submit_type_archive.click(function() {
div_submit_type_bot.hide('fast');
div_submit_type_my.hide('fast');
div_submit_type_archive.show('fast');
div_account_data.show('fast');
$('#form-group-answer_answer').hide('fast');
save_prefer_submit_type('archive');
});
if (submit_type[0] == 'bot') {
div_account_data.hide();
div_submit_type_my.hide();
div_submit_type_archive.hide();
div_submit_type_bot.show();
$('#form-group-answer_answer').show();
input_submit_type_bot[0].checked = true;
} else if (submit_type[0] == 'my') {
div_submit_type_bot.hide();
div_submit_type_my.show();
div_submit_type_archive.hide();
div_account_data.show();
$('#form-group-answer_answer').show();
input_submit_type_my[0].checked = true;
} else if (submit_type[0] == 'archive') {
div_submit_type_bot.hide();
div_submit_type_my.hide();
div_submit_type_archive.show();
div_account_data.show();
$('#form-group-answer_answer').hide();
input_submit_type_archive[0].checked = true;
}
if (submit_type.indexOf('bot') == -1) {
input_submit_type_bot.attr('disabled', 'disabled');
} else if (prefer_submit_type == 'bot') {
div_account_data.hide();
div_submit_type_my.hide();
div_submit_type_archive.hide();
div_submit_type_bot.show();
$('#form-group-answer_answer').show();
input_submit_type_bot[0].checked = true;
input_submit_type_my[0].checked = false;
input_submit_type_archive[0].checked = false;
}
if (submit_type.indexOf('my') == -1) {
@ -1025,8 +1075,25 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
} else if (prefer_submit_type == 'my') {
div_submit_type_bot.hide();
div_submit_type_my.show();
div_submit_type_archive.hide();
div_account_data.show();
$('#form-group-answer_answer').show();
input_submit_type_bot[0].checked = false;
input_submit_type_my[0].checked = true;
input_submit_type_archive[0].checked = false;
}
if (submit_type.indexOf('archive') == -1) {
input_submit_type_archive.attr('disabled', 'disabled');
} else if (prefer_submit_type == 'archive') {
div_submit_type_bot.hide();
div_submit_type_my.hide();
div_submit_type_archive.show();
div_account_data.show();
$('#form-group-answer_answer').hide();
input_submit_type_bot[0].checked = false;
input_submit_type_my[0].checked = false;
input_submit_type_archive[0].checked = true;
}
if (oj == 'luogu') {
@ -1083,7 +1150,7 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
});
}
div_submit_type_my.append(
div_account_data.append(
$('<div class="row mt-3 align-items-center" />')
.append($('<div class="col-sm-2" />').append('<label for="input-luogu_uid" class="col-form-label">_uid</label>'))
.append($('<div class="col-sm-4" />').append(input_luogu_uid))
@ -1135,12 +1202,61 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
});
}
div_submit_type_my.append(
div_account_data.append(
$('<div class="row mt-3 align-items-center" />')
.append($('<div class="col-sm-2" />').append('<label for="input-codeforces_jsessionid" class="col-form-label">JSESSIONID</label>'))
.append($('<div class="col-sm-4" />').append(input_codeforces_jsessionid))
.append($('<div class="col-sm-6" />').append($('<div class="form-text mt-0" />').append('请填入 Cookie 中的 <code>JSESSIONID</code>。')))
).append(input_my_account_data);
} else if (oj == 'loj') {
var loj_account_data = {username: "", token: ""};
var input_loj_token = $('<input class="form-control font-monospace" type="text" name="loj_token" id="input-loj_token" autocomplete="off" />');
if ('localStorage' in window) {
try {
var loj_account_data_str = localStorage.getItem('uoj_remote_judge_loj_account_data');
if (loj_account_data_str) {
loj_account_data = JSON.parse(loj_account_data_str);
}
} catch (e) {}
var save_loj_account_data = function() {
localStorage.setItem('uoj_remote_judge_loj_account_data', JSON.stringify(loj_account_data));
}
} else {
var save_loj_account_data = function() {};
}
input_loj_token.change(function() {
loj_account_data.token = $(this).val();
input_my_account_data.val(JSON.stringify(loj_account_data));
save_loj_account_data();
my_account_validation_status.html('<span class="text-secondary">待验证</span>');
});
my_account_validation_btn.click(function() {
validate_my_account({
type: 'loj',
token: input_loj_token.val(),
});
});
input_my_account_data.val(JSON.stringify(loj_account_data));
input_loj_token.val(loj_account_data.token);
if (loj_account_data.token) {
validate_my_account({
type: 'loj',
token: loj_account_data.token,
});
}
div_account_data.append(
$('<div class="row mt-3 align-items-center" />')
.append($('<div class="col-sm-2" />').append('<label for="input-loj_token" class="col-form-label">Token</label>'))
.append($('<div class="col-sm-4" />').append(input_loj_token))
.append($('<div class="col-sm-6" />').append($('<div class="form-text mt-0" />').append('请前往 <a href="https://loj.ac" target="_blank">LibreOJ</a> 登录账号,然后输入在控制台中运行 <code>console.log(JSON.parse(localStorage.appState).token)</code> 的输出结果。')))
).append(input_my_account_data);
}
$(this).append(
@ -1152,8 +1268,16 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
$('<div class="form-check d-inline-block ms-3" />')
.append(input_submit_type_my)
.append($('<label class="form-check-label" for="' + input_submit_type_my_id + '" />').append(' 自有账号'))
).append(
$('<div class="form-check d-inline-block ms-3" />')
.append(input_submit_type_archive)
.append($('<label class="form-check-label" for="' + input_submit_type_archive_id + '" />').append(' 归档'))
)
).append(div_submit_type_bot).append(div_submit_type_my);
)
.append(div_submit_type_bot)
.append(div_submit_type_my)
.append(div_submit_type_archive)
.append(div_account_data);
});
}