const ID_SIZE = 24; const URLSafeCharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~'; 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(URLSafeCharacters[URLSafeCharacters.length * Math.random() | 0]); return id.join(""); } export function encodeBase(value: string): string { if(value === '') return ''; const buffer = Buffer.from(value, 'utf8'), base = BigInt(URLSafeCharacters.length); let nb = BigInt('0x' + buffer.toHex()), result = []; while(nb > 0) { const remaining = nb % base; result.push(URLSafeCharacters[Number(remaining)]); nb = (nb - remaining) / base; } const text = result.reverse().join(''); return text; } export function decodeBase(value: string): string { if(value === '') return ''; const result = '', base = BigInt(URLSafeCharacters.length); let nb = BigInt(0); value.split('').forEach(e => nb = nb * base + BigInt(URLSafeCharacters.indexOf(e))); const text = Buffer.from(nb.toString(16), 'hex').toString('utf8'); return text; } 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 deepEquals(a: any, b: any): boolean { if(a === b) return true; if (a && b && typeof a == 'object' && typeof b == 'object') { if (a.constructor !== b.constructor) return false; let length, i, keys; if (Array.isArray(a)) { length = a.length; if (length != b.length) return false; for (i = length; i-- !== 0;) if (!deepEquals(a[i], b[i])) return false; return true; } if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); keys = Object.keys(a) as Array; length = keys.length; if (length !== Object.keys(b).length) return false; for (i = length; i-- !== 0;) if (!Object.prototype.hasOwnProperty.call(b, keys[i]!)) return false; for (i = length; i-- !== 0;) if(!deepEquals(a[keys[i]!], b[keys[i]!])) return false; return true; } return a !== a && b !== b; } export function format(date: Date, template: string): string { const months = { 0: 'janvier', 1: 'fevrier', 2: 'mars', 3: 'avril', 4: 'mai', 5: 'juin', 6: 'juillet', 7: 'aout', 8: 'septembre', 9: 'octobre', 10: 'novembre', 11: 'decembre', }; const transforms: Record string> = { "yyyy": (date: Date) => date.getUTCFullYear().toString(), "MMMM": (date: Date) => months[date.getUTCMonth() as keyof typeof months], "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 = key in transforms ? template.replaceAll(key, () => transforms[key]!(date)) : template; } 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 from the URLSafeCharacters set // The URI is (| == picked separator) |v_length|first part of the hash + value + second part of the hash|v_pos as hex. export function cryptURI(key: string, value: number): string { const hash = Bun.hash.crc32(key + value.toString()).toString(16); const pos = Math.floor(Math.random() * hash.length); const separator = URLSafeCharacters[URLSafeCharacters.length * Math.random() | 0]!; return encodeBase(separator + value.toString().length + separator + hash.substring(0, pos) + value + hash.substring(pos) + separator + pos); } export function decryptURI(uri: string, key: string): number | undefined { const _uri = decodeBase(uri); const separator = _uri.charAt(0); const length = parseInt(_uri.substring(1, _uri.indexOf(separator, 1)), 10); const pos = parseInt(_uri.substring(_uri.lastIndexOf(separator) + 1), 16); const _hash = _uri.substring(_uri.lastIndexOf(separator, _uri.length - pos.toString(16).length - 2) + 1, _uri.lastIndexOf(separator)); const value = _hash.substring(pos, pos + length); const hash = _hash.substring(0, pos) + _hash.substring(pos + length); if(Bun.hash.crc32(key + value).toString(16) === hash) return parseInt(value, 10); else return undefined; }