miltu-bot refactor

This commit is contained in:
2025-08-08 11:37:11 +09:00
parent 9750735089
commit ce28f29c69
12 changed files with 518 additions and 178 deletions

View File

@@ -1,36 +1,90 @@
# bot/models.py
from django.db import models
from django.utils import timezone
class TelegramBot(models.Model):
name = models.CharField(max_length=100, help_text="Произвольное имя бота в проекте")
username = models.CharField(max_length=100, blank=True, help_text="@username в Telegram")
token = models.CharField(max_length=200)
is_active = models.BooleanField(default=True)
"""
Карточка бота. Можно держать несколько активных одновременно.
"""
name = models.CharField(
max_length=100,
help_text="Произвольное имя бота в проекте",
)
username = models.CharField(
max_length=100,
blank=True,
help_text="@username в Telegram",
)
token = models.CharField(
max_length=200,
help_text="Токен от @BotFather",
)
is_active = models.BooleanField(
default=True,
help_text="Если включен — может быть запущен runbots",
)
def __str__(self):
class Meta:
verbose_name = "Telegram бот"
verbose_name_plural = "Telegram боты"
ordering = ("-is_active", "name")
def __str__(self) -> str:
return f"{self.name} ({self.username or 'no-username'})"
class BotConfig(models.Model):
bot = models.OneToOneField(TelegramBot, on_delete=models.CASCADE, related_name="config")
parse_mode = models.CharField(
max_length=20, default="HTML",
choices=[("HTML", "HTML"), ("MarkdownV2", "MarkdownV2"), ("None", "None")]
"""
Конфигурация для конкретного бота (парсинг, allowed_updates, админы, вебхук).
Делается OneToOne, но без опоры на обратный атрибут .config у TelegramBot.
"""
bot = models.OneToOneField(
TelegramBot,
on_delete=models.CASCADE,
help_text="Какому боту принадлежит конфигурация",
)
parse_mode = models.CharField(
max_length=20,
default="HTML",
choices=[("HTML", "HTML"), ("MarkdownV2", "MarkdownV2"), ("None", "None")],
help_text="Режим парсинга сообщений (или None для обычного текста)",
)
allowed_updates = models.JSONField(
default=list,
blank=True,
help_text="Список типов апдейтов, например ['message','my_chat_member','chat_member']",
)
admin_user_ids = models.JSONField(
default=list,
blank=True,
help_text="Список Telegram user_id, имеющих права администратора",
)
webhook_url = models.URLField(
blank=True,
default="",
help_text="URL вебхука (если используете вебхуки)",
)
use_webhook = models.BooleanField(
default=False,
help_text="Включить режим webhook вместо polling",
)
allowed_updates = models.JSONField(default=list, blank=True)
admin_user_ids = models.JSONField(default=list, blank=True, help_text="Список Telegram user_id админов")
webhook_url = models.URLField(blank=True, default="", help_text="Если используете вебхуки")
use_webhook = models.BooleanField(default=False)
def __str__(self):
class Meta:
verbose_name = "Конфигурация бота"
verbose_name_plural = "Конфигурации ботов"
def __str__(self) -> str:
return f"Config for {self.bot}"
class TelegramChat(models.Model):
"""
Храним все чаты, где бот состоит/состоял.
chat_id в TG может быть до ~52 бит => BigInteger.
Чаты храним ПО БОТУ. Один и тот же chat_id TG может встречаться у разных ботов,
поэтому уникальность задаётся на (bot, chat_id).
Важно: не используем поле с именем 'id' под chat_id Telegram, чтобы не ломать Django.
Стандартный PK (BigAutoField) оставляем как есть.
"""
CHAT_TYPES = [
("private", "private"),
@@ -39,23 +93,71 @@ class TelegramChat(models.Model):
("channel", "channel"),
]
id = models.BigIntegerField(primary_key=True) # chat_id как PK
type = models.CharField(max_length=20, choices=CHAT_TYPES)
title = models.CharField(max_length=255, blank=True, default="")
username = models.CharField(max_length=255, blank=True, default="")
bot = models.ForeignKey(
TelegramBot,
on_delete=models.CASCADE,
related_name="chats",
help_text="К какому боту относится этот чат",
)
chat_id = models.BigIntegerField(
help_text="Идентификатор чата в Telegram (может быть до ~52 бит)",
)
type = models.CharField(
max_length=20,
choices=CHAT_TYPES,
help_text="Тип чата",
)
title = models.CharField(
max_length=255,
blank=True,
default="",
help_text="Название (для групп/каналов)",
)
username = models.CharField(
max_length=255,
blank=True,
default="",
help_text="Username чата/канала (если есть)",
)
# Статус участия бота
is_member = models.BooleanField(default=True)
joined_at = models.DateTimeField(default=timezone.now)
left_at = models.DateTimeField(null=True, blank=True)
is_member = models.BooleanField(
default=True,
help_text="Состоит ли бот в чате сейчас",
)
joined_at = models.DateTimeField(
default=timezone.now,
help_text="Когда бот был добавлен",
)
left_at = models.DateTimeField(
null=True,
blank=True,
help_text="Когда бот покинул чат",
)
# Метаданные
last_message_at = models.DateTimeField(null=True, blank=True)
def __str__(self):
base = self.title or self.username or str(self.id)
return f"{base} [{self.type}]"
# Метаданные активности
last_message_at = models.DateTimeField(
null=True,
blank=True,
help_text="Когда последний раз видели сообщение в этом чате",
)
class Meta:
verbose_name = "Telegram чат"
verbose_name_plural = "Telegram чаты"
constraints = [
models.UniqueConstraint(
fields=["bot", "chat_id"],
name="uniq_bot_chat",
),
]
indexes = [
models.Index(fields=["bot", "chat_id"]),
models.Index(fields=["bot", "type"]),
models.Index(fields=["bot", "is_member"]),
]
ordering = ("-is_member", "-joined_at")
def __str__(self) -> str:
base = self.title or self.username or str(self.chat_id)
return f"{base} [{self.type}]"