mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-08 14:18:40 +00:00
feat(remote_judger): include source code in result
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
5fac2aa2ec
commit
8defcd448f
@ -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);
|
||||||
|
@ -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.',
|
||||||
});
|
});
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
|
@ -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,
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user