diff --git a/Cargo.toml b/Cargo.toml index 9abb3bc..33a4fe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,5 @@ -[package] -name = "openweatherwidget" -version = "0.0.1" -edition = "2024" - -[dependencies] -toml = "0.9.6" -dirs = "6.0.0" -serde = { version = "1.0.225", features = ["derive"] } -reqwest = {version = "0.12.23", features = ["blocking", "json"] } -serde_json = "1.0.145" - +[workspace] +members = [ + "owm_api25", + "widget", +] diff --git a/owm_api25/Cargo.toml b/owm_api25/Cargo.toml new file mode 100644 index 0000000..dfd4bc1 --- /dev/null +++ b/owm_api25/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "owm_api25" +version = "0.0.1" +edition = "2024" + +[dependencies] +toml = "0.9.6" +dirs = "6.0.0" +serde = { version = "1.0.225", features = ["derive"] } +serde_json = "1.0.145" diff --git a/owm_api25/src/config.rs b/owm_api25/src/config.rs new file mode 100644 index 0000000..d31d426 --- /dev/null +++ b/owm_api25/src/config.rs @@ -0,0 +1,66 @@ +use serde::Deserialize; +use std::fs; + +#[derive(Debug, Deserialize)] +pub struct General { + pub api_key: String, // Required for API query. Get yours for free from https://openweathermap.org + pub city_id: Option, // Any of these location parameters are required + pub city_name: Option, // Find City ID or Name from https://openweathermap.org/find? + pub lat: Option, // Latitude and Longitude must be used together + pub lon: Option, + pub zip: Option, // Zip code + #[serde(default = "default_units")] + pub units: String, // "metric", "imperial", "standard" (Default) + #[serde(default = "default_lang")] + pub lang: String, // Default: "en" +} + +#[derive(Debug, Deserialize)] +pub struct Config { + pub general: General, +} + +fn default_units() -> String { + "standard".into() +} + +fn default_lang() -> String { + "en".into() +} + +impl Config { + pub fn load() -> Result> { + let mut path = dirs::config_dir().ok_or("No config dir found")?; + path.push("candywidgets/openweathermap.toml"); + + let toml_str = fs::read_to_string(&path) + .map_err(|_| format!("Failed to read config: {}", path.display()))?; + + Ok(toml::from_str(&toml_str)?) + } + + pub fn build_url(&self) -> Result { + let base = "https://api.openweathermap.org/data/2.5/weather"; + + let mut params = vec![ + format!("appid={}", self.general.api_key), + format!("units={}", self.general.units), + format!("lang={}", self.general.lang), + "mode=json".to_string(), + ]; + + if let Some(ref id) = self.general.city_id { + params.push(format!("id={id}")); + } else if let Some(ref name) = self.general.city_name { + params.push(format!("q={name}")); + } else if let (Some(lat), Some(lon)) = (self.general.lat, self.general.lon) { + params.push(format!("lat={lat}&lon={lon}")); + } else if let Some(ref zip) = self.general.zip { + params.push(format!("zip={zip}")); + } else { + return Err("No valid location field found in config".into()); + } + + Ok(format!("{}?{}", base, params.join("&"))) + } +} diff --git a/owm_api25/src/lib.rs b/owm_api25/src/lib.rs new file mode 100644 index 0000000..844a605 --- /dev/null +++ b/owm_api25/src/lib.rs @@ -0,0 +1,27 @@ +pub mod config; + +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +pub struct Weather { + pub main: String, + pub icon: String, +} + +#[derive(Debug, Deserialize)] +pub struct Main { + pub temp: f32, +} + +#[derive(Debug, Deserialize)] +pub struct Sys { + pub country: String, +} + +#[derive(Debug, Deserialize)] +pub struct WeatherResponse { + pub name: String, + pub sys: Sys, + pub weather: Vec, + pub main: Main, +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index a528c1a..0000000 --- a/src/main.rs +++ /dev/null @@ -1,65 +0,0 @@ -use reqwest::blocking; -use serde::Deserialize; -use std::fs; - -#[derive(Debug, Deserialize)] -struct General { - api_key: String, - city_id: String, - units: String, -} - -#[derive(Debug, Deserialize)] -struct Config { - general: General, -} - -#[derive(Debug, Deserialize)] -struct Weather { - main: String, - icon: String, -} - -#[derive(Debug, Deserialize)] -struct Main { - temp: f32, -} - -#[derive(Debug, Deserialize)] -struct Sys { - country: String, -} - -#[derive(Debug, Deserialize)] -struct WeatherResponse { - name: String, - sys: Sys, - weather: Vec, - main: Main, -} - -fn main() -> Result<(), Box> { - let mut config_path = dirs::config_dir().ok_or("No config dir found")?; - config_path.push("candywidgets/openweathermap.toml"); - - let config_str = fs::read_to_string(&config_path) - .map_err(|_| format!("Failed to read config: {}", config_path.display()))?; - - let config: Config = toml::from_str(&config_str)?; - - let url = format!( - "https://api.openweathermap.org/data/2.5/weather?units={}&id={}&appid={}", - config.general.units, config.general.city_id, config.general.api_key - ); - - let resp = blocking::get(&url)?.json::()?; - - println!("City: {}, {}", resp.name, resp.sys.country); - if let Some(w) = resp.weather.first() { - println!("Weather: {}", w.main); - println!("Icon: {}", w.icon); - } - println!("Temperature: {}°C", resp.main.temp); - - Ok(()) -} diff --git a/widget/Cargo.toml b/widget/Cargo.toml new file mode 100644 index 0000000..390facf --- /dev/null +++ b/widget/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "openweatherwidget" +version = "0.0.1" +edition = "2024" + +[dependencies] +owm_api25 = { path = "../owm_api25" } +reqwest = {version = "0.12.23", features = ["blocking", "json"] } + diff --git a/widget/src/main.rs b/widget/src/main.rs new file mode 100644 index 0000000..5bf20e2 --- /dev/null +++ b/widget/src/main.rs @@ -0,0 +1,18 @@ +use owm_api25::{WeatherResponse, config::Config}; +use reqwest::blocking; + +fn main() -> Result<(), Box> { + let config = Config::load()?; + let url = config.build_url()?; + + let resp = blocking::get(&url)?.json::()?; + + println!("City: {}, {}", resp.name, resp.sys.country); + if let Some(w) = resp.weather.first() { + println!("Weather: {}", w.main); + println!("Icon: {}", w.icon); + } + println!("Temperature: {}°C", resp.main.temp); + + Ok(()) +}