From 024956a12b32a103477fad328a649bbdfa697d3e Mon Sep 17 00:00:00 2001 From: Matthew Ryan Dillon Date: Sat, 10 May 2025 18:51:49 -0400 Subject: [PATCH] roughing in pollen --- Cargo.lock | 382 +++++++++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 1 + src/config.rs | 1 + src/main.rs | 71 +++++++-- src/pollen.rs | 79 ++++++++++ src/weather.rs | 64 +++++---- 6 files changed, 534 insertions(+), 64 deletions(-) create mode 100644 src/pollen.rs diff --git a/Cargo.lock b/Cargo.lock index a754e05..ab73076 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,19 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy 0.8.25", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -141,7 +154,7 @@ checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" dependencies = [ "chrono", "chrono-tz-build", - "phf", + "phf 0.11.3", ] [[package]] @@ -151,8 +164,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" dependencies = [ "parse-zoneinfo", - "phf", - "phf_codegen", + "phf 0.11.3", + "phf_codegen 0.11.3", ] [[package]] @@ -171,6 +184,40 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cssparser" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.3", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.101", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "dirs" version = "5.0.1" @@ -200,9 +247,30 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "ego-tree" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -264,6 +332,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -303,6 +381,24 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -359,6 +455,20 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "http" version = "1.3.1" @@ -680,6 +790,26 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + [[package]] name = "memchr" version = "2.7.4" @@ -729,6 +859,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "num-traits" version = "0.2.19" @@ -776,7 +912,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -841,13 +977,33 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_shared", + "phf_macros", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", ] [[package]] @@ -856,8 +1012,18 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand", ] [[package]] @@ -866,17 +1032,39 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand", ] +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher", + "siphasher 1.0.1", ] [[package]] @@ -912,9 +1100,15 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro2" version = "1.0.95" @@ -973,7 +1167,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1229,6 +1423,22 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scraper" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585480e3719b311b78a573db1c9d9c4c1f8010c2dee4cc59c2efe58ea4dbc3e1" +dependencies = [ + "ahash", + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "once_cell", + "selectors", + "tendril", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -1252,6 +1462,25 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "new_debug_unreachable", + "phf 0.10.1", + "phf_codegen 0.10.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + [[package]] name = "serde" version = "1.0.219" @@ -1269,7 +1498,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1305,6 +1534,15 @@ dependencies = [ "serde", ] +[[package]] +name = "servo_arc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1320,6 +1558,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "siphasher" version = "1.0.1" @@ -1357,12 +1601,48 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.101" @@ -1391,7 +1671,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1428,6 +1708,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1454,7 +1745,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1465,7 +1756,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1519,7 +1810,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1650,6 +1941,7 @@ dependencies = [ "chrono-tz", "dirs", "reqwest", + "scraper", "serde", "serde_json", "tokio", @@ -1668,6 +1960,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "untrusted" version = "0.9.0" @@ -1685,6 +1983,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -1697,6 +2001,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.1" @@ -1743,7 +2053,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -1778,7 +2088,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1851,7 +2161,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -1862,7 +2172,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] [[package]] @@ -2165,7 +2475,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "synstructure", ] @@ -2176,7 +2486,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive 0.8.25", ] [[package]] @@ -2187,7 +2506,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] @@ -2207,7 +2537,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", "synstructure", ] @@ -2247,5 +2577,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.101", ] diff --git a/Cargo.toml b/Cargo.toml index 43c99d4..36d625c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ chrono = "0.4" serde_json = "1.0" chrono-tz = "0.8" tokio = { version = "1.37", features = ["full"] } +scraper = "0.18" diff --git a/src/config.rs b/src/config.rs index 1276ee2..7b4cd9d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,6 +13,7 @@ pub struct Config { pub calendar_event_count: usize, pub location: String, pub timezone: String, + pub pollen_zip: String, } impl Config { diff --git a/src/main.rs b/src/main.rs index 4e2043d..0303101 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ mod config; +mod pollen; mod weather; -use chrono::{TimeZone, Utc}; +use chrono::{TimeZone, Utc, DateTime}; use chrono_tz::Tz; use config::Config; -use weather::{fetch_weather, WeatherSummary}; +use pollen::fetch_pollen_api; +use weather::{fetch_weather, geocode_city, WeatherSummary}; #[tokio::main] async fn main() { @@ -24,7 +26,15 @@ async fn main() { None } }; - if let Some(w) = weather { + let (_lat, _lon) = if weather.is_some() { + match geocode_city(&config.location, &config.weather_api_key).await { + Ok((lat, lon)) => (lat, lon), + Err(_) => (0.0, 0.0), + } + } else { + (0.0, 0.0) + }; + if let Some(w) = &weather { if w.obs_time_unix > 0 { match config.timezone.parse::() { Ok(tz) => { @@ -54,8 +64,50 @@ async fn main() { println!("Weather: N/A"); } - let pollen = get_pollen_count(&config); - println!("\nPollen count: {pollen}"); + match fetch_pollen_api(&config.pollen_zip).await { + Ok(p) => { + // Format pollen forecast date to match weather, in config.timezone + if let Ok(dt) = DateTime::parse_from_rfc3339(&p.forecast_date) { + match config.timezone.parse::() { + Ok(tz) => { + let local_time = dt.with_timezone(&tz); + println!( + "\nPollen.com ({})\n Forecast for {}:", + p.location, + local_time.format("%Y-%m-%d %H:%M:%S %Z") + ); + } + Err(_) => { + println!( + "\nPollen.com ({})\n Forecast for {} (timezone parse error):", + p.location, p.forecast_date + ); + } + } + } else { + println!( + "\nPollen.com ({})\n Forecast for {}:", + p.location, p.forecast_date + ); + } + + for day in &["Yesterday", "Today", "Tomorrow"] { + if let Some(period) = p.periods.get(*day) { + println!( + " {:9}: {:>4.1} ({})", + day, + period.index, + period.triggers.join(", ") + ); + } else { + println!(" {:9}: N/A", day); + } + } + } + Err(e) => { + println!("\nFailed to fetch pollen (API): {e}"); + } + } println!(); println!("Upcoming calendar events:"); @@ -72,14 +124,8 @@ async fn main() { } } -// Placeholder stubs, replace with actual API calls later -fn get_pollen_count(_config: &Config) -> String { - // TODO: Call pollen count API - "Medium (Tree: 5, Grass: 2, Weed: 1)".to_string() -} - +// Placeholder stubs fn get_calendar_events(_config: &Config, n: usize) -> Vec { - // TODO: Call calendar API vec![ "Team meeting at 10:00 AM".to_string(), "Lunch with Alex at 12:30 PM".to_string(), @@ -91,7 +137,6 @@ fn get_calendar_events(_config: &Config, n: usize) -> Vec { } fn get_shopify_packages(_config: &Config) -> Vec { - // TODO: Call Shopify API vec![ "Order #1234: Shipped - Arriving May 13".to_string(), "Order #5678: In transit - Arriving May 15".to_string(), diff --git a/src/pollen.rs b/src/pollen.rs new file mode 100644 index 0000000..1f2f916 --- /dev/null +++ b/src/pollen.rs @@ -0,0 +1,79 @@ +use serde::Deserialize; +use std::collections::HashMap; +use std::error::Error; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct PollenApiResponse { + forecast_date: String, + location: Option, +} + +#[derive(Debug, Deserialize)] +struct Location { + periods: Vec, + #[serde(rename = "DisplayLocation")] + display_location: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct Period { + r#type: String, + index: f32, + triggers: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct Trigger { + name: String, + #[allow(dead_code)] + plant_type: String, + #[allow(dead_code)] + genus: String, +} + +pub struct PollenPeriod { + pub index: f32, + pub triggers: Vec, +} + +pub struct PollenSummary { + pub location: String, + pub forecast_date: String, + pub periods: HashMap, // "Yesterday", "Today", "Tomorrow" +} + +pub async fn fetch_pollen_api(zip: &str) -> Result> { + let url = format!("https://www.pollen.com/api/forecast/current/pollen/{}", zip); + let resp = reqwest::Client::new() + .get(&url) + .header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36") + .header("Accept", "application/json, text/plain, */*") + .header("Referer", &url) + .header("Cookie", format!("geo={}", zip)) + .send().await? + .text().await?; + + let api: PollenApiResponse = serde_json::from_str(&resp)?; + let loc = api.location.ok_or("No location in pollen.com response")?; + let mut periods = HashMap::new(); + + for period in &loc.periods { + let triggers: Vec = period.triggers.iter().map(|t| t.name.clone()).collect(); + periods.insert( + period.r#type.clone(), + PollenPeriod { + index: period.index, + triggers, + }, + ); + } + + Ok(PollenSummary { + location: loc.display_location, + forecast_date: api.forecast_date, + periods, + }) +} diff --git a/src/weather.rs b/src/weather.rs index a175445..05c0dbb 100644 --- a/src/weather.rs +++ b/src/weather.rs @@ -1,12 +1,47 @@ use serde::Deserialize; use std::error::Error; +#[allow(dead_code)] #[derive(Debug, Deserialize)] struct GeocodeResponse { lat: f64, lon: f64, + #[allow(dead_code)] + name: String, + #[allow(dead_code)] + country: String, } +// returns (lat, lon) for specified city name +pub async fn geocode_city(city: &str, api_key: &str) -> Result<(f64, f64), Box> { + 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); + match geo_parsed { + Ok(geo_vec) => { + if geo_vec.is_empty() { + return Err(format!( + "No geocoding result for city: {} (response: {})", + city, geo_text + ) + .into()); + } + Ok((geo_vec[0].lat, geo_vec[0].lon)) + } + Err(e) => { + eprintln!("Failed to decode geocoding response. Raw response: {geo_text}"); + Err(Box::new(e)) + } + } +} +// ------------------ WEATHER ------------------------- + #[derive(Debug, Deserialize)] pub struct WeatherCurrent { pub temp: f64, @@ -46,33 +81,11 @@ pub struct WeatherSummary { pub obs_time_unix: i64, } +/// Returns WeatherSummary struct pub async fn fetch_weather(city: &str, api_key: &str) -> Result> { + let (lat, lon) = geocode_city(city, api_key).await?; 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)); - } - }; - + // Get weather data from One Call 3.0 let onecall_url = format!( "https://api.openweathermap.org/data/3.0/onecall?lat={}&lon={}&appid={}&units=imperial", lat, lon, api_key @@ -92,6 +105,7 @@ pub async fn fetch_weather(city: &str, api_key: &str) -> Result