diff --git a/src/util/config/types/GeneralConfiguration.ts b/src/util/config/types/GeneralConfiguration.ts
index c20fe9a7..cff8c527 100644
--- a/src/util/config/types/GeneralConfiguration.ts
+++ b/src/util/config/types/GeneralConfiguration.ts
@@ -28,4 +28,5 @@ export class GeneralConfiguration {
correspondenceUserID: string | null = null;
image: string | null = null;
instanceId: string = Snowflake.generate();
+ autoCreateBotUsers: boolean = false;
}
diff --git a/src/util/config/types/SecurityConfiguration.ts b/src/util/config/types/SecurityConfiguration.ts
index 5e971cfe..35776642 100644
--- a/src/util/config/types/SecurityConfiguration.ts
+++ b/src/util/config/types/SecurityConfiguration.ts
@@ -28,7 +28,7 @@ export class SecurityConfiguration {
// header to get the real user ip address
// X-Forwarded-For for nginx/reverse proxies
// CF-Connecting-IP for cloudflare
- forwadedFor: string | null = null;
+ forwardedFor: string | null = null;
ipdataApiKey: string | null =
"eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9";
mfaBackupCodeCount: number = 10;
diff --git a/src/util/config/types/subconfigurations/limits/RateLimits.ts b/src/util/config/types/subconfigurations/limits/RateLimits.ts
index caba740b..0ce0827c 100644
--- a/src/util/config/types/subconfigurations/limits/RateLimits.ts
+++ b/src/util/config/types/subconfigurations/limits/RateLimits.ts
@@ -16,11 +16,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { RouteRateLimit, RateLimitOptions } from ".";
+import { RateLimitOptions, RouteRateLimit } from ".";
export class RateLimits {
enabled: boolean = false;
- ip: Omit<RateLimitOptions, "bot_count"> = {
+ ip: RateLimitOptions = {
count: 500,
window: 5,
};
diff --git a/src/util/connections/Connection.ts b/src/util/connections/Connection.ts
index becee589..5bdebd47 100644
--- a/src/util/connections/Connection.ts
+++ b/src/util/connections/Connection.ts
@@ -24,7 +24,7 @@ import { Config, DiscordApiErrors } from "../util";
/**
* A connection that can be used to connect to an external service.
*/
-export default abstract class Connection {
+export abstract class Connection {
id: string;
settings: { enabled: boolean };
states: Map<string, string> = new Map();
diff --git a/src/util/connections/ConnectionLoader.ts b/src/util/connections/ConnectionLoader.ts
index 28f1a202..e9dc6973 100644
--- a/src/util/connections/ConnectionLoader.ts
+++ b/src/util/connections/ConnectionLoader.ts
@@ -16,9 +16,9 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import { Connection } from "@spacebar/util";
import fs from "fs";
import path from "path";
-import Connection from "./Connection";
import { ConnectionConfig } from "./ConnectionConfig";
import { ConnectionStore } from "./ConnectionStore";
@@ -48,8 +48,7 @@ export class ConnectionLoader {
});
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- public static getConnectionConfig(id: string, defaults?: any): any {
+ public static getConnectionConfig<T>(id: string, defaults?: unknown): T {
let cfg = ConnectionConfig.get()[id];
if (defaults) {
if (cfg) cfg = Object.assign({}, defaults, cfg);
@@ -70,8 +69,7 @@ export class ConnectionLoader {
public static async setConnectionConfig(
id: string,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- config: Partial<any>,
+ config: Partial<unknown>,
): Promise<void> {
if (!config)
console.warn(`[Connections/WARN] ${id} tried to set config=null!`);
diff --git a/src/util/connections/ConnectionStore.ts b/src/util/connections/ConnectionStore.ts
index 39abfea6..95e54fd9 100644
--- a/src/util/connections/ConnectionStore.ts
+++ b/src/util/connections/ConnectionStore.ts
@@ -16,8 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import Connection from "./Connection";
-import RefreshableConnection from "./RefreshableConnection";
+import { Connection } from "./Connection";
+import { RefreshableConnection } from "./RefreshableConnection";
export class ConnectionStore {
public static connections: Map<string, Connection | RefreshableConnection> =
diff --git a/src/util/connections/RefreshableConnection.ts b/src/util/connections/RefreshableConnection.ts
index fd93adfa..88ad8dab 100644
--- a/src/util/connections/RefreshableConnection.ts
+++ b/src/util/connections/RefreshableConnection.ts
@@ -18,13 +18,14 @@
import { ConnectedAccount } from "../entities";
import { ConnectedAccountCommonOAuthTokenResponse } from "../interfaces";
-import Connection from "./Connection";
+import { Connection } from "./Connection";
/**
* A connection that can refresh its token.
*/
-export default abstract class RefreshableConnection extends Connection {
+export abstract class RefreshableConnection extends Connection {
refreshEnabled = true;
+
/**
* Refreshes the token for a connected account.
* @param connectedAccount The connected account to refresh
diff --git a/src/util/dtos/ConnectedAccountDTO.ts b/src/util/dtos/ConnectedAccountDTO.ts
index 0a3604d5..f9efd980 100644
--- a/src/util/dtos/ConnectedAccountDTO.ts
+++ b/src/util/dtos/ConnectedAccountDTO.ts
@@ -30,7 +30,7 @@ export class ConnectedAccountDTO {
verified?: boolean;
visibility?: number;
integrations?: string[];
- metadata_?: any;
+ metadata_?: unknown;
metadata_visibility?: number;
two_way_link?: boolean;
diff --git a/src/util/dtos/ReadyGuildDTO.ts b/src/util/dtos/ReadyGuildDTO.ts
index b21afe74..905ede74 100644
--- a/src/util/dtos/ReadyGuildDTO.ts
+++ b/src/util/dtos/ReadyGuildDTO.ts
@@ -18,13 +18,45 @@
import {
Channel,
+ ChannelOverride,
+ ChannelType,
Emoji,
Guild,
- PublicMember,
+ PublicUser,
Role,
Sticker,
+ UserGuildSettings,
+ PublicMember,
} from "../entities";
+// TODO: this is not the best place for this type
+export type ReadyUserGuildSettingsEntries = Omit<
+ UserGuildSettings,
+ "channel_overrides"
+> & {
+ channel_overrides: (ChannelOverride & { channel_id: string })[];
+};
+
+// TODO: probably should move somewhere else
+export interface ReadyPrivateChannel {
+ id: string;
+ flags: number;
+ is_spam: boolean;
+ last_message_id?: string;
+ recipients: PublicUser[];
+ type: ChannelType.DM | ChannelType.GROUP_DM;
+}
+
+export type GuildOrUnavailable =
+ | { id: string; unavailable: boolean }
+ | (Guild & { joined_at?: Date; unavailable: undefined });
+
+const guildIsAvailable = (
+ guild: GuildOrUnavailable,
+): guild is Guild & { joined_at: Date; unavailable: false } => {
+ return guild.unavailable != true;
+};
+
export interface IReadyGuildDTO {
application_command_counts?: { 1: number; 2: number; 3: number }; // ????????????
channels: Channel[];
@@ -65,12 +97,21 @@ export interface IReadyGuildDTO {
max_members: number | undefined;
nsfw_level: number | undefined;
hub_type?: unknown | null; // ????
+
+ home_header: null; // TODO
+ latest_onboarding_question_id: null; // TODO
+ safety_alerts_channel_id: null; // TODO
+ max_stage_video_channel_users: 50; // TODO
+ nsfw: boolean;
+ id: string;
};
roles: Role[];
stage_instances: unknown[];
stickers: Sticker[];
threads: unknown[];
version: string;
+ guild_hashes: unknown;
+ unavailable: boolean;
}
export class ReadyGuildDTO implements IReadyGuildDTO {
@@ -113,14 +154,30 @@ export class ReadyGuildDTO implements IReadyGuildDTO {
max_members: number | undefined;
nsfw_level: number | undefined;
hub_type?: unknown | null; // ????
+
+ home_header: null; // TODO
+ latest_onboarding_question_id: null; // TODO
+ safety_alerts_channel_id: null; // TODO
+ max_stage_video_channel_users: 50; // TODO
+ nsfw: boolean;
+ id: string;
};
roles: Role[];
stage_instances: unknown[];
stickers: Sticker[];
threads: unknown[];
version: string;
+ guild_hashes: unknown;
+ unavailable: boolean;
+ joined_at: Date;
+
+ constructor(guild: GuildOrUnavailable) {
+ if (!guildIsAvailable(guild)) {
+ this.id = guild.id;
+ this.unavailable = true;
+ return;
+ }
- constructor(guild: Guild) {
this.application_command_counts = {
1: 5,
2: 2,
@@ -164,12 +221,21 @@ export class ReadyGuildDTO implements IReadyGuildDTO {
max_members: guild.max_members,
nsfw_level: guild.nsfw_level,
hub_type: null,
+
+ home_header: null,
+ id: guild.id,
+ latest_onboarding_question_id: null,
+ max_stage_video_channel_users: 50, // TODO
+ nsfw: guild.nsfw,
+ safety_alerts_channel_id: null,
};
this.roles = guild.roles;
this.stage_instances = [];
this.stickers = guild.stickers;
this.threads = [];
this.version = "1"; // ??????
+ this.guild_hashes = {};
+ this.joined_at = guild.joined_at;
}
toJSON() {
diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts
index 9ce04848..19952bc2 100644
--- a/src/util/entities/Channel.ts
+++ b/src/util/entities/Channel.ts
@@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import { HTTPError } from "lambert-server";
import {
Column,
Entity,
@@ -24,26 +25,25 @@ import {
OneToMany,
RelationId,
} from "typeorm";
-import { BaseClass } from "./BaseClass";
-import { Guild } from "./Guild";
-import { PublicUserProjection, User } from "./User";
-import { HTTPError } from "lambert-server";
+import { DmChannelDTO } from "../dtos";
+import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
import {
+ InvisibleCharacters,
+ Snowflake,
containsAll,
emitEvent,
getPermission,
- Snowflake,
trimSpecial,
- InvisibleCharacters,
} from "../util";
-import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
-import { Recipient } from "./Recipient";
+import { BaseClass } from "./BaseClass";
+import { Guild } from "./Guild";
+import { Invite } from "./Invite";
import { Message } from "./Message";
import { ReadState } from "./ReadState";
-import { Invite } from "./Invite";
+import { Recipient } from "./Recipient";
+import { PublicUserProjection, User } from "./User";
import { VoiceState } from "./VoiceState";
import { Webhook } from "./Webhook";
-import { DmChannelDTO } from "../dtos";
export enum ChannelType {
GUILD_TEXT = 0, // a text channel within a guild
@@ -302,8 +302,10 @@ export class Channel extends BaseClass {
: channel.position) || 0,
};
+ const ret = Channel.create(channel);
+
await Promise.all([
- Channel.create(channel).save(),
+ ret.save(),
!opts?.skipEventEmit
? emitEvent({
event: "CHANNEL_CREATE",
@@ -313,7 +315,7 @@ export class Channel extends BaseClass {
: Promise.resolve(),
]);
- return channel;
+ return ret;
}
static async createDMChannel(
@@ -468,6 +470,18 @@ export class Channel extends BaseClass {
];
return disallowedChannelTypes.indexOf(this.type) == -1;
}
+
+ toJSON() {
+ return {
+ ...this,
+
+ // these fields are not returned depending on the type of channel
+ bitrate: this.bitrate || undefined,
+ user_limit: this.user_limit || undefined,
+ rate_limit_per_user: this.rate_limit_per_user || undefined,
+ owner_id: this.owner_id || undefined,
+ };
+ }
}
export interface ChannelPermissionOverwrite {
@@ -482,3 +496,33 @@ export enum ChannelPermissionOverwriteType {
member = 1,
group = 2,
}
+
+export interface DMChannel extends Omit<Channel, "type" | "recipients"> {
+ type: ChannelType.DM | ChannelType.GROUP_DM;
+ recipients: Recipient[];
+}
+
+// TODO: probably more props
+export function isTextChannel(type: ChannelType): boolean {
+ switch (type) {
+ case ChannelType.GUILD_STORE:
+ case ChannelType.GUILD_VOICE:
+ case ChannelType.GUILD_STAGE_VOICE:
+ case ChannelType.GUILD_CATEGORY:
+ case ChannelType.GUILD_FORUM:
+ case ChannelType.DIRECTORY:
+ throw new HTTPError("not a text channel", 400);
+ case ChannelType.DM:
+ case ChannelType.GROUP_DM:
+ case ChannelType.GUILD_NEWS:
+ case ChannelType.GUILD_NEWS_THREAD:
+ case ChannelType.GUILD_PUBLIC_THREAD:
+ case ChannelType.GUILD_PRIVATE_THREAD:
+ case ChannelType.GUILD_TEXT:
+ case ChannelType.ENCRYPTED:
+ case ChannelType.ENCRYPTED_THREAD:
+ return true;
+ default:
+ throw new HTTPError("unimplemented", 400);
+ }
+}
diff --git a/src/util/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts
index 5dd21250..6e089de1 100644
--- a/src/util/entities/ConnectedAccount.ts
+++ b/src/util/entities/ConnectedAccount.ts
@@ -66,6 +66,7 @@ export class ConnectedAccount extends BaseClass {
integrations?: string[] = [];
@Column({ type: "simple-json", name: "metadata", nullable: true })
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
metadata_?: any;
@Column()
diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts
index e8454986..e364ed98 100644
--- a/src/util/entities/Guild.ts
+++ b/src/util/entities/Guild.ts
@@ -24,7 +24,7 @@ import {
OneToMany,
RelationId,
} from "typeorm";
-import { Config, handleFile, Snowflake } from "..";
+import { Config, GuildWelcomeScreen, handleFile, Snowflake } from "..";
import { Ban } from "./Ban";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
@@ -77,7 +77,7 @@ export class Guild extends BaseClass {
afk_channel?: Channel;
@Column({ nullable: true })
- afk_timeout?: number = Config.get().defaults.guild.afkTimeout;
+ afk_timeout?: number;
// * commented out -> use owner instead
// application id of the guild creator if it is bot-created
@@ -95,8 +95,7 @@ export class Guild extends BaseClass {
banner?: string;
@Column({ nullable: true })
- default_message_notifications?: number =
- Config.get().defaults.guild.defaultMessageNotifications;
+ default_message_notifications?: number;
@Column({ nullable: true })
description?: string;
@@ -105,11 +104,10 @@ export class Guild extends BaseClass {
discovery_splash?: string;
@Column({ nullable: true })
- explicit_content_filter?: number =
- Config.get().defaults.guild.explicitContentFilter;
+ explicit_content_filter?: number;
@Column({ type: "simple-array" })
- features: string[] = Config.get().guild.defaultFeatures || []; //TODO use enum
+ features: string[] = []; //TODO use enum
//TODO: https://discord.com/developers/docs/resources/guild#guild-object-guild-features
@Column({ nullable: true })
@@ -122,14 +120,13 @@ export class Guild extends BaseClass {
large?: boolean = false;
@Column({ nullable: true })
- max_members?: number = Config.get().limits.guild.maxMembers;
+ max_members?: number;
@Column({ nullable: true })
- max_presences?: number = Config.get().defaults.guild.maxPresences;
+ max_presences?: number;
@Column({ nullable: true })
- max_video_channel_users?: number =
- Config.get().defaults.guild.maxVideoChannelUsers;
+ max_video_channel_users?: number;
@Column({ nullable: true })
member_count?: number;
@@ -247,7 +244,7 @@ export class Guild extends BaseClass {
rules_channel?: string;
@Column({ nullable: true })
- region?: string = Config.get().regions.default;
+ region?: string;
@Column({ nullable: true })
splash?: string;
@@ -270,16 +267,7 @@ export class Guild extends BaseClass {
verification_level?: number;
@Column({ type: "simple-json" })
- welcome_screen: {
- enabled: boolean;
- description: string;
- welcome_channels: {
- description: string;
- emoji_id?: string;
- emoji_name?: string;
- channel_id: string;
- }[];
- };
+ welcome_screen: GuildWelcomeScreen;
@Column({ nullable: true })
@RelationId((guild: Guild) => guild.widget_channel)
@@ -336,6 +324,18 @@ export class Guild extends BaseClass {
description: "Fill in your description",
welcome_channels: [],
},
+
+ afk_timeout: Config.get().defaults.guild.afkTimeout,
+ default_message_notifications:
+ Config.get().defaults.guild.defaultMessageNotifications,
+ explicit_content_filter:
+ Config.get().defaults.guild.explicitContentFilter,
+ features: Config.get().guild.defaultFeatures,
+ max_members: Config.get().limits.guild.maxMembers,
+ max_presences: Config.get().defaults.guild.maxPresences,
+ max_video_channel_users:
+ Config.get().defaults.guild.maxVideoChannelUsers,
+ region: Config.get().regions.default,
}).save();
// we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error
@@ -353,6 +353,7 @@ export class Guild extends BaseClass {
position: 0,
icon: undefined,
unicode_emoji: undefined,
+ flags: 0, // TODO?
}).save();
if (!body.channels || !body.channels.length)
@@ -389,4 +390,11 @@ export class Guild extends BaseClass {
return guild;
}
+
+ toJSON() {
+ return {
+ ...this,
+ unavailable: this.unavailable == false ? undefined : true,
+ };
+ }
}
diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts
index 8c208202..8be6eae1 100644
--- a/src/util/entities/Member.ts
+++ b/src/util/entities/Member.ts
@@ -344,11 +344,7 @@ export class Member extends BaseClassWithoutId {
relations: ["user", "roles"],
take: 10,
})
- ).map((member) => ({
- ...member.toPublicMember(),
- user: member.user.toPublicUser(),
- roles: member.roles.map((x) => x.id),
- }));
+ ).map((member) => member.toPublicMember());
if (
await Member.count({
@@ -455,6 +451,10 @@ export class Member extends BaseClassWithoutId {
PublicMemberProjection.forEach((x) => {
member[x] = this[x];
});
+
+ if (member.roles) member.roles = member.roles.map((x: Role) => x.id);
+ if (member.user) member.user = member.user.toPublicUser();
+
return member as PublicMember;
}
}
diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts
index 519c431e..3598d29f 100644
--- a/src/util/entities/Message.ts
+++ b/src/util/entities/Message.ts
@@ -193,7 +193,7 @@ export class Message extends BaseClass {
};
@Column({ nullable: true })
- flags?: string;
+ flags?: number;
@Column({ type: "simple-json", nullable: true })
message_reference?: {
@@ -217,6 +217,29 @@ export class Message extends BaseClass {
@Column({ type: "simple-json", nullable: true })
components?: MessageComponent[];
+
+ toJSON(): Message {
+ return {
+ ...this,
+ author_id: undefined,
+ member_id: undefined,
+ webhook_id: undefined,
+ application_id: undefined,
+
+ nonce: this.nonce ?? undefined,
+ tts: this.tts ?? false,
+ guild: this.guild ?? undefined,
+ webhook: this.webhook ?? undefined,
+ interaction: this.interaction ?? undefined,
+ reactions: this.reactions ?? undefined,
+ sticker_items: this.sticker_items ?? undefined,
+ message_reference: this.message_reference ?? undefined,
+ author: this.author?.toPublicUser() ?? undefined,
+ activity: this.activity ?? undefined,
+ application: this.application ?? undefined,
+ components: this.components ?? undefined,
+ };
+ }
}
export interface MessageComponent {
diff --git a/src/util/entities/Role.ts b/src/util/entities/Role.ts
index 85877c12..9a601f31 100644
--- a/src/util/entities/Role.ts
+++ b/src/util/entities/Role.ts
@@ -66,4 +66,7 @@ export class Role extends BaseClass {
integration_id?: string;
premium_subscriber?: boolean;
};
+
+ @Column({ default: 0 })
+ flags: number;
}
diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts
index df9af328..3f1bda05 100644
--- a/src/util/entities/User.ts
+++ b/src/util/entities/User.ts
@@ -26,11 +26,11 @@ import {
OneToOne,
} from "typeorm";
import {
- adjustEmail,
Config,
Email,
FieldErrors,
Snowflake,
+ adjustEmail,
trimSpecial,
} from "..";
import { BitField } from "../util/BitField";
@@ -86,8 +86,7 @@ export const PrivateUserProjection = [
// Private user data that should never get sent to the client
export type PublicUser = Pick<User, PublicUserKeys>;
-
-export type UserPublic = Pick<User, PublicUserKeys>;
+export type PrivateUser = Pick<User, PrivateUserKeys>;
export interface UserPrivate extends Pick<User, PrivateUserKeys> {
locale: string;
@@ -110,8 +109,10 @@ export class User extends BaseClass {
@Column({ nullable: true })
banner?: string; // hash of the user banner
+ // TODO: Separate `User` and `UserProfile` models
+ // puyo: changed from [number, number] because it breaks openapi
@Column({ nullable: true, type: "simple-array" })
- theme_colors?: [number, number]; // TODO: Separate `User` and `UserProfile` models
+ theme_colors?: number[];
@Column({ nullable: true })
pronouns?: string;
@@ -126,10 +127,10 @@ export class User extends BaseClass {
mobile: boolean = false; // if the user has mobile app installed
@Column()
- premium: boolean = Config.get().defaults.user.premium ?? false; // if user bought individual premium
+ premium: boolean; // if user bought individual premium
@Column()
- premium_type: number = Config.get().defaults.user.premiumType ?? 0; // individual premium level
+ premium_type: number; // individual premium level
@Column()
bot: boolean = false; // if user is bot
@@ -156,13 +157,13 @@ export class User extends BaseClass {
totp_last_ticket?: string = "";
@Column()
- created_at: Date = new Date(); // registration date
+ created_at: Date; // registration date
@Column({ nullable: true })
premium_since: Date; // premium date
@Column({ select: false })
- verified: boolean = Config.get().defaults.user.verified ?? true; // email is verified
+ verified: boolean; // email is verified
@Column()
disabled: boolean = false; // if the account is disabled
@@ -174,7 +175,7 @@ export class User extends BaseClass {
email?: string; // email of the user
@Column()
- flags: string = "0"; // UserFlags // TODO: generate
+ flags: number = 0; // UserFlags // TODO: generate
@Column()
public_flags: number = 0;
@@ -280,6 +281,15 @@ export class User extends BaseClass {
return user as PublicUser;
}
+ toPrivateUser() {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const user: any = {};
+ PrivateUserProjection.forEach((x) => {
+ user[x] = this[x];
+ });
+ return user as UserPrivate;
+ }
+
static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) {
return await User.findOneOrFail({
where: { id: user_id },
@@ -381,11 +391,16 @@ export class User extends BaseClass {
valid_tokens_since: new Date(),
},
extended_settings: "{}",
+ settings: settings,
+
premium_since: Config.get().defaults.user.premium
? new Date()
: undefined,
- settings: settings,
rights: Config.get().register.defaultRights,
+ premium: Config.get().defaults.user.premium ?? false,
+ premium_type: Config.get().defaults.user.premiumType ?? 0,
+ verified: Config.get().defaults.user.verified ?? true,
+ created_at: new Date(),
});
user.validate();
diff --git a/src/util/interfaces/Activity.ts b/src/util/interfaces/Activity.ts
index 7654ba90..0227f242 100644
--- a/src/util/interfaces/Activity.ts
+++ b/src/util/interfaces/Activity.ts
@@ -36,7 +36,7 @@ export interface Activity {
};
party?: {
id?: string;
- size?: [number]; // used to show the party's current and maximum size // TODO: array length 2
+ size?: number[]; // used to show the party's current and maximum size // TODO: array length 2
};
assets?: {
large_image?: string; // the id for a large asset of the activity, usually a snowflake
diff --git a/src/util/interfaces/Event.ts b/src/util/interfaces/Event.ts
index 76a5f8d0..deb54428 100644
--- a/src/util/interfaces/Event.ts
+++ b/src/util/interfaces/Event.ts
@@ -28,7 +28,6 @@ import {
Role,
Emoji,
PublicMember,
- UserGuildSettings,
Guild,
Channel,
PublicUser,
@@ -40,6 +39,10 @@ import {
UserSettings,
IReadyGuildDTO,
ReadState,
+ UserPrivate,
+ ReadyUserGuildSettingsEntries,
+ ReadyPrivateChannel,
+ GuildOrUnavailable,
} from "@spacebar/util";
export interface Event {
@@ -68,22 +71,10 @@ export interface PublicRelationship {
export interface ReadyEventData {
v: number;
- user: PublicUser & {
- mobile: boolean;
- desktop: boolean;
- email: string | undefined;
- flags: string;
- mfa_enabled: boolean;
- nsfw_allowed: boolean;
- phone: string | undefined;
- premium: boolean;
- premium_type: number;
- verified: boolean;
- bot: boolean;
- };
- private_channels: Channel[]; // this will be empty for bots
+ user: UserPrivate;
+ private_channels: ReadyPrivateChannel[]; // this will be empty for bots
session_id: string; // resuming
- guilds: IReadyGuildDTO[];
+ guilds: IReadyGuildDTO[] | GuildOrUnavailable[]; // depends on capability
analytics_token?: string;
connected_accounts?: ConnectedAccount[];
consents?: {
@@ -115,7 +106,7 @@ export interface ReadyEventData {
version: number;
};
user_guild_settings?: {
- entries: UserGuildSettings[];
+ entries: ReadyUserGuildSettingsEntries[];
version: number;
partial: boolean;
};
@@ -127,6 +118,17 @@ export interface ReadyEventData {
// probably all users who the user is in contact with
users?: PublicUser[];
sessions: unknown[];
+ api_code_version: number;
+ tutorial: number | null;
+ resume_gateway_url: string;
+ session_type: string;
+ auth_session_id_hash: string;
+ required_action?:
+ | "REQUIRE_VERIFIED_EMAIL"
+ | "REQUIRE_VERIFIED_PHONE"
+ | "REQUIRE_CAPTCHA" // TODO: allow these to be triggered
+ | "TOS_UPDATE_ACKNOWLEDGMENT"
+ | "AGREEMENTS";
}
export interface ReadyEvent extends Event {
@@ -581,6 +583,7 @@ export type EventData =
export enum EVENTEnum {
Ready = "READY",
+ ReadySupplemental = "READY_SUPPLEMENTAL",
ChannelCreate = "CHANNEL_CREATE",
ChannelUpdate = "CHANNEL_UPDATE",
ChannelDelete = "CHANNEL_DELETE",
diff --git a/src/util/interfaces/GuildWelcomeScreen.ts b/src/util/interfaces/GuildWelcomeScreen.ts
new file mode 100644
index 00000000..38b6061b
--- /dev/null
+++ b/src/util/interfaces/GuildWelcomeScreen.ts
@@ -0,0 +1,10 @@
+export interface GuildWelcomeScreen {
+ enabled: boolean;
+ description: string;
+ welcome_channels: {
+ description: string;
+ emoji_id?: string;
+ emoji_name?: string;
+ channel_id: string;
+ }[];
+}
diff --git a/src/util/interfaces/index.ts b/src/util/interfaces/index.ts
index c6a00458..6620ba32 100644
--- a/src/util/interfaces/index.ts
+++ b/src/util/interfaces/index.ts
@@ -19,6 +19,7 @@
export * from "./Activity";
export * from "./ConnectedAccount";
export * from "./Event";
+export * from "./GuildWelcomeScreen";
export * from "./Interaction";
export * from "./Presence";
export * from "./Status";
diff --git a/src/util/schemas/AckBulkSchema.ts b/src/util/schemas/AckBulkSchema.ts
index cf6dc597..5604c2fc 100644
--- a/src/util/schemas/AckBulkSchema.ts
+++ b/src/util/schemas/AckBulkSchema.ts
@@ -17,11 +17,9 @@
*/
export interface AckBulkSchema {
- read_states: [
- {
- channel_id: string;
- message_id: string;
- read_state_type: number; // WHat is this?
- },
- ];
+ read_states: {
+ channel_id: string;
+ message_id: string;
+ read_state_type: number; // WHat is this?
+ }[];
}
diff --git a/src/util/schemas/ConnectedAccountSchema.ts b/src/util/schemas/ConnectedAccountSchema.ts
index fe808a35..5fd05b71 100644
--- a/src/util/schemas/ConnectedAccountSchema.ts
+++ b/src/util/schemas/ConnectedAccountSchema.ts
@@ -30,7 +30,7 @@ export interface ConnectedAccountSchema {
verified?: boolean;
visibility?: number;
integrations?: string[];
- metadata_?: any;
+ metadata_?: unknown;
metadata_visibility?: number;
two_way_link?: boolean;
}
diff --git a/src/util/schemas/ConnectionCallbackSchema.ts b/src/util/schemas/ConnectionCallbackSchema.ts
index eb86c087..b66bfe20 100644
--- a/src/util/schemas/ConnectionCallbackSchema.ts
+++ b/src/util/schemas/ConnectionCallbackSchema.ts
@@ -21,5 +21,5 @@ export interface ConnectionCallbackSchema {
state: string;
insecure: boolean;
friend_sync: boolean;
- openid_params?: any; // TODO: types
+ openid_params?: unknown; // TODO: types
}
diff --git a/src/util/schemas/IdentifySchema.ts b/src/util/schemas/IdentifySchema.ts
index fb48c2a4..0619dacc 100644
--- a/src/util/schemas/IdentifySchema.ts
+++ b/src/util/schemas/IdentifySchema.ts
@@ -66,6 +66,7 @@ export const IdentifySchema = {
$private_channels_version: Number,
$guild_versions: Object,
$api_code_version: Number,
+ $initial_guild_id: String,
},
$clientState: {
$guildHashes: Object,
@@ -75,6 +76,7 @@ export const IdentifySchema = {
$userGuildSettingsVersion: undefined,
$guildVersions: Object,
$apiCodeVersion: Number,
+ $initialGuildId: String,
},
$v: Number,
$version: Number,
@@ -109,7 +111,11 @@ export interface IdentifySchema {
compress?: boolean;
large_threshold?: number;
largeThreshold?: number;
- shard?: [bigint, bigint];
+ /**
+ * @minItems 2
+ * @maxItems 2
+ */
+ shard?: bigint[]; // puyo: changed from [bigint, bigint] because it breaks openapi
guild_subscriptions?: boolean;
capabilities?: number;
client_state?: {
@@ -122,6 +128,7 @@ export interface IdentifySchema {
private_channels_version?: number;
guild_versions?: unknown;
api_code_version?: number;
+ initial_guild_id?: string;
};
clientState?: {
guildHashes?: unknown;
@@ -131,6 +138,7 @@ export interface IdentifySchema {
useruserGuildSettingsVersion?: number;
guildVersions?: unknown;
apiCodeVersion?: number;
+ initialGuildId?: string;
};
v?: number;
}
diff --git a/src/util/schemas/LazyRequestSchema.ts b/src/util/schemas/LazyRequestSchema.ts
index 63e67416..ee52d66c 100644
--- a/src/util/schemas/LazyRequestSchema.ts
+++ b/src/util/schemas/LazyRequestSchema.ts
@@ -18,7 +18,14 @@
export interface LazyRequestSchema {
guild_id: string;
- channels?: Record<string, [number, number][]>;
+ channels?: {
+ /**
+ * @items.type integer
+ * @minItems 2
+ * @maxItems 2
+ */
+ [key: string]: number[][]; // puyo: changed from [number, number] because it breaks openapi
+ };
activities?: boolean;
threads?: boolean;
typing?: true;
diff --git a/src/util/schemas/LoginResponse.ts b/src/util/schemas/LoginResponse.ts
new file mode 100644
index 00000000..faf3f769
--- /dev/null
+++ b/src/util/schemas/LoginResponse.ts
@@ -0,0 +1,14 @@
+import { TokenResponse } from "./responses";
+
+export interface MFAResponse {
+ ticket: string;
+ mfa: true;
+ sms: false; // TODO
+ token: null;
+}
+
+export interface WebAuthnResponse extends MFAResponse {
+ webauthn: string;
+}
+
+export type LoginResponse = TokenResponse | MFAResponse | WebAuthnResponse;
diff --git a/src/util/schemas/MemberChangeProfileSchema.ts b/src/util/schemas/MemberChangeProfileSchema.ts
index e955a0f1..d2d1481d 100644
--- a/src/util/schemas/MemberChangeProfileSchema.ts
+++ b/src/util/schemas/MemberChangeProfileSchema.ts
@@ -21,8 +21,7 @@ export interface MemberChangeProfileSchema {
nick?: string;
bio?: string;
pronouns?: string;
-
- /*
+ /**
* @items.type integer
*/
theme_colors?: [number, number];
diff --git a/src/util/schemas/MessageCreateSchema.ts b/src/util/schemas/MessageCreateSchema.ts
index 45cd735e..7e130751 100644
--- a/src/util/schemas/MessageCreateSchema.ts
+++ b/src/util/schemas/MessageCreateSchema.ts
@@ -29,7 +29,7 @@ export interface MessageCreateSchema {
nonce?: string;
channel_id?: string;
tts?: boolean;
- flags?: string;
+ flags?: number;
embeds?: Embed[];
embed?: Embed;
// TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object)
diff --git a/src/util/schemas/RegisterSchema.ts b/src/util/schemas/RegisterSchema.ts
index f6c99b18..7b7de9c7 100644
--- a/src/util/schemas/RegisterSchema.ts
+++ b/src/util/schemas/RegisterSchema.ts
@@ -42,4 +42,8 @@ export interface RegisterSchema {
captcha_key?: string;
promotional_email_opt_in?: boolean;
+
+ // part of pomelo
+ unique_username_registration?: boolean;
+ global_name?: string;
}
diff --git a/src/util/schemas/UserGuildSettingsSchema.ts b/src/util/schemas/UserGuildSettingsSchema.ts
index c295f767..82edae9c 100644
--- a/src/util/schemas/UserGuildSettingsSchema.ts
+++ b/src/util/schemas/UserGuildSettingsSchema.ts
@@ -16,12 +16,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { UserGuildSettings, ChannelOverride } from "@spacebar/util";
+import { ChannelOverride, UserGuildSettings } from "@spacebar/util";
// This sucks. I would use a DeepPartial, my own or typeorms, but they both generate inncorect schema
export interface UserGuildSettingsSchema
extends Partial<Omit<UserGuildSettings, "channel_overrides">> {
channel_overrides?: {
- [channel_id: string]: Partial<ChannelOverride>;
+ [channel_id: string]: ChannelOverride;
};
}
diff --git a/src/util/schemas/UserNoteUpdateSchema.ts b/src/util/schemas/UserNoteUpdateSchema.ts
new file mode 100644
index 00000000..0a731279
--- /dev/null
+++ b/src/util/schemas/UserNoteUpdateSchema.ts
@@ -0,0 +1,3 @@
+export interface UserNoteUpdateSchema {
+ note: string;
+}
diff --git a/src/util/schemas/UserProfileModifySchema.ts b/src/util/schemas/UserProfileModifySchema.ts
index d49fe326..3dea257a 100644
--- a/src/util/schemas/UserProfileModifySchema.ts
+++ b/src/util/schemas/UserProfileModifySchema.ts
@@ -21,8 +21,7 @@ export interface UserProfileModifySchema {
accent_color?: number | null;
banner?: string | null;
pronouns?: string;
-
- /*
+ /**
* @items.type integer
*/
theme_colors?: [number, number];
diff --git a/src/util/schemas/UserProfileResponse.ts b/src/util/schemas/UserProfileResponse.ts
deleted file mode 100644
index 699d6a29..00000000
--- a/src/util/schemas/UserProfileResponse.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
- Copyright (C) 2023 Spacebar and Spacebar Contributors
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
-
-import { PublicConnectedAccount, UserPublic } from "..";
-
-export interface UserProfileResponse {
- user: UserPublic;
- connected_accounts: PublicConnectedAccount;
- premium_guild_since?: Date;
- premium_since?: Date;
-}
diff --git a/src/util/schemas/UserRelationsResponse.ts b/src/util/schemas/UserRelationsResponse.ts
deleted file mode 100644
index 38507420..00000000
--- a/src/util/schemas/UserRelationsResponse.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
- Copyright (C) 2023 Spacebar and Spacebar Contributors
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
-
-export interface UserRelationsResponse {
- object: {
- id?: string;
- username?: string;
- avatar?: string;
- discriminator?: string;
- public_flags?: number;
- };
-}
diff --git a/src/util/schemas/VoiceStateUpdateSchema.ts b/src/util/schemas/VoiceStateUpdateSchema.ts
index a7d9f9d7..fda073e9 100644
--- a/src/util/schemas/VoiceStateUpdateSchema.ts
+++ b/src/util/schemas/VoiceStateUpdateSchema.ts
@@ -26,6 +26,7 @@ export interface VoiceStateUpdateSchema {
preferred_region?: string;
request_to_speak_timestamp?: Date;
suppress?: boolean;
+ flags?: number;
}
export const VoiceStateUpdateSchema = {
@@ -37,4 +38,5 @@ export const VoiceStateUpdateSchema = {
$preferred_region: String,
$request_to_speak_timestamp: Date,
$suppress: Boolean,
+ $flags: Number,
};
diff --git a/src/util/schemas/WebAuthnSchema.ts b/src/util/schemas/WebAuthnSchema.ts
index 652cda34..3f5e0da7 100644
--- a/src/util/schemas/WebAuthnSchema.ts
+++ b/src/util/schemas/WebAuthnSchema.ts
@@ -28,9 +28,9 @@ export interface CreateWebAuthnCredentialSchema {
ticket: string;
}
-export type WebAuthnPostSchema = Partial<
- GenerateWebAuthnCredentialsSchema | CreateWebAuthnCredentialSchema
->;
+export type WebAuthnPostSchema =
+ | GenerateWebAuthnCredentialsSchema
+ | CreateWebAuthnCredentialSchema;
export interface WebAuthnTotpSchema {
code: string;
diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts
index 2d254752..44a504cd 100644
--- a/src/util/schemas/index.ts
+++ b/src/util/schemas/index.ts
@@ -69,6 +69,7 @@ export * from "./TotpSchema";
export * from "./UserDeleteSchema";
export * from "./UserGuildSettingsSchema";
export * from "./UserModifySchema";
+export * from "./UserNoteUpdateSchema";
export * from "./UserProfileModifySchema";
export * from "./UserSettingsSchema";
export * from "./Validator";
@@ -79,7 +80,4 @@ export * from "./VoiceVideoSchema";
export * from "./WebAuthnSchema";
export * from "./WebhookCreateSchema";
export * from "./WidgetModifySchema";
-export * from "./UserRelationsResponse";
-export * from "./GatewayResponse";
-export * from "./GatewayBotResponse";
-export * from "./UserProfileResponse";
+export * from "./responses";
diff --git a/src/util/schemas/responses/APIErrorOrCaptchaResponse.ts b/src/util/schemas/responses/APIErrorOrCaptchaResponse.ts
new file mode 100644
index 00000000..c9a0e5be
--- /dev/null
+++ b/src/util/schemas/responses/APIErrorOrCaptchaResponse.ts
@@ -0,0 +1,6 @@
+import { APIErrorResponse } from "./APIErrorResponse";
+import { CaptchaRequiredResponse } from "./CaptchaRequiredResponse";
+
+export type APIErrorOrCaptchaResponse =
+ | CaptchaRequiredResponse
+ | APIErrorResponse;
diff --git a/src/util/schemas/responses/APIErrorResponse.ts b/src/util/schemas/responses/APIErrorResponse.ts
new file mode 100644
index 00000000..25bb9504
--- /dev/null
+++ b/src/util/schemas/responses/APIErrorResponse.ts
@@ -0,0 +1,12 @@
+export interface APIErrorResponse {
+ code: number;
+ message: string;
+ errors: {
+ [key: string]: {
+ _errors: {
+ message: string;
+ code: string;
+ }[];
+ };
+ };
+}
diff --git a/src/util/schemas/responses/BackupCodesChallengeResponse.ts b/src/util/schemas/responses/BackupCodesChallengeResponse.ts
new file mode 100644
index 00000000..5473ad1f
--- /dev/null
+++ b/src/util/schemas/responses/BackupCodesChallengeResponse.ts
@@ -0,0 +1,4 @@
+export interface BackupCodesChallengeResponse {
+ nonce: string;
+ regenerate_nonce: string;
+}
diff --git a/src/util/schemas/responses/CaptchaRequiredResponse.ts b/src/util/schemas/responses/CaptchaRequiredResponse.ts
new file mode 100644
index 00000000..9f7f02ff
--- /dev/null
+++ b/src/util/schemas/responses/CaptchaRequiredResponse.ts
@@ -0,0 +1,5 @@
+export interface CaptchaRequiredResponse {
+ captcha_key: string;
+ captcha_sitekey: string;
+ captcha_service: string;
+}
diff --git a/src/util/schemas/responses/DiscoverableGuildsResponse.ts b/src/util/schemas/responses/DiscoverableGuildsResponse.ts
new file mode 100644
index 00000000..2a9fb1bd
--- /dev/null
+++ b/src/util/schemas/responses/DiscoverableGuildsResponse.ts
@@ -0,0 +1,8 @@
+import { Guild } from "../../entities";
+
+export interface DiscoverableGuildsResponse {
+ total: number;
+ guilds: Guild[];
+ offset: number;
+ limit: number;
+}
diff --git a/src/util/schemas/responses/GatewayBotResponse.ts b/src/util/schemas/responses/GatewayBotResponse.ts
new file mode 100644
index 00000000..30f1f57f
--- /dev/null
+++ b/src/util/schemas/responses/GatewayBotResponse.ts
@@ -0,0 +1,10 @@
+export interface GatewayBotResponse {
+ url: string;
+ shards: number;
+ session_start_limit: {
+ total: number;
+ remaining: number;
+ reset_after: number;
+ max_concurrency: number;
+ };
+}
diff --git a/src/util/schemas/responses/GatewayResponse.ts b/src/util/schemas/responses/GatewayResponse.ts
new file mode 100644
index 00000000..e909f7bd
--- /dev/null
+++ b/src/util/schemas/responses/GatewayResponse.ts
@@ -0,0 +1,3 @@
+export interface GatewayResponse {
+ url: string;
+}
diff --git a/src/util/schemas/responses/GenerateRegistrationTokensResponse.ts b/src/util/schemas/responses/GenerateRegistrationTokensResponse.ts
new file mode 100644
index 00000000..8816eabf
--- /dev/null
+++ b/src/util/schemas/responses/GenerateRegistrationTokensResponse.ts
@@ -0,0 +1,3 @@
+export interface GenerateRegistrationTokensResponse {
+ tokens: string[];
+}
diff --git a/src/util/schemas/responses/GuildBansResponse.ts b/src/util/schemas/responses/GuildBansResponse.ts
new file mode 100644
index 00000000..876a4bc4
--- /dev/null
+++ b/src/util/schemas/responses/GuildBansResponse.ts
@@ -0,0 +1,10 @@
+export interface GuildBansResponse {
+ reason: string;
+ user: {
+ username: string;
+ discriminator: string;
+ id: string;
+ avatar: string | null;
+ public_flags: number;
+ };
+}
diff --git a/src/util/schemas/responses/GuildCreateResponse.ts b/src/util/schemas/responses/GuildCreateResponse.ts
new file mode 100644
index 00000000..8185cb86
--- /dev/null
+++ b/src/util/schemas/responses/GuildCreateResponse.ts
@@ -0,0 +1,3 @@
+export interface GuildCreateResponse {
+ id: string;
+}
diff --git a/src/util/schemas/responses/GuildDiscoveryRequirements.ts b/src/util/schemas/responses/GuildDiscoveryRequirements.ts
new file mode 100644
index 00000000..731976f7
--- /dev/null
+++ b/src/util/schemas/responses/GuildDiscoveryRequirements.ts
@@ -0,0 +1,23 @@
+export interface GuildDiscoveryRequirementsResponse {
+ uild_id: string;
+ safe_environment: boolean;
+ healthy: boolean;
+ health_score_pending: boolean;
+ size: boolean;
+ nsfw_properties: unknown;
+ protected: boolean;
+ sufficient: boolean;
+ sufficient_without_grace_period: boolean;
+ valid_rules_channel: boolean;
+ retention_healthy: boolean;
+ engagement_healthy: boolean;
+ age: boolean;
+ minimum_age: number;
+ health_score: {
+ avg_nonnew_participators: number;
+ avg_nonnew_communicators: number;
+ num_intentful_joiners: number;
+ perc_ret_w1_intentful: number;
+ };
+ minimum_size: number;
+}
diff --git a/src/util/schemas/responses/GuildMessagesSearchResponse.ts b/src/util/schemas/responses/GuildMessagesSearchResponse.ts
new file mode 100644
index 00000000..0b6248b7
--- /dev/null
+++ b/src/util/schemas/responses/GuildMessagesSearchResponse.ts
@@ -0,0 +1,32 @@
+import {
+ Attachment,
+ Embed,
+ MessageType,
+ PublicUser,
+ Role,
+} from "../../entities";
+
+export interface GuildMessagesSearchMessage {
+ id: string;
+ type: MessageType;
+ content?: string;
+ channel_id: string;
+ author: PublicUser;
+ attachments: Attachment[];
+ embeds: Embed[];
+ mentions: PublicUser[];
+ mention_roles: Role[];
+ pinned: boolean;
+ mention_everyone?: boolean;
+ tts: boolean;
+ timestamp: string;
+ edited_timestamp: string | null;
+ flags: number;
+ components: unknown[];
+ hit: true;
+}
+
+export interface GuildMessagesSearchResponse {
+ messages: GuildMessagesSearchMessage[];
+ total_results: number;
+}
diff --git a/src/util/schemas/responses/GuildPruneResponse.ts b/src/util/schemas/responses/GuildPruneResponse.ts
new file mode 100644
index 00000000..fb1abb89
--- /dev/null
+++ b/src/util/schemas/responses/GuildPruneResponse.ts
@@ -0,0 +1,7 @@
+export interface GuildPruneResponse {
+ pruned: number;
+}
+
+export interface GuildPurgeResponse {
+ purged: number;
+}
diff --git a/src/util/schemas/responses/GuildRecommendationsResponse.ts b/src/util/schemas/responses/GuildRecommendationsResponse.ts
new file mode 100644
index 00000000..211670a6
--- /dev/null
+++ b/src/util/schemas/responses/GuildRecommendationsResponse.ts
@@ -0,0 +1,6 @@
+import { Guild } from "../../entities";
+
+export interface GuildRecommendationsResponse {
+ recommended_guilds: Guild[];
+ load_id: string;
+}
diff --git a/src/util/schemas/responses/GuildVanityUrl.ts b/src/util/schemas/responses/GuildVanityUrl.ts
new file mode 100644
index 00000000..ff37bf4e
--- /dev/null
+++ b/src/util/schemas/responses/GuildVanityUrl.ts
@@ -0,0 +1,17 @@
+export interface GuildVanityUrl {
+ code: string;
+ uses: number;
+}
+
+export interface GuildVanityUrlNoInvite {
+ code: null;
+}
+
+export type GuildVanityUrlResponse =
+ | GuildVanityUrl
+ | GuildVanityUrl[]
+ | GuildVanityUrlNoInvite;
+
+export interface GuildVanityUrlCreateResponse {
+ code: string;
+}
diff --git a/src/util/schemas/responses/GuildVoiceRegionsResponse.ts b/src/util/schemas/responses/GuildVoiceRegionsResponse.ts
new file mode 100644
index 00000000..8190d5fd
--- /dev/null
+++ b/src/util/schemas/responses/GuildVoiceRegionsResponse.ts
@@ -0,0 +1,7 @@
+export interface GuildVoiceRegion {
+ id: string;
+ name: string;
+ custom: boolean;
+ deprecated: boolean;
+ optimal: boolean;
+}
diff --git a/src/util/schemas/responses/GuildWidgetJsonResponse.ts b/src/util/schemas/responses/GuildWidgetJsonResponse.ts
new file mode 100644
index 00000000..ef85dd08
--- /dev/null
+++ b/src/util/schemas/responses/GuildWidgetJsonResponse.ts
@@ -0,0 +1,21 @@
+import { ClientStatus } from "../../interfaces";
+
+export interface GuildWidgetJsonResponse {
+ id: string;
+ name: string;
+ instant_invite: string;
+ channels: {
+ id: string;
+ name: string;
+ position: number;
+ }[];
+ members: {
+ id: string;
+ username: string;
+ discriminator: string;
+ avatar: string | null;
+ status: ClientStatus;
+ avatar_url: string;
+ }[];
+ presence_count: number;
+}
diff --git a/src/util/schemas/responses/GuildWidgetSettingsResponse.ts b/src/util/schemas/responses/GuildWidgetSettingsResponse.ts
new file mode 100644
index 00000000..3c6b45ce
--- /dev/null
+++ b/src/util/schemas/responses/GuildWidgetSettingsResponse.ts
@@ -0,0 +1,6 @@
+import { Snowflake } from "../../util";
+
+export interface GuildWidgetSettingsResponse {
+ enabled: boolean;
+ channel_id: Snowflake | null;
+}
diff --git a/src/util/schemas/responses/InstanceDomainsResponse.ts b/src/util/schemas/responses/InstanceDomainsResponse.ts
new file mode 100644
index 00000000..60367492
--- /dev/null
+++ b/src/util/schemas/responses/InstanceDomainsResponse.ts
@@ -0,0 +1,6 @@
+export interface InstanceDomainsResponse {
+ cdn: string;
+ gateway: string;
+ defaultApiVersion: string;
+ apiEndpoint: string;
+}
diff --git a/src/util/schemas/responses/InstancePingResponse.ts b/src/util/schemas/responses/InstancePingResponse.ts
new file mode 100644
index 00000000..5f1a9488
--- /dev/null
+++ b/src/util/schemas/responses/InstancePingResponse.ts
@@ -0,0 +1,13 @@
+export interface InstancePingResponse {
+ ping: "pong!";
+ instance: {
+ id: string;
+ name: string;
+ description: string | null;
+ image: string | null;
+ correspondenceEmail: string | null;
+ correspondenceUserID: string | null;
+ frontPage: string | null;
+ tosPage: string | null;
+ };
+}
diff --git a/src/util/schemas/responses/InstanceStatsResponse.ts b/src/util/schemas/responses/InstanceStatsResponse.ts
new file mode 100644
index 00000000..d24fd434
--- /dev/null
+++ b/src/util/schemas/responses/InstanceStatsResponse.ts
@@ -0,0 +1,8 @@
+export interface InstanceStatsResponse {
+ counts: {
+ user: number;
+ guild: number;
+ message: number;
+ members: number;
+ };
+}
diff --git a/src/util/schemas/responses/LocationMetadataResponse.ts b/src/util/schemas/responses/LocationMetadataResponse.ts
new file mode 100644
index 00000000..55337557
--- /dev/null
+++ b/src/util/schemas/responses/LocationMetadataResponse.ts
@@ -0,0 +1,5 @@
+export interface LocationMetadataResponse {
+ consent_required: boolean;
+ country_code: string;
+ promotional_email_opt_in: { required: true; pre_checked: false };
+}
diff --git a/src/util/schemas/responses/MemberJoinGuildResponse.ts b/src/util/schemas/responses/MemberJoinGuildResponse.ts
new file mode 100644
index 00000000..d7b39d10
--- /dev/null
+++ b/src/util/schemas/responses/MemberJoinGuildResponse.ts
@@ -0,0 +1,8 @@
+import { Emoji, Guild, Role, Sticker } from "../../entities";
+
+export interface MemberJoinGuildResponse {
+ guild: Guild;
+ emojis: Emoji[];
+ roles: Role[];
+ stickers: Sticker[];
+}
diff --git a/src/util/schemas/responses/OAuthAuthorizeResponse.ts b/src/util/schemas/responses/OAuthAuthorizeResponse.ts
new file mode 100644
index 00000000..60d6d2e2
--- /dev/null
+++ b/src/util/schemas/responses/OAuthAuthorizeResponse.ts
@@ -0,0 +1,3 @@
+export interface OAuthAuthorizeResponse {
+ location: string;
+}
diff --git a/src/util/schemas/responses/Tenor.ts b/src/util/schemas/responses/Tenor.ts
new file mode 100644
index 00000000..9dddf9d0
--- /dev/null
+++ b/src/util/schemas/responses/Tenor.ts
@@ -0,0 +1,72 @@
+export enum TenorMediaTypes {
+ gif,
+ mediumgif,
+ tinygif,
+ nanogif,
+ mp4,
+ loopedmp4,
+ tinymp4,
+ nanomp4,
+ webm,
+ tinywebm,
+ nanowebm,
+}
+
+export type TenorMedia = {
+ preview: string;
+ url: string;
+ dims: number[];
+ size: number;
+};
+
+export type TenorGif = {
+ created: number;
+ hasaudio: boolean;
+ id: string;
+ media: { [type in keyof typeof TenorMediaTypes]: TenorMedia }[];
+ tags: string[];
+ title: string;
+ itemurl: string;
+ hascaption: boolean;
+ url: string;
+};
+
+export type TenorCategory = {
+ searchterm: string;
+ path: string;
+ image: string;
+ name: string;
+};
+
+export type TenorCategoriesResults = {
+ tags: TenorCategory[];
+};
+
+export type TenorTrendingResults = {
+ next: string;
+ results: TenorGif[];
+ locale: string;
+};
+
+export type TenorSearchResults = {
+ next: string;
+ results: TenorGif[];
+};
+
+export interface TenorGifResponse {
+ id: string;
+ title: string;
+ url: string;
+ src: string;
+ gif_src: string;
+ width: number;
+ height: number;
+ preview: string;
+}
+
+export interface TenorTrendingResponse {
+ categories: TenorCategoriesResults;
+ gifs: TenorGifResponse[];
+}
+
+export type TenorGifsResponse = TenorGifResponse[];
diff --git a/src/util/schemas/responses/TokenResponse.ts b/src/util/schemas/responses/TokenResponse.ts
new file mode 100644
index 00000000..7e93055a
--- /dev/null
+++ b/src/util/schemas/responses/TokenResponse.ts
@@ -0,0 +1,15 @@
+import { BackupCode, UserSettings } from "../../entities";
+
+export interface TokenResponse {
+ token: string;
+ settings: UserSettings;
+}
+
+export interface TokenOnlyResponse {
+ token: string;
+}
+
+export interface TokenWithBackupCodesResponse {
+ token: string;
+ backup_codes: BackupCode[];
+}
diff --git a/src/util/schemas/responses/TypedResponses.ts b/src/util/schemas/responses/TypedResponses.ts
new file mode 100644
index 00000000..4349b93c
--- /dev/null
+++ b/src/util/schemas/responses/TypedResponses.ts
@@ -0,0 +1,88 @@
+import { GeneralConfiguration, LimitsConfiguration } from "../../config";
+import { DmChannelDTO } from "../../dtos";
+import {
+ Application,
+ BackupCode,
+ Categories,
+ Channel,
+ Emoji,
+ Guild,
+ Invite,
+ Member,
+ Message,
+ PrivateUser,
+ PublicMember,
+ PublicUser,
+ Role,
+ Sticker,
+ StickerPack,
+ Template,
+ Webhook,
+} from "../../entities";
+import { GuildVoiceRegion } from "./GuildVoiceRegionsResponse";
+
+// removes internal properties from the guild class
+export type APIGuild = Omit<
+ Guild,
+ | "afk_channel"
+ | "template"
+ | "owner"
+ | "public_updates_channel"
+ | "rules_channel"
+ | "system_channel"
+ | "widget_channel"
+>;
+
+export type APIPublicUser = PublicUser;
+export type APIPrivateUser = PrivateUser;
+
+export type APIGuildArray = APIGuild[];
+
+export type APIDMChannelArray = DmChannelDTO[];
+
+export type APIBackupCodeArray = BackupCode[];
+
+export interface UserUpdateResponse extends APIPrivateUser {
+ newToken?: string;
+}
+
+export type ApplicationDetectableResponse = unknown[];
+
+export type ApplicationEntitlementsResponse = unknown[];
+
+export type ApplicationSkusResponse = unknown[];
+
+export type APIApplicationArray = Application[];
+
+export type APIInviteArray = Invite[];
+
+export type APIMessageArray = Message[];
+
+export type APIWebhookArray = Webhook[];
+
+export type APIDiscoveryCategoryArray = Categories[];
+
+export type APIGeneralConfiguration = GeneralConfiguration;
+
+export type APIChannelArray = Channel[];
+
+export type APIEmojiArray = Emoji[];
+
+export type APIMemberArray = Member[];
+export type APIPublicMember = PublicMember;
+
+export interface APIGuildWithJoinedAt extends Guild {
+ joined_at: string;
+}
+
+export type APIRoleArray = Role[];
+
+export type APIStickerArray = Sticker[];
+
+export type APITemplateArray = Template[];
+
+export type APIGuildVoiceRegion = GuildVoiceRegion[];
+
+export type APILimitsConfiguration = LimitsConfiguration;
+
+export type APIStickerPackArray = StickerPack[];
diff --git a/src/util/schemas/responses/UpdatesResponse.ts b/src/util/schemas/responses/UpdatesResponse.ts
new file mode 100644
index 00000000..6b8566f4
--- /dev/null
+++ b/src/util/schemas/responses/UpdatesResponse.ts
@@ -0,0 +1,6 @@
+export interface UpdatesResponse {
+ name: string;
+ pub_date: string;
+ url: string;
+ notes: string | null;
+}
diff --git a/src/util/schemas/responses/UserNoteResponse.ts b/src/util/schemas/responses/UserNoteResponse.ts
new file mode 100644
index 00000000..b142811e
--- /dev/null
+++ b/src/util/schemas/responses/UserNoteResponse.ts
@@ -0,0 +1,5 @@
+export interface UserNoteResponse {
+ note: string;
+ note_user_id: string;
+ user_id: string;
+}
diff --git a/src/util/schemas/responses/UserProfileResponse.ts b/src/util/schemas/responses/UserProfileResponse.ts
new file mode 100644
index 00000000..eba7cbcc
--- /dev/null
+++ b/src/util/schemas/responses/UserProfileResponse.ts
@@ -0,0 +1,37 @@
+import {
+ Member,
+ PublicConnectedAccount,
+ PublicMember,
+ PublicUser,
+ User,
+} from "@spacebar/util";
+
+export type MutualGuild = {
+ id: string;
+ nick?: string;
+};
+
+export type PublicMemberProfile = Pick<
+ Member,
+ "banner" | "bio" | "guild_id"
+> & {
+ accent_color: null; // TODO
+};
+
+export type UserProfile = Pick<
+ User,
+ "bio" | "accent_color" | "banner" | "pronouns" | "theme_colors"
+>;
+
+export interface UserProfileResponse {
+ user: PublicUser;
+ connected_accounts: PublicConnectedAccount;
+ premium_guild_since?: Date;
+ premium_since?: Date;
+ mutual_guilds: MutualGuild[];
+ premium_type: number;
+ profile_themes_experiment_bucket: number;
+ user_profile: UserProfile;
+ guild_member?: PublicMember;
+ guild_member_profile?: PublicMemberProfile;
+}
diff --git a/src/util/schemas/responses/UserRelationsResponse.ts b/src/util/schemas/responses/UserRelationsResponse.ts
new file mode 100644
index 00000000..e784cafb
--- /dev/null
+++ b/src/util/schemas/responses/UserRelationsResponse.ts
@@ -0,0 +1,7 @@
+import { User } from "@spacebar/util";
+
+export type UserRelationsResponse = (Pick<User, "id"> &
+ Pick<User, "username"> &
+ Pick<User, "discriminator"> &
+ Pick<User, "avatar"> &
+ Pick<User, "public_flags">)[];
diff --git a/src/util/schemas/responses/UserRelationshipsResponse.ts b/src/util/schemas/responses/UserRelationshipsResponse.ts
new file mode 100644
index 00000000..dff2f118
--- /dev/null
+++ b/src/util/schemas/responses/UserRelationshipsResponse.ts
@@ -0,0 +1,8 @@
+import { PublicUser, RelationshipType } from "../../entities";
+
+export interface UserRelationshipsResponse {
+ id: string;
+ type: RelationshipType;
+ nickname: null;
+ user: PublicUser;
+}
diff --git a/src/util/schemas/responses/WebAuthnCreateResponse.ts b/src/util/schemas/responses/WebAuthnCreateResponse.ts
new file mode 100644
index 00000000..9aa9e206
--- /dev/null
+++ b/src/util/schemas/responses/WebAuthnCreateResponse.ts
@@ -0,0 +1,4 @@
+export interface WebAuthnCreateResponse {
+ name: string;
+ id: string;
+}
diff --git a/src/util/schemas/responses/WebhookCreateResponse.ts b/src/util/schemas/responses/WebhookCreateResponse.ts
new file mode 100644
index 00000000..ae142632
--- /dev/null
+++ b/src/util/schemas/responses/WebhookCreateResponse.ts
@@ -0,0 +1,6 @@
+import { User, Webhook } from "../../entities";
+
+export interface WebhookCreateResponse {
+ user: User;
+ hook: Webhook;
+}
diff --git a/src/util/schemas/responses/index.ts b/src/util/schemas/responses/index.ts
new file mode 100644
index 00000000..d8b7fd57
--- /dev/null
+++ b/src/util/schemas/responses/index.ts
@@ -0,0 +1,34 @@
+export * from "./APIErrorOrCaptchaResponse";
+export * from "./APIErrorResponse";
+export * from "./BackupCodesChallengeResponse";
+export * from "./CaptchaRequiredResponse";
+export * from "./DiscoverableGuildsResponse";
+export * from "./GatewayBotResponse";
+export * from "./GatewayResponse";
+export * from "./GenerateRegistrationTokensResponse";
+export * from "./GuildBansResponse";
+export * from "./GuildCreateResponse";
+export * from "./GuildDiscoveryRequirements";
+export * from "./GuildMessagesSearchResponse";
+export * from "./GuildPruneResponse";
+export * from "./GuildRecommendationsResponse";
+export * from "./GuildVanityUrl";
+export * from "./GuildVoiceRegionsResponse";
+export * from "./GuildWidgetJsonResponse";
+export * from "./GuildWidgetSettingsResponse";
+export * from "./InstanceDomainsResponse";
+export * from "./InstancePingResponse";
+export * from "./InstanceStatsResponse";
+export * from "./LocationMetadataResponse";
+export * from "./MemberJoinGuildResponse";
+export * from "./OAuthAuthorizeResponse";
+export * from "./Tenor";
+export * from "./TokenResponse";
+export * from "./TypedResponses";
+export * from "./UpdatesResponse";
+export * from "./UserNoteResponse";
+export * from "./UserProfileResponse";
+export * from "./UserRelationshipsResponse";
+export * from "./UserRelationsResponse";
+export * from "./WebAuthnCreateResponse";
+export * from "./WebhookCreateResponse";
diff --git a/src/util/util/Application.ts b/src/util/util/Application.ts
new file mode 100644
index 00000000..23019a7f
--- /dev/null
+++ b/src/util/util/Application.ts
@@ -0,0 +1,24 @@
+import { Request } from "express";
+import { Application, User } from "../entities";
+
+export async function createAppBotUser(app: Application, req: Request) {
+ const user = await User.register({
+ username: app.name,
+ password: undefined,
+ id: app.id,
+ req,
+ });
+
+ user.id = app.id;
+ user.premium_since = new Date();
+ user.bot = true;
+
+ await user.save();
+
+ // flags is NaN here?
+ app.assign({ bot: user, flags: app.flags || 0 });
+
+ await app.save();
+
+ return user;
+}
diff --git a/src/util/util/AutoUpdate.ts b/src/util/util/AutoUpdate.ts
index 1f90a41e..2af5cf1c 100644
--- a/src/util/util/AutoUpdate.ts
+++ b/src/util/util/AutoUpdate.ts
@@ -18,7 +18,7 @@
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";
diff --git a/src/util/util/Gifs.ts b/src/util/util/Gifs.ts
new file mode 100644
index 00000000..a5a5e64c
--- /dev/null
+++ b/src/util/util/Gifs.ts
@@ -0,0 +1,25 @@
+import { HTTPError } from "lambert-server";
+import { Config } from "./Config";
+import { TenorGif } from "..";
+
+export function parseGifResult(result: TenorGif) {
+ return {
+ id: result.id,
+ title: result.title,
+ url: result.itemurl,
+ src: result.media[0].mp4.url,
+ gif_src: result.media[0].gif.url,
+ width: result.media[0].mp4.dims[0],
+ height: result.media[0].mp4.dims[1],
+ preview: result.media[0].mp4.preview,
+ };
+}
+
+export function getGifApiKey() {
+ const { enabled, provider, apiKey } = Config.get().gif;
+ if (!enabled) throw new HTTPError(`Gifs are disabled`);
+ if (provider !== "tenor" || !apiKey)
+ throw new HTTPError(`${provider} gif provider not supported`);
+
+ return apiKey;
+}
diff --git a/src/util/util/JSON.ts b/src/util/util/JSON.ts
index 1c39b66e..c7dcf47e 100644
--- a/src/util/util/JSON.ts
+++ b/src/util/util/JSON.ts
@@ -27,6 +27,16 @@ const JSONReplacer = function (
return (this[key] as Date).toISOString().replace("Z", "+00:00");
}
+ // erlpack encoding doesn't call json.stringify,
+ // so our toJSON functions don't get called.
+ // manually call it here
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ if (this?.[key]?.toJSON)
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ this[key] = this[key].toJSON();
+
return value;
};
diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts
index 90310176..97bdec74 100644
--- a/src/util/util/Token.ts
+++ b/src/util/util/Token.ts
@@ -19,94 +19,69 @@
import jwt, { VerifyOptions } from "jsonwebtoken";
import { Config } from "./Config";
import { User } from "../entities";
+// TODO: dont use deprecated APIs lol
+import {
+ FindOptionsRelationByString,
+ FindOptionsSelectByString,
+} from "typeorm";
export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] };
export type UserTokenData = {
user: User;
- decoded: { id: string; iat: number };
+ decoded: { id: string; iat: number; email?: string };
};
-async function checkEmailToken(
- decoded: jwt.JwtPayload,
-): Promise<UserTokenData> {
- // eslint-disable-next-line no-async-promise-executor
- return new Promise(async (res, rej) => {
- if (!decoded.iat) return rej("Invalid Token"); // will never happen, just for typings.
-
- const user = await User.findOne({
- where: {
- email: decoded.email,
- },
- select: [
- "email",
- "id",
- "verified",
- "deleted",
- "disabled",
- "username",
- "data",
- ],
- });
-
- if (!user) return rej("Invalid Token");
-
- if (new Date().getTime() > decoded.iat * 1000 + 86400 * 1000)
- return rej("Invalid Token");
-
- // Using as here because we assert `id` and `iat` are in decoded.
- // TS just doesn't want to assume its there, though.
- return res({ decoded, user } as UserTokenData);
- });
-}
-
-export function checkToken(
+export const checkToken = (
token: string,
- jwtSecret: string,
- isEmailVerification = false,
-): Promise<UserTokenData> {
- return new Promise((res, rej) => {
- token = token.replace("Bot ", "");
- token = token.replace("Bearer ", "");
- /**
- in spacebar, even with instances that have bot distinction; we won't enforce "Bot" prefix,
- as we don't really have separate pathways for bots
- **/
-
- jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded) => {
- if (err || !decoded) return rej("Invalid Token");
- if (
- typeof decoded == "string" ||
- !("id" in decoded) ||
- !decoded.iat
- )
- return rej("Invalid Token"); // will never happen, just for typings.
-
- if (isEmailVerification) return res(checkEmailToken(decoded));
-
- const user = await User.findOne({
- where: { id: decoded.id },
- select: ["data", "bot", "disabled", "deleted", "rights"],
- });
-
- if (!user) return rej("Invalid Token");
-
- // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds
- if (
- decoded.iat * 1000 <
- new Date(user.data.valid_tokens_since).setSeconds(0, 0)
- )
- return rej("Invalid Token");
-
- if (user.disabled) return rej("User disabled");
- if (user.deleted) return rej("User not found");
-
- // Using as here because we assert `id` and `iat` are in decoded.
- // TS just doesn't want to assume its there, though.
- return res({ decoded, user } as UserTokenData);
- });
+ opts?: {
+ select?: FindOptionsSelectByString<User>;
+ relations?: FindOptionsRelationByString;
+ },
+): Promise<UserTokenData> =>
+ new Promise((resolve, reject) => {
+ token = token.replace("Bot ", ""); // there is no bot distinction in sb
+ token = token.replace("Bearer ", ""); // allow bearer tokens
+
+ jwt.verify(
+ token,
+ Config.get().security.jwtSecret,
+ JWTOptions,
+ async (err, out) => {
+ const decoded = out as UserTokenData["decoded"];
+ if (err || !decoded) return reject("Invalid Token");
+
+ const user = await User.findOne({
+ where: decoded.email
+ ? { email: decoded.email }
+ : { id: decoded.id },
+ select: [
+ ...(opts?.select || []),
+ "bot",
+ "disabled",
+ "deleted",
+ "rights",
+ "data",
+ ],
+ relations: opts?.relations,
+ });
+
+ if (!user) return reject("User not found");
+
+ // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds
+ if (
+ decoded.iat * 1000 <
+ new Date(user.data.valid_tokens_since).setSeconds(0, 0)
+ )
+ return reject("Invalid Token");
+
+ if (user.disabled) return reject("User disabled");
+ if (user.deleted) return reject("User not found");
+
+ return resolve({ decoded, user });
+ },
+ );
});
-}
export async function generateToken(id: string, email?: string) {
const iat = Math.floor(Date.now() / 1000);
diff --git a/src/util/util/index.ts b/src/util/util/index.ts
index 838239b7..10e09b5c 100644
--- a/src/util/util/index.ts
+++ b/src/util/util/index.ts
@@ -41,3 +41,5 @@ export * from "./String";
export * from "./Token";
export * from "./TraverseDirectory";
export * from "./WebAuthn";
+export * from "./Gifs";
+export * from "./Application";
|