feat: implement unreads functionality

This commit is contained in:
Paul Makles
2023-04-09 16:30:54 +01:00
parent 3c48a79980
commit 26cbd52289
8 changed files with 120 additions and 26 deletions
+27 -1
View File
@@ -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
View File
@@ -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);
+1 -1
View File
@@ -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
View File
@@ -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
+13
View File
@@ -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 -15
View File
@@ -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";
+5
View File
@@ -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": {