Compare commits
15 Commits
dd849e00f3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b68ebd54dd | |||
| 8b6c595184 | |||
| 3f5c332ecd | |||
| 8b37877b3e | |||
| 023966bc5b | |||
| 68caea5937 | |||
| 878a1e1a49 | |||
| becf4f5c99 | |||
| 842710fe5c | |||
| 515f5924f3 | |||
| 1d659ea5ee | |||
| 0fae6ce3f6 | |||
| 7acdcc7465 | |||
| 422389ecfb | |||
| 37b0507c07 |
1
lottery/.gitignore
vendored
1
lottery/.gitignore
vendored
@@ -12,3 +12,4 @@ var/mysql/
|
|||||||
.db
|
.db
|
||||||
.png
|
.png
|
||||||
|
|
||||||
|
db_data/
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.urls import path
|
|||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from .models import BotConfig
|
from .models import BotConfig
|
||||||
from .models import BotConfig, WelcomeMessage
|
from .models import BotConfig, WelcomeMessage, BotMessage, BotEventMessageConfig
|
||||||
from .tasks import restart_bot_container
|
from .tasks import restart_bot_container
|
||||||
|
|
||||||
@admin.register(BotConfig)
|
@admin.register(BotConfig)
|
||||||
@@ -36,3 +36,11 @@ class BotConfigAdmin(admin.ModelAdmin):
|
|||||||
class WWelcomeMessageAdmin(admin.ModelAdmin):
|
class WWelcomeMessageAdmin(admin.ModelAdmin):
|
||||||
list_display = ("bot", "welcome_message", "welcome_image", "admin_contact", "channel_link", "group_link", "custom_link1_name", "custom_link1_url")
|
list_display = ("bot", "welcome_message", "welcome_image", "admin_contact", "channel_link", "group_link", "custom_link1_name", "custom_link1_url")
|
||||||
|
|
||||||
|
@admin.register(BotMessage)
|
||||||
|
class BotMessageAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("name", "text", "image", "buttons_json")
|
||||||
|
|
||||||
|
@admin.register(BotEventMessageConfig)
|
||||||
|
class BotEventMessageConfigAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("event", "message", "enabled")
|
||||||
|
list_filter = ("event", "enabled")
|
||||||
@@ -93,7 +93,7 @@ async def handle_client_card(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
|||||||
|
|
||||||
BINDING_PENDING.add(telegram_chat_id)
|
BINDING_PENDING.add(telegram_chat_id)
|
||||||
await update.message.reply_text("Введите номер вашей клубной карты (КК):")
|
await update.message.reply_text("Введите номер вашей клубной карты (КК):")
|
||||||
|
await send_event_message("binding_started", update, context)
|
||||||
|
|
||||||
async def process_binding_input(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def process_binding_input(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
if not update.message or not update.message.text:
|
if not update.message or not update.message.text:
|
||||||
@@ -124,7 +124,7 @@ async def show_all_invoices_callback(update: Update, context: ContextTypes.DEFAU
|
|||||||
message_text = "💳 *Все счета:*\n"
|
message_text = "💳 *Все счета:*\n"
|
||||||
if invoices:
|
if invoices:
|
||||||
for inv in invoices:
|
for inv in invoices:
|
||||||
message_text += f" • ID {inv.id} (*{inv.sum}*)\n"
|
message_text += f" • ID {inv.ext_id} (*{inv.sum}*)\n"
|
||||||
else:
|
else:
|
||||||
message_text += " _Нет счетов_\n"
|
message_text += " _Нет счетов_\n"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-08-03 05:29
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bot', '0007_botconfig_active_welcome_botconfig_is_active_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BotMessage',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='Название сообщения')),
|
||||||
|
('text', models.TextField(blank=True, verbose_name='Текст')),
|
||||||
|
('image', models.ImageField(blank=True, null=True, upload_to='bot_messages/', verbose_name='Картинка')),
|
||||||
|
('buttons_json', models.JSONField(blank=True, null=True, verbose_name='Кнопки (JSON-формат)')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BotEventMessageConfig',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('event', models.CharField(choices=[('welcome', 'Приветствие'), ('draw_started', 'Розыгрыш начат'), ('draw_finished', 'Розыгрыш завершен'), ('winner_announced', 'Объявление победителя')], max_length=50, unique=True, verbose_name='Событие')),
|
||||||
|
('enabled', models.BooleanField(default=True, verbose_name='Активно')),
|
||||||
|
('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_configs', to='bot.botmessage')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-08-03 05:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bot', '0008_botmessage_boteventmessageconfig'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='boteventmessageconfig',
|
||||||
|
options={'verbose_name': 'Событие сообщения', 'verbose_name_plural': 'События сообщений'},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='botmessage',
|
||||||
|
options={'verbose_name': 'Сообщение', 'verbose_name_plural': 'Сообщения'},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='boteventmessageconfig',
|
||||||
|
name='event',
|
||||||
|
field=models.CharField(choices=[('welcome', 'Приветствие'), ('draw_started', 'Розыгрыш начат'), ('draw_finished', 'Розыгрыш завершен'), ('winner_announced', 'Объявление победителя'), ('guest_binding', 'Подтверждение гостя')], max_length=50, unique=True, verbose_name='Событие'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-08-03 11:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bot', '0009_alter_boteventmessageconfig_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='welcomemessage',
|
||||||
|
name='welcome_image',
|
||||||
|
field=models.ImageField(blank=True, help_text='Загрузите изображение для приветствия', null=True, upload_to='welcome_messages/', verbose_name='Приветственное изображение'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BotConfig(models.Model):
|
class BotConfig(models.Model):
|
||||||
bot_token = models.CharField(max_length=255, help_text="Токен для подключения к Telegram API")
|
bot_token = models.CharField(max_length=255, help_text="Токен для подключения к Telegram API")
|
||||||
channel_id = models.CharField(max_length=100, help_text="ID канала/чата, куда бот будет отправлять сообщения")
|
channel_id = models.CharField(max_length=100, help_text="ID канала/чата, куда бот будет отправлять сообщения")
|
||||||
@@ -44,7 +45,7 @@ class WelcomeMessage(models.Model):
|
|||||||
help_text="Текст, который будет отправлен при запуске команды /start"
|
help_text="Текст, который будет отправлен при запуске команды /start"
|
||||||
)
|
)
|
||||||
welcome_image = models.ImageField(
|
welcome_image = models.ImageField(
|
||||||
upload_to='static/upload_image/',
|
upload_to='welcome_messages/',
|
||||||
verbose_name="Приветственное изображение",
|
verbose_name="Приветственное изображение",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@@ -98,3 +99,38 @@ class WelcomeMessage(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Приветствие для {self.bot}"
|
return f"Приветствие для {self.bot}"
|
||||||
|
|
||||||
|
class BotMessage(models.Model):
|
||||||
|
name = models.CharField("Название сообщения", max_length=100)
|
||||||
|
text = models.TextField("Текст", blank=True)
|
||||||
|
image = models.ImageField("Картинка", upload_to="bot_messages/", blank=True, null=True)
|
||||||
|
buttons_json = models.JSONField("Кнопки (JSON-формат)", blank=True, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Сообщение"
|
||||||
|
verbose_name_plural = "Сообщения"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class BotEventMessageConfig(models.Model):
|
||||||
|
EVENT_CHOICES = [
|
||||||
|
('welcome', 'Приветствие'),
|
||||||
|
('draw_started', 'Розыгрыш начат'),
|
||||||
|
('draw_finished', 'Розыгрыш завершен'),
|
||||||
|
('winner_announced', 'Объявление победителя'),
|
||||||
|
('guest_binding', 'Подтверждение гостя'),
|
||||||
|
# и другие
|
||||||
|
]
|
||||||
|
|
||||||
|
event = models.CharField("Событие", max_length=50, choices=EVENT_CHOICES, unique=True)
|
||||||
|
message = models.ForeignKey(BotMessage, on_delete=models.CASCADE, related_name="event_configs")
|
||||||
|
enabled = models.BooleanField("Активно", default=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Событие сообщения"
|
||||||
|
verbose_name_plural = "События сообщений"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.get_event_display()} → {self.message.name}"
|
||||||
|
|
||||||
@@ -1,105 +1,131 @@
|
|||||||
# notifications.py
|
|
||||||
import logging
|
import logging
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
|
from bot.utils import send_event_message_async
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
class NotificationService:
|
class NotificationService:
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
"""
|
|
||||||
Инициализация сервиса уведомлений.
|
|
||||||
:param bot: экземпляр telegram.Bot, используемый для отправки сообщений.
|
|
||||||
"""
|
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger("NotificationService")
|
||||||
|
self.logger.setLevel(logging.DEBUG)
|
||||||
|
if not self.logger.handlers:
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
handler.setLevel(logging.DEBUG)
|
||||||
|
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
||||||
|
self.logger.addHandler(handler)
|
||||||
|
|
||||||
@sync_to_async
|
@sync_to_async
|
||||||
def _get_all_clients(self):
|
def _get_all_clients(self):
|
||||||
from webapp.models import Client
|
from webapp.models import Client
|
||||||
# Возвращаем только клиентов, у которых telegram_id заполнен корректно
|
clients = list(Client.objects.exclude(telegram_id__in=[None, "", "NULL"]))
|
||||||
return list(Client.objects.exclude(telegram_id__in=[None, "", "NULL"]))
|
self.logger.info(f"Найдено клиентов для уведомления: {len(clients)}")
|
||||||
|
return clients
|
||||||
|
|
||||||
@sync_to_async
|
@sync_to_async
|
||||||
def _prepare_results(self, results):
|
def _prepare_results(self, results):
|
||||||
# Принудительно загружаем связанные объекты через select_related
|
|
||||||
return list(results.select_related('prize', 'participant__invoice'))
|
return list(results.select_related('prize', 'participant__invoice'))
|
||||||
|
|
||||||
async def notify_draw_start(self, lottery):
|
async def notify_draw_start(self, lottery):
|
||||||
"""
|
|
||||||
Уведомляет всех клиентов о запуске розыгрыша.
|
|
||||||
:param lottery: объект розыгрыша (например, модель Lottery)
|
|
||||||
"""
|
|
||||||
message_text = f"🎉 *Розыгрыш '{lottery.name}' начался!* Следите за обновлениями."
|
|
||||||
clients = await self._get_all_clients()
|
clients = await self._get_all_clients()
|
||||||
for client in clients:
|
for client in clients:
|
||||||
if not client.telegram_id:
|
if not client.telegram_id:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
await self.bot.send_message(
|
self.logger.debug(f"Отправка 'draw_started' клиенту {client.telegram_id}")
|
||||||
chat_id=client.telegram_id,
|
await send_event_message_async(
|
||||||
text=message_text,
|
event="draw_started",
|
||||||
parse_mode="Markdown"
|
bot=self.bot,
|
||||||
|
chat_id=int(client.telegram_id),
|
||||||
|
context_vars={"lottery": lottery, "client": client}
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка отправки уведомления о запуске розыгрыша пользователю {client.telegram_id}: {e}")
|
self.logger.error(f"Ошибка отправки draw_started: {e}")
|
||||||
|
|
||||||
async def notify_draw_results(self, lottery, results):
|
# async def notify_draw_results(self, lottery, results):
|
||||||
"""
|
# results_list = await self._prepare_results(results)
|
||||||
Отправляет результаты розыгрыша всем клиентам.
|
# clients = await self._get_all_clients()
|
||||||
:param lottery: объект розыгрыша (Lottery)
|
# for client in clients:
|
||||||
:param results: QuerySet с результатами розыгрыша (например, объекты DrawResult)
|
# if not client.telegram_id:
|
||||||
"""
|
# continue
|
||||||
# Принудительно загружаем связанные объекты
|
# try:
|
||||||
|
# self.logger.debug(f"Отправка 'draw_finished' клиенту {client.telegram_id}")
|
||||||
|
# await send_event_message_async(
|
||||||
|
# event="draw_finished",
|
||||||
|
# bot=self.bot,
|
||||||
|
# chat_id=int(client.telegram_id),
|
||||||
|
# context_vars={"lottery": lottery, "client": client, "results": results_list}
|
||||||
|
# )
|
||||||
|
# except Exception as e:
|
||||||
|
# self.logger.error(f"Ошибка отправки draw_finished: {e}")
|
||||||
|
|
||||||
|
async def notify_draw_results(self, lottery, results, context_vars=None):
|
||||||
results_list = await self._prepare_results(results)
|
results_list = await self._prepare_results(results)
|
||||||
message_text = f"🎉 *Результаты розыгрыша '{lottery.name}':*\n"
|
|
||||||
for result in results_list:
|
|
||||||
status = "✅ Подтвержден" if result.confirmed else "⏳ В ожидании подтверждения"
|
|
||||||
prize = result.prize.reward if result.prize and hasattr(result.prize, "reward") else "неизвестно"
|
|
||||||
account = (str(result.participant.invoice).split('/')[0].strip()
|
|
||||||
if result.participant and hasattr(result.participant, "invoice") else "неизвестно")
|
|
||||||
message_text += f"• Приз: *{prize}*, Счет: _{account}_, Статус: *{status}*\n"
|
|
||||||
clients = await self._get_all_clients()
|
clients = await self._get_all_clients()
|
||||||
|
|
||||||
|
# 🔢 Подготовка суммы и списка победителей
|
||||||
|
total_reward = sum(
|
||||||
|
r.prize.reward for r in results_list if r.prize and r.prize.reward
|
||||||
|
)
|
||||||
|
|
||||||
|
winners_lines = []
|
||||||
|
for r in results_list:
|
||||||
|
try:
|
||||||
|
account_id = r.participant.invoice.ext_id
|
||||||
|
prize_amount = r.prize.reward
|
||||||
|
winners_lines.append(f"• Счёт {account_id} — {prize_amount:,.2f}₩")
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
winners_list = "\n".join(winners_lines)
|
||||||
|
|
||||||
for client in clients:
|
for client in clients:
|
||||||
if not client.telegram_id:
|
if not client.telegram_id:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
await self.bot.send_message(
|
self.logger.debug(f"Отправка 'draw_finished' клиенту {client.telegram_id}")
|
||||||
chat_id=client.telegram_id,
|
await send_event_message_async(
|
||||||
text=message_text,
|
event="draw_finished",
|
||||||
parse_mode="Markdown"
|
bot=self.bot,
|
||||||
|
chat_id=int(client.telegram_id),
|
||||||
|
context_vars={
|
||||||
|
**(context_vars or {}),
|
||||||
|
"lottery": lottery,
|
||||||
|
"client": client,
|
||||||
|
"results": results_list,
|
||||||
|
"total_reward": f"{total_reward:,.2f}",
|
||||||
|
"winners_list": winners_list
|
||||||
|
}
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка отправки результатов розыгрыша пользователю {client.telegram_id}: {e}")
|
self.logger.error(f"Ошибка отправки draw_finished: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def notify_prize_status_update(self, client, result):
|
async def notify_prize_status_update(self, client, result):
|
||||||
"""
|
|
||||||
Отправляет конкретному пользователю обновление по статусу подтверждения приза.
|
|
||||||
:param client: объект клиента (Client)
|
|
||||||
:param result: объект результата розыгрыша (DrawResult)
|
|
||||||
"""
|
|
||||||
status = "✅ Подтвержден" if result.confirmed else "⏳ В ожидании подтверждения"
|
|
||||||
prize = result.prize.reward if result.prize and hasattr(result.prize, "reward") else "неизвестно"
|
|
||||||
message_text = f"🎉 *Обновление розыгрыша:*\nВаш приз *{prize}* имеет статус: *{status}*."
|
|
||||||
try:
|
try:
|
||||||
if client.telegram_id:
|
if client.telegram_id:
|
||||||
await self.bot.send_message(
|
self.logger.debug(f"Отправка 'winner_announced' клиенту {client.telegram_id}")
|
||||||
chat_id=client.telegram_id,
|
await send_event_message_async(
|
||||||
text=message_text,
|
event="winner_announced",
|
||||||
parse_mode="Markdown"
|
bot=self.bot,
|
||||||
|
chat_id=int(client.telegram_id),
|
||||||
|
context_vars={"client": client, "result": result, "prize": result.prize}
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка отправки обновления статуса приза пользователю {client.telegram_id}: {e}")
|
self.logger.error(f"Ошибка отправки winner_announced: {e}")
|
||||||
|
|
||||||
async def notify_binding_complete(self, client):
|
async def notify_binding_complete(self, client):
|
||||||
"""
|
|
||||||
Уведомляет пользователя об окончании привязки клубной карты к Telegram ID.
|
|
||||||
:param client: объект клиента (Client)
|
|
||||||
"""
|
|
||||||
message_text = "✅ *Привязка завершена!* Ваша клубная карта успешно привязана к Telegram. Теперь вы можете участвовать в розыгрышах и чате."
|
|
||||||
try:
|
try:
|
||||||
if client.telegram_id:
|
if client.telegram_id:
|
||||||
await self.bot.send_message(
|
self.logger.debug(f"Отправка 'binding_complete' клиенту {client.telegram_id}")
|
||||||
chat_id=client.telegram_id,
|
await send_event_message_async(
|
||||||
text=message_text,
|
event="binding_complete",
|
||||||
parse_mode="Markdown"
|
bot=self.bot,
|
||||||
|
chat_id=int(client.telegram_id),
|
||||||
|
context_vars={"client": client}
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка отправки уведомления о привязке пользователю {client.telegram_id}: {e}")
|
self.logger.error(f"Ошибка отправки binding_complete: {e}")
|
||||||
|
|||||||
@@ -1,39 +1,99 @@
|
|||||||
# bot/utils.py
|
from telegram import Bot, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from telegram import Bot
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from telegram import Bot
|
|
||||||
from bot.models import BotConfig
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
from bot.models import BotConfig, BotEventMessageConfig
|
||||||
|
import logging
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
from telegram import InputFile
|
||||||
|
import os
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
def create_bot_instance():
|
def create_bot_instance():
|
||||||
"""
|
config = BotConfig.objects.first()
|
||||||
Получает настройки бота из БД и создаёт экземпляр Telegram Bot.
|
|
||||||
"""
|
|
||||||
config = BotConfig.objects.first() # предполагается, что конфигурация одна
|
|
||||||
if not config:
|
if not config:
|
||||||
raise ImproperlyConfigured("Настройки бота (BotConfig) не сконфигурированы в базе данных.")
|
raise ImproperlyConfigured("BotConfig не задан.")
|
||||||
|
|
||||||
# Можно дополнительно использовать config.channel_id и config.bot_name при необходимости.
|
logger.debug("Создан экземпляр бота с токеном.")
|
||||||
bot = Bot(token=config.bot_token)
|
return Bot(token=config.bot_token)
|
||||||
return bot
|
|
||||||
|
|
||||||
|
|
||||||
def notify_user(binding_request, approved=True):
|
def get_event_message_sync(event_key: str, context_vars: dict = None):
|
||||||
bot_token = settings.BOT_CONFIG.bot_token # можно использовать модель BotConfig или настройку из settings
|
logger.debug(f"get_event_message_sync для события: {event_key}")
|
||||||
bot = Bot(token=bot_token)
|
|
||||||
message = "Ваша заявка на привязку успешно подтверждена!" if approved else "Ваша заявка на привязку отклонена."
|
|
||||||
try:
|
try:
|
||||||
bot.send_message(chat_id=binding_request.telegram_chat_id, text=message)
|
config = BotEventMessageConfig.objects.select_related("message").get(event=event_key, enabled=True)
|
||||||
|
msg = config.message
|
||||||
|
context_vars = context_vars or {}
|
||||||
|
|
||||||
|
text = msg.text.format(**context_vars) if msg.text else None
|
||||||
|
image_name = msg.image.name if msg.image else None # только имя файла
|
||||||
|
|
||||||
|
keyboard = None
|
||||||
|
if msg.buttons_json:
|
||||||
|
keyboard = InlineKeyboardMarkup([
|
||||||
|
[InlineKeyboardButton(btn["text"], url=btn["url"])]
|
||||||
|
for btn in msg.buttons_json
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"text": text,
|
||||||
|
"image": image_name,
|
||||||
|
"keyboard": keyboard
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Обработка ошибок отправки сообщения
|
logger.error(f"Ошибка get_event_message_sync для события '{event_key}': {e}")
|
||||||
print(f"Ошибка уведомления: {e}")
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Пример использования в точке входа приложения:
|
def send_event_message_sync(event: str, bot: Bot, chat_id: int, context_vars: dict = None):
|
||||||
if __name__ == '__main__':
|
msg = get_event_message_sync(event, context_vars)
|
||||||
# Предполагается, что Django уже настроен (например, через manage.py shell или management command)
|
if not msg:
|
||||||
bot = create_bot_instance()
|
logger.warning(f"Пропущена отправка: шаблон события '{event}' не найден.")
|
||||||
# Теперь можно использовать объект bot для отправки сообщений, обработки обновлений и т.д.
|
return
|
||||||
print(f"Бот {bot.name} успешно создан!")
|
try:
|
||||||
|
if msg["image"]:
|
||||||
|
bot.send_photo(chat_id=chat_id, photo=msg["image"], caption=msg["text"], reply_markup=msg["keyboard"])
|
||||||
|
elif msg["text"]:
|
||||||
|
bot.send_message(chat_id=chat_id, text=msg["text"], reply_markup=msg["keyboard"])
|
||||||
|
logger.info(f"Сообщение '{event}' успешно отправлено пользователю {chat_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при отправке сообщения '{event}' пользователю {chat_id}: {e}")
|
||||||
|
|
||||||
|
@sync_to_async
|
||||||
|
def get_event_message_async(event_key: str, context_vars: dict = None):
|
||||||
|
return get_event_message_sync(event_key, context_vars)
|
||||||
|
|
||||||
|
async def send_event_message_async(event: str, bot: Bot, chat_id: int, context_vars: dict = None):
|
||||||
|
msg = await get_event_message_async(event, context_vars)
|
||||||
|
if not msg:
|
||||||
|
logger.warning(f"[ASYNC] Пропущена отправка: шаблон события '{event}' не найден.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if msg["image"]:
|
||||||
|
# локальный путь к файлу
|
||||||
|
from django.conf import settings
|
||||||
|
file_path = os.path.join(settings.MEDIA_ROOT, msg["image"])
|
||||||
|
logger.debug(f"[ASYNC] Отправка фото из {file_path}")
|
||||||
|
with open(file_path, "rb") as photo:
|
||||||
|
await bot.send_photo(
|
||||||
|
chat_id=chat_id,
|
||||||
|
photo=InputFile(photo),
|
||||||
|
caption=msg["text"],
|
||||||
|
reply_markup=msg["keyboard"]
|
||||||
|
)
|
||||||
|
elif msg["text"]:
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=chat_id,
|
||||||
|
text=msg["text"],
|
||||||
|
reply_markup=msg["keyboard"]
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"[ASYNC] Сообщение '{event}' отправлено пользователю {chat_id}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[ASYNC] Ошибка Telegram при отправке '{event}' пользователю {chat_id}: {e}")
|
||||||
|
|||||||
BIN
lottery/bot_messages/photo_2025-07-10_09-33-13.jpg
Normal file
BIN
lottery/bot_messages/photo_2025-07-10_09-33-13.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
BIN
lottery/bot_messages/photo_2025-07-10_09-33-22.jpg
Normal file
BIN
lottery/bot_messages/photo_2025-07-10_09-33-22.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
BIN
lottery/bot_messages/photo_2025-07-10_09-33-33.jpg
Normal file
BIN
lottery/bot_messages/photo_2025-07-10_09-33-33.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
lottery/bot_messages/photo_2025-07-10_09-33-35.jpg
Normal file
BIN
lottery/bot_messages/photo_2025-07-10_09-33-35.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
@@ -1,6 +0,0 @@
|
|||||||
[mariadb-client]
|
|
||||||
port=3306
|
|
||||||
socket=/run/mysqld/mysqld.sock
|
|
||||||
user=healthcheck
|
|
||||||
password=9hOHOe^WXq`lsXwRba/@]gB3"|[mOFe{
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1,269 +0,0 @@
|
|||||||
59,5
|
|
||||||
59,4
|
|
||||||
59,3
|
|
||||||
59,2
|
|
||||||
59,1
|
|
||||||
59,0
|
|
||||||
56,4
|
|
||||||
56,3
|
|
||||||
56,2
|
|
||||||
56,1
|
|
||||||
56,0
|
|
||||||
55,4
|
|
||||||
55,3
|
|
||||||
55,2
|
|
||||||
55,1
|
|
||||||
55,0
|
|
||||||
53,4
|
|
||||||
53,3
|
|
||||||
53,2
|
|
||||||
53,1
|
|
||||||
53,0
|
|
||||||
52,5
|
|
||||||
52,4
|
|
||||||
52,3
|
|
||||||
52,2
|
|
||||||
52,1
|
|
||||||
52,0
|
|
||||||
51,5
|
|
||||||
51,4
|
|
||||||
51,3
|
|
||||||
51,2
|
|
||||||
51,1
|
|
||||||
51,0
|
|
||||||
50,6
|
|
||||||
50,5
|
|
||||||
50,4
|
|
||||||
50,3
|
|
||||||
50,2
|
|
||||||
50,1
|
|
||||||
50,0
|
|
||||||
37,3
|
|
||||||
37,2
|
|
||||||
37,1
|
|
||||||
37,0
|
|
||||||
36,4
|
|
||||||
36,3
|
|
||||||
36,2
|
|
||||||
36,1
|
|
||||||
36,0
|
|
||||||
35,4
|
|
||||||
35,3
|
|
||||||
35,2
|
|
||||||
35,1
|
|
||||||
35,0
|
|
||||||
34,3
|
|
||||||
34,2
|
|
||||||
34,1
|
|
||||||
34,0
|
|
||||||
32,4
|
|
||||||
32,3
|
|
||||||
32,2
|
|
||||||
32,1
|
|
||||||
32,0
|
|
||||||
28,4
|
|
||||||
28,3
|
|
||||||
28,2
|
|
||||||
28,1
|
|
||||||
28,0
|
|
||||||
27,4
|
|
||||||
27,3
|
|
||||||
27,2
|
|
||||||
27,1
|
|
||||||
27,0
|
|
||||||
26,5
|
|
||||||
26,4
|
|
||||||
26,3
|
|
||||||
26,2
|
|
||||||
26,1
|
|
||||||
26,0
|
|
||||||
25,5
|
|
||||||
25,4
|
|
||||||
25,3
|
|
||||||
25,2
|
|
||||||
25,1
|
|
||||||
25,0
|
|
||||||
22,5
|
|
||||||
22,4
|
|
||||||
22,3
|
|
||||||
22,2
|
|
||||||
22,1
|
|
||||||
22,0
|
|
||||||
20,5
|
|
||||||
20,4
|
|
||||||
20,3
|
|
||||||
20,2
|
|
||||||
20,1
|
|
||||||
20,0
|
|
||||||
18,5
|
|
||||||
18,4
|
|
||||||
18,3
|
|
||||||
18,2
|
|
||||||
18,1
|
|
||||||
18,0
|
|
||||||
11,4
|
|
||||||
11,3
|
|
||||||
11,2
|
|
||||||
11,1
|
|
||||||
11,0
|
|
||||||
8,3
|
|
||||||
8,2
|
|
||||||
8,1
|
|
||||||
8,0
|
|
||||||
7,3
|
|
||||||
5,5
|
|
||||||
5,4
|
|
||||||
5,3
|
|
||||||
5,2
|
|
||||||
5,0
|
|
||||||
4,3
|
|
||||||
3,2
|
|
||||||
2,2
|
|
||||||
1,2
|
|
||||||
0,9
|
|
||||||
0,2
|
|
||||||
1,45
|
|
||||||
3,44
|
|
||||||
2,44
|
|
||||||
1,44
|
|
||||||
3,43
|
|
||||||
2,43
|
|
||||||
1,43
|
|
||||||
3,42
|
|
||||||
2,42
|
|
||||||
1,42
|
|
||||||
3,41
|
|
||||||
2,41
|
|
||||||
1,41
|
|
||||||
3,40
|
|
||||||
2,40
|
|
||||||
1,40
|
|
||||||
3,39
|
|
||||||
2,39
|
|
||||||
1,39
|
|
||||||
3,38
|
|
||||||
2,38
|
|
||||||
1,38
|
|
||||||
3,37
|
|
||||||
2,37
|
|
||||||
1,37
|
|
||||||
3,36
|
|
||||||
2,36
|
|
||||||
1,36
|
|
||||||
3,35
|
|
||||||
2,35
|
|
||||||
1,35
|
|
||||||
3,34
|
|
||||||
2,34
|
|
||||||
1,34
|
|
||||||
3,33
|
|
||||||
2,33
|
|
||||||
1,33
|
|
||||||
3,32
|
|
||||||
2,32
|
|
||||||
1,32
|
|
||||||
3,31
|
|
||||||
2,31
|
|
||||||
1,31
|
|
||||||
3,30
|
|
||||||
2,30
|
|
||||||
1,30
|
|
||||||
3,29
|
|
||||||
2,29
|
|
||||||
1,29
|
|
||||||
3,28
|
|
||||||
2,28
|
|
||||||
1,28
|
|
||||||
3,27
|
|
||||||
2,27
|
|
||||||
1,27
|
|
||||||
3,26
|
|
||||||
2,26
|
|
||||||
1,26
|
|
||||||
3,25
|
|
||||||
2,25
|
|
||||||
1,25
|
|
||||||
3,24
|
|
||||||
2,24
|
|
||||||
1,24
|
|
||||||
3,23
|
|
||||||
2,23
|
|
||||||
1,23
|
|
||||||
3,22
|
|
||||||
2,22
|
|
||||||
1,22
|
|
||||||
3,21
|
|
||||||
2,21
|
|
||||||
1,21
|
|
||||||
3,20
|
|
||||||
2,20
|
|
||||||
1,20
|
|
||||||
3,19
|
|
||||||
2,19
|
|
||||||
1,19
|
|
||||||
3,18
|
|
||||||
2,18
|
|
||||||
1,18
|
|
||||||
3,17
|
|
||||||
2,17
|
|
||||||
1,17
|
|
||||||
3,16
|
|
||||||
2,16
|
|
||||||
1,16
|
|
||||||
3,15
|
|
||||||
2,15
|
|
||||||
1,15
|
|
||||||
3,14
|
|
||||||
2,14
|
|
||||||
1,14
|
|
||||||
3,13
|
|
||||||
2,13
|
|
||||||
1,13
|
|
||||||
3,12
|
|
||||||
2,12
|
|
||||||
1,12
|
|
||||||
3,11
|
|
||||||
2,11
|
|
||||||
1,11
|
|
||||||
3,10
|
|
||||||
2,10
|
|
||||||
1,10
|
|
||||||
3,9
|
|
||||||
2,9
|
|
||||||
1,9
|
|
||||||
3,8
|
|
||||||
2,8
|
|
||||||
1,8
|
|
||||||
3,7
|
|
||||||
2,7
|
|
||||||
1,7
|
|
||||||
3,6
|
|
||||||
2,6
|
|
||||||
1,6
|
|
||||||
3,5
|
|
||||||
2,5
|
|
||||||
1,5
|
|
||||||
3,4
|
|
||||||
2,4
|
|
||||||
1,4
|
|
||||||
3,3
|
|
||||||
3,0
|
|
||||||
2,3
|
|
||||||
2,0
|
|
||||||
1,3
|
|
||||||
1,0
|
|
||||||
0,6
|
|
||||||
0,0
|
|
||||||
0,47
|
|
||||||
0,46
|
|
||||||
0,49
|
|
||||||
0,48
|
|
||||||
0,45
|
|
||||||
0,12
|
|
||||||
0,10
|
|
||||||
0,8
|
|
||||||
0,11
|
|
||||||
0,5
|
|
||||||
0,7
|
|
||||||
0,4
|
|
||||||
0,3
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
default-character-set=utf8mb4
|
|
||||||
default-collation=utf8mb4_uca1400_ai_ci
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
11.6.2-MariaDB
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
default-character-set=utf8mb4
|
|
||||||
default-collation=utf8mb4_uca1400_ai_ci
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user