1use std::collections::HashMap;
2
3use crate::client::Client;
4use crate::constants;
5use crate::error;
6use crate::http::RequestBody;
7use crate::types::{
8 ImagePostContent, PendingRepliesOptions, Post, PostId, RepliesOptions, RepliesResponse,
9 TextPostContent, VideoPostContent,
10};
11use crate::validation;
12
13impl Client {
14 pub async fn get_replies(
16 &self,
17 post_id: &PostId,
18 opts: Option<&RepliesOptions>,
19 ) -> crate::Result<RepliesResponse> {
20 if !post_id.is_valid() {
21 return Err(error::new_validation_error(
22 0,
23 constants::ERR_EMPTY_POST_ID,
24 "",
25 "post_id",
26 ));
27 }
28
29 if let Some(opts) = opts {
30 validation::validate_replies_options(opts)?;
31 }
32
33 let token = self.access_token().await;
34 let mut params = HashMap::new();
35 params.insert("fields".into(), constants::REPLY_FIELDS.into());
36
37 if let Some(opts) = opts {
38 if let Some(limit) = opts.limit {
39 params.insert("limit".into(), limit.to_string());
40 }
41 if let Some(ref before) = opts.before {
42 params.insert("before".into(), before.clone());
43 }
44 if let Some(ref after) = opts.after {
45 params.insert("after".into(), after.clone());
46 }
47 if let Some(reverse) = opts.reverse {
48 params.insert("reverse".into(), reverse.to_string());
49 }
50 }
51
52 let path = format!("/{}/replies", post_id);
53 let resp = self.http_client.get(&path, params, &token).await?;
54 resp.json()
55 }
56
57 pub async fn get_conversation(
59 &self,
60 post_id: &PostId,
61 opts: Option<&RepliesOptions>,
62 ) -> crate::Result<RepliesResponse> {
63 if !post_id.is_valid() {
64 return Err(error::new_validation_error(
65 0,
66 constants::ERR_EMPTY_POST_ID,
67 "",
68 "post_id",
69 ));
70 }
71
72 if let Some(opts) = opts {
73 validation::validate_replies_options(opts)?;
74 }
75
76 let token = self.access_token().await;
77 let mut params = HashMap::new();
78 params.insert("fields".into(), constants::REPLY_FIELDS.into());
79
80 if let Some(opts) = opts {
81 if let Some(limit) = opts.limit {
82 params.insert("limit".into(), limit.to_string());
83 }
84 if let Some(ref before) = opts.before {
85 params.insert("before".into(), before.clone());
86 }
87 if let Some(ref after) = opts.after {
88 params.insert("after".into(), after.clone());
89 }
90 if let Some(reverse) = opts.reverse {
91 params.insert("reverse".into(), reverse.to_string());
92 }
93 }
94
95 let path = format!("/{}/conversation", post_id);
96 let resp = self.http_client.get(&path, params, &token).await?;
97 resp.json()
98 }
99
100 pub async fn get_pending_replies(
102 &self,
103 post_id: &PostId,
104 opts: Option<&PendingRepliesOptions>,
105 ) -> crate::Result<RepliesResponse> {
106 if !post_id.is_valid() {
107 return Err(error::new_validation_error(
108 0,
109 constants::ERR_EMPTY_POST_ID,
110 "",
111 "post_id",
112 ));
113 }
114
115 if let Some(opts) = opts {
116 validation::validate_pending_replies_options(opts)?;
117 }
118
119 let token = self.access_token().await;
120 let mut params = HashMap::new();
121 params.insert("fields".into(), constants::PENDING_REPLY_FIELDS.into());
122
123 if let Some(opts) = opts {
124 if let Some(limit) = opts.limit {
125 params.insert("limit".into(), limit.to_string());
126 }
127 if let Some(ref before) = opts.before {
128 params.insert("before".into(), before.clone());
129 }
130 if let Some(ref after) = opts.after {
131 params.insert("after".into(), after.clone());
132 }
133 if let Some(reverse) = opts.reverse {
134 params.insert("reverse".into(), reverse.to_string());
135 }
136 if let Some(ref status) = opts.approval_status {
137 params.insert(
138 "approval_status".into(),
139 serde_json::to_string(status)
140 .unwrap_or_default()
141 .trim_matches('"')
142 .to_owned(),
143 );
144 }
145 }
146
147 let path = format!("/{}/pending_replies", post_id);
148 let resp = self.http_client.get(&path, params, &token).await?;
149 resp.json()
150 }
151
152 pub async fn approve_pending_reply(&self, reply_id: &PostId) -> crate::Result<()> {
154 if !reply_id.is_valid() {
155 return Err(error::new_validation_error(
156 0,
157 constants::ERR_EMPTY_POST_ID,
158 "",
159 "reply_id",
160 ));
161 }
162
163 let token = self.access_token().await;
164 let mut params = HashMap::new();
165 params.insert("approve".into(), "true".into());
166
167 let path = format!("/{}/manage_pending_reply", reply_id);
168 let body = RequestBody::Form(params);
169 self.http_client.post(&path, Some(body), &token).await?;
170 Ok(())
171 }
172
173 pub async fn ignore_pending_reply(&self, reply_id: &PostId) -> crate::Result<()> {
175 if !reply_id.is_valid() {
176 return Err(error::new_validation_error(
177 0,
178 constants::ERR_EMPTY_POST_ID,
179 "",
180 "reply_id",
181 ));
182 }
183
184 let token = self.access_token().await;
185 let mut params = HashMap::new();
186 params.insert("approve".into(), "false".into());
187
188 let path = format!("/{}/manage_pending_reply", reply_id);
189 let body = RequestBody::Form(params);
190 self.http_client.post(&path, Some(body), &token).await?;
191 Ok(())
192 }
193
194 pub async fn hide_reply(&self, reply_id: &PostId) -> crate::Result<()> {
196 if !reply_id.is_valid() {
197 return Err(error::new_validation_error(
198 0,
199 constants::ERR_EMPTY_POST_ID,
200 "",
201 "reply_id",
202 ));
203 }
204
205 let token = self.access_token().await;
206 let mut params = HashMap::new();
207 params.insert("hide".into(), "true".into());
208
209 let path = format!("/{}/manage_reply", reply_id);
210 let body = RequestBody::Form(params);
211 self.http_client.post(&path, Some(body), &token).await?;
212 Ok(())
213 }
214
215 pub async fn reply_to_post(&self, post_id: &PostId, text: &str) -> crate::Result<Post> {
220 if !post_id.is_valid() {
221 return Err(error::new_validation_error(
222 0,
223 constants::ERR_EMPTY_POST_ID,
224 "",
225 "post_id",
226 ));
227 }
228
229 let content = TextPostContent {
230 text: text.to_owned(),
231 reply_to_id: Some(post_id.clone()),
232 link_attachment: None,
233 poll_attachment: None,
234 reply_control: None,
235 topic_tag: None,
236 allowlisted_country_codes: None,
237 location_id: None,
238 auto_publish_text: false,
239 quoted_post_id: None,
240 text_entities: None,
241 text_attachment: None,
242 gif_attachment: None,
243 is_ghost_post: false,
244 enable_reply_approvals: false,
245 };
246 self.create_text_post(&content).await
247 }
248
249 pub async fn create_reply(
255 &self,
256 content: &TextPostContent,
257 apply_reply_delay: bool,
258 ) -> crate::Result<Post> {
259 if content.reply_to_id.is_none() {
260 return Err(error::new_validation_error(
261 0,
262 "reply_to_id is required for create_reply",
263 "",
264 "reply_to_id",
265 ));
266 }
267
268 if apply_reply_delay {
269 tokio::time::sleep(constants::REPLY_PUBLISH_DELAY).await;
270 }
271 self.create_text_post(content).await
272 }
273
274 pub async fn create_image_reply(&self, content: &ImagePostContent) -> crate::Result<Post> {
278 if content.reply_to_id.is_none() {
279 return Err(error::new_validation_error(
280 0,
281 "reply_to_id is required for create_image_reply",
282 "",
283 "reply_to_id",
284 ));
285 }
286
287 self.create_image_post(content).await
288 }
289
290 pub async fn create_video_reply(&self, content: &VideoPostContent) -> crate::Result<Post> {
294 if content.reply_to_id.is_none() {
295 return Err(error::new_validation_error(
296 0,
297 "reply_to_id is required for create_video_reply",
298 "",
299 "reply_to_id",
300 ));
301 }
302
303 self.create_video_post(content).await
304 }
305
306 pub async fn unhide_reply(&self, reply_id: &PostId) -> crate::Result<()> {
308 if !reply_id.is_valid() {
309 return Err(error::new_validation_error(
310 0,
311 constants::ERR_EMPTY_POST_ID,
312 "",
313 "reply_id",
314 ));
315 }
316
317 let token = self.access_token().await;
318 let mut params = HashMap::new();
319 params.insert("hide".into(), "false".into());
320
321 let path = format!("/{}/manage_reply", reply_id);
322 let body = RequestBody::Form(params);
323 self.http_client.post(&path, Some(body), &token).await?;
324 Ok(())
325 }
326}