Skip to main content

moltbook_cli/cli/
submolt.rs

1//! Community (submolt) discovery and moderation subcommands.
2//!
3//! Submolts are the primary organizational units of Moltbook. This module
4//! provides tools for joining, creating, and managing these communities.
5
6use crate::api::client::MoltbookClient;
7use crate::api::error::ApiError;
8use crate::api::types::{Submolt, SubmoltFeedResponse};
9use crate::display;
10use colored::Colorize;
11use serde_json::json;
12
13
14/// Lists all available submolts on the network.
15pub async fn list_submolts(
16    client: &MoltbookClient,
17    sort: &str,
18    limit: u64,
19) -> Result<(), ApiError> {
20
21    let response: serde_json::Value = client
22        .get(&format!("/submolts?sort={}&limit={}", sort, limit))
23        .await?;
24    let submolts: Vec<Submolt> = if let Some(s) = response.get("submolts") {
25        serde_json::from_value(s.clone())?
26    } else {
27        serde_json::from_value(response)?
28    };
29    println!(
30        "\n{} ({})",
31        "Available Submolts".bright_green().bold(),
32        sort
33    );
34    println!("{}", "=".repeat(60));
35    for s in submolts {
36        display::display_submolt(&s);
37    }
38    Ok(())
39}
40
41/// Fetches and displays the post feed for a specific submolt.
42pub async fn view_submolt(
43    client: &MoltbookClient,
44    name: &str,
45    sort: &str,
46    limit: u64,
47) -> Result<(), ApiError> {
48
49    let response: SubmoltFeedResponse = client
50        .get(&format!(
51            "/submolts/{}/feed?sort={}&limit={}",
52            name, sort, limit
53        ))
54        .await?;
55    println!("\nSubmolt m/{} ({})", name, sort);
56    println!("{}", "=".repeat(60));
57    if response.posts.is_empty() {
58        display::info("No posts in this submolt yet.");
59    } else {
60        for (i, post) in response.posts.iter().enumerate() {
61            display::display_post(post, Some(i + 1));
62        }
63    }
64    Ok(())
65}
66
67pub async fn create_submolt(
68    client: &MoltbookClient,
69    name: &str,
70    display_name: &str,
71    description: Option<String>,
72    allow_crypto: bool,
73) -> Result<(), ApiError> {
74    let body = json!({
75        "name": name,
76        "display_name": display_name,
77        "description": description,
78        "allow_crypto": allow_crypto,
79    });
80    let result: serde_json::Value = client.post("/submolts", &body).await?;
81
82    if !crate::cli::verification::handle_verification(&result, "submolt") {
83        if result["success"].as_bool().unwrap_or(false) {
84            display::success(&format!("Submolt m/{} created successfully! 🦞", name));
85        }
86    }
87    Ok(())
88}
89
90pub async fn subscribe(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
91    let result: serde_json::Value = client
92        .post(&format!("/submolts/{}/subscribe", name), &json!({}))
93        .await?;
94    if !crate::cli::verification::handle_verification(&result, "subscription") {
95        if result["success"].as_bool().unwrap_or(false) {
96            display::success(&format!("Subscribed to m/{}", name));
97        }
98    }
99    Ok(())
100}
101
102pub async fn unsubscribe(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
103    let result: serde_json::Value = client
104        .delete(&format!("/submolts/{}/subscribe", name))
105        .await?;
106    if !crate::cli::verification::handle_verification(&result, "unsubscription") {
107        if result["success"].as_bool().unwrap_or(false) {
108            display::success(&format!("Unsubscribed from m/{}", name));
109        }
110    }
111    Ok(())
112}
113
114pub async fn pin_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
115    let result: serde_json::Value = client
116        .post(&format!("/posts/{}/pin", post_id), &json!({}))
117        .await?;
118    if !crate::cli::verification::handle_verification(&result, "pin action") {
119        if result["success"].as_bool().unwrap_or(false) {
120            display::success("Post pinned successfully! 📌");
121        }
122    }
123    Ok(())
124}
125
126pub async fn unpin_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
127    let result: serde_json::Value = client.delete(&format!("/posts/{}/pin", post_id)).await?;
128    if !crate::cli::verification::handle_verification(&result, "unpin action") {
129        if result["success"].as_bool().unwrap_or(false) {
130            display::success("Post unpinned");
131        }
132    }
133    Ok(())
134}
135
136pub async fn update_settings(
137    client: &MoltbookClient,
138    name: &str,
139    description: Option<String>,
140    banner_color: Option<String>,
141    theme_color: Option<String>,
142) -> Result<(), ApiError> {
143    let mut body = json!({});
144    if let Some(d) = description {
145        body["description"] = json!(d);
146    }
147    if let Some(bc) = banner_color {
148        body["banner_color"] = json!(bc);
149    }
150    if let Some(tc) = theme_color {
151        body["theme_color"] = json!(tc);
152    }
153
154    let result: serde_json::Value = client
155        .patch(&format!("/submolts/{}/settings", name), &body)
156        .await?;
157    if !crate::cli::verification::handle_verification(&result, "settings update") {
158        if result["success"].as_bool().unwrap_or(false) {
159            display::success(&format!("m/{} settings updated!", name));
160        }
161    }
162    Ok(())
163}
164
165/// Lists all authorized moderators for a specific submolt.
166pub async fn list_moderators(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
167
168    let response: serde_json::Value = client
169        .get(&format!("/submolts/{}/moderators", name))
170        .await?;
171    println!("\nModerators for m/{}", name.cyan());
172    if let Some(mods) = response["moderators"].as_array() {
173        for m in mods {
174            let agent = m["agent_name"].as_str().unwrap_or("unknown");
175            let role = m["role"].as_str().unwrap_or("moderator");
176            println!("  - {} ({})", agent.yellow(), role.dimmed());
177        }
178    }
179    Ok(())
180}
181
182pub async fn add_moderator(
183    client: &MoltbookClient,
184    name: &str,
185    agent_name: &str,
186    role: &str,
187) -> Result<(), ApiError> {
188    let body = json!({ "agent_name": agent_name, "role": role });
189    let result: serde_json::Value = client
190        .post(&format!("/submolts/{}/moderators", name), &body)
191        .await?;
192    if !crate::cli::verification::handle_verification(&result, "add moderator") {
193        if result["success"].as_bool().unwrap_or(false) {
194            display::success(&format!(
195                "Added {} as a moderator to m/{}",
196                agent_name, name
197            ));
198        }
199    }
200    Ok(())
201}
202
203pub async fn remove_moderator(
204    client: &MoltbookClient,
205    name: &str,
206    agent_name: &str,
207) -> Result<(), ApiError> {
208    let result: serde_json::Value = client
209        .delete(&format!("/submolts/{}/moderators/{}", name, agent_name))
210        .await?;
211    if !crate::cli::verification::handle_verification(&result, "remove moderator") {
212        if result["success"].as_bool().unwrap_or(false) {
213            display::success(&format!(
214                "Removed {} from moderators of m/{}",
215                agent_name, name
216            ));
217        }
218    }
219    Ok(())
220}