diff --git a/examples/network_resilience_example.py b/examples/network_resilience_example.py new file mode 100644 index 00000000..f9a09090 --- /dev/null +++ b/examples/network_resilience_example.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Example demonstrating the network resilience features of the VK Bot. + +This example shows how the enhanced bot handles network disconnections, +timeouts, and connection recovery automatically. +""" + +import sys +import os +import time + +# Add python module path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'python')) + +try: + from network_handler import NetworkHandler + from __main__ import Bot + from tokens import BOT_TOKEN + import config +except ImportError as e: + print(f"Import error: {e}") + print("This example requires the bot dependencies to be installed.") + sys.exit(1) + + +def demonstrate_network_resilience(): + """Demonstrate network resilience features.""" + + print("=== VK Bot Network Resilience Demo ===") + print() + + # Create bot instance with network resilience + print("1. Creating bot with network resilience...") + bot = Bot(token=BOT_TOKEN, group_id=config.BOT_GROUP_ID, debug=True) + print("✓ Bot created with automatic network error handling") + print() + + # Show network handler configuration + handler = bot.network_handler + print("2. Network Handler Configuration:") + print(f" - Max retries: {handler.max_retries}") + print(f" - Backoff factor: {handler.backoff_factor}") + print(f" - Connection timeout: {handler.connection_timeout}s") + print(f" - Read timeout: {handler.read_timeout}s") + print(f" - Health check interval: {handler.health_check_interval}s") + print() + + # Show connection statistics + print("3. Connection Statistics:") + stats = handler.get_connection_stats() + print(f" - Connected: {stats['is_connected']}") + print(f" - Connection failures: {stats['connection_failures']}") + print(f" - Last successful request: {stats['last_successful_request']}") + print(f" - Time since last success: {stats['time_since_last_success']:.1f}s") + print() + + # Test API call + print("4. Testing API call with network resilience...") + try: + # This call will automatically retry on network errors + result = bot.call_method('users.get', {'user_ids': 1}) + if 'response' in result: + user = result['response'][0] + print(f"✓ API call successful: User ID {user.get('id')} retrieved") + elif 'error' in result: + print(f"⚠ VK API error: {result['error']['error_msg']}") + else: + print(f"? Unexpected response: {result}") + except Exception as e: + print(f"✗ Network error (after all retries): {e}") + print() + + # Monitor connection for a short time + print("5. Monitoring connection health for 10 seconds...") + start_time = time.time() + while time.time() - start_time < 10: + time.sleep(2) + if handler.is_connected(): + print(" ✓ Connection healthy") + else: + print(" ⚠ Connection issues detected") + print() + + print("6. Features provided by network resilience:") + print(" ✓ Automatic retry on connection errors") + print(" ✓ Exponential backoff for retry delays") + print(" ✓ Background health check monitoring") + print(" ✓ Connection statistics tracking") + print(" ✓ Detailed error logging") + print(" ✓ Graceful handling of network changes") + print() + + print("Demo completed. The bot is now resilient to network issues!") + + +if __name__ == '__main__': + demonstrate_network_resilience() \ No newline at end of file diff --git a/experiments/network_test.py b/experiments/network_test.py new file mode 100644 index 00000000..bb20a377 --- /dev/null +++ b/experiments/network_test.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Network connectivity test script to simulate connection issues. + +This script helps understand how the current VK bot handles network disconnections +and connection failures. +""" +import sys +import os +import time +import signal +import threading +from unittest.mock import patch, MagicMock + +# Add python module path to import Bot +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'python')) + +try: + import requests + from saya import Vk + from python.__main__ import Bot + from python.tokens import BOT_TOKEN + import python.config as config +except ImportError as e: + print(f"Import error: {e}") + print("This is expected in the isolated test environment") + sys.exit(1) + + +class NetworkTestBot(Bot): + """Test bot with network failure simulation.""" + + def __init__(self, *args, **kwargs): + self.connection_lost_count = 0 + self.reconnection_attempts = 0 + self.max_reconnection_attempts = 5 + self.reconnection_delay = 2 # seconds + super().__init__(*args, **kwargs) + + def call_method(self, method, params=None): + """Override call_method to simulate network failures.""" + try: + return super().call_method(method, params) + except (requests.exceptions.ConnectionError, + requests.exceptions.Timeout, + requests.exceptions.HTTPError) as e: + print(f"Network error detected: {type(e).__name__}: {e}") + self.connection_lost_count += 1 + return self._handle_network_error(method, params, e) + + def _handle_network_error(self, method, params, error): + """Handle network errors with reconnection logic.""" + if self.reconnection_attempts >= self.max_reconnection_attempts: + print(f"Max reconnection attempts ({self.max_reconnection_attempts}) reached. Giving up.") + raise error + + self.reconnection_attempts += 1 + print(f"Attempting reconnection {self.reconnection_attempts}/{self.max_reconnection_attempts}") + + # Exponential backoff + delay = self.reconnection_delay * (2 ** (self.reconnection_attempts - 1)) + print(f"Waiting {delay} seconds before retry...") + time.sleep(delay) + + try: + result = super().call_method(method, params) + print(f"Reconnection successful on attempt {self.reconnection_attempts}") + self.reconnection_attempts = 0 # Reset on success + return result + except Exception as e: + print(f"Reconnection attempt {self.reconnection_attempts} failed: {e}") + return self._handle_network_error(method, params, e) + + +def simulate_network_failure(): + """Simulate network failures by patching requests.""" + + def failing_request(*args, **kwargs): + """Mock function that always raises connection error.""" + raise requests.exceptions.ConnectionError("Simulated network failure") + + # Patch requests to simulate network failure + with patch.object(requests.Session, 'post', side_effect=failing_request): + with patch.object(requests.Session, 'get', side_effect=failing_request): + print("Network failure simulation active") + + try: + # Create test bot instance + bot = NetworkTestBot(token=BOT_TOKEN, group_id=config.BOT_GROUP_ID, debug=True) + + # Test method call that should fail + print("Testing API call with simulated network failure...") + result = bot.call_method('users.get', {'user_ids': 1}) + print(f"Unexpected success: {result}") + + except Exception as e: + print(f"Final error after all retry attempts: {type(e).__name__}: {e}") + + +def test_current_bot_resilience(): + """Test how the current bot handles network issues.""" + print("Testing current bot network resilience...") + + # Mock network issues + def intermittent_failure(*args, **kwargs): + """Randomly fail some requests.""" + import random + if random.random() < 0.7: # 70% failure rate + raise requests.exceptions.ConnectionError("Intermittent network failure") + return MagicMock() + + with patch.object(requests.Session, 'post', side_effect=intermittent_failure): + try: + bot = Bot(token=BOT_TOKEN, group_id=config.BOT_GROUP_ID, debug=True) + + # Test multiple calls + for i in range(5): + try: + print(f"Attempt {i+1}: Making API call...") + result = bot.call_method('users.get', {'user_ids': 1}) + print(f"Success: {result}") + except Exception as e: + print(f"Failed: {type(e).__name__}: {e}") + time.sleep(1) + + except Exception as e: + print(f"Bot initialization failed: {e}") + + +if __name__ == '__main__': + print("=== VK Bot Network Resilience Test ===") + print("This script tests how the bot handles network disconnections and failures.") + print() + + print("1. Testing with simulated complete network failure:") + simulate_network_failure() + print() + + print("2. Testing with intermittent network failures:") + test_current_bot_resilience() + print() + + print("Test completed.") \ No newline at end of file diff --git a/experiments/test_network_resilience.py b/experiments/test_network_resilience.py new file mode 100644 index 00000000..81afb9ea --- /dev/null +++ b/experiments/test_network_resilience.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Test script to verify network resilience improvements. + +This script tests the network handler implementation to ensure it properly +handles connection failures, timeouts, and network changes. +""" + +import sys +import os +import time +import unittest +from unittest.mock import patch, MagicMock +import requests + +# Add python module path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'python')) + +try: + from network_handler import NetworkHandler, VkNetworkMixin +except ImportError as e: + print(f"Import error: {e}") + sys.exit(1) + + +class TestNetworkHandler(unittest.TestCase): + """Test cases for NetworkHandler.""" + + def setUp(self): + """Set up test cases.""" + self.handler = NetworkHandler(max_retries=3, backoff_factor=0.1) + self.session = self.handler.create_session() + + def test_session_creation(self): + """Test that session is created with proper configuration.""" + self.assertIsInstance(self.session, requests.Session) + self.assertEqual(self.session.timeout, (10, 30)) # Connection, read timeout + + def test_connection_stats(self): + """Test connection statistics tracking.""" + stats = self.handler.get_connection_stats() + self.assertIn('is_connected', stats) + self.assertIn('connection_failures', stats) + self.assertIn('last_successful_request', stats) + self.assertTrue(stats['is_connected']) + self.assertEqual(stats['connection_failures'], 0) + + @patch('requests.Session.request') + def test_successful_request(self, mock_request): + """Test successful request handling.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {'response': 'success'} + mock_request.return_value = mock_response + + response = self.handler.make_request(self.session, 'POST', 'https://api.vk.com/method/test') + + self.assertEqual(response, mock_response) + self.assertTrue(self.handler.is_connected()) + + @patch('requests.Session.request') + def test_connection_error_handling(self, mock_request): + """Test connection error handling and retry logic.""" + mock_request.side_effect = requests.exceptions.ConnectionError("Connection failed") + + with self.assertRaises(requests.exceptions.ConnectionError): + self.handler.make_request(self.session, 'POST', 'https://api.vk.com/method/test') + + self.assertFalse(self.handler.is_connected()) + self.assertGreater(self.handler._connection_failures, 0) + + @patch('requests.Session.request') + def test_timeout_error_handling(self, mock_request): + """Test timeout error handling.""" + mock_request.side_effect = requests.exceptions.Timeout("Request timed out") + + with self.assertRaises(requests.exceptions.Timeout): + self.handler.make_request(self.session, 'POST', 'https://api.vk.com/method/test') + + def test_health_check_start_stop(self): + """Test health check monitoring start/stop.""" + self.handler.start_health_check() + self.assertTrue(self.handler._health_check_thread.is_alive()) + + self.handler.stop_health_check() + time.sleep(0.1) # Give time for thread to stop + self.assertFalse(self.handler._health_check_thread.is_alive()) + + +class MockVk: + """Mock VK class for testing.""" + + def __init__(self, token, group_id, debug=False, api='5.131'): + self.token = token + self.group_id = group_id + self.debug = debug + self.api_version = api + + def start_listen(self): + """Mock start_listen method.""" + pass + + +class TestVkNetworkMixin(unittest.TestCase): + """Test cases for VkNetworkMixin.""" + + def setUp(self): + """Set up test case.""" + + # Create a test class that combines the mixin with mock VK + class TestBot(VkNetworkMixin, MockVk): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.bot = TestBot(token='test_token', group_id=12345) + + def test_mixin_initialization(self): + """Test that mixin initializes properly.""" + self.assertIsInstance(self.bot.network_handler, NetworkHandler) + self.assertIsNotNone(self.bot._vk_session) + + @patch('requests.Session.request') + def test_call_method_success(self, mock_request): + """Test successful API method call.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {'response': [{'id': 1, 'first_name': 'Test'}]} + mock_request.return_value = mock_response + + result = self.bot.call_method('users.get', {'user_ids': 1}) + + self.assertEqual(result, {'response': [{'id': 1, 'first_name': 'Test'}]}) + mock_request.assert_called_once() + + @patch('requests.Session.request') + def test_call_method_vk_api_error(self, mock_request): + """Test VK API error handling.""" + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + 'error': { + 'error_code': 5, + 'error_msg': 'User authorization failed' + } + } + mock_request.return_value = mock_response + + result = self.bot.call_method('users.get', {'user_ids': 1}) + + # Should return the error response, not raise exception + self.assertIn('error', result) + self.assertEqual(result['error']['error_code'], 5) + + @patch('requests.Session.request') + def test_call_method_network_error(self, mock_request): + """Test network error handling in call_method.""" + mock_request.side_effect = requests.exceptions.ConnectionError("Network error") + + with self.assertRaises(requests.exceptions.ConnectionError): + self.bot.call_method('users.get', {'user_ids': 1}) + + +def simulate_network_recovery(): + """Simulate network recovery scenario.""" + print("\n=== Simulating Network Recovery Scenario ===") + + handler = NetworkHandler(max_retries=2, backoff_factor=0.1) + session = handler.create_session() + + call_count = 0 + + def failing_then_success(*args, **kwargs): + nonlocal call_count + call_count += 1 + if call_count <= 2: # First two calls fail + raise requests.exceptions.ConnectionError(f"Network failure #{call_count}") + else: # Third call succeeds + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {'response': 'success'} + return mock_response + + with patch.object(requests.Session, 'request', side_effect=failing_then_success): + try: + print("Testing network recovery with 2 failures followed by success...") + response = handler.make_request(session, 'POST', 'https://api.vk.com/method/test') + print(f"✓ Recovery successful after {call_count} attempts") + print(f"✓ Connection status: {handler.is_connected()}") + print(f"✓ Failure count: {handler._connection_failures}") + except Exception as e: + print(f"✗ Recovery failed: {e}") + + +def simulate_intermittent_failures(): + """Simulate intermittent network failures.""" + print("\n=== Simulating Intermittent Failures ===") + + handler = NetworkHandler(max_retries=3, backoff_factor=0.1) + session = handler.create_session() + + success_count = 0 + failure_count = 0 + + def intermittent_failure(*args, **kwargs): + import random + if random.random() < 0.3: # 30% success rate + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {'response': 'success'} + return mock_response + else: + raise requests.exceptions.ConnectionError("Intermittent failure") + + with patch.object(requests.Session, 'request', side_effect=intermittent_failure): + for i in range(10): + try: + handler.make_request(session, 'POST', 'https://api.vk.com/method/test') + success_count += 1 + print(f"Request {i+1}: ✓ Success") + except Exception: + failure_count += 1 + print(f"Request {i+1}: ✗ Failed after retries") + + print(f"\nResults: {success_count} successes, {failure_count} final failures") + print(f"Connection status: {handler.is_connected()}") + + +if __name__ == '__main__': + print("=== VK Bot Network Resilience Test Suite ===") + + # Run unit tests + print("\n1. Running Unit Tests:") + unittest.main(verbosity=2, exit=False, argv=['']) + + # Run simulation tests + simulate_network_recovery() + simulate_intermittent_failures() + + print("\n=== Test Suite Completed ===") \ No newline at end of file diff --git a/python/__main__.py b/python/__main__.py index cdcbf7f6..b64ff57f 100644 --- a/python/__main__.py +++ b/python/__main__.py @@ -14,12 +14,13 @@ from userbot import UserBot import patterns import config +from network_handler import VkNetworkMixin CHAT_ID_OFFSET = 2e9 -class Bot(Vk): +class Bot(VkNetworkMixin, Vk): """Provides working with VK API as group. """ def __init__( diff --git a/python/network_handler.py b/python/network_handler.py new file mode 100644 index 00000000..9b2d84d5 --- /dev/null +++ b/python/network_handler.py @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +"""Network error handling and reconnection logic for VK Bot. + +This module provides robust network error handling, automatic reconnection, +and connection health monitoring for the VK bot to handle network changes +and connection losses gracefully. +""" + +import time +import logging +import threading +from typing import Any, Dict, Optional, Callable +from datetime import datetime, timedelta +import requests +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry + + +class NetworkHandler: + """Handles network connectivity issues and provides reconnection logic.""" + + def __init__( + self, + max_retries: int = 5, + backoff_factor: float = 0.5, + retry_status_codes: tuple = (429, 500, 502, 503, 504), + connection_timeout: int = 10, + read_timeout: int = 30, + health_check_interval: int = 60 + ): + """Initialize network handler. + + Args: + max_retries: Maximum number of retry attempts + backoff_factor: Backoff factor for exponential retry delay + retry_status_codes: HTTP status codes that should trigger retries + connection_timeout: Connection timeout in seconds + read_timeout: Read timeout in seconds + health_check_interval: Health check interval in seconds + """ + self.max_retries = max_retries + self.backoff_factor = backoff_factor + self.retry_status_codes = retry_status_codes + self.connection_timeout = connection_timeout + self.read_timeout = read_timeout + self.health_check_interval = health_check_interval + + self._connection_failures = 0 + self._last_successful_request = datetime.now() + self._is_connected = True + self._health_check_thread = None + self._stop_health_check = threading.Event() + + # Configure logging + self.logger = logging.getLogger('NetworkHandler') + if not self.logger.handlers: + handler = logging.StreamHandler() + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + handler.setFormatter(formatter) + self.logger.addHandler(handler) + self.logger.setLevel(logging.INFO) + + def create_session(self) -> requests.Session: + """Create a requests session with retry strategy.""" + session = requests.Session() + + # Configure retry strategy + retry_strategy = Retry( + total=self.max_retries, + backoff_factor=self.backoff_factor, + status_forcelist=self.retry_status_codes, + method_whitelist=["GET", "POST"] + ) + + # Mount adapter with retry strategy + adapter = HTTPAdapter(max_retries=retry_strategy) + session.mount("http://", adapter) + session.mount("https://", adapter) + + # Set timeouts + session.timeout = (self.connection_timeout, self.read_timeout) + + return session + + def make_request( + self, + session: requests.Session, + method: str, + url: str, + **kwargs + ) -> requests.Response: + """Make HTTP request with error handling and logging. + + Args: + session: Requests session + method: HTTP method (GET, POST, etc.) + url: Request URL + **kwargs: Additional request parameters + + Returns: + Response object + + Raises: + requests.RequestException: If request fails after all retries + """ + start_time = time.time() + + try: + response = session.request(method, url, **kwargs) + + # Log successful request + duration = time.time() - start_time + self.logger.debug(f"Request to {url} completed in {duration:.2f}s") + + # Update connection status + self._last_successful_request = datetime.now() + if not self._is_connected: + self.logger.info("Connection restored!") + self._is_connected = True + self._connection_failures = 0 + + return response + + except requests.exceptions.ConnectionError as e: + self._handle_connection_error(e, url) + raise + except requests.exceptions.Timeout as e: + self._handle_timeout_error(e, url) + raise + except requests.exceptions.HTTPError as e: + self._handle_http_error(e, url) + raise + except Exception as e: + self.logger.error(f"Unexpected error during request to {url}: {e}") + raise + + def _handle_connection_error(self, error: Exception, url: str): + """Handle connection errors.""" + self._connection_failures += 1 + self._is_connected = False + + self.logger.warning( + f"Connection error #{self._connection_failures} to {url}: {error}" + ) + + if self._connection_failures >= self.max_retries: + self.logger.error( + f"Max connection failures reached ({self.max_retries}). " + f"Network may be down." + ) + + def _handle_timeout_error(self, error: Exception, url: str): + """Handle timeout errors.""" + self.logger.warning(f"Timeout error for {url}: {error}") + + def _handle_http_error(self, error: Exception, url: str): + """Handle HTTP errors.""" + self.logger.warning(f"HTTP error for {url}: {error}") + + def start_health_check(self, health_check_url: str = "https://api.vk.com"): + """Start background health check monitoring. + + Args: + health_check_url: URL to use for health checks + """ + if self._health_check_thread and self._health_check_thread.is_alive(): + return + + self._stop_health_check.clear() + self._health_check_thread = threading.Thread( + target=self._health_check_worker, + args=(health_check_url,), + daemon=True + ) + self._health_check_thread.start() + self.logger.info(f"Started health check monitoring (interval: {self.health_check_interval}s)") + + def stop_health_check(self): + """Stop background health check monitoring.""" + if self._health_check_thread: + self._stop_health_check.set() + self._health_check_thread.join(timeout=5) + self.logger.info("Stopped health check monitoring") + + def _health_check_worker(self, health_check_url: str): + """Background worker for health check monitoring.""" + session = self.create_session() + + while not self._stop_health_check.wait(self.health_check_interval): + try: + response = session.get(health_check_url, timeout=5) + if response.status_code == 200: + if not self._is_connected: + self.logger.info("Health check: Connection restored") + self._is_connected = True + self._connection_failures = 0 + else: + self.logger.warning(f"Health check failed with status: {response.status_code}") + + except Exception as e: + if self._is_connected: + self.logger.warning(f"Health check failed: {e}") + self._is_connected = False + + def is_connected(self) -> bool: + """Check if connection is healthy.""" + return self._is_connected + + def get_connection_stats(self) -> Dict[str, Any]: + """Get connection statistics.""" + return { + 'is_connected': self._is_connected, + 'connection_failures': self._connection_failures, + 'last_successful_request': self._last_successful_request.isoformat(), + 'time_since_last_success': (datetime.now() - self._last_successful_request).total_seconds() + } + + +class VkNetworkMixin: + """Mixin to add network resilience to VK Bot class.""" + + def __init__(self, *args, **kwargs): + # Initialize network handler + self.network_handler = NetworkHandler() + self._vk_session = self.network_handler.create_session() + + super().__init__(*args, **kwargs) + + # Start health monitoring + self.network_handler.start_health_check() + + def call_method(self, method: str, params: Optional[Dict[str, Any]] = None): + """Override call_method to use network handler.""" + if params is None: + params = {} + + # Add token and version to params + params.update({ + 'access_token': self.token, + 'v': self.api_version if hasattr(self, 'api_version') else '5.131' + }) + + url = f"https://api.vk.com/method/{method}" + + try: + response = self.network_handler.make_request( + self._vk_session, 'POST', url, data=params + ) + response.raise_for_status() + + result = response.json() + + # Handle VK API errors + if 'error' in result: + error_code = result['error'].get('error_code', 0) + error_msg = result['error'].get('error_msg', 'Unknown error') + + # Log VK API errors + self.network_handler.logger.warning( + f"VK API error {error_code}: {error_msg} for method {method}" + ) + + # Handle specific error codes that might indicate network issues + if error_code in [1, 6, 10]: # Various server errors + time.sleep(1) # Brief delay before allowing retry + + return result + + except requests.exceptions.RequestException as e: + self.network_handler.logger.error( + f"Network error calling VK API method {method}: {e}" + ) + raise + + def __del__(self): + """Cleanup network handler on destruction.""" + if hasattr(self, 'network_handler'): + self.network_handler.stop_health_check() \ No newline at end of file diff --git a/python/userbot.py b/python/userbot.py index 74f7bd5d..1e53b876 100644 --- a/python/userbot.py +++ b/python/userbot.py @@ -2,21 +2,27 @@ """Provides working with VK API as user. """ from typing import NoReturn, List, Dict, Any +import logging from exceptions import TooManyMessagesError from tokens import USER_TOKEN from requests import Session +from network_handler import NetworkHandler class UserBot: """Automatically deleting unnecessary messages. """ - session = Session() - url = 'https://api.vk.com/method/' - token = USER_TOKEN + def __init__(self): + """Initialize UserBot with network handling.""" + self.network_handler = NetworkHandler() + self.session = self.network_handler.create_session() + self.url = 'https://api.vk.com/method/' + self.token = USER_TOKEN + self.logger = logging.getLogger('UserBot') - @staticmethod def delete_messages( + self, conversation_message_ids: List[int], peer_id: int ) -> NoReturn: @@ -37,16 +43,27 @@ def delete_messages( } return 1;''' data = { - 'access_token': UserBot.token, + 'access_token': self.token, 'code': code % params, 'v': '5.103' } - return UserBot.execute(data) + try: + return self.execute(data) + except Exception as e: + self.logger.error(f"Failed to delete messages: {e}") + raise raise TooManyMessagesError( 'Maximum amount was reached (%d/24)' % len(conversation_message_ids)) - @staticmethod - def execute(data: str) -> Dict[str, Any]: + def execute(self, data: str) -> Dict[str, Any]: """Executes VK Script. """ - return UserBot.session.post(UserBot.url + 'execute', data=data).json() + try: + response = self.network_handler.make_request( + self.session, 'POST', self.url + 'execute', data=data + ) + response.raise_for_status() + return response.json() + except Exception as e: + self.logger.error(f"Failed to execute VK script: {e}") + raise