mirror of
https://github.com/stoatchat/javascript-client-sdk.git
synced 2026-07-21 04:25:27 -04:00
feat: implement unreads functionality
This commit is contained in:
+27
-1
@@ -7,6 +7,7 @@ import type { DataLogin, RevoltConfig } from "revolt-api";
|
||||
import { Channel, Emoji, Message, Server, ServerMember, User } from "./classes";
|
||||
import {
|
||||
ChannelCollection,
|
||||
ChannelUnreadCollection,
|
||||
EmojiCollection,
|
||||
MessageCollection,
|
||||
ServerCollection,
|
||||
@@ -100,6 +101,12 @@ export type ClientOptions = Partial<EventClientOptions> & {
|
||||
*/
|
||||
partials: boolean;
|
||||
|
||||
/**
|
||||
* Whether to automatically sync unreads information
|
||||
* @default false
|
||||
*/
|
||||
syncUnreads: boolean;
|
||||
|
||||
/**
|
||||
* Whether to reconnect when disconnected
|
||||
* @default true
|
||||
@@ -112,7 +119,15 @@ export type ClientOptions = Partial<EventClientOptions> & {
|
||||
* @returns Delay in seconds
|
||||
* @default (2^x-1) ±20%
|
||||
*/
|
||||
retryDelayFunction: (retryCount: number) => number;
|
||||
retryDelayFunction(retryCount: number): number;
|
||||
|
||||
/**
|
||||
* Check whether a channel is muted
|
||||
* @param channel Channel
|
||||
* @return Whether it is muted
|
||||
* @default false
|
||||
*/
|
||||
channelIsMuted(channel: Channel): boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -120,6 +135,7 @@ export type ClientOptions = Partial<EventClientOptions> & {
|
||||
*/
|
||||
export class Client extends EventEmitter<Events> {
|
||||
readonly channels;
|
||||
readonly channelUnreads;
|
||||
readonly emojis;
|
||||
readonly messages;
|
||||
readonly users;
|
||||
@@ -150,6 +166,7 @@ export class Client extends EventEmitter<Events> {
|
||||
this.options = {
|
||||
baseURL: "https://api.revolt.chat",
|
||||
partials: false,
|
||||
syncUnreads: false,
|
||||
autoReconnect: true,
|
||||
/**
|
||||
* Retry delay function
|
||||
@@ -159,6 +176,14 @@ export class Client extends EventEmitter<Events> {
|
||||
retryDelayFunction(retryCount) {
|
||||
return (Math.pow(2, retryCount) - 1) * (0.8 + Math.random() * 0.4);
|
||||
},
|
||||
/**
|
||||
* Check whether a channel is muted
|
||||
* @param channel Channel
|
||||
* @return Whether it is muted
|
||||
*/
|
||||
channelIsMuted() {
|
||||
return false;
|
||||
},
|
||||
...options,
|
||||
};
|
||||
|
||||
@@ -175,6 +200,7 @@ export class Client extends EventEmitter<Events> {
|
||||
this.#setConnectionFailureCount = setConnectionFailureCount;
|
||||
|
||||
this.channels = new ChannelCollection(this);
|
||||
this.channelUnreads = new ChannelUnreadCollection(this);
|
||||
this.emojis = new EmojiCollection(this);
|
||||
this.messages = new MessageCollection(this);
|
||||
this.users = new UserCollection(this);
|
||||
|
||||
+41
-7
@@ -256,14 +256,35 @@ export class Channel {
|
||||
return this.lastMessageAt ?? this.createdAt;
|
||||
}
|
||||
|
||||
// TODO: lastMessage
|
||||
|
||||
/**
|
||||
* Get whether this channel is unread.
|
||||
*/
|
||||
get unread() {
|
||||
return false;
|
||||
if (
|
||||
!this.lastMessageId ||
|
||||
this.type === "SavedMessages" ||
|
||||
this.type === "VoiceChannel" ||
|
||||
this.#collection.client.options.channelIsMuted(this)
|
||||
)
|
||||
return false;
|
||||
|
||||
return (
|
||||
(
|
||||
this.#collection.client.channelUnreads.get(this.id)?.lastMessageId ??
|
||||
"0"
|
||||
).localeCompare(this.lastMessageId) === -1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mentions in this channel for user.
|
||||
*/
|
||||
get mentions() {
|
||||
return [];
|
||||
if (this.type === "SavedMessages" || this.type === "VoiceChannel")
|
||||
return undefined;
|
||||
|
||||
return this.#collection.client.channelUnreads.get(this.id)
|
||||
?.messageMentionIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -551,17 +572,30 @@ export class Channel {
|
||||
* @param skipRateLimiter Whether to skip the internal rate limiter
|
||||
*/
|
||||
async ack(message?: Message | string, skipRateLimiter?: boolean) {
|
||||
const id =
|
||||
const lastMessageId =
|
||||
(typeof message === "string" ? message : message?.id) ??
|
||||
this.lastMessageId ??
|
||||
ulid();
|
||||
|
||||
const unreads = this.#collection.client.channelUnreads;
|
||||
const channelUnread = unreads.get(this.id);
|
||||
if (channelUnread) {
|
||||
unreads.updateUnderlyingObject(this.id, {
|
||||
lastMessageId,
|
||||
});
|
||||
|
||||
if (channelUnread.messageMentionIds.size) {
|
||||
channelUnread.messageMentionIds.clear();
|
||||
}
|
||||
}
|
||||
|
||||
const performAck = () => {
|
||||
this.#ackLimit = undefined;
|
||||
this.#collection.client.api.put(`/channels/${this.id}/ack/${id as ""}`);
|
||||
this.#collection.client.api.put(
|
||||
`/channels/${this.id}/ack/${lastMessageId as ""}`
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: !this.collection.client.options.ackRateLimiter
|
||||
if (skipRateLimiter) return performAck();
|
||||
|
||||
clearTimeout(this.#ackTimeout);
|
||||
|
||||
@@ -28,6 +28,6 @@ export class ChannelUnread {
|
||||
* List of message IDs that we were mentioned in
|
||||
*/
|
||||
get messageMentionIds() {
|
||||
return this.#collection.getUnderlyingObject(this.id).lastMessageId;
|
||||
return this.#collection.getUnderlyingObject(this.id).messageMentionIds;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-2
@@ -235,12 +235,24 @@ export class Server {
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the server is currently unread
|
||||
* @returns Whether the server is unread
|
||||
*/
|
||||
get unread() {
|
||||
return false;
|
||||
return this.channels.find((channel) => channel.unread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all message IDs of unread messages
|
||||
* @returns Array of message IDs which are unread
|
||||
*/
|
||||
get mentions() {
|
||||
return [];
|
||||
const arr = this.channels.map((channel) =>
|
||||
Array.from(channel.mentions?.values() ?? [])
|
||||
);
|
||||
|
||||
return ([] as string[]).concat(...arr);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,24 @@ export class ChannelUnreadCollection extends ClassCollection<
|
||||
ChannelUnread,
|
||||
HydratedChannelUnread
|
||||
> {
|
||||
/**
|
||||
* Load unread information from server
|
||||
*/
|
||||
async sync() {
|
||||
const unreads = await this.client.api.get("/sync/unreads");
|
||||
this.reset();
|
||||
for (const unread of unreads) {
|
||||
this.getOrCreate(unread._id.channel, unread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all unread data
|
||||
*/
|
||||
reset() {
|
||||
this.updateUnderlyingObject({});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create
|
||||
* @param id Id
|
||||
|
||||
@@ -2,6 +2,7 @@ import { SetStoreFunction } from "solid-js/store";
|
||||
|
||||
import { ReactiveMap } from "@solid-primitives/map";
|
||||
|
||||
import { Client } from "..";
|
||||
import { Hydrators } from "../hydration";
|
||||
import { ObjectStorage } from "../storage/ObjectStorage";
|
||||
|
||||
@@ -177,3 +178,15 @@ export abstract class StoreCollection<T, V> extends Collection<T> {
|
||||
return this.#objects.forEach(cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic class collection backed by store
|
||||
*/
|
||||
export class ClassCollection<T, V> extends StoreCollection<T, V> {
|
||||
readonly client: Client;
|
||||
|
||||
constructor(client: Client) {
|
||||
super();
|
||||
this.client = client;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,4 @@
|
||||
import { Client } from "..";
|
||||
|
||||
import { StoreCollection } from "./Collection";
|
||||
|
||||
/**
|
||||
* Generic class collection backed by store
|
||||
*/
|
||||
export class ClassCollection<T, V> extends StoreCollection<T, V> {
|
||||
readonly client: Client;
|
||||
|
||||
constructor(client: Client) {
|
||||
super();
|
||||
this.client = client;
|
||||
}
|
||||
}
|
||||
export * from "./Collection";
|
||||
|
||||
export { ChannelCollection } from "./ChannelCollection";
|
||||
export { ChannelUnreadCollection } from "./ChannelUnreadCollection";
|
||||
|
||||
@@ -233,8 +233,13 @@ export async function handleEvent(
|
||||
client.emojis.getOrCreate(emoji._id, emoji);
|
||||
}
|
||||
|
||||
if (client.options.syncUnreads) {
|
||||
await client.channelUnreads.sync();
|
||||
}
|
||||
|
||||
setReady(true);
|
||||
client.emit("ready");
|
||||
|
||||
break;
|
||||
}
|
||||
case "Message": {
|
||||
|
||||
Reference in New Issue
Block a user