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