1use serde::{Deserialize, Serialize};
2
3use crate::{DateTime, Paging, endpoint};
4
5pub const URL: &str = "https://friends.roblox.com/v1";
6
7#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
8#[serde(rename_all = "camelCase")]
9pub struct FollowingStatus {
10 #[serde(rename = "userId")]
11 pub id: u64,
12 pub is_following: bool,
13 pub is_followed: bool,
14}
15
16#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
17pub struct FriendStatus {
18 pub id: u64,
19 pub status: String,
20}
21
22#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
23pub enum FriendRequestSourceType {
24 InGame,
25 UserProfile,
26 PlayerSearch,
27 FriendRecommendations,
28}
29
30#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
31#[serde(rename_all = "camelCase")]
32pub struct FriendRequester {
33 #[serde(rename = "senderId")]
34 pub id: u64,
35 #[serde(rename = "senderNickname")]
36 pub display_name: String,
37 pub contact_name: Option<String>,
38
39 pub source_universe_id: Option<u64>,
40 pub origin_source_type: FriendRequestSourceType,
41 pub sent_at: DateTime,
42}
43
44#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
45#[serde(rename_all = "camelCase")]
46pub struct FriendRequest {
47 pub id: u64,
48 pub mutual_friends_list: Vec<String>,
49 #[serde(rename = "friendRequest")]
50 pub requester: FriendRequester,
51}
52
53#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
54#[serde(rename_all = "camelCase")]
55pub struct FriendRequests {
56 #[serde(rename = "data")]
57 pub requests: Vec<FriendRequest>,
58 #[serde(rename = "nextPageCursor")]
59 pub next_cursor: Option<String>,
60 #[serde(rename = "previousPageCursor")]
61 pub previous_cursor: Option<String>,
62}
63
64#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
65#[serde(rename_all = "camelCase")]
66pub struct User {
67 pub id: u64,
68 #[serde(rename = "hasVerifiedBadge")]
69 pub is_verified: Option<bool>,
70}
71
72#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
73#[serde(rename_all = "camelCase")]
74pub struct Followers {
75 #[serde(rename = "data")]
76 pub users: Vec<User>,
77 #[serde(rename = "nextPageCursor")]
78 pub next_cursor: Option<String>,
79 #[serde(rename = "previousPageCursor")]
80 pub previous_cursor: Option<String>,
81}
82
83#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
84#[serde(rename_all = "PascalCase")]
85pub struct FriendsFind {
86 #[serde(rename = "PageItems")]
87 pub users: Vec<User>,
88 pub next_cursor: Option<String>,
89 pub previous_cursor: Option<String>,
90 pub has_more: Option<bool>,
91}
92
93#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
94#[serde(rename_all = "camelCase")]
95pub struct UserPresence {
96 #[serde(rename = "UserPresenceType")]
97 pub kind: String,
98 #[serde(rename = "UserLocationType")]
99 pub location_kind: String,
100
101 #[serde(rename = "lastLocation")]
102 pub status: String,
103 pub last_online: DateTime,
104
105 pub place_id: Option<u64>,
106 pub root_place_id: Option<u64>,
107 pub universe_id: Option<u64>,
108 #[serde(rename = "gameInstanceId")]
109 pub job_id: Option<String>,
110}
111
112#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
113#[serde(rename_all = "camelCase")]
114pub struct FriendOnlineStatus {
115 pub id: u64,
116 #[serde(rename = "userPresence")]
117 pub presence: UserPresence,
118}
119
120#[derive(Debug, Deserialize)]
121struct CountResponse {
122 count: u16,
123}
124
125endpoint! {
126 friend_requests_count() -> u16 {
127 GET "{URL}/user/friend-requests/count";
128 map |r: CountResponse| r.count
129 }
130
131 new_friend_requests_count() -> u16 {
132 GET "{URL}/my/new-friend-requests/count";
133 map |r: CountResponse| r.count
134 }
135
136 user_friends_count(id: u64) -> u16 {
137 GET "{URL}/users/{id}/friends/count";
138 map |r: CountResponse| r.count
139 }
140
141 user_followings_count(id: u64) -> u16 {
142 GET "{URL}/users/{id}/followings/count";
143 map |r: CountResponse| r.count
144 }
145
146 user_followers_count(id: u64) -> u16 {
147 GET "{URL}/users/{id}/followers/count";
148 map |r: CountResponse| r.count
149 }
150
151 following_status(ids: &[u64]) -> Vec<FollowingStatus> {
152 POST "{URL}/user/following-exists";
153 types {
154 Request<'a> {
155 user_ids("targetUserIds"): &'a [u64],
156 }
157 Response {
158 statuses("followings"): Vec<FollowingStatus>,
159 }
160 }
161 body_serialize {
162 &Request { user_ids: ids }
163 }
164 map |r: Response| r.statuses
165 }
166
167 friend_requests(paging: Paging<'_>) -> FriendRequests {
168 GET "{URL}/my/friends/requests";
169 paging_query { paging, limit = 18 }
170 }
171
172 user_followers(id: u64) -> Followers {
173 GET "{URL}/users/{id}/followers";
174 }
175
176 user_followings(id: u64) -> Followers {
177 GET "{URL}/users/{id}/followings";
178 }
179
180 user_friends_online(id: u64) -> Vec<FriendOnlineStatus> {
181 GET "{URL}/users/{id}/friends/online";
182 types {
183 Response {
184 online("data"): Vec<FriendOnlineStatus>,
185 }
186 }
187 map |r: Response| r.online
188 }
189
190 user_friends_find(id: u64, paging: Paging<'_>) -> FriendsFind {
191 GET "{URL}/users/{id}/friends/find";
192 prelude {
193 let limit = paging.limit.unwrap_or(18).to_string();
194 let cursor = paging.cursor.unwrap_or("");
195 }
196 query {
197 "cursor" => cursor,
198 "limit" => &limit,
199 "userSort" => "1",
200 }
201 }
202
203 user_friends_search(id: u64, query: &str, paging: Paging<'_>) -> FriendsFind {
204 GET "{URL}/users/{id}/friends/search";
205 paging_query { paging, limit = 36 }
206 query {
207 "query" => query,
208 }
209 }
210
211 user_friend_statuses(id: u64, friends: &[u64]) -> Vec<FriendStatus> {
212 GET "{URL}/users/{id}/friends/statuses";
213 types {
214 Response {
215 statuses("data"): Vec<FriendStatus>,
216 }
217 }
218 prelude {
219 let ids = friends
220 .iter()
221 .map(|x| x.to_string())
222 .collect::<Vec<String>>()
223 .join(",");
224 }
225 query {
226 "userIds" => &ids,
227 }
228 map |r: Response| r.statuses
229 }
230}