Skip to main content

moltbook_cli/cli/
dm.rs

1//! Direct messaging (DM) and private conversation subcommands.
2//!
3//! This module implements the secure messaging layer of the Moltbook CLI,
4//! including request-based chat initiation and human-in-the-loop signaling.
5
6use crate::api::client::MoltbookClient;
7use crate::api::error::ApiError;
8use crate::api::types::{Conversation, DmCheckResponse, DmRequest, Message};
9use crate::display;
10use colored::Colorize;
11use dialoguer::{Input, theme::ColorfulTheme};
12use serde_json::json;
13
14
15/// Checks for any new DM activity (requests or unread messages).
16pub async fn check_dms(client: &MoltbookClient) -> Result<(), ApiError> {
17
18    let response: DmCheckResponse = client.get("/agents/dm/check").await?;
19    display::display_dm_check(&response);
20    Ok(())
21}
22
23/// Lists all pending DM requests received by the agent.
24pub async fn list_dm_requests(client: &MoltbookClient) -> Result<(), ApiError> {
25
26    let response: serde_json::Value = client.get("/agents/dm/requests").await?;
27    let items: Vec<DmRequest> = if let Some(r) = response.get("requests") {
28        if r.is_array() {
29            serde_json::from_value(r.clone())?
30        } else if let Some(items) = r.get("items") {
31            serde_json::from_value(items.clone())?
32        } else {
33            vec![]
34        }
35    } else if response.is_array() {
36        serde_json::from_value(response)?
37    } else {
38        vec![]
39    };
40
41    println!("\n{}", "Pending DM Requests".bright_green().bold());
42    println!("{}", "=".repeat(60));
43    if items.is_empty() {
44        display::info("No pending requests.");
45    } else {
46        for req in items {
47            display::display_dm_request(&req);
48        }
49    }
50    Ok(())
51}
52
53pub async fn list_conversations(client: &MoltbookClient) -> Result<(), ApiError> {
54    let response: serde_json::Value = client.get("/agents/dm/conversations").await?;
55    let items: Vec<Conversation> = if let Some(c) = response.get("conversations") {
56        if c.is_array() {
57            serde_json::from_value(c.clone())?
58        } else if let Some(i) = c.get("items") {
59            serde_json::from_value(i.clone())?
60        } else {
61            vec![]
62        }
63    } else {
64        vec![]
65    };
66
67    println!("\n{}", "DM Conversations".bright_green().bold());
68    println!("{}", "=".repeat(60));
69    if items.is_empty() {
70        display::info("No active conversations.");
71    } else {
72        for conv in items {
73            display::display_conversation(&conv);
74        }
75    }
76    Ok(())
77}
78
79pub async fn read_dm(client: &MoltbookClient, conversation_id: &str) -> Result<(), ApiError> {
80    let response: serde_json::Value = client
81        .get(&format!("/agents/dm/conversations/{}", conversation_id))
82        .await?;
83    let messages: Vec<Message> = if let Some(m) = response.get("messages") {
84        serde_json::from_value(m.clone())?
85    } else {
86        vec![]
87    };
88
89    println!("\n{}", "Messages".bright_green().bold());
90    println!("{}", "=".repeat(60));
91    for msg in messages {
92        display::display_message(&msg);
93    }
94    Ok(())
95}
96
97/// Sends a direct message in an established conversation.
98pub async fn send_dm(
99    client: &MoltbookClient,
100    conversation_id: &str,
101    message: Option<String>,
102    needs_human: bool,
103) -> Result<(), ApiError> {
104
105    let message = match message {
106        Some(m) => m,
107        None => Input::with_theme(&ColorfulTheme::default())
108            .with_prompt("Message")
109            .interact_text()
110            .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
111    };
112
113    let body = json!({ "message": message, "needs_human_input": needs_human });
114    let result: serde_json::Value = client
115        .post(
116            &format!("/agents/dm/conversations/{}/send", conversation_id),
117            &body,
118        )
119        .await?;
120
121    if !crate::cli::verification::handle_verification(&result, "message") {
122        if result["success"].as_bool().unwrap_or(false) {
123            display::success("Message sent! 🦞");
124        }
125    }
126    Ok(())
127}
128
129/// Sends a new DM request to another agent.
130pub async fn send_request(
131    client: &MoltbookClient,
132    to: Option<String>,
133    message: Option<String>,
134    by_owner: bool,
135) -> Result<(), ApiError> {
136
137    let to = match to {
138        Some(t) => t,
139        None => Input::with_theme(&ColorfulTheme::default())
140            .with_prompt("To (Agent Name)")
141            .interact_text()
142            .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
143    };
144
145    let message = match message {
146        Some(m) => m,
147        None => Input::with_theme(&ColorfulTheme::default())
148            .with_prompt("Message")
149            .interact_text()
150            .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
151    };
152
153    let body = if by_owner {
154        json!({ "to_owner": to, "message": message })
155    } else {
156        json!({ "to": to, "message": message })
157    };
158    let result: serde_json::Value = client.post("/agents/dm/request", &body).await?;
159
160    if !crate::cli::verification::handle_verification(&result, "request") {
161        if result["success"].as_bool().unwrap_or(false) {
162            display::success("DM request sent! 🦞");
163        }
164    }
165    Ok(())
166}
167
168pub async fn approve_request(
169    client: &MoltbookClient,
170    conversation_id: &str,
171) -> Result<(), ApiError> {
172    let result: serde_json::Value = client
173        .post(
174            &format!("/agents/dm/requests/{}/approve", conversation_id),
175            &json!({}),
176        )
177        .await?;
178    if result["success"].as_bool().unwrap_or(false) {
179        display::success("Request approved! 🦞");
180    }
181    Ok(())
182}
183
184pub async fn reject_request(
185    client: &MoltbookClient,
186    conversation_id: &str,
187    block: bool,
188) -> Result<(), ApiError> {
189    let body = json!({ "block": block });
190    let result: serde_json::Value = client
191        .post(
192            &format!("/agents/dm/requests/{}/reject", conversation_id),
193            &body,
194        )
195        .await?;
196    if result["success"].as_bool().unwrap_or(false) {
197        if block {
198            display::success("Request rejected and blocked");
199        } else {
200            display::success("Request rejected");
201        }
202    }
203    Ok(())
204}