255 lines
8.2 KiB
Python
255 lines
8.2 KiB
Python
"""
|
||
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",
|
||
)
|
||
|
||
|
||
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"<BanWord(word='{self.word}', type={self.type})>"
|
||
|
||
|
||
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"<TempBanWord(word='{self.word}', expires={self.expires_at})>"
|
||
|
||
|
||
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"<WhitelistWord(word='{self.word}')>"
|
||
|
||
|
||
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"<Admin(user_id={self.user_id})>"
|
||
|
||
|
||
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"<Setting(key='{self.key}', value='{self.value}')>"
|
||
|
||
|
||
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"<SpamStat(user_id={self.user_id}, word='{self.matched_word}')>"
|
||
|
||
|
||
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)
|
||
)
|