forked from abner/for-legacy-web
Start migration to revolt.js@5.0.0.
200 error milestone
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
|
||||
|
||||
import { createContext } from "preact";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { useClient } from "../context/revoltjs/RevoltClient";
|
||||
|
||||
import { DataStore } from ".";
|
||||
import { Children } from "../types/Preact";
|
||||
|
||||
interface Props {
|
||||
children: Children;
|
||||
}
|
||||
|
||||
export const DataContext = createContext<DataStore>(null!);
|
||||
|
||||
// ! later we can do seamless account switching, by hooking this into Redux
|
||||
// ! and monitoring changes to active account and hence swapping stores.
|
||||
// although this may need more work since we need a Client per account too.
|
||||
|
||||
export default function StateLoader(props: Props) {
|
||||
const client = useClient();
|
||||
const [store] = useState(new DataStore(client));
|
||||
|
||||
useEffect(() => {
|
||||
const packet = (packet: ClientboundNotification) =>
|
||||
store.packet(packet);
|
||||
client.addListener("packet", packet);
|
||||
return () => client.removeListener("packet", packet);
|
||||
}, [client]);
|
||||
|
||||
return (
|
||||
<DataContext.Provider value={store}>
|
||||
{props.children}
|
||||
</DataContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useData = () => useContext(DataContext);
|
||||
@@ -1,443 +0,0 @@
|
||||
import isEqual from "lodash.isequal";
|
||||
import {
|
||||
makeAutoObservable,
|
||||
observable,
|
||||
autorun,
|
||||
runInAction,
|
||||
reaction,
|
||||
makeObservable,
|
||||
action,
|
||||
extendObservable,
|
||||
} from "mobx";
|
||||
import { Client } from "revolt.js";
|
||||
import {
|
||||
Attachment,
|
||||
Channels,
|
||||
Servers,
|
||||
Users,
|
||||
} from "revolt.js/dist/api/objects";
|
||||
import {
|
||||
RemoveChannelField,
|
||||
RemoveMemberField,
|
||||
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");
|
||||
}
|
||||
|
||||
@action setRelationship(relationship: Users.Relationship) {
|
||||
this.relationship = relationship;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@action groupJoin(user: string) {
|
||||
this.recipients?.push(user);
|
||||
}
|
||||
|
||||
@action groupLeave(user: string) {
|
||||
this.recipients = toNullable(
|
||||
this.recipients?.filter((x) => x !== user),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 Member {
|
||||
_id: Servers.MemberCompositeKey;
|
||||
nickname: Nullable<string> = null;
|
||||
avatar: Nullable<Attachment> = null;
|
||||
roles: Nullable<string[]> = null;
|
||||
|
||||
constructor(data: Servers.Member) {
|
||||
this._id = data._id;
|
||||
this.nickname = toNullable(data.nickname);
|
||||
this.avatar = toNullable(data.avatar);
|
||||
this.roles = toNullable(data.roles);
|
||||
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
@action update(data: Partial<Servers.Member>, clear?: RemoveMemberField) {
|
||||
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 "Nickname":
|
||||
this.nickname = null;
|
||||
break;
|
||||
case "Avatar":
|
||||
this.avatar = null;
|
||||
break;
|
||||
}
|
||||
|
||||
apply("nickname");
|
||||
apply("avatar");
|
||||
apply("roles");
|
||||
}
|
||||
}
|
||||
|
||||
export class DataStore {
|
||||
client: Client;
|
||||
|
||||
@observable users = new Map<string, User>();
|
||||
@observable channels = new Map<string, Channel>();
|
||||
@observable servers = new Map<string, Server>();
|
||||
@observable members = new Map<Servers.MemberCompositeKey, Member>();
|
||||
|
||||
constructor(client: Client) {
|
||||
makeAutoObservable(this, undefined, { proxy: false });
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@action
|
||||
async 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 "ChannelCreate": {
|
||||
this.channels.set(packet._id, new Channel(packet));
|
||||
break;
|
||||
}
|
||||
case "ChannelUpdate": {
|
||||
this.channels.get(packet.id)?.update(packet.data, packet.clear);
|
||||
break;
|
||||
}
|
||||
case "ChannelDelete": {
|
||||
this.channels.delete(packet.id);
|
||||
break;
|
||||
}
|
||||
case "ChannelGroupJoin": {
|
||||
this.channels.get(packet.id)?.groupJoin(packet.user);
|
||||
|
||||
if (!this.users.has(packet.user)) {
|
||||
let user = await this.client.users.fetch(packet.user);
|
||||
this.users.set(packet.user, new User(user));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "ChannelGroupLeave": {
|
||||
this.channels.get(packet.id)?.groupJoin(packet.user);
|
||||
break;
|
||||
}
|
||||
case "UserUpdate": {
|
||||
this.users.get(packet.id)?.update(packet.data, packet.clear);
|
||||
break;
|
||||
}
|
||||
case "UserRelationship": {
|
||||
if (!this.users.has(packet.user._id)) {
|
||||
this.users.set(packet.user._id, new User(packet.user));
|
||||
}
|
||||
|
||||
this.users.get(packet.user._id)?.setRelationship(packet.status);
|
||||
break;
|
||||
}
|
||||
case "ServerUpdate": {
|
||||
this.servers.get(packet.id)?.update(packet.data, packet.clear);
|
||||
break;
|
||||
}
|
||||
case "ServerDelete": {
|
||||
let server = this.servers.get(packet.id);
|
||||
if (server) {
|
||||
for (let channel of server.channels) {
|
||||
this.channels.delete(channel);
|
||||
}
|
||||
}
|
||||
|
||||
this.servers.delete(packet.id);
|
||||
break;
|
||||
}
|
||||
case "ServerMemberUpdate": {
|
||||
this.members.get(packet.id)?.update(packet.data, packet.clear);
|
||||
break;
|
||||
}
|
||||
case "ServerMemberJoin": {
|
||||
const _id = { server: packet.id, user: packet.user };
|
||||
this.members.set(_id, new Member({ _id }));
|
||||
|
||||
if (!this.servers.has(packet.id)) {
|
||||
let server = await this.client.servers.fetch(packet.id);
|
||||
this.servers.set(packet.id, new Server(server));
|
||||
|
||||
for (let id of server.channels) {
|
||||
let channel = this.client.channels.get(id);
|
||||
if (channel) {
|
||||
this.channels.set(id, new Channel(channel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.users.has(packet.user)) {
|
||||
let user = await this.client.users.fetch(packet.user);
|
||||
this.users.set(packet.user, new User(user));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "ServerMemberLeave": {
|
||||
this.members.delete({ server: packet.id, user: packet.user });
|
||||
if (packet.user === this.client.user!._id) {
|
||||
await this.packet({ type: "ServerDelete", id: packet.id });
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fetchMembers(server: string) {
|
||||
let res = await this.client.members.fetchMembers(server);
|
||||
|
||||
for (let user of res.users) {
|
||||
if (!this.users.has(user._id)) {
|
||||
this.users.set(user._id, new User(user));
|
||||
}
|
||||
}
|
||||
|
||||
return res.members;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user