Initial commit — Wormhole v3.0.0: advanced cross-server relay cog
Features: - Named wormhole networks with full per-network config - Webhook relay with custom avatar/name modes (user, server, both, custom) - Message edit, delete, reply, reaction, thread & sticker sync - Staff system (owner + staff) with granular permissions - Moderation: user ban/mute, server ban/mute, allowlist - Word & regex content filters with auto-delete - Token-bucket rate limiting per user per network - Per-network logging channel - Statistics dashboard - NSFW gating, freeze/pause, silent mode - Custom network icon, description, accent colour - Server nickname overrides - Ownership transfer - Thread support - Embed forwarding - Professional README, info.json, LICENSE
This commit is contained in:
commit
6ca37b9ba1
7 changed files with 1716 additions and 0 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 everestmcarthur
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
78
README.md
Normal file
78
README.md
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
# EveCogs
|
||||||
|
|
||||||
|
> A curated collection of advanced, professional-grade cogs for [Red-DiscordBot](https://github.com/Cog-Creators/Red-DiscordBot).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
[p]repo add EveCogs https://github.com/everestmcarthur/EveCogs
|
||||||
|
[p]cog install EveCogs <cog_name>
|
||||||
|
[p]load <cog_name>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌀 Wormhole
|
||||||
|
|
||||||
|
**The most advanced cross-server relay system for Red-DiscordBot.**
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Highlights
|
||||||
|
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
### 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Overview
|
||||||
|
|
||||||
|
| 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` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT — do whatever you want.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Made with ❤️ by everestmcarthur*
|
||||||
6
info.json
Normal file
6
info.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
6
wormhole/__init__.py
Normal file
6
wormhole/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from .wormhole import Wormhole
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot):
|
||||||
|
cog = Wormhole(bot)
|
||||||
|
await bot.add_cog(cog)
|
||||||
18
wormhole/info.json
Normal file
18
wormhole/info.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"min_bot_version": "3.5.0",
|
||||||
|
"min_python_version": [3, 9, 0],
|
||||||
|
"tags": [
|
||||||
|
"wormhole",
|
||||||
|
"relay",
|
||||||
|
"bridge",
|
||||||
|
"cross-server",
|
||||||
|
"network",
|
||||||
|
"moderation",
|
||||||
|
"webhook"
|
||||||
|
]
|
||||||
|
}
|
||||||
113
wormhole/utils.py
Normal file
113
wormhole/utils.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
"""Utility helpers for Wormhole cog."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from collections import defaultdict
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# ── Cooldown bucket ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class CooldownBucket:
|
||||||
|
"""Per-user per-network token-bucket rate limiter."""
|
||||||
|
|
||||||
|
def __init__(self, rate: int = 5, per: float = 10.0):
|
||||||
|
self.rate = rate
|
||||||
|
self.per = per
|
||||||
|
self._buckets: dict[str, list[float]] = defaultdict(list)
|
||||||
|
|
||||||
|
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]
|
||||||
|
if len(self._buckets[key]) >= self.rate:
|
||||||
|
return True
|
||||||
|
self._buckets[key].append(now)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def update(self, rate: int, per: float):
|
||||||
|
self.rate = rate
|
||||||
|
self.per = per
|
||||||
|
|
||||||
|
|
||||||
|
# ── 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:
|
||||||
|
return word
|
||||||
|
for pattern in regex_filters:
|
||||||
|
try:
|
||||||
|
if re.search(pattern, content, re.IGNORECASE):
|
||||||
|
return pattern
|
||||||
|
except re.error:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# ── Embed builders ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def make_embed(
|
||||||
|
title: str = "",
|
||||||
|
description: str = "",
|
||||||
|
colour: discord.Colour = COLOUR_INFO,
|
||||||
|
**kwargs,
|
||||||
|
) -> discord.Embed:
|
||||||
|
em = discord.Embed(title=title, description=description, colour=colour, **kwargs)
|
||||||
|
return em
|
||||||
|
|
||||||
|
|
||||||
|
def ok_embed(description: str, title: str = "✅ Success") -> discord.Embed:
|
||||||
|
return make_embed(title=title, description=description, colour=COLOUR_OK)
|
||||||
|
|
||||||
|
|
||||||
|
def err_embed(description: str, title: str = "❌ Error") -> discord.Embed:
|
||||||
|
return make_embed(title=title, description=description, colour=COLOUR_ERR)
|
||||||
|
|
||||||
|
|
||||||
|
def warn_embed(description: str, title: str = "⚠️ Warning") -> discord.Embed:
|
||||||
|
return make_embed(title=title, description=description, colour=COLOUR_WARN)
|
||||||
|
|
||||||
|
|
||||||
|
def info_embed(description: str, title: str = "ℹ️ Info") -> discord.Embed:
|
||||||
|
return make_embed(title=title, description=description, colour=COLOUR_INFO)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Formatting helpers ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def truncate(text: str, length: int = 2000) -> str:
|
||||||
|
if len(text) <= length:
|
||||||
|
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")
|
||||||
|
return " ".join(parts)
|
||||||
1474
wormhole/wormhole.py
Normal file
1474
wormhole/wormhole.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue