v3.2.0 — Phase 3: DM relay, starboard, karma, auto-mod, highlights, search & more

Complete ground-up rewrite (2,762 lines across 3 files):

Phase 3 features:
- Bidirectional DM relay (subscribe, send, receive, 3 formats)
- Cross-network starboard with configurable threshold
- Reputation/karma system with leaderboard
- Keyword highlight notifications (DM alerts)
- Anti-raid detection with auto-freeze
- Attachment filters (blocked extensions, max filesize)
- Pin sync across channels
- Typing indicator relay
- Network discovery (public flag + tags)
- Message search across network
- Scheduled messages (timed broadcasts)
- Network-wide slowmode
- Relay delay (configurable)
- Vanity invite codes
- Per-channel setting overrides
- Purge command (mass delete relayed)
- Audit log (500 entries, full trail)
- MOTD and rules
- Comprehensive README with full command reference

Phase 2 features (carried forward):
- Auto-moderation engine (spam, caps, invites, links, zalgo, spoiler, emote/newline spam)
- Invite system with expiry and max uses
- Staff announcements and portal messages
- Welcome messages and mention control
- User profiles and network stats
- Backup/restore (JSON export/import)
- Blackout scheduling
- Global blocklist (bot owner)
- Embed and compact relay modes

Phase 1 features (carried forward):
- Named networks with webhook relay
- Edit/delete/reply/reaction/sticker sync
- Staff system with ownership transfer
- Word and regex content filters
- Rate limiting and NSFW gate
- Server nicknames, custom identity
- Logging channel
This commit is contained in:
everestmcarthur 2026-05-07 22:19:09 +00:00
parent 6ca37b9ba1
commit 680b87db39
6 changed files with 2528 additions and 1203 deletions

250
README.md
View file

@ -1,78 +1,222 @@
# EveCogs
# 🌀 EveCogs
> A curated collection of advanced, professional-grade cogs for [Red-DiscordBot](https://github.com/Cog-Creators/Red-DiscordBot).
A premium cog repository for [Red-DiscordBot](https://github.com/Cog-Creators/Red-DiscordBot).
---
## 📦 Installation
## Installation
```
[p]repo add EveCogs https://github.com/everestmcarthur/EveCogs
[p]cog install EveCogs <cog_name>
[p]load <cog_name>
[p]cog install EveCogs wormhole
[p]load wormhole
```
---
## 🌀 Wormhole
# 🌀 Wormhole v3.2.0
**The most advanced cross-server relay system for Red-DiscordBot.**
**The ultimate cross-server message relay for Discord.**
Connect channels across unlimited servers into named networks. Every message, edit, delete, reaction, reply, sticker, embed, and file is relayed in real-time via webhooks — with full moderation, staff roles, filters, rate-limiting, and per-network customisation.
Connect channels across multiple servers into named networks. Messages, edits, deletes, reactions, replies, stickers, pins, typing indicators, and files are relayed in real-time. Features DM relay, starboard, karma, auto-moderation, invites, portals, and 50+ commands.
### Highlights
## ✨ Features
| Feature | Description |
|---|---|
| **Named Networks** | Create unlimited named networks — each with its own config, staff, and channels |
| **Webhook Relay** | Messages appear under the sender's name and avatar (or server icon, or custom) |
| **Edit & Delete Sync** | Edits and deletes propagate across every linked channel instantly |
| **Reply Context** | Replies include a preview of the referenced message |
| **Reaction Sync** | Reactions mirror across channels |
| **Sticker & Embed Forwarding** | Stickers, rich embeds, and file attachments are all relayed |
| **Staff System** | Network owner + unlimited staff with moderation access |
| **User Ban / Mute** | Ban or mute individual users from the network |
| **Server Ban / Mute** | Block or silence entire servers |
| **Server Allowlist** | Restrict which servers may join a network |
| **Word & Regex Filters** | Auto-delete messages that match filters before relay |
| **Rate Limiting** | Token-bucket per-user per-network spam protection |
| **Logging Channel** | Designate a channel to receive moderation logs |
| **Custom Identity** | Name mode (user / server / both / custom template), image mode, custom icon |
| **Server Nicknames** | Override how your server's name appears on the network |
| **Freeze / Pause** | Temporarily halt all relay on a network |
| **Silent Mode** | Suppress join/leave announcements |
| **NSFW Gating** | Block relay from NSFW channels |
| **Thread Support** | Optionally relay messages from threads |
| **Ownership Transfer** | Hand a network to another user |
| **Statistics** | Track total messages relayed, channels linked, and more |
### 📡 Core Relay
- **Named networks** — create unlimited named relay networks
- **Three relay modes** — `webhook` (rich identity), `embed` (styled cards), `compact` (minimal text)
- **Full sync** — edits, deletes, reactions, replies, stickers, threads, pins, typing indicators
- **File forwarding** — images, videos, documents all relayed automatically
- **Embed forwarding** — rich embeds from bots/links passed through
- **Reply context** — quoted reply previews carried across servers
### Quick Start
### 📧 DM Relay
- **Subscribe** to receive network messages directly in your DMs
- **Send from DMs** — participate in networks without being in a server channel
- **Three DM formats** — embed, compact, or plain text
- **Bidirectional** — full two-way communication via DMs
### 🛡️ Moderation
- **Ban / mute** individual users or entire servers
- **Word filters** — block specific words
- **Regex filters** — pattern-based content blocking
- **Allowlist** — restrict which servers can join
- **Purge** — mass delete relayed messages across all channels
- **Audit log** — full trail of all admin/mod actions (last 500 entries)
### 🤖 Auto-Moderation Engine
- **Anti-spam** — duplicate message detection with configurable window/threshold
- **Anti-mention spam** — block mass mentions
- **Anti-caps** — configurable caps percentage threshold
- **Anti-invite** — block Discord invite links
- **Anti-link** — block all URLs
- **Anti-zalgo** — block combining character abuse
- **Anti-spoiler spam** — block excessive spoiler tags
- **Anti-emote spam** — limit custom emoji per message
- **Anti-newline spam** — limit vertical space abuse
- **Anti-raid** — auto-freeze network on flood detection
### ⭐ Starboard
- Cross-network starboard — star reactions collected across all channels
- Configurable threshold and dedicated starboard channel
- Auto-updating star count on the board embed
### 💎 Karma / Reputation
- React with a configurable emoji to give karma
- Per-user karma scores tracked across the network
- Leaderboard command
### 🔔 Keyword Highlights
- Subscribe to keywords — get DM'd when they're mentioned
- Per-user, per-network keyword lists
### 🔗 Invites
- **Invite codes** — shareable codes with optional max uses and expiry
- **Vanity invites** — custom words (e.g. `gaming` instead of `xK4mR2pQ`)
- Join networks from any server with just a code
### 📢 Communication
- **Staff announcements** — broadcast to all channels + DM subscribers
- **Scheduled messages** — send timed announcements
- **MOTD** — message of the day shown on join
- **Rules** — network rules displayed on demand
- **Welcome messages** — greet new servers automatically
### 🌀 Portal Messages
- Persistent, auto-updating status embeds pinned in channels
- Shows network status, channel count, features, MOTD
- Refreshes every 5 minutes automatically
### 🔍 Search & Discovery
- **Search** — search recent messages across the entire network
- **Network discovery** — list public networks with tags
- **Tags** — categorize networks for easy discovery
### ⚙️ Advanced Configuration
- **Per-channel overrides** — different relay mode per channel
- **Server nicknames** — custom display names per server
- **Custom identity** — custom icons, names, colour accents
- **Name modes** — show user, server, both, or custom template
- **Mention control** — strip @everyone, @here, @role, @user
- **Attachment filters** — block file types and enforce size limits
- **Relay delay** — configurable seconds before relay (for review)
- **Slowmode** — network-wide per-user cooldown
- **Rate limiting** — configurable messages per time window
- **NSFW gate** — block relay from NSFW channels
- **Freeze** — pause a network instantly
- **Silent mode** — suppress join/leave notifications
### 🌙 Blackout Scheduling
- Auto-freeze/unfreeze on schedule
- Per-day-of-week and hour range (UTC)
- Multiple schedules per network
### 💾 Backup & Restore
- Export entire network config as JSON
- Restore to same or different name
- Preserves all settings, filters, profiles
### 👤 User Profiles & Stats
- Per-user message counts, first-seen date, server list
- Network-wide statistics with top-5 leaderboard
- Karma integration in profiles
### 🌐 Global Blocklist (Bot Owner)
- Block users/servers from ALL wormhole networks
- Independent of per-network moderation
### ⭐ Staff System
- **Owner** — full control, can add/remove staff
- **Staff** — moderation, settings, filters, announcements
- **Ownership transfer** — hand off networks to other users
## 📋 Quick Start
```
[p]wh create my-network A cool global chat
[p]wh open my-network (run in each channel you want linked)
[p]wh set webhooks my-network true
[p]wh set name-mode my-network both
[p]wh info my-network
[p]wh help
[p]wh create mynetwork A cool cross-server chat!
[p]wh open mynetwork # in each channel you want linked
[p]wh invite create mynetwork # share the code
[p]wh dm enable mynetwork # enable DM relay
[p]wh set relay-mode mynetwork webhook
[p]wh automod enable mynetwork
[p]wh starboard enable mynetwork #starboard 3
[p]wh karma enable mynetwork 👍
```
### Command Overview
## 📖 Command Reference
| Group | Commands |
|---|---|
| `[p]wh` | `create`, `delete`, `open`, `close`, `list`, `info`, `stats`, `transfer`, `help` |
| `[p]wh set` | `webhooks`, `name-mode`, `image-mode`, `custom-icon`, `custom-name`, `description`, `colour`, `ratelimit`, `log-channel`, `nickname`, `freeze`, `silent`, `nsfw-gate`, `sync-edits`, `sync-deletes`, `sync-reactions`, `sync-replies`, `sync-stickers`, `sync-threads`, `forward-embeds` |
| `[p]wh staff` | `add`, `remove`, `list` |
| `[p]wh mod` | `ban`, `unban`, `mute`, `unmute`, `ban-server`, `unban-server`, `mute-server`, `unmute-server`, `allowlist-add`, `allowlist-remove` |
| `[p]wh filter` | `add-word`, `remove-word`, `add-regex`, `remove-regex`, `list` |
Run `[p]wh help` in Discord for the full interactive reference (3-page embed).
---
### Network Management
| Command | Description |
|---------|-------------|
| `wh create <name> [desc]` | Create a new network |
| `wh delete <name>` | Delete a network (with confirmation) |
| `wh open <name>` | Link current channel |
| `wh close [name]` | Unlink current channel |
| `wh list` | List all networks |
| `wh discover` | List public networks |
| `wh info <name>` | Detailed network info |
| `wh stats <name>` | Statistics & leaderboard |
| `wh transfer <name> @user` | Transfer ownership |
| `wh announce <name> <msg>` | Staff broadcast |
| `wh portal [name]` | Create/refresh portal |
| `wh search <name> <query>` | Search messages |
| `wh schedule <name> <min> <msg>` | Scheduled message |
| `wh rules <name>` | Show rules |
| `wh motd <name>` | Show MOTD |
| `wh audit <name> [n]` | View audit log |
| `wh backup <name>` | Export JSON backup |
| `wh restore [name]` | Import from JSON |
### DM Relay
| Command | Description |
|---------|-------------|
| `wh dm enable/disable <name>` | Toggle DM relay |
| `wh dm mode <name> <format>` | Set DM format |
| `wh dm subscribe <name>` | Subscribe to DMs |
| `wh dm unsubscribe <name>` | Unsubscribe |
| `wh dm send <name> <msg>` | Send via DM |
| `wh dm list` | Your subscriptions |
### Settings (`wh set`)
`relay-mode` · `webhooks` · `name-mode` · `image-mode` · `custom-icon` · `custom-name` · `description` · `colour` · `ratelimit` · `slowmode` · `relay-delay` · `log-channel` · `nickname` · `welcome` · `motd` · `rules` · `tags` · `public` · `max-filesize` · `blocked-extensions` · `freeze` · `silent` · `nsfw-gate` · `sync-edits` · `sync-deletes` · `sync-reactions` · `sync-replies` · `sync-stickers` · `sync-threads` · `sync-pins` · `sync-typing` · `forward-embeds` · `strip-everyone` · `strip-roles` · `strip-users` · `channel-override`
### Moderation (`wh mod`)
`ban/unban` · `mute/unmute` · `ban-server/unban-server` · `mute-server/unmute-server` · `allowlist-add/remove` · `purge`
### Auto-Moderation (`wh automod`)
`enable/disable` · `anti-spam` · `anti-mentions` · `anti-caps` · `anti-invite` · `anti-link` · `anti-zalgo` · `anti-spoiler` · `anti-emote-spam` · `anti-newlines` · `anti-raid` · `status`
### More
| Category | Commands |
|----------|----------|
| **Staff** | `wh staff add/remove/list` |
| **Invites** | `wh invite create/vanity/use/revoke/list` |
| **Filters** | `wh filter add-word/remove-word/add-regex/remove-regex/list` |
| **Starboard** | `wh starboard enable/disable` |
| **Karma** | `wh karma enable/disable/check/leaderboard` |
| **Highlights** | `wh highlight add/remove/list` |
| **Blackout** | `wh blackout add/clear/list` |
| **Profiles** | `wh profile <name> [@user]` |
| **Global** | `wh global ban-user/unban-user/ban-server/unban-server/list` |
## 📊 Architecture
```
EveCogs/
├── README.md
├── LICENSE (MIT)
├── info.json (repo metadata)
└── wormhole/
├── __init__.py (cog loader + data statement)
├── info.json (cog metadata)
├── utils.py (helpers, embeds, detectors, filters)
└── wormhole.py (main cog — 2,200+ lines, 50+ commands)
```
## 📄 License
MIT — do whatever you want.
MIT — see [LICENSE](LICENSE).
---
## 🤝 Credits
*Made with ❤️ by everestmcarthur*
Built by [everestmcarthur](https://github.com/everestmcarthur) with assistance from Viktor AI.

View file

@ -1,6 +1,8 @@
{
"author": ["everestmcarthur"],
"description": "EveCogs — a curated collection of advanced, professional-grade cogs for Red-DiscordBot.",
"install_msg": "Thank you for installing **EveCogs**! Browse available cogs with `[p]cog list` and install what you need.",
"short": "Advanced cogs for Red-DiscordBot"
"install_msg": "Welcome to **EveCogs**! Use `[p]cog list` to see available cogs.",
"name": "EveCogs",
"short": "A collection of advanced cogs for Red-DiscordBot.",
"description": "EveCogs is a premium cog repository featuring Wormhole — the ultimate cross-server relay system with DM relay, starboard, karma, auto-moderation, invites, portals, and 50+ commands.",
"type": "REPO"
}

View file

@ -1,5 +1,13 @@
"""Wormhole — the ultimate cross-server relay cog for Red-DiscordBot."""
from .wormhole import Wormhole
__red_end_user_data_statement__ = (
"This cog stores Discord user IDs, guild IDs, and channel IDs for relay functionality. "
"Per-user data includes message counts, karma scores, keyword highlights, and DM relay subscriptions. "
"Users may request deletion of their data via the bot owner."
)
async def setup(bot):
cog = Wormhole(bot)

View file

@ -1,18 +1,28 @@
{
"author": ["everestmcarthur"],
"description": "Wormhole — an advanced cross-server/cross-channel communication relay with named networks, moderation, staff roles, message sync (edits/deletes/replies/reactions), per-network customisation, logging, statistics, filters, rate-limiting, and much more.",
"install_msg": "Thanks for installing **Wormhole**! Run `[p]wh help` to get started.",
"short": "Advanced cross-server relay with networks, moderation & full sync",
"end_user_data_statement": "This cog stores user IDs, guild IDs, channel IDs, and message-mapping data required for relay functionality. No message content is stored persistently.",
"install_msg": "Thanks for installing **Wormhole v3.2.0** — the ultimate cross-server relay!\n\nGet started: `[p]wh create <network-name>` then `[p]wh open <network-name>` in channels you want to link.\n\nFull help: `[p]wh help`",
"name": "Wormhole",
"disabled": false,
"short": "The ultimate cross-server message relay with DM relay, starboard, karma, auto-mod, invites & more.",
"description": "Wormhole v3.2.0 connects Discord channels across multiple servers into named networks. Messages, edits, deletes, reactions, replies, stickers, pins, typing indicators, and files are all relayed in real-time via webhooks, embeds, or compact text. Features include: bidirectional DM relay, cross-network starboard, reputation/karma system, keyword highlight notifications, auto-moderation engine (spam/caps/invites/links/zalgo/raid detection), network invite codes with vanity URLs, staff announcements, persistent portal embeds, user profiles and stats, backup/restore, blackout scheduling, message search, scheduled messages, per-channel overrides, network discovery, relay delay, slowmode, purge, audit log, attachment filters, and a global blocklist.",
"tags": [
"relay",
"crossserver",
"wormhole",
"chat",
"bridge",
"network",
"webhook",
"moderation",
"automod",
"starboard",
"karma",
"dm",
"global"
],
"type": "COG",
"end_user_data_statement": "This cog stores Discord user IDs, guild IDs, and channel IDs for relay functionality. Per-user data includes message counts, karma scores, keyword highlights, and DM relay subscriptions.",
"min_bot_version": "3.5.0",
"min_python_version": [3, 9, 0],
"tags": [
"wormhole",
"relay",
"bridge",
"cross-server",
"network",
"moderation",
"webhook"
]
"requirements": []
}

View file

@ -1,21 +1,50 @@
"""Utility helpers for Wormhole cog."""
"""
Utility helpers for Wormhole v3.2.0 — the ultimate cross-server relay cog.
"""
from __future__ import annotations
import hashlib
import json
import random
import re
import string
import time
from collections import defaultdict
from typing import Optional
from datetime import datetime, timezone
from typing import Dict, List, Optional, Set, Tuple
import discord
# ── Colour palette ──────────────────────────────────────────────────────────
COLOUR_OK = discord.Colour.from_rgb(87, 242, 135) # green
COLOUR_WARN = discord.Colour.from_rgb(254, 231, 92) # yellow
COLOUR_ERR = discord.Colour.from_rgb(237, 66, 69) # red
COLOUR_INFO = discord.Colour.from_rgb(88, 101, 242) # blurple
COLOUR_NEUTRAL = discord.Colour.from_rgb(114, 137, 218) # light blurple
COLOUR_OK = discord.Colour.from_rgb(87, 242, 135)
COLOUR_WARN = discord.Colour.from_rgb(254, 231, 92)
COLOUR_ERR = discord.Colour.from_rgb(237, 66, 69)
COLOUR_INFO = discord.Colour.from_rgb(88, 101, 242)
COLOUR_NEUTRAL = discord.Colour.from_rgb(114, 137, 218)
COLOUR_ANNOUNCE = discord.Colour.from_rgb(255, 163, 26)
COLOUR_STAR = discord.Colour.from_rgb(255, 172, 51)
COLOUR_DM = discord.Colour.from_rgb(87, 165, 242)
_SERVER_COLOURS = [
discord.Colour.from_rgb(231, 76, 60),
discord.Colour.from_rgb(46, 204, 113),
discord.Colour.from_rgb(52, 152, 219),
discord.Colour.from_rgb(155, 89, 182),
discord.Colour.from_rgb(241, 196, 15),
discord.Colour.from_rgb(26, 188, 156),
discord.Colour.from_rgb(230, 126, 34),
discord.Colour.from_rgb(233, 30, 99),
discord.Colour.from_rgb(0, 188, 212),
discord.Colour.from_rgb(139, 195, 74),
discord.Colour.from_rgb(255, 87, 34),
discord.Colour.from_rgb(121, 85, 72),
]
def server_colour(guild_id: int) -> discord.Colour:
return _SERVER_COLOURS[guild_id % len(_SERVER_COLOURS)]
# ── Cooldown bucket ────────────────────────────────────────────────────────
@ -31,9 +60,7 @@ class CooldownBucket:
def is_rate_limited(self, user_id: int, network: str) -> bool:
key = f"{network}:{user_id}"
now = time.monotonic()
bucket = self._buckets[key]
# Purge old timestamps
self._buckets[key] = [t for t in bucket if now - t < self.per]
self._buckets[key] = [t for t in self._buckets[key] if now - t < self.per]
if len(self._buckets[key]) >= self.rate:
return True
self._buckets[key].append(now)
@ -47,7 +74,6 @@ class CooldownBucket:
# ── Filter helpers ──────────────────────────────────────────────────────────
def check_filters(content: str, word_filters: list[str], regex_filters: list[str]) -> Optional[str]:
"""Return the first matched filter pattern, or None if clean."""
lowered = content.lower()
for word in word_filters:
if word.lower() in lowered:
@ -61,32 +87,286 @@ def check_filters(content: str, word_filters: list[str], regex_filters: list[str
return None
# ── Auto-moderation helpers ─────────────────────────────────────────────────
INVITE_PATTERN = re.compile(
r"(discord\.gg|discord\.com/invite|discordapp\.com/invite|dsc\.gg)/[\w-]+",
re.IGNORECASE,
)
LINK_PATTERN = re.compile(r"https?://\S+", re.IGNORECASE)
MENTION_PATTERN = re.compile(r"<@!?\d+>|<@&\d+>|@everyone|@here")
BLOCKED_EXTENSIONS = {
".exe", ".bat", ".cmd", ".scr", ".pif", ".msi", ".com",
".vbs", ".vbe", ".js", ".jse", ".wsf", ".wsh", ".ps1",
}
def check_automod(content: str, automod_config: dict) -> Optional[str]:
if not automod_config:
return None
if automod_config.get("anti_invite") and INVITE_PATTERN.search(content):
return "Discord invite link detected"
if automod_config.get("anti_link") and LINK_PATTERN.search(content):
return "Link detected"
if automod_config.get("anti_mention_spam"):
max_mentions = automod_config.get("max_mentions", 5)
if len(MENTION_PATTERN.findall(content)) > max_mentions:
return f"Too many mentions"
if automod_config.get("anti_caps"):
threshold = automod_config.get("caps_threshold", 0.7)
alpha = [c for c in content if c.isalpha()]
if len(alpha) >= 10:
ratio = sum(1 for c in alpha if c.isupper()) / len(alpha)
if ratio >= threshold:
return f"Excessive caps ({ratio:.0%})"
if automod_config.get("anti_zalgo"):
# Detect zalgo text (combining characters)
combining = sum(1 for c in content if '\u0300' <= c <= '\u036f' or '\u0489' <= c <= '\u0489')
if combining > 10:
return "Zalgo text detected"
if automod_config.get("anti_spoiler"):
spoiler_count = content.count("||")
if spoiler_count >= 6: # 3+ spoiler blocks
return "Excessive spoiler tags"
if automod_config.get("anti_emote_spam"):
max_emotes = automod_config.get("max_emotes", 10)
emote_count = len(re.findall(r"<a?:\w+:\d+>", content))
if emote_count > max_emotes:
return f"Too many custom emotes ({emote_count})"
if automod_config.get("anti_newline_spam"):
max_newlines = automod_config.get("max_newlines", 15)
if content.count("\n") > max_newlines:
return "Too many newlines"
return None
def check_attachment_filters(
attachments: list,
blocked_exts: Optional[set] = None,
max_filesize: Optional[int] = None,
) -> Optional[str]:
"""Check attachments against extension and size filters."""
exts = blocked_exts or set()
for att in attachments:
name_lower = att.filename.lower()
for ext in exts:
if name_lower.endswith(ext):
return f"Blocked file type: {ext}"
if max_filesize and att.size > max_filesize:
mb = max_filesize / (1024 * 1024)
return f"File too large (max {mb:.1f} MB)"
return None
class DuplicateDetector:
"""Detects duplicate/spam messages per user per network."""
def __init__(self, window: float = 30.0, threshold: int = 3):
self.window = window
self.threshold = threshold
self._history: Dict[Tuple[str, int], List[Tuple[str, float]]] = defaultdict(list)
def is_duplicate(self, network: str, user_id: int, content: str) -> bool:
key = (network, user_id)
now = time.monotonic()
content_hash = hashlib.md5(content.lower().strip().encode()).hexdigest()
self._history[key] = [(h, t) for h, t in self._history[key] if now - t < self.window]
dup_count = sum(1 for h, _ in self._history[key] if h == content_hash)
self._history[key].append((content_hash, now))
if len(self._history[key]) > 50:
self._history[key] = self._history[key][-25:]
return dup_count >= self.threshold
def update(self, window: float, threshold: int):
self.window = window
self.threshold = threshold
class RaidDetector:
"""Detects raid patterns: many new unique users posting in short window."""
def __init__(self, window: float = 60.0, user_threshold: int = 10):
self.window = window
self.user_threshold = user_threshold
# {network: [(user_id, timestamp), ...]}
self._activity: Dict[str, List[Tuple[int, float]]] = defaultdict(list)
def record(self, network: str, user_id: int) -> bool:
"""Record activity. Returns True if raid detected."""
now = time.monotonic()
self._activity[network] = [
(uid, t) for uid, t in self._activity[network] if now - t < self.window
]
self._activity[network].append((user_id, now))
unique = len(set(uid for uid, _ in self._activity[network]))
return unique >= self.user_threshold
# ── Invite code generation ──────────────────────────────────────────────────
def generate_invite_code(length: int = 8) -> str:
chars = string.ascii_letters + string.digits
return "".join(random.choices(chars, k=length))
# ── Mention sanitisation ───────────────────────────────────────────────────
def sanitise_mentions(content: str, config: dict) -> str:
if config.get("strip_everyone"):
content = content.replace("@everyone", "@\u200beveryone").replace("@here", "@\u200bhere")
if config.get("strip_role_mentions"):
content = re.sub(r"<@&(\d+)>", r"@role", content)
if config.get("strip_user_mentions"):
content = re.sub(r"<@!?(\d+)>", r"@user", content)
return content
# ── Embed builders ──────────────────────────────────────────────────────────
def make_embed(
title: str = "",
description: str = "",
colour: discord.Colour = COLOUR_INFO,
**kwargs,
def make_embed(title: str = "", description: str = "", colour: discord.Colour = COLOUR_INFO, **kw) -> discord.Embed:
return discord.Embed(title=title, description=description, colour=colour, **kw)
def ok_embed(desc: str, title: str = "✅ Success") -> discord.Embed:
return make_embed(title=title, description=desc, colour=COLOUR_OK)
def err_embed(desc: str, title: str = "❌ Error") -> discord.Embed:
return make_embed(title=title, description=desc, colour=COLOUR_ERR)
def warn_embed(desc: str, title: str = "⚠️ Warning") -> discord.Embed:
return make_embed(title=title, description=desc, colour=COLOUR_WARN)
def info_embed(desc: str, title: str = "ℹ️ Info") -> discord.Embed:
return make_embed(title=title, description=desc, colour=COLOUR_INFO)
def announce_embed(desc: str, title: str = "📢 Announcement") -> discord.Embed:
return make_embed(title=title, description=desc, colour=COLOUR_ANNOUNCE)
def star_embed(desc: str, title: str = "⭐ Starred") -> discord.Embed:
return make_embed(title=title, description=desc, colour=COLOUR_STAR)
def dm_embed(desc: str, title: str = "✉️ DM Relay") -> discord.Embed:
return make_embed(title=title, description=desc, colour=COLOUR_DM)
# ── Embed relay builder ────────────────────────────────────────────────────
def build_relay_embed(
message: discord.Message,
server_nick: Optional[str] = None,
net_colour: Optional[int] = None,
) -> discord.Embed:
em = discord.Embed(title=title, description=description, colour=colour, **kwargs)
guild_label = server_nick or message.guild.name
colour = discord.Colour(net_colour) if net_colour else server_colour(message.guild.id)
em = discord.Embed(description=message.content or "*[no text]*", colour=colour, timestamp=message.created_at)
em.set_author(name=message.author.display_name, icon_url=message.author.display_avatar.url)
footer_icon = message.guild.icon.url if message.guild.icon else discord.Embed.Empty
em.set_footer(text=f"🌐 {guild_label} • #{message.channel.name}", icon_url=footer_icon)
if message.attachments:
img_set = False
file_lines = []
for att in message.attachments:
if att.content_type and att.content_type.startswith("image/") and not img_set:
em.set_image(url=att.url)
img_set = True
else:
file_lines.append(f"[{att.filename}]({att.url})")
if file_lines:
em.add_field(name="📎 Attachments", value="\n".join(file_lines), inline=False)
if message.stickers:
em.add_field(name="🎨 Stickers", value=", ".join(f"[{s.name}]({s.url})" for s in message.stickers), inline=False)
return em
def ok_embed(description: str, title: str = "✅ Success") -> discord.Embed:
return make_embed(title=title, description=description, colour=COLOUR_OK)
def build_dm_relay_embed(
user: discord.User,
content: str,
network_name: str,
) -> discord.Embed:
"""Build an embed for messages sent from DM to the network."""
em = discord.Embed(description=content, colour=COLOUR_DM, timestamp=datetime.now(timezone.utc))
em.set_author(name=f"{user.display_name} (via DM)", icon_url=user.display_avatar.url)
em.set_footer(text=f"✉️ DM → {network_name}")
return em
def err_embed(description: str, title: str = "❌ Error") -> discord.Embed:
return make_embed(title=title, description=description, colour=COLOUR_ERR)
def build_dm_incoming_embed(
author_name: str,
author_avatar: str,
server_name: str,
channel_name: str,
content: str,
network_name: str,
net_colour: Optional[int] = None,
) -> discord.Embed:
"""Build an embed for network messages forwarded to DM subscribers."""
colour = discord.Colour(net_colour) if net_colour else COLOUR_INFO
em = discord.Embed(description=content or "*[no text]*", colour=colour, timestamp=datetime.now(timezone.utc))
em.set_author(name=author_name, icon_url=author_avatar)
em.set_footer(text=f"🌐 {server_name} • #{channel_name} • {network_name}")
return em
def warn_embed(description: str, title: str = "⚠️ Warning") -> discord.Embed:
return make_embed(title=title, description=description, colour=COLOUR_WARN)
# ── Portal embed builder ───────────────────────────────────────────────────
def build_portal_embed(
network_name: str, data: dict, channel_count: int, total_messages: int,
) -> discord.Embed:
colour = discord.Colour(data["colour"]) if data.get("colour") else COLOUR_INFO
desc = data.get("description") or "*A wormhole network.*"
status = "❄️ Frozen" if data.get("frozen") else "🟢 Active"
em = discord.Embed(title=f"🌀 Wormhole — {network_name}", description=desc, colour=colour)
em.add_field(name="Status", value=status, inline=True)
em.add_field(name="Channels", value=str(channel_count), inline=True)
em.add_field(name="Messages", value=f"{total_messages:,}", inline=True)
dm_count = len(data.get("dm_subscribers", []))
if dm_count:
em.add_field(name="DM Subscribers", value=str(dm_count), inline=True)
features = []
for key, label in [
("use_webhooks", "Webhooks"), ("sync_edits", "Edit sync"),
("sync_deletes", "Delete sync"), ("sync_reactions", "Reaction sync"),
("sync_replies", "Reply sync"), ("dm_enabled", "DM relay"),
]:
if data.get(key):
features.append(f"✅ {label}")
if features:
em.add_field(name="Features", value=" • ".join(features), inline=False)
if data.get("motd"):
em.add_field(name="📋 MOTD", value=data["motd"][:256], inline=False)
if data.get("custom_icon"):
em.set_thumbnail(url=data["custom_icon"])
em.set_footer(text="Portal auto-updates • Wormhole v3.2.0")
em.timestamp = datetime.now(timezone.utc)
return em
def info_embed(description: str, title: str = "ℹ️ Info") -> discord.Embed:
return make_embed(title=title, description=description, colour=COLOUR_INFO)
# ── Starboard embed ────────────────────────────────────────────────────────
def build_star_embed(
author_name: str,
author_avatar: str,
content: str,
stars: int,
server_name: str,
channel_name: str,
image_url: Optional[str] = None,
) -> discord.Embed:
em = discord.Embed(description=content, colour=COLOUR_STAR, timestamp=datetime.now(timezone.utc))
em.set_author(name=author_name, icon_url=author_avatar)
em.add_field(name="⭐ Stars", value=str(stars), inline=True)
em.set_footer(text=f"{server_name} • #{channel_name}")
if image_url:
em.set_image(url=image_url)
return em
# ── Formatting helpers ──────────────────────────────────────────────────────
@ -96,18 +376,26 @@ def truncate(text: str, length: int = 2000) -> str:
return text
return text[: length - 3] + "..."
def human_timedelta(seconds: float) -> str:
m, s = divmod(int(seconds), 60)
h, m = divmod(m, 60)
d, h = divmod(h, 24)
parts = []
if d:
parts.append(f"{d}d")
if h:
parts.append(f"{h}h")
if m:
parts.append(f"{m}m")
if s or not parts:
parts.append(f"{s}s")
if d: parts.append(f"{d}d")
if h: parts.append(f"{h}h")
if m: parts.append(f"{m}m")
if s or not parts: parts.append(f"{s}s")
return " ".join(parts)
def compact_format(server_name: str, user_name: str, content: str) -> str:
return truncate(f"**[{server_name}] {user_name}:** {content}", 2000)
def format_audit_entry(action: str, user: str, target: str = "", details: str = "") -> dict:
"""Create an audit log entry."""
return {
"timestamp": datetime.now(timezone.utc).isoformat(),
"action": action,
"user": user,
"target": target,
"details": details,
}

File diff suppressed because it is too large Load diff