mirror of
https://github.com/spacebarchat/server.git
synced 2024-11-07 03:12:39 +01:00
Merge pull request #369 from AlTech98/fix-dm
This commit is contained in:
commit
03afedc627
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
@ -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;
|
||||
@ -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);
|
||||
|
@ -26,7 +26,7 @@ router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Reques
|
||||
data: {
|
||||
channel_id,
|
||||
message_id,
|
||||
version: 496
|
||||
version: 3763
|
||||
}
|
||||
} as MessageAckEvent);
|
||||
|
||||
|
@ -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";
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -151,10 +150,11 @@ 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,
|
||||
@ -162,9 +162,37 @@ router.post(
|
||||
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);
|
||||
}
|
||||
);
|
||||
|
@ -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
api/src/routes/sticker-packs/#id/index.ts
Normal file
18
api/src/routes/sticker-packs/#id/index.ts
Normal file
@ -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
api/src/routes/sticker-packs/index.ts
Normal file
10
api/src/routes/sticker-packs/index.ts
Normal file
@ -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;
|
@ -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,
|
||||
|
@ -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 {
|
||||
@ -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;
|
||||
|
@ -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"] });
|
||||
|
||||
return res.json(user.relationships);
|
||||
//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(related_users);
|
||||
});
|
||||
|
||||
export interface RelationshipPutSchema {
|
||||
@ -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
|
||||
);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
@ -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;
|
||||
|
@ -88,20 +88,17 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
||||
const user_guild_settings_entries = members.map((x) => x.settings);
|
||||
|
||||
const recipients = await Recipient.find({
|
||||
where: { user_id: this.user_id },
|
||||
where: { user_id: this.user_id, closed: false },
|
||||
relations: ["channel", "channel.recipients", "channel.recipients.user"],
|
||||
// TODO: public user selection
|
||||
});
|
||||
const channels = recipients.map((x) => {
|
||||
// @ts-ignore
|
||||
x.channel.recipients = x.channel.recipients?.map((x) => x.user);
|
||||
// @ts-ignore
|
||||
users = users.concat(x.channel.recipients);
|
||||
if (x.channel.type === ChannelType.DM) {
|
||||
x.channel.recipients = [
|
||||
// @ts-ignore
|
||||
x.channel.recipients.find((x) => x.id !== this.user_id),
|
||||
];
|
||||
//TODO is this needed? check if users in group dm that are not friends are sent in the READY event
|
||||
//users = users.concat(x.channel.recipients);
|
||||
if (x.channel.isDm()) {
|
||||
x.channel.recipients = x.channel.recipients!.filter((x) => x.id !== this.user_id);
|
||||
}
|
||||
return x.channel;
|
||||
});
|
||||
@ -111,16 +108,19 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
||||
});
|
||||
if (!user) return this.close(CLOSECODES.Authentication_failed);
|
||||
|
||||
const public_user = {
|
||||
username: user.username,
|
||||
discriminator: user.discriminator,
|
||||
id: user.id,
|
||||
public_flags: user.public_flags,
|
||||
avatar: user.avatar,
|
||||
bot: user.bot,
|
||||
bio: user.bio,
|
||||
for (let relation of user.relationships) {
|
||||
const related_user = relation.to
|
||||
const public_related_user = {
|
||||
username: related_user.username,
|
||||
discriminator: related_user.discriminator,
|
||||
id: related_user.id,
|
||||
public_flags: related_user.public_flags,
|
||||
avatar: related_user.avatar,
|
||||
bot: related_user.bot,
|
||||
bio: related_user.bio,
|
||||
};
|
||||
users.push(public_user);
|
||||
users.push(public_related_user);
|
||||
}
|
||||
|
||||
const session_id = genSessionId();
|
||||
this.session_id = session_id; //Set the session of the WebSocket object
|
||||
@ -201,7 +201,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
||||
// @ts-ignore
|
||||
experiments: experiments, // TODO
|
||||
guild_join_requests: [], // TODO what is this?
|
||||
users: users.unique(), // TODO
|
||||
users: users.unique(),
|
||||
merged_members: merged_members,
|
||||
// shard // TODO: only for bots sharding
|
||||
// application // TODO for applications
|
||||
|
@ -21,5 +21,6 @@ export default {
|
||||
8: onRequestGuildMembers,
|
||||
// 9: Invalid Session
|
||||
// 10: Hello
|
||||
// 13: Dm_update
|
||||
14: onLazyRequest,
|
||||
};
|
||||
|
35
util/src/dtos/DmChannelDTO.ts
Normal file
35
util/src/dtos/DmChannelDTO.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { MinimalPublicUserDTO } from "./UserDTO";
|
||||
import { Channel, PublicUserProjection, User } from "../entities";
|
||||
|
||||
export class DmChannelDTO {
|
||||
icon: string | null;
|
||||
id: string;
|
||||
last_message_id: string | null;
|
||||
name: string | null;
|
||||
origin_channel_id: string | null;
|
||||
owner_id?: string;
|
||||
recipients: MinimalPublicUserDTO[];
|
||||
type: number;
|
||||
|
||||
static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) {
|
||||
const obj = new DmChannelDTO()
|
||||
obj.icon = channel.icon || null
|
||||
obj.id = channel.id
|
||||
obj.last_message_id = channel.last_message_id || null
|
||||
obj.name = channel.name || null
|
||||
obj.origin_channel_id = origin_channel_id || null
|
||||
obj.owner_id = channel.owner_id
|
||||
obj.type = channel.type
|
||||
obj.recipients = (await Promise.all(channel.recipients!.filter(r => !excluded_recipients.includes(r.user_id)).map(async r => {
|
||||
return await User.findOneOrFail({ where: { id: r.user_id }, select: PublicUserProjection })
|
||||
}))).map(u => new MinimalPublicUserDTO(u))
|
||||
return obj
|
||||
}
|
||||
|
||||
excludedRecipients(excluded_recipients: string[]): DmChannelDTO {
|
||||
return {
|
||||
...this,
|
||||
recipients: this.recipients.filter(r => !excluded_recipients.includes(r.id))
|
||||
}
|
||||
}
|
||||
}
|
17
util/src/dtos/UserDTO.ts
Normal file
17
util/src/dtos/UserDTO.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { User } from "../entities";
|
||||
|
||||
export class MinimalPublicUserDTO {
|
||||
avatar?: string | null;
|
||||
discriminator: string;
|
||||
id: string;
|
||||
public_flags: number;
|
||||
username: string;
|
||||
|
||||
constructor(user: User) {
|
||||
this.avatar = user.avatar
|
||||
this.discriminator = user.discriminator
|
||||
this.id = user.id
|
||||
this.public_flags = user.public_flags
|
||||
this.username = user.username
|
||||
}
|
||||
}
|
2
util/src/dtos/index.ts
Normal file
2
util/src/dtos/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./DmChannelDTO";
|
||||
export * from "./UserDTO";
|
@ -1,12 +1,13 @@
|
||||
import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Guild } from "./Guild";
|
||||
import { Message } from "./Message";
|
||||
import { User } from "./User";
|
||||
import { PublicUserProjection, User } from "./User";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { emitEvent, getPermission, Snowflake } from "../util";
|
||||
import { ChannelCreateEvent } from "../interfaces";
|
||||
import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial } from "../util";
|
||||
import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
|
||||
import { Recipient } from "./Recipient";
|
||||
import { DmChannelDTO } from "../dtos";
|
||||
import { Message } from "./Message";
|
||||
|
||||
export enum ChannelType {
|
||||
GUILD_TEXT = 0, // a text channel within a server
|
||||
@ -31,6 +32,9 @@ export class Channel extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
name?: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
icon?: string | null;
|
||||
|
||||
@Column({ type: "simple-enum", enum: ChannelType })
|
||||
type: ChannelType;
|
||||
|
||||
@ -38,13 +42,8 @@ export class Channel extends BaseClass {
|
||||
recipients?: Recipient[];
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((channel: Channel) => channel.last_message)
|
||||
last_message_id: string;
|
||||
|
||||
@JoinColumn({ name: "last_message_id" })
|
||||
@ManyToOne(() => Message)
|
||||
last_message?: Message;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((channel: Channel) => channel.guild)
|
||||
guild_id?: string;
|
||||
@ -100,7 +99,6 @@ export class Channel extends BaseClass {
|
||||
@Column({ nullable: true })
|
||||
topic?: string;
|
||||
|
||||
// TODO: DM channel
|
||||
static async createChannel(
|
||||
channel: Partial<Channel>,
|
||||
user_id: string = "0",
|
||||
@ -162,6 +160,117 @@ export class Channel extends BaseClass {
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) {
|
||||
recipients = recipients.unique().filter((x) => x !== creator_user_id);
|
||||
const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });
|
||||
|
||||
// TODO: check config for max number of recipients
|
||||
if (otherRecipientsUsers.length !== recipients.length) {
|
||||
throw new HTTPError("Recipient/s not found");
|
||||
}
|
||||
|
||||
const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM;
|
||||
|
||||
let channel = null;
|
||||
|
||||
const channelRecipients = [...recipients, creator_user_id]
|
||||
|
||||
const userRecipients = await Recipient.find({ where: { user_id: creator_user_id }, relations: ["channel", "channel.recipients"] })
|
||||
|
||||
for (let ur of userRecipients) {
|
||||
let re = ur.channel.recipients!.map(r => r.user_id)
|
||||
if (re.length === channelRecipients.length) {
|
||||
if (containsAll(re, channelRecipients)) {
|
||||
if (channel == null) {
|
||||
channel = ur.channel
|
||||
await ur.assign({ closed: false }).save()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (channel == null) {
|
||||
name = trimSpecial(name);
|
||||
|
||||
channel = await new Channel({
|
||||
name,
|
||||
type,
|
||||
owner_id: (type === ChannelType.DM ? undefined : creator_user_id),
|
||||
created_at: new Date(),
|
||||
last_message_id: null,
|
||||
recipients: channelRecipients.map((x) => new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })),
|
||||
}).save();
|
||||
}
|
||||
|
||||
|
||||
const channel_dto = await DmChannelDTO.from(channel)
|
||||
|
||||
if (type === ChannelType.GROUP_DM) {
|
||||
|
||||
for (let recipient of channel.recipients!) {
|
||||
await emitEvent({
|
||||
event: "CHANNEL_CREATE",
|
||||
data: channel_dto.excludedRecipients([recipient.user_id]),
|
||||
user_id: recipient.user_id
|
||||
})
|
||||
}
|
||||
} else {
|
||||
await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id });
|
||||
}
|
||||
|
||||
return channel_dto.excludedRecipients([creator_user_id])
|
||||
}
|
||||
|
||||
static async removeRecipientFromChannel(channel: Channel, user_id: string) {
|
||||
await Recipient.delete({ channel_id: channel.id, user_id: user_id })
|
||||
channel.recipients = channel.recipients?.filter(r => r.user_id !== user_id)
|
||||
|
||||
if (channel.recipients?.length === 0) {
|
||||
await Channel.deleteChannel(channel);
|
||||
await emitEvent({
|
||||
event: "CHANNEL_DELETE",
|
||||
data: await DmChannelDTO.from(channel, [user_id]),
|
||||
user_id: user_id
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
await emitEvent({
|
||||
event: "CHANNEL_DELETE",
|
||||
data: await DmChannelDTO.from(channel, [user_id]),
|
||||
user_id: user_id
|
||||
});
|
||||
|
||||
//If the owner leave we make the first recipient in the list the new owner
|
||||
if (channel.owner_id === user_id) {
|
||||
channel.owner_id = channel.recipients!.find(r => r.user_id !== user_id)!.user_id //Is there a criteria to choose the new owner?
|
||||
await emitEvent({
|
||||
event: "CHANNEL_UPDATE",
|
||||
data: await DmChannelDTO.from(channel, [user_id]),
|
||||
channel_id: channel.id
|
||||
});
|
||||
}
|
||||
|
||||
await channel.save()
|
||||
|
||||
await emitEvent({
|
||||
event: "CHANNEL_RECIPIENT_REMOVE", data: {
|
||||
channel_id: channel.id,
|
||||
user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection })
|
||||
}, channel_id: channel.id
|
||||
} as ChannelRecipientRemoveEvent);
|
||||
}
|
||||
|
||||
static async deleteChannel(channel: Channel) {
|
||||
await Message.delete({ channel_id: channel.id }) //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util
|
||||
//TODO before deleting the channel we should check and delete other relations
|
||||
await Channel.delete({ id: channel.id })
|
||||
}
|
||||
|
||||
isDm() {
|
||||
return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM
|
||||
}
|
||||
}
|
||||
|
||||
export interface ChannelPermissionOverwrite {
|
||||
|
@ -19,5 +19,8 @@ export class Recipient extends BaseClass {
|
||||
@ManyToOne(() => require("./User").User)
|
||||
user: import("./User").User;
|
||||
|
||||
@Column({ default: false })
|
||||
closed: boolean;
|
||||
|
||||
// TODO: settings/mute/nick/added at/encryption keys/read_state
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ export class User extends BaseClass {
|
||||
flags: string; // UserFlags
|
||||
|
||||
@Column()
|
||||
public_flags: string;
|
||||
public_flags: number;
|
||||
|
||||
@JoinColumn({ name: "relationship_ids" })
|
||||
@OneToMany(() => Relationship, (relationship: Relationship) => relationship.from)
|
||||
|
@ -4,6 +4,7 @@ import "reflect-metadata";
|
||||
export * from "./util/index";
|
||||
export * from "./interfaces/index";
|
||||
export * from "./entities/index";
|
||||
export * from "./dtos/index";
|
||||
|
||||
// import Config from "../util/Config";
|
||||
// import db, { MongooseCache, toObject } from "./util/Database";
|
||||
|
@ -127,6 +127,22 @@ export interface ChannelPinsUpdateEvent extends Event {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChannelRecipientAddEvent extends Event {
|
||||
event: "CHANNEL_RECIPIENT_ADD";
|
||||
data: {
|
||||
channel_id: string;
|
||||
user: User;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChannelRecipientRemoveEvent extends Event {
|
||||
event: "CHANNEL_RECIPIENT_REMOVE";
|
||||
data: {
|
||||
channel_id: string;
|
||||
user: User;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GuildCreateEvent extends Event {
|
||||
event: "GUILD_CREATE";
|
||||
data: Guild & {
|
||||
@ -436,6 +452,8 @@ export type EventData =
|
||||
| ChannelUpdateEvent
|
||||
| ChannelDeleteEvent
|
||||
| ChannelPinsUpdateEvent
|
||||
| ChannelRecipientAddEvent
|
||||
| ChannelRecipientRemoveEvent
|
||||
| GuildCreateEvent
|
||||
| GuildUpdateEvent
|
||||
| GuildDeleteEvent
|
||||
@ -482,6 +500,8 @@ export enum EVENTEnum {
|
||||
ChannelUpdate = "CHANNEL_UPDATE",
|
||||
ChannelDelete = "CHANNEL_DELETE",
|
||||
ChannelPinsUpdate = "CHANNEL_PINS_UPDATE",
|
||||
ChannelRecipientAdd = "CHANNEL_RECIPIENT_ADD",
|
||||
ChannelRecipientRemove = "CHANNEL_RECIPIENT_REMOVE",
|
||||
GuildCreate = "GUILD_CREATE",
|
||||
GuildUpdate = "GUILD_UPDATE",
|
||||
GuildDelete = "GUILD_DELETE",
|
||||
@ -525,6 +545,8 @@ export type EVENT =
|
||||
| "CHANNEL_UPDATE"
|
||||
| "CHANNEL_DELETE"
|
||||
| "CHANNEL_PINS_UPDATE"
|
||||
| "CHANNEL_RECIPIENT_ADD"
|
||||
| "CHANNEL_RECIPIENT_REMOVE"
|
||||
| "GUILD_CREATE"
|
||||
| "GUILD_UPDATE"
|
||||
| "GUILD_DELETE"
|
||||
|
3
util/src/util/Array.ts
Normal file
3
util/src/util/Array.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function containsAll(arr: any[], target: any[]) {
|
||||
return target.every(v => arr.includes(v));
|
||||
}
|
@ -92,6 +92,7 @@ export class Permissions extends BitField {
|
||||
}
|
||||
|
||||
overwriteChannel(overwrites: ChannelPermissionOverwrite[]) {
|
||||
if (!overwrites) return this
|
||||
if (!this.cache) throw new Error("permission chache not available");
|
||||
overwrites = overwrites.filter((x) => {
|
||||
if (x.type === 0 && this.cache.roles?.some((r) => r.id === x.id)) return true;
|
||||
|
@ -12,3 +12,4 @@ export * from "./RabbitMQ";
|
||||
export * from "./Regex";
|
||||
export * from "./Snowflake";
|
||||
export * from "./String";
|
||||
export * from "./Array";
|
||||
|
Loading…
Reference in New Issue
Block a user