Add more readings, remove description
This commit is contained in:
parent
10b376e06e
commit
bc2f19efd8
1 changed files with 96 additions and 33 deletions
129
src/main.rs
129
src/main.rs
|
@ -8,10 +8,18 @@ use colored::Colorize;
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
#[clap(help = "Plume Labs location ID. For example, https://air.plumelabs.com/air-quality-in-XXX")]
|
#[clap(help = "Plume Labs location ID. For example, https://air.plumelabs.com/air-quality-in-XXX")]
|
||||||
location: String,
|
location: String,
|
||||||
#[clap(short, long, help = "Display raw Air Quality Index")]
|
#[clap(short, long, help = "Display raw value without descriptor")]
|
||||||
raw: bool,
|
raw: bool,
|
||||||
#[clap(short, long, help = "Display AccuWeather Air Quality Index description")]
|
#[clap(short, long, help = "Display raw concentration in µg/m³")]
|
||||||
description: bool,
|
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)]
|
#[derive(From, Debug)]
|
||||||
|
@ -21,26 +29,91 @@ enum Error {
|
||||||
InvalidResponse,
|
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> {
|
fn main() -> Result<(), Error> {
|
||||||
let arguments = Arguments::parse();
|
let arguments = Arguments::parse();
|
||||||
let response = reqwest::blocking::get(format!("https://air.plumelabs.com/air-quality-in-{}", arguments.location))?;
|
let data = Data::fetch(&arguments.location)?;
|
||||||
if !response.status().is_success() {
|
let descriptor;
|
||||||
return Err(Error::HttpError);
|
let Reading { aqi, concentration } = if arguments.fine_particulate {
|
||||||
}
|
descriptor = "PM2.5 ";
|
||||||
let content = response.text_with_charset("utf-8")?;
|
data.fine_particulate
|
||||||
let document = Html::parse_document(&content);
|
} else if arguments.coarse_particulate {
|
||||||
let selector = Selector::parse("span[data-role=\"current-pi\"]").unwrap();
|
descriptor = "PM10 ";
|
||||||
let aqi = match match document.select(&selector).next() {
|
data.coarse_particulate
|
||||||
Some(element) => element,
|
} else if arguments.nitrogen_dioxide {
|
||||||
None => return Err(Error::InvalidResponse),
|
descriptor = "NO2 ";
|
||||||
}.inner_html().trim().parse::<f32>() {
|
data.nitrogen_dioxide
|
||||||
Ok(aqi) => aqi,
|
} else if arguments.ozone {
|
||||||
Err(_) => return Err(Error::InvalidResponse),
|
descriptor = "O3 ";
|
||||||
} as u32;
|
data.ozone
|
||||||
|
} else {
|
||||||
|
descriptor = "";
|
||||||
|
Reading {
|
||||||
|
aqi: data.aqi,
|
||||||
|
concentration: data.aqi,
|
||||||
|
}
|
||||||
|
};
|
||||||
if arguments.raw {
|
if arguments.raw {
|
||||||
println!("{}", aqi);
|
println!("{}", if arguments.concentration {
|
||||||
|
concentration
|
||||||
|
} else {
|
||||||
|
aqi
|
||||||
|
});
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
let display = if arguments.concentration {
|
||||||
|
format!("{concentration} µg/m³")
|
||||||
|
} else {
|
||||||
|
format!("{aqi} AQI")
|
||||||
|
};
|
||||||
|
print!("{}", descriptor);
|
||||||
println!("{} {}",
|
println!("{} {}",
|
||||||
match aqi {
|
match aqi {
|
||||||
250.. => "Dangerous".purple(),
|
250.. => "Dangerous".purple(),
|
||||||
|
@ -52,22 +125,12 @@ fn main() -> Result<(), Error> {
|
||||||
}.bold(),
|
}.bold(),
|
||||||
match aqi {
|
match aqi {
|
||||||
// 250.. => (no appropriate terminal color) // Dangerous 250+
|
// 250.. => (no appropriate terminal color) // Dangerous 250+
|
||||||
150.. => aqi.to_string().purple(), // Very Unhealthy 150-249
|
150.. => display.purple(), // Very Unhealthy 150-249
|
||||||
100.. => aqi.to_string().magenta(), // Unhealthy 100-149
|
100.. => display.magenta(), // Unhealthy 100-149
|
||||||
50.. => aqi.to_string().red(), // Poor 50-99
|
50.. => display.red(), // Poor 50-99
|
||||||
20.. => aqi.to_string().yellow(), // Fair 20-49
|
20.. => display.yellow(), // Fair 20-49
|
||||||
0.. => aqi.to_string().green(), // Excellent 0-19
|
0.. => display.green(), // Excellent 0-19
|
||||||
}.bold()
|
}.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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue