Skip to main content

moltbook_cli/api/
types.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Serialize, Deserialize, Debug, Clone)]
4pub struct ApiResponse<T> {
5    pub success: bool,
6    #[serde(flatten)]
7    pub data: Option<T>,
8    pub error: Option<String>,
9    pub hint: Option<String>,
10    pub retry_after_minutes: Option<u64>,
11    pub retry_after_seconds: Option<u64>,
12}
13
14#[derive(Serialize, Deserialize, Debug, Clone)]
15pub struct Agent {
16    pub id: String,
17    pub name: String,
18    pub description: Option<String>,
19    #[serde(
20        default,
21        deserialize_with = "serde_helpers::deserialize_option_string_or_i64"
22    )]
23    pub karma: Option<i64>,
24    #[serde(
25        default,
26        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
27    )]
28    pub follower_count: Option<u64>,
29    #[serde(
30        default,
31        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
32    )]
33    pub following_count: Option<u64>,
34    pub is_claimed: Option<bool>,
35    pub is_active: Option<bool>,
36    pub created_at: Option<String>,
37    pub last_active: Option<String>,
38    pub claimed_at: Option<String>,
39    pub owner_id: Option<String>,
40    pub owner: Option<OwnerInfo>,
41    pub stats: Option<AgentStats>,
42    pub metadata: Option<serde_json::Value>,
43    pub recent_posts: Option<Vec<Post>>,
44}
45
46#[derive(Serialize, Deserialize, Debug, Clone)]
47pub struct OwnerInfo {
48    #[serde(alias = "xHandle")]
49    pub x_handle: Option<String>,
50    #[serde(alias = "xName")]
51    pub x_name: Option<String>,
52    #[serde(alias = "xAvatar")]
53    pub x_avatar: Option<String>,
54    #[serde(alias = "xBio")]
55    pub x_bio: Option<String>,
56    #[serde(
57        default,
58        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
59    )]
60    pub x_follower_count: Option<u64>,
61    #[serde(
62        default,
63        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
64    )]
65    pub x_following_count: Option<u64>,
66    pub x_verified: Option<bool>,
67}
68
69#[derive(Serialize, Deserialize, Debug, Clone)]
70pub struct AgentStats {
71    pub posts: Option<u64>,
72    pub comments: Option<u64>,
73    pub subscriptions: Option<u64>,
74}
75
76#[derive(Serialize, Deserialize, Debug, Clone)]
77pub struct StatusResponse {
78    pub status: Option<String>,
79    pub message: Option<String>,
80    pub next_step: Option<String>,
81    pub agent: Option<Agent>,
82}
83
84#[derive(Serialize, Deserialize, Debug, Clone)]
85pub struct PostResponse {
86    pub success: bool,
87    pub message: Option<String>,
88    pub post: Option<Post>,
89    pub verification_required: Option<bool>,
90    pub verification: Option<VerificationChallenge>,
91}
92
93#[derive(Serialize, Deserialize, Debug, Clone)]
94pub struct VerificationChallenge {
95    pub code: String,
96    pub challenge: String,
97    pub instructions: String,
98    pub verify_endpoint: String,
99}
100
101#[derive(Serialize, Deserialize, Debug, Clone)]
102pub struct Post {
103    pub id: String,
104    pub title: String,
105    pub content: Option<String>,
106    pub url: Option<String>,
107    #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
108    pub upvotes: i64,
109    #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
110    pub downvotes: i64,
111    #[serde(
112        default,
113        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
114    )]
115    pub comment_count: Option<u64>,
116    pub created_at: String,
117    pub author: Author,
118    pub submolt: Option<SubmoltInfo>,
119    pub submolt_name: Option<String>,
120}
121
122#[derive(Serialize, Deserialize, Debug, Clone)]
123pub struct Author {
124    pub id: Option<String>,
125    pub name: String,
126    pub description: Option<String>,
127    #[serde(
128        default,
129        deserialize_with = "serde_helpers::deserialize_option_string_or_i64"
130    )]
131    pub karma: Option<i64>,
132    #[serde(
133        default,
134        alias = "followerCount",
135        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
136    )]
137    pub follower_count: Option<u64>,
138    pub owner: Option<OwnerInfo>,
139}
140
141#[derive(Serialize, Deserialize, Debug, Clone)]
142pub struct SubmoltInfo {
143    pub name: String,
144    pub display_name: String,
145}
146
147#[derive(Serialize, Deserialize, Debug, Clone)]
148pub struct SearchResult {
149    pub id: String,
150    #[serde(rename = "type")]
151    pub result_type: String,
152    pub title: Option<String>,
153    pub content: Option<String>,
154    #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
155    pub upvotes: i64,
156    #[serde(deserialize_with = "serde_helpers::deserialize_string_or_i64")]
157    pub downvotes: i64,
158    #[serde(alias = "relevance")]
159    pub similarity: Option<f64>,
160    pub author: Author,
161    pub post_id: Option<String>,
162}
163
164#[derive(Serialize, Deserialize, Debug, Clone)]
165pub struct Submolt {
166    pub id: Option<String>,
167    pub name: String,
168    pub display_name: String,
169    pub description: Option<String>,
170    #[serde(
171        default,
172        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
173    )]
174    pub subscriber_count: Option<u64>,
175    pub allow_crypto: Option<bool>,
176    pub created_at: Option<String>,
177    pub last_activity_at: Option<String>,
178}
179
180#[derive(Serialize, Deserialize, Debug, Clone)]
181pub struct DmRequest {
182    pub from: Author,
183    pub message: Option<String>,
184    pub message_preview: Option<String>,
185    pub conversation_id: String,
186}
187
188#[derive(Serialize, Deserialize, Debug, Clone)]
189pub struct Conversation {
190    pub conversation_id: String,
191    pub with_agent: Author,
192    pub unread_count: u64,
193}
194
195#[derive(Serialize, Deserialize, Debug, Clone)]
196pub struct Message {
197    pub from_agent: Author,
198    pub message: String,
199    pub from_you: bool,
200    pub needs_human_input: bool,
201    pub created_at: String,
202}
203
204#[derive(Serialize, Deserialize, Debug, Clone)]
205pub struct FeedResponse {
206    pub success: bool,
207    pub posts: Vec<Post>,
208    #[serde(
209        default,
210        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
211    )]
212    pub count: Option<u64>,
213    pub has_more: Option<bool>,
214    #[serde(
215        default,
216        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
217    )]
218    pub next_offset: Option<u64>,
219    pub authenticated: Option<bool>,
220}
221
222#[derive(Serialize, Deserialize, Debug, Clone)]
223pub struct SearchResponse {
224    pub results: Vec<SearchResult>,
225}
226
227#[derive(Serialize, Deserialize, Debug, Clone)]
228pub struct SubmoltsResponse {
229    pub submolts: Vec<Submolt>,
230}
231
232#[derive(Serialize, Deserialize, Debug, Clone)]
233pub struct DmCheckResponse {
234    pub has_activity: bool,
235    pub summary: Option<String>,
236    pub requests: Option<DmRequestsData>,
237    pub messages: Option<DmMessagesData>,
238}
239
240#[derive(Serialize, Deserialize, Debug, Clone)]
241pub struct SubmoltFeedResponse {
242    pub posts: Vec<Post>,
243    #[serde(
244        default,
245        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
246    )]
247    pub total: Option<u64>,
248}
249
250#[derive(Serialize, Deserialize, Debug, Clone)]
251pub struct DmRequestsData {
252    #[serde(
253        default,
254        deserialize_with = "serde_helpers::deserialize_option_string_or_u64"
255    )]
256    pub count: Option<u64>,
257    pub items: Vec<DmRequest>,
258}
259
260#[derive(Serialize, Deserialize, Debug, Clone)]
261pub struct DmMessagesData {
262    #[serde(deserialize_with = "serde_helpers::deserialize_string_or_u64")]
263    pub total_unread: u64,
264}
265
266#[derive(Serialize, Deserialize, Debug, Clone)]
267pub struct DmListResponse {
268    pub conversations: DmConversationsData,
269    #[serde(deserialize_with = "serde_helpers::deserialize_string_or_u64")]
270    pub total_unread: u64,
271}
272
273#[derive(Serialize, Deserialize, Debug, Clone)]
274pub struct DmConversationsData {
275    pub items: Vec<Conversation>,
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn test_post_deserialization() {
284        let json = r#"{
285            "id": "123",
286            "title": "Test Post",
287            "content": "Content",
288            "upvotes": 10,
289            "downvotes": 0,
290            "created_at": "2024-01-01T00:00:00Z",
291            "author": {"name": "Bot"},
292            "submolt": {"name": "general", "display_name": "General"}
293        }"#;
294
295        let post: Post = serde_json::from_str(json).unwrap();
296        assert_eq!(post.title, "Test Post");
297        assert_eq!(post.upvotes, 10);
298    }
299
300    #[test]
301    fn test_api_response_success() {
302        let json = r#"{"success": true, "id": "123", "name": "Test"}"#;
303        let resp: ApiResponse<serde_json::Value> = serde_json::from_str(json).unwrap();
304        assert!(resp.success);
305        assert!(resp.data.is_some());
306    }
307
308    #[test]
309    fn test_api_response_error() {
310        let json =
311            r#"{"success": false, "error": "Invalid key", "hint": "Check your credentials"}"#;
312        let resp: ApiResponse<serde_json::Value> = serde_json::from_str(json).unwrap();
313        assert!(!resp.success);
314        assert_eq!(resp.error, Some("Invalid key".to_string()));
315        assert_eq!(resp.hint, Some("Check your credentials".to_string()));
316    }
317}
318
319#[derive(Serialize, Deserialize, Debug, Clone)]
320pub struct RegistrationResponse {
321    pub success: bool,
322    pub agent: RegisteredAgent,
323}
324
325#[derive(Serialize, Deserialize, Debug, Clone)]
326pub struct RegisteredAgent {
327    pub name: String,
328    pub api_key: String,
329    pub claim_url: String,
330    pub verification_code: String,
331}
332
333mod serde_helpers {
334    use serde::{Deserialize, Deserializer};
335
336    pub fn deserialize_option_string_or_u64<'de, D>(
337        deserializer: D,
338    ) -> Result<Option<u64>, D::Error>
339    where
340        D: Deserializer<'de>,
341    {
342        #[derive(Deserialize)]
343        #[serde(untagged)]
344        enum StringOrInt {
345            String(String),
346            Int(u64),
347        }
348
349        match Option::<StringOrInt>::deserialize(deserializer)? {
350            Some(StringOrInt::String(s)) => {
351                s.parse::<u64>().map(Some).map_err(serde::de::Error::custom)
352            }
353            Some(StringOrInt::Int(i)) => Ok(Some(i)),
354            None => Ok(None),
355        }
356    }
357
358    pub fn deserialize_string_or_i64<'de, D>(deserializer: D) -> Result<i64, D::Error>
359    where
360        D: Deserializer<'de>,
361    {
362        #[derive(Deserialize)]
363        #[serde(untagged)]
364        enum StringOrInt {
365            String(String),
366            Int(i64),
367        }
368
369        match StringOrInt::deserialize(deserializer)? {
370            StringOrInt::String(s) => s.parse::<i64>().map_err(serde::de::Error::custom),
371            StringOrInt::Int(i) => Ok(i),
372        }
373    }
374
375    pub fn deserialize_option_string_or_i64<'de, D>(
376        deserializer: D,
377    ) -> Result<Option<i64>, D::Error>
378    where
379        D: Deserializer<'de>,
380    {
381        #[derive(Deserialize)]
382        #[serde(untagged)]
383        enum StringOrInt {
384            String(String),
385            Int(i64),
386        }
387
388        match Option::<StringOrInt>::deserialize(deserializer)? {
389            Some(StringOrInt::String(s)) => {
390                s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
391            }
392            Some(StringOrInt::Int(i)) => Ok(Some(i)),
393            None => Ok(None),
394        }
395    }
396
397    pub fn deserialize_string_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
398    where
399        D: Deserializer<'de>,
400    {
401        #[derive(Deserialize)]
402        #[serde(untagged)]
403        enum StringOrInt {
404            String(String),
405            Int(u64),
406        }
407
408        match StringOrInt::deserialize(deserializer)? {
409            StringOrInt::String(s) => s.parse::<u64>().map_err(serde::de::Error::custom),
410            StringOrInt::Int(i) => Ok(i),
411        }
412    }
413}