summary refs log tree commit diff
path: root/util/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util/dtos/DmChannelDTO.ts (renamed from util/src/dtos/DmChannelDTO.ts)4
-rw-r--r--src/util/dtos/UserDTO.ts (renamed from util/src/dtos/UserDTO.ts)0
-rw-r--r--src/util/dtos/index.ts (renamed from util/src/dtos/index.ts)0
-rw-r--r--src/util/entities/Application.ts (renamed from util/src/entities/Application.ts)101
-rw-r--r--src/util/entities/Attachment.ts (renamed from util/src/entities/Attachment.ts)2
-rw-r--r--src/util/entities/AuditLog.ts (renamed from util/src/entities/AuditLog.ts)18
-rw-r--r--src/util/entities/Ban.ts (renamed from util/src/entities/Ban.ts)4
-rw-r--r--src/util/entities/Categories.ts (renamed from util/src/entities/Categories.ts)24
-rw-r--r--src/util/entities/Channel.ts (renamed from util/src/entities/Channel.ts)769
-rw-r--r--src/util/entities/ClientRelease.ts (renamed from util/src/entities/ClientRelease.ts)2
-rw-r--r--src/util/entities/ConnectedAccount.ts (renamed from util/src/entities/ConnectedAccount.ts)2
-rw-r--r--src/util/entities/Emoji.ts (renamed from util/src/entities/Emoji.ts)5
-rw-r--r--src/util/entities/Group.ts (renamed from util/src/entities/Group.ts)8
-rw-r--r--src/util/entities/Guild.ts (renamed from util/src/entities/Guild.ts)80
-rw-r--r--src/util/entities/Invite.ts (renamed from util/src/entities/Invite.ts)25
-rw-r--r--src/util/entities/Member.ts (renamed from util/src/entities/Member.ts)150
-rw-r--r--src/util/entities/Message.ts (renamed from util/src/entities/Message.ts)42
-rw-r--r--src/util/entities/Migration.ts (renamed from util/src/entities/Migration.ts)4
-rw-r--r--src/util/entities/RateLimit.ts (renamed from util/src/entities/RateLimit.ts)0
-rw-r--r--src/util/entities/ReadState.ts (renamed from util/src/entities/ReadState.ts)9
-rw-r--r--src/util/entities/Recipient.ts (renamed from util/src/entities/Recipient.ts)4
-rw-r--r--src/util/entities/Relationship.ts (renamed from util/src/entities/Relationship.ts)8
-rw-r--r--src/util/entities/Role.ts (renamed from util/src/entities/Role.ts)2
-rw-r--r--src/util/entities/Session.ts (renamed from util/src/entities/Session.ts)16
-rw-r--r--src/util/entities/Sticker.ts (renamed from util/src/entities/Sticker.ts)12
-rw-r--r--src/util/entities/StickerPack.ts (renamed from util/src/entities/StickerPack.ts)4
-rw-r--r--src/util/entities/Team.ts (renamed from util/src/entities/Team.ts)4
-rw-r--r--src/util/entities/TeamMember.ts (renamed from util/src/entities/TeamMember.ts)6
-rw-r--r--src/util/entities/Template.ts (renamed from util/src/entities/Template.ts)0
-rw-r--r--src/util/entities/User.ts (renamed from util/src/entities/User.ts)224
-rw-r--r--src/util/entities/UserGroup.ts (renamed from util/src/entities/UserGroup.ts)14
-rw-r--r--src/util/entities/VoiceState.ts (renamed from util/src/entities/VoiceState.ts)8
-rw-r--r--src/util/entities/Webhook.ts (renamed from util/src/entities/Webhook.ts)12
-rw-r--r--src/util/entities/index.ts (renamed from util/src/entities/index.ts)5
-rw-r--r--src/util/index.ts (renamed from util/src/index.ts)9
-rw-r--r--src/util/interfaces/Activity.ts (renamed from util/src/interfaces/Activity.ts)2
-rw-r--r--src/util/interfaces/Event.ts (renamed from util/src/interfaces/Event.ts)22
-rw-r--r--src/util/interfaces/Interaction.ts (renamed from util/src/interfaces/Interaction.ts)4
-rw-r--r--src/util/interfaces/Presence.ts (renamed from util/src/interfaces/Presence.ts)4
-rw-r--r--src/util/interfaces/Status.ts (renamed from util/src/interfaces/Status.ts)0
-rw-r--r--src/util/interfaces/index.ts (renamed from util/src/interfaces/index.ts)4
-rw-r--r--src/util/util/ApiError.ts (renamed from util/src/util/ApiError.ts)3
-rw-r--r--src/util/util/Array.ts (renamed from util/src/util/Array.ts)0
-rw-r--r--src/util/util/AutoUpdate.ts (renamed from util/src/util/AutoUpdate.ts)13
-rw-r--r--src/util/util/BitField.ts (renamed from util/src/util/BitField.ts)3
-rw-r--r--src/util/util/Categories.ts (renamed from util/src/util/Categories.ts)2
-rw-r--r--src/util/util/Constants.ts (renamed from util/src/util/Constants.ts)136
-rw-r--r--src/util/util/Email.ts (renamed from util/src/util/Email.ts)2
-rw-r--r--src/util/util/Event.ts (renamed from util/src/util/Event.ts)17
-rw-r--r--src/util/util/FieldError.ts (renamed from util/src/util/FieldError.ts)8
-rw-r--r--src/util/util/Intents.ts (renamed from util/src/util/Intents.ts)5
-rw-r--r--src/util/util/MessageFlags.ts (renamed from util/src/util/MessageFlags.ts)2
-rw-r--r--src/util/util/Permissions.ts (renamed from util/src/util/Permissions.ts)48
-rw-r--r--src/util/util/RabbitMQ.ts (renamed from util/src/util/RabbitMQ.ts)4
-rw-r--r--src/util/util/Regex.ts (renamed from util/src/util/Regex.ts)0
-rw-r--r--src/util/util/Rights.ts (renamed from util/src/util/Rights.ts)24
-rw-r--r--src/util/util/Snowflake.ts (renamed from util/src/util/Snowflake.ts)17
-rw-r--r--src/util/util/String.ts (renamed from util/src/util/String.ts)0
-rw-r--r--src/util/util/Token.ts (renamed from util/src/util/Token.ts)17
-rw-r--r--src/util/util/TraverseDirectory.ts (renamed from util/src/util/TraverseDirectory.ts)9
-rw-r--r--src/util/util/cdn.ts (renamed from util/src/util/cdn.ts)13
-rw-r--r--src/util/util/index.ts (renamed from util/src/util/index.ts)9
-rw-r--r--util/src/entities/BaseClass.ts75
-rw-r--r--util/src/entities/Config.ts407
-rw-r--r--util/src/entities/Encryption.ts35
-rw-r--r--util/src/migrations/1633864260873-EmojiRoles.ts13
-rw-r--r--util/src/migrations/1633864669243-EmojiUser.ts23
-rw-r--r--util/src/migrations/1633881705509-VanityInvite.ts19
-rw-r--r--util/src/migrations/1634308884591-Stickers.ts66
-rw-r--r--util/src/migrations/1634424361103-Presence.ts11
-rw-r--r--util/src/migrations/1634426540271-MigrationTimestamp.ts15
-rw-r--r--util/src/migrations/1648643945733-ReleaseTypo.ts16
-rw-r--r--util/src/util/Config.ts81
-rw-r--r--util/src/util/Database.ts72
-rw-r--r--util/src/util/InvisibleCharacters.ts56
75 files changed, 890 insertions, 1944 deletions
diff --git a/util/src/dtos/DmChannelDTO.ts b/src/util/dtos/DmChannelDTO.ts

index 226b2f9d..93b1adfa 100644 --- a/util/src/dtos/DmChannelDTO.ts +++ b/src/util/dtos/DmChannelDTO.ts
@@ -1,5 +1,5 @@ -import { MinimalPublicUserDTO } from "./UserDTO"; import { Channel, PublicUserProjection, User } from "../entities"; +import { MinimalPublicUserDTO } from "./UserDTO"; export class DmChannelDTO { icon: string | null; @@ -35,7 +35,7 @@ export class DmChannelDTO { excludedRecipients(excluded_recipients: string[]): DmChannelDTO { return { ...this, - recipients: this.recipients.filter((r) => !excluded_recipients.includes(r.id)), + recipients: this.recipients.filter((r) => !excluded_recipients.includes(r.id)) }; } } diff --git a/util/src/dtos/UserDTO.ts b/src/util/dtos/UserDTO.ts
index ee2752a4..ee2752a4 100644 --- a/util/src/dtos/UserDTO.ts +++ b/src/util/dtos/UserDTO.ts
diff --git a/util/src/dtos/index.ts b/src/util/dtos/index.ts
index 0e8f8459..0e8f8459 100644 --- a/util/src/dtos/index.ts +++ b/src/util/dtos/index.ts
diff --git a/util/src/entities/Application.ts b/src/util/entities/Application.ts
index fab3d93f..35fc496d 100644 --- a/util/src/entities/Application.ts +++ b/src/util/entities/Application.ts
@@ -1,6 +1,5 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from "typeorm"; import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; import { Team } from "./Team"; import { User } from "./User"; @@ -12,55 +11,101 @@ export class Application extends BaseClass { @Column({ nullable: true }) icon?: string; - @Column() + @Column({ nullable: true }) description: string; - @Column({ type: "simple-array", nullable: true }) - rpc_origins?: string[]; + @Column({ nullable: true }) + summary: string = ""; + + @Column({ type: "simple-json", nullable: true }) + type?: any; @Column() - bot_public: boolean; + hook: boolean = true; @Column() - bot_require_code_grant: boolean; + bot_public?: boolean = true; - @Column({ nullable: true }) - terms_of_service_url?: string; + @Column() + bot_require_code_grant?: boolean = false; - @Column({ nullable: true }) - privacy_policy_url?: string; + @Column() + verify_key: string; @JoinColumn({ name: "owner_id" }) @ManyToOne(() => User) - owner?: User; + owner: User; + + @Column() + flags: number = 0; + + @Column({ type: "simple-array", nullable: true }) + redirect_uris: string[] = []; @Column({ nullable: true }) - summary?: string; + rpc_application_state: number = 0; - @Column() - verify_key: string; + @Column({ nullable: true }) + store_application_state: number = 1; - @JoinColumn({ name: "team_id" }) - @ManyToOne(() => Team, { - onDelete: "CASCADE", - }) - team?: Team; + @Column({ nullable: true }) + verification_state: number = 1; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) - guild: Guild; // if this application is a game sold, this field will be the guild to which it has been linked + @Column({ nullable: true }) + interactions_endpoint_url?: string; @Column({ nullable: true }) - primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created, + integration_public: boolean = true; @Column({ nullable: true }) - slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page + integration_require_code_grant: boolean = false; + + @Column({ nullable: true }) + discoverability_state: number = 1; + + @Column({ nullable: true }) + discovery_eligibility_flags: number = 2240; + + @JoinColumn({ name: "bot_user_id" }) + @OneToOne(() => User) + bot?: User; + + @Column({ type: "simple-array", nullable: true }) + tags?: string[]; @Column({ nullable: true }) cover_image?: string; // the application's default rich presence invite cover image hash - @Column() - flags: string; // the application's public flags + @Column({ type: "simple-json", nullable: true }) + install_params?: { scopes: string[]; permissions: string }; + + @Column({ nullable: true }) + terms_of_service_url?: string; + + @Column({ nullable: true }) + privacy_policy_url?: string; + + //just for us + + //@Column({ type: "simple-array", nullable: true }) + //rpc_origins?: string[]; + + //@JoinColumn({ name: "guild_id" }) + //@ManyToOne(() => Guild) + //guild?: Guild; // if this application is a game sold, this field will be the guild to which it has been linked + + //@Column({ nullable: true }) + //primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created, + + //@Column({ nullable: true }) + //slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page + + @JoinColumn({ name: "team_id" }) + @ManyToOne(() => Team, { + onDelete: "CASCADE", + nullable: true + }) + team?: Team; } export interface ApplicationCommand { @@ -93,7 +138,7 @@ export enum ApplicationCommandOptionType { BOOLEAN = 5, USER = 6, CHANNEL = 7, - ROLE = 8, + ROLE = 8 } export interface ApplicationCommandInteractionData { diff --git a/util/src/entities/Attachment.ts b/src/util/entities/Attachment.ts
index 7b4b17eb..8392f415 100644 --- a/util/src/entities/Attachment.ts +++ b/src/util/entities/Attachment.ts
@@ -32,7 +32,7 @@ export class Attachment extends BaseClass { @JoinColumn({ name: "message_id" }) @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) message: import("./Message").Message; diff --git a/util/src/entities/AuditLog.ts b/src/util/entities/AuditLog.ts
index b003e7ba..6f394f42 100644 --- a/util/src/entities/AuditLog.ts +++ b/src/util/entities/AuditLog.ts
@@ -5,24 +5,24 @@ import { User } from "./User"; export enum AuditLogEvents { // guild level - GUILD_UPDATE = 1, + GUILD_UPDATE = 1, GUILD_IMPORT = 2, GUILD_EXPORTED = 3, GUILD_ARCHIVE = 4, GUILD_UNARCHIVE = 5, // join-leave - USER_JOIN = 6, + USER_JOIN = 6, USER_LEAVE = 7, // channels - CHANNEL_CREATE = 10, + CHANNEL_CREATE = 10, CHANNEL_UPDATE = 11, CHANNEL_DELETE = 12, // permission overrides - CHANNEL_OVERWRITE_CREATE = 13, + CHANNEL_OVERWRITE_CREATE = 13, CHANNEL_OVERWRITE_UPDATE = 14, CHANNEL_OVERWRITE_DELETE = 15, // kick and ban - MEMBER_KICK = 20, + MEMBER_KICK = 20, MEMBER_PRUNE = 21, MEMBER_BAN_ADD = 22, MEMBER_BAN_REMOVE = 23, @@ -79,18 +79,18 @@ export enum AuditLogEvents { // application commands APPLICATION_COMMAND_PERMISSION_UPDATE = 121, // automod - POLICY_CREATE = 140, + POLICY_CREATE = 140, POLICY_UPDATE = 141, POLICY_DELETE = 142, - MESSAGE_BLOCKED_BY_POLICIES = 143, // in fosscord, blocked messages are stealth-dropped + MESSAGE_BLOCKED_BY_POLICIES = 143, // in fosscord, blocked messages are stealth-dropped // instance policies affecting the guild GUILD_AFFECTED_BY_POLICIES = 216, // message moves IN_GUILD_MESSAGE_MOVE = 223, CROSS_GUILD_MESSAGE_MOVE = 224, // message routing - ROUTE_CREATE = 225, - ROUTE_UPDATE = 226, + ROUTE_CREATE = 225, + ROUTE_UPDATE = 226 } @Entity("audit_logs") diff --git a/util/src/entities/Ban.ts b/src/util/entities/Ban.ts
index 9504bd8e..27c75278 100644 --- a/util/src/entities/Ban.ts +++ b/src/util/entities/Ban.ts
@@ -11,7 +11,7 @@ export class Ban extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; @@ -21,7 +21,7 @@ export class Ban extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; diff --git a/util/src/entities/Categories.ts b/src/util/entities/Categories.ts
index 81fbc303..08a79112 100644 --- a/util/src/entities/Categories.ts +++ b/src/util/entities/Categories.ts
@@ -1,4 +1,4 @@ -import { PrimaryColumn, Column, Entity} from "typeorm"; +import { Column, Entity, PrimaryColumn } from "typeorm"; import { BaseClassWithoutId } from "./BaseClass"; // TODO: categories: @@ -16,18 +16,18 @@ import { BaseClassWithoutId } from "./BaseClass"; // Also populate discord default categories @Entity("categories") -export class Categories extends BaseClassWithoutId { // Not using snowflake - - @PrimaryColumn() - id: number; +export class Categories extends BaseClassWithoutId { + // Not using snowflake - @Column({ nullable: true }) - name: string; + @PrimaryColumn() + id: number; - @Column({ type: "simple-json" }) - localizations: string; + @Column({ nullable: true }) + name: string; - @Column({ nullable: true }) - is_primary: boolean; + @Column({ type: "simple-json" }) + localizations: string; -} \ No newline at end of file + @Column({ nullable: true }) + is_primary: boolean; +} diff --git a/util/src/entities/Channel.ts b/src/util/entities/Channel.ts
index 69c08be7..b17fdba0 100644 --- a/util/src/entities/Channel.ts +++ b/src/util/entities/Channel.ts
@@ -1,380 +1,389 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; -import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; -import { PublicUserProjection, User } from "./User"; -import { HTTPError } from "lambert-server"; -import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters } from "../util"; -import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces"; -import { Recipient } from "./Recipient"; -import { Message } from "./Message"; -import { ReadState } from "./ReadState"; -import { Invite } from "./Invite"; -import { VoiceState } from "./VoiceState"; -import { Webhook } from "./Webhook"; -import { DmChannelDTO } from "../dtos"; - -export enum ChannelType { - GUILD_TEXT = 0, // a text channel within a guild - DM = 1, // a direct message between users - GUILD_VOICE = 2, // a voice channel within a guild - GROUP_DM = 3, // a direct message between multiple users - GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels - GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route - GUILD_STORE = 6, // a channel in which game developers can sell their things - ENCRYPTED = 7, // end-to-end encrypted channel - ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel - TRANSACTIONAL = 9, // event chain style transactional channel - GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel - GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel - GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission - GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience - DIRECTORY = 14, // guild directory listing channel - GUILD_FORUM = 15, // forum composed of IM threads - TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12 - KANBAN = 34, // confluence like kanban board - VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage) - CUSTOM_START = 64, // start custom channel types from here - UNHANDLED = 255 // unhandled unowned pass-through channel type -} - -@Entity("channels") -export class Channel extends BaseClass { - @Column() - created_at: Date; - - @Column({ nullable: true }) - name?: string; - - @Column({ type: "text", nullable: true }) - icon?: string | null; - - @Column({ type: "int" }) - type: ChannelType; - - @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - recipients?: Recipient[]; - - @Column({ nullable: true }) - last_message_id: string; - - @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.guild) - guild_id?: string; - - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { - onDelete: "CASCADE", - }) - guild: Guild; - - @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.parent) - parent_id: string; - - @JoinColumn({ name: "parent_id" }) - @ManyToOne(() => Channel) - parent?: Channel; - - // for group DMs and owned custom channel types - @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.owner) - owner_id: string; - - @JoinColumn({ name: "owner_id" }) - @ManyToOne(() => User) - owner: User; - - @Column({ nullable: true }) - last_pin_timestamp?: number; - - @Column({ nullable: true }) - default_auto_archive_duration?: number; - - @Column({ nullable: true }) - position?: number; - - @Column({ type: "simple-json", nullable: true }) - permission_overwrites?: ChannelPermissionOverwrite[]; - - @Column({ nullable: true }) - video_quality_mode?: number; - - @Column({ nullable: true }) - bitrate?: number; - - @Column({ nullable: true }) - user_limit?: number; - - @Column({ nullable: true }) - nsfw?: boolean; - - @Column({ nullable: true }) - rate_limit_per_user?: number; - - @Column({ nullable: true }) - topic?: string; - - @OneToMany(() => Invite, (invite: Invite) => invite.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - invites?: Invite[]; - - @Column({ nullable: true }) - retention_policy_id?: string; - - @OneToMany(() => Message, (message: Message) => message.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - messages?: Message[]; - - @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - voice_states?: VoiceState[]; - - @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - read_states?: ReadState[]; - - @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - webhooks?: Webhook[]; - - // TODO: DM channel - static async createChannel( - channel: Partial<Channel>, - user_id: string = "0", - opts?: { - keepId?: boolean; - skipExistsCheck?: boolean; - skipPermissionCheck?: boolean; - skipEventEmit?: boolean; - skipNameChecks?: boolean; - } - ) { - if (!opts?.skipPermissionCheck) { - // Always check if user has permission first - const permissions = await getPermission(user_id, channel.guild_id); - permissions.hasThrow("MANAGE_CHANNELS"); - } - - if (!opts?.skipNameChecks) { - const guild = await Guild.findOneOrFail({ id: channel.guild_id }); - if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) { - for (var character of InvisibleCharacters) - if (channel.name.includes(character)) - throw new HTTPError("Channel name cannot include invalid characters", 403); - - if (channel.name.match(/\-\-+/g)) - throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403) - - if (channel.name.charAt(0) === "-" || - channel.name.charAt(channel.name.length - 1) === "-") - throw new HTTPError("Channel name cannot start/end with dash.", 403) - } - - if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) { - if (!channel.name) - throw new HTTPError("Channel name cannot be empty.", 403); - } - } - - switch (channel.type) { - case ChannelType.GUILD_TEXT: - case ChannelType.GUILD_NEWS: - case ChannelType.GUILD_VOICE: - if (channel.parent_id && !opts?.skipExistsCheck) { - const exists = await Channel.findOneOrFail({ id: channel.parent_id }); - if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); - if (exists.guild_id !== channel.guild_id) - throw new HTTPError("The category channel needs to be in the guild"); - } - break; - case ChannelType.GUILD_CATEGORY: - case ChannelType.UNHANDLED: - break; - case ChannelType.DM: - case ChannelType.GROUP_DM: - throw new HTTPError("You can't create a dm channel in a guild"); - case ChannelType.GUILD_STORE: - default: - throw new HTTPError("Not yet supported"); - } - - if (!channel.permission_overwrites) channel.permission_overwrites = []; - // TODO: eagerly auto generate position of all guild channels - - channel = { - ...channel, - ...(!opts?.keepId && { id: Snowflake.generate() }), - created_at: new Date(), - position: (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0, - }; - - await Promise.all([ - new Channel(channel).save(), - !opts?.skipEventEmit - ? emitEvent({ - event: "CHANNEL_CREATE", - data: channel, - guild_id: channel.guild_id, - } as ChannelCreateEvent) - : Promise.resolve(), - ]); - - 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 you want to disallow note to self channels, uncomment the conditional below - 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 : null, // 1:1 DMs are ownerless in fosscord-server - 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 }); - } - - if (recipients.length === 1) return channel_dto; - else 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 the server user is the new owner - if (channel.owner_id === user_id) { - channel.owner_id = "1"; // The channel is now owned by the server user - 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; - } - - // Does the channel support sending messages ( eg categories do not ) - isWritable() { - const disallowedChannelTypes = [ - ChannelType.GUILD_CATEGORY, - ChannelType.GUILD_STAGE_VOICE, - ChannelType.VOICELESS_WHITEBOARD, - ]; - return disallowedChannelTypes.indexOf(this.type) == -1; - } -} - -export interface ChannelPermissionOverwrite { - allow: string; - deny: string; - id: string; - type: ChannelPermissionOverwriteType; -} - -export enum ChannelPermissionOverwriteType { - role = 0, - member = 1, - group = 2, -} +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { DmChannelDTO } from "../dtos"; +import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces"; +import { containsAll, emitEvent, getPermission, InvisibleCharacters, Snowflake, trimSpecial } from "../util"; +import { HTTPError } from "../util/imports/HTTPError"; +import { OrmUtils } from "../util/imports/OrmUtils"; +import { BaseClass } from "./BaseClass"; +import { Guild } from "./Guild"; +import { Invite } from "./Invite"; +import { Message } from "./Message"; +import { ReadState } from "./ReadState"; +import { Recipient } from "./Recipient"; +import { PublicUserProjection, User } from "./User"; +import { VoiceState } from "./VoiceState"; +import { Webhook } from "./Webhook"; + +export enum ChannelType { + GUILD_TEXT = 0, // a text channel within a guild + DM = 1, // a direct message between users + GUILD_VOICE = 2, // a voice channel within a guild + GROUP_DM = 3, // a direct message between multiple users + GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels + GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route + GUILD_STORE = 6, // a channel in which game developers can sell their things + ENCRYPTED = 7, // end-to-end encrypted channel + ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel + TRANSACTIONAL = 9, // event chain style transactional channel + GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel + GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel + GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission + GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience + DIRECTORY = 14, // guild directory listing channel + GUILD_FORUM = 15, // forum composed of IM threads + TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12 + KANBAN = 34, // confluence like kanban board + VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage) + CUSTOM_START = 64, // start custom channel types from here + UNHANDLED = 255 // unhandled unowned pass-through channel type +} + +@Entity("channels") +export class Channel extends BaseClass { + @Column() + created_at: Date; + + @Column({ nullable: true }) + name?: string; + + @Column({ type: "text", nullable: true }) + icon?: string | null; + + @Column({ type: "int" }) + type: ChannelType; + + @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + recipients?: Recipient[]; + + @Column({ nullable: true }) + last_message_id: string; + + @Column({ nullable: true }) + @RelationId((channel: Channel) => channel.guild) + guild_id?: string; + + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, { + onDelete: "CASCADE" + }) + guild: Guild; + + @Column({ nullable: true }) + @RelationId((channel: Channel) => channel.parent) + parent_id: string; + + @JoinColumn({ name: "parent_id" }) + @ManyToOne(() => Channel) + parent?: Channel; + + // for group DMs and owned custom channel types + @Column({ nullable: true }) + @RelationId((channel: Channel) => channel.owner) + owner_id: string; + + @JoinColumn({ name: "owner_id" }) + @ManyToOne(() => User) + owner: User; + + @Column({ nullable: true }) + last_pin_timestamp?: number; + + @Column({ nullable: true }) + default_auto_archive_duration?: number; + + @Column({ nullable: true }) + position?: number; + + @Column({ type: "simple-json", nullable: true }) + permission_overwrites?: ChannelPermissionOverwrite[]; + + @Column({ nullable: true }) + video_quality_mode?: number; + + @Column({ nullable: true }) + bitrate?: number; + + @Column({ nullable: true }) + user_limit?: number; + + @Column({ nullable: true }) + nsfw?: boolean; + + @Column({ nullable: true }) + rate_limit_per_user?: number; + + @Column({ nullable: true }) + topic?: string; + + @OneToMany(() => Invite, (invite: Invite) => invite.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + invites?: Invite[]; + + @Column({ nullable: true }) + retention_policy_id?: string; + + @OneToMany(() => Message, (message: Message) => message.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + messages?: Message[]; + + @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + voice_states?: VoiceState[]; + + @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + read_states?: ReadState[]; + + @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + webhooks?: Webhook[]; + + @Column({ nullable: true }) + flags?: number = 0; + + @Column({ nullable: true }) + default_thread_rate_limit_per_user?: number = 0; + + // TODO: DM channel + static async createChannel( + channel: Partial<Channel>, + user_id: string = "0", + opts?: { + keepId?: boolean; + skipExistsCheck?: boolean; + skipPermissionCheck?: boolean; + skipEventEmit?: boolean; + skipNameChecks?: boolean; + } + ) { + if (!opts?.skipPermissionCheck) { + // Always check if user has permission first + const permissions = await getPermission(user_id, channel.guild_id); + permissions.hasThrow("MANAGE_CHANNELS"); + } + + if (!opts?.skipNameChecks) { + const guild = await Guild.findOneOrFail({ where: { id: channel.guild_id } }); + if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) { + for (let character of InvisibleCharacters) + if (channel.name.includes(character)) throw new HTTPError("Channel name cannot include invalid characters", 403); + + // Categories and voice skip these checks on discord.com + const skipChecksTypes = [ChannelType.GUILD_CATEGORY, ChannelType.GUILD_VOICE]; + if ((channel.type && !skipChecksTypes.includes(channel.type)) || guild.features.includes("IRC_LIKE_CHANNEL_NAMES")) { + if (channel.name.includes(" ")) throw new HTTPError("Channel name cannot include invalid characters", 403); + + if (channel.name.match(/\-\-+/g)) throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403); + + if (channel.name.charAt(0) === "-" || channel.name.charAt(channel.name.length - 1) === "-") + throw new HTTPError("Channel name cannot start/end with dash.", 403); + } else channel.name = channel.name.trim(); //category names are trimmed client side on discord.com + } + + if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) { + if (!channel.name) throw new HTTPError("Channel name cannot be empty.", 403); + } + } + + switch (channel.type) { + case ChannelType.GUILD_TEXT: + case ChannelType.GUILD_NEWS: + case ChannelType.GUILD_VOICE: + if (channel.parent_id && !opts?.skipExistsCheck) { + const exists = await Channel.findOneOrFail({ where: { id: channel.parent_id } }); + if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); + if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild"); + } + break; + case ChannelType.GUILD_CATEGORY: + case ChannelType.UNHANDLED: + break; + case ChannelType.DM: + case ChannelType.GROUP_DM: + throw new HTTPError("You can't create a dm channel in a guild"); + case ChannelType.GUILD_STORE: + default: + throw new HTTPError("Not yet supported"); + } + + if (!channel.permission_overwrites) channel.permission_overwrites = []; + // TODO: eagerly auto generate position of all guild channels + + channel = { + ...channel, + ...(!opts?.keepId && { id: Snowflake.generate() }), + created_at: new Date(), + position: (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0 + }; + + await Promise.all([ + OrmUtils.mergeDeep(new Channel(), channel).save(), + !opts?.skipEventEmit + ? emitEvent({ + event: "CHANNEL_CREATE", + data: channel, + guild_id: channel.guild_id + } as ChannelCreateEvent) + : Promise.resolve() + ]); + + 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 you want to disallow note to self channels, uncomment the conditional below + if (otherRecipientsUsers.length !== recipients.length) { + throw new HTTPError("Recipient/s not found"); + } + **/ + + const type = recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.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; + ur = OrmUtils.mergeDeep(ur, { closed: false }); + await ur.save(); + } + } + } + } + + if (channel == null) { + name = trimSpecial(name); + + channel = await ( + OrmUtils.mergeDeep(new Channel(), { + name, + type, + owner_id: type === ChannelType.DM ? undefined : null, // 1:1 DMs are ownerless in fosscord-server + created_at: new Date(), + last_message_id: null, + recipients: channelRecipients.map((x) => + OrmUtils.mergeDeep(new Recipient(), { + user_id: x, + closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) + }) + ) + }) as Channel + ).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 }); + } + + if (recipients.length === 1) return channel_dto; + else 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 the server user is the new owner + if (channel.owner_id === user_id) { + channel.owner_id = "1"; // The channel is now owned by the server user + 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; + } + + // Does the channel support sending messages ( eg categories do not ) + isWritable() { + const disallowedChannelTypes = [ChannelType.GUILD_CATEGORY, ChannelType.GUILD_STAGE_VOICE, ChannelType.VOICELESS_WHITEBOARD]; + return disallowedChannelTypes.indexOf(this.type) == -1; + } +} + +export interface ChannelPermissionOverwrite { + allow: string; + deny: string; + id: string; + type: ChannelPermissionOverwriteType; +} + +export enum ChannelPermissionOverwriteType { + role = 0, + member = 1, + group = 2 +} diff --git a/util/src/entities/ClientRelease.ts b/src/util/entities/ClientRelease.ts
index c5afd307..2723ab67 100644 --- a/util/src/entities/ClientRelease.ts +++ b/src/util/entities/ClientRelease.ts
@@ -1,4 +1,4 @@ -import { Column, Entity} from "typeorm"; +import { Column, Entity } from "typeorm"; import { BaseClass } from "./BaseClass"; @Entity("client_release") diff --git a/util/src/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts
index 09ae30ab..018b3995 100644 --- a/util/src/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts
@@ -12,7 +12,7 @@ export class ConnectedAccount extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; diff --git a/util/src/entities/Emoji.ts b/src/util/entities/Emoji.ts
index a3615b7d..a2552995 100644 --- a/util/src/entities/Emoji.ts +++ b/src/util/entities/Emoji.ts
@@ -2,7 +2,6 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { User } from "."; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; -import { Role } from "./Role"; @Entity("emojis") export class Emoji extends BaseClass { @@ -17,7 +16,7 @@ export class Emoji extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; @@ -40,7 +39,7 @@ export class Emoji extends BaseClass { @Column({ type: "simple-array" }) roles: string[]; // roles this emoji is whitelisted to (new discord feature?) - + @Column({ type: "simple-array", nullable: true }) groups: string[]; // user groups this emoji is whitelisted to (Fosscord extension) } diff --git a/util/src/entities/Group.ts b/src/util/entities/Group.ts
index b24d38cf..23aaabf2 100644 --- a/util/src/entities/Group.ts +++ b/src/util/entities/Group.ts
@@ -1,11 +1,11 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity } from "typeorm"; import { BaseClass } from "./BaseClass"; @Entity("groups") export class UserGroup extends BaseClass { - @Column({ nullable: true }) - parent?: BigInt; + @Column({ nullable: true }) + parent?: BigInt; @Column() color: number; @@ -13,7 +13,7 @@ export class UserGroup extends BaseClass { @Column() hoist: boolean; - @Column() + @Column() mentionable: boolean; @Column() diff --git a/util/src/entities/Guild.ts b/src/util/entities/Guild.ts
index 70bb41c5..015c6d04 100644 --- a/util/src/entities/Guild.ts +++ b/src/util/entities/Guild.ts
@@ -1,5 +1,6 @@ -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { Config, handleFile, Snowflake } from ".."; +import { OrmUtils } from "../util/imports/OrmUtils"; import { Ban } from "./Ban"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; @@ -31,15 +32,7 @@ import { Webhook } from "./Webhook"; // "Gacha" // ], -export const PublicGuildRelations = [ - "channels", - "emojis", - "members", - "roles", - "stickers", - "voice_states", - "members.user", -]; +export const PublicGuildRelations = ["channels", "emojis", "members", "roles", "stickers", "voice_states", "members.user"]; @Entity("guilds") export class Guild extends BaseClass { @@ -52,7 +45,7 @@ export class Guild extends BaseClass { afk_channel?: Channel; @Column({ nullable: true }) - afk_timeout?: number; + afk_timeout?: number = Config.get().defaults.guild.afkTimeout; // * commented out -> use owner instead // application id of the guild creator if it is bot-created @@ -62,7 +55,7 @@ export class Guild extends BaseClass { @JoinColumn({ name: "ban_ids" }) @OneToMany(() => Ban, (ban: Ban) => ban.guild, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) bans: Ban[]; @@ -70,7 +63,7 @@ export class Guild extends BaseClass { banner?: string; @Column({ nullable: true }) - default_message_notifications?: number; + default_message_notifications?: number = Config.get().defaults.guild.defaultMessageNotifications; @Column({ nullable: true }) description?: string; @@ -79,7 +72,7 @@ export class Guild extends BaseClass { discovery_splash?: string; @Column({ nullable: true }) - explicit_content_filter?: number; + explicit_content_filter?: number = Config.get().defaults.guild.explicitContentFilter; @Column({ type: "simple-array" }) features: string[]; //TODO use enum @@ -95,24 +88,24 @@ export class Guild extends BaseClass { large?: boolean; @Column({ nullable: true }) - max_members?: number; // e.g. default 100.000 + max_members?: number = Config.get().limits.guild.maxMembers; // e.g. default 100.000 @Column({ nullable: true }) - max_presences?: number; + max_presences?: number = Config.get().defaults.guild.maxPresences; @Column({ nullable: true }) - max_video_channel_users?: number; // ? default: 25, is this max 25 streaming or watching + max_video_channel_users?: number = Config.get().defaults.guild.maxVideoChannelUsers; // ? default: 25, is this max 25 streaming or watching @Column({ nullable: true }) - member_count?: number; + member_count?: number = 0; @Column({ nullable: true }) - presence_count?: number; // users online + presence_count?: number = 0; // users online @OneToMany(() => Member, (member: Member) => member.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) members: Member[]; @@ -120,14 +113,14 @@ export class Guild extends BaseClass { @OneToMany(() => Role, (role: Role) => role.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) roles: Role[]; @JoinColumn({ name: "channel_ids" }) @OneToMany(() => Channel, (channel: Channel) => channel.guild, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) channels: Channel[]; @@ -143,7 +136,7 @@ export class Guild extends BaseClass { @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) emojis: Emoji[]; @@ -151,7 +144,7 @@ export class Guild extends BaseClass { @OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) stickers: Sticker[]; @@ -159,7 +152,7 @@ export class Guild extends BaseClass { @OneToMany(() => Invite, (invite: Invite) => invite.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) invites: Invite[]; @@ -167,7 +160,7 @@ export class Guild extends BaseClass { @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) voice_states: VoiceState[]; @@ -175,7 +168,7 @@ export class Guild extends BaseClass { @OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) webhooks: Webhook[]; @@ -269,7 +262,7 @@ export class Guild extends BaseClass { @Column({ nullable: true }) nsfw?: boolean; - + // TODO: nested guilds @Column({ nullable: true }) parent?: string; @@ -277,15 +270,14 @@ export class Guild extends BaseClass { // only for developer portal permissions?: number; - static async createGuild(body: { - name?: string; - icon?: string | null; - owner_id?: string; - channels?: Partial<Channel>[]; - }) { + //new guild settings, 11/08/2022: + @Column({ nullable: true }) + premium_progress_bar_enabled: boolean = false; + + static async createGuild(body: { name?: string; icon?: string | null; owner_id?: string; channels?: Partial<Channel>[] }) { const guild_id = Snowflake.generate(); - const guild = await new Guild({ + const guild: Guild = OrmUtils.mergeDeep(new Guild(), { name: body.name || "Fosscord", icon: await handleFile(`/icons/${guild_id}`, body.icon as string), region: Config.get().regions.default, @@ -313,14 +305,15 @@ export class Guild extends BaseClass { welcome_screen: { enabled: false, description: "Fill in your description", - welcome_channels: [], + welcome_channels: [] }, - widget_enabled: true, // NB: don't set it as false to prevent artificial restrictions - }).save(); + widget_enabled: true // NB: don't set it as false to prevent artificial restrictions + }); + await guild.save(); // we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error // TODO: make the @everyone a pseudorole that is dynamically generated at runtime so we can save storage - await new Role({ + let role: Role = OrmUtils.mergeDeep(new Role(), { id: guild_id, guild_id: guild_id, color: 0, @@ -333,7 +326,8 @@ export class Guild extends BaseClass { position: 0, icon: null, unicode_emoji: null - }).save(); + }); + await role.save(); if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }]; @@ -346,15 +340,15 @@ export class Guild extends BaseClass { }); for (const channel of body.channels?.sort((a, b) => (a.parent_id ? 1 : -1))) { - var id = ids.get(channel.id) || Snowflake.generate(); + let id = ids.get(channel.id) || Snowflake.generate(); - var parent_id = ids.get(channel.parent_id); + let parent_id = ids.get(channel.parent_id); await Channel.createChannel({ ...channel, guild_id, id, parent_id }, body.owner_id, { keepId: true, skipExistsCheck: true, skipPermissionCheck: true, - skipEventEmit: true, + skipEventEmit: true }); } diff --git a/util/src/entities/Invite.ts b/src/util/entities/Invite.ts
index 6ac64ddc..f6ba85d7 100644 --- a/util/src/entities/Invite.ts +++ b/src/util/entities/Invite.ts
@@ -1,8 +1,9 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId, PrimaryColumn } from "typeorm"; -import { Member } from "./Member"; +import { random } from "@fosscord/api"; +import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from "typeorm"; import { BaseClassWithoutId } from "./BaseClass"; import { Channel } from "./Channel"; import { Guild } from "./Guild"; +import { Member } from "./Member"; import { User } from "./User"; export const PublicInviteRelation = ["inviter", "guild", "channel"]; @@ -10,13 +11,13 @@ export const PublicInviteRelation = ["inviter", "guild", "channel"]; @Entity("invites") export class Invite extends BaseClassWithoutId { @PrimaryColumn() - code: string; + code: string = random(); @Column() - temporary: boolean; + temporary: boolean = true; @Column() - uses: number; + uses: number = 0; @Column() max_uses: number; @@ -25,7 +26,7 @@ export class Invite extends BaseClassWithoutId { max_age: number; @Column() - created_at: Date; + created_at: Date = new Date(); @Column() expires_at: Date; @@ -36,7 +37,7 @@ export class Invite extends BaseClassWithoutId { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; @@ -46,7 +47,7 @@ export class Invite extends BaseClassWithoutId { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -55,7 +56,9 @@ export class Invite extends BaseClassWithoutId { inviter_id: string; @JoinColumn({ name: "inviter_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE" + }) inviter: User; @Column({ nullable: true }) @@ -64,7 +67,7 @@ export class Invite extends BaseClassWithoutId { @JoinColumn({ name: "target_user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) target_user?: string; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62 @@ -75,7 +78,7 @@ export class Invite extends BaseClassWithoutId { vanity_url?: boolean; static async joinGuild(user_id: string, code: string) { - const invite = await Invite.findOneOrFail({ code }); + const invite = await Invite.findOneOrFail({ where: { code } }); if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code }); else await invite.save(); diff --git a/util/src/entities/Member.ts b/src/util/entities/Member.ts
index a6798a7c..f67ec02c 100644 --- a/util/src/entities/Member.ts +++ b/src/util/entities/Member.ts
@@ -1,32 +1,14 @@ -import { PublicUser, User } from "./User"; -import { Message } from "./Message"; -import { BaseClass } from "./BaseClass"; -import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, - PrimaryGeneratedColumn, - RelationId, -} from "typeorm"; -import { Guild } from "./Guild"; +import { Column, Entity, Index, JoinColumn, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, RelationId } from "typeorm"; +import { Ban, PublicGuildRelations, Message } from "."; +import { GuildCreateEvent, GuildDeleteEvent, GuildMemberAddEvent, GuildMemberRemoveEvent, GuildMemberUpdateEvent, MessageCreateEvent } from "../interfaces"; import { Config, emitEvent } from "../util"; -import { - GuildCreateEvent, - GuildDeleteEvent, - GuildMemberAddEvent, - GuildMemberRemoveEvent, - GuildMemberUpdateEvent, - MessageCreateEvent, -} from "../interfaces"; -import { HTTPError } from "lambert-server"; -import { Role } from "./Role"; -import { BaseClassWithoutId } from "./BaseClass"; -import { Ban, PublicGuildRelations } from "."; import { DiscordApiErrors } from "../util/Constants"; +import { HTTPError } from "../util/imports/HTTPError"; +import { OrmUtils } from "../util/imports/OrmUtils"; +import { BaseClassWithoutId } from "./BaseClass"; +import { Guild } from "./Guild"; +import { Role } from "./Role"; +import { PublicUser, User } from "./User"; export const MemberPrivateProjection: (keyof Member)[] = [ "id", @@ -41,7 +23,7 @@ export const MemberPrivateProjection: (keyof Member)[] = [ "premium_since", "roles", "settings", - "user", + "user" ]; @Entity("members") @@ -56,7 +38,7 @@ export class Member extends BaseClassWithoutId { @JoinColumn({ name: "id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; @@ -66,7 +48,7 @@ export class Member extends BaseClassWithoutId { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; @@ -78,8 +60,8 @@ export class Member extends BaseClassWithoutId { joinColumn: { name: "index", referencedColumnName: "index" }, inverseJoinColumn: { name: "role_id", - referencedColumnName: "id", - }, + referencedColumnName: "id" + } }) @ManyToMany(() => Role, { cascade: true }) roles: Role[]; @@ -87,8 +69,8 @@ export class Member extends BaseClassWithoutId { @Column() joined_at: Date; - @Column({ type: "bigint", nullable: true }) - premium_since?: number; + @Column({ nullable: true }) + premium_since?: Date; @Column() deaf: boolean; @@ -119,35 +101,37 @@ export class Member extends BaseClassWithoutId { // read_state: ReadState; static async IsInGuildOrFail(user_id: string, guild_id: string) { - if (await Member.count({ id: user_id, guild: { id: guild_id } })) return true; + if (await Member.count({ where: { id: user_id, guild: { id: guild_id } } })) return true; throw new HTTPError("You are not member of this guild", 403); } static async removeFromGuild(user_id: string, guild_id: string) { - const guild = await Guild.findOneOrFail({ select: ["owner_id"], where: { id: guild_id } }); + const guild = await Guild.findOneOrFail({ select: ["owner_id", "member_count"], where: { id: guild_id } }); if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild"); const member = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user"] }); // use promise all to execute all promises at the same time -> save time + //TODO: check for bugs + if (guild.member_count) guild.member_count--; return Promise.all([ Member.delete({ id: user_id, - guild_id, + guild_id }), - Guild.decrement({ id: guild_id }, "member_count", -1), + //Guild.decrement({ id: guild_id }, "member_count", -1), emitEvent({ event: "GUILD_DELETE", data: { - id: guild_id, + id: guild_id }, - user_id: user_id, + user_id: user_id } as GuildDeleteEvent), emitEvent({ event: "GUILD_MEMBER_REMOVE", data: { guild_id, user: member.user }, - guild_id, - } as GuildMemberRemoveEvent), + guild_id + } as GuildMemberRemoveEvent) ]); } @@ -157,11 +141,11 @@ export class Member extends BaseClassWithoutId { Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user", "roles"], // we don't want to load the role objects just the ids - select: ["index", "roles.id"], + select: ["index"] }), - Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }), + Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }) ]); - member.roles.push(new Role({ id: role_id })); + member.roles.push(OrmUtils.mergeDeep(new Role(), { id: role_id })); await Promise.all([ member.save(), @@ -170,10 +154,10 @@ export class Member extends BaseClassWithoutId { data: { guild_id, user: member.user, - roles: member.roles.map((x) => x.id), + roles: member.roles.map((x) => x.id) }, - guild_id, - } as GuildMemberUpdateEvent), + guild_id + } as GuildMemberUpdateEvent) ]); } @@ -183,9 +167,9 @@ export class Member extends BaseClassWithoutId { Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user", "roles"], // we don't want to load the role objects just the ids - select: ["roles.id", "index"], + select: ["index"] }), - await Role.findOneOrFail({ id: role_id, guild_id }), + await Role.findOneOrFail({ where: { id: role_id, guild_id } }) ]); member.roles = member.roles.filter((x) => x.id == role_id); @@ -196,10 +180,10 @@ export class Member extends BaseClassWithoutId { data: { guild_id, user: member.user, - roles: member.roles.map((x) => x.id), + roles: member.roles.map((x) => x.id) }, - guild_id, - } as GuildMemberUpdateEvent), + guild_id + } as GuildMemberUpdateEvent) ]); } @@ -207,9 +191,9 @@ export class Member extends BaseClassWithoutId { const member = await Member.findOneOrFail({ where: { id: user_id, - guild_id, + guild_id }, - relations: ["user"], + relations: ["user"] }); member.nick = nickname; @@ -221,10 +205,10 @@ export class Member extends BaseClassWithoutId { data: { guild_id, user: member.user, - nick: nickname, + nick: nickname }, - guild_id, - } as GuildMemberUpdateEvent), + guild_id + } as GuildMemberUpdateEvent) ]); } @@ -235,19 +219,19 @@ export class Member extends BaseClassWithoutId { throw DiscordApiErrors.USER_BANNED; } const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ id: user_id }); + const guild_count = await Member.count({ where: { id: user_id } }); if (guild_count >= maxGuilds) { throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403); } const guild = await Guild.findOneOrFail({ where: { - id: guild_id, + id: guild_id }, - relations: [ ...PublicGuildRelations, "system_channel" ], + relations: PublicGuildRelations }); - if (await Member.count({ id: user.id, guild: { id: guild_id } })) + if (await Member.count({ where: { id: user.id, guild: { id: guild_id } } })) throw new HTTPError("You are already a member of this guild", 400); const member = { @@ -256,16 +240,17 @@ export class Member extends BaseClassWithoutId { nick: undefined, roles: [guild_id], // @everyone role joined_at: new Date(), - premium_since: (new Date()).getTime(), + premium_since: null, deaf: false, mute: false, - pending: false, + pending: false }; - + //TODO: check for bugs + if (guild.member_count) guild.member_count++; await Promise.all([ - new Member({ + OrmUtils.mergeDeep(new Member(), { ...member, - roles: [new Role({ id: guild_id })], + roles: [OrmUtils.mergeDeep(new Role(), { id: guild_id })], // read_state: {}, settings: { channel_overrides: [], @@ -274,19 +259,19 @@ export class Member extends BaseClassWithoutId { muted: false, suppress_everyone: false, suppress_roles: false, - version: 0, - }, + version: 0 + } // Member.save is needed because else the roles relations wouldn't be updated }).save(), - Guild.increment({ id: guild_id }, "member_count", 1), + //Guild.increment({ id: guild_id }, "member_count", 1), emitEvent({ event: "GUILD_MEMBER_ADD", data: { ...member, user, - guild_id, + guild_id }, - guild_id, + guild_id } as GuildMemberAddEvent), emitEvent({ event: "GUILD_CREATE", @@ -299,15 +284,15 @@ export class Member extends BaseClassWithoutId { joined_at: member.joined_at, presences: [], stage_instances: [], - threads: [], + threads: [] }, - user_id, - } as GuildCreateEvent), + user_id + } as GuildCreateEvent) ]); if (guild.system_channel_id) { // send welcome message - const message = new Message({ + const message = OrmUtils.mergeDeep(new Message(), { type: 7, guild_id: guild.id, channel_id: guild.system_channel_id, @@ -349,16 +334,7 @@ export interface MuteConfig { selected_time_window: number; } -export type PublicMemberKeys = - | "id" - | "guild_id" - | "nick" - | "roles" - | "joined_at" - | "pending" - | "deaf" - | "mute" - | "premium_since"; +export type PublicMemberKeys = "id" | "guild_id" | "nick" | "roles" | "joined_at" | "pending" | "deaf" | "mute" | "premium_since"; export const PublicMemberProjection: PublicMemberKeys[] = [ "id", @@ -369,7 +345,7 @@ export const PublicMemberProjection: PublicMemberKeys[] = [ "pending", "deaf", "mute", - "premium_since", + "premium_since" ]; // @ts-ignore diff --git a/util/src/entities/Message.ts b/src/util/entities/Message.ts
index e18cf691..8122b532 100644 --- a/util/src/entities/Message.ts +++ b/src/util/entities/Message.ts
@@ -1,29 +1,15 @@ -import { User } from "./User"; -import { Member } from "./Member"; -import { Role } from "./Role"; -import { Channel } from "./Channel"; +import { Column, CreateDateColumn, Entity, Index, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; import { InteractionType } from "../interfaces/Interaction"; import { Application } from "./Application"; -import { - Column, - CreateDateColumn, - Entity, - FindConditions, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, - OneToMany, - RelationId, - RemoveOptions, - UpdateDateColumn, -} from "typeorm"; +import { Attachment } from "./Attachment"; import { BaseClass } from "./BaseClass"; +import { Channel } from "./Channel"; import { Guild } from "./Guild"; -import { Webhook } from "./Webhook"; +import { Member } from "./Member"; +import { Role } from "./Role"; import { Sticker } from "./Sticker"; -import { Attachment } from "./Attachment"; +import { User } from "./User"; +import { Webhook } from "./Webhook"; export enum MessageType { DEFAULT = 0, @@ -63,7 +49,7 @@ export class Message extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -73,7 +59,7 @@ export class Message extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild?: Guild; @@ -84,7 +70,7 @@ export class Message extends BaseClass { @JoinColumn({ name: "author_id", referencedColumnName: "id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) author?: User; @@ -94,7 +80,7 @@ export class Message extends BaseClass { @JoinColumn({ name: "member_id", referencedColumnName: "id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) member?: Member; @@ -148,7 +134,7 @@ export class Message extends BaseClass { @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) attachments?: Attachment[]; @@ -213,7 +199,7 @@ export interface MessageComponent { export enum MessageComponentType { Script = 0, // self command script ActionRow = 1, - Button = 2, + Button = 2 } export interface Embed { @@ -254,7 +240,7 @@ export enum EmbedType { video = "video", gifv = "gifv", article = "article", - link = "link", + link = "link" } export interface EmbedImage { diff --git a/util/src/entities/Migration.ts b/src/util/entities/Migration.ts
index 3f39ae72..626ec429 100644 --- a/util/src/entities/Migration.ts +++ b/src/util/entities/Migration.ts
@@ -1,9 +1,7 @@ import { Column, Entity, ObjectIdColumn, PrimaryGeneratedColumn } from "typeorm"; import { BaseClassWithoutId } from "."; -export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith("mongodb") - ? ObjectIdColumn - : PrimaryGeneratedColumn; +export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryGeneratedColumn; @Entity("migrations") export class Migration extends BaseClassWithoutId { diff --git a/util/src/entities/RateLimit.ts b/src/util/entities/RateLimit.ts
index f5916f6b..f5916f6b 100644 --- a/util/src/entities/RateLimit.ts +++ b/src/util/entities/RateLimit.ts
diff --git a/util/src/entities/ReadState.ts b/src/util/entities/ReadState.ts
index b915573b..77d2c08a 100644 --- a/util/src/entities/ReadState.ts +++ b/src/util/entities/ReadState.ts
@@ -1,7 +1,6 @@ import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; -import { Message } from "./Message"; import { User } from "./User"; // for read receipts @@ -17,7 +16,7 @@ export class ReadState extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -27,14 +26,14 @@ export class ReadState extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; // fully read marker @Column({ nullable: true }) - last_message_id: string; - + last_message_id: string; + // public read receipt @Column({ nullable: true }) public_ack: string; diff --git a/util/src/entities/Recipient.ts b/src/util/entities/Recipient.ts
index a945f938..fc9e629b 100644 --- a/util/src/entities/Recipient.ts +++ b/src/util/entities/Recipient.ts
@@ -9,7 +9,7 @@ export class Recipient extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => require("./Channel").Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: import("./Channel").Channel; @@ -19,7 +19,7 @@ export class Recipient extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => require("./User").User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: import("./User").User; diff --git a/util/src/entities/Relationship.ts b/src/util/entities/Relationship.ts
index c3592c76..b55d9e64 100644 --- a/util/src/entities/Relationship.ts +++ b/src/util/entities/Relationship.ts
@@ -6,7 +6,7 @@ export enum RelationshipType { outgoing = 4, incoming = 3, blocked = 2, - friends = 1, + friends = 1 } @Entity("relationships") @@ -18,7 +18,7 @@ export class Relationship extends BaseClass { @JoinColumn({ name: "from_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) from: User; @@ -28,7 +28,7 @@ export class Relationship extends BaseClass { @JoinColumn({ name: "to_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) to: User; @@ -43,7 +43,7 @@ export class Relationship extends BaseClass { id: this.to?.id || this.to_id, type: this.type, nickname: this.nickname, - user: this.to?.toPublicUser(), + user: this.to?.toPublicUser() }; } } diff --git a/util/src/entities/Role.ts b/src/util/entities/Role.ts
index 4b721b5b..b1fd9bb1 100644 --- a/util/src/entities/Role.ts +++ b/src/util/entities/Role.ts
@@ -11,7 +11,7 @@ export class Role extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; diff --git a/util/src/entities/Session.ts b/src/util/entities/Session.ts
index 969efa89..0cb4c309 100644 --- a/util/src/entities/Session.ts +++ b/src/util/entities/Session.ts
@@ -1,8 +1,8 @@ -import { User } from "./User"; -import { BaseClass } from "./BaseClass"; import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; -import { Status } from "../interfaces/Status"; import { Activity } from "../interfaces/Activity"; +import { Status } from "../interfaces/Status"; +import { BaseClass } from "./BaseClass"; +import { User } from "./User"; //TODO we need to remove all sessions on server start because if the server crashes without closing websockets it won't delete them @@ -14,7 +14,7 @@ export class Session extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; @@ -37,10 +37,4 @@ export class Session extends BaseClass { status: Status; //TODO enum } -export const PrivateSessionProjection: (keyof Session)[] = [ - "user_id", - "session_id", - "activities", - "client_info", - "status", -]; +export const PrivateSessionProjection: (keyof Session)[] = ["user_id", "session_id", "activities", "client_info", "status"]; diff --git a/util/src/entities/Sticker.ts b/src/util/entities/Sticker.ts
index 37bc6fbe..69836e62 100644 --- a/util/src/entities/Sticker.ts +++ b/src/util/entities/Sticker.ts
@@ -1,18 +1,18 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; -import { User } from "./User"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; +import { User } from "./User"; export enum StickerType { STANDARD = 1, - GUILD = 2, + GUILD = 2 } export enum StickerFormatType { GIF = 0, // gif is a custom format type and not in discord spec PNG = 1, APNG = 2, - LOTTIE = 3, + LOTTIE = 3 } @Entity("stickers") @@ -36,7 +36,7 @@ export class Sticker extends BaseClass { @JoinColumn({ name: "pack_id" }) @ManyToOne(() => require("./StickerPack").StickerPack, { onDelete: "CASCADE", - nullable: true, + nullable: true }) pack: import("./StickerPack").StickerPack; @@ -45,7 +45,7 @@ export class Sticker extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild?: Guild; @@ -54,7 +54,7 @@ export class Sticker extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user?: User; diff --git a/util/src/entities/StickerPack.ts b/src/util/entities/StickerPack.ts
index ec8c69a2..4619af34 100644 --- a/util/src/entities/StickerPack.ts +++ b/src/util/entities/StickerPack.ts
@@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { Sticker } from "."; import { BaseClass } from "./BaseClass"; @@ -15,7 +15,7 @@ export class StickerPack extends BaseClass { @OneToMany(() => Sticker, (sticker: Sticker) => sticker.pack, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) stickers: Sticker[]; diff --git a/util/src/entities/Team.ts b/src/util/entities/Team.ts
index 22140b7f..1d2d7002 100644 --- a/util/src/entities/Team.ts +++ b/src/util/entities/Team.ts
@@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { TeamMember } from "./TeamMember"; import { User } from "./User"; @@ -10,7 +10,7 @@ export class Team extends BaseClass { @JoinColumn({ name: "member_ids" }) @OneToMany(() => TeamMember, (member: TeamMember) => member.team, { - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) members: TeamMember[]; diff --git a/util/src/entities/TeamMember.ts b/src/util/entities/TeamMember.ts
index b726e1e8..d11ebf95 100644 --- a/util/src/entities/TeamMember.ts +++ b/src/util/entities/TeamMember.ts
@@ -4,7 +4,7 @@ import { User } from "./User"; export enum TeamMemberState { INVITED = 1, - ACCEPTED = 2, + ACCEPTED = 2 } @Entity("team_members") @@ -21,7 +21,7 @@ export class TeamMember extends BaseClass { @JoinColumn({ name: "team_id" }) @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) team: import("./Team").Team; @@ -31,7 +31,7 @@ export class TeamMember extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; } diff --git a/util/src/entities/Template.ts b/src/util/entities/Template.ts
index 1d952283..1d952283 100644 --- a/util/src/entities/Template.ts +++ b/src/util/entities/Template.ts
diff --git a/util/src/entities/User.ts b/src/util/entities/User.ts
index 9b1c494e..1237b676 100644 --- a/util/src/entities/User.ts +++ b/src/util/entities/User.ts
@@ -1,10 +1,11 @@ -import { Column, Entity, FindOneOptions, JoinColumn, ManyToMany, OneToMany, RelationId } from "typeorm"; -import { BaseClass } from "./BaseClass"; +import { Column, Entity, FindOneOptions, FindOptionsSelectByString, JoinColumn, OneToMany, OneToOne } from "typeorm"; +import { Member, Session, UserSettings } from "."; +import { Config, FieldErrors, Snowflake, trimSpecial } from ".."; import { BitField } from "../util/BitField"; -import { Relationship } from "./Relationship"; +import { OrmUtils } from "../util/imports/OrmUtils"; +import { BaseClass } from "./BaseClass"; import { ConnectedAccount } from "./ConnectedAccount"; -import { Config, FieldErrors, Snowflake, trimSpecial } from ".."; -import { Member, Session } from "."; +import { Relationship } from "./Relationship"; export enum PublicUserEnum { username, @@ -16,7 +17,7 @@ export enum PublicUserEnum { banner, bio, bot, - premium_since, + premium_since } export type PublicUserKeys = keyof typeof PublicUserEnum; @@ -30,17 +31,15 @@ export enum PrivateUserEnum { premium, premium_type, disabled, - settings, + settings // locale } export type PrivateUserKeys = keyof typeof PrivateUserEnum | PublicUserKeys; -export const PublicUserProjection = Object.values(PublicUserEnum).filter( - (x) => typeof x === "string" -) as PublicUserKeys[]; +export const PublicUserProjection = Object.values(PublicUserEnum).filter((x) => typeof x === "string") as PublicUserKeys[]; export const PrivateUserProjection = [ ...PublicUserProjection, - ...Object.values(PrivateUserEnum).filter((x) => typeof x === "string"), + ...Object.values(PrivateUserEnum).filter((x) => typeof x === "string") ] as PrivateUserKeys[]; // Private user data that should never get sent to the client @@ -82,58 +81,64 @@ export class User extends BaseClass { phone?: string; // phone number of the user @Column({ select: false }) - desktop: boolean; // if the user has desktop app installed + desktop: boolean = false; // if the user has desktop app installed @Column({ select: false }) - mobile: boolean; // if the user has mobile app installed + mobile: boolean = false; // if the user has mobile app installed @Column() - premium: boolean; // if user bought individual premium - - @Column() - premium_type: number; // individual premium level + premium: boolean = Config.get().defaults.user.premium; // if user bought individual premium @Column() - bot: boolean; // if user is bot + premium_type: number = Config.get().defaults.user.premium_type; // individual premium level @Column() + bot: boolean = false; // if user is bot + + @Column({ nullable: true }) bio: string; // short description of the user (max 190 chars -> should be configurable) @Column() - system: boolean; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author + system: boolean = false; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author @Column({ select: false }) - nsfw_allowed: boolean; // if the user can do age-restricted actions (NSFW channels/guilds/commands) - - @Column({ select: false }) + nsfw_allowed: boolean = true; // if the user can do age-restricted actions (NSFW channels/guilds/commands) // TODO: depending on age + + @Column({ select: false, nullable: true }) mfa_enabled: boolean; // if multi factor authentication is enabled + @Column({ select: false, nullable: true }) + totp_secret?: string; + + @Column({ nullable: true, select: false }) + totp_last_ticket?: string; + @Column() - created_at: Date; // registration date + created_at: Date = new Date(); // registration date @Column({ nullable: true }) - premium_since: Date; // premium date + premium_since: Date = new Date(); // premium date @Column({ select: false }) - verified: boolean; // if the user is offically verified + verified: boolean = Config.get().defaults.user.verified; // if the user is offically verified @Column() - disabled: boolean; // if the account is disabled + disabled: boolean = false; // if the account is disabled @Column() - deleted: boolean; // if the user was deleted + deleted: boolean = false; // if the user was deleted @Column({ nullable: true, select: false }) email?: string; // email of the user @Column() - flags: string; // UserFlags + flags: string = "0"; // UserFlags // TODO: generate @Column() - public_flags: number; + public_flags: number = 0; @Column({ type: "bigint" }) - rights: string; // Rights + rights: string = Config.get().register.defaultRights; // Rights @OneToMany(() => Session, (session: Session) => session.user) sessions: Session[]; @@ -141,14 +146,14 @@ export class User extends BaseClass { @JoinColumn({ name: "relationship_ids" }) @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) relationships: Relationship[]; @JoinColumn({ name: "connected_account_ids" }) @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) connected_accounts: ConnectedAccount[]; @@ -159,17 +164,29 @@ export class User extends BaseClass { }; @Column({ type: "simple-array", select: false }) - fingerprints: string[]; // array of fingerprints -> used to prevent multiple accounts + fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts - @Column({ type: "simple-json", select: false }) + @OneToOne(() => UserSettings, { + cascade: true, + orphanedRowAction: "delete", + eager: false + }) + @JoinColumn() settings: UserSettings; - + // workaround to prevent fossord-unaware clients from deleting settings not used by them @Column({ type: "simple-json", select: false }) - extended_settings: string; + extended_settings: string = "{}"; @Column({ type: "simple-json" }) - notes: { [key: string]: string }; //key is ID of user + notes: { [key: string]: string } = {}; //key is ID of user + + async save(): Promise<any> { + if (!this.settings) this.settings = new UserSettings(); + this.settings.id = this.id; + //await this.settings.save(); + return super.save(); + } toPublicUser() { const user: any = {}; @@ -180,19 +197,17 @@ export class User extends BaseClass { } static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) { - return await User.findOneOrFail( - { id: user_id }, - { - ...opts, - select: [...PublicUserProjection, ...(opts?.select || [])], - } - ); + return await User.findOneOrFail({ + where: { id: user_id }, + select: [...PublicUserProjection, ...((opts?.select as FindOptionsSelectByString<User>) || [])], + ...opts + }); } - private static async generateDiscriminator(username: string): Promise<string | undefined> { + public static async generateDiscriminator(username: string): Promise<string | undefined> { if (Config.get().register.incrementingDiscriminators) { // discriminator will be incrementally generated - + // First we need to figure out the currently highest discrimnator for the given username and then increment it const users = await User.find({ where: { username }, select: ["discriminator"] }); const highestDiscriminator = Math.max(0, ...users.map((u) => Number(u.discriminator))); @@ -223,7 +238,7 @@ export class User extends BaseClass { username, password, date_of_birth, - req, + req }: { username: string; password?: string; @@ -240,48 +255,30 @@ export class User extends BaseClass { throw FieldErrors({ username: { code: "USERNAME_TOO_MANY_USERS", - message: req.t("auth:register.USERNAME_TOO_MANY_USERS"), - }, + message: req?.t("auth:register.USERNAME_TOO_MANY_USERS") + } }); } // TODO: save date_of_birth // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false - const language = req.language === "en" ? "en-US" : req.language || "en-US"; + const language = req?.language === "en" ? "en-US" : req?.language || "en-US"; - const user = new User({ - created_at: new Date(), + const user = OrmUtils.mergeDeep(new User(), { + //required: username: username, discriminator, id: Snowflake.generate(), - bot: false, - system: false, - premium_since: new Date(), - desktop: false, - mobile: false, - premium: true, - premium_type: 2, - bio: "", - mfa_enabled: false, - verified: true, - disabled: false, - deleted: false, email: email, - rights: "0", // TODO: grant rights correctly, as 0 actually stands for no rights at all - nsfw_allowed: true, // TODO: depending on age - public_flags: "0", - flags: "0", // TODO: generate data: { hash: password, - valid_tokens_since: new Date(), + valid_tokens_since: new Date() }, - settings: { ...defaultSettings, locale: language }, - extended_settings: {}, - fingerprints: [], - notes: {}, + settings: { ...new UserSettings(), locale: language } }); + //await (user.settings as UserSettings).save(); await user.save(); setImmediate(async () => { @@ -296,85 +293,6 @@ export class User extends BaseClass { } } -export const defaultSettings: UserSettings = { - afk_timeout: 3600, - allow_accessibility_detection: true, - animate_emoji: true, - animate_stickers: 0, - contact_sync_enabled: false, - convert_emoticons: false, - custom_status: null, - default_guilds_restricted: false, - detect_platform_accounts: false, - developer_mode: true, - disable_games_tab: true, - enable_tts_command: false, - explicit_content_filter: 0, - friend_source_flags: { all: true }, - gateway_connected: false, - gif_auto_play: true, - guild_folders: [], - guild_positions: [], - inline_attachment_media: true, - inline_embed_media: true, - locale: "en-US", - message_display_compact: true, - native_phone_integration_enabled: true, - render_embeds: true, - render_reactions: true, - restricted_guilds: [], - show_current_game: true, - status: "online", - stream_notifications_enabled: false, - theme: "dark", - timezone_offset: 0, // TODO: timezone from request -}; - -export interface UserSettings { - afk_timeout: number; - allow_accessibility_detection: boolean; - animate_emoji: boolean; - animate_stickers: number; - contact_sync_enabled: boolean; - convert_emoticons: boolean; - custom_status: { - emoji_id?: string; - emoji_name?: string; - expires_at?: number; - text?: string; - } | null; - default_guilds_restricted: boolean; - detect_platform_accounts: boolean; - developer_mode: boolean; - disable_games_tab: boolean; - enable_tts_command: boolean; - explicit_content_filter: number; - friend_source_flags: { all: boolean }; - gateway_connected: boolean; - gif_auto_play: boolean; - // every top guild is displayed as a "folder" - guild_folders: { - color: number; - guild_ids: string[]; - id: number; - name: string; - }[]; - guild_positions: string[]; // guild ids ordered by position - inline_attachment_media: boolean; - inline_embed_media: boolean; - locale: string; // en_US - message_display_compact: boolean; - native_phone_integration_enabled: boolean; - render_embeds: boolean; - render_reactions: boolean; - restricted_guilds: string[]; - show_current_game: boolean; - status: "online" | "offline" | "dnd" | "idle" | "invisible"; - stream_notifications_enabled: boolean; - theme: "dark" | "white"; // dark - timezone_offset: number; // e.g -60 -} - export const CUSTOM_USER_FLAG_OFFSET = BigInt(1) << BigInt(32); export class UserFlags extends BitField { @@ -398,6 +316,6 @@ export class UserFlags extends BitField { VERIFIED_BOT: BigInt(1) << BigInt(16), EARLY_VERIFIED_BOT_DEVELOPER: BigInt(1) << BigInt(17), CERTIFIED_MODERATOR: BigInt(1) << BigInt(18), - BOT_HTTP_INTERACTIONS: BigInt(1) << BigInt(19), + BOT_HTTP_INTERACTIONS: BigInt(1) << BigInt(19) }; } diff --git a/util/src/entities/UserGroup.ts b/src/util/entities/UserGroup.ts
index 709b9d0b..08d68a4e 100644 --- a/util/src/entities/UserGroup.ts +++ b/src/util/entities/UserGroup.ts
@@ -1,7 +1,6 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; import { User } from "./User"; @Entity("groups") @@ -11,11 +10,11 @@ export class UserGroup extends BaseClass { @Column() hoist: boolean; - + @JoinColumn({ name: "controller", referencedColumnName: "id" }) @ManyToOne(() => User) controller?: User; - + @Column() mentionable_by?: string; @@ -27,11 +26,10 @@ export class UserGroup extends BaseClass { @Column({ nullable: true }) icon: string; - + @Column({ nullable: true }) parent?: string; - - @Column({ type: "simple-array", nullable: true}) - associciations: string[]; + @Column({ type: "simple-array", nullable: true }) + associciations: string[]; } diff --git a/util/src/entities/VoiceState.ts b/src/util/entities/VoiceState.ts
index 75748a01..baf2c687 100644 --- a/util/src/entities/VoiceState.ts +++ b/src/util/entities/VoiceState.ts
@@ -2,8 +2,8 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; import { Guild } from "./Guild"; -import { User } from "./User"; import { Member } from "./Member"; +import { User } from "./User"; //https://gist.github.com/vassjozsef/e482c65df6ee1facaace8b3c9ff66145#file-voice_state-ex @Entity("voice_states") @@ -14,7 +14,7 @@ export class VoiceState extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild?: Guild; @@ -24,7 +24,7 @@ export class VoiceState extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -34,7 +34,7 @@ export class VoiceState extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; diff --git a/util/src/entities/Webhook.ts b/src/util/entities/Webhook.ts
index 89538417..3d94ddb6 100644 --- a/util/src/entities/Webhook.ts +++ b/src/util/entities/Webhook.ts
@@ -7,7 +7,7 @@ import { User } from "./User"; export enum WebhookType { Incoming = 1, - ChannelFollower = 2, + ChannelFollower = 2 } @Entity("webhooks") @@ -30,7 +30,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; @@ -40,7 +40,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -50,7 +50,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "application_id" }) @ManyToOne(() => Application, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) application: Application; @@ -60,7 +60,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; @@ -70,7 +70,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "source_guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) source_guild: Guild; } diff --git a/util/src/entities/index.ts b/src/util/entities/index.ts
index f023d5a6..2b91c2ba 100644 --- a/util/src/entities/index.ts +++ b/src/util/entities/index.ts
@@ -1,10 +1,12 @@ export * from "./Application"; export * from "./Attachment"; export * from "./AuditLog"; +export * from "./BackupCodes"; export * from "./Ban"; export * from "./BaseClass"; export * from "./Categories"; export * from "./Channel"; +export * from "./ClientRelease"; export * from "./Config"; export * from "./ConnectedAccount"; export * from "./Emoji"; @@ -13,6 +15,7 @@ export * from "./Invite"; export * from "./Member"; export * from "./Message"; export * from "./Migration"; +export * from "./Note"; export * from "./RateLimit"; export * from "./ReadState"; export * from "./Recipient"; @@ -25,6 +28,6 @@ export * from "./Team"; export * from "./TeamMember"; export * from "./Template"; export * from "./User"; +export * from "./UserSettings"; export * from "./VoiceState"; export * from "./Webhook"; -export * from "./ClientRelease"; \ No newline at end of file diff --git a/util/src/index.ts b/src/util/index.ts
index ae0f7e54..b26ed278 100644 --- a/util/src/index.ts +++ b/src/util/index.ts
@@ -1,6 +1,9 @@ import "reflect-metadata"; -export * from "./util/index"; -export * from "./interfaces/index"; -export * from "./entities/index"; +export * from "./config/index"; export * from "./dtos/index"; +export * from "./entities/index"; +export * from "./interfaces/index"; +export * from "./schemas"; +export * from "./util/index"; +export * from "./util/MFA"; diff --git a/util/src/interfaces/Activity.ts b/src/util/interfaces/Activity.ts
index 43984afd..3b36b4a6 100644 --- a/util/src/interfaces/Activity.ts +++ b/src/util/interfaces/Activity.ts
@@ -40,5 +40,5 @@ export enum ActivityType { STREAMING = 1, LISTENING = 2, CUSTOM = 4, - COMPETING = 5, + COMPETING = 5 } diff --git a/util/src/interfaces/Event.ts b/src/util/interfaces/Event.ts
index 416082ed..f97f4615 100644 --- a/util/src/interfaces/Event.ts +++ b/src/util/interfaces/Event.ts
@@ -1,19 +1,19 @@ -import { PublicUser, User, UserSettings } from "../entities/User"; +import { Activity, Status } from "."; +import { Sticker, UserSettings } from ".."; +import { ApplicationCommand } from "../entities/Application"; import { Channel } from "../entities/Channel"; -import { Guild } from "../entities/Guild"; -import { Member, PublicMember, UserGuildSettings } from "../entities/Member"; +import { ConnectedAccount } from "../entities/ConnectedAccount"; import { Emoji } from "../entities/Emoji"; -import { Role } from "../entities/Role"; +import { Guild } from "../entities/Guild"; import { Invite } from "../entities/Invite"; +import { PublicMember, UserGuildSettings } from "../entities/Member"; import { Message, PartialEmoji } from "../entities/Message"; +import { RelationshipType } from "../entities/Relationship"; +import { Role } from "../entities/Role"; +import { PublicUser, User } from "../entities/User"; import { VoiceState } from "../entities/VoiceState"; -import { ApplicationCommand } from "../entities/Application"; import { Interaction } from "./Interaction"; -import { ConnectedAccount } from "../entities/ConnectedAccount"; -import { Relationship, RelationshipType } from "../entities/Relationship"; import { Presence } from "./Presence"; -import { Sticker } from ".."; -import { Activity, Status } from "."; export interface Event { guild_id?: string; @@ -93,7 +93,7 @@ export interface ReadyEventData { }; application?: { id: string; - flags: string; + flags: number; }; merged_members?: PublicMember[][]; // probably all users who the user is in contact with @@ -580,7 +580,7 @@ export enum EVENTEnum { ApplicationCommandCreate = "APPLICATION_COMMAND_CREATE", ApplicationCommandUpdate = "APPLICATION_COMMAND_UPDATE", ApplicationCommandDelete = "APPLICATION_COMMAND_DELETE", - SessionsReplace = "SESSIONS_REPLACE", + SessionsReplace = "SESSIONS_REPLACE" } export type EVENT = diff --git a/util/src/interfaces/Interaction.ts b/src/util/interfaces/Interaction.ts
index 5d3aae24..c53a1ed4 100644 --- a/util/src/interfaces/Interaction.ts +++ b/src/util/interfaces/Interaction.ts
@@ -14,7 +14,7 @@ export interface Interaction { export enum InteractionType { SelfCommand = 0, Ping = 1, - ApplicationCommand = 2, + ApplicationCommand = 2 } export enum InteractionResponseType { @@ -23,7 +23,7 @@ export enum InteractionResponseType { Acknowledge = 2, ChannelMessage = 3, ChannelMessageWithSource = 4, - AcknowledgeWithSource = 5, + AcknowledgeWithSource = 5 } export interface InteractionApplicationCommandCallbackData { diff --git a/util/src/interfaces/Presence.ts b/src/util/interfaces/Presence.ts
index 7663891a..5b66139e 100644 --- a/util/src/interfaces/Presence.ts +++ b/src/util/interfaces/Presence.ts
@@ -1,6 +1,6 @@ -import { ClientStatus, Status } from "./Status"; -import { Activity } from "./Activity"; import { PublicUser } from "../entities/User"; +import { Activity } from "./Activity"; +import { ClientStatus, Status } from "./Status"; export interface Presence { user: PublicUser; diff --git a/util/src/interfaces/Status.ts b/src/util/interfaces/Status.ts
index 5d2e1bba..5d2e1bba 100644 --- a/util/src/interfaces/Status.ts +++ b/src/util/interfaces/Status.ts
diff --git a/util/src/interfaces/index.ts b/src/util/interfaces/index.ts
index ab7fa429..a074030e 100644 --- a/util/src/interfaces/index.ts +++ b/src/util/interfaces/index.ts
@@ -1,5 +1,5 @@ export * from "./Activity"; -export * from "./Presence"; -export * from "./Interaction"; export * from "./Event"; +export * from "./Interaction"; +export * from "./Presence"; export * from "./Status"; diff --git a/util/src/util/ApiError.ts b/src/util/util/ApiError.ts
index f1a9b4f6..c133e6e7 100644 --- a/util/src/util/ApiError.ts +++ b/src/util/util/ApiError.ts
@@ -9,8 +9,7 @@ export class ApiError extends Error { } withDefaultParams(): ApiError { - if (this.defaultParams) - return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus); + if (this.defaultParams) return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus); return this; } diff --git a/util/src/util/Array.ts b/src/util/util/Array.ts
index 5a45d1b5..5a45d1b5 100644 --- a/util/src/util/Array.ts +++ b/src/util/util/Array.ts
diff --git a/util/src/util/AutoUpdate.ts b/src/util/util/AutoUpdate.ts
index 531bd8b7..08418040 100644 --- a/util/src/util/AutoUpdate.ts +++ b/src/util/util/AutoUpdate.ts
@@ -1,13 +1,12 @@ -import "missing-native-js-functions"; -import fetch from "node-fetch"; -import ProxyAgent from 'proxy-agent'; -import readline from "readline"; import fs from "fs/promises"; +import fetch from "node-fetch"; import path from "path"; +import ProxyAgent from "proxy-agent"; +import readline from "readline"; const rl = readline.createInterface({ input: process.stdin, - output: process.stdout, + output: process.stdout }); export function enableAutoUpdate(opts: { @@ -18,7 +17,7 @@ export function enableAutoUpdate(opts: { downloadType?: "zip"; }) { if (!opts.checkInterval) return; - var interval = 1000 * 60 * 60 * 24; + let interval = 1000 * 60 * 60 * 24; if (typeof opts.checkInterval === "number") opts.checkInterval = 1000 * interval; const i = setInterval(async () => { @@ -76,7 +75,7 @@ async function getLatestVersion(url: string) { try { const agent = new ProxyAgent(); const response = await fetch(url, { agent }); - const content = await response.json(); + const content: any = await response.json(); return content.version; } catch (error) { throw new Error("[Auto update] check failed for " + url); diff --git a/util/src/util/BitField.ts b/src/util/util/BitField.ts
index fb887e05..306bfb32 100644 --- a/util/src/util/BitField.ts +++ b/src/util/util/BitField.ts
@@ -138,6 +138,9 @@ export class BitField { return bit.map((p) => resolve.call(this, p)).reduce((prev, p) => BigInt(prev) | BigInt(p), BigInt(0)); } if (typeof bit === "string" && typeof FLAGS[bit] !== "undefined") return FLAGS[bit]; + if (bit === "0") return BigInt(0); //special case: 0 + if (typeof bit === "string") return BigInt(bit); //last ditch effort... + if (/--debug|--inspect/.test(process.execArgv.join(" "))) debugger; //if you're here, we have an invalid bitfield... if bit is 0, thats fine, I guess... throw new RangeError("BITFIELD_INVALID: " + bit); } } diff --git a/util/src/util/Categories.ts b/src/util/util/Categories.ts
index a3c69da7..cd706a8a 100644 --- a/util/src/util/Categories.ts +++ b/src/util/util/Categories.ts
@@ -1 +1 @@ -//TODO: populate default discord categories + init, get and set methods \ No newline at end of file +//TODO: populate default discord categories + init, get and set methods diff --git a/util/src/util/Constants.ts b/src/util/util/Constants.ts
index a5d3fcd2..f7aff26a 100644 --- a/util/src/util/Constants.ts +++ b/src/util/util/Constants.ts
@@ -6,7 +6,7 @@ export const WSCodes = { 4010: "SHARDING_INVALID", 4011: "SHARDING_REQUIRED", 4013: "INVALID_INTENTS", - 4014: "DISALLOWED_INTENTS", + 4014: "DISALLOWED_INTENTS" }; /** @@ -31,7 +31,7 @@ export const WsStatus = { DISCONNECTED: 5, WAITING_FOR_GUILDS: 6, IDENTIFYING: 7, - RESUMING: 8, + RESUMING: 8 }; /** @@ -48,7 +48,7 @@ export const VoiceStatus = { CONNECTING: 1, AUTHENTICATING: 2, RECONNECTING: 3, - DISCONNECTED: 4, + DISCONNECTED: 4 }; export const OPCodes = { @@ -63,7 +63,7 @@ export const OPCodes = { REQUEST_GUILD_MEMBERS: 8, INVALID_SESSION: 9, HELLO: 10, - HEARTBEAT_ACK: 11, + HEARTBEAT_ACK: 11 }; export const VoiceOPCodes = { @@ -75,7 +75,7 @@ export const VoiceOPCodes = { SPEAKING: 5, HELLO: 8, CLIENT_CONNECT: 12, - CLIENT_DISCONNECT: 13, + CLIENT_DISCONNECT: 13 }; export const Events = { @@ -133,7 +133,7 @@ export const Events = { SHARD_READY: "shardReady", SHARD_RESUME: "shardResume", INVALIDATED: "invalidated", - RAW: "raw", + RAW: "raw" }; export const ShardEvents = { @@ -142,7 +142,7 @@ export const ShardEvents = { INVALID_SESSION: "invalidSession", READY: "ready", RESUMED: "resumed", - ALL_READY: "allReady", + ALL_READY: "allReady" }; /** @@ -234,7 +234,7 @@ export const WSEvents = keyMirror([ "TYPING_START", "VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE", - "WEBHOOKS_UPDATE", + "WEBHOOKS_UPDATE" ]); /** @@ -277,7 +277,7 @@ export const MessageTypes = [ null, null, null, - "REPLY", + "REPLY" ]; /** @@ -286,9 +286,7 @@ export const MessageTypes = [ * * REPLY * @typedef {string} SystemMessageType */ -export const SystemMessageTypes = MessageTypes.filter( - (type: string | null) => type && type !== "DEFAULT" && type !== "REPLY" -); +export const SystemMessageTypes = MessageTypes.filter((type: string | null) => type && type !== "DEFAULT" && type !== "REPLY"); /** * <info>Bots cannot set a `CUSTOM_STATUS`, it is only for custom statuses received from users</info> @@ -310,12 +308,12 @@ export const ChannelTypes = { GROUP: 3, CATEGORY: 4, NEWS: 5, - STORE: 6, + STORE: 6 }; export const ClientApplicationAssetTypes = { SMALL: 1, - BIG: 2, + BIG: 2 }; export const Colors = { @@ -347,7 +345,7 @@ export const Colors = { BLURPLE: 0x7289da, GREYPLE: 0x99aab5, DARK_BUT_NOT_BLACK: 0x2c2f33, - NOT_QUITE_BLACK: 0x23272a, + NOT_QUITE_BLACK: 0x23272a }; /** @@ -556,14 +554,8 @@ export const DiscordApiErrors = { UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError("Unknown Guild Scheduled Event User", 10071), BOT_PROHIBITED_ENDPOINT: new ApiError("Bots cannot use this endpoint", 20001), BOT_ONLY_ENDPOINT: new ApiError("Only bots can use this endpoint", 20002), - EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError( - "Explicit content cannot be sent to the desired recipient(s)", - 20009 - ), - ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError( - "You are not authorized to perform this action on this application", - 20012 - ), + EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError("Explicit content cannot be sent to the desired recipient(s)", 20009), + ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError("You are not authorized to perform this action on this application", 20012), SLOWMODE_RATE_LIMIT: new ApiError("This action cannot be performed due to slowmode rate limit", 20016), ONLY_OWNER: new ApiError("Only the owner of this account can perform this action", 20018), ANNOUNCEMENT_RATE_LIMITS: new ApiError("This message cannot be edited due to announcement rate limits", 20022), @@ -576,40 +568,25 @@ export const DiscordApiErrors = { MAXIMUM_GUILDS: new ApiError("Maximum number of guilds reached ({})", 30001, undefined, ["100"]), MAXIMUM_FRIENDS: new ApiError("Maximum number of friends reached ({})", 30002, undefined, ["1000"]), MAXIMUM_PINS: new ApiError("Maximum number of pins reached for the channel ({})", 30003, undefined, ["50"]), - MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, [ - "10", - ]), + MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, ["10"]), MAXIMUM_ROLES: new ApiError("Maximum number of guild roles reached ({})", 30005, undefined, ["250"]), MAXIMUM_WEBHOOKS: new ApiError("Maximum number of webhooks reached ({})", 30007, undefined, ["10"]), MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError("Maximum number of emojis reached", 30008), MAXIMUM_REACTIONS: new ApiError("Maximum number of reactions reached ({})", 30010, undefined, ["20"]), MAXIMUM_CHANNELS: new ApiError("Maximum number of guild channels reached ({})", 30013, undefined, ["500"]), - MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, [ - "10", - ]), + MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, ["10"]), MAXIMUM_INVITES: new ApiError("Maximum number of invites reached ({})", 30016, undefined, ["1000"]), MAXIMUM_ANIMATED_EMOJIS: new ApiError("Maximum number of animated emojis reached", 30018), MAXIMUM_SERVER_MEMBERS: new ApiError("Maximum number of server members reached", 30019), - MAXIMUM_SERVER_CATEGORIES: new ApiError( - "Maximum number of server categories has been reached ({})", - 30030, - undefined, - ["5"] - ), + MAXIMUM_SERVER_CATEGORIES: new ApiError("Maximum number of server categories has been reached ({})", 30030, undefined, ["5"]), GUILD_ALREADY_HAS_TEMPLATE: new ApiError("Guild already has a template", 30031), MAXIMUM_THREAD_PARTICIPANTS: new ApiError("Max number of thread participants has been reached", 30033), - MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError( - "Maximum number of bans for non-guild members have been exceeded", - 30035 - ), + MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError("Maximum number of bans for non-guild members have been exceeded", 30035), MAXIMUM_BANS_FETCHES: new ApiError("Maximum number of bans fetches has been reached", 30037), MAXIMUM_STICKERS: new ApiError("Maximum number of stickers reached", 30039), MAXIMUM_PRUNE_REQUESTS: new ApiError("Maximum number of prune requests has been reached. Try again later", 30040), UNAUTHORIZED: new ApiError("Unauthorized. Provide a valid token and try again", 40001), - ACCOUNT_VERIFICATION_REQUIRED: new ApiError( - "You need to verify your account in order to perform this action", - 40002 - ), + ACCOUNT_VERIFICATION_REQUIRED: new ApiError("You need to verify your account in order to perform this action", 40002), OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError("You are opening direct messages too fast", 40003), REQUEST_ENTITY_TOO_LARGE: new ApiError("Request entity too large. Try sending something smaller in size", 40005), FEATURE_TEMPORARILY_DISABLED: new ApiError("This feature has been temporarily disabled server-side", 40006), @@ -625,10 +602,7 @@ export const DiscordApiErrors = { CANNOT_SEND_EMPTY_MESSAGE: new ApiError("Cannot send an empty message", 50006), CANNOT_MESSAGE_USER: new ApiError("Cannot send messages to this user", 50007), CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError("Cannot send messages in a voice channel", 50008), - CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError( - "Channel verification level is too high for you to gain access", - 50009 - ), + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError("Channel verification level is too high for you to gain access", 50009), OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010), MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011), INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012), @@ -641,10 +615,7 @@ export const DiscordApiErrors = { undefined, ["2", "100"] ), - CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError( - "A message can only be pinned to the channel it was sent in", - 50019 - ), + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError("A message can only be pinned to the channel it was sent in", 50019), INVALID_OR_TAKEN_INVITE_CODE: new ApiError("Invite code was either invalid or taken", 50020), CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError("Cannot execute action on a system message", 50021), CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError("Cannot execute action on this channel type", 50024), @@ -658,34 +629,22 @@ export const DiscordApiErrors = { "Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided", 50035 ), - INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError( - "An invite was accepted to a guild the application's bot is not in", - 50036 - ), + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError("An invite was accepted to a guild the application's bot is not in", 50036), INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041), FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError("File uploaded exceeds the maximum size", 50045), INVALID_FILE_UPLOADED: new ApiError("Invalid file uploaded", 50046), CANNOT_SELF_REDEEM_GIFT: new ApiError("Cannot self-redeem this gift", 50054), PAYMENT_SOURCE_REQUIRED: new ApiError("Payment source required to redeem gift", 50070), - CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError( - "Cannot delete a channel required for Community guilds", - 50074 - ), + CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError("Cannot delete a channel required for Community guilds", 50074), INVALID_STICKER_SENT: new ApiError("Invalid sticker sent", 50081), CANNOT_EDIT_ARCHIVED_THREAD: new ApiError( "Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread", 50083 ), INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError("Invalid thread notification settings", 50084), - BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError( - "before value is earlier than the thread creation date", - 50085 - ), + BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError("before value is earlier than the thread creation date", 50085), SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError("This server is not available in your location", 50095), - SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError( - "This server needs monetization enabled in order to perform this action", - 50097 - ), + SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError("This server needs monetization enabled in order to perform this action", 50097), TWO_FACTOR_REQUIRED: new ApiError("Two factor is required for this operation", 60003), NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError("No users with DiscordTag exist", 80004), REACTION_BLOCKED: new ApiError("Reaction was blocked", 90001), @@ -694,33 +653,17 @@ export const DiscordApiErrors = { THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError("A thread has already been created for this message", 160004), THREAD_IS_LOCKED: new ApiError("Thread is locked", 160005), MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError("Maximum number of active threads reached", 160006), - MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError( - "Maximum number of active announcement threads reached", - 160007 - ), + MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError("Maximum number of active announcement threads reached", 160007), INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError("Invalid JSON for uploaded Lottie file", 170001), - LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError( - "Uploaded Lotties cannot contain rasterized images such as PNG or JPEG", - 170002 - ), + LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError("Uploaded Lotties cannot contain rasterized images such as PNG or JPEG", 170002), STICKER_MAXIMUM_FRAMERATE: new ApiError("Sticker maximum framerate exceeded", 170003), - STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, [ - "1000", - ]), + STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, ["1000"]), LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError("Lottie animation maximum dimensions exceeded", 170005), - STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError( - "Sticker frame rate is either too small or too large", - 170006 - ), - STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError( - "Sticker animation duration exceeds maximum of {} seconds", - 170007, - undefined, - ["5"] - ), + STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError("Sticker frame rate is either too small or too large", 170006), + STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError("Sticker animation duration exceeds maximum of {} seconds", 170007, undefined, ["5"]), //Other errors - UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404), + UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404) }; /** @@ -746,7 +689,7 @@ export const FosscordApiErrors = { CANNOT_BACKFILL_TO_THE_FUTURE: new ApiError("You cannot backfill messages in the future", 55003), CANNOT_GRANT_PERMISSIONS_EXCEEDING_RIGHTS: new ApiError("You cannot grant permissions exceeding your own rights", 50050), ROUTES_LOOPING: new ApiError("Loops in the route definition ({})", 50060, undefined, [""]), - CANNOT_REMOVE_ROUTE: new ApiError("Cannot remove message route while it is in effect and being used", 50061), + CANNOT_REMOVE_ROUTE: new ApiError("Cannot remove message route while it is in effect and being used", 50061) }; /** @@ -765,11 +708,7 @@ export const DefaultMessageNotifications = ["ALL", "MENTIONS", "MUTED"]; * * INSERTED (Fosscord extension) * @typedef {string} MembershipStates */ -export const MembershipStates = [ - "INSERTED", - "INVITED", - "ACCEPTED", -]; +export const MembershipStates = ["INSERTED", "INVITED", "ACCEPTED"]; /** * The value set for a webhook's type: @@ -778,15 +717,10 @@ export const MembershipStates = [ * * Custom (Fosscord extension) * @typedef {string} WebhookTypes */ -export const WebhookTypes = [ - "Custom", - "Incoming", - "Channel Follower", -]; +export const WebhookTypes = ["Custom", "Incoming", "Channel Follower"]; function keyMirror(arr: string[]) { let tmp = Object.create(null); for (const value of arr) tmp[value] = value; return tmp; } - diff --git a/util/src/util/Email.ts b/src/util/util/Email.ts
index 6885da33..c98ccff0 100644 --- a/util/src/util/Email.ts +++ b/src/util/util/Email.ts
@@ -15,7 +15,7 @@ export function adjustEmail(email?: string): string | undefined { // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator let v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com"; } - + if (domain === "google.com") { // replace .dots and +alternatives -> Google Staff GMail Dot Trick let v = user.replace(/[.]|(\+.*)/g, "") + "@google.com"; diff --git a/util/src/util/Event.ts b/src/util/util/Event.ts
index bb624051..383e4e50 100644 --- a/util/src/util/Event.ts +++ b/src/util/util/Event.ts
@@ -1,7 +1,7 @@ import { Channel } from "amqplib"; -import { RabbitMQ } from "./RabbitMQ"; import EventEmitter from "events"; import { EVENT, Event } from "../interfaces"; +import { RabbitMQ } from "./RabbitMQ"; export const events = new EventEmitter(); export async function emitEvent(payload: Omit<Event, "created_at">) { @@ -58,8 +58,8 @@ export async function listenEvent(event: string, callback: (event: EventOpts) => process.setMaxListeners(process.getMaxListeners() - 1); }; - const listener = (msg: ProcessEvent) => { - msg.type === "event" && msg.id === event && callback({ ...msg.event, cancel }); + const listener = (message: any) => { + message.type === "event" && message.id === event && callback({ ...message.event, cancel }); }; process.addListener("message", listener); @@ -79,12 +79,7 @@ export async function listenEvent(event: string, callback: (event: EventOpts) => } } -async function rabbitListen( - channel: Channel, - id: string, - callback: (event: EventOpts) => any, - opts?: { acknowledge?: boolean } -) { +async function rabbitListen(channel: Channel, id: string, callback: (event: EventOpts) => any, opts?: { acknowledge?: boolean }) { await channel.assertExchange(id, "fanout", { durable: false }); const q = await channel.assertQueue("", { exclusive: true, autoDelete: true }); @@ -109,12 +104,12 @@ async function rabbitListen( channel.ack(opts); }, channel, - cancel, + cancel }); // rabbitCh.ack(opts); }, { - noAck: !opts?.acknowledge, + noAck: !opts?.acknowledge } ); diff --git a/util/src/util/FieldError.ts b/src/util/util/FieldError.ts
index 406b33e8..bdffd618 100644 --- a/util/src/util/FieldError.ts +++ b/src/util/util/FieldError.ts
@@ -1,5 +1,3 @@ -import "missing-native-js-functions"; - export function FieldErrors(fields: Record<string, { code?: string; message: string }>) { return new FieldError( 50035, @@ -8,9 +6,9 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str _errors: [ { message, - code: code || "BASE_TYPE_INVALID", - }, - ], + code: code || "BASE_TYPE_INVALID" + } + ] })) ); } diff --git a/util/src/util/Intents.ts b/src/util/util/Intents.ts
index 1e840b76..db4c9e28 100644 --- a/util/src/util/Intents.ts +++ b/src/util/util/Intents.ts
@@ -22,13 +22,12 @@ export class Intents extends BitField { GUILD_POLICY_EXECUTION: BigInt(1) << BigInt(21), // guild policy execution LIVE_MESSAGE_COMPOSITION: BigInt(1) << BigInt(32), // allow composing messages using the gateway GUILD_ROUTES: BigInt(1) << BigInt(41), // message routes affecting the guild - DIRECT_MESSAGES_THREADS: BigInt(1) << BigInt(42), // direct message threads + DIRECT_MESSAGES_THREADS: BigInt(1) << BigInt(42), // direct message threads JUMBO_EVENTS: BigInt(1) << BigInt(43), // jumbo events (size limits to be defined later) LOBBIES: BigInt(1) << BigInt(44), // lobbies - INSTANCE_ROUTES: BigInt(1) << BigInt(60), // all message route changes + INSTANCE_ROUTES: BigInt(1) << BigInt(60), // all message route changes INSTANCE_GUILD_CHANGES: BigInt(1) << BigInt(61), // all guild create, guild object patch, split, merge and delete events INSTANCE_POLICY_UPDATES: BigInt(1) << BigInt(62), // all instance policy updates INSTANCE_USER_UPDATES: BigInt(1) << BigInt(63) // all instance user updates }; } - diff --git a/util/src/util/MessageFlags.ts b/src/util/util/MessageFlags.ts
index b59295c4..c5d5d02a 100644 --- a/util/src/util/MessageFlags.ts +++ b/src/util/util/MessageFlags.ts
@@ -15,6 +15,6 @@ export class MessageFlags extends BitField { INTERACTION_WAIT: BigInt(1) << BigInt(7), // discord.com calls this LOADING // FAILED_TO_MENTION_SOME_ROLES_IN_THREAD: BigInt(1) << BigInt(8) SCRIPT_WAIT: BigInt(1) << BigInt(24), // waiting for the self command to complete - IMPORT_WAIT: BigInt(1) << BigInt(25), // latest message of a bulk import, waiting for the rest of the channel to be backfilled + IMPORT_WAIT: BigInt(1) << BigInt(25) // latest message of a bulk import, waiting for the rest of the channel to be backfilled }; } diff --git a/util/src/util/Permissions.ts b/src/util/util/Permissions.ts
index e5459ab5..b8d0d8ae 100644 --- a/util/src/util/Permissions.ts +++ b/src/util/util/Permissions.ts
@@ -1,17 +1,8 @@ // https://github.com/discordjs/discord.js/blob/master/src/util/Permissions.js // Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah +import { HTTPError } from ".."; import { Channel, ChannelPermissionOverwrite, Guild, Member, Role } from "../entities"; -import { BitField } from "./BitField"; -import "missing-native-js-functions"; -import { BitFieldResolvable, BitFlag } from "./BitField"; - -var HTTPError: any; - -try { - HTTPError = require("lambert-server").HTTPError; -} catch (e) { - HTTPError = Error; -} +import { BitField, BitFieldResolvable, BitFlag } from "./BitField"; export type PermissionResolvable = bigint | number | Permissions | PermissionResolvable[] | PermissionString; @@ -68,7 +59,7 @@ export class Permissions extends BitField { MANAGE_THREADS: BitFlag(34), USE_PUBLIC_THREADS: BitFlag(35), USE_PRIVATE_THREADS: BitFlag(36), - USE_EXTERNAL_STICKERS: BitFlag(37), + USE_EXTERNAL_STICKERS: BitFlag(37) /** * CUSTOM PERMISSIONS ideas: @@ -132,7 +123,7 @@ export class Permissions extends BitField { static finalPermission({ user, guild, - channel, + channel }: { user: { id: string; roles: string[] }; guild: { roles: Role[] }; @@ -172,7 +163,7 @@ export class Permissions extends BitField { "USE_EXTERNAL_EMOJIS", "CONNECT", "SPEAK", - "MANAGE_CHANNELS", + "MANAGE_CHANNELS" ]); } @@ -207,9 +198,9 @@ export async function getPermission( } = {} ) { if (!user_id) throw new HTTPError("User not found"); - var channel: Channel | undefined; - var member: Member | undefined; - var guild: Guild | undefined; + let channel: Channel | undefined; + let member: Member | undefined; + let guild: Guild | undefined; if (channel_id) { channel = await Channel.findOneOrFail({ @@ -222,8 +213,8 @@ export async function getPermission( "owner_id", "guild_id", // @ts-ignore - ...(opts.channel_select || []), - ], + ...(opts.channel_select || []) + ] }); if (channel.guild_id) guild_id = channel.guild_id; // derive guild_id from the channel } @@ -235,9 +226,9 @@ export async function getPermission( "id", "owner_id", // @ts-ignore - ...(opts.guild_select || []), + ...(opts.guild_select || []) ], - relations: opts.guild_relations, + relations: opts.guild_relations }); if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); @@ -247,9 +238,10 @@ export async function getPermission( select: [ "id", "roles", + "index", // @ts-ignore - ...(opts.member_select || []), - ], + ...(opts.member_select || []) + ] }); } @@ -257,19 +249,19 @@ export async function getPermission( if (!recipient_ids?.length) recipient_ids = null; // TODO: remove guild.roles and convert recipient_ids to recipients - var permission = Permissions.finalPermission({ + let permission = Permissions.finalPermission({ user: { id: user_id, - roles: member?.roles.map((x) => x.id) || [], + roles: member?.roles.map((x) => x.id) || [] }, guild: { - roles: member?.roles || [], + roles: member?.roles || [] }, channel: { overwrites: channel?.permission_overwrites, owner_id: channel?.owner_id, - recipient_ids, - }, + recipient_ids + } }); const obj = new Permissions(permission); diff --git a/util/src/util/RabbitMQ.ts b/src/util/util/RabbitMQ.ts
index 0f5eb6aa..638b805b 100644 --- a/util/src/util/RabbitMQ.ts +++ b/src/util/util/RabbitMQ.ts
@@ -1,4 +1,4 @@ -import amqp, { Connection, Channel } from "amqplib"; +import { Channel, Connection } from "amqplib"; // import Config from "./Config"; export const RabbitMQ: { connection: Connection | null; channel: Channel | null; init: () => Promise<void> } = { @@ -15,5 +15,5 @@ export const RabbitMQ: { connection: Connection | null; channel: Channel | null; // console.log(`[RabbitMQ] connected`); // this.channel = await this.connection.createChannel(); // console.log(`[RabbitMQ] channel created`); - }, + } }; diff --git a/util/src/util/Regex.ts b/src/util/util/Regex.ts
index 83fc9fe8..83fc9fe8 100644 --- a/util/src/util/Regex.ts +++ b/src/util/util/Regex.ts
diff --git a/util/src/util/Rights.ts b/src/util/util/Rights.ts
index b28c75b7..51bb098c 100644 --- a/util/src/util/Rights.ts +++ b/src/util/util/Rights.ts
@@ -1,15 +1,6 @@ -import { BitField } from "./BitField"; -import "missing-native-js-functions"; -import { BitFieldResolvable, BitFlag } from "./BitField"; +import { HTTPError } from ".."; import { User } from "../entities"; - -var HTTPError: any; - -try { - HTTPError = require("lambert-server").HTTPError; -} catch (e) { - HTTPError = Error; -} +import { BitField, BitFieldResolvable, BitFlag } from "./BitField"; export type RightResolvable = bigint | number | Rights | RightResolvable[] | RightString; @@ -60,7 +51,7 @@ export class Rights extends BitField { CREDITABLE: BitFlag(32), // can receive money from monetisation related features KICK_BAN_MEMBERS: BitFlag(33), // can kick or ban guild or group DM members in the guilds/groups that they have KICK_MEMBERS, or BAN_MEMBERS - SELF_LEAVE_GROUPS: BitFlag(34), + SELF_LEAVE_GROUPS: BitFlag(34), // can leave the guilds or group DMs that they joined on their own (one can always leave a guild or group DMs they have been force-added) PRESENCE: BitFlag(35), // inverts the presence confidentiality default (OPERATOR's presence is not routed by default, others' are) for a given user @@ -88,15 +79,16 @@ export class Rights extends BitField { // @ts-ignore throw new HTTPError(`You are missing the following rights ${permission}`, 403); } - } const ALL_RIGHTS = Object.values(Rights.FLAGS).reduce((total, val) => total | val, BigInt(0)); -export async function getRights( user_id: string +export async function getRights( + user_id: string /**, opts: { in_behalf?: (keyof User)[]; - } = {} **/) { + } = {} **/ +) { let user = await User.findOneOrFail({ where: { id: user_id } }); return new Rights(user.rights); -} +} diff --git a/util/src/util/Snowflake.ts b/src/util/util/Snowflake.ts
index 134d526e..89f4f0c3 100644 --- a/util/src/util/Snowflake.ts +++ b/src/util/util/Snowflake.ts
@@ -83,14 +83,15 @@ export class Snowflake { return dec; } - static generateWorkerProcess() { // worker process - returns a number - var time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22); - var worker = Snowflake.workerId << 17n; - var process = Snowflake.processId << 12n; - var increment = Snowflake.INCREMENT++; + static generateWorkerProcess() { + // worker process - returns a number + let time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22); + let worker = Snowflake.workerId << 17n; + let process = Snowflake.processId << 12n; + let increment = Snowflake.INCREMENT++; return BigInt(time | worker | process | increment); } - + static generate() { return Snowflake.generateWorkerProcess().toString(); } @@ -117,13 +118,13 @@ export class Snowflake { workerID: parseInt(BINARY.substring(42, 47), 2), processID: parseInt(BINARY.substring(47, 52), 2), increment: parseInt(BINARY.substring(52, 64), 2), - binary: BINARY, + binary: BINARY }; Object.defineProperty(res, "date", { get: function get() { return new Date(this.timestamp); }, - enumerable: true, + enumerable: true }); return res; } diff --git a/util/src/util/String.ts b/src/util/util/String.ts
index 55f11e8d..55f11e8d 100644 --- a/util/src/util/String.ts +++ b/src/util/util/String.ts
diff --git a/util/src/util/Token.ts b/src/util/util/Token.ts
index 500ace45..d192a13a 100644 --- a/util/src/util/Token.ts +++ b/src/util/util/Token.ts
@@ -1,6 +1,6 @@ import jwt, { VerifyOptions } from "jsonwebtoken"; -import { Config } from "./Config"; import { User } from "../entities"; +import { Config } from "./Config"; export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] }; @@ -11,18 +11,17 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> { in fosscord, even with instances that have bot distinction; we won't enforce "Bot" prefix, as we don't really have separate pathways for bots **/ - + jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded: any) => { if (err || !decoded) return rej("Invalid Token"); - const user = await User.findOne( - { id: decoded.id }, - { select: ["data", "bot", "disabled", "deleted", "rights"] } - ); + const user = await User.findOne({ + where: { id: decoded.id }, + select: ["data", "bot", "disabled", "deleted", "rights"] + }); if (!user) return rej("Invalid Token"); // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds - if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) - return rej("Invalid Token"); + if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) return rej("Invalid Token"); if (user.disabled) return rej("User disabled"); if (user.deleted) return rej("User not found"); @@ -40,7 +39,7 @@ export async function generateToken(id: string) { { id: id, iat }, Config.get().security.jwtSecret, { - algorithm, + algorithm }, (err, token) => { if (err) return rej(err); diff --git a/util/src/util/TraverseDirectory.ts b/src/util/util/TraverseDirectory.ts
index 3d0d6279..6f60e73b 100644 --- a/util/src/util/TraverseDirectory.ts +++ b/src/util/util/TraverseDirectory.ts
@@ -1,13 +1,10 @@ import { Server, traverseDirectory } from "lambert-server"; //if we're using ts-node, use ts files instead of js -const extension = Symbol.for("ts-node.register.instance") in process ? "ts" : "js" +const extension = Symbol.for("ts-node.register.instance") in process ? "ts" : "js"; -const DEFAULT_FILTER = new RegExp("^([^\.].*)(?<!\.d)\.(" + extension + ")$"); +const DEFAULT_FILTER = new RegExp("^([^.].*)(?<!.d).(" + extension + ")$"); export function registerRoutes(server: Server, root: string) { - return traverseDirectory( - { dirname: root, recursive: true, filter: DEFAULT_FILTER }, - server.registerRoute.bind(server, root) - ); + return traverseDirectory({ dirname: root, recursive: true, filter: DEFAULT_FILTER }, server.registerRoute.bind(server, root)); } diff --git a/util/src/util/cdn.ts b/src/util/util/cdn.ts
index ea950cd1..5573b848 100644 --- a/util/src/util/cdn.ts +++ b/src/util/util/cdn.ts
@@ -1,8 +1,7 @@ import FormData from "form-data"; -import { HTTPError } from "lambert-server"; import fetch from "node-fetch"; +import { HTTPError } from ".."; import { Config } from "./Config"; -import multer from "multer"; export async function uploadFile(path: string, file?: Express.Multer.File) { if (!file?.buffer) throw new HTTPError("Missing file in body"); @@ -10,16 +9,16 @@ export async function uploadFile(path: string, file?: Express.Multer.File) { const form = new FormData(); form.append("file", file.buffer, { contentType: file.mimetype, - filename: file.originalname, + filename: file.originalname }); const response = await fetch(`${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`, { headers: { signature: Config.get().security.requestSignature, - ...form.getHeaders(), + ...form.getHeaders() }, method: "POST", - body: form, + body: form }); const result = await response.json(); @@ -45,9 +44,9 @@ export async function handleFile(path: string, body?: string): Promise<string | export async function deleteFile(path: string) { const response = await fetch(`${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`, { headers: { - signature: Config.get().security.requestSignature, + signature: Config.get().security.requestSignature }, - method: "DELETE", + method: "DELETE" }); const result = await response.json(); diff --git a/util/src/util/index.ts b/src/util/util/index.ts
index f7a273cb..11f0b72a 100644 --- a/util/src/util/index.ts +++ b/src/util/util/index.ts
@@ -1,6 +1,6 @@ export * from "./ApiError"; +export * from "./Array"; export * from "./BitField"; -export * from "./Token"; //export * from "./Categories"; export * from "./cdn"; export * from "./Config"; @@ -9,7 +9,11 @@ export * from "./Database"; export * from "./Email"; export * from "./Event"; export * from "./FieldError"; +export * from "./imports/HTTPError"; +export * from "./imports/index"; +export * from "./imports/OrmUtils"; export * from "./Intents"; +export * from "./InvisibleCharacters"; export * from "./MessageFlags"; export * from "./Permissions"; export * from "./RabbitMQ"; @@ -17,6 +21,5 @@ export * from "./Regex"; export * from "./Rights"; export * from "./Snowflake"; export * from "./String"; -export * from "./Array"; +export * from "./Token"; export * from "./TraverseDirectory"; -export * from "./InvisibleCharacters"; \ No newline at end of file diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts deleted file mode 100644
index aabca016..00000000 --- a/util/src/entities/BaseClass.ts +++ /dev/null
@@ -1,75 +0,0 @@ -import "reflect-metadata"; -import { BaseEntity, EntityMetadata, FindConditions, ObjectIdColumn, PrimaryColumn } from "typeorm"; -import { Snowflake } from "../util/Snowflake"; -import "missing-native-js-functions"; - -export class BaseClassWithoutId extends BaseEntity { - constructor(props?: any) { - super(); - this.assign(props); - } - - private get construct(): any { - return this.constructor; - } - - private get metadata() { - return this.construct.getRepository().metadata as EntityMetadata; - } - - assign(props: any = {}) { - delete props.opts; - delete props.props; - - const properties = new Set( - this.metadata.columns - .map((x: any) => x.propertyName) - .concat(this.metadata.relations.map((x) => x.propertyName)) - ); - // will not include relational properties - - for (const key in props) { - if (!properties.has(key)) continue; - // @ts-ignore - const setter = this[`set${key.capitalize()}`]; // use setter function if it exists - - if (setter) { - setter.call(this, props[key]); - } else { - // @ts-ignore - this[key] = props[key]; - } - } - } - - toJSON(): any { - return Object.fromEntries( - this.metadata.columns // @ts-ignore - .map((x) => [x.propertyName, this[x.propertyName]]) // @ts-ignore - .concat(this.metadata.relations.map((x) => [x.propertyName, this[x.propertyName]])) - ); - } - - static increment<T extends BaseClass>(conditions: FindConditions<T>, propertyPath: string, value: number | string) { - const repository = this.getRepository(); - return repository.increment(conditions as T, propertyPath, value); - } - - static decrement<T extends BaseClass>(conditions: FindConditions<T>, propertyPath: string, value: number | string) { - const repository = this.getRepository(); - return repository.decrement(conditions as T, propertyPath, value); - } -} - -export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn; - -export class BaseClass extends BaseClassWithoutId { - @PrimaryIdColumn() - id: string; - - assign(props: any = {}) { - super.assign(props); - if (!this.id) this.id = Snowflake.generate(); - return this; - } -} diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts deleted file mode 100644
index 3756d686..00000000 --- a/util/src/entities/Config.ts +++ /dev/null
@@ -1,407 +0,0 @@ -import { Column, Entity } from "typeorm"; -import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass"; -import crypto from "crypto"; -import { Snowflake } from "../util/Snowflake"; -import { SessionsReplace } from ".."; -import { hostname } from "os"; - -@Entity("config") -export class ConfigEntity extends BaseClassWithoutId { - @PrimaryIdColumn() - key: string; - - @Column({ type: "simple-json", nullable: true }) - value: number | boolean | null | string | undefined; -} - -export interface RateLimitOptions { - bot?: number; - count: number; - window: number; - onyIp?: boolean; -} - -export interface Region { - id: string; - name: string; - endpoint: string; - location?: { - latitude: number; - longitude: number; - }; - vip: boolean; - custom: boolean; - deprecated: boolean; -} - -export interface KafkaBroker { - ip: string; - port: number; -} - -export interface ConfigValue { - gateway: { - endpointClient: string | null; - endpointPrivate: string | null; - endpointPublic: string | null; - }; - cdn: { - endpointClient: string | null; - endpointPublic: string | null; - endpointPrivate: string | null; - }; - api: { - defaultVersion: string; - activeVersions: string[]; - useFosscordEnhancements: boolean; - }; - general: { - instanceName: string; - instanceDescription: string | null; - frontPage: string | null; - tosPage: string | null; - correspondenceEmail: string | null; - correspondenceUserID: string | null; - image: string | null; - instanceId: string; - }; - limits: { - user: { - maxGuilds: number; - maxUsername: number; - maxFriends: number; - }; - guild: { - maxRoles: number; - maxEmojis: number; - maxMembers: number; - maxChannels: number; - maxChannelsInCategory: number; - hideOfflineMember: number; - }; - message: { - maxCharacters: number; - maxTTSCharacters: number; - maxReactions: number; - maxAttachmentSize: number; - maxBulkDelete: number; - maxEmbedDownloadSize: number; - }; - channel: { - maxPins: number; - maxTopic: number; - maxWebhooks: number; - }; - rate: { - disabled: boolean; - ip: Omit<RateLimitOptions, "bot_count">; - global: RateLimitOptions; - error: RateLimitOptions; - routes: { - guild: RateLimitOptions; - webhook: RateLimitOptions; - channel: RateLimitOptions; - auth: { - login: RateLimitOptions; - register: RateLimitOptions; - }; - // TODO: rate limit configuration for all routes - }; - }; - }; - security: { - autoUpdate: boolean | number; - requestSignature: string; - jwtSecret: string; - forwadedFor: string | null; // header to get the real user ip address - captcha: { - enabled: boolean; - service: "recaptcha" | "hcaptcha" | null; // TODO: hcaptcha, custom - sitekey: string | null; - secret: string | null; - }; - ipdataApiKey: string | null; - }; - login: { - requireCaptcha: boolean; - }; - register: { - email: { - required: boolean; - allowlist: boolean; - blocklist: boolean; - domains: string[]; - }; - dateOfBirth: { - required: boolean; - minimum: number; // in years - }; - disabled: boolean; - requireCaptcha: boolean; - requireInvite: boolean; - guestsRequireInvite: boolean; - allowNewRegistration: boolean; - allowMultipleAccounts: boolean; - blockProxies: boolean; - password: { - required: boolean; - minLength: number; - minNumbers: number; - minUpperCase: number; - minSymbols: number; - }; - incrementingDiscriminators: boolean; // random otherwise - }; - regions: { - default: string; - useDefaultAsOptimal: boolean; - available: Region[]; - }; - guild: { - discovery: { - showAllGuilds: boolean; - useRecommendation: boolean; // TODO: Recommendation, privacy concern? - offset: number; - limit: number; - }; - autoJoin: { - enabled: boolean; - guilds: string[]; - canLeave: boolean; - }; - }; - gif: { - enabled: boolean; - provider: "tenor"; // more coming soon - apiKey?: string; - }; - rabbitmq: { - host: string | null; - }; - kafka: { - brokers: KafkaBroker[] | null; - }; - templates: { - enabled: Boolean; - allowTemplateCreation: Boolean; - allowDiscordTemplates: Boolean; - allowRaws: Boolean; - }, - client: { - useTestClient: Boolean; - releases: { - useLocalRelease: Boolean; //TODO - upstreamVersion: string; - } - }, - metrics: { - timeout: number; - }, - sentry: { - enabled: boolean; - endpoint: string; - traceSampleRate: number; - environment: string; - } -} - -export const DefaultConfigOptions: ConfigValue = { - gateway: { - endpointClient: null, - endpointPrivate: null, - endpointPublic: null, - }, - cdn: { - endpointClient: null, - endpointPrivate: null, - endpointPublic: null, - }, - api: { - defaultVersion: "9", - activeVersions: ["6", "7", "8", "9"], - useFosscordEnhancements: true, - }, - general: { - instanceName: "Fosscord Instance", - instanceDescription: "This is a Fosscord instance made in pre-release days", - frontPage: null, - tosPage: null, - correspondenceEmail: "noreply@localhost.local", - correspondenceUserID: null, - image: null, - instanceId: Snowflake.generate(), - }, - limits: { - user: { - maxGuilds: 1048576, - maxUsername: 127, - maxFriends: 5000, - }, - guild: { - maxRoles: 1000, - maxEmojis: 2000, - maxMembers: 25000000, - maxChannels: 65535, - maxChannelsInCategory: 65535, - hideOfflineMember: 3, - }, - message: { - maxCharacters: 1048576, - maxTTSCharacters: 160, - maxReactions: 2048, - maxAttachmentSize: 1024 * 1024 * 1024, - maxEmbedDownloadSize: 1024 * 1024 * 5, - maxBulkDelete: 1000, - }, - channel: { - maxPins: 500, - maxTopic: 1024, - maxWebhooks: 100, - }, - rate: { - disabled: true, - ip: { - count: 500, - window: 5, - }, - global: { - count: 250, - window: 5, - }, - error: { - count: 10, - window: 5, - }, - routes: { - guild: { - count: 5, - window: 5, - }, - webhook: { - count: 10, - window: 5, - }, - channel: { - count: 10, - window: 5, - }, - auth: { - login: { - count: 5, - window: 60, - }, - register: { - count: 2, - window: 60 * 60 * 12, - }, - }, - }, - }, - }, - security: { - autoUpdate: true, - requestSignature: crypto.randomBytes(32).toString("base64"), - jwtSecret: crypto.randomBytes(256).toString("base64"), - forwadedFor: null, - // forwadedFor: "X-Forwarded-For" // nginx/reverse proxy - // forwadedFor: "CF-Connecting-IP" // cloudflare: - captcha: { - enabled: false, - service: null, - sitekey: null, - secret: null, - }, - ipdataApiKey: "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9", - }, - login: { - requireCaptcha: false, - }, - register: { - email: { - required: false, - allowlist: false, - blocklist: true, - domains: [], // TODO: efficiently save domain blocklist in database - // domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"), - }, - dateOfBirth: { - required: true, - minimum: 13, - }, - disabled: false, - requireInvite: false, - guestsRequireInvite: true, - requireCaptcha: true, - allowNewRegistration: true, - allowMultipleAccounts: true, - blockProxies: true, - password: { - required: false, - minLength: 8, - minNumbers: 2, - minUpperCase: 2, - minSymbols: 0, - }, - incrementingDiscriminators: false, - }, - regions: { - default: "fosscord", - useDefaultAsOptimal: true, - available: [ - { - id: "fosscord", - name: "Fosscord", - endpoint: "127.0.0.1:3004", - vip: false, - custom: false, - deprecated: false, - }, - ], - }, - guild: { - discovery: { - showAllGuilds: false, - useRecommendation: false, - offset: 0, - limit: 24, - }, - autoJoin: { - enabled: true, - canLeave: true, - guilds: [], - }, - }, - gif: { - enabled: true, - provider: "tenor", - apiKey: "LIVDSRZULELA", - }, - rabbitmq: { - host: null, - }, - kafka: { - brokers: null, - }, - templates: { - enabled: true, - allowTemplateCreation: true, - allowDiscordTemplates: true, - allowRaws: false - }, - client: { - useTestClient: true, - releases: { - useLocalRelease: true, - upstreamVersion: "0.0.264" - } - }, - metrics: { - timeout: 30000 - }, - sentry: { - enabled: false, - endpoint: "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6", - traceSampleRate: 1.0, - environment: hostname() - } -}; diff --git a/util/src/entities/Encryption.ts b/util/src/entities/Encryption.ts deleted file mode 100644
index 3b82ff84..00000000 --- a/util/src/entities/Encryption.ts +++ /dev/null
@@ -1,35 +0,0 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; -import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; -import { PublicUserProjection, User } from "./User"; -import { HTTPError } from "lambert-server"; -import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters } from "../util"; -import { BitField, BitFieldResolvable, BitFlag } from "../util/BitField"; -import { Recipient } from "./Recipient"; -import { Message } from "./Message"; -import { ReadState } from "./ReadState"; -import { Invite } from "./Invite"; -import { DmChannelDTO } from "../dtos"; - -@Entity("security_settings") -export class SecuritySettings extends BaseClass { - - @Column({nullable: true}) - guild_id: Snowflake; - - @Column({nullable: true}) - channel_id: Snowflake; - - @Column() - encryption_permission_mask: BitField; - - @Column() - allowed_algorithms: string[]; - - @Column() - current_algorithm: string; - - @Column({nullable: true}) - used_since_message: Snowflake; - -} diff --git a/util/src/migrations/1633864260873-EmojiRoles.ts b/util/src/migrations/1633864260873-EmojiRoles.ts deleted file mode 100644
index f0d709f2..00000000 --- a/util/src/migrations/1633864260873-EmojiRoles.ts +++ /dev/null
@@ -1,13 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class EmojiRoles1633864260873 implements MigrationInterface { - name = "EmojiRoles1633864260873"; - - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query(`ALTER TABLE "emojis" ADD "roles" text NOT NULL DEFAULT ''`); - } - - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN column_name "roles"`); - } -} diff --git a/util/src/migrations/1633864669243-EmojiUser.ts b/util/src/migrations/1633864669243-EmojiUser.ts deleted file mode 100644
index 982405d7..00000000 --- a/util/src/migrations/1633864669243-EmojiUser.ts +++ /dev/null
@@ -1,23 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class EmojiUser1633864669243 implements MigrationInterface { - name = "EmojiUser1633864669243"; - - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query(`ALTER TABLE "emojis" ADD "user_id" varchar`); - try { - await queryRunner.query( - `ALTER TABLE "emojis" ADD CONSTRAINT FK_fa7ddd5f9a214e28ce596548421 FOREIGN KEY (user_id) REFERENCES users(id)` - ); - } catch (error) { - console.error( - "sqlite doesn't support altering foreign keys: https://stackoverflow.com/questions/1884818/how-do-i-add-a-foreign-key-to-an-existing-sqlite-table" - ); - } - } - - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN column_name "user_id"`); - await queryRunner.query(`ALTER TABLE "emojis" DROP CONSTRAINT FK_fa7ddd5f9a214e28ce596548421`); - } -} diff --git a/util/src/migrations/1633881705509-VanityInvite.ts b/util/src/migrations/1633881705509-VanityInvite.ts deleted file mode 100644
index 45485310..00000000 --- a/util/src/migrations/1633881705509-VanityInvite.ts +++ /dev/null
@@ -1,19 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class VanityInvite1633881705509 implements MigrationInterface { - name = "VanityInvite1633881705509"; - - public async up(queryRunner: QueryRunner): Promise<void> { - try { - await queryRunner.query(`ALTER TABLE "emojis" DROP COLUMN vanity_url_code`); - await queryRunner.query(`ALTER TABLE "emojis" DROP CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad`); - } catch (error) {} - } - - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query(`ALTER TABLE "emojis" ADD vanity_url_code varchar`); - await queryRunner.query( - `ALTER TABLE "emojis" ADD CONSTRAINT FK_c2c1809d79eb120ea0cb8d342ad FOREIGN KEY ("vanity_url_code") REFERENCES "invites"("code") ON DELETE NO ACTION ON UPDATE NO ACTION` - ); - } -} diff --git a/util/src/migrations/1634308884591-Stickers.ts b/util/src/migrations/1634308884591-Stickers.ts deleted file mode 100644
index fbc4649f..00000000 --- a/util/src/migrations/1634308884591-Stickers.ts +++ /dev/null
@@ -1,66 +0,0 @@ -import { MigrationInterface, QueryRunner, Table, TableColumn, TableForeignKey } from "typeorm"; - -export class Stickers1634308884591 implements MigrationInterface { - name = "Stickers1634308884591"; - - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.dropForeignKey("read_states", "FK_6f255d873cfbfd7a93849b7ff74"); - await queryRunner.changeColumn( - "stickers", - "tags", - new TableColumn({ name: "tags", type: "varchar", isNullable: true }) - ); - await queryRunner.changeColumn( - "stickers", - "pack_id", - new TableColumn({ name: "pack_id", type: "varchar", isNullable: true }) - ); - await queryRunner.changeColumn("stickers", "type", new TableColumn({ name: "type", type: "integer" })); - await queryRunner.changeColumn( - "stickers", - "format_type", - new TableColumn({ name: "format_type", type: "integer" }) - ); - await queryRunner.changeColumn( - "stickers", - "available", - new TableColumn({ name: "available", type: "boolean", isNullable: true }) - ); - await queryRunner.changeColumn( - "stickers", - "user_id", - new TableColumn({ name: "user_id", type: "boolean", isNullable: true }) - ); - await queryRunner.createForeignKey( - "stickers", - new TableForeignKey({ - name: "FK_8f4ee73f2bb2325ff980502e158", - columnNames: ["user_id"], - referencedColumnNames: ["id"], - referencedTableName: "users", - onDelete: "CASCADE", - }) - ); - await queryRunner.createTable( - new Table({ - name: "sticker_packs", - columns: [ - new TableColumn({ name: "id", type: "varchar", isPrimary: true }), - new TableColumn({ name: "name", type: "varchar" }), - new TableColumn({ name: "description", type: "varchar", isNullable: true }), - new TableColumn({ name: "banner_asset_id", type: "varchar", isNullable: true }), - new TableColumn({ name: "cover_sticker_id", type: "varchar", isNullable: true }), - ], - foreignKeys: [ - new TableForeignKey({ - columnNames: ["cover_sticker_id"], - referencedColumnNames: ["id"], - referencedTableName: "stickers", - }), - ], - }) - ); - } - - public async down(queryRunner: QueryRunner): Promise<void> {} -} diff --git a/util/src/migrations/1634424361103-Presence.ts b/util/src/migrations/1634424361103-Presence.ts deleted file mode 100644
index 729955b8..00000000 --- a/util/src/migrations/1634424361103-Presence.ts +++ /dev/null
@@ -1,11 +0,0 @@ -import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; - -export class Presence1634424361103 implements MigrationInterface { - name = "Presence1634424361103"; - - public async up(queryRunner: QueryRunner): Promise<void> { - queryRunner.addColumn("sessions", new TableColumn({ name: "activites", type: "text" })); - } - - public async down(queryRunner: QueryRunner): Promise<void> {} -} diff --git a/util/src/migrations/1634426540271-MigrationTimestamp.ts b/util/src/migrations/1634426540271-MigrationTimestamp.ts deleted file mode 100644
index 3208b25b..00000000 --- a/util/src/migrations/1634426540271-MigrationTimestamp.ts +++ /dev/null
@@ -1,15 +0,0 @@ -import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; - -export class MigrationTimestamp1634426540271 implements MigrationInterface { - name = "MigrationTimestamp1634426540271"; - - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.changeColumn( - "migrations", - "timestamp", - new TableColumn({ name: "timestampe", type: "bigint", isNullable: false }) - ); - } - - public async down(queryRunner: QueryRunner): Promise<void> {} -} diff --git a/util/src/migrations/1648643945733-ReleaseTypo.ts b/util/src/migrations/1648643945733-ReleaseTypo.ts deleted file mode 100644
index 944b9dd9..00000000 --- a/util/src/migrations/1648643945733-ReleaseTypo.ts +++ /dev/null
@@ -1,16 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class ReleaseTypo1648643945733 implements MigrationInterface { - name = "ReleaseTypo1648643945733"; - - public async up(queryRunner: QueryRunner): Promise<void> { - //drop table first because typeorm creates it before migrations run - await queryRunner.dropTable("client_release", true); - await queryRunner.renameTable("client_relase", "client_release"); - } - - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.dropTable("client_relase", true); - await queryRunner.renameTable("client_release", "client_relase"); - } -} diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts deleted file mode 100644
index 31b8d97c..00000000 --- a/util/src/util/Config.ts +++ /dev/null
@@ -1,81 +0,0 @@ -import "missing-native-js-functions"; -import { ConfigValue, ConfigEntity, DefaultConfigOptions } from "../entities/Config"; -import path from "path"; -import fs from "fs"; - -// TODO: yaml instead of json -// const overridePath = path.join(process.cwd(), "config.json"); - -var config: ConfigValue; -var pairs: ConfigEntity[]; - -// TODO: use events to inform about config updates -// Config keys are separated with _ - -export const Config = { - init: async function init() { - if (config) return config; - pairs = await ConfigEntity.find(); - config = pairsToConfig(pairs); - config = (config || {}).merge(DefaultConfigOptions); - - // try { - // const overrideConfig = JSON.parse(fs.readFileSync(overridePath, { encoding: "utf8" })); - // config = overrideConfig.merge(config); - // } catch (error) { - // fs.writeFileSync(overridePath, JSON.stringify(config, null, 4)); - // } - - return this.set(config); - }, - get: function get() { - return config; - }, - set: function set(val: Partial<ConfigValue>) { - if (!config || !val) return; - config = val.merge(config); - - return applyConfig(config); - }, -}; - -function applyConfig(val: ConfigValue) { - async function apply(obj: any, key = ""): Promise<any> { - if (typeof obj === "object" && obj !== null) - return Promise.all(Object.keys(obj).map((k) => apply(obj[k], key ? `${key}_${k}` : k))); - - let pair = pairs.find((x) => x.key === key); - if (!pair) pair = new ConfigEntity(); - - pair.key = key; - pair.value = obj; - return pair.save(); - } - // fs.writeFileSync(overridePath, JSON.stringify(val, null, 4)); - - return apply(val); -} - -function pairsToConfig(pairs: ConfigEntity[]) { - var value: any = {}; - - pairs.forEach((p) => { - const keys = p.key.split("_"); - let obj = value; - let prev = ""; - let prevObj = obj; - let i = 0; - - for (const key of keys) { - if (!isNaN(Number(key)) && !prevObj[prev]?.length) prevObj[prev] = obj = []; - if (i++ === keys.length - 1) obj[key] = p.value; - else if (!obj[key]) obj[key] = {}; - - prev = key; - prevObj = obj; - obj = obj[key]; - } - }); - - return value as ConfigValue; -} diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts deleted file mode 100644
index 9ab5d14c..00000000 --- a/util/src/util/Database.ts +++ /dev/null
@@ -1,72 +0,0 @@ -import path from "path"; -import "reflect-metadata"; -import { Connection, createConnection } from "typeorm"; -import * as Models from "../entities"; -import { Migration } from "../entities/Migration"; -import { yellow, green, red } from "picocolors"; - -// UUID extension option is only supported with postgres -// We want to generate all id's with Snowflakes that's why we have our own BaseEntity class - -var promise: Promise<any>; -var dbConnection: Connection | undefined; -let dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db"); - -export function initDatabase(): Promise<Connection> { - if (promise) return promise; // prevent initalizing multiple times - - const type = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : "sqlite"; - const isSqlite = type.includes("sqlite"); - - console.log(`[Database] ${yellow(`connecting to ${type} db`)}`); - if(isSqlite) { - console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`); - } - // @ts-ignore - promise = createConnection({ - type, - charset: 'utf8mb4', - url: isSqlite ? undefined : dbConnectionString, - database: isSqlite ? dbConnectionString : undefined, - // @ts-ignore - entities: Object.values(Models).filter((x) => x.constructor.name !== "Object" && x.name), - synchronize: type !== "mongodb", - logging: false, - cache: { - duration: 1000 * 3, // cache all find queries for 3 seconds - }, - bigNumberStrings: false, - supportBigNumbers: true, - name: "default", - migrations: [path.join(__dirname, "..", "migrations", "*.js")], - }); - - promise.then(async (connection: Connection) => { - dbConnection = connection; - - // run migrations, and if it is a new fresh database, set it to the last migration - if (connection.migrations.length) { - if (!(await Migration.findOne({}))) { - let i = 0; - - await Migration.insert( - connection.migrations.map((x) => ({ - id: i++, - name: x.name, - timestamp: Date.now(), - })) - ); - } - } - await connection.runMigrations(); - console.log(`[Database] ${green("connected")}`); - }); - - return promise; -} - -export { dbConnection }; - -export function closeDatabase() { - dbConnection?.close(); -} diff --git a/util/src/util/InvisibleCharacters.ts b/util/src/util/InvisibleCharacters.ts deleted file mode 100644
index 2b014e14..00000000 --- a/util/src/util/InvisibleCharacters.ts +++ /dev/null
@@ -1,56 +0,0 @@ -// List from https://invisible-characters.com/ -export const InvisibleCharacters = [ - '\u{9}', //Tab - '\u{20}', //Space - '\u{ad}', //Soft hyphen - '\u{34f}', //Combining grapheme joiner - '\u{61c}', //Arabic letter mark - '\u{115f}', //Hangul choseong filler - '\u{1160}', //Hangul jungseong filler - '\u{17b4}', //Khmer vowel inherent AQ - '\u{17b5}', //Khmer vowel inherent AA - '\u{180e}', //Mongolian vowel separator - '\u{2000}', //En quad - '\u{2001}', //Em quad - '\u{2002}', //En space - '\u{2003}', //Em space - '\u{2004}', //Three-per-em space - '\u{2005}', //Four-per-em space - '\u{2006}', //Six-per-em space - '\u{2007}', //Figure space - '\u{2008}', //Punctuation space - '\u{2009}', //Thin space - '\u{200a}', //Hair space - '\u{200b}', //Zero width space - '\u{200c}', //Zero width non-joiner - '\u{200d}', //Zero width joiner - '\u{200e}', //Left-to-right mark - '\u{200f}', //Right-to-left mark - '\u{202f}', //Narrow no-break space - '\u{205f}', //Medium mathematical space - '\u{2060}', //Word joiner - '\u{2061}', //Function application - '\u{2062}', //Invisible times - '\u{2063}', //Invisible separator - '\u{2064}', //Invisible plus - '\u{206a}', //Inhibit symmetric swapping - '\u{206b}', //Activate symmetric swapping - '\u{206c}', //Inhibit arabic form shaping - '\u{206d}', //Activate arabic form shaping - '\u{206e}', //National digit shapes - '\u{206f}', //Nominal digit shapes - '\u{3000}', //Ideographic space - '\u{2800}', //Braille pattern blank - '\u{3164}', //Hangul filler - '\u{feff}', //Zero width no-break space - '\u{ffa0}', //Haldwidth hangul filler - '\u{1d159}', //Musical symbol null notehead - '\u{1d173}', //Musical symbol begin beam - '\u{1d174}', //Musical symbol end beam - '\u{1d175}', //Musical symbol begin tie - '\u{1d176}', //Musical symbol end tie - '\u{1d177}', //Musical symbol begin slur - '\u{1d178}', //Musical symbol end slur - '\u{1d179}', //Musical symbol begin phrase - '\u{1d17a}' //Musical symbol end phrase -]; \ No newline at end of file