Add support for admin delete.

This commit is contained in:
Michelle Tang
2026-02-20 14:44:34 -05:00
committed by Cody Henthorne
parent 1968438ebb
commit 071fbfd916
45 changed files with 648 additions and 132 deletions

View File

@@ -283,6 +283,10 @@ object ImportSkips {
return log(0, "Missing recipient for chat $chatId")
}
fun missingAdminDeleteRecipient(sentTimestamp: Long, chatId: Long): String {
return log(sentTimestamp, "Missing admin delete recipient for chat $chatId")
}
private fun log(sentTimestamp: Long, message: String): String {
return "[SKIP][$sentTimestamp] $message"
}

View File

@@ -44,7 +44,6 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, self
${MessageTable.FROM_RECIPIENT_ID},
${MessageTable.TO_RECIPIENT_ID},
${MessageTable.EXPIRE_STARTED},
${MessageTable.REMOTE_DELETED},
${MessageTable.UNIDENTIFIED},
${MessageTable.LINK_PREVIEWS},
${MessageTable.SHARED_CONTACTS},
@@ -68,7 +67,8 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, self
${MessageTable.VIEW_ONCE},
${MessageTable.PINNED_UNTIL},
${MessageTable.PINNING_MESSAGE_ID},
${MessageTable.PINNED_AT}
${MessageTable.PINNED_AT},
${MessageTable.DELETED_BY}
)
WHERE $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1
""".trimMargin()
@@ -136,7 +136,6 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, self
MessageTable.TO_RECIPIENT_ID,
EXPIRES_IN,
MessageTable.EXPIRE_STARTED,
MessageTable.REMOTE_DELETED,
MessageTable.UNIDENTIFIED,
MessageTable.LINK_PREVIEWS,
MessageTable.SHARED_CONTACTS,
@@ -161,7 +160,8 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, self
PARENT_STORY_ID,
MessageTable.PINNED_UNTIL,
MessageTable.PINNING_MESSAGE_ID,
MessageTable.PINNED_AT
MessageTable.PINNED_AT,
MessageTable.DELETED_BY
)
.from("${MessageTable.TABLE_NAME} INDEXED BY $dateReceivedIndex")
.where("$STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND ($EXPIRES_IN == 0 OR $EXPIRES_IN > ${1.days.inWholeMilliseconds}) AND $DATE_RECEIVED >= $lastSeenReceivedTime $cutoffQuery")

View File

@@ -19,7 +19,6 @@ import org.signal.core.util.UuidUtil
import org.signal.core.util.bytes
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.emptyIfNull
import org.signal.core.util.isEmpty
import org.signal.core.util.isNotEmpty
import org.signal.core.util.isNotNullOrBlank
import org.signal.core.util.kibiBytes
@@ -42,6 +41,7 @@ import org.thoughtcrime.securesms.backup.v2.BackupMode
import org.thoughtcrime.securesms.backup.v2.ExportOddities
import org.thoughtcrime.securesms.backup.v2.ExportSkips
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.proto.AdminDeletedMessage
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
import org.thoughtcrime.securesms.backup.v2.proto.ContactAttachment
@@ -193,11 +193,16 @@ class ChatItemArchiveExporter(
}
when {
record.remoteDeleted -> {
record.deletedBy == record.fromRecipientId -> {
builder.remoteDeletedMessage = RemoteDeletedMessage()
transformTimer.emit("remote-delete")
}
record.deletedBy != null -> {
builder.adminDeletedMessage = AdminDeletedMessage(adminId = record.deletedBy)
transformTimer.emit("admin-delete")
}
MessageTypes.isJoinedType(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.JOINED_SIGNAL)
transformTimer.emit("simple-update")
@@ -564,7 +569,7 @@ private fun BackupMessageRecord.toBasicChatItemBuilder(selfRecipientId: Recipien
}
val direction = when {
record.type.isDirectionlessType() && !record.remoteDeleted -> {
record.type.isDirectionlessType() && record.deletedBy == null -> {
Direction.DIRECTIONLESS
}
MessageTypes.isOutgoingMessageType(record.type) || record.fromRecipientId == selfRecipientId.toLong() -> {
@@ -1662,7 +1667,8 @@ private fun ChatItem.validateChatItem(exportState: ExportState, selfRecipientId:
this.giftBadge == null &&
this.viewOnceMessage == null &&
this.directStoryReplyMessage == null &&
this.poll == null
this.poll == null &&
this.adminDeletedMessage == null
) {
Log.w(TAG, ExportSkips.emptyChatItem(this.dateSent))
return null
@@ -1805,7 +1811,6 @@ private fun Cursor.toBackupMessageRecord(pastIds: Set<Long>, backupStartTime: Lo
toRecipientId = this.requireLong(MessageTable.TO_RECIPIENT_ID),
expiresIn = expiresIn,
expireStarted = expireStarted,
remoteDeleted = this.requireBoolean(MessageTable.REMOTE_DELETED),
sealedSender = this.requireBoolean(MessageTable.UNIDENTIFIED),
linkPreview = this.requireString(MessageTable.LINK_PREVIEWS),
sharedContacts = this.requireString(MessageTable.SHARED_CONTACTS),
@@ -1830,6 +1835,7 @@ private fun Cursor.toBackupMessageRecord(pastIds: Set<Long>, backupStartTime: Lo
parentStoryId = this.requireLong(MessageTable.PARENT_STORY_ID),
pinnedAt = this.requireLong(MessageTable.PINNED_AT),
pinnedUntil = this.requireLong(MessageTable.PINNED_UNTIL),
deletedBy = this.requireLongOrNull(MessageTable.DELETED_BY),
messageExtrasSize = messageExtras?.size ?: 0
)
}
@@ -1847,7 +1853,6 @@ private class BackupMessageRecord(
val toRecipientId: Long,
val expiresIn: Long,
val expireStarted: Long,
val remoteDeleted: Boolean,
val sealedSender: Boolean,
val linkPreview: String?,
val sharedContacts: String?,
@@ -1872,6 +1877,7 @@ private class BackupMessageRecord(
val viewOnce: Boolean,
val pinnedAt: Long,
val pinnedUntil: Long,
val deletedBy: Long?,
private val messageExtrasSize: Int
) {
val estimatedSizeInBytes: Int = (body?.length ?: 0) +

View File

@@ -121,7 +121,6 @@ class ChatItemArchiveImporter(
MessageTable.EXPIRES_IN,
MessageTable.EXPIRE_STARTED,
MessageTable.UNIDENTIFIED,
MessageTable.REMOTE_DELETED,
MessageTable.NETWORK_FAILURES,
MessageTable.QUOTE_ID,
MessageTable.QUOTE_AUTHOR,
@@ -141,7 +140,8 @@ class ChatItemArchiveImporter(
MessageTable.NOTIFIED,
MessageTable.PINNED_UNTIL,
MessageTable.PINNING_MESSAGE_ID,
MessageTable.PINNED_AT
MessageTable.PINNED_AT,
MessageTable.DELETED_BY
)
private val REACTION_COLUMNS = arrayOf(
@@ -193,6 +193,12 @@ class ChatItemArchiveImporter(
Log.w(TAG, ImportSkips.chatIdRemoteRecipientNotFound(chatItem.dateSent, chatItem.chatId))
return
}
if (chatItem.adminDeletedMessage != null && importState.remoteToLocalRecipientId[chatItem.adminDeletedMessage.adminId] == null) {
Log.w(TAG, ImportSkips.missingAdminDeleteRecipient(chatItem.dateSent, chatItem.chatId))
return
}
val messageInsert = chatItem.toMessageInsert(fromLocalRecipientId, chatLocalRecipientId, localThreadId)
if (chatItem.revisions.isNotEmpty()) {
// Flush to avoid having revisions cross batch boundaries, which will cause a foreign key failure
@@ -672,7 +678,6 @@ class ChatItemArchiveImporter(
contentValues.put(MessageTable.QUOTE_MISSING, 0)
contentValues.put(MessageTable.QUOTE_TYPE, 0)
contentValues.put(MessageTable.VIEW_ONCE, 0)
contentValues.put(MessageTable.REMOTE_DELETED, 0)
contentValues.put(MessageTable.PARENT_STORY_ID, 0)
if (this.pinDetails != null) {
@@ -683,12 +688,13 @@ class ChatItemArchiveImporter(
when {
this.standardMessage != null -> contentValues.addStandardMessage(this.standardMessage)
this.remoteDeletedMessage != null -> contentValues.put(MessageTable.REMOTE_DELETED, 1)
this.remoteDeletedMessage != null -> contentValues.put(MessageTable.DELETED_BY, fromRecipientId.toLong())
this.updateMessage != null -> contentValues.addUpdateMessage(this.updateMessage, fromRecipientId, toRecipientId)
this.paymentNotification != null -> contentValues.addPaymentNotification(this, chatRecipientId)
this.giftBadge != null -> contentValues.addGiftBadge(this.giftBadge)
this.viewOnceMessage != null -> contentValues.addViewOnce(this.viewOnceMessage)
this.directStoryReplyMessage != null -> contentValues.addDirectStoryReply(this.directStoryReplyMessage, toRecipientId)
this.adminDeletedMessage != null -> contentValues.put(MessageTable.DELETED_BY, importState.remoteToLocalRecipientId[this.adminDeletedMessage.adminId]!!.toLong())
}
return contentValues