From 57699d5aa03a63d1f5ca82cf67735a828f9bc849 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 02:33:10 +0300 Subject: [PATCH 1/4] Initial commit with task details for issue #57 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/57 --- 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..84e4ca80 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/linksplatform/Bot/issues/57 +Your prepared branch: issue-57-af4f56b0 +Your prepared working directory: /tmp/gh-issue-solver-1757806387646 + +Proceed. \ No newline at end of file From 9d3754fef7ac9ecc8caaa51392a15b96a2df091e Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 02:37:36 +0300 Subject: [PATCH 2/4] Implement message logging from main chat to logging chat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add configuration options for main chats and logging chat in config.py - Implement message forwarding functionality in Bot._forward_message_to_logging_chat() - Add chat title resolution for better log formatting - Include comprehensive message metadata (timestamp, user, attachments, etc.) - Add safety checks to prevent infinite loops and handle errors gracefully - Add documentation for configuration and usage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- python/MESSAGE_LOGGING.md | 85 +++++++++++++++++++++++++++++++++++++ python/__main__.py | 88 +++++++++++++++++++++++++++++++++++++++ python/config.py | 10 +++++ 3 files changed, 183 insertions(+) create mode 100644 python/MESSAGE_LOGGING.md diff --git a/python/MESSAGE_LOGGING.md b/python/MESSAGE_LOGGING.md new file mode 100644 index 00000000..047c6ab6 --- /dev/null +++ b/python/MESSAGE_LOGGING.md @@ -0,0 +1,85 @@ +# Message Logging Feature + +This feature allows the bot to forward all messages from specified main chats to a dedicated logging chat for monitoring and archival purposes. + +## Configuration + +To enable message logging, edit `config.py` and configure the following variables: + +### MAIN_CHATS +List of chat IDs from which messages should be forwarded to the logging chat. + +```python +MAIN_CHATS = [ + 2000000001, # Example main chat ID + 2000000011 # Another main chat ID +] +``` + +### LOGGING_CHAT_ID +The chat ID where all messages from main chats will be forwarded for logging. + +```python +LOGGING_CHAT_ID = 2000000020 # Example logging chat ID +``` + +## How to Find Chat IDs + +1. Go to the VK conversation you want to use +2. Look at the URL in your browser address bar +3. For group chats, the URL will look like: `https://vk.com/im?peers=c477` +4. The chat ID is `2000000000 + 477 = 2000000477` + +## Message Format + +Messages forwarded to the logging chat include: + +- **Timestamp**: When the original message was sent +- **Chat title**: Name of the source chat +- **User name**: Who sent the message +- **Message content**: The actual message text +- **Attachments**: List of attachment types (if any) +- **Special indicators**: For forwarded messages and replies + +Example logged message: +``` +[2024-01-01 12:30:45] Development Chat +John Doe: Hello, how is everyone doing? +Attachments: [photo], [doc] +[Reply to message] +``` + +## Features + +### What Gets Logged +- All text messages from configured main chats +- Information about attachments (type only, not content) +- Indication of forwarded messages +- Indication of replies to other messages +- User names and chat titles for context + +### What Doesn't Get Logged +- Messages from non-main chats +- Bot messages (to avoid infinite loops) +- Messages when logging is disabled or misconfigured +- Actual attachment content (for privacy and performance) + +### Error Handling +- If logging fails for any reason, an error is printed to console +- The bot continues to function normally even if logging fails +- Invalid configurations are silently ignored + +## Security Considerations + +- The logging chat should have restricted access +- Only trusted administrators should have access to logged messages +- Consider the privacy implications of logging all messages +- Ensure the logging chat is properly secured against unauthorized access + +## Disabling Logging + +To disable logging, set either: +- `LOGGING_CHAT_ID = None` +- `MAIN_CHATS = []` + +The bot will automatically skip logging when these are not properly configured. \ No newline at end of file diff --git a/python/__main__.py b/python/__main__.py index cdcbf7f6..ce6ea7f3 100644 --- a/python/__main__.py +++ b/python/__main__.py @@ -78,6 +78,9 @@ def message_new( from_id = event["from_id"] msg_id = event["conversation_message_id"] + # Forward messages from main chats to logging chat + self._forward_message_to_logging_chat(event, peer_id, from_id) + if peer_id in self.messages_to_delete: peer = CHAT_ID_OFFSET + config.USERBOT_CHATS[peer_id] new_messages_to_delete = [] @@ -189,6 +192,91 @@ def get_user_name( 'users.get', dict(user_ids=uid, name_case=name_case) )['response'][0]["first_name"] + def _forward_message_to_logging_chat( + self, + event: Dict[str, Any], + peer_id: int, + from_id: int + ) -> NoReturn: + """Forwards messages from main chats to logging chat. + + :param event: message event data + :param peer_id: chat ID where the message was sent + :param from_id: user ID who sent the message + """ + # Check if logging is enabled and configured + if not config.LOGGING_CHAT_ID or not config.MAIN_CHATS: + return + + # Only forward messages from specified main chats + if peer_id not in config.MAIN_CHATS: + return + + # Don't forward our own messages to avoid loops + if from_id < 0: # Negative from_id means it's from a group/bot + return + + try: + # Get user name for better logging format + user_name = self.get_user_name(from_id) if from_id > 0 else "Unknown" + + # Format the original message with metadata + original_text = event.get("text", "") + chat_title = self._get_chat_title(peer_id) + timestamp = datetime.fromtimestamp(event.get("date", 0)).strftime("%Y-%m-%d %H:%M:%S") + + # Create formatted log message + log_message = f"[{timestamp}] {chat_title}\n{user_name}: {original_text}" + + # Handle attachments + attachments = event.get("attachments", []) + if attachments: + attachment_info = [] + for attachment in attachments: + att_type = attachment.get("type", "unknown") + attachment_info.append(f"[{att_type}]") + if attachment_info: + log_message += f"\nAttachments: {', '.join(attachment_info)}" + + # Handle forwarded messages + fwd_messages = event.get("fwd_messages", []) + if fwd_messages: + log_message += f"\n[Forwarded {len(fwd_messages)} message(s)]" + + # Handle reply to message + reply_message = event.get("reply_message", {}) + if reply_message: + log_message += "\n[Reply to message]" + + # Send to logging chat + self.send_msg(log_message, config.LOGGING_CHAT_ID) + + except Exception as e: + print(f"Error forwarding message to logging chat: {e}") + + def _get_chat_title(self, peer_id: int) -> str: + """Get chat title for better logging format. + + :param peer_id: chat ID + :return: chat title or formatted chat ID + """ + try: + # For group chats (peer_id > 2000000000), try to get conversation info + if peer_id > 2000000000: + response = self.call_method( + 'messages.getConversationsById', + {'peer_ids': peer_id} + ) + if 'response' in response and 'items' in response['response']: + items = response['response']['items'] + if items: + chat_settings = items[0].get('chat_settings', {}) + title = chat_settings.get('title', f'Chat {peer_id}') + return title + return f'Chat {peer_id}' + except Exception: + return f'Chat {peer_id}' + @staticmethod def get_messages( event: Dict[str, Any] diff --git a/python/config.py b/python/config.py index 1613aec9..65bb2c9b 100644 --- a/python/config.py +++ b/python/config.py @@ -27,6 +27,16 @@ 2000000011 ] +# Message logging configuration +# Main chats that should have their messages forwarded to logging chat +MAIN_CHATS = [ + # 2000000001, # Add your main chat IDs here + # 2000000011 +] + +# Chat ID where all messages from main chats will be forwarded for logging +LOGGING_CHAT_ID = None # Set this to your logging chat ID (e.g., 2000000020) + POSITIVE_VOTES_PER_KARMA = 2 NEGATIVE_VOTES_PER_KARMA = 3 From 5970bb7a21835910a6ec4fd74dc46e81460aac3c Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 02:38:26 +0300 Subject: [PATCH 3/4] 'Auto-commit changes made by Claude MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude ' --- python/test_message_logging.py | 222 +++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 python/test_message_logging.py diff --git a/python/test_message_logging.py b/python/test_message_logging.py new file mode 100644 index 00000000..e475cebd --- /dev/null +++ b/python/test_message_logging.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Test script for message logging functionality.""" + +import unittest +from unittest.mock import Mock, patch, call +from datetime import datetime +import sys +import os + +# Add the current directory to the path to import modules +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +# Import Bot class by importing the main module +import importlib.util +import sys + +# Load the main module +spec = importlib.util.spec_from_file_location("main", "__main__.py") +main_module = importlib.util.module_from_spec(spec) +sys.modules["main"] = main_module +spec.loader.exec_module(main_module) + +Bot = main_module.Bot +import config + + +class TestMessageLogging(unittest.TestCase): + """Test cases for message logging functionality.""" + + def setUp(self): + """Set up test fixtures.""" + # Mock the BOT_TOKEN to avoid requiring real token for tests + with patch('__main__.BOT_TOKEN', 'test_token'): + self.bot = Bot(token='test_token', group_id=12345, debug=False) + + # Mock the VK API methods + self.bot.call_method = Mock() + self.bot.send_msg = Mock() + self.bot.get_user_name = Mock(return_value="Test User") + + def test_logging_disabled_when_no_config(self): + """Test that logging is disabled when configuration is missing.""" + # Backup original config + original_logging_chat = config.LOGGING_CHAT_ID + original_main_chats = config.MAIN_CHATS + + try: + # Test with no logging chat configured + config.LOGGING_CHAT_ID = None + config.MAIN_CHATS = [2000000001] + + event = { + "text": "Test message", + "date": 1640995200, + "attachments": [], + "fwd_messages": [], + "reply_message": {} + } + + self.bot._forward_message_to_logging_chat(event, 2000000001, 12345) + + # Should not call send_msg + self.bot.send_msg.assert_not_called() + + # Test with no main chats configured + config.LOGGING_CHAT_ID = 2000000020 + config.MAIN_CHATS = [] + + self.bot._forward_message_to_logging_chat(event, 2000000001, 12345) + + # Should still not call send_msg + self.bot.send_msg.assert_not_called() + + finally: + # Restore original config + config.LOGGING_CHAT_ID = original_logging_chat + config.MAIN_CHATS = original_main_chats + + def test_message_not_forwarded_from_non_main_chat(self): + """Test that messages from non-main chats are not forwarded.""" + # Backup original config + original_logging_chat = config.LOGGING_CHAT_ID + original_main_chats = config.MAIN_CHATS + + try: + config.LOGGING_CHAT_ID = 2000000020 + config.MAIN_CHATS = [2000000001] # Only this chat should be forwarded + + event = { + "text": "Test message", + "date": 1640995200, + "attachments": [], + "fwd_messages": [], + "reply_message": {} + } + + # Send from a chat not in MAIN_CHATS + self.bot._forward_message_to_logging_chat(event, 2000000002, 12345) + + # Should not call send_msg + self.bot.send_msg.assert_not_called() + + finally: + # Restore original config + config.LOGGING_CHAT_ID = original_logging_chat + config.MAIN_CHATS = original_main_chats + + def test_bot_messages_not_forwarded(self): + """Test that bot messages (negative from_id) are not forwarded.""" + # Backup original config + original_logging_chat = config.LOGGING_CHAT_ID + original_main_chats = config.MAIN_CHATS + + try: + config.LOGGING_CHAT_ID = 2000000020 + config.MAIN_CHATS = [2000000001] + + event = { + "text": "Bot message", + "date": 1640995200, + "attachments": [], + "fwd_messages": [], + "reply_message": {} + } + + # Send from bot (negative from_id) + self.bot._forward_message_to_logging_chat(event, 2000000001, -12345) + + # Should not call send_msg + self.bot.send_msg.assert_not_called() + + finally: + # Restore original config + config.LOGGING_CHAT_ID = original_logging_chat + config.MAIN_CHATS = original_main_chats + + def test_message_forwarding_basic(self): + """Test basic message forwarding functionality.""" + # Backup original config + original_logging_chat = config.LOGGING_CHAT_ID + original_main_chats = config.MAIN_CHATS + + try: + config.LOGGING_CHAT_ID = 2000000020 + config.MAIN_CHATS = [2000000001] + + event = { + "text": "Hello, world!", + "date": 1640995200, # 2022-01-01 00:00:00 + "attachments": [], + "fwd_messages": [], + "reply_message": {} + } + + self.bot._get_chat_title = Mock(return_value="Test Chat") + + self.bot._forward_message_to_logging_chat(event, 2000000001, 12345) + + # Should call send_msg with formatted message + self.bot.send_msg.assert_called_once() + call_args = self.bot.send_msg.call_args + sent_message = call_args[0][0] # First positional argument + sent_to_chat = call_args[0][1] # Second positional argument + + # Check that message was sent to logging chat + self.assertEqual(sent_to_chat, 2000000020) + + # Check message format + self.assertIn("Test Chat", sent_message) + self.assertIn("Test User", sent_message) + self.assertIn("Hello, world!", sent_message) + self.assertIn("2022-01-01", sent_message) + + finally: + # Restore original config + config.LOGGING_CHAT_ID = original_logging_chat + config.MAIN_CHATS = original_main_chats + + def test_message_with_attachments(self): + """Test message forwarding with attachments.""" + # Backup original config + original_logging_chat = config.LOGGING_CHAT_ID + original_main_chats = config.MAIN_CHATS + + try: + config.LOGGING_CHAT_ID = 2000000020 + config.MAIN_CHATS = [2000000001] + + event = { + "text": "Check this out!", + "date": 1640995200, + "attachments": [ + {"type": "photo"}, + {"type": "doc"} + ], + "fwd_messages": [], + "reply_message": {} + } + + self.bot._get_chat_title = Mock(return_value="Test Chat") + + self.bot._forward_message_to_logging_chat(event, 2000000001, 12345) + + # Should call send_msg + self.bot.send_msg.assert_called_once() + call_args = self.bot.send_msg.call_args + sent_message = call_args[0][0] + + # Check that attachments are mentioned + self.assertIn("Attachments:", sent_message) + self.assertIn("[photo]", sent_message) + self.assertIn("[doc]", sent_message) + + finally: + # Restore original config + config.LOGGING_CHAT_ID = original_logging_chat + config.MAIN_CHATS = original_main_chats + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 4f261a1c9b7a6adcfed2f8e5b76d937034c06f31 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 02:38:28 +0300 Subject: [PATCH 4/4] 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 84e4ca80..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/linksplatform/Bot/issues/57 -Your prepared branch: issue-57-af4f56b0 -Your prepared working directory: /tmp/gh-issue-solver-1757806387646 - -Proceed. \ No newline at end of file