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
|
/// Views detectors
|
||||||
#[arg(short = 'f', long, default_value_t = false)]
|
#[arg(short = 'f', long, default_value_t = false)]
|
||||||
detector_fetch: bool,
|
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 {
|
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
|
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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
@ -67,6 +252,8 @@ async fn main() {
|
||||||
println!(" delta: {}", args.delta);
|
println!(" delta: {}", args.delta);
|
||||||
println!(" ntfy_topic: {}", args.ntfy_topic);
|
println!(" ntfy_topic: {}", args.ntfy_topic);
|
||||||
println!(" detector_fetch: {}", args.detector_fetch);
|
println!(" detector_fetch: {}", args.detector_fetch);
|
||||||
|
println!(" langs: {:?}", args.langs);
|
||||||
|
println!(" priority: {}", args.priority);
|
||||||
|
|
||||||
CryptoProvider::install_default(rustls_rustcrypto::provider())
|
CryptoProvider::install_default(rustls_rustcrypto::provider())
|
||||||
.expect("install rustcrypto provider");
|
.expect("install rustcrypto provider");
|
||||||
|
|
@ -174,16 +361,28 @@ async fn main() {
|
||||||
let distance_km =
|
let distance_km =
|
||||||
haversine_distance(args.center_lat, args.center_lon, lat, lon);
|
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!(
|
println!(
|
||||||
"Delay: {}ms, offset to now: {}ms, status: {}, region: {} | \n Strike at: {}, {} | Distance to you: {:.2} 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
|
all.delay,
|
||||||
|
offset_ms,
|
||||||
|
all.status,
|
||||||
|
all.region,
|
||||||
|
lon,
|
||||||
|
lat,
|
||||||
|
distance_km,
|
||||||
|
location_name
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut detector_details: Vec<String> = Vec::new();
|
let mut detector_details: Vec<String> = Vec::new();
|
||||||
|
|
||||||
if args.detector_fetch {
|
if args.detector_fetch {
|
||||||
println!("Used detectors:");
|
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() {
|
for feature in detector_features.clone().unwrap().features.iter() {
|
||||||
if sig.sta == feature.properties.station as i64 {
|
if sig.sta == feature.properties.station as i64 {
|
||||||
let detail = format!(
|
let detail = format!(
|
||||||
|
|
@ -213,51 +412,31 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = format!(
|
for lang in &args.langs {
|
||||||
"• Delay: {}ms\n\
|
let lang = lang.trim().to_lowercase();
|
||||||
• Status: {}\n\
|
if lang != "en" && lang != "lv" && lang != "ru" {
|
||||||
• Region: {}\n\
|
continue;
|
||||||
• Location: {:.5}, {:.5}\n\
|
}
|
||||||
• Distance to you: {:.2} km\n\
|
let (message, title) = get_localized_message(
|
||||||
• Used detectors:\n{}\n\
|
&lang,
|
||||||
Stay safe! 🌩️",
|
&all,
|
||||||
all.delay,
|
|
||||||
all.status,
|
|
||||||
all.region,
|
|
||||||
lon,
|
lon,
|
||||||
lat,
|
lat,
|
||||||
distance_km,
|
distance_km,
|
||||||
{
|
&detector_details,
|
||||||
if args.detector_fetch {
|
args.detector_fetch,
|
||||||
let shown = 2;
|
&location_name,
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
let action = get_localized_action(&lang, lon, lat);
|
||||||
|
|
||||||
|
let topic = format!("{}-{}", args.ntfy_topic, lang);
|
||||||
|
|
||||||
let json_payload = serde_json::json!({
|
let json_payload = serde_json::json!({
|
||||||
"topic": args.ntfy_topic,
|
"topic": topic,
|
||||||
"message": message,
|
"message": message,
|
||||||
"title": "⚡ Lightning Strike Detected",
|
"title": title,
|
||||||
"priority": 4,
|
"priority": args.priority,
|
||||||
"actions": [
|
"actions": [action]
|
||||||
{
|
|
||||||
"action": "view",
|
|
||||||
"label": "View in Google Maps",
|
|
||||||
"url": format!("geo:{:.5},{:.5}", lon, lat),
|
|
||||||
"clear": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
|
|
@ -268,8 +447,11 @@ async fn main() {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(_) => println!("Notification sent to ntfy.sh!"),
|
Ok(_) => println!("Notification sent to ntfy.sh topic {}!", topic),
|
||||||
Err(e) => println!("Failed to send notification: {}", e),
|
Err(e) => {
|
||||||
|
println!("Failed to send notification to {}: {}", topic, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print!(
|
print!(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue