Initial commit

This commit is contained in:
machka
2025-05-14 08:11:59 +00:00
commit 77584b5b51
41 changed files with 5205 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
import logging
import asyncio
from aiogram import Bot ,Dispatcher ,Router ,F
from aiogram .enums import ParseMode
from aiogram .filters import CommandStart ,Command
from aiogram .client .default import DefaultBotProperties
from aiogram .webhook .aiohttp_server import SimpleRequestHandler ,setup_application
from aiogram .fsm .storage .memory import MemoryStorage
from aiohttp import web
from apscheduler .schedulers .asyncio import AsyncIOScheduler
from config .settings import Settings ,get_settings
from .middlewares .i18n import I18nMiddleware ,get_i18n_instance ,JsonI18n
from .middlewares .ban_check_middleware import BanCheckMiddleware
from .middlewares .action_logger_middleware import ActionLoggerMiddleware
from .handlers .user import user_router_aggregate
from .handlers .user import payment as user_payment_webhook_module
from .handlers .admin import admin_router_aggregate
from .filters .admin_filter import AdminFilter
from db .database import get_db_connection_manager
from .services .notification_service import schedule_subscription_notifications
from .services .payment_service import YooKassaService
from .services .panel_api_service import PanelApiService
from .services .subscription_service import SubscriptionService
from .services .referral_service import ReferralService
from .services .promo_code_service import PromoCodeService
async def register_all_routers (dp :Dispatcher ,settings :Settings ):
dp .include_router (user_router_aggregate )
admin_filtered_router_wrapper =Router (name ="admin_filtered_router_wrapper")
admin_filter_instance =AdminFilter (admin_ids =settings .ADMIN_IDS )
admin_filtered_router_wrapper .message .filter (admin_filter_instance )
admin_filtered_router_wrapper .callback_query .filter (admin_filter_instance )
admin_filtered_router_wrapper .include_router (admin_router_aggregate )
dp .include_router (admin_filtered_router_wrapper )
logging .info ("All application routers registered.")
async def on_startup_configured (dispatcher :Dispatcher ):
bot :Bot =dispatcher ["bot_instance"];settings :Settings =dispatcher ["settings"];i18n_instance :JsonI18n =dispatcher ["i18n_instance"]
logging .info ("STARTUP: on_startup_configured executing...")
scheduler =AsyncIOScheduler (timezone ="UTC")
try :await schedule_subscription_notifications (bot ,settings ,i18n_instance ,scheduler );scheduler .start ();dispatcher ["scheduler"]=scheduler ;logging .info ("STARTUP: APScheduler started.")
except Exception as e :logging .error (f"STARTUP: Failed to start APScheduler: {e}",exc_info =True )
telegram_webhook_url_to_set =getattr (settings ,'TELEGRAM_WEBHOOK_BASE_URL',None )
if telegram_webhook_url_to_set :
if settings .BOT_TOKEN in telegram_webhook_url_to_set :logging .error (f"CRITICAL: Bot token in TELEGRAM_WEBHOOK_BASE_URL ('{telegram_webhook_url_to_set}').");full_telegram_webhook_url ="ERROR_URL"
else :full_telegram_webhook_url =f"{str(telegram_webhook_url_to_set).rstrip('/')}/{settings.BOT_TOKEN}"
logging .info (f"STARTUP: Attempting to set Telegram webhook to: {full_telegram_webhook_url}")
try :
current_webhook_info_before =await bot .get_webhook_info ();logging .info (f"STARTUP: Current webhook info BEFORE: {current_webhook_info_before.model_dump_json(exclude_none=True, indent=2)}")
if full_telegram_webhook_url !="ERROR_URL":
set_success =await bot .set_webhook (url =full_telegram_webhook_url ,drop_pending_updates =True ,allowed_updates =dispatcher .resolve_used_update_types ())
if set_success :logging .info (f"STARTUP: bot.set_webhook to {full_telegram_webhook_url} returned SUCCESS (True).")
else :logging .error (f"STARTUP: bot.set_webhook to {full_telegram_webhook_url} returned FAILURE (False).")
new_webhook_info =await bot .get_webhook_info ();logging .info (f"STARTUP: Webhook info AFTER: {new_webhook_info.model_dump_json(exclude_none=True, indent=2)}")
if not new_webhook_info .url :logging .error ("STARTUP: CRITICAL - Webhook URL EMPTY after set attempt.")
else :logging .error ("STARTUP: Skipped setting webhook due to URL config error.")
except Exception as e_setwebhook :logging .error (f"STARTUP: EXCEPTION during set/get Telegram webhook: {e_setwebhook}",exc_info =True )
else :
logging .info ("STARTUP: TELEGRAM_WEBHOOK_BASE_URL not set. Attempting to delete webhook.");await bot .delete_webhook (drop_pending_updates =True )
logging .info ("STARTUP: Bot on_startup_configured completed.")
async def on_shutdown_configured (dispatcher :Dispatcher ):
logging .warning ("SHUTDOWN: on_shutdown_configured executing...");scheduler :AsyncIOScheduler =dispatcher .get ("scheduler")
if scheduler and scheduler .running :
try :scheduler .shutdown (wait =False );logging .info ("SHUTDOWN: APScheduler shut down.")
except Exception as e :logging .error (f"SHUTDOWN: Error APScheduler: {e}",exc_info =True )
panel_service :PanelApiService =dispatcher .get ("panel_service")
if panel_service and panel_service ._session and not panel_service ._session .closed :await panel_service .close_session ()
bot :Bot =dispatcher .get ("bot_instance")
if bot and bot .session and not bot .session .closed :await bot .session .close ()
logging .info ("SHUTDOWN: Bot on_shutdown_configured completed.")
async def run_bot (settings_param :Settings ):
storage =MemoryStorage ()
default_props =DefaultBotProperties (parse_mode =ParseMode .HTML )
bot =Bot (token =settings_param .BOT_TOKEN ,default =default_props )
dp =Dispatcher (storage =storage ,settings =settings_param ,bot_instance =bot )
actual_bot_username ="your_bot_username"
try :
bot_info =await bot .get_me ()
actual_bot_username =bot_info .username
logging .info (f"Bot username: @{actual_bot_username}")
except Exception as e :
logging .error (f"Failed to get bot info: {e}. Using fallback username: {actual_bot_username}")
default_lang =settings_param .DEFAULT_LANGUAGE
i18n_instance =get_i18n_instance (path ="locales",default =default_lang )
yookassa_service =YooKassaService (
shop_id =settings_param .YOOKASSA_SHOP_ID ,
secret_key =settings_param .YOOKASSA_SECRET_KEY ,
configured_return_url =settings_param .YOOKASSA_RETURN_URL ,
bot_username_for_default =actual_bot_username ,
settings_obj =settings_param
)
panel_service =PanelApiService (settings_param )
subscription_service =SubscriptionService (get_db_connection_manager ,settings_param ,panel_service )
referral_service =ReferralService (get_db_connection_manager ,settings_param ,subscription_service ,bot ,i18n_instance )
promo_code_service =PromoCodeService (get_db_connection_manager ,settings_param ,subscription_service ,bot ,i18n_instance )
dp ["i18n_instance"]=i18n_instance
dp ["i18n_data"]={"i18n_instance":i18n_instance ,"current_language":default_lang }
dp ["yookassa_service"]=yookassa_service
dp ["panel_service"]=panel_service
dp ["subscription_service"]=subscription_service
dp ["referral_service"]=referral_service
dp ["promo_code_service"]=promo_code_service
dp .update .outer_middleware (I18nMiddleware (i18n =i18n_instance ,settings =settings_param ))
dp .update .outer_middleware (BanCheckMiddleware (settings =settings_param ,i18n_instance =i18n_instance ))
dp .update .outer_middleware (ActionLoggerMiddleware (settings =settings_param ))
dp .startup .register (on_startup_configured )
dp .shutdown .register (on_shutdown_configured )
await register_all_routers (dp ,settings_param )
tg_webhook_base =getattr (settings_param ,'TELEGRAM_WEBHOOK_BASE_URL',None );yk_webhook_base =getattr (settings_param ,'YOOKASSA_WEBHOOK_BASE_URL',None )
logging .info (f"--- Determining Run Mode ---");logging .info (f"Configured TELEGRAM_WEBHOOK_BASE_URL: '{tg_webhook_base}'");logging .info (f"Configured YOOKASSA_WEBHOOK_BASE_URL: '{yk_webhook_base}'")
should_run_aiohttp =bool (yk_webhook_base and settings_param .yookassa_webhook_path )or bool (tg_webhook_base )
telegram_uses_webhook =bool (tg_webhook_base );telegram_should_poll =not telegram_uses_webhook
logging .info (f"Decision: Run AIOHTTP server: {should_run_aiohttp}");logging .info (f"Decision: Telegram uses webhook: {telegram_uses_webhook}");logging .info (f"Decision: Telegram should poll: {telegram_should_poll}");logging .info (f"--- End Run Mode Decision ---")
web_app_runner =None ;main_tasks_to_await =[]
if should_run_aiohttp :
app =web .Application ();app ['bot']=bot ;app ['dp']=dp ;app ['settings']=settings_param ;app ['i18n']=i18n_instance
app ['yookassa_service']=yookassa_service ;app ['panel_service']=panel_service ;app ['subscription_service']=subscription_service
app ['referral_service']=referral_service ;app ['promo_code_service']=promo_code_service
setup_application (app ,dp ,bot =bot );logging .info ("AIOHTTP app context populated and dispatcher lifecycle linked.")
if telegram_uses_webhook :telegram_webhook_path =f"/{settings_param.BOT_TOKEN}";app .router .add_post (telegram_webhook_path ,SimpleRequestHandler (dispatcher =dp ,bot =bot ));logging .info (f"Telegram webhook route: {telegram_webhook_path}.")
if yk_webhook_base and settings_param .yookassa_webhook_path :app .router .add_post (settings_param .yookassa_webhook_path ,user_payment_webhook_module .yookassa_webhook_route );logging .info (f"YK webhook route: {settings_param.yookassa_webhook_path}")
web_app_runner =web .AppRunner (app );await web_app_runner .setup ();site =web .TCPSite (web_app_runner ,host =settings_param .WEB_SERVER_HOST ,port =settings_param .WEB_SERVER_PORT )
async def web_server_task_wrapper ():await site .start ();logging .info (f"AIOHTTP server started on {settings_param.WEB_SERVER_HOST}:{settings_param.WEB_SERVER_PORT}.");await asyncio .Event ().wait ()if not telegram_should_poll else await asyncio .sleep (31536000 )
main_tasks_to_await .append (asyncio .create_task (web_server_task_wrapper (),name ="AIOHTTPServerWrapperTask"))
if telegram_should_poll :logging .info ("TG polling task created.");main_tasks_to_await .append (asyncio .create_task (dp .start_polling (bot ,allowed_updates =dp .resolve_used_update_types ()),name ="TelegramPollingTask"))
if not main_tasks_to_await :logging .error ("Bot not configured for any mode.");await dp .emit_shutdown ();return
logging .info (f"Starting bot with main tasks: {[task.get_name() for task in main_tasks_to_await]}")
try :
if main_tasks_to_await :await asyncio .gather (*main_tasks_to_await )
except (KeyboardInterrupt ,SystemExit ,asyncio .CancelledError )as e :logging .info (f"Main loop interrupted: {type(e).__name__}")
finally :
logging .info ("Initiating final shutdown sequence...");
for task in main_tasks_to_await :
if task and not task .done ():task .cancel ();
try :await task
except asyncio .CancelledError :logging .info (f"Task '{task.get_name()}' cancelled.")
except Exception as e_cancel :logging .error (f"Error cancelling task '{task.get_name()}': {e_cancel}",exc_info =True )
if web_app_runner :await web_app_runner .cleanup ();logging .info ("AIOHTTP AppRunner cleaned up.")
if not (telegram_should_poll and len (main_tasks_to_await )==1 and any (t .get_name ()=="TelegramPollingTask"for t in main_tasks_to_await if t and not t .done ())):
logging .info ("Explicitly calling dp.emit_shutdown() in run_bot finally.")
await dp .emit_shutdown ()
if bot .session and not bot .session .closed :await bot .session .close ()
logging .info ("Bot run_bot function finished.")