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 | null; function toNullable(data?: T) { return typeof data === "undefined" ? null : data; } export class User { _id: string; username: string; avatar: Nullable; badges: Nullable; status: Nullable; relationship: Nullable; online: Nullable; 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, 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 = null; // Group owner: Nullable = null; // Server server: Nullable = null; // Permissions permissions: Nullable = null; default_permissions: Nullable = null; role_permissions: Nullable<{ [key: string]: number }> = null; // Common name: Nullable = null; icon: Nullable = null; description: Nullable = null; recipients: Nullable = null; last_message: Nullable = 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, 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 = null; channels: string[] = []; categories: Nullable = null; system_messages: Nullable = null; roles: Nullable<{ [key: string]: Servers.Role }> = null; default_permissions: Servers.PermissionTuple; icon: Nullable = null; banner: Nullable = 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, 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(); @observable channels = new Map(); @observable servers = new Map(); 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; } } } }