refactor(remote_judger): remote_submit_type

This commit is contained in:
Baoshuo Ren 2023-02-03 09:38:27 +08:00
parent 2ab8d07683
commit 7bc29d60c9
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
6 changed files with 130 additions and 66 deletions

View File

@ -7,6 +7,7 @@ import * as TIME from './utils/time';
import { apply } from './vjudge'; import { apply } from './vjudge';
import path from 'path'; import path from 'path';
import child from 'child_process'; import child from 'child_process';
import htmlspecialchars from './utils/htmlspecialchars';
proxy(superagent); proxy(superagent);
@ -146,7 +147,8 @@ export default async function daemon(config: UOJConfig) {
config.remote_problem_id, config.remote_problem_id,
config.answer_language, config.answer_language,
code, code,
judge_time judge_time,
config
); );
} catch (err) { } catch (err) {
await request('/submit', { await request('/submit', {
@ -157,7 +159,7 @@ export default async function daemon(config: UOJConfig) {
status: 'Judged', status: 'Judged',
score: 0, score: 0,
error: 'Judgment Failed', error: 'Judgment Failed',
details: `<error>No details.</error>`, details: `<error>${htmlspecialchars(err.message)}</error>`,
}), }),
judge_time, judge_time,
}); });

View File

@ -73,6 +73,10 @@ export default class AtcoderProvider implements IBasicProvider {
this.account.endpoint ||= 'https://atcoder.jp'; this.account.endpoint ||= 'https://atcoder.jp';
} }
static constructFromAccountData(data) {
throw new Error('Method not implemented.');
}
cookie: string[] = ['language=en']; cookie: string[] = ['language=en'];
csrf: string; csrf: string;

View File

@ -87,6 +87,10 @@ export default class CodeforcesProvider implements IBasicProvider {
this.account.endpoint ||= 'https://codeforces.com'; this.account.endpoint ||= 'https://codeforces.com';
} }
static constructFromAccountData(data) {
throw new Error('Method not implemented.');
}
cookie: string[] = []; cookie: string[] = [];
csrf: string; csrf: string;

View File

@ -156,6 +156,10 @@ export default class LibreojProvider implements IBasicProvider {
this.account.endpoint ||= 'https://api.loj.ac.cn/api'; this.account.endpoint ||= 'https://api.loj.ac.cn/api';
} }
static constructFromAccountData(data) {
throw new Error('Method not implemented.');
}
get(url: string) { get(url: string) {
logger.debug('get', url); logger.debug('get', url);
if (!url.includes('//')) url = `${this.account.endpoint}${url}`; if (!url.includes('//')) url = `${this.account.endpoint}${url}`;

View File

@ -99,6 +99,10 @@ export default class UOJProvider implements IBasicProvider {
if (account.cookie) this.cookie = account.cookie; if (account.cookie) this.cookie = account.cookie;
} }
static constructFromAccountData(data) {
throw new Error('Method not implemented.');
}
cookie: string[] = []; cookie: string[] = [];
csrf: string; csrf: string;

View File

@ -8,11 +8,7 @@ const logger = new Logger('vjudge');
class AccountService { class AccountService {
api: IBasicProvider; api: IBasicProvider;
constructor( constructor(public Provider: BasicProvider, public account: RemoteAccount) {
public Provider: BasicProvider,
public account: RemoteAccount,
private request: any
) {
this.api = new Provider(account); this.api = new Provider(account);
this.main().catch(e => this.main().catch(e =>
logger.error(`Error occured in ${account.type}/${account.handle}`, e) logger.error(`Error occured in ${account.type}/${account.handle}`, e)
@ -24,7 +20,81 @@ class AccountService {
problem_id: string, problem_id: string,
language: string, language: string,
code: 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<string, any> = {};
private providers: Record<string, AccountService> = {};
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 => { const next = async payload => {
return await this.request('/submit', { return await this.request('/submit', {
@ -77,75 +147,51 @@ class AccountService {
}); });
}; };
try { if (!config.remote_submit_type || config.remote_submit_type == 'bot') {
const rid = await this.api.submitProblem( if (!this.providers[type]) throw new Error(`No provider ${type}`);
await this.providers[type].judge(
id,
problem_id, problem_id,
language, language,
code, code,
id,
next, next,
end 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); const rid = await provider.submitProblem(
} catch (e) { problem_id,
logger.error(e); language,
await end({ error: true, status: 'Judgment Failed', message: e.message }); 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() { throw new Error(
const login = await this.api.ensureLogin(); 'Unsupported remote submit type: ' + config.remote_submit_type
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 providers: Record<string, AccountService> = {};
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);
} }
} }