feat: initial permissions implementation
Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
parent
4206bcf304
commit
dc9de08bc5
|
|
@ -0,0 +1,105 @@
|
|||
package chat.revolt.api.internals
|
||||
|
||||
enum class PermissionBit(val value: Long) {
|
||||
// * Generic permissions
|
||||
ManageChannel(1L shl 0),
|
||||
ManageServer(1L shl 1),
|
||||
ManagePermissions(1L shl 2),
|
||||
ManageRole(1L shl 3),
|
||||
ManageCustomisation(1L shl 4),
|
||||
|
||||
// % 1 bit reserved
|
||||
|
||||
// * Member permissions
|
||||
KickMembers(1L shl 6),
|
||||
BanMembers(1L shl 7),
|
||||
TimeoutMembers(1L shl 8),
|
||||
AssignRoles(1L shl 9),
|
||||
ChangeNickname(1L shl 10),
|
||||
ManageNicknames(1L shl 11),
|
||||
ChangeAvatar(1L shl 12),
|
||||
RemoveAvatars(1L shl 13),
|
||||
|
||||
// % 7 bits reserved
|
||||
|
||||
// * Channel permissions
|
||||
ViewChannel(1L shl 20),
|
||||
ReadMessageHistory(1L shl 21),
|
||||
SendMessage(1L shl 22),
|
||||
ManageMessages(1L shl 23),
|
||||
ManageWebhooks(1L shl 24),
|
||||
InviteOthers(1L shl 25),
|
||||
SendEmbeds(1L shl 26),
|
||||
UploadFiles(1L shl 27),
|
||||
Masquerade(1L shl 28),
|
||||
React(1L shl 29),
|
||||
|
||||
// * Voice permissions
|
||||
Connect(1L shl 30),
|
||||
Speak(1L shl 31),
|
||||
Video(1L shl 32),
|
||||
MuteMembers(1L shl 33),
|
||||
DeafenMembers(1L shl 34),
|
||||
MoveMembers(1L shl 35),
|
||||
|
||||
// * Misc. permissions
|
||||
// % Bits 36 to 52: free area
|
||||
// % Bits 53 to 64: do not use
|
||||
|
||||
// * Grant all permissions
|
||||
GrantAllSafe(0x000FFFFFFFFFFFFFL),
|
||||
GrantAll(Long.MAX_VALUE);
|
||||
|
||||
operator fun plus(other: PermissionBit): Long {
|
||||
return this.value or other.value
|
||||
}
|
||||
|
||||
operator fun plus(other: Long): Long {
|
||||
return this.value or other
|
||||
}
|
||||
}
|
||||
|
||||
operator fun Long.plus(other: PermissionBit): Long {
|
||||
return this or other.value
|
||||
}
|
||||
|
||||
fun Long.hasPermission(permission: PermissionBit): Boolean {
|
||||
return this and permission.value == permission.value
|
||||
}
|
||||
|
||||
object BitDefaults {
|
||||
val AllowedInTimeout =
|
||||
PermissionBit.ViewChannel + PermissionBit.ReadMessageHistory
|
||||
|
||||
val ViewOnly =
|
||||
PermissionBit.ViewChannel + PermissionBit.ReadMessageHistory
|
||||
|
||||
val Default =
|
||||
ViewOnly +
|
||||
PermissionBit.SendMessage +
|
||||
PermissionBit.InviteOthers +
|
||||
PermissionBit.SendEmbeds +
|
||||
PermissionBit.UploadFiles +
|
||||
PermissionBit.Connect +
|
||||
PermissionBit.Speak
|
||||
|
||||
val SavedMessages =
|
||||
PermissionBit.GrantAllSafe.value
|
||||
|
||||
val DirectMessages =
|
||||
Default +
|
||||
PermissionBit.ManageChannel +
|
||||
PermissionBit.React
|
||||
|
||||
val Server =
|
||||
Default +
|
||||
PermissionBit.React +
|
||||
PermissionBit.ChangeNickname +
|
||||
PermissionBit.ChangeAvatar
|
||||
|
||||
val Webhook =
|
||||
PermissionBit.SendMessage +
|
||||
PermissionBit.SendEmbeds +
|
||||
PermissionBit.Masquerade +
|
||||
PermissionBit.React
|
||||
}
|
||||
|
|
@ -1,7 +1,14 @@
|
|||
package chat.revolt.api.internals
|
||||
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.schemas.Channel
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
import chat.revolt.api.schemas.Member
|
||||
import chat.revolt.api.schemas.PermissionDescription
|
||||
import chat.revolt.api.schemas.Role
|
||||
import chat.revolt.api.schemas.Server
|
||||
import chat.revolt.api.schemas.User
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
object Roles {
|
||||
// lowest rank = highest role
|
||||
|
|
@ -39,4 +46,70 @@ object Roles {
|
|||
|
||||
return server.roles?.values?.filter(predicate)?.sortedBy { it.rank } ?: emptyList()
|
||||
}
|
||||
|
||||
fun permissionFor(server: Server, member: Member): Long {
|
||||
val user = RevoltAPI.userCache[member.id?.user] ?: return 0L
|
||||
|
||||
if (user.privileged == true) return PermissionBit.GrantAllSafe.value
|
||||
if (server.owner == member.id?.user) return PermissionBit.GrantAllSafe.value
|
||||
|
||||
var calculated = server.defaultPermissions ?: 0L
|
||||
|
||||
member.roles?.forEach { roleId ->
|
||||
val role = server.roles?.get(roleId) ?: return@forEach
|
||||
val permissions = role.permissions ?: PermissionDescription(0, 0)
|
||||
|
||||
calculated = calculated or permissions.a and permissions.d.inv()
|
||||
}
|
||||
|
||||
if (member.timeoutTimestamp()?.let { it > Clock.System.now() } == true) {
|
||||
calculated = calculated and BitDefaults.AllowedInTimeout
|
||||
}
|
||||
|
||||
return calculated
|
||||
}
|
||||
|
||||
// TODO may not be exactly accurate
|
||||
// See https://github.com/revoltchat/revolt.js/blob/2ba023c879b2a53f9a3cc7042e6721c28dd970ba/src/permissions/calculator.ts#L80-L158
|
||||
fun permissionFor(channel: Channel, user: User? = null, member: Member? = null): Long {
|
||||
return when (channel.channelType) {
|
||||
ChannelType.SavedMessages -> BitDefaults.SavedMessages
|
||||
|
||||
ChannelType.DirectMessage -> BitDefaults.DirectMessages
|
||||
ChannelType.Group -> if (channel.owner == user?.id) PermissionBit.GrantAllSafe.value else BitDefaults.DirectMessages
|
||||
|
||||
ChannelType.TextChannel, ChannelType.VoiceChannel -> {
|
||||
val server = RevoltAPI.serverCache[channel.server] ?: return 0L
|
||||
|
||||
if (server.owner == user?.id) return PermissionBit.GrantAllSafe.value
|
||||
|
||||
val chMember = member ?: RevoltAPI.members.getMember(
|
||||
server.id ?: return 0L,
|
||||
user?.id ?: return 0L
|
||||
) ?: return 0L
|
||||
|
||||
var calculated = permissionFor(server, chMember)
|
||||
|
||||
if (channel.defaultPermissions != null) {
|
||||
calculated =
|
||||
calculated or channel.defaultPermissions.a and channel.defaultPermissions.d.inv()
|
||||
}
|
||||
|
||||
if (chMember.roles?.isNotEmpty() == true) {
|
||||
chMember.roles.forEach { roleId ->
|
||||
val override = channel.rolePermissions?.get(roleId) ?: return@forEach
|
||||
calculated = calculated or override.a and override.d.inv()
|
||||
}
|
||||
}
|
||||
|
||||
if (chMember.timeoutTimestamp()?.let { it > Clock.System.now() } == true) {
|
||||
calculated = calculated and BitDefaults.AllowedInTimeout
|
||||
}
|
||||
|
||||
return calculated
|
||||
}
|
||||
|
||||
null -> 0L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package chat.revolt.api.schemas
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -32,7 +33,9 @@ data class Member(
|
|||
|
||||
val avatar: AutumnResource? = null,
|
||||
val roles: List<String>? = null,
|
||||
val nickname: String? = null
|
||||
val nickname: String? = null,
|
||||
|
||||
val timeout: String? = null,
|
||||
) {
|
||||
fun mergeWithPartial(other: Member): Member {
|
||||
return Member(
|
||||
|
|
@ -40,9 +43,14 @@ data class Member(
|
|||
joinedAt = other.joinedAt ?: joinedAt,
|
||||
avatar = other.avatar ?: avatar,
|
||||
roles = other.roles ?: roles,
|
||||
nickname = other.nickname ?: nickname
|
||||
nickname = other.nickname ?: nickname,
|
||||
timeout = other.timeout ?: timeout,
|
||||
)
|
||||
}
|
||||
|
||||
fun timeoutTimestamp(): Instant? {
|
||||
return timeout?.let { Instant.parse(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
@ -63,9 +71,9 @@ data class Channel(
|
|||
val permissions: Long? = null,
|
||||
val server: String? = null,
|
||||
@SerialName("role_permissions")
|
||||
val rolePermissions: Map<String, DefaultPermissions>? = null,
|
||||
val rolePermissions: Map<String, PermissionDescription>? = null,
|
||||
@SerialName("default_permissions")
|
||||
val defaultPermissions: DefaultPermissions? = null,
|
||||
val defaultPermissions: PermissionDescription? = null,
|
||||
val nsfw: Boolean? = null,
|
||||
val type: String? = null, // this is _only_ used for websocket events!
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -59,16 +59,16 @@ data class SystemMessages(
|
|||
@Serializable
|
||||
data class Role(
|
||||
val name: String? = null,
|
||||
val permissions: DefaultPermissions? = null,
|
||||
val permissions: PermissionDescription? = null,
|
||||
val colour: String? = null,
|
||||
val hoist: Boolean? = null,
|
||||
val rank: Double? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DefaultPermissions(
|
||||
val a: Long? = null,
|
||||
val d: Long? = null
|
||||
data class PermissionDescription(
|
||||
val a: Long,
|
||||
val d: Long
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -274,6 +274,10 @@ fun ChannelScreen(
|
|||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(viewModel.activeChannel, RevoltAPI.channelCache, RevoltAPI.serverCache) {
|
||||
viewModel.checkShouldDenyMessageField()
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.weight(1f),
|
||||
contentAlignment = Alignment.BottomEnd
|
||||
|
|
|
|||
|
|
@ -14,8 +14,11 @@ import chat.revolt.R
|
|||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.RevoltJson
|
||||
import chat.revolt.api.internals.ChannelUtils
|
||||
import chat.revolt.api.internals.PermissionBit
|
||||
import chat.revolt.api.internals.Roles
|
||||
import chat.revolt.api.internals.SpecialUsers
|
||||
import chat.revolt.api.internals.ULID
|
||||
import chat.revolt.api.internals.hasPermission
|
||||
import chat.revolt.api.realtime.RealtimeSocketFrames
|
||||
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame
|
||||
import chat.revolt.api.realtime.frames.receivable.ChannelStopTypingFrame
|
||||
|
|
@ -160,8 +163,6 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
activeChannel = RevoltAPI.channelCache[id]
|
||||
}
|
||||
|
||||
checkShouldDenyMessageField()
|
||||
|
||||
if (activeChannel?.lastMessageID != null) {
|
||||
ackNewest()
|
||||
} else {
|
||||
|
|
@ -438,13 +439,19 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
pendingMessageContent = ""
|
||||
}
|
||||
|
||||
private fun checkShouldDenyMessageField() {
|
||||
// TODO Check for send message permission.
|
||||
val hasPermission = true
|
||||
|
||||
suspend fun checkShouldDenyMessageField() {
|
||||
if (activeChannel == null) return
|
||||
|
||||
val partnerId = ChannelUtils.resolveDMPartner(activeChannel!!) ?: return
|
||||
val selfUser = RevoltAPI.userCache[RevoltAPI.selfId] ?: return
|
||||
val selfMember =
|
||||
activeChannel?.server?.let { RevoltAPI.members.getMember(it, selfUser.id!!) }
|
||||
?: fetchMember(activeChannel!!.server!!, selfUser.id!!)
|
||||
|
||||
val hasPermission =
|
||||
Roles.permissionFor(activeChannel!!, selfUser, selfMember)
|
||||
.hasPermission(PermissionBit.SendMessage)
|
||||
|
||||
val partnerId = ChannelUtils.resolveDMPartner(activeChannel!!)
|
||||
|
||||
denyMessageField = when {
|
||||
partnerId == SpecialUsers.PLATFORM_MODERATION_USER -> true
|
||||
|
|
|
|||
Loading…
Reference in New Issue