use std::sync::OnceLock;
use serde::Deserialize;
use tracing::warn;
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct DirectApisConfig {
pub weather: WeatherApis,
pub crypto: CryptoApis,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct WeatherApis {
pub weather_cn: WeatherCnConfig,
pub openmeteo: UrlOnly,
pub wttr: UrlOnly,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct WeatherCnConfig {
pub city_search_url: String,
pub calendar_url: String,
pub js_var_name: String,
pub referer: String,
pub cityid_prefix: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct CryptoApis {
pub coingecko: UrlOnly,
pub cryptocompare: UrlOnly,
pub jinse: JinseConfig,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct UrlOnly {
pub url: String,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct JinseConfig {
pub news_url: String,
}
impl Default for WeatherCnConfig {
fn default() -> Self {
Self {
city_search_url: "https://toy1.weather.com.cn/search?cityname={name}".into(),
calendar_url:
"https://d1.weather.com.cn/calendar_new/{year}/{cityid}_{yyyymm}.html?_={now_ms}"
.into(),
js_var_name: "fc40".into(),
referer: "https://www.weather.com.cn/".into(),
cityid_prefix: "10".into(),
}
}
}
impl Default for WeatherApis {
fn default() -> Self {
Self {
weather_cn: WeatherCnConfig::default(),
openmeteo: UrlOnly {
url: "https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}\
&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,weathercode\
&forecast_days=7&timezone=Asia/Shanghai"
.into(),
},
wttr: UrlOnly {
url: "https://wttr.in/{location}?format=j1".into(),
},
}
}
}
impl Default for CryptoApis {
fn default() -> Self {
Self {
coingecko: UrlOnly {
url: "https://api.coingecko.com/api/v3/simple/price?\
ids={coin}&vs_currencies=usd,cny"
.into(),
},
cryptocompare: UrlOnly {
url: "https://min-api.cryptocompare.com/data/price?fsym={symbol}&tsyms=USD,CNY"
.into(),
},
jinse: JinseConfig {
news_url:
"https://api.jinse2.com/v6/information/list?catelogue_key=news&limit={limit}"
.into(),
},
}
}
}
impl Default for DirectApisConfig {
fn default() -> Self {
Self {
weather: WeatherApis::default(),
crypto: CryptoApis::default(),
}
}
}
#[derive(Debug, Deserialize, Default)]
struct DefaultsToml {
#[serde(default)]
direct_apis: DirectApisConfig,
}
impl DirectApisConfig {
pub fn load() -> Self {
let toml_text = rsclaw_config::loader::load_defaults_toml();
match toml::from_str::<DefaultsToml>(&toml_text) {
Ok(d) => d.direct_apis,
Err(e) => {
warn!(error = %e, "defaults.toml: direct_apis parse failed, using built-in URLs");
Self::default()
}
}
}
}
static DIRECT_APIS: OnceLock<DirectApisConfig> = OnceLock::new();
pub fn config() -> &'static DirectApisConfig {
DIRECT_APIS.get_or_init(DirectApisConfig::load)
}