chore: use explicit return types and fix imports

This commit is contained in:
Jersey
2025-03-14 15:34:20 -04:00
parent 63c2b643ec
commit f7c75a169f
50 changed files with 763 additions and 631 deletions
+39 -44
View File
@@ -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 EventEmitter from "eventemitter3";
import type { DataLogin, Error, RevoltConfig } from "revolt-api";
import { API, Role } from "revolt-api";
import type { DataLogin, RevoltConfig, Role } from "revolt-api";
import { API } 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;
@@ -275,21 +270,21 @@ export class Client extends EventEmitter<Events> {
});
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];
@@ -298,7 +293,7 @@ export class Client extends EventEmitter<Events> {
/**
* Connect to Revolt
*/
connect() {
connect(): void {
clearTimeout(this.#reconnectTimeout);
this.events.disconnect();
this.#setReady(false);
@@ -311,7 +306,7 @@ export class Client extends EventEmitter<Events> {
/**
* Fetches the configuration of the server if it has not been already fetched.
*/
async #fetchConfiguration() {
async #fetchConfiguration(): Promise<void> {
if (!this.configuration) {
this.configuration = await this.api.get("/");
}
@@ -320,7 +315,7 @@ export class Client extends EventEmitter<Events> {
/**
* Update API object to use authentication.
*/
#updateHeaders() {
#updateHeaders(): void {
(this.api as API) = new API({
baseURL: this.options.baseURL,
authentication: {
@@ -334,7 +329,7 @@ export class Client extends EventEmitter<Events> {
* @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<void> {
await this.#fetchConfiguration();
const data = await this.api.post("/auth/session/login", details);
if (data.result === "Success") {
@@ -348,7 +343,7 @@ export class Client extends EventEmitter<Events> {
/**
* Use an existing session
*/
async useExistingSession(session: Session) {
useExistingSession(session: Session): void {
this.#session = session;
this.#updateHeaders();
}
@@ -357,7 +352,7 @@ export class Client extends EventEmitter<Events> {
* Log in as a bot
* @param token Bot token
*/
async loginBot(token: string) {
async loginBot(token: string): Promise<void> {
await this.#fetchConfiguration();
this.#session = token;
this.#updateHeaders();
@@ -369,7 +364,7 @@ export class Client extends EventEmitter<Events> {
* @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);
+5 -3
View File
@@ -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;
+21 -18
View File
@@ -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<void> {
await this.#collection.client.api.patch(`/bots/${this.id as ""}`, data);
}
/**
* Delete a bot
*/
async delete() {
async delete(): Promise<void> {
await this.#collection.client.api.delete(`/bots/${this.id as ""}`);
this.#collection.delete(this.id);
}
+91 -67
View File
@@ -1,25 +1,34 @@
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 { Message } from "../index.js";
import type { ChannelCollection } from "../collections/ChannelCollection.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
*/
@@ -41,35 +50,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 {
@@ -80,21 +89,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"
@@ -105,35 +114,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<string> {
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)!
);
@@ -142,14 +151,14 @@ export class Channel {
/**
* User ids of recipients of the group
*/
get recipientIds() {
get recipientIds(): ReactiveSet<string> {
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)!);
@@ -158,7 +167,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
@@ -169,14 +178,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!
);
@@ -185,14 +194,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!
);
@@ -201,14 +210,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!
);
@@ -217,49 +226,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<string, { a: number; d: number }> | 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;
@@ -268,14 +277,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" ||
@@ -295,7 +304,7 @@ export class Channel {
/**
* Get mentions in this channel for user.
*/
get mentions() {
get mentions(): ReactiveSet<string> | undefined {
if (this.type === "SavedMessages" || this.type === "VoiceChannel")
return undefined;
@@ -306,21 +315,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) ||
@@ -342,7 +351,7 @@ export class Channel {
/**
* Permission the currently authenticated user has against this channel
*/
get permission() {
get permission(): number {
return calculatePermission(this.#collection.client, this);
}
@@ -351,7 +360,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])
@@ -363,7 +372,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])
@@ -376,7 +385,7 @@ export class Channel {
* @requires `Group`
* @returns An array of the channel's members.
*/
async fetchMembers() {
async fetchMembers(): Promise<User[]> {
const members = await this.#collection.client.api.get(
`/channels/${this.id as ""}/members`
);
@@ -393,7 +402,7 @@ export class Channel {
* @requires `TextChannel`, `Group`
* @returns Webhooks
*/
async fetchWebhooks() {
async fetchWebhooks(): Promise<ChannelWebhook[]> {
const webhooks = await this.#collection.client.api.get(
`/channels/${this.id as ""}/webhooks`
);
@@ -409,7 +418,7 @@ export class Channel {
* Edit a channel
* @param data Changes
*/
async edit(data: DataEditChannel) {
async edit(data: DataEditChannel): Promise<void> {
await this.#collection.client.api.patch(`/channels/${this.id as ""}`, data);
}
@@ -418,7 +427,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<void> {
await this.#collection.client.api.delete(`/channels/${this.id as ""}`, {
leave_silently: leaveSilently,
});
@@ -436,7 +445,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<void> {
return await this.#collection.client.api.put(
`/channels/${this.id as ""}/recipients/${user_id as ""}`
);
@@ -447,7 +456,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<void> {
return await this.#collection.client.api.delete(
`/channels/${this.id as ""}/recipients/${user_id as ""}`
);
@@ -462,7 +471,7 @@ export class Channel {
async sendMessage(
data: string | DataMessageSend,
idempotencyKey: string = ulid()
) {
): Promise<Message> {
const msg: DataMessageSend =
typeof data === "string" ? { content: data } : data;
@@ -496,7 +505,7 @@ export class Channel {
* @requires `SavedMessages`, `DirectMessage`, `Group`, `TextChannel`
* @returns Message
*/
async fetchMessage(messageId: string) {
async fetchMessage(messageId: string): Promise<Message> {
const message = await this.#collection.client.api.get(
`/channels/${this.id as ""}/messages/${messageId as ""}`
);
@@ -518,11 +527,11 @@ export class Channel {
})["params"],
"include_users"
>
) {
): Promise<Message[]> {
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)
@@ -543,11 +552,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) =>
@@ -568,11 +581,13 @@ export class Channel {
* @requires `SavedMessages`, `DirectMessage`, `Group`, `TextChannel`
* @returns Messages
*/
async search(params: Omit<DataMessageSearch, "include_users">) {
async search(
params: Omit<DataMessageSearch, "include_users">
): Promise<Message[]> {
const messages = (await this.#collection.client.api.post(
`/channels/${this.id as ""}/search`,
params
)) as ApiMessage[];
)) as APIMessage[];
return batch(() =>
messages.map((message) =>
@@ -587,14 +602,20 @@ export class Channel {
* @requires `SavedMessages`, `DirectMessage`, `Group`, `TextChannel`
* @returns Object including messages and users
*/
async searchWithUsers(params: Omit<DataMessageSearch, "include_users">) {
async searchWithUsers(
params: Omit<DataMessageSearch, "include_users">
): 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) =>
@@ -614,7 +635,7 @@ export class Channel {
* @param ids List of message IDs
* @requires `SavedMessages`, `DirectMessage`, `Group`, `TextChannel`
*/
async deleteMessages(ids: string[]) {
async deleteMessages(ids: string[]): Promise<void> {
await this.#collection.client.api.delete(
`/channels/${this.id as ""}/messages/bulk`,
{
@@ -628,7 +649,7 @@ export class Channel {
* @requires `TextChannel`, `VoiceChannel`
* @returns Newly created invite code
*/
async createInvite() {
async createInvite(): Promise<Invite> {
return await this.#collection.client.api.post(
`/channels/${this.id as ""}/invites`
);
@@ -651,7 +672,7 @@ export class Channel {
skipRateLimiter?: boolean,
skipRequest?: boolean,
skipNextMarking?: boolean
) {
): Promise<void> {
if (!message && this.#manuallyMarked) {
this.#manuallyMarked = false;
return;
@@ -684,7 +705,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 ""}`
@@ -712,7 +733,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<APIChannel> {
return await this.#collection.client.api.put(
`/channels/${this.id as ""}/permissions/${role_id as ""}`,
{ permissions }
@@ -723,7 +747,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,
@@ -734,7 +758,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,
+6 -4
View File
@@ -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<string> {
return this.#collection.getUnderlyingObject(this.id).messageMentionIds;
}
}
+13 -10
View File
@@ -1,6 +1,9 @@
import { ChannelWebhookCollection } from "../collections/index.js";
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 +24,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 +54,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 +70,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,7 +78,7 @@ export class ChannelWebhook {
* Edit this webhook
* TODO: not in production
*/
async edit(data: any /*: DataEditWebhook*/) {
async edit(data: any /*: DataEditWebhook*/): Promise<void> {
const webhook = await this.#collection.client.api.patch(
// @ts-expect-error not in prod
`/webhooks/${this.id as ""}/${this.token as ""}`,
@@ -93,7 +96,7 @@ export class ChannelWebhook {
* Delete this webhook
* TODO: not in production
*/
async delete() {
async delete(): Promise<void> {
await this.#collection.client.api.delete(
// @ts-expect-error not in prod
`/webhooks/${this.id}/${this.token}`
+13 -10
View File
@@ -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<void> {
await this.#collection.client.api.delete(`/custom/emoji/${this.id}`);
const emoji = this.#collection.getUnderlyingObject(this.id);
+8 -8
View File
@@ -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<API.File, "_id" | "tag" | "metadata"> & Partial<API.File>
file: Pick<APIFile, "_id" | "tag" | "metadata"> & Partial<APIFile>
) {
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;
+15 -9
View File
@@ -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<void> {
await this.client!.api.delete(`/invites/${this.id}`);
}
}
+24 -21
View File
@@ -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<MFATicket> {
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<void> {
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<string[]> {
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<string[]> {
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<string> {
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<void> {
this.#consume();
await this.#client.api.delete("/auth/mfa/totp", undefined, {
@@ -179,7 +182,7 @@ export class MFATicket {
/**
* Disable account
*/
disableAccount() {
disableAccount(): Promise<void> {
this.#consume();
return this.#client.api.post("/auth/account/disable", undefined, {
headers: {
@@ -191,7 +194,7 @@ export class MFATicket {
/**
* Delete account
*/
deleteAccount() {
deleteAccount(): Promise<void> {
this.#consume();
return this.#client.api.post("/auth/account/delete", undefined, {
headers: {
+56 -44
View File
@@ -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<string, ReactiveSet<string>> {
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 (
@@ -215,14 +227,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 (
@@ -236,7 +248,7 @@ export class Message {
/**
* Get the animated avatar URL for this message
*/
get animatedAvatarURL() {
get animatedAvatarURL(): string | undefined {
const webhook = this.webhook;
return (
@@ -252,7 +264,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;
}
@@ -260,7 +272,7 @@ export class Message {
/**
* Whether this message has suppressed desktop/push notifications
*/
get isSuppressed() {
get isSuppressed(): boolean {
return (this.flags & 1) === 1;
}
@@ -268,7 +280,7 @@ export class Message {
* Edit a message
* @param data Message edit route data
*/
async edit(data: DataEditMessage) {
async edit(data: DataEditMessage): Promise<APIMessage> {
return await this.#collection.client.api.patch(
`/channels/${this.channelId as ""}/messages/${this.id as ""}`,
data
@@ -278,7 +290,7 @@ export class Message {
/**
* Delete a message
*/
async delete() {
async delete(): Promise<void> {
return await this.#collection.client.api.delete(
`/channels/${this.channelId as ""}/messages/${this.id as ""}`
);
@@ -287,7 +299,7 @@ export class Message {
/**
* Acknowledge this message as read
*/
ack() {
ack(): void {
this.channel?.ack(this);
}
@@ -301,7 +313,7 @@ export class Message {
nonce?: string;
}),
mention = true
) {
): Promise<Message> | undefined {
const obj = typeof data === "string" ? { content: data } : data;
return this.channel?.sendMessage({
...obj,
@@ -312,7 +324,7 @@ export class Message {
/**
* Clear all reactions from this message
*/
async clearReactions() {
async clearReactions(): Promise<void> {
return await this.#collection.client.api.delete(
`/channels/${this.channelId as ""}/messages/${this.id as ""}/reactions`
);
@@ -322,7 +334,7 @@ export class Message {
* React to a message
* @param emoji Unicode or emoji ID
*/
async react(emoji: string) {
async react(emoji: string): Promise<void> {
return await this.#collection.client.api.put(
`/channels/${this.channelId as ""}/messages/${this.id as ""}/reactions/${
emoji as ""
@@ -334,7 +346,7 @@ export class Message {
* Un-react from a message
* @param emoji Unicode or emoji ID
*/
async unreact(emoji: string) {
async unreact(emoji: string): Promise<void> {
return await this.#collection.client.api.delete(
`/channels/${this.channelId as ""}/messages/${this.id as ""}/reactions/${
emoji as ""
@@ -358,7 +370,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;
@@ -378,7 +390,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`
+19 -24
View File
@@ -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<API.Embed & { type: "Image" }, "type">
) {
constructor(client: Client, embed: Omit<Embed & { type: "Image" }, "type">) {
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<API.Embed & { type: "Video" }, "type">
) {
constructor(client: Client, embed: Omit<Embed & { type: "Video" }, "type">) {
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<API.Embed & { type: "Website" }, "type">
embed: Omit<Embed & { type: "Website" }, "type">
) {
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<API.Embed & { type: "Text" }, "type">
) {
constructor(client: Client, embed: Omit<Embed & { type: "Text" }, "type">) {
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;
}
}
+11 -5
View File
@@ -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,
+12 -7
View File
@@ -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<Server> {
const existingServer = this.client!.servers.get(this.serverId);
if (existingServer) return existingServer;
+87 -58
View File
@@ -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<string> {
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<Channel> {
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<void> {
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<void> {
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<void> {
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<ServerBan> {
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<void> {
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<void> {
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<ChannelInvite[]> {
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<ServerBan[]> {
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<APIServer> {
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<Role> {
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<void> {
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<ServerMember> {
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<void> {
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<DataCreateEmoji, "parent">
) {
): Promise<Emoji> {
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<Emoji[]> {
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<void> {
await this.#collection.client.api.delete(`/custom/emoji/${emojiId}`);
}
}
+10 -7
View File
@@ -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<void> {
await this.client.api.delete(
`/servers/${this.id.server as ""}/bans/${this.id.user as ""}`
);
+29 -26
View File
@@ -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<Role> & { 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<Role> | 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<void> {
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<void> {
this.server?.banUser(this, options);
}
/**
* Kick this member from the server
*/
async kick() {
async kick(): Promise<void> {
this.server?.kickUser(this);
}
}
+8 -8
View File
@@ -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<void> {
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<void> {
await this.#collection.client.api.delete(`/auth/session/${this.id as ""}`);
this.#collection.delete(this.id);
}
+19 -16
View File
@@ -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);
}
}
+40 -33
View File
@@ -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<void> {
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<APIUser> {
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<Channel> {
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<User> {
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<void> {
await this.#collection.client.api.delete(`/users/${this.id as ""}/friend`);
}
/**
* Block a user
*/
async blockUser() {
async blockUser(): Promise<void> {
await this.#collection.client.api.put(`/users/${this.id as ""}/block`);
}
/**
* Unblock a user
*/
async unblockUser() {
async unblockUser(): Promise<void> {
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<UserProfile> {
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`
);
+8 -4
View File
@@ -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);
}
}
+21 -21
View File
@@ -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<string> {
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<MFA> {
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<void> {
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<void> {
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<void> {
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<unknown> {
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<void> {
return this.client.api.put("/auth/account/delete", { token });
}
@@ -90,7 +87,7 @@ export class AccountCollection {
token: string,
newPassword: string,
removeSessions: boolean
) {
): Promise<void> {
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<void> {
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<void> {
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<Record<string, [number, string]>> {
return this.client.api.post("/sync/settings/fetch", { keys }) as Promise<
Record<string, [number, string]>
>;
@@ -139,7 +136,10 @@ export class AccountCollection {
* @param settings Settings
* @param timestamp Timestamp
*/
setSettings(settings: Record<string, any>, timestamp = +new Date()) {
setSettings(
settings: Record<string, any>,
timestamp = +new Date()
): Promise<void> {
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<void> {
return this.client.api.post("/push/subscribe", subscription);
}
/**
* Remove existing Web Push subscription
*/
webPushUnsubscribe() {
webPushUnsubscribe(): Promise<void> {
return this.client.api.post("/push/unsubscribe");
}
}
+7 -6
View File
@@ -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<Bot, HydratedBot> {
* @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<Bot, HydratedBot> {
* @param name Bot name
* @returns The newly-created bot
*/
async createBot(name: string) {
async createBot(name: string): Promise<Bot> {
const bot = await this.client.api.post(`/bots/create`, {
name,
});
+9 -6
View File
@@ -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<Channel> {
const group = await this.client.api.post(`/channels/create`, {
name,
users: users.map((user) => (user instanceof User ? user.id : user)),
+8 -7
View File
@@ -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<void> {
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 {
+7 -6
View File
@@ -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 {
+12 -12
View File
@@ -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";
/**
@@ -59,14 +59,14 @@ export abstract class Collection<T> {
* @param cb Callback for each pair
*/
abstract forEach(
cb: (value: T, key: string, map: ReactiveMap<string, T>) => void
cb: (value: T, key: string, map: Map<string, T>) => void
): void;
/**
* List of values in the map
* @returns List
*/
toList() {
toList(): T[] {
return [...this.values()];
}
@@ -143,7 +143,7 @@ export abstract class StoreCollection<T, V> extends Collection<T> {
* @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<T, V> extends Collection<T> {
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<T, V> extends Collection<T> {
* Number of stored objects
* @returns Size
*/
size() {
size(): number {
return this.#objects.size;
}
@@ -196,7 +196,7 @@ export abstract class StoreCollection<T, V> extends Collection<T> {
* Iterable of keys in the map
* @returns Iterable
*/
keys() {
keys(): IterableIterator<string> {
return this.#objects.keys();
}
@@ -204,7 +204,7 @@ export abstract class StoreCollection<T, V> extends Collection<T> {
* Iterable of values in the map
* @returns Iterable
*/
values() {
values(): IterableIterator<T> {
return this.#objects.values();
}
@@ -212,7 +212,7 @@ export abstract class StoreCollection<T, V> extends Collection<T> {
* 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<T, V> extends Collection<T> {
* @param cb Callback for each pair
* @returns Iterable
*/
forEach(cb: (value: T, key: string, map: ReactiveMap<string, T>) => void) {
forEach(cb: (value: T, key: string, map: Map<string, T>) => void): void {
return this.#objects.forEach(cb);
}
}
+7 -5
View File
@@ -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<Emoji, HydratedEmoji> {
* @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<Emoji, HydratedEmoji> {
* 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) {
+7 -5
View File
@@ -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) {
+12 -8
View File
@@ -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<Server, HydratedServer> {
});
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<Server, HydratedServer> {
* @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<Server, HydratedServer> {
* 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<Server, HydratedServer> {
* @param data Server options
* @returns The newly-created server
*/
async createServer(data: DataCreateServer) {
async createServer(data: DataCreateServer): Promise<Server> {
const { server, channels } = await this.client.api.post(
`/servers/create`,
data
+11 -9
View File
@@ -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) {
+7 -6
View File
@@ -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<void> {
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 {
+8 -5
View File
@@ -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<User, HydratedUser> {
* @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<User, HydratedUser> {
* 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) {
+37 -9
View File
@@ -1,10 +1,28 @@
import { Accessor, Setter, createSignal } from "solid-js";
import type { Accessor, Setter } from "solid-js";
import { createSignal } from "solid-js";
import EventEmitter from "eventemitter3";
import WebSocket from "isomorphic-ws";
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<T extends AvailableProtocols> = Protocols[T];
/**
* All possible event client states.
@@ -118,7 +136,7 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
* Set the current state
* @param state state
*/
private setState(state: ConnectionState) {
private setState(state: ConnectionState): void {
this.#setStateSetter(state);
this.emit("state", state);
}
@@ -128,7 +146,7 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
* @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);
@@ -182,7 +200,7 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
/**
* Disconnect the websocket client.
*/
disconnect() {
disconnect(): void {
if (!this.#socket) return;
clearInterval(this.#heartbeatIntervalReference);
clearInterval(this.#connectTimeoutReference);
@@ -196,7 +214,7 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
* Send an event to the server.
* @param event Event
*/
send(event: EventProtocol<T>["client"]) {
send(event: EventProtocol<T>["client"]): void {
this.options.debug && console.debug("[C->S]", event);
if (!this.#socket) throw "Socket closed, trying to send.";
this.#socket.send(JSON.stringify(event));
@@ -206,7 +224,7 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
* Handle events intended for client before passing them along.
* @param event Event
*/
handle(event: EventProtocol<T>["server"]) {
handle(event: EventProtocol<T>["server"]): void {
this.options.debug && console.debug("[S->C]", event);
switch (event.type) {
case "Ping":
@@ -258,7 +276,17 @@ export class EventClient<T extends AvailableProtocols> extends EventEmitter<
/**
* 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;
}
}
-22
View File
@@ -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<T extends AvailableProtocols> = Protocols[T];
+5 -3
View File
@@ -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<boolean>
) {
): Promise<void> {
if (client.options.debug) {
console.debug("[S->C]", event);
}
+3 -3
View File
@@ -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<ApiBot, HydratedBot> = {
export const botHydration: Hydrate<APIBot, HydratedBot> = {
keyMapping: {
_id: "id",
owner: "ownerId",
+6 -5
View File
@@ -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<Merge<ApiChannel>, HydratedChannel> = {
export const channelHydration: Hydrate<Merge<APIChannel>, HydratedChannel> = {
keyMapping: {
_id: "id",
channel_type: "channelType",
+3 -3
View File
@@ -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<API.ChannelUnread>,
Merge<ChannelUnread>,
HydratedChannelUnread
> = {
keyMapping: {
+6 -3
View File
@@ -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<API.Webhook>,
Merge<Webhook>,
HydratedChannelWebhook
> = {
keyMapping: {
+3 -3
View File
@@ -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<Merge<ApiEmoji>, HydratedEmoji> = {
export const emojiHydration: Hydrate<Merge<APIEmoji>, HydratedEmoji> = {
keyMapping: {
_id: "id",
creator_id: "creatorId",
+1 -16
View File
@@ -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
*/
@@ -113,7 +98,7 @@ export function hydrate<T extends keyof Hydrators>(
input: Partial<ExtractInput<Hydrators[T]>>,
context: unknown,
initial?: boolean
) {
): ExtractOutput<Hydrators[T]> {
return hydrateInternal(
hydrators[type] as never,
initial ? { ...hydrators[type].initialHydration(), ...input } : input,
+10 -12
View File
@@ -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<string, ReactiveSet<string>>;
interactions?: API.Interactions;
masquerade?: API.Masquerade;
interactions?: Interactions;
masquerade?: Masquerade;
flags?: number;
};
export const messageHydration: Hydrate<Merge<API.Message>, HydratedMessage> = {
export const messageHydration: Hydrate<Merge<Message>, HydratedMessage> = {
keyMapping: {
_id: "id",
channel: "channelId",
+6 -5
View File
@@ -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<ApiServer, HydratedServer> = {
export const serverHydration: Hydrate<APIServer, HydratedServer> = {
keyMapping: {
_id: "id",
owner: "ownerId",
+5 -4
View File
@@ -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<ApiMember>,
Merge<APIMember>,
HydratedServerMember
> = {
keyMapping: {
+3 -3
View File
@@ -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<ApiSession, HydratedSession> = {
export const sessionHydration: Hydrate<APISession, HydratedSession> = {
keyMapping: {
_id: "id",
},
+6 -5
View File
@@ -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<ApiUser, HydratedUser> = {
export const userHydration: Hydrate<APIUser, HydratedUser> = {
keyMapping: {
_id: "id",
display_name: "displayName",
+4 -7
View File
@@ -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";
+5 -2
View File
@@ -1,6 +1,9 @@
import Long from "long";
import { Channel, Client, Server, ServerMember } from "../index.js";
import type { Client } from "../Client.js";
import type { Channel } from "../classes/Channel.js";
import { Server } from "../classes/Server.js";
import type { ServerMember } from "../classes/ServerMember.js";
import {
ALLOW_IN_TIMEOUT,
@@ -15,7 +18,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.or(cur), Long.fromNumber(0));
return value.and(a).eq(value);
}
+10 -4
View File
@@ -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<T> {
* @param id ID
* @returns Object
*/
get(id: string) {
get(id: string): T | undefined {
return this.store[id];
}
@@ -35,7 +36,12 @@ export class ObjectStorage<T> {
* @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);