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, tag: &str, val: &str) -> Self {
87 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 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 return Ok(vec_result);
145 }
146 if let Message::Text(text) = data {
147 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}