merge from remote

This commit is contained in:
Alexander Kalinovsky
2025-02-13 02:13:22 +01:00
2 changed files with 110 additions and 76 deletions

View File

@@ -1,74 +0,0 @@
from aiogram.types import BotCommand
from contextlib import asynccontextmanager
from .main import QBotApp
from logging import getLogger
logger = getLogger(__name__)
@asynccontextmanager
async def default_lifespan(app: QBotApp):
logger.debug("starting qbot app")
if app.config.USE_NGROK:
try:
from pyngrok import ngrok
from pyngrok.conf import PyngrokConfig
except ImportError:
logger.error("pyngrok is not installed")
raise
tunnel = ngrok.connect(
app.config.API_PORT,
pyngrok_config=PyngrokConfig(auth_token=app.config.NGROK_AUTH_TOKEN),
)
app.config.NGROK_URL = tunnel.public_url
commands_captions = dict[str, list[tuple[str, str]]]()
for command_name, command in app.bot_commands.items():
if command.show_in_bot_commands:
if isinstance(command.caption, str) or command.caption is None:
if "default" not in commands_captions:
commands_captions["default"] = []
commands_captions["default"].append(
(command_name, command.caption or command_name)
)
else:
for locale, description in command.caption.items():
if locale not in commands_captions:
commands_captions[locale] = []
commands_captions[locale].append((command_name, description))
for locale, commands in commands_captions.items():
await app.bot.set_my_commands(
[
BotCommand(command=command[0], description=command[1])
for command in commands
],
language_code=None if locale == "default" else locale,
)
await app.bot.set_webhook(
url=f"{app.config.API_URL}/api/telegram/webhook",
drop_pending_updates=True,
allowed_updates=["message", "callback_query", "pre_checkout_query"],
secret_token=app.bot_auth_token,
)
logger.info("qbot app started")
if app.lifespan:
async with app.lifespan(app):
yield
else:
yield
logger.info("stopping qbot app")
await app.bot.delete_webhook()
if app.config.USE_NGROK:
ngrok.disconnect(app.config.NGROK_URL)
ngrok.kill()
logger.info("qbot app stopped")

112
main.py
View File

@@ -1,12 +1,14 @@
from contextlib import asynccontextmanager
from typing import Callable, Any from typing import Callable, Any
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties from aiogram.client.default import DefaultBotProperties
from aiogram.types import Message from aiogram.types import Message, BotCommand as AiogramBotCommand
from aiogram.utils.callback_answer import CallbackAnswerMiddleware from aiogram.utils.callback_answer import CallbackAnswerMiddleware
from aiogram.utils.i18n import I18n from aiogram.utils.i18n import I18n
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.applications import Lifespan, AppType from fastapi.applications import Lifespan, AppType
from secrets import token_hex from secrets import token_hex
from logging import getLogger
from .config import Config from .config import Config
from .fsm.db_storage import DbStorage from .fsm.db_storage import DbStorage
@@ -17,6 +19,38 @@ from .model.descriptors import BotCommand
from .router import Router from .router import Router
logger = getLogger(__name__)
@asynccontextmanager
async def default_lifespan(app: "QBotApp"):
logger.debug("starting qbot app")
if app.lifespan_bot_init:
if app.config.USE_NGROK:
app.ngrok_init()
await app.bot_init()
logger.info("qbot app started")
if app.lifespan:
async with app.lifespan(app):
yield
else:
yield
logger.info("stopping qbot app")
if app.lifespan_bot_init:
await app.bot_close()
if app.config.USE_NGROK:
app.ngrok_stop()
logger.info("qbot app stopped")
class QBotApp[UserType: UserBase](FastAPI): class QBotApp[UserType: UserBase](FastAPI):
""" """
Main class for the QBot application Main class for the QBot application
@@ -35,6 +69,8 @@ class QBotApp[UserType: UserBase](FastAPI):
None, None,
] = None, ] = None,
lifespan: Lifespan[AppType] | None = None, lifespan: Lifespan[AppType] | None = None,
lifespan_bot_init: bool = True,
allowed_updates: list[str] | None = None,
*args, *args,
**kwargs, **kwargs,
): ):
@@ -46,6 +82,8 @@ class QBotApp[UserType: UserBase](FastAPI):
user_class = DefaultUser user_class = DefaultUser
self.allowed_updates = allowed_updates or ["message", "callback_query"]
self.user_class = user_class self.user_class = user_class
self.entity_metadata: EntityMetadata = user_class.entity_metadata self.entity_metadata: EntityMetadata = user_class.entity_metadata
self.config = config self.config = config
@@ -80,7 +118,7 @@ class QBotApp[UserType: UserBase](FastAPI):
self.start_handler = bot_start self.start_handler = bot_start
self.bot_commands = dict[str, BotCommand]() self.bot_commands = dict[str, BotCommand]()
from .lifespan import default_lifespan self.lifespan_bot_init = lifespan_bot_init
super().__init__(lifespan=default_lifespan, *args, **kwargs) super().__init__(lifespan=default_lifespan, *args, **kwargs)
@@ -91,7 +129,77 @@ class QBotApp[UserType: UserBase](FastAPI):
self.root_router._commands = self.bot_commands self.root_router._commands = self.bot_commands
self.command = self.root_router.command self.command = self.root_router.command
def register_routers(self, *routers: Router): def register_routers(self, *routers: Router):
for router in routers: for router in routers:
for command_name, command in router._commands.items(): for command_name, command in router._commands.items():
self.bot_commands[command_name] = command self.bot_commands[command_name] = command
def ngrok_init(self):
try:
from pyngrok import ngrok
from pyngrok.conf import PyngrokConfig
except ImportError:
logger.error("pyngrok is not installed")
raise
tunnel = ngrok.connect(
self.config.API_PORT,
pyngrok_config=PyngrokConfig(auth_token=self.config.NGROK_AUTH_TOKEN),
)
self.config.NGROK_URL = tunnel.public_url
def ngrok_stop(self):
try:
from pyngrok import ngrok
except ImportError:
logger.error("pyngrok is not installed")
raise
ngrok.disconnect(self.config.NGROK_URL)
ngrok.kill()
async def bot_init(self):
commands_captions = dict[str, list[tuple[str, str]]]()
for command_name, command in self.bot_commands.items():
if command.show_in_bot_commands:
if isinstance(command.caption, str) or command.caption is None:
if "default" not in commands_captions:
commands_captions["default"] = []
commands_captions["default"].append(
(command_name, command.caption or command_name)
)
else:
for locale, description in command.caption.items():
if locale not in commands_captions:
commands_captions[locale] = []
commands_captions[locale].append((command_name, description))
for locale, commands in commands_captions.items():
await self.bot.set_my_commands(
[
AiogramBotCommand(command=command[0], description=command[1])
for command in commands
],
language_code=None if locale == "default" else locale,
)
await self.bot.set_webhook(
url=f"{self.config.API_URL}/api/telegram/webhook",
drop_pending_updates=True,
allowed_updates=self.allowed_updates,
secret_token=self.bot_auth_token,
)
async def bot_close(self):
await self.bot.delete_webhook()