From 4c9554c5e9edc0d2cb477b2da67a775da5919a52 Mon Sep 17 00:00:00 2001
From: Paul <paulmakles@gmail.com>
Date: Fri, 30 Jul 2021 22:40:49 +0100
Subject: [PATCH] Zero error milestone.

---
 src/components/common/AutoComplete.tsx        |   5 +-
 src/components/common/ChannelIcon.tsx         |   4 +-
 src/components/common/IconBase.tsx            |   2 +-
 src/components/common/ServerHeader.tsx        |  17 +--
 src/components/common/ServerIcon.tsx          |   3 +-
 .../common/messaging/MessageBase.tsx          |  82 ++++++-------
 .../common/messaging/MessageBox.tsx           |  26 ++--
 .../common/messaging/SystemMessage.tsx        |  21 ++--
 .../messaging/attachments/Attachment.tsx      |   4 +-
 .../attachments/AttachmentActions.tsx         |   2 +-
 .../common/messaging/attachments/TextFile.tsx |   2 +-
 .../common/messaging/bars/ReplyBar.tsx        |   9 +-
 .../common/messaging/bars/TypingIndicator.tsx |  21 +---
 .../common/messaging/embed/Embed.tsx          |   4 +-
 .../common/messaging/embed/EmbedMedia.tsx     |   2 +-
 .../messaging/embed/EmbedMediaActions.tsx     |   2 +-
 src/components/common/user/UserCheckbox.tsx   |   2 +-
 src/components/common/user/UserHeader.tsx     |   4 +-
 src/components/common/user/UserHover.tsx      |   3 +-
 src/components/common/user/UserIcon.tsx       |  13 +-
 src/components/common/user/UserShort.tsx      |   3 +-
 src/components/common/user/UserStatus.tsx     |  10 +-
 .../navigation/BottomNavigation.tsx           |   7 +-
 .../navigation/items/ButtonItem.tsx           |  11 +-
 .../navigation/left/HomeSidebar.tsx           |  19 +--
 .../navigation/left/ServerListSidebar.tsx     |  27 +++--
 .../navigation/left/ServerSidebar.tsx         |  14 +--
 src/components/navigation/left/common.ts      |  15 +--
 .../navigation/right/MemberSidebar.tsx        | 114 +++++-------------
 src/context/Voice.tsx                         |  19 +--
 src/context/index.tsx                         |   5 +-
 src/context/intermediate/Intermediate.tsx     |   2 +-
 src/context/intermediate/modals/Input.tsx     |  19 +--
 src/context/intermediate/modals/Prompt.tsx    |  76 +++++-------
 .../intermediate/popovers/ChannelInfo.tsx     |   8 +-
 .../intermediate/popovers/ImageViewer.tsx     |   8 +-
 .../intermediate/popovers/PendingRequests.tsx |   3 +-
 .../intermediate/popovers/UserPicker.tsx      |  12 +-
 src/pages/channels/actions/HeaderActions.tsx  |   2 +-
 src/pages/friends/Friend.tsx                  |   8 +-
 src/pages/settings/server/Roles.tsx           |   2 +-
 41 files changed, 242 insertions(+), 370 deletions(-)

diff --git a/src/components/common/AutoComplete.tsx b/src/components/common/AutoComplete.tsx
index 28fecbf..a6fdf1c 100644
--- a/src/components/common/AutoComplete.tsx
+++ b/src/components/common/AutoComplete.tsx
@@ -187,8 +187,9 @@ export function useAutoComplete(
             if (type === "channel" && searchClues?.channels) {
                 const channels = client.servers
                     .get(searchClues.channels.server)
-                    ?.channels.map((x) => client.channels.get(x))
-                    .filter((x) => typeof x !== "undefined") as Channel[];
+                    ?.channels.filter(
+                        (x) => typeof x !== "undefined",
+                    ) as Channel[];
 
                 const matches = (
                     search.length > 0
diff --git a/src/components/common/ChannelIcon.tsx b/src/components/common/ChannelIcon.tsx
index 301b009..c020ec3 100644
--- a/src/components/common/ChannelIcon.tsx
+++ b/src/components/common/ChannelIcon.tsx
@@ -1,11 +1,9 @@
 import { Hash, VolumeFull } from "@styled-icons/boxicons-regular";
 import { observer } from "mobx-react-lite";
-import { Channels } from "revolt.js/dist/api/objects";
+import { Channel } from "revolt.js/dist/maps/Channels";
 
 import { useContext } from "preact/hooks";
 
-import { Channel } from "../../mobx";
-
 import { AppContext } from "../../context/revoltjs/RevoltClient";
 
 import { ImageIconBase, IconBaseProps } from "./IconBase";
diff --git a/src/components/common/IconBase.tsx b/src/components/common/IconBase.tsx
index 305e5a3..533c4f0 100644
--- a/src/components/common/IconBase.tsx
+++ b/src/components/common/IconBase.tsx
@@ -1,4 +1,4 @@
-import { Attachment } from "revolt.js/dist/api/objects";
+import { Attachment } from "revolt-api/types/Autumn";
 import styled, { css } from "styled-components";
 
 export interface IconBaseProps<T> {
diff --git a/src/components/common/ServerHeader.tsx b/src/components/common/ServerHeader.tsx
index fc0d64e..a14f48e 100644
--- a/src/components/common/ServerHeader.tsx
+++ b/src/components/common/ServerHeader.tsx
@@ -2,13 +2,9 @@ import { Cog } from "@styled-icons/boxicons-solid";
 import { observer } from "mobx-react-lite";
 import { Link } from "react-router-dom";
 import { ServerPermission } from "revolt.js/dist/api/permissions";
+import { Server } from "revolt.js/dist/maps/Servers";
 import styled from "styled-components";
 
-import { Server } from "../../mobx";
-
-import { useClient } from "../../context/revoltjs/RevoltClient";
-import { useServerPermission } from "../../context/revoltjs/hooks";
-
 import Header from "../ui/Header";
 import IconButton from "../ui/IconButton";
 
@@ -21,14 +17,7 @@ const ServerName = styled.div`
 `;
 
 export default observer(({ server }: Props) => {
-    const permissions = useServerPermission(server._id);
-    const client = useClient();
-
-    const bannerURL = client.servers.getBannerURL(
-        server._id,
-        { width: 480 },
-        true,
-    );
+    const bannerURL = server.generateBannerURL({ width: 480 });
 
     return (
         <Header
@@ -39,7 +28,7 @@ export default observer(({ server }: Props) => {
                 background: bannerURL ? `url('${bannerURL}')` : undefined,
             }}>
             <ServerName>{server.name}</ServerName>
-            {(permissions & ServerPermission.ManageServer) > 0 && (
+            {(server.permission & ServerPermission.ManageServer) > 0 && (
                 <div className="actions">
                     <Link to={`/server/${server._id}/settings`}>
                         <IconButton>
diff --git a/src/components/common/ServerIcon.tsx b/src/components/common/ServerIcon.tsx
index 5208918..6863f73 100644
--- a/src/components/common/ServerIcon.tsx
+++ b/src/components/common/ServerIcon.tsx
@@ -1,10 +1,9 @@
 import { observer } from "mobx-react-lite";
+import { Server } from "revolt.js/dist/maps/Servers";
 import styled from "styled-components";
 
 import { useContext } from "preact/hooks";
 
-import { Server } from "../../mobx";
-
 import { AppContext } from "../../context/revoltjs/RevoltClient";
 
 import { IconBaseProps, ImageIconBase } from "./IconBase";
diff --git a/src/components/common/messaging/MessageBase.tsx b/src/components/common/messaging/MessageBase.tsx
index a42f62f..2149cb2 100644
--- a/src/components/common/messaging/MessageBase.tsx
+++ b/src/components/common/messaging/MessageBase.tsx
@@ -1,3 +1,5 @@
+import { observer } from "mobx-react-lite";
+import { Message } from "revolt.js/dist/maps/Messages";
 import styled, { css, keyframes } from "styled-components";
 import { decodeTime } from "ulid";
 
@@ -6,7 +8,6 @@ import { Text } from "preact-i18n";
 import { useDictionary } from "../../../lib/i18n";
 
 import { dayjs } from "../../../context/Locale";
-import { MessageObject } from "../../../context/revoltjs/util";
 
 import Tooltip from "../Tooltip";
 
@@ -192,57 +193,54 @@ export const DetailBase = styled.div`
     }
 `;
 
-export function MessageDetail({
-    message,
-    position,
-}: {
-    message: MessageObject;
-    position: "left" | "top";
-}) {
-    const dict = useDictionary();
-
-    if (position === "left") {
-        if (message.edited) {
+export const MessageDetail = observer(
+    ({ message, position }: { message: Message; position: "left" | "top" }) => {
+        const dict = useDictionary();
+
+        if (position === "left") {
+            if (message.edited) {
+                return (
+                    <>
+                        <time className="copyTime">
+                            <i className="copyBracket">[</i>
+                            {dayjs(decodeTime(message._id)).format(
+                                dict.dayjs.timeFormat,
+                            )}
+                            <i className="copyBracket">]</i>
+                        </time>
+                        <span className="edited">
+                            <Tooltip
+                                content={dayjs(message.edited).format("LLLL")}>
+                                <Text id="app.main.channel.edited" />
+                            </Tooltip>
+                        </span>
+                    </>
+                );
+            }
             return (
                 <>
-                    <time className="copyTime">
+                    <time>
                         <i className="copyBracket">[</i>
                         {dayjs(decodeTime(message._id)).format(
                             dict.dayjs.timeFormat,
                         )}
                         <i className="copyBracket">]</i>
                     </time>
-                    <span className="edited">
-                        <Tooltip content={dayjs(message.edited).format("LLLL")}>
-                            <Text id="app.main.channel.edited" />
-                        </Tooltip>
-                    </span>
                 </>
             );
         }
+
         return (
-            <>
-                <time>
-                    <i className="copyBracket">[</i>
-                    {dayjs(decodeTime(message._id)).format(
-                        dict.dayjs.timeFormat,
-                    )}
-                    <i className="copyBracket">]</i>
-                </time>
-            </>
+            <DetailBase>
+                <time>{dayjs(decodeTime(message._id)).calendar()}</time>
+                {message.edited && (
+                    <Tooltip content={dayjs(message.edited).format("LLLL")}>
+                        <span className="edited">
+                            <Text id="app.main.channel.edited" />
+                        </span>
+                    </Tooltip>
+                )}
+            </DetailBase>
         );
-    }
-
-    return (
-        <DetailBase>
-            <time>{dayjs(decodeTime(message._id)).calendar()}</time>
-            {message.edited && (
-                <Tooltip content={dayjs(message.edited).format("LLLL")}>
-                    <span className="edited">
-                        <Text id="app.main.channel.edited" />
-                    </span>
-                </Tooltip>
-            )}
-        </DetailBase>
-    );
-}
+    },
+);
diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
index 2983295..b74c98b 100644
--- a/src/components/common/messaging/MessageBox.tsx
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -1,7 +1,7 @@
-import { Send, HappyAlt, ShieldX } from "@styled-icons/boxicons-solid";
-import { Styleshare } from "@styled-icons/simple-icons";
+import { Send, ShieldX } from "@styled-icons/boxicons-solid";
 import Axios, { CancelTokenSource } from "axios";
 import { ChannelPermission } from "revolt.js/dist/api/permissions";
+import { Channel } from "revolt.js/dist/maps/Channels";
 import styled from "styled-components";
 import { ulid } from "ulid";
 
@@ -19,7 +19,6 @@ import {
     SMOOTH_SCROLL_ON_RECEIVE,
 } from "../../../lib/renderer/Singleton";
 
-import { Channel } from "../../../mobx";
 import { dispatch, getState } from "../../../redux";
 import { Reply } from "../../../redux/reducers/queue";
 
@@ -31,7 +30,6 @@ import {
     uploadFile,
 } from "../../../context/revoltjs/FileUploads";
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
-import { useChannelPermission } from "../../../context/revoltjs/hooks";
 import { takeError } from "../../../context/revoltjs/util";
 
 import IconButton from "../../ui/IconButton";
@@ -123,8 +121,7 @@ export default function MessageBox({ channel }: Props) {
     const client = useContext(AppContext);
     const translate = useTranslation();
 
-    const permissions = useChannelPermission(channel._id);
-    if (!(permissions & ChannelPermission.SendMessage)) {
+    if (!(channel.permission & ChannelPermission.SendMessage)) {
         return (
             <Base>
                 <Blocked>
@@ -194,7 +191,8 @@ export default function MessageBox({ channel }: Props) {
         playSound("outbound");
 
         const nonce = ulid();
-        dispatch({
+        // ! FIXME: queued
+        /*dispatch({
             type: "QUEUE_ADD",
             nonce,
             channel: channel._id,
@@ -206,7 +204,7 @@ export default function MessageBox({ channel }: Props) {
                 content,
                 replies,
             },
-        });
+        });*/
 
         defer(() =>
             SingletonMessageRenderer.jumpToBottom(
@@ -216,7 +214,7 @@ export default function MessageBox({ channel }: Props) {
         );
 
         try {
-            await client.channels.sendMessage(channel._id, {
+            await channel.sendMessage({
                 content,
                 nonce,
                 replies,
@@ -290,7 +288,7 @@ export default function MessageBox({ channel }: Props) {
 
         const nonce = ulid();
         try {
-            await client.channels.sendMessage(channel._id, {
+            await channel.sendMessage({
                 content,
                 nonce,
                 replies,
@@ -360,7 +358,7 @@ export default function MessageBox({ channel }: Props) {
         users: { type: "channel", id: channel._id },
         channels:
             channel.channel_type === "TextChannel"
-                ? { server: channel.server! }
+                ? { server: channel.server_id! }
                 : undefined,
     });
 
@@ -403,7 +401,7 @@ export default function MessageBox({ channel }: Props) {
                 setReplies={setReplies}
             />
             <Base>
-                {permissions & ChannelPermission.UploadFiles ? (
+                {channel.permission & ChannelPermission.UploadFiles ? (
                     <Action>
                         <FileUploader
                             size={24}
@@ -475,9 +473,7 @@ export default function MessageBox({ channel }: Props) {
                     placeholder={
                         channel.channel_type === "DirectMessage"
                             ? translate("app.main.channel.message_who", {
-                                  person: client.users.get(
-                                      client.channels.getRecipient(channel._id),
-                                  )?.username,
+                                  person: channel.recipient?.username,
                               })
                             : channel.channel_type === "SavedMessages"
                             ? translate("app.main.channel.message_saved")
diff --git a/src/components/common/messaging/SystemMessage.tsx b/src/components/common/messaging/SystemMessage.tsx
index 19c5325..24e18ef 100644
--- a/src/components/common/messaging/SystemMessage.tsx
+++ b/src/components/common/messaging/SystemMessage.tsx
@@ -1,14 +1,13 @@
 import { observer } from "mobx-react-lite";
+import { Message } from "revolt.js/dist/maps/Messages";
+import { User } from "revolt.js/dist/maps/Users";
 import styled from "styled-components";
 
 import { attachContextMenu } from "preact-context-menu";
 
 import { TextReact } from "../../../lib/i18n";
 
-import { User } from "../../../mobx";
-import { useData } from "../../../mobx/State";
-
-import { MessageObject } from "../../../context/revoltjs/util";
+import { useClient } from "../../../context/revoltjs/RevoltClient";
 
 import UserShort from "../user/UserShort";
 import MessageBase, { MessageDetail, MessageInfo } from "./MessageBase";
@@ -36,14 +35,14 @@ type SystemMessageParsed =
 
 interface Props {
     attachContext?: boolean;
-    message: MessageObject;
+    message: Message;
     highlight?: boolean;
     hideInfo?: boolean;
 }
 
 export const SystemMessage = observer(
     ({ attachContext, message, highlight, hideInfo }: Props) => {
-        const store = useData();
+        const client = useClient();
 
         let data: SystemMessageParsed;
         const content = message.content;
@@ -56,8 +55,8 @@ export const SystemMessage = observer(
                 case "user_remove":
                     data = {
                         type: content.type,
-                        user: store.users.get(content.id)!,
-                        by: store.users.get(content.by)!,
+                        user: client.users.get(content.id)!,
+                        by: client.users.get(content.by)!,
                     };
                     break;
                 case "user_joined":
@@ -66,21 +65,21 @@ export const SystemMessage = observer(
                 case "user_banned":
                     data = {
                         type: content.type,
-                        user: store.users.get(content.id)!,
+                        user: client.users.get(content.id)!,
                     };
                     break;
                 case "channel_renamed":
                     data = {
                         type: "channel_renamed",
                         name: content.name,
-                        by: store.users.get(content.by)!,
+                        by: client.users.get(content.by)!,
                     };
                     break;
                 case "channel_description_changed":
                 case "channel_icon_changed":
                     data = {
                         type: content.type,
-                        by: store.users.get(content.by)!,
+                        by: client.users.get(content.by)!,
                     };
                     break;
                 default:
diff --git a/src/components/common/messaging/attachments/Attachment.tsx b/src/components/common/messaging/attachments/Attachment.tsx
index 75ebe70..0633579 100644
--- a/src/components/common/messaging/attachments/Attachment.tsx
+++ b/src/components/common/messaging/attachments/Attachment.tsx
@@ -1,4 +1,4 @@
-import { Attachment as AttachmentRJS } from "revolt.js/dist/api/objects";
+import { Attachment as AttachmentI } from "revolt-api/types/Autumn";
 
 import styles from "./Attachment.module.scss";
 import classNames from "classnames";
@@ -13,7 +13,7 @@ import Spoiler from "./Spoiler";
 import TextFile from "./TextFile";
 
 interface Props {
-    attachment: AttachmentRJS;
+    attachment: AttachmentI;
     hasContent: boolean;
 }
 
diff --git a/src/components/common/messaging/attachments/AttachmentActions.tsx b/src/components/common/messaging/attachments/AttachmentActions.tsx
index e9ad921..daba3f7 100644
--- a/src/components/common/messaging/attachments/AttachmentActions.tsx
+++ b/src/components/common/messaging/attachments/AttachmentActions.tsx
@@ -5,7 +5,7 @@ import {
     Headphone,
     Video,
 } from "@styled-icons/boxicons-regular";
-import { Attachment } from "revolt.js/dist/api/objects";
+import { Attachment } from "revolt-api/types/Autumn";
 
 import styles from "./AttachmentActions.module.scss";
 import classNames from "classnames";
diff --git a/src/components/common/messaging/attachments/TextFile.tsx b/src/components/common/messaging/attachments/TextFile.tsx
index e5fa839..6a4a923 100644
--- a/src/components/common/messaging/attachments/TextFile.tsx
+++ b/src/components/common/messaging/attachments/TextFile.tsx
@@ -1,5 +1,5 @@
 import axios from "axios";
-import { Attachment } from "revolt.js/dist/api/objects";
+import { Attachment } from "revolt-api/types/Autumn";
 
 import styles from "./Attachment.module.scss";
 import { useContext, useEffect, useState } from "preact/hooks";
diff --git a/src/components/common/messaging/bars/ReplyBar.tsx b/src/components/common/messaging/bars/ReplyBar.tsx
index a6b282d..74413e8 100644
--- a/src/components/common/messaging/bars/ReplyBar.tsx
+++ b/src/components/common/messaging/bars/ReplyBar.tsx
@@ -14,7 +14,6 @@ import { StateUpdater, useEffect } from "preact/hooks";
 import { internalSubscribe } from "../../../../lib/eventEmitter";
 import { useRenderState } from "../../../../lib/renderer/Singleton";
 
-import { useData } from "../../../../mobx/State";
 import { Reply } from "../../../../redux/reducers/queue";
 
 import IconButton from "../../../ui/IconButton";
@@ -74,9 +73,6 @@ export default observer(({ channel, replies, setReplies }: Props) => {
     const ids = replies.map((x) => x.id);
     const messages = view.messages.filter((x) => ids.includes(x._id));
 
-    const store = useData();
-    const users = messages.map((x) => store.users.get(x.author));
-
     return (
         <div>
             {replies.map((reply, index) => {
@@ -92,17 +88,16 @@ export default observer(({ channel, replies, setReplies }: Props) => {
                         </span>
                     );
 
-                const user = users[index];
                 return (
                     <Base key={reply.id}>
                         <ReplyBase preview>
                             <ReplyIcon size={22} />
-                            <UserShort user={user} size={16} />
+                            <UserShort user={message.author} size={16} />
                             {message.attachments &&
                                 message.attachments.length > 0 && (
                                     <File size={16} />
                                 )}
-                            {message.author === SYSTEM_USER_ID ? (
+                            {message.author_id === SYSTEM_USER_ID ? (
                                 <SystemMessage message={message} />
                             ) : (
                                 <Markdown
diff --git a/src/components/common/messaging/bars/TypingIndicator.tsx b/src/components/common/messaging/bars/TypingIndicator.tsx
index d8c0edf..3704e79 100644
--- a/src/components/common/messaging/bars/TypingIndicator.tsx
+++ b/src/components/common/messaging/bars/TypingIndicator.tsx
@@ -1,22 +1,12 @@
 import { observer } from "mobx-react-lite";
-import { User } from "revolt.js";
 import styled from "styled-components";
 
 import { Text } from "preact-i18n";
-import { useContext } from "preact/hooks";
 
-import { TextReact } from "../../../../lib/i18n";
-
-import { useData } from "../../../../mobx/State";
 import { connectState } from "../../../../redux/connector";
 import { TypingUser } from "../../../../redux/reducers/typing";
 
-import {
-    AppContext,
-    useClient,
-} from "../../../../context/revoltjs/RevoltClient";
-
-import { Username } from "../../user/UserShort";
+import { useClient } from "../../../../context/revoltjs/RevoltClient";
 
 interface Props {
     typing?: TypingUser[];
@@ -68,9 +58,8 @@ const Base = styled.div`
 export const TypingIndicator = observer(({ typing }: Props) => {
     if (typing && typing.length > 0) {
         const client = useClient();
-        const store = useData();
         const users = typing
-            .map((x) => store.users.get(x.id)!)
+            .map((x) => client.users.get(x.id)!)
             .filter((x) => typeof x !== "undefined");
 
         users.sort((a, b) =>
@@ -113,11 +102,7 @@ export const TypingIndicator = observer(({ typing }: Props) => {
                         {users.map((user) => (
                             <img
                                 loading="eager"
-                                src={client.users.getAvatarURL(
-                                    user._id,
-                                    { max_side: 256 },
-                                    true,
-                                )}
+                                src={user.generateAvatarURL({ max_side: 256 })}
                             />
                         ))}
                     </div>
diff --git a/src/components/common/messaging/embed/Embed.tsx b/src/components/common/messaging/embed/Embed.tsx
index 4ccfd70..7875b36 100644
--- a/src/components/common/messaging/embed/Embed.tsx
+++ b/src/components/common/messaging/embed/Embed.tsx
@@ -1,4 +1,4 @@
-import { Embed as EmbedRJS } from "revolt.js/dist/api/objects";
+import { Embed as EmbedI } from "revolt-api/types/January";
 
 import styles from "./Embed.module.scss";
 import classNames from "classnames";
@@ -11,7 +11,7 @@ import { MessageAreaWidthContext } from "../../../../pages/channels/messaging/Me
 import EmbedMedia from "./EmbedMedia";
 
 interface Props {
-    embed: EmbedRJS;
+    embed: EmbedI;
 }
 
 const MAX_EMBED_WIDTH = 480;
diff --git a/src/components/common/messaging/embed/EmbedMedia.tsx b/src/components/common/messaging/embed/EmbedMedia.tsx
index 169192e..d46b10c 100644
--- a/src/components/common/messaging/embed/EmbedMedia.tsx
+++ b/src/components/common/messaging/embed/EmbedMedia.tsx
@@ -1,4 +1,4 @@
-import { Embed } from "revolt.js/dist/api/objects";
+import { Embed } from "revolt-api/types/January";
 
 import styles from "./Embed.module.scss";
 
diff --git a/src/components/common/messaging/embed/EmbedMediaActions.tsx b/src/components/common/messaging/embed/EmbedMediaActions.tsx
index 9aae7b6..fe9f6cd 100644
--- a/src/components/common/messaging/embed/EmbedMediaActions.tsx
+++ b/src/components/common/messaging/embed/EmbedMediaActions.tsx
@@ -1,5 +1,5 @@
 import { LinkExternal } from "@styled-icons/boxicons-regular";
-import { EmbedImage } from "revolt.js/dist/api/objects";
+import { EmbedImage } from "revolt-api/types/January";
 
 import styles from "./Embed.module.scss";
 
diff --git a/src/components/common/user/UserCheckbox.tsx b/src/components/common/user/UserCheckbox.tsx
index 6f88230..bbde2a0 100644
--- a/src/components/common/user/UserCheckbox.tsx
+++ b/src/components/common/user/UserCheckbox.tsx
@@ -1,4 +1,4 @@
-import { User } from "../../../mobx";
+import { User } from "revolt.js/dist/maps/Users";
 
 import Checkbox, { CheckboxProps } from "../../ui/Checkbox";
 
diff --git a/src/components/common/user/UserHeader.tsx b/src/components/common/user/UserHeader.tsx
index d45bfbd..3c55ea7 100644
--- a/src/components/common/user/UserHeader.tsx
+++ b/src/components/common/user/UserHeader.tsx
@@ -1,6 +1,7 @@
 import { Cog } from "@styled-icons/boxicons-solid";
 import { observer } from "mobx-react-lite";
 import { Link } from "react-router-dom";
+import { User } from "revolt.js/dist/maps/Users";
 import styled from "styled-components";
 
 import { openContextMenu } from "preact-context-menu";
@@ -9,15 +10,12 @@ import { Localizer } from "preact-i18n";
 
 import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
 
-import { User } from "../../../mobx";
-
 import { useIntermediate } from "../../../context/intermediate/Intermediate";
 
 import Header from "../../ui/Header";
 import IconButton from "../../ui/IconButton";
 
 import Tooltip from "../Tooltip";
-import UserIcon from "./UserIcon";
 import UserStatus from "./UserStatus";
 
 const HeaderBase = styled.div`
diff --git a/src/components/common/user/UserHover.tsx b/src/components/common/user/UserHover.tsx
index fbff942..087245e 100644
--- a/src/components/common/user/UserHover.tsx
+++ b/src/components/common/user/UserHover.tsx
@@ -1,7 +1,6 @@
+import { User } from "revolt.js/dist/maps/Users";
 import styled from "styled-components";
 
-import { User } from "../../../mobx";
-
 import { Children } from "../../../types/Preact";
 import Tooltip from "../Tooltip";
 import { Username } from "./UserShort";
diff --git a/src/components/common/user/UserIcon.tsx b/src/components/common/user/UserIcon.tsx
index 1aea5f2..ba5322f 100644
--- a/src/components/common/user/UserIcon.tsx
+++ b/src/components/common/user/UserIcon.tsx
@@ -1,6 +1,7 @@
 import { MicrophoneOff } from "@styled-icons/boxicons-regular";
 import { observer } from "mobx-react-lite";
-import { Users } from "revolt.js/dist/api/objects";
+import { Presence } from "revolt-api/types/Users";
+import { User } from "revolt.js/dist/maps/Users";
 import styled, { css } from "styled-components";
 
 import { useContext } from "preact/hooks";
@@ -8,7 +9,6 @@ import { useContext } from "preact/hooks";
 import { ThemeContext } from "../../../context/Theme";
 import { AppContext } from "../../../context/revoltjs/RevoltClient";
 
-import { User } from "../../../mobx";
 import IconBase, { IconBaseProps } from "../IconBase";
 import fallback from "../assets/user.png";
 
@@ -22,10 +22,10 @@ interface Props extends IconBaseProps<User> {
 export function useStatusColour(user?: User) {
     const theme = useContext(ThemeContext);
 
-    return user?.online && user?.status?.presence !== Users.Presence.Invisible
-        ? user?.status?.presence === Users.Presence.Idle
+    return user?.online && user?.status?.presence !== Presence.Invisible
+        ? user?.status?.presence === Presence.Idle
             ? theme["status-away"]
-            : user?.status?.presence === Users.Presence.Busy
+            : user?.status?.presence === Presence.Busy
             ? theme["status-busy"]
             : theme["status-online"]
         : theme["status-invisible"];
@@ -72,8 +72,7 @@ export default observer(
                 target?.avatar ?? attachment,
                 { max_side: 256 },
                 animate,
-            ) ??
-            (target ? client.users.getDefaultAvatarURL(target._id) : fallback);
+            ) ?? (target ? target.defaultAvatarURL : fallback);
 
         return (
             <IconBase
diff --git a/src/components/common/user/UserShort.tsx b/src/components/common/user/UserShort.tsx
index ec47c0d..cee11ff 100644
--- a/src/components/common/user/UserShort.tsx
+++ b/src/components/common/user/UserShort.tsx
@@ -1,9 +1,8 @@
 import { observer } from "mobx-react-lite";
+import { User } from "revolt.js/dist/maps/Users";
 
 import { Text } from "preact-i18n";
 
-import { User } from "../../../mobx";
-
 import UserIcon from "./UserIcon";
 
 export const Username = observer(
diff --git a/src/components/common/user/UserStatus.tsx b/src/components/common/user/UserStatus.tsx
index 8c95d04..ff8a354 100644
--- a/src/components/common/user/UserStatus.tsx
+++ b/src/components/common/user/UserStatus.tsx
@@ -1,9 +1,9 @@
 import { observer } from "mobx-react-lite";
-import { Users } from "revolt.js/dist/api/objects";
+import { Presence } from "revolt-api/types/Users";
+import { User } from "revolt.js/dist/maps/Users";
 
 import { Text } from "preact-i18n";
 
-import { User } from "../../../mobx";
 import Tooltip from "../Tooltip";
 
 interface Props {
@@ -25,15 +25,15 @@ export default observer(({ user, tooltip }: Props) => {
             return <>{user.status.text}</>;
         }
 
-        if (user.status?.presence === Users.Presence.Busy) {
+        if (user.status?.presence === Presence.Busy) {
             return <Text id="app.status.busy" />;
         }
 
-        if (user.status?.presence === Users.Presence.Idle) {
+        if (user.status?.presence === Presence.Idle) {
             return <Text id="app.status.idle" />;
         }
 
-        if (user.status?.presence === Users.Presence.Invisible) {
+        if (user.status?.presence === Presence.Invisible) {
             return <Text id="app.status.offline" />;
         }
 
diff --git a/src/components/navigation/BottomNavigation.tsx b/src/components/navigation/BottomNavigation.tsx
index 71a1a51..16ec584 100644
--- a/src/components/navigation/BottomNavigation.tsx
+++ b/src/components/navigation/BottomNavigation.tsx
@@ -1,12 +1,10 @@
-import { Search } from "@styled-icons/boxicons-regular";
-import { Message, Group, Inbox } from "@styled-icons/boxicons-solid";
+import { Message, Group } from "@styled-icons/boxicons-solid";
 import { observer } from "mobx-react-lite";
 import { useHistory, useLocation } from "react-router";
 import styled, { css } from "styled-components";
 
 import ConditionalLink from "../../lib/ConditionalLink";
 
-import { useData } from "../../mobx/State";
 import { connectState } from "../../redux/connector";
 import { LastOpened } from "../../redux/reducers/last_opened";
 
@@ -55,8 +53,7 @@ interface Props {
 
 export const BottomNavigation = observer(({ lastOpened }: Props) => {
     const client = useClient();
-    const store = useData();
-    const user = store.users.get(client.user!._id);
+    const user = client.users.get(client.user!._id);
 
     const history = useHistory();
     const path = useLocation().pathname;
diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx
index 734b522..9372c0f 100644
--- a/src/components/navigation/items/ButtonItem.tsx
+++ b/src/components/navigation/items/ButtonItem.tsx
@@ -1,6 +1,8 @@
 import { X, Crown } from "@styled-icons/boxicons-regular";
 import { observer } from "mobx-react-lite";
-import { Channels, Users } from "revolt.js/dist/api/objects";
+import { Presence } from "revolt-api/types/Users";
+import { Channel } from "revolt.js/dist/maps/Channels";
+import { User } from "revolt.js/dist/maps/Users";
 
 import styles from "./Item.module.scss";
 import classNames from "classnames";
@@ -10,8 +12,6 @@ import { Localizer, Text } from "preact-i18n";
 import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
 import { stopPropagation } from "../../../lib/stopPropagation";
 
-import { Channel, User } from "../../../mobx";
-
 import { useIntermediate } from "../../../context/intermediate/Intermediate";
 
 import ChannelIcon from "../../common/ChannelIcon";
@@ -51,8 +51,7 @@ export const UserButton = observer((props: UserProps) => {
             data-alert={typeof alert === "string"}
             data-online={
                 typeof channel !== "undefined" ||
-                (user.online &&
-                    user.status?.presence !== Users.Presence.Invisible)
+                (user.online && user.status?.presence !== Presence.Invisible)
             }
             onContextMenu={attachContextMenu("Menu", {
                 user: user._id,
@@ -82,7 +81,7 @@ export const UserButton = observer((props: UserProps) => {
             </div>
             <div className={styles.button}>
                 {context?.channel_type === "Group" &&
-                    context.owner === user._id && (
+                    context.owner_id === user._id && (
                         <Localizer>
                             <Tooltip
                                 content={<Text id="app.main.groups.owner" />}>
diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx
index 187680b..9f4fe3e 100644
--- a/src/components/navigation/left/HomeSidebar.tsx
+++ b/src/components/navigation/left/HomeSidebar.tsx
@@ -6,8 +6,7 @@ import {
 } from "@styled-icons/boxicons-solid";
 import { observer } from "mobx-react-lite";
 import { Link, Redirect, useLocation, useParams } from "react-router-dom";
-import { Channels } from "revolt.js/dist/api/objects";
-import { Users as UsersNS } from "revolt.js/dist/api/objects";
+import { RelationshipStatus } from "revolt-api/types/Users";
 
 import { Text } from "preact-i18n";
 import { useContext, useEffect } from "preact/hooks";
@@ -16,7 +15,6 @@ import ConditionalLink from "../../../lib/ConditionalLink";
 import PaintCounter from "../../../lib/PaintCounter";
 import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
 
-import { useData } from "../../../mobx/State";
 import { dispatch } from "../../../redux";
 import { connectState } from "../../../redux/connector";
 import { Unreads } from "../../../redux/reducers/unreads";
@@ -42,8 +40,7 @@ const HomeSidebar = observer((props: Props) => {
     const { channel } = useParams<{ channel: string }>();
     const { openScreen } = useIntermediate();
 
-    const store = useData();
-    const channels = [...store.channels.values()]
+    const channels = [...client.channels.values()]
         .filter(
             (x) =>
                 x.channel_type === "DirectMessage" ||
@@ -51,7 +48,7 @@ const HomeSidebar = observer((props: Props) => {
         )
         .map((x) => mapChannelWithUnread(x, props.unreads));
 
-    const obj = store.channels.get(channel);
+    const obj = client.channels.get(channel);
     if (channel && !obj) return <Redirect to="/" />;
     if (obj) useUnreads({ ...props, channel: obj });
 
@@ -87,10 +84,10 @@ const HomeSidebar = observer((props: Props) => {
                             <ButtonItem
                                 active={pathname === "/friends"}
                                 alert={
-                                    typeof [...store.users.values()].find(
+                                    typeof [...client.users.values()].find(
                                         (user) =>
                                             user?.relationship ===
-                                            UsersNS.Relationship.Incoming,
+                                            RelationshipStatus.Incoming,
                                     ) !== "undefined"
                                         ? "unread"
                                         : undefined
@@ -139,11 +136,7 @@ const HomeSidebar = observer((props: Props) => {
                     let user;
                     if (x.channel.channel_type === "DirectMessage") {
                         if (!x.channel.active) return null;
-
-                        const recipient = client.channels.getRecipient(
-                            x.channel._id,
-                        );
-                        user = store.users.get(recipient);
+                        user = x.channel.recipient;
 
                         if (!user) {
                             console.warn(
diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx
index fc60f94..a00897e 100644
--- a/src/components/navigation/left/ServerListSidebar.tsx
+++ b/src/components/navigation/left/ServerListSidebar.tsx
@@ -1,7 +1,7 @@
 import { Plus } from "@styled-icons/boxicons-regular";
 import { observer } from "mobx-react-lite";
 import { useLocation, useParams } from "react-router-dom";
-import { Channel, Servers, Users } from "revolt.js/dist/api/objects";
+import { RelationshipStatus } from "revolt-api/types/Users";
 import styled, { css } from "styled-components";
 
 import { attachContextMenu, openContextMenu } from "preact-context-menu";
@@ -10,7 +10,6 @@ import ConditionalLink from "../../../lib/ConditionalLink";
 import PaintCounter from "../../../lib/PaintCounter";
 import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
 
-import { useData } from "../../../mobx/State";
 import { connectState } from "../../../redux/connector";
 import { LastOpened } from "../../../redux/reducers/last_opened";
 import { Unreads } from "../../../redux/reducers/unreads";
@@ -175,14 +174,12 @@ interface Props {
 }
 
 export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => {
-    const store = useData();
     const client = useClient();
-    const self = store.users.get(client.user!._id);
 
     const { server: server_id } = useParams<{ server?: string }>();
-    const server = server_id ? store.servers.get(server_id) : undefined;
-    const activeServers = [...store.servers.values()];
-    const channels = [...store.channels.values()].map((x) =>
+    const server = server_id ? client.servers.get(server_id) : undefined;
+    const activeServers = [...client.servers.values()];
+    const channels = [...client.channels.values()].map((x) =>
         mapChannelWithUnread(x, unreads),
     );
 
@@ -192,7 +189,7 @@ export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => {
 
     const servers = activeServers.map((server) => {
         let alertCount = 0;
-        for (const id of server.channels) {
+        for (const id of server.channel_ids) {
             const channel = channels.find((x) => x.channel?._id === id);
             if (channel?.alertCount) {
                 alertCount += channel.alertCount;
@@ -201,7 +198,7 @@ export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => {
 
         return {
             server,
-            unread: (typeof server.channels.find((x) =>
+            unread: (typeof server.channel_ids.find((x) =>
                 unreadChannels.includes(x),
             ) !== "undefined"
                 ? alertCount > 0
@@ -230,8 +227,8 @@ export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => {
     }
 
     if (
-        [...store.users.values()].find(
-            (x) => x.relationship === Users.Relationship.Incoming,
+        [...client.users.values()].find(
+            (x) => x.relationship === RelationshipStatus.Incoming,
         )
     ) {
         alertCount++;
@@ -254,9 +251,13 @@ export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => {
                             onClick={() =>
                                 homeActive && openContextMenu("Status")
                             }>
-                            <UserHover user={self}>
+                            <UserHover user={client.user}>
                                 <Icon size={42} unread={homeUnread}>
-                                    <UserIcon target={self} size={32} status />
+                                    <UserIcon
+                                        target={client.user}
+                                        size={32}
+                                        status
+                                    />
                                 </Icon>
                             </UserHover>
                         </div>
diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx
index de7dd7e..cd23059 100644
--- a/src/components/navigation/left/ServerSidebar.tsx
+++ b/src/components/navigation/left/ServerSidebar.tsx
@@ -1,6 +1,5 @@
 import { observer } from "mobx-react-lite";
 import { Redirect, useParams } from "react-router";
-import { Channels } from "revolt.js/dist/api/objects";
 import styled from "styled-components";
 
 import { attachContextMenu } from "preact-context-menu";
@@ -9,11 +8,12 @@ import { useEffect } from "preact/hooks";
 import ConditionalLink from "../../../lib/ConditionalLink";
 import PaintCounter from "../../../lib/PaintCounter";
 
-import { useData } from "../../../mobx/State";
 import { dispatch } from "../../../redux";
 import { connectState } from "../../../redux/connector";
 import { Unreads } from "../../../redux/reducers/unreads";
 
+import { useClient } from "../../../context/revoltjs/RevoltClient";
+
 import CollapsibleSection from "../../common/CollapsibleSection";
 import ServerHeader from "../../common/ServerHeader";
 import Category from "../../ui/Category";
@@ -50,14 +50,14 @@ const ServerList = styled.div`
 `;
 
 const ServerSidebar = observer((props: Props) => {
-    const store = useData();
+    const client = useClient();
     const { server: server_id, channel: channel_id } =
         useParams<{ server: string; channel?: string }>();
 
-    const server = store.servers.get(server_id);
+    const server = client.servers.get(server_id);
     if (!server) return <Redirect to="/" />;
 
-    const channel = channel_id ? store.channels.get(channel_id) : undefined;
+    const channel = channel_id ? client.channels.get(channel_id) : undefined;
     if (channel_id && !channel) return <Redirect to={`/server/${server_id}`} />;
     if (channel) useUnreads({ ...props, channel });
 
@@ -71,11 +71,11 @@ const ServerSidebar = observer((props: Props) => {
         });
     }, [channel_id]);
 
-    const uncategorised = new Set(server.channels);
+    const uncategorised = new Set(server.channel_ids);
     const elements = [];
 
     function addChannel(id: string) {
-        const entry = store.channels.get(id);
+        const entry = client.channels.get(id);
         if (!entry) return;
 
         const active = channel?._id === entry._id;
diff --git a/src/components/navigation/left/common.ts b/src/components/navigation/left/common.ts
index 1096bf1..4f085d8 100644
--- a/src/components/navigation/left/common.ts
+++ b/src/components/navigation/left/common.ts
@@ -1,11 +1,12 @@
+import { autorun } from "mobx";
+import { Channel } from "revolt.js/dist/maps/Channels";
+
 import { useLayoutEffect } from "preact/hooks";
 
-import { Channel } from "../../../mobx";
 import { dispatch } from "../../../redux";
 import { Unreads } from "../../../redux/reducers/unreads";
 
 import { useClient } from "../../../context/revoltjs/RevoltClient";
-import { HookContext, useForceUpdate } from "../../../context/revoltjs/hooks";
 
 type UnreadProps = {
     channel: Channel;
@@ -16,7 +17,10 @@ export function useUnreads({ channel, unreads }: UnreadProps) {
     const client = useClient();
 
     useLayoutEffect(() => {
-        function checkUnread(target?: Channel) {
+        function checkUnread(
+            target: Channel,
+            last_message: Channel["last_message"],
+        ) {
             if (!target) return;
             if (target._id !== channel._id) return;
             if (
@@ -46,10 +50,7 @@ export function useUnreads({ channel, unreads }: UnreadProps) {
             }
         }
 
-        checkUnread(channel);
-
-        client.channels.addListener("mutation", checkUnread);
-        return () => client.channels.removeListener("mutation", checkUnread);
+        return autorun(() => checkUnread(channel!, channel!.last_message));
     }, [channel, unreads]);
 }
 
diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx
index 838acd3..579692c 100644
--- a/src/components/navigation/right/MemberSidebar.tsx
+++ b/src/components/navigation/right/MemberSidebar.tsx
@@ -1,15 +1,15 @@
 import { observer } from "mobx-react-lite";
-import { useParams } from "react-router";
 import { Link } from "react-router-dom";
-import { User } from "revolt.js";
-import { Channels, Message, Servers, Users } from "revolt.js/dist/api/objects";
+import { Presence } from "revolt-api/types/Users";
+import { Channel } from "revolt.js/dist/maps/Channels";
+import Members, { Member } from "revolt.js/dist/maps/Members";
+import { Message } from "revolt.js/dist/maps/Messages";
+import { User } from "revolt.js/dist/maps/Users";
 import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
 
 import { Text } from "preact-i18n";
 import { useContext, useEffect, useState } from "preact/hooks";
 
-import { Channel } from "../../../mobx";
-import { useData } from "../../../mobx/State";
 import { getState } from "../../../redux";
 
 import { useIntermediate } from "../../../context/intermediate/Intermediate";
@@ -17,8 +17,8 @@ import {
     AppContext,
     ClientStatus,
     StatusContext,
+    useClient,
 } from "../../../context/revoltjs/RevoltClient";
-import { HookContext } from "../../../context/revoltjs/hooks";
 
 import CollapsibleSection from "../../common/CollapsibleSection";
 import Button from "../../ui/Button";
@@ -46,10 +46,10 @@ export const GroupMemberSidebar = observer(
     ({ channel }: { channel: Channel }) => {
         const { openScreen } = useIntermediate();
 
-        const store = useData();
-        const members = channel.recipients
-            ?.map((member) => store.users.get(member)!)
-            .filter((x) => typeof x !== "undefined");
+        const client = useClient();
+        const members = channel.recipients?.filter(
+            (x) => typeof x !== "undefined",
+        );
 
         /*const voice = useContext(VoiceContext);
     const voiceActive = voice.roomId === channel._id;
@@ -70,14 +70,12 @@ export const GroupMemberSidebar = observer(
             // ! FIXME: should probably rewrite all this code
             const l =
                 +(
-                    (a.online &&
-                        a.status?.presence !== Users.Presence.Invisible) ??
+                    (a!.online && a!.status?.presence !== Presence.Invisible) ??
                     false
                 ) | 0;
             const r =
                 +(
-                    (b.online &&
-                        b.status?.presence !== Users.Presence.Invisible) ??
+                    (b!.online && b!.status?.presence !== Presence.Invisible) ??
                     false
                 ) | 0;
 
@@ -86,14 +84,14 @@ export const GroupMemberSidebar = observer(
                 return n;
             }
 
-            return a.username.localeCompare(b.username);
+            return a!.username.localeCompare(b!.username);
         });
 
         return (
             <GenericSidebarBase>
                 <GenericSidebarList>
                     <ChannelDebugInfo id={channel._id} />
-                    <Search channel={channel._id} />
+                    <Search channel={channel} />
 
                     {/*voiceActive && voiceParticipants.length !== 0 && (
                     <Fragment>
@@ -163,71 +161,32 @@ export const GroupMemberSidebar = observer(
 
 export const ServerMemberSidebar = observer(
     ({ channel }: { channel: Channel }) => {
-        const [members, setMembers] = useState<Servers.Member[] | undefined>(
-            undefined,
-        );
-
-        const store = useData();
-        const users = members
-            ?.map((member) => store.users.get(member._id.user)!)
-            .filter((x) => typeof x !== "undefined");
-
+        const client = useClient();
         const { openScreen } = useIntermediate();
         const status = useContext(StatusContext);
-        const client = useContext(AppContext);
 
         useEffect(() => {
-            if (
-                status === ClientStatus.ONLINE &&
-                typeof members === "undefined"
-            ) {
-                store
-                    .fetchMembers(channel.server!)
-                    .then((members) => setMembers(members));
+            if (status === ClientStatus.ONLINE) {
+                channel.server!.fetchMembers();
             }
         }, [status]);
 
-        // ! FIXME: temporary code
-        useEffect(() => {
-            function onPacket(packet: ClientboundNotification) {
-                if (!members) return;
-                if (packet.type === "ServerMemberJoin") {
-                    if (packet.id !== channel.server) return;
-                    setMembers([
-                        ...members,
-                        { _id: { server: packet.id, user: packet.user } },
-                    ]);
-                } else if (packet.type === "ServerMemberLeave") {
-                    if (packet.id !== channel.server) return;
-                    setMembers(
-                        members.filter(
-                            (x) =>
-                                !(
-                                    x._id.user === packet.user &&
-                                    x._id.server === packet.id
-                                ),
-                        ),
-                    );
-                }
-            }
-
-            client.addListener("packet", onPacket);
-            return () => client.removeListener("packet", onPacket);
-        }, [members]);
+        let users = [...client.members.keys()]
+            .filter((x) => x.server === channel.server_id)
+            .map((y) => client.users.get(y.user)!)
+            .filter((z) => typeof z !== "undefined");
 
         // copy paste from above
-        users?.sort((a, b) => {
+        users.sort((a, b) => {
             // ! FIXME: should probably rewrite all this code
             const l =
                 +(
-                    (a.online &&
-                        a.status?.presence !== Users.Presence.Invisible) ??
+                    (a.online && a.status?.presence !== Presence.Invisible) ??
                     false
                 ) | 0;
             const r =
                 +(
-                    (b.online &&
-                        b.status?.presence !== Users.Presence.Invisible) ??
+                    (b.online && b.status?.presence !== Presence.Invisible) ??
                     false
                 ) | 0;
 
@@ -243,9 +202,9 @@ export const ServerMemberSidebar = observer(
             <GenericSidebarBase>
                 <GenericSidebarList>
                     <ChannelDebugInfo id={channel._id} />
-                    <Search channel={channel._id} />
-                    <div>{!members && <Preloader type="ring" />}</div>
-                    {members && (
+                    <Search channel={channel} />
+                    <div>{users.length === 0 && <Preloader type="ring" />}</div>
+                    {users.length > 0 && (
                         <CollapsibleSection
                             //sticky //will re-add later, need to fix css
                             id="members"
@@ -256,10 +215,7 @@ export const ServerMemberSidebar = observer(
                                     {users?.length ?? 0}
                                 </span>
                             }>
-                            {(users?.length ?? 0) === 0 && (
-                                <img src={placeholderSVG} loading="eager" />
-                            )}
-                            {users?.map(
+                            {users.map(
                                 (user) =>
                                     user && (
                                         <UserButton
@@ -283,7 +239,7 @@ export const ServerMemberSidebar = observer(
     },
 );
 
-function Search({ channel }: { channel: string }) {
+function Search({ channel }: { channel: Channel }) {
     if (!getState().experiments.enabled?.includes("search")) return null;
 
     const client = useContext(AppContext);
@@ -294,11 +250,7 @@ function Search({ channel }: { channel: string }) {
     const [results, setResults] = useState<Message[]>([]);
 
     async function search() {
-        const data = await client.channels.searchWithUsers(
-            channel,
-            { query, sort },
-            true,
-        );
+        const data = await channel.searchWithUsers({ query, sort });
         setResults(data.messages);
     }
 
@@ -340,7 +292,6 @@ function Search({ channel }: { channel: string }) {
                 }}>
                 {results.map((message) => {
                     let href = "";
-                    const channel = client.channels.get(message.channel);
                     if (channel?.channel_type === "TextChannel") {
                         href += `/server/${channel.server}`;
                     }
@@ -355,10 +306,7 @@ function Search({ channel }: { channel: string }) {
                                     padding: "6px",
                                     background: "var(--primary-background)",
                                 }}>
-                                <b>
-                                    @
-                                    {client.users.get(message.author)?.username}
-                                </b>
+                                <b>@{message.author?.username}</b>
                                 <br />
                                 {message.content}
                             </div>
diff --git a/src/context/Voice.tsx b/src/context/Voice.tsx
index 9fe0676..43fbb9d 100644
--- a/src/context/Voice.tsx
+++ b/src/context/Voice.tsx
@@ -1,3 +1,5 @@
+import { Channel } from "revolt.js/dist/maps/Channels";
+
 import { createContext } from "preact";
 import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
 
@@ -21,7 +23,7 @@ export enum VoiceStatus {
 }
 
 export interface VoiceOperations {
-    connect: (channelId: string) => Promise<void>;
+    connect: (channel: Channel) => Promise<Channel>;
     disconnect: () => void;
     isProducing: (type: ProduceType) => boolean;
     startProducing: (type: ProduceType) => Promise<void>;
@@ -79,27 +81,25 @@ export default function Voice({ children }: Props) {
     const isConnecting = useRef(false);
     const operations: VoiceOperations = useMemo(() => {
         return {
-            connect: async (channelId) => {
+            connect: async (channel) => {
                 if (!client?.supported()) throw new Error("RTC is unavailable");
 
                 isConnecting.current = true;
-                setStatus(VoiceStatus.CONNECTING, channelId);
+                setStatus(VoiceStatus.CONNECTING, channel._id);
 
                 try {
-                    const call = await revoltClient.channels.joinCall(
-                        channelId,
-                    );
+                    const call = await channel.joinCall();
 
                     if (!isConnecting.current) {
                         setStatus(VoiceStatus.READY);
-                        return;
+                        return channel;
                     }
 
                     // ! FIXME: use configuration to check if voso is enabled
                     // await client.connect("wss://voso.revolt.chat/ws");
                     await client.connect(
                         "wss://voso.revolt.chat/ws",
-                        channelId,
+                        channel._id,
                     );
 
                     setStatus(VoiceStatus.AUTHENTICATING);
@@ -111,11 +111,12 @@ export default function Voice({ children }: Props) {
                 } catch (error) {
                     console.error(error);
                     setStatus(VoiceStatus.READY);
-                    return;
+                    return channel;
                 }
 
                 setStatus(VoiceStatus.CONNECTED);
                 isConnecting.current = false;
+                return channel;
             },
             disconnect: () => {
                 if (!client?.supported()) throw new Error("RTC is unavailable");
diff --git a/src/context/index.tsx b/src/context/index.tsx
index 46966c0..f7a3ccb 100644
--- a/src/context/index.tsx
+++ b/src/context/index.tsx
@@ -1,6 +1,5 @@
 import { BrowserRouter as Router } from "react-router-dom";
 
-import MobXState from "../mobx/State";
 import State from "../redux/State";
 
 import { Children } from "../types/Preact";
@@ -20,9 +19,7 @@ export default function Context({ children }: { children: Children }) {
                         <Locale>
                             <Intermediate>
                                 <Client>
-                                    <MobXState>
-                                        <Voice>{children}</Voice>
-                                    </MobXState>
+                                    <Voice>{children}</Voice>
                                 </Client>
                             </Intermediate>
                         </Locale>
diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx
index d472b09..9ff6657 100644
--- a/src/context/intermediate/Intermediate.tsx
+++ b/src/context/intermediate/Intermediate.tsx
@@ -57,7 +57,7 @@ export type Screen =
             }
           | {
                 type: "create_role";
-                server: string;
+                server: Server;
                 callback: (id: string) => void;
             }
       ))
diff --git a/src/context/intermediate/modals/Input.tsx b/src/context/intermediate/modals/Input.tsx
index 16eb011..a08f1da 100644
--- a/src/context/intermediate/modals/Input.tsx
+++ b/src/context/intermediate/modals/Input.tsx
@@ -1,4 +1,5 @@
 import { useHistory } from "react-router";
+import { Server } from "revolt.js/dist/maps/Servers";
 import { ulid } from "ulid";
 
 import { Text } from "preact-i18n";
@@ -81,7 +82,7 @@ type SpecialProps = { onClose: () => void } & (
               | "set_custom_status"
               | "add_friend";
       }
-    | { type: "create_role"; server: string; callback: (id: string) => void }
+    | { type: "create_role"; server: Server; callback: (id: string) => void }
 );
 
 export function SpecialInputModal(props: SpecialProps) {
@@ -134,10 +135,7 @@ export function SpecialInputModal(props: SpecialProps) {
                     }
                     field={<Text id="app.settings.permissions.role_name" />}
                     callback={async (name) => {
-                        const role = await client.servers.createRole(
-                            props.server,
-                            name,
-                        );
+                        const role = await props.server.createRole(name);
                         props.callback(role.id);
                     }}
                 />
@@ -151,7 +149,7 @@ export function SpecialInputModal(props: SpecialProps) {
                     field={<Text id="app.context_menu.custom_status" />}
                     defaultValue={client.user?.status?.text}
                     callback={(text) =>
-                        client.users.editUser({
+                        client.users.edit({
                             status: {
                                 ...client.user?.status,
                                 text: text.trim().length > 0 ? text : undefined,
@@ -166,7 +164,14 @@ export function SpecialInputModal(props: SpecialProps) {
                 <InputModal
                     onClose={onClose}
                     question={"Add Friend"}
-                    callback={(username) => client.users.addFriend(username)}
+                    callback={(username) =>
+                        client
+                            .req(
+                                "PUT",
+                                `/users/${username}/friend` as "/users/id/friend",
+                            )
+                            .then(undefined)
+                    }
                 />
             );
         }
diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx
index ebe44d7..34564e9 100644
--- a/src/context/intermediate/modals/Prompt.tsx
+++ b/src/context/intermediate/modals/Prompt.tsx
@@ -1,6 +1,9 @@
 import { observer } from "mobx-react-lite";
 import { useHistory } from "react-router-dom";
-import { Channels, Servers, Users } from "revolt.js/dist/api/objects";
+import { Channel } from "revolt.js/dist/maps/Channels";
+import { Message as MessageI } from "revolt.js/dist/maps/Messages";
+import { Server } from "revolt.js/dist/maps/Servers";
+import { User } from "revolt.js/dist/maps/Users";
 import { ulid } from "ulid";
 
 import styles from "./Prompt.module.scss";
@@ -9,9 +12,6 @@ import { useContext, useEffect, useState } from "preact/hooks";
 
 import { TextReact } from "../../../lib/i18n";
 
-import { Channel, Server, User } from "../../../mobx";
-import { useData } from "../../../mobx/State";
-
 import Message from "../../../components/common/messaging/Message";
 import UserIcon from "../../../components/common/user/UserIcon";
 import InputBox from "../../../components/ui/InputBox";
@@ -21,7 +21,7 @@ import Radio from "../../../components/ui/Radio";
 
 import { Children } from "../../../types/Preact";
 import { AppContext } from "../../revoltjs/RevoltClient";
-import { mapMessage, takeError } from "../../revoltjs/util";
+import { takeError } from "../../revoltjs/util";
 import { useIntermediate } from "../Intermediate";
 
 interface Props {
@@ -60,7 +60,7 @@ type SpecialProps = { onClose: () => void } & (
     | { type: "leave_server"; target: Server }
     | { type: "delete_server"; target: Server }
     | { type: "delete_channel"; target: Channel }
-    | { type: "delete_message"; target: Channels.Message }
+    | { type: "delete_message"; target: MessageI }
     | {
           type: "create_invite";
           target: Channel;
@@ -104,9 +104,7 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
                     name = props.target.username;
                     break;
                 case "close_dm":
-                    name = client.users.get(
-                        client.channels.getRecipient(props.target._id),
-                    )?.username;
+                    name = props.target.recipient?.username;
                     break;
                 default:
                     name = props.target.name;
@@ -137,27 +135,19 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
                                 try {
                                     switch (props.type) {
                                         case "unfriend_user":
-                                            await client.users.removeFriend(
-                                                props.target._id,
-                                            );
+                                            await props.target.removeFriend();
                                             break;
                                         case "block_user":
-                                            await client.users.blockUser(
-                                                props.target._id,
-                                            );
+                                            await props.target.blockUser();
                                             break;
                                         case "leave_group":
                                         case "close_dm":
                                         case "delete_channel":
-                                            await client.channels.delete(
-                                                props.target._id,
-                                            );
+                                            props.target.delete();
                                             break;
                                         case "leave_server":
                                         case "delete_server":
-                                            await client.servers.delete(
-                                                props.target._id,
-                                            );
+                                            props.target.delete();
                                             break;
                                     }
 
@@ -203,11 +193,7 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
                                 setProcessing(true);
 
                                 try {
-                                    await client.channels.deleteMessage(
-                                        props.target.channel,
-                                        props.target._id,
-                                    );
-
+                                    props.target.deleteMessage();
                                     onClose();
                                 } catch (err) {
                                     setError(takeError(err));
@@ -229,7 +215,7 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
                                 id={`app.special.modals.prompt.confirm_delete_message_long`}
                             />
                             <Message
-                                message={mapMessage(props.target)}
+                                message={props.target}
                                 head={true}
                                 contrast
                             />
@@ -247,8 +233,8 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
             useEffect(() => {
                 setProcessing(true);
 
-                client.channels
-                    .createInvite(props.target._id)
+                props.target
+                    .createInvite()
                     .then((code) => setCode(code))
                     .catch((err) => setError(takeError(err)))
                     .finally(() => setProcessing(false));
@@ -306,10 +292,13 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
                                 setProcessing(true);
 
                                 try {
-                                    await client.members.kickMember(
-                                        props.target._id,
-                                        props.user._id,
-                                    );
+                                    client.members
+                                        .get({
+                                            server: props.target._id,
+                                            user: props.user._id,
+                                        })
+                                        ?.kick();
+
                                     onClose();
                                 } catch (err) {
                                     setError(takeError(err));
@@ -357,11 +346,9 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
                                 setProcessing(true);
 
                                 try {
-                                    await client.servers.banUser(
-                                        props.target._id,
-                                        props.user._id,
-                                        { reason },
-                                    );
+                                    await props.target.banUser(props.user._id, {
+                                        reason,
+                                    });
                                     onClose();
                                 } catch (err) {
                                     setError(takeError(err));
@@ -420,14 +407,11 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
 
                                 try {
                                     const channel =
-                                        await client.servers.createChannel(
-                                            props.target._id,
-                                            {
-                                                type,
-                                                name,
-                                                nonce: ulid(),
-                                            },
-                                        );
+                                        await props.target.createChannel({
+                                            type,
+                                            name,
+                                            nonce: ulid(),
+                                        });
 
                                     history.push(
                                         `/server/${props.target._id}/channel/${channel._id}`,
diff --git a/src/context/intermediate/popovers/ChannelInfo.tsx b/src/context/intermediate/popovers/ChannelInfo.tsx
index 3b15cd7..a2f41f8 100644
--- a/src/context/intermediate/popovers/ChannelInfo.tsx
+++ b/src/context/intermediate/popovers/ChannelInfo.tsx
@@ -1,15 +1,12 @@
 import { X } from "@styled-icons/boxicons-regular";
 import { observer } from "mobx-react-lite";
+import { Channel } from "revolt.js/dist/maps/Channels";
 
 import styles from "./ChannelInfo.module.scss";
 
-import { Channel } from "../../../mobx";
-
 import Modal from "../../../components/ui/Modal";
 
 import Markdown from "../../../components/markdown/Markdown";
-import { useClient } from "../../revoltjs/RevoltClient";
-import { useForceUpdate } from "../../revoltjs/hooks";
 import { getChannelName } from "../../revoltjs/util";
 
 interface Props {
@@ -26,12 +23,11 @@ export const ChannelInfo = observer(({ channel, onClose }: Props) => {
         return null;
     }
 
-    const client = useClient();
     return (
         <Modal visible={true} onClose={onClose}>
             <div className={styles.info}>
                 <div className={styles.header}>
-                    <h1>{getChannelName(client, channel, true)}</h1>
+                    <h1>{getChannelName(channel, true)}</h1>
                     <div onClick={onClose}>
                         <X size={36} />
                     </div>
diff --git a/src/context/intermediate/popovers/ImageViewer.tsx b/src/context/intermediate/popovers/ImageViewer.tsx
index b3f049e..73d4a07 100644
--- a/src/context/intermediate/popovers/ImageViewer.tsx
+++ b/src/context/intermediate/popovers/ImageViewer.tsx
@@ -1,11 +1,7 @@
-import {
-    Attachment,
-    AttachmentMetadata,
-    EmbedImage,
-} from "revolt.js/dist/api/objects";
+import { Attachment, AttachmentMetadata } from "revolt-api/types/Autumn";
+import { EmbedImage } from "revolt-api/types/January";
 
 import styles from "./ImageViewer.module.scss";
-import { useContext, useEffect } from "preact/hooks";
 
 import AttachmentActions from "../../../components/common/messaging/attachments/AttachmentActions";
 import EmbedMediaActions from "../../../components/common/messaging/embed/EmbedMediaActions";
diff --git a/src/context/intermediate/popovers/PendingRequests.tsx b/src/context/intermediate/popovers/PendingRequests.tsx
index 03449e8..df7d66b 100644
--- a/src/context/intermediate/popovers/PendingRequests.tsx
+++ b/src/context/intermediate/popovers/PendingRequests.tsx
@@ -1,10 +1,9 @@
 import { observer } from "mobx-react-lite";
+import { User } from "revolt.js/dist/maps/Users";
 
 import styles from "./UserPicker.module.scss";
 import { Text } from "preact-i18n";
 
-import { User } from "../../../mobx";
-
 import Modal from "../../../components/ui/Modal";
 
 import { Friend } from "../../../pages/friends/Friend";
diff --git a/src/context/intermediate/popovers/UserPicker.tsx b/src/context/intermediate/popovers/UserPicker.tsx
index 3eaeb1d..3d2cec3 100644
--- a/src/context/intermediate/popovers/UserPicker.tsx
+++ b/src/context/intermediate/popovers/UserPicker.tsx
@@ -1,14 +1,14 @@
-import { Users } from "revolt.js/dist/api/objects";
+import { RelationshipStatus } from "revolt-api/types/Users";
 
 import styles from "./UserPicker.module.scss";
 import { Text } from "preact-i18n";
 import { useState } from "preact/hooks";
 
-import { useData } from "../../../mobx/State";
-
 import UserCheckbox from "../../../components/common/user/UserCheckbox";
 import Modal from "../../../components/ui/Modal";
 
+import { useClient } from "../../revoltjs/RevoltClient";
+
 interface Props {
     omit?: string[];
     onClose: () => void;
@@ -19,7 +19,7 @@ export function UserPicker(props: Props) {
     const [selected, setSelected] = useState<string[]>([]);
     const omit = [...(props.omit || []), "00000000000000000000000000"];
 
-    const store = useData();
+    const client = useClient();
 
     return (
         <Modal
@@ -33,11 +33,11 @@ export function UserPicker(props: Props) {
                 },
             ]}>
             <div className={styles.list}>
-                {[...store.users.values()]
+                {[...client.users.values()]
                     .filter(
                         (x) =>
                             x &&
-                            x.relationship === Users.Relationship.Friend &&
+                            x.relationship === RelationshipStatus.Friend &&
                             !omit.includes(x._id),
                     )
                     .map((x) => (
diff --git a/src/pages/channels/actions/HeaderActions.tsx b/src/pages/channels/actions/HeaderActions.tsx
index e3edd59..4451399 100644
--- a/src/pages/channels/actions/HeaderActions.tsx
+++ b/src/pages/channels/actions/HeaderActions.tsx
@@ -91,7 +91,7 @@ function VoiceActions({ channel }: Pick<ChannelHeaderProps, "channel">) {
             <IconButton
                 onClick={() => {
                     disconnect();
-                    connect(channel._id);
+                    connect(channel);
                 }}>
                 <PhoneCall size={24} />
             </IconButton>
diff --git a/src/pages/friends/Friend.tsx b/src/pages/friends/Friend.tsx
index 219f613..d796dce 100644
--- a/src/pages/friends/Friend.tsx
+++ b/src/pages/friends/Friend.tsx
@@ -48,10 +48,10 @@ export const Friend = observer(({ user }: Props) => {
                     onClick={(ev) =>
                         stopPropagation(
                             ev,
-                            user.openDM().then((channel) => {
-                                connect(channel._id);
-                                history.push(`/channel/${channel._id}`);
-                            }),
+                            user
+                                .openDM()
+                                .then(connect)
+                                .then((x) => history.push(`/channel/${x._id}`)),
                         )
                     }>
                     <PhoneCall size={20} />
diff --git a/src/pages/settings/server/Roles.tsx b/src/pages/settings/server/Roles.tsx
index 4431e47..cf5044f 100644
--- a/src/pages/settings/server/Roles.tsx
+++ b/src/pages/settings/server/Roles.tsx
@@ -97,7 +97,7 @@ export const Roles = observer(({ server }: Props) => {
                             openScreen({
                                 id: "special_input",
                                 type: "create_role",
-                                server: server._id,
+                                server,
                                 callback: (id) => setRole(id),
                             })
                         }
-- 
GitLab