various plugin refactors and fixes

This commit is contained in:
2026-05-09 04:51:50 -05:00
parent f822d6a450
commit 5c6234a317
25 changed files with 2044 additions and 3674 deletions
+130 -139
View File
@@ -2,28 +2,26 @@
"""
plugins/subnet.py Subnet calculator and network splitting plugin for Funguy Bot.
Provides the following commands:
!subnet info <CIDR> Show detailed info about a network
!subnet split <CIDR> --prefix <N> Split network into smaller subnets (new prefix length)
!subnet split <CIDR> --diff <N> Split network into equal subnets (prefixlen delta)
!subnet adjacent <CIDR> <count> Show given network and next <count> adjacent ones
!subnet help Display this help
Commands:
!subnet info <CIDR>
!subnet split <CIDR> --prefix <N>
!subnet split <CIDR> --diff <N>
!subnet adjacent <CIDR> <count>
!subnet help
Examples:
!subnet info 192.168.4.0/26
!subnet split 192.168.4.0/24 --prefix 26
!subnet split 10.0.0.0/16 --diff 2
!subnet adjacent 192.168.4.0/26 3
Output is a clean code block with emojis and perfectly aligned columns.
"""
import ipaddress
import sys
from typing import Union
import simplematrixbotlib as botlib
from plugins.common import collapsible_summary, html_escape, code_block
# ------------------------------- helper functions --------------------------------
# -------------------------------------------------------------------
# Helper functions (synchronous)
# -------------------------------------------------------------------
def _fmt_subnet_info(net: Union[ipaddress.IPv4Network, ipaddress.IPv6Network]) -> str:
"""Return a humanreadable string with all relevant subnet details."""
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
@@ -50,102 +48,124 @@ def _fmt_subnet_info(net: Union[ipaddress.IPv4Network, ipaddress.IPv6Network]) -
first = last = None
usable_count = 0
lines = [
f"CIDR: {net.with_prefixlen}",
f"Network: {nw}",
f"Broadcast: {bc if bc is not None else 'N/A'}",
f"Netmask: {net.netmask if hasattr(net, 'netmask') else 'N/A'}",
f"Wildcard Mask: {net.hostmask if hasattr(net, 'hostmask') else 'N/A'}",
f"Total IPs: {total}",
f"Usable Hosts: {usable_count}",
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:
lines.append(f"First Usable: {first}")
lines.append(f"Last Usable: {last}")
lines.append(f"Usable Range: {first} - {last}")
return "\n".join(lines)
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: int) -> str:
def _split_by_prefix(net, new_prefix):
if new_prefix < net.prefixlen:
return f"[!] New prefix /{new_prefix} is smaller than current prefix /{net.prefixlen}. Cannot split."
out = [f"# Splitting {net.with_prefixlen} into /{new_prefix} subnets:"]
for i, sub in enumerate(net.subnets(new_prefix=new_prefix)):
out.append(f"\n-- Subnet #{i+1} --")
out.append(_fmt_subnet_info(sub))
return "\n".join(out)
return None
return list(net.subnets(new_prefix=new_prefix))
def _split_by_diff(net, diff: int) -> str:
new_prefix = net.prefixlen + diff
return _split_by_prefix(net, new_prefix)
def _split_by_diff(net, diff):
return _split_by_prefix(net, net.prefixlen + diff)
def _adjacent_networks(net, count: int) -> str:
out = [f"# Adjacent networks of size /{net.prefixlen} (starting at {net.with_prefixlen}):"]
def _adjacent_networks(net, count):
nets = [net]
current = net
for i in range(count + 1):
out.append(f"\n-- Adjacent #{i} --")
out.append(_fmt_subnet_info(current))
for _ in range(count):
try:
next_net_addr = current.network_address + current.num_addresses
current = ipaddress.ip_network(f"{next_net_addr}/{current.prefixlen}", strict=True)
except ValueError:
out.append("[!] Reached address space limit.")
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 "\n".join(out)
return nets
# ------------------------------- bot plugin entry -------------------------------
# -------------------------------------------------------------------
# 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 = """
<details>
<summary><strong>!subnet</strong> Subnet calculator and exploration</summary>
<pre>
!subnet info &lt;CIDR&gt; Show detailed info for a network
!subnet split &lt;CIDR&gt; --prefix &lt;N&gt; Split into smaller subnets (new prefix)
!subnet split &lt;CIDR&gt; --diff &lt;N&gt; Split by prefix delta
!subnet adjacent &lt;CIDR&gt; &lt;count&gt; Show current and adjacent networks
</pre>
<p>Example: <code>!subnet info 192.168.1.0/24</code></p>
<ul>
<li>IPv4 /31 and /32 networks show both addresses as usable (RFC 3021).</li>
<li>IPv6 networks list all addresses as hosts (no broadcast).</li>
</ul>
</details>
"""
# -------------------------------------------------------------------
# Command handler
# -------------------------------------------------------------------
async def handle_command(room, message, bot, prefix, config):
import simplematrixbotlib as botlib
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 <info|split|adjacent> ...\n"
" !subnet help show full help"
)
await bot.api.send_text_message(room.room_id, "Usage: !subnet <info|split|adjacent> ...\n !subnet help")
return
subcmd = args[0].lower()
# --- help ---
if subcmd in ("help", "-h", "--help"):
# Send nicely formatted HTML in a details tag via markdown
html = "<details><summary><strong>!subnet</strong> Subnet calculator and exploration</summary>\n"
html += "<p>Calculate subnet details, split networks, or enumerate adjacent subnets.</p>\n"
html += "<h4>Commands</h4>\n"
html += "<ul>\n"
html += "<li><b>info</b> Show detailed info for a network<br>\n"
html += "<code>!subnet info &lt;CIDR&gt;</code><br>\n"
html += "Example: <code>!subnet info 192.168.1.0/24</code></li>\n"
html += "<li><b>split</b> Split a network into smaller subnets<br>\n"
html += "<code>!subnet split &lt;CIDR&gt; --prefix &lt;new_prefix&gt;</code><br>\n"
html += "Example: <code>!subnet split 192.168.1.0/24 --prefix 26</code><br>\n"
html += "<i>Alternatively, use --diff to split by prefix delta:</i><br>\n"
html += "<code>!subnet split &lt;CIDR&gt; --diff &lt;delta&gt;</code><br>\n"
html += "Example: <code>!subnet split 10.0.0.0/16 --diff 2</code> (creates 4 subnets)</li>\n"
html += "<li><b>adjacent</b> Show the current network and adjacent ones<br>\n"
html += "<code>!subnet adjacent &lt;CIDR&gt; &lt;count&gt;</code><br>\n"
html += "Example: <code>!subnet adjacent 192.168.4.0/26 3</code></li>\n"
html += "</ul>\n"
html += "<h4>Notes</h4>\n"
html += "<ul>\n"
html += "<li>IPv4 /31 and /32 networks show both addresses as usable (RFC 3021).</li>\n"
html += "<li>IPv6 networks list all addresses as hosts (no broadcast).</li>\n"
html += "</ul>\n"
html += "</details>"
await bot.api.send_markdown_message(room.room_id, html)
await bot.api.send_markdown_message(room.room_id, _HELP_MD)
return
# --- info (or a CIDR passed directly) ---
if subcmd == "info" or "/" in subcmd:
cidr = args[1] if subcmd == "info" else subcmd
try:
@@ -153,16 +173,13 @@ async def handle_command(room, message, bot, prefix, config):
except ValueError as e:
await bot.api.send_text_message(room.room_id, f"[!] Invalid CIDR: {e}")
return
await bot.api.send_text_message(room.room_id, _fmt_subnet_info(net))
output = _info_output(net)
await bot.api.send_markdown_message(room.room_id, output)
return
# --- split ---
if subcmd == "split":
if len(args) < 2:
await bot.api.send_text_message(
room.room_id,
"Usage: !subnet split <CIDR> --prefix <new_prefix> OR --diff <delta>"
)
await bot.api.send_text_message(room.room_id, "Usage: !subnet split <CIDR> --prefix <N> OR !subnet split <CIDR> --diff <delta>")
return
cidr = args[1]
try:
@@ -176,39 +193,31 @@ async def handle_command(room, message, bot, prefix, config):
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 <CIDR> --prefix <number>"
)
await bot.api.send_text_message(room.room_id, "Usage: !subnet split <CIDR> --prefix <number>")
return
result = _split_by_prefix(net, new_prefix)
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 <CIDR> --diff <delta>"
)
await bot.api.send_text_message(room.room_id, "Usage: !subnet split <CIDR> --diff <delta>")
return
result = _split_by_diff(net, diff)
subnets = _split_by_diff(net, diff)
else:
await bot.api.send_text_message(
room.room_id,
"You must provide either --prefix <N> or --diff <N> for split."
)
await bot.api.send_text_message(room.room_id, "You must provide --prefix <N> or --diff <N> for split.")
return
await bot.api.send_text_message(room.room_id, result)
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
# --- adjacent ---
if subcmd == "adjacent":
if len(args) < 3:
await bot.api.send_text_message(
room.room_id,
"Usage: !subnet adjacent <CIDR> <count>"
)
await bot.api.send_text_message(room.room_id, "Usage: !subnet adjacent <CIDR> <count>")
return
cidr = args[1]
try:
@@ -219,39 +228,21 @@ async def handle_command(room, message, bot, prefix, config):
try:
count = int(args[2])
except ValueError:
await bot.api.send_text_message(
room.room_id,
"Count must be an integer."
)
await bot.api.send_text_message(room.room_id, "Count must be an integer.")
return
result = _adjacent_networks(net, count)
await bot.api.send_text_message(room.room_id, result)
networks = _adjacent_networks(net, count)
output = _adjacent_output(networks)
await bot.api.send_markdown_message(room.room_id, output)
return
# Unknown subcommand
await bot.api.send_text_message(
room.room_id,
f"Unknown subcommand '{subcmd}'. Use !subnet help to see available commands."
)
await bot.api.send_text_message(room.room_id, f"Unknown subcommand '{subcmd}'. Use !subnet help.")
# Plugin metadata
__version__ = "1.0.1"
# ---------------------------------------------------------------------------
# Plugin Metadata
# ---------------------------------------------------------------------------
__version__ = "1.3.2"
__author__ = "Funguy Bot"
__description__ = "Subnet calculator, splitter, and adjacent network enumerator"
__help__ = """
<details>
<summary><strong>!subnet</strong> Subnet calculator and exploration</summary>
<p>Calculate subnet details, split networks, or enumerate adjacent subnets.</p>
<ul>
<li><code>!subnet info &lt;CIDR&gt;</code> Show detailed info for a network<br>
Example: <code>!subnet info 192.168.1.0/24</code></li>
<li><code>!subnet split &lt;CIDR&gt; --prefix &lt;new_prefix&gt;</code> Split into smaller subnets<br>
Example: <code>!subnet split 192.168.1.0/24 --prefix 26</code></li>
<li><code>!subnet split &lt;CIDR&gt; --diff &lt;delta&gt;</code> Split by prefix delta<br>
Example: <code>!subnet split 10.0.0.0/16 --diff 2</code></li>
<li><code>!subnet adjacent &lt;CIDR&gt; &lt;count&gt;</code> Show adjacent networks<br>
Example: <code>!subnet adjacent 192.168.4.0/26 3</code></li>
</ul>
</details>
"""
__description__ = "Subnet calculator"
__help__ = _HELP_MD