From 7b6abe2a0e7b5fcacad3f1aab613a34d79aa54d4 Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 26 Aug 2025 02:14:11 +0300 Subject: [PATCH] WIP: Temporary commit for branch move --- .gitignore | 34 + Makefile | 73 + README_TESTING.md | 259 ++ database/db.py | 2376 +++++++++-------- .../__pycache__/__init__.cpython-312.pyc | Bin 227 -> 0 bytes helper_bot/handlers/admin/admin_handlers.py | 483 ++-- .../__pycache__/__init__.cpython-312.pyc | Bin 236 -> 0 bytes .../callback/__pycache__/main.cpython-312.pyc | Bin 15306 -> 0 bytes .../handlers/callback/callback_handlers.py | 10 +- .../__pycache__/__init__.cpython-312.pyc | Bin 227 -> 0 bytes helper_bot/handlers/group/group_handlers.py | 4 +- .../__pycache__/__init__.cpython-312.pyc | Bin 233 -> 0 bytes .../private/__pycache__/main.cpython-312.pyc | Bin 23314 -> 0 bytes .../handlers/private/private_handlers.py | 847 +++--- .../__pycache__/__init__.cpython-312.pyc | Bin 264 -> 0 bytes helper_bot/keyboards/keyboards.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 170 -> 0 bytes .../text_middleware.cpython-312.pyc | Bin 2735 -> 0 bytes .../middlewares/blacklist_middleware.py | 4 +- .../__pycache__/__init__.cpython-312.pyc | Bin 206 -> 0 bytes .../base_dependency_factory.cpython-312.pyc | Bin 2272 -> 0 bytes .../__pycache__/helper_func.cpython-312.pyc | Bin 17749 -> 0 bytes .../__pycache__/messages.cpython-312.pyc | Bin 5461 -> 0 bytes .../utils/__pycache__/state.cpython-312.pyc | Bin 703 -> 0 bytes helper_bot/utils/base_dependency_factory.py | 81 +- helper_bot/utils/helper_func.py | 783 +++--- helper_bot/utils/state.py | 2 + migrations/000_migrations_init.py | 68 +- migrations/001_create_new_tables.py | 126 +- migrations/002_create_tables_media_group.py | 122 +- pytest.ini | 25 +- run_helper.py | 4 +- tests/conftest.py | 234 ++ tests/mocks.py | 52 + tests/test_bot.py | 339 +++ tests/test_db.py | 42 +- tests/test_error_handling.py | 339 +++ tests/test_keyboards_and_filters.py | 330 +++ tests/test_media_handlers.py | 292 ++ tests/test_settings.ini | 13 + tests/test_utils.py | 208 ++ voice_bot/utils/helper_func.py | 4 +- voice_bot/voice_handler/voice_handler.py | 4 +- voice_bot_v2.py | 6 +- 44 files changed, 4783 insertions(+), 2383 deletions(-) create mode 100644 Makefile create mode 100644 README_TESTING.md delete mode 100644 helper_bot/handlers/admin/__pycache__/__init__.cpython-312.pyc delete mode 100644 helper_bot/handlers/callback/__pycache__/__init__.cpython-312.pyc delete mode 100644 helper_bot/handlers/callback/__pycache__/main.cpython-312.pyc delete mode 100644 helper_bot/handlers/group/__pycache__/__init__.cpython-312.pyc delete mode 100644 helper_bot/handlers/private/__pycache__/__init__.cpython-312.pyc delete mode 100644 helper_bot/handlers/private/__pycache__/main.cpython-312.pyc delete mode 100644 helper_bot/keyboards/__pycache__/__init__.cpython-312.pyc delete mode 100644 helper_bot/middlewares/__pycache__/__init__.cpython-312.pyc delete mode 100644 helper_bot/middlewares/__pycache__/text_middleware.cpython-312.pyc delete mode 100644 helper_bot/utils/__pycache__/__init__.cpython-312.pyc delete mode 100644 helper_bot/utils/__pycache__/base_dependency_factory.cpython-312.pyc delete mode 100644 helper_bot/utils/__pycache__/helper_func.cpython-312.pyc delete mode 100644 helper_bot/utils/__pycache__/messages.cpython-312.pyc delete mode 100644 helper_bot/utils/__pycache__/state.cpython-312.pyc create mode 100644 tests/conftest.py create mode 100644 tests/mocks.py create mode 100644 tests/test_bot.py create mode 100644 tests/test_error_handling.py create mode 100644 tests/test_keyboards_and_filters.py create mode 100644 tests/test_media_handlers.py create mode 100644 tests/test_settings.ini create mode 100644 tests/test_utils.py diff --git a/.gitignore b/.gitignore index 851f65f..29527f2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,37 @@ /settings.ini /myenv/ /venv/ +/.idea/ +/logs/*.log + +# Testing and coverage files +.coverage +coverage.xml +htmlcov/ +.pytest_cache/ +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +*.so + +# Test database files +database/test.db +test.db +*.db + +# IDE and editor files +.vscode/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bf4e8f7 --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ +.PHONY: help test test-db test-coverage test-html clean install + +# Default target +help: + @echo "Available commands:" + @echo " install - Install dependencies" + @echo " test - Run all tests" + @echo " test-db - Run database tests only" + @echo " test-bot - Run bot startup and handler tests only" + @echo " test-media - Run media handler tests only" + @echo " test-errors - Run error handling tests only" + @echo " test-utils - Run utility functions tests only" + @echo " test-keyboards - Run keyboard and filter tests only" + @echo " test-coverage - Run tests with coverage report (helper_bot + database)" + @echo " test-html - Run tests and generate HTML coverage report" + @echo " clean - Clean up generated files" + @echo " coverage - Show coverage report only" + +# Install dependencies +install: + python3 -m pip install -r requirements.txt + python3 -m pip install pytest-cov + +# Run all tests +test: + python3 -m pytest tests/ -v + +# Run database tests only +test-db: + python3 -m pytest tests/test_db.py -v + +# Run bot tests only +test-bot: + python3 -m pytest tests/test_bot.py -v + +# Run media handler tests only +test-media: + python3 -m pytest tests/test_media_handlers.py -v + +# Run error handling tests only +test-errors: + python3 -m pytest tests/test_error_handling.py -v + +# Run utils tests only +test-utils: + python3 -m pytest tests/test_utils.py -v + +# Run keyboard and filter tests only +test-keyboards: + python3 -m pytest tests/test_keyboards_and_filters.py -v + +# Run tests with coverage +test-coverage: + python3 -m pytest tests/ --cov=helper_bot --cov=database --cov-report=term + +# Run tests and generate HTML coverage report +test-html: + python3 -m pytest tests/ --cov=helper_bot --cov=database --cov-report=html:htmlcov --cov-report=term + @echo "HTML coverage report generated in htmlcov/index.html" + +# Show coverage report only +coverage: + python3 -m coverage report --include="helper_bot/*,database/*" + +# Clean up generated files +clean: + rm -rf htmlcov/ + rm -f coverage.xml + rm -f .coverage + rm -f database/test.db + rm -f test.db + find . -type d -name "__pycache__" -exec rm -rf {} + + find . -type f -name "*.pyc" -delete diff --git a/README_TESTING.md b/README_TESTING.md new file mode 100644 index 0000000..43a32b8 --- /dev/null +++ b/README_TESTING.md @@ -0,0 +1,259 @@ +# Тестирование Telegram Helper Bot + +Этот документ описывает систему тестирования для Telegram Helper Bot. + +## Структура тестов + +Тесты организованы в следующие файлы: + +- `tests/test_bot.py` - Основные тесты бота (запуск, хэндлеры, интеграция) +- `tests/test_media_handlers.py` - Тесты обработки медиа-контента +- `tests/test_error_handling.py` - Тесты обработки ошибок и граничных случаев +- `tests/test_utils.py` - Тесты утилит и вспомогательных функций +- `tests/test_keyboards_and_filters.py` - Тесты клавиатур и фильтров +- `tests/test_db.py` - Тесты базы данных +- `tests/conftest.py` - Общие фикстуры и конфигурация + +## Установка зависимостей + +```bash +make install +``` + +## Запуск тестов + +### Все тесты +```bash +make test +``` + +### Отдельные категории тестов +```bash +# Тесты базы данных +make test-db + +# Тесты бота (запуск и хэндлеры) +make test-bot + +# Тесты обработки медиа +make test-media + +# Тесты обработки ошибок +make test-errors + +# Тесты утилит +make test-utils + +# Тесты клавиатур и фильтров +make test-keyboards +``` + +### Тесты с покрытием +```bash +# Покрытие с выводом в терминал +make test-coverage + +# Покрытие с HTML отчетом +make test-html +``` + +### Фильтрация тестов +```bash +# Только unit тесты +pytest -m unit + +# Только интеграционные тесты +pytest -m integration + +# Только асинхронные тесты +pytest -m asyncio + +# Исключить медленные тесты +pytest -m "not slow" + +# Конкретный файл тестов +pytest tests/test_bot.py + +# Конкретный тест +pytest tests/test_bot.py::TestBotStartup::test_bot_initialization +``` + +## Типы тестов + +### Unit тесты +Тестируют отдельные функции и компоненты в изоляции: +- Вспомогательные функции (`get_first_name`, `get_text_message`) +- Утилиты (`BaseDependencyFactory`, `get_message`) +- Фильтры (`ChatTypeFilter`) +- Клавиатуры + +### Интеграционные тесты +Тестируют взаимодействие между компонентами: +- Регистрация роутеров в диспетчере +- Обработка сообщений через хэндлеры +- Интеграция с базой данных + +### Асинхронные тесты +Тестируют асинхронные функции: +- Хэндлеры сообщений +- Запуск бота +- Обработка медиа-контента + +## Моки и фикстуры + +### Основные фикстуры +- `mock_message` - Мок сообщения Telegram +- `mock_state` - Мок состояния FSM +- `mock_db` - Мок базы данных +- `mock_bot` - Мок бота +- `mock_dispatcher` - Мок диспетчера +- `mock_factory` - Мок фабрики зависимостей + +### Специализированные фикстуры +- `sample_photo_message` - Сообщение с фото +- `sample_video_message` - Сообщение с видео +- `sample_audio_message` - Сообщение с аудио +- `sample_voice_message` - Голосовое сообщение +- `sample_video_note_message` - Видеокружок +- `sample_media_group` - Медиагруппа +- `sample_text_message` - Текстовое сообщение + +## Покрытие тестами + +### Основные компоненты +- ✅ Запуск бота (`start_bot`) +- ✅ Приватные хэндлеры (`handle_start_message`, `suggest_post`, etc.) +- ✅ Обработка медиа-контента (фото, видео, аудио, голос) +- ✅ Обработка ошибок и исключений +- ✅ Утилиты и вспомогательные функции +- ✅ Клавиатуры и фильтры +- ✅ Фабрика зависимостей + +### Тестируемые сценарии +- ✅ Новые пользователи +- ✅ Существующие пользователи +- ✅ Пользователи без username +- ✅ Обработка различных типов контента +- ✅ Медиагруппы +- ✅ Ошибки при получении стикеров +- ✅ Ошибки базы данных +- ✅ Граничные случаи (пустой текст, отсутствие подписей) + +## Настройка окружения + +### Переменные окружения +Для тестов не требуются реальные токены бота или подключения к базе данных, так как все внешние зависимости замоканы. + +### Конфигурация pytest +Настройки pytest находятся в файле `pytest.ini`: +- Автоматический режим asyncio +- Фильтрация предупреждений +- Маркеры для категоризации тестов + +## Добавление новых тестов + +### Структура теста +```python +@pytest.mark.asyncio +async def test_function_name(mock_message, mock_state, mock_db): + """Описание теста""" + # Arrange (подготовка) + mock_message.text = "test" + + # Act (действие) + result = await function_to_test(mock_message, mock_state) + + # Assert (проверка) + assert result is True + mock_message.answer.assert_called_once() +``` + +### Маркировка тестов +```python +@pytest.mark.unit # Unit тест +@pytest.mark.integration # Интеграционный тест +@pytest.mark.asyncio # Асинхронный тест +@pytest.mark.slow # Медленный тест +``` + +### Использование фикстур +```python +def test_with_fixtures(mock_message, sample_photo_message, mock_db): + # Используем готовые фикстуры + pass +``` + +## Отладка тестов + +### Подробный вывод +```bash +pytest -v -s +``` + +### Остановка на первой ошибке +```bash +pytest -x +``` + +### Вывод полного traceback +```bash +pytest --tb=long +``` + +### Запуск конкретного теста +```bash +pytest tests/test_bot.py::TestPrivateHandlers::test_handle_start_message_new_user -v +``` + +## CI/CD интеграция + +Тесты могут быть интегрированы в CI/CD pipeline: + +```yaml +# Пример для GitHub Actions +- name: Run tests + run: | + make install + make test-coverage +``` + +## Покрытие кода + +Для просмотра покрытия кода: +```bash +make test-html +# Открыть htmlcov/index.html в браузере +``` + +## Лучшие практики + +1. **Изоляция тестов** - каждый тест должен быть независимым +2. **Использование моков** - избегайте реальных внешних зависимостей +3. **Описательные имена** - имена тестов должны описывать что тестируется +4. **Arrange-Act-Assert** - структурируйте тесты по этому паттерну +5. **Фикстуры** - используйте фикстуры для переиспользования кода +6. **Маркировка** - правильно маркируйте тесты для фильтрации + +## Устранение неполадок + +### Ошибки импорта +Убедитесь, что Python path настроен правильно: +```bash +export PYTHONPATH="${PYTHONPATH}:$(pwd)" +``` + +### Ошибки asyncio +Для асинхронных тестов используйте маркер `@pytest.mark.asyncio` + +### Ошибки моков +Проверьте, что все внешние зависимости замоканы: +```python +with patch('module.function') as mock_func: + # тест +``` + +### Медленные тесты +Используйте маркер `@pytest.mark.slow` для медленных тестов и исключайте их при необходимости: +```bash +pytest -m "not slow" +``` diff --git a/database/db.py b/database/db.py index ce6621d..498fbb0 100644 --- a/database/db.py +++ b/database/db.py @@ -1,1169 +1,1207 @@ -import os -import sqlite3 -from datetime import datetime - -from logs.custom_logger import logger - - -class BotDB: - def __init__(self, current_dir, name): - self.db_file = os.path.join(current_dir, name) - self.conn = None - self.cursor = None - self.logger = logger - self.logger.info(f'Инициация базы данных: {self.db_file}') - - def connect(self): - """Создание соединения и курсора.""" - self.conn = sqlite3.connect(self.db_file) - self.cursor = self.conn.cursor() - - def create_table(self, sql_script): - """ - Создает таблицу в базе. Используется в миграциях - - Args: - sql_script: DDL скрипт таблицы - - Returns: - None - """ - try: - self.connect() - self.cursor.execute(sql_script) - self.conn.commit() - self.logger.info(f'Таблица создана: {sql_script}') - except Exception as e: - self.logger.error(f'Ошибка при создании таблицы. Данные: {sql_script} Ошибка: {e}') - raise - finally: - self.close() - - def get_current_version(self): - """ - Возвращает текущую последнюю версию миграции - - Args: - None - - Returns: - int: Версия последней миграции. - """ - self.logger.info(f'Попытка получения версии миграции') - try: - self.connect() - self.cursor.execute("SELECT version FROM migrations ORDER BY version DESC LIMIT 1") - version = self.cursor.fetchone()[0] - self.logger.info(f'Получена текущая версия миграции: {version}') - return version - except Exception as e: - self.logger.error(f'Ошибка при получении текущей версии миграции: {str(e)}') - raise - finally: - self.close() - - def update_version(self, new_version: int, script_name: str): - """ - Обновляет версию миграций в таблице migrations. - - Добавляет новую запись в таблицу migrations с указанной версией, - именем скрипта и текущей датой и временем. - - Args: - new_version (int): Новая версия миграции - script_name (str): Имя скрипта миграции - - Returns: - None - - Raises: - sqlite3. IntegrityError: Если возникает ошибка целостности при вставке - данных в таблицу migrations. - Exception: Если возникает любая другая ошибка при обновлении версии. - """ - self.logger.info(f'Попытка обновления версии: {new_version}, название скрипта: {script_name}') - try: - self.connect() - today = datetime.now().strftime("%d-%m-%Y %H:%M:%S") - self.cursor.execute( - "INSERT INTO migrations (version, script_name, created_at) VALUES(?, ?, ?)", - (new_version, script_name, today), - ) - self.conn.commit() - self.logger.info(f"Версия обновлена: {new_version}, название скрипта: {script_name}") - except sqlite3.IntegrityError as e: - self.logger.error(f"Ошибка при обновлении версии: {e}") - raise - except Exception as e: - self.logger.error(f"Ошибка при обновлении версии: {e}") - raise - finally: - self.close() - - # TODO: Deprecated. Остался только в voice боте, удалить и оттуда - def get_error_message_from_db(self, id: int): - """ - @deprecated - Функция для запроса к базе данных и получения сообщений ошибки. В аргументы передаются: - id - идентификатор ошибки - """ - # Подключаемся к базе - try: - self.connect() - self.cursor.execute(f"SELECT * FROM error_messages WHERE id=?", (id,)) - response_from_database = str(self.cursor.fetchone()[1]) - return response_from_database - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении сообщения об ошибка voice_bot: {error}") - finally: - self.close() - - def add_new_user_in_db(self, user_id: int, first_name: str, full_name: str, username: str, is_bot: bool, - language_code: str, date_added: str, - date_changed: str): - """ - Добавляет нового пользователя в базу данных. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - first_name (str): Имя пользователя. - full_name (str): Полное имя пользователя. - username (str): Username пользователя в Telegram. - is_bot (bool): Флаг, указывающий, является ли пользователь ботом. - language_code (str): Код языка пользователя. - date_added (str): Дата добавления пользователя в базу. - date_changed (str): Дата последнего изменения данных пользователя. - - Returns: - None: Если запись успешно добавлена в базу. - Exception: Если произошла ошибка при добавлении записи. - """ - self.logger.info(f"Попытка добавить пользователя в базу данных: user_id={user_id}, first_name={first_name}") - try: - self.connect() - self.cursor.execute("INSERT INTO 'our_users' ('user_id', 'first_name', 'full_name', 'username', 'is_bot', " - "'language_code', 'date_added', 'date_changed') VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - (user_id, first_name, full_name, - username, is_bot, language_code, date_added, date_changed)) - self.conn.commit() - self.logger.info(f"Новый пользователь добавлен в базу: user_id={user_id}, first_name={first_name}") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка при добавлении пользователя в базу: {error}. " - f"Данные пользователя: user_id={user_id}, first_name={first_name}") - raise - finally: - self.close() - - def user_exists(self, user_id: int): - """ - Проверяет, существует ли пользователь в базе данных. - - Args: - user_id (int): Идентификатор пользователя. - - Returns: - bool: True, если пользователь найден, False - иначе. - """ - self.logger.info(f"Попытка проверки существования пользователя: user_id={user_id}") - try: - self.connect() - self.cursor.execute("SELECT id FROM our_users WHERE user_id = ?", (user_id,)) - result = self.cursor.fetchall() - self.logger.info(f"Проверка существования пользователя: user_id={user_id}, результат={result}") - return bool(len(result)) - except sqlite3.Error as error: - self.logger.error(f"Ошибка при проверке существования пользователя: {error}") - raise - finally: - self.close() - - def get_user_id(self, user_id: int): - """ - @deprecated - Возвращает ID пользователя в базе данных по его user_id. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - int: ID пользователя в базе данных. - None: Если пользователь не найден. - """ - self.logger.info(f"Попытка получения ID пользователя в базе данных для user_id={user_id}") - try: - self.connect() - self.cursor.execute("SELECT id FROM our_users WHERE user_id = ?", (user_id,)) - result = self.cursor.fetchone() - if result: - user_id_db = result[0] - self.logger.info(f"ID пользователя в базе найден: user_id={user_id}, id_db={user_id_db}") - return user_id_db - else: - self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении ID пользователя из базы данных: {error}") - raise - finally: - self.close() - - def get_username(self, user_id: int): - """ - Возвращает username пользователя из базы данных по его user_id в Telegram. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - str: Username пользователя. - None: Если пользователь не найден. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - try: - self.connect() - self.cursor.execute("SELECT username FROM our_users WHERE user_id = ?", (user_id,)) - result = self.cursor.fetchone() - if result: - username = result[0] - self.logger.info(f"Username пользователя найден: user_id={user_id}, username={username}") - return username - else: - self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении username из базы данных: {error}") - raise - finally: - self.close() - - def get_user_id_by_username(self, username: str): - """ - Возвращает user_id пользователя из базы данных по его user_name в Telegram. - - Args: - username (str): Username пользователя. - - Returns: - user_id (int): Идентификатор пользователя в Telegram. - None: Если пользователь не найден. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - try: - self.connect() - self.cursor.execute("SELECT user_id FROM our_users WHERE username = ?", (username,)) - result = self.cursor.fetchone() - if result: - user_id = result[0] - self.logger.info(f"User_id пользователя найден: username={username}, user_id={user_id}") - return user_id - else: - self.logger.info(f"Пользователь с username={username} не найден в базе данных.") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении username из базы данных: {error}") - raise - finally: - self.close() - - def get_full_name_by_id(self, user_id: str): - """ - Возвращает full_name пользователя из базы данных по его username в Telegram. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - full_name (str): Username пользователя. - None: Если пользователь не найден. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - try: - self.connect() - self.cursor.execute("SELECT full_name FROM our_users WHERE user_id = ?", (user_id,)) - result = self.cursor.fetchone() - if result: - full_name = result[0] - self.logger.info(f"Username пользователя найден: user_id={user_id}, full_name={full_name}") - return full_name - else: - self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении username из базы данных: {error}") - raise - finally: - self.close() - - def get_all_user_id(self): - """ - Возвращает список всех user_id из базы данных. - - Returns: - list: Список user_id. - []: Если в базе данных нет пользователей. - - Raises: - sqlite3. Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Попытка получения всех user_id") - try: - self.connect() - self.cursor.execute("SELECT user_id FROM our_users") - fetch_all = self.cursor.fetchall() - list_of_users = [user_id[0] for user_id in fetch_all] - self.logger.info(f"Получен список всех user_id: {list_of_users}") - return list_of_users - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении списка user_id из базы данных: {error}") - raise - finally: - self.close() - - def get_user_first_name(self, user_id: int): - """ - Возвращает имя пользователя из базы данных по его user_id в Telegram. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - str: Имя пользователя. - None: Если пользователь не найден. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Попытка получения имени пользователя по user_id={user_id}") - try: - self.connect() - self.cursor.execute("SELECT first_name FROM our_users WHERE user_id = ?", (user_id,)) - result = self.cursor.fetchone() - if result: - first_name = result[0] - self.logger.info(f"Имя пользователя найдено: user_id={user_id}, first_name={first_name}") - return first_name - else: - self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении имени пользователя из базы данных: {error}") - raise - finally: - self.close() - - def get_info_about_stickers(self, user_id: int): - """ - Проверяет, получил ли пользователь стикеры. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - bool: True, если пользователь получил стикеры, False - иначе. - None: Если пользователь не найден. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Попытка проверки получил ли пользователь с user_id={user_id} стикеры.") - try: - self.connect() - self.cursor.execute("SELECT has_stickers FROM our_users WHERE user_id = ?", (user_id,)) - result = self.cursor.fetchone() - if result: - has_stickers = result[0] == 1 - self.logger.info( - f"Проверено получение стикеров пользователем: user_id={user_id}, has_stickers={has_stickers}") - return has_stickers - else: - self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении информации о получении стикеров: {error}") - raise - finally: - self.close() - - def update_info_about_stickers(self, user_id): - """ - Обновляет информацию о получении стикеров пользователем. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - None: Если обновление успешно выполнено. - - Raises: - sqlite3. Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции update_info_about_stickers. Параметры: user_id={user_id}") - try: - self.connect() - self.cursor.execute("UPDATE our_users SET has_stickers = 1 WHERE user_id = ?", (user_id,)) - self.conn.commit() - self.logger.info(f"Информация о получении стикеров обновлена: user_id={user_id}") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка при обновлении информации о получении стикеров: {error}") - raise - finally: - self.close() - - def get_users_blacklist(self): - """ - Возвращает список пользователей в черном списке. - - Returns: - dict: Словарь, где ключ - user_id, значение - username. - {}: Если в черном списке нет пользователей. - - Raises: - sqlite3. Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции get_users_blacklist") - try: - self.connect() - self.cursor.execute("SELECT user_id, user_name FROM blacklist") - fetch_all = self.cursor.fetchall() - list_of_users = {user_id: username for user_id, username in fetch_all} - self.logger.info(f"Получен список пользователей в черном списке") - return list_of_users - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении списка пользователей в черном списке: {error}") - raise - finally: - self.close() - - def get_users_for_unblock_today(self, date_to_unban: str): - """ - Возвращает список пользователей, у которых истекает срок блокировки сегодня. - - Args: - date_to_unban (str): Дата разблокировки. - - Returns: - dict: Словарь, где ключ - user_id, значение - username. - {}: Если сегодня нет пользователей, у которых истекает срок блокировки. - - Raises: - sqlite3. Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции get_users_for_unblock_today: date_to_unban={date_to_unban}") - try: - self.connect() - result = self.cursor.execute("SELECT user_id, user_name " - "FROM blacklist WHERE date_to_unban = ?", (date_to_unban,)) - fetch_all = result.fetchall() - list_of_users = {user_id: username for user_id, username in fetch_all} - self.logger.info(f"Получен список пользователей для разблокировки сегодня: {list_of_users}") - return list_of_users - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении списка пользователей для разблокировки: {error}") - raise - finally: - self.close() - - def get_blacklist_users_by_id(self, user_id: int): - """ - Возвращает информацию о пользователе в черном списке по user_id. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - tuple: Кортеж (user_id, user_name, message_for_user, date_to_unban). - None: Если пользователь не найден в черном списке. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции get_blacklist_users_by_id: user_id={user_id}") - try: - self.connect() - result = self.cursor.execute("SELECT user_id, user_name, message_for_user, date_to_unban " - "FROM blacklist WHERE user_id = ?", (user_id,)) - return self.cursor.fetchone() - except sqlite3.Error as error: - self.logger.error(f"Ошибка при получении информации о пользователе в черном списке: {error}") - raise - finally: - self.close() - - def check_user_in_blacklist(self, user_id: int): - """ - Проверяет, существует ли запись с данным user_id в blacklist. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - bool: True, если пользователь найден в черном списке, False - иначе. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции check_user_in_blacklist: user_id={user_id}") - try: - self.connect() - self.cursor.execute("SELECT 1 FROM blacklist WHERE user_id = ?", (user_id,)) - result = self.cursor.fetchone() - self.logger.info(f"Существует ли пользователь: user_id={user_id} Итог: {result}") - return bool(result) - except sqlite3.Error as error: - self.logger.error(f"Ошибка при проверке пользователя в черном списке. user_id: {user_id} : {error}") - raise - finally: - self.close() - - def set_user_blacklist(self, user_id: int, user_name=None, message_for_user=None, date_to_unban=None): - """ - Добавляет пользователя в черный список. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - user_name (str, optional): Username пользователя. Defaults to None. - message_for_user (str, optional): Сообщение для пользователя. Defaults to None. - date_to_unban (datetime, optional): Дата разблокировки. Defaults to None. - - Returns: - None: Если добавление в черный список успешно выполнено. - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции set_user_blacklist: user_id={user_id}, user_name={user_name}," - f" message_for_user={message_for_user}, date_to_unban={date_to_unban}") - try: - self.connect() - result = self.cursor.execute("INSERT INTO 'blacklist' ('user_id', 'user_name'," - " 'message_for_user', 'date_to_unban') VALUES (?, ?, ?, ?)", - (user_id, user_name, message_for_user, date_to_unban,)) - self.conn.commit() - self.logger.info(f"Пользователь добавлен в черный список: user_id={user_id}") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка при добавлении пользователя в черный список: {error}") - return error - finally: - self.close() - - def delete_user_blacklist(self, user_id: int): - """ - Удаляет пользователя из черного списка. - - Args: - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - bool: True, если удаление прошло успешно, False - в случае ошибки. - - Raises: - None: Ошибки обрабатываются в блоке except, возвращая False. - """ - self.logger.info(f"Запуск функции delete_user_blacklist: user_id={user_id}") - try: - self.connect() - self.cursor.execute("DELETE FROM blacklist WHERE user_id = ?", (user_id,)) - self.conn.commit() - self.logger.info(f"Пользователь с идентификатором {user_id} успешно удален из черного списка.") - return True - except sqlite3.Error as error: - self.logger.error(f"Ошибка удаления пользователя с идентификатором {user_id} " - f"из таблицы blacklist. Ошибка: {str(error)}") - return False - finally: - self.close() - - def add_new_message_in_db(self, message_text: str, user_id: int, message_id: int, date: str): - """ - Добавляет новое сообщение пользователя в базу данных. - - Args: - message_text (str): Текст сообщения. - user_id (int): Идентификатор пользователя в Telegram. - message_id (int): Идентификатор сообщения в Telegram. - date (str): Дата отправки сообщения. - - Returns: - None: Если добавление прошло успешно. - sqlite3.Error: Если произошла ошибка при выполнении запроса. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info( - f"Запуск функции add_new_message_in_db: user_id={user_id}, message_id={message_id}, date={date}") - try: - self.connect() - self.cursor.execute( - "INSERT INTO user_messages (message_text, user_id, message_id, date) " - "VALUES (?, ?, ?, ?)", - (message_text, user_id, message_id, date)) - self.conn.commit() - self.logger.info(f"Новое сообщение добавлено в базу данных: message_id={message_id}") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка добавления сообщения в базу данных: {error}") - raise - finally: - self.close() - - def get_username_and_full_name(self, user_id: int): - """ - Получает full_name и username пользователя по ID из базы - - Args: - date (str): Новая дата изменения. - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - username (str): username пользователя - full_name (str): full_name пользователя - """ - self.logger.info( - f"Запуск функции check_username_and_first_name: user_id={user_id}") - try: - self.connect() - self.cursor.execute("SELECT username FROM our_users WHERE user_id = ?", (user_id,)) - username = self.cursor.fetchone()[0] - self.cursor.execute("SELECT full_name FROM our_users WHERE user_id = ?", (user_id,)) - full_name = self.cursor.fetchone()[0] - self.logger.info( - f"Функция check_username_and_first_name успешно отработала: user_id={user_id}, username={username}, full_name={full_name}") - return username, full_name - except sqlite3.Error as error: - self.logger.error(f"Ошибка в функции get_username_and_first_name: {error}") - return None - finally: - self.close() - - def update_username_and_full_name(self, user_id: int, username: str, full_name: str): - """ - Обновляет full_name и username пользователя - - Args: - username (str): username пользователя - full_name (str): full_name пользователя - user_id (int): Идентификатор пользователя в Telegram - - Returns: - True (bool): Если обновления прошли успешно - sqlite3. Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info( - f"Запуск функции update_username_and_full_name: user_id={user_id}, username={username}, full_name={full_name}") - try: - self.connect() - self.cursor.execute("UPDATE our_users SET username = ?, full_name = ? WHERE user_id = ?", - (username, full_name, user_id,)) - self.conn.commit() - self.logger.info( - f"Функция update_username_and_full_name. Данные пользователя: user_id={user_id} успешно обновлены") - return True - except sqlite3.Error as error: - self.logger.error(f"Ошибка в функции update_username_and_full_name: {error}") - raise - finally: - self.close() - - def update_date_for_user(self, date: str, user_id: int): - """ - #TODO: Не возвращается ошибка sqlite3. Error. Тест не перехватывает. Возвращается no such table: our_users - Обновляет дату последнего изменения данных пользователя в базе. - - Args: - date (str): Новая дата изменения. - user_id (int): Идентификатор пользователя в Telegram. - - Returns: - None: Если обновление прошло успешно. - sqlite3. Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции update_date_for_user: user_id={user_id}, date={date}") - try: - self.connect() - self.cursor.execute("UPDATE our_users SET date_changed = ? WHERE user_id = ?", - (date, user_id,)) - self.conn.commit() - self.logger.info(f"Дата изменения обновлена для пользователя: user_id={user_id}") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка обновления даты изменения для пользователя: {error}") - return error - finally: - self.close() - - def is_admin(self, user_id: int): - """ - Проверяет, является ли пользователь администратором. - - Args: - user_id: ID пользователя Telegram. - - Returns: - True, если пользователь администратор, иначе False. - - Raises: - None: В случае ошибки возвращается None - """ - self.logger.info(f"Запуск функции is_admin: user_id={user_id}") - try: - self.connect() - self.cursor.execute("SELECT 1 FROM admins WHERE user_id = ?", (user_id,)) - result = self.cursor.fetchone() - return bool(result) - except sqlite3.Error as error: - self.logger.error(f"Ошибка добавления сообщения в базу данных: {error}") - return None - finally: - self.close() - - def add_admin(self, user_id: int, role: str): - """ - Добавляет пользователя в список администраторов. - - Args: - user_id (int): ID пользователя Telegram. - role (str): Роль пользователя. Доступные варианты: - 1. creator - создатель - 2. admin - обычная роль - - Returns: - None: Если добавление прошло успешно. - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции add_admin: user_id={user_id}, role={role}") - try: - self.connect() - self.cursor.execute("INSERT INTO admins (user_id, role) VALUES (?, ?)", (user_id, role)) - self.conn.commit() - self.logger.info(f"Пользователь с user_id={user_id} добавлен в список администраторов с ролью {role}.") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка добавления пользователя в список администраторов: {error}") - return error - finally: - self.close() - - def remove_admin(self, user_id: int): - """ - Удаляет пользователя из списка администраторов. - - Args: - user_id (int): ID пользователя Telegram. - - Returns: - None: Если удаление прошло успешно. - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции remove_admin: user_id={user_id}") - try: - self.connect() - self.cursor.execute("DELETE FROM admins WHERE user_id = ?", (user_id,)) - self.conn.commit() - self.logger.info(f"Пользователь с user_id={user_id} удален из списка администраторов.") - return None - except sqlite3.Error as error: - self.logger.error(f"Ошибка удаления пользователя из списка администраторов: {error}") - return error - finally: - self.connect() - - def get_user_by_message_id(self, message_id: int): - """ - #TODO: Возвращается TypeError вместо None - Возвращает идентификатор пользователя по идентификатору сообщения. - - Args: - message_id (int): Идентификатор сообщения в Telegram. - - Returns: - int: Идентификатор пользователя. - None: Если пользователь не найден. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции get_user_by_message_id: message_id={message_id}") - try: - self.connect() - result = self.cursor.execute("SELECT user_id FROM user_messages WHERE message_id = ?", (message_id,)) - user = result.fetchone()[0] - self.logger.info(f"Пользователь успешно получен user_id={user} по message_id={message_id}") - return user - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения user_id по message_id: {error}") - raise - finally: - self.close() - - def get_last_users_from_db(self): - """ - Возвращает список идентификаторов последних 30 пользователей, обращавшихся в бот. - - Returns: - list: Список кортежей (full_name, user_id) последних 30 пользователей. - []: Если в базе данных нет пользователей. - - Raises: - sqlite3. Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info("Запуск функции get_last_users_from_db") - try: - self.connect() - result = self.cursor.execute("SELECT full_name, user_id FROM our_users ORDER BY date_changed DESC LIMIT 30") - users = result.fetchall() - self.logger.info(f"Получен список последних 30 пользователей: {users}") - return users - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения списка последних пользователей: {error}") - raise - finally: - self.close() - - def get_banned_users_from_db(self): - """ - Возвращает список идентификаторов пользователей в черном списке бота. - - Returns: - list: Список кортежей (user_name, user_id, message_for_user, date_to_unban) пользователей в черном списке. - []: Если в черном списке нет пользователей. - - Raises: - sqlite3.Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info("Запуск функции get_banned_users_from_db") - try: - self.connect() - result = self.cursor.execute("SELECT user_name, user_id, message_for_user, date_to_unban FROM blacklist") - users = result.fetchall() - self.logger.info(f"Получен список пользователей в черном списке: {users}") - return users - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения списка пользователей в черном списке: {error}") - raise - finally: - self.close() - - def get_banned_users_from_db_with_limits(self, offset: int, limit: int): - """ - Возвращает список идентификаторов пользователей в черном списке бота с учетом смещения и ограничения. - - Args: - offset (int): Смещение для выборки - limit (int): Ограничение количества возвращаемых записей. - - Returns: - list: Список кортежей (user_name, user_id, message_for_user, date_to_unban) пользователей в черном списке. - []: Если в черном списке нет пользователей или количество записей с учетом смещения и ограничения равно 0. - - Raises: - sqlite3. Error: Если произошла ошибка при выполнении запроса. - """ - self.logger.info(f"Запуск функции get_banned_users_from_db_with_limits: offset={offset}, limit={limit}") - try: - self.connect() - result = self.cursor.execute("SELECT user_name, user_id, message_for_user, date_to_unban " - "FROM blacklist LIMIT ?, ?", (offset, limit,)) - users = result.fetchall() - self.logger.info(f"Получен список пользователей в черном списке (offset={offset}, limit={limit}): {users}") - return users - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения списка пользователей в черном списке: {error}") - raise - finally: - self.close() - - def get_post_content_from_telegram_by_last_id(self, last_post_id: int): - self.logger.info( - f"Запуск функции get_post_content_from_telegram_by_last_id, идентификатор поста {last_post_id}") - try: - self.connect() - result = self.cursor.execute(""" - SELECT cpft.content_name, cpft.content_type - FROM post_from_telegram_suggest pft - JOIN message_link_to_content mltc - ON pft.message_id = mltc.post_id - JOIN content_post_from_telegram cpft - ON cpft.message_id = mltc.message_id - WHERE pft.helper_text_message_id = ? - """, (last_post_id,)) - post_content = result.fetchall() - self.logger.info( - f"Функция get_post_content_from_telegram_by_last_id получила список контента: {post_content}") - return post_content - finally: - self.close() - - def get_post_ids_from_telegram_by_last_id(self, last_post_id: int): - self.logger.info( - f"Запуск функции get_post_ids_from_telegram_by_last_id, идентификатор поста {last_post_id}") - try: - self.connect() - result = self.cursor.execute(""" - SELECT mltc.message_id - FROM post_from_telegram_suggest pft - JOIN message_link_to_content mltc - ON pft.message_id = mltc.post_id - WHERE pft.helper_text_message_id = ? - """, (last_post_id,)) - post_ids = result.fetchall() - self.logger.info(f"Функция get_post_ids_from_telegram_by_last_id " - f"получила идентификаторы сообщений: {post_ids}") - return post_ids - except Exception as e: - self.logger.error(f"Ошибка в функции get_post_ids_from_telegram_by_last_id {str(e)}") - return False - finally: - self.close() - - def get_post_text_from_telegram_by_last_id(self, last_post_id: int): - self.logger.info(f"Запуск функции get_post_text_from_telegram_by_last_id, идентификатор поста {last_post_id}") - try: - self.connect() - result = self.cursor.execute("SELECT text " - "FROM post_from_telegram_suggest WHERE helper_text_message_id = ?", - (last_post_id,)) - text = result.fetchone()[0] - self.logger.info(f"Функция get_post_text_from_telegram_by_last_id получила text") - return text - except Exception as e: - self.logger.error(f"Ошибка в функции get_post_text_from_telegram_by_last_id {str(e)}") - - def get_author_id_by_message_id(self, message_id: int): - self.logger.info(f"Запуск функции get_author_id_by_message_id, идентификатор поста {message_id}") - try: - self.connect() - result = self.cursor.execute("SELECT author_id " - "FROM post_from_telegram_suggest WHERE message_id = ?", - (message_id,)) - author_id = result.fetchone()[0] - self.logger.info(f"Функция get_author_id_by_message_id получила author_id {author_id}") - return author_id - except Exception as e: - self.logger.error(f"Ошибка в функции get_author_id_by_message_id {str(e)}") - - def get_author_id_by_helper_message_id(self, helper_text_message_id: int): - self.logger.info(f"Запуск функции get_author_id_by_helper_message_id, идентификатор поста " - f"{helper_text_message_id}") - try: - self.connect() - result = self.cursor.execute("SELECT author_id " - "FROM post_from_telegram_suggest WHERE helper_text_message_id = ?", - (helper_text_message_id,)) - author_id = result.fetchone()[0] - self.logger.info(f"Функция get_author_id_by_helper_message_id получила author_id {author_id}") - return author_id - except Exception as e: - self.logger.error(f"Ошибка в функции get_author_id_by_helper_message_id {str(e)}") - - def add_post_content_in_db(self, post_id: int, message_id: int, content_name: str, type_content: str): - self.logger.info( - f"Запуск функции add_post_content_in_db: post_id={post_id}, message_id={message_id}, " - f"content_name={content_name}, content_type={type_content}") - try: - self.connect() - self.cursor.execute( - "INSERT INTO message_link_to_content (post_id, message_id)" - "VALUES (?, ?)", (post_id, message_id)) - self.conn.commit() - self.cursor.execute( - "INSERT INTO content_post_from_telegram (message_id, content_name, content_type)" - "VALUES (?, ?, ?)", (message_id, content_name, type_content)) - self.conn.commit() - self.logger.info(f"Функция add_post_content_in_db отработала успешно") - return True - except Exception as e: - self.logger.error(f"Ошибка в функции add_post_content_in_db при добавлении поста в базу данных: {e}") - return False - - def add_post_in_db(self, message_id: int, text: str, author_id: int): - self.logger.info( - f"Запуск функции add_post_in_db: message_id={message_id}, " - f"author_id={author_id}") - try: - today = datetime.now().strftime("%d-%m-%Y %H:%M:%S") - self.connect() - self.cursor.execute( - "INSERT INTO post_from_telegram_suggest (message_id, text, author_id, created_at)" - "VALUES (?, ?, ?, ?)", (message_id, text, author_id, today)) - self.conn.commit() - self.logger.info(f"Функция add_post_in_db отработала успешно") - return True - except Exception as e: - self.logger.error(f"Ошибка в функции add_post_in_db при добавлении поста в базу данных: {e}") - return False - - def update_helper_message_in_db(self, message_id: int, helper_message_id: int): - self.logger.info( - f"Запуск функции update_helper_message_in_db: message_id={message_id}, " - f"helper_message_id={helper_message_id}") - try: - self.connect() - self.cursor.execute( - "UPDATE post_from_telegram_suggest SET helper_text_message_id = ? WHERE message_id = ?", - (helper_message_id, message_id,)) - self.conn.commit() - self.logger.info(f"Функция update_helper_message_in_db отработала успешно") - return True - except Exception as e: - self.logger.error(f"Ошибка в функции update_helper_message_in_db при добавлении поста в базу данных: {e}") - return False - - def add_audio_record(self, file_name, author_id, date_added, listen_count, file_id): - """Добавляет информацию о войсе юзера в БД""" - self.logger.info( - f"Запуск функции add_audio_record (file_name = {file_name}, author_id = {author_id}," - f" date_added = {date_added}") - try: - self.connect() - result = self.cursor.execute( - "INSERT INTO `audio_message_reference` (file_name, author_id, date_added, listen_count, file_id) " - "VALUES (?, ?, ?, ?, ?)", - (file_name, author_id, date_added, listen_count, file_id)) - self.conn.commit() - self.logger.info( - f"Аудио успешно добавлено в БД (file_name = {file_name}, author_id = {author_id}, " - f"date_added = {date_added}") - return None - except sqlite3.Error as error: - print(error) - raise - finally: - self.close() - - def last_date_audio(self): - """Получаем дату последнего войса""" - self.logger.info( - f"Запуск функции last_date_audio") - try: - self.connect() - result = self.cursor.execute( - "SELECT `date_added` FROM `audio_message_reference` ORDER BY date_added DESC LIMIT 1") - last_date = result.fetchone()[0] - self.logger.info(f"Последняя дата сообщения {last_date}") - return last_date - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения последней даты войса: {error}") - raise - finally: - self.close() - - def get_last_user_audio_record(self, user_id): - """Получает данные о количестве записей пользователя""" - self.logger.info( - f"Запуск функции get_last_user_audio_record. user_id={user_id}") - try: - self.connect() - r = self.cursor.execute("SELECT `file_id` FROM `audio_message_reference` WHERE `author_id` = ?", - (user_id,)) - result = bool(len(r.fetchall())) - self.logger.info( - f"Результат функции get_last_user_audio_record: {result}") - return result - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения последней даты войса: {error}") - raise - finally: - self.close() - - def get_id_for_audio_record(self, user_id): - """Получает ID аудио сообщения пользователя""" - self.logger.info( - f"Запуск функции get_id_for_audio_record. user_id={user_id}") - try: - self.connect() - r = self.cursor.execute( - "SELECT `file_id` FROM `audio_message_reference` WHERE `author_id` = ? " - "ORDER BY date_added DESC LIMIT 1", - (user_id,)) - result = r.fetchone()[0] - self.logger.info( - f"Результат функции get_id_for_audio_record: {result}") - return result - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения последней даты войса: {error}") - raise - finally: - self.close() - - def get_path_for_audio_record(self, user_id): - """Получает данные о названии файла""" - self.logger.info( - f"Запуск функции get_path_for_audio_record. user_id={user_id}") - try: - self.connect() - r = self.cursor.execute( - "SELECT `file_name` " - "FROM `audio_message_reference` " - "WHERE `author_id` = ? " - "ORDER BY date_added " - "DESC LIMIT 1", - (user_id,)) - result = r.fetchone()[0] - self.logger.info( - f"Результат функции get_path_for_audio_record: {result}") - return result - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения последней даты войса: {error}") - raise - finally: - self.close() - - def check_listen_audio(self, user_id): - """Проверяет прослушано ли аудио пользователем""" - self.logger.info( - f"Запуск функции check_listen_audio. user_id={user_id}") - try: - self.connect() - query_listen_audio = self.cursor.execute( - """SELECT l.file_name - FROM audio_message_reference a - LEFT JOIN listen_audio_users l ON l.file_name = a.file_name - WHERE l.user_id = ? - AND l.file_name IS NOT NULL""", (user_id,)) - check_sign = query_listen_audio.fetchall() - query_all_audio = self.cursor.execute('SELECT file_name FROM audio_message_reference WHERE author_id <> ?', - (user_id,)) - sign_all_audio = query_all_audio.fetchall() - new_sign1 = list(set(sign_all_audio) - set(check_sign)) - new_sign = [] - for i in new_sign1: - new_sign.append(i[0]) - self.logger.info( - f"Функция check_listen_audio успешно отработала.") - return new_sign - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения последней даты войса: {error}") - raise - finally: - self.close() - - def mark_listened_audio(self, file_name, user_id): - """Отмечает аудио прослушанным для конкретного пользователя.""" - self.logger.info( - f"Запуск функции mark_listened_audio. file_name={file_name}, user_id={user_id}") - try: - self.connect() - result = self.cursor.execute( - "INSERT INTO `listen_audio_users` (file_name, user_id, is_listen) VALUES (?, ?, ?)", - (file_name, user_id, 1)) - return self.conn.commit() - except sqlite3.Error as error: - self.logger.error(f"Ошибка получения последней даты войса: {error}") - raise - finally: - self.close() - - def close(self): - """Закрытие соединения и курсора.""" - if self.cursor: - self.cursor.close() - if self.conn: - self.conn.close() +import os +import sqlite3 +from datetime import datetime + +from logs.custom_logger import logger + + +class BotDB: + def __init__(self, current_dir, name): + # Формируем правильный путь к базе данных + if name.startswith('database/'): + self.db_file = os.path.join(current_dir, name) + else: + self.db_file = os.path.join(current_dir, 'database', name) + self.conn = None + self.cursor = None + self.logger = logger + self.logger.info(f'Инициация базы данных: {self.db_file}') + + def connect(self): + """Создание соединения и курсора.""" + self.conn = sqlite3.connect(self.db_file) + self.cursor = self.conn.cursor() + + def create_table(self, sql_script): + """ + Создает таблицу в базе. Используется в миграциях + + Args: + sql_script: DDL скрипт таблицы + + Returns: + None + """ + try: + self.connect() + self.cursor.execute(sql_script) + self.conn.commit() + self.logger.info(f'Таблица создана: {sql_script}') + except Exception as e: + self.logger.error(f'Ошибка при создании таблицы. Данные: {sql_script} Ошибка: {e}') + raise + finally: + self.close() + + def get_current_version(self): + """ + Возвращает текущую последнюю версию миграции + + Args: + None + + Returns: + int: Версия последней миграции. + """ + self.logger.info(f'Попытка получения версии миграции') + try: + self.connect() + self.cursor.execute("SELECT version FROM migrations ORDER BY version DESC LIMIT 1") + version = self.cursor.fetchone()[0] + self.logger.info(f'Получена текущая версия миграции: {version}') + return version + except Exception as e: + self.logger.error(f'Ошибка при получении текущей версии миграции: {str(e)}') + raise + finally: + self.close() + + def update_version(self, new_version: int, script_name: str): + """ + Обновляет версию миграций в таблице migrations. + + Добавляет новую запись в таблицу migrations с указанной версией, + именем скрипта и текущей датой и временем. + + Args: + new_version (int): Новая версия миграции + script_name (str): Имя скрипта миграции + + Returns: + None + + Raises: + sqlite3. IntegrityError: Если возникает ошибка целостности при вставке + данных в таблицу migrations. + Exception: Если возникает любая другая ошибка при обновлении версии. + """ + self.logger.info(f'Попытка обновления версии: {new_version}, название скрипта: {script_name}') + try: + self.connect() + today = datetime.now().strftime("%d-%m-%Y %H:%M:%S") + self.cursor.execute( + "INSERT INTO migrations (version, script_name, created_at) VALUES(?, ?, ?)", + (new_version, script_name, today), + ) + self.conn.commit() + self.logger.info(f"Версия обновлена: {new_version}, название скрипта: {script_name}") + except sqlite3.IntegrityError as e: + self.logger.error(f"Ошибка при обновлении версии: {e}") + raise + except Exception as e: + self.logger.error(f"Ошибка при обновлении версии: {e}") + raise + finally: + self.close() + + # TODO: Deprecated. Остался только в voice боте, удалить и оттуда + def get_error_message_from_db(self, id: int): + """ + @deprecated + Функция для запроса к базе данных и получения сообщений ошибки. В аргументы передаются: + id - идентификатор ошибки + """ + # Подключаемся к базе + try: + self.connect() + self.cursor.execute(f"SELECT * FROM error_messages WHERE id=?", (id,)) + response_from_database = str(self.cursor.fetchone()[1]) + return response_from_database + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении сообщения об ошибка voice_bot: {error}") + finally: + self.close() + + def add_new_user_in_db(self, user_id: int, first_name: str, full_name: str, username: str, is_bot: bool, + language_code: str, date_added: str, + date_changed: str): + """ + Добавляет нового пользователя в базу данных. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + first_name (str): Имя пользователя. + full_name (str): Полное имя пользователя. + username (str): Username пользователя в Telegram. + is_bot (bool): Флаг, указывающий, является ли пользователь ботом. + language_code (str): Код языка пользователя. + date_added (str): Дата добавления пользователя в базу. + date_changed (str): Дата последнего изменения данных пользователя. + + Returns: + None: Если запись успешно добавлена в базу. + Exception: Если произошла ошибка при добавлении записи. + """ + self.logger.info(f"Попытка добавить пользователя в базу данных: user_id={user_id}, first_name={first_name}") + try: + self.connect() + self.cursor.execute("INSERT INTO 'our_users' ('user_id', 'first_name', 'full_name', 'username', 'is_bot', " + "'language_code', 'date_added', 'date_changed') VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + (user_id, first_name, full_name, + username, is_bot, language_code, date_added, date_changed)) + self.conn.commit() + self.logger.info(f"Новый пользователь добавлен в базу: user_id={user_id}, first_name={first_name}") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при добавлении пользователя в базу: {error}. " + f"Данные пользователя: user_id={user_id}, first_name={first_name}") + raise + finally: + self.close() + + def user_exists(self, user_id: int): + """ + Проверяет, существует ли пользователь в базе данных. + + Args: + user_id (int): Идентификатор пользователя. + + Returns: + bool: True, если пользователь найден, False - иначе. + """ + self.logger.info(f"Попытка проверки существования пользователя: user_id={user_id}") + try: + self.connect() + self.cursor.execute("SELECT id FROM our_users WHERE user_id = ?", (user_id,)) + result = self.cursor.fetchall() + self.logger.info(f"Проверка существования пользователя: user_id={user_id}, результат={result}") + return bool(len(result)) + except sqlite3.Error as error: + self.logger.error(f"Ошибка при проверке существования пользователя: {error}") + raise + finally: + self.close() + + def get_user_id(self, user_id: int): + """ + @deprecated + Возвращает ID пользователя в базе данных по его user_id. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + int: ID пользователя в базе данных. + None: Если пользователь не найден. + """ + self.logger.info(f"Попытка получения ID пользователя в базе данных для user_id={user_id}") + try: + self.connect() + self.cursor.execute("SELECT id FROM our_users WHERE user_id = ?", (user_id,)) + result = self.cursor.fetchone() + if result: + user_id_db = result[0] + self.logger.info(f"ID пользователя в базе найден: user_id={user_id}, id_db={user_id_db}") + return user_id_db + else: + self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении ID пользователя из базы данных: {error}") + raise + finally: + self.close() + + def get_username(self, user_id: int): + """ + Возвращает username пользователя из базы данных по его user_id в Telegram. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + str: Username пользователя. + None: Если пользователь не найден. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + try: + self.connect() + self.cursor.execute("SELECT username FROM our_users WHERE user_id = ?", (user_id,)) + result = self.cursor.fetchone() + if result: + username = result[0] + self.logger.info(f"Username пользователя найден: user_id={user_id}, username={username}") + return username + else: + self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении username из базы данных: {error}") + raise + finally: + self.close() + + def get_user_id_by_username(self, username: str): + """ + Возвращает user_id пользователя из базы данных по его user_name в Telegram. + + Args: + username (str): Username пользователя. + + Returns: + user_id (int): Идентификатор пользователя в Telegram. + None: Если пользователь не найден. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + try: + self.connect() + self.cursor.execute("SELECT user_id FROM our_users WHERE username = ?", (username,)) + result = self.cursor.fetchone() + if result: + user_id = result[0] + self.logger.info(f"User_id пользователя найден: username={username}, user_id={user_id}") + return user_id + else: + self.logger.info(f"Пользователь с username={username} не найден в базе данных.") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении username из базы данных: {error}") + raise + finally: + self.close() + + def get_full_name_by_id(self, user_id: str): + """ + Возвращает full_name пользователя из базы данных по его username в Telegram. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + full_name (str): Username пользователя. + None: Если пользователь не найден. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + try: + self.connect() + self.cursor.execute("SELECT full_name FROM our_users WHERE user_id = ?", (user_id,)) + result = self.cursor.fetchone() + if result: + full_name = result[0] + self.logger.info(f"Username пользователя найден: user_id={user_id}, full_name={full_name}") + return full_name + else: + self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении username из базы данных: {error}") + raise + finally: + self.close() + + def get_user_info_by_id(self, user_id: int): + """ + Возвращает информацию о пользователе из базы данных по его user_id. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + dict: Словарь с информацией о пользователе (username, full_name). + None: Если пользователь не найден. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + try: + self.connect() + self.cursor.execute("SELECT username, full_name FROM our_users WHERE user_id = ?", (user_id,)) + result = self.cursor.fetchone() + if result: + user_info = { + 'username': result[0], + 'full_name': result[1] + } + self.logger.info(f"Информация о пользователе найдена: user_id={user_id}, username={user_info['username']}, full_name={user_info['full_name']}") + return user_info + else: + self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении информации о пользователе из базы данных: {error}") + raise + finally: + self.close() + + def get_all_user_id(self): + """ + Возвращает список всех user_id из базы данных. + + Returns: + list: Список user_id. + []: Если в базе данных нет пользователей. + + Raises: + sqlite3. Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Попытка получения всех user_id") + try: + self.connect() + self.cursor.execute("SELECT user_id FROM our_users") + fetch_all = self.cursor.fetchall() + list_of_users = [user_id[0] for user_id in fetch_all] + self.logger.info(f"Получен список всех user_id: {list_of_users}") + return list_of_users + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении списка user_id из базы данных: {error}") + raise + finally: + self.close() + + def get_user_first_name(self, user_id: int): + """ + Возвращает имя пользователя из базы данных по его user_id в Telegram. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + str: Имя пользователя. + None: Если пользователь не найден. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Попытка получения имени пользователя по user_id={user_id}") + try: + self.connect() + self.cursor.execute("SELECT first_name FROM our_users WHERE user_id = ?", (user_id,)) + result = self.cursor.fetchone() + if result: + first_name = result[0] + self.logger.info(f"Имя пользователя найдено: user_id={user_id}, first_name={first_name}") + return first_name + else: + self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении имени пользователя из базы данных: {error}") + raise + finally: + self.close() + + def get_info_about_stickers(self, user_id: int): + """ + Проверяет, получил ли пользователь стикеры. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + bool: True, если пользователь получил стикеры, False - иначе. + None: Если пользователь не найден. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Попытка проверки получил ли пользователь с user_id={user_id} стикеры.") + try: + self.connect() + self.cursor.execute("SELECT has_stickers FROM our_users WHERE user_id = ?", (user_id,)) + result = self.cursor.fetchone() + if result: + has_stickers = result[0] == 1 + self.logger.info( + f"Проверено получение стикеров пользователем: user_id={user_id}, has_stickers={has_stickers}") + return has_stickers + else: + self.logger.info(f"Пользователь с user_id={user_id} не найден в базе данных.") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении информации о получении стикеров: {error}") + raise + finally: + self.close() + + def update_info_about_stickers(self, user_id): + """ + Обновляет информацию о получении стикеров пользователем. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + None: Если обновление успешно выполнено. + + Raises: + sqlite3. Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции update_info_about_stickers. Параметры: user_id={user_id}") + try: + self.connect() + self.cursor.execute("UPDATE our_users SET has_stickers = 1 WHERE user_id = ?", (user_id,)) + self.conn.commit() + self.logger.info(f"Информация о получении стикеров обновлена: user_id={user_id}") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при обновлении информации о получении стикеров: {error}") + raise + finally: + self.close() + + def get_users_blacklist(self): + """ + Возвращает список пользователей в черном списке. + + Returns: + dict: Словарь, где ключ - user_id, значение - username. + {}: Если в черном списке нет пользователей. + + Raises: + sqlite3. Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции get_users_blacklist") + try: + self.connect() + self.cursor.execute("SELECT user_id, user_name FROM blacklist") + fetch_all = self.cursor.fetchall() + list_of_users = {user_id: username for user_id, username in fetch_all} + self.logger.info(f"Получен список пользователей в черном списке") + return list_of_users + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении списка пользователей в черном списке: {error}") + raise + finally: + self.close() + + def get_users_for_unblock_today(self, date_to_unban: str): + """ + Возвращает список пользователей, у которых истекает срок блокировки сегодня. + + Args: + date_to_unban (str): Дата разблокировки. + + Returns: + dict: Словарь, где ключ - user_id, значение - username. + {}: Если сегодня нет пользователей, у которых истекает срок блокировки. + + Raises: + sqlite3. Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции get_users_for_unblock_today: date_to_unban={date_to_unban}") + try: + self.connect() + result = self.cursor.execute("SELECT user_id, user_name " + "FROM blacklist WHERE date_to_unban = ?", (date_to_unban,)) + fetch_all = result.fetchall() + list_of_users = {user_id: username for user_id, username in fetch_all} + self.logger.info(f"Получен список пользователей для разблокировки сегодня: {list_of_users}") + return list_of_users + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении списка пользователей для разблокировки: {error}") + raise + finally: + self.close() + + def get_blacklist_users_by_id(self, user_id: int): + """ + Возвращает информацию о пользователе в черном списке по user_id. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + tuple: Кортеж (user_id, user_name, message_for_user, date_to_unban). + None: Если пользователь не найден в черном списке. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции get_blacklist_users_by_id: user_id={user_id}") + try: + self.connect() + result = self.cursor.execute("SELECT user_id, user_name, message_for_user, date_to_unban " + "FROM blacklist WHERE user_id = ?", (user_id,)) + return self.cursor.fetchone() + except sqlite3.Error as error: + self.logger.error(f"Ошибка при получении информации о пользователе в черном списке: {error}") + raise + finally: + self.close() + + def check_user_in_blacklist(self, user_id: int): + """ + Проверяет, существует ли запись с данным user_id в blacklist. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + bool: True, если пользователь найден в черном списке, False - иначе. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции check_user_in_blacklist: user_id={user_id}") + try: + self.connect() + self.cursor.execute("SELECT 1 FROM blacklist WHERE user_id = ?", (user_id,)) + result = self.cursor.fetchone() + self.logger.info(f"Существует ли пользователь: user_id={user_id} Итог: {result}") + return bool(result) + except sqlite3.Error as error: + self.logger.error(f"Ошибка при проверке пользователя в черном списке. user_id: {user_id} : {error}") + raise + finally: + self.close() + + def set_user_blacklist(self, user_id: int, user_name=None, message_for_user=None, date_to_unban=None): + """ + Добавляет пользователя в черный список. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + user_name (str, optional): Username пользователя. Defaults to None. + message_for_user (str, optional): Сообщение для пользователя. Defaults to None. + date_to_unban (datetime, optional): Дата разблокировки. Defaults to None. + + Returns: + None: Если добавление в черный список успешно выполнено. + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции set_user_blacklist: user_id={user_id}, user_name={user_name}," + f" message_for_user={message_for_user}, date_to_unban={date_to_unban}") + try: + self.connect() + result = self.cursor.execute("INSERT INTO 'blacklist' ('user_id', 'user_name'," + " 'message_for_user', 'date_to_unban') VALUES (?, ?, ?, ?)", + (user_id, user_name, message_for_user, date_to_unban,)) + self.conn.commit() + self.logger.info(f"Пользователь добавлен в черный список: user_id={user_id}") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка при добавлении пользователя в черный список: {error}") + return error + finally: + self.close() + + def delete_user_blacklist(self, user_id: int): + """ + Удаляет пользователя из черного списка. + + Args: + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + bool: True, если удаление прошло успешно, False - в случае ошибки. + + Raises: + None: Ошибки обрабатываются в блоке except, возвращая False. + """ + self.logger.info(f"Запуск функции delete_user_blacklist: user_id={user_id}") + try: + self.connect() + self.cursor.execute("DELETE FROM blacklist WHERE user_id = ?", (user_id,)) + self.conn.commit() + self.logger.info(f"Пользователь с идентификатором {user_id} успешно удален из черного списка.") + return True + except sqlite3.Error as error: + self.logger.error(f"Ошибка удаления пользователя с идентификатором {user_id} " + f"из таблицы blacklist. Ошибка: {str(error)}") + return False + finally: + self.close() + + def add_new_message_in_db(self, message_text: str, user_id: int, message_id: int, date: str): + """ + Добавляет новое сообщение пользователя в базу данных. + + Args: + message_text (str): Текст сообщения. + user_id (int): Идентификатор пользователя в Telegram. + message_id (int): Идентификатор сообщения в Telegram. + date (str): Дата отправки сообщения. + + Returns: + None: Если добавление прошло успешно. + sqlite3.Error: Если произошла ошибка при выполнении запроса. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info( + f"Запуск функции add_new_message_in_db: user_id={user_id}, message_id={message_id}, date={date}") + try: + self.connect() + self.cursor.execute( + "INSERT INTO user_messages (message_text, user_id, message_id, date) " + "VALUES (?, ?, ?, ?)", + (message_text, user_id, message_id, date)) + self.conn.commit() + self.logger.info(f"Новое сообщение добавлено в базу данных: message_id={message_id}") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка добавления сообщения в базу данных: {error}") + raise + finally: + self.close() + + def get_username_and_full_name(self, user_id: int): + """ + Получает full_name и username пользователя по ID из базы + + Args: + date (str): Новая дата изменения. + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + username (str): username пользователя + full_name (str): full_name пользователя + """ + self.logger.info( + f"Запуск функции check_username_and_first_name: user_id={user_id}") + try: + self.connect() + self.cursor.execute("SELECT username FROM our_users WHERE user_id = ?", (user_id,)) + username = self.cursor.fetchone()[0] + self.cursor.execute("SELECT full_name FROM our_users WHERE user_id = ?", (user_id,)) + full_name = self.cursor.fetchone()[0] + self.logger.info( + f"Функция check_username_and_first_name успешно отработала: user_id={user_id}, username={username}, full_name={full_name}") + return username, full_name + except sqlite3.Error as error: + self.logger.error(f"Ошибка в функции get_username_and_first_name: {error}") + return None + finally: + self.close() + + def update_username_and_full_name(self, user_id: int, username: str, full_name: str): + """ + Обновляет full_name и username пользователя + + Args: + username (str): username пользователя + full_name (str): full_name пользователя + user_id (int): Идентификатор пользователя в Telegram + + Returns: + True (bool): Если обновления прошли успешно + sqlite3. Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info( + f"Запуск функции update_username_and_full_name: user_id={user_id}, username={username}, full_name={full_name}") + try: + self.connect() + self.cursor.execute("UPDATE our_users SET username = ?, full_name = ? WHERE user_id = ?", + (username, full_name, user_id,)) + self.conn.commit() + self.logger.info( + f"Функция update_username_and_full_name. Данные пользователя: user_id={user_id} успешно обновлены") + return True + except sqlite3.Error as error: + self.logger.error(f"Ошибка в функции update_username_and_full_name: {error}") + raise + finally: + self.close() + + def update_date_for_user(self, date: str, user_id: int): + """ + #TODO: Не возвращается ошибка sqlite3. Error. Тест не перехватывает. Возвращается no such table: our_users + Обновляет дату последнего изменения данных пользователя в базе. + + Args: + date (str): Новая дата изменения. + user_id (int): Идентификатор пользователя в Telegram. + + Returns: + None: Если обновление прошло успешно. + sqlite3. Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции update_date_for_user: user_id={user_id}, date={date}") + try: + self.connect() + self.cursor.execute("UPDATE our_users SET date_changed = ? WHERE user_id = ?", + (date, user_id,)) + self.conn.commit() + self.logger.info(f"Дата изменения обновлена для пользователя: user_id={user_id}") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка обновления даты изменения для пользователя: {error}") + return error + finally: + self.close() + + def is_admin(self, user_id: int): + """ + Проверяет, является ли пользователь администратором. + + Args: + user_id: ID пользователя Telegram. + + Returns: + True, если пользователь администратор, иначе False. + + Raises: + None: В случае ошибки возвращается None + """ + self.logger.info(f"Запуск функции is_admin: user_id={user_id}") + try: + self.connect() + self.cursor.execute("SELECT 1 FROM admins WHERE user_id = ?", (user_id,)) + result = self.cursor.fetchone() + return bool(result) + except sqlite3.Error as error: + self.logger.error(f"Ошибка добавления сообщения в базу данных: {error}") + return None + finally: + self.close() + + def add_admin(self, user_id: int, role: str): + """ + Добавляет пользователя в список администраторов. + + Args: + user_id (int): ID пользователя Telegram. + role (str): Роль пользователя. Доступные варианты: + 1. creator - создатель + 2. admin - обычная роль + + Returns: + None: Если добавление прошло успешно. + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции add_admin: user_id={user_id}, role={role}") + try: + self.connect() + self.cursor.execute("INSERT INTO admins (user_id, role) VALUES (?, ?)", (user_id, role)) + self.conn.commit() + self.logger.info(f"Пользователь с user_id={user_id} добавлен в список администраторов с ролью {role}.") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка добавления пользователя в список администраторов: {error}") + return error + finally: + self.close() + + def remove_admin(self, user_id: int): + """ + Удаляет пользователя из списка администраторов. + + Args: + user_id (int): ID пользователя Telegram. + + Returns: + None: Если удаление прошло успешно. + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции remove_admin: user_id={user_id}") + try: + self.connect() + self.cursor.execute("DELETE FROM admins WHERE user_id = ?", (user_id,)) + self.conn.commit() + self.logger.info(f"Пользователь с user_id={user_id} удален из списка администраторов.") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка удаления пользователя из списка администраторов: {error}") + return error + finally: + self.connect() + + def get_user_by_message_id(self, message_id: int): + """ + #TODO: Возвращается TypeError вместо None + Возвращает идентификатор пользователя по идентификатору сообщения. + + Args: + message_id (int): Идентификатор сообщения в Telegram. + + Returns: + int: Идентификатор пользователя. + None: Если пользователь не найден. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции get_user_by_message_id: message_id={message_id}") + try: + self.connect() + result = self.cursor.execute("SELECT user_id FROM user_messages WHERE message_id = ?", (message_id,)) + user = result.fetchone()[0] + self.logger.info(f"Пользователь успешно получен user_id={user} по message_id={message_id}") + return user + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения user_id по message_id: {error}") + raise + finally: + self.close() + + def get_last_users_from_db(self): + """ + Возвращает список идентификаторов последних 30 пользователей, обращавшихся в бот. + + Returns: + list: Список кортежей (full_name, user_id) последних 30 пользователей. + []: Если в базе данных нет пользователей. + + Raises: + sqlite3. Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info("Запуск функции get_last_users_from_db") + try: + self.connect() + result = self.cursor.execute("SELECT full_name, user_id FROM our_users ORDER BY date_changed DESC LIMIT 30") + users = result.fetchall() + self.logger.info(f"Получен список последних 30 пользователей: {users}") + return users + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения списка последних пользователей: {error}") + raise + finally: + self.close() + + def get_banned_users_from_db(self): + """ + Возвращает список идентификаторов пользователей в черном списке бота. + + Returns: + list: Список кортежей (user_name, user_id, message_for_user, date_to_unban) пользователей в черном списке. + []: Если в черном списке нет пользователей. + + Raises: + sqlite3.Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info("Запуск функции get_banned_users_from_db") + try: + self.connect() + result = self.cursor.execute("SELECT user_name, user_id, message_for_user, date_to_unban FROM blacklist") + users = result.fetchall() + self.logger.info(f"Получен список пользователей в черном списке: {users}") + return users + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения списка пользователей в черном списке: {error}") + raise + finally: + self.close() + + def get_banned_users_from_db_with_limits(self, offset: int, limit: int): + """ + Возвращает список идентификаторов пользователей в черном списке бота с учетом смещения и ограничения. + + Args: + offset (int): Смещение для выборки + limit (int): Ограничение количества возвращаемых записей. + + Returns: + list: Список кортежей (user_name, user_id, message_for_user, date_to_unban) пользователей в черном списке. + []: Если в черном списке нет пользователей или количество записей с учетом смещения и ограничения равно 0. + + Raises: + sqlite3. Error: Если произошла ошибка при выполнении запроса. + """ + self.logger.info(f"Запуск функции get_banned_users_from_db_with_limits: offset={offset}, limit={limit}") + try: + self.connect() + result = self.cursor.execute("SELECT user_name, user_id, message_for_user, date_to_unban " + "FROM blacklist LIMIT ?, ?", (offset, limit,)) + users = result.fetchall() + self.logger.info(f"Получен список пользователей в черном списке (offset={offset}, limit={limit}): {users}") + return users + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения списка пользователей в черном списке: {error}") + raise + finally: + self.close() + + def get_post_content_from_telegram_by_last_id(self, last_post_id: int): + self.logger.info( + f"Запуск функции get_post_content_from_telegram_by_last_id, идентификатор поста {last_post_id}") + try: + self.connect() + result = self.cursor.execute(""" + SELECT cpft.content_name, cpft.content_type + FROM post_from_telegram_suggest pft + JOIN message_link_to_content mltc + ON pft.message_id = mltc.post_id + JOIN content_post_from_telegram cpft + ON cpft.message_id = mltc.message_id + WHERE pft.helper_text_message_id = ? + """, (last_post_id,)) + post_content = result.fetchall() + self.logger.info( + f"Функция get_post_content_from_telegram_by_last_id получила список контента: {post_content}") + return post_content + finally: + self.close() + + def get_post_ids_from_telegram_by_last_id(self, last_post_id: int): + self.logger.info( + f"Запуск функции get_post_ids_from_telegram_by_last_id, идентификатор поста {last_post_id}") + try: + self.connect() + result = self.cursor.execute(""" + SELECT mltc.message_id + FROM post_from_telegram_suggest pft + JOIN message_link_to_content mltc + ON pft.message_id = mltc.post_id + WHERE pft.helper_text_message_id = ? + """, (last_post_id,)) + post_ids = result.fetchall() + self.logger.info(f"Функция get_post_ids_from_telegram_by_last_id " + f"получила идентификаторы сообщений: {post_ids}") + return post_ids + except Exception as e: + self.logger.error(f"Ошибка в функции get_post_ids_from_telegram_by_last_id {str(e)}") + return False + finally: + self.close() + + def get_post_text_from_telegram_by_last_id(self, last_post_id: int): + self.logger.info(f"Запуск функции get_post_text_from_telegram_by_last_id, идентификатор поста {last_post_id}") + try: + self.connect() + result = self.cursor.execute("SELECT text " + "FROM post_from_telegram_suggest WHERE helper_text_message_id = ?", + (last_post_id,)) + text = result.fetchone()[0] + self.logger.info(f"Функция get_post_text_from_telegram_by_last_id получила text") + return text + except Exception as e: + self.logger.error(f"Ошибка в функции get_post_text_from_telegram_by_last_id {str(e)}") + + def get_author_id_by_message_id(self, message_id: int): + self.logger.info(f"Запуск функции get_author_id_by_message_id, идентификатор поста {message_id}") + try: + self.connect() + result = self.cursor.execute("SELECT author_id " + "FROM post_from_telegram_suggest WHERE message_id = ?", + (message_id,)) + author_id = result.fetchone()[0] + self.logger.info(f"Функция get_author_id_by_message_id получила author_id {author_id}") + return author_id + except Exception as e: + self.logger.error(f"Ошибка в функции get_author_id_by_message_id {str(e)}") + + def get_author_id_by_helper_message_id(self, helper_text_message_id: int): + self.logger.info(f"Запуск функции get_author_id_by_helper_message_id, идентификатор поста " + f"{helper_text_message_id}") + try: + self.connect() + result = self.cursor.execute("SELECT author_id " + "FROM post_from_telegram_suggest WHERE helper_text_message_id = ?", + (helper_text_message_id,)) + author_id = result.fetchone()[0] + self.logger.info(f"Функция get_author_id_by_helper_message_id получила author_id {author_id}") + return author_id + except Exception as e: + self.logger.error(f"Ошибка в функции get_author_id_by_helper_message_id {str(e)}") + + def add_post_content_in_db(self, post_id: int, message_id: int, content_name: str, type_content: str): + self.logger.info( + f"Запуск функции add_post_content_in_db: post_id={post_id}, message_id={message_id}, " + f"content_name={content_name}, content_type={type_content}") + try: + self.connect() + self.cursor.execute( + "INSERT INTO message_link_to_content (post_id, message_id)" + "VALUES (?, ?)", (post_id, message_id)) + self.conn.commit() + self.cursor.execute( + "INSERT INTO content_post_from_telegram (message_id, content_name, content_type)" + "VALUES (?, ?, ?)", (message_id, content_name, type_content)) + self.conn.commit() + self.logger.info(f"Функция add_post_content_in_db отработала успешно") + return True + except Exception as e: + self.logger.error(f"Ошибка в функции add_post_content_in_db при добавлении поста в базу данных: {e}") + return False + + def add_post_in_db(self, message_id: int, text: str, author_id: int): + self.logger.info( + f"Запуск функции add_post_in_db: message_id={message_id}, " + f"author_id={author_id}") + try: + today = datetime.now().strftime("%d-%m-%Y %H:%M:%S") + self.connect() + self.cursor.execute( + "INSERT INTO post_from_telegram_suggest (message_id, text, author_id, created_at)" + "VALUES (?, ?, ?, ?)", (message_id, text, author_id, today)) + self.conn.commit() + self.logger.info(f"Функция add_post_in_db отработала успешно") + return True + except Exception as e: + self.logger.error(f"Ошибка в функции add_post_in_db при добавлении поста в базу данных: {e}") + return False + + def update_helper_message_in_db(self, message_id: int, helper_message_id: int): + self.logger.info( + f"Запуск функции update_helper_message_in_db: message_id={message_id}, " + f"helper_message_id={helper_message_id}") + try: + self.connect() + self.cursor.execute( + "UPDATE post_from_telegram_suggest SET helper_text_message_id = ? WHERE message_id = ?", + (helper_message_id, message_id,)) + self.conn.commit() + self.logger.info(f"Функция update_helper_message_in_db отработала успешно") + return True + except Exception as e: + self.logger.error(f"Ошибка в функции update_helper_message_in_db при добавлении поста в базу данных: {e}") + return False + + def add_audio_record(self, file_name, author_id, date_added, listen_count, file_id): + """Добавляет информацию о войсе юзера в БД""" + self.logger.info( + f"Запуск функции add_audio_record (file_name = {file_name}, author_id = {author_id}," + f" date_added = {date_added}") + try: + self.connect() + result = self.cursor.execute( + "INSERT INTO `audio_message_reference` (file_name, author_id, date_added, listen_count, file_id) " + "VALUES (?, ?, ?, ?, ?)", + (file_name, author_id, date_added, listen_count, file_id)) + self.conn.commit() + self.logger.info( + f"Аудио успешно добавлено в БД (file_name = {file_name}, author_id = {author_id}, " + f"date_added = {date_added}") + return None + except sqlite3.Error as error: + print(error) + raise + finally: + self.close() + + def last_date_audio(self): + """Получаем дату последнего войса""" + self.logger.info( + f"Запуск функции last_date_audio") + try: + self.connect() + result = self.cursor.execute( + "SELECT `date_added` FROM `audio_message_reference` ORDER BY date_added DESC LIMIT 1") + last_date = result.fetchone()[0] + self.logger.info(f"Последняя дата сообщения {last_date}") + return last_date + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения последней даты войса: {error}") + raise + finally: + self.close() + + def get_last_user_audio_record(self, user_id): + """Получает данные о количестве записей пользователя""" + self.logger.info( + f"Запуск функции get_last_user_audio_record. user_id={user_id}") + try: + self.connect() + r = self.cursor.execute("SELECT `file_id` FROM `audio_message_reference` WHERE `author_id` = ?", + (user_id,)) + result = bool(len(r.fetchall())) + self.logger.info( + f"Результат функции get_last_user_audio_record: {result}") + return result + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения последней даты войса: {error}") + raise + finally: + self.close() + + def get_id_for_audio_record(self, user_id): + """Получает ID аудио сообщения пользователя""" + self.logger.info( + f"Запуск функции get_id_for_audio_record. user_id={user_id}") + try: + self.connect() + r = self.cursor.execute( + "SELECT `file_id` FROM `audio_message_reference` WHERE `author_id` = ? " + "ORDER BY date_added DESC LIMIT 1", + (user_id,)) + result = r.fetchone()[0] + self.logger.info( + f"Результат функции get_id_for_audio_record: {result}") + return result + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения последней даты войса: {error}") + raise + finally: + self.close() + + def get_path_for_audio_record(self, user_id): + """Получает данные о названии файла""" + self.logger.info( + f"Запуск функции get_path_for_audio_record. user_id={user_id}") + try: + self.connect() + r = self.cursor.execute( + "SELECT `file_name` " + "FROM `audio_message_reference` " + "WHERE `author_id` = ? " + "ORDER BY date_added " + "DESC LIMIT 1", + (user_id,)) + result = r.fetchone()[0] + self.logger.info( + f"Результат функции get_path_for_audio_record: {result}") + return result + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения последней даты войса: {error}") + raise + finally: + self.close() + + def check_listen_audio(self, user_id): + """Проверяет прослушано ли аудио пользователем""" + self.logger.info( + f"Запуск функции check_listen_audio. user_id={user_id}") + try: + self.connect() + query_listen_audio = self.cursor.execute( + """SELECT l.file_name + FROM audio_message_reference a + LEFT JOIN listen_audio_users l ON l.file_name = a.file_name + WHERE l.user_id = ? + AND l.file_name IS NOT NULL""", (user_id,)) + check_sign = query_listen_audio.fetchall() + query_all_audio = self.cursor.execute('SELECT file_name FROM audio_message_reference WHERE author_id <> ?', + (user_id,)) + sign_all_audio = query_all_audio.fetchall() + new_sign1 = list(set(sign_all_audio) - set(check_sign)) + new_sign = [] + for i in new_sign1: + new_sign.append(i[0]) + self.logger.info( + f"Функция check_listen_audio успешно отработала.") + return new_sign + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения последней даты войса: {error}") + raise + finally: + self.close() + + def mark_listened_audio(self, file_name, user_id): + """Отмечает аудио прослушанным для конкретного пользователя.""" + self.logger.info( + f"Запуск функции mark_listened_audio. file_name={file_name}, user_id={user_id}") + try: + self.connect() + result = self.cursor.execute( + "INSERT INTO `listen_audio_users` (file_name, user_id, is_listen) VALUES (?, ?, ?)", + (file_name, user_id, 1)) + return self.conn.commit() + except sqlite3.Error as error: + self.logger.error(f"Ошибка получения последней даты войса: {error}") + raise + finally: + self.close() + + def close(self): + """Закрытие соединения и курсора.""" + if self.cursor: + self.cursor.close() + if self.conn: + self.conn.close() diff --git a/helper_bot/handlers/admin/__pycache__/__init__.cpython-312.pyc b/helper_bot/handlers/admin/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 6c6f3148d7c76b44511038f65d2b802a47ceae87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 227 zcmX@j%ge<81U;Qo(=>qeV-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9i2>VNJ$c zJc%i}nR)R=`K2YPMShx0xA-6e8Hsr*IjKd(Ma)2TD;Yk6O#9{IViglmnVgYWlp9c# zpOuB^G=4F<|$LkeT{^GF7 g%}*)KNwq8D02&T*Pcew`ftit!@g9Rl5gU*L0E}HfCIA2c diff --git a/helper_bot/handlers/admin/admin_handlers.py b/helper_bot/handlers/admin/admin_handlers.py index 3f64ed7..abf0bae 100644 --- a/helper_bot/handlers/admin/admin_handlers.py +++ b/helper_bot/handlers/admin/admin_handlers.py @@ -1,179 +1,304 @@ -import traceback - -from aiogram import Router, types, F -from aiogram.filters import Command, StateFilter -from aiogram.fsm.context import FSMContext - -from helper_bot.filters.main import ChatTypeFilter -from helper_bot.keyboards.keyboards import get_reply_keyboard_admin, create_keyboard_with_pagination, \ - create_keyboard_for_ban_days, create_keyboard_for_approve_ban, create_keyboard_for_ban_reason -from helper_bot.utils.base_dependency_factory import BaseDependencyFactory -from helper_bot.utils.helper_func import check_access, add_days_to_date, get_banned_users_buttons, get_banned_users_list -from logs.custom_logger import logger - -admin_router = Router() - -bdf = BaseDependencyFactory() -GROUP_FOR_POST = bdf.settings['Telegram']['group_for_posts'] -GROUP_FOR_MESSAGE = bdf.settings['Telegram']['group_for_message'] -MAIN_PUBLIC = bdf.settings['Telegram']['main_public'] -GROUP_FOR_LOGS = bdf.settings['Telegram']['group_for_logs'] -IMPORTANT_LOGS = bdf.settings['Telegram']['important_logs'] -PREVIEW_LINK = bdf.settings['Telegram']['preview_link'] -LOGS = bdf.settings['Settings']['logs'] -TEST = bdf.settings['Settings']['test'] - -BotDB = bdf.get_db() - - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - Command('admin') -) -async def admin_panel(message: types.Message, state: FSMContext): - try: - if check_access(message.from_user.id): - await state.set_state("ADMIN") - logger.info(f"Запуск админ панели для пользователя: {message.from_user.id}") - markup = get_reply_keyboard_admin() - await message.answer("Добро пожаловать в админку. Выбери что хочешь:", - reply_markup=markup) - else: - await message.answer('Доступ запрещен, досвидания!') - except Exception as e: - logger.error(f"Ошибка при запуске админ панели: {e}") - await message.bot.send_message(IMPORTANT_LOGS, - f'Ошибка в функции admin_panel {e}. Traceback: {traceback.format_exc()}') - - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - StateFilter("ADMIN"), - F.text == 'Бан (Список)' -) -async def get_last_users(message: types.Message): - logger.info( - f"Попытка получения списка последних пользователей. Текст сообщения: {message.text} Имя автора сообщения: {message.from_user.full_name})") - list_users = BotDB.get_last_users_from_db() - keyboard = create_keyboard_with_pagination(1, len(list_users), list_users, 'ban') - await message.answer(text="Список пользователей которые последними обращались к боту", - reply_markup=keyboard) - - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - StateFilter("ADMIN"), - F.text == 'Бан по нику' -) -async def ban_by_nickname(message: types.Message, state: FSMContext): - await message.answer('Пришли мне username блокируемого пользователя') - await state.set_state('PRE_BAN') - - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - F.text == 'Отменить' -) -async def decline_ban(message: types.Message, state: FSMContext): - await state.set_data({}) - await state.set_state("ADMIN") - logger.info(f"Отмена процедуры блокировки") - markup = get_reply_keyboard_admin() - await message.answer('Вернулись в меню', reply_markup=markup) - - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - StateFilter("PRE_BAN") -) -async def ban_by_nickname_step_2(message: types.Message, state: FSMContext): - logger.info( - f"Функция ban_by_nickname_2. Получен никнейм пользователя: {message.text}") - user_name = message.text - user_id = BotDB.get_user_id_by_username(user_name) - await state.update_data(user_id=user_id, user_name=user_name, message_for_user=None, - date_to_unban=None) - full_name = BotDB.get_full_name_by_id(user_id) - markup = create_keyboard_for_ban_reason() - await message.answer( - text=f"Выбран пользователь:\nid: {user_id}\nusername: {user_name}\n" - f"Имя:{full_name}\nВыбери причину бана из списка или напиши ее в чат", - reply_markup=markup) - await state.set_state('BAN_2') - - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - StateFilter("ADMIN"), - F.text == 'Разбан (список)' -) -async def get_banned_users(message): - logger.info( - f"Попытка получения списка заблокированных пользователей. Текст сообщения: {message.text} Имя автора сообщения: {message.from_user.full_name})") - message_text = get_banned_users_list(0) - buttons_list = get_banned_users_buttons() - if buttons_list: - k = create_keyboard_with_pagination(1, len(buttons_list), buttons_list, 'unlock') - await message.answer(text=message_text, reply_markup=k) - else: - await message.answer(text="В списке забанненых пользователей никого нет") - - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - StateFilter("BAN_2") -) -async def ban_user_step_2(message: types.Message, state: FSMContext): - user_data = await state.get_data() - logger.info(f"Переход на шаг 2 бана пользователя. Словарь с данными для бана: {user_data})") - await state.update_data(message_for_user=message.text) - markup = create_keyboard_for_ban_days() - await message.answer(f"Выбрана причина: {message.text}. Выбери срок бана в днях или напиши " - f"его в чат", reply_markup=markup) - await state.set_state("BAN_3") - - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - StateFilter("BAN_3") -) -async def ban_user_step_3(message: types.Message, state: FSMContext): - logger.info(f"ban_user_step_3. Расчет даты разбана. Входные данные {message.text}") - if message.text != 'Навсегда': - count_days = int(message.text) - date_to_unban = add_days_to_date(count_days) - else: - date_to_unban = None - logger.info(f"ban_user_step_3. Расчет даты разбана. date_to_unban: {date_to_unban}") - await state.update_data(date_to_unban=date_to_unban) - user_data = await state.get_data() - markup = create_keyboard_for_approve_ban() - await message.answer( - f"Необходимо подтверждение:\nПользователь:{user_data['user_id']}\nПричина бана:{user_data['message_for_user']}\nСрок бана:{user_data['date_to_unban']}", - reply_markup=markup) - await state.set_state("BAN_FINAL") - - -@admin_router.message( - ChatTypeFilter(chat_type=["private"]), - StateFilter("BAN_FINAL"), - F.text == 'Подтвердить' -) -async def approve_ban(message: types.Message, state: FSMContext): - user_data = await state.get_data() - logger.info(f"Переход на финальный шаг бана пользователя. Словарь с данными для бана: {user_data})") - exists = BotDB.check_user_in_blacklist(user_data['user_id']) - if exists: - await message.reply(f"Пользователь уже был заблокирован ранее.") - logger.info(f"Пользователь: {user_data['user_id']} был заблокирован ранее)") - await state.set_state('ADMIN') - else: - BotDB.set_user_blacklist(user_data['user_id'], - user_data['user_name'], - user_data['message_for_user'], - user_data['date_to_unban']) - await message.reply(f"Пользователь {user_data['user_name']} успешно заблокирован.") - logger.info(f"Пользователь: {user_data['user_id']} успешно заблокирован)") - await state.set_state('ADMIN') - markup = get_reply_keyboard_admin() - await message.answer('Вернулись в меню', reply_markup=markup) +import traceback + +from aiogram import Router, types, F +from aiogram.filters import Command, StateFilter +from aiogram.fsm.context import FSMContext + +from helper_bot.filters.main import ChatTypeFilter +from helper_bot.keyboards.keyboards import get_reply_keyboard_admin, create_keyboard_with_pagination, \ + create_keyboard_for_ban_days, create_keyboard_for_approve_ban, create_keyboard_for_ban_reason +from helper_bot.utils.base_dependency_factory import get_global_instance +from helper_bot.utils.helper_func import check_access, add_days_to_date, get_banned_users_buttons, get_banned_users_list +from logs.custom_logger import logger + +admin_router = Router() + +bdf = get_global_instance() +GROUP_FOR_POST = bdf.settings['Telegram']['group_for_posts'] +GROUP_FOR_MESSAGE = bdf.settings['Telegram']['group_for_message'] +MAIN_PUBLIC = bdf.settings['Telegram']['main_public'] +GROUP_FOR_LOGS = bdf.settings['Telegram']['group_for_logs'] +IMPORTANT_LOGS = bdf.settings['Telegram']['important_logs'] +PREVIEW_LINK = bdf.settings['Telegram']['preview_link'] +LOGS = bdf.settings['Settings']['logs'] +TEST = bdf.settings['Settings']['test'] + +BotDB = bdf.get_db() + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + Command('admin') +) +async def admin_panel(message: types.Message, state: FSMContext): + try: + if check_access(message.from_user.id, BotDB): + await state.set_state("ADMIN") + logger.info(f"Запуск админ панели для пользователя: {message.from_user.id}") + markup = get_reply_keyboard_admin() + await message.answer("Добро пожаловать в админку. Выбери что хочешь:", + reply_markup=markup) + else: + await message.answer('Доступ запрещен, досвидания!') + except Exception as e: + logger.error(f"Ошибка при запуске админ панели: {e}") + await message.bot.send_message(IMPORTANT_LOGS, + f'Ошибка в функции admin_panel {e}. Traceback: {traceback.format_exc()}') + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("ADMIN"), + F.text == 'Бан (Список)' +) +async def get_last_users(message: types.Message): + logger.info( + f"Попытка получения списка последних пользователей. Текст сообщения: {message.text} Имя автора сообщения: {message.from_user.full_name})") + list_users = BotDB.get_last_users_from_db() + keyboard = create_keyboard_with_pagination(1, len(list_users), list_users, 'ban') + await message.answer(text="Список пользователей которые последними обращались к боту", + reply_markup=keyboard) + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("ADMIN"), + F.text == 'Бан по нику' +) +async def ban_by_nickname(message: types.Message, state: FSMContext): + await message.answer('Пришли мне username блокируемого пользователя') + await state.set_state('PRE_BAN') + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("ADMIN"), + F.text == 'Бан по ID' +) +async def ban_by_id(message: types.Message, state: FSMContext): + await message.answer('Пришли мне ID блокируемого пользователя') + await state.set_state('PRE_BAN_ID') + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("ADMIN"), + F.text == 'Тестовый бан' +) +async def ban_by_forward(message: types.Message, state: FSMContext): + await message.answer('Перешлите мне сообщение от пользователя, которого хотите заблокировать') + await state.set_state('PRE_BAN_FORWARD') + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + F.text == 'Отменить' +) +async def decline_ban(message: types.Message, state: FSMContext): + current_state = await state.get_state() + await state.set_data({}) + await state.set_state("ADMIN") + logger.info(f"Отмена процедуры блокировки из состояния: {current_state}") + markup = get_reply_keyboard_admin() + await message.answer('Вернулись в меню', reply_markup=markup) + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("PRE_BAN") +) +async def ban_by_nickname_step_2(message: types.Message, state: FSMContext): + logger.info( + f"Функция ban_by_nickname_2. Получен никнейм пользователя: {message.text}") + user_name = message.text + user_id = BotDB.get_user_id_by_username(user_name) + await state.update_data(user_id=user_id, user_name=user_name, message_for_user=None, + date_to_unban=None) + full_name = BotDB.get_full_name_by_id(user_id) + markup = create_keyboard_for_ban_reason() + await message.answer( + text=f"Выбран пользователь:\nid: {user_id}\nusername: {user_name}\n" + f"Имя:{full_name}\nВыбери причину бана из списка или напиши ее в чат", + reply_markup=markup) + await state.set_state('BAN_2') + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("PRE_BAN_ID") +) +async def ban_by_id_step_2(message: types.Message, state: FSMContext): + try: + user_id = int(message.text) + logger.info(f"Функция ban_by_id_step_2. Получен ID пользователя: {user_id}") + + # Проверяем, существует ли пользователь в базе + user_info = BotDB.get_user_info_by_id(user_id) + if not user_info: + await message.answer(f"Пользователь с ID {user_id} не найден в базе данных.") + await state.set_state('ADMIN') + markup = get_reply_keyboard_admin() + await message.answer('Вернулись в меню', reply_markup=markup) + return + + user_name = user_info.get('username', 'Неизвестно') + full_name = user_info.get('full_name', 'Неизвестно') + + await state.update_data(user_id=user_id, user_name=user_name, message_for_user=None, + date_to_unban=None) + + markup = create_keyboard_for_ban_reason() + await message.answer( + text=f"Выбран пользователь:\nid: {user_id}\nusername: {user_name}\n" + f"Имя:{full_name}\nВыбери причину бана из списка или напиши ее в чат", + reply_markup=markup) + await state.set_state('BAN_2') + + except ValueError: + await message.answer("Пожалуйста, введите корректный числовой ID пользователя.") + await state.set_state('ADMIN') + markup = get_reply_keyboard_admin() + await message.answer('Вернулись в меню', reply_markup=markup) + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("PRE_BAN_FORWARD"), + F.forward_from +) +async def ban_by_forward_step_2(message: types.Message, state: FSMContext): + """Обработчик пересланных сообщений для бана пользователя""" + try: + # Получаем информацию о пользователе из пересланного сообщения + forwarded_user = message.forward_from + + if not forwarded_user: + await message.answer("Не удалось получить информацию о пользователе из пересланного сообщения. Возможно, пользователь скрыл возможность пересылки своих сообщений.") + await state.set_state('ADMIN') + markup = get_reply_keyboard_admin() + await message.answer('Вернулись в меню', reply_markup=markup) + return + + user_id = forwarded_user.id + user_name = forwarded_user.username or "private_username" + full_name = forwarded_user.full_name or "Неизвестно" + + logger.info(f"Функция ban_by_forward_step_2. Получен пользователь из пересланного сообщения: ID={user_id}, username={user_name}, full_name={full_name}") + + # Проверяем, существует ли пользователь в базе + user_info = BotDB.get_user_info_by_id(user_id) + if not user_info: + # Если пользователя нет в базе, используем информацию из пересланного сообщения + logger.info(f"Пользователь с ID {user_id} не найден в базе данных, используем данные из пересланного сообщения") + user_name = user_name + full_name = full_name + else: + # Если пользователь есть в базе, используем данные из базы + user_name = user_info.get('username', user_name) + full_name = user_info.get('full_name', full_name) + + await state.update_data(user_id=user_id, user_name=user_name, message_for_user=None, + date_to_unban=None) + + markup = create_keyboard_for_ban_reason() + await message.answer( + text=f"Выбран пользователь из пересланного сообщения:\nid: {user_id}\nusername: {user_name}\n" + f"Имя:{full_name}\nВыбери причину бана из списка или напиши ее в чат", + reply_markup=markup) + await state.set_state('BAN_2') + + except Exception as e: + logger.error(f"Ошибка при обработке пересланного сообщения: {e}") + await message.answer("Произошла ошибка при обработке пересланного сообщения.") + await state.set_state('ADMIN') + markup = get_reply_keyboard_admin() + await message.answer('Вернулись в меню', reply_markup=markup) + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("PRE_BAN_FORWARD") +) +async def ban_by_forward_invalid(message: types.Message, state: FSMContext): + """Обработчик для случаев, когда сообщение не является пересланным или не содержит информацию о пользователе""" + if message.forward_from_chat: + await message.answer("Пересланное сообщение из канала или группы не содержит информацию о конкретном пользователе. Пожалуйста, перешлите сообщение из приватного чата.") + else: + await message.answer("Пожалуйста, перешлите сообщение от пользователя, которого хотите заблокировать. Обычное сообщение не подходит.") + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("ADMIN"), + F.text == 'Разбан (список)' +) +async def get_banned_users(message): + logger.info( + f"Попытка получения списка заблокированных пользователей. Текст сообщения: {message.text} Имя автора сообщения: {message.from_user.full_name})") + message_text = get_banned_users_list(0, BotDB) + buttons_list = get_banned_users_buttons(BotDB) + if buttons_list: + k = create_keyboard_with_pagination(1, len(buttons_list), buttons_list, 'unlock') + await message.answer(text=message_text, reply_markup=k) + else: + await message.answer(text="В списке забанненых пользователей никого нет") + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("BAN_2") +) +async def ban_user_step_2(message: types.Message, state: FSMContext): + user_data = await state.get_data() + logger.info(f"Переход на шаг 2 бана пользователя. Словарь с данными для бана: {user_data})") + await state.update_data(message_for_user=message.text) + markup = create_keyboard_for_ban_days() + await message.answer(f"Выбрана причина: {message.text}. Выбери срок бана в днях или напиши " + f"его в чат", reply_markup=markup) + await state.set_state("BAN_3") + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("BAN_3") +) +async def ban_user_step_3(message: types.Message, state: FSMContext): + logger.info(f"ban_user_step_3. Расчет даты разбана. Входные данные {message.text}") + if message.text != 'Навсегда': + count_days = int(message.text) + date_to_unban = add_days_to_date(count_days) + else: + date_to_unban = None + logger.info(f"ban_user_step_3. Расчет даты разбана. date_to_unban: {date_to_unban}") + await state.update_data(date_to_unban=date_to_unban) + user_data = await state.get_data() + markup = create_keyboard_for_approve_ban() + await message.answer( + f"Необходимо подтверждение:\nПользователь:{user_data['user_id']}\nПричина бана:{user_data['message_for_user']}\nСрок бана:{user_data['date_to_unban']}", + reply_markup=markup) + await state.set_state("BAN_FINAL") + + +@admin_router.message( + ChatTypeFilter(chat_type=["private"]), + StateFilter("BAN_FINAL"), + F.text == 'Подтвердить' +) +async def approve_ban(message: types.Message, state: FSMContext): + user_data = await state.get_data() + logger.info(f"Переход на финальный шаг бана пользователя. Словарь с данными для бана: {user_data})") + exists = BotDB.check_user_in_blacklist(user_data['user_id']) + if exists: + await message.reply(f"Пользователь уже был заблокирован ранее.") + logger.info(f"Пользователь: {user_data['user_id']} был заблокирован ранее)") + await state.set_state('ADMIN') + else: + BotDB.set_user_blacklist(user_data['user_id'], + user_data['user_name'], + user_data['message_for_user'], + user_data['date_to_unban']) + await message.reply(f"Пользователь {user_data['user_name']} успешно заблокирован.") + logger.info(f"Пользователь: {user_data['user_id']} успешно заблокирован)") + await state.set_state('ADMIN') + markup = get_reply_keyboard_admin() + await message.answer('Вернулись в меню', reply_markup=markup) diff --git a/helper_bot/handlers/callback/__pycache__/__init__.cpython-312.pyc b/helper_bot/handlers/callback/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index c0e98a3fe33c5265a0d5fd37d39761eb6a80d55a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236 zcmX@j%ge<81U;Qo(+q(0V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9i2>VNJ$c z{K<(qIZ27h+3`jBr6s9Fews|T1mR*CiFqkGsYS&_%s|yE89sx|{N?Xr6%$aIoRL_R z8&H&=m6}{q98;2-lbT+Xn5&zSnp2Qkq??pq5(8nzgPBlsVqhl6#K&jmWtPOp>lIY~ j;;_lhPbtkwwJYKPnh$bVF^KVjnURt49)n&H8;}D4A!tIW diff --git a/helper_bot/handlers/callback/__pycache__/main.cpython-312.pyc b/helper_bot/handlers/callback/__pycache__/main.cpython-312.pyc deleted file mode 100644 index b532bc4adf2807275d1de05cbd8b92eaee9b775a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15306 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 diff --git a/helper_bot/handlers/group/group_handlers.py b/helper_bot/handlers/group/group_handlers.py index 6028d83..8f5528e 100644 --- a/helper_bot/handlers/group/group_handlers.py +++ b/helper_bot/handlers/group/group_handlers.py @@ -3,13 +3,13 @@ from aiogram.fsm.context import FSMContext from helper_bot.filters.main import ChatTypeFilter from helper_bot.keyboards.keyboards import get_reply_keyboard_leave_chat -from helper_bot.utils.base_dependency_factory import BaseDependencyFactory +from helper_bot.utils.base_dependency_factory import get_global_instance from helper_bot.utils.helper_func import send_text_message from logs.custom_logger import logger group_router = Router() -bdf = BaseDependencyFactory() +bdf = get_global_instance() GROUP_FOR_POST = bdf.settings['Telegram']['group_for_posts'] GROUP_FOR_MESSAGE = bdf.settings['Telegram']['group_for_message'] MAIN_PUBLIC = bdf.settings['Telegram']['main_public'] diff --git a/helper_bot/handlers/private/__pycache__/__init__.cpython-312.pyc b/helper_bot/handlers/private/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 96f1713ef0ee497c53aafa9f59d19f9f6c66e0e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 233 zcmX@j%ge<81U;Qo)AWG!V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9i2>VNJ$c zd<8|BWr-!J@kRNiC8z7*6Hu9)kyw-) zP?VpQnp{#GQ<9pKnqHKctDBLUQ;=Guo0MM?17XI4nNV|Lpa#dp$7kkcmc+;F6;%G> iu*uC&Da}c>E8+ke4{}v8i1C4$k&*EpgH90}kOKgOl0pms diff --git a/helper_bot/handlers/private/__pycache__/main.cpython-312.pyc b/helper_bot/handlers/private/__pycache__/main.cpython-312.pyc deleted file mode 100644 index bb4cb4ce7d2636ee7e28abe96159adfcbcaea12e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23314 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 diff --git a/helper_bot/handlers/private/private_handlers.py b/helper_bot/handlers/private/private_handlers.py index d0ab731..ea95aae 100644 --- a/helper_bot/handlers/private/private_handlers.py +++ b/helper_bot/handlers/private/private_handlers.py @@ -1,407 +1,440 @@ -import random -import traceback -from datetime import datetime -from pathlib import Path -from time import sleep - -from aiogram import types, Router, F -from aiogram.filters import Command, StateFilter -from aiogram.fsm.context import FSMContext -from aiogram.types import FSInputFile - -from helper_bot.filters.main import ChatTypeFilter -from helper_bot.keyboards import get_reply_keyboard, get_reply_keyboard_for_post -from helper_bot.keyboards.keyboards import get_reply_keyboard_leave_chat -from helper_bot.middlewares.album_middleware import AlbumMiddleware -from helper_bot.middlewares.blacklist_middleware import BlacklistMiddleware -from helper_bot.utils import messages -from helper_bot.utils.base_dependency_factory import BaseDependencyFactory -from helper_bot.utils.helper_func import get_first_name, get_text_message, send_text_message, send_photo_message, \ - send_media_group_message_to_private_chat, prepare_media_group_from_middlewares, check_username_and_full_name, \ - send_video_message, send_video_note_message, send_audio_message, send_voice_message, add_in_db_media -from logs.custom_logger import logger - -private_router = Router() - -private_router.message.middleware(AlbumMiddleware()) -private_router.message.middleware(BlacklistMiddleware()) - -bdf = BaseDependencyFactory() -GROUP_FOR_POST = bdf.settings['Telegram']['group_for_posts'] -GROUP_FOR_MESSAGE = bdf.settings['Telegram']['group_for_message'] -MAIN_PUBLIC = bdf.settings['Telegram']['main_public'] -GROUP_FOR_LOGS = bdf.settings['Telegram']['group_for_logs'] -IMPORTANT_LOGS = bdf.settings['Telegram']['important_logs'] -PREVIEW_LINK = bdf.settings['Telegram']['preview_link'] -LOGS = bdf.settings['Settings']['logs'] -TEST = bdf.settings['Settings']['test'] - -BotDB = bdf.get_db() - - -@private_router.message( - ChatTypeFilter(chat_type=["private"]), - Command("start") -) -@private_router.message( - ChatTypeFilter(chat_type=["private"]), - F.text == 'Вернуться в бота' -) -async def handle_start_message(message: types.Message, state: FSMContext): - try: - await message.forward(chat_id=GROUP_FOR_LOGS) - full_name = message.from_user.full_name - username = message.from_user.username - first_name = get_first_name(message) - is_bot = message.from_user.is_bot - language_code = message.from_user.language_code - user_id = message.from_user.id - current_date = datetime.now() - date = current_date.strftime("%Y-%m-%d %H:%M:%S") - if not BotDB.user_exists(user_id): - BotDB.add_new_user_in_db(user_id, first_name, full_name, username, is_bot, language_code, date, - date) - else: - is_need_update = check_username_and_full_name(user_id, username, full_name) - if is_need_update: - BotDB.update_username_and_full_name(user_id, username, full_name) - await message.answer( - f"Давно не виделись! Вижу что ты изменился;) Теперь буду звать тебя: {full_name} и ник @{username}") - await message.bot.send_message(chat_id=GROUP_FOR_LOGS, - text=f'Для пользователя: {user_id} обновлены данные в БД.\nНовое имя: {full_name}\nНовый ник:{username}') - sleep(1) - BotDB.update_date_for_user(date, user_id) - await state.set_state("START") - logger.info( - f"Формирование приветственного сообщения для пользователя. Сообщение: {message.text} " - f"Имя автора сообщения: {message.from_user.full_name})") - name_stick_hello = list(Path('Stick').rglob('Hello_*')) - random_stick_hello = random.choice(name_stick_hello) - random_stick_hello = FSInputFile(path=random_stick_hello) - logger.info(f"Стикер успешно получен из БД") - await message.answer_sticker(random_stick_hello) - sleep(0.3) - except Exception as e: - logger.error(f"Произошла ошибка handle_start_message при получении стикеров. Ошибка:{str(e)}") - await message.bot.send_message(chat_id=IMPORTANT_LOGS, - text=f"Произошла ошибка при получении стикеров: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") - try: - markup = get_reply_keyboard(BotDB, message.from_user.id) - hello_message = messages.get_message(get_first_name(message), 'HELLO_MESSAGE') - await message.answer(hello_message, reply_markup=markup, parse_mode='HTML') - except Exception as e: - logger.error( - f"Произошла ошибка при отправке приветственного сообщения для пользователя {message.from_user.id} Имя: {message.from_user.full_name}. Ошибка: {str(e)}") - await message.bot.send_message(IMPORTANT_LOGS, - f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") - - -@private_router.message( - StateFilter("START"), - ChatTypeFilter(chat_type=["private"]), - F.text == '📢Предложить свой пост' -) -async def suggest_post(message: types.Message, state: FSMContext): - try: - user_id = message.from_user.id - current_date = datetime.now() - date = current_date.strftime("%Y-%m-%d %H:%M:%S") - BotDB.update_date_for_user(date, user_id) - await message.forward(chat_id=GROUP_FOR_LOGS) - await state.set_state("SUGGEST") - current_state = await state.get_state() - logger.info( - f"Вызов функции suggest_post. Сообщение: {message.text} Имя автора сообщения: {message.from_user.full_name} Идентификатор сообщения: {message.message_id}. State - {current_state}") - markup = types.ReplyKeyboardRemove() - suggest_news = messages.get_message(get_first_name(message), 'SUGGEST_NEWS') - await message.answer(suggest_news) - sleep(0.3) - suggest_news_2 = messages.get_message(get_first_name(message), 'SUGGEST_NEWS_2') - await message.answer(suggest_news_2, reply_markup=markup) - except Exception as e: - await message.bot.send_message(IMPORTANT_LOGS, - f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") - - -@private_router.message( - ChatTypeFilter(chat_type=["private"]), - F.text == '👋🏼Сказать пока!' -) -@private_router.message( - ChatTypeFilter(chat_type=["private"]), - F.text == 'Выйти из чата' -) -async def end_message(message: types.Message, state: FSMContext): - try: - user_id = message.from_user.id - current_date = datetime.now() - date = current_date.strftime("%Y-%m-%d %H:%M:%S") - BotDB.update_date_for_user(date, user_id) - await message.forward(chat_id=GROUP_FOR_LOGS) - logger.info( - f"Вызов функции end_message. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") - name_stick_bye = list(Path('Stick').rglob('Universal_*')) - random_stick_bye = random.choice(name_stick_bye) - random_stick_bye = FSInputFile(path=random_stick_bye) - await message.answer_sticker(random_stick_bye) - except Exception as e: - logger.error( - f"Ошибка в функции end_message при получении стикера: {str(e)} Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") - await message.bot.send_message(chat_id=IMPORTANT_LOGS, - text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") - try: - markup = types.ReplyKeyboardRemove() - bye_message = messages.get_message(get_first_name(message), 'BYE_MESSAGE') - await message.answer(bye_message, reply_markup=markup) - await state.set_state("START") - except Exception as e: - logger.error( - f"Ошибка в функции stickers при получении сообщения: {str(e)} Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") - await message.bot.send_message(chat_id=IMPORTANT_LOGS, - text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") - - -@private_router.message( - StateFilter("SUGGEST"), - ChatTypeFilter(chat_type=["private"]), -) -async def suggest_router(message: types.Message, state: FSMContext, album: list = None): - logger.info( - f"Вызов функции suggest_router. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") - first_name = get_first_name(message) - try: - post_caption = '' - if message.media_group_id is not None: - await send_text_message(GROUP_FOR_LOGS, message, - f'Закинул медиагруппу, пользователь: имя - {first_name}, ник - {message.from_user.username}') - else: - await message.forward(chat_id=GROUP_FOR_LOGS) - if message.content_type == 'text': - lower_text = message.text.lower() - # Получаем текст сообщения и преобразовываем его по правилам - post_text = get_text_message(lower_text, first_name, - message.from_user.username) - # Получаем клавиатуру для поста - markup = get_reply_keyboard_for_post() - - # Отправляем сообщение в приватный канал - sent_message_id = await send_text_message(GROUP_FOR_POST, message, post_text, markup) - - # Записываем в базу пост - BotDB.add_post_in_db(sent_message_id, message.text, message.from_user.id) - - # Отправляем юзеру ответ, что сообщение отравлено и возвращаем его в меню - markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) - success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') - await message.answer(success_send_message, reply_markup=markup_for_user) - await state.set_state("START") - - elif message.content_type == 'photo' and message.media_group_id is None: - if message.caption: - lower_caption = message.caption.lower() - # Получаем текст сообщения и преобразовываем его по правилам - post_caption = get_text_message(lower_caption, first_name, - message.from_user.username) - markup = get_reply_keyboard_for_post() - - # Отправляем фото и текст в приватный канал - sent_message = await send_photo_message(GROUP_FOR_POST, message, - message.photo[-1].file_id, post_caption, markup) - # Записываем в базу пост и контент - BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) - await add_in_db_media(sent_message) - - # Отправляем юзеру ответ и возвращаем его в меню - markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) - success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') - await message.answer(success_send_message, reply_markup=markup_for_user) - await state.set_state("START") - - elif message.content_type == 'video' and message.media_group_id is None: - if message.caption: - lower_caption = message.caption.lower() - post_caption = get_text_message(lower_caption, first_name, - message.from_user.username) - markup = get_reply_keyboard_for_post() - # Получаем текст сообщения и преобразовываем его по правилам - - # Отправляем видео и текст в приватный канал - sent_message = await send_video_message(GROUP_FOR_POST, message, - message.video.file_id, post_caption, markup) - - # Записываем в базу пост и контент - BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) - await add_in_db_media(sent_message) - - # Записываем в базу пост и контент - BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) - await add_in_db_media(sent_message) - - # Отправляем юзеру ответ и возвращаем его в меню - markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) - success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') - await message.answer(success_send_message, reply_markup=markup_for_user) - await state.set_state("START") - - elif message.content_type == 'video_note' and message.media_group_id is None: - markup = get_reply_keyboard_for_post() - - # Отправляем видеокружок в приватный канал - sent_message = await send_video_note_message(GROUP_FOR_POST, message, - message.video_note.file_id, markup) - - # Записываем в базу пост и контент - BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) - await add_in_db_media(sent_message) - - # Отправляем юзеру ответ и возвращаем его в меню - markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) - success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') - await message.answer(success_send_message, reply_markup=markup_for_user) - await state.set_state("START") - - elif message.content_type == 'audio' and message.media_group_id is None: - if message.caption: - lower_caption = message.caption.lower() - # Получаем текст сообщения и преобразовываем его по правилам - post_caption = get_text_message(lower_caption, first_name, - message.from_user.username) - markup = get_reply_keyboard_for_post() - - # Отправляем аудио и текст в приватный канал - sent_message = await send_audio_message(GROUP_FOR_POST, message, - message.audio.file_id, post_caption, markup) - - # Записываем в базу пост и контент - BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) - await add_in_db_media(sent_message) - - # Отправляем юзеру ответ и возвращаем его в меню - markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) - success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') - await message.answer(success_send_message, reply_markup=markup_for_user) - await state.set_state("START") - - elif message.content_type == 'voice' and message.media_group_id is None: - markup = get_reply_keyboard_for_post() - - # Отправляем войс и текст в приватный канал - sent_message = await send_voice_message(GROUP_FOR_POST, message, - message.voice.file_id, markup) - - # Записываем в базу пост и контент - BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) - await add_in_db_media(sent_message) - - # Отправляем юзеру ответ и возвращаем его в меню - markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) - success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') - await message.answer(success_send_message, reply_markup=markup_for_user) - await state.set_state("START") - - elif message.media_group_id is not None: - post_caption = " " - - # Получаем сообщение и проверяем есть ли подпись. Если подпись есть, то преобразуем ее через функцию - if album[0].caption: - lower_caption = album[0].caption.lower() - post_caption = get_text_message(lower_caption, first_name, - message.from_user.username) - - # Иначе обрабатываем фото и получаем медиагруппу - media_group = await prepare_media_group_from_middlewares(album, post_caption) - - # Отправляем медиагруппу в секретный чат - media_group_message_id = await send_media_group_message_to_private_chat(GROUP_FOR_POST, message, - media_group) - sleep(0.2) - - # Получаем клавиатуру и отправляем еще одно текстовое сообщение с кнопками - markup = get_reply_keyboard_for_post() - help_message_id = await send_text_message(GROUP_FOR_POST, message, "^", markup) - - # Записываем в state идентификаторы текстового сообщения И последнего сообщения медиагруппы - BotDB.update_helper_message_in_db(message_id=media_group_message_id, helper_message_id=help_message_id) - - # Получаем клавиатуру для пользователя, благодарим за пост, и возвращаем в дефолтное сообщение - markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) - success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') - await message.answer(success_send_message, reply_markup=markup_for_user) - await state.set_state("START") - else: - await message.bot.send_message(message.chat.id, - 'Я пока не умею работать с таким сообщением. ' - 'Пришли текст и фото/фоты(ы). А лучше перешли это сообщение админу @kerrad1\n' - 'Мы добавим его к обработке если необходимо') - except Exception as e: - await message.bot.send_message(chat_id=IMPORTANT_LOGS, - text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") - - -@private_router.message( - ChatTypeFilter(chat_type=["private"]), - F.text == '🤪Хочу стикеры' -) -async def stickers(message: types.Message, state: FSMContext): - logger.info( - f"Вызов функции stickers. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") - markup = get_reply_keyboard(BotDB, message.from_user.id) - try: - BotDB.update_info_about_stickers(user_id=message.from_user.id) - await message.forward(chat_id=GROUP_FOR_LOGS) - await message.answer(text='Хорошо, лови, добавить можно отсюда: https://t.me/addstickers/love_biysk', - reply_markup=markup) - await state.set_state("START") - except Exception as e: - await message.bot.send_message(chat_id=IMPORTANT_LOGS, - text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") - logger.error( - f"Ошибка функции stickers. Ошибка: {str(e)} Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") - - -@private_router.message( - StateFilter("START"), - ChatTypeFilter(chat_type=["private"]), - F.text == '📩Связаться с админами' -) -async def connect_with_admin(message: types.Message, state: FSMContext): - logger.info( - f"Вызов функции connect_with_admin. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") - user_id = message.from_user.id - current_date = datetime.now() - date = current_date.strftime("%Y-%m-%d %H:%M:%S") - BotDB.update_date_for_user(date, user_id) - admin_message = messages.get_message(get_first_name(message), 'CONNECT_WITH_ADMIN') - await message.answer(admin_message, parse_mode="html") - await message.forward(chat_id=GROUP_FOR_LOGS) - await state.set_state("PRE_CHAT") - - -@private_router.message( - StateFilter("PRE_CHAT"), - ChatTypeFilter(chat_type=["private"]), -) -@private_router.message( - StateFilter("CHAT"), - ChatTypeFilter(chat_type=["private"]), -) -async def resend_message_in_group_for_message(message: types.Message, state: FSMContext): - user_id = message.from_user.id - current_date = datetime.now() - date = current_date.strftime("%Y-%m-%d %H:%M:%S") - BotDB.update_date_for_user(date, user_id) - logger.info( - f"Попытка пересылки сообщения в связь с админами. Сообщение: {message.text} Имя автора сообщения: {message.from_user.full_name} Идентификатор сообщения: {message.message_id})") - await message.forward(chat_id=GROUP_FOR_MESSAGE) - current_date = datetime.now() - date = current_date.strftime("%Y-%m-%d %H:%M:%S") - BotDB.add_new_message_in_db(message.text, message.from_user.id, message.message_id + 1, date) - question = messages.get_message(get_first_name(message), 'QUESTION') - user_state = await state.get_state() - if user_state == "PRE_CHAT": - markup = get_reply_keyboard(BotDB, message.from_user.id) - await message.answer(question, reply_markup=markup) - await state.set_state("START") - elif user_state == "CHAT": - markup = get_reply_keyboard_leave_chat() - await message.answer(question, reply_markup=markup) +import random +import traceback +from datetime import datetime +from pathlib import Path +from time import sleep + +from aiogram import types, Router, F +from aiogram.filters import Command, StateFilter +from aiogram.fsm.context import FSMContext +from aiogram.types import FSInputFile + +from helper_bot.filters.main import ChatTypeFilter +from helper_bot.keyboards import get_reply_keyboard, get_reply_keyboard_for_post +from helper_bot.keyboards.keyboards import get_reply_keyboard_leave_chat +from helper_bot.middlewares.album_middleware import AlbumMiddleware +from helper_bot.middlewares.blacklist_middleware import BlacklistMiddleware +from helper_bot.utils import messages +from helper_bot.utils.base_dependency_factory import get_global_instance +from helper_bot.utils.helper_func import get_first_name, get_text_message, send_text_message, send_photo_message, \ + send_media_group_message_to_private_chat, prepare_media_group_from_middlewares, check_username_and_full_name, \ + send_video_message, send_video_note_message, send_audio_message, send_voice_message, add_in_db_media +from logs.custom_logger import logger + +private_router = Router() + +private_router.message.middleware(AlbumMiddleware()) +private_router.message.middleware(BlacklistMiddleware()) + +bdf = get_global_instance() +GROUP_FOR_POST = bdf.settings['Telegram']['group_for_posts'] +GROUP_FOR_MESSAGE = bdf.settings['Telegram']['group_for_message'] +MAIN_PUBLIC = bdf.settings['Telegram']['main_public'] +GROUP_FOR_LOGS = bdf.settings['Telegram']['group_for_logs'] +IMPORTANT_LOGS = bdf.settings['Telegram']['important_logs'] +PREVIEW_LINK = bdf.settings['Telegram']['preview_link'] +LOGS = bdf.settings['Settings']['logs'] +TEST = bdf.settings['Settings']['test'] + +BotDB = bdf.get_db() + + +@private_router.message( + ChatTypeFilter(chat_type=["private"]), + Command("start") +) +@private_router.message( + ChatTypeFilter(chat_type=["private"]), + F.text == 'Вернуться в бота' +) +async def handle_start_message(message: types.Message, state: FSMContext): + try: + await message.forward(chat_id=GROUP_FOR_LOGS) + full_name = message.from_user.full_name + username = message.from_user.username + first_name = get_first_name(message) + is_bot = message.from_user.is_bot + language_code = message.from_user.language_code + user_id = message.from_user.id + + # Проверяем наличие username для логирования + if not username: + await message.bot.send_message(chat_id=GROUP_FOR_LOGS, + text=f'Пользователь {user_id} ({full_name}) обратился к боту без username') + logger.warning(f"Пользователь {user_id} ({full_name}) обратился к боту без username") + # Устанавливаем значение по умолчанию для username + username = "private_username" + + current_date = datetime.now() + date = current_date.strftime("%Y-%m-%d %H:%M:%S") + if not BotDB.user_exists(user_id): + BotDB.add_new_user_in_db(user_id, first_name, full_name, username, is_bot, language_code, date, + date) + else: + is_need_update = check_username_and_full_name(user_id, username, full_name, BotDB) + if is_need_update: + BotDB.update_username_and_full_name(user_id, username, full_name) + await message.answer( + f"Давно не виделись! Вижу что ты изменился;) Теперь буду звать тебя: {full_name} и ник @{username}") + await message.bot.send_message(chat_id=GROUP_FOR_LOGS, + text=f'Для пользователя: {user_id} обновлены данные в БД.\nНовое имя: {full_name}\nНовый ник:{username}') + sleep(1) + BotDB.update_date_for_user(date, user_id) + await state.set_state("START") + logger.info( + f"Формирование приветственного сообщения для пользователя. Сообщение: {message.text} " + f"Имя автора сообщения: {message.from_user.full_name})") + name_stick_hello = list(Path('Stick').rglob('Hello_*')) + random_stick_hello = random.choice(name_stick_hello) + random_stick_hello = FSInputFile(path=random_stick_hello) + logger.info(f"Стикер успешно получен из БД") + await message.answer_sticker(random_stick_hello) + sleep(0.3) + except Exception as e: + logger.error(f"Произошла ошибка handle_start_message при получении стикеров. Ошибка:{str(e)}") + await message.bot.send_message(chat_id=IMPORTANT_LOGS, + text=f"Произошла ошибка при получении стикеров: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") + try: + markup = get_reply_keyboard(BotDB, message.from_user.id) + hello_message = messages.get_message(get_first_name(message), 'HELLO_MESSAGE') + await message.answer(hello_message, reply_markup=markup, parse_mode='HTML') + except Exception as e: + logger.error( + f"Произошла ошибка при отправке приветственного сообщения для пользователя {message.from_user.id} Имя: {message.from_user.full_name}. Ошибка: {str(e)}") + await message.bot.send_message(IMPORTANT_LOGS, + f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") + + +@private_router.message( + ChatTypeFilter(chat_type=["private"]), + Command("restart") +) +async def restart_function(message: types.Message, state: FSMContext): + await message.forward(chat_id=GROUP_FOR_LOGS) + full_name = message.from_user.full_name + username = message.from_user.username + user_id = message.from_user.id + + # Проверяем наличие username для логирования + if not username: + await message.bot.send_message(chat_id=GROUP_FOR_LOGS, + text=f'Пользователь {user_id} ({full_name}) обратился к боту без username') + logger.warning(f"Пользователь {user_id} ({full_name}) обратился к боту без username") + # Устанавливаем значение по умолчанию для username + username = "private_username" + + markup = get_reply_keyboard(BotDB, message.from_user.id) + await message.answer(text='Я перезапущен!', + reply_markup=markup) + await state.set_state('START') + + +@private_router.message( + StateFilter("START"), + ChatTypeFilter(chat_type=["private"]), + F.text == '📢Предложить свой пост' +) +async def suggest_post(message: types.Message, state: FSMContext): + try: + user_id = message.from_user.id + current_date = datetime.now() + date = current_date.strftime("%Y-%m-%d %H:%M:%S") + BotDB.update_date_for_user(date, user_id) + await message.forward(chat_id=GROUP_FOR_LOGS) + await state.set_state("SUGGEST") + current_state = await state.get_state() + logger.info( + f"Вызов функции suggest_post. Сообщение: {message.text} Имя автора сообщения: {message.from_user.full_name} Идентификатор сообщения: {message.message_id}. State - {current_state}") + markup = types.ReplyKeyboardRemove() + suggest_news = messages.get_message(get_first_name(message), 'SUGGEST_NEWS') + await message.answer(suggest_news) + sleep(0.3) + suggest_news_2 = messages.get_message(get_first_name(message), 'SUGGEST_NEWS_2') + await message.answer(suggest_news_2, reply_markup=markup) + except Exception as e: + await message.bot.send_message(IMPORTANT_LOGS, + f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") + + +@private_router.message( + ChatTypeFilter(chat_type=["private"]), + F.text == '👋🏼Сказать пока!' +) +@private_router.message( + ChatTypeFilter(chat_type=["private"]), + F.text == 'Выйти из чата' +) +async def end_message(message: types.Message, state: FSMContext): + try: + user_id = message.from_user.id + current_date = datetime.now() + date = current_date.strftime("%Y-%m-%d %H:%M:%S") + BotDB.update_date_for_user(date, user_id) + await message.forward(chat_id=GROUP_FOR_LOGS) + logger.info( + f"Вызов функции end_message. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") + name_stick_bye = list(Path('Stick').rglob('Universal_*')) + random_stick_bye = random.choice(name_stick_bye) + random_stick_bye = FSInputFile(path=random_stick_bye) + await message.answer_sticker(random_stick_bye) + except Exception as e: + logger.error( + f"Ошибка в функции end_message при получении стикера: {str(e)} Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") + await message.bot.send_message(chat_id=IMPORTANT_LOGS, + text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") + try: + markup = types.ReplyKeyboardRemove() + bye_message = messages.get_message(get_first_name(message), 'BYE_MESSAGE') + await message.answer(bye_message, reply_markup=markup) + await state.set_state("START") + except Exception as e: + logger.error( + f"Ошибка в функции stickers при получении сообщения: {str(e)} Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") + await message.bot.send_message(chat_id=IMPORTANT_LOGS, + text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") + + +@private_router.message( + StateFilter("SUGGEST"), + ChatTypeFilter(chat_type=["private"]), +) +async def suggest_router(message: types.Message, state: FSMContext, album: list = None): + logger.info( + f"Вызов функции suggest_router. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") + first_name = get_first_name(message) + try: + post_caption = '' + if message.media_group_id is not None: + await send_text_message(GROUP_FOR_LOGS, message, + f'Закинул медиагруппу, пользователь: имя - {first_name}, ник - {message.from_user.username}') + else: + await message.forward(chat_id=GROUP_FOR_LOGS) + if message.content_type == 'text': + lower_text = message.text.lower() + # Получаем текст сообщения и преобразовываем его по правилам + post_text = get_text_message(lower_text, first_name, + message.from_user.username) + # Получаем клавиатуру для поста + markup = get_reply_keyboard_for_post() + + # Отправляем сообщение в приватный канал + sent_message_id = await send_text_message(GROUP_FOR_POST, message, post_text, markup) + + # Записываем в базу пост + BotDB.add_post_in_db(sent_message_id, message.text, message.from_user.id) + + # Отправляем юзеру ответ, что сообщение отравлено и возвращаем его в меню + markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) + success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') + await message.answer(success_send_message, reply_markup=markup_for_user) + await state.set_state("START") + + elif message.content_type == 'photo' and message.media_group_id is None: + if message.caption: + lower_caption = message.caption.lower() + # Получаем текст сообщения и преобразовываем его по правилам + post_caption = get_text_message(lower_caption, first_name, + message.from_user.username) + markup = get_reply_keyboard_for_post() + + # Отправляем фото и текст в приватный канал + sent_message = await send_photo_message(GROUP_FOR_POST, message, + message.photo[-1].file_id, post_caption, markup) + # Записываем в базу пост и контент + BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) + await add_in_db_media(sent_message, BotDB) + + # Отправляем юзеру ответ и возвращаем его в меню + markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) + success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') + await message.answer(success_send_message, reply_markup=markup_for_user) + await state.set_state("START") + + elif message.content_type == 'video' and message.media_group_id is None: + if message.caption: + lower_caption = message.caption.lower() + post_caption = get_text_message(lower_caption, first_name, + message.from_user.username) + markup = get_reply_keyboard_for_post() + # Получаем текст сообщения и преобразовываем его по правилам + + # Отправляем видео и текст в приватный канал + sent_message = await send_video_message(GROUP_FOR_POST, message, + message.video.file_id, post_caption, markup) + + # Записываем в базу пост и контент + BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) + await add_in_db_media(sent_message, BotDB) + + # Записываем в базу пост и контент + BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) + await add_in_db_media(sent_message) + + # Отправляем юзеру ответ и возвращаем его в меню + markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) + success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') + await message.answer(success_send_message, reply_markup=markup_for_user) + await state.set_state("START") + + elif message.content_type == 'video_note' and message.media_group_id is None: + markup = get_reply_keyboard_for_post() + + # Отправляем видеокружок в приватный канал + sent_message = await send_video_note_message(GROUP_FOR_POST, message, + message.video_note.file_id, markup) + + # Записываем в базу пост и контент + BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) + await add_in_db_media(sent_message, BotDB) + + # Отправляем юзеру ответ и возвращаем его в меню + markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) + success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') + await message.answer(success_send_message, reply_markup=markup_for_user) + await state.set_state("START") + + elif message.content_type == 'audio' and message.media_group_id is None: + if message.caption: + lower_caption = message.caption.lower() + # Получаем текст сообщения и преобразовываем его по правилам + post_caption = get_text_message(lower_caption, first_name, + message.from_user.username) + markup = get_reply_keyboard_for_post() + + # Отправляем аудио и текст в приватный канал + sent_message = await send_audio_message(GROUP_FOR_POST, message, + message.audio.file_id, post_caption, markup) + + # Записываем в базу пост и контент + BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) + await add_in_db_media(sent_message, BotDB) + + # Отправляем юзеру ответ и возвращаем его в меню + markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) + success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') + await message.answer(success_send_message, reply_markup=markup_for_user) + await state.set_state("START") + + elif message.content_type == 'voice' and message.media_group_id is None: + markup = get_reply_keyboard_for_post() + + # Отправляем войс и текст в приватный канал + sent_message = await send_voice_message(GROUP_FOR_POST, message, + message.voice.file_id, markup) + + # Записываем в базу пост и контент + BotDB.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id) + await add_in_db_media(sent_message, BotDB) + + # Отправляем юзеру ответ и возвращаем его в меню + markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) + success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') + await message.answer(success_send_message, reply_markup=markup_for_user) + await state.set_state("START") + + elif message.media_group_id is not None: + post_caption = " " + + # Получаем сообщение и проверяем есть ли подпись. Если подпись есть, то преобразуем ее через функцию + if album[0].caption: + lower_caption = album[0].caption.lower() + post_caption = get_text_message(lower_caption, first_name, + message.from_user.username) + + # Иначе обрабатываем фото и получаем медиагруппу + media_group = await prepare_media_group_from_middlewares(album, post_caption) + + # Отправляем медиагруппу в секретный чат + media_group_message_id = await send_media_group_message_to_private_chat(GROUP_FOR_POST, message, + media_group, BotDB) + sleep(0.2) + + # Получаем клавиатуру и отправляем еще одно текстовое сообщение с кнопками + markup = get_reply_keyboard_for_post() + help_message_id = await send_text_message(GROUP_FOR_POST, message, "^", markup) + + # Записываем в state идентификаторы текстового сообщения И последнего сообщения медиагруппы + BotDB.update_helper_message_in_db(message_id=media_group_message_id, helper_message_id=help_message_id) + + # Получаем клавиатуру для пользователя, благодарим за пост, и возвращаем в дефолтное сообщение + markup_for_user = get_reply_keyboard(BotDB, message.from_user.id) + success_send_message = messages.get_message(first_name, 'SUCCESS_SEND_MESSAGE') + await message.answer(success_send_message, reply_markup=markup_for_user) + await state.set_state("START") + else: + await message.bot.send_message(message.chat.id, + 'Я пока не умею работать с таким сообщением. ' + 'Пришли текст и фото/фоты(ы). А лучше перешли это сообщение админу @kerrad1\n' + 'Мы добавим его к обработке если необходимо') + except Exception as e: + await message.bot.send_message(chat_id=IMPORTANT_LOGS, + text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") + + +@private_router.message( + ChatTypeFilter(chat_type=["private"]), + F.text == '🤪Хочу стикеры' +) +async def stickers(message: types.Message, state: FSMContext): + logger.info( + f"Вызов функции stickers. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") + markup = get_reply_keyboard(BotDB, message.from_user.id) + try: + BotDB.update_info_about_stickers(user_id=message.from_user.id) + await message.forward(chat_id=GROUP_FOR_LOGS) + await message.answer(text='Хорошо, лови, добавить можно отсюда: https://t.me/addstickers/love_biysk', + reply_markup=markup) + await state.set_state("START") + except Exception as e: + await message.bot.send_message(chat_id=IMPORTANT_LOGS, + text=f"Произошла ошибка: {str(e)}\n\nTraceback:\n{traceback.format_exc()}") + logger.error( + f"Ошибка функции stickers. Ошибка: {str(e)} Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") + + +@private_router.message( + StateFilter("START"), + ChatTypeFilter(chat_type=["private"]), + F.text == '📩Связаться с админами' +) +async def connect_with_admin(message: types.Message, state: FSMContext): + logger.info( + f"Вызов функции connect_with_admin. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") + user_id = message.from_user.id + current_date = datetime.now() + date = current_date.strftime("%Y-%m-%d %H:%M:%S") + BotDB.update_date_for_user(date, user_id) + admin_message = messages.get_message(get_first_name(message), 'CONNECT_WITH_ADMIN') + await message.answer(admin_message, parse_mode="html") + await message.forward(chat_id=GROUP_FOR_LOGS) + await state.set_state("PRE_CHAT") + + +@private_router.message( + StateFilter("PRE_CHAT"), + ChatTypeFilter(chat_type=["private"]), +) +@private_router.message( + StateFilter("CHAT"), + ChatTypeFilter(chat_type=["private"]), +) +async def resend_message_in_group_for_message(message: types.Message, state: FSMContext): + user_id = message.from_user.id + current_date = datetime.now() + date = current_date.strftime("%Y-%m-%d %H:%M:%S") + BotDB.update_date_for_user(date, user_id) + logger.info( + f"Попытка пересылки сообщения в связь с админами. Сообщение: {message.text} Имя автора сообщения: {message.from_user.full_name} Идентификатор сообщения: {message.message_id})") + await message.forward(chat_id=GROUP_FOR_MESSAGE) + current_date = datetime.now() + date = current_date.strftime("%Y-%m-%d %H:%M:%S") + BotDB.add_new_message_in_db(message.text, message.from_user.id, message.message_id + 1, date) + question = messages.get_message(get_first_name(message), 'QUESTION') + user_state = await state.get_state() + if user_state == "PRE_CHAT": + markup = get_reply_keyboard(BotDB, message.from_user.id) + await message.answer(question, reply_markup=markup) + await state.set_state("START") + elif user_state == "CHAT": + markup = get_reply_keyboard_leave_chat() + await message.answer(question, reply_markup=markup) diff --git a/helper_bot/keyboards/__pycache__/__init__.cpython-312.pyc b/helper_bot/keyboards/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index f1c797eadcdec538b8fae329838ecd1b76e03c34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 264 zcmX@j%ge<81nbYwN%H{Gk3k$5V1hC}ivStZ8B!Qh7;_kM8KW3;nWC5&87i4HnO`yj zr8JptNvEfl#22L&L67Uc#M_J-6Hu9)kyw-) zP?VpQnp{#GQ<9pKnqHKctDBLUQ;=Guo0MM?17XI4n7NrLDLJX-iAAZ!G4b)4d6^~g l@p=W7zc_4i^HWN5QtgUZfhIEoaWRPTk(rT^v4|PS0szxqEe`+y diff --git a/helper_bot/middlewares/__pycache__/text_middleware.cpython-312.pyc b/helper_bot/middlewares/__pycache__/text_middleware.cpython-312.pyc deleted file mode 100644 index 6cc57890965f16f18fb91b0bdc8ede1598836b7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2735 zcmbVOZ)g-p6rb6Dx4Az#HO3^D*lbLdZZPo=iK$f+HL*g~D2ND$u&lRp@y@-wbata& zdSa+S5>T`sT9dY5`l%6H+K>9BMEp`Hl$;UHZYi}$q5XCx7Q|0|vwLe|2()#Wee>ST zn>TM}e(%lx5Qzj4lBW}@FW1dFeb!h#v3B3B0&=Jw7|tsQZfQC8qUjV(24FdPY-7ny>J4iZj)>xpWv1_ z!FaZ@1qH}WRKNwK0q2zz0tx$49^J+RDo5FgJ3*aU*9_g(Rn8GCVkX@Iiy2gu7svYF zIdv?bIHS|dF`7%0gl(O&iAe^jo@qZr%ppSCPv`7Y9;-1n^YXx$>IGX{?C2QEJF=#w zvMF2BM(UUCtivAx1R!e_kWuuc?u~KbYWQ)iXF<63;+gZH0|TLvOa4H_)g_|FtP*w z!0XxfEgeTHH?k|JR7zjYG~xD?j0a##$t4y03{fmSL!i@0>B=C@4G(p^oZ-m6t|~ay zu8QWWXeon6z)45ahlWVjaCnns0Z`S@N7n$UfQLqz(z=AVvgRSpw>h$D`R%W)$81Zd^u?uOXvdST-q|&Co&Dqe*N;vdojg!#+FFDf-d2`SRpfef zB04FS>Ngjm%I!1hqP(*t?=8xEXKNqH2cJo3Rn3jgo4p_PPIr}ByNgh3-o6_v*7VOu z56sC2=xVkDfklTSQDP6%Y=S|}FO0to-@=b@eTNWc(BoEui?y0#G07G9I~53C1~_~G zko|+FFC z;NMQa8(n<>-!=H;ule&>l}6qQ`oVKVPi2+GG^Uuj!Bk=~QU>T+@H_D`!-By08u%7s zKmZkJB~%rwzSlq-}Y~Xj(!yO-<|AZRUWp zdVukCcffor2xJt^w0yJq_U2LKf*GvyXGz`>-Dp&0S_?X~9*7grt$a3-$~mHC5;EjI z1GJ6}yTLA4Iclo`H_DyB89fWHB6Y)^GE6vOJ7?>rcA6w}lsF+z(vvo!PW7vyZ?Ozl zIa)7Az?9yVpyzHXSXEn!wmyuu{?wpM*Ukv@4eh0x_PKC71b%%} zscw6*Zu_@g^L0DN!w}}tb=NZ!naTcJho=tDSF0sios(7QuZvB-d27qmmYI#Eww@x? zSnq7G7&|n-?(ldBEQRYQcRdVkdK_K@LhOxO%~Q=YwIy{=5o$xv-H~F$d*juQ<#i>w zu?SV!F()@ZlDi+*ZyFcIPfkQ&R%G?k%rqC?_FHY!6#~=<2FhLog1%Qm{3@HpLj5!Rsj8Tk?43$ip%r6;%!kUb? zID<pDM`&q zO)pB!)y+uFDM&5SP0BBcfiUAi%+iv~oZ^`H_{_Y_lK6PNg34bUHo5sJr8%i~MI1mg VK~@)o7$2A!85!>}$P}>wIRNa{Hk|+f diff --git a/helper_bot/utils/__pycache__/base_dependency_factory.cpython-312.pyc b/helper_bot/utils/__pycache__/base_dependency_factory.cpython-312.pyc deleted file mode 100644 index f818b5d967f5539a5897150c30006f228865ebd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2272 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 diff --git a/helper_bot/utils/__pycache__/messages.cpython-312.pyc b/helper_bot/utils/__pycache__/messages.cpython-312.pyc deleted file mode 100644 index 177f9a624fdf0db4a0f450f88dedbeeb396f824d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5461 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 diff --git a/helper_bot/utils/__pycache__/state.cpython-312.pyc b/helper_bot/utils/__pycache__/state.cpython-312.pyc deleted file mode 100644 index 9b45a9df5b9b93a8cdba9ec41109cf56a8fb9ca7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 703 zcmZ9KOKaOe5XWcr@Y`uZlajCCKujNjLJ6g6?aGE!nAo`p!=hT7N0B0TB~yHJ!8cz5 zIk=a6fRK;UPvE`ekW+32CzqVEtCbJiJjd5CGS}6|M$NnzP}5ksn|KL{Z5oY+%uHb*^Q6@> zSpMN+4g_4}gR5yk^qVZ?8VZ>TP)VV(LKTIoW5n-KOj|#S33X~~tbp;k z7ZQwF3F9#GM}g24jDL>2Kt`y++vhj#S%*EA?;hE9v)f~))%ezSnDM1$^;n^E*u<&I z3-#G@n8^)(E8RQk_E=GT{MvS`gKJodp*($ReCT(^gHw-&9UA>0gCy=JBp@f$3wKXR zFeG&MdzAF0#6pgevmoxr;&0T4W18dJq(-zEx44 zyx5%mnza6lWd)gA-@45IZqu#R)=te}8{SzYx~der3Fs*sT)l*?# g&hsPP=E&b8#3JLLju5(q*AKZ8+P)}10(WWEGt$qcf&c&j diff --git a/helper_bot/utils/base_dependency_factory.py b/helper_bot/utils/base_dependency_factory.py index 988ebe3..adb9706 100644 --- a/helper_bot/utils/base_dependency_factory.py +++ b/helper_bot/utils/base_dependency_factory.py @@ -1,35 +1,46 @@ -import configparser -import os -import sys - -from database.db import BotDB - -current_dir = os.getcwd() - - -class BaseDependencyFactory: - def __init__(self): - # Загрузка настроек из settings.ini - config_path = os.path.join(sys.path[0], 'settings.ini') - self.config = configparser.ConfigParser() - self.config.read(config_path) - self.settings = {} - self.database = BotDB(current_dir, 'database/tg-bot-database') - - for section in self.config.sections(): - self.settings[section] = {} - for key in self.config[section]: - # Преобразование значений в соответствующий тип - if key == 'PREVIEW_LINK': - self.settings[section][key] = self.config.getboolean(section, key) - elif key == 'LOGS' or key == 'TEST': - self.settings[section][key] = self.config.getboolean(section, key) - else: - self.settings[section][key] = self.config.get(section, key) - - def get_settings(self): - return self.settings - - def get_db(self) -> BotDB: - """Возвращает подключение к базе данных.""" - return self.database +import configparser +import os +import sys + +from database.db import BotDB + +current_dir = os.getcwd() + + +class BaseDependencyFactory: + def __init__(self): + # Загрузка настроек из settings.ini + config_path = os.path.join(sys.path[0], 'settings.ini') + self.config = configparser.ConfigParser() + self.config.read(config_path) + self.settings = {} + self.database = BotDB(current_dir, 'tg-bot-database.db') + + for section in self.config.sections(): + self.settings[section] = {} + for key in self.config[section]: + # Преобразование значений в соответствующий тип + if key == 'PREVIEW_LINK': + self.settings[section][key] = self.config.getboolean(section, key) + elif key == 'LOGS' or key == 'TEST': + self.settings[section][key] = self.config.getboolean(section, key) + else: + self.settings[section][key] = self.config.get(section, key) + + def get_settings(self): + return self.settings + + def get_db(self) -> BotDB: + """Возвращает подключение к базе данных.""" + return self.database + + +# Создаем единый экземпляр для всего приложения +_global_instance = None + +def get_global_instance(): + """Возвращает глобальный экземпляр BaseDependencyFactory.""" + global _global_instance + if _global_instance is None: + _global_instance = BaseDependencyFactory() + return _global_instance diff --git a/helper_bot/utils/helper_func.py b/helper_bot/utils/helper_func.py index 185308f..d749d54 100644 --- a/helper_bot/utils/helper_func.py +++ b/helper_bot/utils/helper_func.py @@ -1,389 +1,394 @@ -import html -import os -from datetime import datetime, timedelta - -from aiogram import types -from aiogram.types import InputMediaPhoto, FSInputFile, InputMediaVideo, InputMediaAudio - -from helper_bot.utils.base_dependency_factory import BaseDependencyFactory -from logs.custom_logger import logger - -bdf = BaseDependencyFactory() - -BotDB = bdf.get_db() - - -def get_first_name(message: types.Message) -> str: - first_name = html.escape(message.from_user.first_name) - return first_name - - -def get_text_message(post_text: str, first_name: str, username: str): - """ - Форматирует текст сообщения для публикации в зависимости от наличия ключевых слов "анон" и "неанон". - - Args: - post_text: Текст сообщения - first_name: Имя автора поста - username: Юзернейм автора поста - - Returns: - str: - Сформированный текст сообщения. - """ - if "неанон" in post_text or "не анон" in post_text: - return f'Пост из ТГ:\n{post_text}\n\nАвтор поста: {first_name} @{username}' - elif "анон" in post_text: - return f'Пост из ТГ:\n{post_text}\n\nПост опубликован анонимно' - else: - return f'Пост из ТГ:\n{post_text}\n\nАвтор поста: {first_name} @{username}' - - -async def download_file(message: types.Message, file_id: str): - """ - Скачивает файл по file_id из Telegram. - - Args: - message: сообщение - file_id: File ID фотографии - filename: Имя файла, под которым будет сохранено фото - - Returns: - Путь к сохраненному файлу, если файл был скачан успешно, иначе None - """ - try: - os.makedirs("files", exist_ok=True) - os.makedirs("files/photos", exist_ok=True) - os.makedirs("files/videos", exist_ok=True) - os.makedirs("files/music", exist_ok=True) - os.makedirs("files/voice", exist_ok=True) - os.makedirs("files/video_notes", exist_ok=True) - file = await message.bot.get_file(file_id) - file_path = os.path.join("files", file.file_path) - await message.bot.download_file(file_path=file.file_path, destination=file_path) - return file_path - except Exception as e: - logger.error(f"Ошибка скачивания фотографии: {e}") - return None - - -async def prepare_media_group_from_middlewares(album, post_caption: str = ''): - """ - Создает MediaGroup. - - Args: - album: Album объект из Telegram API. - post_caption: Текст подписи к первому фото. - - Returns: - Список InputMediaPhoto (MediaGroup). - """ - media_group = [] - - for i, message in enumerate(album): - if message.photo: - file_id = message.photo[-1].file_id - media_type = 'photo' - elif message.video: - file_id = message.video.file_id - media_type = 'video' - elif message.audio: - file_id = message.audio.file_id - media_type = 'audio' - else: - # Если нет фото, видео или аудио, пропускаем сообщение - continue - - # Формируем объект MediaGroup с учетом типа медиа - if i == len(album) - 1: - if media_type == 'photo': - media_group.append(InputMediaPhoto(media=file_id, caption=post_caption)) - elif media_type == 'video': - media_group.append(InputMediaVideo(media=file_id, caption=post_caption)) - elif media_type == 'audio': - media_group.append(InputMediaAudio(media=file_id, caption=post_caption)) - else: - if media_type == 'photo': - media_group.append(InputMediaPhoto(media=file_id)) - elif media_type == 'video': - media_group.append(InputMediaVideo(media=file_id)) - elif media_type == 'audio': - media_group.append(InputMediaAudio(media=file_id)) - - return media_group # Возвращаем MediaGroup - - -async def add_in_db_media_mediagroup(sent_message): - """ - Идентификатор медиа-группы - - Args: - sent_message: sent_message объект из Telegram API - - Returns: - Список InputFile (FSInputFile). - """ - media_group_message_id = sent_message[-1].message_id # Получаем идентификатор медиа-группы - for i, message in enumerate(sent_message): - if message.photo: - file_id = message.photo[-1].file_id - file_path = await download_file(message, file_id=file_id) - BotDB.add_post_content_in_db(media_group_message_id, message.message_id, file_path, 'photo') - elif message.video: - file_id = message.video.file_id - file_path = await download_file(message, file_id=file_id) - BotDB.add_post_content_in_db(media_group_message_id, message.message_id, file_path, 'video') - else: - # Если нет фото, видео или аудио, или другой контент, пропускаем сообщение - continue - - -async def add_in_db_media(sent_message): - """ - Args: - sent_message: sent_message объект из Telegram API - - Returns: - Список InputFile (FSInputFile). - """ - if sent_message.photo: - file_id = sent_message.photo[-1].file_id - file_path = await download_file(sent_message, file_id=file_id) - BotDB.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'photo') - elif sent_message.video: - file_id = sent_message.video.file_id - file_path = await download_file(sent_message, file_id=file_id) - BotDB.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'video') - elif sent_message.voice: - file_id = sent_message.voice.file_id - file_path = await download_file(sent_message, file_id=file_id) - BotDB.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'voice') - elif sent_message.audio: - file_id = sent_message.audio.file_id - file_path = await download_file(sent_message, file_id=file_id) - BotDB.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'audio') - elif sent_message.video_note: - file_id = sent_message.video_note.file_id - file_path = await download_file(sent_message, file_id=file_id) - BotDB.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'video_note') - - -async def send_media_group_message_to_private_chat(chat_id: int, message: types.Message, - media_group: list[InputMediaPhoto]): - sent_message = await message.bot.send_media_group( - chat_id=chat_id, - media=media_group, - ) - BotDB.add_post_in_db(sent_message[-1].message_id, sent_message[-1].caption, message.from_user.id) - await add_in_db_media_mediagroup(sent_message) - message_id = sent_message[-1].message_id - return message_id - - -async def send_media_group_to_channel(bot, chat_id: int, post_content: list[tuple[str]], post_text: str): - """ - Отправляет медиа-группу с подписью к последнему файлу. - - Args: - bot: Экземпляр бота aiogram. - chat_id: ID чата для отправки. - post_content: Список кортежей с путями к файлам. - post_text: Текст подписи. - """ - media = [] - for file_path in post_content: - try: - file = FSInputFile(path=file_path[0]) - type = file_path[1] - if type == 'video': - media.append(types.InputMediaVideo(media=file)) - if type == 'photo': - media.append(types.InputMediaPhoto(media=file)) - except FileNotFoundError: - logger.error(f"Файл не найден: {file_path[0]}") - return - - # Добавляем подпись к последнему файлу - if media: - media[-1].caption = post_text - - await bot.send_media_group(chat_id=chat_id, media=media) - - -async def send_text_message(chat_id, message: types.Message, post_text: str, markup: types.ReplyKeyboardMarkup = None): - if markup is None: - sent_message = await message.bot.send_message( - chat_id=chat_id, - text=post_text - ) - message_id = sent_message.message_id - return message_id - else: - sent_message = await message.bot.send_message( - chat_id=chat_id, - text=post_text, - reply_markup=markup - ) - message_id = sent_message.message_id - return message_id - - -async def send_photo_message(chat_id, message: types.Message, photo: str, post_text: str, - markup: types.ReplyKeyboardMarkup = None): - if markup is None: - sent_message = await message.bot.send_photo( - chat_id=chat_id, - caption=post_text, - photo=photo - ) - else: - sent_message = await message.bot.send_photo( - chat_id=chat_id, - caption=post_text, - photo=photo, - reply_markup=markup - ) - return sent_message - - -async def send_video_message(chat_id, message: types.Message, video: str, post_text: str = "", - markup: types.ReplyKeyboardMarkup = None): - if markup is None: - sent_message = await message.bot.send_video( - chat_id=chat_id, - caption=post_text, - video=video - ) - else: - sent_message = await message.bot.send_video( - chat_id=chat_id, - caption=post_text, - video=video, - reply_markup=markup - ) - return sent_message - - -async def send_video_note_message(chat_id, message: types.Message, video_note: str, - markup: types.ReplyKeyboardMarkup = None): - if markup is None: - sent_message = await message.bot.send_video_note( - chat_id=chat_id, - video_note=video_note - ) - else: - sent_message = await message.bot.send_video_note( - chat_id=chat_id, - video_note=video_note, - reply_markup=markup - ) - return sent_message - - -async def send_audio_message(chat_id, message: types.Message, audio: str, post_text: str, - markup: types.ReplyKeyboardMarkup = None): - if markup is None: - sent_message = await message.bot.send_audio( - chat_id=chat_id, - caption=post_text, - audio=audio - ) - else: - sent_message = await message.bot.send_audio( - chat_id=chat_id, - caption=post_text, - audio=audio, - reply_markup=markup - ) - return sent_message - - -async def send_voice_message(chat_id, message: types.Message, voice: str, - markup: types.ReplyKeyboardMarkup = None): - if markup is None: - sent_message = await message.bot.send_voice( - chat_id=chat_id, - voice=voice - ) - else: - sent_message = await message.bot.send_voice( - chat_id=chat_id, - voice=voice, - reply_markup=markup - ) - return sent_message - - -def check_access(user_id: int): - """Проверка прав на совершение действий""" - return BotDB.is_admin(user_id) - - -def add_days_to_date(days: int): - """Прибавляет указанное количество дней к текущей дате и возвращает дату в формате DD-MM-YYYY.""" - current_date = datetime.now() - future_date = current_date + timedelta(days=days) - formatted_date = future_date.strftime("%d-%m-%Y") - return formatted_date - - -def get_banned_users_list(offset: int): - """ - Возвращает сообщение со списком пользователей и словарь с ником + идентификатором - - Args: - offset: отступ для запроса в базу данных - - Returns: - message - текст сообщения - user_ids - лист кортежей [(user_name: user_id)] - """ - users = BotDB.get_banned_users_from_db_with_limits(limit=7, offset=offset) - message = "Список заблокированных пользователей:\n" - - for user in users: - message += f"Пользователь: {user[0]}\n" - message += f"Причина бана: {user[2]}\n" - message += f"Дата разбана: {user[3]}\n\n" - return message - - -def get_banned_users_buttons(): - """ - Возвращает сообщение со списком пользователей и словарь с ником + идентификатором - - Args: - offset: отступ для запроса в базу данных - - Returns: - message - текст сообщения - user_ids - лист кортежей [(user_name: user_id)] - """ - users = BotDB.get_banned_users_from_db() - user_ids = [] - - for user in users: - user_ids.append((user[0], user[1])) - return user_ids - - -def delete_user_blacklist(user_id: int): - return BotDB.delete_user_blacklist(user_id=user_id) - - -def check_username_and_full_name(user_id: int, username: str, full_name: str): - username_db, full_name_db = BotDB.get_username_and_full_name(user_id=user_id) - return username != username_db or full_name != full_name_db - - -def unban_notifier(self): - # Получение сегодняшней даты в формате DD-MM-YYYY - current_date = datetime.now() - today = current_date.strftime("%d-%m-%Y") - # Получение списка разблокированных пользователей - unblocked_users = self.BotDB.get_users_for_unblock_today(today) - message = "Разблокированные пользователи:\n" - for user_id, user_name in unblocked_users.items(): - message += f"ID: {user_id}, Имя: {user_name}\n" - - # Отправка сообщения в канал - self.bot.send_message(self.GROUP_FOR_MESSAGE, message) +import html +import os +from datetime import datetime, timedelta + +from aiogram import types +from aiogram.types import InputMediaPhoto, FSInputFile, InputMediaVideo, InputMediaAudio + +from helper_bot.utils.base_dependency_factory import BaseDependencyFactory +from logs.custom_logger import logger + + +def get_first_name(message: types.Message) -> str: + first_name = html.escape(message.from_user.first_name) + return first_name + + +def get_text_message(post_text: str, first_name: str, username: str = None): + """ + Форматирует текст сообщения для публикации в зависимости от наличия ключевых слов "анон" и "неанон". + + Args: + post_text: Текст сообщения + first_name: Имя автора поста + username: Юзернейм автора поста (может быть None) + + Returns: + str: - Сформированный текст сообщения. + """ + # Формируем строку с информацией об авторе + if username: + author_info = f"{first_name} @{username}" + else: + author_info = f"{first_name} (Ник не указан)" + + if "неанон" in post_text or "не анон" in post_text: + return f'Пост из ТГ:\n{post_text}\n\nАвтор поста: {author_info}' + elif "анон" in post_text: + return f'Пост из ТГ:\n{post_text}\n\nПост опубликован анонимно' + else: + return f'Пост из ТГ:\n{post_text}\n\nАвтор поста: {author_info}' + + +async def download_file(message: types.Message, file_id: str): + """ + Скачивает файл по file_id из Telegram. + + Args: + message: сообщение + file_id: File ID фотографии + filename: Имя файла, под которым будет сохранено фото + + Returns: + Путь к сохраненному файлу, если файл был скачан успешно, иначе None + """ + try: + os.makedirs("files", exist_ok=True) + os.makedirs("files/photos", exist_ok=True) + os.makedirs("files/videos", exist_ok=True) + os.makedirs("files/music", exist_ok=True) + os.makedirs("files/voice", exist_ok=True) + os.makedirs("files/video_notes", exist_ok=True) + file = await message.bot.get_file(file_id) + file_path = os.path.join("files", file.file_path) + await message.bot.download_file(file_path=file.file_path, destination=file_path) + return file_path + except Exception as e: + logger.error(f"Ошибка скачивания фотографии: {e}") + return None + + +async def prepare_media_group_from_middlewares(album, post_caption: str = ''): + """ + Создает MediaGroup. + + Args: + album: Album объект из Telegram API. + post_caption: Текст подписи к первому фото. + + Returns: + Список InputMediaPhoto (MediaGroup). + """ + media_group = [] + + for i, message in enumerate(album): + if message.photo: + file_id = message.photo[-1].file_id + media_type = 'photo' + elif message.video: + file_id = message.video.file_id + media_type = 'video' + elif message.audio: + file_id = message.audio.file_id + media_type = 'audio' + else: + # Если нет фото, видео или аудио, пропускаем сообщение + continue + + # Формируем объект MediaGroup с учетом типа медиа + if i == len(album) - 1: + if media_type == 'photo': + media_group.append(InputMediaPhoto(media=file_id, caption=post_caption)) + elif media_type == 'video': + media_group.append(InputMediaVideo(media=file_id, caption=post_caption)) + elif media_type == 'audio': + media_group.append(InputMediaAudio(media=file_id, caption=post_caption)) + else: + if media_type == 'photo': + media_group.append(InputMediaPhoto(media=file_id)) + elif media_type == 'video': + media_group.append(InputMediaVideo(media=file_id)) + elif media_type == 'audio': + media_group.append(InputMediaAudio(media=file_id)) + + return media_group # Возвращаем MediaGroup + + +async def add_in_db_media_mediagroup(sent_message, bot_db): + """ + Идентификатор медиа-группы + + Args: + sent_message: sent_message объект из Telegram API + bot_db: Экземпляр базы данных + + Returns: + Список InputFile (FSInputFile). + """ + media_group_message_id = sent_message[-1].message_id # Получаем идентификатор медиа-группы + for i, message in enumerate(sent_message): + if message.photo: + file_id = message.photo[-1].file_id + file_path = await download_file(message, file_id=file_id) + bot_db.add_post_content_in_db(media_group_message_id, message.message_id, file_path, 'photo') + elif message.video: + file_id = message.video.file_id + file_path = await download_file(message, file_id=file_id) + bot_db.add_post_content_in_db(media_group_message_id, message.message_id, file_path, 'video') + else: + # Если нет фото, видео или аудио, или другой контент, пропускаем сообщение + continue + + +async def add_in_db_media(sent_message, bot_db): + """ + Args: + sent_message: sent_message объект из Telegram API + bot_db: Экземпляр базы данных + + Returns: + Список InputFile (FSInputFile). + """ + if sent_message.photo: + file_id = sent_message.photo[-1].file_id + file_path = await download_file(sent_message, file_id=file_id) + bot_db.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'photo') + elif sent_message.video: + file_id = sent_message.video.file_id + file_path = await download_file(sent_message, file_id=file_id) + bot_db.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'video') + elif sent_message.voice: + file_id = sent_message.voice.file_id + file_path = await download_file(sent_message, file_id=file_id) + bot_db.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'voice') + elif sent_message.audio: + file_id = sent_message.audio.file_id + file_path = await download_file(sent_message, file_id=file_id) + bot_db.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'audio') + elif sent_message.video_note: + file_id = sent_message.video_note.file_id + file_path = await download_file(sent_message, file_id=file_id) + bot_db.add_post_content_in_db(sent_message.message_id, sent_message.message_id, file_path, 'video_note') + + +async def send_media_group_message_to_private_chat(chat_id: int, message: types.Message, + media_group: list[InputMediaPhoto], bot_db): + sent_message = await message.bot.send_media_group( + chat_id=chat_id, + media=media_group, + ) + bot_db.add_post_in_db(sent_message[-1].message_id, sent_message[-1].caption, message.from_user.id) + await add_in_db_media_mediagroup(sent_message, bot_db) + message_id = sent_message[-1].message_id + return message_id + + +async def send_media_group_to_channel(bot, chat_id: int, post_content: list[tuple[str]], post_text: str): + """ + Отправляет медиа-группу с подписью к последнему файлу. + + Args: + bot: Экземпляр бота aiogram. + chat_id: ID чата для отправки. + post_content: Список кортежей с путями к файлам. + post_text: Текст подписи. + """ + media = [] + for file_path in post_content: + try: + file = FSInputFile(path=file_path[0]) + type = file_path[1] + if type == 'video': + media.append(types.InputMediaVideo(media=file)) + if type == 'photo': + media.append(types.InputMediaPhoto(media=file)) + except FileNotFoundError: + logger.error(f"Файл не найден: {file_path[0]}") + return + + # Добавляем подпись к последнему файлу + if media: + media[-1].caption = post_text + + await bot.send_media_group(chat_id=chat_id, media=media) + + +async def send_text_message(chat_id, message: types.Message, post_text: str, markup: types.ReplyKeyboardMarkup = None): + if markup is None: + sent_message = await message.bot.send_message( + chat_id=chat_id, + text=post_text + ) + message_id = sent_message.message_id + return message_id + else: + sent_message = await message.bot.send_message( + chat_id=chat_id, + text=post_text, + reply_markup=markup + ) + message_id = sent_message.message_id + return message_id + + +async def send_photo_message(chat_id, message: types.Message, photo: str, post_text: str, + markup: types.ReplyKeyboardMarkup = None): + if markup is None: + sent_message = await message.bot.send_photo( + chat_id=chat_id, + caption=post_text, + photo=photo + ) + else: + sent_message = await message.bot.send_photo( + chat_id=chat_id, + caption=post_text, + photo=photo, + reply_markup=markup + ) + return sent_message + + +async def send_video_message(chat_id, message: types.Message, video: str, post_text: str = "", + markup: types.ReplyKeyboardMarkup = None): + if markup is None: + sent_message = await message.bot.send_video( + chat_id=chat_id, + caption=post_text, + video=video + ) + else: + sent_message = await message.bot.send_video( + chat_id=chat_id, + caption=post_text, + video=video, + reply_markup=markup + ) + return sent_message + + +async def send_video_note_message(chat_id, message: types.Message, video_note: str, + markup: types.ReplyKeyboardMarkup = None): + if markup is None: + sent_message = await message.bot.send_video_note( + chat_id=chat_id, + video_note=video_note + ) + else: + sent_message = await message.bot.send_video_note( + chat_id=chat_id, + video_note=video_note, + reply_markup=markup + ) + return sent_message + + +async def send_audio_message(chat_id, message: types.Message, audio: str, post_text: str, + markup: types.ReplyKeyboardMarkup = None): + if markup is None: + sent_message = await message.bot.send_audio( + chat_id=chat_id, + caption=post_text, + audio=audio + ) + else: + sent_message = await message.bot.send_audio( + chat_id=chat_id, + caption=post_text, + audio=audio, + reply_markup=markup + ) + return sent_message + + +async def send_voice_message(chat_id, message: types.Message, voice: str, + markup: types.ReplyKeyboardMarkup = None): + if markup is None: + sent_message = await message.bot.send_voice( + chat_id=chat_id, + voice=voice + ) + else: + sent_message = await message.bot.send_voice( + chat_id=chat_id, + voice=voice, + reply_markup=markup + ) + return sent_message + + +def check_access(user_id: int, bot_db): + """Проверка прав на совершение действий""" + return bot_db.is_admin(user_id) + + +def add_days_to_date(days: int): + """Прибавляет указанное количество дней к текущей дате и возвращает дату в формате DD-MM-YYYY.""" + current_date = datetime.now() + future_date = current_date + timedelta(days=days) + formatted_date = future_date.strftime("%d-%m-%Y") + return formatted_date + + +def get_banned_users_list(offset: int, bot_db): + """ + Возвращает сообщение со списком пользователей и словарь с ником + идентификатором + + Args: + offset: отступ для запроса в базу данных + bot_db: Экземпляр базы данных + + Returns: + message - текст сообщения + user_ids - лист кортежей [(user_name: user_id)] + """ + users = bot_db.get_banned_users_from_db_with_limits(limit=7, offset=offset) + message = "Список заблокированных пользователей:\n" + + for user in users: + message += f"Пользователь: {user[0]}\n" + message += f"Причина бана: {user[2]}\n" + message += f"Дата разбана: {user[3]}\n\n" + return message + + +def get_banned_users_buttons(bot_db): + """ + Возвращает сообщение со списком пользователей и словарь с ником + идентификатором + + Args: + bot_db: Экземпляр базы данных + + Returns: + message - текст сообщения + user_ids - лист кортежей [(user_name: user_id)] + """ + users = bot_db.get_banned_users_from_db() + user_ids = [] + + for user in users: + user_ids.append((user[0], user[1])) + return user_ids + + +def delete_user_blacklist(user_id: int, bot_db): + return bot_db.delete_user_blacklist(user_id=user_id) + + +def check_username_and_full_name(user_id: int, username: str, full_name: str, bot_db): + username_db, full_name_db = bot_db.get_username_and_full_name(user_id=user_id) + return username != username_db or full_name != full_name_db + + +def unban_notifier(self): + # Получение сегодняшней даты в формате DD-MM-YYYY + current_date = datetime.now() + today = current_date.strftime("%d-%m-%Y") + # Получение списка разблокированных пользователей + unblocked_users = self.BotDB.get_users_for_unblock_today(today) + message = "Разблокированные пользователи:\n" + for user_id, user_name in unblocked_users.items(): + message += f"ID: {user_id}, Имя: {user_name}\n" + + # Отправка сообщения в канал + self.bot.send_message(self.GROUP_FOR_MESSAGE, message) diff --git a/helper_bot/utils/state.py b/helper_bot/utils/state.py index 92f1fda..65a2020 100644 --- a/helper_bot/utils/state.py +++ b/helper_bot/utils/state.py @@ -8,6 +8,8 @@ class StateUser(StatesGroup): CHAT = State() PRE_CHAT = State() PRE_BAN = State() + PRE_BAN_ID = State() + PRE_BAN_FORWARD = State() BAN_2 = State() BAN_3 = State() BAN_4 = State() diff --git a/migrations/000_migrations_init.py b/migrations/000_migrations_init.py index cc3374e..765de44 100644 --- a/migrations/000_migrations_init.py +++ b/migrations/000_migrations_init.py @@ -1,34 +1,34 @@ -import os - -from database.db import BotDB - -# Получаем текущую директорию -current_dir = os.path.dirname(__file__) - -# Переходим на уровень выше -parent_dir = os.path.dirname(current_dir) - -BotDB = BotDB(parent_dir, 'database/tg-bot-database') - - -def get_filename(): - """Возвращает имя файла без расширения.""" - filename = os.path.basename(__file__) - filename = os.path.splitext(filename)[0] - return filename - - -def main(): - migrations_init = """ - CREATE TABLE IF NOT EXISTS migrations ( - version INTEGER PRIMARY KEY NOT NULL, - script_name TEXT NOT NULL, - created_at TEXT - ); - """ - BotDB.create_table(migrations_init) - BotDB.update_version(0, get_filename()) - - -if __name__ == "__main__": - main() +import os + +from database.db import BotDB + +# Получаем текущую директорию +current_dir = os.path.dirname(__file__) + +# Переходим на уровень выше +parent_dir = os.path.dirname(current_dir) + +BotDB = BotDB(parent_dir, 'tg-bot-database.db') + + +def get_filename(): + """Возвращает имя файла без расширения.""" + filename = os.path.basename(__file__) + filename = os.path.splitext(filename)[0] + return filename + + +def main(): + migrations_init = """ + CREATE TABLE IF NOT EXISTS migrations ( + version INTEGER PRIMARY KEY NOT NULL, + script_name TEXT NOT NULL, + created_at TEXT + ); + """ + BotDB.create_table(migrations_init) + BotDB.update_version(0, get_filename()) + + +if __name__ == "__main__": + main() diff --git a/migrations/001_create_new_tables.py b/migrations/001_create_new_tables.py index 9bc06f9..8be6907 100644 --- a/migrations/001_create_new_tables.py +++ b/migrations/001_create_new_tables.py @@ -1,63 +1,63 @@ -import os - -from database.db import BotDB - -# Получаем текущую директорию -current_dir = os.path.dirname(__file__) - -# Переходим на уровень выше -parent_dir = os.path.dirname(current_dir) - -BotDB = BotDB(parent_dir, 'database/tg-bot-database') - - -def get_filename(): - """Возвращает имя файла без расширения.""" - filename = os.path.basename(__file__) - filename = os.path.splitext(filename)[0] - return filename - - -def main(): - # Проверка версии миграций - current_version = BotDB.get_current_version() # Добавьте функцию для получения версии - - # Выполнение миграций и проверка последней версии - if current_version < 1: - # Скрипты миграции - create_table_sql_1 = """ - CREATE TABLE IF NOT EXISTS "admins" ( - user_id INTEGER NOT NULL, - "role" TEXT - ); - """ - create_table_sql_2 = """CREATE TABLE IF NOT EXISTS "blacklist" - ( - "user_id" INTEGER NOT NULL UNIQUE, - "user_name" INTEGER, - "message_for_user" INTEGER, - "date_to_unban" INTEGER - ); - """ - create_table_sql_3 = """ - CREATE TABLE IF NOT EXISTS user_messages ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - message_text TEXT, - user_id INTEGER, - message_id INTEGER NOT NULL, - date TEXT - ); - """ - # Применение миграции - BotDB.create_table(create_table_sql_1) - BotDB.create_table(create_table_sql_2) - BotDB.create_table(create_table_sql_3) - BotDB.add_admin(842766148, 'creator') - BotDB.add_admin(920057022, 'admin') - filename = get_filename() - - BotDB.update_version(1, filename) - - -if __name__ == "__main__": - main() +import os + +from database.db import BotDB + +# Получаем текущую директорию +current_dir = os.path.dirname(__file__) + +# Переходим на уровень выше +parent_dir = os.path.dirname(current_dir) + +BotDB = BotDB(parent_dir, 'tg-bot-database.db') + + +def get_filename(): + """Возвращает имя файла без расширения.""" + filename = os.path.basename(__file__) + filename = os.path.splitext(filename)[0] + return filename + + +def main(): + # Проверка версии миграций + current_version = BotDB.get_current_version() # Добавьте функцию для получения версии + + # Выполнение миграций и проверка последней версии + if current_version < 1: + # Скрипты миграции + create_table_sql_1 = """ + CREATE TABLE IF NOT EXISTS "admins" ( + user_id INTEGER NOT NULL, + "role" TEXT + ); + """ + create_table_sql_2 = """CREATE TABLE IF NOT EXISTS "blacklist" + ( + "user_id" INTEGER NOT NULL UNIQUE, + "user_name" INTEGER, + "message_for_user" INTEGER, + "date_to_unban" INTEGER + ); + """ + create_table_sql_3 = """ + CREATE TABLE IF NOT EXISTS user_messages ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + message_text TEXT, + user_id INTEGER, + message_id INTEGER NOT NULL, + date TEXT + ); + """ + # Применение миграции + BotDB.create_table(create_table_sql_1) + BotDB.create_table(create_table_sql_2) + BotDB.create_table(create_table_sql_3) + BotDB.add_admin(842766148, 'creator') + BotDB.add_admin(920057022, 'admin') + filename = get_filename() + + BotDB.update_version(1, filename) + + +if __name__ == "__main__": + main() diff --git a/migrations/002_create_tables_media_group.py b/migrations/002_create_tables_media_group.py index b87f5aa..baa5713 100644 --- a/migrations/002_create_tables_media_group.py +++ b/migrations/002_create_tables_media_group.py @@ -1,61 +1,61 @@ -import os - -from database.db import BotDB - -# Получаем текущую директорию -current_dir = os.path.dirname(__file__) - -# Переходим на уровень выше -parent_dir = os.path.dirname(current_dir) - -BotDB = BotDB(parent_dir, 'database/tg-bot-database') - - -def get_filename(): - """Возвращает имя файла без расширения.""" - filename = os.path.basename(__file__) - filename = os.path.splitext(filename)[0] - return filename - - -def main(): - # Проверка версии миграций - current_version = BotDB.get_current_version() # Добавьте функцию для получения версии - - # Выполнение миграций и проверка последней версии - if current_version < 2: - # Скрипты миграции - create_table_sql_1 = """ - CREATE TABLE IF NOT EXISTS "post_from_telegram_suggest" - ( - message_id INTEGER not null, - text TEXT, - helper_text_message_id INTEGER, - author_id INTEGER, - created_at TEXT - ); - """ - create_table_sql_2 = """ - CREATE TABLE IF NOT EXISTS message_link_to_content ( - post_id INTEGER NOT NULL, - message_id INTEGER NOT NULL - ); - """ - create_table_sql_3 = """ - CREATE TABLE IF NOT EXISTS content_post_from_telegram ( - message_id INTEGER NOT NULL, - content_name TEXT NOT NULL, - content_type TEXT - ); - """ - # Применение миграции - BotDB.create_table(create_table_sql_1) - BotDB.create_table(create_table_sql_2) - BotDB.create_table(create_table_sql_3) - filename = get_filename() - - BotDB.update_version(2, filename) - - -if __name__ == "__main__": - main() +import os + +from database.db import BotDB + +# Получаем текущую директорию +current_dir = os.path.dirname(__file__) + +# Переходим на уровень выше +parent_dir = os.path.dirname(current_dir) + +BotDB = BotDB(parent_dir, 'tg-bot-database.db') + + +def get_filename(): + """Возвращает имя файла без расширения.""" + filename = os.path.basename(__file__) + filename = os.path.splitext(filename)[0] + return filename + + +def main(): + # Проверка версии миграций + current_version = BotDB.get_current_version() # Добавьте функцию для получения версии + + # Выполнение миграций и проверка последней версии + if current_version < 2: + # Скрипты миграции + create_table_sql_1 = """ + CREATE TABLE IF NOT EXISTS "post_from_telegram_suggest" + ( + message_id INTEGER not null, + text TEXT, + helper_text_message_id INTEGER, + author_id INTEGER, + created_at TEXT + ); + """ + create_table_sql_2 = """ + CREATE TABLE IF NOT EXISTS message_link_to_content ( + post_id INTEGER NOT NULL, + message_id INTEGER NOT NULL + ); + """ + create_table_sql_3 = """ + CREATE TABLE IF NOT EXISTS content_post_from_telegram ( + message_id INTEGER NOT NULL, + content_name TEXT NOT NULL, + content_type TEXT + ); + """ + # Применение миграции + BotDB.create_table(create_table_sql_1) + BotDB.create_table(create_table_sql_2) + BotDB.create_table(create_table_sql_3) + filename = get_filename() + + BotDB.update_version(2, filename) + + +if __name__ == "__main__": + main() diff --git a/pytest.ini b/pytest.ini index 685a09e..6b22b97 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,8 +1,19 @@ -[pytest] -pythonpath = . -python_files = test_*.py *_test.py -python_functions = test_* +[tool:pytest] testpaths = tests - -[report] -omit = *myenv/*, custom_logger.py, *venv/*, tests/* \ No newline at end of file +python_files = test_*.py +python_classes = Test* +python_functions = test_* +addopts = + -v + --tb=short + --strict-markers + --disable-warnings + --asyncio-mode=auto +markers = + asyncio: marks tests as async (deselect with '-m "not asyncio"') + slow: marks tests as slow (deselect with '-m "not slow"') + integration: marks tests as integration tests + unit: marks tests as unit tests +filterwarnings = + ignore::DeprecationWarning + ignore::PendingDeprecationWarning \ No newline at end of file diff --git a/run_helper.py b/run_helper.py index d7fa497..16a0eaf 100644 --- a/run_helper.py +++ b/run_helper.py @@ -1,7 +1,7 @@ import asyncio from helper_bot.main import start_bot -from helper_bot.utils.base_dependency_factory import BaseDependencyFactory +from helper_bot.utils.base_dependency_factory import get_global_instance if __name__ == '__main__': - asyncio.run(start_bot(BaseDependencyFactory())) + asyncio.run(start_bot(get_global_instance())) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..dab99b4 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,234 @@ +import pytest +import asyncio +import os +import sys +from unittest.mock import Mock, AsyncMock, patch +from aiogram.types import Message, User, Chat +from aiogram.fsm.context import FSMContext + +from database.db import BotDB + +# Импортируем моки в самом начале +import tests.mocks + + +@pytest.fixture(scope="session") +def event_loop(): + """Создает event loop для асинхронных тестов""" + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +@pytest.fixture +def mock_message(): + """Создает базовый мок сообщения для тестов""" + message = Mock(spec=Message) + message.from_user = Mock(spec=User) + message.from_user.id = 123456 + message.from_user.full_name = "Test User" + message.from_user.username = "testuser" + message.from_user.first_name = "Test" + message.from_user.is_bot = False + message.from_user.language_code = "ru" + message.chat = Mock(spec=Chat) + message.chat.id = 123456 + message.chat.type = "private" + message.message_id = 1 + message.text = "/start" + message.forward = AsyncMock() + message.answer = AsyncMock() + message.answer_sticker = AsyncMock() + message.bot.send_message = AsyncMock() + return message + + +@pytest.fixture +def mock_state(): + """Создает мок состояния FSM для тестов""" + state = Mock(spec=FSMContext) + state.set_state = AsyncMock() + state.get_state = AsyncMock(return_value="START") + return state + + +@pytest.fixture +def mock_db(): + """Создает мок базы данных для тестов""" + db = Mock(spec=BotDB) + db.user_exists = Mock(return_value=False) + db.add_new_user_in_db = Mock() + db.update_date_for_user = Mock() + db.update_username_and_full_name = Mock() + db.add_post_in_db = Mock() + db.update_info_about_stickers = Mock() + db.add_new_message_in_db = Mock() + db.get_info_about_stickers = Mock(return_value=False) + db.get_username_and_full_name = Mock(return_value=("testuser", "Test User")) + return db + + +@pytest.fixture +def mock_bot(): + """Создает мок бота для тестов""" + bot = AsyncMock() + bot.send_message = AsyncMock() + bot.delete_webhook = AsyncMock() + return bot + + +@pytest.fixture +def mock_dispatcher(): + """Создает мок диспетчера для тестов""" + dispatcher = AsyncMock() + dispatcher.include_routers = Mock() + dispatcher.start_polling = AsyncMock() + return dispatcher + + +@pytest.fixture +def test_settings(): + """Возвращает тестовые настройки""" + return { + 'Telegram': { + 'bot_token': 'test_token_123', + 'preview_link': False, + 'group_for_posts': '-1001234567890', + 'group_for_message': '-1001234567891', + 'main_public': '-1001234567892', + 'group_for_logs': '-1001234567893', + 'important_logs': '-1001234567894' + }, + 'Settings': { + 'logs': True, + 'test': False + } + } + + +@pytest.fixture +def mock_factory(test_settings, mock_db): + """Создает мок фабрики зависимостей""" + factory = Mock() + factory.settings = test_settings + factory.get_db = Mock(return_value=mock_db) + factory.database = mock_db + return factory + + +@pytest.fixture +def sample_photo_message(mock_message): + """Создает сообщение с фото для тестов""" + mock_message.content_type = 'photo' + mock_message.caption = 'Тестовое фото' + mock_message.media_group_id = None + mock_message.photo = [Mock()] + mock_message.photo[-1].file_id = 'photo_file_id' + return mock_message + + +@pytest.fixture +def sample_video_message(mock_message): + """Создает сообщение с видео для тестов""" + mock_message.content_type = 'video' + mock_message.caption = 'Тестовое видео' + mock_message.media_group_id = None + mock_message.video = Mock() + mock_message.video.file_id = 'video_file_id' + return mock_message + + +@pytest.fixture +def sample_audio_message(mock_message): + """Создает сообщение с аудио для тестов""" + mock_message.content_type = 'audio' + mock_message.caption = 'Тестовое аудио' + mock_message.media_group_id = None + mock_message.audio = Mock() + mock_message.audio.file_id = 'audio_file_id' + return mock_message + + +@pytest.fixture +def sample_voice_message(mock_message): + """Создает голосовое сообщение для тестов""" + mock_message.content_type = 'voice' + mock_message.media_group_id = None + mock_message.voice = Mock() + mock_message.voice.file_id = 'voice_file_id' + return mock_message + + +@pytest.fixture +def sample_video_note_message(mock_message): + """Создает видеокружок для тестов""" + mock_message.content_type = 'video_note' + mock_message.media_group_id = None + mock_message.video_note = Mock() + mock_message.video_note.file_id = 'video_note_file_id' + return mock_message + + +@pytest.fixture +def sample_media_group(mock_message): + """Создает медиагруппу для тестов""" + mock_message.media_group_id = 'group_123' + mock_message.content_type = 'photo' + album = [mock_message] + album[0].caption = 'Подпись к медиагруппе' + return album + + +@pytest.fixture +def sample_text_message(mock_message): + """Создает текстовое сообщение для тестов""" + mock_message.content_type = 'text' + mock_message.text = 'Тестовое текстовое сообщение' + mock_message.media_group_id = None + return mock_message + + +@pytest.fixture +def sample_document_message(mock_message): + """Создает сообщение с документом для тестов""" + mock_message.content_type = 'document' + mock_message.media_group_id = None + return mock_message + + +# Маркеры для категоризации тестов +def pytest_configure(config): + """Настройка маркеров pytest""" + config.addinivalue_line( + "markers", "asyncio: mark test as async" + ) + config.addinivalue_line( + "markers", "slow: mark test as slow" + ) + config.addinivalue_line( + "markers", "integration: mark test as integration test" + ) + config.addinivalue_line( + "markers", "unit: mark test as unit test" + ) + + +# Автоматическая маркировка тестов +def pytest_collection_modifyitems(config, items): + """Автоматически маркирует тесты по их расположению""" + for item in items: + # Маркируем асинхронные тесты + if "async" in item.name or "Async" in item.name: + item.add_marker(pytest.mark.asyncio) + + # Маркируем интеграционные тесты + if "integration" in item.name.lower() or "Integration" in str(item.cls): + item.add_marker(pytest.mark.integration) + + # Маркируем unit тесты + if "unit" in item.name.lower() or "Unit" in str(item.cls): + item.add_marker(pytest.mark.unit) + + # Маркируем медленные тесты + if "slow" in item.name.lower() or "Slow" in str(item.cls): + item.add_marker(pytest.mark.slow) diff --git a/tests/mocks.py b/tests/mocks.py new file mode 100644 index 0000000..c64667e --- /dev/null +++ b/tests/mocks.py @@ -0,0 +1,52 @@ +""" +Моки для тестового окружения +""" +import sys +import os +from unittest.mock import Mock, patch + +# Патчим загрузку настроек до импорта модулей +def setup_test_mocks(): + """Настройка моков для тестов""" + # Мокаем ConfigParser + mock_config = Mock() + + def mock_getitem(section): + if section == 'Telegram': + return { + 'bot_token': 'test_token_123', + 'preview_link': 'False', + 'main_public': '@test', + 'group_for_posts': '-1001234567890', + 'group_for_message': '-1001234567891', + 'group_for_logs': '-1001234567893', + 'important_logs': '-1001234567894', + 'test_channel': '-1001234567895' + } + elif section == 'Settings': + return { + 'logs': 'True', + 'test': 'False' + } + return {} + + # Создаем MagicMock для поддержки __getitem__ + mock_config_instance = Mock() + mock_config_instance.sections.return_value = ['Telegram', 'Settings'] + mock_config_instance.__getitem__ = Mock(side_effect=mock_getitem) + + mock_config.return_value = mock_config_instance + + # Применяем патчи + config_patcher = patch('helper_bot.utils.base_dependency_factory.configparser.ConfigParser', mock_config) + config_patcher.start() + + # Мокаем BotDB + mock_db = Mock() + db_patcher = patch('helper_bot.utils.base_dependency_factory.BotDB', mock_db) + db_patcher.start() + + return config_patcher, db_patcher + +# Настраиваем моки при импорте модуля +config_patcher, db_patcher = setup_test_mocks() diff --git a/tests/test_bot.py b/tests/test_bot.py new file mode 100644 index 0000000..a08942f --- /dev/null +++ b/tests/test_bot.py @@ -0,0 +1,339 @@ +# Импортируем моки в самом начале +import tests.mocks + +import pytest +import asyncio +from unittest.mock import Mock, AsyncMock, patch, MagicMock +from aiogram import Bot, Dispatcher +from aiogram.types import Message, User, Chat, MessageEntity +from aiogram.fsm.context import FSMContext +from aiogram.fsm.storage.memory import MemoryStorage + +from helper_bot.main import start_bot +from helper_bot.handlers.private.private_handlers import ( + handle_start_message, + restart_function, + suggest_post, + end_message, + suggest_router, + stickers, + connect_with_admin, + resend_message_in_group_for_message +) +from helper_bot.utils.base_dependency_factory import BaseDependencyFactory, get_global_instance +from database.db import BotDB + + +class TestBotStartup: + """Тесты для проверки запуска бота""" + + @pytest.mark.asyncio + async def test_bot_initialization(self): + """Тест инициализации бота""" + with patch('helper_bot.main.Bot') as mock_bot_class: + with patch('helper_bot.main.Dispatcher') as mock_dp_class: + with patch('helper_bot.main.MemoryStorage') as mock_storage: + # Мокаем зависимости + mock_bot = AsyncMock(spec=Bot) + mock_dp = AsyncMock(spec=Dispatcher) + mock_bot_class.return_value = mock_bot + mock_dp_class.return_value = mock_dp + + # Мокаем factory + mock_factory = Mock(spec=BaseDependencyFactory) + mock_factory.settings = { + 'Telegram': { + 'bot_token': 'test_token', + 'preview_link': False + } + } + + # Запускаем бота + await start_bot(mock_factory) + + # Проверяем, что бот был создан с правильными параметрами + mock_bot_class.assert_called_once() + call_args = mock_bot_class.call_args + assert call_args[1]['token'] == 'test_token' + assert call_args[1]['default'].parse_mode == 'HTML' + assert call_args[1]['default'].link_preview_is_disabled is False + + # Проверяем, что диспетчер был настроен + mock_dp.include_routers.assert_called_once() + mock_bot.delete_webhook.assert_called_once_with(drop_pending_updates=True) + mock_dp.start_polling.assert_called_once_with(mock_bot, skip_updates=True) + + +class TestPrivateHandlers: + """Тесты для приватных хэндлеров""" + + @pytest.fixture + def mock_message(self): + """Создает мок сообщения""" + message = Mock(spec=Message) + message.from_user = Mock(spec=User) + message.from_user.id = 123456 + message.from_user.full_name = "Test User" + message.from_user.username = "testuser" + message.from_user.first_name = "Test" + message.from_user.is_bot = False + message.from_user.language_code = "ru" + message.chat = Mock(spec=Chat) + message.chat.id = 123456 + message.chat.type = "private" + message.text = "/start" + message.message_id = 1 + message.forward = AsyncMock() + message.answer = AsyncMock() + message.answer_sticker = AsyncMock() + message.bot.send_message = AsyncMock() + return message + + @pytest.fixture + def mock_state(self): + """Создает мок состояния""" + state = Mock(spec=FSMContext) + state.set_state = AsyncMock() + state.get_state = AsyncMock(return_value="START") + return state + + @pytest.fixture + def mock_db(self): + """Создает мок базы данных""" + db = Mock(spec=BotDB) + db.user_exists = Mock(return_value=False) + db.add_new_user_in_db = Mock() + db.update_date_for_user = Mock() + db.update_username_and_full_name = Mock() + db.add_post_in_db = Mock() + db.update_info_about_stickers = Mock() + db.add_new_message_in_db = Mock() + return db + + @pytest.mark.asyncio + async def test_handle_start_message_new_user(self, mock_message, mock_state, mock_db): + """Тест обработки команды /start для нового пользователя""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.Path') as mock_path: + with patch('helper_bot.handlers.private.private_handlers.FSInputFile') as mock_fs: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков + mock_keyboard.return_value = Mock() + mock_messages.return_value = "Привет!" + mock_path.return_value.rglob.return_value = ["sticker1.tgs"] + mock_fs.return_value = "sticker_file" + + # Выполнение теста + await handle_start_message(mock_message, mock_state) + + # Проверки + mock_message.forward.assert_called_once() + mock_db.user_exists.assert_called_once_with(123456) + mock_db.add_new_user_in_db.assert_called_once() + mock_state.set_state.assert_called_with("START") + mock_message.answer_sticker.assert_called_once() + mock_message.answer.assert_called_once() + + @pytest.mark.asyncio + async def test_handle_start_message_existing_user(self, mock_message, mock_state, mock_db): + """Тест обработки команды /start для существующего пользователя""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.Path') as mock_path: + with patch('helper_bot.handlers.private.private_handlers.FSInputFile') as mock_fs: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + with patch('helper_bot.handlers.private.private_handlers.check_username_and_full_name') as mock_check: + # Настройка моков + mock_db.user_exists.return_value = True + mock_check.return_value = False + mock_keyboard.return_value = Mock() + mock_messages.return_value = "Привет!" + mock_path.return_value.rglob.return_value = ["sticker1.tgs"] + mock_fs.return_value = "sticker_file" + + # Выполнение теста + await handle_start_message(mock_message, mock_state) + + # Проверки + mock_db.user_exists.assert_called_once_with(123456) + mock_db.add_new_user_in_db.assert_not_called() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_restart_function(self, mock_message, mock_state): + """Тест функции перезапуска""" + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + mock_keyboard.return_value = Mock() + + await restart_function(mock_message, mock_state) + + mock_message.forward.assert_called_once() + mock_message.answer.assert_called_once_with( + text='Я перезапущен!', + reply_markup=mock_keyboard.return_value + ) + mock_state.set_state.assert_called_with('START') + + @pytest.mark.asyncio + async def test_suggest_post(self, mock_message, mock_state, mock_db): + """Тест функции предложения поста""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + mock_message.text = '📢Предложить свой пост' + mock_messages.side_effect = ["Введите текст поста", "Дополнительная информация"] + + await suggest_post(mock_message, mock_state) + + mock_message.forward.assert_called_once() + mock_state.set_state.assert_called_with("SUGGEST") + assert mock_message.answer.call_count == 2 + + @pytest.mark.asyncio + async def test_end_message(self, mock_message, mock_state): + """Тест функции прощания""" + with patch('helper_bot.handlers.private.private_handlers.Path') as mock_path: + with patch('helper_bot.handlers.private.private_handlers.FSInputFile') as mock_fs: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + mock_message.text = '👋🏼Сказать пока!' + mock_path.return_value.rglob.return_value = ["sticker1.tgs"] + mock_fs.return_value = "sticker_file" + mock_messages.return_value = "До свидания!" + + await end_message(mock_message, mock_state) + + mock_message.forward.assert_called_once() + mock_message.answer_sticker.assert_called_once() + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_suggest_router_text(self, mock_message, mock_state, mock_db): + """Тест обработки текстового поста""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_text_message') as mock_get_text: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.send_text_message') as mock_send: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков + mock_message.content_type = 'text' + mock_message.text = 'Тестовый пост' + mock_message.media_group_id = None + mock_get_text.return_value = 'Обработанный текст' + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_send.return_value = 123 + mock_messages.return_value = "Пост отправлен!" + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверки + mock_message.forward.assert_called_once() + mock_send.assert_called() + mock_db.add_post_in_db.assert_called_once() + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_stickers(self, mock_message, mock_state, mock_db): + """Тест функции стикеров""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + mock_message.text = '🤪Хочу стикеры' + mock_keyboard.return_value = Mock() + + await stickers(mock_message, mock_state) + + mock_message.forward.assert_called_once() + mock_db.update_info_about_stickers.assert_called_once_with(user_id=123456) + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_connect_with_admin(self, mock_message, mock_state, mock_db): + """Тест функции связи с админами""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + mock_message.text = '📩Связаться с админами' + mock_messages.return_value = "Свяжитесь с админами" + + await connect_with_admin(mock_message, mock_state) + + mock_db.update_date_for_user.assert_called_once() + mock_message.answer.assert_called_once() + mock_message.forward.assert_called_once() + mock_state.set_state.assert_called_with("PRE_CHAT") + + @pytest.mark.asyncio + async def test_resend_message_in_group_pre_chat(self, mock_message, mock_state, mock_db): + """Тест пересылки сообщения в группу (PRE_CHAT состояние)""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + mock_message.text = 'Тестовое сообщение' + mock_keyboard.return_value = Mock() + mock_messages.return_value = "Вопрос" + mock_state.get_state.return_value = "PRE_CHAT" + + await resend_message_in_group_for_message(mock_message, mock_state) + + mock_db.update_date_for_user.assert_called_once() + mock_message.forward.assert_called_once() + mock_db.add_new_message_in_db.assert_called_once() + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + +class TestDependencyFactory: + """Тесты для фабрики зависимостей""" + + def test_get_global_instance_singleton(self): + """Тест что get_global_instance возвращает синглтон""" + instance1 = get_global_instance() + instance2 = get_global_instance() + assert instance1 is instance2 + + def test_base_dependency_factory_initialization(self): + """Тест инициализации BaseDependencyFactory""" + # Этот тест пропускаем из-за сложности мокирования configparser в уже загруженном модуле + pass + + +class TestBotIntegration: + """Интеграционные тесты бота""" + + @pytest.mark.asyncio + async def test_bot_router_registration(self): + """Тест регистрации роутеров в диспетчере""" + with patch('helper_bot.main.Bot') as mock_bot_class: + with patch('helper_bot.main.Dispatcher') as mock_dp_class: + mock_bot = AsyncMock(spec=Bot) + mock_dp = AsyncMock(spec=Dispatcher) + mock_bot_class.return_value = mock_bot + mock_dp_class.return_value = mock_dp + + mock_factory = Mock(spec=BaseDependencyFactory) + mock_factory.settings = { + 'Telegram': { + 'bot_token': 'test_token', + 'preview_link': False + } + } + + await start_bot(mock_factory) + + # Проверяем, что все роутеры были зарегистрированы + mock_dp.include_routers.assert_called_once() + call_args = mock_dp.include_routers.call_args[0] + assert len(call_args) == 4 # private, callback, group, admin routers + + +if __name__ == '__main__': + pytest.main([__file__, '-v']) diff --git a/tests/test_db.py b/tests/test_db.py index 56489a8..8db993e 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -11,7 +11,7 @@ from database.db import BotDB def bot(): """Фикстура для создания объекта BotDB.""" current_dir = os.getcwd() - return BotDB(current_dir, "test.db") + return BotDB(current_dir, "database/test.db") @pytest.fixture(autouse=True, ) @@ -38,7 +38,7 @@ def setup_db(): # Other data date = "2024-07-10" next_date = "2024-07-11" - conn = sqlite3.connect("test.db") + conn = sqlite3.connect("database/test.db") cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS "admins" ( @@ -139,12 +139,12 @@ def setup_db(): conn.commit() conn.close() yield - os.remove('test.db') + os.remove('database/test.db') def test_bot_init(bot): """Проверяет, что объект BotDB инициализируется с правильным именем файла.""" - assert bot.db_file == os.path.join(os.getcwd(), "test.db") + assert bot.db_file == os.path.join(os.getcwd(), "database", "test.db") # Проверьте, что соединения с базой данных нет, так как оно не устанавливается в init assert bot.conn is None assert bot.cursor is None @@ -174,7 +174,7 @@ def test_create_table_success(bot): bot.create_table(sql_script) # Проверяем, что таблица создана - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='test_table'") result = cursor.fetchone() @@ -192,7 +192,7 @@ def test_create_table_error(bot): def test_get_current_version_success(bot): - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("INSERT INTO migrations (version, script_name) VALUES (123, 'test')") conn.commit() @@ -216,7 +216,7 @@ def test_update_version_success(bot): bot.update_version(new_version, script_name) # Проверяем, что данные записаны в таблицу - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("SELECT * FROM migrations WHERE version = ?", (new_version,)) result = cursor.fetchone() @@ -228,7 +228,7 @@ def test_update_version_success(bot): def test_update_version_integrity_error(bot): - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("INSERT INTO migrations (version, script_name) VALUES (123, 'test')") conn.commit() @@ -261,7 +261,7 @@ def test_add_new_user_in_db(bot): ) # Проверяем наличие записи в базе данных - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("SELECT * FROM our_users WHERE user_id = ?", (user_id,)) result = cursor.fetchone() @@ -306,7 +306,7 @@ def test_add_new_user_in_db_empty_first_name(bot): ) # Проверяем наличие записи в базе данных - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute(f"SELECT * FROM our_users WHERE user_id = ?", (user_id,)) result = cursor.fetchone() @@ -388,7 +388,7 @@ def test_get_username_error(bot): def test_get_all_user_id_empty(bot): """Проверяет, что функция возвращает пустой список, если в базе нет пользователей.""" - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("DELETE FROM our_users") conn.commit() @@ -481,7 +481,7 @@ def test_update_info_about_stickers_success(bot): bot.update_info_about_stickers(user_id) # Проверяем, что информация обновлена - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("SELECT has_stickers FROM our_users WHERE user_id = ?", (user_id,)) result = cursor.fetchone() @@ -495,7 +495,7 @@ def test_update_info_about_stickers_not_found(bot): bot.update_info_about_stickers(user_id) # Проверяем, что база данных не изменилась - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM our_users WHERE user_id = ?", (user_id,)) result = cursor.fetchone() @@ -512,7 +512,7 @@ def test_update_info_about_stickers_error(bot): def test_get_users_blacklist_empty(bot): """Проверяет, что функция возвращает пустой словарь, если в черном списке нет пользователей.""" - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("DELETE FROM blacklist") conn.commit() @@ -619,7 +619,7 @@ def test_set_user_blacklist_success(bot): assert bot.set_user_blacklist(user_id, user_name, message_for_user, date_to_unban) is None # Проверяем, что запись добавлена в базу - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("SELECT * FROM blacklist WHERE user_id = ?", (user_id,)) result = cursor.fetchone() @@ -658,7 +658,7 @@ def test_delete_user_blacklist_success(bot): @pytest.mark.xfail def test_delete_user_blacklist_not_found(bot): - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("INSERT INTO blacklist (user_id, user_name, date_to_unban) VALUES (?, ?, ?)", (12345, "JohnDoe", "2023-12-26")) @@ -691,7 +691,7 @@ def test_add_new_message_in_db_error(bot): def test_update_date_for_user_success(bot): bot.update_date_for_user('2024-07-15', 12345) - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("SELECT date_changed FROM our_users WHERE user_id = ?", (12345,)) new_date = cursor.fetchone()[0] @@ -741,7 +741,7 @@ def test_get_last_users_from_db_success(bot): def test_get_last_users_from_db_empty(bot): - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("DELETE FROM our_users") conn.commit() @@ -768,7 +768,7 @@ def test_get_banned_users_from_db_success(bot): def test_get_banned_users_from_db_empty(bot): - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("DELETE FROM blacklist") conn.commit() @@ -801,7 +801,7 @@ def test_get_banned_users_from_db_with_limits_success_offset(bot): def test_get_banned_users_from_db_with_limits_empty(bot): - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute("DELETE FROM blacklist") conn.commit() @@ -818,7 +818,7 @@ def test_get_banned_users_from_db_with_limits_error(bot): def __drop_table(table_name: str): - conn = sqlite3.connect('test.db') + conn = sqlite3.connect('database/test.db') cursor = conn.cursor() cursor.execute(f"DROP TABLE {table_name}") conn.commit() diff --git a/tests/test_error_handling.py b/tests/test_error_handling.py new file mode 100644 index 0000000..b934f45 --- /dev/null +++ b/tests/test_error_handling.py @@ -0,0 +1,339 @@ +# Импортируем моки в самом начале +import tests.mocks + +import pytest +from unittest.mock import Mock, AsyncMock, patch +from aiogram.types import Message, User, Chat + +from helper_bot.handlers.private.private_handlers import ( + handle_start_message, + suggest_router, + end_message, + stickers +) +from database.db import BotDB + + +class TestErrorHandling: + """Тесты для обработки ошибок и граничных случаев""" + + @pytest.fixture + def mock_message(self): + """Создает базовый мок сообщения""" + message = Mock(spec=Message) + message.from_user = Mock(spec=User) + message.from_user.id = 123456 + message.from_user.full_name = "Test User" + message.from_user.username = "testuser" + message.from_user.first_name = "Test" + message.from_user.is_bot = False + message.from_user.language_code = "ru" + message.chat = Mock(spec=Chat) + message.chat.id = 123456 + message.chat.type = "private" + message.message_id = 1 + message.forward = AsyncMock() + message.answer = AsyncMock() + message.answer_sticker = AsyncMock() + message.bot.send_message = AsyncMock() + return message + + @pytest.fixture + def mock_state(self): + """Создает мок состояния""" + state = Mock() + state.set_state = AsyncMock() + state.get_state = AsyncMock(return_value="START") + return state + + @pytest.fixture + def mock_db(self): + """Создает мок базы данных""" + db = Mock(spec=BotDB) + db.user_exists = Mock(return_value=False) + db.add_new_user_in_db = Mock() + db.update_date_for_user = Mock() + db.update_username_and_full_name = Mock() + db.add_post_in_db = Mock() + db.update_info_about_stickers = Mock() + return db + + @pytest.mark.asyncio + async def test_handle_start_message_user_without_username(self, mock_message, mock_state, mock_db): + """Тест обработки пользователя без username""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.Path') as mock_path: + with patch('helper_bot.handlers.private.private_handlers.FSInputFile') as mock_fs: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков + mock_message.from_user.username = None + mock_keyboard.return_value = Mock() + mock_messages.return_value = "Привет!" + mock_path.return_value.rglob.return_value = ["sticker1.tgs"] + mock_fs.return_value = "sticker_file" + + # Выполнение теста + await handle_start_message(mock_message, mock_state) + + # Проверки + mock_message.bot.send_message.assert_called() + # Проверяем, что отправлено сообщение о пользователе без username + call_args = mock_message.bot.send_message.call_args_list + username_log_call = next( + (call for call in call_args if 'без username' in call[1]['text']), + None + ) + assert username_log_call is not None + + @pytest.mark.asyncio + async def test_handle_start_message_sticker_error(self, mock_message, mock_state, mock_db): + """Тест обработки ошибки при получении стикера""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.Path') as mock_path: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков с ошибкой + mock_path.return_value.rglob.side_effect = Exception("Sticker error") + mock_keyboard.return_value = Mock() + mock_messages.return_value = "Привет!" + + # Выполнение теста + await handle_start_message(mock_message, mock_state) + + # Проверки + mock_message.bot.send_message.assert_called() + # Проверяем, что отправлено сообщение об ошибке + call_args = mock_message.bot.send_message.call_args_list + error_call = next( + (call for call in call_args if 'ошибка при получении стикеров' in call[1]['text']), + None + ) + assert error_call is not None + + @pytest.mark.asyncio + async def test_handle_start_message_message_error(self, mock_message, mock_state, mock_db): + """Тест обработки ошибки при отправке приветственного сообщения""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.Path') as mock_path: + with patch('helper_bot.handlers.private.private_handlers.FSInputFile') as mock_fs: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков + mock_keyboard.return_value = Mock() + mock_messages.side_effect = Exception("Message error") + mock_path.return_value.rglob.return_value = ["sticker1.tgs"] + mock_fs.return_value = "sticker_file" + + # Выполнение теста + await handle_start_message(mock_message, mock_state) + + # Проверки + mock_message.bot.send_message.assert_called() + # Проверяем, что отправлено сообщение об ошибке + call_args = mock_message.bot.send_message.call_args_list + # Проверяем, что было отправлено хотя бы одно сообщение + assert len(call_args) > 0 + # Проверяем, что в одном из сообщений есть текст об ошибке + error_found = False + for call in call_args: + text = call.kwargs.get('text', '') or (call[0][1] if len(call[0]) > 1 else '') + if 'Произошла ошибка' in text: + error_found = True + break + assert error_found + + @pytest.mark.asyncio + async def test_suggest_router_exception_handling(self, mock_message, mock_state): + """Тест обработки исключений в suggest_router""" + with patch('helper_bot.handlers.private.private_handlers.BotDB') as mock_db: + with patch('helper_bot.handlers.private.private_handlers.get_text_message') as mock_get_text: + # Настройка моков с ошибкой + mock_message.content_type = 'text' + mock_message.text = 'Тестовый пост' + mock_message.media_group_id = None + mock_get_text.side_effect = Exception("Processing error") + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверки + mock_message.bot.send_message.assert_called_once() + call_args = mock_message.bot.send_message.call_args + assert 'Произошла ошибка' in call_args[1]['text'] + + @pytest.mark.asyncio + async def test_end_message_sticker_error(self, mock_message, mock_state): + """Тест обработки ошибки при получении стикера в end_message""" + with patch('helper_bot.handlers.private.private_handlers.Path') as mock_path: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков с ошибкой + mock_message.text = '👋🏼Сказать пока!' + mock_path.return_value.rglob.side_effect = Exception("Sticker error") + mock_messages.return_value = "До свидания!" + + # Выполнение теста + await end_message(mock_message, mock_state) + + # Проверки + mock_message.bot.send_message.assert_called() + call_args = mock_message.bot.send_message.call_args_list + # Проверяем, что в одном из сообщений есть текст об ошибке + error_found = False + for call in call_args: + text = call.kwargs.get('text', '') or (call[0][1] if len(call[0]) > 1 else '') + if 'Произошла ошибка' in text: + error_found = True + break + assert error_found + + @pytest.mark.asyncio + async def test_end_message_message_error(self, mock_message, mock_state): + """Тест обработки ошибки при отправке сообщения в end_message""" + with patch('helper_bot.handlers.private.private_handlers.Path') as mock_path: + with patch('helper_bot.handlers.private.private_handlers.FSInputFile') as mock_fs: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков + mock_message.text = '👋🏼Сказать пока!' + mock_path.return_value.rglob.return_value = ["sticker1.tgs"] + mock_fs.return_value = "sticker_file" + mock_messages.side_effect = Exception("Message error") + + # Выполнение теста + await end_message(mock_message, mock_state) + + # Проверки + mock_message.bot.send_message.assert_called() + call_args = mock_message.bot.send_message.call_args_list + # Проверяем, что в одном из сообщений есть текст об ошибке + error_found = False + for call in call_args: + text = call.kwargs.get('text', '') or (call[0][1] if len(call[0]) > 1 else '') + if 'Произошла ошибка' in text: + error_found = True + break + assert error_found + + @pytest.mark.asyncio + async def test_stickers_exception_handling(self, mock_message, mock_state, mock_db): + """Тест обработки исключений в stickers""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + # Настройка моков с ошибкой + mock_message.text = '🤪Хочу стикеры' + mock_db.update_info_about_stickers.side_effect = Exception("Database error") + mock_keyboard.return_value = Mock() + + # Выполнение теста + await stickers(mock_message, mock_state) + + # Проверки + mock_message.bot.send_message.assert_called_once() + call_args = mock_message.bot.send_message.call_args + assert 'Произошла ошибка' in call_args[1]['text'] + + @pytest.mark.asyncio + async def test_suggest_router_empty_text(self, mock_message, mock_state, mock_db): + """Тест обработки пустого текста""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_text_message') as mock_get_text: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.send_text_message') as mock_send: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков + mock_message.content_type = 'text' + mock_message.text = '' + mock_message.media_group_id = None + mock_get_text.return_value = '' + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_send.return_value = 123 + mock_messages.return_value = "Пост отправлен!" + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверки - даже пустой текст должен обрабатываться + mock_message.forward.assert_called_once() + mock_send.assert_called() + mock_db.add_post_in_db.assert_called_once() + + @pytest.mark.asyncio + async def test_suggest_router_photo_without_caption(self, mock_message, mock_state, mock_db): + """Тест обработки фото без подписи""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.send_photo_message') as mock_send_photo: + with patch('helper_bot.handlers.private.private_handlers.add_in_db_media') as mock_add_media: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков для фото без подписи + mock_message.content_type = 'photo' + mock_message.caption = None + mock_message.media_group_id = None + mock_message.photo = [Mock()] + mock_message.photo[-1].file_id = 'photo_file_id' + + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_send_photo.return_value = Mock() + mock_send_photo.return_value.message_id = 123 + mock_send_photo.return_value.caption = '' + mock_messages.return_value = "Фото отправлено!" + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверки + mock_message.forward.assert_called_once() + mock_send_photo.assert_called_once() + # Проверяем, что send_photo_message вызван с пустой подписью + call_args = mock_send_photo.call_args + assert call_args.kwargs.get('caption', '') == '' + + @pytest.mark.asyncio + async def test_suggest_router_media_group_without_caption(self, mock_message, mock_state, mock_db): + """Тест обработки медиагруппы без подписи""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.prepare_media_group_from_middlewares') as mock_prepare: + with patch('helper_bot.handlers.private.private_handlers.send_media_group_message_to_private_chat') as mock_send_group: + with patch('helper_bot.handlers.private.private_handlers.send_text_message') as mock_send_text: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков для медиагруппы без подписи + mock_message.media_group_id = 'group_123' + mock_message.content_type = 'photo' + + # Создаем мок альбома без подписи + album = [mock_message] + album[0].caption = None + + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_prepare.return_value = ['media1', 'media2'] + mock_send_group.return_value = 123 + mock_send_text.return_value = 456 + mock_messages.return_value = "Медиагруппа отправлена!" + + # Выполнение теста + await suggest_router(mock_message, mock_state, album) + + # Проверки + mock_prepare.assert_called_once() + # Проверяем, что prepare_media_group_from_middlewares вызван с пустой подписью + call_args = mock_prepare.call_args + assert call_args.kwargs.get('post_caption', '') == '' + + +if __name__ == '__main__': + pytest.main([__file__, '-v']) diff --git a/tests/test_keyboards_and_filters.py b/tests/test_keyboards_and_filters.py new file mode 100644 index 0000000..5d673fd --- /dev/null +++ b/tests/test_keyboards_and_filters.py @@ -0,0 +1,330 @@ +import pytest +from unittest.mock import Mock, patch +from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton + +from helper_bot.keyboards.keyboards import ( + get_reply_keyboard, + get_reply_keyboard_for_post, + get_reply_keyboard_leave_chat +) +from helper_bot.filters.main import ChatTypeFilter +from database.db import BotDB + + +class TestKeyboards: + """Тесты для клавиатур""" + + @pytest.fixture + def mock_db(self): + """Создает мок базы данных""" + db = Mock(spec=BotDB) + db.get_user_info = Mock(return_value={ + 'stickers': True, + 'admin': False + }) + return db + + def test_get_reply_keyboard_basic(self, mock_db): + """Тест базовой клавиатуры""" + user_id = 123456 + + keyboard = get_reply_keyboard(mock_db, user_id) + + # Проверяем, что возвращается клавиатура + assert isinstance(keyboard, ReplyKeyboardMarkup) + assert keyboard.keyboard is not None + assert len(keyboard.keyboard) > 0 + + # Проверяем наличие основных кнопок + all_buttons = [] + for row in keyboard.keyboard: + for button in row: + all_buttons.append(button.text) + + # Проверяем наличие основных кнопок + assert '📢Предложить свой пост' in all_buttons + assert '👋🏼Сказать пока!' in all_buttons + assert '📩Связаться с админами' in all_buttons + + def test_get_reply_keyboard_with_stickers(self, mock_db): + """Тест клавиатуры со стикерами""" + user_id = 123456 + # Мокаем метод get_info_about_stickers + mock_db.get_info_about_stickers = Mock(return_value=False) + + keyboard = get_reply_keyboard(mock_db, user_id) + + all_buttons = [] + for row in keyboard.keyboard: + for button in row: + all_buttons.append(button.text) + + # Проверяем наличие кнопки стикеров + assert '🤪Хочу стикеры' in all_buttons + + def test_get_reply_keyboard_without_stickers(self, mock_db): + """Тест клавиатуры без стикеров""" + user_id = 123456 + # Мокаем метод get_info_about_stickers + mock_db.get_info_about_stickers = Mock(return_value=True) + + keyboard = get_reply_keyboard(mock_db, user_id) + + all_buttons = [] + for row in keyboard.keyboard: + for button in row: + all_buttons.append(button.text) + + # Проверяем отсутствие кнопки стикеров + assert '🤪Хочу стикеры' not in all_buttons + + def test_get_reply_keyboard_admin(self, mock_db): + """Тест клавиатуры для админа""" + user_id = 123456 + # Мокаем метод get_info_about_stickers + mock_db.get_info_about_stickers = Mock(return_value=False) + + keyboard = get_reply_keyboard(mock_db, user_id) + + all_buttons = [] + for row in keyboard.keyboard: + for button in row: + all_buttons.append(button.text) + + # Проверяем наличие основных кнопок + assert '📢Предложить свой пост' in all_buttons + assert '👋🏼Сказать пока!' in all_buttons + assert '📩Связаться с админами' in all_buttons + + def test_get_reply_keyboard_for_post(self): + """Тест клавиатуры для постов""" + keyboard = get_reply_keyboard_for_post() + + assert isinstance(keyboard, InlineKeyboardMarkup) + assert keyboard.inline_keyboard is not None + assert len(keyboard.inline_keyboard) > 0 + + all_buttons = [] + for row in keyboard.inline_keyboard: + for button in row: + all_buttons.append(button.text) + + # Проверяем наличие кнопок для постов + assert 'Опубликовать' in all_buttons + assert 'Отклонить' in all_buttons + + def test_get_reply_keyboard_leave_chat(self): + """Тест клавиатуры для выхода из чата""" + keyboard = get_reply_keyboard_leave_chat() + + assert isinstance(keyboard, ReplyKeyboardMarkup) + assert keyboard.keyboard is not None + assert len(keyboard.keyboard) > 0 + + all_buttons = [] + for row in keyboard.keyboard: + for button in row: + all_buttons.append(button.text) + + # Проверяем наличие кнопки выхода + assert 'Выйти из чата' in all_buttons + + def test_keyboard_resize(self): + """Тест настройки resize клавиатуры""" + keyboard = get_reply_keyboard_for_post() + + # Проверяем, что клавиатура настроена правильно + # InlineKeyboardMarkup не имеет resize_keyboard + assert isinstance(keyboard, InlineKeyboardMarkup) + + def test_keyboard_one_time(self): + """Тест настройки one_time клавиатуры""" + keyboard = get_reply_keyboard_leave_chat() + + # Проверяем, что клавиатура настроена правильно + assert hasattr(keyboard, 'one_time_keyboard') + assert keyboard.one_time_keyboard is True + + +class TestChatTypeFilter: + """Тесты для фильтра типа чата""" + + @pytest.fixture + def mock_message(self): + """Создает мок сообщения""" + message = Mock() + message.chat = Mock() + return message + + @pytest.mark.asyncio + async def test_chat_type_filter_private(self, mock_message): + """Тест фильтра для приватного чата""" + mock_message.chat.type = "private" + + filter_private = ChatTypeFilter(chat_type=["private"]) + filter_group = ChatTypeFilter(chat_type=["group"]) + filter_supergroup = ChatTypeFilter(chat_type=["supergroup"]) + + # Проверяем, что фильтр работает правильно + assert await filter_private(mock_message) is True + assert await filter_group(mock_message) is False + assert await filter_supergroup(mock_message) is False + + @pytest.mark.asyncio + async def test_chat_type_filter_group(self, mock_message): + """Тест фильтра для группового чата""" + mock_message.chat.type = "group" + + filter_private = ChatTypeFilter(chat_type=["private"]) + filter_group = ChatTypeFilter(chat_type=["group"]) + filter_supergroup = ChatTypeFilter(chat_type=["supergroup"]) + + # Проверяем, что фильтр работает правильно + assert await filter_private(mock_message) is False + assert await filter_group(mock_message) is True + assert await filter_supergroup(mock_message) is False + + @pytest.mark.asyncio + async def test_chat_type_filter_supergroup(self, mock_message): + """Тест фильтра для супергруппы""" + mock_message.chat.type = "supergroup" + + filter_private = ChatTypeFilter(chat_type=["private"]) + filter_group = ChatTypeFilter(chat_type=["group"]) + filter_supergroup = ChatTypeFilter(chat_type=["supergroup"]) + + # Проверяем, что фильтр работает правильно + assert await filter_private(mock_message) is False + assert await filter_group(mock_message) is False + assert await filter_supergroup(mock_message) is True + + @pytest.mark.asyncio + async def test_chat_type_filter_multiple_types(self, mock_message): + """Тест фильтра с несколькими типами чатов""" + filter_private_group = ChatTypeFilter(chat_type=["private", "group"]) + filter_all = ChatTypeFilter(chat_type=["private", "group", "supergroup"]) + + # Тест для приватного чата + mock_message.chat.type = "private" + assert await filter_private_group(mock_message) is True + assert await filter_all(mock_message) is True + + # Тест для группового чата + mock_message.chat.type = "group" + assert await filter_private_group(mock_message) is True + assert await filter_all(mock_message) is True + + # Тест для супергруппы + mock_message.chat.type = "supergroup" + assert await filter_private_group(mock_message) is False + assert await filter_all(mock_message) is True + + @pytest.mark.asyncio + async def test_chat_type_filter_channel(self, mock_message): + """Тест фильтра для канала""" + mock_message.chat.type = "channel" + + filter_channel = ChatTypeFilter(chat_type=["channel"]) + filter_private = ChatTypeFilter(chat_type=["private"]) + + assert await filter_channel(mock_message) is True + assert await filter_private(mock_message) is False + + @pytest.mark.asyncio + async def test_chat_type_filter_empty_list(self, mock_message): + """Тест фильтра с пустым списком типов""" + mock_message.chat.type = "private" + + filter_empty = ChatTypeFilter(chat_type=[]) + + # Фильтр с пустым списком должен возвращать False + assert await filter_empty(mock_message) is False + + @pytest.mark.asyncio + async def test_chat_type_filter_invalid_type(self, mock_message): + """Тест фильтра с несуществующим типом чата""" + mock_message.chat.type = "invalid_type" + + filter_private = ChatTypeFilter(chat_type=["private"]) + filter_invalid = ChatTypeFilter(chat_type=["invalid_type"]) + + assert await filter_private(mock_message) is False + assert await filter_invalid(mock_message) is True + + +class TestKeyboardIntegration: + """Интеграционные тесты клавиатур""" + + def test_keyboard_structure_consistency(self): + """Тест консистентности структуры клавиатур""" + # Мокаем базу данных + mock_db = Mock(spec=BotDB) + mock_db.get_info_about_stickers = Mock(return_value=False) + + # Тестируем все типы клавиатур + keyboards = [ + get_reply_keyboard(mock_db, 123456), + get_reply_keyboard_for_post(), + get_reply_keyboard_leave_chat() + ] + + # Проверяем первую клавиатуру (ReplyKeyboardMarkup) + keyboard1 = keyboards[0] + assert isinstance(keyboard1, ReplyKeyboardMarkup) + assert hasattr(keyboard1, 'keyboard') + assert isinstance(keyboard1.keyboard, list) + + # Проверяем вторую клавиатуру (InlineKeyboardMarkup) + keyboard2 = keyboards[1] + assert isinstance(keyboard2, InlineKeyboardMarkup) + assert hasattr(keyboard2, 'inline_keyboard') + assert isinstance(keyboard2.inline_keyboard, list) + + # Проверяем третью клавиатуру (ReplyKeyboardMarkup) + keyboard3 = keyboards[2] + assert isinstance(keyboard3, ReplyKeyboardMarkup) + assert hasattr(keyboard3, 'keyboard') + assert isinstance(keyboard3.keyboard, list) + + def test_keyboard_button_texts(self): + """Тест текстов кнопок клавиатур""" + # Тестируем основные кнопки + db = Mock(spec=BotDB) + db.get_info_about_stickers = Mock(return_value=False) + + main_keyboard = get_reply_keyboard(db, 123456) + post_keyboard = get_reply_keyboard_for_post() + leave_keyboard = get_reply_keyboard_leave_chat() + + # Собираем все тексты кнопок + main_buttons = [] + for row in main_keyboard.keyboard: + for button in row: + main_buttons.append(button.text) + + post_buttons = [] + for row in post_keyboard.inline_keyboard: + for button in row: + post_buttons.append(button.text) + + leave_buttons = [] + for row in leave_keyboard.keyboard: + for button in row: + leave_buttons.append(button.text) + + # Проверяем наличие основных кнопок + assert '📢Предложить свой пост' in main_buttons + assert '👋🏼Сказать пока!' in main_buttons + assert '📩Связаться с админами' in main_buttons + assert '🤪Хочу стикеры' in main_buttons + + # Проверяем кнопки для постов + assert 'Опубликовать' in post_buttons + assert 'Отклонить' in post_buttons + + # Проверяем кнопку выхода + assert 'Выйти из чата' in leave_buttons + + +if __name__ == '__main__': + pytest.main([__file__, '-v']) diff --git a/tests/test_media_handlers.py b/tests/test_media_handlers.py new file mode 100644 index 0000000..fff30d6 --- /dev/null +++ b/tests/test_media_handlers.py @@ -0,0 +1,292 @@ +# Импортируем моки в самом начале +import tests.mocks + +import pytest +from unittest.mock import Mock, AsyncMock, patch +from aiogram.types import Message, User, Chat, PhotoSize, Video, Audio, Voice, VideoNote + +from helper_bot.handlers.private.private_handlers import suggest_router +from database.db import BotDB + + +class TestMediaHandlers: + """Тесты для обработки медиа-контента""" + + @pytest.fixture + def mock_message(self): + """Создает базовый мок сообщения""" + message = Mock(spec=Message) + message.from_user = Mock(spec=User) + message.from_user.id = 123456 + message.from_user.full_name = "Test User" + message.from_user.username = "testuser" + message.from_user.first_name = "Test" + message.chat = Mock(spec=Chat) + message.chat.id = 123456 + message.chat.type = "private" + message.message_id = 1 + message.forward = AsyncMock() + message.answer = AsyncMock() + message.bot.send_message = AsyncMock() + return message + + @pytest.fixture + def mock_state(self): + """Создает мок состояния""" + state = Mock() + state.set_state = AsyncMock() + return state + + @pytest.fixture + def mock_db(self): + """Создает мок базы данных""" + db = Mock(spec=BotDB) + db.add_post_in_db = Mock() + return db + + @pytest.mark.asyncio + async def test_suggest_router_photo(self, mock_message, mock_state, mock_db): + """Тест обработки фото""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_text_message') as mock_get_text: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.send_photo_message') as mock_send_photo: + with patch('helper_bot.handlers.private.private_handlers.add_in_db_media') as mock_add_media: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков для фото + mock_message.content_type = 'photo' + mock_message.caption = 'Тестовое фото' + mock_message.media_group_id = None + mock_message.photo = [Mock(spec=PhotoSize)] + mock_message.photo[-1].file_id = 'photo_file_id' + + mock_get_text.return_value = 'Обработанная подпись' + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_send_photo.return_value = Mock() + mock_send_photo.return_value.message_id = 123 + mock_send_photo.return_value.caption = 'Обработанная подпись' + mock_messages.return_value = "Фото отправлено!" + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверки + mock_message.forward.assert_called_once() + mock_send_photo.assert_called_once() + mock_db.add_post_in_db.assert_called_once() + # Проверяем, что add_in_db_media был вызван (может быть вызван несколько раз) + assert mock_add_media.call_count >= 1 + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_suggest_router_video(self, mock_message, mock_state, mock_db): + """Тест обработки видео""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_text_message') as mock_get_text: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.send_video_message') as mock_send_video: + with patch('helper_bot.handlers.private.private_handlers.add_in_db_media') as mock_add_media: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков для видео + mock_message.content_type = 'video' + mock_message.caption = 'Тестовое видео' + mock_message.media_group_id = None + mock_message.video = Mock(spec=Video) + mock_message.video.file_id = 'video_file_id' + + mock_get_text.return_value = 'Обработанная подпись' + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_send_video.return_value = Mock() + mock_send_video.return_value.message_id = 123 + mock_send_video.return_value.caption = 'Обработанная подпись' + mock_messages.return_value = "Видео отправлено!" + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверки + mock_message.forward.assert_called_once() + mock_send_video.assert_called_once() + # Проверяем, что add_post_in_db был вызван (может быть вызван несколько раз) + assert mock_db.add_post_in_db.call_count >= 1 + # Проверяем, что add_in_db_media был вызван (может быть вызван несколько раз) + assert mock_add_media.call_count >= 1 + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_suggest_router_video_note(self, mock_message, mock_state, mock_db): + """Тест обработки видеокружка""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.send_video_note_message') as mock_send_video_note: + with patch('helper_bot.handlers.private.private_handlers.add_in_db_media') as mock_add_media: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков для видеокружка + mock_message.content_type = 'video_note' + mock_message.media_group_id = None + mock_message.video_note = Mock(spec=VideoNote) + mock_message.video_note.file_id = 'video_note_file_id' + + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_send_video_note.return_value = Mock() + mock_send_video_note.return_value.message_id = 123 + mock_messages.return_value = "Видеокружок отправлен!" + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверки + mock_message.forward.assert_called_once() + mock_send_video_note.assert_called_once() + # Проверяем, что add_post_in_db был вызван (может быть вызван несколько раз) + assert mock_db.add_post_in_db.call_count >= 1 + # Проверяем, что add_in_db_media был вызван (может быть вызван несколько раз) + assert mock_add_media.call_count >= 1 + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_suggest_router_audio(self, mock_message, mock_state, mock_db): + """Тест обработки аудио""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_text_message') as mock_get_text: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.send_audio_message') as mock_send_audio: + with patch('helper_bot.handlers.private.private_handlers.add_in_db_media') as mock_add_media: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков для аудио + mock_message.content_type = 'audio' + mock_message.caption = 'Тестовое аудио' + mock_message.media_group_id = None + mock_message.audio = Mock(spec=Audio) + mock_message.audio.file_id = 'audio_file_id' + + mock_get_text.return_value = 'Обработанная подпись' + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_send_audio.return_value = Mock() + mock_send_audio.return_value.message_id = 123 + mock_send_audio.return_value.caption = 'Обработанная подпись' + mock_messages.return_value = "Аудио отправлено!" + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверки + mock_message.forward.assert_called_once() + mock_send_audio.assert_called_once() + # Проверяем, что add_post_in_db был вызван (может быть вызван несколько раз) + assert mock_db.add_post_in_db.call_count >= 1 + # Проверяем, что add_in_db_media был вызван (может быть вызван несколько раз) + assert mock_add_media.call_count >= 1 + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_suggest_router_voice(self, mock_message, mock_state, mock_db): + """Тест обработки голосового сообщения""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.send_voice_message') as mock_send_voice: + with patch('helper_bot.handlers.private.private_handlers.add_in_db_media') as mock_add_media: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков для голосового сообщения + mock_message.content_type = 'voice' + mock_message.media_group_id = None + mock_message.voice = Mock(spec=Voice) + mock_message.voice.file_id = 'voice_file_id' + + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_send_voice.return_value = Mock() + mock_send_voice.return_value.message_id = 123 + mock_messages.return_value = "Голосовое сообщение отправлено!" + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверки + mock_message.forward.assert_called_once() + mock_send_voice.assert_called_once() + mock_db.add_post_in_db.assert_called_once() + # Проверяем, что add_in_db_media был вызван (может быть вызван несколько раз) + assert mock_add_media.call_count >= 1 + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_suggest_router_media_group(self, mock_message, mock_state, mock_db): + """Тест обработки медиагруппы""" + with patch('helper_bot.handlers.private.private_handlers.BotDB', mock_db): + with patch('helper_bot.handlers.private.private_handlers.get_text_message') as mock_get_text: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard_for_post') as mock_keyboard_post: + with patch('helper_bot.handlers.private.private_handlers.get_reply_keyboard') as mock_keyboard: + with patch('helper_bot.handlers.private.private_handlers.prepare_media_group_from_middlewares') as mock_prepare: + with patch('helper_bot.handlers.private.private_handlers.send_media_group_message_to_private_chat') as mock_send_group: + with patch('helper_bot.handlers.private.private_handlers.send_text_message') as mock_send_text: + with patch('helper_bot.handlers.private.private_handlers.messages.get_message') as mock_messages: + with patch('helper_bot.handlers.private.private_handlers.sleep'): + # Настройка моков для медиагруппы + mock_message.media_group_id = 'group_123' + mock_message.content_type = 'photo' + + # Создаем мок альбома + album = [mock_message] + album[0].caption = 'Подпись к медиагруппе' + + mock_get_text.return_value = 'Обработанная подпись' + mock_keyboard_post.return_value = Mock() + mock_keyboard.return_value = Mock() + mock_prepare.return_value = ['media1', 'media2'] + mock_send_group.return_value = 123 + mock_send_text.return_value = 456 + mock_messages.return_value = "Медиагруппа отправлена!" + + # Выполнение теста + await suggest_router(mock_message, mock_state, album) + + # Проверки + mock_get_text.assert_called_once() + mock_prepare.assert_called_once() + mock_send_group.assert_called_once() + # Проверяем, что send_text_message был вызван (может быть вызван несколько раз) + assert mock_send_text.call_count >= 1 + mock_db.update_helper_message_in_db.assert_called_once() + mock_message.answer.assert_called_once() + mock_state.set_state.assert_called_with("START") + + @pytest.mark.asyncio + async def test_suggest_router_unsupported_content(self, mock_message, mock_state): + """Тест обработки неподдерживаемого типа контента""" + # Настройка моков для неподдерживаемого контента + mock_message.content_type = 'document' + mock_message.media_group_id = None + + # Выполнение теста + await suggest_router(mock_message, mock_state) + + # Проверяем, что отправлено сообщение о неподдерживаемом типе + mock_message.bot.send_message.assert_called_once() + call_args = mock_message.bot.send_message.call_args + # Проверяем текст сообщения (может быть в позиционных или именованных аргументах) + text = call_args.kwargs.get('text', '') or (call_args[0][1] if len(call_args[0]) > 1 else '') + assert 'не умею работать с таким сообщением' in text + + +if __name__ == '__main__': + pytest.main([__file__, '-v']) diff --git a/tests/test_settings.ini b/tests/test_settings.ini new file mode 100644 index 0000000..e05cb42 --- /dev/null +++ b/tests/test_settings.ini @@ -0,0 +1,13 @@ +[Telegram] +bot_token = test_token_123 +preview_link = false +main_public = @test +group_for_posts = -1001234567890 +group_for_message = -1001234567891 +group_for_logs = -1001234567893 +important_logs = -1001234567894 +test_channel = -1001234567895 + +[Settings] +logs = true +test = false diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..8381363 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,208 @@ +import pytest +from unittest.mock import Mock, patch +from datetime import datetime + +from helper_bot.utils.helper_func import ( + get_first_name, + get_text_message, + check_username_and_full_name +) +from helper_bot.utils.messages import get_message +from helper_bot.utils.base_dependency_factory import BaseDependencyFactory, get_global_instance +from database.db import BotDB + + +class TestHelperFunctions: + """Тесты для вспомогательных функций""" + + @pytest.fixture + def mock_message(self): + """Создает мок сообщения для тестирования""" + message = Mock() + message.from_user = Mock() + message.from_user.first_name = "Test" + message.from_user.full_name = "Test User" + message.from_user.username = "testuser" + return message + + def test_get_first_name(self, mock_message): + """Тест функции получения имени пользователя""" + # Тест с обычным именем + result = get_first_name(mock_message) + assert result == "Test" + + # Тест с пустым именем - функция get_first_name не обрабатывает None + # поэтому этот тест будет падать, что ожидаемо + mock_message.from_user.first_name = None + try: + result = get_first_name(mock_message) + assert False, "Ожидалась ошибка при None first_name" + except AttributeError: + pass # Ожидаемое поведение + + def test_get_text_message(self, mock_message): + """Тест функции обработки текста сообщения""" + # Тест с обычным текстом + text = "Привет, это тестовое сообщение" + result = get_text_message(text, "Test", "testuser") + assert "Test" in result + assert "testuser" in result + assert "тестовое сообщение" in result + + # Тест с пустым текстом + result = get_text_message("", "Test", "testuser") + assert "Test" in result + assert "testuser" in result + + # Тест с текстом без специальных слов + text = "Обычный текст без специальных слов" + result = get_text_message(text, "Test", "testuser") + assert "Test" in result + assert "testuser" in result + assert "Обычный текст без специальных слов" in result + + def test_check_username_and_full_name(self): + """Тест функции проверки изменений username и full_name""" + # Создаем мок базы данных + mock_db = Mock(spec=BotDB) + mock_db.get_username_and_full_name = Mock(return_value=("olduser", "Old User")) + + # Тест с измененными данными + result = check_username_and_full_name(123456, "newuser", "New User", mock_db) + assert result is True + + # Тест с неизмененными данными + result = check_username_and_full_name(123456, "olduser", "Old User", mock_db) + assert result is False + + # Тест с частично измененными данными + result = check_username_and_full_name(123456, "olduser", "New User", mock_db) + assert result is True + + result = check_username_and_full_name(123456, "newuser", "Old User", mock_db) + assert result is True + + +class TestMessages: + """Тесты для системы сообщений""" + + def test_get_message(self): + """Тест функции получения сообщений""" + # Тест с существующим ключом + result = get_message("Test", "HELLO_MESSAGE") + assert isinstance(result, str) + assert len(result) > 0 + + # Тест с несуществующим ключом + try: + result = get_message("Test", "NON_EXISTENT_KEY") + assert False, "Ожидалась ошибка KeyError" + except KeyError: + pass # Ожидаемое поведение + + # Тест с пустым именем + result = get_message("", "HELLO_MESSAGE") + assert isinstance(result, str) + assert len(result) > 0 + + # Тест с None именем - ожидаем ошибку + try: + result = get_message(None, "HELLO_MESSAGE") + assert False, "Ожидалась ошибка TypeError" + except TypeError: + pass # Ожидаемое поведение + + def test_get_message_all_types(self): + """Тест всех типов сообщений""" + message_types = [ + "HELLO_MESSAGE", + "SUGGEST_NEWS", + "SUGGEST_NEWS_2", + "BYE_MESSAGE", + "SUCCESS_SEND_MESSAGE", + "CONNECT_WITH_ADMIN", + "QUESTION" + ] + + for msg_type in message_types: + result = get_message("Test", msg_type) + assert isinstance(result, str) + assert len(result) > 0 + + +class TestBaseDependencyFactory: + """Тесты для фабрики зависимостей""" + + def test_singleton_pattern(self): + """Тест паттерна синглтон""" + # Сбрасываем глобальный экземпляр + import helper_bot.utils.base_dependency_factory + helper_bot.utils.base_dependency_factory._global_instance = None + + # Получаем два экземпляра + instance1 = get_global_instance() + instance2 = get_global_instance() + + # Проверяем, что это один и тот же объект + assert instance1 is instance2 + assert id(instance1) == id(instance2) + + def test_factory_initialization_with_mock_config(self): + """Тест инициализации фабрики с мок конфигурацией""" + # Этот тест пропускаем, так как сложно замокать ConfigParser + # в контексте уже загруженных модулей + pass + + def test_get_settings_method(self): + """Тест метода get_settings""" + # Этот тест пропускаем, так как сложно замокать ConfigParser + # в контексте уже загруженных модулей + pass + + def test_get_db_method(self): + """Тест метода get_db""" + with patch('helper_bot.utils.base_dependency_factory.configparser.ConfigParser'): + with patch('helper_bot.utils.base_dependency_factory.BotDB') as mock_db: + factory = BaseDependencyFactory() + db = factory.get_db() + + assert db is not None + assert db == factory.database + + +class TestDatabaseIntegration: + """Тесты интеграции с базой данных""" + + def test_database_connection(self): + """Тест подключения к базе данных""" + with patch('helper_bot.utils.base_dependency_factory.configparser.ConfigParser'): + with patch('helper_bot.utils.base_dependency_factory.BotDB') as mock_db: + factory = BaseDependencyFactory() + + # Проверяем, что база данных была создана + mock_db.assert_called_once() + + # Проверяем, что get_db возвращает тот же экземпляр + db1 = factory.get_db() + db2 = factory.get_db() + assert db1 is db2 + + +class TestConfigurationHandling: + """Тесты обработки конфигурации""" + + def test_boolean_config_values(self): + """Тест обработки булевых значений в конфигурации""" + # Этот тест пропускаем, так как сложно замокать ConfigParser + # в контексте уже загруженных модулей + pass + + def test_string_config_values(self): + """Тест обработки строковых значений в конфигурации""" + # Этот тест пропускаем, так как сложно замокать ConfigParser + # в контексте уже загруженных модулей + pass + + +if __name__ == '__main__': + pytest.main([__file__, '-v']) diff --git a/voice_bot/utils/helper_func.py b/voice_bot/utils/helper_func.py index ca23b25..17d9d26 100644 --- a/voice_bot/utils/helper_func.py +++ b/voice_bot/utils/helper_func.py @@ -1,9 +1,9 @@ import time from datetime import datetime -from helper_bot.utils.base_dependency_factory import BaseDependencyFactory +from helper_bot.utils.base_dependency_factory import get_global_instance -bdf = BaseDependencyFactory() +bdf = get_global_instance() BotDB = bdf.get_db() diff --git a/voice_bot/voice_handler/voice_handler.py b/voice_bot/voice_handler/voice_handler.py index 75f9205..ef3bcf4 100644 --- a/voice_bot/voice_handler/voice_handler.py +++ b/voice_bot/voice_handler/voice_handler.py @@ -9,14 +9,14 @@ from aiogram.fsm.context import FSMContext from aiogram.types import FSInputFile from helper_bot.filters.main import ChatTypeFilter -from helper_bot.utils.base_dependency_factory import BaseDependencyFactory +from helper_bot.utils.base_dependency_factory import get_global_instance from logs.custom_logger import logger from voice_bot.keyboards.keyboards import get_main_keyboard from voice_bot.utils.helper_func import last_message voice_router = Router() -bdf = BaseDependencyFactory() +bdf = get_global_instance() GROUP_FOR_LOGS = bdf.settings['Telegram']['group_for_logs'] IMPORTANT_LOGS = bdf.settings['Telegram']['important_logs'] diff --git a/voice_bot_v2.py b/voice_bot_v2.py index d788edd..d94f598 100644 --- a/voice_bot_v2.py +++ b/voice_bot_v2.py @@ -1,9 +1,9 @@ import asyncio -from helper_bot.utils.base_dependency_factory import BaseDependencyFactory +from helper_bot.utils.base_dependency_factory import get_global_instance from voice_bot.main import start_bot -bdf = BaseDependencyFactory() +bdf = get_global_instance() if __name__ == '__main__': - asyncio.run(start_bot(BaseDependencyFactory())) + asyncio.run(start_bot(get_global_instance()))