1use anyhow::Result;
2use chrono::{DateTime, TimeZone, Utc};
3use futures::StreamExt;
4use indicatif::{ProgressBar, ProgressStyle};
5use reqwest_eventsource::{Error as EventSourceError, Event, EventSource};
6use serde::Deserialize;
7
8#[derive(Debug, Deserialize)]
9struct Entry {
10 domain: String,
11 subject_common_name: Option<String>,
12 not_before: i64,
13}
14
15#[derive(Debug, Deserialize)]
16struct TailEntry {
17 hostname: String,
18}
19
20#[derive(Debug, Deserialize)]
21struct ProgressEvent {
22 progress_percentage: f64,
23}
24
25trait Printable {
26 fn print(&self);
27}
28
29impl Printable for Entry {
30 fn print(&self) {
31 let timestamp = Utc.timestamp_opt(self.not_before, 0).unwrap();
32 println!(
33 "domain={} subject_common_name={} not_before={} human_readable_not_before={}",
34 self.domain,
35 if let Some(subject_common_name) = &self.subject_common_name {
36 subject_common_name
37 } else {
38 "N/A"
39 },
40 self.not_before,
41 format_timestamp(timestamp)
42 );
43 }
44}
45
46impl Printable for TailEntry {
47 fn print(&self) {
48 let current_time: DateTime<Utc> = Utc::now();
49 println!(
50 "hostname={} timestamp={} human_readable_not_before={}",
51 self.hostname,
52 current_time.to_rfc3339(),
53 format_timestamp(current_time)
54 );
55 }
56}
57
58async fn process_event_stream<T>(url: &str) -> Result<()>
59where
60 T: for<'de> Deserialize<'de> + Printable,
61{
62 let request = reqwest::Client::new().get(url).header("User-Agent", "merklemap-cli/rs");
63
64 let mut es = EventSource::new(request)?;
65
66 let pb = ProgressBar::new(100);
67 pb.set_style(ProgressStyle::default_bar()
68 .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {percent}% ({eta})")?
69 .progress_chars("#>-"));
70
71 while let Some(event) = es.next().await {
72 match event {
73 Ok(Event::Message(message)) => {
74 if let Ok(progress) = serde_json::from_str::<ProgressEvent>(&message.data) {
75 pb.set_position(progress.progress_percentage as u64);
76 } else if let Ok(entry) = serde_json::from_str::<T>(&message.data) {
77 entry.print();
78 }
79 }
80 Ok(Event::Open) => {}
81 Err(EventSourceError::StreamEnded) => break,
82 Err(e) => eprintln!("Error: {:?}", e),
83 }
84 }
85
86 pb.finish_and_clear();
87 Ok(())
88}
89
90fn format_timestamp(timestamp: DateTime<Utc>) -> String {
91 timestamp.format("%Y-%m-%d %H:%M:%S UTC").to_string()
92}
93
94pub async fn search(query: &str) -> Result<()> {
95 let url = format!("https://api.merklemap.com/search?query={}&stream=true&stream_progress=true", query);
96 process_event_stream::<Entry>(&url).await
97}
98
99pub async fn tail() -> Result<()> {
100 let url = "https://api.merklemap.com/live-domains?no_throttle=true";
101 process_event_stream::<TailEntry>(url).await
102}