mirror of
https://github.com/renbaoshuo/UOJ-Luogu-RemoteJudge.git
synced 2024-11-23 19:38:42 +00:00
feat: luogu remote judger
This commit is contained in:
parent
e92475e70a
commit
ce4fdc1523
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules/
|
14
luogu_remote_judger/.prettierrc
Normal file
14
luogu_remote_judger/.prettierrc
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"arrowParens": "avoid",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "lf",
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 120,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"organizeImportsSkipDestructiveCodeActions": true
|
||||
}
|
2
luogu_remote_judger/add_judger.sql
Normal file
2
luogu_remote_judger/add_judger.sql
Normal file
@ -0,0 +1,2 @@
|
||||
USE `app_uoj233`;
|
||||
insert into judger_info (judger_name, password, ip) values ('luogu_remote_judger', '_judger_password_', 's2oj-luogu-remote-judger');
|
181
luogu_remote_judger/daemon.js
Normal file
181
luogu_remote_judger/daemon.js
Normal file
@ -0,0 +1,181 @@
|
||||
import fs from 'fs-extra';
|
||||
import superagent from 'superagent';
|
||||
import path from 'node:path';
|
||||
import child from 'node:child_process';
|
||||
|
||||
import Luogu, { getAccountInfoFromEnv } from './luogu.js';
|
||||
import Logger from './utils/logger.js';
|
||||
import sleep from './utils/sleep.js';
|
||||
import * as TIME from './utils/time.js';
|
||||
import htmlspecialchars from './utils/htmlspecialchars.js';
|
||||
|
||||
const logger = new Logger('daemon');
|
||||
|
||||
async function daemon(config) {
|
||||
function request(url, data) {
|
||||
const req_url = `${config.server_url}/judge${url}`;
|
||||
|
||||
logger.debug('request', req_url, data);
|
||||
|
||||
return superagent
|
||||
.post(req_url)
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.send(
|
||||
Object.entries({
|
||||
judger_name: config.judger_name,
|
||||
password: config.password,
|
||||
...data,
|
||||
})
|
||||
.map(([k, v]) => `${k}=${encodeURIComponent(typeof v === 'string' ? v : JSON.stringify(v))}`)
|
||||
.join('&')
|
||||
);
|
||||
}
|
||||
|
||||
const luogu = new Luogu(getAccountInfoFromEnv());
|
||||
|
||||
logger.info('Daemon started.');
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
await sleep(TIME.second);
|
||||
|
||||
const { text, error } = await request('/submit');
|
||||
|
||||
if (error) {
|
||||
logger.error('/submit', error.message);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (text.startsWith('Nothing to judge')) {
|
||||
logger.debug('Nothing to judge.');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = JSON.parse(text);
|
||||
const { id, content, judge_time } = data;
|
||||
const config = Object.fromEntries(content.config);
|
||||
const tmpdir = `/tmp/s2oj_rmj/${id}/`;
|
||||
|
||||
if (config.test_sample_only === 'on') {
|
||||
await request('/submit', {
|
||||
submit: 1,
|
||||
fetch_new: 0,
|
||||
id,
|
||||
result: JSON.stringify({
|
||||
status: 'Judged',
|
||||
score: 100,
|
||||
time: 0,
|
||||
memory: 0,
|
||||
details: '<info-block>Sample test is not available.</info-block>',
|
||||
}),
|
||||
judge_time,
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
fs.ensureDirSync(tmpdir);
|
||||
|
||||
let code = '';
|
||||
|
||||
try {
|
||||
// =========================
|
||||
// Download source code
|
||||
// =========================
|
||||
|
||||
logger.debug('Downloading source code for ' + id);
|
||||
|
||||
const zipFilePath = path.resolve(tmpdir, 'all.zip');
|
||||
const res = request(`/download${content.file_name}`);
|
||||
const stream = fs.createWriteStream(zipFilePath);
|
||||
|
||||
res.pipe(stream);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
stream.on('finish', resolve);
|
||||
stream.on('error', reject);
|
||||
});
|
||||
|
||||
// =========================
|
||||
// Extract source code
|
||||
// =========================
|
||||
|
||||
logger.debug('Extracting source code for ' + id);
|
||||
|
||||
const extractedPath = path.resolve(tmpdir, 'all');
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
child.exec(`unzip ${zipFilePath} -d ${extractedPath}`, e => {
|
||||
if (e) reject(e);
|
||||
else resolve(true);
|
||||
});
|
||||
});
|
||||
|
||||
// =========================
|
||||
// Read source code
|
||||
// =========================
|
||||
logger.debug('Reading source code.', id);
|
||||
|
||||
const sourceCodePath = path.resolve(extractedPath, 'answer.code');
|
||||
|
||||
code = fs.readFileSync(sourceCodePath, 'utf-8');
|
||||
} catch (e) {
|
||||
await request('/submit', {
|
||||
submit: 1,
|
||||
fetch_new: 0,
|
||||
id,
|
||||
result: JSON.stringify({
|
||||
status: 'Judged',
|
||||
score: 0,
|
||||
error: 'Judgement Failed',
|
||||
details: `<error>Failed to download and extract source code.</error>`,
|
||||
}),
|
||||
judge_time,
|
||||
});
|
||||
|
||||
logger.error('Failed to download and extract source code.', id, e.message);
|
||||
|
||||
fs.removeSync(tmpdir);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// =========================
|
||||
// Start judging
|
||||
// =========================
|
||||
|
||||
logger.info('Start judging', id, `(problem ${data.problem_id})`);
|
||||
|
||||
try {
|
||||
await luogu.judge(id, config.luogu_pid, config.answer_language, code, judge_time, config, request);
|
||||
} catch (err) {
|
||||
await request('/submit', {
|
||||
submit: 1,
|
||||
fetch_new: 0,
|
||||
id,
|
||||
result: JSON.stringify({
|
||||
status: 'Judged',
|
||||
score: 0,
|
||||
error: 'Judgement Failed',
|
||||
details: `<error>${htmlspecialchars(err.message)}</error>`,
|
||||
}),
|
||||
judge_time,
|
||||
});
|
||||
|
||||
logger.error('Judgement Failed.', id, err.message);
|
||||
|
||||
fs.removeSync(tmpdir);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
fs.removeSync(tmpdir);
|
||||
} catch (err) {
|
||||
logger.error(err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default daemon;
|
16
luogu_remote_judger/entrypoint.js
Normal file
16
luogu_remote_judger/entrypoint.js
Normal file
@ -0,0 +1,16 @@
|
||||
import daemon from './daemon.js';
|
||||
|
||||
const {
|
||||
UOJ_PROTOCOL = 'http',
|
||||
UOJ_HOST = 'uoj-web',
|
||||
UOJ_JUDGER_NAME = 'luogu_remote_judger',
|
||||
UOJ_JUDGER_PASSWORD = '',
|
||||
} = process.env;
|
||||
|
||||
const UOJ_BASEURL = `${UOJ_PROTOCOL}://${UOJ_HOST}`;
|
||||
|
||||
daemon({
|
||||
server_url: UOJ_BASEURL,
|
||||
judger_name: UOJ_JUDGER_NAME,
|
||||
password: UOJ_JUDGER_PASSWORD,
|
||||
});
|
377
luogu_remote_judger/luogu.js
Normal file
377
luogu_remote_judger/luogu.js
Normal file
@ -0,0 +1,377 @@
|
||||
import superagent from 'superagent';
|
||||
|
||||
import Logger from './utils/logger.js';
|
||||
import sleep from './utils/sleep.js';
|
||||
import htmlspecialchars from './utils/htmlspecialchars.js';
|
||||
|
||||
const logger = new Logger('remote/luogu');
|
||||
|
||||
const USER_AGENT = 'UniversalOJ/1.0 UOJ-Luogu-RemoteJudge/1.0 ( https://github.com/renbaoshuo/UOJ-Luogu-RemoteJudge )';
|
||||
const HTTP_ERROR_MAP = {
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
402: 'Payment Required',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
405: 'Method Not Allowed',
|
||||
500: 'Internal Server Error',
|
||||
502: 'Bad Gateway',
|
||||
503: 'Service Unavailable',
|
||||
504: 'Gateway Timeout',
|
||||
};
|
||||
const STATUS_MAP = [
|
||||
'Waiting',
|
||||
'Judging',
|
||||
'Compile Error',
|
||||
'Output Limit Exceeded',
|
||||
'Memory Limit Exceeded',
|
||||
'Time Limit Exceeded',
|
||||
'Wrong Answer',
|
||||
'Runtime Error',
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
'Judgment Failed',
|
||||
'Accepted',
|
||||
0,
|
||||
'Wrong Answer', // WA
|
||||
];
|
||||
const LANGS_MAP = {
|
||||
C: {
|
||||
id: 'c/99/gcc',
|
||||
name: 'C',
|
||||
comment: '//',
|
||||
},
|
||||
'C++': {
|
||||
id: 'cxx/98/gcc',
|
||||
name: 'C++ 98',
|
||||
comment: '//',
|
||||
},
|
||||
'C++11': {
|
||||
id: 'cxx/11/gcc',
|
||||
name: 'C++ 11',
|
||||
comment: '//',
|
||||
},
|
||||
Python3: {
|
||||
id: 'python3/c',
|
||||
name: 'Python 3',
|
||||
comment: '#',
|
||||
},
|
||||
Java8: {
|
||||
id: 'java/8',
|
||||
name: 'Java 8',
|
||||
comment: '//',
|
||||
},
|
||||
Pascal: {
|
||||
id: 'pascal/fpc',
|
||||
name: 'Pascal',
|
||||
comment: '//',
|
||||
},
|
||||
};
|
||||
|
||||
function buildLuoguTestCaseInfoBlock(test) {
|
||||
const attrs = [
|
||||
['num', test.id],
|
||||
['info', STATUS_MAP[test.status]],
|
||||
['time', test.time || -1],
|
||||
['memory', test.memory || -1],
|
||||
['score', test.score || ''],
|
||||
]
|
||||
.map(o => `${o[0]}="${o[1]}"`)
|
||||
.join(' ');
|
||||
const desc = htmlspecialchars(test.description || '');
|
||||
|
||||
return `<test ${attrs}><res>${desc}</res></test>`;
|
||||
}
|
||||
|
||||
export default class Luogu {
|
||||
account;
|
||||
|
||||
constructor(account) {
|
||||
if (!account) {
|
||||
throw new Error('No account info provided');
|
||||
}
|
||||
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
get(url) {
|
||||
if (!url.includes('//')) {
|
||||
url = `${this.account.endpoint || 'https://open-v1.lgapi.cn'}${url}`;
|
||||
}
|
||||
|
||||
logger.debug('get', url, this.cookie);
|
||||
|
||||
const req = superagent
|
||||
.get(url)
|
||||
.set('User-Agent', USER_AGENT)
|
||||
.auth(this.account.username, this.account.password)
|
||||
.set('X-Requested-With', 'S2OJ Remote Judge (OpenSource Version)');
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
post(url) {
|
||||
if (!url.includes('//')) {
|
||||
url = `${this.account.endpoint || 'https://open-v1.lgapi.cn'}${url}`;
|
||||
}
|
||||
|
||||
logger.debug('post', url);
|
||||
|
||||
const req = superagent
|
||||
.post(url)
|
||||
.set('User-Agent', USER_AGENT)
|
||||
.auth(this.account.username, this.account.password)
|
||||
.set('X-Requested-With', 'S2OJ Remote Judge (OpenSource Version)');
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
async submitProblem(id, lang, code, submissionId, next, end) {
|
||||
if (code.length < 10) {
|
||||
await 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('/judge/problem')
|
||||
.send({
|
||||
pid: id,
|
||||
code,
|
||||
lang: programType.id,
|
||||
o2: 1,
|
||||
trackId: submissionId,
|
||||
})
|
||||
.ok(status => true);
|
||||
|
||||
if (HTTP_ERROR_MAP[result.status]) {
|
||||
await end({
|
||||
error: true,
|
||||
status: 'Judgment Failed',
|
||||
message: `[Luogu] ${HTTP_ERROR_MAP[result.status]}.`,
|
||||
});
|
||||
|
||||
logger.error('submitProblem', result.status, HTTP_ERROR_MAP[result.status]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.body.requestId;
|
||||
}
|
||||
|
||||
async waitForSubmission(id, next, end) {
|
||||
let fail = 0;
|
||||
let count = 0;
|
||||
|
||||
while (count < 360 && fail < 60) {
|
||||
await sleep(500);
|
||||
|
||||
count++;
|
||||
|
||||
try {
|
||||
const result = await this.get(`/judge/result?id=${id}`);
|
||||
|
||||
if (HTTP_ERROR_MAP[result.status]) {
|
||||
await end({
|
||||
error: true,
|
||||
status: 'Judgment Failed',
|
||||
message: `[Luogu] ${HTTP_ERROR_MAP[result.status]}.`,
|
||||
});
|
||||
|
||||
logger.error('submitProblem', result.status, HTTP_ERROR_MAP[result.status]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = result.body.data;
|
||||
|
||||
if (result.status == 204) {
|
||||
await next({ status: '[Luogu] Judging' });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.status == 200 && !data) {
|
||||
return await end({
|
||||
error: true,
|
||||
id,
|
||||
status: 'Judgment Failed',
|
||||
message: 'Failed to fetch submission details.',
|
||||
});
|
||||
}
|
||||
|
||||
if (data.compile && data.compile.success === false) {
|
||||
return await end({
|
||||
error: true,
|
||||
id,
|
||||
status: 'Compile Error',
|
||||
message: data.compile.message,
|
||||
});
|
||||
}
|
||||
|
||||
if (!data.judge?.subtasks) continue;
|
||||
|
||||
const finishedTestCases = Object.entries(data.judge.subtasks)
|
||||
.map(o => o[1])
|
||||
.reduce(
|
||||
(acc, sub) =>
|
||||
acc +
|
||||
Object.entries(sub.cases)
|
||||
.map(o => o[1])
|
||||
.filter(test => test.status >= 2).length,
|
||||
0
|
||||
);
|
||||
|
||||
await next({
|
||||
status: `[Luogu] Judging (${finishedTestCases} judged)`,
|
||||
});
|
||||
|
||||
if (data.status < 2) continue;
|
||||
|
||||
logger.info('RecordID:', id, 'done');
|
||||
|
||||
let details = '';
|
||||
|
||||
details += '<tests>';
|
||||
|
||||
if (data.judge.subtasks.length === 1) {
|
||||
details += Object.entries(data.judge.subtasks[0].cases)
|
||||
.map(o => o[1])
|
||||
.map(buildLuoguTestCaseInfoBlock)
|
||||
.join('\n');
|
||||
} else {
|
||||
details += Object.entries(data.judge.subtasks)
|
||||
.map(o => o[1])
|
||||
.map(
|
||||
(subtask, index) =>
|
||||
`<subtask num="${index}" info="${STATUS_MAP[subtask.status]}" time="${subtask.time || -1}" memory="${
|
||||
subtask.memory || -1
|
||||
}" score="${subtask.score || ''}">${Object.entries(subtask.cases)
|
||||
.map(o => o[1])
|
||||
.map(buildLuoguTestCaseInfoBlock)
|
||||
.join('\n')}</subtask>`
|
||||
)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
details += '</tests>';
|
||||
|
||||
return await end({
|
||||
id,
|
||||
status: STATUS_MAP[data.judge.status],
|
||||
score:
|
||||
STATUS_MAP[data.judge.status] === 'Accepted'
|
||||
? 100
|
||||
: // Workaround for UOJ feature
|
||||
Math.min(97, data.judge.score),
|
||||
time: data.judge.time,
|
||||
memory: data.judge.memory,
|
||||
details,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
return await end({
|
||||
error: true,
|
||||
id,
|
||||
status: 'Judgment Failed',
|
||||
message: 'Failed to fetch submission details.',
|
||||
});
|
||||
}
|
||||
|
||||
async judge(id, pid, lang, code, judge_time, config, request) {
|
||||
const next = payload =>
|
||||
request('/submit', {
|
||||
'update-status': 1,
|
||||
fetch_new: 0,
|
||||
id,
|
||||
status: payload.status || (payload.test_id ? `Judging Test #${payload.test_id}` : 'Judging'),
|
||||
});
|
||||
|
||||
const end = payload => {
|
||||
if (payload.error) {
|
||||
return request('/submit', {
|
||||
submit: 1,
|
||||
fetch_new: 0,
|
||||
id,
|
||||
result: JSON.stringify({
|
||||
status: 'Judged',
|
||||
score: 0,
|
||||
error: payload.status,
|
||||
details:
|
||||
payload.details ||
|
||||
'<div>' +
|
||||
`<info-block>ID = ${payload.id || 'None'}</info-block>` +
|
||||
`<error>${htmlspecialchars(payload.message)}</error>` +
|
||||
'</div>',
|
||||
}),
|
||||
judge_time,
|
||||
});
|
||||
}
|
||||
|
||||
return request('/submit', {
|
||||
submit: 1,
|
||||
fetch_new: 0,
|
||||
id,
|
||||
result: JSON.stringify({
|
||||
status: 'Judged',
|
||||
score: payload.score,
|
||||
time: payload.time,
|
||||
memory: payload.memory,
|
||||
details:
|
||||
payload.details ||
|
||||
'<div>' +
|
||||
`<info-block>REMOTE_SUBMISSION_ID = ${payload.id || 'None'}\nVERDICT = ${payload.status}</info-block>` +
|
||||
'</div>',
|
||||
}),
|
||||
judge_time,
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
const rid = await this.submitProblem(pid, lang, code, id, next, end);
|
||||
|
||||
if (!rid) return;
|
||||
|
||||
await this.waitForSubmission(rid, next, end);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
|
||||
await end({
|
||||
error: true,
|
||||
status: 'Judgment Failed',
|
||||
message: e.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getAccountInfoFromEnv() {
|
||||
const { LUOGU_API_USERNAME, LUOGU_API_PASSWORD, LUOGU_API_ENDPOINT = 'https://open-v1.lgapi.cn' } = process.env;
|
||||
|
||||
if (!LUOGU_API_USERNAME || !LUOGU_API_PASSWORD) return null;
|
||||
|
||||
const account = {
|
||||
type: 'luogu-api',
|
||||
username: LUOGU_API_USERNAME,
|
||||
password: LUOGU_API_PASSWORD,
|
||||
endpoint: LUOGU_API_ENDPOINT,
|
||||
};
|
||||
|
||||
return account;
|
||||
}
|
388
luogu_remote_judger/package-lock.json
generated
Normal file
388
luogu_remote_judger/package-lock.json
generated
Normal file
@ -0,0 +1,388 @@
|
||||
{
|
||||
"name": "s2oj-luogu-remote-judger",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "s2oj-luogu-remote-judger",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.1.1",
|
||||
"reggol": "^1.3.5",
|
||||
"superagent": "^8.0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
},
|
||||
"node_modules/cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw=="
|
||||
},
|
||||
"node_modules/cosmokit": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cosmokit/-/cosmokit-1.4.1.tgz",
|
||||
"integrity": "sha512-d3ZRpKFahJRvLbo1T4y0ELCudjk9AeDUsfgKm+iAti6yPCeoPLGNUGT4expTWsNkrSA1uk7CKhtBPiizFYvDgA=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
|
||||
"integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
|
||||
"dependencies": {
|
||||
"dezalgo": "^1.0.4",
|
||||
"hexoid": "^1.0.0",
|
||||
"once": "^1.4.0",
|
||||
"qs": "^6.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz",
|
||||
"integrity": "sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hexoid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
|
||||
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
|
||||
"integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/reggol": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/reggol/-/reggol-1.3.5.tgz",
|
||||
"integrity": "sha512-kzkzs4nhZeiphyh+amekq25/3PndZDq+5Yt8qCJqPSyMXPC1pkwhfYCQyJdXxoRz3/uqt0+VqHulagUCVY84vA==",
|
||||
"dependencies": {
|
||||
"cosmokit": "^1.4.0",
|
||||
"object-inspect": "^1.12.2",
|
||||
"supports-color": "^8.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "8.0.9",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.9.tgz",
|
||||
"integrity": "sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==",
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.4",
|
||||
"debug": "^4.3.4",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"form-data": "^4.0.0",
|
||||
"formidable": "^2.1.2",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "2.6.0",
|
||||
"qs": "^6.11.0",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.4.0 <13 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
25
luogu_remote_judger/package.json
Normal file
25
luogu_remote_judger/package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "s2oj-luogu-remote-judger",
|
||||
"version": "0.0.0",
|
||||
"description": "Luogu remote judger for S2OJ.",
|
||||
"private": true,
|
||||
"main": "entrypoint.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node entrypoint.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/renbaoshuo/S2OJ.git"
|
||||
},
|
||||
"author": "Baoshuo <i@baoshuo.ren>",
|
||||
"bugs": {
|
||||
"url": "https://github.com/renbaoshuo/S2OJ/issues"
|
||||
},
|
||||
"homepage": "https://github.com/renbaoshuo/S2OJ#readme",
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.1.1",
|
||||
"reggol": "^1.3.5",
|
||||
"superagent": "^8.0.9"
|
||||
}
|
||||
}
|
11
luogu_remote_judger/utils/htmlspecialchars.js
Normal file
11
luogu_remote_judger/utils/htmlspecialchars.js
Normal file
@ -0,0 +1,11 @@
|
||||
export default function htmlspecialchars(text) {
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, m => map[m]);
|
||||
}
|
11
luogu_remote_judger/utils/logger.js
Normal file
11
luogu_remote_judger/utils/logger.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Logger from 'reggol';
|
||||
|
||||
Logger.levels.base = process.env.DEV ? 3 : 2;
|
||||
Logger.targets[0].showTime = 'dd hh:mm:ss';
|
||||
Logger.targets[0].label = {
|
||||
align: 'right',
|
||||
width: 9,
|
||||
margin: 1,
|
||||
};
|
||||
|
||||
export default Logger;
|
5
luogu_remote_judger/utils/sleep.js
Normal file
5
luogu_remote_judger/utils/sleep.js
Normal file
@ -0,0 +1,5 @@
|
||||
export default function sleep(timeout) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => resolve(true), timeout);
|
||||
});
|
||||
}
|
5
luogu_remote_judger/utils/time.js
Normal file
5
luogu_remote_judger/utils/time.js
Normal file
@ -0,0 +1,5 @@
|
||||
export const second = 1000;
|
||||
export const minute = second * 60;
|
||||
export const hour = minute * 60;
|
||||
export const day = hour * 24;
|
||||
export const week = day * 7;
|
Loading…
Reference in New Issue
Block a user