Files
IsulionandClaude Opus 5.5 96553dee1c feat: v6.1.0 — Censored Part & Prompt Vault nodes, selector RNG, hardening
New nodes:
- Isulion Censored Part: blur/pixelate/black bar/noise over a mask and/or
  a percent rectangle; accepts 2D/3D/4D masks and fails closed (blacks the
  item out) instead of passing an uncensored image through
- Prompt Vault Save/Load: named prompt library stored in ComfyUI's user
  directory (outside the checkout, so updates never conflict), seeded from
  prompt_vault.example.json; Load re-runs when the vault changes

Refactor:
- SimpleSelectorNode base: ~20 selector nodes use an instance RNG instead
  of reseeding the global random module (same seed => same output)
- Theme handlers declare DISPLAY_NAME; theme package auto-discovers modules
- __init__ imports node classes directly

Fixes:
- net_utils: re-validate every redirect hop, unwrap IPv4-mapped IPv6,
  block NAT64 and all non-global addresses (SSRF)
- Multiple Prompt Generator: no duplicate prompts for themes listed in two
  categories; 6 themes missing from every category are now reachable
- Shutdown node: parameter renamed to match its "any" input (TypeError)
- ConfigManager: deterministic (sorted) merge order, warns on key
  collisions; drop stale space_colony copy from tropical_paradise_config
- Mega Prompt V3: seed honored again with randomize=disable

Breaking: Civitai api_key widget removed (use CIVITAI_API_TOKEN), absolute
paths refused by Load Images, Negative Prompt pools de-duplicated (shorter
"strict" output), Dynamic Random theme order is now alphabetical.

Tooling: pytest suite (51 tests), GitHub CI (compileall, pytest, ruff),
ruff config, uv.lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:35:56 +02:00

72 lines
2.3 KiB
Python

import os
import subprocess
import shutil
import logging
logger = logging.getLogger(__name__)
# The node only actually shuts down the machine when explicitly armed:
# ISULION_ENABLE_SHUTDOWN=1
ISULION_ENABLE_SHUTDOWN = os.getenv("ISULION_ENABLE_SHUTDOWN", "0") == "1"
if ISULION_ENABLE_SHUTDOWN:
print("\033[93m⚠️ SECURITY WARNING: '💤 IsulionShutdown' is ARMED (ISULION_ENABLE_SHUTDOWN=1).\033[0m")
print("\033[93m Executing this node will shut down the entire system.\033[0m")
class ShutdownNode:
"""
A node that can shut down the computer when the workflow reaches it.
It stays registered for workflow compatibility, but performs a no-op
unless the environment variable ISULION_ENABLE_SHUTDOWN=1 is set.
"""
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"any": ("*",),
"delay": ("INT", {
"default": 30,
"min": 0,
"display": "number"
})
}
}
RETURN_TYPES = ()
FUNCTION = "shutdown_computer"
CATEGORY = "utils"
OUTPUT_NODE = True
def shutdown_computer(self, any, delay):
if not ISULION_ENABLE_SHUTDOWN:
logger.info("IsulionShutdown: disabled (set ISULION_ENABLE_SHUTDOWN=1 to arm). Skipping shutdown.")
return ()
try:
if os.name == 'nt': # Windows
args = ["shutdown", "/s", "/f", "/t", str(delay)]
else: # Linux/Unix
minutes = max(1, round(delay / 60)) # Convert seconds to minutes, minimum 1 minute
args = ["shutdown", "-h", f"+{minutes}"]
if shutil.which(args[0]) is None:
logger.error(f"IsulionShutdown: '{args[0]}' not found on PATH. Cannot shut down.")
return ()
subprocess.run(args, check=False)
logger.info(f"Shutdown command initiated. Computer will shutdown in {delay} seconds.")
except Exception as e:
logger.error(f"Error initiating shutdown: {str(e)}")
return ()
NODE_CLASS_MAPPINGS = {
"💤 IsulionShutdown": ShutdownNode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"💤 IsulionShutdown": "💤 Isulion Shutdown"
}