import { GuildMember, User } from "discord.js"; import { getRepository, In } from "typeorm"; import { KnownUser } from "@shared/db/entity/KnownUser"; import { Option, } from "@shared/common/async_utils"; import humanizeDuration from "humanize-duration"; import * as t from "io-ts"; import YAML from "yaml"; import { PathReporter } from "io-ts/PathReporter"; const VALID_EXTENSIONS = new Set([ "png", "jpg", "jpeg", "bmp", ]); export type EsModuleClass = { prototype: T; new(...params: unknown[]): T; }; export function isModuleClass(obj: unknown) : obj is EsModuleClass { return Object.prototype.hasOwnProperty.call(obj, "prototype"); } export function isDevelopment(): boolean { return process.env.NODE_ENV == "dev"; } export function parseYaml(type: t.Type, yaml: string): Option<{ result: A }, { errors: string[] }> { const t = type.decode(YAML.parse(yaml)); if (t._tag == "Left"){ return { ok: false, errors: PathReporter.report(t) }; } else { return { ok: true, result: t.right }; } } export function isValidImage(fileName?: string | null): boolean { if (!fileName) return false; const extPosition = fileName.lastIndexOf("."); if (extPosition < 0) return false; const ext = fileName.substring(extPosition + 1).toLowerCase(); return VALID_EXTENSIONS.has(ext); } export async function isAuthorisedAsync(member: GuildMember | User | null | undefined): Promise { if (!member) return false; const repo = getRepository(KnownUser); const user = await repo.findOne({ where: { userID: member.id }, select: ["canModerate"] }); if (user && user.canModerate) return true; if (member instanceof GuildMember) { const role = await repo.findOne({ select: ["userID"], where: { userID: In(member.roles.cache.keyArray()), canModerate: true } }); if (role) return true; } return false; } export function compareNumbers(prop: (o: T) => number) { return (a: T, b: T): -1 | 0 | 1 => { const ap = prop(a); const bp = prop(b); if (ap < bp) return 1; else if (ap > bp) return -1; return 0; }; } export type Dict = { [key: string]: TVal }; export function getNumberEnums(e : Record) : number[] { return Object.keys(e).filter(k => typeof e[k as keyof E] === "number").map(k => e[k as keyof E]); } export function formatString(str: string, vars: Record): string { return Object.keys(vars).filter(s => Object.prototype.hasOwnProperty.call(vars, s)).reduce((s, cur) => s.replace(`{${cur}}`, vars[cur]), str); } export function parseArgs(str: string): string[] { const result: string[] = []; let quoteMode = false; let escapeNext = false; let buffer = ""; for (const char of str) { if (!escapeNext) if (char == "\"") { if (!quoteMode) { quoteMode = true; } else { quoteMode = false; result.push(buffer); buffer = ""; } continue; } else if (char == "\\") { escapeNext = true; continue; } else if (/\s/.test(char)) { if (buffer.length == 0) continue; if (!quoteMode) { result.push(buffer); buffer = ""; continue; } } buffer += char; escapeNext = false; } if (buffer.length != 0) result.push(buffer); return result; } export const UNIT_MEASURES: humanizeDuration.UnitMeasuresOptions = { ms: 1, s: 1000, m: 60 * 1000, h: 60 * 60 * 1000, d: 24 * 60 * 60 * 1000, w: 7 * 24 * 60 * 60 * 1000, mo: 30 * 24 * 60 * 60 * 1000, y: 365 * 24 * 60 * 60 * 1000 }; const durationUnits = UNIT_MEASURES as Record; const durationPattern = new RegExp(`(\\d+(?:[.,]?\\d+)?)\\s*(${Object.keys(durationUnits).sort((a, b) => b.length - a.length).join("|")})`, "g"); export function parseDuration(s: string): number | undefined { if (!s) return undefined; s = s.trim(); let buffer = s; let result = 0; let match; while ((match = durationPattern.exec(s))) { buffer = buffer.replace(match[0], "").trim(); result += +match[1] * durationUnits[match[2]]; } if (buffer.length != 0) return undefined; return result; }