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:
parent
b1e08ab720
commit
077e0032cf
|
|
@ -1,5 +1,13 @@
|
||||||
# Changelog
|
# 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
|
## [0.2.4] - 2026-02-15
|
||||||
### Added
|
### Added
|
||||||
- Neuer Befehl /mesh - zeigt Mesh-Netzwerk-Infos (Nodes online/gesamt, aktiv 24h, Positionen, Hop-Verteilung, Top-Hardware)
|
- Neuer Befehl /mesh - zeigt Mesh-Netzwerk-Infos (Nodes online/gesamt, aktiv 24h, Positionen, Hop-Verteilung, Top-Hardware)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
version: "0.2.4"
|
version: "0.2.5"
|
||||||
|
|
||||||
bot:
|
bot:
|
||||||
name: "MeshDD-Bot"
|
name: "MeshDD-Bot"
|
||||||
|
|
|
||||||
|
|
@ -208,19 +208,51 @@ class MeshBot:
|
||||||
response = f"⏱️ Uptime: {self._format_uptime()}"
|
response = f"⏱️ Uptime: {self._format_uptime()}"
|
||||||
|
|
||||||
if response:
|
if response:
|
||||||
self._send_text(response, channel)
|
await self._send_text(response, channel)
|
||||||
await self.db.insert_command(cmd)
|
await self.db.insert_command(cmd)
|
||||||
if self.ws_manager:
|
if self.ws_manager:
|
||||||
stats = await self.db.get_stats()
|
stats = await self.db.get_stats()
|
||||||
await self.ws_manager.broadcast("stats_update", stats)
|
await self.ws_manager.broadcast("stats_update", stats)
|
||||||
|
|
||||||
def _send_text(self, text: str, channel: int):
|
async def _send_text(self, text: str, channel: int, max_len: int = 170):
|
||||||
if self.interface:
|
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:
|
try:
|
||||||
self.interface.sendText(text, channelIndex=channel)
|
self.interface.sendText(part, channelIndex=channel)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Error sending text")
|
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:
|
def _format_uptime(self) -> str:
|
||||||
elapsed = int(time.time() - self.start_time)
|
elapsed = int(time.time() - self.start_time)
|
||||||
days, remainder = divmod(elapsed, 86400)
|
days, remainder = divmod(elapsed, 86400)
|
||||||
|
|
@ -250,7 +282,10 @@ class MeshBot:
|
||||||
h = n.get("hop_count")
|
h = n.get("hop_count")
|
||||||
if h is not None:
|
if h is not None:
|
||||||
hop_counts[h] = hop_counts.get(h, 0) + 1
|
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)
|
# Hardware distribution (top 3)
|
||||||
hw_counts = {}
|
hw_counts = {}
|
||||||
|
|
@ -259,16 +294,20 @@ class MeshBot:
|
||||||
if hw:
|
if hw:
|
||||||
hw_counts[hw] = hw_counts.get(hw, 0) + 1
|
hw_counts[hw] = hw_counts.get(hw, 0) + 1
|
||||||
top_hw = sorted(hw_counts.items(), key=lambda x: -x[1])[:3]
|
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"🕸️ Mesh-Netzwerk:\n"
|
||||||
f"Nodes: {total} ({online} online)\n"
|
f"Nodes: {total} ({online} online)\n"
|
||||||
f"Aktiv 24h: {active_24h}\n"
|
f"Aktiv 24h: {active_24h}\n"
|
||||||
f"Mit Position: {with_pos}\n"
|
f"Mit Position: {with_pos}",
|
||||||
f"Hops: {hop_str or '-'}\n"
|
]
|
||||||
f"Hardware: {hw_str or '-'}"
|
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:
|
async def _get_weather(self, from_id: str) -> str:
|
||||||
node = await self.db.get_node(from_id)
|
node = await self.db.get_node(from_id)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue