Bot container restart from admin-panel
This commit is contained in:
@@ -1,11 +1,36 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
from .models import BotConfig
|
||||
from .models import BotConfig, WelcomeMessage
|
||||
|
||||
from .tasks import restart_bot_container
|
||||
|
||||
@admin.register(BotConfig)
|
||||
class BotConfigAdmin(admin.ModelAdmin):
|
||||
list_display = ("bot_name", "channel_id", "bot_token")
|
||||
search_fields = ("bot_name", "channel_id", "bot_token")
|
||||
change_form_template = "admin/botconfig_change_form.html"
|
||||
|
||||
def get_urls(self):
|
||||
urls = super().get_urls()
|
||||
custom_urls = [
|
||||
path(
|
||||
"<int:botconfig_id>/restart/",
|
||||
self.admin_site.admin_view(self.restart_bot),
|
||||
name="botconfig-restart"
|
||||
),
|
||||
]
|
||||
return custom_urls + urls
|
||||
|
||||
def restart_bot(self, request, botconfig_id):
|
||||
try:
|
||||
BotConfig.objects.get(id=botconfig_id)
|
||||
except BotConfig.DoesNotExist:
|
||||
self.message_user(request, "Настройка бота с таким ID не найдена.", level=messages.ERROR)
|
||||
return redirect("..")
|
||||
|
||||
restart_bot_container.delay()
|
||||
self.message_user(request, "Бот будет перезапущен в фоновом режиме.", messages.SUCCESS)
|
||||
return redirect("..")
|
||||
|
||||
@admin.register(WelcomeMessage)
|
||||
class WWelcomeMessageAdmin(admin.ModelAdmin):
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# bot/handlers.py
|
||||
import re
|
||||
from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from telegram.ext import ContextTypes
|
||||
from asgiref.sync import sync_to_async
|
||||
from webapp.models import BindingRequest, Client, Invoice
|
||||
from draw.models import Lottery, DrawResult, LotteryParticipant
|
||||
from draw.models import DrawResult
|
||||
from bot.state import BINDING_PENDING
|
||||
from bot.models import BotConfig
|
||||
|
||||
|
||||
@sync_to_async
|
||||
def get_client_by_telegram_id(telegram_id: int):
|
||||
@@ -32,9 +33,11 @@ def create_binding_request(telegram_chat_id, client_card):
|
||||
client_card=client_card
|
||||
)
|
||||
|
||||
|
||||
async def whoami(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if not update.message:
|
||||
return
|
||||
|
||||
chat_id = update.message.chat_id
|
||||
client = await get_client_by_telegram_id(chat_id)
|
||||
if not client:
|
||||
@@ -45,7 +48,7 @@ async def whoami(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
|
||||
invoices = await get_invoices_for_client(client)
|
||||
draws = await get_draws_for_client(client)
|
||||
|
||||
|
||||
message_text = f"👤 *Профиль клиента:*\n" \
|
||||
f"• *Имя:* {client.name}\n" \
|
||||
f"• *Клубная карта:* {client.club_card_number}\n\n"
|
||||
@@ -55,8 +58,7 @@ async def whoami(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
show_all = len(invoices) > 5
|
||||
displayed_invoices = invoices[:5] if show_all else invoices
|
||||
for inv in displayed_invoices:
|
||||
invoice_str = str(inv).split('/')[0].strip()
|
||||
message_text += f" • {invoice_str} (*{inv.sum}*)\n"
|
||||
message_text += f" • ID {inv.id} (*{inv.sum}*)\n"
|
||||
else:
|
||||
message_text += " _Нет счетов_\n"
|
||||
|
||||
@@ -64,12 +66,8 @@ async def whoami(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if draws:
|
||||
for draw in draws:
|
||||
lottery_name = draw.lottery.name if draw.lottery else "неизвестно"
|
||||
invoice_info = (draw.participant.invoice
|
||||
if draw.participant and hasattr(draw.participant, 'invoice')
|
||||
else "неизвестно")
|
||||
prize_info = (draw.prize.reward
|
||||
if draw.prize and hasattr(draw.prize, 'reward')
|
||||
else "неизвестно")
|
||||
invoice_info = getattr(draw.participant.invoice, 'id', 'неизвестно') if draw.participant else 'неизвестно'
|
||||
prize_info = getattr(draw.prize, 'reward', 'неизвестно')
|
||||
message_text += f" • Лотерея: *{lottery_name}*\n Счет: _{invoice_info}_\n Выигрыш: *{prize_info}*\n\n"
|
||||
else:
|
||||
message_text += " _Нет результатов розыгрышей_\n"
|
||||
@@ -82,43 +80,37 @@ async def whoami(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
|
||||
await update.message.reply_text(message_text, parse_mode="Markdown", reply_markup=reply_markup)
|
||||
|
||||
|
||||
async def handle_client_card(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""
|
||||
Обработчик команды /bind.
|
||||
После ввода команды бот запрашивает ввод номера КК и ожидает сообщение с кодом.
|
||||
Здесь мы просто добавляем ID пользователя в pending.
|
||||
"""
|
||||
if not update.message:
|
||||
return
|
||||
|
||||
telegram_chat_id = update.message.chat_id
|
||||
# Если пользователь уже привязан, сообщаем об этом.
|
||||
client = await get_client_by_telegram_id(telegram_chat_id)
|
||||
if client:
|
||||
await update.message.reply_text("✅ Вы уже привязаны!")
|
||||
return
|
||||
|
||||
# Добавляем пользователя в список pending и просим ввести номер КК.
|
||||
BINDING_PENDING.add(telegram_chat_id)
|
||||
await update.message.reply_text("Введите номер вашей клубной карты (КК):")
|
||||
|
||||
|
||||
async def process_binding_input(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""
|
||||
Обрабатывает ввод номера КК от пользователя, который находится в состоянии ожидания.
|
||||
"""
|
||||
if not update.message or not update.message.text:
|
||||
return
|
||||
|
||||
client_card = update.message.text.strip()
|
||||
telegram_chat_id = update.message.chat_id
|
||||
|
||||
if not re.fullmatch(r'\d{3,}', client_card):
|
||||
await update.message.reply_text("❌ Неверный формат клиентской карты. Пожалуйста, введите корректный код (минимум 3 цифры).")
|
||||
await update.message.reply_text("❌ Неверный формат клиентской карты. Пожалуйста, введите минимум 3 цифры.")
|
||||
return
|
||||
|
||||
await create_binding_request(telegram_chat_id, client_card)
|
||||
if telegram_chat_id in BINDING_PENDING:
|
||||
BINDING_PENDING.remove(telegram_chat_id)
|
||||
BINDING_PENDING.discard(telegram_chat_id)
|
||||
await update.message.reply_text("✅ Заявка отправлена. После подтверждения вы сможете участвовать в чате.")
|
||||
|
||||
|
||||
async def show_all_invoices_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
query = update.callback_query
|
||||
await query.answer()
|
||||
@@ -132,9 +124,18 @@ async def show_all_invoices_callback(update: Update, context: ContextTypes.DEFAU
|
||||
message_text = "💳 *Все счета:*\n"
|
||||
if invoices:
|
||||
for inv in invoices:
|
||||
invoice_str = str(inv).split('/')[0].strip()
|
||||
message_text += f" • {invoice_str} (*{inv.sum}*)\n"
|
||||
message_text += f" • ID {inv.id} (*{inv.sum}*)\n"
|
||||
else:
|
||||
message_text += " _Нет счетов_\n"
|
||||
|
||||
await query.edit_message_text(message_text, parse_mode="Markdown")
|
||||
|
||||
|
||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
if not update.message:
|
||||
return
|
||||
bot_config = await sync_to_async(BotConfig.objects.filter(is_active=True).first)()
|
||||
if bot_config and bot_config.active_welcome:
|
||||
await update.message.reply_text(bot_config.active_welcome.welcome_message)
|
||||
else:
|
||||
await update.message.reply_text("Привет! Бот работает.")
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.6 on 2025-07-20 09:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bot', '0005_alter_welcomemessage_welcome_image'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='botconfig',
|
||||
name='global_block_message',
|
||||
field=models.TextField(blank=True, help_text='Сообщение, которое показывается пользователям, если чат отключён.', null=True, verbose_name='Сообщение о глобальном блоке'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='welcomemessage',
|
||||
name='welcome_image',
|
||||
field=models.ImageField(blank=True, help_text='Загрузите изображение для приветствия', null=True, upload_to='static/upload_image/', verbose_name='Приветственное изображение'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 5.1.6 on 2025-07-20 23:33
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bot', '0006_alter_botconfig_global_block_message_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='botconfig',
|
||||
name='active_welcome',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='used_by', to='bot.welcomemessage', verbose_name='Активное приветствие'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='botconfig',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=False, verbose_name='Включен'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='botconfig',
|
||||
name='welcome_messages',
|
||||
field=models.ManyToManyField(blank=True, to='bot.welcomemessage', verbose_name='Варианты приветствия'),
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class BotConfig(models.Model):
|
||||
bot_token = models.CharField(max_length=255, help_text="Токен для подключения к Telegram API")
|
||||
channel_id = models.CharField(max_length=100, help_text="ID канала/чата, куда бот будет отправлять сообщения")
|
||||
@@ -15,67 +16,79 @@ class BotConfig(models.Model):
|
||||
verbose_name="Сообщение о глобальном блоке",
|
||||
help_text="Сообщение, которое показывается пользователям, если чат отключён."
|
||||
)
|
||||
welcome_messages = models.ManyToManyField('WelcomeMessage', blank=True, verbose_name="Варианты приветствия")
|
||||
active_welcome = models.ForeignKey(
|
||||
'WelcomeMessage',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="used_by",
|
||||
verbose_name="Активное приветствие"
|
||||
)
|
||||
is_active = models.BooleanField(default=False, verbose_name="Включен")
|
||||
|
||||
def __str__(self):
|
||||
return self.bot_name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Настройка бота"
|
||||
verbose_name_plural = "Настройки Ботов"
|
||||
|
||||
|
||||
|
||||
|
||||
class WelcomeMessage(models.Model):
|
||||
bot = models.ForeignKey(BotConfig, on_delete=models.CASCADE, verbose_name="Бот")
|
||||
welcome_message = models.TextField(
|
||||
verbose_name="Приветственное сообщение",
|
||||
blank=True,
|
||||
verbose_name="Приветственное сообщение",
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Текст, который будет отправлен при запуске команды /start"
|
||||
)
|
||||
welcome_image = models.ImageField(
|
||||
upload_to='static/upload_image/',
|
||||
verbose_name="Приветственное изображение",
|
||||
blank=True,
|
||||
verbose_name="Приветственное изображение",
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Загрузите изображение для приветствия"
|
||||
)
|
||||
admin_contact = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name="Контакт администратора",
|
||||
blank=True,
|
||||
max_length=255,
|
||||
verbose_name="Контакт администратора",
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Ссылка или контакт для связи с администратором"
|
||||
)
|
||||
channel_link = models.URLField(
|
||||
verbose_name="Ссылка на канал",
|
||||
blank=True,
|
||||
verbose_name="Ссылка на канал",
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="URL канала бота"
|
||||
)
|
||||
group_link = models.URLField(
|
||||
verbose_name="Ссылка на группу",
|
||||
blank=True,
|
||||
verbose_name="Ссылка на группу",
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="URL группы бота"
|
||||
)
|
||||
custom_link1_name = models.CharField(
|
||||
max_length=100,
|
||||
verbose_name="Название кастомной ссылки 1",
|
||||
blank=True,
|
||||
max_length=100,
|
||||
verbose_name="Название кастомной ссылки 1",
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
custom_link1_url = models.URLField(
|
||||
verbose_name="URL кастомной ссылки 1",
|
||||
blank=True,
|
||||
verbose_name="URL кастомной ссылки 1",
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
custom_link2_name = models.CharField(
|
||||
max_length=100,
|
||||
verbose_name="Название кастомной ссылки 2",
|
||||
blank=True,
|
||||
max_length=100,
|
||||
verbose_name="Название кастомной ссылки 2",
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
custom_link2_url = models.URLField(
|
||||
verbose_name="URL кастомной ссылки 2",
|
||||
blank=True,
|
||||
verbose_name="URL кастомной ссылки 2",
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
@@ -84,4 +97,4 @@ class WelcomeMessage(models.Model):
|
||||
verbose_name_plural = "Приветственные сообщения"
|
||||
|
||||
def __str__(self):
|
||||
return f"Приветствие для {self.bot}"
|
||||
return f"Приветствие для {self.bot}"
|
||||
|
||||
21
lottery/bot/tasks.py
Normal file
21
lottery/bot/tasks.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from celery import shared_task
|
||||
import subprocess
|
||||
|
||||
@shared_task
|
||||
def restart_bot_container(container_name="bot"):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["docker", "restart", container_name],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
return f"Success: {result.stdout.strip()}"
|
||||
except subprocess.CalledProcessError as e:
|
||||
return f"Error restarting container: {e.stderr.strip()}"
|
||||
|
||||
|
||||
@shared_task
|
||||
def ping():
|
||||
return "pong"
|
||||
Reference in New Issue
Block a user