use tokio::io::{self, AsyncReadExt}; use bytes::BytesMut; use chrono::{DateTime, Datelike, Timelike}; use reqwest; use serde::{Deserialize, Serialize}; use tokio::time::error::Error; use tokio::time::{sleep, Duration}; use webbrowser::open_browser; #[derive(Serialize, Deserialize, Debug)] struct Appointment { locationId: u32, startTimestamp: String, endTimestamp: String, active: bool, duration: u32, remoteInd: bool, } const TIME: u64 = 3000; const CHARLOTTE: u32 = 14321; // const TESTING: u32 = 5023; #[tokio::main] async fn main() { let mut location_id: u32 = CHARLOTTE; let mut buf = [0, 0, 0, 0]; println!( "Enter a location id to override the default (Charlotte, NC, {:?}): ", CHARLOTTE ); io::stdin().read(&mut buf).await.unwrap(); let loc = String::from_utf8(buf.to_vec()).unwrap().trim().parse(); if loc.is_ok() { location_id = loc.unwrap(); } println!("Looking for appointments at location id: {}", location_id); loop { look_for_appointments(location_id).await; println!( "No appointments found, trying again in {} seconds", TIME / 1000 ); sleep(Duration::from_millis(TIME)).await; } } async fn look_for_appointments(location_id: u32) { let data = get_data(location_id).await.unwrap(); for appointment in data { let time = appointment.startTimestamp + ":00-05:00"; let at = DateTime::parse_from_rfc3339(&time).unwrap(); println!( "Appointment found on {}/{}/{} at {}:{}", at.day(), at.month(), at.year(), at.hour(), at.minute(), ); open_browser(webbrowser::Browser::Default, "https://ttp.cbp.dhs.gov/credential/v1/login?app=GOES-prod&lang=en&state=en:IonIgXB4I9qt02S6cgKydMVA9HXHC1vq").unwrap(); println!("Press any key to continue..."); io::stdin() .read_buf(&mut BytesMut::with_capacity(10)) .await .unwrap(); } } async fn get_data(location_id: u32) -> Result, Error> { let res = reqwest::get(format!( "https://ttp.cbp.dhs.gov/schedulerapi/slots?orderBy=soonest&limit=3&locationId={}&minimum=1", location_id )) .await; let body: Vec = res.unwrap().json().await.unwrap(); Ok(body) }