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
0
bot/db/__init__.py
Normal file
0
bot/db/__init__.py
Normal file
54
bot/db/engine.py
Normal file
54
bot/db/engine.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import aiosqlite
|
||||
|
||||
from bot.config import settings
|
||||
|
||||
_db: aiosqlite.Connection | None = None
|
||||
|
||||
SCHEMA = """\
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
telegram_id INTEGER PRIMARY KEY,
|
||||
username TEXT,
|
||||
first_name TEXT,
|
||||
lang TEXT NOT NULL DEFAULT 'en',
|
||||
free_used INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS analyses (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
telegram_id INTEGER NOT NULL REFERENCES users(telegram_id),
|
||||
channel TEXT NOT NULL,
|
||||
depth TEXT NOT NULL,
|
||||
focus_areas TEXT NOT NULL,
|
||||
model_id TEXT NOT NULL,
|
||||
stars_paid INTEGER NOT NULL DEFAULT 0,
|
||||
report_path TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS payments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
telegram_id INTEGER NOT NULL REFERENCES users(telegram_id),
|
||||
telegram_payment_id TEXT NOT NULL UNIQUE,
|
||||
stars_amount INTEGER NOT NULL,
|
||||
analysis_id INTEGER REFERENCES analyses(id),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
"""
|
||||
|
||||
|
||||
async def get_db() -> aiosqlite.Connection:
|
||||
global _db
|
||||
if _db is None:
|
||||
_db = await aiosqlite.connect(settings.db_path)
|
||||
_db.row_factory = aiosqlite.Row
|
||||
await _db.executescript(SCHEMA)
|
||||
await _db.commit()
|
||||
return _db
|
||||
|
||||
|
||||
async def close_db() -> None:
|
||||
global _db
|
||||
if _db is not None:
|
||||
await _db.close()
|
||||
_db = None
|
||||
46
bot/db/usage_repo.py
Normal file
46
bot/db/usage_repo.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import json
|
||||
|
||||
from bot.db.engine import get_db
|
||||
|
||||
|
||||
async def record_analysis(
|
||||
telegram_id: int,
|
||||
channel: str,
|
||||
depth: str,
|
||||
focus_areas: list[str],
|
||||
model_id: str,
|
||||
stars_paid: int = 0,
|
||||
report_path: str | None = None,
|
||||
) -> int:
|
||||
db = await get_db()
|
||||
cursor = await db.execute(
|
||||
"INSERT INTO analyses (telegram_id, channel, depth, focus_areas, model_id, stars_paid, report_path) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
(telegram_id, channel, depth, json.dumps(focus_areas), model_id, stars_paid, report_path),
|
||||
)
|
||||
await db.commit()
|
||||
return cursor.lastrowid
|
||||
|
||||
|
||||
async def update_report_path(analysis_id: int, report_path: str) -> None:
|
||||
db = await get_db()
|
||||
await db.execute(
|
||||
"UPDATE analyses SET report_path = ? WHERE id = ?", (report_path, analysis_id)
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def record_payment(
|
||||
telegram_id: int,
|
||||
telegram_payment_id: str,
|
||||
stars_amount: int,
|
||||
analysis_id: int | None = None,
|
||||
) -> int:
|
||||
db = await get_db()
|
||||
cursor = await db.execute(
|
||||
"INSERT INTO payments (telegram_id, telegram_payment_id, stars_amount, analysis_id) "
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
(telegram_id, telegram_payment_id, stars_amount, analysis_id),
|
||||
)
|
||||
await db.commit()
|
||||
return cursor.lastrowid
|
||||
52
bot/db/user_repo.py
Normal file
52
bot/db/user_repo.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from bot.db.engine import get_db
|
||||
|
||||
|
||||
async def get_or_create(
|
||||
telegram_id: int,
|
||||
username: str | None = None,
|
||||
first_name: str | None = None,
|
||||
lang: str = "en",
|
||||
) -> dict:
|
||||
db = await get_db()
|
||||
row = await db.execute_fetchall(
|
||||
"SELECT * FROM users WHERE telegram_id = ?", (telegram_id,)
|
||||
)
|
||||
if row:
|
||||
return dict(row[0])
|
||||
|
||||
await db.execute(
|
||||
"INSERT INTO users (telegram_id, username, first_name, lang) VALUES (?, ?, ?, ?)",
|
||||
(telegram_id, username, first_name, lang),
|
||||
)
|
||||
await db.commit()
|
||||
row = await db.execute_fetchall(
|
||||
"SELECT * FROM users WHERE telegram_id = ?", (telegram_id,)
|
||||
)
|
||||
return dict(row[0])
|
||||
|
||||
|
||||
async def update_lang(telegram_id: int, lang: str) -> None:
|
||||
db = await get_db()
|
||||
await db.execute("UPDATE users SET lang = ? WHERE telegram_id = ?", (lang, telegram_id))
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def increment_free(telegram_id: int) -> int:
|
||||
db = await get_db()
|
||||
await db.execute(
|
||||
"UPDATE users SET free_used = free_used + 1 WHERE telegram_id = ?",
|
||||
(telegram_id,),
|
||||
)
|
||||
await db.commit()
|
||||
row = await db.execute_fetchall(
|
||||
"SELECT free_used FROM users WHERE telegram_id = ?", (telegram_id,)
|
||||
)
|
||||
return row[0][0]
|
||||
|
||||
|
||||
async def get_free_used(telegram_id: int) -> int:
|
||||
db = await get_db()
|
||||
row = await db.execute_fetchall(
|
||||
"SELECT free_used FROM users WHERE telegram_id = ?", (telegram_id,)
|
||||
)
|
||||
return row[0][0] if row else 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue