mirror of
https://github.com/FerraSoft/bottohelp.git
synced 2026-08-06 21:55:03 +00:00
feat: добавлены комплексные тесты для системы модерации
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
# 🚀 Telegram Bot с системой отчетов об ошибках и ИИ-анализом
|
||||
|
||||
Многофункциональный Telegram бот на Python с продвинутой системой ранжирования пользователей, играми и интеграцией с внешними API.
|
||||
|
||||
[](https://python.org)
|
||||
[](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 ботов**
|
||||
@@ -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
@@ -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
@@ -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"]
|
||||
@@ -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 для подробностей.
|
||||
MIT License - см. файл LICENSE для подробностей.
|
||||
|
||||
---
|
||||
|
||||
*Последнее обновление: 16 октября 2025 (добавлена версия 1.4.0 с критическими исправлениями и система отчетов об ошибках)*
|
||||
@@ -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.
@@ -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!")
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
python-telegram-bot==20.7
|
||||
requests==2.31.0
|
||||
speech_recognition==3.10.0
|
||||
pydub==0.25.1
|
||||
pydub==0.25.1
|
||||
openai==1.3.7
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user