roblox_api/api/platform_chat/
v1.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{DateTime, Error, Paging, client::Client};
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
137pub async fn conversation_metadata(client: &mut Client) -> Result<ConversationMetadata, Error> {
138    let result = client
139        .requestor
140        .client
141        .get(format!("{URL}/get-conversation-metadata"))
142        .headers(client.requestor.default_headers.clone())
143        .send()
144        .await;
145
146    let response = client.requestor.validate_response(result).await?;
147    client
148        .requestor
149        .parse_json::<ConversationMetadata>(response)
150        .await
151}
152
153pub async fn conversations_participant_metadata(
154    client: &mut Client,
155    ids: &[&str],
156) -> Result<Vec<ConversationsParticipantMetadata>, Error> {
157    #[derive(Debug, Serialize)]
158    struct Request<'a> {
159        #[serde(rename = "conversation_ids")]
160        ids: &'a [&'a str],
161    }
162
163    let result = client
164        .requestor
165        .client
166        .post(format!("{URL}/get-conversations-participants-metadata"))
167        .json(&Request { ids })
168        .headers(client.requestor.default_headers.clone())
169        .send()
170        .await;
171
172    #[derive(Debug, Deserialize)]
173    struct ParticipantPending {
174        is_pending: bool,
175    }
176
177    #[derive(Debug, Deserialize)]
178    struct ParticipantsMetadata {
179        participants_metadata: HashMap<String, ParticipantPending>,
180    }
181
182    #[derive(Debug, Deserialize)]
183    struct Response {
184        #[serde(rename = "conversation_participants_metadata")]
185        metadata: HashMap<String, ParticipantsMetadata>,
186    }
187
188    let response = client.requestor.validate_response(result).await?;
189    let response = client.requestor.parse_json::<Response>(response).await?;
190
191    let mut metadata = Vec::new();
192    for (k, v) in &response.metadata {
193        let mut participants = Vec::new();
194        for (k, v) in &v.participants_metadata {
195            participants.push(ParticipantMetadata {
196                id: k.parse().unwrap(),
197                is_pending: v.is_pending,
198            });
199        }
200
201        metadata.push(ConversationsParticipantMetadata {
202            id: k.to_owned(),
203            participants,
204        })
205    }
206
207    Ok(metadata)
208}
209
210pub async fn conversations(client: &mut Client, ids: &[&str]) -> Result<Conversations, Error> {
211    #[derive(Debug, Serialize)]
212    struct Request<'a> {
213        ids: &'a [&'a str],
214        include_messages: bool,
215        include_user_data: bool,
216        include_participants: bool,
217    }
218
219    let result = client
220        .requestor
221        .client
222        .post(format!("{URL}/get-conversations"))
223        .json(&Request {
224            ids,
225            include_messages: true,
226            include_user_data: true,
227            include_participants: true,
228        })
229        .headers(client.requestor.default_headers.clone())
230        .send()
231        .await;
232
233    let response = client.requestor.validate_response(result).await?;
234    client.requestor.parse_json::<Conversations>(response).await
235}
236
237pub async fn user_conversations(
238    client: &mut Client,
239    paging: Paging<'_>,
240) -> Result<Conversations, Error> {
241    let limit = paging.limit.unwrap_or(20).to_string();
242    let cursor = match paging.cursor {
243        Some(cursor) => cursor.to_string(),
244        None => String::new(),
245    };
246
247    let result = client
248        .requestor
249        .client
250        .get(format!("{URL}/get-user-conversations"))
251        .query(&[
252            ("cursor", cursor),
253            ("include_user_data", true.to_string()),
254            ("pageSize", limit),
255        ])
256        .headers(client.requestor.default_headers.clone())
257        .send()
258        .await;
259
260    let response = client.requestor.validate_response(result).await?;
261    client.requestor.parse_json::<Conversations>(response).await
262}
263
264pub async fn conversation_messages(
265    client: &mut Client,
266    id: &str,
267) -> Result<ConversationMessages, Error> {
268    let result = client
269        .requestor
270        .client
271        .get(format!("{URL}/get-conversation-messages"))
272        .query(&[("conversation_id", id)])
273        .headers(client.requestor.default_headers.clone())
274        .send()
275        .await;
276
277    let response = client.requestor.validate_response(result).await?;
278    client
279        .requestor
280        .parse_json::<ConversationMessages>(response)
281        .await
282}
283
284/// Apparently you can only send 1 message at a time, but it's a vector in case roblox decides to change this behavior
285pub async fn send_messages_in_conversation(
286    client: &mut Client,
287    id: &str,
288    messages: &[&str],
289) -> Result<ConversationMessages, Error> {
290    #[derive(Debug, Serialize)]
291    struct MessageToPost<'a> {
292        content: &'a str,
293    }
294
295    #[derive(Debug, Serialize)]
296    struct Request<'a> {
297        #[serde(rename = "conversation_id")]
298        id: &'a str,
299        messages: &'a [MessageToPost<'a>],
300    }
301
302    let messages = &messages
303        .iter()
304        .map(|x| MessageToPost { content: x })
305        .collect::<Vec<_>>();
306
307    let result = client
308        .requestor
309        .client
310        .post(format!("{URL}/send-messages"))
311        .json(&Request { id, messages })
312        .headers(client.requestor.default_headers.clone())
313        .send()
314        .await;
315
316    let response = client.requestor.validate_response(result).await?;
317    client
318        .requestor
319        .parse_json::<ConversationMessages>(response)
320        .await
321}
322
323pub async fn update_typing_status_in_conversation(
324    client: &mut Client,
325    id: &str,
326) -> Result<String, Error> {
327    #[derive(Debug, Serialize)]
328    struct Request<'a> {
329        #[serde(rename = "conversation_id")]
330        id: &'a str,
331    }
332
333    let result = client
334        .requestor
335        .client
336        .post(format!("{URL}/update-typing-status"))
337        .json(&Request { id })
338        .headers(client.requestor.default_headers.clone())
339        .send()
340        .await;
341
342    #[derive(Debug, Deserialize)]
343    struct Response {
344        status: String,
345    }
346
347    let response = client.requestor.validate_response(result).await?;
348    Ok(client
349        .requestor
350        .parse_json::<Response>(response)
351        .await?
352        .status)
353}
354
355pub async fn add_users_to_conversation(
356    client: &mut Client,
357    id: &str,
358    users: &[u64],
359) -> Result<String, Error> {
360    #[derive(Debug, Serialize)]
361    struct Request<'a> {
362        #[serde(rename = "conversation_id")]
363        id: &'a str,
364        #[serde(rename = "user_ids")]
365        users: &'a [u64],
366    }
367
368    let result = client
369        .requestor
370        .client
371        .post(format!("{URL}/add-users"))
372        .json(&Request { id, users })
373        .headers(client.requestor.default_headers.clone())
374        .send()
375        .await;
376
377    #[derive(Debug, Deserialize)]
378    struct Response {
379        status: String,
380    }
381
382    let response = client.requestor.validate_response(result).await?;
383    Ok(client
384        .requestor
385        .parse_json::<Response>(response)
386        .await?
387        .status)
388}
389
390pub async fn remove_users_from_conversation(
391    client: &mut Client,
392    id: &str,
393    users: &[u64],
394) -> Result<String, Error> {
395    #[derive(Debug, Serialize)]
396    struct Request<'a> {
397        #[serde(rename = "conversation_id")]
398        id: &'a str,
399        #[serde(rename = "user_ids")]
400        users: &'a [u64],
401    }
402
403    let result = client
404        .requestor
405        .client
406        .post(format!("{URL}/remove-users"))
407        .json(&Request { id, users })
408        .headers(client.requestor.default_headers.clone())
409        .send()
410        .await;
411
412    #[derive(Debug, Deserialize)]
413    struct Response {
414        status: String,
415    }
416
417    let response = client.requestor.validate_response(result).await?;
418    Ok(client
419        .requestor
420        .parse_json::<Response>(response)
421        .await?
422        .status)
423}
424
425pub async fn create_conversations(
426    client: &mut Client,
427    conversations: &[ConversationCreateRequest],
428) -> Result<Conversations, Error> {
429    #[derive(Debug, Serialize)]
430    struct ConversationToCreate<'a> {
431        name: &'a str,
432        #[serde(rename = "type")]
433        kind: &'a str,
434        #[serde(rename = "participant_user_ids")]
435        users: &'a [u64],
436    }
437
438    #[derive(Debug, Serialize)]
439    struct Request<'a> {
440        conversations: &'a [ConversationToCreate<'a>],
441        include_user_data: bool,
442    }
443
444    let conversations = &conversations
445        .iter()
446        .map(|x| ConversationToCreate {
447            name: &x.name,
448            kind: "group",
449            users: &x.users,
450        })
451        .collect::<Vec<_>>();
452
453    let result = client
454        .requestor
455        .client
456        .post(format!("{URL}/create-conversations"))
457        .json(&Request {
458            conversations,
459            include_user_data: true,
460        })
461        .headers(client.requestor.default_headers.clone())
462        .send()
463        .await;
464
465    let response = client.requestor.validate_response(result).await?;
466    client.requestor.parse_json::<Conversations>(response).await
467}
468
469pub async fn rename_conversations(
470    client: &mut Client,
471    ids: &[&str],
472    names: &[&str],
473) -> Result<Conversations, Error> {
474    #[derive(Debug, Serialize)]
475    struct ConversationToUpdate<'a> {
476        id: &'a str,
477        name: &'a str,
478    }
479
480    #[derive(Debug, Serialize)]
481    struct Request<'a> {
482        conversations: &'a [ConversationToUpdate<'a>],
483    }
484
485    let conversations = &ids
486        .iter()
487        .zip(names.into_iter())
488        .collect::<Vec<_>>()
489        .iter()
490        .map(|(id, name)| ConversationToUpdate { id, name })
491        .collect::<Vec<_>>();
492
493    let result = client
494        .requestor
495        .client
496        .post(format!("{URL}/update-conversations"))
497        .json(&Request { conversations })
498        .headers(client.requestor.default_headers.clone())
499        .send()
500        .await;
501
502    let response = client.requestor.validate_response(result).await?;
503    client.requestor.parse_json::<Conversations>(response).await
504}
505
506pub async fn mark_conversations_as_read(
507    client: &mut Client,
508    ids: &[&str],
509) -> Result<Vec<ConversationMarkedStatus>, Error> {
510    #[derive(Debug, Serialize)]
511    struct Request<'a> {
512        #[serde(rename = "conversation_ids")]
513        ids: &'a [&'a str],
514    }
515
516    let result = client
517        .requestor
518        .client
519        .post(format!("{URL}/mark-conversations"))
520        .json(&Request { ids })
521        .headers(client.requestor.default_headers.clone())
522        .send()
523        .await;
524
525    #[derive(Debug, Deserialize)]
526    struct Response {
527        results: Vec<ConversationMarkedStatus>,
528    }
529
530    let response = client.requestor.validate_response(result).await?;
531    Ok(client
532        .requestor
533        .parse_json::<Response>(response)
534        .await?
535        .results)
536}