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<(), 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 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}