bsky_sdk/
preference.rs

1//! Preferences for Bluesky application.
2use crate::moderation::ModerationPrefs;
3use atrium_api::app::bsky::actor::defs::SavedFeed;
4use atrium_api::types::Object;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// A preference for a feed view.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(rename_all = "camelCase")]
11pub struct FeedViewPreferenceData {
12    pub hide_replies: bool,
13    pub hide_replies_by_unfollowed: bool,
14    pub hide_replies_by_like_count: i64,
15    pub hide_reposts: bool,
16    pub hide_quote_posts: bool,
17}
18
19impl Default for FeedViewPreferenceData {
20    fn default() -> Self {
21        Self {
22            hide_replies: false,
23            hide_replies_by_unfollowed: true,
24            hide_replies_by_like_count: 0,
25            hide_reposts: false,
26            hide_quote_posts: false,
27        }
28    }
29}
30
31pub type FeedViewPreference = Object<FeedViewPreferenceData>;
32
33/// A preference for a thread view.
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
35#[serde(rename_all = "camelCase")]
36pub struct ThreadViewPreferenceData {
37    pub sort: String,
38    pub prioritize_followed_users: bool,
39}
40
41impl ThreadViewPreferenceData {
42    pub const SORT_OLDEST: &'static str = "oldest";
43    pub const SORT_NEWEST: &'static str = "newest";
44    pub const SORT_MOST_LIKES: &'static str = "most-likes";
45    pub const SORT_RANDOM: &'static str = "random";
46}
47
48impl Default for ThreadViewPreferenceData {
49    fn default() -> Self {
50        Self { sort: Self::SORT_OLDEST.to_string(), prioritize_followed_users: true }
51    }
52}
53
54pub type ThreadViewPreference = Object<ThreadViewPreferenceData>;
55
56/// Preferences for Bluesky application.
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
58#[serde(rename_all = "camelCase")]
59pub struct Preferences {
60    pub saved_feeds: Vec<SavedFeed>,
61    pub feed_view_prefs: HashMap<String, FeedViewPreference>,
62    pub thread_view_prefs: ThreadViewPreference,
63    pub moderation_prefs: ModerationPrefs,
64}
65
66impl Default for Preferences {
67    fn default() -> Self {
68        Self {
69            saved_feeds: Default::default(),
70            feed_view_prefs: Default::default(),
71            thread_view_prefs: ThreadViewPreferenceData::default().into(),
72            moderation_prefs: Default::default(),
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::moderation::ModerationPrefsLabeler;
81    use atrium_api::app::bsky::actor::get_preferences::Output;
82    use serde_json::{from_str, to_string, Value};
83
84    const XRPC_PREFERENCES_JSON: &str = r#"{
85    "preferences": [
86        {
87            "$type": "app.bsky.actor.defs#savedFeedsPrefV2",
88            "items": [
89                {
90                    "id": "3kt2b4tp6gs2a",
91                    "pinned": true,
92                    "type": "timeline",
93                    "value": "following"
94                }
95            ]
96        },
97        {
98            "$type": "app.bsky.actor.defs#threadViewPref",
99            "sort": "oldest",
100            "lab_treeViewEnabled": false
101        },
102        {
103            "$type": "app.bsky.actor.defs#feedViewPref",
104            "feed": "home",
105            "hideRepliesByUnfollowed": false,
106            "lab_mergeFeedEnabled": true
107        }
108    ]
109}"#;
110
111    #[test]
112    fn xrpc_preferences_json() {
113        let deserialized1 = from_str::<Output>(XRPC_PREFERENCES_JSON)
114            .expect("deserializing preferences should succeed");
115        assert_eq!(deserialized1.preferences.len(), 3);
116        let serialized = to_string(&deserialized1).expect("serializing preferences should succeed");
117        assert_eq!(
118            serialized.replace(char::is_whitespace, ""),
119            XRPC_PREFERENCES_JSON.replace(char::is_whitespace, "")
120        );
121        let deserialized2 =
122            from_str::<Output>(&serialized).expect("deserializing preferences should succeed");
123        assert_eq!(deserialized1, deserialized2);
124    }
125
126    #[test]
127    fn sdk_preferences_json() {
128        let preferences = Preferences {
129            saved_feeds: Vec::new(),
130            feed_view_prefs: HashMap::new(),
131            thread_view_prefs: ThreadViewPreferenceData::default().into(),
132            moderation_prefs: ModerationPrefs {
133                labelers: vec![
134                    ModerationPrefsLabeler::default(),
135                    ModerationPrefsLabeler {
136                        did: "did:fake:labeler.test".parse().expect("invalid did"),
137                        labels: HashMap::new(),
138                        is_default_labeler: false,
139                    },
140                ],
141                ..Default::default()
142            },
143        };
144        let serialized1 = to_string(&preferences).expect("serializing preferences should succeed");
145        let deserialized = from_str::<Preferences>(&serialized1)
146            .expect("deserializing preferences should succeed");
147        assert_eq!(preferences, deserialized);
148        let serialized2 = to_string(&deserialized).expect("serializing preferences should succeed");
149        assert_eq!(
150            from_str::<Value>(&serialized1).expect("deserializing to value should succeed"),
151            from_str::<Value>(&serialized2).expect("deserializing to value should succeed"),
152        );
153    }
154}