mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 16:48:26 +00:00
open group message & open group poller
This commit is contained in:
parent
73c4e44711
commit
2a4046d12c
@ -3,6 +3,8 @@ package org.session.libsession.messaging
|
|||||||
import org.session.libsession.messaging.jobs.AttachmentUploadJob
|
import org.session.libsession.messaging.jobs.AttachmentUploadJob
|
||||||
import org.session.libsession.messaging.jobs.Job
|
import org.session.libsession.messaging.jobs.Job
|
||||||
import org.session.libsession.messaging.jobs.MessageSendJob
|
import org.session.libsession.messaging.jobs.MessageSendJob
|
||||||
|
import org.session.libsession.messaging.messages.Message
|
||||||
|
import org.session.libsession.messaging.messages.visible.Attachment
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||||
import org.session.libsession.messaging.threads.Address
|
import org.session.libsession.messaging.threads.Address
|
||||||
import org.session.libsession.messaging.threads.GroupRecord
|
import org.session.libsession.messaging.threads.GroupRecord
|
||||||
@ -10,6 +12,7 @@ import org.session.libsession.messaging.threads.GroupRecord
|
|||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey
|
import org.session.libsignal.libsignal.ecc.ECPrivateKey
|
||||||
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
|
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
interface StorageProtocol {
|
interface StorageProtocol {
|
||||||
|
|
||||||
@ -53,6 +56,10 @@ interface StorageProtocol {
|
|||||||
fun getOpenGroupPublicKey(server: String): String?
|
fun getOpenGroupPublicKey(server: String): String?
|
||||||
fun setOpenGroupPublicKey(server: String, newValue: String)
|
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?
|
||||||
|
|
||||||
// Last Message Server ID
|
// Last Message Server ID
|
||||||
fun getLastMessageServerID(group: Long, server: String): Long?
|
fun getLastMessageServerID(group: Long, server: String): Long?
|
||||||
fun setLastMessageServerID(group: Long, server: String, newValue: Long)
|
fun setLastMessageServerID(group: Long, server: String, newValue: Long)
|
||||||
@ -73,6 +80,11 @@ interface StorageProtocol {
|
|||||||
// Message Handling
|
// Message Handling
|
||||||
fun getReceivedMessageTimestamps(): Set<Long>
|
fun getReceivedMessageTimestamps(): Set<Long>
|
||||||
fun addReceivedMessageTimestamp(timestamp: Long)
|
fun addReceivedMessageTimestamp(timestamp: Long)
|
||||||
|
// Returns the IDs of the saved attachments.
|
||||||
|
fun persist(attachments: List<Attachment>): List<String>
|
||||||
|
fun insertMessageOutbox(message: Message)
|
||||||
|
fun insertMessageInbox(message: Message)
|
||||||
|
fun setErrorMessage(message: Message, error: Exception)
|
||||||
|
|
||||||
// Closed Groups
|
// Closed Groups
|
||||||
fun getGroup(groupID: String): GroupRecord?
|
fun getGroup(groupID: String): GroupRecord?
|
||||||
@ -81,14 +93,15 @@ interface StorageProtocol {
|
|||||||
fun removeMember(groupID: String, member: Address)
|
fun removeMember(groupID: String, member: Address)
|
||||||
fun updateMembers(groupID: String, members: List<Address>)
|
fun updateMembers(groupID: String, members: List<Address>)
|
||||||
|
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
fun setProfileSharing(address: Address, value: Boolean)
|
fun setProfileSharing(address: Address, value: Boolean)
|
||||||
|
|
||||||
// Thread
|
// Thread
|
||||||
fun getOrCreateThreadIdFor(address: Address): String
|
fun getOrCreateThreadIdFor(address: Address): String
|
||||||
|
fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): String?
|
||||||
fun getThreadIdFor(address: Address): String?
|
fun getThreadIdFor(address: Address): String?
|
||||||
|
|
||||||
|
// Session Request
|
||||||
fun getSessionRequestSentTimestamp(publicKey: String): Long?
|
fun getSessionRequestSentTimestamp(publicKey: String): Long?
|
||||||
fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long)
|
fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long)
|
||||||
fun getSessionRequestProcessedTimestamp(publicKey: String): Long?
|
fun getSessionRequestProcessedTimestamp(publicKey: String): Long?
|
||||||
|
@ -15,7 +15,7 @@ public data class OpenGroupMessage(
|
|||||||
public val timestamp: Long,
|
public val timestamp: Long,
|
||||||
public val type: String,
|
public val type: String,
|
||||||
public val quote: Quote?,
|
public val quote: Quote?,
|
||||||
public val attachments: List<Attachment>,
|
public val attachments: MutableList<Attachment>,
|
||||||
public val profilePicture: ProfilePicture?,
|
public val profilePicture: ProfilePicture?,
|
||||||
public val signature: Signature?,
|
public val signature: Signature?,
|
||||||
public val serverTimestamp: Long,
|
public val serverTimestamp: Long,
|
||||||
@ -26,14 +26,18 @@ public data class OpenGroupMessage(
|
|||||||
fun from(message: VisibleMessage, server: String): OpenGroupMessage? {
|
fun from(message: VisibleMessage, server: String): OpenGroupMessage? {
|
||||||
val storage = MessagingConfiguration.shared.storage
|
val storage = MessagingConfiguration.shared.storage
|
||||||
val userPublicKey = storage.getUserPublicKey() ?: return null
|
val userPublicKey = storage.getUserPublicKey() ?: return null
|
||||||
|
var attachmentIDs = message.attachmentIDs
|
||||||
// Validation
|
// Validation
|
||||||
if (!message.isValid()) { return null } // Should be valid at this point
|
if (!message.isValid()) { return null } // Should be valid at this point
|
||||||
// Quote
|
// Quote
|
||||||
val quote: Quote? = {
|
val quote: Quote? = {
|
||||||
val quote = message.quote
|
val quote = message.quote
|
||||||
if (quote != null && quote.isValid()) {
|
if (quote != null && quote.isValid()) {
|
||||||
val quotedMessageServerID = storage.getQuoteServerID(quote.id, quote.publicKey)
|
val quotedMessageBody = quote.text ?: quote.timestamp!!.toString()
|
||||||
Quote(quote.timestamp!!, quote.publicKey!!, quote.text!!, quotedMessageServerID)
|
val quotedAttachmentID = quote.attachmentID
|
||||||
|
if (quotedAttachmentID != null) { attachmentIDs.remove(quotedAttachmentID) }
|
||||||
|
// FIXME: For some reason the server always returns a 500 if quotedMessageServerID is set...
|
||||||
|
Quote(quote.timestamp!!, quote.publicKey!!, quotedMessageBody, null)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -46,41 +50,43 @@ public data class OpenGroupMessage(
|
|||||||
val linkPreview = message.linkPreview
|
val linkPreview = message.linkPreview
|
||||||
linkPreview?.let {
|
linkPreview?.let {
|
||||||
if (!linkPreview.isValid()) { return@let }
|
if (!linkPreview.isValid()) { return@let }
|
||||||
val attachment = linkPreview.getImage() ?: return@let
|
val attachmentID = linkPreview.attachmentID ?: return@let
|
||||||
|
val attachment = MessagingConfiguration.shared.messageDataProvider.getAttachmentPointer(attachmentID) ?: return@let
|
||||||
val openGroupLinkPreview = Attachment(
|
val openGroupLinkPreview = Attachment(
|
||||||
Attachment.Kind.LinkPreview,
|
Attachment.Kind.LinkPreview,
|
||||||
server,
|
server,
|
||||||
attachment.getId(),
|
attachment.id,
|
||||||
attachment.getContentType(),
|
attachment.contentType,
|
||||||
attachment.getSize(),
|
attachment.size.get(),
|
||||||
attachment.getFileName(),
|
attachment.fileName.get(),
|
||||||
attachment.getFlags(),
|
0,
|
||||||
attachment.getWidth(),
|
attachment.width,
|
||||||
attachment.getHeight(),
|
attachment.height,
|
||||||
attachment.getCaption(),
|
attachment.caption.get(),
|
||||||
attachment.getUrl(),
|
attachment.url,
|
||||||
linkPreview.url,
|
linkPreview.url,
|
||||||
linkPreview.title)
|
linkPreview.title)
|
||||||
result.attachments.add(openGroupLinkPreview)
|
result.attachments.add(openGroupLinkPreview)
|
||||||
}
|
}
|
||||||
// Attachments
|
// Attachments
|
||||||
val attachments = message.getAttachemnts().forEach {
|
val attachments = message.attachmentIDs.mapNotNull {
|
||||||
val attachement = Attachment(
|
val attachment = MessagingConfiguration.shared.messageDataProvider.getAttachmentPointer(it) ?: return@mapNotNull null
|
||||||
|
return@mapNotNull Attachment(
|
||||||
Attachment.Kind.Attachment,
|
Attachment.Kind.Attachment,
|
||||||
server,
|
server,
|
||||||
it.getId(),
|
attachment.id,
|
||||||
it.getContentType(),
|
attachment.contentType,
|
||||||
it.getSize(),
|
attachment.size.get(),
|
||||||
it.getFileName(),
|
attachment.fileName.get(),
|
||||||
it.getFlags(),
|
0,
|
||||||
it.getWidth(),
|
attachment.width,
|
||||||
it.getHeight(),
|
attachment.height,
|
||||||
it.getCaption(),
|
attachment.caption.get(),
|
||||||
it.getUrl(),
|
attachment.url,
|
||||||
linkPreview.getUrl(),
|
null,
|
||||||
linkPreview.getTitle())
|
null)
|
||||||
result.attachments.add(attachement)
|
|
||||||
}
|
}
|
||||||
|
result.attachments.addAll(attachments)
|
||||||
// Return
|
// Return
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -145,7 +151,7 @@ public data class OpenGroupMessage(
|
|||||||
|
|
||||||
// region Initialization
|
// region Initialization
|
||||||
constructor(hexEncodedPublicKey: String, displayName: String, body: String, timestamp: Long, type: String, quote: Quote?, attachments: List<Attachment>)
|
constructor(hexEncodedPublicKey: String, displayName: String, body: String, timestamp: Long, type: String, quote: Quote?, attachments: List<Attachment>)
|
||||||
: this(null, hexEncodedPublicKey, displayName, body, timestamp, type, quote, attachments, null, null, 0)
|
: this(null, hexEncodedPublicKey, displayName, body, timestamp, type, quote, attachments as MutableList<Attachment>, null, null, 0)
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Crypto
|
// region Crypto
|
||||||
|
@ -1,56 +1,33 @@
|
|||||||
package org.session.libsession.messaging.sending_receiving.pollers
|
package org.session.libsession.messaging.sending_receiving.pollers
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import org.thoughtcrime.securesms.logging.Log
|
import com.google.protobuf.ByteString
|
||||||
import androidx.annotation.WorkerThread
|
|
||||||
import nl.komponents.kovenant.Promise
|
|
||||||
import nl.komponents.kovenant.functional.bind
|
|
||||||
import nl.komponents.kovenant.functional.map
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
|
||||||
import org.thoughtcrime.securesms.database.Address
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob
|
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
|
|
||||||
import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.successBackground
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
|
||||||
import org.thoughtcrime.securesms.util.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.messages.multidevice.SentTranscriptMessage
|
|
||||||
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.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
|
|
||||||
class OpenGroupPoller(private val context: Context, private val group: PublicChat) {
|
import nl.komponents.kovenant.Promise
|
||||||
|
import nl.komponents.kovenant.deferred
|
||||||
|
import org.session.libsession.messaging.MessagingConfiguration
|
||||||
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
|
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroupAPI
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroupMessage
|
||||||
|
import org.session.libsession.utilities.GroupUtil
|
||||||
|
import org.session.libsession.utilities.successBackground
|
||||||
|
|
||||||
|
import org.session.libsignal.libsignal.logging.Log
|
||||||
|
import org.session.libsignal.service.internal.push.SignalServiceProtos.*
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class OpenGroupPoller(private val openGroup: OpenGroup) {
|
||||||
private val handler by lazy { Handler() }
|
private val handler by lazy { Handler() }
|
||||||
private var hasStarted = false
|
private var hasStarted = false
|
||||||
private var isPollOngoing = false
|
private var isPollOngoing = false
|
||||||
public var isCaughtUp = false
|
public var isCaughtUp = false
|
||||||
|
|
||||||
// region Convenience
|
// region Convenience
|
||||||
private val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
private val userHexEncodedPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: ""
|
||||||
private var displayNameUpdatees = setOf<String>()
|
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
|
// endregion
|
||||||
|
|
||||||
// region Tasks
|
// region Tasks
|
||||||
@ -116,169 +93,145 @@ class OpenGroupPoller(private val context: Context, private val group: PublicCha
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Polling
|
// region Polling
|
||||||
private fun getDataMessage(message: PublicChatMessage): SignalServiceDataMessage {
|
fun pollForNewMessages(): Promise<Unit, Exception> {
|
||||||
val id = group.id.toByteArray()
|
return pollForNewMessages(false)
|
||||||
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
|
|
||||||
return SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, false, 0, false, null, false, quote, null, signalLinkPreviews, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pollForNewMessages(): Promise<Unit, Exception> {
|
fun pollForNewMessages(isBackgroundPoll: Boolean): Promise<Unit, Exception> {
|
||||||
fun processIncomingMessage(message: PublicChatMessage) {
|
|
||||||
// If the sender of the current message is not a slave device, set the display name in the database
|
|
||||||
val masterHexEncodedPublicKey = MultiDeviceProtocol.shared.getMasterDevice(message.senderPublicKey)
|
|
||||||
if (masterHexEncodedPublicKey == null) {
|
|
||||||
val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})"
|
|
||||||
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName)
|
|
||||||
}
|
|
||||||
val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.senderPublicKey
|
|
||||||
val serviceDataMessage = getDataMessage(message)
|
|
||||||
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.serverTimestamp, false, 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun processOutgoingMessage(message: PublicChatMessage) {
|
|
||||||
val messageServerID = message.serverID ?: return
|
|
||||||
val messageID = DatabaseFactory.getLokiMessageDatabase(context).getMessageID(messageServerID)
|
|
||||||
var isDuplicate = false
|
|
||||||
if (messageID != null) {
|
|
||||||
isDuplicate = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageID) >= 0
|
|
||||||
|| DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageID) >= 0
|
|
||||||
}
|
|
||||||
if (isDuplicate) { return }
|
|
||||||
if (message.body.isEmpty() && message.attachments.isEmpty() && message.quote == null) { return }
|
|
||||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
||||||
val dataMessage = getDataMessage(message)
|
|
||||||
SessionMetaProtocol.dropFromTimestampCacheIfNeeded(message.serverTimestamp)
|
|
||||||
val transcript = SentTranscriptMessage(userHexEncodedPublicKey, message.serverTimestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(userHexEncodedPublicKey, false))
|
|
||||||
transcript.messageServerID = messageServerID
|
|
||||||
if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) {
|
|
||||||
PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript)
|
|
||||||
} else {
|
|
||||||
PushDecryptJob(context).handleSynchronizeSentTextMessage(transcript)
|
|
||||||
}
|
|
||||||
// If we got a message from our master device then make sure our mapping stays in sync
|
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(message.senderPublicKey), false)
|
|
||||||
if (recipient.isUserMasterDevice && message.profilePicture != null) {
|
|
||||||
val profileKey = message.profilePicture!!.profileKey
|
|
||||||
val url = message.profilePicture!!.url
|
|
||||||
if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, profileKey)) {
|
|
||||||
val database = DatabaseFactory.getRecipientDatabase(context)
|
|
||||||
database.setProfileKey(recipient, profileKey)
|
|
||||||
database.setProfileAvatar(recipient, url)
|
|
||||||
ApplicationContext.getInstance(context).updateOpenGroupProfilePicturesIfNeeded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isPollOngoing) { return Promise.of(Unit) }
|
if (isPollOngoing) { return Promise.of(Unit) }
|
||||||
isPollOngoing = true
|
isPollOngoing = true
|
||||||
val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userHexEncodedPublicKey)
|
val deferred = deferred<Unit, Exception>()
|
||||||
var uniqueDevices = setOf<String>()
|
// Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below
|
||||||
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
|
OpenGroupAPI.getMessages(openGroup.channel, openGroup.server).successBackground { messages ->
|
||||||
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 ->
|
|
||||||
/*
|
|
||||||
if (messages.isNotEmpty()) {
|
|
||||||
// We need to fetch the device mapping for any devices we don't have
|
|
||||||
uniqueDevices = messages.map { it.senderPublicKey }.toSet()
|
|
||||||
val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && FileServerAPI.shared.hasDeviceLinkCacheExpired(publicKey = it) }
|
|
||||||
if (devicesToUpdate.isNotEmpty()) {
|
|
||||||
return@bind FileServerAPI.shared.getDeviceLinks(devicesToUpdate.toSet()).then { messages }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
Promise.of(messages)
|
|
||||||
}
|
|
||||||
promise.successBackground {
|
|
||||||
/*
|
|
||||||
val newDisplayNameUpdatees = uniqueDevices.mapNotNull {
|
|
||||||
// This will return null if the current device is a master device
|
|
||||||
MultiDeviceProtocol.shared.getMasterDevice(it)
|
|
||||||
}.toSet()
|
|
||||||
// Fetch the display names of the master devices
|
|
||||||
displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees)
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
promise.successBackground { messages ->
|
|
||||||
// Process messages in the background
|
// Process messages in the background
|
||||||
messages.forEach { message ->
|
messages.forEach { message ->
|
||||||
if (userDevices.contains(message.senderPublicKey)) {
|
val senderPublicKey = message.senderPublicKey
|
||||||
processOutgoingMessage(message)
|
val wasSentByCurrentUser = (senderPublicKey == userHexEncodedPublicKey)
|
||||||
|
fun generateDisplayName(rawDisplayName: String): String {
|
||||||
|
return "${rawDisplayName} (${senderPublicKey.takeLast(8)})"
|
||||||
|
}
|
||||||
|
val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName("Anonymous")
|
||||||
|
val id = GroupUtil.getEncodedOpenGroupIDAsData(openGroup.id)
|
||||||
|
// Main message
|
||||||
|
val dataMessageProto = DataMessage.newBuilder()
|
||||||
|
val body = if (message.body == message.timestamp.toString()) { "" } else { message.body }
|
||||||
|
dataMessageProto.setBody(body)
|
||||||
|
dataMessageProto.setTimestamp(message.timestamp)
|
||||||
|
// Attachments
|
||||||
|
val attachmentProtos = message.attachments.mapNotNull { attachment ->
|
||||||
|
if (attachment.kind != OpenGroupMessage.Attachment.Kind.Attachment) { return@mapNotNull null }
|
||||||
|
val attachmentProto = AttachmentPointer.newBuilder()
|
||||||
|
attachmentProto.setId(attachment.serverID)
|
||||||
|
attachmentProto.setContentType(attachment.contentType)
|
||||||
|
attachmentProto.setSize(attachment.size)
|
||||||
|
attachmentProto.setFileName(attachment.fileName)
|
||||||
|
attachmentProto.setFlags(attachment.flags)
|
||||||
|
attachmentProto.setWidth(attachment.width)
|
||||||
|
attachmentProto.setHeight(attachment.height)
|
||||||
|
attachment.caption.let { attachmentProto.setCaption(it) }
|
||||||
|
attachmentProto.setUrl(attachment.url)
|
||||||
|
attachmentProto.build()
|
||||||
|
}
|
||||||
|
dataMessageProto.addAllAttachments(attachmentProtos)
|
||||||
|
// Link preview
|
||||||
|
val linkPreview = message.attachments.firstOrNull { it.kind == OpenGroupMessage.Attachment.Kind.LinkPreview }
|
||||||
|
if (linkPreview != null) {
|
||||||
|
val linkPreviewProto = DataMessage.Preview.newBuilder()
|
||||||
|
linkPreviewProto.setUrl(linkPreview.linkPreviewURL!!)
|
||||||
|
linkPreviewProto.setTitle(linkPreview.linkPreviewTitle!!)
|
||||||
|
val attachmentProto = AttachmentPointer.newBuilder()
|
||||||
|
attachmentProto.setId(linkPreview.serverID)
|
||||||
|
attachmentProto.setContentType(linkPreview.contentType)
|
||||||
|
attachmentProto.setSize(linkPreview.size)
|
||||||
|
attachmentProto.setFileName(linkPreview.fileName)
|
||||||
|
attachmentProto.setFlags(linkPreview.flags)
|
||||||
|
attachmentProto.setWidth(linkPreview.width)
|
||||||
|
attachmentProto.setHeight(linkPreview.height)
|
||||||
|
linkPreview.caption.let { attachmentProto.setCaption(it) }
|
||||||
|
attachmentProto.setUrl(linkPreview.url)
|
||||||
|
linkPreviewProto.setImage(attachmentProto.build())
|
||||||
|
dataMessageProto.addPreview(linkPreviewProto.build())
|
||||||
|
}
|
||||||
|
// Quote
|
||||||
|
val quote = message.quote
|
||||||
|
if (quote != null) {
|
||||||
|
val quoteProto = DataMessage.Quote.newBuilder()
|
||||||
|
quoteProto.setId(quote.quotedMessageTimestamp)
|
||||||
|
quoteProto.setAuthor(quote.quoteePublicKey)
|
||||||
|
if (quote.quotedMessageBody != quote.quotedMessageTimestamp.toString()) { quoteProto.setText(quote.quotedMessageBody) }
|
||||||
|
dataMessageProto.setQuote(quoteProto.build())
|
||||||
|
}
|
||||||
|
// Profile
|
||||||
|
val profileProto = DataMessage.LokiProfile.newBuilder()
|
||||||
|
profileProto.setDisplayName(message.displayName)
|
||||||
|
val profilePicture = message.profilePicture
|
||||||
|
if (profilePicture != null) {
|
||||||
|
profileProto.setProfilePicture(profilePicture.url)
|
||||||
|
dataMessageProto.setProfileKey(ByteString.copyFrom(profilePicture.profileKey))
|
||||||
|
}
|
||||||
|
dataMessageProto.setProfile(profileProto.build())
|
||||||
|
// Open group info
|
||||||
|
val messageServerID = message.serverID
|
||||||
|
if (messageServerID != null) {
|
||||||
|
val openGroupProto = PublicChatInfo.newBuilder()
|
||||||
|
openGroupProto.setServerID(messageServerID)
|
||||||
|
dataMessageProto.setPublicChatInfo(openGroupProto.build())
|
||||||
|
}
|
||||||
|
// Signal group context
|
||||||
|
val groupProto = GroupContext.newBuilder()
|
||||||
|
groupProto.setId(ByteString.copyFrom(id))
|
||||||
|
groupProto.setType(GroupContext.Type.DELIVER)
|
||||||
|
groupProto.setName(openGroup.displayName)
|
||||||
|
dataMessageProto.setGroup(groupProto.build())
|
||||||
|
// Content
|
||||||
|
val content = Content.newBuilder()
|
||||||
|
if (!wasSentByCurrentUser) { // Incoming message
|
||||||
|
content.setDataMessage(dataMessageProto.build())
|
||||||
|
} else { // Outgoing message
|
||||||
|
// FIXME: This needs to be updated as we removed sync message handling
|
||||||
|
val syncMessageSentBuilder = SyncMessage.Sent.newBuilder()
|
||||||
|
syncMessageSentBuilder.setMessage(dataMessageProto)
|
||||||
|
syncMessageSentBuilder.setDestination(userHexEncodedPublicKey)
|
||||||
|
syncMessageSentBuilder.setTimestamp(message.timestamp)
|
||||||
|
val syncMessageSent = syncMessageSentBuilder.build()
|
||||||
|
val syncMessageBuilder = SyncMessage.newBuilder()
|
||||||
|
syncMessageBuilder.setSent(syncMessageSent)
|
||||||
|
content.setSyncMessage(syncMessageBuilder.build())
|
||||||
|
}
|
||||||
|
// Envelope
|
||||||
|
val builder = Envelope.newBuilder()
|
||||||
|
builder.type = Envelope.Type.UNIDENTIFIED_SENDER
|
||||||
|
builder.source = senderPublicKey
|
||||||
|
builder.sourceDevice = 1
|
||||||
|
builder.setContent(content.build().toByteString())
|
||||||
|
builder.serverTimestamp = message.serverTimestamp
|
||||||
|
val envelope = builder.build()
|
||||||
|
val job = MessageReceiveJob(envelope.toByteArray(), isBackgroundPoll, messageServerID, openGroup.id)
|
||||||
|
if (isBackgroundPoll) {
|
||||||
|
job.executeAsync().success { deferred.resolve(Unit) }.fail { deferred.resolve(Unit) }
|
||||||
|
// The promise is just used to keep track of when we're done
|
||||||
} else {
|
} else {
|
||||||
processIncomingMessage(message)
|
JobQueue.shared.add(job)
|
||||||
|
deferred.resolve(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isCaughtUp = true
|
isCaughtUp = true
|
||||||
isPollOngoing = false
|
isPollOngoing = false
|
||||||
}
|
}.fail {
|
||||||
promise.fail {
|
Log.d("Loki", "Failed to get messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.")
|
||||||
Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.")
|
|
||||||
isPollOngoing = false
|
isPollOngoing = false
|
||||||
}
|
}
|
||||||
return promise.map { Unit }
|
return deferred.promise
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pollForDisplayNames() {
|
private fun pollForDisplayNames() {
|
||||||
if (displayNameUpdatees.isEmpty()) { return }
|
if (displayNameUpdatees.isEmpty()) { return }
|
||||||
val hexEncodedPublicKeys = displayNameUpdatees
|
val hexEncodedPublicKeys = displayNameUpdatees
|
||||||
displayNameUpdatees = setOf()
|
displayNameUpdatees = setOf()
|
||||||
api.getDisplayNames(hexEncodedPublicKeys, group.server).successBackground { mapping ->
|
OpenGroupAPI.getDisplayNames(hexEncodedPublicKeys, openGroup.server).successBackground { mapping ->
|
||||||
for (pair in mapping.entries) {
|
for (pair in mapping.entries) {
|
||||||
val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})"
|
val senderDisplayName = "${pair.value} (...${pair.key.takeLast(8)})"
|
||||||
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, pair.key, senderDisplayName)
|
MessagingConfiguration.shared.storage.setOpenGroupDisplayName(pair.key, openGroup.channel, openGroup.server, senderDisplayName)
|
||||||
}
|
}
|
||||||
}.fail {
|
}.fail {
|
||||||
displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys)
|
displayNameUpdatees = displayNameUpdatees.union(hexEncodedPublicKeys)
|
||||||
@ -286,22 +239,18 @@ class OpenGroupPoller(private val context: Context, private val group: PublicCha
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun pollForDeletedMessages() {
|
private fun pollForDeletedMessages() {
|
||||||
api.getDeletedMessageServerIDs(group.channel, group.server).success { deletedMessageServerIDs ->
|
OpenGroupAPI.getDeletedMessageServerIDs(openGroup.channel, openGroup.server).success { deletedMessageServerIDs ->
|
||||||
val lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(context)
|
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getMessageID(it) }
|
||||||
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { lokiMessageDatabase.getMessageID(it) }
|
|
||||||
val smsMessageDatabase = DatabaseFactory.getSmsDatabase(context)
|
|
||||||
val mmsMessageDatabase = DatabaseFactory.getMmsDatabase(context)
|
|
||||||
deletedMessageIDs.forEach {
|
deletedMessageIDs.forEach {
|
||||||
smsMessageDatabase.deleteMessage(it)
|
MessagingConfiguration.shared.messageDataProvider.deleteMessage(it)
|
||||||
mmsMessageDatabase.delete(it)
|
|
||||||
}
|
}
|
||||||
}.fail {
|
}.fail {
|
||||||
Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${group.channel} on server: ${group.server}.")
|
Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pollForModerators() {
|
private fun pollForModerators() {
|
||||||
api.getModerators(group.channel, group.server)
|
OpenGroupAPI.getModerators(openGroup.channel, openGroup.server)
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user