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