From b4c06d09a3ebd06f7147790b8524399020578651 Mon Sep 17 00:00:00 2001 From: Admin Date: Wed, 4 Mar 2026 18:32:09 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=B8=20=D0=A0=D0=90=D0=A1=D0=A1=D0=AB?= =?UTF-8?q?=D0=9B=D0=9A=D0=90=20=D0=A1=20=D0=9A=D0=90=D0=A0=D0=A2=D0=98?= =?UTF-8?q?=D0=9D=D0=9A=D0=90=D0=9C=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Реализовано: - Рассылка текста (работает) - Рассылка картинки с подписью (НОВОЕ!) - Отправка в общий чат + в личку каждому - Отчёт админу об успешной рассылке 🎯 Как использовать: 1. /admin → 📤 Рассылка 2. Отправить текст ИЛИ картинку с подписью 3. Бот разошлёт всем верифицированным: - В общий чат - В личку каждому 📝 Для срочного объявления: 1. Напиши боту в личку 2. /admin 3. 📤 Рассылка 4. Отправь картинку + текст 5. Готово! 💡 Важно: - Рассылка только верифицированным - Картинка сжимается Telegram - Подпись до 1024 символов Co-authored-by: Qwen-Coder --- handlers/admin.py | 124 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 100 insertions(+), 24 deletions(-) diff --git a/handlers/admin.py b/handlers/admin.py index d5aec69..576d6fc 100644 --- a/handlers/admin.py +++ b/handlers/admin.py @@ -32,6 +32,7 @@ router = Router() class BroadcastState(StatesGroup): waiting_for_message = State() + waiting_for_photo = State() # Состояние для ожидания картинки # ============================================================================= @@ -296,7 +297,7 @@ async def cb_admin_export(callback: CallbackQuery): async def cb_admin_broadcast(callback: CallbackQuery): """Рассылка сообщений""" await callback.answer() - + # Проверяем что это личка if callback.message.chat.type != 'private': await callback.message.answer( @@ -305,14 +306,15 @@ async def cb_admin_broadcast(callback: CallbackQuery): parse_mode='HTML' ) return - + await callback.message.answer( '📤 Рассылка\n\n' - 'Теперь отправьте сообщение, которое нужно разослать всем жильцам.\n\n' + 'Вариант 1: Отправьте текст\n' + 'Вариант 2: Отправьте картинку с подписью\n\n' 'Или отмените командой /cancel', parse_mode='HTML' ) - + # Включаем состояние await callback.bot.set_state(callback.from_user.id, BroadcastState.waiting_for_message) @@ -324,54 +326,81 @@ async def cb_admin_broadcast(callback: CallbackQuery): @router.message(StateFilter(BroadcastState.waiting_for_message)) @admin_only async def process_broadcast_message(message: Message): - """Обработка сообщения для рассылки""" - broadcast_text = message.text - - # Отключаем состояние - await message.bot.delete_state(message.from_user.id) - - # Подтверждаем - await message.answer( - f'📤 Начинаю рассылку...\n\n' - f'Сообщение: {broadcast_text[:100]}...' - ) + """Обработка сообщения для рассылки (текст или фото)""" + # Проверяем что прислал админ + if message.photo: + # Это картинка с подписью + photo_id = message.photo[-1].file_id # Берём наибольшее качество + caption = message.caption or '' + + # Отключаем состояние + await message.bot.delete_state(message.from_user.id) + + # Подтверждаем + await message.answer( + f'📤 Начинаю рассылку с картинкой...\n\n' + f'Текст: {caption[:100]}...' if caption else 'Без текста' + ) + + # Рассылаем + await send_broadcast_with_photo(message.bot, photo_id, caption) + else: + # Это текст + broadcast_text = message.text or '' + + # Отключаем состояние + await message.bot.delete_state(message.from_user.id) + + # Подтверждаем + await message.answer( + f'📤 Начинаю рассылку...\n\n' + f'Сообщение: {broadcast_text[:100]}...' + ) + + # Рассылаем текст + await send_broadcast_text(message.bot, broadcast_text) + + +async def send_broadcast_text(bot, text: str): + """Рассылка текста""" # Отправляем в чат дома try: - await message.bot.send_message( + await bot.send_message( config.ADMIN_CHAT_ID, f'📢 Объявление от администрации\n\n' - f'{broadcast_text}', + f'{text}', parse_mode='HTML' ) logger.info(f'Рассылка отправлена в чат {config.ADMIN_CHAT_ID}') except Exception as e: logger.error(f'Ошибка отправки в чат: {e}') - + # Рассылаем всем верифицированным в личку async with AsyncSessionLocal() as session: stmt = select(User).where(User.verified == True) result = await session.execute(stmt) users = list(result.scalars().all()) - + success_count = 0 error_count = 0 - + for user in users: try: - await message.bot.send_message( + await bot.send_message( user.user_id, f'📢 Объявление от администрации\n\n' - f'{broadcast_text}', + f'{text}', parse_mode='HTML' ) success_count += 1 except Exception as e: logger.error(f'Не удалось отправить пользователю {user.user_id}: {e}') error_count += 1 - + # Отчёт админу - await message.answer( + await bot.send_message( + config.ADMIN_USER_ID, f'✅ Рассылка завершена!\n\n' f'Успешно: {success_count}\n' f'Ошибок: {error_count}\n' @@ -380,6 +409,53 @@ async def process_broadcast_message(message: Message): ) +async def send_broadcast_with_photo(bot, photo_id: str, caption: str): + """Рассылка картинки с подписью""" + # Отправляем в чат дома + try: + await bot.send_photo( + config.ADMIN_CHAT_ID, + photo_id, + caption=f'📢 Объявление от администрации\n\n{caption}', + parse_mode='HTML' + ) + logger.info(f'Рассылка с фото отправлена в чат {config.ADMIN_CHAT_ID}') + except Exception as e: + logger.error(f'Ошибка отправки фото в чат: {e}') + + # Рассылаем всем верифицированным в личку + async with AsyncSessionLocal() as session: + stmt = select(User).where(User.verified == True) + result = await session.execute(stmt) + users = list(result.scalars().all()) + + success_count = 0 + error_count = 0 + + for user in users: + try: + await bot.send_photo( + user.user_id, + photo_id, + caption=f'📢 Объявление от администрации\n\n{caption}', + parse_mode='HTML' + ) + success_count += 1 + except Exception as e: + logger.error(f'Не удалось отправить фото пользователю {user.user_id}: {e}') + error_count += 1 + + # Отчёт админу + await bot.send_message( + config.ADMIN_USER_ID, + f'✅ Рассылка с фото завершена!\n\n' + f'Успешно: {success_count}\n' + f'Ошибок: {error_count}\n' + f'В чат: ✅', + parse_mode='HTML' + ) + + # ============================================================================= # СНЯТИЕ ВЕРИФИКАЦИИ (БЫСТРОЕ) # =============================================================================