You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
vore-rpg/src/managers/users.ts

87 lines
3.5 KiB

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<boolean> {
return this._table.getUserExistsAndIsAdmin(userSnowflakeToUuid(snowflake))
}
async updateSnowflakeActivity(snowflake: Snowflake): Promise<void> {
return this._table.createUserOrUpdateActiveTime(userSnowflakeToUuid(snowflake))
}
async setSnowflakeAdmin(snowflake: Snowflake): Promise<void> {
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,
})
}