Skip to main content

matrix_sdk/
account.rs

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