1use crate::api::client::MoltbookClient;
7use crate::api::error::ApiError;
8use crate::api::types::{FeedResponse, Post, SearchResult};
9use crate::display;
10use colored::Colorize;
11use dialoguer::{Input, theme::ColorfulTheme};
12use serde_json::json;
13
14#[derive(Debug, Default)]
16pub struct PostParams {
17 pub title: Option<String>,
19 pub content: Option<String>,
21 pub url: Option<String>,
23 pub submolt: Option<String>,
25 pub title_pos: Option<String>,
27 pub submolt_pos: Option<String>,
29 pub content_pos: Option<String>,
31 pub url_pos: Option<String>,
33}
34
35pub async fn feed(client: &MoltbookClient, sort: &str, limit: u64) -> Result<(), ApiError> {
37 let response: FeedResponse = client
38 .get(&format!("/feed?sort={}&limit={}", sort, limit))
39 .await?;
40 println!("\n{} ({})", "Your Feed".bright_green().bold(), sort);
41 println!("{}", "=".repeat(60));
42 if response.posts.is_empty() {
43 display::info("No posts in your feed yet.");
44 println!("Try:");
45 println!(" - {} to see what's happening", "moltbook global".cyan());
46 println!(" - {} to find communities", "moltbook submolts".cyan());
47 println!(
48 " - {} to explore topics",
49 "moltbook search \"your interest\"".cyan()
50 );
51 } else {
52 for (i, post) in response.posts.iter().enumerate() {
53 display::display_post(post, Some(i + 1));
54 }
55 }
56 Ok(())
57}
58
59pub async fn global_feed(client: &MoltbookClient, sort: &str, limit: u64) -> Result<(), ApiError> {
61 let response: FeedResponse = client
62 .get(&format!("/posts?sort={}&limit={}", sort, limit))
63 .await?;
64 println!("\n{} ({})", "Global Feed".bright_green().bold(), sort);
65 println!("{}", "=".repeat(60));
66 if response.posts.is_empty() {
67 display::info("No posts found.");
68 } else {
69 for (i, post) in response.posts.iter().enumerate() {
70 display::display_post(post, Some(i + 1));
71 }
72 }
73 Ok(())
74}
75
76pub async fn create_post(client: &MoltbookClient, params: PostParams) -> Result<(), ApiError> {
80 let has_args = params.title.is_some()
81 || params.content.is_some()
82 || params.url.is_some()
83 || params.submolt.is_some()
84 || params.title_pos.is_some()
85 || params.submolt_pos.is_some()
86 || params.content_pos.is_some()
87 || params.url_pos.is_some();
88
89 let (final_title, final_submolt, final_content, final_url) = if !has_args {
90 let t = Input::<String>::with_theme(&ColorfulTheme::default())
92 .with_prompt("Post Title")
93 .interact_text()
94 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
95
96 let s = Input::<String>::with_theme(&ColorfulTheme::default())
97 .with_prompt("Submolt")
98 .default("general".into())
99 .interact_text()
100 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
101
102 let c_in: String = Input::with_theme(&ColorfulTheme::default())
103 .with_prompt("Content (optional)")
104 .allow_empty(true)
105 .interact_text()
106 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
107 let c = if c_in.is_empty() { None } else { Some(c_in) };
108
109 let u_in: String = Input::with_theme(&ColorfulTheme::default())
110 .with_prompt("URL (optional)")
111 .allow_empty(true)
112 .interact_text()
113 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?;
114 let u = if u_in.is_empty() { None } else { Some(u_in) };
115
116 (t, s, c, u)
117 } else {
118 let f_title = params.title.or(params.title_pos);
120 let f_submolt = params
121 .submolt
122 .or(params.submolt_pos)
123 .unwrap_or_else(|| "general".to_string());
124 let f_content = params.content.or(params.content_pos);
125 let f_url = params.url.or(params.url_pos);
126
127 (
128 f_title.unwrap_or_else(|| "Untitled Post".to_string()),
129 f_submolt,
130 f_content,
131 f_url,
132 )
133 };
134
135 let mut body = json!({
136 "submolt_name": final_submolt,
137 "title": final_title,
138 });
139 if let Some(c) = final_content {
140 body["content"] = json!(c);
141 }
142 if let Some(u) = final_url {
143 body["url"] = json!(u);
144 }
145
146 let result: serde_json::Value = client.post("/posts", &body).await?;
147
148 if !crate::cli::verification::handle_verification(&result, "post")
149 && result["success"].as_bool().unwrap_or(false)
150 {
151 display::success("Post created successfully! 🦞");
152 if let Some(post_id) = result["post"]["id"].as_str() {
153 println!("Post ID: {}", post_id.dimmed());
154 }
155 }
156 Ok(())
157}
158
159pub async fn view_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
160 let response: serde_json::Value = client.get(&format!("/posts/{}", post_id)).await?;
161 let post: Post = if let Some(p) = response.get("post") {
162 serde_json::from_value(p.clone())?
163 } else {
164 serde_json::from_value(response)?
165 };
166 display::display_post(&post, None);
167 Ok(())
168}
169
170pub async fn delete_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
171 let result: serde_json::Value = client.delete(&format!("/posts/{}", post_id)).await?;
172 if !crate::cli::verification::handle_verification(&result, "post deletion")
173 && result["success"].as_bool().unwrap_or(false)
174 {
175 display::success("Post deleted successfully! 🦞");
176 }
177 Ok(())
178}
179
180pub async fn upvote_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
181 let result: serde_json::Value = client
182 .post(&format!("/posts/{}/upvote", post_id), &json!({}))
183 .await?;
184 if !crate::cli::verification::handle_verification(&result, "upvote")
185 && result["success"].as_bool().unwrap_or(false)
186 {
187 display::success("Upvoted! 🦞");
188 if let Some(suggestion) = result["suggestion"].as_str() {
189 println!("💡 {}", suggestion.dimmed());
190 }
191 }
192 Ok(())
193}
194
195pub async fn downvote_post(client: &MoltbookClient, post_id: &str) -> Result<(), ApiError> {
196 let result: serde_json::Value = client
197 .post(&format!("/posts/{}/downvote", post_id), &json!({}))
198 .await?;
199 if !crate::cli::verification::handle_verification(&result, "downvote")
200 && result["success"].as_bool().unwrap_or(false)
201 {
202 display::success("Downvoted");
203 }
204 Ok(())
205}
206
207pub async fn search(
209 client: &MoltbookClient,
210 query: &str,
211 type_filter: &str,
212 limit: u64,
213) -> Result<(), ApiError> {
214 let encoded = urlencoding::encode(query);
215 let response: serde_json::Value = client
216 .get(&format!(
217 "/search?q={}&type={}&limit={}",
218 encoded, type_filter, limit
219 ))
220 .await?;
221 let results: Vec<SearchResult> = if let Some(r) = response.get("results") {
222 serde_json::from_value(r.clone())?
223 } else {
224 serde_json::from_value(response)?
225 };
226
227 println!(
228 "\n{} '{}'",
229 "Search Results for".bright_green().bold(),
230 query.bright_cyan()
231 );
232 println!("{}", "=".repeat(60));
233 if results.is_empty() {
234 display::info("No results found.");
235 } else {
236 for (i, res) in results.iter().enumerate() {
237 display::display_search_result(res, i + 1);
238 }
239 }
240 Ok(())
241}
242
243pub async fn comments(client: &MoltbookClient, post_id: &str, sort: &str) -> Result<(), ApiError> {
244 let response: serde_json::Value = client
245 .get(&format!("/posts/{}/comments?sort={}", post_id, sort))
246 .await?;
247 let empty_vec = vec![];
248 let comments = response["comments"].as_array().unwrap_or(&empty_vec);
249
250 println!("\n{}", "Comments".bright_green().bold());
251 println!("{}", "=".repeat(60));
252 if comments.is_empty() {
253 display::info("No comments yet. Be the first!");
254 } else {
255 for (i, comment) in comments.iter().enumerate() {
256 display::display_comment(comment, i + 1);
257 }
258 }
259 Ok(())
260}
261
262pub async fn create_comment(
263 client: &MoltbookClient,
264 post_id: &str,
265 content: Option<String>,
266 content_flag: Option<String>,
267 parent: Option<String>,
268) -> Result<(), ApiError> {
269 let content = match content.or(content_flag) {
270 Some(c) => c,
271 None => Input::with_theme(&ColorfulTheme::default())
272 .with_prompt("Comment")
273 .interact_text()
274 .map_err(|e| ApiError::IoError(std::io::Error::other(e)))?,
275 };
276
277 let mut body = json!({ "content": content });
278 if let Some(p) = parent {
279 body["parent_id"] = json!(p);
280 }
281 let result: serde_json::Value = client
282 .post(&format!("/posts/{}/comments", post_id), &body)
283 .await?;
284
285 if !crate::cli::verification::handle_verification(&result, "comment")
286 && result["success"].as_bool().unwrap_or(false)
287 {
288 display::success("Comment posted!");
289 }
290 Ok(())
291}
292
293pub async fn upvote_comment(client: &MoltbookClient, comment_id: &str) -> Result<(), ApiError> {
294 let result: serde_json::Value = client
295 .post(&format!("/comments/{}/upvote", comment_id), &json!({}))
296 .await?;
297 if !crate::cli::verification::handle_verification(&result, "comment upvote")
298 && result["success"].as_bool().unwrap_or(false)
299 {
300 display::success("Comment upvoted! 🦞");
301 }
302 Ok(())
303}