1use std::collections::HashMap;
2
3use crate::client::Client;
4use crate::constants;
5use crate::error;
6use crate::types::{AccountInsightsOptions, InsightsResponse, PostId, PostInsightsOptions, UserId};
7
8pub fn get_available_post_insight_metrics() -> Vec<&'static str> {
10 vec!["views", "likes", "replies", "reposts", "quotes", "shares"]
11}
12
13pub fn get_available_account_insight_metrics() -> Vec<&'static str> {
15 vec![
16 "views",
17 "likes",
18 "replies",
19 "reposts",
20 "quotes",
21 "clicks",
22 "followers_count",
23 "follower_demographics",
24 ]
25}
26
27pub fn get_available_insight_periods() -> Vec<&'static str> {
29 vec!["day", "lifetime"]
30}
31
32pub fn get_available_follower_demographics_breakdowns() -> Vec<&'static str> {
34 vec!["country", "city", "age", "gender"]
35}
36
37impl Client {
38 pub async fn get_post_insights(
40 &self,
41 post_id: &PostId,
42 metrics: &[&str],
43 ) -> crate::Result<InsightsResponse> {
44 if !post_id.is_valid() {
45 return Err(error::new_validation_error(
46 0,
47 constants::ERR_EMPTY_POST_ID,
48 "",
49 "post_id",
50 ));
51 }
52
53 let token = self.access_token().await;
54 let mut params = HashMap::new();
55
56 let metric_str = if metrics.is_empty() {
57 "views,likes,replies,reposts,quotes".to_owned()
58 } else {
59 metrics.join(",")
60 };
61 params.insert("metric".into(), metric_str);
62
63 let path = format!("/{}/insights", post_id);
64 let resp = self.http_client.get(&path, params, &token).await?;
65 resp.json()
66 }
67
68 pub async fn get_account_insights(
70 &self,
71 user_id: &UserId,
72 metrics: &[&str],
73 period: &str,
74 ) -> crate::Result<InsightsResponse> {
75 if !user_id.is_valid() {
76 return Err(error::new_validation_error(
77 0,
78 constants::ERR_EMPTY_USER_ID,
79 "",
80 "user_id",
81 ));
82 }
83
84 let token = self.access_token().await;
85 let mut params = HashMap::new();
86
87 let metric_str = if metrics.is_empty() {
88 "views,likes,replies,reposts,quotes,followers_count".to_owned()
89 } else {
90 metrics.join(",")
91 };
92 params.insert("metric".into(), metric_str);
93
94 let period_str = if period.is_empty() {
95 "lifetime".to_owned()
96 } else {
97 period.to_owned()
98 };
99 params.insert("period".into(), period_str);
100
101 let path = format!("/{}/threads_insights", user_id);
102 let resp = self.http_client.get(&path, params, &token).await?;
103 resp.json()
104 }
105
106 pub async fn get_post_insights_with_options(
108 &self,
109 post_id: &PostId,
110 opts: &PostInsightsOptions,
111 ) -> crate::Result<InsightsResponse> {
112 if !post_id.is_valid() {
113 return Err(error::new_validation_error(
114 0,
115 constants::ERR_EMPTY_POST_ID,
116 "",
117 "post_id",
118 ));
119 }
120
121 let token = self.access_token().await;
122 let mut params = HashMap::new();
123
124 let metric_str = match &opts.metrics {
125 Some(metrics) if !metrics.is_empty() => metrics
126 .iter()
127 .map(|m| {
128 serde_json::to_string(m)
129 .unwrap_or_default()
130 .trim_matches('"')
131 .to_owned()
132 })
133 .collect::<Vec<_>>()
134 .join(","),
135 _ => "views,likes,replies,reposts,quotes".to_owned(),
136 };
137 params.insert("metric".into(), metric_str);
138
139 if let Some(since) = opts.since {
143 params.insert("since".into(), since.timestamp().to_string());
144 }
145 if let Some(until) = opts.until {
146 params.insert("until".into(), until.timestamp().to_string());
147 }
148
149 let path = format!("/{}/insights", post_id);
150 let resp = self.http_client.get(&path, params, &token).await?;
151 resp.json()
152 }
153
154 pub async fn get_account_insights_with_options(
156 &self,
157 user_id: &UserId,
158 opts: &AccountInsightsOptions,
159 ) -> crate::Result<InsightsResponse> {
160 if !user_id.is_valid() {
161 return Err(error::new_validation_error(
162 0,
163 constants::ERR_EMPTY_USER_ID,
164 "",
165 "user_id",
166 ));
167 }
168
169 let token = self.access_token().await;
170 let mut params = HashMap::new();
171
172 let metric_str = match &opts.metrics {
173 Some(metrics) if !metrics.is_empty() => metrics
174 .iter()
175 .map(|m| {
176 serde_json::to_string(m)
177 .unwrap_or_default()
178 .trim_matches('"')
179 .to_owned()
180 })
181 .collect::<Vec<_>>()
182 .join(","),
183 _ => "views,likes,replies,reposts,quotes,followers_count".to_owned(),
184 };
185 params.insert("metric".into(), metric_str);
186
187 let period_str = match &opts.period {
188 Some(period) => serde_json::to_string(period)
189 .unwrap_or_default()
190 .trim_matches('"')
191 .to_owned(),
192 None => "lifetime".to_owned(),
193 };
194 params.insert("period".into(), period_str);
195
196 if let Some(since) = opts.since {
197 params.insert("since".into(), since.timestamp().to_string());
198 }
199 if let Some(until) = opts.until {
200 params.insert("until".into(), until.timestamp().to_string());
201 }
202 if let Some(ref breakdown) = opts.breakdown {
203 params.insert(
204 "breakdown".into(),
205 serde_json::to_string(breakdown)
206 .unwrap_or_default()
207 .trim_matches('"')
208 .to_owned(),
209 );
210 }
211
212 let path = format!("/{}/threads_insights", user_id);
213 let resp = self.http_client.get(&path, params, &token).await?;
214 resp.json()
215 }
216}