diff --git a/scripts/openapi.js b/scripts/openapi.js
index 589614cc..a1188c54 100644
--- a/scripts/openapi.js
+++ b/scripts/openapi.js
@@ -20,9 +20,7 @@ require("module-alias/register");
const getRouteDescriptions = require("./util/getRouteDescriptions");
const path = require("path");
const fs = require("fs");
-const {
- NO_AUTHORIZATION_ROUTES,
-} = require("../dist/api/middlewares/Authentication");
+const { NO_AUTHORIZATION_ROUTES } = require("../dist/api/middlewares/Authentication");
require("../dist/util/util/extensions");
const { bgRedBright } = require("picocolors");
@@ -89,8 +87,7 @@ function combineSchemas(schemas) {
continue;
}
specification.components = specification.components || {};
- specification.components.schemas =
- specification.components.schemas || {};
+ specification.components.schemas = specification.components.schemas || {};
specification.components.schemas[key] = definitions[key];
delete definitions[key].additionalProperties;
delete definitions[key].$schema;
@@ -122,7 +119,7 @@ function apiRoutes(missingRoutes) {
const tags = Array.from(routes.keys())
.map((x) => getTag(x))
.sort((a, b) => a.localeCompare(b));
- specification.tags = tags.distinct().map((x) => ({ name: x }));
+ specification.tags = [...new Set(tags)].map((x) => ({ name: x }));
routes.forEach((route, pathAndMethod) => {
const [p, method] = pathAndMethod.split("|");
@@ -135,8 +132,7 @@ function apiRoutes(missingRoutes) {
if (
!NO_AUTHORIZATION_ROUTES.some((x) => {
- if (typeof x === "string")
- return (method.toUpperCase() + " " + path).startsWith(x);
+ if (typeof x === "string") return (method.toUpperCase() + " " + path).startsWith(x);
return x.test(method.toUpperCase() + " " + path);
})
) {
@@ -177,9 +173,7 @@ function apiRoutes(missingRoutes) {
};
else
obj.responses[k] = {
- description:
- obj?.responses?.[k]?.description ||
- "No description available",
+ description: obj?.responses?.[k]?.description || "No description available",
};
}
} else {
@@ -214,7 +208,7 @@ function apiRoutes(missingRoutes) {
obj.parameters = [...(obj.parameters || []), ...query];
}
- obj.tags = [...(obj.tags || []), getTag(p)].distinct();
+ obj.tags = [...new Set([...(obj.tags || []), getTag(p)])];
if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) {
obj["x-badges"] = [
@@ -225,45 +219,28 @@ function apiRoutes(missingRoutes) {
];
}
- specification.paths[path] = Object.assign(
- specification.paths[path] || {},
- {
- [method]: obj,
- },
- );
+ specification.paths[path] = Object.assign(specification.paths[path] || {}, {
+ [method]: obj,
+ });
});
}
async function main() {
console.log("Generating OpenAPI Specification...");
- const routesRes = await fetch(
- "https://github.com/spacebarchat/missing-routes/raw/main/missing.json",
- {
- headers: {
- Accept: "application/json",
- },
+ const routesRes = await fetch("https://github.com/spacebarchat/missing-routes/raw/main/missing.json", {
+ headers: {
+ Accept: "application/json",
},
- );
+ });
const missingRoutes = await routesRes.json();
combineSchemas(schemas);
apiRoutes(missingRoutes);
- fs.writeFileSync(
- openapiPath,
- JSON.stringify(specification, null, 4)
- .replaceAll("#/definitions", "#/components/schemas")
- .replaceAll("bigint", "number"),
- );
+ fs.writeFileSync(openapiPath, JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number"));
console.log("Wrote OpenAPI specification to", openapiPath);
- console.log(
- "Specification contains",
- Object.keys(specification.paths).length,
- "paths and",
- Object.keys(specification.components.schemas).length,
- "schemas.",
- );
+ console.log("Specification contains", Object.keys(specification.paths).length, "paths and", Object.keys(specification.components.schemas).length, "schemas.");
}
main();
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/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index d0d445b9..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";
@@ -244,24 +245,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;
+ 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"] });
- });
+ msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] });
+ }),
+ );
return res.json(ret);
},
@@ -449,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/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/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/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;
+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;
+ // 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 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),
- );
+ 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),
- ),
- ]);
+ // 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);
- },
-);
+ res.sendStatus(204);
+});
export default router;
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/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<Message>[] = [
+ {
+ 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 {
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<UserSettings>(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();
diff --git a/src/gateway/events/Close.ts b/src/gateway/events/Close.ts
index dbbc41d8..909ede96 100644
--- a/src/gateway/events/Close.ts
+++ b/src/gateway/events/Close.ts
@@ -17,16 +17,7 @@
*/
import { WebSocket } from "@spacebar/gateway";
-import {
- emitEvent,
- PresenceUpdateEvent,
- PrivateSessionProjection,
- Session,
- SessionsReplace,
- User,
- VoiceState,
- VoiceStateUpdateEvent,
-} from "@spacebar/util";
+import { emitEvent, PresenceUpdateEvent, PrivateSessionProjection, Session, SessionsReplace, User, VoiceState, VoiceStateUpdateEvent } from "@spacebar/util";
export async function Close(this: WebSocket, code: number, reason: Buffer) {
console.log("[WebSocket] closed", code, reason.toString());
@@ -44,11 +35,7 @@ export async function Close(this: WebSocket, code: number, reason: Buffer) {
});
// clear the voice state for this session if user was in voice channel
- if (
- voiceState &&
- voiceState.session_id === this.session_id &&
- voiceState.channel_id
- ) {
+ if (voiceState && voiceState.session_id === this.session_id && voiceState.channel_id) {
const prevGuildId = voiceState.guild_id;
const prevChannelId = voiceState.channel_id;
@@ -83,7 +70,7 @@ export async function Close(this: WebSocket, code: number, reason: Buffer) {
user_id: this.user_id,
data: sessions,
} as SessionsReplace);
- const session = sessions.first() || {
+ const session = sessions[0] || {
activities: [],
client_status: {},
status: "offline",
diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts
index 94dc7517..c3c74990 100644
--- a/src/gateway/opcodes/LazyRequest.ts
+++ b/src/gateway/opcodes/LazyRequest.ts
@@ -16,28 +16,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {
- getDatabase,
- getPermission,
- listenEvent,
- Member,
- Role,
- Session,
- User,
- Presence,
- Channel,
- Permissions,
-} from "@spacebar/util";
-import {
- WebSocket,
- Payload,
- handlePresenceUpdate,
- OPCODES,
- Send,
-} from "@spacebar/gateway";
+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";
-import { LazyRequestSchema } from "@spacebar/schemas"
+import { LazyRequestSchema } from "@spacebar/schemas";
// TODO: only show roles/members that have access to this channel
// TODO: config: to list all members (even those who are offline) sorted by role, or just those who are online
@@ -53,14 +36,10 @@ const getMostRelevantSession = (sessions: Session[]) => {
};
// sort sessions by relevance
sessions = sessions.sort((a, b) => {
- return (
- statusMap[a.status] -
- statusMap[b.status] +
- ((a.activities?.length ?? 0) - (b.activities?.length ?? 0)) * 2
- );
+ return statusMap[a.status] - statusMap[b.status] + ((a.activities?.length ?? 0) - (b.activities?.length ?? 0)) * 2;
});
- return sessions.first();
+ return sessions[0];
};
async function getMembers(guild_id: string, range: [number, number]) {
@@ -79,10 +58,7 @@ async function getMembers(guild_id: string, range: [number, number]) {
.leftJoinAndSelect("member.user", "user")
.leftJoinAndSelect("user.sessions", "session")
.addSelect("user.settings")
- .addSelect(
- "CASE WHEN session.status IS NULL OR session.status = 'offline' OR session.status = 'invisible' THEN 0 ELSE 1 END",
- "_status",
- )
+ .addSelect("CASE WHEN session.status IS NULL OR session.status = 'offline' OR session.status = 'invisible' THEN 0 ELSE 1 END", "_status")
.orderBy("_status", "DESC")
.addOrderBy("role.position", "DESC")
.addOrderBy("user.username", "ASC")
@@ -104,10 +80,14 @@ async function getMembers(guild_id: string, range: [number, number]) {
const groups = [];
const items = [];
- const member_roles = members
- .map((m) => m.roles)
- .flat()
- .distinctBy((r: Role) => r.id);
+ const member_roles = [
+ ...new Map(
+ members
+ .map((m) => m.roles)
+ .flat()
+ .map((role) => [role.id, role] as [string, Role]),
+ ).values(),
+ ];
member_roles.push(
member_roles.splice(
member_roles.findIndex((x) => x.id === x.guild_id),
@@ -118,9 +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,
@@ -130,13 +108,9 @@ async function getMembers(guild_id: string, range: [number, number]) {
groups.push(group);
for (const member of role_members) {
- const roles = member.roles
- .filter((x: Role) => x.id !== guild_id)
- .map((x: Role) => x.id);
+ const roles = member.roles.filter((x: Role) => x.id !== guild_id).map((x: Role) => x.id);
- const session: Session | undefined = getMostRelevantSession(
- member.user.sessions,
- );
+ const session: Session | undefined = getMostRelevantSession(member.user.sessions);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
@@ -157,11 +131,7 @@ async function getMembers(guild_id: string, range: [number, number]) {
},
};
- if (
- !session ||
- session.status == "invisible" ||
- session.status == "offline"
- ) {
+ if (!session || session.status == "invisible" || session.status == "offline") {
item.member.presence.status = "offline";
offlineItems.push(item);
group.count--;
@@ -188,24 +158,14 @@ async function getMembers(guild_id: string, range: [number, number]) {
items,
groups,
range,
- members: items
- .map((x) =>
- "member" in x
- ? { ...x.member, settings: undefined }
- : undefined,
- )
- .filter((x) => !!x),
+ members: items.map((x) => ("member" in x ? { ...x.member, settings: undefined } : undefined)).filter((x) => !!x),
};
}
async function subscribeToMemberEvents(this: WebSocket, user_id: string) {
if (this.events[user_id]) return false; // already subscribed as friend
if (this.member_events[user_id]) return false; // already subscribed in member list
- this.member_events[user_id] = await listenEvent(
- user_id,
- handlePresenceUpdate.bind(this),
- this.listen_options,
- );
+ this.member_events[user_id] = await listenEvent(user_id, handlePresenceUpdate.bind(this), this.listen_options);
return true;
}
@@ -213,8 +173,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
const startTime = Date.now();
// TODO: check data
check.call(this, LazyRequestSchema, d);
- const { guild_id, typing, channels, activities, members } =
- d as LazyRequestSchema;
+ const { guild_id, typing, channels, activities, members } = d as LazyRequestSchema;
if (members) {
// Client has requested a PRESENCE_UPDATE for specific member
@@ -222,10 +181,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
await Promise.all([
members.map(async (x) => {
if (!x) return;
- const didSubscribe = await subscribeToMemberEvents.call(
- this,
- x,
- );
+ const didSubscribe = await subscribeToMemberEvents.call(this, x);
if (!didSubscribe) return;
// if we didn't subscribe just now, this is a new subscription
@@ -257,7 +213,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
if (!channels) throw new Error("Must provide channel ranges");
- const channel_id = Object.keys(channels || {}).first();
+ const channel_id = Object.keys(channels || {})[0];
if (!channel_id) return;
const permissions = await getPermission(this.user_id, guild_id, channel_id);
@@ -267,9 +223,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
if (!Array.isArray(ranges)) throw new Error("Not a valid Array");
const member_count = await Member.count({ where: { guild_id } });
- const ops = await Promise.all(
- ranges.map((x) => getMembers(guild_id, x as [number, number])),
- );
+ const ops = await Promise.all(ranges.map((x) => getMembers(guild_id, x as [number, number])));
let list_id = "everyone";
@@ -282,10 +236,8 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
channel.permission_overwrites.forEach((overwrite) => {
const { id, allow, deny } = overwrite;
- if (BigInt(allow) & Permissions.FLAGS.VIEW_CHANNEL)
- perms.push(`allow:${id}`);
- else if (BigInt(deny) & Permissions.FLAGS.VIEW_CHANNEL)
- perms.push(`deny:${id}`);
+ if (BigInt(allow) & Permissions.FLAGS.VIEW_CHANNEL) perms.push(`allow:${id}`);
+ else if (BigInt(deny) & Permissions.FLAGS.VIEW_CHANNEL) perms.push(`deny:${id}`);
});
if (perms.length > 0) {
@@ -302,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,
@@ -317,9 +266,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
op: "SYNC",
range: x.range,
})),
- online_count:
- member_count -
- (groups.find((x) => x.id == "offline")?.count ?? 0),
+ online_count: member_count - (groups.find((x) => x.id == "offline")?.count ?? 0),
member_count,
id: list_id,
guild_id,
@@ -327,7 +274,5 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
},
});
- console.log(
- `[Gateway] LAZY_REQUEST ${guild_id} ${channel_id} took ${Date.now() - startTime}ms`,
- );
+ console.log(`[Gateway] LAZY_REQUEST ${guild_id} ${channel_id} took ${Date.now() - startTime}ms`);
}
diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts
index 252fb8f9..78242044 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)].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
@@ -280,7 +280,7 @@ export class Channel extends BaseClass {
if (!ur.channel.recipients) continue;
const re = ur.channel.recipients.map((r) => r.user_id);
if (re.length === channelRecipients.length) {
- if (re.containsAll(channelRecipients)) {
+ if (channelRecipients.every((_) => re.includes(_))) {
if (channel == null) {
channel = ur.channel;
await ur.assign({ closed: false }).save();
@@ -429,7 +429,7 @@ export class Channel extends BaseClass {
}
async getUserPermissions(opts: { user_id?: string; user?: User; member?: Member; guild?: Guild }): Promise<Permissions> {
- if(this.isDm()) this.owner_id == (opts.user_id ?? opts.user?.id) ? Permissions.ALL : Permissions.DEFAULT_DM_PERMISSIONS;
+ if (this.isDm()) return this.owner_id == (opts.user_id ?? opts.user?.id) ? Permissions.ALL : Permissions.DEFAULT_DM_PERMISSIONS;
let guild = opts.guild;
if (!guild) {
if (this.guild) guild = this.guild;
@@ -470,7 +470,7 @@ export class Channel extends BaseClass {
position: true,
},
},
- loadEagerRelations: false
+ loadEagerRelations: false,
})
).roles
).sort((a, b) => a.position - b.position); // ascending by position
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/entities/User.ts b/src/util/entities/User.ts
index 4343aaf4..afa7c00e 100644
--- a/src/util/entities/User.ts
+++ b/src/util/entities/User.ts
@@ -354,9 +354,9 @@ export class User extends BaseClass {
for (const channel of qry) {
console.warn(JSON.stringify(channel));
}
+ throw new Error("Array contains more than one matching element");
}
- // throw if multiple
- return qry.single((_) => true);
+ return qry[0];
}
}
diff --git a/src/util/util/FieldError.ts b/src/util/util/FieldError.ts
index a99a0486..83d6051c 100644
--- a/src/util/util/FieldError.ts
+++ b/src/util/util/FieldError.ts
@@ -31,7 +31,7 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str
return new FieldError(
50035,
"Invalid Form Body",
- fields.map<ErrorContent, ObjectErrorContent>(({ message, code }) => ({
+ Object.values(fields).map(({ message, code }) => ({
_errors: [
{
message,
@@ -39,7 +39,7 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str
},
],
})),
- errors
+ errors,
);
}
@@ -51,7 +51,7 @@ export class FieldError extends Error {
public code: string | number,
public message: string,
public errors?: object, // TODO: I don't like this typing.
- public _ajvErrors?: ErrorObject[]
+ public _ajvErrors?: ErrorObject[],
) {
super(message);
}
diff --git a/src/util/util/KittyLogo.ts b/src/util/util/KittyLogo.ts
index 2af0357e..5251227d 100644
--- a/src/util/util/KittyLogo.ts
+++ b/src/util/util/KittyLogo.ts
@@ -15,12 +15,9 @@ export class KittyLogo {
public static async initialise() {
this.isSupported = await this.checkSupport();
if (this.isSupported)
- this.iconCache = readFileSync(
- __dirname + "/../../../assets/icon.png",
- {
- encoding: "base64",
- },
- );
+ this.iconCache = readFileSync(__dirname + "/../../../assets/icon.png", {
+ encoding: "base64",
+ });
}
public static printLogo(): void {
@@ -77,11 +74,9 @@ export class KittyLogo {
if (resp.startsWith("\x1B_Gi=31;OK")) resolve(true);
else resolve(false);
});
- process.stdout.write(
- "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c",
- );
+ process.stdout.write("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c");
- await sleep(5000);
+ await new Promise((res) => setTimeout(res, 5000));
resolve(false);
})();
});
@@ -111,9 +106,7 @@ export class KittyLogo {
while (pngData.length > 0) {
const dataSize = Math.min(pngData.length, chunkSize);
- process.stdout.write(
- header + `,m=${dataSize == chunkSize ? 1 : 0};`,
- );
+ process.stdout.write(header + `,m=${dataSize == chunkSize ? 1 : 0};`);
process.stdout.write(pngData.slice(0, chunkSize));
pngData = pngData.slice(chunkSize);
process.stdout.write("\x1b\\");
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/Url.ts b/src/util/util/Url.ts
index c0f07b37..808bdeaa 100644
--- a/src/util/util/extensions/Url.ts
+++ b/src/util/util/Url.ts
@@ -16,19 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-declare module "url" {
- interface URL {
- normalize(): string;
- }
-}
-
-/**
- * Normalize a URL by:
- * - Removing trailing slashes (except root path)
- * - Sorting query params alphabetically
- * - Removing empty query strings
- * - Removing fragments
- */
export function normalizeUrl(input: string): string {
try {
const u = new URL(input);
@@ -52,9 +39,3 @@ export function normalizeUrl(input: string): string {
return input;
}
}
-
-// register extensions
-if (!URL.prototype.normalize)
- URL.prototype.normalize = function () {
- return normalizeUrl(this.toString());
- };
diff --git a/src/util/util/extensions/Array.test.ts b/src/util/util/extensions/Array.test.ts
index d41442c9..8952c901 100644
--- a/src/util/util/extensions/Array.test.ts
+++ b/src/util/util/extensions/Array.test.ts
@@ -1,102 +1,9 @@
import moduleAlias from "module-alias";
moduleAlias();
-import './Array';
-import { describe, it } from 'node:test';
-import assert from 'node:assert/strict';
+import "./Array";
+import { describe, it } from "node:test";
+import assert from "node:assert/strict";
describe("Array extensions", () => {
-
- it("containsAll", () => {
- const arr = [1, 2, 3, 4, 5];
- assert(arr.containsAll([1, 2]));
- assert(!arr.containsAll([1, 6]));
- assert(arr.containsAll([]));
- assert([].containsAll([]));
-
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- assert(![].containsAll([1]));
- });
-
- it("partition", () => {
- const arr = [1, 2, 3, 4, 5];
- const [even, odd] = arr.partition((n) => n % 2 === 0);
- assert.deepEqual(even, [2, 4]);
- assert.deepEqual(odd, [1, 3, 5]);
- });
-
- it("single", () => {
- const arr = [1, 2, 3, 4, 5];
- assert.strictEqual(arr.single((n) => n === 3), 3);
- assert.strictEqual(arr.single((n) => n === 6), null);
- assert.throws(() => arr.single((n) => n > 2));
- });
-
- it("forEachAsync", async () => {
- const arr = [1, 2, 3];
- let sum = 0;
- await arr.forEachAsync(async (n) => {
- sum += n;
- });
- assert.strictEqual(sum, 6);
- });
-
- it("filterAsync", async () => {
- const arr = [1, 2, 3, 4, 5];
- const even = await arr.filterAsync(async (n) => n % 2 === 0);
- assert.deepEqual(even, [2, 4]);
- });
-
- 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]);
- });
-
- it("first", () => {
- const arr = [1, 2, 3];
- assert.strictEqual(arr.first(), 1);
- assert.strictEqual([].first(), undefined);
- });
-
- it("last", () => {
- const arr = [1, 2, 3];
- assert.strictEqual(arr.last(), 3);
- assert.strictEqual([].last(), undefined);
- });
-
- it("distinct", () => {
- const arr = [1, 2, 2, 3, 3, 3];
- assert.deepEqual(arr.distinct(), [1, 2, 3]);
- assert.deepEqual([].distinct(), []);
- });
-
- it("distinctBy", () => {
- const arr = [{ id: 1 }, { id: 2 }, { id: 1 }, { id: 3 }];
- assert.deepEqual(arr.distinctBy((x) => x.id), [{ id: 1 }, { id: 2 }, { id: 3 }]);
- assert.deepEqual([].distinctBy((x) => x), []);
- });
-
- it("intersect", () => {
- const arr1 = [1, 2, 3, 4];
- const arr2 = [3, 4, 5, 6];
- assert.deepEqual(arr1.intersect(arr2), [3, 4]);
- assert.deepEqual(arr1.intersect([]), []);
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- assert.deepEqual([].intersect(arr2), []);
- });
-
- it("except", () => {
- const arr1 = [1, 2, 3, 4];
- const arr2 = [3, 4, 5, 6];
- assert.deepEqual(arr1.except(arr2), [1, 2]);
- assert.deepEqual(arr1.except([]), [1, 2, 3, 4]);
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- assert.deepEqual([].except(arr2), []);
- });
-
-});
\ No newline at end of file
+ //
+});
diff --git a/src/util/util/extensions/Array.ts b/src/util/util/extensions/Array.ts
index c32b86ce..bbd86164 100644
--- a/src/util/util/extensions/Array.ts
+++ b/src/util/util/extensions/Array.ts
@@ -18,25 +18,12 @@
declare global {
interface Array<T> {
- containsAll(target: T[]): boolean;
- partition(filter: (elem: T) => boolean): [T[], T[]];
- single(filter: (elem: T) => boolean): T | null;
- forEachAsync(callback: (elem: T, index: number, array: T[]) => Promise<void>): Promise<void>;
- filterAsync(callback: (elem: T, index: number, array: T[]) => Promise<boolean>): Promise<T[]>;
- remove(item: T): void;
- first(): T | undefined;
- last(): T | undefined;
- distinct(): T[];
- distinctBy<K>(key: (elem: T) => K): T[];
- intersect(other: T[]): T[];
- except(other: T[]): T[];
+ /**
+ * @deprecated never use, idk why but I can't get rid of this without errors
+ */
+ remove(h: T): never;
}
}
-
-export function arrayContainsAll<T>(arr: T[], target: T[]) {
- return target.every((v) => arr.includes(v));
-}
-
/* https://stackoverflow.com/a/50636286 */
export function arrayPartition<T>(array: T[], filter: (elem: T) => boolean): [T[], T[]] {
const pass: T[] = [],
@@ -45,108 +32,11 @@ export function arrayPartition<T>(array: T[], filter: (elem: T) => boolean): [T[
return [pass, fail];
}
-export function arraySingle<T>(array: T[], filter: (elem: T) => boolean): T | null {
- const results = array.filter(filter);
- if (results.length > 1) throw new Error("Array contains more than one matching element");
- if (results.length === 0) return null;
- return results[0];
-}
-
-export async function arrayForEachAsync<T>(array: T[], callback: (elem: T, index: number, array: T[]) => Promise<void>): Promise<void> {
- await Promise.all(array.map(callback));
-}
-
-export async function arrayFilterAsync<T>(array: T[], callback: (elem: T, index: number, array: T[]) => Promise<boolean>): Promise<T[]> {
- const results = await Promise.all(array.map(callback));
- return array.filter((_, index) => results[index]);
-}
-
-export function arrayRemove<T>(this: T[], item: T): void {
- const index = this.indexOf(item);
+export function arrayRemove<T>(array: T[], item: T): void {
+ const index = array.indexOf(item);
if (index > -1) {
- this.splice(index, 1);
+ array.splice(index, 1);
}
}
-export function arrayFirst<T>(this: T[]): T | undefined {
- return this[0];
-}
-
-export function arrayLast<T>(this: T[]): T | undefined {
- return this[this.length - 1];
-}
-
-export function arrayDistinct<T>(this: T[]): T[] {
- return Array.from(new Set(this));
-}
-
-export function arrayDistinctBy<T, K>(this: T[], key: (elem: T) => K): T[] {
- const seen = new Set<K>();
- return this.filter((item) => {
- const k = key(item);
- if (seen.has(k)) {
- return false;
- } else {
- seen.add(k);
- return true;
- }
- });
-}
-
-export function arrayIntersect<T>(this: T[], other: T[]): T[] {
- return this.filter((value) => other.includes(value));
-}
-
-export function arrayExcept<T>(this: T[], other: T[]): T[] {
- return this.filter((value) => !other.includes(value));
-}
-
// register extensions
-if (!Array.prototype.containsAll)
- Array.prototype.containsAll = function <T>(this: T[], target: T[]) {
- return arrayContainsAll(this, target);
- };
-if (!Array.prototype.partition)
- Array.prototype.partition = function <T>(this: T[], filter: (elem: T) => boolean) {
- return arrayPartition(this, filter);
- };
-if (!Array.prototype.single)
- Array.prototype.single = function <T>(this: T[], filter: (elem: T) => boolean) {
- return arraySingle(this, filter);
- };
-if (!Array.prototype.forEachAsync)
- Array.prototype.forEachAsync = function <T>(this: T[], callback: (elem: T, index: number, array: T[]) => Promise<void>) {
- return arrayForEachAsync(this, callback);
- };
-if (!Array.prototype.filterAsync)
- Array.prototype.filterAsync = function <T>(this: T[], callback: (elem: T, index: number, array: T[]) => Promise<boolean>) {
- return arrayFilterAsync(this, callback);
- };
-if (!Array.prototype.remove)
- Array.prototype.remove = function <T>(this: T[], item: T) {
- return arrayRemove.call(this, item);
- };
-if (!Array.prototype.first)
- Array.prototype.first = function <T>(this: T[]) {
- return arrayFirst.call(this);
- };
-if (!Array.prototype.last)
- Array.prototype.last = function <T>(this: T[]) {
- return arrayLast.call(this);
- };
-if (!Array.prototype.distinct)
- Array.prototype.distinct = function <T>(this: T[]) {
- return arrayDistinct.call(this);
- };
-if (!Array.prototype.distinctBy)
- Array.prototype.distinctBy = function <T, K>(this: T[], key: (elem: T) => K) {
- return arrayDistinctBy.call(this, key as ((elem: unknown) => unknown));
- };
-if (!Array.prototype.intersect)
- Array.prototype.intersect = function <T>(this: T[], other: T[]) {
- return arrayIntersect.call(this, other);
- };
-if (!Array.prototype.except)
- Array.prototype.except = function <T>(this: T[], other: T[]) {
- return arrayExcept.call(this, other);
- };
\ No newline at end of file
diff --git a/src/util/util/extensions/Global.test.ts b/src/util/util/extensions/Global.test.ts
deleted file mode 100644
index 0c6a93dc..00000000
--- a/src/util/util/extensions/Global.test.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import moduleAlias from "module-alias";
-moduleAlias();
-import './Global';
-import { describe, it } from 'node:test';
-import assert from 'node:assert/strict';
-
-describe("Global extensions", () => {
-
- it("sleep", async () => {
- const start = Date.now();
- await sleep(100);
- const duration = Date.now() - start;
- assert(duration >= 100, `Sleep duration was less than expected: ${duration}ms`);
- });
-
-});
\ No newline at end of file
diff --git a/src/util/util/extensions/Global.ts b/src/util/util/extensions/Global.ts
deleted file mode 100644
index 4634573d..00000000
--- a/src/util/util/extensions/Global.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-declare global {
- function sleep(ms: number): Promise<void>;
-}
-
-export function globalSleep(ms: number): Promise<void> {
- return new Promise((resolve) => setTimeout(resolve, ms));
-}
-
-if (!globalThis.sleep)
- globalThis.sleep = function (ms: number): Promise<void> {
- return globalSleep(ms);
- };
diff --git a/src/util/util/extensions/Math.test.ts b/src/util/util/extensions/Math.test.ts
deleted file mode 100644
index 5f112dc0..00000000
--- a/src/util/util/extensions/Math.test.ts
+++ /dev/null
@@ -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
deleted file mode 100644
index a5bd80c3..00000000
--- a/src/util/util/extensions/Math.ts
+++ /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
-*/
-
-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/Object.test.ts b/src/util/util/extensions/Object.test.ts
deleted file mode 100644
index 570b8024..00000000
--- a/src/util/util/extensions/Object.test.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import moduleAlias from "module-alias";
-moduleAlias();
-import "./Object";
-import { describe, it } from "node:test";
-import assert from "node:assert/strict";
-
-describe("Object extensions", () => {
- it("forEach", async () => {
- const obj: { [index:string]: number } = { a: 1, b: 2, c: 3 };
- const keys: string[] = [];
- const values: number[] = [];
- obj.forEach<number>((value, key, _) => {
- keys.push(key);
- values.push(value);
- });
- console.log(keys, values);
- assert.deepEqual(keys, ["a", "b", "c"]);
- assert.deepEqual(values, [1, 2, 3]);
- });
-
- it("map", async () => {
- const obj = { a: 1, b: 2, c: 3 };
- const result = obj.map((value, key) => `${key}:${value}`);
- assert.deepEqual(result, { a: "a:1", b: "b:2", c: "c:3" });
- });
-});
diff --git a/src/util/util/extensions/Object.ts b/src/util/util/extensions/Object.ts
deleted file mode 100644
index bda4ccf2..00000000
--- a/src/util/util/extensions/Object.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-declare global {
- interface Object {
- forEach<T>(callback: (value: T, key: string, object: { [index: string]: T }) => void): void;
- map<SV, TV>(callback: (value: SV, key: string, object: { [index: string]: SV }) => TV): { [index: string]: TV };
- }
-}
-
-export function objectForEach<T>(obj: { [index: string]: T }, callback: (value: T, key: string, object: { [index: string]: T }) => void): void {
- Object.keys(obj).forEach((key) => {
- callback(obj[key], key, obj);
- });
-}
-
-export function objectMap<SV, TV>(srcObj: { [index: string]: SV }, callback: (value: SV, key: string, object: { [index: string]: SV }) => TV): { [index: string]: TV } {
- if (typeof callback !== "function") throw new TypeError(`${callback} is not a function`);
- const obj: { [index: string]: TV } = {};
- Object.keys(srcObj).forEach((key) => {
- obj[key] = callback(srcObj[key], key, srcObj);
- });
- return obj;
-}
-
-if (!Object.prototype.forEach)
- Object.defineProperty(Object.prototype, "forEach", {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- value: function (cb) {
- return objectForEach(this, cb);
- },
- enumerable: false,
- writable: true,
- });
-
-if (!Object.prototype.map)
- Object.defineProperty(Object.prototype, "map", {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- value: function (cb) {
- return objectMap(this, cb);
- },
- enumerable: false,
- writable: true,
- });
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 <https://www.gnu.org/licenses/>.
-*/
-
-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/Url.test.ts b/src/util/util/extensions/Url.test.ts
deleted file mode 100644
index 37afd0a2..00000000
--- a/src/util/util/extensions/Url.test.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import moduleAlias from "module-alias";
-moduleAlias();
-import './Url';
-import { describe, it } from 'node:test';
-import assert from 'node:assert/strict';
-
-describe("URL extensions", () => {
-
- it("normalize", async () => {
- const tests: [string, string][] = [
- ["http://example.com", "http://example.com/"],
- ["http://example.com/", "http://example.com/"],
- ["http://example.com/path/", "http://example.com/path"],
- ["http://example.com/path//", "http://example.com/path/"],
- ["http://example.com/path?b=2&a=1", "http://example.com/path?a=1&b=2"],
- ["http://example.com/path?b=2&a=1&", "http://example.com/path?a=1&b=2"],
- ["http://example.com/path?", "http://example.com/path"],
- ["http://example.com/path#fragment", "http://example.com/path"],
- ["http://example.com/path/?b=2&a=1#fragment", "http://example.com/path?a=1&b=2"],
- ["ftp://example.com/resource/", "ftp://example.com/resource"],
- ["https://example.com/resource?z=3&y=2&x=1", "https://example.com/resource?x=1&y=2&z=3"],
- ["https://example.com/resource?z=3&y=2&x=1#", "https://example.com/resource?x=1&y=2&z=3"],
- ["https://example.com/resource?z=3&y=2&x=1#section", "https://example.com/resource?x=1&y=2&z=3"],
- ["https://example.com/resource/?z=3&y=2&x=1#section", "https://example.com/resource?x=1&y=2&z=3"],
- ["https://example.com/resource//?z=3&y=2&x=1#section", "https://example.com/resource/?x=1&y=2&z=3"],
- ["https://example.com/", "https://example.com/"],
- ["https://example.com", "https://example.com/"],
- ];
- for (const [input, expected] of tests) {
- assert.doesNotThrow(() => new URL(input), `URL("${input}") should not throw`);
- const url = new URL(input);
- const normalized = url.normalize();
- assert.strictEqual(normalized, expected, `normalize("${input}") = "${normalized}", expected "${expected}"`);
- }
- });
-
-});
\ No newline at end of file
diff --git a/src/util/util/extensions/index.ts b/src/util/util/extensions/index.ts
index e006591f..e1946bb3 100644
--- a/src/util/util/extensions/index.ts
+++ b/src/util/util/extensions/index.ts
@@ -1,5 +1 @@
export * from "./Array";
-export * from "./Math";
-export * from "./Url";
-export * from "./Object";
-export * from "./String";
\ No newline at end of file
diff --git a/src/util/util/index.ts b/src/util/util/index.ts
index 11c4899c..920c330d 100644
--- a/src/util/util/index.ts
+++ b/src/util/util/index.ts
@@ -48,4 +48,5 @@ export * from "./Application";
export * from "./NameValidation";
export * from "../../schemas/HelperTypes";
export * from "./extensions";
-export * from "./Random";
\ No newline at end of file
+export * from "./Random";
+export * from "./Url";
diff --git a/src/util/util/lambert-server/check.ts b/src/util/util/lambert-server/check.ts
index 4dabc5ce..bca65bf4 100644
--- a/src/util/util/lambert-server/check.ts
+++ b/src/util/util/lambert-server/check.ts
@@ -2,8 +2,7 @@ import { NextFunction, Request, Response } from "express";
import { HTTPError } from ".";
const OPTIONAL_PREFIX = "$";
-const EMAIL_REGEX =
- /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
export function check(schema: any) {
return (req: Request, res: Response, next: NextFunction) => {
@@ -31,11 +30,7 @@ export class Email {
}
}
-export function instanceOf(
- type: any,
- value: any,
- { path = "", optional = false }: { path?: string; optional?: boolean } = {}
-): Boolean {
+export function instanceOf(type: any, value: any, { path = "", optional = false }: { path?: string; optional?: boolean } = {}): boolean {
if (!type) return true; // no type was specified
if (value == null) {
@@ -55,7 +50,9 @@ export function instanceOf(
try {
value = BigInt(value);
if (typeof value === "bigint") return true;
- } catch (error) {}
+ } catch (error) {
+ //Ignore BigInt error
+ }
throw `${path} must be a bigint`;
case Boolean:
if (value == "true") value = true;
@@ -98,9 +95,8 @@ export function instanceOf(
}
if (typeof value !== "object") throw `${path} must be a object`;
- const diff = Object.keys(value).except(
- Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x))
- );
+ const filterset = new Set(Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x)));
+ const diff = Object.keys(value).filter((_) => !filterset.has(_));
if (diff.length) throw `Unknown key ${diff}`;
|