1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use anyhow::Result;
use chrono::{DateTime, TimeZone, Utc};
use futures::StreamExt;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest_eventsource::{Error as EventSourceError, Event, EventSource};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Entry {
    domain: String,
    subject_common_name: Option<String>,
    not_before: i64,
}

#[derive(Debug, Deserialize)]
struct TailEntry {
    hostname: String,
}

#[derive(Debug, Deserialize)]
struct ProgressEvent {
    progress_percentage: f64,
}

trait Printable {
    fn print(&self);
}

impl Printable for Entry {
    fn print(&self) {
        let timestamp = Utc.timestamp_opt(self.not_before, 0).unwrap();
        println!(
            "domain={} subject_common_name={} not_before={} human_readable_not_before={}",
            self.domain,
            if let Some(subject_common_name) = &self.subject_common_name {
                subject_common_name
            } else {
                "N/A"
            },
            self.not_before,
            format_timestamp(timestamp)
        );
    }
}

impl Printable for TailEntry {
    fn print(&self) {
        let current_time: DateTime<Utc> = Utc::now();
        println!(
            "hostname={} timestamp={} human_readable_not_before={}",
            self.hostname,
            current_time.to_rfc3339(),
            format_timestamp(current_time)
        );
    }
}

async fn process_event_stream<T>(url: &str) -> Result<()>
where
    T: for<'de> Deserialize<'de> + Printable,
{
    let mut es = EventSource::get(url);
    let pb = ProgressBar::new(100);
    pb.set_style(ProgressStyle::default_bar()
        .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {percent}% ({eta})")?
        .progress_chars("#>-"));

    while let Some(event) = es.next().await {
        match event {
            Ok(Event::Message(message)) => {
                if let Ok(progress) = serde_json::from_str::<ProgressEvent>(&message.data) {
                    pb.set_position(progress.progress_percentage as u64);
                } else if let Ok(entry) = serde_json::from_str::<T>(&message.data) {
                    entry.print();
                }
            }
            Ok(Event::Open) => {}
            Err(EventSourceError::StreamEnded) => break,
            Err(e) => eprintln!("Error: {:?}", e),
        }
    }

    pb.finish_and_clear();
    Ok(())
}

fn format_timestamp(timestamp: DateTime<Utc>) -> String {
    timestamp.format("%Y-%m-%d %H:%M:%S UTC").to_string()
}

pub async fn search(query: &str) -> Result<()> {
    let url = format!("https://api.merklemap.com/search?query={}&stream=true&stream_progress=true", query);
    process_event_stream::<Entry>(&url).await
}

pub async fn tail() -> Result<()> {
    let url = "https://api.merklemap.com/live-domains?no_throttle=true";
    process_event_stream::<TailEntry>(url).await
}