From fe1c390973221703bfdeefeebd59c5fdee3f5f2d Mon Sep 17 00:00:00 2001 From: Rory& Date: Thu, 11 Jun 2026 18:07:50 +0200 Subject: Allow subpath imports, move extensions to separate module --- .../#channel_id/messages/#message_id/reactions.ts | 2 +- .../guilds/#guild_id/roles/#role_id/members.ts | 3 +- src/api/util/handlers/Message.ts | 4 +- src/api/util/utility/EmbedHandlers.ts | 3 +- src/cdn/routes/avatars.ts | 4 +- src/extensions/Array.ts | 82 ++++++++++++++++++++++ src/extensions/Math.ts | 22 ++++++ src/extensions/index.ts | 25 +++++++ src/gateway/opcodes/Identify.ts | 2 +- src/gateway/opcodes/LazyRequest.ts | 3 +- src/gateway/util/Utils.ts | 21 +++++- src/util/entities/Guild.ts | 2 +- src/util/util/extensions/Array.ts | 82 ---------------------- src/util/util/extensions/Math.ts | 22 ------ src/util/util/extensions/index.ts | 25 ------- src/util/util/index.ts | 2 - .../util/ipc/listener/RabbitMqSingleListener.ts | 9 +-- src/util/util/ipc/listener/UnixSocketListener.ts | 5 +- src/util/util/ipc/writer/RabbitMqSingleWriter.ts | 5 +- 19 files changed, 172 insertions(+), 151 deletions(-) create mode 100644 src/extensions/Array.ts create mode 100644 src/extensions/Math.ts create mode 100644 src/extensions/index.ts delete mode 100644 src/util/util/extensions/Array.ts delete mode 100644 src/util/util/extensions/Math.ts delete mode 100644 src/util/util/extensions/index.ts (limited to 'src') diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts index 305f81fcc..839ae6eeb 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts @@ -17,6 +17,7 @@ */ import { route } from "@spacebar/api"; +import { arrayRemove } from "@spacebar/extensions"; import { Channel, emitEvent, @@ -29,7 +30,6 @@ import { MessageReactionRemoveEmojiEvent, MessageReactionRemoveEvent, User, - arrayRemove, ReactionType, } from "@spacebar/util"; import { Request, Response, Router } from "express"; diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts index aea54b1d2..5a55c731e 100644 --- a/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts +++ b/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts @@ -17,7 +17,8 @@ */ import { Router, Request, Response } from "express"; -import { DiscordApiErrors, Member, arrayPartition } from "@spacebar/util"; +import { arrayPartition } from "@spacebar/extensions"; +import { DiscordApiErrors, Member } from "@spacebar/util"; import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts index acfe7f311..74c2d6d0f 100644 --- a/src/api/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts @@ -17,10 +17,9 @@ */ import { fillMessageUrlEmbeds, randomString } from "@spacebar/api"; +import { mathLogBase, arrayDistributeSequentially } from "@spacebar/extensions"; import { Application, - arrayDistributeSequentially, - arrayPartition, Attachment, Channel, CloudAttachment, @@ -35,7 +34,6 @@ import { Guild, handleFile, HERE_MENTION, - mathLogBase, Member, Message, MessageCreateEvent, diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts index f242d6863..fa3d74f04 100644 --- a/src/api/util/utility/EmbedHandlers.ts +++ b/src/api/util/utility/EmbedHandlers.ts @@ -16,7 +16,8 @@ along with this program. If not, see . */ -import { arrayDistinctBy, arrayGroupBy, Config, EmbedCache, emitEvent, Message, MessageUpdateEvent, normalizeUrl, OrmUtils, sleep } from "@spacebar/util"; +import { sleep, arrayDistinctBy, arrayGroupBy } from "@spacebar/extensions"; +import { Config, EmbedCache, emitEvent, Message, MessageUpdateEvent, normalizeUrl, OrmUtils } from "@spacebar/util"; import { Embed, EmbedImage, EmbedType } from "@spacebar/schemas"; import * as cheerio from "cheerio"; import crypto from "node:crypto"; diff --git a/src/cdn/routes/avatars.ts b/src/cdn/routes/avatars.ts index bf2ab4371..6685ef7f7 100644 --- a/src/cdn/routes/avatars.ts +++ b/src/cdn/routes/avatars.ts @@ -16,12 +16,12 @@ along with this program. If not, see . */ +import crypto from "node:crypto"; import { Router, Response, Request } from "express"; +import { fileTypeFromBuffer } from "file-type"; import { Config } from "@spacebar/util"; import { storage } from "@spacebar/cdn"; -import { fileTypeFromBuffer } from "file-type"; import { HTTPError } from "lambert-server"; -import crypto from "node:crypto"; import { multer } from "../util/multer"; import { cache } from "../util/cache"; diff --git a/src/extensions/Array.ts b/src/extensions/Array.ts new file mode 100644 index 000000000..c95328b5a --- /dev/null +++ b/src/extensions/Array.ts @@ -0,0 +1,82 @@ +/* + 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://stackoverflow.com/a/50636286 */ +export function arrayPartition(array: T[], filter: (elem: T) => boolean): [T[], T[]] { + const pass: T[] = [], + fail: T[] = []; + array.forEach((e) => (filter(e) ? pass : fail).push(e)); + return [pass, fail]; +} + +export function arrayRemove(array: T[], item: T): void { + const index = array.indexOf(item); + if (index > -1) { + array.splice(index, 1); + } +} + +export function arrayDistinctBy(array: T[], selector: (elem: T) => M): T[] { + const mapped = new Set(); + + return array.filter((item) => { + const mappedValue = selector(item); + if (mapped.has(mappedValue)) return false; + + mapped.add(mappedValue); + return true; + }); +} + +export function arrayGroupBy(array: T[], selector: (elem: T) => M): Map { + const map = new Map(); + + array.forEach((item) => { + const mappedValue = selector(item); + const existing = map.get(mappedValue); + if (existing) existing.push(item); + else map.set(mappedValue, [item]); + }); + + return map; +} + +// https://github.com/TheArcaneBrony/ArcaneLibs/blob/ca7ce2bb57a5dffd891b0b86ec5f358df02735c0/ArcaneLibs/Extensions/CollectionExtensions.cs#L103 +export function arrayDistributeSequentially(array: T[], count: number): T[][] { + if (count <= 0) throw new Error("Count must be greater than 0"); + if (count == 1) return [array]; + + const list = array; + const groups: T[][] = []; + for (let i = 0; i < count; i++) groups.push([]); + + // [1,2,3],[4,5,6],[7,8,9]... + for (let i = 0; i < list.length; i++) { + const idx: number = Math.floor((i / list.length) * count); + // console.log(`${i}/${list.length} -> ${idx}:${groups[idx].length}/{count}`); + groups[idx].push(list[i]); + } + + return groups; +} + +//region Numerics +export function arraySum(array: number[]) { + return array.reduce((prev, curr) => prev + curr, 0); +} +//endregion diff --git a/src/extensions/Math.ts b/src/extensions/Math.ts new file mode 100644 index 000000000..0a34aaabf --- /dev/null +++ b/src/extensions/Math.ts @@ -0,0 +1,22 @@ +/* + 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://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log +export function mathLogBase(num: number, base: number) { + return Math.log(num) / Math.log(base); +} diff --git a/src/extensions/index.ts b/src/extensions/index.ts new file mode 100644 index 000000000..53bd443c8 --- /dev/null +++ b/src/extensions/index.ts @@ -0,0 +1,25 @@ +/* + 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 . +*/ + +export * from "./Array"; +export * from "./Math"; + +// TODO: move to a separate file +export async function sleep(ms: number) { + return new Promise((resolve) => void setTimeout(resolve, ms)); +} diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index 91fdcd887..74907f451 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -17,9 +17,9 @@ */ import { Capabilities, CLOSECODES, OPCODES, Payload, Send, setupListener, WebSocket } from "@spacebar/gateway"; +import { arrayGroupBy } from "@spacebar/extensions"; import { Application, - arrayGroupBy, Channel, checkToken, Config, diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts index 74b941ac6..0080e589c 100644 --- a/src/gateway/opcodes/LazyRequest.ts +++ b/src/gateway/opcodes/LazyRequest.ts @@ -16,7 +16,8 @@ along with this program. If not, see . */ -import { getDatabase, getPermission, listenEvent, Member, Role, Session, User, Presence, Channel, Permissions, arrayPartition, getMostRelevantSession } from "@spacebar/util"; +import { arrayPartition } from "@spacebar/extensions"; +import { getDatabase, getPermission, listenEvent, Member, Role, Session, User, Presence, Channel, Permissions, getMostRelevantSession } from "@spacebar/util"; import { WebSocket, Payload, handlePresenceUpdate, OPCODES, Send } from "@spacebar/gateway"; import murmur from "murmurhash-js/murmurhash3_gc"; import { check } from "./instanceOf"; diff --git a/src/gateway/util/Utils.ts b/src/gateway/util/Utils.ts index ebf7ae023..208806224 100644 --- a/src/gateway/util/Utils.ts +++ b/src/gateway/util/Utils.ts @@ -1,4 +1,23 @@ -import { Event, Session, sleep, TimeSpan, VoiceState } 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 . +*/ + +import { sleep } from "@spacebar/extensions"; +import { Event, Session, TimeSpan, VoiceState } from "@spacebar/util"; import { WebSocket } from "./WebSocket"; import { OPCODES } from "./Constants"; import { Send } from "./Send"; diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts index aea911eb5..0cea637e1 100644 --- a/src/util/entities/Guild.ts +++ b/src/util/entities/Guild.ts @@ -17,6 +17,7 @@ */ import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { arrayRemove } from "@spacebar/extensions"; import { Config, GuildWelcomeScreen, Snowflake, handleFile } from ".."; import { Ban } from "./Ban"; import { BaseClass } from "./BaseClass"; @@ -30,7 +31,6 @@ import { Template } from "./Template"; import { User } from "./User"; import { VoiceState } from "./VoiceState"; import { Webhook } from "./Webhook"; -import { arrayRemove } from "@spacebar/util"; // TODO: application_command_count, application_command_counts: {1: 0, 2: 0, 3: 0} // TODO: guild_scheduled_events // TODO: stage_instances diff --git a/src/util/util/extensions/Array.ts b/src/util/util/extensions/Array.ts deleted file mode 100644 index c95328b5a..000000000 --- a/src/util/util/extensions/Array.ts +++ /dev/null @@ -1,82 +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://stackoverflow.com/a/50636286 */ -export function arrayPartition(array: T[], filter: (elem: T) => boolean): [T[], T[]] { - const pass: T[] = [], - fail: T[] = []; - array.forEach((e) => (filter(e) ? pass : fail).push(e)); - return [pass, fail]; -} - -export function arrayRemove(array: T[], item: T): void { - const index = array.indexOf(item); - if (index > -1) { - array.splice(index, 1); - } -} - -export function arrayDistinctBy(array: T[], selector: (elem: T) => M): T[] { - const mapped = new Set(); - - return array.filter((item) => { - const mappedValue = selector(item); - if (mapped.has(mappedValue)) return false; - - mapped.add(mappedValue); - return true; - }); -} - -export function arrayGroupBy(array: T[], selector: (elem: T) => M): Map { - const map = new Map(); - - array.forEach((item) => { - const mappedValue = selector(item); - const existing = map.get(mappedValue); - if (existing) existing.push(item); - else map.set(mappedValue, [item]); - }); - - return map; -} - -// https://github.com/TheArcaneBrony/ArcaneLibs/blob/ca7ce2bb57a5dffd891b0b86ec5f358df02735c0/ArcaneLibs/Extensions/CollectionExtensions.cs#L103 -export function arrayDistributeSequentially(array: T[], count: number): T[][] { - if (count <= 0) throw new Error("Count must be greater than 0"); - if (count == 1) return [array]; - - const list = array; - const groups: T[][] = []; - for (let i = 0; i < count; i++) groups.push([]); - - // [1,2,3],[4,5,6],[7,8,9]... - for (let i = 0; i < list.length; i++) { - const idx: number = Math.floor((i / list.length) * count); - // console.log(`${i}/${list.length} -> ${idx}:${groups[idx].length}/{count}`); - groups[idx].push(list[i]); - } - - return groups; -} - -//region Numerics -export function arraySum(array: number[]) { - return array.reduce((prev, curr) => prev + curr, 0); -} -//endregion diff --git a/src/util/util/extensions/Math.ts b/src/util/util/extensions/Math.ts deleted file mode 100644 index 0a34aaabf..000000000 --- a/src/util/util/extensions/Math.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - 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://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log -export function mathLogBase(num: number, base: number) { - return Math.log(num) / Math.log(base); -} diff --git a/src/util/util/extensions/index.ts b/src/util/util/extensions/index.ts deleted file mode 100644 index 53bd443c8..000000000 --- a/src/util/util/extensions/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - 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 . -*/ - -export * from "./Array"; -export * from "./Math"; - -// TODO: move to a separate file -export async function sleep(ms: number) { - return new Promise((resolve) => void setTimeout(resolve, ms)); -} diff --git a/src/util/util/index.ts b/src/util/util/index.ts index f6de6a955..2c028e1d4 100644 --- a/src/util/util/index.ts +++ b/src/util/util/index.ts @@ -17,7 +17,6 @@ */ export * from "./ApiError"; -export * from "./extensions/Array"; export * from "./BitField"; //export * from "./Categories"; export * from "./cdn"; @@ -52,7 +51,6 @@ export * from "./Gifs"; export * from "./Application"; export * from "./NameValidation"; export * from "../../schemas/HelperTypes"; -export * from "./extensions"; export * from "./Random"; export * from "./Url"; export * from "./Version"; diff --git a/src/util/util/ipc/listener/RabbitMqSingleListener.ts b/src/util/util/ipc/listener/RabbitMqSingleListener.ts index 7e5867acc..d221cbb8c 100644 --- a/src/util/util/ipc/listener/RabbitMqSingleListener.ts +++ b/src/util/util/ipc/listener/RabbitMqSingleListener.ts @@ -18,12 +18,13 @@ import EventEmitter from "node:events"; import { randomUUID } from "node:crypto"; -import { BaseEventListener } from "./BaseEventListener"; -import { arraySum, EVENT, Event, EventOpts, sleep } from "@spacebar/util"; import amqp, { Channel, ChannelModel } from "amqplib"; -import { ProcessLifecycle } from "../../ProcessLifecycle"; -import { Monitoring } from "../../../monitoring/Monitoring"; import { Gauge } from "prom-client"; +import { sleep, arraySum } from "@spacebar/extensions"; +import { EVENT, Event, EventOpts } from "@spacebar/util"; +import { Monitoring } from "../../../monitoring/Monitoring"; +import { ProcessLifecycle } from "../../ProcessLifecycle"; +import { BaseEventListener } from "./BaseEventListener"; export class RabbitMqSingleListener extends BaseEventListener { static openListenersMetric: Gauge; diff --git a/src/util/util/ipc/listener/UnixSocketListener.ts b/src/util/util/ipc/listener/UnixSocketListener.ts index 50ffe2b09..cb9ea96b7 100644 --- a/src/util/util/ipc/listener/UnixSocketListener.ts +++ b/src/util/util/ipc/listener/UnixSocketListener.ts @@ -19,10 +19,11 @@ import EventEmitter from "node:events"; import fs from "node:fs"; import net, { Server } from "node:net"; +import { Gauge } from "prom-client"; +import { arraySum } from "@spacebar/extensions"; +import { EVENT, Event, EventOpts } from "@spacebar/util"; import { BaseEventListener } from "./BaseEventListener"; -import { arraySum, EVENT, Event, EventOpts } from "@spacebar/util"; import { ProcessLifecycle } from "../../ProcessLifecycle"; -import { Gauge } from "prom-client"; import { Monitoring } from "../../../monitoring/Monitoring"; export class UnixSocketListener extends BaseEventListener { diff --git a/src/util/util/ipc/writer/RabbitMqSingleWriter.ts b/src/util/util/ipc/writer/RabbitMqSingleWriter.ts index ad00aa2b5..96e820367 100644 --- a/src/util/util/ipc/writer/RabbitMqSingleWriter.ts +++ b/src/util/util/ipc/writer/RabbitMqSingleWriter.ts @@ -16,10 +16,11 @@ along with this program. If not, see . */ -import { BaseEventWriter } from "./BaseEventWriter"; import amqp, { Channel, ChannelModel } from "amqplib"; -import { Event, sleep } from "@spacebar/util"; +import { sleep } from "@spacebar/extensions"; +import { Event } from "@spacebar/util"; import { ProcessLifecycle } from "../../ProcessLifecycle"; +import { BaseEventWriter } from "./BaseEventWriter"; export class RabbitMqSingleWriter extends BaseEventWriter { private readonly host: string; -- cgit 1.5.1