Add more readings, remove description

main
Elnu 2 years ago
parent 10b376e06e
commit bc2f19efd8

@ -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<Self, Error> {
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::<f32>() {
Ok(aqi) => aqi,
Err(_) => return Err(Error::InvalidResponse),
} as u32)
};
let get_reading = |name| -> Result<Reading, Error> {
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::<f32>() {
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(());
}