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