diff --git a/restoran_max_bot/max_bot/max_api.py b/restoran_max_bot/max_bot/max_api.py index ce029e8..d868280 100644 --- a/restoran_max_bot/max_bot/max_api.py +++ b/restoran_max_bot/max_bot/max_api.py @@ -373,3 +373,26 @@ def maxbot_send_link_button(text, title_button, link, chat_id, max_token): response = requests.request("POST", url, headers=headers, data=payload, verify=max_verify) rt = {'success': True, 'error': '', 'data': response} + + +def maxbot_set_commands(max_token: str, commands: list) -> requests.Response: + """ + Устанавливает команды для бота через PATCH /me/commands. + Аргументы: + max_token: токен авторизации бота + commands: список словарей с полями name и description + Пример: + commands = [ + {"name": "start", "description": "Начать работу с ботом"}, + {"name": "menu", "description": "Показать меню"} + ] + Возвращает объект Response. + """ + url = f"{url_max}/me/commands" + headers = { + 'Authorization': f'{max_token}', + 'Content-Type': 'application/json' + } + payload = {"commands": commands} + response = requests.patch(url, headers=headers, json=payload, verify=max_verify) + return response diff --git a/restoran_max_bot/max_bot/tests.py b/restoran_max_bot/max_bot/tests.py index 7ce503c..fa0e21c 100644 --- a/restoran_max_bot/max_bot/tests.py +++ b/restoran_max_bot/max_bot/tests.py @@ -1,3 +1,55 @@ from django.test import TestCase +from unittest.mock import patch, MagicMock +from max_bot.max_api import maxbot_set_commands -# Create your tests here. + +class MaxApiCommandsTest(TestCase): + """Тесты для функции maxbot_set_commands.""" + + @patch('max_bot.max_api.requests.patch') + def test_maxbot_set_commands_success(self, mock_patch): + """Проверка успешного создания команд.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {'success': True} + mock_patch.return_value = mock_response + + max_token = 'test_token' + commands = [ + {'name': 'start', 'description': 'Начать'}, + {'name': 'menu', 'description': 'Меню'} + ] + response = maxbot_set_commands(max_token, commands) + + mock_patch.assert_called_once_with( + 'https://platform-api2.max.ru/me/commands', + headers={ + 'Authorization': 'test_token', + 'Content-Type': 'application/json' + }, + json={'commands': commands}, + verify=mock_patch.call_args[1].get('verify') + ) + self.assertEqual(response, mock_response) + + @patch('max_bot.max_api.requests.patch') + def test_maxbot_set_commands_empty_list(self, mock_patch): + """Проверка передачи пустого списка команд.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_patch.return_value = mock_response + + response = maxbot_set_commands('token', []) + mock_patch.assert_called_once() + self.assertEqual(response, mock_response) + + @patch('max_bot.max_api.requests.patch') + def test_maxbot_set_commands_error_response(self, mock_patch): + """Проверка обработки ошибки API.""" + mock_response = MagicMock() + mock_response.status_code = 400 + mock_response.json.return_value = {'error': 'Bad Request'} + mock_patch.return_value = mock_response + + response = maxbot_set_commands('token', [{'name': 'test', 'description': 'test'}]) + self.assertEqual(response.status_code, 400) diff --git a/restoran_max_bot/restoran_max_bot/promo_handlers.py b/restoran_max_bot/restoran_max_bot/promo_handlers.py new file mode 100644 index 0000000..96257f2 --- /dev/null +++ b/restoran_max_bot/restoran_max_bot/promo_handlers.py @@ -0,0 +1,54 @@ +# promo_handlers.py +import qrcode +from max_bot.models import Client, PromoCode +from restoran_max_bot.common import common_get_data +from restoran_max_bot.message_sender import send_text, send_image +from restoran_max_bot.settings import STATICFILES_DIRS, BASE_URL + + +def bot_promo(client: Client, message: dict, settings_max: dict) -> bool: + """Обработчик промокодов (QR-коды).""" + chat_id = message['chat_id'] + promo = message['payload'].lower() + messenger = 'max-bot' + client_id = client.id + + data = common_get_data(client=client, key=promo) + if not data: + return True + + promo_obj, created = PromoCode.objects.get_or_create( + promo=promo, + chat_id=chat_id, + client=client, + defaults={'messenger': messenger, 'used': False} + ) + + if not created: + if promo_obj.used: + send_text(chat_id, settings_max['token'], "⚠️ Этот промокод уже был использован.") + else: + send_text(chat_id, settings_max['token'], "ℹ️ QR-код уже был отправлен.") + return True + + # Генерируем QR + base_url = f"{BASE_URL}/promo/status/" + url_promo = f"{base_url}{promo}/{chat_id}/{client_id}" + img = qrcode.make(url_promo) + file_name = f"{promo}-{chat_id}-{client_id}.png" + img.save(f"{STATICFILES_DIRS[0]}/client_qr/{file_name}") + qr_url = f"{BASE_URL}/static/client_qr/{file_name}" + + send_image(chat_id, settings_max['token'], qr_url, 'Ваш QR-код.') + + # Отправляем дополнительный контент + for item in data: + if item.img: + img_url = f"https://cdn.telefon-ip.ru/{item.img}?thumb=600" + send_image(chat_id, settings_max['token'], img_url, item.descr) + if item.title and not item.img: + send_text(chat_id, settings_max['token'], item.descr) + if item.url: + send_text(chat_id, settings_max['token'], item.url) + + return True diff --git a/restoran_max_bot/restoran_max_bot/user_logger.py b/restoran_max_bot/restoran_max_bot/user_logger.py new file mode 100644 index 0000000..5a972b9 --- /dev/null +++ b/restoran_max_bot/restoran_max_bot/user_logger.py @@ -0,0 +1,55 @@ +# user_logger.py +import json +import os +from datetime import datetime +from threading import Lock +from max_bot.models import Client + +# Папка для логов активности пользователей +LOG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'log', 'user_activity') +os.makedirs(LOG_DIR, exist_ok=True) + +_lock = Lock() + +def log_user_action( + client: Client, + chat_id: str, + action_type: str, + details: dict = None, + user_query: str = None, + intent: str = None, + entities: dict = None, + response: str = None, + command: str = None +) -> None: + """ + Логирует действие пользователя в структурированном JSON-формате. + Запись добавляется в файл user_activity_YYYY-MM-DD.log. + """ + timestamp = datetime.now().isoformat() + log_entry = { + "timestamp": timestamp, + "client_id": client.id, + "chat_id": chat_id, + "action_type": action_type, + "details": details or {}, + } + if user_query is not None: + log_entry["user_query"] = user_query + if intent is not None: + log_entry["intent"] = intent + if entities is not None: + log_entry["entities"] = entities + if response is not None: + log_entry["response"] = response + if command is not None: + log_entry["command"] = command + + # Определяем имя файла по дате + date_str = datetime.now().strftime("%Y-%m-%d") + log_file = os.path.join(LOG_DIR, f"user_activity_{date_str}.log") + + # Потокобезопасная запись + with _lock: + with open(log_file, 'a', encoding='utf-8') as f: + f.write(json.dumps(log_entry, ensure_ascii=False) + '\n')