matrix_sdk/
account.rs

1// Copyright 2020 Damir Jelić
2// Copyright 2020 The Matrix.org Foundation C.I.C.
3// Copyright 2022 Kévin Commaille
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17use futures_core::Stream;
18use futures_util::{stream, StreamExt};
19use matrix_sdk_base::{
20    media::{MediaFormat, MediaRequestParameters},
21    store::StateStoreExt,
22    StateStoreDataKey, StateStoreDataValue,
23};
24use mime::Mime;
25use ruma::{
26    api::client::{
27        account::{
28            add_3pid, change_password, deactivate, delete_3pid, get_3pids,
29            request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
30        },
31        config::{get_global_account_data, set_global_account_data},
32        error::ErrorKind,
33        profile::{
34            get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
35        },
36        uiaa::AuthData,
37    },
38    assign,
39    events::{
40        ignored_user_list::{IgnoredUser, IgnoredUserListEventContent},
41        media_preview_config::{
42            InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews,
43            UnstableMediaPreviewConfigEventContent,
44        },
45        push_rules::PushRulesEventContent,
46        room::MediaSource,
47        AnyGlobalAccountDataEventContent, GlobalAccountDataEvent, GlobalAccountDataEventContent,
48        GlobalAccountDataEventType, StaticEventContent,
49    },
50    push::Ruleset,
51    serde::Raw,
52    thirdparty::Medium,
53    ClientSecret, MxcUri, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, SessionId, UInt, UserId,
54};
55use serde::Deserialize;
56use tracing::error;
57
58use crate::{config::RequestConfig, Client, Error, Result};
59
60/// A high-level API to manage the client owner's account.
61///
62/// All the methods on this struct send a request to the homeserver.
63#[derive(Debug, Clone)]
64pub struct Account {
65    /// The underlying HTTP client.
66    client: Client,
67}
68
69impl Account {
70    /// The maximum number of visited room identifiers to keep in the state
71    /// store.
72    const VISITED_ROOMS_LIMIT: usize = 20;
73
74    pub(crate) fn new(client: Client) -> Self {
75        Self { client }
76    }
77
78    /// Get the display name of the account.
79    ///
80    /// # Examples
81    ///
82    /// ```no_run
83    /// # use matrix_sdk::Client;
84    /// # use url::Url;
85    /// # async {
86    /// # let homeserver = Url::parse("http://example.com")?;
87    /// let user = "example";
88    /// let client = Client::new(homeserver).await?;
89    /// client.matrix_auth().login_username(user, "password").send().await?;
90    ///
91    /// if let Some(name) = client.account().get_display_name().await? {
92    ///     println!("Logged in as user '{user}' with display name '{name}'");
93    /// }
94    /// # anyhow::Ok(()) };
95    /// ```
96    pub async fn get_display_name(&self) -> Result<Option<String>> {
97        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
98        let request = get_display_name::v3::Request::new(user_id.to_owned());
99        let request_config = self.client.request_config().force_auth();
100        let response = self.client.send(request).with_request_config(request_config).await?;
101        Ok(response.displayname)
102    }
103
104    /// Set the display name of the account.
105    ///
106    /// # Examples
107    ///
108    /// ```no_run
109    /// # use matrix_sdk::Client;
110    /// # use url::Url;
111    /// # async {
112    /// # let homeserver = Url::parse("http://example.com")?;
113    /// let user = "example";
114    /// let client = Client::new(homeserver).await?;
115    /// client.matrix_auth().login_username(user, "password").send().await?;
116    ///
117    /// client.account().set_display_name(Some("Alice")).await?;
118    /// # anyhow::Ok(()) };
119    /// ```
120    pub async fn set_display_name(&self, name: Option<&str>) -> Result<()> {
121        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
122        let request =
123            set_display_name::v3::Request::new(user_id.to_owned(), name.map(ToOwned::to_owned));
124        self.client.send(request).await?;
125        Ok(())
126    }
127
128    /// Get the MXC URI of the account's avatar, if set.
129    ///
130    /// This always sends a request to the server to retrieve this information.
131    /// If successful, this fills the cache, and makes it so that
132    /// [`Self::get_cached_avatar_url`] will always return something.
133    ///
134    /// # Examples
135    ///
136    /// ```no_run
137    /// # use matrix_sdk::Client;
138    /// # use url::Url;
139    /// # async {
140    /// # let homeserver = Url::parse("http://example.com")?;
141    /// # let user = "example";
142    /// let client = Client::new(homeserver).await?;
143    /// client.matrix_auth().login_username(user, "password").send().await?;
144    ///
145    /// if let Some(url) = client.account().get_avatar_url().await? {
146    ///     println!("Your avatar's mxc url is {url}");
147    /// }
148    /// # anyhow::Ok(()) };
149    /// ```
150    pub async fn get_avatar_url(&self) -> Result<Option<OwnedMxcUri>> {
151        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
152        let request = get_avatar_url::v3::Request::new(user_id.to_owned());
153
154        let config = Some(RequestConfig::new().force_auth());
155
156        let response = self.client.send(request).with_request_config(config).await?;
157        if let Some(url) = response.avatar_url.clone() {
158            // If an avatar is found cache it.
159            let _ = self
160                .client
161                .state_store()
162                .set_kv_data(
163                    StateStoreDataKey::UserAvatarUrl(user_id),
164                    StateStoreDataValue::UserAvatarUrl(url),
165                )
166                .await;
167        } else {
168            // If there is no avatar the user has removed it and we uncache it.
169            let _ = self
170                .client
171                .state_store()
172                .remove_kv_data(StateStoreDataKey::UserAvatarUrl(user_id))
173                .await;
174        }
175        Ok(response.avatar_url)
176    }
177
178    /// Get the URL of the account's avatar, if is stored in cache.
179    pub async fn get_cached_avatar_url(&self) -> Result<Option<OwnedMxcUri>> {
180        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
181        let data = self
182            .client
183            .state_store()
184            .get_kv_data(StateStoreDataKey::UserAvatarUrl(user_id))
185            .await?;
186        Ok(data.map(|v| v.into_user_avatar_url().expect("Session data is not a user avatar url")))
187    }
188
189    /// Set the MXC URI of the account's avatar.
190    ///
191    /// The avatar is unset if `url` is `None`.
192    pub async fn set_avatar_url(&self, url: Option<&MxcUri>) -> Result<()> {
193        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
194        let request =
195            set_avatar_url::v3::Request::new(user_id.to_owned(), url.map(ToOwned::to_owned));
196        self.client.send(request).await?;
197        Ok(())
198    }
199
200    /// Get the account's avatar, if set.
201    ///
202    /// Returns the avatar.
203    ///
204    /// If a thumbnail is requested no guarantee on the size of the image is
205    /// given.
206    ///
207    /// # Arguments
208    ///
209    /// * `format` - The desired format of the avatar.
210    ///
211    /// # Examples
212    ///
213    /// ```no_run
214    /// # use matrix_sdk::Client;
215    /// # use matrix_sdk::ruma::room_id;
216    /// # use matrix_sdk::media::MediaFormat;
217    /// # use url::Url;
218    /// # async {
219    /// # let homeserver = Url::parse("http://example.com")?;
220    /// # let user = "example";
221    /// let client = Client::new(homeserver).await?;
222    /// client.matrix_auth().login_username(user, "password").send().await?;
223    ///
224    /// if let Some(avatar) = client.account().get_avatar(MediaFormat::File).await?
225    /// {
226    ///     std::fs::write("avatar.png", avatar);
227    /// }
228    /// # anyhow::Ok(()) };
229    /// ```
230    pub async fn get_avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
231        if let Some(url) = self.get_avatar_url().await? {
232            let request = MediaRequestParameters { source: MediaSource::Plain(url), format };
233            Ok(Some(self.client.media().get_media_content(&request, true).await?))
234        } else {
235            Ok(None)
236        }
237    }
238
239    /// Upload and set the account's avatar.
240    ///
241    /// This will upload the data produced by the reader to the homeserver's
242    /// content repository, and set the user's avatar to the MXC URI for the
243    /// uploaded file.
244    ///
245    /// This is a convenience method for calling [`Media::upload()`],
246    /// followed by [`Account::set_avatar_url()`].
247    ///
248    /// Returns the MXC URI of the uploaded avatar.
249    ///
250    /// # Examples
251    ///
252    /// ```no_run
253    /// # use std::fs;
254    /// # use matrix_sdk::Client;
255    /// # use url::Url;
256    /// # async {
257    /// # let homeserver = Url::parse("http://localhost:8080")?;
258    /// # let client = Client::new(homeserver).await?;
259    /// let image = fs::read("/home/example/selfie.jpg")?;
260    ///
261    /// client.account().upload_avatar(&mime::IMAGE_JPEG, image).await?;
262    /// # anyhow::Ok(()) };
263    /// ```
264    ///
265    /// [`Media::upload()`]: crate::Media::upload
266    pub async fn upload_avatar(&self, content_type: &Mime, data: Vec<u8>) -> Result<OwnedMxcUri> {
267        let upload_response = self.client.media().upload(content_type, data, None).await?;
268        self.set_avatar_url(Some(&upload_response.content_uri)).await?;
269        Ok(upload_response.content_uri)
270    }
271
272    /// Get the profile of this account.
273    ///
274    /// Allows to get all the profile data in a single call.
275    ///
276    /// # Examples
277    ///
278    /// ```no_run
279    /// # use matrix_sdk::Client;
280    /// use ruma::api::client::profile::{AvatarUrl, DisplayName};
281    /// # use url::Url;
282    /// # async {
283    /// # let homeserver = Url::parse("http://localhost:8080")?;
284    /// # let client = Client::new(homeserver).await?;
285    ///
286    /// let profile = client.account().fetch_user_profile().await?;
287    /// let display_name = profile.get_static::<DisplayName>()?;
288    /// let avatar_url = profile.get_static::<AvatarUrl>()?;
289    ///
290    /// println!("You are '{display_name:?}' with avatar '{avatar_url:?}'");
291    /// # anyhow::Ok(()) };
292    /// ```
293    pub async fn fetch_user_profile(&self) -> Result<get_profile::v3::Response> {
294        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
295        self.fetch_user_profile_of(user_id).await
296    }
297
298    /// Get the profile for a given user id
299    ///
300    /// # Arguments
301    ///
302    /// * `user_id` the matrix id this function downloads the profile for
303    pub async fn fetch_user_profile_of(
304        &self,
305        user_id: &UserId,
306    ) -> Result<get_profile::v3::Response> {
307        let request = get_profile::v3::Request::new(user_id.to_owned());
308        Ok(self
309            .client
310            .send(request)
311            .with_request_config(RequestConfig::short_retry().force_auth())
312            .await?)
313    }
314
315    /// Change the password of the account.
316    ///
317    /// # Arguments
318    ///
319    /// * `new_password` - The new password to set.
320    ///
321    /// * `auth_data` - This request uses the [User-Interactive Authentication
322    ///   API][uiaa]. The first request needs to set this to `None` and will
323    ///   always fail with an [`UiaaResponse`]. The response will contain
324    ///   information for the interactive auth and the same request needs to be
325    ///   made but this time with some `auth_data` provided.
326    ///
327    /// # Returns
328    ///
329    /// This method might return an [`ErrorKind::WeakPassword`] error if the new
330    /// password is considered insecure by the homeserver, with details about
331    /// the strength requirements in the error's message.
332    ///
333    /// # Examples
334    ///
335    /// ```no_run
336    /// # use matrix_sdk::Client;
337    /// # use matrix_sdk::ruma::{
338    /// #     api::client::{
339    /// #         account::change_password::v3::{Request as ChangePasswordRequest},
340    /// #         uiaa::{AuthData, Dummy},
341    /// #     },
342    /// #     assign,
343    /// # };
344    /// # use url::Url;
345    /// # async {
346    /// # let homeserver = Url::parse("http://localhost:8080")?;
347    /// # let client = Client::new(homeserver).await?;
348    /// client.account().change_password(
349    ///     "myverysecretpassword",
350    ///     Some(AuthData::Dummy(Dummy::new())),
351    /// ).await?;
352    /// # anyhow::Ok(()) };
353    /// ```
354    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
355    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
356    /// [`ErrorKind::WeakPassword`]: ruma::api::client::error::ErrorKind::WeakPassword
357    pub async fn change_password(
358        &self,
359        new_password: &str,
360        auth_data: Option<AuthData>,
361    ) -> Result<change_password::v3::Response> {
362        let request = assign!(change_password::v3::Request::new(new_password.to_owned()), {
363            auth: auth_data,
364        });
365        Ok(self.client.send(request).await?)
366    }
367
368    /// Deactivate this account definitively.
369    ///
370    /// # Arguments
371    ///
372    /// * `id_server` - The identity server from which to unbind the user’s
373    ///   [Third Party Identifiers][3pid].
374    ///
375    /// * `auth_data` - This request uses the [User-Interactive Authentication
376    ///   API][uiaa]. The first request needs to set this to `None` and will
377    ///   always fail with an [`UiaaResponse`]. The response will contain
378    ///   information for the interactive auth and the same request needs to be
379    ///   made but this time with some `auth_data` provided.
380    ///
381    /// * `erase` - Whether the user would like their content to be erased as
382    ///   much as possible from the server.
383    ///
384    /// # Examples
385    ///
386    /// ```no_run
387    /// # use matrix_sdk::Client;
388    /// # use matrix_sdk::ruma::{
389    /// #     api::client::{
390    /// #         account::change_password::v3::{Request as ChangePasswordRequest},
391    /// #         uiaa::{AuthData, Dummy},
392    /// #     },
393    /// #     assign,
394    /// # };
395    /// # use url::Url;
396    /// # async {
397    /// # let homeserver = Url::parse("http://localhost:8080")?;
398    /// # let client = Client::new(homeserver).await?;
399    /// # let account = client.account();
400    /// let response = account.deactivate(None, None, false).await;
401    ///
402    /// // Proceed with UIAA.
403    /// # anyhow::Ok(()) };
404    /// ```
405    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
406    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
407    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
408    pub async fn deactivate(
409        &self,
410        id_server: Option<&str>,
411        auth_data: Option<AuthData>,
412        erase_data: bool,
413    ) -> Result<deactivate::v3::Response> {
414        let request = assign!(deactivate::v3::Request::new(), {
415            id_server: id_server.map(ToOwned::to_owned),
416            auth: auth_data,
417            erase: erase_data,
418        });
419        Ok(self.client.send(request).await?)
420    }
421
422    /// Get the registered [Third Party Identifiers][3pid] on the homeserver of
423    /// the account.
424    ///
425    /// These 3PIDs may be used by the homeserver to authenticate the user
426    /// during sensitive operations.
427    ///
428    /// # Examples
429    ///
430    /// ```no_run
431    /// # use matrix_sdk::Client;
432    /// # use url::Url;
433    /// # async {
434    /// # let homeserver = Url::parse("http://localhost:8080")?;
435    /// # let client = Client::new(homeserver).await?;
436    /// let threepids = client.account().get_3pids().await?.threepids;
437    ///
438    /// for threepid in threepids {
439    ///     println!(
440    ///         "Found 3PID '{}' of type '{}'",
441    ///         threepid.address, threepid.medium
442    ///     );
443    /// }
444    /// # anyhow::Ok(()) };
445    /// ```
446    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
447    pub async fn get_3pids(&self) -> Result<get_3pids::v3::Response> {
448        let request = get_3pids::v3::Request::new();
449        Ok(self.client.send(request).await?)
450    }
451
452    /// Request a token to validate an email address as a [Third Party
453    /// Identifier][3pid].
454    ///
455    /// This is the first step in registering an email address as 3PID. Next,
456    /// call [`Account::add_3pid()`] with the same `client_secret` and the
457    /// returned `sid`.
458    ///
459    /// # Arguments
460    ///
461    /// * `client_secret` - A client-generated secret string used to protect
462    ///   this session.
463    ///
464    /// * `email` - The email address to validate.
465    ///
466    /// * `send_attempt` - The attempt number. This number needs to be
467    ///   incremented if you want to request another token for the same
468    ///   validation.
469    ///
470    /// # Returns
471    ///
472    /// * `sid` - The session ID to be used in following requests for this 3PID.
473    ///
474    /// * `submit_url` - If present, the user will submit the token to the
475    ///   client, that must send it to this URL. If not, the client will not be
476    ///   involved in the token submission.
477    ///
478    /// This method might return an [`ErrorKind::ThreepidInUse`] error if the
479    /// email address is already registered for this account or another, or an
480    /// [`ErrorKind::ThreepidDenied`] error if it is denied.
481    ///
482    /// # Examples
483    ///
484    /// ```no_run
485    /// # use matrix_sdk::Client;
486    /// # use matrix_sdk::ruma::{ClientSecret, uint};
487    /// # use url::Url;
488    /// # async {
489    /// # let homeserver = Url::parse("http://localhost:8080")?;
490    /// # let client = Client::new(homeserver).await?;
491    /// # let account = client.account();
492    /// # let secret = ClientSecret::parse("secret")?;
493    /// let token_response = account
494    ///     .request_3pid_email_token(&secret, "john@matrix.org", uint!(0))
495    ///     .await?;
496    ///
497    /// // Wait for the user to confirm that the token was submitted or prompt
498    /// // the user for the token and send it to submit_url.
499    ///
500    /// let uiaa_response =
501    ///     account.add_3pid(&secret, &token_response.sid, None).await;
502    ///
503    /// // Proceed with UIAA.
504    /// # anyhow::Ok(()) };
505    /// ```
506    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
507    /// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
508    /// [`ErrorKind::ThreepidDenied`]: ruma::api::client::error::ErrorKind::ThreepidDenied
509    pub async fn request_3pid_email_token(
510        &self,
511        client_secret: &ClientSecret,
512        email: &str,
513        send_attempt: UInt,
514    ) -> Result<request_3pid_management_token_via_email::v3::Response> {
515        let request = request_3pid_management_token_via_email::v3::Request::new(
516            client_secret.to_owned(),
517            email.to_owned(),
518            send_attempt,
519        );
520        Ok(self.client.send(request).await?)
521    }
522
523    /// Request a token to validate a phone number as a [Third Party
524    /// Identifier][3pid].
525    ///
526    /// This is the first step in registering a phone number as 3PID. Next,
527    /// call [`Account::add_3pid()`] with the same `client_secret` and the
528    /// returned `sid`.
529    ///
530    /// # Arguments
531    ///
532    /// * `client_secret` - A client-generated secret string used to protect
533    ///   this session.
534    ///
535    /// * `country` - The two-letter uppercase ISO-3166-1 alpha-2 country code
536    ///   that the number in phone_number should be parsed as if it were dialled
537    ///   from.
538    ///
539    /// * `phone_number` - The phone number to validate.
540    ///
541    /// * `send_attempt` - The attempt number. This number needs to be
542    ///   incremented if you want to request another token for the same
543    ///   validation.
544    ///
545    /// # Returns
546    ///
547    /// * `sid` - The session ID to be used in following requests for this 3PID.
548    ///
549    /// * `submit_url` - If present, the user will submit the token to the
550    ///   client, that must send it to this URL. If not, the client will not be
551    ///   involved in the token submission.
552    ///
553    /// This method might return an [`ErrorKind::ThreepidInUse`] error if the
554    /// phone number is already registered for this account or another, or an
555    /// [`ErrorKind::ThreepidDenied`] error if it is denied.
556    ///
557    /// # Examples
558    ///
559    /// ```no_run
560    /// # use matrix_sdk::Client;
561    /// # use matrix_sdk::ruma::{ClientSecret, uint};
562    /// # use url::Url;
563    /// # async {
564    /// # let homeserver = Url::parse("http://localhost:8080")?;
565    /// # let client = Client::new(homeserver).await?;
566    /// # let account = client.account();
567    /// # let secret = ClientSecret::parse("secret")?;
568    /// let token_response = account
569    ///     .request_3pid_msisdn_token(&secret, "FR", "0123456789", uint!(0))
570    ///     .await?;
571    ///
572    /// // Wait for the user to confirm that the token was submitted or prompt
573    /// // the user for the token and send it to submit_url.
574    ///
575    /// let uiaa_response =
576    ///     account.add_3pid(&secret, &token_response.sid, None).await;
577    ///
578    /// // Proceed with UIAA.
579    /// # anyhow::Ok(()) };
580    /// ```
581    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
582    /// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
583    /// [`ErrorKind::ThreepidDenied`]: ruma::api::client::error::ErrorKind::ThreepidDenied
584    pub async fn request_3pid_msisdn_token(
585        &self,
586        client_secret: &ClientSecret,
587        country: &str,
588        phone_number: &str,
589        send_attempt: UInt,
590    ) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
591        let request = request_3pid_management_token_via_msisdn::v3::Request::new(
592            client_secret.to_owned(),
593            country.to_owned(),
594            phone_number.to_owned(),
595            send_attempt,
596        );
597        Ok(self.client.send(request).await?)
598    }
599
600    /// Add a [Third Party Identifier][3pid] on the homeserver for this
601    /// account.
602    ///
603    /// This 3PID may be used by the homeserver to authenticate the user
604    /// during sensitive operations.
605    ///
606    /// This method should be called after
607    /// [`Account::request_3pid_email_token()`] or
608    /// [`Account::request_3pid_msisdn_token()`] to complete the 3PID
609    ///
610    /// # Arguments
611    ///
612    /// * `client_secret` - The same client secret used in
613    ///   [`Account::request_3pid_email_token()`] or
614    ///   [`Account::request_3pid_msisdn_token()`].
615    ///
616    /// * `sid` - The session ID returned in
617    ///   [`Account::request_3pid_email_token()`] or
618    ///   [`Account::request_3pid_msisdn_token()`].
619    ///
620    /// * `auth_data` - This request uses the [User-Interactive Authentication
621    ///   API][uiaa]. The first request needs to set this to `None` and will
622    ///   always fail with an [`UiaaResponse`]. The response will contain
623    ///   information for the interactive auth and the same request needs to be
624    ///   made but this time with some `auth_data` provided.
625    ///
626    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
627    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
628    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
629    pub async fn add_3pid(
630        &self,
631        client_secret: &ClientSecret,
632        sid: &SessionId,
633        auth_data: Option<AuthData>,
634    ) -> Result<add_3pid::v3::Response> {
635        #[rustfmt::skip] // rustfmt wants to merge the next two lines
636        let request =
637            assign!(add_3pid::v3::Request::new(client_secret.to_owned(), sid.to_owned()), {
638                auth: auth_data
639            });
640        Ok(self.client.send(request).await?)
641    }
642
643    /// Delete a [Third Party Identifier][3pid] from the homeserver for this
644    /// account.
645    ///
646    /// # Arguments
647    ///
648    /// * `address` - The 3PID being removed.
649    ///
650    /// * `medium` - The type of the 3PID.
651    ///
652    /// * `id_server` - The identity server to unbind from. If not provided, the
653    ///   homeserver should unbind the 3PID from the identity server it was
654    ///   bound to previously.
655    ///
656    /// # Returns
657    ///
658    /// * [`ThirdPartyIdRemovalStatus::Success`] if the 3PID was also unbound
659    ///   from the identity server.
660    ///
661    /// * [`ThirdPartyIdRemovalStatus::NoSupport`] if the 3PID was not unbound
662    ///   from the identity server. This can also mean that the 3PID was not
663    ///   bound to an identity server in the first place.
664    ///
665    /// # Examples
666    ///
667    /// ```no_run
668    /// # use matrix_sdk::Client;
669    /// # use matrix_sdk::ruma::thirdparty::Medium;
670    /// # use matrix_sdk::ruma::api::client::account::ThirdPartyIdRemovalStatus;
671    /// # use url::Url;
672    /// # async {
673    /// # let homeserver = Url::parse("http://localhost:8080")?;
674    /// # let client = Client::new(homeserver).await?;
675    /// # let account = client.account();
676    /// match account
677    ///     .delete_3pid("paul@matrix.org", Medium::Email, None)
678    ///     .await?
679    ///     .id_server_unbind_result
680    /// {
681    ///     ThirdPartyIdRemovalStatus::Success => {
682    ///         println!("3PID unbound from the Identity Server");
683    ///     }
684    ///     _ => println!("Could not unbind 3PID from the Identity Server"),
685    /// }
686    /// # anyhow::Ok(()) };
687    /// ```
688    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
689    /// [`ThirdPartyIdRemovalStatus::Success`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::Success
690    /// [`ThirdPartyIdRemovalStatus::NoSupport`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::NoSupport
691    pub async fn delete_3pid(
692        &self,
693        address: &str,
694        medium: Medium,
695        id_server: Option<&str>,
696    ) -> Result<delete_3pid::v3::Response> {
697        let request = assign!(delete_3pid::v3::Request::new(medium, address.to_owned()), {
698            id_server: id_server.map(ToOwned::to_owned),
699        });
700        Ok(self.client.send(request).await?)
701    }
702
703    /// Get the content of an account data event of statically-known type, from
704    /// storage.
705    ///
706    /// # Examples
707    ///
708    /// ```no_run
709    /// # use matrix_sdk::Client;
710    /// # async {
711    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
712    /// # let account = client.account();
713    /// use matrix_sdk::ruma::events::ignored_user_list::IgnoredUserListEventContent;
714    ///
715    /// let maybe_content = account.account_data::<IgnoredUserListEventContent>().await?;
716    /// if let Some(raw_content) = maybe_content {
717    ///     let content = raw_content.deserialize()?;
718    ///     println!("Ignored users:");
719    ///     for user_id in content.ignored_users.keys() {
720    ///         println!("- {user_id}");
721    ///     }
722    /// }
723    /// # anyhow::Ok(()) };
724    /// ```
725    pub async fn account_data<C>(&self) -> Result<Option<Raw<C>>>
726    where
727        C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = ruma::events::False>,
728    {
729        get_raw_content(self.client.state_store().get_account_data_event_static::<C>().await?)
730    }
731
732    /// Get the content of an account data event of a given type, from storage.
733    pub async fn account_data_raw(
734        &self,
735        event_type: GlobalAccountDataEventType,
736    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>> {
737        get_raw_content(self.client.state_store().get_account_data_event(event_type).await?)
738    }
739
740    /// Fetch a global account data event from the server.
741    ///
742    /// The content from the response will not be persisted in the store.
743    ///
744    /// Examples
745    ///
746    /// ```no_run
747    /// # use matrix_sdk::Client;
748    /// # async {
749    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
750    /// # let account = client.account();
751    /// use matrix_sdk::ruma::events::{ignored_user_list::IgnoredUserListEventContent, GlobalAccountDataEventType};
752    ///
753    /// if let Some(raw_content) = account.fetch_account_data(GlobalAccountDataEventType::IgnoredUserList).await? {
754    ///     let content = raw_content.deserialize_as_unchecked::<IgnoredUserListEventContent>()?;
755    ///
756    ///     println!("Ignored users:");
757    ///
758    ///     for user_id in content.ignored_users.keys() {
759    ///         println!("- {user_id}");
760    ///     }
761    /// }
762    /// # anyhow::Ok(()) };
763    pub async fn fetch_account_data(
764        &self,
765        event_type: GlobalAccountDataEventType,
766    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>> {
767        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
768
769        let request = get_global_account_data::v3::Request::new(own_user.to_owned(), event_type);
770
771        match self.client.send(request).await {
772            Ok(r) => Ok(Some(r.account_data)),
773            Err(e) => {
774                if let Some(kind) = e.client_api_error_kind() {
775                    if kind == &ErrorKind::NotFound {
776                        Ok(None)
777                    } else {
778                        Err(e.into())
779                    }
780                } else {
781                    Err(e.into())
782                }
783            }
784        }
785    }
786
787    /// Fetch an account data event of statically-known type from the server.
788    pub async fn fetch_account_data_static<C>(&self) -> Result<Option<Raw<C>>>
789    where
790        C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = ruma::events::False>,
791    {
792        Ok(self.fetch_account_data(C::TYPE.into()).await?.map(Raw::cast_unchecked))
793    }
794
795    /// Set the given account data event.
796    ///
797    /// # Examples
798    ///
799    /// ```no_run
800    /// # use matrix_sdk::Client;
801    /// # async {
802    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
803    /// # let account = client.account();
804    /// use matrix_sdk::ruma::{
805    ///     events::ignored_user_list::{IgnoredUser, IgnoredUserListEventContent},
806    ///     user_id,
807    /// };
808    ///
809    /// let mut content = account
810    ///     .account_data::<IgnoredUserListEventContent>()
811    ///     .await?
812    ///     .map(|c| c.deserialize())
813    ///     .transpose()?
814    ///     .unwrap_or_default();
815    /// content
816    ///     .ignored_users
817    ///     .insert(user_id!("@foo:bar.com").to_owned(), IgnoredUser::new());
818    /// account.set_account_data(content).await?;
819    /// # anyhow::Ok(()) };
820    /// ```
821    pub async fn set_account_data<T>(
822        &self,
823        content: T,
824    ) -> Result<set_global_account_data::v3::Response>
825    where
826        T: GlobalAccountDataEventContent,
827    {
828        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
829
830        let request = set_global_account_data::v3::Request::new(own_user.to_owned(), &content)?;
831
832        Ok(self.client.send(request).await?)
833    }
834
835    /// Set the given raw account data event.
836    pub async fn set_account_data_raw(
837        &self,
838        event_type: GlobalAccountDataEventType,
839        content: Raw<AnyGlobalAccountDataEventContent>,
840    ) -> Result<set_global_account_data::v3::Response> {
841        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
842
843        let request =
844            set_global_account_data::v3::Request::new_raw(own_user.to_owned(), event_type, content);
845
846        Ok(self.client.send(request).await?)
847    }
848
849    /// Marks the room identified by `room_id` as a "direct chat" with each
850    /// user in `user_ids`.
851    ///
852    /// # Arguments
853    ///
854    /// * `room_id` - The room ID of the direct message room.
855    /// * `user_ids` - The user IDs to be associated with this direct message
856    ///   room.
857    pub async fn mark_as_dm(&self, room_id: &RoomId, user_ids: &[OwnedUserId]) -> Result<()> {
858        use ruma::events::direct::DirectEventContent;
859
860        // This function does a read/update/store of an account data event stored on the
861        // homeserver. We first fetch the existing account data event, the event
862        // contains a map which gets updated by this method, finally we upload the
863        // modified event.
864        //
865        // To prevent multiple calls to this method trying to update the map of DMs same
866        // time, and thus trampling on each other we introduce a lock which acts
867        // as a semaphore.
868        let _guard = self.client.locks().mark_as_dm_lock.lock().await;
869
870        // Now we need to mark the room as a DM for ourselves, we fetch the
871        // existing `m.direct` event and append the room to the list of DMs we
872        // have with this user.
873
874        // We are fetching the content from the server because we currently can't rely
875        // on `/sync` giving us the correct data in a timely manner.
876        let raw_content = self.fetch_account_data_static::<DirectEventContent>().await?;
877
878        let mut content = if let Some(raw_content) = raw_content {
879            // Log the error and pass it upwards if we fail to deserialize the m.direct
880            // event.
881            raw_content.deserialize().map_err(|err| {
882                error!("unable to deserialize m.direct event content; aborting request to mark {room_id} as dm: {err}");
883                err
884            })?
885        } else {
886            // If there was no m.direct event server-side, create a default one.
887            Default::default()
888        };
889
890        for user_id in user_ids {
891            content.entry(user_id.into()).or_default().push(room_id.to_owned());
892        }
893
894        // TODO: We should probably save the fact that we need to send this out
895        // because otherwise we might end up in a state where we have a DM that
896        // isn't marked as one.
897        self.set_account_data(content).await?;
898
899        Ok(())
900    }
901
902    /// Adds the given user ID to the account's ignore list.
903    pub async fn ignore_user(&self, user_id: &UserId) -> Result<()> {
904        let own_user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
905        if user_id == own_user_id {
906            return Err(Error::CantIgnoreLoggedInUser);
907        }
908
909        let mut ignored_user_list = self.get_ignored_user_list_event_content().await?;
910        ignored_user_list.ignored_users.insert(user_id.to_owned(), IgnoredUser::new());
911
912        self.set_account_data(ignored_user_list).await?;
913
914        // In theory, we should also clear some caches here, because they may include
915        // events sent by the ignored user. In practice, we expect callers to
916        // take care of this, or subsystems to listen to user list changes and
917        // clear caches accordingly.
918
919        Ok(())
920    }
921
922    /// Removes the given user ID from the account's ignore list.
923    pub async fn unignore_user(&self, user_id: &UserId) -> Result<()> {
924        let mut ignored_user_list = self.get_ignored_user_list_event_content().await?;
925
926        // Only update account data if the user was ignored in the first place.
927        if ignored_user_list.ignored_users.remove(user_id).is_some() {
928            self.set_account_data(ignored_user_list).await?;
929        }
930
931        // See comment in `ignore_user`.
932        Ok(())
933    }
934
935    async fn get_ignored_user_list_event_content(&self) -> Result<IgnoredUserListEventContent> {
936        let ignored_user_list = self
937            .account_data::<IgnoredUserListEventContent>()
938            .await?
939            .map(|c| c.deserialize())
940            .transpose()?
941            .unwrap_or_default();
942        Ok(ignored_user_list)
943    }
944
945    /// Get the current push rules from storage.
946    ///
947    /// If no push rules event was found, or it fails to deserialize, a ruleset
948    /// with the server-default push rules is returned.
949    ///
950    /// Panics if called when the client is not logged in.
951    pub async fn push_rules(&self) -> Result<Ruleset> {
952        Ok(self
953            .account_data::<PushRulesEventContent>()
954            .await?
955            .and_then(|r| match r.deserialize() {
956                Ok(r) => Some(r.global),
957                Err(e) => {
958                    error!("Push rules event failed to deserialize: {e}");
959                    None
960                }
961            })
962            .unwrap_or_else(|| {
963                Ruleset::server_default(
964                    self.client.user_id().expect("The client should be logged in"),
965                )
966            }))
967    }
968
969    /// Retrieves the user's recently visited room list
970    pub async fn get_recently_visited_rooms(&self) -> Result<Vec<OwnedRoomId>> {
971        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
972        let data = self
973            .client
974            .state_store()
975            .get_kv_data(StateStoreDataKey::RecentlyVisitedRooms(user_id))
976            .await?;
977
978        Ok(data
979            .map(|v| {
980                v.into_recently_visited_rooms()
981                    .expect("Session data is not a list of recently visited rooms")
982            })
983            .unwrap_or_default())
984    }
985
986    /// Moves/inserts the given room to the front of the recently visited list
987    pub async fn track_recently_visited_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
988        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
989
990        // Get the previously stored recently visited rooms
991        let mut recently_visited_rooms = self.get_recently_visited_rooms().await?;
992
993        // Remove all other occurrences of the new room_id
994        recently_visited_rooms.retain(|r| r != &room_id);
995
996        // And insert it as the most recent
997        recently_visited_rooms.insert(0, room_id);
998
999        // Cap the whole list to the VISITED_ROOMS_LIMIT
1000        recently_visited_rooms.truncate(Self::VISITED_ROOMS_LIMIT);
1001
1002        let data = StateStoreDataValue::RecentlyVisitedRooms(recently_visited_rooms);
1003        self.client
1004            .state_store()
1005            .set_kv_data(StateStoreDataKey::RecentlyVisitedRooms(user_id), data)
1006            .await?;
1007        Ok(())
1008    }
1009
1010    /// Observes the media preview configuration.
1011    ///
1012    /// This value is linked to the [MSC 4278](https://github.com/matrix-org/matrix-spec-proposals/pull/4278) which is still in an unstable state.
1013    ///
1014    /// This will return the initial value of the configuration and a stream
1015    /// that will yield new values as they are received.
1016    ///
1017    /// The initial value is the one that was stored in the account data
1018    /// when the client was started.
1019    /// and the following code is using a temporary solution until we know which
1020    /// Matrix version will support the stable type.
1021    ///
1022    /// # Examples
1023    ///
1024    /// ```no_run
1025    /// # use futures_util::{pin_mut, StreamExt};
1026    /// # use matrix_sdk::Client;
1027    /// # use matrix_sdk::ruma::events::media_preview_config::MediaPreviews;
1028    /// # use url::Url;
1029    /// # async {
1030    /// # let homeserver = Url::parse("http://localhost:8080")?;
1031    /// # let client = Client::new(homeserver).await?;
1032    /// let account = client.account();
1033    ///
1034    /// let (initial_config, config_stream) =
1035    ///     account.observe_media_preview_config().await?;
1036    ///
1037    /// println!("Initial media preview config: {:?}", initial_config);
1038    ///
1039    /// pin_mut!(config_stream);
1040    /// while let Some(new_config) = config_stream.next().await {
1041    ///     println!("Updated media preview config: {:?}", new_config);
1042    /// }
1043    /// # anyhow::Ok(()) };
1044    /// ```
1045    pub async fn observe_media_preview_config(
1046        &self,
1047    ) -> Result<
1048        (
1049            Option<MediaPreviewConfigEventContent>,
1050            impl Stream<Item = MediaPreviewConfigEventContent>,
1051        ),
1052        Error,
1053    > {
1054        // We need to create two observers, one for the stable event and one for the
1055        // unstable and combine them into a single stream.
1056        let first_observer = self
1057            .client
1058            .observe_events::<GlobalAccountDataEvent<MediaPreviewConfigEventContent>, ()>();
1059
1060        let stream = first_observer.subscribe().map(|event| event.0.content);
1061
1062        let second_observer = self
1063            .client
1064            .observe_events::<GlobalAccountDataEvent<UnstableMediaPreviewConfigEventContent>, ()>();
1065
1066        let second_stream = second_observer.subscribe().map(|event| event.0.content.0);
1067
1068        let mut combined_stream = stream::select(stream, second_stream);
1069
1070        let result_stream = async_stream::stream! {
1071            // The observers need to be alive for the individual streams to be alive, so let's now
1072            // create a stream that takes ownership of them.
1073            let _first_observer = first_observer;
1074            let _second_observer = second_observer;
1075
1076            while let Some(item) = combined_stream.next().await {
1077                yield item
1078            }
1079        };
1080
1081        // We need to get the initial value of the media preview config event
1082        // we do this after creating the observers to make sure that we don't
1083        // create a race condition
1084        let initial_value = self.get_media_preview_config_event_content().await?;
1085
1086        Ok((initial_value, result_stream))
1087    }
1088
1089    /// Fetch the media preview configuration event content from the server.
1090    ///
1091    /// Will check first for the stable event and then for the unstable one.
1092    pub async fn fetch_media_preview_config_event_content(
1093        &self,
1094    ) -> Result<Option<MediaPreviewConfigEventContent>> {
1095        // First we check if there is a value in the stable event
1096        let media_preview_config =
1097            self.fetch_account_data_static::<MediaPreviewConfigEventContent>().await?;
1098
1099        let media_preview_config = if let Some(media_preview_config) = media_preview_config {
1100            Some(media_preview_config)
1101        } else {
1102            // If there is no value in the stable event, we check the unstable
1103            self.fetch_account_data_static::<UnstableMediaPreviewConfigEventContent>()
1104                .await?
1105                .map(Raw::cast)
1106        };
1107
1108        // We deserialize the content of the event, if is not found we return the
1109        // default
1110        let media_preview_config = media_preview_config.and_then(|value| value.deserialize().ok());
1111
1112        Ok(media_preview_config)
1113    }
1114
1115    /// Get the media preview configuration event content stored in the cache.
1116    ///
1117    /// Will check first for the stable event and then for the unstable one.
1118    pub async fn get_media_preview_config_event_content(
1119        &self,
1120    ) -> Result<Option<MediaPreviewConfigEventContent>> {
1121        let media_preview_config = self
1122            .account_data::<MediaPreviewConfigEventContent>()
1123            .await?
1124            .and_then(|r| r.deserialize().ok());
1125
1126        if let Some(media_preview_config) = media_preview_config {
1127            Ok(Some(media_preview_config))
1128        } else {
1129            Ok(self
1130                .account_data::<UnstableMediaPreviewConfigEventContent>()
1131                .await?
1132                .and_then(|r| r.deserialize().ok())
1133                .map(Into::into))
1134        }
1135    }
1136
1137    /// Set the media previews display policy in the timeline.
1138    ///
1139    /// This will always use the unstable event until we know which Matrix
1140    /// version will support it.
1141    pub async fn set_media_previews_display_policy(&self, policy: MediaPreviews) -> Result<()> {
1142        let mut media_preview_config =
1143            self.fetch_media_preview_config_event_content().await?.unwrap_or_default();
1144        media_preview_config.media_previews = Some(policy);
1145
1146        // Updating the unstable account data
1147        let unstable_media_preview_config =
1148            UnstableMediaPreviewConfigEventContent::from(media_preview_config);
1149        self.set_account_data(unstable_media_preview_config).await?;
1150        Ok(())
1151    }
1152
1153    /// Set the display policy for avatars in invite requests.
1154    ///
1155    /// This will always use the unstable event until we know which matrix
1156    /// version will support it.
1157    pub async fn set_invite_avatars_display_policy(&self, policy: InviteAvatars) -> Result<()> {
1158        let mut media_preview_config =
1159            self.fetch_media_preview_config_event_content().await?.unwrap_or_default();
1160        media_preview_config.invite_avatars = Some(policy);
1161
1162        // Updating the unstable account data
1163        let unstable_media_preview_config =
1164            UnstableMediaPreviewConfigEventContent::from(media_preview_config);
1165        self.set_account_data(unstable_media_preview_config).await?;
1166        Ok(())
1167    }
1168}
1169
1170fn get_raw_content<Ev, C>(raw: Option<Raw<Ev>>) -> Result<Option<Raw<C>>> {
1171    #[derive(Deserialize)]
1172    #[serde(bound = "C: Sized")] // Replace default Deserialize bound
1173    struct GetRawContent<C> {
1174        content: Raw<C>,
1175    }
1176
1177    Ok(raw
1178        .map(|event| event.deserialize_as_unchecked::<GetRawContent<C>>())
1179        .transpose()?
1180        .map(|get_raw| get_raw.content))
1181}
1182
1183#[cfg(test)]
1184mod tests {
1185    use assert_matches::assert_matches;
1186    use matrix_sdk_test::async_test;
1187
1188    use crate::{test_utils::client::MockClientBuilder, Error};
1189
1190    #[async_test]
1191    async fn test_dont_ignore_oneself() {
1192        let client = MockClientBuilder::new(None).build().await;
1193
1194        // It's forbidden to ignore the logged-in user.
1195        assert_matches!(
1196            client.account().ignore_user(client.user_id().unwrap()).await,
1197            Err(Error::CantIgnoreLoggedInUser)
1198        );
1199    }
1200}