Skip to main content

matrix_sdk/
account.rs

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