summary refs log tree commit diff
path: root/src/util
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2026-06-12 16:27:38 +0200
committerRory& <root@rory.gay>2026-06-12 16:27:38 +0200
commit96bb1ed05c7da098a693ef781e0874b5f9d3ba5d (patch)
treed6101462a387ff6523fce39fa0bdb58789a797ef /src/util
parentMove migrations to database (diff)
downloadserver-ts-96bb1ed05c7da098a693ef781e0874b5f9d3ba5d.tar.xz
Move more extensions to extensions
Diffstat (limited to 'src/util')
-rw-r--r--src/util/util/DateBuilder.test.ts144
-rw-r--r--src/util/util/DateBuilder.ts89
-rw-r--r--src/util/util/ElapsedTime.test.ts80
-rw-r--r--src/util/util/ElapsedTime.ts84
-rw-r--r--src/util/util/Random.ts74
-rw-r--r--src/util/util/Stopwatch.test.ts54
-rw-r--r--src/util/util/Stopwatch.ts70
-rw-r--r--src/util/util/String.ts46
-rw-r--r--src/util/util/Timespan.test.ts122
-rw-r--r--src/util/util/Timespan.ts130
-rw-r--r--src/util/util/Token.ts2
-rw-r--r--src/util/util/Url.ts41
-rw-r--r--src/util/util/index.ts7
-rw-r--r--src/util/util/ipc/writer/UnixSocketWriter.ts9
-rw-r--r--src/util/util/json/JsonSerializer.test.ts2
-rw-r--r--src/util/util/networking/abuseipdb/AbuseIpDbClient.ts21
-rw-r--r--src/util/util/networking/ipdata/IpDataClient.ts21
-rw-r--r--src/util/util/networking/stopforumspam/StopForumSpamClient.ts2
-rw-r--r--src/util/util/workers/bcrypt/BcryptWorkerPool.ts2
19 files changed, 49 insertions, 951 deletions
diff --git a/src/util/util/DateBuilder.test.ts b/src/util/util/DateBuilder.test.ts
deleted file mode 100644

index 74de4dcc..00000000 --- a/src/util/util/DateBuilder.test.ts +++ /dev/null
@@ -1,144 +0,0 @@ -import { describe, test } from "node:test"; -import assert from "node:assert/strict"; -import { DateBuilder } from "./DateBuilder"; - -describe("DateBuilder", () => { - test("should be able to be initialised", () => { - const db = new DateBuilder(); - assert.equal(db instanceof DateBuilder, true); - }); - - test("should be able to build current date", () => { - const now = new Date(); - const db = new DateBuilder(); - const built = db.build(); - assert.equal(built.getFullYear(), now.getFullYear()); - assert.equal(built.getMonth(), now.getMonth()); - assert.equal(built.getDate(), now.getDate()); - assert.equal(built.getHours(), now.getHours()); - assert.equal(built.getMinutes(), now.getMinutes()); - assert.equal(built.getSeconds(), now.getSeconds()); - }); - - test("should be able to build timestamp", () => { - const now = new Date(); - const db = new DateBuilder(); - const built = db.buildTimestamp(); - assert.equal(built, now.getTime()); - }); - - test("should be able to add days", () => { - const db = new DateBuilder(new Date(2024, 0, 1)); // Jan 1, 2024 - db.addDays(30); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 0); // January - assert.equal(built.getDate(), 31); // January has 31 days - }); - - test("should be able to add months", () => { - const db = new DateBuilder(new Date(2024, 0, 1)); // Jan 31, 2024 - db.addMonths(1); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 1); // February - }); - - test("should be able to add years", () => { - const db = new DateBuilder(new Date(2020, 1, 1)); - db.addYears(1); - const built = db.build(); - assert.equal(built.getFullYear(), 2021); - assert.equal(built.getMonth(), 1); // February - assert.equal(built.getDate(), 1); - }); - - test("should be able to set date", () => { - const db = new DateBuilder(); - db.withDate(2022, 12, 25); // Dec 25, 2022 - const built = db.build(); - assert.equal(built.getFullYear(), 2022); - assert.equal(built.getMonth(), 11); // December - assert.equal(built.getDate(), 25); - }); - - test("should be able to set time", () => { - const db = new DateBuilder(); - db.withTime(15, 30, 45, 123); // 15:30:45.123 - const built = db.build(); - assert.equal(built.getHours(), 15); - assert.equal(built.getMinutes(), 30); - assert.equal(built.getSeconds(), 45); - assert.equal(built.getMilliseconds(), 123); - }); - - test("should be able to set start of day", () => { - const db = new DateBuilder(new Date(2024, 5, 15, 10, 20, 30, 456)); // June 15, 2024, 10:20:30.456 - db.atStartOfDay(); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 5); // June - assert.equal(built.getDate(), 15); - assert.equal(built.getHours(), 0); - assert.equal(built.getMinutes(), 0); - assert.equal(built.getSeconds(), 0); - assert.equal(built.getMilliseconds(), 0); - }); - - test("should be able to set end of day", () => { - const db = new DateBuilder(new Date(2024, 5, 15, 10, 20, 30, 456)); // June 15, 2024, 10:20:30.456 - db.atEndOfDay(); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 5); // June - assert.equal(built.getDate(), 15); - assert.equal(built.getHours(), 23); - assert.equal(built.getMinutes(), 59); - assert.equal(built.getSeconds(), 59); - assert.equal(built.getMilliseconds(), 999); - }); - - test("should be able to chain methods", () => { - const db = new DateBuilder(new Date(2024, 0, 1)); // Jan 1, 2024 - db.addDays(1).addMonths(1).addYears(1).withTime(12, 0, 0).atEndOfDay(); - const built = db.build(); - assert.equal(built.getFullYear(), 2025); - assert.equal(built.getMonth(), 1); // March - assert.equal(built.getDate(), 2); - assert.equal(built.getHours(), 23); - assert.equal(built.getMinutes(), 59); - assert.equal(built.getSeconds(), 59); - assert.equal(built.getMilliseconds(), 999); - }); - - test("should not mutate original date", () => { - const original = new Date(2024, 0, 1); // Jan 1, 2024 - const db = new DateBuilder(original); - db.addDays(10); - const built = db.build(); - assert.equal(original.getFullYear(), 2024); - assert.equal(original.getMonth(), 0); // January - assert.equal(original.getDate(), 1); // Original date should remain unchanged - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 0); // January - assert.equal(built.getDate(), 11); // New date should be Jan 11, 2024 - }); - - test("should handle leap years correctly", () => { - const db = new DateBuilder(new Date(2020, 1, 29)); // Feb 29, 2020 (leap year) - db.addYears(1); - const built = db.build(); - assert.equal(built.getFullYear(), 2021); - assert.equal(built.getMonth(), 2); // March - assert.equal(built.getDate(), 1); // March 1, 2021 (not a leap year) - }); - - test("should handle month overflow correctly", () => { - const db = new DateBuilder(new Date(2024, 0, 31)); // Jan 31, 2024 - db.addDays(1); - const built = db.build(); - assert.equal(built.getFullYear(), 2024); - assert.equal(built.getMonth(), 1); // February - assert.equal(built.getDate(), 1); // Feb 29, 2024 (leap year) - }); -}); diff --git a/src/util/util/DateBuilder.ts b/src/util/util/DateBuilder.ts deleted file mode 100644
index 58588c8b..00000000 --- a/src/util/util/DateBuilder.ts +++ /dev/null
@@ -1,89 +0,0 @@ -/* - Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Spacebar and Spacebar Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ - -export class DateBuilder { - private date: Date; - // constructors - constructor(date: Date = new Date()) { - this.date = new Date(date.getTime()); // Create a copy to avoid mutating the original date - } - - // methods - addYears(years: number) { - this.date.setFullYear(this.date.getFullYear() + years); - return this; - } - - addMonths(months: number) { - this.date.setMonth(this.date.getMonth() + months); - return this; - } - - addDays(days: number) { - this.date.setDate(this.date.getDate() + days); - return this; - } - - addHours(hours: number) { - this.date.setHours(this.date.getHours() + hours); - return this; - } - - addMinutes(minutes: number) { - this.date.setMinutes(this.date.getMinutes() + minutes); - return this; - } - - addSeconds(seconds: number) { - this.date.setSeconds(this.date.getSeconds() + seconds); - return this; - } - - addMillis(millis: number) { - this.date.setTime(this.date.getTime() + millis); - return this; - } - - withDate(year: number, month: number, day: number | undefined) { - this.date.setFullYear(year, month - 1, day); // month is 0-based - return this; - } - - withTime(hour: number, minute = 0, second = 0, millisecond = 0) { - this.date.setHours(hour, minute, second, millisecond); - return this; - } - - atStartOfDay() { - this.date.setHours(0, 0, 0, 0); - return this; - } - - atEndOfDay() { - this.date.setHours(23, 59, 59, 999); - return this; - } - - build() { - return new Date(this.date.getTime()); // Return a copy to avoid external mutation - } - - buildTimestamp() { - return this.date.getTime(); - } -} diff --git a/src/util/util/ElapsedTime.test.ts b/src/util/util/ElapsedTime.test.ts deleted file mode 100644
index b8cbbdce..00000000 --- a/src/util/util/ElapsedTime.test.ts +++ /dev/null
@@ -1,80 +0,0 @@ -import { describe, test } from "node:test"; -import assert from "node:assert/strict"; -import { ElapsedTime } from "./ElapsedTime"; - -describe("ElapsedTime", () => { - test("should be able to be initialised", () => { - const db = new ElapsedTime(0n); - assert.equal(db != null, true); - }); - - test("should return correct total nanoseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.totalNanoseconds, 1234567890n); - }); - - test("should return correct total microseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.totalMicroseconds, 1234567); - }); - - test("should return correct total milliseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.totalMilliseconds, 1234); - }); - - test("should return correct total seconds", () => { - const db = new ElapsedTime(5000000000n); - assert.equal(db.totalSeconds, 5); - }); - - test("should return correct total minutes", () => { - const db = new ElapsedTime(300000000000n); // 5 minutes - assert.equal(db.totalMinutes, 5); - }); - - test("should return correct total hours", () => { - const db = new ElapsedTime(7200000000000n); // 2 hours - assert.equal(db.totalHours, 2); - }); - - test("should return correct total days", () => { - const db = new ElapsedTime(172800000000000n); // 2 days - assert.equal(db.totalDays, 2); - }); - - test("should return correct nanoseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.nanoseconds, 890); - }); - - test("should return correct microseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.microseconds, 567); - }); - - test("should return correct milliseconds", () => { - const db = new ElapsedTime(1234567890n); - assert.equal(db.milliseconds, 234); - }); - - test("should return correct seconds", () => { - const db = new ElapsedTime(5000000000n); - assert.equal(db.seconds, 5); - }); - - test("should return correct minutes", () => { - const db = new ElapsedTime(300000000000n); // 5 minutes - assert.equal(db.minutes, 5); - }); - - test("should return correct hours", () => { - const db = new ElapsedTime(7200000000000n); // 2 hours - assert.equal(db.hours, 2); - }); - - test("should return correct days", () => { - const db = new ElapsedTime(172800000000000n); // 2 days - assert.equal(db.days, 2); - }); -}); diff --git a/src/util/util/ElapsedTime.ts b/src/util/util/ElapsedTime.ts deleted file mode 100644
index cce91280..00000000 --- a/src/util/util/ElapsedTime.ts +++ /dev/null
@@ -1,84 +0,0 @@ -/* - Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Spacebar and Spacebar Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ - -// Inspired by the dotnet Stopwatch class -// Provides a simple interface to get elapsed time in high resolution - -export class ElapsedTime { - private readonly timeNanos: bigint; - - constructor(timeNanos: bigint) { - this.timeNanos = timeNanos; - } - - get totalNanoseconds(): bigint { - return this.timeNanos; - } - get totalMicroseconds(): number { - return Number(this.timeNanos / 1_000n); - } - get totalMilliseconds(): number { - return Number(this.timeNanos / 1_000_000n); - } - get totalSeconds(): number { - return Number(this.timeNanos / 1_000_000_000n); - } - get totalMinutes(): number { - return this.totalSeconds / 60; - } - get totalHours(): number { - return this.totalMinutes / 60; - } - get totalDays(): number { - return this.totalHours / 24; - } - get nanoseconds(): number { - return Number(this.timeNanos % 1_000n); - } - get microseconds(): number { - return Number(this.timeNanos / 1_000n) % 1000; - } - get milliseconds(): number { - return Number(this.timeNanos / 1_000_000n) % 1000; - } - get seconds(): number { - return Number(this.timeNanos / 1_000_000_000n) % 60; - } - get minutes(): number { - return this.totalMinutes % 60; - } - get hours(): number { - return this.totalHours % 24; - } - get days(): number { - return this.totalDays; - } - - toString(): string { - // Format: "DD.HH:MM:SS.mmmuuuNNN", with days being optional - const daysPart = Math.floor(this.days) > 0 ? `${Math.floor(this.days)}.` : ""; - const hoursPart = Math.floor(this.hours).toString().padStart(2, "0"); - const minutesPart = Math.floor(this.minutes).toString().padStart(2, "0"); - const secondsPart = Math.floor(this.seconds).toString().padStart(2, "0"); - const millisecondsPart = Math.floor(this.milliseconds).toString().padStart(3, "0"); - const microsecondsPart = Math.floor(this.microseconds).toString().padStart(3, "0"); - const nanosecondsPart = Math.floor(this.nanoseconds).toString().padStart(3, "0"); - - return `${daysPart}${hoursPart}:${minutesPart}:${secondsPart}.${millisecondsPart}${microsecondsPart}${nanosecondsPart}`; - } -} diff --git a/src/util/util/Random.ts b/src/util/util/Random.ts deleted file mode 100644
index b81e17cd..00000000 --- a/src/util/util/Random.ts +++ /dev/null
@@ -1,74 +0,0 @@ -// 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<T>(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<number>(); - 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<T>(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/Stopwatch.test.ts b/src/util/util/Stopwatch.test.ts deleted file mode 100644
index 35618314..00000000 --- a/src/util/util/Stopwatch.test.ts +++ /dev/null
@@ -1,54 +0,0 @@ -import { describe, test } from "node:test"; -import assert from "node:assert/strict"; -import { Stopwatch, timePromise } from "./Stopwatch"; - -describe("Stopwatch", () => { - test("should be able to be initialised", () => { - const sw = new Stopwatch(); - assert.equal(sw != null, true); - }); - - test("should measure elapsed time", async () => { - const sw = Stopwatch.startNew(); - await new Promise((resolve) => void setTimeout(resolve, 101)); - sw.stop(); - const elapsed = sw.elapsed(); - assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); - }); - - test("should reset correctly", async () => { - const sw = Stopwatch.startNew(); - await new Promise((resolve) => void setTimeout(resolve, 101)); - sw.stop(); - let elapsed = sw.elapsed(); - assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); - - sw.reset(); - await new Promise((resolve) => void setTimeout(resolve, 50)); - sw.stop(); - elapsed = sw.elapsed(); - assert(elapsed.totalMilliseconds >= 49 && elapsed.totalMilliseconds < 100, `Elapsed time after reset was ${elapsed.totalMilliseconds} ms`); - }); - - test("getElapsedAndReset should work correctly", async () => { - const sw = Stopwatch.startNew(); - await new Promise((resolve) => void setTimeout(resolve, 101)); - sw.stop(); - let elapsed = sw.getElapsedAndReset(); - assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); - - await new Promise((resolve) => void setTimeout(resolve, 50)); - sw.stop(); - elapsed = sw.elapsed(); - assert(elapsed.totalMilliseconds >= 50 && elapsed.totalMilliseconds < 100, `Elapsed time after getElapsedAndReset was ${elapsed.totalMilliseconds} ms`); - }); - - test("timePromise should measure promise execution time", async () => { - const { result, elapsed } = await timePromise(async () => { - await new Promise((resolve) => void setTimeout(resolve, 101)); - return 42; - }); - assert.equal(result, 42); - assert(elapsed.totalMilliseconds >= 100, `Elapsed time was ${elapsed.totalMilliseconds} ms`); - }); -}); diff --git a/src/util/util/Stopwatch.ts b/src/util/util/Stopwatch.ts deleted file mode 100644
index 1f83c847..00000000 --- a/src/util/util/Stopwatch.ts +++ /dev/null
@@ -1,70 +0,0 @@ -/* - Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Spacebar and Spacebar Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ - -// Inspired by the dotnet Stopwatch class -// Provides a simple interface to get elapsed time in high resolution -import { ElapsedTime } from "./ElapsedTime"; - -export class Stopwatch { - private startTime: bigint; - private endTime: bigint | null = null; - - static startNew(): Stopwatch { - const stopwatch = new Stopwatch(); - stopwatch.start(); - return stopwatch; - } - - start(): void { - this.startTime = process.hrtime.bigint(); - this.endTime = null; - } - - reset(): void { - this.startTime = process.hrtime.bigint(); - this.endTime = null; - } - - stop(): void { - this.endTime = process.hrtime.bigint(); - } - - elapsed(): ElapsedTime { - return new ElapsedTime((this.endTime ?? process.hrtime.bigint()) - this.startTime); - } - - getElapsedAndReset(): ElapsedTime { - const elapsed = this.elapsed(); - this.reset(); - return elapsed; - } -} - -export async function timePromise<T>(fn: () => Promise<T>): Promise<{ result: T; elapsed: ElapsedTime }> { - const stopwatch = Stopwatch.startNew(); - const result = await fn(); - const elapsed = stopwatch.elapsed(); - return { result, elapsed }; -} - -export function timeFunction<T>(fn: () => T): { result: T; elapsed: ElapsedTime } { - const stopwatch = Stopwatch.startNew(); - const result = fn(); - const elapsed = stopwatch.elapsed(); - return { result, elapsed }; -} diff --git a/src/util/util/String.ts b/src/util/util/String.ts deleted file mode 100644
index d0e75700..00000000 --- a/src/util/util/String.ts +++ /dev/null
@@ -1,46 +0,0 @@ -/* - Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Spacebar and Spacebar Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ - -import { SPECIAL_CHAR } from "./Regex"; - -export function trimSpecial(str?: string): string { - if (!str) return ""; - return str.replace(SPECIAL_CHAR, "").trim(); -} - -/** - * Capitalizes the first letter of a string. - * @param str The string to capitalize. - * @returns The capitalized string. - */ -export function capitalize(str: string): string { - if (!str) return ""; - return str.charAt(0).toUpperCase() + str.slice(1); -} - -export function centerString(str: string, len: number): string { - const pad = len - str.length; - const padLeft = Math.floor(pad / 2) + str.length; - return str.padStart(padLeft).padEnd(len); -} - -export function stringGlobToRegexp(str: string, flags?: string): RegExp { - // Convert simple wildcard patterns to regex - const escaped = str.replace(".", "\\.").replace("?", ".").replace("*", ".*"); - return new RegExp(escaped, flags); -} diff --git a/src/util/util/Timespan.test.ts b/src/util/util/Timespan.test.ts deleted file mode 100644
index 02195506..00000000 --- a/src/util/util/Timespan.test.ts +++ /dev/null
@@ -1,122 +0,0 @@ -import { describe, test } from "node:test"; -import assert from "node:assert/strict"; -import { TimeSpan } from "./Timespan"; - -describe("TimeSpan", () => { - test("should be able to be initialised", () => { - const db = new TimeSpan(); - assert.equal(db != null, true); - }); - - test("should be able to be initialised with start and end", () => { - const now = Date.now(); - const later = now + 5000; - const ts = new TimeSpan(now, later); - assert.equal(ts.start, now); - assert.equal(ts.end, later); - }); - - test("should be able to be initialised with start and end (fromDates static method)", () => { - const now = Date.now(); - const later = now + 5000; - const ts = TimeSpan.fromDates(now, later); - assert.equal(ts.start, now); - assert.equal(ts.end, later); - }); - - test("should throw error if start is greater than end", () => { - assert.throws(() => { - new TimeSpan(2000, 1000); - }, /Start time must be less than or equal to end time./); - }); - - test("should be able to return zero", () => { - const ts = new TimeSpan(); - assert.equal(ts.totalMillis, 0); - }); - - test("should be able to return timespan from milliseconds", () => { - const ts = TimeSpan.fromMillis(1000); - assert.equal(ts.totalMillis, 1000); - assert.equal(ts.totalSeconds, 1); - }); - - test("should be able to return timespan from seconds", () => { - const ts = TimeSpan.fromSeconds(60); - assert.equal(ts.totalMillis, 60000); - assert.equal(ts.totalSeconds, 60); - assert.equal(ts.totalMinutes, 1); - assert.equal(ts.minutes, 1); - assert.equal(ts.hours, 0); - assert.equal(ts.days, 0); - }); - - test("should be pure", () => { - const count = 10; - const timestamps = []; - for (let i = 0; i < count; i++) { - timestamps.push(TimeSpan.fromMillis(8972347984)); - for (const ts2 of timestamps) { - assert.equal(ts2.totalMillis, 8972347984); - assert.equal(ts2.totalSeconds, 8972347); - assert.equal(ts2.totalMinutes, 149539); - assert.equal(ts2.totalHours, 2492); - assert.equal(ts2.totalDays, 103); - assert.equal(ts2.totalWeeks, 14); - assert.equal(ts2.totalMonths, 3); - assert.equal(ts2.totalYears, 0); - - assert.equal(ts2.millis, 984); - assert.equal(ts2.seconds, 7); - assert.equal(ts2.minutes, 19); - assert.equal(ts2.hours, 20); - assert.equal(ts2.days, 12); - assert.equal(ts2.weekDays, 5); - assert.equal(ts2.weeks, 1); - assert.equal(ts2.months, 3); - assert.equal(ts2.years, 0); - } - } - }); - - test("should be able to stringify", () => { - const ts = TimeSpan.fromMillis(8972347984); - assert.equal(ts.toString(), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds"); - assert.equal(ts.toString(true), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds"); - assert.equal(ts.toString(true, false), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds"); - assert.equal(ts.toString(false), "3 months, 12 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds"); - assert.equal(ts.toString(false, false), "3 months, 12 days, 20 hours, 19 minutes, 7 seconds"); - }); - - test("should be able to shortStringify", () => { - const ts = TimeSpan.fromMillis(8972347984); - assert.equal(ts.toShortString(), "3mo1w5d20h19m7s984ms"); - assert.equal(ts.toShortString(true), "3mo1w5d20h19m7s984ms"); - assert.equal(ts.toShortString(true, false), "3mo1w5d20h19m7s"); - assert.equal(ts.toShortString(false), "3mo12d20h19m7s984ms"); - assert.equal(ts.toShortString(false, false), "3mo12d20h19m7s"); - }); - - test("should be able to shortStringify with spaces", () => { - const ts = TimeSpan.fromMillis(8972347984); - assert.equal(ts.toShortString(undefined, undefined, true), "3mo 1w 5d 20h 19m 7s 984ms"); - assert.equal(ts.toShortString(true, undefined, true), "3mo 1w 5d 20h 19m 7s 984ms"); - assert.equal(ts.toShortString(true, false, true), "3mo 1w 5d 20h 19m 7s"); - assert.equal(ts.toShortString(false, undefined, true), "3mo 12d 20h 19m 7s 984ms"); - assert.equal(ts.toShortString(false, false, true), "3mo 12d 20h 19m 7s"); - }); - - test("should be able to return start date", () => { - const now = Date.now(); - const later = now + 5000; - const ts = new TimeSpan(now, later); - assert.equal(ts.startDate.getTime(), now); - }); - - test("should be able to return end date", () => { - const now = Date.now(); - const later = now + 5000; - const ts = new TimeSpan(now, later); - assert.equal(ts.endDate.getTime(), later); - }); -}); diff --git a/src/util/util/Timespan.ts b/src/util/util/Timespan.ts deleted file mode 100644
index ad0c1d10..00000000 --- a/src/util/util/Timespan.ts +++ /dev/null
@@ -1,130 +0,0 @@ -/** - * Represents a timespan with a start and end time. - */ -export class TimeSpan { - public readonly start: number; - public readonly end: number; - // constructors - constructor(start = Date.now(), end = Date.now()) { - if (start > end) { - throw new Error("Start time must be less than or equal to end time."); - } - this.start = start; - this.end = end; - } - - static fromDates(startDate: number, endDate: number) { - return new TimeSpan(startDate, endDate); - } - - static fromMillis(millis: number) { - return new TimeSpan(0, millis); - } - - static fromSeconds(seconds: number) { - return TimeSpan.fromMillis(seconds * 1000); - } - - // methods - get totalMillis() { - return this.end - this.start; - } - - get millis() { - return Math.floor(this.totalMillis % 1000); - } - - get totalSeconds() { - return Math.floor(this.totalMillis / 1000); - } - - get seconds() { - return Math.floor((this.totalMillis / 1000) % 60); - } - - get totalMinutes() { - return Math.floor(this.totalMillis / 1000 / 60); - } - - get minutes() { - return Math.floor((this.totalMillis / 1000 / 60) % 60); - } - - get totalHours() { - return Math.floor(this.totalMillis / 1000 / 60 / 60); - } - - get hours() { - return Math.floor((this.totalMillis / 1000 / 60 / 60) % 24); - } - - get totalDays() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24); - } - - get days() { - return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24) % 30.44); // Average days in a month - } - - get weekDays() { - return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24) % 7); - } - - get totalWeeks() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 7); - } - - get weeks() { - return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24 / 7) % 4.345); // Average weeks in a month - } - - get totalMonths() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 30.44); // Average days in a month - } - - get months() { - return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24 / 30.44) % 12); // Average days in a month - } - - get totalYears() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25); // Average days in a year - } - - get years() { - return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25); // Average days in a year - } - - toString(includeWeeks = true, includeMillis = true) { - const parts = []; - if (this.totalYears >= 1) parts.push(`${this.totalYears} years`); - if (this.totalMonths >= 1) parts.push(`${this.months} months`); - if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks} weeks`); - if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days} days`); - if (this.totalHours >= 1) parts.push(`${this.hours} hours`); - if (this.totalMinutes >= 1) parts.push(`${this.minutes} minutes`); - if (this.totalSeconds >= 1) parts.push(`${this.seconds} seconds`); - if (includeMillis) parts.push(`${this.millis} milliseconds`); - return parts.join(", "); - } - - toShortString(includeWeeks = true, includeMillis = true, withSpaces = false) { - const parts = []; - if (this.totalYears >= 1) parts.push(`${this.totalYears}y`); - if (this.totalMonths >= 1) parts.push(`${this.months}mo`); - if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks}w`); - if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days}d`); - if (this.totalHours >= 1) parts.push(`${this.hours}h`); - if (this.totalMinutes >= 1) parts.push(`${this.minutes}m`); - if (this.totalSeconds >= 1) parts.push(`${this.seconds}s`); - if (includeMillis) parts.push(`${this.millis}ms`); - return parts.join(withSpaces ? " " : ""); - } - - get startDate() { - return new Date(this.start); - } - - get endDate() { - return new Date(this.end); - } -} diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts
index 10bd5e2c..e8cd9ecb 100644 --- a/src/util/util/Token.ts +++ b/src/util/util/Token.ts
@@ -25,7 +25,7 @@ import { existsSync } from "node:fs"; // TODO: dont use deprecated APIs lol import { FindOptionsRelationByString, FindOptionsSelectByString } from "typeorm"; import { randomUpperString } from "@spacebar/api"; -import { TimeSpan } from "./Timespan"; +import { TimeSpan } from "../../extensions/Timespan"; import { HTTPError } from "lambert-server/HTTPError"; import path from "node:path"; diff --git a/src/util/util/Url.ts b/src/util/util/Url.ts deleted file mode 100644
index 7fa681bf..00000000 --- a/src/util/util/Url.ts +++ /dev/null
@@ -1,41 +0,0 @@ -/* - Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2025 Spacebar and Spacebar Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ - -export function normalizeUrl(input: string): string { - try { - const u = new URL(input); - // Remove fragment - u.hash = ""; - // Normalize pathname - remove trailing slash except for root "/" - if (u.pathname !== "/" && u.pathname.endsWith("/")) { - u.pathname = u.pathname.slice(0, -1); - } - // Normalize query params: sort by key - if (u.search) { - const params = Array.from(u.searchParams.entries()); - params.sort(([a], [b]) => a.localeCompare(b)); - u.search = params.length ? "?" + params.map(([k, v]) => `${k}=${v}`).join("&") : ""; - } else { - // Ensure no empty search string - u.search = ""; - } - return u.toString(); - } catch (e) { - return input; - } -} diff --git a/src/util/util/index.ts b/src/util/util/index.ts
index d4f21d61..fb7c209c 100644 --- a/src/util/util/index.ts +++ b/src/util/util/index.ts
@@ -22,9 +22,7 @@ export * from "./BitField"; export * from "./cdn"; export * from "./Config"; export * from "./Constants"; -export * from "./DateBuilder"; export * from "./email"; -export * from "./ElapsedTime"; export * from "./ipc/Event"; export * from "./FieldError"; export * from "./Intents"; @@ -39,9 +37,6 @@ export * from "./ipc/RabbitMQ"; export * from "./Regex"; export * from "./Rights"; export * from "./Snowflake"; -export * from "./Stopwatch"; -export * from "./String"; -export * from "./Timespan"; export * from "./Token"; export * from "./TraverseDirectory"; export * from "./WebAuthn"; @@ -49,7 +44,5 @@ export * from "./ChannelFlags"; export * from "./Gifs"; export * from "./Application"; export * from "./NameValidation"; -export * from "./Random"; -export * from "./Url"; export * from "./Version"; export * from "./Presence"; diff --git a/src/util/util/ipc/writer/UnixSocketWriter.ts b/src/util/util/ipc/writer/UnixSocketWriter.ts
index b50f53dc..e8b810a8 100644 --- a/src/util/util/ipc/writer/UnixSocketWriter.ts +++ b/src/util/util/ipc/writer/UnixSocketWriter.ts
@@ -20,11 +20,12 @@ import net, { Socket } from "node:net"; import fs, { FSWatcher } from "node:fs"; import path from "node:path"; import { red } from "picocolors"; -import { BaseEventWriter } from "./BaseEventWriter"; -import { Event, Stopwatch } from "@spacebar/util"; -import { ProcessLifecycle } from "../../ProcessLifecycle"; -import { Monitoring } from "../../../monitoring/Monitoring"; import { Gauge } from "prom-client"; +import { Stopwatch } from "@spacebar/extensions"; +import { Event } from "@spacebar/util"; +import { Monitoring } from "../../../monitoring/Monitoring"; +import { ProcessLifecycle } from "../../ProcessLifecycle"; +import { BaseEventWriter } from "./BaseEventWriter"; export class UnixSocketWriter extends BaseEventWriter { private static openConnectionsMetric: Gauge; diff --git a/src/util/util/json/JsonSerializer.test.ts b/src/util/util/json/JsonSerializer.test.ts
index 5adf6c1e..ff5bb744 100644 --- a/src/util/util/json/JsonSerializer.test.ts +++ b/src/util/util/json/JsonSerializer.test.ts
@@ -2,7 +2,7 @@ import { JsonSerializer } from "./JsonSerializer"; import { describe, it } from "node:test"; import { strict as assert } from "node:assert"; import fs from "node:fs/promises"; -import { Stopwatch } from "../Stopwatch"; +import { Stopwatch } from "../../../extensions/Stopwatch"; import { JsonValue } from "@protobuf-ts/runtime"; describe("JsonSerializer", () => { diff --git a/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts b/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts
index b001fc75..327d052d 100644 --- a/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts +++ b/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts
@@ -1,4 +1,23 @@ -import { Config, DateBuilder } from "@spacebar/util"; +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2025 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +import { DateBuilder } from "@spacebar/extensions"; +import { Config } from "@spacebar/util"; import { AbuseIpDbBlacklistResponse, AbuseIpDbCheckResponse } from "./AbuseIpDbSampleResponses"; export class AbuseIpDbClient { diff --git a/src/util/util/networking/ipdata/IpDataClient.ts b/src/util/util/networking/ipdata/IpDataClient.ts
index b1551389..f717e330 100644 --- a/src/util/util/networking/ipdata/IpDataClient.ts +++ b/src/util/util/networking/ipdata/IpDataClient.ts
@@ -1,4 +1,23 @@ -import { Config, DateBuilder } from "@spacebar/util"; +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2026 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +import { DateBuilder } from "@spacebar/extensions"; +import { Config } from "@spacebar/util"; import { IpDataIpLookupResponse } from "./IpDataSampleResponses"; export class IpDataClient { diff --git a/src/util/util/networking/stopforumspam/StopForumSpamClient.ts b/src/util/util/networking/stopforumspam/StopForumSpamClient.ts
index 7767915a..c64d6dc3 100644 --- a/src/util/util/networking/stopforumspam/StopForumSpamClient.ts +++ b/src/util/util/networking/stopforumspam/StopForumSpamClient.ts
@@ -16,7 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { DateBuilder } from "@spacebar/util"; +import { DateBuilder } from "@spacebar/extensions"; // https://www.stopforumspam.com/usage export class StopForumSpamClient { diff --git a/src/util/util/workers/bcrypt/BcryptWorkerPool.ts b/src/util/util/workers/bcrypt/BcryptWorkerPool.ts
index 1d1705ef..6316adac 100644 --- a/src/util/util/workers/bcrypt/BcryptWorkerPool.ts +++ b/src/util/util/workers/bcrypt/BcryptWorkerPool.ts
@@ -1,6 +1,6 @@ import { isMainThread, parentPort, Worker } from "node:worker_threads"; import { ProcessLifecycle } from "../../ProcessLifecycle"; -import { Stopwatch } from "../../Stopwatch"; +import { Stopwatch } from "../../../../extensions/Stopwatch"; import bcrypt from "bcrypt"; export class BcryptWorkerPool {