Skip to main content

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::{StreamExt, stream};
19#[cfg(feature = "experimental-element-recent-emojis")]
20use itertools::Itertools;
21#[cfg(feature = "experimental-element-recent-emojis")]
22use js_int::uint;
23#[cfg(feature = "experimental-element-recent-emojis")]
24use matrix_sdk_base::recent_emojis::RecentEmojisContent;
25use matrix_sdk_base::{
26    SendOutsideWasm, StateStoreDataKey, StateStoreDataValue, SyncOutsideWasm,
27    media::{MediaFormat, MediaRequestParameters},
28    store::StateStoreExt,
29};
30use mime::Mime;
31#[cfg(feature = "unstable-msc4426")]
32use ruma::SecondsSinceUnixEpoch;
33#[cfg(feature = "experimental-element-recent-emojis")]
34use ruma::api::client::config::set_global_account_data::v3::Request as UpdateGlobalAccountDataRequest;
35#[cfg(feature = "unstable-msc4426")]
36use ruma::profile::{CallProfileField, StatusProfileField};
37use ruma::{
38    ClientSecret, MxcUri, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, SessionId, UInt, UserId,
39    api::{
40        Metadata,
41        client::{
42            account::{
43                add_3pid, change_password, deactivate, delete_3pid, get_3pids,
44                request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
45                request_openid_token,
46            },
47            config::{get_global_account_data, set_global_account_data},
48            profile::{
49                DisplayName, StaticProfileField, delete_profile_field, get_avatar_url, get_profile,
50                get_profile_field, set_avatar_url, set_display_name, set_profile_field,
51            },
52            uiaa::AuthData,
53        },
54        error::ErrorKind,
55    },
56    assign,
57    events::{
58        AnyGlobalAccountDataEventContent, GlobalAccountDataEvent, GlobalAccountDataEventContent,
59        GlobalAccountDataEventType, StaticEventContent,
60        ignored_user_list::{IgnoredUser, IgnoredUserListEventContent},
61        invite_permission_config::InvitePermissionConfigEventContent,
62        media_preview_config::{
63            InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews,
64            UnstableMediaPreviewConfigEventContent,
65        },
66        push_rules::PushRulesEventContent,
67        room::MediaSource,
68    },
69    profile::{ProfileFieldName, ProfileFieldValue, UserProfileChanges, UserProfileUpdate},
70    push::Ruleset,
71    serde::Raw,
72    thirdparty::Medium,
73};
74use serde::Deserialize;
75use tracing::{debug, error, warn};
76
77use crate::{Client, Error, Result, config::RequestConfig};
78
79/// The maximum number of recent emojis that should be stored and loaded.
80#[cfg(feature = "experimental-element-recent-emojis")]
81const MAX_RECENT_EMOJI_COUNT: usize = 100;
82
83/// A high-level API to manage the client owner's account.
84///
85/// All the methods on this struct send a request to the homeserver.
86#[derive(Debug, Clone)]
87pub struct Account {
88    /// The underlying HTTP client.
89    client: Client,
90}
91
92impl Account {
93    /// The maximum number of visited room identifiers to keep in the state
94    /// store.
95    const VISITED_ROOMS_LIMIT: usize = 20;
96
97    pub(crate) fn new(client: Client) -> Self {
98        Self { client }
99    }
100
101    /// Get the display name of the account.
102    ///
103    /// # Examples
104    ///
105    /// ```no_run
106    /// # use matrix_sdk::Client;
107    /// # use url::Url;
108    /// # async {
109    /// # let homeserver = Url::parse("http://example.com")?;
110    /// let user = "example";
111    /// let client = Client::new(homeserver).await?;
112    /// client.matrix_auth().login_username(user, "password").send().await?;
113    ///
114    /// if let Some(name) = client.account().get_display_name().await? {
115    ///     println!("Logged in as user '{user}' with display name '{name}'");
116    /// }
117    /// # anyhow::Ok(()) };
118    /// ```
119    pub async fn get_display_name(&self) -> Result<Option<String>> {
120        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
121        self.fetch_profile_field_of_static::<DisplayName>(user_id.to_owned()).await
122    }
123
124    /// Set the display name of the account.
125    ///
126    /// # Examples
127    ///
128    /// ```no_run
129    /// # use matrix_sdk::Client;
130    /// # use url::Url;
131    /// # async {
132    /// # let homeserver = Url::parse("http://example.com")?;
133    /// let user = "example";
134    /// let client = Client::new(homeserver).await?;
135    /// client.matrix_auth().login_username(user, "password").send().await?;
136    ///
137    /// client.account().set_display_name(Some("Alice")).await?;
138    /// # anyhow::Ok(()) };
139    /// ```
140    pub async fn set_display_name(&self, name: Option<&str>) -> Result<()> {
141        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
142
143        // Prefer the endpoint to delete profile fields, if it is supported.
144        if name.is_none() {
145            let versions = self.client.supported_versions().await?;
146
147            if delete_profile_field::v3::Request::PATH_BUILDER.is_supported(&versions) {
148                return self.delete_profile_field(ProfileFieldName::DisplayName).await;
149            }
150        }
151
152        // If name is `Some(_)`, this endpoint is the same as
153        // `set_profile_field`, but we still need to use it in case it is `None`
154        // and the server doesn't support the delete endpoint yet.
155        #[allow(deprecated)]
156        let request =
157            set_display_name::v3::Request::new(user_id.to_owned(), name.map(ToOwned::to_owned));
158        self.client.send(request).await?;
159
160        Ok(())
161    }
162
163    /// Request an OpenID token for the current account.
164    pub async fn request_openid_token(&self) -> Result<request_openid_token::v3::Response> {
165        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
166
167        self.client
168            .send(request_openid_token::v3::Request::new(user_id.to_owned()))
169            .await
170            .map_err(|error| Error::Http(Box::new(error)))
171    }
172
173    /// Get the MXC URI of the account's avatar, if set.
174    ///
175    /// This always sends a request to the server to retrieve this information.
176    /// If successful, this fills the cache, and makes it so that
177    /// [`Self::get_cached_avatar_url`] will always return something.
178    ///
179    /// # Examples
180    ///
181    /// ```no_run
182    /// # use matrix_sdk::Client;
183    /// # use url::Url;
184    /// # async {
185    /// # let homeserver = Url::parse("http://example.com")?;
186    /// # let user = "example";
187    /// let client = Client::new(homeserver).await?;
188    /// client.matrix_auth().login_username(user, "password").send().await?;
189    ///
190    /// if let Some(url) = client.account().get_avatar_url().await? {
191    ///     println!("Your avatar's mxc url is {url}");
192    /// }
193    /// # anyhow::Ok(()) };
194    /// ```
195    pub async fn get_avatar_url(&self) -> Result<Option<OwnedMxcUri>> {
196        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
197
198        #[allow(deprecated)] // get_profile_field fails when the response is {"avatar_url":null} 🤷‍♂️
199        let request = get_avatar_url::v3::Request::new(user_id.to_owned());
200        let avatar_url = self
201            .client
202            .send(request)
203            .with_request_config(RequestConfig::short_retry().force_auth())
204            .await?
205            .avatar_url;
206
207        if let Some(url) = avatar_url.clone() {
208            // If an avatar is found cache it.
209            let _ = self
210                .client
211                .state_store()
212                .set_kv_data(
213                    StateStoreDataKey::UserAvatarUrl(user_id),
214                    StateStoreDataValue::UserAvatarUrl(url),
215                )
216                .await;
217        } else {
218            // If there is no avatar the user has removed it and we uncache it.
219            let _ = self
220                .client
221                .state_store()
222                .remove_kv_data(StateStoreDataKey::UserAvatarUrl(user_id))
223                .await;
224        }
225        Ok(avatar_url)
226    }
227
228    /// Get the URL of the account's avatar, if is stored in cache.
229    pub async fn get_cached_avatar_url(&self) -> Result<Option<OwnedMxcUri>> {
230        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
231        let data = self
232            .client
233            .state_store()
234            .get_kv_data(StateStoreDataKey::UserAvatarUrl(user_id))
235            .await?;
236        Ok(data.map(|v| v.into_user_avatar_url().expect("Session data is not a user avatar url")))
237    }
238
239    /// Set the MXC URI of the account's avatar.
240    ///
241    /// The avatar is unset if `url` is `None`.
242    pub async fn set_avatar_url(&self, url: Option<&MxcUri>) -> Result<()> {
243        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
244
245        // Prefer the endpoint to delete profile fields, if it is supported.
246        if url.is_none() {
247            let versions = self.client.supported_versions().await?;
248
249            if delete_profile_field::v3::Request::PATH_BUILDER.is_supported(&versions) {
250                return self.delete_profile_field(ProfileFieldName::AvatarUrl).await;
251            }
252        }
253
254        // If url is `Some(_)`, this endpoint is the same as
255        // `set_profile_field`, but we still need to use it in case it is `None`
256        // and the server doesn't support the delete endpoint yet.
257        #[allow(deprecated)]
258        let request =
259            set_avatar_url::v3::Request::new(user_id.to_owned(), url.map(ToOwned::to_owned));
260        self.client.send(request).await?;
261
262        Ok(())
263    }
264
265    /// Get the account's avatar, if set.
266    ///
267    /// Returns the avatar.
268    ///
269    /// If a thumbnail is requested no guarantee on the size of the image is
270    /// given.
271    ///
272    /// # Arguments
273    ///
274    /// - `format` - The desired format of the avatar.
275    ///
276    /// # Examples
277    ///
278    /// ```no_run
279    /// # use matrix_sdk::Client;
280    /// # use matrix_sdk::ruma::room_id;
281    /// # use matrix_sdk::media::MediaFormat;
282    /// # use url::Url;
283    /// # async {
284    /// # let homeserver = Url::parse("http://example.com")?;
285    /// # let user = "example";
286    /// let client = Client::new(homeserver).await?;
287    /// client.matrix_auth().login_username(user, "password").send().await?;
288    ///
289    /// if let Some(avatar) = client.account().get_avatar(MediaFormat::File).await?
290    /// {
291    ///     std::fs::write("avatar.png", avatar);
292    /// }
293    /// # anyhow::Ok(()) };
294    /// ```
295    pub async fn get_avatar(&self, format: MediaFormat) -> Result<Option<Vec<u8>>> {
296        if let Some(url) = self.get_avatar_url().await? {
297            let request = MediaRequestParameters { source: MediaSource::Plain(url), format };
298            Ok(Some(self.client.media().get_media_content(&request, true).await?))
299        } else {
300            Ok(None)
301        }
302    }
303
304    /// Upload and set the account's avatar.
305    ///
306    /// This will upload the data produced by the reader to the homeserver's
307    /// content repository, and set the user's avatar to the MXC URI for the
308    /// uploaded file.
309    ///
310    /// This is a convenience method for calling [`Media::upload()`], followed
311    /// by [`Account::set_avatar_url()`].
312    ///
313    /// Returns the MXC URI of the uploaded avatar.
314    ///
315    /// # Examples
316    ///
317    /// ```no_run
318    /// # use std::fs;
319    /// # use matrix_sdk::Client;
320    /// # use url::Url;
321    /// # async {
322    /// # let homeserver = Url::parse("http://localhost:8080")?;
323    /// # let client = Client::new(homeserver).await?;
324    /// let image = fs::read("/home/example/selfie.jpg")?;
325    ///
326    /// client.account().upload_avatar(&mime::IMAGE_JPEG, image).await?;
327    /// # anyhow::Ok(()) };
328    /// ```
329    ///
330    /// [`Media::upload()`]: crate::Media::upload
331    pub async fn upload_avatar(&self, content_type: &Mime, data: Vec<u8>) -> Result<OwnedMxcUri> {
332        let upload_response = self.client.media().upload(content_type, data, None).await?;
333        self.set_avatar_url(Some(&upload_response.content_uri)).await?;
334        Ok(upload_response.content_uri)
335    }
336
337    /// Get the profile of this account.
338    ///
339    /// Allows to get all the profile data in a single call.
340    ///
341    /// # Examples
342    ///
343    /// ```no_run
344    /// # use matrix_sdk::Client;
345    /// use ruma::api::client::profile::{AvatarUrl, DisplayName};
346    /// # use url::Url;
347    /// # async {
348    /// # let homeserver = Url::parse("http://localhost:8080")?;
349    /// # let client = Client::new(homeserver).await?;
350    ///
351    /// let profile = client.account().fetch_user_profile().await?;
352    /// let display_name = profile.get_static::<DisplayName>()?;
353    /// let avatar_url = profile.get_static::<AvatarUrl>()?;
354    ///
355    /// println!("You are '{display_name:?}' with avatar '{avatar_url:?}'");
356    /// # anyhow::Ok(()) };
357    /// ```
358    pub async fn fetch_user_profile(&self) -> Result<get_profile::v3::Response> {
359        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
360        self.fetch_user_profile_of(user_id).await
361    }
362
363    /// Get the profile for a given user id
364    ///
365    /// # Arguments
366    ///
367    /// * `user_id` the matrix id this function downloads the profile for
368    pub async fn fetch_user_profile_of(
369        &self,
370        user_id: &UserId,
371    ) -> Result<get_profile::v3::Response> {
372        let request = get_profile::v3::Request::new(user_id.to_owned());
373        Ok(self
374            .client
375            .send(request)
376            .with_request_config(RequestConfig::short_retry().force_auth())
377            .await?)
378    }
379
380    /// Get the given field from the given user's profile.
381    ///
382    /// # Arguments
383    ///
384    /// - `user_id` - The ID of the user to get the profile field of.
385    /// - `field` - The name of the profile field to get.
386    ///
387    /// # Returns
388    ///
389    /// Returns an error if the request fails or if deserialization of the
390    /// response fails.
391    ///
392    /// If the field is not set, the server should respond with an error with an
393    /// [`ErrorCode::NotFound`], but it might also respond with an empty
394    /// response, which would result in `Ok(None)`. Note that this error code
395    /// might also mean that the given user ID doesn't exist.
396    ///
397    /// [`ErrorCode::NotFound`]: ruma::api::error::ErrorCode::NotFound
398    pub async fn fetch_profile_field_of(
399        &self,
400        user_id: OwnedUserId,
401        field: ProfileFieldName,
402    ) -> Result<Option<ProfileFieldValue>> {
403        let request = get_profile_field::v3::Request::new(user_id, field);
404        let response = self
405            .client
406            .send(request)
407            .with_request_config(RequestConfig::short_retry().force_auth())
408            .await?;
409
410        Ok(response.value)
411    }
412
413    /// Get the given statically-known field from the given user's profile.
414    ///
415    /// # Arguments
416    ///
417    /// - `user_id` - The ID of the user to get the profile field of.
418    ///
419    /// # Returns
420    ///
421    /// Returns an error if the request fails or if deserialization of the
422    /// response fails.
423    ///
424    /// If the field is not set, the server should respond with an error with an
425    /// [`ErrorCode::NotFound`], but it might also respond with an empty
426    /// response, which would result in `Ok(None)`. Note that this error code
427    /// might also mean that the given user ID doesn't exist.
428    ///
429    /// [`ErrorCode::NotFound`]: ruma::api::error::ErrorCode::NotFound
430    pub async fn fetch_profile_field_of_static<F>(
431        &self,
432        user_id: OwnedUserId,
433    ) -> Result<Option<F::Value>>
434    where
435        F: StaticProfileField
436            + std::fmt::Debug
437            + Clone
438            + SendOutsideWasm
439            + SyncOutsideWasm
440            + 'static,
441        F::Value: SendOutsideWasm + SyncOutsideWasm,
442    {
443        let request = get_profile_field::v3::Request::new_static::<F>(user_id);
444        let response = self
445            .client
446            .send(request)
447            .with_request_config(RequestConfig::short_retry().force_auth())
448            .await?;
449
450        Ok(response.value)
451    }
452
453    /// Set the user's status (MSC4426 `m.status` profile field).
454    ///
455    /// Replaces any existing status. Use [`Self::clear_status`] to remove it.
456    ///
457    /// # Arguments
458    ///
459    /// - `emoji` - the status emoji. The MSC limits this to 32 bytes; not
460    ///   enforced client-side.
461    /// - `text` - the status text. The MSC limits this to 256 bytes; not
462    ///   enforced client-side.
463    #[cfg(feature = "unstable-msc4426")]
464    pub async fn set_status(&self, emoji: String, text: String) -> Result<()> {
465        let value = StatusProfileField::new(text, emoji);
466        self.set_profile_field(ProfileFieldValue::Status(value)).await
467    }
468
469    /// Clear the user's status (deletes the MSC4426 `m.status` profile field).
470    #[cfg(feature = "unstable-msc4426")]
471    pub async fn clear_status(&self) -> Result<()> {
472        self.delete_profile_field(ProfileFieldName::Status).await
473    }
474
475    /// Set the user's call indicator (MSC4426 `m.call` profile field).
476    ///
477    /// # Arguments
478    ///
479    /// - `call_joined_ts` - when the user joined the current call, in seconds
480    ///   since the Unix epoch. `None` if the joined time isn't known.
481    #[cfg(feature = "unstable-msc4426")]
482    pub async fn set_call(&self, call_joined_ts: Option<SecondsSinceUnixEpoch>) -> Result<()> {
483        let mut value = CallProfileField::new();
484        value.call_joined_ts = call_joined_ts;
485        self.set_profile_field(ProfileFieldValue::Call(value)).await
486    }
487
488    /// Clear the user's call indicator (deletes the MSC4426 `m.call` profile
489    /// field).
490    #[cfg(feature = "unstable-msc4426")]
491    pub async fn clear_call(&self) -> Result<()> {
492        self.delete_profile_field(ProfileFieldName::Call).await
493    }
494
495    /// Set the given field of our own user's profile.
496    ///
497    /// [`Client::homeserver_capabilities()`] should be called first to check it
498    /// the field can be set on the homeserver.
499    ///
500    /// # Arguments
501    ///
502    /// - `value` - The value of the profile field to set.
503    ///
504    /// # Returns
505    ///
506    /// Returns an error if the request fails.
507    pub async fn set_profile_field(&self, value: ProfileFieldValue) -> Result<()> {
508        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
509        let request = set_profile_field::v3::Request::new(user_id.to_owned(), value.clone());
510        self.client.send(request).await?;
511
512        let mut changes = UserProfileChanges::new();
513        changes.insert_updated_value(value);
514        self.own_profile_updated(changes).await;
515
516        Ok(())
517    }
518
519    /// Delete the given field of our own user's profile.
520    ///
521    /// [`Client::homeserver_capabilities()`] should be called first to check it
522    /// the field can be modified on the homeserver.
523    ///
524    /// # Arguments
525    ///
526    /// - `field` - The profile field to delete.
527    ///
528    /// # Returns
529    ///
530    /// Returns an error if the server doesn't support extended profile fields
531    /// of if the request fails in some other way.
532    pub async fn delete_profile_field(&self, field: ProfileFieldName) -> Result<()> {
533        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
534        let request = delete_profile_field::v3::Request::new(user_id.to_owned(), field.clone());
535        self.client.send(request).await?;
536
537        let mut changes = UserProfileChanges::new();
538        changes.removed.push(field);
539        self.own_profile_updated(changes).await;
540
541        Ok(())
542    }
543
544    /// Apply the given changes to the locally stored copy of our own profile,
545    /// so they are observable before the next sync reflects them.
546    async fn own_profile_updated(&self, changes: UserProfileChanges) {
547        match self.client.is_global_profile_sync_enabled().await {
548            Ok(true) => {}
549
550            Ok(false) => {
551                debug!("Server doesn't support global profile sync: skip the local echo.");
552                return;
553            }
554
555            Err(error) => {
556                warn!(?error, "Unknown support for global profile sync: skipping the local echo.");
557                return;
558            }
559        }
560
561        if let Err(error) =
562            self.client.base_client().own_profile_updated(UserProfileUpdate::Updated(changes)).await
563        {
564            // The homeserver has already accepted the changes at this point, so
565            // we only need to log the failure.
566            warn!(?error, "Failed to update the locally stored copy of our own profile");
567        }
568    }
569
570    /// Change the password of the account.
571    ///
572    /// # Arguments
573    ///
574    /// - `new_password` - The new password to set.
575    /// - `auth_data` - This request uses the [User-Interactive Authentication
576    ///   API][uiaa]. The first request needs to set this to `None` and will
577    ///   always fail with an [`UiaaResponse`]. The response will contain
578    ///   information for the interactive auth and the same request needs to be
579    ///   made but this time with some `auth_data` provided.
580    ///
581    /// # Returns
582    ///
583    /// This method might return an [`ErrorKind::WeakPassword`] error if the new
584    /// password is considered insecure by the homeserver, with details about
585    /// the strength requirements in the error's message.
586    ///
587    /// # Examples
588    ///
589    /// ```no_run
590    /// # use matrix_sdk::Client;
591    /// # use matrix_sdk::ruma::{
592    /// #     api::client::{
593    /// #         account::change_password::v3::{Request as ChangePasswordRequest},
594    /// #         uiaa::{AuthData, Dummy},
595    /// #     },
596    /// #     assign,
597    /// # };
598    /// # use url::Url;
599    /// # async {
600    /// # let homeserver = Url::parse("http://localhost:8080")?;
601    /// # let client = Client::new(homeserver).await?;
602    /// client.account().change_password(
603    ///     "myverysecretpassword",
604    ///     Some(AuthData::Dummy(Dummy::new())),
605    /// ).await?;
606    /// # anyhow::Ok(()) };
607    /// ```
608    ///
609    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
610    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
611    /// [`ErrorKind::WeakPassword`]: ruma::api::error::ErrorKind::WeakPassword
612    pub async fn change_password(
613        &self,
614        new_password: &str,
615        auth_data: Option<AuthData>,
616    ) -> Result<change_password::v3::Response> {
617        let request = assign!(change_password::v3::Request::new(new_password.to_owned()), {
618            auth: auth_data,
619        });
620        Ok(self.client.send(request).await?)
621    }
622
623    /// Deactivate this account definitively.
624    ///
625    /// # Arguments
626    ///
627    /// - `id_server` - The identity server from which to unbind the user’s
628    ///   [Third Party Identifiers][3pid].
629    ///
630    /// - `auth_data` - This request uses the [User-Interactive Authentication
631    ///   API][uiaa]. The first request needs to set this to `None` and will
632    ///   always fail with an [`UiaaResponse`]. The response will contain
633    ///   information for the interactive auth and the same request needs to be
634    ///   made but this time with some `auth_data` provided.
635    ///
636    /// - `erase` - Whether the user would like their content to be erased as
637    ///   much as possible from the server.
638    ///
639    /// # Examples
640    ///
641    /// ```no_run
642    /// # use matrix_sdk::Client;
643    /// # use matrix_sdk::ruma::{
644    /// #     api::client::{
645    /// #         account::change_password::v3::{Request as ChangePasswordRequest},
646    /// #         uiaa::{AuthData, Dummy},
647    /// #     },
648    /// #     assign,
649    /// # };
650    /// # use url::Url;
651    /// # async {
652    /// # let homeserver = Url::parse("http://localhost:8080")?;
653    /// # let client = Client::new(homeserver).await?;
654    /// # let account = client.account();
655    /// let response = account.deactivate(None, None, false).await;
656    ///
657    /// // Proceed with UIAA.
658    /// # anyhow::Ok(()) };
659    /// ```
660    ///
661    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
662    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
663    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
664    pub async fn deactivate(
665        &self,
666        id_server: Option<&str>,
667        auth_data: Option<AuthData>,
668        erase_data: bool,
669    ) -> Result<deactivate::v3::Response> {
670        let request = assign!(deactivate::v3::Request::new(), {
671            id_server: id_server.map(ToOwned::to_owned),
672            auth: auth_data,
673            erase: erase_data,
674        });
675        Ok(self.client.send(request).await?)
676    }
677
678    /// Get the registered [Third Party Identifiers][3pid] on the homeserver of
679    /// the account.
680    ///
681    /// These 3PIDs may be used by the homeserver to authenticate the user
682    /// during sensitive operations.
683    ///
684    /// # Examples
685    ///
686    /// ```no_run
687    /// # use matrix_sdk::Client;
688    /// # use url::Url;
689    /// # async {
690    /// # let homeserver = Url::parse("http://localhost:8080")?;
691    /// # let client = Client::new(homeserver).await?;
692    /// let threepids = client.account().get_3pids().await?.threepids;
693    ///
694    /// for threepid in threepids {
695    ///     println!(
696    ///         "Found 3PID '{}' of type '{}'",
697    ///         threepid.address, threepid.medium
698    ///     );
699    /// }
700    /// # anyhow::Ok(()) };
701    /// ```
702    ///
703    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
704    pub async fn get_3pids(&self) -> Result<get_3pids::v3::Response> {
705        let request = get_3pids::v3::Request::new();
706        Ok(self.client.send(request).await?)
707    }
708
709    /// Request a token to validate an email address as a
710    /// [Third Party Identifier][3pid].
711    ///
712    /// This is the first step in registering an email address as 3PID. Next,
713    /// call [`Account::add_3pid()`] with the same `client_secret` and the
714    /// returned `sid`.
715    ///
716    /// # Arguments
717    ///
718    /// - `client_secret` - A client-generated secret string used to protect
719    ///   this session.
720    ///
721    /// - `email` - The email address to validate.
722    /// - `send_attempt` - The attempt number. This number needs to be
723    ///   incremented if you want to request another token for the same
724    ///   validation.
725    ///
726    /// # Returns
727    ///
728    /// - `sid` - The session ID to be used in following requests for this 3PID.
729    /// - `submit_url` - If present, the user will submit the token to the
730    ///   client, that must send it to this URL. If not, the client will not be
731    ///   involved in the token submission.
732    ///
733    /// This method might return an [`ErrorKind::ThreepidInUse`] error if the
734    /// email address is already registered for this account or another, or an
735    /// [`ErrorKind::ThreepidDenied`] error if it is denied.
736    ///
737    /// # Examples
738    ///
739    /// ```no_run
740    /// # use matrix_sdk::Client;
741    /// # use matrix_sdk::ruma::{ClientSecret, uint};
742    /// # use url::Url;
743    /// # async {
744    /// # let homeserver = Url::parse("http://localhost:8080")?;
745    /// # let client = Client::new(homeserver).await?;
746    /// # let account = client.account();
747    /// # let secret = ClientSecret::parse("secret")?;
748    /// let token_response = account
749    ///     .request_3pid_email_token(&secret, "john@matrix.org", uint!(0))
750    ///     .await?;
751    ///
752    /// // Wait for the user to confirm that the token was submitted or prompt
753    /// // the user for the token and send it to submit_url.
754    ///
755    /// let uiaa_response =
756    ///     account.add_3pid(&secret, &token_response.sid, None).await;
757    ///
758    /// // Proceed with UIAA.
759    /// # anyhow::Ok(()) };
760    /// ```
761    ///
762    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
763    /// [`ErrorKind::ThreepidInUse`]: ruma::api::error::ErrorKind::ThreepidInUse
764    /// [`ErrorKind::ThreepidDenied`]: ruma::api::error::ErrorKind::ThreepidDenied
765    pub async fn request_3pid_email_token(
766        &self,
767        client_secret: &ClientSecret,
768        email: &str,
769        send_attempt: UInt,
770    ) -> Result<request_3pid_management_token_via_email::v3::Response> {
771        let request = request_3pid_management_token_via_email::v3::Request::new(
772            client_secret.to_owned(),
773            email.to_owned(),
774            send_attempt,
775        );
776        Ok(self.client.send(request).await?)
777    }
778
779    /// Request a token to validate a phone number as a
780    /// [Third Party Identifier][3pid].
781    ///
782    /// This is the first step in registering a phone number as 3PID. Next, call
783    /// [`Account::add_3pid()`] with the same `client_secret` and the returned
784    /// `sid`.
785    ///
786    /// # Arguments
787    ///
788    /// - `client_secret` - A client-generated secret string used to protect
789    ///   this session.
790    ///
791    /// - `country` - The two-letter uppercase ISO-3166-1 alpha-2 country code
792    ///   that the number in phone_number should be parsed as if it were dialled
793    ///   from.
794    ///
795    /// - `phone_number` - The phone number to validate.
796    /// - `send_attempt` - The attempt number. This number needs to be
797    ///   incremented if you want to request another token for the same
798    ///   validation.
799    ///
800    /// # Returns
801    ///
802    /// - `sid` - The session ID to be used in following requests for this 3PID.
803    /// - `submit_url` - If present, the user will submit the token to the
804    ///   client, that must send it to this URL. If not, the client will not be
805    ///   involved in the token submission.
806    ///
807    /// This method might return an [`ErrorKind::ThreepidInUse`] error if the
808    /// phone number is already registered for this account or another, or an
809    /// [`ErrorKind::ThreepidDenied`] error if it is denied.
810    ///
811    /// # Examples
812    ///
813    /// ```no_run
814    /// # use matrix_sdk::Client;
815    /// # use matrix_sdk::ruma::{ClientSecret, uint};
816    /// # use url::Url;
817    /// # async {
818    /// # let homeserver = Url::parse("http://localhost:8080")?;
819    /// # let client = Client::new(homeserver).await?;
820    /// # let account = client.account();
821    /// # let secret = ClientSecret::parse("secret")?;
822    /// let token_response = account
823    ///     .request_3pid_msisdn_token(&secret, "FR", "0123456789", uint!(0))
824    ///     .await?;
825    ///
826    /// // Wait for the user to confirm that the token was submitted or prompt
827    /// // the user for the token and send it to submit_url.
828    ///
829    /// let uiaa_response =
830    ///     account.add_3pid(&secret, &token_response.sid, None).await;
831    ///
832    /// // Proceed with UIAA.
833    /// # anyhow::Ok(()) };
834    /// ```
835    ///
836    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
837    /// [`ErrorKind::ThreepidInUse`]: ruma::api::error::ErrorKind::ThreepidInUse
838    /// [`ErrorKind::ThreepidDenied`]: ruma::api::error::ErrorKind::ThreepidDenied
839    pub async fn request_3pid_msisdn_token(
840        &self,
841        client_secret: &ClientSecret,
842        country: &str,
843        phone_number: &str,
844        send_attempt: UInt,
845    ) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
846        let request = request_3pid_management_token_via_msisdn::v3::Request::new(
847            client_secret.to_owned(),
848            country.to_owned(),
849            phone_number.to_owned(),
850            send_attempt,
851        );
852        Ok(self.client.send(request).await?)
853    }
854
855    /// Add a [Third Party Identifier][3pid] on the homeserver for this account.
856    ///
857    /// This 3PID may be used by the homeserver to authenticate the user during
858    /// sensitive operations.
859    ///
860    /// This method should be called after
861    /// [`Account::request_3pid_email_token()`] or
862    /// [`Account::request_3pid_msisdn_token()`] to complete the 3PID
863    ///
864    /// # Arguments
865    ///
866    /// - `client_secret` - The same client secret used in
867    ///   [`Account::request_3pid_email_token()`] or
868    ///   [`Account::request_3pid_msisdn_token()`].
869    ///
870    /// - `sid` - The session ID returned in
871    ///   [`Account::request_3pid_email_token()`] or
872    ///   [`Account::request_3pid_msisdn_token()`].
873    ///
874    /// - `auth_data` - This request uses the [User-Interactive Authentication
875    ///   API][uiaa]. The first request needs to set this to `None` and will
876    ///   always fail with an [`UiaaResponse`]. The response will contain
877    ///   information for the interactive auth and the same request needs to be
878    ///   made but this time with some `auth_data` provided.
879    ///
880    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
881    /// [uiaa]: https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
882    /// [`UiaaResponse`]: ruma::api::client::uiaa::UiaaResponse
883    pub async fn add_3pid(
884        &self,
885        client_secret: &ClientSecret,
886        sid: &SessionId,
887        auth_data: Option<AuthData>,
888    ) -> Result<add_3pid::v3::Response> {
889        #[rustfmt::skip] // rustfmt wants to merge the next two lines
890        let request =
891            assign!(add_3pid::v3::Request::new(client_secret.to_owned(), sid.to_owned()), {
892                auth: auth_data
893            });
894        Ok(self.client.send(request).await?)
895    }
896
897    /// Delete a [Third Party Identifier][3pid] from the homeserver for this
898    /// account.
899    ///
900    /// # Arguments
901    ///
902    /// - `address` - The 3PID being removed.
903    /// - `medium` - The type of the 3PID.
904    /// - `id_server` - The identity server to unbind from. If not provided, the
905    ///   homeserver should unbind the 3PID from the identity server it was
906    ///   bound to previously.
907    ///
908    /// # Returns
909    ///
910    /// - [`ThirdPartyIdRemovalStatus::Success`] if the 3PID was also unbound
911    ///   from the identity server.
912    ///
913    /// - [`ThirdPartyIdRemovalStatus::NoSupport`] if the 3PID was not unbound
914    ///   from the identity server. This can also mean that the 3PID was not
915    ///   bound to an identity server in the first place.
916    ///
917    /// # Examples
918    ///
919    /// ```no_run
920    /// # use matrix_sdk::Client;
921    /// # use matrix_sdk::ruma::thirdparty::Medium;
922    /// # use matrix_sdk::ruma::api::client::account::ThirdPartyIdRemovalStatus;
923    /// # use url::Url;
924    /// # async {
925    /// # let homeserver = Url::parse("http://localhost:8080")?;
926    /// # let client = Client::new(homeserver).await?;
927    /// # let account = client.account();
928    /// match account
929    ///     .delete_3pid("paul@matrix.org", Medium::Email, None)
930    ///     .await?
931    ///     .id_server_unbind_result
932    /// {
933    ///     ThirdPartyIdRemovalStatus::Success => {
934    ///         println!("3PID unbound from the Identity Server");
935    ///     }
936    ///     _ => println!("Could not unbind 3PID from the Identity Server"),
937    /// }
938    /// # anyhow::Ok(()) };
939    /// ```
940    ///
941    /// [3pid]: https://spec.matrix.org/v1.2/appendices/#3pid-types
942    /// [`ThirdPartyIdRemovalStatus::Success`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::Success
943    /// [`ThirdPartyIdRemovalStatus::NoSupport`]: ruma::api::client::account::ThirdPartyIdRemovalStatus::NoSupport
944    pub async fn delete_3pid(
945        &self,
946        address: &str,
947        medium: Medium,
948        id_server: Option<&str>,
949    ) -> Result<delete_3pid::v3::Response> {
950        let request = assign!(delete_3pid::v3::Request::new(medium, address.to_owned()), {
951            id_server: id_server.map(ToOwned::to_owned),
952        });
953        Ok(self.client.send(request).await?)
954    }
955
956    /// Get the content of an account data event of statically-known type, from
957    /// storage.
958    ///
959    /// # Examples
960    ///
961    /// ```no_run
962    /// # use matrix_sdk::Client;
963    /// # async {
964    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
965    /// # let account = client.account();
966    /// use matrix_sdk::ruma::events::ignored_user_list::IgnoredUserListEventContent;
967    ///
968    /// let maybe_content = account.account_data::<IgnoredUserListEventContent>().await?;
969    /// if let Some(raw_content) = maybe_content {
970    ///     let content = raw_content.deserialize()?;
971    ///     println!("Ignored users:");
972    ///     for user_id in content.ignored_users.keys() {
973    ///         println!("- {user_id}");
974    ///     }
975    /// }
976    /// # anyhow::Ok(()) };
977    /// ```
978    pub async fn account_data<C>(&self) -> Result<Option<Raw<C>>>
979    where
980        C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = ruma::events::False>,
981    {
982        get_raw_content(self.client.state_store().get_account_data_event_static::<C>().await?)
983    }
984
985    /// Get the content of an account data event of a given type, from storage.
986    pub async fn account_data_raw(
987        &self,
988        event_type: GlobalAccountDataEventType,
989    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>> {
990        get_raw_content(self.client.state_store().get_account_data_event(event_type).await?)
991    }
992
993    /// Fetch a global account data event from the server.
994    ///
995    /// The content from the response will not be persisted in the store.
996    ///
997    /// Examples
998    ///
999    /// ```no_run
1000    /// # use matrix_sdk::Client;
1001    /// # async {
1002    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
1003    /// # let account = client.account();
1004    /// use matrix_sdk::ruma::events::{
1005    ///     GlobalAccountDataEventType,
1006    ///     ignored_user_list::IgnoredUserListEventContent,
1007    /// };
1008    ///
1009    /// if let Some(raw_content) = account
1010    ///     .fetch_account_data(GlobalAccountDataEventType::IgnoredUserList)
1011    ///     .await?
1012    /// {
1013    ///     let content = raw_content
1014    ///         .deserialize_as_unchecked::<IgnoredUserListEventContent>()?;
1015    ///
1016    ///     println!("Ignored users:");
1017    ///
1018    ///     for user_id in content.ignored_users.keys() {
1019    ///         println!("- {user_id}");
1020    ///     }
1021    /// }
1022    /// # anyhow::Ok(()) };
1023    /// ```
1024    pub async fn fetch_account_data(
1025        &self,
1026        event_type: GlobalAccountDataEventType,
1027    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>> {
1028        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1029
1030        let request = get_global_account_data::v3::Request::new(own_user.to_owned(), event_type);
1031
1032        match self.client.send(request).await {
1033            Ok(r) => Ok(Some(r.account_data)),
1034            Err(e) => {
1035                if let Some(kind) = e.client_api_error_kind() {
1036                    if kind == &ErrorKind::NotFound { Ok(None) } else { Err(e.into()) }
1037                } else {
1038                    Err(e.into())
1039                }
1040            }
1041        }
1042    }
1043
1044    /// Fetch an account data event of statically-known type from the server.
1045    pub async fn fetch_account_data_static<C>(&self) -> Result<Option<Raw<C>>>
1046    where
1047        C: GlobalAccountDataEventContent + StaticEventContent<IsPrefix = ruma::events::False>,
1048    {
1049        Ok(self.fetch_account_data(C::TYPE.into()).await?.map(Raw::cast_unchecked))
1050    }
1051
1052    /// Set the given account data event.
1053    ///
1054    /// # Examples
1055    ///
1056    /// ```no_run
1057    /// # use matrix_sdk::Client;
1058    /// # async {
1059    /// # let client = Client::new("http://localhost:8080".parse()?).await?;
1060    /// # let account = client.account();
1061    /// use matrix_sdk::ruma::{
1062    ///     events::ignored_user_list::{IgnoredUser, IgnoredUserListEventContent},
1063    ///     user_id,
1064    /// };
1065    ///
1066    /// let mut content = account
1067    ///     .account_data::<IgnoredUserListEventContent>()
1068    ///     .await?
1069    ///     .map(|c| c.deserialize())
1070    ///     .transpose()?
1071    ///     .unwrap_or_default();
1072    /// content
1073    ///     .ignored_users
1074    ///     .insert(user_id!("@foo:bar.com").to_owned(), IgnoredUser::new());
1075    /// account.set_account_data(content).await?;
1076    /// # anyhow::Ok(()) };
1077    /// ```
1078    pub async fn set_account_data<T>(
1079        &self,
1080        content: T,
1081    ) -> Result<set_global_account_data::v3::Response>
1082    where
1083        T: GlobalAccountDataEventContent,
1084    {
1085        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1086
1087        let request = set_global_account_data::v3::Request::new(own_user.to_owned(), &content)?;
1088
1089        Ok(self.client.send(request).await?)
1090    }
1091
1092    /// Set the given raw account data event.
1093    pub async fn set_account_data_raw(
1094        &self,
1095        event_type: GlobalAccountDataEventType,
1096        content: Raw<AnyGlobalAccountDataEventContent>,
1097    ) -> Result<set_global_account_data::v3::Response> {
1098        let own_user = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1099
1100        let request =
1101            set_global_account_data::v3::Request::new_raw(own_user.to_owned(), event_type, content);
1102
1103        Ok(self.client.send(request).await?)
1104    }
1105
1106    /// Marks the room identified by `room_id` as a "direct chat" with each user
1107    /// in `user_ids`.
1108    ///
1109    /// # Arguments
1110    ///
1111    /// - `room_id` - The room ID of the direct message room.
1112    /// - `user_ids` - The user IDs to be associated with this direct message
1113    ///   room.
1114    pub async fn mark_as_dm(&self, room_id: &RoomId, user_ids: &[OwnedUserId]) -> Result<()> {
1115        use ruma::events::direct::DirectEventContent;
1116
1117        // This function does a read/update/store of an account data event
1118        // stored on the homeserver. We first fetch the existing account data
1119        // event, the event contains a map which gets updated by this method,
1120        // finally we upload the modified event.
1121        //
1122        // To prevent multiple calls to this method trying to update the map of
1123        // DMs same time, and thus trampling on each other we introduce a lock
1124        // which acts as a semaphore.
1125        let _guard = self.client.locks().mark_as_dm_lock.lock().await;
1126
1127        // Now we need to mark the room as a DM for ourselves, we fetch the
1128        // existing `m.direct` event and append the room to the list of DMs we
1129        // have with this user.
1130
1131        // We are fetching the content from the server because we currently
1132        // can't rely on `/sync` giving us the correct data in a timely manner.
1133        let raw_content = self.fetch_account_data_static::<DirectEventContent>().await?;
1134
1135        let mut content = if let Some(raw_content) = raw_content {
1136            // Log the error and pass it upwards if we fail to deserialize the
1137            // m.direct event.
1138            raw_content.deserialize().map_err(|err| {
1139                error!("unable to deserialize m.direct event content; aborting request to mark {room_id} as dm: {err}");
1140                err
1141            })?
1142        } else {
1143            // If there was no m.direct event server-side, create a default one.
1144            Default::default()
1145        };
1146
1147        for user_id in user_ids {
1148            content.entry(user_id.into()).or_default().push(room_id.to_owned());
1149        }
1150
1151        // TODO: We should probably save the fact that we need to send this out
1152        // because otherwise we might end up in a state where we have a DM that
1153        // isn't marked as one.
1154        self.set_account_data(content).await?;
1155
1156        Ok(())
1157    }
1158
1159    /// Set the invite permission configuration for this account.
1160    ///
1161    /// The configuration determines how the server should handle incoming
1162    /// room invitations. If `default_action` is unset, then it allows
1163    /// invitations as normal, but it is still subject to other
1164    /// restrictions such as ignored users.
1165    ///
1166    /// See the [Matrix specification](https://spec.matrix.org/v1.19/client-server-api/#minvite_permission_config).
1167    pub async fn set_invite_permission_config(
1168        &self,
1169        config: InvitePermissionConfigEventContent,
1170    ) -> Result<()> {
1171        self.set_account_data(config).await?;
1172        Ok(())
1173    }
1174
1175    /// Fetches the invite permission configuration from the server.
1176    ///
1177    /// Returns `None` if no configuration has been set for this account.
1178    pub async fn fetch_invite_permission_config(
1179        &self,
1180    ) -> Result<Option<InvitePermissionConfigEventContent>> {
1181        self.fetch_account_data_static::<InvitePermissionConfigEventContent>()
1182            .await?
1183            .map(|c| c.deserialize())
1184            .transpose()
1185            .map_err(Into::into)
1186    }
1187
1188    /// Gets the invite permission configuration stored in the local state
1189    /// store.
1190    ///
1191    /// Returns `None` if no configuration has been received yet.
1192    pub async fn get_invite_permission_config(
1193        &self,
1194    ) -> Result<Option<InvitePermissionConfigEventContent>> {
1195        self.account_data::<InvitePermissionConfigEventContent>()
1196            .await?
1197            .map(|c| c.deserialize())
1198            .transpose()
1199            .map_err(Into::into)
1200    }
1201
1202    /// Observes changes to the invite permission configuration.
1203    ///
1204    /// This will return the current value of the configuration from the local
1205    /// state store and a stream that will yield new values as they are
1206    /// received.
1207    ///
1208    /// The initial value may be `None` if no configuration has been received
1209    /// yet.
1210    pub async fn observe_invite_permission_config(
1211        &self,
1212    ) -> Result<
1213        (
1214            Option<InvitePermissionConfigEventContent>,
1215            impl Stream<Item = InvitePermissionConfigEventContent> + use<>,
1216        ),
1217        Error,
1218    > {
1219        let observer = self
1220            .client
1221            .observe_events::<GlobalAccountDataEvent<InvitePermissionConfigEventContent>, ()>();
1222
1223        let mut stream = observer.subscribe().map(|event| event.0.content);
1224
1225        let result_stream = async_stream::stream! {
1226            // The observer needs to be alive for the lifetime of the stream, we need to take
1227            // ownership.
1228            let _observer = observer;
1229
1230            while let Some(item) = stream.next().await {
1231                yield item;
1232            }
1233        };
1234
1235        // We need to read the initial value after subscribing to avoid missing
1236        // updates.
1237        let initial_value = self.get_invite_permission_config().await?;
1238
1239        Ok((initial_value, result_stream))
1240    }
1241
1242    /// Adds the given user ID to the account's ignore list.
1243    pub async fn ignore_user(&self, user_id: &UserId) -> Result<()> {
1244        let own_user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1245        if user_id == own_user_id {
1246            return Err(Error::CantIgnoreLoggedInUser);
1247        }
1248
1249        let mut ignored_user_list = self.get_ignored_user_list_event_content().await?;
1250        ignored_user_list.ignored_users.insert(user_id.to_owned(), IgnoredUser::new());
1251
1252        self.set_account_data(ignored_user_list).await?;
1253
1254        // In theory, we should also clear some caches here, because they may
1255        // include events sent by the ignored user. In practice, we expect
1256        // callers to take care of this, or subsystems to listen to user list
1257        // changes and clear caches accordingly.
1258
1259        Ok(())
1260    }
1261
1262    /// Removes the given user ID from the account's ignore list.
1263    pub async fn unignore_user(&self, user_id: &UserId) -> Result<()> {
1264        let mut ignored_user_list = self.get_ignored_user_list_event_content().await?;
1265
1266        // Only update account data if the user was ignored in the first place.
1267        if ignored_user_list.ignored_users.remove(user_id).is_some() {
1268            self.set_account_data(ignored_user_list).await?;
1269        }
1270
1271        // See comment in `ignore_user`.
1272        Ok(())
1273    }
1274
1275    async fn get_ignored_user_list_event_content(&self) -> Result<IgnoredUserListEventContent> {
1276        let ignored_user_list = self
1277            .account_data::<IgnoredUserListEventContent>()
1278            .await?
1279            .map(|c| c.deserialize())
1280            .transpose()?
1281            .unwrap_or_default();
1282        Ok(ignored_user_list)
1283    }
1284
1285    /// Get the current push rules from storage.
1286    ///
1287    /// If no push rules event was found, or it fails to deserialize, a ruleset
1288    /// with the server-default push rules is returned.
1289    ///
1290    /// Panics if called when the client is not logged in.
1291    pub async fn push_rules(&self) -> Result<Ruleset> {
1292        Ok(self
1293            .account_data::<PushRulesEventContent>()
1294            .await?
1295            .and_then(|r| match r.deserialize() {
1296                Ok(r) => Some(r.global),
1297                Err(e) => {
1298                    error!("Push rules event failed to deserialize: {e}");
1299                    None
1300                }
1301            })
1302            .unwrap_or_else(|| {
1303                Ruleset::server_default(
1304                    self.client.user_id().expect("The client should be logged in"),
1305                )
1306            }))
1307    }
1308
1309    /// Retrieves the user's recently visited room list
1310    pub async fn get_recently_visited_rooms(&self) -> Result<Vec<OwnedRoomId>> {
1311        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1312        let data = self
1313            .client
1314            .state_store()
1315            .get_kv_data(StateStoreDataKey::RecentlyVisitedRooms(user_id))
1316            .await?;
1317
1318        Ok(data
1319            .map(|v| {
1320                v.into_recently_visited_rooms()
1321                    .expect("Session data is not a list of recently visited rooms")
1322            })
1323            .unwrap_or_default())
1324    }
1325
1326    /// Moves/inserts the given room to the front of the recently visited list
1327    pub async fn track_recently_visited_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
1328        let user_id = self.client.user_id().ok_or(Error::AuthenticationRequired)?;
1329
1330        // Get the previously stored recently visited rooms
1331        let mut recently_visited_rooms = self.get_recently_visited_rooms().await?;
1332
1333        // Remove all other occurrences of the new room_id
1334        recently_visited_rooms.retain(|r| r != &room_id);
1335
1336        // And insert it as the most recent
1337        recently_visited_rooms.insert(0, room_id);
1338
1339        // Cap the whole list to the VISITED_ROOMS_LIMIT
1340        recently_visited_rooms.truncate(Self::VISITED_ROOMS_LIMIT);
1341
1342        let data = StateStoreDataValue::RecentlyVisitedRooms(recently_visited_rooms);
1343        self.client
1344            .state_store()
1345            .set_kv_data(StateStoreDataKey::RecentlyVisitedRooms(user_id), data)
1346            .await?;
1347        Ok(())
1348    }
1349
1350    /// Observes the media preview configuration.
1351    ///
1352    /// This value is linked to the
1353    /// [MSC 4278](https://github.com/matrix-org/matrix-spec-proposals/pull/4278)
1354    /// which is still in an unstable state.
1355    ///
1356    /// This will return the initial value of the configuration and a stream
1357    /// that will yield new values as they are received.
1358    ///
1359    /// The initial value is the one that was stored in the account data when
1360    /// the client was started. and the following code is using a temporary
1361    /// solution until we know which Matrix version will support the stable
1362    /// type.
1363    ///
1364    /// # Examples
1365    ///
1366    /// ```no_run
1367    /// # use std::pin::pin;
1368    /// # use futures_util::StreamExt;
1369    /// # use matrix_sdk::Client;
1370    /// # use matrix_sdk::ruma::events::media_preview_config::MediaPreviews;
1371    /// # use url::Url;
1372    /// # async {
1373    /// # let homeserver = Url::parse("http://localhost:8080")?;
1374    /// # let client = Client::new(homeserver).await?;
1375    /// let account = client.account();
1376    ///
1377    /// let (initial_config, config_stream) =
1378    ///     account.observe_media_preview_config().await?;
1379    ///
1380    /// println!("Initial media preview config: {:?}", initial_config);
1381    ///
1382    /// let mut config_stream = pin!(config_stream);
1383    /// while let Some(new_config) = config_stream.next().await {
1384    ///     println!("Updated media preview config: {:?}", new_config);
1385    /// }
1386    /// # anyhow::Ok(()) };
1387    /// ```
1388    pub async fn observe_media_preview_config(
1389        &self,
1390    ) -> Result<
1391        (
1392            Option<MediaPreviewConfigEventContent>,
1393            impl Stream<Item = MediaPreviewConfigEventContent> + use<>,
1394        ),
1395        Error,
1396    > {
1397        // We need to create two observers, one for the stable event and one for
1398        // the unstable and combine them into a single stream.
1399        let first_observer = self
1400            .client
1401            .observe_events::<GlobalAccountDataEvent<MediaPreviewConfigEventContent>, ()>();
1402
1403        let stream = first_observer.subscribe().map(|event| event.0.content);
1404
1405        let second_observer = self
1406            .client
1407            .observe_events::<GlobalAccountDataEvent<UnstableMediaPreviewConfigEventContent>, ()>();
1408
1409        let second_stream = second_observer.subscribe().map(|event| event.0.content.0);
1410
1411        let mut combined_stream = stream::select(stream, second_stream);
1412
1413        let result_stream = async_stream::stream! {
1414            // The observers need to be alive for the individual streams to be
1415            // alive, so let's now create a stream that takes ownership of them.
1416            let _first_observer = first_observer;
1417            let _second_observer = second_observer;
1418
1419            while let Some(item) = combined_stream.next().await {
1420                yield item
1421            }
1422        };
1423
1424        // We need to get the initial value of the media preview config event we
1425        // do this after creating the observers to make sure that we don't
1426        // create a race condition
1427        let initial_value = self.get_media_preview_config_event_content().await?;
1428
1429        Ok((initial_value, result_stream))
1430    }
1431
1432    /// Fetch the media preview configuration event content from the server.
1433    ///
1434    /// Will check first for the stable event and then for the unstable one.
1435    pub async fn fetch_media_preview_config_event_content(
1436        &self,
1437    ) -> Result<Option<MediaPreviewConfigEventContent>> {
1438        // First we check if there is a value in the stable event
1439        let media_preview_config =
1440            self.fetch_account_data_static::<MediaPreviewConfigEventContent>().await?;
1441
1442        let media_preview_config = if let Some(media_preview_config) = media_preview_config {
1443            Some(media_preview_config)
1444        } else {
1445            // If there is no value in the stable event, we check the unstable
1446            self.fetch_account_data_static::<UnstableMediaPreviewConfigEventContent>()
1447                .await?
1448                .map(Raw::cast)
1449        };
1450
1451        // We deserialize the content of the event, if is not found we return
1452        // the default
1453        let media_preview_config = media_preview_config.and_then(|value| value.deserialize().ok());
1454
1455        Ok(media_preview_config)
1456    }
1457
1458    /// Get the media preview configuration event content stored in the cache.
1459    ///
1460    /// Will check first for the stable event and then for the unstable one.
1461    pub async fn get_media_preview_config_event_content(
1462        &self,
1463    ) -> Result<Option<MediaPreviewConfigEventContent>> {
1464        let media_preview_config = self
1465            .account_data::<MediaPreviewConfigEventContent>()
1466            .await?
1467            .and_then(|r| r.deserialize().ok());
1468
1469        if let Some(media_preview_config) = media_preview_config {
1470            Ok(Some(media_preview_config))
1471        } else {
1472            Ok(self
1473                .account_data::<UnstableMediaPreviewConfigEventContent>()
1474                .await?
1475                .and_then(|r| r.deserialize().ok())
1476                .map(Into::into))
1477        }
1478    }
1479
1480    /// Set the media previews display policy in the timeline.
1481    ///
1482    /// This will always use the unstable event until we know which Matrix
1483    /// version will support it.
1484    pub async fn set_media_previews_display_policy(&self, policy: MediaPreviews) -> Result<()> {
1485        let mut media_preview_config =
1486            self.fetch_media_preview_config_event_content().await?.unwrap_or_default();
1487        media_preview_config.media_previews = Some(policy);
1488
1489        // Updating the unstable account data
1490        let unstable_media_preview_config =
1491            UnstableMediaPreviewConfigEventContent::from(media_preview_config);
1492        self.set_account_data(unstable_media_preview_config).await?;
1493        Ok(())
1494    }
1495
1496    /// Set the display policy for avatars in invite requests.
1497    ///
1498    /// This will always use the unstable event until we know which matrix
1499    /// version will support it.
1500    pub async fn set_invite_avatars_display_policy(&self, policy: InviteAvatars) -> Result<()> {
1501        let mut media_preview_config =
1502            self.fetch_media_preview_config_event_content().await?.unwrap_or_default();
1503        media_preview_config.invite_avatars = Some(policy);
1504
1505        // Updating the unstable account data
1506        let unstable_media_preview_config =
1507            UnstableMediaPreviewConfigEventContent::from(media_preview_config);
1508        self.set_account_data(unstable_media_preview_config).await?;
1509        Ok(())
1510    }
1511
1512    /// Adds a recently used emoji to the list and uploads the updated
1513    /// `io.element.recent_emoji` content to the global account data.
1514    ///
1515    /// Before updating the data, it'll fetch it from the homeserver, to make
1516    /// sure the updated values are always used. However, note this could still
1517    /// result in a race condition if it's used concurrently.
1518    #[cfg(feature = "experimental-element-recent-emojis")]
1519    pub async fn add_recent_emoji(&self, emoji: &str) -> Result<()> {
1520        let Some(user_id) = self.client.user_id() else {
1521            return Err(Error::AuthenticationRequired);
1522        };
1523        let mut recent_emojis = self.get_recent_emojis(true).await?;
1524
1525        let index = recent_emojis.iter().position(|(unicode, _)| unicode == emoji);
1526
1527        // Truncate to the max allowed size, which will remove any emojis that
1528        // haven't been used in a very long time. This will also ease the
1529        // pressure on `remove` and `insert` shifting lots of elements in the
1530        // list
1531        recent_emojis.truncate(MAX_RECENT_EMOJI_COUNT);
1532
1533        // Remove the emoji from the list if it was present and get it's `count`
1534        // value
1535        let count = if let Some(index) = index { recent_emojis.remove(index).1 } else { uint!(0) };
1536
1537        // Insert the emoji with the updated count at the start of the list, so
1538        // it's considered the most recently used emoji
1539        recent_emojis.insert(0, (emoji.to_owned(), count + uint!(1)));
1540
1541        // If the item was a new one, the list will now be
1542        // `MAX_RECENT_EMOJI_COUNT` + 1, so truncate it again (this is a no-op
1543        // if it already has the right size)
1544        recent_emojis.truncate(MAX_RECENT_EMOJI_COUNT);
1545
1546        let request = UpdateGlobalAccountDataRequest::new(
1547            user_id.to_owned(),
1548            &RecentEmojisContent::new(recent_emojis),
1549        )?;
1550        let _ = self.client.send(request).await?;
1551
1552        Ok(())
1553    }
1554
1555    /// Gets the list of recently used emojis from the `io.element.recent_emoji`
1556    /// global account data.
1557    ///
1558    /// If the `refresh` param is `true`, the data will be fetched from the
1559    /// homeserver instead of the local storage.
1560    #[cfg(feature = "experimental-element-recent-emojis")]
1561    pub async fn get_recent_emojis(&self, refresh: bool) -> Result<Vec<(String, UInt)>> {
1562        let content = if refresh {
1563            let Some(user_id) = self.client.user_id() else {
1564                return Err(Error::AuthenticationRequired);
1565            };
1566            let event_type = RecentEmojisContent::default().event_type();
1567            let response = self
1568                .client
1569                .send(get_global_account_data::v3::Request::new(
1570                    user_id.to_owned(),
1571                    event_type.clone(),
1572                ))
1573                .await?;
1574            let content = response.account_data.cast_unchecked().deserialize()?;
1575            Some(content)
1576        } else {
1577            self.client
1578                .state_store()
1579                .get_account_data_event_static::<RecentEmojisContent>()
1580                .await?
1581                .map(|raw| raw.deserialize().map(|event| event.content))
1582                .transpose()?
1583        };
1584
1585        if let Some(content) = content {
1586            // Sort by count, descending. For items with the same count, since
1587            // they were previously ordered by recency in the list, more recent
1588            // emojis will be returned first.
1589            let sorted_emojis = content
1590                .recent_emoji
1591                .into_iter()
1592                // Items with higher counts should be first
1593                .sorted_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a))
1594                // Make sure we take only up to MAX_RECENT_EMOJI_COUNT
1595                .take(MAX_RECENT_EMOJI_COUNT)
1596                .collect();
1597            Ok(sorted_emojis)
1598        } else {
1599            Ok(Vec::new())
1600        }
1601    }
1602}
1603
1604fn get_raw_content<Ev, C>(raw: Option<Raw<Ev>>) -> Result<Option<Raw<C>>> {
1605    #[derive(Deserialize)]
1606    #[serde(bound = "C: Sized")] // Replace default Deserialize bound
1607    struct GetRawContent<C> {
1608        content: Raw<C>,
1609    }
1610
1611    Ok(raw
1612        .map(|event| event.deserialize_as_unchecked::<GetRawContent<C>>())
1613        .transpose()?
1614        .map(|get_raw| get_raw.content))
1615}
1616
1617#[cfg(test)]
1618mod tests {
1619    use assert_matches::assert_matches;
1620    use matrix_sdk_test::async_test;
1621
1622    use crate::{Error, test_utils::client::MockClientBuilder};
1623
1624    #[async_test]
1625    async fn test_dont_ignore_oneself() {
1626        let client = MockClientBuilder::new(None).build().await;
1627
1628        // It's forbidden to ignore the logged-in user.
1629        assert_matches!(
1630            client.account().ignore_user(client.user_id().unwrap()).await,
1631            Err(Error::CantIgnoreLoggedInUser)
1632        );
1633    }
1634}
1635
1636#[cfg(test)]
1637#[cfg(feature = "experimental-element-recent-emojis")]
1638mod test_recent_emojis {
1639    use js_int::{UInt, uint};
1640    use matrix_sdk_base::recent_emojis::RecentEmojisContent;
1641    use matrix_sdk_test::{async_test, event_factory::EventFactory};
1642
1643    use crate::{
1644        account::MAX_RECENT_EMOJI_COUNT, config::SyncSettings, test_utils::mocks::MatrixMockServer,
1645    };
1646
1647    #[async_test]
1648    async fn test_recent_emojis() {
1649        let server = MatrixMockServer::new().await;
1650        let client = server.client_builder().build().await;
1651        let user_id = client.user_id().expect("session_id");
1652
1653        server
1654            .mock_add_recent_emojis()
1655            .ok(user_id)
1656            .named("Update recent emojis global account data")
1657            .mock_once()
1658            .mount()
1659            .await;
1660
1661        let recent_emojis = client.account().get_recent_emojis(false).await.expect("recent emojis");
1662        assert!(recent_emojis.is_empty());
1663
1664        let emoji_list = vec![
1665            (":/".to_owned(), uint!(1)),
1666            (":)".to_owned(), uint!(12)),
1667            (":D".to_owned(), uint!(12)),
1668        ];
1669
1670        server
1671            .mock_get_recent_emojis()
1672            .ok(user_id, emoji_list.clone())
1673            .named("Fetch recent emojis")
1674            .mock_once()
1675            .mount()
1676            .await;
1677
1678        client.account().add_recent_emoji(":)").await.expect("adding emoji");
1679
1680        server
1681            .mock_sync()
1682            .ok(|builder| {
1683                let content = RecentEmojisContent::new(emoji_list);
1684                let event_builder = EventFactory::new().global_account_data(content);
1685                builder.add_global_account_data(event_builder);
1686            })
1687            .named("Sync")
1688            .mount()
1689            .await;
1690
1691        client.sync_once(SyncSettings::default()).await.expect("sync failed");
1692
1693        let recent_emojis = client.account().get_recent_emojis(false).await.expect("recent emojis");
1694
1695        // Assert size
1696        assert_eq!(recent_emojis.len(), 3);
1697
1698        // Assert ordering: first by times used, then by recency
1699        assert_eq!(recent_emojis[0].0, ":)");
1700        assert_eq!(recent_emojis[1].0, ":D");
1701        assert_eq!(recent_emojis[2].0, ":/");
1702    }
1703
1704    #[async_test]
1705    async fn test_max_recent_emoji_count() {
1706        let server = MatrixMockServer::new().await;
1707        let client = server.client_builder().build().await;
1708        let user_id = client.user_id().expect("session_id");
1709
1710        // This list is > the MAX_RECENT_EMOJI_COUNT
1711        let long_emoji_list = (0..MAX_RECENT_EMOJI_COUNT * 2)
1712            .map(|i| (i.to_string(), uint!(1)))
1713            .collect::<Vec<(String, UInt)>>();
1714
1715        // Initially we locally don't have any emojis
1716        let recent_emojis = client.account().get_recent_emojis(false).await.expect("recent emojis");
1717        assert!(recent_emojis.is_empty());
1718
1719        server
1720            .mock_get_recent_emojis()
1721            .ok(user_id, long_emoji_list.clone())
1722            .named("Fetch recent emojis")
1723            .expect(3)
1724            .mount()
1725            .await;
1726
1727        // Now with a list of emojis longer than the max count, we fetch the
1728        // emoji list
1729        let recent_emojis = client.account().get_recent_emojis(true).await.expect("recent emojis");
1730
1731        // It should only return until the max count
1732        assert_eq!(recent_emojis.len(), MAX_RECENT_EMOJI_COUNT);
1733        assert_eq!(recent_emojis, long_emoji_list[..MAX_RECENT_EMOJI_COUNT]);
1734
1735        // Simulate the logic we expect when adding a new emoji:
1736        //
1737        // 1. Remove the existing emoji if present
1738        // 2. Increase its count value and insert it at the front.
1739        // 3. Truncate at MAX_RECENT_EMOJI_COUNT
1740        let expected_updated_emoji_list = {
1741            let mut list = long_emoji_list.clone();
1742            let item = list.remove(50);
1743            list.insert(0, (item.0, item.1 + uint!(1)));
1744            list.truncate(MAX_RECENT_EMOJI_COUNT);
1745            list
1746        };
1747
1748        // Now if we add a new emoji that was not in the list, the last one in
1749        // the list should be gone
1750        server
1751            .mock_add_recent_emojis()
1752            .match_emojis_in_request_body(expected_updated_emoji_list)
1753            .ok(user_id)
1754            .named("Update recent emojis global account data with existing emoji")
1755            .mock_once()
1756            .mount()
1757            .await;
1758
1759        client.account().add_recent_emoji("50").await.expect("adding emoji");
1760
1761        // Do the same, but now with a new emoji that wasn't previously in the
1762        // list
1763        let expected_updated_emoji_list = {
1764            let mut list = long_emoji_list.clone();
1765            let item = (":D".to_owned(), uint!(1));
1766            list.insert(0, item);
1767            list.truncate(MAX_RECENT_EMOJI_COUNT);
1768            list
1769        };
1770
1771        // We should still have `MAX_RECENT_EMOJI_COUNT` items
1772        server
1773            .mock_add_recent_emojis()
1774            .match_emojis_in_request_body(expected_updated_emoji_list)
1775            .ok(user_id)
1776            .named("Update recent emojis global account data with new emoji")
1777            .mock_once()
1778            .mount()
1779            .await;
1780
1781        client.account().add_recent_emoji(":D").await.expect("adding emoji");
1782    }
1783}