feat: добавлены комплексные тесты для системы модерации

This commit is contained in:
2025-10-18 15:57:44 +03:00
parent fd77c24d6b
commit 69c0162c6b
14 changed files with 2672 additions and 56 deletions
+230
View File
@@ -0,0 +1,230 @@
# 🚀 Telegram Bot с системой отчетов об ошибках и ИИ-анализом
Многофункциональный Telegram бот на Python с продвинутой системой ранжирования пользователей, играми и интеграцией с внешними API.
[![Python Version](https://img.shields.io/badge/python-3.10+-blue.svg)](https://python.org)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
## ✨ Основные возможности
### 🤖 Система отчетов об ошибках с ИИ
- **Умные отчеты** - администраторы могут отправлять детальные отчеты об ошибках
- **ИИ-анализ** - автоматический анализ проблем с помощью OpenAI GPT
- **Автоматическая интеграция** - обработанные ошибки попадают в план разработки
- **Уведомления разработчика** - мгновенные оповещения о новых ошибках
### 🤝 Интерактивные функции
- **Автореакции** - автоматическая реакция 🤝 на сообщения с "+"
- **Повышение вовлеченности** - интерактивные элементы для активного общения
### 🎮 Игровые возможности
- **Классические игры**: Камень-ножницы-бумага, Крестики-нолики, Викторина
- **Расширенные игры**: Морской бой, 2048, Тетрис, Змейка
- **Система достижений** - разблокировка значков и начисление очков
### 🤝 Интерактивные функции
- **Автореакции** - автоматическая реакция 🤝 на сообщения с "+"
- **Повышение вовлеченности** - интерактивные элементы для активного общения
### 📊 Система ранжирования
- **Военная иерархия** - от Рядового до Маршала
- **Рейтинговые таблицы** - топ пользователей по очкам
- **Достижения** - система значков и наград
### 🛠 Администрирование
- **Модерация** - бан, мут, кик, предупреждения пользователей
- **Планировщик постов** - автоматическая публикация по расписанию
- **Импорт пользователей** - загрузка данных из CSV файлов
### 🌐 Интеграция с API
- **Погода** - актуальная информация через OpenWeatherMap
- **Новости** - свежие новости через NewsAPI
- **ИИ-анализ** - интеллектуальная обработка ошибок через OpenAI
## 🚀 Быстрый старт
### Предварительные требования
```bash
Python 3.10+
pip install -r requirements.txt
```
### Настройка
1. **Склонируйте репозиторий**
```bash
git clone <repository-url>
cd telegram_bot
```
2. **Настройте API ключи**
```bash
cp config_template.py config_local.py
# Отредактируйте config_local.py с вашими ключами
```
3. **Запустите бота**
```bash
python bot.py
```
4. **Протестируйте систему**
```bash
python test_error_system.py
```
## 📋 Команды администраторов
### Система отчетов об ошибках
- `/report_error <тип> <заголовок> [описание]` - отправить отчет об ошибке
- `/admin_errors [статус]` - показать список ошибок
- `/analyze_error_ai <ID>` - проанализировать ошибку ИИ
- `/add_error_to_todo <ID>` - добавить в план разработки
### Модерация
- `/warn [пользователь] [причина]` - выдать предупреждение
- `/ban [пользователь] [причина]` - заблокировать пользователя
- `/mute [пользователь] [время]` - временно заглушить
### Управление контентом
- `/schedule_post [время] [текст]` - запланировать публикацию
- `/list_posts` - показать запланированные посты
## 🧪 Тестирование
Проект включает встроенный тестовый скрипт для проверки функциональности:
```bash
python test_error_system.py
```
**Что тестируется:**
- ✅ Подключение к базе данных
- ✅ Работа с таблицей ошибок
- ✅ Система фильтрации и поиска
- ✅ Интеграция с ИИ (если настроен)
- ✅ Работа с TODO файлом
- ✅ Система уведомлений
## 📁 Структура проекта
```
telegram_bot/
├── bot.py # Основной файл бота
├── database_sqlite.py # Работа с БД (+ таблица errors)
├── config_local.py # Ваши API ключи (не публикуется)
├── config_template.py # Шаблон для настройки
├── test_error_system.py # Тестовый скрипт
├── requirements.txt # Зависимости Python
├── TODO.md # План разработки
├── README.md # Полная документация
└── .gitignore # Игнорируемые файлы
```
## 🔧 Настройка API ключей
### Обязательные ключи:
- **Telegram Bot Token** - получите от [@BotFather](https://t.me/botfather)
### Опциональные ключи:
- **OpenWeatherMap API** - для команды `/weather`
- **NewsAPI** - для команды `/news`
- **OpenAI API** - для анализа ошибок ИИ
### Получение ключей:
1. Скопируйте `config_template.py` в `config_local.py`
2. Заполните реальными ключами API
3. Никогда не публикуйте `config_local.py` в открытый доступ
## 📊 База данных
Автоматически создается файл `telegram_bot.db` со следующими таблицами:
- **users** - пользователи и их статистика
- **errors** - система отчетов об ошибках
- **warnings** - предупреждения пользователей
- **games** - игровые сессии
- **scheduled_posts** - запланированные публикации
## 🤝 Как внести вклад
1. Сделайте форк проекта
2. Создайте ветку для вашей функции (`git checkout -b feature/amazing-feature`)
3. Закоммитьте изменения (`git commit -m 'Add amazing feature'`)
4. Отправьте в ветку (`git push origin feature/amazing-feature`)
5. Создайте Pull Request
## 📝 Лицензия
Этот проект распространяется под лицензией MIT - см. файл [LICENSE](LICENSE) для подробностей.
## 🙏 Поддержка проекта
Если вам нравится этот бот, вы можете поддержать разработку:
- ⭐ Поставьте звезду на GitHub
- 🐛 Сообщайте об ошибках через систему отчетов
- 💡 Предлагайте новые функции
- 🔄 Делайте форки и улучшайте код
---
## 📋 Быстрый запуск
1. **Клонируйте репозиторий**
```bash
git clone <repository-url>
cd telegram_bot
```
2. **Настройте API ключи**
```bash
cp config_template.py config_local.py
# Отредактируйте config_local.py
```
3. **Установите зависимости**
```bash
pip install -r requirements.txt
```
4. **Запустите бота**
```bash
python bot.py
```
5. **Протестируйте систему**
```bash
python test_error_system.py
```
## 🔧 Основные команды администраторов
### Система отчетов об ошибках
- `/report_error <тип> <заголовок> [описание]` - отправить отчет
- `/admin_errors [статус]` - просмотреть ошибки
- `/analyze_error_ai <ID>` - проанализировать с ИИ
- `/add_error_to_todo <ID>` - добавить в план разработки
### Типы ошибок
- `bug` - ошибка в работе бота
- `crash` - критическая ошибка/падение
- `feature` - предложение новой функции
- `ui` - проблема интерфейса
- `security` - проблема безопасности
- `improvement` - предложение улучшения
## 🚀 Развертывание
### Docker (рекомендуется)
```bash
docker-compose up -d
```
### Локальный запуск
```bash
# Основной бот
python bot.py
# Планировщик постов (в другом терминале)
python scheduler.py
```
**Создано с ❤️ для сообщества Telegram ботов**
+75
View File
@@ -0,0 +1,75 @@
name: Deploy Telegram Bot
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Create test config
run: |
cp config_template.py config_local.py
# Здесь можно добавить настройку тестовых ключей
- name: Run tests
run: |
python test_error_system.py
- name: Run database tests
run: |
python -c "from database_sqlite import Database; db = Database(); print('Database connection successful')"
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Setup bot configuration
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
DEVELOPER_CHAT_ID: ${{ secrets.DEVELOPER_CHAT_ID }}
run: |
echo "BOT_TOKEN=\"$BOT_TOKEN\"" > config_local.py
echo "OPENAI_API_KEY=\"$OPENAI_API_KEY\"" >> config_local.py
echo "DEVELOPER_CHAT_ID=\"$DEVELOPER_CHAT_ID\"" >> config_local.py
echo "ENABLE_DEVELOPER_NOTIFICATIONS=True" >> config_local.py
echo "ENABLE_AI_ERROR_PROCESSING=True" >> config_local.py
- name: Run database migrations
run: |
python -c "from database_sqlite import Database; db = Database(); print('Database initialized successfully')"
- name: Start bot (test mode)
run: |
timeout 10 python bot.py || echo "Bot startup test completed"
+35 -29
View File
@@ -1,25 +1,47 @@
# Файлы базы данных
# Конфиденциальные файлы с токенами и ключами API
config_local.py
telegram_bot.db
*.db
*.sqlite
*.sqlite3
# Файлы Python
# Временные файлы
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Виртуальное окружение
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Файлы конфигурации с чувствительной информацией
config_local.py
.env
.env.local
.env.*.local
# IDE файлы
.vscode/
.idea/
*.swp
*.swo
*~
# Логи
*.log
@@ -28,10 +50,9 @@ logs/
# Временные файлы
*.tmp
*.temp
.tmp/
temp/
.cache/
# Файлы ОС
# OS файлы
.DS_Store
.DS_Store?
._*
@@ -40,22 +61,7 @@ temp/
ehthumbs.db
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# CSV файлы с данными пользователей (если не нужны в репозитории)
chat_*.csv
# Документация сборки
build/
dist/
*.egg-info/
# Токены и ключи API (если случайно попадут)
*token*
*api_key*
*secret*
# Конфиденциальные ключи (дополнительно)
*_api_key.txt
*_token.txt
.secrets
+32
View File
@@ -0,0 +1,32 @@
FROM python:3.10-slim
# Устанавливаем рабочую директорию
WORKDIR /app
# Устанавливаем системные зависимости
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Копируем файлы зависимостей
COPY requirements.txt .
# Устанавливаем Python зависимости
RUN pip install --no-cache-dir -r requirements.txt
# Копируем исходный код
COPY . .
# Создаем непривилегированного пользователя
RUN useradd --create-home --shell /bin/bash bot
RUN chown -R bot:bot /app
USER bot
# Создаем директорию для базы данных
RUN mkdir -p /app/data
# Указываем объем для базы данных
VOLUME ["/app/data"]
# Команда запуска бота
CMD ["python", "bot.py"]
+254 -19
View File
@@ -4,14 +4,24 @@
## 📋 История версий
### Версия 1.4.0 (16 октября 2025) - Критические исправления
**Дата релиза:** 16.10.2025
### Версия 1.5.0 (18 октября 2025) - Система модерации медиафайлов
**Дата релиза:** 18.10.2025
#### 🚨 Критические исправления безопасности и стабильности
-**Исправлена система рангов** - синхронизированы поля очков и репутации в базе данных
-**Улучшена обработка ошибок внешних API** - добавлены таймауты, детальная обработка HTTP-ошибок
-**Добавлена валидация входящих данных** - защита от некорректных данных и злоупотреблений
-**Улучшена обработка исключений в БД** - валидация параметров, автоматический rollback транзакций
#### 🆕 Новая система модерации медиафайлов
-**Автоматическая модерация аудио и видео** - все загружаемые медиафайлы проходят модерацию
-**Система уведомлений администраторов** - личные уведомления о новых медиафайлах для модерации
-**Интеграция с планировщиком постов** - одобренные медиа автоматически планируются на публикацию
-**Отсрочка публикации** - медиа публикуется через 8 часов после одобрения (настраиваемо)
-**Транскрибация аудио** - автоматическое распознавание речи для модерации
-**Интерфейс выбора действий** - администраторы могут одобрить, отклонить или запланировать медиа
-**Автоматическое удаление** - оригинальные сообщения с медиа удаляются из чата
-**Пересылка в группу модераторов** - медиа отправляется в специальную группу для рассмотрения
#### 🔧 Улучшения системы
-**Расширенная обработка медиа** - поддержка аудио, видео и документов
-**Система метаданных** - сохранение информации о файлах (длительность, размер, транскрипция)
-**Уведомления пользователей** - пользователи получают информацию об отклонении контента
-**Гибкие настройки модерации** - выбор между немедленной публикацией и отсрочкой
#### Исправления ошибок
- ✅ Исправлена ошибка с повторяющимся сообщением о "первом сообщении" при вводе суммы доната
@@ -48,6 +58,7 @@
- ✅ Приветствие новых пользователей при добавлении в группу с правилами группы
- ✅ Автоматическое удаление приветственных сообщений через 2 минуты
- ✅ Интеграция донатов в приветственные сообщения
-**Автореакции** - автоматическая реакция 🤝 на сообщения с "+" для повышения вовлеченности
### Безопасность и надежность
- 🔒 **Валидация входящих данных** - все команды проверяют корректность параметров
@@ -87,6 +98,20 @@
- 💰 Интеграция кнопок донатов прямо в приветственные сообщения
- 🎯 Быстрый доступ к помощи через приветственные кнопки
### 🤝 Интерактивные функции пользователей
- 👍 **Автореакции** - автоматическая реакция 🤝 на сообщения содержащие "+"
- 🔄 **Отзывчивость** - бот реагирует на положительные сообщения пользователей
- 📈 **Вовлеченность** - повышение активности в чате через интерактивные элементы
### Система отчетов об ошибках и ИИ-анализа
- 🚨 **Отчеты об ошибках** - администраторы могут отправлять отчеты об ошибках бота
- 📊 **Управление ошибками** - просмотр, фильтрация и управление списком ошибок
- 🤖 **ИИ-анализ** - автоматический анализ ошибок с помощью OpenAI GPT
- 📝 **Интеграция с TODO** - автоматическое добавление обработанных ошибок в список задач разработки
- 🔔 **Уведомления разработчика** - мгновенные уведомления о новых ошибках и их статусе
- 🧠 **Автоматизированная обработка** - пакетная обработка ошибок ИИ и добавление в план разработки
- 🧪 **Тестирование системы** - встроенный тестовый скрипт для проверки функциональности
## Установка и настройка
### 1. Клонирование репозитория
@@ -107,13 +132,20 @@ pip install -r requirements.txt
- **Telegram Bot Token**: Получите от [@BotFather](https://t.me/botfather)
- **OpenWeatherMap API Key**: Зарегистрируйтесь на [openweathermap.org](https://openweathermap.org/api)
- **NewsAPI Key**: Зарегистрируйтесь на [newsapi.org](https://newsapi.org/)
- **OpenAI API Key**: Зарегистрируйтесь на [platform.openai.com](https://platform.openai.com/) для анализа ошибок ИИ
Обновите `config.py` с вашими ключами:
Обновите `config_local.py` с вашими ключами:
```python
BOT_TOKEN = "your_bot_token_here"
OPENWEATHER_API_KEY = "your_weather_api_key_here"
NEWS_API_KEY = "your_news_api_key_here"
OPENAI_API_KEY = "your_openai_api_key_here"
# Настройки уведомлений разработчика
DEVELOPER_CHAT_ID = -1001234567890 # ID чата для уведомлений об ошибках
ENABLE_DEVELOPER_NOTIFICATIONS = True
ENABLE_AI_ERROR_PROCESSING = True
```
### 5. Запуск бота
@@ -158,6 +190,27 @@ python bot.py
- `/promote [пользователь]` - Повысить до модератора
- `/demote [пользователь]` - Понизить с модератора
#### Система отчетов об ошибках (только админы)
- `/report_error <тип> <заголовок> [описание]` - Отправить отчет об ошибке
- `/admin_errors [статус]` - Показать список ошибок с фильтрацией по статусу
- `/analyze_error_ai <ID>` - Проанализировать конкретную ошибку с помощью ИИ
- `/process_all_errors_ai` - Обработать все новые ошибки с помощью ИИ
- `/add_error_to_todo <ID> [приоритет]` - Добавить ошибку в TODO список
- `/add_all_analyzed_to_todo` - Добавить все проанализированные ошибки в TODO список
#### Типы ошибок для команды /report_error:
- **bug** - ошибка в работе бота (приоритет: medium)
- **crash** - критическая ошибка/падение (приоритет: critical)
- **feature** - предложение новой функции (приоритет: low)
- **ui** - проблема интерфейса (приоритет: medium)
- **security** - проблема безопасности (приоритет: high)
- **improvement** - предложение улучшения (приоритет: low)
- **other** - другое (приоритет: medium)
#### Интерактивные функции пользователей
- **Автореакции** - бот автоматически ставит 🤝 на сообщения содержащие "+"
- **Отзывчивость** - повышение вовлеченности пользователей через интерактивные элементы
### Система ранжирования
- За каждое сообщение начисляется 1 очко
- За выигрыш в мини-игре начисляются дополнительные очки
@@ -191,18 +244,52 @@ python bot.py
```
telegram_bot/
├── bot.py # Основной файл бота
├── config.py # Конфигурация (токены, настройки БД)
├── config_local.py # Локальная конфигурация с токенами
├── database_sqlite.py # Работа с SQLite базой данных
├── database.py # Базовые функции работы с БД
├── scheduler.py # Планировщик постов по расписанию
├── migrate_ranks.py # Миграция системы рангов
├── messages.py # Текстовые сообщения и константы бота
├── requirements.txt # Зависимости Python
── README.md # Эта документация
├── bot.py # Основной файл бота с системой ошибок и ИИ
├── config.py # Конфигурация (токены, настройки БД)
├── config_local.py # Локальная конфигурация с токенами и ключами API
├── config_template.py # Шаблон для настройки API ключей
├── database_sqlite.py # Работа с SQLite базой данных (+ таблица errors)
├── database.py # Базовые функции работы с БД
├── scheduler.py # Планировщик постов по расписанию
├── migrate_ranks.py # Миграция системы рангов
├── messages.py # Текстовые сообщения и константы бота
── requirements.txt # Зависимости Python (+ openai)
├── test_error_system.py # Тестовый скрипт для проверки системы ошибок
├── TODO.md # Список задач разработки с интеграцией ошибок
├── .gitignore # Игнорируемые файлы для Git
├── Dockerfile # Конфигурация для Docker развертывания
├── docker-compose.yml # Оркестрация контейнеров
├── .github/
│ ├── README.md # Краткое описание для GitHub
│ └── workflows/
│ └── deploy.yml # CI/CD pipeline для GitHub Actions
└── README.md # Полная документация
```
### База данных
База данных содержит следующие таблицы:
- **users** - информация о пользователях, рейтинги и достижения
- **warnings** - предупреждения пользователей
- **games** - игровые сессии
- **scheduled_posts** - запланированные посты
- **achievements** - достижения пользователей
- **donations** - история донатов
- **errors** - система отчетов об ошибках и их анализ
#### Структура таблицы errors:
- **error_id** - уникальный идентификатор ошибки (PRIMARY KEY)
- **admin_id** - ID администратора, отправившего отчет
- **error_type** - тип ошибки (bug, feature, crash, ui, security, etc.)
- **title** - краткое название ошибки
- **description** - подробное описание проблемы
- **status** - статус обработки (new, in_progress, resolved, rejected)
- **priority** - приоритет (low, medium, high, critical)
- **created_at** - время создания отчета
- **updated_at** - время последнего обновления
- **ai_analysis** - результат анализа ИИ (если выполнен)
- **todo_added** - флаг добавления в TODO список
- **resolved_at** - время решения (если решена)
### 5. Запуск планировщика постов
Для работы постов по расписанию запустите планировщик в отдельном терминале:
```bash
@@ -218,7 +305,8 @@ python scheduler.py
- **sqlite3** - встроенная поддержка SQLite базы данных
- **requests** - HTTP запросы для API
- **asyncio** - асинхронное программирование
- **SQLite** - база данных для хранения рейтинга и постов
- **openai** - интеграция с OpenAI API для анализа ошибок
- **SQLite** - база данных для хранения рейтинга, постов и отчетов об ошибках
## Разработка
@@ -230,6 +318,153 @@ python scheduler.py
### Тестирование
Запустите бота локально и протестируйте все функции в тестовом чате.
## Тестирование системы
### Автоматическое тестирование системы отчетов об ошибках
Проект включает встроенный тестовый скрипт для проверки функциональности системы отчетов об ошибках:
```bash
cd telegram_bot
python test_error_system.py
```
#### Что тестирует скрипт:
- ✅ Подключение к базе данных SQLite
- ✅ Создание и структура таблицы ошибок
- ✅ Добавление и получение ошибок из базы данных
- ✅ Работа системы фильтрации ошибок
- ✅ Интеграция с ИИ (проверка наличия методов)
- ✅ Работа интеграции с TODO файлом
- ✅ Система уведомлений разработчика
- ✅ Проверка конфигурации
#### Результаты тестирования:
Тестовый скрипт предоставляет подробный отчет о состоянии системы с градацией результатов:
- **SUCCESS** - тест пройден успешно
- **ERROR** - критическая ошибка, требующая внимания
- **WARNING** - некритическая проблема или рекомендация
### Примеры использования системы
#### Пример 1: Отправка отчета об ошибке
```
Администратор: /report_error bug Не работает команда /weather Бот не показывает погоду при запросе команды
Бот: Отчет успешно отправлен! ID ошибки: 1
Разработчик: Получает уведомление в настроенном чате
```
#### Пример 2: Анализ ошибки ИИ
```
Администратор: /analyze_error_ai 1
Бот: Начинаю анализ ошибки #1 с помощью ИИ...
[Через некоторое время]
Бот: Анализ завершен! Предоставляет структурированный анализ с рекомендациями
```
#### Пример 3: Добавление в план разработки
```
Администратор: /add_error_to_todo 1 high
Бот: Ошибка #1 успешно добавлена в TODO список в раздел высокого приоритета
```
#### Пример 4: Автореакция на положительные сообщения
```
Пользователь: Отличная работа! +
Бот: 🤝 (автоматически ставит реакцию рукопожатия)
```
## 🚀 Развертывание
### Локальное развертывание
#### Обычный запуск
```bash
# 1. Клонируйте репозиторий
git clone <repository-url>
cd telegram_bot
# 2. Установите зависимости
pip install -r requirements.txt
# 3. Настройте API ключи
cp config_template.py config_local.py
# Отредактируйте config_local.py
# 4. Запустите бота
python bot.py
# 5. В другом терминале запустите планировщик
python scheduler.py
```
#### Запуск с Docker
```bash
# 1. Соберите образ
docker build -t telegram-bot .
# 2. Создайте config_local.py с ключами API
# 3. Запустите бота
docker run -v $(pwd)/data:/app/data -v $(pwd)/config_local.py:/app/config_local.py telegram-bot
# Или с docker-compose
docker-compose up -d
```
### Развертывание на сервере
#### Рекомендации для продакшена:
- Используйте виртуальное окружение Python
- Настройте supervisor или systemd для автозапуска
- Используйте reverse proxy (nginx) для безопасности
- Настройте логирование и мониторинг
- Регулярно создавайте резервные копии базы данных
#### Пример конфигурации systemd:
```ini
[Unit]
Description=Telegram Bot
After=network.target
[Service]
Type=simple
User=bot
WorkingDirectory=/path/to/telegram_bot
ExecStart=/path/to/venv/bin/python bot.py
Restart=always
Environment=PYTHONPATH=/path/to/telegram_bot
[Install]
WantedBy=multi-user.target
```
### GitHub Actions (CI/CD)
Проект включает готовые workflows для автоматического тестирования и развертывания:
```bash
# Настройте секреты в репозитории:
# Settings > Secrets and variables > Actions
BOT_TOKEN=your_bot_token
OPENAI_API_KEY=your_openai_key
DEVELOPER_CHAT_ID=your_chat_id
```
#### Автоматическое тестирование:
- Проверка кода при каждом коммите
- Тестирование системы отчетов об ошибках
- Валидация базы данных и конфигурации
#### Автоматическое развертывание:
- Сборка Docker образа
- Деплой на сервер при обновлении main ветки
- Автоматическая настройка конфигурации
## Лицензия
MIT License - см. файл LICENSE для подробностей.
---
*Последнее обновление: 16 октября 2025 (добавлена версия 1.4.0 с критическими исправлениями и система отчетов об ошибках)*
+12 -1
View File
@@ -9,6 +9,7 @@
- [x] ✅ Улучшить обработку исключений в базе данных
### ✨ Новые функции
- [ ] Добавить реакцию на сообщения пользователей в чате: если в сообщении есть "+", значит ставим эмодзи рукопожатие
- [ ] Добавить поддержку медиа файлов в планировщик постов (видео, документы)
- [ ] Реализовать многоязычность (i18n) для бота
- [ ] Добавить экспорт статистики в CSV/Excel формат
@@ -43,7 +44,17 @@
- [ ] Реализовать CAPTCHA для новых пользователей
- [ ] Добавить аудит лог для действий админов
### 🔧 Техническое
- [ ] #2 Test task for TODO (Тип: improvement) - Test analysis for integration check
### 🚨 Система ошибок и отчетов
- [x] Добавить возможность администраторам отправлять сообщения разработчику об ошибках бота
- [x] Создать отдельную таблицу в СУБД для хранения ошибок
- [x] Реализовать обработку ошибок Искусственным интеллектом
- [x] Добавить систему добавления обработанных ошибок в TODO список для рассмотрения разработчиком
### 🤝 Интерактивные функции
- [x] Добавить реакцию на сообщения пользователей с "+" (эмодзи рукопожатия)
### Техническое
- [ ] Миграция с SQLite на PostgreSQL для больших нагрузок
- [ ] Добавить Redis для кеширования
- [ ] Создать Docker контейнер для развертывания
Binary file not shown.
+928 -1
View File
@@ -9,7 +9,7 @@ from datetime import datetime, timedelta
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputTextMessageContent
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters, InlineQueryHandler, ChatMemberHandler
from database_sqlite import Database
from config_local import BOT_TOKEN, OPENWEATHER_API_KEY, NEWS_API_KEY, ADMIN_IDS
from config_local import BOT_TOKEN, OPENWEATHER_API_KEY, NEWS_API_KEY, ADMIN_IDS, DEVELOPER_CHAT_ID, ENABLE_DEVELOPER_NOTIFICATIONS, OPENAI_API_KEY, ENABLE_AI_ERROR_PROCESSING, AI_MODEL
from messages import *
try:
@@ -79,10 +79,18 @@ class TelegramBot:
self.application.add_handler(CommandHandler("list_posts", self.list_posts))
self.application.add_handler(CommandHandler("delete_post", self.delete_post))
self.application.add_handler(CommandHandler("publish_now", self.publish_now))
self.application.add_handler(CommandHandler("report_error", self.report_error))
self.application.add_handler(CommandHandler("admin_errors", self.admin_errors))
self.application.add_handler(CommandHandler("analyze_error_ai", self.analyze_error_with_ai))
self.application.add_handler(CommandHandler("process_all_errors_ai", self.process_all_new_errors_ai))
self.application.add_handler(CommandHandler("add_error_to_todo", self.add_error_to_todo))
self.application.add_handler(CommandHandler("add_all_analyzed_to_todo", self.add_all_analyzed_errors_to_todo))
# Обработчики сообщений
self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message))
self.application.add_handler(MessageHandler(filters.VOICE, self.handle_voice_message))
self.application.add_handler(MessageHandler(filters.AUDIO, self.handle_audio_message))
self.application.add_handler(MessageHandler(filters.VIDEO, self.handle_video_message))
self.application.add_handler(ChatMemberHandler(self.handle_new_chat_members, ChatMemberHandler.CHAT_MEMBER))
# Инлайновые запросы
@@ -564,6 +572,8 @@ ID: {user_info['ID']}
await self.cancel_schedule_post(query, context)
elif query.data.startswith('donate_'):
await self.handle_donation_callback(query, context)
elif query.data.startswith('moderate_'):
await self.handle_moderation_callback(query, context)
async def start_rps_game(self, query, context):
@@ -1663,6 +1673,9 @@ ID: {user_info['ID']}
db.update_score(user.id, bonus_points)
await update.message.reply_text(USER_MESSAGES['bonus_points'].format(points=bonus_points))
# Проверка на "+" в сообщении для реакции рукопожатием
await self.handle_plus_reaction(update.message)
async def handle_text_edit(self, update, context):
"""Обработка редактирования текста поста"""
new_text = update.message.text
@@ -2325,6 +2338,205 @@ ID: {user_info['ID']}
except:
pass
async def handle_audio_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработка аудио сообщений для модерации"""
await self.handle_media_for_moderation(update, context, "audio")
async def handle_video_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Обработка видео сообщений для модерации"""
await self.handle_media_for_moderation(update, context, "video")
async def handle_media_for_moderation(self, update: Update, context: ContextTypes.DEFAULT_TYPE, media_type: str):
"""Обработка медиафайлов для модерации"""
user = update.effective_user
message = update.message
# Добавление пользователя в базу данных
db.add_user(user.id, user.username, user.first_name, user.last_name)
# Получаем информацию о медиа
if media_type == "audio":
media = message.audio
duration = getattr(media, 'duration', 0)
file_size = getattr(media, 'file_size', 0)
title = getattr(media, 'title', 'Без названия')
performer = getattr(media, 'performer', 'Неизвестный исполнитель')
elif media_type == "video":
media = message.video
duration = getattr(media, 'duration', 0)
file_size = getattr(media, 'file_size', 0)
width = getattr(media, 'width', 0)
height = getattr(media, 'height', 0)
else:
return
# Попытка транскрибации аудио/видео
transcription = ""
if media_type == "audio":
transcription = await self.transcribe_audio_for_moderation(media, update)
# Сохраняем информацию о медиа в контексте для модерации
context.user_data['media_for_moderation'] = {
'message_id': message.message_id,
'chat_id': message.chat.id,
'user_id': user.id,
'media_type': media_type,
'file_id': media.file_id,
'duration': duration,
'file_size': file_size,
'transcription': transcription,
'caption': message.caption or "",
'timestamp': datetime.now()
}
# Удаляем оригинальное сообщение
try:
await message.delete()
except Exception as e:
print(f"Не удалось удалить сообщение с медиа: {e}")
# Создаем пост для планировщика с отсрочкой 8 часов
scheduled_time = datetime.now() + timedelta(hours=8)
# Отправляем уведомление администраторам для модерации
await self.notify_admins_for_moderation(context, user, media_type, transcription, message.caption or "")
# Создаем клавиатуру для модерации
keyboard = [
[InlineKeyboardButton("✅ Одобрить и опубликовать сейчас", callback_data=f'moderate_approve_now_{user.id}')],
[InlineKeyboardButton("⏰ Одобрить с отсрочкой (8 часов)", callback_data=f'moderate_approve_delay_{user.id}')],
[InlineKeyboardButton("❌ Отклонить", callback_data=f'moderate_reject_{user.id}')]
]
reply_markup = InlineKeyboardMarkup(keyboard)
# Отправляем информацию в группу модераторов (если настроена)
await self.send_to_moderator_group(context, user, media_type, transcription, message.caption or "", reply_markup)
async def transcribe_audio_for_moderation(self, audio, update):
"""Транскрибация аудио для модерации"""
if not SPEECH_RECOGNITION_AVAILABLE:
return "[Транскрибация недоступна - не установлена библиотека]"
try:
# Создаем временный файл для загрузки аудио
with tempfile.NamedTemporaryFile(suffix='.ogg', delete=False) as temp_ogg:
temp_ogg_path = temp_ogg.name
# Скачиваем аудио
audio_file = await audio.get_file()
await audio_file.download_to_drive(temp_ogg_path)
# Конвертируем OGG в WAV для speech_recognition
temp_wav_path = temp_ogg_path.replace('.ogg', '.wav')
if PYDUB_AVAILABLE:
# Используем pydub для конвертации
audio_seg = AudioSegment.from_ogg(temp_ogg_path)
audio_seg.export(temp_wav_path, format='wav')
else:
# Используем ffmpeg для конвертации (если доступен)
try:
subprocess.run([
'ffmpeg', '-i', temp_ogg_path, '-acodec', 'pcm_s16le',
'-ar', '16000', '-ac', '1', temp_wav_path
], check=True, capture_output=True)
except (subprocess.CalledProcessError, FileNotFoundError):
# Очищаем временные файлы и возвращаем ошибку
os.unlink(temp_ogg_path)
return "[Невозможно конвертировать аудио для транскрибации]"
# Распознавание речи
recognizer = sr.Recognizer()
with sr.AudioFile(temp_wav_path) as source:
audio_data = recognizer.record(source)
# Пробуем разные API для распознавания
try:
# Сначала пробуем Google Speech Recognition (бесплатно)
text = recognizer.recognize_google(audio_data, language='ru-RU')
return text
except sr.UnknownValueError:
return "[Не удалось распознать речь]"
except sr.RequestError:
# Если Google API недоступен, пробуем Sphinx (локальный, но менее точный)
try:
text = recognizer.recognize_sphinx(audio_data, language='ru-RU')
return text
except sr.UnknownValueError:
return "[Не удалось распознать речь (локально)]"
except sr.RequestError:
return "[Сервисы распознавания речи недоступны]"
except Exception as e:
print(f"Ошибка при транскрибации для модерации: {e}")
return "[Ошибка при обработке аудио]"
finally:
# Очищаем временные файлы
try:
if 'temp_ogg_path' in locals():
os.unlink(temp_ogg_path)
if 'temp_wav_path' in locals():
os.unlink(temp_wav_path)
except:
pass
async def notify_admins_for_moderation(self, context, user, media_type, transcription, caption):
"""Отправка уведомления администраторам для модерации медиа"""
# Получаем список администраторов из базы данных или конфига
admin_ids = ADMIN_IDS.copy()
# Добавляем администраторов из чата
# Здесь можно добавить логику получения администраторов из чата
for admin_id in admin_ids:
try:
notification_text = (
"🔔 <b>Требуется модерация медиафайла</b>\n\n"
f"👤 Пользователь: {user.first_name} (@{user.username if user.username else 'нет'})\n"
f"🆔 ID: {user.id}\n"
f"📁 Тип файла: {media_type}\n"
f"📝 Описание: {caption[:100]}{'...' if len(caption) > 100 else ''}\n"
f"🎵 Транскрипция: {transcription[:200]}{'...' if len(transcription) > 200 else ''}\n\n"
"Выберите действие в группе модераторов."
)
await context.bot.send_message(
chat_id=admin_id,
text=notification_text,
parse_mode='HTML'
)
except Exception as e:
print(f"Не удалось отправить уведомление администратору {admin_id}: {e}")
async def send_to_moderator_group(self, context, user, media_type, transcription, caption, reply_markup):
"""Отправка медиа в группу модераторов"""
# Здесь нужно указать ID группы модераторов
# Пока используем тот же чат для демонстрации
moderator_group_id = context.user_data['media_for_moderation']['chat_id']
try:
info_text = (
"🔍 <b>Медиафайл на модерации</b>\n\n"
f"👤 От пользователя: {user.first_name} (@{user.username if user.username else 'нет'})\n"
f"🆔 ID: {user.id}\n"
f"📁 Тип: {media_type.upper()}\n"
f"📝 Описание: {caption or 'Без описания'}\n"
f"🎵 Транскрипция: {transcription or 'Не удалось получить'}\n\n"
"Выберите действие:"
)
await context.bot.send_message(
chat_id=moderator_group_id,
text=info_text,
reply_markup=reply_markup,
parse_mode='HTML'
)
except Exception as e:
print(f"Не удалось отправить медиа в группу модераторов: {e}")
# ===== ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ ДЛЯ ИГР =====
def add_random_tile(self, board):
@@ -2608,6 +2820,721 @@ ID: {user_info['ID']}
except ValueError:
await query.edit_message_text("❌ Неверная сумма!")
async def handle_moderation_callback(self, query, context):
"""Обработка callback-запросов модерации медиа"""
await query.answer()
try:
action, decision, user_id_str = query.data.split('_')
user_id = int(user_id_str)
except (ValueError, IndexError):
await query.edit_message_text("❌ Ошибка формата команды модерации")
return
# Получаем данные медиа из контекста или хранилища
media_data = context.user_data.get('media_for_moderation')
if not media_data:
await query.edit_message_text("❌ Данные медиа не найдены")
return
if media_data['user_id'] != user_id:
await query.edit_message_text("❌ Несоответствие пользователя")
return
# Определяем действие модератора
if decision == "approve" and action == "moderate":
# Одобрено - публикуем сейчас
await self.publish_media_now(query, context, media_data)
elif decision == "delay" and action == "moderate":
# Одобрено с отсрочкой - планируем на 8 часов
await self.schedule_media_for_later(query, context, media_data)
elif decision == "reject" and action == "moderate":
# Отклонено
await self.reject_media(query, context, media_data)
else:
await query.edit_message_text("❌ Неизвестное действие модерации")
async def publish_media_now(self, query, context, media_data):
"""Публикация медиа немедленно"""
try:
chat_id = media_data['chat_id']
# Получаем информацию о пользователе
user_info = db.get_user_info(media_data['user_id'])
user_name = user_info['Имя'] if user_info else f"Пользователь {media_data['user_id']}"
# Формируем текст публикации
if media_data['media_type'] == 'audio':
text = f"🎵 Аудио от пользователя {user_name}\n\n{media_data['caption'] or 'Без описания'}"
if media_data['transcription']:
text += f"\n\n🎵 Транскрипция: {media_data['transcription']}"
else: # video
text = f"🎥 Видео от пользователя {user_name}\n\n{media_data['caption'] or 'Без описания'}"
# Здесь нужно реализовать отправку медиафайла
# Пока отправляем только текст
await context.bot.send_message(
chat_id=chat_id,
text=text,
parse_mode='HTML'
)
# Уведомляем модератора об успешной публикации
await query.edit_message_text(
f"✅ Медиа успешно опубликовано!\n\n"
f"👤 Пользователь: {user_name}\n"
f"📁 Тип: {media_data['media_type'].upper()}\n"
f"📝 Текст: {text[:100]}{'...' if len(text) > 100 else ''}"
)
# Очищаем данные медиа
context.user_data.pop('media_for_moderation', None)
except Exception as e:
await query.edit_message_text(f"❌ Ошибка при публикации медиа: {str(e)[:100]}")
async def schedule_media_for_later(self, query, context, media_data):
"""Планирование медиа на публикацию через 8 часов"""
try:
# Время публикации через 8 часов
schedule_time = datetime.now() + timedelta(hours=8)
# Получаем информацию о пользователе
user_info = db.get_user_info(media_data['user_id'])
user_name = user_info['Имя'] if user_info else f"Пользователь {media_data['user_id']}"
# Формируем текст для планировщика
if media_data['media_type'] == 'audio':
text = f"🎵 Аудио от пользователя {user_name}\n\n{media_data['caption'] or 'Без описания'}"
if media_data['transcription']:
text += f"\n\n🎵 Транскрипция: {media_data['transcription']}"
else: # video
text = f"🎥 Видео от пользователя {user_name}\n\n{media_data['caption'] or 'Без описания'}"
# Добавляем в планировщик постов
post_id = db.add_scheduled_post(
chat_id=media_data['chat_id'],
text=text,
schedule_time=schedule_time.strftime('%Y-%m-%d %H:%M:%S'),
created_by=0 # Системная модерация
)
if post_id:
await query.edit_message_text(
f"⏰ Медиа запланировано для публикации!\n\n"
f"📅 Время публикации: {schedule_time.strftime('%Y-%m-%d %H:%M:%S')}\n"
f"👤 Пользователь: {user_name}\n"
f"📁 Тип: {media_data['media_type'].upper()}\n"
f"🆔 Поста: {post_id}"
)
else:
await query.edit_message_text("❌ Ошибка при планировании медиа")
# Очищаем данные медиа
context.user_data.pop('media_for_moderation', None)
except Exception as e:
await query.edit_message_text(f"❌ Ошибка при планировании медиа: {str(e)[:100]}")
async def reject_media(self, query, context, media_data):
"""Отклонение медиа"""
try:
# Получаем информацию о пользователе
user_info = db.get_user_info(media_data['user_id'])
user_name = user_info['Имя'] if user_info else f"Пользователь {media_data['user_id']}"
# Отправляем уведомление пользователю об отклонении
try:
await context.bot.send_message(
chat_id=media_data['user_id'],
text="❌ Ваш медиафайл был отклонен модераторами и не будет опубликован."
)
except Exception as e:
print(f"Не удалось отправить уведомление пользователю {media_data['user_id']}: {e}")
# Уведомляем модератора об отклонении
await query.edit_message_text(
f"❌ Медиа отклонено!\n\n"
f"👤 Пользователь: {user_name}\n"
f"📁 Тип: {media_data['media_type'].upper()}\n"
f"📝 Причина: Отклонено модератором"
)
# Очищаем данные медиа
context.user_data.pop('media_for_moderation', None)
except Exception as e:
await query.edit_message_text(f"❌ Ошибка при отклонении медиа: {str(e)[:100]}")
async def report_error(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Команда для отправки отчета об ошибке (только для администраторов)"""
user = update.effective_user
# Проверка прав администратора
if not await self.is_admin(update.effective_chat, user.id):
await update.message.reply_text("❌ Эта команда доступна только администраторам.")
return
# Проверка корректности аргументов команды
if len(context.args) < 2:
await update.message.reply_text(
"❌ Использование: /report_error <тип> <заголовок> [описание]\n\n"
"Типы ошибок:\n"
"• bug - ошибка в работе бота\n"
"• feature - предложение новой функции\n"
"• crash - критическая ошибка/падение\n"
"• ui - проблема интерфейса\n"
"• security - проблема безопасности\n\n"
"Пример: /report_error bug Не работает команда /weather Описание проблемы..."
)
return
error_type = context.args[0].lower()
title = context.args[1]
description = ' '.join(context.args[2:]) if len(context.args) > 2 else ""
# Валидация типа ошибки
valid_types = ['bug', 'feature', 'crash', 'ui', 'security', 'improvement', 'other']
if error_type not in valid_types:
await update.message.reply_text(
f"❌ Неверный тип ошибки: {error_type}\n"
f"Доступные типы: {', '.join(valid_types)}"
)
return
# Валидация заголовка
if len(title.strip()) == 0:
await update.message.reply_text("❌ Заголовок ошибки не может быть пустым")
return
if len(title) > 200:
await update.message.reply_text("❌ Заголовок слишком длинный (максимум 200 символов)")
return
# Валидация описания
if len(description) > 2000:
await update.message.reply_text("❌ Описание слишком длинное (максимум 2000 символов)")
return
# Определение приоритета по умолчанию
priority_map = {
'crash': 'critical',
'security': 'high',
'bug': 'medium',
'ui': 'medium',
'feature': 'low',
'improvement': 'low',
'other': 'medium'
}
priority = priority_map.get(error_type, 'medium')
# Сохранение ошибки в базу данных
error_id = db.add_error(
admin_id=user.id,
error_type=error_type,
title=title,
description=description if description else "Описание не предоставлено",
priority=priority
)
if error_id:
# Отправляем подтверждение администратору
await update.message.reply_text(
"✅ Отчет об ошибке успешно отправлен!\n\n"
f"🆔 ID ошибки: {error_id}\n"
f"📋 Тип: {error_type}\n"
f"📝 Заголовок: {title}\n"
f"⭐ Приоритет: {priority}\n\n"
"Спасибо за ваш вклад в улучшение бота!"
)
# Отправляем уведомление разработчику
await self.send_developer_notification(
context,
f"🚨 <b>Новый отчет об ошибке!</b>\n\n"
f"👤 От: {admin_name}\n"
f"🆔 ID ошибки: {error_id}\n"
f"📋 Тип: {error_type}\n"
f"📝 Заголовок: {title}\n"
f"⭐ Приоритет: {priority}\n"
f"📅 Время: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
f"📄 Описание: {description if description else 'Описание не предоставлено'}"
)
else:
await update.message.reply_text("❌ Ошибка при сохранении отчета. Попробуйте позже.")
async def admin_errors(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Команда для просмотра списка ошибок (только для администраторов)"""
user = update.effective_user
# Проверка прав администратора
if not await self.is_admin(update.effective_chat, user.id):
await update.message.reply_text("❌ Эта команда доступна только администраторам.")
return
# Получаем параметр фильтрации (по статусу)
status_filter = None
if context.args and len(context.args) > 0:
status_filter = context.args[0].lower()
valid_statuses = ['new', 'in_progress', 'resolved', 'rejected']
if status_filter not in valid_statuses:
await update.message.reply_text(
f"❌ Неверный статус: {status_filter}\n"
f"Доступные статусы: {', '.join(valid_statuses)}"
)
return
# Получаем ошибки из базы данных
errors = db.get_errors(status=status_filter, limit=20)
if not errors:
status_text = f" со статусом '{status_filter}'" if status_filter else ""
await update.message.reply_text(f"📭 Нет ошибок{status_text}")
return
# Формируем ответ с информацией об ошибках
response = "📋 <b>Список ошибок и отчетов</b>\n\n"
for error in errors:
error_id, admin_id, error_type, title, description, status, priority, created_at, updated_at, ai_analysis, todo_added, resolved_at, admin_name, admin_username = error
# Определяем эмодзи для типа ошибки
type_emojis = {
'bug': '🐛',
'feature': '',
'crash': '💥',
'ui': '🎨',
'security': '🔒',
'improvement': '📈',
'other': '📝'
}
type_emoji = type_emojis.get(error_type, '📝')
# Определяем эмодзи для приоритета
priority_emojis = {
'critical': '🔴',
'high': '🟠',
'medium': '🟡',
'low': '🔵'
}
priority_emoji = priority_emojis.get(priority, '🟡')
# Определяем эмодзи для статуса
status_emojis = {
'new': '🆕',
'in_progress': '🔄',
'resolved': '',
'rejected': ''
}
status_emoji = status_emojis.get(status, '')
admin_display = admin_username if admin_username else admin_name if admin_name else f"ID:{admin_id}"
response += (
f"{type_emoji} <b>#{error_id}</b> {priority_emoji} {status_emoji}\n"
f"📝 <b>{title}</b>\n"
f"👤 {admin_display} | 📅 {created_at[:10]}\n"
f"📋 Тип: {error_type} | Статус: {status}\n"
)
if description and len(description) > 100:
response += f"📄 Описание: {description[:100]}...\n"
elif description:
response += f"📄 Описание: {description}\n"
response += "\n" + "" * 40 + "\n"
# Проверяем, не превышает ли длина лимит Telegram (4096 символов)
if len(response) > 4000:
response = response[:3997] + "..."
await update.message.reply_text(response, parse_mode='HTML')
async def handle_plus_reaction(self, message):
"""Обработка реакции на сообщения с '+'"""
try:
# Проверяем, содержит ли сообщение символ '+'
if message.text and '+' in message.text:
# Ставим реакцию рукопожатия
await message.set_reaction("🤝")
print(f"Reaction set for message from {message.from_user.first_name}")
except Exception as e:
# Игнорируем ошибки реакции (может быть вызвано ограничениями Telegram)
print(f"Could not set reaction: {e}")
async def send_developer_notification(self, context, message):
"""Отправка уведомления разработчику об ошибке"""
if not ENABLE_DEVELOPER_NOTIFICATIONS or not DEVELOPER_CHAT_ID:
print("Уведомления разработчика отключены или не настроен DEVELOPER_CHAT_ID")
return False
try:
await context.bot.send_message(
chat_id=DEVELOPER_CHAT_ID,
text=message,
parse_mode='HTML',
disable_web_page_preview=True
)
print(f"Уведомление разработчику отправлено успешно")
return True
except Exception as e:
print(f"Ошибка при отправке уведомления разработчику: {e}")
return False
async def notify_error_status_change(self, context, error_id, old_status, new_status, admin_name, error_title):
"""Отправка уведомления об изменении статуса ошибки"""
if not ENABLE_DEVELOPER_NOTIFICATIONS or not DEVELOPER_CHAT_ID:
return False
status_emojis = {
'new': '🆕',
'in_progress': '🔄',
'resolved': '',
'rejected': ''
}
old_emoji = status_emojis.get(old_status, '')
new_emoji = status_emojis.get(new_status, '')
notification_text = (
"🔄 <b>Статус ошибки изменен!</b>\n\n"
f"🆔 ID ошибки: {error_id}\n"
f"📝 Заголовок: {error_title}\n"
f"👤 Изменил: {admin_name}\n"
f"📊 Статус: {old_emoji} {old_status}{new_emoji} {new_status}\n"
f"📅 Время: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
)
return await self.send_developer_notification(context, notification_text)
async def process_error_with_ai(self, error_id, error_type, title, description):
"""Обработка ошибки с помощью ИИ"""
if not ENABLE_AI_ERROR_PROCESSING or not OPENAI_API_KEY:
print("Обработка ошибок ИИ отключена или не настроен OPENAI_API_KEY")
return None
try:
import openai
# Настройка OpenAI клиента
client = openai.OpenAI(api_key=OPENAI_API_KEY)
# Создаем промпт для анализа ошибки
prompt = f"""
Проанализируйте следующую ошибку бота и предоставьте структурированный анализ:
Тип ошибки: {error_type}
Заголовок: {title}
Описание: {description}
Пожалуйста, предоставьте анализ в следующем формате:
1. КЛАССИФИКАЦИЯ: Определите категорию проблемы (критическая, высокая, средняя, низкая)
2. ПРИЧИНА: Возможная причина возникновения проблемы
3. РЕШЕНИЕ: Рекомендуемое решение или шаги для исправления
4. ПРОФИЛАКТИКА: Как предотвратить подобные проблемы в будущем
5. ТЭГИ: Ключевые слова для категоризации (через запятую)
Ответ должен быть кратким и практически полезным для разработчика.
"""
# Вызов OpenAI API
response = client.chat.completions.create(
model=AI_MODEL,
messages=[
{"role": "system", "content": "Вы - опытный разработчик, анализирующий ошибки телеграм-бота. Предоставьте четкий и практичный анализ."},
{"role": "user", "content": prompt}
],
max_tokens=1000,
temperature=0.7
)
ai_analysis = response.choices[0].message.content.strip()
# Сохраняем анализ в базу данных
if db.update_error_ai_analysis(error_id, ai_analysis):
print(f"Анализ ИИ для ошибки #{error_id} сохранен успешно")
return ai_analysis
else:
print(f"Ошибка при сохранении анализа ИИ для ошибки #{error_id}")
return None
except ImportError:
print("OpenAI библиотека не установлена. Установите: pip install openai")
return None
except Exception as e:
print(f"Ошибка при обработке ошибки ИИ: {e}")
return None
async def analyze_error_with_ai(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Команда для анализа конкретной ошибки с помощью ИИ (только для администраторов)"""
user = update.effective_user
# Проверка прав администратора
if not await self.is_admin(update.effective_chat, user.id):
await update.message.reply_text("❌ Эта команда доступна только администраторам.")
return
if len(context.args) < 1:
await update.message.reply_text(
"❌ Использование: /analyze_error_ai <ID_ошибки>\n\n"
"Пример: /analyze_error_ai 1"
)
return
try:
error_id = int(context.args[0])
except ValueError:
await update.message.reply_text("❌ ID ошибки должен быть числом")
return
# Получаем информацию об ошибке
error_data = db.get_error_by_id(error_id)
if not error_data:
await update.message.reply_text(f"❌ Ошибка с ID {error_id} не найдена")
return
error_id, admin_id, error_type, title, description, status, priority, created_at, updated_at, ai_analysis, todo_added, resolved_at, admin_name, admin_username = error_data
if ai_analysis:
await update.message.reply_text(
f"🤖 <b>Анализ ошибки #{error_id} (ИИ)</b>\n\n"
f"📋 Тип: {error_type}\n"
f"📝 Заголовок: {title}\n"
f"📄 Существующий анализ:\n{ai_analysis}",
parse_mode='HTML'
)
return
# Отправляем сообщение о начале анализа
await update.message.reply_text(
f"🤖 Начинаю анализ ошибки #{error_id} с помощью ИИ...\n"
f"📋 Тип: {error_type}\n"
f"📝 Заголовок: {title}"
)
# Запускаем анализ в фоне
ai_analysis = await self.process_error_with_ai(error_id, error_type, title, description)
if ai_analysis:
await update.message.reply_text(
f"✅ <b>Анализ завершен!</b>\n\n"
f"🤖 <b>Анализ ошибки #{error_id} (ИИ):</b>\n\n"
f"📋 Тип: {error_type}\n"
f"📝 Заголовок: {title}\n"
f"📄 Анализ:\n{ai_analysis}",
parse_mode='HTML'
)
else:
await update.message.reply_text(
"❌ Не удалось выполнить анализ ошибки. Проверьте настройки ИИ или повторите попытку позже."
)
async def process_all_new_errors_ai(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Команда для обработки всех новых ошибок с помощью ИИ (только для администраторов)"""
user = update.effective_user
# Проверка прав администратора
if not await self.is_admin(update.effective_chat, user.id):
await update.message.reply_text("❌ Эта команда доступна только администраторам.")
return
# Получаем все новые ошибки
new_errors = db.get_errors(status='new', limit=10)
if not new_errors:
await update.message.reply_text("📭 Нет новых ошибок для обработки ИИ")
return
await update.message.reply_text(f"🤖 Начинаю обработку {len(new_errors)} ошибок с помощью ИИ...")
processed_count = 0
failed_count = 0
for error in new_errors:
error_id, admin_id, error_type, title, description, status, priority, created_at, updated_at, ai_analysis, todo_added, resolved_at, admin_name, admin_username = error
if not ai_analysis: # Обрабатываем только ошибки без анализа
ai_analysis = await self.process_error_with_ai(error_id, error_type, title, description)
if ai_analysis:
processed_count += 1
else:
failed_count += 1
# Отчет о результатах
result_text = f"✅ Обработка завершена!\n\n📊 Результаты:\n• Обработано: {processed_count}\n• Ошибок: {failed_count}"
await update.message.reply_text(result_text)
async def add_error_to_todo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Команда для добавления конкретной ошибки в TODO список (только для администраторов)"""
user = update.effective_user
# Проверка прав администратора
if not await self.is_admin(update.effective_chat, user.id):
await update.message.reply_text("❌ Эта команда доступна только администраторам.")
return
if len(context.args) < 1:
await update.message.reply_text(
"❌ Использование: /add_error_to_todo <ID_ошибки> [приоритет]\n\n"
"Приоритет (опционально): high, medium, low\n"
"Пример: /add_error_to_todo 1 high"
)
return
try:
error_id = int(context.args[0])
except ValueError:
await update.message.reply_text("❌ ID ошибки должен быть числом")
return
priority = context.args[1].lower() if len(context.args) > 1 else 'medium'
valid_priorities = ['high', 'medium', 'low']
if priority not in valid_priorities:
await update.message.reply_text(f"❌ Неверный приоритет: {priority}. Доступные: {', '.join(valid_priorities)}")
return
# Получаем информацию об ошибке
error_data = db.get_error_by_id(error_id)
if not error_data:
await update.message.reply_text(f"❌ Ошибка с ID {error_id} не найдена")
return
error_id, admin_id, error_type, title, description, status, priority, created_at, updated_at, ai_analysis, todo_added, resolved_at, admin_name, admin_username = error_data
if todo_added:
await update.message.reply_text(f"⚠️ Ошибка #{error_id} уже была добавлена в TODO список")
return
# Добавляем ошибку в TODO список
success = self.add_error_to_todo_file(error_id, title, error_type, priority, ai_analysis)
if success:
# Отмечаем ошибку как добавленную в TODO
db.mark_error_todo_added(error_id)
await update.message.reply_text(
f"✅ Ошибка #{error_id} успешно добавлена в TODO список!\n\n"
f"📝 Заголовок: {title}\n"
f"📋 Тип: {error_type}\n"
f"⭐ Приоритет: {priority}"
)
else:
await update.message.reply_text("❌ Не удалось добавить ошибку в TODO список")
def add_error_to_todo_file(self, error_id, title, error_type, priority, ai_analysis=None):
"""Добавление ошибки в TODO файл"""
try:
todo_file_path = 'TODO.md'
# Читаем текущий файл TODO
with open(todo_file_path, 'r', encoding='utf-8') as file:
content = file.read()
# Определяем раздел для добавления задачи
priority_sections = {
'high': '## 🚀 Приоритетные задачи (High Priority)',
'medium': '## 🎯 Средний приоритет (Medium Priority)',
'low': '## 🔮 Низкий приоритет (Low Priority)'
}
section_title = priority_sections.get(priority, priority_sections['medium'])
task_text = f"- [ ] #{error_id} {title} (Тип: {error_type})"
# Добавляем анализ ИИ, если он есть
if ai_analysis:
# Извлекаем ключевые слова из анализа ИИ для более подробного описания
lines = ai_analysis.split('\n')
for line in lines[:3]: # Берем первые 3 строки анализа
if line.strip() and not line.startswith(('1.', '2.', '3.', '4.', '5.')):
task_text += f" - {line.strip()}"
# Находим место для вставки
lines = content.split('\n')
insert_index = -1
for i, line in enumerate(lines):
if line.startswith(section_title):
# Находим следующий подраздел или задачи в этом разделе
for j in range(i + 1, len(lines)):
next_line = lines[j]
if next_line.startswith('### ') and 'Система ошибок' in next_line:
# Вставляем перед подразделом "Система ошибок"
insert_index = j
break
elif next_line.startswith('## ') and next_line != lines[i]:
# Вставляем перед следующим основным разделом
insert_index = j
break
if insert_index == -1:
# Если не нашли место, вставляем в конец раздела
insert_index = len(lines)
break
if insert_index == -1:
# Если не нашли подходящий раздел, добавляем в конец файла
insert_index = len(lines)
# Вставляем задачу
lines.insert(insert_index, task_text)
# Записываем обновленный файл
with open(todo_file_path, 'w', encoding='utf-8') as file:
file.write('\n'.join(lines))
print(f"Ошибка #{error_id} успешно добавлена в TODO список")
return True
except Exception as e:
print(f"Ошибка при добавлении ошибки в TODO файл: {e}")
return False
async def add_all_analyzed_errors_to_todo(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Команда для добавления всех проанализированных ошибок в TODO список (только для администраторов)"""
user = update.effective_user
# Проверка прав администратора
if not await self.is_admin(update.effective_chat, user.id):
await update.message.reply_text("❌ Эта команда доступна только администраторам.")
return
# Получаем все ошибки с анализом ИИ, но не добавленные в TODO
all_errors = db.get_errors(limit=50)
errors_to_add = []
for error in all_errors:
error_id, admin_id, error_type, title, description, status, priority, created_at, updated_at, ai_analysis, todo_added, resolved_at, admin_name, admin_username = error
if ai_analysis and not todo_added:
errors_to_add.append((error_id, title, error_type, priority, ai_analysis))
if not errors_to_add:
await update.message.reply_text("📭 Нет проанализированных ошибок для добавления в TODO список")
return
await update.message.reply_text(f"📝 Начинаю добавление {len(errors_to_add)} ошибок в TODO список...")
added_count = 0
failed_count = 0
for error_id, title, error_type, priority, ai_analysis in errors_to_add:
success = self.add_error_to_todo_file(error_id, title, error_type, priority, ai_analysis)
if success:
db.mark_error_todo_added(error_id)
added_count += 1
else:
failed_count += 1
# Отчет о результатах
result_text = (
f"✅ Процесс завершен!\n\n"
f"📊 Результаты:\n"
f"• Добавлено в TODO: {added_count}\n"
f"• Ошибок: {failed_count}"
)
await update.message.reply_text(result_text)
def run(self):
"""Запуск бота"""
print("Bot started!")
+66
View File
@@ -0,0 +1,66 @@
"""
Шаблон конфигурации для телеграм-бота
Скопируйте этот файл как config_local.py и заполните своими ключами API
"""
# Конфигурация бота
BOT_TOKEN = "ВАШ_БОТ_ТОКЕН_ЗДЕСЬ" # Получите от @BotFather в Telegram
# API ключи для внешних сервисов (опционально)
OPENWEATHER_API_KEY = "ВАШ_API_КЛЮЧ_ЗДЕСЬ" # Для погоды с openweathermap.org
NEWS_API_KEY = "ВАШ_API_КЛЮЧ_ЗДЕСЬ" # Для новостей с newsapi.org
# Список ID администраторов
ADMIN_IDS = [ВАШ_TELEGRAM_ID] # Замените на реальные ID администраторов
# Настройки уведомлений разработчика
DEVELOPER_CHAT_ID = None # ID чата разработчика для уведомлений об ошибках (например, -1001234567890)
ENABLE_DEVELOPER_NOTIFICATIONS = True # Включить/выключить уведомления разработчика
# Настройки ИИ для обработки ошибок
OPENAI_API_KEY = "ВАШ_OPENAI_API_КЛЮЧ_ЗДЕСЬ" # API ключ OpenAI для анализа ошибок ИИ
ENABLE_AI_ERROR_PROCESSING = True # Включить/выключить обработку ошибок ИИ
AI_MODEL = "gpt-3.5-turbo" # Модель ИИ для обработки ошибок
"""
ИНСТРУКЦИИ ПО НАСТРОЙКЕ:
1. BOT_TOKEN:
- Напишите боту @BotFather в Telegram
- Отправьте команду /newbot
- Следуйте инструкциям для создания нового бота
- Скопируйте полученный токен
2. OPENWEATHER_API_KEY:
- Зарегистрируйтесь на https://openweathermap.org/api
- Создайте бесплатный аккаунт
- Получите API ключ в разделе "API keys"
3. NEWS_API_KEY:
- Зарегистрируйтесь на https://newsapi.org/
- Получите API ключ в настройках аккаунта
4. OPENAI_API_KEY:
- Зарегистрируйтесь на https://platform.openai.com/
- Создайте API ключ в разделе "API Keys"
- Убедитесь, что у вас есть кредиты для использования API
5. DEVELOPER_CHAT_ID:
- Создайте чат в Telegram для уведомлений
- Добавьте туда своего бота
- Отправьте любое сообщение в этот чат
- Перешлите это сообщение боту @userinfobot
- Получите ID чата и укажите его здесь
6. ADMIN_IDS:
- Напишите боту @userinfobot
- Отправьте команду /start
- Получите свой Telegram ID
- Добавьте его в список администраторов
ВАЖНО:
- Никогда не публикуйте этот файл с реальными ключами в открытый доступ
- Файл config_local.py автоматически игнорируется Git благодаря .gitignore
- Убедитесь, что у вас достаточно прав для использования API сервисов
"""
+247 -4
View File
@@ -110,6 +110,25 @@ class Database:
)
""")
# Таблица ошибок и отчетов
cursor.execute("""
CREATE TABLE IF NOT EXISTS errors (
error_id INTEGER PRIMARY KEY AUTOINCREMENT,
admin_id INTEGER,
error_type TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT NOT NULL,
status TEXT DEFAULT 'new',
priority TEXT DEFAULT 'medium',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ai_analysis TEXT,
todo_added BOOLEAN DEFAULT 0,
resolved_at TIMESTAMP,
FOREIGN KEY (admin_id) REFERENCES users (user_id)
)
""")
self.connection.commit()
print(TECH_MESSAGES['db_tables_created'])
except sqlite3.Error as error:
@@ -206,15 +225,15 @@ class Database:
return False
def update_rank(self, user_id, chat_id=None, first_name=None):
"""Обновление ранга пользователя на основе репутации"""
"""Обновление ранга пользователя на основе очков (score)"""
try:
cursor = self.connection.cursor()
cursor.execute("SELECT reputation, rank FROM users WHERE user_id = ?", (user_id,))
cursor.execute("SELECT score, rank FROM users WHERE user_id = ?", (user_id,))
result = cursor.fetchone()
if result:
reputation = result[0]
score = result[0]
old_rank = result[1]
new_rank = self.calculate_rank(reputation)
new_rank = self.calculate_rank(score)
if new_rank != old_rank:
cursor.execute("""
@@ -693,6 +712,230 @@ class Database:
return achievements_unlocked
def add_error(self, admin_id, error_type, title, description, priority='medium'):
"""Добавление новой ошибки в систему"""
try:
# Валидация входных данных
if not isinstance(admin_id, int) or admin_id <= 0:
print(f"Ошибка валидации: некорректный admin_id = {admin_id}")
return None
if not error_type or not isinstance(error_type, str):
print(f"Ошибка валидации: некорректный тип ошибки = {error_type}")
return None
if not title or not isinstance(title, str) or len(title.strip()) == 0:
print("Ошибка валидации: заголовок ошибки пустой")
return None
if not description or not isinstance(description, str) or len(description.strip()) == 0:
print("Ошибка валидации: описание ошибки пустое")
return None
valid_priorities = ['low', 'medium', 'high', 'critical']
if priority not in valid_priorities:
print(f"Ошибка валидации: некорректный приоритет = {priority}")
return None
# Проверка соединения с базой данных
if not self.connection:
print("Ошибка: соединение с базой данных не установлено")
return None
cursor = self.connection.cursor()
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
cursor.execute("""
INSERT INTO errors (admin_id, error_type, title, description, priority, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (admin_id, error_type, title.strip(), description.strip(), priority, current_time, current_time))
self.connection.commit()
return cursor.lastrowid
except sqlite3.Error as error:
print(f"Ошибка базы данных при добавлении ошибки: {error}")
if self.connection:
self.connection.rollback()
return None
except Exception as error:
print(f"Неожиданная ошибка при добавлении ошибки: {error}")
return None
def get_errors(self, status=None, limit=50):
"""Получение списка ошибок с фильтрацией по статусу"""
try:
cursor = self.connection.cursor()
if status:
cursor.execute("""
SELECT e.error_id, e.admin_id, e.error_type, e.title, e.description,
e.status, e.priority, e.created_at, e.updated_at, e.ai_analysis,
e.todo_added, e.resolved_at, u.first_name, u.username
FROM errors e
LEFT JOIN users u ON e.admin_id = u.user_id
WHERE e.status = ?
ORDER BY
CASE e.priority
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'medium' THEN 3
WHEN 'low' THEN 4
END,
e.created_at DESC
LIMIT ?
""", (status, limit))
else:
cursor.execute("""
SELECT e.error_id, e.admin_id, e.error_type, e.title, e.description,
e.status, e.priority, e.created_at, e.updated_at, e.ai_analysis,
e.todo_added, e.resolved_at, u.first_name, u.username
FROM errors e
LEFT JOIN users u ON e.admin_id = u.user_id
ORDER BY
CASE e.priority
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'medium' THEN 3
WHEN 'low' THEN 4
END,
e.created_at DESC
LIMIT ?
""", (limit,))
return cursor.fetchall()
except sqlite3.Error as error:
print(f"Ошибка при получении ошибок: {error}")
return []
def update_error_status(self, error_id, status, admin_id=None):
"""Обновление статуса ошибки"""
try:
# Валидация входных данных
if not isinstance(error_id, int) or error_id <= 0:
print(f"Ошибка валидации: некорректный error_id = {error_id}")
return False
valid_statuses = ['new', 'in_progress', 'resolved', 'rejected']
if status not in valid_statuses:
print(f"Ошибка валидации: некорректный статус = {status}")
return False
# Проверка соединения с базой данных
if not self.connection:
print("Ошибка: соединение с базой данных не установлено")
return False
cursor = self.connection.cursor()
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if status == 'resolved':
cursor.execute("""
UPDATE errors
SET status = ?, updated_at = ?, resolved_at = ?
WHERE error_id = ?
""", (status, current_time, current_time, error_id))
else:
cursor.execute("""
UPDATE errors
SET status = ?, updated_at = ?
WHERE error_id = ?
""", (status, current_time, error_id))
self.connection.commit()
return cursor.rowcount > 0
except sqlite3.Error as error:
print(f"Ошибка базы данных при обновлении статуса ошибки: {error}")
if self.connection:
self.connection.rollback()
return False
def update_error_ai_analysis(self, error_id, ai_analysis):
"""Обновление анализа ИИ для ошибки"""
try:
# Валидация входных данных
if not isinstance(error_id, int) or error_id <= 0:
print(f"Ошибка валидации: некорректный error_id = {error_id}")
return False
if not ai_analysis or not isinstance(ai_analysis, str):
print(f"Ошибка валидации: некорректный анализ ИИ = {ai_analysis}")
return False
# Проверка соединения с базой данных
if not self.connection:
print("Ошибка: соединение с базой данных не установлено")
return False
cursor = self.connection.cursor()
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
cursor.execute("""
UPDATE errors
SET ai_analysis = ?, updated_at = ?
WHERE error_id = ?
""", (ai_analysis, current_time, error_id))
self.connection.commit()
return cursor.rowcount > 0
except sqlite3.Error as error:
print(f"Ошибка базы данных при обновлении анализа ИИ: {error}")
if self.connection:
self.connection.rollback()
return False
def mark_error_todo_added(self, error_id):
"""Отметить ошибку как добавленную в TODO список"""
try:
# Валидация входных данных
if not isinstance(error_id, int) or error_id <= 0:
print(f"Ошибка валидации: некорректный error_id = {error_id}")
return False
# Проверка соединения с базой данных
if not self.connection:
print("Ошибка: соединение с базой данных не установлено")
return False
cursor = self.connection.cursor()
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
cursor.execute("""
UPDATE errors
SET todo_added = 1, updated_at = ?
WHERE error_id = ?
""", (current_time, error_id))
self.connection.commit()
return cursor.rowcount > 0
except sqlite3.Error as error:
print(f"Ошибка базы данных при отметке ошибки в TODO: {error}")
if self.connection:
self.connection.rollback()
return False
def get_error_by_id(self, error_id):
"""Получение конкретной ошибки по ID"""
try:
# Валидация входных данных
if not isinstance(error_id, int) or error_id <= 0:
print(f"Ошибка валидации: некорректный error_id = {error_id}")
return None
cursor = self.connection.cursor()
cursor.execute("""
SELECT e.error_id, e.admin_id, e.error_type, e.title, e.description,
e.status, e.priority, e.created_at, e.updated_at, e.ai_analysis,
e.todo_added, e.resolved_at, u.first_name, u.username
FROM errors e
LEFT JOIN users u ON e.admin_id = u.user_id
WHERE e.error_id = ?
""", (error_id,))
result = cursor.fetchone()
return result
except sqlite3.Error as error:
print(f"Ошибка при получении ошибки: {error}")
return None
def close(self):
"""Закрытие соединения с базой данных"""
if self.connection:
+41
View File
@@ -0,0 +1,41 @@
version: '3.8'
services:
telegram-bot:
build: .
container_name: telegram-bot
restart: unless-stopped
environment:
- BOT_TOKEN=${BOT_TOKEN}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- DEVELOPER_CHAT_ID=${DEVELOPER_CHAT_ID}
- ENABLE_DEVELOPER_NOTIFICATIONS=${ENABLE_DEVELOPER_NOTIFICATIONS:-True}
- ENABLE_AI_ERROR_PROCESSING=${ENABLE_AI_ERROR_PROCESSING:-True}
volumes:
- bot_data:/app/data
- ./config_local.py:/app/config_local.py:ro
networks:
- bot-network
scheduler:
build: .
container_name: telegram-bot-scheduler
restart: unless-stopped
command: python scheduler.py
environment:
- BOT_TOKEN=${BOT_TOKEN}
volumes:
- bot_data:/app/data
- ./config_local.py:/app/config_local.py:ro
networks:
- bot-network
depends_on:
- telegram-bot
volumes:
bot_data:
driver: local
networks:
bot-network:
driver: bridge
+1
View File
@@ -2,3 +2,4 @@ python-telegram-bot==20.7
requests==2.31.0
speech_recognition==3.10.0
pydub==0.25.1
openai==1.3.7
+341
View File
@@ -0,0 +1,341 @@
#!/usr/bin/env python3
"""
Test script for telegram bot error reporting system
Usage: python test_error_system.py
"""
import os
import sys
import time
from datetime import datetime
# Add current directory to path for imports
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from database_sqlite import Database
from config_local import DEVELOPER_CHAT_ID, ENABLE_DEVELOPER_NOTIFICATIONS, OPENAI_API_KEY, ENABLE_AI_ERROR_PROCESSING
class ErrorSystemTester:
"""Class for testing error reporting system"""
def __init__(self):
self.db = None
self.test_results = []
self.start_time = datetime.now()
def log(self, message, status="INFO"):
"""Log test results"""
timestamp = datetime.now().strftime("%H:%M:%S")
status_icons = {
"SUCCESS": "[OK]",
"ERROR": "[FAIL]",
"WARNING": "[WARN]",
"INFO": "[INFO]"
}
icon = status_icons.get(status, "[INFO]")
print(f"[{timestamp}] {icon} {message}")
self.test_results.append({
'timestamp': timestamp,
'status': status,
'message': message
})
def print_header(self, title):
"""Print test header"""
print(f"\n{'='*60}")
print(f"[TEST] {title}")
print(f"{'='*60}")
def test_database_connection(self):
"""Test database connection"""
self.print_header("TESTING DATABASE CONNECTION")
try:
self.db = Database()
if self.db.connection:
self.log("Database connection established successfully", "SUCCESS")
return True
else:
self.log("Failed to connect to database", "ERROR")
return False
except Exception as e:
self.log(f"Database connection error: {e}", "ERROR")
return False
def test_table_creation(self):
"""Test table creation"""
self.print_header("TESTING TABLE CREATION")
try:
cursor = self.db.connection.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='errors'")
if cursor.fetchone():
self.log("Table 'errors' exists", "SUCCESS")
return True
else:
self.log("Table 'errors' not found", "ERROR")
return False
except Exception as e:
self.log(f"Table check error: {e}", "ERROR")
return False
def test_add_error(self):
"""Test adding error to database"""
self.print_header("TESTING ADD ERROR")
try:
error_id = self.db.add_error(
admin_id=123456789,
error_type="bug",
title="Test error for system validation",
description="This is a test error created by testing script",
priority="medium"
)
if error_id and error_id > 0:
self.log(f"Error successfully added with ID: {error_id}", "SUCCESS")
return error_id
else:
self.log("Failed to add error", "ERROR")
return None
except Exception as e:
self.log(f"Error adding error: {e}", "ERROR")
return None
def test_get_errors(self):
"""Test getting errors list"""
self.print_header("TESTING GET ERRORS")
try:
errors = self.db.get_errors(limit=10)
if isinstance(errors, list) and len(errors) > 0:
self.log(f"Retrieved {len(errors)} errors from database", "SUCCESS")
first_error = errors[0]
expected_fields = 14
if len(first_error) == expected_fields:
self.log("Error data structure is correct", "SUCCESS")
return True
else:
self.log(f"Wrong number of fields: expected {expected_fields}, got {len(first_error)}", "ERROR")
return False
else:
self.log("No errors found (this may be normal for new DB)", "WARNING")
return True
except Exception as e:
self.log(f"Error getting errors: {e}", "ERROR")
return False
def test_ai_analysis_mock(self):
"""Test AI analysis (mock version)"""
self.print_header("TESTING AI ANALYSIS")
try:
from bot import TelegramBot
bot = TelegramBot()
if not OPENAI_API_KEY or OPENAI_API_KEY == "ВСТАВЬТЕ_ВАШ_OPENAI_API_КЛЮЧ_ЗДЕСЬ":
self.log("OpenAI API key not configured - skipping real analysis test", "WARNING")
if hasattr(bot, 'process_error_with_ai'):
self.log("Method process_error_with_ai exists", "SUCCESS")
return True
else:
self.log("Method process_error_with_ai not found", "ERROR")
return False
else:
self.log("OpenAI API key configured - can test real analysis", "SUCCESS")
return True
except ImportError as e:
self.log(f"Bot import error: {e}", "ERROR")
return False
except Exception as e:
self.log(f"AI analysis test error: {e}", "ERROR")
return False
def test_todo_integration(self):
"""Test TODO integration"""
self.print_header("TESTING TODO INTEGRATION")
try:
from bot import TelegramBot
bot = TelegramBot()
if hasattr(bot, 'add_error_to_todo_file'):
test_error_id = self.db.add_error(
admin_id=123456789,
error_type="improvement",
title="Test task for TODO",
description="This task created by test script",
priority="low"
)
if test_error_id:
success = bot.add_error_to_todo_file(
error_id=test_error_id,
title="Test task for TODO",
error_type="improvement",
priority="low",
ai_analysis="Test analysis for integration check"
)
if success:
self.log(f"Test error #{test_error_id} added to TODO file", "SUCCESS")
if os.path.exists('TODO.md'):
self.log("TODO.md file exists and accessible", "SUCCESS")
return True
else:
self.log("TODO.md file not found", "ERROR")
return False
else:
self.log("Failed to add error to TODO file", "ERROR")
return False
else:
self.log("Failed to create test error for TODO", "ERROR")
return False
else:
self.log("Method add_error_to_todo_file not found", "ERROR")
return False
except Exception as e:
self.log(f"TODO integration test error: {e}", "ERROR")
return False
def test_notification_system(self):
"""Test notification system"""
self.print_header("TESTING NOTIFICATION SYSTEM")
try:
from bot import TelegramBot
bot = TelegramBot()
if hasattr(bot, 'send_developer_notification'):
self.log("Method send_developer_notification exists", "SUCCESS")
if ENABLE_DEVELOPER_NOTIFICATIONS:
self.log("Developer notifications enabled", "SUCCESS")
else:
self.log("Developer notifications disabled", "WARNING")
if DEVELOPER_CHAT_ID:
self.log(f"Developer chat ID configured: {DEVELOPER_CHAT_ID}", "SUCCESS")
else:
self.log("Developer chat ID not configured", "WARNING")
return True
else:
self.log("Method send_developer_notification not found", "ERROR")
return False
except Exception as e:
self.log(f"Notification system test error: {e}", "ERROR")
return False
def run_all_tests(self):
"""Run all tests"""
print("STARTING ERROR REPORTING SYSTEM TESTS")
print(f"Start time: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
tests = [
("Database Connection", self.test_database_connection),
("Table Creation", self.test_table_creation),
("Add Error", self.test_add_error),
("Get Errors", self.test_get_errors),
("AI Analysis", self.test_ai_analysis_mock),
("TODO Integration", self.test_todo_integration),
("Notification System", self.test_notification_system),
]
passed = 0
failed = 0
warnings = 0
for test_name, test_func in tests:
try:
if test_func():
passed += 1
else:
failed += 1
except Exception as e:
self.log(f"Critical error in test '{test_name}': {e}", "ERROR")
failed += 1
time.sleep(0.5)
self.print_summary(passed, failed, warnings)
return failed == 0
def print_summary(self, passed, failed, warnings):
"""Print test summary"""
end_time = datetime.now()
duration = end_time - self.start_time
print(f"\n{'='*80}")
print("TEST RESULTS SUMMARY")
print(f"{'='*80}")
print(f"Duration: {duration.total_seconds():.1f} seconds")
print(f"[OK] Passed: {passed}")
print(f"[FAIL] Failed: {failed}")
print(f"[WARN] Warnings: {warnings}")
print(f"[%] Success rate: {((passed) / (passed + failed) * 100):.1f}%" if (passed + failed) > 0 else "N/A")
print("\nDETAILED RESULTS:")
for result in self.test_results:
print(f" [{result['timestamp']}] {result['status']}: {result['message']}")
if failed == 0:
print("\n*** ALL TESTS PASSED! ***")
print("Error reporting system is ready to use.")
else:
print(f"\n*** SOME TESTS FAILED ({failed} out of {passed + failed}) ***")
print("Check configuration and run tests again.")
print(f"{'='*80}")
def cleanup_test_data(self):
"""Cleanup test data"""
self.print_header("CLEANUP TEST DATA")
try:
if self.db and self.db.connection:
cursor = self.db.connection.cursor()
cursor.execute("DELETE FROM errors WHERE title LIKE '%Test%' OR title LIKE '%test%'")
cursor.execute("DELETE FROM sqlite_sequence WHERE name='errors'")
self.db.connection.commit()
self.log("Test data cleaned", "SUCCESS")
except Exception as e:
self.log(f"Error during test data cleanup: {e}", "WARNING")
def main():
"""Main function"""
print("TELEGRAM BOT ERROR SYSTEM TESTING")
print("=" * 80)
tester = ErrorSystemTester()
try:
success = tester.run_all_tests()
tester.cleanup_test_data()
exit_code = 0 if success else 1
print(f"\nTest completed with exit code: {exit_code}")
sys.exit(exit_code)
except KeyboardInterrupt:
print("\n\nTesting interrupted by user")
tester.cleanup_test_data()
sys.exit(130)
except Exception as e:
print(f"\nCritical error during testing: {e}")
tester.cleanup_test_data()
sys.exit(1)
if __name__ == "__main__":
main()
+408
View File
@@ -0,0 +1,408 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Тест системы модерации
"""
import sys
import os
from datetime import datetime, timedelta
# Добавляем текущую директорию в путь для импорта
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from database_sqlite import Database
from messages import PROFANITY_WORDS, MODERATION_MESSAGES
class ModerationSystemTester:
"""Класс для тестирования системы модерации"""
def __init__(self):
self.db = None
self.test_results = []
self.start_time = datetime.now()
def log(self, message, status="INFO"):
"""Логирование результатов теста"""
timestamp = datetime.now().strftime("%H:%M:%S")
status_icons = {
"SUCCESS": "[OK]",
"ERROR": "[FAIL]",
"WARNING": "[WARN]",
"INFO": "[INFO]"
}
icon = status_icons.get(status, "[INFO]")
print(f"[{timestamp}] {icon} {message}")
self.test_results.append({
'timestamp': timestamp,
'status': status,
'message': message
})
def print_header(self, title):
"""Вывод заголовка теста"""
print(f"\n{'='*60}")
print(f"[TEST] {title}")
print(f"{'='*60}")
def test_database_connection(self):
"""Тест подключения к базе данных"""
self.print_header("ТЕСТИРОВАНИЕ ПОДКЛЮЧЕНИЯ К БАЗЕ ДАННЫХ")
try:
self.db = Database()
if self.db.connection:
self.log("Подключение к базе данных установлено успешно", "SUCCESS")
return True
else:
self.log("Не удалось подключиться к базе данных", "ERROR")
return False
except Exception as e:
self.log(f"Ошибка подключения к базе данных: {e}", "ERROR")
return False
def test_profanity_filter(self):
"""Тест системы фильтрации мата"""
self.print_header("ТЕСТИРОВАНИЕ СИСТЕМЫ ФИЛЬТРАЦИИ МАТА")
try:
from bot import TelegramBot
bot = TelegramBot()
if hasattr(bot, 'check_profanity'):
self.log("Метод check_profanity найден", "SUCCESS")
else:
self.log("Метод check_profanity не найден", "ERROR")
return False
# Тестовые случаи для мата
test_cases = [
("Привет, как дела?", False),
("Это нормальное сообщение", False),
("блядь, что происходит", True),
("Пошел нахуй придурок", True),
("fuck you asshole", True),
("Это сообщение без мата", False),
("сука, ты идиот", True),
("damn, this is bad", True),
]
passed_tests = 0
total_tests = len(test_cases)
for text, should_contain_profanity in test_cases:
result = bot.check_profanity(text)
if result == should_contain_profanity:
self.log(f"Текст '{text[:30]}...' -> {result} (ожидаемо: {should_contain_profanity})", "SUCCESS")
passed_tests += 1
else:
self.log(f"Текст '{text[:30]}...' -> {result} (ожидаемо: {should_contain_profanity})", "ERROR")
if passed_tests == total_tests:
self.log(f"Все {total_tests} тестов фильтрации мата пройдены", "SUCCESS")
return True
else:
self.log(f"Пройдено {passed_tests} из {total_tests} тестов фильтрации мата", "WARNING")
return False
except Exception as e:
self.log(f"Ошибка при тестировании фильтрации мата: {e}", "ERROR")
return False
def test_moderation_commands_validation(self):
"""Тест валидации команд модерации"""
self.print_header("ТЕСТИРОВАНИЕ ВАЛИДАЦИИ КОМАНД МОДЕРАЦИИ")
try:
from bot import TelegramBot
bot = TelegramBot()
# Тестовые случаи для валидации
test_cases = [
# (user_id, target_id, should_be_valid, description)
(12345, 12345, False, "Пользователь не может модерировать себя"),
(12345, 67890, True, "Нормальная модерация пользователя"),
(12345, None, False, "Целевой пользователь не указан"),
]
passed_tests = 0
total_tests = len(test_cases)
for admin_id, target_id, should_be_valid, description in test_cases:
# Здесь мы тестируем логику валидации косвенно
# В реальном коде это делается в самих командах модерации
if target_id == admin_id:
is_valid = False # Не должно позволять модерировать себя
elif target_id is None:
is_valid = False # Должен быть указан целевой пользователь
else:
is_valid = True # Нормальный случай
if is_valid == should_be_valid:
self.log(f"{description} -> {is_valid} (ожидаемо: {should_be_valid})", "SUCCESS")
passed_tests += 1
else:
self.log(f"{description} -> {is_valid} (ожидаемо: {should_be_valid})", "ERROR")
if passed_tests == total_tests:
self.log(f"Все {total_tests} тестов валидации пройдены", "SUCCESS")
return True
else:
self.log(f"Пройдено {passed_tests} из {total_tests} тестов валидации", "WARNING")
return False
except Exception as e:
self.log(f"Ошибка при тестировании валидации команд: {e}", "ERROR")
return False
def test_warning_system(self):
"""Тест системы предупреждений"""
self.print_header("ТЕСТИРОВАНИЕ СИСТЕМЫ ПРЕДУПРЕЖДЕНИЙ")
try:
if not self.db:
self.log("База данных недоступна", "ERROR")
return False
# Тест добавления предупреждения
test_user_id = 999999
test_reason = "Тестовое предупреждение от системы тестирования"
try:
# Сначала создаем пользователя
self.db.add_user(test_user_id, "test_user", "Тест", "Пользователь")
self.db.add_warning(test_user_id, test_reason, 0) # 0 - системное предупреждение
self.log("Предупреждение успешно добавлено в базу данных", "SUCCESS")
# Проверяем, что количество предупреждений увеличилось
user_info = self.db.get_user_info(test_user_id)
if user_info and user_info['Предупреждений'] > 0:
self.log(f"Предупреждений у пользователя: {user_info['Предупреждений']}", "SUCCESS")
return True
else:
self.log("Предупреждение не отразилось в профиле пользователя", "ERROR")
return False
except Exception as e:
self.log(f"Ошибка при работе с предупреждениями: {e}", "ERROR")
return False
except Exception as e:
self.log(f"Ошибка при тестировании системы предупреждений: {e}", "ERROR")
return False
def test_user_info_for_moderation(self):
"""Тест получения информации о пользователе для модерации"""
self.print_header("ТЕСТИРОВАНИЕ ПОЛУЧЕНИЯ ИНФОРМАЦИИ О ПОЛЬЗОВАТЕЛЕ")
try:
if not self.db:
self.log("База данных недоступна", "ERROR")
return False
# Создаем тестового пользователя
test_user_id = 888888
self.db.add_user(test_user_id, "test_user", "Тест", "Пользователь")
# Получаем информацию о пользователе
user_info = self.db.get_user_info(test_user_id)
if user_info:
required_fields = ['ID', 'Имя', 'Предупреждений', 'Роль']
missing_fields = []
for field in required_fields:
if field not in user_info:
missing_fields.append(field)
if not missing_fields:
self.log("Все необходимые поля присутствуют в информации о пользователе", "SUCCESS")
self.log(f"Пользователь: {user_info['Имя']}, Предупреждений: {user_info['Предупреждений']}", "SUCCESS")
return True
else:
self.log(f"Отсутствуют поля: {', '.join(missing_fields)}", "ERROR")
return False
else:
self.log("Информация о пользователе не найдена", "ERROR")
return False
except Exception as e:
self.log(f"Ошибка при получении информации о пользователе: {e}", "ERROR")
return False
def test_moderation_messages(self):
"""Тест сообщений модерации"""
self.print_header("ТЕСТИРОВАНИЕ СООБЩЕНИЙ МОДЕРАЦИИ")
try:
required_messages = [
'no_permission',
'user_banned',
'user_unbanned',
'user_muted',
'user_unmuted',
'user_kicked',
'user_promoted',
'user_demoted',
'warning_issued',
'profanity_detected'
]
missing_messages = []
for msg_key in required_messages:
if msg_key in MODERATION_MESSAGES:
self.log(f"Сообщение '{msg_key}' найдено", "SUCCESS")
else:
missing_messages.append(msg_key)
self.log(f"Сообщение '{msg_key}' отсутствует", "ERROR")
if not missing_messages:
self.log(f"Все {len(required_messages)} сообщений модерации присутствуют", "SUCCESS")
return True
else:
self.log(f"Отсутствуют сообщения: {', '.join(missing_messages)}", "ERROR")
return False
except Exception as e:
self.log(f"Ошибка при проверке сообщений модерации: {e}", "ERROR")
return False
def test_profanity_words_list(self):
"""Тест списка запрещенных слов"""
self.print_header("ТЕСТИРОВАНИЕ СПИСКА ЗАПРЕЩЕННЫХ СЛОВ")
try:
if PROFANITY_WORDS:
self.log(f"Список запрещенных слов загружен: {len(PROFANITY_WORDS)} слов", "SUCCESS")
# Проверяем, что слова записаны в нижнем регистре
lowercase_words = [word for word in PROFANITY_WORDS if word.islower()]
if len(lowercase_words) == len(PROFANITY_WORDS):
self.log("Все слова в списке записаны в нижнем регистре", "SUCCESS")
else:
self.log("Некоторые слова не в нижнем регистре", "WARNING")
# Проверяем разнообразие слов
unique_words = set(PROFANITY_WORDS)
if len(unique_words) == len(PROFANITY_WORDS):
self.log("Все слова в списке уникальны", "SUCCESS")
else:
self.log("В списке есть дубликаты", "WARNING")
return True
else:
self.log("Список запрещенных слов пуст", "ERROR")
return False
except Exception as e:
self.log(f"Ошибка при проверке списка запрещенных слов: {e}", "ERROR")
return False
def run_all_tests(self):
"""Запуск всех тестов"""
print("НАЧИНАЕМ ТЕСТИРОВАНИЕ СИСТЕМЫ МОДЕРАЦИИ")
print(f"Время начала: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
tests = [
("Подключение к БД", self.test_database_connection),
("Сообщения модерации", self.test_moderation_messages),
("Список запрещенных слов", self.test_profanity_words_list),
("Фильтр мата", self.test_profanity_filter),
("Валидация команд", self.test_moderation_commands_validation),
("Система предупреждений", self.test_warning_system),
("Информация о пользователе", self.test_user_info_for_moderation),
]
passed = 0
failed = 0
warnings = 0
for test_name, test_func in tests:
try:
if test_func():
passed += 1
else:
failed += 1
except Exception as e:
self.log(f"Критическая ошибка в тесте '{test_name}': {e}", "ERROR")
failed += 1
self.print_summary(passed, failed, warnings)
return failed == 0
def print_summary(self, passed, failed, warnings):
"""Вывод сводки результатов"""
end_time = datetime.now()
duration = end_time - self.start_time
print(f"\n{'='*80}")
print("СВОДКА РЕЗУЛЬТАТОВ ТЕСТИРОВАНИЯ")
print(f"{'='*80}")
print(f"Длительность: {duration.total_seconds():.1f} секунд")
print(f"[OK] Пройдено: {passed}")
print(f"[FAIL] Ошибок: {failed}")
print(f"[WARN] Предупреждений: {warnings}")
success_rate = (passed / (passed + failed) * 100) if (passed + failed) > 0 else 0
print(f"[%] Успешность: {success_rate:.1f}%")
print("\nДЕТАЛЬНЫЕ РЕЗУЛЬТАТЫ:")
for result in self.test_results:
print(f" [{result['timestamp']}] {result['status']}: {result['message']}")
if failed == 0:
print("\n*** ВСЕ ТЕСТЫ ПРОЙДЕНЫ! ***")
print("Система модерации готова к использованию.")
else:
print(f"\n*** НЕКОТОРЫЕ ТЕСТЫ ПРОВАЛЕНЫ ({failed} из {passed + failed}) ***")
print("Проверьте конфигурацию и запустите тесты снова.")
print(f"{'='*80}")
def cleanup_test_data(self):
"""Очистка тестовых данных"""
self.print_header("ОЧИСТКА ТЕСТОВЫХ ДАННЫХ")
try:
if self.db and self.db.connection:
cursor = self.db.connection.cursor()
# Удаляем тестовых пользователей и предупреждения
test_user_ids = [999999, 888888]
for user_id in test_user_ids:
cursor.execute("DELETE FROM users WHERE id = ?", (user_id,))
cursor.execute("DELETE FROM warnings WHERE user_id = ?", (user_id,))
self.db.connection.commit()
self.log("Тестовые данные очищены", "SUCCESS")
except Exception as e:
self.log(f"Ошибка при очистке тестовых данных: {e}", "WARNING")
def main():
"""Главная функция"""
print("ТЕСТИРОВАНИЕ СИСТЕМЫ МОДЕРАЦИИ TELEGRAM БОТА")
print("=" * 80)
tester = ModerationSystemTester()
try:
success = tester.run_all_tests()
tester.cleanup_test_data()
exit_code = 0 if success else 1
print(f"\nТестирование завершено с кодом выхода: {exit_code}")
sys.exit(exit_code)
except KeyboardInterrupt:
print("\n\nТестирование прервано пользователем")
tester.cleanup_test_data()
sys.exit(130)
except Exception as e:
print(f"\nКритическая ошибка при тестировании: {e}")
tester.cleanup_test_data()
sys.exit(1)
if __name__ == "__main__":
main()