feat: refactor member manager, expand websocket coverage

Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
Infi 2023-07-04 16:48:29 +02:00
parent 33fd3c6bd4
commit 98aa10c39e
8 changed files with 158 additions and 19 deletions

View File

@ -5,6 +5,7 @@ import android.os.Looper
import android.util.Log
import androidx.compose.runtime.mutableStateMapOf
import chat.revolt.BuildConfig
import chat.revolt.api.internals.Members
import chat.revolt.api.realtime.DisconnectionState
import chat.revolt.api.realtime.RealtimeSocket
import chat.revolt.api.routes.user.fetchSelf
@ -98,6 +99,8 @@ object RevoltAPI {
val emojiCache = mutableStateMapOf<String, Emoji>()
val messageCache = mutableStateMapOf<String, Message>()
val members = Members()
val unreads = Unreads()
var selfId: String? = null
@ -182,6 +185,7 @@ object RevoltAPI {
emojiCache.clear()
messageCache.clear()
members.clear()
unreads.clear()
socketCoroutine?.cancel()

View File

@ -2,7 +2,7 @@ package chat.revolt.api.internals
import chat.revolt.api.schemas.Member
object Members {
class Members {
// memberCache (mapping of serverId to userId to member)
private val memberCache = mutableMapOf<String, MutableMap<String, Member>>()
@ -14,11 +14,19 @@ object Members {
return memberCache[serverId]?.containsKey(userId) ?: false
}
fun addMember(serverId: String, member: Member) {
fun setMember(serverId: String, member: Member) {
if (!memberCache.containsKey(serverId)) {
memberCache[serverId] = mutableMapOf()
}
memberCache[serverId]?.set(member.id.user, member)
}
fun removeMember(serverId: String, userId: String) {
memberCache[serverId]?.remove(userId)
}
fun clear() {
memberCache.clear()
}
}

View File

@ -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.ReadyFrame
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.sendable.AuthorizationFrame
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.websocket.CloseReason
import io.ktor.websocket.Frame
@ -288,6 +294,97 @@ object RealtimeSocket {
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" -> {
/* no-op */
}

View File

@ -4,7 +4,6 @@ import chat.revolt.api.RevoltAPI
import chat.revolt.api.RevoltError
import chat.revolt.api.RevoltHttp
import chat.revolt.api.RevoltJson
import chat.revolt.api.internals.Members
import chat.revolt.api.schemas.Member
import chat.revolt.api.schemas.User
import io.ktor.client.request.get
@ -54,8 +53,8 @@ suspend fun fetchMembers(
}
membersResponse.members.forEach { member ->
if (!Members.hasMember(serverId, member.id.user)) {
Members.addMember(serverId, member)
if (!RevoltAPI.members.hasMember(serverId, member.id.user)) {
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())
if (!pure) {
if (!Members.hasMember(serverId, member.id.user)) {
Members.addMember(serverId, member)
if (!RevoltAPI.members.hasMember(serverId, member.id.user)) {
RevoltAPI.members.setMember(serverId, member)
}
}

View File

@ -1,9 +1,13 @@
package chat.revolt.api.schemas
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
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
data class MessagesInChannel(
@ -29,7 +33,17 @@ data class Member(
val avatar: AutumnResource? = null,
val roles: List<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
data class Channel(

View File

@ -20,7 +20,26 @@ data class Server(
val flags: Long? = null,
val analytics: 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
data class Category(

View File

@ -11,7 +11,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import chat.revolt.api.RevoltAPI
import chat.revolt.api.RevoltJson
import chat.revolt.api.internals.Members
import chat.revolt.api.internals.ULID
import chat.revolt.api.realtime.RealtimeSocketFrames
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame
@ -129,8 +128,8 @@ class ChannelScreenViewModel : ViewModel() {
}
it.members?.forEach { member ->
if (!Members.hasMember(member.id.server, member.id.user)) {
Members.addMember(member.id.server, member)
if (!RevoltAPI.members.hasMember(member.id.server, member.id.user)) {
RevoltAPI.members.setMember(member.id.server, member)
}
}
}

View File

@ -26,7 +26,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import chat.revolt.R
import chat.revolt.api.RevoltAPI
import chat.revolt.api.internals.Members
import chat.revolt.api.internals.WebCompat
import chat.revolt.api.internals.solidColor
import chat.revolt.api.routes.user.fetchUserProfile
@ -44,7 +43,7 @@ fun UserContextSheet(
) {
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]