From 3586e8e901453fc76d45bf50dce4858191cc4b8d Mon Sep 17 00:00:00 2001 From: Tesso M Costa Date: Sun, 14 Jun 2026 10:41:41 -0600 Subject: [PATCH] feat(path): add total path distance display Sum the haversine distance from sender through each repeater to the bot and append it to the path response. Silently omitted when any node in the chain has unknown coordinates. Controlled by show_path_distance config flag (default: true). Add distance_traveled translation key for all 10 supported locales. --- config.ini.example | 4 +++ modules/commands/path_command.py | 42 ++++++++++++++++++++++++++++++++ translations/de.json | 3 ++- translations/en-GB.json | 3 ++- translations/en.json | 3 ++- translations/es.json | 3 ++- translations/fr-CA.json | 3 ++- translations/fr.json | 3 ++- translations/nl.json | 3 ++- translations/pl.json | 3 ++- translations/pt-BR.json | 3 ++- translations/pt.json | 3 ++- 12 files changed, 66 insertions(+), 10 deletions(-) diff --git a/config.ini.example b/config.ini.example index efe2d6fd..389168db 100644 --- a/config.ini.example +++ b/config.ini.example @@ -885,6 +885,10 @@ require_path_bytes_greater_or_equal_to = 0 # Optional response when rejected by path-byte requirement; leave empty for silent reject require_path_bytes_failure_response = +# Append total distance traveled (sender → repeaters → bot) to path responses (default: true) +# Silently omitted when any repeater in the path has no known coordinates. +show_path_distance = true + # Enable "p" shortcut for path command (similar to "t" for test command) # true: Respond to just "p" or "p " as a shortcut for "path" (default) # false: Only respond to "path", "decode", or "route" keywords diff --git a/modules/commands/path_command.py b/modules/commands/path_command.py index e76fb1e0..95f7cbae 100644 --- a/modules/commands/path_command.py +++ b/modules/commands/path_command.py @@ -132,6 +132,10 @@ def __init__(self, bot): self.medium_confidence_symbol = bot.config.get('Path_Command', 'medium_confidence_symbol', fallback='📍') self.low_confidence_symbol = bot.config.get('Path_Command', 'low_confidence_symbol', fallback='❓') + self.show_path_distance = self.get_config_value( + 'Path_Command', 'show_path_distance', fallback=True, value_type='bool' + ) + # Check if "p" shortcut is enabled (on by default) self.enable_p_shortcut = bot.config.getboolean('Path_Command', 'enable_p_shortcut', fallback=True) if self.enable_p_shortcut: @@ -1709,6 +1713,11 @@ def _format_path_response(self, node_ids: list[str], repeater_info: dict[str, di lines.append(line) + if self.show_path_distance: + km, missing = self._calculate_full_path_km(node_ids) + if km is not None and missing == 0: + lines.append(self.translate('commands.path.distance_traveled', km=km)) + # Return all lines - let _send_path_response handle the splitting return "\n".join(lines) @@ -1819,6 +1828,39 @@ def _truncate_to_byte_length(self, text: str, max_bytes: int, ellipsis: str = ". truncated: str = text_bytes[:available].decode('utf-8', errors='ignore') return truncated + ellipsis + def _calculate_full_path_km(self, node_ids: list[str]) -> tuple[Optional[float], int]: + """Sum distance sender → repeaters → bot. Returns (km, missing_count). + + missing_count is the number of node_ids with no location in the DB. + km is None when fewer than 2 waypoints have known coordinates. + """ + waypoints: list[tuple[float, float]] = [] + missing = 0 + + sender_loc = self._get_sender_location() + if sender_loc: + waypoints.append(sender_loc) + + for node_id in node_ids: + loc = self._get_node_location(node_id) + if loc: + waypoints.append(loc) + else: + missing += 1 + + if self.bot_latitude is not None and self.bot_longitude is not None: + waypoints.append((self.bot_latitude, self.bot_longitude)) + + if len(waypoints) < 2: + return None, missing + + total = sum( + calculate_distance(waypoints[i][0], waypoints[i][1], + waypoints[i + 1][0], waypoints[i + 1][1]) + for i in range(len(waypoints) - 1) + ) + return round(total, 1), missing + def get_help(self) -> str: """Get help text for the path command""" return self.translate('commands.path.help') diff --git a/translations/de.json b/translations/de.json index 9823728a..cf75335e 100644 --- a/translations/de.json +++ b/translations/de.json @@ -358,7 +358,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Unbekannt", - "repeater_resolution_deferred": "Pfad: {path}\n\n💡 Hinweis: Repeater-Erkennung erfordert Pfade mit {minimum_path_bytes} Byte(s) pro Hop." + "repeater_resolution_deferred": "Pfad: {path}\n\n💡 Hinweis: Repeater-Erkennung erfordert Pfade mit {minimum_path_bytes} Byte(s) pro Hop.", + "distance_traveled": "📏 {km} km zurückgelegt" }, "prefix": { "description": "Repeater nach zweistelligem Präfix suchen (z.B. 'prefix 1A')", diff --git a/translations/en-GB.json b/translations/en-GB.json index 33d24aed..f481cd3b 100644 --- a/translations/en-GB.json +++ b/translations/en-GB.json @@ -338,7 +338,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Unknown", - "repeater_resolution_deferred": "Path: {path}\n\n💡 Tip: Repeater identification requires {minimum_path_bytes}-byte paths." + "repeater_resolution_deferred": "Path: {path}\n\n💡 Tip: Repeater identification requires {minimum_path_bytes}-byte paths.", + "distance_traveled": "📏 {km} km travelled" }, "prefix": { "description": "Look up repeaters by two-character prefix (e.g., 'prefix 1A')", diff --git a/translations/en.json b/translations/en.json index 07c7f587..b6d7f589 100644 --- a/translations/en.json +++ b/translations/en.json @@ -272,7 +272,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Unknown", - "repeater_resolution_deferred": "Path: {path}\n\n💡 Tip: Repeater identification requires {minimum_path_bytes}-byte paths." + "repeater_resolution_deferred": "Path: {path}\n\n💡 Tip: Repeater identification requires {minimum_path_bytes}-byte paths.", + "distance_traveled": "📏 {km} km traveled" }, "multitest": { "description": "Listens for 6 seconds and collects all unique paths from incoming messages with the same packet hash", diff --git a/translations/es.json b/translations/es.json index d036010c..8f1a02f7 100644 --- a/translations/es.json +++ b/translations/es.json @@ -214,7 +214,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Desconocido", - "repeater_resolution_deferred": "Ruta: {path}\n\n💡 Consejo: la identificación de repetidores requiere rutas de {minimum_path_bytes} byte(s) por salto." + "repeater_resolution_deferred": "Ruta: {path}\n\n💡 Consejo: la identificación de repetidores requiere rutas de {minimum_path_bytes} byte(s) por salto.", + "distance_traveled": "📏 {km} km recorridos" }, "prefix": { "description": "Buscar repetidores por prefijo de dos caracteres (ej., 'prefix 1A')", diff --git a/translations/fr-CA.json b/translations/fr-CA.json index 5b08587c..1fa57ebd 100644 --- a/translations/fr-CA.json +++ b/translations/fr-CA.json @@ -410,7 +410,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Inconnu", - "repeater_resolution_deferred": "Chemin : {path}\n\n💡 Astuce : l'identification des répéteurs exige des chemins de {minimum_path_bytes} octet(s) par saut." + "repeater_resolution_deferred": "Chemin : {path}\n\n💡 Astuce : l'identification des répéteurs exige des chemins de {minimum_path_bytes} octet(s) par saut.", + "distance_traveled": "📏 {km} km parcourus" }, "prefix": { "description": "Rechercher des répéteurs par préfixe de deux caractères (ex : 'prefix 1A')", diff --git a/translations/fr.json b/translations/fr.json index 7d47900f..99d9bce4 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -338,7 +338,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Inconnu", - "repeater_resolution_deferred": "Chemin : {path}\n\n💡 Astuce : l'identification des répéteurs exige des chemins de {minimum_path_bytes} octet(s) par saut." + "repeater_resolution_deferred": "Chemin : {path}\n\n💡 Astuce : l'identification des répéteurs exige des chemins de {minimum_path_bytes} octet(s) par saut.", + "distance_traveled": "📏 {km} km parcourus" }, "prefix": { "description": "Recherche répéteurs par préfixe 2 caractères (ex: 'prefix 1A')", diff --git a/translations/nl.json b/translations/nl.json index 5fb9f878..72218d7a 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -338,7 +338,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Onbekend", - "repeater_resolution_deferred": "Pad: {path}\n\n💡 Tip: repeateridentificatie vereist paden met {minimum_path_bytes} byte(s) per hop." + "repeater_resolution_deferred": "Pad: {path}\n\n💡 Tip: repeateridentificatie vereist paden met {minimum_path_bytes} byte(s) per hop.", + "distance_traveled": "📏 {km} km afgelegd" }, "prefix": { "description": "Zoek repeaters op twee-karakter prefix (bijv. 'prefix 1A')", diff --git a/translations/pl.json b/translations/pl.json index dec1b3a9..49ce0db2 100644 --- a/translations/pl.json +++ b/translations/pl.json @@ -215,7 +215,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Nieznana", - "repeater_resolution_deferred": "Ścieżka: {path}\n\n💡 Wskazówka: identyfikacja repeaterów wymaga ścieżek z {minimum_path_bytes} bajt(ami) na skok." + "repeater_resolution_deferred": "Ścieżka: {path}\n\n💡 Wskazówka: identyfikacja repeaterów wymaga ścieżek z {minimum_path_bytes} bajt(ami) na skok.", + "distance_traveled": "📏 {km} km pokonano" }, "multitest": { "description": "Nasłuchuje 6 sekund aby zebrać unikalne ścieżki z przychodzących wiadomości z tym samym hash'em pakietu", diff --git a/translations/pt-BR.json b/translations/pt-BR.json index b858e037..7f7d0799 100644 --- a/translations/pt-BR.json +++ b/translations/pt-BR.json @@ -335,7 +335,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Desconhecido", - "repeater_resolution_deferred": "Caminho: {path}\n\n💡 Dica: a identificação de repetidores exige caminhos com {minimum_path_bytes} byte(s) por salto." + "repeater_resolution_deferred": "Caminho: {path}\n\n💡 Dica: a identificação de repetidores exige caminhos com {minimum_path_bytes} byte(s) por salto.", + "distance_traveled": "📏 {km} km percorridos" }, "prefix": { "description": "Procurar repetidores por prefixo de dois caracteres (ex: 'prefixo 1A')", diff --git a/translations/pt.json b/translations/pt.json index d0089d0e..c125530e 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -334,7 +334,8 @@ "continuation_end": "\n...", "truncation": "...", "unknown_name": "Desconhecido", - "repeater_resolution_deferred": "Caminho: {path}\n\n💡 Dica: a identificação de repetidores exige caminhos com {minimum_path_bytes} byte(s) por salto." + "repeater_resolution_deferred": "Caminho: {path}\n\n💡 Dica: a identificação de repetidores exige caminhos com {minimum_path_bytes} byte(s) por salto.", + "distance_traveled": "📏 {km} km percorridos" }, "prefix": { "description": "Pesquisar repetidores por prefixo de dois caracteres (ex: 'prefixo 1A')",