mirror of
https://github.com/spacebarchat/server.git
synced 2026-04-17 13:15:41 +00:00
299 lines
8.5 KiB
TypeScript
299 lines
8.5 KiB
TypeScript
/*
|
|
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 { InteractionType } from "../interfaces/Interaction";
|
|
import { Application } from "./Application";
|
|
import { Column, CreateDateColumn, Entity, Index, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } 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 { ActionRowComponent, ApplicationCommandType, Embed, MessageType, PartialMessage, Poll, Reaction } from "@spacebar/schemas";
|
|
|
|
@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.guild)
|
|
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)
|
|
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)
|
|
mention_channels: Channel[];
|
|
|
|
@JoinTable({ name: "message_stickers" })
|
|
@ManyToMany(() => Sticker, { cascade: true, onDelete: "CASCADE" })
|
|
sticker_items?: Sticker[];
|
|
|
|
@OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, {
|
|
cascade: true,
|
|
orphanedRowAction: "delete",
|
|
})
|
|
attachments?: Attachment[];
|
|
|
|
@Column({ type: "simple-json" })
|
|
embeds: Embed[];
|
|
|
|
@Column({ type: "simple-json" })
|
|
reactions: Reaction[];
|
|
|
|
@Column({ type: "text", nullable: true })
|
|
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: "simple-json", nullable: true })
|
|
activity?: {
|
|
type: number;
|
|
party_id: string;
|
|
};
|
|
|
|
@Column({ default: 0 })
|
|
flags: number;
|
|
|
|
@Column({ type: "simple-json", nullable: true })
|
|
message_reference?: {
|
|
message_id: string;
|
|
channel_id?: string;
|
|
guild_id?: string;
|
|
type?: number; // 0 = DEFAULT, 1 = FORWARD
|
|
};
|
|
|
|
@JoinColumn({ name: "message_reference_id" })
|
|
@ManyToOne(() => Message)
|
|
referenced_message?: Message | null;
|
|
|
|
@Column({ type: "simple-json", nullable: true })
|
|
interaction?: {
|
|
id: string;
|
|
type: InteractionType;
|
|
name: string;
|
|
};
|
|
|
|
@Column({ type: "simple-json", nullable: true })
|
|
interaction_metadata?: {
|
|
id: string;
|
|
type: InteractionType;
|
|
user_id: string;
|
|
authorizing_integration_owners: object;
|
|
name: string;
|
|
command_type: ApplicationCommandType;
|
|
};
|
|
|
|
@Column({ type: "simple-json", nullable: true })
|
|
components?: ActionRowComponent[];
|
|
|
|
@Column({ type: "simple-json", nullable: true })
|
|
poll?: Poll;
|
|
|
|
@Column({ nullable: true })
|
|
username?: string;
|
|
|
|
@Column({ nullable: true })
|
|
avatar?: string;
|
|
|
|
toJSON(): Message {
|
|
return {
|
|
...this,
|
|
author_id: undefined,
|
|
member_id: undefined,
|
|
webhook_id: this.webhook_id ?? undefined,
|
|
application_id: undefined,
|
|
|
|
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,
|
|
author: {
|
|
...(this.author?.toPublicUser() ?? undefined),
|
|
// Webhooks
|
|
username: this.username ?? this.author?.username,
|
|
avatar: this.avatar ?? this.author?.avatar,
|
|
},
|
|
activity: this.activity ?? undefined,
|
|
application: this.application ?? undefined,
|
|
components: this.components ?? undefined,
|
|
poll: this.poll ?? undefined,
|
|
content: this.content ?? "",
|
|
pinned: this.pinned,
|
|
};
|
|
}
|
|
|
|
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
|
|
};
|
|
}
|
|
|
|
withSignedAttachments(data: NewUrlUserSignatureData) {
|
|
return {
|
|
...this,
|
|
attachments: this.attachments?.map((attachment: Attachment) => Attachment.prototype.signUrls.call(attachment, data)),
|
|
};
|
|
}
|
|
|
|
static async createWithDefaults(opts: Partial<Message>): Promise<Message> {
|
|
const message = new Message();
|
|
if (!opts.author) {
|
|
if (!opts.author_id) throw new Error("Either author or author_id must be provided to create a Message");
|
|
message.author = await User.findOneOrFail({ where: { id: opts.author_id! } });
|
|
}
|
|
|
|
if (!opts.member) {
|
|
if (opts.member_id) message.member = await Member.findOneOrFail({ where: { id: opts.member_id! } });
|
|
else if (message.author && opts.guild_id) message.member = await Member.findOneOrFail({ where: { id: message.author.id, guild_id: opts.guild_id! } });
|
|
}
|
|
|
|
if (!opts.channel) {
|
|
if (!opts.channel_id) throw new Error("Either channel or channel_id must be provided to create a Message");
|
|
message.channel = await Channel.findOneOrFail({ where: { id: opts.channel_id! } });
|
|
}
|
|
|
|
if (!opts.guild) {
|
|
if (opts.guild_id) message.guild = await Guild.findOneOrFail({ where: { id: opts.guild_id! } });
|
|
else if (opts.channel?.guild) message.guild = opts.channel.guild;
|
|
else if (opts.channel?.guild_id) message.guild = await Guild.findOneOrFail({ where: { id: opts.channel.guild_id! } });
|
|
else if (opts.member?.guild) message.guild = opts.member.guild;
|
|
else if (opts.member?.guild_id) message.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) message.member = await Member.findOneOrFail({ where: { id: message.author!.id, guild_id: message.guild!.id } });
|
|
|
|
Object.assign(message, {
|
|
tts: false,
|
|
embeds: [],
|
|
reactions: [],
|
|
flags: 0,
|
|
type: 0,
|
|
timestamp: new Date(),
|
|
...opts,
|
|
});
|
|
return message;
|
|
}
|
|
}
|