feat: refactor member manager, expand websocket coverage
Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
33fd3c6bd4
commit
98aa10c39e
|
|
@ -5,6 +5,7 @@ import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.mutableStateMapOf
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import chat.revolt.BuildConfig
|
import chat.revolt.BuildConfig
|
||||||
|
import chat.revolt.api.internals.Members
|
||||||
import chat.revolt.api.realtime.DisconnectionState
|
import chat.revolt.api.realtime.DisconnectionState
|
||||||
import chat.revolt.api.realtime.RealtimeSocket
|
import chat.revolt.api.realtime.RealtimeSocket
|
||||||
import chat.revolt.api.routes.user.fetchSelf
|
import chat.revolt.api.routes.user.fetchSelf
|
||||||
|
|
@ -98,6 +99,8 @@ object RevoltAPI {
|
||||||
val emojiCache = mutableStateMapOf<String, Emoji>()
|
val emojiCache = mutableStateMapOf<String, Emoji>()
|
||||||
val messageCache = mutableStateMapOf<String, Message>()
|
val messageCache = mutableStateMapOf<String, Message>()
|
||||||
|
|
||||||
|
val members = Members()
|
||||||
|
|
||||||
val unreads = Unreads()
|
val unreads = Unreads()
|
||||||
|
|
||||||
var selfId: String? = null
|
var selfId: String? = null
|
||||||
|
|
@ -182,6 +185,7 @@ object RevoltAPI {
|
||||||
emojiCache.clear()
|
emojiCache.clear()
|
||||||
messageCache.clear()
|
messageCache.clear()
|
||||||
|
|
||||||
|
members.clear()
|
||||||
unreads.clear()
|
unreads.clear()
|
||||||
|
|
||||||
socketCoroutine?.cancel()
|
socketCoroutine?.cancel()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package chat.revolt.api.internals
|
||||||
|
|
||||||
import chat.revolt.api.schemas.Member
|
import chat.revolt.api.schemas.Member
|
||||||
|
|
||||||
object Members {
|
class Members {
|
||||||
// memberCache (mapping of serverId to userId to member)
|
// memberCache (mapping of serverId to userId to member)
|
||||||
private val memberCache = mutableMapOf<String, MutableMap<String, Member>>()
|
private val memberCache = mutableMapOf<String, MutableMap<String, Member>>()
|
||||||
|
|
||||||
|
|
@ -14,11 +14,19 @@ object Members {
|
||||||
return memberCache[serverId]?.containsKey(userId) ?: false
|
return memberCache[serverId]?.containsKey(userId) ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addMember(serverId: String, member: Member) {
|
fun setMember(serverId: String, member: Member) {
|
||||||
if (!memberCache.containsKey(serverId)) {
|
if (!memberCache.containsKey(serverId)) {
|
||||||
memberCache[serverId] = mutableMapOf()
|
memberCache[serverId] = mutableMapOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
memberCache[serverId]?.set(member.id.user, member)
|
memberCache[serverId]?.set(member.id.user, member)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeMember(serverId: String, userId: String) {
|
||||||
|
memberCache[serverId]?.remove(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
memberCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,9 +17,15 @@ import chat.revolt.api.realtime.frames.receivable.MessageUpdateFrame
|
||||||
import chat.revolt.api.realtime.frames.receivable.PongFrame
|
import chat.revolt.api.realtime.frames.receivable.PongFrame
|
||||||
import chat.revolt.api.realtime.frames.receivable.ReadyFrame
|
import chat.revolt.api.realtime.frames.receivable.ReadyFrame
|
||||||
import chat.revolt.api.realtime.frames.receivable.ServerCreateFrame
|
import chat.revolt.api.realtime.frames.receivable.ServerCreateFrame
|
||||||
|
import chat.revolt.api.realtime.frames.receivable.ServerDeleteFrame
|
||||||
|
import chat.revolt.api.realtime.frames.receivable.ServerMemberJoinFrame
|
||||||
|
import chat.revolt.api.realtime.frames.receivable.ServerMemberLeaveFrame
|
||||||
|
import chat.revolt.api.realtime.frames.receivable.ServerMemberUpdateFrame
|
||||||
|
import chat.revolt.api.realtime.frames.receivable.ServerUpdateFrame
|
||||||
import chat.revolt.api.realtime.frames.receivable.UserUpdateFrame
|
import chat.revolt.api.realtime.frames.receivable.UserUpdateFrame
|
||||||
import chat.revolt.api.realtime.frames.sendable.AuthorizationFrame
|
import chat.revolt.api.realtime.frames.sendable.AuthorizationFrame
|
||||||
import chat.revolt.api.realtime.frames.sendable.PingFrame
|
import chat.revolt.api.realtime.frames.sendable.PingFrame
|
||||||
|
import chat.revolt.api.routes.server.fetchMember
|
||||||
import io.ktor.client.plugins.websocket.ws
|
import io.ktor.client.plugins.websocket.ws
|
||||||
import io.ktor.websocket.CloseReason
|
import io.ktor.websocket.CloseReason
|
||||||
import io.ktor.websocket.Frame
|
import io.ktor.websocket.Frame
|
||||||
|
|
@ -288,6 +294,97 @@ object RealtimeSocket {
|
||||||
RevoltAPI.wsFrameChannel.send(channelStopTypingFrame)
|
RevoltAPI.wsFrameChannel.send(channelStopTypingFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"ServerUpdate" -> {
|
||||||
|
val serverUpdateFrame =
|
||||||
|
RevoltJson.decodeFromString(ServerUpdateFrame.serializer(), rawFrame)
|
||||||
|
Log.d(
|
||||||
|
"RealtimeSocket",
|
||||||
|
"Received server update frame for ${serverUpdateFrame.id}."
|
||||||
|
)
|
||||||
|
|
||||||
|
val existing = RevoltAPI.serverCache[serverUpdateFrame.id]
|
||||||
|
?: return // if we don't have the server no point in updating it
|
||||||
|
|
||||||
|
var updated =
|
||||||
|
existing.mergeWithPartial(serverUpdateFrame.data)
|
||||||
|
|
||||||
|
serverUpdateFrame.clear?.forEach {
|
||||||
|
when (it) {
|
||||||
|
"Icon" -> updated = updated.copy(icon = null)
|
||||||
|
"Banner" -> updated = updated.copy(banner = null)
|
||||||
|
"Description" -> updated = updated.copy(description = null)
|
||||||
|
else -> Log.e("RealtimeSocket", "Unknown server clear field: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RevoltAPI.serverCache[serverUpdateFrame.id] = updated
|
||||||
|
}
|
||||||
|
|
||||||
|
"ServerDelete" -> {
|
||||||
|
val serverDeleteFrame =
|
||||||
|
RevoltJson.decodeFromString(ServerDeleteFrame.serializer(), rawFrame)
|
||||||
|
Log.d(
|
||||||
|
"RealtimeSocket",
|
||||||
|
"Received server delete frame for ${serverDeleteFrame.id}."
|
||||||
|
)
|
||||||
|
|
||||||
|
RevoltAPI.serverCache.remove(serverDeleteFrame.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
"ServerMemberUpdate" -> {
|
||||||
|
val serverMemberUpdateFrame =
|
||||||
|
RevoltJson.decodeFromString(ServerMemberUpdateFrame.serializer(), rawFrame)
|
||||||
|
Log.d(
|
||||||
|
"RealtimeSocket",
|
||||||
|
"Received server member update frame for ${serverMemberUpdateFrame.id.user} in ${serverMemberUpdateFrame.id.server}."
|
||||||
|
)
|
||||||
|
|
||||||
|
val existing = RevoltAPI.members.getMember(
|
||||||
|
serverMemberUpdateFrame.id.server,
|
||||||
|
serverMemberUpdateFrame.id.user
|
||||||
|
)
|
||||||
|
?: return // if we don't have the member no point in updating them
|
||||||
|
|
||||||
|
var updated = existing.mergeWithPartial(serverMemberUpdateFrame.data)
|
||||||
|
|
||||||
|
serverMemberUpdateFrame.clear?.forEach {
|
||||||
|
when (it) {
|
||||||
|
"Avatar" -> updated = updated.copy(avatar = null)
|
||||||
|
"Nickname" -> updated = updated.copy(nickname = null)
|
||||||
|
else -> Log.e("RealtimeSocket", "Unknown server member clear field: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RevoltAPI.members.setMember(serverMemberUpdateFrame.id.server, updated)
|
||||||
|
}
|
||||||
|
|
||||||
|
"ServerMemberJoin" -> {
|
||||||
|
val serverMemberJoinFrame =
|
||||||
|
RevoltJson.decodeFromString(ServerMemberJoinFrame.serializer(), rawFrame)
|
||||||
|
Log.d(
|
||||||
|
"RealtimeSocket",
|
||||||
|
"Received server member join frame for ${serverMemberJoinFrame.user} in ${serverMemberJoinFrame.id}."
|
||||||
|
)
|
||||||
|
|
||||||
|
val member = fetchMember(serverMemberJoinFrame.id, serverMemberJoinFrame.user)
|
||||||
|
|
||||||
|
RevoltAPI.members.setMember(serverMemberJoinFrame.id, member)
|
||||||
|
}
|
||||||
|
|
||||||
|
"ServerMemberLeave" -> {
|
||||||
|
val serverMemberLeaveFrame =
|
||||||
|
RevoltJson.decodeFromString(ServerMemberLeaveFrame.serializer(), rawFrame)
|
||||||
|
Log.d(
|
||||||
|
"RealtimeSocket",
|
||||||
|
"Received server member leave frame for ${serverMemberLeaveFrame.user} in ${serverMemberLeaveFrame.id}."
|
||||||
|
)
|
||||||
|
|
||||||
|
RevoltAPI.members.removeMember(
|
||||||
|
serverMemberLeaveFrame.id,
|
||||||
|
serverMemberLeaveFrame.user
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
"Authenticated" -> {
|
"Authenticated" -> {
|
||||||
/* no-op */
|
/* no-op */
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.RevoltError
|
import chat.revolt.api.RevoltError
|
||||||
import chat.revolt.api.RevoltHttp
|
import chat.revolt.api.RevoltHttp
|
||||||
import chat.revolt.api.RevoltJson
|
import chat.revolt.api.RevoltJson
|
||||||
import chat.revolt.api.internals.Members
|
|
||||||
import chat.revolt.api.schemas.Member
|
import chat.revolt.api.schemas.Member
|
||||||
import chat.revolt.api.schemas.User
|
import chat.revolt.api.schemas.User
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
|
|
@ -54,8 +53,8 @@ suspend fun fetchMembers(
|
||||||
}
|
}
|
||||||
|
|
||||||
membersResponse.members.forEach { member ->
|
membersResponse.members.forEach { member ->
|
||||||
if (!Members.hasMember(serverId, member.id.user)) {
|
if (!RevoltAPI.members.hasMember(serverId, member.id.user)) {
|
||||||
Members.addMember(serverId, member)
|
RevoltAPI.members.setMember(serverId, member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,8 +80,8 @@ suspend fun fetchMember(serverId: String, userId: String, pure: Boolean = false)
|
||||||
val member = RevoltJson.decodeFromString(Member.serializer(), response.bodyAsText())
|
val member = RevoltJson.decodeFromString(Member.serializer(), response.bodyAsText())
|
||||||
|
|
||||||
if (!pure) {
|
if (!pure) {
|
||||||
if (!Members.hasMember(serverId, member.id.user)) {
|
if (!RevoltAPI.members.hasMember(serverId, member.id.user)) {
|
||||||
Members.addMember(serverId, member)
|
RevoltAPI.members.setMember(serverId, member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
package chat.revolt.api.schemas
|
package chat.revolt.api.schemas
|
||||||
|
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.descriptors.*
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.encoding.*
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class MessagesInChannel(
|
data class MessagesInChannel(
|
||||||
|
|
@ -29,7 +33,17 @@ data class Member(
|
||||||
val avatar: AutumnResource? = null,
|
val avatar: AutumnResource? = null,
|
||||||
val roles: List<String>? = null,
|
val roles: List<String>? = null,
|
||||||
val nickname: String? = null
|
val nickname: String? = null
|
||||||
)
|
) {
|
||||||
|
fun mergeWithPartial(other: Member): Member {
|
||||||
|
return Member(
|
||||||
|
id = other.id,
|
||||||
|
joinedAt = other.joinedAt ?: joinedAt,
|
||||||
|
avatar = other.avatar ?: avatar,
|
||||||
|
roles = other.roles ?: roles,
|
||||||
|
nickname = other.nickname ?: nickname
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Channel(
|
data class Channel(
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,26 @@ data class Server(
|
||||||
val flags: Long? = null,
|
val flags: Long? = null,
|
||||||
val analytics: Boolean? = null,
|
val analytics: Boolean? = null,
|
||||||
val discoverable: Boolean? = null,
|
val discoverable: Boolean? = null,
|
||||||
)
|
) {
|
||||||
|
fun mergeWithPartial(other: Server): Server {
|
||||||
|
return Server(
|
||||||
|
id = other.id ?: id,
|
||||||
|
owner = other.owner ?: owner,
|
||||||
|
name = other.name ?: name,
|
||||||
|
description = other.description ?: description,
|
||||||
|
channels = other.channels ?: channels,
|
||||||
|
categories = other.categories ?: categories,
|
||||||
|
systemMessages = other.systemMessages ?: systemMessages,
|
||||||
|
roles = other.roles ?: roles,
|
||||||
|
defaultPermissions = other.defaultPermissions ?: defaultPermissions,
|
||||||
|
icon = other.icon ?: icon,
|
||||||
|
banner = other.banner ?: banner,
|
||||||
|
flags = other.flags ?: flags,
|
||||||
|
analytics = other.analytics ?: analytics,
|
||||||
|
discoverable = other.discoverable ?: discoverable,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Category(
|
data class Category(
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.RevoltJson
|
import chat.revolt.api.RevoltJson
|
||||||
import chat.revolt.api.internals.Members
|
|
||||||
import chat.revolt.api.internals.ULID
|
import chat.revolt.api.internals.ULID
|
||||||
import chat.revolt.api.realtime.RealtimeSocketFrames
|
import chat.revolt.api.realtime.RealtimeSocketFrames
|
||||||
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame
|
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame
|
||||||
|
|
@ -129,8 +128,8 @@ class ChannelScreenViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
it.members?.forEach { member ->
|
it.members?.forEach { member ->
|
||||||
if (!Members.hasMember(member.id.server, member.id.user)) {
|
if (!RevoltAPI.members.hasMember(member.id.server, member.id.user)) {
|
||||||
Members.addMember(member.id.server, member)
|
RevoltAPI.members.setMember(member.id.server, member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.revolt.R
|
import chat.revolt.R
|
||||||
import chat.revolt.api.RevoltAPI
|
import chat.revolt.api.RevoltAPI
|
||||||
import chat.revolt.api.internals.Members
|
|
||||||
import chat.revolt.api.internals.WebCompat
|
import chat.revolt.api.internals.WebCompat
|
||||||
import chat.revolt.api.internals.solidColor
|
import chat.revolt.api.internals.solidColor
|
||||||
import chat.revolt.api.routes.user.fetchUserProfile
|
import chat.revolt.api.routes.user.fetchUserProfile
|
||||||
|
|
@ -44,7 +43,7 @@ fun UserContextSheet(
|
||||||
) {
|
) {
|
||||||
val user = RevoltAPI.userCache[userId]
|
val user = RevoltAPI.userCache[userId]
|
||||||
|
|
||||||
val member = serverId?.let { Members.getMember(it, userId) }
|
val member = serverId?.let { RevoltAPI.members.getMember(it, userId) }
|
||||||
|
|
||||||
val server = RevoltAPI.serverCache[serverId]
|
val server = RevoltAPI.serverCache[serverId]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue