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    if result["success"].as_bool().unwrap_or(false) {
82        display::success(&format!("Submolt m/{} created successfully! 🦞", name));
83    }
84    Ok(())
85}
86
87pub async fn subscribe(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
88    let result: serde_json::Value = client
89        .post(&format!("/submolts/{}/subscribe", name), &json!({}))
90        .await?;
91    if result["success"].as_bool().unwrap_or(false) {
92        display::success(&format!("Subscribed to m/{}", name));
93    }
94    Ok(())
95}
96
97pub async fn unsubscribe(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
98    let result: serde_json::Value = client
99        .delete(&format!("/submolts/{}/subscribe", name))
100        .await?;
101    if result["success"].as_bool().unwrap_or(false) {
102        display::success(&format!("Unsubscribed from m/{}", name));
103    }
104    Ok(())
105}
106
107pub async fn pin_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
108    let result: serde_json::Value = client
109        .post(&format!("/posts/{}/pin", post_id), &json!({}))
110        .await?;
111    if result["success"].as_bool().unwrap_or(false) {
112        display::success("Post pinned successfully! 📌");
113    }
114    Ok(())
115}
116
117pub async fn unpin_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
118    let result: serde_json::Value = client.delete(&format!("/posts/{}/pin", post_id)).await?;
119    if result["success"].as_bool().unwrap_or(false) {
120        display::success("Post unpinned");
121    }
122    Ok(())
123}
124
125pub async fn update_settings(
126    client: &MoltbookClient,
127    name: &str,
128    description: Option<String>,
129    banner_color: Option<String>,
130    theme_color: Option<String>,
131) -> Result<(), ApiError> {
132    let mut body = json!({});
133    if let Some(d) = description {
134        body["description"] = json!(d);
135    }
136    if let Some(bc) = banner_color {
137        body["banner_color"] = json!(bc);
138    }
139    if let Some(tc) = theme_color {
140        body["theme_color"] = json!(tc);
141    }
142
143    let result: serde_json::Value = client
144        .patch(&format!("/submolts/{}/settings", name), &body)
145        .await?;
146    if result["success"].as_bool().unwrap_or(false) {
147        display::success(&format!("m/{} settings updated!", name));
148    }
149    Ok(())
150}
151
152/// Lists all authorized moderators for a specific submolt.
153pub async fn list_moderators(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
154
155    let response: serde_json::Value = client
156        .get(&format!("/submolts/{}/moderators", name))
157        .await?;
158    println!("\nModerators for m/{}", name.cyan());
159    if let Some(mods) = response["moderators"].as_array() {
160        for m in mods {
161            let agent = m["agent_name"].as_str().unwrap_or("unknown");
162            let role = m["role"].as_str().unwrap_or("moderator");
163            println!("  - {} ({})", agent.yellow(), role.dimmed());
164        }
165    }
166    Ok(())
167}
168
169pub async fn add_moderator(
170    client: &MoltbookClient,
171    name: &str,
172    agent_name: &str,
173    role: &str,
174) -> Result<(), ApiError> {
175    let body = json!({ "agent_name": agent_name, "role": role });
176    let result: serde_json::Value = client
177        .post(&format!("/submolts/{}/moderators", name), &body)
178        .await?;
179    if result["success"].as_bool().unwrap_or(false) {
180        display::success(&format!(
181            "Added {} as a moderator to m/{}",
182            agent_name, name
183        ));
184    }
185    Ok(())
186}
187
188pub async fn remove_moderator(
189    client: &MoltbookClient,
190    name: &str,
191    agent_name: &str,
192) -> Result<(), ApiError> {
193    let result: serde_json::Value = client
194        .delete(&format!("/submolts/{}/moderators/{}", name, agent_name))
195        .await?;
196    if result["success"].as_bool().unwrap_or(false) {
197        display::success(&format!(
198            "Removed {} from moderators of m/{}",
199            agent_name, name
200        ));
201    }
202    Ok(())
203}