mirror of
https://github.com/spacebarchat/server.git
synced 2026-04-14 05:06:04 +00:00
Merge branch 'typeorm' into typeorm
This commit is contained in:
@@ -18,9 +18,9 @@ export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//;
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user_id: any;
|
||||
user_id: string;
|
||||
user_bot: boolean;
|
||||
token: any;
|
||||
token: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export async function Authentication(req: Request, res: Response, next: NextFunc
|
||||
req.user_id = decoded.id;
|
||||
req.user_bot = user.bot;
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(new HTTPError(error.toString(), 400));
|
||||
} catch (error: any) {
|
||||
return next(new HTTPError(error?.toString(), 400));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { EntityNotFoundError } from "typeorm";
|
||||
import { FieldError } from "../util/instanceOf";
|
||||
import {ApiError} from "../util/ApiError";
|
||||
|
||||
@@ -19,12 +20,18 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
|
||||
message = error.message;
|
||||
httpcode = error.httpStatus;
|
||||
}
|
||||
else if (error instanceof FieldError) {
|
||||
else if (error instanceof EntityNotFoundError) {
|
||||
message = `${(error as any).stringifyTarget} can not be found`;
|
||||
code = 404;
|
||||
} else if (error instanceof FieldError) {
|
||||
code = Number(error.code);
|
||||
message = error.message;
|
||||
errors = error.errors;
|
||||
} else {
|
||||
console.error(`[Error] ${code} ${req.url}`, errors || error, "body:", req.body);
|
||||
|
||||
if (req.server?.options?.production) {
|
||||
// don't expose internal errors to the user, instead human errors should be thrown as HTTPError
|
||||
message = "Internal Server Error";
|
||||
}
|
||||
code = httpcode = 500;
|
||||
@@ -32,8 +39,6 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
|
||||
|
||||
if (httpcode > 511) httpcode = 400;
|
||||
|
||||
console.error(`[Error] ${code} ${req.url}`, errors || error, "body:", req.body);
|
||||
|
||||
res.status(httpcode).json({ code: code, message, errors });
|
||||
} catch (error) {
|
||||
console.error(`[Internal Server Error] 500`, error);
|
||||
|
||||
@@ -181,10 +181,11 @@ router.post(
|
||||
// appearently discord doesn't save the date of birth and just calculate if nsfw is allowed
|
||||
// if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false
|
||||
|
||||
const user = await new User({
|
||||
const user = {
|
||||
created_at: new Date(),
|
||||
username: adjusted_username,
|
||||
discriminator,
|
||||
id: Snowflake.generate(),
|
||||
bot: false,
|
||||
system: false,
|
||||
desktop: false,
|
||||
@@ -204,8 +205,10 @@ router.post(
|
||||
hash: adjusted_password,
|
||||
valid_tokens_since: new Date()
|
||||
},
|
||||
settings: defaultSettings
|
||||
}).save();
|
||||
settings: defaultSettings,
|
||||
fingerprints: []
|
||||
};
|
||||
await User.insert(user);
|
||||
|
||||
return res.json({ token: await generateToken(user.id) });
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response
|
||||
body = { flags: body.flags }; // admins can only suppress embeds of other messages
|
||||
}
|
||||
|
||||
const opts = await handleMessage({
|
||||
const new_message = await handleMessage({
|
||||
// TODO: should be message_reference overridable?
|
||||
// @ts-ignore
|
||||
message_reference: message.message_reference,
|
||||
...body,
|
||||
author_id: message.author_id,
|
||||
@@ -28,10 +30,9 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response
|
||||
id: message_id,
|
||||
edited_timestamp: new Date()
|
||||
});
|
||||
message.assign(opts);
|
||||
|
||||
await Promise.all([
|
||||
message.save(),
|
||||
new_message.save(),
|
||||
await emitEvent({
|
||||
event: "MESSAGE_UPDATE",
|
||||
channel_id,
|
||||
|
||||
@@ -31,10 +31,7 @@ export function isTextChannel(type: ChannelType): boolean {
|
||||
// get messages
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
const channel_id = req.params.channel_id;
|
||||
const channel = await Channel.findOneOrFail(
|
||||
{ id: channel_id },
|
||||
{ select: ["guild_id", "type", "permission_overwrites", "recipient_ids", "owner_id"] }
|
||||
); // lean is needed, because we don't want to populate .recipients that also auto deletes .recipient_ids
|
||||
const channel = await Channel.findOneOrFail({ id: channel_id });
|
||||
if (!channel) throw new HTTPError("Channel not found", 404);
|
||||
|
||||
isTextChannel(channel.type);
|
||||
@@ -56,7 +53,12 @@ router.get("/", async (req: Request, res: Response) => {
|
||||
permissions.hasThrow("VIEW_CHANNEL");
|
||||
if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
|
||||
|
||||
var query: FindManyOptions<Message> & { where: { id?: any } } = { order: { id: "DESC" }, take: limit, where: { channel_id } };
|
||||
var query: FindManyOptions<Message> & { where: { id?: any } } = {
|
||||
order: { id: "DESC" },
|
||||
take: limit,
|
||||
where: { channel_id },
|
||||
relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"]
|
||||
};
|
||||
|
||||
if (after) query.where.id = MoreThan(after);
|
||||
else if (before) query.where.id = LessThan(before);
|
||||
@@ -69,18 +71,20 @@ router.get("/", async (req: Request, res: Response) => {
|
||||
|
||||
const messages = await Message.find(query);
|
||||
|
||||
return res.json(messages).map((x) => {
|
||||
(x.reactions || []).forEach((x: any) => {
|
||||
return res.json(
|
||||
messages.map((x) => {
|
||||
(x.reactions || []).forEach((x: any) => {
|
||||
// @ts-ignore
|
||||
if ((x.user_ids || []).includes(req.user_id)) x.me = true;
|
||||
// @ts-ignore
|
||||
delete x.user_ids;
|
||||
});
|
||||
// @ts-ignore
|
||||
if ((x.user_ids || []).includes(req.user_id)) x.me = true;
|
||||
// @ts-ignore
|
||||
delete x.user_ids;
|
||||
});
|
||||
// @ts-ignore
|
||||
if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null };
|
||||
if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null };
|
||||
|
||||
return x;
|
||||
});
|
||||
return x;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: config max upload size
|
||||
@@ -136,5 +140,5 @@ router.post("/", messageUpload.single("file"), async (req: Request, res: Respons
|
||||
edited_timestamp: undefined
|
||||
});
|
||||
|
||||
return res.send(data);
|
||||
return res.json(data);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ router.put("/:message_id", async (req: Request, res: Response) => {
|
||||
// * in dm channels anyone can pin messages -> only check for guilds
|
||||
if (message.guild_id) permission.hasThrow("MANAGE_MESSAGES");
|
||||
|
||||
const pinned_count = await Message.count({ channel_id, pinned: true });
|
||||
const pinned_count = await Message.count({ channel: { id: channel_id }, pinned: true });
|
||||
const { maxPins } = Config.get().limits.channel;
|
||||
if (pinned_count >= maxPins) throw new HTTPError("Max pin count reached: " + maxPins);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ router.post("/", async (req: Request, res: Response) => {
|
||||
channel_id: channel_id,
|
||||
data: {
|
||||
// this is the paylod
|
||||
member: { ...member, roles: member.role_ids },
|
||||
member: { ...member, roles: member.roles.map((x) => x.id) },
|
||||
channel_id,
|
||||
timestamp,
|
||||
user_id,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { HTTPError } from "lambert-server";
|
||||
import { ChannelModifySchema } from "../../../schema/Channel";
|
||||
|
||||
import { check } from "../../../util/instanceOf";
|
||||
import { createChannel } from "../../../util/Channel";
|
||||
const router = Router();
|
||||
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
@@ -22,7 +21,7 @@ router.post("/", check(ChannelModifySchema), async (req: Request, res: Response)
|
||||
const { guild_id } = req.params;
|
||||
const body = req.body as ChannelModifySchema;
|
||||
|
||||
const channel = await createChannel({ ...body, guild_id }, req.user_id);
|
||||
const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id);
|
||||
|
||||
res.status(201).json(channel);
|
||||
});
|
||||
|
||||
@@ -14,8 +14,8 @@ router.get("/", async (req: Request, res: Response) => {
|
||||
|
||||
const [guild, member_count, member] = await Promise.all([
|
||||
Guild.findOneOrFail({ id: guild_id }),
|
||||
Member.count({ guild_id: guild_id, id: req.user_id }),
|
||||
Member.findOneOrFail(req.user_id)
|
||||
Member.count({ guild: { id: guild_id }, id: req.user_id }),
|
||||
Member.findOneOrFail({ id: req.user_id })
|
||||
]);
|
||||
if (!member_count) throw new HTTPError("You are not a member of the guild you are trying to access", 401);
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response)
|
||||
emitEvent({
|
||||
event: "GUILD_MEMBER_UPDATE",
|
||||
guild_id,
|
||||
data: { ...member, roles: member.role_ids }
|
||||
data: { ...member, roles: member.roles.map((x) => x.id) }
|
||||
} as GuildMemberUpdateEvent)
|
||||
]);
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res
|
||||
const perms = await getPermission(req.user_id, guild_id);
|
||||
perms.hasThrow("MANAGE_ROLES");
|
||||
|
||||
const role = new Role({ ...body, role_id, guild_id, permissions: perms.bitfield & (body.permissions || 0n) });
|
||||
const role = new Role({ ...body, id: role_id, guild_id, permissions: perms.bitfield & (body.permissions || 0n) });
|
||||
|
||||
await Promise.all([
|
||||
role.save(),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Router, Request, Response } from "express";
|
||||
import { Role, Guild, Snowflake, Config, User, Member } from "@fosscord/util";
|
||||
import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { check } from "./../../util/instanceOf";
|
||||
import { GuildCreateSchema } from "../../schema/Guild";
|
||||
import { createChannel } from "../../util/Channel";
|
||||
|
||||
const router: Router = Router();
|
||||
|
||||
@@ -13,14 +12,15 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
|
||||
const body = req.body as GuildCreateSchema;
|
||||
|
||||
const { maxGuilds } = Config.get().limits.user;
|
||||
const guild_count = await Member.count({ where: { id: req.user_id } });
|
||||
const guild_count = await Member.count({ id: req.user_id });
|
||||
if (guild_count >= maxGuilds) {
|
||||
throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403);
|
||||
}
|
||||
|
||||
const guild_id = Snowflake.generate();
|
||||
const guild = new Guild(
|
||||
{
|
||||
|
||||
const [guild, role] = await Promise.all([
|
||||
Guild.insert({
|
||||
name: body.name,
|
||||
region: Config.get().regions.default,
|
||||
owner_id: req.user_id,
|
||||
@@ -38,7 +38,7 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
|
||||
preferred_locale: "en-US",
|
||||
premium_subscription_count: 0,
|
||||
premium_tier: 0,
|
||||
system_channel_flags: "0",
|
||||
system_channel_flags: 0,
|
||||
unavailable: false,
|
||||
verification_level: 0,
|
||||
welcome_screen: {
|
||||
@@ -47,11 +47,9 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
|
||||
welcome_channels: []
|
||||
},
|
||||
widget_enabled: false
|
||||
},
|
||||
{ id: guild_id }
|
||||
);
|
||||
const role = new Role(
|
||||
{
|
||||
}),
|
||||
Role.insert({
|
||||
id: guild_id,
|
||||
guild_id: guild_id,
|
||||
color: 0,
|
||||
hoist: false,
|
||||
@@ -59,15 +57,9 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
|
||||
mentionable: false,
|
||||
name: "@everyone",
|
||||
permissions: String("2251804225"),
|
||||
position: 0,
|
||||
tags: null
|
||||
},
|
||||
{
|
||||
id: guild_id
|
||||
}
|
||||
);
|
||||
|
||||
await Promise.all([guild.save(), role.save()]);
|
||||
position: 0
|
||||
})
|
||||
]);
|
||||
|
||||
if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }];
|
||||
|
||||
@@ -86,13 +78,18 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) =
|
||||
// TODO: should we abort if parent_id is a category? (to disallow sub category channels)
|
||||
var parent_id = ids.get(x.parent_id);
|
||||
|
||||
return createChannel({ ...x, guild_id, id, parent_id }, req.user_id, { keepId: true, skipExistsCheck: true });
|
||||
return Channel.createChannel({ ...x, guild_id, id, parent_id }, req.user_id, {
|
||||
keepId: true,
|
||||
skipExistsCheck: true,
|
||||
skipPermissionCheck: true,
|
||||
skipEventEmit: true
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
await Member.addToGuild(req.user_id, guild_id);
|
||||
|
||||
res.status(201).json({ id: guild.id });
|
||||
res.status(201).json({ id: guild_id });
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -5,13 +5,14 @@ import { HTTPError } from "lambert-server";
|
||||
import { DmChannelCreateSchema } from "../../../schema/Channel";
|
||||
import { check } from "../../../util/instanceOf";
|
||||
import { In } from "typeorm";
|
||||
import { Recipient } from "../../../../../util/dist/entities/Recipient";
|
||||
|
||||
const router: Router = Router();
|
||||
|
||||
router.get("/", async (req: Request, res: Response) => {
|
||||
var channels = await Channel.find({ recipient_ids: req.user_id });
|
||||
const recipients = await Recipient.find({ where: { id: req.user_id }, relations: ["channel"] });
|
||||
|
||||
res.json(channels);
|
||||
res.json(recipients.map((x) => x.channel));
|
||||
});
|
||||
|
||||
router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Response) => {
|
||||
@@ -34,7 +35,7 @@ router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Respons
|
||||
owner_id: req.user_id,
|
||||
created_at: new Date(),
|
||||
last_message_id: null,
|
||||
recipient_ids: [...body.recipients, req.user_id]
|
||||
recipients: [...body.recipients.map((x) => new Recipient({ id: x })), new Recipient({ id: req.user_id })]
|
||||
}).save();
|
||||
|
||||
await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent);
|
||||
|
||||
@@ -5,7 +5,7 @@ import bcrypt from "bcrypt";
|
||||
const router = Router();
|
||||
|
||||
router.post("/", async (req: Request, res: Response) => {
|
||||
const user = await User.findOneOrFail(req.user_id); //User object
|
||||
const user = await User.findOneOrFail({ id: req.user_id }); //User object
|
||||
let correctpass = true;
|
||||
|
||||
if (user.data.hash) {
|
||||
|
||||
@@ -37,7 +37,7 @@ router.patch("/", check(UserModifySchema), async (req: Request, res: Response) =
|
||||
if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string);
|
||||
if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
|
||||
|
||||
const user = await new User({ ...body }, { id: req.user_id }).save();
|
||||
const user = await new User({ ...body, id: req.user_id }).save();
|
||||
// TODO: dispatch user update event
|
||||
|
||||
res.json(user);
|
||||
|
||||
@@ -26,7 +26,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
|
||||
const id = friend.id;
|
||||
if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend");
|
||||
|
||||
const user = await User.findOneOrFail(req.user_id, { relations: ["relationships"], select: userProjection });
|
||||
const user = await User.findOneOrFail({ id: req.user_id }, { relations: ["relationships"], select: userProjection });
|
||||
|
||||
var relationship = user.relationships.find((x) => x.id === id);
|
||||
const friendRequest = friend.relationships.find((x) => x.id === req.user_id);
|
||||
@@ -67,8 +67,8 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ
|
||||
return res.sendStatus(204);
|
||||
}
|
||||
|
||||
var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming }, { id: req.user_id });
|
||||
var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing }, { id });
|
||||
var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, id: req.user_id });
|
||||
var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing, id });
|
||||
|
||||
if (friendRequest) {
|
||||
if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
|
||||
@@ -113,7 +113,7 @@ router.put("/:id", check({ $type: new Length(Number, 1, 4) }), async (req: Reque
|
||||
return await updateRelationship(
|
||||
req,
|
||||
res,
|
||||
await User.findOneOrFail(req.params.id, { relations: ["relationships"], select: userProjection }),
|
||||
await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }),
|
||||
req.body.type
|
||||
);
|
||||
});
|
||||
@@ -135,8 +135,8 @@ router.delete("/:id", async (req: Request, res: Response) => {
|
||||
const { id } = req.params;
|
||||
if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
|
||||
|
||||
const user = await User.findOneOrFail(req.user_id, { select: userProjection, relations: ["relationships"] });
|
||||
const friend = await User.findOneOrFail(id, { select: userProjection, relations: ["relationships"] });
|
||||
const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] });
|
||||
const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] });
|
||||
|
||||
const relationship = user.relationships.find((x) => x.id === id);
|
||||
const friendRequest = friend.relationships.find((x) => x.id === req.user_id);
|
||||
|
||||
@@ -81,7 +81,7 @@ export interface MessageCreateSchema {
|
||||
message_id: string;
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
fail_if_not_exists: boolean;
|
||||
fail_if_not_exists?: boolean;
|
||||
};
|
||||
payload_json?: string;
|
||||
file?: any;
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import { ChannelCreateEvent, Channel, ChannelType, emitEvent, getPermission, Snowflake } from "@fosscord/util";
|
||||
import { HTTPError } from "lambert-server";
|
||||
|
||||
// TODO: DM channel
|
||||
export async function createChannel(
|
||||
channel: Partial<Channel>,
|
||||
user_id: string = "0",
|
||||
opts?: {
|
||||
keepId?: boolean;
|
||||
skipExistsCheck?: boolean;
|
||||
}
|
||||
) {
|
||||
// Always check if user has permission first
|
||||
const permissions = await getPermission(user_id, channel.guild_id);
|
||||
permissions.hasThrow("MANAGE_CHANNELS");
|
||||
|
||||
switch (channel.type) {
|
||||
case ChannelType.GUILD_TEXT:
|
||||
case ChannelType.GUILD_VOICE:
|
||||
if (channel.parent_id && !opts?.skipExistsCheck) {
|
||||
const exists = await Channel.findOneOrFail({ id: channel.parent_id });
|
||||
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");
|
||||
}
|
||||
break;
|
||||
case ChannelType.GUILD_CATEGORY:
|
||||
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");
|
||||
}
|
||||
|
||||
if (!channel.permission_overwrites) channel.permission_overwrites = [];
|
||||
// TODO: auto generate position
|
||||
|
||||
channel = await new Channel({
|
||||
...channel,
|
||||
...(!opts?.keepId && { id: Snowflake.generate() }),
|
||||
created_at: new Date(),
|
||||
// @ts-ignore
|
||||
recipient_ids: null
|
||||
}).save();
|
||||
|
||||
await emitEvent({ event: "CHANNEL_CREATE", data: channel, guild_id: channel.guild_id } as ChannelCreateEvent);
|
||||
|
||||
return channel;
|
||||
}
|
||||
@@ -13,11 +13,16 @@ import {
|
||||
Role,
|
||||
EVERYONE_MENTION,
|
||||
HERE_MENTION,
|
||||
MessageType
|
||||
MessageType,
|
||||
User,
|
||||
Application,
|
||||
Webhook,
|
||||
Attachment
|
||||
} from "@fosscord/util";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import fetch from "node-fetch";
|
||||
import cheerio from "cheerio";
|
||||
import { MessageCreateSchema } from "../schema/Message";
|
||||
|
||||
// TODO: check webhook, application, system author
|
||||
|
||||
@@ -34,17 +39,37 @@ const DEFAULT_FETCH_OPTIONS: any = {
|
||||
method: "GET"
|
||||
};
|
||||
|
||||
export async function handleMessage(opts: Partial<Message>): Promise<Message> {
|
||||
const channel = await Channel.findOneOrFail(
|
||||
{ id: opts.channel_id },
|
||||
{ select: ["guild_id", "type", "permission_overwrites", "recipient_ids", "owner_id"] }
|
||||
); // lean is needed, because we don't want to populate .recipients that also auto deletes .recipient_ids
|
||||
export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
const channel = await Channel.findOneOrFail({ where: { id: opts.channel_id }, relations: ["recipients"] });
|
||||
if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404);
|
||||
// TODO: are tts messages allowed in dm channels? should permission be checked?
|
||||
|
||||
// @ts-ignore
|
||||
const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id, { channel });
|
||||
const message = new Message({
|
||||
...opts,
|
||||
guild_id: channel.guild_id,
|
||||
channel_id: opts.channel_id,
|
||||
attachments: opts.attachments || [],
|
||||
embeds: opts.embeds || [],
|
||||
reactions: /*opts.reactions ||*/ [],
|
||||
type: opts.type ?? 0
|
||||
});
|
||||
|
||||
// TODO: are tts messages allowed in dm channels? should permission be checked?
|
||||
if (opts.author_id) {
|
||||
message.author = await User.getPublicUser(opts.author_id);
|
||||
}
|
||||
if (opts.application_id) {
|
||||
message.application = await Application.findOneOrFail({ id: opts.application_id });
|
||||
}
|
||||
if (opts.webhook_id) {
|
||||
message.webhook = await Webhook.findOneOrFail({ id: opts.webhook_id });
|
||||
}
|
||||
|
||||
const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id);
|
||||
permission.hasThrow("SEND_MESSAGES");
|
||||
if (permission.cache.member) {
|
||||
message.member = permission.cache.member;
|
||||
}
|
||||
|
||||
if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES");
|
||||
if (opts.message_reference) {
|
||||
permission.hasThrow("READ_MESSAGE_HISTORY");
|
||||
@@ -52,10 +77,11 @@ export async function handleMessage(opts: Partial<Message>): Promise<Message> {
|
||||
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?
|
||||
// @ts-ignore
|
||||
opts.type = MessageType.REPLY;
|
||||
message.type = MessageType.REPLY;
|
||||
}
|
||||
|
||||
if (!opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.stickers?.length && !opts.activity) {
|
||||
// TODO: stickers/activity
|
||||
if (!opts.content && !opts.embeds?.length && !opts.attachments?.length) {
|
||||
throw new HTTPError("Empty messages are not allowed", 50006);
|
||||
}
|
||||
|
||||
@@ -64,10 +90,9 @@ export async function handleMessage(opts: Partial<Message>): Promise<Message> {
|
||||
var mention_role_ids = [] as string[];
|
||||
var mention_user_ids = [] as string[];
|
||||
var mention_everyone = false;
|
||||
var mention_everyone = false;
|
||||
|
||||
if (content) {
|
||||
content = content.trim();
|
||||
message.content = content.trim();
|
||||
for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) {
|
||||
if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(mention);
|
||||
}
|
||||
@@ -90,21 +115,14 @@ export async function handleMessage(opts: Partial<Message>): Promise<Message> {
|
||||
}
|
||||
}
|
||||
|
||||
message.mention_channels = mention_channel_ids.map((x) => new Channel({ id: x }));
|
||||
message.mention_roles = mention_role_ids.map((x) => new Role({ id: x }));
|
||||
message.mentions = mention_user_ids.map((x) => new User({ id: x }));
|
||||
message.mention_everyone = mention_everyone;
|
||||
|
||||
// TODO: check and put it all in the body
|
||||
|
||||
return {
|
||||
...opts,
|
||||
guild_id: channel.guild_id,
|
||||
channel_id: opts.channel_id,
|
||||
mention_channel_ids,
|
||||
mention_role_ids,
|
||||
mention_user_ids,
|
||||
mention_everyone,
|
||||
attachments: opts.attachments || [],
|
||||
embeds: opts.embeds || [],
|
||||
reactions: opts.reactions || [],
|
||||
type: opts.type ?? 0
|
||||
} as Message;
|
||||
return message;
|
||||
}
|
||||
|
||||
// TODO: cache link result in db
|
||||
@@ -160,14 +178,29 @@ export async function postHandleMessage(message: Message) {
|
||||
]);
|
||||
}
|
||||
|
||||
export async function sendMessage(opts: Partial<Message>) {
|
||||
const message = await handleMessage({ ...opts, id: Snowflake.generate(), timestamp: new Date() });
|
||||
export async function sendMessage(opts: MessageOptions) {
|
||||
const message = await handleMessage({ ...opts, timestamp: new Date() });
|
||||
|
||||
const data = await new Message(message).save();
|
||||
await Promise.all([
|
||||
message.save(),
|
||||
emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message.toJSON() } as MessageCreateEvent)
|
||||
]);
|
||||
|
||||
await emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data } as MessageCreateEvent);
|
||||
postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error
|
||||
|
||||
postHandleMessage(data).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error
|
||||
|
||||
return data;
|
||||
return message;
|
||||
}
|
||||
|
||||
interface MessageOptions extends MessageCreateSchema {
|
||||
id?: string;
|
||||
type?: MessageType;
|
||||
pinned?: boolean;
|
||||
author_id?: string;
|
||||
webhook_id?: string;
|
||||
application_id?: string;
|
||||
embeds?: Embed[];
|
||||
channel_id?: string;
|
||||
attachments?: Attachment[];
|
||||
edited_timestamp?: Date;
|
||||
timestamp?: Date;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user