const ID_SIZE = 32; export function unifySlug(slug: string | string[]): string { return (Array.isArray(slug) ? slug.join('/') : slug); } export function getID() { for (var id = [], i = 0; i < ID_SIZE; i++) id.push((36 * Math.random() | 0).toString(36)); return id.join(""); } export function group< T, K extends keyof T, V extends keyof T, KeyType extends string | number | symbol = Extract >( table: T[], key: K & (T[K] extends string | number | symbol ? K : never), value: V ): Record { return table.reduce((p, v) => { p[v[key] as KeyType] = v[value]; return p; }, {} as Record); } export function parsePath(path: string): string { return path.replace(/(\d+?)\. ?/g, '').toLowerCase().replaceAll(" ", "-").normalize("NFD").replaceAll(/[\u0300-\u036f]/g, "").replaceAll('(', '').replaceAll(')', '').replace(/\-+/g, '-'); } export function parseId(id: string | undefined): string | undefined { return id; //return id?.normalize('NFD')?.replace(/[\u0300-\u036f]/g, '')?.replace(/^\d\. */g, '')?.replace(/\s/g, "-")?.replace(/%/g, "-percent")?.replace(/\?/g, "-q")?.toLowerCase(); } export function padLeft(text: string, pad: string, length: number): string { return text.concat(pad.repeat(length - text.length)); } export function padRight(text: string, pad: string, length: number): string { return pad.repeat(length - text.length).concat(text); } export function format(date: Date, template: string): string { const transforms: Record string> = { "yyyy": (date: Date) => date.getUTCFullYear().toString(), "MM": (date: Date) => padRight((date.getUTCMonth() + 1).toString(), '0', 2), "dd": (date: Date) => padRight(date.getUTCDate().toString(), '0', 2), "mm": (date: Date) => padRight(date.getUTCMinutes().toString(), '0', 2), "HH": (date: Date) => padRight(date.getUTCHours().toString(), '0', 2), "ss": (date: Date) => padRight(date.getUTCSeconds().toString(), '0', 2), }; const keys = Object.keys(transforms); for(const key of keys) { template = template.replaceAll(key, () => transforms[key]!(date)); } return template; } export function clamp(x: number, min: number, max: number): number { return x > max ? max : x < min ? min : x; } export function lerp(x: number, a: number, b: number): number { return (1-x)*a+x*b; } // The value position is randomized // The metadata separator is randomized as a letter (to avoid collision with numbers) // The URI is (| == picked separator) |v_length|first part of the hash + seed + second part of the hash|v_pos|seed as hex. // Every number are converted to string as hexadecimal values export function cryptURI(key: string, value: number, seed?: number): string { const _seed = seed ?? Date.now(); const hash = Bun.hash(key + value.toString(), _seed).toString(16); const pos = Math.floor(Math.random() * hash.length); const separator = String.fromCharCode(Math.floor(Math.random() * 26 + 96)); return Bun.zstdCompressSync(separator + value.toString(16).length + separator + hash.substring(0, pos) + value + hash.substring(pos) + separator + pos + separator + _seed.toString(16), { level: 1 }).toString('base64'); } export function decryptURI(uri: string, key: string): number | undefined { const decoder = new TextDecoder(); const _uri = decoder.decode(Bun.zstdDecompressSync(Buffer.from(uri, 'base64'))); const separator = _uri.charAt(0); const length = parseInt(_uri.substring(1, _uri.indexOf(separator, 1)), 10); const seed = parseInt(_uri.substring(_uri.lastIndexOf(separator) + 1), 16); const pos = parseInt(_uri.substring(_uri.lastIndexOf(separator, _uri.length - seed.toString(16).length - 2) + 1, _uri.lastIndexOf(separator)), 10); const _hash = _uri.substring(2 + length.toString(10).length, _uri.length - (2 + seed.toString(16).length + pos.toString(10).length)); const value = _hash.substring(pos, pos + length); const hash = _hash.substring(0, pos) + _hash.substring(pos + length); if(Bun.hash(key + value, seed).toString(16) === hash) return parseInt(value, 10); else return undefined; }