Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
commonMain.net.folivo.trixnity.client.user.UserService.kt Maven / Gradle / Ivy
package net.folivo.trixnity.client.user
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.flow.*
import net.folivo.trixnity.client.store.*
import net.folivo.trixnity.core.UserInfo
import net.folivo.trixnity.core.model.EventId
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.EventContent
import net.folivo.trixnity.core.model.events.GlobalAccountDataEventContent
import net.folivo.trixnity.core.model.events.MessageEventContent
import net.folivo.trixnity.core.model.events.RedactedEventContent
import net.folivo.trixnity.core.model.events.m.PresenceEventContent
import net.folivo.trixnity.core.model.events.m.room.*
import net.folivo.trixnity.core.model.events.m.room.PowerLevelsEventContent.Companion.BAN_DEFAULT
import net.folivo.trixnity.core.model.events.m.room.PowerLevelsEventContent.Companion.EVENTS_DEFAULT
import net.folivo.trixnity.core.model.events.m.room.PowerLevelsEventContent.Companion.INVITE_DEFAULT
import net.folivo.trixnity.core.model.events.m.room.PowerLevelsEventContent.Companion.KICK_DEFAULT
import net.folivo.trixnity.core.model.events.m.room.PowerLevelsEventContent.Companion.REDACT_DEFAULT
import net.folivo.trixnity.core.model.events.m.room.PowerLevelsEventContent.Companion.STATE_DEFAULT
import net.folivo.trixnity.core.model.events.m.room.PowerLevelsEventContent.Companion.USERS_DEFAULT
import net.folivo.trixnity.core.serialization.events.EventContentSerializerMappings
import kotlin.reflect.KClass
private val log = KotlinLogging.logger {}
interface UserService {
val userPresence: StateFlow>
suspend fun loadMembers(roomId: RoomId, wait: Boolean = true)
fun getAll(roomId: RoomId): Flow>>
fun getById(roomId: RoomId, userId: UserId): Flow
fun getAllReceipts(roomId: RoomId): Flow>>
fun getReceiptsById(roomId: RoomId, userId: UserId): Flow
fun getPowerLevel(roomId: RoomId, userId: UserId): Flow
fun getPowerLevel(
userId: UserId,
roomCreator: UserId,
powerLevelsEventContent: PowerLevelsEventContent?,
): Long
fun canKickUser(roomId: RoomId, userId: UserId): Flow
fun canBanUser(roomId: RoomId, userId: UserId): Flow
fun canUnbanUser(roomId: RoomId, userId: UserId): Flow
fun canInviteUser(roomId: RoomId, userId: UserId): Flow
fun canInvite(roomId: RoomId): Flow
fun canRedactEvent(roomId: RoomId, eventId: EventId): Flow
fun canSetPowerLevelToMax(roomId: RoomId, userId: UserId): Flow
fun canSendEvent(roomId: RoomId, eventClass: KClass): Flow
fun getAccountData(
eventContentClass: KClass,
key: String = "",
): Flow
}
class UserServiceImpl(
private val roomStore: RoomStore,
private val roomUserStore: RoomUserStore,
private val roomStateStore: RoomStateStore,
private val roomTimelineStore: RoomTimelineStore,
private val globalAccountDataStore: GlobalAccountDataStore,
private val loadMembersService: LoadMembersService,
presenceEventHandler: PresenceEventHandler,
userInfo: UserInfo,
private val mappings: EventContentSerializerMappings,
) : UserService {
override val userPresence = presenceEventHandler.userPresence
private val ownUserId = userInfo.userId
override suspend fun loadMembers(roomId: RoomId, wait: Boolean) = loadMembersService(roomId, wait)
override fun getAll(roomId: RoomId): Flow>> {
return roomUserStore.getAll(roomId)
}
override fun getById(roomId: RoomId, userId: UserId): Flow {
return roomUserStore.get(userId, roomId)
}
override fun getAllReceipts(roomId: RoomId): Flow>> {
return roomUserStore.getAllReceipts(roomId)
}
override fun getReceiptsById(roomId: RoomId, userId: UserId): Flow {
return roomUserStore.getReceipts(userId, roomId)
}
override fun getPowerLevel(
roomId: RoomId,
userId: UserId
): Flow =
combine(
roomStateStore.getContentByStateKey(roomId),
roomStateStore.getByStateKey(roomId).filterNotNull()
) { powerLevels, createEvent ->
getPowerLevel(userId, createEvent.sender, powerLevels)
}.distinctUntilChanged()
override fun getPowerLevel(
userId: UserId,
roomCreator: UserId,
powerLevelsEventContent: PowerLevelsEventContent?
): Long {
return when (powerLevelsEventContent) {
null -> if (roomCreator == userId) 100 else 0
else -> powerLevelsEventContent.users[userId] ?: powerLevelsEventContent.usersDefault
}
}
override fun canKickUser(roomId: RoomId, userId: UserId): Flow =
combine(
roomStore.get(roomId).map { it?.membership }.filterNotNull(),
roomStateStore.getContentByStateKey(roomId),
roomStateStore.getByStateKey(roomId).filterNotNull(),
) { membership, powerLevels, createEvent ->
if (membership == Membership.JOIN) {
val ownPowerLevel = getPowerLevel(ownUserId, createEvent.sender, powerLevels)
val otherPowerLevel = getPowerLevel(userId, createEvent.sender, powerLevels)
val kickLevel = powerLevels?.kick ?: KICK_DEFAULT
ownPowerLevel >= kickLevel && ownPowerLevel > otherPowerLevel
} else false
}.distinctUntilChanged()
override fun canBanUser(roomId: RoomId, userId: UserId): Flow =
combine(
roomStore.get(roomId).map { it?.membership }.filterNotNull(),
roomStateStore.getContentByStateKey(roomId),
roomStateStore.getByStateKey(roomId).filterNotNull(),
) { membership, powerLevels, createEvent ->
if (membership == Membership.JOIN) {
val ownPowerLevel = getPowerLevel(ownUserId, createEvent.sender, powerLevels)
val otherPowerLevel = getPowerLevel(userId, createEvent.sender, powerLevels)
val banLevel = powerLevels?.ban ?: BAN_DEFAULT
ownPowerLevel >= banLevel && ownPowerLevel > otherPowerLevel
} else false
}.distinctUntilChanged()
override fun canUnbanUser(roomId: RoomId, userId: UserId): Flow =
combine(
roomStore.get(roomId).map { it?.membership }.filterNotNull(),
roomStateStore.getContentByStateKey(roomId),
roomStateStore.getByStateKey(roomId).filterNotNull(),
) { membership, powerLevels, createEvent ->
if (membership == Membership.JOIN) {
val ownPowerLevel = getPowerLevel(ownUserId, createEvent.sender, powerLevels)
val otherPowerLevel = getPowerLevel(userId, createEvent.sender, powerLevels)
val banLevel = powerLevels?.ban ?: BAN_DEFAULT
val kickLevel = powerLevels?.kick ?: KICK_DEFAULT
ownPowerLevel >= banLevel && ownPowerLevel >= kickLevel && ownPowerLevel > otherPowerLevel
} else false
}.distinctUntilChanged()
override fun canInviteUser(roomId: RoomId, userId: UserId): Flow =
combine(
roomStore.get(roomId).map { it?.membership }.filterNotNull(),
getById(roomId, userId).map { it?.membership },
roomStateStore.getContentByStateKey(roomId),
roomStateStore.getByStateKey(roomId).filterNotNull(),
) { membership, otherMembership, powerLevels, createEvent ->
if (membership == Membership.JOIN) {
val ownPowerLevel = getPowerLevel(ownUserId, createEvent.sender, powerLevels)
val inviteLevel = powerLevels?.invite ?: INVITE_DEFAULT
ownPowerLevel >= inviteLevel && otherMembership != Membership.BAN
} else false
}.distinctUntilChanged()
override fun canInvite(roomId: RoomId): Flow =
combine(
roomStore.get(roomId).map { it?.membership }.filterNotNull(),
roomStateStore.getContentByStateKey(roomId),
roomStateStore.getByStateKey(roomId).filterNotNull(),
) { membership, powerLevels, createEvent ->
if (membership == Membership.JOIN) {
val ownPowerLevel = getPowerLevel(ownUserId, createEvent.sender, powerLevels)
val inviteLevel = powerLevels?.invite ?: INVITE_DEFAULT
ownPowerLevel >= inviteLevel
} else false
}.distinctUntilChanged()
override fun canRedactEvent(
roomId: RoomId,
eventId: EventId,
): Flow =
combine(
roomStore.get(roomId).map { it?.membership }.filterNotNull(),
roomStateStore.getContentByStateKey(roomId),
roomTimelineStore.get(eventId, roomId).filterNotNull(),
) { membership, powerLevels, timelineEvent ->
if (membership == Membership.JOIN) {
val ownPowerLevel = powerLevels?.users?.get(ownUserId) ?: powerLevels?.usersDefault ?: USERS_DEFAULT
val sendRedactionEventPowerLevel =
powerLevels?.events?.get() ?: powerLevels?.eventsDefault ?: EVENTS_DEFAULT
val redactPowerLevelNeeded = powerLevels?.redact ?: REDACT_DEFAULT
val isOwnMessage by lazy { timelineEvent.event.sender == ownUserId }
val allowRedactOwnMessages by lazy { ownPowerLevel >= sendRedactionEventPowerLevel }
val allowRedactOtherMessages by lazy { ownPowerLevel >= redactPowerLevelNeeded }
val content = timelineEvent.content?.getOrNull()
content is MessageEventContent && content !is RedactedEventContent &&
(isOwnMessage && allowRedactOwnMessages || allowRedactOtherMessages)
} else false
}.distinctUntilChanged()
override fun canSendEvent(roomId: RoomId, eventClass: KClass): Flow =
combine(
roomStore.get(roomId).map { it?.membership }.filterNotNull(),
roomStateStore.getContentByStateKey(roomId),
roomStateStore.getByStateKey(roomId).filterNotNull(),
) { membership, powerLevels, createEvent ->
if (membership == Membership.JOIN) {
val ownPowerLevel = getPowerLevel(ownUserId, createEvent.sender, powerLevels)
val sendEventPowerLevel = powerLevels?.events?.get(eventClass)
?: when {
mappings.state.any { it.kClass == eventClass } -> powerLevels?.stateDefault ?: STATE_DEFAULT
mappings.message.any { it.kClass == eventClass } -> powerLevels?.eventsDefault ?: EVENTS_DEFAULT
else -> throw IllegalArgumentException("eventClass $eventClass does not match any event in mappings")
}
ownPowerLevel >= sendEventPowerLevel
} else false
}.distinctUntilChanged()
override fun canSetPowerLevelToMax(
roomId: RoomId,
userId: UserId
): Flow =
combine(
roomStore.get(roomId).map { it?.membership }.filterNotNull(),
roomStateStore.getContentByStateKey(roomId),
roomStateStore.getByStateKey(roomId).filterNotNull(),
) { membership, powerLevels, createEvent ->
if (membership == Membership.JOIN) {
val ownPowerLevel = getPowerLevel(ownUserId, createEvent.sender, powerLevels)
val otherPowerLevel = getPowerLevel(userId, createEvent.sender, powerLevels)
when {
(powerLevels?.events?.get() ?: powerLevels?.stateDefault
?: STATE_DEFAULT) > ownPowerLevel -> null
otherPowerLevel >= ownPowerLevel && userId != ownUserId -> null
else -> ownPowerLevel
}
} else null
}.distinctUntilChanged()
override fun getAccountData(
eventContentClass: KClass,
key: String,
): Flow {
return globalAccountDataStore.get(eventContentClass, key)
.map { it?.content }
}
}