From 3d3a5480e311b6d743cba4e62d12f4319b95c5f8 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 23 Jan 2023 16:40:33 +0800 Subject: [PATCH] vjudge: use BasicFetcher (#496) --- packages/vjudge/package.json | 4 +- packages/vjudge/src/fetch.ts | 50 +++++++++++++++++ packages/vjudge/src/model.ts | 26 +++------ packages/vjudge/src/providers/codeforces.ts | 35 ++---------- packages/vjudge/src/providers/csgoj.ts | 34 ++---------- packages/vjudge/src/providers/hustoj.ts | 38 ++++++------- packages/vjudge/src/providers/luogu.ts | 61 ++++++--------------- packages/vjudge/src/providers/poj.ts | 26 +-------- packages/vjudge/src/providers/spoj.ts | 37 +++---------- packages/vjudge/src/providers/uoj.ts | 25 +-------- 10 files changed, 118 insertions(+), 218 deletions(-) create mode 100644 packages/vjudge/src/fetch.ts diff --git a/packages/vjudge/package.json b/packages/vjudge/package.json index 5d12ebdc5..fe442103c 100644 --- a/packages/vjudge/package.json +++ b/packages/vjudge/package.json @@ -2,7 +2,7 @@ "name": "@hydrooj/vjudge", "version": "1.7.3", "description": "Submit problems to remote oj", - "main": "package.json", + "main": "src/model.ts", "repository": "https://github.com/hydro-dev/Hydro.git", "author": "undefined", "license": "AGPL-3.0-or-later", @@ -10,12 +10,10 @@ "dependencies": { "@hydrooj/utils": "workspace:*", "jsdom": "^21.0.0", - "superagent": "^8.0.6", "superagent-proxy": "^3.0.0" }, "devDependencies": { "@types/jsdom": "^20.0.1", - "@types/superagent": "^4.1.16", "@types/superagent-proxy": "^3.0.0" } } diff --git a/packages/vjudge/src/fetch.ts b/packages/vjudge/src/fetch.ts new file mode 100644 index 000000000..02c968b90 --- /dev/null +++ b/packages/vjudge/src/fetch.ts @@ -0,0 +1,50 @@ +import { DOMWindow, JSDOM } from 'jsdom'; +import proxy from 'superagent-proxy'; +import { Logger } from '@hydrooj/utils'; +import { superagent } from 'hydrooj'; +import { RemoteAccount } from './interface'; + +proxy(superagent); + +interface FetchOptions { + headers?: { [key: string]: string }; + post?: Omit + get?: Omit +} + +export class BasicFetcher { + cookie: string[] = []; + + constructor( + public account: RemoteAccount, private defaultEndpoint: string, + private formType: 'form' | 'json', public logger: Logger, + public fetchOptions: FetchOptions = {}, + ) { + if (account.cookie) this.cookie = account.cookie; + } + + get(url: string) { + this.logger.debug('get', url); + url = new URL(url, this.account.endpoint || this.defaultEndpoint).toString(); + let req = superagent.get(url).set('Cookie', this.cookie); + if (this.fetchOptions.headers) req = req.set(this.fetchOptions.headers); + if (this.fetchOptions.get?.headers) req = req.set(this.fetchOptions.get.headers); + return this.account.proxy ? req.proxy(this.account.proxy) : req; + } + + async html(url: string) { + const { text: html } = await this.get(url); + const $dom = new JSDOM(html); + $dom.window.html = html; + return $dom.window as DOMWindow & { html: string }; + } + + post(url: string) { + this.logger.debug('post', url, this.cookie); + url = new URL(url, this.account.endpoint || this.defaultEndpoint).toString(); + let req = superagent.post(url).set('Cookie', this.cookie).type(this.formType); + if (this.fetchOptions.headers) req = req.set(this.fetchOptions.headers); + if (this.fetchOptions.post?.headers) req = req.set(this.fetchOptions.post.headers); + return this.account.proxy ? req.proxy(this.account.proxy) : req; + } +} diff --git a/packages/vjudge/src/model.ts b/packages/vjudge/src/model.ts index 64b44184c..c31901d0d 100644 --- a/packages/vjudge/src/model.ts +++ b/packages/vjudge/src/model.ts @@ -5,7 +5,6 @@ import { ProblemModel, RecordModel, Service, sleep, STATUS, TaskModel, Time, } from 'hydrooj'; import { BasicProvider, IBasicProvider, RemoteAccount } from './interface'; -import { getDifficulty } from './providers/codeforces'; import providers from './providers/index'; const coll = db.collection('vjudge'); @@ -45,7 +44,10 @@ class AccountService { await next({ status: STATUS.STATUS_JUDGING, message: `ID = ${rid}` }); await this.api.waitForSubmission(rid, next, end); } catch (e) { - if (process.env.DEV) logger.error(e); + if (process.env.DEV) { + logger.error(e); + if (e.response) console.error(e.response); + } end({ status: STATUS.STATUS_SYSTEM_ERROR, message: e.message }); } } @@ -123,24 +125,11 @@ class AccountService { } declare module 'hydrooj' { - interface Model { - vjudge: VJudgeModel; - } interface Context { vjudge: VJudgeService; } } -class VJudgeModel { - static async fixCodeforcesDifficulty(domainId = 'codeforces') { - const pdocs = await ProblemModel.getMulti(domainId, {}).toArray(); - for (const pdoc of pdocs) { - await ProblemModel.edit(domainId, pdoc.docId, { difficulty: getDifficulty(pdoc.tag) }); - } - return true; - } -} - class VJudgeService extends Service { constructor(ctx) { super(ctx, 'vjudge', false); @@ -153,8 +142,9 @@ class VJudgeService extends Service { this.accounts = await coll.find().toArray(); } - addProvider(type: string, provider: BasicProvider) { - if (this.providers[type]) throw new Error(`duplicate provider ${type}`); + addProvider(type: string, provider: BasicProvider, override = false) { + if (process.env.VJUDGE_DEBUG && process.env.VJUDGE_DEBUG !== type) return; + if (!override && this.providers[type]) throw new Error(`duplicate provider ${type}`); this.providers[type] = provider; for (const account of this.accounts.filter((a) => a.type === type)) { if (account.enableOn && !account.enableOn.includes(os.hostname())) continue; @@ -166,8 +156,6 @@ class VJudgeService extends Service { } } -global.Hydro.model.vjudge = VJudgeModel; - Context.service('vjudge', VJudgeService); export const name = 'vjudge'; export async function apply(ctx: Context) { diff --git a/packages/vjudge/src/providers/codeforces.ts b/packages/vjudge/src/providers/codeforces.ts index 956ea91ed..0f143e822 100644 --- a/packages/vjudge/src/providers/codeforces.ts +++ b/packages/vjudge/src/providers/codeforces.ts @@ -2,15 +2,13 @@ import { PassThrough } from 'stream'; import yaml from 'js-yaml'; import { JSDOM } from 'jsdom'; -import superagent from 'superagent'; -import proxy from 'superagent-proxy'; import { buildContent, Logger, SettingModel, sleep, STATUS, } from 'hydrooj'; +import { BasicFetcher } from '../fetch'; import { IBasicProvider, RemoteAccount } from '../interface'; import { normalize, VERDICT } from '../verdict'; -proxy(superagent); const logger = new Logger('remote/codeforces'); function parseProblemId(id: string) { @@ -61,31 +59,13 @@ ${lines.map((i) => i.innerHTML).join('\n').trim()} \n`; }; -export default class CodeforcesProvider implements IBasicProvider { +export default class CodeforcesProvider extends BasicFetcher implements IBasicProvider { constructor(public account: RemoteAccount, private save: (data: any) => Promise) { - if (account.cookie) this.cookie = account.cookie; - this.account.endpoint ||= 'https://codeforces.com'; + super(account, 'https://codeforces.com', 'form', logger); } - cookie: string[] = []; csrf: string; - get(url: string) { - logger.debug('get', url); - if (!url.includes('//')) url = `${this.account.endpoint}${url}`; - const req = superagent.get(url).set('Cookie', this.cookie); - 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}${url}`; - const req = superagent.post(url).type('form').set('Cookie', this.cookie); - if (this.account.proxy) return req.proxy(this.account.proxy); - return req; - } - getCookie(target: string) { return this.cookie.find((i) => i.startsWith(`${target}=`))?.split('=')[1]?.split(';')[0]; } @@ -103,8 +83,7 @@ export default class CodeforcesProvider implements IBasicProvider { } async getCsrfToken(url: string) { - const { text: html } = await this.get(url); - const { window: { document } } = new JSDOM(html); + const { document, html } = await this.html(url); if (document.body.children.length < 2 && html.length < 512) { throw new Error(document.body.textContent); } @@ -261,13 +240,12 @@ export default class CodeforcesProvider implements IBasicProvider { if (resync && page > 1) return []; if (resync && listName.startsWith('GYM')) return []; if (listName.startsWith('GYM') && page > 1) return []; - const res = await this.get(listName === 'main' + const { document } = await this.html(listName === 'main' ? `/problemset/page/${page}` : listName === 'gym' ? `/gyms/page/${page}` : `/gym/${listName.split('GYM')[1]}`, ); - const { window: { document } } = new JSDOM(res.text); if (['gym', 'main'].includes(listName)) { const index = document.querySelector('.page-index.active').getAttribute('pageindex'); if (index !== page.toString()) return []; @@ -330,10 +308,9 @@ export default class CodeforcesProvider implements IBasicProvider { end({ status: STATUS.STATUS_COMPILE_ERROR, message }); return null; } - const { text: status } = await this.get(type !== 'GYM' + const { document } = await this.html(type !== 'GYM' ? '/problemset/status?my=on' : `/gym/${contestId}/my`); - const { window: { document } } = new JSDOM(status); this.csrf = document.querySelector('meta[name="X-Csrf-Token"]').getAttribute('content'); return document.querySelector('[data-submission-id]').getAttribute('data-submission-id'); } diff --git a/packages/vjudge/src/providers/csgoj.ts b/packages/vjudge/src/providers/csgoj.ts index 77c83770a..cd35c0c5e 100644 --- a/packages/vjudge/src/providers/csgoj.ts +++ b/packages/vjudge/src/providers/csgoj.ts @@ -1,12 +1,10 @@ /* eslint-disable no-await-in-loop */ import { PassThrough } from 'stream'; import { JSDOM } from 'jsdom'; -import * as superagent from 'superagent'; -import proxy from 'superagent-proxy'; import { Logger, sleep, STATUS } from 'hydrooj'; +import { BasicFetcher } from '../fetch'; import { IBasicProvider, RemoteAccount } from '../interface'; -proxy(superagent as any); const logger = new Logger('remote/csgoj'); const statusDict = { 0: STATUS.STATUS_COMPILING, @@ -53,35 +51,15 @@ csgoj.3: const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0'; -export default class CSGOJProvider implements IBasicProvider { +export default class CSGOJProvider extends BasicFetcher implements IBasicProvider { constructor(public account: RemoteAccount, private save: (data: any) => Promise) { + super(account, 'https://cpc.csgrandeur.cn', 'form', logger, { + headers: { 'User-Agent': userAgent }, + post: { headers: { 'X-Requested-With': 'XMLHttpRequest' } }, + }); if (account.cookie) this.cookie = account.cookie; } - cookie: string[] = []; - - get(url: string) { - logger.debug('get', url); - if (!url.includes('//')) url = `${this.account.endpoint || 'https://cpc.csgrandeur.cn'}${url}`; - const req = superagent.get(url) - .set('Cookie', this.cookie) - .set('User-Agent', userAgent); - 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://cpc.csgrandeur.cn'}${url}`; - const req = superagent.post(url) - .set('Cookie', this.cookie) - .set('X-Requested-With', 'XMLHttpRequest') - .set('User-Agent', userAgent) - .type('form'); - if (this.account.proxy) return req.proxy(this.account.proxy); - return req; - } - get loggedIn() { return this.get('/').then(({ text: html }) => !html .includes('