""" Shared utilities for FunguyBot plugins. """ import html import ipaddress import socket import logging logger = logging.getLogger(__name__) # Networks considered unsafe for outbound connections _PRIVATE_RANGES = [ ipaddress.ip_network('10.0.0.0/8'), ipaddress.ip_network('172.16.0.0/12'), ipaddress.ip_network('192.168.0.0/16'), ipaddress.ip_network('127.0.0.0/8'), ipaddress.ip_network('169.254.0.0/16'), ipaddress.ip_network('0.0.0.0/8'), ipaddress.ip_network('::1/128'), ipaddress.ip_network('fc00::/7'), ipaddress.ip_network('fe80::/10'), ipaddress.ip_network('::/128'), ] def html_escape(text: str) -> str: """Escape HTML special characters for safe embedding in messages.""" return html.escape(str(text), quote=False) def collapsible_summary(title: str, body: str, expanded: bool = False) -> str: """Wrap content in a collapsible HTML details block.""" open_attr = ' open' if expanded else '' return f"\n{title}\n{body}\n" def is_public_destination(target: str) -> bool: """ Returns True if `target` (hostname or IP) does NOT resolve to any private, loopback, or link‑local address. """ try: addr = ipaddress.ip_address(target) if any(addr in net for net in _PRIVATE_RANGES): return False return True except ValueError: pass try: addrinfo = socket.getaddrinfo(target, None) for _, _, _, _, sockaddr in addrinfo: ip = sockaddr[0] addr = ipaddress.ip_address(ip) if any(addr in net for net in _PRIVATE_RANGES): return False return True except Exception as e: logger.warning(f"Cannot resolve {target}: {e}") return False async def handle_command(room, message, bot, prefix, config): """No-op handler so the bot doesn't crash when loading this module as a plugin.""" pass async def send_html_message(bot, room_id, html_body, markdown_fallback): """Send an HTML-formatted message with a Markdown fallback. Args: bot: simplematrixbotlib.Bot instance room_id: Matrix room ID html_body: HTML string (table, etc.) markdown_fallback: Markdown/plain text for clients that don't render HTML """ content = { "msgtype": "m.text", "body": markdown_fallback, "format": "org.matrix.custom.html", "formatted_body": html_body } await bot.async_client.room_send( room_id=room_id, message_type="m.room.message", content=content )