import {Snowflake} from "discord-api-types/globals"; import {parse as uuidParse, v1 as uuidV1, validate as uuidValidate, version as uuidVersion} from "uuid"; import {SnowflakeUtil} from "discord.js"; import {fast1a52 as fnvFast1a52} from "fnv-plus"; import {UserTable} from "../database/users"; export class UsersManager { private readonly _table: UserTable constructor({users}: { users: UserTable }) { this._table = users } async isSnowflakeAdmin(snowflake: Snowflake): Promise { return this._table.getUserExistsAndIsAdmin(userSnowflakeToUuid(snowflake)) } async updateSnowflakeActivity(snowflake: Snowflake): Promise { return this._table.createUserOrUpdateActiveTime(userSnowflakeToUuid(snowflake)) } async setSnowflakeAdmin(snowflake: Snowflake): Promise { return this._table.createOrSetUserAsAdmin(userSnowflakeToUuid(snowflake)) } } export function userUuidToSnowflake(uuid: string): Snowflake | null { if (!uuidValidate(uuid) || uuidVersion(uuid) !== 1) { return null } const bytes = uuidParse(uuid) const time_low = bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3] const time_mid = bytes[4] << 8 | bytes[5] const time_high = (bytes[6] & 0x0F) << 8 | bytes[7] const timestamp = ((BigInt(time_high) << 48n) | (BigInt(time_mid) << 32n) | BigInt(time_low)) / 10000n const increment = BigInt((bytes[8] & 0x0F) << 8 | bytes[9]) const workerId = BigInt((bytes[14] & 0x03) << 3 | (bytes[15] & 0x07) >> 5) const processId = BigInt(bytes[15] & 0x1F) return SnowflakeUtil.generate({ timestamp, increment, workerId, processId, }).toString(10) } export function userSnowflakeToUuid(snowflake: Snowflake): string { const { timestamp, increment, workerId, processId, } = SnowflakeUtil.deconstruct(snowflake) // Grab 52 hash bits for filling the empty spaces in the UUID. const hashCode = fnvFast1a52(snowflake) // We get 10000 possibilities for number of 100-nanosecond periods. const nanoseconds = (hashCode % 10000) // The node ID is 48 bits, and we have 10 from the process and worker IDs. // The first bit must be 1 to indicate a made up node name, but the remaining 37 are open. // Grab 37 bits after removing the 10000 variants of the 100-nanosecond periods. const nodeIdPadding = Math.floor(hashCode / 10000) & 0x1FFFFFFFFF const nodeId = new Uint8Array(6) // Set the highest bit, indicating a multicast address... // ... indicating an invalid address, indicating a made up node name. // Then insert the hash padding and worker/process IDs. // It doesn't matter what order they're in, as long as it's consistent. // In this case, we choose the padding to be most significant, then worker, then process. nodeId[0] = 0x80 | ((nodeIdPadding >> 30) & 0x7F) nodeId[1] = (nodeIdPadding >> 22) & 0xFF nodeId[2] = (nodeIdPadding >> 14) & 0xFF nodeId[3] = (nodeIdPadding >> 6) & 0xFF nodeId[4] = ((nodeIdPadding & 0x3F) << 2) | Number((workerId >> 3n) & 0x03n) nodeId[5] = Number(((workerId & 0x07n) << 5n) | (processId & 0x1Fn)) return uuidV1({ rng: () => { throw Error("rng is not supposed to be invoked") }, // The snowflake increment is 12 bits. // UUIDs use a 12 bit clock sequence number. Perfect fit! clockseq: Number(increment & 0xFFFn), nsecs: nanoseconds, msecs: Number(timestamp & 0x3FFFFFFFFFFn), node: nodeId, }) }