Skip to main content

threads_rs/api/
search.rs

1use std::collections::HashMap;
2
3use crate::client::Client;
4use crate::constants;
5use crate::error;
6use crate::types::search::SearchOptions;
7use crate::types::{PostsResponse, SearchMode};
8use crate::validation;
9
10impl Client {
11    /// Search for posts by keyword.
12    pub async fn keyword_search(
13        &self,
14        query: &str,
15        opts: Option<&SearchOptions>,
16    ) -> crate::Result<PostsResponse> {
17        if query.is_empty() {
18            return Err(error::new_validation_error(
19                0,
20                constants::ERR_EMPTY_SEARCH_QUERY,
21                "",
22                "query",
23            ));
24        }
25
26        if let Some(opts) = opts {
27            validation::validate_search_options(opts)?;
28        }
29
30        let token = self.access_token().await;
31        let mut params = HashMap::new();
32        params.insert("q".into(), query.to_owned());
33        params.insert("fields".into(), constants::POST_EXTENDED_FIELDS.into());
34
35        if let Some(opts) = opts {
36            if let Some(ref search_type) = opts.search_type {
37                params.insert(
38                    "search_type".into(),
39                    serde_json::to_string(search_type)
40                        .unwrap_or_default()
41                        .trim_matches('"')
42                        .to_owned(),
43                );
44            }
45            if let Some(ref search_mode) = opts.search_mode {
46                params.insert(
47                    "search_mode".into(),
48                    serde_json::to_string(search_mode)
49                        .unwrap_or_default()
50                        .trim_matches('"')
51                        .to_owned(),
52                );
53            }
54            if let Some(ref media_type) = opts.media_type {
55                params.insert("media_type".into(), media_type.clone());
56            }
57            if let Some(ref author) = opts.author_username {
58                params.insert("author_username".into(), author.clone());
59            }
60            if let Some(limit) = opts.limit {
61                params.insert("limit".into(), limit.to_string());
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            if let Some(ref before) = opts.before {
70                params.insert("before".into(), before.clone());
71            }
72            if let Some(ref after) = opts.after {
73                params.insert("after".into(), after.clone());
74            }
75        }
76
77        let resp = self
78            .http_client
79            .get("/keyword_search", params, &token)
80            .await?;
81        resp.json()
82    }
83
84    /// Search for posts by hashtag.
85    ///
86    /// Convenience wrapper around `keyword_search` with `search_mode` set to `TAG`.
87    pub async fn hashtag_search(
88        &self,
89        tag: &str,
90        opts: Option<&SearchOptions>,
91    ) -> crate::Result<PostsResponse> {
92        let mut tag_opts = opts.cloned().unwrap_or_default();
93        tag_opts.search_mode = Some(SearchMode::Tag);
94        self.keyword_search(tag, Some(&tag_opts)).await
95    }
96}