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

30
bot/i18n/__init__.py Normal file
View file

@ -0,0 +1,30 @@
from enum import Enum
from bot.i18n.strings import STRINGS
class Lang(Enum):
EN = "en"
RU = "ru"
def t(key: str, lang: Lang | str = Lang.EN, **kwargs: object) -> str:
if isinstance(lang, Lang):
lang_str = lang.value
elif lang in ("en", "ru"):
lang_str = lang
else:
lang_str = "en"
entry = STRINGS.get(key)
if not entry:
return key
text = entry.get(lang_str, entry.get("en", key))
if kwargs:
text = text.format(**kwargs)
return text
def detect_lang(language_code: str | None) -> Lang:
if language_code and language_code.startswith("ru"):
return Lang.RU
return Lang.EN

255
bot/i18n/strings.py Normal file
View file

@ -0,0 +1,255 @@
STRINGS: dict[str, dict[str, str]] = {
# /start, /help
"welcome": {
"en": (
"<b>Telegram Channel Analyzer</b>\n\n"
"Analyze any public Telegram channel using AI.\n\n"
"<b>Commands:</b>\n"
"<code>/analyze @channel</code> — Start analysis\n"
"<code>/lang en|ru</code> — Change language\n"
"<code>/features</code> — Available focus areas\n"
"<code>/prices</code> — Pricing info\n"
),
"ru": (
"<b>Анализатор Telegram-каналов</b>\n\n"
"Анализ любого публичного канала с помощью ИИ.\n\n"
"<b>Команды:</b>\n"
"<code>/analyze @channel</code> — Начать анализ\n"
"<code>/lang en|ru</code> — Сменить язык\n"
"<code>/features</code> — Доступные области анализа\n"
"<code>/prices</code> — Информация о ценах\n"
),
},
# /analyze
"provide_channel": {
"en": "Please provide a channel: <code>/analyze @channel</code>",
"ru": "Укажите канал: <code>/analyze @канал</code>",
},
"bad_channel": {
"en": "Could not parse channel name. Use @username or t.me/username.",
"ru": "Не удалось разобрать имя канала. Используйте @username или t.me/username.",
},
"choose_depth": {
"en": "Channel: <b>@{channel}</b>\n\nChoose analysis depth:",
"ru": "Канал: <b>@{channel}</b>\n\nВыберите глубину анализа:",
},
"choose_focus": {
"en": "Select focus areas (tap to toggle, then <b>Done</b>):",
"ru": "Выберите области анализа (нажмите для выбора, затем <b>Готово</b>):",
},
"choose_model": {
"en": "Choose AI model:",
"ru": "Выберите модель ИИ:",
},
"session_expired": {
"en": "Session expired. Please run /analyze again.",
"ru": "Сессия истекла. Запустите /analyze снова.",
},
"no_focus_selected": {
"en": "Please select at least one focus area.",
"ru": "Выберите хотя бы одну область анализа.",
},
# Depth labels
"depth_basic": {
"en": "Basic (fast)",
"ru": "Базовый (быстрый)",
},
"depth_standard": {
"en": "Standard",
"ru": "Стандартный",
},
"depth_full": {
"en": "Full (detailed)",
"ru": "Полный (детальный)",
},
# Focus area labels
"focus_psychology": {
"en": "Psychology & Influence",
"ru": "Психология и влияние",
},
"focus_business": {
"en": "Business & Monetization",
"ru": "Бизнес и монетизация",
},
"focus_marketing": {
"en": "Marketing & Growth",
"ru": "Маркетинг и рост",
},
"focus_content": {
"en": "Content Strategy",
"ru": "Контент-стратегия",
},
"focus_audience": {
"en": "Audience & Engagement",
"ru": "Аудитория и вовлечённость",
},
"focus_sentiment": {
"en": "Sentiment Analysis",
"ru": "Анализ тональности",
},
# Analysis progress
"fetching": {
"en": "Fetching messages...",
"ru": "Загрузка сообщений...",
},
"fetched_n": {
"en": "Fetched {n} messages. Chunking...",
"ru": "Загружено {n} сообщений. Разбиение...",
},
"chunked": {
"en": "{n} messages in {c} chunks. Analyzing...",
"ru": "{n} сообщений в {c} частях. Анализ...",
},
"analyzing_chunk": {
"en": "Analyzing chunk {i}/{total}...",
"ru": "Анализ части {i}/{total}...",
},
"chunk_done_cooldown": {
"en": "Chunk {i}/{total} done. Cooling down 60s...",
"ru": "Часть {i}/{total} готова. Пауза 60с...",
},
"generating_report": {
"en": "Generating final report...",
"ru": "Генерация финального отчёта...",
},
"sending_report": {
"en": "Sending report...",
"ru": "Отправка отчёта...",
},
"analysis_starting": {
"en": "Starting analysis for @{channel}...\n\n{status}",
"ru": "Начинаю анализ @{channel}...\n\n{status}",
},
# Errors
"channel_private": {
"en": "Channel is private or does not exist.",
"ru": "Канал приватный или не существует.",
},
"channel_not_found": {
"en": "Channel username not found.",
"ru": "Имя канала не найдено.",
},
"flood_wait": {
"en": "Rate limited by Telegram. Retry in {s}s.",
"ru": "Ограничение Telegram. Повторите через {s}с.",
},
"fetch_failed": {
"en": "Failed to fetch channel: {e}",
"ru": "Ошибка получения канала: {e}",
},
"no_messages": {
"en": "No text messages found in this channel.",
"ru": "В канале не найдено текстовых сообщений.",
},
"analysis_failed": {
"en": "Analysis failed: {e}",
"ru": "Ошибка анализа: {e}",
},
"internal_error": {
"en": "Internal error: Telethon client not configured.",
"ru": "Внутренняя ошибка: клиент Telethon не настроен.",
},
"rate_limited": {
"en": "Rate limited, waiting {s}s...",
"ru": "Лимит запросов, ожидание {s}с...",
},
# Payment
"free_analysis": {
"en": "This analysis is free ({used}/{max} free trial).",
"ru": "Этот анализ бесплатный ({used}/{max} пробный).",
},
"payment_required": {
"en": "This analysis costs {price} Stars.",
"ru": "Этот анализ стоит {price} Stars.",
},
"invoice_title": {
"en": "Channel Analysis — {depth}",
"ru": "Анализ канала — {depth}",
},
"invoice_description": {
"en": "Analysis of @{channel} ({depth} depth)",
"ru": "Анализ @{channel} ({depth})",
},
"payment_success": {
"en": "Payment received! Starting analysis...",
"ru": "Оплата получена! Начинаю анализ...",
},
# /lang
"lang_set": {
"en": "Language set to English.",
"ru": "Язык установлен: Русский.",
},
"lang_usage": {
"en": "Usage: <code>/lang en</code> or <code>/lang ru</code>",
"ru": "Использование: <code>/lang en</code> или <code>/lang ru</code>",
},
# /features
"features_title": {
"en": "<b>Available Focus Areas</b>\n",
"ru": "<b>Доступные области анализа</b>\n",
},
"features_psychology": {
"en": "<b>Psychology & Influence</b> — Persuasion techniques, cognitive biases, emotional triggers",
"ru": "<b>Психология и влияние</b> — Техники убеждения, когнитивные искажения, эмоциональные триггеры",
},
"features_business": {
"en": "<b>Business & Monetization</b> — Revenue models, product placement, conversion patterns",
"ru": "<b>Бизнес и монетизация</b> — Модели доходов, продвижение продуктов, воронки конверсии",
},
"features_marketing": {
"en": "<b>Marketing & Growth</b> — Growth tactics, viral mechanics, audience acquisition",
"ru": "<b>Маркетинг и рост</b> — Тактики роста, вирусные механики, привлечение аудитории",
},
"features_content": {
"en": "<b>Content Strategy</b> — Topics, formats, posting patterns, narrative arcs",
"ru": "<b>Контент-стратегия</b> — Темы, форматы, паттерны публикаций, нарративы",
},
"features_audience": {
"en": "<b>Audience & Engagement</b> — Interaction patterns, community dynamics, engagement drivers",
"ru": "<b>Аудитория и вовлечённость</b> — Паттерны взаимодействия, динамика сообщества",
},
"features_sentiment": {
"en": "<b>Sentiment Analysis</b> — Tone distribution, emotional shifts, sentiment by topic",
"ru": "<b>Анализ тональности</b> — Распределение тона, эмоциональные сдвиги, тональность по темам",
},
# /prices
"prices": {
"en": (
"<b>Pricing</b>\n\n"
"First analysis is free!\n\n"
"• <b>Basic</b> — {basic} Stars\n"
"• <b>Standard</b> — {standard} Stars\n"
"• <b>Full</b> — {full} Stars\n\n"
"Payment via Telegram Stars."
),
"ru": (
"<b>Цены</b>\n\n"
"Первый анализ бесплатно!\n\n"
"• <b>Базовый</b> — {basic} Stars\n"
"• <b>Стандартный</b> — {standard} Stars\n"
"• <b>Полный</b> — {full} Stars\n\n"
"Оплата через Telegram Stars."
),
},
# Report download
"download_report": {
"en": "Download report",
"ru": "Скачать отчёт",
},
# Done button
"done": {
"en": "Done",
"ru": "Готово",
},
}