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 b265bc5f..54a6539e 100644
--- a/src/api/routes/users/@me/settings.ts
+++ b/src/api/routes/users/@me/settings.ts
@@ -67,7 +67,7 @@ router.patch(
relations: ["settings"],
});
- if (!user.settings) user.settings = UserSettings.create(body as UserSettingsUpdateSchema);
+ 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;
|