Implement GuardDen Discord moderation bot
Features: - Core moderation: warn, kick, ban, timeout, strike system - Automod: banned words filter, scam detection, anti-spam, link filtering - AI moderation: Claude/OpenAI integration, NSFW detection, phishing analysis - Verification system: button, captcha, math, emoji challenges - Rate limiting system with configurable scopes - Event logging: joins, leaves, message edits/deletes, voice activity - Per-guild configuration with caching - Docker deployment support Bug fixes applied: - Fixed await on session.delete() in guild_config.py - Fixed memory leak in AI moderation message tracking (use deque) - Added error handling to bot shutdown - Added error handling to timeout command - Removed unused Literal import - Added prefix validation - Added image analysis limit (3 per message) - Fixed test mock for SQLAlchemy model
This commit is contained in:
117
src/guardden/models/guild.py
Normal file
117
src/guardden/models/guild.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""Guild-related database models."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Boolean, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from guardden.models.base import Base, SnowflakeID, TimestampMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from guardden.models.moderation import ModerationLog, Strike
|
||||
|
||||
|
||||
class Guild(Base, TimestampMixin):
|
||||
"""Represents a Discord guild (server) configuration."""
|
||||
|
||||
__tablename__ = "guilds"
|
||||
|
||||
id: Mapped[int] = mapped_column(SnowflakeID, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
owner_id: Mapped[int] = mapped_column(SnowflakeID, nullable=False)
|
||||
premium: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
|
||||
# Relationships
|
||||
settings: Mapped["GuildSettings"] = relationship(
|
||||
back_populates="guild", uselist=False, cascade="all, delete-orphan"
|
||||
)
|
||||
banned_words: Mapped[list["BannedWord"]] = relationship(
|
||||
back_populates="guild", cascade="all, delete-orphan"
|
||||
)
|
||||
moderation_logs: Mapped[list["ModerationLog"]] = relationship(
|
||||
back_populates="guild", cascade="all, delete-orphan"
|
||||
)
|
||||
strikes: Mapped[list["Strike"]] = relationship(
|
||||
back_populates="guild", cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
|
||||
class GuildSettings(Base, TimestampMixin):
|
||||
"""Per-guild bot settings and configuration."""
|
||||
|
||||
__tablename__ = "guild_settings"
|
||||
|
||||
guild_id: Mapped[int] = mapped_column(
|
||||
SnowflakeID, ForeignKey("guilds.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
|
||||
# General settings
|
||||
prefix: Mapped[str] = mapped_column(String(10), default="!", nullable=False)
|
||||
locale: Mapped[str] = mapped_column(String(10), default="en", nullable=False)
|
||||
|
||||
# Channel configuration (stored as snowflake IDs)
|
||||
log_channel_id: Mapped[int | None] = mapped_column(SnowflakeID, nullable=True)
|
||||
mod_log_channel_id: Mapped[int | None] = mapped_column(SnowflakeID, nullable=True)
|
||||
welcome_channel_id: Mapped[int | None] = mapped_column(SnowflakeID, nullable=True)
|
||||
|
||||
# Role configuration
|
||||
mute_role_id: Mapped[int | None] = mapped_column(SnowflakeID, nullable=True)
|
||||
verified_role_id: Mapped[int | None] = mapped_column(SnowflakeID, nullable=True)
|
||||
mod_role_ids: Mapped[dict] = mapped_column(JSONB, default=list, nullable=False)
|
||||
|
||||
# Moderation settings
|
||||
automod_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
anti_spam_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||
link_filter_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
|
||||
# Strike thresholds (actions at each threshold)
|
||||
strike_actions: Mapped[dict] = mapped_column(
|
||||
JSONB,
|
||||
default=lambda: {
|
||||
"1": {"action": "warn"},
|
||||
"3": {"action": "timeout", "duration": 3600},
|
||||
"5": {"action": "kick"},
|
||||
"7": {"action": "ban"},
|
||||
},
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# AI moderation settings
|
||||
ai_moderation_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
ai_sensitivity: Mapped[int] = mapped_column(Integer, default=50, nullable=False) # 0-100 scale
|
||||
nsfw_detection_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
|
||||
# Verification settings
|
||||
verification_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
verification_type: Mapped[str] = mapped_column(
|
||||
String(20), default="button", nullable=False
|
||||
) # button, captcha, questions
|
||||
|
||||
# Relationship
|
||||
guild: Mapped["Guild"] = relationship(back_populates="settings")
|
||||
|
||||
|
||||
class BannedWord(Base, TimestampMixin):
|
||||
"""Banned words/phrases for a guild with regex support."""
|
||||
|
||||
__tablename__ = "banned_words"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
guild_id: Mapped[int] = mapped_column(
|
||||
SnowflakeID, ForeignKey("guilds.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
|
||||
pattern: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
is_regex: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
action: Mapped[str] = mapped_column(
|
||||
String(20), default="delete", nullable=False
|
||||
) # delete, warn, strike
|
||||
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
# Who added this and when
|
||||
added_by: Mapped[int] = mapped_column(SnowflakeID, nullable=False)
|
||||
|
||||
# Relationship
|
||||
guild: Mapped["Guild"] = relationship(back_populates="banned_words")
|
||||
Reference in New Issue
Block a user