diff options
author | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2022-09-26 22:29:30 +1000 |
---|---|---|
committer | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2022-09-26 22:41:21 +1000 |
commit | 99ee7e9400f06e8718612d8b52d15215dc620774 (patch) | |
tree | 08de8c5d3985b9c2eaa419f5198f891ecd82d012 /src/util | |
parent | Remove the cdn storage location log (diff) | |
download | server-99ee7e9400f06e8718612d8b52d15215dc620774.tar.xz |
Prettier
Diffstat (limited to 'src/util')
102 files changed, 1816 insertions, 945 deletions
diff --git a/src/util/dtos/DmChannelDTO.ts b/src/util/dtos/DmChannelDTO.ts index 226b2f9d..fcc91204 100644 --- a/src/util/dtos/DmChannelDTO.ts +++ b/src/util/dtos/DmChannelDTO.ts @@ -11,7 +11,11 @@ export class DmChannelDTO { recipients: MinimalPublicUserDTO[]; type: number; - static async from(channel: Channel, excluded_recipients: string[] = [], origin_channel_id?: string) { + static async from( + channel: Channel, + excluded_recipients: string[] = [], + origin_channel_id?: string, + ) { const obj = new DmChannelDTO(); obj.icon = channel.icon || null; obj.id = channel.id; @@ -23,10 +27,15 @@ export class DmChannelDTO { obj.recipients = ( await Promise.all( channel - .recipients!.filter((r) => !excluded_recipients.includes(r.user_id)) + .recipients!.filter( + (r) => !excluded_recipients.includes(r.user_id), + ) .map(async (r) => { - return await User.findOneOrFail({ where: { id: r.user_id }, select: PublicUserProjection }); - }) + return await User.findOneOrFail({ + where: { id: r.user_id }, + select: PublicUserProjection, + }); + }), ) ).map((u) => new MinimalPublicUserDTO(u)); return obj; @@ -35,7 +44,9 @@ 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/src/util/entities/Attachment.ts b/src/util/entities/Attachment.ts index 7b4b17eb..055b6f4b 100644 --- a/src/util/entities/Attachment.ts +++ b/src/util/entities/Attachment.ts @@ -1,4 +1,11 @@ -import { BeforeRemove, Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { + BeforeRemove, + Column, + Entity, + JoinColumn, + ManyToOne, + RelationId, +} from "typeorm"; import { URL } from "url"; import { deleteFile } from "../util/cdn"; import { BaseClass } from "./BaseClass"; @@ -31,9 +38,13 @@ export class Attachment extends BaseClass { message_id: string; @JoinColumn({ name: "message_id" }) - @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, { - onDelete: "CASCADE", - }) + @ManyToOne( + () => require("./Message").Message, + (message: import("./Message").Message) => message.attachments, + { + onDelete: "CASCADE", + }, + ) message: import("./Message").Message; @BeforeRemove() diff --git a/src/util/entities/AuditLog.ts b/src/util/entities/AuditLog.ts index b003e7ba..9cc97742 100644 --- a/src/util/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,17 +79,17 @@ 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_CREATE = 225, ROUTE_UPDATE = 226, } diff --git a/src/util/entities/BackupCodes.ts b/src/util/entities/BackupCodes.ts index d532a39a..81cdbb6d 100644 --- a/src/util/entities/BackupCodes.ts +++ b/src/util/entities/BackupCodes.ts @@ -24,7 +24,7 @@ export function generateMfaBackupCodes(user_id: string) { for (let i = 0; i < 10; i++) { const code = BackupCode.create({ user: { id: user_id }, - code: crypto.randomBytes(4).toString("hex"), // 8 characters + code: crypto.randomBytes(4).toString("hex"), // 8 characters consumed: false, expired: false, }); @@ -32,4 +32,4 @@ export function generateMfaBackupCodes(user_id: string) { } return backup_codes; -} \ No newline at end of file +} diff --git a/src/util/entities/BaseClass.ts b/src/util/entities/BaseClass.ts index d5a7c2bf..9942b60e 100644 --- a/src/util/entities/BaseClass.ts +++ b/src/util/entities/BaseClass.ts @@ -1,5 +1,12 @@ import "reflect-metadata"; -import { BaseEntity, BeforeInsert, BeforeUpdate, FindOptionsWhere, ObjectIdColumn, PrimaryColumn } from "typeorm"; +import { + BaseEntity, + BeforeInsert, + BeforeUpdate, + FindOptionsWhere, + ObjectIdColumn, + PrimaryColumn, +} from "typeorm"; import { Snowflake } from "../util/Snowflake"; import "missing-native-js-functions"; import { getDatabase } from ".."; @@ -22,23 +29,40 @@ export class BaseClassWithoutId extends BaseEntity { 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]])) + .map((x) => [x.propertyName, this[x.propertyName]]) + .concat( + // @ts-ignore + this.metadata.relations.map((x) => [ + x.propertyName, + // @ts-ignore + this[x.propertyName], + ]), + ), ); } - static increment<T extends BaseClass>(conditions: FindOptionsWhere<T>, propertyPath: string, value: number | string) { + static increment<T extends BaseClass>( + conditions: FindOptionsWhere<T>, + propertyPath: string, + value: number | string, + ) { const repository = this.getRepository(); return repository.increment(conditions, propertyPath, value); } - static decrement<T extends BaseClass>(conditions: FindOptionsWhere<T>, propertyPath: string, value: number | string) { + static decrement<T extends BaseClass>( + conditions: FindOptionsWhere<T>, + propertyPath: string, + value: number | string, + ) { const repository = this.getRepository(); return repository.decrement(conditions, propertyPath, value); } } -export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn; +export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") + ? ObjectIdColumn + : PrimaryColumn; export class BaseClass extends BaseClassWithoutId { @PrimaryIdColumn() diff --git a/src/util/entities/Categories.ts b/src/util/entities/Categories.ts index 81fbc303..f12b237d 100644 --- a/src/util/entities/Categories.ts +++ b/src/util/entities/Categories.ts @@ -1,4 +1,4 @@ -import { PrimaryColumn, Column, Entity} from "typeorm"; +import { PrimaryColumn, Column, Entity } 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/src/util/entities/Channel.ts b/src/util/entities/Channel.ts index 2200bfa3..14f36857 100644 --- a/src/util/entities/Channel.ts +++ b/src/util/entities/Channel.ts @@ -1,389 +1,457 @@ -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, ChannelTypes } 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() - nsfw: boolean = false; - - @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({ where: { 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); - - // Categories skip these checks on discord.com - if (channel.type !== ChannelType.GUILD_CATEGORY) { - 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([ - Channel.create(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); - // TODO: check config for max number of recipients - /** if you want to disallow note to self channels, uncomment the conditional below - - const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); - if (otherRecipientsUsers.length !== recipients.length) { - throw new HTTPError("Recipient/s not found"); - } - **/ - - const type = recipients.length > 1 ? ChannelType.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; - await ur.assign({ closed: false }).save(); - } - } - } - } - - if (channel == null) { - name = trimSpecial(name); - - channel = await Channel.create({ - name, - type, - owner_id: undefined, - created_at: new Date(), - last_message_id: undefined, - recipients: channelRecipients.map( - (x) => - Recipient.create({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) }) - ), - nsfw: false, - }).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 { BaseClass } from "./BaseClass"; +import { Guild } from "./Guild"; +import { PublicUserProjection, User } from "./User"; +import { HTTPError } from "lambert-server"; +import { + containsAll, + emitEvent, + getPermission, + Snowflake, + trimSpecial, + InvisibleCharacters, + ChannelTypes, +} 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() + nsfw: boolean = false; + + @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({ + where: { 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, + ); + + // Categories skip these checks on discord.com + if (channel.type !== ChannelType.GUILD_CATEGORY) { + 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([ + Channel.create(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); + // TODO: check config for max number of recipients + /** if you want to disallow note to self channels, uncomment the conditional below + + const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); + if (otherRecipientsUsers.length !== recipients.length) { + throw new HTTPError("Recipient/s not found"); + } + **/ + + const type = + recipients.length > 1 ? ChannelType.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; + await ur.assign({ closed: false }).save(); + } + } + } + } + + if (channel == null) { + name = trimSpecial(name); + + channel = await Channel.create({ + name, + type, + owner_id: undefined, + created_at: new Date(), + last_message_id: undefined, + recipients: channelRecipients.map((x) => + Recipient.create({ + user_id: x, + closed: !( + type === ChannelType.GROUP_DM || + x === creator_user_id + ), + }), + ), + nsfw: false, + }).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/src/util/entities/ClientRelease.ts b/src/util/entities/ClientRelease.ts index c5afd307..2723ab67 100644 --- a/src/util/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/src/util/entities/Config.ts b/src/util/entities/Config.ts index 9aabc1a8..cd7a6923 100644 --- a/src/util/entities/Config.ts +++ b/src/util/entities/Config.ts @@ -191,17 +191,17 @@ export interface ConfigValue { allowTemplateCreation: Boolean; allowDiscordTemplates: Boolean; allowRaws: Boolean; - }, + }; client: { useTestClient: Boolean; releases: { useLocalRelease: Boolean; //TODO upstreamVersion: string; }; - }, + }; metrics: { timeout: number; - }, + }; sentry: { enabled: boolean; endpoint: string; @@ -230,7 +230,8 @@ export const DefaultConfigOptions: ConfigValue = { }, general: { instanceName: "Fosscord Instance", - instanceDescription: "This is a Fosscord instance made in pre-release days", + instanceDescription: + "This is a Fosscord instance made in pre-release days", frontPage: null, tosPage: null, correspondenceEmail: "noreply@localhost.local", @@ -318,8 +319,9 @@ export const DefaultConfigOptions: ConfigValue = { sitekey: null, secret: null, }, - ipdataApiKey: "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9", - defaultRights: "30644591655936", // See util/scripts/rights.js + ipdataApiKey: + "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9", + defaultRights: "30644591655936", // See util/scripts/rights.js }, login: { requireCaptcha: false, @@ -395,22 +397,23 @@ export const DefaultConfigOptions: ConfigValue = { enabled: true, allowTemplateCreation: true, allowDiscordTemplates: true, - allowRaws: false + allowRaws: false, }, client: { useTestClient: true, releases: { useLocalRelease: true, - upstreamVersion: "0.0.264" - } + upstreamVersion: "0.0.264", + }, }, metrics: { - timeout: 30000 + timeout: 30000, }, sentry: { enabled: false, - endpoint: "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6", + endpoint: + "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6", traceSampleRate: 1.0, - environment: hostname() - } -}; \ No newline at end of file + environment: hostname(), + }, +}; diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts index 09ae30ab..a893ff34 100644 --- a/src/util/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts @@ -2,7 +2,8 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { User } from "./User"; -export interface PublicConnectedAccount extends Pick<ConnectedAccount, "name" | "type" | "verified"> {} +export interface PublicConnectedAccount + extends Pick<ConnectedAccount, "name" | "type" | "verified"> {} @Entity("connected_accounts") export class ConnectedAccount extends BaseClass { diff --git a/src/util/entities/Emoji.ts b/src/util/entities/Emoji.ts index a3615b7d..0aa640b5 100644 --- a/src/util/entities/Emoji.ts +++ b/src/util/entities/Emoji.ts @@ -40,7 +40,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/src/util/entities/Encryption.ts b/src/util/entities/Encryption.ts index b597b90a..4c427b32 100644 --- a/src/util/entities/Encryption.ts +++ b/src/util/entities/Encryption.ts @@ -1,9 +1,23 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; +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 { + containsAll, + emitEvent, + getPermission, + Snowflake, + trimSpecial, + InvisibleCharacters, +} from "../util"; import { BitField, BitFieldResolvable, BitFlag } from "../util/BitField"; import { Recipient } from "./Recipient"; import { Message } from "./Message"; @@ -13,7 +27,6 @@ import { DmChannelDTO } from "../dtos"; @Entity("security_settings") export class SecuritySettings extends BaseClass { - @Column({ nullable: true }) guild_id: string; @@ -31,5 +44,4 @@ export class SecuritySettings extends BaseClass { @Column({ nullable: true }) used_since_message: string; - } diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts index 2ce7c213..8854fec0 100644 --- a/src/util/entities/Guild.ts +++ b/src/util/entities/Guild.ts @@ -1,4 +1,13 @@ -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; +import { + Column, + Entity, + JoinColumn, + ManyToMany, + ManyToOne, + OneToMany, + OneToOne, + RelationId, +} from "typeorm"; import { Config, handleFile, Snowflake } from ".."; import { Ban } from "./Ban"; import { BaseClass } from "./BaseClass"; @@ -86,7 +95,7 @@ export class Guild extends BaseClass { //TODO: https://discord.com/developers/docs/resources/guild#guild-object-guild-features @Column({ nullable: true }) - primary_category_id?: string; // TODO: this was number? + primary_category_id?: string; // TODO: this was number? @Column({ nullable: true }) icon?: string; @@ -269,7 +278,7 @@ export class Guild extends BaseClass { @Column() nsfw: boolean; - + // TODO: nested guilds @Column({ nullable: true }) parent?: string; @@ -332,10 +341,13 @@ export class Guild extends BaseClass { permissions: String("2251804225"), position: 0, icon: undefined, - unicode_emoji: undefined + unicode_emoji: undefined, }).save(); - if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general", nsfw: false }]; + if (!body.channels || !body.channels.length) + body.channels = [ + { id: "01", type: 0, name: "general", nsfw: false }, + ]; const ids = new Map(); @@ -345,17 +357,23 @@ export class Guild extends BaseClass { } }); - for (const channel of body.channels?.sort((a, b) => (a.parent_id ? 1 : -1))) { + for (const channel of body.channels?.sort((a, b) => + a.parent_id ? 1 : -1, + )) { var id = ids.get(channel.id) || Snowflake.generate(); var 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, - }); + await Channel.createChannel( + { ...channel, guild_id, id, parent_id }, + body.owner_id, + { + keepId: true, + skipExistsCheck: true, + skipPermissionCheck: true, + skipEventEmit: true, + }, + ); } return guild; diff --git a/src/util/entities/Invite.ts b/src/util/entities/Invite.ts index 4f36f247..90dec92a 100644 --- a/src/util/entities/Invite.ts +++ b/src/util/entities/Invite.ts @@ -1,4 +1,11 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId, PrimaryColumn } from "typeorm"; +import { + Column, + Entity, + JoinColumn, + ManyToOne, + RelationId, + PrimaryColumn, +} from "typeorm"; import { Member } from "./Member"; import { BaseClassWithoutId } from "./BaseClass"; import { Channel } from "./Channel"; @@ -76,7 +83,8 @@ export class Invite extends BaseClassWithoutId { static async joinGuild(user_id: string, code: string) { const invite = await Invite.findOneOrFail({ where: { code } }); - if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code }); + if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) + await Invite.delete({ code }); else await invite.save(); await Member.addToGuild(user_id, invite.guild_id); diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts index 7d1346ba..f2762adc 100644 --- a/src/util/entities/Member.ts +++ b/src/util/entities/Member.ts @@ -22,7 +22,6 @@ import { GuildMemberRemoveEvent, GuildMemberUpdateEvent, MessageCreateEvent, - } from "../interfaces"; import { HTTPError } from "lambert-server"; import { Role } from "./Role"; @@ -126,19 +125,34 @@ export class Member extends BaseClassWithoutId { if (this.nick) { this.nick = this.nick.split("\n").join(""); this.nick = this.nick.split("\t").join(""); - if (BannedWords.find(this.nick)) throw FieldErrors({ nick: { message: "Bad nickname", code: "INVALID_NICKNAME" } }); + if (BannedWords.find(this.nick)) + throw FieldErrors({ + nick: { message: "Bad nickname", code: "INVALID_NICKNAME" }, + }); } } static async IsInGuildOrFail(user_id: string, guild_id: string) { - if (await Member.count({ where: { 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 } }); - 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"] }); + const guild = await Guild.findOneOrFail({ + select: ["owner_id"], + 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 return Promise.all([ @@ -169,9 +183,12 @@ export class Member extends BaseClassWithoutId { where: { id: user_id, guild_id }, relations: ["user", "roles"], // we don't want to load the role objects just the ids //@ts-ignore - select: ["index", "roles.id"], // TODO fix type + select: ["index", "roles.id"], // TODO fix type + }), + Role.findOneOrFail({ + where: { id: role_id, guild_id }, + select: ["id"], }), - Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }), ]); member.roles.push(Role.create({ id: role_id })); @@ -189,7 +206,11 @@ export class Member extends BaseClassWithoutId { ]); } - static async removeRole(user_id: string, guild_id: string, role_id: string) { + static async removeRole( + user_id: string, + guild_id: string, + role_id: string, + ) { const [member] = await Promise.all([ Member.findOneOrFail({ where: { id: user_id, guild_id }, @@ -215,7 +236,11 @@ export class Member extends BaseClassWithoutId { ]); } - static async changeNickname(user_id: string, guild_id: string, nickname: string) { + static async changeNickname( + user_id: string, + guild_id: string, + nickname: string, + ) { const member = await Member.findOneOrFail({ where: { id: user_id, @@ -249,7 +274,10 @@ export class Member extends BaseClassWithoutId { const { maxGuilds } = Config.get().limits.user; 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); + throw new HTTPError( + `You are at the ${maxGuilds} server limit.`, + 403, + ); } const guild = await Guild.findOneOrFail({ @@ -259,7 +287,11 @@ export class Member extends BaseClassWithoutId { relations: [...PublicGuildRelations, "system_channel"], }); - if (await Member.count({ where: { 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 = { @@ -268,7 +300,7 @@ export class Member extends BaseClassWithoutId { nick: undefined, roles: [guild_id], // @everyone role joined_at: new Date(), - premium_since: (new Date()).getTime(), + premium_since: new Date().getTime(), deaf: false, mute: false, pending: false, @@ -339,7 +371,11 @@ export class Member extends BaseClassWithoutId { }); await Promise.all([ message.save(), - emitEvent({ event: "MESSAGE_CREATE", channel_id: message.channel_id, data: message } as MessageCreateEvent) + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: message.channel_id, + data: message, + } as MessageCreateEvent), ]); } } @@ -362,7 +398,7 @@ export interface UserGuildSettings { channel_overrides: { [channel_id: string]: ChannelOverride; - } | null, + } | null; message_notifications: number; mobile_push: boolean; mute_config: MuteConfig | null; @@ -389,7 +425,7 @@ export const DefaultUserGuildSettings: UserGuildSettings = { notify_highlights: 0, suppress_everyone: false, suppress_roles: false, - version: 453, // ? + version: 453, // ? guild_id: null, }; diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts index be790502..a52b4785 100644 --- a/src/util/entities/Message.ts +++ b/src/util/entities/Message.ts @@ -51,7 +51,7 @@ export enum MessageType { SELF_COMMAND_SCRIPT = 43, // self command scripts ENCRYPTION = 50, CUSTOM_START = 63, - UNHANDLED = 255 + UNHANDLED = 255, } @Entity("messages") @@ -115,7 +115,10 @@ export class Message extends BaseClass { @ManyToOne(() => Application) application?: Application; - @Column({ nullable: true, type: process.env.PRODUCTION ? "longtext" : undefined }) + @Column({ + nullable: true, + type: process.env.PRODUCTION ? "longtext" : undefined, + }) content?: string; @Column() @@ -147,10 +150,14 @@ export class Message extends BaseClass { @ManyToMany(() => Sticker, { cascade: true, onDelete: "CASCADE" }) sticker_items?: Sticker[]; - @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { - cascade: true, - orphanedRowAction: "delete", - }) + @OneToMany( + () => Attachment, + (attachment: Attachment) => attachment.message, + { + cascade: true, + orphanedRowAction: "delete", + }, + ) attachments?: Attachment[]; @Column({ type: "simple-json" }) @@ -176,7 +183,7 @@ export class Message extends BaseClass { @Column({ nullable: true }) flags?: string; - + @Column({ type: "simple-json", nullable: true }) message_reference?: { message_id: string; @@ -204,7 +211,11 @@ export class Message extends BaseClass { @BeforeInsert() validate() { if (this.content) { - if (BannedWords.find(this.content)) throw new HTTPError("Message was blocked by automatic moderation", 200000); + if (BannedWords.find(this.content)) + throw new HTTPError( + "Message was blocked by automatic moderation", + 200000, + ); } } } diff --git a/src/util/entities/Migration.ts b/src/util/entities/Migration.ts index 3f39ae72..f4e54eae 100644 --- a/src/util/entities/Migration.ts +++ b/src/util/entities/Migration.ts @@ -1,7 +1,14 @@ -import { Column, Entity, ObjectIdColumn, PrimaryGeneratedColumn } from "typeorm"; +import { + Column, + Entity, + ObjectIdColumn, + PrimaryGeneratedColumn, +} from "typeorm"; import { BaseClassWithoutId } from "."; -export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith("mongodb") +export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith( + "mongodb", +) ? ObjectIdColumn : PrimaryGeneratedColumn; diff --git a/src/util/entities/Note.ts b/src/util/entities/Note.ts index 36017c5e..b3ac45ee 100644 --- a/src/util/entities/Note.ts +++ b/src/util/entities/Note.ts @@ -15,4 +15,4 @@ export class Note extends BaseClass { @Column() content: string; -} \ No newline at end of file +} diff --git a/src/util/entities/ReadState.ts b/src/util/entities/ReadState.ts index b915573b..53ed5589 100644 --- a/src/util/entities/ReadState.ts +++ b/src/util/entities/ReadState.ts @@ -1,4 +1,11 @@ -import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { + Column, + Entity, + Index, + JoinColumn, + ManyToOne, + RelationId, +} from "typeorm"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; import { Message } from "./Message"; @@ -33,8 +40,8 @@ export class ReadState extends BaseClass { // 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/src/util/entities/Relationship.ts b/src/util/entities/Relationship.ts index c3592c76..25b52757 100644 --- a/src/util/entities/Relationship.ts +++ b/src/util/entities/Relationship.ts @@ -1,4 +1,11 @@ -import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { + Column, + Entity, + Index, + JoinColumn, + ManyToOne, + RelationId, +} from "typeorm"; import { BaseClass } from "./BaseClass"; import { User } from "./User"; diff --git a/src/util/entities/StickerPack.ts b/src/util/entities/StickerPack.ts index ec8c69a2..04d74bac 100644 --- a/src/util/entities/StickerPack.ts +++ b/src/util/entities/StickerPack.ts @@ -1,4 +1,12 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + OneToOne, + RelationId, +} from "typeorm"; import { Sticker } from "."; import { BaseClass } from "./BaseClass"; diff --git a/src/util/entities/Team.ts b/src/util/entities/Team.ts index 22140b7f..8f410bb4 100644 --- a/src/util/entities/Team.ts +++ b/src/util/entities/Team.ts @@ -1,4 +1,12 @@ -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { + Column, + Entity, + JoinColumn, + ManyToMany, + ManyToOne, + OneToMany, + RelationId, +} from "typeorm"; import { BaseClass } from "./BaseClass"; import { TeamMember } from "./TeamMember"; import { User } from "./User"; diff --git a/src/util/entities/TeamMember.ts b/src/util/entities/TeamMember.ts index b726e1e8..3f4a0422 100644 --- a/src/util/entities/TeamMember.ts +++ b/src/util/entities/TeamMember.ts @@ -20,9 +20,13 @@ export class TeamMember extends BaseClass { team_id: string; @JoinColumn({ name: "team_id" }) - @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, { - onDelete: "CASCADE", - }) + @ManyToOne( + () => require("./Team").Team, + (team: import("./Team").Team) => team.members, + { + onDelete: "CASCADE", + }, + ) team: import("./Team").Team; @Column({ nullable: true }) diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts index 84a8a674..1389a424 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -1,9 +1,24 @@ -import { BeforeInsert, BeforeUpdate, Column, Entity, FindOneOptions, JoinColumn, OneToMany } from "typeorm"; +import { + BeforeInsert, + BeforeUpdate, + Column, + Entity, + FindOneOptions, + JoinColumn, + OneToMany, +} from "typeorm"; import { BaseClass } from "./BaseClass"; import { BitField } from "../util/BitField"; import { Relationship } from "./Relationship"; import { ConnectedAccount } from "./ConnectedAccount"; -import { Config, FieldErrors, Snowflake, trimSpecial, BannedWords, adjustEmail } from ".."; +import { + Config, + FieldErrors, + Snowflake, + trimSpecial, + BannedWords, + adjustEmail, +} from ".."; import { Member, Session } from "."; export enum PublicUserEnum { @@ -38,7 +53,7 @@ export enum PrivateUserEnum { export type PrivateUserKeys = keyof typeof PrivateUserEnum | PublicUserKeys; export const PublicUserProjection = Object.values(PublicUserEnum).filter( - (x) => typeof x === "string" + (x) => typeof x === "string", ) as PublicUserKeys[]; export const PrivateUserProjection = [ ...PublicUserProjection, @@ -48,7 +63,7 @@ export const PrivateUserProjection = [ // Private user data that should never get sent to the client export type PublicUser = Pick<User, PublicUserKeys>; -export interface UserPublic extends Pick<User, PublicUserKeys> { } +export interface UserPublic extends Pick<User, PublicUserKeys> {} export interface UserPrivate extends Pick<User, PrivateUserKeys> { locale: string; @@ -144,17 +159,25 @@ export class User extends BaseClass { sessions: Session[]; @JoinColumn({ name: "relationship_ids" }) - @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, { - cascade: true, - orphanedRowAction: "delete", - }) + @OneToMany( + () => Relationship, + (relationship: Relationship) => relationship.from, + { + cascade: true, + orphanedRowAction: "delete", + }, + ) relationships: Relationship[]; @JoinColumn({ name: "connected_account_ids" }) - @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, { - cascade: true, - orphanedRowAction: "delete", - }) + @OneToMany( + () => ConnectedAccount, + (account: ConnectedAccount) => account.user, + { + cascade: true, + orphanedRowAction: "delete", + }, + ) connected_accounts: ConnectedAccount[]; @Column({ type: "simple-json", select: false }) @@ -177,16 +200,43 @@ export class User extends BaseClass { @BeforeInsert() validate() { this.email = adjustEmail(this.email); - if (!this.email) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } }); - if (!this.email.match(/([a-z\d.-]{3,})@([a-z\d.-]+).([a-z]{2,})/g)) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } }); + if (!this.email) + throw FieldErrors({ + email: { message: "Invalid email", code: "EMAIL_INVALID" }, + }); + if (!this.email.match(/([a-z\d.-]{3,})@([a-z\d.-]+).([a-z]{2,})/g)) + throw FieldErrors({ + email: { message: "Invalid email", code: "EMAIL_INVALID" }, + }); const discrim = Number(this.discriminator); - if (this.discriminator.length > 4) throw FieldErrors({ email: { message: "Discriminator cannot be more than 4 digits.", code: "DISCRIMINATOR_INVALID" } }); - if (isNaN(discrim)) throw FieldErrors({ email: { message: "Discriminator must be a number.", code: "DISCRIMINATOR_INVALID" } }); - if (discrim <= 0 || discrim >= 10000) throw FieldErrors({ email: { message: "Discriminator must be a number.", code: "DISCRIMINATOR_INVALID" } }); + if (this.discriminator.length > 4) + throw FieldErrors({ + email: { + message: "Discriminator cannot be more than 4 digits.", + code: "DISCRIMINATOR_INVALID", + }, + }); + if (isNaN(discrim)) + throw FieldErrors({ + email: { + message: "Discriminator must be a number.", + code: "DISCRIMINATOR_INVALID", + }, + }); + if (discrim <= 0 || discrim >= 10000) + throw FieldErrors({ + email: { + message: "Discriminator must be a number.", + code: "DISCRIMINATOR_INVALID", + }, + }); this.discriminator = discrim.toString().padStart(4, "0"); - if (BannedWords.find(this.username)) throw FieldErrors({ username: { message: "Bad username", code: "INVALID_USERNAME" } }); + if (BannedWords.find(this.username)) + throw FieldErrors({ + username: { message: "Bad username", code: "INVALID_USERNAME" }, + }); } toPublicUser() { @@ -202,17 +252,25 @@ export class User extends BaseClass { where: { id: user_id }, ...opts, //@ts-ignore - select: [...PublicUserProjection, ...(opts?.select || [])], // TODO: fix + select: [...PublicUserProjection, ...(opts?.select || [])], // TODO: fix }); } - private static async generateDiscriminator(username: string): Promise<string | undefined> { + private 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))); + const users = await User.find({ + where: { username }, + select: ["discriminator"], + }); + const highestDiscriminator = Math.max( + 0, + ...users.map((u) => Number(u.discriminator)), + ); const discriminator = highestDiscriminator + 1; if (discriminator >= 10000) { @@ -226,8 +284,13 @@ export class User extends BaseClass { // randomly generates a discriminator between 1 and 9999 and checks max five times if it already exists // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the database? for (let tries = 0; tries < 5; tries++) { - const discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); - const exists = await User.findOne({ where: { discriminator, username: username }, select: ["id"] }); + const discriminator = Math.randomIntBetween(1, 9999) + .toString() + .padStart(4, "0"); + const exists = await User.findOne({ + where: { discriminator, username: username }, + select: ["id"], + }); if (!exists) return discriminator; } @@ -265,7 +328,8 @@ export class User extends BaseClass { // 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 = User.create({ created_at: new Date(), @@ -295,8 +359,8 @@ export class User extends BaseClass { }, settings: { ...defaultSettings, locale: language }, purchased_flags: 5, // TODO: idk what the values for this are - premium_usage_flags: 2, // TODO: idk what the values for this are - extended_settings: "", // TODO: was {} + premium_usage_flags: 2, // TODO: idk what the values for this are + extended_settings: "", // TODO: was {} fingerprints: [], }); @@ -305,7 +369,7 @@ export class User extends BaseClass { setImmediate(async () => { if (Config.get().guild.autoJoin.enabled) { for (const guild of Config.get().guild.autoJoin.guilds || []) { - await Member.addToGuild(user.id, guild).catch((e) => { }); + await Member.addToGuild(user.id, guild).catch((e) => {}); } } }); @@ -372,7 +436,7 @@ export interface UserSettings { disable_games_tab: boolean; enable_tts_command: boolean; explicit_content_filter: number; - friend_source_flags: { all: boolean; }; + friend_source_flags: { all: boolean }; gateway_connected: boolean; gif_auto_play: boolean; // every top guild is displayed as a "folder" diff --git a/src/util/entities/index.ts b/src/util/entities/index.ts index 49793810..c439a4b7 100644 --- a/src/util/entities/index.ts +++ b/src/util/entities/index.ts @@ -29,4 +29,4 @@ export * from "./VoiceState"; export * from "./Webhook"; export * from "./ClientRelease"; export * from "./BackupCodes"; -export * from "./Note"; \ No newline at end of file +export * from "./Note"; diff --git a/src/util/imports/OrmUtils.ts b/src/util/imports/OrmUtils.ts index 68a1932c..26652db0 100644 --- a/src/util/imports/OrmUtils.ts +++ b/src/util/imports/OrmUtils.ts @@ -9,7 +9,12 @@ export class OrmUtils { return !item.constructor || item.constructor === Object; } - private static mergeArrayKey(target: any, key: number, value: any, memo: Map<any, any>) { + private static mergeArrayKey( + target: any, + key: number, + value: any, + memo: Map<any, any>, + ) { // Have we seen this before? Prevent infinite recursion. if (memo.has(value)) { target[key] = memo.get(value); @@ -38,7 +43,12 @@ export class OrmUtils { memo.delete(value); } - private static mergeObjectKey(target: any, key: string, value: any, memo: Map<any, any>) { + private static mergeObjectKey( + target: any, + key: string, + value: any, + memo: Map<any, any>, + ) { // Have we seen this before? Prevent infinite recursion. if (memo.has(value)) { Object.assign(target, { [key]: memo.get(value) }); @@ -67,7 +77,11 @@ export class OrmUtils { memo.delete(value); } - private static merge(target: any, source: any, memo: Map<any, any> = new Map()): any { + private static merge( + target: any, + source: any, + memo: Map<any, any> = new Map(), + ): any { if (Array.isArray(target) && Array.isArray(source)) { for (let key = 0; key < source.length; key++) { this.mergeArrayKey(target, key, source[key], memo); @@ -93,4 +107,4 @@ export class OrmUtils { return target; } -} \ No newline at end of file +} diff --git a/src/util/imports/index.ts b/src/util/imports/index.ts index 5d9bfb5f..823151d9 100644 --- a/src/util/imports/index.ts +++ b/src/util/imports/index.ts @@ -1 +1 @@ -export * from "./OrmUtils"; \ No newline at end of file +export * from "./OrmUtils"; diff --git a/src/util/index.ts b/src/util/index.ts index 385070a3..3773c275 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -5,4 +5,4 @@ export * from "./interfaces/index"; export * from "./entities/index"; export * from "./dtos/index"; export * from "./schemas"; -export * from "./imports"; \ No newline at end of file +export * from "./imports"; diff --git a/src/util/interfaces/Activity.ts b/src/util/interfaces/Activity.ts index 9912e197..279ee40f 100644 --- a/src/util/interfaces/Activity.ts +++ b/src/util/interfaces/Activity.ts @@ -36,7 +36,8 @@ export interface Activity { id?: string; sync_id?: string; - metadata?: { // spotify + metadata?: { + // spotify context_uri?: string; album_id: string; artist_ids: string[]; diff --git a/src/util/interfaces/Event.ts b/src/util/interfaces/Event.ts index 59f995db..472fd572 100644 --- a/src/util/interfaces/Event.ts +++ b/src/util/interfaces/Event.ts @@ -75,7 +75,7 @@ export interface ReadyEventData { number, [[number, { e: number; s: number }[]]], [number, [[number, [number, number]]]], - { b: number; k: bigint[] }[] + { b: number; k: bigint[] }[], ][]; guild_join_requests?: any[]; // ? what is this? this is new shard?: [number, number]; diff --git a/src/util/migrations/1633864260873-EmojiRoles.ts b/src/util/migrations/1633864260873-EmojiRoles.ts index f0d709f2..31ced96b 100644 --- a/src/util/migrations/1633864260873-EmojiRoles.ts +++ b/src/util/migrations/1633864260873-EmojiRoles.ts @@ -4,10 +4,14 @@ 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 ''`); + 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"`); + await queryRunner.query( + `ALTER TABLE "emojis" DROP COLUMN column_name "roles"`, + ); } } diff --git a/src/util/migrations/1633864669243-EmojiUser.ts b/src/util/migrations/1633864669243-EmojiUser.ts index 982405d7..9610216b 100644 --- a/src/util/migrations/1633864669243-EmojiUser.ts +++ b/src/util/migrations/1633864669243-EmojiUser.ts @@ -7,17 +7,21 @@ export class EmojiUser1633864669243 implements MigrationInterface { 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)` + `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" + "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`); + 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/src/util/migrations/1633881705509-VanityInvite.ts b/src/util/migrations/1633881705509-VanityInvite.ts index 45485310..16072473 100644 --- a/src/util/migrations/1633881705509-VanityInvite.ts +++ b/src/util/migrations/1633881705509-VanityInvite.ts @@ -5,15 +5,21 @@ export class VanityInvite1633881705509 implements MigrationInterface { 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`); + 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` + `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/src/util/migrations/1634308884591-Stickers.ts b/src/util/migrations/1634308884591-Stickers.ts index fbc4649f..f7b83242 100644 --- a/src/util/migrations/1634308884591-Stickers.ts +++ b/src/util/migrations/1634308884591-Stickers.ts @@ -1,35 +1,64 @@ -import { MigrationInterface, QueryRunner, Table, TableColumn, TableForeignKey } from "typeorm"; +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.dropForeignKey( + "read_states", + "FK_6f255d873cfbfd7a93849b7ff74", + ); await queryRunner.changeColumn( "stickers", "tags", - new TableColumn({ name: "tags", type: "varchar", isNullable: true }) + new TableColumn({ + name: "tags", + type: "varchar", + isNullable: true, + }), ); await queryRunner.changeColumn( "stickers", "pack_id", - new TableColumn({ name: "pack_id", type: "varchar", isNullable: true }) + new TableColumn({ + name: "pack_id", + type: "varchar", + isNullable: true, + }), + ); + await queryRunner.changeColumn( + "stickers", + "type", + new TableColumn({ name: "type", type: "integer" }), ); - await queryRunner.changeColumn("stickers", "type", new TableColumn({ name: "type", type: "integer" })); await queryRunner.changeColumn( "stickers", "format_type", - new TableColumn({ name: "format_type", type: "integer" }) + new TableColumn({ name: "format_type", type: "integer" }), ); await queryRunner.changeColumn( "stickers", "available", - new TableColumn({ name: "available", type: "boolean", isNullable: true }) + new TableColumn({ + name: "available", + type: "boolean", + isNullable: true, + }), ); await queryRunner.changeColumn( "stickers", "user_id", - new TableColumn({ name: "user_id", type: "boolean", isNullable: true }) + new TableColumn({ + name: "user_id", + type: "boolean", + isNullable: true, + }), ); await queryRunner.createForeignKey( "stickers", @@ -39,17 +68,33 @@ export class Stickers1634308884591 implements MigrationInterface { 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: "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 }), + 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({ @@ -58,7 +103,7 @@ export class Stickers1634308884591 implements MigrationInterface { referencedTableName: "stickers", }), ], - }) + }), ); } diff --git a/src/util/migrations/1634424361103-Presence.ts b/src/util/migrations/1634424361103-Presence.ts index 729955b8..a71cb253 100644 --- a/src/util/migrations/1634424361103-Presence.ts +++ b/src/util/migrations/1634424361103-Presence.ts @@ -4,7 +4,10 @@ export class Presence1634424361103 implements MigrationInterface { name = "Presence1634424361103"; public async up(queryRunner: QueryRunner): Promise<void> { - queryRunner.addColumn("sessions", new TableColumn({ name: "activites", type: "text" })); + queryRunner.addColumn( + "sessions", + new TableColumn({ name: "activites", type: "text" }), + ); } public async down(queryRunner: QueryRunner): Promise<void> {} diff --git a/src/util/migrations/1634426540271-MigrationTimestamp.ts b/src/util/migrations/1634426540271-MigrationTimestamp.ts index 3208b25b..fb596906 100644 --- a/src/util/migrations/1634426540271-MigrationTimestamp.ts +++ b/src/util/migrations/1634426540271-MigrationTimestamp.ts @@ -7,7 +7,11 @@ export class MigrationTimestamp1634426540271 implements MigrationInterface { await queryRunner.changeColumn( "migrations", "timestamp", - new TableColumn({ name: "timestampe", type: "bigint", isNullable: false }) + new TableColumn({ + name: "timestampe", + type: "bigint", + isNullable: false, + }), ); } diff --git a/src/util/migrations/1660678870706-opencordFixes.ts b/src/util/migrations/1660678870706-opencordFixes.ts index 1f10c212..53c561ce 100644 --- a/src/util/migrations/1660678870706-opencordFixes.ts +++ b/src/util/migrations/1660678870706-opencordFixes.ts @@ -1,53 +1,52 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class opencordFixes1660678870706 implements MigrationInterface { - name = 'opencordFixes1660678870706' + name = "opencordFixes1660678870706"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query(` + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` ALTER TABLE \`users\` ADD \`purchased_flags\` int NOT NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`users\` ADD \`premium_usage_flags\` int NOT NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`user_settings\` ADD \`friend_discovery_flags\` int NOT NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`user_settings\` ADD \`view_nsfw_guilds\` tinyint NOT NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`user_settings\` ADD \`passwordless\` tinyint NOT NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`users\` CHANGE \`mfa_enabled\` \`mfa_enabled\` tinyint NOT NULL `); - } + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query(` + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` ALTER TABLE \`users\` CHANGE \`mfa_enabled\` \`mfa_enabled\` tinyint NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`user_settings\` DROP COLUMN \`passwordless\` `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`user_settings\` DROP COLUMN \`view_nsfw_guilds\` `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`user_settings\` DROP COLUMN \`friend_discovery_flags\` `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`users\` DROP COLUMN \`premium_usage_flags\` `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`users\` DROP COLUMN \`purchased_flags\` `); - } - -} \ No newline at end of file + } +} diff --git a/src/util/migrations/1660689892073-mobileFixes2.ts b/src/util/migrations/1660689892073-mobileFixes2.ts index bd28694e..63e7e032 100644 --- a/src/util/migrations/1660689892073-mobileFixes2.ts +++ b/src/util/migrations/1660689892073-mobileFixes2.ts @@ -1,37 +1,36 @@ import { MigrationInterface, QueryRunner } from "typeorm"; export class mobileFixes21660689892073 implements MigrationInterface { - name = 'mobileFixes21660689892073' + name = "mobileFixes21660689892073"; - public async up(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query(` + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` ALTER TABLE \`user_settings\` ADD \`banner_color\` varchar(255) NULL `); await queryRunner.query(` UPDATE \`channels\` SET \`nsfw\` = 0 WHERE \`nsfw\` = NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`channels\` CHANGE \`nsfw\` \`nsfw\` tinyint NOT NULL `); await queryRunner.query(` UPDATE \`guilds\` SET \`nsfw\` = 0 WHERE \`nsfw\` = NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`guilds\` CHANGE \`nsfw\` \`nsfw\` tinyint NOT NULL `); - } + } - public async down(queryRunner: QueryRunner): Promise<void> { - await queryRunner.query(` + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` ALTER TABLE \`guilds\` CHANGE \`nsfw\` \`nsfw\` tinyint NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`channels\` CHANGE \`nsfw\` \`nsfw\` tinyint NULL `); - await queryRunner.query(` + await queryRunner.query(` ALTER TABLE \`user_settings\` DROP COLUMN \`banner_color\` `); - } - -} \ No newline at end of file + } +} diff --git a/src/util/schemas/ActivitySchema.ts b/src/util/schemas/ActivitySchema.ts index d316420e..5a3d205b 100644 --- a/src/util/schemas/ActivitySchema.ts +++ b/src/util/schemas/ActivitySchema.ts @@ -41,7 +41,8 @@ export const ActivitySchema = { $id: String, $sync_id: String, - $metadata: { // spotify + $metadata: { + // spotify $context_uri: String, album_id: String, artist_ids: [String], @@ -57,4 +58,4 @@ export interface ActivitySchema { status: Status; activities?: Activity[]; since?: number; // unix time (in milliseconds) of when the client went idle, or null if the client is not idle -} \ No newline at end of file +} diff --git a/src/util/schemas/BackupCodesChallengeSchema.ts b/src/util/schemas/BackupCodesChallengeSchema.ts index d6b519b7..8e2f0649 100644 --- a/src/util/schemas/BackupCodesChallengeSchema.ts +++ b/src/util/schemas/BackupCodesChallengeSchema.ts @@ -1,3 +1,3 @@ export interface BackupCodesChallengeSchema { password: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/BanCreateSchema.ts b/src/util/schemas/BanCreateSchema.ts index 876b2a89..834577dc 100644 --- a/src/util/schemas/BanCreateSchema.ts +++ b/src/util/schemas/BanCreateSchema.ts @@ -1,4 +1,4 @@ export interface BanCreateSchema { delete_message_days?: string; reason?: string; -}; \ No newline at end of file +} diff --git a/src/util/schemas/BanModeratorSchema.ts b/src/util/schemas/BanModeratorSchema.ts index 8efa2402..afb76433 100644 --- a/src/util/schemas/BanModeratorSchema.ts +++ b/src/util/schemas/BanModeratorSchema.ts @@ -4,4 +4,4 @@ export interface BanModeratorSchema { guild_id: string; executor_id: string; reason?: string | undefined; -}; \ No newline at end of file +} diff --git a/src/util/schemas/BanRegistrySchema.ts b/src/util/schemas/BanRegistrySchema.ts index 8680d3db..501f94dc 100644 --- a/src/util/schemas/BanRegistrySchema.ts +++ b/src/util/schemas/BanRegistrySchema.ts @@ -5,4 +5,4 @@ export interface BanRegistrySchema { executor_id: string; ip?: string; reason?: string | undefined; -}; \ No newline at end of file +} diff --git a/src/util/schemas/BulkDeleteSchema.ts b/src/util/schemas/BulkDeleteSchema.ts index 6a71e052..bfc4df65 100644 --- a/src/util/schemas/BulkDeleteSchema.ts +++ b/src/util/schemas/BulkDeleteSchema.ts @@ -1,3 +1,3 @@ export interface BulkDeleteSchema { messages: string[]; -} \ No newline at end of file +} diff --git a/src/util/schemas/ChannelModifySchema.ts b/src/util/schemas/ChannelModifySchema.ts index 835ea2d7..9a07f983 100644 --- a/src/util/schemas/ChannelModifySchema.ts +++ b/src/util/schemas/ChannelModifySchema.ts @@ -27,4 +27,4 @@ export interface ChannelModifySchema { flags?: number; default_thread_rate_limit_per_user?: number; video_quality_mode?: number; -} \ No newline at end of file +} diff --git a/src/util/schemas/CodesVerificationSchema.ts b/src/util/schemas/CodesVerificationSchema.ts index e8e2e7b4..73c371eb 100644 --- a/src/util/schemas/CodesVerificationSchema.ts +++ b/src/util/schemas/CodesVerificationSchema.ts @@ -2,4 +2,4 @@ export interface CodesVerificationSchema { key: string; nonce: string; regenerate?: boolean; -} \ No newline at end of file +} diff --git a/src/util/schemas/DmChannelCreateSchema.ts b/src/util/schemas/DmChannelCreateSchema.ts index 04b8ff69..1b0fe86d 100644 --- a/src/util/schemas/DmChannelCreateSchema.ts +++ b/src/util/schemas/DmChannelCreateSchema.ts @@ -1,4 +1,4 @@ export interface DmChannelCreateSchema { name?: string; recipients: string[]; -} \ No newline at end of file +} diff --git a/src/util/schemas/EmojiCreateSchema.ts b/src/util/schemas/EmojiCreateSchema.ts index 8e2a2307..34084713 100644 --- a/src/util/schemas/EmojiCreateSchema.ts +++ b/src/util/schemas/EmojiCreateSchema.ts @@ -3,4 +3,4 @@ export interface EmojiCreateSchema { image: string; require_colons?: boolean | null; roles?: string[]; -} \ No newline at end of file +} diff --git a/src/util/schemas/EmojiModifySchema.ts b/src/util/schemas/EmojiModifySchema.ts index cd5b7e3e..05d2d395 100644 --- a/src/util/schemas/EmojiModifySchema.ts +++ b/src/util/schemas/EmojiModifySchema.ts @@ -1,4 +1,4 @@ export interface EmojiModifySchema { name?: string; roles?: string[]; -} \ No newline at end of file +} diff --git a/src/util/schemas/GuildCreateSchema.ts b/src/util/schemas/GuildCreateSchema.ts index 9b5f7dc2..f3de7007 100644 --- a/src/util/schemas/GuildCreateSchema.ts +++ b/src/util/schemas/GuildCreateSchema.ts @@ -11,4 +11,4 @@ export interface GuildCreateSchema { guild_template_code?: string; system_channel_id?: string; rules_channel_id?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/GuildTemplateCreateSchema.ts b/src/util/schemas/GuildTemplateCreateSchema.ts index 7caefcb8..59db8428 100644 --- a/src/util/schemas/GuildTemplateCreateSchema.ts +++ b/src/util/schemas/GuildTemplateCreateSchema.ts @@ -1,4 +1,4 @@ export interface GuildTemplateCreateSchema { name: string; avatar?: string | null; -} \ No newline at end of file +} diff --git a/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts b/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts index 0022da6e..e271b83e 100644 --- a/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts +++ b/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts @@ -7,4 +7,4 @@ export interface GuildUpdateWelcomeScreenSchema { }[]; enabled?: boolean; description?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/InviteCreateSchema.ts b/src/util/schemas/InviteCreateSchema.ts index 83ae22dd..cac11147 100644 --- a/src/util/schemas/InviteCreateSchema.ts +++ b/src/util/schemas/InviteCreateSchema.ts @@ -8,4 +8,4 @@ export interface InviteCreateSchema { unique?: boolean; target_user?: string; target_user_type?: number; -} \ No newline at end of file +} diff --git a/src/util/schemas/LoginSchema.ts b/src/util/schemas/LoginSchema.ts index 543d236c..dc889d94 100644 --- a/src/util/schemas/LoginSchema.ts +++ b/src/util/schemas/LoginSchema.ts @@ -5,4 +5,4 @@ export interface LoginSchema { captcha_key?: string; login_source?: string; gift_code_sku_id?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/MemberChangeSchema.ts b/src/util/schemas/MemberChangeSchema.ts index 566d7e20..2367bef3 100644 --- a/src/util/schemas/MemberChangeSchema.ts +++ b/src/util/schemas/MemberChangeSchema.ts @@ -1,4 +1,4 @@ export interface MemberChangeSchema { roles?: string[]; nick?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/MemberNickChangeSchema.ts b/src/util/schemas/MemberNickChangeSchema.ts index ed9fdb7b..d863038c 100644 --- a/src/util/schemas/MemberNickChangeSchema.ts +++ b/src/util/schemas/MemberNickChangeSchema.ts @@ -1,3 +1,3 @@ export interface MemberNickChangeSchema { nick: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/MessageAcknowledgeSchema.ts b/src/util/schemas/MessageAcknowledgeSchema.ts index 1e7fb80d..194bb4b4 100644 --- a/src/util/schemas/MessageAcknowledgeSchema.ts +++ b/src/util/schemas/MessageAcknowledgeSchema.ts @@ -1,4 +1,4 @@ export interface MessageAcknowledgeSchema { manual?: boolean; mention_count?: number; -} \ No newline at end of file +} diff --git a/src/util/schemas/MessageCreateSchema.ts b/src/util/schemas/MessageCreateSchema.ts index 9d77c485..bf3470bb 100644 --- a/src/util/schemas/MessageCreateSchema.ts +++ b/src/util/schemas/MessageCreateSchema.ts @@ -30,4 +30,4 @@ export interface MessageCreateSchema { **/ attachments?: any[]; sticker_ids?: string[]; -} \ No newline at end of file +} diff --git a/src/util/schemas/MfaCodesSchema.ts b/src/util/schemas/MfaCodesSchema.ts index 226c43f1..ac05b9a4 100644 --- a/src/util/schemas/MfaCodesSchema.ts +++ b/src/util/schemas/MfaCodesSchema.ts @@ -1,4 +1,4 @@ export interface MfaCodesSchema { password: string; regenerate?: boolean; -} \ No newline at end of file +} diff --git a/src/util/schemas/ModifyGuildStickerSchema.ts b/src/util/schemas/ModifyGuildStickerSchema.ts index 06bf4ffe..159cc44f 100644 --- a/src/util/schemas/ModifyGuildStickerSchema.ts +++ b/src/util/schemas/ModifyGuildStickerSchema.ts @@ -12,4 +12,4 @@ export interface ModifyGuildStickerSchema { * @maxLength 200 */ tags: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/PruneSchema.ts b/src/util/schemas/PruneSchema.ts index 60601d81..bea5e2b4 100644 --- a/src/util/schemas/PruneSchema.ts +++ b/src/util/schemas/PruneSchema.ts @@ -3,4 +3,4 @@ export interface PruneSchema { * @min 0 */ days: number; -} \ No newline at end of file +} diff --git a/src/util/schemas/PurgeSchema.ts b/src/util/schemas/PurgeSchema.ts index 8916be92..f5ab0a20 100644 --- a/src/util/schemas/PurgeSchema.ts +++ b/src/util/schemas/PurgeSchema.ts @@ -1,4 +1,4 @@ export interface PurgeSchema { before: string; after: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/RegisterSchema.ts b/src/util/schemas/RegisterSchema.ts index c0cc3805..865f55b3 100644 --- a/src/util/schemas/RegisterSchema.ts +++ b/src/util/schemas/RegisterSchema.ts @@ -24,4 +24,4 @@ export interface RegisterSchema { captcha_key?: string; promotional_email_opt_in?: boolean; -} \ No newline at end of file +} diff --git a/src/util/schemas/RelationshipPostSchema.ts b/src/util/schemas/RelationshipPostSchema.ts index 3ff6eade..774c67f6 100644 --- a/src/util/schemas/RelationshipPostSchema.ts +++ b/src/util/schemas/RelationshipPostSchema.ts @@ -1,4 +1,4 @@ export interface RelationshipPostSchema { discriminator: string; username: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/RelationshipPutSchema.ts b/src/util/schemas/RelationshipPutSchema.ts index 455f854e..0a7f9720 100644 --- a/src/util/schemas/RelationshipPutSchema.ts +++ b/src/util/schemas/RelationshipPutSchema.ts @@ -2,4 +2,4 @@ import { RelationshipType } from "@fosscord/util"; export interface RelationshipPutSchema { type?: RelationshipType; -} \ No newline at end of file +} diff --git a/src/util/schemas/RoleModifySchema.ts b/src/util/schemas/RoleModifySchema.ts index adb0c1a6..f3f4a20e 100644 --- a/src/util/schemas/RoleModifySchema.ts +++ b/src/util/schemas/RoleModifySchema.ts @@ -7,4 +7,4 @@ export interface RoleModifySchema { position?: number; icon?: string; unicode_emoji?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/SelectProtocolSchema.ts b/src/util/schemas/SelectProtocolSchema.ts index 92958e97..0ba0c23b 100644 --- a/src/util/schemas/SelectProtocolSchema.ts +++ b/src/util/schemas/SelectProtocolSchema.ts @@ -1,12 +1,12 @@ export interface SelectProtocolSchema { protocol: "webrtc" | "udp"; data: - | string - | { - address: string; - port: number; - mode: string; - }; + | string + | { + address: string; + port: number; + mode: string; + }; sdp?: string; codecs?: { name: "opus" | "VP8" | "VP9" | "H264"; @@ -16,4 +16,4 @@ export interface SelectProtocolSchema { rtx_payload_type?: number | null; }[]; rtc_connection_id?: string; // uuid -} \ No newline at end of file +} diff --git a/src/util/schemas/TemplateCreateSchema.ts b/src/util/schemas/TemplateCreateSchema.ts index 3f98f692..160934f5 100644 --- a/src/util/schemas/TemplateCreateSchema.ts +++ b/src/util/schemas/TemplateCreateSchema.ts @@ -1,4 +1,4 @@ export interface TemplateCreateSchema { name: string; description?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/TemplateModifySchema.ts b/src/util/schemas/TemplateModifySchema.ts index 3e6efb74..f9c9d14b 100644 --- a/src/util/schemas/TemplateModifySchema.ts +++ b/src/util/schemas/TemplateModifySchema.ts @@ -1,4 +1,4 @@ export interface TemplateModifySchema { name: string; description?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/TotpDisableSchema.ts b/src/util/schemas/TotpDisableSchema.ts index 05192bfa..51446e1c 100644 --- a/src/util/schemas/TotpDisableSchema.ts +++ b/src/util/schemas/TotpDisableSchema.ts @@ -1,3 +1,3 @@ export interface TotpDisableSchema { code: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/TotpEnableSchema.ts b/src/util/schemas/TotpEnableSchema.ts index 7f6fb5a9..4e3551d9 100644 --- a/src/util/schemas/TotpEnableSchema.ts +++ b/src/util/schemas/TotpEnableSchema.ts @@ -2,4 +2,4 @@ export interface TotpEnableSchema { password: string; code?: string; secret?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/TotpSchema.ts b/src/util/schemas/TotpSchema.ts index 889cb443..941a92ec 100644 --- a/src/util/schemas/TotpSchema.ts +++ b/src/util/schemas/TotpSchema.ts @@ -1,6 +1,6 @@ export interface TotpSchema { - code: string, - ticket: string, - gift_code_sku_id?: string | null, - login_source?: string | null, -} \ No newline at end of file + code: string; + ticket: string; + gift_code_sku_id?: string | null; + login_source?: string | null; +} diff --git a/src/util/schemas/UserModifySchema.ts b/src/util/schemas/UserModifySchema.ts index 34e0f135..5327e34b 100644 --- a/src/util/schemas/UserModifySchema.ts +++ b/src/util/schemas/UserModifySchema.ts @@ -16,4 +16,4 @@ export interface UserModifySchema { code?: string; email?: string; discriminator?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/Validator.ts b/src/util/schemas/Validator.ts index b71bf6a1..e85cdf7b 100644 --- a/src/util/schemas/Validator.ts +++ b/src/util/schemas/Validator.ts @@ -3,7 +3,14 @@ import addFormats from "ajv-formats"; import fs from "fs"; import path from "path"; -const SchemaPath = path.join(__dirname, "..", "..", "..", "assets", "schemas.json"); +const SchemaPath = path.join( + __dirname, + "..", + "..", + "..", + "assets", + "schemas.json", +); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); export const ajv = new Ajv({ @@ -14,7 +21,7 @@ export const ajv = new Ajv({ coerceTypes: true, messages: true, strict: true, - strictRequired: true + strictRequired: true, }); addFormats(ajv); @@ -41,7 +48,14 @@ export const normalizeBody = (body: any = {}) => { } else { for (const [key, value] of Object.entries(object)) { if (value == null) { - if (key === "icon" || key === "avatar" || key === "banner" || key === "splash" || key === "discovery_splash") continue; + if ( + key === "icon" || + key === "avatar" || + key === "banner" || + key === "splash" || + key === "discovery_splash" + ) + continue; delete object[key]; } else if (typeof value === "object") { normalizeObject(value); @@ -51,4 +65,4 @@ export const normalizeBody = (body: any = {}) => { }; normalizeObject(body); return body; -}; \ No newline at end of file +}; diff --git a/src/util/schemas/VanityUrlSchema.ts b/src/util/schemas/VanityUrlSchema.ts index 28bf7f2b..4dd9b9da 100644 --- a/src/util/schemas/VanityUrlSchema.ts +++ b/src/util/schemas/VanityUrlSchema.ts @@ -4,4 +4,4 @@ export interface VanityUrlSchema { * @maxLength 20 */ code?: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/VoiceIdentifySchema.ts b/src/util/schemas/VoiceIdentifySchema.ts index d48de347..df023713 100644 --- a/src/util/schemas/VoiceIdentifySchema.ts +++ b/src/util/schemas/VoiceIdentifySchema.ts @@ -9,4 +9,4 @@ export interface VoiceIdentifySchema { rid: string; quality: number; }[]; -} \ No newline at end of file +} diff --git a/src/util/schemas/VoiceStateUpdateSchema.ts b/src/util/schemas/VoiceStateUpdateSchema.ts index 5f805f4d..79e93b07 100644 --- a/src/util/schemas/VoiceStateUpdateSchema.ts +++ b/src/util/schemas/VoiceStateUpdateSchema.ts @@ -15,8 +15,8 @@ export const VoiceStateUpdateSchema = { $channel_id: String, self_mute: Boolean, self_deaf: Boolean, - $self_video: Boolean, //required in docs but bots don't always send it + $self_video: Boolean, //required in docs but bots don't always send it $preferred_region: String, $request_to_speak_timestamp: Date, $suppress: Boolean, -}; \ No newline at end of file +}; diff --git a/src/util/schemas/VoiceVideoSchema.ts b/src/util/schemas/VoiceVideoSchema.ts index 837ee1e7..0ba519e1 100644 --- a/src/util/schemas/VoiceVideoSchema.ts +++ b/src/util/schemas/VoiceVideoSchema.ts @@ -12,6 +12,6 @@ export interface VoiceVideoSchema { rtx_ssrc: number; max_bitrate: number; max_framerate: number; - max_resolution: { type: string; width: number; height: number; }; + max_resolution: { type: string; width: number; height: number }; }[]; -} \ No newline at end of file +} diff --git a/src/util/schemas/WebhookCreateSchema.ts b/src/util/schemas/WebhookCreateSchema.ts index c32b642d..99f6a12f 100644 --- a/src/util/schemas/WebhookCreateSchema.ts +++ b/src/util/schemas/WebhookCreateSchema.ts @@ -5,4 +5,4 @@ export interface WebhookCreateSchema { */ name: string; avatar: string; -} \ No newline at end of file +} diff --git a/src/util/schemas/WidgetModifySchema.ts b/src/util/schemas/WidgetModifySchema.ts index 3c84b3a1..26d4504f 100644 --- a/src/util/schemas/WidgetModifySchema.ts +++ b/src/util/schemas/WidgetModifySchema.ts @@ -1,4 +1,4 @@ export interface WidgetModifySchema { enabled: boolean; // whether the widget is enabled channel_id: string; // the widget channel id -} \ No newline at end of file +} diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts index f86552f3..ae80de71 100644 --- a/src/util/schemas/index.ts +++ b/src/util/schemas/index.ts @@ -38,4 +38,4 @@ export * from "./VoiceStateUpdateSchema"; export * from "./VoiceVideoSchema"; export * from "./IdentifySchema"; export * from "./ActivitySchema"; -export * from "./LazyRequestSchema"; \ No newline at end of file +export * from "./LazyRequestSchema"; diff --git a/src/util/util/ApiError.ts b/src/util/util/ApiError.ts index f1a9b4f6..0fce6882 100644 --- a/src/util/util/ApiError.ts +++ b/src/util/util/ApiError.ts @@ -3,23 +3,34 @@ export class ApiError extends Error { readonly message: string, public readonly code: number, public readonly httpStatus: number = 400, - public readonly defaultParams?: string[] + public readonly defaultParams?: string[], ) { super(message); } withDefaultParams(): ApiError { if (this.defaultParams) - return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus); + return new ApiError( + applyParamsToString(this.message, this.defaultParams), + this.code, + this.httpStatus, + ); return this; } withParams(...params: (string | number)[]): ApiError { - return new ApiError(applyParamsToString(this.message, params), this.code, this.httpStatus); + return new ApiError( + applyParamsToString(this.message, params), + this.code, + this.httpStatus, + ); } } -export function applyParamsToString(s: string, params: (string | number)[]): string { +export function applyParamsToString( + s: string, + params: (string | number)[], +): string { let newString = s; params.forEach((a) => { newString = newString.replace("{}", "" + a); diff --git a/src/util/util/AutoUpdate.ts b/src/util/util/AutoUpdate.ts index fd65ecf5..769d959f 100644 --- a/src/util/util/AutoUpdate.ts +++ b/src/util/util/AutoUpdate.ts @@ -1,6 +1,6 @@ import "missing-native-js-functions"; import fetch from "node-fetch"; -import ProxyAgent from 'proxy-agent'; +import ProxyAgent from "proxy-agent"; import readline from "readline"; import fs from "fs/promises"; import path from "path"; @@ -19,14 +19,17 @@ export function enableAutoUpdate(opts: { }) { if (!opts.checkInterval) return; var interval = 1000 * 60 * 60 * 24; - if (typeof opts.checkInterval === "number") opts.checkInterval = 1000 * interval; + if (typeof opts.checkInterval === "number") + opts.checkInterval = 1000 * interval; const i = setInterval(async () => { const currentVersion = await getCurrentVersion(opts.path); const latestVersion = await getLatestVersion(opts.packageJsonLink); if (currentVersion !== latestVersion) { clearInterval(i); - console.log(`[Auto Update] Current version (${currentVersion}) is out of date, updating ...`); + console.log( + `[Auto Update] Current version (${currentVersion}) is out of date, updating ...`, + ); await download(opts.downloadUrl, opts.path); } }, interval); @@ -43,7 +46,7 @@ export function enableAutoUpdate(opts: { } else { console.log(`[Auto update] aborted`); } - } + }, ); } }); @@ -65,7 +68,9 @@ async function download(url: string, dir: string) { async function getCurrentVersion(dir: string) { try { - const content = await fs.readFile(path.join(dir, "package.json"), { encoding: "utf8" }); + const content = await fs.readFile(path.join(dir, "package.json"), { + encoding: "utf8", + }); return JSON.parse(content).version; } catch (error) { throw new Error("[Auto update] couldn't get current version in " + dir); @@ -76,7 +81,7 @@ async function getLatestVersion(url: string) { try { const agent = new ProxyAgent(); const response = await fetch(url, { agent }); - const content = await response.json() as any; // TODO: types + const content = (await response.json()) as any; // TODO: types return content.version; } catch (error) { throw new Error("[Auto update] check failed for " + url); diff --git a/src/util/util/BannedWords.ts b/src/util/util/BannedWords.ts index 891a5980..74a82efe 100644 --- a/src/util/util/BannedWords.ts +++ b/src/util/util/BannedWords.ts @@ -6,7 +6,9 @@ var words: string[]; export const BannedWords = { init: async function init() { if (words) return words; - const file = (await fs.readFile(path.join(process.cwd(), "bannedWords"))).toString(); + const file = ( + await fs.readFile(path.join(process.cwd(), "bannedWords")) + ).toString(); if (!file) { words = []; return []; @@ -18,6 +20,6 @@ export const BannedWords = { get: () => words, find: (val: string) => { - return words.some(x => val.indexOf(x) != -1); - } -}; \ No newline at end of file + return words.some((x) => val.indexOf(x) != -1); + }, +}; diff --git a/src/util/util/BitField.ts b/src/util/util/BitField.ts index fb887e05..4e606660 100644 --- a/src/util/util/BitField.ts +++ b/src/util/util/BitField.ts @@ -3,7 +3,12 @@ // https://github.com/discordjs/discord.js/blob/master/src/util/BitField.js // Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah -export type BitFieldResolvable = number | BigInt | BitField | string | BitFieldResolvable[]; +export type BitFieldResolvable = + | number + | BigInt + | BitField + | string + | BitFieldResolvable[]; /** * Data structure that makes it easy to interact with a bitfield. @@ -91,7 +96,8 @@ export class BitField { */ serialize() { const serialized: Record<string, boolean> = {}; - for (const [flag, bit] of Object.entries(BitField.FLAGS)) serialized[flag] = this.has(bit); + for (const [flag, bit] of Object.entries(BitField.FLAGS)) + serialized[flag] = this.has(bit); return serialized; } @@ -130,14 +136,21 @@ export class BitField { static resolve(bit: BitFieldResolvable = BigInt(0)): bigint { // @ts-ignore const FLAGS = this.FLAGS || this.constructor?.FLAGS; - if ((typeof bit === "number" || typeof bit === "bigint") && bit >= BigInt(0)) return BigInt(bit); + if ( + (typeof bit === "number" || typeof bit === "bigint") && + bit >= BigInt(0) + ) + return BigInt(bit); if (bit instanceof BitField) return bit.bitfield; if (Array.isArray(bit)) { // @ts-ignore const resolve = this.constructor?.resolve || this.resolve; - return bit.map((p) => resolve.call(this, p)).reduce((prev, p) => BigInt(prev) | BigInt(p), BigInt(0)); + 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 (typeof bit === "string" && typeof FLAGS[bit] !== "undefined") + return FLAGS[bit]; throw new RangeError("BITFIELD_INVALID: " + bit); } } diff --git a/src/util/util/Categories.ts b/src/util/util/Categories.ts index a3c69da7..cd706a8a 100644 --- a/src/util/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/src/util/util/Config.ts b/src/util/util/Config.ts index 31b8d97c..7fab7f90 100644 --- a/src/util/util/Config.ts +++ b/src/util/util/Config.ts @@ -1,5 +1,9 @@ import "missing-native-js-functions"; -import { ConfigValue, ConfigEntity, DefaultConfigOptions } from "../entities/Config"; +import { + ConfigValue, + ConfigEntity, + DefaultConfigOptions, +} from "../entities/Config"; import path from "path"; import fs from "fs"; @@ -42,7 +46,11 @@ export const 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))); + 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(); @@ -67,7 +75,8 @@ function pairsToConfig(pairs: ConfigEntity[]) { let i = 0; for (const key of keys) { - if (!isNaN(Number(key)) && !prevObj[prev]?.length) prevObj[prev] = obj = []; + 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] = {}; diff --git a/src/util/util/Constants.ts b/src/util/util/Constants.ts index 81a7165d..7c5b7dcb 100644 --- a/src/util/util/Constants.ts +++ b/src/util/util/Constants.ts @@ -77,9 +77,9 @@ export const VoiceOPCodes = { RESUME: 7, HELLO: 8, RESUMED: 9, - CLIENT_CONNECT: 12, // incorrect, op 12 is probably used for video - CLIENT_DISCONNECT: 13, // incorrect - VERSION: 16, //not documented + CLIENT_CONNECT: 12, // incorrect, op 12 is probably used for video + CLIENT_DISCONNECT: 13, // incorrect + VERSION: 16, //not documented }; export const Events = { @@ -160,7 +160,13 @@ export const ShardEvents = { * sidebar for more information.</warn> * @typedef {string} PartialType */ -export const PartialTypes = keyMirror(["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"]); +export const PartialTypes = keyMirror([ + "USER", + "CHANNEL", + "GUILD_MEMBER", + "MESSAGE", + "REACTION", +]); /** * The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events: @@ -291,7 +297,7 @@ export const MessageTypes = [ * @typedef {string} SystemMessageType */ export const SystemMessageTypes = MessageTypes.filter( - (type: string | null) => type && type !== "DEFAULT" && type !== "REPLY" + (type: string | null) => type && type !== "DEFAULT" && type !== "REPLY", ); /** @@ -305,7 +311,14 @@ export const SystemMessageTypes = MessageTypes.filter( * * COMPETING * @typedef {string} ActivityType */ -export const ActivityTypes = ["PLAYING", "STREAMING", "LISTENING", "WATCHING", "CUSTOM_STATUS", "COMPETING"]; +export const ActivityTypes = [ + "PLAYING", + "STREAMING", + "LISTENING", + "WATCHING", + "CUSTOM_STATUS", + "COMPETING", +]; export const ChannelTypes = { TEXT: 0, @@ -361,7 +374,11 @@ export const Colors = { * * ALL_MEMBERS * @typedef {string} ExplicitContentFilterLevel */ -export const ExplicitContentFilterLevels = ["DISABLED", "MEMBERS_WITHOUT_ROLES", "ALL_MEMBERS"]; +export const ExplicitContentFilterLevels = [ + "DISABLED", + "MEMBERS_WITHOUT_ROLES", + "ALL_MEMBERS", +]; /** * The value set for the verification levels for a guild: @@ -372,7 +389,13 @@ export const ExplicitContentFilterLevels = ["DISABLED", "MEMBERS_WITHOUT_ROLES", * * VERY_HIGH * @typedef {string} VerificationLevel */ -export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"]; +export const VerificationLevels = [ + "NONE", + "LOW", + "MEDIUM", + "HIGH", + "VERY_HIGH", +]; /** * An error encountered while performing an API request. Here are the potential errors: @@ -517,7 +540,10 @@ export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"] */ export const DiscordApiErrors = { //https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes - GENERAL_ERROR: new ApiError("General error (such as a malformed request body, amongst other things)", 0), + GENERAL_ERROR: new ApiError( + "General error (such as a malformed request body, amongst other things)", + 0, + ), UNKNOWN_ACCOUNT: new ApiError("Unknown account", 10001), UNKNOWN_APPLICATION: new ApiError("Unknown application", 10002), UNKNOWN_CHANNEL: new ApiError("Unknown channel", 10003), @@ -542,185 +568,410 @@ export const DiscordApiErrors = { UNKNOWN_BUILD: new ApiError("Unknown build", 10030), UNKNOWN_LOBBY: new ApiError("Unknown lobby", 10031), UNKNOWN_BRANCH: new ApiError("Unknown branch", 10032), - UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError("Unknown store directory layout", 10033), + UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError( + "Unknown store directory layout", + 10033, + ), UNKNOWN_REDISTRIBUTABLE: new ApiError("Unknown redistributable", 10036), UNKNOWN_GIFT_CODE: new ApiError("Unknown gift code", 10038), UNKNOWN_STREAM: new ApiError("Unknown stream", 10049), - UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError("Unknown premium server subscribe cooldown", 10050), + UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError( + "Unknown premium server subscribe cooldown", + 10050, + ), UNKNOWN_GUILD_TEMPLATE: new ApiError("Unknown guild template", 10057), - UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError("Unknown discoverable server category", 10059), + UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError( + "Unknown discoverable server category", + 10059, + ), UNKNOWN_STICKER: new ApiError("Unknown sticker", 10060), UNKNOWN_INTERACTION: new ApiError("Unknown interaction", 10062), - UNKNOWN_APPLICATION_COMMAND: new ApiError("Unknown application command", 10063), - UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError("Unknown application command permissions", 10066), + UNKNOWN_APPLICATION_COMMAND: new ApiError( + "Unknown application command", + 10063, + ), + UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError( + "Unknown application command permissions", + 10066, + ), UNKNOWN_STAGE_INSTANCE: new ApiError("Unknown Stage Instance", 10067), - UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError("Unknown Guild Member Verification Form", 10068), - UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError("Unknown Guild Welcome Screen", 10069), - UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError("Unknown Guild Scheduled Event", 10070), - UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError("Unknown Guild Scheduled Event User", 10071), - BOT_PROHIBITED_ENDPOINT: new ApiError("Bots cannot use this endpoint", 20001), + UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError( + "Unknown Guild Member Verification Form", + 10068, + ), + UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError( + "Unknown Guild Welcome Screen", + 10069, + ), + UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError( + "Unknown Guild Scheduled Event", + 10070, + ), + 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 + 20009, ), ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError( "You are not authorized to perform this action on this application", - 20012 + 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, + ), + CHANNEL_WRITE_RATELIMIT: new ApiError( + "The channel you are writing has hit the write rate limit", + 20028, ), - 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), - CHANNEL_WRITE_RATELIMIT: new ApiError("The channel you are writing has hit the write rate limit", 20028), WORDS_NOT_ALLOWED: new ApiError( "Your Stage topic, server name, server description, or channel names contain words that are not allowed", - 20031 - ), - GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError("Guild premium subscription level too low", 20035), - 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_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_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), + 20031, + ), + GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError( + "Guild premium subscription level too low", + 20035, + ), + 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_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_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"] + ["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, ), - 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 + 30035, + ), + MAXIMUM_BANS_FETCHES: new ApiError( + "Maximum number of bans fetches has been reached", + 30037, ), - 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), + 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 + 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, ), - 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), USER_BANNED: new ApiError("The user is banned from this guild", 40007), - TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError("Target user is not connected to voice", 40032), - ALREADY_CROSSPOSTED: new ApiError("This message has already been crossposted", 40033), - APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError("An application command with that name already exists", 40041), + TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError( + "Target user is not connected to voice", + 40032, + ), + ALREADY_CROSSPOSTED: new ApiError( + "This message has already been crossposted", + 40033, + ), + APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError( + "An application command with that name already exists", + 40041, + ), MISSING_ACCESS: new ApiError("Missing access", 50001), INVALID_ACCOUNT_TYPE: new ApiError("Invalid account type", 50002), - CANNOT_EXECUTE_ON_DM: new ApiError("Cannot execute action on a DM channel", 50003), + CANNOT_EXECUTE_ON_DM: new ApiError( + "Cannot execute action on a DM channel", + 50003, + ), EMBED_DISABLED: new ApiError("Guild widget disabled", 50004), - CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError("Cannot edit a message authored by another user", 50005), - 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), + CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError( + "Cannot edit a message authored by another user", + 50005, + ), + 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 + 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, ), - 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), - MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action ({})", 50013, undefined, [""]), - INVALID_AUTHENTICATION_TOKEN: new ApiError("Invalid authentication token provided", 50014), + MISSING_PERMISSIONS: new ApiError( + "You lack permissions to perform that action ({})", + 50013, + undefined, + [""], + ), + INVALID_AUTHENTICATION_TOKEN: new ApiError( + "Invalid authentication token provided", + 50014, + ), NOTE_TOO_LONG: new ApiError("Note was too long", 50015), INVALID_BULK_DELETE_QUANTITY: new ApiError( "Provided too few or too many messages to delete. Must provide at least {} and fewer than {} messages to delete", 50016, undefined, - ["2", "100"] + ["2", "100"], ), 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), - INVALID_OAUTH_TOKEN: new ApiError("Invalid OAuth2 access token provided", 50025), - MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError("Missing required OAuth2 scope", 50026), - INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError("Invalid webhook token provided", 50027), + 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, + ), + INVALID_OAUTH_TOKEN: new ApiError( + "Invalid OAuth2 access token provided", + 50025, + ), + MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError( + "Missing required OAuth2 scope", + 50026, + ), + INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError( + "Invalid webhook token provided", + 50027, + ), INVALID_ROLE: new ApiError("Invalid role", 50028), INVALID_RECIPIENT: new ApiError("Invalid Recipient(s)", 50033), - BULK_DELETE_MESSAGE_TOO_OLD: new ApiError("A message provided was too old to bulk delete", 50034), + BULK_DELETE_MESSAGE_TOO_OLD: new ApiError( + "A message provided was too old to bulk delete", + 50034, + ), INVALID_FORM_BODY: new ApiError( "Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided", - 50035 + 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 + 50036, ), INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041), - FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError("File uploaded exceeds the maximum size", 50045), + 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_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 + 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 + 50083, + ), + INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError( + "Invalid thread notification settings", + 50084, ), - 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 + 50085, + ), + SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError( + "This server is not available in your location", + 50095, ), - 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 + 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, ), - 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), - RESOURCE_OVERLOADED: new ApiError("API resource is currently overloaded. Try again a little later", 130000), + RESOURCE_OVERLOADED: new ApiError( + "API resource is currently overloaded. Try again a little later", + 130000, + ), STAGE_ALREADY_OPEN: new ApiError("The Stage is already open", 150006), - THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError("A thread has already been created for this message", 160004), + 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_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 + 160007, + ), + INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError( + "Invalid JSON for uploaded Lottie file", + 170001, ), - 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 + 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"], + ), + LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError( + "Lottie animation maximum dimensions exceeded", + 170005, ), - 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", - ]), - 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 + 170006, ), STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError( "Sticker animation duration exceeds maximum of {} seconds", 170007, undefined, - ["5"] + ["5"], ), //Other errors @@ -731,26 +982,92 @@ export const DiscordApiErrors = { * An error encountered while performing an API request (Fosscord only). Here are the potential errors: */ export const FosscordApiErrors = { - MANUALLY_TRIGGERED_ERROR: new ApiError("This is an artificial error", 1, 500), - PREMIUM_DISABLED_FOR_GUILD: new ApiError("This guild cannot be boosted", 25001), - NO_FURTHER_PREMIUM: new ApiError("This guild does not receive further boosts", 25002), - GUILD_PREMIUM_DISABLED_FOR_YOU: new ApiError("This guild cannot be boosted by you", 25003, 403), + MANUALLY_TRIGGERED_ERROR: new ApiError( + "This is an artificial error", + 1, + 500, + ), + PREMIUM_DISABLED_FOR_GUILD: new ApiError( + "This guild cannot be boosted", + 25001, + ), + NO_FURTHER_PREMIUM: new ApiError( + "This guild does not receive further boosts", + 25002, + ), + GUILD_PREMIUM_DISABLED_FOR_YOU: new ApiError( + "This guild cannot be boosted by you", + 25003, + 403, + ), CANNOT_FRIEND_SELF: new ApiError("Cannot friend oneself", 25009), - USER_SPECIFIC_INVITE_WRONG_RECIPIENT: new ApiError("This invite is not meant for you", 25010), + USER_SPECIFIC_INVITE_WRONG_RECIPIENT: new ApiError( + "This invite is not meant for you", + 25010, + ), USER_SPECIFIC_INVITE_FAILED: new ApiError("Failed to invite user", 25011), - CANNOT_MODIFY_USER_GROUP: new ApiError("This user cannot manipulate this group", 25050, 403), - CANNOT_REMOVE_SELF_FROM_GROUP: new ApiError("This user cannot remove oneself from user group", 25051), - CANNOT_BAN_OPERATOR: new ApiError("Non-OPERATOR cannot ban OPERATOR from instance", 25052), - CANNOT_LEAVE_GUILD: new ApiError("You are not allowed to leave guilds that you joined by yourself", 25059, 403), - EDITS_DISABLED: new ApiError("You are not allowed to edit your own messages", 25060, 403), - DELETE_MESSAGE_DISABLED: new ApiError("You are not allowed to delete your own messages", 25061, 403), - FEATURE_PERMANENTLY_DISABLED: new ApiError("This feature has been disabled server-side", 45006, 501), - MISSING_RIGHTS: new ApiError("You lack rights to perform that action ({})", 50013, undefined, [""]), - CANNOT_REPLACE_BY_BACKFILL: new ApiError("Cannot backfill to message ID that already exists", 55002, 409), - 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_MODIFY_USER_GROUP: new ApiError( + "This user cannot manipulate this group", + 25050, + 403, + ), + CANNOT_REMOVE_SELF_FROM_GROUP: new ApiError( + "This user cannot remove oneself from user group", + 25051, + ), + CANNOT_BAN_OPERATOR: new ApiError( + "Non-OPERATOR cannot ban OPERATOR from instance", + 25052, + ), + CANNOT_LEAVE_GUILD: new ApiError( + "You are not allowed to leave guilds that you joined by yourself", + 25059, + 403, + ), + EDITS_DISABLED: new ApiError( + "You are not allowed to edit your own messages", + 25060, + 403, + ), + DELETE_MESSAGE_DISABLED: new ApiError( + "You are not allowed to delete your own messages", + 25061, + 403, + ), + FEATURE_PERMANENTLY_DISABLED: new ApiError( + "This feature has been disabled server-side", + 45006, + 501, + ), + MISSING_RIGHTS: new ApiError( + "You lack rights to perform that action ({})", + 50013, + undefined, + [""], + ), + CANNOT_REPLACE_BY_BACKFILL: new ApiError( + "Cannot backfill to message ID that already exists", + 55002, + 409, + ), + 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, + ), }; /** @@ -769,11 +1086,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: @@ -782,15 +1095,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/src/util/util/Database.ts b/src/util/util/Database.ts index ddbea57d..e96be6c4 100644 --- a/src/util/util/Database.ts +++ b/src/util/util/Database.ts @@ -9,7 +9,8 @@ import { yellow, green, red } from "picocolors"; // We want to generate all id's with Snowflakes that's why we have our own BaseEntity class var dbConnection: DataSource | undefined; -let dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db"); +let dbConnectionString = + process.env.DATABASE || path.join(process.cwd(), "database.db"); export function getDatabase(): DataSource | null { // if (!dbConnection) throw new Error("Tried to get database before it was initialised"); @@ -20,18 +21,24 @@ export function getDatabase(): DataSource | null { export async function initDatabase(): Promise<DataSource> { if (dbConnection) return dbConnection; - const type = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : "sqlite"; + 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!`)}`); + console.log( + `[Database] ${red( + `You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`, + )}`, + ); } const dataSource = new DataSource({ //@ts-ignore type, - charset: 'utf8mb4', + charset: "utf8mb4", url: isSqlite ? undefined : dbConnectionString, database: isSqlite ? dbConnectionString : undefined, entities: ["dist/util/entities/*.js"], diff --git a/src/util/util/Email.ts b/src/util/util/Email.ts index 6885da33..c98ccff0 100644 --- a/src/util/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/src/util/util/Event.ts b/src/util/util/Event.ts index 20a638a0..c81de951 100644 --- a/src/util/util/Event.ts +++ b/src/util/util/Event.ts @@ -5,15 +5,27 @@ import { EVENT, Event } from "../interfaces"; export const events = new EventEmitter(); export async function emitEvent(payload: Omit<Event, "created_at">) { - const id = (payload.channel_id || payload.user_id || payload.guild_id) as string; + const id = (payload.channel_id || + payload.user_id || + payload.guild_id) as string; if (!id) return console.error("event doesn't contain any id", payload); if (RabbitMQ.connection) { - const data = typeof payload.data === "object" ? JSON.stringify(payload.data) : payload.data; // use rabbitmq for event transmission - await RabbitMQ.channel?.assertExchange(id, "fanout", { durable: false }); + const data = + typeof payload.data === "object" + ? JSON.stringify(payload.data) + : payload.data; // use rabbitmq for event transmission + await RabbitMQ.channel?.assertExchange(id, "fanout", { + durable: false, + }); // assertQueue isn't needed, because a queue will automatically created if it doesn't exist - const successful = RabbitMQ.channel?.publish(id, "", Buffer.from(`${data}`), { type: payload.event }); + const successful = RabbitMQ.channel?.publish( + id, + "", + Buffer.from(`${data}`), + { type: payload.event }, + ); if (!successful) throw new Error("failed to send event"); } else if (process.env.EVENT_TRANSMISSION === "process") { process.send?.({ type: "event", event: payload, id } as ProcessEvent); @@ -48,10 +60,19 @@ export interface ProcessEvent { id: string; } -export async function listenEvent(event: string, callback: (event: EventOpts) => any, opts?: ListenEventOpts) { +export async function listenEvent( + event: string, + callback: (event: EventOpts) => any, + opts?: ListenEventOpts, +) { if (RabbitMQ.connection) { - // @ts-ignore - return rabbitListen(opts?.channel || RabbitMQ.channel, event, callback, { acknowledge: opts?.acknowledge }); + return rabbitListen( + // @ts-ignore + opts?.channel || RabbitMQ.channel, + event, + callback, + { acknowledge: opts?.acknowledge }, + ); } else if (process.env.EVENT_TRANSMISSION === "process") { const cancel = () => { process.removeListener("message", listener); @@ -59,7 +80,9 @@ export async function listenEvent(event: string, callback: (event: EventOpts) => }; const listener = (msg: ProcessEvent) => { - msg.type === "event" && msg.id === event && callback({ ...msg.event, cancel }); + msg.type === "event" && + msg.id === event && + callback({ ...msg.event, cancel }); }; //@ts-ignore apparently theres no function addListener with this signature @@ -84,10 +107,13 @@ async function rabbitListen( channel: Channel, id: string, callback: (event: EventOpts) => any, - opts?: { acknowledge?: boolean } + opts?: { acknowledge?: boolean }, ) { await channel.assertExchange(id, "fanout", { durable: false }); - const q = await channel.assertQueue("", { exclusive: true, autoDelete: true }); + const q = await channel.assertQueue("", { + exclusive: true, + autoDelete: true, + }); const cancel = () => { channel.cancel(q.queue); @@ -116,7 +142,7 @@ async function rabbitListen( }, { noAck: !opts?.acknowledge, - } + }, ); return cancel; diff --git a/src/util/util/FieldError.ts b/src/util/util/FieldError.ts index 406b33e8..24818fed 100644 --- a/src/util/util/FieldError.ts +++ b/src/util/util/FieldError.ts @@ -1,6 +1,8 @@ import "missing-native-js-functions"; -export function FieldErrors(fields: Record<string, { code?: string; message: string }>) { +export function FieldErrors( + fields: Record<string, { code?: string; message: string }>, +) { return new FieldError( 50035, "Invalid Form Body", @@ -11,7 +13,7 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str code: code || "BASE_TYPE_INVALID", }, ], - })) + })), ); } @@ -19,7 +21,11 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str // Ensure you use the proper content type (image/jpeg, image/png, image/gif) that matches the image data being provided. export class FieldError extends Error { - constructor(public code: string | number, public message: string, public errors?: any) { + constructor( + public code: string | number, + public message: string, + public errors?: any, + ) { super(message); } } diff --git a/src/util/util/Intents.ts b/src/util/util/Intents.ts index 1e840b76..b9f4d65a 100644 --- a/src/util/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 + INSTANCE_USER_UPDATES: BigInt(1) << BigInt(63), // all instance user updates }; } - diff --git a/src/util/util/InvisibleCharacters.ts b/src/util/util/InvisibleCharacters.ts index a48cfab0..da295d68 100644 --- a/src/util/util/InvisibleCharacters.ts +++ b/src/util/util/InvisibleCharacters.ts @@ -1,56 +1,56 @@ -// List from https://invisible-characters.com/ -export const InvisibleCharacters = [ - '\u{9}', //Tab - //'\u{20}', //Space //categories can have spaces in them - '\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 +// List from https://invisible-characters.com/ +export const InvisibleCharacters = [ + "\u{9}", //Tab + //'\u{20}', //Space //categories can have spaces in them + "\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 +]; diff --git a/src/util/util/Permissions.ts b/src/util/util/Permissions.ts index a432af76..0c12487e 100644 --- a/src/util/util/Permissions.ts +++ b/src/util/util/Permissions.ts @@ -1,6 +1,12 @@ // https://github.com/discordjs/discord.js/blob/master/src/util/Permissions.js // Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah -import { Channel, ChannelPermissionOverwrite, Guild, Member, Role } from "../entities"; +import { + Channel, + ChannelPermissionOverwrite, + Guild, + Member, + Role, +} from "../entities"; import { BitField } from "./BitField"; import "missing-native-js-functions"; import { BitFieldResolvable, BitFlag } from "./BitField"; @@ -13,7 +19,12 @@ try { HTTPError = Error; } -export type PermissionResolvable = bigint | number | Permissions | PermissionResolvable[] | PermissionString; +export type PermissionResolvable = + | bigint + | number + | Permissions + | PermissionResolvable[] + | PermissionString; type PermissionString = keyof typeof Permissions.FLAGS; @@ -80,14 +91,20 @@ export class Permissions extends BitField { }; any(permission: PermissionResolvable, checkAdmin = true) { - return (checkAdmin && super.any(Permissions.FLAGS.ADMINISTRATOR)) || super.any(permission); + return ( + (checkAdmin && super.any(Permissions.FLAGS.ADMINISTRATOR)) || + super.any(permission) + ); } /** * Checks whether the bitfield has a permission, or multiple permissions. */ has(permission: PermissionResolvable, checkAdmin = true) { - return (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || super.has(permission); + return ( + (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || + super.has(permission) + ); } /** @@ -96,28 +113,39 @@ export class Permissions extends BitField { hasThrow(permission: PermissionResolvable) { if (this.has(permission) && this.has("VIEW_CHANNEL")) return true; // @ts-ignore - throw new HTTPError(`You are missing the following permissions ${permission}`, 403); + throw new HTTPError( + `You are missing the following permissions ${permission}`, + 403, + ); } overwriteChannel(overwrites: ChannelPermissionOverwrite[]) { if (!overwrites) return this; if (!this.cache) throw new Error("permission chache not available"); overwrites = overwrites.filter((x) => { - if (x.type === 0 && this.cache.roles?.some((r) => r.id === x.id)) return true; + if (x.type === 0 && this.cache.roles?.some((r) => r.id === x.id)) + return true; if (x.type === 1 && x.id == this.cache.user_id) return true; return false; }); - return new Permissions(Permissions.channelPermission(overwrites, this.bitfield)); + return new Permissions( + Permissions.channelPermission(overwrites, this.bitfield), + ); } - static channelPermission(overwrites: ChannelPermissionOverwrite[], init?: bigint) { + static channelPermission( + overwrites: ChannelPermissionOverwrite[], + init?: bigint, + ) { // TODO: do not deny any permissions if admin return overwrites.reduce((permission, overwrite) => { // apply disallowed permission // * permission: current calculated permission (e.g. 010) // * deny contains all denied permissions (e.g. 011) // * allow contains all explicitly allowed permisions (e.g. 100) - return (permission & ~BigInt(overwrite.deny)) | BigInt(overwrite.allow); + return ( + (permission & ~BigInt(overwrite.deny)) | BigInt(overwrite.allow) + ); // ~ operator inverts deny (e.g. 011 -> 100) // & operator only allows 1 for both ~deny and permission (e.g. 010 & 100 -> 000) // | operators adds both together (e.g. 000 + 100 -> 100) @@ -126,7 +154,10 @@ export class Permissions extends BitField { static rolePermission(roles: Role[]) { // adds all permissions of all roles together (Bit OR) - return roles.reduce((permission, role) => permission | BigInt(role.permissions), BigInt(0)); + return roles.reduce( + (permission, role) => permission | BigInt(role.permissions), + BigInt(0), + ); } static finalPermission({ @@ -157,7 +188,8 @@ export class Permissions extends BitField { } if (channel?.recipient_ids) { - if (channel?.owner_id === user.id) return new Permissions("ADMINISTRATOR"); + if (channel?.owner_id === user.id) + return new Permissions("ADMINISTRATOR"); if (channel.recipient_ids.includes(user.id)) { // Default dm permissions return new Permissions([ @@ -183,7 +215,10 @@ export class Permissions extends BitField { } } -const ALL_PERMISSIONS = Object.values(Permissions.FLAGS).reduce((total, val) => total | val, BigInt(0)); +const ALL_PERMISSIONS = Object.values(Permissions.FLAGS).reduce( + (total, val) => total | val, + BigInt(0), +); export type PermissionCache = { channel?: Channel | undefined; @@ -204,7 +239,7 @@ export async function getPermission( channel_relations?: string[]; member_select?: (keyof Member)[]; member_relations?: string[]; - } = {} + } = {}, ) { if (!user_id) throw new HTTPError("User not found"); var channel: Channel | undefined; @@ -239,16 +274,17 @@ export async function getPermission( ], relations: opts.guild_relations, }); - if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); + if (guild.owner_id === user_id) + return new Permissions(Permissions.FLAGS.ADMINISTRATOR); member = await Member.findOneOrFail({ where: { guild_id, id: user_id }, relations: ["roles", ...(opts.member_relations || [])], // select: [ - // "id", // TODO: Bug in typeorm? adding these selects breaks the query. - // "roles", - // @ts-ignore - // ...(opts.member_select || []), + // "id", // TODO: Bug in typeorm? adding these selects breaks the query. + // "roles", + // @ts-ignore + // ...(opts.member_select || []), // ], }); } diff --git a/src/util/util/RabbitMQ.ts b/src/util/util/RabbitMQ.ts index 0f5eb6aa..1bfb3f5c 100644 --- a/src/util/util/RabbitMQ.ts +++ b/src/util/util/RabbitMQ.ts @@ -1,7 +1,11 @@ import amqp, { Connection, Channel } from "amqplib"; // import Config from "./Config"; -export const RabbitMQ: { connection: Connection | null; channel: Channel | null; init: () => Promise<void> } = { +export const RabbitMQ: { + connection: Connection | null; + channel: Channel | null; + init: () => Promise<void>; +} = { connection: null, channel: null, init: async function () { diff --git a/src/util/util/Rights.ts b/src/util/util/Rights.ts index b28c75b7..659353a6 100644 --- a/src/util/util/Rights.ts +++ b/src/util/util/Rights.ts @@ -11,7 +11,12 @@ try { HTTPError = Error; } -export type RightResolvable = bigint | number | Rights | RightResolvable[] | RightString; +export type RightResolvable = + | bigint + | number + | Rights + | RightResolvable[] + | RightString; type RightString = keyof typeof Rights.FLAGS; // TODO: just like roles for members, users should have privilidges which combine multiple rights into one and make it easy to assign @@ -60,7 +65,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 @@ -72,31 +77,44 @@ export class Rights extends BitField { RESPOND_TO_INTERACTIONS: BitFlag(41), // can respond to interactions SEND_BACKDATED_EVENTS: BitFlag(42), // can send backdated events USE_MASS_INVITES: BitFlag(43), // added per @xnacly's request — can accept mass invites - ACCEPT_INVITES: BitFlag(44) // added per @xnacly's request — can accept user-specific invites and DM requests + ACCEPT_INVITES: BitFlag(44), // added per @xnacly's request — can accept user-specific invites and DM requests }; any(permission: RightResolvable, checkOperator = true) { - return (checkOperator && super.any(Rights.FLAGS.OPERATOR)) || super.any(permission); + return ( + (checkOperator && super.any(Rights.FLAGS.OPERATOR)) || + super.any(permission) + ); } has(permission: RightResolvable, checkOperator = true) { - return (checkOperator && super.has(Rights.FLAGS.OPERATOR)) || super.has(permission); + return ( + (checkOperator && super.has(Rights.FLAGS.OPERATOR)) || + super.has(permission) + ); } hasThrow(permission: RightResolvable) { if (this.has(permission)) return true; // @ts-ignore - throw new HTTPError(`You are missing the following rights ${permission}`, 403); + 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)); +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/src/util/util/Snowflake.ts b/src/util/util/Snowflake.ts index 134d526e..69effb2e 100644 --- a/src/util/util/Snowflake.ts +++ b/src/util/util/Snowflake.ts @@ -17,7 +17,9 @@ export class Snowflake { static workerId = BigInt((cluster.worker?.id || 0) % 31); // max 31 constructor() { - throw new Error(`The ${this.constructor.name} class may not be instantiated.`); + throw new Error( + `The ${this.constructor.name} class may not be instantiated.`, + ); } /** @@ -83,14 +85,15 @@ export class Snowflake { return dec; } - static generateWorkerProcess() { // worker process - returns a number + 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++; return BigInt(time | worker | process | increment); } - + static generate() { return Snowflake.generateWorkerProcess().toString(); } @@ -111,7 +114,9 @@ export class Snowflake { * @returns {DeconstructedSnowflake} Deconstructed snowflake */ static deconstruct(snowflake) { - const BINARY = Snowflake.idToBinary(snowflake).toString(2).padStart(64, "0"); + const BINARY = Snowflake.idToBinary(snowflake) + .toString(2) + .padStart(64, "0"); const res = { timestamp: parseInt(BINARY.substring(0, 42), 2) + Snowflake.EPOCH, workerID: parseInt(BINARY.substring(42, 47), 2), diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts index 5ba3e1ec..19e64f47 100644 --- a/src/util/util/Token.ts +++ b/src/util/util/Token.ts @@ -17,11 +17,14 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> { const user = await User.findOne({ where: { id: decoded.id }, - select: ["data", "bot", "disabled", "deleted", "rights"] + 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)) + 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"); @@ -45,7 +48,7 @@ export async function generateToken(id: string) { (err, token) => { if (err) return rej(err); return res(token); - } + }, ); }); } diff --git a/src/util/util/TraverseDirectory.ts b/src/util/util/TraverseDirectory.ts index 3d0d6279..3f5b0385 100644 --- a/src/util/util/TraverseDirectory.ts +++ b/src/util/util/TraverseDirectory.ts @@ -1,13 +1,14 @@ 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) + server.registerRoute.bind(server, root), ); } diff --git a/src/util/util/cdn.ts b/src/util/util/cdn.ts index 812a4e1d..6b2c8424 100644 --- a/src/util/util/cdn.ts +++ b/src/util/util/cdn.ts @@ -4,7 +4,10 @@ import fetch from "node-fetch"; import { Attachment } from "../entities"; import { Config } from "./Config"; -export async function uploadFile(path: string, file?: Express.Multer.File): Promise<Attachment> { +export async function uploadFile( + path: string, + file?: Express.Multer.File, +): Promise<Attachment> { if (!file?.buffer) throw new HTTPError("Missing file in body"); const form = new FormData(); @@ -13,28 +16,38 @@ export async function uploadFile(path: string, file?: Express.Multer.File): Prom filename: file.originalname, }); - const response = await fetch(`${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`, { - headers: { - signature: Config.get().security.requestSignature, - ...form.getHeaders(), + const response = await fetch( + `${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`, + { + headers: { + signature: Config.get().security.requestSignature, + ...form.getHeaders(), + }, + method: "POST", + body: form, }, - method: "POST", - body: form, - }); - const result = await response.json() as Attachment; + ); + const result = (await response.json()) as Attachment; if (response.status !== 200) throw result; return result; } -export async function handleFile(path: string, body?: string): Promise<string | undefined> { +export async function handleFile( + path: string, + body?: string, +): Promise<string | undefined> { if (!body || !body.startsWith("data:")) return undefined; try { const mimetype = body.split(":")[1].split(";")[0]; const buffer = Buffer.from(body.split(",")[1], "base64"); // @ts-ignore - const { id } = await uploadFile(path, { buffer, mimetype, originalname: "banner" }); + const { id } = await uploadFile(path, { + buffer, + mimetype, + originalname: "banner", + }); return id; } catch (error) { console.error(error); @@ -43,12 +56,15 @@ 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, + const response = await fetch( + `${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`, + { + headers: { + signature: Config.get().security.requestSignature, + }, + method: "DELETE", }, - method: "DELETE", - }); + ); const result = await response.json(); if (response.status !== 200) throw result; diff --git a/src/util/util/index.ts b/src/util/util/index.ts index b2bd6489..aa38e472 100644 --- a/src/util/util/index.ts +++ b/src/util/util/index.ts @@ -20,4 +20,4 @@ export * from "./String"; export * from "./Array"; export * from "./TraverseDirectory"; export * from "./InvisibleCharacters"; -export * from "./BannedWords"; \ No newline at end of file +export * from "./BannedWords"; |