mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-22 00:28:42 +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.php
|
||||||
.config.development.php
|
.config.development.php
|
||||||
.config.local.php
|
.config.local.php
|
||||||
|
*.development.env
|
||||||
|
*.local.env
|
||||||
|
@ -52,9 +52,14 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./uoj_data/judger/log:/opt/uoj_judger/log
|
- ./uoj_data/judger/log:/opt/uoj_judger/log
|
||||||
|
env_file:
|
||||||
|
- remote-judger.development.env
|
||||||
environment:
|
environment:
|
||||||
|
- DEV=true
|
||||||
- UOJ_PROTOCOL=http
|
- UOJ_PROTOCOL=http
|
||||||
- UOJ_HOST=uoj-web
|
- UOJ_HOST=uoj-web
|
||||||
|
- UOJ_JUDGER_NAME=remote_judger
|
||||||
|
- UOJ_JUDGER_PASSWORD=_judger_password_
|
||||||
|
|
||||||
uoj-web:
|
uoj-web:
|
||||||
build:
|
build:
|
||||||
|
@ -9,4 +9,4 @@ COPY . .
|
|||||||
|
|
||||||
RUN npm run build
|
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",
|
"version": "0.0.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"fs-extra": "^11.1.0",
|
||||||
"jsdom": "^21.0.0",
|
"jsdom": "^21.0.0",
|
||||||
"math-sum": "^2.0.0",
|
"math-sum": "^2.0.0",
|
||||||
"reggol": "^1.3.4",
|
"reggol": "^1.3.4",
|
||||||
"superagent": "^8.0.6",
|
"superagent": "^8.0.6",
|
||||||
"superagent-prefix": "^0.0.2",
|
"superagent-proxy": "^3.0.0"
|
||||||
"superagent-proxy": "^3.0.0",
|
|
||||||
"xml-js": "^1.6.11"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/fs-extra": "^11.0.1",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/jsdom": "^20.0.1",
|
"@types/jsdom": "^20.0.1",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
@ -41,6 +41,16 @@
|
|||||||
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/@types/js-yaml": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
||||||
@ -58,6 +68,15 @@
|
|||||||
"parse5": "^7.0.0"
|
"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": {
|
"node_modules/@types/node": {
|
||||||
"version": "18.11.18",
|
"version": "18.11.18",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||||
@ -464,16 +483,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "8.1.0",
|
"version": "11.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
|
||||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
"integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^6.0.1",
|
||||||
"universalify": "^0.1.0"
|
"universalify": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6 <7 || >=8"
|
"node": ">=14.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ftp": {
|
"node_modules/ftp": {
|
||||||
@ -530,6 +549,35 @@
|
|||||||
"node": ">= 6"
|
"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": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.10",
|
"version": "4.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
|
||||||
@ -712,9 +760,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsonfile": {
|
"node_modules/jsonfile": {
|
||||||
"version": "4.0.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
@ -1047,11 +1098,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"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": {
|
"node_modules/saxes": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||||
@ -1177,11 +1223,6 @@
|
|||||||
"node": ">=6.4.0 <13 || >=14"
|
"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": {
|
"node_modules/superagent-proxy": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-3.0.0.tgz",
|
||||||
@ -1286,11 +1327,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "0.1.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 4.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unpipe": {
|
"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": {
|
"node_modules/xml-name-validator": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||||
@ -1469,6 +1499,16 @@
|
|||||||
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
||||||
"dev": true
|
"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": {
|
"@types/js-yaml": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz",
|
||||||
@ -1486,6 +1526,15 @@
|
|||||||
"parse5": "^7.0.0"
|
"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": {
|
"@types/node": {
|
||||||
"version": "18.11.18",
|
"version": "18.11.18",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||||
@ -1794,13 +1843,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fs-extra": {
|
"fs-extra": {
|
||||||
"version": "8.1.0",
|
"version": "11.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.0.tgz",
|
||||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
"integrity": "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^6.0.1",
|
||||||
"universalify": "^0.1.0"
|
"universalify": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ftp": {
|
"ftp": {
|
||||||
@ -1844,6 +1893,29 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||||
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="
|
"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": {
|
"jsonfile": {
|
||||||
"version": "4.0.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6",
|
||||||
|
"universalify": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"levn": {
|
"levn": {
|
||||||
@ -2249,11 +2322,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"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": {
|
"saxes": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||||
@ -2349,11 +2417,6 @@
|
|||||||
"semver": "^7.3.8"
|
"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": {
|
"superagent-proxy": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-3.0.0.tgz",
|
||||||
@ -2425,9 +2488,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"universalify": {
|
"universalify": {
|
||||||
"version": "0.1.2",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||||
},
|
},
|
||||||
"unpipe": {
|
"unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -2513,14 +2576,6 @@
|
|||||||
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
|
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
|
||||||
"requires": {}
|
"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": {
|
"xml-name-validator": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||||
|
@ -12,15 +12,15 @@
|
|||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"fs-extra": "^11.1.0",
|
||||||
"jsdom": "^21.0.0",
|
"jsdom": "^21.0.0",
|
||||||
"math-sum": "^2.0.0",
|
"math-sum": "^2.0.0",
|
||||||
"reggol": "^1.3.4",
|
"reggol": "^1.3.4",
|
||||||
"superagent": "^8.0.6",
|
"superagent": "^8.0.6",
|
||||||
"superagent-prefix": "^0.0.2",
|
"superagent-proxy": "^3.0.0"
|
||||||
"superagent-proxy": "^3.0.0",
|
|
||||||
"xml-js": "^1.6.11"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/fs-extra": "^11.0.1",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/jsdom": "^20.0.1",
|
"@types/jsdom": "^20.0.1",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs-extra';
|
||||||
import superagent from 'superagent';
|
import superagent from 'superagent';
|
||||||
import proxy from 'superagent-proxy';
|
import proxy from 'superagent-proxy';
|
||||||
import prefix from 'superagent-prefix';
|
import Logger from './utils/logger';
|
||||||
import Logger from '@/utils/logger';
|
import sleep from './utils/sleep';
|
||||||
import sleep from '@/utils/sleep';
|
import * as TIME from './utils/time';
|
||||||
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);
|
proxy(superagent);
|
||||||
|
|
||||||
@ -22,41 +25,152 @@ interface UOJSubmission {
|
|||||||
problem_mtime: number;
|
problem_mtime: number;
|
||||||
content: any;
|
content: any;
|
||||||
status: string;
|
status: string;
|
||||||
|
judge_time: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function daemon(config: UOJConfig) {
|
export default async function daemon(config: UOJConfig) {
|
||||||
const agent = superagent
|
const request = (url: string, data = {}) =>
|
||||||
.agent()
|
superagent
|
||||||
.use(prefix(`${this.config.server_url}/judge`))
|
.post(`${config.server_url}/judge${url}`)
|
||||||
.type('application/x-www-form-urlencoded')
|
.set('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
.serialize(data =>
|
.send(
|
||||||
new URLSearchParams({
|
Object.entries({
|
||||||
judger_name: config.judger_name,
|
judger_name: config.judger_name,
|
||||||
password: config.password,
|
password: config.password,
|
||||||
...data,
|
...data,
|
||||||
}).toString()
|
})
|
||||||
);
|
.map(
|
||||||
|
([k, v]) =>
|
||||||
|
`${k}=${encodeURIComponent(
|
||||||
|
typeof v === 'string' ? v : JSON.stringify(v)
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
.join('&')
|
||||||
|
);
|
||||||
|
const vjudge = await apply(request);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const { body, error } = await agent.post('/submit');
|
const { text, error } = await request('/submit');
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error(error.message);
|
logger.error('/submit', error.message);
|
||||||
|
|
||||||
await sleep(TIME.second);
|
await sleep(3 * TIME.second);
|
||||||
} else if (body === 'Nothing to judge') {
|
} else if (text.startsWith('Nothing to judge')) {
|
||||||
await sleep(2 * TIME.second);
|
await sleep(3 * TIME.second);
|
||||||
} else {
|
} 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) {
|
} catch (err) {
|
||||||
logger.error(err);
|
logger.error(err.message);
|
||||||
await sleep(TIME.second);
|
|
||||||
|
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,
|
"isolatedModules": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
|
||||||
"@/*": ["src/*"]
|
|
||||||
},
|
|
||||||
"outDir": "./dist"
|
"outDir": "./dist"
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts"],
|
"include": ["**/*.ts"],
|
||||||
|
@ -687,7 +687,7 @@ class JudgmentDetailsPrinter {
|
|||||||
}
|
}
|
||||||
echo '<h4 class="mb-2">', $node->getAttribute("title"), ":</h4>";
|
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);
|
$this->_print_c($node);
|
||||||
echo "\n</pre>";
|
echo "\n</pre>";
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
|
Loading…
Reference in New Issue
Block a user