From 54540a8b14c463aca83430c2a8a5f8d7322a2d66 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 25 Nov 2025 23:49:03 -0600 Subject: Delete Object.map --- src/api/routes/users/@me/settings.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts index beef662b..3a972424 100644 --- a/src/api/routes/users/@me/settings.ts +++ b/src/api/routes/users/@me/settings.ts @@ -19,7 +19,7 @@ import { route } from "@spacebar/api"; import { User, UserSettings } from "@spacebar/util"; import { Request, Response, Router } from "express"; -import { UserSettingsUpdateSchema, UserSettingsSchema } from "@spacebar/schemas" +import { UserSettingsUpdateSchema, UserSettingsSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); @@ -36,7 +36,7 @@ router.get( }, }), async (req: Request, res: Response) => { - const settings = await UserSettings.getOrDefault(req.user_id) + const settings = await UserSettings.getOrDefault(req.user_id); return res.json(settings); }, ); @@ -67,13 +67,10 @@ router.patch( relations: ["settings"], }); - if (!user.settings) - user.settings = UserSettings.create(body as UserSettingsUpdateSchema); - else - user.settings.assign(body); + if (!user.settings) user.settings = UserSettings.create(body); + else user.settings.assign(body); - if (body.guild_folders) - user.settings.guild_folders = body.guild_folders; + if (body.guild_folders) user.settings.guild_folders = body.guild_folders; await user.settings.save(); await user.save(); -- cgit 1.5.1 From 5a3965ab229b65dc01cabb448b0f5cfa2a48d5ef Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Tue, 25 Nov 2025 23:57:56 -0600 Subject: rid of Math.clamp --- src/api/routes/users/#user_id/messages.ts | 4 ++-- src/util/util/extensions/Math.test.ts | 19 ------------------- src/util/util/extensions/Math.ts | 31 ------------------------------- src/util/util/extensions/index.ts | 1 - 4 files changed, 2 insertions(+), 53 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/users/#user_id/messages.ts b/src/api/routes/users/#user_id/messages.ts index 9ce0b369..717bca96 100644 --- a/src/api/routes/users/#user_id/messages.ts +++ b/src/api/routes/users/#user_id/messages.ts @@ -19,7 +19,7 @@ import { route } from "@spacebar/api"; import { Config, Message, User } from "@spacebar/util"; import { Request, Response, Router } from "express"; -import { DmMessagesResponseSchema } from "@spacebar/schemas" +import { DmMessagesResponseSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( @@ -42,7 +42,7 @@ router.get( await Message.find({ where: { channel_id: channel?.id }, order: { timestamp: "DESC" }, - take: Math.clamp(req.query.limit ? Number(req.query.limit) : 50, 1, Config.get().limits.message.maxPreloadCount), + take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1), Config.get().limits.message.maxPreloadCount), }) ).filter((x) => x !== null) as Message[]; diff --git a/src/util/util/extensions/Math.test.ts b/src/util/util/extensions/Math.test.ts index 5f112dc0..e69de29b 100644 --- a/src/util/util/extensions/Math.test.ts +++ b/src/util/util/extensions/Math.test.ts @@ -1,19 +0,0 @@ -import moduleAlias from "module-alias"; -moduleAlias(); -import './Math'; -import { describe, it } from 'node:test'; -import assert from 'node:assert/strict'; - -describe("Math extensions", () => { - - it("clamp", async () => { - assert.strictEqual(Math.clamp(5, 1, 10), 5); - assert.strictEqual(Math.clamp(0, 1, 10), 1); - assert.strictEqual(Math.clamp(15, 1, 10), 10); - assert.strictEqual(Math.clamp(-5, -10, -1), -5); - assert.strictEqual(Math.clamp(-15, -10, -1), -10); - assert.strictEqual(Math.clamp(-0.5, -1, 0), -0.5); - assert.strictEqual(Math.clamp(1.5, 1, 2), 1.5); - }); - -}); \ No newline at end of file diff --git a/src/util/util/extensions/Math.ts b/src/util/util/extensions/Math.ts index a5bd80c3..e69de29b 100644 --- a/src/util/util/extensions/Math.ts +++ b/src/util/util/extensions/Math.ts @@ -1,31 +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 . -*/ - -declare global { - interface Math { - clamp(value: number, min: number, max: number): number; - } -} - -export function mathClamp(value: number, min: number, max: number): number { - return Math.min(Math.max(value, min), max); -} - -// register extensions -if (!Math.clamp) - Math.clamp = mathClamp; \ No newline at end of file diff --git a/src/util/util/extensions/index.ts b/src/util/util/extensions/index.ts index 6d9ed8de..afd6c0b3 100644 --- a/src/util/util/extensions/index.ts +++ b/src/util/util/extensions/index.ts @@ -1,4 +1,3 @@ export * from "./Array"; -export * from "./Math"; export * from "./Url"; export * from "./String"; -- cgit 1.5.1 From 3080445768d7fa710fa8371934e8b09110269f0b Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 26 Nov 2025 00:11:22 -0600 Subject: rid of forEachAsync --- .../routes/channels/#channel_id/messages/index.ts | 36 ++++++++++++---------- src/api/routes/guilds/#guild_id/invites.ts | 12 +++++--- src/util/util/extensions/Array.ts | 10 ------ 3 files changed, 27 insertions(+), 31 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts index d0d445b9..99523dab 100644 --- a/src/api/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts @@ -244,24 +244,28 @@ router.get( return x; }); - await ret - .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user) - .forEachAsync(async (x: MessageCreateSchema) => { - x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } }); - }); + await Promise.all( + ret + .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user) + .map(async (x: MessageCreateSchema) => { + x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } }); + }), + ); // polyfill message references for old messages - await ret - .filter((msg) => msg.message_reference && !msg.referenced_message?.id) - .forEachAsync(async (msg) => { - const whereOptions: { id: string; guild_id?: string; channel_id?: string } = { - id: msg.message_reference!.message_id, - }; - if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id; - if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id; - - msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] }); - }); + await Promise.all( + ret + .filter((msg) => msg.message_reference && !msg.referenced_message?.id) + .map(async (msg) => { + const whereOptions: { id: string; guild_id?: string; channel_id?: string } = { + id: msg.message_reference!.message_id, + }; + if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id; + if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id; + + msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] }); + }), + ); return res.json(ret); }, diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts index ec3116c9..9372651a 100644 --- a/src/api/routes/guilds/#guild_id/invites.ts +++ b/src/api/routes/guilds/#guild_id/invites.ts @@ -40,11 +40,13 @@ router.get( relations: PublicInviteRelation, }); - await invites - .filter((i) => i.isExpired()) - .forEachAsync(async (i) => { - await Invite.delete({ code: i.code }); - }); + await Promise.all( + invites + .filter((i) => i.isExpired()) + .map(async (i) => { + await Invite.delete({ code: i.code }); + }), + ); return res.json(invites.filter((i) => !i.isExpired())); }, diff --git a/src/util/util/extensions/Array.ts b/src/util/util/extensions/Array.ts index a4b1a899..74cceb76 100644 --- a/src/util/util/extensions/Array.ts +++ b/src/util/util/extensions/Array.ts @@ -19,7 +19,6 @@ declare global { interface Array { partition(filter: (elem: T) => boolean): [T[], T[]]; - forEachAsync(callback: (elem: T, index: number, array: T[]) => Promise): Promise; remove(item: T): void; distinct(): T[]; } @@ -33,10 +32,6 @@ export function arrayPartition(array: T[], filter: (elem: T) => boolean): [T[ return [pass, fail]; } -export async function arrayForEachAsync(array: T[], callback: (elem: T, index: number, array: T[]) => Promise): Promise { - await Promise.all(array.map(callback)); -} - export function arrayRemove(this: T[], item: T): void { const index = this.indexOf(item); if (index > -1) { @@ -54,11 +49,6 @@ if (!Array.prototype.partition) return arrayPartition(this, filter); }; -if (!Array.prototype.forEachAsync) - Array.prototype.forEachAsync = function (this: T[], callback: (elem: T, index: number, array: T[]) => Promise) { - return arrayForEachAsync(this, callback); - }; - if (!Array.prototype.remove) Array.prototype.remove = function (this: T[], item: T) { return arrayRemove.call(this, item); -- cgit 1.5.1 From 12763d1c593daabe170cf10e48fb460f86e9bed7 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 26 Nov 2025 14:45:19 -0600 Subject: remove distinct --- src/api/routes/channels/#channel_id/recipients.ts | 32 ++++------------------- src/gateway/opcodes/LazyRequest.ts | 5 +--- src/util/entities/Channel.ts | 2 +- src/util/util/extensions/Array.test.ts | 6 ----- src/util/util/extensions/Array.ts | 10 ------- 5 files changed, 7 insertions(+), 48 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts index 65160274..6388cfe2 100644 --- a/src/api/routes/channels/#channel_id/recipients.ts +++ b/src/api/routes/channels/#channel_id/recipients.ts @@ -17,15 +17,7 @@ */ import { route } from "@spacebar/api"; -import { - Channel, - ChannelRecipientAddEvent, - DiscordApiErrors, - DmChannelDTO, - emitEvent, - Recipient, - User, -} from "@spacebar/util"; +import { Channel, ChannelRecipientAddEvent, DiscordApiErrors, DmChannelDTO, emitEvent, Recipient, User } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { ChannelType, PublicUserProjection } from "@spacebar/schemas"; @@ -47,24 +39,16 @@ router.put( }); if (channel.type !== ChannelType.GROUP_DM) { - const recipients = [ - ...(channel.recipients?.map((r) => r.user_id) || []), - user_id, - ].distinct(); + const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])]; - const new_channel = await Channel.createDMChannel( - recipients, - req.user_id, - ); + const new_channel = await Channel.createDMChannel(recipients, req.user_id); return res.status(201).json(new_channel); } else { if (channel.recipients?.map((r) => r.user_id).includes(user_id)) { throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? } - channel.recipients?.push( - Recipient.create({ channel_id: channel_id, user_id: user_id }), - ); + channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id })); await channel.save(); await emitEvent({ @@ -103,13 +87,7 @@ router.delete( where: { id: channel_id }, relations: ["recipients"], }); - if ( - !( - channel.type === ChannelType.GROUP_DM && - (channel.owner_id === req.user_id || user_id === req.user_id) - ) - ) - throw DiscordApiErrors.MISSING_PERMISSIONS; + if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) throw DiscordApiErrors.MISSING_PERMISSIONS; if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) { throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts index e7490ff3..bd22bdb0 100644 --- a/src/gateway/opcodes/LazyRequest.ts +++ b/src/gateway/opcodes/LazyRequest.ts @@ -254,10 +254,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { }); }); - const groups = ops - .map((x) => x.groups) - .flat() - .distinct(); + const groups = [...new Set(ops.map((x) => x.groups).flat())]; await Send(this, { op: OPCODES.Dispatch, diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts index 39ab04e4..ff3de280 100644 --- a/src/util/entities/Channel.ts +++ b/src/util/entities/Channel.ts @@ -255,7 +255,7 @@ export class Channel extends BaseClass { } static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { - recipients = recipients.distinct().filter((x) => x !== creator_user_id); + recipients = [...new Set(recipients.distinct().filter((x) => x !== creator_user_id))]; // TODO: check config for max number of recipients /** if you want to disallow note to self channels, uncomment the conditional below diff --git a/src/util/util/extensions/Array.test.ts b/src/util/util/extensions/Array.test.ts index 91619251..3550654a 100644 --- a/src/util/util/extensions/Array.test.ts +++ b/src/util/util/extensions/Array.test.ts @@ -19,10 +19,4 @@ describe("Array extensions", () => { arr.remove(6); assert.deepEqual(arr, [1, 2, 4, 5]); }); - - it("distinct", () => { - const arr = [1, 2, 2, 3, 3, 3]; - assert.deepEqual(arr.distinct(), [1, 2, 3]); - assert.deepEqual([].distinct(), []); - }); }); diff --git a/src/util/util/extensions/Array.ts b/src/util/util/extensions/Array.ts index 74cceb76..58ec0a1b 100644 --- a/src/util/util/extensions/Array.ts +++ b/src/util/util/extensions/Array.ts @@ -20,7 +20,6 @@ declare global { interface Array { partition(filter: (elem: T) => boolean): [T[], T[]]; remove(item: T): void; - distinct(): T[]; } } @@ -39,10 +38,6 @@ export function arrayRemove(this: T[], item: T): void { } } -export function arrayDistinct(this: T[]): T[] { - return Array.from(new Set(this)); -} - // register extensions if (!Array.prototype.partition) Array.prototype.partition = function (this: T[], filter: (elem: T) => boolean) { @@ -53,8 +48,3 @@ if (!Array.prototype.remove) Array.prototype.remove = function (this: T[], item: T) { return arrayRemove.call(this, item); }; - -if (!Array.prototype.distinct) - Array.prototype.distinct = function (this: T[]) { - return arrayDistinct.call(this); - }; -- cgit 1.5.1 From 8f2efff694daace68724f2d56c66e33a3a5a94d3 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 26 Nov 2025 14:48:03 -0600 Subject: change partition --- .../guilds/#guild_id/roles/#role_id/members.ts | 57 ++++++++-------------- src/gateway/opcodes/LazyRequest.ts | 4 +- src/util/util/extensions/Array.ts | 11 ----- 3 files changed, 23 insertions(+), 49 deletions(-) (limited to 'src/api/routes') 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 4cfe21b9..d22d1434 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,45 +17,30 @@ */ import { Router, Request, Response } from "express"; -import { DiscordApiErrors, Member } from "@spacebar/util"; +import { DiscordApiErrors, Member, arrayPartition } from "@spacebar/util"; import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); -router.patch( - "/", - route({ permission: "MANAGE_ROLES" }), - async (req: Request, res: Response) => { - // Payload is JSON containing a list of member_ids, the new list of members to have the role - const { guild_id, role_id } = req.params; - const { member_ids } = req.body; - - // don't mess with @everyone - if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE; - - const members = await Member.find({ - where: { guild_id }, - relations: ["roles"], - }); - - const [add, remove] = members.partition( - (member) => - member_ids.includes(member.id) && - !member.roles.map((role) => role.id).includes(role_id), - ); - - // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn - await Promise.all([ - ...add.map((member) => - Member.addRole(member.id, guild_id, role_id), - ), - ...remove.map((member) => - Member.removeRole(member.id, guild_id, role_id), - ), - ]); - - res.sendStatus(204); - }, -); +router.patch("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { + // Payload is JSON containing a list of member_ids, the new list of members to have the role + const { guild_id, role_id } = req.params; + const { member_ids } = req.body; + + // don't mess with @everyone + if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE; + + const members = await Member.find({ + where: { guild_id }, + relations: ["roles"], + }); + + const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id)); + + // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn + await Promise.all([...add.map((member) => Member.addRole(member.id, guild_id, role_id)), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id))]); + + res.sendStatus(204); +}); export default router; diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts index bd22bdb0..c3c74990 100644 --- a/src/gateway/opcodes/LazyRequest.ts +++ b/src/gateway/opcodes/LazyRequest.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getDatabase, getPermission, listenEvent, Member, Role, Session, User, Presence, Channel, Permissions } from "@spacebar/util"; +import { getDatabase, getPermission, listenEvent, Member, Role, Session, User, Presence, Channel, Permissions, arrayPartition } from "@spacebar/util"; import { WebSocket, Payload, handlePresenceUpdate, OPCODES, Send } from "@spacebar/gateway"; import murmur from "murmurhash-js/murmurhash3_gc"; import { check } from "./instanceOf"; @@ -98,7 +98,7 @@ async function getMembers(guild_id: string, range: [number, number]) { const offlineItems = []; for (const role of member_roles) { - const [role_members, other_members] = members.partition((m: Member) => !!m.roles.find((r) => r.id === role.id)); + const [role_members, other_members] = arrayPartition(members, (m: Member) => !!m.roles.find((r) => r.id === role.id)); const group = { count: role_members.length, id: role.id === guild_id ? "online" : role.id, diff --git a/src/util/util/extensions/Array.ts b/src/util/util/extensions/Array.ts index 58ec0a1b..7ed1d1f0 100644 --- a/src/util/util/extensions/Array.ts +++ b/src/util/util/extensions/Array.ts @@ -18,18 +18,11 @@ declare global { interface Array { - partition(filter: (elem: T) => boolean): [T[], T[]]; remove(item: T): void; } } /* 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(this: T[], item: T): void { const index = this.indexOf(item); @@ -39,10 +32,6 @@ export function arrayRemove(this: T[], item: T): void { } // register extensions -if (!Array.prototype.partition) - Array.prototype.partition = function (this: T[], filter: (elem: T) => boolean) { - return arrayPartition(this, filter); - }; if (!Array.prototype.remove) Array.prototype.remove = function (this: T[], item: T) { -- cgit 1.5.1 From f09a6976b27743322b6cf760c677340ddd428ec0 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 26 Nov 2025 15:55:47 -0600 Subject: remove remove --- .../#channel_id/messages/#message_id/reactions.ts | 7 ++++--- src/util/entities/Guild.ts | 4 ++-- src/util/util/extensions/Array.test.ts | 8 +------- src/util/util/extensions/Array.ts | 17 +++++++---------- 4 files changed, 14 insertions(+), 22 deletions(-) (limited to 'src/api/routes') 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 fe2a2509..63c41cd7 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 @@ -29,6 +29,7 @@ import { MessageReactionRemoveEmojiEvent, MessageReactionRemoveEvent, User, + arrayRemove, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -112,7 +113,7 @@ router.delete( const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!already_added) throw new HTTPError("Reaction not found", 404); - message.reactions.remove(already_added); + arrayRemove(message.reactions, already_added); await Promise.all([ message.save(), @@ -283,7 +284,7 @@ router.delete( already_added.count--; - if (already_added.count <= 0) message.reactions.remove(already_added); + if (already_added.count <= 0) arrayRemove(message.reactions, already_added); else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); await message.save(); @@ -340,7 +341,7 @@ router.delete( already_added.count--; - if (already_added.count <= 0) message.reactions.remove(already_added); + if (already_added.count <= 0) arrayRemove(message.reactions, already_added); else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); await message.save(); diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts index 0b09f859..92d4925a 100644 --- a/src/util/entities/Guild.ts +++ b/src/util/entities/Guild.ts @@ -30,7 +30,7 @@ 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 @@ -420,7 +420,7 @@ export class Guild extends BaseClass { if (typeof insertPoint == "string") position = guild.channel_ordering.indexOf(insertPoint) + 1; else position = insertPoint; - guild.channel_ordering.remove(channel_id); + arrayRemove(guild.channel_ordering, channel_id); guild.channel_ordering.splice(position, 0, channel_id); await Guild.update({ id: guild_id }, { channel_ordering: guild.channel_ordering }); diff --git a/src/util/util/extensions/Array.test.ts b/src/util/util/extensions/Array.test.ts index 6b15f5d5..8952c901 100644 --- a/src/util/util/extensions/Array.test.ts +++ b/src/util/util/extensions/Array.test.ts @@ -5,11 +5,5 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; describe("Array extensions", () => { - it("remove", () => { - const arr = [1, 2, 3, 4, 5]; - arr.remove(3); - assert.deepEqual(arr, [1, 2, 4, 5]); - arr.remove(6); - assert.deepEqual(arr, [1, 2, 4, 5]); - }); + // }); diff --git a/src/util/util/extensions/Array.ts b/src/util/util/extensions/Array.ts index b58b1c03..bbd86164 100644 --- a/src/util/util/extensions/Array.ts +++ b/src/util/util/extensions/Array.ts @@ -18,10 +18,12 @@ declare global { interface Array { - remove(item: T): void; + /** + * @deprecated never use, idk why but I can't get rid of this without errors + */ + remove(h: T): never; } } - /* https://stackoverflow.com/a/50636286 */ export function arrayPartition(array: T[], filter: (elem: T) => boolean): [T[], T[]] { const pass: T[] = [], @@ -30,16 +32,11 @@ export function arrayPartition(array: T[], filter: (elem: T) => boolean): [T[ return [pass, fail]; } -export function arrayRemove(this: T[], item: T): void { - const index = this.indexOf(item); +export function arrayRemove(array: T[], item: T): void { + const index = array.indexOf(item); if (index > -1) { - this.splice(index, 1); + array.splice(index, 1); } } // register extensions - -if (!Array.prototype.remove) - Array.prototype.remove = function (this: T[], item: T) { - return arrayRemove.call(this, item); - }; -- cgit 1.5.1 From 10649cdbc6a232ada7da0ee0d1177a3279173347 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 26 Nov 2025 17:17:15 -0600 Subject: make mentions not use the pollyfills --- src/api/routes/users/@me/mentions.ts | 100 +++++++++-------------------------- 1 file changed, 26 insertions(+), 74 deletions(-) (limited to 'src/api/routes') diff --git a/src/api/routes/users/@me/mentions.ts b/src/api/routes/users/@me/mentions.ts index cb2aac33..37a7abe1 100644 --- a/src/api/routes/users/@me/mentions.ts +++ b/src/api/routes/users/@me/mentions.ts @@ -19,7 +19,7 @@ import { route } from "@spacebar/api"; import { Snowflake, User, Message, Member, Channel, Permissions, timePromise, NewUrlUserSignatureData, Stopwatch, Attachment } from "@spacebar/util"; import { Request, Response, Router } from "express"; -import { In, LessThan } from "typeorm"; +import { In, LessThan, FindOptionsWhere } from "typeorm"; const router: Router = Router({ mergeParams: true }); @@ -70,7 +70,7 @@ router.get( const channels = await Channel.find({ where: { - guild_id: In(memberships.map((m) => m.guild_id).distinct()), + guild_id: In(memberships.map((m) => m.guild_id)), }, select: { id: true, guild_id: true, permission_overwrites: true }, }); @@ -78,7 +78,7 @@ router.get( const visibleChannels = channels.filter((c) => { const member = memberships.find((m) => m.guild_id === c.guild_id)!; return Permissions.finalPermission({ - user: { id: member.id, roles: member.roles.map((r) => r.id).distinct(), communication_disabled_until: member.communication_disabled_until, flags: 0 }, + user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 }, guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles }, channel: c, }).has("VIEW_CHANNEL"); @@ -90,81 +90,32 @@ router.get( return acc; }, [] as Snowflake[]); - const [ - { result: userMentions, elapsed: userMentionQueryTime }, - { result: roleMentions, elapsed: roleMentionQueryTime }, - { result: everyoneMentions, elapsed: everyoneMentionQueryTime }, - ] = await Promise.all([ - await timePromise(() => - Message.find({ - where: { - channel_id: In(visibleChannelIds), - mentions: { id: user.id }, - ...(before === undefined ? {} : { id: LessThan(before) }), - }, - select: { - id: true, - timestamp: true, - }, - order: { - timestamp: "DESC", - }, - take: limit, - }), - ), - await timePromise(() => - !roles - ? Promise.resolve([]) - : Message.find({ - where: { - channel_id: In(visibleChannelIds), - mention_roles: { id: In(ownedMentionableRoleIds) }, - ...(before === undefined ? {} : { id: LessThan(before) }), - }, - select: { - id: true, - timestamp: true, - }, - order: { - timestamp: "DESC", - }, - take: limit, - }), - ), - await timePromise(() => - !everyone - ? Promise.resolve([]) - : Message.find({ - where: { - channel_id: In(visibleChannelIds), - mention_everyone: true, - ...(before === undefined ? {} : { id: LessThan(before) }), - }, - select: { - id: true, - timestamp: true, - }, - order: { - timestamp: "DESC", - }, - take: limit, - }), - ), - ]); - - const allMentions = [...userMentions, ...roleMentions, ...everyoneMentions]; - console.log( - `[Inbox/mentions] User ${user.id} query results: totalRecs=${allMentions.length} | user=${userMentions.length} (took ${userMentionQueryTime.totalMilliseconds}ms), role=${roleMentions.length} (took ${roleMentionQueryTime.totalMilliseconds}ms), everyone=${everyoneMentions.length} (took ${everyoneMentionQueryTime.totalMilliseconds}ms)`, - ); - const messageIdsToReturn = allMentions - .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) - .distinctBy((m) => m.id) - .slice(0, limit); + const whereQuery: FindOptionsWhere[] = [ + { + channel_id: In(visibleChannelIds), + mentions: { id: user.id }, + id: before ? LessThan(before) : undefined, + }, + ]; + if (everyone) { + whereQuery.push({ + channel_id: In(visibleChannelIds), + mention_everyone: true, + id: before ? LessThan(before) : undefined, + }); + } + if (roles) { + whereQuery.push({ + channel_id: In(visibleChannelIds), + mention_roles: { id: In(ownedMentionableRoleIds) }, + id: before ? LessThan(before) : undefined, + }); + } const sw = Stopwatch.startNew(); const finalMessages = ( await Message.find({ - where: { id: In(messageIdsToReturn.map((m) => m.id)) }, + where: whereQuery, order: { timestamp: "DESC" }, relations: [ "author", @@ -185,6 +136,7 @@ router.get( "referenced_message.sticker_items", "referenced_message.attachments", ], + take: limit, }) ).map((m) => { return { -- cgit 1.5.1 From d18cdd2d28202119bc306a99389f5f60d255c878 Mon Sep 17 00:00:00 2001 From: MathMan05 Date: Wed, 26 Nov 2025 17:20:22 -0600 Subject: remove string proto fake polyfill --- .../routes/channels/#channel_id/messages/index.ts | 5 +-- src/util/util/String.ts | 8 ++++- src/util/util/extensions/String.test.ts | 15 --------- src/util/util/extensions/String.ts | 37 ---------------------- src/util/util/extensions/index.ts | 1 - 5 files changed, 10 insertions(+), 56 deletions(-) delete mode 100644 src/util/util/extensions/String.test.ts delete mode 100644 src/util/util/extensions/String.ts (limited to 'src/api/routes') diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts index 99523dab..d399e53a 100644 --- a/src/api/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts @@ -41,6 +41,7 @@ import { Snowflake, uploadFile, User, + stringGlobToRegexp, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -453,8 +454,8 @@ router.post( if (rule.trigger_type == AutomodTriggerTypes.CUSTOM_WORDS) { const triggerMeta = rule.trigger_metadata as AutomodCustomWordsRule; - const regexes = triggerMeta.regex_patterns.map((x) => new RegExp(x, "i")).concat(triggerMeta.keyword_filter.map((k) => k.globToRegexp("i"))); - const allowedRegexes = triggerMeta.allow_list.map((k) => k.globToRegexp("i")); + const regexes = triggerMeta.regex_patterns.map((x) => new RegExp(x, "i")).concat(triggerMeta.keyword_filter.map((k) => stringGlobToRegexp(k, "i"))); + const allowedRegexes = triggerMeta.allow_list.map((k) => stringGlobToRegexp(k, "i")); const matches = regexes .map((r) => message.content!.match(r)) diff --git a/src/util/util/String.ts b/src/util/util/String.ts index 2d2e132a..f79e73f2 100644 --- a/src/util/util/String.ts +++ b/src/util/util/String.ts @@ -37,4 +37,10 @@ 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); -} \ No newline at end of file +} + +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/extensions/String.test.ts b/src/util/util/extensions/String.test.ts deleted file mode 100644 index d5cc9292..00000000 --- a/src/util/util/extensions/String.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import moduleAlias from "module-alias"; -moduleAlias(); -import './String'; -import { describe, it } from 'node:test'; -import assert from 'node:assert/strict'; - -describe("String extensions", () => { - - it("globToRegexp", () => { - const pattern = "file-*.txt"; - const regex = pattern.globToRegexp(); - assert.ok(regex.test("file-123.txt")); - }); - -}); \ No newline at end of file diff --git a/src/util/util/extensions/String.ts b/src/util/util/extensions/String.ts deleted file mode 100644 index ce9b1a51..00000000 --- a/src/util/util/extensions/String.ts +++ /dev/null @@ -1,37 +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 . -*/ - -declare global { - interface String { - globToRegexp(flags?: string): RegExp; - } -} - -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); -} - -// Register extensions -if (!String.prototype.globToRegexp) - String.prototype.globToRegexp = function (str: string, flags?: string) { - return stringGlobToRegexp.call(null, str, flags); - }; \ No newline at end of file diff --git a/src/util/util/extensions/index.ts b/src/util/util/extensions/index.ts index b7dcf4d5..e1946bb3 100644 --- a/src/util/util/extensions/index.ts +++ b/src/util/util/extensions/index.ts @@ -1,2 +1 @@ export * from "./Array"; -export * from "./String"; -- cgit 1.5.1