From 7cdb39267a71f4a4e66e627aa10b8b8d239e6442 Mon Sep 17 00:00:00 2001
From: Baoshuo
Date: Thu, 2 Feb 2023 20:07:35 +0800
Subject: [PATCH 1/7] feat(problem/remote): prepare for luogu
---
web/app/controllers/new_remote_problem.php | 9 +-
.../controllers/problem_statement_manage.php | 13 +-
web/app/libs/uoj-form-lib.php | 141 ++++++++++++++++--
web/app/libs/uoj-html-lib.php | 18 ++-
web/app/models/UOJForm.php | 1 +
web/app/models/UOJProblem.php | 2 +
web/app/models/UOJRemoteProblem.php | 28 ++++
web/app/models/UOJSubmission.php | 7 +-
web/app/models/UOJSubmissionLikeTrait.php | 18 +++
9 files changed, 201 insertions(+), 36 deletions(-)
diff --git a/web/app/controllers/new_remote_problem.php b/web/app/controllers/new_remote_problem.php
index 63cf369..e7af67d 100644
--- a/web/app/controllers/new_remote_problem.php
+++ b/web/app/controllers/new_remote_problem.php
@@ -73,14 +73,7 @@ $new_remote_problem_form->handle = function (&$vdata) {
UOJResponse::page500('题目抓取失败,可能是题目不存在或者没有题面!如果题目没有问题,请稍后再试。返回');
}
- $submission_requirement = [
- [
- "name" => "answer",
- "type" => "source code",
- "file_name" => "answer.code",
- "languages" => $remote_provider['languages'],
- ]
- ];
+ $submission_requirement = UOJRemoteProblem::getSubmissionRequirements($remote_online_judge);
$enc_submission_requirement = json_encode($submission_requirement);
$extra_config = [
diff --git a/web/app/controllers/problem_statement_manage.php b/web/app/controllers/problem_statement_manage.php
index 3ccc75b..3b3353a 100644
--- a/web/app/controllers/problem_statement_manage.php
+++ b/web/app/controllers/problem_statement_manage.php
@@ -118,7 +118,7 @@ if (UOJProblem::info('type') == 'remote') {
EOD);
$re_crawl_form->config['submit_button']['text'] = '重新爬取';
- $re_crawl_form->handle = function () use ($remote_online_judge, $remote_problem_id, $remote_provider) {
+ $re_crawl_form->handle = function () use ($remote_online_judge, $remote_problem_id) {
try {
$data = UOJRemoteProblem::getProblemBasicInfo($remote_online_judge, $remote_problem_id);
} catch (Exception $e) {
@@ -134,16 +134,9 @@ if (UOJProblem::info('type') == 'remote') {
$data['difficulty'] = UOJProblem::info('difficulty');
}
- $submission_requirement = [
- [
- "name" => "answer",
- "type" => "source code",
- "file_name" => "answer.code",
- "languages" => $remote_provider['languages'],
- ]
- ];
+ $submission_requirement = UOJRemoteProblem::getSubmissionRequirements($remote_online_judge);
$enc_submission_requirement = json_encode($submission_requirement);
-
+
$extra_config = [
'remote_online_judge' => $remote_online_judge,
'remote_problem_id' => $remote_problem_id,
diff --git a/web/app/libs/uoj-form-lib.php b/web/app/libs/uoj-form-lib.php
index aef5409..41a3efa 100644
--- a/web/app/libs/uoj-form-lib.php
+++ b/web/app/libs/uoj-form-lib.php
@@ -46,6 +46,7 @@ function newAddDelCmdForm($form_name, $validate, $handle, $final = null) {
function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle) {
$form = new UOJForm($form_name);
+
foreach ($requirement as $req) {
if ($req['type'] == "source code") {
$languages = UOJLang::getAvailableLanguages(isset($req['languages']) ? $req['languages'] : null);
@@ -57,6 +58,101 @@ function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle
$form->addTextFileInput("{$form_name}_{$req['name']}", [
'filename' => $req['file_name'],
]);
+ } else if ($req['type'] == "remote submission") {
+ if ($req['name'] == 'luogu') {
+ $form->appendHTML(HTML::tag_begin('div', ['class' => 'row']));
+ $form->appendHTML(HTML::tag(
+ 'div',
+ [
+ 'class' => 'col-sm-2',
+ ],
+ HTML::tag('label', [
+ 'class' => 'form-col-label',
+ ], '_uid')
+ ));
+ $form->addInput("{$form_name}_{$req['name']}_uid", [
+ 'div_class' => 'col-sm-4',
+ 'validator_php' => function ($x) {
+ if (!validateUInt($x)) {
+ return 'ID 不合法';
+ }
+
+ return '';
+ },
+ ]);
+ $form->appendHTML(HTML::tag(
+ 'div',
+ [
+ 'class' => 'col-sm-6',
+ ],
+ HTML::tag('div', [
+ 'class' => 'form-text',
+ ], '请在 Cookie 中找到 _uid
,然后填入框中。')
+ ));
+ $form->appendHTML(HTML::tag_end('div'));
+
+ $form->appendHTML(HTML::tag_begin('div', ['class' => 'row mt-3']));
+ $form->appendHTML(HTML::tag(
+ 'div',
+ [
+ 'class' => 'col-sm-2',
+ ],
+ HTML::tag('label', [
+ 'class' => 'form-col-label',
+ ], '__clientid')
+ ));
+ $form->addInput("{$form_name}_{$req['name']}_clientid", [
+ 'div_class' => 'col-sm-4',
+ 'validator_php' => function ($x) {
+ if (!validateString($x)) {
+ return 'ID 不合法';
+ }
+
+ return '';
+ },
+ ]);
+ $form->appendHTML(HTML::tag(
+ 'div',
+ [
+ 'class' => 'col-sm-6',
+ ],
+ HTML::tag('div', [
+ 'class' => 'form-text',
+ ], '请在 Cookie 中找到 __clientid
,然后填入框中。')
+ ));
+ $form->appendHTML(HTML::tag_end('div'));
+
+ $form->appendHTML(HTML::tag_begin('div', ['class' => 'row mt-3']));
+ $form->appendHTML(HTML::tag(
+ 'div',
+ [
+ 'class' => 'col-sm-2',
+ ],
+ HTML::tag('label', [
+ 'class' => 'form-col-label',
+ ], '提交记录 ID')
+ ));
+ $form->addInput("{$form_name}_{$req['name']}_submission_id", [
+ 'div_class' => 'col-sm-4',
+ 'validator_php' => function ($x) {
+ if (!validateUInt($x)) {
+ return 'ID 不合法';
+ }
+
+ return '';
+ },
+ ]);
+ $form->appendHTML(HTML::tag(
+ 'div',
+ [
+ 'class' => 'col-sm-6',
+ ],
+ HTML::tag('div', [
+ 'class' => 'form-text',
+ ], '请填入提交记录 ID(不带开头的字母 R
)。')
+ ));
+ $form->appendHTML(HTML::tag_end('div'));
+ }
}
}
@@ -74,35 +170,48 @@ function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle
$content = [];
$content['file_name'] = $zip_file_name;
$content['config'] = [];
+
foreach ($requirement as $req) {
if ($req['type'] == "source code") {
$content['config'][] = ["{$req['name']}_language", $_POST["{$form_name}_{$req['name']}_language"]];
+ } else if ($req['type'] == "remote submission") {
+ $content['no_rejudge'] = true;
+
+ if ($req['name'] == "luogu") {
+ $content['config'][] = ["{$req['name']}_uid", $_POST["{$form_name}_{$req['name']}_uid"]];
+ $content['config'][] = ["{$req['name']}_clientid", $_POST["{$form_name}_{$req['name']}_clientid"]];
+ $content['config'][] = ["{$req['name']}_submission_id", $_POST["{$form_name}_{$req['name']}_submission_id"]];
+ }
}
}
foreach ($requirement as $req) {
- if ($_POST["{$form_name}_{$req['name']}_upload_type"] == 'editor') {
- $zip_file->addFromString($req['file_name'], $_POST["{$form_name}_{$req['name']}_editor"]);
+ if ($req['type'] == "remote submission") {
+ $zip_file->addFromString($req['name'], '');
} else {
- $tmp_name = UOJForm::uploadedFileTmpName("{$form_name}_{$req['name']}_file");
- if ($tmp_name == null) {
- $zip_file->addFromString($req['file_name'], '');
+ if ($_POST["{$form_name}_{$req['name']}_upload_type"] == 'editor') {
+ $zip_file->addFromString($req['file_name'], $_POST["{$form_name}_{$req['name']}_editor"]);
} else {
- $zip_file->addFile($tmp_name, $req['file_name']);
+ $tmp_name = UOJForm::uploadedFileTmpName("{$form_name}_{$req['name']}_file");
+ if ($tmp_name == null) {
+ $zip_file->addFromString($req['file_name'], '');
+ } else {
+ $zip_file->addFile($tmp_name, $req['file_name']);
+ }
}
- }
- $stat = $zip_file->statName($req['file_name']);
+ $stat = $zip_file->statName($req['file_name']);
- if ($req['type'] == 'source code') {
- $max_size = isset($req['size']) ? (int)$req['size'] : 100;
- if ($stat['size'] > $max_size * 1024) {
- $zip_file->close();
- unlink(UOJContext::storagePath() . $zip_file_name);
- UOJResponse::message("源代码长度不能超过 {$max_size} kB。");
+ if ($req['type'] == 'source code') {
+ $max_size = isset($req['size']) ? (int)$req['size'] : 100;
+ if ($stat['size'] > $max_size * 1024) {
+ $zip_file->close();
+ unlink(UOJContext::storagePath() . $zip_file_name);
+ UOJResponse::message("源代码长度不能超过 {$max_size} kB。");
+ }
}
- }
- $tot_size += $stat['size'];
+ $tot_size += $stat['size'];
+ }
}
$zip_file->close();
diff --git a/web/app/libs/uoj-html-lib.php b/web/app/libs/uoj-html-lib.php
index 0fa2ddb..d6b5157 100644
--- a/web/app/libs/uoj-html-lib.php
+++ b/web/app/libs/uoj-html-lib.php
@@ -403,7 +403,7 @@ function echoSubmissionContent($submission, $requirement) {
echo '';
echo '';
echo '';
- } elseif ($req['type'] == "text") {
+ } else if ($req['type'] == "text") {
$file_content = $zip_file->getFromName("{$req['file_name']}", 504);
$file_content = strOmit($file_content, 500);
$file_content = uojTextEncode($file_content, array('allow_CR' => true, 'html_escape' => true));
@@ -417,6 +417,22 @@ function echoSubmissionContent($submission, $requirement) {
echo '';
echo '';
echo '';
+ } else if ($req['type'] == "remote submission") {
+ $remote_provider = UOJRemoteProblem::$providers[$req['name']];
+ $content = '';
+
+ if ($req['name'] == 'luogu') {
+ $content .= '
远端评测 ID:' .
+ HTML::tag(
+ 'a',
+ [
+ 'href' => $remote_provider['url'] . '/record/' . $config['luogu_submission_id']
+ ],
+ 'R' . $config['luogu_submission_id']
+ ) . '
';
+ }
+
+ HTML::echoPanel('', '远端评测记录', $content);
}
}
diff --git a/web/app/models/UOJForm.php b/web/app/models/UOJForm.php
index 55882e4..ad3a541 100644
--- a/web/app/models/UOJForm.php
+++ b/web/app/models/UOJForm.php
@@ -665,6 +665,7 @@ class UOJForm {
EOD;
} else {
echo <<form_name}").addClass('disabled');
return ok;
EOD;
}
diff --git a/web/app/models/UOJProblem.php b/web/app/models/UOJProblem.php
index ee0da7d..2db8eb6 100644
--- a/web/app/models/UOJProblem.php
+++ b/web/app/models/UOJProblem.php
@@ -562,6 +562,8 @@ class UOJProblem {
foreach ($submission_requirement as $req) {
if ($req['type'] == 'source code') {
return false;
+ } else if ($req['type'] == 'remote submission') {
+ return false;
}
}
diff --git a/web/app/models/UOJRemoteProblem.php b/web/app/models/UOJRemoteProblem.php
index 11e5fe8..cf9c74a 100644
--- a/web/app/models/UOJRemoteProblem.php
+++ b/web/app/models/UOJRemoteProblem.php
@@ -40,6 +40,12 @@ class UOJRemoteProblem {
'url' => 'https://loj.ac',
'languages' => ['C', 'C++03', 'C++11', 'C++', 'C++17', 'C++20', 'Python3', 'Python2.7', 'Java17', 'Pascal'],
],
+ 'luogu' => [
+ 'name' => '洛谷',
+ 'short_name' => '洛谷',
+ 'url' => 'https://www.luogu.com.cn',
+ 'languages' => [],
+ ],
];
static function curl_get($url) {
@@ -394,6 +400,28 @@ class UOJRemoteProblem {
];
}
+ public static function getSubmissionRequirements($oj) {
+ $remote_provider = UOJRemoteProblem::$providers[$oj];
+
+ if ($oj == 'luogu') {
+ return [
+ [
+ "name" => "luogu",
+ "type" => "remote submission",
+ ]
+ ];
+ }
+
+ return [
+ [
+ "name" => "answer",
+ "type" => "source code",
+ "file_name" => "answer.code",
+ "languages" => $remote_provider['languages'],
+ ]
+ ];
+ }
+
public static function getProblemRemoteUrl($oj, $id) {
if ($oj === 'codeforces') {
return static::getCodeforcesProblemUrl($id);
diff --git a/web/app/models/UOJSubmission.php b/web/app/models/UOJSubmission.php
index 8f5e341..ad9f20f 100644
--- a/web/app/models/UOJSubmission.php
+++ b/web/app/models/UOJSubmission.php
@@ -89,7 +89,8 @@ class UOJSubmission {
$content['config'][] = ['problem_id', UOJProblem::info('id')];
if (UOJProblem::info('type') == 'remote') {
- $content['config'][] = ['remote_online_judge', UOJProblem::cur()->getExtraConfig('remote_online_judge')];
+ $remote_online_judge = UOJProblem::cur()->getExtraConfig('remote_online_judge');
+ $content['config'][] = ['remote_online_judge', $remote_online_judge];
$content['config'][] = ['remote_problem_id', UOJProblem::cur()->getExtraConfig('remote_problem_id')];
}
@@ -418,6 +419,10 @@ class UOJSubmission {
}
public function userCanRejudge(array $user = null) {
+ if ($this->getContent('no_rejudge')) {
+ return false;
+ }
+
if (isSuperUser($user)) {
return true;
}
diff --git a/web/app/models/UOJSubmissionLikeTrait.php b/web/app/models/UOJSubmissionLikeTrait.php
index 5166ff6..c4126eb 100644
--- a/web/app/models/UOJSubmissionLikeTrait.php
+++ b/web/app/models/UOJSubmissionLikeTrait.php
@@ -210,6 +210,24 @@ trait UOJSubmissionLikeTrait {
EOD;
+ } else if ($req['type'] == "remote submission") {
+ $remote_provider = UOJRemoteProblem::$providers[$req['name']];
+ $content = '';
+
+ if ($req['name'] == 'luogu') {
+ $content .= HTML::tag('div', [], [
+ '远端评测 ID:',
+ HTML::tag(
+ 'a',
+ [
+ 'href' => "{$remote_provider['url']}/record/{$config['luogu_submission_id']}"
+ ],
+ 'R' . $config['luogu_submission_id']
+ ),
+ ]);
+ }
+
+ HTML::echoPanel('mb-3', '远端评测记录', $content);
}
}
$zip_file->close();
From dcbe7691a2be5f223e0a44a14ce91c02cf4e17b1 Mon Sep 17 00:00:00 2001
From: Baoshuo
Date: Thu, 2 Feb 2023 21:53:36 +0800
Subject: [PATCH 2/7] feat(problem/remote): add luogu
---
web/app/controllers/new_remote_problem.php | 10 ++++
web/app/libs/uoj-form-lib.php | 1 +
web/app/libs/uoj-validate-lib.php | 4 ++
web/app/models/UOJRemoteProblem.php | 61 ++++++++++++++++++++++
4 files changed, 76 insertions(+)
diff --git a/web/app/controllers/new_remote_problem.php b/web/app/controllers/new_remote_problem.php
index e7af67d..cd4f4b3 100644
--- a/web/app/controllers/new_remote_problem.php
+++ b/web/app/controllers/new_remote_problem.php
@@ -51,6 +51,16 @@ $new_remote_problem_form->addInput('remote_problem_id', [
$vdata['remote_problem_id'] = $id;
+ return '';
+ } else if ($remote_oj === 'luogu') {
+ $id = trim(strtoupper($id));
+
+ if (!validateLuoguProblemId($id)) {
+ return '不合法的题目 ID';
+ }
+
+ $vdata['remote_problem_id'] = $id;
+
return '';
}
diff --git a/web/app/libs/uoj-form-lib.php b/web/app/libs/uoj-form-lib.php
index 41a3efa..f2c4355 100644
--- a/web/app/libs/uoj-form-lib.php
+++ b/web/app/libs/uoj-form-lib.php
@@ -176,6 +176,7 @@ function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle
$content['config'][] = ["{$req['name']}_language", $_POST["{$form_name}_{$req['name']}_language"]];
} else if ($req['type'] == "remote submission") {
$content['no_rejudge'] = true;
+ $content['manual_submit'] = true;
if ($req['name'] == "luogu") {
$content['config'][] = ["{$req['name']}_uid", $_POST["{$form_name}_{$req['name']}_uid"]];
diff --git a/web/app/libs/uoj-validate-lib.php b/web/app/libs/uoj-validate-lib.php
index 426cc92..82b2d95 100644
--- a/web/app/libs/uoj-validate-lib.php
+++ b/web/app/libs/uoj-validate-lib.php
@@ -79,3 +79,7 @@ function is_short_string($str) {
function validateCodeforcesProblemId($str) {
return preg_match('/(|GYM)[1-9][0-9]{0,5}[A-Z][1-9]?/', $str) !== true;
}
+
+function validateLuoguProblemId($str) {
+ return preg_match('/P[1-9][0-9]{4,5}/', $str) !== true;
+}
diff --git a/web/app/models/UOJRemoteProblem.php b/web/app/models/UOJRemoteProblem.php
index cf9c74a..caeb50a 100644
--- a/web/app/models/UOJRemoteProblem.php
+++ b/web/app/models/UOJRemoteProblem.php
@@ -96,6 +96,10 @@ class UOJRemoteProblem {
return static::$providers['loj']['url'] . '/p/' . $id;
}
+ static function getLuoguProblemUrl($id) {
+ return static::$providers['luogu']['url'] . '/problem/' . $id;
+ }
+
static function getCodeforcesProblemBasicInfoFromHtml($id, $html) {
$remote_provider = static::$providers['codeforces'];
@@ -400,6 +404,59 @@ class UOJRemoteProblem {
];
}
+ static function getLuoguProblemBasicInfo($id) {
+ $remote_provider = static::$providers['luogu'];
+ $res = static::curl_get(static::getLuoguProblemUrl($id) . '?_contentOnly=1');
+
+ if (!$res) return null;
+
+ // Convert stdClass to array
+ $res = json_decode(json_encode($res['response']), true);
+
+ if (!isset($res['code']) || $res['code'] != 200) return null;
+
+ $problem = $res['currentData']['problem'];
+ $statement = '';
+
+ if ($problem['background']) {
+ $statement .= "\n### 题目背景\n\n";
+ $statement .= $problem['background'] . "\n";
+ }
+
+ $statement .= "\n### 题目描述\n\n";
+ $statement .= $problem['description'] . "\n";
+
+ $statement .= "\n### 输入格式\n\n";
+ $statement .= $problem['inputFormat'] . "\n";
+
+ $statement .= "\n### 输出格式\n\n";
+ $statement .= $problem['outputFormat'] . "\n";
+
+ $statement .= "\n### 输入输出样例\n\n";
+
+ foreach ($problem['samples'] as $id => $sample) {
+ $display_sample_id = $id + 1;
+
+ $statement .= "\n#### 样例输入 #{$display_sample_id}\n\n";
+ $statement .= "\n```text\n{$sample[0]}\n```\n\n";
+
+ $statement .= "\n#### 样例输出 #{$display_sample_id}\n\n";
+ $statement .= "\n```text\n{$sample[1]}\n```\n\n";
+ }
+
+ $statement .= "\n### 说明/提示\n\n";
+ $statement .= $problem['hint'] . "\n";
+
+ return [
+ 'type' => 'html',
+ 'title' => "【{$remote_provider['short_name']}{$problem['pid']}】{$problem['title']}",
+ 'time_limit' => (float)max($problem['limits']['time']) / 1000.0,
+ 'memory_limit' => (float)max($problem['limits']['memory']) / 1024.0,
+ 'difficulty' => -1,
+ 'statement' => HTML::parsedown()->text($statement),
+ ];
+ }
+
public static function getSubmissionRequirements($oj) {
$remote_provider = UOJRemoteProblem::$providers[$oj];
@@ -431,6 +488,8 @@ class UOJRemoteProblem {
return static::getUojProblemUrl($id);
} else if ($oj === 'loj') {
return static::getLojProblemUrl($id);
+ } else if ($oj === 'luogu') {
+ return static::getLuoguProblemUrl($id);
}
return null;
@@ -446,6 +505,8 @@ class UOJRemoteProblem {
return static::getUojProblemBasicInfo($id);
} else if ($oj === 'loj') {
return static::getLojProblemBasicInfo($id);
+ } else if ($oj === 'luogu') {
+ return static::getLuoguProblemBasicInfo($id);
}
return null;
From 2ab8d076833c3cbb5b64b2195e6da8784891e9ad Mon Sep 17 00:00:00 2001
From: Baoshuo
Date: Fri, 3 Feb 2023 09:06:47 +0800
Subject: [PATCH 3/7] refactor(problem/remote): custom account field
---
web/app/controllers/problem.php | 31 ++++-
web/app/libs/uoj-form-lib.php | 144 +++-------------------
web/app/libs/uoj-html-lib.php | 16 ---
web/app/models/UOJProblem.php | 2 -
web/app/models/UOJRemoteProblem.php | 16 +--
web/app/models/UOJSubmission.php | 3 +-
web/app/models/UOJSubmissionLikeTrait.php | 18 ---
web/js/uoj.js | 102 +++++++++++++++
8 files changed, 157 insertions(+), 175 deletions(-)
diff --git a/web/app/controllers/problem.php b/web/app/controllers/problem.php
index 8e289e3..29d2e00 100644
--- a/web/app/controllers/problem.php
+++ b/web/app/controllers/problem.php
@@ -104,6 +104,17 @@ $custom_test_enabled = $custom_test_requirement && $pre_submit_check_ret === tru
function handleUpload($zip_file_name, $content, $tot_size) {
global $is_participating;
+
+ $remote_oj = UOJProblem::cur()->getExtraConfig('remote_online_judge');
+ $remote_provider = UOJRemoteProblem::$providers[$remote_oj];
+
+ 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['no_rejudge'] = true;
+ $content['config'][] = ['remote_submit_type', $submit_type];
+ $content['config'][] = ['remote_account_data', $_POST['answer_remote_account_data']];
+ }
+
UOJSubmission::onUpload($zip_file_name, $content, $tot_size, $is_participating);
}
function handleCustomTestUpload($zip_file_name, $content, $tot_size) {
@@ -182,7 +193,7 @@ if ($pre_submit_check_ret === true && !$no_more_submission) {
if (UOJProblem::cur()->userCanUploadSubmissionViaZip(Auth::user())) {
$zip_answer_form = newZipSubmissionForm(
- 'zip-answer',
+ 'zip_answer',
$submission_requirement,
'FS::randomAvailableSubmissionFileName',
'handleUpload'
@@ -198,6 +209,24 @@ if ($pre_submit_check_ret === true && !$no_more_submission) {
'FS::randomAvailableSubmissionFileName',
'handleUpload'
);
+
+ if (UOJProblem::info('type') == 'remote') {
+ $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']);
+
+ $answer_form->addNoVal('answer_remote_submit_type', '');
+ $answer_form->addNoVal('answer_remote_account_data', '');
+ $answer_form->appendHTML(<<Remote Judge 配置
+
+
+ EOD);
+ }
+
$answer_form->extra_validator = $submission_extra_validator;
$answer_form->succ_href = $is_participating ? '/contest/' . UOJContest::info('id') . '/submissions' : '/submissions';
$answer_form->runAtServer();
diff --git a/web/app/libs/uoj-form-lib.php b/web/app/libs/uoj-form-lib.php
index f2c4355..ddde655 100644
--- a/web/app/libs/uoj-form-lib.php
+++ b/web/app/libs/uoj-form-lib.php
@@ -58,101 +58,6 @@ function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle
$form->addTextFileInput("{$form_name}_{$req['name']}", [
'filename' => $req['file_name'],
]);
- } else if ($req['type'] == "remote submission") {
- if ($req['name'] == 'luogu') {
- $form->appendHTML(HTML::tag_begin('div', ['class' => 'row']));
- $form->appendHTML(HTML::tag(
- 'div',
- [
- 'class' => 'col-sm-2',
- ],
- HTML::tag('label', [
- 'class' => 'form-col-label',
- ], '_uid')
- ));
- $form->addInput("{$form_name}_{$req['name']}_uid", [
- 'div_class' => 'col-sm-4',
- 'validator_php' => function ($x) {
- if (!validateUInt($x)) {
- return 'ID 不合法';
- }
-
- return '';
- },
- ]);
- $form->appendHTML(HTML::tag(
- 'div',
- [
- 'class' => 'col-sm-6',
- ],
- HTML::tag('div', [
- 'class' => 'form-text',
- ], '请在 Cookie 中找到 _uid
,然后填入框中。')
- ));
- $form->appendHTML(HTML::tag_end('div'));
-
- $form->appendHTML(HTML::tag_begin('div', ['class' => 'row mt-3']));
- $form->appendHTML(HTML::tag(
- 'div',
- [
- 'class' => 'col-sm-2',
- ],
- HTML::tag('label', [
- 'class' => 'form-col-label',
- ], '__clientid')
- ));
- $form->addInput("{$form_name}_{$req['name']}_clientid", [
- 'div_class' => 'col-sm-4',
- 'validator_php' => function ($x) {
- if (!validateString($x)) {
- return 'ID 不合法';
- }
-
- return '';
- },
- ]);
- $form->appendHTML(HTML::tag(
- 'div',
- [
- 'class' => 'col-sm-6',
- ],
- HTML::tag('div', [
- 'class' => 'form-text',
- ], '请在 Cookie 中找到 __clientid
,然后填入框中。')
- ));
- $form->appendHTML(HTML::tag_end('div'));
-
- $form->appendHTML(HTML::tag_begin('div', ['class' => 'row mt-3']));
- $form->appendHTML(HTML::tag(
- 'div',
- [
- 'class' => 'col-sm-2',
- ],
- HTML::tag('label', [
- 'class' => 'form-col-label',
- ], '提交记录 ID')
- ));
- $form->addInput("{$form_name}_{$req['name']}_submission_id", [
- 'div_class' => 'col-sm-4',
- 'validator_php' => function ($x) {
- if (!validateUInt($x)) {
- return 'ID 不合法';
- }
-
- return '';
- },
- ]);
- $form->appendHTML(HTML::tag(
- 'div',
- [
- 'class' => 'col-sm-6',
- ],
- HTML::tag('div', [
- 'class' => 'form-text',
- ], '请填入提交记录 ID(不带开头的字母 R
)。')
- ));
- $form->appendHTML(HTML::tag_end('div'));
- }
}
}
@@ -174,45 +79,32 @@ function newSubmissionForm($form_name, $requirement, $zip_file_name_gen, $handle
foreach ($requirement as $req) {
if ($req['type'] == "source code") {
$content['config'][] = ["{$req['name']}_language", $_POST["{$form_name}_{$req['name']}_language"]];
- } else if ($req['type'] == "remote submission") {
- $content['no_rejudge'] = true;
- $content['manual_submit'] = true;
-
- if ($req['name'] == "luogu") {
- $content['config'][] = ["{$req['name']}_uid", $_POST["{$form_name}_{$req['name']}_uid"]];
- $content['config'][] = ["{$req['name']}_clientid", $_POST["{$form_name}_{$req['name']}_clientid"]];
- $content['config'][] = ["{$req['name']}_submission_id", $_POST["{$form_name}_{$req['name']}_submission_id"]];
- }
}
}
foreach ($requirement as $req) {
- if ($req['type'] == "remote submission") {
- $zip_file->addFromString($req['name'], '');
+ if ($_POST["{$form_name}_{$req['name']}_upload_type"] == 'editor') {
+ $zip_file->addFromString($req['file_name'], $_POST["{$form_name}_{$req['name']}_editor"]);
} else {
- if ($_POST["{$form_name}_{$req['name']}_upload_type"] == 'editor') {
- $zip_file->addFromString($req['file_name'], $_POST["{$form_name}_{$req['name']}_editor"]);
+ $tmp_name = UOJForm::uploadedFileTmpName("{$form_name}_{$req['name']}_file");
+ if ($tmp_name == null) {
+ $zip_file->addFromString($req['file_name'], '');
} else {
- $tmp_name = UOJForm::uploadedFileTmpName("{$form_name}_{$req['name']}_file");
- if ($tmp_name == null) {
- $zip_file->addFromString($req['file_name'], '');
- } else {
- $zip_file->addFile($tmp_name, $req['file_name']);
- }
+ $zip_file->addFile($tmp_name, $req['file_name']);
}
- $stat = $zip_file->statName($req['file_name']);
-
- if ($req['type'] == 'source code') {
- $max_size = isset($req['size']) ? (int)$req['size'] : 100;
- if ($stat['size'] > $max_size * 1024) {
- $zip_file->close();
- unlink(UOJContext::storagePath() . $zip_file_name);
- UOJResponse::message("源代码长度不能超过 {$max_size} kB。");
- }
- }
-
- $tot_size += $stat['size'];
}
+ $stat = $zip_file->statName($req['file_name']);
+
+ if ($req['type'] == 'source code') {
+ $max_size = isset($req['size']) ? (int)$req['size'] : 100;
+ if ($stat['size'] > $max_size * 1024) {
+ $zip_file->close();
+ unlink(UOJContext::storagePath() . $zip_file_name);
+ UOJResponse::message("源代码长度不能超过 {$max_size} kB。");
+ }
+ }
+
+ $tot_size += $stat['size'];
}
$zip_file->close();
diff --git a/web/app/libs/uoj-html-lib.php b/web/app/libs/uoj-html-lib.php
index d6b5157..c874368 100644
--- a/web/app/libs/uoj-html-lib.php
+++ b/web/app/libs/uoj-html-lib.php
@@ -417,22 +417,6 @@ function echoSubmissionContent($submission, $requirement) {
echo '';
echo '';
echo '';
- } else if ($req['type'] == "remote submission") {
- $remote_provider = UOJRemoteProblem::$providers[$req['name']];
- $content = '';
-
- if ($req['name'] == 'luogu') {
- $content .= '远端评测 ID:' .
- HTML::tag(
- 'a',
- [
- 'href' => $remote_provider['url'] . '/record/' . $config['luogu_submission_id']
- ],
- 'R' . $config['luogu_submission_id']
- ) . '
';
- }
-
- HTML::echoPanel('', '远端评测记录', $content);
}
}
diff --git a/web/app/models/UOJProblem.php b/web/app/models/UOJProblem.php
index 2db8eb6..ee0da7d 100644
--- a/web/app/models/UOJProblem.php
+++ b/web/app/models/UOJProblem.php
@@ -562,8 +562,6 @@ class UOJProblem {
foreach ($submission_requirement as $req) {
if ($req['type'] == 'source code') {
return false;
- } else if ($req['type'] == 'remote submission') {
- return false;
}
}
diff --git a/web/app/models/UOJRemoteProblem.php b/web/app/models/UOJRemoteProblem.php
index caeb50a..636984a 100644
--- a/web/app/models/UOJRemoteProblem.php
+++ b/web/app/models/UOJRemoteProblem.php
@@ -14,6 +14,7 @@ class UOJRemoteProblem {
'ограничение по времени на тест',
],
'languages' => ['C', 'C++', 'C++17', 'C++20', 'Java17', 'Pascal', 'Python2', 'Python3'],
+ 'submit_type' => ['bot'],
],
'atcoder' => [
'name' => 'AtCoder',
@@ -24,6 +25,7 @@ class UOJRemoteProblem {
'指定されたタスクが見つかりません',
],
'languages' => ['C', 'C++', 'Java11', 'Python3', 'Pascal'],
+ 'submit_type' => ['bot'],
],
'uoj' => [
'name' => 'UniversalOJ',
@@ -33,18 +35,21 @@ class UOJRemoteProblem {
'未找到该页面',
],
'languages' => ['C', 'C++03', 'C++11', 'C++', 'C++17', 'C++20', 'Python3', 'Python2.7', 'Java8', 'Java11', 'Java17', 'Pascal'],
+ 'submit_type' => ['bot'],
],
'loj' => [
'name' => 'LibreOJ',
'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'],
],
'luogu' => [
'name' => '洛谷',
'short_name' => '洛谷',
'url' => 'https://www.luogu.com.cn',
- 'languages' => [],
+ 'languages' => ['C', 'C++98', 'C++11', 'C++', 'C++17', 'C++20', 'Python3', 'Java8', 'Pascal'],
+ 'submit_type' => ['my'],
],
];
@@ -460,15 +465,6 @@ class UOJRemoteProblem {
public static function getSubmissionRequirements($oj) {
$remote_provider = UOJRemoteProblem::$providers[$oj];
- if ($oj == 'luogu') {
- return [
- [
- "name" => "luogu",
- "type" => "remote submission",
- ]
- ];
- }
-
return [
[
"name" => "answer",
diff --git a/web/app/models/UOJSubmission.php b/web/app/models/UOJSubmission.php
index ad9f20f..6ae9327 100644
--- a/web/app/models/UOJSubmission.php
+++ b/web/app/models/UOJSubmission.php
@@ -89,8 +89,7 @@ class UOJSubmission {
$content['config'][] = ['problem_id', UOJProblem::info('id')];
if (UOJProblem::info('type') == 'remote') {
- $remote_online_judge = UOJProblem::cur()->getExtraConfig('remote_online_judge');
- $content['config'][] = ['remote_online_judge', $remote_online_judge];
+ $content['config'][] = ['remote_online_judge', UOJProblem::cur()->getExtraConfig('remote_online_judge')];
$content['config'][] = ['remote_problem_id', UOJProblem::cur()->getExtraConfig('remote_problem_id')];
}
diff --git a/web/app/models/UOJSubmissionLikeTrait.php b/web/app/models/UOJSubmissionLikeTrait.php
index c4126eb..5166ff6 100644
--- a/web/app/models/UOJSubmissionLikeTrait.php
+++ b/web/app/models/UOJSubmissionLikeTrait.php
@@ -210,24 +210,6 @@ trait UOJSubmissionLikeTrait {
EOD;
- } else if ($req['type'] == "remote submission") {
- $remote_provider = UOJRemoteProblem::$providers[$req['name']];
- $content = '';
-
- if ($req['name'] == 'luogu') {
- $content .= HTML::tag('div', [], [
- '远端评测 ID:',
- HTML::tag(
- 'a',
- [
- 'href' => "{$remote_provider['url']}/record/{$config['luogu_submission_id']}"
- ],
- 'R' . $config['luogu_submission_id']
- ),
- ]);
- }
-
- HTML::echoPanel('mb-3', '远端评测记录', $content);
}
}
$zip_file->close();
diff --git a/web/js/uoj.js b/web/js/uoj.js
index 41bf66a..652b357 100644
--- a/web/js/uoj.js
+++ b/web/js/uoj.js
@@ -947,6 +947,108 @@ $.fn.text_file_form_group = function(name, text) {
});
}
+// remote judge submit type group
+$.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 div_submit_type_bot_id = 'div-submit_type_bot';
+ var div_submit_type_my_id = 'div-submit_type_my';
+
+ var input_submit_type_bot = $('');
+ var input_submit_type_my = $('');
+ var input_my_account_data = $('');
+
+ var div_submit_type_bot = $('')
+ .append('将使用公用账号提交本题。
');
+ var div_submit_type_my = $('')
+ .append('将使用您的账号提交本题。
');
+
+ input_submit_type_bot.click(function() {
+ div_submit_type_my.hide('fast');
+ div_submit_type_bot.show('fast');
+ });
+ input_submit_type_my.click(function() {
+ div_submit_type_bot.hide('fast');
+ div_submit_type_my.show('fast');
+ });
+
+ if (submit_type[0] == 'bot') {
+ div_submit_type_my.hide();
+ input_submit_type_bot[0].checked = true;
+ } else if (submit_type[0] == 'my') {
+ div_submit_type_bot.hide();
+ input_submit_type_my[0].checked = true;
+ }
+
+ if (submit_type.indexOf('bot') == -1) {
+ input_submit_type_bot.attr('disabled', 'disabled');
+ }
+ if (submit_type.indexOf('my') == -1) {
+ input_submit_type_my.attr('disabled', 'disabled');
+ }
+
+ if (oj == 'luogu') {
+ var luogu_account_data = {"_uid": "", "__clientid": ""};
+ var input_luogu_uid = $('');
+ var input_luogu_clientid = $('');
+
+ if ('localStorage' in window) {
+ try {
+ var luogu_account_data_str = localStorage.getItem('uoj_remote_judge_luogu_account_data');
+ if (luogu_account_data_str) {
+ luogu_account_data = JSON.parse(luogu_account_data_str);
+ }
+ } catch (e) {}
+
+ var save_luogu_account_data = function() {
+ localStorage.setItem('uoj_remote_judge_luogu_account_data', JSON.stringify(luogu_account_data));
+ }
+ } else {
+ var save_luogu_account_data = function() {};
+ }
+
+ input_luogu_uid.change(function() {
+ luogu_account_data._uid = $(this).val();
+ input_my_account_data.val(JSON.stringify(luogu_account_data));
+ save_luogu_account_data();
+ });
+
+ input_luogu_clientid.change(function() {
+ luogu_account_data.__clientid = $(this).val();
+ input_my_account_data.val(JSON.stringify(luogu_account_data));
+ save_luogu_account_data();
+ });
+
+ input_my_account_data.val(JSON.stringify(luogu_account_data));
+
+ div_submit_type_my.append(
+ $('')
+ .append($('').append(''))
+ .append($('').append(input_luogu_uid))
+ .append($('').append($('').append('请填入 Cookie 中的 _uid
。')))
+ ).append(
+ $('')
+ .append($('').append(''))
+ .append($('').append(input_luogu_clientid))
+ .append($('').append($('').append('请填入 Cookie 中的 __clientid
。')))
+ ).append(input_my_account_data);
+ }
+
+ $(this).append(
+ $('').append(
+ $('')
+ .append(input_submit_type_bot)
+ .append($('').append(' 公用账号'))
+ ).append(
+ $('')
+ .append(input_submit_type_my)
+ .append($('').append(' 自有账号'))
+ )
+ ).append(div_submit_type_bot).append(div_submit_type_my);
+ });
+}
+
// custom test
function custom_test_onsubmit(response_text, div_result, url) {
if (response_text != '') {
From 7bc29d60c910807cf4ff98a8f30b8987789971a8 Mon Sep 17 00:00:00 2001
From: Baoshuo
Date: Fri, 3 Feb 2023 09:38:27 +0800
Subject: [PATCH 4/7] refactor(remote_judger): remote_submit_type
---
remote_judger/src/daemon.ts | 6 +-
remote_judger/src/providers/atcoder.ts | 4 +
remote_judger/src/providers/codeforces.ts | 4 +
remote_judger/src/providers/loj.ts | 4 +
remote_judger/src/providers/uoj.ts | 4 +
remote_judger/src/vjudge.ts | 174 ++++++++++++++--------
6 files changed, 130 insertions(+), 66 deletions(-)
diff --git a/remote_judger/src/daemon.ts b/remote_judger/src/daemon.ts
index 0e88968..bfa52c5 100644
--- a/remote_judger/src/daemon.ts
+++ b/remote_judger/src/daemon.ts
@@ -7,6 +7,7 @@ import * as TIME from './utils/time';
import { apply } from './vjudge';
import path from 'path';
import child from 'child_process';
+import htmlspecialchars from './utils/htmlspecialchars';
proxy(superagent);
@@ -146,7 +147,8 @@ export default async function daemon(config: UOJConfig) {
config.remote_problem_id,
config.answer_language,
code,
- judge_time
+ judge_time,
+ config
);
} catch (err) {
await request('/submit', {
@@ -157,7 +159,7 @@ export default async function daemon(config: UOJConfig) {
status: 'Judged',
score: 0,
error: 'Judgment Failed',
- details: `No details.`,
+ details: `${htmlspecialchars(err.message)}`,
}),
judge_time,
});
diff --git a/remote_judger/src/providers/atcoder.ts b/remote_judger/src/providers/atcoder.ts
index f96bde7..757de3a 100644
--- a/remote_judger/src/providers/atcoder.ts
+++ b/remote_judger/src/providers/atcoder.ts
@@ -73,6 +73,10 @@ export default class AtcoderProvider implements IBasicProvider {
this.account.endpoint ||= 'https://atcoder.jp';
}
+ static constructFromAccountData(data) {
+ throw new Error('Method not implemented.');
+ }
+
cookie: string[] = ['language=en'];
csrf: string;
diff --git a/remote_judger/src/providers/codeforces.ts b/remote_judger/src/providers/codeforces.ts
index 1131c4b..76eb301 100644
--- a/remote_judger/src/providers/codeforces.ts
+++ b/remote_judger/src/providers/codeforces.ts
@@ -87,6 +87,10 @@ export default class CodeforcesProvider implements IBasicProvider {
this.account.endpoint ||= 'https://codeforces.com';
}
+ static constructFromAccountData(data) {
+ throw new Error('Method not implemented.');
+ }
+
cookie: string[] = [];
csrf: string;
diff --git a/remote_judger/src/providers/loj.ts b/remote_judger/src/providers/loj.ts
index cd79213..793b319 100644
--- a/remote_judger/src/providers/loj.ts
+++ b/remote_judger/src/providers/loj.ts
@@ -156,6 +156,10 @@ export default class LibreojProvider implements IBasicProvider {
this.account.endpoint ||= 'https://api.loj.ac.cn/api';
}
+ static constructFromAccountData(data) {
+ throw new Error('Method not implemented.');
+ }
+
get(url: string) {
logger.debug('get', url);
if (!url.includes('//')) url = `${this.account.endpoint}${url}`;
diff --git a/remote_judger/src/providers/uoj.ts b/remote_judger/src/providers/uoj.ts
index eafe22b..a7ae179 100644
--- a/remote_judger/src/providers/uoj.ts
+++ b/remote_judger/src/providers/uoj.ts
@@ -99,6 +99,10 @@ export default class UOJProvider implements IBasicProvider {
if (account.cookie) this.cookie = account.cookie;
}
+ static constructFromAccountData(data) {
+ throw new Error('Method not implemented.');
+ }
+
cookie: string[] = [];
csrf: string;
diff --git a/remote_judger/src/vjudge.ts b/remote_judger/src/vjudge.ts
index 20aab01..dda6303 100644
--- a/remote_judger/src/vjudge.ts
+++ b/remote_judger/src/vjudge.ts
@@ -8,11 +8,7 @@ const logger = new Logger('vjudge');
class AccountService {
api: IBasicProvider;
- constructor(
- public Provider: BasicProvider,
- public account: RemoteAccount,
- private request: any
- ) {
+ constructor(public Provider: BasicProvider, public account: RemoteAccount) {
this.api = new Provider(account);
this.main().catch(e =>
logger.error(`Error occured in ${account.type}/${account.handle}`, e)
@@ -24,7 +20,81 @@ class AccountService {
problem_id: string,
language: string,
code: string,
- judge_time: string
+ next,
+ end
+ ) {
+ try {
+ const rid = await this.api.submitProblem(
+ problem_id,
+ language,
+ code,
+ id,
+ next,
+ end
+ );
+
+ if (!rid) return;
+
+ await this.api.waitForSubmission(problem_id, rid, next, end);
+ } catch (e) {
+ logger.error(e);
+
+ await end({ error: true, status: 'Judgment Failed', message: e.message });
+ }
+ }
+
+ async login() {
+ const login = await this.api.ensureLogin();
+ if (login === true) {
+ logger.info(`${this.account.type}/${this.account.handle}: logged in`);
+ return true;
+ }
+ logger.warn(
+ `${this.account.type}/${this.account.handle}: login fail`,
+ login || ''
+ );
+ return false;
+ }
+
+ async main() {
+ const res = await this.login();
+ if (!res) return;
+ setInterval(() => this.login(), Time.hour);
+ }
+}
+
+class VJudge {
+ private p_imports: Record = {};
+ private providers: Record = {};
+
+ constructor(private request: any) {}
+
+ async importProvider(type: string) {
+ if (this.p_imports[type]) throw new Error(`duplicate provider ${type}`);
+ const provider = await import(`./providers/${type}`);
+
+ this.p_imports[type] = provider.default;
+ }
+
+ async addProvider(type: string) {
+ if (this.p_imports[type]) throw new Error(`duplicate provider ${type}`);
+ const provider = await import(`./providers/${type}`);
+ const account = provider.getAccountInfoFromEnv();
+
+ if (!account) throw new Error(`no account info for ${type}`);
+
+ this.p_imports[type] = provider.default;
+ this.providers[type] = new AccountService(provider.default, account);
+ }
+
+ async judge(
+ id: number,
+ type: string,
+ problem_id: string,
+ language: string,
+ code: string,
+ judge_time: string,
+ config
) {
const next = async payload => {
return await this.request('/submit', {
@@ -77,75 +147,51 @@ class AccountService {
});
};
- try {
- const rid = await this.api.submitProblem(
+ if (!config.remote_submit_type || config.remote_submit_type == 'bot') {
+ if (!this.providers[type]) throw new Error(`No provider ${type}`);
+
+ await this.providers[type].judge(
+ id,
problem_id,
language,
code,
- id,
next,
end
);
+ } else if (config.remote_submit_type == 'my') {
+ if (!this.p_imports[type]) throw new Error(`No provider ${type}`);
- if (!rid) return;
+ try {
+ const provider = this.p_imports[type].constructFromAccountData(
+ JSON.parse(config.remote_account_data)
+ );
- await this.api.waitForSubmission(problem_id, rid, next, end);
- } catch (e) {
- logger.error(e);
- await end({ error: true, status: 'Judgment Failed', message: e.message });
+ const rid = await provider.submitProblem(
+ problem_id,
+ language,
+ code,
+ id,
+ next,
+ end
+ );
+
+ if (!rid) return;
+
+ await provider.waitForSubmission(problem_id, rid, next, end);
+ } catch (e) {
+ logger.error(e);
+
+ await end({
+ error: true,
+ status: 'Judgment Failed',
+ message: e.message,
+ });
+ }
}
- }
- async login() {
- const login = await this.api.ensureLogin();
- if (login === true) {
- logger.info(`${this.account.type}/${this.account.handle}: logged in`);
- return true;
- }
- logger.warn(
- `${this.account.type}/${this.account.handle}: login fail`,
- login || ''
+ throw new Error(
+ 'Unsupported remote submit type: ' + config.remote_submit_type
);
- return false;
- }
-
- async main() {
- const res = await this.login();
- if (!res) return;
- setInterval(() => this.login(), Time.hour);
- }
-}
-
-class VJudge {
- private providers: Record = {};
-
- constructor(private request: any) {}
-
- async addProvider(type: string) {
- if (this.providers[type]) throw new Error(`duplicate provider ${type}`);
- const provider = await import(`./providers/${type}`);
- const account = provider.getAccountInfoFromEnv();
-
- if (!account) throw new Error(`no account info for ${type}`);
-
- this.providers[type] = new AccountService(
- provider.default,
- account,
- this.request
- );
- }
-
- async judge(
- id: number,
- type: string,
- problem_id: string,
- language: string,
- code: string,
- judge_time: string
- ) {
- if (!this.providers[type]) throw new Error(`no provider ${type}`);
-
- await this.providers[type].judge(id, problem_id, language, code, judge_time);
}
}
From 5cfd8d58468c6ecd203d96f8f303b8d53bdd961e Mon Sep 17 00:00:00 2001
From: Baoshuo
Date: Fri, 3 Feb 2023 10:47:58 +0800
Subject: [PATCH 5/7] feat(remote_judger): add luogu
---
remote_judger/src/interface.ts | 4 +-
remote_judger/src/providers/luogu.ts | 281 +++++++++++++++++++++++++
remote_judger/src/utils/flattenDeep.ts | 6 +
remote_judger/src/vjudge.ts | 14 +-
web/app/controllers/problem.php | 4 +-
web/js/uoj.js | 16 +-
6 files changed, 309 insertions(+), 16 deletions(-)
create mode 100644 remote_judger/src/providers/luogu.ts
create mode 100644 remote_judger/src/utils/flattenDeep.ts
diff --git a/remote_judger/src/interface.ts b/remote_judger/src/interface.ts
index 77af4c5..579d507 100644
--- a/remote_judger/src/interface.ts
+++ b/remote_judger/src/interface.ts
@@ -1,8 +1,8 @@
export interface RemoteAccount {
type: string;
cookie?: string[];
- handle: string;
- password: string;
+ handle?: string;
+ password?: string;
endpoint?: string;
proxy?: string;
}
diff --git a/remote_judger/src/providers/luogu.ts b/remote_judger/src/providers/luogu.ts
new file mode 100644
index 0000000..7932499
--- /dev/null
+++ b/remote_judger/src/providers/luogu.ts
@@ -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.',
+ });
+ }
+}
diff --git a/remote_judger/src/utils/flattenDeep.ts b/remote_judger/src/utils/flattenDeep.ts
new file mode 100644
index 0000000..92e68aa
--- /dev/null
+++ b/remote_judger/src/utils/flattenDeep.ts
@@ -0,0 +1,6 @@
+const flattenDeep = arr =>
+ Array.isArray(arr)
+ ? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
+ : [arr];
+
+export default flattenDeep;
diff --git a/remote_judger/src/vjudge.ts b/remote_judger/src/vjudge.ts
index dda6303..50d611f 100644
--- a/remote_judger/src/vjudge.ts
+++ b/remote_judger/src/vjudge.ts
@@ -139,8 +139,9 @@ class VJudge {
details:
payload.details ||
'' +
- `ID = ${payload.id || 'None'}` +
- `VERDICT = ${payload.status}` +
+ `REMOTE_SUBMISSION_ID = ${
+ payload.id || 'None'
+ }\nVERDICT = ${payload.status}` +
'
',
}),
judge_time,
@@ -187,11 +188,11 @@ class VJudge {
message: e.message,
});
}
+ } else {
+ throw new Error(
+ 'Unsupported remote submit type: ' + config.remote_submit_type
+ );
}
-
- throw new Error(
- 'Unsupported remote submit type: ' + config.remote_submit_type
- );
}
}
@@ -202,6 +203,7 @@ export async function apply(request: any) {
await vjudge.addProvider('atcoder');
await vjudge.addProvider('uoj');
await vjudge.addProvider('loj');
+ await vjudge.importProvider('luogu');
return vjudge;
}
diff --git a/web/app/controllers/problem.php b/web/app/controllers/problem.php
index 29d2e00..ed3c704 100644
--- a/web/app/controllers/problem.php
+++ b/web/app/controllers/problem.php
@@ -110,7 +110,9 @@ 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['no_rejudge'] = true;
+ if ($submit_type != 'bot') {
+ $content['no_rejudge'] = true;
+ }
$content['config'][] = ['remote_submit_type', $submit_type];
$content['config'][] = ['remote_account_data', $_POST['answer_remote_account_data']];
}
diff --git a/web/js/uoj.js b/web/js/uoj.js
index 652b357..3e1f1c2 100644
--- a/web/js/uoj.js
+++ b/web/js/uoj.js
@@ -989,9 +989,9 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
}
if (oj == 'luogu') {
- var luogu_account_data = {"_uid": "", "__clientid": ""};
+ var luogu_account_data = {"_uid": "", "__client_id": ""};
var input_luogu_uid = $('');
- var input_luogu_clientid = $('');
+ var input_luogu_client_id = $('');
if ('localStorage' in window) {
try {
@@ -1014,13 +1014,15 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
save_luogu_account_data();
});
- input_luogu_clientid.change(function() {
- luogu_account_data.__clientid = $(this).val();
+ input_luogu_client_id.change(function() {
+ luogu_account_data.__client_id = $(this).val();
input_my_account_data.val(JSON.stringify(luogu_account_data));
save_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(
$('')
@@ -1029,9 +1031,9 @@ $.fn.remote_submit_type_group = function(oj, pid, url, submit_type) {
.append($('').append($('').append('请填入 Cookie 中的 _uid
。')))
).append(
$('')
- .append($('').append(''))
- .append($('').append(input_luogu_clientid))
- .append($('').append($('').append('请填入 Cookie 中的 __clientid
。')))
+ .append($('').append(''))
+ .append($('').append(input_luogu_client_id))
+ .append($('').append($('').append('请填入 Cookie 中的 __client_id
。')))
).append(input_my_account_data);
}
From 4de24c44e0d0554ed4b605af99cd1f4d11a29fc0 Mon Sep 17 00:00:00 2001
From: Baoshuo
Date: Fri, 3 Feb 2023 11:35:43 +0800
Subject: [PATCH 6/7] fix(remote_judger/luogu): sec cookie
---
remote_judger/package-lock.json | 42 +++++++++++++++
remote_judger/package.json | 2 +
remote_judger/src/providers/luogu.ts | 72 +++++++++++++++++---------
remote_judger/src/utils/flattenDeep.ts | 6 ---
4 files changed, 92 insertions(+), 30 deletions(-)
delete mode 100644 remote_judger/src/utils/flattenDeep.ts
diff --git a/remote_judger/package-lock.json b/remote_judger/package-lock.json
index d66645f..ee07390 100644
--- a/remote_judger/package-lock.json
+++ b/remote_judger/package-lock.json
@@ -12,6 +12,7 @@
"crlf-normalize": "^1.0.18",
"fs-extra": "^11.1.0",
"jsdom": "^21.0.0",
+ "lodash.flattendeep": "^4.4.0",
"math-sum": "^2.0.0",
"reggol": "^1.3.4",
"superagent": "^8.0.6",
@@ -21,6 +22,7 @@
"@types/fs-extra": "^11.0.1",
"@types/js-yaml": "^4.0.5",
"@types/jsdom": "^20.0.1",
+ "@types/lodash.flattendeep": "^4.4.7",
"@types/node": "^18.11.18",
"@types/superagent": "^4.1.16",
"@types/superagent-proxy": "^3.0.0",
@@ -78,6 +80,21 @@
"@types/node": "*"
}
},
+ "node_modules/@types/lodash": {
+ "version": "4.14.191",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
+ "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
+ "dev": true
+ },
+ "node_modules/@types/lodash.flattendeep": {
+ "version": "4.4.7",
+ "resolved": "https://registry.npmjs.org/@types/lodash.flattendeep/-/lodash.flattendeep-4.4.7.tgz",
+ "integrity": "sha512-1h6GW/AeZw/Wej6uxrqgmdTDZX1yFS39lRsXYkg+3kWvOWWrlGCI6H7lXxlUHOzxDT4QeYGmgPpQ3BX9XevzOg==",
+ "dev": true,
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
"node_modules/@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
@@ -790,6 +807,11 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lodash.flattendeep": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
+ "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ=="
+ },
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -1567,6 +1589,21 @@
"@types/node": "*"
}
},
+ "@types/lodash": {
+ "version": "4.14.191",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
+ "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
+ "dev": true
+ },
+ "@types/lodash.flattendeep": {
+ "version": "4.4.7",
+ "resolved": "https://registry.npmjs.org/@types/lodash.flattendeep/-/lodash.flattendeep-4.4.7.tgz",
+ "integrity": "sha512-1h6GW/AeZw/Wej6uxrqgmdTDZX1yFS39lRsXYkg+3kWvOWWrlGCI6H7lXxlUHOzxDT4QeYGmgPpQ3BX9XevzOg==",
+ "dev": true,
+ "requires": {
+ "@types/lodash": "*"
+ }
+ },
"@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
@@ -2113,6 +2150,11 @@
"type-check": "~0.3.2"
}
},
+ "lodash.flattendeep": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
+ "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ=="
+ },
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
diff --git a/remote_judger/package.json b/remote_judger/package.json
index 86ff0e9..3d1d50d 100644
--- a/remote_judger/package.json
+++ b/remote_judger/package.json
@@ -15,6 +15,7 @@
"crlf-normalize": "^1.0.18",
"fs-extra": "^11.1.0",
"jsdom": "^21.0.0",
+ "lodash.flattendeep": "^4.4.0",
"math-sum": "^2.0.0",
"reggol": "^1.3.4",
"superagent": "^8.0.6",
@@ -24,6 +25,7 @@
"@types/fs-extra": "^11.0.1",
"@types/js-yaml": "^4.0.5",
"@types/jsdom": "^20.0.1",
+ "@types/lodash.flattendeep": "^4.4.7",
"@types/node": "^18.11.18",
"@types/superagent": "^4.1.16",
"@types/superagent-proxy": "^3.0.0",
diff --git a/remote_judger/src/providers/luogu.ts b/remote_judger/src/providers/luogu.ts
index 7932499..237b291 100644
--- a/remote_judger/src/providers/luogu.ts
+++ b/remote_judger/src/providers/luogu.ts
@@ -4,7 +4,7 @@ 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';
+import flattenDeep from 'lodash.flattendeep';
proxy(superagent);
const logger = new Logger('remote/luogu');
@@ -106,6 +106,23 @@ export default class LuoguProvider implements IBasicProvider {
return req;
}
+ async safeGet(url: string) {
+ const res = await this.get(url);
+
+ if (res.text.startsWith('