From 7d2fd7b4f8d5ab7e27bfbea6a4498edbbaf7df9c Mon Sep 17 00:00:00 2001 From: Rory& Date: Fri, 3 Oct 2025 20:29:32 +0200 Subject: [PATCH] Add dotnet-ish Random class --- src/api/util/utility/String.ts | 4 +- src/util/entities/User.ts | 4 +- src/util/util/Random.ts | 74 ++++++++++++++++++++++++++++++++++ src/util/util/index.ts | 1 + 4 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 src/util/util/Random.ts diff --git a/src/api/util/utility/String.ts b/src/api/util/utility/String.ts index eef69e39..348f33cf 100644 --- a/src/api/util/utility/String.ts +++ b/src/api/util/utility/String.ts @@ -18,7 +18,7 @@ import { Request } from "express"; import { ntob } from "./Base64"; -import { FieldErrors } from "@spacebar/util"; +import { FieldErrors, Random } from "@spacebar/util"; export function checkLength( str: string, @@ -40,5 +40,5 @@ export function checkLength( } export function generateCode() { - return ntob(Date.now() + Math.randomIntBetween(0, 10000)); + return ntob(Date.now() + Random.nextInt(0, 10000)); } diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts index 2375008e..d1b3c709 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -19,7 +19,7 @@ import { Request } from "express"; import { Column, Entity, FindOneOptions, JoinColumn, OneToMany, OneToOne } from "typeorm"; import { Channel, ChannelType, Config, Email, FieldErrors, Snowflake, trimSpecial } from ".."; -import { BitField } from "../util/BitField"; +import { BitField, Random } from "../util"; import { BaseClass } from "./BaseClass"; import { ConnectedAccount } from "./ConnectedAccount"; import { Member } from "./Member"; @@ -284,7 +284,7 @@ export class User extends BaseClass { // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the database? for (let tries = 0; tries < 5; tries++) { - const discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); + const discriminator = Random.nextInt(1, 9999).toString().padStart(4, "0"); const exists = await User.findOne({ where: { discriminator, username: username }, select: ["id"], diff --git a/src/util/util/Random.ts b/src/util/util/Random.ts new file mode 100644 index 00000000..8e4598dc --- /dev/null +++ b/src/util/util/Random.ts @@ -0,0 +1,74 @@ +// Inspired by dotnet: https://learn.microsoft.com/en-us/dotnet/api/system.random?view=net-9.0#methods +export class Random { + public static nextInt(min?: number, max?: number): number { + if (min === undefined && max === undefined) { + // Next() + return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); + } else if (max === undefined) { + // Next(Int32) + if (min! <= 0) throw new RangeError("min must be greater than 0"); + return Math.floor(Math.random() * min!); + } else { + // Next(Int32, Int32) + if (min! >= max!) throw new RangeError("min must be less than max"); + return Math.floor(Math.random() * (max! - min!)) + min!; + } + } + + public static nextDouble(min?: number, max?: number): number { + if (min === undefined && max === undefined) { + // NextDouble() + return Math.random(); + } else if (max === undefined) { + // NextDouble(Double) + if (min! <= 0) throw new RangeError("min must be greater than 0"); + return Math.random() * min!; + } else { + // NextDouble(Double, Double) + if (min! >= max!) throw new RangeError("min must be less than max"); + return Math.random() * (max! - min!) + min!; + } + } + + public static nextBytes(count: number): Uint8Array { + if (count <= 0) throw new RangeError("count must be greater than 0"); + const arr = new Uint8Array(count); + for (let i = 0; i < count; i++) { + arr[i] = Math.floor(Math.random() * 256); + } + return arr; + } + + public static nextBytesArray(count: number) { + if (count <= 0) throw new RangeError("count must be greater than 0"); + const arr = []; + for (let i = 0; i < count; i++) { + arr.push(Math.floor(Math.random() * 256)); + } + return arr; + } + + public static getItems(items: T[], count: number): T[] { + if (count <= 0) throw new RangeError("count must be greater than 0"); + if (count >= items.length) return this.shuffle(items); + const usedIndices = new Set(); + const result: T[] = []; + while (result.length < count && usedIndices.size < items.length) { + const index = Math.floor(Math.random() * items.length); + if (!usedIndices.has(index)) { + usedIndices.add(index); + result.push(items[index]); + } + } + return result; + } + + public static shuffle(items: T[]): T[] { + const array = [...items]; + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + return array; + } +} diff --git a/src/util/util/index.ts b/src/util/util/index.ts index e7d8caa9..f50db274 100644 --- a/src/util/util/index.ts +++ b/src/util/util/index.ts @@ -48,3 +48,4 @@ export * from "./Application"; export * from "./NameValidation"; export * from "./HelperTypes"; export * from "./extensions"; +export * from "./Random"; \ No newline at end of file