Handle message deletion

This commit is contained in:
SessionHero01 2024-10-30 11:40:19 +11:00
parent b06aee7a20
commit 22b4479019
No known key found for this signature in database
11 changed files with 157 additions and 68 deletions

View File

@ -44,7 +44,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration;
import org.thoughtcrime.securesms.configs.ConfigToDatabaseSync; import org.thoughtcrime.securesms.configs.ConfigToDatabaseSync;
import org.thoughtcrime.securesms.configs.ConfigUploader; import org.thoughtcrime.securesms.configs.ConfigUploader;
import org.session.libsession.messaging.groups.GroupManagerV2; import org.session.libsession.messaging.groups.GroupManagerV2;
import org.session.libsession.messaging.groups.RemoveGroupMemberHandler; import org.thoughtcrime.securesms.groups.handler.RemoveGroupMemberHandler;
import org.session.libsession.messaging.notifications.TokenFetcher; import org.session.libsession.messaging.notifications.TokenFetcher;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.session.libsession.messaging.sending_receiving.pollers.LegacyClosedGroupPollerV2; import org.session.libsession.messaging.sending_receiving.pollers.LegacyClosedGroupPollerV2;
@ -85,10 +85,8 @@ import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.notifications.BackgroundPollWorker; import org.thoughtcrime.securesms.notifications.BackgroundPollWorker;
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.PushRegistrationHandler; import org.thoughtcrime.securesms.notifications.PushRegistrationHandler;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;

View File

@ -7,7 +7,6 @@ import org.greenrobot.eventbus.EventBus
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.messages.MarkAsDeletedMessage import org.session.libsession.messaging.messages.MarkAsDeletedMessage
import org.session.libsession.messaging.messages.control.UnsendRequest
import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
@ -249,6 +248,51 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
} }
} }
override fun markMessagesAsDeleted(
threadId: Long,
serverHashes: List<String>,
displayedMessage: String
) {
val sendersForHashes = DatabaseComponent.get(context).lokiMessageDatabase()
.getSendersForHashes(threadId, serverHashes.toSet())
val smsMessages = sendersForHashes.asSequence()
.filter { it.isSms }
.map { msg -> MarkAsDeletedMessage(messageId = msg.messageId, isOutgoing = msg.isOutgoing) }
.toList()
val mmsMessages = sendersForHashes.asSequence()
.filter { !it.isSms }
.map { msg -> MarkAsDeletedMessage(messageId = msg.messageId, isOutgoing = msg.isOutgoing) }
.toList()
markMessagesAsDeleted(smsMessages, isSms = true, displayedMessage)
markMessagesAsDeleted(mmsMessages, isSms = false, displayedMessage)
}
override fun markUserMessagesAsDeleted(
threadId: Long,
until: Long,
sender: String,
displayedMessage: String
) {
val mmsMessages = mutableListOf<MarkAsDeletedMessage>()
val smsMessages = mutableListOf<MarkAsDeletedMessage>()
DatabaseComponent.get(context).mmsSmsDatabase().getUserMessages(threadId, sender)
.filter { it.timestamp <= until }
.forEach { record ->
if (record.isMms) {
mmsMessages.add(MarkAsDeletedMessage(record.id, record.isOutgoing))
} else {
smsMessages.add(MarkAsDeletedMessage(record.id, record.isOutgoing))
}
}
markMessagesAsDeleted(smsMessages, isSms = true, displayedMessage)
markMessagesAsDeleted(mmsMessages, isSms = false, displayedMessage)
}
override fun getServerHashForMessage(messageID: Long, mms: Boolean): String? = override fun getServerHashForMessage(messageID: Long, mms: Boolean): String? =
DatabaseComponent.get(context).lokiMessageDatabase().getMessageServerHash(messageID, mms) DatabaseComponent.get(context).lokiMessageDatabase().getMessageServerHash(messageID, mms)

View File

@ -310,25 +310,6 @@ public class MmsSmsDatabase extends Database {
return identifiedMessages; return identifiedMessages;
} }
// Version of the above `getAllMessageRecordsFromSenderInThread` method that returns the message
// Ids rather than the set of MessageRecords - currently unused by potentially useful in the future.
public Set<Long> getAllMessageIdsFromSenderInThread(long threadId, String serializedAuthor) {
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsSmsColumns.ADDRESS + " = \"" + serializedAuthor + "\"";
Set<Long> identifiedMessages = new HashSet<Long>();
// Try everything with resources so that they auto-close on end of scope
try (Cursor cursor = queryTables(PROJECTION, selection, null, null)) {
try (MmsSmsDatabase.Reader reader = readerFor(cursor)) {
MessageRecord messageRecord;
while ((messageRecord = reader.getNext()) != null) {
identifiedMessages.add(messageRecord.id);
}
}
}
return identifiedMessages;
}
public long getLastOutgoingTimestamp(long threadId) { public long getLastOutgoingTimestamp(long threadId) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;

View File

@ -30,7 +30,6 @@ import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.groups.GroupManagerV2
import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.ConfigUpdateNotification
import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.AccountId
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.ConfigFactory
const val MAX_GROUP_NAME_LENGTH = 100 const val MAX_GROUP_NAME_LENGTH = 100

View File

@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.R
import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE
import network.loki.messenger.libsession_util.util.Conversation import network.loki.messenger.libsession_util.util.Conversation
import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.GroupInfo
@ -939,6 +940,7 @@ class GroupManagerV2Impl @Inject constructor(
override suspend fun handleDeleteMemberContent( override suspend fun handleDeleteMemberContent(
groupId: AccountId, groupId: AccountId,
deleteMemberContent: GroupUpdateDeleteMemberContentMessage, deleteMemberContent: GroupUpdateDeleteMemberContentMessage,
timestamp: Long,
sender: AccountId, sender: AccountId,
senderIsVerifiedAdmin: Boolean, senderIsVerifiedAdmin: Boolean,
): Unit = withContext(dispatcher) { ): Unit = withContext(dispatcher) {
@ -951,24 +953,31 @@ class GroupManagerV2Impl @Inject constructor(
val memberIds = deleteMemberContent.memberSessionIdsList val memberIds = deleteMemberContent.memberSessionIdsList
if (hashes.isNotEmpty()) { if (hashes.isNotEmpty()) {
if (senderIsVerifiedAdmin) { // If the sender is a verified admin, or the sender is the actual sender of the messages,
// We'll delete everything the admin says // we can mark them as deleted locally.
storage.deleteMessagesByHash(threadId, hashes) if (senderIsVerifiedAdmin ||
} else if (storage.ensureMessageHashesAreSender( storage.ensureMessageHashesAreSender(
hashes.toSet(), hashes.toSet(),
sender.hexString, sender.hexString,
groupId.hexString groupId.hexString
)) {
// We'll delete everything the admin says
messageDataProvider.markMessagesAsDeleted(
threadId = threadId,
serverHashes = hashes,
displayedMessage = application.getString(
R.string.deleteMessageDeletedGlobally
)
) )
) {
// For deleting message by hashes, we'll likely only need to mark
// them as deleted
storage.deleteMessagesByHash(threadId, hashes)
} }
} }
// To be able to delete a user's messages, the sender must be a verified admin
if (memberIds.isNotEmpty() && senderIsVerifiedAdmin) { if (memberIds.isNotEmpty() && senderIsVerifiedAdmin) {
for (member in memberIds) { for (member in memberIds) {
storage.deleteMessagesByUser(threadId, member) messageDataProvider.markUserMessagesAsDeleted(threadId, timestamp, member, application.getString(
R.string.deleteMessageDeletedGlobally
))
} }
} }

View File

@ -303,6 +303,27 @@ private fun ConfirmRemovingMemberDialog(
groupName: String, groupName: String,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val buttons = buildList {
this += DialogButtonModel(
text = GetString(R.string.remove),
color = LocalColors.current.danger,
onClick = { onConfirmed(member.accountId, false) }
)
if (BuildConfig.DEBUG) {
this += DialogButtonModel(
text = GetString("Remove with messages"),
color = LocalColors.current.danger,
onClick = { onConfirmed(member.accountId, true) }
)
}
this += DialogButtonModel(
text = GetString(R.string.cancel),
onClick = onDismissRequest,
)
}
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
text = Phrase.from(context, R.string.groupRemoveDescription) text = Phrase.from(context, R.string.groupRemoveDescription)
@ -311,17 +332,7 @@ private fun ConfirmRemovingMemberDialog(
.format() .format()
.toString(), .toString(),
title = stringResource(R.string.remove), title = stringResource(R.string.remove),
buttons = listOf( buttons = buttons
DialogButtonModel(
text = GetString(R.string.remove),
color = LocalColors.current.danger,
onClick = { onConfirmed(member.accountId, false) }
),
DialogButtonModel(
text = GetString(R.string.cancel),
onClick = onDismissRequest,
)
)
) )
} }
@ -346,7 +357,7 @@ private fun MemberOptionsDialog(
) )
} }
if (false && member.canPromote) { if (BuildConfig.DEBUG && member.canPromote) {
this += BottomOptionsDialogItem( this += BottomOptionsDialogItem(
title = context.getString(R.string.adminPromoteToAdmin), title = context.getString(R.string.adminPromoteToAdmin),
iconRes = R.drawable.ic_profile_default, iconRes = R.drawable.ic_profile_default,

View File

@ -1,37 +1,45 @@
package org.session.libsession.messaging.groups package org.thoughtcrime.securesms.groups.handler
import android.content.Context
import android.os.SystemClock import android.os.SystemClock
import kotlinx.coroutines.CoroutineScope import com.google.protobuf.ByteString
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.R
import network.loki.messenger.libsession_util.ReadableGroupKeysConfig import network.loki.messenger.libsession_util.ReadableGroupKeysConfig
import network.loki.messenger.libsession_util.util.GroupMember import network.loki.messenger.libsession_util.util.GroupMember
import network.loki.messenger.libsession_util.util.Sodium import network.loki.messenger.libsession_util.util.Sodium
import org.session.libsession.database.MessageDataProvider
import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.groups.GroupManagerV2
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.GroupUpdated import org.session.libsession.messaging.messages.control.GroupUpdated
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.utilities.MessageAuthentication
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.snode.OwnedSwarmAuth import org.session.libsession.snode.OwnedSwarmAuth
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeClock
import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.SnodeMessage
import org.session.libsession.snode.utilities.await import org.session.libsession.snode.utilities.await
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.waitUntilGroupConfigsPushed import org.session.libsession.utilities.waitUntilGroupConfigsPushed
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage
import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.AccountId
import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Namespace import org.session.libsignal.utilities.Namespace
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
private const val TAG = "RemoveGroupMemberHandler" private const val TAG = "RemoveGroupMemberHandler"
private const val MIN_PROCESS_INTERVAL_MILLS = 1_000L private const val MIN_PROCESS_INTERVAL_MILLS = 1_000L
/** /**
@ -39,10 +47,15 @@ private const val MIN_PROCESS_INTERVAL_MILLS = 1_000L
* *
* It automatically does so by listening to the config updates changes and checking for any pending removals. * It automatically does so by listening to the config updates changes and checking for any pending removals.
*/ */
@Singleton
class RemoveGroupMemberHandler @Inject constructor( class RemoveGroupMemberHandler @Inject constructor(
@ApplicationContext private val context: Context,
private val configFactory: ConfigFactoryProtocol, private val configFactory: ConfigFactoryProtocol,
private val textSecurePreferences: TextSecurePreferences, private val textSecurePreferences: TextSecurePreferences,
private val groupManager: GroupManagerV2, private val groupManager: GroupManagerV2,
private val clock: SnodeClock,
private val messageDataProvider: MessageDataProvider,
private val storage: StorageProtocol,
) { ) {
private var job: Job? = null private var job: Job? = null
@ -121,7 +134,12 @@ class RemoveGroupMemberHandler @Inject constructor(
// Call No 2. Send a "kicked" message to the revoked namespace // Call No 2. Send a "kicked" message to the revoked namespace
calls += SnodeAPI.buildAuthenticatedStoreBatchInfo( calls += SnodeAPI.buildAuthenticatedStoreBatchInfo(
namespace = Namespace.REVOKED_GROUP_MESSAGES(), namespace = Namespace.REVOKED_GROUP_MESSAGES(),
message = buildGroupKickMessage(groupAccountId.hexString, pendingRemovals, configs.groupKeys, adminKey), message = buildGroupKickMessage(
groupAccountId.hexString,
pendingRemovals,
configs.groupKeys,
adminKey
),
auth = groupAuth, auth = groupAuth,
) )
@ -130,11 +148,12 @@ class RemoveGroupMemberHandler @Inject constructor(
calls += SnodeAPI.buildAuthenticatedStoreBatchInfo( calls += SnodeAPI.buildAuthenticatedStoreBatchInfo(
namespace = Namespace.CLOSED_GROUP_MESSAGES(), namespace = Namespace.CLOSED_GROUP_MESSAGES(),
message = buildDeleteGroupMemberContentMessage( message = buildDeleteGroupMemberContentMessage(
adminKey = adminKey,
groupAccountId = groupAccountId.hexString, groupAccountId = groupAccountId.hexString,
memberSessionIDs = pendingRemovals memberSessionIDs = pendingRemovals
.asSequence() .asSequence()
.filter { it.shouldRemoveMessages } .filter { it.shouldRemoveMessages }
.map { it.sessionId } .map { it.sessionId },
), ),
auth = groupAuth, auth = groupAuth,
) )
@ -148,7 +167,8 @@ class RemoveGroupMemberHandler @Inject constructor(
} }
val node = SnodeAPI.getSingleTargetSnode(groupAccountId.hexString).await() val node = SnodeAPI.getSingleTargetSnode(groupAccountId.hexString).await()
val response = SnodeAPI.getBatchResponse(node, groupAccountId.hexString, batchCalls, sequence = true) val response =
SnodeAPI.getBatchResponse(node, groupAccountId.hexString, batchCalls, sequence = true)
val firstError = response.results.firstOrNull { !it.isSuccessful } val firstError = response.results.firstOrNull { !it.isSuccessful }
check(firstError == null) { check(firstError == null) {
@ -172,36 +192,58 @@ class RemoveGroupMemberHandler @Inject constructor(
// cases (a.k.a the GroupUpdateDeleteMemberContent message handling) and could be by different admins. // cases (a.k.a the GroupUpdateDeleteMemberContent message handling) and could be by different admins.
val deletingMessagesForMembers = pendingRemovals.filter { it.shouldRemoveMessages } val deletingMessagesForMembers = pendingRemovals.filter { it.shouldRemoveMessages }
if (deletingMessagesForMembers.isNotEmpty()) { if (deletingMessagesForMembers.isNotEmpty()) {
try { val threadId = storage.getThreadId(Address.fromSerialized(groupAccountId.hexString))
groupManager.removeMemberMessages( if (threadId != null) {
groupAccountId, val until = clock.currentTimeMills()
deletingMessagesForMembers.map { AccountId(it.sessionId) } for (member in deletingMessagesForMembers) {
) try {
} catch (e: Exception) { messageDataProvider.markUserMessagesAsDeleted(
Log.e(TAG, "Error deleting messages for removed members", e) threadId = threadId,
until = until,
sender = member.sessionId,
displayedMessage = context.getString(R.string.deleteMessageDeletedGlobally)
)
} catch (e: Exception) {
Log.e(TAG, "Error deleting messages for removed member", e)
}
}
} }
} }
} }
private fun buildDeleteGroupMemberContentMessage( private fun buildDeleteGroupMemberContentMessage(
adminKey: ByteArray,
groupAccountId: String, groupAccountId: String,
memberSessionIDs: Sequence<String> memberSessionIDs: Sequence<String>
): SnodeMessage { ): SnodeMessage {
val timestamp = clock.currentTimeMills()
return MessageSender.buildWrappedMessageToSnode( return MessageSender.buildWrappedMessageToSnode(
destination = Destination.ClosedGroup(groupAccountId), destination = Destination.ClosedGroup(groupAccountId),
message = GroupUpdated( message = GroupUpdated(
GroupUpdateMessage.newBuilder() SignalServiceProtos.DataMessage.GroupUpdateMessage.newBuilder()
.setDeleteMemberContent( .setDeleteMemberContent(
SignalServiceProtos.DataMessage.GroupUpdateDeleteMemberContentMessage SignalServiceProtos.DataMessage.GroupUpdateDeleteMemberContentMessage.newBuilder()
.newBuilder()
.apply { .apply {
for (id in memberSessionIDs) { for (id in memberSessionIDs) {
addMemberSessionIds(id) addMemberSessionIds(id)
} }
} }
.setAdminSignature(
ByteString.copyFrom(
SodiumUtilities.sign(
MessageAuthentication.buildDeleteMemberContentSignature(
memberIds = memberSessionIDs.map { AccountId(it) }
.toList(),
messageHashes = emptyList(),
timestamp = timestamp,
), adminKey
)
)
)
) )
.build() .build()
), ).apply { sentTimestamp = timestamp },
isSyncMessage = false isSyncMessage = false
) )
} }
@ -227,7 +269,6 @@ class RemoveGroupMemberHandler @Inject constructor(
) )
), ),
ttl = SnodeMessage.DEFAULT_TTL, ttl = SnodeMessage.DEFAULT_TTL,
timestamp = SnodeAPI.nowWithOffset timestamp = clock.currentTimeMills()
) )
} }

View File

@ -26,6 +26,8 @@ interface MessageDataProvider {
fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean) fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean)
fun markMessageAsDeleted(timestamp: Long, author: String, displayedMessage: String) fun markMessageAsDeleted(timestamp: Long, author: String, displayedMessage: String)
fun markMessagesAsDeleted(messages: List<MarkAsDeletedMessage>, isSms: Boolean, displayedMessage: String) fun markMessagesAsDeleted(messages: List<MarkAsDeletedMessage>, isSms: Boolean, displayedMessage: String)
fun markMessagesAsDeleted(threadId: Long, serverHashes: List<String>, displayedMessage: String)
fun markUserMessagesAsDeleted(threadId: Long, until: Long, sender: String, displayedMessage: String)
fun getServerHashForMessage(messageID: Long, mms: Boolean): String? fun getServerHashForMessage(messageID: Long, mms: Boolean): String?
fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment?
fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream?

View File

@ -3,7 +3,9 @@ package org.session.libsession.database
data class ServerHashToMessageId( data class ServerHashToMessageId(
val serverHash: String, val serverHash: String,
/** /**
* This will only be the "sender" when the message is incoming. * This will only be the "sender" when the message is incoming, when the message is outgoing,
* the value here could be the receiver of the message, it's better not to rely on opposite
* meaning of this field.
*/ */
val sender: String, val sender: String,
val messageId: Long, val messageId: Long,

View File

@ -93,6 +93,7 @@ interface GroupManagerV2 {
suspend fun handleDeleteMemberContent( suspend fun handleDeleteMemberContent(
groupId: AccountId, groupId: AccountId,
deleteMemberContent: GroupUpdateDeleteMemberContentMessage, deleteMemberContent: GroupUpdateDeleteMemberContentMessage,
timestamp: Long,
sender: AccountId, sender: AccountId,
senderIsVerifiedAdmin: Boolean, senderIsVerifiedAdmin: Boolean,
) )

View File

@ -651,6 +651,7 @@ private fun handleDeleteMemberContent(message: GroupUpdated, closedGroup: Accoun
MessagingModuleConfiguration.shared.groupManagerV2.handleDeleteMemberContent( MessagingModuleConfiguration.shared.groupManagerV2.handleDeleteMemberContent(
groupId = closedGroup, groupId = closedGroup,
deleteMemberContent = deleteMemberContent, deleteMemberContent = deleteMemberContent,
timestamp = message.sentTimestamp!!,
sender = AccountId(message.sender!!), sender = AccountId(message.sender!!),
senderIsVerifiedAdmin = hasValidAdminSignature senderIsVerifiedAdmin = hasValidAdminSignature
) )