diff --git a/util/src/dtos/DmChannelDTO.ts b/util/src/dtos/DmChannelDTO.ts
new file mode 100644
index 00000000..8b7a18fd
--- /dev/null
+++ b/util/src/dtos/DmChannelDTO.ts
@@ -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))
+ }
+ }
+}
\ No newline at end of file
diff --git a/util/src/dtos/UserDTO.ts b/util/src/dtos/UserDTO.ts
new file mode 100644
index 00000000..f09b5f4e
--- /dev/null
+++ b/util/src/dtos/UserDTO.ts
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/util/src/dtos/index.ts b/util/src/dtos/index.ts
new file mode 100644
index 00000000..13702342
--- /dev/null
+++ b/util/src/dtos/index.ts
@@ -0,0 +1,2 @@
+export * from "./DmChannelDTO";
+export * from "./UserDTO";
\ No newline at end of file
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index fc954f63..6eac19ca 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -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 {
diff --git a/util/src/entities/Recipient.ts b/util/src/entities/Recipient.ts
index 2a27b29f..bb280588 100644
--- a/util/src/entities/Recipient.ts
+++ b/util/src/entities/Recipient.ts
@@ -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
}
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 736704f8..cef88777 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -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)
diff --git a/util/src/index.ts b/util/src/index.ts
index f3bd9e9b..538bfdd1 100644
--- a/util/src/index.ts
+++ b/util/src/index.ts
@@ -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";
diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index aff50300..03099bbb 100644
--- a/util/src/interfaces/Event.ts
+++ b/util/src/interfaces/Event.ts
@@ -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"
diff --git a/util/src/services/ChannelService.ts b/util/src/services/ChannelService.ts
new file mode 100644
index 00000000..319475b6
--- /dev/null
+++ b/util/src/services/ChannelService.ts
@@ -0,0 +1,89 @@
+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 })) });
+
+ // 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 (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);
+ }
+}
diff --git a/util/src/services/index.ts b/util/src/services/index.ts
new file mode 100644
index 00000000..c012a208
--- /dev/null
+++ b/util/src/services/index.ts
@@ -0,0 +1 @@
+export * from "./ChannelService";
diff --git a/util/src/util/Event.ts b/util/src/util/Event.ts
index 765e5fc7..ae296df9 100644
--- a/util/src/util/Event.ts
+++ b/util/src/util/Event.ts
@@ -5,6 +5,7 @@ import { EVENT, Event } from "../interfaces";
const events = new EventEmitter();
export async function emitEvent(payload: Omit<Event, "created_at">) {
+ console.log(payload) //TODO remove before merge
const id = (payload.channel_id || payload.user_id || payload.guild_id) as string;
if (!id) return console.error("event doesn't contain any id", payload);
diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts
index 9d87253a..44852f1e 100644
--- a/util/src/util/Permissions.ts
+++ b/util/src/util/Permissions.ts
@@ -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;
|