fix: moderator status going off open chat API instead of PublicChatAPI

pull/486/head
jubb 3 years ago
parent 65fbd56b6a
commit a445e0a326

@ -52,11 +52,21 @@ import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.opengroups.OpenGroupAPI;
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.ThemeUtil;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.ViewUtil;
import org.session.libsession.utilities.views.Stub;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.BindableConversationItem;
import org.thoughtcrime.securesms.MediaPreviewActivity;
@ -69,7 +79,6 @@ import org.thoughtcrime.securesms.components.LinkPreviewView;
import org.thoughtcrime.securesms.components.QuoteView;
import org.thoughtcrime.securesms.components.StickerView;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
@ -78,7 +87,6 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.session.libsignal.utilities.logging.Log;
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities;
import org.thoughtcrime.securesms.loki.views.MessageAudioView;
import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
@ -89,22 +97,11 @@ import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
import org.thoughtcrime.securesms.mms.TextSlide;
import org.session.libsession.messaging.threads.recipients.Recipient;
import org.session.libsession.messaging.threads.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.LongClickCopySpan;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
import org.thoughtcrime.securesms.util.SearchUtil;
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.ThemeUtil;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.GroupUtil;
import org.session.libsession.utilities.ViewUtil;
import org.session.libsession.utilities.views.Stub;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -916,7 +913,7 @@ public class ConversationItem extends LinearLayout
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(messageRecord.getThreadId());
if (publicChat != null) {
boolean isModerator = PublicChatAPI.Companion.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer());
boolean isModerator = OpenGroupAPI.isUserModerator(current.getRecipient().getAddress().toString(), publicChat.getChannel(), publicChat.getServer());
visibility = isModerator ? View.VISIBLE : View.GONE;
}

@ -0,0 +1,238 @@
package org.thoughtcrime.securesms.loki.api
import android.content.Context
import android.os.Handler
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.functional.map
import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsession.utilities.IdentityKeyUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import org.session.libsignal.service.api.messages.SignalServiceContent
import org.session.libsignal.service.api.messages.SignalServiceDataMessage
import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.api.push.SignalServiceAddress
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import org.session.libsignal.service.loki.api.opengroups.PublicChat
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI
import org.session.libsignal.service.loki.api.opengroups.PublicChatMessage
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.successBackground
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.jobs.PushDecryptJob
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import java.security.MessageDigest
import java.util.*
class PublicChatPoller(private val context: Context, private val group: PublicChat) {
private val handler by lazy { Handler() }
private var hasStarted = false
private var isPollOngoing = false
public var isCaughtUp = false
// region Convenience
private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)!!
private var displayNameUpdatees = setOf<String>()
private val api: PublicChatAPI
get() = {
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
val lokiUserDatabase = DatabaseFactory.getLokiUserDatabase(context)
val openGroupDatabase = DatabaseFactory.getGroupDatabase(context)
PublicChatAPI(userHexEncodedPublicKey, userPrivateKey, lokiAPIDatabase, lokiUserDatabase, openGroupDatabase)
}()
// endregion
// region Tasks
private val pollForNewMessagesTask = object : Runnable {
override fun run() {
pollForNewMessages()
handler.postDelayed(this, pollForNewMessagesInterval)
}
}
private val pollForDeletedMessagesTask = object : Runnable {
override fun run() {
pollForDeletedMessages()
handler.postDelayed(this, pollForDeletedMessagesInterval)
}
}
private val pollForModeratorsTask = object : Runnable {
override fun run() {
pollForModerators()
handler.postDelayed(this, pollForModeratorsInterval)
}
}
private val pollForDisplayNamesTask = object : Runnable {
override fun run() {
pollForDisplayNames()
handler.postDelayed(this, pollForDisplayNamesInterval)
}
}
// endregion
// region Settings
companion object {
private val pollForNewMessagesInterval: Long = 4 * 1000
private val pollForDeletedMessagesInterval: Long = 60 * 1000
private val pollForModeratorsInterval: Long = 10 * 60 * 1000
private val pollForDisplayNamesInterval: Long = 60 * 1000
}
// endregion
// region Lifecycle
fun startIfNeeded() {
if (hasStarted) return
pollForNewMessagesTask.run()
pollForDeletedMessagesTask.run()
pollForModeratorsTask.run()
pollForDisplayNamesTask.run()
hasStarted = true
}
fun stop() {
handler.removeCallbacks(pollForNewMessagesTask)
handler.removeCallbacks(pollForDeletedMessagesTask)
handler.removeCallbacks(pollForModeratorsTask)
handler.removeCallbacks(pollForDisplayNamesTask)
hasStarted = false
}
// endregion
// region Polling
private fun getDataMessage(message: PublicChatMessage): SignalServiceDataMessage {
val id = group.id.toByteArray()
val serviceGroup = SignalServiceGroup(SignalServiceGroup.Type.UPDATE, id, SignalServiceGroup.GroupType.PUBLIC_CHAT, null, null, null, null)
val quote = if (message.quote != null) {
SignalServiceDataMessage.Quote(message.quote!!.quotedMessageTimestamp, SignalServiceAddress(message.quote!!.quoteePublicKey), message.quote!!.quotedMessageBody, listOf())
} else {
null
}
val attachments = message.attachments.mapNotNull { attachment ->
if (attachment.kind != PublicChatMessage.Attachment.Kind.Attachment) { return@mapNotNull null }
SignalServiceAttachmentPointer(
attachment.serverID,
attachment.contentType,
ByteArray(0),
Optional.of(attachment.size),
Optional.absent(),
attachment.width, attachment.height,
Optional.absent(),
Optional.of(attachment.fileName),
false,
Optional.fromNullable(attachment.caption),
attachment.url)
}
val linkPreview = message.attachments.firstOrNull { it.kind == PublicChatMessage.Attachment.Kind.LinkPreview }
val signalLinkPreviews = mutableListOf<SignalServiceDataMessage.Preview>()
if (linkPreview != null) {
val attachment = SignalServiceAttachmentPointer(
linkPreview.serverID,
linkPreview.contentType,
ByteArray(0),
Optional.of(linkPreview.size),
Optional.absent(),
linkPreview.width, linkPreview.height,
Optional.absent(),
Optional.of(linkPreview.fileName),
false,
Optional.fromNullable(linkPreview.caption),
linkPreview.url)
signalLinkPreviews.add(SignalServiceDataMessage.Preview(linkPreview.linkPreviewURL!!, linkPreview.linkPreviewTitle!!, Optional.of(attachment)))
}
val body = if (message.body == message.timestamp.toString()) "" else message.body // Workaround for the fact that the back-end doesn't accept messages without a body
val syncTarget = if (message.senderPublicKey == userHexEncodedPublicKey) group.id else null
return SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, 0, false, null, quote, null, signalLinkPreviews, null, syncTarget)
}
fun pollForNewMessages(): Promise<Unit, Exception> {
if (isPollOngoing) { return Promise.of(Unit) }
isPollOngoing = true
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB)
// Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below
val promise = api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages ->
Promise.of(messages)
}
promise.successBackground { messages ->
// Process messages in the background
messages.forEach { message ->
// If the sender of the current message is not a slave device, set the display name in the database
val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})"
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName)
val senderHexEncodedPublicKey = message.senderPublicKey
val serviceDataMessage = getDataMessage(message)
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.serverTimestamp, false)
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
} else {
PushDecryptJob(context).handleTextMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
}
// Update profile picture if needed
val senderAsRecipient = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false)
if (message.profilePicture != null && message.profilePicture!!.url.isNotEmpty()) {
val profileKey = message.profilePicture!!.profileKey
val url = message.profilePicture!!.url
if (senderAsRecipient.profileKey == null || !MessageDigest.isEqual(senderAsRecipient.profileKey, profileKey)) {
val database = DatabaseFactory.getRecipientDatabase(context)
database.setProfileKey(senderAsRecipient, profileKey)
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(senderAsRecipient, url))
}
}
}
isCaughtUp = true
isPollOngoing = false
}
promise.fail {
Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.")
isPollOngoing = false
}
return promise.map { Unit }
}
private fun pollForDisplayNames() {
if (displayNameUpdatees.isEmpty()) { return }
val hexEncodedPublicKeys = displayNameUpdatees
displayNameUpdatees = setOf()
api.getDisplayNames(hexEncodedPublicKeys, group.server).successBackground { mapping ->
for (pair in mapping.entries) {
val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})"
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName)
}
}.fail {
displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys)
}
}
private fun pollForDeletedMessages() {
api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs ->
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { lokiMessageDatabase.getMessageID(it) }
val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context)
val mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context)
deletedMessageIDs.forEach {
smsMessageDatabase.deleteMessage(it)
mmsMessageDatabase.delete(it)
}
}.fail {
Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${group.channel} on server: ${group.server}.")
}
}
private fun pollForModerators() {
api.getModerators(group.channel, group.server)
}
// endregion
}

@ -130,7 +130,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS
val context = MessagingConfiguration.shared.context
// Update profile if needed
val newProfile = message.profile
if (newProfile != null && openGroupID.isNullOrEmpty()) {
if (newProfile != null) {
val profileManager = SSKEnvironment.shared.profileManager
val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false)
val displayName = newProfile.displayName!!

@ -83,7 +83,10 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
messages.forEach { message ->
try {
val senderPublicKey = message.senderPublicKey
val senderDisplayName = message.displayName
fun generateDisplayName(rawDisplayName: String): String {
return "$rawDisplayName (...${senderPublicKey.takeLast(8)})"
}
val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName(message.displayName)
val id = openGroup.id.toByteArray()
// Main message
val dataMessageProto = DataMessage.newBuilder()
@ -187,6 +190,7 @@ class OpenGroupPoller(private val openGroup: OpenGroup, private val executorServ
Log.e("Loki", "Exception parsing message", e)
}
}
displayNameUpdates = displayNameUpdates + messages.map { it.senderPublicKey }.toSet() - userHexEncodedPublicKey
isCaughtUp = true
isPollOngoing = false
deferred.resolve(Unit)

Loading…
Cancel
Save