Code Refactoring

This commit is contained in:
machka
2025-05-14 08:43:23 +00:00
parent 7f94d794d6
commit d406baf0c1
33 changed files with 6015 additions and 3571 deletions
+8 -12
View File
@@ -1,6 +1,5 @@
from aiogram import Router
from .common import router as common_admin_router
from .promo_codes import router as promo_codes_admin_router
from .user_management import router as user_management_admin_router
@@ -9,15 +8,12 @@ from .statistics import router as statistics_admin_router
from .sync_admin import router as sync_admin_router
from .logs_admin import router as logs_admin_router
admin_router_aggregate = Router(name="admin_router_aggregate")
admin_router_aggregate =Router (name ="admin_router_aggregate")
admin_router_aggregate .include_router (common_admin_router )
admin_router_aggregate .include_router (promo_codes_admin_router )
admin_router_aggregate .include_router (user_management_admin_router )
admin_router_aggregate .include_router (broadcast_admin_router )
admin_router_aggregate .include_router (statistics_admin_router )
admin_router_aggregate .include_router (sync_admin_router )
admin_router_aggregate .include_router (logs_admin_router )
admin_router_aggregate.include_router(common_admin_router)
admin_router_aggregate.include_router(promo_codes_admin_router)
admin_router_aggregate.include_router(user_management_admin_router)
admin_router_aggregate.include_router(broadcast_admin_router)
admin_router_aggregate.include_router(statistics_admin_router)
admin_router_aggregate.include_router(sync_admin_router)
admin_router_aggregate.include_router(logs_admin_router)
+167 -135
View File
@@ -1,172 +1,204 @@
import logging
import asyncio
from aiogram import Router ,F ,types ,Bot
from aiogram .filters import Command
from aiogram .fsm .context import FSMContext
from aiogram import Router, F, types, Bot
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from typing import Optional
from config .settings import Settings
from config.settings import Settings
from db .database import get_all_users_for_broadcast ,log_user_action
from bot .states .admin_states import AdminStates
from bot .keyboards .inline .admin_keyboards import get_broadcast_confirmation_keyboard ,get_back_to_admin_panel_keyboard
from bot .middlewares .i18n import JsonI18n
from db.database import get_all_users_for_broadcast, log_user_action
from bot.states.admin_states import AdminStates
from bot.keyboards.inline.admin_keyboards import get_broadcast_confirmation_keyboard, get_back_to_admin_panel_keyboard
from bot.middlewares.i18n import JsonI18n
router =Router (name ="admin_broadcast_router")
router = Router(name="admin_broadcast_router")
async def broadcast_message_prompt_handler (
callback :types .CallbackQuery ,
state :FSMContext ,
i18n_data :dict ,
settings :Settings
):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :
logging .error ("i18n missing in broadcast_message_prompt_handler")
await callback .answer ("Language service error.",show_alert =True )
async def broadcast_message_prompt_handler(callback: types.CallbackQuery,
state: FSMContext, i18n_data: dict,
settings: Settings):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing in broadcast_message_prompt_handler")
await callback.answer("Language service error.", show_alert=True)
return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
prompt_text =_ ("admin_broadcast_enter_message")
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
prompt_text = _("admin_broadcast_enter_message")
if callback .message :
try :
await callback .message .edit_text (prompt_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
except Exception as e :
logging .warning (f"Could not edit message for broadcast prompt: {e}")
await callback .message .answer (prompt_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
await callback .answer ()
await state .set_state (AdminStates .waiting_for_broadcast_message )
if callback.message:
try:
await callback.message.edit_text(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
except Exception as e:
logging.warning(
f"Could not edit message for broadcast prompt: {e}")
await callback.message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
await callback.answer()
await state.set_state(AdminStates.waiting_for_broadcast_message)
@router .message (AdminStates .waiting_for_broadcast_message ,F .text )
async def process_broadcast_message_handler (
message :types .Message ,
state :FSMContext ,
i18n_data :dict ,
settings :Settings
):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :
logging .error ("i18n missing in process_broadcast_message_handler")
await message .reply ("Language service error.")
@router.message(AdminStates.waiting_for_broadcast_message, F.text)
async def process_broadcast_message_handler(message: types.Message,
state: FSMContext, i18n_data: dict,
settings: Settings):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing in process_broadcast_message_handler")
await message.reply("Language service error.")
return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
broadcast_message_text =message .html_text
await state .update_data (broadcast_message =broadcast_message_text )
broadcast_message_text = message.html_text
await state.update_data(broadcast_message=broadcast_message_text)
preview_snippet =broadcast_message_text [:200 ]+"..."if len (broadcast_message_text )>200 else broadcast_message_text
confirmation_prompt =_ ("admin_broadcast_confirm_prompt",message_preview =preview_snippet )
preview_snippet = broadcast_message_text[:200] + "..." if len(
broadcast_message_text) > 200 else broadcast_message_text
confirmation_prompt = _("admin_broadcast_confirm_prompt",
message_preview=preview_snippet)
await message .answer (
confirmation_prompt ,
reply_markup =get_broadcast_confirmation_keyboard (current_lang ,i18n ),
parse_mode ="HTML"
)
await state .set_state (AdminStates .confirming_broadcast )
await message.answer(confirmation_prompt,
reply_markup=get_broadcast_confirmation_keyboard(
current_lang, i18n),
parse_mode="HTML")
await state.set_state(AdminStates.confirming_broadcast)
@router .callback_query (F .data =="admin_action:main",AdminStates .waiting_for_broadcast_message )
async def cancel_broadcast_at_prompt_stage (callback :types .CallbackQuery ,state :FSMContext ,settings :Settings ,i18n_data :dict ):
@router.callback_query(F.data == "admin_action:main",
AdminStates.waiting_for_broadcast_message)
async def cancel_broadcast_at_prompt_stage(callback: types.CallbackQuery,
state: FSMContext,
settings: Settings,
i18n_data: dict):
from .common import admin_panel_actions_callback_handler
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :logging .error ("i18n missing");await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing")
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
if callback .message :
try :await callback .message .edit_text (_ ("admin_broadcast_cancelled"),reply_markup =None )
except Exception :await callback .message .answer (_ ("admin_broadcast_cancelled"))
await callback .answer (_ ("admin_broadcast_cancelled"))
await state .clear ()
if callback.message:
try:
await callback.message.edit_text(_("admin_broadcast_cancelled"),
reply_markup=None)
except Exception:
await callback.message.answer(_("admin_broadcast_cancelled"))
await callback.answer(_("admin_broadcast_cancelled"))
await state.clear()
callback.data = "admin_action:main"
from bot.keyboards.inline.admin_keyboards import get_admin_panel_keyboard
if callback.message:
await callback.message.answer(_("admin_panel_title"),
reply_markup=get_admin_panel_keyboard(
i18n, current_lang))
callback .data ="admin_action:main"
@router.callback_query(F.data.startswith("broadcast_final_action:"),
AdminStates.confirming_broadcast)
async def confirm_broadcast_callback_handler(callback: types.CallbackQuery,
state: FSMContext,
i18n_data: dict, bot: Bot,
settings: Settings):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing")
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
action = callback.data.split(":")[1]
user_fsm_data = await state.get_data()
broadcast_message = user_fsm_data.get("broadcast_message")
if not callback.message:
await callback.answer("Error: message context lost.", show_alert=True)
await state.clear()
return
if action == "send":
if not broadcast_message:
await callback.message.edit_text(
_("admin_broadcast_error_no_message"))
await state.clear()
await callback.answer(show_alert=True)
return
from bot .keyboards .inline .admin_keyboards import get_admin_panel_keyboard
if callback .message :
await callback .message .answer (_ ("admin_panel_title"),reply_markup =get_admin_panel_keyboard (i18n ,current_lang ))
await callback.message.edit_text(_("admin_broadcast_sending_started"),
reply_markup=None)
await callback.answer()
users_to_broadcast = await get_all_users_for_broadcast()
sent_count = 0
failed_count = 0
logging.info(
f"Starting broadcast: '{broadcast_message[:50]}...' to {len(users_to_broadcast)} users."
)
@router .callback_query (F .data .startswith ("broadcast_final_action:"),AdminStates .confirming_broadcast )
async def confirm_broadcast_callback_handler (
callback :types .CallbackQuery ,
state :FSMContext ,
i18n_data :dict ,
bot :Bot ,
settings :Settings
):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :logging .error ("i18n missing");await callback .answer ("Language error.",show_alert =True );return
admin_user = callback.from_user
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
for user_row in users_to_broadcast:
user_id = user_row['user_id']
try:
await bot.send_message(user_id,
broadcast_message,
parse_mode="HTML")
sent_count += 1
action =callback .data .split (":")[1 ]
user_fsm_data =await state .get_data ()
broadcast_message =user_fsm_data .get ("broadcast_message")
if not callback .message :
await callback .answer ("Error: message context lost.",show_alert =True )
await state .clear ();return
if action =="send":
if not broadcast_message :
await callback .message .edit_text (_ ("admin_broadcast_error_no_message"))
await state .clear ();await callback .answer (show_alert =True );return
await callback .message .edit_text (_ ("admin_broadcast_sending_started"),reply_markup =None )
await callback .answer ()
users_to_broadcast =await get_all_users_for_broadcast ()
sent_count =0 ;failed_count =0
logging .info (f"Starting broadcast: '{broadcast_message[:50]}...' to {len(users_to_broadcast)} users.")
admin_user =callback .from_user
for user_row in users_to_broadcast :
user_id =user_row ['user_id']
try :
await bot .send_message (user_id ,broadcast_message ,parse_mode ="HTML")
sent_count +=1
await log_user_action (
user_id =admin_user .id ,
telegram_username =admin_user .username ,
telegram_first_name =admin_user .first_name ,
event_type ="admin_broadcast_sent",
content =f"To user {user_id}: {broadcast_message[:70]}...",
is_admin_event =True ,
target_user_id =user_id
await log_user_action(
user_id=admin_user.id,
telegram_username=admin_user.username,
telegram_first_name=admin_user.first_name,
event_type="admin_broadcast_sent",
content=f"To user {user_id}: {broadcast_message[:70]}...",
is_admin_event=True,
target_user_id=user_id)
except Exception as e:
failed_count += 1
logging.warning(
f"Failed to send broadcast to user {user_id}: {type(e).__name__} - {e}"
)
except Exception as e :
failed_count +=1
logging .warning (f"Failed to send broadcast to user {user_id}: {type(e).__name__} - {e}")
await log_user_action (
user_id =admin_user .id ,
telegram_username =admin_user .username ,
telegram_first_name =admin_user .first_name ,
event_type ="admin_broadcast_failed",
content =f"For user {user_id}: {type(e).__name__} - {str(e)[:70]}...",
is_admin_event =True ,
target_user_id =user_id
)
await asyncio .sleep (0.1 )
await log_user_action(
user_id=admin_user.id,
telegram_username=admin_user.username,
telegram_first_name=admin_user.first_name,
event_type="admin_broadcast_failed",
content=
f"For user {user_id}: {type(e).__name__} - {str(e)[:70]}...",
is_admin_event=True,
target_user_id=user_id)
await asyncio.sleep(0.1)
result_message =_ ("admin_broadcast_finished_stats",sent_count =sent_count ,failed_count =failed_count )
await callback .message .answer (result_message ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
result_message = _("admin_broadcast_finished_stats",
sent_count=sent_count,
failed_count=failed_count)
await callback.message.answer(
result_message,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n))
elif action =="cancel":
await callback .message .edit_text (_ ("admin_broadcast_cancelled"),reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
await callback .answer ()
elif action == "cancel":
await callback.message.edit_text(
_("admin_broadcast_cancelled"),
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n))
await callback.answer()
await state .clear ()
await state.clear()
+100 -82
View File
@@ -1,96 +1,114 @@
import logging
from aiogram import Router ,F ,types ,Bot
from aiogram .filters import Command
from aiogram .fsm .context import FSMContext
from aiogram import Router, F, types, Bot
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from typing import Optional
from config .settings import Settings
from bot .keyboards .inline .admin_keyboards import get_admin_panel_keyboard
from bot .middlewares .i18n import JsonI18n
from bot .services .panel_api_service import PanelApiService
from config.settings import Settings
from bot.keyboards.inline.admin_keyboards import get_admin_panel_keyboard
from bot.middlewares.i18n import JsonI18n
from bot.services.panel_api_service import PanelApiService
from . import broadcast as admin_broadcast_handlers
from . import promo_codes as admin_promo_handlers
from . import user_management as admin_user_mgmnt_handlers
from . import statistics as admin_stats_handlers
from . import sync_admin as admin_sync_handlers
from . import logs_admin as admin_logs_handlers
router = Router(name="admin_common_router")
from .import broadcast as admin_broadcast_handlers
from .import promo_codes as admin_promo_handlers
from .import user_management as admin_user_mgmnt_handlers
from .import statistics as admin_stats_handlers
from .import sync_admin as admin_sync_handlers
from .import logs_admin as admin_logs_handlers
router =Router (name ="admin_common_router")
@router .message (Command ("admin"))
async def admin_panel_command_handler (message :types .Message ,settings :Settings ,i18n_data :dict ):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :
logging .error ("i18n missing in admin_panel_command_handler")
await message .answer ("Language service error.")
@router.message(Command("admin"))
async def admin_panel_command_handler(message: types.Message,
settings: Settings, i18n_data: dict):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing in admin_panel_command_handler")
await message.answer("Language service error.")
return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
await message .answer (
_ (key ="admin_panel_title"),
reply_markup =get_admin_panel_keyboard (i18n ,current_lang ,settings )
)
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
await message.answer(_(key="admin_panel_title"),
reply_markup=get_admin_panel_keyboard(
i18n, current_lang, settings))
@router .callback_query (F .data .startswith ("admin_action:"))
async def admin_panel_actions_callback_handler (
callback :types .CallbackQuery ,
state :FSMContext ,
settings :Settings ,
i18n_data :dict ,
bot :Bot ,
panel_service :PanelApiService
):
action_parts =callback .data .split (":")
action =action_parts [1 ]
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :
logging .error ("i18n missing in admin_panel_actions_callback_handler")
await callback .answer ("Language error.",show_alert =True )
@router.callback_query(F.data.startswith("admin_action:"))
async def admin_panel_actions_callback_handler(callback: types.CallbackQuery,
state: FSMContext,
settings: Settings,
i18n_data: dict, bot: Bot,
panel_service: PanelApiService):
action_parts = callback.data.split(":")
action = action_parts[1]
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing in admin_panel_actions_callback_handler")
await callback.answer("Language error.", show_alert=True)
return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
if not callback .message :
logging .error (f"CallbackQuery {callback.id} from {callback.from_user.id} has no message for action {action}")
await callback .answer ("Error processing action: message context lost.",show_alert =True )
if not callback.message:
logging.error(
f"CallbackQuery {callback.id} from {callback.from_user.id} has no message for action {action}"
)
await callback.answer("Error processing action: message context lost.",
show_alert=True)
return
if action =="stats":
await admin_stats_handlers .show_statistics_handler (callback ,i18n_data ,settings )
elif action =="broadcast":
await admin_broadcast_handlers .broadcast_message_prompt_handler (callback ,state ,i18n_data ,settings )
elif action =="create_promo":
await admin_promo_handlers .create_promo_prompt_handler (callback ,state ,i18n_data ,settings )
elif action =="view_promos":
await admin_promo_handlers .view_promo_codes_handler (callback ,i18n_data ,settings )
elif action =="ban_user_prompt":
await admin_user_mgmnt_handlers .ban_user_prompt_handler (callback ,state ,i18n_data ,settings )
elif action =="unban_user_prompt":
await admin_user_mgmnt_handlers .unban_user_prompt_handler (callback ,state ,i18n_data ,settings )
elif action =="view_banned":
if action == "stats":
await admin_stats_handlers.show_statistics_handler(
callback, i18n_data, settings)
elif action == "broadcast":
await admin_broadcast_handlers.broadcast_message_prompt_handler(
callback, state, i18n_data, settings)
elif action == "create_promo":
await admin_promo_handlers.create_promo_prompt_handler(
callback, state, i18n_data, settings)
elif action == "view_promos":
await admin_promo_handlers.view_promo_codes_handler(
callback, i18n_data, settings)
elif action == "ban_user_prompt":
await admin_user_mgmnt_handlers.ban_user_prompt_handler(
callback, state, i18n_data, settings)
elif action == "unban_user_prompt":
await admin_user_mgmnt_handlers.unban_user_prompt_handler(
callback, state, i18n_data, settings)
elif action == "view_banned":
await admin_user_mgmnt_handlers .view_banned_users_handler (callback ,i18n_data ,settings ,state )
elif action =="view_logs_menu":
await admin_logs_handlers .display_logs_menu (callback ,i18n_data ,settings )
elif action =="sync_panel":
await admin_sync_handlers .sync_command_handler (callback .message ,bot =bot ,settings =settings ,i18n_data =i18n_data ,panel_service =panel_service )
await callback .answer (_ ("admin_sync_initiated_from_panel"))
elif action =="main":
try :
await callback .message .edit_text (
_ (key ="admin_panel_title"),
reply_markup =get_admin_panel_keyboard (i18n ,current_lang ,settings )
)
except Exception :
await callback .message .answer (
_ (key ="admin_panel_title"),
reply_markup =get_admin_panel_keyboard (i18n ,current_lang ,settings )
)
await callback .answer ()
else :
logging .warning (f"Unknown admin_action received: {action} from callback {callback.data}")
await callback .answer (_ ("admin_unknown_action"),show_alert =True )
await admin_user_mgmnt_handlers.view_banned_users_handler(
callback, i18n_data, settings, state)
elif action == "view_logs_menu":
await admin_logs_handlers.display_logs_menu(callback, i18n_data,
settings)
elif action == "sync_panel":
await admin_sync_handlers.sync_command_handler(
callback.message,
bot=bot,
settings=settings,
i18n_data=i18n_data,
panel_service=panel_service)
await callback.answer(_("admin_sync_initiated_from_panel"))
elif action == "main":
try:
await callback.message.edit_text(
_(key="admin_panel_title"),
reply_markup=get_admin_panel_keyboard(i18n, current_lang,
settings))
except Exception:
await callback.message.answer(
_(key="admin_panel_title"),
reply_markup=get_admin_panel_keyboard(i18n, current_lang,
settings))
await callback.answer()
else:
logging.warning(
f"Unknown admin_action received: {action} from callback {callback.data}"
)
await callback.answer(_("admin_unknown_action"), show_alert=True)
+286 -198
View File
@@ -1,233 +1,321 @@
import logging
import math
import re
from aiogram import Router ,F ,types ,Bot
from aiogram .fsm .context import FSMContext
from typing import Optional ,List ,Dict ,Any
from aiogram import Router, F, types, Bot
from aiogram.fsm.context import FSMContext
from typing import Optional, List, Dict, Any
import aiosqlite
from config .settings import Settings
from db .database import (
get_all_message_logs_paginated ,count_all_message_logs ,
get_user_message_logs_paginated ,count_user_message_logs ,
get_user ,get_user_by_telegram_username
)
from bot .states .admin_states import AdminStates
from bot .keyboards .inline .admin_keyboards import (
get_logs_menu_keyboard ,
get_logs_pagination_keyboard ,
get_back_to_admin_panel_keyboard ,
get_admin_panel_keyboard
)
from bot .middlewares .i18n import JsonI18n
from config.settings import Settings
from db.database import (get_all_message_logs_paginated,
count_all_message_logs,
get_user_message_logs_paginated,
count_user_message_logs, get_user,
get_user_by_telegram_username)
from bot.states.admin_states import AdminStates
from bot.keyboards.inline.admin_keyboards import (
get_logs_menu_keyboard, get_logs_pagination_keyboard,
get_back_to_admin_panel_keyboard, get_admin_panel_keyboard)
from bot.middlewares.i18n import JsonI18n
router =Router (name ="admin_logs_router")
USERNAME_REGEX =re .compile (r"^[a-zA-Z0-9_]{5,32}$")
router = Router(name="admin_logs_router")
USERNAME_REGEX = re.compile(r"^[a-zA-Z0-9_]{5,32}$")
async def display_logs_menu (callback :types .CallbackQuery ,i18n_data :dict ,settings :Settings ):
current_lang =i18n_data .get ("current_language",settings .DEFAULT_LANGUAGE )
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :
logging .error ("i18n_instance missing in display_logs_menu")
await callback .answer ("Language service error.",show_alert =True )
async def display_logs_menu(callback: types.CallbackQuery, i18n_data: dict,
settings: Settings):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n_instance missing in display_logs_menu")
await callback.answer("Language service error.", show_alert=True)
return
if not callback .message :
logging .error ("CallbackQuery has no message in display_logs_menu")
await callback .answer ("Error processing request.",show_alert =True )
if not callback.message:
logging.error("CallbackQuery has no message in display_logs_menu")
await callback.answer("Error processing request.", show_alert=True)
return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
try :
await callback .message .edit_text (
text =_ (key ="admin_logs_menu_title"),
reply_markup =get_logs_menu_keyboard (i18n ,current_lang )
try:
await callback.message.edit_text(text=_(key="admin_logs_menu_title"),
reply_markup=get_logs_menu_keyboard(
i18n, current_lang))
except Exception as e:
logging.warning(f"Failed to edit message for logs menu: {e}")
await callback.message.answer(text=_(key="admin_logs_menu_title"),
reply_markup=get_logs_menu_keyboard(
i18n, current_lang))
await callback.answer()
async def _display_formatted_logs(target_message: types.Message,
logs: List[aiosqlite.Row],
total_logs: int,
current_page: int,
settings: Settings,
title_key: str,
base_pagination_callback_data: str,
i18n: JsonI18n,
current_lang: str,
title_kwargs: Optional[Dict[str,
Any]] = None):
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
page_size = settings.LOGS_PAGE_SIZE
actual_title_kwargs = title_kwargs or {}
if not logs and total_logs == 0:
text = _(
title_key, current_page=1, total_pages=1, **
actual_title_kwargs) + "\n\n" + _("admin_no_logs_found")
reply_markup = get_logs_pagination_keyboard(
current_page,
1,
base_pagination_callback_data,
i18n,
current_lang,
back_to_logs_menu=True)
else:
total_pages = math.ceil(total_logs / page_size) if page_size > 0 else 1
text = _(title_key,
current_page=current_page + 1,
total_pages=max(1, total_pages),
**actual_title_kwargs) + "\n"
log_entries_text = []
for log_entry in logs:
user_display_parts = []
telegram_first_name = log_entry[
'telegram_first_name'] if 'telegram_first_name' in log_entry.keys(
) and log_entry['telegram_first_name'] else None
telegram_username = log_entry[
'telegram_username'] if 'telegram_username' in log_entry.keys(
) and log_entry['telegram_username'] else None
user_id_from_log = log_entry[
'user_id'] if 'user_id' in log_entry.keys(
) and log_entry['user_id'] else None
if telegram_first_name:
user_display_parts.append(telegram_first_name)
if telegram_username:
user_display_parts.append(f"(@{telegram_username})")
user_display = " ".join(user_display_parts).strip()
if not user_display:
user_display = _(
"system_or_unknown_user"
) if not user_id_from_log else f"ID: {user_id_from_log}"
user_id_display = str(
user_id_from_log) if user_id_from_log is not None else "N/A"
content_raw = log_entry['content'] if 'content' in log_entry.keys(
) and log_entry['content'] else ""
content_preview = (content_raw[:100] +
"...") if len(content_raw) > 100 else (
content_raw or "N/A")
log_entries_text.append(
_("admin_log_entry_format",
timestamp_str=log_entry['timestamp_str']
if 'timestamp_str' in log_entry.keys() else 'N/A',
user_display=user_display,
user_id=user_id_display,
event_type=log_entry['event_type']
if 'event_type' in log_entry.keys() else 'N/A',
content_preview=content_preview).replace("\n", "\n "))
text += "\n\n".join(log_entries_text)
reply_markup = get_logs_pagination_keyboard(
current_page,
total_pages,
base_pagination_callback_data,
i18n,
current_lang,
back_to_logs_menu=True)
try:
await target_message.edit_text(text,
reply_markup=reply_markup,
parse_mode="HTML",
disable_web_page_preview=True)
except Exception as e:
logging.warning(
f"Failed to edit message for logs display: {e}. Content length: {len(text)}"
)
except Exception as e :
logging .warning (f"Failed to edit message for logs menu: {e}")
await callback .message .answer (
text =_ (key ="admin_logs_menu_title"),
reply_markup =get_logs_menu_keyboard (i18n ,current_lang )
)
await callback .answer ()
chunk_size = 4000
for i in range(0, len(text), chunk_size):
chunk = text[i:i + chunk_size]
is_last_chunk = (i + chunk_size) >= len(text)
await target_message.answer(
chunk,
reply_markup=reply_markup if is_last_chunk else None,
parse_mode="HTML",
disable_web_page_preview=True)
async def _display_formatted_logs (
target_message :types .Message ,
logs :List [aiosqlite .Row ],
total_logs :int ,
current_page :int ,
settings :Settings ,
title_key :str ,
base_pagination_callback_data :str ,
i18n :JsonI18n ,
current_lang :str ,
title_kwargs :Optional [Dict [str ,Any ]]=None
):
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
page_size =settings .LOGS_PAGE_SIZE
actual_title_kwargs =title_kwargs or {}
@router.callback_query(F.data.startswith("admin_logs:view_all"))
async def view_all_logs_handler(callback: types.CallbackQuery,
settings: Settings, i18n_data: dict):
page = 0
parts = callback.data.split(":")
if not logs and total_logs ==0 :
text =_ (title_key ,current_page =1 ,total_pages =1 ,**actual_title_kwargs )+"\n\n"+_ ("admin_no_logs_found")
reply_markup =get_logs_pagination_keyboard (current_page ,1 ,base_pagination_callback_data ,i18n ,current_lang ,back_to_logs_menu =True )
else :
total_pages =math .ceil (total_logs /page_size )if page_size >0 else 1
text =_ (title_key ,current_page =current_page +1 ,total_pages =max (1 ,total_pages ),**actual_title_kwargs )+"\n"
if len(parts) == 3:
try:
page = int(parts[2])
except ValueError:
page = 0
log_entries_text =[]
for log_entry in logs :
user_display_parts =[]
telegram_first_name =log_entry ['telegram_first_name']if 'telegram_first_name'in log_entry .keys ()and log_entry ['telegram_first_name']else None
telegram_username =log_entry ['telegram_username']if 'telegram_username'in log_entry .keys ()and log_entry ['telegram_username']else None
user_id_from_log =log_entry ['user_id']if 'user_id'in log_entry .keys ()and log_entry ['user_id']else None
if telegram_first_name :user_display_parts .append (telegram_first_name )
if telegram_username :user_display_parts .append (f"(@{telegram_username})")
user_display =" ".join (user_display_parts ).strip ()
if not user_display :
user_display =_ ("system_or_unknown_user")if not user_id_from_log else f"ID: {user_id_from_log}"
user_id_display =str (user_id_from_log )if user_id_from_log is not None else "N/A"
content_raw =log_entry ['content']if 'content'in log_entry .keys ()and log_entry ['content']else ""
content_preview =(content_raw [:100 ]+"...")if len (content_raw )>100 else (content_raw or "N/A")
log_entries_text .append (
_ ("admin_log_entry_format",
timestamp_str =log_entry ['timestamp_str']if 'timestamp_str'in log_entry .keys ()else 'N/A',
user_display =user_display ,
user_id =user_id_display ,
event_type =log_entry ['event_type']if 'event_type'in log_entry .keys ()else 'N/A',
content_preview =content_preview
).replace ("\n","\n ")
)
text +="\n\n".join (log_entries_text )
reply_markup =get_logs_pagination_keyboard (current_page ,total_pages ,base_pagination_callback_data ,i18n ,current_lang ,back_to_logs_menu =True )
try :
await target_message .edit_text (text ,reply_markup =reply_markup ,parse_mode ="HTML",disable_web_page_preview =True )
except Exception as e :
logging .warning (f"Failed to edit message for logs display: {e}. Content length: {len(text)}")
chunk_size =4000
for i in range (0 ,len (text ),chunk_size ):
chunk =text [i :i +chunk_size ];is_last_chunk =(i +chunk_size )>=len (text )
await target_message .answer (chunk ,reply_markup =reply_markup if is_last_chunk else None ,parse_mode ="HTML",disable_web_page_preview =True )
@router .callback_query (F .data .startswith ("admin_logs:view_all"))
async def view_all_logs_handler (callback :types .CallbackQuery ,settings :Settings ,i18n_data :dict ):
page =0
parts =callback .data .split (":")
if len (parts )==3 :
try :
page =int (parts [2 ])
except ValueError :
page =0
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
current_lang =i18n_data .get ("current_language",settings .DEFAULT_LANGUAGE )
if not i18n or not callback .message :
await callback .answer ("Error processing request.",show_alert =True )
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
if not i18n or not callback.message:
await callback.answer("Error processing request.", show_alert=True)
return
logs ,total_logs =await get_all_message_logs_paginated (settings .LOGS_PAGE_SIZE ,page *settings .LOGS_PAGE_SIZE ),await count_all_message_logs ()
logs, total_logs = await get_all_message_logs_paginated(
settings.LOGS_PAGE_SIZE,
page * settings.LOGS_PAGE_SIZE), await count_all_message_logs()
await _display_formatted_logs (
target_message =callback .message ,logs =logs ,total_logs =total_logs ,
current_page =page ,settings =settings ,
title_key ="admin_all_logs_title",base_pagination_callback_data ="admin_logs:view_all",
i18n =i18n ,current_lang =current_lang
await _display_formatted_logs(
target_message=callback.message,
logs=logs,
total_logs=total_logs,
current_page=page,
settings=settings,
title_key="admin_all_logs_title",
base_pagination_callback_data="admin_logs:view_all",
i18n=i18n,
current_lang=current_lang)
await callback.answer()
@router.callback_query(F.data == "admin_logs:prompt_user")
async def prompt_user_for_logs_handler(callback: types.CallbackQuery,
state: FSMContext, i18n_data: dict,
settings: Settings):
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
if not i18n or not callback.message:
await callback.answer("Error")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
await callback.message.edit_text(
text=_("admin_prompt_for_user_id_or_username_logs"),
reply_markup=get_logs_menu_keyboard(i18n, current_lang))
await state.set_state(AdminStates.waiting_for_user_id_for_logs)
await callback.answer()
@router.message(AdminStates.waiting_for_user_id_for_logs, F.text)
async def process_user_id_for_logs_handler(message: types.Message,
state: FSMContext,
settings: Settings,
i18n_data: dict):
current_state_fsm = await state.get_state()
logging.info(
f"Processing user input for logs in state {current_state_fsm}: '{message.text}'"
)
await callback .answer ()
await state.clear()
@router .callback_query (F .data =="admin_logs:prompt_user")
async def prompt_user_for_logs_handler (callback :types .CallbackQuery ,state :FSMContext ,i18n_data :dict ,settings :Settings ):
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
current_lang =i18n_data .get ("current_language",settings .DEFAULT_LANGUAGE )
if not i18n or not callback .message :await callback .answer ("Error");return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
if not i18n:
await message.reply("Language error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
await callback .message .edit_text (
text =_ ("admin_prompt_for_user_id_or_username_logs"),
reply_markup =get_logs_menu_keyboard (i18n ,current_lang )
)
await state .set_state (AdminStates .waiting_for_user_id_for_logs )
await callback .answer ()
input_text = message.text.strip()
user_data_for_logs: Optional[aiosqlite.Row] = None
@router .message (AdminStates .waiting_for_user_id_for_logs ,F .text )
async def process_user_id_for_logs_handler (message :types .Message ,state :FSMContext ,settings :Settings ,i18n_data :dict ):
current_state_fsm =await state .get_state ()
logging .info (f"Processing user input for logs in state {current_state_fsm}: '{message.text}'")
await state .clear ()
if input_text.isdigit():
try:
user_data_for_logs = await get_user(int(input_text))
except ValueError:
pass
elif input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]):
user_data_for_logs = await get_user_by_telegram_username(input_text[1:]
)
elif USERNAME_REGEX.match(input_text):
user_data_for_logs = await get_user_by_telegram_username(input_text)
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
current_lang =i18n_data .get ("current_language",settings .DEFAULT_LANGUAGE )
if not i18n :await message .reply ("Language error.");return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
input_text =message .text .strip ()
user_data_for_logs :Optional [aiosqlite .Row ]=None
if input_text .isdigit ():
try :user_data_for_logs =await get_user (int (input_text ))
except ValueError :pass
elif input_text .startswith ("@")and USERNAME_REGEX .match (input_text [1 :]):
user_data_for_logs =await get_user_by_telegram_username (input_text [1 :])
elif USERNAME_REGEX .match (input_text ):
user_data_for_logs =await get_user_by_telegram_username (input_text )
if not user_data_for_logs :
await message .answer (_ ("admin_log_user_not_found",input =input_text ))
if not user_data_for_logs:
await message.answer(_("admin_log_user_not_found", input=input_text))
return
target_user_id =user_data_for_logs ['user_id']
user_display =user_data_for_logs ['first_name']or (f"@{user_data_for_logs['username']}"if user_data_for_logs .get ('username')else f"ID {target_user_id}")
target_user_id = user_data_for_logs['user_id']
user_display = user_data_for_logs['first_name'] or (
f"@{user_data_for_logs['username']}"
if user_data_for_logs.get('username') else f"ID {target_user_id}")
logs ,total_logs =await get_user_message_logs_paginated (target_user_id ,settings .LOGS_PAGE_SIZE ,0 ),await count_user_message_logs (target_user_id )
logs, total_logs = await get_user_message_logs_paginated(
target_user_id, settings.LOGS_PAGE_SIZE,
0), await count_user_message_logs(target_user_id)
await _display_formatted_logs (
target_message =message ,logs =logs ,total_logs =total_logs ,
current_page =0 ,settings =settings ,
title_key ="admin_user_logs_title",
base_pagination_callback_data =f"admin_logs:view_user:{target_user_id}",
i18n =i18n ,current_lang =current_lang ,
title_kwargs ={"user_display":user_display }
)
await _display_formatted_logs(
target_message=message,
logs=logs,
total_logs=total_logs,
current_page=0,
settings=settings,
title_key="admin_user_logs_title",
base_pagination_callback_data=f"admin_logs:view_user:{target_user_id}",
i18n=i18n,
current_lang=current_lang,
title_kwargs={"user_display": user_display})
@router .callback_query (F .data .startswith ("admin_logs:view_user:"))
async def view_user_logs_paginated_handler (callback :types .CallbackQuery ,settings :Settings ,i18n_data :dict ):
try :
parts =callback .data .split (":")
target_user_id =int (parts [2 ])
page =int (parts [3 ])
except (IndexError ,ValueError ):
await callback .answer ("Invalid log request.",show_alert =True );return
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
current_lang =i18n_data .get ("current_language",settings .DEFAULT_LANGUAGE )
if not i18n or not callback .message :await callback .answer ("Error");return
@router.callback_query(F.data.startswith("admin_logs:view_user:"))
async def view_user_logs_paginated_handler(callback: types.CallbackQuery,
settings: Settings,
i18n_data: dict):
try:
parts = callback.data.split(":")
target_user_id = int(parts[2])
page = int(parts[3])
except (IndexError, ValueError):
await callback.answer("Invalid log request.", show_alert=True)
return
user_data_for_logs =await get_user (target_user_id )
if not user_data_for_logs :
await callback .message .edit_text ("User not found for logs.");await callback .answer ();return
user_display =user_data_for_logs ['first_name']or (f"@{user_data_for_logs['username']}"if user_data_for_logs .get ('username')else f"ID {target_user_id}")
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
if not i18n or not callback.message:
await callback.answer("Error")
return
logs ,total_logs =await get_user_message_logs_paginated (target_user_id ,settings .LOGS_PAGE_SIZE ,page *settings .LOGS_PAGE_SIZE ),await count_user_message_logs (target_user_id )
user_data_for_logs = await get_user(target_user_id)
if not user_data_for_logs:
await callback.message.edit_text("User not found for logs.")
await callback.answer()
return
user_display = user_data_for_logs['first_name'] or (
f"@{user_data_for_logs['username']}"
if user_data_for_logs.get('username') else f"ID {target_user_id}")
await _display_formatted_logs (
target_message =callback .message ,logs =logs ,total_logs =total_logs ,
current_page =page ,settings =settings ,
title_key ="admin_user_logs_title",
base_pagination_callback_data =f"admin_logs:view_user:{target_user_id}",
i18n =i18n ,current_lang =current_lang ,
title_kwargs ={"user_display":user_display }
)
await callback .answer ()
logs, total_logs = await get_user_message_logs_paginated(
target_user_id, settings.LOGS_PAGE_SIZE, page *
settings.LOGS_PAGE_SIZE), await count_user_message_logs(target_user_id)
@router .callback_query (F .data =="admin_action:view_logs_menu",AdminStates .waiting_for_user_id_for_logs )
async def cancel_log_user_input_state_to_menu (callback :types .CallbackQuery ,state :FSMContext ,settings :Settings ,i18n_data :dict ):
await state .clear ()
await display_logs_menu (callback ,i18n_data ,settings )
await _display_formatted_logs(
target_message=callback.message,
logs=logs,
total_logs=total_logs,
current_page=page,
settings=settings,
title_key="admin_user_logs_title",
base_pagination_callback_data=f"admin_logs:view_user:{target_user_id}",
i18n=i18n,
current_lang=current_lang,
title_kwargs={"user_display": user_display})
await callback.answer()
@router.callback_query(F.data == "admin_action:view_logs_menu",
AdminStates.waiting_for_user_id_for_logs)
async def cancel_log_user_input_state_to_menu(callback: types.CallbackQuery,
state: FSMContext,
settings: Settings,
i18n_data: dict):
await state.clear()
await display_logs_menu(callback, i18n_data, settings)
+155 -125
View File
@@ -1,157 +1,187 @@
import logging
from aiogram import Router ,F ,types ,Bot
from aiogram .filters import Command
from aiogram .fsm .context import FSMContext
from datetime import datetime ,timedelta ,timezone
from aiogram import Router, F, types, Bot
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from datetime import datetime, timedelta, timezone
from typing import Optional
from config .settings import Settings
from db .database import create_promo_code_db ,get_promo_codes_db
from bot .states .admin_states import AdminStates
from bot .keyboards .inline .admin_keyboards import get_back_to_admin_panel_keyboard
from bot .middlewares .i18n import JsonI18n
from config.settings import Settings
from db.database import create_promo_code_db, get_promo_codes_db
from bot.states.admin_states import AdminStates
from bot.keyboards.inline.admin_keyboards import get_back_to_admin_panel_keyboard
from bot.middlewares.i18n import JsonI18n
router =Router (name ="admin_promo_codes_router")
router = Router(name="admin_promo_codes_router")
async def create_promo_prompt_handler (
callback :types .CallbackQuery ,
state :FSMContext ,
i18n_data :dict ,
settings :Settings
):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :
logging .error ("i18n missing in create_promo_prompt_handler")
await callback .answer ("Language service error.",show_alert =True )
async def create_promo_prompt_handler(callback: types.CallbackQuery,
state: FSMContext, i18n_data: dict,
settings: Settings):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing in create_promo_prompt_handler")
await callback.answer("Language service error.", show_alert=True)
return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
prompt_text =_ ("admin_promo_create_prompt",example_format ="MYPROMO20 7 100 30")
prompt_text = _("admin_promo_create_prompt",
example_format="MYPROMO20 7 100 30")
if callback .message :
try :
await callback .message .edit_text (prompt_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
except Exception as e :
logging .warning (f"Could not edit message for promo prompt: {e}")
await callback .message .answer (prompt_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
await callback .answer ()
await state .set_state (AdminStates .waiting_for_promo_details )
if callback.message:
try:
await callback.message.edit_text(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
except Exception as e:
logging.warning(f"Could not edit message for promo prompt: {e}")
await callback.message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
await callback.answer()
await state.set_state(AdminStates.waiting_for_promo_details)
@router .message (AdminStates .waiting_for_promo_details ,F .text )
async def process_promo_code_details_handler (
message :types .Message ,
state :FSMContext ,
i18n_data :dict ,
settings :Settings
):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :
logging .error ("i18n missing in process_promo_code_details_handler")
await message .reply ("Language service error.")
@router.message(AdminStates.waiting_for_promo_details, F.text)
async def process_promo_code_details_handler(message: types.Message,
state: FSMContext,
i18n_data: dict,
settings: Settings):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing in process_promo_code_details_handler")
await message.reply("Language service error.")
return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
parts =message .text .strip ().split ()
if not (3 <=len (parts )<=4 ):
await message .answer (_ ("admin_promo_invalid_format"))
parts = message.text.strip().split()
if not (3 <= len(parts) <= 4):
await message.answer(_("admin_promo_invalid_format"))
return
try :
code =parts [0 ].upper ()
if not (3 <=len (code )<=30 and code .isalnum ()):
raise ValueError ("Promo code must be 3-30 alphanumeric characters.")
bonus_days =int (parts [1 ]);max_activations =int (parts [2 ])
valid_until_date :Optional [datetime ]=None
valid_until_str_display =_ ("admin_promo_valid_indefinitely")
try:
code = parts[0].upper()
if not (3 <= len(code) <= 30 and code.isalnum()):
raise ValueError(
"Promo code must be 3-30 alphanumeric characters.")
bonus_days = int(parts[1])
max_activations = int(parts[2])
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 ("Validity days (if provided) must be positive.")
if len(parts) == 4:
valid_days_from_now = int(parts[3])
if valid_days_from_now <= 0:
raise ValueError(
"Validity days (if provided) must be positive.")
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'))
if bonus_days <=0 or max_activations <=0 :raise ValueError ("Bonus days and max activations must be positive.")
except ValueError as e :
await message .answer (_ ("admin_promo_invalid_values",error =str (e )))
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'))
if bonus_days <= 0 or max_activations <= 0:
raise ValueError(
"Bonus days and max activations must be positive.")
except ValueError as e:
await message.answer(_("admin_promo_invalid_values", error=str(e)))
return
admin_id =message .from_user .id
admin_id = message.from_user.id
promo_id =await create_promo_code_db (code ,bonus_days ,max_activations ,admin_id ,valid_until_date )
promo_id = await create_promo_code_db(code, bonus_days, max_activations,
admin_id, valid_until_date)
if promo_id :
success_text =_ ("admin_promo_created_success",code =code ,bonus_days =bonus_days ,max_activations =max_activations ,valid_until_str =valid_until_str_display )
await message .answer (success_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
else :
await message .answer (_ ("admin_promo_creation_failed"))
await state .clear ()
if promo_id:
success_text = _("admin_promo_created_success",
code=code,
bonus_days=bonus_days,
max_activations=max_activations,
valid_until_str=valid_until_str_display)
await message.answer(success_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
else:
await message.answer(_("admin_promo_creation_failed"))
await state.clear()
async def view_promo_codes_handler (
callback :types .CallbackQuery ,
i18n_data :dict ,
settings :Settings
):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :
logging .error ("i18n missing in view_promo_codes_handler")
await callback .answer ("Language error.",show_alert =True )
async def view_promo_codes_handler(callback: types.CallbackQuery,
i18n_data: dict, settings: Settings):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing in view_promo_codes_handler")
await callback.answer("Language error.", show_alert=True)
return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
promos =await get_promo_codes_db (is_active_only =True ,limit =20 )
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
promos = await get_promo_codes_db(is_active_only=True, limit=20)
if not callback .message :
await callback .answer ("Error: message context lost.",show_alert =True );return
if not callback.message:
await callback.answer("Error: message context lost.", show_alert=True)
return
if not promos :
await callback .message .edit_text (_ ("admin_no_active_promos"),reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
await callback .answer ();return
if not promos:
await callback.message.edit_text(
_("admin_no_active_promos"),
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n))
await callback.answer()
return
response_text_parts =[f"<b>{_('admin_active_promos_list_header')}</b>\n"]
for promo in promos :
valid_until_display_text =_ ("admin_promo_valid_indefinitely")
if promo ['valid_until']:
try :
response_text_parts = [f"<b>{_('admin_active_promos_list_header')}</b>\n"]
for promo in promos:
valid_until_display_text = _("admin_promo_valid_indefinitely")
if promo['valid_until']:
try:
valid_until_dt: Optional[datetime] = None
if 'T' in promo['valid_until']:
valid_until_dt = datetime.fromisoformat(
promo['valid_until'].replace("Z", "+00:00"))
else:
valid_until_dt = datetime.strptime(promo['valid_until'],
'%Y-%m-%d %H:%M:%S')
if valid_until_dt and valid_until_dt.tzinfo is None:
valid_until_dt = valid_until_dt.replace(
tzinfo=timezone.utc)
valid_until_display_text = valid_until_dt.strftime('%Y-%m-%d')
except ValueError as e:
logging.warning(
f"Could not parse valid_until date string '{promo['valid_until']}' for promo code {promo['code']}: {e}"
)
valid_until_display_text = promo['valid_until']
response_text_parts.append(
_("admin_promo_list_item",
code=promo['code'],
bonus=promo['bonus_days'],
current=promo['current_activations'],
max=promo['max_activations'],
valid_until=valid_until_display_text))
valid_until_dt :Optional [datetime ]=None
if 'T'in promo ['valid_until']:
valid_until_dt =datetime .fromisoformat (promo ['valid_until'].replace ("Z","+00:00"))
else :
valid_until_dt =datetime .strptime (promo ['valid_until'],'%Y-%m-%d %H:%M:%S')
if valid_until_dt and valid_until_dt .tzinfo is None :
valid_until_dt =valid_until_dt .replace (tzinfo =timezone .utc )
valid_until_display_text =valid_until_dt .strftime ('%Y-%m-%d')
except ValueError as e :
logging .warning (f"Could not parse valid_until date string '{promo['valid_until']}' for promo code {promo['code']}: {e}")
valid_until_display_text =promo ['valid_until']
response_text_parts .append (
_ ("admin_promo_list_item",code =promo ['code'],bonus =promo ['bonus_days'],
current =promo ['current_activations'],max =promo ['max_activations'],
valid_until =valid_until_display_text )
)
final_text ="\n".join (response_text_parts )
try :
await callback .message .edit_text (final_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ),parse_mode ="HTML")
except Exception as e :
logging .warning (f"Failed to edit message for promo list: {e}")
if callback .message :
await callback .message .answer (final_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ),parse_mode ="HTML")
await callback .answer ()
final_text = "\n".join(response_text_parts)
try:
await callback.message.edit_text(
final_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML")
except Exception as e:
logging.warning(f"Failed to edit message for promo list: {e}")
if callback.message:
await callback.message.answer(
final_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n),
parse_mode="HTML")
await callback.answer()
+99 -81
View File
@@ -1,101 +1,119 @@
import logging
from aiogram import Router ,F ,types
from typing import Optional ,Dict
from aiogram import Router, F, types
from typing import Optional, Dict
from datetime import datetime
from config .settings import Settings
from db .database import get_user_count_stats ,get_payment_logs ,get_message_logs_db ,get_last_sync_status
from bot .keyboards .inline .admin_keyboards import get_back_to_admin_panel_keyboard
from bot .middlewares .i18n import JsonI18n
from config.settings import Settings
from db.database import get_user_count_stats, get_payment_logs, get_message_logs_db, get_last_sync_status
from bot.keyboards.inline.admin_keyboards import get_back_to_admin_panel_keyboard
from bot.middlewares.i18n import JsonI18n
router =Router (name ="admin_statistics_router")
router = Router(name="admin_statistics_router")
async def show_statistics_handler (callback :types .CallbackQuery ,i18n_data :dict ,settings :Settings ):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :
logging .error ("i18n missing in show_statistics_handler")
await callback .answer ("Language service error.",show_alert =True )
async def show_statistics_handler(callback: types.CallbackQuery,
i18n_data: dict, settings: Settings):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing in show_statistics_handler")
await callback.answer("Language service error.", show_alert=True)
return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
await callback .answer ()
await callback.answer()
stats_text_parts =[f"<b>{_('admin_stats_header')}</b>"]
stats_text_parts = [f"<b>{_('admin_stats_header')}</b>"]
user_stats = await get_user_count_stats()
stats_text_parts.append(
_("admin_stats_users",
total_users=user_stats.get("total_users", 0),
banned_users=user_stats.get("banned_users", 0),
active_subs=user_stats.get("users_with_active_subscriptions", 0)))
user_stats =await get_user_count_stats ()
stats_text_parts .append (
_ ("admin_stats_users",
total_users =user_stats .get ("total_users",0 ),
banned_users =user_stats .get ("banned_users",0 ),
active_subs =user_stats .get ("users_with_active_subscriptions",0 )
)
)
last_payments = await get_payment_logs(limit=5)
if last_payments:
stats_text_parts.append(
f"\n<b>{_('admin_stats_recent_payments_header')}</b>")
for payment in last_payments:
status_emoji = "" if payment['status'] == 'succeeded' else (
"" if payment['status'] == 'pending' else "")
user_info = f"User {payment['user_id']}" + (
f" (@{payment['username']})" if payment['username'] else "")
payment_date_str = payment['created_at']
if isinstance(payment_date_str,
str) and len(payment_date_str) > 10:
payment_date_str = payment_date_str[:10]
stats_text_parts.append(
_("admin_stats_payment_item",
status_emoji=status_emoji,
amount=payment['amount'],
currency=payment['currency'],
user_info=user_info,
p_status=payment['status'],
p_date=payment_date_str))
else:
stats_text_parts.append(f"\n{_('admin_stats_no_payments_found')}")
last_payments =await get_payment_logs (limit =5 )
if last_payments :
stats_text_parts .append (f"\n<b>{_('admin_stats_recent_payments_header')}</b>")
for payment in last_payments :
status_emoji =""if payment ['status']=='succeeded'else (""if payment ['status']=='pending'else "")
user_info =f"User {payment['user_id']}"+(f" (@{payment['username']})"if payment ['username']else "")
payment_date_str =payment ['created_at']
if isinstance (payment_date_str ,str )and len (payment_date_str )>10 :
payment_date_str =payment_date_str [:10 ]
sync_status = await get_last_sync_status()
if sync_status:
stats_text_parts.append(
f"\n<b>{_('admin_stats_last_sync_header')}</b>")
stats_text_parts .append (
_ ("admin_stats_payment_item",
status_emoji =status_emoji ,amount =payment ['amount'],currency =payment ['currency'],
user_info =user_info ,p_status =payment ['status'],p_date =payment_date_str
)
)
else :
stats_text_parts .append (f"\n{_('admin_stats_no_payments_found')}")
sync_time_val = sync_status['last_sync_time']
sync_time_str = "N/A"
if isinstance(sync_time_val, datetime):
sync_time_str = sync_time_val.strftime('%Y-%m-%d %H:%M:%S UTC')
elif isinstance(sync_time_val, str):
sync_time_str = sync_time_val[:19] if len(
sync_time_val) > 19 else sync_time_val
details_val = sync_status['details']
details_str = (details_val[:100] +
"...") if details_val and len(details_val) > 100 else (
details_val or "N/A")
sync_status =await get_last_sync_status ()
if sync_status :
stats_text_parts .append (f"\n<b>{_('admin_stats_last_sync_header')}</b>")
stats_text_parts.append(
f" {_('admin_stats_sync_time')}: {sync_time_str}")
stats_text_parts.append(
f" {_('admin_stats_sync_status')}: {sync_status['status']}")
stats_text_parts.append(
f" {_('admin_stats_sync_users_processed')}: {sync_status['users_processed_from_panel']}"
)
stats_text_parts.append(
f" {_('admin_stats_sync_subs_synced')}: {sync_status['subscriptions_synced']}"
)
stats_text_parts.append(
f" {_('admin_stats_sync_details_label')}: {details_str}")
else:
stats_text_parts.append(f"\n{_('admin_sync_status_never_run')}")
sync_time_val =sync_status ['last_sync_time']
sync_time_str ="N/A"
if isinstance (sync_time_val ,datetime ):
sync_time_str =sync_time_val .strftime ('%Y-%m-%d %H:%M:%S UTC')
elif isinstance (sync_time_val ,str ):
sync_time_str =sync_time_val [:19 ]if len (sync_time_val )>19 else sync_time_val
final_text = "\n".join(stats_text_parts)
details_val =sync_status ['details']
details_str =(details_val [:100 ]+"...")if details_val and len (details_val )>100 else (details_val or "N/A")
if callback.message:
try:
await callback.message.edit_text(
final_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n),
parse_mode="HTML")
except Exception as e:
logging.error(f"Error editing message for statistics: {e}",
exc_info=True)
stats_text_parts .append (f" {_('admin_stats_sync_time')}: {sync_time_str}")
stats_text_parts .append (f" {_('admin_stats_sync_status')}: {sync_status['status']}")
stats_text_parts .append (f" {_('admin_stats_sync_users_processed')}: {sync_status['users_processed_from_panel']}")
stats_text_parts .append (f" {_('admin_stats_sync_subs_synced')}: {sync_status['subscriptions_synced']}")
stats_text_parts .append (f" {_('admin_stats_sync_details_label')}: {details_str}")
else :
stats_text_parts .append (f"\n{_('admin_sync_status_never_run')}")
final_text ="\n".join (stats_text_parts )
if callback .message :
try :
await callback .message .edit_text (
final_text ,
reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ),
parse_mode ="HTML"
)
except Exception as e :
logging .error (f"Error editing message for statistics: {e}",exc_info =True )
for chunk in [final_text [i :i +4000 ]for i in range (0 ,len (final_text ),4000 )]:
await callback .message .answer (
chunk ,
reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n )if chunk ==final_text [-len (chunk ):]else None ,
parse_mode ="HTML"
)
else :
logging .error ("Cannot send statistics, callback.message is None.")
for chunk in [
final_text[i:i + 4000]
for i in range(0, len(final_text), 4000)
]:
await callback.message.answer(
chunk,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n)
if chunk == final_text[-len(chunk):] else None,
parse_mode="HTML")
else:
logging.error("Cannot send statistics, callback.message is None.")
+121 -99
View File
@@ -1,126 +1,148 @@
import logging
from aiogram import Router ,types ,Bot
from aiogram .filters import Command
from aiogram import Router, types, Bot
from aiogram.filters import Command
from typing import Optional
from config .settings import Settings
from bot .services .panel_api_service import PanelApiService
from config.settings import Settings
from bot.services.panel_api_service import PanelApiService
from db .database import update_sync_status ,get_last_sync_status ,sync_panel_user_data
from bot .middlewares .i18n import JsonI18n
from bot .keyboards .inline .admin_keyboards import get_back_to_admin_panel_keyboard
from db.database import update_sync_status, get_last_sync_status, sync_panel_user_data
from bot.middlewares.i18n import JsonI18n
from bot.keyboards.inline.admin_keyboards import get_back_to_admin_panel_keyboard
router =Router (name ="admin_sync_router")
router = Router(name="admin_sync_router")
@router .message (Command ("sync"))
async def sync_command_handler (
message :types .Message ,
bot :Bot ,
settings :Settings ,
i18n_data :dict ,
panel_service :PanelApiService
):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'))
i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :logging .error ("i18n missing");await message .answer ("Language error.");return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
await message .answer (_ ("sync_started"))
logging .info ("Admin triggered panel sync.")
@router.message(Command("sync"))
async def sync_command_handler(message: types.Message, bot: Bot,
settings: Settings, i18n_data: dict,
panel_service: PanelApiService):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing")
await message.answer("Language error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
users_processed_count =0
users_synced_successfully =0
sync_errors =[]
await message.answer(_("sync_started"))
logging.info("Admin triggered panel sync.")
try :
panel_users_data =await panel_service .get_all_panel_users ()
users_processed_count = 0
users_synced_successfully = 0
sync_errors = []
if panel_users_data is None :
error_msg ="Failed to fetch users from panel or panel API issue."
sync_errors .append (error_msg )
await update_sync_status ("failed",error_msg )
await message .answer (_ ("sync_failed",details =error_msg ))
try:
panel_users_data = await panel_service.get_all_panel_users()
if panel_users_data is None:
error_msg = "Failed to fetch users from panel or panel API issue."
sync_errors.append(error_msg)
await update_sync_status("failed", error_msg)
await message.answer(_("sync_failed", details=error_msg))
return
if not panel_users_data :
status_msg ="No users found in the panel to sync."
await update_sync_status ("success",status_msg ,0 ,0 )
await message .answer (_ ("sync_completed",status ="Success",details =status_msg ))
if not panel_users_data:
status_msg = "No users found in the panel to sync."
await update_sync_status("success", status_msg, 0, 0)
await message.answer(
_("sync_completed", status="Success", details=status_msg))
return
total_panel_users =len (panel_users_data )
logging .info (f"Starting sync for {total_panel_users} panel users.")
for panel_user_dict in panel_users_data :
users_processed_count +=1
telegram_id_from_panel =panel_user_dict .get ('telegramId')
panel_uuid =panel_user_dict .get ('uuid')
if not telegram_id_from_panel :
logging .info (f"Panel user {panel_uuid} (username: {panel_user_dict.get('username')}) has no 'telegramId'. Skipping TG ID based sync.")
total_panel_users = len(panel_users_data)
logging.info(f"Starting sync for {total_panel_users} panel users.")
for panel_user_dict in panel_users_data:
users_processed_count += 1
telegram_id_from_panel = panel_user_dict.get('telegramId')
panel_uuid = panel_user_dict.get('uuid')
if not telegram_id_from_panel:
logging.info(
f"Panel user {panel_uuid} (username: {panel_user_dict.get('username')}) has no 'telegramId'. Skipping TG ID based sync."
)
continue
if not panel_uuid :
logging .warning (f"Panel user (TG ID: {telegram_id_from_panel}) missing 'uuid'. Skipping.")
sync_errors .append (f"Panel user data for TG ID {telegram_id_from_panel} missing UUID.")
if not panel_uuid:
logging.warning(
f"Panel user (TG ID: {telegram_id_from_panel}) missing 'uuid'. Skipping."
)
sync_errors.append(
f"Panel user data for TG ID {telegram_id_from_panel} missing UUID."
)
continue
if await sync_panel_user_data(panel_user_dict):
users_synced_successfully += 1
else:
sync_errors.append(
f"Sync issue for panel user: {panel_uuid} (TG ID: {telegram_id_from_panel})"
)
if await sync_panel_user_data (panel_user_dict ):
users_synced_successfully +=1
else :
sync_errors .append (f"Sync issue for panel user: {panel_uuid} (TG ID: {telegram_id_from_panel})")
if users_processed_count % 20 == 0:
logging.info(
f"Sync progress: {users_processed_count}/{total_panel_users} users processed."
)
if users_processed_count %20 ==0 :
logging .info (f"Sync progress: {users_processed_count}/{total_panel_users} users processed.")
status_msg = f"Panel users checked: {total_panel_users}. Users/Subscriptions synced via TG ID: {users_synced_successfully}."
if sync_errors:
status_msg += f" Errors encountered: {len(sync_errors)}. See logs for details."
error_preview = "\n".join(sync_errors[:3])
await update_sync_status("partial_success",
status_msg + " " + error_preview,
total_panel_users,
users_synced_successfully)
await message.answer(
_("sync_completed",
status="Partial Success",
details=status_msg))
else:
await update_sync_status("success", status_msg, total_panel_users,
users_synced_successfully)
await message.answer(
_("sync_completed", status="Success", details=status_msg))
status_msg =f"Panel users checked: {total_panel_users}. Users/Subscriptions synced via TG ID: {users_synced_successfully}."
if sync_errors :
status_msg +=f" Errors encountered: {len(sync_errors)}. See logs for details."
error_preview ="\n".join (sync_errors [:3 ])
await update_sync_status ("partial_success",status_msg +" "+error_preview ,total_panel_users ,users_synced_successfully )
await message .answer (_ ("sync_completed",status ="Partial Success",details =status_msg ))
else :
await update_sync_status ("success",status_msg ,total_panel_users ,users_synced_successfully )
await message .answer (_ ("sync_completed",status ="Success",details =status_msg ))
except Exception as e:
logging.error(f"Error during /sync command: {e}", exc_info=True)
error_detail = f"An unexpected error occurred during sync: {str(e)}"
await update_sync_status("failed", error_detail, users_processed_count,
users_synced_successfully)
await message.answer(_("sync_failed", details=error_detail))
except Exception as e :
logging .error (f"Error during /sync command: {e}",exc_info =True )
error_detail =f"An unexpected error occurred during sync: {str(e)}"
await update_sync_status ("failed",error_detail ,users_processed_count ,users_synced_successfully )
await message .answer (_ ("sync_failed",details =error_detail ))
@router .message (Command ("syncstatus"))
async def sync_status_command_handler (message :types .Message ,i18n_data :dict ,settings :Settings ):
@router.message(Command("syncstatus"))
async def sync_status_command_handler(message: types.Message, i18n_data: dict,
settings: Settings):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :await message .answer ("Language error.");return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs );status_record =await get_last_sync_status ();response_text =""
if status_record :
last_time_val =status_record ['last_sync_time'];last_time_str =last_time_val .strftime ('%Y-%m-%d %H:%M:%S UTC')if isinstance (last_time_val ,datetime )else str (last_time_val )
if isinstance (last_time_str ,str )and len (last_time_str )>19 :last_time_str =last_time_str [:19 ]
details_val =status_record ['details'];details_str =(details_val [:200 ]+"...")if details_val and len (details_val )>200 else (details_val or "N/A")
response_text =(f"<b>{_('admin_stats_last_sync_header')}</b>\n"
f" {_('admin_stats_sync_time')}: {last_time_str}\n"
f" {_('admin_stats_sync_status')}: {status_record['status']}\n"
f" {_('admin_stats_sync_users_processed')}: {status_record['users_processed_from_panel']}\n"
f" {_('admin_stats_sync_subs_synced')}: {status_record['subscriptions_synced']}\n"
f" {_('admin_stats_sync_details_label')}: {details_str}")
else :response_text =_ ("admin_sync_status_never_run")
await message .answer (response_text ,parse_mode ="HTML")
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await message.answer("Language error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
status_record = await get_last_sync_status()
response_text = ""
if status_record:
last_time_val = status_record['last_sync_time']
last_time_str = last_time_val.strftime(
'%Y-%m-%d %H:%M:%S UTC') if isinstance(
last_time_val, datetime) else str(last_time_val)
if isinstance(last_time_str, str) and len(last_time_str) > 19:
last_time_str = last_time_str[:19]
details_val = status_record['details']
details_str = (details_val[:200] +
"...") if details_val and len(details_val) > 200 else (
details_val or "N/A")
response_text = (
f"<b>{_('admin_stats_last_sync_header')}</b>\n"
f" {_('admin_stats_sync_time')}: {last_time_str}\n"
f" {_('admin_stats_sync_status')}: {status_record['status']}\n"
f" {_('admin_stats_sync_users_processed')}: {status_record['users_processed_from_panel']}\n"
f" {_('admin_stats_sync_subs_synced')}: {status_record['subscriptions_synced']}\n"
f" {_('admin_stats_sync_details_label')}: {details_str}")
else:
response_text = _("admin_sync_status_never_run")
await message.answer(response_text, parse_mode="HTML")
+565 -205
View File
@@ -1,220 +1,580 @@
import logging
import re
from aiogram import Router ,F ,types ,Bot
from aiogram .fsm .context import FSMContext
from typing import Optional ,Tuple ,List ,Any
from aiogram import Router, F, types, Bot
from aiogram.fsm.context import FSMContext
from typing import Optional, Tuple, List, Any
import math
import aiosqlite
from datetime import datetime
from config .settings import Settings
from db .database import (
get_user ,set_user_ban_status_db ,get_user_by_telegram_username ,
get_banned_users_list_paginated ,get_user_active_subscription_end_date
)
from bot .services .panel_api_service import PanelApiService
from bot .states .admin_states import AdminStates
from bot .keyboards .inline .admin_keyboards import (
get_back_to_admin_panel_keyboard ,get_user_card_keyboard ,
get_banned_users_keyboard ,get_confirmation_keyboard ,get_admin_panel_keyboard
)
from bot .middlewares .i18n import JsonI18n
from config.settings import Settings
from db.database import (get_user, set_user_ban_status_db,
get_user_by_telegram_username,
get_banned_users_list_paginated,
get_user_active_subscription_end_date)
from bot.services.panel_api_service import PanelApiService
from bot.states.admin_states import AdminStates
from bot.keyboards.inline.admin_keyboards import (
get_back_to_admin_panel_keyboard, get_user_card_keyboard,
get_banned_users_keyboard, get_confirmation_keyboard,
get_admin_panel_keyboard)
from bot.middlewares.i18n import JsonI18n
router =Router (name ="admin_user_management_router")
router = Router(name="admin_user_management_router")
USERNAME_REGEX =re .compile (r"^[a-zA-Z0-9_]{5,32}$")
USERNAME_REGEX = re.compile(r"^[a-zA-Z0-9_]{5,32}$")
async def ban_user_prompt_handler (callback :types .CallbackQuery ,state :FSMContext ,i18n_data :dict ,settings :Settings ):
async def ban_user_prompt_handler(callback: types.CallbackQuery,
state: FSMContext, i18n_data: dict,
settings: Settings):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :logging .error ("i18n missing");await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs );prompt_text =_ ("admin_ban_user_prompt")
if callback .message :
try :await callback .message .edit_text (prompt_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
except Exception as e :logging .warning (f"Edit failed: {e}");await callback .message .answer (prompt_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
await callback .answer ();await state .set_state (AdminStates .waiting_for_user_id_to_ban )
@router .message (AdminStates .waiting_for_user_id_to_ban ,F .text )
async def process_user_input_to_ban_handler (message :types .Message ,state :FSMContext ,i18n_data :dict ,settings :Settings ,panel_service :PanelApiService ):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :logging .error ("i18n missing");await message .reply ("Language error.");return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs );input_text =message .text .strip ();user_to_ban_data :Optional [aiosqlite .Row ]=None ;user_id_or_username_for_msg =input_text
if input_text .isdigit ():
try :user_id_to_ban_val =int (input_text );user_to_ban_data =await get_user (user_id_to_ban_val )
except ValueError :await message .answer (_ ("admin_invalid_user_id_format"));return
elif input_text .startswith ("@")and USERNAME_REGEX .match (input_text [1 :]):user_to_ban_data =await get_user_by_telegram_username (input_text [1 :])
elif USERNAME_REGEX .match (input_text ):user_to_ban_data =await get_user_by_telegram_username (input_text )
else :await message .answer (_ ("admin_invalid_user_id_format")+" "+_ ("admin_invalid_username_format"));return
if not user_to_ban_data :await message .answer (_ ("admin_user_not_found_in_bot_db",user_id =input_text ));await state .clear ();return
user_id_to_ban =user_to_ban_data ['user_id'];user_id_or_username_for_msg =f"@{user_to_ban_data['username']}"if user_to_ban_data .get ('username')else str (user_id_to_ban )
if user_id_to_ban ==message .from_user .id or user_id_to_ban in settings .ADMIN_IDS :await message .answer (_ ("admin_cannot_ban_self_or_admin"));await state .clear ();return
if user_to_ban_data ['is_banned']:await message .answer (_ ("admin_user_already_banned",user_id_or_username =user_id_or_username_for_msg ));await state .clear ();return
panel_user_uuid =user_to_ban_data ['panel_user_uuid']if user_to_ban_data and 'panel_user_uuid'in user_to_ban_data .keys ()and user_to_ban_data ['panel_user_uuid']else None
await set_user_ban_status_db (user_id_to_ban ,is_banned =True )
reply_markup_val =get_back_to_admin_panel_keyboard (current_lang ,i18n )
if panel_user_uuid :
panel_ban_success =await panel_service .update_user_status_on_panel (panel_user_uuid ,enable =False )
if panel_ban_success :await message .answer (_ ("admin_user_banned_success_panel_too",user_id_or_username =user_id_or_username_for_msg ),reply_markup =reply_markup_val )
else :await message .answer (_ ("admin_user_banned_local_panel_fail",user_id_or_username =user_id_or_username_for_msg ),reply_markup =reply_markup_val )
else :await message .answer (_ ("admin_user_banned_local_no_panel_uuid",user_id_or_username =user_id_or_username_for_msg ),reply_markup =reply_markup_val )
await state .clear ()
async def unban_user_prompt_handler (callback :types .CallbackQuery ,state :FSMContext ,i18n_data :dict ,settings :Settings ):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :logging .error ("i18n missing");await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs );prompt_text =_ ("admin_unban_user_prompt")
if callback .message :
try :await callback .message .edit_text (prompt_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
except :await callback .message .answer (prompt_text ,reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
await callback .answer ();await state .set_state (AdminStates .waiting_for_user_id_to_unban )
@router .message (AdminStates .waiting_for_user_id_to_unban ,F .text )
async def process_user_input_to_unban_handler (message :types .Message ,state :FSMContext ,i18n_data :dict ,settings :Settings ,panel_service :PanelApiService ):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :logging .error ("i18n missing");await message .reply ("Language error.");return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs );input_text =message .text .strip ();user_to_unban_data :Optional [aiosqlite .Row ]=None ;user_id_or_username_for_msg =input_text
if input_text .isdigit ():
try :user_id_to_unban_val =int (input_text );user_to_unban_data =await get_user (user_id_to_unban_val )
except ValueError :await message .answer (_ ("admin_invalid_user_id_format"));return
elif input_text .startswith ("@")and USERNAME_REGEX .match (input_text [1 :]):user_to_unban_data =await get_user_by_telegram_username (input_text [1 :])
elif USERNAME_REGEX .match (input_text ):user_to_unban_data =await get_user_by_telegram_username (input_text )
else :await message .answer (_ ("admin_invalid_user_id_format")+" "+_ ("admin_invalid_username_format"));return
if not user_to_unban_data :await message .answer (_ ("admin_user_not_found_in_bot_db",user_id =input_text ));await state .clear ();return
user_id_to_unban =user_to_unban_data ['user_id'];user_id_or_username_for_msg =f"@{user_to_unban_data['username']}"if user_to_unban_data .get ('username')else str (user_id_to_unban )
if not user_to_unban_data ['is_banned']:await message .answer (_ ("admin_user_not_banned",user_id_or_username =user_id_or_username_for_msg ));await state .clear ();return
panel_user_uuid =user_to_unban_data ['panel_user_uuid']if user_to_unban_data and 'panel_user_uuid'in user_to_unban_data .keys ()and user_to_unban_data ['panel_user_uuid']else None
await set_user_ban_status_db (user_id_to_unban ,is_banned =False )
reply_markup_val =get_back_to_admin_panel_keyboard (current_lang ,i18n )
if panel_user_uuid :
panel_unban_success =await panel_service .update_user_status_on_panel (panel_user_uuid ,enable =True )
if panel_unban_success :await message .answer (_ ("admin_user_unbanned_success_panel_too",user_id_or_username =user_id_or_username_for_msg ),reply_markup =reply_markup_val )
else :await message .answer (_ ("admin_user_unbanned_local_panel_fail",user_id_or_username =user_id_or_username_for_msg ),reply_markup =reply_markup_val )
else :await message .answer (_ ("admin_user_unbanned_local_no_panel_uuid",user_id_or_username =user_id_or_username_for_msg ),reply_markup =reply_markup_val )
await state .clear ()
async def view_banned_users_handler (callback :types .CallbackQuery ,i18n_data :dict ,settings :Settings ,state :FSMContext ):
await state .clear ();current_page =0 ;
if ":"in callback .data and callback .data .count (":")==2 :
try :current_page =int (callback .data .split (":")[-1 ])
except ValueError :current_page =0
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
banned_users ,total_banned =await get_banned_users_list_paginated (limit =settings .LOGS_PAGE_SIZE ,offset =current_page *settings .LOGS_PAGE_SIZE )
if not callback .message :await callback .answer ("Error.");return
if total_banned ==0 :
await callback .message .edit_text (_ ("admin_no_banned_users"),reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ))
else :
total_pages =math .ceil (total_banned /settings .LOGS_PAGE_SIZE )if settings .LOGS_PAGE_SIZE >0 else 1
await callback .message .edit_text (text =_ ("admin_banned_list_title",current_page =current_page +1 ,total_pages =max (1 ,total_pages )),reply_markup =get_banned_users_keyboard (banned_users ,current_page ,total_banned ,i18n ,current_lang ,settings ))
await callback .answer ()
@router .callback_query (F .data .startswith ("admin_user_card:"))
async def show_user_card_handler (callback :types .CallbackQuery ,i18n_data :dict ,settings :Settings ,panel_service :PanelApiService ,state :FSMContext ,force_user_id :Optional [int ]=None ,force_page :Optional [int ]=None ):
await state .clear ();user_id_to_show =0 ;banned_list_page_to_return =0
if force_user_id is not None and force_page is not None :user_id_to_show =force_user_id ;banned_list_page_to_return =force_page
else :
try :parts =callback .data .split (":");user_id_to_show =int (parts [1 ]);banned_list_page_to_return =int (parts [2 ])if len (parts )>2 else 0
except (IndexError ,ValueError ):await callback .answer ("Invalid user card data.",show_alert =True );return
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
user_data =await get_user (user_id_to_show )
if not callback .message :await callback .answer ("Error.");return
if not user_data :await callback .message .edit_text (_ ("admin_user_not_found_in_bot_db",user_id =user_id_to_show ),reply_markup =get_back_to_admin_panel_keyboard (current_lang ,i18n ));await callback .answer ();return
user_display_name =user_data ['first_name']or (f"@{user_data['username']}"if user_data .get ('username')else f"ID: {user_id_to_show}")
sub_end_date_str =await get_user_active_subscription_end_date (user_id_to_show )or _ ("user_card_sub_na")
reg_date_from_db =user_data ['registration_date_str']if 'registration_date_str'in user_data .keys ()else "N/A"
reg_date_display =reg_date_from_db [:10 ]if reg_date_from_db and reg_date_from_db !="N/A"else "N/A"
card_text =_ ("user_card_info",user_id =user_data ['user_id'],username =user_data .get ('username',"N/A"),first_name =user_data .get ('first_name',""),last_name =user_data .get ('last_name',""),language_code =user_data .get ('language_code',"N/A"),panel_user_uuid =user_data .get ('panel_user_uuid',"N/A"),ban_status =_ (key ="user_card_banned")if user_data ['is_banned']else _ (key ="user_card_active"),reg_date =reg_date_display ,sub_end_date =sub_end_date_str )
await callback .message .edit_text (text =f"{_('admin_user_card_title', user_display=user_display_name)}\n\n{card_text}",reply_markup =get_user_card_keyboard (user_id_to_show ,bool (user_data ['is_banned']),i18n ,current_lang ,banned_list_page_to_return ),parse_mode ="HTML")
if force_user_id is None :await callback .answer ()
@router .callback_query (F .data .startswith ("admin_unban_confirm:"))
async def confirm_unban_handler (callback :types .CallbackQuery ,i18n_data :dict ,settings :Settings ):
try :_ ,user_id_str ,page_str =callback .data .split (":");user_id =int (user_id_str );banned_list_page =int (page_str )
except :await callback .answer ("Invalid data",show_alert =True );return
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
user_data =await get_user (user_id );user_display =(user_data ['first_name']or (f"@{user_data['username']}"if user_data .get ('username')else f"ID {user_id}"))if user_data else f"ID {user_id}"
if callback .message :await callback .message .edit_text (text =_ ("admin_confirm_unban_prompt",user_display =user_display ,user_id =user_id ),reply_markup =get_confirmation_keyboard (yes_callback_data =f"admin_unban_do:{user_id}:{banned_list_page}",no_callback_data =f"admin_user_card:{user_id}:{banned_list_page}",i18n_instance =i18n ,lang =current_lang ),parse_mode ="HTML")
await callback .answer ()
@router .callback_query (F .data .startswith ("admin_ban_confirm:"))
async def confirm_ban_handler (callback :types .CallbackQuery ,i18n_data :dict ,settings :Settings ):
try :_ ,user_id_str ,page_str =callback .data .split (":");user_id =int (user_id_str );banned_list_page =int (page_str )
except :await callback .answer ("Invalid data",show_alert =True );return
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
user_data =await get_user (user_id );user_display =(user_data ['first_name']or (f"@{user_data['username']}"if user_data .get ('username')else f"ID {user_id}"))if user_data else f"ID {user_id}"
if callback .message :
await callback .message .edit_text (text =_ ("admin_confirm_ban_prompt",user_display =user_display ,user_id =user_id ),reply_markup =get_confirmation_keyboard (yes_callback_data =f"admin_ban_do:{user_id}:{banned_list_page}",no_callback_data =f"admin_user_card:{user_id}:{banned_list_page}",i18n_instance =i18n ,lang =current_lang ),parse_mode ="HTML")
await callback .answer ()
@router .callback_query (F .data .startswith ("admin_unban_do:"))
async def do_unban_user_handler (callback :types .CallbackQuery ,i18n_data :dict ,settings :Settings ,panel_service :PanelApiService ,state :FSMContext ):
try :_ ,user_id_str ,page_str =callback .data .split (":");user_id_to_unban =int (user_id_str );banned_list_page =int (page_str )
except :await callback .answer ("Invalid data",show_alert =True );return
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
user_to_unban_data =await get_user (user_id_to_unban );user_display_name =(user_to_unban_data ['first_name']or (f"@{user_to_unban_data['username']}"if user_to_unban_data .get ('username')else f"ID {user_id_to_unban}"))if user_to_unban_data else f"ID {user_id_to_unban}"
if not user_to_unban_data or not user_to_unban_data ['is_banned']:
await callback .answer (_ ("admin_user_not_banned",user_id_or_username =user_display_name ),show_alert =True )
if callback .message :await show_user_card_handler (callback ,i18n_data ,settings ,panel_service ,state ,force_user_id =user_id_to_unban ,force_page =banned_list_page )
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing")
await callback.answer("Language error.", show_alert=True)
return
await set_user_ban_status_db (user_id_to_unban ,is_banned =False )
panel_user_uuid =user_to_unban_data ['panel_user_uuid']if user_to_unban_data and 'panel_user_uuid'in user_to_unban_data .keys ()and user_to_unban_data ['panel_user_uuid']else None
if panel_user_uuid :
if not await panel_service .update_user_status_on_panel (panel_user_uuid ,enable =True ):logging .warning (f"Panel status update fail for unban {user_id_to_unban}")
await callback .answer (_ ("admin_user_unbanned_from_card",user_display =user_display_name ,user_id =user_id_to_unban ),show_alert =False )
if callback .message :await show_user_card_handler (callback ,i18n_data ,settings ,panel_service ,state ,force_user_id =user_id_to_unban ,force_page =banned_list_page )
@router .callback_query (F .data .startswith ("admin_ban_do:"))
async def do_ban_user_handler (callback :types .CallbackQuery ,i18n_data :dict ,settings :Settings ,panel_service :PanelApiService ,state :FSMContext ):
try :_ ,user_id_str ,page_str =callback .data .split (":");user_id_to_ban =int (user_id_str );banned_list_page =int (page_str )
except :await callback .answer ("Invalid data",show_alert =True );return
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
user_to_ban_data =await get_user (user_id_to_ban );user_display_name =(user_to_ban_data ['first_name']or (f"@{user_to_ban_data['username']}"if user_to_ban_data .get ('username')else f"ID {user_id_to_ban}"))if user_to_ban_data else f"ID {user_id_to_ban}"
if not user_to_ban_data :await callback .answer (_ ("admin_user_not_found_in_bot_db",user_id =user_id_to_ban ),show_alert =True );return
if user_to_ban_data ['is_banned']:await callback .answer (_ ("admin_user_already_banned",user_id_or_username =user_display_name ),show_alert =True );return
if user_id_to_ban ==callback .from_user .id or user_id_to_ban in settings .ADMIN_IDS :await callback .answer (_ ("admin_cannot_ban_self_or_admin"),show_alert =True );return
await set_user_ban_status_db (user_id_to_ban ,is_banned =True )
panel_user_uuid =user_to_ban_data ['panel_user_uuid']if user_to_ban_data and 'panel_user_uuid'in user_to_ban_data .keys ()and user_to_ban_data ['panel_user_uuid']else None
if panel_user_uuid :
if not await panel_service .update_user_status_on_panel (panel_user_uuid ,enable =False ):logging .warning (f"Panel status update fail for ban {user_id_to_ban}")
await callback .answer (_ ("admin_user_banned_from_card",user_display =user_display_name ,user_id =user_id_to_ban ),show_alert =False )
if callback .message :await show_user_card_handler (callback ,i18n_data ,settings ,panel_service ,state ,force_user_id =user_id_to_ban ,force_page =banned_list_page )
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
prompt_text = _("admin_ban_user_prompt")
if callback.message:
try:
await callback.message.edit_text(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
except Exception as e:
logging.warning(f"Edit failed: {e}")
await callback.message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
await callback.answer()
await state.set_state(AdminStates.waiting_for_user_id_to_ban)
@router .callback_query (F .data =="admin_action:main",AdminStates .waiting_for_user_id_to_ban )
@router .callback_query (F .data =="admin_action:main",AdminStates .waiting_for_user_id_to_unban )
async def cancel_user_management_input_state (callback :types .CallbackQuery ,state :FSMContext ,settings :Settings ,i18n_data :dict ,bot :Bot ):
@router.message(AdminStates.waiting_for_user_id_to_ban, F.text)
async def process_user_input_to_ban_handler(message: types.Message,
state: FSMContext, i18n_data: dict,
settings: Settings,
panel_service: PanelApiService):
current_lang =i18n_data .get ("current_language",getattr (settings ,'DEFAULT_LANGUAGE','en'));i18n :Optional [JsonI18n ]=i18n_data .get ("i18n_instance")
if not i18n :await callback .answer ("Language error.",show_alert =True );return
_ =lambda key ,**kwargs :i18n .gettext (current_lang ,key ,**kwargs )
if callback .message :
try :await callback .message .edit_text (_ ("admin_action_cancelled_default"),reply_markup =get_admin_panel_keyboard (i18n ,current_lang ,settings ))
except :await callback .message .answer (_ ("admin_action_cancelled_default"),reply_markup =get_admin_panel_keyboard (i18n ,current_lang ,settings ))
await callback .answer (_ ("admin_action_cancelled_default"));await state .clear ()
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing")
await message.reply("Language error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
input_text = message.text.strip()
user_to_ban_data: Optional[aiosqlite.Row] = None
user_id_or_username_for_msg = input_text
if input_text.isdigit():
try:
user_id_to_ban_val = int(input_text)
user_to_ban_data = await get_user(user_id_to_ban_val)
except ValueError:
await message.answer(_("admin_invalid_user_id_format"))
return
elif input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]):
user_to_ban_data = await get_user_by_telegram_username(input_text[1:])
elif USERNAME_REGEX.match(input_text):
user_to_ban_data = await get_user_by_telegram_username(input_text)
else:
await message.answer(
_("admin_invalid_user_id_format") + " " +
_("admin_invalid_username_format"))
return
if not user_to_ban_data:
await message.answer(
_("admin_user_not_found_in_bot_db", user_id=input_text))
await state.clear()
return
user_id_to_ban = user_to_ban_data['user_id']
user_id_or_username_for_msg = f"@{user_to_ban_data['username']}" if user_to_ban_data.get(
'username') else str(user_id_to_ban)
if user_id_to_ban == message.from_user.id or user_id_to_ban in settings.ADMIN_IDS:
await message.answer(_("admin_cannot_ban_self_or_admin"))
await state.clear()
return
if user_to_ban_data['is_banned']:
await message.answer(
_("admin_user_already_banned",
user_id_or_username=user_id_or_username_for_msg))
await state.clear()
return
panel_user_uuid = user_to_ban_data[
'panel_user_uuid'] if user_to_ban_data and 'panel_user_uuid' in user_to_ban_data.keys(
) and user_to_ban_data['panel_user_uuid'] else None
await set_user_ban_status_db(user_id_to_ban, is_banned=True)
reply_markup_val = get_back_to_admin_panel_keyboard(current_lang, i18n)
if panel_user_uuid:
panel_ban_success = await panel_service.update_user_status_on_panel(
panel_user_uuid, enable=False)
if panel_ban_success:
await message.answer(_(
"admin_user_banned_success_panel_too",
user_id_or_username=user_id_or_username_for_msg),
reply_markup=reply_markup_val)
else:
await message.answer(_(
"admin_user_banned_local_panel_fail",
user_id_or_username=user_id_or_username_for_msg),
reply_markup=reply_markup_val)
else:
await message.answer(_(
"admin_user_banned_local_no_panel_uuid",
user_id_or_username=user_id_or_username_for_msg),
reply_markup=reply_markup_val)
await state.clear()
async def unban_user_prompt_handler(callback: types.CallbackQuery,
state: FSMContext, i18n_data: dict,
settings: Settings):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing")
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
prompt_text = _("admin_unban_user_prompt")
if callback.message:
try:
await callback.message.edit_text(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
except:
await callback.message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
await callback.answer()
await state.set_state(AdminStates.waiting_for_user_id_to_unban)
@router.message(AdminStates.waiting_for_user_id_to_unban, F.text)
async def process_user_input_to_unban_handler(message: types.Message,
state: FSMContext,
i18n_data: dict,
settings: Settings,
panel_service: PanelApiService):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing")
await message.reply("Language error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
input_text = message.text.strip()
user_to_unban_data: Optional[aiosqlite.Row] = None
user_id_or_username_for_msg = input_text
if input_text.isdigit():
try:
user_id_to_unban_val = int(input_text)
user_to_unban_data = await get_user(user_id_to_unban_val)
except ValueError:
await message.answer(_("admin_invalid_user_id_format"))
return
elif input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]):
user_to_unban_data = await get_user_by_telegram_username(input_text[1:]
)
elif USERNAME_REGEX.match(input_text):
user_to_unban_data = await get_user_by_telegram_username(input_text)
else:
await message.answer(
_("admin_invalid_user_id_format") + " " +
_("admin_invalid_username_format"))
return
if not user_to_unban_data:
await message.answer(
_("admin_user_not_found_in_bot_db", user_id=input_text))
await state.clear()
return
user_id_to_unban = user_to_unban_data['user_id']
user_id_or_username_for_msg = f"@{user_to_unban_data['username']}" if user_to_unban_data.get(
'username') else str(user_id_to_unban)
if not user_to_unban_data['is_banned']:
await message.answer(
_("admin_user_not_banned",
user_id_or_username=user_id_or_username_for_msg))
await state.clear()
return
panel_user_uuid = user_to_unban_data[
'panel_user_uuid'] if user_to_unban_data and 'panel_user_uuid' in user_to_unban_data.keys(
) and user_to_unban_data['panel_user_uuid'] else None
await set_user_ban_status_db(user_id_to_unban, is_banned=False)
reply_markup_val = get_back_to_admin_panel_keyboard(current_lang, i18n)
if panel_user_uuid:
panel_unban_success = await panel_service.update_user_status_on_panel(
panel_user_uuid, enable=True)
if panel_unban_success:
await message.answer(_(
"admin_user_unbanned_success_panel_too",
user_id_or_username=user_id_or_username_for_msg),
reply_markup=reply_markup_val)
else:
await message.answer(_(
"admin_user_unbanned_local_panel_fail",
user_id_or_username=user_id_or_username_for_msg),
reply_markup=reply_markup_val)
else:
await message.answer(_(
"admin_user_unbanned_local_no_panel_uuid",
user_id_or_username=user_id_or_username_for_msg),
reply_markup=reply_markup_val)
await state.clear()
async def view_banned_users_handler(callback: types.CallbackQuery,
i18n_data: dict, settings: Settings,
state: FSMContext):
await state.clear()
current_page = 0
if ":" in callback.data and callback.data.count(":") == 2:
try:
current_page = int(callback.data.split(":")[-1])
except ValueError:
current_page = 0
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
banned_users, total_banned = await get_banned_users_list_paginated(
limit=settings.LOGS_PAGE_SIZE,
offset=current_page * settings.LOGS_PAGE_SIZE)
if not callback.message:
await callback.answer("Error.")
return
if total_banned == 0:
await callback.message.edit_text(
_("admin_no_banned_users"),
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n))
else:
total_pages = math.ceil(
total_banned /
settings.LOGS_PAGE_SIZE) if settings.LOGS_PAGE_SIZE > 0 else 1
await callback.message.edit_text(
text=_("admin_banned_list_title",
current_page=current_page + 1,
total_pages=max(1, total_pages)),
reply_markup=get_banned_users_keyboard(banned_users, current_page,
total_banned, i18n,
current_lang, settings))
await callback.answer()
@router.callback_query(F.data.startswith("admin_user_card:"))
async def show_user_card_handler(callback: types.CallbackQuery,
i18n_data: dict,
settings: Settings,
panel_service: PanelApiService,
state: FSMContext,
force_user_id: Optional[int] = None,
force_page: Optional[int] = None):
await state.clear()
user_id_to_show = 0
banned_list_page_to_return = 0
if force_user_id is not None and force_page is not None:
user_id_to_show = force_user_id
banned_list_page_to_return = force_page
else:
try:
parts = callback.data.split(":")
user_id_to_show = int(parts[1])
banned_list_page_to_return = int(parts[2]) if len(parts) > 2 else 0
except (IndexError, ValueError):
await callback.answer("Invalid user card data.", show_alert=True)
return
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
user_data = await get_user(user_id_to_show)
if not callback.message:
await callback.answer("Error.")
return
if not user_data:
await callback.message.edit_text(
_("admin_user_not_found_in_bot_db", user_id=user_id_to_show),
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n))
await callback.answer()
return
user_display_name = user_data['first_name'] or (
f"@{user_data['username']}"
if user_data.get('username') else f"ID: {user_id_to_show}")
sub_end_date_str = await get_user_active_subscription_end_date(
user_id_to_show) or _("user_card_sub_na")
reg_date_from_db = user_data[
'registration_date_str'] if 'registration_date_str' in user_data.keys(
) else "N/A"
reg_date_display = reg_date_from_db[:
10] if reg_date_from_db and reg_date_from_db != "N/A" else "N/A"
card_text = _(
"user_card_info",
user_id=user_data['user_id'],
username=user_data.get('username', "N/A"),
first_name=user_data.get('first_name', ""),
last_name=user_data.get('last_name', ""),
language_code=user_data.get('language_code', "N/A"),
panel_user_uuid=user_data.get('panel_user_uuid', "N/A"),
ban_status=_(key="user_card_banned") if user_data['is_banned'] else _(
key="user_card_active"),
reg_date=reg_date_display,
sub_end_date=sub_end_date_str)
await callback.message.edit_text(
text=
f"{_('admin_user_card_title', user_display=user_display_name)}\n\n{card_text}",
reply_markup=get_user_card_keyboard(user_id_to_show,
bool(user_data['is_banned']), i18n,
current_lang,
banned_list_page_to_return),
parse_mode="HTML")
if force_user_id is None: await callback.answer()
@router.callback_query(F.data.startswith("admin_unban_confirm:"))
async def confirm_unban_handler(callback: types.CallbackQuery, i18n_data: dict,
settings: Settings):
try:
_, user_id_str, page_str = callback.data.split(":")
user_id = int(user_id_str)
banned_list_page = int(page_str)
except:
await callback.answer("Invalid data", show_alert=True)
return
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
user_data = await get_user(user_id)
user_display = (user_data['first_name'] or
(f"@{user_data['username']}" if user_data.get('username')
else f"ID {user_id}")) if user_data else f"ID {user_id}"
if callback.message:
await callback.message.edit_text(
text=_("admin_confirm_unban_prompt",
user_display=user_display,
user_id=user_id),
reply_markup=get_confirmation_keyboard(
yes_callback_data=
f"admin_unban_do:{user_id}:{banned_list_page}",
no_callback_data=
f"admin_user_card:{user_id}:{banned_list_page}",
i18n_instance=i18n,
lang=current_lang),
parse_mode="HTML")
await callback.answer()
@router.callback_query(F.data.startswith("admin_ban_confirm:"))
async def confirm_ban_handler(callback: types.CallbackQuery, i18n_data: dict,
settings: Settings):
try:
_, user_id_str, page_str = callback.data.split(":")
user_id = int(user_id_str)
banned_list_page = int(page_str)
except:
await callback.answer("Invalid data", show_alert=True)
return
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
user_data = await get_user(user_id)
user_display = (user_data['first_name'] or
(f"@{user_data['username']}" if user_data.get('username')
else f"ID {user_id}")) if user_data else f"ID {user_id}"
if callback.message:
await callback.message.edit_text(
text=_("admin_confirm_ban_prompt",
user_display=user_display,
user_id=user_id),
reply_markup=get_confirmation_keyboard(
yes_callback_data=f"admin_ban_do:{user_id}:{banned_list_page}",
no_callback_data=
f"admin_user_card:{user_id}:{banned_list_page}",
i18n_instance=i18n,
lang=current_lang),
parse_mode="HTML")
await callback.answer()
@router.callback_query(F.data.startswith("admin_unban_do:"))
async def do_unban_user_handler(callback: types.CallbackQuery, i18n_data: dict,
settings: Settings,
panel_service: PanelApiService,
state: FSMContext):
try:
_, user_id_str, page_str = callback.data.split(":")
user_id_to_unban = int(user_id_str)
banned_list_page = int(page_str)
except:
await callback.answer("Invalid data", show_alert=True)
return
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
user_to_unban_data = await get_user(user_id_to_unban)
user_display_name = (
user_to_unban_data['first_name'] or
(f"@{user_to_unban_data['username']}"
if user_to_unban_data.get('username') else f"ID {user_id_to_unban}")
) if user_to_unban_data else f"ID {user_id_to_unban}"
if not user_to_unban_data or not user_to_unban_data['is_banned']:
await callback.answer(_("admin_user_not_banned",
user_id_or_username=user_display_name),
show_alert=True)
if callback.message:
await show_user_card_handler(callback,
i18n_data,
settings,
panel_service,
state,
force_user_id=user_id_to_unban,
force_page=banned_list_page)
return
await set_user_ban_status_db(user_id_to_unban, is_banned=False)
panel_user_uuid = user_to_unban_data[
'panel_user_uuid'] if user_to_unban_data and 'panel_user_uuid' in user_to_unban_data.keys(
) and user_to_unban_data['panel_user_uuid'] else None
if panel_user_uuid:
if not await panel_service.update_user_status_on_panel(panel_user_uuid,
enable=True):
logging.warning(
f"Panel status update fail for unban {user_id_to_unban}")
await callback.answer(_("admin_user_unbanned_from_card",
user_display=user_display_name,
user_id=user_id_to_unban),
show_alert=False)
if callback.message:
await show_user_card_handler(callback,
i18n_data,
settings,
panel_service,
state,
force_user_id=user_id_to_unban,
force_page=banned_list_page)
@router.callback_query(F.data.startswith("admin_ban_do:"))
async def do_ban_user_handler(callback: types.CallbackQuery, i18n_data: dict,
settings: Settings,
panel_service: PanelApiService,
state: FSMContext):
try:
_, user_id_str, page_str = callback.data.split(":")
user_id_to_ban = int(user_id_str)
banned_list_page = int(page_str)
except:
await callback.answer("Invalid data", show_alert=True)
return
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
user_to_ban_data = await get_user(user_id_to_ban)
user_display_name = (
user_to_ban_data['first_name'] or
(f"@{user_to_ban_data['username']}"
if user_to_ban_data.get('username') else f"ID {user_id_to_ban}")
) if user_to_ban_data else f"ID {user_id_to_ban}"
if not user_to_ban_data:
await callback.answer(_("admin_user_not_found_in_bot_db",
user_id=user_id_to_ban),
show_alert=True)
return
if user_to_ban_data['is_banned']:
await callback.answer(_("admin_user_already_banned",
user_id_or_username=user_display_name),
show_alert=True)
return
if user_id_to_ban == callback.from_user.id or user_id_to_ban in settings.ADMIN_IDS:
await callback.answer(_("admin_cannot_ban_self_or_admin"),
show_alert=True)
return
await set_user_ban_status_db(user_id_to_ban, is_banned=True)
panel_user_uuid = user_to_ban_data[
'panel_user_uuid'] if user_to_ban_data and 'panel_user_uuid' in user_to_ban_data.keys(
) and user_to_ban_data['panel_user_uuid'] else None
if panel_user_uuid:
if not await panel_service.update_user_status_on_panel(panel_user_uuid,
enable=False):
logging.warning(
f"Panel status update fail for ban {user_id_to_ban}")
await callback.answer(_("admin_user_banned_from_card",
user_display=user_display_name,
user_id=user_id_to_ban),
show_alert=False)
if callback.message:
await show_user_card_handler(callback,
i18n_data,
settings,
panel_service,
state,
force_user_id=user_id_to_ban,
force_page=banned_list_page)
@router.callback_query(F.data == "admin_action:main",
AdminStates.waiting_for_user_id_to_ban)
@router.callback_query(F.data == "admin_action:main",
AdminStates.waiting_for_user_id_to_unban)
async def cancel_user_management_input_state(callback: types.CallbackQuery,
state: FSMContext,
settings: Settings,
i18n_data: dict, bot: Bot):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
if callback.message:
try:
await callback.message.edit_text(
_("admin_action_cancelled_default"),
reply_markup=get_admin_panel_keyboard(i18n, current_lang,
settings))
except:
await callback.message.answer(
_("admin_action_cancelled_default"),
reply_markup=get_admin_panel_keyboard(i18n, current_lang,
settings))
await callback.answer(_("admin_action_cancelled_default"))
await state.clear()