feat: locale support, and get rough location using nominatim aswell
This commit is contained in:
parent
10cde9881d
commit
a7aa030e75
1 changed files with 240 additions and 58 deletions
268
src/main.rs
268
src/main.rs
|
|
@ -37,6 +37,20 @@ struct Args {
|
|||
/// Views detectors
|
||||
#[arg(short = 'f', long, default_value_t = false)]
|
||||
detector_fetch: bool,
|
||||
|
||||
/// Notification languages, comma separated (e.g. "en,lv,ru")
|
||||
#[arg(long = "lang", value_delimiter = ',', default_value = "en")]
|
||||
langs: Vec<String>,
|
||||
|
||||
/// Priority in which the Ntfy notifications are sent.
|
||||
/// Possible values:
|
||||
/// 5 - max/urgent: Really long vibration bursts, default notification sound with a pop-over notification.
|
||||
/// 4 - high: Long vibration burst, default notification sound with a pop-over notification.
|
||||
/// 3 - default: Short default vibration and sound. Default notification behavior.
|
||||
/// 2 - low: No vibration or sound. Notification will not visibly show up until notification drawer is pulled down.
|
||||
/// 1 - min: No vibration or sound. The notification will be under the fold in "Other notifications".
|
||||
#[arg(short = 'p', long, default_value_t = 3)]
|
||||
priority: i32,
|
||||
}
|
||||
|
||||
fn in_bounds(lat: f64, lon: f64, center_lat: f64, center_lon: f64, delta: f64) -> bool {
|
||||
|
|
@ -58,6 +72,177 @@ fn haversine_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
|
|||
r * c
|
||||
}
|
||||
|
||||
async fn get_location_name(lat: f64, lon: f64) -> Option<String> {
|
||||
let url = format!(
|
||||
"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={:.5}&lon={:.5}",
|
||||
lat, lon
|
||||
);
|
||||
let client = reqwest::Client::new();
|
||||
let res = client
|
||||
.get(&url)
|
||||
.header("User-Agent", "blitzortung-lightning-alert/1.0")
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(resp) => {
|
||||
let json: serde_json::Value =
|
||||
match serde_json::from_str(&resp.text().await.unwrap_or_default()) {
|
||||
Ok(j) => j,
|
||||
Err(_) => return None,
|
||||
};
|
||||
// Try to get display_name, fallback to address if available
|
||||
if let Some(name) = json.get("display_name").and_then(|v| v.as_str()) {
|
||||
Some(name.to_string())
|
||||
} else if let Some(addr) = json.get("address") {
|
||||
Some(addr.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_localized_message(
|
||||
lang: &str,
|
||||
all: &TungMessage,
|
||||
lon: f64,
|
||||
lat: f64,
|
||||
distance_km: f64,
|
||||
detector_details: &[String],
|
||||
detector_fetch: bool,
|
||||
location_name: &str,
|
||||
) -> (String, String) {
|
||||
match lang {
|
||||
"lv" => {
|
||||
let detectors_str = if detector_fetch {
|
||||
let shown = 2;
|
||||
let total = detector_details.len();
|
||||
let mut lines = Vec::new();
|
||||
for d in detector_details.iter().take(shown) {
|
||||
lines.push(format!(" - {}", d));
|
||||
}
|
||||
if total > shown {
|
||||
lines.push(format!(" (+{} pārējie..)", total - shown));
|
||||
}
|
||||
lines.join("\n")
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
(
|
||||
format!(
|
||||
"• Kavēšanās: {}ms\n\
|
||||
• Statuss: {}\n\
|
||||
• Reģions: {}\n\
|
||||
• Atrašanās vieta: {:.5}, {:.5}\n\
|
||||
• Aptuvenā vieta: {}\n\
|
||||
• Attālums līdz jums: {:.2} km\n\
|
||||
• Izmantotie detektori:\n{}\n\
|
||||
Esiet drošībā! 🌩️",
|
||||
all.delay,
|
||||
all.status,
|
||||
all.region,
|
||||
lon,
|
||||
lat,
|
||||
location_name,
|
||||
distance_km,
|
||||
detectors_str
|
||||
),
|
||||
"⚡ Zibens trieciens konstatēts".to_string(),
|
||||
)
|
||||
}
|
||||
"ru" => {
|
||||
let detectors_str = if detector_fetch {
|
||||
let shown = 2;
|
||||
let total = detector_details.len();
|
||||
let mut lines = Vec::new();
|
||||
for d in detector_details.iter().take(shown) {
|
||||
lines.push(format!(" - {}", d));
|
||||
}
|
||||
if total > shown {
|
||||
lines.push(format!(" (+{} других..)", total - shown));
|
||||
}
|
||||
lines.join("\n")
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
(
|
||||
format!(
|
||||
"• Задержка: {}мс\n\
|
||||
• Статус: {}\n\
|
||||
• Регион: {}\n\
|
||||
• Местоположение: {:.5}, {:.5}\n\
|
||||
• Примерное место: {}\n\
|
||||
• Расстояние до вас: {:.2} км\n\
|
||||
• Использованные детекторы:\n{}\n\
|
||||
Будьте осторожны! 🌩️",
|
||||
all.delay,
|
||||
all.status,
|
||||
all.region,
|
||||
lon,
|
||||
lat,
|
||||
location_name,
|
||||
distance_km,
|
||||
detectors_str
|
||||
),
|
||||
"⚡ Обнаружен удар молнии".to_string(),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let detectors_str = if detector_fetch {
|
||||
let shown = 2;
|
||||
let total = detector_details.len();
|
||||
let mut lines = Vec::new();
|
||||
for d in detector_details.iter().take(shown) {
|
||||
lines.push(format!(" - {}", d));
|
||||
}
|
||||
if total > shown {
|
||||
lines.push(format!(" (+{} others..)", total - shown));
|
||||
}
|
||||
lines.join("\n")
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
(
|
||||
format!(
|
||||
"• Delay: {}ms\n\
|
||||
• Status: {}\n\
|
||||
• Region: {}\n\
|
||||
• Location: {:.5}, {:.5}\n\
|
||||
• Approximate place: {}\n\
|
||||
• Distance to you: {:.2} km\n\
|
||||
• Used detectors:\n{}\n\
|
||||
Stay safe! 🌩️",
|
||||
all.delay,
|
||||
all.status,
|
||||
all.region,
|
||||
lon,
|
||||
lat,
|
||||
location_name,
|
||||
distance_km,
|
||||
detectors_str
|
||||
),
|
||||
"⚡ Lightning Strike Detected".to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_localized_action(lang: &str, lon: f64, lat: f64) -> serde_json::Value {
|
||||
let label = match lang {
|
||||
"lv" => "Skatīt Google Maps",
|
||||
"ru" => "Открыть в Google Maps",
|
||||
_ => "View in Google Maps",
|
||||
};
|
||||
serde_json::json!({
|
||||
"action": "view",
|
||||
"label": label,
|
||||
"url": format!("geo:{:.5},{:.5}", lon, lat),
|
||||
"clear": false
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = Args::parse();
|
||||
|
|
@ -67,6 +252,8 @@ async fn main() {
|
|||
println!(" delta: {}", args.delta);
|
||||
println!(" ntfy_topic: {}", args.ntfy_topic);
|
||||
println!(" detector_fetch: {}", args.detector_fetch);
|
||||
println!(" langs: {:?}", args.langs);
|
||||
println!(" priority: {}", args.priority);
|
||||
|
||||
CryptoProvider::install_default(rustls_rustcrypto::provider())
|
||||
.expect("install rustcrypto provider");
|
||||
|
|
@ -174,16 +361,28 @@ async fn main() {
|
|||
let distance_km =
|
||||
haversine_distance(args.center_lat, args.center_lon, lat, lon);
|
||||
|
||||
let location_name = match get_location_name(lat, lon).await {
|
||||
Some(name) => name,
|
||||
None => "Unknown".to_string(),
|
||||
};
|
||||
|
||||
println!(
|
||||
"Delay: {}ms, offset to now: {}ms, status: {}, region: {} | \n Strike at: {}, {} | Distance to you: {:.2} km",
|
||||
all.delay, offset_ms, all.status, all.region, lon, lat, distance_km
|
||||
"Delay: {}ms, offset to now: {}ms, status: {}, region: {} | \n Strike at: {}, {} | Distance to you: {:.2} km | Location: {}",
|
||||
all.delay,
|
||||
offset_ms,
|
||||
all.status,
|
||||
all.region,
|
||||
lon,
|
||||
lat,
|
||||
distance_km,
|
||||
location_name
|
||||
);
|
||||
|
||||
let mut detector_details: Vec<String> = Vec::new();
|
||||
|
||||
if args.detector_fetch {
|
||||
println!("Used detectors:");
|
||||
for sig in all.sig.into_iter() {
|
||||
for sig in all.sig.clone().into_iter() {
|
||||
for feature in detector_features.clone().unwrap().features.iter() {
|
||||
if sig.sta == feature.properties.station as i64 {
|
||||
let detail = format!(
|
||||
|
|
@ -213,51 +412,31 @@ async fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
let message = format!(
|
||||
"• Delay: {}ms\n\
|
||||
• Status: {}\n\
|
||||
• Region: {}\n\
|
||||
• Location: {:.5}, {:.5}\n\
|
||||
• Distance to you: {:.2} km\n\
|
||||
• Used detectors:\n{}\n\
|
||||
Stay safe! 🌩️",
|
||||
all.delay,
|
||||
all.status,
|
||||
all.region,
|
||||
for lang in &args.langs {
|
||||
let lang = lang.trim().to_lowercase();
|
||||
if lang != "en" && lang != "lv" && lang != "ru" {
|
||||
continue;
|
||||
}
|
||||
let (message, title) = get_localized_message(
|
||||
&lang,
|
||||
&all,
|
||||
lon,
|
||||
lat,
|
||||
distance_km,
|
||||
{
|
||||
if args.detector_fetch {
|
||||
let shown = 2;
|
||||
let total = detector_details.len();
|
||||
let mut lines = Vec::new();
|
||||
for d in detector_details.iter().take(shown) {
|
||||
lines.push(format!(" - {}", d));
|
||||
}
|
||||
if total > shown {
|
||||
lines.push(format!(" (+{} others..)", total - shown));
|
||||
}
|
||||
lines.join("\n")
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
&detector_details,
|
||||
args.detector_fetch,
|
||||
&location_name,
|
||||
);
|
||||
let action = get_localized_action(&lang, lon, lat);
|
||||
|
||||
let topic = format!("{}-{}", args.ntfy_topic, lang);
|
||||
|
||||
let json_payload = serde_json::json!({
|
||||
"topic": args.ntfy_topic,
|
||||
"topic": topic,
|
||||
"message": message,
|
||||
"title": "⚡ Lightning Strike Detected",
|
||||
"priority": 4,
|
||||
"actions": [
|
||||
{
|
||||
"action": "view",
|
||||
"label": "View in Google Maps",
|
||||
"url": format!("geo:{:.5},{:.5}", lon, lat),
|
||||
"clear": false
|
||||
}
|
||||
]
|
||||
"title": title,
|
||||
"priority": args.priority,
|
||||
"actions": [action]
|
||||
});
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
|
@ -268,8 +447,11 @@ async fn main() {
|
|||
.await;
|
||||
|
||||
match res {
|
||||
Ok(_) => println!("Notification sent to ntfy.sh!"),
|
||||
Err(e) => println!("Failed to send notification: {}", e),
|
||||
Ok(_) => println!("Notification sent to ntfy.sh topic {}!", topic),
|
||||
Err(e) => {
|
||||
println!("Failed to send notification to {}: {}", topic, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue