mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-07-19 01:56:18 +00:00
feat: Enhance elapsed time handling in messages
- Updated the message handling logic to format elapsed time more accurately, displaying "Nms" when the device clock is valid or "Sync Device Clock" when invalid.
- Introduced a new utility function `format_elapsed_display` to centralize elapsed time formatting, improving code maintainability.
- Modified configuration examples to clarify the usage of the {elapsed} placeholder.
- Added translations for the "Sync Device Clock" message in multiple languages to support internationalization.
This commit is contained in:
+1
-1
@@ -245,7 +245,7 @@ long_jokes = false
|
||||
# {path} - Message routing path (e.g., "01,5f (2 hops)")
|
||||
# {path_distance} - Total distance between all hops in path with locations (e.g., "123.4km (3 segs, 1 no-loc)")
|
||||
# {firstlast_distance} - Distance between first and last repeater in path (e.g., "45.6km" or empty if locations missing)
|
||||
# {elapsed} - Message elapsed time
|
||||
# {elapsed} - Elapsed time (e.g. 1234ms) or "Sync Device Clock" when device clock is invalid (use {elapsed} only; do not append ms)
|
||||
#
|
||||
# To add newlines in responses, use \n (single backslash + n):
|
||||
# Example: test = "Line 1\nLine 2\nLine 3"
|
||||
|
||||
@@ -183,7 +183,7 @@ admin_commands = repeater,webviewer
|
||||
# {path} - Message routing path (e.g., "01,5f (2 hops)")
|
||||
# {path_distance} - Total distance between all hops in path with locations (e.g., "123.4km (3 segs, 1 no-loc)")
|
||||
# {firstlast_distance} - Distance between first and last repeater in path (e.g., "45.6km" or empty if locations missing)
|
||||
# {elapsed} - Message elapsed time
|
||||
# {elapsed} - Elapsed time (e.g. 1234ms) or "Sync Device Clock" when device clock is invalid (use {elapsed} only; do not append ms)
|
||||
test = "ack @[{sender}]{phrase_part} | {connection_info} | Received at: {timestamp}"
|
||||
ping = "Pong!"
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import pytz
|
||||
import re
|
||||
from ..models import MeshMessage
|
||||
from ..security_utils import validate_pubkey_format
|
||||
from ..utils import format_elapsed_display
|
||||
|
||||
|
||||
class BaseCommand(ABC):
|
||||
@@ -642,27 +643,9 @@ class BaseCommand(ABC):
|
||||
return "Unknown"
|
||||
|
||||
def format_elapsed(self, message: MeshMessage) -> str:
|
||||
"""Format message timestamp for display"""
|
||||
if message.timestamp and message.timestamp != 'unknown':
|
||||
try:
|
||||
from datetime import datetime,UTC
|
||||
el = round((datetime.now(UTC).timestamp()-message.timestamp)*1000)
|
||||
return f"{el}ms"
|
||||
except:
|
||||
return str(message.timestamp)
|
||||
else:
|
||||
return "Unknown"
|
||||
def format_elapsed(self, message: MeshMessage) -> str:
|
||||
"""Format message timestamp for display"""
|
||||
if message.timestamp and message.timestamp != 'unknown':
|
||||
try:
|
||||
from datetime import datetime,UTC
|
||||
el = round((datetime.now(UTC).timestamp()-message.timestamp)*1000)
|
||||
return f"{el}ms"
|
||||
except:
|
||||
return str(message.timestamp)
|
||||
else:
|
||||
return "Unknown"
|
||||
"""Format message elapsed for display. Uses 'Sync Device Clock' when device clock is invalid."""
|
||||
translator = getattr(self.bot, 'translator', None)
|
||||
return format_elapsed_display(message.timestamp, translator)
|
||||
|
||||
def format_response(self, message: MeshMessage, response_format: str) -> str:
|
||||
"""Format a response string with message data"""
|
||||
|
||||
+11
-12
@@ -14,7 +14,7 @@ from meshcore import EventType
|
||||
|
||||
from .models import MeshMessage
|
||||
from .enums import PayloadType, PayloadVersion, RouteType, AdvertFlags, DeviceRole
|
||||
from .utils import calculate_packet_hash
|
||||
from .utils import calculate_packet_hash, format_elapsed_display
|
||||
from .security_utils import sanitize_input
|
||||
|
||||
|
||||
@@ -303,17 +303,11 @@ class MessageHandler:
|
||||
message_content = payload.get('text', '')
|
||||
message_content = sanitize_input(message_content, max_length=None, strip_controls=True)
|
||||
|
||||
# Format timestamp
|
||||
if timestamp and timestamp != 'unknown':
|
||||
try:
|
||||
from datetime import datetime,UTC
|
||||
dt = datetime.fromtimestamp(message.timestamp)
|
||||
elapsed_str = round((datetime.now(UTC).timestamp()-message.timestamp)*1000)
|
||||
except:
|
||||
elapsed_str = "Unknown"
|
||||
else:
|
||||
elapsed_str = "Unknown"
|
||||
|
||||
# Elapsed: "Nms" when device clock is valid, or "Sync Device Clock" when
|
||||
# invalid (e.g. T-Deck before GPS sync: 0, future, or far in the past).
|
||||
translator = getattr(self.bot, 'translator', None)
|
||||
elapsed_str = format_elapsed_display(timestamp, translator)
|
||||
|
||||
# Convert to our message format
|
||||
message = MeshMessage(
|
||||
content=message_content,
|
||||
@@ -1611,6 +1605,10 @@ class MessageHandler:
|
||||
self.logger.debug(f"Found full public key for {sender_id}: {sender_pubkey[:16]}...")
|
||||
break
|
||||
|
||||
# Elapsed: "Nms" when device clock is valid, or "Sync Device Clock" when invalid.
|
||||
_translator = getattr(self.bot, 'translator', None)
|
||||
_elapsed = format_elapsed_display(payload.get('sender_timestamp'), _translator)
|
||||
|
||||
# Convert to our message format
|
||||
message = MeshMessage(
|
||||
content=message_content, # Use the extracted message content
|
||||
@@ -1622,6 +1620,7 @@ class MessageHandler:
|
||||
rssi=rssi,
|
||||
hops=hops,
|
||||
path=path_string, # Use the path information extracted from RF data
|
||||
elapsed=_elapsed,
|
||||
is_dm=False
|
||||
)
|
||||
|
||||
|
||||
+42
-1
@@ -1698,6 +1698,42 @@ def _get_node_location_from_db(bot: Any, node_id: str) -> Optional[Tuple[float,
|
||||
return None
|
||||
|
||||
|
||||
# Maximum plausible elapsed ms (5 minutes) for device clock validation.
|
||||
# Values above indicate device time is far in the past (e.g. epoch); negative = in the future.
|
||||
_ELAPSED_MS_MAX = 5 * 60 * 1000 # 5 minutes in milliseconds
|
||||
|
||||
|
||||
def format_elapsed_display(ts: Any, translator: Any = None) -> str:
|
||||
"""Format elapsed time from sender timestamp for {elapsed} placeholder.
|
||||
|
||||
Returns "Nms" when valid, or the i18n "Sync Device Clock" when the device
|
||||
clock is invalid (e.g. T-Deck before GPS sync: 0, future, or far in the past).
|
||||
|
||||
Args:
|
||||
ts: Sender timestamp (int, float, None, or 'unknown').
|
||||
translator: Bot translator for i18n; uses "Sync Device Clock" if None.
|
||||
|
||||
Returns:
|
||||
str: e.g. "1234ms" or translated "Sync Device Clock".
|
||||
"""
|
||||
def _sync_str() -> str:
|
||||
if translator:
|
||||
return translator.translate('elapsed.sync_device_clock')
|
||||
return "Sync Device Clock"
|
||||
|
||||
if ts is None or ts == 'unknown':
|
||||
return _sync_str()
|
||||
try:
|
||||
ts_f = float(ts)
|
||||
except (TypeError, ValueError):
|
||||
return _sync_str()
|
||||
from datetime import UTC, datetime
|
||||
elapsed_ms = (datetime.now(UTC).timestamp() - ts_f) * 1000
|
||||
if elapsed_ms < 0 or elapsed_ms > _ELAPSED_MS_MAX:
|
||||
return _sync_str()
|
||||
return f"{round(elapsed_ms)}ms"
|
||||
|
||||
|
||||
def format_keyword_response_with_placeholders(
|
||||
response_format: str,
|
||||
message: Any,
|
||||
@@ -1728,7 +1764,12 @@ def format_keyword_response_with_placeholders(
|
||||
replacements['path'] = message.path or "Unknown"
|
||||
replacements['snr'] = message.snr or "Unknown"
|
||||
replacements['rssi'] = message.rssi or "Unknown"
|
||||
replacements['elapsed'] = message.elapsed or "Unknown"
|
||||
# Compute elapsed from message.timestamp (same as TestCommand) so it's available
|
||||
# for all keywords. Using message.elapsed would miss when it's unset on some paths.
|
||||
_translator = getattr(bot, 'translator', None)
|
||||
replacements['elapsed'] = format_elapsed_display(
|
||||
getattr(message, 'timestamp', None), _translator
|
||||
)
|
||||
|
||||
# Build connection_info
|
||||
routing_info = message.path or "Unknown routing"
|
||||
|
||||
@@ -962,6 +962,9 @@
|
||||
"description": "Verfügbare Befehle auflisten"
|
||||
}
|
||||
},
|
||||
"elapsed": {
|
||||
"sync_device_clock": "Geräteuhr synchronisieren"
|
||||
},
|
||||
"errors": {
|
||||
"rate_limited": "Rate-limitiert. Warte {seconds:.1f} Sekunden",
|
||||
"cooldown": "Befehl '{command}' hat Wartezeit. Warte {seconds} Sekunden.",
|
||||
|
||||
@@ -942,6 +942,9 @@
|
||||
"description": "List available commands"
|
||||
}
|
||||
},
|
||||
"elapsed": {
|
||||
"sync_device_clock": "Sync Device Clock"
|
||||
},
|
||||
"errors": {
|
||||
"rate_limited": "Rate limited. Wait {seconds:.1f} seconds",
|
||||
"cooldown": "Command '{command}' is on cooldown. Wait {seconds} seconds.",
|
||||
|
||||
@@ -992,6 +992,9 @@
|
||||
"description": "List available commands"
|
||||
}
|
||||
},
|
||||
"elapsed": {
|
||||
"sync_device_clock": "Sync Device Clock"
|
||||
},
|
||||
"errors": {
|
||||
"rate_limited": "Rate limited. Wait {seconds:.1f} seconds",
|
||||
"cooldown": "Command '{command}' is on cooldown. Wait {seconds} seconds.",
|
||||
|
||||
@@ -814,6 +814,9 @@
|
||||
"description": "Listar comandos disponibles"
|
||||
}
|
||||
},
|
||||
"elapsed": {
|
||||
"sync_device_clock": "Sincronizar reloj del dispositivo"
|
||||
},
|
||||
"errors": {
|
||||
"rate_limited": "Límite de solicitudes. Espera {seconds:.1f} segundos",
|
||||
"cooldown": "El comando '{command}' está en espera. Espera {seconds} segundos.",
|
||||
|
||||
@@ -1016,6 +1016,9 @@
|
||||
"description": "Lister les commandes disponibles"
|
||||
}
|
||||
},
|
||||
"elapsed": {
|
||||
"sync_device_clock": "Synchroniser l'horloge du dispositif"
|
||||
},
|
||||
"errors": {
|
||||
"rate_limited": "Limite de débit atteinte. Attendez {seconds:.1f} secondes",
|
||||
"cooldown": "La commande '{command}' est en délai d'attente. Attendez {seconds} secondes.",
|
||||
|
||||
@@ -933,6 +933,9 @@
|
||||
"description": "Lister commandes disponibles"
|
||||
}
|
||||
},
|
||||
"elapsed": {
|
||||
"sync_device_clock": "Synchroniser l'horloge du dispositif"
|
||||
},
|
||||
"errors": {
|
||||
"rate_limited": "Limite atteinte. Attendez {seconds:.1f} secondes",
|
||||
"cooldown": "Commande '{command}' en attente. Attendez {seconds} secondes.",
|
||||
|
||||
@@ -942,6 +942,9 @@
|
||||
"description": "Lijst beschikbare commando's"
|
||||
}
|
||||
},
|
||||
"elapsed": {
|
||||
"sync_device_clock": "Synchroniseer apparaatklok"
|
||||
},
|
||||
"errors": {
|
||||
"rate_limited": "Rate limited. Wacht {seconds:.1f} seconden",
|
||||
"cooldown": "Commando '{command}' is in cooldown. Wacht {seconds} seconden.",
|
||||
|
||||
@@ -939,6 +939,9 @@
|
||||
"description": "Listar comandos disponíveis"
|
||||
}
|
||||
},
|
||||
"elapsed": {
|
||||
"sync_device_clock": "Sincronizar relógio do dispositivo"
|
||||
},
|
||||
"errors": {
|
||||
"rate_limited": "Limite de taxa atingido. Aguarda {seconds:.1f} segundos",
|
||||
"cooldown": "Comando '{command}' em espera. Aguarda {seconds} segundos.",
|
||||
|
||||
@@ -938,6 +938,9 @@
|
||||
"description": "Listar comandos disponíveis"
|
||||
}
|
||||
},
|
||||
"elapsed": {
|
||||
"sync_device_clock": "Sincronizar relógio do dispositivo"
|
||||
},
|
||||
"errors": {
|
||||
"rate_limited": "Limitado por taxa. Aguarde {seconds:.1f} segundos",
|
||||
"cooldown": "Comando '{command}' em espera. Aguarde {seconds} segundos.",
|
||||
|
||||
Reference in New Issue
Block a user