mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
文件结构更改
This commit is contained in:
1 parent
b3af28dc43
commit
1f1d12ad73
109 files changed
+544
-3296
No files matched your search
@@ -0,0 +1,19 @@
|
||||
import superagent from 'superagent';
|
||||
import fs from 'fs';
|
||||
|
||||
async function _download(url: string, path: string, retry: number) {
|
||||
const w = fs.createWriteStream(path);
|
||||
superagent.get(url).retry(retry).pipe(w);
|
||||
await new Promise((resolve, reject) => {
|
||||
w.on('finish', resolve);
|
||||
w.on('error', reject);
|
||||
});
|
||||
return path;
|
||||
}
|
||||
|
||||
export default function download(url: string, path?: string, retry = 3) {
|
||||
if (path) return _download(url, path, retry);
|
||||
return superagent.get(url).retry(retry);
|
||||
}
|
||||
|
||||
global.Hydro.lib.download = download;
|
||||
@@ -0,0 +1,7 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
export default function hash(password: string, salt: string) {
|
||||
return crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha256').toString('hex').substr(0, 64);
|
||||
}
|
||||
|
||||
global.Hydro.lib['hash.hydro'] = hash;
|
||||
@@ -0,0 +1,56 @@
|
||||
const locales = {};
|
||||
|
||||
declare global {
|
||||
interface String {
|
||||
format: (...args: Array<any>) => string;
|
||||
rawformat: (object: any) => string;
|
||||
translate: (...languages: string[]) => string;
|
||||
}
|
||||
}
|
||||
|
||||
String.prototype.format = function formatStr(...args) {
|
||||
let result = this;
|
||||
if (args.length > 0) {
|
||||
if (args.length === 1 && typeof (args[0]) === 'object') {
|
||||
for (const key in args) {
|
||||
if (args[key] !== undefined) {
|
||||
const reg = new RegExp(`(\\{${key}\\})`, 'g');
|
||||
result = result.replace(reg, args[key]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] !== undefined) {
|
||||
const reg = new RegExp(`(\\{)${i}(\\})`, 'g');
|
||||
result = result.replace(reg, args[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
String.prototype.rawformat = function rawFormat(object) {
|
||||
const res = this.split('{@}');
|
||||
return [res[0], object, res[1]].join();
|
||||
};
|
||||
|
||||
String.prototype.translate = function translate(...languages: string[]) {
|
||||
if (languages.length === 0) languages = ['zh_CN'];
|
||||
for (const language of languages) {
|
||||
if (locales[language]) {
|
||||
if (locales[language][this] !== undefined) return locales[language][this];
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
export default function load(data) {
|
||||
for (const i in data) {
|
||||
if (!locales[i]) locales[i] = data[i];
|
||||
else locales[i] = Object.assign(locales[i], data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
global.Hydro.locales = locales;
|
||||
global.Hydro.lib.i18n = load;
|
||||
@@ -0,0 +1,71 @@
|
||||
import assert from 'assert';
|
||||
import superagent from 'superagent';
|
||||
import download from './download';
|
||||
import { ValidationError, RemoteOnlineJudgeError } from '../error';
|
||||
|
||||
const RE_SYZOJ = /https?:\/\/([a-zA-Z0-9.]+)\/problem\/([0-9]+)\/?/i;
|
||||
|
||||
export async function syzoj(url: string, handler: any) {
|
||||
assert(url.match(RE_SYZOJ), new ValidationError('url'));
|
||||
if (!url.endsWith('/')) url += '/';
|
||||
const [, host, pid] = RE_SYZOJ.exec(url);
|
||||
const res = await superagent.get(`${url}export`);
|
||||
assert(res.status === 200, new RemoteOnlineJudgeError('Cannot connect to target server'));
|
||||
assert(res.body.success, new RemoteOnlineJudgeError((res.body.error || {}).message));
|
||||
const p = res.body.obj;
|
||||
const content = [];
|
||||
if (p.description) {
|
||||
content.push(
|
||||
handler.translate('problem.import.problem_description'),
|
||||
p.description,
|
||||
);
|
||||
}
|
||||
if (p.input_format) {
|
||||
content.push(
|
||||
handler.translate('problem.import.input_format'),
|
||||
p.input_format,
|
||||
);
|
||||
}
|
||||
if (p.output_format) {
|
||||
content.push(
|
||||
handler.translate('problem.import.output_format'),
|
||||
p.output_format,
|
||||
);
|
||||
}
|
||||
if (p.hint) {
|
||||
content.push(
|
||||
handler.translate('problem.import.hint'),
|
||||
p.hint,
|
||||
);
|
||||
}
|
||||
if (p.limit_and_hint) {
|
||||
content.push(
|
||||
handler.translate('problem.import.limit_and_hint'),
|
||||
p.limit_and_hint,
|
||||
);
|
||||
}
|
||||
if (p.have_additional_file) {
|
||||
content.push(
|
||||
handler.translate('problem.import.additional_file'),
|
||||
`${url}download/additional_file`,
|
||||
);
|
||||
}
|
||||
const pdoc = {
|
||||
title: p.title,
|
||||
content: content.join(' \n'),
|
||||
owner: handler.user._id,
|
||||
from: url,
|
||||
pid: `${host}_${pid}`,
|
||||
tags: p.tags,
|
||||
config: {
|
||||
time: p.time_limit,
|
||||
memory: p.memory_limit * 1024,
|
||||
filename: p.file_io_input_name,
|
||||
type: p.type,
|
||||
},
|
||||
};
|
||||
const r = download(`${url}testdata/download`);
|
||||
return [pdoc, r];
|
||||
}
|
||||
|
||||
global.Hydro.lib['import.syzoj'] = syzoj;
|
||||
@@ -0,0 +1,35 @@
|
||||
function decodeBase64WithUriEncoding(encodedText: string) {
|
||||
return Buffer.from(encodedText, 'base64').toString('utf8');
|
||||
}
|
||||
|
||||
function unescapedString(escapedString: string) {
|
||||
escapedString += new Array(5 - (escapedString.length % 4)).join('=');
|
||||
return escapedString.replace(/-/g, '+').replace(/_/g, '/');
|
||||
}
|
||||
|
||||
function decodeJWT(idToken: string) {
|
||||
const token = idToken.split('.');
|
||||
if (token.length !== 3) {
|
||||
throw new Error('Invalid idToken');
|
||||
}
|
||||
try {
|
||||
const headerSegment = JSON.parse(decodeBase64WithUriEncoding(token[0]));
|
||||
const payloadSegment = JSON.parse(decodeBase64WithUriEncoding(token[1]));
|
||||
const signature = unescapedString(token[2]);
|
||||
return {
|
||||
dataToSign: [token[0], token[1]].join('.'),
|
||||
header: headerSegment,
|
||||
payload: payloadSegment,
|
||||
signature,
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error('Invalid payload');
|
||||
}
|
||||
}
|
||||
|
||||
export function decode(idToken: string) {
|
||||
const decodedJWT = decodeJWT(idToken);
|
||||
return decodedJWT.payload;
|
||||
}
|
||||
|
||||
global.Hydro.lib.jwt = { decode };
|
||||
@@ -0,0 +1,41 @@
|
||||
import serialize from 'serialize-javascript';
|
||||
|
||||
function wrap(func: Function, type: string) {
|
||||
return (...args: any) => {
|
||||
const time = new Date();
|
||||
func(...args);
|
||||
if (global.Hydro.model.message) {
|
||||
try {
|
||||
global.Hydro.model.message.send(1, 1, serialize({ time, type, args }), 0);
|
||||
} catch (e) {
|
||||
func(e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class Logger {
|
||||
log: (...args: any[]) => void;
|
||||
|
||||
error: (...args: any[]) => void;
|
||||
|
||||
info: (...args: any[]) => void;
|
||||
|
||||
warn: (...args: any[]) => void;
|
||||
|
||||
debug: (...args: any[]) => void;
|
||||
|
||||
constructor() {
|
||||
this.log = wrap(console.log, 'log');
|
||||
this.error = wrap(console.error, 'error');
|
||||
this.info = wrap(console.info, 'info');
|
||||
this.warn = wrap(console.warn, 'warn');
|
||||
this.debug = wrap(console.debug, 'debug');
|
||||
}
|
||||
}
|
||||
|
||||
const exp = new Logger();
|
||||
|
||||
global.Hydro.lib.logger = exp;
|
||||
|
||||
export default exp;
|
||||
@@ -0,0 +1,39 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
import { SendMailError } from '../error';
|
||||
|
||||
export async function sendMail(to: string, subject: string, text: string, html: string) {
|
||||
let t: unknown;
|
||||
try {
|
||||
const { system } = global.Hydro.model;
|
||||
const [host, port, secure, user, pass, from] = await Promise.all([
|
||||
system.get('smtp.host'),
|
||||
system.get('smtp.port'),
|
||||
system.get('smtp.secure'),
|
||||
system.get('smtp.user'),
|
||||
system.get('smtp.pass'),
|
||||
system.get('smtp.from'),
|
||||
]);
|
||||
const transporter = nodemailer.createTransport({
|
||||
host, port, secure, auth: { user, pass },
|
||||
});
|
||||
t = await new Promise((resolve, reject) => {
|
||||
transporter.sendMail({
|
||||
from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
}, (err, info) => {
|
||||
if (err) reject(err);
|
||||
else resolve(info);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
throw new SendMailError(to);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
global.Hydro.lib.mail = {
|
||||
sendMail,
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
// From https://github.com/pvorb/node-md5/blob/master/md5.js
|
||||
// BSD 3-Clause "New" or "Revised" License
|
||||
|
||||
/* eslint-disable max-len */
|
||||
function safeAdd(x, y) {
|
||||
const lsw = (x & 0xffff) + (y & 0xffff);
|
||||
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xffff);
|
||||
}
|
||||
const bitRotateLeft = (num, cnt) => (num << cnt) | (num >>> (32 - cnt));
|
||||
const md5cmn = (q, a, b, x, s, t) => safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
|
||||
const md5ff = (a, b, c, d, x, s, t) => md5cmn((b & c) | (~b & d), a, b, x, s, t);
|
||||
const md5gg = (a, b, c, d, x, s, t) => md5cmn((b & d) | (c & ~d), a, b, x, s, t);
|
||||
const md5hh = (a, b, c, d, x, s, t) => md5cmn(b ^ c ^ d, a, b, x, s, t);
|
||||
const md5ii = (a, b, c, d, x, s, t) => md5cmn(c ^ (b | ~d), a, b, x, s, t);
|
||||
function binlMD5(x, len) {
|
||||
x[len >> 5] |= 0x80 << len % 32;
|
||||
x[(((len + 64) >>> 9) << 4) + 14] = len;
|
||||
let i;
|
||||
let olda;
|
||||
let oldb;
|
||||
let oldc;
|
||||
let oldd;
|
||||
let a = 1732584193;
|
||||
let b = -271733879;
|
||||
let c = -1732584194;
|
||||
let d = 271733878;
|
||||
for (i = 0; i < x.length; i += 16) {
|
||||
olda = a;
|
||||
oldb = b;
|
||||
oldc = c;
|
||||
oldd = d;
|
||||
a = md5ff(a, b, c, d, x[i], 7, -680876936);
|
||||
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
|
||||
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
|
||||
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
|
||||
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
|
||||
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
|
||||
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
|
||||
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
|
||||
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
|
||||
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
|
||||
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
|
||||
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
|
||||
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
|
||||
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
|
||||
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
|
||||
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
|
||||
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
|
||||
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
|
||||
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
|
||||
b = md5gg(b, c, d, a, x[i], 20, -373897302);
|
||||
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
|
||||
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
|
||||
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
|
||||
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
|
||||
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
|
||||
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
|
||||
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
|
||||
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
|
||||
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
|
||||
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
|
||||
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
|
||||
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
|
||||
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
|
||||
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
|
||||
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
|
||||
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
|
||||
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
|
||||
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
|
||||
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
|
||||
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
|
||||
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
|
||||
d = md5hh(d, a, b, c, x[i], 11, -358537222);
|
||||
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
|
||||
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
|
||||
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
|
||||
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
|
||||
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
|
||||
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
|
||||
a = md5ii(a, b, c, d, x[i], 6, -198630844);
|
||||
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
|
||||
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
|
||||
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
|
||||
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
|
||||
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
|
||||
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
|
||||
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
|
||||
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
|
||||
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
|
||||
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
|
||||
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
|
||||
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
|
||||
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
|
||||
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
|
||||
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551);
|
||||
a = safeAdd(a, olda);
|
||||
b = safeAdd(b, oldb);
|
||||
c = safeAdd(c, oldc);
|
||||
d = safeAdd(d, oldd);
|
||||
}
|
||||
return [a, b, c, d];
|
||||
}
|
||||
function binl2rstr(input) {
|
||||
let i;
|
||||
let output = '';
|
||||
const length32 = input.length * 32;
|
||||
for (i = 0; i < length32; i += 8) { output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff); }
|
||||
return output;
|
||||
}
|
||||
function rstr2binl(input) {
|
||||
let i;
|
||||
const output = [];
|
||||
output[(input.length >> 2) - 1] = undefined;
|
||||
for (i = 0; i < output.length; i += 1) { output[i] = 0; }
|
||||
const length8 = input.length * 8;
|
||||
for (i = 0; i < length8; i += 8) output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32;
|
||||
return output;
|
||||
}
|
||||
const rstrMD5 = (s) => binl2rstr(binlMD5(rstr2binl(s), s.length * 8));
|
||||
function rstr2hex(input) {
|
||||
const hexTab = '0123456789abcdef';
|
||||
let output = '';
|
||||
let x;
|
||||
let i;
|
||||
for (i = 0; i < input.length; i += 1) {
|
||||
x = input.charCodeAt(i);
|
||||
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export default function md5(s: string) {
|
||||
return rstr2hex(rstrMD5(unescape(encodeURIComponent(s))));
|
||||
}
|
||||
|
||||
global.Hydro.lib.md5 = md5;
|
||||
@@ -0,0 +1,78 @@
|
||||
import AnsiUp from 'ansi_up';
|
||||
import { ObjectID } from 'mongodb';
|
||||
import md5 from './md5';
|
||||
|
||||
const AU = new AnsiUp();
|
||||
|
||||
export function ansiToHtml(str: string) {
|
||||
return AU.ansi_to_html(str);
|
||||
}
|
||||
|
||||
export function gravatar(email: string, s = 32) {
|
||||
return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${s}`;
|
||||
}
|
||||
|
||||
export function datetimeSpan(dt: Date | ObjectID, relative = true, format = '%Y-%m-%d %H:%M:%S') {
|
||||
if (!dt) return 'DATETIME_SPAN_ERROR';
|
||||
if (dt instanceof ObjectID) dt = new Date(dt.generationTime * 1000);
|
||||
else if (typeof dt === 'number' || typeof dt === 'string') dt = new Date(dt);
|
||||
return '<span class="time{0}" data-timestamp="{1}">{2}</span>'.format(
|
||||
relative ? ' relative' : '',
|
||||
dt.getTime() / 1000,
|
||||
dt.format(format),
|
||||
);
|
||||
}
|
||||
|
||||
export function* paginate(page: number, numPages: number) {
|
||||
const radius = 2;
|
||||
let first: number;
|
||||
let last: number;
|
||||
if (page > 1) {
|
||||
if (page > 2) yield ['first', 1];
|
||||
yield ['previous', page - 1];
|
||||
}
|
||||
if (page <= radius) [first, last] = [1, Math.min(1 + radius * 2, numPages)];
|
||||
else if (page >= numPages - radius) {
|
||||
[first, last] = [Math.max(1, numPages - radius * 2), numPages];
|
||||
} else {
|
||||
[first, last] = [page - radius, page + radius];
|
||||
}
|
||||
if (first > 1) yield ['ellipsis', 0];
|
||||
for (let page0 = first; page0 < last + 1; page0++) {
|
||||
if (page0 !== page) yield ['page', page0];
|
||||
else yield ['current', page];
|
||||
}
|
||||
if (last < numPages) yield ['ellipsis', 0];
|
||||
if (page < numPages) {
|
||||
yield ['next', page + 1];
|
||||
if (page < numPages - 1) yield ['last', numPages];
|
||||
}
|
||||
}
|
||||
|
||||
export function size(s: number, base = 1) {
|
||||
s *= base;
|
||||
const unit = 1024;
|
||||
const unitNames = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||
for (const unitName of unitNames) {
|
||||
if (s < unit) return '{0} {1}'.format(Math.round(s * 10) / 10, unitName);
|
||||
s /= unit;
|
||||
}
|
||||
return `${Math.round(s * unit)} ${unitNames[unitNames.length - 1]}`;
|
||||
}
|
||||
|
||||
function _digit2(number: number) {
|
||||
return number < 10 ? `0${number}` : number.toString();
|
||||
}
|
||||
|
||||
export function formatSeconds(_seconds = '0') {
|
||||
const seconds = parseInt(_seconds, 10);
|
||||
return '{0}:{1}:{2}'.format(
|
||||
_digit2(Math.floor(seconds / 3600)),
|
||||
_digit2(Math.floor((seconds % 3600) / 60)),
|
||||
_digit2(seconds % 60),
|
||||
);
|
||||
}
|
||||
|
||||
global.Hydro.lib.misc = {
|
||||
gravatar, datetimeSpan, paginate, size, formatSeconds, ansiToHtml,
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { isNull } from 'lodash';
|
||||
import { PERM, PRIV } from '../model/builtin';
|
||||
|
||||
global.Hydro.ui.nav = [];
|
||||
|
||||
const trueChecker = () => true;
|
||||
const Checker = (perm: bigint, priv: number, checker: Function = trueChecker) => (handler) => (
|
||||
checker(handler)
|
||||
&& (perm ? handler.user.hasPerm(perm) : true)
|
||||
&& (priv ? handler.user.hasPriv(priv) : true)
|
||||
);
|
||||
|
||||
const Item = (
|
||||
name: string, args: any, prefix: string,
|
||||
...permPrivChecker: Array<number | bigint | Function>
|
||||
) => {
|
||||
let _priv: number;
|
||||
let _perm: bigint;
|
||||
let checker: Function = trueChecker;
|
||||
for (const item of permPrivChecker) {
|
||||
if (item instanceof Object && !isNull(item)) {
|
||||
if (item instanceof Array) {
|
||||
if (typeof item[0] === 'number') {
|
||||
// @ts-ignore
|
||||
_priv = item;
|
||||
} else if (typeof item[0] === 'bigint') {
|
||||
// @ts-ignore
|
||||
_perm = item;
|
||||
}
|
||||
} else if (typeof item.call !== 'undefined') {
|
||||
checker = item;
|
||||
}
|
||||
} else if (typeof item === 'number') {
|
||||
_priv = item;
|
||||
} else if (typeof item === 'bigint') {
|
||||
_perm = item;
|
||||
}
|
||||
}
|
||||
checker = Checker(_perm, _priv, checker);
|
||||
global.Hydro.ui.nav.push({
|
||||
name, args: args || {}, prefix, checker,
|
||||
});
|
||||
};
|
||||
|
||||
Item('homepage', null, 'homepage');
|
||||
Item('problem_main', null, 'problem', PERM.PERM_VIEW_PROBLEM);
|
||||
Item('training_main', null, 'training', PERM.PERM_VIEW_TRAINING);
|
||||
Item('homework_main', null, 'homework', PERM.PERM_VIEW_HOMEWORK);
|
||||
Item('discussion_main', null, 'discussion', PERM.PERM_VIEW_DISCUSSION);
|
||||
Item('contest_main', null, 'contest', PERM.PERM_VIEW_CONTEST);
|
||||
Item('record_main', null, 'record');
|
||||
Item('ranking', null, 'ranking', PERM.PERM_VIEW_RANKING);
|
||||
Item('domain_dashboard', null, 'domain', PERM.PERM_EDIT_DOMAIN);
|
||||
Item('manage_dashboard', null, 'manage', PRIV.PRIV_EDIT_SYSTEM);
|
||||
|
||||
global.Hydro.lib.nav = Item;
|
||||
export default Item;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Cursor } from 'mongodb';
|
||||
import { ValidationError } from '../error';
|
||||
|
||||
export default async function paginate(
|
||||
cursor: Cursor, page: number, pageSize: number,
|
||||
): Promise<[any[]/* docs */, number/* numPages */, number/* count */]> {
|
||||
if (page <= 0) throw new ValidationError('page');
|
||||
const [count, pageDocs] = await Promise.all([
|
||||
cursor.count(),
|
||||
cursor.skip((page - 1) * pageSize).limit(pageSize).toArray(),
|
||||
]);
|
||||
const numPages = Math.floor((count + pageSize - 1) / pageSize);
|
||||
return [pageDocs, numPages, count];
|
||||
}
|
||||
|
||||
global.Hydro.lib.paginate = paginate;
|
||||
@@ -0,0 +1,14 @@
|
||||
function* ranked<T>(diter: T[], equ = (a: T, b: T) => a === b): Generator<[number, T], void> {
|
||||
let last = null;
|
||||
let r = 0;
|
||||
let count = 0;
|
||||
for (const doc of diter) {
|
||||
count++;
|
||||
if (count === 1 || !equ(last, doc)) r = count;
|
||||
last = doc;
|
||||
yield [r, doc];
|
||||
}
|
||||
}
|
||||
|
||||
global.Hydro.lib.rank = ranked;
|
||||
export default ranked;
|
||||
@@ -0,0 +1,127 @@
|
||||
// Based on https://github.com/QAQrz/Codeforces-Rating-System/blob/master/rating.py
|
||||
|
||||
class User {
|
||||
uid: number;
|
||||
|
||||
rank: number;
|
||||
|
||||
old: number;
|
||||
|
||||
seed?: number;
|
||||
|
||||
delta?: number;
|
||||
|
||||
new?: number;
|
||||
|
||||
constructor(rank: number, old: number, uid = 0) {
|
||||
this.rank = rank;
|
||||
this.old = old;
|
||||
this.seed = 1;
|
||||
this.uid = uid;
|
||||
}
|
||||
}
|
||||
|
||||
class RatingCalculator {
|
||||
users: User[];
|
||||
|
||||
constructor(users: User[]) {
|
||||
this.users = [];
|
||||
for (const user of users) {
|
||||
this.users.push(new User(user.rank, user.old, user.uid));
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
calP(a: User, b: User) {
|
||||
return 1 / (1 + 10 ** ((b.old - a.old) / 400));
|
||||
}
|
||||
|
||||
getExSeed(rating: number, ownUser: User) {
|
||||
const exUser = new User(0.0, rating);
|
||||
let result = 1;
|
||||
for (const user of this.users) {
|
||||
if (user !== ownUser) result += this.calP(user, exUser);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
calRating(rank: number, user: User) {
|
||||
let left = 1;
|
||||
let right = 8000;
|
||||
while (right - left > 1) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
if (this.getExSeed(mid, user) < rank) right = mid;
|
||||
else left = mid;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
calculate() {
|
||||
// Calculate seed
|
||||
for (let i = 0; i < this.users.length; i++) {
|
||||
this.users[i].seed = 1;
|
||||
for (let j = 0; j < this.users.length; j++) {
|
||||
if (i !== j) {
|
||||
this.users[i].seed += this.calP(this.users[j], this.users[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Calculate initial delta and sum_delta
|
||||
let sumDelta = 0;
|
||||
for (const user of this.users) {
|
||||
user.delta = (this.calRating(Math.sqrt(user.rank * user.seed), user) - user.old) / 2;
|
||||
sumDelta += user.delta;
|
||||
}
|
||||
// Calculate first inc
|
||||
let inc = Math.floor(-sumDelta / this.users.length) - 1;
|
||||
for (const user of this.users) user.delta += inc;
|
||||
// Calculate second inc
|
||||
this.users = this.users.sort((a, b) => b.old - a.old);
|
||||
const s = Math.min(
|
||||
this.users.length,
|
||||
Math.floor(4 * Math.round(Math.sqrt(this.users.length))),
|
||||
);
|
||||
let sumS = 0;
|
||||
for (let i = 0; i < s; i++) {
|
||||
sumS += this.users[i].delta;
|
||||
}
|
||||
inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0);
|
||||
// Calculate new rating
|
||||
for (const user of this.users) {
|
||||
user.delta += inc;
|
||||
user.new = user.old + user.delta;
|
||||
}
|
||||
this.users = this.users.sort((a, b) => a.rank - b.rank);
|
||||
return this.users;
|
||||
}
|
||||
}
|
||||
|
||||
interface RatingInputUser {
|
||||
old: number,
|
||||
uid: number,
|
||||
rank: number,
|
||||
}
|
||||
|
||||
interface RatingOutputUser {
|
||||
new: number,
|
||||
uid: number,
|
||||
}
|
||||
|
||||
export default function calculate(users: RatingInputUser[]): RatingOutputUser[] {
|
||||
let last_idx = 0;
|
||||
let last_rank = 1;
|
||||
for (let i = 1; i < users.length; i++) {
|
||||
if (users[i].rank > last_rank) {
|
||||
for (let j = last_idx; j < i; j++) users[j].rank = i;
|
||||
last_idx = i;
|
||||
last_rank = users[i].rank;
|
||||
}
|
||||
}
|
||||
for (let i = last_idx; i < users.length; i++) {
|
||||
users[i].rank = users.length;
|
||||
}
|
||||
const calculator = new RatingCalculator(users);
|
||||
return calculator.calculate() as RatingOutputUser[];
|
||||
}
|
||||
|
||||
global.Hydro.lib.rating = calculate;
|
||||
@@ -0,0 +1,10 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
function sha1(content: string) {
|
||||
const hash = crypto.createHash('sha1');
|
||||
hash.update(content);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
global.Hydro.lib.sha1 = sha1;
|
||||
export default sha1;
|
||||
@@ -0,0 +1,62 @@
|
||||
import systeminformation from 'systeminformation';
|
||||
import { size } from './misc';
|
||||
|
||||
const cache = {
|
||||
mid: '', cpu: '', osinfo: '', flags: '',
|
||||
};
|
||||
|
||||
export async function get() {
|
||||
const [Cpu, Memory, OsInfo, CurrentLoad, CpuFlags, CpuTemp, Battery] = await Promise.all([
|
||||
systeminformation.cpu(),
|
||||
systeminformation.mem(),
|
||||
systeminformation.osInfo(),
|
||||
systeminformation.currentLoad(),
|
||||
systeminformation.cpuFlags(),
|
||||
systeminformation.cpuTemperature(),
|
||||
systeminformation.battery(),
|
||||
]);
|
||||
const cpu = `${Cpu.manufacturer} ${Cpu.brand}`;
|
||||
const memory = `${size(Memory.active)}/${size(Memory.total)}`;
|
||||
const osinfo = `${OsInfo.distro} ${OsInfo.release} ${OsInfo.codename} ${OsInfo.kernel} ${OsInfo.arch}`;
|
||||
const load = `${CurrentLoad.avgload}`;
|
||||
const flags = CpuFlags;
|
||||
let battery;
|
||||
if (!Battery.hasbattery) battery = 'No battery';
|
||||
else battery = `${Battery.type} ${Battery.model} ${Battery.percent}%${Battery.ischarging ? ' Charging' : ''}`;
|
||||
const mid = OsInfo.serial;
|
||||
cache.cpu = cpu;
|
||||
cache.osinfo = osinfo;
|
||||
cache.flags = flags;
|
||||
cache.mid = mid;
|
||||
return {
|
||||
mid, cpu, memory, osinfo, load, flags, CpuTemp, battery,
|
||||
};
|
||||
}
|
||||
|
||||
export async function update(): Promise<[string, any, any]> {
|
||||
const [Memory, CurrentLoad, CpuTemp, Battery] = await Promise.all([
|
||||
systeminformation.mem(),
|
||||
systeminformation.currentLoad(),
|
||||
systeminformation.cpuTemperature(),
|
||||
systeminformation.battery(),
|
||||
]);
|
||||
const {
|
||||
mid, cpu, osinfo, flags,
|
||||
} = cache;
|
||||
const memory = `${size(Memory.active)}/${size(Memory.total)}`;
|
||||
const load = `${CurrentLoad.avgload}`;
|
||||
let battery;
|
||||
if (!Battery.hasbattery) battery = 'No battery';
|
||||
else battery = `${Battery.type} ${Battery.model} ${Battery.percent}%${Battery.ischarging ? ' Charging' : ''}`;
|
||||
return [
|
||||
mid,
|
||||
{
|
||||
memory, load, battery, CpuTemp,
|
||||
},
|
||||
{
|
||||
mid, cpu, memory, osinfo, load, flags, battery, CpuTemp,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
global.Hydro.lib.sysinfo = { get, update };
|
||||
@@ -0,0 +1,32 @@
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
export default function convert(ini: string) {
|
||||
const f = ini.split('\n');
|
||||
const count = parseInt(f[0], 10);
|
||||
const res = { cases: [] };
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const [input, output, time, score, memory] = f[i].split('|');
|
||||
const cur = {
|
||||
input, output, score: parseInt(score, 10), time: `${time}s`, memory: '128m',
|
||||
};
|
||||
if (!Number.isNaN(parseInt(memory, 10))) cur.memory = `${Math.floor(parseInt(memory, 10) / 1024)}m`;
|
||||
res.cases.push(cur);
|
||||
}
|
||||
if (res.cases.length > 0) {
|
||||
const { time, score, memory } = res.cases[0];
|
||||
for (let i = 1; i < res.cases.length; i++) {
|
||||
if (res.cases[i].time !== time
|
||||
|| res.cases[i].score !== score
|
||||
|| res.cases[i].memory !== memory) {
|
||||
return yaml.safeDump(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
return yaml.safeDump({
|
||||
time: res.cases[0].time,
|
||||
score: res.cases[0].score,
|
||||
memory: res.cases[0].memory,
|
||||
});
|
||||
}
|
||||
|
||||
global.Hydro.lib['testdata.convert.ini'] = convert;
|
||||
@@ -0,0 +1,60 @@
|
||||
import Zip from 'adm-zip';
|
||||
import { safeLoad } from 'js-yaml';
|
||||
import { LocalProblemConfig } from '../interface';
|
||||
|
||||
export async function readConfig(filePath: string) {
|
||||
const data = new Zip(filePath);
|
||||
const entries = data.getEntries();
|
||||
for (const entry of entries) {
|
||||
if (entry.name.toLowerCase() === 'config.yaml') {
|
||||
return entry.getData().toString();
|
||||
}
|
||||
if (entry.name.toLowerCase() === 'config.ini') {
|
||||
const ini = entry.getData().toString();
|
||||
const conv = global.Hydro.lib['testdata.convert.ini'];
|
||||
return conv(ini);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
interface ParseResult {
|
||||
count: number,
|
||||
memoryMax: number,
|
||||
memoryMin: number,
|
||||
timeMax: number,
|
||||
timeMin: number,
|
||||
}
|
||||
|
||||
export async function parseConfig(config: string | LocalProblemConfig) {
|
||||
const result: ParseResult = {
|
||||
count: 0,
|
||||
memoryMin: Number.MAX_SAFE_INTEGER,
|
||||
memoryMax: 0,
|
||||
timeMin: Number.MAX_SAFE_INTEGER,
|
||||
timeMax: 0,
|
||||
};
|
||||
let cfg: LocalProblemConfig = {};
|
||||
if (typeof config === 'string') {
|
||||
// TODO should validate here?
|
||||
cfg = safeLoad(config) as LocalProblemConfig;
|
||||
} else cfg = config;
|
||||
if (cfg.cases) {
|
||||
for (const c of cfg.cases) {
|
||||
result.memoryMax = Math.max(result.memoryMax, c.memory);
|
||||
result.memoryMin = Math.min(result.memoryMin, c.memory);
|
||||
result.timeMax = Math.max(result.timeMax, c.time);
|
||||
result.timeMin = Math.min(result.timeMin, c.time);
|
||||
result.count++;
|
||||
}
|
||||
} else if (cfg.subtasks) {
|
||||
for (const subtask of cfg.subtasks) {
|
||||
result.memoryMax = Math.max(result.memoryMax, subtask.memory);
|
||||
result.memoryMin = Math.min(result.memoryMin, subtask.memory);
|
||||
result.timeMax = Math.max(result.timeMax, subtask.time);
|
||||
result.timeMin = Math.min(result.timeMin, subtask.time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.Hydro.lib.testdataConfig = { readConfig, parseConfig };
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as browser from 'detect-browser';
|
||||
|
||||
export function parse(str: string) {
|
||||
return browser.parseUserAgent(str);
|
||||
}
|
||||
|
||||
export function icon(str: string) {
|
||||
return str.split(' ')[0].toLowerCase();
|
||||
}
|
||||
|
||||
global.Hydro.lib.useragent = { parse, icon };
|
||||
@@ -0,0 +1,157 @@
|
||||
/* eslint-disable no-empty */
|
||||
import { ValidationError } from '../error';
|
||||
|
||||
const RE_UID = /^-?\d+$/i;
|
||||
const RE_PID = /^([a-zA-Z]+[a-zA-Z0-9]*)|$/i;
|
||||
const RE_UNAME = /[^\s\u3000](.{,254}[^\s\u3000])?$/i;
|
||||
const RE_ROLE = /^[_0-9A-Za-z]{1,256}$/i;
|
||||
const RE_MAIL = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i;
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
function _validate(scheme: any, arg: any, base: string): void {
|
||||
if (!(scheme instanceof Object)) return _validate({ $type: scheme }, arg, base);
|
||||
if (scheme.$validate) {
|
||||
const result = scheme.$validate(arg);
|
||||
if (result === false) throw new ValidationError(base);
|
||||
}
|
||||
if (scheme.$group) _validate(scheme.$group, arg, base);
|
||||
if (scheme.$or) {
|
||||
if (scheme.$or instanceof Array) {
|
||||
for (const s of scheme.$or) {
|
||||
let success = false;
|
||||
try {
|
||||
_validate(s, arg, base);
|
||||
success = true;
|
||||
} catch { }
|
||||
// TODO beautify output
|
||||
if (!success) throw new ValidationError(scheme.$or);
|
||||
}
|
||||
} else {
|
||||
for (const skey in scheme.$or) {
|
||||
let success = false;
|
||||
try {
|
||||
_validate(scheme.$or[skey], arg[skey], `${base}.${skey}`);
|
||||
success = true;
|
||||
} catch { }
|
||||
// TODO beautify output
|
||||
if (!success) throw new ValidationError(base, scheme.$or);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scheme.$and) for (const s of scheme.$and) _validate(s, arg, base);
|
||||
if (scheme.$type) {
|
||||
if (typeof scheme.$type === 'string') {
|
||||
if (!scheme.$type.endsWith('?')) {
|
||||
if (!typeof arg === scheme.$type) {
|
||||
throw new ValidationError(base);
|
||||
}
|
||||
} else if (arg && !scheme.$type.includes(typeof arg)) {
|
||||
throw new ValidationError(base);
|
||||
}
|
||||
} else if (!(arg instanceof scheme.$type)) {
|
||||
throw new ValidationError(base);
|
||||
}
|
||||
}
|
||||
if (scheme.$eq && scheme.$eq !== arg) throw new ValidationError(base);
|
||||
if (scheme.$not && scheme.$not === arg) throw new ValidationError(base);
|
||||
if (scheme.$in && !(scheme.$in.includes(arg))) throw new ValidationError(base);
|
||||
if (scheme.$nin && scheme.$nin.includes(arg)) throw new ValidationError(base);
|
||||
for (const key in scheme) {
|
||||
if (key[0] !== '$') {
|
||||
if (arg instanceof Object) {
|
||||
_validate(scheme[key], arg[key], `${base}.${key}`);
|
||||
} else {
|
||||
throw new ValidationError(base);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validate(scheme: any, arg: any) {
|
||||
return _validate(scheme, arg, '');
|
||||
}
|
||||
|
||||
export const isTitle = (s) => s && s.length < 64;
|
||||
export const checkTitle = (s) => { if (!(s && s.length < 64)) throw new ValidationError('title'); else return s; };
|
||||
export const isUid = (s) => RE_UID.test(s);
|
||||
export const checkUid = (s) => { if (!isUid(s)) throw new ValidationError('uid'); else return s; };
|
||||
export const isUname = (s) => RE_UNAME.test(s);
|
||||
export const checkUname = (s) => { if (!isUname(s)) throw new ValidationError('uname'); else return s; };
|
||||
export const isRole = (s) => RE_ROLE.test(s);
|
||||
export const checkRole = (s) => { if (!isRole(s)) throw new ValidationError('role'); else return s; };
|
||||
export const isPassword = (s) => s.length >= 5;
|
||||
export const checkPassword = (s) => { if (!(s && s.length >= 5)) throw new ValidationError('password'); else return s; };
|
||||
export const isEmail = (s) => RE_MAIL.test(s);
|
||||
export const checkEmail = (s) => { if (!RE_MAIL.test(s)) throw new ValidationError('mail'); else return s; };
|
||||
export const isContent = (s: any) => s && s.length < 65536;
|
||||
export const checkContent = (s) => { if (!(s && s.length < 65536)) throw new ValidationError('content'); else return s; };
|
||||
export const isName = (s) => s && s.length < 256;
|
||||
export const checkName = (s) => { if (!isName(s)) throw new ValidationError('name'); else return s; };
|
||||
export const isPid = (s) => RE_PID.test(s.toString());
|
||||
export const checkPid = (s) => { if (!RE_PID.test(s)) throw new ValidationError('pid'); else return s; };
|
||||
export const isIntro = () => true;
|
||||
export const checkIntro = (s) => { if (!isIntro()) throw new ValidationError('intro'); else return s; };
|
||||
export const isDescription = (s: any) => s && s.length < 65536;
|
||||
export const checkDescription = (s) => { if (!(s && s.length < 65536)) throw new ValidationError('description'); else return s; };
|
||||
|
||||
global.Hydro.lib.validator = {
|
||||
validate,
|
||||
isTitle,
|
||||
checkTitle,
|
||||
isUid,
|
||||
checkUid,
|
||||
isUname,
|
||||
checkUname,
|
||||
isRole,
|
||||
checkRole,
|
||||
isPassword,
|
||||
checkPassword,
|
||||
isEmail,
|
||||
checkEmail,
|
||||
isContent,
|
||||
checkContent,
|
||||
isName,
|
||||
checkName,
|
||||
isPid,
|
||||
checkPid,
|
||||
isIntro,
|
||||
checkIntro,
|
||||
isDescription,
|
||||
checkDescription,
|
||||
};
|
||||
/*
|
||||
ID_RE = re.compile(r'[^\\/\s\u3000]([^\\/\n\r]*[^\\/\s\u3000])?')
|
||||
|
||||
def is_id(s):
|
||||
return bool(ID_RE.fullmatch(s))
|
||||
|
||||
def check_category_name(s):
|
||||
if not is_id(s):
|
||||
raise error.ValidationError('category_name')
|
||||
|
||||
def check_node_name(s):
|
||||
if not is_id(s):
|
||||
raise error.ValidationError('node_name')
|
||||
|
||||
def is_intro(s):
|
||||
return isinstance(s, str) and 0 < len(s.strip()) < 500
|
||||
|
||||
def check_intro(s):
|
||||
if not is_intro(s):
|
||||
raise error.ValidationError('intro')
|
||||
|
||||
def is_description(s):
|
||||
return isinstance(s, str) and len(s) < 65536
|
||||
|
||||
def check_description(s):
|
||||
if not is_description(s):
|
||||
raise error.ValidationError('description')
|
||||
|
||||
def is_lang(i):
|
||||
return i in export const ant.language.LANG_TEXTS
|
||||
|
||||
def check_lang(i):
|
||||
if not is_lang(i):
|
||||
raise error.ValidationError('lang')
|
||||
|
||||
*/
|
||||
Reference in new issue
Block a user