diff --git a/src/util/Channel.ts b/src/util/Channel.ts
new file mode 100644
index 00000000..c8df85bc
--- /dev/null
+++ b/src/util/Channel.ts
@@ -0,0 +1,54 @@
+import {
+ ChannelCreateEvent,
+ ChannelModel,
+ ChannelType,
+ getPermission,
+ GuildModel,
+ Snowflake,
+ TextChannel,
+ VoiceChannel
+} from "@fosscord/server-util";
+import { HTTPError } from "lambert-server";
+import { emitEvent } from "./Event";
+
+// TODO: DM channel
+export async function createChannel(channel: Partial<TextChannel | VoiceChannel>, user_id: string = "0") {
+ if (!channel.permission_overwrites) channel.permission_overwrites = [];
+
+ switch (channel.type) {
+ case ChannelType.GUILD_TEXT:
+ case ChannelType.GUILD_VOICE:
+ break;
+ case ChannelType.DM:
+ case ChannelType.GROUP_DM:
+ throw new HTTPError("You can't create a dm channel in a guild");
+ // TODO: check if guild is community server
+ case ChannelType.GUILD_STORE:
+ case ChannelType.GUILD_NEWS:
+ default:
+ throw new HTTPError("Not yet supported");
+ }
+
+ const permissions = await getPermission(user_id, channel.guild_id);
+ permissions.hasThrow("MANAGE_CHANNELS");
+
+ if (channel.parent_id) {
+ const exists = await ChannelModel.findOne({ id: channel.parent_id }, { guild_id: true }).exec();
+ if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400);
+ if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild");
+ }
+
+ // TODO: auto generate position
+
+ channel = await new ChannelModel({
+ ...channel,
+ id: Snowflake.generate(),
+ created_at: new Date(),
+ // @ts-ignore
+ recipients: null
+ }).save();
+
+ await emitEvent({ event: "CHANNEL_CREATE", data: channel, guild_id: channel.guild_id } as ChannelCreateEvent);
+
+ return channel;
+}
diff --git a/src/util/Member.ts b/src/util/Member.ts
index 2842298d..d03a8f12 100644
--- a/src/util/Member.ts
+++ b/src/util/Member.ts
@@ -10,7 +10,7 @@ import {
RoleModel,
toObject,
UserModel,
- GuildDocument,
+ GuildDocument
} from "@fosscord/server-util";
import { HTTPError } from "lambert-server";
@@ -27,7 +27,7 @@ export const PublicMemberProjection = {
pending: true,
deaf: true,
mute: true,
- premium_since: true,
+ premium_since: true
};
export async function isMember(user_id: string, guild_id: string) {
@@ -59,12 +59,13 @@ export async function addMember(user_id: string, guild_id: string, cache?: { gui
premium_since: undefined,
deaf: false,
mute: false,
- pending: false,
+ pending: false
};
await Promise.all([
new MemberModel({
...member,
+ read_state: {},
settings: {
channel_overrides: [],
message_notifications: 0,
@@ -73,8 +74,8 @@ export async function addMember(user_id: string, guild_id: string, cache?: { gui
muted: false,
suppress_everyone: false,
suppress_roles: false,
- version: 0,
- },
+ version: 0
+ }
}).save(),
UserModel.updateOne({ id: user_id }, { $push: { guilds: guild_id } }).exec(),
@@ -85,10 +86,10 @@ export async function addMember(user_id: string, guild_id: string, cache?: { gui
data: {
...member,
user,
- guild_id: guild_id,
+ guild_id: guild_id
},
- guild_id: guild_id,
- } as GuildMemberAddEvent),
+ guild_id: guild_id
+ } as GuildMemberAddEvent)
]);
await emitEvent({
@@ -99,7 +100,7 @@ export async function addMember(user_id: string, guild_id: string, cache?: { gui
.populate({ path: "joined_at", match: { id: user.id } })
.execPopulate()
),
- user_id,
+ user_id
} as GuildCreateEvent);
}
@@ -115,7 +116,7 @@ export async function removeMember(user_id: string, guild_id: string) {
return Promise.all([
MemberModel.deleteOne({
id: user_id,
- guild_id: guild_id,
+ guild_id: guild_id
}).exec(),
UserModel.updateOne({ id: user.id }, { $pull: { guilds: guild_id } }).exec(),
GuildModel.updateOne({ id: guild_id }, { $inc: { member_count: -1 } }).exec(),
@@ -123,18 +124,18 @@ export async function removeMember(user_id: string, guild_id: string) {
emitEvent({
event: "GUILD_DELETE",
data: {
- id: guild_id,
+ id: guild_id
},
- user_id: user_id,
+ user_id: user_id
} as GuildDeleteEvent),
emitEvent({
event: "GUILD_MEMBER_REMOVE",
data: {
guild_id: guild_id,
- user: user,
+ user: user
},
- guild_id: guild_id,
- } as GuildMemberRemoveEvent),
+ guild_id: guild_id
+ } as GuildMemberRemoveEvent)
]);
}
@@ -147,7 +148,7 @@ export async function addRole(user_id: string, guild_id: string, role_id: string
var memberObj = await MemberModel.findOneAndUpdate(
{
id: user_id,
- guild_id: guild_id,
+ guild_id: guild_id
},
{ $push: { roles: role_id } }
).exec();
@@ -159,9 +160,9 @@ export async function addRole(user_id: string, guild_id: string, role_id: string
data: {
guild_id: guild_id,
user: user,
- roles: memberObj.roles,
+ roles: memberObj.roles
},
- guild_id: guild_id,
+ guild_id: guild_id
} as GuildMemberUpdateEvent);
}
@@ -174,7 +175,7 @@ export async function removeRole(user_id: string, guild_id: string, role_id: str
var memberObj = await MemberModel.findOneAndUpdate(
{
id: user_id,
- guild_id: guild_id,
+ guild_id: guild_id
},
{ $pull: { roles: role_id } }
).exec();
@@ -186,9 +187,9 @@ export async function removeRole(user_id: string, guild_id: string, role_id: str
data: {
guild_id: guild_id,
user: user,
- roles: memberObj.roles,
+ roles: memberObj.roles
},
- guild_id: guild_id,
+ guild_id: guild_id
} as GuildMemberUpdateEvent);
}
@@ -198,7 +199,7 @@ export async function changeNickname(user_id: string, guild_id: string, nickname
var memberObj = await MemberModel.findOneAndUpdate(
{
id: user_id,
- guild_id: guild_id,
+ guild_id: guild_id
},
{ nick: nickname }
).exec();
@@ -210,8 +211,8 @@ export async function changeNickname(user_id: string, guild_id: string, nickname
data: {
guild_id: guild_id,
user: user,
- nick: nickname,
+ nick: nickname
},
- guild_id: guild_id,
+ guild_id: guild_id
} as GuildMemberUpdateEvent);
}
diff --git a/src/util/Message.ts b/src/util/Message.ts
new file mode 100644
index 00000000..0d3cdac7
--- /dev/null
+++ b/src/util/Message.ts
@@ -0,0 +1,54 @@
+import { ChannelModel, MessageCreateEvent } from "@fosscord/server-util";
+import { Snowflake } from "@fosscord/server-util";
+import { MessageModel } from "@fosscord/server-util";
+import { PublicMemberProjection } from "@fosscord/server-util";
+import { toObject } from "@fosscord/server-util";
+import { getPermission } from "@fosscord/server-util";
+import { Message } from "@fosscord/server-util";
+import { HTTPError } from "lambert-server";
+import { emitEvent } from "./Event";
+// TODO: check webhook, application, system author
+
+export async function handleMessage(opts: Partial<Message>) {
+ const channel = await ChannelModel.findOne({ id: opts.channel_id }, { guild_id: true, type: true, permission_overwrites: true }).exec();
+ if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
+ // TODO: are tts messages allowed in dm channels? should permission be checked?
+
+ const permissions = await getPermission(opts.author_id, channel.guild_id, opts.channel_id, { channel });
+ permissions.hasThrow("SEND_MESSAGES");
+ if (opts.tts) permissions.hasThrow("SEND_TTS_MESSAGES");
+ if (opts.message_reference) {
+ permissions.hasThrow("READ_MESSAGE_HISTORY");
+ if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild");
+ }
+
+ if (opts.message_reference) {
+ if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
+ // TODO: should be checked if the referenced message exists?
+ }
+
+ // TODO: check and put it all in the body
+ return {
+ ...opts,
+ guild_id: channel.guild_id,
+ channel_id: opts.channel_id,
+ // TODO: generate mentions and check permissions
+ mention_channels_ids: [],
+ mention_role_ids: [],
+ mention_user_ids: [],
+ attachments: [], // TODO: message attachments
+ embeds: opts.embeds || [],
+ reactions: opts.reactions || [],
+ type: opts.type ?? 0
+ };
+}
+
+export async function sendMessage(opts: Partial<Message>) {
+ const message = await handleMessage({ ...opts, id: Snowflake.generate(), timestamp: new Date() });
+
+ const data = toObject(await new MessageModel(message).populate({ path: "member", select: PublicMemberProjection }).save());
+
+ await emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data, guild_id: message.guild_id } as MessageCreateEvent);
+
+ return data;
+}
diff --git a/src/util/instanceOf.ts b/src/util/instanceOf.ts
index e4e58092..b67bde27 100644
--- a/src/util/instanceOf.ts
+++ b/src/util/instanceOf.ts
@@ -5,7 +5,8 @@ import { Tuple } from "lambert-server";
import "missing-native-js-functions";
export const OPTIONAL_PREFIX = "$";
-export 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 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) => {
@@ -27,9 +28,9 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str
_errors: [
{
message,
- code: code || "BASE_TYPE_INVALID",
- },
- ],
+ code: code || "BASE_TYPE_INVALID"
+ }
+ ]
}))
);
}
@@ -68,7 +69,7 @@ export function instanceOf(
optional = false,
errors = {},
req,
- ref,
+ ref
}: { path?: string; optional?: boolean; errors?: any; req: Request; ref?: { key: string | number; obj: any } }
): Boolean {
if (!ref) ref = { obj: null, key: "" };
@@ -131,7 +132,7 @@ export function instanceOf(
optional,
errors: errors[i],
req,
- ref: { key: i, obj: value },
+ ref: { key: i, obj: value }
}) === true
) {
delete errors[i];
@@ -153,7 +154,7 @@ export function instanceOf(
throw new FieldError(
"BASE_TYPE_BAD_LENGTH",
req.t("common:field.BASE_TYPE_BAD_LENGTH", {
- length: `${type.min} - ${type.max}`,
+ length: `${type.min} - ${type.max}`
})
);
}
@@ -185,7 +186,7 @@ export function instanceOf(
optional: OPTIONAL,
errors: errors[newKey],
req,
- ref: { key: newKey, obj: value },
+ ref: { key: newKey, obj: value }
}) === true
) {
delete errors[newKey];
|