subnet and encoder plugins added. stable diffusion fix. updated requirements.txt

This commit is contained in:
2026-05-08 20:08:59 -05:00
parent a51f759259
commit 52a9621d50
5 changed files with 1550 additions and 6 deletions
+57
View File
@@ -0,0 +1,57 @@
"""
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"<details{open_attr}>\n<summary><strong>{title}</strong></summary>\n{body}\n</details>"
def is_public_destination(target: str) -> bool:
"""
Returns True if `target` (hostname or IP) does NOT resolve to any
private, loopback, or linklocal 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
+1203
View File
File diff suppressed because it is too large Load Diff
+25 -5
View File
@@ -1,5 +1,8 @@
#!/usr/bin/env python3
""" """
Plugin for generating images using self-hosted Stable Diffusion and sending them to a Matrix chat room. Plugin for generating images using self-hosted Stable Diffusion and sending them to a Matrix chat room.
Now supports a `--seed` parameter to control deterministic generation.
""" """
import requests import requests
@@ -90,8 +93,13 @@ async def handle_command(room, message, bot, prefix, config):
parser.add_argument('--cfg', type=int, default=2, help='CFG scale, default=2') parser.add_argument('--cfg', type=int, default=2, help='CFG scale, default=2')
parser.add_argument('--h', type=int, default=512, help='Height of the image, default=512') parser.add_argument('--h', type=int, default=512, help='Height of the image, default=512')
parser.add_argument('--w', type=int, default=512, help='Width of the image, default=512') parser.add_argument('--w', type=int, default=512, help='Width of the image, default=512')
parser.add_argument('--neg', type=str, nargs='+', default=['((((ugly)))), (((duplicate))), ((morbid)), ((mutilated)), out of frame, extra fingers, mutated hands, ((poorly drawn hands)), ((poorly drawn face)), (((mutation))), (((deformed))), ((ugly)), blurry, ((bad anatomy)), (((bad proportions))), ((extra limbs)), cloned face, (((disfigured))), out of frame, ugly, extra limbs, (bad anatomy), gross proportions, (malformed limbs), ((missing arms)), ((missing legs)), (((extra arms))), (((extra legs))), mutated hands, (fused fingers), (too many fingers), (((long neck)))'], help='Negative prompt') parser.add_argument('--neg', type=str, nargs='+',
parser.add_argument('--sampler', type=str, nargs='*', default=['DPM++', 'SDE'], help='Sampler name, default=DPM++ SDE') default=['((((ugly)))), (((duplicate))), ((morbid)), ((mutilated)), out of frame, extra fingers, mutated hands, ((poorly drawn hands)), ((poorly drawn face)), (((mutation))), (((deformed))), ((ugly)), blurry, ((bad anatomy)), (((bad proportions))), ((extra limbs)), cloned face, (((disfigured))), out of frame, ugly, extra limbs, (bad anatomy), gross proportions, (malformed limbs), ((missing arms)), ((missing legs)), (((extra arms))), (((extra legs))), mutated hands, (fused fingers), (too many fingers), (((long neck)))'],
help='Negative prompt')
parser.add_argument('--sampler', type=str, nargs='*', default=['DPM++', 'SDE Karras'],
help='Sampler name, default=DPM++ SDE')
parser.add_argument('--seed', type=int, default=None,
help='Seed for deterministic generation (omit for random)')
parser.add_argument('prompt', type=str, nargs='*', help='Prompt for the image') parser.add_argument('prompt', type=str, nargs='*', help='Prompt for the image')
args = parser.parse_args(message.body.split()[1:]) # skip command prefix args = parser.parse_args(message.body.split()[1:]) # skip command prefix
@@ -112,6 +120,9 @@ async def handle_command(room, message, bot, prefix, config):
"width": args.w, "width": args.w,
"height": args.h, "height": args.h,
} }
# Add seed only if explicitly provided
if args.seed is not None:
payload["seed"] = args.seed
url = "http://127.0.0.1:7860/sdapi/v1/txt2img" url = "http://127.0.0.1:7860/sdapi/v1/txt2img"
response = requests.post(url=url, json=payload, timeout=600) response = requests.post(url=url, json=payload, timeout=600)
@@ -127,7 +138,14 @@ async def handle_command(room, message, bot, prefix, config):
# Optional: send info about generated image # Optional: send info about generated image
neg_prompt_clean = neg_prompt.replace(" ", "") neg_prompt_clean = neg_prompt.replace(" ", "")
info_msg = f"""<details><summary>🔍 Image Info</summary><strong>Prompt:</strong> {prompt[:100]}<br><strong>Steps:</strong> {args.steps}<br><strong>Dimensions:</strong> {args.h}x{args.w}<br><strong>Sampler:</strong> {sampler_name}<br><strong>CFG Scale:</strong> {args.cfg}<br><strong>Negative Prompt:</strong> {neg_prompt_clean}</details>""" seed_info = f"<br><strong>Seed:</strong> {args.seed}" if args.seed is not None else ""
info_msg = f"""<details><summary>🔍 Image Info</summary>
<strong>Prompt:</strong> {prompt[:100]}<br>
<strong>Steps:</strong> {args.steps}<br>
<strong>Dimensions:</strong> {args.h}x{args.w}<br>
<strong>Sampler:</strong> {sampler_name}<br>
<strong>CFG Scale:</strong> {args.cfg}{seed_info}<br>
<strong>Negative Prompt:</strong> {neg_prompt_clean}</details>"""
# await bot.api.send_markdown_message(room.room_id, info_msg) # await bot.api.send_markdown_message(room.room_id, info_msg)
# Clean up temp file # Clean up temp file
@@ -167,6 +185,7 @@ def print_help():
<li>--w W - Width of the image, default=512</li> <li>--w W - Width of the image, default=512</li>
<li>--neg NEG - Negative prompt, default=((((ugly)))), (((duplicate))), ((morbid)), ((mutilated)), out of frame, extra fingers, mutated hands, ((poorly drawn hands)), ((poorly drawn face)), (((mutation))), (((deformed))), ((ugly)), blurry, ((bad anatomy)), (((bad proportions))), ((extra limbs)), cloned face, (((disfigured))), out of frame, ugly, extra limbs, (bad anatomy), gross proportions, (malformed limbs), ((missing arms)), ((missing legs)), (((extra arms))), (((extra legs))), mutated hands, (fused fingers), (too many fingers), (((long neck)))</li> <li>--neg NEG - Negative prompt, default=((((ugly)))), (((duplicate))), ((morbid)), ((mutilated)), out of frame, extra fingers, mutated hands, ((poorly drawn hands)), ((poorly drawn face)), (((mutation))), (((deformed))), ((ugly)), blurry, ((bad anatomy)), (((bad proportions))), ((extra limbs)), cloned face, (((disfigured))), out of frame, ugly, extra limbs, (bad anatomy), gross proportions, (malformed limbs), ((missing arms)), ((missing legs)), (((extra arms))), (((extra legs))), mutated hands, (fused fingers), (too many fingers), (((long neck)))</li>
<li>--sampler SAMPLER - Sampler name, default=DPM++ SDE</li> <li>--sampler SAMPLER - Sampler name, default=DPM++ SDE</li>
<li>--seed SEED - Seed for deterministic generation (omit for random)</li>
</ul> </ul>
<p>LORA List:</p> <p>LORA List:</p>
@@ -191,9 +210,9 @@ def print_help():
# Plugin Metadata # Plugin Metadata
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
__version__ = "1.0.0" __version__ = "1.1.0"
__author__ = "Funguy Bot" __author__ = "Funguy Bot"
__description__ = "Stable Diffusion image generation" __description__ = "Stable Diffusion image generation (supports --seed)"
__help__ = """ __help__ = """
<details> <details>
<summary><strong>!sd</strong> Generate images via Stable Diffusion</summary> <summary><strong>!sd</strong> Generate images via Stable Diffusion</summary>
@@ -204,6 +223,7 @@ __help__ = """
<li><code>--h H --w W</code> Image dimensions (default 512)</li> <li><code>--h H --w W</code> Image dimensions (default 512)</li>
<li><code>--neg &lt;negative prompt&gt;</code></li> <li><code>--neg &lt;negative prompt&gt;</code></li>
<li><code>--sampler SAMPLER</code> Sampler name (default DPM++ SDE)</li> <li><code>--sampler SAMPLER</code> Sampler name (default DPM++ SDE)</li>
<li><code>--seed SEED</code> Deterministic seed (optional)</li>
</ul> </ul>
<p>Requires a locally running Stable Diffusion API.</p> <p>Requires a locally running Stable Diffusion API.</p>
</details> </details>
+257
View File
@@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""
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
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
"""
import ipaddress
import sys
from typing import Union
# ------------------------------- helper functions --------------------------------
def _fmt_subnet_info(net: Union[ipaddress.IPv4Network, ipaddress.IPv6Network]) -> str:
"""Return a humanreadable string with all relevant subnet details."""
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
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}",
]
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)
def _split_by_prefix(net, new_prefix: int) -> str:
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)
def _split_by_diff(net, diff: int) -> str:
new_prefix = net.prefixlen + diff
return _split_by_prefix(net, new_prefix)
def _adjacent_networks(net, count: int) -> str:
out = [f"# Adjacent networks of size /{net.prefixlen} (starting at {net.with_prefixlen}):"]
current = net
for i in range(count + 1):
out.append(f"\n-- Adjacent #{i} --")
out.append(_fmt_subnet_info(current))
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.")
break
return "\n".join(out)
# ------------------------------- bot plugin entry -------------------------------
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"
)
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)
return
# --- info (or a CIDR passed directly) ---
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
await bot.api.send_text_message(room.room_id, _fmt_subnet_info(net))
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>"
)
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 <CIDR> --prefix <number>"
)
return
result = _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>"
)
return
result = _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."
)
return
await bot.api.send_text_message(room.room_id, result)
return
# --- adjacent ---
if subcmd == "adjacent":
if len(args) < 3:
await bot.api.send_text_message(
room.room_id,
"Usage: !subnet adjacent <CIDR> <count>"
)
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
result = _adjacent_networks(net, count)
await bot.api.send_text_message(room.room_id, result)
return
# Unknown subcommand
await bot.api.send_text_message(
room.room_id,
f"Unknown subcommand '{subcmd}'. Use !subnet help to see available commands."
)
# Plugin metadata
__version__ = "1.0.1"
__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>
"""
+8 -1
View File
@@ -23,4 +23,11 @@ pytz
ddgs ddgs
playwright playwright
lxml lxml
beautifulsoup4 beautifulsoup4
cryptography
bcrypt
argon2-cffi
yara-python
asn1crypto
PyYAML
lxml