AGENTS.md
Project Overview
Payment Integration (Tribute)
- Payment Provider: Tribute (@tribute)
- Product Type: Digital Product (разовая покупка на 30 дней)
- Payment Link: https://t.me/tribute/app?startapp=pq5z
- Webhook Server:
webhook_server.pyruns on port 8080 - Webhook Event:
new_digital_product— grants 30 days premium - Setup Guide: See
WEBHOOK_SETUP.mdfor detailed instructions
Subscription Flow
- User clicks "💎 Купить подписку Premium" in profile or /premium
- Opens Tribute payment page via
TRIBUTE_PAYMENT_LINK - User completes payment in Tribute
- Tribute sends POST to webhook (
/webhook/tribute) - Webhook grants 30 days premium + notifies user
- If referral exists, referrer gets +30 days bonus
Key Files
bot/handlers/profile.py— Shows subscription button in profilebot/handlers/premium.py— /premium command with payment buttonbot/keyboards/inline.py—get_profile_subscription_keyboard(),get_premium_keyboard()webhook_server.py— Handles Tribute webhooks
Project Overview
HanYu 汉语 — Telegram-бот для практики разговорного китайского языка с AI.
Tech Stack
- Runtime: Python 3.11+
- Bot Framework: aiogram 3.x
- Database: SQLite 3.x + aiosqlite 0.19+
- AI: OpenAI API (Whisper, GPT-4o-mini, TTS)
- Validation: Pydantic 2.x, pydantic-settings 2.x
Architecture
- Event-driven with aiogram handlers
- Middleware chain: Throttling → Auth → Subscription
- Repository pattern for database operations
- Service layer for AI integrations
Dev Environment Tips
Setup
python -m venv venv
source venv/bin/activate # or venv\Scripts\activate on Windows
pip install -r requirements.txt
cp .env.example .env
# Fill in BOT_TOKEN, OPENAI_API_KEY, ADMIN_IDS in .env
Project Structure
hanyu-bot/
├── bot/
│ ├── main.py # Entry point
│ ├── config.py # Pydantic settings
│ ├── handlers/ # Command handlers (dialog, settings, premium, etc.)
│ ├── keyboards/ # Reply & Inline keyboards
│ ├── middlewares/ # Auth, subscription, throttling
│ ├── services/ # AI services (Whisper, GPT, TTS)
│ ├── database/ # Models, repos, schema
│ └── utils/ # HSK dictionaries
├── data/ # HSK JSON files
└── requirements.txt
Key Files
bot/config.py— All settings from .envbot/services/ai.py— Whisper, GPT, TTS integrationbot/handlers/dialog.py— Main voice/text processingbot/handlers/callbacks.py— Inline button handlers (Text, Help, Translate, Explain)bot/database/repositories.py— All CRUD operations
Help Button Feature
- When user clicks "❓ Помощь" button, bot generates 2-3 response suggestions
- Each suggestion includes Chinese text + pinyin (format: "我今天看书。- Wǒ jīntiān kàn shū.")
- Suggestions are context-aware based on conversation history and HSK level
Error Correction Feature
- When AI detects errors in user's Chinese text, it displays a correction message
- Correction format:
错误文本→ ✅ 正确文本 + pinyin - Pinyin is automatically generated for the corrected text
- User can click "💡 Объяснить" button for detailed explanation of the error
Dialogue Topics
Available topics for conversation:
- ✈️ Путешествия (旅游) - travel
- 🍜 Еда (美食) - food
- 💼 Работа (工作) - work
- 🏠 Быт (日常生活) - daily life
- 📚 Учёба (学习) - study
- 🏥 Здоровье (健康) - health
- 💬 Свободный диалог (自由对话) - free conversation on any topic
Build & Run Commands
# Option 1: Run bot only (polling mode)
python -m bot.main
# Option 2: Run bot + webhook server together
python start_all.py
# Option 3: Run separately in different terminals
# Terminal 1: Webhook server
python webhook_server.py
# Terminal 2: Bot
python -m bot.main
# Terminal 3 (if testing locally): ngrok for webhook
ngrok http 8080
# Run with specific log level
LOG_LEVEL=DEBUG python -m bot.main
# Format code
black bot/
isort bot/
# Type checking (if installed)
mypy bot/
Deployment (Production)
Railway Deployment (Current Setup)
Initial Setup
-
Connect GitHub repository to Railway
- Go to Railway Dashboard
- Create new project → Deploy from GitHub
- Select
speaky-chineserepository
-
Configure Environment Variables Add these in Railway dashboard:
BOT_TOKEN=your_telegram_bot_token OPENAI_API_KEY=your_openai_api_key ADMIN_IDS=comma,separated,telegram,ids TRIBUTE_API_KEY=your_tribute_api_key TRIBUTE_PRODUCT_ID=your_product_id TRIBUTE_PAYMENT_LINK=https://t.me/tribute/app?startapp=pq5z LOG_LEVEL=INFO -
Railway will automatically deploy
- Uses
Procfile→ runsrailway_start.py(webhook mode) - Port is provided by Railway via
PORTenv var - Public URL is provided via
RAILWAY_STATIC_URL
- Uses
Important Railway Settings
⚠️ CRITICAL: Set Replica Count to 1
- Go to Settings → Deploy
- Set "Replicas" = 1
- Multiple replicas cause "Conflict: terminated by other getUpdates" error
Railway Configuration (railway.json):
{
"deploy": {
"numReplicas": 1,
"restartPolicyType": "ON_FAILURE"
}
}
Deployment Commands
# Deploy via git push
git add .
git commit -m "update: feature description"
git push origin main # Railway auto-deploys
# View logs
railway logs
# Local Railway CLI
railway up # Deploy from CLI
railway run python railway_start.py # Test locally with Railway env vars
Health Check
# Check bot is running
curl https://your-railway-app.railway.app/health
# Response should be:
# {"status": "ok", "service": "speaky-chinese-bot", "mode": "webhook"}
Webhook vs Polling Mode
- Railway (Production): Uses
railway_start.py→ Webhook mode - Local Development: Use
python -m bot.main→ Polling mode - Never run polling mode on Railway → causes multiple instances conflict
bot.mainblocks polling if Railway env vars are present
Troubleshooting Railway
"Conflict: terminated by other getUpdates"
- ✅ Check: Only 1 replica running (Railway Settings → Deploy)
- ✅ Check: No other deployments active
- ✅ Check: Using
railway_start.py(webhook), notbot.main(polling) - ✅ Fix: Delete webhook if needed:
python delete_webhook.py
Check active deployments:
railway status
# Should show only 1 active deployment
Force restart:
railway restart
# or via dashboard: Deployments → Active → Restart
Alternative: Systemd Service (VPS/Timeweb)
If deploying to VPS instead of Railway:
# 1. Setup on server
cd /root/speaky-chinese/speaky-chinese-ai
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# 2. Configure .env with production values
nano .env
# 3. Install systemd service
cp speaky-chinese.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable speaky-chinese.service
systemctl start speaky-chinese.service
# 4. Check status
systemctl status speaky-chinese
journalctl -u speaky-chinese.service -f
Testing Instructions
Manual Testing
- Start bot:
python -m bot.main - Send /start to bot in Telegram
- Test voice message → should get voice reply
- Test text message → should get voice reply
- Test /settings, /topic, /premium commands
- Test /admin (only works for ADMIN_IDS)
Key Flows to Test
- New user registration (/start)
- Referral link (/start?start=ref_CODE)
- Voice message processing
- Text message processing
- Error correction display (with pinyin for corrected text)
- Inline buttons (Text, Help, Translate, Explain)
- Settings change (level, speed, topic)
- Settings navigation (submenu → back → close)
- Profile subscription button (👤 Профиль → "Купить/Управление подпиской")
- Premium purchase via Tribute (/premium → payment link)
- Webhook payment processing (POST /webhook/tribute)
- Admin panel (stats, users, broadcast)
- Admin premium management (grant days, grant permanent premium)
- Grant permanent premium to all admins (one-click button)
Code Style Guidelines
Naming
- Files:
snake_case.py - Classes:
PascalCase - Functions/Variables:
snake_case - Constants:
UPPER_SNAKE_CASE
Imports Order
- Standard library
- Third-party (aiogram, openai, pydantic)
- Local imports (bot.*)
Handler Pattern
@router.message(Command("cmd"))
async def cmd_handler(message: Message, user: User):
"""Handle /cmd command."""
# Handler logic
Repository Pattern
- All DB operations through repository classes
- Use
async with get_db()for connections - Return dataclass models from repositories
Git & PR Instructions
Branch Naming
feature/descriptionfix/descriptionrefactor/description
Commit Message Format
type: short description
- detail 1
- detail 2
Types: feat, fix, refactor, docs, test, chore
Admin Features
Premium Management
Admins can grant premium subscriptions to users through the admin panel:
-
Grant Premium to User
/admin→ Search user or browse user list- Click user card → "💎 Выдать Premium"
- Select duration: 7, 30, 90 days, or ♾️ Навсегда (permanent)
- Permanent premium = 100 years (36500 days)
-
Grant Premium to All Admins
/admin→ "♾️ Premium для админов"- One-click grants permanent premium to all admin IDs from config
- All admins get notification about permanent premium
-
Premium Features
- Permanent premium displayed as "♾️ Навсегда" in UI
- Users get notified when premium is granted
- Premium status affects subscription middleware (unlimited usage)
Admin Panel Features
- 📊 Statistics: Users, premium count, DAU/WAU/MAU, revenue
- 👥 User Management: Browse all users, premium users only
- 🔍 Search: Find users by Telegram ID or @username
- 💎 Grant Premium: 7/30/90 days or permanent subscription
- 🚫 Block/Unblock: Manage access to bot
- 📨 Direct Messages: Send message to specific user
- 📢 Broadcast: Send message to all/premium/free users
Security & Best Practices
Secrets
- Never commit
.envfile - Use
.env.examplefor documentation - Admin IDs in env, not in code
Input Validation
- Max text length: 500 chars (configurable)
- Max voice duration: 60 sec (configurable)
- Rate limiting: 1 msg/sec per user
Error Handling
- Log errors with context
- Show user-friendly messages
- Don't expose internal errors
Database
- SQLite for simplicity
- Foreign keys enabled
- Indexes on frequently queried columns
- Railway Persistence: Set
DB_PATH=/data/bot.db+ create Railway Volume mounted at/data - Migrations run automatically on startup (safe
ALTER TABLE ADD COLUMN)
Subscription Expiry Notifications
- Background task runs every 1 hour (
subscription_checker.py) - Detects expired trials → sends message with Tribute payment button
- Detects expired premium → sends renewal notification
- Notification flags (
trial_notified,premium_expired_notified) prevent duplicate messages premium_expired_notifiedresets when premium is re-activated
Testing
# Run all subscription lifecycle tests
python -m pytest tests/test_subscription_lifecycle.py -v
# Test covers:
# - Trial/Free/Premium status transitions
# - Tribute payment → 30 days premium
# - Premium expiry → features blocked
# - Renewal restores premium
# - Free tier limits enforcement
# - Expiry notification system
# - Webhook signature verification
# - DB persistence across connections