EN language only

This commit is contained in:
vrubelroman 2025-11-12 23:20:01 +03:00
parent 3ec1fe614d
commit 3362bf89e2
4 changed files with 353 additions and 94 deletions

View file

@ -1,5 +1,6 @@
import asyncio
import logging
import sqlite3
from datetime import datetime, timedelta
from typing import Dict, Any, Optional
@ -17,6 +18,7 @@ from config import (
from database import Database
from lichess_api import LichessAPI
from formatters import StatsFormatter
from i18n import t
# Configure logging
logging.basicConfig(
@ -35,6 +37,20 @@ class LichessBot:
self.periodic_tasks = {} # Store periodic tasks
self.period_start_times = {} # Store start times for each gamer
self.application = None # Will be set when application is created
def get_user_language_from_update(self, update: Update) -> str:
"""Always return English language"""
# Update user info in database
user = update.effective_user
if user:
self.db.add_or_get_telegram_user(
user_id=user.id,
username=user.username,
first_name=user.first_name,
last_name=user.last_name,
language_code=None
)
return 'en'
async def start_existing_periodic_tasks(self):
"""Start periodic tasks for all user-gamer pairs that have periods set"""
@ -56,41 +72,17 @@ class LichessBot:
"""Start command handler"""
# Register user in database
user = update.effective_user
lang_code = user.language_code if user else None
self.db.add_or_get_telegram_user(
user_id=user.id,
username=user.username,
first_name=user.first_name,
last_name=user.last_name
last_name=user.last_name,
language_code=lang_code
)
await update.message.reply_text(
"Бот отслеживает игру одного или нескольких игроков на Lichess.\n"
"Он показывает рейтинги, дневную, вчерашнюю и недельную активность.\n"
"При указании user_token (с правом чтения задач) можно также получать данные по задачам.\n"
"Бот поддерживает автоматические проверки с заданным интервалом и отправляет отчёт, если за это время была активность.\n\n"
"Пример за неделю:\n"
"🧩 Задачи: 114 (✅ 81 - ❌ 33)\n\n"
"🔥 Blitz — 5 игр • 🔴 -10\n"
"Рейтинг: 2245\n"
"✅ Победы: 1\n"
"❌ Поражения: 3\n"
"🤝 Ничьи: 1\n\n"
"🐇 Rapid — 19 игр • 🟢 +20\n"
"Рейтинг: 2248\n"
"✅ Победы: 8\n"
"❌ Поражения: 4\n"
"🤝 Ничьи: 7\n\n"
"📋 Доступные команды:\n"
"/addgamer - Добавить игрока Lichess для отслеживания (только username)\n"
"/addtoken - Добавить игрока с токеном для получения данных по задачам\n"
"/getgamers - Выбрать активного игрока (для которого будут работать команды /today и тд)\n"
"/delgamer - Удалить игрока из списка отслеживаемых\n"
"/today - Статистика за сегодня\n"
"/yesterday - Статистика за вчера\n"
"/week - Статистика за неделю\n"
"/setperiod - Настроить периодические уведомления активного игрока\n"
"(активный игрок меняется в меню команды /getgamers)"
)
lang = self.get_user_language_from_update(update)
await update.message.reply_text(t('start_message', lang))
async def start_and_addgamer(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Start command that automatically launches addgamer"""
@ -101,16 +93,14 @@ class LichessBot:
async def addgamer_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Start addgamer command - simple username only"""
await update.message.reply_text("👤 Введите Lichess username игрока для отслеживания:")
lang = self.get_user_language_from_update(update)
await update.message.reply_text(t('addgamer_prompt', lang))
return WAITING_FOR_USERNAME
async def addtoken_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Start addtoken command - token required"""
await update.message.reply_text(
"🔑 Введите Lichess API токен, чтобы получать данные по задачам.\n"
"Токен создаётся в настройках профиля — дайте ему только право puzzle:read.\n"
"После этого будет добавлен игрок, соответствующий этому токену."
)
lang = self.get_user_language_from_update(update)
await update.message.reply_text(t('addtoken_prompt', lang))
return WAITING_FOR_TOKEN
async def handle_token(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
@ -127,11 +117,12 @@ class LichessBot:
user_gamers = self.db.get_user_gamers(user_id)
existing_gamer = next((g for g in user_gamers if g['username'] == username), None)
lang = self.get_user_language_from_update(update)
if existing_gamer:
# Update token for existing gamer
self.db.add_user_gamer(user_id, existing_gamer['id'], token)
await update.message.reply_text(
f"✅ Токен добавлен для игрока {username}!"
t('token_added', lang, username=username)
)
else:
# Add new gamer and link with token
@ -147,17 +138,19 @@ class LichessBot:
self.db.set_user_active_gamer(user_id, gamer_id)
await update.message.reply_text(
f"✅ Игрок {username} добавлен с токеном!"
t('gamer_added_with_token', lang, username=username)
)
return ConversationHandler.END
else:
lang = self.get_user_language_from_update(update)
await update.message.reply_text(
"Не удалось получить username из токена. Попробуйте еще раз."
t('token_username_error', lang)
)
return WAITING_FOR_TOKEN
else:
lang = self.get_user_language_from_update(update)
await update.message.reply_text(
"❌ Неверный токен. Попробуйте еще раз."
t('invalid_token', lang)
)
return WAITING_FOR_TOKEN
@ -166,9 +159,10 @@ class LichessBot:
username = update.message.text.strip()
user_id = update.effective_user.id
lang = self.get_user_language_from_update(update)
if not username:
await update.message.reply_text(
"❌ Username не может быть пустым. Попробуйте еще раз."
t('empty_username', lang)
)
return WAITING_FOR_USERNAME
@ -176,7 +170,7 @@ class LichessBot:
user_exists = await self.lichess_api.check_user_exists(username)
if not user_exists:
await update.message.reply_text(
f"❌ Игрок {username} не найден на Lichess. Проверьте правильность написания имени."
t('user_not_found', lang, username=username)
)
return WAITING_FOR_USERNAME
@ -193,8 +187,9 @@ class LichessBot:
if len(user_gamers) == 1:
self.db.set_user_active_gamer(user_id, gamer_id)
lang = self.get_user_language_from_update(update)
await update.message.reply_text(
f"✅ Игрок {username} успешно добавлен!"
t('gamer_added', lang, username=username)
)
return ConversationHandler.END
@ -206,12 +201,13 @@ class LichessBot:
logger.info(f"getgamers: user_id={user_id}, found {len(gamers)} gamers")
lang = self.get_user_language_from_update(update)
if not gamers:
await update.message.reply_text("📭 В базе нет игроков. Используйте /adduser для добавления.")
await update.message.reply_text(t('no_gamers', lang))
return
# Show loading message
loading_msg = await update.message.reply_text("🔄 Загружаем рейтинги игроков...")
loading_msg = await update.message.reply_text(t('loading_ratings', lang))
# Prepare data for each gamer
gamers_data = []
@ -240,7 +236,8 @@ class LichessBot:
# Add period information if period > 0
period_minutes = gamer.get('period_minutes', 0)
period_text = f" · {period_minutes}м" if period_minutes > 0 else ""
period_suffix = t('period_minutes_suffix', lang)
period_text = f" · {period_minutes}{period_suffix}" if period_minutes > 0 else ""
gamers_data.append({
'id': gamer['id'],
@ -257,6 +254,9 @@ class LichessBot:
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
# Still add the gamer with N/A ratings
period_minutes = gamer.get('period_minutes', 0)
period_suffix = t('period_minutes_suffix', lang)
period_text = f" · {period_minutes}{period_suffix}" if period_minutes > 0 else ""
gamers_data.append({
'id': gamer['id'],
'status': "🟢" if gamer['is_active'] else "",
@ -264,7 +264,7 @@ class LichessBot:
'bullet': 'N/A',
'blitz': 'N/A',
'rapid': 'N/A',
'period': f" · {gamer.get('period_minutes', 0)}м" if gamer.get('period_minutes', 0) > 0 else ""
'period': period_text
})
logger.info(f"Added gamer {gamer['username']} with N/A ratings due to error")
@ -277,7 +277,7 @@ class LichessBot:
)
logger.info(f"getgamers: prepared {len(gamers_data)} gamers for display")
gamers_text = "👥 <b>Выберите активного игрока:</b>\n\n" + "\n".join(text_lines)
gamers_text = t('select_active_gamer', lang) + "\n".join(text_lines)
logger.info(f"getgamers: message length: {len(gamers_text)} characters")
@ -325,26 +325,37 @@ class LichessBot:
gamers = self.db.get_user_gamers(user_id)
selected_gamer = next((g for g in gamers if g['id'] == gamer_id), None)
# Для callback query обновляем язык пользователя если есть в update
if update.effective_user:
self.db.add_or_get_telegram_user(
user_id=update.effective_user.id,
username=update.effective_user.username,
first_name=update.effective_user.first_name,
last_name=update.effective_user.last_name,
language_code=update.effective_user.language_code
)
lang = self.db.get_user_language(user_id)
if selected_gamer:
# Set active gamer for this user
self.db.set_user_active_gamer(user_id, gamer_id)
await query.edit_message_text(
f"✅ Активный игрок: {selected_gamer['username']}"
t('active_gamer_set', lang, username=selected_gamer['username'])
)
else:
await query.edit_message_text("❌ Игрок не найден")
await query.edit_message_text(t('gamer_not_found', lang))
async def delgamer(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Show gamers list for deletion"""
user_id = update.effective_user.id
gamers = self.db.get_user_gamers(user_id)
lang = self.get_user_language_from_update(update)
if not gamers:
await update.message.reply_text("📭 У вас нет отслеживаемых игроков.")
await update.message.reply_text(t('no_gamers_to_delete', lang))
return
# Show loading message
loading_msg = await update.message.reply_text("🔄 Загружаем список игроков...")
loading_msg = await update.message.reply_text(t('loading_gamers', lang))
# Create text message with stats
text_lines = []
@ -370,7 +381,8 @@ class LichessBot:
bullet_rating = blitz_rating = rapid_rating = 'N/A'
period_minutes = gamer.get('period_minutes', 0)
period_text = f" · {period_minutes}м" if period_minutes > 0 else ""
period_suffix = t('period_minutes_suffix', lang)
period_text = f" · {period_minutes}{period_suffix}" if period_minutes > 0 else ""
text_lines.append(
f"{status} <b>{username}</b> "
@ -383,7 +395,7 @@ class LichessBot:
callback_data=f"delete_{gamer['id']}"
)])
gamers_text = "🗑️ <b>Выберите игрока для удаления:</b>\n\n" + "\n".join(text_lines)
gamers_text = t('select_gamer_to_delete', lang) + "\n".join(text_lines)
reply_markup = InlineKeyboardMarkup(keyboard)
@ -418,19 +430,29 @@ class LichessBot:
gamers = self.db.get_user_gamers(user_id)
gamer_to_delete = next((g for g in gamers if g['id'] == gamer_id), None)
# Для callback query получаем язык из БД
if update.effective_user:
self.db.add_or_get_telegram_user(
user_id=update.effective_user.id,
username=update.effective_user.username,
first_name=update.effective_user.first_name,
last_name=update.effective_user.last_name,
language_code=update.effective_user.language_code
)
lang = self.db.get_user_language(user_id)
if gamer_to_delete:
username = gamer_to_delete['username']
deleted = self.db.remove_user_gamer(user_id, gamer_id)
if deleted:
await query.edit_message_text(
f"✅ Игрок <b>{username}</b> удален из списка отслеживаемых.",
t('gamer_deleted', lang, username=username),
parse_mode='HTML'
)
else:
await query.edit_message_text("Не удалось удалить игрока")
await query.edit_message_text(t('delete_failed', lang))
else:
await query.edit_message_text("❌ Игрок не найден")
await query.edit_message_text(t('gamer_not_found', lang))
async def get_stats(self, update: Update, context: ContextTypes.DEFAULT_TYPE, period: str):
"""Get statistics for a period"""
@ -439,9 +461,10 @@ class LichessBot:
# Get active gamer for this user
active_gamer = self.db.get_user_active_gamer(user_id)
lang = self.get_user_language_from_update(update)
if not active_gamer:
await update.message.reply_text(
"❌ Нет активного игрока. Используйте /getgamers для выбора."
t('no_active_gamer', lang)
)
return
@ -455,11 +478,11 @@ class LichessBot:
elif period == "week":
data = await self.lichess_api.get_week_stats(username)
else:
await update.message.reply_text("❌ Неизвестный период")
await update.message.reply_text(t('unknown_period', lang))
return
# Format and send response
formatted_response = StatsFormatter.format_stats_response(data, username, period)
formatted_response = StatsFormatter.format_stats_response(data, username, period, lang)
await update.message.reply_text(formatted_response)
async def today(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
@ -481,24 +504,24 @@ class LichessBot:
# Get active gamer for this user
active_gamer = self.db.get_user_active_gamer(user_id)
lang = self.get_user_language_from_update(update)
if not active_gamer:
await update.message.reply_text(
"❌ Нет активного игрока. Используйте /getgamers для выбора."
t('no_active_gamer', lang)
)
return
keyboard = []
for period in PERIOD_OPTIONS:
if period == 0:
button_text = "❌ Отключить уведомления"
button_text = t('disable_notifications', lang)
else:
button_text = f"{period} минут"
button_text = t('period_minutes', lang, period=period)
keyboard.append([InlineKeyboardButton(button_text, callback_data=f"period_{period}")])
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
f"⏱️ Выберите период для игрока {active_gamer['username']}:\n"
f"📱 Уведомления будут приходить в личные сообщения",
t('select_period', lang, username=active_gamer['username']),
reply_markup=reply_markup
)
@ -513,23 +536,83 @@ class LichessBot:
# Get active gamer for this user
active_gamer = self.db.get_user_active_gamer(user_id)
# Для callback query получаем язык из БД
if update.effective_user:
self.db.add_or_get_telegram_user(
user_id=update.effective_user.id,
username=update.effective_user.username,
first_name=update.effective_user.first_name,
last_name=update.effective_user.last_name,
language_code=update.effective_user.language_code
)
lang = self.db.get_user_language(user_id)
if active_gamer:
# Set period for this user-gamer pair
self.db.set_user_gamer_period(user_id, active_gamer['id'], period)
if period == 0:
await query.edit_message_text(
f"✅ Уведомления для {active_gamer['username']} отключены"
t('notifications_disabled', lang, username=active_gamer['username'])
)
else:
await query.edit_message_text(
f"✅ Период {period} минут установлен для {active_gamer['username']}\n"
f"📱 Уведомления будут приходить в личные сообщения"
t('period_set', lang, period=period, username=active_gamer['username'])
)
# Start periodic task for this gamer (send to user's personal messages)
await self.start_periodic_task(active_gamer, user_id, period)
async def check_language(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Check and display current language settings"""
user = update.effective_user
if user:
# Обновляем язык из update
lang = self.get_user_language_from_update(update)
# Получаем язык из БД для отображения
db_lang = self.db.get_user_language(user.id)
# Получаем language_code из БД напрямую
with sqlite3.connect(self.db.db_path) as conn:
cursor = conn.cursor()
cursor.execute("SELECT language_code FROM telegram_users WHERE user_id = ?", (user.id,))
row = cursor.fetchone()
db_language_code = row[0] if row and row[0] else None
message = (
f"🌐 Language Info:\n"
f"Current language: {lang}\n"
f"DB language: {db_lang}\n"
f"DB language_code: {db_language_code}\n"
f"Update language_code: {user.language_code}\n\n"
f"Language used: {lang}\n\n"
f"Bot uses English language only."
)
await update.message.reply_text(message)
else:
await update.message.reply_text("❌ Failed to get user information")
async def reset_language(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Reset user language in database - will be detected from /start"""
user = update.effective_user
if user:
# Сбрасываем язык в БД (устанавливаем NULL)
with sqlite3.connect(self.db.db_path) as conn:
cursor = conn.cursor()
cursor.execute(
"UPDATE telegram_users SET language_code = NULL WHERE user_id = ?",
(user.id,)
)
conn.commit()
# Language is always English
lang = 'en'
await update.message.reply_text(
"✅ Language reset! Bot uses English language only."
)
else:
await update.message.reply_text("❌ Failed to get user information")
async def start_periodic_task(self, gamer: Dict[str, Any], user_id: int, period_minutes: int):
"""Start periodic task for a gamer"""
task_key = f"{gamer['id']}_{user_id}"
@ -622,8 +705,10 @@ class LichessBot:
# Отправляем уведомление только если есть реальная активность
if has_games or has_puzzles:
try:
# Get user language from database
user_lang = self.db.get_user_language(user_id)
notification = StatsFormatter.format_period_notification(
gamer['username'], games_data, puzzles_data, period_minutes
gamer['username'], games_data, puzzles_data, period_minutes, lang=user_lang
)
if self.application:
@ -690,6 +775,8 @@ class LichessBot:
application.add_handler(CommandHandler("yesterday", self.yesterday))
application.add_handler(CommandHandler("week", self.week))
application.add_handler(CommandHandler("setperiod", self.setperiod))
application.add_handler(CommandHandler("lang", self.check_language))
application.add_handler(CommandHandler("resetlang", self.reset_language))
# Callback handlers
application.add_handler(CallbackQueryHandler(self.select_gamer, pattern="^select_"))

View file

@ -22,10 +22,18 @@ class Database:
username TEXT,
first_name TEXT,
last_name TEXT,
language_code TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Add language_code column if it doesn't exist
try:
cursor.execute("ALTER TABLE telegram_users ADD COLUMN language_code TEXT")
except sqlite3.OperationalError:
# Column already exists
pass
# Create gamers table (Lichess players only)
cursor.execute('''
CREATE TABLE IF NOT EXISTS gamers (
@ -104,7 +112,8 @@ class Database:
logger.info(f"Migrated {migrated} tokens from gamers to user_gamers")
def add_or_get_telegram_user(self, user_id: int, username: Optional[str] = None,
first_name: Optional[str] = None, last_name: Optional[str] = None) -> bool:
first_name: Optional[str] = None, last_name: Optional[str] = None,
language_code: Optional[str] = None) -> bool:
"""Add or update Telegram user"""
with sqlite3.connect(self.db_path) as conn:
cursor = conn.cursor()
@ -114,13 +123,24 @@ class Database:
if not existing:
cursor.execute(
"INSERT INTO telegram_users (user_id, username, first_name, last_name) VALUES (?, ?, ?, ?)",
(user_id, username, first_name, last_name)
"INSERT INTO telegram_users (user_id, username, first_name, last_name, language_code) VALUES (?, ?, ?, ?, ?)",
(user_id, username, first_name, last_name, language_code)
)
conn.commit()
return True
else:
# Update language_code if provided (always update, even if None to clear old value)
cursor.execute(
"UPDATE telegram_users SET language_code = ? WHERE user_id = ?",
(language_code, user_id)
)
conn.commit()
return False
def get_user_language(self, user_id: int) -> str:
"""Always return English language"""
return 'en'
def add_gamer(self, username: str) -> int:
"""Add a new gamer to the database (return gamer_id)"""
with sqlite3.connect(self.db_path) as conn:

View file

@ -1,5 +1,6 @@
from typing import Dict, Any, Optional
from datetime import datetime
from i18n import t
class StatsFormatter:
@staticmethod
@ -13,10 +14,10 @@ class StatsFormatter:
return f"0"
@staticmethod
def format_stats_response(data: Dict[str, Any], username: str, period: str) -> str:
def format_stats_response(data: Dict[str, Any], username: str, period: str, lang: str = 'en') -> str:
"""Format statistics response according to the template"""
if not data or data.get('data') is None:
message = data.get('message', 'Нет данных') if data else 'Нет данных'
message = data.get('message', t('no_data', lang)) if data else t('no_data', lang)
return f"📭 {message}"
# Extract data from API response
@ -25,7 +26,7 @@ class StatsFormatter:
games = api_data.get('games', {})
# Format date range
date_range = StatsFormatter._get_date_range(period)
date_range = StatsFormatter._get_date_range(period, lang)
# Format tasks section
task_text = ""
@ -33,7 +34,7 @@ class StatsFormatter:
total_tasks = tasks.get('total', 0)
solved = tasks.get('solved', 0)
unsolved = tasks.get('unsolved', 0)
task_text = f"🧩 Задачи: {total_tasks} (✅ {solved} - ❌ {unsolved})\n\n"
task_text = t('puzzles_section', lang, total=total_tasks, solved=solved, unsolved=unsolved)
# Format games section
games_text = ""
@ -55,21 +56,29 @@ class StatsFormatter:
# Format rating change
rating_change_str = StatsFormatter._format_rating_change(rating_change)
games_text += f"{emoji} {game_type.title()}{games_count} игр • {rating_change_str}\n"
games_text += f"Рейтинг: {rating}\n"
games_text += f"✅ Победы: {wins}\n"
games_text += f"❌ Поражения: {losses}\n"
games_text += f"🤝 Ничьи: {draws}\n\n"
# Get game type name (capitalize first letter)
game_type_name = game_type.title()
games_text += t('games_section', lang,
emoji=emoji,
game_type=game_type_name,
games_count=games_count,
rating_change=rating_change_str,
rating=rating,
wins=wins,
losses=losses,
draws=draws
)
# Combine all parts
result = f"📊 Статистика {username}{date_range}\n\n"
result = t('stats_title', lang, username=username, date_range=date_range)
result += task_text
result += games_text.rstrip()
return result
@staticmethod
def _get_date_range(period: str) -> str:
def _get_date_range(period: str, lang: str = 'en') -> str:
"""Get date range string for the period"""
from datetime import datetime, timedelta
@ -99,19 +108,19 @@ class StatsFormatter:
return emoji_map.get(game_type.lower(), '🎯')
@staticmethod
def format_period_notification(username: str, games_data: Optional[Dict], puzzles_data: Optional[Dict], period_minutes: int) -> str:
def format_period_notification(username: str, games_data: Optional[Dict], puzzles_data: Optional[Dict], period_minutes: int, lang: str = 'en') -> str:
"""Format notification for periodic checks"""
from datetime import datetime
# Format period text
if period_minutes == 1:
period_text = "за 1 минуту"
period_text = t('period_1_minute', lang)
elif period_minutes in [2, 3, 4]:
period_text = f"за {period_minutes} минуты"
period_text = t('period_2_3_4_minutes', lang, period=period_minutes)
else:
period_text = f"за {period_minutes} минут"
period_text = t('period_minutes_text', lang, period=period_minutes)
result = f"📊 Статистика {username}{period_text}\n\n"
result = t('period_notification_title', lang, username=username, period_text=period_text)
# Format puzzles first (if available and there's actual activity)
if puzzles_data and puzzles_data.get('data'):
@ -122,7 +131,7 @@ class StatsFormatter:
# Only show tasks section if there's actual activity (not all zeros)
if total_puzzles > 0 or solved > 0 or failed > 0:
result += f"🧩 Задачи: {total_puzzles} (✅ {solved} - ❌ {failed})\n\n"
result += t('period_puzzles_section', lang, total=total_puzzles, solved=solved, failed=failed)
# Format games
if games_data and games_data.get('data'):
@ -142,14 +151,21 @@ class StatsFormatter:
draws = game_data.get('draws', 0)
rating_change_str = StatsFormatter._format_rating_change(rating_change)
result += f"{emoji} {game_type.title()}{games_count} игр • {rating_change_str}\n"
result += f"Рейтинг: {rating}\n"
result += f"✅ Победы: {wins}\n"
result += f"❌ Поражения: {losses}\n"
result += f"🤝 Ничьи: {draws}\n\n"
game_type_name = game_type.title()
result += t('period_games_section', lang,
emoji=emoji,
game_type=game_type_name,
games_count=games_count,
rating_change=rating_change_str,
rating=rating,
wins=wins,
losses=losses,
draws=draws
)
# If no activity
if not (games_data and games_data.get('data')) and not (puzzles_data and puzzles_data.get('data')):
result += "📭 Нет активности за этот период"
result += t('no_activity', lang)
return result.rstrip()

136
LichessClientTG_bot/i18n.py Normal file
View file

@ -0,0 +1,136 @@
"""
Internationalization module for the Lichess Telegram Bot
English language only
"""
from typing import Optional
# Translation dictionary (English only)
TRANSLATIONS = {
'en': {
# Start command
'start_message': (
"The bot tracks one or more players on Lichess.\n"
"It shows ratings, daily, yesterday and weekly activity.\n"
"When specifying a user_token (with puzzle read permission), you can also get puzzle data.\n"
"The bot supports automatic checks at specified intervals and sends a report if there was activity during that time.\n\n"
"Example for a week:\n"
"🧩 Puzzles: 114 (✅ 81 - ❌ 33)\n\n"
"🔥 Blitz — 5 games • 🔴 -10\n"
"Rating: 2245\n"
"✅ Wins: 1\n"
"❌ Losses: 3\n"
"🤝 Draws: 1\n\n"
"🐇 Rapid — 19 games • 🟢 +20\n"
"Rating: 2248\n"
"✅ Wins: 8\n"
"❌ Losses: 4\n"
"🤝 Draws: 7\n\n"
"📋 Available commands:\n"
"/addgamer - Add a Lichess player to track (username only)\n"
"/addtoken - Add a player with a token to get puzzle data\n"
"/getgamers - Select active player (for which /today and other commands will work)\n"
"/delgamer - Remove a player from the tracked list\n"
"/today - Statistics for today\n"
"/yesterday - Statistics for yesterday\n"
"/week - Statistics for the week\n"
"/setperiod - Set up periodic notifications for the active player\n"
"(active player changes in the /getgamers menu)"
),
# Add gamer commands
'addgamer_prompt': "👤 Enter the Lichess username of the player to track:",
'addtoken_prompt': (
"🔑 Enter the Lichess API token to get puzzle data.\n"
"The token is created in profile settings — give it only puzzle:read permission.\n"
"After that, the player corresponding to this token will be added."
),
'token_added': "✅ Token added for player {username}!",
'gamer_added_with_token': "✅ Player {username} added with token!",
'gamer_added': "✅ Player {username} successfully added!",
'invalid_token': "❌ Invalid token. Please try again.",
'token_username_error': "❌ Failed to get username from token. Please try again.",
'empty_username': "❌ Username cannot be empty. Please try again.",
'user_not_found': "❌ Player {username} not found on Lichess. Check the spelling of the name.",
# Get gamers
'no_gamers': "📭 No players in database. Use /addgamer to add.",
'loading_ratings': "🔄 Loading player ratings...",
'select_active_gamer': "👥 <b>Select active player:</b>\n\n",
'active_gamer_set': "✅ Active player: {username}",
'gamer_not_found': "❌ Player not found",
# Delete gamer
'no_gamers_to_delete': "📭 You have no tracked players.",
'loading_gamers': "🔄 Loading player list...",
'select_gamer_to_delete': "🗑️ <b>Select player to delete:</b>\n\n",
'gamer_deleted': "✅ Player <b>{username}</b> removed from tracked list.",
'delete_failed': "❌ Failed to delete player",
# Stats commands
'no_active_gamer': "❌ No active player. Use /getgamers to select.",
'unknown_period': "❌ Unknown period",
'no_data': "📭 No data",
'stats_title': "📊 Statistics {username}{date_range}\n\n",
'puzzles_section': "🧩 Puzzles: {total} (✅ {solved} - ❌ {unsolved})\n\n",
'games_section': "{emoji} {game_type}{games_count} games • {rating_change}\nRating: {rating}\n✅ Wins: {wins}\n❌ Losses: {losses}\n🤝 Draws: {draws}\n\n",
# Set period
'select_period': "⏱️ Select period for player {username}:\n📱 Notifications will be sent to personal messages",
'notifications_disabled': "✅ Notifications disabled for {username}",
'period_set': "✅ Period {period} minutes set for {username}\n📱 Notifications will be sent to personal messages",
'disable_notifications': "❌ Disable notifications",
'period_minutes': "{period} minutes",
# Period notification
'period_1_minute': "for 1 minute",
'period_2_3_4_minutes': "for {period} minutes",
'period_minutes_text': "for {period} minutes",
'period_notification_title': "📊 Statistics {username}{period_text}\n\n",
'period_puzzles_section': "🧩 Puzzles: {total} (✅ {solved} - ❌ {failed})\n\n",
'period_games_section': "{emoji} {game_type}{games_count} games • {rating_change}\nRating: {rating}\n✅ Wins: {wins}\n❌ Losses: {losses}\n🤝 Draws: {draws}\n\n",
'no_activity': "📭 No activity for this period",
# Common
'period_minutes_suffix': "m",
}
}
def get_user_language(update) -> str:
"""
Always return English language.
"""
return 'en'
def t(key: str, lang: str = 'en', **kwargs) -> str:
"""
Translate a key (always English).
Args:
key: Translation key
lang: Language code (ignored, always uses English)
**kwargs: Format arguments for the translation string
Returns:
Translated string with formatted arguments
"""
translation = TRANSLATIONS['en'].get(key, key)
# Format the string if kwargs provided
if kwargs:
try:
return translation.format(**kwargs)
except KeyError:
# If formatting fails, return the translation as-is
return translation
return translation
def get_lang_from_update(update) -> str:
"""
Always return English.
"""
return 'en'