gnostr_query/
lib.rs

1use futures::{SinkExt, StreamExt};
2use log::debug;
3use serde_json::{json, Map};
4use tokio_tungstenite::{connect_async, tungstenite::Message};
5use url::Url;
6
7pub mod cli;
8
9#[derive(Debug)]
10pub struct Config {
11    host: String,
12    port: u16,
13    use_tls: bool,
14    retries: u8,
15    authors: String,
16    ids: String,
17    limit: i32,
18    generic: (String, String),
19    hashtag: String,
20    mentions: String,
21    references: String,
22    kinds: String,
23}
24
25#[derive(Debug, Default)]
26pub struct ConfigBuilder {
27    host: Option<String>,
28    port: Option<u16>,
29    use_tls: bool,
30    retries: u8,
31    authors: Option<String>,
32    ids: Option<String>,
33    limit: Option<i32>,
34    generic: Option<(String, String)>,
35    hashtag: Option<String>,
36    mentions: Option<String>,
37    references: Option<String>,
38    kinds: Option<String>,
39}
40impl ConfigBuilder {
41    pub fn new() -> Self {
42        ConfigBuilder {
43            host: None,
44            port: None,
45            use_tls: false,
46            retries: 0,
47            authors: None,
48            ids: None,
49            limit: None,
50            generic: None,
51            hashtag: None,
52            mentions: None,
53            references: None,
54            kinds: None,
55        }
56    }
57    pub fn host(mut self, host: &str) -> Self {
58        self.host = Some(host.to_string());
59        self
60    }
61    pub fn port(mut self, port: u16) -> Self {
62        self.port = Some(port);
63        self
64    }
65    pub fn use_tls(mut self, use_tls: bool) -> Self {
66        self.use_tls = use_tls;
67        self
68    }
69    pub fn retries(mut self, retries: u8) -> Self {
70        self.retries = retries;
71        self
72    }
73    pub fn authors(mut self, authors: &str) -> Self {
74        self.authors = Some(authors.to_string());
75        self
76    }
77    pub fn ids(mut self, ids: &str) -> Self {
78        self.ids = Some(ids.to_string());
79        self
80    }
81    pub fn limit(mut self, limit: i32) -> Self {
82        self.limit = Some(limit);
83        self
84    }
85    //pub fn generic(mut self, generic: &(&str, &str), tag: &str, val: &str) -> Self {
86    pub fn generic(mut self, tag: &str, val: &str) -> Self {
87        //self.generic = Some(("".to_string(), "".to_string()));
88        self.generic = Some((tag.to_string(), val.to_string()));
89        self
90    }
91    pub fn hashtag(mut self, hashtag: &str) -> Self {
92        self.hashtag = Some(hashtag.to_string());
93        self
94    }
95    pub fn mentions(mut self, mentions: &str) -> Self {
96        self.mentions = Some(mentions.to_string());
97        self
98    }
99    pub fn references(mut self, references: &str) -> Self {
100        self.references = Some(references.to_string());
101        self
102    }
103    pub fn kinds(mut self, kinds: &str) -> Self {
104        self.kinds = Some(kinds.to_string());
105        self
106    }
107    pub fn build(self) -> Result<Config, String> {
108        Ok(Config {
109            host: self.host.ok_or("Missing host")?,
110            port: self.port.ok_or("Missing port")?,
111            use_tls: self.use_tls,
112            retries: self.retries,
113            authors: self.authors.ok_or("")?,
114            ids: self.ids.ok_or("")?,
115            limit: self.limit.ok_or("")?,
116            generic: self.generic.ok_or("")?,
117            hashtag: self.hashtag.ok_or("")?,
118            mentions: self.mentions.ok_or("")?,
119            references: self.references.ok_or("")?,
120            kinds: self.kinds.ok_or("")?,
121        })
122    }
123}
124pub async fn send(
125    query_string: String,
126    relay_url: Url,
127    limit: Option<i32>,
128) -> Result<Vec<String>, Box<dyn std::error::Error>> {
129    //println!("\n{}\n", query_string);
130    //println!("\n{}\n", relay_url);
131    //println!("\n{}\n", limit.unwrap());
132    debug!("\n{query_string}\n");
133    debug!("\n{relay_url}\n");
134    debug!("\n{}\n", limit.unwrap());
135    let (ws_stream, _) = connect_async(relay_url).await?;
136    let (mut write, mut read) = ws_stream.split();
137    write.send(Message::Text(query_string)).await?;
138    let mut count: i32 = 0;
139    let mut vec_result: Vec<String> = vec![];
140    while let Some(message) = read.next().await {
141        let data = message?;
142        if count >= limit.unwrap() {
143            //std::process::exit(0);
144            return Ok(vec_result);
145        }
146        if let Message::Text(text) = data {
147            //print!("{text}");
148            vec_result.push(text);
149            count += 1;
150        }
151    }
152    Ok(vec_result)
153}
154
155pub fn build_gnostr_query(
156    authors: Option<&str>,
157    ids: Option<&str>,
158    limit: Option<i32>,
159    generic: Option<(&str, &str)>,
160    hashtag: Option<&str>,
161    mentions: Option<&str>,
162    references: Option<&str>,
163    kinds: Option<&str>,
164) -> Result<String, Box<dyn std::error::Error>> {
165    let mut filt = Map::new();
166
167    if let Some(authors) = authors {
168        filt.insert(
169            "authors".to_string(),
170            json!(authors.split(',').collect::<Vec<&str>>()),
171        );
172    }
173
174    if let Some(ids) = ids {
175        filt.insert(
176            "ids".to_string(),
177            json!(ids.split(',').collect::<Vec<&str>>()),
178        );
179    }
180
181    if let Some(limit) = limit {
182        filt.insert("limit".to_string(), json!(limit));
183    }
184
185    if let Some((tag, val)) = generic {
186        let tag_with_hash = format!("#{tag}");
187        filt.insert(tag_with_hash, json!(val.split(',').collect::<Vec<&str>>()));
188    }
189
190    if let Some(hashtag) = hashtag {
191        filt.insert(
192            "#t".to_string(),
193            json!(hashtag.split(',').collect::<Vec<&str>>()),
194        );
195    }
196
197    if let Some(mentions) = mentions {
198        filt.insert(
199            "#p".to_string(),
200            json!(mentions.split(',').collect::<Vec<&str>>()),
201        );
202    }
203
204    if let Some(references) = references {
205        filt.insert(
206            "#e".to_string(),
207            json!(references.split(',').collect::<Vec<&str>>()),
208        );
209    }
210
211    if let Some(kinds) = kinds {
212        let kind_ints: Result<Vec<i64>, _> = kinds.split(',').map(|s| s.parse::<i64>()).collect();
213        match kind_ints {
214            Ok(kind_ints) => {
215                filt.insert("kinds".to_string(), json!(kind_ints));
216            }
217            Err(_) => {
218                return Err("Error parsing kinds. Ensure they are integers.".into());
219            }
220        }
221    }
222
223    let q = json!(["REQ", "gnostr-query", filt]);
224    Ok(serde_json::to_string(&q)?)
225}