feat: v0.2.5 - Message splitting, improved /mesh readability

- Split long messages at 170 bytes with 1.5s delay between parts
- /mesh: proper sections with line breaks and indentation
- Hop labels: "Direkt", "1 Hop", "2 Hops" instead of "0h", "1h"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ppfeiffer 2026-02-15 14:38:57 +01:00
parent b1e08ab720
commit 077e0032cf
3 changed files with 59 additions and 12 deletions

View file

@ -1,5 +1,13 @@
# Changelog
## [0.2.5] - 2026-02-15
### Added
- Automatisches Aufteilen langer Nachrichten (max 170 Zeichen) mit 1,5s Pause
### Changed
- /mesh Befehl: bessere Lesbarkeit mit Absätzen und Einrückungen
- Hop-Verteilung: "Direkt", "1 Hop", "2 Hops" statt "0h", "1h", "2h"
## [0.2.4] - 2026-02-15
### Added
- Neuer Befehl /mesh - zeigt Mesh-Netzwerk-Infos (Nodes online/gesamt, aktiv 24h, Positionen, Hop-Verteilung, Top-Hardware)

View file

@ -1,4 +1,4 @@
version: "0.2.4"
version: "0.2.5"
bot:
name: "MeshDD-Bot"

View file

@ -208,19 +208,51 @@ class MeshBot:
response = f"⏱️ Uptime: {self._format_uptime()}"
if response:
self._send_text(response, channel)
await self._send_text(response, channel)
await self.db.insert_command(cmd)
if self.ws_manager:
stats = await self.db.get_stats()
await self.ws_manager.broadcast("stats_update", stats)
def _send_text(self, text: str, channel: int):
if self.interface:
async def _send_text(self, text: str, channel: int, max_len: int = 170):
if not self.interface:
return
parts = self._split_message(text, max_len)
for i, part in enumerate(parts):
if i > 0:
await asyncio.sleep(1.5)
try:
self.interface.sendText(text, channelIndex=channel)
self.interface.sendText(part, channelIndex=channel)
except Exception:
logger.exception("Error sending text")
@staticmethod
def _split_message(text: str, max_len: int) -> list[str]:
if len(text.encode('utf-8')) <= max_len:
return [text]
lines = text.split('\n')
parts = []
current = ""
for line in lines:
candidate = f"{current}\n{line}" if current else line
if len(candidate.encode('utf-8')) > max_len:
if current:
parts.append(current)
if len(line.encode('utf-8')) > max_len:
while line:
chunk = line[:max_len]
while len(chunk.encode('utf-8')) > max_len:
chunk = chunk[:-1]
parts.append(chunk)
line = line[len(chunk):]
else:
current = line
else:
current = candidate
if current:
parts.append(current)
return parts
def _format_uptime(self) -> str:
elapsed = int(time.time() - self.start_time)
days, remainder = divmod(elapsed, 86400)
@ -250,7 +282,10 @@ class MeshBot:
h = n.get("hop_count")
if h is not None:
hop_counts[h] = hop_counts.get(h, 0) + 1
hop_str = ", ".join(f"{k}h:{v}" for k, v in sorted(hop_counts.items()))
hop_lines = []
for k in sorted(hop_counts.keys()):
label = "Direkt" if k == 0 else f"{k} Hop{'s' if k > 1 else ''}"
hop_lines.append(f" {label}: {hop_counts[k]}")
# Hardware distribution (top 3)
hw_counts = {}
@ -259,16 +294,20 @@ class MeshBot:
if hw:
hw_counts[hw] = hw_counts.get(hw, 0) + 1
top_hw = sorted(hw_counts.items(), key=lambda x: -x[1])[:3]
hw_str = ", ".join(f"{hw}:{cnt}" for hw, cnt in top_hw)
hw_lines = [f" {hw}: {cnt}" for hw, cnt in top_hw]
return (
parts = [
f"🕸️ Mesh-Netzwerk:\n"
f"Nodes: {total} ({online} online)\n"
f"Aktiv 24h: {active_24h}\n"
f"Mit Position: {with_pos}\n"
f"Hops: {hop_str or '-'}\n"
f"Hardware: {hw_str or '-'}"
)
f"Mit Position: {with_pos}",
]
if hop_lines:
parts.append("📊 Hop-Verteilung:\n" + "\n".join(hop_lines))
if hw_lines:
parts.append("🔧 Top Hardware:\n" + "\n".join(hw_lines))
return "\n\n".join(parts)
async def _get_weather(self, from_id: str) -> str:
node = await self.db.get_node(from_id)