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
|
*.db
|
||||||
*.sqlite
|
*.sqlite
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
|
||||||
# Файлы Python
|
# Временные файлы
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
*.so
|
*.so
|
||||||
.Python
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Виртуальное окружение
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
|
||||||
# Файлы конфигурации с чувствительной информацией
|
# IDE файлы
|
||||||
config_local.py
|
.vscode/
|
||||||
.env
|
.idea/
|
||||||
.env.local
|
*.swp
|
||||||
.env.*.local
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
# Логи
|
# Логи
|
||||||
*.log
|
*.log
|
||||||
@@ -28,10 +50,9 @@ logs/
|
|||||||
# Временные файлы
|
# Временные файлы
|
||||||
*.tmp
|
*.tmp
|
||||||
*.temp
|
*.temp
|
||||||
.tmp/
|
.cache/
|
||||||
temp/
|
|
||||||
|
|
||||||
# Файлы ОС
|
# OS файлы
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.DS_Store?
|
.DS_Store?
|
||||||
._*
|
._*
|
||||||
@@ -40,22 +61,7 @@ temp/
|
|||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# IDE
|
# Конфиденциальные ключи (дополнительно)
|
||||||
.vscode/
|
*_api_key.txt
|
||||||
.idea/
|
*_token.txt
|
||||||
*.swp
|
.secrets
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
|
|
||||||
# CSV файлы с данными пользователей (если не нужны в репозитории)
|
|
||||||
chat_*.csv
|
|
||||||
|
|
||||||
# Документация сборки
|
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
*.egg-info/
|
|
||||||
|
|
||||||
# Токены и ключи API (если случайно попадут)
|
|
||||||
*token*
|
|
||||||
*api_key*
|
|
||||||
*secret*
|
|
||||||
+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) - Критические исправления
|
### Версия 1.5.0 (18 октября 2025) - Система модерации медиафайлов
|
||||||
**Дата релиза:** 16.10.2025
|
**Дата релиза:** 18.10.2025
|
||||||
|
|
||||||
#### 🚨 Критические исправления безопасности и стабильности
|
#### 🆕 Новая система модерации медиафайлов
|
||||||
- ✅ **Исправлена система рангов** - синхронизированы поля очков и репутации в базе данных
|
- ✅ **Автоматическая модерация аудио и видео** - все загружаемые медиафайлы проходят модерацию
|
||||||
- ✅ **Улучшена обработка ошибок внешних API** - добавлены таймауты, детальная обработка HTTP-ошибок
|
- ✅ **Система уведомлений администраторов** - личные уведомления о новых медиафайлах для модерации
|
||||||
- ✅ **Добавлена валидация входящих данных** - защита от некорректных данных и злоупотреблений
|
- ✅ **Интеграция с планировщиком постов** - одобренные медиа автоматически планируются на публикацию
|
||||||
- ✅ **Улучшена обработка исключений в БД** - валидация параметров, автоматический rollback транзакций
|
- ✅ **Отсрочка публикации** - медиа публикуется через 8 часов после одобрения (настраиваемо)
|
||||||
|
- ✅ **Транскрибация аудио** - автоматическое распознавание речи для модерации
|
||||||
|
- ✅ **Интерфейс выбора действий** - администраторы могут одобрить, отклонить или запланировать медиа
|
||||||
|
- ✅ **Автоматическое удаление** - оригинальные сообщения с медиа удаляются из чата
|
||||||
|
- ✅ **Пересылка в группу модераторов** - медиа отправляется в специальную группу для рассмотрения
|
||||||
|
|
||||||
|
#### 🔧 Улучшения системы
|
||||||
|
- ✅ **Расширенная обработка медиа** - поддержка аудио, видео и документов
|
||||||
|
- ✅ **Система метаданных** - сохранение информации о файлах (длительность, размер, транскрипция)
|
||||||
|
- ✅ **Уведомления пользователей** - пользователи получают информацию об отклонении контента
|
||||||
|
- ✅ **Гибкие настройки модерации** - выбор между немедленной публикацией и отсрочкой
|
||||||
|
|
||||||
#### Исправления ошибок
|
#### Исправления ошибок
|
||||||
- ✅ Исправлена ошибка с повторяющимся сообщением о "первом сообщении" при вводе суммы доната
|
- ✅ Исправлена ошибка с повторяющимся сообщением о "первом сообщении" при вводе суммы доната
|
||||||
@@ -48,6 +58,7 @@
|
|||||||
- ✅ Приветствие новых пользователей при добавлении в группу с правилами группы
|
- ✅ Приветствие новых пользователей при добавлении в группу с правилами группы
|
||||||
- ✅ Автоматическое удаление приветственных сообщений через 2 минуты
|
- ✅ Автоматическое удаление приветственных сообщений через 2 минуты
|
||||||
- ✅ Интеграция донатов в приветственные сообщения
|
- ✅ Интеграция донатов в приветственные сообщения
|
||||||
|
- ✅ **Автореакции** - автоматическая реакция 🤝 на сообщения с "+" для повышения вовлеченности
|
||||||
|
|
||||||
### Безопасность и надежность
|
### Безопасность и надежность
|
||||||
- 🔒 **Валидация входящих данных** - все команды проверяют корректность параметров
|
- 🔒 **Валидация входящих данных** - все команды проверяют корректность параметров
|
||||||
@@ -87,6 +98,20 @@
|
|||||||
- 💰 Интеграция кнопок донатов прямо в приветственные сообщения
|
- 💰 Интеграция кнопок донатов прямо в приветственные сообщения
|
||||||
- 🎯 Быстрый доступ к помощи через приветственные кнопки
|
- 🎯 Быстрый доступ к помощи через приветственные кнопки
|
||||||
|
|
||||||
|
### 🤝 Интерактивные функции пользователей
|
||||||
|
- 👍 **Автореакции** - автоматическая реакция 🤝 на сообщения содержащие "+"
|
||||||
|
- 🔄 **Отзывчивость** - бот реагирует на положительные сообщения пользователей
|
||||||
|
- 📈 **Вовлеченность** - повышение активности в чате через интерактивные элементы
|
||||||
|
|
||||||
|
### Система отчетов об ошибках и ИИ-анализа
|
||||||
|
- 🚨 **Отчеты об ошибках** - администраторы могут отправлять отчеты об ошибках бота
|
||||||
|
- 📊 **Управление ошибками** - просмотр, фильтрация и управление списком ошибок
|
||||||
|
- 🤖 **ИИ-анализ** - автоматический анализ ошибок с помощью OpenAI GPT
|
||||||
|
- 📝 **Интеграция с TODO** - автоматическое добавление обработанных ошибок в список задач разработки
|
||||||
|
- 🔔 **Уведомления разработчика** - мгновенные уведомления о новых ошибках и их статусе
|
||||||
|
- 🧠 **Автоматизированная обработка** - пакетная обработка ошибок ИИ и добавление в план разработки
|
||||||
|
- 🧪 **Тестирование системы** - встроенный тестовый скрипт для проверки функциональности
|
||||||
|
|
||||||
## Установка и настройка
|
## Установка и настройка
|
||||||
|
|
||||||
### 1. Клонирование репозитория
|
### 1. Клонирование репозитория
|
||||||
@@ -107,13 +132,20 @@ pip install -r requirements.txt
|
|||||||
- **Telegram Bot Token**: Получите от [@BotFather](https://t.me/botfather)
|
- **Telegram Bot Token**: Получите от [@BotFather](https://t.me/botfather)
|
||||||
- **OpenWeatherMap API Key**: Зарегистрируйтесь на [openweathermap.org](https://openweathermap.org/api)
|
- **OpenWeatherMap API Key**: Зарегистрируйтесь на [openweathermap.org](https://openweathermap.org/api)
|
||||||
- **NewsAPI Key**: Зарегистрируйтесь на [newsapi.org](https://newsapi.org/)
|
- **NewsAPI Key**: Зарегистрируйтесь на [newsapi.org](https://newsapi.org/)
|
||||||
|
- **OpenAI API Key**: Зарегистрируйтесь на [platform.openai.com](https://platform.openai.com/) для анализа ошибок ИИ
|
||||||
|
|
||||||
Обновите `config.py` с вашими ключами:
|
Обновите `config_local.py` с вашими ключами:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
BOT_TOKEN = "your_bot_token_here"
|
BOT_TOKEN = "your_bot_token_here"
|
||||||
OPENWEATHER_API_KEY = "your_weather_api_key_here"
|
OPENWEATHER_API_KEY = "your_weather_api_key_here"
|
||||||
NEWS_API_KEY = "your_news_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. Запуск бота
|
### 5. Запуск бота
|
||||||
@@ -158,6 +190,27 @@ python bot.py
|
|||||||
- `/promote [пользователь]` - Повысить до модератора
|
- `/promote [пользователь]` - Повысить до модератора
|
||||||
- `/demote [пользователь]` - Понизить с модератора
|
- `/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 очко
|
- За каждое сообщение начисляется 1 очко
|
||||||
- За выигрыш в мини-игре начисляются дополнительные очки
|
- За выигрыш в мини-игре начисляются дополнительные очки
|
||||||
@@ -191,18 +244,52 @@ python bot.py
|
|||||||
|
|
||||||
```
|
```
|
||||||
telegram_bot/
|
telegram_bot/
|
||||||
├── bot.py # Основной файл бота
|
├── bot.py # Основной файл бота с системой ошибок и ИИ
|
||||||
├── config.py # Конфигурация (токены, настройки БД)
|
├── config.py # Конфигурация (токены, настройки БД)
|
||||||
├── config_local.py # Локальная конфигурация с токенами
|
├── config_local.py # Локальная конфигурация с токенами и ключами API
|
||||||
├── database_sqlite.py # Работа с SQLite базой данных
|
├── config_template.py # Шаблон для настройки API ключей
|
||||||
├── database.py # Базовые функции работы с БД
|
├── database_sqlite.py # Работа с SQLite базой данных (+ таблица errors)
|
||||||
├── scheduler.py # Планировщик постов по расписанию
|
├── database.py # Базовые функции работы с БД
|
||||||
├── migrate_ranks.py # Миграция системы рангов
|
├── scheduler.py # Планировщик постов по расписанию
|
||||||
├── messages.py # Текстовые сообщения и константы бота
|
├── migrate_ranks.py # Миграция системы рангов
|
||||||
├── requirements.txt # Зависимости Python
|
├── messages.py # Текстовые сообщения и константы бота
|
||||||
└── README.md # Эта документация
|
├── 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. Запуск планировщика постов
|
### 5. Запуск планировщика постов
|
||||||
Для работы постов по расписанию запустите планировщик в отдельном терминале:
|
Для работы постов по расписанию запустите планировщик в отдельном терминале:
|
||||||
```bash
|
```bash
|
||||||
@@ -218,7 +305,8 @@ python scheduler.py
|
|||||||
- **sqlite3** - встроенная поддержка SQLite базы данных
|
- **sqlite3** - встроенная поддержка SQLite базы данных
|
||||||
- **requests** - HTTP запросы для API
|
- **requests** - HTTP запросы для API
|
||||||
- **asyncio** - асинхронное программирование
|
- **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] ✅ Улучшить обработку исключений в базе данных
|
- [x] ✅ Улучшить обработку исключений в базе данных
|
||||||
|
|
||||||
### ✨ Новые функции
|
### ✨ Новые функции
|
||||||
|
- [ ] Добавить реакцию на сообщения пользователей в чате: если в сообщении есть "+", значит ставим эмодзи рукопожатие
|
||||||
- [ ] Добавить поддержку медиа файлов в планировщик постов (видео, документы)
|
- [ ] Добавить поддержку медиа файлов в планировщик постов (видео, документы)
|
||||||
- [ ] Реализовать многоязычность (i18n) для бота
|
- [ ] Реализовать многоязычность (i18n) для бота
|
||||||
- [ ] Добавить экспорт статистики в CSV/Excel формат
|
- [ ] Добавить экспорт статистики в CSV/Excel формат
|
||||||
@@ -43,7 +44,17 @@
|
|||||||
- [ ] Реализовать CAPTCHA для новых пользователей
|
- [ ] Реализовать CAPTCHA для новых пользователей
|
||||||
- [ ] Добавить аудит лог для действий админов
|
- [ ] Добавить аудит лог для действий админов
|
||||||
|
|
||||||
### 🔧 Техническое
|
- [ ] #2 Test task for TODO (Тип: improvement) - Test analysis for integration check
|
||||||
|
### 🚨 Система ошибок и отчетов
|
||||||
|
- [x] Добавить возможность администраторам отправлять сообщения разработчику об ошибках бота
|
||||||
|
- [x] Создать отдельную таблицу в СУБД для хранения ошибок
|
||||||
|
- [x] Реализовать обработку ошибок Искусственным интеллектом
|
||||||
|
- [x] Добавить систему добавления обработанных ошибок в TODO список для рассмотрения разработчиком
|
||||||
|
|
||||||
|
### 🤝 Интерактивные функции
|
||||||
|
- [x] Добавить реакцию на сообщения пользователей с "+" (эмодзи рукопожатия)
|
||||||
|
|
||||||
|
### Техническое
|
||||||
- [ ] Миграция с SQLite на PostgreSQL для больших нагрузок
|
- [ ] Миграция с SQLite на PostgreSQL для больших нагрузок
|
||||||
- [ ] Добавить Redis для кеширования
|
- [ ] Добавить Redis для кеширования
|
||||||
- [ ] Создать Docker контейнер для развертывания
|
- [ ] Создать Docker контейнер для развертывания
|
||||||
|
|||||||
Binary file not shown.
@@ -9,7 +9,7 @@ from datetime import datetime, timedelta
|
|||||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputTextMessageContent
|
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputTextMessageContent
|
||||||
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters, InlineQueryHandler, ChatMemberHandler
|
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters, InlineQueryHandler, ChatMemberHandler
|
||||||
from database_sqlite import Database
|
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 *
|
from messages import *
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -79,10 +79,18 @@ class TelegramBot:
|
|||||||
self.application.add_handler(CommandHandler("list_posts", self.list_posts))
|
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("delete_post", self.delete_post))
|
||||||
self.application.add_handler(CommandHandler("publish_now", self.publish_now))
|
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.TEXT & ~filters.COMMAND, self.handle_message))
|
||||||
self.application.add_handler(MessageHandler(filters.VOICE, self.handle_voice_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))
|
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)
|
await self.cancel_schedule_post(query, context)
|
||||||
elif query.data.startswith('donate_'):
|
elif query.data.startswith('donate_'):
|
||||||
await self.handle_donation_callback(query, context)
|
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):
|
async def start_rps_game(self, query, context):
|
||||||
@@ -1663,6 +1673,9 @@ ID: {user_info['ID']}
|
|||||||
db.update_score(user.id, bonus_points)
|
db.update_score(user.id, bonus_points)
|
||||||
await update.message.reply_text(USER_MESSAGES['bonus_points'].format(points=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):
|
async def handle_text_edit(self, update, context):
|
||||||
"""Обработка редактирования текста поста"""
|
"""Обработка редактирования текста поста"""
|
||||||
new_text = update.message.text
|
new_text = update.message.text
|
||||||
@@ -2325,6 +2338,205 @@ ID: {user_info['ID']}
|
|||||||
except:
|
except:
|
||||||
pass
|
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):
|
def add_random_tile(self, board):
|
||||||
@@ -2608,6 +2820,721 @@ ID: {user_info['ID']}
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
await query.edit_message_text("❌ Неверная сумма!")
|
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):
|
def run(self):
|
||||||
"""Запуск бота"""
|
"""Запуск бота"""
|
||||||
print("Bot started!")
|
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()
|
self.connection.commit()
|
||||||
print(TECH_MESSAGES['db_tables_created'])
|
print(TECH_MESSAGES['db_tables_created'])
|
||||||
except sqlite3.Error as error:
|
except sqlite3.Error as error:
|
||||||
@@ -206,15 +225,15 @@ class Database:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def update_rank(self, user_id, chat_id=None, first_name=None):
|
def update_rank(self, user_id, chat_id=None, first_name=None):
|
||||||
"""Обновление ранга пользователя на основе репутации"""
|
"""Обновление ранга пользователя на основе очков (score)"""
|
||||||
try:
|
try:
|
||||||
cursor = self.connection.cursor()
|
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()
|
result = cursor.fetchone()
|
||||||
if result:
|
if result:
|
||||||
reputation = result[0]
|
score = result[0]
|
||||||
old_rank = result[1]
|
old_rank = result[1]
|
||||||
new_rank = self.calculate_rank(reputation)
|
new_rank = self.calculate_rank(score)
|
||||||
|
|
||||||
if new_rank != old_rank:
|
if new_rank != old_rank:
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
@@ -693,6 +712,230 @@ class Database:
|
|||||||
|
|
||||||
return achievements_unlocked
|
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):
|
def close(self):
|
||||||
"""Закрытие соединения с базой данных"""
|
"""Закрытие соединения с базой данных"""
|
||||||
if self.connection:
|
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,3 +2,4 @@ python-telegram-bot==20.7
|
|||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
speech_recognition==3.10.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