Add multi-user features: i18n, payments, multi-model AI, focus areas

Replace single report type with composable analysis: depth levels
(basic/standard/full) + focus area multi-select (psychology, business,
marketing, content, audience, sentiment). Multi-step inline keyboard
flow guides users through selection.

- i18n: English + Russian, auto-detect from Telegram, /lang override
- AI providers: Anthropic + OpenRouter via AIClient abstraction
- Telegram Stars payments with per-depth pricing and free trial
- SQLite (aiosqlite) for users, analyses, payments tracking
- User middleware for auto-registration and language detection
- Report persistence: save .md locally, offer file download
- New commands: /features, /prices, /lang
- Composable prompt system: depth modifiers + focus area fragments
This commit is contained in:
Sergei Poljanski 2026-02-23 01:18:12 +02:00
commit c30b7e4675
27 changed files with 1155 additions and 280 deletions

View file

@ -0,0 +1,41 @@
from typing import Any, Awaitable, Callable
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject, Update
from bot.db import user_repo
from bot.i18n import Lang, detect_lang
class UserMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: dict[str, Any],
) -> Any:
user = None
if isinstance(event, Update):
if event.message and event.message.from_user:
user = event.message.from_user
elif event.callback_query and event.callback_query.from_user:
user = event.callback_query.from_user
elif event.pre_checkout_query and event.pre_checkout_query.from_user:
user = event.pre_checkout_query.from_user
elif hasattr(event, "from_user") and event.from_user:
user = event.from_user
if user:
db_user = await user_repo.get_or_create(
telegram_id=user.id,
username=user.username,
first_name=user.first_name,
lang=detect_lang(user.language_code).value,
)
data["lang"] = Lang(db_user["lang"])
data["db_user"] = db_user
else:
data["lang"] = Lang.EN
data["db_user"] = None
return await handler(event, data)