mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-23 09:58:41 +00:00
feat(remote_judger): add codeforces
This commit is contained in:
parent
3a2e3ce1db
commit
3d6102a3f9
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,3 +6,5 @@ docker-compose.local.yml
|
||||
.config.php
|
||||
.config.development.php
|
||||
.config.local.php
|
||||
*.development.env
|
||||
*.local.env
|
||||
|
@ -52,9 +52,14 @@ services:
|
||||
restart: always
|
||||
volumes:
|
||||
- ./uoj_data/judger/log:/opt/uoj_judger/log
|
||||
env_file:
|
||||
- remote-judger.development.env
|
||||
environment:
|
||||
- DEV=true
|
||||
- UOJ_PROTOCOL=http
|
||||
- UOJ_HOST=uoj-web
|
||||
- UOJ_JUDGER_NAME=remote_judger
|
||||
- UOJ_JUDGER_PASSWORD=_judger_password_
|
||||
|
||||
uoj-web:
|
||||
build:
|
||||
|
@ -9,4 +9,4 @@ COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
CMD [ "node", "dist/entrypoint.js" ]
|
||||
CMD [ "node", "--experimental-specifier-resolution=node", "dist/entrypoint.js" ]
|
||||
|
5
remote_judger/README
Normal file
5
remote_judger/README
Normal file
@ -0,0 +1,5 @@
|
||||
本模块借鉴了以下项目的源码:
|
||||
|
||||
- https://github.com/hydro-dev/Hydro/blob/feb51804766e35dbd13f7cb74fda95c0b783c49d/packages/vjudge/
|
||||
|
||||
在此表示感谢。
|
2
remote_judger/add_judger.sql
Normal file
2
remote_judger/add_judger.sql
Normal file
@ -0,0 +1,2 @@
|
||||
USE `app_uoj233`;
|
||||
insert into judger_info (judger_name, password, ip) values ('remote_judger', '_judger_password_', 'uoj-remote-judger');
|
189
remote_judger/package-lock.json
generated
189
remote_judger/package-lock.json
generated
@ -9,15 +9,15 @@
|
||||
"version": "0.0.0",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.1.0",
|
||||
"jsdom": "^21.0.0",
|
||||
"math-sum": "^2.0.0",
|
||||
"reggol": "^1.3.4",
|
||||
"superagent": "^8.0.6",
|
||||
"superagent-prefix": "^0.0.2",
|
||||
"superagent-proxy": "^3.0.0",
|
||||
"xml-js": "^1.6.11"
|
||||
"superagent-proxy": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^20.0.1",
|
||||
"@types/node": "^18.11.18",
|
||||
@ -41,6 +41,16 @@
|
||||
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/fs-extra": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz",
|
||||
"integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/jsonfile": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/js-yaml": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
||||
@ -58,6 +68,15 @@
|
||||
"parse5": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsonfile": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz",
|
||||
"integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.11.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||
@ -464,16 +483,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
|
||||
"integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6 <7 || >=8"
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/ftp": {
|
||||
@ -530,6 +549,35 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-uri/node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/get-uri/node_modules/jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-uri/node_modules/universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.10",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||
@ -712,9 +760,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"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"
|
||||
}
|
||||
@ -1047,11 +1098,6 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
@ -1177,11 +1223,6 @@
|
||||
"node": ">=6.4.0 <13 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent-prefix": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/superagent-prefix/-/superagent-prefix-0.0.2.tgz",
|
||||
"integrity": "sha512-LssjLYPklgE/ZlaowG+DZjSvXkiwyT47U3D1tvgDlAeG7w7ew5MowVXwepIt+yoVql2xQmgR/sJr2sivkRyN7g=="
|
||||
},
|
||||
"node_modules/superagent-proxy": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-3.0.0.tgz",
|
||||
@ -1286,11 +1327,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
@ -1419,17 +1460,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml-js": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
|
||||
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
|
||||
"dependencies": {
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"xml-js": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
@ -1469,6 +1499,16 @@
|
||||
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/fs-extra": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz",
|
||||
"integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/jsonfile": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/js-yaml": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
||||
@ -1486,6 +1526,15 @@
|
||||
"parse5": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@types/jsonfile": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz",
|
||||
"integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.11.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||
@ -1794,13 +1843,13 @@
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
|
||||
"integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"ftp": {
|
||||
@ -1844,6 +1893,29 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1985,11 +2057,12 @@
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"levn": {
|
||||
@ -2249,11 +2322,6 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
@ -2349,11 +2417,6 @@
|
||||
"semver": "^7.3.8"
|
||||
}
|
||||
},
|
||||
"superagent-prefix": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/superagent-prefix/-/superagent-prefix-0.0.2.tgz",
|
||||
"integrity": "sha512-LssjLYPklgE/ZlaowG+DZjSvXkiwyT47U3D1tvgDlAeG7w7ew5MowVXwepIt+yoVql2xQmgR/sJr2sivkRyN7g=="
|
||||
},
|
||||
"superagent-proxy": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-3.0.0.tgz",
|
||||
@ -2425,9 +2488,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
@ -2513,14 +2576,6 @@
|
||||
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
|
||||
"requires": {}
|
||||
},
|
||||
"xml-js": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
|
||||
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
|
||||
"requires": {
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
|
@ -12,15 +12,15 @@
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.1.0",
|
||||
"jsdom": "^21.0.0",
|
||||
"math-sum": "^2.0.0",
|
||||
"reggol": "^1.3.4",
|
||||
"superagent": "^8.0.6",
|
||||
"superagent-prefix": "^0.0.2",
|
||||
"superagent-proxy": "^3.0.0",
|
||||
"xml-js": "^1.6.11"
|
||||
"superagent-proxy": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^20.0.1",
|
||||
"@types/node": "^18.11.18",
|
||||
|
@ -1,10 +1,13 @@
|
||||
import fs from 'fs';
|
||||
import fs from 'fs-extra';
|
||||
import superagent from 'superagent';
|
||||
import proxy from 'superagent-proxy';
|
||||
import prefix from 'superagent-prefix';
|
||||
import Logger from '@/utils/logger';
|
||||
import sleep from '@/utils/sleep';
|
||||
import * as TIME from '@/utils/time';
|
||||
import Logger from './utils/logger';
|
||||
import sleep from './utils/sleep';
|
||||
import * as TIME from './utils/time';
|
||||
import htmlspecialchars from './utils/htmlspecialchars';
|
||||
import { apply } from './vjudge';
|
||||
import path from 'path';
|
||||
import child from 'child_process';
|
||||
|
||||
proxy(superagent);
|
||||
|
||||
@ -22,41 +25,152 @@ interface UOJSubmission {
|
||||
problem_mtime: number;
|
||||
content: any;
|
||||
status: string;
|
||||
judge_time: string;
|
||||
}
|
||||
|
||||
export default async function daemon(config: UOJConfig) {
|
||||
const agent = superagent
|
||||
.agent()
|
||||
.use(prefix(`${this.config.server_url}/judge`))
|
||||
.type('application/x-www-form-urlencoded')
|
||||
.serialize(data =>
|
||||
new URLSearchParams({
|
||||
const request = (url: string, data = {}) =>
|
||||
superagent
|
||||
.post(`${config.server_url}/judge${url}`)
|
||||
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||
.send(
|
||||
Object.entries({
|
||||
judger_name: config.judger_name,
|
||||
password: config.password,
|
||||
...data,
|
||||
}).toString()
|
||||
})
|
||||
.map(
|
||||
([k, v]) =>
|
||||
`${k}=${encodeURIComponent(
|
||||
typeof v === 'string' ? v : JSON.stringify(v)
|
||||
)}`
|
||||
)
|
||||
.join('&')
|
||||
);
|
||||
const vjudge = await apply(request);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const { body, error } = await agent.post('/submit');
|
||||
const { text, error } = await request('/submit');
|
||||
|
||||
if (error) {
|
||||
logger.error(error.message);
|
||||
logger.error('/submit', error.message);
|
||||
|
||||
await sleep(TIME.second);
|
||||
} else if (body === 'Nothing to judge') {
|
||||
await sleep(2 * TIME.second);
|
||||
await sleep(3 * TIME.second);
|
||||
} else if (text.startsWith('Nothing to judge')) {
|
||||
await sleep(3 * TIME.second);
|
||||
} else {
|
||||
const data: UOJSubmission = JSON.parse(body);
|
||||
const data: UOJSubmission = JSON.parse(text);
|
||||
const { id, content, judge_time } = data;
|
||||
const config = Object.fromEntries(content.config);
|
||||
const tmpdir = `/tmp/s2oj_rmj/${id}/`;
|
||||
|
||||
logger.info('Start judging', data.id);
|
||||
fs.ensureDirSync(tmpdir);
|
||||
|
||||
// TODO: judge
|
||||
const reportError = async (error: string, details: string) => {
|
||||
await request('/submit', {
|
||||
submit: true,
|
||||
fetch_new: false,
|
||||
id,
|
||||
result: JSON.stringify({
|
||||
status: 'Judged',
|
||||
score: 0,
|
||||
error,
|
||||
details: `<error>${htmlspecialchars(details)}</error>`,
|
||||
}),
|
||||
judge_time,
|
||||
});
|
||||
};
|
||||
|
||||
// Download source code
|
||||
logger.debug('Downloading source code.', id);
|
||||
const zipFilePath = path.resolve(tmpdir, 'all.zip');
|
||||
const res = request(`/download${content.file_name}`);
|
||||
const stream = fs.createWriteStream(zipFilePath);
|
||||
res.pipe(stream);
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
stream.on('finish', resolve);
|
||||
stream.on('error', reject);
|
||||
});
|
||||
} catch (e) {
|
||||
await reportError(
|
||||
'Judgment Failed',
|
||||
`Failed to download source code.`
|
||||
);
|
||||
logger.error('Failed to download source code.', id, e.message);
|
||||
|
||||
fs.removeSync(tmpdir);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unzip source code
|
||||
logger.debug('Unzipping source code.', id);
|
||||
const extractedPath = path.resolve(tmpdir, 'all');
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
child.exec(`unzip ${zipFilePath} -d ${extractedPath}`, e => {
|
||||
if (e) reject(e);
|
||||
else resolve(true);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
await reportError('Judgment Failed', `Failed to unzip source code.`);
|
||||
logger.error('Failed to unzip source code.', id, e.message);
|
||||
|
||||
fs.removeSync(tmpdir);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read source code
|
||||
logger.debug('Reading source code.', id);
|
||||
const sourceCodePath = path.resolve(extractedPath, 'answer.code');
|
||||
let code = '';
|
||||
|
||||
try {
|
||||
code = fs.readFileSync(sourceCodePath, 'utf-8');
|
||||
} catch (e) {
|
||||
await reportError('Judgment Failed', `Failed to read source code.`);
|
||||
logger.error('Failed to read source code.', id, e.message);
|
||||
|
||||
fs.removeSync(tmpdir);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start judging
|
||||
logger.info('Start judging', id, `(problem ${data.problem_id})`);
|
||||
try {
|
||||
await vjudge.judge(
|
||||
id,
|
||||
config.remote_online_judge,
|
||||
config.remote_problem_id,
|
||||
config.answer_language,
|
||||
code,
|
||||
judge_time
|
||||
);
|
||||
} catch (err) {
|
||||
await reportError(
|
||||
'Judgment Failed',
|
||||
'No details, please contact admin!'
|
||||
);
|
||||
logger.error('Judgment Failed.', id, err.message);
|
||||
|
||||
fs.removeSync(tmpdir);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
fs.removeSync(tmpdir);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
await sleep(TIME.second);
|
||||
logger.error(err.message);
|
||||
|
||||
await sleep(3 * TIME.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
remote_judger/src/interface.ts
Normal file
31
remote_judger/src/interface.ts
Normal file
@ -0,0 +1,31 @@
|
||||
export interface RemoteAccount {
|
||||
type: string;
|
||||
cookie?: string[];
|
||||
handle: string;
|
||||
password: string;
|
||||
endpoint?: string;
|
||||
proxy?: string;
|
||||
}
|
||||
|
||||
export type NextFunction = (body: Partial<any>) => void;
|
||||
|
||||
export interface IBasicProvider {
|
||||
ensureLogin(): Promise<boolean | string>;
|
||||
submitProblem(
|
||||
id: string,
|
||||
lang: string,
|
||||
code: string,
|
||||
submissionId: number,
|
||||
next: NextFunction,
|
||||
end: NextFunction
|
||||
): Promise<string | void>;
|
||||
waitForSubmission(
|
||||
id: string,
|
||||
next: NextFunction,
|
||||
end: NextFunction
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface BasicProvider {
|
||||
new (account: RemoteAccount): IBasicProvider;
|
||||
}
|
325
remote_judger/src/providers/codeforces.ts
Normal file
325
remote_judger/src/providers/codeforces.ts
Normal file
@ -0,0 +1,325 @@
|
||||
import { JSDOM } from 'jsdom';
|
||||
import superagent from 'superagent';
|
||||
import proxy from 'superagent-proxy';
|
||||
import sleep from '../utils/sleep';
|
||||
import mathSum from 'math-sum';
|
||||
import { IBasicProvider, RemoteAccount } from '../interface';
|
||||
import { normalize, VERDICT } from '../verdict';
|
||||
import Logger from '../utils/logger';
|
||||
|
||||
proxy(superagent);
|
||||
const logger = new Logger('remote/codeforces');
|
||||
|
||||
const langs_map = {
|
||||
C: {
|
||||
name: 'GNU GCC C11 5.1.0',
|
||||
id: 43,
|
||||
comment: '//',
|
||||
},
|
||||
'C++': {
|
||||
name: 'GNU G++14 6.4.0',
|
||||
id: 50,
|
||||
comment: '//',
|
||||
},
|
||||
'C++17': {
|
||||
name: 'GNU G++17 7.3.0',
|
||||
id: 54,
|
||||
comment: '//',
|
||||
},
|
||||
Pascal: {
|
||||
name: 'Free Pascal 3.0.2',
|
||||
id: 4,
|
||||
comment: '//',
|
||||
},
|
||||
'Python2.7': {
|
||||
name: 'Python 2.7.18',
|
||||
id: 7,
|
||||
comment: '#',
|
||||
},
|
||||
Python3: {
|
||||
name: 'Python 3.9.1',
|
||||
id: 31,
|
||||
comment: '#',
|
||||
},
|
||||
};
|
||||
|
||||
export function getAccountInfoFromEnv(): RemoteAccount | null {
|
||||
const {
|
||||
CODEFORCES_HANDLE,
|
||||
CODEFORCES_PASSWORD,
|
||||
CODEFORCES_ENDPOINT = 'https://codeforces.com',
|
||||
CODEFORCES_PROXY,
|
||||
} = process.env;
|
||||
|
||||
if (!CODEFORCES_HANDLE || !CODEFORCES_PASSWORD) return null;
|
||||
|
||||
const account: RemoteAccount = {
|
||||
type: 'codeforces',
|
||||
handle: CODEFORCES_HANDLE,
|
||||
password: CODEFORCES_PASSWORD,
|
||||
endpoint: CODEFORCES_ENDPOINT,
|
||||
};
|
||||
|
||||
if (CODEFORCES_PROXY) account.proxy = CODEFORCES_PROXY;
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
function parseProblemId(id: string) {
|
||||
const [, type, contestId, problemId] = id.startsWith('921')
|
||||
? ['', '921', '01']
|
||||
: /^(|GYM)(\d+)([A-Z]+[0-9]*)$/.exec(id);
|
||||
if (type === 'GYM' && +contestId < 100000) {
|
||||
return [type, (+contestId + 100000).toString(), problemId];
|
||||
}
|
||||
return [type, contestId, problemId];
|
||||
}
|
||||
|
||||
export default class CodeforcesProvider implements IBasicProvider {
|
||||
constructor(public account: RemoteAccount) {
|
||||
if (account.cookie) this.cookie = account.cookie;
|
||||
this.account.endpoint ||= 'https://codeforces.com';
|
||||
}
|
||||
|
||||
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)
|
||||
.set(
|
||||
'User-Agent',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36 S2OJ/3.1.0'
|
||||
);
|
||||
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)
|
||||
.set(
|
||||
'User-Agent',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36 S2OJ/3.1.0'
|
||||
);
|
||||
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];
|
||||
}
|
||||
|
||||
setCookie(target: string, value: string) {
|
||||
this.cookie = this.cookie.filter(i => !i.startsWith(`${target}=`));
|
||||
this.cookie.push(`${target}=${value}`);
|
||||
}
|
||||
|
||||
tta(_39ce7: string) {
|
||||
let _tta = 0;
|
||||
for (let c = 0; c < _39ce7.length; c++) {
|
||||
_tta = (_tta + (c + 1) * (c + 2) * _39ce7.charCodeAt(c)) % 1009;
|
||||
if (c % 3 === 0) _tta++;
|
||||
if (c % 2 === 0) _tta *= 2;
|
||||
if (c > 0)
|
||||
_tta -=
|
||||
Math.floor(_39ce7.charCodeAt(Math.floor(c / 2)) / 2) * (_tta % 5);
|
||||
_tta = ((_tta % 1009) + 1009) % 1009;
|
||||
}
|
||||
return _tta;
|
||||
}
|
||||
|
||||
async getCsrfToken(url: string) {
|
||||
const { text: html } = await this.get(url);
|
||||
const {
|
||||
window: { document },
|
||||
} = new JSDOM(html);
|
||||
if (document.body.children.length < 2 && html.length < 512) {
|
||||
throw new Error(document.body.textContent!);
|
||||
}
|
||||
const ftaa = this.getCookie('70a7c28f3de') || 'n/a';
|
||||
const bfaa = this.getCookie('raa') || this.getCookie('bfaa') || 'n/a';
|
||||
return [
|
||||
(
|
||||
document.querySelector('meta[name="X-Csrf-Token"]') ||
|
||||
document.querySelector('input[name="csrf_token"]')
|
||||
)?.getAttribute('content'),
|
||||
ftaa,
|
||||
bfaa,
|
||||
];
|
||||
}
|
||||
|
||||
get loggedIn() {
|
||||
return this.get('/enter').then(res => {
|
||||
const html = res.text;
|
||||
if (html.includes('Login into Codeforces')) return false;
|
||||
if (html.length < 1000 && html.includes('Redirecting...')) {
|
||||
logger.debug('Got a redirect', html);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
async ensureLogin() {
|
||||
if (await this.loggedIn) return true;
|
||||
logger.info('retry normal login');
|
||||
const [csrf, ftaa, bfaa] = await this.getCsrfToken('/enter');
|
||||
const { header } = await this.get('/enter');
|
||||
if (header['set-cookie']) {
|
||||
this.cookie = header['set-cookie'];
|
||||
}
|
||||
const res = await this.post('/enter').send({
|
||||
csrf_token: csrf,
|
||||
action: 'enter',
|
||||
ftaa,
|
||||
bfaa,
|
||||
handleOrEmail: this.account.handle,
|
||||
password: this.account.password,
|
||||
remember: 'on',
|
||||
_tta: this.tta(this.getCookie('39ce7')),
|
||||
});
|
||||
const cookie = res.header['set-cookie'];
|
||||
if (cookie) {
|
||||
this.cookie = cookie;
|
||||
}
|
||||
if (await this.loggedIn) {
|
||||
logger.success('Logged in');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async submitProblem(
|
||||
id: string,
|
||||
lang: string,
|
||||
code: string,
|
||||
submissionId: number,
|
||||
next,
|
||||
end
|
||||
) {
|
||||
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 [type, contestId, problemId] = parseProblemId(id);
|
||||
const [csrf, ftaa, bfaa] = await this.getCsrfToken(
|
||||
type !== 'GYM' ? '/problemset/submit' : `/gym/${contestId}/submit`
|
||||
);
|
||||
logger.debug(
|
||||
'Submitting',
|
||||
id,
|
||||
programType,
|
||||
lang,
|
||||
`(S2OJ Submission #${submissionId})`
|
||||
);
|
||||
// TODO: check submit time to ensure submission
|
||||
const { text: submit } = await this.post(
|
||||
`/${
|
||||
type !== 'GYM' ? 'problemset' : `gym/${contestId}`
|
||||
}/submit?csrf_token=${csrf}`
|
||||
).send({
|
||||
csrf_token: csrf,
|
||||
action: 'submitSolutionFormSubmitted',
|
||||
programTypeId: programType.id,
|
||||
source: code,
|
||||
tabsize: 4,
|
||||
sourceFile: '',
|
||||
ftaa,
|
||||
bfaa,
|
||||
_tta: this.tta(this.getCookie('39ce7')),
|
||||
...(type !== 'GYM'
|
||||
? {
|
||||
submittedProblemCode: contestId + problemId,
|
||||
sourceCodeConfirmed: true,
|
||||
}
|
||||
: {
|
||||
submittedProblemIndex: problemId,
|
||||
}),
|
||||
});
|
||||
const {
|
||||
window: { document: statusDocument },
|
||||
} = new JSDOM(submit);
|
||||
const message = Array.from(statusDocument.querySelectorAll('.error'))
|
||||
.map(i => i.textContent)
|
||||
.join('')
|
||||
.replace(/ /g, ' ')
|
||||
.trim();
|
||||
if (message) {
|
||||
end({ error: true, status: 'Compile Error', message });
|
||||
return null;
|
||||
}
|
||||
const { text: status } = await this.get(
|
||||
type !== 'GYM' ? '/problemset/status?my=on' : `/gym/${contestId}/my`
|
||||
).retry(3);
|
||||
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');
|
||||
}
|
||||
|
||||
async waitForSubmission(id: string, next, end) {
|
||||
let i = 1;
|
||||
|
||||
while (true) {
|
||||
await sleep(3000);
|
||||
const { body } = await this.post('/data/submitSource')
|
||||
.send({
|
||||
csrf_token: this.csrf,
|
||||
submissionId: id,
|
||||
})
|
||||
.retry(3);
|
||||
if (body.compilationError === 'true') {
|
||||
return await end({
|
||||
id,
|
||||
error: 1,
|
||||
status: 'Compile Error',
|
||||
message: body['checkerStdoutAndStderr#1'],
|
||||
});
|
||||
}
|
||||
const time = mathSum(
|
||||
Object.keys(body)
|
||||
.filter(k => k.startsWith('timeConsumed#'))
|
||||
.map(k => +body[k])
|
||||
);
|
||||
const memory =
|
||||
Math.max(
|
||||
...Object.keys(body)
|
||||
.filter(k => k.startsWith('memoryConsumed#'))
|
||||
.map(k => +body[k])
|
||||
) / 1024;
|
||||
await next({ test_id: body.testCount });
|
||||
if (body.waiting === 'true') continue;
|
||||
const status =
|
||||
VERDICT[
|
||||
Object.keys(VERDICT).find(k => normalize(body.verdict).includes(k))
|
||||
];
|
||||
return await end({
|
||||
id,
|
||||
status,
|
||||
score: status === 'Accepted' ? 100 : 0,
|
||||
time,
|
||||
memory,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
11
remote_judger/src/utils/htmlspecialchars.ts
Normal file
11
remote_judger/src/utils/htmlspecialchars.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export default function htmlspecialchars(text: string) {
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, m => map[m]);
|
||||
}
|
32
remote_judger/src/verdict.ts
Normal file
32
remote_judger/src/verdict.ts
Normal file
@ -0,0 +1,32 @@
|
||||
export function normalize(key: string) {
|
||||
return key.toUpperCase().replace(/ /g, '_');
|
||||
}
|
||||
|
||||
export const VERDICT = new Proxy<Record<string, string>>(
|
||||
{
|
||||
RUNTIME_ERROR: 'Runtime Error',
|
||||
WRONG_ANSWER: 'Wrong Answer',
|
||||
OK: 'Accepted',
|
||||
COMPILING: 'Compiling',
|
||||
TIME_LIMIT_EXCEEDED: 'Time Limit Exceeded',
|
||||
MEMORY_LIMIT_EXCEEDED: 'Memory Limit Exceeded',
|
||||
IDLENESS_LIMIT_EXCEEDED: 'Idleness Limit Exceeded',
|
||||
ACCEPTED: 'Accepted',
|
||||
PRESENTATION_ERROR: 'Wrong Answer',
|
||||
OUTPUT_LIMIT_EXCEEDED: 'Output Limit Exceeded',
|
||||
EXTRA_TEST_PASSED: 'Accepted',
|
||||
COMPILE_ERROR: 'Compile Error',
|
||||
'RUNNING_&_JUDGING': 'Judging',
|
||||
|
||||
// Codeforces
|
||||
'HAPPY_NEW_YEAR!': 'Accepted',
|
||||
},
|
||||
{
|
||||
get(self, key) {
|
||||
if (typeof key === 'symbol') return null;
|
||||
key = normalize(key);
|
||||
if (self[key]) return self[key];
|
||||
return null;
|
||||
},
|
||||
}
|
||||
);
|
155
remote_judger/src/vjudge.ts
Normal file
155
remote_judger/src/vjudge.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import type { BasicProvider, IBasicProvider, RemoteAccount } from './interface';
|
||||
import * as Time from './utils/time';
|
||||
import Logger from './utils/logger';
|
||||
import htmlspecialchars from './utils/htmlspecialchars';
|
||||
|
||||
const logger = new Logger('vjudge');
|
||||
|
||||
class AccountService {
|
||||
api: IBasicProvider;
|
||||
|
||||
constructor(
|
||||
public Provider: BasicProvider,
|
||||
public account: RemoteAccount,
|
||||
private request: any
|
||||
) {
|
||||
this.api = new Provider(account);
|
||||
this.main().catch(e =>
|
||||
logger.error(`Error occured in ${account.type}/${account.handle}`, e)
|
||||
);
|
||||
}
|
||||
|
||||
async judge(
|
||||
id: number,
|
||||
problem_id: string,
|
||||
language: string,
|
||||
code: string,
|
||||
judge_time: string
|
||||
) {
|
||||
const next = async payload => {
|
||||
return await this.request('/submit', {
|
||||
'update-status': true,
|
||||
fetch_new: false,
|
||||
id,
|
||||
status: `Judging Test #${payload.test_id}`,
|
||||
});
|
||||
};
|
||||
|
||||
const end = async payload => {
|
||||
if (payload.error) {
|
||||
return await this.request('/submit', {
|
||||
submit: true,
|
||||
fetch_new: false,
|
||||
id,
|
||||
result: JSON.stringify({
|
||||
status: 'Judged',
|
||||
score: 0,
|
||||
error: payload.status,
|
||||
details:
|
||||
'<div>' +
|
||||
`<info-block>ID = ${payload.id || 'None'}</info-block>` +
|
||||
`<error>${htmlspecialchars(payload.message)}</error>` +
|
||||
'</div>',
|
||||
}),
|
||||
judge_time,
|
||||
});
|
||||
}
|
||||
|
||||
return await this.request('/submit', {
|
||||
submit: true,
|
||||
fetch_new: false,
|
||||
id,
|
||||
result: JSON.stringify({
|
||||
status: 'Judged',
|
||||
score: payload.score,
|
||||
time: payload.time,
|
||||
memory: payload.memory,
|
||||
details:
|
||||
'<div>' +
|
||||
`<info-block>ID = ${payload.id || 'None'}</info-block>` +
|
||||
`<info-block>VERDICT = ${payload.status}</info-block>` +
|
||||
'</div>',
|
||||
}),
|
||||
judge_time,
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
const rid = await this.api.submitProblem(
|
||||
problem_id,
|
||||
language,
|
||||
code,
|
||||
id,
|
||||
next,
|
||||
end
|
||||
);
|
||||
|
||||
if (!rid) return;
|
||||
|
||||
await this.api.waitForSubmission(rid, next, end);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
await end({ error: true, 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 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}`);
|
||||
|
||||
this.providers[type].judge(id, problem_id, language, code, judge_time);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply(request: any) {
|
||||
const vjudge = new VJudge(request);
|
||||
|
||||
await vjudge.addProvider('codeforces');
|
||||
|
||||
return vjudge;
|
||||
}
|
@ -13,9 +13,6 @@
|
||||
"isolatedModules": true,
|
||||
"incremental": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
|
@ -687,7 +687,7 @@ class JudgmentDetailsPrinter {
|
||||
}
|
||||
echo '<h4 class="mb-2">', $node->getAttribute("title"), ":</h4>";
|
||||
}
|
||||
echo '<pre>', "\n";
|
||||
echo '<pre class="bg-light p-3 rounded">', "\n";
|
||||
$this->_print_c($node);
|
||||
echo "\n</pre>";
|
||||
echo '</div>';
|
||||
|
Loading…
Reference in New Issue
Block a user