Files
PrimoGuardBot/database/models.py

373 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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):
"""Типы банвордов"""
WORD = "word"
LEMMA = "lemma"
PART = "part"
CONFLICT_WORD = "conflict_word"
CONFLICT_LEMMA = "conflict_lemma"
CONFLICT_PART = "conflict_part"
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,
values_callable=lambda enum: [e.value for e in enum]
),
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,
values_callable=lambda enum: [e.value for e in enum]
),
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
)
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})>"
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"<Report(id={self.report_id}, reporter={self.reporter_id}, reported={self.reported_user_id})>"