init commit

This commit is contained in:
2025-06-13 21:10:20 +09:00
commit d52c611afb
269 changed files with 37162 additions and 0 deletions

0
lottery/bot/__init__.py Normal file
View File

13
lottery/bot/admin.py Normal file
View File

@@ -0,0 +1,13 @@
from django.contrib import admin
from .models import BotConfig, WelcomeMessage
@admin.register(BotConfig)
class BotConfigAdmin(admin.ModelAdmin):
list_display = ("bot_name", "channel_id", "bot_token")
search_fields = ("bot_name", "channel_id", "bot_token")
@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")

7
lottery/bot/apps.py Normal file
View File

@@ -0,0 +1,7 @@
from django.apps import AppConfig
class BotConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'bot'
verbose_name='Telegram Бот'

92
lottery/bot/chat.py Normal file
View File

@@ -0,0 +1,92 @@
# chat.py
import logging
from telegram import Update, InputMediaPhoto
from telegram.ext import ContextTypes
from asgiref.sync import sync_to_async
from bot.state import BINDING_PENDING
class ChatHandler:
def __init__(self, bot):
self.bot = bot
self.logger = logging.getLogger(__name__)
@sync_to_async
def get_bot_settings(self):
from bot.models import BotConfig
settings = BotConfig.objects.first()
if settings is None:
settings = BotConfig.objects.create(chat_enabled=True)
return settings
@sync_to_async
def get_client_by_telegram_id(self, telegram_id: int):
from webapp.models import Client
try:
return Client.objects.get(telegram_id=str(telegram_id))
except Client.DoesNotExist:
return None
@sync_to_async
def get_all_clients(self):
from webapp.models import Client
return list(Client.objects.all())
async def broadcast_message(self, sender, message_text: str, photo_media: InputMediaPhoto = None):
clients = await self.get_all_clients()
for client in clients:
if client.telegram_id == sender.telegram_id:
continue
try:
if photo_media:
await self.bot.send_photo(
chat_id=client.telegram_id,
photo=photo_media.media,
caption=photo_media.caption,
parse_mode="Markdown"
)
else:
await self.bot.send_message(
chat_id=client.telegram_id,
text=message_text,
parse_mode="Markdown"
)
except Exception as e:
self.logger.error(f"Ошибка отправки сообщения пользователю {client.telegram_id}: {e}")
async def handle_chat_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
if not update.message:
return
bot_settings = await self.get_bot_settings()
if not bot_settings.chat_enabled:
await update.message.reply_text("🚫 Чат временно отключён администрацией.")
return
chat_id = update.message.chat_id
sender = await self.get_client_by_telegram_id(chat_id)
# Если пользователь не привязан, но находится в pending, обрабатываем как ввод КК
if not sender and chat_id in BINDING_PENDING:
# Импортируем функцию process_binding_input из bot.handlers
from bot.handlers import process_binding_input
await process_binding_input(update, context)
return
# Если пользователь не привязан и не находится в pending, просим привязаться
if not sender:
await update.message.reply_text("🚫 Для участия в чате привяжите свою клиентскую карту с помощью /bind")
return
# Если у пользователя отключена возможность отправки сообщений
if getattr(sender, "chat_disabled", False):
await update.message.reply_text("🚫 Вы не можете отправлять сообщения. Обратитесь к администратору.")
return
sender_name = sender.name if sender else "Аноним"
broadcast_text = f"💬 *{sender_name} пишет:*\n{update.message.text or ''}"
photo_media = None
if update.message.photo:
photo = update.message.photo[-1]
caption = update.message.caption or broadcast_text
photo_media = InputMediaPhoto(photo.file_id, caption=caption)
await self.broadcast_message(sender, broadcast_text, photo_media)

140
lottery/bot/handlers.py Normal file
View File

@@ -0,0 +1,140 @@
# 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 bot.state import BINDING_PENDING
@sync_to_async
def get_client_by_telegram_id(telegram_id: int):
try:
return Client.objects.get(telegram_id=str(telegram_id))
except Client.DoesNotExist:
return None
@sync_to_async
def get_invoices_for_client(client):
return list(Invoice.objects.filter(client_club_card_number=client.club_card_number))
@sync_to_async
def get_draws_for_client(client):
return list(
DrawResult.objects.filter(participant__invoice__client_club_card_number=client.club_card_number)
.select_related('lottery', 'prize', 'participant__invoice')
)
@sync_to_async
def create_binding_request(telegram_chat_id, client_card):
return BindingRequest.objects.create(
telegram_chat_id=str(telegram_chat_id),
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:
await update.message.reply_text(
"❌ Пользователь не найден. Возможно, Вы ещё не привязали свою клубную карту."
)
return
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"
message_text += "💳 *Счета:*\n"
if invoices:
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"
else:
message_text += " _Нет счетов_\n"
message_text += "\n🎉 *Розыгрыши и выигранные призы:*\n"
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 "неизвестно")
message_text += f" • Лотерея: *{lottery_name}*\n Счет: _{invoice_info}_\n Выигрыш: *{prize_info}*\n\n"
else:
message_text += " _Нет результатов розыгрышей_\n"
reply_markup = None
if invoices and len(invoices) > 5:
reply_markup = InlineKeyboardMarkup([
[InlineKeyboardButton("Показать все счета", callback_data="show_all_invoices")]
])
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 цифры).")
return
await create_binding_request(telegram_chat_id, client_card)
if telegram_chat_id in BINDING_PENDING:
BINDING_PENDING.remove(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()
chat_id = query.message.chat_id
client = await get_client_by_telegram_id(chat_id)
if not client:
await query.edit_message_text("❌ Пользователь не найден. Возможно, Вы ещё не привязали свою клубную карту.")
return
invoices = await get_invoices_for_client(client)
message_text = "💳 *Все счета:*\n"
if invoices:
for inv in invoices:
invoice_str = str(inv).split('/')[0].strip()
message_text += f"{invoice_str} (*{inv.sum}*)\n"
else:
message_text += " _Нет счетов_\n"
await query.edit_message_text(message_text, parse_mode="Markdown")

View File

@@ -0,0 +1,33 @@
# bot/management/commands/runbot.py
from django.core.management.base import BaseCommand, CommandError
from bot.utils import create_bot_instance
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, filters, CallbackQueryHandler
from bot.handlers import whoami, show_all_invoices_callback, handle_client_card
from bot.chat import ChatHandler
from bot.welcome import WelcomeHandler
class Command(BaseCommand):
help = 'Запускает Telegram-бота'
def handle(self, *args, **options):
try:
bot = create_bot_instance()
except Exception as e:
raise CommandError(f"Ошибка при создании бота: {e}")
application = ApplicationBuilder().token(bot.token).build()
async def start_command(update, context):
await update.message.reply_text("Здравствуйте! Используйте команду /bind для привязки клиентской карты.")
welcome_handler = WelcomeHandler(bot=application.bot)
application.add_handler(CommandHandler("start", welcome_handler.send_welcome))
application.add_handler(CommandHandler("bind", handle_client_card))
application.add_handler(CommandHandler("whoami", whoami))
application.add_handler(CallbackQueryHandler(show_all_invoices_callback, pattern="^show_all_invoices$"))
chat_handler = ChatHandler(bot=application.bot)
application.add_handler(MessageHandler(filters.TEXT | filters.PHOTO, chat_handler.handle_chat_message))
self.stdout.write("Бот запущен. Ожидание обновлений...")
application.run_polling()
self.stdout.write("Бот остановлен.")

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.1.6 on 2025-03-21 03:44
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='BotConfig',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('bot_token', models.CharField(help_text='Токен для подключения к Telegram API', max_length=255)),
('channel_id', models.CharField(help_text='ID канала/чата, куда бот будет отправлять сообщения', max_length=100)),
('bot_name', models.CharField(help_text='Отображаемое имя бота', max_length=100)),
],
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.1.6 on 2025-03-21 12:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bot', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='botconfig',
name='chat_enabled',
field=models.BooleanField(default=True, help_text='Если выключено, то глобально блокируется рассылка сообщений в чате.', verbose_name='Чат включён'),
),
migrations.AddField(
model_name='botconfig',
name='global_block_message',
field=models.TextField(default='', help_text='Сообщение, которое показывается пользователям, если чат отключён.', verbose_name='Сообщение о глобальном блоке'),
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 5.1.6 on 2025-03-23 01:00
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bot', '0002_botconfig_chat_enabled_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='botconfig',
options={'verbose_name': 'Настройка бота', 'verbose_name_plural': 'Настройки Ботов'},
),
migrations.CreateModel(
name='WelcomeMessage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('welcome_message', models.TextField(blank=True, help_text='Текст, который будет отправлен при запуске команды /start', null=True, verbose_name='Приветственное сообщение')),
('welcome_image', models.URLField(blank=True, help_text='URL изображения для приветствия', null=True, verbose_name='Ссылка на приветственное изображение')),
('admin_contact', models.CharField(blank=True, help_text='Ссылка или контакт для связи с администратором', max_length=255, null=True, verbose_name='Контакт администратора')),
('channel_link', models.URLField(blank=True, help_text='URL канала бота', null=True, verbose_name='Ссылка на канал')),
('group_link', models.URLField(blank=True, help_text='URL группы бота', null=True, verbose_name='Ссылка на группу')),
('custom_link1_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Название кастомной ссылки 1')),
('custom_link1_url', models.URLField(blank=True, null=True, verbose_name='URL кастомной ссылки 1')),
('custom_link2_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Название кастомной ссылки 2')),
('custom_link2_url', models.URLField(blank=True, null=True, verbose_name='URL кастомной ссылки 2')),
('bot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bot.botconfig', verbose_name='Бот')),
],
options={
'verbose_name': 'Приветственное сообщение',
'verbose_name_plural': 'Приветственные сообщения',
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.6 on 2025-03-23 01:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bot', '0003_alter_botconfig_options_welcomemessage'),
]
operations = [
migrations.AlterField(
model_name='welcomemessage',
name='welcome_image',
field=models.ImageField(blank=True, help_text='Загрузите изображение для приветствия', null=True, upload_to='welcome_images/', verbose_name='Приветственное изображение'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.6 on 2025-03-23 01:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bot', '0004_alter_welcomemessage_welcome_image'),
]
operations = [
migrations.AlterField(
model_name='welcomemessage',
name='welcome_image',
field=models.ImageField(blank=True, help_text='Загрузите изображение для приветствия', null=True, upload_to='', verbose_name='Приветственное изображение'),
),
]

View File

87
lottery/bot/models.py Normal file
View File

@@ -0,0 +1,87 @@
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 канала/чата, куда бот будет отправлять сообщения")
bot_name = models.CharField(max_length=100, help_text="Отображаемое имя бота")
chat_enabled = models.BooleanField(
default=True,
verbose_name="Чат включён",
help_text="Если выключено, то глобально блокируется рассылка сообщений в чате."
)
global_block_message = models.TextField(
blank=True,
null=True,
verbose_name="Сообщение о глобальном блоке",
help_text="Сообщение, которое показывается пользователям, если чат отключён."
)
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,
null=True,
help_text="Текст, который будет отправлен при запуске команды /start"
)
welcome_image = models.ImageField(
upload_to='static/upload_image/',
verbose_name="Приветственное изображение",
blank=True,
null=True,
help_text="Загрузите изображение для приветствия"
)
admin_contact = models.CharField(
max_length=255,
verbose_name="Контакт администратора",
blank=True,
null=True,
help_text="Ссылка или контакт для связи с администратором"
)
channel_link = models.URLField(
verbose_name="Ссылка на канал",
blank=True,
null=True,
help_text="URL канала бота"
)
group_link = models.URLField(
verbose_name="Ссылка на группу",
blank=True,
null=True,
help_text="URL группы бота"
)
custom_link1_name = models.CharField(
max_length=100,
verbose_name="Название кастомной ссылки 1",
blank=True,
null=True
)
custom_link1_url = models.URLField(
verbose_name="URL кастомной ссылки 1",
blank=True,
null=True
)
custom_link2_name = models.CharField(
max_length=100,
verbose_name="Название кастомной ссылки 2",
blank=True,
null=True
)
custom_link2_url = models.URLField(
verbose_name="URL кастомной ссылки 2",
blank=True,
null=True
)
class Meta:
verbose_name = "Приветственное сообщение"
verbose_name_plural = "Приветственные сообщения"
def __str__(self):
return f"Приветствие для {self.bot}"

View File

@@ -0,0 +1,105 @@
# notifications.py
import logging
from asgiref.sync import sync_to_async
class NotificationService:
def __init__(self, bot):
"""
Инициализация сервиса уведомлений.
:param bot: экземпляр telegram.Bot, используемый для отправки сообщений.
"""
self.bot = bot
self.logger = logging.getLogger(__name__)
@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"]))
@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"
)
except Exception as e:
self.logger.error(f"Ошибка отправки уведомления о запуске розыгрыша пользователю {client.telegram_id}: {e}")
async def notify_draw_results(self, lottery, results):
"""
Отправляет результаты розыгрыша всем клиентам.
:param lottery: объект розыгрыша (Lottery)
:param results: QuerySet с результатами розыгрыша (например, объекты DrawResult)
"""
# Принудительно загружаем связанные объекты
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()
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"
)
except Exception as e:
self.logger.error(f"Ошибка отправки результатов розыгрыша пользователю {client.telegram_id}: {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"
)
except Exception as e:
self.logger.error(f"Ошибка отправки обновления статуса приза пользователю {client.telegram_id}: {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"
)
except Exception as e:
self.logger.error(f"Ошибка отправки уведомления о привязке пользователю {client.telegram_id}: {e}")

2
lottery/bot/state.py Normal file
View File

@@ -0,0 +1,2 @@
# bot/state.py
BINDING_PENDING = set()

9
lottery/bot/storage.py Normal file
View File

@@ -0,0 +1,9 @@
import os
from django.conf import settings
from django.core.files.storage import FileSystemStorage
# Кастомное хранилище для загрузки изображений в /static/upload_image
static_storage = FileSystemStorage(
location=os.path.join(settings.BASE_DIR, 'static', 'upload_image'),
base_url='/static/upload_image/'
)

3
lottery/bot/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

39
lottery/bot/utils.py Normal file
View File

@@ -0,0 +1,39 @@
# bot/utils.py
from telegram import Bot
from django.conf import settings
from telegram import Bot
from bot.models import BotConfig
from django.core.exceptions import ImproperlyConfigured
def create_bot_instance():
"""
Получает настройки бота из БД и создаёт экземпляр Telegram Bot.
"""
config = BotConfig.objects.first() # предполагается, что конфигурация одна
if not config:
raise ImproperlyConfigured("Настройки бота (BotConfig) не сконфигурированы в базе данных.")
# Можно дополнительно использовать config.channel_id и config.bot_name при необходимости.
bot = Bot(token=config.bot_token)
return bot
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 "Ваша заявка на привязку отклонена."
try:
bot.send_message(chat_id=binding_request.telegram_chat_id, text=message)
except Exception as e:
# Обработка ошибок отправки сообщения
print(f"Ошибка уведомления: {e}")
# Пример использования в точке входа приложения:
if __name__ == '__main__':
# Предполагается, что Django уже настроен (например, через manage.py shell или management command)
bot = create_bot_instance()
# Теперь можно использовать объект bot для отправки сообщений, обработки обновлений и т.д.
print(f"Бот {bot.name} успешно создан!")

3
lottery/bot/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

82
lottery/bot/welcome.py Normal file
View File

@@ -0,0 +1,82 @@
# bot/welcome.py
import logging
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes
from asgiref.sync import sync_to_async
from bot.models import WelcomeMessage
class WelcomeHandler:
def __init__(self, bot):
"""
Инициализация обработчика приветственных сообщений.
:param bot: экземпляр telegram.Bot для отправки сообщений.
"""
self.bot = bot
self.logger = logging.getLogger(__name__)
@sync_to_async
def get_welcome_config(self):
"""
Получает первую запись настроек приветствия.
"""
try:
return WelcomeMessage.objects.select_related("bot").first()
except WelcomeMessage.DoesNotExist:
return None
async def send_welcome(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""
Отправляет приветственное сообщение с inline-кнопками, расположенными в 3 строки:
1 кнопка, затем 2 и затем 2.
"""
if not update.message:
return
config = await self.get_welcome_config()
if not config:
await update.message.reply_text("Добро пожаловать!")
return
# Формируем список всех кнопок
buttons = []
if config.admin_contact:
buttons.append(InlineKeyboardButton("📞 Связаться с администратором", url=config.admin_contact))
if config.channel_link:
buttons.append(InlineKeyboardButton(" 📢 Канал", url=config.channel_link))
if config.group_link:
buttons.append(InlineKeyboardButton("👥 Группа", url=config.group_link))
if config.custom_link1_name and config.custom_link1_url:
buttons.append(InlineKeyboardButton(config.custom_link1_name, url=config.custom_link1_url))
if config.custom_link2_name and config.custom_link2_url:
buttons.append(InlineKeyboardButton(config.custom_link2_name, url=config.custom_link2_url))
# Распределяем кнопки по рядам: первая строка 1 кнопка, вторая 2, третья 2.
row1 = buttons[0:1] if len(buttons) >= 1 else []
row2 = buttons[1:3] if len(buttons) >= 3 else buttons[1:] if len(buttons) > 1 else []
row3 = buttons[3:5] if len(buttons) >= 5 else []
# Собираем список рядов, исключая пустые строки
keyboard = []
if row1:
keyboard.append(row1)
if row2:
keyboard.append(row2)
if row3:
keyboard.append(row3)
reply_markup = InlineKeyboardMarkup(keyboard) if keyboard else None
# Отправка сообщения с изображением, если задано
if config.welcome_image:
await update.message.reply_photo(
photo=config.welcome_image,
caption=config.welcome_message,
parse_mode="Markdown",
reply_markup=reply_markup
)
else:
await update.message.reply_text(
text=config.welcome_message,
parse_mode="Markdown",
reply_markup=reply_markup
)