Skip to main content

sioul_sync/
weather.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2// Copyright © 2026 Aurélien Pierre
3
4//! The weather and its places, asked of Open-Meteo: a place's coordinates
5//! (two decimals, about a kilometre) and nothing else. No key, no account.
6
7use crate::SyncError;
8use sioul_core::weather::{self, Forecast, Place};
9use std::time::Duration;
10
11fn get(url: &str) -> Result<String, SyncError> {
12    let agent: ureq::Agent = ureq::Agent::config_builder().timeout_global(Some(Duration::from_secs(20))).http_status_as_error(false).build().into();
13    let mut response = agent
14        .get(url)
15        .header("User-Agent", concat!("Sioul/", env!("CARGO_PKG_VERSION"), " (desktop mail and contacts client)"))
16        .call()
17        .map_err(|e| SyncError::Network(e.to_string()))?;
18    let status = response.status().as_u16();
19    let text = response.body_mut().read_to_string().unwrap_or_default();
20    if status != 200 {
21        return Err(SyncError::Server(format!("Open-Meteo: {status}")));
22    }
23    Ok(text)
24}
25
26/// The forecast at a place, from this hour on.
27pub fn forecast(latitude: f64, longitude: f64, now: i64) -> Result<Forecast, SyncError> {
28    weather::parse(&get(&weather::query(latitude, longitude))?, now).map_err(SyncError::Server)
29}
30
31/// Places by name, in your language.
32pub fn places(name: &str, language: &str) -> Result<Vec<Place>, SyncError> {
33    weather::parse_places(&get(&weather::place_query(name, language))?).map_err(SyncError::Server)
34}