Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Direct Messages and other fixes #369

Merged
merged 11 commits into from
Sep 18, 2021
1,190 changes: 589 additions & 601 deletions api/assets/schemas.json

Large diffs are not rendered by default.

36 changes: 26 additions & 10 deletions api/src/routes/channels/#channel_id/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, ChannelType, ChannelPermissionOverwriteType } from "@fosscord/util";
import { Router, Response, Request } from "express";
import { route } from "@fosscord/api";
import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util";
import { Request, Response, Router } from "express";
import { handleFile, route } from "@fosscord/api";

const router: Router = Router();
// TODO: delete channel
// TODO: Get channel
Expand All @@ -16,23 +17,37 @@ router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res:
router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
const { channel_id } = req.params;

const channel = await Channel.findOneOrFail({ id: channel_id });
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });

// TODO: Dm channel "close" not delete
const data = channel;
if (channel.type === ChannelType.DM) {
const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } })
recipient.closed = true
await Promise.all([
recipient.save(),
emitEvent({ event: "CHANNEL_DELETE", data: channel, user_id: req.user_id } as ChannelDeleteEvent)
]);

await Promise.all([emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent), Channel.delete({ id: channel_id })]);
} else if (channel.type === ChannelType.GROUP_DM) {
await Channel.removeRecipientFromChannel(channel, req.user_id)
} else {
//TODO messages in this channel should be deleted before deleting the channel
await Promise.all([
Channel.delete({ id: channel_id }),
emitEvent({ event: "CHANNEL_DELETE", data: channel, channel_id } as ChannelDeleteEvent)
]);
}

res.send(data);
res.send(channel);
});

export interface ChannelModifySchema {
/**
* @maxLength 100
*/
name: string;
type: ChannelType;
name?: string;
type?: ChannelType;
topic?: string;
icon?: string | null;
bitrate?: number;
user_limit?: number;
rate_limit_per_user?: number;
Expand All @@ -53,6 +68,7 @@ export interface ChannelModifySchema {
router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => {
var payload = req.body as ChannelModifySchema;
const { channel_id } = req.params;
if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon);

const channel = await Channel.findOneOrFail({ id: channel_id });
channel.assign(payload);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Reques
data: {
channel_id,
message_id,
version: 496
version: 3763
}
} as MessageAckEvent);

Expand Down
46 changes: 37 additions & 9 deletions api/src/routes/channels/#channel_id/messages/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Router, Response, Request } from "express";
import { Attachment, Channel, ChannelType, Embed, getPermission, Message } from "@fosscord/util";
import { Attachment, Channel, ChannelType, DmChannelDTO, Embed, emitEvent, getPermission, Message, MessageCreateEvent, Recipient } from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { route } from "@fosscord/api";
import { handleMessage, postHandleMessage, route } from "@fosscord/api";
import multer from "multer";
import { sendMessage } from "@fosscord/api";
import { uploadFile } from "@fosscord/api";
import { FindManyOptions, LessThan, MoreThan } from "typeorm";

Expand Down Expand Up @@ -62,9 +61,9 @@ router.get("/", async (req: Request, res: Response) => {
if (!channel) throw new HTTPError("Channel not found", 404);

isTextChannel(channel.type);
const around = `${req.query.around}`;
const before = `${req.query.before}`;
const after = `${req.query.after}`;
const around = req.query.around ? `${req.query.around}` : undefined;
const before = req.query.before ? `${req.query.before}` : undefined;
const after = req.query.after ? `${req.query.after}` : undefined;
const limit = Number(req.query.limit) || 50;
if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100");

Expand Down Expand Up @@ -151,20 +150,49 @@ router.post(
return res.status(400).json(error);
}
}
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] })

const embeds = [];
if (body.embed) embeds.push(body.embed);
const data = await sendMessage({
let message = await handleMessage({
...body,
type: 0,
pinned: false,
author_id: req.user_id,
embeds,
channel_id,
attachments,
edited_timestamp: undefined
edited_timestamp: undefined,
timestamp: new Date()
});

return res.json(data);
message = await message.save()

await channel.assign({ last_message_id: message.id }).save()

if (channel.isDm()) {
const channel_dto = await DmChannelDTO.from(channel)

for (let recipient of channel.recipients!) {
if (recipient.closed) {
await emitEvent({
event: "CHANNEL_CREATE",
data: channel_dto.excludedRecipients([recipient.user_id]),
user_id: recipient.user_id
})
}
}

//Only one recipients should be closed here, since in group DMs the recipient is deleted not closed
await Promise.all(channel.recipients!.filter(r => r.closed).map(async r => {
r.closed = false;
return await r.save()
}));
}

await emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent)
postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error

return res.json(message);
}
);
56 changes: 54 additions & 2 deletions api/src/routes/channels/#channel_id/recipients.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,57 @@
import { Router, Response, Request } from "express";
import { Request, Response, Router } from "express";
import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util";

const router: Router = Router();
// TODO:

router.put("/:user_id", async (req: Request, res: Response) => {
const { channel_id, user_id } = req.params;
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });

if (channel.type !== ChannelType.GROUP_DM) {
const recipients = [
...channel.recipients!.map(r => r.user_id),
user_id
].unique()

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(new Recipient({ channel_id: channel_id, user_id: user_id }));
await channel.save()

await emitEvent({
event: "CHANNEL_CREATE",
data: await DmChannelDTO.from(channel, [user_id]),
user_id: user_id
});

await emitEvent({
event: "CHANNEL_RECIPIENT_ADD", data: {
channel_id: channel_id,
user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection })
}, channel_id: channel_id
} as ChannelRecipientAddEvent);
return res.sendStatus(204);
}
});

router.delete("/:user_id", async (req: Request, res: Response) => {
const { channel_id, user_id } = req.params;
const channel = await Channel.findOneOrFail({ 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.recipients!.map(r => r.user_id).includes(user_id)) {
throw DiscordApiErrors.INVALID_RECIPIENT //TODO is this the right error?
}

await Channel.removeRecipientFromChannel(channel, user_id)

return res.sendStatus(204);
});

export default router;
18 changes: 18 additions & 0 deletions api/src/routes/sticker-packs/#id/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Request, Response, Router } from "express";

const router: Router = Router();

router.get("/", async (req: Request, res: Response) => {
//TODO
res.json({
id: "",
stickers: [],
name: "",
sku_id: "",
cover_sticker_id: "",
description: "",
banner_asset_id: ""
}).status(200);
});

export default router;
10 changes: 10 additions & 0 deletions api/src/routes/sticker-packs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Request, Response, Router } from "express";

const router: Router = Router();

router.get("/", async (req: Request, res: Response) => {
//TODO
res.json({ sticker_packs: [] }).status(200);
});

export default router;
1 change: 1 addition & 0 deletions api/src/routes/users/#id/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req
connected_accounts: user.connected_accounts,
premium_guild_since: null, // TODO
premium_since: null, // TODO
mutual_guilds: [], // TODO {id: "", nick: null} when ?with_mutual_guilds=true
user: {
username: user.username,
discriminator: user.discriminator,
Expand Down
36 changes: 5 additions & 31 deletions api/src/routes/users/@me/channels.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { Router, Request, Response } from "express";
import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent, Recipient } from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { Request, Response, Router } from "express";
import { Recipient, DmChannelDTO, Channel } from "@fosscord/util";
import { route } from "@fosscord/api";
import { In } from "typeorm";

const router: Router = Router();

router.get("/", route({}), async (req: Request, res: Response) => {
const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel"] });

res.json(recipients.map((x) => x.channel));
const recipients = await Recipient.find({ where: { user_id: req.user_id, closed: false }, relations: ["channel", "channel.recipients"] });
res.json(await Promise.all(recipients.map(r => DmChannelDTO.from(r.channel, [req.user_id]))));
});

export interface DmChannelCreateSchema {
Expand All @@ -19,30 +16,7 @@ export interface DmChannelCreateSchema {

router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => {
const body = req.body as DmChannelCreateSchema;

body.recipients = body.recipients.filter((x) => x !== req.user_id).unique();

const recipients = await User.find({ where: body.recipients.map((x) => ({ id: x })) });

if (recipients.length !== body.recipients.length) {
throw new HTTPError("Recipient/s not found");
}

const type = body.recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM;
const name = trimSpecial(body.name);

const channel = await new Channel({
name,
type,
// owner_id only for group dm channels
created_at: new Date(),
last_message_id: null,
recipients: [...body.recipients.map((x) => new Recipient({ user_id: x })), new Recipient({ user_id: req.user_id })]
}).save();

await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent);

res.json(channel);
res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
});

export default router;
19 changes: 16 additions & 3 deletions api/src/routes/users/@me/relationships.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,19 @@ const router = Router();
const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection];

router.get("/", route({}), async (req: Request, res: Response) => {
const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships"] });
const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: ["relationships", "relationships.to"] });

//TODO DTO
const related_users = user.relationships.map(r => {
return {
id: r.to.id,
type: r.type,
nickname: null,
user: r.to.toPublicUser(),
}
})

return res.json(user.relationships);
return res.json(related_users);
});

export interface RelationshipPutSchema {
Expand Down Expand Up @@ -48,7 +58,10 @@ router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request,
await User.findOneOrFail({
relations: ["relationships", "relationships.to"],
select: userProjection,
where: req.body as { discriminator: string; username: string }
where: {
discriminator: String(req.body.discriminator,).padStart(4, '0'), //Discord send the discriminator as integer, we need to add leading zeroes
username: req.body.username
}
}),
req.body.type
);
Expand Down
3 changes: 3 additions & 0 deletions cdn/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export class CDNServer extends Server {
this.app.use("/team-icons/", avatarsRoute);
this.log("verbose", "[Server] Route /team-icons registered");

this.app.use("/channel-icons/", avatarsRoute);
this.log("verbose", "[Server] Route /channel-icons registered");

return super.start();
}

Expand Down
4 changes: 2 additions & 2 deletions gateway/src/listener/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function setupListener(this: WebSocket) {
});
const guilds = members.map((x) => x.guild);
const recipients = await Recipient.find({
where: { user_id: this.user_id },
where: { user_id: this.user_id, closed: false },
relations: ["channel"],
});
const dm_channels = recipients.map((x) => x.channel);
Expand Down Expand Up @@ -116,7 +116,7 @@ async function consume(this: WebSocket, opts: EventOpts) {
.has("VIEW_CHANNEL")
)
return;
// TODO: check if user has permission to channel
//No break needed here, we need to call the listenEvent function below
case "GUILD_CREATE":
this.events[id] = await listenEvent(id, consumer, listenOpts);
break;
Expand Down
Loading