summary refs log tree commit diff
path: root/src/util
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2023-07-28 08:24:15 +1000
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2023-07-28 08:24:15 +1000
commit8a3989c29776ad7eba8077bf7cc9c56e28b9b8c3 (patch)
treee71d68052e6bf5ddfad64c643a8fb2d04c2e183c /src/util
parentMerge branch 'master' into feat/refactorIdentify (diff)
parentMerge pull request #1075 from SpecificProtagonist/get_messages_around (diff)
downloadserver-8a3989c29776ad7eba8077bf7cc9c56e28b9b8c3.tar.xz
Merge branch 'master' into feat/refactorIdentify
Diffstat (limited to 'src/util')
-rw-r--r--src/util/config/types/GeneralConfiguration.ts1
-rw-r--r--src/util/config/types/SecurityConfiguration.ts2
-rw-r--r--src/util/config/types/subconfigurations/limits/RateLimits.ts4
-rw-r--r--src/util/entities/Channel.ts25
-rw-r--r--src/util/entities/Guild.ts44
-rw-r--r--src/util/entities/Member.ts10
-rw-r--r--src/util/entities/User.ts24
-rw-r--r--src/util/interfaces/Activity.ts2
-rw-r--r--src/util/interfaces/GuildWelcomeScreen.ts10
-rw-r--r--src/util/interfaces/index.ts1
-rw-r--r--src/util/schemas/AckBulkSchema.ts12
-rw-r--r--src/util/schemas/IdentifySchema.ts10
-rw-r--r--src/util/schemas/LazyRequestSchema.ts9
-rw-r--r--src/util/schemas/LoginResponse.ts14
-rw-r--r--src/util/schemas/MemberChangeProfileSchema.ts3
-rw-r--r--src/util/schemas/UserGuildSettingsSchema.ts4
-rw-r--r--src/util/schemas/UserNoteUpdateSchema.ts3
-rw-r--r--src/util/schemas/UserProfileModifySchema.ts3
-rw-r--r--src/util/schemas/UserProfileResponse.ts26
-rw-r--r--src/util/schemas/UserRelationsResponse.ts27
-rw-r--r--src/util/schemas/VoiceStateUpdateSchema.ts2
-rw-r--r--src/util/schemas/WebAuthnSchema.ts6
-rw-r--r--src/util/schemas/index.ts6
-rw-r--r--src/util/schemas/responses/APIErrorOrCaptchaResponse.ts6
-rw-r--r--src/util/schemas/responses/APIErrorResponse.ts12
-rw-r--r--src/util/schemas/responses/BackupCodesChallengeResponse.ts4
-rw-r--r--src/util/schemas/responses/CaptchaRequiredResponse.ts5
-rw-r--r--src/util/schemas/responses/DiscoverableGuildsResponse.ts8
-rw-r--r--src/util/schemas/responses/GatewayBotResponse.ts10
-rw-r--r--src/util/schemas/responses/GatewayResponse.ts3
-rw-r--r--src/util/schemas/responses/GenerateRegistrationTokensResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildBansResponse.ts10
-rw-r--r--src/util/schemas/responses/GuildCreateResponse.ts3
-rw-r--r--src/util/schemas/responses/GuildDiscoveryRequirements.ts23
-rw-r--r--src/util/schemas/responses/GuildMessagesSearchResponse.ts32
-rw-r--r--src/util/schemas/responses/GuildPruneResponse.ts7
-rw-r--r--src/util/schemas/responses/GuildRecommendationsResponse.ts6
-rw-r--r--src/util/schemas/responses/GuildVanityUrl.ts17
-rw-r--r--src/util/schemas/responses/GuildVoiceRegionsResponse.ts7
-rw-r--r--src/util/schemas/responses/GuildWidgetJsonResponse.ts21
-rw-r--r--src/util/schemas/responses/GuildWidgetSettingsResponse.ts6
-rw-r--r--src/util/schemas/responses/InstanceDomainsResponse.ts6
-rw-r--r--src/util/schemas/responses/InstancePingResponse.ts13
-rw-r--r--src/util/schemas/responses/InstanceStatsResponse.ts8
-rw-r--r--src/util/schemas/responses/LocationMetadataResponse.ts5
-rw-r--r--src/util/schemas/responses/MemberJoinGuildResponse.ts8
-rw-r--r--src/util/schemas/responses/OAuthAuthorizeResponse.ts3
-rw-r--r--src/util/schemas/responses/Tenor.ts72
-rw-r--r--src/util/schemas/responses/TokenResponse.ts15
-rw-r--r--src/util/schemas/responses/TypedResponses.ts88
-rw-r--r--src/util/schemas/responses/UpdatesResponse.ts6
-rw-r--r--src/util/schemas/responses/UserNoteResponse.ts5
-rw-r--r--src/util/schemas/responses/UserProfileResponse.ts37
-rw-r--r--src/util/schemas/responses/UserRelationsResponse.ts7
-rw-r--r--src/util/schemas/responses/UserRelationshipsResponse.ts8
-rw-r--r--src/util/schemas/responses/WebAuthnCreateResponse.ts4
-rw-r--r--src/util/schemas/responses/WebhookCreateResponse.ts6
-rw-r--r--src/util/schemas/responses/index.ts34
-rw-r--r--src/util/util/Application.ts24
-rw-r--r--src/util/util/Gifs.ts25
-rw-r--r--src/util/util/index.ts2
61 files changed, 691 insertions, 116 deletions
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/entities/Channel.ts b/src/util/entities/Channel.ts
index 72490028..19a7a41a 100644
--- a/src/util/entities/Channel.ts
+++ b/src/util/entities/Channel.ts
@@ -486,6 +486,29 @@ export enum ChannelPermissionOverwriteType {
 export interface DMChannel extends Omit<Channel, "type" | "recipients"> {
 	type: ChannelType.DM | ChannelType.GROUP_DM;
 	recipients: Recipient[];
+}
 
-	// TODO: probably more props
+// 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/Guild.ts b/src/util/entities/Guild.ts
index 64dc2420..4c2949a3 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
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/User.ts b/src/util/entities/User.ts
index b30a7c58..68d7b5e8 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
@@ -390,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/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/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/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/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/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";