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