import crypto from 'crypto'; import os from 'os'; import path from 'path'; import { Duplex } from 'stream'; import { inspect } from 'util'; import fs from 'fs-extra'; import moment, { isMoment, Moment } from 'moment-timezone'; import { ObjectId } from 'mongodb'; import Logger from 'reggol'; export * as yaml from 'js-yaml'; export * as fs from 'fs-extra'; Logger.levels.base = process.env.DEV ? 3 : 2; Logger.targets[0].showTime = 'dd hh:mm:ss'; Logger.targets[0].label = { align: 'right', width: 9, margin: 1, }; export { Logger, moment }; const encrypt = (algorithm, content) => crypto.createHash(algorithm).update(content).digest('hex'); export const sha1 = (content: string) => encrypt('sha1', content); export const md5 = (content: string) => encrypt('md5', content); export function folderSize(folderPath: string) { // eslint-disable-next-line @typescript-eslint/no-shadow let size = 0; const _next = function a(p: string) { if (p) { const stats = fs.statSync(p); if (!stats.isDirectory() || stats.isSymbolicLink()) { if (!stats.isSymbolicLink()) size += stats.size; } else { size += stats.size; const files = fs.readdirSync(p); if (Array.isArray(files)) { files.forEach((file) => { _next(path.join(p, file)); }); } } } }; _next(folderPath); return size; } String.prototype.format = function formatStr(...args) { let result = this; if (args.length) { if (args.length === 1 && typeof args[0] === 'object') { const t = args[0]; for (const key in t) { if (!key.startsWith('_') && t[key] !== undefined) { if (t._inspect && typeof t[key] === 'object') { t[key] = inspect(t[key], { colors: process?.stderr?.isTTY }); } const reg = new RegExp(`(\\{${key}\\})`, 'g'); result = result.replace(reg, t[key]); } } } else return this.formatFromArray(args); } return result.toString(); }; export function isClass(obj: any, strict = false): obj is new (...args: any) => any { if (typeof obj !== 'function') return false; if (obj.prototype === undefined) return false; if (obj.prototype.constructor !== obj) return false; if (Object.getOwnPropertyNames(obj.prototype).length >= 2) return true; const str = obj.toString(); if (str.slice(0, 5) === 'class') return true; if (/^function\s+\(|^function\s+anonymous\(/.test(str)) return false; if (strict && /^function\s+[A-Z]/.test(str)) return true; if (/\b\(this\b|\bthis[.[]\b/.test(str)) { if (!strict || /classCallCheck\(this/.test(str)) return true; return /^function\sdefault_\d+\s*\(/.test(str); } return false; } export function streamToBuffer(stream: any, maxSize = 0): Promise { return new Promise((resolve, reject) => { const buffers = []; let length = 0; function onData(data) { buffers.push(data); length += data.length; if (maxSize && length > maxSize) { stream.removeListener('data', onData); reject(new Error('buffer length exceeded')); } } stream.on('error', reject); stream.on('data', onData); stream.on('end', () => resolve(Buffer.concat(buffers))); }); } export function bufferToStream(buffer: Buffer): NodeJS.ReadableStream { const stream = new Duplex(); stream.push(buffer); stream.push(null); return stream; } export namespace Time { export const second = 1000; export const minute = second * 60; export const hour = minute * 60; export const day = hour * 24; export const week = day * 7; export function formatTimeShort(ms: number) { const abs = Math.abs(ms); if (abs >= day - hour / 2) return `${Math.round(ms / day)}d`; if (abs >= hour - minute / 2) return `${Math.round(ms / hour)}h`; if (abs >= minute - second / 2) return `${Math.round(ms / minute)}m`; if (abs >= second) return `${Math.round(ms / second)}s`; return `${ms}ms`; } export function getObjectID(timestamp: string | Date | Moment, allZero = true) { let _timestamp: number; if (typeof timestamp === 'string') _timestamp = new Date(timestamp).getTime(); else if (isMoment(timestamp)) _timestamp = timestamp.toDate().getTime(); else _timestamp = timestamp.getTime(); const hexSeconds = Math.floor(_timestamp / 1000).toString(16); return new ObjectId(`${hexSeconds}${allZero ? '0000000000000000' : new ObjectId().toHexString().substr(8)}`); } } export function errorMessage(err: Error | string) { const t = typeof err === 'string' ? err : err.stack; const lines = t.split('\n') .filter((i) => !i.includes(' (node:') && !i.includes('(internal')); let cursor = 1; let count = 0; while (cursor < lines.length) { if (lines[cursor] !== lines[cursor - 1]) { if (count) { lines[cursor - 1] += ` [+${count}]`; count = 0; } cursor++; } else { count++; lines.splice(cursor, 1); } } const parsed = lines.join('\n') .replace(/[A-Z]:\\.+\\@hydrooj\\/g, '@hydrooj\\') .replace(/\/.+\/@hydrooj\//g, '\\') .replace(/[A-Z]:\\.+\\hydrooj\\/g, 'hydrooj\\') .replace(/\/.+\/hydrooj\//g, 'hydrooj/') .replace(/[A-Z]:\\.+\\node_modules\\/g, '') .replace(/\/.+\/node_modules\//g, '') .replace(/\\/g, '/'); if (typeof err === 'string') return parsed; err.stack = parsed; return err; } export function changeErrorType(err: any, Err: any) { const e = new Err(err.message); e.stack = err.stack; if (err.params) e.params = err.params; return e; } export async function findFile(pathname: string, doThrow = true) { if (await fs.pathExists(path.resolve(pathname))) return path.resolve(pathname); if (await fs.pathExists(path.resolve(process.cwd(), pathname))) return path.resolve(process.cwd(), pathname); if (await fs.pathExists(path.resolve(__dirname, pathname))) return path.resolve(__dirname, pathname); try { return require.resolve(pathname); } catch (e) { } if (pathname.includes('/')) { const eles = pathname.split('/'); let pkg = eles.shift(); if (pkg.startsWith('@')) pkg = `${pkg}/${eles.shift()}`; const rest = eles.join('/'); try { const p = path.dirname(require.resolve(path.join(pkg, 'package.json'))); if (await fs.pathExists(path.resolve(p, rest))) return path.resolve(p, rest); } catch (e) { } } if (await fs.pathExists(path.resolve(os.homedir(), pathname))) return path.resolve(os.homedir(), pathname); if (await fs.pathExists(path.resolve(os.homedir(), '.hydro', pathname))) return path.resolve(os.homedir(), '.hydro', pathname); // eslint-disable-next-line max-len if (await fs.pathExists(path.resolve(os.homedir(), '.config', 'hydro', pathname))) return path.resolve(os.homedir(), '.config', 'hydro', pathname); if (doThrow) throw new Error(`File ${pathname} not found`); return null; } export function findFileSync(pathname: string, doThrow: boolean | Error = true) { if (fs.pathExistsSync(path.resolve(pathname))) return path.resolve(pathname); if (fs.pathExistsSync(path.resolve(process.cwd(), pathname))) return path.resolve(process.cwd(), pathname); if (fs.pathExistsSync(path.resolve(__dirname, pathname))) return path.resolve(__dirname, pathname); try { return require.resolve(pathname); } catch (e) { } if (pathname.includes('/')) { const eles = pathname.split('/'); let pkg = eles.shift(); if (pkg.startsWith('@')) pkg = `${pkg}/${eles.shift()}`; const rest = eles.join('/'); try { const p = path.dirname(require.resolve(path.join(pkg, 'package.json'))); if (fs.statSync(path.resolve(p, rest))) return path.resolve(p, rest); } catch (e) { } } if (fs.pathExistsSync(path.resolve(os.homedir(), pathname))) return path.resolve(os.homedir(), pathname); if (fs.pathExistsSync(path.resolve(os.homedir(), '.hydro', pathname))) return path.resolve(os.homedir(), '.hydro', pathname); if (fs.pathExistsSync(path.resolve(os.homedir(), '.config', 'hydro', pathname))) return path.resolve(os.homedir(), '.config', 'hydro', pathname); if (doThrow) throw (typeof doThrow !== 'boolean' ? doThrow : new Error(`File ${pathname} not found`)); return null; } export async function retry(func: Function, ...args: any[]): Promise; export async function retry(times: number, func: Function, ...args: any[]): Promise; // eslint-disable-next-line consistent-return export async function retry(arg0: number | Function, func: any, ...args: any[]) { let res; if (typeof arg0 !== 'number') { args = [func, ...args]; func = arg0; arg0 = 3; } for (let i = 1; i <= arg0; i++) { try { // eslint-disable-next-line no-await-in-loop res = await func(...args); } catch (e) { if (i === arg0) throw e; continue; } return res; } } export function CallableInstance(property = '__call__') { let func; if (typeof property === 'function') func = property; else func = this.constructor.prototype[property]; const apply = function __call__(...args) { return func.apply(apply, ...args); }; Object.setPrototypeOf(apply, this.constructor.prototype); Object.getOwnPropertyNames(func).forEach((p) => { Object.defineProperty(apply, p, Object.getOwnPropertyDescriptor(func, p)); }); return apply; } CallableInstance.prototype = Object.create(Function.prototype); export const htmlEncode = (str: string) => str.replace(/[&<>'"]/g, (tag: string) => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"', }[tag])); export function Counter() { return new Proxy({}, { get: (target, prop) => { if (target[prop] === undefined) return 0; return target[prop]; }, }) as Record; } export * from '@hydrooj/utils/lib/common';