diff --git a/src/Client.ts b/src/Client.ts index 14c41187..77f9240c 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1,44 +1,39 @@ -import { Accessor, Setter, batch, createSignal } from "solid-js"; +import type { Accessor, Setter } from "solid-js"; +import { batch, createSignal } from "solid-js"; import { AsyncEventEmitter } from "@vladfrangu/async_event_emitter"; -import type { DataLogin, RevoltConfig } from "revolt-api"; -import { API, Role } from "revolt-api"; +import { API } from "revolt-api"; +import type { DataLogin, RevoltConfig, Role } from "revolt-api"; -import { - Channel, - Emoji, - Message, - Server, - ServerMember, - User, -} from "./classes/index.js"; +import type { Channel } from "./classes/Channel.js"; +import type { Emoji } from "./classes/Emoji.js"; +import type { Message } from "./classes/Message.js"; +import type { Server } from "./classes/Server.js"; +import type { ServerMember } from "./classes/ServerMember.js"; +import type { User } from "./classes/User.js"; import { AccountCollection } from "./collections/AccountCollection.js"; -import { - BotCollection, - ChannelCollection, - ChannelUnreadCollection, - ChannelWebhookCollection, - EmojiCollection, - MessageCollection, - ServerCollection, - ServerMemberCollection, - SessionCollection, - UserCollection, -} from "./collections/index.js"; +import { BotCollection } from "./collections/BotCollection.js"; +import { ChannelCollection } from "./collections/ChannelCollection.js"; +import { ChannelUnreadCollection } from "./collections/ChannelUnreadCollection.js"; +import { ChannelWebhookCollection } from "./collections/ChannelWebhookCollection.js"; +import { EmojiCollection } from "./collections/EmojiCollection.js"; +import { MessageCollection } from "./collections/MessageCollection.js"; +import { ServerCollection } from "./collections/ServerCollection.js"; +import { ServerMemberCollection } from "./collections/ServerMemberCollection.js"; +import { SessionCollection } from "./collections/SessionCollection.js"; +import { UserCollection } from "./collections/UserCollection.js"; import { ConnectionState, EventClient, - EventClientOptions, - handleEventV1, -} from "./events/index.js"; -import { - HydratedChannel, - HydratedEmoji, - HydratedMessage, - HydratedServer, - HydratedServerMember, - HydratedUser, -} from "./hydration/index.js"; + type EventClientOptions, +} from "./events/EventClient.js"; +import { handleEvent } from "./events/v1.js"; +import type { HydratedChannel } from "./hydration/channel.js"; +import type { HydratedEmoji } from "./hydration/emoji.js"; +import type { HydratedMessage } from "./hydration/message.js"; +import type { HydratedServer } from "./hydration/server.js"; +import type { HydratedServerMember } from "./hydration/serverMember.js"; +import type { HydratedUser } from "./hydration/user.js"; import { RE_CHANNELS, RE_MENTIONS, RE_SPOILER } from "./lib/regex.js"; export type Session = { _id: string; token: string; user_id: string } | string; @@ -272,21 +267,21 @@ export class Client extends AsyncEventEmitter { }); this.events.on("event", (event) => - handleEventV1(this, event, this.#setReady), + handleEvent(this, event, this.#setReady), ); } /** * Current session id */ - get sessionId() { + get sessionId(): string | undefined { return typeof this.#session === "string" ? undefined : this.#session?._id; } /** * Get authentication header */ - get authenticationHeader() { + get authenticationHeader(): [string, string] { return typeof this.#session === "string" ? ["X-Bot-Token", this.#session] : ["X-Session-Token", this.#session?.token as string]; @@ -295,7 +290,7 @@ export class Client extends AsyncEventEmitter { /** * Connect to Revolt */ - connect() { + connect(): void { clearTimeout(this.#reconnectTimeout); this.events.disconnect(); this.#setReady(false); @@ -308,7 +303,7 @@ export class Client extends AsyncEventEmitter { /** * Fetches the configuration of the server if it has not been already fetched. */ - async #fetchConfiguration() { + async #fetchConfiguration(): Promise { if (!this.configuration) { this.configuration = await this.api.get("/"); } @@ -317,7 +312,7 @@ export class Client extends AsyncEventEmitter { /** * Update API object to use authentication. */ - #updateHeaders() { + #updateHeaders(): void { (this.api as API) = new API({ baseURL: this.options.baseURL, authentication: { @@ -331,7 +326,7 @@ export class Client extends AsyncEventEmitter { * @param details Login data object * @returns An on-boarding function if on-boarding is required, undefined otherwise */ - async login(details: DataLogin) { + async login(details: DataLogin): Promise { await this.#fetchConfiguration(); const data = await this.api.post("/auth/session/login", details); if (data.result === "Success") { @@ -345,7 +340,7 @@ export class Client extends AsyncEventEmitter { /** * Use an existing session */ - async useExistingSession(session: Session) { + useExistingSession(session: Session): void { this.#session = session; this.#updateHeaders(); } @@ -354,7 +349,7 @@ export class Client extends AsyncEventEmitter { * Log in as a bot * @param token Bot token */ - async loginBot(token: string) { + async loginBot(token: string): Promise { await this.#fetchConfiguration(); this.#session = token; this.#updateHeaders(); @@ -366,7 +361,7 @@ export class Client extends AsyncEventEmitter { * @param source Source markdown text * @returns Modified plain text */ - markdownToText(source: string) { + markdownToText(source: string): string { return source .replace(RE_MENTIONS, (sub: string, id: string) => { const user = this.users.get(id as string); diff --git a/src/classes/BannedUser.ts b/src/classes/BannedUser.ts index ce96c9d6..4283cc71 100644 --- a/src/classes/BannedUser.ts +++ b/src/classes/BannedUser.ts @@ -1,6 +1,8 @@ -import { BannedUser as ApiBannedUser } from "revolt-api"; +import type { BannedUser as APIBannedUser } from "revolt-api"; -import { Client, File } from "../index.js"; +import type { Client } from "../Client.js"; + +import { File } from "./File.js"; /** * Banned User @@ -16,7 +18,7 @@ export class BannedUser { * @param client Client * @param data Data */ - constructor(client: Client, data: ApiBannedUser) { + constructor(client: Client, data: APIBannedUser) { this.id = data._id; this.avatar = data.avatar ? new File(client, data.avatar) : undefined; this.username = data.username; diff --git a/src/classes/Bot.ts b/src/classes/Bot.ts index 4527c773..d28140e4 100644 --- a/src/classes/Bot.ts +++ b/src/classes/Bot.ts @@ -1,7 +1,10 @@ -import { DataEditBot } from "revolt-api"; +import type { DataEditBot } from "revolt-api"; import { decodeTime } from "ulid"; -import { BotCollection } from "../collections/index.js"; +import type { BotCollection } from "../collections/BotCollection.js"; +import type { BotFlags } from "../hydration/bot.js"; + +import type { User } from "./User.js"; /** * Bot Class @@ -24,98 +27,98 @@ export class Bot { * Convert to string * @returns String */ - toString() { + toString(): string { return `<@${this.id}>`; } /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !!this.#collection.getUnderlyingObject(this.id).id; } /** * Time when this user created their account */ - get createdAt() { + get createdAt(): Date { return new Date(decodeTime(this.id)); } /** * Corresponding user */ - get user() { + get user(): User | undefined { return this.#collection.client.users.get(this.id); } /** * Owner's Id */ - get ownerId() { + get ownerId(): string { return this.#collection.getUnderlyingObject(this.id).ownerId; } /** * Owner */ - get owner() { + get owner(): User | undefined { return this.#collection.client.users.get(this.ownerId); } /** * Bot Token */ - get token() { + get token(): string { return this.#collection.getUnderlyingObject(this.id).token; } /** * Whether this bot can be invited by anyone */ - get public() { + get public(): boolean { return this.#collection.getUnderlyingObject(this.id).public; } /** * Whether this bot has analytics enabled */ - get analytics() { + get analytics(): boolean { return this.#collection.getUnderlyingObject(this.id).analytics; } /** * Whether this bot shows up on Discover */ - get discoverable() { + get discoverable(): boolean { return this.#collection.getUnderlyingObject(this.id).discoverable; } /** * Interactions URL */ - get interactionsUrl() { + get interactionsUrl(): string | undefined { return this.#collection.getUnderlyingObject(this.id).interactionsUrl; } /** * Link to terms of service */ - get termsOfServiceUrl() { + get termsOfServiceUrl(): string | undefined { return this.#collection.getUnderlyingObject(this.id).termsOfServiceUrl; } /** * Link to privacy policy */ - get privacyPolicyUrl() { + get privacyPolicyUrl(): string | undefined { return this.#collection.getUnderlyingObject(this.id).privacyPolicyUrl; } /** * Bot Flags */ - get flags() { + get flags(): BotFlags { return this.#collection.getUnderlyingObject(this.id).flags; } @@ -123,14 +126,14 @@ export class Bot { * Edit a bot * @param data Changes */ - async edit(data: DataEditBot) { + async edit(data: DataEditBot): Promise { await this.#collection.client.api.patch(`/bots/${this.id as ""}`, data); } /** * Delete a bot */ - async delete() { + async delete(): Promise { await this.#collection.client.api.delete(`/bots/${this.id as ""}`); this.#collection.delete(this.id); } diff --git a/src/classes/Channel.ts b/src/classes/Channel.ts index 48f4fa27..bbfc0c46 100644 --- a/src/classes/Channel.ts +++ b/src/classes/Channel.ts @@ -1,26 +1,35 @@ import { batch } from "solid-js"; +import type { ReactiveSet } from "@solid-primitives/set"; import type { - Member as ApiMember, - Message as ApiMessage, - User as ApiUser, + Channel as APIChannel, + Member as APIMember, + Message as APIMessage, + User as APIUser, DataEditChannel, DataMessageSearch, DataMessageSend, + Invite, Override, } from "revolt-api"; -import { APIRoutes } from "revolt-api/dist/routes"; +import type { APIRoutes } from "revolt-api/dist/routes"; import { decodeTime, ulid } from "ulid"; import { ChannelCollection } from "../collections/index.js"; import { hydrate } from "../hydration/index.js"; -import { Message } from "../index.js"; import { bitwiseAndEq, calculatePermission, } from "../permissions/calculator.js"; import { Permission } from "../permissions/definitions.js"; +import type { ChannelWebhook } from "./ChannelWebhook.js"; +import type { File } from "./File.js"; +import type { Message } from "./Message.js"; +import type { Server } from "./Server.js"; +import type { ServerMember } from "./ServerMember.js"; +import type { User } from "./User.js"; + /** * Channel Class */ @@ -42,35 +51,35 @@ export class Channel { * Write to string as a channel mention * @returns Formatted String */ - toString() { + toString(): string { return `<#${this.id}>`; } /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !!this.#collection.getUnderlyingObject(this.id).id; } /** * Time when this server was created */ - get createdAt() { + get createdAt(): Date { return new Date(decodeTime(this.id)); } /** * Channel type */ - get type() { + get type(): APIChannel["channel_type"] { return this.#collection.getUnderlyingObject(this.id).channelType; } /** * Absolute pathname to this channel in the client */ - get path() { + get path(): string { if (this.serverId) { return `/server/${this.serverId}/channel/${this.id}`; } else { @@ -81,21 +90,21 @@ export class Channel { /** * URL to this channel */ - get url() { + get url(): string { return this.#collection.client.configuration?.app + this.path; } /** * Channel name */ - get name() { + get name(): string { return this.#collection.getUnderlyingObject(this.id).name; } /** * Display name */ - get displayName() { + get displayName(): string | undefined { return this.type === "SavedMessages" ? this.user?.username : this.type === "DirectMessage" @@ -106,35 +115,35 @@ export class Channel { /** * Channel description */ - get description() { + get description(): string | undefined { return this.#collection.getUnderlyingObject(this.id).description; } /** * Channel icon */ - get icon() { + get icon(): File | undefined { return this.#collection.getUnderlyingObject(this.id).icon; } /** * Whether the conversation is active */ - get active() { + get active(): boolean { return this.#collection.getUnderlyingObject(this.id).active; } /** * User ids of people currently typing in channel */ - get typingIds() { + get typingIds(): ReactiveSet { return this.#collection.getUnderlyingObject(this.id).typingIds; } /** * Users currently trying in channel */ - get typing() { + get typing(): User[] { return [...this.typingIds.values()].map( (id) => this.#collection.client.users.get(id)!, ); @@ -143,14 +152,14 @@ export class Channel { /** * User ids of recipients of the group */ - get recipientIds() { + get recipientIds(): ReactiveSet { return this.#collection.getUnderlyingObject(this.id).recipientIds; } /** * Recipients of the group */ - get recipients() { + get recipients(): User[] { return [ ...this.#collection.getUnderlyingObject(this.id).recipientIds.values(), ].map((id) => this.#collection.client.users.get(id)!); @@ -159,7 +168,7 @@ export class Channel { /** * Find recipient of this DM */ - get recipient() { + get recipient(): User | undefined { return this.type === "DirectMessage" ? this.recipients?.find( (user) => user?.id !== this.#collection.client.user!.id, @@ -170,14 +179,14 @@ export class Channel { /** * User ID */ - get userId() { + get userId(): string { return this.#collection.getUnderlyingObject(this.id).userId!; } /** * User this channel belongs to */ - get user() { + get user(): User | undefined { return this.#collection.client.users.get( this.#collection.getUnderlyingObject(this.id).userId!, ); @@ -186,14 +195,14 @@ export class Channel { /** * Owner ID */ - get ownerId() { + get ownerId(): string { return this.#collection.getUnderlyingObject(this.id).ownerId!; } /** * Owner of the group */ - get owner() { + get owner(): User | undefined { return this.#collection.client.users.get( this.#collection.getUnderlyingObject(this.id).ownerId!, ); @@ -202,14 +211,14 @@ export class Channel { /** * Server ID */ - get serverId() { + get serverId(): string { return this.#collection.getUnderlyingObject(this.id).serverId!; } /** * Server this channel is in */ - get server() { + get server(): Server | undefined { return this.#collection.client.servers.get( this.#collection.getUnderlyingObject(this.id).serverId!, ); @@ -218,49 +227,49 @@ export class Channel { /** * Permissions allowed for users in this group */ - get permissions() { + get permissions(): number | undefined { return this.#collection.getUnderlyingObject(this.id).permissions; } /** * Default permissions for this server channel */ - get defaultPermissions() { + get defaultPermissions(): { a: number; d: number } | undefined { return this.#collection.getUnderlyingObject(this.id).defaultPermissions; } /** * Role permissions for this server channel */ - get rolePermissions() { + get rolePermissions(): Record | undefined { return this.#collection.getUnderlyingObject(this.id).rolePermissions; } /** * Whether this channel is marked as mature */ - get mature() { + get mature(): boolean { return this.#collection.getUnderlyingObject(this.id).nsfw; } /** * ID of the last message sent in this channel */ - get lastMessageId() { + get lastMessageId(): string | undefined { return this.#collection.getUnderlyingObject(this.id).lastMessageId; } /** * Last message sent in this channel */ - get lastMessage() { + get lastMessage(): Message | undefined { return this.#collection.client.messages.get(this.lastMessageId!); } /** * Time when the last message was sent */ - get lastMessageAt() { + get lastMessageAt(): Date | undefined { return this.lastMessageId ? new Date(decodeTime(this.lastMessageId)) : undefined; @@ -269,14 +278,14 @@ export class Channel { /** * Time when the channel was last updated (either created or a message was sent) */ - get updatedAt() { + get updatedAt(): Date { return this.lastMessageAt ?? this.createdAt; } /** * Get whether this channel is unread. */ - get unread() { + get unread(): boolean { if ( !this.lastMessageId || this.type === "SavedMessages" || @@ -296,7 +305,7 @@ export class Channel { /** * Get mentions in this channel for user. */ - get mentions() { + get mentions(): ReactiveSet | undefined { if (this.type === "SavedMessages" || this.type === "VoiceChannel") return undefined; @@ -307,21 +316,21 @@ export class Channel { /** * URL to the channel icon */ - get iconURL() { + get iconURL(): string | undefined { return this.icon?.createFileURL() ?? this.recipient?.avatarURL; } /** * URL to the animated channel icon */ - get animatedIconURL() { + get animatedIconURL(): string | undefined { return this.icon?.createFileURL(true) ?? this.recipient?.animatedAvatarURL; } /** * Whether this channel may be hidden to some users */ - get potentiallyRestrictedChannel() { + get potentiallyRestrictedChannel(): string | boolean | undefined { if (!this.serverId) return false; return ( bitwiseAndEq(this.defaultPermissions?.d ?? 0, Permission.ViewChannel) || @@ -343,7 +352,7 @@ export class Channel { /** * Permission the currently authenticated user has against this channel */ - get permission() { + get permission(): number { return calculatePermission(this.#collection.client, this); } @@ -352,7 +361,7 @@ export class Channel { * @param permission Permission Names * @returns Whether we have this permission */ - havePermission(...permission: (keyof typeof Permission)[]) { + havePermission(...permission: (keyof typeof Permission)[]): boolean { return bitwiseAndEq( this.permission, ...permission.map((x) => Permission[x]), @@ -364,7 +373,7 @@ export class Channel { * @param permission Permission Names * @returns Whether we have one of the permissions */ - orPermission(...permission: (keyof typeof Permission)[]) { + orPermission(...permission: (keyof typeof Permission)[]): boolean { return ( permission.findIndex((x) => bitwiseAndEq(this.permission, Permission[x]), @@ -377,7 +386,7 @@ export class Channel { * @requires `Group` * @returns An array of the channel's members. */ - async fetchMembers() { + async fetchMembers(): Promise { const members = await this.#collection.client.api.get( `/channels/${this.id as ""}/members`, ); @@ -394,7 +403,7 @@ export class Channel { * @requires `TextChannel`, `Group` * @returns Webhooks */ - async fetchWebhooks() { + async fetchWebhooks(): Promise { const webhooks = await this.#collection.client.api.get( `/channels/${this.id as ""}/webhooks`, ); @@ -416,12 +425,12 @@ export class Channel { async edit(data: DataEditChannel) { const channel = await this.#collection.client.api.patch( `/channels/${this.id as ""}`, - data + data, ); this.#collection.updateUnderlyingObject( this.id, - hydrate("channel", channel, this.#collection.client, false) + hydrate("channel", channel, this.#collection.client, false), ); } @@ -430,7 +439,7 @@ export class Channel { * @param leaveSilently Whether to not send a message on leave * @requires `DirectMessage`, `Group`, `TextChannel`, `VoiceChannel` */ - async delete(leaveSilently?: boolean) { + async delete(leaveSilently?: boolean): Promise { await this.#collection.client.api.delete(`/channels/${this.id as ""}`, { leave_silently: leaveSilently, }); @@ -448,7 +457,7 @@ export class Channel { * @param user_id ID of the target user * @requires `Group` */ - async addMember(user_id: string) { + async addMember(user_id: string): Promise { return await this.#collection.client.api.put( `/channels/${this.id as ""}/recipients/${user_id as ""}`, ); @@ -459,7 +468,7 @@ export class Channel { * @param user_id ID of the target user * @requires `Group` */ - async removeMember(user_id: string) { + async removeMember(user_id: string): Promise { return await this.#collection.client.api.delete( `/channels/${this.id as ""}/recipients/${user_id as ""}`, ); @@ -474,7 +483,7 @@ export class Channel { async sendMessage( data: string | DataMessageSend, idempotencyKey: string = ulid(), - ) { + ): Promise { const msg: DataMessageSend = typeof data === "string" ? { content: data } : data; @@ -508,7 +517,7 @@ export class Channel { * @requires `SavedMessages`, `DirectMessage`, `Group`, `TextChannel` * @returns Message */ - async fetchMessage(messageId: string) { + async fetchMessage(messageId: string): Promise { const message = await this.#collection.client.api.get( `/channels/${this.id as ""}/messages/${messageId as ""}`, ); @@ -530,11 +539,11 @@ export class Channel { })["params"], "include_users" >, - ) { + ): Promise { const messages = (await this.#collection.client.api.get( `/channels/${this.id as ""}/messages`, { ...params }, - )) as ApiMessage[]; + )) as APIMessage[]; return messages.map((message) => this.#collection.client.messages.getOrCreate(message._id, message), @@ -555,11 +564,15 @@ export class Channel { })["params"], "include_users" >, - ) { + ): Promise<{ + messages: Message[]; + users: User[]; + members: ServerMember[] | undefined; + }> { const data = (await this.#collection.client.api.get( `/channels/${this.id as ""}/messages`, { ...params, include_users: true }, - )) as { messages: ApiMessage[]; users: ApiUser[]; members?: ApiMember[] }; + )) as { messages: APIMessage[]; users: APIUser[]; members?: APIMember[] }; return batch(() => ({ messages: data.messages.map((message) => @@ -580,11 +593,13 @@ export class Channel { * @requires `SavedMessages`, `DirectMessage`, `Group`, `TextChannel` * @returns Messages */ - async search(params: Omit) { + async search( + params: Omit, + ): Promise { const messages = (await this.#collection.client.api.post( `/channels/${this.id as ""}/search`, params, - )) as ApiMessage[]; + )) as APIMessage[]; return batch(() => messages.map((message) => @@ -599,14 +614,20 @@ export class Channel { * @requires `SavedMessages`, `DirectMessage`, `Group`, `TextChannel` * @returns Object including messages and users */ - async searchWithUsers(params: Omit) { + async searchWithUsers( + params: Omit, + ): Promise<{ + messages: Message[]; + users: User[]; + members: ServerMember[] | undefined; + }> { const data = (await this.#collection.client.api.post( `/channels/${this.id as ""}/search`, { ...params, include_users: true, }, - )) as { messages: ApiMessage[]; users: ApiUser[]; members?: ApiMember[] }; + )) as { messages: APIMessage[]; users: APIUser[]; members?: APIMember[] }; return batch(() => ({ messages: data.messages.map((message) => @@ -626,7 +647,7 @@ export class Channel { * @param ids List of message IDs * @requires `SavedMessages`, `DirectMessage`, `Group`, `TextChannel` */ - async deleteMessages(ids: string[]) { + async deleteMessages(ids: string[]): Promise { await this.#collection.client.api.delete( `/channels/${this.id as ""}/messages/bulk`, { @@ -640,7 +661,7 @@ export class Channel { * @requires `TextChannel`, `VoiceChannel` * @returns Newly created invite code */ - async createInvite() { + async createInvite(): Promise { return await this.#collection.client.api.post( `/channels/${this.id as ""}/invites`, ); @@ -663,7 +684,7 @@ export class Channel { skipRateLimiter?: boolean, skipRequest?: boolean, skipNextMarking?: boolean, - ) { + ): Promise { if (!message && this.#manuallyMarked) { this.#manuallyMarked = false; return; @@ -696,7 +717,7 @@ export class Channel { /** * Send the actual acknowledgement request */ - const performAck = () => { + const performAck = (): void => { this.#ackLimit = undefined; this.#collection.client.api.put( `/channels/${this.id}/ack/${lastMessageId as ""}`, @@ -724,7 +745,10 @@ export class Channel { * @param permissions Permission value * @requires `Group`, `TextChannel`, `VoiceChannel` */ - async setPermissions(role_id = "default", permissions: Override) { + async setPermissions( + role_id = "default", + permissions: Override, + ): Promise { return await this.#collection.client.api.put( `/channels/${this.id as ""}/permissions/${role_id as ""}`, { permissions }, @@ -735,7 +759,7 @@ export class Channel { * Start typing in this channel * @requires `DirectMessage`, `Group`, `TextChannel` */ - startTyping() { + startTyping(): void { this.#collection.client.events.send({ type: "BeginTyping", channel: this.id, @@ -746,7 +770,7 @@ export class Channel { * Stop typing in this channel * @requires `DirectMessage`, `Group`, `TextChannel` */ - stopTyping() { + stopTyping(): void { this.#collection.client.events.send({ type: "EndTyping", channel: this.id, diff --git a/src/classes/ChannelUnread.ts b/src/classes/ChannelUnread.ts index 738174be..4b31911c 100644 --- a/src/classes/ChannelUnread.ts +++ b/src/classes/ChannelUnread.ts @@ -1,4 +1,6 @@ -import { ChannelUnreadCollection } from "../collections/index.js"; +import type { ReactiveSet } from "@solid-primitives/set"; + +import type { ChannelUnreadCollection } from "../collections/ChannelUnreadCollection.js"; /** * Channel Unread Class @@ -20,21 +22,21 @@ export class ChannelUnread { /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !!this.#collection.getUnderlyingObject(this.id).id; } /** * Last read message id */ - get lastMessageId() { + get lastMessageId(): string | undefined { return this.#collection.getUnderlyingObject(this.id).lastMessageId; } /** * List of message IDs that we were mentioned in */ - get messageMentionIds() { + get messageMentionIds(): ReactiveSet { return this.#collection.getUnderlyingObject(this.id).messageMentionIds; } } diff --git a/src/classes/ChannelWebhook.ts b/src/classes/ChannelWebhook.ts index 871a6457..87a197c5 100644 --- a/src/classes/ChannelWebhook.ts +++ b/src/classes/ChannelWebhook.ts @@ -1,6 +1,11 @@ -import { ChannelWebhookCollection } from "../collections/index.js"; +import * as APITmp from "revolt-api"; + +import type { ChannelWebhookCollection } from "../collections/ChannelWebhookCollection.js"; import { hydrate } from "../hydration/index.js"; +import type { Channel } from "./Channel.js"; +import type { File } from "./File.js"; + /** * Channel Webhook Class */ @@ -21,28 +26,28 @@ export class ChannelWebhook { /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !!this.#collection.getUnderlyingObject(this.id).id; } /** * Webhook name */ - get name() { + get name(): string { return this.#collection.getUnderlyingObject(this.id).name; } /** * Webhook avatar */ - get avatar() { + get avatar(): File | undefined { return this.#collection.getUnderlyingObject(this.id).avatar; } /** * Webhook avatar URL */ - get avatarURL() { + get avatarURL(): string | undefined { return this.#collection .getUnderlyingObject(this.id) .avatar?.createFileURL(); @@ -51,14 +56,14 @@ export class ChannelWebhook { /** * Channel ID this webhook belongs to */ - get channelId() { + get channelId(): string { return this.#collection.getUnderlyingObject(this.id).channelId; } /** * Channel this webhook belongs to */ - get channel() { + get channel(): Channel | undefined { return this.#collection.client.channels.get( this.#collection.getUnderlyingObject(this.id).channelId, ); @@ -67,7 +72,7 @@ export class ChannelWebhook { /** * Secret token for sending messages to this webhook */ - get token() { + get token(): string { return this.#collection.getUnderlyingObject(this.id).token; } @@ -75,8 +80,18 @@ export class ChannelWebhook { * Edit this webhook * TODO: not in production */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async edit(data: any /*: DataEditWebhook*/) { + async edit( + data: Partial<{ + name: string; + avatar: string; + permissions: number; + remove: ["Icon"]; + }>, + ): Promise { + // @ts-expect-error this should error once edit webhook is stable + // eslint-disable-next-line + type TodoUseThisDefinitionOnceItExists = APITmp.DataEditWebhook; + const webhook = await this.#collection.client.api.patch( // @ts-expect-error not in prod `/webhooks/${this.id as ""}/${this.token as ""}`, @@ -94,7 +109,7 @@ export class ChannelWebhook { * Delete this webhook * TODO: not in production */ - async delete() { + async delete(): Promise { await this.#collection.client.api.delete( // @ts-expect-error not in prod `/webhooks/${this.id}/${this.token}`, diff --git a/src/classes/Emoji.ts b/src/classes/Emoji.ts index b2f6aed7..7b157fee 100644 --- a/src/classes/Emoji.ts +++ b/src/classes/Emoji.ts @@ -1,6 +1,9 @@ +import type { EmojiParent } from "revolt-api"; import { decodeTime } from "ulid"; -import { EmojiCollection } from "../collections/index.js"; +import type { EmojiCollection } from "../collections/EmojiCollection.js"; + +import type { User } from "./User.js"; /** * Emoji Class @@ -23,35 +26,35 @@ export class Emoji { * Convert to string * @returns String */ - toString() { + toString(): string { return `:${this.id}:`; } /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !!this.#collection.getUnderlyingObject(this.id).id; } /** * Time when this emoji was created */ - get createdAt() { + get createdAt(): Date { return new Date(decodeTime(this.id)); } /** * Information about the parent of this emoji */ - get parent() { + get parent(): EmojiParent { return this.#collection.getUnderlyingObject(this.id).parent; } /** * Creator of the emoji */ - get creator() { + get creator(): User | undefined { return this.#collection.client.users.get( this.#collection.getUnderlyingObject(this.id).creatorId, ); @@ -60,28 +63,28 @@ export class Emoji { /** * Name */ - get name() { + get name(): string { return this.#collection.getUnderlyingObject(this.id).name; } /** * Whether the emoji is animated */ - get animated() { + get animated(): boolean { return this.#collection.getUnderlyingObject(this.id).animated; } /** * Whether the emoji is marked as mature */ - get mature() { + get mature(): boolean { return this.#collection.getUnderlyingObject(this.id).nsfw; } /** * Delete Emoji */ - async delete() { + async delete(): Promise { await this.#collection.client.api.delete(`/custom/emoji/${this.id}`); const emoji = this.#collection.getUnderlyingObject(this.id); diff --git a/src/classes/File.ts b/src/classes/File.ts index ae00f5d1..21767c02 100644 --- a/src/classes/File.ts +++ b/src/classes/File.ts @@ -1,6 +1,6 @@ -import { Metadata } from "revolt-api"; +import type { File as APIFile, Metadata } from "revolt-api"; -import { API, Client } from "../index.js"; +import type { Client } from "../Client.js"; /** * Uploaded File @@ -45,7 +45,7 @@ export class File { */ constructor( client: Client, - file: Pick & Partial, + file: Pick & Partial, ) { this.#client = client; this.id = file._id; @@ -59,7 +59,7 @@ export class File { /** * Direct URL to the file */ - get url() { + get url(): string { return `${this.#client.configuration?.features.autumn.url}/${this.tag}/${ this.id }/${this.filename}`; @@ -68,7 +68,7 @@ export class File { /** * Download URL for the file */ - get downloadURL() { + get downloadURL(): string { return `${this.#client.configuration?.features.autumn.url}/${ this.tag }/download/${this.id}/${this.filename}`; @@ -77,7 +77,7 @@ export class File { /** * Human readable file size */ - get humanReadableSize() { + get humanReadableSize(): string { if (!this.size) return "Unknown size"; if (this.size > 1e6) { @@ -92,7 +92,7 @@ export class File { /** * Whether this file should have a spoiler */ - get isSpoiler() { + get isSpoiler(): boolean { return this.filename?.toLowerCase().startsWith("spoiler_") ?? false; } @@ -101,7 +101,7 @@ export class File { * @param forceAnimation Returns GIF if applicable (for avatars/icons) * @returns Generated URL or nothing */ - createFileURL(forceAnimation?: boolean) { + createFileURL(forceAnimation?: boolean): string | undefined { const autumn = this.#client.configuration?.features.autumn; if (!autumn?.enabled) return; diff --git a/src/classes/Invite.ts b/src/classes/Invite.ts index 3b9b3a71..df197bc1 100644 --- a/src/classes/Invite.ts +++ b/src/classes/Invite.ts @@ -1,18 +1,24 @@ -import { API, Client } from "../index.js"; +import type { Invite } from "revolt-api"; + +import type { Client } from "../Client.js"; + +import type { Channel } from "./Channel.js"; +import type { Server } from "./Server.js"; +import type { User } from "./User.js"; /** * Channel Invite */ export abstract class ChannelInvite { protected client?: Client; - readonly type: API.Invite["type"] | "None"; + readonly type: Invite["type"] | "None"; /** * Construct Channel Invite * @param client Client * @param type Type */ - constructor(client?: Client, type: API.Invite["type"] | "None" = "None") { + constructor(client?: Client, type: Invite["type"] | "None" = "None") { this.client = client; this.type = type; } @@ -23,7 +29,7 @@ export abstract class ChannelInvite { * @param invite Data * @returns Invite */ - static from(client: Client, invite: API.Invite): ChannelInvite { + static from(client: Client, invite: Invite): ChannelInvite { switch (invite.type) { case "Server": return new ServerInvite(client, invite); @@ -52,7 +58,7 @@ export class ServerInvite extends ChannelInvite { * @param client Client * @param invite Invite */ - constructor(client: Client, invite: API.Invite & { type: "Server" }) { + constructor(client: Client, invite: Invite & { type: "Server" }) { super(client, "Server"); this.id = invite._id; @@ -64,28 +70,28 @@ export class ServerInvite extends ChannelInvite { /** * Creator of the invite */ - get creator() { + get creator(): User | undefined { return this.client!.users.get(this.creatorId); } /** * Server this invite points to */ - get server() { + get server(): Server | undefined { return this.client!.servers.get(this.serverId); } /** * Channel this invite points to */ - get channel() { + get channel(): Channel | undefined { return this.client!.channels.get(this.channelId); } /** * Delete the invite */ - async delete() { + async delete(): Promise { await this.client!.api.delete(`/invites/${this.id}`); } } diff --git a/src/classes/MFA.ts b/src/classes/MFA.ts index c3f126ba..72255332 100644 --- a/src/classes/MFA.ts +++ b/src/classes/MFA.ts @@ -1,13 +1,14 @@ -import { SetStoreFunction, createStore } from "solid-js/store"; +import type { SetStoreFunction } from "solid-js/store"; +import { createStore } from "solid-js/store"; -import { +import type { MFAMethod, MFAResponse, MultiFactorStatus, MFATicket as TicketType, } from "revolt-api"; -import { Client } from "../index.js"; +import type { Client } from "../Client.js"; /** * Multi-Factor Authentication @@ -29,14 +30,14 @@ export class MFA { /** * Whether authenticator app is enabled */ - get authenticatorEnabled() { + get authenticatorEnabled(): boolean { return this.#store[0].totp_mfa; } /** * Whether recovery codes are enabled */ - get recoveryEnabled() { + get recoveryEnabled(): boolean { return this.#store[0].recovery_active; } @@ -56,17 +57,19 @@ export class MFA { * @param params * @returns Token */ - createTicket(params: MFAResponse) { - return this.#client.api - .put("/auth/mfa/ticket", params) - .then((ticket) => new MFATicket(this.#client, ticket, this.#store[1])); + async createTicket(params: MFAResponse): Promise { + return new MFATicket( + this.#client, + await this.#client.api.put("/auth/mfa/ticket", params), + this.#store[1] + ); } /** * Enable authenticator using token generated from secret found earlier * @param token Token */ - async enableAuthenticator(token: string) { + async enableAuthenticator(token: string): Promise { await this.#client.api.put("/auth/mfa/totp", { totp_code: token }); this.#store[1]("totp_mfa", true); } @@ -100,14 +103,14 @@ export class MFATicket { /** * Token */ - get token() { + get token(): string { return this.#ticket.token; } /** * Use the ticket */ - #consume() { + #consume(): void { if (this.#used) throw "Already used this ticket!"; this.#used = true; } @@ -116,7 +119,7 @@ export class MFATicket { * Fetch recovery codes * @returns List of codes */ - fetchRecoveryCodes() { + fetchRecoveryCodes(): Promise { this.#consume(); return this.#client.api.post("/auth/mfa/recovery", undefined, { headers: { @@ -129,7 +132,7 @@ export class MFATicket { * Generate new set of recovery codes * @returns List of codes */ - async generateRecoveryCodes() { + async generateRecoveryCodes(): Promise { this.#consume(); const codes = await this.#client.api.patch( @@ -150,21 +153,21 @@ export class MFATicket { * Generate new authenticator secret * @returns Secret */ - generateAuthenticatorSecret() { + async generateAuthenticatorSecret(): Promise { this.#consume(); - return this.#client.api - .post("/auth/mfa/totp", undefined, { + return ( + await this.#client.api.post("/auth/mfa/totp", undefined, { headers: { "X-MFA-Ticket": this.token, }, }) - .then((response) => response.secret); + ).secret; } /** * Disable authenticator */ - async disableAuthenticator() { + async disableAuthenticator(): Promise { this.#consume(); await this.#client.api.delete("/auth/mfa/totp", undefined, { @@ -179,7 +182,7 @@ export class MFATicket { /** * Disable account */ - disableAccount() { + disableAccount(): Promise { this.#consume(); return this.#client.api.post("/auth/account/disable", undefined, { headers: { @@ -191,7 +194,7 @@ export class MFATicket { /** * Delete account */ - deleteAccount() { + deleteAccount(): Promise { this.#consume(); return this.#client.api.post("/auth/account/delete", undefined, { headers: { diff --git a/src/classes/Message.ts b/src/classes/Message.ts index fee8f4cc..c07aa71d 100644 --- a/src/classes/Message.ts +++ b/src/classes/Message.ts @@ -1,12 +1,24 @@ -import { - MessageWebhook as ApiMessageWebhook, +import type { ReactiveMap } from "@solid-primitives/map"; +import type { ReactiveSet } from "@solid-primitives/set"; +import type { + Message as APIMessage, + MessageWebhook as APIMessageWebhook, DataEditMessage, DataMessageSend, + Masquerade, } from "revolt-api"; import { decodeTime } from "ulid"; -import { MessageCollection } from "../collections/index.js"; -import { Client, File } from "../index.js"; +import type { Client } from "../Client.js"; +import type { MessageCollection } from "../collections/MessageCollection.js"; + +import type { Channel } from "./Channel.js"; +import { File } from "./File.js"; +import type { MessageEmbed } from "./MessageEmbed.js"; +import type { Server } from "./Server.js"; +import type { ServerMember } from "./ServerMember.js"; +import type { SystemMessage } from "./SystemMessage.js"; +import type { User } from "./User.js"; /** * Message Class @@ -28,49 +40,49 @@ export class Message { /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !!this.#collection.getUnderlyingObject(this.id).id; } /** * Time when this message was posted */ - get createdAt() { + get createdAt(): Date { return new Date(decodeTime(this.id)); } /** * Absolute pathname to this message in the client */ - get path() { + get path(): string { return `${this.channel?.path}/${this.id}`; } /** * URL to this message */ - get url() { + get url(): string | undefined { return this.#collection.client.configuration?.app + this.path; } /** * Nonce value */ - get nonce() { + get nonce(): string | undefined { return this.#collection.getUnderlyingObject(this.id).nonce; } /** * Id of channel this message was sent in */ - get channelId() { + get channelId(): string | undefined { return this.#collection.getUnderlyingObject(this.id).channelId; } /** * Channel this message was sent in */ - get channel() { + get channel(): Channel | undefined { return this.#collection.client.channels.get( this.#collection.getUnderlyingObject(this.id).channelId, ); @@ -79,14 +91,14 @@ export class Message { /** * Server this message was sent in */ - get server() { + get server(): Server | undefined { return this.channel?.server; } /** * Member this message was sent by */ - get member() { + get member(): ServerMember | undefined { return this.#collection.client.serverMembers.getByKey({ server: this.channel?.serverId as string, user: this.authorId!, @@ -96,14 +108,14 @@ export class Message { /** * Id of user or webhook this message was sent by */ - get authorId() { + get authorId(): string | undefined { return this.#collection.getUnderlyingObject(this.id).authorId; } /** * User this message was sent by */ - get author() { + get author(): User | undefined { return this.#collection.client.users.get( this.#collection.getUnderlyingObject(this.id).authorId!, ); @@ -112,98 +124,98 @@ export class Message { /** * Webhook information for this message */ - get webhook() { + get webhook(): MessageWebhook | undefined { return this.#collection.getUnderlyingObject(this.id).webhook!; } /** * Content */ - get content() { + get content(): string { return this.#collection.getUnderlyingObject(this.id).content ?? ""; } /** * System message content */ - get systemMessage() { + get systemMessage(): SystemMessage | undefined { return this.#collection.getUnderlyingObject(this.id).systemMessage; } /** * Attachments */ - get attachments() { + get attachments(): File[] | undefined { return this.#collection.getUnderlyingObject(this.id).attachments; } /** * Time at which this message was edited */ - get editedAt() { + get editedAt(): Date | undefined { return this.#collection.getUnderlyingObject(this.id).editedAt; } /** * Embeds */ - get embeds() { + get embeds(): MessageEmbed[] | undefined { return this.#collection.getUnderlyingObject(this.id).embeds; } /** * IDs of users this message mentions */ - get mentionIds() { + get mentionIds(): string[] | undefined { return this.#collection.getUnderlyingObject(this.id).mentionIds; } /** * Whether this message mentions us */ - get mentioned() { - return this.mentionIds?.includes(this.#collection.client.user!.id); + get mentioned(): boolean { + return this.mentionIds?.includes(this.#collection.client.user!.id) ?? false; } /** * IDs of messages this message replies to */ - get replyIds() { + get replyIds(): string[] | undefined { return this.#collection.getUnderlyingObject(this.id).replyIds; } /** * Reactions */ - get reactions() { + get reactions(): ReactiveMap> { return this.#collection.getUnderlyingObject(this.id).reactions; } /** * Interactions */ - get interactions() { + get interactions(): APIMessage["interactions"] { return this.#collection.getUnderlyingObject(this.id).interactions; } /** * Masquerade */ - get masquerade() { + get masquerade(): Masquerade | undefined { return this.#collection.getUnderlyingObject(this.id).masquerade; } /** * Flags */ - get flags() { + get flags(): number { return this.#collection.getUnderlyingObject(this.id).flags || 0; } /** * Get the username for this message */ - get username() { + get username(): string | undefined { const webhook = this.webhook; return ( @@ -217,14 +229,14 @@ export class Message { /** * Get the role colour for this message */ - get roleColour() { + get roleColour(): string | null | undefined { return this.masquerade?.colour ?? this.member?.roleColour; } /** * Get the avatar URL for this message */ - get avatarURL() { + get avatarURL(): string | undefined { const webhook = this.webhook; return ( @@ -238,7 +250,7 @@ export class Message { /** * Get the animated avatar URL for this message */ - get animatedAvatarURL() { + get animatedAvatarURL(): string | undefined { const webhook = this.webhook; return ( @@ -254,7 +266,7 @@ export class Message { /** * Avatar URL from the masquerade */ - get masqueradeAvatarURL() { + get masqueradeAvatarURL(): string | undefined { const avatar = this.masquerade?.avatar; return avatar ? this.#collection.client.proxyFile(avatar) : undefined; } @@ -262,7 +274,7 @@ export class Message { /** * Whether this message has suppressed desktop/push notifications */ - get isSuppressed() { + get isSuppressed(): boolean { return (this.flags & 1) === 1; } @@ -270,7 +282,7 @@ export class Message { * Edit a message * @param data Message edit route data */ - async edit(data: DataEditMessage) { + async edit(data: DataEditMessage): Promise { return await this.#collection.client.api.patch( `/channels/${this.channelId as ""}/messages/${this.id as ""}`, data, @@ -280,7 +292,7 @@ export class Message { /** * Delete a message */ - async delete() { + async delete(): Promise { return await this.#collection.client.api.delete( `/channels/${this.channelId as ""}/messages/${this.id as ""}`, ); @@ -289,7 +301,7 @@ export class Message { /** * Acknowledge this message as read */ - ack() { + ack(): void { this.channel?.ack(this); } @@ -303,7 +315,7 @@ export class Message { nonce?: string; }), mention = true, - ) { + ): Promise | undefined { const obj = typeof data === "string" ? { content: data } : data; return this.channel?.sendMessage({ ...obj, @@ -314,7 +326,7 @@ export class Message { /** * Clear all reactions from this message */ - async clearReactions() { + async clearReactions(): Promise { return await this.#collection.client.api.delete( `/channels/${this.channelId as ""}/messages/${this.id as ""}/reactions`, ); @@ -324,7 +336,7 @@ export class Message { * React to a message * @param emoji Unicode or emoji ID */ - async react(emoji: string) { + async react(emoji: string): Promise { return await this.#collection.client.api.put( `/channels/${this.channelId as ""}/messages/${this.id as ""}/reactions/${ emoji as "" @@ -336,7 +348,7 @@ export class Message { * Un-react from a message * @param emoji Unicode or emoji ID */ - async unreact(emoji: string) { + async unreact(emoji: string): Promise { return await this.#collection.client.api.delete( `/channels/${this.channelId as ""}/messages/${this.id as ""}/reactions/${ emoji as "" @@ -360,7 +372,7 @@ export class MessageWebhook { * @param client Client * @param webhook Webhook data */ - constructor(client: Client, webhook: ApiMessageWebhook, id: string) { + constructor(client: Client, webhook: APIMessageWebhook, id: string) { this.#client = client; this.id = id; this.name = webhook.name; @@ -380,7 +392,7 @@ export class MessageWebhook { /** * Get the avatar URL for this message webhook */ - get avatarURL() { + get avatarURL(): string { return ( this.avatar?.createFileURL() ?? `${this.#client.options.baseURL}/users/${this.id}/default_avatar` diff --git a/src/classes/MessageEmbed.ts b/src/classes/MessageEmbed.ts index a1a65473..5b6b77c9 100644 --- a/src/classes/MessageEmbed.ts +++ b/src/classes/MessageEmbed.ts @@ -1,18 +1,22 @@ -import { API, Client, File } from "../index.js"; +import type { Embed, ImageSize, Special } from "revolt-api"; + +import type { Client } from "../Client.js"; + +import { File } from "./File.js"; /** * Message Embed */ export abstract class MessageEmbed { protected client?: Client; - readonly type: API.Embed["type"]; + readonly type: Embed["type"]; /** * Construct Embed * @param client Client * @param type Type */ - constructor(client?: Client, type: API.Embed["type"] = "None") { + constructor(client?: Client, type: Embed["type"] = "None") { this.client = client; this.type = type; } @@ -23,7 +27,7 @@ export abstract class MessageEmbed { * @param embed Data * @returns Embed */ - static from(client: Client, embed: API.Embed): MessageEmbed { + static from(client: Client, embed: Embed): MessageEmbed { switch (embed.type) { case "Image": return new ImageEmbed(client, embed); @@ -51,17 +55,14 @@ export class ImageEmbed extends MessageEmbed { readonly url: string; readonly width: number; readonly height: number; - readonly size: API.ImageSize; + readonly size: ImageSize; /** * Construct Image Embed * @param client Client * @param embed Embed */ - constructor( - client: Client, - embed: Omit, - ) { + constructor(client: Client, embed: Omit) { super(client, "Image"); this.url = embed.url; @@ -73,7 +74,7 @@ export class ImageEmbed extends MessageEmbed { /** * Proxied image URL */ - get proxiedURL() { + get proxiedURL(): string | undefined { return this.client?.proxyFile(this.url); } } @@ -91,10 +92,7 @@ export class VideoEmbed extends MessageEmbed { * @param client Client * @param embed Embed */ - constructor( - client: Client, - embed: Omit, - ) { + constructor(client: Client, embed: Omit) { super(client, "Video"); this.url = embed.url; @@ -105,7 +103,7 @@ export class VideoEmbed extends MessageEmbed { /** * Proxied video URL */ - get proxiedURL() { + get proxiedURL(): string | undefined { return this.client?.proxyFile(this.url); } } @@ -116,7 +114,7 @@ export class VideoEmbed extends MessageEmbed { export class WebsiteEmbed extends MessageEmbed { readonly url?: string; readonly originalUrl?: string; - readonly specialContent?: API.Special; + readonly specialContent?: Special; readonly title?: string; readonly description?: string; readonly image?: ImageEmbed; @@ -132,7 +130,7 @@ export class WebsiteEmbed extends MessageEmbed { */ constructor( client: Client, - embed: Omit, + embed: Omit, ) { super(client, "Website"); @@ -151,14 +149,14 @@ export class WebsiteEmbed extends MessageEmbed { /** * Proxied icon URL */ - get proxiedIconURL() { + get proxiedIconURL(): string | undefined { return this.iconUrl ? this.client?.proxyFile(this.iconUrl) : undefined; } /** * If special content is present, generate the embed URL */ - get embedURL() { + get embedURL(): string | undefined { switch (this.specialContent?.type) { case "YouTube": { let timestamp = ""; @@ -209,10 +207,7 @@ export class TextEmbed extends MessageEmbed { * @param client Client * @param embed Embed */ - constructor( - client: Client, - embed: Omit, - ) { + constructor(client: Client, embed: Omit) { super(client, "Text"); this.iconUrl = embed.icon_url!; @@ -226,7 +221,7 @@ export class TextEmbed extends MessageEmbed { /** * Proxied icon URL */ - get proxiedIconURL() { + get proxiedIconURL(): string | undefined { return this.iconUrl ? this.client?.proxyFile(this.iconUrl) : undefined; } } diff --git a/src/classes/PublicBot.ts b/src/classes/PublicBot.ts index fa07542b..3653d3f9 100644 --- a/src/classes/PublicBot.ts +++ b/src/classes/PublicBot.ts @@ -1,4 +1,10 @@ -import { API, Channel, Client, File, Server } from "../index.js"; +import type { File as APIFile, PublicBot as APIPublicBot } from "revolt-api"; + +import type { Client } from "../Client.js"; + +import { Channel } from "./Channel.js"; +import { File } from "./File.js"; +import { Server } from "./Server.js"; /** * Public Bot Class @@ -16,7 +22,7 @@ export class PublicBot { * @param client Client * @param data Data */ - constructor(client: Client, data: API.PublicBot) { + constructor(client: Client, data: APIPublicBot) { this.#client = client; this.id = data._id; this.username = data.username; @@ -24,7 +30,7 @@ export class PublicBot { ? new File(client, { _id: data.avatar, tag: "avatars", - } as API.File) + } as APIFile) : undefined; this.description = data.description!; } @@ -33,7 +39,7 @@ export class PublicBot { * Add the bot to a server * @param server Server */ - addToServer(server: Server | string) { + addToServer(server: Server | string): void { this.#client.api.post(`/bots/${this.id as ""}/invite`, { server: server instanceof Server ? server.id : server, }); @@ -43,7 +49,7 @@ export class PublicBot { * Add the bot to a group * @param group Group */ - addToGroup(group: Channel | string) { + addToGroup(group: Channel | string): void { // TODO: should use GroupChannel once that is added this.#client.api.post(`/bots/${this.id as ""}/invite`, { group: group instanceof Channel ? group.id : group, diff --git a/src/classes/PublicInvite.ts b/src/classes/PublicInvite.ts index b55416f0..e6ecf9f0 100644 --- a/src/classes/PublicInvite.ts +++ b/src/classes/PublicInvite.ts @@ -1,21 +1,26 @@ import { batch } from "solid-js"; -import { ServerFlags } from "../hydration/server.js"; -import { API, Client, File } from "../index.js"; +import type { Invite, InviteResponse } from "revolt-api"; + +import type { Client } from "../Client.js"; +import type { ServerFlags } from "../hydration/server.js"; + +import { File } from "./File.js"; +import type { Server } from "./Server.js"; /** * Public Channel Invite */ export abstract class PublicChannelInvite { protected client?: Client; - readonly type: API.Invite["type"] | "None"; + readonly type: Invite["type"] | "None"; /** * Construct Channel Invite * @param client Client * @param type Type */ - constructor(client?: Client, type: API.Invite["type"] | "None" = "None") { + constructor(client?: Client, type: Invite["type"] | "None" = "None") { this.client = client; this.type = type; } @@ -26,7 +31,7 @@ export abstract class PublicChannelInvite { * @param invite Data * @returns Invite */ - static from(client: Client, invite: API.InviteResponse): PublicChannelInvite { + static from(client: Client, invite: InviteResponse): PublicChannelInvite { switch (invite.type) { case "Server": return new ServerPublicInvite(client, invite); @@ -65,7 +70,7 @@ export class ServerPublicInvite extends PublicChannelInvite { * @param client Client * @param invite Invite */ - constructor(client: Client, invite: API.InviteResponse & { type: "Server" }) { + constructor(client: Client, invite: InviteResponse & { type: "Server" }) { super(client, "Server"); this.code = invite.code; @@ -93,7 +98,7 @@ export class ServerPublicInvite extends PublicChannelInvite { /** * Join the server */ - async join() { + async join(): Promise { const existingServer = this.client!.servers.get(this.serverId); if (existingServer) return existingServer; diff --git a/src/classes/Server.ts b/src/classes/Server.ts index 04196fa8..298c028b 100644 --- a/src/classes/Server.ts +++ b/src/classes/Server.ts @@ -1,6 +1,9 @@ import { batch } from "solid-js"; +import type { ReactiveMap } from "@solid-primitives/map"; +import type { ReactiveSet } from "@solid-primitives/set"; import type { + Server as APIServer, AllMemberResponse, BannedUser, Category, @@ -10,21 +13,27 @@ import type { DataEditRole, DataEditServer, Override, + OverrideField, + Role, } from "revolt-api"; import { decodeTime } from "ulid"; -import { ServerCollection } from "../collections/index.js"; +import type { ServerCollection } from "../collections/ServerCollection.js"; import { hydrate } from "../hydration/index.js"; -import { ServerMember, User } from "../index.js"; +import type { ServerFlags } from "../hydration/server.js"; import { bitwiseAndEq, calculatePermission, } from "../permissions/calculator.js"; import { Permission } from "../permissions/definitions.js"; -import { Channel } from "./Channel.js"; +import type { Channel } from "./Channel.js"; +import type { Emoji } from "./Emoji.js"; +import type { File } from "./File.js"; import { ChannelInvite } from "./Invite.js"; import { ServerBan } from "./ServerBan.js"; +import { ServerMember } from "./ServerMember.js"; +import { User } from "./User.js"; /** * Server Class @@ -42,40 +51,39 @@ export class Server { this.#collection = collection; this.id = id; } - /** * Convert to string * @returns String */ - toString() { + toString(): string { return `<%${this.id}>`; } /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !!this.#collection.getUnderlyingObject(this.id).id; } /** * Time when this server was created */ - get createdAt() { + get createdAt(): Date { return new Date(decodeTime(this.id)); } /** * Owner's user ID */ - get ownerId() { + get ownerId(): string { return this.#collection.getUnderlyingObject(this.id).ownerId; } /** * Owner */ - get owner() { + get owner(): User | undefined { return this.#collection.client.users.get( this.#collection.getUnderlyingObject(this.id).ownerId, ); @@ -84,42 +92,42 @@ export class Server { /** * Name */ - get name() { + get name(): string { return this.#collection.getUnderlyingObject(this.id).name; } /** * Description */ - get description() { + get description(): string | undefined { return this.#collection.getUnderlyingObject(this.id).description; } /** * Icon */ - get icon() { + get icon(): File | undefined { return this.#collection.getUnderlyingObject(this.id).icon; } /** * Banner */ - get banner() { + get banner(): File | undefined { return this.#collection.getUnderlyingObject(this.id).banner; } /** * Channel IDs */ - get channelIds() { + get channelIds(): ReactiveSet { return this.#collection.getUnderlyingObject(this.id).channelIds; } /** * Channels */ - get channels() { + get channels(): Channel[] { return [ ...this.#collection.getUnderlyingObject(this.id).channelIds.values(), ] @@ -130,56 +138,65 @@ export class Server { /** * Categories */ - get categories() { + get categories(): Category[] | undefined { return this.#collection.getUnderlyingObject(this.id).categories; } /** * System message channels */ - get systemMessages() { + get systemMessages(): APIServer["system_messages"] { return this.#collection.getUnderlyingObject(this.id).systemMessages; } /** * Roles */ - get roles() { + get roles(): ReactiveMap< + string, + { + name: string; + permissions: OverrideField; + colour?: string | null; + hoist?: boolean; + rank?: number; + } + > { return this.#collection.getUnderlyingObject(this.id).roles; } /** * Default permissions */ - get defaultPermissions() { + get defaultPermissions(): number { return this.#collection.getUnderlyingObject(this.id).defaultPermissions; } /** * Server flags */ - get flags() { + get flags(): ServerFlags { return this.#collection.getUnderlyingObject(this.id).flags; } /** * Whether analytics are enabled for this server */ - get analytics() { + get analytics(): boolean { return this.#collection.getUnderlyingObject(this.id).analytics; } /** * Whether this server is publicly discoverable */ - get discoverable() { + get discoverable(): boolean { return this.#collection.getUnderlyingObject(this.id).discoverable; } /** * Whether this server is marked as mature */ - get mature() { + get mature(): boolean { return this.#collection.getUnderlyingObject(this.id).nsfw; } @@ -252,7 +269,14 @@ export class Server { * ranking roles. This is dictated by the "rank" property * which is smaller for higher priority roles. */ - get orderedRoles() { + get orderedRoles(): { + name: string; + permissions: OverrideField; + colour?: string | null; + hoist?: boolean; + rank?: number; + id: string; + }[] { const roles = this.roles; return roles ? [...roles.entries()] @@ -265,15 +289,15 @@ export class Server { * Check whether the server is currently unread * @returns Whether the server is unread */ - get unread() { - return this.channels.find((channel) => channel.unread); + get unread(): boolean { + return !!this.channels.find((channel) => channel.unread); } /** * Find all message IDs of unread messages * @returns Array of message IDs which are unread */ - get mentions() { + get mentions(): string[] { const arr = this.channels.map((channel) => Array.from(channel.mentions?.values() ?? []), ); @@ -284,28 +308,28 @@ export class Server { /** * URL to the server's icon */ - get iconURL() { + get iconURL(): string | undefined { return this.icon?.createFileURL(); } /** * URL to the server's animated icon */ - get animatedIconURL() { + get animatedIconURL(): string | undefined { return this.icon?.createFileURL(true); } /** * URL to the server's banner */ - get bannerURL() { + get bannerURL(): string | undefined { return this.banner?.createFileURL(); } /** * Own member object for this server */ - get member() { + get member(): ServerMember | undefined { return this.#collection.client.serverMembers.getByKey({ server: this.id, user: this.#collection.client.user!.id, @@ -315,7 +339,7 @@ export class Server { /** * Permission the currently authenticated user has against this server */ - get permission() { + get permission(): number { return calculatePermission(this.#collection.client, this); } @@ -324,7 +348,7 @@ export class Server { * @param permission Permission Names * @returns Whether we have this permission */ - havePermission(...permission: (keyof typeof Permission)[]) { + havePermission(...permission: (keyof typeof Permission)[]): boolean { return bitwiseAndEq( this.permission, ...permission.map((x) => Permission[x]), @@ -336,7 +360,7 @@ export class Server { * @param permission Permission Names * @returns Whether we have one of the permissions */ - orPermission(...permission: (keyof typeof Permission)[]) { + orPermission(...permission: (keyof typeof Permission)[]): boolean { return ( permission.findIndex((x) => bitwiseAndEq(this.permission, Permission[x]), @@ -349,7 +373,7 @@ export class Server { * @param userId User's ID * @returns Server Member (if cached) */ - getMember(userId: string) { + getMember(userId: string): ServerMember | undefined { return this.#collection.client.serverMembers.getByKey({ server: this.id, user: userId, @@ -361,7 +385,7 @@ export class Server { * @param data Channel create route data * @returns The newly-created channel */ - async createChannel(data: DataCreateServerChannel) { + async createChannel(data: DataCreateServerChannel): Promise { const channel = await this.#collection.client.api.post( `/servers/${this.id as ""}/channels`, data, @@ -374,7 +398,7 @@ export class Server { * Edit a server * @param data Changes */ - async edit(data: DataEditServer) { + async edit(data: DataEditServer): Promise { this.#collection.updateUnderlyingObject( this.id, hydrate( @@ -393,7 +417,7 @@ export class Server { * Delete the underlying server * @param leaveEvent Whether we are leaving */ - $delete(leaveEvent?: boolean) { + $delete(leaveEvent?: boolean): void { batch(() => { const server = this.#collection.client.servers.getUnderlyingObject( this.id, @@ -420,7 +444,7 @@ export class Server { * Delete or leave a server * @param leaveSilently Whether to not send a message on leave */ - async delete(leaveSilently?: boolean) { + async delete(leaveSilently?: boolean): Promise { await this.#collection.client.api.delete(`/servers/${this.id as ""}`, { leave_silently: leaveSilently, }); @@ -431,7 +455,7 @@ export class Server { /** * Mark a server as read */ - async ack() { + async ack(): Promise { batch(() => { for (const channel of this.channels) { channel.ack(undefined, false, true); @@ -449,7 +473,7 @@ export class Server { async banUser( user: string | User | ServerMember, options: DataBanCreate = {}, - ) { + ): Promise { const userId = user instanceof User ? user.id @@ -469,7 +493,7 @@ export class Server { * Kick user from this server * @param user User */ - async kickUser(user: string | User | ServerMember) { + async kickUser(user: string | User | ServerMember): Promise { return await this.#collection.client.api.delete( `/servers/${this.id as ""}/members/${ typeof user === "string" @@ -485,7 +509,7 @@ export class Server { * Pardon user's ban * @param user User */ - async unbanUser(user: string | User) { + async unbanUser(user: string | User): Promise { const userId = user instanceof User ? user.id : user; return await this.#collection.client.api.delete( `/servers/${this.id as ""}/bans/${userId}`, @@ -496,7 +520,7 @@ export class Server { * Fetch a server's invites * @returns An array of the server's invites */ - async fetchInvites() { + async fetchInvites(): Promise { const invites = await this.#collection.client.api.get( `/servers/${this.id as ""}/invites`, ); @@ -510,7 +534,7 @@ export class Server { * Fetch a server's bans * @returns An array of the server's bans. */ - async fetchBans() { + async fetchBans(): Promise { const { users, bans } = await this.#collection.client.api.get( `/servers/${this.id as ""}/bans`, ); @@ -531,7 +555,10 @@ export class Server { * @param roleId Role Id, set to 'default' to affect all users * @param permissions Permission value */ - async setPermissions(roleId = "default", permissions: Override | number) { + async setPermissions( + roleId = "default", + permissions: Override | number, + ): Promise { return await this.#collection.client.api.put( `/servers/${this.id as ""}/permissions/${roleId as ""}`, { permissions: permissions as Override }, @@ -542,7 +569,7 @@ export class Server { * Create role * @param name Role name */ - async createRole(name: string) { + async createRole(name: string): Promise<{ id: string; role: Role }> { return await this.#collection.client.api.post( `/servers/${this.id as ""}/roles`, { @@ -556,7 +583,7 @@ export class Server { * @param roleId Role ID * @param data Role editing route data */ - async editRole(roleId: string, data: DataEditRole) { + async editRole(roleId: string, data: DataEditRole): Promise { return await this.#collection.client.api.patch( `/servers/${this.id as ""}/roles/${roleId as ""}`, data, @@ -567,7 +594,7 @@ export class Server { * Delete role * @param roleId Role ID */ - async deleteRole(roleId: string) { + async deleteRole(roleId: string): Promise { return await this.#collection.client.api.delete( `/servers/${this.id as ""}/roles/${roleId as ""}`, ); @@ -578,7 +605,7 @@ export class Server { * @param user User * @returns Server member object */ - async fetchMember(user: User | string) { + async fetchMember(user: User | string): Promise { const userId = typeof user === "string" ? user : user.id; const existing = this.#collection.client.serverMembers.getByKey({ server: this.id, @@ -595,7 +622,7 @@ export class Server { * Optimised member fetch route * @param excludeOffline */ - async syncMembers(excludeOffline?: boolean) { + async syncMembers(excludeOffline?: boolean): Promise { if (this.#synced && (this.#synced === "full" || excludeOffline)) return; const data = await this.#collection.client.api.get( @@ -633,7 +660,7 @@ export class Server { /** * Reset member sync status */ - resetSyncStatus() { + resetSyncStatus(): void { this.#synced = undefined; } @@ -641,7 +668,7 @@ export class Server { * Fetch a server's members * @returns List of the server's members and their user objects */ - async fetchMembers() { + async fetchMembers(): Promise<{ members: ServerMember[]; users: User[] }> { const data = (await this.#collection.client.api.get( // @ts-expect-error TODO weird typing issue `/servers/${this.id as ""}/members`, @@ -662,7 +689,9 @@ export class Server { * @param query Name * @returns List of the server's members and their user objects */ - async queryMembersExperimental(query: string) { + async queryMembersExperimental( + query: string, + ): Promise<{ members: ServerMember[]; users: User[] }> { const data = (await this.#collection.client.api.get( `/servers/${ this.id as "" @@ -689,7 +718,7 @@ export class Server { async createEmoji( autumnId: string, options: Omit, - ) { + ): Promise { const emoji = await this.#collection.client.api.put( `/custom/emoji/${autumnId as ""}`, { @@ -708,7 +737,7 @@ export class Server { * Fetch a server's emoji * @returns List of server emoji */ - async fetchEmojis() { + async fetchEmojis(): Promise { const emojis = await this.#collection.client.api.get( `/servers/${this.id as ""}/emojis`, ); @@ -724,7 +753,7 @@ export class Server { * Delete emoji * @param emojiId Emoji ID */ - async deleteEmoji(emojiId: string) { - return await this.#collection.client.api.delete(`/custom/emoji/${emojiId}`); + async deleteEmoji(emojiId: string): Promise { + await this.#collection.client.api.delete(`/custom/emoji/${emojiId}`); } } diff --git a/src/classes/ServerBan.ts b/src/classes/ServerBan.ts index 52a44165..807f15a5 100644 --- a/src/classes/ServerBan.ts +++ b/src/classes/ServerBan.ts @@ -1,10 +1,13 @@ -import { - BannedUser as ApiBannedUser, - ServerBan as ApiServerBan, +import type { + BannedUser as APIBannedUser, + ServerBan as APIServerBan, MemberCompositeKey, } from "revolt-api"; -import { BannedUser, Client } from "../index.js"; +import type { Client } from "../Client.js"; + +import { BannedUser } from "./BannedUser.js"; +import type { Server } from "./Server.js"; /** * Server Ban @@ -20,7 +23,7 @@ export class ServerBan { * @param client Client * @param data Data */ - constructor(client: Client, data: ApiServerBan, user?: ApiBannedUser) { + constructor(client: Client, data: APIServerBan, user?: APIBannedUser) { this.client = client; this.id = data._id; this.reason = data.reason!; @@ -30,14 +33,14 @@ export class ServerBan { /** * Server */ - get server() { + get server(): Server | undefined { return this.client.servers.get(this.id.server); } /** * Remove this server ban */ - async pardon() { + async pardon(): Promise { await this.client.api.delete( `/servers/${this.id.server as ""}/bans/${this.id.user as ""}`, ); diff --git a/src/classes/ServerMember.ts b/src/classes/ServerMember.ts index 6a97e122..ec4d2559 100644 --- a/src/classes/ServerMember.ts +++ b/src/classes/ServerMember.ts @@ -2,24 +2,27 @@ import type { DataBanCreate, DataMemberEdit, MemberCompositeKey, + Role, } from "revolt-api"; -import { ServerMemberCollection } from "../collections/index.js"; +import type { ServerMemberCollection } from "../collections/ServerMemberCollection.js"; import { bitwiseAndEq, calculatePermission, } from "../permissions/calculator.js"; import { Permission } from "../permissions/definitions.js"; -import { Channel } from "./Channel.js"; -import { Server } from "./Server.js"; +import type { Channel } from "./Channel.js"; +import type { File } from "./File.js"; +import type { Server } from "./Server.js"; +import type { User } from "./User.js"; /** * Deterministic conversion of member composite key to string ID * @param key Key * @returns String key */ -function key(key: MemberCompositeKey) { +function key(key: MemberCompositeKey): string { return key.server + key.user; } @@ -44,70 +47,70 @@ export class ServerMember { * Convert to string * @returns String */ - toString() { + toString(): string { return `<@${this.id.user}>`; } /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !this.#collection.getUnderlyingObject(key(this.id)).id; } /** * Server this member belongs to */ - get server() { + get server(): Server | undefined { return this.#collection.client.servers.get(this.id.server); } /** * User corresponding to this member */ - get user() { + get user(): User | undefined { return this.#collection.client.users.get(this.id.user); } /** * When this user joined the server */ - get joinedAt() { + get joinedAt(): Date { return this.#collection.getUnderlyingObject(key(this.id)).joinedAt; } /** * Nickname */ - get nickname() { + get nickname(): string | undefined { return this.#collection.getUnderlyingObject(key(this.id)).nickname; } /** * Avatar */ - get avatar() { + get avatar(): File | undefined { return this.#collection.getUnderlyingObject(key(this.id)).avatar; } /** * List of role IDs */ - get roles() { + get roles(): string[] { return this.#collection.getUnderlyingObject(key(this.id)).roles; } /** * Time at which timeout expires */ - get timeout() { + get timeout(): Date | undefined { return this.#collection.getUnderlyingObject(key(this.id)).timeout; } /** * Ordered list of roles for this member, from lowest to highest priority. */ - get orderedRoles() { + get orderedRoles(): (Partial & { id: string })[] { const server = this.server!; return ( this.roles @@ -122,7 +125,7 @@ export class ServerMember { /** * Member's currently hoisted role. */ - get hoistedRole() { + get hoistedRole(): Partial | null { const roles = this.orderedRoles.filter((x) => x.hoist); if (roles.length > 0) { return roles[roles.length - 1]; @@ -134,7 +137,7 @@ export class ServerMember { /** * Member's current role colour. */ - get roleColour() { + get roleColour(): string | null | undefined { const roles = this.orderedRoles.filter((x) => x.colour); if (roles.length > 0) { return roles[roles.length - 1].colour; @@ -147,7 +150,7 @@ export class ServerMember { * Member's ranking * Smaller values are ranked as higher priority */ - get ranking() { + get ranking(): number { if (this.id.user === this.server?.ownerId) { return -Infinity; } @@ -165,7 +168,7 @@ export class ServerMember { * @param target Target object to check permissions against * @returns Permissions that this member has */ - getPermissions(target: Server | Channel) { + getPermissions(target: Server | Channel): number { return calculatePermission(this.#collection.client, target, { member: this, }); @@ -180,7 +183,7 @@ export class ServerMember { hasPermission( target: Server | Channel, ...permission: (keyof typeof Permission)[] - ) { + ): boolean { return bitwiseAndEq( this.getPermissions(target), ...permission.map((x) => Permission[x]), @@ -192,28 +195,28 @@ export class ServerMember { * @param target The member to compare against * @returns Whether this member is inferior to the target */ - inferiorTo(target: ServerMember) { + inferiorTo(target: ServerMember): boolean { return target.ranking < this.ranking; } /** * Display name */ - get displayName() { + get displayName(): string | undefined { return this.nickname ?? this.user?.displayName; } /** * URL to the member's avatar */ - get avatarURL() { + get avatarURL(): string | undefined { return this.avatar?.createFileURL() ?? this.user?.avatarURL; } /** * URL to the member's animated avatar */ - get animatedAvatarURL() { + get animatedAvatarURL(): string | undefined { return this.avatar?.createFileURL(true) ?? this.user?.animatedAvatarURL; } @@ -221,7 +224,7 @@ export class ServerMember { * Edit a member * @param data Changes */ - async edit(data: DataMemberEdit) { + async edit(data: DataMemberEdit): Promise { await this.#collection.client.api.patch( `/servers/${this.id.server as ""}/members/${this.id.user as ""}`, data, @@ -232,14 +235,14 @@ export class ServerMember { * Ban this member from the server * @param options Ban options */ - async ban(options: DataBanCreate) { + async ban(options: DataBanCreate): Promise { this.server?.banUser(this, options); } /** * Kick this member from the server */ - async kick() { + async kick(): Promise { this.server?.kickUser(this); } } diff --git a/src/classes/Session.ts b/src/classes/Session.ts index d0952ba1..bbf244ec 100644 --- a/src/classes/Session.ts +++ b/src/classes/Session.ts @@ -1,6 +1,6 @@ import { decodeTime } from "ulid"; -import { SessionCollection } from "../collections/index.js"; +import type { SessionCollection } from "../collections/SessionCollection.js"; /** * Session Class @@ -23,35 +23,35 @@ export class Session { * Convert to string * @returns String */ - toString() { + toString(): string { return this.name; } /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !!this.#collection.getUnderlyingObject(this.id).id; } /** * Time when this session was created */ - get createdAt() { + get createdAt(): Date { return new Date(decodeTime(this.id)); } /** * Whether this is the current session */ - get current() { + get current(): boolean { return this.id === this.#collection.client.sessionId; } /** * Name */ - get name() { + get name(): string { return this.#collection.getUnderlyingObject(this.id).name; } @@ -59,7 +59,7 @@ export class Session { * Rename a session * @param name New name */ - async rename(name: string) { + async rename(name: string): Promise { await this.#collection.client.api.patch(`/auth/session/${this.id}`, { friendly_name: name, }); @@ -70,7 +70,7 @@ export class Session { /** * Delete a session */ - async delete() { + async delete(): Promise { await this.#collection.client.api.delete(`/auth/session/${this.id as ""}`); this.#collection.delete(this.id); } diff --git a/src/classes/SystemMessage.ts b/src/classes/SystemMessage.ts index e0d37104..3dcfbc5e 100644 --- a/src/classes/SystemMessage.ts +++ b/src/classes/SystemMessage.ts @@ -1,18 +1,22 @@ -import { API, Client } from "../index.js"; +import type { SystemMessage as APISystemMessage } from "revolt-api"; + +import type { Client } from "../Client.js"; + +import type { User } from "./User.js"; /** * System Message */ export abstract class SystemMessage { protected client?: Client; - readonly type: API.SystemMessage["type"]; + readonly type: APISystemMessage["type"]; /** * Construct System Message * @param client Client * @param type Type */ - constructor(client: Client, type: API.SystemMessage["type"]) { + constructor(client: Client, type: APISystemMessage["type"]) { this.client = client; this.type = type; } @@ -23,7 +27,7 @@ export abstract class SystemMessage { * @param embed Data * @returns System Message */ - static from(client: Client, message: API.SystemMessage): SystemMessage { + static from(client: Client, message: APISystemMessage): SystemMessage { switch (message.type) { case "text": return new TextSystemMessage(client, message); @@ -64,7 +68,7 @@ export class TextSystemMessage extends SystemMessage { */ constructor( client: Client, - systemMessage: API.SystemMessage & { type: "text" }, + systemMessage: APISystemMessage & { type: "text" }, ) { super(client, systemMessage.type); this.content = systemMessage.content; @@ -84,7 +88,7 @@ export class UserSystemMessage extends SystemMessage { */ constructor( client: Client, - systemMessage: API.SystemMessage & { + systemMessage: APISystemMessage & { type: | "user_added" | "user_remove" @@ -101,7 +105,7 @@ export class UserSystemMessage extends SystemMessage { /** * User this message concerns */ - get user() { + get user(): User | undefined { return this.client!.users.get(this.userId); } } @@ -119,7 +123,7 @@ export class UserModeratedSystemMessage extends UserSystemMessage { */ constructor( client: Client, - systemMessage: API.SystemMessage & { + systemMessage: APISystemMessage & { type: "user_added" | "user_remove"; }, ) { @@ -130,8 +134,7 @@ export class UserModeratedSystemMessage extends UserSystemMessage { /** * User this action was performed by */ - get by() { - console.info("deez!", this.byId); + get by(): User | undefined { return this.client!.users.get(this.byId); } } @@ -149,7 +152,7 @@ export class ChannelEditSystemMessage extends SystemMessage { */ constructor( client: Client, - systemMessage: API.SystemMessage & { + systemMessage: APISystemMessage & { type: | "channel_renamed" | "channel_description_changed" @@ -163,7 +166,7 @@ export class ChannelEditSystemMessage extends SystemMessage { /** * User this action was performed by */ - get by() { + get by(): User | undefined { return this.client!.users.get(this.byId); } } @@ -181,7 +184,7 @@ export class ChannelRenamedSystemMessage extends ChannelEditSystemMessage { */ constructor( client: Client, - systemMessage: API.SystemMessage & { + systemMessage: APISystemMessage & { type: "channel_renamed"; }, ) { @@ -204,7 +207,7 @@ export class ChannelOwnershipChangeSystemMessage extends SystemMessage { */ constructor( client: Client, - systemMessage: API.SystemMessage & { + systemMessage: APISystemMessage & { type: "channel_ownership_changed"; }, ) { @@ -216,14 +219,14 @@ export class ChannelOwnershipChangeSystemMessage extends SystemMessage { /** * User giving away channel ownership */ - get from() { + get from(): User | undefined { return this.client!.users.get(this.fromId); } /** * User receiving channel ownership */ - get to() { + get to(): User | undefined { return this.client!.users.get(this.toId); } } diff --git a/src/classes/User.ts b/src/classes/User.ts index 4243f22e..2ce6cee9 100644 --- a/src/classes/User.ts +++ b/src/classes/User.ts @@ -1,10 +1,13 @@ -import { DataEditUser, Presence } from "revolt-api"; +import type { User as APIUser, DataEditUser, Presence } from "revolt-api"; import { decodeTime } from "ulid"; -import { UserCollection } from "../collections/index.js"; -import { UserProfile } from "../index.js"; +import type { UserCollection } from "../collections/UserCollection.js"; import { U32_MAX, UserPermission } from "../permissions/definitions.js"; +import type { Channel } from "./Channel.js"; +import type { File } from "./File.js"; +import { UserProfile } from "./UserProfile.js"; + /** * User Class */ @@ -26,42 +29,42 @@ export class User { * Write to string as a user mention * @returns Formatted String */ - toString() { + toString(): string { return `<@${this.id}>`; } /** * Whether this object exists */ - get $exists() { + get $exists(): boolean { return !!this.#collection.getUnderlyingObject(this.id).id; } /** * Time when this user created their account */ - get createdAt() { + get createdAt(): Date { return new Date(decodeTime(this.id)); } /** * Username */ - get username() { + get username(): string { return this.#collection.getUnderlyingObject(this.id).username; } /** * Discriminator */ - get discriminator() { + get discriminator(): string { return this.#collection.getUnderlyingObject(this.id).discriminator; } /** * Display Name */ - get displayName() { + get displayName(): string { return ( this.#collection.getUnderlyingObject(this.id).displayName ?? this.#collection.getUnderlyingObject(this.id).username @@ -71,21 +74,23 @@ export class User { /** * Avatar */ - get avatar() { + get avatar(): File | undefined { return this.#collection.getUnderlyingObject(this.id).avatar; } /** * Badges */ - get badges() { + get badges(): number { return this.#collection.getUnderlyingObject(this.id).badges; } /** * User Status */ - get status() { + get status(): + | { text?: string | null; presence?: Presence | null } + | undefined { // TODO: issue with API, upstream fix required #319 if (!this.online) return { text: undefined, presence: "Invisible" as const }; @@ -95,49 +100,49 @@ export class User { /** * Relationship with user */ - get relationship() { + get relationship(): string { return this.#collection.getUnderlyingObject(this.id).relationship; } /** * Whether the user is online */ - get online() { + get online(): boolean { return this.#collection.getUnderlyingObject(this.id).online; } /** * Whether the user is privileged */ - get privileged() { + get privileged(): boolean { return this.#collection.getUnderlyingObject(this.id).privileged; } /** * Flags */ - get flags() { + get flags(): number { return this.#collection.getUnderlyingObject(this.id).flags; } /** * Bot information */ - get bot() { + get bot(): { owner: string } | undefined { return this.#collection.getUnderlyingObject(this.id).bot; } /** * Whether this user is ourselves */ - get self() { + get self(): boolean { return this.#collection.client.user === this; } /** * URL to the user's default avatar */ - get defaultAvatarURL() { + get defaultAvatarURL(): string { return `${this.#collection.client.options.baseURL}/users/${ this.id }/default_avatar`; @@ -146,21 +151,21 @@ export class User { /** * URL to the user's avatar */ - get avatarURL() { + get avatarURL(): string { return this.avatar?.createFileURL() ?? this.defaultAvatarURL; } /** * URL to the user's animated avatar */ - get animatedAvatarURL() { + get animatedAvatarURL(): string { return this.avatar?.createFileURL(true) ?? this.defaultAvatarURL; } /** * Presence */ - get presence() { + get presence(): Presence { return this.online ? (this.status?.presence ?? "Online") : "Invisible"; } @@ -169,7 +174,9 @@ export class User { * @param translate Translation function * @returns Status message */ - statusMessage(translate: (presence: Presence) => string = (a) => a) { + statusMessage( + translate: (presence: Presence) => string = (a) => a, + ): string | undefined { return this.online ? (this.status?.text ?? (this.presence === "Focus" ? translate("Focus") : undefined)) @@ -179,7 +186,7 @@ export class User { /** * Permissions against this user */ - get permission() { + get permission(): number { let permissions = 0; switch (this.relationship) { case "Friend": @@ -217,7 +224,7 @@ export class User { * Edit the user * @param data Changes */ - async edit(data: DataEditUser) { + async edit(data: DataEditUser): Promise { await this.#collection.client.api.patch( `/users/${ this.id === this.#collection.client.user?.id ? "@me" : this.id @@ -231,7 +238,7 @@ export class User { * @param username New username * @param password Current password */ - async changeUsername(username: string, password: string) { + async changeUsername(username: string, password: string): Promise { return await this.#collection.client.api.patch("/users/@me/username", { username, password, @@ -242,7 +249,7 @@ export class User { * Open a DM with a user * @returns DM Channel */ - async openDM() { + async openDM(): Promise { let dm = [...this.#collection.client.channels.values()].find( (x) => x.type === "DirectMessage" && x.recipient == this, ); @@ -269,7 +276,7 @@ export class User { /** * Send a friend request to a user */ - async addFriend() { + async addFriend(): Promise { const user = await this.#collection.client.api.post(`/users/friend`, { username: this.username + "#" + this.discriminator, }); @@ -280,21 +287,21 @@ export class User { /** * Remove a user from the friend list */ - async removeFriend() { + async removeFriend(): Promise { await this.#collection.client.api.delete(`/users/${this.id as ""}/friend`); } /** * Block a user */ - async blockUser() { + async blockUser(): Promise { await this.#collection.client.api.put(`/users/${this.id as ""}/block`); } /** * Unblock a user */ - async unblockUser() { + async unblockUser(): Promise { await this.#collection.client.api.delete(`/users/${this.id as ""}/block`); } @@ -302,7 +309,7 @@ export class User { * Fetch the profile of a user * @returns The profile of the user */ - async fetchProfile() { + async fetchProfile(): Promise { return new UserProfile( this.#collection.client, await this.#collection.client.api.get(`/users/${this.id as ""}/profile`), @@ -313,7 +320,7 @@ export class User { * Fetch the mutual connections of the current user and a target user * @returns The mutual connections of the current user and a target user */ - async fetchMutual() { + async fetchMutual(): Promise<{ users: string[]; servers: string[] }> { return await this.#collection.client.api.get( `/users/${this.id as ""}/mutual`, ); diff --git a/src/classes/UserProfile.ts b/src/classes/UserProfile.ts index a2e10b7e..a2a2b7b1 100644 --- a/src/classes/UserProfile.ts +++ b/src/classes/UserProfile.ts @@ -1,4 +1,8 @@ -import { API, Client, File } from "../index.js"; +import type { UserProfile as APIUserProfile } from "revolt-api"; + +import type { Client } from "../Client.js"; + +import { File } from "./File.js"; /** * User Profile Class @@ -12,7 +16,7 @@ export class UserProfile { * @param client Client * @param data Data */ - constructor(client: Client, data: API.UserProfile) { + constructor(client: Client, data: APIUserProfile) { this.content = data.content!; this.banner = data.background ? new File(client, data.background) @@ -22,14 +26,14 @@ export class UserProfile { /** * URL to the user's banner */ - get bannerURL() { + get bannerURL(): string | undefined { return this.banner?.createFileURL(); } /** * URL to the user's animated banner */ - get animatedBannerURL() { + get animatedBannerURL(): string | undefined { return this.banner?.createFileURL(true); } } diff --git a/src/collections/AccountCollection.ts b/src/collections/AccountCollection.ts index 18f1331b..1f49675e 100644 --- a/src/collections/AccountCollection.ts +++ b/src/collections/AccountCollection.ts @@ -1,7 +1,7 @@ -import { DataCreateAccount, WebPushSubscription } from "revolt-api"; +import type { DataCreateAccount, WebPushSubscription } from "revolt-api"; +import type { Client } from "../Client.js"; import { MFA } from "../classes/MFA.js"; -import { Client } from "../index.js"; /** * Utility functions for working with accounts @@ -21,25 +21,22 @@ export class AccountCollection { * Fetch current account email * @returns Email */ - fetchEmail() { - return this.client.api - .get("/auth/account/") - .then((account) => account.email); + async fetchEmail(): Promise { + return (await this.client.api.get("/auth/account/")).email; } /** * Create a MFA helper */ - async mfa() { - const state = await this.client.api.get("/auth/mfa/"); - return new MFA(this.client, state); + async mfa(): Promise { + return new MFA(this.client, await this.client.api.get("/auth/mfa/")); } /** * Create a new account * @param data Account details */ - create(data: DataCreateAccount) { + create(data: DataCreateAccount): Promise { return this.client.api.post("/auth/account/create", data); } @@ -48,7 +45,7 @@ export class AccountCollection { * @param email Email * @param captcha Captcha if enabled */ - reverify(email: string, captcha?: string) { + reverify(email: string, captcha?: string): Promise { return this.client.api.post("/auth/account/reverify", { email, captcha }); } @@ -57,7 +54,7 @@ export class AccountCollection { * @param email Email * @param captcha Captcha if enabled */ - resetPassword(email: string, captcha?: string) { + resetPassword(email: string, captcha?: string): Promise { return this.client.api.post("/auth/account/reset_password", { email, captcha, @@ -68,7 +65,7 @@ export class AccountCollection { * Verify an account given the code * @param code Verification code */ - verify(code: string) { + verify(code: string): Promise { return this.client.api.post(`/auth/account/verify/${code}`); } @@ -76,7 +73,7 @@ export class AccountCollection { * Confirm account deletion * @param token Deletion token */ - confirmDelete(token: string) { + confirmDelete(token: string): Promise { return this.client.api.put("/auth/account/delete", { token }); } @@ -90,7 +87,7 @@ export class AccountCollection { token: string, newPassword: string, removeSessions: boolean, - ) { + ): Promise { return this.client.api.patch("/auth/account/reset_password", { token, password: newPassword, @@ -103,7 +100,7 @@ export class AccountCollection { * @param newPassword New password * @param currentPassword Current password */ - changePassword(newPassword: string, currentPassword: string) { + changePassword(newPassword: string, currentPassword: string): Promise { return this.client.api.patch("/auth/account/change/password", { password: newPassword, current_password: currentPassword, @@ -115,7 +112,7 @@ export class AccountCollection { * @param newEmail New email * @param currentPassword Current password */ - changeEmail(newEmail: string, currentPassword: string) { + changeEmail(newEmail: string, currentPassword: string): Promise { return this.client.api.patch("/auth/account/change/email", { email: newEmail, current_password: currentPassword, @@ -127,7 +124,7 @@ export class AccountCollection { * @param keys Keys * @returns Settings */ - fetchSettings(keys: string[]) { + fetchSettings(keys: string[]): Promise> { return this.client.api.post("/sync/settings/fetch", { keys }) as Promise< Record >; @@ -139,7 +136,10 @@ export class AccountCollection { * @param settings Settings * @param timestamp Timestamp */ - setSettings(settings: Record, timestamp = +new Date()) { + setSettings( + settings: Record, + timestamp = +new Date(), + ): Promise { return this.client.api.post("/sync/settings/set", { ...settings, timestamp, @@ -151,14 +151,14 @@ export class AccountCollection { * Create a new Web Push subscription * @param subscription Subscription */ - webPushSubscribe(subscription: WebPushSubscription) { + webPushSubscribe(subscription: WebPushSubscription): Promise { return this.client.api.post("/push/subscribe", subscription); } /** * Remove existing Web Push subscription */ - webPushUnsubscribe() { + webPushUnsubscribe(): Promise { return this.client.api.post("/push/unsubscribe"); } } diff --git a/src/collections/BotCollection.ts b/src/collections/BotCollection.ts index ed6dcd15..2b0e2ff5 100644 --- a/src/collections/BotCollection.ts +++ b/src/collections/BotCollection.ts @@ -1,11 +1,12 @@ import { batch } from "solid-js"; -import { OwnedBotsResponse } from "revolt-api"; +import type { Bot as APIBot, OwnedBotsResponse } from "revolt-api"; -import { HydratedBot } from "../hydration/bot.js"; -import { API, Bot, PublicBot } from "../index.js"; +import { Bot } from "../classes/Bot.js"; +import { PublicBot } from "../classes/PublicBot.js"; +import type { HydratedBot } from "../hydration/bot.js"; -import { ClassCollection } from "./index.js"; +import { ClassCollection } from "./Collection.js"; /** * Collection of Bots @@ -54,7 +55,7 @@ export class BotCollection extends ClassCollection { * @param data Data * @returns Bot */ - getOrCreate(id: string, data: API.Bot) { + getOrCreate(id: string, data: APIBot): Bot { if (this.has(id)) { return this.get(id)!; } else { @@ -69,7 +70,7 @@ export class BotCollection extends ClassCollection { * @param name Bot name * @returns The newly-created bot */ - async createBot(name: string) { + async createBot(name: string): Promise { const bot = await this.client.api.post(`/bots/create`, { name, }); diff --git a/src/collections/ChannelCollection.ts b/src/collections/ChannelCollection.ts index 39ed2e62..407f5359 100644 --- a/src/collections/ChannelCollection.ts +++ b/src/collections/ChannelCollection.ts @@ -1,7 +1,10 @@ -import { HydratedChannel } from "../hydration/index.js"; -import { API, Channel, User } from "../index.js"; +import type { Channel as APIChannel } from "revolt-api"; -import { ClassCollection } from "./index.js"; +import { Channel } from "../classes/Channel.js"; +import { User } from "../classes/User.js"; +import type { HydratedChannel } from "../hydration/channel.js"; + +import { ClassCollection } from "./Collection.js"; /** * Collection of Channels @@ -38,7 +41,7 @@ export class ChannelCollection extends ClassCollection< * @param data Data * @param isNew Whether this object is new */ - getOrCreate(id: string, data: API.Channel, isNew = false) { + getOrCreate(id: string, data: APIChannel, isNew = false): Channel { if (this.has(id) && !this.isPartial(id)) { return this.get(id)!; } else { @@ -53,7 +56,7 @@ export class ChannelCollection extends ClassCollection< * Get or return partial * @param id Id */ - getOrPartial(id: string) { + getOrPartial(id: string): Channel | undefined { if (this.has(id)) { return this.get(id)!; } else if (this.client.options.partials) { @@ -72,7 +75,7 @@ export class ChannelCollection extends ClassCollection< * @param users Users to add * @returns The newly-created group */ - async createGroup(name: string, users: (User | string)[]) { + async createGroup(name: string, users: (User | string)[]): Promise { const group = await this.client.api.post(`/channels/create`, { name, users: users.map((user) => (user instanceof User ? user.id : user)), diff --git a/src/collections/ChannelUnreadCollection.ts b/src/collections/ChannelUnreadCollection.ts index 7615b65d..d71e10c8 100644 --- a/src/collections/ChannelUnreadCollection.ts +++ b/src/collections/ChannelUnreadCollection.ts @@ -1,10 +1,11 @@ import { batch } from "solid-js"; -import { ChannelUnread } from "../classes/ChannelUnread.js"; -import { HydratedChannelUnread } from "../hydration/index.js"; -import { API } from "../index.js"; +import type { ChannelUnread as APIChannelUnread } from "revolt-api"; -import { ClassCollection } from "./index.js"; +import { ChannelUnread } from "../classes/ChannelUnread.js"; +import type { HydratedChannelUnread } from "../hydration/channelUnread.js"; + +import { ClassCollection } from "./Collection.js"; /** * Collection of Channel Unreads @@ -16,7 +17,7 @@ export class ChannelUnreadCollection extends ClassCollection< /** * Load unread information from server */ - async sync() { + async sync(): Promise { const unreads = await this.client.api.get("/sync/unreads"); batch(() => { this.reset(); @@ -29,7 +30,7 @@ export class ChannelUnreadCollection extends ClassCollection< /** * Clear all unread data */ - reset() { + reset(): void { this.updateUnderlyingObject({}); } @@ -38,7 +39,7 @@ export class ChannelUnreadCollection extends ClassCollection< * @param id Id * @param data Data */ - getOrCreate(id: string, data: API.ChannelUnread) { + getOrCreate(id: string, data: APIChannelUnread): ChannelUnread { if (this.has(id)) { return this.get(id)!; } else { diff --git a/src/collections/ChannelWebhookCollection.ts b/src/collections/ChannelWebhookCollection.ts index 5fa0fd91..a8cad39c 100644 --- a/src/collections/ChannelWebhookCollection.ts +++ b/src/collections/ChannelWebhookCollection.ts @@ -1,8 +1,9 @@ -import { ChannelWebhook } from "../classes/ChannelWebhook.js"; -import { HydratedChannelWebhook } from "../hydration/channelWebhook.js"; -import { API } from "../index.js"; +import type { Webhook } from "revolt-api"; -import { ClassCollection } from "./index.js"; +import { ChannelWebhook } from "../classes/ChannelWebhook.js"; +import type { HydratedChannelWebhook } from "../hydration/channelWebhook.js"; + +import { ClassCollection } from "./Collection.js"; /** * Collection of Channel Webhooks @@ -22,7 +23,7 @@ export class ChannelWebhookCollection extends ClassCollection< // @ts-expect-error not in prod const data = await this.client.api.get(`/webhooks/${id as ""}`); // @ts-expect-error not in prod - return this.getOrCreate(data.id, data as API.Webhook); + return this.getOrCreate(data.id, data as Webhook); } /** @@ -47,7 +48,7 @@ export class ChannelWebhookCollection extends ClassCollection< * @param id Id * @param data Data */ - getOrCreate(id: string, data: API.Webhook) { + getOrCreate(id: string, data: Webhook): ChannelWebhook { if (this.has(id)) { return this.get(id)!; } else { diff --git a/src/collections/Collection.ts b/src/collections/Collection.ts index 8ca9c576..153daeee 100644 --- a/src/collections/Collection.ts +++ b/src/collections/Collection.ts @@ -1,9 +1,9 @@ -import { SetStoreFunction } from "solid-js/store"; +import type { SetStoreFunction } from "solid-js/store"; import { ReactiveMap } from "@solid-primitives/map"; -import { Hydrators } from "../hydration/index.js"; -import { Client } from "../index.js"; +import type { Client } from "../Client.js"; +import type { Hydrators } from "../hydration/index.js"; import { ObjectStorage } from "../storage/ObjectStorage.js"; /** @@ -66,7 +66,7 @@ export abstract class Collection { * List of values in the map * @returns List */ - toList() { + toList(): T[] { return [...this.values()]; } @@ -143,7 +143,7 @@ export abstract class StoreCollection extends Collection { * @param id Id * @returns Whether it exists */ - has(id: string) { + has(id: string): boolean { return this.#objects.has(id); } @@ -170,7 +170,7 @@ export abstract class StoreCollection extends Collection { instance: T, context: unknown, data?: unknown, - ) { + ): void { this.#storage.hydrate(id, type, context, data); this.#objects.set(id, instance); } @@ -188,7 +188,7 @@ export abstract class StoreCollection extends Collection { * Number of stored objects * @returns Size */ - size() { + size(): number { return this.#objects.size; } @@ -196,7 +196,7 @@ export abstract class StoreCollection extends Collection { * Iterable of keys in the map * @returns Iterable */ - keys() { + keys(): IterableIterator { return this.#objects.keys(); } @@ -204,7 +204,7 @@ export abstract class StoreCollection extends Collection { * Iterable of values in the map * @returns Iterable */ - values() { + values(): IterableIterator { return this.#objects.values(); } @@ -212,7 +212,7 @@ export abstract class StoreCollection extends Collection { * Iterable of key, value pairs in the map * @returns Iterable */ - entries() { + entries(): IterableIterator<[string, T]> { return this.#objects.entries(); } @@ -221,7 +221,7 @@ export abstract class StoreCollection extends Collection { * @param cb Callback for each pair * @returns Iterable */ - forEach(cb: (value: T, key: string, map: Map) => void) { + forEach(cb: (value: T, key: string, map: Map) => void): void { return this.#objects.forEach(cb); } } diff --git a/src/collections/EmojiCollection.ts b/src/collections/EmojiCollection.ts index 559ffad5..eb700f54 100644 --- a/src/collections/EmojiCollection.ts +++ b/src/collections/EmojiCollection.ts @@ -1,7 +1,9 @@ -import { HydratedEmoji } from "../hydration/index.js"; -import { API, Emoji } from "../index.js"; +import type { Emoji as APIEmoji } from "revolt-api"; -import { ClassCollection } from "./index.js"; +import { Emoji } from "../classes/Emoji.js"; +import type { HydratedEmoji } from "../hydration/emoji.js"; + +import { ClassCollection } from "./Collection.js"; /** * Collection of Emoji @@ -25,7 +27,7 @@ export class EmojiCollection extends ClassCollection { * @param data Data * @param isNew Whether this object is new */ - getOrCreate(id: string, data: API.Emoji, isNew = false) { + getOrCreate(id: string, data: APIEmoji, isNew = false): Emoji { if (this.has(id) && !this.isPartial(id)) { return this.get(id)!; } else { @@ -40,7 +42,7 @@ export class EmojiCollection extends ClassCollection { * Get or return partial * @param id Id */ - getOrPartial(id: string) { + getOrPartial(id: string): Emoji | undefined { if (this.has(id)) { return this.get(id)!; } else if (this.client.options.partials) { diff --git a/src/collections/MessageCollection.ts b/src/collections/MessageCollection.ts index 23b1a0c0..efd351de 100644 --- a/src/collections/MessageCollection.ts +++ b/src/collections/MessageCollection.ts @@ -1,7 +1,9 @@ -import { HydratedMessage } from "../hydration"; -import { API, Message } from "../index.js"; +import type { Message as APIMessage } from "revolt-api"; -import { ClassCollection } from "./index.js"; +import { Message } from "../classes/Message.js"; +import type { HydratedMessage } from "../hydration/message.js"; + +import { ClassCollection } from "./Collection.js"; /** * Collection of Messages @@ -33,7 +35,7 @@ export class MessageCollection extends ClassCollection< * @param data Data * @param isNew Whether this object is new */ - getOrCreate(id: string, data: API.Message, isNew = false) { + getOrCreate(id: string, data: APIMessage, isNew = false): Message { if (this.has(id) && !this.isPartial(id)) { return this.get(id)!; } else { @@ -48,7 +50,7 @@ export class MessageCollection extends ClassCollection< * Get or return partial * @param id Id */ - getOrPartial(id: string) { + getOrPartial(id: string): Message | undefined { if (this.has(id)) { return this.get(id)!; } else if (this.client.options.partials) { diff --git a/src/collections/ServerCollection.ts b/src/collections/ServerCollection.ts index 8605db79..e7c30c0e 100644 --- a/src/collections/ServerCollection.ts +++ b/src/collections/ServerCollection.ts @@ -1,11 +1,15 @@ import { batch } from "solid-js"; -import { DataCreateServer } from "revolt-api"; +import type { + Server as APIServer, + Channel, + DataCreateServer, +} from "revolt-api"; -import { HydratedServer } from "../hydration/index.js"; -import { API, Server } from "../index.js"; +import { Server } from "../classes/Server.js"; +import type { HydratedServer } from "../hydration/server.js"; -import { ClassCollection } from "./index.js"; +import { ClassCollection } from "./Collection.js"; /** * Collection of Servers @@ -26,7 +30,7 @@ export class ServerCollection extends ClassCollection { }); return batch(() => { - for (const channel of data.channels as unknown as API.Channel[]) { + for (const channel of data.channels as unknown as Channel[]) { if (typeof channel !== "string") { this.client.channels.getOrCreate(channel._id, channel); } @@ -42,7 +46,7 @@ export class ServerCollection extends ClassCollection { * @param data Data * @param isNew Whether this object is new */ - getOrCreate(id: string, data: API.Server, isNew = false) { + getOrCreate(id: string, data: APIServer, isNew = false): Server { if (this.has(id) && !this.isPartial(id)) { return this.get(id)!; } else { @@ -57,7 +61,7 @@ export class ServerCollection extends ClassCollection { * Get or return partial * @param id Id */ - getOrPartial(id: string) { + getOrPartial(id: string): Server | undefined { if (this.has(id)) { return this.get(id)!; } else if (this.client.options.partials) { @@ -75,7 +79,7 @@ export class ServerCollection extends ClassCollection { * @param data Server options * @returns The newly-created server */ - async createServer(data: DataCreateServer) { + async createServer(data: DataCreateServer): Promise { const { server, channels } = await this.client.api.post( `/servers/create`, data, diff --git a/src/collections/ServerMemberCollection.ts b/src/collections/ServerMemberCollection.ts index 59094f04..d421c2b8 100644 --- a/src/collections/ServerMemberCollection.ts +++ b/src/collections/ServerMemberCollection.ts @@ -1,7 +1,9 @@ -import { HydratedServerMember } from "../hydration/index.js"; -import { API, ServerMember } from "../index.js"; +import type { Member, MemberCompositeKey } from "revolt-api"; -import { ClassCollection } from "./index.js"; +import { ServerMember } from "../classes/ServerMember.js"; +import type { HydratedServerMember } from "../hydration/serverMember.js"; + +import { ClassCollection } from "./Collection.js"; /** * Collection of Server Members @@ -15,7 +17,7 @@ export class ServerMemberCollection extends ClassCollection< * @param id Id * @returns Whether it exists */ - hasByKey(id: API.MemberCompositeKey) { + hasByKey(id: MemberCompositeKey): boolean { return super.has(id.server + id.user); } @@ -24,7 +26,7 @@ export class ServerMemberCollection extends ClassCollection< * @param id Id * @returns Member */ - getByKey(id: API.MemberCompositeKey) { + getByKey(id: MemberCompositeKey): ServerMember | undefined { return super.get(id.server + id.user); } @@ -33,7 +35,7 @@ export class ServerMemberCollection extends ClassCollection< * @param id Id * @returns Member */ - isPartialByKey(id: API.MemberCompositeKey) { + isPartialByKey(id: MemberCompositeKey): boolean { return super.isPartial(id.server + id.user); } @@ -53,7 +55,7 @@ export class ServerMemberCollection extends ClassCollection< roles: false, }, // TODO: fix typings in revolt-api - )) as API.Member; + )) as Member; return this.getOrCreate(data._id, data); } @@ -63,7 +65,7 @@ export class ServerMemberCollection extends ClassCollection< * @param id Id * @param data Data */ - getOrCreate(id: API.MemberCompositeKey, data: API.Member) { + getOrCreate(id: MemberCompositeKey, data: Member): ServerMember { if (this.hasByKey(id) && !this.isPartialByKey(id)) { return this.getByKey(id)!; } else { @@ -83,7 +85,7 @@ export class ServerMemberCollection extends ClassCollection< * Get or return partial * @param id Id */ - getOrPartial(id: API.MemberCompositeKey) { + getOrPartial(id: MemberCompositeKey): ServerMember | undefined { if (this.hasByKey(id)) { return this.getByKey(id)!; } else if (this.client.options.partials) { diff --git a/src/collections/SessionCollection.ts b/src/collections/SessionCollection.ts index f08ca4b7..59274b98 100644 --- a/src/collections/SessionCollection.ts +++ b/src/collections/SessionCollection.ts @@ -1,10 +1,11 @@ import { batch } from "solid-js"; -import { Session } from "../classes/index.js"; -import { HydratedSession } from "../hydration/session.js"; -import { API } from "../index.js"; +import type { SessionInfo } from "revolt-api"; -import { ClassCollection } from "./index.js"; +import { Session } from "../classes/Session.js"; +import type { HydratedSession } from "../hydration/session.js"; + +import { ClassCollection } from "./Collection.js"; /** * Collection of Sessions @@ -28,7 +29,7 @@ export class SessionCollection extends ClassCollection< * Delete all sessions, optionally including self * @param revokeSelf Whether to remove current session too */ - async deleteAll(revokeSelf = false) { + async deleteAll(revokeSelf = false): Promise { await this.client.api.delete("/auth/session/all", { revoke_self: revokeSelf, }); @@ -45,7 +46,7 @@ export class SessionCollection extends ClassCollection< * @param data Data * @returns Session */ - getOrCreate(id: string, data: API.SessionInfo) { + getOrCreate(id: string, data: SessionInfo): Session { if (this.has(id)) { return this.get(id)!; } else { diff --git a/src/collections/UserCollection.ts b/src/collections/UserCollection.ts index a052eab2..4ae2c23e 100644 --- a/src/collections/UserCollection.ts +++ b/src/collections/UserCollection.ts @@ -1,7 +1,10 @@ -import { HydratedUser } from "../hydration/index.js"; -import { API, Client, User } from "../index.js"; +import type { User as APIUser } from "revolt-api"; -import { ClassCollection } from "./index.js"; +import type { Client } from "../Client.js"; +import { User } from "../classes/User.js"; +import type { HydratedUser } from "../hydration/user.js"; + +import { ClassCollection } from "./Collection.js"; /** * Collection of Users @@ -41,7 +44,7 @@ export class UserCollection extends ClassCollection { * @param data Data * @param isNew Whether this object is new */ - getOrCreate(id: string, data: API.User) { + getOrCreate(id: string, data: APIUser): User { if (this.has(id) && !this.isPartial(id)) { return this.get(id)!; } else { @@ -55,7 +58,7 @@ export class UserCollection extends ClassCollection { * Get or return partial * @param id Id */ - getOrPartial(id: string) { + getOrPartial(id: string): User | undefined { if (this.has(id)) { return this.get(id)!; } else if (this.client.options.partials) { diff --git a/src/events/EventClient.ts b/src/events/EventClient.ts index 55f89168..f9b9fced 100644 --- a/src/events/EventClient.ts +++ b/src/events/EventClient.ts @@ -1,9 +1,27 @@ -import { Accessor, Setter, createSignal } from "solid-js"; +import type { Accessor, Setter } from "solid-js"; +import { createSignal } from "solid-js"; import { AsyncEventEmitter } from "@vladfrangu/async_event_emitter"; -import { Error } from "revolt-api"; +import type { Error } from "revolt-api"; -import type { AvailableProtocols, EventProtocol } from "./index.js"; +import type { ProtocolV1 } from "./v1.js"; + +/** + * Available protocols to connect with + */ +export type AvailableProtocols = 1; + +/** + * Protocol mapping + */ +type Protocols = { + 1: ProtocolV1; +}; + +/** + * Select a protocol by its key + */ +export type EventProtocol = Protocols[T]; /** * All possible event client states. @@ -117,7 +135,7 @@ export class EventClient< * Set the current state * @param state state */ - private setState(state: ConnectionState) { + private setState(state: ConnectionState): void { this.#setStateSetter(state); this.emit("state", state); } @@ -127,7 +145,7 @@ export class EventClient< * @param uri WebSocket URI * @param token Authentication token */ - connect(uri: string, token: string) { + connect(uri: string, token: string): void { this.disconnect(); this.#lastError = undefined; this.setState(ConnectionState.Connecting); @@ -179,7 +197,7 @@ export class EventClient< /** * Disconnect the websocket client. */ - disconnect() { + disconnect(): void { if (!this.#socket) return; clearInterval(this.#heartbeatIntervalReference); clearInterval(this.#connectTimeoutReference); @@ -193,7 +211,7 @@ export class EventClient< * Send an event to the server. * @param event Event */ - send(event: EventProtocol["client"]) { + send(event: EventProtocol["client"]): void { if (this.options.debug) console.debug("[C->S]", event); if (!this.#socket) throw "Socket closed, trying to send."; this.#socket.send(JSON.stringify(event)); @@ -203,7 +221,7 @@ export class EventClient< * Handle events intended for client before passing them along. * @param event Event */ - handle(event: EventProtocol["server"]) { + handle(event: EventProtocol["server"]): void { if (this.options.debug) console.debug("[S->C]", event); switch (event.type) { case "Ping": @@ -253,7 +271,17 @@ export class EventClient< /** * Last error encountered by events client */ - get lastError() { + get lastError(): + | { + type: "socket"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any; + } + | { + type: "revolt"; + data: Error; + } + | undefined { return this.#lastError; } } diff --git a/src/events/index.ts b/src/events/index.ts deleted file mode 100644 index 33a26217..00000000 --- a/src/events/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { ProtocolV1 } from "./v1.js"; - -export { handleEvent as handleEventV1 } from "./v1.js"; - -export * from "./EventClient.js"; - -/** - * Available protocols to connect with - */ -export type AvailableProtocols = 1; - -/** - * Protocol mapping - */ -type Protocols = { - 1: ProtocolV1; -}; - -/** - * Select a protocol by its key - */ -export type EventProtocol = Protocols[T]; diff --git a/src/events/v1.ts b/src/events/v1.ts index c76bca1b..0834c4b8 100644 --- a/src/events/v1.ts +++ b/src/events/v1.ts @@ -1,4 +1,5 @@ -import { Setter, batch } from "solid-js"; +import type { Setter } from "solid-js"; +import { batch } from "solid-js"; import { ReactiveSet } from "@solid-primitives/set"; import type { @@ -18,8 +19,9 @@ import type { User, } from "revolt-api"; +import type { Client } from "../Client.js"; +import { MessageEmbed } from "../classes/MessageEmbed.js"; import { hydrate } from "../hydration/index.js"; -import { Client, MessageEmbed } from "../index.js"; /** * Version 1 of the events protocol @@ -189,7 +191,7 @@ export async function handleEvent( client: Client, event: ServerMessage, setReady: Setter, -) { +): Promise { if (client.options.debug) { console.debug("[S->C]", event); } diff --git a/src/hydration/bot.ts b/src/hydration/bot.ts index 666b6af3..663d49a8 100644 --- a/src/hydration/bot.ts +++ b/src/hydration/bot.ts @@ -1,6 +1,6 @@ -import { Bot as ApiBot } from "revolt-api"; +import type { Bot as APIBot } from "revolt-api"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedBot = { id: string; @@ -15,7 +15,7 @@ export type HydratedBot = { flags: BotFlags; }; -export const botHydration: Hydrate = { +export const botHydration: Hydrate = { keyMapping: { _id: "id", owner: "ownerId", diff --git a/src/hydration/channel.ts b/src/hydration/channel.ts index aef59c30..5307ff6f 100644 --- a/src/hydration/channel.ts +++ b/src/hydration/channel.ts @@ -1,14 +1,15 @@ import { ReactiveSet } from "@solid-primitives/set"; -import { Channel as ApiChannel, OverrideField } from "revolt-api"; +import type { Channel as APIChannel, OverrideField } from "revolt-api"; -import { Client, File } from "../index.js"; +import type { Client } from "../Client.js"; +import { File } from "../classes/File.js"; import type { Merge } from "../lib/merge.js"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedChannel = { id: string; - channelType: ApiChannel["channel_type"]; + channelType: APIChannel["channel_type"]; name: string; description?: string; @@ -30,7 +31,7 @@ export type HydratedChannel = { lastMessageId?: string; }; -export const channelHydration: Hydrate, HydratedChannel> = { +export const channelHydration: Hydrate, HydratedChannel> = { keyMapping: { _id: "id", channel_type: "channelType", diff --git a/src/hydration/channelUnread.ts b/src/hydration/channelUnread.ts index f293327e..b30f503c 100644 --- a/src/hydration/channelUnread.ts +++ b/src/hydration/channelUnread.ts @@ -1,9 +1,9 @@ import { ReactiveSet } from "@solid-primitives/set"; +import type { ChannelUnread } from "revolt-api"; -import { API } from "../index.js"; import type { Merge } from "../lib/merge.js"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedChannelUnread = { id: string; @@ -12,7 +12,7 @@ export type HydratedChannelUnread = { }; export const channelUnreadHydration: Hydrate< - Merge, + Merge, HydratedChannelUnread > = { keyMapping: { diff --git a/src/hydration/channelWebhook.ts b/src/hydration/channelWebhook.ts index 3a2dd564..ecf3abfb 100644 --- a/src/hydration/channelWebhook.ts +++ b/src/hydration/channelWebhook.ts @@ -1,7 +1,10 @@ -import { API, Client, File } from "../index.js"; +import type { Webhook } from "revolt-api"; + +import type { Client } from "../Client.js"; +import { File } from "../classes/File.js"; import type { Merge } from "../lib/merge.js"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedChannelWebhook = { id: string; @@ -12,7 +15,7 @@ export type HydratedChannelWebhook = { }; export const channelWebhookHydration: Hydrate< - Merge, + Merge, HydratedChannelWebhook > = { keyMapping: { diff --git a/src/hydration/emoji.ts b/src/hydration/emoji.ts index c86aa70f..90e0564f 100644 --- a/src/hydration/emoji.ts +++ b/src/hydration/emoji.ts @@ -1,8 +1,8 @@ -import { Emoji as ApiEmoji, EmojiParent } from "revolt-api"; +import type { Emoji as APIEmoji, EmojiParent } from "revolt-api"; import type { Merge } from "../lib/merge.js"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedEmoji = { id: string; @@ -13,7 +13,7 @@ export type HydratedEmoji = { nsfw: boolean; }; -export const emojiHydration: Hydrate, HydratedEmoji> = { +export const emojiHydration: Hydrate, HydratedEmoji> = { keyMapping: { _id: "id", creator_id: "creatorId", diff --git a/src/hydration/index.ts b/src/hydration/index.ts index 7c3f742f..0852ef9d 100644 --- a/src/hydration/index.ts +++ b/src/hydration/index.ts @@ -9,21 +9,6 @@ import { serverMemberHydration } from "./serverMember.js"; import { sessionHydration } from "./session.js"; import { userHydration } from "./user.js"; -export { BotFlags } from "./bot.js"; -export { ServerFlags } from "./server.js"; -export { UserBadges, UserFlags } from "./user.js"; - -export type { HydratedBot } from "./bot.js"; -export type { HydratedChannel } from "./channel.js"; -export type { HydratedChannelUnread } from "./channelUnread.js"; -export type { HydratedChannelWebhook } from "./channelWebhook.js"; -export type { HydratedEmoji } from "./emoji.js"; -export type { HydratedMessage } from "./message.js"; -export type { HydratedServer } from "./server.js"; -export type { HydratedServerMember } from "./serverMember.js"; -export type { HydratedSession } from "./session.js"; -export type { HydratedUser } from "./user.js"; - /** * Functions to map from one object to another */ @@ -114,7 +99,7 @@ export function hydrate( input: Partial>, context: unknown, initial?: boolean, -) { +): ExtractOutput { return hydrateInternal( hydrators[type] as never, initial ? { ...hydrators[type].initialHydration(), ...input } : input, diff --git a/src/hydration/message.ts b/src/hydration/message.ts index a75ed487..22744988 100644 --- a/src/hydration/message.ts +++ b/src/hydration/message.ts @@ -1,17 +1,15 @@ import { ReactiveMap } from "@solid-primitives/map"; import { ReactiveSet } from "@solid-primitives/set"; +import type { Interactions, Masquerade, Message } from "revolt-api"; -import { - API, - Client, - File, - MessageEmbed, - MessageWebhook, - SystemMessage, -} from "../index.js"; +import type { Client } from "../Client.js"; +import { File } from "../classes/File.js"; +import { MessageWebhook } from "../classes/Message.js"; +import { MessageEmbed } from "../classes/MessageEmbed.js"; +import { SystemMessage } from "../classes/SystemMessage.js"; import type { Merge } from "../lib/merge.js"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedMessage = { id: string; @@ -27,12 +25,12 @@ export type HydratedMessage = { mentionIds?: string[]; replyIds?: string[]; reactions: ReactiveMap>; - interactions?: API.Interactions; - masquerade?: API.Masquerade; + interactions?: Interactions; + masquerade?: Masquerade; flags?: number; }; -export const messageHydration: Hydrate, HydratedMessage> = { +export const messageHydration: Hydrate, HydratedMessage> = { keyMapping: { _id: "id", channel: "channelId", diff --git a/src/hydration/server.ts b/src/hydration/server.ts index af80f0fc..0cb19b03 100644 --- a/src/hydration/server.ts +++ b/src/hydration/server.ts @@ -1,15 +1,16 @@ import { ReactiveMap } from "@solid-primitives/map"; import { ReactiveSet } from "@solid-primitives/set"; -import { - Server as ApiServer, +import type { + Server as APIServer, Category, Role, SystemMessageChannels, } from "revolt-api"; -import { Client, File } from "../index.js"; +import type { Client } from "../Client.js"; +import { File } from "../classes/File.js"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedServer = { id: string; @@ -34,7 +35,7 @@ export type HydratedServer = { nsfw: boolean; }; -export const serverHydration: Hydrate = { +export const serverHydration: Hydrate = { keyMapping: { _id: "id", owner: "ownerId", diff --git a/src/hydration/serverMember.ts b/src/hydration/serverMember.ts index 1d2c6ce2..ce3137ae 100644 --- a/src/hydration/serverMember.ts +++ b/src/hydration/serverMember.ts @@ -1,9 +1,10 @@ -import { Member as ApiMember, MemberCompositeKey } from "revolt-api"; +import type { Member as APIMember, MemberCompositeKey } from "revolt-api"; -import { Client, File } from "../index.js"; +import type { Client } from "../Client.js"; +import { File } from "../classes/File.js"; import type { Merge } from "../lib/merge.js"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedServerMember = { id: MemberCompositeKey; @@ -15,7 +16,7 @@ export type HydratedServerMember = { }; export const serverMemberHydration: Hydrate< - Merge, + Merge, HydratedServerMember > = { keyMapping: { diff --git a/src/hydration/session.ts b/src/hydration/session.ts index 11b0c31b..d16221e4 100644 --- a/src/hydration/session.ts +++ b/src/hydration/session.ts @@ -1,13 +1,13 @@ -import { SessionInfo as ApiSession } from "revolt-api"; +import type { SessionInfo as APISession } from "revolt-api"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedSession = { id: string; name: string; }; -export const sessionHydration: Hydrate = { +export const sessionHydration: Hydrate = { keyMapping: { _id: "id", }, diff --git a/src/hydration/user.ts b/src/hydration/user.ts index d57a18ec..952b5290 100644 --- a/src/hydration/user.ts +++ b/src/hydration/user.ts @@ -1,13 +1,14 @@ -import { - User as ApiUser, +import type { + User as APIUser, BotInformation, RelationshipStatus, UserStatus, } from "revolt-api"; -import { Client, File } from "../index.js"; +import type { Client } from "../Client.js"; +import { File } from "../classes/File.js"; -import { Hydrate } from "./index.js"; +import type { Hydrate } from "./index.js"; export type HydratedUser = { id: string; @@ -28,7 +29,7 @@ export type HydratedUser = { bot?: BotInformation; }; -export const userHydration: Hydrate = { +export const userHydration: Hydrate = { keyMapping: { _id: "id", display_name: "displayName", diff --git a/src/index.ts b/src/index.ts index f32105c5..e15d7e9f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,11 +3,8 @@ export { Client } from "./Client.js"; export type { ClientOptions, Session as PrivateSession } from "./Client.js"; export * from "./classes/index.js"; export * from "./collections/index.js"; -export { ConnectionState, EventClient } from "./events/index.js"; -export { - BotFlags, - ServerFlags, - UserBadges, - UserFlags, -} from "./hydration/index.js"; +export { ConnectionState, EventClient } from "./events/EventClient.js"; +export { BotFlags } from "./hydration/bot.js"; +export { ServerFlags } from "./hydration/server.js"; +export { UserBadges, UserFlags } from "./hydration/user.js"; export * from "./lib/regex.js"; diff --git a/src/permissions/calculator.ts b/src/permissions/calculator.ts index 28bd3c8d..2daab6ef 100644 --- a/src/permissions/calculator.ts +++ b/src/permissions/calculator.ts @@ -13,7 +13,7 @@ import { * @param a Input A * @param b Inputs (OR'd together) */ -export function bitwiseAndEq(a: number, ...b: number[]) { +export function bitwiseAndEq(a: number, ...b: number[]): boolean { const value = b.reduce((prev, cur) => prev | BigInt(cur), 0n); return (value & BigInt(a)) === value; } diff --git a/src/storage/ObjectStorage.ts b/src/storage/ObjectStorage.ts index 458b50c3..366389c1 100644 --- a/src/storage/ObjectStorage.ts +++ b/src/storage/ObjectStorage.ts @@ -1,6 +1,7 @@ -import { SetStoreFunction, createStore } from "solid-js/store"; +import type { SetStoreFunction } from "solid-js/store"; +import { createStore } from "solid-js/store"; -import { Hydrators, hydrate } from "../hydration/index.js"; +import { type Hydrators, hydrate } from "../hydration/index.js"; /** * Wrapper around Solid.js store @@ -24,7 +25,7 @@ export class ObjectStorage { * @param id ID * @returns Object */ - get(id: string) { + get(id: string): T | undefined { return this.store[id]; } @@ -35,7 +36,12 @@ export class ObjectStorage { * @param context Context * @param data Input Data */ - hydrate(id: string, type: keyof Hydrators, context: unknown, data?: unknown) { + hydrate( + id: string, + type: keyof Hydrators, + context: unknown, + data?: unknown + ): void { if (data) { data = { partial: false, ...data }; this.set(id, hydrate(type, data as never, context, true) as T);