""" SQLAlchemy модели для банвордов. """ from datetime import datetime, timezone from enum import Enum as PyEnum from typing import Optional from sqlalchemy import String, Integer, DateTime, Text, Enum, BigInteger from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column __all__ = ( "Base", "BanWordType", "SpamMode", "BanWord", "TempBanWord", "WhitelistWord", "Admin", "Setting", "SpamStat", "SpamLog", "AutoComment", "Report", ) class Base(DeclarativeBase): """Базовый класс для всех моделей""" pass class BanWordType(str, PyEnum): """Типы банвордов""" SUBSTRING = "substring" LEMMA = "lemma" PART = "part" CONFLICT_SUBSTRING = "conflict_substring" CONFLICT_LEMMA = "conflict_lemma" class SpamMode(str, PyEnum): """Режимы работы спам-фильтра""" NORMAL = "normal" SILENCE = "silence" CONFLICT = "conflict" class BanWord(Base): """ Постоянные банворды. Attributes: id: Уникальный ID word: Само слово (lowercase) type: Тип банворда added_by: Telegram ID добавившего админа added_at: Дата добавления reason: Причина добавления """ __tablename__ = "banwords" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) word: Mapped[str] = mapped_column(String(255), nullable=False, index=True) type: Mapped[BanWordType] = mapped_column( Enum(BanWordType, native_enum=False), nullable=False, index=True ) added_by: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) added_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.now, nullable=False ) reason: Mapped[Optional[str]] = mapped_column(Text, nullable=True) def __repr__(self) -> str: return f"" class TempBanWord(Base): """ Временные банворды (с автоудалением). Attributes: id: Уникальный ID word: Само слово type: Тип банворда added_by: ID админа added_at: Дата добавления expires_at: Дата истечения """ __tablename__ = "temp_banwords" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) word: Mapped[str] = mapped_column(String(255), nullable=False, index=True) type: Mapped[BanWordType] = mapped_column( Enum(BanWordType, native_enum=False), nullable=False ) added_by: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) added_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.now, nullable=False ) expires_at: Mapped[datetime] = mapped_column( DateTime, nullable=False, index=True ) def is_expired(self) -> bool: """Проверяет, истёк ли срок""" return datetime.now() >= self.expires_at def __repr__(self) -> str: return f"" class WhitelistWord(Base): """ Белый список (исключения из проверки). Attributes: id: Уникальный ID word: Слово-исключение added_by: ID админа added_at: Дата добавления reason: Причина (например, "ложное срабатывание") """ __tablename__ = "whitelist" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) word: Mapped[str] = mapped_column(String(255), nullable=False, unique=True, index=True) added_by: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) added_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.now, nullable=False ) reason: Mapped[Optional[str]] = mapped_column(Text, nullable=True) def __repr__(self) -> str: return f"" class Admin(Base): """ Дополнительные администраторы бота. Attributes: id: Уникальный ID записи user_id: Telegram ID пользователя (уникальный) added_by: ID суперадмина, который добавил added_at: Дата добавления permissions: JSON со списком прав (для будущего) """ __tablename__ = "admins" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(Integer, nullable=False, unique=True, index=True) added_by: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) added_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.now, nullable=False ) permissions: Mapped[Optional[str]] = mapped_column( Text, default="[]", nullable=True ) def __repr__(self) -> str: return f"" class Setting(Base): """ Настройки и состояния бота. Attributes: key: Ключ настройки (primary key) value: Значение (JSON string) updated_at: Дата обновления Examples: - silence_until: datetime ISO string - conflict_until: datetime ISO string - spam_mode: "normal"/"silence"/"conflict" """ __tablename__ = "settings" key: Mapped[str] = mapped_column(String(100), primary_key=True) value: Mapped[Optional[str]] = mapped_column(Text, nullable=True) updated_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.now, onupdate=datetime.now, nullable=False ) def __repr__(self) -> str: return f"" class SpamStat(Base): """ Статистика удалённых спам-сообщений. Attributes: id: Уникальный ID user_id: Telegram ID отправителя username: Username отправителя chat_id: ID чата message_text: Текст сообщения (до 500 символов) matched_word: Слово, по которому сработал фильтр match_type: Тип проверки (substring/lemma/part) deleted_at: Дата удаления """ __tablename__ = "spam_stats" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True) username: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) chat_id: Mapped[int] = mapped_column(Integer, nullable=False) message_text: Mapped[str] = mapped_column(Text, nullable=False) matched_word: Mapped[str] = mapped_column(String(255), nullable=False) match_type: Mapped[str] = mapped_column(String(50), nullable=False) deleted_at: Mapped[datetime] = mapped_column( DateTime, default=datetime.now, nullable=False, index=True ) def __repr__(self) -> str: return f"" class SpamLog(Base): """Модель для логирования срабатываний спам-фильтра""" __tablename__ = "spam_logs" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(BigInteger, nullable=False) username: Mapped[str] = mapped_column(String(255), nullable=True) chat_id: Mapped[int] = mapped_column(BigInteger, nullable=False) message_text: Mapped[str] = mapped_column(Text, nullable=True) matched_word: Mapped[str] = mapped_column(String(255), nullable=True) # <-- Должно быть! match_type: Mapped[str] = mapped_column(String(50), nullable=True) # <-- Должно быть! timestamp: Mapped[datetime] = mapped_column( DateTime, default=lambda: datetime.now(timezone.utc) ) class AutoComment(Base): """ Настройки автокомментариев для каналов. Attributes: id: Уникальный ID channel_id: ID канала (-100...) text: Текст комментария (HTML) button_text: Текст кнопки button_url: URL кнопки photo_url: URL фото для preview is_enabled: Включены ли автокомментарии для этого канала created_at: Дата создания updated_at: Дата последнего обновления updated_by: ID админа, который последним изменил """ __tablename__ = "auto_comments" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) channel_id: Mapped[int] = mapped_column(BigInteger, nullable=False, unique=True, index=True) text: Mapped[str] = mapped_column(Text, nullable=False) button_text: Mapped[str] = mapped_column(String(100), nullable=False) button_url: Mapped[str] = mapped_column(String(500), nullable=False) photo_url: Mapped[str] = mapped_column(String(500), nullable=False) is_enabled: Mapped[bool] = mapped_column(Integer, default=1, nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime, default=lambda: datetime.now(timezone.utc), nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False ) updated_by: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) def __repr__(self) -> str: return f"" class Report(Base): """ Модель для хранения статистики репортов. Attributes: id: Уникальный ID репорта report_id: Строковый ID репорта (timestamp) reporter_id: ID пользователя, который пожаловался reporter_username: Username жалобщика reported_user_id: ID пользователя, на которого пожаловались reported_username: Username нарушителя chat_id: ID чата, где произошло нарушение chat_title: Название чата message_id: ID сообщения-нарушения message_thread_id: ID топика (если есть) message_text: Текст сообщения (до 500 символов) reason: Причина жалобы status: Статус репорта (pending, closed, banned, deleted) processed_by: ID админа, который обработал created_at: Дата создания репорта processed_at: Дата обработки """ __tablename__ = "reports" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) report_id: Mapped[str] = mapped_column(String(50), nullable=False, unique=True, index=True) # Информация о жалобщике reporter_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True) reporter_username: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # Информация о нарушителе reported_user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True) reported_username: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # Информация о чате и сообщении chat_id: Mapped[int] = mapped_column(BigInteger, nullable=False) chat_title: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) message_id: Mapped[int] = mapped_column(Integer, nullable=False) message_thread_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) message_text: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # Причина и статус reason: Mapped[str] = mapped_column(Text, nullable=False) status: Mapped[str] = mapped_column( String(20), default="pending", nullable=False, index=True ) # pending, closed, banned, deleted # Обработка processed_by: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime, default=lambda: datetime.now(timezone.utc), nullable=False, index=True ) processed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) def __repr__(self) -> str: return f""