"""Plugin: CIDR/subnet calculator.""" from __future__ import annotations import ipaddress from derp.plugin import command @command("cidr", help="Subnet info: !cidr | !cidr contains ") async def cmd_cidr(bot, message): """Calculate subnet properties or check IP membership. !cidr 192.168.1.0/24 !cidr contains 10.0.0.0/8 10.1.2.3 """ parts = message.text.split() if len(parts) < 2: await bot.reply(message, "Usage: !cidr | !cidr contains ") return subcmd = parts[1].lower() if subcmd == "contains" and len(parts) >= 4: await _contains(bot, message, parts[2], parts[3]) elif subcmd == "contains": await bot.reply(message, "Usage: !cidr contains ") else: await _info(bot, message, parts[1]) async def _info(bot, message, network_str: str) -> None: """Show subnet information.""" try: net = ipaddress.ip_network(network_str, strict=False) except ValueError: await bot.reply(message, f"Invalid network: {network_str}") return if isinstance(net, ipaddress.IPv4Network): host_count = net.num_addresses - 2 if net.prefixlen < 31 else net.num_addresses parts = [ f"net:{net.network_address}/{net.prefixlen}", f"range:{net[0]}-{net[-1]}", f"hosts:{host_count}", f"mask:{net.netmask}", f"wildcard:{net.hostmask}", ] if net.prefixlen < 31: parts.append(f"broadcast:{net.broadcast_address}") else: parts = [ f"net:{net.network_address}/{net.prefixlen}", f"range:{net[0]}-{net[-1]}", f"hosts:{net.num_addresses}", ] await bot.reply(message, " | ".join(parts)) async def _contains(bot, message, network_str: str, ip_str: str) -> None: """Check if an IP belongs to a network.""" try: net = ipaddress.ip_network(network_str, strict=False) except ValueError: await bot.reply(message, f"Invalid network: {network_str}") return try: addr = ipaddress.ip_address(ip_str) except ValueError: await bot.reply(message, f"Invalid IP: {ip_str}") return if addr in net: await bot.reply(message, f"{addr} is in {net}") else: await bot.reply(message, f"{addr} is NOT in {net}")