handmade-revolt/src/mobx/index.ts

288 lines
8.0 KiB
TypeScript

import isEqual from "lodash.isequal";
import {
makeAutoObservable,
observable,
autorun,
runInAction,
reaction,
makeObservable,
action,
extendObservable,
} from "mobx";
import {
Attachment,
Channels,
Servers,
Users,
} from "revolt.js/dist/api/objects";
import {
RemoveChannelField,
RemoveServerField,
RemoveUserField,
} from "revolt.js/dist/api/routes";
import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
type Nullable<T> = T | null;
function toNullable<T>(data?: T) {
return typeof data === "undefined" ? null : data;
}
export class User {
_id: string;
username: string;
avatar: Nullable<Attachment>;
badges: Nullable<number>;
status: Nullable<Users.Status>;
relationship: Nullable<Users.Relationship>;
online: Nullable<boolean>;
constructor(data: Users.User) {
this._id = data._id;
this.username = data.username;
this.avatar = toNullable(data.avatar);
this.badges = toNullable(data.badges);
this.status = toNullable(data.status);
this.relationship = toNullable(data.relationship);
this.online = toNullable(data.online);
makeAutoObservable(this);
}
@action update(data: Partial<Users.User>, clear?: RemoveUserField) {
const apply = (key: string) => {
// This code has been tested.
// @ts-expect-error
if (data[key] && !isEqual(this[key], data[key])) {
// @ts-expect-error
this[key] = data[key];
}
};
switch (clear) {
case "Avatar":
this.avatar = null;
break;
case "StatusText": {
if (this.status) {
this.status.text = undefined;
}
}
}
apply("username");
apply("avatar");
apply("badges");
apply("status");
apply("relationship");
apply("online");
}
}
export class Channel {
_id: string;
channel_type: Channels.Channel["channel_type"];
// Direct Message
active: Nullable<boolean> = null;
// Group
owner: Nullable<string> = null;
// Server
server: Nullable<string> = null;
// Permissions
permissions: Nullable<number> = null;
default_permissions: Nullable<number> = null;
role_permissions: Nullable<{ [key: string]: number }> = null;
// Common
name: Nullable<string> = null;
icon: Nullable<Attachment> = null;
description: Nullable<string> = null;
recipients: Nullable<string[]> = null;
last_message: Nullable<string | Channels.LastMessage> = null;
constructor(data: Channels.Channel) {
this._id = data._id;
this.channel_type = data.channel_type;
switch (data.channel_type) {
case "DirectMessage": {
this.active = toNullable(data.active);
this.recipients = toNullable(data.recipients);
this.last_message = toNullable(data.last_message);
break;
}
case "Group": {
this.recipients = toNullable(data.recipients);
this.name = toNullable(data.name);
this.owner = toNullable(data.owner);
this.description = toNullable(data.description);
this.last_message = toNullable(data.last_message);
this.icon = toNullable(data.icon);
this.permissions = toNullable(data.permissions);
break;
}
case "TextChannel":
case "VoiceChannel": {
this.server = toNullable(data.server);
this.name = toNullable(data.name);
this.description = toNullable(data.description);
this.icon = toNullable(data.icon);
this.default_permissions = toNullable(data.default_permissions);
this.role_permissions = toNullable(data.role_permissions);
if (data.channel_type === "TextChannel") {
this.last_message = toNullable(data.last_message);
}
break;
}
}
makeAutoObservable(this);
}
@action update(
data: Partial<Channels.Channel>,
clear?: RemoveChannelField,
) {
const apply = (key: string) => {
// This code has been tested.
// @ts-expect-error
if (data[key] && !isEqual(this[key], data[key])) {
// @ts-expect-error
this[key] = data[key];
}
};
switch (clear) {
case "Description":
this.description = null;
break;
case "Icon":
this.icon = null;
break;
}
apply("active");
apply("owner");
apply("permissions");
apply("default_permissions");
apply("role_permissions");
apply("name");
apply("icon");
apply("description");
apply("recipients");
apply("last_message");
}
}
export class Server {
_id: string;
owner: string;
name: string;
description: Nullable<string> = null;
channels: string[] = [];
categories: Nullable<Servers.Category[]> = null;
system_messages: Nullable<Servers.SystemMessageChannels> = null;
roles: Nullable<{ [key: string]: Servers.Role }> = null;
default_permissions: Servers.PermissionTuple;
icon: Nullable<Attachment> = null;
banner: Nullable<Attachment> = null;
constructor(data: Servers.Server) {
this._id = data._id;
this.owner = data.owner;
this.name = data.name;
this.description = toNullable(data.description);
this.channels = data.channels;
this.categories = toNullable(data.categories);
this.system_messages = toNullable(data.system_messages);
this.roles = toNullable(data.roles);
this.default_permissions = data.default_permissions;
this.icon = toNullable(data.icon);
this.banner = toNullable(data.banner);
makeAutoObservable(this);
}
@action update(data: Partial<Servers.Server>, clear?: RemoveServerField) {
const apply = (key: string) => {
// This code has been tested.
// @ts-expect-error
if (data[key] && !isEqual(this[key], data[key])) {
// @ts-expect-error
this[key] = data[key];
}
};
switch (clear) {
case "Banner":
this.banner = null;
break;
case "Description":
this.description = null;
break;
case "Icon":
this.icon = null;
break;
}
apply("owner");
apply("name");
apply("description");
apply("channels");
apply("categories");
apply("system_messages");
apply("roles");
apply("default_permissions");
apply("icon");
apply("banner");
}
}
export class DataStore {
@observable users = new Map<string, User>();
@observable channels = new Map<string, Channel>();
@observable servers = new Map<string, Server>();
constructor() {
makeAutoObservable(this);
}
@action
packet(packet: ClientboundNotification) {
switch (packet.type) {
case "Ready": {
for (let user of packet.users) {
this.users.set(user._id, new User(user));
}
for (let channel of packet.channels) {
this.channels.set(channel._id, new Channel(channel));
}
for (let server of packet.servers) {
this.servers.set(server._id, new Server(server));
}
break;
}
case "UserUpdate": {
this.users.get(packet.id)?.update(packet.data, packet.clear);
break;
}
}
}
}