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<(), 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    while let Some(message) = read.next().await {
140        let data = message?;
141        if count >= limit.unwrap() {
142            std::process::exit(0);
143        }
144        if let Message::Text(text) = data {
145            print!("{text}");
146            count += 1;
147        }
148    }
149    Ok(())
150}
151
152pub fn build_gnostr_query(
153    authors: Option<&str>,
154    ids: Option<&str>,
155    limit: Option<i32>,
156    generic: Option<(&str, &str)>,
157    hashtag: Option<&str>,
158    mentions: Option<&str>,
159    references: Option<&str>,
160    kinds: Option<&str>,
161) -> Result<String, Box<dyn std::error::Error>> {
162    let mut filt = Map::new();
163
164    if let Some(authors) = authors {
165        filt.insert(
166            "authors".to_string(),
167            json!(authors.split(',').collect::<Vec<&str>>()),
168        );
169    }
170
171    if let Some(ids) = ids {
172        filt.insert(
173            "ids".to_string(),
174            json!(ids.split(',').collect::<Vec<&str>>()),
175        );
176    }
177
178    if let Some(limit) = limit {
179        filt.insert("limit".to_string(), json!(limit));
180    }
181
182    if let Some((tag, val)) = generic {
183        let tag_with_hash = format!("#{tag}");
184        filt.insert(tag_with_hash, json!(val.split(',').collect::<Vec<&str>>()));
185    }
186
187    if let Some(hashtag) = hashtag {
188        filt.insert(
189            "#t".to_string(),
190            json!(hashtag.split(',').collect::<Vec<&str>>()),
191        );
192    }
193
194    if let Some(mentions) = mentions {
195        filt.insert(
196            "#p".to_string(),
197            json!(mentions.split(',').collect::<Vec<&str>>()),
198        );
199    }
200
201    if let Some(references) = references {
202        filt.insert(
203            "#e".to_string(),
204            json!(references.split(',').collect::<Vec<&str>>()),
205        );
206    }
207
208    if let Some(kinds) = kinds {
209        let kind_ints: Result<Vec<i64>, _> = kinds.split(',').map(|s| s.parse::<i64>()).collect();
210        match kind_ints {
211            Ok(kind_ints) => {
212                filt.insert("kinds".to_string(), json!(kind_ints));
213            }
214            Err(_) => {
215                return Err("Error parsing kinds. Ensure they are integers.".into());
216            }
217        }
218    }
219
220    let q = json!(["REQ", "gnostr-query", filt]);
221    Ok(serde_json::to_string(&q)?)
222}