various plugin refactors and fixes
This commit is contained in:
+130
-139
@@ -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 human‑readable 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 <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
|
||||
</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 <CIDR></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 <CIDR> --prefix <new_prefix></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 <CIDR> --diff <delta></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 <CIDR> <count></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 <CIDR></code> – Show detailed info for a network<br>
|
||||
Example: <code>!subnet info 192.168.1.0/24</code></li>
|
||||
<li><code>!subnet split <CIDR> --prefix <new_prefix></code> – Split into smaller subnets<br>
|
||||
Example: <code>!subnet split 192.168.1.0/24 --prefix 26</code></li>
|
||||
<li><code>!subnet split <CIDR> --diff <delta></code> – Split by prefix delta<br>
|
||||
Example: <code>!subnet split 10.0.0.0/16 --diff 2</code></li>
|
||||
<li><code>!subnet adjacent <CIDR> <count></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
|
||||
|
||||
Reference in New Issue
Block a user