Merge pull request #7988 from vector-im/yostyle/msc3912

Implement MSC3912 to delete server side all the data of a deleted voice broadcast
This commit is contained in:
Benoit Marty 2023-01-25 10:32:21 +01:00 committed by GitHub
commit c802e2d0f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 221 additions and 72 deletions

1
changelog.d/7967.feature Normal file
View File

@ -0,0 +1 @@
[Voice Broadcast] Use MSC3912 to delete server side all the related events

1
changelog.d/7988.sdk Normal file
View File

@ -0,0 +1 @@
Implement [MSC3912](https://github.com/matrix-org/matrix-spec-proposals/pull/3912): Relation-based redactions

View File

@ -75,6 +75,11 @@ data class HomeServerCapabilities(
* True if the home server supports remote toggle of Pusher for a given device. * True if the home server supports remote toggle of Pusher for a given device.
*/ */
val canRemotelyTogglePushNotificationsOfDevices: Boolean = false, val canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
/**
* True if the home server supports event redaction with relations.
*/
var canRedactEventWithRelations: Boolean = false,
) { ) {
enum class RoomCapabilitySupport { enum class RoomCapabilitySupport {

View File

@ -156,11 +156,12 @@ interface SendService {
/** /**
* Redact (delete) the given event. * Redact (delete) the given event.
* @param event The event to redact * @param event the event to redact
* @param reason Optional reason string * @param reason optional reason string
* @param withRelations the list of relation types to redact with this event
* @param additionalContent additional content to put in the event content * @param additionalContent additional content to put in the event content
*/ */
fun redactEvent(event: Event, reason: String?, additionalContent: Content? = null): Cancelable fun redactEvent(event: Event, reason: String?, withRelations: List<String>? = null, additionalContent: Content? = null): Cancelable
/** /**
* Schedule this message to be resent. * Schedule this message to be resent.

View File

@ -58,6 +58,8 @@ private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882"
private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771" private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771"
private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773" private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773"
private const val FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881 = "org.matrix.msc3881" private const val FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881 = "org.matrix.msc3881"
private const val FEATURE_EVENT_REDACTION_WITH_RELATIONS = "org.matrix.msc3912"
private const val FEATURE_EVENT_REDACTION_WITH_RELATIONS_STABLE = "org.matrix.msc3912.stable"
/** /**
* Return true if the SDK supports this homeserver version. * Return true if the SDK supports this homeserver version.
@ -153,3 +155,13 @@ private fun Versions.getMaxVersion(): HomeServerVersion {
internal fun Versions.doesServerSupportRemoteToggleOfPushNotifications(): Boolean { internal fun Versions.doesServerSupportRemoteToggleOfPushNotifications(): Boolean {
return unstableFeatures?.get(FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881).orFalse() return unstableFeatures?.get(FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881).orFalse()
} }
/**
* Indicate if the server supports MSC3912: https://github.com/matrix-org/matrix-spec-proposals/pull/3912.
*
* @return true if event redaction with relations is supported
*/
internal fun Versions.doesServerSupportRedactEventWithRelations(): Boolean {
return unstableFeatures?.get(FEATURE_EVENT_REDACTION_WITH_RELATIONS).orFalse() ||
unstableFeatures?.get(FEATURE_EVENT_REDACTION_WITH_RELATIONS_STABLE).orFalse()
}

View File

@ -15,9 +15,12 @@
*/ */
package org.matrix.android.sdk.internal.crypto.tasks package org.matrix.android.sdk.internal.crypto.tasks
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -26,22 +29,34 @@ internal interface RedactEventTask : Task<RedactEventTask.Params, String> {
val txID: String, val txID: String,
val roomId: String, val roomId: String,
val eventId: String, val eventId: String,
val reason: String? val reason: String?,
val withRelations: List<String>?,
) )
} }
internal class DefaultRedactEventTask @Inject constructor( internal class DefaultRedactEventTask @Inject constructor(
private val roomAPI: RoomAPI, private val roomAPI: RoomAPI,
private val globalErrorReceiver: GlobalErrorReceiver private val globalErrorReceiver: GlobalErrorReceiver,
private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
) : RedactEventTask { ) : RedactEventTask {
override suspend fun execute(params: RedactEventTask.Params): String { override suspend fun execute(params: RedactEventTask.Params): String {
val withRelations = if (homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.canRedactEventWithRelations.orFalse() &&
!params.withRelations.isNullOrEmpty()) {
params.withRelations
} else {
null
}
val response = executeRequest(globalErrorReceiver) { val response = executeRequest(globalErrorReceiver) {
roomAPI.redactEvent( roomAPI.redactEvent(
txId = params.txID, txId = params.txID,
roomId = params.roomId, roomId = params.roomId,
eventId = params.eventId, eventId = params.eventId,
reason = if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) body = EventRedactBody(
reason = params.reason,
withRelations = withRelations,
)
) )
} }
return response.eventId return response.eventId

View File

@ -65,6 +65,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049
import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject import javax.inject.Inject
@ -73,7 +74,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer private val normalizer: Normalizer
) : MatrixRealmMigration( ) : MatrixRealmMigration(
dbName = "Session", dbName = "Session",
schemaVersion = 48L, schemaVersion = 49L,
) { ) {
/** /**
* Forces all RealmSessionStoreMigration instances to be equal. * Forces all RealmSessionStoreMigration instances to be equal.
@ -131,5 +132,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 46) MigrateSessionTo046(realm).perform() if (oldVersion < 46) MigrateSessionTo046(realm).perform()
if (oldVersion < 47) MigrateSessionTo047(realm).perform() if (oldVersion < 47) MigrateSessionTo047(realm).perform()
if (oldVersion < 48) MigrateSessionTo048(realm).perform() if (oldVersion < 48) MigrateSessionTo048(realm).perform()
if (oldVersion < 49) MigrateSessionTo049(realm).perform()
} }
} }

View File

@ -47,6 +47,7 @@ internal object HomeServerCapabilitiesMapper {
canLoginWithQrCode = entity.canLoginWithQrCode, canLoginWithQrCode = entity.canLoginWithQrCode,
canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications, canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications,
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices, canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
canRedactEventWithRelations = entity.canRedactEventWithRelations,
) )
} }

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 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.database.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateSessionTo049(realm: DynamicRealm) : RealmMigrator(realm, 49) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, Boolean::class.java)
?.transform { obj ->
obj.set(HomeServerCapabilitiesEntityFields.CAN_REDACT_EVENT_WITH_RELATIONS, false)
}
?.forceRefreshOfHomeServerCapabilities()
}
}

View File

@ -34,6 +34,7 @@ internal open class HomeServerCapabilitiesEntity(
var canLoginWithQrCode: Boolean = false, var canLoginWithQrCode: Boolean = false,
var canUseThreadReadReceiptsAndNotifications: Boolean = false, var canUseThreadReadReceiptsAndNotifications: Boolean = false,
var canRemotelyTogglePushNotificationsOfDevices: Boolean = false, var canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
var canRedactEventWithRelations: Boolean = false,
) : RealmObject() { ) : RealmObject() {
companion object companion object

View File

@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices
import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin
import org.matrix.android.sdk.internal.auth.version.doesServerSupportRedactEventWithRelations
import org.matrix.android.sdk.internal.auth.version.doesServerSupportRemoteToggleOfPushNotifications import org.matrix.android.sdk.internal.auth.version.doesServerSupportRemoteToggleOfPushNotifications
import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications
import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads
@ -154,6 +155,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
getVersionResult.doesServerSupportQrCodeLogin() getVersionResult.doesServerSupportQrCodeLogin()
homeServerCapabilitiesEntity.canRemotelyTogglePushNotificationsOfDevices = homeServerCapabilitiesEntity.canRemotelyTogglePushNotificationsOfDevices =
getVersionResult.doesServerSupportRemoteToggleOfPushNotifications() getVersionResult.doesServerSupportRemoteToggleOfPushNotifications()
homeServerCapabilitiesEntity.canRedactEventWithRelations =
getVersionResult.doesServerSupportRedactEventWithRelations()
} }
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {

View File

@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
import org.matrix.android.sdk.internal.session.room.relation.threads.ThreadSummariesResponse import org.matrix.android.sdk.internal.session.room.relation.threads.ThreadSummariesResponse
import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody
import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.session.room.send.SendResponse
import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody
import org.matrix.android.sdk.internal.session.room.tags.TagBody import org.matrix.android.sdk.internal.session.room.tags.TagBody
import org.matrix.android.sdk.internal.session.room.timeline.EventContextResponse import org.matrix.android.sdk.internal.session.room.timeline.EventContextResponse
import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse
@ -61,7 +62,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms")
suspend fun publicRooms( suspend fun publicRooms(
@Query("server") server: String?, @Query("server") server: String?,
@Body publicRoomsParams: PublicRoomsParams @Body publicRoomsParams: PublicRoomsParams,
): PublicRoomsResponse ): PublicRoomsResponse
/** /**
@ -91,7 +92,7 @@ internal interface RoomAPI {
@Query("from") from: String, @Query("from") from: String,
@Query("dir") dir: String, @Query("dir") dir: String,
@Query("limit") limit: Int?, @Query("limit") limit: Int?,
@Query("filter") filter: String? @Query("filter") filter: String?,
): PaginationResponse ): PaginationResponse
/** /**
@ -107,7 +108,7 @@ internal interface RoomAPI {
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Query("at") syncToken: String?, @Query("at") syncToken: String?,
@Query("membership") membership: Membership?, @Query("membership") membership: Membership?,
@Query("not_membership") notMembership: Membership? @Query("not_membership") notMembership: Membership?,
): RoomMembersResponse ): RoomMembersResponse
/** /**
@ -123,7 +124,7 @@ internal interface RoomAPI {
@Path("txId") txId: String, @Path("txId") txId: String,
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("eventType") eventType: String, @Path("eventType") eventType: String,
@Body content: Content? @Body content: Content?,
): SendResponse ): SendResponse
/** /**
@ -139,7 +140,7 @@ internal interface RoomAPI {
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("eventId") eventId: String, @Path("eventId") eventId: String,
@Query("limit") limit: Int, @Query("limit") limit: Int,
@Query("filter") filter: String? = null @Query("filter") filter: String? = null,
): EventContextResponse ): EventContextResponse
/** /**
@ -151,7 +152,7 @@ internal interface RoomAPI {
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}") @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}")
suspend fun getEvent( suspend fun getEvent(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("eventId") eventId: String @Path("eventId") eventId: String,
): Event ): Event
/** /**
@ -163,7 +164,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers")
suspend fun sendReadMarker( suspend fun sendReadMarker(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Body markers: Map<String, String> @Body markers: Map<String, String>,
) )
/** /**
@ -174,7 +175,7 @@ internal interface RoomAPI {
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("receiptType") receiptType: String, @Path("receiptType") receiptType: String,
@Path("eventId") eventId: String, @Path("eventId") eventId: String,
@Body body: ReadBody @Body body: ReadBody,
) )
/** /**
@ -187,7 +188,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
suspend fun invite( suspend fun invite(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Body body: InviteBody @Body body: InviteBody,
) )
/** /**
@ -199,7 +200,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
suspend fun invite3pid( suspend fun invite3pid(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Body body: ThreePidInviteBody @Body body: ThreePidInviteBody,
) )
/** /**
@ -213,7 +214,7 @@ internal interface RoomAPI {
suspend fun sendStateEvent( suspend fun sendStateEvent(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("state_event_type") stateEventType: String, @Path("state_event_type") stateEventType: String,
@Body params: JsonDict @Body params: JsonDict,
): SendResponse ): SendResponse
/** /**
@ -229,7 +230,7 @@ internal interface RoomAPI {
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("state_event_type") stateEventType: String, @Path("state_event_type") stateEventType: String,
@Path("state_key") stateKey: String, @Path("state_key") stateKey: String,
@Body params: JsonDict @Body params: JsonDict,
): SendResponse ): SendResponse
/** /**
@ -257,7 +258,7 @@ internal interface RoomAPI {
@Path("eventType") eventType: String, @Path("eventType") eventType: String,
@Query("from") from: String? = null, @Query("from") from: String? = null,
@Query("to") to: String? = null, @Query("to") to: String? = null,
@Query("limit") limit: Int? = null @Query("limit") limit: Int? = null,
): RelationsResponse ): RelationsResponse
/** /**
@ -277,7 +278,7 @@ internal interface RoomAPI {
@Path("relationType") relationType: String, @Path("relationType") relationType: String,
@Query("from") from: String? = null, @Query("from") from: String? = null,
@Query("to") to: String? = null, @Query("to") to: String? = null,
@Query("limit") limit: Int? = null @Query("limit") limit: Int? = null,
): RelationsResponse ): RelationsResponse
/** /**
@ -291,7 +292,7 @@ internal interface RoomAPI {
suspend fun join( suspend fun join(
@Path("roomIdOrAlias") roomIdOrAlias: String, @Path("roomIdOrAlias") roomIdOrAlias: String,
@Query("server_name") viaServers: List<String>, @Query("server_name") viaServers: List<String>,
@Body params: JsonDict @Body params: JsonDict,
): JoinRoomResponse ): JoinRoomResponse
/** /**
@ -303,7 +304,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave")
suspend fun leave( suspend fun leave(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Body params: Map<String, String?> @Body params: Map<String, String?>,
) )
/** /**
@ -315,7 +316,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban")
suspend fun ban( suspend fun ban(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Body userIdAndReason: UserIdAndReason @Body userIdAndReason: UserIdAndReason,
) )
/** /**
@ -327,7 +328,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban")
suspend fun unban( suspend fun unban(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Body userIdAndReason: UserIdAndReason @Body userIdAndReason: UserIdAndReason,
) )
/** /**
@ -339,7 +340,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick")
suspend fun kick( suspend fun kick(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Body userIdAndReason: UserIdAndReason @Body userIdAndReason: UserIdAndReason,
) )
/** /**
@ -350,14 +351,14 @@ internal interface RoomAPI {
* @param txId the transaction Id * @param txId the transaction Id
* @param roomId the room id * @param roomId the room id
* @param eventId the event to delete * @param eventId the event to delete
* @param reason json containing reason key {"reason": "Indecent material"} * @param body body containing reason key {"reason": "Indecent material"} and with_relations
*/ */
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}") @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}")
suspend fun redactEvent( suspend fun redactEvent(
@Path("txnId") txId: String, @Path("txnId") txId: String,
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("eventId") eventId: String, @Path("eventId") eventId: String,
@Body reason: Map<String, String> @Body body: EventRedactBody,
): SendResponse ): SendResponse
/** /**
@ -371,7 +372,7 @@ internal interface RoomAPI {
suspend fun reportContent( suspend fun reportContent(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("eventId") eventId: String, @Path("eventId") eventId: String,
@Body body: ReportContentBody @Body body: ReportContentBody,
) )
/** /**
@ -388,7 +389,7 @@ internal interface RoomAPI {
suspend fun sendTypingState( suspend fun sendTypingState(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("userId") userId: String, @Path("userId") userId: String,
@Body body: TypingBody @Body body: TypingBody,
) )
/* /*
@ -403,7 +404,7 @@ internal interface RoomAPI {
@Path("userId") userId: String, @Path("userId") userId: String,
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("tag") tag: String, @Path("tag") tag: String,
@Body body: TagBody @Body body: TagBody,
) )
/** /**
@ -413,7 +414,7 @@ internal interface RoomAPI {
suspend fun deleteTag( suspend fun deleteTag(
@Path("userId") userId: String, @Path("userId") userId: String,
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("tag") tag: String @Path("tag") tag: String,
) )
/** /**
@ -424,7 +425,7 @@ internal interface RoomAPI {
@Path("userId") userId: String, @Path("userId") userId: String,
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("type") type: String, @Path("type") type: String,
@Body content: JsonDict @Body content: JsonDict,
) )
/** /**
@ -437,7 +438,7 @@ internal interface RoomAPI {
suspend fun deleteRoomAccountData( suspend fun deleteRoomAccountData(
@Path("userId") userId: String, @Path("userId") userId: String,
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Path("type") type: String @Path("type") type: String,
) )
/** /**
@ -450,7 +451,7 @@ internal interface RoomAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
suspend fun upgradeRoom( suspend fun upgradeRoom(
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Body body: RoomUpgradeBody @Body body: RoomUpgradeBody,
): RoomUpgradeResponse ): RoomUpgradeResponse
/** /**
@ -462,7 +463,7 @@ internal interface RoomAPI {
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary") @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary")
suspend fun getRoomSummary( suspend fun getRoomSummary(
@Path("roomIdOrAlias") roomidOrAlias: String, @Path("roomIdOrAlias") roomidOrAlias: String,
@Query("via") viaServers: List<String>? @Query("via") viaServers: List<String>?,
): RoomStrippedState ): RoomStrippedState
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/threads") @GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/threads")
@ -470,6 +471,6 @@ internal interface RoomAPI {
@Path("roomId") roomId: String, @Path("roomId") roomId: String,
@Query("include") include: String? = "all", @Query("include") include: String? = "all",
@Query("from") from: String? = null, @Query("from") from: String? = null,
@Query("limit") limit: Int? = null @Query("limit") limit: Int? = null,
): ThreadSummariesResponse ): ThreadSummariesResponse
} }

View File

@ -140,11 +140,11 @@ internal class DefaultSendService @AssistedInject constructor(
.let { sendEvent(it) } .let { sendEvent(it) }
} }
override fun redactEvent(event: Event, reason: String?, additionalContent: Content?): Cancelable { override fun redactEvent(event: Event, reason: String?, withRelations: List<String>?, additionalContent: Content?): Cancelable {
// TODO manage media/attachements? // TODO manage media/attachements?
val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, additionalContent) val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason, withRelations, additionalContent)
.also { createLocalEcho(it) } .also { createLocalEcho(it) }
return eventSenderProcessor.postRedaction(redactionEcho, reason) return eventSenderProcessor.postRedaction(redactionEcho, reason, withRelations)
} }
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable { override fun resendTextMessage(localEcho: TimelineEvent): Cancelable {

View File

@ -70,6 +70,7 @@ import org.matrix.android.sdk.api.util.TextContent
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor
import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
import java.util.UUID import java.util.UUID
@ -795,8 +796,16 @@ internal class LocalEchoEventFactory @Inject constructor(
} }
} }
*/ */
fun createRedactEvent(roomId: String, eventId: String, reason: String?, additionalContent: Content? = null): Event { fun createRedactEvent(roomId: String, eventId: String, reason: String?, withRelations: List<String>? = null, additionalContent: Content? = null): Event {
val localId = LocalEcho.createLocalEchoId() val localId = LocalEcho.createLocalEchoId()
val content = if (reason != null || withRelations != null) {
EventRedactBody(
reason = reason,
withRelations = withRelations,
).toContent().plus(additionalContent.orEmpty())
} else {
additionalContent
}
return Event( return Event(
roomId = roomId, roomId = roomId,
originServerTs = dummyOriginServerTs(), originServerTs = dummyOriginServerTs(),
@ -804,7 +813,7 @@ internal class LocalEchoEventFactory @Inject constructor(
eventId = localId, eventId = localId,
type = EventType.REDACTION, type = EventType.REDACTION,
redacts = eventId, redacts = eventId,
content = reason?.let { mapOf("reason" to it).toContent().plus(additionalContent.orEmpty()) } ?: additionalContent, content = content,
unsignedData = UnsignedData(age = null, transactionId = localId) unsignedData = UnsignedData(age = null, transactionId = localId)
) )
} }
@ -830,10 +839,10 @@ internal class LocalEchoEventFactory @Inject constructor(
createMessageEvent( createMessageEvent(
roomId, roomId,
textContent.toThreadTextContent( textContent.toThreadTextContent(
rootThreadEventId = rootThreadEventId, rootThreadEventId = rootThreadEventId,
latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
msgType = MessageType.MSGTYPE_TEXT msgType = MessageType.MSGTYPE_TEXT
), ),
additionalContent, additionalContent,
) )
} else { } else {

View File

@ -20,8 +20,8 @@ import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
@ -43,27 +43,29 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters, ses
val roomId: String, val roomId: String,
val eventId: String, val eventId: String,
val reason: String?, val reason: String?,
val withRelations: List<String>? = null,
override val lastFailureMessage: String? = null override val lastFailureMessage: String? = null
) : SessionWorkerParams ) : SessionWorkerParams
@Inject lateinit var roomAPI: RoomAPI @Inject lateinit var roomAPI: RoomAPI
@Inject lateinit var globalErrorReceiver: GlobalErrorReceiver @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver
@Inject lateinit var redactEventTask: RedactEventTask
override fun injectWith(injector: SessionComponent) { override fun injectWith(injector: SessionComponent) {
injector.inject(this) injector.inject(this)
} }
override suspend fun doSafeWork(params: Params): Result { override suspend fun doSafeWork(params: Params): Result {
val eventId = params.eventId
return runCatching { return runCatching {
executeRequest(globalErrorReceiver) { redactEventTask.execute(
roomAPI.redactEvent( RedactEventTask.Params(
params.txID, txID = params.txID,
params.roomId, roomId = params.roomId,
eventId, eventId = params.eventId,
if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) reason = params.reason,
) withRelations = params.withRelations,
} )
)
}.fold( }.fold(
{ {
Result.success() Result.success()

View File

@ -0,0 +1,29 @@
/*
* Copyright 2023 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.room.send.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class EventRedactBody(
@Json(name = "reason")
val reason: String? = null,
@Json(name = "org.matrix.msc3912.with_relations")
val withRelations: List<String>? = null,
)

View File

@ -26,9 +26,9 @@ internal interface EventSenderProcessor : SessionLifecycleObserver {
fun postEvent(event: Event, encrypt: Boolean): Cancelable fun postEvent(event: Event, encrypt: Boolean): Cancelable
fun postRedaction(redactionLocalEcho: Event, reason: String?): Cancelable fun postRedaction(redactionLocalEcho: Event, reason: String?, withRelations: List<String>? = null): Cancelable
fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?): Cancelable fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?, withRelations: List<String>? = null): Cancelable
fun postTask(task: QueuedTask): Cancelable fun postTask(task: QueuedTask): Cancelable

View File

@ -101,12 +101,18 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
return postTask(task) return postTask(task)
} }
override fun postRedaction(redactionLocalEcho: Event, reason: String?): Cancelable { override fun postRedaction(redactionLocalEcho: Event, reason: String?, withRelations: List<String>?): Cancelable {
return postRedaction(redactionLocalEcho.eventId!!, redactionLocalEcho.redacts!!, redactionLocalEcho.roomId!!, reason) return postRedaction(redactionLocalEcho.eventId!!, redactionLocalEcho.redacts!!, redactionLocalEcho.roomId!!, reason, withRelations)
} }
override fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?): Cancelable { override fun postRedaction(
val task = queuedTaskFactory.createRedactTask(redactionLocalEchoId, eventToRedactId, roomId, reason) redactionLocalEchoId: String,
eventToRedactId: String,
roomId: String,
reason: String?,
withRelations: List<String>?
): Cancelable {
val task = queuedTaskFactory.createRedactTask(redactionLocalEchoId, eventToRedactId, roomId, reason, withRelations)
return postTask(task) return postTask(task)
} }

View File

@ -19,9 +19,11 @@ package org.matrix.android.sdk.internal.session.room.send.queue
import android.content.Context import android.content.Context
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.session.room.send.model.EventRedactBody
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -107,10 +109,18 @@ internal class QueueMemento @Inject constructor(
info.redactionLocalEcho?.let { localEchoRepository.getUpToDateEcho(it) }?.let { info.redactionLocalEcho?.let { localEchoRepository.getUpToDateEcho(it) }?.let {
localEchoRepository.updateSendState(it.eventId!!, it.roomId, SendState.UNSENT) localEchoRepository.updateSendState(it.eventId!!, it.roomId, SendState.UNSENT)
// try to get reason // try to get reason
val reason = it.content?.get("reason") as? String val body = it.content.toModel<EventRedactBody>()
if (it.redacts != null && it.roomId != null) { if (it.redacts != null && it.roomId != null) {
Timber.d("## Send -Reschedule redact $info") Timber.d("## Send -Reschedule redact $info")
eventProcessor.postTask(queuedTaskFactory.createRedactTask(it.eventId, it.redacts, it.roomId, reason)) eventProcessor.postTask(
queuedTaskFactory.createRedactTask(
redactionLocalEcho = it.eventId,
eventId = it.redacts,
roomId = it.roomId,
reason = body?.reason,
withRelations = body?.withRelations,
)
)
} }
} }
// postTask(queuedTaskFactory.createRedactTask(info.eventToRedactId, info.) // postTask(queuedTaskFactory.createRedactTask(info.eventToRedactId, info.)

View File

@ -43,12 +43,13 @@ internal class QueuedTaskFactory @Inject constructor(
) )
} }
fun createRedactTask(redactionLocalEcho: String, eventId: String, roomId: String, reason: String?): QueuedTask { fun createRedactTask(redactionLocalEcho: String, eventId: String, roomId: String, reason: String?, withRelations: List<String>? = null): QueuedTask {
return RedactQueuedTask( return RedactQueuedTask(
redactionLocalEchoId = redactionLocalEcho, redactionLocalEchoId = redactionLocalEcho,
toRedactEventId = eventId, toRedactEventId = eventId,
roomId = roomId, roomId = roomId,
reason = reason, reason = reason,
withRelations = withRelations,
redactEventTask = redactEventTask, redactEventTask = redactEventTask,
localEchoRepository = localEchoRepository, localEchoRepository = localEchoRepository,
cancelSendTracker = cancelSendTracker cancelSendTracker = cancelSendTracker

View File

@ -26,13 +26,14 @@ internal class RedactQueuedTask(
val redactionLocalEchoId: String, val redactionLocalEchoId: String,
private val roomId: String, private val roomId: String,
private val reason: String?, private val reason: String?,
private val withRelations: List<String>?,
private val redactEventTask: RedactEventTask, private val redactEventTask: RedactEventTask,
private val localEchoRepository: LocalEchoRepository, private val localEchoRepository: LocalEchoRepository,
private val cancelSendTracker: CancelSendTracker private val cancelSendTracker: CancelSendTracker
) : QueuedTask(queueIdentifier = roomId, taskIdentifier = redactionLocalEchoId) { ) : QueuedTask(queueIdentifier = roomId, taskIdentifier = redactionLocalEchoId) {
override suspend fun doExecute() { override suspend fun doExecute() {
redactEventTask.execute(RedactEventTask.Params(redactionLocalEchoId, roomId, toRedactEventId, reason)) redactEventTask.execute(RedactEventTask.Params(redactionLocalEchoId, roomId, toRedactEventId, reason, withRelations))
} }
override fun onTaskFailed() { override fun onTaskFailed() {

View File

@ -20,6 +20,7 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.model.isVoiceBroadcast
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@ -49,3 +50,7 @@ fun TimelineEvent.getVectorLastMessageContent(): MessageContent? {
else -> getLastMessageContent() else -> getLastMessageContent()
} }
} }
fun TimelineEvent.isVoiceBroadcast(): Boolean {
return root.isVoiceBroadcast()
}

View File

@ -32,6 +32,7 @@ import im.vector.app.R
import im.vector.app.SpaceStateHandler import im.vector.app.SpaceStateHandler
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.isVoiceBroadcast
import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.mvrx.runCatchingToAsync
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.BuildMeta
@ -859,12 +860,18 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleRedactEvent(action: RoomDetailAction.RedactAction) { private fun handleRedactEvent(action: RoomDetailAction.RedactAction) {
val event = room?.getTimelineEvent(action.targetEventId) ?: return val event = room?.getTimelineEvent(action.targetEventId) ?: return
if (event.isLiveLocation()) { when {
viewModelScope.launch { event.isLiveLocation() -> {
redactLiveLocationShareEventUseCase.execute(event.root, room, action.reason) viewModelScope.launch {
redactLiveLocationShareEventUseCase.execute(event.root, room, action.reason)
}
}
event.isVoiceBroadcast() -> {
room.sendService().redactEvent(event.root, action.reason, listOf(RelationType.REFERENCE))
}
else -> {
room.sendService().redactEvent(event.root, action.reason)
} }
} else {
room.sendService().redactEvent(event.root, action.reason)
} }
} }

View File

@ -49,7 +49,9 @@ value class VoiceBroadcastEvent(val root: Event) {
get() = root.content.toModel() get() = root.content.toModel()
} }
fun Event.isVoiceBroadcast() = type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
/** /**
* Map a [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent]. * Map a [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent].
*/ */
fun Event.asVoiceBroadcastEvent() = if (type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) VoiceBroadcastEvent(this) else null fun Event.asVoiceBroadcastEvent() = if (isVoiceBroadcast()) VoiceBroadcastEvent(this) else null