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}