diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml index 10bc74d..ef44e77 100644 --- a/.forgejo/workflows/deploy.yml +++ b/.forgejo/workflows/deploy.yml @@ -33,7 +33,6 @@ jobs: docker push 192.168.8.174:3000/vrubel/lichess-api:latest docker push 192.168.8.174:3000/vrubel/lichess-bot:latest docker push 192.168.8.174:3000/vrubel/lichess-admin:latest - docker push 192.168.8.174:3000/vrubel/lichess-web:latest - name: Transfer images to prod run: | @@ -41,7 +40,6 @@ jobs: 192.168.8.174:3000/vrubel/lichess-api:latest \ 192.168.8.174:3000/vrubel/lichess-bot:latest \ 192.168.8.174:3000/vrubel/lichess-admin:latest \ - 192.168.8.174:3000/vrubel/lichess-web:latest \ | ssh -i /data/.ssh/id_ed25519 -o StrictHostKeyChecking=no root@130.17.6.76 docker load - name: Deploy on prod host diff --git a/.gitignore b/.gitignore index 6cb6333..0b371e1 100644 --- a/.gitignore +++ b/.gitignore @@ -44,9 +44,6 @@ logs/ .env.local .env.backup -# Auth config (contains passwords) -LichessWebView/auth_config.json - # Node modules (если будут использоваться) node_modules/ diff --git a/LichessWebView/Dockerfile b/LichessWebView/Dockerfile deleted file mode 100644 index 23a2103..0000000 --- a/LichessWebView/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . - -EXPOSE 5000 - -CMD ["python", "app.py"] - diff --git a/LichessWebView/app.py b/LichessWebView/app.py deleted file mode 100644 index 94a2158..0000000 --- a/LichessWebView/app.py +++ /dev/null @@ -1,317 +0,0 @@ -from flask import Flask, jsonify, render_template, request, session, redirect, url_for -from flask_cors import CORS -import sqlite3 -from datetime import datetime, date -from functools import wraps -import os -import auth_config - -app = Flask(__name__) -# Secret key для сессий (в продакшене лучше использовать переменную окружения) -app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'your-secret-key-change-in-production-please') -CORS(app) - -# Путь к базе данных бота -DB_PATH = "/app/data/lichess_bot.db" - - -def login_required(f): - """Декоратор для защиты роутов, требующих аутентификации""" - @wraps(f) - def decorated_function(*args, **kwargs): - if 'authenticated' not in session or not session['authenticated']: - return redirect(url_for('login')) - return f(*args, **kwargs) - return decorated_function - -def get_message_counters_stats(db_path): - """Get message counters statistics directly from database""" - try: - with sqlite3.connect(db_path) as conn: - cursor = conn.cursor() - - # Check if table exists - cursor.execute(''' - SELECT name FROM sqlite_master - WHERE type='table' AND name='message_counters' - ''') - if not cursor.fetchone(): - # Table doesn't exist, return empty stats - return { - 'total_all_time': 0, - 'total_today': 0, - 'by_command': {} - } - - # Reset daily counters if needed - today = date.today() - cursor.execute(''' - SELECT command, last_reset_date FROM message_counters - ''') - - for row in cursor.fetchall(): - cmd, last_reset = row[0], row[1] - if last_reset: - try: - last_reset_date = datetime.strptime(last_reset, '%Y-%m-%d').date() - if last_reset_date < today: - cursor.execute(''' - UPDATE message_counters - SET today_count = 0, last_reset_date = ? - WHERE command = ? - ''', (today.isoformat(), cmd)) - except (ValueError, TypeError): - cursor.execute(''' - UPDATE message_counters - SET today_count = 0, last_reset_date = ? - WHERE command = ? - ''', (today.isoformat(), cmd)) - else: - cursor.execute(''' - UPDATE message_counters - SET last_reset_date = ? - WHERE command = ? - ''', (today.isoformat(), cmd)) - - conn.commit() - - # Get all counters - cursor.execute(''' - SELECT command, total_count, today_count - FROM message_counters - ORDER BY command - ''') - - stats = { - 'by_command': {}, - 'total_all_time': 0, - 'total_today': 0 - } - - for row in cursor.fetchall(): - cmd, total, today = row[0], row[1], row[2] - stats['by_command'][cmd] = { - 'total': total, - 'today': today - } - stats['total_all_time'] += total - stats['total_today'] += today - - return { - 'total_all_time': stats['total_all_time'], - 'total_today': stats['total_today'], - 'by_command': stats['by_command'] - } - except Exception as e: - print(f"Error getting message counters: {e}") - import traceback - print(traceback.format_exc()) - return { - 'total_all_time': 0, - 'total_today': 0, - 'by_command': {} - } - -@app.route('/login', methods=['GET', 'POST']) -def login(): - """Страница входа""" - if request.method == 'POST': - username = request.form.get('username', '').strip() - password = request.form.get('password', '') - - if auth_config.validate_credentials(username, password): - session['authenticated'] = True - session['username'] = username - return redirect(url_for('index')) - else: - error_msg = 'Неверный логин или пароль' - return redirect(url_for('login', error=error_msg)) - - # GET запрос - показать форму логина - error = request.args.get('error', '') - # Если уже авторизован, перенаправляем на главную - if 'authenticated' in session and session.get('authenticated'): - return redirect(url_for('index')) - return render_template('login.html', error=error) - - -@app.route('/logout') -def logout(): - """Выход из системы""" - session.pop('authenticated', None) - session.pop('username', None) - return redirect(url_for('login')) - - -@app.route('/') -@login_required -def index(): - """Главная страница""" - return render_template('index.html') - -@app.route('/api/users') -@login_required -def get_users(): - """Получить всех пользователей""" - try: - with sqlite3.connect(DB_PATH) as conn: - cursor = conn.cursor() - - # Получаем всех пользователей - cursor.execute(''' - SELECT - tu.user_id, - tu.username, - tu.first_name, - tu.last_name, - tu.created_at, - tu.bot_language, - COUNT(ug.id) as gamer_count, - SUM(CASE WHEN ug.period_minutes > 0 THEN 1 ELSE 0 END) as monitored_gamers - FROM telegram_users tu - LEFT JOIN user_gamers ug ON tu.user_id = ug.user_id - GROUP BY tu.user_id, tu.username, tu.first_name, tu.last_name, tu.created_at, tu.bot_language - ORDER BY tu.created_at DESC - ''') - - rows = cursor.fetchall() - users = [] - - for row in rows: - users.append({ - 'user_id': row[0], - 'username': row[1] or '-', - 'first_name': row[2] or '-', - 'last_name': row[3], - 'created_at': row[4], - 'bot_language': row[5] or 'en', # Default to 'en' if not set - 'gamer_count': row[6], - 'monitored_gamers': row[7] - }) - - # Получаем общее количество игроков (уникальных) - cursor.execute(''' - SELECT COUNT(DISTINCT g.id) - FROM gamers g - ''') - total_gamers = cursor.fetchone()[0] - - # Count new users today - today = date.today().isoformat() - cursor.execute("SELECT COUNT(*) FROM telegram_users WHERE DATE(created_at) = ?", (today,)) - users_today = cursor.fetchone()[0] - - # Count users without gamers - cursor.execute(""" - SELECT COUNT(DISTINCT tu.user_id) - FROM telegram_users tu - LEFT JOIN user_gamers ug ON tu.user_id = ug.user_id - WHERE ug.id IS NULL - """) - users_without_gamers = cursor.fetchone()[0] - - # Count users with Russian language - cursor.execute(""" - SELECT COUNT(DISTINCT tu.user_id) - FROM telegram_users tu - WHERE tu.bot_language = 'ru' - """) - users_with_ru_language = cursor.fetchone()[0] - - # Calculate percentages - total_users = len(users) - users_without_gamers_percent = round((users_without_gamers / total_users * 100)) if total_users > 0 else 0 - users_ru_language_percent = round((users_with_ru_language / total_users * 100)) if total_users > 0 else 0 - - # Count new gamers today (from user_gamers table) - cursor.execute(""" - SELECT COUNT(DISTINCT g.id) - FROM user_gamers ug - JOIN gamers g ON ug.gamer_id = g.id - WHERE DATE(ug.created_at) = ? - """, (today,)) - gamers_today = cursor.fetchone()[0] - - # Get message counters statistics - message_stats = get_message_counters_stats(DB_PATH) - - # Filter out excluded commands (same as in admin_bot) - excluded_commands = {'lang', 'resetlang', 'start'} - if message_stats.get('by_command'): - filtered_by_command = { - cmd: data for cmd, data in message_stats['by_command'].items() - if cmd not in excluded_commands - } - message_stats['by_command'] = filtered_by_command - - return jsonify({ - 'success': True, - 'users': users, - 'total_users': total_users, - 'users_today': users_today, - 'users_without_gamers': users_without_gamers, - 'users_without_gamers_percent': users_without_gamers_percent, - 'users_with_ru_language': users_with_ru_language, - 'users_ru_language_percent': users_ru_language_percent, - 'total_gamers': total_gamers, - 'gamers_today': gamers_today, - 'message_stats': message_stats - }) - - except Exception as e: - return jsonify({ - 'success': False, - 'error': str(e) - }), 500 - -@app.route('/api/users//gamers') -@login_required -def get_user_gamers(user_id): - """Получить игроков конкретного пользователя""" - try: - with sqlite3.connect(DB_PATH) as conn: - cursor = conn.cursor() - - # Получаем игроков пользователя - cursor.execute(''' - SELECT - g.id, - g.username, - ug.token, - ug.period_minutes, - ug.created_at - FROM user_gamers ug - JOIN gamers g ON ug.gamer_id = g.id - WHERE ug.user_id = ? - ORDER BY g.username - ''', (user_id,)) - - rows = cursor.fetchall() - gamers = [] - - for row in rows: - gamers.append({ - 'id': row[0], - 'username': row[1], - 'has_token': bool(row[2]), # Token from user_gamers, not gamers - 'period_minutes': row[3], - 'created_at': row[4] - }) - - return jsonify({ - 'success': True, - 'gamers': gamers - }) - - except Exception as e: - return jsonify({ - 'success': False, - 'error': str(e) - }), 500 - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) - - - - diff --git a/LichessWebView/auth_config.example.json b/LichessWebView/auth_config.example.json deleted file mode 100644 index 642bee2..0000000 --- a/LichessWebView/auth_config.example.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "users": [ - { - "username": "admin", - "password": "change_me_123" - }, - { - "username": "user2", - "password": "another_password" - } - ] -} - - diff --git a/LichessWebView/auth_config.py b/LichessWebView/auth_config.py deleted file mode 100644 index e72e086..0000000 --- a/LichessWebView/auth_config.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Модуль для загрузки конфигурации аутентификации""" -import json -import os -from typing import Dict, List, Optional - -AUTH_CONFIG_PATH = "/app/auth_config.json" -AUTH_CONFIG_EXAMPLE_PATH = "/app/auth_config.example.json" - - -def load_auth_config() -> Optional[Dict]: - """ - Загружает конфигурацию аутентификации из файла. - Возвращает None, если файл не найден. - """ - config_path = AUTH_CONFIG_PATH - - # Если файл не существует, пробуем example - if not os.path.exists(config_path): - if os.path.exists(AUTH_CONFIG_EXAMPLE_PATH): - print(f"Warning: {config_path} not found, using example config") - config_path = AUTH_CONFIG_EXAMPLE_PATH - else: - print(f"Error: Neither {AUTH_CONFIG_PATH} nor {AUTH_CONFIG_EXAMPLE_PATH} found") - return None - - try: - with open(config_path, 'r', encoding='utf-8') as f: - config = json.load(f) - return config - except json.JSONDecodeError as e: - print(f"Error parsing auth config: {e}") - return None - except Exception as e: - print(f"Error loading auth config: {e}") - return None - - -def get_users() -> List[Dict[str, str]]: - """ - Возвращает список пользователей из конфига. - """ - config = load_auth_config() - if not config: - return [] - - return config.get('users', []) - - -def validate_credentials(username: str, password: str) -> bool: - """ - Проверяет правильность логина и пароля. - """ - users = get_users() - - for user in users: - if user.get('username') == username and user.get('password') == password: - return True - - return False - - diff --git a/LichessWebView/requirements.txt b/LichessWebView/requirements.txt deleted file mode 100644 index f1fcb6f..0000000 --- a/LichessWebView/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Flask==3.0.0 -flask-cors==4.0.0 - - - - diff --git a/LichessWebView/templates/index.html b/LichessWebView/templates/index.html deleted file mode 100644 index 565a22f..0000000 --- a/LichessWebView/templates/index.html +++ /dev/null @@ -1,597 +0,0 @@ - - - - - - Lichess Bot Users Monitor - - - -
- -
-
-

👥 Пользователи

- Выход -
- -
- Всего пользователей: 0 (сегодня: 0)
- 👤 Пользователей без игроков: 0 (0%)
- 🇷🇺 Пользователей с русским языком: 0 (0%)
- Кол-во игроков: 0 (сегодня: 0) -
- -
-

📨 Счетчики сообщений

-
- Всего отправлено: 0 -
-
- Сегодня отправлено: 0 -
-
-
-
-
За все время:
-
-
-
-
За сегодня:
-
-
-
-
-
- - - -
-
Загрузка...
-
-
- - -
-

🎮 Отслеживаемые игроки

- - - -
-
Выберите пользователя для просмотра его игроков
-
-
-
- - - - - - - - diff --git a/LichessWebView/templates/login.html b/LichessWebView/templates/login.html deleted file mode 100644 index 4f15abc..0000000 --- a/LichessWebView/templates/login.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - Вход в админ-панель - - - -
-

🔐 Вход в админ-панель

- -
- -
- {% if error %} -
{{ error }}
- {% endif %} -
- - -
- -
- - -
- - -
-
- - - - - diff --git a/README.md b/README.md index f14ad50..d7a6c73 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ # Lichess Statistics Ecosystem -Полнофункциональная система для отслеживания статистики игроков Lichess.org с Telegram ботом и веб-интерфейсом. +Полнофункциональная система для отслеживания статистики игроков Lichess.org с Telegram ботом. ## 🎯 Описание проекта -Система состоит из трех взаимосвязанных компонентов: +Система состоит из двух взаимосвязанных компонентов: 1. **LichessWebServices** - REST API для получения статистики игроков Lichess 2. **LichessClientTG_bot** - Telegram бот для управления подписками и уведомлений -3. **LichessWebView** - Веб-интерфейс для просмотра пользователей и их игроков ## 🏗️ Архитектура @@ -21,15 +20,13 @@ │ │ (Управление подписками, уведомления) │ │ │ └────────┬───────────────────────────────────────────┘ │ │ │ │ -│ ├──────────────────────┐ │ -│ │ │ │ -│ ┌────────▼──────────┐ ┌──────▼──────────────┐ │ -│ │ LichessWebServices │ │ LichessWebView │ │ -│ │ │ │ │ │ -│ │ REST API для │ │ Веб-интерфейс │ │ -│ │ получения │ │ для просмотра │ │ -│ │ статистики │ │ пользователей │ │ -│ └────────┬───────────┘ └────────────────────┘ │ +│ ┌────────▼───────────┐ │ +│ │ LichessWebServices │ │ +│ │ │ │ +│ │ REST API для │ │ +│ │ получения │ │ +│ │ статистики │ │ +│ └────────┬────────────┘ │ │ │ │ └───────────┼──────────────────────────────────────────────┘ │ @@ -59,7 +56,6 @@ cd LichessStatTgWeb После запуска доступны: - **API документация**: http://localhost:8001/docs -- **Веб-интерфейс**: http://localhost:5000 - **Telegram бот**: работает в фоне ## 📦 Структура проекта @@ -79,11 +75,6 @@ LichessStatTgWeb/ │ ├── formatters.py # Форматирование ответов │ └── config.py # Конфигурация │ -├── LichessWebView/ # Веб-интерфейс -│ ├── app.py # Flask приложение -│ └── templates/ # HTML шаблоны -│ └── index.html # Главная страница -│ ├── docker-compose.yml # Общая конфигурация контейнеров ├── start.sh # Скрипт запуска всех сервисов └── README.md # Этот файл @@ -142,19 +133,6 @@ Telegram бот для управления отслеживанием игро **Подробнее о боте:** см. [about.md](about.md) -### 3. LichessWebView (Веб-интерфейс) - -Веб-интерфейс для просмотра пользователей и их игроков. - -**Возможности:** -- Просмотр всех пользователей бота -- Фильтрация пользователей по имени/никнейму -- Отображение игроков каждого пользователя -- Статистика по каждому пользователю -- Отображение активных игроков и периодов -- Отображение языка пользователя (🇷🇺 ru / 🇬🇧 en) -- Статистика пользователей с русским языком (количество и процент) - ## 🗄️ База данных Система использует SQLite базу данных с таблицами: @@ -187,20 +165,15 @@ docker-compose down ### Telegram бот -Токены ботов настраиваются в `LichessClientTG_bot/config.py`: -```python -IS_PROD = False # True для продакшн, False для тестовых токенов - -# Production токены (используются при IS_PROD = True) -TELEGRAM_BOT_TOKEN_PROD = "YOUR_PROD_TOKEN" -ADMINPANEL_TELEGRAM_BOT_TOKEN_PROD = "YOUR_ADMIN_PROD_TOKEN" - -# Test токены (используются при IS_PROD = False) -TELEGRAM_BOT_TOKEN_TEST = "YOUR_TEST_TOKEN" -ADMINPANEL_TELEGRAM_BOT_TOKEN_TEST = "YOUR_ADMIN_TEST_TOKEN" +Токены ботов задаются переменными окружения в `.env` (см. `.env.example`): +``` +TELEGRAM_BOT_TOKEN=YOUR_BOT_TOKEN +ADMINPANEL_TELEGRAM_BOT_TOKEN=YOUR_ADMIN_BOT_TOKEN ``` -Система автоматически выбирает нужные токены в зависимости от значения `IS_PROD`. +`LichessClientTG_bot/config.py` читает их через `os.getenv(...)`. Разделение на тестовый и +продакшн стенды обеспечивается не флагом в коде, а раздельными `.env` на каждом окружении: +на локальном/тестовом стенде — тестовые токены, на проде — свои, боевые. ### Lichess API @@ -231,10 +204,6 @@ docker-compose up -d # Telegram бот cd LichessClientTG_bot docker-compose up -d - -# Веб-интерфейс -cd LichessWebView -docker-compose up -d ``` ### Логи @@ -245,9 +214,6 @@ docker logs lichesswebservices_lichess-api_1 -f # Логи бота docker logs lichess-telegram-bot -f - -# Логи веб-интерфейса -docker logs lichess-web-view -f ``` ## 📝 Лицензия diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e483a0c..3c170d9 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -50,17 +50,6 @@ services: depends_on: - lichess-api - web-view: - image: 192.168.8.174:3000/vrubel/lichess-web:latest - container_name: lichess-web-view - ports: - - "5000:5000" - volumes: - - ./data:/app/data:ro - restart: always - depends_on: - - lichess-bot - networks: default: name: lichess-network diff --git a/docker-compose.yml b/docker-compose.yml index 10c187e..0f25aac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,20 +62,6 @@ services: depends_on: - lichess-api - # Web View Interface - web-view: - build: ./LichessWebView - image: 192.168.8.174:3000/vrubel/lichess-web:latest - container_name: lichess-web-view - ports: - - "5000:5000" - volumes: - - ./LichessClientTG_bot/data:/app/data:ro - - ./LichessWebView:/app - restart: always - depends_on: - - lichess-bot - networks: default: name: lichess-network diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 779d6cb..46cb0bd 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2,11 +2,10 @@ ## Обзор проекта -Проект состоит из трех основных компонентов: +Проект состоит из двух основных компонентов: 1. **LichessWebServices** - REST API для получения статистики с Lichess 2. **LichessClientTG_bot** - Telegram бот для отслеживания игроков -3. **LichessWebView** - Веб-интерфейс для просмотра пользователей и игроков ## Структура базы данных @@ -116,17 +115,15 @@ CREATE TABLE user_gamers ( ## Docker Compose -Все три сервиса запускаются через единый `docker-compose.yml`: +Оба сервиса запускаются через единый `docker-compose.yml`: - **lichess-api**: API на порту 8001 - **lichess-telegram-bot**: Бот (host network) -- **lichess-web-view**: Веб-интерфейс на порту 5000 База данных монтируется как bind mount: `./LichessClientTG_bot/data:/app/data` ## Безопасность - Токены Lichess API хранятся в базе данных и используются только для запросов к Lichess -- Веб-интерфейс работает **без пароля** - доступен всем кто знает URL - Telegram бот токен хранится в `config.py` (можно вынести в `.env`) diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index f9717a3..f1073f7 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -41,13 +41,12 @@ chmod +x start.sh - Остановит существующие контейнеры (если есть) - Создаст бэкап базы данных - Пересоберет Docker образы -- Запустит все три сервиса +- Запустит оба сервиса ### Шаг 4: Проверка Откройте в браузере: - **API документация**: http://localhost:8001/docs -- **Веб-интерфейс**: http://localhost:5000 Проверьте логи: @@ -127,12 +126,6 @@ docker logs lichess-telegram-bot -f docker logs lichess-api -f ``` -### Только веб-интерфейс - -```bash -docker logs lichess-web-view -f -``` - ## Остановка и удаление ### Остановка без удаления данных @@ -179,7 +172,6 @@ chmod +x import_db.sh start.sh ```bash # Найдите процесс на порту sudo fuser -k 8001/tcp # Для API -sudo fuser -k 5000/tcp # Для веб-интерфейса # Перезапустите ./start.sh @@ -212,7 +204,6 @@ LichessStatTgWeb/ ├── LichessClientTG_bot/ # Telegram бот │ └── data/ │ └── lichess_bot.db # База данных (НЕ в git!) -├── LichessWebView/ # Веб-интерфейс ├── backups/ # Бэкапы БД (НЕ в git!) ├── docs/ # Документация ├── docker-compose.yml # Конфигурация Docker diff --git a/start.sh b/start.sh index daed230..c355bbd 100755 --- a/start.sh +++ b/start.sh @@ -54,7 +54,6 @@ echo "✅ Все сервисы запущены!" echo "" echo "🌐 Доступные сервисы:" echo " - API документация: http://localhost:8002/docs" -echo " - Веб-интерфейс: http://localhost:5000" echo "" echo "📋 Для просмотра логов используйте:" echo " ${COMPOSE_CMD_DISPLAY} logs -f"