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:
parent
8a84145e15
commit
c30b7e4675
27 changed files with 1155 additions and 280 deletions
30
bot/i18n/__init__.py
Normal file
30
bot/i18n/__init__.py
Normal 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
255
bot/i18n/strings.py
Normal 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": "Готово",
|
||||
},
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue