Files
remnawave-minishop/bot/main_bot.py
T
2025-05-14 08:43:23 +00:00

295 lines
13 KiB
Python

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.")