summary refs log tree commit diff
path: root/src/util/entities/Message.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/entities/Message.ts')
-rw-r--r--src/util/entities/Message.ts486
1 files changed, 0 insertions, 486 deletions
diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts
deleted file mode 100644

index 68e7cbbb..00000000 --- a/src/util/entities/Message.ts +++ /dev/null
@@ -1,486 +0,0 @@ -/* - Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Spacebar and Spacebar Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ - -import { User } from "./User"; -import { Member } from "./Member"; -import { Role } from "./Role"; -import { Channel } from "./Channel"; -import { Application } from "./Application"; -import { Column, CreateDateColumn, Entity, Index, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId, FindOneOptions, Raw, Not, BaseEntity, In } from "typeorm"; -import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; -import { Webhook } from "./Webhook"; -import { Sticker } from "./Sticker"; -import { Attachment } from "./Attachment"; -import { NewUrlUserSignatureData } from "../Signing"; -import { - ApplicationCommandType, - BaseMessageComponents, - Embed, - MessageComponentType, - MessageSnapshot, - MessageType, - PartialMessage, - Poll, - PublicMessage, - Reaction, - UnfurledMediaItem, - PartialUser, - InteractionType, -} from "@spacebar/schemas"; -import { MessageFlags } from "@spacebar/util"; -import { JsonRemoveEmpty } from "../util/Decorators"; - -@Entity({ - name: "messages", -}) -@Index(["channel_id", "id"], { unique: true }) -export class Message extends BaseClass { - @Column({ nullable: true }) - @RelationId((message: Message) => message.channel) - @Index() - channel_id?: string; - - @JoinColumn({ name: "channel_id" }) - @ManyToOne(() => Channel, { - onDelete: "CASCADE", - }) - channel: Channel; - - @Column({ nullable: true }) - @RelationId((message: Message) => message.thread) - @JsonRemoveEmpty - thread_id?: string; - - @JoinColumn({ name: "thread_id" }) - @ManyToOne(() => Channel, { - onDelete: "CASCADE", - }) - @JsonRemoveEmpty - thread?: Channel; - - @Column({ nullable: true }) - @RelationId((message: Message) => message.guild) - @JsonRemoveEmpty - guild_id?: string; - - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { - onDelete: "CASCADE", - }) - guild?: Guild; - - @Column({ nullable: true }) - @RelationId((message: Message) => message.author) - @Index() - author_id?: string; - - @JoinColumn({ name: "author_id", referencedColumnName: "id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - author?: User; - - @Column({ nullable: true }) - @RelationId((message: Message) => message.member) - member_id?: string; - - @JoinColumn({ name: "member_id", referencedColumnName: "id" }) - @ManyToOne(() => User, { - onDelete: "CASCADE", - }) - member?: Member; - - @Column({ nullable: true }) - @RelationId((message: Message) => message.webhook) - @JsonRemoveEmpty - webhook_id?: string; - - @JoinColumn({ name: "webhook_id" }) - @ManyToOne(() => Webhook) - webhook?: Webhook; - - @Column({ nullable: true }) - @RelationId((message: Message) => message.application) - application_id?: string; - - @JoinColumn({ name: "application_id" }) - @ManyToOne(() => Application) - application?: Application; - - @Column({ nullable: true }) - content?: string; - - @Column() - @CreateDateColumn() - timestamp: Date; - - @Column({ nullable: true }) - edited_timestamp?: Date; - - @Column({ nullable: true }) - tts?: boolean; - - @Column({ nullable: true }) - mention_everyone?: boolean; - - @JoinTable({ name: "message_user_mentions" }) - @ManyToMany(() => User) - mentions: User[]; - - @JoinTable({ name: "message_role_mentions" }) - @ManyToMany(() => Role) - mention_roles: Role[]; - - @JoinTable({ name: "message_channel_mentions" }) - @ManyToMany(() => Channel) - @JsonRemoveEmpty - mention_channels: Channel[]; - - @JoinTable({ name: "message_stickers" }) - @ManyToMany(() => Sticker, { cascade: true, onDelete: "CASCADE" }) - @JsonRemoveEmpty - sticker_items?: Sticker[]; - - @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { - cascade: true, - orphanedRowAction: "delete", - }) - attachments?: Attachment[]; - - @Column({ type: "jsonb" }) - embeds: Embed[]; - - @Column({ type: "jsonb" }) - @JsonRemoveEmpty - reactions: Reaction[]; - - @Column({ type: "text", nullable: true }) - @JsonRemoveEmpty - nonce?: string; - - @Column({ nullable: true, type: Date }) - pinned_at?: Date | null; - - get pinned(): boolean { - return this.pinned_at != null; - } - - @Column({ type: "int" }) - type: MessageType; - - @Column({ type: "jsonb", nullable: true }) - @JsonRemoveEmpty - activity?: { - type: number; - party_id: string; - }; - - @Column({ default: 0 }) - flags: number; - - @Column({ type: "jsonb", nullable: true }) - @JsonRemoveEmpty - message_reference?: { - message_id?: string; - channel_id?: string; - guild_id?: string; - type?: number; // 0 = DEFAULT, 1 = FORWARD - }; - - @JoinColumn({ name: "message_reference_id" }) - @ManyToOne(() => Message, { onDelete: "SET NULL" }) - referenced_message?: Message | null; - - @Column({ type: "jsonb", nullable: true }) - @JsonRemoveEmpty - interaction?: { - id: string; - type: InteractionType; - name: string; - }; - - @Column({ type: "jsonb", nullable: true }) - @JsonRemoveEmpty - interaction_metadata?: { - id: string; - type: InteractionType; - user_id: string; - authorizing_integration_owners: object; - name: string; - command_type: ApplicationCommandType; - }; - - @Column({ type: "jsonb", nullable: true }) - components?: BaseMessageComponents[]; - - @Column({ type: "jsonb", nullable: true }) - @JsonRemoveEmpty - poll?: Poll; - - @Column({ nullable: true }) - username?: string; - - @Column({ nullable: true }) - avatar?: string; - - @Column({ default: "[]", type: "jsonb" }) - message_snapshots: MessageSnapshot[]; - - get isWebhook() { - return this.webhook_id != null && this.webhook != null; - } - - static async fillReplies(messages: Message[]) { - const ms = messages - .filter((msg) => msg.message_reference && !msg.referenced_message?.id && msg.message_reference.message_id) - .filter((msg) => [MessageType.REPLY, MessageType.THREAD_STARTER_MESSAGE, MessageType.CONTEXT_MENU_COMMAND].includes(msg.type)); - if (!ms.length) return; - const curMs = new Map(messages.map((m) => [m.id, m] as const)); - const neededIds = new Set(ms.map((m) => m.message_reference!.message_id as string)).difference(curMs); - if (neededIds.size) { - const newMessages = await Message.find({ - where: { - id: In([...neededIds]), - }, - relations: { author: true, mentions: true, mention_roles: true, mention_channels: true }, - }); - newMessages.forEach((msg) => curMs.set(msg.id, msg)); - } - for (const message of ms) { - message.referenced_message = curMs.get(message.message_reference!.message_id as string) || null; - } - } - - toJSON(shallow = false): PublicMessage { - // this.clean_data(); - return { - ...this, - channel_id: this.channel_id ?? this.channel.id, - channel: undefined, - - timestamp: this.timestamp.toISOString(), - edited_timestamp: this.edited_timestamp ? this.edited_timestamp.toISOString() : null, - - author_id: undefined, - member_id: undefined, - webhook_id: this.webhook_id ?? undefined, - application_id: undefined, - mentions: this.mentions?.map((user) => { - if (user && !user.toPublicUser) console.trace("toPublic user missing!!!"); - return (user?.toPublicUser?.() ?? user ?? undefined) as unknown as PartialUser; - }), - - mention_roles: this.mention_roles?.map((role) => role.id) ?? [], - mention_channels: this.mention_channels?.map((ch) => ch.toJSON()) ?? [], - attachments: this.attachments?.map((att) => att.toJSON()) ?? [], - - nonce: this.nonce ?? undefined, - tts: this.tts ?? false, - guild: this.guild ?? undefined, - webhook: this.webhook ?? undefined, - interaction: this.interaction ?? undefined, - interaction_metadata: this.interaction_metadata ?? undefined, - reactions: this.reactions ?? undefined, - sticker_items: this.sticker_items ?? undefined, - message_reference: this.message_reference ?? undefined, - mention_everyone: this.mention_everyone ?? false, - author: { - ...(this.author?.toPublicUser() ?? undefined), - // Webhooks - username: this.username ?? this.author?.username ?? null, - avatar: this.avatar ?? this.author?.avatar ?? null, - }, - activity: this.activity ?? undefined, - application: this.application ?? undefined, - components: this.components ?? [], - poll: this.poll ?? undefined, - content: this.content ?? "", - pinned: this.pinned, - thread: this.thread ? this.thread.toJSON() : this.thread, - referenced_message: this.referenced_message && !shallow ? this.referenced_message.toJSON(true) : undefined, - }; - } - - toPartialMessage(): PartialMessage { - return { - id: this.id, - // lobby_id: this.lobby_id, - channel_id: this.channel_id!, - type: this.type, - content: this.content!, - author: { ...this.author!, avatar: this.author?.avatar ?? null }, - flags: this.flags, - application_id: this.application_id, - //channel: this.channel, // TODO: ephemeral DM channels - // recipient_id: this.recipient_id, // TODO: ephemeral DM channels - }; - } - - toSnapshot(): MessageSnapshot { - return { - message: { - attachments: this.attachments?.map((x) => x.toJSON()), - components: this.components, - content: this.content!, - edited_timestamp: this.edited_timestamp, - embeds: this.embeds, - flags: this.flags, - mention_roles: this.mention_roles?.map((x) => x.id), - mentions: this.mentions.map((x) => x.toPublicUser() as unknown as PartialUser), // TODO: write a proper method for this - timestamp: this.timestamp, - type: this.type, - }, - }; - } - - withSignedAttachments(data: NewUrlUserSignatureData) { - function signMedia(media: UnfurledMediaItem) { - Object.assign(media, Attachment.prototype.signUrls.call(media, data)); - } - return { - ...this, - attachments: this.attachments?.map((attachment: Attachment) => Attachment.prototype.signUrls.call(attachment, data)), - components: this.components - ? this.components.map((comp) => { - comp = structuredClone(comp); - if (comp.type === MessageComponentType.Section) { - const accessory = comp.accessory; - if (accessory.type === MessageComponentType.Thumbnail) { - signMedia(accessory.media); - } - } else if (comp.type === MessageComponentType.MediaGallery) { - comp.items.forEach(({ media }) => signMedia(media)); - } else if (comp.type === MessageComponentType.File) { - signMedia(comp.file); - } else if (comp.type === MessageComponentType.Container) { - for (const elm of comp.components) { - switch (elm.type) { - case MessageComponentType.Separator: - case MessageComponentType.TextDisplay: - case MessageComponentType.ActionRow: - break; - case MessageComponentType.Section: { - const accessory = elm.accessory; - if (accessory.type === MessageComponentType.Thumbnail) { - signMedia(accessory.media); - } - break; - } - case MessageComponentType.MediaGallery: - elm.items.forEach(({ media }) => signMedia(media)); - break; - case MessageComponentType.File: { - signMedia(elm.file); - break; - } - - default: - elm satisfies never; - } - } - } - return comp; - }) - : this.components, - }; - } - - static async createWithDefaults(opts: Partial<Message>): Promise<Message> { - const message = Message.create(); - - if (!opts.author) { - if (!opts.author_id) throw new Error("Either author or author_id must be provided to create a Message"); - opts.author = await User.findOneOrFail({ where: { id: opts.author_id! } }); - } - - if (!opts.channel) { - if (!opts.channel_id) throw new Error("Either channel or channel_id must be provided to create a Message"); - opts.channel = await Channel.findOneOrFail({ where: { id: opts.channel_id! } }); - opts.guild_id ??= opts.channel.guild_id; - } - - if (!opts.member_id) opts.member_id = message.author_id; - if (!opts.member) opts.member = await Member.findOneOrFail({ where: { id: opts.member_id! } }); - - if (!opts.guild) { - if (opts.guild_id) opts.guild = await Guild.findOneOrFail({ where: { id: opts.guild_id! } }); - else if (opts.channel?.guild?.id) opts.guild = opts.channel.guild; - else if (opts.channel?.guild_id) opts.guild = await Guild.findOneOrFail({ where: { id: opts.channel.guild_id! } }); - else if (opts.member?.guild?.id) opts.guild = opts.member.guild; - else if (opts.member?.guild_id) opts.guild = await Guild.findOneOrFail({ where: { id: opts.member.guild_id! } }); - else throw new Error("Either guild, guild_id, channel.guild, channel.guild_id, member.guild or member.guild_id must be provided to create a Message"); - } - - // try 2 now that we have a guild - if (!opts.member) opts.member = await Member.findOneOrFail({ where: { id: opts.author!.id, guild_id: opts.guild!.id } }); - - // set reply type if a message if referenced - if (opts.message_reference && !opts.type) message.type = MessageType.REPLY; - - // backpropagate ids - opts.channel_id = opts.channel.id; - opts.guild_id = opts.guild.id; - opts.author_id = opts.author.id; - opts.member_id = opts.member.id; - opts.webhook_id = opts.webhook?.id; - opts.application_id = opts.application?.id; - - delete opts.member; - - Object.assign(message, { - tts: false, - embeds: [], - reactions: [], - flags: 0, - type: 0, - timestamp: new Date(), - ...opts, - }); - return message; - } - static addDefault(options: FindOneOptions<Message>) { - if (options.where) { - const arr = options.where instanceof Array ? options.where : [options.where]; - for (const thing of arr) { - if (!("flags" in thing)) { - thing.flags = Not(Raw((alias) => `${alias} & ${MessageFlags.FLAGS.EPHEMERAL} = ${MessageFlags.FLAGS.EPHEMERAL}`)); - } - } - } - } -} - -//@ts-expect-error It works but TS types hate it -Message.findOneOrFail = function (this: Message, options: FindOneOptions<Message>): Promise<Message> { - Message.addDefault(options as FindOneOptions<Message>); - //@ts-expect-error how to use generics on call, who knows! - return BaseEntity.findOneOrFail.call(Message, options); -}; -//@ts-expect-error It works but TS types hate it -Message.findOne = function (this: Message, options: FindOneOptions<Message>): Promise<Message> { - Message.addDefault(options as FindOneOptions<Message>); - //@ts-expect-error how to use generics on call, who knows! - return BaseEntity.findOne.call(Message, options); -}; -//@ts-expect-error It works but TS types hate it -Message.find = function (this: Message, options: FindOneOptions<Message>): Promise<Message[]> { - Message.addDefault(options as FindOneOptions<Message>); - //@ts-expect-error how to use generics on call, who knows! - return BaseEntity.find.call(Message, options); -};