297 lines
10 KiB
Python
297 lines
10 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",
|
||
"AutoComment",
|
||
)
|
||
|
||
|
||
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)
|
||
)
|
||
|
||
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"<AutoComment(channel_id={self.channel_id}, enabled={self.is_enabled})>"
|