Skip to main content

roblox_api/api/platform_chat/
v1.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{DateTime, Paging, endpoint};
6
7pub const URL: &str = "https://apis.roblox.com/platform-chat-api/v1";
8
9#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
10#[serde(rename_all = "snake_case")]
11pub enum ConversationType {
12    OneToOne,
13    Group,
14}
15
16#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
17#[serde(rename_all = "snake_case")]
18pub enum ConversationSource {
19    Channels,
20    Friends,
21}
22
23#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
24pub struct ConversationUser {
25    pub id: u64,
26    pub name: String,
27    pub display_name: String,
28    // what in the fuck is this, seems to be just display_name.unwrap_or(name) so just display_name, cause display_name defaults to name if unset
29    pub combined_name: String,
30    pub is_verified: bool,
31}
32
33#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
34pub struct Message {
35    pub id: String,
36    pub content: String,
37    #[serde(rename = "type")]
38    pub kind: String, // maybe enum
39
40    #[serde(rename = "sender_user_id")]
41    pub sender_id: u64,
42    pub replies_to: Option<()>, // to who?
43
44    #[serde(rename = "created_at")]
45    pub created: DateTime,
46
47    pub moderation_type: String, // maybe enum
48
49    pub is_deleted: bool,
50    pub is_badgeable: bool,
51    pub is_previewable: bool,
52}
53
54#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
55pub struct Conversation {
56    pub id: Option<String>,
57    pub name: String,
58    #[serde(rename = "type")]
59    pub kind: ConversationType,
60    pub source: String, // maybe enum?
61
62    #[serde(rename = "created_by")]
63    pub creator_id: Option<u64>,
64    #[serde(rename = "participant_user_ids")]
65    pub participants: Vec<u64>,
66    #[serde(rename = "user_data")]
67    // I tried with serde_with to make this a Vec<V>, although I failed
68    // TODO: refactor to Vec<ConversationUser>
69    pub users: HashMap<String, ConversationUser>,
70
71    pub messages: Vec<Message>,
72    pub preview_message: Option<Message>,
73
74    pub sort_index: u64,
75    pub unread_message_count: u64,
76
77    #[serde(rename = "created_at")]
78    pub created: DateTime,
79    #[serde(rename = "updated_at")]
80    pub updated: DateTime,
81
82    pub status: Option<String>,          // maybe enum
83    pub moderation_type: Option<String>, // maybe enum
84
85    pub user_pending_status: Option<String>, // maybe enum
86    pub participant_pending_status: Option<String>, // maybe enum
87    pub osa_acknowledgement_status: String,  // maybe enum
88
89    pub is_default_name: bool,
90}
91
92#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
93pub struct Conversations {
94    pub conversations: Vec<Conversation>,
95    pub next_cursor: Option<String>,
96    pub previous_cursor: Option<String>,
97}
98
99#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
100pub struct ConversationMessages {
101    pub messages: Vec<Message>,
102    pub next_cursor: Option<String>,
103    pub previous_cursor: Option<String>,
104}
105
106#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
107pub struct ConversationMetadata {
108    pub global_unread_count: u64,
109    pub global_unread_message_count: u64,
110}
111
112#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
113pub struct ParticipantMetadata {
114    pub id: u64,
115    pub is_pending: bool,
116}
117
118#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
119pub struct ConversationsParticipantMetadata {
120    pub id: String,
121    pub participants: Vec<ParticipantMetadata>,
122}
123
124#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
125pub struct ConversationMarkedStatus {
126    #[serde(rename = "conversation_id")]
127    pub id: String,
128    pub status: String, // maybe enum?
129}
130
131#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
132pub struct ConversationCreateRequest {
133    pub name: String,
134    pub users: Vec<u64>,
135}
136
137endpoint! {
138    conversation_metadata() -> ConversationMetadata {
139        GET "{URL}/get-conversation-metadata";
140    }
141
142    conversations_participant_metadata(ids: &[&str]) -> Vec<ConversationsParticipantMetadata> {
143        POST "{URL}/get-conversations-participants-metadata";
144        types {
145            Request<'a> { ids("conversation_ids"): &'a [&'a str] }
146            Response { metadata("conversation_participants_metadata"): HashMap<String, ParticipantsMetadata> }
147            ParticipantsMetadata { participants_metadata: HashMap<String, ParticipantPending> }
148            ParticipantPending { is_pending: bool }
149        }
150        body_serialize { Request { ids } }
151        map |r: Response| {
152            let mut metadata = Vec::new();
153            for (conv_id, v) in &r.metadata {
154                let mut participants = Vec::new();
155                for (user_id, pending) in &v.participants_metadata {
156                    participants.push(ParticipantMetadata {
157                        id: user_id.parse().unwrap(),
158                        is_pending: pending.is_pending,
159                    });
160                }
161                metadata.push(ConversationsParticipantMetadata {
162                    id: conv_id.to_owned(),
163                    participants,
164                });
165            }
166            metadata
167        }
168    }
169
170    conversations(ids: &[&str]) -> Conversations {
171        POST "{URL}/get-conversations";
172        types {
173            Request<'a> {
174                ids: &'a [&'a str],
175                include_messages: bool,
176                include_user_data: bool,
177                include_participants: bool,
178            }
179        }
180        body_serialize {
181            Request { ids, include_messages: true, include_user_data: true, include_participants: true }
182        }
183    }
184
185    user_conversations(paging: Paging<'_>) -> Conversations {
186        GET "{URL}/get-user-conversations";
187        prelude {
188            let limit = paging.limit.unwrap_or(20).to_string();
189            let cursor_str = match paging.cursor {
190                Some(c) => c.to_string(),
191                None => String::new(),
192            };
193        }
194        query {
195            "cursor" => &cursor_str,
196            "include_user_data" => "true",
197            "pageSize" => &limit,
198        }
199    }
200
201    conversation_messages(id: &str) -> ConversationMessages {
202        GET "{URL}/get-conversation-messages";
203        query { "conversation_id" => id }
204    }
205
206    /// Apparently you can only send 1 message at a time, but it's a vector in case roblox decides to change this behavior
207    send_messages_in_conversation(id: &str, messages: &[&str]) -> ConversationMessages {
208        POST "{URL}/send-messages";
209        types {
210            MessageToPost { content: String }
211            Request<'a> { id("conversation_id"): &'a str, messages: &'a [MessageToPost] }
212        }
213        prelude {
214            let msgs: Vec<MessageToPost> = messages.iter().map(|x| MessageToPost { content: x.to_string() }).collect();
215        }
216        body_serialize { Request { id, messages: &msgs } }
217    }
218
219    update_typing_status_in_conversation(id: &str) -> String {
220        POST "{URL}/update-typing-status";
221        types {
222            Request<'a> { id("conversation_id"): &'a str }
223            Response { status: String }
224        }
225        body_serialize { Request { id } }
226        map |r: Response| r.status
227    }
228
229    add_users_to_conversation(id: &str, users: &[u64]) -> String {
230        POST "{URL}/add-users";
231        types {
232            Request<'a> { id("conversation_id"): &'a str, users("user_ids"): &'a [u64] }
233            Response { status: String }
234        }
235        body_serialize { Request { id, users } }
236        map |r: Response| r.status
237    }
238
239    remove_users_from_conversation(id: &str, users: &[u64]) -> String {
240        POST "{URL}/remove-users";
241        types {
242            Request<'a> { id("conversation_id"): &'a str, users("user_ids"): &'a [u64] }
243            Response { status: String }
244        }
245        body_serialize { Request { id, users } }
246        map |r: Response| r.status
247    }
248
249    create_conversations(conversations: &[ConversationCreateRequest]) -> Conversations {
250        POST "{URL}/create-conversations";
251        types {
252            ConversationToCreate {
253                name: String,
254                kind("type"): String,
255                users("participant_user_ids"): Vec<u64>,
256            }
257            Request<'a> { conversations: &'a [ConversationToCreate], include_user_data: bool }
258        }
259        prelude {
260            let convs: Vec<ConversationToCreate> = conversations.iter().map(|x| ConversationToCreate {
261                name: x.name.clone(),
262                kind: "group".to_string(),
263                users: x.users.clone(),
264            }).collect();
265        }
266        body_serialize { Request { conversations: &convs, include_user_data: true } }
267    }
268
269    rename_conversations(ids: &[&str], names: &[&str]) -> Conversations {
270        POST "{URL}/update-conversations";
271        types {
272            ConversationToUpdate { id: String, name: String }
273            Request<'a> { conversations: &'a [ConversationToUpdate] }
274        }
275        prelude {
276            let updates: Vec<ConversationToUpdate> = ids.iter()
277                .zip(names.iter())
278                .map(|(id, name)| ConversationToUpdate { id: id.to_string(), name: name.to_string() })
279                .collect();
280        }
281        body_serialize { Request { conversations: &updates } }
282    }
283
284    mark_conversations_as_read(ids: &[&str]) -> Vec<ConversationMarkedStatus> {
285        POST "{URL}/mark-conversations";
286        types {
287            Request<'a> { ids("conversation_ids"): &'a [&'a str] }
288            Response { results: Vec<ConversationMarkedStatus> }
289        }
290        body_serialize { Request { ids } }
291        map |r: Response| r.results
292    }
293}