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