diff --git a/Cargo.lock b/Cargo.lock index 3ba1f52..a754e05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -77,6 +86,12 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -98,6 +113,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.41" @@ -112,6 +133,28 @@ dependencies = [ "windows-link", ] +[[package]] +name = "chrono-tz" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -228,7 +271,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -237,12 +279,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - [[package]] name = "futures-sink" version = "0.3.31" @@ -262,13 +298,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", - "futures-io", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "slab", ] [[package]] @@ -278,8 +310,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -400,6 +434,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots 0.26.11", ] [[package]] @@ -629,6 +664,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.27" @@ -758,12 +803,82 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -791,6 +906,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -800,6 +924,58 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom 0.2.16", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.40" @@ -815,6 +991,45 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -823,9 +1038,38 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror", + "thiserror 1.0.69", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "reqwest" version = "0.12.15" @@ -835,7 +1079,6 @@ dependencies = [ "base64", "bytes", "encoding_rs", - "futures-channel", "futures-core", "futures-util", "h2", @@ -854,7 +1097,10 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", @@ -862,12 +1108,14 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.26.11", "windows-registry", ] @@ -891,6 +1139,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustix" version = "1.0.7" @@ -911,6 +1165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -932,6 +1187,7 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ + "web-time", "zeroize", ] @@ -967,6 +1223,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "2.11.1" @@ -1049,6 +1311,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -1157,7 +1434,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] @@ -1171,6 +1457,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -1181,6 +1478,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.45.0" @@ -1191,11 +1503,25 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -1321,9 +1647,12 @@ name = "trmnl" version = "0.1.0" dependencies = [ "chrono", + "chrono-tz", "dirs", "reqwest", "serde", + "serde_json", + "tokio", "toml", ] @@ -1473,6 +1802,34 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + +[[package]] +name = "webpki-roots" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "windows-core" version = "0.61.0" @@ -1812,6 +2169,27 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 4b725e9..43c99d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,10 @@ edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } toml = "0.8" -reqwest = { version = "0.12", features = ["json", "blocking"] } +# Only keep one of blocking+async if all features converted +reqwest = { version = "0.12", features = ["json", "rustls-tls" ] } dirs = "5" chrono = "0.4" +serde_json = "1.0" +chrono-tz = "0.8" +tokio = { version = "1.37", features = ["full"] } diff --git a/src/config.rs b/src/config.rs index d95a9f0..1276ee2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,7 @@ pub struct Config { pub shopify_store: String, pub calendar_event_count: usize, pub location: String, + pub timezone: String, } impl Config { diff --git a/src/main.rs b/src/main.rs index bb25913..4e2043d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,13 @@ mod config; +mod weather; +use chrono::{TimeZone, Utc}; +use chrono_tz::Tz; use config::Config; +use weather::{fetch_weather, WeatherSummary}; -fn main() { - // Load config +#[tokio::main] +async fn main() { let config = match Config::load() { Ok(cfg) => cfg, Err(e) => { @@ -12,27 +16,56 @@ fn main() { } }; - // Weather - let (temp, conditions, high, low, forecast) = get_weather(&config); - println!("Weather for today:"); - println!(" - Temperature: {temp}"); - println!(" - Conditions: {conditions}"); - println!(" - High: {high}, Low: {low}"); - println!(" - Forecast: {forecast}"); + let weather: Option = + match fetch_weather(&config.location, &config.weather_api_key).await { + Ok(data) => Some(data), + Err(e) => { + eprintln!("Failed to fetch weather: {e}"); + None + } + }; + if let Some(w) = weather { + if w.obs_time_unix > 0 { + match config.timezone.parse::() { + Ok(tz) => { + let dt_local = tz.from_utc_datetime( + &Utc.timestamp_opt(w.obs_time_unix, 0).unwrap().naive_utc(), + ); + println!( + "Weather (as of {}):", + dt_local.format("%Y-%m-%d %H:%M:%S %Z") + ); + } + Err(_) => { + println!( + "Weather (as of unix timestamp {}) (timezone parse error)", + w.obs_time_unix + ); + } + } + } else { + println!("Weather:"); + } + println!(" - Temperature: {}", w.temp); + println!(" - Conditions: {}", w.current_desc); + println!(" - High: {}, Low: {}", w.high, w.low); + println!(" - Forecast: {}", w.daily_desc); + } else { + println!("Weather: N/A"); + } - // Pollen count let pollen = get_pollen_count(&config); println!("\nPollen count: {pollen}"); - // Calendar events - println!("\nUpcoming calendar events:"); + println!(); + println!("Upcoming calendar events:"); let events = get_calendar_events(&config, config.calendar_event_count); for (i, event) in events.iter().enumerate() { println!("{}. {}", i + 1, event); } - // Shopify inbound packages - println!("\nInbound Shopify packages:"); + println!(); + println!("Inbound Shopify packages:"); let packages = get_shopify_packages(&config); for (i, pkg) in packages.iter().enumerate() { println!("{}. {}", i + 1, pkg); @@ -40,17 +73,6 @@ fn main() { } // Placeholder stubs, replace with actual API calls later -fn get_weather(_config: &Config) -> (String, String, String, String, String) { - // TODO: Call weather API - ( - "72°F".to_string(), - "Partly cloudy".to_string(), - "78°F".to_string(), - "65°F".to_string(), - "Mostly sunny, light breeze".to_string(), - ) -} - fn get_pollen_count(_config: &Config) -> String { // TODO: Call pollen count API "Medium (Tree: 5, Grass: 2, Weed: 1)".to_string() diff --git a/src/weather.rs b/src/weather.rs new file mode 100644 index 0000000..a175445 --- /dev/null +++ b/src/weather.rs @@ -0,0 +1,126 @@ +use serde::Deserialize; +use std::error::Error; + +#[derive(Debug, Deserialize)] +struct GeocodeResponse { + lat: f64, + lon: f64, +} + +#[derive(Debug, Deserialize)] +pub struct WeatherCurrent { + pub temp: f64, + pub weather: Vec, + pub dt: i64, // Unix timestamp +} + +#[derive(Debug, Deserialize)] +pub struct WeatherDailyTemp { + pub min: f64, + pub max: f64, +} + +#[derive(Debug, Deserialize)] +pub struct WeatherDesc { + pub description: String, +} + +#[derive(Debug, Deserialize)] +pub struct WeatherDaily { + pub temp: WeatherDailyTemp, + pub weather: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct OneCallResult { + pub current: WeatherCurrent, + pub daily: Vec, +} + +pub struct WeatherSummary { + pub temp: String, + pub current_desc: String, + pub high: String, + pub low: String, + pub daily_desc: String, + pub obs_time_unix: i64, +} + +pub async fn fetch_weather(city: &str, api_key: &str) -> Result> { + let client = reqwest::Client::new(); + let geocode_url = format!( + "https://api.openweathermap.org/geo/1.0/direct?q={}&limit=1&appid={}", + city, api_key + ); + let geo_resp = client.get(&geocode_url).send().await?; + let geo_text = geo_resp.text().await?; + let geo_parsed: Result, serde_json::Error> = + serde_json::from_str(&geo_text); + let (lat, lon) = match geo_parsed { + Ok(geo_vec) => { + if geo_vec.is_empty() { + return Err(format!( + "No geocoding result for city: {} (response: {})", + city, geo_text + ) + .into()); + } + (geo_vec[0].lat, geo_vec[0].lon) + } + Err(e) => { + eprintln!("Failed to decode geocoding response. Raw response: {geo_text}"); + return Err(Box::new(e)); + } + }; + + let onecall_url = format!( + "https://api.openweathermap.org/data/3.0/onecall?lat={}&lon={}&appid={}&units=imperial", + lat, lon, api_key + ); + let one_resp = client.get(&onecall_url).send().await?; + let one_status = one_resp.status(); + let one_text = one_resp.text().await?; + if !one_status.is_success() { + return Err(format!("HTTP error {}: {}", one_status, one_text).into()); + } + + let one_parsed: OneCallResult = match serde_json::from_str(&one_text) { + Ok(data) => data, + Err(e) => { + eprintln!("Failed to decode One Call weather response. Raw response: {one_text}"); + return Err(Box::new(e)); + } + }; + + let temp = format!("{:.1}°F", one_parsed.current.temp); + let current_desc = one_parsed + .current + .weather + .first() + .map(|w| w.description.clone()) + .unwrap_or_else(|| "N/A".to_string()); + let current_dt = one_parsed.current.dt; // UNIX timestamp, UTC + + // Today's forecast is daily[0] + let (high, low, daily_desc) = if let Some(today) = one_parsed.daily.first() { + let high = format!("{:.1}°F", today.temp.max); + let low = format!("{:.1}°F", today.temp.min); + let desc = today + .weather + .first() + .map(|w| w.description.clone()) + .unwrap_or_else(|| "N/A".to_string()); + (high, low, desc) + } else { + ("N/A".to_string(), "N/A".to_string(), "N/A".to_string()) + }; + + Ok(WeatherSummary { + temp, + current_desc, + high, + low, + daily_desc, + obs_time_unix: current_dt, + }) +}