Clean up ChatItemExportIterator.

This commit is contained in:
Greyson Parrelli 2024-06-19 10:18:05 -04:00
parent 6fa8337058
commit 890facc6f6

View file

@ -132,248 +132,86 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
val builder = record.toBasicChatItemBuilder(groupReceiptsById[id])
when {
record.remoteDeleted -> builder.remoteDeletedMessage = RemoteDeletedMessage()
MessageTypes.isJoinedType(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.JOINED_SIGNAL))
MessageTypes.isIdentityUpdate(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.IDENTITY_UPDATE))
MessageTypes.isIdentityVerified(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.IDENTITY_VERIFIED))
MessageTypes.isIdentityDefault(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.IDENTITY_DEFAULT))
record.remoteDeleted -> {
builder.remoteDeletedMessage = RemoteDeletedMessage()
}
MessageTypes.isJoinedType(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.JOINED_SIGNAL)
}
MessageTypes.isIdentityUpdate(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.IDENTITY_UPDATE)
}
MessageTypes.isIdentityVerified(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.IDENTITY_VERIFIED)
}
MessageTypes.isIdentityDefault(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.IDENTITY_DEFAULT)
}
MessageTypes.isChangeNumber(record.type) -> {
builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.CHANGE_NUMBER))
builder.sms = false
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.CHANGE_NUMBER)
}
MessageTypes.isBoostRequest(record.type) -> {
builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.BOOST_REQUEST))
builder.sms = false
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.BOOST_REQUEST)
}
MessageTypes.isEndSessionType(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.END_SESSION)
}
MessageTypes.isChatSessionRefresh(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.CHAT_SESSION_REFRESH)
}
MessageTypes.isBadDecryptType(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.BAD_DECRYPT)
}
MessageTypes.isPaymentsActivated(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.PAYMENTS_ACTIVATED)
}
MessageTypes.isPaymentsRequestToActivate(record.type) -> {
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.PAYMENT_ACTIVATION_REQUEST)
}
MessageTypes.isEndSessionType(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.END_SESSION))
MessageTypes.isChatSessionRefresh(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.CHAT_SESSION_REFRESH))
MessageTypes.isBadDecryptType(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.BAD_DECRYPT))
MessageTypes.isPaymentsActivated(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.PAYMENTS_ACTIVATED))
MessageTypes.isPaymentsRequestToActivate(record.type) -> builder.updateMessage = ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = SimpleChatUpdate.Type.PAYMENT_ACTIVATION_REQUEST))
MessageTypes.isExpirationTimerUpdate(record.type) -> {
builder.updateMessage = ChatUpdateMessage(expirationTimerChange = ExpirationTimerChatUpdate(record.expiresIn.toInt()))
builder.expiresInMs = 0
}
MessageTypes.isProfileChange(record.type) -> {
val profileChangeDetails = if (record.messageExtras != null) {
record.messageExtras.profileChangeDetails
} else {
Base64.decodeOrNull(record.body)?.let { ProfileChangeDetails.ADAPTER.decode(it) }
}
builder.updateMessage = if (profileChangeDetails?.profileNameChange != null) {
ChatUpdateMessage(profileChange = ProfileChangeChatUpdate(previousName = profileChangeDetails.profileNameChange.previous, newName = profileChangeDetails.profileNameChange.newValue))
} else if (profileChangeDetails?.learnedProfileName != null) {
ChatUpdateMessage(learnedProfileChange = LearnedProfileChatUpdate(e164 = profileChangeDetails.learnedProfileName.e164?.e164ToLong(), username = profileChangeDetails.learnedProfileName.username))
} else {
continue
}
builder.sms = false
builder.updateMessage = record.toProfileChangeUpdate()
}
MessageTypes.isSessionSwitchoverType(record.type) -> {
builder.updateMessage = ChatUpdateMessage(
sessionSwitchover = try {
val event = SessionSwitchoverEvent.ADAPTER.decode(Base64.decodeOrThrow(record.body!!))
SessionSwitchoverChatUpdate(event.e164.e164ToLong()!!)
} catch (e: Exception) {
SessionSwitchoverChatUpdate()
}
)
builder.updateMessage = record.toSessionSwitchoverUpdate()
}
MessageTypes.isThreadMergeType(record.type) -> {
builder.updateMessage = ChatUpdateMessage(
threadMerge = try {
val event = ThreadMergeEvent.ADAPTER.decode(Base64.decodeOrThrow(record.body!!))
ThreadMergeChatUpdate(event.previousE164.e164ToLong()!!)
} catch (e: Exception) {
ThreadMergeChatUpdate()
}
)
builder.updateMessage = record.toThreadMergeUpdate()
}
MessageTypes.isGroupV2(record.type) && MessageTypes.isGroupUpdate(record.type) -> {
val groupChange = record.messageExtras?.gv2UpdateDescription?.groupChangeUpdate
if (groupChange != null) {
builder.updateMessage = ChatUpdateMessage(
groupChange = groupChange
)
} else if (record.body != null) {
try {
val decoded: ByteArray = Base64.decode(record.body)
val context = DecryptedGroupV2Context.ADAPTER.decode(decoded)
builder.updateMessage = ChatUpdateMessage(
groupChange = GroupsV2UpdateMessageConverter.translateDecryptedChange(selfIds = SignalStore.account().getServiceIds(), context)
)
} catch (e: IOException) {
continue
}
} else {
continue
}
builder.updateMessage = record.toGroupUpdate()
}
MessageTypes.isCallLog(record.type) -> {
builder.sms = false
val call = calls.getCallByMessageId(record.id)
if (call != null) {
if (call.type == CallTable.Type.GROUP_CALL) {
builder.updateMessage = ChatUpdateMessage(
groupCall = GroupCall(
callId = record.id,
state = when (call.event) {
CallTable.Event.MISSED -> GroupCall.State.MISSED
CallTable.Event.ONGOING -> GroupCall.State.GENERIC
CallTable.Event.ACCEPTED -> GroupCall.State.ACCEPTED
CallTable.Event.NOT_ACCEPTED -> GroupCall.State.GENERIC
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> GroupCall.State.MISSED_NOTIFICATION_PROFILE
CallTable.Event.DELETE -> continue
CallTable.Event.GENERIC_GROUP_CALL -> GroupCall.State.GENERIC
CallTable.Event.JOINED -> GroupCall.State.JOINED
CallTable.Event.RINGING -> GroupCall.State.RINGING
CallTable.Event.DECLINED -> GroupCall.State.DECLINED
CallTable.Event.OUTGOING_RING -> GroupCall.State.OUTGOING_RING
},
ringerRecipientId = call.ringerRecipient?.toLong(),
startedCallRecipientId = call.ringerRecipient?.toLong(),
startedCallTimestamp = call.timestamp
)
)
} else if (call.type != CallTable.Type.AD_HOC_CALL) {
builder.updateMessage = ChatUpdateMessage(
individualCall = IndividualCall(
callId = call.callId,
type = if (call.type == CallTable.Type.VIDEO_CALL) IndividualCall.Type.VIDEO_CALL else IndividualCall.Type.AUDIO_CALL,
direction = if (call.direction == CallTable.Direction.INCOMING) IndividualCall.Direction.INCOMING else IndividualCall.Direction.OUTGOING,
state = when (call.event) {
CallTable.Event.MISSED -> IndividualCall.State.MISSED
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> IndividualCall.State.MISSED_NOTIFICATION_PROFILE
CallTable.Event.ACCEPTED -> IndividualCall.State.ACCEPTED
CallTable.Event.NOT_ACCEPTED -> IndividualCall.State.NOT_ACCEPTED
else -> IndividualCall.State.UNKNOWN_STATE
},
startedCallTimestamp = call.timestamp
)
)
} else {
continue
}
} else {
when {
MessageTypes.isMissedAudioCall(record.type) -> {
builder.updateMessage = ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.AUDIO_CALL,
state = IndividualCall.State.MISSED,
direction = IndividualCall.Direction.INCOMING
)
)
}
MessageTypes.isMissedVideoCall(record.type) -> {
builder.updateMessage = ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.VIDEO_CALL,
state = IndividualCall.State.MISSED,
direction = IndividualCall.Direction.INCOMING
)
)
}
MessageTypes.isIncomingAudioCall(record.type) -> {
builder.updateMessage = ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.AUDIO_CALL,
state = IndividualCall.State.ACCEPTED,
direction = IndividualCall.Direction.INCOMING
)
)
}
MessageTypes.isIncomingVideoCall(record.type) -> {
builder.updateMessage = ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.VIDEO_CALL,
state = IndividualCall.State.ACCEPTED,
direction = IndividualCall.Direction.INCOMING
)
)
}
MessageTypes.isOutgoingAudioCall(record.type) -> {
builder.updateMessage = ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.AUDIO_CALL,
state = IndividualCall.State.ACCEPTED,
direction = IndividualCall.Direction.OUTGOING
)
)
}
MessageTypes.isOutgoingVideoCall(record.type) -> {
builder.updateMessage = ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.VIDEO_CALL,
state = IndividualCall.State.ACCEPTED,
direction = IndividualCall.Direction.OUTGOING
)
)
}
MessageTypes.isGroupCall(record.type) -> {
try {
val groupCallUpdateDetails = GroupCallUpdateDetailsUtil.parse(record.body)
builder.updateMessage = ChatUpdateMessage(
groupCall = GroupCall(
state = GroupCall.State.GENERIC,
startedCallRecipientId = recipients.getByAci(ACI.from(UuidUtil.parseOrThrow(groupCallUpdateDetails.startedCallUuid))).getOrNull()?.toLong(),
startedCallTimestamp = groupCallUpdateDetails.startedCallTimestamp,
endedCallTimestamp = groupCallUpdateDetails.endedCallTimestamp
)
)
} catch (exception: java.lang.Exception) {
continue
}
}
}
}
builder.updateMessage = record.toCallUpdate()
}
MessageTypes.isPaymentsNotification(record.type) -> {
val paymentUuid = UuidUtil.parseOrNull(record.body)
val payment = if (paymentUuid != null) {
SignalDatabase.payments.getPayment(paymentUuid)
} else {
null
}
if (payment == null) {
builder.paymentNotification = PaymentNotification()
} else {
builder.paymentNotification = PaymentNotification(
amountMob = payment.amount.serializeAmountString(),
feeMob = payment.fee.serializeAmountString(),
note = payment.note,
transactionDetails = payment.getTransactionDetails()
)
}
builder.paymentNotification = record.toPaymentNotificationUpdate()
}
MessageTypes.isGiftBadge(record.type) -> {
builder.giftBadge = record.toGiftBadgeUpdate()
}
MessageTypes.isGiftBadge(record.type) -> { builder.giftBadge = record.toBackupGiftBadge() }
else -> {
if (record.body == null && !attachmentsById.containsKey(record.id)) {
Log.w(TAG, "Record missing a body and doesnt have attachments, skipping")
Log.w(TAG, "Record with ID ${record.id} missing a body and doesn't have attachments. Skipping.")
continue
}
val attachments = attachmentsById[record.id]
val sticker = attachments?.firstOrNull { dbAttachment ->
dbAttachment.isSticker
}
if (sticker != null) {
val stickerLocator = sticker.stickerLocator!!
builder.stickerMessage = StickerMessage(
sticker = Sticker(
packId = Hex.fromStringCondensed(stickerLocator.packId).toByteString(),
packKey = Hex.fromStringCondensed(stickerLocator.packKey).toByteString(),
stickerId = stickerLocator.stickerId,
emoji = stickerLocator.emoji,
data_ = sticker.toBackupAttachment().pointer
),
reactions = reactionsById[id].toBackupReactions()
)
if (sticker?.stickerLocator != null) {
builder.stickerMessage = sticker.toStickerMessage(reactionsById[id])
} else {
builder.standardMessage = record.toStandardMessage(reactionsById[id], mentions = mentionsById[id], attachments = attachmentsById[record.id])
}
}
}
if (record.latestRevisionId == null) {
val previousEdits = revisionMap.remove(record.id)
if (previousEdits != null) {
@ -401,14 +239,8 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
cursor.close()
}
private fun String.e164ToLong(): Long? {
val fixed = if (this.startsWith("+")) {
this.substring(1)
} else {
this
}
return fixed.toLongOrNull()
private fun simpleUpdate(type: SimpleChatUpdate.Type): ChatUpdateMessage {
return ChatUpdateMessage(simpleUpdate = SimpleChatUpdate(type = type))
}
private fun BackupMessageRecord.toBasicChatItemBuilder(groupReceipts: List<GroupReceiptTable.GroupReceiptInfo>?): ChatItem.Builder {
@ -421,7 +253,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
expireStartDate = if (record.expireStarted > 0) record.expireStarted else 0
expiresInMs = if (record.expiresIn > 0) record.expiresIn else 0
revisions = emptyList()
sms = !MessageTypes.isSecureType(record.type)
sms = record.type.isSmsType()
if (MessageTypes.isCallLog(record.type)) {
directionless = ChatItem.DirectionlessMessageDetails()
} else if (MessageTypes.isOutgoingMessageType(record.type)) {
@ -439,6 +271,220 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
}
}
private fun BackupMessageRecord.toProfileChangeUpdate(): ChatUpdateMessage? {
val profileChangeDetails = if (this.messageExtras != null) {
this.messageExtras.profileChangeDetails
} else {
Base64.decodeOrNull(this.body)?.let { ProfileChangeDetails.ADAPTER.decode(it) }
}
return if (profileChangeDetails?.profileNameChange != null) {
ChatUpdateMessage(profileChange = ProfileChangeChatUpdate(previousName = profileChangeDetails.profileNameChange.previous, newName = profileChangeDetails.profileNameChange.newValue))
} else if (profileChangeDetails?.learnedProfileName != null) {
ChatUpdateMessage(learnedProfileChange = LearnedProfileChatUpdate(e164 = profileChangeDetails.learnedProfileName.e164?.e164ToLong(), username = profileChangeDetails.learnedProfileName.username))
} else {
null
}
}
private fun BackupMessageRecord.toSessionSwitchoverUpdate(): ChatUpdateMessage {
if (this.body == null) {
return ChatUpdateMessage(sessionSwitchover = SessionSwitchoverChatUpdate())
}
return ChatUpdateMessage(
sessionSwitchover = try {
val event = SessionSwitchoverEvent.ADAPTER.decode(Base64.decodeOrThrow(this.body))
SessionSwitchoverChatUpdate(event.e164.e164ToLong()!!)
} catch (e: IOException) {
SessionSwitchoverChatUpdate()
}
)
}
private fun BackupMessageRecord.toThreadMergeUpdate(): ChatUpdateMessage {
if (this.body == null) {
return ChatUpdateMessage(threadMerge = ThreadMergeChatUpdate())
}
return ChatUpdateMessage(
threadMerge = try {
val event = ThreadMergeEvent.ADAPTER.decode(Base64.decodeOrThrow(this.body))
ThreadMergeChatUpdate(event.previousE164.e164ToLong()!!)
} catch (e: IOException) {
ThreadMergeChatUpdate()
}
)
}
private fun BackupMessageRecord.toGroupUpdate(): ChatUpdateMessage? {
val groupChange = this.messageExtras?.gv2UpdateDescription?.groupChangeUpdate
return if (groupChange != null) {
ChatUpdateMessage(
groupChange = groupChange
)
} else if (this.body != null) {
try {
val decoded: ByteArray = Base64.decode(this.body)
val context = DecryptedGroupV2Context.ADAPTER.decode(decoded)
ChatUpdateMessage(
groupChange = GroupsV2UpdateMessageConverter.translateDecryptedChange(selfIds = SignalStore.account().getServiceIds(), context)
)
} catch (e: IOException) {
null
}
} else {
null
}
}
private fun BackupMessageRecord.toCallUpdate(): ChatUpdateMessage? {
val call = calls.getCallByMessageId(this.id)
return if (call != null) {
call.toCallUpdate()
} else {
when {
MessageTypes.isMissedAudioCall(this.type) -> {
ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.AUDIO_CALL,
state = IndividualCall.State.MISSED,
direction = IndividualCall.Direction.INCOMING
)
)
}
MessageTypes.isMissedVideoCall(this.type) -> {
ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.VIDEO_CALL,
state = IndividualCall.State.MISSED,
direction = IndividualCall.Direction.INCOMING
)
)
}
MessageTypes.isIncomingAudioCall(this.type) -> {
ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.AUDIO_CALL,
state = IndividualCall.State.ACCEPTED,
direction = IndividualCall.Direction.INCOMING
)
)
}
MessageTypes.isIncomingVideoCall(this.type) -> {
ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.VIDEO_CALL,
state = IndividualCall.State.ACCEPTED,
direction = IndividualCall.Direction.INCOMING
)
)
}
MessageTypes.isOutgoingAudioCall(this.type) -> {
ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.AUDIO_CALL,
state = IndividualCall.State.ACCEPTED,
direction = IndividualCall.Direction.OUTGOING
)
)
}
MessageTypes.isOutgoingVideoCall(this.type) -> {
ChatUpdateMessage(
individualCall = IndividualCall(
type = IndividualCall.Type.VIDEO_CALL,
state = IndividualCall.State.ACCEPTED,
direction = IndividualCall.Direction.OUTGOING
)
)
}
MessageTypes.isGroupCall(this.type) -> {
try {
val groupCallUpdateDetails = GroupCallUpdateDetailsUtil.parse(this.body)
ChatUpdateMessage(
groupCall = GroupCall(
state = GroupCall.State.GENERIC,
startedCallRecipientId = recipients.getByAci(ACI.from(UuidUtil.parseOrThrow(groupCallUpdateDetails.startedCallUuid))).getOrNull()?.toLong(),
startedCallTimestamp = groupCallUpdateDetails.startedCallTimestamp,
endedCallTimestamp = groupCallUpdateDetails.endedCallTimestamp
)
)
} catch (exception: IOException) {
null
}
}
else -> {
null
}
}
}
}
private fun CallTable.Call.toCallUpdate(): ChatUpdateMessage? {
return if (this.type == CallTable.Type.GROUP_CALL) {
ChatUpdateMessage(
groupCall = GroupCall(
callId = this.messageId,
state = when (this.event) {
CallTable.Event.MISSED -> GroupCall.State.MISSED
CallTable.Event.ONGOING -> GroupCall.State.GENERIC
CallTable.Event.ACCEPTED -> GroupCall.State.ACCEPTED
CallTable.Event.NOT_ACCEPTED -> GroupCall.State.GENERIC
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> GroupCall.State.MISSED_NOTIFICATION_PROFILE
CallTable.Event.GENERIC_GROUP_CALL -> GroupCall.State.GENERIC
CallTable.Event.JOINED -> GroupCall.State.JOINED
CallTable.Event.RINGING -> GroupCall.State.RINGING
CallTable.Event.DECLINED -> GroupCall.State.DECLINED
CallTable.Event.OUTGOING_RING -> GroupCall.State.OUTGOING_RING
CallTable.Event.DELETE -> return null
},
ringerRecipientId = this.ringerRecipient?.toLong(),
startedCallRecipientId = this.ringerRecipient?.toLong(),
startedCallTimestamp = this.timestamp
)
)
} else if (this.type != CallTable.Type.AD_HOC_CALL) {
ChatUpdateMessage(
individualCall = IndividualCall(
callId = this.callId,
type = if (this.type == CallTable.Type.VIDEO_CALL) IndividualCall.Type.VIDEO_CALL else IndividualCall.Type.AUDIO_CALL,
direction = if (this.direction == CallTable.Direction.INCOMING) IndividualCall.Direction.INCOMING else IndividualCall.Direction.OUTGOING,
state = when (this.event) {
CallTable.Event.MISSED -> IndividualCall.State.MISSED
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> IndividualCall.State.MISSED_NOTIFICATION_PROFILE
CallTable.Event.ACCEPTED -> IndividualCall.State.ACCEPTED
CallTable.Event.NOT_ACCEPTED -> IndividualCall.State.NOT_ACCEPTED
else -> IndividualCall.State.UNKNOWN_STATE
},
startedCallTimestamp = this.timestamp
)
)
} else {
null
}
}
private fun BackupMessageRecord.toPaymentNotificationUpdate(): PaymentNotification {
val paymentUuid = UuidUtil.parseOrNull(this.body)
val payment = if (paymentUuid != null) {
SignalDatabase.payments.getPayment(paymentUuid)
} else {
null
}
return if (payment == null) {
PaymentNotification()
} else {
PaymentNotification(
amountMob = payment.amount.serializeAmountString(),
feeMob = payment.fee.serializeAmountString(),
note = payment.note,
transactionDetails = payment.getTransactionDetails()
)
}
}
private fun BackupMessageRecord.toStandardMessage(reactionRecords: List<ReactionRecord>?, mentions: List<Mention>?, attachments: List<DatabaseAttachment>?): StandardMessage {
val text = if (body == null) {
null
@ -454,7 +500,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
quote = this.toQuote(quotedAttachments),
text = text,
attachments = messageAttachments.toBackupAttachments(),
// TODO Link previews!
// TODO [backup] Link previews!
linkPreview = emptyList(),
longText = null,
reactions = reactionRecords.toBackupReactions()
@ -480,7 +526,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
}
}
private fun BackupMessageRecord.toBackupGiftBadge(): BackupGiftBadge {
private fun BackupMessageRecord.toGiftBadgeUpdate(): BackupGiftBadge {
val giftBadge = try {
GiftBadge.ADAPTER.decode(Base64.decode(this.body ?: ""))
} catch (e: IOException) {
@ -499,6 +545,20 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
)
}
private fun DatabaseAttachment.toStickerMessage(reactions: List<ReactionRecord>?): StickerMessage {
val stickerLocator = this.stickerLocator!!
return StickerMessage(
sticker = Sticker(
packId = Hex.fromStringCondensed(stickerLocator.packId).toByteString(),
packKey = Hex.fromStringCondensed(stickerLocator.packKey).toByteString(),
stickerId = stickerLocator.stickerId,
emoji = stickerLocator.emoji,
data_ = this.toBackupAttachment().pointer
),
reactions = reactions.toBackupReactions()
)
}
private fun List<DatabaseAttachment>.toBackupQuoteAttachments(): List<Quote.QuotedAttachment> {
return this.map { attachment ->
Quote.QuotedAttachment(
@ -747,6 +807,28 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
}
}
private fun Long.isSmsType(): Boolean {
if (MessageTypes.isSecureType(this)) {
return false
}
if (MessageTypes.isCallLog(this)) {
return false
}
return MessageTypes.isOutgoingMessageType(this) || MessageTypes.isInboxType(this)
}
private fun String.e164ToLong(): Long? {
val fixed = if (this.startsWith("+")) {
this.substring(1)
} else {
this
}
return fixed.toLongOrNull()
}
private fun Cursor.toBackupMessageRecord(): BackupMessageRecord {
return BackupMessageRecord(
id = this.requireLong(MessageTable.ID),