Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions python/RULES_FEATURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Rules Management Feature

This document describes the new rules management functionality added to the VK bot.

## Overview

The bot now supports monitoring GitHub Gists containing chat rules and automatically updating pinned messages when rules change.

## Features

### 1. Set Rules from GitHub Gist
- **Command**: `set rules <gist_url>` or `установить правила <gist_url>`
- **Description**: Sets a GitHub Gist as the source for chat rules
- **Example**: `set rules https://gist.github.com/Konard/a7cd43f91c035e412037cbb3de75d540`
- **Requirements**:
- Only works in group chats (peer_id > 2e9)
- Admin permissions recommended (simplified check in current implementation)
- Gist must be publicly accessible

### 2. Remove Rules Monitoring
- **Command**: `remove rules` or `убрать правила`
- **Description**: Removes rules monitoring for the current chat
- **Requirements**: Only works in group chats

### 3. Check Rules Status
- **Command**: `rules status` or `статус правил`
- **Description**: Shows current rules configuration status
- **Shows**:
- Source gist URL
- Who set the rules
- When rules were set
- Last check timestamp

## How It Works

### Initial Setup
1. Admin uses `set rules <gist_url>` command
2. Bot validates the gist URL and checks accessibility
3. Bot fetches current rules content from the gist
4. Bot posts rules as a pinned message in the chat
5. Bot stores configuration for monitoring

### Automatic Monitoring
1. Background thread checks all configured chats every 5 minutes
2. For each chat, bot checks if gist content has changed
3. If content changed:
- Bot tries to edit the existing pinned message
- If editing fails, bot creates a new pinned message
- Bot notifies the chat about the update

### Data Storage
Rules configuration is stored in `chat_rules.json` with the following structure:
```json
{
"chat_id": {
"gist_url": "https://gist.github.com/user/gist_id",
"gist_id": "gist_id",
"set_by": user_id,
"set_at": "2023-01-01T12:00:00",
"last_check": "2023-01-01T12:05:00",
"last_content_hash": 12345,
"pinned_message_id": 67890
}
}
```

## Implementation Details

### New Files
- `modules/rules_service.py` - Core rules management service
- `python/test_patterns_only.py` - Test script for patterns and GitHub API
- `python/RULES_FEATURE.md` - This documentation

### Modified Files
- `python/patterns.py` - Added new command patterns
- `python/__main__.py` - Added VK API methods and monitoring thread
- `python/modules/commands.py` - Added rules command handlers
- `python/modules/commands_builder.py` - Updated help message

### New VK API Methods
- `pin_message()` - Pin a message in chat
- `unpin_message()` - Unpin a message in chat
- `send_and_pin_message()` - Send and immediately pin a message
- `edit_message()` - Edit an existing message

### Error Handling
- Network errors when fetching gist content
- VK API errors when pinning/editing messages
- Invalid gist URLs
- Gist access permission issues
- Missing or malformed configuration files

## Usage Examples

### Setting Rules
```
set rules https://gist.github.com/Konard/a7cd43f91c035e412037cbb3de75d540
```
Bot response: "✅ Правила успешно установлены и закреплены!"

### Checking Status
```
rules status
```
Bot response:
```
📊 Статус правил:
🔗 Источник: https://gist.github.com/Konard/a7cd43f91c035e412037cbb3de75d540
👤 Установил: John Doe
📅 Дата установки: 2023-01-01
🔄 Последняя проверка: 2023-01-01T12:05:00
```

### Removing Rules
```
remove rules
```
Bot response: "✅ Мониторинг правил отключен для этого чата."

## Benefits

1. **Centralized Rules Management**: Rules are stored in GitHub Gists, making them easy to edit and version control
2. **Automatic Updates**: No need to manually update pinned messages when rules change
3. **Multi-language Support**: Commands work in both English and Russian
4. **Persistent Configuration**: Settings are saved and restored between bot restarts
5. **Error Resilience**: Comprehensive error handling for network and API issues

## Future Enhancements

1. **Admin Permission Checks**: Implement proper VK admin permission verification
2. **Multiple Gists**: Support multiple gist sources per chat
3. **Custom Update Intervals**: Allow chats to configure monitoring frequency
4. **Rich Text Formatting**: Support Markdown or HTML formatting in rules
5. **Notification Settings**: Allow chats to configure update notifications
6. **Backup/Restore**: Export/import rules configurations
157 changes: 154 additions & 3 deletions python/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
"""Main Bot module.
"""
from datetime import datetime, timedelta
from typing import NoReturn, List, Dict, Any
from typing import NoReturn, List, Dict, Any, Optional
import threading
import time

from saya import Vk
import requests

from modules import (
BetterBotBaseDataService, Commands
)
from modules.rules_service import RulesService
from tokens import BOT_TOKEN
from userbot import UserBot
import patterns
Expand Down Expand Up @@ -38,7 +41,8 @@ def __init__(
self.messages_to_delete = {}
self.userbot = UserBot()
self.data = BetterBotBaseDataService()
self.commands = Commands(self, self.data)
self.rules_service = RulesService()
self.commands = Commands(self, self.data, self.rules_service)
self.commands.register_cmds(
(patterns.HELP, self.commands.help_message),
(patterns.INFO, self.commands.info_message),
Expand All @@ -63,8 +67,15 @@ def __init__(
(patterns.WHAT_IS, self.commands.what_is),
(patterns.WHAT_MEAN, self.commands.what_is),
(patterns.APPLY_KARMA, self.commands.apply_karma),
(patterns.GITHUB_COPILOT, self.commands.github_copilot)
(patterns.GITHUB_COPILOT, self.commands.github_copilot),
(patterns.SET_RULES_GIST, self.commands.set_rules_gist),
(patterns.REMOVE_RULES_GIST, self.commands.remove_rules_gist),
(patterns.GET_RULES_STATUS, self.commands.get_rules_status)
)

# Start rules monitoring thread
self.rules_monitor_thread = threading.Thread(target=self._monitor_rules, daemon=True)
self.rules_monitor_thread.start()

def message_new(
self,
Expand Down Expand Up @@ -167,6 +178,83 @@ def send_msg(
dict(
message=msg, peer_id=peer_id,
disable_mentions=1, random_id=0))

def pin_message(
self,
peer_id: int,
message_id: int
) -> dict:
"""Pin a message in chat

:param peer_id: chat ID
:param message_id: message ID to pin
"""
return self.call_method(
'messages.pin',
dict(peer_id=peer_id, message_id=message_id))

def unpin_message(
self,
peer_id: int,
message_id: int
) -> dict:
"""Unpin a message in chat

:param peer_id: chat ID
:param message_id: message ID to unpin
"""
return self.call_method(
'messages.unpin',
dict(peer_id=peer_id, message_id=message_id))

def send_and_pin_message(
self,
msg: str,
peer_id: int
) -> Optional[int]:
"""Send a message and pin it

:param msg: message text
:param peer_id: chat ID
:return: message ID if successful, None otherwise
"""
try:
# Send message
response = self.call_method(
'messages.send',
dict(
message=msg, peer_id=peer_id,
disable_mentions=1, random_id=0))

if 'response' in response:
message_id = response['response']
# Pin the message
pin_response = self.pin_message(peer_id, message_id)
if 'response' in pin_response:
return message_id
return None
except Exception as e:
print(f"Error sending and pinning message: {e}")
return None

def edit_message(
self,
peer_id: int,
message_id: int,
message: str
) -> dict:
"""Edit a message

:param peer_id: chat ID
:param message_id: message ID to edit
:param message: new message text
"""
return self.call_method(
'messages.edit',
dict(
peer_id=peer_id,
message_id=message_id,
message=message))

def get_user_name(
self,
Expand Down Expand Up @@ -197,6 +285,69 @@ def get_messages(
"""
reply_message = event.get("reply_message", {})
return [reply_message] if reply_message else event.get("fwd_messages", [])

def _monitor_rules(self) -> NoReturn:
"""Background thread to monitor rules changes"""
while True:
try:
# Check every 5 minutes
time.sleep(300)

# Get all monitored chats
monitored_chats = self.rules_service.get_all_monitored_chats()

for peer_id_str, config in monitored_chats.items():
peer_id = int(peer_id_str)

# Check for updates
updated_content = self.rules_service.check_gist_updates(peer_id)

if updated_content:
# Rules have been updated
new_rules_message = f"📋 Правила чата:\n\n{updated_content}"

# Try to edit existing pinned message
pinned_message_id = config.get("pinned_message_id")

if pinned_message_id:
try:
# Try to edit the existing pinned message
edit_response = self.edit_message(
peer_id, pinned_message_id, new_rules_message)

if 'response' in edit_response:
# Successfully edited
self.send_msg(
"🔄 Правила чата обновлены в закрепленном сообщении!",
peer_id)
else:
# Failed to edit, create new pinned message
self._create_new_pinned_rules(peer_id, new_rules_message)
except Exception as e:
print(f"Error editing pinned message: {e}")
self._create_new_pinned_rules(peer_id, new_rules_message)
else:
# No existing pinned message, create new one
self._create_new_pinned_rules(peer_id, new_rules_message)

except Exception as e:
print(f"Error in rules monitoring: {e}")

def _create_new_pinned_rules(self, peer_id: int, rules_message: str) -> NoReturn:
"""Helper method to create and pin new rules message"""
try:
message_id = self.send_and_pin_message(rules_message, peer_id)
if message_id:
self.rules_service.update_pinned_message_id(peer_id, message_id)
self.send_msg(
"🔄 Правила чата обновлены и закреплено новое сообщение!",
peer_id)
else:
self.send_msg(
"🔄 Правила чата обновлены, но не удалось закрепить сообщение.",
peer_id)
except Exception as e:
print(f"Error creating new pinned rules: {e}")


if __name__ == '__main__':
Expand Down
Loading
Loading