feat(remote_judger): include source code in result
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Baoshuo Ren 2023-03-26 09:38:54 +08:00
parent 5fac2aa2ec
commit 8defcd448f
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
6 changed files with 79 additions and 37 deletions

View File

@ -7,7 +7,6 @@ import { IBasicProvider, RemoteAccount, USER_AGENT } from '../interface';
import Logger from '../utils/logger'; import Logger from '../utils/logger';
import { normalize, VERDICT } from '../verdict'; import { normalize, VERDICT } from '../verdict';
import htmlspecialchars from '../utils/htmlspecialchars'; import htmlspecialchars from '../utils/htmlspecialchars';
import { stripHtml } from 'string-strip-html';
proxy(superagent); proxy(superagent);
const logger = new Logger('remote/loj'); const logger = new Logger('remote/loj');
@ -131,11 +130,13 @@ const LANGS_MAP = {
}, },
}; };
const parse = (str: string) => crlf(str, LF);
export function getAccountInfoFromEnv(): RemoteAccount | null { export function getAccountInfoFromEnv(): RemoteAccount | null {
const { const {
LOJ_HANDLE, LOJ_HANDLE,
LOJ_TOKEN, LOJ_TOKEN,
LOJ_ENDPOINT = 'https://api.loj.ac.cn/api', LOJ_ENDPOINT = 'https://api.loj.ac/api',
LOJ_PROXY, LOJ_PROXY,
} = process.env; } = process.env;
@ -155,7 +156,7 @@ export function getAccountInfoFromEnv(): RemoteAccount | null {
export default class LibreojProvider implements IBasicProvider { export default class LibreojProvider implements IBasicProvider {
constructor(public account: RemoteAccount) { constructor(public account: RemoteAccount) {
this.account.endpoint ||= 'https://api.loj.ac.cn/api'; this.account.endpoint ||= 'https://api.loj.ac/api';
} }
static constructFromAccountData(data) { static constructFromAccountData(data) {
@ -316,17 +317,29 @@ export default class LibreojProvider implements IBasicProvider {
.send({ submissionId: String(id), locale: 'zh_CN' }) .send({ submissionId: String(id), locale: 'zh_CN' })
.retry(3); .retry(3);
let files = [];
if (body.content?.code) {
files.push({
name: 'answer.code',
content: parse(body.content.code),
});
}
if (body.meta.problem.displayId != problem_id) { if (body.meta.problem.displayId != problem_id) {
return await end({ return await end({
id, id,
error: true, error: true,
status: 'Judgment Failed', status: 'Judgment Failed',
message: 'Submission does not match current problem.', message: 'Submission does not match current problem.',
result: { files },
}); });
} }
if (!body.progress) { if (!body.progress) {
await next({ status: 'Waiting for Remote Judge' }); await next({
status: 'Waiting for Remote Judge',
});
continue; continue;
} }
@ -352,6 +365,7 @@ export default class LibreojProvider implements IBasicProvider {
id, id,
status: 'Compile Error', status: 'Compile Error',
message: stripVTControlCharacters(body.progress.compile.message), message: stripVTControlCharacters(body.progress.compile.message),
result: { files },
}); });
} }
@ -361,11 +375,10 @@ export default class LibreojProvider implements IBasicProvider {
id, id,
status: 'Judgment Failed', status: 'Judgment Failed',
message: 'Error occurred on remote online judge.', message: 'Error occurred on remote online judge.',
result: { files },
}); });
} }
const parse = (str: string) => crlf(str, LF);
const getSubtaskStatusDisplayText = (testcases: any): string => { const getSubtaskStatusDisplayText = (testcases: any): string => {
let result: string = null; let result: string = null;
@ -479,13 +492,6 @@ export default class LibreojProvider implements IBasicProvider {
'</remote-result-table>' + '</remote-result-table>' +
'</remote-result-container>'; '</remote-result-container>';
if (result_show_source) {
details +=
`<remote-source-code language="${body.content.language}">` +
htmlspecialchars(parse(body.content.code)) +
'</remote-source-code>';
}
// Samples // Samples
if (body.progress.samples) { if (body.progress.samples) {
details += `<subtask title="Samples" info="${getSubtaskStatusDisplayText( details += `<subtask title="Samples" info="${getSubtaskStatusDisplayText(
@ -534,6 +540,7 @@ export default class LibreojProvider implements IBasicProvider {
time: body.meta.timeUsed, time: body.meta.timeUsed,
memory: body.meta.memoryUsed, memory: body.meta.memoryUsed,
details: `<div>${details}</div>`, details: `<div>${details}</div>`,
result: { files },
}); });
} catch (e) { } catch (e) {
logger.error(e); logger.error(e);

View File

@ -73,7 +73,7 @@ const LANGS_MAP = {
name: 'Pascal', name: 'Pascal',
comment: '//', comment: '//',
}, },
}; } as const;
const API_LANGS_MAP = { const API_LANGS_MAP = {
C: { C: {
@ -385,7 +385,10 @@ export default class LuoguProvider implements IBasicProvider {
count++; count++;
try { try {
let result, data, body; let result,
data,
body,
files = [];
if (this.account.type == 'luogu-api') { if (this.account.type == 'luogu-api') {
result = await this.get(`/judge/result?id=${id}`); result = await this.get(`/judge/result?id=${id}`);
@ -395,6 +398,17 @@ export default class LuoguProvider implements IBasicProvider {
result = await this.safeGet(`/record/${id}?_contentOnly=1`); result = await this.safeGet(`/record/${id}?_contentOnly=1`);
body = result.body; body = result.body;
data = body.currentData.record; data = body.currentData.record;
if (data?.sourceCode) {
files.push({
name: 'answer.code',
content: data.sourceCode,
lang:
Object.keys(LANGS_MAP).find(
lang => LANGS_MAP[lang].id == data.language
) || '/',
});
}
} }
if (result.status == 204) { if (result.status == 204) {
@ -409,6 +423,7 @@ export default class LuoguProvider implements IBasicProvider {
id, id,
status: 'Judgment Failed', status: 'Judgment Failed',
message: 'Failed to fetch submission details.', message: 'Failed to fetch submission details.',
result: { files },
}); });
} }
@ -421,6 +436,7 @@ export default class LuoguProvider implements IBasicProvider {
error: true, error: true,
status: 'Judgment Failed', status: 'Judgment Failed',
message: 'Submission does not match current problem.', message: 'Submission does not match current problem.',
result: { files },
}); });
} }
@ -453,6 +469,7 @@ export default class LuoguProvider implements IBasicProvider {
id, id,
status: 'Compile Error', status: 'Compile Error',
message: data.detail.compileResult.message, message: data.detail.compileResult.message,
result: { files },
}); });
} }
@ -558,6 +575,7 @@ export default class LuoguProvider implements IBasicProvider {
this.account.type != 'luogu-api' this.account.type != 'luogu-api'
? `<div>${details}</div>` ? `<div>${details}</div>`
: details, : details,
result: { files },
}); });
} catch (e) { } catch (e) {
logger.error(e); logger.error(e);
@ -568,7 +586,7 @@ export default class LuoguProvider implements IBasicProvider {
return await end({ return await end({
error: true, error: true,
id: `R${id}`, id,
status: 'Judgment Failed', status: 'Judgment Failed',
message: 'Failed to fetch submission details.', message: 'Failed to fetch submission details.',
}); });

View File

@ -122,6 +122,7 @@ class VJudge {
`<info-block>ID = ${payload.id || 'None'}</info-block>` + `<info-block>ID = ${payload.id || 'None'}</info-block>` +
`<error>${htmlspecialchars(payload.message)}</error>` + `<error>${htmlspecialchars(payload.message)}</error>` +
'</div>', '</div>',
...(payload.result || {}),
}), }),
judge_time, judge_time,
}); });
@ -143,6 +144,7 @@ class VJudge {
payload.id || 'None' payload.id || 'None'
}\nVERDICT = ${payload.status}</info-block>` + }\nVERDICT = ${payload.status}</info-block>` +
'</div>', '</div>',
...(payload.result || {}),
}), }),
judge_time, judge_time,
}); });

View File

@ -391,7 +391,7 @@ class UOJRemoteProblem {
$curl->setHeader('Content-Type', 'application/json'); $curl->setHeader('Content-Type', 'application/json');
$res = retry_loop(function () use (&$curl, $id) { $res = retry_loop(function () use (&$curl, $id) {
$curl->post('https://api.loj.ac.cn/api/problem/getProblem', json_encode([ $curl->post('https://api.loj.ac/api/problem/getProblem', json_encode([
'displayId' => (int)$id, 'displayId' => (int)$id,
'localizedContentsOfLocale' => 'zh_CN', 'localizedContentsOfLocale' => 'zh_CN',
'samples' => true, 'samples' => true,

View File

@ -172,6 +172,13 @@ class UOJSubmission {
} }
} }
$post_result = json_decode($post_result, true);
if (isset($post_result['files'])) {
$files = $post_result['files'];
unset($post_result['files']);
}
$set_q = [ $set_q = [
'status' => 'Judged', 'status' => 'Judged',
'status_details' => '', 'status_details' => '',
@ -179,14 +186,15 @@ class UOJSubmission {
if ($submission->info['status'] == 'Judged, Judging') { // for UOJ-OI if ($submission->info['status'] == 'Judged, Judging') { // for UOJ-OI
$result = json_decode($submission->info['result'], true); $result = json_decode($submission->info['result'], true);
$result['final_result'] = json_decode($post_result, true); $result['final_result'] = $post_result;
$result['final_result']['details'] = uojTextEncode($result['final_result']['details']); $result['final_result']['details'] = uojTextEncode($result['final_result']['details']);
$set_q += [ $set_q += [
'result' => json_encode($result, JSON_UNESCAPED_UNICODE), 'result' => json_encode($result, JSON_UNESCAPED_UNICODE),
]; ];
} elseif ($submission->info['status'] == 'Judging') { } elseif ($submission->info['status'] == 'Judging') {
$result = json_decode($post_result, true); $result = $post_result;
if (isset($result['details'])) { if (isset($result['details'])) {
$result['details'] = uojTextEncode($result['details']); $result['details'] = uojTextEncode($result['details']);
} else { } else {
@ -239,6 +247,30 @@ class UOJSubmission {
return; return;
} }
if (isset($files)) {
$zip_file = new ZipArchive();
$zip_file->open(UOJContext::storagePath() . $submission->getContent('file_name'), ZipArchive::CREATE);
$tot_size = 0;
$language = '/';
foreach ($files as $file) {
$zip_file->addFromString($file['name'], $file['content']);
if (isset($file['lang'])) {
$language = UOJLang::getUpgradedLangCode($file['lang']);
}
$tot_size += $zip_file->statName($file['name'])['size'];
}
$set_q += [
'tot_size' => $tot_size,
'language' => $language,
];
$zip_file->close();
}
if ($submission->isLatest()) { if ($submission->isLatest()) {
DB::update([ DB::update([
"update submissions", "update submissions",
@ -359,7 +391,7 @@ class UOJSubmission {
]) ])
]) ])
], DB::for_update() ], DB::for_update()
]); ]);
DB::update(["update submissions", "set", $cfg['set_q'], "where", [$cond]]); DB::update(["update submissions", "set", $cfg['set_q'], "where", [$cond]]);
}); });
} else { } else {

View File

@ -158,23 +158,6 @@ trait UOJSubmissionLikeTrait {
$card_footer_class = 'text-end mt-2'; $card_footer_class = 'text-end mt-2';
} }
if ($content['remote_submission_id']) {
echo <<<EOD
<div class="{$card_class}">
<div class="{$card_header_class}">
远程提交
</div>
<div class="{$card_body_class}">
远程提交 ID: {$content['remote_submission_id']}
<br>
源代码请在「详细信息」选项卡查看。
</div>
</div>
EOD;
return true;
}
$zip_file = new ZipArchive(); $zip_file = new ZipArchive();
if ($zip_file->open(UOJContext::storagePath() . $content['file_name'], ZipArchive::RDONLY) !== true) { if ($zip_file->open(UOJContext::storagePath() . $content['file_name'], ZipArchive::RDONLY) !== true) {
echo <<<EOD echo <<<EOD