- Remove unused imports from nodes (time, torch, requests, itertools)
- Move uuid import to top-level in bot/bot.py
- Add 4 new test files with 86 tests for shared utilities
- Update CHANGELOG.md with version 2.0.0 refactoring summary
- Update REFACTOR_PRD.md to mark Phase 5 complete
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add WebSocket reconnection with exponential backoff (1s-60s, ±10% jitter)
- Add error delivery to notify users when jobs fail
- Create templates cog with /template save/load/list/delete commands
- Create history cog with /history (paginated) and /rerun commands
- Fix BotConfig import in bot.py
- Enable templates and history cogs in bot loader
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The build_metadata_section function used "\n".join(metadata_lines)
which lacks a trailing newline. When dimensions were appended later,
they concatenated directly without proper spacing, producing malformed
output like "**Format:** PNG**Original Dimensions:** 1024x1024".
Added trailing newline so subsequent content appears on a new line.
Fixes issue identified in PR #35 review.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The refactored code checked only `if save_cdn_urls:` instead of the
original `if save_cdn_urls and response.status_code == 200:`. While
extract_cdn_urls_from_response correctly returns empty for 204 responses,
accumulated URLs from prior iterations could still trigger the
`if batch_cdn_urls:` condition, causing duplicate sends.
Fixed in both individual image send and batch send sections.
Fixes issue identified in PR #35 review.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The refactored prompt extractor only detected CLIPTextEncode nodes,
losing support for SDXL-specific nodes that was present in the original
code. This caused SDXL workflows using SDXLPromptEncoder or SDXLTextEncode
nodes to not have their prompts included in Discord messages.
Changes:
- Add PROMPT_NODE_TYPES list with CLIPTextEncode, SDXLPromptEncoder,
and SDXLTextEncode
- Rename functions from clip-specific to generic prompt node naming
- Add fallback detection for custom text/encode/prompt nodes
- Update docstrings to reflect broader node type support
Fixes regression identified in PR #35 review.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When add_dimensions or resize_to_power_of_2 was enabled but add_date,
add_time, and include_format_in_message were all disabled, dimensions
would be appended to the Discord message without the "Image Information:"
section header.
Now checks if dimensions will be added but no other metadata exists,
and adds the section header in that case.
Fixes issue identified in PR #35 review.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The else block for error handling was incorrectly associated with the
`if save_cdn_urls:` condition instead of the `if response.status_code
in [200, 204]:` check. This caused discord_send_success to always be
set to False after a successful batch send, regardless of the actual
outcome.
Fixes control flow bug identified in PR #35 review.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create nodes/base_node.py with BaseDiscordNode class
- Provide common INPUT_TYPES generators (discord, cdn, filename)
- Extract shared methods:
- sanitize_workflow_data()
- build_filename_prefix()
- get_dest_folder()
- extract_workflow_from_metadata()
- build_prompt_message()
- send_discord_files()
- send_cdn_urls_to_discord()
- update_github_cdn()
The base class is ready for nodes to inherit from, enabling
gradual adoption of shared functionality without disrupting
existing node implementations.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create shared/media/format_utils.py with format detection and validation
- Create shared/media/video_encoder.py with FFmpegEncoder and PILEncoder classes
- Extract validate_video_for_discord to shared utility
- Extract ffmpeg detection to shared detect_ffmpeg() function
- Extract Discord video optimization to shared optimize_video_for_discord()
- Remove duplicate code from video_node.py (-109 lines)
Phase 3 summary:
- video_node.py: 1201 -> 1092 lines (9% reduction this phase)
- Total reduction from original: 1562 -> 1092 lines (30% reduction)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create shared/filename_utils.py for date/time/dimension filename building
- Create shared/path_utils.py for output directory handling
- Create shared/discord/message_builder.py for Discord message construction
- Create shared/discord/cdn_extractor.py for CDN URL extraction
- Refactor image_node.py to use shared utilities (-161 lines, 16.3%)
- Refactor video_node.py to use shared utilities (-361 lines, 23.1%)
- Fix setup_logging missing from logging_config.py
- Fix test imports to use new module paths (nodes.* instead of discord_*_node)
- Total reduction: 522 lines (20.5%), exceeding PRD target of ~200 lines
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Updated `tests/test_image_node_sanitization.py` to include `github_token` in test inputs.
- Verified test now correctly covers github token sanitization.
- Updated `tests/test_image_node_sanitization.py` to avoid bare `try...except` that swallowed `AssertionError`.
- Verified the test fails correctly when sanitization is broken.
I removed a try-except block that attempted to import non-existent variables (`ffmpeg_path`, `get_audio`, etc.) from `discordsend_utils`. These variables were not used in the codebase and were causing import errors or false positives in static analysis.
- Replaced the failing import block with explicit definitions for `ffmpeg_path`, `ENCODE_ARGS`, and `has_vhs_formats`.
- Imported `ProgressBar` from `comfy.utils` with a fallback for standalone usage.
- Removed unused variables `floatOrInt`, `imageOrLatent`, and `BIGMAX`.
- Verified that the removed variables and functions are dead code.
- Removed redundant calls to `sanitize_json_for_export` in `discord_image_node.py` loop.
- Verified ~33ms performance gain per batch via benchmark.
- Added regression test `tests/test_image_node_sanitization.py`.
- Update `filename_prefix` tooltip to clarify `%batch_num%` usage.
- Update `github_repo` tooltip with an example format.
- Update `github_token` tooltip with navigation instructions to find the token.
- Standardize `webhook_url` tooltip across image and video nodes.
- Add `.jules/palette.md` for UX learnings.
Phase 0 of separation-of-concerns refactor:
- Fix BotConfig class name mismatch in bot/__main__.py
(was importing 'Config' but class is 'BotConfig')
- Add missing 'import json' to bot/services/delivery.py
(json.loads was called without import)
- Remove unused PermissionLevel import from bot/cogs/admin.py
- Fix config attribute path: config.comfyui.url (was config.comfyui_url)
Also adds REFACTOR_PRD.md to .gitignore for project planning docs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix potential BufferError in video frame writing path by enforcing C-contiguity with np.ascontiguousarray
- Add regression tests for non-contiguous array writing to Popen.stdin
- Rename variables for clarity (images_bytes -> image_chunks)
- Ensure cross-platform compatibility in tests
- Fix BufferError when passing non-contiguous transposed audio array to memoryview/subprocess by using np.ascontiguousarray
- Rename 'images_bytes' to 'image_chunks' to better reflect that it contains numpy arrays, not bytes objects
- Improve test portability by using sys.executable instead of 'cat'
Avoids unnecessary memory copying by passing numpy arrays/memoryviews directly to subprocess stdin instead of creating intermediate bytes objects. This reduces memory pressure and allocation overhead when processing high-resolution video frames.
💡 What: Updated tooltips for `add_time` (video/image) and `filename_prefix` (image).
🎯 Why:
- Video `add_time`: Warns users that disabling this causes the "single frame" bug on Discord, guiding them to `include_video_info` instead.
- Image `add_time`: Clarifies caching behavior.
- Image `filename_prefix`: Documents `%batch_num%` support.
♿ Accessibility: Provides critical context directly in the UI, reducing reliance on external documentation (README).
Prevent arbitrary file write and repo traversal by strictly validating
`github_repo` and `file_path` inputs in `update_github_cdn_urls`.
Added `validate_github_repo` and `validate_file_path` functions to
enforce strict whitelisting of characters and reject path traversal sequences.
Added comprehensive unit tests in `tests/test_github_validation.py`.
- Sanitize `DiscordWebhookClient` exception messages to redact webhook tokens.
- Sanitize GitHub API response text in error messages to prevent token leakage.
- Update `tests/test_utils.py` with mocks and new security test cases.
- Record security learning in `.jules/sentinel.md`.
This addresses potential credential exposure in logs and error messages.
This change modifies `DiscordSendSaveVideo.INPUT_TYPES` to check for `ffmpeg_path` existence.
- If FFmpeg is missing: The default format is set to "video/gif" (the only available fallback), and the tooltip is updated to explicitly warn the user and explain that FFmpeg installation is required for other formats.
- If FFmpeg is present: The behavior remains unchanged with the full list of formats and detailed tooltip.
This improves UX by preventing confusion when users see MP4 options in the tooltip but can't select them, and provides a clear call to action.
Moved expensive metadata sanitization and JSON serialization outside of the image processing loop in `discord_image_node.py`.
💡 What:
- Hoisted `sanitize_json_for_export` calls for `prompt` and `extra_pnginfo` out of the batch loop.
- Removed redundant triple-sanitization check inside the PNG saving block.
- Created `metadata` object once per batch instead of N times.
🎯 Why:
- Sanitizing complex workflow JSON (recursive + regex) for every image in a batch is O(N * M) work.
- Previous implementation re-sanitized already clean data multiple times per image.
📊 Impact:
- Benchmark shows massive reduction in processing time for batches with large metadata.
- Processing 50 images with complex workflow metadata:
- Before: ~8.9s
- After: ~0.34s
- Speedup: ~25x faster for this specific operation.
🔬 Measurement:
- Verified with `tests/benchmark_metadata.py` (simulating 500-node workflow).
- Verified existing tests pass with `python -m unittest discover tests`.
- Default `add_time` to True in `discord_image_node.py` to prevent Discord caching issues.
- Clarify `lossless` tooltip to explain PNG vs WebP behavior.
- Add security warnings to `webhook_url` and `github_token` tooltips.
- Add markdown examples to `discord_message` tooltip.
- Ensure consistency between image and video node tooltips.
- Update `resize_method` tooltip in `discord_image_node.py` to explain when to use each algorithm (e.g., Lanczos for photos, Nearest for pixel art).
- Update `webhook_url` tooltip to clearly label it as sensitive data.
This improves the user experience by helping users make informed decisions directly within the ComfyUI interface.
This change updates the `validate_webhook_url` function to strictly enforce the use of `https://` for Discord webhook URLs. This prevents the accidental transmission of sensitive webhook tokens over unencrypted HTTP connections.
Security Impact:
- Prevents potential Man-in-the-Middle (MitM) attacks from capturing webhook tokens if a user inadvertently copies an `http://` URL (e.g. from a proxy or non-standard source).
- Aligns with Discord's API security best practices.
Changes:
- Updated `WEBHOOK_URL_PATTERNS` regex to require `https`.
- Updated `validate_webhook_url` logic to check for `https://` prefix.
- Added unit test `test_http_url_rejected` to verify the fix.
Avoid unnecessary PIL->Numpy conversion when sending PNGs to Discord, saving ~500ms for 4K images.
This is achieved by tracking if the image was resized and reusing the original numpy array if possible.
The `overwrite_last`, `add_dimensions`, and `resize_to_power_of_2` inputs were checking for string equality `== "enable"`, but the inputs are defined as `BOOLEAN` in `INPUT_TYPES`, which pass Python boolean values (`True`/`False`). This commit fixes the conditional logic to check for truthiness, restoring the functionality of these UI controls.
This is a UX improvement as it fixes broken UI controls.