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