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    if result["success"].as_bool().unwrap_or(false) {
121        display::success("Message sent! 🦞");
122    }
123    Ok(())
124}
125
126/// Sends a new DM request to another agent.
127pub async fn send_request(
128    client: &MoltbookClient,
129    to: Option<String>,
130    message: Option<String>,
131    by_owner: bool,
132) -> Result<(), ApiError> {
133
134    let to = match to {
135        Some(t) => t,
136        None => Input::with_theme(&ColorfulTheme::default())
137            .with_prompt("To (Agent Name)")
138            .interact_text()
139            .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
140    };
141
142    let message = match message {
143        Some(m) => m,
144        None => Input::with_theme(&ColorfulTheme::default())
145            .with_prompt("Message")
146            .interact_text()
147            .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
148    };
149
150    let body = if by_owner {
151        json!({ "to_owner": to, "message": message })
152    } else {
153        json!({ "to": to, "message": message })
154    };
155    let result: serde_json::Value = client.post("/agents/dm/request", &body).await?;
156    if result["success"].as_bool().unwrap_or(false) {
157        display::success("DM request sent! 🦞");
158    }
159    Ok(())
160}
161
162pub async fn approve_request(
163    client: &MoltbookClient,
164    conversation_id: &str,
165) -> Result<(), ApiError> {
166    let result: serde_json::Value = client
167        .post(
168            &format!("/agents/dm/requests/{}/approve", conversation_id),
169            &json!({}),
170        )
171        .await?;
172    if result["success"].as_bool().unwrap_or(false) {
173        display::success("Request approved! 🦞");
174    }
175    Ok(())
176}
177
178pub async fn reject_request(
179    client: &MoltbookClient,
180    conversation_id: &str,
181    block: bool,
182) -> Result<(), ApiError> {
183    let body = json!({ "block": block });
184    let result: serde_json::Value = client
185        .post(
186            &format!("/agents/dm/requests/{}/reject", conversation_id),
187            &body,
188        )
189        .await?;
190    if result["success"].as_bool().unwrap_or(false) {
191        if block {
192            display::success("Request rejected and blocked");
193        } else {
194            display::success("Request rejected");
195        }
196    }
197    Ok(())
198}