#!/usr/bin/env python3 """ plugins/subnet.py – Subnet calculator and network splitting plugin for Funguy Bot. Commands: !subnet info !subnet split --prefix !subnet split --diff !subnet adjacent !subnet help Output is a clean code block with emojis and perfectly aligned columns. """ import ipaddress import simplematrixbotlib as botlib from plugins.common import collapsible_summary, html_escape, code_block # ------------------------------------------------------------------- # Helper functions (synchronous) # ------------------------------------------------------------------- def _fmt_subnet_info_rows(net): """Return list of (emoji, label, value) tuples.""" nw = net.network_address bc = net.broadcast_address if hasattr(net, "broadcast_address") else None total = net.num_addresses if net.version == 4: if net.prefixlen == 32: usable_count = 1 first = last = nw elif net.prefixlen == 31: usable_count = 2 first = nw last = bc else: usable_count = max(0, total - 2) first = nw + 1 if usable_count > 0 else None last = bc - 1 if usable_count > 0 else None else: hosts_iter = net.hosts() try: first = next(hosts_iter) last = net.network_address + (total - 1) usable_count = total except StopIteration: first = last = None usable_count = 0 rows = [ ("🌐", "CIDR", str(net.with_prefixlen)), ("📡", "Network", str(nw)), ("📢", "Broadcast", str(bc) if bc is not None else "N/A"), ("🧱", "Netmask", str(net.netmask) if hasattr(net, "netmask") else "N/A"), ("🕳️", "Wildcard Mask", str(net.hostmask) if hasattr(net, "hostmask") else "N/A"), ("🔢", "Total IPs", str(total)), ("👥", "Usable Hosts", str(usable_count)), ] if first is not None and last is not None: rows.append(("🏁", "First Usable", str(first))) rows.append(("🏁", "Last Usable", str(last))) rows.append(("↔️", "Usable Range", f"{first} - {last}")) return rows def _split_by_prefix(net, new_prefix): if new_prefix < net.prefixlen: return None return list(net.subnets(new_prefix=new_prefix)) def _split_by_diff(net, diff): return _split_by_prefix(net, net.prefixlen + diff) def _adjacent_networks(net, count): nets = [net] current = net for _ in range(count): try: next_addr = current.network_address + current.num_addresses current = ipaddress.ip_network(f"{next_addr}/{current.prefixlen}", strict=True) nets.append(current) except (ValueError, ipaddress.AddressValueError): break return nets # ------------------------------------------------------------------- # Output builders (each returns a collapsible Markdown message) # ------------------------------------------------------------------- def _info_output(net): """Build a collapsible message for a single subnet.""" title = f"🔍 Subnet {net.with_prefixlen}" rows = _fmt_subnet_info_rows(net) block = code_block(title, [{"title": "", "rows": rows}]) return collapsible_summary(title, block) def _split_output(networks): """Build a collapsible message for a split operation.""" total = len(networks) title = f"🔀 Split into {total} subnets" sections = [] for i, sub in enumerate(networks, 1): rows = _fmt_subnet_info_rows(sub) sections.append({"title": f"Subnet {sub.with_prefixlen}", "rows": rows}) block = code_block(title, sections) return collapsible_summary(title, block) def _adjacent_output(networks): """Build a collapsible message for adjacent networks.""" base = networks[0] title = f"📐 Adjacent networks (base {base.with_prefixlen})" sections = [] for i, net in enumerate(networks): label = "Base network" if i == 0 else f"Adjacent #{i}" rows = _fmt_subnet_info_rows(net) sections.append({"title": label, "rows": rows}) block = code_block(title, sections) return collapsible_summary(title, block) # ------------------------------------------------------------------- # Help # ------------------------------------------------------------------- _HELP_MD = """
!subnet – Subnet calculator and exploration
!subnet info <CIDR>                        Show detailed info for a network
!subnet split <CIDR> --prefix <N>        Split into smaller subnets (new prefix)
!subnet split <CIDR> --diff <N>          Split by prefix delta
!subnet adjacent <CIDR> <count>          Show current and adjacent networks

Example: !subnet info 192.168.1.0/24

  • IPv4 /31 and /32 networks show both addresses as usable (RFC 3021).
  • IPv6 networks list all addresses as hosts (no broadcast).
""" # ------------------------------------------------------------------- # Command handler # ------------------------------------------------------------------- async def handle_command(room, message, bot, prefix, config): match = botlib.MessageMatch(room, message, bot, prefix) if not (match.is_not_from_this_bot() and match.prefix() and match.command("subnet")): return args = match.args() if not args: await bot.api.send_text_message(room.room_id, "Usage: !subnet ...\n !subnet help") return subcmd = args[0].lower() if subcmd in ("help", "-h", "--help"): await bot.api.send_markdown_message(room.room_id, _HELP_MD) return if subcmd == "info" or "/" in subcmd: cidr = args[1] if subcmd == "info" else subcmd try: net = ipaddress.ip_network(cidr, strict=False) except ValueError as e: await bot.api.send_text_message(room.room_id, f"[!] Invalid CIDR: {e}") return output = _info_output(net) await bot.api.send_markdown_message(room.room_id, output) return if subcmd == "split": if len(args) < 2: await bot.api.send_text_message(room.room_id, "Usage: !subnet split --prefix OR !subnet split --diff ") return cidr = args[1] try: net = ipaddress.ip_network(cidr, strict=False) except ValueError as e: await bot.api.send_text_message(room.room_id, f"[!] Invalid CIDR: {e}") return if "--prefix" in args: try: idx = args.index("--prefix") new_prefix = int(args[idx + 1]) except (ValueError, IndexError): await bot.api.send_text_message(room.room_id, "Usage: !subnet split --prefix ") return subnets = _split_by_prefix(net, new_prefix) elif "--diff" in args: try: idx = args.index("--diff") diff = int(args[idx + 1]) except (ValueError, IndexError): await bot.api.send_text_message(room.room_id, "Usage: !subnet split --diff ") return subnets = _split_by_diff(net, diff) else: await bot.api.send_text_message(room.room_id, "You must provide --prefix or --diff for split.") return if subnets is None: await bot.api.send_text_message(room.room_id, f"[!] New prefix /{new_prefix} is smaller than current prefix /{net.prefixlen}. Cannot split.") return output = _split_output(subnets) await bot.api.send_markdown_message(room.room_id, output) return if subcmd == "adjacent": if len(args) < 3: await bot.api.send_text_message(room.room_id, "Usage: !subnet adjacent ") return cidr = args[1] try: net = ipaddress.ip_network(cidr, strict=False) except ValueError as e: await bot.api.send_text_message(room.room_id, f"[!] Invalid CIDR: {e}") return try: count = int(args[2]) except ValueError: await bot.api.send_text_message(room.room_id, "Count must be an integer.") return networks = _adjacent_networks(net, count) output = _adjacent_output(networks) await bot.api.send_markdown_message(room.room_id, output) return await bot.api.send_text_message(room.room_id, f"Unknown subcommand '{subcmd}'. Use !subnet help.") # --------------------------------------------------------------------------- # Plugin Metadata # --------------------------------------------------------------------------- __version__ = "1.3.2" __author__ = "Funguy Bot" __description__ = "Subnet calculator" __help__ = _HELP_MD