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 the account.
273    ///
274    /// Allows to get both the display name and avatar URL in a single call.
275    ///
276    /// # Examples
277    ///
278    /// ```no_run
279    /// # use matrix_sdk::Client;
280    /// # use url::Url;
281    /// # async {
282    /// # let homeserver = Url::parse("http://localhost:8080")?;
283    /// # let client = Client::new(homeserver).await?;
284    /// let profile = client.account().fetch_user_profile().await?;
285    /// println!(
286    ///     "You are '{:?}' with avatar '{:?}'",
287    ///     profile.displayname, profile.avatar_url
288    /// );
289    /// # anyhow::Ok(()) };
290    /// ```
291    pub async fn fetch_user_profile(&self) -> Result<get_profile::v3::Response> {
292        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
293        self.fetch_user_profile_of(user_id).await
294    }
295
296    /// Get the profile for a given user id
297    ///
298    /// # Arguments
299    ///
300    /// * `user_id` the matrix id this function downloads the profile for
301    pub async fn fetch_user_profile_of(
302        &self,
303        user_id: &UserId,
304    ) -> Result<get_profile::v3::Response> {
305        let request = get_profile::v3::Request::new(user_id.to_owned());
306        Ok(self
307            .client
308            .send(request)
309            .with_request_config(RequestConfig::short_retry().force_auth())
310            .await?)
311    }
312
313    /// Change the password of the account.
314    ///
315    /// # Arguments
316    ///
317    /// * `new_password` - The new password to set.
318    ///
319    /// * `auth_data` - This request uses the [User-Interactive Authentication
320    ///   API][uiaa]. The first request needs to set this to `None` and will
321    ///   always fail with an [`UiaaResponse`]. The response will contain
322    ///   information for the interactive auth and the same request needs to be
323    ///   made but this time with some `auth_data` provided.
324    ///
325    /// # Returns
326    ///
327    /// This method might return an [`ErrorKind::WeakPassword`] error if the new
328    /// password is considered insecure by the homeserver, with details about
329    /// the strength requirements in the error's message.
330    ///
331    /// # Examples
332    ///
333    /// ```no_run
334    /// # use matrix_sdk::Client;
335    /// # use matrix_sdk::ruma::{
336    /// #     api::client::{
337    /// #         account::change_password::v3::{Request as ChangePasswordRequest},
338    /// #         uiaa::{AuthData, Dummy},
339    /// #     },
340    /// #     assign,
341    /// # };
342    /// # use url::Url;
343    /// # async {
344    /// # let homeserver = Url::parse("http://localhost:8080")?;
345    /// # let client = Client::new(homeserver).await?;
346    /// client.account().change_password(
347    ///     "myverysecretpassword",
348    ///     Some(AuthData::Dummy(Dummy::new())),
349    /// ).await?;
350    /// # anyhow::Ok(()) };
351    /// ```
352    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
353    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
354    /// [`ErrorKind::WeakPassword`]: ruma::api::client::error::ErrorKind::WeakPassword
355    pub async fn change_password(
356        &self,
357        new_password: &str,
358        auth_data: Option<AuthData>,
359    ) -> Result<change_password::v3::Response> {
360        let request = assign!(change_password::v3::Request::new(new_password.to_owned()), {
361            auth: auth_data,
362        });
363        Ok(self.client.send(request).await?)
364    }
365
366    /// Deactivate this account definitively.
367    ///
368    /// # Arguments
369    ///
370    /// * `id_server` - The identity server from which to unbind the user’s
371    ///   [Third Party Identifiers][3pid].
372    ///
373    /// * `auth_data` - This request uses the [User-Interactive Authentication
374    ///   API][uiaa]. The first request needs to set this to `None` and will
375    ///   always fail with an [`UiaaResponse`]. The response will contain
376    ///   information for the interactive auth and the same request needs to be
377    ///   made but this time with some `auth_data` provided.
378    ///
379    /// * `erase` - Whether the user would like their content to be erased as
380    ///   much as possible from the server.
381    ///
382    /// # Examples
383    ///
384    /// ```no_run
385    /// # use matrix_sdk::Client;
386    /// # use matrix_sdk::ruma::{
387    /// #     api::client::{
388    /// #         account::change_password::v3::{Request as ChangePasswordRequest},
389    /// #         uiaa::{AuthData, Dummy},
390    /// #     },
391    /// #     assign,
392    /// # };
393    /// # use url::Url;
394    /// # async {
395    /// # let homeserver = Url::parse("http://localhost:8080")?;
396    /// # let client = Client::new(homeserver).await?;
397    /// # let account = client.account();
398    /// let response = account.deactivate(None, None, false).await;
399    ///
400    /// // Proceed with UIAA.
401    /// # anyhow::Ok(()) };
402    /// ```
403    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
404    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
405    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
406    pub async fn deactivate(
407        &self,
408        id_server: Option<&str>,
409        auth_data: Option<AuthData>,
410        erase_data: bool,
411    ) -> Result<deactivate::v3::Response> {
412        let request = assign!(deactivate::v3::Request::new(), {
413            id_server: id_server.map(ToOwned::to_owned),
414            auth: auth_data,
415            erase: erase_data,
416        });
417        Ok(self.client.send(request).await?)
418    }
419
420    /// Get the registered [Third Party Identifiers][3pid] on the homeserver of
421    /// the account.
422    ///
423    /// These 3PIDs may be used by the homeserver to authenticate the user
424    /// during sensitive operations.
425    ///
426    /// # Examples
427    ///
428    /// ```no_run
429    /// # use matrix_sdk::Client;
430    /// # use url::Url;
431    /// # async {
432    /// # let homeserver = Url::parse("http://localhost:8080")?;
433    /// # let client = Client::new(homeserver).await?;
434    /// let threepids = client.account().get_3pids().await?.threepids;
435    ///
436    /// for threepid in threepids {
437    ///     println!(
438    ///         "Found 3PID '{}' of type '{}'",
439    ///         threepid.address, threepid.medium
440    ///     );
441    /// }
442    /// # anyhow::Ok(()) };
443    /// ```
444    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
445    pub async fn get_3pids(&self) -> Result<get_3pids::v3::Response> {
446        let request = get_3pids::v3::Request::new();
447        Ok(self.client.send(request).await?)
448    }
449
450    /// Request a token to validate an email address as a [Third Party
451    /// Identifier][3pid].
452    ///
453    /// This is the first step in registering an email address as 3PID. Next,
454    /// call [`Account::add_3pid()`] with the same `client_secret` and the
455    /// returned `sid`.
456    ///
457    /// # Arguments
458    ///
459    /// * `client_secret` - A client-generated secret string used to protect
460    ///   this session.
461    ///
462    /// * `email` - The email address to validate.
463    ///
464    /// * `send_attempt` - The attempt number. This number needs to be
465    ///   incremented if you want to request another token for the same
466    ///   validation.
467    ///
468    /// # Returns
469    ///
470    /// * `sid` - The session ID to be used in following requests for this 3PID.
471    ///
472    /// * `submit_url` - If present, the user will submit the token to the
473    ///   client, that must send it to this URL. If not, the client will not be
474    ///   involved in the token submission.
475    ///
476    /// This method might return an [`ErrorKind::ThreepidInUse`] error if the
477    /// email address is already registered for this account or another, or an
478    /// [`ErrorKind::ThreepidDenied`] error if it is denied.
479    ///
480    /// # Examples
481    ///
482    /// ```no_run
483    /// # use matrix_sdk::Client;
484    /// # use matrix_sdk::ruma::{ClientSecret, uint};
485    /// # use url::Url;
486    /// # async {
487    /// # let homeserver = Url::parse("http://localhost:8080")?;
488    /// # let client = Client::new(homeserver).await?;
489    /// # let account = client.account();
490    /// # let secret = ClientSecret::parse("secret")?;
491    /// let token_response = account
492    ///     .request_3pid_email_token(&secret, "john@matrix.org", uint!(0))
493    ///     .await?;
494    ///
495    /// // Wait for the user to confirm that the token was submitted or prompt
496    /// // the user for the token and send it to submit_url.
497    ///
498    /// let uiaa_response =
499    ///     account.add_3pid(&secret, &token_response.sid, None).await;
500    ///
501    /// // Proceed with UIAA.
502    /// # anyhow::Ok(()) };
503    /// ```
504    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
505    /// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
506    /// [`ErrorKind::ThreepidDenied`]: ruma::api::client::error::ErrorKind::ThreepidDenied
507    pub async fn request_3pid_email_token(
508        &self,
509        client_secret: &ClientSecret,
510        email: &str,
511        send_attempt: UInt,
512    ) -> Result<request_3pid_management_token_via_email::v3::Response> {
513        let request = request_3pid_management_token_via_email::v3::Request::new(
514            client_secret.to_owned(),
515            email.to_owned(),
516            send_attempt,
517        );
518        Ok(self.client.send(request).await?)
519    }
520
521    /// Request a token to validate a phone number as a [Third Party
522    /// Identifier][3pid].
523    ///
524    /// This is the first step in registering a phone number as 3PID. Next,
525    /// call [`Account::add_3pid()`] with the same `client_secret` and the
526    /// returned `sid`.
527    ///
528    /// # Arguments
529    ///
530    /// * `client_secret` - A client-generated secret string used to protect
531    ///   this session.
532    ///
533    /// * `country` - The two-letter uppercase ISO-3166-1 alpha-2 country code
534    ///   that the number in phone_number should be parsed as if it were dialled
535    ///   from.
536    ///
537    /// * `phone_number` - The phone number to validate.
538    ///
539    /// * `send_attempt` - The attempt number. This number needs to be
540    ///   incremented if you want to request another token for the same
541    ///   validation.
542    ///
543    /// # Returns
544    ///
545    /// * `sid` - The session ID to be used in following requests for this 3PID.
546    ///
547    /// * `submit_url` - If present, the user will submit the token to the
548    ///   client, that must send it to this URL. If not, the client will not be
549    ///   involved in the token submission.
550    ///
551    /// This method might return an [`ErrorKind::ThreepidInUse`] error if the
552    /// phone number is already registered for this account or another, or an
553    /// [`ErrorKind::ThreepidDenied`] error if it is denied.
554    ///
555    /// # Examples
556    ///
557    /// ```no_run
558    /// # use matrix_sdk::Client;
559    /// # use matrix_sdk::ruma::{ClientSecret, uint};
560    /// # use url::Url;
561    /// # async {
562    /// # let homeserver = Url::parse("http://localhost:8080")?;
563    /// # let client = Client::new(homeserver).await?;
564    /// # let account = client.account();
565    /// # let secret = ClientSecret::parse("secret")?;
566    /// let token_response = account
567    ///     .request_3pid_msisdn_token(&secret, "FR", "0123456789", uint!(0))
568    ///     .await?;
569    ///
570    /// // Wait for the user to confirm that the token was submitted or prompt
571    /// // the user for the token and send it to submit_url.
572    ///
573    /// let uiaa_response =
574    ///     account.add_3pid(&secret, &token_response.sid, None).await;
575    ///
576    /// // Proceed with UIAA.
577    /// # anyhow::Ok(()) };
578    /// ```
579    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
580    /// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
581    /// [`ErrorKind::ThreepidDenied`]: ruma::api::client::error::ErrorKind::ThreepidDenied
582    pub async fn request_3pid_msisdn_token(
583        &self,
584        client_secret: &ClientSecret,
585        country: &str,
586        phone_number: &str,
587        send_attempt: UInt,
588    ) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
589        let request = request_3pid_management_token_via_msisdn::v3::Request::new(
590            client_secret.to_owned(),
591            country.to_owned(),
592            phone_number.to_owned(),
593            send_attempt,
594        );
595        Ok(self.client.send(request).await?)
596    }
597
598    /// Add a [Third Party Identifier][3pid] on the homeserver for this
599    /// account.
600    ///
601    /// This 3PID may be used by the homeserver to authenticate the user
602    /// during sensitive operations.
603    ///
604    /// This method should be called after
605    /// [`Account::request_3pid_email_token()`] or
606    /// [`Account::request_3pid_msisdn_token()`] to complete the 3PID
607    ///
608    /// # Arguments
609    ///
610    /// * `client_secret` - The same client secret used in
611    ///   [`Account::request_3pid_email_token()`] or
612    ///   [`Account::request_3pid_msisdn_token()`].
613    ///
614    /// * `sid` - The session ID returned in
615    ///   [`Account::request_3pid_email_token()`] or
616    ///   [`Account::request_3pid_msisdn_token()`].
617    ///
618    /// * `auth_data` - This request uses the [User-Interactive Authentication
619    ///   API][uiaa]. The first request needs to set this to `None` and will
620    ///   always fail with an [`UiaaResponse`]. The response will contain
621    ///   information for the interactive auth and the same request needs to be
622    ///   made but this time with some `auth_data` provided.
623    ///
624    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
625    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
626    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
627    pub async fn add_3pid(
628        &self,
629        client_secret: &ClientSecret,
630        sid: &SessionId,
631        auth_data: Option<AuthData>,
632    ) -> Result<add_3pid::v3::Response> {
633        #[rustfmt::skip] // rustfmt wants to merge the next two lines
634        let request =
635            assign!(add_3pid::v3::Request::new(client_secret.to_owned(), sid.to_owned()), {
636                auth: auth_data
637            });
638        Ok(self.client.send(request).await?)
639    }
640
641    /// Delete a [Third Party Identifier][3pid] from the homeserver for this
642    /// account.
643    ///
644    /// # Arguments
645    ///
646    /// * `address` - The 3PID being removed.
647    ///
648    /// * `medium` - The type of the 3PID.
649    ///
650    /// * `id_server` - The identity server to unbind from. If not provided, the
651    ///   homeserver should unbind the 3PID from the identity server it was
652    ///   bound to previously.
653    ///
654    /// # Returns
655    ///
656    /// * [`ThirdPartyIdRemovalStatus::Success`] if the 3PID was also unbound
657    ///   from the identity server.
658    ///
659    /// * [`ThirdPartyIdRemovalStatus::NoSupport`] if the 3PID was not unbound
660    ///   from the identity server. This can also mean that the 3PID was not
661    ///   bound to an identity server in the first place.
662    ///
663    /// # Examples
664    ///
665    /// ```no_run
666    /// # use matrix_sdk::Client;
667    /// # use matrix_sdk::ruma::thirdparty::Medium;
668    /// # use matrix_sdk::ruma::api::client::account::ThirdPartyIdRemovalStatus;
669    /// # use url::Url;
670    /// # async {
671    /// # let homeserver = Url::parse("http://localhost:8080")?;
672    /// # let client = Client::new(homeserver).await?;
673    /// # let account = client.account();
674    /// match account
675    ///     .delete_3pid("paul@matrix.org", Medium::Email, None)
676    ///     .await?
677    ///     .id_server_unbind_result
678    /// {
679    ///     ThirdPartyIdRemovalStatus::Success => {
680    ///         println!("3PID unbound from the Identity Server");
681    ///     }
682    ///     _ => println!("Could not unbind 3PID from the Identity Server"),
683    /// }
684    /// # anyhow::Ok(()) };
685    /// ```
686    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
687    /// [`ThirdPartyIdRemovalStatus::Success`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::Success
688    /// [`ThirdPartyIdRemovalStatus::NoSupport`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::NoSupport
689    pub async fn delete_3pid(
690        &self,
691        address: &str,
692        medium: Medium,
693        id_server: Option<&str>,
694    ) -> Result<delete_3pid::v3::Response> {
695        let request = assign!(delete_3pid::v3::Request::new(medium, address.to_owned()), {
696            id_server: id_server.map(ToOwned::to_owned),
697        });
698        Ok(self.client.send(request).await?)
699    }
700
701    /// Get the content of an account data event of statically-known type, from
702    /// storage.
703    ///
704    /// # Examples
705    ///
706    /// ```no_run
707    /// # use matrix_sdk::Client;
708    /// # async {
709    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
710    /// # let account = client.account();
711    /// use matrix_sdk::ruma::events::ignored_user_list::IgnoredUserListEventContent;
712    ///
713    /// let maybe_content = account.account_data::<IgnoredUserListEventContent>().await?;
714    /// if let Some(raw_content) = maybe_content {
715    ///     let content = raw_content.deserialize()?;
716    ///     println!("Ignored users:");
717    ///     for user_id in content.ignored_users.keys() {
718    ///         println!("- {user_id}");
719    ///     }
720    /// }
721    /// # anyhow::Ok(()) };
722    /// ```
723    pub async fn account_data<C>(&self) -> Result<Option<Raw<C>>>
724    where
725        C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = ruma::events::False>,
726    {
727        get_raw_content(self.client.state_store().get_account_data_event_static::<C>().await?)
728    }
729
730    /// Get the content of an account data event of a given type, from storage.
731    pub async fn account_data_raw(
732        &self,
733        event_type: GlobalAccountDataEventType,
734    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>> {
735        get_raw_content(self.client.state_store().get_account_data_event(event_type).await?)
736    }
737
738    /// Fetch a global account data event from the server.
739    ///
740    /// The content from the response will not be persisted in the store.
741    ///
742    /// Examples
743    ///
744    /// ```no_run
745    /// # use matrix_sdk::Client;
746    /// # async {
747    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
748    /// # let account = client.account();
749    /// use matrix_sdk::ruma::events::{ignored_user_list::IgnoredUserListEventContent, GlobalAccountDataEventType};
750    ///
751    /// if let Some(raw_content) = account.fetch_account_data(GlobalAccountDataEventType::IgnoredUserList).await? {
752    ///     let content = raw_content.deserialize_as_unchecked::<IgnoredUserListEventContent>()?;
753    ///
754    ///     println!("Ignored users:");
755    ///
756    ///     for user_id in content.ignored_users.keys() {
757    ///         println!("- {user_id}");
758    ///     }
759    /// }
760    /// # anyhow::Ok(()) };
761    pub async fn fetch_account_data(
762        &self,
763        event_type: GlobalAccountDataEventType,
764    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>> {
765        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
766
767        let request = get_global_account_data::v3::Request::new(own_user.to_owned(), event_type);
768
769        match self.client.send(request).await {
770            Ok(r) => Ok(Some(r.account_data)),
771            Err(e) => {
772                if let Some(kind) = e.client_api_error_kind() {
773                    if kind == &ErrorKind::NotFound {
774                        Ok(None)
775                    } else {
776                        Err(e.into())
777                    }
778                } else {
779                    Err(e.into())
780                }
781            }
782        }
783    }
784
785    /// Fetch an account data event of statically-known type from the server.
786    pub async fn fetch_account_data_static<C>(&self) -> Result<Option<Raw<C>>>
787    where
788        C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = ruma::events::False>,
789    {
790        Ok(self.fetch_account_data(C::TYPE.into()).await?.map(Raw::cast_unchecked))
791    }
792
793    /// Set the given account data event.
794    ///
795    /// # Examples
796    ///
797    /// ```no_run
798    /// # use matrix_sdk::Client;
799    /// # async {
800    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
801    /// # let account = client.account();
802    /// use matrix_sdk::ruma::{
803    ///     events::ignored_user_list::{IgnoredUser, IgnoredUserListEventContent},
804    ///     user_id,
805    /// };
806    ///
807    /// let mut content = account
808    ///     .account_data::<IgnoredUserListEventContent>()
809    ///     .await?
810    ///     .map(|c| c.deserialize())
811    ///     .transpose()?
812    ///     .unwrap_or_default();
813    /// content
814    ///     .ignored_users
815    ///     .insert(user_id!("@foo:bar.com").to_owned(), IgnoredUser::new());
816    /// account.set_account_data(content).await?;
817    /// # anyhow::Ok(()) };
818    /// ```
819    pub async fn set_account_data<T>(
820        &self,
821        content: T,
822    ) -> Result<set_global_account_data::v3::Response>
823    where
824        T: GlobalAccountDataEventContent,
825    {
826        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
827
828        let request = set_global_account_data::v3::Request::new(own_user.to_owned(), &content)?;
829
830        Ok(self.client.send(request).await?)
831    }
832
833    /// Set the given raw account data event.
834    pub async fn set_account_data_raw(
835        &self,
836        event_type: GlobalAccountDataEventType,
837        content: Raw<AnyGlobalAccountDataEventContent>,
838    ) -> Result<set_global_account_data::v3::Response> {
839        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
840
841        let request =
842            set_global_account_data::v3::Request::new_raw(own_user.to_owned(), event_type, content);
843
844        Ok(self.client.send(request).await?)
845    }
846
847    /// Marks the room identified by `room_id` as a "direct chat" with each
848    /// user in `user_ids`.
849    ///
850    /// # Arguments
851    ///
852    /// * `room_id` - The room ID of the direct message room.
853    /// * `user_ids` - The user IDs to be associated with this direct message
854    ///   room.
855    pub async fn mark_as_dm(&self, room_id: &RoomId, user_ids: &[OwnedUserId]) -> Result<()> {
856        use ruma::events::direct::DirectEventContent;
857
858        // This function does a read/update/store of an account data event stored on the
859        // homeserver. We first fetch the existing account data event, the event
860        // contains a map which gets updated by this method, finally we upload the
861        // modified event.
862        //
863        // To prevent multiple calls to this method trying to update the map of DMs same
864        // time, and thus trampling on each other we introduce a lock which acts
865        // as a semaphore.
866        let _guard = self.client.locks().mark_as_dm_lock.lock().await;
867
868        // Now we need to mark the room as a DM for ourselves, we fetch the
869        // existing `m.direct` event and append the room to the list of DMs we
870        // have with this user.
871
872        // We are fetching the content from the server because we currently can't rely
873        // on `/sync` giving us the correct data in a timely manner.
874        let raw_content = self.fetch_account_data_static::<DirectEventContent>().await?;
875
876        let mut content = if let Some(raw_content) = raw_content {
877            // Log the error and pass it upwards if we fail to deserialize the m.direct
878            // event.
879            raw_content.deserialize().map_err(|err| {
880                error!("unable to deserialize m.direct event content; aborting request to mark {room_id} as dm: {err}");
881                err
882            })?
883        } else {
884            // If there was no m.direct event server-side, create a default one.
885            Default::default()
886        };
887
888        for user_id in user_ids {
889            content.entry(user_id.into()).or_default().push(room_id.to_owned());
890        }
891
892        // TODO: We should probably save the fact that we need to send this out
893        // because otherwise we might end up in a state where we have a DM that
894        // isn't marked as one.
895        self.set_account_data(content).await?;
896
897        Ok(())
898    }
899
900    /// Adds the given user ID to the account's ignore list.
901    pub async fn ignore_user(&self, user_id: &UserId) -> Result<()> {
902        let own_user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
903        if user_id == own_user_id {
904            return Err(Error::CantIgnoreLoggedInUser);
905        }
906
907        let mut ignored_user_list = self.get_ignored_user_list_event_content().await?;
908        ignored_user_list.ignored_users.insert(user_id.to_owned(), IgnoredUser::new());
909
910        self.set_account_data(ignored_user_list).await?;
911
912        // In theory, we should also clear some caches here, because they may include
913        // events sent by the ignored user. In practice, we expect callers to
914        // take care of this, or subsystems to listen to user list changes and
915        // clear caches accordingly.
916
917        Ok(())
918    }
919
920    /// Removes the given user ID from the account's ignore list.
921    pub async fn unignore_user(&self, user_id: &UserId) -> Result<()> {
922        let mut ignored_user_list = self.get_ignored_user_list_event_content().await?;
923
924        // Only update account data if the user was ignored in the first place.
925        if ignored_user_list.ignored_users.remove(user_id).is_some() {
926            self.set_account_data(ignored_user_list).await?;
927        }
928
929        // See comment in `ignore_user`.
930        Ok(())
931    }
932
933    async fn get_ignored_user_list_event_content(&self) -> Result<IgnoredUserListEventContent> {
934        let ignored_user_list = self
935            .account_data::<IgnoredUserListEventContent>()
936            .await?
937            .map(|c| c.deserialize())
938            .transpose()?
939            .unwrap_or_default();
940        Ok(ignored_user_list)
941    }
942
943    /// Get the current push rules from storage.
944    ///
945    /// If no push rules event was found, or it fails to deserialize, a ruleset
946    /// with the server-default push rules is returned.
947    ///
948    /// Panics if called when the client is not logged in.
949    pub async fn push_rules(&self) -> Result<Ruleset> {
950        Ok(self
951            .account_data::<PushRulesEventContent>()
952            .await?
953            .and_then(|r| match r.deserialize() {
954                Ok(r) => Some(r.global),
955                Err(e) => {
956                    error!("Push rules event failed to deserialize: {e}");
957                    None
958                }
959            })
960            .unwrap_or_else(|| {
961                Ruleset::server_default(
962                    self.client.user_id().expect("The client should be logged in"),
963                )
964            }))
965    }
966
967    /// Retrieves the user's recently visited room list
968    pub async fn get_recently_visited_rooms(&self) -> Result<Vec<OwnedRoomId>> {
969        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
970        let data = self
971            .client
972            .state_store()
973            .get_kv_data(StateStoreDataKey::RecentlyVisitedRooms(user_id))
974            .await?;
975
976        Ok(data
977            .map(|v| {
978                v.into_recently_visited_rooms()
979                    .expect("Session data is not a list of recently visited rooms")
980            })
981            .unwrap_or_default())
982    }
983
984    /// Moves/inserts the given room to the front of the recently visited list
985    pub async fn track_recently_visited_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
986        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
987
988        // Get the previously stored recently visited rooms
989        let mut recently_visited_rooms = self.get_recently_visited_rooms().await?;
990
991        // Remove all other occurrences of the new room_id
992        recently_visited_rooms.retain(|r| r != &room_id);
993
994        // And insert it as the most recent
995        recently_visited_rooms.insert(0, room_id);
996
997        // Cap the whole list to the VISITED_ROOMS_LIMIT
998        recently_visited_rooms.truncate(Self::VISITED_ROOMS_LIMIT);
999
1000        let data = StateStoreDataValue::RecentlyVisitedRooms(recently_visited_rooms);
1001        self.client
1002            .state_store()
1003            .set_kv_data(StateStoreDataKey::RecentlyVisitedRooms(user_id), data)
1004            .await?;
1005        Ok(())
1006    }
1007
1008    /// Observes the media preview configuration.
1009    ///
1010    /// 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.
1011    ///
1012    /// This will return the initial value of the configuration and a stream
1013    /// that will yield new values as they are received.
1014    ///
1015    /// The initial value is the one that was stored in the account data
1016    /// when the client was started.
1017    /// and the following code is using a temporary solution until we know which
1018    /// Matrix version will support the stable type.
1019    ///
1020    /// # Examples
1021    ///
1022    /// ```no_run
1023    /// # use futures_util::{pin_mut, StreamExt};
1024    /// # use matrix_sdk::Client;
1025    /// # use matrix_sdk::ruma::events::media_preview_config::MediaPreviews;
1026    /// # use url::Url;
1027    /// # async {
1028    /// # let homeserver = Url::parse("http://localhost:8080")?;
1029    /// # let client = Client::new(homeserver).await?;
1030    /// let account = client.account();
1031    ///
1032    /// let (initial_config, config_stream) =
1033    ///     account.observe_media_preview_config().await?;
1034    ///
1035    /// println!("Initial media preview config: {:?}", initial_config);
1036    ///
1037    /// pin_mut!(config_stream);
1038    /// while let Some(new_config) = config_stream.next().await {
1039    ///     println!("Updated media preview config: {:?}", new_config);
1040    /// }
1041    /// # anyhow::Ok(()) };
1042    /// ```
1043    pub async fn observe_media_preview_config(
1044        &self,
1045    ) -> Result<
1046        (
1047            Option<MediaPreviewConfigEventContent>,
1048            impl Stream<Item = MediaPreviewConfigEventContent>,
1049        ),
1050        Error,
1051    > {
1052        // We need to create two observers, one for the stable event and one for the
1053        // unstable and combine them into a single stream.
1054        let first_observer = self
1055            .client
1056            .observe_events::<GlobalAccountDataEvent<MediaPreviewConfigEventContent>, ()>();
1057
1058        let stream = first_observer.subscribe().map(|event| event.0.content);
1059
1060        let second_observer = self
1061            .client
1062            .observe_events::<GlobalAccountDataEvent<UnstableMediaPreviewConfigEventContent>, ()>();
1063
1064        let second_stream = second_observer.subscribe().map(|event| event.0.content.0);
1065
1066        let mut combined_stream = stream::select(stream, second_stream);
1067
1068        let result_stream = async_stream::stream! {
1069            // The observers need to be alive for the individual streams to be alive, so let's now
1070            // create a stream that takes ownership of them.
1071            let _first_observer = first_observer;
1072            let _second_observer = second_observer;
1073
1074            while let Some(item) = combined_stream.next().await {
1075                yield item
1076            }
1077        };
1078
1079        // We need to get the initial value of the media preview config event
1080        // we do this after creating the observers to make sure that we don't
1081        // create a race condition
1082        let initial_value = self.get_media_preview_config_event_content().await?;
1083
1084        Ok((initial_value, result_stream))
1085    }
1086
1087    /// Fetch the media preview configuration event content from the server.
1088    ///
1089    /// Will check first for the stable event and then for the unstable one.
1090    pub async fn fetch_media_preview_config_event_content(
1091        &self,
1092    ) -> Result<Option<MediaPreviewConfigEventContent>> {
1093        // First we check if there is a value in the stable event
1094        let media_preview_config =
1095            self.fetch_account_data_static::<MediaPreviewConfigEventContent>().await?;
1096
1097        let media_preview_config = if let Some(media_preview_config) = media_preview_config {
1098            Some(media_preview_config)
1099        } else {
1100            // If there is no value in the stable event, we check the unstable
1101            self.fetch_account_data_static::<UnstableMediaPreviewConfigEventContent>()
1102                .await?
1103                .map(Raw::cast)
1104        };
1105
1106        // We deserialize the content of the event, if is not found we return the
1107        // default
1108        let media_preview_config = media_preview_config.and_then(|value| value.deserialize().ok());
1109
1110        Ok(media_preview_config)
1111    }
1112
1113    /// Get the media preview configuration event content stored in the cache.
1114    ///
1115    /// Will check first for the stable event and then for the unstable one.
1116    pub async fn get_media_preview_config_event_content(
1117        &self,
1118    ) -> Result<Option<MediaPreviewConfigEventContent>> {
1119        let media_preview_config = self
1120            .account_data::<MediaPreviewConfigEventContent>()
1121            .await?
1122            .and_then(|r| r.deserialize().ok());
1123
1124        if let Some(media_preview_config) = media_preview_config {
1125            Ok(Some(media_preview_config))
1126        } else {
1127            Ok(self
1128                .account_data::<UnstableMediaPreviewConfigEventContent>()
1129                .await?
1130                .and_then(|r| r.deserialize().ok())
1131                .map(Into::into))
1132        }
1133    }
1134
1135    /// Set the media previews display policy in the timeline.
1136    ///
1137    /// This will always use the unstable event until we know which Matrix
1138    /// version will support it.
1139    pub async fn set_media_previews_display_policy(&self, policy: MediaPreviews) -> Result<()> {
1140        let mut media_preview_config =
1141            self.fetch_media_preview_config_event_content().await?.unwrap_or_default();
1142        media_preview_config.media_previews = Some(policy);
1143
1144        // Updating the unstable account data
1145        let unstable_media_preview_config =
1146            UnstableMediaPreviewConfigEventContent::from(media_preview_config);
1147        self.set_account_data(unstable_media_preview_config).await?;
1148        Ok(())
1149    }
1150
1151    /// Set the display policy for avatars in invite requests.
1152    ///
1153    /// This will always use the unstable event until we know which matrix
1154    /// version will support it.
1155    pub async fn set_invite_avatars_display_policy(&self, policy: InviteAvatars) -> Result<()> {
1156        let mut media_preview_config =
1157            self.fetch_media_preview_config_event_content().await?.unwrap_or_default();
1158        media_preview_config.invite_avatars = Some(policy);
1159
1160        // Updating the unstable account data
1161        let unstable_media_preview_config =
1162            UnstableMediaPreviewConfigEventContent::from(media_preview_config);
1163        self.set_account_data(unstable_media_preview_config).await?;
1164        Ok(())
1165    }
1166}
1167
1168fn get_raw_content<Ev, C>(raw: Option<Raw<Ev>>) -> Result<Option<Raw<C>>> {
1169    #[derive(Deserialize)]
1170    #[serde(bound = "C: Sized")] // Replace default Deserialize bound
1171    struct GetRawContent<C> {
1172        content: Raw<C>,
1173    }
1174
1175    Ok(raw
1176        .map(|event| event.deserialize_as_unchecked::<GetRawContent<C>>())
1177        .transpose()?
1178        .map(|get_raw| get_raw.content))
1179}
1180
1181#[cfg(test)]
1182mod tests {
1183    use assert_matches::assert_matches;
1184    use matrix_sdk_test::async_test;
1185
1186    use crate::{test_utils::client::MockClientBuilder, Error};
1187
1188    #[async_test]
1189    async fn test_dont_ignore_oneself() {
1190        let client = MockClientBuilder::new(None).build().await;
1191
1192        // It's forbidden to ignore the logged-in user.
1193        assert_matches!(
1194            client.account().ignore_user(client.user_id().unwrap()).await,
1195            Err(Error::CantIgnoreLoggedInUser)
1196        );
1197    }
1198}