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