Skip to main content

threads_rs/api/
posts_read.rs

1use std::collections::HashMap;
2
3use crate::client::Client;
4use crate::constants;
5use crate::error;
6use crate::types::{Post, PostId, PostsOptions, PostsResponse, PublishingLimits, UserId};
7use crate::validation;
8
9impl Client {
10    /// Get a single post by ID with extended fields.
11    pub async fn get_post(&self, post_id: &PostId) -> crate::Result<Post> {
12        if !post_id.is_valid() {
13            return Err(error::new_validation_error(
14                0,
15                constants::ERR_EMPTY_POST_ID,
16                "",
17                "post_id",
18            ));
19        }
20
21        let token = self.access_token().await;
22        let mut params = HashMap::new();
23        params.insert("fields".into(), constants::POST_EXTENDED_FIELDS.into());
24
25        let path = format!("/{}", post_id);
26        let resp = self.http_client.get(&path, params, &token).await?;
27        resp.json()
28    }
29
30    /// Get posts created by a user.
31    pub async fn get_user_posts(
32        &self,
33        user_id: &UserId,
34        opts: Option<&PostsOptions>,
35    ) -> crate::Result<PostsResponse> {
36        if !user_id.is_valid() {
37            return Err(error::new_validation_error(
38                0,
39                constants::ERR_EMPTY_USER_ID,
40                "",
41                "user_id",
42            ));
43        }
44
45        if let Some(opts) = opts {
46            validation::validate_posts_options(opts)?;
47        }
48
49        let token = self.access_token().await;
50        let mut params = HashMap::new();
51        params.insert("fields".into(), constants::POST_EXTENDED_FIELDS.into());
52
53        if let Some(opts) = opts {
54            if let Some(limit) = opts.limit {
55                params.insert("limit".into(), limit.to_string());
56            }
57            if let Some(ref before) = opts.before {
58                params.insert("before".into(), before.clone());
59            }
60            if let Some(ref after) = opts.after {
61                params.insert("after".into(), after.clone());
62            }
63            if let Some(since) = opts.since {
64                params.insert("since".into(), since.to_string());
65            }
66            if let Some(until) = opts.until {
67                params.insert("until".into(), until.to_string());
68            }
69        }
70
71        let path = format!("/{}/threads", user_id);
72        let resp = self.http_client.get(&path, params, &token).await?;
73        resp.json()
74    }
75
76    /// Get posts where the user is mentioned.
77    ///
78    /// Supports `since` and `until` for time-range filtering in addition to
79    /// cursor-based pagination.
80    pub async fn get_user_mentions(
81        &self,
82        user_id: &UserId,
83        opts: Option<&PostsOptions>,
84    ) -> crate::Result<PostsResponse> {
85        if !user_id.is_valid() {
86            return Err(error::new_validation_error(
87                0,
88                constants::ERR_EMPTY_USER_ID,
89                "",
90                "user_id",
91            ));
92        }
93
94        if let Some(opts) = opts {
95            validation::validate_posts_options(opts)?;
96        }
97
98        let token = self.access_token().await;
99        let mut params = HashMap::new();
100        params.insert("fields".into(), constants::POST_EXTENDED_FIELDS.into());
101
102        if let Some(opts) = opts {
103            if let Some(limit) = opts.limit {
104                params.insert("limit".into(), limit.to_string());
105            }
106            if let Some(ref before) = opts.before {
107                params.insert("before".into(), before.clone());
108            }
109            if let Some(ref after) = opts.after {
110                params.insert("after".into(), after.clone());
111            }
112            if let Some(since) = opts.since {
113                params.insert("since".into(), since.to_string());
114            }
115            if let Some(until) = opts.until {
116                params.insert("until".into(), until.to_string());
117            }
118        }
119
120        let path = format!("/{}/mentions", user_id);
121        let resp = self.http_client.get(&path, params, &token).await?;
122        resp.json()
123    }
124
125    /// Get ghost posts for a user.
126    ///
127    /// Supports `since` and `until` for time-range filtering in addition to
128    /// cursor-based pagination.
129    pub async fn get_user_ghost_posts(
130        &self,
131        user_id: &UserId,
132        opts: Option<&PostsOptions>,
133    ) -> crate::Result<PostsResponse> {
134        if !user_id.is_valid() {
135            return Err(error::new_validation_error(
136                0,
137                constants::ERR_EMPTY_USER_ID,
138                "",
139                "user_id",
140            ));
141        }
142
143        if let Some(opts) = opts {
144            validation::validate_posts_options(opts)?;
145        }
146
147        let token = self.access_token().await;
148        let mut params = HashMap::new();
149        params.insert("fields".into(), constants::GHOST_POST_FIELDS.into());
150
151        if let Some(opts) = opts {
152            if let Some(limit) = opts.limit {
153                params.insert("limit".into(), limit.to_string());
154            }
155            if let Some(ref before) = opts.before {
156                params.insert("before".into(), before.clone());
157            }
158            if let Some(ref after) = opts.after {
159                params.insert("after".into(), after.clone());
160            }
161            if let Some(since) = opts.since {
162                params.insert("since".into(), since.to_string());
163            }
164            if let Some(until) = opts.until {
165                params.insert("until".into(), until.to_string());
166            }
167        }
168
169        let path = format!("/{}/ghost_posts", user_id);
170        let resp = self.http_client.get(&path, params, &token).await?;
171        resp.json()
172    }
173
174    /// Get the publishing rate limits for the authenticated user.
175    pub async fn get_publishing_limits(&self) -> crate::Result<PublishingLimits> {
176        let user_id = self.user_id().await;
177        if user_id.is_empty() {
178            return Err(error::new_authentication_error(
179                401,
180                constants::ERR_EMPTY_USER_ID,
181                "No user ID available from token",
182            ));
183        }
184
185        let token = self.access_token().await;
186        let mut params = HashMap::new();
187        params.insert("fields".into(), constants::PUBLISHING_LIMIT_FIELDS.into());
188
189        let path = format!("/{}/threads_publishing_limit", user_id);
190        let resp = self.http_client.get(&path, params, &token).await?;
191
192        // API wraps publishing limits in {"data": [PublishingLimits]}
193        #[derive(serde::Deserialize)]
194        struct Wrapper {
195            data: Vec<PublishingLimits>,
196        }
197
198        let wrapper: Wrapper = resp.json()?;
199        wrapper
200            .data
201            .into_iter()
202            .next()
203            .ok_or_else(|| error::new_api_error(0, "No publishing limits data returned", "", ""))
204    }
205}