diff --git a/bot/handlers/admin/promo_codes.py b/bot/handlers/admin/promo_codes.py index 9775ecf..b230ec8 100644 --- a/bot/handlers/admin/promo_codes.py +++ b/bot/handlers/admin/promo_codes.py @@ -35,7 +35,7 @@ async def create_promo_prompt_handler(callback: types.CallbackQuery, # Step 1: Ask for promo code prompt_text = _( "admin_promo_step1_code", - default="🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 1 ΠΈΠ· 5: Код ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΠ΄ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° (3-30 символов, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π±ΡƒΠΊΠ²Ρ‹ ΠΈ Ρ†ΠΈΡ„Ρ€Ρ‹):" + default="🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 1 ΠΈΠ· 4: Код ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΠ΄ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° (3-30 символов, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π±ΡƒΠΊΠ²Ρ‹ ΠΈ Ρ†ΠΈΡ„Ρ€Ρ‹):" ) try: @@ -91,7 +91,7 @@ async def process_promo_code_handler(message: types.Message, # Step 2: Ask for bonus days prompt_text = _( "admin_promo_step2_bonus_days", - default="🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 2 ΠΈΠ· 5: БонусныС Π΄Π½ΠΈ\n\nКод: {code}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство бонусных Π΄Π½Π΅ΠΉ (1-365):", + default="🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 2 ΠΈΠ· 4: БонусныС Π΄Π½ΠΈ\n\nКод: {code}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство бонусных Π΄Π½Π΅ΠΉ (1-365):", code=code_str ) @@ -137,7 +137,7 @@ async def process_promo_bonus_days_handler(message: types.Message, # Step 3: Ask for max activations prompt_text = _( "admin_promo_step3_max_activations", - default="🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 3 ΠΈΠ· 5: ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ максимальноС количСство Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΉ (1-10000):", + default="🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 3 ΠΈΠ· 4: ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ максимальноС количСство Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΉ (1-10000):", code=data['promo_code'], bonus_days=bonus_days ) @@ -203,7 +203,7 @@ async def process_promo_max_activations_handler(message: types.Message, prompt_text = _( "admin_promo_step4_validity", - default="🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 4 ΠΈΠ· 5: Π‘Ρ€ΠΎΠΊ дСйствия\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\nМакс. Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_activations}\n\nΠ’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ срок дСйствия ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°:", + default="🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 4 ΠΈΠ· 4: Π‘Ρ€ΠΎΠΊ дСйствия\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\nМакс. Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_activations}\n\nΠ’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ срок дСйствия ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°:", code=data['promo_code'], bonus_days=data['bonus_days'], max_activations=max_activations @@ -227,9 +227,10 @@ async def process_promo_max_activations_handler(message: types.Message, async def process_promo_unlimited_validity(callback: types.CallbackQuery, state: FSMContext, i18n_data: dict, - settings: Settings): + settings: Settings, + session: AsyncSession): await state.update_data(validity_days=None) - await ask_for_promo_description(callback, state, i18n_data, settings) + await create_promo_code_final(callback, state, i18n_data, settings, session) @router.callback_query(F.data == "promo_set_validity", StateFilter(AdminStates.waiting_for_promo_validity_days)) async def process_promo_set_validity(callback: types.CallbackQuery, @@ -293,7 +294,7 @@ async def process_promo_validity_days_handler(message: types.Message, data="fake" ) - await ask_for_promo_description(fake_callback, state, i18n_data, settings) + await create_promo_code_final(fake_callback, state, i18n_data, settings, session) except ValueError: await message.answer(_( @@ -301,116 +302,7 @@ async def process_promo_validity_days_handler(message: types.Message, default="❌ Π”Π½ΠΈ дСйствия Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 1 Π΄ΠΎ 365" )) -async def ask_for_promo_description(callback_or_message, - state: FSMContext, - i18n_data: dict, - settings: Settings): - """Ask for promo description (final step)""" - current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) - i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") - if not i18n: - if hasattr(callback_or_message, 'answer'): - await callback_or_message.answer("Language service error.", show_alert=True) - else: - await callback_or_message.reply("Language service error.") - return - _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) - - data = await state.get_data() - validity_days = data.get('validity_days') - validity_text = "НСограничСнно" if validity_days is None else f"{validity_days} Π΄Π½." - - # Step 5: Ask for description (optional) - keyboard = InlineKeyboardBuilder() - keyboard.row( - InlineKeyboardButton( - text=_("admin_promo_skip_description", default="⏭️ ΠŸΡ€ΠΎΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ"), - callback_data="promo_skip_description" - ) - ) - keyboard.row( - InlineKeyboardButton( - text=_("admin_panel_back_button", default="⬅️ Назад"), - callback_data="admin_panel_back" - ) - ) - - prompt_text = _( - "admin_promo_step5_description", - default="🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 5 ΠΈΠ· 5: ОписаниС (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ)\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\nМакс. Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_activations}\nДСйствуСт: {validity}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ описаниС ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° ΠΈΠ»ΠΈ Π½Π°ΠΆΠΌΠΈΡ‚Π΅ \"ΠŸΡ€ΠΎΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ\":", - code=data['promo_code'], - bonus_days=data['bonus_days'], - max_activations=data['max_activations'], - validity=validity_text - ) - - if hasattr(callback_or_message, 'message'): - try: - await callback_or_message.message.edit_text( - prompt_text, - reply_markup=keyboard.as_markup(), - parse_mode="HTML" - ) - except: - await callback_or_message.message.answer( - prompt_text, - reply_markup=keyboard.as_markup(), - parse_mode="HTML" - ) - if hasattr(callback_or_message, 'answer'): - await callback_or_message.answer() - else: - await callback_or_message.answer( - prompt_text, - reply_markup=keyboard.as_markup(), - parse_mode="HTML" - ) - - await state.set_state(AdminStates.waiting_for_promo_description) - -# Handle skip description button -@router.callback_query(F.data == "promo_skip_description", StateFilter(AdminStates.waiting_for_promo_description)) -async def process_promo_skip_description(callback: types.CallbackQuery, - state: FSMContext, - i18n_data: dict, - settings: Settings, - session: AsyncSession): - await state.update_data(description=None) - await create_promo_code_final(callback, state, i18n_data, settings, session) - -# Step 5: Process description (optional) -@router.message(AdminStates.waiting_for_promo_description, F.text) -async def process_promo_description_handler(message: types.Message, - state: FSMContext, - i18n_data: dict, - settings: Settings, - session: AsyncSession): - description = message.text.strip() - if len(description) > 200: - current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) - i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") - if i18n: - _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) - await message.answer(_( - "admin_promo_description_too_long", - default="❌ ОписаниС Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΏΡ€Π΅Π²Ρ‹ΡˆΠ°Ρ‚ΡŒ 200 символов" - )) - else: - await message.answer("Description too long") - return - - await state.update_data(description=description) - - # Create a fake callback for consistency - fake_callback = types.CallbackQuery( - id="fake", - from_user=message.from_user, - chat_instance="fake", - message=message, - data="fake" - ) - - await create_promo_code_final(fake_callback, state, i18n_data, settings, session) +# Description handlers removed - not needed for this model async def create_promo_code_final(callback_or_message, state: FSMContext, @@ -433,7 +325,6 @@ async def create_promo_code_final(callback_or_message, bonus_days = data['bonus_days'] max_activations = data['max_activations'] validity_days = data.get('validity_days') - description = data.get('description') # Create validity date valid_until_date: Optional[datetime] = None @@ -468,7 +359,7 @@ async def create_promo_code_final(callback_or_message, "current_activations": 0, "is_active": True, "valid_until": valid_until_date, - "description": description + "created_by_admin_id": callback_or_message.from_user.id if hasattr(callback_or_message, 'from_user') and callback_or_message.from_user else 0 } await promo_code_dal.create_promo_code(session, promo_data) @@ -477,12 +368,11 @@ async def create_promo_code_final(callback_or_message, # Send success message success_text = _( "admin_promo_created_success", - default="βœ… ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ создан!\n\n🎟 Код: {code}\n🎁 БонусныС Π΄Π½ΠΈ: {days}\nπŸ”’ ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_act}\n⏰ ДСйствуСт Π΄ΠΎ: {validity}\nπŸ“ ОписаниС: {description}", + default="βœ… ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ создан!\n\n🎟 Код: {code}\n🎁 БонусныС Π΄Π½ΠΈ: {days}\nπŸ”’ ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_act}\n⏰ ДСйствуСт Π΄ΠΎ: {validity}", code=promo_code, days=bonus_days, max_act=max_activations, - validity=valid_until_str_display, - description=description or "НС ΡƒΠΊΠ°Π·Π°Π½ΠΎ" + validity=valid_until_str_display ) if hasattr(callback_or_message, 'message'): @@ -721,9 +611,7 @@ async def promo_delete_handler(callback: types.CallbackQuery, i18n_data: dict, AdminStates.waiting_for_promo_bonus_days, AdminStates.waiting_for_promo_max_activations, AdminStates.waiting_for_promo_validity_days, - AdminStates.waiting_for_promo_description, AdminStates.waiting_for_promo_edit_details, - AdminStates.waiting_for_bulk_promo_details, AdminStates.waiting_for_bulk_promo_quantity, AdminStates.waiting_for_bulk_promo_bonus_days, AdminStates.waiting_for_bulk_promo_max_activations, @@ -1108,7 +996,7 @@ async def create_bulk_promo_codes_final(callback_or_message, "current_activations": 0, "is_active": True, "valid_until": valid_until_date, - "description": f"Bulk created #{i+1}/{quantity}" + "created_by_admin_id": callback_or_message.from_user.id if hasattr(callback_or_message, 'from_user') and callback_or_message.from_user else 0 } await promo_code_dal.create_promo_code(session, promo_data) @@ -1177,177 +1065,7 @@ async def create_bulk_promo_codes_final(callback_or_message, await state.clear() -@router.message(AdminStates.waiting_for_bulk_promo_details, F.text) -async def process_bulk_promo_details_handler(message: types.Message, - state: FSMContext, - i18n_data: dict, - settings: Settings, - session: AsyncSession): - current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) - i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") - if not i18n: - await message.reply("Language service error.") - return - _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) - - if not message.text: - await message.answer(_("admin_promo_invalid_format")) - return - - parts = message.text.strip().split() - if not (3 <= len(parts) <= 4): - await message.answer(_( - "admin_bulk_promo_invalid_format", - default="❌ НСвСрный Ρ„ΠΎΡ€ΠΌΠ°Ρ‚. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅: <количСство> <бонусныС_Π΄Π½ΠΈ> <максимальноС_использованиС> [Π΄Π½ΠΈ_дСйствия]" - )) - return - - try: - count = int(parts[0]) - bonus_days = int(parts[1]) - max_activations = int(parts[2]) - - if count <= 0 or count > 1000: - raise ValueError(_("admin_bulk_promo_invalid_count", default="ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‚ 1 Π΄ΠΎ 1000")) - - if bonus_days <= 0 or max_activations <= 0: - raise ValueError(_("admin_promo_invalid_bonus_or_activations")) - - valid_until_date: Optional[datetime] = None - valid_until_str_display = _("admin_promo_valid_indefinitely") - - if len(parts) == 4: - valid_days_from_now = int(parts[3]) - if valid_days_from_now <= 0: - raise ValueError(_("admin_promo_invalid_validity_days")) - valid_until_date = datetime.now( - timezone.utc) + timedelta(days=valid_days_from_now) - valid_until_str_display = _( - "admin_promo_valid_until_display", - date=valid_until_date.strftime('%Y-%m-%d')) - - except ValueError as e: - await message.answer(_( - "admin_bulk_promo_invalid_values", - default="❌ НСвСрныС значСния: {error}", - error=str(e) - )) - return - except Exception as e_parse: - logging.error(f"Error parsing bulk promo details '{message.text}': {e_parse}") - await message.answer(_("admin_promo_invalid_format_general")) - return - - admin_id = message.from_user.id if message.from_user else 0 - - # Generate unique codes and create promo codes - created_codes = [] - failed_codes = [] - - await message.answer(_( - "admin_bulk_promo_creating", - default="πŸ”„ Боздаю {count} ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²...", - count=count - )) - - for i in range(count): - try: - # Generate unique code - attempts = 0 - while attempts < 10: # Max 10 attempts to generate unique code - code = generate_unique_promo_code() - - # Check if code already exists - existing_promo = await promo_code_dal.get_promo_code_by_code(session, code) - if not existing_promo: - break - attempts += 1 - - if attempts >= 10: - failed_codes.append(f"Failed to generate unique code #{i+1}") - continue - - promo_data_to_create = { - "code": code, - "bonus_days": bonus_days, - "max_activations": max_activations, - "created_by_admin_id": admin_id, - "valid_until": valid_until_date, - "is_active": True, - "current_activations": 0 - } - - created_promo = await promo_code_dal.create_promo_code( - session, promo_data_to_create) - - if created_promo: - created_codes.append(created_promo.code) - else: - failed_codes.append(f"Failed to create code #{i+1}") - - except Exception as e: - logging.error(f"Error creating bulk promo code #{i+1}: {e}") - failed_codes.append(f"Error creating code #{i+1}: {str(e)}") - - try: - await session.commit() - - # Prepare success message - success_text_parts = [ - _( - "admin_bulk_promo_created_success", - default="βœ… МассовоС созданиС Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΎ!\n\nπŸ“¦ Π‘ΠΎΠ·Π΄Π°Π½ΠΎ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²: {created_count}\nπŸ’Ž Бонусных Π΄Π½Π΅ΠΉ: {bonus_days}\nπŸ”„ Макс. Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΉ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ: {max_activations}\n⏰ Π”Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ Π΄ΠΎ: {valid_until}", - created_count=len(created_codes), - bonus_days=bonus_days, - max_activations=max_activations, - valid_until=valid_until_str_display - ) - ] - - if failed_codes: - success_text_parts.append(f"\n❌ Ошибок: {len(failed_codes)}") - - if created_codes: - # Show first few codes as examples - codes_to_show = created_codes[:10] # Show first 10 - success_text_parts.append(f"\nπŸ“ ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ созданных ΠΊΠΎΠ΄ΠΎΠ²:") - success_text_parts.append("\n".join([f"β€’ {code}" for code in codes_to_show])) - - if len(created_codes) > 10: - success_text_parts.append(f"... ΠΈ Π΅Ρ‰Π΅ {len(created_codes) - 10} ΠΊΠΎΠ΄ΠΎΠ²") - - # Send codes as a file if many were created - if len(created_codes) > 20: - try: - codes_text = "\n".join(created_codes) - codes_file = types.BufferedInputFile( - codes_text.encode('utf-8'), - filename=f"promo_codes_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt" - ) - await message.answer_document( - codes_file, - caption=_( - "admin_bulk_promo_codes_file", - default="πŸ“„ Π€Π°ΠΉΠ» со всСми созданными ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°ΠΌΠΈ" - ) - ) - except Exception as e: - logging.error(f"Failed to send codes file: {e}") - - final_text = "\n".join(success_text_parts) - await message.answer( - final_text, - reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n)) - - except Exception as e_db_commit: - await session.rollback() - logging.error(f"Failed to commit bulk promo codes creation: {e_db_commit}", exc_info=True) - await message.answer(_( - "admin_bulk_promo_creation_failed", - default="❌ Ошибка ΠΏΡ€ΠΈ сохранСнии ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ² Π² Π±Π°Π·Ρƒ Π΄Π°Π½Π½Ρ‹Ρ…" - )) - - await state.clear() +# Old bulk promo handler removed - now using step-by-step process diff --git a/bot/handlers/admin/statistics.py b/bot/handlers/admin/statistics.py index bcc7dce..d7c1af4 100644 --- a/bot/handlers/admin/statistics.py +++ b/bot/handlers/admin/statistics.py @@ -139,20 +139,27 @@ async def show_statistics_handler(callback: types.CallbackQuery, stats_text_parts.append(f"\n{_('admin_sync_status_never_run')}") # Panel Statistics + stats_text_parts.append(f"\nπŸ–₯ {_('admin_panel_stats_header', default='Бтатистика ΠΏΠ°Π½Π΅Π»ΠΈ')}") + try: panel_service = PanelApiService(settings) # Get panel system statistics + logging.info("Fetching panel statistics...") panel_stats = await panel_service.get_panel_statistics() nodes_stats = await panel_service.get_nodes_statistics() online_count = await panel_service.get_online_users_count() users_activity = await panel_service.get_users_activity_stats() - stats_text_parts.append(f"\nπŸ–₯ {_('admin_panel_stats_header', default='Бтатистика ΠΏΠ°Π½Π΅Π»ΠΈ')}") + logging.info(f"Panel stats response: panel_stats={panel_stats}, nodes_stats={nodes_stats}, online_count={online_count}, users_activity={users_activity}") + # Online users if online_count is not None: stats_text_parts.append(f"🟒 Онлайн сСйчас: {online_count}") + else: + stats_text_parts.append(f"🟒 Онлайн сСйчас: N/A") + # Users activity if users_activity: today_connected = users_activity.get('today_connected', 'N/A') week_connected = users_activity.get('week_connected', 'N/A') @@ -160,12 +167,18 @@ async def show_statistics_handler(callback: types.CallbackQuery, stats_text_parts.append(f"πŸ“… ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π°Π»ΠΈΡΡŒ сСгодня: {today_connected}") stats_text_parts.append(f"πŸ“… ΠŸΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π°Π»ΠΈΡΡŒ Π·Π° нСдСлю: {week_connected}") stats_text_parts.append(f"❌ Никогда Π½Π΅ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π°Π»ΠΈΡΡŒ: {never_connected}") + else: + stats_text_parts.append(f"πŸ“… ΠΠΊΡ‚ΠΈΠ²Π½ΠΎΡΡ‚ΡŒ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ: N/A") + # Nodes statistics if nodes_stats: active_nodes = len([node for node in nodes_stats if node.get('status') == 'active']) total_nodes = len(nodes_stats) stats_text_parts.append(f"πŸ”— Активных Π½ΠΎΠ΄: {active_nodes}/{total_nodes}") + else: + stats_text_parts.append(f"πŸ”— Ноды: N/A") + # System statistics if panel_stats: system_info = panel_stats.get('system', {}) if system_info: @@ -173,13 +186,17 @@ async def show_statistics_handler(callback: types.CallbackQuery, cpu_usage = system_info.get('cpu_usage_percent', 'N/A') stats_text_parts.append(f"πŸ’Ύ ИспользованиС RAM: {memory_usage}%") stats_text_parts.append(f"πŸ”„ Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° CPU: {cpu_usage}%") + else: + stats_text_parts.append(f"πŸ’Ύ БистСмная информация: N/A") + else: + stats_text_parts.append(f"πŸ’Ύ БистСмная статистика: N/A") await panel_service.close() except Exception as e: - logging.warning(f"Failed to fetch panel statistics: {e}") - stats_text_parts.append(f"\nπŸ–₯ Бтатистика ΠΏΠ°Π½Π΅Π»ΠΈ") - stats_text_parts.append("❌ НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ Π΄Π°Π½Π½Ρ‹Π΅ с ΠΏΠ°Π½Π΅Π»ΠΈ") + logging.error(f"Failed to fetch panel statistics: {e}", exc_info=True) + stats_text_parts.append("❌ Ошибка получСния Π΄Π°Π½Π½Ρ‹Ρ… с ΠΏΠ°Π½Π΅Π»ΠΈ") + stats_text_parts.append(f"⚠️ Π”Π΅Ρ‚Π°Π»ΠΈ: {str(e)}") final_text = "\n".join(stats_text_parts) diff --git a/bot/handlers/admin/user_management.py b/bot/handlers/admin/user_management.py index 21032f5..65daa9d 100644 --- a/bot/handlers/admin/user_management.py +++ b/bot/handlers/admin/user_management.py @@ -363,8 +363,10 @@ async def handle_toggle_ban(callback: types.CallbackQuery, user: User, # Refresh user card with updated ban status user.is_banned = new_ban_status # Update local object from config.settings import Settings + from bot.services.panel_api_service import PanelApiService settings = Settings() - subscription_service = SubscriptionService(settings) + panel_service = PanelApiService(settings) + subscription_service = SubscriptionService(settings, panel_service) await handle_refresh_user_card(callback, user, subscription_service, session, i18n_instance, lang) except Exception as e: @@ -628,7 +630,9 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte )) # Show user card again - subscription_service = SubscriptionService(settings) + from bot.services.panel_api_service import PanelApiService + panel_service = PanelApiService(settings) + subscription_service = SubscriptionService(settings, panel_service) user_card_text = await format_user_card(target_user, session, subscription_service, i18n, current_lang) keyboard = get_user_card_keyboard(target_user.user_id, i18n, current_lang) diff --git a/bot/states/admin_states.py b/bot/states/admin_states.py index 21ee390..3b8af20 100644 --- a/bot/states/admin_states.py +++ b/bot/states/admin_states.py @@ -10,9 +10,7 @@ class AdminStates(StatesGroup): waiting_for_promo_bonus_days = State() waiting_for_promo_max_activations = State() waiting_for_promo_validity_days = State() - waiting_for_promo_description = State() waiting_for_promo_edit_details = State() - waiting_for_bulk_promo_details = State() waiting_for_bulk_promo_quantity = State() waiting_for_bulk_promo_bonus_days = State() waiting_for_bulk_promo_max_activations = State() diff --git a/locales/en.json b/locales/en.json index f02392b..0705c2d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -344,11 +344,10 @@ "admin_bulk_promo_creating": "⏳ Creating {quantity} promo codes...", - "admin_promo_step1_code": "🎟 Create Promo Code\n\nStep 1 of 5: Promo Code\n\nEnter promo code (3-30 characters, letters and numbers only):", - "admin_promo_step2_bonus_days": "🎟 Create Promo Code\n\nStep 2 of 5: Bonus Days\n\nCode: {code}\n\nEnter the number of bonus days (1-365):", - "admin_promo_step3_max_activations": "🎟 Create Promo Code\n\nStep 3 of 5: Max Activations\n\nCode: {code}\nBonus days: {bonus_days}\n\nEnter the maximum number of activations (1-10000):", - "admin_promo_step4_validity": "🎟 Create Promo Code\n\nStep 4 of 5: Validity Period\n\nCode: {code}\nBonus days: {bonus_days}\nMax activations: {max_activations}\n\nChoose the validity period for the promo code:", - "admin_promo_step5_description": "🎟 Create Promo Code\n\nStep 5 of 5: Description (optional)\n\nCode: {code}\nBonus days: {bonus_days}\nMax activations: {max_activations}\nValid until: {validity}\n\nEnter promo code description or click \"Skip\":", + "admin_promo_step1_code": "🎟 Create Promo Code\n\nStep 1 of 4: Promo Code\n\nEnter promo code (3-30 characters, letters and numbers only):", + "admin_promo_step2_bonus_days": "🎟 Create Promo Code\n\nStep 2 of 4: Bonus Days\n\nCode: {code}\n\nEnter the number of bonus days (1-365):", + "admin_promo_step3_max_activations": "🎟 Create Promo Code\n\nStep 3 of 4: Max Activations\n\nCode: {code}\nBonus days: {bonus_days}\n\nEnter the maximum number of activations (1-10000):", + "admin_promo_step4_validity": "🎟 Create Promo Code\n\nStep 4 of 4: Validity Period\n\nCode: {code}\nBonus days: {bonus_days}\nMax activations: {max_activations}\n\nChoose the validity period for the promo code:", "admin_promo_invalid_code_format": "❌ Promo code must contain 3-30 characters (letters and numbers only)", "admin_promo_code_already_exists": "❌ A promo code with this code already exists", @@ -359,6 +358,9 @@ "admin_promo_skip_description": "⏭️ Skip", "admin_promo_description_too_long": "❌ Description must not exceed 200 characters", "admin_promo_creating": "⏳ Creating promo code...", - "admin_promo_created_success": "βœ… Promo code created!\n\n🎟 Code: {code}\n🎁 Bonus days: {days}\nπŸ”’ Max activations: {max_act}\n⏰ Valid until: {validity}\nπŸ“ Description: {description}", - "admin_promo_creation_failed": "❌ Error creating promo code: {error}" + "admin_promo_created_success": "βœ… Promo code created!\n\n🎟 Code: {code}\n🎁 Bonus days: {days}\nπŸ”’ Max activations: {max_act}\n⏰ Valid until: {validity}", + "admin_promo_creation_failed": "❌ Error creating promo code: {error}", + + "admin_panel_back_button": "⬅️ Back", + "admin_bulk_promo_creation_failed": "❌ Error creating promo codes: {error}" } diff --git a/locales/ru.json b/locales/ru.json index a0c6bbf..15211e8 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -344,11 +344,10 @@ "admin_bulk_promo_creating": "⏳ Боздаю {quantity} ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²...", - "admin_promo_step1_code": "🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 1 ΠΈΠ· 5: Код ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΠ΄ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° (3-30 символов, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π±ΡƒΠΊΠ²Ρ‹ ΠΈ Ρ†ΠΈΡ„Ρ€Ρ‹):", - "admin_promo_step2_bonus_days": "🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 2 ΠΈΠ· 5: БонусныС Π΄Π½ΠΈ\n\nКод: {code}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство бонусных Π΄Π½Π΅ΠΉ (1-365):", - "admin_promo_step3_max_activations": "🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 3 ΠΈΠ· 5: ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ максимальноС количСство Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΉ (1-10000):", - "admin_promo_step4_validity": "🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 4 ΠΈΠ· 5: Π‘Ρ€ΠΎΠΊ дСйствия\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\nМакс. Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_activations}\n\nΠ’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ срок дСйствия ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°:", - "admin_promo_step5_description": "🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 5 ΠΈΠ· 5: ОписаниС (ΠΎΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½ΠΎ)\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\nМакс. Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_activations}\nДСйствуСт: {validity}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ описаниС ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° ΠΈΠ»ΠΈ Π½Π°ΠΆΠΌΠΈΡ‚Π΅ \"ΠŸΡ€ΠΎΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ\":", + "admin_promo_step1_code": "🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 1 ΠΈΠ· 4: Код ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ ΠΊΠΎΠ΄ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° (3-30 символов, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π±ΡƒΠΊΠ²Ρ‹ ΠΈ Ρ†ΠΈΡ„Ρ€Ρ‹):", + "admin_promo_step2_bonus_days": "🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 2 ΠΈΠ· 4: БонусныС Π΄Π½ΠΈ\n\nКод: {code}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ количСство бонусных Π΄Π½Π΅ΠΉ (1-365):", + "admin_promo_step3_max_activations": "🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 3 ΠΈΠ· 4: ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\n\nΠ’Π²Π΅Π΄ΠΈΡ‚Π΅ максимальноС количСство Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΉ (1-10000):", + "admin_promo_step4_validity": "🎟 Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°\n\nΠ¨Π°Π³ 4 ΠΈΠ· 4: Π‘Ρ€ΠΎΠΊ дСйствия\n\nКод: {code}\nБонусныС Π΄Π½ΠΈ: {bonus_days}\nМакс. Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_activations}\n\nΠ’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ срок дСйствия ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°:", "admin_promo_invalid_code_format": "❌ Код ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π° Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ 3-30 символов (Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π±ΡƒΠΊΠ²Ρ‹ ΠΈ Ρ†ΠΈΡ„Ρ€Ρ‹)", "admin_promo_code_already_exists": "❌ ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ с Ρ‚Π°ΠΊΠΈΠΌ ΠΊΠΎΠ΄ΠΎΠΌ ΡƒΠΆΠ΅ сущСствуСт", @@ -359,6 +358,9 @@ "admin_promo_skip_description": "⏭️ ΠŸΡ€ΠΎΠΏΡƒΡΡ‚ΠΈΡ‚ΡŒ", "admin_promo_description_too_long": "❌ ОписаниС Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΏΡ€Π΅Π²Ρ‹ΡˆΠ°Ρ‚ΡŒ 200 символов", "admin_promo_creating": "⏳ Боздаю ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄...", - "admin_promo_created_success": "βœ… ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ создан!\n\n🎟 Код: {code}\n🎁 БонусныС Π΄Π½ΠΈ: {days}\nπŸ”’ ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_act}\n⏰ ДСйствуСт Π΄ΠΎ: {validity}\nπŸ“ ОписаниС: {description}", - "admin_promo_creation_failed": "❌ Ошибка создания ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°: {error}" + "admin_promo_created_success": "βœ… ΠŸΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ создан!\n\n🎟 Код: {code}\n🎁 БонусныС Π΄Π½ΠΈ: {days}\nπŸ”’ ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ: {max_act}\n⏰ ДСйствуСт Π΄ΠΎ: {validity}", + "admin_promo_creation_failed": "❌ Ошибка создания ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄Π°: {error}", + + "admin_panel_back_button": "⬅️ Назад", + "admin_bulk_promo_creation_failed": "❌ Ошибка создания ΠΏΡ€ΠΎΠΌΠΎΠΊΠΎΠ΄ΠΎΠ²: {error}" }