mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 11:05:25 +00:00
Handle message deletion
This commit is contained in:
parent
b06aee7a20
commit
22b4479019
@ -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;
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -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?
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user