mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-01-30 10:54:58 +01:00
Thread awareness, map threads events to replies
This commit is contained in:
parent
a7d5c6a437
commit
8f0074911a
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.network
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.network.ApiInterceptorListener
|
||||
import org.matrix.android.sdk.api.network.ApiPath
|
||||
import org.matrix.android.sdk.internal.di.MatrixScope
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* The goal of this interceptor is to map thread events to be handled as replies.
|
||||
* The interceptor is responsible for mapping a thread event:
|
||||
* "m.relates_to":{
|
||||
* "event_id":"$eventId",
|
||||
* "rel_type":"io.element.thread"
|
||||
* }
|
||||
* to an equivalent reply event:
|
||||
* m.relates_to":{
|
||||
* "m.in_reply_to":{
|
||||
* "event_id":"$eventId"
|
||||
* }
|
||||
*/
|
||||
@MatrixScope
|
||||
internal class ThreadToReplyMapInterceptor @Inject constructor() : Interceptor {
|
||||
|
||||
init {
|
||||
Timber.d("MapThreadToReplyInterceptor.init")
|
||||
}
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
val response = chain.proceed(request)
|
||||
|
||||
if (isSyncRequest(request)) {
|
||||
Timber.i(" ------> found SYNC REQUEST")
|
||||
|
||||
return response.body?.let {
|
||||
val contentType = it.contentType()
|
||||
val rawBody = it.string()
|
||||
Timber.i(" ------> $rawBody")
|
||||
|
||||
if(rawBody.contains("\"rel_type\":\"io.element.thread\"")){
|
||||
Timber.i(" ------> Thread found")
|
||||
val start = rawBody.indexOf("\"rel_type\":\"io.element.thread\"") - "\"m.relates_to\":{\"event_id\":\"-GoMTnxkfmZczOPvbjcK43WqNib3wiJVaeO_vRxwHIDA\",\"".length +1
|
||||
val end = rawBody.indexOf("\"rel_type\":\"io.element.thread\"") + "\"rel_type\":\"io.element.thread\"".length +2
|
||||
val substr = rawBody.subSequence(start,end)
|
||||
val newRaw = rawBody.replaceRange(start,end,"\"m.relates_to\":{\"m.in_reply_to\":{\"event_id\":\"\$HDddlX2bJQmVS0bN5R9HDzcrGDap18b3cFDDYjTjctc\"}},")
|
||||
Timber.i(" ------> ${substr}")
|
||||
Timber.i(" ------> new raw $newRaw")
|
||||
val newBody = newRaw.toResponseBody(contentType)
|
||||
response.newBuilder().body(newBody).build()
|
||||
|
||||
}else{
|
||||
val newBody = rawBody.toResponseBody(contentType)
|
||||
response.newBuilder().body(newBody).build()
|
||||
}
|
||||
} ?: response
|
||||
|
||||
// response.peekBody(Long.MAX_VALUE).string().let { networkResponse ->
|
||||
// Timber.i(" ------> ThreadToReplyMapInterceptor $networkResponse")
|
||||
// }
|
||||
}
|
||||
|
||||
// val path = request.url.encodedPath
|
||||
// if(path.contains("/sync/")){
|
||||
// Timber.i("-----> SYNC REQUEST --> $responseBody")
|
||||
//
|
||||
//
|
||||
// }
|
||||
|
||||
// val body = ResponseBody.create()
|
||||
// val newResponse = response.newBuilder().body(body)
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the request is a sync request, false otherwise
|
||||
* Example of a sync request:
|
||||
* https://matrix-client.matrix.org/_matrix/client/r0/sync?filter=0&set_presence=online&t...
|
||||
*/
|
||||
private fun isSyncRequest(request: Request): Boolean =
|
||||
ApiPath.SYNC.method == request.method && request.url.encodedPath.contains(ApiPath.SYNC.path)
|
||||
}
|
@ -76,6 +76,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||
private val cryptoService: DefaultCryptoService,
|
||||
private val roomMemberEventHandler: RoomMemberEventHandler,
|
||||
private val roomTypingUsersHandler: RoomTypingUsersHandler,
|
||||
private val threadsAwarenessHandler: ThreadsAwarenessHandler,
|
||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||
@UserId private val userId: String,
|
||||
private val timelineInput: TimelineInput) {
|
||||
@ -366,6 +367,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
|
||||
decryptIfNeeded(event, roomId)
|
||||
}
|
||||
|
||||
threadsAwarenessHandler.handleIfNeeded(realm, roomId, event, ::decryptIfNeeded)
|
||||
|
||||
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
|
||||
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
|
||||
if (event.stateKey != null) {
|
||||
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.sync.handler.room
|
||||
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.database.mapper.EventMapper
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomTagEntity
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This handler is responsible for a smooth threads migration. It will map all incoming
|
||||
* threads as replies. So a device without threads enabled/updated will be able to view
|
||||
* threads response as replies to the orighinal message
|
||||
*/
|
||||
internal class ThreadsAwarenessHandler @Inject constructor(
|
||||
private val permalinkFactory: PermalinkFactory
|
||||
) {
|
||||
|
||||
fun handleIfNeeded(realm: Realm,
|
||||
roomId: String,
|
||||
event: Event,
|
||||
decryptIfNeeded: (event: Event, roomId: String) -> Unit) {
|
||||
|
||||
if (!isThreadEvent(event)) return
|
||||
val rootThreadEventId = getRootThreadEventId(event) ?: return
|
||||
val payload = event.mxDecryptionResult?.payload?.toMutableMap() ?: return
|
||||
val body = getValueFromPayload(payload, "body") ?: return
|
||||
val msgType = getValueFromPayload(payload, "msgtype") ?: return
|
||||
val rootThreadEventEntity = EventEntity.where(realm, eventId = rootThreadEventId).findFirst() ?: return
|
||||
val rootThreadEvent = EventMapper.map(rootThreadEventEntity)
|
||||
val rootThreadEventSenderId = rootThreadEvent.senderId ?: return
|
||||
val rootThreadEventEventId = rootThreadEvent.eventId ?: return
|
||||
|
||||
Timber.i("------> Thread event detected!")
|
||||
|
||||
if (rootThreadEvent.isEncrypted()) {
|
||||
decryptIfNeeded(rootThreadEvent, roomId)
|
||||
}
|
||||
|
||||
val rootThreadEventBody = getValueFromPayload(rootThreadEvent.mxDecryptionResult?.payload?.toMutableMap(),"body")
|
||||
|
||||
val permalink = permalinkFactory.createPermalink(roomId, rootThreadEventEventId, false)
|
||||
val userLink = permalinkFactory.createPermalink(rootThreadEventSenderId, false) ?: ""
|
||||
|
||||
val replyFormatted = LocalEchoEventFactory.REPLY_PATTERN.format(
|
||||
permalink,
|
||||
userLink,
|
||||
rootThreadEventSenderId,
|
||||
// Remove inner mx_reply tags if any
|
||||
rootThreadEventBody,
|
||||
body)
|
||||
|
||||
val messageTextContent = MessageTextContent(
|
||||
msgType = msgType,
|
||||
format = MessageFormat.FORMAT_MATRIX_HTML,
|
||||
body = body,
|
||||
formattedBody = replyFormatted
|
||||
).toContent()
|
||||
|
||||
payload["content"] = messageTextContent
|
||||
|
||||
event.mxDecryptionResult = event.mxDecryptionResult?.copy(payload = payload )
|
||||
|
||||
}
|
||||
|
||||
private fun isThreadEvent(event: Event): Boolean =
|
||||
event.content.toModel<MessageRelationContent>()?.relatesTo?.type == "io.element.thread"
|
||||
|
||||
private fun getRootThreadEventId(event: Event): String? =
|
||||
event.content.toModel<MessageRelationContent>()?.relatesTo?.eventId
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun getValueFromPayload(payload: JsonDict?, key: String): String? {
|
||||
val content = payload?.get("content") as? JsonDict
|
||||
return content?.get(key) as? String
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user