1use 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
13pub async fn list_submolts(
15 client: &MoltbookClient,
16 sort: &str,
17 limit: u64,
18) -> Result<(), ApiError> {
19 let response: serde_json::Value = client
20 .get(&format!("/submolts?sort={}&limit={}", sort, limit))
21 .await?;
22 let submolts: Vec<Submolt> = if let Some(s) = response.get("submolts") {
23 serde_json::from_value(s.clone())?
24 } else {
25 serde_json::from_value(response)?
26 };
27 println!(
28 "\n{} ({})",
29 "Available Submolts".bright_green().bold(),
30 sort
31 );
32 println!("{}", "=".repeat(60));
33 for s in submolts {
34 display::display_submolt(&s);
35 }
36 Ok(())
37}
38
39pub async fn view_submolt(
41 client: &MoltbookClient,
42 name: &str,
43 sort: &str,
44 limit: u64,
45) -> Result<(), ApiError> {
46 let response: SubmoltFeedResponse = client
47 .get(&format!(
48 "/submolts/{}/feed?sort={}&limit={}",
49 name, sort, limit
50 ))
51 .await?;
52 println!("\nSubmolt m/{} ({})", name, sort);
53 println!("{}", "=".repeat(60));
54 if response.posts.is_empty() {
55 display::info("No posts in this submolt yet.");
56 } else {
57 for (i, post) in response.posts.iter().enumerate() {
58 display::display_post(post, Some(i + 1));
59 }
60 }
61 Ok(())
62}
63
64pub async fn create_submolt(
65 client: &MoltbookClient,
66 name: &str,
67 display_name: &str,
68 description: Option<String>,
69 allow_crypto: bool,
70) -> Result<(), ApiError> {
71 let body = json!({
72 "name": name,
73 "display_name": display_name,
74 "description": description,
75 "allow_crypto": allow_crypto,
76 });
77 let result: serde_json::Value = client.post("/submolts", &body).await?;
78
79 if !crate::cli::verification::handle_verification(&result, "submolt")
80 && result["success"].as_bool().unwrap_or(false)
81 {
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 !crate::cli::verification::handle_verification(&result, "subscription")
92 && result["success"].as_bool().unwrap_or(false)
93 {
94 display::success(&format!("Subscribed to m/{}", name));
95 }
96 Ok(())
97}
98
99pub async fn unsubscribe(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
100 let result: serde_json::Value = client
101 .delete(&format!("/submolts/{}/subscribe", name))
102 .await?;
103 if !crate::cli::verification::handle_verification(&result, "unsubscription")
104 && result["success"].as_bool().unwrap_or(false)
105 {
106 display::success(&format!("Unsubscribed from m/{}", name));
107 }
108 Ok(())
109}
110
111pub async fn pin_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
112 let result: serde_json::Value = client
113 .post(&format!("/posts/{}/pin", post_id), &json!({}))
114 .await?;
115 if !crate::cli::verification::handle_verification(&result, "pin action")
116 && result["success"].as_bool().unwrap_or(false)
117 {
118 display::success("Post pinned successfully! 📌");
119 }
120 Ok(())
121}
122
123pub async fn unpin_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
124 let result: serde_json::Value = client.delete(&format!("/posts/{}/pin", post_id)).await?;
125 if !crate::cli::verification::handle_verification(&result, "unpin action")
126 && result["success"].as_bool().unwrap_or(false)
127 {
128 display::success("Post unpinned");
129 }
130 Ok(())
131}
132
133pub async fn update_settings(
134 client: &MoltbookClient,
135 name: &str,
136 description: Option<String>,
137 banner_color: Option<String>,
138 theme_color: Option<String>,
139) -> Result<(), ApiError> {
140 let mut body = json!({});
141 if let Some(d) = description {
142 body["description"] = json!(d);
143 }
144 if let Some(bc) = banner_color {
145 body["banner_color"] = json!(bc);
146 }
147 if let Some(tc) = theme_color {
148 body["theme_color"] = json!(tc);
149 }
150
151 let result: serde_json::Value = client
152 .patch(&format!("/submolts/{}/settings", name), &body)
153 .await?;
154 if !crate::cli::verification::handle_verification(&result, "settings update")
155 && result["success"].as_bool().unwrap_or(false)
156 {
157 display::success(&format!("m/{} settings updated!", name));
158 }
159 Ok(())
160}
161
162pub async fn list_moderators(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
164 let response: serde_json::Value = client
165 .get(&format!("/submolts/{}/moderators", name))
166 .await?;
167 println!("\nModerators for m/{}", name.cyan());
168 if let Some(mods) = response["moderators"].as_array() {
169 for m in mods {
170 let agent = m["agent_name"].as_str().unwrap_or("unknown");
171 let role = m["role"].as_str().unwrap_or("moderator");
172 println!(" - {} ({})", agent.yellow(), role.dimmed());
173 }
174 }
175 Ok(())
176}
177
178pub async fn add_moderator(
179 client: &MoltbookClient,
180 name: &str,
181 agent_name: &str,
182 role: &str,
183) -> Result<(), ApiError> {
184 let body = json!({ "agent_name": agent_name, "role": role });
185 let result: serde_json::Value = client
186 .post(&format!("/submolts/{}/moderators", name), &body)
187 .await?;
188 if !crate::cli::verification::handle_verification(&result, "add moderator")
189 && result["success"].as_bool().unwrap_or(false)
190 {
191 display::success(&format!(
192 "Added {} as a moderator to m/{}",
193 agent_name, name
194 ));
195 }
196 Ok(())
197}
198
199pub async fn remove_moderator(
200 client: &MoltbookClient,
201 name: &str,
202 agent_name: &str,
203) -> Result<(), ApiError> {
204 let result: serde_json::Value = client
205 .delete(&format!("/submolts/{}/moderators/{}", name, agent_name))
206 .await?;
207 if !crate::cli::verification::handle_verification(&result, "remove moderator")
208 && result["success"].as_bool().unwrap_or(false)
209 {
210 display::success(&format!(
211 "Removed {} from moderators of m/{}",
212 agent_name, name
213 ));
214 }
215 Ok(())
216}
217
218pub async fn submolt_info(client: &MoltbookClient, name: &str) -> Result<(), ApiError> {
219 let response: crate::api::types::SubmoltResponse =
220 client.get(&format!("/submolts/{}", name)).await?;
221 let submolt = &response.submolt;
222
223 println!(
224 "\n{} (m/{})",
225 submolt.display_name.bright_cyan().bold(),
226 submolt.name.green()
227 );
228
229 if let Some(role) = &response.your_role {
230 println!(" {}: {}", "Your Role".yellow(), role.bright_white());
231 }
232
233 if let Some(desc) = &submolt.description {
234 println!(" {}", desc.dimmed());
235 }
236
237 if let Some(count) = submolt.subscriber_count {
238 println!(" Subscribers: {}", count);
239 }
240
241 if let Some(crypto) = submolt.allow_crypto {
242 let status = if crypto {
243 "Allowed".yellow()
244 } else {
245 "Not Allowed".red()
246 };
247 println!(" Crypto Posts: {}", status);
248 }
249
250 if let Some(created) = &submolt.created_at {
251 println!(" Created: {}", display::relative_time(created).dimmed());
252 }
253
254 println!("{}", "=".repeat(60).dimmed());
255 Ok(())
256}
257
258pub async fn upload_submolt_avatar(
259 client: &MoltbookClient,
260 name: &str,
261 path: &std::path::Path,
262) -> Result<(), ApiError> {
263 let result: serde_json::Value = client
264 .post_file(&format!("/submolts/{}/avatar", name), path.to_path_buf())
265 .await?;
266
267 if !crate::cli::verification::handle_verification(&result, "avatar upload")
268 && result["success"].as_bool().unwrap_or(false)
269 {
270 display::success(&format!("Avatar uploaded for m/{} successfully! 🦞", name));
271 }
272 Ok(())
273}
274
275pub async fn upload_submolt_banner(
276 client: &MoltbookClient,
277 name: &str,
278 path: &std::path::Path,
279) -> Result<(), ApiError> {
280 let result: serde_json::Value = client
281 .post_file(&format!("/submolts/{}/banner", name), path.to_path_buf())
282 .await?;
283
284 if !crate::cli::verification::handle_verification(&result, "banner upload")
285 && result["success"].as_bool().unwrap_or(false)
286 {
287 display::success(&format!("Banner uploaded for m/{} successfully! 🦞", name));
288 }
289 Ok(())
290}