Enhance AutoMod configuration by introducing event-specific settings and thresholds. Refactor Automod schemas to include event configurations, thresholds, and limits for ignored roles and channels. Update database interactions to support new structure and improve handling of AutoMod rules in cogs, ensuring dynamic thresholds for various events. Update dashboard components to accommodate new configurations and improve user experience in managing AutoMod settings.
This commit is contained in:
187
bot/utils/automod_settings.py
Normal file
187
bot/utils/automod_settings.py
Normal file
@@ -0,0 +1,187 @@
|
||||
# ╔══════════════════════════════════════════════════════════════════╗
|
||||
# ║ ║
|
||||
# ║ +-+-+-+-+-+-+-+-+ ║
|
||||
# ║ |H|e|x|a|H|o|s|t| ║
|
||||
# ║ +-+-+-+-+-+-+-+-+ ║
|
||||
# ║ ║
|
||||
# ║ © 2026 HexaHost — All Rights Reserved ║
|
||||
# ║ ║
|
||||
# ║ discord ── https://discord.gg/hexahost ║
|
||||
# ║ github ── https://github.com/theoneandonlymace ║
|
||||
# ║ ║
|
||||
# ╚══════════════════════════════════════════════════════════════════╝
|
||||
"""Shared AutoMod settings: event IDs, punishments, thresholds."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import aiosqlite
|
||||
|
||||
from utils.db_paths import db_path
|
||||
|
||||
# API id -> bot DB event name
|
||||
EVENT_ID_TO_DB: dict[str, str] = {
|
||||
"anti_spam": "Anti spam",
|
||||
"anti_caps": "Anti caps",
|
||||
"anti_link": "Anti link",
|
||||
"anti_invites": "Anti invites",
|
||||
"anti_mass_mention": "Anti mass mention",
|
||||
"anti_emoji_spam": "Anti emoji spam",
|
||||
"anti_nsfw_link": "Anti NSFW link",
|
||||
}
|
||||
|
||||
# Also accept legacy UI ids
|
||||
EVENT_ID_ALIASES: dict[str, str] = {
|
||||
"anti_links": "anti_link",
|
||||
"anti_mentions": "anti_mass_mention",
|
||||
}
|
||||
|
||||
DB_EVENT_TO_ID: dict[str, str] = {v: k for k, v in EVENT_ID_TO_DB.items()}
|
||||
|
||||
VALID_PUNISHMENTS = frozenset({"Mute", "Kick", "Ban"})
|
||||
PUNISHMENT_ALIASES: dict[str, str] = {
|
||||
"mute": "Mute",
|
||||
"kick": "Kick",
|
||||
"ban": "Ban",
|
||||
"Mute": "Mute",
|
||||
"Kick": "Kick",
|
||||
"Ban": "Ban",
|
||||
"delete": "Mute", # legacy dashboard — map to Mute
|
||||
"warn": "Mute",
|
||||
}
|
||||
|
||||
MAX_IGNORED = 10
|
||||
|
||||
NSFW_KEYWORDS = [
|
||||
"porn", "xxx", "adult", "sex", "nsfw", "xnxx", "onlyfans", "brazzers",
|
||||
"xhamster", "xvideos", "pornhub", "redtube", "livejasmin", "youporn",
|
||||
"tube8", "pornhat", "swxvid", "ixxx",
|
||||
]
|
||||
|
||||
DEFAULT_THRESHOLDS: dict[str, dict[str, float]] = {
|
||||
"anti_spam": {"max_messages": 5, "window_seconds": 10, "mute_minutes": 12},
|
||||
"anti_caps": {"percent": 70, "min_length": 45, "mute_minutes": 1},
|
||||
"anti_link": {"mute_minutes": 7},
|
||||
"anti_invites": {"mute_minutes": 12},
|
||||
"anti_mass_mention": {"max_mentions": 5, "mute_minutes": 3},
|
||||
"anti_emoji_spam": {"max_emojis": 5, "mute_minutes": 1},
|
||||
"anti_nsfw_link": {},
|
||||
}
|
||||
|
||||
THRESHOLD_META: dict[str, list[dict[str, Any]]] = {
|
||||
"anti_spam": [
|
||||
{"key": "max_messages", "label": "Max messages", "min": 2, "max": 20, "step": 1},
|
||||
{"key": "window_seconds", "label": "Window (seconds)", "min": 3, "max": 60, "step": 1},
|
||||
{"key": "mute_minutes", "label": "Mute minutes", "min": 1, "max": 1440, "step": 1},
|
||||
],
|
||||
"anti_caps": [
|
||||
{"key": "percent", "label": "Caps percent", "min": 40, "max": 100, "step": 1},
|
||||
{"key": "min_length", "label": "Min message length", "min": 10, "max": 200, "step": 1},
|
||||
{"key": "mute_minutes", "label": "Mute minutes", "min": 1, "max": 1440, "step": 1},
|
||||
],
|
||||
"anti_link": [
|
||||
{"key": "mute_minutes", "label": "Mute minutes", "min": 1, "max": 1440, "step": 1},
|
||||
],
|
||||
"anti_invites": [
|
||||
{"key": "mute_minutes", "label": "Mute minutes", "min": 1, "max": 1440, "step": 1},
|
||||
],
|
||||
"anti_mass_mention": [
|
||||
{"key": "max_mentions", "label": "Max mentions", "min": 2, "max": 20, "step": 1},
|
||||
{"key": "mute_minutes", "label": "Mute minutes", "min": 1, "max": 1440, "step": 1},
|
||||
],
|
||||
"anti_emoji_spam": [
|
||||
{"key": "max_emojis", "label": "Max emojis", "min": 2, "max": 30, "step": 1},
|
||||
{"key": "mute_minutes", "label": "Mute minutes", "min": 1, "max": 1440, "step": 1},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def normalize_event_id(raw: str) -> str | None:
|
||||
if raw in EVENT_ID_TO_DB:
|
||||
return raw
|
||||
if raw in EVENT_ID_ALIASES:
|
||||
return EVENT_ID_ALIASES[raw]
|
||||
if raw in DB_EVENT_TO_ID:
|
||||
return DB_EVENT_TO_ID[raw]
|
||||
return None
|
||||
|
||||
|
||||
def normalize_punishment(raw: str | None) -> str | None:
|
||||
if raw is None:
|
||||
return None
|
||||
return PUNISHMENT_ALIASES.get(raw) or PUNISHMENT_ALIASES.get(raw.strip())
|
||||
|
||||
|
||||
async def ensure_thresholds_table(db: aiosqlite.Connection) -> None:
|
||||
await db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS automod_thresholds (
|
||||
guild_id INTEGER,
|
||||
event TEXT,
|
||||
key TEXT,
|
||||
value REAL,
|
||||
PRIMARY KEY (guild_id, event, key)
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
async def get_thresholds(guild_id: int, event_id: str) -> dict[str, float]:
|
||||
"""Return merged defaults + DB overrides for an API event id."""
|
||||
defaults = dict(DEFAULT_THRESHOLDS.get(event_id, {}))
|
||||
db_event = EVENT_ID_TO_DB.get(event_id)
|
||||
if not db_event:
|
||||
return defaults
|
||||
|
||||
async with aiosqlite.connect(db_path("automod.db")) as db:
|
||||
await ensure_thresholds_table(db)
|
||||
cursor = await db.execute(
|
||||
"SELECT key, value FROM automod_thresholds WHERE guild_id = ? AND event = ?",
|
||||
(guild_id, db_event),
|
||||
)
|
||||
for key, value in await cursor.fetchall():
|
||||
defaults[str(key)] = float(value)
|
||||
return defaults
|
||||
|
||||
|
||||
async def get_all_thresholds(guild_id: int) -> dict[str, dict[str, float]]:
|
||||
out: dict[str, dict[str, float]] = {
|
||||
eid: dict(vals) for eid, vals in DEFAULT_THRESHOLDS.items()
|
||||
}
|
||||
async with aiosqlite.connect(db_path("automod.db")) as db:
|
||||
await ensure_thresholds_table(db)
|
||||
cursor = await db.execute(
|
||||
"SELECT event, key, value FROM automod_thresholds WHERE guild_id = ?",
|
||||
(guild_id,),
|
||||
)
|
||||
for event, key, value in await cursor.fetchall():
|
||||
eid = DB_EVENT_TO_ID.get(event)
|
||||
if not eid:
|
||||
continue
|
||||
out.setdefault(eid, {})[str(key)] = float(value)
|
||||
return out
|
||||
|
||||
|
||||
async def set_thresholds(
|
||||
db: aiosqlite.Connection,
|
||||
guild_id: int,
|
||||
thresholds: dict[str, dict[str, float]],
|
||||
) -> None:
|
||||
await ensure_thresholds_table(db)
|
||||
for event_id, pairs in thresholds.items():
|
||||
eid = normalize_event_id(event_id)
|
||||
if not eid or eid == "anti_nsfw_link":
|
||||
continue
|
||||
db_event = EVENT_ID_TO_DB[eid]
|
||||
allowed = DEFAULT_THRESHOLDS.get(eid, {})
|
||||
for key, value in pairs.items():
|
||||
if key not in allowed:
|
||||
continue
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO automod_thresholds (guild_id, event, key, value)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""",
|
||||
(guild_id, db_event, key, float(value)),
|
||||
)
|
||||
Reference in New Issue
Block a user