diff --git a/src/Client.ts b/src/Client.ts index 3119525c..480f085a 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1,6 +1,7 @@ import type { Accessor, Setter } from "solid-js"; import { batch, createSignal } from "solid-js"; +import { ReactiveMap } from "@solid-primitives/map"; import { AsyncEventEmitter } from "@vladfrangu/async_event_emitter"; import { API } from "stoat-api"; import type { DataLogin, RevoltConfig, Role } from "stoat-api"; @@ -27,7 +28,7 @@ import { EventClient, type EventClientOptions, } from "./events/EventClient.js"; -import { ProtocolV1, handleEvent } from "./events/v1.js"; +import { ProtocolV1, UserSlowmodes, handleEvent } from "./events/v1.js"; import type { HydratedChannel } from "./hydration/channel.js"; import type { HydratedEmoji } from "./hydration/emoji.js"; import type { HydratedMessage } from "./hydration/message.js"; @@ -99,6 +100,8 @@ export type Events = { emojiCreate: [emoji: Emoji]; emojiDelete: [emoji: HydratedEmoji]; + + userSlowmodes: []; }; /** @@ -181,6 +184,7 @@ export class Client extends AsyncEventEmitter { readonly serverMembers; readonly sessions; readonly users; + readonly userSlowmodes; readonly api: API; readonly options: ClientOptions; @@ -199,7 +203,7 @@ export class Client extends AsyncEventEmitter { readonly connectionFailureCount: Accessor; #setConnectionFailureCount: Setter; #reconnectTimeout: number | undefined; - + #slowmodeTimers = new Map>(); /** * Create Stoat.js Client */ @@ -274,6 +278,7 @@ export class Client extends AsyncEventEmitter { this.serverMembers = new ServerMemberCollection(this); this.sessions = new SessionCollection(this); this.users = new UserCollection(this); + this.userSlowmodes = new ReactiveMap(); this.events = new EventClient(1, "json", this.options); this.events.on("error", (error) => this.emit("error", error)); @@ -578,4 +583,19 @@ export class Client extends AsyncEventEmitter { return data.id; } + + setSlowmode(channelId: string, data: UserSlowmodes): void { + const existing = this.#slowmodeTimers.get(channelId); + if (existing) clearTimeout(existing); + + this.userSlowmodes.set(channelId, { ...data, receivedAt: Date.now() }); + + const timer = setTimeout(() => { + this.userSlowmodes.delete(channelId); + this.#slowmodeTimers.delete(channelId); + this.emit("userSlowmodes"); + }, data.retry_after * 1000); + + this.#slowmodeTimers.set(channelId, timer); + } } diff --git a/src/events/v1.ts b/src/events/v1.ts index eb51b2f4..a085e6b1 100644 --- a/src/events/v1.ts +++ b/src/events/v1.ts @@ -203,6 +203,10 @@ type ServerMessage = type: "UserMoveVoiceChannel"; node: string; token: string; + } + | { + type: "UserSlowmodes"; + slowmodes: UserSlowmodes[]; }; /** @@ -235,6 +239,16 @@ type ChannelVoiceState = { participants: UserVoiceState[]; }; +/** + * Channel slowmodes for the active user + */ +export type UserSlowmodes = { + channel_id: string; + duration: number; + retry_after: number; + receivedAt?: number; +}; + /** * Initial synchronisation packet */ @@ -984,5 +998,12 @@ export async function handleEvent( // todo break; } + case "UserSlowmodes": { + for (const slowmode of event.slowmodes) { + client.setSlowmode(slowmode.channel_id, slowmode); + } + client.emit("userSlowmodes"); + break; + } } }