From fd04ebfaba6af12f390541a6ffe4cae059e53725 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Wed, 23 Jul 2025 15:21:26 +0200 Subject: [PATCH] feat(sdk): add support for threads subscription CRUD (msc4306) This doesn't store the subscriptions locally, nor does it implement the automatic thread subscription. --- Cargo.toml | 1 + crates/matrix-sdk/CHANGELOG.md | 5 ++ crates/matrix-sdk/src/room/mod.rs | 96 +++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 0756a2c2e..4e99c8d19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ ruma = { git = "https://github.com/ruma/ruma", rev = "184d4f85b201bc1e932a8344d7 "unstable-msc4171", "unstable-msc4278", "unstable-msc4286", + "unstable-msc4306" ] } ruma-common = { git = "https://github.com/ruma/ruma", rev = "184d4f85b201bc1e932a8344d78575e826f31efa" } sentry = "0.36.0" diff --git a/crates/matrix-sdk/CHANGELOG.md b/crates/matrix-sdk/CHANGELOG.md index 41cd9117c..478d76611 100644 --- a/crates/matrix-sdk/CHANGELOG.md +++ b/crates/matrix-sdk/CHANGELOG.md @@ -8,6 +8,11 @@ All notable changes to this project will be documented in this file. ### Features +- Add experimental support for + [MSC4306](https://github.com/matrix-org/matrix-spec-proposals/pull/4306), with the + `Room::fetch_thread_subscription()`, `Room::subscribe_thread()` and `Room::unsubscribe_thread()` + methods. + ([#5442](https://github.com/matrix-org/matrix-rust-sdk/pull/5442)) - [**breaking**] `RoomMemberRole` has a new `Creator` variant, that differentiates room creators with infinite power levels, as introduced in room version 12. diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index e782fc24f..027e08cd2 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -80,6 +80,7 @@ use ruma::{ room::{get_room_event, report_content, report_room}, state::{get_state_event_for_key, send_state_event}, tag::{create_tag, delete_tag}, + threads::{get_thread_subscription, subscribe_thread, unsubscribe_thread}, typing::create_typing_event::{self, v3::Typing}, }, assign, @@ -3711,6 +3712,101 @@ impl Room { ) -> Result { opts.send(self, event_id).await } + + /// Subscribe to a given thread in this room. + /// + /// This will subscribe the user to the thread, so that they will receive + /// notifications for that thread specifically. + /// + /// # Arguments + /// + /// - `thread_root`: The ID of the thread root event to subscribe to. + /// - `automatic`: Whether the subscription was made automatically by a + /// client, not by manual user choice. If there was a previous automatic + /// subscription, and that's set to `true` (i.e. we're now subscribing + /// manually), the subscription will be overridden to a manual one + /// instead. + /// + /// # Returns + /// + /// - A 404 error if the event isn't known, or isn't a thread root. + /// - An `Ok` result if the subscription was successful. + pub async fn subscribe_thread(&self, thread_root: OwnedEventId, automatic: bool) -> Result<()> { + self.client + .send(subscribe_thread::unstable::Request::new( + self.room_id().to_owned(), + thread_root, + automatic, + )) + .await?; + Ok(()) + } + + /// Unsubscribe from a given thread in this room. + /// + /// # Arguments + /// + /// - `thread_root`: The ID of the thread root event to unsubscribe to. + /// + /// # Returns + /// + /// - An `Ok` result if the unsubscription was successful, or the thread was + /// already unsubscribed. + /// - A 404 error if the event isn't known, or isn't a thread root. + pub async fn unsubscribe_thread(&self, thread_root: OwnedEventId) -> Result<()> { + self.client + .send(unsubscribe_thread::unstable::Request::new( + self.room_id().to_owned(), + thread_root, + )) + .await?; + Ok(()) + } + + /// Return the current thread subscription for the given thread root in this + /// room. + /// + /// # Arguments + /// + /// - `thread_root`: The ID of the thread root event to get the subscription + /// for. + /// + /// # Returns + /// + /// - An `Ok` result with `Some(ThreadSubscription)` if the subscription + /// exists. + /// - An `Ok` result with `None` if the subscription does not exist, or the + /// event couldn't be found, or the event isn't a thread. + /// - An error if the request fails for any other reason, such as a network + /// error. + pub async fn fetch_thread_subscription( + &self, + thread_root: OwnedEventId, + ) -> Result> { + let result = self + .client + .send(get_thread_subscription::unstable::Request::new( + self.room_id().to_owned(), + thread_root, + )) + .await; + + match result { + Ok(response) => Ok(Some(ThreadSubscription { automatic: response.automatic })), + Err(http_error) => match http_error.as_client_api_error() { + Some(error) if error.status_code == StatusCode::NOT_FOUND => Ok(None), + _ => Err(http_error.into()), + }, + } + } +} + +/// Status of a thread subscription. +#[derive(Debug, Clone, Copy)] +pub struct ThreadSubscription { + /// Whether the subscription was made automatically by a client, not by + /// manual user choice. + pub automatic: bool, } #[cfg(feature = "e2e-encryption")]