From cec30b3d5f8a416eaa06945c18baf492e0073c5f Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 13 Sep 2025 18:57:40 +0300 Subject: [PATCH 1/3] Initial commit with task details for issue #70 Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/linksplatform/Bot/issues/70 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..29896a0c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/linksplatform/Bot/issues/70 +Your prepared branch: issue-70-152d0295 +Your prepared working directory: /tmp/gh-issue-solver-1757779057722 + +Proceed. \ No newline at end of file From f3b712b015e50d43b12b4c1966edf36046224ff7 Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 13 Sep 2025 19:05:25 +0300 Subject: [PATCH 2/3] Implement robust network reconnection handling for VK Bot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add NetworkHandler class with automatic retry logic and exponential backoff - Implement VkNetworkMixin to enhance Bot class with network resilience - Add connection health monitoring with background health checks - Update Bot and UserBot classes to use network error handling - Include comprehensive error logging and connection statistics - Add test scripts and examples demonstrating network resilience features This fixes the issue where the bot loses connection with VK during network changes or temporary disconnections. The bot now automatically detects network issues and reconnects gracefully. Fixes #70 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- examples/network_resilience_example.py | 98 +++++++++ experiments/network_test.py | 143 +++++++++++++ experiments/test_network_resilience.py | 239 +++++++++++++++++++++ python/__main__.py | 3 +- python/network_handler.py | 280 +++++++++++++++++++++++++ python/userbot.py | 35 +++- 6 files changed, 788 insertions(+), 10 deletions(-) create mode 100644 examples/network_resilience_example.py create mode 100644 experiments/network_test.py create mode 100644 experiments/test_network_resilience.py create mode 100644 python/network_handler.py 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 From 6966c272189f71f1ee8c2f7cdda52528874b2a4e Mon Sep 17 00:00:00 2001 From: konard Date: Sat, 13 Sep 2025 19:06:35 +0300 Subject: [PATCH 3/3] Remove CLAUDE.md - Claude command completed --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 29896a0c..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/linksplatform/Bot/issues/70 -Your prepared branch: issue-70-152d0295 -Your prepared working directory: /tmp/gh-issue-solver-1757779057722 - -Proceed. \ No newline at end of file