summaryrefslogtreecommitdiff
path: root/clients
diff options
context:
space:
mode:
authorauric <auric@japegames.com>2026-02-22 21:26:48 -0600
committerauric <auric@japegames.com>2026-02-22 21:26:48 -0600
commitf1af8621c19390b81847eeccd4e47b1ddb42d4ab (patch)
treea121dbb04c4653e3cd928bda8f623af026afa038 /clients
parente20456da3d77fa15c2e6b93584142b2808e3b6a4 (diff)
umbrella-bot: fix Matrix markdown rendering, remove emojis and em dashes
formatted_body was passing raw Markdown to Matrix clients, causing literal asterisks and backticks to display instead of rendered formatting. Now converts to HTML via the markdown library with the fenced_code extension. Also replaced emoji characters (checkmarks, X marks) with plain text and substituted em dashes with hyphens throughout response strings and HELP_TEXT. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'clients')
-rwxr-xr-xclients/umbrella-bot/umbrella-bot.py55
1 files changed, 28 insertions, 27 deletions
diff --git a/clients/umbrella-bot/umbrella-bot.py b/clients/umbrella-bot/umbrella-bot.py
index 6e26947..d6c6edb 100755
--- a/clients/umbrella-bot/umbrella-bot.py
+++ b/clients/umbrella-bot/umbrella-bot.py
@@ -6,7 +6,7 @@ Connects to the umbrella Unix socket and bridges commands from a Matrix
room to umbrella units. Requires matrix-nio with encryption support.
Dependencies:
- pip install matrix-nio[e2e] aiofiles
+ pip install matrix-nio[e2e] aiofiles markdown
Setup:
1. Create /etc/umbrella/bot.conf (see bot.conf.example)
@@ -16,6 +16,7 @@ Setup:
import asyncio
import json
+import markdown as md_lib
import os
import re
import signal
@@ -62,15 +63,15 @@ TAIL_MAX_LINES = 30
HELP_TEXT = """\
Umbrella bot commands:
- !status <unit> — Show unit status
- !cmd <unit> <command> — Send a console command
- !tail <unit> — Show recent output from a unit
- !restart <unit> — Restart the unit's systemd service
- !update <unit> — Run the update action
- !action <unit> <action> — Run a named action
- !broadcast <message> — Send message to all running units
- !units — List all units
- !help — Show this message
+ !status <unit> - Show unit status
+ !cmd <unit> <command> - Send a console command
+ !tail <unit> - Show recent output from a unit
+ !restart <unit> - Restart the unit's systemd service
+ !update <unit> - Run the update action
+ !action <unit> <action> - Run a named action
+ !broadcast <message> - Send message to all running units
+ !units - List all units
+ !help - Show this message
"""
# ── Umbrella socket client ────────────────────────────────────────────────────
@@ -209,7 +210,7 @@ async def handle_command(
# Check power level
power = await get_user_power_level(client, room.room_id, sender)
if power < 50:
- return f"❌ Permission denied. Requires power level ≥ 50 (you have {power})."
+ return f"Permission denied. Requires power level >= 50 (you have {power})."
try:
if cmd == "!help":
@@ -224,7 +225,7 @@ async def handle_command(
pl = u.get("players", -1)
mx = u.get("max_players", -1)
players = f" ({pl}/{mx})" if pl >= 0 and mx >= 0 else ""
- lines.append(f" `{u['name']}` — {u['display']} [{u['state']}{players}]")
+ lines.append(f" `{u['name']}` - {u['display']} [{u['state']}{players}]")
return "\n".join(lines)
elif cmd == "!status":
@@ -233,7 +234,7 @@ async def handle_command(
unit = parts[1]
resp = umbrella.status(unit)
if resp.get("type") == "error":
- return f"❌ {resp['message']}"
+ return f"Error: {resp['message']}"
pl = resp.get("players", -1)
mx = resp.get("max_players", -1)
map_name = resp.get("map", "")
@@ -254,9 +255,9 @@ async def handle_command(
command = " ".join(parts[2:])
resp = umbrella.send_input(unit, command)
if resp.get("type") == "error":
- return f"❌ {resp['message']}"
+ return f"Error: {resp['message']}"
audit_log(sender, unit, command)
- return f"✓ Sent: `{command}`"
+ return f"Sent: `{command}`"
elif cmd == "!tail":
if len(parts) < 2:
@@ -264,7 +265,7 @@ async def handle_command(
unit = parts[1]
resp = umbrella.tail(unit)
if resp.get("type") == "error":
- return f"❌ {resp.get('message', 'unknown error')}"
+ return f"Error: {resp.get('message', 'unknown error')}"
data = resp.get("data", "")
if not data or not data.strip():
return f"No output buffered for `{unit}`."
@@ -284,10 +285,10 @@ async def handle_command(
message = " ".join(parts[1:])
resp = umbrella.broadcast(message)
if resp.get("type") == "error":
- return f"❌ {resp.get('message', 'unknown error')}"
+ return f"Error: {resp.get('message', 'unknown error')}"
sent = resp.get("sent", 0)
failed = resp.get("failed", 0)
- result = f"✓ Broadcast sent to {sent} unit(s)"
+ result = f"Broadcast sent to {sent} unit(s)"
if failed:
result += f", {failed} failed"
return result
@@ -298,8 +299,8 @@ async def handle_command(
unit = parts[1]
resp = umbrella.run_action(unit, "restart")
if resp.get("type") == "error":
- return f"❌ {resp['message']}"
- return f"✓ Restart dispatched for `{unit}`"
+ return f"Error: {resp['message']}"
+ return f"Restart dispatched for `{unit}`"
elif cmd == "!update":
if len(parts) < 2:
@@ -307,8 +308,8 @@ async def handle_command(
unit = parts[1]
resp = umbrella.run_action(unit, "update")
if resp.get("type") == "error":
- return f"❌ {resp['message']}"
- return f"✓ Update dispatched for `{unit}`"
+ return f"Error: {resp['message']}"
+ return f"Update dispatched for `{unit}`"
elif cmd == "!action":
if len(parts) < 3:
@@ -317,14 +318,14 @@ async def handle_command(
action = parts[2]
resp = umbrella.run_action(unit, action)
if resp.get("type") == "error":
- return f"❌ {resp['message']}"
- return f"✓ Action `{action}` dispatched for `{unit}`"
+ return f"Error: {resp['message']}"
+ return f"Action `{action}` dispatched for `{unit}`"
except RuntimeError as e:
- return f"❌ Umbrella error: {e}"
+ return f"Umbrella error: {e}"
except Exception as e:
log.exception(f"Command error: {e}")
- return f"❌ Internal error: {e}"
+ return f"Internal error: {e}"
return None
@@ -398,7 +399,7 @@ async def run_bot(config: dict):
"msgtype": "m.text",
"body": reply,
"format": "org.matrix.custom.html",
- "formatted_body": reply.replace("\n", "<br>"),
+ "formatted_body": md_lib.markdown(reply, extensions=["fenced_code"]),
},
ignore_unverified_devices=True,
)