From 6f8ebbb9f0fa5fca2202c63ef177cc33b76957ef Mon Sep 17 00:00:00 2001 From: Egor Date: Mon, 8 Sep 2025 06:10:34 +0300 Subject: [PATCH] Create updates.py --- app/handlers/admin/updates.py | 274 ++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 app/handlers/admin/updates.py diff --git a/app/handlers/admin/updates.py b/app/handlers/admin/updates.py new file mode 100644 index 00000000..53716ff1 --- /dev/null +++ b/app/handlers/admin/updates.py @@ -0,0 +1,274 @@ +import logging +from aiogram import Dispatcher, types, F +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from sqlalchemy.ext.asyncio import AsyncSession + +from app.config import settings +from app.database.models import User +from app.services.version_service import version_service +from app.utils.decorators import admin_required, error_handler + +logger = logging.getLogger(__name__) + + +def get_updates_keyboard(language: str = "ru") -> InlineKeyboardMarkup: + buttons = [ + [ + InlineKeyboardButton( + text="🔄 Проверить обновления", + callback_data="admin_updates_check" + ) + ], + [ + InlineKeyboardButton( + text="📋 Информация о версии", + callback_data="admin_updates_info" + ) + ], + [ + InlineKeyboardButton( + text="🔗 Открыть репозиторий", + url=f"https://github.com/{version_service.repo}/releases" + ) + ], + [ + InlineKeyboardButton( + text="◀️ Назад", + callback_data="admin_panel" + ) + ] + ] + + return InlineKeyboardMarkup(inline_keyboard=buttons) + + +def get_version_info_keyboard(language: str = "ru") -> InlineKeyboardMarkup: + buttons = [ + [ + InlineKeyboardButton( + text="🔄 Обновить", + callback_data="admin_updates_info" + ) + ], + [ + InlineKeyboardButton( + text="◀️ К обновлениям", + callback_data="admin_updates" + ) + ] + ] + + return InlineKeyboardMarkup(inline_keyboard=buttons) + + +@admin_required +@error_handler +async def show_updates_menu( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession +): + try: + version_info = await version_service.get_version_info() + + current_version = version_info['current_version'] + has_updates = version_info['has_updates'] + total_newer = version_info['total_newer'] + last_check = version_info['last_check'] + + status_icon = "🆕" if has_updates else "✅" + status_text = f"Доступно {total_newer} обновлений" if has_updates else "Актуальная версия" + + last_check_text = "" + if last_check: + last_check_text = f"\n🕐 Последняя проверка: {last_check.strftime('%d.%m.%Y %H:%M')}" + + message = f"""🔄 СИСТЕМА ОБНОВЛЕНИЙ + +📦 Текущая версия: {current_version} +{status_icon} Статус: {status_text} + +🔗 Репозиторий: {version_service.repo}{last_check_text} + +ℹ️ Система автоматически проверяет обновления каждый час и отправляет уведомления о новых версиях.""" + + await callback.message.edit_text( + message, + reply_markup=get_updates_keyboard(db_user.language), + parse_mode="HTML" + ) + await callback.answer() + + except Exception as e: + logger.error(f"Ошибка показа меню обновлений: {e}") + await callback.answer("❌ Ошибка загрузки меню обновлений", show_alert=True) + + +@admin_required +@error_handler +async def check_updates( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession +): + await callback.answer("🔄 Проверяю обновления...") + + try: + has_updates, newer_releases = await version_service.check_for_updates(force=True) + + if not has_updates: + message = f"""✅ ОБНОВЛЕНИЯ НЕ НАЙДЕНЫ + +📦 Текущая версия: {version_service.current_version} +🎯 Статус: У вас установлена последняя версия + +🔗 Репозиторий: {version_service.repo}""" + + else: + updates_list = [] + for i, release in enumerate(newer_releases[:5]): + icon = version_service.format_version_display(release).split()[0] + updates_list.append( + f"{i+1}. {icon} {release.tag_name} • {release.formatted_date}" + ) + + updates_text = "\n".join(updates_list) + more_text = f"\n\n📋 И еще {len(newer_releases) - 5} обновлений..." if len(newer_releases) > 5 else "" + + message = f"""🆕 НАЙДЕНЫ ОБНОВЛЕНИЯ + +📦 Текущая версия: {version_service.current_version} +🎯 Доступно обновлений: {len(newer_releases)} + +📋 Последние версии: +{updates_text}{more_text} + +🔗 Репозиторий: {version_service.repo}""" + + keyboard = get_updates_keyboard(db_user.language) + + if has_updates: + keyboard.inline_keyboard.insert(-2, [ + InlineKeyboardButton( + text="📋 Подробнее о версиях", + callback_data="admin_updates_info" + ) + ]) + + await callback.message.edit_text( + message, + reply_markup=keyboard, + parse_mode="HTML" + ) + + except Exception as e: + logger.error(f"Ошибка проверки обновлений: {e}") + await callback.message.edit_text( + f"❌ ОШИБКА ПРОВЕРКИ ОБНОВЛЕНИЙ\n\n" + f"Не удалось связаться с сервером GitHub.\n" + f"Попробуйте позже.\n\n" + f"📦 Текущая версия: {version_service.current_version}", + reply_markup=get_updates_keyboard(db_user.language), + parse_mode="HTML" + ) + + +@admin_required +@error_handler +async def show_version_info( + callback: types.CallbackQuery, + db_user: User, + db: AsyncSession +): + await callback.answer("📋 Загружаю информацию о версиях...") + + try: + version_info = await version_service.get_version_info() + + current_version = version_info['current_version'] + current_release = version_info['current_release'] + newer_releases = version_info['newer_releases'] + has_updates = version_info['has_updates'] + last_check = version_info['last_check'] + repo_url = version_info['repo_url'] + + current_info = f"📦 ТЕКУЩАЯ ВЕРСИЯ\n\n" + + if current_release: + current_info += f"🏷️ Версия: {current_release.tag_name}\n" + current_info += f"📅 Дата релиза: {current_release.formatted_date}\n" + if current_release.short_description: + current_info += f"📝 Описание:\n{current_release.short_description}\n" + else: + current_info += f"🏷️ Версия: {current_version}\n" + current_info += f"ℹ️ Статус: Информация о релизе недоступна\n" + + message_parts = [current_info] + + if has_updates and newer_releases: + updates_info = f"\n🆕 ДОСТУПНЫЕ ОБНОВЛЕНИЯ\n\n" + + for i, release in enumerate(newer_releases): + icon = "🔥" if i == 0 else "📦" + if release.prerelease: + icon = "🧪" + elif release.is_dev: + icon = "🔧" + + updates_info += f"{icon} {release.tag_name}\n" + updates_info += f" 📅 {release.formatted_date}\n" + if release.short_description: + updates_info += f" 📝 {release.short_description}\n" + updates_info += "\n" + + message_parts.append(updates_info.rstrip()) + + system_info = f"\n🔧 СИСТЕМА ОБНОВЛЕНИЙ\n\n" + system_info += f"🔗 Репозиторий: {version_service.repo}\n" + system_info += f"⚡ Автопроверка: {'Включена' if version_service.enabled else 'Отключена'}\n" + system_info += f"🕐 Интервал: Каждый час\n" + + if last_check: + system_info += f"🕐 Последняя проверка: {last_check.strftime('%d.%m.%Y %H:%M')}\n" + + message_parts.append(system_info.rstrip()) + + final_message = "\n".join(message_parts) + + if len(final_message) > 4000: + final_message = final_message[:3900] + "\n\n... (информация обрезана)" + + await callback.message.edit_text( + final_message, + reply_markup=get_version_info_keyboard(db_user.language), + parse_mode="HTML", + disable_web_page_preview=True + ) + + except Exception as e: + logger.error(f"Ошибка получения информации о версиях: {e}") + await callback.message.edit_text( + f"❌ ОШИБКА ЗАГРУЗКИ\n\n" + f"Не удалось получить информацию о версиях.\n\n" + f"📦 Текущая версия: {version_service.current_version}", + reply_markup=get_version_info_keyboard(db_user.language), + parse_mode="HTML" + ) + + +def register_handlers(dp: Dispatcher): + + dp.callback_query.register( + show_updates_menu, + F.data == "admin_updates" + ) + + dp.callback_query.register( + check_updates, + F.data == "admin_updates_check" + ) + + dp.callback_query.register( + show_version_info, + F.data == "admin_updates_info" + )