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 matrix_sdk_base::{
18    media::{MediaFormat, MediaRequestParameters},
19    store::StateStoreExt,
20    StateStoreDataKey, StateStoreDataValue,
21};
22use mime::Mime;
23use ruma::{
24    api::client::{
25        account::{
26            add_3pid, change_password, deactivate, delete_3pid, get_3pids,
27            request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
28        },
29        config::{get_global_account_data, set_global_account_data},
30        error::ErrorKind,
31        profile::{
32            get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
33        },
34        uiaa::AuthData,
35    },
36    assign,
37    events::{
38        ignored_user_list::{IgnoredUser, IgnoredUserListEventContent},
39        push_rules::PushRulesEventContent,
40        room::MediaSource,
41        AnyGlobalAccountDataEventContent, GlobalAccountDataEventContent,
42        GlobalAccountDataEventType, StaticEventContent,
43    },
44    push::Ruleset,
45    serde::Raw,
46    thirdparty::Medium,
47    ClientSecret, MxcUri, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, SessionId, UInt, UserId,
48};
49use serde::Deserialize;
50use tracing::error;
51
52use crate::{config::RequestConfig, Client, Error, Result};
53
54/// A high-level API to manage the client owner's account.
55///
56/// All the methods on this struct send a request to the homeserver.
57#[derive(Debug, Clone)]
58pub struct Account {
59    /// The underlying HTTP client.
60    client: Client,
61}
62
63impl Account {
64    /// The maximum number of visited room identifiers to keep in the state
65    /// store.
66    const VISITED_ROOMS_LIMIT: usize = 20;
67
68    pub(crate) fn new(client: Client) -> Self {
69        Self { client }
70    }
71
72    /// Get the display name of the account.
73    ///
74    /// # Examples
75    ///
76    /// ```no_run
77    /// # use matrix_sdk::Client;
78    /// # use url::Url;
79    /// # async {
80    /// # let homeserver = Url::parse("http://example.com")?;
81    /// let user = "example";
82    /// let client = Client::new(homeserver).await?;
83    /// client.matrix_auth().login_username(user, "password").send().await?;
84    ///
85    /// if let Some(name) = client.account().get_display_name().await? {
86    ///     println!("Logged in as user '{user}' with display name '{name}'");
87    /// }
88    /// # anyhow::Ok(()) };
89    /// ```
90    pub async fn get_display_name(&self) -> Result<Option<String>> {
91        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
92        let request = get_display_name::v3::Request::new(user_id.to_owned());
93        let request_config = self.client.request_config().force_auth();
94        let response = self.client.send(request).with_request_config(request_config).await?;
95        Ok(response.displayname)
96    }
97
98    /// Set the display name of the account.
99    ///
100    /// # Examples
101    ///
102    /// ```no_run
103    /// # use matrix_sdk::Client;
104    /// # use url::Url;
105    /// # async {
106    /// # let homeserver = Url::parse("http://example.com")?;
107    /// let user = "example";
108    /// let client = Client::new(homeserver).await?;
109    /// client.matrix_auth().login_username(user, "password").send().await?;
110    ///
111    /// client.account().set_display_name(Some("Alice")).await?;
112    /// # anyhow::Ok(()) };
113    /// ```
114    pub async fn set_display_name(&self, name: Option<&str>) -> Result<()> {
115        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
116        let request =
117            set_display_name::v3::Request::new(user_id.to_owned(), name.map(ToOwned::to_owned));
118        self.client.send(request).await?;
119        Ok(())
120    }
121
122    /// Get the MXC URI of the account's avatar, if set.
123    ///
124    /// This always sends a request to the server to retrieve this information.
125    /// If successful, this fills the cache, and makes it so that
126    /// [`Self::get_cached_avatar_url`] will always return something.
127    ///
128    /// # Examples
129    ///
130    /// ```no_run
131    /// # use matrix_sdk::Client;
132    /// # use url::Url;
133    /// # async {
134    /// # let homeserver = Url::parse("http://example.com")?;
135    /// # let user = "example";
136    /// let client = Client::new(homeserver).await?;
137    /// client.matrix_auth().login_username(user, "password").send().await?;
138    ///
139    /// if let Some(url) = client.account().get_avatar_url().await? {
140    ///     println!("Your avatar's mxc url is {url}");
141    /// }
142    /// # anyhow::Ok(()) };
143    /// ```
144    pub async fn get_avatar_url(&self) -> Result<Option<OwnedMxcUri>> {
145        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
146        let request = get_avatar_url::v3::Request::new(user_id.to_owned());
147
148        let config = Some(RequestConfig::new().force_auth());
149
150        let response = self.client.send(request).with_request_config(config).await?;
151        if let Some(url) = response.avatar_url.clone() {
152            // If an avatar is found cache it.
153            let _ = self
154                .client
155                .state_store()
156                .set_kv_data(
157                    StateStoreDataKey::UserAvatarUrl(user_id),
158                    StateStoreDataValue::UserAvatarUrl(url),
159                )
160                .await;
161        } else {
162            // If there is no avatar the user has removed it and we uncache it.
163            let _ = self
164                .client
165                .state_store()
166                .remove_kv_data(StateStoreDataKey::UserAvatarUrl(user_id))
167                .await;
168        }
169        Ok(response.avatar_url)
170    }
171
172    /// Get the URL of the account's avatar, if is stored in cache.
173    pub async fn get_cached_avatar_url(&self) -> Result<Option<OwnedMxcUri>> {
174        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
175        let data = self
176            .client
177            .state_store()
178            .get_kv_data(StateStoreDataKey::UserAvatarUrl(user_id))
179            .await?;
180        Ok(data.map(|v| v.into_user_avatar_url().expect("Session data is not a user avatar url")))
181    }
182
183    /// Set the MXC URI of the account's avatar.
184    ///
185    /// The avatar is unset if `url` is `None`.
186    pub async fn set_avatar_url(&self, url: Option<&MxcUri>) -> Result<()> {
187        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
188        let request =
189            set_avatar_url::v3::Request::new(user_id.to_owned(), url.map(ToOwned::to_owned));
190        self.client.send(request).await?;
191        Ok(())
192    }
193
194    /// Get the account's avatar, if set.
195    ///
196    /// Returns the avatar.
197    ///
198    /// If a thumbnail is requested no guarantee on the size of the image is
199    /// given.
200    ///
201    /// # Arguments
202    ///
203    /// * `format` - The desired format of the avatar.
204    ///
205    /// # Examples
206    ///
207    /// ```no_run
208    /// # use matrix_sdk::Client;
209    /// # use matrix_sdk::ruma::room_id;
210    /// # use matrix_sdk::media::MediaFormat;
211    /// # use url::Url;
212    /// # async {
213    /// # let homeserver = Url::parse("http://example.com")?;
214    /// # let user = "example";
215    /// let client = Client::new(homeserver).await?;
216    /// client.matrix_auth().login_username(user, "password").send().await?;
217    ///
218    /// if let Some(avatar) = client.account().get_avatar(MediaFormat::File).await?
219    /// {
220    ///     std::fs::write("avatar.png", avatar);
221    /// }
222    /// # anyhow::Ok(()) };
223    /// ```
224    pub async fn get_avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
225        if let Some(url) = self.get_avatar_url().await? {
226            let request = MediaRequestParameters { source: MediaSource::Plain(url), format };
227            Ok(Some(self.client.media().get_media_content(&request, true).await?))
228        } else {
229            Ok(None)
230        }
231    }
232
233    /// Upload and set the account's avatar.
234    ///
235    /// This will upload the data produced by the reader to the homeserver's
236    /// content repository, and set the user's avatar to the MXC URI for the
237    /// uploaded file.
238    ///
239    /// This is a convenience method for calling [`Media::upload()`],
240    /// followed by [`Account::set_avatar_url()`].
241    ///
242    /// Returns the MXC URI of the uploaded avatar.
243    ///
244    /// # Examples
245    ///
246    /// ```no_run
247    /// # use std::fs;
248    /// # use matrix_sdk::Client;
249    /// # use url::Url;
250    /// # async {
251    /// # let homeserver = Url::parse("http://localhost:8080")?;
252    /// # let client = Client::new(homeserver).await?;
253    /// let image = fs::read("/home/example/selfie.jpg")?;
254    ///
255    /// client.account().upload_avatar(&mime::IMAGE_JPEG, image).await?;
256    /// # anyhow::Ok(()) };
257    /// ```
258    ///
259    /// [`Media::upload()`]: crate::Media::upload
260    pub async fn upload_avatar(&self, content_type: &Mime, data: Vec<u8>) -> Result<OwnedMxcUri> {
261        let upload_response = self.client.media().upload(content_type, data, None).await?;
262        self.set_avatar_url(Some(&upload_response.content_uri)).await?;
263        Ok(upload_response.content_uri)
264    }
265
266    /// Get the profile of the account.
267    ///
268    /// Allows to get both the display name and avatar URL in a single call.
269    ///
270    /// # Examples
271    ///
272    /// ```no_run
273    /// # use matrix_sdk::Client;
274    /// # use url::Url;
275    /// # async {
276    /// # let homeserver = Url::parse("http://localhost:8080")?;
277    /// # let client = Client::new(homeserver).await?;
278    /// let profile = client.account().fetch_user_profile().await?;
279    /// println!(
280    ///     "You are '{:?}' with avatar '{:?}'",
281    ///     profile.displayname, profile.avatar_url
282    /// );
283    /// # anyhow::Ok(()) };
284    /// ```
285    pub async fn fetch_user_profile(&self) -> Result<get_profile::v3::Response> {
286        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
287        self.fetch_user_profile_of(user_id).await
288    }
289
290    /// Get the profile for a given user id
291    ///
292    /// # Arguments
293    ///
294    /// * `user_id` the matrix id this function downloads the profile for
295    pub async fn fetch_user_profile_of(
296        &self,
297        user_id: &UserId,
298    ) -> Result<get_profile::v3::Response> {
299        let request = get_profile::v3::Request::new(user_id.to_owned());
300        Ok(self
301            .client
302            .send(request)
303            .with_request_config(RequestConfig::short_retry().force_auth())
304            .await?)
305    }
306
307    /// Change the password of the account.
308    ///
309    /// # Arguments
310    ///
311    /// * `new_password` - The new password to set.
312    ///
313    /// * `auth_data` - This request uses the [User-Interactive Authentication
314    ///   API][uiaa]. The first request needs to set this to `None` and will
315    ///   always fail with an [`UiaaResponse`]. The response will contain
316    ///   information for the interactive auth and the same request needs to be
317    ///   made but this time with some `auth_data` provided.
318    ///
319    /// # Returns
320    ///
321    /// This method might return an [`ErrorKind::WeakPassword`] error if the new
322    /// password is considered insecure by the homeserver, with details about
323    /// the strength requirements in the error's message.
324    ///
325    /// # Examples
326    ///
327    /// ```no_run
328    /// # use matrix_sdk::Client;
329    /// # use matrix_sdk::ruma::{
330    /// #     api::client::{
331    /// #         account::change_password::v3::{Request as ChangePasswordRequest},
332    /// #         uiaa::{AuthData, Dummy},
333    /// #     },
334    /// #     assign,
335    /// # };
336    /// # use url::Url;
337    /// # async {
338    /// # let homeserver = Url::parse("http://localhost:8080")?;
339    /// # let client = Client::new(homeserver).await?;
340    /// client.account().change_password(
341    ///     "myverysecretpassword",
342    ///     Some(AuthData::Dummy(Dummy::new())),
343    /// ).await?;
344    /// # anyhow::Ok(()) };
345    /// ```
346    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
347    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
348    /// [`ErrorKind::WeakPassword`]: ruma::api::client::error::ErrorKind::WeakPassword
349    pub async fn change_password(
350        &self,
351        new_password: &str,
352        auth_data: Option<AuthData>,
353    ) -> Result<change_password::v3::Response> {
354        let request = assign!(change_password::v3::Request::new(new_password.to_owned()), {
355            auth: auth_data,
356        });
357        Ok(self.client.send(request).await?)
358    }
359
360    /// Deactivate this account definitively.
361    ///
362    /// # Arguments
363    ///
364    /// * `id_server` - The identity server from which to unbind the user’s
365    ///   [Third Party Identifiers][3pid].
366    ///
367    /// * `auth_data` - This request uses the [User-Interactive Authentication
368    ///   API][uiaa]. The first request needs to set this to `None` and will
369    ///   always fail with an [`UiaaResponse`]. The response will contain
370    ///   information for the interactive auth and the same request needs to be
371    ///   made but this time with some `auth_data` provided.
372    ///
373    /// * `erase` - Whether the user would like their content to be erased as
374    ///   much as possible from the server.
375    ///
376    /// # Examples
377    ///
378    /// ```no_run
379    /// # use matrix_sdk::Client;
380    /// # use matrix_sdk::ruma::{
381    /// #     api::client::{
382    /// #         account::change_password::v3::{Request as ChangePasswordRequest},
383    /// #         uiaa::{AuthData, Dummy},
384    /// #     },
385    /// #     assign,
386    /// # };
387    /// # use url::Url;
388    /// # async {
389    /// # let homeserver = Url::parse("http://localhost:8080")?;
390    /// # let client = Client::new(homeserver).await?;
391    /// # let account = client.account();
392    /// let response = account.deactivate(None, None, false).await;
393    ///
394    /// // Proceed with UIAA.
395    /// # anyhow::Ok(()) };
396    /// ```
397    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
398    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
399    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
400    pub async fn deactivate(
401        &self,
402        id_server: Option<&str>,
403        auth_data: Option<AuthData>,
404        erase_data: bool,
405    ) -> Result<deactivate::v3::Response> {
406        let request = assign!(deactivate::v3::Request::new(), {
407            id_server: id_server.map(ToOwned::to_owned),
408            auth: auth_data,
409            erase: erase_data,
410        });
411        Ok(self.client.send(request).await?)
412    }
413
414    /// Get the registered [Third Party Identifiers][3pid] on the homeserver of
415    /// the account.
416    ///
417    /// These 3PIDs may be used by the homeserver to authenticate the user
418    /// during sensitive operations.
419    ///
420    /// # Examples
421    ///
422    /// ```no_run
423    /// # use matrix_sdk::Client;
424    /// # use url::Url;
425    /// # async {
426    /// # let homeserver = Url::parse("http://localhost:8080")?;
427    /// # let client = Client::new(homeserver).await?;
428    /// let threepids = client.account().get_3pids().await?.threepids;
429    ///
430    /// for threepid in threepids {
431    ///     println!(
432    ///         "Found 3PID '{}' of type '{}'",
433    ///         threepid.address, threepid.medium
434    ///     );
435    /// }
436    /// # anyhow::Ok(()) };
437    /// ```
438    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
439    pub async fn get_3pids(&self) -> Result<get_3pids::v3::Response> {
440        let request = get_3pids::v3::Request::new();
441        Ok(self.client.send(request).await?)
442    }
443
444    /// Request a token to validate an email address as a [Third Party
445    /// Identifier][3pid].
446    ///
447    /// This is the first step in registering an email address as 3PID. Next,
448    /// call [`Account::add_3pid()`] with the same `client_secret` and the
449    /// returned `sid`.
450    ///
451    /// # Arguments
452    ///
453    /// * `client_secret` - A client-generated secret string used to protect
454    ///   this session.
455    ///
456    /// * `email` - The email address to validate.
457    ///
458    /// * `send_attempt` - The attempt number. This number needs to be
459    ///   incremented if you want to request another token for the same
460    ///   validation.
461    ///
462    /// # Returns
463    ///
464    /// * `sid` - The session ID to be used in following requests for this 3PID.
465    ///
466    /// * `submit_url` - If present, the user will submit the token to the
467    ///   client, that must send it to this URL. If not, the client will not be
468    ///   involved in the token submission.
469    ///
470    /// This method might return an [`ErrorKind::ThreepidInUse`] error if the
471    /// email address is already registered for this account or another, or an
472    /// [`ErrorKind::ThreepidDenied`] error if it is denied.
473    ///
474    /// # Examples
475    ///
476    /// ```no_run
477    /// # use matrix_sdk::Client;
478    /// # use matrix_sdk::ruma::{ClientSecret, uint};
479    /// # use url::Url;
480    /// # async {
481    /// # let homeserver = Url::parse("http://localhost:8080")?;
482    /// # let client = Client::new(homeserver).await?;
483    /// # let account = client.account();
484    /// # let secret = ClientSecret::parse("secret")?;
485    /// let token_response = account
486    ///     .request_3pid_email_token(&secret, "john@matrix.org", uint!(0))
487    ///     .await?;
488    ///
489    /// // Wait for the user to confirm that the token was submitted or prompt
490    /// // the user for the token and send it to submit_url.
491    ///
492    /// let uiaa_response =
493    ///     account.add_3pid(&secret, &token_response.sid, None).await;
494    ///
495    /// // Proceed with UIAA.
496    /// # anyhow::Ok(()) };
497    /// ```
498    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
499    /// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
500    /// [`ErrorKind::ThreepidDenied`]: ruma::api::client::error::ErrorKind::ThreepidDenied
501    pub async fn request_3pid_email_token(
502        &self,
503        client_secret: &ClientSecret,
504        email: &str,
505        send_attempt: UInt,
506    ) -> Result<request_3pid_management_token_via_email::v3::Response> {
507        let request = request_3pid_management_token_via_email::v3::Request::new(
508            client_secret.to_owned(),
509            email.to_owned(),
510            send_attempt,
511        );
512        Ok(self.client.send(request).await?)
513    }
514
515    /// Request a token to validate a phone number as a [Third Party
516    /// Identifier][3pid].
517    ///
518    /// This is the first step in registering a phone number as 3PID. Next,
519    /// call [`Account::add_3pid()`] with the same `client_secret` and the
520    /// returned `sid`.
521    ///
522    /// # Arguments
523    ///
524    /// * `client_secret` - A client-generated secret string used to protect
525    ///   this session.
526    ///
527    /// * `country` - The two-letter uppercase ISO-3166-1 alpha-2 country code
528    ///   that the number in phone_number should be parsed as if it were dialled
529    ///   from.
530    ///
531    /// * `phone_number` - The phone number to validate.
532    ///
533    /// * `send_attempt` - The attempt number. This number needs to be
534    ///   incremented if you want to request another token for the same
535    ///   validation.
536    ///
537    /// # Returns
538    ///
539    /// * `sid` - The session ID to be used in following requests for this 3PID.
540    ///
541    /// * `submit_url` - If present, the user will submit the token to the
542    ///   client, that must send it to this URL. If not, the client will not be
543    ///   involved in the token submission.
544    ///
545    /// This method might return an [`ErrorKind::ThreepidInUse`] error if the
546    /// phone number is already registered for this account or another, or an
547    /// [`ErrorKind::ThreepidDenied`] error if it is denied.
548    ///
549    /// # Examples
550    ///
551    /// ```no_run
552    /// # use matrix_sdk::Client;
553    /// # use matrix_sdk::ruma::{ClientSecret, uint};
554    /// # use url::Url;
555    /// # async {
556    /// # let homeserver = Url::parse("http://localhost:8080")?;
557    /// # let client = Client::new(homeserver).await?;
558    /// # let account = client.account();
559    /// # let secret = ClientSecret::parse("secret")?;
560    /// let token_response = account
561    ///     .request_3pid_msisdn_token(&secret, "FR", "0123456789", uint!(0))
562    ///     .await?;
563    ///
564    /// // Wait for the user to confirm that the token was submitted or prompt
565    /// // the user for the token and send it to submit_url.
566    ///
567    /// let uiaa_response =
568    ///     account.add_3pid(&secret, &token_response.sid, None).await;
569    ///
570    /// // Proceed with UIAA.
571    /// # anyhow::Ok(()) };
572    /// ```
573    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
574    /// [`ErrorKind::ThreepidInUse`]: ruma::api::client::error::ErrorKind::ThreepidInUse
575    /// [`ErrorKind::ThreepidDenied`]: ruma::api::client::error::ErrorKind::ThreepidDenied
576    pub async fn request_3pid_msisdn_token(
577        &self,
578        client_secret: &ClientSecret,
579        country: &str,
580        phone_number: &str,
581        send_attempt: UInt,
582    ) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
583        let request = request_3pid_management_token_via_msisdn::v3::Request::new(
584            client_secret.to_owned(),
585            country.to_owned(),
586            phone_number.to_owned(),
587            send_attempt,
588        );
589        Ok(self.client.send(request).await?)
590    }
591
592    /// Add a [Third Party Identifier][3pid] on the homeserver for this
593    /// account.
594    ///
595    /// This 3PID may be used by the homeserver to authenticate the user
596    /// during sensitive operations.
597    ///
598    /// This method should be called after
599    /// [`Account::request_3pid_email_token()`] or
600    /// [`Account::request_3pid_msisdn_token()`] to complete the 3PID
601    ///
602    /// # Arguments
603    ///
604    /// * `client_secret` - The same client secret used in
605    ///   [`Account::request_3pid_email_token()`] or
606    ///   [`Account::request_3pid_msisdn_token()`].
607    ///
608    /// * `sid` - The session ID returned in
609    ///   [`Account::request_3pid_email_token()`] or
610    ///   [`Account::request_3pid_msisdn_token()`].
611    ///
612    /// * `auth_data` - This request uses the [User-Interactive Authentication
613    ///   API][uiaa]. The first request needs to set this to `None` and will
614    ///   always fail with an [`UiaaResponse`]. The response will contain
615    ///   information for the interactive auth and the same request needs to be
616    ///   made but this time with some `auth_data` provided.
617    ///
618    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
619    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
620    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
621    pub async fn add_3pid(
622        &self,
623        client_secret: &ClientSecret,
624        sid: &SessionId,
625        auth_data: Option<AuthData>,
626    ) -> Result<add_3pid::v3::Response> {
627        #[rustfmt::skip] // rustfmt wants to merge the next two lines
628        let request =
629            assign!(add_3pid::v3::Request::new(client_secret.to_owned(), sid.to_owned()), {
630                auth: auth_data
631            });
632        Ok(self.client.send(request).await?)
633    }
634
635    /// Delete a [Third Party Identifier][3pid] from the homeserver for this
636    /// account.
637    ///
638    /// # Arguments
639    ///
640    /// * `address` - The 3PID being removed.
641    ///
642    /// * `medium` - The type of the 3PID.
643    ///
644    /// * `id_server` - The identity server to unbind from. If not provided, the
645    ///   homeserver should unbind the 3PID from the identity server it was
646    ///   bound to previously.
647    ///
648    /// # Returns
649    ///
650    /// * [`ThirdPartyIdRemovalStatus::Success`] if the 3PID was also unbound
651    ///   from the identity server.
652    ///
653    /// * [`ThirdPartyIdRemovalStatus::NoSupport`] if the 3PID was not unbound
654    ///   from the identity server. This can also mean that the 3PID was not
655    ///   bound to an identity server in the first place.
656    ///
657    /// # Examples
658    ///
659    /// ```no_run
660    /// # use matrix_sdk::Client;
661    /// # use matrix_sdk::ruma::thirdparty::Medium;
662    /// # use matrix_sdk::ruma::api::client::account::ThirdPartyIdRemovalStatus;
663    /// # use url::Url;
664    /// # async {
665    /// # let homeserver = Url::parse("http://localhost:8080")?;
666    /// # let client = Client::new(homeserver).await?;
667    /// # let account = client.account();
668    /// match account
669    ///     .delete_3pid("paul@matrix.org", Medium::Email, None)
670    ///     .await?
671    ///     .id_server_unbind_result
672    /// {
673    ///     ThirdPartyIdRemovalStatus::Success => {
674    ///         println!("3PID unbound from the Identity Server");
675    ///     }
676    ///     _ => println!("Could not unbind 3PID from the Identity Server"),
677    /// }
678    /// # anyhow::Ok(()) };
679    /// ```
680    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
681    /// [`ThirdPartyIdRemovalStatus::Success`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::Success
682    /// [`ThirdPartyIdRemovalStatus::NoSupport`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::NoSupport
683    pub async fn delete_3pid(
684        &self,
685        address: &str,
686        medium: Medium,
687        id_server: Option<&str>,
688    ) -> Result<delete_3pid::v3::Response> {
689        let request = assign!(delete_3pid::v3::Request::new(medium, address.to_owned()), {
690            id_server: id_server.map(ToOwned::to_owned),
691        });
692        Ok(self.client.send(request).await?)
693    }
694
695    /// Get the content of an account data event of statically-known type.
696    ///
697    /// # Examples
698    ///
699    /// ```no_run
700    /// # use matrix_sdk::Client;
701    /// # async {
702    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
703    /// # let account = client.account();
704    /// use matrix_sdk::ruma::events::ignored_user_list::IgnoredUserListEventContent;
705    ///
706    /// let maybe_content = account.account_data::<IgnoredUserListEventContent>().await?;
707    /// if let Some(raw_content) = maybe_content {
708    ///     let content = raw_content.deserialize()?;
709    ///     println!("Ignored users:");
710    ///     for user_id in content.ignored_users.keys() {
711    ///         println!("- {user_id}");
712    ///     }
713    /// }
714    /// # anyhow::Ok(()) };
715    /// ```
716    pub async fn account_data<C>(&self) -> Result<Option<Raw<C>>>
717    where
718        C: GlobalAccountDataEventContent + StaticEventContent,
719    {
720        get_raw_content(self.client.state_store().get_account_data_event_static::<C>().await?)
721    }
722
723    /// Get the content of an account data event of a given type.
724    pub async fn account_data_raw(
725        &self,
726        event_type: GlobalAccountDataEventType,
727    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>> {
728        get_raw_content(self.client.state_store().get_account_data_event(event_type).await?)
729    }
730
731    /// Fetch a global account data event from the server.
732    ///
733    /// The content from the response will not be persisted in the store.
734    ///
735    /// Examples
736    ///
737    /// ```no_run
738    /// # use matrix_sdk::Client;
739    /// # async {
740    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
741    /// # let account = client.account();
742    /// use matrix_sdk::ruma::events::{ignored_user_list::IgnoredUserListEventContent, GlobalAccountDataEventType};
743    ///
744    /// if let Some(raw_content) = account.fetch_account_data(GlobalAccountDataEventType::IgnoredUserList).await? {
745    ///     let content = raw_content.deserialize_as::<IgnoredUserListEventContent>()?;
746    ///
747    ///     println!("Ignored users:");
748    ///
749    ///     for user_id in content.ignored_users.keys() {
750    ///         println!("- {user_id}");
751    ///     }
752    /// }
753    /// # anyhow::Ok(()) };
754    pub async fn fetch_account_data(
755        &self,
756        event_type: GlobalAccountDataEventType,
757    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>> {
758        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
759
760        let request = get_global_account_data::v3::Request::new(own_user.to_owned(), event_type);
761
762        match self.client.send(request).await {
763            Ok(r) => Ok(Some(r.account_data)),
764            Err(e) => {
765                if let Some(kind) = e.client_api_error_kind() {
766                    if kind == &ErrorKind::NotFound {
767                        Ok(None)
768                    } else {
769                        Err(e.into())
770                    }
771                } else {
772                    Err(e.into())
773                }
774            }
775        }
776    }
777
778    /// Set the given account data event.
779    ///
780    /// # Examples
781    ///
782    /// ```no_run
783    /// # use matrix_sdk::Client;
784    /// # async {
785    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
786    /// # let account = client.account();
787    /// use matrix_sdk::ruma::{
788    ///     events::ignored_user_list::{IgnoredUser, IgnoredUserListEventContent},
789    ///     user_id,
790    /// };
791    ///
792    /// let mut content = account
793    ///     .account_data::<IgnoredUserListEventContent>()
794    ///     .await?
795    ///     .map(|c| c.deserialize())
796    ///     .transpose()?
797    ///     .unwrap_or_default();
798    /// content
799    ///     .ignored_users
800    ///     .insert(user_id!("@foo:bar.com").to_owned(), IgnoredUser::new());
801    /// account.set_account_data(content).await?;
802    /// # anyhow::Ok(()) };
803    /// ```
804    pub async fn set_account_data<T>(
805        &self,
806        content: T,
807    ) -> Result<set_global_account_data::v3::Response>
808    where
809        T: GlobalAccountDataEventContent,
810    {
811        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
812
813        let request = set_global_account_data::v3::Request::new(own_user.to_owned(), &content)?;
814
815        Ok(self.client.send(request).await?)
816    }
817
818    /// Set the given raw account data event.
819    pub async fn set_account_data_raw(
820        &self,
821        event_type: GlobalAccountDataEventType,
822        content: Raw<AnyGlobalAccountDataEventContent>,
823    ) -> Result<set_global_account_data::v3::Response> {
824        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
825
826        let request =
827            set_global_account_data::v3::Request::new_raw(own_user.to_owned(), event_type, content);
828
829        Ok(self.client.send(request).await?)
830    }
831
832    /// Marks the room identified by `room_id` as a "direct chat" with each
833    /// user in `user_ids`.
834    ///
835    /// # Arguments
836    ///
837    /// * `room_id` - The room ID of the direct message room.
838    /// * `user_ids` - The user IDs to be associated with this direct message
839    ///   room.
840    pub async fn mark_as_dm(&self, room_id: &RoomId, user_ids: &[OwnedUserId]) -> Result<()> {
841        use ruma::events::direct::DirectEventContent;
842
843        // This function does a read/update/store of an account data event stored on the
844        // homeserver. We first fetch the existing account data event, the event
845        // contains a map which gets updated by this method, finally we upload the
846        // modified event.
847        //
848        // To prevent multiple calls to this method trying to update the map of DMs same
849        // time, and thus trampling on each other we introduce a lock which acts
850        // as a semaphore.
851        let _guard = self.client.locks().mark_as_dm_lock.lock().await;
852
853        // Now we need to mark the room as a DM for ourselves, we fetch the
854        // existing `m.direct` event and append the room to the list of DMs we
855        // have with this user.
856
857        // We are fetching the content from the server because we currently can't rely
858        // on `/sync` giving us the correct data in a timely manner.
859        let raw_content = self.fetch_account_data(GlobalAccountDataEventType::Direct).await?;
860
861        let mut content = if let Some(raw_content) = raw_content {
862            // Log the error and pass it upwards if we fail to deserialize the m.direct
863            // event.
864            raw_content.deserialize_as::<DirectEventContent>().map_err(|err| {
865                error!("unable to deserialize m.direct event content; aborting request to mark {room_id} as dm: {err}");
866                err
867            })?
868        } else {
869            // If there was no m.direct event server-side, create a default one.
870            Default::default()
871        };
872
873        for user_id in user_ids {
874            content.entry(user_id.into()).or_default().push(room_id.to_owned());
875        }
876
877        // TODO: We should probably save the fact that we need to send this out
878        // because otherwise we might end up in a state where we have a DM that
879        // isn't marked as one.
880        self.set_account_data(content).await?;
881
882        Ok(())
883    }
884
885    /// Adds the given user ID to the account's ignore list.
886    pub async fn ignore_user(&self, user_id: &UserId) -> Result<()> {
887        let mut ignored_user_list = self.get_ignored_user_list_event_content().await?;
888        ignored_user_list.ignored_users.insert(user_id.to_owned(), IgnoredUser::new());
889
890        // Updating the account data
891        self.set_account_data(ignored_user_list).await?;
892        // TODO: I think I should reset all the storage and perform a new local sync
893        // here but I don't know how
894        Ok(())
895    }
896
897    /// Removes the given user ID from the account's ignore list.
898    pub async fn unignore_user(&self, user_id: &UserId) -> Result<()> {
899        let mut ignored_user_list = self.get_ignored_user_list_event_content().await?;
900        ignored_user_list.ignored_users.remove(user_id);
901
902        // Updating the account data
903        self.set_account_data(ignored_user_list).await?;
904        // TODO: I think I should reset all the storage and perform a new local sync
905        // here but I don't know how
906        Ok(())
907    }
908
909    async fn get_ignored_user_list_event_content(&self) -> Result<IgnoredUserListEventContent> {
910        let ignored_user_list = self
911            .account_data::<IgnoredUserListEventContent>()
912            .await?
913            .map(|c| c.deserialize())
914            .transpose()?
915            .unwrap_or_default();
916        Ok(ignored_user_list)
917    }
918
919    /// Get the current push rules from storage.
920    ///
921    /// If no push rules event was found, or it fails to deserialize, a ruleset
922    /// with the server-default push rules is returned.
923    ///
924    /// Panics if called when the client is not logged in.
925    pub async fn push_rules(&self) -> Result<Ruleset> {
926        Ok(self
927            .account_data::<PushRulesEventContent>()
928            .await?
929            .and_then(|r| match r.deserialize() {
930                Ok(r) => Some(r.global),
931                Err(e) => {
932                    error!("Push rules event failed to deserialize: {e}");
933                    None
934                }
935            })
936            .unwrap_or_else(|| {
937                Ruleset::server_default(
938                    self.client.user_id().expect("The client should be logged in"),
939                )
940            }))
941    }
942
943    /// Retrieves the user's recently visited room list
944    pub async fn get_recently_visited_rooms(&self) -> Result<Vec<OwnedRoomId>> {
945        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
946        let data = self
947            .client
948            .state_store()
949            .get_kv_data(StateStoreDataKey::RecentlyVisitedRooms(user_id))
950            .await?;
951
952        Ok(data
953            .map(|v| {
954                v.into_recently_visited_rooms()
955                    .expect("Session data is not a list of recently visited rooms")
956            })
957            .unwrap_or_default())
958    }
959
960    /// Moves/inserts the given room to the front of the recently visited list
961    pub async fn track_recently_visited_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
962        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
963
964        // Get the previously stored recently visited rooms
965        let mut recently_visited_rooms = self.get_recently_visited_rooms().await?;
966
967        // Remove all other occurrences of the new room_id
968        recently_visited_rooms.retain(|r| r != &room_id);
969
970        // And insert it as the most recent
971        recently_visited_rooms.insert(0, room_id);
972
973        // Cap the whole list to the VISITED_ROOMS_LIMIT
974        recently_visited_rooms.truncate(Self::VISITED_ROOMS_LIMIT);
975
976        let data = StateStoreDataValue::RecentlyVisitedRooms(recently_visited_rooms);
977        self.client
978            .state_store()
979            .set_kv_data(StateStoreDataKey::RecentlyVisitedRooms(user_id), data)
980            .await?;
981        Ok(())
982    }
983}
984
985fn get_raw_content<Ev, C>(raw: Option<Raw<Ev>>) -> Result<Option<Raw<C>>> {
986    #[derive(Deserialize)]
987    #[serde(bound = "C: Sized")] // Replace default Deserialize bound
988    struct GetRawContent<C> {
989        content: Raw<C>,
990    }
991
992    Ok(raw
993        .map(|event| event.deserialize_as::<GetRawContent<C>>())
994        .transpose()?
995        .map(|get_raw| get_raw.content))
996}