Messages within each channel now sort oldest-first (history()/purge()
return newest-first, which read backwards). Added a real mobile layout:
below 700px the sidebar stacks above the message view and its channel
list becomes a horizontally-scrolling chip strip instead of a cramped
vertical column. Report title/footer and the Discord log embeds now
carry Ruby branding alongside the GhostWipe module name.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The HTML report was entirely JavaScript-driven — stats/sidebar/messages only
populated from an embedded JSON blob after load. Viewers that don't execute
scripts (or block network access for local files) showed a blank 0/0/0
skeleton even though the purge worked correctly. Now every stat, the channel
list, and every message are rendered directly into the HTML by Python; JS is
only a progressive enhancement for search. Avatar is downloaded and embedded
as a base64 data URI instead of a live CDN link, so it always renders. Also
parallelized archived-thread lookups, skip the rate-limit pause on channels
with nothing to delete, and post an immediate placeholder log message that
gets edited with the final results instead of the channel sitting silent.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Watches configured channels for image attachments, reposts each image (with
attribution and message text) into a staff-only vault channel, then deletes
the original. Retrieval re-fetches the vault message fresh each time so
there's no stale-link problem, and spoiler tags round-trip intact. Access
to config is admin-only; retrieval defaults to open for everyone.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deletes a member's messages server-wide the moment they leave, are kicked,
or are banned (all fire on_member_remove; audit log classifies which).
Generates a self-contained HTML report with channel-switching, search, and
attachment previews, alongside per-guild config for triggers, exemptions,
scan scope, dry-run mode, and report retention.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Added null check for network existence in all wh set commands
- All setting changes now log to the log channel with descriptive titles
- Prevents KeyError crashes on invalid network names
- Improves audit trail for administrator actions
- Add `allowed_mentions=discord.AllowedMentions(parse=[])` to all webhook and channel sends
- Prevents users from being pinged when replies are relayed across the network
- Fixes issue where reply context could trigger unintended mentions across all servers
- Applies to webhook sends (primary and fallback) and embed/compact relay modes
Remove Red's built-in contact and dm commands in setup() before the Support
cog loads to prevent command registration conflicts. Commands are stored and
restored in teardown() when the cog unloads.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Root cause: _init() was blocking on wait_until_ready() for up to 30 seconds,
causing Red's package load to timeout. Red then reports 'Failed to load
package wormhole (timeout)' but the cog is actually partially loaded,
causing 'already loaded' errors on retry.
Timeline:
1. [p]load wormhole starts
2. setup() calls bot.add_cog(cog) ✓
3. setup() calls await cog._init()
4. _init() blocks on wait_until_ready() for 30s
5. Red's load timeout (30s) triggers
6. Red reports 'timeout' but cog is registered
7. [p]load wormhole again → 'already loaded' error
Solution: Remove wait_until_ready() from _init() since:
- Bot is already ready when cogs load on startup
- Background loops have their own wait_until_ready() calls
- _init() shouldn't block setup()
Background loops still wait_until_ready() independently (correct behavior).
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Problem: Red's internal state has stale 'Wormhole' cog registrations from
previous versions (both identifier 7730187301 and 928374650), causing
'Cog named Wormhole already loaded' errors even on fresh bot starts.
Root cause: Multiple config identifiers registered under same cog name:
- wormhole: {'wormhole': {}} (old lowercase)
- Wormhole: {'7730187301': {}, '928374650': {}} (two identifiers!)
Solution: Rename cog class Wormhole → WormholeV4 to bypass all stale
registration state. This gives a clean slate without requiring users to
manually purge config data.
Changes:
- class Wormhole → class WormholeV4 in core.py
- Updated __init__.py import and setup()
- Config identifier unchanged (928374650)
This completely sidesteps the phantom load bug without touching user data.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Problem: If bot crashes or shuts down ungracefully, context menus stay
registered in Discord's command tree. On restart, add_command() fails
with 'already loaded' error.
Solution: Remove existing context menus before registering new ones.
This makes registration idempotent (safe to run multiple times).
Changes:
- _register_context_menus() now removes existing menus first
- Wrapped in try/except to handle 'not found' gracefully
- Ensures clean reload even after ungraceful shutdown
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Keeping the 4,782-line v3.4.6 monolith as wormhole.py.old for:
- Historical reference
- Rollback safety
- Migration verification
This file is NOT loaded by the cog - it's purely archival.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
**The Problem:**
Discord CDN URLs (even proxy URLs) expire after message deletion, so image
previews in logs would show as broken after a few minutes.
**The Solution:**
- New "reupload" feature (enabled by default) downloads and re-uploads attachments
- Deleted images/GIFs are permanently preserved in the log channel
- Works with aiohttp to download attachments asynchronously
- Attaches files directly to log message (up to 10 per message)
**Features:**
- `[p]rlog attachments reupload` - Toggle re-uploading on/off
- Downloads images when message is deleted
- Re-uploads as Discord Files (permanent storage)
- Falls back to URL embedding if download fails
- Shows status in settings display
**Improvements:**
- Use proxy URLs throughout for better persistence
- Pass cached attachments through message object
- Better file extension detection (.png, .jpg, .jpeg, .gif, .webp)
- Track ephemeral attachments
- Exception handling for download failures
**Trade-offs:**
- Uses bandwidth to download/upload
- Files become permanent in log channel
- Worth it for permanent image preservation!
Images and GIFs now remain visible in logs forever! 🖼️
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
**Fixed Duplicate Logs:**
- Skip registering generic handlers for message_delete and bulk_delete
- These events are now handled only by specialized attachment handlers
- Eliminates 2x delete logs and 3x edit logs issue
**Added Image Previews:**
- Deleted messages now show image previews in the embed
- Cached attachments display the actual image/GIF
- Multiple images shown as separate embeds with numbering
- set_image() on main embed for first attachment
**Improved Message Edit Display:**
- No more duplicate "Argument 1" and "Argument 2"
- Clean before/after content comparison
- Shows context (author, channel, jump link) separately
- Better content truncation (200 chars vs 50)
- Pin status with emoji (📌 Pinned)
- Tracks attachment count changes
**Better Change Detection:**
- Message edits: old/new content, pin status, embeds, attachments
- Cleaner formatting with proper line breaks
- Longer content previews for readability
All logs are now single, beautiful embeds with image previews!
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Fixed issue where help was being sent twice by completely removing our
custom help command and instead hooking Red's bot.send_help_for method.
This allows Red's help command to process normally, then we intercept
the actual help rendering when CV2 is enabled.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
User-install contexts (DMs, group DMs, non-member guilds) now only show
commands that can actually work outside a guild:
- _command_needs_guild() heuristic checks @guild_only, Red privilege
levels (MOD/ADMIN/GUILD_OWNER), and user permission requirements,
walking the parent chain for subcommands.
- Autocomplete pre-filters the candidate pool in user-install contexts
and shows available DM-compatible commands when nothing is typed.
- Command execution blocks guild-only commands early with a clear
ephemeral error instead of letting them fail cryptically.
Also:
- Slash command description changed to 'Shine bright like a Ruby'
- Added [p]userslash setdescription command to change it on the fly
(persisted in Config, applied on cog load)
The join hybrid_group has 34 subcommands which exceeds Discord's 25
subcommand limit for slash command groups. Since UserSlash already
bridges all text commands to slash, the hybrid registration is redundant.
Changed commands.hybrid_group → commands.group to avoid the ValueError.
The file userslash/commands.py shadowed redbot.core.commands when Python
registered the submodule on the parent package, causing:
AttributeError: module 'userslash.commands' has no attribute 'Cog'
Renaming the file to slash_bridge.py avoids the collision entirely.
Also adds a user-install whitelist feature:
- [p]userslash whitelist enable/disable
- [p]userslash whitelist add/remove @user
- [p]userslash whitelist list
- [p]userslash whitelist clear
When enabled, only whitelisted users may use the bot via user-install
(DMs, group DMs, non-member guilds). Guild-installed usage is unaffected.
Replaces OneTrueSlash with full user-install support:
- Single proxy slash command bridges all text commands
- Works in servers, DMs, and group DMs
- REST API patcher sets integration_types & contexts on all global commands
- Owner commands: [p]userslash sync/patch/status
- Fuzzy autocomplete (rapidfuzz)
- Enhanced InterContext/InterMessage for non-guild contexts
- Mirror channels now receive relayed messages (was blocked from sending but never sent to)
- Server allowlist enforced in relay (allowlist_servers gate check)
- Silent mode suppresses status broadcasts (_status respects 'silent' flag)
- Welcome message sent when channel joins via 'wh open'
Commands now show as 'wh create', 'wh staff add', 'wh set relaymode'
instead of 'wh-create', 'wh-staff add', 'wh-set relaymode'.
- Added commands/_base.py with root 'wh' group and all 20 subgroup stubs
- All 13 command mixins inherit from WormholeBase
- Decorators changed: @commands.hybrid_command(name='wh-X') → @WormholeBase.wh.command(name='X')
- Subgroup commands: @wh_X.command() → @WormholeBase.wh_X.command()
- Removed duplicate 'wh' group from core.py (now in _base.py)
- Fixed alias: 'wh-color' → 'color' for colour command
The hybrid_command decorator on mixin classes already auto-registers
slash commands. The manual registrations in _register_context_menus
clashed, causing 'wh-list already registered' on load. Removed all
manual slash commands — only context menus remain.
Embed mode was missing foreign emoji support. Now all three relay
modes (webhook, embed, compact) show unknown emojis as embed
thumbnails via attachment:// scheme.
File attachments render as massive images in Discord regardless of
source size. Switch to embed thumbnails via the attachment:// scheme:
1. Download emoji from CDN at 48px
2. Attach as a File to the message
3. Reference via attachment://name.png in a minimal embed's thumbnail
4. Thumbnails render at ~80×80 px — much closer to emoji size
Fresh File/Embed objects are built per channel (consumed on send).
Bumps to v3.4.6.
File attachments render as massive images in Discord. Replace the
download-and-attach approach with clickable [:name:](cdn_url) inline
links — keeps foreign emojis compact and inline with the message text.
Removes aiohttp dependency and simplifies the helper to synchronous.
Bumps to v3.4.5.
Custom emojis the bot can't see (bot.get_emoji returns None) are now:
1. Replaced with :name: in message text
2. Downloaded from Discord CDN at 64px
3. Attached as small image files to the relay message
Known emojis (from any server the bot is in) keep the <:name:id>
syntax and render normally. Up to 5 emoji images per message.
Works in webhook and compact relay modes. Bumps to v3.4.4.
The forward map only stores relay targets — the source channel (where
the original message was typed) was never included. When someone in
Server B replied to a relayed message, the button for Server A fell
back to the fallback URL pointing at Server B instead of Server A.
Fix: after building the relay-target URLs, identify the source channel
by finding network channels not present in the forward map, and map
them to the original message ID.
Passing view=None to wh.send() on every message caused all relays
to fail. Now uses **_view_kw dict unpacking so the view parameter
is only included when there is actually a reply-jump button to show.
When a reply is relayed across servers, each copy now includes a URL
button underneath the quoted reply text:
- If the referenced message has a relayed copy in the target channel,
the button links to that local copy (so users stay in their own server).
- Otherwise, it falls back to the original message URL.
Works across all relay modes (webhook, embed, compact) and leverages
the existing msg_map to resolve per-channel message IDs.
Bumps version to 3.4.3.
Ultimate owner-only server management with whitelist, blacklist,
interactive browser, purge, lock mode, logging, export & more.
Co-authored-by: Viktor <viktor@getviktor.com>