mirror of
https://github.com/stoatchat/javascript-client-sdk.git
synced 2026-07-19 17:13:31 -04:00
c6b90b08ee
* feat: pronouns Signed-off-by: İspik <ispik@ispik.dev> * feat: add pronouns Signed-off-by: İspik <ispik@ispik.dev> --------- Signed-off-by: İspik <ispik@ispik.dev>
1016 lines
25 KiB
TypeScript
1016 lines
25 KiB
TypeScript
import type { Setter } from "solid-js";
|
|
import { batch } from "solid-js";
|
|
|
|
import { ReactiveSet } from "@solid-primitives/set";
|
|
import type {
|
|
Channel,
|
|
ChannelUnread,
|
|
Emoji,
|
|
Error,
|
|
FieldsChannel,
|
|
FieldsMember,
|
|
FieldsServer,
|
|
FieldsUser,
|
|
Member,
|
|
MemberCompositeKey,
|
|
Message,
|
|
RelationshipStatus,
|
|
Role,
|
|
Server,
|
|
User,
|
|
} from "stoat-api";
|
|
|
|
import type { Client } from "../Client.js";
|
|
import { MessageEmbed } from "../classes/MessageEmbed.js";
|
|
import { ServerRole } from "../classes/ServerRole.js";
|
|
import { VoiceParticipant } from "../classes/VoiceParticipant.js";
|
|
import { hydrate } from "../hydration/index.js";
|
|
|
|
/**
|
|
* Version 1 of the events protocol
|
|
*/
|
|
export type ProtocolV1 = {
|
|
client: ClientMessage;
|
|
server: ServerMessage;
|
|
|
|
types: {
|
|
policyChange: PolicyChange;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Messages sent to the server
|
|
*/
|
|
type ClientMessage =
|
|
| { type: "Authenticate"; token: string }
|
|
| {
|
|
type: "BeginTyping";
|
|
channel: string;
|
|
}
|
|
| {
|
|
type: "EndTyping";
|
|
channel: string;
|
|
}
|
|
| {
|
|
type: "Ping";
|
|
data: number;
|
|
}
|
|
| {
|
|
type: "Pong";
|
|
data: number;
|
|
};
|
|
|
|
/**
|
|
* Messages sent from the server
|
|
*/
|
|
type ServerMessage =
|
|
| { type: "Error"; data: Error }
|
|
| { type: "Bulk"; v: ServerMessage[] }
|
|
| { type: "Authenticated" }
|
|
| ({ type: "Ready" } & Partial<ReadyData>)
|
|
| { type: "Ping"; data: number }
|
|
| { type: "Pong"; data: number }
|
|
| ({ type: "Message" } & Message)
|
|
| {
|
|
type: "MessageUpdate";
|
|
id: string;
|
|
channel: string;
|
|
data: Partial<Message>;
|
|
}
|
|
| {
|
|
type: "MessageAppend";
|
|
id: string;
|
|
channel: string;
|
|
append: Pick<Partial<Message>, "embeds">;
|
|
}
|
|
| { type: "MessageDelete"; id: string; channel: string }
|
|
| {
|
|
type: "MessageReact";
|
|
id: string;
|
|
channel_id: string;
|
|
user_id: string;
|
|
emoji_id: string;
|
|
}
|
|
| {
|
|
type: "MessageUnreact";
|
|
id: string;
|
|
channel_id: string;
|
|
user_id: string;
|
|
emoji_id: string;
|
|
}
|
|
| {
|
|
type: "MessageRemoveReaction";
|
|
id: string;
|
|
channel_id: string;
|
|
emoji_id: string;
|
|
}
|
|
| { type: "BulkMessageDelete"; channel: string; ids: string[] }
|
|
| ({ type: "ChannelCreate" } & Channel)
|
|
| {
|
|
type: "ChannelUpdate";
|
|
id: string;
|
|
data: Partial<Channel>;
|
|
clear?: FieldsChannel[];
|
|
}
|
|
| { type: "ChannelDelete"; id: string }
|
|
| { type: "ChannelGroupJoin"; id: string; user: string }
|
|
| { type: "ChannelGroupLeave"; id: string; user: string }
|
|
| { type: "ChannelStartTyping"; id: string; user: string }
|
|
| { type: "ChannelStopTyping"; id: string; user: string }
|
|
| { type: "ChannelAck"; id: string; user: string; message_id: string }
|
|
| {
|
|
type: "ServerCreate";
|
|
id: string;
|
|
server: Server;
|
|
channels: Channel[];
|
|
}
|
|
| {
|
|
type: "ServerUpdate";
|
|
id: string;
|
|
data: Partial<Server>;
|
|
clear?: FieldsServer[];
|
|
}
|
|
| { type: "ServerDelete"; id: string }
|
|
| {
|
|
type: "ServerMemberUpdate";
|
|
id: MemberCompositeKey;
|
|
data: Partial<Member>;
|
|
clear?: FieldsMember[];
|
|
}
|
|
| { type: "ServerMemberJoin"; id: string; user: string }
|
|
| { type: "ServerMemberLeave"; id: string; user: string }
|
|
| {
|
|
type: "ServerRoleUpdate";
|
|
id: string;
|
|
role_id: string;
|
|
data: Partial<Role>;
|
|
}
|
|
| { type: "ServerRoleDelete"; id: string; role_id: string }
|
|
| {
|
|
type: "UserUpdate";
|
|
id: string;
|
|
data: Partial<User>;
|
|
clear?: FieldsUser[];
|
|
}
|
|
| { type: "UserRelationship"; user: User; status: RelationshipStatus }
|
|
| { type: "UserPresence"; id: string; online: boolean }
|
|
| {
|
|
type: "UserSettingsUpdate";
|
|
id: string;
|
|
update: { [key: string]: [number, string] };
|
|
}
|
|
| { type: "UserPlatformWipe"; user_id: string; flags: number }
|
|
| ({ type: "EmojiCreate" } & Emoji)
|
|
| { type: "EmojiDelete"; id: string }
|
|
| ({
|
|
type: "Auth";
|
|
} & (
|
|
| {
|
|
event_type: "DeleteSession";
|
|
user_id: string;
|
|
session_id: string;
|
|
}
|
|
| {
|
|
event_type: "DeleteAllSessions";
|
|
user_id: string;
|
|
exclude_session_id: string;
|
|
}
|
|
))
|
|
| {
|
|
type: "VoiceChannelJoin";
|
|
id: string;
|
|
state: UserVoiceState;
|
|
}
|
|
| {
|
|
type: "VoiceChannelLeave";
|
|
id: string;
|
|
user: string;
|
|
}
|
|
| {
|
|
type: "VoiceChannelMove";
|
|
user: string;
|
|
from: string;
|
|
to: string;
|
|
state: UserVoiceState;
|
|
}
|
|
| {
|
|
type: "UserVoiceStateUpdate";
|
|
id: string;
|
|
channel_id: string;
|
|
data: Partial<UserVoiceState>;
|
|
}
|
|
| {
|
|
type: "UserMoveVoiceChannel";
|
|
node: string;
|
|
token: string;
|
|
}
|
|
| {
|
|
type: "UserSlowmodes";
|
|
slowmodes: UserSlowmodes[];
|
|
};
|
|
|
|
/**
|
|
* Policy change type
|
|
*/
|
|
type PolicyChange = {
|
|
created_time: string;
|
|
effective_time: string;
|
|
description: string;
|
|
url: string;
|
|
};
|
|
|
|
/**
|
|
* Voice state for a user
|
|
*/
|
|
export type UserVoiceState = {
|
|
id: string;
|
|
joined_at: number;
|
|
is_receiving: boolean;
|
|
is_publishing: boolean;
|
|
screensharing: boolean;
|
|
camera: boolean;
|
|
};
|
|
|
|
/**
|
|
* Voice state for a channel
|
|
*/
|
|
type ChannelVoiceState = {
|
|
id: string;
|
|
participants: UserVoiceState[];
|
|
};
|
|
|
|
/**
|
|
* Channel slowmodes for the active user
|
|
*/
|
|
export type UserSlowmodes = {
|
|
channel_id: string;
|
|
duration: number;
|
|
retry_after: number;
|
|
receivedAt?: number;
|
|
};
|
|
|
|
/**
|
|
* Initial synchronisation packet
|
|
*/
|
|
type ReadyData = {
|
|
users: User[];
|
|
servers: Server[];
|
|
channels: Channel[];
|
|
members: Member[];
|
|
emojis: Emoji[];
|
|
voice_states: ChannelVoiceState[];
|
|
|
|
user_settings: Record<string, unknown>;
|
|
channel_unreads: ChannelUnread[];
|
|
|
|
policy_changes: PolicyChange[];
|
|
};
|
|
|
|
/**
|
|
* Handle an event for the Client
|
|
* @param client Client
|
|
* @param event Event
|
|
* @param setReady Signal state change
|
|
*/
|
|
export async function handleEvent(
|
|
client: Client,
|
|
event: ServerMessage,
|
|
setReady: Setter<boolean>,
|
|
): Promise<void> {
|
|
if (client.options.debug) {
|
|
console.debug("[S->C]", event);
|
|
}
|
|
|
|
switch (event.type) {
|
|
case "Bulk": {
|
|
for (const item of event.v) {
|
|
handleEvent(client, item, setReady);
|
|
}
|
|
break;
|
|
}
|
|
case "Ready": {
|
|
batch(() => {
|
|
if (event.users) {
|
|
for (const user of event.users) {
|
|
const u = client.users.getOrCreate(user._id, user);
|
|
|
|
if (u.relationship === "User") {
|
|
client.user = u;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (event.servers) {
|
|
for (const server of event.servers) {
|
|
client.servers.getOrCreate(server._id, server);
|
|
}
|
|
}
|
|
|
|
if (event.members) {
|
|
for (const member of event.members) {
|
|
client.serverMembers.getOrCreate(member._id, member);
|
|
}
|
|
}
|
|
|
|
if (event.channels) {
|
|
for (const channel of event.channels) {
|
|
client.channels.getOrCreate(channel._id, channel);
|
|
}
|
|
}
|
|
|
|
if (event.voice_states) {
|
|
for (const state of event.voice_states) {
|
|
const channel = client.channels.get(state.id);
|
|
if (channel) {
|
|
channel.voiceParticipants.clear();
|
|
|
|
for (const participant of state.participants) {
|
|
channel.voiceParticipants.set(
|
|
participant.id,
|
|
new VoiceParticipant(client, participant),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (event.emojis) {
|
|
for (const emoji of event.emojis) {
|
|
client.emojis.getOrCreate(emoji._id, emoji);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (client.options.syncUnreads) {
|
|
await client.channelUnreads.sync();
|
|
}
|
|
|
|
setReady(true);
|
|
client.emit("ready");
|
|
|
|
if (event.policy_changes?.length) {
|
|
client.emit("policyChanges", event.policy_changes, async () =>
|
|
client.api.post("/policy/acknowledge"),
|
|
);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "Message": {
|
|
if (!client.messages.has(event._id)) {
|
|
batch(() => {
|
|
if (event.member) {
|
|
client.serverMembers.getOrCreate(event.member._id, event.member);
|
|
}
|
|
|
|
if (event.user) {
|
|
client.users.getOrCreate(event.user._id, event.user);
|
|
}
|
|
|
|
delete event.member;
|
|
delete event.user;
|
|
|
|
client.messages.getOrCreate(event._id, event, true);
|
|
|
|
const channel = client.channels.get(event.channel);
|
|
if (!channel) return;
|
|
|
|
client.channels.updateUnderlyingObject(
|
|
channel.id,
|
|
"lastMessageId",
|
|
event._id,
|
|
);
|
|
|
|
if (
|
|
event.mentions?.includes(client.user!.id) &&
|
|
client.options.syncUnreads
|
|
) {
|
|
const unread = client.channelUnreads.for(channel);
|
|
unread.messageMentionIds.add(event._id);
|
|
client.channels.updateUnderlyingObject(
|
|
event.channel,
|
|
"lastMessageId",
|
|
event._id,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case "MessageUpdate": {
|
|
const message = client.messages.getOrPartial(event.id);
|
|
if (message) {
|
|
const previousMessage = {
|
|
...client.messages.getUnderlyingObject(event.id),
|
|
channelId: event.channel,
|
|
};
|
|
|
|
client.messages.updateUnderlyingObject(event.id, {
|
|
...hydrate(
|
|
"message",
|
|
{ ...event.data, channel: event.channel },
|
|
client,
|
|
false,
|
|
),
|
|
editedAt: new Date(),
|
|
});
|
|
|
|
client.emit("messageUpdate", message, previousMessage);
|
|
}
|
|
break;
|
|
}
|
|
case "MessageAppend": {
|
|
const message = client.messages.getOrPartial(event.id);
|
|
if (message) {
|
|
const previousMessage = {
|
|
...client.messages.getUnderlyingObject(event.id),
|
|
channelId: event.channel,
|
|
};
|
|
|
|
client.messages.updateUnderlyingObject(event.id, "embeds", (embeds) => [
|
|
...(embeds ?? []),
|
|
...(event.append.embeds?.map((embed) =>
|
|
MessageEmbed.from(client, embed),
|
|
) ?? []),
|
|
]);
|
|
|
|
client.messages.updateUnderlyingObject(
|
|
event.id,
|
|
"channelId",
|
|
event.channel,
|
|
);
|
|
|
|
client.emit("messageUpdate", message, previousMessage);
|
|
}
|
|
break;
|
|
}
|
|
case "MessageDelete": {
|
|
if (client.messages.getOrPartial(event.id)) {
|
|
const message = client.messages.getUnderlyingObject(event.id);
|
|
client.emit("messageDelete", message);
|
|
client.messages.delete(event.id);
|
|
}
|
|
break;
|
|
}
|
|
case "BulkMessageDelete": {
|
|
batch(() =>
|
|
client.emit(
|
|
"messageDeleteBulk",
|
|
event.ids
|
|
.map((id) => {
|
|
if (client.messages.has(id)) {
|
|
const message = client.messages.getUnderlyingObject(id);
|
|
client.messages.delete(id);
|
|
return message!;
|
|
}
|
|
|
|
return undefined!;
|
|
})
|
|
.filter((x) => x),
|
|
client.channels.get(event.channel),
|
|
),
|
|
);
|
|
break;
|
|
}
|
|
case "MessageReact": {
|
|
const message = client.messages.getOrPartial(event.id);
|
|
if (message) {
|
|
const reactions = message.reactions;
|
|
const set = reactions.get(event.emoji_id)!;
|
|
if (set) {
|
|
if (set.has(event.user_id)) return;
|
|
set.add(event.user_id);
|
|
} else {
|
|
reactions.set(event.emoji_id, new ReactiveSet([event.user_id]));
|
|
}
|
|
|
|
client.emit(
|
|
"messageReactionAdd",
|
|
message,
|
|
event.user_id,
|
|
event.emoji_id,
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "MessageUnreact": {
|
|
const message = client.messages.getOrPartial(event.id);
|
|
if (message) {
|
|
const set = message.reactions.get(event.emoji_id);
|
|
if (set?.has(event.user_id)) {
|
|
if (
|
|
set.size === 1 &&
|
|
!message.interactions?.reactions?.includes(event.emoji_id)
|
|
) {
|
|
message.reactions.delete(event.emoji_id);
|
|
} else {
|
|
set.delete(event.user_id);
|
|
}
|
|
} else if (!client.messages.isPartial(event.id)) {
|
|
return;
|
|
}
|
|
|
|
client.emit(
|
|
"messageReactionRemove",
|
|
message,
|
|
event.user_id,
|
|
event.emoji_id,
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "MessageRemoveReaction": {
|
|
const message = client.messages.getOrPartial(event.id);
|
|
if (message) {
|
|
const reactions = message.reactions;
|
|
if (reactions.has(event.emoji_id)) {
|
|
reactions.delete(event.emoji_id);
|
|
} else if (!client.messages.isPartial(event.id)) {
|
|
return;
|
|
}
|
|
|
|
client.emit("messageReactionRemoveEmoji", message, event.emoji_id);
|
|
}
|
|
break;
|
|
}
|
|
case "ChannelCreate": {
|
|
if (!client.channels.has(event._id)) {
|
|
client.channels.getOrCreate(event._id, event, true);
|
|
}
|
|
break;
|
|
}
|
|
case "ChannelUpdate": {
|
|
const channel = client.channels.getOrPartial(event.id);
|
|
if (channel) {
|
|
const previousChannel = {
|
|
...client.channels.getUnderlyingObject(event.id),
|
|
};
|
|
|
|
const changes = hydrate("channel", event.data, client, false);
|
|
|
|
if (event.clear) {
|
|
for (const remove of event.clear) {
|
|
switch (remove) {
|
|
case "Description":
|
|
changes["description"] = undefined;
|
|
break;
|
|
case "DefaultPermissions":
|
|
changes["defaultPermissions"] = undefined;
|
|
break;
|
|
case "Icon":
|
|
changes["icon"] = undefined;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
client.channels.updateUnderlyingObject(event.id, changes);
|
|
client.emit("channelUpdate", channel, previousChannel);
|
|
}
|
|
break;
|
|
}
|
|
case "ChannelDelete": {
|
|
if (client.channels.getOrPartial(event.id)) {
|
|
const channel = client.channels.getUnderlyingObject(event.id);
|
|
client.emit("channelDelete", channel);
|
|
client.channels.delete(event.id);
|
|
}
|
|
break;
|
|
}
|
|
case "ChannelGroupJoin": {
|
|
const channel = client.channels.getOrPartial(event.id);
|
|
if (channel) {
|
|
if (!channel.recipientIds.has(event.user)) {
|
|
channel.recipientIds.add(event.user);
|
|
} else if (!client.channels.isPartial(event.id)) {
|
|
return;
|
|
}
|
|
|
|
client.emit(
|
|
"channelGroupJoin",
|
|
channel,
|
|
await client.users.fetch(event.user),
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "ChannelGroupLeave": {
|
|
const channel = client.channels.getOrPartial(event.id);
|
|
if (channel) {
|
|
if (channel.recipientIds.has(event.user)) {
|
|
channel.recipientIds.delete(event.user);
|
|
} else if (!client.channels.isPartial(event.id)) {
|
|
return;
|
|
}
|
|
|
|
client.emit(
|
|
"channelGroupLeave",
|
|
channel,
|
|
client.users.getOrPartial(event.user)!,
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "ChannelStartTyping": {
|
|
const channel = client.channels.getOrPartial(event.id);
|
|
if (channel) {
|
|
if (!channel.typingIds.has(event.user)) {
|
|
channel.typingIds.add(event.user);
|
|
|
|
clearTimeout(channel._typingTimers[event.user]);
|
|
channel._typingTimers[event.user] = setTimeout(
|
|
() =>
|
|
handleEvent(
|
|
client,
|
|
{ ...event, type: "ChannelStopTyping" },
|
|
setReady,
|
|
),
|
|
4000,
|
|
) as never;
|
|
|
|
client.emit(
|
|
"channelStartTyping",
|
|
channel,
|
|
client.users.getOrPartial(event.user)!,
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "ChannelStopTyping": {
|
|
const channel = client.channels.getOrPartial(event.id);
|
|
if (channel) {
|
|
if (channel.typingIds.has(event.user)) {
|
|
channel.typingIds.delete(event.user);
|
|
|
|
clearTimeout(channel._typingTimers[event.user]);
|
|
delete channel._typingTimers[event.user];
|
|
|
|
client.emit(
|
|
"channelStopTyping",
|
|
channel,
|
|
client.users.getOrPartial(event.user)!,
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "ChannelAck": {
|
|
const channel = client.channels.getOrPartial(event.id);
|
|
if (channel) {
|
|
client.emit("channelAcknowledged", channel, event.message_id);
|
|
}
|
|
break;
|
|
}
|
|
case "ServerCreate": {
|
|
if (!client.servers.has(event.server._id)) {
|
|
batch(() => {
|
|
for (const channel of event.channels) {
|
|
client.channels.getOrCreate(channel._id, channel);
|
|
}
|
|
|
|
client.servers.getOrCreate(event.server._id, event.server, true);
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case "ServerUpdate": {
|
|
const server = client.servers.getOrPartial(event.id);
|
|
if (server) {
|
|
const previousServer = {
|
|
...client.servers.getUnderlyingObject(event.id),
|
|
};
|
|
|
|
const changes = hydrate("server", event.data, client, false);
|
|
|
|
if (event.clear) {
|
|
for (const remove of event.clear) {
|
|
switch (remove) {
|
|
case "Banner":
|
|
changes["banner"] = undefined;
|
|
break;
|
|
case "Categories":
|
|
changes["categories"] = undefined;
|
|
break;
|
|
case "SystemMessages":
|
|
changes["systemMessages"] = undefined;
|
|
break;
|
|
case "Description":
|
|
changes["description"] = undefined;
|
|
break;
|
|
case "Icon":
|
|
changes["icon"] = undefined;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
client.servers.updateUnderlyingObject(event.id, changes);
|
|
client.emit("serverUpdate", server, previousServer);
|
|
}
|
|
break;
|
|
}
|
|
case "ServerDelete": {
|
|
const server = client.servers.getOrPartial(event.id);
|
|
if (server) {
|
|
// TODO: server should tell us if it's a leave or delete on our end
|
|
server.$delete();
|
|
}
|
|
break;
|
|
}
|
|
case "ServerRoleUpdate": {
|
|
const server = client.servers.getOrPartial(event.id);
|
|
if (server) {
|
|
const role = server.roles.get(event.role_id) ?? {};
|
|
server.roles.set(
|
|
event.role_id,
|
|
new ServerRole(client, server.id, event.role_id, {
|
|
...role,
|
|
...event.data,
|
|
} as never),
|
|
);
|
|
|
|
client.emit("serverRoleUpdate", server, event.role_id, role as never);
|
|
}
|
|
break;
|
|
}
|
|
case "ServerRoleDelete": {
|
|
const server = client.servers.getOrPartial(event.id);
|
|
if (server) {
|
|
let role = {};
|
|
const roles = server.roles;
|
|
if (roles.has(event.role_id)) {
|
|
role = roles.get(event.role_id) as never;
|
|
roles.delete(event.role_id);
|
|
} else if (!client.servers.isPartial(event.id)) {
|
|
return;
|
|
}
|
|
|
|
client.emit("serverRoleDelete", server, event.role_id, role as never);
|
|
}
|
|
break;
|
|
}
|
|
case "ServerMemberJoin": {
|
|
const id = {
|
|
server: event.id,
|
|
user: event.user,
|
|
};
|
|
|
|
if (!client.serverMembers.hasByKey(id)) {
|
|
if (!client.users.has(id.user)) {
|
|
if (client.options.eagerFetching) {
|
|
await client.users.fetch(id.user);
|
|
}
|
|
}
|
|
|
|
client.emit(
|
|
"serverMemberJoin",
|
|
client.serverMembers.getOrCreate(id, {
|
|
_id: id,
|
|
joined_at: new Date().toUTCString(),
|
|
}),
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case "ServerMemberUpdate": {
|
|
const member = client.serverMembers.getOrPartial(event.id);
|
|
if (member) {
|
|
const previousMember = {
|
|
...client.serverMembers.getUnderlyingObject(
|
|
event.id.server + event.id.user,
|
|
),
|
|
};
|
|
|
|
const changes = hydrate("serverMember", event.data, client, false);
|
|
|
|
if (event.clear) {
|
|
for (const remove of event.clear) {
|
|
switch (remove) {
|
|
case "Nickname":
|
|
changes["nickname"] = undefined;
|
|
break;
|
|
case "Avatar":
|
|
changes["avatar"] = undefined;
|
|
break;
|
|
case "Roles":
|
|
changes["roles"] = [];
|
|
break;
|
|
case "Timeout":
|
|
changes["timeout"] = undefined;
|
|
break;
|
|
case "Pronouns":
|
|
changes["pronouns"] = undefined;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
client.serverMembers.updateUnderlyingObject(
|
|
event.id.server + event.id.user,
|
|
changes as never,
|
|
);
|
|
|
|
client.emit("serverMemberUpdate", member, previousMember);
|
|
}
|
|
break;
|
|
}
|
|
case "ServerMemberLeave": {
|
|
if (event.user && event.user === client.user!.id) {
|
|
handleEvent(
|
|
client,
|
|
{
|
|
type: "ServerDelete",
|
|
id: event.id,
|
|
},
|
|
setReady,
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
const id = {
|
|
server: event.id,
|
|
user: event.user,
|
|
};
|
|
|
|
if (client.serverMembers.getOrPartial(id)) {
|
|
const member = client.serverMembers.getUnderlyingObject(
|
|
id.server + id.user,
|
|
);
|
|
|
|
client.emit("serverMemberLeave", member);
|
|
client.serverMembers.delete(id.server + id.user);
|
|
}
|
|
break;
|
|
}
|
|
case "UserUpdate": {
|
|
const user = client.users.getOrPartial(event.id);
|
|
if (user) {
|
|
const previousUser = {
|
|
...client.users.getUnderlyingObject(event.id),
|
|
};
|
|
|
|
const changes = hydrate("user", event.data, client, false);
|
|
|
|
if (event.clear) {
|
|
for (const remove of event.clear) {
|
|
switch (remove) {
|
|
case "Avatar":
|
|
changes["avatar"] = undefined;
|
|
break;
|
|
case "StatusPresence":
|
|
changes["status"] = {
|
|
...(previousUser.status ?? {}),
|
|
...(changes["status"] ?? {}),
|
|
presence: undefined,
|
|
};
|
|
break;
|
|
case "StatusText":
|
|
changes["status"] = {
|
|
...(previousUser.status ?? {}),
|
|
...(changes["status"] ?? {}),
|
|
text: undefined,
|
|
};
|
|
break;
|
|
case "Pronouns":
|
|
changes["pronouns"] = undefined;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
client.users.updateUnderlyingObject(event.id, changes as never);
|
|
client.emit("userUpdate", user, previousUser);
|
|
}
|
|
break;
|
|
}
|
|
case "UserRelationship": {
|
|
handleEvent(
|
|
client,
|
|
{
|
|
type: "UserUpdate",
|
|
id: event.user._id,
|
|
data: {
|
|
relationship: event.user.relationship!,
|
|
},
|
|
},
|
|
setReady,
|
|
);
|
|
break;
|
|
}
|
|
case "UserPresence": {
|
|
handleEvent(
|
|
client,
|
|
{
|
|
type: "UserUpdate",
|
|
id: event.id,
|
|
data: {
|
|
online: event.online,
|
|
},
|
|
},
|
|
setReady,
|
|
);
|
|
break;
|
|
}
|
|
case "UserSettingsUpdate": {
|
|
client.emit("userSettingsUpdate", event.id, event.update);
|
|
break;
|
|
}
|
|
case "UserPlatformWipe": {
|
|
batch(() => {
|
|
handleEvent(
|
|
client,
|
|
{
|
|
type: "BulkMessageDelete",
|
|
channel: "0",
|
|
ids: client.messages
|
|
.toList()
|
|
.filter((message) => message.authorId === event.user_id)
|
|
.map((message) => message.id),
|
|
},
|
|
setReady,
|
|
);
|
|
|
|
handleEvent(
|
|
client,
|
|
{
|
|
type: "UserUpdate",
|
|
id: event.user_id,
|
|
data: {
|
|
username: `Deleted User`,
|
|
online: false,
|
|
flags: event.flags,
|
|
badges: 0,
|
|
relationship: "None",
|
|
},
|
|
clear: ["Avatar", "StatusPresence", "StatusText"],
|
|
},
|
|
setReady,
|
|
);
|
|
});
|
|
|
|
break;
|
|
}
|
|
case "EmojiCreate": {
|
|
if (!client.emojis.has(event._id)) {
|
|
client.emojis.getOrCreate(event._id, event, true);
|
|
}
|
|
break;
|
|
}
|
|
case "EmojiDelete": {
|
|
if (client.emojis.getOrPartial(event.id)) {
|
|
const emoji = client.emojis.getUnderlyingObject(event.id);
|
|
client.emit("emojiDelete", emoji);
|
|
client.emojis.delete(event.id);
|
|
}
|
|
break;
|
|
}
|
|
case "Auth": {
|
|
// TODO: implement DeleteSession and DeleteAllSessions
|
|
break;
|
|
}
|
|
case "VoiceChannelJoin": {
|
|
const channel = client.channels.getOrPartial(event.id);
|
|
if (channel) {
|
|
channel.voiceParticipants.set(
|
|
event.state.id,
|
|
new VoiceParticipant(client, event.state),
|
|
);
|
|
// todo: event
|
|
}
|
|
break;
|
|
}
|
|
case "VoiceChannelLeave": {
|
|
const channel = client.channels.getOrPartial(event.id);
|
|
if (channel) {
|
|
channel.voiceParticipants.delete(event.user);
|
|
// todo: event
|
|
}
|
|
break;
|
|
}
|
|
case "VoiceChannelMove": {
|
|
// todo
|
|
break;
|
|
}
|
|
case "UserVoiceStateUpdate": {
|
|
const channel = client.channels.getOrPartial(event.channel_id);
|
|
if (channel) {
|
|
channel.voiceParticipants.get(event.id)?.update(event.data);
|
|
// todo: event
|
|
}
|
|
break;
|
|
}
|
|
case "UserMoveVoiceChannel": {
|
|
// todo
|
|
break;
|
|
}
|
|
case "UserSlowmodes": {
|
|
for (const slowmode of event.slowmodes) {
|
|
client.setSlowmode(slowmode.channel_id, slowmode);
|
|
}
|
|
client.emit("userSlowmodes");
|
|
break;
|
|
}
|
|
}
|
|
}
|