Compare commits
17 Commits
dd849e00f3
...
bot_refact
| Author | SHA1 | Date | |
|---|---|---|---|
| 53da914bea | |||
| 8945fae0c0 | |||
| 8c00c92577 | |||
| 4c595b1797 | |||
| a7137678b7 | |||
| b5f3b8ce22 | |||
| e08e54e4ad | |||
| 8b6c595184 | |||
| 3f5c332ecd | |||
| 8b37877b3e | |||
| 023966bc5b | |||
| 68caea5937 | |||
| becf4f5c99 | |||
| 842710fe5c | |||
| 1d659ea5ee | |||
| 7acdcc7465 | |||
| 37b0507c07 |
1
lottery/.gitignore
vendored
1
lottery/.gitignore
vendored
@@ -12,3 +12,4 @@ var/mysql/
|
||||
.db
|
||||
.png
|
||||
|
||||
db_data/
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 .models import BotConfig, WelcomeMessage, BotMessage, BotEventMessageConfig
|
||||
from .tasks import restart_bot_container
|
||||
|
||||
@admin.register(BotConfig)
|
||||
@@ -35,4 +35,12 @@ class BotConfigAdmin(admin.ModelAdmin):
|
||||
@admin.register(WelcomeMessage)
|
||||
class WWelcomeMessageAdmin(admin.ModelAdmin):
|
||||
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)
|
||||
await update.message.reply_text("Введите номер вашей клубной карты (КК):")
|
||||
|
||||
await send_event_message("binding_started", update, context)
|
||||
|
||||
async def process_binding_input(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
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"
|
||||
if 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:
|
||||
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
|
||||
|
||||
|
||||
|
||||
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 канала/чата, куда бот будет отправлять сообщения")
|
||||
@@ -44,7 +45,7 @@ class WelcomeMessage(models.Model):
|
||||
help_text="Текст, который будет отправлен при запуске команды /start"
|
||||
)
|
||||
welcome_image = models.ImageField(
|
||||
upload_to='static/upload_image/',
|
||||
upload_to='welcome_messages/',
|
||||
verbose_name="Приветственное изображение",
|
||||
blank=True,
|
||||
null=True,
|
||||
@@ -98,3 +99,38 @@ class WelcomeMessage(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
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
|
||||
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:
|
||||
def __init__(self, bot):
|
||||
"""
|
||||
Инициализация сервиса уведомлений.
|
||||
:param bot: экземпляр telegram.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
|
||||
def _get_all_clients(self):
|
||||
from webapp.models import Client
|
||||
# Возвращаем только клиентов, у которых telegram_id заполнен корректно
|
||||
return list(Client.objects.exclude(telegram_id__in=[None, "", "NULL"]))
|
||||
clients = list(Client.objects.exclude(telegram_id__in=[None, "", "NULL"]))
|
||||
self.logger.info(f"Найдено клиентов для уведомления: {len(clients)}")
|
||||
return clients
|
||||
|
||||
@sync_to_async
|
||||
def _prepare_results(self, results):
|
||||
# Принудительно загружаем связанные объекты через select_related
|
||||
return list(results.select_related('prize', 'participant__invoice'))
|
||||
|
||||
async def notify_draw_start(self, lottery):
|
||||
"""
|
||||
Уведомляет всех клиентов о запуске розыгрыша.
|
||||
:param lottery: объект розыгрыша (например, модель Lottery)
|
||||
"""
|
||||
message_text = f"🎉 *Розыгрыш '{lottery.name}' начался!* Следите за обновлениями."
|
||||
clients = await self._get_all_clients()
|
||||
for client in clients:
|
||||
if not client.telegram_id:
|
||||
continue
|
||||
try:
|
||||
await self.bot.send_message(
|
||||
chat_id=client.telegram_id,
|
||||
text=message_text,
|
||||
parse_mode="Markdown"
|
||||
self.logger.debug(f"Отправка 'draw_started' клиенту {client.telegram_id}")
|
||||
await send_event_message_async(
|
||||
event="draw_started",
|
||||
bot=self.bot,
|
||||
chat_id=int(client.telegram_id),
|
||||
context_vars={"lottery": lottery, "client": client}
|
||||
)
|
||||
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):
|
||||
"""
|
||||
Отправляет результаты розыгрыша всем клиентам.
|
||||
:param lottery: объект розыгрыша (Lottery)
|
||||
:param results: QuerySet с результатами розыгрыша (например, объекты DrawResult)
|
||||
"""
|
||||
# Принудительно загружаем связанные объекты
|
||||
# async def notify_draw_results(self, lottery, results):
|
||||
# results_list = await self._prepare_results(results)
|
||||
# clients = await self._get_all_clients()
|
||||
# for client in clients:
|
||||
# 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)
|
||||
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()
|
||||
|
||||
# 🔢 Подготовка суммы и списка победителей
|
||||
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:
|
||||
if not client.telegram_id:
|
||||
continue
|
||||
try:
|
||||
await self.bot.send_message(
|
||||
chat_id=client.telegram_id,
|
||||
text=message_text,
|
||||
parse_mode="Markdown"
|
||||
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={
|
||||
**(context_vars or {}),
|
||||
"lottery": lottery,
|
||||
"client": client,
|
||||
"results": results_list,
|
||||
"total_reward": f"{total_reward:,.2f}",
|
||||
"winners_list": winners_list
|
||||
}
|
||||
)
|
||||
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):
|
||||
"""
|
||||
Отправляет конкретному пользователю обновление по статусу подтверждения приза.
|
||||
: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:
|
||||
if client.telegram_id:
|
||||
await self.bot.send_message(
|
||||
chat_id=client.telegram_id,
|
||||
text=message_text,
|
||||
parse_mode="Markdown"
|
||||
self.logger.debug(f"Отправка 'winner_announced' клиенту {client.telegram_id}")
|
||||
await send_event_message_async(
|
||||
event="winner_announced",
|
||||
bot=self.bot,
|
||||
chat_id=int(client.telegram_id),
|
||||
context_vars={"client": client, "result": result, "prize": result.prize}
|
||||
)
|
||||
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):
|
||||
"""
|
||||
Уведомляет пользователя об окончании привязки клубной карты к Telegram ID.
|
||||
:param client: объект клиента (Client)
|
||||
"""
|
||||
message_text = "✅ *Привязка завершена!* Ваша клубная карта успешно привязана к Telegram. Теперь вы можете участвовать в розыгрышах и чате."
|
||||
try:
|
||||
if client.telegram_id:
|
||||
await self.bot.send_message(
|
||||
chat_id=client.telegram_id,
|
||||
text=message_text,
|
||||
parse_mode="Markdown"
|
||||
self.logger.debug(f"Отправка 'binding_complete' клиенту {client.telegram_id}")
|
||||
await send_event_message_async(
|
||||
event="binding_complete",
|
||||
bot=self.bot,
|
||||
chat_id=int(client.telegram_id),
|
||||
context_vars={"client": client}
|
||||
)
|
||||
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
|
||||
from telegram import Bot, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from django.conf import settings
|
||||
from telegram import Bot
|
||||
from bot.models import BotConfig
|
||||
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():
|
||||
"""
|
||||
Получает настройки бота из БД и создаёт экземпляр Telegram Bot.
|
||||
"""
|
||||
config = BotConfig.objects.first() # предполагается, что конфигурация одна
|
||||
config = BotConfig.objects.first()
|
||||
if not config:
|
||||
raise ImproperlyConfigured("Настройки бота (BotConfig) не сконфигурированы в базе данных.")
|
||||
raise ImproperlyConfigured("BotConfig не задан.")
|
||||
|
||||
# Можно дополнительно использовать config.channel_id и config.bot_name при необходимости.
|
||||
bot = Bot(token=config.bot_token)
|
||||
return bot
|
||||
logger.debug("Создан экземпляр бота с токеном.")
|
||||
return Bot(token=config.bot_token)
|
||||
|
||||
|
||||
def notify_user(binding_request, approved=True):
|
||||
bot_token = settings.BOT_CONFIG.bot_token # можно использовать модель BotConfig или настройку из settings
|
||||
bot = Bot(token=bot_token)
|
||||
message = "Ваша заявка на привязку успешно подтверждена!" if approved else "Ваша заявка на привязку отклонена."
|
||||
def get_event_message_sync(event_key: str, context_vars: dict = None):
|
||||
logger.debug(f"get_event_message_sync для события: {event_key}")
|
||||
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:
|
||||
# Обработка ошибок отправки сообщения
|
||||
print(f"Ошибка уведомления: {e}")
|
||||
logger.error(f"Ошибка get_event_message_sync для события '{event_key}': {e}")
|
||||
return None
|
||||
|
||||
|
||||
# Пример использования в точке входа приложения:
|
||||
if __name__ == '__main__':
|
||||
# Предполагается, что Django уже настроен (например, через manage.py shell или management command)
|
||||
bot = create_bot_instance()
|
||||
# Теперь можно использовать объект bot для отправки сообщений, обработки обновлений и т.д.
|
||||
print(f"Бот {bot.name} успешно создан!")
|
||||
|
||||
|
||||
def send_event_message_sync(event: str, bot: Bot, chat_id: int, context_vars: dict = None):
|
||||
msg = get_event_message_sync(event, context_vars)
|
||||
if not msg:
|
||||
logger.warning(f"Пропущена отправка: шаблон события '{event}' не найден.")
|
||||
return
|
||||
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