Initial commit
This commit is contained in:
+171
@@ -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.")
|
||||
Reference in New Issue
Block a user