feat: more opengroup in chat manager, poller and API. refactor mentions to libsession

This commit is contained in:
jubb
2021-04-15 17:17:55 +10:00
parent 6f46bbefbe
commit 96e604d06b
14 changed files with 241 additions and 63 deletions

View File

@@ -71,8 +71,8 @@ interface StorageProtocol {
fun setOpenGroupPublicKey(server: String, newValue: String)
// Open Group User Info
fun setOpenGroupDisplayName(publicKey: String, channel: Long, server: String, displayName: String)
fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String?
fun setOpenGroupDisplayName(publicKey: String, room: String, server: String, displayName: String)
fun getOpenGroupDisplayName(publicKey: String, room: String, server: String): String?
// Open Group Metadata
@@ -180,4 +180,7 @@ interface StorageProtocol {
fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String)
fun getOpenGroupProfilePictureURL(group: Long, server: String): String?
fun setOpenGroupDisplayName(publicKey: String, channel: Long, server: String, displayName: String)
fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String?
}

View File

@@ -1,32 +1,153 @@
package org.session.libsession.messaging.opengroups
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind
import okhttp3.HttpUrl
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.service.loki.api.utilities.HTTP
import java.util.*
object OpenGroupAPIV2: DotNetAPI() {
enum class Error {
GENERIC,
PARSING_FAILED,
DECRYPTION_FAILED,
SIGNING_FAILED,
INVALID_URL,
NO_PUBLIC_KEY
}
object OpenGroupAPIV2 {
private val moderators: HashMap<String, HashMap<String, Set<String>>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
const val DEFAULT_SERVER = "https://sessionopengroup.com"
const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b"
fun getMessages(room: String, server: String): Promise<List<OpenGroupV2Message>, Exception> {
sealed class Error : Exception() {
object GENERIC : Error()
object PARSING_FAILED : Error()
object DECRYPTION_FAILED : Error()
object SIGNING_FAILED : Error()
object INVALID_URL : Error()
object NO_PUBLIC_KEY : Error()
}
data class Info(
val id: String,
val name: String,
val imageID: String
)
data class Request(
val verb: HTTP.Verb,
val room: String?,
val server: String,
val endpoint: String,
val queryParameters: Map<String, String>,
val parameters: Any,
val headers: Map<String, String>,
val isAuthRequired: Boolean,
// Always `true` under normal circumstances. You might want to disable
// this when running over Lokinet.
val useOnionRouting: Boolean
)
private fun send(request: Request): Promise<Any, Exception> {
val parsed = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.INVALID_URL)
val urlBuilder = HttpUrl.Builder()
.scheme(parsed.scheme())
.host(parsed.host())
.addPathSegment(request.endpoint)
for ((key, value) in request.queryParameters) {
urlBuilder.addQueryParameter(key, value)
}
fun execute(token: String?): Promise<Map<*, *>, Exception> {
}
return if (request.isAuthRequired) {
getAuthToken(request.room!!, request.server).bind(::execute)
} else {
execute(null)
}
}
fun getAuthToken(room: String, server: String): Promise<String, Exception> {
val storage = MessagingConfiguration.shared.storage
return storage.getAuthToken(room, server)?.let {
Promise.of(it)
} ?: run {
requestNewAuthToken(room, server)
.bind { claimAuthToken(it, room, server) }
.success { authToken ->
storage.setAuthToken(room, server, authToken)
}
}
}
fun requestNewAuthToken(room: String, server: String): Promise<String, Exception> {
val (publicKey, _) = MessagingConfiguration.shared.storage.getUserKeyPair()
?: return Promise.ofFail(Error.GENERIC)
val queryParameters = mutableMapOf("public_key" to publicKey)
}
}
fun claimAuthToken(authToken: String, room: String, server: String): Promise<String, Exception> {
TODO("implement")
}
data class Info(val id: String, val name: String, val imageId: String?)
fun deleteAuthToken(room: String, server: String): Promise<Long, Exception> {
TODO("implement")
}
fun upload(file: ByteArray, room: String, server: String): Promise<Long, Exception> {
TODO("implement")
}
fun download(file: Long, room: String, server: String): Promise<ByteArray, Exception> {
TODO("implement")
}
fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> {
TODO("implement")
}
fun getMessages(room: String, server: String): Promise<List<OpenGroupMessageV2>, Exception> {
TODO("implement")
}
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
TODO("implement")
}
fun getDeletedMessages(room: String, server: String): Promise<List<Long>, Exception> {
TODO("implement")
}
fun getModerators(room: String, server: String): Promise<List<String>, Exception> {
TODO("implement")
}
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
TODO("implement")
}
fun unban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
TODO("implement")
}
fun isUserModerator(publicKey: String, room: String, server: String): Promise<Boolean, Exception> {
TODO("implement")
}
fun getDefaultRoomsIfNeeded() {
TODO("implement")
}
fun getInfo(room: String, server: String): Promise<Info, Exception> {
TODO("implement")
}
fun getAllRooms(server: String): Promise<List<Info>, Exception> {
TODO("implement")
}
fun getMemberCount(room: String, server: String): Promise<Long, Exception> {
TODO("implement")
}
}
fun Error.errorDescription() = when (this) {
Error.GENERIC -> "An error occurred."

View File

@@ -5,7 +5,7 @@ import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.logging.Log
import org.whispersystems.curve25519.Curve25519
data class OpenGroupV2Message(
data class OpenGroupMessageV2(
val serverID: Long?,
val sender: String?,
val sentTimestamp: Long,
@@ -20,7 +20,7 @@ data class OpenGroupV2Message(
private val curve = Curve25519.getInstance(Curve25519.BEST)
}
fun sign(): OpenGroupV2Message? {
fun sign(): OpenGroupMessageV2? {
if (base64EncodedData.isEmpty()) return null
val (publicKey, privateKey) = MessagingConfiguration.shared.storage.getUserKeyPair() ?: return null
@@ -41,11 +41,21 @@ data class OpenGroupV2Message(
serverID?.let { jsonMap["server_id"] = serverID }
sender?.let { jsonMap["public_key"] = sender }
base64EncodedSignature?.let { jsonMap["signature"] = base64EncodedSignature }
return jsonMap
}
fun fromJSON(json: Map<String, Any>): OpenGroupV2Message? {
if (!json.containsKey("data") || !json.containsKey("timestamp")) return null
fun fromJSON(json: Map<String, Any>): OpenGroupMessageV2? {
val base64EncodedData = json["data"] as? String ?: return null
val sentTimestamp = json["timestamp"] as? Long ?: return null
val serverID = json["server_id"] as? Long
val sender = json["public_key"] as? String
val base64EncodedSignature = json["signature"] as? String
return OpenGroupMessageV2(serverID = serverID,
sender = sender,
sentTimestamp = sentTimestamp,
base64EncodedData = base64EncodedData,
base64EncodedSignature = base64EncodedSignature
)
}

View File

@@ -0,0 +1,3 @@
package org.session.libsession.utilities.mentions
data class Mention(val publicKey: String, val displayName: String)

View File

@@ -0,0 +1,62 @@
package org.session.libsession.utilities.mentions
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol
class MentionsManager(private val userPublicKey: String, private val threadDatabase: LokiThreadDatabaseProtocol,
private val userDatabase: LokiUserDatabaseProtocol) {
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
companion object {
lateinit var shared: MentionsManager
fun configureIfNeeded(userPublicKey: String, threadDatabase: LokiThreadDatabaseProtocol, userDatabase: LokiUserDatabaseProtocol) {
if (::shared.isInitialized) { return; }
shared = MentionsManager(userPublicKey, threadDatabase, userDatabase)
}
}
fun cache(publicKey: String, threadID: Long) {
val cache = userPublicKeyCache[threadID]
if (cache != null) {
userPublicKeyCache[threadID] = cache.plus(publicKey)
} else {
userPublicKeyCache[threadID] = setOf( publicKey )
}
}
fun getMentionCandidates(query: String, threadID: Long): List<Mention> {
// Prepare
val cache = userPublicKeyCache[threadID] ?: return listOf()
// Gather candidates
val storage = MessagingConfiguration.shared.storage
val publicChat = threadDatabase.getPublicChat(threadID)
val openGroupV2 = storage.getV2OpenGroup(threadID.toString())
var candidates: List<Mention> = cache.mapNotNull { publicKey ->
val displayName: String?
if (publicChat != null) {
displayName = userDatabase.getServerDisplayName(publicChat.id, publicKey)
} else if (openGroupV2 != null) {
displayName = userDatabase.getServerDisplayName(openGroupV2.id, publicKey)
} else {
displayName = userDatabase.getDisplayName(publicKey)
}
if (displayName == null) { return@mapNotNull null }
if (displayName.startsWith("Anonymous")) { return@mapNotNull null }
Mention(publicKey, displayName)
}
candidates = candidates.filter { it.publicKey != userPublicKey }
// Sort alphabetically first
candidates.sortedBy { it.displayName }
if (query.length >= 2) {
// Filter out any non-matching candidates
candidates = candidates.filter { it.displayName.toLowerCase().contains(query.toLowerCase()) }
// Sort based on where in the candidate the query occurs
candidates.sortedBy { it.displayName.toLowerCase().indexOf(query.toLowerCase()) }
}
// Return
return candidates
}
}