Files
javascript-client-sdk/src/classes/User.ts
T
2025-10-16 18:40:31 +01:00

329 lines
7.4 KiB
TypeScript

import type { User as APIUser, DataEditUser, Presence } from "stoat-api";
import { decodeTime } from "ulid";
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
*/
export class User {
readonly #collection: UserCollection;
readonly id: string;
/**
* Construct User
* @param collection Collection
* @param id Id
*/
constructor(collection: UserCollection, id: string) {
this.#collection = collection;
this.id = id;
}
/**
* Write to string as a user mention
* @returns Formatted String
*/
toString(): string {
return `<@${this.id}>`;
}
/**
* Whether this object exists
*/
get $exists(): boolean {
return !!this.#collection.getUnderlyingObject(this.id).id;
}
/**
* Time when this user created their account
*/
get createdAt(): Date {
return new Date(decodeTime(this.id));
}
/**
* Username
*/
get username(): string {
return this.#collection.getUnderlyingObject(this.id).username;
}
/**
* Discriminator
*/
get discriminator(): string {
return this.#collection.getUnderlyingObject(this.id).discriminator;
}
/**
* Display Name
*/
get displayName(): string {
return (
this.#collection.getUnderlyingObject(this.id).displayName ??
this.#collection.getUnderlyingObject(this.id).username
);
}
/**
* Avatar
*/
get avatar(): File | undefined {
return this.#collection.getUnderlyingObject(this.id).avatar;
}
/**
* Badges
*/
get badges(): number {
return this.#collection.getUnderlyingObject(this.id).badges;
}
/**
* User 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 };
return this.#collection.getUnderlyingObject(this.id).status;
}
/**
* Relationship with user
*/
get relationship(): string {
return this.#collection.getUnderlyingObject(this.id).relationship;
}
/**
* Whether the user is online
*/
get online(): boolean {
return this.#collection.getUnderlyingObject(this.id).online;
}
/**
* Whether the user is privileged
*/
get privileged(): boolean {
return this.#collection.getUnderlyingObject(this.id).privileged;
}
/**
* Flags
*/
get flags(): number {
return this.#collection.getUnderlyingObject(this.id).flags;
}
/**
* Bot information
*/
get bot(): { owner: string } | undefined {
return this.#collection.getUnderlyingObject(this.id).bot;
}
/**
* Whether this user is ourselves
*/
get self(): boolean {
return this.#collection.client.user === this;
}
/**
* URL to the user's default avatar
*/
get defaultAvatarURL(): string {
return `${this.#collection.client.options.baseURL}/users/${
this.id
}/default_avatar`;
}
/**
* URL to the user's avatar
*/
get avatarURL(): string {
return this.avatar?.createFileURL() ?? this.defaultAvatarURL;
}
/**
* URL to the user's animated avatar
*/
get animatedAvatarURL(): string {
return this.avatar?.createFileURL(true) ?? this.defaultAvatarURL;
}
/**
* Presence
*/
get presence(): Presence {
return this.online ? (this.status?.presence ?? "Online") : "Invisible";
}
/**
* Generate status message
* @param translate Translation function
* @returns Status message
*/
statusMessage(
translate: (presence: Presence) => string = (a) => a,
): string | undefined {
return this.online
? (this.status?.text ??
(this.presence === "Focus" ? translate("Focus") : undefined))
: undefined;
}
/**
* Permissions against this user
*/
get permission(): number {
let permissions = 0;
switch (this.relationship) {
case "Friend":
case "User":
return U32_MAX;
case "Blocked":
case "BlockedOther":
return UserPermission.Access;
case "Incoming":
case "Outgoing":
permissions = UserPermission.Access;
}
if (
this.#collection.client.channels.find(
(channel) =>
(channel.type === "Group" || channel.type === "DirectMessage") &&
channel.recipientIds.has(this.id),
) ||
this.#collection.client.serverMembers.find(
(member) => member.id.user === this.id,
)
) {
if (this.#collection.client.user?.bot || this.bot) {
permissions |= UserPermission.SendMessage;
}
permissions |= UserPermission.Access | UserPermission.ViewProfile;
}
return permissions;
}
/**
* Edit the user
* @param data Changes
*/
async edit(data: DataEditUser): Promise<void> {
await this.#collection.client.api.patch(
`/users/${
this.id === this.#collection.client.user?.id ? "@me" : this.id
}`,
data,
);
}
/**
* Change the username of the current user
* @param username New username
* @param password Current password
*/
async changeUsername(username: string, password: string): Promise<APIUser> {
return await this.#collection.client.api.patch("/users/@me/username", {
username,
password,
});
}
/**
* Open a DM with a user
* @returns DM Channel
*/
async openDM(): Promise<Channel> {
let dm = [...this.#collection.client.channels.values()].find(
(x) => x.type === "DirectMessage" && x.recipient == this,
);
if (dm) {
if (!dm.active) {
this.#collection.client.channels.updateUnderlyingObject(
dm.id,
"active",
true,
);
}
} else {
const data = await this.#collection.client.api.get(
`/users/${this.id as ""}/dm`,
);
dm = this.#collection.client.channels.getOrCreate(data._id, data)!;
}
return dm;
}
/**
* Send a friend request to a user
*/
async addFriend(): Promise<User> {
const user = await this.#collection.client.api.post(`/users/friend`, {
username: this.username + "#" + this.discriminator,
});
return this.#collection.getOrCreate(user._id, user);
}
/**
* Remove a user from the friend list
*/
async removeFriend(): Promise<void> {
await this.#collection.client.api.delete(`/users/${this.id as ""}/friend`);
}
/**
* Block a user
*/
async blockUser(): Promise<void> {
await this.#collection.client.api.put(`/users/${this.id as ""}/block`);
}
/**
* Unblock a user
*/
async unblockUser(): Promise<void> {
await this.#collection.client.api.delete(`/users/${this.id as ""}/block`);
}
/**
* Fetch the profile of a user
* @returns The profile of the user
*/
async fetchProfile(): Promise<UserProfile> {
return new UserProfile(
this.#collection.client,
await this.#collection.client.api.get(`/users/${this.id as ""}/profile`),
);
}
/**
* 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(): Promise<{ users: string[]; servers: string[] }> {
return await this.#collection.client.api.get(
`/users/${this.id as ""}/mutual`,
);
}
}