admin panel
This commit is contained in:
parent
3362bf89e2
commit
23de80f94d
6 changed files with 424 additions and 20 deletions
30
LichessClientTG_bot/Dockerfile.admin
Normal file
30
LichessClientTG_bot/Dockerfile.admin
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
FROM python:3.11-slim
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy requirements first for better caching
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install Python dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Create directory for database
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONPATH=/app
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Run the admin bot
|
||||
CMD ["python", "admin_bot.py"]
|
||||
|
||||
|
||||
223
LichessClientTG_bot/admin_bot.py
Normal file
223
LichessClientTG_bot/admin_bot.py
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
"""
|
||||
Admin Panel Telegram Bot
|
||||
Sends notifications about new users and new tracked players
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from telegram import Update
|
||||
from telegram.ext import Application, CommandHandler, ContextTypes
|
||||
|
||||
from config import ADMINPANEL_TELEGRAM_BOT_TOKEN, DATABASE_PATH
|
||||
from database import Database
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AdminBot:
|
||||
def __init__(self):
|
||||
self.db = Database()
|
||||
self.application = None
|
||||
|
||||
async def send_notification(self, message: str):
|
||||
"""Send notification to admin chat"""
|
||||
admin_chat_id = self.get_admin_chat_id()
|
||||
if not admin_chat_id:
|
||||
logger.warning("Admin chat ID not set, cannot send notification")
|
||||
return
|
||||
|
||||
# Try to use application if available (when running as separate service)
|
||||
if self.application:
|
||||
try:
|
||||
await self.application.bot.send_message(
|
||||
chat_id=admin_chat_id,
|
||||
text=message,
|
||||
parse_mode='HTML'
|
||||
)
|
||||
logger.debug(f"Notification sent via application to chat {admin_chat_id}")
|
||||
return
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to send via application: {e}, trying direct API call")
|
||||
|
||||
# Fallback: use direct Telegram API call
|
||||
try:
|
||||
import aiohttp
|
||||
url = f"https://api.telegram.org/bot{ADMINPANEL_TELEGRAM_BOT_TOKEN}/sendMessage"
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, json={
|
||||
"chat_id": admin_chat_id,
|
||||
"text": message,
|
||||
"parse_mode": "HTML"
|
||||
}) as response:
|
||||
if response.status == 200:
|
||||
logger.debug(f"Notification sent via direct API to chat {admin_chat_id}")
|
||||
else:
|
||||
error_text = await response.text()
|
||||
logger.error(f"Failed to send notification: {response.status} - {error_text}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send admin notification via API: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def get_admin_chat_id(self) -> Optional[int]:
|
||||
"""Get admin chat ID from database"""
|
||||
return self.db.get_admin_chat_id()
|
||||
|
||||
def set_admin_chat_id(self, chat_id: int):
|
||||
"""Set admin chat ID in database"""
|
||||
self.db.set_admin_chat_id(chat_id)
|
||||
|
||||
async def notify_new_user(self, user_id: int, username: Optional[str], first_name: Optional[str]):
|
||||
"""Send notification about new Telegram user"""
|
||||
username_text = f"@{username}" if username else "без username"
|
||||
name_text = first_name if first_name else "без имени"
|
||||
|
||||
message = (
|
||||
f"🆕 <b>Новый пользователь Telegram</b>\n\n"
|
||||
f"ID: {user_id}\n"
|
||||
f"Username: {username_text}\n"
|
||||
f"Имя: {name_text}"
|
||||
)
|
||||
await self.send_notification(message)
|
||||
|
||||
async def notify_new_player(self, player_username: str, added_by_user_id: int, added_by_username: Optional[str]):
|
||||
"""Send notification about new tracked player"""
|
||||
added_by_text = f"@{added_by_username}" if added_by_username else f"ID: {added_by_user_id}"
|
||||
lichess_url = f"https://lichess.org/@/{player_username}"
|
||||
|
||||
message = (
|
||||
f"🎮 <b>Добавлен новый игрок для отслеживания</b>\n\n"
|
||||
f"Игрок: <a href=\"{lichess_url}\">{player_username}</a>\n"
|
||||
f"Добавил: {added_by_text}"
|
||||
)
|
||||
await self.send_notification(message)
|
||||
|
||||
async def status(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Status command - show statistics"""
|
||||
try:
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(self.db.db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Count users
|
||||
cursor.execute("SELECT COUNT(*) FROM telegram_users")
|
||||
users_count = cursor.fetchone()[0]
|
||||
|
||||
# Count unique gamers
|
||||
cursor.execute("SELECT COUNT(DISTINCT username) FROM gamers")
|
||||
gamers_count = cursor.fetchone()[0]
|
||||
|
||||
conn.close()
|
||||
|
||||
message = (
|
||||
f"📊 <b>Статистика базы данных</b>\n\n"
|
||||
f"👥 Пользователей Telegram: {users_count}\n"
|
||||
f"🎮 Отслеживаемых игроков: {gamers_count}"
|
||||
)
|
||||
await update.message.reply_text(message, parse_mode='HTML')
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting status: {e}")
|
||||
await update.message.reply_text(f"❌ Ошибка получения статистики: {e}")
|
||||
|
||||
async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Start command - register admin chat"""
|
||||
try:
|
||||
chat_id = update.effective_chat.id
|
||||
self.set_admin_chat_id(chat_id)
|
||||
|
||||
await update.message.reply_text(
|
||||
"✅ Админ-панель активирована!\n\n"
|
||||
"Доступные команды:\n"
|
||||
"/status - Показать статистику"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in start command: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
try:
|
||||
await update.message.reply_text(f"Ошибка: {e}")
|
||||
except:
|
||||
pass
|
||||
|
||||
def setup_handlers(self, application: Application):
|
||||
"""Setup all handlers"""
|
||||
self.application = application # Store application reference
|
||||
|
||||
# Add start command handler
|
||||
application.add_handler(CommandHandler("start", self.start))
|
||||
|
||||
# Add status command handler
|
||||
application.add_handler(CommandHandler("status", self.status))
|
||||
|
||||
|
||||
# Global admin bot instance
|
||||
_admin_bot_instance: Optional[AdminBot] = None
|
||||
|
||||
|
||||
def get_admin_bot() -> Optional[AdminBot]:
|
||||
"""Get global admin bot instance"""
|
||||
return _admin_bot_instance
|
||||
|
||||
|
||||
def init_admin_bot():
|
||||
"""Initialize admin bot"""
|
||||
global _admin_bot_instance
|
||||
if not _admin_bot_instance:
|
||||
_admin_bot_instance = AdminBot()
|
||||
return _admin_bot_instance
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function"""
|
||||
admin_bot = init_admin_bot()
|
||||
|
||||
# Create application with Long Polling configuration
|
||||
application = Application.builder().token(ADMINPANEL_TELEGRAM_BOT_TOKEN).build()
|
||||
|
||||
# Setup handlers
|
||||
admin_bot.setup_handlers(application)
|
||||
|
||||
# Set application reference
|
||||
admin_bot.application = application
|
||||
|
||||
# Add error handler
|
||||
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Log the error and send a telegram message to notify the developer."""
|
||||
logger.error(f"Exception while handling an update: {context.error}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
application.add_error_handler(error_handler)
|
||||
|
||||
# Add post_init hook to log bot info
|
||||
async def post_init(app: Application) -> None:
|
||||
bot_info = await app.bot.get_me()
|
||||
logger.info(f"Admin bot initialized: @{bot_info.username} (ID: {bot_info.id})")
|
||||
|
||||
application.post_init = post_init
|
||||
|
||||
# Start the bot with Long Polling
|
||||
logger.info("Starting Admin Panel Bot with Long Polling...")
|
||||
try:
|
||||
application.run_polling(
|
||||
poll_interval=1.0,
|
||||
timeout=30,
|
||||
drop_pending_updates=True,
|
||||
allowed_updates=["message", "callback_query"]
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Fatal error in run_polling: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
|
@ -13,12 +13,13 @@ from telegram.ext import (
|
|||
from config import (
|
||||
TELEGRAM_BOT_TOKEN, PERIOD_OPTIONS, POLL_INTERVAL,
|
||||
POLL_TIMEOUT, DROP_PENDING_UPDATES, ALLOWED_UPDATES,
|
||||
LICHESS_STATS_API_BASE_URL
|
||||
LICHESS_STATS_API_BASE_URL, ADMINPANEL_TELEGRAM_BOT_TOKEN
|
||||
)
|
||||
from database import Database
|
||||
from lichess_api import LichessAPI
|
||||
from formatters import StatsFormatter
|
||||
from i18n import t
|
||||
from admin_bot import get_admin_bot, init_admin_bot
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
|
|
@ -70,31 +71,60 @@ class LichessBot:
|
|||
|
||||
async def start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Start command handler"""
|
||||
logger.info(f"📝 start() method called for user {update.effective_user.id}")
|
||||
# Register user in database
|
||||
user = update.effective_user
|
||||
lang_code = user.language_code if user else None
|
||||
self.db.add_or_get_telegram_user(
|
||||
logger.info(f"User info: id={user.id}, username={user.username}, lang_code={lang_code}")
|
||||
is_new_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=lang_code
|
||||
)
|
||||
# Notify admin bot about new user
|
||||
if is_new_user:
|
||||
admin_bot = get_admin_bot()
|
||||
if admin_bot:
|
||||
await admin_bot.notify_new_user(
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
first_name=user.first_name
|
||||
)
|
||||
|
||||
lang = self.get_user_language_from_update(update)
|
||||
await update.message.reply_text(t('start_message', lang))
|
||||
start_msg = t('start_message', lang)
|
||||
await update.message.reply_text(start_msg)
|
||||
|
||||
async def start_and_addgamer(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Start command that automatically launches addgamer"""
|
||||
# First run the regular start command
|
||||
await self.start(update, context)
|
||||
# Then start addgamer conversation
|
||||
return await self.addgamer_start(update, context)
|
||||
"""Start command that shows welcome message and starts addgamer conversation"""
|
||||
try:
|
||||
# Run the regular start command
|
||||
await self.start(update, context)
|
||||
# Start addgamer conversation and return state
|
||||
return await self.addgamer_start(update, context)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in start_and_addgamer: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
try:
|
||||
await update.message.reply_text(f"Error: {e}")
|
||||
except:
|
||||
pass
|
||||
return ConversationHandler.END
|
||||
|
||||
async def addgamer_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Start addgamer command - simple username only"""
|
||||
logger.info(f"addgamer_start called for user {update.effective_user.id}")
|
||||
lang = self.get_user_language_from_update(update)
|
||||
await update.message.reply_text(t('addgamer_prompt', lang))
|
||||
try:
|
||||
await update.message.reply_text(t('addgamer_prompt', lang))
|
||||
logger.info(f"Addgamer prompt sent to user {update.effective_user.id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending addgamer prompt: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
return WAITING_FOR_USERNAME
|
||||
|
||||
async def addtoken_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
|
|
@ -126,6 +156,14 @@ class LichessBot:
|
|||
)
|
||||
else:
|
||||
# Add new gamer and link with token
|
||||
# Check if gamer already exists
|
||||
import sqlite3
|
||||
with sqlite3.connect(self.db.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT id FROM gamers WHERE username = ?", (username,))
|
||||
existing_gamer = cursor.fetchone()
|
||||
is_new_gamer = existing_gamer is None
|
||||
|
||||
gamer_id = self.db.add_gamer(username)
|
||||
self.db.add_user_gamer(user_id, gamer_id, token)
|
||||
|
||||
|
|
@ -137,6 +175,17 @@ class LichessBot:
|
|||
if len(user_gamers) == 1:
|
||||
self.db.set_user_active_gamer(user_id, gamer_id)
|
||||
|
||||
# Notify admin bot about new player (only if it's a new gamer)
|
||||
if is_new_gamer:
|
||||
admin_bot = get_admin_bot()
|
||||
if admin_bot:
|
||||
user_obj = update.effective_user
|
||||
await admin_bot.notify_new_player(
|
||||
player_username=username,
|
||||
added_by_user_id=user_id,
|
||||
added_by_username=user_obj.username if user_obj else None
|
||||
)
|
||||
|
||||
await update.message.reply_text(
|
||||
t('gamer_added_with_token', lang, username=username)
|
||||
)
|
||||
|
|
@ -175,6 +224,14 @@ class LichessBot:
|
|||
return WAITING_FOR_USERNAME
|
||||
|
||||
# Add gamer to database (without token)
|
||||
# Check if gamer already exists
|
||||
import sqlite3
|
||||
with sqlite3.connect(self.db.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT id FROM gamers WHERE username = ?", (username,))
|
||||
existing_gamer = cursor.fetchone()
|
||||
is_new_gamer = existing_gamer is None
|
||||
|
||||
gamer_id = self.db.add_gamer(username)
|
||||
# Link user to gamer (without token)
|
||||
self.db.add_user_gamer(user_id, gamer_id, None)
|
||||
|
|
@ -187,6 +244,28 @@ class LichessBot:
|
|||
if len(user_gamers) == 1:
|
||||
self.db.set_user_active_gamer(user_id, gamer_id)
|
||||
|
||||
# Notify admin bot about new player (only if it's a new gamer)
|
||||
if is_new_gamer:
|
||||
logger.info(f"New gamer detected: {username}, notifying admin bot...")
|
||||
admin_bot = get_admin_bot()
|
||||
if admin_bot:
|
||||
user_obj = update.effective_user
|
||||
try:
|
||||
await admin_bot.notify_new_player(
|
||||
player_username=username,
|
||||
added_by_user_id=user_id,
|
||||
added_by_username=user_obj.username if user_obj else None
|
||||
)
|
||||
logger.info(f"Admin bot notification sent for player {username}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to notify admin bot: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
else:
|
||||
logger.warning("Admin bot not available for notification")
|
||||
else:
|
||||
logger.info(f"Gamer {username} already exists, skipping admin notification")
|
||||
|
||||
lang = self.get_user_language_from_update(update)
|
||||
await update.message.reply_text(
|
||||
t('gamer_added', lang, username=username)
|
||||
|
|
@ -748,12 +827,13 @@ class LichessBot:
|
|||
addgamer_conv = ConversationHandler(
|
||||
entry_points=[
|
||||
CommandHandler("addgamer", self.addgamer_start),
|
||||
CommandHandler("start", self.start_and_addgamer) # Custom entry point that calls start and addgamer
|
||||
CommandHandler("start", self.start_and_addgamer) # Also handle /start to start addgamer flow
|
||||
],
|
||||
states={
|
||||
WAITING_FOR_USERNAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_username)],
|
||||
},
|
||||
fallbacks=[CommandHandler("cancel", lambda u, c: ConversationHandler.END)]
|
||||
fallbacks=[CommandHandler("cancel", lambda u, c: ConversationHandler.END)],
|
||||
name="addgamer_conversation"
|
||||
)
|
||||
|
||||
# Conversation handler for addtoken (token required)
|
||||
|
|
@ -765,8 +845,7 @@ class LichessBot:
|
|||
fallbacks=[CommandHandler("cancel", lambda u, c: ConversationHandler.END)]
|
||||
)
|
||||
|
||||
# Add all handlers
|
||||
# Note: start command is handled by addgamer_conv entry_points
|
||||
# Add conversation handlers
|
||||
application.add_handler(addgamer_conv)
|
||||
application.add_handler(addtoken_conv)
|
||||
application.add_handler(CommandHandler("getgamers", self.getgamers))
|
||||
|
|
@ -785,6 +864,9 @@ class LichessBot:
|
|||
|
||||
def main():
|
||||
"""Main function"""
|
||||
# Initialize admin bot for notifications (admin bot runs as separate service)
|
||||
init_admin_bot()
|
||||
|
||||
bot = LichessBot()
|
||||
|
||||
# Create application with Long Polling configuration
|
||||
|
|
@ -802,14 +884,29 @@ def main():
|
|||
|
||||
application.post_init = post_init
|
||||
|
||||
# Add error handler
|
||||
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Log the error and send a telegram message to notify the developer."""
|
||||
logger.error(f"Exception while handling an update: {context.error}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
application.add_error_handler(error_handler)
|
||||
|
||||
# Start the bot with Long Polling
|
||||
logger.info("Starting Lichess Statistics Bot with Long Polling...")
|
||||
application.run_polling(
|
||||
poll_interval=POLL_INTERVAL,
|
||||
timeout=POLL_TIMEOUT,
|
||||
drop_pending_updates=DROP_PENDING_UPDATES,
|
||||
allowed_updates=ALLOWED_UPDATES
|
||||
)
|
||||
try:
|
||||
application.run_polling(
|
||||
poll_interval=POLL_INTERVAL,
|
||||
timeout=POLL_TIMEOUT,
|
||||
drop_pending_updates=DROP_PENDING_UPDATES,
|
||||
allowed_updates=ALLOWED_UPDATES
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Fatal error in run_polling: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
raise
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ load_dotenv()
|
|||
# Telegram Bot Configuration
|
||||
TELEGRAM_BOT_TOKEN = "7903295042:AAGBO2k8pfBDy4RoLRFsknwE7z0N-thAPI8"
|
||||
|
||||
# Admin Panel Bot Configuration
|
||||
ADMINPANEL_TELEGRAM_BOT_TOKEN = "8588876086:AAHoZncfhTCbul1BblpvnZMzvz7jAYVFmcw"
|
||||
|
||||
# Lichess API Configuration
|
||||
LICHESS_API_BASE_URL = "https://lichess.org/api"
|
||||
LICHESS_STATS_API_BASE_URL = "http://localhost:8001" # For Docker container access
|
||||
|
|
@ -19,5 +22,5 @@ PERIOD_OPTIONS = [0, 15, 30, 60, 120, 180] # minutes
|
|||
# Telegram Bot Long Polling Configuration
|
||||
POLL_INTERVAL = 1.0 # seconds
|
||||
POLL_TIMEOUT = 30 # seconds
|
||||
DROP_PENDING_UPDATES = True
|
||||
DROP_PENDING_UPDATES = True # Drop pending updates on startup
|
||||
ALLOWED_UPDATES = ["message", "callback_query"]
|
||||
|
|
|
|||
|
|
@ -67,6 +67,15 @@ class Database:
|
|||
# Column already exists
|
||||
pass
|
||||
|
||||
# Create admin_settings table for admin bot configuration
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS admin_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Migrate tokens from gamers to user_gamers if needed
|
||||
|
|
@ -329,3 +338,27 @@ class Database:
|
|||
})
|
||||
|
||||
return gamers
|
||||
|
||||
def get_admin_chat_id(self) -> Optional[int]:
|
||||
"""Get admin chat ID from database"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT value FROM admin_settings WHERE key = 'admin_chat_id'")
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
try:
|
||||
return int(result[0])
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return None
|
||||
|
||||
def set_admin_chat_id(self, chat_id: int):
|
||||
"""Set admin chat ID in database"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO admin_settings (key, value, updated_at)
|
||||
VALUES ('admin_chat_id', ?, CURRENT_TIMESTAMP)
|
||||
''', (str(chat_id),))
|
||||
conn.commit()
|
||||
logger.info(f"Admin chat ID saved to database: {chat_id}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue