discord_sdk/
relations.rs

1//! Provides types and functionality for [Relationships](https://discord.com/developers/docs/game-sdk/relationships)
2
3pub mod events;
4pub mod state;
5
6use crate::{user::User, Error};
7use serde::Deserialize;
8#[cfg(test)]
9use serde::Serialize;
10
11#[derive(Copy, Clone, Debug, PartialEq, Eq, serde_repr::Deserialize_repr)]
12#[cfg_attr(test, derive(serde_repr::Serialize_repr))]
13#[repr(u8)]
14pub enum RelationKind {
15    /// User has no intrinsic relationship
16    None = 0,
17    /// User is a friend
18    Friend = 1,
19    /// User is blocked
20    Blocked = 2,
21    /// User has a pending incoming friend request to connected user
22    PendingIncoming = 3,
23    /// Current user has a pending outgoing friend request to user
24    PendingOutgoing = 4,
25    /// User is not friends, but interacts with current user often (frequency + recency)
26    Implicit = 5,
27}
28
29#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
30#[cfg_attr(test, derive(Serialize))]
31#[serde(rename_all = "snake_case")]
32pub enum RelationStatus {
33    /// The user is offline
34    Offline,
35    /// The user is online and active
36    Online,
37    /// The user is online, but inactive
38    Idle,
39    /// The user has set their status to Do Not Disturb
40    #[serde(rename = "dnd")]
41    DoNotDisturb,
42}
43
44/// The start and end timestamp of the activity. These are unix timestamps.
45///
46/// [API docs](https://discord.com/developers/docs/game-sdk/activities#data-models-activitytimestamps-struct)
47#[derive(Default, Clone, Debug, Deserialize)]
48#[cfg_attr(test, derive(Serialize))]
49pub struct RelationshipActivityTimestamps {
50    #[serde(
51        skip_serializing_if = "Option::is_none",
52        with = "crate::util::datetime_opt",
53        default
54    )]
55    pub start: Option<time::OffsetDateTime>,
56    #[serde(
57        skip_serializing_if = "Option::is_none",
58        with = "crate::util::datetime_opt",
59        default
60    )]
61    pub end: Option<time::OffsetDateTime>,
62}
63
64use crate::activity;
65
66#[derive(Default, Clone, Debug, Deserialize)]
67#[cfg_attr(test, derive(Serialize))]
68pub struct RelationshipActivity {
69    /// The unique identifier for the activity
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub session_id: Option<String>,
72    /// The timestamp the activity was created
73    #[serde(skip_serializing, with = "crate::util::datetime_opt")]
74    pub created_at: Option<time::OffsetDateTime>,
75    /// The player's current party status
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub state: Option<String>,
78    /// What the player is currently doing
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub details: Option<String>,
81    /// Helps create elapsed/remaining timestamps on a player's profile
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub timestamps: Option<RelationshipActivityTimestamps>,
84    /// Assets to display on the player's profile
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub assets: Option<activity::Assets>,
87    /// Information about the player's party
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub party: Option<activity::Party>,
90    /// Secret passwords for joining and spectating the player's game
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub secrets: Option<activity::Secrets>,
93    #[serde(rename = "type")]
94    pub kind: activity::ActivityKind,
95    #[serde(default)]
96    /// Whether this activity is an instanced context, like a match
97    pub instance: bool,
98}
99
100#[derive(Debug, Clone, Deserialize)]
101#[cfg_attr(test, derive(Serialize))]
102pub struct RelationshipPresence {
103    pub status: RelationStatus,
104    pub activity: Option<RelationshipActivity>,
105}
106
107#[derive(Debug, Clone, Deserialize)]
108#[cfg_attr(test, derive(Serialize))]
109pub struct Relationship {
110    /// What kind of relationship it is
111    #[serde(rename = "type")]
112    pub kind: RelationKind,
113    pub user: User,
114    pub presence: RelationshipPresence,
115}
116
117impl crate::Discord {
118    /// The regular Game SDK does not really expose this functionality directly,
119    /// but rather exposed via the "on refresh" event as described in the
120    /// [docs](https://discord.com/developers/docs/game-sdk/relationships#onrefresh).
121    ///
122    /// Basically, this method should be used to bootstrap the relationships for
123    /// the current user, with updates to that list coming via the
124    /// [`RelationshipUpdate`](crate::Event::RelationshipUpdate) event
125    pub async fn get_relationships(&self) -> Result<Vec<Relationship>, Error> {
126        let rx = self.send_rpc(crate::proto::CommandKind::GetRelationships, ())?;
127
128        handle_response!(rx, crate::proto::Command::GetRelationships { relationships } => {
129            Ok(relationships)
130        })
131    }
132}
133
134#[cfg(test)]
135mod test {
136    use super::*;
137    use crate::{activity, proto::event};
138
139    #[test]
140    fn deserializes() {
141        let event = r#"{"cmd":"DISPATCH","data":{"type":1,"user":{"id":"682969165652689005","username":"jake.shadle","discriminator":"7557","avatar":"15bbd75c8ee6610d045852e7ea998a35","bot":false,"flags":0,"premium_type":0},"presence":{"status":"online","activity":{"created_at":"1632819046295","id":"e92ece5eb4ce629","name":"Ark [dev debug]","timestamps":{"start":"1632819046199"},"type":0}}},"evt":"RELATIONSHIP_UPDATE","nonce":null}"#;
142
143        let update: crate::proto::event::EventFrame =
144            serde_json::from_str(event).expect("failed to deserialize");
145
146        insta::assert_json_snapshot!(update);
147    }
148
149    #[test]
150    fn serde() {
151        let eve = event::EventFrame {
152            inner: event::Event::RelationshipUpdate(std::sync::Arc::new(Relationship {
153                kind: RelationKind::Friend,
154                user: User {
155                    id: crate::types::Snowflake(123414231424),
156                    username: "name".to_owned(),
157                    discriminator: Some(52),
158                    avatar: Some(crate::user::Avatar([
159                        0xf6, 0x2f, 0x2a, 0x75, 0x5c, 0xb1, 0x8c, 0x94, 0xdc, 0x5c, 0xda, 0x94,
160                        0x44, 0x10, 0x24, 0xf1,
161                    ])),
162                    is_bot: false,
163                },
164                presence: RelationshipPresence {
165                    status: RelationStatus::DoNotDisturb,
166                    activity: Some(RelationshipActivity {
167                        session_id: Some("6bb1ddaea510750e905615286709d632".to_owned()),
168                        created_at: Some(crate::util::timestamp(1628629162447)),
169                        assets: Some(activity::Assets {
170                            large_image: Some(
171                                "spotify:ab67616d0000b273d1e326d10706f3d8562d77f8".to_owned(),
172                            ),
173                            large_text: Some("To the Moon".to_owned()),
174                            small_image: None,
175                            small_text: None,
176                        }),
177                        details: Some("To the Moon".to_owned()),
178                        instance: false,
179                        kind: activity::ActivityKind::Listening,
180                        party: Some(activity::Party {
181                            id: "spotify: 216453179196440576".to_owned(),
182                            size: None,
183                            privacy: None,
184                        }),
185                        secrets: None,
186                        state: Some("Rob Curly".to_owned()),
187                        timestamps: Some(RelationshipActivityTimestamps {
188                            start: Some(crate::util::timestamp(1628629161811)),
189                            end: Some(crate::util::timestamp(1628629327961)),
190                        }),
191                    }),
192                },
193            })),
194        };
195
196        insta::assert_json_snapshot!(eve);
197    }
198}