feat: Remove replyToMessageId extraction and related logic from MessageRepository
This commit is contained in:
@@ -662,21 +662,145 @@ object MessageCrypto {
|
||||
|
||||
/**
|
||||
* Converts bytes to string mimicking JavaScript's Buffer.toString('utf-8') behavior.
|
||||
* Invalid UTF-8 sequences are replaced with U+FFFD (replacement character).
|
||||
* Uses WHATWG UTF-8 decoder algorithm where each invalid byte produces exactly ONE U+FFFD.
|
||||
* This is critical for cross-platform compatibility with React Native!
|
||||
*/
|
||||
private fun bytesToJsUtf8String(bytes: ByteArray): String {
|
||||
// CharsetDecoder with REPLACE action mimics JS behavior
|
||||
val decoder = Charsets.UTF_8.newDecoder()
|
||||
decoder.onMalformedInput(java.nio.charset.CodingErrorAction.REPLACE)
|
||||
decoder.onUnmappableCharacter(java.nio.charset.CodingErrorAction.REPLACE)
|
||||
decoder.replaceWith("\uFFFD")
|
||||
val result = StringBuilder()
|
||||
var i = 0
|
||||
|
||||
return try {
|
||||
decoder.decode(java.nio.ByteBuffer.wrap(bytes)).toString()
|
||||
} catch (e: Exception) {
|
||||
// Fallback to standard UTF-8 (shouldn't happen with REPLACE action)
|
||||
String(bytes, Charsets.UTF_8)
|
||||
while (i < bytes.size) {
|
||||
val b0 = bytes[i].toInt() and 0xFF
|
||||
|
||||
when {
|
||||
// ASCII (0x00-0x7F) - single byte
|
||||
b0 <= 0x7F -> {
|
||||
result.append(b0.toChar())
|
||||
i++
|
||||
}
|
||||
|
||||
// Continuation byte without starter (0x80-0xBF) - invalid
|
||||
b0 <= 0xBF -> {
|
||||
result.append('\uFFFD')
|
||||
i++
|
||||
}
|
||||
|
||||
// 2-byte sequence (0xC0-0xDF)
|
||||
b0 <= 0xDF -> {
|
||||
if (i + 1 >= bytes.size) {
|
||||
// Truncated - emit replacement for this byte
|
||||
result.append('\uFFFD')
|
||||
i++
|
||||
} else {
|
||||
val b1 = bytes[i + 1].toInt() and 0xFF
|
||||
if (b1 and 0xC0 != 0x80) {
|
||||
// Invalid continuation - emit replacement for starter only
|
||||
result.append('\uFFFD')
|
||||
i++
|
||||
} else {
|
||||
val codePoint = ((b0 and 0x1F) shl 6) or (b1 and 0x3F)
|
||||
// Check for overlong encoding (should be >= 0x80 for 2-byte)
|
||||
if (codePoint < 0x80 || b0 == 0xC0 || b0 == 0xC1) {
|
||||
// Overlong - emit replacement for each byte
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
} else {
|
||||
result.append(codePoint.toChar())
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3-byte sequence (0xE0-0xEF)
|
||||
b0 <= 0xEF -> {
|
||||
if (i + 2 >= bytes.size) {
|
||||
// Truncated
|
||||
val remaining = bytes.size - i
|
||||
repeat(remaining) { result.append('\uFFFD') }
|
||||
i = bytes.size
|
||||
} else {
|
||||
val b1 = bytes[i + 1].toInt() and 0xFF
|
||||
val b2 = bytes[i + 2].toInt() and 0xFF
|
||||
|
||||
if (b1 and 0xC0 != 0x80) {
|
||||
// Invalid first continuation
|
||||
result.append('\uFFFD')
|
||||
i++
|
||||
} else if (b2 and 0xC0 != 0x80) {
|
||||
// Invalid second continuation - emit for first two bytes
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
i += 2
|
||||
} else {
|
||||
val codePoint = ((b0 and 0x0F) shl 12) or ((b1 and 0x3F) shl 6) or (b2 and 0x3F)
|
||||
// Check for overlong (should be >= 0x800 for 3-byte) and surrogates
|
||||
if (codePoint < 0x800 || (codePoint >= 0xD800 && codePoint <= 0xDFFF)) {
|
||||
// Invalid - emit replacement for each byte
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
} else {
|
||||
result.append(codePoint.toChar())
|
||||
}
|
||||
i += 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4-byte sequence (0xF0-0xF7)
|
||||
b0 <= 0xF7 -> {
|
||||
if (i + 3 >= bytes.size) {
|
||||
// Truncated
|
||||
val remaining = bytes.size - i
|
||||
repeat(remaining) { result.append('\uFFFD') }
|
||||
i = bytes.size
|
||||
} else {
|
||||
val b1 = bytes[i + 1].toInt() and 0xFF
|
||||
val b2 = bytes[i + 2].toInt() and 0xFF
|
||||
val b3 = bytes[i + 3].toInt() and 0xFF
|
||||
|
||||
if (b1 and 0xC0 != 0x80) {
|
||||
result.append('\uFFFD')
|
||||
i++
|
||||
} else if (b2 and 0xC0 != 0x80) {
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
i += 2
|
||||
} else if (b3 and 0xC0 != 0x80) {
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
i += 3
|
||||
} else {
|
||||
val codePoint = ((b0 and 0x07) shl 18) or ((b1 and 0x3F) shl 12) or
|
||||
((b2 and 0x3F) shl 6) or (b3 and 0x3F)
|
||||
// Check for overlong (should be >= 0x10000) and max Unicode
|
||||
if (codePoint < 0x10000 || codePoint > 0x10FFFF) {
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
result.append('\uFFFD')
|
||||
} else {
|
||||
// Encode as surrogate pair
|
||||
val adjusted = codePoint - 0x10000
|
||||
result.append((0xD800 + (adjusted shr 10)).toChar())
|
||||
result.append((0xDC00 + (adjusted and 0x3FF)).toChar())
|
||||
}
|
||||
i += 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid starter byte (0xF8-0xFF)
|
||||
else -> {
|
||||
result.append('\uFFFD')
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -284,9 +284,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
privateKey
|
||||
)
|
||||
|
||||
// Извлекаем replyToMessageId из attachments
|
||||
val replyToMessageId = extractReplyToMessageId(packet.attachments)
|
||||
|
||||
// 🔒 Шифруем plainMessage с использованием приватного ключа
|
||||
val encryptedPlainMessage = CryptoManager.encryptWithPassword(plainText, privateKey)
|
||||
|
||||
@@ -304,7 +301,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
messageId = messageId, // 🔥 Используем сгенерированный messageId!
|
||||
plainMessage = encryptedPlainMessage, // 🔒 Зашифрованный текст
|
||||
attachments = attachmentsJson,
|
||||
replyToMessageId = replyToMessageId, // 🔥 Добавляем replyToMessageId из attachments!
|
||||
dialogKey = dialogKey
|
||||
)
|
||||
|
||||
@@ -556,32 +552,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
plainMessage
|
||||
}
|
||||
|
||||
// Десериализуем attachments из JSON
|
||||
val attachmentsList = try {
|
||||
if (attachments.isNotEmpty() && attachments != "[]") {
|
||||
val jsonArray = JSONArray(attachments)
|
||||
(0 until jsonArray.length()).mapNotNull { i ->
|
||||
try {
|
||||
val obj = jsonArray.getJSONObject(i)
|
||||
MessageAttachment(
|
||||
id = obj.optString("id", ""),
|
||||
blob = obj.optString("blob", ""),
|
||||
type = AttachmentType.fromInt(obj.optInt("type", 0)),
|
||||
preview = obj.optString("preview", ""),
|
||||
width = obj.optInt("width", 0),
|
||||
height = obj.optInt("height", 0)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
return Message(
|
||||
id = id,
|
||||
messageId = messageId,
|
||||
@@ -592,7 +562,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
isFromMe = fromMe == 1,
|
||||
isRead = read == 1,
|
||||
deliveryStatus = DeliveryStatus.fromInt(delivered),
|
||||
attachments = attachmentsList, // Десериализованные attachments
|
||||
replyToMessageId = replyToMessageId
|
||||
)
|
||||
}
|
||||
@@ -630,39 +599,6 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
return jsonArray.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлечение replyToMessageId из attachments
|
||||
* Ищет MESSAGES (type=1) attachment и парсит message_id
|
||||
*/
|
||||
private fun extractReplyToMessageId(attachments: List<MessageAttachment>): String? {
|
||||
if (attachments.isEmpty()) return null
|
||||
|
||||
for (attachment in attachments) {
|
||||
if (attachment.type == AttachmentType.MESSAGES) {
|
||||
try {
|
||||
// Берем blob или preview
|
||||
val dataJson = attachment.blob.ifEmpty { attachment.preview }
|
||||
if (dataJson.isEmpty() || dataJson.contains(":")) {
|
||||
// Пропускаем зашифрованные старые сообщения
|
||||
continue
|
||||
}
|
||||
|
||||
val messagesArray = JSONArray(dataJson)
|
||||
if (messagesArray.length() > 0) {
|
||||
val replyMessage = messagesArray.getJSONObject(0)
|
||||
val replyMessageId = replyMessage.optString("message_id", "")
|
||||
if (replyMessageId.isNotEmpty()) {
|
||||
return replyMessageId
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Сериализация attachments в JSON с расшифровкой MESSAGES blob
|
||||
* Для MESSAGES типа blob расшифровывается и сохраняется в preview (как в RN)
|
||||
|
||||
Reference in New Issue
Block a user