Skip to main content

roblox_api/api/friends/
v1.rs

1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::{DateTime, Error, Paging, client::Client};
5
6pub const URL: &str = "https://friends.roblox.com/v1";
7
8#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
9#[serde(rename_all = "camelCase")]
10pub struct FollowingStatus {
11    #[serde(rename = "userId")]
12    pub id: u64,
13    pub is_following: bool,
14    pub is_followed: bool,
15}
16
17#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
18pub struct FriendStatus {
19    pub id: u64,
20    pub status: String,
21}
22
23#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
24pub enum FriendRequestSourceType {
25    InGame,
26    UserProfile,
27    PlayerSearch,
28    FriendRecommendations,
29}
30
31#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
32#[serde(rename_all = "camelCase")]
33pub struct FriendRequester {
34    #[serde(rename = "senderId")]
35    pub id: u64,
36    #[serde(rename = "senderNickname")]
37    pub display_name: String,
38    pub contact_name: Option<String>,
39
40    pub source_universe_id: Option<u64>,
41    pub origin_source_type: FriendRequestSourceType,
42    pub sent_at: DateTime,
43}
44
45#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
46#[serde(rename_all = "camelCase")]
47pub struct FriendRequest {
48    pub id: u64,
49    pub mutual_friends_list: Vec<String>,
50    #[serde(rename = "friendRequest")]
51    pub requester: FriendRequester,
52}
53
54#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
55#[serde(rename_all = "camelCase")]
56pub struct FriendRequests {
57    #[serde(rename = "data")]
58    pub requests: Vec<FriendRequest>,
59    #[serde(rename = "nextPageCursor")]
60    pub next_cursor: Option<String>,
61    #[serde(rename = "previousPageCursor")]
62    pub previous_cursor: Option<String>,
63}
64
65#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
66#[serde(rename_all = "camelCase")]
67pub struct User {
68    pub id: u64,
69    #[serde(rename = "hasVerifiedBadge")]
70    pub is_verified: Option<bool>,
71}
72
73#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
74#[serde(rename_all = "camelCase")]
75pub struct Followers {
76    #[serde(rename = "data")]
77    pub users: Vec<User>,
78    #[serde(rename = "nextPageCursor")]
79    pub next_cursor: Option<String>,
80    #[serde(rename = "previousPageCursor")]
81    pub previous_cursor: Option<String>,
82}
83
84#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
85#[serde(rename_all = "PascalCase")]
86pub struct FriendsFind {
87    #[serde(rename = "PageItems")]
88    pub users: Vec<User>,
89    pub next_cursor: Option<String>,
90    pub previous_cursor: Option<String>,
91    pub has_more: Option<bool>,
92}
93
94#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
95#[serde(rename_all = "camelCase")]
96pub struct UserPresence {
97    #[serde(rename = "UserPresenceType")]
98    pub kind: String,
99    #[serde(rename = "UserLocationType")]
100    pub location_kind: String,
101
102    #[serde(rename = "lastLocation")]
103    pub status: String,
104    pub last_online: DateTime,
105
106    pub place_id: Option<u64>,
107    pub root_place_id: Option<u64>,
108    pub universe_id: Option<u64>,
109    #[serde(rename = "gameInstanceId")]
110    pub job_id: Option<String>,
111}
112
113#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
114#[serde(rename_all = "camelCase")]
115pub struct FriendOnlineStatus {
116    pub id: u64,
117    #[serde(rename = "userPresence")]
118    pub presence: UserPresence,
119}
120
121async fn generic_count(client: &mut Client, path: &str) -> Result<u16, Error> {
122    #[derive(Debug, Deserialize)]
123    struct Response {
124        count: u16,
125    }
126
127    Ok(client
128        .requestor
129        .request::<()>(
130            Method::GET,
131            &format!("{URL}/{path}/count"),
132            None,
133            None,
134            None,
135        )
136        .await?
137        .json::<Response>()
138        .await?
139        .count)
140}
141
142pub async fn friend_requests_count(client: &mut Client) -> Result<u16, Error> {
143    generic_count(client, "user/friend-requests").await
144}
145
146pub async fn new_friend_requests_count(client: &mut Client) -> Result<u16, Error> {
147    generic_count(client, "my/new-friend-requests").await
148}
149
150pub async fn user_friends_count(client: &mut Client, id: u64) -> Result<u16, Error> {
151    generic_count(client, &format!("users/{id}/friends")).await
152}
153
154pub async fn user_followings_count(client: &mut Client, id: u64) -> Result<u16, Error> {
155    generic_count(client, &format!("users/{id}/followings")).await
156}
157
158pub async fn user_followers_count(client: &mut Client, id: u64) -> Result<u16, Error> {
159    generic_count(client, &format!("users/{id}/followers")).await
160}
161
162pub async fn following_status(
163    client: &mut Client,
164    ids: &[u64],
165) -> Result<Vec<FollowingStatus>, Error> {
166    #[derive(Debug, Serialize)]
167    struct Request<'a> {
168        #[serde(rename = "targetUserIds")]
169        user_ids: &'a [u64],
170    }
171
172    #[derive(Debug, Deserialize)]
173    struct Response {
174        #[serde(rename = "followings")]
175        statuses: Vec<FollowingStatus>,
176    }
177
178    Ok(client
179        .requestor
180        .request::<Request>(
181            Method::POST,
182            &format!("{URL}/user/following-exists"),
183            Some(&Request { user_ids: ids }),
184            None,
185            None,
186        )
187        .await?
188        .json::<Response>()
189        .await?
190        .statuses)
191}
192
193pub async fn friend_requests(
194    client: &mut Client,
195    paging: Paging<'_>,
196) -> Result<FriendRequests, Error> {
197    let limit = paging.limit.unwrap_or(18).to_string();
198    let cursor = paging.cursor.unwrap_or("");
199
200    client
201        .requestor
202        .request::<()>(
203            Method::GET,
204            &format!("{URL}/my/friends/requests"),
205            None,
206            Some(&[("cursor", cursor), ("limit", &limit)]),
207            None,
208        )
209        .await?
210        .json::<FriendRequests>()
211        .await
212}
213
214pub async fn user_followers(client: &mut Client, id: u64) -> Result<Followers, Error> {
215    client
216        .requestor
217        .request::<()>(
218            Method::GET,
219            &format!("{URL}/users/{id}/followers"),
220            None,
221            None,
222            None,
223        )
224        .await?
225        .json::<Followers>()
226        .await
227}
228
229pub async fn user_followings(client: &mut Client, id: u64) -> Result<Followers, Error> {
230    client
231        .requestor
232        .request::<()>(
233            Method::GET,
234            &format!("{URL}/users/{id}/followings"),
235            None,
236            None,
237            None,
238        )
239        .await?
240        .json::<Followers>()
241        .await
242}
243
244pub async fn user_friends_online(
245    client: &mut Client,
246    id: u64,
247) -> Result<Vec<FriendOnlineStatus>, Error> {
248    #[derive(Debug, Deserialize)]
249    struct Response {
250        #[serde(rename = "data")]
251        online: Vec<FriendOnlineStatus>,
252    }
253
254    Ok(client
255        .requestor
256        .request::<()>(
257            Method::GET,
258            &format!("{URL}/users/{id}/friends/online"),
259            None,
260            None,
261            None,
262        )
263        .await?
264        .json::<Response>()
265        .await?
266        .online)
267}
268
269pub async fn user_friends_find(
270    client: &mut Client,
271    id: u64,
272    paging: Paging<'_>,
273) -> Result<FriendsFind, Error> {
274    let limit = paging.limit.unwrap_or(18).to_string();
275    let cursor = paging.cursor.unwrap_or("");
276
277    client
278        .requestor
279        .request::<()>(
280            Method::GET,
281            &format!("{URL}/users/{id}/friends/find"),
282            None,
283            Some(&[("cursor", cursor), ("limit", &limit), ("userSort", "1")]),
284            None,
285        )
286        .await?
287        .json::<FriendsFind>()
288        .await
289}
290
291pub async fn user_friends_search(
292    client: &mut Client,
293    id: u64,
294    query: &str,
295    paging: Paging<'_>,
296) -> Result<FriendsFind, Error> {
297    let limit = paging.limit.unwrap_or(36).to_string();
298    let cursor = paging.cursor.unwrap_or("");
299
300    client
301        .requestor
302        .request::<()>(
303            Method::GET,
304            &format!("{URL}/users/{id}/friends/search"),
305            None,
306            Some(&[("cursor", &cursor), ("limit", &limit), ("query", query)]),
307            None,
308        )
309        .await?
310        .json::<FriendsFind>()
311        .await
312}
313
314pub async fn user_friend_statuses(
315    client: &mut Client,
316    id: u64,
317    friends: &[u64],
318) -> Result<Vec<FriendStatus>, Error> {
319    #[derive(Debug, Deserialize)]
320    struct Response {
321        #[serde(rename = "data")]
322        statuses: Vec<FriendStatus>,
323    }
324
325    let ids = friends
326        .iter()
327        .map(|x| x.to_string())
328        .collect::<Vec<String>>()
329        .join(",");
330
331    Ok(client
332        .requestor
333        .request::<()>(
334            Method::GET,
335            &format!("{URL}/users/{id}/friends/statuses"),
336            None,
337            Some(&[("userIds", &ids)]),
338            None,
339        )
340        .await?
341        .json::<Response>()
342        .await?
343        .statuses)
344}