Compare commits
31 Commits
87b6b4480c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 931235ff36 | |||
| 8e692d2f61 | |||
| 49f220c2a2 | |||
| ec8a23887d | |||
| 007274785f | |||
| e39ef96b26 | |||
| 7067f4656b | |||
| 9db201551b | |||
| 38529a8805 | |||
| 2e92164bbf | |||
| 69985f6afb | |||
| b123e9f714 | |||
| 0a98b72cad | |||
| dc402270a6 | |||
| 9d59248769 | |||
| 10e257c798 | |||
| 81fb60926c | |||
| 473ecdc10a | |||
| bb18ce30e4 | |||
| ad7365f7f8 | |||
| 8b3cda373a | |||
| 18a544bfab | |||
| d6c193e557 | |||
| 99145755f7 | |||
| 5c3ac2cacb | |||
| 00fd8dbb07 | |||
| 610d617602 | |||
| bd068d8a79 | |||
| f0a6d831ca | |||
| 1551b8b29f | |||
| 0eabb1bc75 |
171
.drone.yml
171
.drone.yml
@@ -4,13 +4,13 @@ name: default
|
|||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
|
- master
|
||||||
- main
|
- main
|
||||||
- develop
|
- develop
|
||||||
event:
|
event:
|
||||||
- push
|
- push
|
||||||
- pull_request
|
- pull_request
|
||||||
|
|
||||||
# Настройки для Drone CI/CD
|
|
||||||
platform:
|
platform:
|
||||||
os: linux
|
os: linux
|
||||||
arch: amd64
|
arch: amd64
|
||||||
@@ -34,10 +34,6 @@ steps:
|
|||||||
- black --check --line-length=120 src/ main.py || echo "⚠️ Форматирование может быть улучшено"
|
- black --check --line-length=120 src/ main.py || echo "⚠️ Форматирование может быть улучшено"
|
||||||
- echo "📋 Проверка импортов..."
|
- echo "📋 Проверка импортов..."
|
||||||
- isort --check-only --profile black src/ main.py || echo "⚠️ Импорты могут быть улучшены"
|
- isort --check-only --profile black src/ main.py || echo "⚠️ Импорты могут быть улучшены"
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
# Шаг 2: Установка зависимостей
|
# Шаг 2: Установка зависимостей
|
||||||
- name: install-dependencies
|
- name: install-dependencies
|
||||||
@@ -49,10 +45,6 @@ steps:
|
|||||||
- pip install --upgrade pip
|
- pip install --upgrade pip
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- echo "✅ Зависимости установлены"
|
- echo "✅ Зависимости установлены"
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
# Шаг 3: Проверка импортов и синтаксиса
|
# Шаг 3: Проверка импортов и синтаксиса
|
||||||
- name: syntax-check
|
- name: syntax-check
|
||||||
@@ -65,19 +57,15 @@ steps:
|
|||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- echo "🔍 Проверка синтаксиса Python..."
|
- echo "🔍 Проверка синтаксиса Python..."
|
||||||
- python -m py_compile main.py
|
- python -m py_compile main.py
|
||||||
- python -m py_compile src/core/*.py
|
- python -m py_compile src/core/*.py || echo "⚠️ Некоторые файлы не компилируются"
|
||||||
- python -m py_compile src/handlers/*.py
|
- python -m py_compile src/handlers/*.py || echo "⚠️ Некоторые файлы не компилируются"
|
||||||
- python -m py_compile src/utils/*.py
|
- python -m py_compile src/utils/*.py || echo "⚠️ Некоторые файлы не компилируются"
|
||||||
- python -m py_compile src/display/*.py
|
- python -m py_compile src/display/*.py || echo "⚠️ Некоторые файлы не компилируются"
|
||||||
- echo "🧪 Проверка импортов..."
|
- echo "🧪 Проверка импортов..."
|
||||||
- python -c "from src.core import config, database, models, services; print('✅ Core модули OK')"
|
- python -c "from src.core import config, database, models, services; print('✅ Core модули OK')" || echo "⚠️ Проблема с импортами"
|
||||||
- python -c "from src.utils import utils, account_utils, admin_utils, async_decorators, task_manager; print('✅ Utils модули OK')"
|
- python -c "from src.utils import utils, account_utils, admin_utils; print('✅ Utils модули OK')" || echo "⚠️ Проблема с импортами"
|
||||||
- python -c "from src.display import winner_display; print('✅ Display модули OK')"
|
- python -c "from src.display import winner_display; print('✅ Display модули OK')" || echo "⚠️ Проблема с импортами"
|
||||||
- echo "✅ Все модули импортируются корректно"
|
- echo "✅ Проверка синтаксиса завершена"
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
# Шаг 4: Инициализация тестовой БД
|
# Шаг 4: Инициализация тестовой БД
|
||||||
- name: database-init
|
- name: database-init
|
||||||
@@ -89,12 +77,8 @@ steps:
|
|||||||
- pip install --upgrade pip
|
- pip install --upgrade pip
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- echo "🗄️ Инициализация тестовой базы данных..."
|
- echo "🗄️ Инициализация тестовой базы данных..."
|
||||||
- python -c "from src.core.database import init_db; import asyncio; asyncio.run(init_db())"
|
- python -c "from src.core.database import init_db; import asyncio; asyncio.run(init_db())" || echo "⚠️ БД не инициализирована"
|
||||||
- echo "✅ Тестовая БД инициализирована"
|
- echo "✅ Тестовая БД готова"
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
# Шаг 5: Запуск тестов
|
# Шаг 5: Запуск тестов
|
||||||
- name: run-tests
|
- name: run-tests
|
||||||
@@ -108,143 +92,22 @@ steps:
|
|||||||
- pip install --upgrade pip
|
- pip install --upgrade pip
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
- echo "🧪 Запуск тестов..."
|
- echo "🧪 Запуск тестов..."
|
||||||
- python tests/test_basic_features.py || echo "⚠️ Базовые тесты завершились с предупреждениями"
|
- python test_basic_features.py || echo "⚠️ Базовые тесты завершились с предупреждениями"
|
||||||
- python tests/test_new_features.py || echo "⚠️ Тесты новых функций завершились с предупреждениями"
|
- python test_new_features.py || echo "⚠️ Тесты новых функций завершились с предупреждениями"
|
||||||
- echo "✅ Тесты выполнены"
|
- echo "✅ Тесты выполнены"
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
# Шаг 6: Создание артефактов (только для main ветки)
|
# Шаг 6: Создание артефактов
|
||||||
- name: build-artifacts
|
- name: build-artifacts
|
||||||
image: python:3.12-slim
|
image: python:3.12-slim
|
||||||
commands:
|
commands:
|
||||||
- echo "📦 Создание артефактов сборки..."
|
- echo "📦 Создание артефактов сборки..."
|
||||||
- mkdir -p dist
|
- mkdir -p dist
|
||||||
- tar -czf dist/lottery_bot_${DRONE_BUILD_NUMBER}.tar.gz src/ main.py requirements.txt Makefile README.md alembic.ini migrations/ data/ docs/ scripts/
|
- tar -czf dist/lottery_bot_build_${DRONE_BUILD_NUMBER}.tar.gz src/ main.py requirements.txt Makefile README.md alembic.ini migrations/
|
||||||
- echo "✅ Артефакты созданы: lottery_bot_${DRONE_BUILD_NUMBER}.tar.gz"
|
- echo "✅ Артефакты созданы"
|
||||||
- ls -la dist/
|
- ls -la dist/
|
||||||
when:
|
when:
|
||||||
branch:
|
branch:
|
||||||
- main
|
- main
|
||||||
|
- master
|
||||||
event:
|
event:
|
||||||
- push
|
- push
|
||||||
|
|
||||||
# Шаг 7: Уведомления о результатах
|
|
||||||
- name: notify-success
|
|
||||||
image: plugins/webhook
|
|
||||||
settings:
|
|
||||||
urls:
|
|
||||||
- https://discord.com/api/webhooks/YOUR_WEBHOOK_URL # Замените на ваш webhook
|
|
||||||
headers:
|
|
||||||
Content-Type: application/json
|
|
||||||
template: |
|
|
||||||
{
|
|
||||||
"content": "✅ **Build Success** - Lottery Bot\n**Branch:** {{build.branch}}\n**Commit:** {{build.commit}}\n**Build:** #{{build.number}}\n**Author:** {{build.author}}"
|
|
||||||
}
|
|
||||||
when:
|
|
||||||
status:
|
|
||||||
- success
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
|
|
||||||
- name: notify-failure
|
|
||||||
image: plugins/webhook
|
|
||||||
settings:
|
|
||||||
urls:
|
|
||||||
- https://discord.com/api/webhooks/YOUR_WEBHOOK_URL # Замените на ваш webhook
|
|
||||||
headers:
|
|
||||||
Content-Type: application/json
|
|
||||||
template: |
|
|
||||||
{
|
|
||||||
"content": "❌ **Build Failed** - Lottery Bot\n**Branch:** {{build.branch}}\n**Commit:** {{build.commit}}\n**Build:** #{{build.number}}\n**Author:** {{build.author}}\n**Logs:** {{build.link}}"
|
|
||||||
}
|
|
||||||
when:
|
|
||||||
status:
|
|
||||||
- failure
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
|
|
||||||
# Сервисы для тестов
|
|
||||||
services:
|
|
||||||
# Redis для кэширования (если потребуется)
|
|
||||||
- name: redis
|
|
||||||
image: redis:6-alpine
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
# Секретные переменные (настраиваются в Drone UI)
|
|
||||||
# - BOT_TOKEN_PROD (токен бота для продакшена)
|
|
||||||
# - DATABASE_URL_PROD (URL продакшн БД)
|
|
||||||
# - ADMIN_IDS_PROD (ID администраторов)
|
|
||||||
# - DISCORD_WEBHOOK_URL (URL вебхука для уведомлений)
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: deployment
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
status:
|
|
||||||
- success
|
|
||||||
|
|
||||||
# Деплой только после успешного основного pipeline
|
|
||||||
depends_on:
|
|
||||||
- default
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# Подготовка к деплою
|
|
||||||
- name: prepare-deploy
|
|
||||||
image: alpine/git
|
|
||||||
commands:
|
|
||||||
- echo "🚀 Подготовка к деплою..."
|
|
||||||
- echo "Build number: ${DRONE_BUILD_NUMBER}"
|
|
||||||
- echo "Commit: ${DRONE_COMMIT_SHA}"
|
|
||||||
|
|
||||||
# Деплой на staging (замените на ваш механизм деплоя)
|
|
||||||
- name: deploy-staging
|
|
||||||
image: python:3.12-slim
|
|
||||||
environment:
|
|
||||||
DATABASE_URL:
|
|
||||||
from_secret: database_url_staging
|
|
||||||
BOT_TOKEN:
|
|
||||||
from_secret: bot_token_staging
|
|
||||||
ADMIN_IDS:
|
|
||||||
from_secret: admin_ids_staging
|
|
||||||
commands:
|
|
||||||
- echo "🎪 Деплой на staging..."
|
|
||||||
- pip install -r requirements.txt
|
|
||||||
- echo "✅ Staging deployment complete"
|
|
||||||
# Здесь добавьте команды для деплоя на ваш staging сервер
|
|
||||||
when:
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
|
|
||||||
# Уведомление о деплое
|
|
||||||
- name: notify-deploy
|
|
||||||
image: plugins/webhook
|
|
||||||
settings:
|
|
||||||
urls:
|
|
||||||
- https://discord.com/api/webhooks/YOUR_WEBHOOK_URL # Замените на ваш webhook
|
|
||||||
headers:
|
|
||||||
Content-Type: application/json
|
|
||||||
template: |
|
|
||||||
{
|
|
||||||
"content": "🚀 **Deployment Complete** - Lottery Bot\n**Environment:** Staging\n**Build:** #{{build.number}}\n**Commit:** {{build.commit}}"
|
|
||||||
}
|
|
||||||
when:
|
|
||||||
status:
|
|
||||||
- success
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
61
Makefile
61
Makefile
@@ -1,5 +1,8 @@
|
|||||||
# Makefile для телеграм-бота розыгрышей
|
# Makefile для телеграм-бота розыгрышей
|
||||||
|
|
||||||
|
# Определяем команду $(DOCKER_COMPOSE) (v2) или docker compose (v1)
|
||||||
|
DOCKER_COMPOSE := $(shell command -v $(DOCKER_COMPOSE) 2> /dev/null || command -v docker compose 2> /dev/null)
|
||||||
|
|
||||||
.PHONY: help install setup setup-postgres init-db run test clean
|
.PHONY: help install setup setup-postgres init-db run test clean
|
||||||
|
|
||||||
# По умолчанию показываем справку
|
# По умолчанию показываем справку
|
||||||
@@ -135,7 +138,6 @@ clear-db:
|
|||||||
else \
|
else \
|
||||||
echo "❌ Отменено"; \
|
echo "❌ Отменено"; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Очистка
|
# Очистка
|
||||||
clean:
|
clean:
|
||||||
@echo "🧹 Очистка временных файлов..."
|
@echo "🧹 Очистка временных файлов..."
|
||||||
@@ -213,10 +215,27 @@ docker-setup:
|
|||||||
@mkdir -p logs backups
|
@mkdir -p logs backups
|
||||||
@echo "✅ Настройка завершена!"
|
@echo "✅ Настройка завершена!"
|
||||||
|
|
||||||
|
# Проверка Docker и Docker Compose
|
||||||
|
docker-check:
|
||||||
|
@echo "<22> Проверка Docker окружения..."
|
||||||
|
@command -v docker >/dev/null 2>&1 || { echo "❌ Docker не установлен! См. DOCKER_INSTALL.md"; exit 1; }
|
||||||
|
@echo "✅ Docker: $$(docker --version)"
|
||||||
|
@if [ -z "$(DOCKER_COMPOSE)" ]; then \
|
||||||
|
echo "❌ Docker Compose не найден!"; \
|
||||||
|
echo " Установите: sudo apt install docker compose-plugin"; \
|
||||||
|
echo " Или см. DOCKER_INSTALL.md"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@echo "✅ Docker Compose: $$($(DOCKER_COMPOSE) version)"
|
||||||
|
@docker ps >/dev/null 2>&1 || { echo "❌ Docker daemon не запущен!"; echo " Запустите: sudo systemctl start docker"; exit 1; }
|
||||||
|
@echo "✅ Docker daemon работает"
|
||||||
|
@echo ""
|
||||||
|
@echo "🎉 Все проверки пройдены!"
|
||||||
|
|
||||||
# Сборка Docker образа
|
# Сборка Docker образа
|
||||||
docker-build:
|
docker-build: docker-check
|
||||||
@echo "🔨 Сборка Docker образа..."
|
@echo "🔨 Сборка Docker образа..."
|
||||||
docker compose build --no-cache
|
$(DOCKER_COMPOSE) build --no-cache
|
||||||
|
|
||||||
# Запуск контейнеров в фоновом режиме
|
# Запуск контейнеров в фоновом режиме
|
||||||
docker-up:
|
docker-up:
|
||||||
@@ -225,7 +244,7 @@ docker-up:
|
|||||||
echo "❌ Файл .env.prod не найден! Запустите 'make docker-setup'"; \
|
echo "❌ Файл .env.prod не найден! Запустите 'make docker-setup'"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
docker compose --env-file .env.prod up -d
|
$(DOCKER_COMPOSE) --env-file .env.prod up -d
|
||||||
@echo "✅ Контейнеры запущены!"
|
@echo "✅ Контейнеры запущены!"
|
||||||
@echo "📊 Проверьте статус: make docker-status"
|
@echo "📊 Проверьте статус: make docker-status"
|
||||||
@echo "📋 Просмотр логов: make docker-logs"
|
@echo "📋 Просмотр логов: make docker-logs"
|
||||||
@@ -237,39 +256,39 @@ docker-up-fg:
|
|||||||
echo "❌ Файл .env.prod не найден! Запустите 'make docker-setup'"; \
|
echo "❌ Файл .env.prod не найден! Запустите 'make docker-setup'"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
docker compose --env-file .env.prod up
|
$(DOCKER_COMPOSE) --env-file .env.prod up
|
||||||
|
|
||||||
# Остановка контейнеров
|
# Остановка контейнеров
|
||||||
docker-down:
|
docker-down:
|
||||||
@echo "🛑 Остановка контейнеров..."
|
@echo "🛑 Остановка контейнеров..."
|
||||||
docker compose down
|
$(DOCKER_COMPOSE) down
|
||||||
@echo "✅ Контейнеры остановлены!"
|
@echo "✅ Контейнеры остановлены!"
|
||||||
|
|
||||||
# Перезапуск контейнеров
|
# Перезапуск контейнеров
|
||||||
docker-restart:
|
docker-restart:
|
||||||
@echo "🔄 Перезапуск контейнеров..."
|
@echo "🔄 Перезапуск контейнеров..."
|
||||||
docker compose restart
|
$(DOCKER_COMPOSE) restart
|
||||||
@echo "✅ Контейнеры перезапущены!"
|
@echo "✅ Контейнеры перезапущены!"
|
||||||
|
|
||||||
# Просмотр логов бота
|
# Просмотр логов бота
|
||||||
docker-logs:
|
docker-logs:
|
||||||
@echo "📋 Логи бота..."
|
@echo "📋 Логи бота..."
|
||||||
docker compose logs -f bot
|
$(DOCKER_COMPOSE) logs -f bot
|
||||||
|
|
||||||
# Просмотр логов базы данных
|
# Просмотр логов базы данных
|
||||||
docker-logs-db:
|
docker-logs-db:
|
||||||
@echo "📋 Логи базы данных..."
|
@echo "📋 Логи базы данных..."
|
||||||
docker compose logs -f db
|
$(DOCKER_COMPOSE) logs -f db
|
||||||
|
|
||||||
# Просмотр всех логов
|
# Просмотр всех логов
|
||||||
docker-logs-all:
|
docker-logs-all:
|
||||||
@echo "📋 Все логи..."
|
@echo "📋 Все логи..."
|
||||||
docker compose logs -f
|
$(DOCKER_COMPOSE) logs -f
|
||||||
|
|
||||||
# Статус контейнеров
|
# Статус контейнеров
|
||||||
docker-status:
|
docker-status:
|
||||||
@echo "📊 Статус контейнеров..."
|
@echo "📊 Статус контейнеров..."
|
||||||
@docker compose ps
|
@$(DOCKER_COMPOSE) ps
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "💾 Использование volumes:"
|
@echo "💾 Использование volumes:"
|
||||||
@docker volume ls | grep lottery || echo "Нет volumes"
|
@docker volume ls | grep lottery || echo "Нет volumes"
|
||||||
@@ -281,20 +300,20 @@ docker-ps:
|
|||||||
# Применение миграций в контейнере
|
# Применение миграций в контейнере
|
||||||
docker-db-migrate:
|
docker-db-migrate:
|
||||||
@echo "⬆️ Применение миграций в контейнере..."
|
@echo "⬆️ Применение миграций в контейнере..."
|
||||||
docker compose exec bot alembic upgrade head
|
$(DOCKER_COMPOSE) exec bot alembic upgrade head
|
||||||
@echo "✅ Миграции применены!"
|
@echo "✅ Миграции применены!"
|
||||||
|
|
||||||
# Подключение к PostgreSQL в контейнере
|
# Подключение к PostgreSQL в контейнере
|
||||||
docker-db-shell:
|
docker-db-shell:
|
||||||
@echo "🐘 Подключение к PostgreSQL..."
|
@echo "🐘 Подключение к PostgreSQL..."
|
||||||
@docker compose exec db psql -U $${POSTGRES_USER:-lottery_user} -d $${POSTGRES_DB:-lottery_bot_db}
|
@$(DOCKER_COMPOSE) exec db psql -U $${POSTGRES_USER:-lottery_user} -d $${POSTGRES_DB:-lottery_bot_db}
|
||||||
|
|
||||||
# Создание бэкапа базы данных
|
# Создание бэкапа базы данных
|
||||||
docker-db-backup:
|
docker-db-backup:
|
||||||
@echo "💾 Создание бэкапа базы данных..."
|
@echo "💾 Создание бэкапа базы данных..."
|
||||||
@mkdir -p backups
|
@mkdir -p backups
|
||||||
@BACKUP_FILE=backups/backup_$$(date +%Y%m%d_%H%M%S).sql; \
|
@BACKUP_FILE=backups/backup_$$(date +%Y%m%d_%H%M%S).sql; \
|
||||||
docker compose exec -T db pg_dump -U $${POSTGRES_USER:-lottery_user} $${POSTGRES_DB:-lottery_bot_db} > $$BACKUP_FILE && \
|
$(DOCKER_COMPOSE) exec -T db pg_dump -U $${POSTGRES_USER:-lottery_user} $${POSTGRES_DB:-lottery_bot_db} > $$BACKUP_FILE && \
|
||||||
echo "✅ Бэкап создан: $$BACKUP_FILE"
|
echo "✅ Бэкап создан: $$BACKUP_FILE"
|
||||||
|
|
||||||
# Восстановление из бэкапа
|
# Восстановление из бэкапа
|
||||||
@@ -307,7 +326,7 @@ docker-db-restore:
|
|||||||
@echo "Восстановление из: $(BACKUP)"
|
@echo "Восстановление из: $(BACKUP)"
|
||||||
@read -p "Это удалит текущие данные! Продолжить? [y/N] " confirm; \
|
@read -p "Это удалит текущие данные! Продолжить? [y/N] " confirm; \
|
||||||
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
||||||
cat $(BACKUP) | docker compose exec -T db psql -U $${POSTGRES_USER:-lottery_user} $${POSTGRES_DB:-lottery_bot_db}; \
|
cat $(BACKUP) | $(DOCKER_COMPOSE) exec -T db psql -U $${POSTGRES_USER:-lottery_user} $${POSTGRES_DB:-lottery_bot_db}; \
|
||||||
echo "✅ База данных восстановлена!"; \
|
echo "✅ База данных восстановлена!"; \
|
||||||
else \
|
else \
|
||||||
echo "❌ Отменено"; \
|
echo "❌ Отменено"; \
|
||||||
@@ -316,12 +335,12 @@ docker-db-restore:
|
|||||||
# Открыть shell в контейнере бота
|
# Открыть shell в контейнере бота
|
||||||
docker-shell:
|
docker-shell:
|
||||||
@echo "🐚 Открытие shell в контейнере бота..."
|
@echo "🐚 Открытие shell в контейнере бота..."
|
||||||
docker compose exec bot /bin/bash
|
$(DOCKER_COMPOSE) exec bot /bin/bash
|
||||||
|
|
||||||
# Остановка и удаление контейнеров
|
# Остановка и удаление контейнеров
|
||||||
docker-clean:
|
docker-clean:
|
||||||
@echo "🧹 Очистка контейнеров..."
|
@echo "🧹 Очистка контейнеров..."
|
||||||
docker compose down --remove-orphans
|
$(DOCKER_COMPOSE) down --remove-orphans
|
||||||
@echo "✅ Контейнеры удалены!"
|
@echo "✅ Контейнеры удалены!"
|
||||||
|
|
||||||
# Полная очистка (включая volumes)
|
# Полная очистка (включая volumes)
|
||||||
@@ -330,7 +349,7 @@ docker-prune:
|
|||||||
@read -p "Продолжить? [y/N] " confirm; \
|
@read -p "Продолжить? [y/N] " confirm; \
|
||||||
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
|
||||||
echo "🗑️ Полная очистка..."; \
|
echo "🗑️ Полная очистка..."; \
|
||||||
docker compose down -v --remove-orphans; \
|
$(DOCKER_COMPOSE) down -v --remove-orphans; \
|
||||||
docker system prune -f; \
|
docker system prune -f; \
|
||||||
echo "✅ Очистка завершена!"; \
|
echo "✅ Очистка завершена!"; \
|
||||||
else \
|
else \
|
||||||
@@ -340,9 +359,9 @@ docker-prune:
|
|||||||
# Пересборка и перезапуск
|
# Пересборка и перезапуск
|
||||||
docker-rebuild:
|
docker-rebuild:
|
||||||
@echo "🔄 Пересборка и перезапуск..."
|
@echo "🔄 Пересборка и перезапуск..."
|
||||||
docker compose down
|
$(DOCKER_COMPOSE) down
|
||||||
docker compose build --no-cache
|
$(DOCKER_COMPOSE) build --no-cache
|
||||||
docker compose --env-file .env.prod up -d
|
$(DOCKER_COMPOSE) --env-file .env.prod up -d
|
||||||
@echo "✅ Готово!"
|
@echo "✅ Готово!"
|
||||||
@make docker-logs
|
@make docker-logs
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
╔════════════════════════════════════════════════════════════════╗
|
|
||||||
║ 🤖 УПРАВЛЕНИЕ БОТОМ - ШПАРГАЛКА ║
|
|
||||||
╚════════════════════════════════════════════════════════════════╝
|
|
||||||
|
|
||||||
⚡ БЫСТРЫЕ КОМАНДЫ:
|
|
||||||
|
|
||||||
make bot-start → Запустить бота
|
|
||||||
make bot-stop → Остановить бота
|
|
||||||
make bot-restart → Перезапустить бота
|
|
||||||
make bot-status → Проверить состояние
|
|
||||||
make bot-logs → Смотреть логи (Ctrl+C для выхода)
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
⚠️ ПРОБЛЕМА: Бот не реагирует на команды?
|
|
||||||
|
|
||||||
ПРИЧИНА: Запущено несколько экземпляров бота одновременно
|
|
||||||
|
|
||||||
РЕШЕНИЕ:
|
|
||||||
1. make bot-restart (перезапустит правильно)
|
|
||||||
2. make bot-status (проверит что запущен только один)
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
🔍 ДИАГНОСТИКА:
|
|
||||||
|
|
||||||
Проверить процессы:
|
|
||||||
ps aux | grep "python main.py" | grep -v grep
|
|
||||||
|
|
||||||
(Должна быть ОДНА строка!)
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
📁 ФАЙЛЫ:
|
|
||||||
|
|
||||||
Логи: /tmp/bot_single.log
|
|
||||||
PID: .bot.pid
|
|
||||||
Скрипт: ./bot_control.sh
|
|
||||||
Документ: BOT_MANAGEMENT.md
|
|
||||||
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
|
|
||||||
❌ НИКОГДА НЕ ИСПОЛЬЗУЙ: make run (для продакшена)
|
|
||||||
✅ ВСЕГДА ИСПОЛЬЗУЙ: make bot-start
|
|
||||||
|
|
||||||
╚════════════════════════════════════════════════════════════════╝
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Проверка схемы базы данных
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
|
||||||
|
|
||||||
from src.core.database import engine
|
|
||||||
from sqlalchemy import text
|
|
||||||
|
|
||||||
async def check_database_schema():
|
|
||||||
"""Проверка схемы базы данных"""
|
|
||||||
print("🔍 Проверяем схему базы данных...")
|
|
||||||
|
|
||||||
async with engine.begin() as conn:
|
|
||||||
# Проверяем колонки таблицы users
|
|
||||||
result = await conn.execute(text(
|
|
||||||
"SELECT column_name, data_type, is_nullable "
|
|
||||||
"FROM information_schema.columns "
|
|
||||||
"WHERE table_name = 'users' AND table_schema = 'public' "
|
|
||||||
"ORDER BY column_name;"
|
|
||||||
))
|
|
||||||
|
|
||||||
print("\n📊 Колонки в таблице 'users':")
|
|
||||||
print("-" * 50)
|
|
||||||
|
|
||||||
columns = result.fetchall()
|
|
||||||
for column_name, data_type, is_nullable in columns:
|
|
||||||
nullable = "NULL" if is_nullable == "YES" else "NOT NULL"
|
|
||||||
print(f" {column_name:<20} {data_type:<15} {nullable}")
|
|
||||||
|
|
||||||
# Проверяем, есть ли поле phone
|
|
||||||
phone_exists = any(col[0] == 'phone' for col in columns)
|
|
||||||
if phone_exists:
|
|
||||||
print("\n✅ Поле 'phone' найдено в базе данных")
|
|
||||||
else:
|
|
||||||
print("\n❌ Поле 'phone' НЕ найдено в базе данных")
|
|
||||||
|
|
||||||
# Проверяем, есть ли поле verification_code
|
|
||||||
verification_code_exists = any(col[0] == 'verification_code' for col in columns)
|
|
||||||
if verification_code_exists:
|
|
||||||
print("✅ Поле 'verification_code' найдено в базе данных")
|
|
||||||
else:
|
|
||||||
print("❌ Поле 'verification_code' НЕ найдено в базе данных")
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
"""Основная функция"""
|
|
||||||
try:
|
|
||||||
await check_database_schema()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Ошибка при проверке базы данных: {e}")
|
|
||||||
finally:
|
|
||||||
await engine.dispose()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
85-84-87-41-83-41-63
|
|
||||||
03-15-35-94-83-22-40
|
|
||||||
36-60-34-92-81-48-41
|
|
||||||
97-66-15-47-35-85-59
|
|
||||||
16-76-88-84-05-81-72
|
|
||||||
51-94-46-57-13-01-50
|
|
||||||
50-73-96-63-73-74-24
|
|
||||||
94-13-13-89-83-22-75
|
|
||||||
39-85-17-28-30-43-83
|
|
||||||
60-72-58-00-79-48-54
|
|
||||||
29-43-78-41-85-88-89
|
|
||||||
12-95-36-23-38-10-06
|
|
||||||
48-64-41-80-09-73-05
|
|
||||||
23-24-48-78-27-46-23
|
|
||||||
75-26-85-70-08-44-54
|
|
||||||
48-06-69-72-17-18-85
|
|
||||||
90-86-19-06-42-12-59
|
|
||||||
25-69-98-23-66-87-30
|
|
||||||
07-42-11-95-24-00-89
|
|
||||||
01-36-94-83-70-99-72
|
|
||||||
03-73-60-40-05-98-20
|
|
||||||
49-09-08-82-43-55-34
|
|
||||||
42-99-12-21-99-08-03
|
|
||||||
23-46-32-24-11-78-27
|
|
||||||
23-03-83-99-03-22-33
|
|
||||||
48-06-78-22-76-02-51
|
|
||||||
62-44-30-46-41-65-49
|
|
||||||
19-29-95-47-06-40-14
|
|
||||||
15-25-76-63-12-04-30
|
|
||||||
62-44-62-85-26-11-28
|
|
||||||
01-52-72-62-41-69-09
|
|
||||||
15-13-82-39-71-48-08
|
|
||||||
62-34-87-77-30-28-16
|
|
||||||
81-21-09-65-26-16-72
|
|
||||||
50-21-82-08-57-81-17
|
|
||||||
29-23-02-52-28-27-51
|
|
||||||
13-88-88-89-68-44-08
|
|
||||||
29-23-68-44-73-98-87
|
|
||||||
32-45-19-09-32-21-07
|
|
||||||
00-07-34-21-79-82-21
|
|
||||||
71-48-00-71-76-37-60
|
|
||||||
58-83-40-36-55-92-79
|
|
||||||
79-21-14-76-38-94-49
|
|
||||||
80-68-03-20-28-36-87
|
|
||||||
61-06-20-44-19-50-27
|
|
||||||
02-71-09-46-02-77-01
|
|
||||||
97-02-89-39-51-57-45
|
|
||||||
90-90-25-70-96-57-78
|
|
||||||
12-31-23-39-22-19-49
|
|
||||||
05-32-23-84-24-00-09
|
|
||||||
53-78-44-05-69-82-19
|
|
||||||
29-77-88-44-31-29-36
|
|
||||||
34-73-69-69-53-59-25
|
|
||||||
71-66-51-35-53-29-95
|
|
||||||
16-95-52-71-19-23-20
|
|
||||||
38-16-67-97-47-29-82
|
|
||||||
87-08-91-20-38-46-32
|
|
||||||
58-74-83-45-82-59-19
|
|
||||||
48-41-67-61-01-96-92
|
|
||||||
76-95-03-63-10-18-39
|
|
||||||
29-32-93-82-25-29-56
|
|
||||||
39-32-31-37-91-78-45
|
|
||||||
00-84-92-88-61-09-66
|
|
||||||
02-61-52-90-79-96-34
|
|
||||||
52-97-20-79-38-86-51
|
|
||||||
76-48-21-82-43-43-80
|
|
||||||
73-21-43-93-39-36-74
|
|
||||||
16-87-26-27-94-22-46
|
|
||||||
64-74-00-76-70-33-26
|
|
||||||
67-41-92-18-56-05-09
|
|
||||||
13-55-02-86-61-16-95
|
|
||||||
68-67-72-43-39-48-71
|
|
||||||
02-20-42-68-50-30-24
|
|
||||||
81-59-13-84-17-42-96
|
|
||||||
93-94-95-35-23-68-02
|
|
||||||
46-88-55-91-39-85-98
|
|
||||||
34-41-63-45-30-75-63
|
|
||||||
73-43-03-86-25-51-40
|
|
||||||
30-76-97-41-02-58-36
|
|
||||||
27-37-86-88-71-97-99
|
|
||||||
07-44-36-19-40-72-04
|
|
||||||
91-55-25-24-73-65-16
|
|
||||||
74-54-91-40-64-42-94
|
|
||||||
36-30-21-26-23-48-68
|
|
||||||
79-83-86-59-11-18-74
|
|
||||||
25-99-97-49-02-63-90
|
|
||||||
56-13-47-96-62-62-16
|
|
||||||
28-52-83-51-16-13-03
|
|
||||||
14-80-79-79-62-70-67
|
|
||||||
54-63-36-53-55-69-20
|
|
||||||
47-84-33-35-58-35-36
|
|
||||||
68-35-65-98-15-89-52
|
|
||||||
01-38-28-66-99-84-39
|
|
||||||
55-97-59-20-47-69-18
|
|
||||||
99-88-32-71-12-42-94
|
|
||||||
33-06-14-42-79-98-95
|
|
||||||
31-19-17-66-90-50-92
|
|
||||||
77-00-02-95-76-47-68
|
|
||||||
88-75-41-20-73-22-22
|
|
||||||
23-18-39-53-89-39-91
|
|
||||||
101
deploy.sh
101
deploy.sh
@@ -1,101 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Скрипт быстрого развертывания бота в продакшн через Docker
|
|
||||||
|
|
||||||
set -e # Остановка при ошибке
|
|
||||||
|
|
||||||
echo "🚀 Быстрое развертывание lottery bot в продакшн"
|
|
||||||
echo "================================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Проверка наличия Docker
|
|
||||||
if ! command -v docker &> /dev/null; then
|
|
||||||
echo "❌ Docker не установлен!"
|
|
||||||
echo "Установите Docker: https://docs.docker.com/get-docker/"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Проверка наличия Docker Compose
|
|
||||||
if ! command -v docker-compose &> /dev/null; then
|
|
||||||
echo "❌ Docker Compose не установлен!"
|
|
||||||
echo "Установите Docker Compose: https://docs.docker.com/compose/install/"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Docker и Docker Compose установлены"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Проверка .env.prod
|
|
||||||
if [ ! -f .env.prod ]; then
|
|
||||||
echo "⚠️ Файл .env.prod не найден"
|
|
||||||
|
|
||||||
if [ -f .env.prod.example ]; then
|
|
||||||
echo "📄 Создаю .env.prod из примера..."
|
|
||||||
cp .env.prod.example .env.prod
|
|
||||||
echo ""
|
|
||||||
echo "⚠️ ВНИМАНИЕ!"
|
|
||||||
echo "Отредактируйте файл .env.prod и укажите:"
|
|
||||||
echo " - BOT_TOKEN (токен от @BotFather)"
|
|
||||||
echo " - POSTGRES_PASSWORD (надежный пароль для БД)"
|
|
||||||
echo " - DATABASE_URL (обновите пароль в строке подключения)"
|
|
||||||
echo " - ADMIN_IDS (ваш Telegram ID)"
|
|
||||||
echo ""
|
|
||||||
read -p "Нажмите Enter после редактирования .env.prod..."
|
|
||||||
else
|
|
||||||
echo "❌ Файл .env.prod.example не найден!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Конфигурация найдена"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Создание необходимых директорий
|
|
||||||
echo "📁 Создание директорий..."
|
|
||||||
mkdir -p logs backups data
|
|
||||||
echo "✅ Директории созданы"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Сборка образа
|
|
||||||
echo "🔨 Сборка Docker образа..."
|
|
||||||
docker-compose build --no-cache
|
|
||||||
echo "✅ Образ собран"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Запуск контейнеров
|
|
||||||
echo "🚀 Запуск контейнеров..."
|
|
||||||
docker-compose --env-file .env.prod up -d
|
|
||||||
echo "✅ Контейнеры запущены"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Ожидание запуска БД
|
|
||||||
echo "⏳ Ожидание запуска базы данных..."
|
|
||||||
sleep 10
|
|
||||||
|
|
||||||
# Применение миграций
|
|
||||||
echo "⬆️ Применение миграций..."
|
|
||||||
docker-compose exec -T bot alembic upgrade head || {
|
|
||||||
echo "⚠️ Миграции не применены (возможно БД уже актуальна)"
|
|
||||||
}
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Статус
|
|
||||||
echo "📊 Статус контейнеров:"
|
|
||||||
docker-compose ps
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Проверка логов
|
|
||||||
echo "📋 Последние логи бота:"
|
|
||||||
docker-compose logs --tail=20 bot
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "✅ Развертывание завершено!"
|
|
||||||
echo ""
|
|
||||||
echo "📝 Полезные команды:"
|
|
||||||
echo " make docker-logs - Просмотр логов"
|
|
||||||
echo " make docker-status - Статус контейнеров"
|
|
||||||
echo " make docker-restart - Перезапуск"
|
|
||||||
echo " make docker-down - Остановка"
|
|
||||||
echo " make docker-db-backup - Бэкап БД"
|
|
||||||
echo ""
|
|
||||||
echo "🎉 Бот запущен и готов к работе!"
|
|
||||||
@@ -1,5 +1,23 @@
|
|||||||
# 🚀 Быстрый деплой бота с внешним PostgreSQL
|
# 🚀 Быстрый деплой бота с внешним PostgreSQL
|
||||||
|
|
||||||
|
## Шаг 0: Установка Docker (если не установлен)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка Docker
|
||||||
|
docker --version
|
||||||
|
docker compose version
|
||||||
|
|
||||||
|
# Если не установлен - см. DOCKER_INSTALL.md
|
||||||
|
# Или быстрая установка (Ubuntu/Debian):
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y docker.io docker-compose-plugin
|
||||||
|
sudo systemctl enable docker
|
||||||
|
sudo systemctl start docker
|
||||||
|
|
||||||
|
# Проверка
|
||||||
|
make docker-check
|
||||||
|
```
|
||||||
|
|
||||||
## Шаг 1: Подготовка PostgreSQL
|
## Шаг 1: Подготовка PostgreSQL
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
170
docs/DOCKER_INSTALL.md
Normal file
170
docs/DOCKER_INSTALL.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Установка Docker и Docker Compose
|
||||||
|
|
||||||
|
## Для Ubuntu/Debian
|
||||||
|
|
||||||
|
### Установка Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Обновление системы
|
||||||
|
sudo apt update
|
||||||
|
sudo apt upgrade -y
|
||||||
|
|
||||||
|
# Установка зависимостей
|
||||||
|
sudo apt install -y ca-certificates curl gnupg lsb-release
|
||||||
|
|
||||||
|
# Добавление GPG ключа Docker
|
||||||
|
sudo install -m 0755 -d /etc/apt/keyrings
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||||
|
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||||
|
|
||||||
|
# Добавление репозитория Docker
|
||||||
|
echo \
|
||||||
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||||||
|
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
|
||||||
|
# Установка Docker Engine
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
|
|
||||||
|
# Проверка установки
|
||||||
|
docker --version
|
||||||
|
docker compose version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Настройка прав (опционально)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Добавить пользователя в группу docker (чтобы не использовать sudo)
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
|
||||||
|
# Применить изменения (нужно перелогиниться или выполнить)
|
||||||
|
newgrp docker
|
||||||
|
|
||||||
|
# Проверка
|
||||||
|
docker ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### Автозапуск Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable docker
|
||||||
|
sudo systemctl start docker
|
||||||
|
```
|
||||||
|
|
||||||
|
## Для других систем
|
||||||
|
|
||||||
|
### CentOS/RHEL/Fedora
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Установка Docker
|
||||||
|
sudo yum install -y yum-utils
|
||||||
|
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||||
|
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
|
||||||
|
# Запуск
|
||||||
|
sudo systemctl start docker
|
||||||
|
sudo systemctl enable docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debian
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Для Debian используйте те же команды что и для Ubuntu
|
||||||
|
# Но в добавлении репозитория используйте:
|
||||||
|
echo \
|
||||||
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
|
||||||
|
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка установки
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Версия Docker
|
||||||
|
docker --version
|
||||||
|
# Должно вывести: Docker version 24.0.x или новее
|
||||||
|
|
||||||
|
# Версия Docker Compose
|
||||||
|
docker compose version
|
||||||
|
# Должно вывести: Docker Compose version v2.x.x или новее
|
||||||
|
|
||||||
|
# Тест Docker
|
||||||
|
docker run hello-world
|
||||||
|
```
|
||||||
|
|
||||||
|
## Если Docker Compose v1 (старая версия)
|
||||||
|
|
||||||
|
Если у вас установлен `docker-compose` (v1) вместо `docker compose` (v2):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Удалите старую версию
|
||||||
|
sudo apt remove docker-compose
|
||||||
|
|
||||||
|
# Установите плагин compose
|
||||||
|
sudo apt install docker-compose-plugin
|
||||||
|
|
||||||
|
# Проверка
|
||||||
|
docker compose version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Ошибка: "Cannot connect to the Docker daemon"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Запустите Docker
|
||||||
|
sudo systemctl start docker
|
||||||
|
|
||||||
|
# Проверьте статус
|
||||||
|
sudo systemctl status docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ошибка: "permission denied"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Добавьте пользователя в группу docker
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
|
||||||
|
# Перелогиньтесь или выполните
|
||||||
|
newgrp docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ошибка: "docker-compose: command not found" но Docker Compose установлен
|
||||||
|
|
||||||
|
Makefile автоматически определит правильную команду:
|
||||||
|
- `docker compose` (v2, рекомендуется)
|
||||||
|
- `docker-compose` (v1, устаревшая)
|
||||||
|
|
||||||
|
## Полезные команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Информация о Docker
|
||||||
|
docker info
|
||||||
|
|
||||||
|
# Список запущенных контейнеров
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
# Список всех контейнеров
|
||||||
|
docker ps -a
|
||||||
|
|
||||||
|
# Список образов
|
||||||
|
docker images
|
||||||
|
|
||||||
|
# Очистка неиспользуемых ресурсов
|
||||||
|
docker system prune -a
|
||||||
|
|
||||||
|
# Логи контейнера
|
||||||
|
docker logs container_name
|
||||||
|
|
||||||
|
# Остановить все контейнеры
|
||||||
|
docker stop $(docker ps -aq)
|
||||||
|
|
||||||
|
# Удалить все контейнеры
|
||||||
|
docker rm $(docker ps -aq)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обновление Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt upgrade docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||||||
|
```
|
||||||
118
fix_db_schema.py
118
fix_db_schema.py
@@ -1,118 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Исправление схемы базы данных
|
|
||||||
Добавление недостающих полей в таблицу users
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
|
||||||
|
|
||||||
from src.core.database import engine
|
|
||||||
from sqlalchemy import text
|
|
||||||
|
|
||||||
async def fix_database_schema():
|
|
||||||
"""Добавление недостающих полей в базу данных"""
|
|
||||||
print("🔧 Исправляем схему базы данных...")
|
|
||||||
|
|
||||||
async with engine.begin() as conn:
|
|
||||||
|
|
||||||
# Проверяем, есть ли поле phone
|
|
||||||
result = await conn.execute(text(
|
|
||||||
"SELECT column_name FROM information_schema.columns "
|
|
||||||
"WHERE table_name = 'users' AND column_name = 'phone'"
|
|
||||||
))
|
|
||||||
|
|
||||||
if not result.fetchone():
|
|
||||||
print("📞 Добавляем поле 'phone'...")
|
|
||||||
await conn.execute(text(
|
|
||||||
"ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL"
|
|
||||||
))
|
|
||||||
print("✅ Поле 'phone' добавлено")
|
|
||||||
else:
|
|
||||||
print("✅ Поле 'phone' уже существует")
|
|
||||||
|
|
||||||
# Проверяем, есть ли поле club_card_number
|
|
||||||
result = await conn.execute(text(
|
|
||||||
"SELECT column_name FROM information_schema.columns "
|
|
||||||
"WHERE table_name = 'users' AND column_name = 'club_card_number'"
|
|
||||||
))
|
|
||||||
|
|
||||||
if not result.fetchone():
|
|
||||||
print("💳 Добавляем поле 'club_card_number'...")
|
|
||||||
await conn.execute(text(
|
|
||||||
"ALTER TABLE users ADD COLUMN club_card_number VARCHAR(50) NULL"
|
|
||||||
))
|
|
||||||
await conn.execute(text(
|
|
||||||
"CREATE UNIQUE INDEX ix_users_club_card_number ON users (club_card_number)"
|
|
||||||
))
|
|
||||||
print("✅ Поле 'club_card_number' добавлено")
|
|
||||||
else:
|
|
||||||
print("✅ Поле 'club_card_number' уже существует")
|
|
||||||
|
|
||||||
# Проверяем, есть ли поле is_registered
|
|
||||||
result = await conn.execute(text(
|
|
||||||
"SELECT column_name FROM information_schema.columns "
|
|
||||||
"WHERE table_name = 'users' AND column_name = 'is_registered'"
|
|
||||||
))
|
|
||||||
|
|
||||||
if not result.fetchone():
|
|
||||||
print("📝 Добавляем поле 'is_registered'...")
|
|
||||||
await conn.execute(text(
|
|
||||||
"ALTER TABLE users ADD COLUMN is_registered BOOLEAN DEFAULT FALSE NOT NULL"
|
|
||||||
))
|
|
||||||
print("✅ Поле 'is_registered' добавлено")
|
|
||||||
else:
|
|
||||||
print("✅ Поле 'is_registered' уже существует")
|
|
||||||
|
|
||||||
# Проверяем, есть ли поле verification_code
|
|
||||||
result = await conn.execute(text(
|
|
||||||
"SELECT column_name FROM information_schema.columns "
|
|
||||||
"WHERE table_name = 'users' AND column_name = 'verification_code'"
|
|
||||||
))
|
|
||||||
|
|
||||||
if not result.fetchone():
|
|
||||||
print("🔐 Добавляем поле 'verification_code'...")
|
|
||||||
await conn.execute(text(
|
|
||||||
"ALTER TABLE users ADD COLUMN verification_code VARCHAR(10) NULL"
|
|
||||||
))
|
|
||||||
await conn.execute(text(
|
|
||||||
"CREATE UNIQUE INDEX ix_users_verification_code ON users (verification_code)"
|
|
||||||
))
|
|
||||||
print("✅ Поле 'verification_code' добавлено")
|
|
||||||
else:
|
|
||||||
print("✅ Поле 'verification_code' уже существует")
|
|
||||||
|
|
||||||
# Удаляем поле account_number, если оно есть (оно перенесено в отдельную таблицу)
|
|
||||||
result = await conn.execute(text(
|
|
||||||
"SELECT column_name FROM information_schema.columns "
|
|
||||||
"WHERE table_name = 'users' AND column_name = 'account_number'"
|
|
||||||
))
|
|
||||||
|
|
||||||
if result.fetchone():
|
|
||||||
print("🗑️ Удаляем устаревшее поле 'account_number'...")
|
|
||||||
# Сначала удаляем индекс
|
|
||||||
try:
|
|
||||||
await conn.execute(text("DROP INDEX IF EXISTS ix_users_account_number"))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
await conn.execute(text(
|
|
||||||
"ALTER TABLE users DROP COLUMN account_number"
|
|
||||||
))
|
|
||||||
print("✅ Поле 'account_number' удалено")
|
|
||||||
else:
|
|
||||||
print("✅ Поле 'account_number' уже удалено")
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
"""Основная функция"""
|
|
||||||
try:
|
|
||||||
await fix_database_schema()
|
|
||||||
print("\n🎉 Схема базы данных успешно исправлена!")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Ошибка при исправлении базы данных: {e}")
|
|
||||||
finally:
|
|
||||||
await engine.dispose()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Генератор тестовых счетов для проверки производительности розыгрыша
|
|
||||||
"""
|
|
||||||
import random
|
|
||||||
|
|
||||||
|
|
||||||
def generate_account_number():
|
|
||||||
"""Генерирует случайный номер счета в формате XX-XX-XX-XX-XX-XX-XX"""
|
|
||||||
parts = []
|
|
||||||
for _ in range(7):
|
|
||||||
part = f"{random.randint(0, 99):02d}"
|
|
||||||
parts.append(part)
|
|
||||||
return "-".join(parts)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_accounts(count, card_numbers=None):
|
|
||||||
"""
|
|
||||||
Генерирует список уникальных счетов
|
|
||||||
|
|
||||||
Args:
|
|
||||||
count: Количество счетов для генерации
|
|
||||||
card_numbers: Список номеров карт (опционально)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[str]: Список счетов
|
|
||||||
"""
|
|
||||||
accounts = set()
|
|
||||||
|
|
||||||
while len(accounts) < count:
|
|
||||||
account = generate_account_number()
|
|
||||||
|
|
||||||
# Добавляем с картой или без
|
|
||||||
if card_numbers and random.random() > 0.3: # 70% с картой
|
|
||||||
card = random.choice(card_numbers)
|
|
||||||
full_account = f"{card} {account}"
|
|
||||||
else:
|
|
||||||
full_account = account
|
|
||||||
|
|
||||||
accounts.add(full_account)
|
|
||||||
|
|
||||||
return list(accounts)
|
|
||||||
|
|
||||||
|
|
||||||
def save_to_file(accounts, filename):
|
|
||||||
"""Сохраняет счета в файл"""
|
|
||||||
with open(filename, 'w', encoding='utf-8') as f:
|
|
||||||
for account in accounts:
|
|
||||||
f.write(account + '\n')
|
|
||||||
print(f"✅ Сохранено {len(accounts)} счетов в файл {filename}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Главная функция"""
|
|
||||||
print("🎲 Генератор тестовых счетов для розыгрыша\n")
|
|
||||||
|
|
||||||
# Параметры
|
|
||||||
counts = [100, 500, 1000, 2000, 5000]
|
|
||||||
card_numbers = ['2521', '2522', '2523', '2524', '2525']
|
|
||||||
|
|
||||||
for count in counts:
|
|
||||||
print(f"Генерация {count} счетов...")
|
|
||||||
accounts = generate_accounts(count, card_numbers)
|
|
||||||
filename = f"test_accounts_{count}.txt"
|
|
||||||
save_to_file(accounts, filename)
|
|
||||||
|
|
||||||
print("\n✅ Генерация завершена!")
|
|
||||||
print("\nИспользование:")
|
|
||||||
print("1. Скопируйте содержимое нужного файла")
|
|
||||||
print("2. В боте: Управление розыгрышами → Выберите розыгрыш → Участники → Добавить массово")
|
|
||||||
print("3. Вставьте содержимое файла")
|
|
||||||
print("4. Проведите розыгрыш и проверьте время выполнения")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
10
main.py
10
main.py
@@ -39,6 +39,16 @@ dp = Dispatcher(storage=storage)
|
|||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
# Middleware для логирования всех callback'ов
|
||||||
|
@dp.callback_query.middleware()
|
||||||
|
async def log_callback_middleware(handler, event, data):
|
||||||
|
"""Middleware для логирования всех callback запросов"""
|
||||||
|
logger.warning(f"🔔 MIDDLEWARE CALLBACK: data='{event.data}', user_id={event.from_user.id}")
|
||||||
|
result = await handler(event, data)
|
||||||
|
logger.warning(f"🔔 MIDDLEWARE CALLBACK HANDLED: data='{event.data}', result={result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def get_controller():
|
async def get_controller():
|
||||||
"""Контекстный менеджер для получения контроллера с БД сессией"""
|
"""Контекстный менеджер для получения контроллера с БД сессией"""
|
||||||
|
|||||||
1427
main_old.py
1427
main_old.py
File diff suppressed because it is too large
Load Diff
@@ -1,97 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Минимальная рабочая версия main.py для лотерейного бота
|
|
||||||
"""
|
|
||||||
from aiogram import Bot, Dispatcher
|
|
||||||
from aiogram.types import BotCommand
|
|
||||||
from aiogram.fsm.storage.memory import MemoryStorage
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from src.core.config import BOT_TOKEN, ADMIN_IDS
|
|
||||||
from src.core.database import async_session_maker, init_db
|
|
||||||
|
|
||||||
# Настройка логирования
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Инициализация бота
|
|
||||||
bot = Bot(token=BOT_TOKEN)
|
|
||||||
storage = MemoryStorage()
|
|
||||||
dp = Dispatcher(storage=storage)
|
|
||||||
|
|
||||||
async def set_commands():
|
|
||||||
"""Установка команд бота"""
|
|
||||||
commands = [
|
|
||||||
BotCommand(command="start", description="🚀 Запустить бота"),
|
|
||||||
BotCommand(command="help", description="❓ Помощь"),
|
|
||||||
]
|
|
||||||
await bot.set_my_commands(commands)
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
"""Главная функция"""
|
|
||||||
try:
|
|
||||||
logger.info("🔄 Инициализация базы данных...")
|
|
||||||
await init_db()
|
|
||||||
|
|
||||||
logger.info("🔄 Установка команд...")
|
|
||||||
await set_commands()
|
|
||||||
|
|
||||||
# Импортируем и подключаем роутеры
|
|
||||||
logger.info("🔄 Подключение роутеров...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
from src.handlers.registration_handlers import router as registration_router
|
|
||||||
dp.include_router(registration_router)
|
|
||||||
logger.info("✅ Registration router подключен")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Ошибка подключения registration router: {e}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
from src.handlers.admin_panel import admin_router
|
|
||||||
dp.include_router(admin_router)
|
|
||||||
logger.info("✅ Admin router подключен")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Ошибка подключения admin router: {e}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
from src.handlers.account_handlers import account_router
|
|
||||||
dp.include_router(account_router)
|
|
||||||
logger.info("✅ Account router подключен")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Ошибка подключения account router: {e}")
|
|
||||||
|
|
||||||
# Обработка сигналов для graceful shutdown
|
|
||||||
def signal_handler():
|
|
||||||
logger.info("Получен сигнал завершения, остановка бота...")
|
|
||||||
|
|
||||||
# Настройка обработчиков сигналов
|
|
||||||
if sys.platform != "win32":
|
|
||||||
for sig in (signal.SIGTERM, signal.SIGINT):
|
|
||||||
asyncio.get_event_loop().add_signal_handler(sig, signal_handler)
|
|
||||||
|
|
||||||
# Получаем информацию о боте
|
|
||||||
bot_info = await bot.get_me()
|
|
||||||
logger.info(f"🚀 Бот запущен: @{bot_info.username} ({bot_info.first_name})")
|
|
||||||
|
|
||||||
# Запуск бота
|
|
||||||
await dp.start_polling(bot)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Критическая ошибка: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
finally:
|
|
||||||
logger.info("Завершение работы")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
asyncio.run(main())
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logger.info("Бот остановлен пользователем")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Критическая ошибка: {e}")
|
|
||||||
finally:
|
|
||||||
logger.info("Завершение работы")
|
|
||||||
@@ -21,7 +21,7 @@ class KeyboardBuilderImpl(IKeyboardBuilder):
|
|||||||
if is_admin:
|
if is_admin:
|
||||||
buttons.extend([
|
buttons.extend([
|
||||||
[InlineKeyboardButton(text="⚙️ Админ панель", callback_data="admin_panel")],
|
[InlineKeyboardButton(text="⚙️ Админ панель", callback_data="admin_panel")],
|
||||||
[InlineKeyboardButton(text="➕ Создать розыгрыш", callback_data="create_lottery")]
|
[InlineKeyboardButton(text="➕ Создать розыгрыш", callback_data="admin_create_lottery")]
|
||||||
])
|
])
|
||||||
|
|
||||||
return InlineKeyboardMarkup(inline_keyboard=buttons)
|
return InlineKeyboardMarkup(inline_keyboard=buttons)
|
||||||
@@ -30,8 +30,9 @@ class KeyboardBuilderImpl(IKeyboardBuilder):
|
|||||||
"""Получить админскую клавиатуру"""
|
"""Получить админскую клавиатуру"""
|
||||||
buttons = [
|
buttons = [
|
||||||
[InlineKeyboardButton(text="🎲 Управление розыгрышами", callback_data="admin_lotteries")],
|
[InlineKeyboardButton(text="🎲 Управление розыгрышами", callback_data="admin_lotteries")],
|
||||||
[InlineKeyboardButton(text="<EFBFBD> Управление участниками", callback_data="admin_participants")],
|
[InlineKeyboardButton(text="👥 Управление участниками", callback_data="admin_participants")],
|
||||||
[InlineKeyboardButton(text="👑 Управление победителями", callback_data="admin_winners")],
|
[InlineKeyboardButton(text="👑 Управление победителями", callback_data="admin_winners")],
|
||||||
|
[InlineKeyboardButton(text="💬 Сообщения пользователей", callback_data="admin_messages")],
|
||||||
[InlineKeyboardButton(text="📊 Статистика", callback_data="admin_stats")],
|
[InlineKeyboardButton(text="📊 Статистика", callback_data="admin_stats")],
|
||||||
[InlineKeyboardButton(text="⚙️ Настройки", callback_data="admin_settings")],
|
[InlineKeyboardButton(text="⚙️ Настройки", callback_data="admin_settings")],
|
||||||
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")]
|
[InlineKeyboardButton(text="🔙 Назад", callback_data="back_to_main")]
|
||||||
|
|||||||
@@ -87,8 +87,21 @@ class BotController(IBotController):
|
|||||||
is_registered=user.is_registered
|
is_registered=user.is_registered
|
||||||
)
|
)
|
||||||
|
|
||||||
await callback.message.edit_text(
|
try:
|
||||||
text,
|
await callback.message.edit_text(
|
||||||
reply_markup=keyboard,
|
text,
|
||||||
parse_mode="Markdown"
|
reply_markup=keyboard,
|
||||||
)
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# Если сообщение не изменилось - просто отвечаем на callback
|
||||||
|
if "message is not modified" in str(e):
|
||||||
|
await callback.answer("✅ Уже показаны активные розыгрыши")
|
||||||
|
else:
|
||||||
|
# Другие ошибки - пробуем отправить новое сообщение
|
||||||
|
await callback.answer()
|
||||||
|
await callback.message.answer(
|
||||||
|
text,
|
||||||
|
reply_markup=keyboard,
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
@@ -284,6 +284,58 @@ class ChatMessageService:
|
|||||||
|
|
||||||
result = await session.execute(query)
|
result = await session.execute(query)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_user_messages_all(
|
||||||
|
session: AsyncSession,
|
||||||
|
limit: int = 50,
|
||||||
|
offset: int = 0,
|
||||||
|
include_deleted: bool = False
|
||||||
|
) -> List[ChatMessage]:
|
||||||
|
"""Получить последние сообщения всех пользователей"""
|
||||||
|
query = select(ChatMessage).options(selectinload(ChatMessage.sender))
|
||||||
|
|
||||||
|
if not include_deleted:
|
||||||
|
query = query.where(ChatMessage.is_deleted == False)
|
||||||
|
|
||||||
|
query = query.order_by(ChatMessage.created_at.desc()).limit(limit).offset(offset)
|
||||||
|
|
||||||
|
result = await session.execute(query)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def count_messages(
|
||||||
|
session: AsyncSession,
|
||||||
|
include_deleted: bool = False
|
||||||
|
) -> int:
|
||||||
|
"""Подсчитать количество сообщений"""
|
||||||
|
from sqlalchemy import func
|
||||||
|
query = select(func.count(ChatMessage.id))
|
||||||
|
|
||||||
|
if not include_deleted:
|
||||||
|
query = query.where(ChatMessage.is_deleted == False)
|
||||||
|
|
||||||
|
result = await session.execute(query)
|
||||||
|
return result.scalar() or 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def mark_as_deleted(
|
||||||
|
session: AsyncSession,
|
||||||
|
message_id: int,
|
||||||
|
deleted_by: int
|
||||||
|
) -> bool:
|
||||||
|
"""Пометить сообщение как удаленное"""
|
||||||
|
result = await session.execute(
|
||||||
|
update(ChatMessage)
|
||||||
|
.where(ChatMessage.id == message_id)
|
||||||
|
.values(
|
||||||
|
is_deleted=True,
|
||||||
|
deleted_by=deleted_by,
|
||||||
|
deleted_at=datetime.now(timezone.utc)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
return result.rowcount > 0
|
||||||
|
|
||||||
|
|
||||||
class ChatPermissionService:
|
class ChatPermissionService:
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ class UserService:
|
|||||||
)
|
)
|
||||||
return result.scalar_one_or_none()
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_user_by_id(session: AsyncSession, user_id: int) -> Optional[User]:
|
||||||
|
"""Получить пользователя по ID"""
|
||||||
|
result = await session.execute(select(User).where(User.id == user_id))
|
||||||
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_user_by_username(session: AsyncSession, username: str) -> Optional[User]:
|
async def get_user_by_username(session: AsyncSession, username: str) -> Optional[User]:
|
||||||
"""Получить пользователя по username"""
|
"""Получить пользователя по username"""
|
||||||
@@ -227,6 +233,25 @@ class LotteryService:
|
|||||||
|
|
||||||
result = await session.execute(query)
|
result = await session.execute(query)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def update_lottery(
|
||||||
|
session: AsyncSession,
|
||||||
|
lottery_id: int,
|
||||||
|
**updates
|
||||||
|
) -> bool:
|
||||||
|
"""Обновить данные розыгрыша"""
|
||||||
|
try:
|
||||||
|
await session.execute(
|
||||||
|
update(Lottery)
|
||||||
|
.where(Lottery.id == lottery_id)
|
||||||
|
.values(**updates)
|
||||||
|
)
|
||||||
|
await session.commit()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
await session.rollback()
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_all_lotteries(session: AsyncSession, limit: Optional[int] = None) -> List[Lottery]:
|
async def get_all_lotteries(session: AsyncSession, limit: Optional[int] = None) -> List[Lottery]:
|
||||||
@@ -264,10 +289,16 @@ class LotteryService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def conduct_draw(session: AsyncSession, lottery_id: int) -> Dict[int, Dict[str, Any]]:
|
async def conduct_draw(session: AsyncSession, lottery_id: int) -> Dict[int, Dict[str, Any]]:
|
||||||
"""Провести розыгрыш с учетом ручных победителей"""
|
"""Провести розыгрыш с учетом ручных победителей"""
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
logger.info(f"conduct_draw: начало для lottery_id={lottery_id}")
|
||||||
lottery = await LotteryService.get_lottery(session, lottery_id)
|
lottery = await LotteryService.get_lottery(session, lottery_id)
|
||||||
if not lottery or lottery.is_completed:
|
if not lottery or lottery.is_completed:
|
||||||
|
logger.warning(f"conduct_draw: lottery не найден или завершён")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
logger.info(f"conduct_draw: получаем участников")
|
||||||
# Получаем всех участников (включая тех, у кого нет user)
|
# Получаем всех участников (включая тех, у кого нет user)
|
||||||
participants = []
|
participants = []
|
||||||
for p in lottery.participations:
|
for p in lottery.participations:
|
||||||
@@ -282,7 +313,9 @@ class LotteryService:
|
|||||||
'account_number': p.account_number
|
'account_number': p.account_number
|
||||||
})())
|
})())
|
||||||
|
|
||||||
|
logger.info(f"conduct_draw: участников {len(participants)}")
|
||||||
if not participants:
|
if not participants:
|
||||||
|
logger.warning(f"conduct_draw: нет участников")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Определяем количество призовых мест
|
# Определяем количество призовых мест
|
||||||
@@ -336,6 +369,7 @@ class LotteryService:
|
|||||||
session.add(winner)
|
session.add(winner)
|
||||||
|
|
||||||
# Обновляем статус розыгрыша
|
# Обновляем статус розыгрыша
|
||||||
|
logger.info(f"conduct_draw: обновляем статус lottery")
|
||||||
lottery.is_completed = True
|
lottery.is_completed = True
|
||||||
lottery.draw_results = {}
|
lottery.draw_results = {}
|
||||||
for place, info in results.items():
|
for place, info in results.items():
|
||||||
@@ -349,7 +383,8 @@ class LotteryService:
|
|||||||
'is_manual': info['is_manual']
|
'is_manual': info['is_manual']
|
||||||
}
|
}
|
||||||
|
|
||||||
await session.commit()
|
# НЕ коммитим здесь - это должно сделать вызывающая функция
|
||||||
|
logger.info(f"conduct_draw: изменения подготовлены, победителей: {len(results)}")
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ class AddAccountStates(StatesGroup):
|
|||||||
choosing_lottery = State()
|
choosing_lottery = State()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(Command("cancel"))
|
||||||
|
@admin_only
|
||||||
|
async def cancel_command(message: Message, state: FSMContext):
|
||||||
|
"""Отменить текущую операцию и сбросить состояние"""
|
||||||
|
await state.clear()
|
||||||
|
await message.answer("✅ Состояние сброшено. Все операции отменены.")
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("add_account"))
|
@router.message(Command("add_account"))
|
||||||
@admin_only
|
@admin_only
|
||||||
async def add_account_command(message: Message, state: FSMContext):
|
async def add_account_command(message: Message, state: FSMContext):
|
||||||
@@ -43,11 +51,12 @@ async def add_account_command(message: Message, state: FSMContext):
|
|||||||
await state.set_state(AddAccountStates.waiting_for_data)
|
await state.set_state(AddAccountStates.waiting_for_data)
|
||||||
await message.answer(
|
await message.answer(
|
||||||
"💳 **Добавление счетов**\n\n"
|
"💳 **Добавление счетов**\n\n"
|
||||||
"Отправьте данные в формате:\n"
|
"📋 **Формат 1 (однострочный):**\n"
|
||||||
"`клубная_карта номер_счета`\n\n"
|
"`карта счет`\n"
|
||||||
"**Для одного счета:**\n"
|
"Пример: `2223 11-22-33-44-55-66-77`\n\n"
|
||||||
"`2223 11-22-33-44-55-66-77`\n\n"
|
"📋 **Формат 2 (многострочный из таблицы):**\n"
|
||||||
"**Для нескольких счетов (каждый с новой строки):**\n"
|
"Скопируйте столбцы со счетами и картами - система сама распознает\n\n"
|
||||||
|
"**Для нескольких счетов:**\n"
|
||||||
"`2223 11-22-33-44-55-66-77`\n"
|
"`2223 11-22-33-44-55-66-77`\n"
|
||||||
"`2223 88-99-00-11-22-33-44`\n"
|
"`2223 88-99-00-11-22-33-44`\n"
|
||||||
"`3334 12-34-56-78-90-12-34`\n\n"
|
"`3334 12-34-56-78-90-12-34`\n\n"
|
||||||
@@ -86,13 +95,14 @@ async def process_single_account(message: Message, club_card: str, account_numbe
|
|||||||
if owner:
|
if owner:
|
||||||
text += f"👤 Владелец: {owner.first_name}\n\n"
|
text += f"👤 Владелец: {owner.first_name}\n\n"
|
||||||
|
|
||||||
# Отправляем уведомление владельцу
|
# Отправляем уведомление владельцу с форматированием
|
||||||
try:
|
try:
|
||||||
await message.bot.send_message(
|
await message.bot.send_message(
|
||||||
owner.telegram_id,
|
owner.telegram_id,
|
||||||
f"✅ К вашему профилю добавлен счет:\n\n"
|
f"✅ К вашему профилю добавлен счет:\n\n"
|
||||||
f"💳 {account_number}\n\n"
|
f"💳 `{account_number}`\n\n"
|
||||||
f"Теперь вы можете участвовать в розыгрышах с этим счетом!"
|
f"Теперь вы можете участвовать в розыгрышах!",
|
||||||
|
parse_mode="Markdown"
|
||||||
)
|
)
|
||||||
text += "📨 Владельцу отправлено уведомление\n\n"
|
text += "📨 Владельцу отправлено уведомление\n\n"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -118,17 +128,66 @@ async def process_accounts_data(message: Message, state: FSMContext):
|
|||||||
return
|
return
|
||||||
|
|
||||||
lines = message.text.strip().split('\n')
|
lines = message.text.strip().split('\n')
|
||||||
|
|
||||||
|
# Ограничение: максимум 1000 счетов за раз
|
||||||
|
MAX_ACCOUNTS = 1000
|
||||||
|
if len(lines) > MAX_ACCOUNTS:
|
||||||
|
await message.answer(
|
||||||
|
f"⚠️ Слишком много счетов!\n\n"
|
||||||
|
f"Максимум за раз: {MAX_ACCOUNTS}\n"
|
||||||
|
f"Вы отправили: {len(lines)} строк\n\n"
|
||||||
|
f"Разделите данные на несколько частей."
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Отправляем начальное уведомление
|
||||||
|
progress_msg = await message.answer(
|
||||||
|
f"⏳ Обработка {len(lines)} строк...\n"
|
||||||
|
f"Пожалуйста, подождите..."
|
||||||
|
)
|
||||||
|
|
||||||
accounts_data = []
|
accounts_data = []
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
for i, line in enumerate(lines, 1):
|
BATCH_SIZE = 100 # Обрабатываем по 100 счетов за раз
|
||||||
parts = line.strip().split()
|
|
||||||
if len(parts) != 2:
|
# Универсальный парсер: поддержка однострочного и многострочного формата
|
||||||
errors.append(f"Строка {i}: неверный формат (ожидается: клубная_карта номер_счета)")
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i].strip()
|
||||||
|
|
||||||
|
# Пропускаем пустые строки и строки с названиями/датами
|
||||||
|
if not line or any(x in line.lower() for x in ['viposnova', '0.00', ':']):
|
||||||
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
club_card, account_number = parts
|
# Проверяем, есть ли в строке пробел (однострочный формат: "карта счет")
|
||||||
|
if ' ' in line:
|
||||||
|
# Однострочный формат: разделяем по первому пробелу
|
||||||
|
parts = line.split(maxsplit=1)
|
||||||
|
if len(parts) == 2:
|
||||||
|
club_card, account_number = parts
|
||||||
|
else:
|
||||||
|
errors.append(f"Строка {i+1}: неверный формат")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Многострочный формат: текущая строка - счет, следующая - карта
|
||||||
|
account_number = line
|
||||||
|
i += 1
|
||||||
|
if i >= len(lines):
|
||||||
|
errors.append(f"Строка {i}: отсутствует номер карты после счета {account_number}")
|
||||||
|
break
|
||||||
|
|
||||||
|
club_card = lines[i].strip()
|
||||||
|
# Пропускаем, если следующая строка содержит мусор
|
||||||
|
if not club_card or any(x in club_card.lower() for x in ['viposnova', '0.00', ':']):
|
||||||
|
errors.append(f"Строка {i}: некорректный номер карты после счета {account_number}")
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Создаем счет
|
||||||
try:
|
try:
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
account = await AccountService.create_account(
|
account = await AccountService.create_account(
|
||||||
@@ -143,25 +202,99 @@ async def process_accounts_data(message: Message, state: FSMContext):
|
|||||||
'club_card': club_card,
|
'club_card': club_card,
|
||||||
'account_number': account_number,
|
'account_number': account_number,
|
||||||
'account_id': account.id,
|
'account_id': account.id,
|
||||||
'owner': owner
|
'owner': owner,
|
||||||
|
'owner_id': owner.telegram_id if owner else None
|
||||||
})
|
})
|
||||||
|
|
||||||
# Отправляем уведомление владельцу
|
# Обновляем progress каждые 50 счетов
|
||||||
if owner:
|
if len(accounts_data) % 50 == 0:
|
||||||
try:
|
try:
|
||||||
await message.bot.send_message(
|
await progress_msg.edit_text(
|
||||||
owner.telegram_id,
|
f"⏳ Обработано: {len(accounts_data)} / ~{len(lines)}\n"
|
||||||
f"✅ К вашему профилю добавлен счет:\n\n"
|
f"❌ Ошибок: {len(errors)}"
|
||||||
f"💳 {account_number}\n\n"
|
|
||||||
f"Теперь вы можете участвовать в розыгрышах!"
|
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
pass
|
pass # Игнорируем ошибки редактирования
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
errors.append(f"Строка {i} ({club_card} {account_number}): {str(e)}")
|
errors.append(f"Счет {account_number} (карта {club_card}): {str(e)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append(f"Строка {i}: {str(e)}")
|
errors.append(f"Счет {account_number}: {str(e)}")
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Удаляем progress сообщение
|
||||||
|
try:
|
||||||
|
await progress_msg.delete()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Группируем счета по владельцам и отправляем групповые уведомления
|
||||||
|
if accounts_data:
|
||||||
|
from collections import defaultdict
|
||||||
|
accounts_by_owner = defaultdict(list)
|
||||||
|
|
||||||
|
for acc in accounts_data:
|
||||||
|
if acc['owner_id']:
|
||||||
|
accounts_by_owner[acc['owner_id']].append(acc['account_number'])
|
||||||
|
|
||||||
|
# Отправляем групповые уведомления
|
||||||
|
for owner_id, account_numbers in accounts_by_owner.items():
|
||||||
|
try:
|
||||||
|
if len(account_numbers) == 1:
|
||||||
|
# Одиночное уведомление
|
||||||
|
notification_text = (
|
||||||
|
"✅ К вашему профилю добавлен счет:\n\n"
|
||||||
|
f"💳 `{account_numbers[0]}`\n\n"
|
||||||
|
"Теперь вы можете участвовать в розыгрышах!"
|
||||||
|
)
|
||||||
|
await message.bot.send_message(
|
||||||
|
owner_id,
|
||||||
|
notification_text,
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
elif len(account_numbers) <= 50:
|
||||||
|
# Групповое уведомление (до 50 счетов)
|
||||||
|
notification_text = (
|
||||||
|
f"✅ К вашему профилю добавлено счетов: *{len(account_numbers)}*\n\n"
|
||||||
|
"💳 *Ваши счета:*\n"
|
||||||
|
)
|
||||||
|
for acc_num in account_numbers:
|
||||||
|
notification_text += f"• `{acc_num}`\n"
|
||||||
|
notification_text += "\nТеперь вы можете участвовать в розыгрышах!"
|
||||||
|
|
||||||
|
await message.bot.send_message(
|
||||||
|
owner_id,
|
||||||
|
notification_text,
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Много счетов - показываем первые 10 и кнопку
|
||||||
|
notification_text = (
|
||||||
|
f"✅ К вашему профилю добавлено счетов: *{len(account_numbers)}*\n\n"
|
||||||
|
"💳 *Первые 10 счетов:*\n"
|
||||||
|
)
|
||||||
|
for acc_num in account_numbers[:10]:
|
||||||
|
notification_text += f"• `{acc_num}`\n"
|
||||||
|
notification_text += f"\n_...и ещё {len(account_numbers) - 10} счетов_\n\n"
|
||||||
|
notification_text += "Теперь вы можете участвовать в розыгрышах!"
|
||||||
|
|
||||||
|
# Кнопка для просмотра всех счетов
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(
|
||||||
|
text="📋 Просмотреть все счета",
|
||||||
|
callback_data="view_my_accounts"
|
||||||
|
)]
|
||||||
|
])
|
||||||
|
|
||||||
|
await message.bot.send_message(
|
||||||
|
owner_id,
|
||||||
|
notification_text,
|
||||||
|
parse_mode="Markdown",
|
||||||
|
reply_markup=keyboard
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
pass # Игнорируем ошибки отправки уведомлений
|
||||||
|
|
||||||
# Формируем отчет
|
# Формируем отчет
|
||||||
text = f"📊 **Результаты добавления счетов**\n\n"
|
text = f"📊 **Результаты добавления счетов**\n\n"
|
||||||
@@ -305,31 +438,70 @@ async def skip_lottery_add(callback: CallbackQuery, state: FSMContext):
|
|||||||
@admin_only
|
@admin_only
|
||||||
async def remove_account_command(message: Message):
|
async def remove_account_command(message: Message):
|
||||||
"""
|
"""
|
||||||
Деактивировать счет
|
Деактивировать счет(а)
|
||||||
Формат: /remove_account <account_number>
|
Формат: /remove_account <account_number1> [account_number2] [account_number3] ...
|
||||||
|
Можно указать несколько счетов через пробел для массового удаления
|
||||||
"""
|
"""
|
||||||
|
|
||||||
parts = message.text.split()
|
parts = message.text.split()
|
||||||
if len(parts) != 2:
|
if len(parts) < 2:
|
||||||
await message.answer(
|
await message.answer(
|
||||||
"❌ Неверный формат команды\n\n"
|
"❌ Неверный формат команды\n\n"
|
||||||
"Используйте: /remove_account <account_number>"
|
"Используйте: /remove_account <account_number1> [account_number2] ...\n\n"
|
||||||
|
"Примеры:\n"
|
||||||
|
"• /remove_account 12-34-56-78-90-12-34\n"
|
||||||
|
"• /remove_account 12-34-56-78-90-12-34 98-76-54-32-10-98-76"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
account_number = parts[1]
|
account_numbers = parts[1:] # Все аргументы после команды
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with async_session_maker() as session:
|
results = {
|
||||||
success = await AccountService.deactivate_account(session, account_number)
|
'success': [],
|
||||||
|
'not_found': [],
|
||||||
|
'errors': []
|
||||||
|
}
|
||||||
|
|
||||||
if success:
|
async with async_session_maker() as session:
|
||||||
await message.answer(f"✅ Счет {account_number} деактивирован")
|
for account_number in account_numbers:
|
||||||
|
try:
|
||||||
|
success = await AccountService.deactivate_account(session, account_number)
|
||||||
|
if success:
|
||||||
|
results['success'].append(account_number)
|
||||||
|
else:
|
||||||
|
results['not_found'].append(account_number)
|
||||||
|
except Exception as e:
|
||||||
|
results['errors'].append((account_number, str(e)))
|
||||||
|
|
||||||
|
# Формируем отчёт
|
||||||
|
response_parts = []
|
||||||
|
|
||||||
|
if results['success']:
|
||||||
|
response_parts.append(
|
||||||
|
f"✅ *Деактивировано счетов: {len(results['success'])}*\n"
|
||||||
|
+ "\n".join(f"• `{acc}`" for acc in results['success'])
|
||||||
|
)
|
||||||
|
|
||||||
|
if results['not_found']:
|
||||||
|
response_parts.append(
|
||||||
|
f"❌ *Не найдено счетов: {len(results['not_found'])}*\n"
|
||||||
|
+ "\n".join(f"• `{acc}`" for acc in results['not_found'])
|
||||||
|
)
|
||||||
|
|
||||||
|
if results['errors']:
|
||||||
|
response_parts.append(
|
||||||
|
f"⚠️ *Ошибки при обработке: {len(results['errors'])}*\n"
|
||||||
|
+ "\n".join(f"• `{acc}`: {err}" for acc, err in results['errors'])
|
||||||
|
)
|
||||||
|
|
||||||
|
if not response_parts:
|
||||||
|
await message.answer("❌ Не удалось обработать ни один счет")
|
||||||
else:
|
else:
|
||||||
await message.answer(f"❌ Счет {account_number} не найден")
|
await message.answer("\n\n".join(response_parts), parse_mode="Markdown")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await message.answer(f"❌ Ошибка: {str(e)}")
|
await message.answer(f"❌ Критическая ошибка: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("verify_winner"))
|
@router.message(Command("verify_winner"))
|
||||||
@@ -569,3 +741,71 @@ async def user_info_command(message: Message):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await message.answer(f"❌ Ошибка: {str(e)}")
|
await message.answer(f"❌ Ошибка: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "view_my_accounts")
|
||||||
|
async def view_my_accounts_callback(callback: CallbackQuery):
|
||||||
|
"""Показать все счета пользователя"""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
# Получаем пользователя
|
||||||
|
user_result = await session.execute(
|
||||||
|
select(User).where(User.telegram_id == callback.from_user.id)
|
||||||
|
)
|
||||||
|
user = user_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
await callback.answer("❌ Пользователь не найден", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Получаем все счета
|
||||||
|
accounts = await AccountService.get_user_accounts(session, user.id)
|
||||||
|
|
||||||
|
if not accounts:
|
||||||
|
await callback.answer("У вас нет счетов", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Отвечаем на callback сразу, чтобы не было timeout
|
||||||
|
await callback.answer("⏳ Загружаю ваши счета...")
|
||||||
|
|
||||||
|
# Если счетов много - предупреждаем о задержке
|
||||||
|
batches_count = (len(accounts) + 49) // 50 # Округление вверх
|
||||||
|
if batches_count > 5:
|
||||||
|
await callback.message.answer(
|
||||||
|
f"📊 Найдено счетов: *{len(accounts)}*\n"
|
||||||
|
f"📤 Отправка {batches_count} сообщений с задержкой (~{batches_count//2} сек)\n\n"
|
||||||
|
f"⏳ _Пожалуйста, подождите. Бот не завис._",
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Формируем сообщение с пагинацией (по 50 счетов на сообщение)
|
||||||
|
BATCH_SIZE = 50
|
||||||
|
for i in range(0, len(accounts), BATCH_SIZE):
|
||||||
|
batch = accounts[i:i+BATCH_SIZE]
|
||||||
|
|
||||||
|
text = f"💳 *Ваши счета ({i+1}-{min(i+BATCH_SIZE, len(accounts))} из {len(accounts)}):*\n\n"
|
||||||
|
for acc in batch:
|
||||||
|
status = "✅" if acc.is_active else "❌"
|
||||||
|
text += f"{status} `{acc.account_number}`\n"
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.answer(text, parse_mode="Markdown")
|
||||||
|
# Задержка между сообщениями для избежания flood control
|
||||||
|
if i + BATCH_SIZE < len(accounts):
|
||||||
|
await asyncio.sleep(0.5) # 500ms между сообщениями
|
||||||
|
except Exception as send_error:
|
||||||
|
# Если flood control - ждём дольше
|
||||||
|
if "Flood control" in str(send_error) or "Too Many Requests" in str(send_error):
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
await callback.message.answer(text, parse_mode="Markdown")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Не используем callback.answer в except - может быть timeout
|
||||||
|
try:
|
||||||
|
await callback.message.answer(f"❌ Ошибка: {str(e)}")
|
||||||
|
except:
|
||||||
|
pass # Игнорируем если не получилось отправить
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from aiogram import Router, F
|
|||||||
from aiogram.types import (
|
from aiogram.types import (
|
||||||
CallbackQuery, Message, InlineKeyboardButton, InlineKeyboardMarkup
|
CallbackQuery, Message, InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
)
|
)
|
||||||
|
from aiogram.exceptions import TelegramBadRequest
|
||||||
from aiogram.filters import StateFilter
|
from aiogram.filters import StateFilter
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
@@ -15,12 +16,39 @@ import json
|
|||||||
|
|
||||||
from ..core.database import async_session_maker
|
from ..core.database import async_session_maker
|
||||||
from ..core.services import UserService, LotteryService, ParticipationService
|
from ..core.services import UserService, LotteryService, ParticipationService
|
||||||
|
from ..core.chat_services import ChatMessageService
|
||||||
from ..core.config import ADMIN_IDS
|
from ..core.config import ADMIN_IDS
|
||||||
from ..core.models import User, Lottery, Participation, Account
|
from ..core.models import User, Lottery, Participation, Account, ChatMessage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def safe_edit_message(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
text: str,
|
||||||
|
reply_markup: InlineKeyboardMarkup | None = None,
|
||||||
|
parse_mode: str = "Markdown"
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Безопасное редактирование сообщения с обработкой ошибки 'message is not modified'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True если сообщение отредактировано, False если не изменилось
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text,
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
parse_mode=parse_mode
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except TelegramBadRequest as e:
|
||||||
|
if "message is not modified" in str(e):
|
||||||
|
await callback.answer("Сообщение уже актуально", show_alert=False)
|
||||||
|
return False
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
# Состояния для админки
|
# Состояния для админки
|
||||||
class AdminStates(StatesGroup):
|
class AdminStates(StatesGroup):
|
||||||
# Создание розыгрыша
|
# Создание розыгрыша
|
||||||
@@ -177,30 +205,73 @@ async def show_lottery_management(callback: CallbackQuery):
|
|||||||
@admin_router.callback_query(F.data == "admin_create_lottery")
|
@admin_router.callback_query(F.data == "admin_create_lottery")
|
||||||
async def start_create_lottery(callback: CallbackQuery, state: FSMContext):
|
async def start_create_lottery(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Начать создание розыгрыша"""
|
"""Начать создание розыгрыша"""
|
||||||
|
logging.info(f"🎯 Callback admin_create_lottery получен от пользователя {callback.from_user.id}")
|
||||||
|
|
||||||
|
# Сразу отвечаем на callback
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
if not is_admin(callback.from_user.id):
|
if not is_admin(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
logging.warning(f"⚠️ Пользователь {callback.from_user.id} не является админом")
|
||||||
|
await callback.message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
logging.info(f"✅ Админ {callback.from_user.id} начинает создание розыгрыша")
|
||||||
|
|
||||||
text = "📝 Создание нового розыгрыша\n\n"
|
text = "📝 Создание нового розыгрыша\n\n"
|
||||||
text += "Шаг 1 из 4\n\n"
|
text += "Шаг 1 из 4\n\n"
|
||||||
text += "Введите название розыгрыша:"
|
text += "Введите название розыгрыша:"
|
||||||
|
|
||||||
await callback.message.edit_text(
|
try:
|
||||||
text,
|
await callback.message.edit_text(
|
||||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
text,
|
||||||
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_lotteries")]
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||||
])
|
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_lotteries")]
|
||||||
)
|
])
|
||||||
await state.set_state(AdminStates.lottery_title)
|
)
|
||||||
|
await state.set_state(AdminStates.lottery_title)
|
||||||
|
logging.info(f"✅ Состояние установлено: AdminStates.lottery_title")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"❌ Ошибка при создании розыгрыша: {e}")
|
||||||
|
await callback.message.answer(f"❌ Ошибка: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
@admin_router.message(StateFilter(AdminStates.lottery_title))
|
@admin_router.message(StateFilter(AdminStates.lottery_title))
|
||||||
async def process_lottery_title(message: Message, state: FSMContext):
|
async def process_lottery_title(message: Message, state: FSMContext):
|
||||||
"""Обработка названия розыгрыша"""
|
"""Обработка названия розыгрыша (создание или редактирование)"""
|
||||||
if not is_admin(message.from_user.id):
|
if not is_admin(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
data = await state.get_data()
|
||||||
|
edit_lottery_id = data.get('edit_lottery_id')
|
||||||
|
|
||||||
|
# Если это редактирование существующего розыгрыша
|
||||||
|
if edit_lottery_id:
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
success = await LotteryService.update_lottery(
|
||||||
|
session,
|
||||||
|
edit_lottery_id,
|
||||||
|
title=message.text
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
await message.answer(f"✅ Название изменено на: {message.text}")
|
||||||
|
await state.clear()
|
||||||
|
# Возвращаемся к выбору полей
|
||||||
|
from aiogram.types import CallbackQuery
|
||||||
|
fake_callback = CallbackQuery(
|
||||||
|
id="fake",
|
||||||
|
from_user=message.from_user,
|
||||||
|
chat_instance="fake",
|
||||||
|
data=f"admin_edit_lottery_select_{edit_lottery_id}",
|
||||||
|
message=message
|
||||||
|
)
|
||||||
|
await choose_edit_field(fake_callback, state)
|
||||||
|
else:
|
||||||
|
await message.answer("❌ Ошибка при изменении названия")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Если это создание нового розыгрыша
|
||||||
await state.update_data(title=message.text)
|
await state.update_data(title=message.text)
|
||||||
|
|
||||||
text = f"📝 Создание нового розыгрыша\n\n"
|
text = f"📝 Создание нового розыгрыша\n\n"
|
||||||
@@ -214,11 +285,42 @@ async def process_lottery_title(message: Message, state: FSMContext):
|
|||||||
|
|
||||||
@admin_router.message(StateFilter(AdminStates.lottery_description))
|
@admin_router.message(StateFilter(AdminStates.lottery_description))
|
||||||
async def process_lottery_description(message: Message, state: FSMContext):
|
async def process_lottery_description(message: Message, state: FSMContext):
|
||||||
"""Обработка описания розыгрыша"""
|
"""Обработка описания розыгрыша (создание или редактирование)"""
|
||||||
if not is_admin(message.from_user.id):
|
if not is_admin(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
data = await state.get_data()
|
||||||
|
edit_lottery_id = data.get('edit_lottery_id')
|
||||||
|
|
||||||
|
# Если это редактирование существующего розыгрыша
|
||||||
|
if edit_lottery_id:
|
||||||
|
description = None if message.text == "-" else message.text
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
success = await LotteryService.update_lottery(
|
||||||
|
session,
|
||||||
|
edit_lottery_id,
|
||||||
|
description=description
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
await message.answer(f"✅ Описание изменено")
|
||||||
|
await state.clear()
|
||||||
|
# Возвращаемся к выбору полей
|
||||||
|
from aiogram.types import CallbackQuery
|
||||||
|
fake_callback = CallbackQuery(
|
||||||
|
id="fake",
|
||||||
|
from_user=message.from_user,
|
||||||
|
chat_instance="fake",
|
||||||
|
data=f"admin_edit_lottery_select_{edit_lottery_id}",
|
||||||
|
message=message
|
||||||
|
)
|
||||||
|
await choose_edit_field(fake_callback, state)
|
||||||
|
else:
|
||||||
|
await message.answer("❌ Ошибка при изменении описания")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Если это создание нового розыгрыша
|
||||||
description = None if message.text == "-" else message.text
|
description = None if message.text == "-" else message.text
|
||||||
await state.update_data(description=description)
|
await state.update_data(description=description)
|
||||||
|
|
||||||
@@ -241,12 +343,43 @@ async def process_lottery_description(message: Message, state: FSMContext):
|
|||||||
|
|
||||||
@admin_router.message(StateFilter(AdminStates.lottery_prizes))
|
@admin_router.message(StateFilter(AdminStates.lottery_prizes))
|
||||||
async def process_lottery_prizes(message: Message, state: FSMContext):
|
async def process_lottery_prizes(message: Message, state: FSMContext):
|
||||||
"""Обработка призов розыгрыша"""
|
"""Обработка призов розыгрыша (создание или редактирование)"""
|
||||||
if not is_admin(message.from_user.id):
|
if not is_admin(message.from_user.id):
|
||||||
await message.answer("❌ Недостаточно прав")
|
await message.answer("❌ Недостаточно прав")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
data = await state.get_data()
|
||||||
|
edit_lottery_id = data.get('edit_lottery_id')
|
||||||
|
|
||||||
prizes = [prize.strip() for prize in message.text.split('\n') if prize.strip()]
|
prizes = [prize.strip() for prize in message.text.split('\n') if prize.strip()]
|
||||||
|
|
||||||
|
# Если это редактирование существующего розыгрыша
|
||||||
|
if edit_lottery_id:
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
success = await LotteryService.update_lottery(
|
||||||
|
session,
|
||||||
|
edit_lottery_id,
|
||||||
|
prizes=prizes
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
await message.answer(f"✅ Призы изменены")
|
||||||
|
await state.clear()
|
||||||
|
# Возвращаемся к выбору полей
|
||||||
|
from aiogram.types import CallbackQuery
|
||||||
|
fake_callback = CallbackQuery(
|
||||||
|
id="fake",
|
||||||
|
from_user=message.from_user,
|
||||||
|
chat_instance="fake",
|
||||||
|
data=f"admin_edit_lottery_select_{edit_lottery_id}",
|
||||||
|
message=message
|
||||||
|
)
|
||||||
|
await choose_edit_field(fake_callback, state)
|
||||||
|
else:
|
||||||
|
await message.answer("❌ Ошибка при изменении призов")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Если это создание нового розыгрыша
|
||||||
await state.update_data(prizes=prizes)
|
await state.update_data(prizes=prizes)
|
||||||
|
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
@@ -1678,6 +1811,47 @@ async def start_edit_lottery(callback: CallbackQuery, state: FSMContext):
|
|||||||
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
|
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.callback_query(F.data.startswith("admin_edit_field_"))
|
||||||
|
async def handle_edit_field(callback: CallbackQuery, state: FSMContext):
|
||||||
|
"""Обработка выбора поля для редактирования"""
|
||||||
|
if not is_admin(callback.from_user.id):
|
||||||
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Парсим callback_data: admin_edit_field_{lottery_id}_{field_name}
|
||||||
|
parts = callback.data.split("_")
|
||||||
|
if len(parts) < 5:
|
||||||
|
await callback.answer("❌ Неверный формат данных", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
lottery_id = int(parts[3]) # admin_edit_field_{lottery_id}_...
|
||||||
|
field_name = "_".join(parts[4:]) # Всё после lottery_id это имя поля
|
||||||
|
|
||||||
|
await state.update_data(edit_lottery_id=lottery_id, edit_field=field_name)
|
||||||
|
|
||||||
|
# Определяем, что редактируем
|
||||||
|
if field_name == "title":
|
||||||
|
text = "📝 Введите новое название розыгрыша:"
|
||||||
|
await state.set_state(AdminStates.lottery_title)
|
||||||
|
elif field_name == "description":
|
||||||
|
text = "📄 Введите новое описание розыгрыша:"
|
||||||
|
await state.set_state(AdminStates.lottery_description)
|
||||||
|
elif field_name == "prizes":
|
||||||
|
text = "🎁 Введите новый список призов (каждый приз с новой строки):"
|
||||||
|
await state.set_state(AdminStates.lottery_prizes)
|
||||||
|
else:
|
||||||
|
await callback.answer("❌ Неизвестное поле", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text,
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="❌ Отмена", callback_data="admin_lotteries")]
|
||||||
|
])
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
@admin_router.callback_query(F.data.startswith("admin_edit_"))
|
@admin_router.callback_query(F.data.startswith("admin_edit_"))
|
||||||
async def redirect_to_edit_lottery(callback: CallbackQuery, state: FSMContext):
|
async def redirect_to_edit_lottery(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Редирект на редактирование розыгрыша из детального просмотра"""
|
"""Редирект на редактирование розыгрыша из детального просмотра"""
|
||||||
@@ -1736,7 +1910,7 @@ async def choose_edit_field(callback: CallbackQuery, state: FSMContext):
|
|||||||
|
|
||||||
|
|
||||||
@admin_router.callback_query(F.data.startswith("admin_toggle_active_"))
|
@admin_router.callback_query(F.data.startswith("admin_toggle_active_"))
|
||||||
async def toggle_lottery_active(callback: CallbackQuery):
|
async def toggle_lottery_active(callback: CallbackQuery, state: FSMContext):
|
||||||
"""Переключить активность розыгрыша"""
|
"""Переключить активность розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not is_admin(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
@@ -1758,7 +1932,7 @@ async def toggle_lottery_active(callback: CallbackQuery):
|
|||||||
await callback.answer("❌ Ошибка изменения статуса", show_alert=True)
|
await callback.answer("❌ Ошибка изменения статуса", show_alert=True)
|
||||||
|
|
||||||
# Обновляем отображение
|
# Обновляем отображение
|
||||||
await choose_edit_field(callback, None)
|
await choose_edit_field(callback, state)
|
||||||
|
|
||||||
|
|
||||||
@admin_router.callback_query(F.data == "admin_finish_lottery")
|
@admin_router.callback_query(F.data == "admin_finish_lottery")
|
||||||
@@ -2592,7 +2766,7 @@ async def choose_lottery_for_draw(callback: CallbackQuery):
|
|||||||
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
|
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
|
||||||
|
|
||||||
|
|
||||||
@admin_router.callback_query(F.data.startswith("admin_conduct_"))
|
@admin_router.callback_query(F.data.regexp(r"^admin_conduct_\d+$"))
|
||||||
async def conduct_lottery_draw_confirm(callback: CallbackQuery):
|
async def conduct_lottery_draw_confirm(callback: CallbackQuery):
|
||||||
"""Запрос подтверждения проведения розыгрыша"""
|
"""Запрос подтверждения проведения розыгрыша"""
|
||||||
if not is_admin(callback.from_user.id):
|
if not is_admin(callback.from_user.id):
|
||||||
@@ -2622,39 +2796,48 @@ async def conduct_lottery_draw_confirm(callback: CallbackQuery):
|
|||||||
prizes_count = len(lottery.prizes) if lottery.prizes else 0
|
prizes_count = len(lottery.prizes) if lottery.prizes else 0
|
||||||
|
|
||||||
# Формируем сообщение с подтверждением
|
# Формируем сообщение с подтверждением
|
||||||
text = f"⚠️ <b>Подтверждение проведения розыгрыша</b>\n\n"
|
text = f"⚠️ *Подтверждение проведения розыгрыша*\n\n"
|
||||||
text += f"🎲 <b>Розыгрыш:</b> {lottery.title}\n"
|
text += f"🎲 *Розыгрыш:* {lottery.title}\n"
|
||||||
text += f"👥 <b>Участников:</b> {participants_count}\n"
|
text += f"👥 *Участников:* {participants_count}\n"
|
||||||
text += f"🏆 <b>Призов:</b> {prizes_count}\n\n"
|
text += f"🏆 *Призов:* {prizes_count}\n\n"
|
||||||
|
|
||||||
if lottery.prizes:
|
if lottery.prizes:
|
||||||
text += "<b>Призы:</b>\n"
|
text += "*Призы:*\n"
|
||||||
for i, prize in enumerate(lottery.prizes, 1):
|
for i, prize in enumerate(lottery.prizes, 1):
|
||||||
text += f"{i}. {prize}\n"
|
text += f"{i}. {prize}\n"
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
|
||||||
text += "❗️ <b>Внимание:</b> После проведения розыгрыша результаты нельзя будет изменить!\n\n"
|
text += "❗️ *Внимание:* После проведения розыгрыша результаты нельзя будет изменить!\n\n"
|
||||||
text += "Продолжить?"
|
text += "Продолжить?"
|
||||||
|
|
||||||
|
confirm_callback = f"admin_conduct_confirmed_{lottery_id}"
|
||||||
|
logger.info(f"Создаём кнопку подтверждения с callback_data='{confirm_callback}'")
|
||||||
|
|
||||||
buttons = [
|
buttons = [
|
||||||
[InlineKeyboardButton(text="✅ Да, провести розыгрыш", callback_data=f"admin_conduct_confirmed_{lottery_id}")],
|
[InlineKeyboardButton(text="✅ Да, провести розыгрыш", callback_data=confirm_callback)],
|
||||||
[InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_lottery_{lottery_id}")]
|
[InlineKeyboardButton(text="❌ Отмена", callback_data=f"admin_lottery_{lottery_id}")]
|
||||||
]
|
]
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons))
|
await safe_edit_message(callback, text, InlineKeyboardMarkup(inline_keyboard=buttons))
|
||||||
|
|
||||||
|
|
||||||
@admin_router.callback_query(F.data.startswith("admin_conduct_confirmed_"))
|
@admin_router.callback_query(F.data.startswith("admin_conduct_confirmed_"))
|
||||||
async def conduct_lottery_draw(callback: CallbackQuery):
|
async def conduct_lottery_draw(callback: CallbackQuery):
|
||||||
"""Проведение розыгрыша после подтверждения"""
|
"""Проведение розыгрыша после подтверждения"""
|
||||||
|
logger.info(f"🎯 conduct_lottery_draw HANDLER TRIGGERED! data={callback.data}, user={callback.from_user.id}")
|
||||||
|
logger.info(f"conduct_lottery_draw вызван: callback.data={callback.data}, user_id={callback.from_user.id}")
|
||||||
|
|
||||||
if not is_admin(callback.from_user.id):
|
if not is_admin(callback.from_user.id):
|
||||||
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
lottery_id = int(callback.data.split("_")[-1])
|
lottery_id = int(callback.data.split("_")[-1])
|
||||||
|
logger.info(f"Извлечен lottery_id={lottery_id}")
|
||||||
|
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
|
logger.info(f"Создана сессия БД")
|
||||||
lottery = await LotteryService.get_lottery(session, lottery_id)
|
lottery = await LotteryService.get_lottery(session, lottery_id)
|
||||||
|
logger.info(f"Получен lottery: {lottery.title if lottery else None}, is_completed={lottery.is_completed if lottery else None}")
|
||||||
|
|
||||||
if not lottery:
|
if not lottery:
|
||||||
await callback.answer("Розыгрыш не найден", show_alert=True)
|
await callback.answer("Розыгрыш не найден", show_alert=True)
|
||||||
@@ -2674,9 +2857,21 @@ async def conduct_lottery_draw(callback: CallbackQuery):
|
|||||||
await callback.answer("⏳ Проводится розыгрыш...", show_alert=True)
|
await callback.answer("⏳ Проводится розыгрыш...", show_alert=True)
|
||||||
|
|
||||||
# Проводим розыгрыш через сервис
|
# Проводим розыгрыш через сервис
|
||||||
winners_dict = await LotteryService.conduct_draw(session, lottery_id)
|
logger.info(f"Начинаем проведение розыгрыша {lottery_id}")
|
||||||
|
try:
|
||||||
|
winners_dict = await LotteryService.conduct_draw(session, lottery_id)
|
||||||
|
logger.info(f"Розыгрыш {lottery_id} проведён, победителей: {len(winners_dict)}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при проведении розыгрыша {lottery_id}: {e}", exc_info=True)
|
||||||
|
await session.rollback()
|
||||||
|
await callback.answer(f"❌ Ошибка: {e}", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
if winners_dict:
|
if winners_dict:
|
||||||
|
# Коммитим изменения в БД
|
||||||
|
await session.commit()
|
||||||
|
logger.info(f"Изменения закоммичены для розыгрыша {lottery_id}")
|
||||||
|
|
||||||
# Отправляем уведомления победителям
|
# Отправляем уведомления победителям
|
||||||
from ..utils.notifications import notify_winners_async
|
from ..utils.notifications import notify_winners_async
|
||||||
try:
|
try:
|
||||||
@@ -3218,5 +3413,286 @@ async def apply_display_type(callback: CallbackQuery, state: FSMContext):
|
|||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
# ============= УПРАВЛЕНИЕ СООБЩЕНИЯМИ ПОЛЬЗОВАТЕЛЕЙ =============
|
||||||
|
|
||||||
|
@admin_router.callback_query(F.data == "admin_messages")
|
||||||
|
async def show_messages_menu(callback: CallbackQuery):
|
||||||
|
"""Показать меню управления сообщениями"""
|
||||||
|
if not is_admin(callback.from_user.id):
|
||||||
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = "💬 *Управление сообщениями пользователей*\n\n"
|
||||||
|
text += "Здесь вы можете просматривать и удалять сообщения пользователей.\n\n"
|
||||||
|
text += "Выберите действие:"
|
||||||
|
|
||||||
|
buttons = [
|
||||||
|
[InlineKeyboardButton(text="📋 Последние сообщения", callback_data="admin_messages_recent")],
|
||||||
|
[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_panel")]
|
||||||
|
]
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text,
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.callback_query(F.data == "admin_messages_recent")
|
||||||
|
async def show_recent_messages(callback: CallbackQuery, page: int = 0):
|
||||||
|
"""Показать последние сообщения"""
|
||||||
|
if not is_admin(callback.from_user.id):
|
||||||
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
limit = 10
|
||||||
|
offset = page * limit
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
messages = await ChatMessageService.get_user_messages_all(
|
||||||
|
session,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
include_deleted=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if not messages:
|
||||||
|
text = "💬 Нет сообщений для отображения"
|
||||||
|
buttons = [[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_messages")]]
|
||||||
|
else:
|
||||||
|
text = f"💬 *Последние сообщения*\n\n"
|
||||||
|
|
||||||
|
# Добавляем кнопки для просмотра сообщений
|
||||||
|
buttons = []
|
||||||
|
for msg in messages:
|
||||||
|
sender = msg.sender
|
||||||
|
username = f"@{sender.username}" if sender.username else f"ID{sender.telegram_id}"
|
||||||
|
msg_preview = ""
|
||||||
|
if msg.text:
|
||||||
|
msg_preview = msg.text[:20] + "..." if len(msg.text) > 20 else msg.text
|
||||||
|
else:
|
||||||
|
msg_preview = msg.message_type
|
||||||
|
|
||||||
|
buttons.append([InlineKeyboardButton(
|
||||||
|
text=f"👁 {username}: {msg_preview}",
|
||||||
|
callback_data=f"admin_message_view_{msg.id}"
|
||||||
|
)])
|
||||||
|
|
||||||
|
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_messages")])
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text,
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.callback_query(F.data.startswith("admin_message_view_"))
|
||||||
|
async def view_message(callback: CallbackQuery):
|
||||||
|
"""Просмотр конкретного сообщения"""
|
||||||
|
if not is_admin(callback.from_user.id):
|
||||||
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
message_id = int(callback.data.split("_")[-1])
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
msg = await ChatMessageService.get_message(session, message_id)
|
||||||
|
|
||||||
|
if not msg:
|
||||||
|
await callback.answer("❌ Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
sender = msg.sender
|
||||||
|
username = f"@{sender.username}" if sender.username else f"ID: {sender.telegram_id}"
|
||||||
|
|
||||||
|
text = f"💬 *Просмотр сообщения*\n\n"
|
||||||
|
text += f"👤 Отправитель: {username}\n"
|
||||||
|
text += f"🆔 Telegram ID: `{sender.telegram_id}`\n"
|
||||||
|
text += f"📝 Тип: {msg.message_type}\n"
|
||||||
|
text += f"📅 Дата: {msg.created_at.strftime('%d.%m.%Y %H:%M:%S')}\n\n"
|
||||||
|
|
||||||
|
if msg.text:
|
||||||
|
text += f"📄 *Текст:*\n{msg.text}\n\n"
|
||||||
|
|
||||||
|
if msg.file_id:
|
||||||
|
text += f"📎 File ID: `{msg.file_id}`\n\n"
|
||||||
|
|
||||||
|
if msg.is_deleted:
|
||||||
|
text += f"🗑 *Удалено:* Да\n"
|
||||||
|
if msg.deleted_at:
|
||||||
|
text += f" Дата: {msg.deleted_at.strftime('%d.%m.%Y %H:%M')}\n"
|
||||||
|
|
||||||
|
buttons = []
|
||||||
|
|
||||||
|
# Кнопка удаления (если еще не удалено)
|
||||||
|
if not msg.is_deleted:
|
||||||
|
buttons.append([InlineKeyboardButton(
|
||||||
|
text="🗑 Удалить сообщение",
|
||||||
|
callback_data=f"admin_message_delete_{message_id}"
|
||||||
|
)])
|
||||||
|
|
||||||
|
# Кнопка для просмотра всех сообщений пользователя
|
||||||
|
buttons.append([InlineKeyboardButton(
|
||||||
|
text="📋 Все сообщения пользователя",
|
||||||
|
callback_data=f"admin_messages_user_{sender.id}"
|
||||||
|
)])
|
||||||
|
|
||||||
|
buttons.append([InlineKeyboardButton(text="🔙 К списку", callback_data="admin_messages_recent")])
|
||||||
|
|
||||||
|
# Если сообщение содержит медиа, попробуем его показать
|
||||||
|
if msg.file_id and msg.message_type in ['photo', 'video', 'document', 'animation']:
|
||||||
|
try:
|
||||||
|
if msg.message_type == 'photo':
|
||||||
|
await callback.message.answer_photo(
|
||||||
|
photo=msg.file_id,
|
||||||
|
caption=text,
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
await callback.message.delete()
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
elif msg.message_type == 'video':
|
||||||
|
await callback.message.answer_video(
|
||||||
|
video=msg.file_id,
|
||||||
|
caption=text,
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
await callback.message.delete()
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при отправке медиа: {e}")
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text,
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.callback_query(F.data.startswith("admin_message_delete_"))
|
||||||
|
async def delete_message(callback: CallbackQuery):
|
||||||
|
"""Удалить сообщение пользователя"""
|
||||||
|
if not is_admin(callback.from_user.id):
|
||||||
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
message_id = int(callback.data.split("_")[-1])
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
msg = await ChatMessageService.get_message(session, message_id)
|
||||||
|
|
||||||
|
if not msg:
|
||||||
|
await callback.answer("❌ Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Получаем админа
|
||||||
|
admin = await UserService.get_or_create_user(
|
||||||
|
session,
|
||||||
|
callback.from_user.id,
|
||||||
|
callback.from_user.username,
|
||||||
|
callback.from_user.first_name,
|
||||||
|
callback.from_user.last_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Помечаем сообщение как удаленное
|
||||||
|
success = await ChatMessageService.mark_as_deleted(
|
||||||
|
session,
|
||||||
|
message_id,
|
||||||
|
admin.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Пытаемся удалить сообщение из чата пользователя
|
||||||
|
try:
|
||||||
|
if msg.forwarded_message_ids:
|
||||||
|
# Удаляем пересланные копии у всех пользователей
|
||||||
|
for user_tg_id, tg_msg_id in msg.forwarded_message_ids.items():
|
||||||
|
try:
|
||||||
|
await callback.bot.delete_message(
|
||||||
|
chat_id=int(user_tg_id),
|
||||||
|
message_id=tg_msg_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Не удалось удалить сообщение {tg_msg_id} у пользователя {user_tg_id}: {e}")
|
||||||
|
|
||||||
|
# Удаляем оригинальное сообщение у отправителя
|
||||||
|
try:
|
||||||
|
await callback.bot.delete_message(
|
||||||
|
chat_id=msg.sender.telegram_id,
|
||||||
|
message_id=msg.telegram_message_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Не удалось удалить оригинальное сообщение: {e}")
|
||||||
|
|
||||||
|
await callback.answer("✅ Сообщение удалено!", show_alert=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при удалении сообщений: {e}")
|
||||||
|
await callback.answer("⚠️ Помечено как удаленное", show_alert=True)
|
||||||
|
else:
|
||||||
|
await callback.answer("❌ Ошибка при удалении", show_alert=True)
|
||||||
|
|
||||||
|
# Возвращаемся к списку
|
||||||
|
await show_recent_messages(callback, 0)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.callback_query(F.data.startswith("admin_messages_user_"))
|
||||||
|
async def show_user_messages(callback: CallbackQuery):
|
||||||
|
"""Показать все сообщения конкретного пользователя"""
|
||||||
|
if not is_admin(callback.from_user.id):
|
||||||
|
await callback.answer("❌ Недостаточно прав", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = int(callback.data.split("_")[-1])
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
user = await UserService.get_user_by_id(session, user_id)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
await callback.answer("❌ Пользователь не найден", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
messages = await ChatMessageService.get_user_messages(
|
||||||
|
session,
|
||||||
|
user_id,
|
||||||
|
limit=20,
|
||||||
|
include_deleted=True
|
||||||
|
)
|
||||||
|
|
||||||
|
username = f"@{user.username}" if user.username else f"ID: {user.telegram_id}"
|
||||||
|
|
||||||
|
text = f"💬 *Сообщения {username}*\n\n"
|
||||||
|
|
||||||
|
if not messages:
|
||||||
|
text += "Нет сообщений"
|
||||||
|
buttons = [[InlineKeyboardButton(text="🔙 Назад", callback_data="admin_messages_recent")]]
|
||||||
|
else:
|
||||||
|
# Кнопки для просмотра отдельных сообщений
|
||||||
|
buttons = []
|
||||||
|
for msg in messages[:15]:
|
||||||
|
status = "🗑" if msg.is_deleted else "✅"
|
||||||
|
msg_preview = ""
|
||||||
|
if msg.text:
|
||||||
|
msg_preview = msg.text[:25] + "..." if len(msg.text) > 25 else msg.text
|
||||||
|
else:
|
||||||
|
msg_preview = msg.message_type
|
||||||
|
|
||||||
|
buttons.append([InlineKeyboardButton(
|
||||||
|
text=f"{status} {msg_preview} ({msg.created_at.strftime('%d.%m %H:%M')})",
|
||||||
|
callback_data=f"admin_message_view_{msg.id}"
|
||||||
|
)])
|
||||||
|
|
||||||
|
buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="admin_messages_recent")])
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
text,
|
||||||
|
reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons),
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Экспорт роутера
|
# Экспорт роутера
|
||||||
__all__ = ['admin_router']
|
__all__ = ['admin_router']
|
||||||
@@ -109,6 +109,73 @@ async def handle_text_message(message: Message):
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.info(f"[CHAT] handle_text_message вызван: user={message.from_user.id}, text={message.text[:50] if message.text else 'None'}")
|
logger.info(f"[CHAT] handle_text_message вызван: user={message.from_user.id}, text={message.text[:50] if message.text else 'None'}")
|
||||||
|
|
||||||
|
# БЫСТРОЕ УДАЛЕНИЕ: Если админ отвечает на сообщение словом "удалить"/"del"/"-"
|
||||||
|
if message.reply_to_message and is_admin(message.from_user.id):
|
||||||
|
if message.text and message.text.lower().strip() in ['удалить', 'del', '-']:
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
# Ищем сообщение в БД по telegram_message_id
|
||||||
|
msg_to_delete = await ChatMessageService.get_message_by_telegram_id(
|
||||||
|
session,
|
||||||
|
telegram_message_id=message.reply_to_message.message_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if msg_to_delete:
|
||||||
|
# Получаем админа
|
||||||
|
admin = await UserService.get_user_by_telegram_id(
|
||||||
|
session,
|
||||||
|
message.from_user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Помечаем как удаленное
|
||||||
|
success = await ChatMessageService.mark_as_deleted(
|
||||||
|
session,
|
||||||
|
msg_to_delete.id,
|
||||||
|
admin.id if admin else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Удаляем у всех получателей
|
||||||
|
deleted_count = 0
|
||||||
|
if msg_to_delete.forwarded_message_ids:
|
||||||
|
for user_tg_id, tg_msg_id in msg_to_delete.forwarded_message_ids.items():
|
||||||
|
try:
|
||||||
|
await message.bot.delete_message(
|
||||||
|
chat_id=int(user_tg_id),
|
||||||
|
message_id=tg_msg_id
|
||||||
|
)
|
||||||
|
deleted_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Не удалось удалить {tg_msg_id} у {user_tg_id}: {e}")
|
||||||
|
|
||||||
|
# Удаляем оригинал у отправителя
|
||||||
|
try:
|
||||||
|
await message.bot.delete_message(
|
||||||
|
chat_id=msg_to_delete.sender.telegram_id,
|
||||||
|
message_id=msg_to_delete.telegram_message_id
|
||||||
|
)
|
||||||
|
deleted_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Не удалось удалить оригинал: {e}")
|
||||||
|
|
||||||
|
# Удаляем команду админа
|
||||||
|
try:
|
||||||
|
await message.delete()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Отправляем уведомление (самоудаляющееся)
|
||||||
|
notification = await message.answer(f"✅ Сообщение удалено у {deleted_count} получателей")
|
||||||
|
await asyncio.sleep(3)
|
||||||
|
try:
|
||||||
|
await notification.delete()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
await message.answer("❌ Сообщение не найдено в БД")
|
||||||
|
return
|
||||||
|
|
||||||
# Проверяем является ли это командой
|
# Проверяем является ли это командой
|
||||||
if message.text and message.text.startswith('/'):
|
if message.text and message.text.startswith('/'):
|
||||||
# Список команд, которые НЕ нужно пересылать
|
# Список команд, которые НЕ нужно пересылать
|
||||||
@@ -123,21 +190,20 @@ async def handle_text_message(message: Message):
|
|||||||
# Извлекаем команду (первое слово)
|
# Извлекаем команду (первое слово)
|
||||||
command = message.text.split()[0] if message.text else ''
|
command = message.text.split()[0] if message.text else ''
|
||||||
|
|
||||||
# Если это пользовательская команда - пропускаем, она будет обработана другими обработчиками
|
# ИЗМЕНЕНИЕ: Если это команда от АДМИНА - не пересылаем (админ сам её видит)
|
||||||
if command in user_commands:
|
if is_admin(message.from_user.id):
|
||||||
return
|
# Если это админская команда - пропускаем, она будет обработана другими обработчиками
|
||||||
|
if command in admin_commands:
|
||||||
# Если это админская команда
|
|
||||||
if command in admin_commands:
|
|
||||||
# Проверяем права админа
|
|
||||||
if not is_admin(message.from_user.id):
|
|
||||||
await message.answer("❌ У вас нет прав для выполнения этой команды")
|
|
||||||
return
|
return
|
||||||
# Если админ - команда будет обработана другими обработчиками, пропускаем пересылку
|
# Если это пользовательская команда от админа - тоже пропускаем
|
||||||
|
if command in user_commands:
|
||||||
|
return
|
||||||
|
# Любая другая команда от админа - тоже не пересылаем
|
||||||
return
|
return
|
||||||
|
|
||||||
# Если неизвестная команда - тоже не пересылаем
|
# ИЗМЕНЕНИЕ: Если команда от обычного пользователя - ПЕРЕСЫЛАЕМ админу
|
||||||
return
|
# Чтобы админ видел, что пользователь отправил /start или другую команду
|
||||||
|
# НЕ делаем return, продолжаем выполнение для пересылки
|
||||||
|
|
||||||
async with async_session_maker() as session:
|
async with async_session_maker() as session:
|
||||||
# Проверяем права на отправку
|
# Проверяем права на отправку
|
||||||
@@ -163,7 +229,8 @@ async def handle_text_message(message: Message):
|
|||||||
# Обрабатываем в зависимости от режима
|
# Обрабатываем в зависимости от режима
|
||||||
if settings.mode == 'broadcast':
|
if settings.mode == 'broadcast':
|
||||||
# Режим рассылки с планировщиком
|
# Режим рассылки с планировщиком
|
||||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
# НЕ исключаем отправителя - админ должен видеть все сообщения
|
||||||
|
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None)
|
||||||
|
|
||||||
# Сохраняем сообщение в историю
|
# Сохраняем сообщение в историю
|
||||||
await ChatMessageService.save_message(
|
await ChatMessageService.save_message(
|
||||||
@@ -231,7 +298,12 @@ async def handle_photo_message(message: Message):
|
|||||||
photo = message.photo[-1]
|
photo = message.photo[-1]
|
||||||
|
|
||||||
if settings.mode == 'broadcast':
|
if settings.mode == 'broadcast':
|
||||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
# Отправляем только админам
|
||||||
|
forwarded_ids, success, fail = await broadcast_message_with_scheduler(
|
||||||
|
message,
|
||||||
|
exclude_user_id=message.from_user.id,
|
||||||
|
admin_only=True
|
||||||
|
)
|
||||||
|
|
||||||
await ChatMessageService.save_message(
|
await ChatMessageService.save_message(
|
||||||
session,
|
session,
|
||||||
@@ -245,7 +317,7 @@ async def handle_photo_message(message: Message):
|
|||||||
|
|
||||||
# Показываем статистику только админам
|
# Показываем статистику только админам
|
||||||
if is_admin(message.from_user.id):
|
if is_admin(message.from_user.id):
|
||||||
await message.answer(f"✅ Фото разослано: {success} получателей")
|
await message.answer(f"✅ Фото отправлено админам: {success}")
|
||||||
|
|
||||||
elif settings.mode == 'forward':
|
elif settings.mode == 'forward':
|
||||||
if settings.forward_chat_id:
|
if settings.forward_chat_id:
|
||||||
@@ -285,7 +357,8 @@ async def handle_video_message(message: Message):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if settings.mode == 'broadcast':
|
if settings.mode == 'broadcast':
|
||||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
# НЕ исключаем отправителя - админ должен видеть все сообщения
|
||||||
|
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None)
|
||||||
|
|
||||||
await ChatMessageService.save_message(
|
await ChatMessageService.save_message(
|
||||||
session,
|
session,
|
||||||
@@ -339,7 +412,8 @@ async def handle_document_message(message: Message):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if settings.mode == 'broadcast':
|
if settings.mode == 'broadcast':
|
||||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
# НЕ исключаем отправителя - админ должен видеть все сообщения
|
||||||
|
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None)
|
||||||
|
|
||||||
await ChatMessageService.save_message(
|
await ChatMessageService.save_message(
|
||||||
session,
|
session,
|
||||||
@@ -393,7 +467,8 @@ async def handle_animation_message(message: Message):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if settings.mode == 'broadcast':
|
if settings.mode == 'broadcast':
|
||||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
# НЕ исключаем отправителя - админ должен видеть все сообщения
|
||||||
|
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None)
|
||||||
|
|
||||||
await ChatMessageService.save_message(
|
await ChatMessageService.save_message(
|
||||||
session,
|
session,
|
||||||
@@ -447,7 +522,8 @@ async def handle_sticker_message(message: Message):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if settings.mode == 'broadcast':
|
if settings.mode == 'broadcast':
|
||||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
# НЕ исключаем отправителя - админ должен видеть все сообщения
|
||||||
|
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None)
|
||||||
|
|
||||||
await ChatMessageService.save_message(
|
await ChatMessageService.save_message(
|
||||||
session,
|
session,
|
||||||
@@ -480,51 +556,19 @@ async def handle_sticker_message(message: Message):
|
|||||||
|
|
||||||
@router.message(F.voice)
|
@router.message(F.voice)
|
||||||
async def handle_voice_message(message: Message):
|
async def handle_voice_message(message: Message):
|
||||||
"""Обработчик голосовых сообщений"""
|
"""Обработчик голосовых сообщений - ЗАБЛОКИРОВАНО"""
|
||||||
async with async_session_maker() as session:
|
await message.answer(
|
||||||
can_send, reason = await ChatPermissionService.can_send_message(
|
"🚫 Голосовые сообщения запрещены.\n\n"
|
||||||
session,
|
"Пожалуйста, используйте текстовые сообщения или изображения."
|
||||||
message.from_user.id,
|
)
|
||||||
is_admin=is_admin(message.from_user.id)
|
return
|
||||||
)
|
|
||||||
|
|
||||||
if not can_send:
|
@router.message(F.audio)
|
||||||
await message.answer(f"❌ {reason}")
|
async def handle_audio_message(message: Message):
|
||||||
return
|
"""Обработчик аудиофайлов (музыка, аудиозаписи) - ЗАБЛОКИРОВАНО"""
|
||||||
|
await message.answer(
|
||||||
settings = await ChatSettingsService.get_or_create_settings(session)
|
"🚫 Аудиофайлы запрещены.\n\n"
|
||||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
"Пожалуйста, используйте текстовые сообщения или изображения."
|
||||||
|
)
|
||||||
if not user:
|
return
|
||||||
return
|
|
||||||
|
|
||||||
if settings.mode == 'broadcast':
|
|
||||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
|
||||||
|
|
||||||
await ChatMessageService.save_message(
|
|
||||||
session,
|
|
||||||
user_id=user.id,
|
|
||||||
telegram_message_id=message.message_id,
|
|
||||||
message_type='voice',
|
|
||||||
file_id=message.voice.file_id,
|
|
||||||
forwarded_ids=forwarded_ids
|
|
||||||
)
|
|
||||||
|
|
||||||
# Показываем статистику только админам
|
|
||||||
if is_admin(message.from_user.id):
|
|
||||||
await message.answer(f"✅ Голосовое сообщение разослано: {success} получателей")
|
|
||||||
|
|
||||||
elif settings.mode == 'forward':
|
|
||||||
if settings.forward_chat_id:
|
|
||||||
success, channel_msg_id = await forward_to_channel(message, settings.forward_chat_id)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
await ChatMessageService.save_message(
|
|
||||||
session,
|
|
||||||
user_id=user.id,
|
|
||||||
telegram_message_id=message.message_id,
|
|
||||||
message_type='voice',
|
|
||||||
file_id=message.voice.file_id,
|
|
||||||
forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None
|
|
||||||
)
|
|
||||||
await message.answer("✅ Голосовое сообщение переслано в канал")
|
|
||||||
|
|||||||
@@ -304,3 +304,97 @@ async def redraw_lottery(message: Message):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await message.answer(f"❌ Ошибка: {str(e)}")
|
await message.answer(f"❌ Ошибка: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("confirm_win_"))
|
||||||
|
async def confirm_winner_callback(callback_query):
|
||||||
|
"""Обработка подтверждения выигрыша победителем"""
|
||||||
|
from aiogram.types import CallbackQuery
|
||||||
|
|
||||||
|
winner_id = int(callback_query.data.split("_")[-1])
|
||||||
|
|
||||||
|
async with async_session_maker() as session:
|
||||||
|
# Получаем информацию о победителе
|
||||||
|
winner_result = await session.execute(
|
||||||
|
select(Winner).where(Winner.id == winner_id)
|
||||||
|
)
|
||||||
|
winner = winner_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not winner:
|
||||||
|
await callback_query.answer("❌ Победитель не найден", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if winner.is_claimed:
|
||||||
|
await callback_query.answer(
|
||||||
|
"✅ Этот выигрыш уже подтвержден!",
|
||||||
|
show_alert=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Проверяем, что пользователь является владельцем счёта
|
||||||
|
if winner.account_number:
|
||||||
|
owner = await AccountService.get_account_owner(session, winner.account_number)
|
||||||
|
if not owner or owner.telegram_id != callback_query.from_user.id:
|
||||||
|
await callback_query.answer(
|
||||||
|
"❌ Вы не являетесь владельцем этого счёта",
|
||||||
|
show_alert=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Проверяем срок действия (24 часа с момента создания winner)
|
||||||
|
if winner.created_at:
|
||||||
|
time_since_creation = datetime.now(timezone.utc) - winner.created_at
|
||||||
|
if time_since_creation > timedelta(hours=24):
|
||||||
|
await callback_query.answer(
|
||||||
|
"❌ Срок подтверждения истёк (24 часа). Приз будет разыгран заново.",
|
||||||
|
show_alert=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Подтверждаем выигрыш
|
||||||
|
winner.is_claimed = True
|
||||||
|
winner.claimed_at = datetime.now(timezone.utc)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
# Получаем данные о розыгрыше
|
||||||
|
lottery = await LotteryService.get_lottery(session, winner.lottery_id)
|
||||||
|
|
||||||
|
# Отправляем подтверждение пользователю
|
||||||
|
confirmation_text = (
|
||||||
|
f"✅ **Выигрыш подтвержден!**\n\n"
|
||||||
|
f"🎯 Розыгрыш: {lottery.title}\n"
|
||||||
|
f"🏆 Место: {winner.place}\n"
|
||||||
|
f"🎁 Приз: {winner.prize}\n"
|
||||||
|
f"💳 Счет: {winner.account_number}\n\n"
|
||||||
|
f"📞 С вами свяжется администратор для вручения приза.\n"
|
||||||
|
f"Спасибо за участие!"
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback_query.message.edit_text(
|
||||||
|
confirmation_text,
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Уведомляем админов
|
||||||
|
for admin_id in ADMIN_IDS:
|
||||||
|
try:
|
||||||
|
admin_text = (
|
||||||
|
f"✅ **Подтверждение выигрыша**\n\n"
|
||||||
|
f"👤 Пользователь: {callback_query.from_user.full_name} "
|
||||||
|
f"(@{callback_query.from_user.username or 'нет username'})\n"
|
||||||
|
f"🎯 Розыгрыш: {lottery.title}\n"
|
||||||
|
f"🏆 Место: {winner.place}\n"
|
||||||
|
f"🎁 Приз: {winner.prize}\n"
|
||||||
|
f"💳 Счет: {winner.account_number}"
|
||||||
|
)
|
||||||
|
|
||||||
|
from aiogram import Bot
|
||||||
|
from src.core.config import BOT_TOKEN
|
||||||
|
bot = Bot(token=BOT_TOKEN)
|
||||||
|
await bot.send_message(admin_id, admin_text, parse_mode="Markdown")
|
||||||
|
except Exception as e:
|
||||||
|
import logging
|
||||||
|
logging.getLogger(__name__).error(f"Ошибка отправки админу {admin_id}: {e}")
|
||||||
|
|
||||||
|
await callback_query.answer("✅ Выигрыш подтвержден!", show_alert=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
2521 11-22-33-44-55-66-77
|
|
||||||
2521 12-23-34-45-56-67-78
|
|
||||||
2521 13-24-35-46-57-68-79
|
|
||||||
2521 14-25-36-47-58-69-80
|
|
||||||
2521 15-26-37-48-59-70-81
|
|
||||||
2521 16-27-38-49-60-71-82
|
|
||||||
2521 17-28-39-50-61-72-83
|
|
||||||
2521 18-29-40-51-62-73-84
|
|
||||||
2521 19-30-41-52-63-74-85
|
|
||||||
2521 20-31-42-53-64-75-86
|
|
||||||
|
|
||||||
2522 21-32-43-54-65-76-87
|
|
||||||
2522 22-33-44-55-66-77-88
|
|
||||||
2522 23-34-45-56-67-78-89
|
|
||||||
2522 24-35-46-57-68-79-90
|
|
||||||
2522 25-36-47-58-69-80-91
|
|
||||||
2522 26-37-48-59-70-81-92
|
|
||||||
2522 27-38-49-60-71-82-93
|
|
||||||
2522 28-39-50-61-72-83-94
|
|
||||||
2522 29-40-51-62-73-84-95
|
|
||||||
2522 30-41-52-63-74-85-96
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
2524 13-44-65-38-31-54-67
|
|
||||||
2523 31-91-70-64-88-67-03
|
|
||||||
2525 21-87-28-91-13-49-61
|
|
||||||
2523 35-22-65-25-15-99-32
|
|
||||||
2525 12-72-37-11-82-58-23
|
|
||||||
2525 96-39-53-66-81-43-28
|
|
||||||
2522 31-19-65-97-82-87-06
|
|
||||||
2521 54-03-08-21-52-27-86
|
|
||||||
2525 42-85-32-06-39-68-81
|
|
||||||
2522 94-50-44-81-24-67-25
|
|
||||||
28-66-94-77-24-23-40
|
|
||||||
72-64-73-89-62-11-90
|
|
||||||
2522 12-25-21-03-46-98-22
|
|
||||||
2524 54-06-23-93-94-44-50
|
|
||||||
2523 23-61-39-40-29-15-28
|
|
||||||
2525 13-85-23-66-37-16-95
|
|
||||||
2525 97-28-72-80-14-30-78
|
|
||||||
2525 11-69-37-13-79-35-12
|
|
||||||
89-44-47-63-67-54-12
|
|
||||||
2525 07-09-98-78-15-23-50
|
|
||||||
2523 05-03-90-01-62-57-18
|
|
||||||
65-07-18-74-28-42-66
|
|
||||||
2525 39-77-17-98-01-23-29
|
|
||||||
2522 05-50-21-93-79-11-61
|
|
||||||
2525 61-18-20-81-60-90-05
|
|
||||||
2521 15-92-74-93-64-78-54
|
|
||||||
2523 22-21-96-99-90-45-27
|
|
||||||
2521 30-97-48-67-95-75-79
|
|
||||||
2524 39-57-99-03-13-46-35
|
|
||||||
2522 98-54-80-56-33-65-44
|
|
||||||
20-91-91-30-15-65-25
|
|
||||||
98-04-80-73-50-11-42
|
|
||||||
98-34-41-64-88-01-63
|
|
||||||
2525 29-35-02-04-32-78-51
|
|
||||||
2523 62-44-20-56-62-78-01
|
|
||||||
2524 14-36-17-91-34-91-55
|
|
||||||
2524 17-01-76-83-62-31-93
|
|
||||||
04-44-22-26-04-55-87
|
|
||||||
2523 11-43-07-89-40-00-88
|
|
||||||
2521 84-28-72-28-33-60-44
|
|
||||||
2525 95-40-78-88-00-43-13
|
|
||||||
2522 69-21-29-41-81-96-77
|
|
||||||
2524 37-22-41-64-08-13-92
|
|
||||||
2524 73-96-94-27-64-09-09
|
|
||||||
33-27-89-47-46-62-85
|
|
||||||
2523 75-75-48-01-28-10-88
|
|
||||||
72-57-79-14-18-91-23
|
|
||||||
98-32-02-86-87-59-11
|
|
||||||
97-19-28-45-03-08-64
|
|
||||||
2523 74-22-18-22-46-58-94
|
|
||||||
2525 18-13-73-83-02-10-09
|
|
||||||
2523 41-15-99-26-09-14-97
|
|
||||||
2525 43-58-60-55-40-73-67
|
|
||||||
2523 42-97-48-61-70-60-38
|
|
||||||
80-70-44-15-17-55-49
|
|
||||||
2522 76-81-33-86-19-53-45
|
|
||||||
2525 45-94-04-45-89-90-28
|
|
||||||
2522 20-97-12-37-10-83-76
|
|
||||||
2524 34-32-51-50-78-80-97
|
|
||||||
2522 30-97-39-84-02-45-49
|
|
||||||
83-67-91-16-68-14-66
|
|
||||||
94-71-04-28-57-75-45
|
|
||||||
2524 83-82-42-15-67-91-48
|
|
||||||
2523 97-98-88-10-36-79-53
|
|
||||||
41-22-09-70-75-40-57
|
|
||||||
2522 77-94-56-22-88-02-16
|
|
||||||
2525 43-11-72-35-15-47-04
|
|
||||||
2525 35-57-25-41-26-07-37
|
|
||||||
57-06-88-62-15-34-66
|
|
||||||
2525 98-66-63-02-15-71-13
|
|
||||||
58-20-77-41-06-52-33
|
|
||||||
2521 11-98-92-27-38-94-75
|
|
||||||
2525 09-48-71-70-71-41-26
|
|
||||||
2525 79-05-30-49-24-22-33
|
|
||||||
26-70-94-22-64-89-48
|
|
||||||
2524 34-71-40-14-68-80-57
|
|
||||||
18-87-93-44-52-37-69
|
|
||||||
2524 09-39-78-85-80-17-81
|
|
||||||
2521 32-08-76-43-59-61-14
|
|
||||||
2523 93-56-87-85-14-53-72
|
|
||||||
2521 78-51-66-89-56-33-49
|
|
||||||
2522 20-24-45-32-47-44-53
|
|
||||||
41-37-43-28-56-43-54
|
|
||||||
2525 95-88-82-26-44-81-83
|
|
||||||
95-26-50-93-40-82-27
|
|
||||||
2521 32-43-09-99-96-51-73
|
|
||||||
2522 62-54-92-00-89-19-66
|
|
||||||
2525 28-53-29-95-71-21-66
|
|
||||||
2523 68-33-54-40-40-99-32
|
|
||||||
2523 60-51-93-71-70-19-35
|
|
||||||
2524 01-72-11-22-48-64-15
|
|
||||||
80-56-98-36-74-46-98
|
|
||||||
2524 08-02-36-94-18-37-27
|
|
||||||
2524 33-98-00-04-99-88-91
|
|
||||||
2523 90-77-79-06-91-29-07
|
|
||||||
2521 63-16-29-62-15-87-98
|
|
||||||
2522 61-37-16-90-50-14-83
|
|
||||||
2521 52-13-01-97-57-81-05
|
|
||||||
29-11-89-59-59-44-05
|
|
||||||
96-42-02-79-02-80-82
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,500 +0,0 @@
|
|||||||
2524 88-62-46-84-72-08-35
|
|
||||||
2522 10-22-27-22-58-78-17
|
|
||||||
51-13-02-75-49-33-24
|
|
||||||
70-89-01-27-80-15-07
|
|
||||||
34-92-77-76-25-70-93
|
|
||||||
38-32-72-86-17-33-56
|
|
||||||
87-60-70-50-25-91-84
|
|
||||||
2523 21-14-04-05-19-46-25
|
|
||||||
2524 89-84-04-85-69-48-11
|
|
||||||
2524 50-35-99-27-26-02-20
|
|
||||||
2523 28-62-92-35-74-98-25
|
|
||||||
2522 93-14-72-96-97-42-96
|
|
||||||
2525 25-30-32-74-67-29-85
|
|
||||||
2521 36-86-88-64-61-88-89
|
|
||||||
2522 44-74-59-58-15-14-89
|
|
||||||
01-30-55-20-38-31-72
|
|
||||||
71-18-33-96-66-96-26
|
|
||||||
2524 94-32-58-56-35-13-97
|
|
||||||
2523 28-87-80-20-45-21-05
|
|
||||||
2524 72-50-32-62-44-95-03
|
|
||||||
2522 25-60-16-18-19-11-70
|
|
||||||
98-79-01-28-64-95-66
|
|
||||||
96-26-10-27-17-87-71
|
|
||||||
23-99-31-56-74-73-76
|
|
||||||
88-78-77-67-55-73-96
|
|
||||||
95-27-40-11-78-13-64
|
|
||||||
81-54-62-27-54-62-69
|
|
||||||
31-94-80-51-25-36-79
|
|
||||||
2524 44-10-96-63-84-30-07
|
|
||||||
2522 53-36-32-70-62-28-43
|
|
||||||
2523 60-82-65-57-94-68-25
|
|
||||||
2523 90-62-99-58-03-02-57
|
|
||||||
84-93-24-28-61-92-83
|
|
||||||
38-97-88-51-57-47-91
|
|
||||||
2522 97-18-71-19-17-46-11
|
|
||||||
74-25-19-72-73-69-05
|
|
||||||
2523 96-41-78-01-63-40-13
|
|
||||||
2525 93-75-84-73-30-84-68
|
|
||||||
2523 29-78-54-03-00-21-31
|
|
||||||
2524 74-45-78-17-55-77-54
|
|
||||||
2525 42-11-31-48-56-32-88
|
|
||||||
2525 69-47-22-59-62-43-20
|
|
||||||
2523 01-22-13-57-05-25-44
|
|
||||||
2525 59-22-43-08-53-48-82
|
|
||||||
2525 32-15-12-73-96-25-50
|
|
||||||
2525 90-04-74-22-33-88-10
|
|
||||||
2522 30-32-71-15-43-34-55
|
|
||||||
30-92-54-05-94-53-54
|
|
||||||
2525 29-58-08-99-46-04-29
|
|
||||||
46-27-64-43-09-37-58
|
|
||||||
2525 77-95-40-98-58-08-54
|
|
||||||
2525 02-66-43-02-60-18-34
|
|
||||||
16-37-17-50-65-63-51
|
|
||||||
28-00-31-28-74-01-13
|
|
||||||
2521 18-65-37-13-86-46-08
|
|
||||||
2524 88-84-69-86-18-46-49
|
|
||||||
25-23-65-85-03-80-42
|
|
||||||
2523 10-64-29-31-20-89-52
|
|
||||||
2524 65-21-51-30-91-21-68
|
|
||||||
33-24-81-00-31-10-06
|
|
||||||
2522 66-21-20-66-66-77-70
|
|
||||||
64-36-82-81-22-07-90
|
|
||||||
2524 59-29-33-33-51-95-17
|
|
||||||
2523 00-93-53-78-54-23-22
|
|
||||||
2522 73-77-13-34-10-90-73
|
|
||||||
2521 80-60-56-32-06-52-22
|
|
||||||
61-17-66-25-81-17-53
|
|
||||||
2524 60-47-94-82-73-16-91
|
|
||||||
2524 42-23-08-47-92-68-73
|
|
||||||
2523 96-42-17-80-54-21-92
|
|
||||||
43-41-24-82-73-89-70
|
|
||||||
58-59-94-04-58-25-95
|
|
||||||
65-09-40-69-61-49-66
|
|
||||||
2524 50-80-86-64-00-07-03
|
|
||||||
2525 49-88-90-85-64-35-76
|
|
||||||
2524 45-24-80-26-42-84-59
|
|
||||||
2524 95-24-66-37-33-61-07
|
|
||||||
2523 49-58-55-29-51-10-61
|
|
||||||
2525 39-03-45-88-41-32-53
|
|
||||||
2523 73-96-56-70-51-13-71
|
|
||||||
65-18-22-20-11-92-26
|
|
||||||
2525 80-30-71-96-23-95-74
|
|
||||||
2521 68-19-86-32-40-86-59
|
|
||||||
2522 07-03-45-99-77-61-66
|
|
||||||
2522 53-26-95-59-95-36-13
|
|
||||||
2525 41-02-61-74-69-53-72
|
|
||||||
2521 40-42-28-13-59-79-73
|
|
||||||
2522 03-31-84-02-95-87-67
|
|
||||||
04-54-85-07-18-08-63
|
|
||||||
2522 51-18-39-20-56-42-88
|
|
||||||
2525 90-88-19-93-08-36-74
|
|
||||||
2522 23-14-28-13-65-76-55
|
|
||||||
2523 24-89-75-22-08-30-07
|
|
||||||
15-26-21-64-07-12-45
|
|
||||||
2521 79-89-45-51-27-87-84
|
|
||||||
2525 01-11-24-63-37-93-77
|
|
||||||
2524 81-41-39-29-85-72-75
|
|
||||||
2525 64-96-76-67-37-51-52
|
|
||||||
21-31-25-17-61-80-92
|
|
||||||
2524 72-32-21-73-93-88-48
|
|
||||||
84-27-78-23-47-96-13
|
|
||||||
2523 52-86-55-42-99-36-96
|
|
||||||
2524 10-33-99-48-82-51-25
|
|
||||||
95-69-56-50-65-47-42
|
|
||||||
2525 99-89-69-98-27-91-33
|
|
||||||
06-20-51-97-71-00-53
|
|
||||||
2522 22-05-43-81-46-67-40
|
|
||||||
2521 37-08-49-25-33-08-77
|
|
||||||
2524 63-03-27-24-77-20-41
|
|
||||||
65-59-99-21-28-67-74
|
|
||||||
51-89-42-53-15-48-48
|
|
||||||
41-60-33-82-91-19-40
|
|
||||||
2522 47-26-52-13-21-61-61
|
|
||||||
32-81-00-16-63-90-66
|
|
||||||
2524 18-12-12-11-89-20-60
|
|
||||||
2522 29-93-53-71-59-57-17
|
|
||||||
2522 17-61-02-56-63-48-90
|
|
||||||
2522 87-56-66-57-13-34-32
|
|
||||||
27-43-61-72-26-68-94
|
|
||||||
2525 15-74-04-57-85-46-89
|
|
||||||
2525 58-35-93-12-58-24-84
|
|
||||||
41-09-96-02-81-97-85
|
|
||||||
04-92-76-03-21-36-38
|
|
||||||
36-82-09-76-50-91-40
|
|
||||||
2521 31-48-77-83-23-85-58
|
|
||||||
91-08-41-12-22-67-92
|
|
||||||
2525 91-01-95-06-20-56-66
|
|
||||||
2523 92-09-07-53-90-73-56
|
|
||||||
2523 24-88-11-05-06-18-63
|
|
||||||
2525 14-89-03-92-45-65-53
|
|
||||||
2523 73-98-00-08-94-74-60
|
|
||||||
11-25-05-77-54-25-38
|
|
||||||
2525 24-14-14-61-13-96-41
|
|
||||||
28-33-55-89-06-90-31
|
|
||||||
2523 92-90-32-07-42-96-04
|
|
||||||
2525 79-80-48-56-75-29-12
|
|
||||||
2521 77-97-88-83-04-44-09
|
|
||||||
2523 82-96-37-98-15-52-75
|
|
||||||
2522 64-34-21-10-96-85-39
|
|
||||||
2524 31-52-64-02-96-39-16
|
|
||||||
03-50-03-64-37-62-21
|
|
||||||
2521 49-63-37-97-53-63-00
|
|
||||||
2525 94-49-52-77-74-48-81
|
|
||||||
55-40-74-74-81-86-50
|
|
||||||
2524 06-70-54-03-82-67-17
|
|
||||||
75-19-75-29-43-35-82
|
|
||||||
2521 42-96-95-66-89-84-01
|
|
||||||
2521 55-33-17-44-67-26-89
|
|
||||||
2524 56-64-65-06-52-00-85
|
|
||||||
2522 93-66-95-15-90-23-90
|
|
||||||
2523 31-25-99-15-61-01-30
|
|
||||||
2525 54-54-54-47-69-06-33
|
|
||||||
2525 17-40-02-42-79-86-21
|
|
||||||
2522 21-12-01-11-51-55-14
|
|
||||||
2521 46-20-64-13-21-06-15
|
|
||||||
2523 92-85-71-89-97-70-84
|
|
||||||
2523 22-84-47-04-78-47-01
|
|
||||||
62-49-03-81-98-15-91
|
|
||||||
2524 79-54-71-16-36-91-63
|
|
||||||
2522 02-11-79-98-69-92-57
|
|
||||||
2525 32-76-56-57-96-23-90
|
|
||||||
2523 06-87-57-07-02-01-85
|
|
||||||
2521 18-35-94-83-28-73-15
|
|
||||||
2523 97-04-86-66-40-64-86
|
|
||||||
2521 55-97-94-59-99-20-57
|
|
||||||
2525 18-46-50-17-69-33-41
|
|
||||||
2522 09-48-99-58-34-13-61
|
|
||||||
2523 28-82-53-71-21-05-09
|
|
||||||
2523 08-12-90-23-74-10-27
|
|
||||||
2525 32-08-45-22-72-72-76
|
|
||||||
60-67-63-50-96-10-27
|
|
||||||
2525 75-03-19-97-62-80-88
|
|
||||||
2522 97-86-67-50-27-37-08
|
|
||||||
49-08-22-06-86-17-86
|
|
||||||
2524 09-80-21-70-82-91-48
|
|
||||||
96-06-92-25-94-08-57
|
|
||||||
2525 21-35-94-03-85-72-61
|
|
||||||
2521 39-93-53-66-86-81-96
|
|
||||||
2524 06-18-23-18-88-94-09
|
|
||||||
2521 52-96-14-51-04-51-36
|
|
||||||
2522 10-62-26-66-78-03-94
|
|
||||||
2525 58-22-74-01-66-37-97
|
|
||||||
2524 22-82-49-98-55-97-36
|
|
||||||
2523 04-16-77-51-80-89-13
|
|
||||||
70-51-03-12-10-26-56
|
|
||||||
2521 80-93-55-85-90-06-27
|
|
||||||
2525 18-63-31-58-45-52-61
|
|
||||||
17-10-85-46-30-32-82
|
|
||||||
73-84-60-73-28-53-48
|
|
||||||
2521 13-98-24-82-40-06-10
|
|
||||||
2521 58-59-74-00-18-34-85
|
|
||||||
2524 92-02-64-75-83-14-50
|
|
||||||
10-26-44-71-18-12-71
|
|
||||||
2523 25-09-58-53-10-53-54
|
|
||||||
2521 34-51-86-52-12-41-76
|
|
||||||
2522 71-42-30-72-71-45-59
|
|
||||||
2524 00-71-32-40-12-45-68
|
|
||||||
2524 74-50-48-06-05-52-06
|
|
||||||
48-88-23-94-23-40-74
|
|
||||||
2525 91-22-15-04-72-70-70
|
|
||||||
2521 76-78-90-23-44-92-83
|
|
||||||
2525 57-39-63-94-24-69-04
|
|
||||||
14-88-43-54-27-70-11
|
|
||||||
2522 18-25-25-91-36-53-23
|
|
||||||
2524 36-15-88-30-21-64-83
|
|
||||||
2525 66-11-70-60-37-02-63
|
|
||||||
43-11-84-99-73-28-48
|
|
||||||
01-03-64-24-84-70-15
|
|
||||||
2524 48-76-97-28-23-64-71
|
|
||||||
2524 77-08-08-23-73-96-22
|
|
||||||
2521 64-02-43-87-85-72-84
|
|
||||||
2525 85-46-13-04-03-63-60
|
|
||||||
2524 56-96-76-02-20-13-95
|
|
||||||
31-54-15-57-42-74-53
|
|
||||||
89-00-93-32-62-12-11
|
|
||||||
45-76-98-25-74-09-04
|
|
||||||
2521 64-30-44-10-39-95-33
|
|
||||||
44-71-95-86-12-54-08
|
|
||||||
63-13-57-14-13-48-16
|
|
||||||
41-87-71-95-17-22-88
|
|
||||||
2521 55-23-84-04-27-20-38
|
|
||||||
2523 80-64-38-39-76-43-04
|
|
||||||
2523 81-83-82-90-45-95-65
|
|
||||||
2523 57-84-88-16-25-30-98
|
|
||||||
2525 78-21-73-66-17-08-23
|
|
||||||
13-96-69-65-56-65-03
|
|
||||||
2522 76-37-07-36-14-56-29
|
|
||||||
2525 25-69-00-04-35-06-73
|
|
||||||
2525 63-19-14-57-67-48-50
|
|
||||||
2521 35-43-79-88-05-41-04
|
|
||||||
2525 24-39-13-22-92-33-38
|
|
||||||
39-87-05-09-65-00-95
|
|
||||||
2522 18-68-83-63-94-11-52
|
|
||||||
59-66-84-42-56-03-62
|
|
||||||
36-35-03-95-91-45-41
|
|
||||||
16-11-69-63-84-39-80
|
|
||||||
04-84-19-52-59-91-38
|
|
||||||
2523 18-18-33-99-33-21-00
|
|
||||||
2524 23-70-82-88-62-37-02
|
|
||||||
2524 84-81-71-58-92-39-45
|
|
||||||
45-37-02-62-10-07-76
|
|
||||||
82-02-00-62-68-89-90
|
|
||||||
2524 86-09-14-71-82-07-96
|
|
||||||
00-46-39-33-52-92-78
|
|
||||||
2522 52-39-25-89-07-07-57
|
|
||||||
2524 84-73-35-01-08-20-67
|
|
||||||
01-20-59-64-93-70-69
|
|
||||||
2521 54-32-02-66-48-17-66
|
|
||||||
2522 27-88-88-20-04-95-37
|
|
||||||
2522 64-20-24-10-80-29-56
|
|
||||||
97-57-32-45-22-40-46
|
|
||||||
96-34-25-40-82-57-74
|
|
||||||
2522 81-31-85-33-45-63-70
|
|
||||||
2524 66-71-41-81-31-98-25
|
|
||||||
49-82-16-11-72-89-45
|
|
||||||
2521 66-43-39-05-15-18-35
|
|
||||||
2525 33-11-45-38-33-86-68
|
|
||||||
2522 98-15-12-20-40-53-38
|
|
||||||
2523 88-42-37-81-18-01-02
|
|
||||||
2521 11-65-99-21-43-15-22
|
|
||||||
53-13-41-07-68-00-08
|
|
||||||
2524 47-73-46-61-53-08-26
|
|
||||||
2523 08-19-28-22-45-02-64
|
|
||||||
2521 44-82-74-93-95-67-71
|
|
||||||
2523 58-08-17-31-34-08-12
|
|
||||||
2525 14-35-43-99-32-32-85
|
|
||||||
16-39-50-48-61-01-68
|
|
||||||
21-01-79-67-64-02-34
|
|
||||||
2523 29-90-42-53-74-49-24
|
|
||||||
43-36-98-42-50-74-58
|
|
||||||
2521 94-81-74-15-33-82-12
|
|
||||||
2525 58-11-35-62-67-84-14
|
|
||||||
51-29-63-65-41-59-61
|
|
||||||
2521 83-82-27-34-21-39-89
|
|
||||||
2524 02-33-52-60-73-83-02
|
|
||||||
98-60-39-67-78-63-16
|
|
||||||
2523 64-01-33-01-30-29-51
|
|
||||||
11-75-71-71-03-02-16
|
|
||||||
2522 26-61-47-07-99-43-61
|
|
||||||
2525 47-52-94-94-22-86-50
|
|
||||||
38-06-39-62-20-43-40
|
|
||||||
2525 35-95-33-15-26-71-68
|
|
||||||
2525 42-85-13-31-42-01-39
|
|
||||||
2522 49-75-29-96-44-83-78
|
|
||||||
77-78-32-83-24-38-75
|
|
||||||
2523 49-04-42-96-56-31-75
|
|
||||||
2525 97-48-18-70-00-51-18
|
|
||||||
44-65-44-13-62-33-58
|
|
||||||
41-59-53-82-42-97-31
|
|
||||||
2525 25-11-42-32-67-02-45
|
|
||||||
71-63-18-02-65-19-04
|
|
||||||
95-17-37-75-09-90-68
|
|
||||||
2524 03-54-07-90-12-65-23
|
|
||||||
80-79-45-70-64-72-68
|
|
||||||
2523 31-58-15-79-76-04-38
|
|
||||||
20-15-21-46-53-62-33
|
|
||||||
2521 36-38-82-78-34-89-65
|
|
||||||
2524 84-20-61-66-19-69-95
|
|
||||||
2525 48-16-40-86-41-78-35
|
|
||||||
2524 03-37-64-84-01-78-94
|
|
||||||
2524 44-67-25-32-81-53-15
|
|
||||||
2525 48-52-48-87-90-98-18
|
|
||||||
30-60-22-87-47-25-15
|
|
||||||
2525 33-84-89-80-86-70-09
|
|
||||||
73-93-46-17-69-91-97
|
|
||||||
2522 84-97-55-42-32-60-92
|
|
||||||
2525 07-07-64-14-63-51-14
|
|
||||||
2524 55-03-93-60-14-91-74
|
|
||||||
2523 32-19-25-22-77-78-15
|
|
||||||
2521 73-53-49-22-54-23-90
|
|
||||||
2521 78-87-15-24-92-85-90
|
|
||||||
2522 34-62-94-56-11-17-51
|
|
||||||
2522 30-07-45-21-59-94-54
|
|
||||||
2523 55-92-76-54-95-29-71
|
|
||||||
76-03-18-42-39-37-30
|
|
||||||
89-26-94-14-17-99-40
|
|
||||||
50-10-05-18-34-97-32
|
|
||||||
2521 04-25-61-71-00-32-50
|
|
||||||
2523 56-82-78-00-94-99-90
|
|
||||||
2524 34-99-74-17-91-98-84
|
|
||||||
75-74-30-25-42-81-71
|
|
||||||
2524 37-69-87-33-41-40-02
|
|
||||||
50-19-15-78-99-25-22
|
|
||||||
18-49-62-94-65-95-87
|
|
||||||
2523 77-16-41-76-81-66-35
|
|
||||||
2522 59-70-39-69-97-92-96
|
|
||||||
2525 81-72-07-51-68-40-23
|
|
||||||
2525 63-60-68-44-43-62-08
|
|
||||||
2521 73-20-40-52-98-97-29
|
|
||||||
2523 38-27-54-83-03-00-26
|
|
||||||
2522 08-39-39-32-25-45-56
|
|
||||||
2523 40-34-67-04-37-33-29
|
|
||||||
2524 11-41-84-92-94-16-33
|
|
||||||
2521 89-55-98-69-20-03-41
|
|
||||||
2521 27-09-16-26-04-82-81
|
|
||||||
2521 38-83-20-21-79-29-81
|
|
||||||
2525 61-09-59-92-28-67-66
|
|
||||||
47-19-80-43-43-20-93
|
|
||||||
2521 87-80-59-51-20-32-74
|
|
||||||
2524 70-14-85-72-40-80-60
|
|
||||||
2523 77-57-03-64-45-21-38
|
|
||||||
2521 88-33-82-62-01-49-55
|
|
||||||
88-11-93-34-85-87-69
|
|
||||||
06-02-35-69-77-05-11
|
|
||||||
2525 84-91-87-54-60-51-46
|
|
||||||
2525 78-99-73-78-24-94-24
|
|
||||||
29-50-87-38-87-93-90
|
|
||||||
2521 84-73-41-32-87-95-52
|
|
||||||
2521 53-62-20-06-17-74-40
|
|
||||||
2524 13-47-06-47-93-65-29
|
|
||||||
2522 38-85-34-37-71-05-30
|
|
||||||
2523 48-39-49-57-23-78-96
|
|
||||||
2522 81-22-48-06-91-47-42
|
|
||||||
15-65-95-20-46-73-48
|
|
||||||
2521 80-46-01-82-74-75-03
|
|
||||||
2521 11-40-88-15-16-96-49
|
|
||||||
2524 43-94-42-84-35-12-17
|
|
||||||
2524 18-12-45-80-30-07-72
|
|
||||||
2525 57-99-35-42-43-67-68
|
|
||||||
63-99-70-67-80-84-31
|
|
||||||
2521 19-80-66-96-16-61-44
|
|
||||||
90-66-93-65-04-32-71
|
|
||||||
52-73-25-85-08-22-10
|
|
||||||
41-42-86-69-91-89-93
|
|
||||||
2525 69-06-01-51-03-59-91
|
|
||||||
2522 25-00-80-31-11-83-55
|
|
||||||
18-77-42-88-77-67-11
|
|
||||||
2525 83-90-27-60-78-24-26
|
|
||||||
2523 94-00-59-37-68-05-50
|
|
||||||
2521 55-74-61-32-63-51-01
|
|
||||||
2522 61-90-85-23-11-51-03
|
|
||||||
2523 94-78-26-87-62-57-55
|
|
||||||
2524 22-42-80-60-85-42-48
|
|
||||||
2521 47-06-03-02-78-96-05
|
|
||||||
2524 78-54-40-11-40-54-75
|
|
||||||
68-20-77-52-00-10-70
|
|
||||||
2521 04-82-37-21-22-19-17
|
|
||||||
2524 62-94-76-61-11-56-75
|
|
||||||
14-04-11-98-47-23-56
|
|
||||||
2521 54-41-86-59-91-91-61
|
|
||||||
14-00-07-96-01-62-04
|
|
||||||
29-18-98-86-00-88-70
|
|
||||||
62-78-07-66-28-68-93
|
|
||||||
23-67-08-74-60-57-55
|
|
||||||
2521 44-26-69-25-31-41-36
|
|
||||||
2523 65-82-68-93-69-64-68
|
|
||||||
25-23-22-44-51-33-19
|
|
||||||
2521 45-37-36-91-84-70-59
|
|
||||||
2521 99-23-86-83-01-62-70
|
|
||||||
85-94-26-28-50-89-75
|
|
||||||
2521 16-30-23-12-48-81-01
|
|
||||||
36-43-94-12-58-24-73
|
|
||||||
2522 22-11-15-28-77-93-46
|
|
||||||
24-00-68-13-80-33-10
|
|
||||||
2524 79-10-22-21-74-10-56
|
|
||||||
2525 50-92-57-27-51-67-57
|
|
||||||
53-28-93-58-39-45-05
|
|
||||||
2522 49-13-78-56-46-96-33
|
|
||||||
2523 65-40-89-45-25-45-78
|
|
||||||
2523 59-35-54-94-01-68-62
|
|
||||||
2521 21-26-28-37-80-04-15
|
|
||||||
31-71-93-03-54-89-84
|
|
||||||
2524 06-16-02-83-98-00-11
|
|
||||||
2524 79-24-11-13-14-02-37
|
|
||||||
2522 08-95-10-92-33-49-44
|
|
||||||
2521 49-65-96-35-05-04-53
|
|
||||||
2522 41-32-18-41-45-88-81
|
|
||||||
2521 53-55-62-25-06-39-43
|
|
||||||
2521 05-14-32-15-50-24-82
|
|
||||||
2525 60-47-47-27-56-11-89
|
|
||||||
2521 44-77-64-51-88-05-75
|
|
||||||
2523 25-51-51-60-61-81-76
|
|
||||||
2523 92-38-26-84-23-01-06
|
|
||||||
28-67-09-28-67-04-31
|
|
||||||
2525 29-39-37-88-09-23-79
|
|
||||||
33-48-56-81-66-84-89
|
|
||||||
23-38-63-69-33-39-02
|
|
||||||
2522 70-04-29-62-18-94-74
|
|
||||||
2524 31-07-43-44-22-06-24
|
|
||||||
2524 58-41-39-65-11-94-61
|
|
||||||
2525 85-80-40-57-39-02-03
|
|
||||||
2524 45-80-38-47-70-95-24
|
|
||||||
82-85-24-60-48-90-50
|
|
||||||
04-03-01-57-35-97-62
|
|
||||||
2524 82-00-55-91-97-52-37
|
|
||||||
2523 97-00-38-05-71-74-38
|
|
||||||
32-09-89-80-29-48-51
|
|
||||||
84-75-37-85-77-75-29
|
|
||||||
2523 51-44-85-74-10-90-74
|
|
||||||
2523 25-63-16-22-75-48-79
|
|
||||||
80-59-44-91-58-46-30
|
|
||||||
2522 31-48-06-26-42-59-84
|
|
||||||
48-50-24-48-30-74-73
|
|
||||||
31-26-27-54-59-28-34
|
|
||||||
2522 87-66-84-15-33-31-95
|
|
||||||
51-85-47-66-51-64-87
|
|
||||||
2523 55-09-83-65-81-58-51
|
|
||||||
2522 99-11-54-41-04-24-54
|
|
||||||
78-44-82-14-91-00-67
|
|
||||||
31-38-18-34-44-79-59
|
|
||||||
2521 75-13-20-65-21-16-15
|
|
||||||
2523 26-44-92-56-41-70-22
|
|
||||||
95-71-53-73-55-50-94
|
|
||||||
10-44-09-45-67-13-75
|
|
||||||
2525 06-21-87-86-54-94-02
|
|
||||||
2524 31-85-09-42-29-45-57
|
|
||||||
2525 42-01-75-05-25-11-40
|
|
||||||
2524 12-14-10-27-19-30-99
|
|
||||||
79-97-04-48-87-42-00
|
|
||||||
2521 90-02-73-89-64-29-10
|
|
||||||
2523 29-17-32-76-08-65-75
|
|
||||||
2524 70-31-69-39-33-84-38
|
|
||||||
2525 71-52-62-55-12-16-57
|
|
||||||
36-69-53-13-49-70-66
|
|
||||||
85-12-10-39-29-80-35
|
|
||||||
2524 26-09-42-08-04-99-55
|
|
||||||
2523 33-23-74-47-43-33-24
|
|
||||||
2525 06-91-79-15-79-29-41
|
|
||||||
60-88-10-40-92-23-52
|
|
||||||
2523 24-05-58-34-80-77-14
|
|
||||||
2522 74-71-28-79-29-38-72
|
|
||||||
2521 80-50-12-20-47-99-78
|
|
||||||
2521 06-83-17-55-45-79-82
|
|
||||||
2521 13-52-26-76-99-70-20
|
|
||||||
2524 84-64-14-58-40-09-62
|
|
||||||
2524 86-97-11-55-57-83-16
|
|
||||||
2522 79-38-56-35-52-07-41
|
|
||||||
91-38-01-67-78-65-73
|
|
||||||
2523 05-11-50-18-20-12-38
|
|
||||||
2521 03-88-90-27-37-15-37
|
|
||||||
2525 83-26-08-00-50-20-68
|
|
||||||
2521 68-65-73-31-70-44-45
|
|
||||||
2524 54-66-91-09-07-74-26
|
|
||||||
2525 72-65-73-73-62-24-96
|
|
||||||
07-41-74-07-86-07-39
|
|
||||||
2522 64-48-93-29-40-97-14
|
|
||||||
2525 79-90-61-88-87-15-59
|
|
||||||
2524 50-47-16-17-09-15-14
|
|
||||||
2521 46-06-40-88-48-85-88
|
|
||||||
91-27-05-71-25-84-20
|
|
||||||
2522 12-22-39-13-04-78-78
|
|
||||||
2525 58-11-44-63-05-97-71
|
|
||||||
2521 70-16-43-07-87-51-85
|
|
||||||
2521 58-92-61-20-12-28-60
|
|
||||||
57-80-24-58-22-03-15
|
|
||||||
2524 12-08-29-52-75-46-34
|
|
||||||
2524 63-17-74-41-08-29-16
|
|
||||||
81-05-91-02-20-96-92
|
|
||||||
96-59-37-84-38-68-85
|
|
||||||
34-09-34-90-82-90-14
|
|
||||||
45-66-92-96-14-48-83
|
|
||||||
2522 01-61-02-21-68-28-60
|
|
||||||
89-01-37-64-20-77-75
|
|
||||||
14-00-50-43-04-66-06
|
|
||||||
2521 06-35-29-40-03-24-19
|
|
||||||
2524 78-34-98-20-72-56-24
|
|
||||||
54-05-64-46-00-00-54
|
|
||||||
87-00-71-87-41-99-40
|
|
||||||
70-50-43-54-84-95-28
|
|
||||||
2524 87-53-38-76-20-49-78
|
|
||||||
File diff suppressed because it is too large
Load Diff
68
test_bot.py
68
test_bot.py
@@ -1,68 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Упрощенная версия main.py для диагностики
|
|
||||||
"""
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# Настройка логирования
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
||||||
)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
async def test_imports():
|
|
||||||
"""Тест импортов по порядку"""
|
|
||||||
try:
|
|
||||||
logger.info("1. Тест импорта config...")
|
|
||||||
from src.core.config import BOT_TOKEN, ADMIN_IDS, DATABASE_URL
|
|
||||||
logger.info(f"✅ Config OK. BOT_TOKEN: {BOT_TOKEN[:10]}..., ADMIN_IDS: {ADMIN_IDS}")
|
|
||||||
|
|
||||||
logger.info("2. Тест импорта aiogram...")
|
|
||||||
from aiogram import Bot, Dispatcher
|
|
||||||
logger.info("✅ Aiogram OK")
|
|
||||||
|
|
||||||
logger.info("3. Тест создания бота...")
|
|
||||||
bot = Bot(token=BOT_TOKEN)
|
|
||||||
logger.info("✅ Bot created OK")
|
|
||||||
|
|
||||||
logger.info("4. Тест импорта database...")
|
|
||||||
from src.core.database import async_session_maker, init_db
|
|
||||||
logger.info("✅ Database imports OK")
|
|
||||||
|
|
||||||
logger.info("5. Тест подключения к БД...")
|
|
||||||
async with async_session_maker() as session:
|
|
||||||
logger.info("✅ Database connection OK")
|
|
||||||
|
|
||||||
logger.info("6. Тест импорта services...")
|
|
||||||
from src.core.services import UserService, LotteryService
|
|
||||||
logger.info("✅ Services OK")
|
|
||||||
|
|
||||||
logger.info("7. Тест импорта handlers...")
|
|
||||||
from src.handlers.registration_handlers import router as registration_router
|
|
||||||
logger.info("✅ Registration handlers OK")
|
|
||||||
|
|
||||||
from src.handlers.admin_panel import admin_router
|
|
||||||
logger.info("✅ Admin panel OK")
|
|
||||||
|
|
||||||
logger.info("8. Тест создания диспетчера...")
|
|
||||||
dp = Dispatcher()
|
|
||||||
dp.include_router(registration_router)
|
|
||||||
dp.include_router(admin_router)
|
|
||||||
logger.info("✅ Dispatcher OK")
|
|
||||||
|
|
||||||
logger.info("9. Тест получения информации о боте...")
|
|
||||||
bot_info = await bot.get_me()
|
|
||||||
logger.info(f"✅ Bot info: {bot_info.username} ({bot_info.first_name})")
|
|
||||||
|
|
||||||
await bot.session.close()
|
|
||||||
logger.info("✅ Все тесты пройдены успешно!")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Ошибка: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(test_imports())
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Скрипт для тестирования функциональности бота
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
|
||||||
|
|
||||||
from src.core.database import async_session_maker
|
|
||||||
from src.core.models import User, Lottery
|
|
||||||
from sqlalchemy import select
|
|
||||||
|
|
||||||
async def test_database_connectivity():
|
|
||||||
"""Тест подключения к базе данных"""
|
|
||||||
print("🔌 Тестируем подключение к базе данных...")
|
|
||||||
|
|
||||||
async with async_session_maker() as session:
|
|
||||||
# Проверяем подключение
|
|
||||||
result = await session.execute(select(1))
|
|
||||||
print("✅ Подключение к PostgreSQL работает")
|
|
||||||
|
|
||||||
# Проверяем количество пользователей
|
|
||||||
users_count = await session.execute(select(User))
|
|
||||||
users = users_count.scalars().all()
|
|
||||||
print(f"📊 В базе {len(users)} пользователей")
|
|
||||||
|
|
||||||
# Проверяем количество лотерей
|
|
||||||
lotteries_count = await session.execute(select(Lottery))
|
|
||||||
lotteries = lotteries_count.scalars().all()
|
|
||||||
print(f"🎰 В базе {len(lotteries)} лотерей")
|
|
||||||
|
|
||||||
async def test_bot_imports():
|
|
||||||
"""Тест импортов бота"""
|
|
||||||
print("🔄 Тестируем импорты модулей...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
from src.handlers.registration_handlers import router as registration_router
|
|
||||||
print("✅ registration_router импортирован")
|
|
||||||
|
|
||||||
from src.handlers.admin_panel import admin_router
|
|
||||||
print("✅ admin_router импортирован")
|
|
||||||
|
|
||||||
from src.handlers.account_handlers import account_router
|
|
||||||
print("✅ account_router импортирован")
|
|
||||||
|
|
||||||
from src.core.config import BOT_TOKEN
|
|
||||||
print("✅ BOT_TOKEN получен из конфигурации")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Ошибка импорта: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
"""Основная функция тестирования"""
|
|
||||||
print("🤖 Тестирование функциональности лотерейного бота")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
# Тест импортов
|
|
||||||
imports_ok = await test_bot_imports()
|
|
||||||
|
|
||||||
if imports_ok:
|
|
||||||
print("\n")
|
|
||||||
# Тест базы данных
|
|
||||||
await test_database_connectivity()
|
|
||||||
|
|
||||||
print("\n" + "=" * 50)
|
|
||||||
print("✅ Тестирование завершено")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
||||||
Reference in New Issue
Block a user