# 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, help_text="Токен от @BotFather", ) is_active = models.BooleanField( default=True, help_text="Если включен — может быть запущен runbots", ) 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): """ Конфигурация для конкретного бота (парсинг, 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", ) class Meta: verbose_name = "Конфигурация бота" verbose_name_plural = "Конфигурации ботов" def __str__(self) -> str: return f"Config for {self.bot}" class TelegramChat(models.Model): """ Чаты храним ПО БОТУ. Один и тот же chat_id TG может встречаться у разных ботов, поэтому уникальность задаётся на (bot, chat_id). Важно: не используем поле с именем 'id' под chat_id Telegram, чтобы не ломать Django. Стандартный PK (BigAutoField) оставляем как есть. """ CHAT_TYPES = [ ("private", "private"), ("group", "group"), ("supergroup", "supergroup"), ("channel", "channel"), ] 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, 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, 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}]"