From bc2f19efd864ca30686b1ac5fe6e2d9ff45a9c9c Mon Sep 17 00:00:00 2001 From: ElnuDev Date: Sun, 25 Sep 2022 16:23:01 -0700 Subject: [PATCH] Add more readings, remove description --- src/main.rs | 129 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 96 insertions(+), 33 deletions(-) diff --git a/src/main.rs b/src/main.rs index f66f7aa..e482da5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,18 @@ use colored::Colorize; struct Arguments { #[clap(help = "Plume Labs location ID. For example, https://air.plumelabs.com/air-quality-in-XXX")] location: String, - #[clap(short, long, help = "Display raw Air Quality Index")] + #[clap(short, long, help = "Display raw value without descriptor")] raw: bool, - #[clap(short, long, help = "Display AccuWeather Air Quality Index description")] - description: bool, + #[clap(short, long, help = "Display raw concentration in µg/m³")] + concentration: bool, + #[clap(short = 'p', long = "pm25", help = "Display fine PM2.5 inhalable particulate matter")] + fine_particulate: bool, + #[clap(short = 'P', long = "pm10", help = "Display coarse PM10 inhalable particulate matter")] + coarse_particulate: bool, + #[clap(short, long = "no2", help = "Display nitrogen dioxide NO2")] + nitrogen_dioxide: bool, + #[clap(short, long = "o3", help = "Display ozone O3")] + ozone: bool, } #[derive(From, Debug)] @@ -21,26 +29,91 @@ enum Error { InvalidResponse, } +struct Reading { + concentration: u32, + aqi: u32, +} + +struct Data { + aqi: u32, + fine_particulate: Reading, + coarse_particulate: Reading, + nitrogen_dioxide: Reading, + ozone: Reading, +} + +impl Data { + fn fetch(location: &str) -> Result { + let response = reqwest::blocking::get(format!("https://air.plumelabs.com/air-quality-in-{location}"))?; + if !response.status().is_success() { + return Err(Error::HttpError); + } + let content = response.text_with_charset("utf-8")?; + let document = Html::parse_document(&content); + let get_value = |selector: &str| { + let selector = selector.to_owned(); + let selector = Selector::parse(&selector).unwrap(); + Ok(match match document.select(&selector).next() { + Some(element) => element, + None => return Err(Error::InvalidResponse), + }.inner_html().trim().parse::() { + Ok(aqi) => aqi, + Err(_) => return Err(Error::InvalidResponse), + } as u32) + }; + let get_reading = |name| -> Result { + Ok(Reading { + concentration: get_value(&format!("li[data-id=\"{name}\"][data-format=\"value_upm\"] div[data-role=\"pollutant-level\"]"))?, + aqi: get_value(&format!("li[data-id=\"{name}\"][data-format=\"pi\"] div[data-role=\"pollutant-level\"]"))?, + }) + }; + Ok(Self { + aqi: get_value("span[data-role=\"current-pi\"")?, + fine_particulate: get_reading("PM25")?, + coarse_particulate: get_reading("PM10")?, + nitrogen_dioxide: get_reading("NO2")?, + ozone: get_reading("O3")?, + }) + } +} + fn main() -> Result<(), Error> { let arguments = Arguments::parse(); - let response = reqwest::blocking::get(format!("https://air.plumelabs.com/air-quality-in-{}", arguments.location))?; - if !response.status().is_success() { - return Err(Error::HttpError); - } - let content = response.text_with_charset("utf-8")?; - let document = Html::parse_document(&content); - let selector = Selector::parse("span[data-role=\"current-pi\"]").unwrap(); - let aqi = match match document.select(&selector).next() { - Some(element) => element, - None => return Err(Error::InvalidResponse), - }.inner_html().trim().parse::() { - Ok(aqi) => aqi, - Err(_) => return Err(Error::InvalidResponse), - } as u32; + let data = Data::fetch(&arguments.location)?; + let descriptor; + let Reading { aqi, concentration } = if arguments.fine_particulate { + descriptor = "PM2.5 "; + data.fine_particulate + } else if arguments.coarse_particulate { + descriptor = "PM10 "; + data.coarse_particulate + } else if arguments.nitrogen_dioxide { + descriptor = "NO2 "; + data.nitrogen_dioxide + } else if arguments.ozone { + descriptor = "O3 "; + data.ozone + } else { + descriptor = ""; + Reading { + aqi: data.aqi, + concentration: data.aqi, + } + }; if arguments.raw { - println!("{}", aqi); + println!("{}", if arguments.concentration { + concentration + } else { + aqi + }); return Ok(()); } + let display = if arguments.concentration { + format!("{concentration} µg/m³") + } else { + format!("{aqi} AQI") + }; + print!("{}", descriptor); println!("{} {}", match aqi { 250.. => "Dangerous".purple(), @@ -52,22 +125,12 @@ fn main() -> Result<(), Error> { }.bold(), match aqi { // 250.. => (no appropriate terminal color) // Dangerous 250+ - 150.. => aqi.to_string().purple(), // Very Unhealthy 150-249 - 100.. => aqi.to_string().magenta(), // Unhealthy 100-149 - 50.. => aqi.to_string().red(), // Poor 50-99 - 20.. => aqi.to_string().yellow(), // Fair 20-49 - 0.. => aqi.to_string().green(), // Excellent 0-19 + 150.. => display.purple(), // Very Unhealthy 150-249 + 100.. => display.magenta(), // Unhealthy 100-149 + 50.. => display.red(), // Poor 50-99 + 20.. => display.yellow(), // Fair 20-49 + 0.. => display.green(), // Excellent 0-19 }.bold() ); - if arguments.description { - println!("{}", match aqi { - 250.. => "Any exposure to the air, even for a few minutes, can lead to serious health effects on everybody. Avoid outdoor activities.", - 150.. => "Health effects will be immediately felt by sensitive groups and should avoid outdoor activity. Healthy individuals are likely to experience difficulty breathing and throat irritation; consider staying indoors and rescheduling outdoor activities.", - 100.. => "Health effects can be immediately felt by sensitive groups. Healthy individuals may experience difficulty breathing and throat irritation with prolonged exposure. Limit outdoor activity.", - 50.. => "The air has reached a high level of pollution and is unhealthy for sensitive groups. Reduce time spent outside if you are feeling symptoms such as difficulty breathing or throat irritation.", - 20.. => "The air quality is generally acceptable for most individuals. However, sensitive groups may experience minor to moderate symptoms from long-term exposure.", - 0.. => "The air quality is ideal for most individuals; enjoy your normal outdoor activities.", - }); - } return Ok(()); }