Implemented DMs and group DMs
This commit is contained in:
		
							parent
							
								
									0506d40aa1
								
							
						
					
					
						commit
						d630f09f80
					
				| @ -1,6 +1,7 @@ | ||||
| import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, ChannelType, ChannelPermissionOverwriteType } from "@fosscord/util"; | ||||
| import { Router, Response, Request } from "express"; | ||||
| import { Channel, ChannelDeleteEvent, ChannelPermissionOverwriteType, ChannelService, ChannelType, ChannelUpdateEvent, emitEvent, Recipient } from "@fosscord/util"; | ||||
| import { Request, Response, Router } from "express"; | ||||
| import { route } from "@fosscord/api"; | ||||
| 
 | ||||
| const router: Router = Router(); | ||||
| // TODO: delete channel
 | ||||
| // TODO: Get channel
 | ||||
| @ -16,14 +17,27 @@ 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 ChannelService.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 { | ||||
|  | ||||
| @ -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 } 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,12 @@ router.post( | ||||
| 				return res.status(400).json(error); | ||||
| 			} | ||||
| 		} | ||||
| 		//TODO querying the DB at every message post should be avoided, caching maybe?
 | ||||
| 		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 +163,36 @@ 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 | ||||
| 					}) | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			await Promise.all(channel.recipients!.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, ChannelService, 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 ChannelService.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 ChannelService.removeRecipientFromChannel(channel, user_id) | ||||
| 
 | ||||
| 	return res.sendStatus(204); | ||||
| }); | ||||
| 
 | ||||
| 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,21 @@ | ||||
| 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 { PublicUserProjection, Recipient, User, ChannelService } 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"] }); | ||||
| 	const recipients = await Recipient.find({ where: { user_id: req.user_id }, relations: ["channel", "user"] }); | ||||
| 
 | ||||
| 	res.json(recipients.map((x) => x.channel)); | ||||
| 	//TODO check if this is right
 | ||||
| 	const aa = await Promise.all(recipients.map(async (x) => { | ||||
| 		return { | ||||
| 			...(x.channel), | ||||
| 			recipients: await User.findOneOrFail({ where: { id: x.user_id }, select: PublicUserProjection }), | ||||
| 		} | ||||
| 	})) | ||||
| 
 | ||||
| 	res.json(aa); | ||||
| }); | ||||
| 
 | ||||
| export interface DmChannelCreateSchema { | ||||
| @ -19,30 +25,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 ChannelService.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 | ||||
| 	); | ||||
|  | ||||
| @ -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, | ||||
| 	}; | ||||
| 	users.push(public_user); | ||||
| 	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_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,7 +1,6 @@ | ||||
| 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 { HTTPError } from "lambert-server"; | ||||
| import { emitEvent, getPermission, Snowflake } from "../util"; | ||||
| @ -31,6 +30,9 @@ export class Channel extends BaseClass { | ||||
| 	@Column({ nullable: true }) | ||||
| 	name?: string; | ||||
| 
 | ||||
| 	@Column({ nullable: true }) | ||||
| 	icon?: string; | ||||
| 
 | ||||
| 	@Column({ type: "simple-enum", enum: ChannelType }) | ||||
| 	type: ChannelType; | ||||
| 
 | ||||
| @ -38,13 +40,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; | ||||
| @ -162,6 +159,10 @@ export class Channel extends BaseClass { | ||||
| 
 | ||||
| 		return channel; | ||||
| 	} | ||||
| 
 | ||||
| 	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,8 @@ import "reflect-metadata"; | ||||
| export * from "./util/index"; | ||||
| export * from "./interfaces/index"; | ||||
| export * from "./entities/index"; | ||||
| export * from "./services/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" | ||||
|  | ||||
							
								
								
									
										88
									
								
								util/src/services/ChannelService.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								util/src/services/ChannelService.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | ||||
| import { Channel, ChannelType, PublicUserProjection, Recipient, User } from "../entities"; | ||||
| import { HTTPError } from "lambert-server"; | ||||
| import { emitEvent, trimSpecial } from "../util"; | ||||
| import { DmChannelDTO } from "../dtos"; | ||||
| import { ChannelRecipientRemoveEvent } from "../interfaces"; | ||||
| 
 | ||||
| export function checker(arr: any[], target: any[]) { | ||||
| 	return target.every(v => arr.includes(v)); | ||||
| } | ||||
| 
 | ||||
| export class ChannelService { | ||||
| 	public 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 })) }); | ||||
| 
 | ||||
| 		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 (checker(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]) | ||||
| 	} | ||||
| 
 | ||||
| 	public static async removeRecipientFromChannel(channel: Channel, user_id: string) { | ||||
| 		await Recipient.delete({ channel_id: channel.id, user_id: user_id }) | ||||
| 
 | ||||
| 		await emitEvent({ | ||||
| 			event: "CHANNEL_DELETE", | ||||
| 			data: await DmChannelDTO.from(channel, [user_id]), | ||||
| 			user_id: user_id | ||||
| 		}); | ||||
| 
 | ||||
| 		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); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										1
									
								
								util/src/services/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								util/src/services/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| export * from "./ChannelService"; | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 AlTech98
						AlTech98