Добавлена возможность пересылки нескольких сообщений в запросе к ИИ (только для VK).

Команду !проверка могут вызывать все участники.
This commit is contained in:
Kirill Kirilenko 2025-12-30 20:14:42 +03:00
parent 71bb3c024d
commit a181d00da5
8 changed files with 95 additions and 82 deletions

View file

@ -43,11 +43,11 @@ class AiAgent:
self.chat_contexts: Dict[int, ChatContext] = {} self.chat_contexts: Dict[int, ChatContext] = {}
async def get_reply(self, chat_id: int, chat_prompt: str, async def get_reply(self, chat_id: int, chat_prompt: str,
message: AiMessage, forwarded_message: Optional[AiMessage]) -> str: message: AiMessage, forwarded_messages: List[AiMessage]) -> str:
message_text = message.text message_text = message.text
if forwarded_message: for fwd_message in forwarded_messages:
message_text += '\n<Цитируемое сообщение от {}>\n'.format(forwarded_message.user_name) message_text += '\n<Цитируемое сообщение от {}>\n'.format(fwd_message.user_name)
message_text += forwarded_message.text + '\n' message_text += fwd_message.text + '\n'
message_text += '<Конец цитаты>' message_text += '<Конец цитаты>'
context = self._get_chat_context(chat_id, chat_prompt) context = self._get_chat_context(chat_id, chat_prompt)
@ -81,7 +81,7 @@ class AiAgent:
def _get_chat_context(self, chat_id: int, chat_prompt: Optional[str]) -> ChatContext: def _get_chat_context(self, chat_id: int, chat_prompt: Optional[str]) -> ChatContext:
"""Get or create chat context for a specific chat""" """Get or create chat context for a specific chat"""
if chat_id not in self.chat_contexts: if chat_id not in self.chat_contexts:
self.chat_contexts[chat_id] = ChatContext(10) self.chat_contexts[chat_id] = ChatContext(max_messages=20)
prompt = SYSTEM_PROMPT prompt = SYSTEM_PROMPT
if chat_prompt is not None: if chat_prompt is not None:
prompt += '\n\n' + chat_prompt prompt += '\n\n' + chat_prompt

View file

@ -1,6 +1,7 @@
MESSAGE_CHAT_NOT_ACTIVE = 'Извините, но я пока не работаю в этом чате.' MESSAGE_CHAT_NOT_ACTIVE = 'Извините, но я пока не работаю в этом чате.'
MESSAGE_PERMISSION_DENIED = 'Извините, но о таком меня может попросить только администратор чата.' MESSAGE_PERMISSION_DENIED = 'Извините, но о таком меня может попросить только администратор чата.'
MESSAGE_NEED_REPLY = 'Извините, но эту команду нужно вызывать в ответном сообщении.' MESSAGE_NEED_REPLY = 'Извините, но эту команду нужно вызывать в ответ на текстовое сообщение.'
MESSAGE_NEED_REPLY_OR_FORWARD = 'Извините, но эту команду нужно вызывать в ответ на текстовое сообщение или с пересылкой текстовых сообщений.'
MESSAGE_DEFAULT_RULES = 'Правила не установлены. Просто ведите себя хорошо.' MESSAGE_DEFAULT_RULES = 'Правила не установлены. Просто ведите себя хорошо.'
MESSAGE_DEFAULT_CHECK_RULES = 'Правила чата не установлены. Проверка невозможна.' MESSAGE_DEFAULT_CHECK_RULES = 'Правила чата не установлены. Проверка невозможна.'
MESSAGE_DEFAULT_GREETING_JOIN = 'Добро пожаловать, {name}!' MESSAGE_DEFAULT_GREETING_JOIN = 'Добро пожаловать, {name}!'

View file

@ -5,7 +5,7 @@ from aiogram.utils.formatting import Bold
import utils import utils
from messages import * from messages import *
import tg.tg_database as database import tg.tg_database as database
from .default import clear_ai_chat_context, get_ai_reply from .default import clear_ai_chat_context
router = Router() router = Router()
@ -94,35 +94,6 @@ async def set_ai_prompt_handler(message: Message, bot: Bot):
await message.answer('Личность ИИ изменена.') await message.answer('Личность ИИ изменена.')
@router.message(F.text == "!проверка")
async def check_rules_violation_handler(message: Message, bot: Bot):
chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
if not await tg_user_is_admin(chat_id, message.from_user.id, bot):
await message.answer(MESSAGE_PERMISSION_DENIED)
return
if message.reply_to_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
chat_rules = chat['rules']
if chat_rules is None:
await message.answer(MESSAGE_DEFAULT_CHECK_RULES)
return
prompt = 'В чате действуют следующие правила:\n'
prompt += chat_rules + '\n\n'
prompt += 'Проверь, нарушает ли правила следующее сообщение (используй ссылки на конкретные пункты правил):'
await message.answer(await get_ai_reply(message.bot, chat_id, message.from_user, prompt,
message.reply_to_message.from_user, message.reply_to_message.text))
@router.message(F.text == "!предупреждение") @router.message(F.text == "!предупреждение")
async def warning_handler(message: Message, bot: Bot): async def warning_handler(message: Message, bot: Bot):
chat_id = message.chat.id chat_id = message.chat.id

View file

@ -66,7 +66,7 @@ async def any_message_handler(message: Message):
bot_user = await message.bot.get_me() bot_user = await message.bot.get_me()
ai_message = AiMessage() ai_message = AiMessage()
ai_fwd_message: Optional[AiMessage] = None ai_fwd_messages: list[AiMessage] = []
# Ответ на сообщение бота # Ответ на сообщение бота
if message.reply_to_message and message.reply_to_message.from_user.id == bot_user.id: if message.reply_to_message and message.reply_to_message.from_user.id == bot_user.id:
@ -80,9 +80,9 @@ async def any_message_handler(message: Message):
return return
if message.reply_to_message and message.reply_to_message.content_type == ContentType.TEXT: if message.reply_to_message and message.reply_to_message.content_type == ContentType.TEXT:
ai_fwd_message = ( ai_fwd_messages = [
AiMessage(user_name=await get_user_name_for_ai(message.reply_to_message.from_user), AiMessage(user_name=await get_user_name_for_ai(message.reply_to_message.from_user),
text=message.reply_to_message.text)) text=message.reply_to_message.text)]
ai_message.user_name = await get_user_name_for_ai(message.from_user) ai_message.user_name = await get_user_name_for_ai(message.from_user)
chat_prompt = chat['ai_prompt'] chat_prompt = chat['ai_prompt']
@ -93,7 +93,7 @@ async def any_message_handler(message: Message):
agent = AiAgent(message.bot.config['openrouter_token']) agent = AiAgent(message.bot.config['openrouter_token'])
await message.bot.send_chat_action(chat_id, 'typing') await message.bot.send_chat_action(chat_id, 'typing')
await message.reply(await agent.get_reply(chat_id, chat_prompt, ai_message, ai_fwd_message)) await message.reply(await agent.get_reply(chat_id, chat_prompt, ai_message, ai_fwd_messages))
def clear_ai_chat_context(chat_id: int): def clear_ai_chat_context(chat_id: int):
@ -107,7 +107,7 @@ async def get_ai_reply(bot: Bot, chat_id, user: User, message: str, fwd_user: Us
chat_prompt = chat['ai_prompt'] chat_prompt = chat['ai_prompt']
ai_message = AiMessage(user_name=await get_user_name_for_ai(user), text=message) ai_message = AiMessage(user_name=await get_user_name_for_ai(user), text=message)
ai_fwd_message = AiMessage(user_name=await get_user_name_for_ai(fwd_user), text=fwd_message) ai_fwd_messages = [AiMessage(user_name=await get_user_name_for_ai(fwd_user), text=fwd_message)]
global agent global agent
if agent is None: if agent is None:
@ -115,4 +115,4 @@ async def get_ai_reply(bot: Bot, chat_id, user: User, message: str, fwd_user: Us
agent = AiAgent(bot.config['openrouter_token']) agent = AiAgent(bot.config['openrouter_token'])
await bot.send_chat_action(chat_id, 'typing') await bot.send_chat_action(chat_id, 'typing')
return await agent.get_reply(chat_id, chat_prompt, ai_message, ai_fwd_message) return await agent.get_reply(chat_id, chat_prompt, ai_message, ai_fwd_messages)

View file

@ -1,12 +1,14 @@
from typing import List, Any from typing import List, Any
from aiogram import Bot, Router, F from aiogram import Bot, Router, F
from aiogram.enums import ContentType
from aiogram.types import Message from aiogram.types import Message
from aiogram.utils.formatting import Bold, Italic from aiogram.utils.formatting import Bold, Italic
import utils import utils
from messages import * from messages import *
import tg.tg_database as database import tg.tg_database as database
from .default import get_ai_reply
router = Router() router = Router()
@ -44,6 +46,7 @@ async def help_handler(message: Message):
response += '!месяц - статистика сообщений за месяц\n' response += '!месяц - статистика сообщений за месяц\n'
response += '!молчуны - список молчунов\n' response += '!молчуны - список молчунов\n'
response += '!предупреждения - список участников с предупреждениями\n' response += '!предупреждения - список участников с предупреждениями\n'
response += '!проверка* - проверить сообщения на нарушение правил\n'
response += '\n' response += '\n'
response += Bold('Команды для администраторов') + '\n' response += Bold('Команды для администраторов') + '\n'
response += '!старт - начать работу в чате\n' response += '!старт - начать работу в чате\n'
@ -129,3 +132,28 @@ async def warnings_handler(message: Message, bot: Bot):
response = Bold('Участники с предупреждениями') + '\n' response = Bold('Участники с предупреждениями') + '\n'
response += await format_rating(chat_id, top_users, bot) response += await format_rating(chat_id, top_users, bot)
await message.answer(**response.as_kwargs()) await message.answer(**response.as_kwargs())
@router.message(F.text == "!проверка")
async def check_rules_violation_handler(message: Message):
chat_id = message.chat.id
chat = database.DB.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
if message.reply_to_message is None or message.reply_to_message.content_type != ContentType.TEXT:
await message.answer(MESSAGE_NEED_REPLY)
return
chat_rules = chat['rules']
if chat_rules is None:
await message.answer(MESSAGE_DEFAULT_CHECK_RULES)
return
prompt = 'В чате действуют следующие правила:\n'
prompt += chat_rules + '\n\n'
prompt += 'Проверь, нарушает ли правила следующее сообщение (используй ссылки на конкретные пункты правил):'
await message.answer(await get_ai_reply(message.bot, chat_id, message.from_user, prompt,
message.reply_to_message.from_user, message.reply_to_message.text))

View file

@ -6,7 +6,7 @@ from vkbottle_types.codegen.objects import MessagesGetConversationMembers
from messages import * from messages import *
import utils import utils
import vk.vk_database as database import vk.vk_database as database
from .default import clear_ai_chat_context, get_ai_reply from .default import clear_ai_chat_context
labeler = BotLabeler() labeler = BotLabeler()
@ -149,36 +149,6 @@ async def set_ai_prompt_handler(message: Message):
await message.answer('Личность ИИ изменена.') await message.answer('Личность ИИ изменена.')
@labeler.chat_message(text="!проверка")
async def check_rules_violation_handler(message: Message):
chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
chat_members = await message.ctx_api.messages.get_conversation_members(peer_id=chat_id, extended=False)
if not vk_user_is_admin(message.from_id, chat_members):
await message.answer(MESSAGE_PERMISSION_DENIED)
return
if message.reply_message is None:
await message.answer(MESSAGE_NEED_REPLY)
return
chat_rules = chat['rules']
if chat_rules is None:
await message.answer(MESSAGE_DEFAULT_CHECK_RULES)
return
prompt = 'В чате действуют следующие правила:\n'
prompt += chat_rules + '\n\n'
prompt += 'Проверь, нарушает ли правила следующее сообщение (используй ссылки на конкретные пункты правил):'
await message.answer(await get_ai_reply(message.ctx_api, chat_id, message.from_id, prompt,
message.reply_message.from_id, message.reply_message.text))
@labeler.chat_message(text="!предупреждение") @labeler.chat_message(text="!предупреждение")
async def warning_handler(message: Message): async def warning_handler(message: Message):
chat_id = message.peer_id chat_id = message.peer_id

View file

@ -1,5 +1,5 @@
import re import re
from typing import Optional from typing import Optional, Tuple, List
from vkbottle import API from vkbottle import API
from vkbottle.bot import Message from vkbottle.bot import Message
@ -50,7 +50,7 @@ async def any_message_handler(message: Message):
bot_user = (await message.ctx_api.groups.get_by_id()).groups[0] bot_user = (await message.ctx_api.groups.get_by_id()).groups[0]
ai_message = AiMessage() ai_message = AiMessage()
ai_fwd_message: Optional[AiMessage] = None ai_fwd_messages: list[AiMessage] = []
# Ответ на сообщение бота # Ответ на сообщение бота
if message.reply_message and message.reply_message.from_id == -bot_user.id: if message.reply_message and message.reply_message.from_id == -bot_user.id:
@ -68,10 +68,16 @@ async def any_message_handler(message: Message):
else: else:
return return
if message.reply_message and message.reply_message.text is not None: if message.reply_message and len(message.reply_message.text) > 0:
ai_fwd_message = ( ai_fwd_messages.append(
AiMessage(user_name=await get_user_name_for_ai(message.ctx_api, message.reply_message.from_id), AiMessage(user_name=await get_user_name_for_ai(message.ctx_api, message.reply_message.from_id),
text=message.reply_message.text)) text=message.reply_message.text))
else:
for fwd_message in message.fwd_messages:
if len(fwd_message.text) > 0:
ai_fwd_messages.append(
AiMessage(user_name=await get_user_name_for_ai(message.ctx_api, fwd_message.from_id),
text=fwd_message.text))
ai_message.user_name = await get_user_name_for_ai(message.ctx_api, message.from_id) ai_message.user_name = await get_user_name_for_ai(message.ctx_api, message.from_id)
chat_prompt = chat['ai_prompt'] chat_prompt = chat['ai_prompt']
@ -82,7 +88,7 @@ async def any_message_handler(message: Message):
agent = AiAgent(message.ctx_api.config['openrouter_token']) agent = AiAgent(message.ctx_api.config['openrouter_token'])
await message.ctx_api.messages.set_activity(peer_id=chat_id, type='typing') await message.ctx_api.messages.set_activity(peer_id=chat_id, type='typing')
await message.reply(await agent.get_reply(chat_id, chat_prompt, ai_message, ai_fwd_message)) await message.reply(await agent.get_reply(chat_id, chat_prompt, ai_message, ai_fwd_messages))
def clear_ai_chat_context(chat_id: int): def clear_ai_chat_context(chat_id: int):
@ -91,12 +97,15 @@ def clear_ai_chat_context(chat_id: int):
agent.clear_chat_context(chat_id) agent.clear_chat_context(chat_id)
async def get_ai_reply(api: API, chat_id, user_id: int, message: str, fwd_user_id: int, fwd_message: str) -> str: async def get_ai_reply(api: API, chat_id, message: Tuple[int, str], fwd_messages: List[Tuple[int, str]]) -> str:
chat = database.DB.create_chat_if_not_exists(chat_id) chat = database.DB.create_chat_if_not_exists(chat_id)
chat_prompt = chat['ai_prompt'] chat_prompt = chat['ai_prompt']
ai_message = AiMessage(user_name=await get_user_name_for_ai(api, user_id), text=message) ai_message = AiMessage(user_name=await get_user_name_for_ai(api, message[0]), text=message[1])
ai_fwd_message = AiMessage(user_name=await get_user_name_for_ai(api, fwd_user_id), text=fwd_message) ai_fwd_messages: List[AiMessage] = []
for fwd_message in fwd_messages:
ai_fwd_messages.append(
AiMessage(user_name=await get_user_name_for_ai(api, fwd_message[0]), text=fwd_message[1]))
global agent global agent
if agent is None: if agent is None:
@ -104,4 +113,4 @@ async def get_ai_reply(api: API, chat_id, user_id: int, message: str, fwd_user_i
agent = AiAgent(api.config['openrouter_token']) agent = AiAgent(api.config['openrouter_token'])
await api.messages.set_activity(peer_id=chat_id, type='typing') await api.messages.set_activity(peer_id=chat_id, type='typing')
return await agent.get_reply(chat_id, chat_prompt, ai_message, ai_fwd_message) return await agent.get_reply(chat_id, chat_prompt, ai_message, ai_fwd_messages)

View file

@ -6,6 +6,7 @@ from vkbottle.framework.labeler import BotLabeler
from messages import * from messages import *
import vk.vk_database as database import vk.vk_database as database
from .default import get_ai_reply
labeler = BotLabeler() labeler = BotLabeler()
@ -50,6 +51,7 @@ async def rules_handler(message: Message):
response += '!молчуны - список молчунов\n' response += '!молчуны - список молчунов\n'
response += '!предупреждения - список участников с предупреждениями\n' response += '!предупреждения - список участников с предупреждениями\n'
response += '!поздравление - запретить/разрешить поздравлять с днем рождения\n' response += '!поздравление - запретить/разрешить поздравлять с днем рождения\n'
response += '!проверка* - проверить сообщения на нарушение правил\n'
response += '\n' response += '\n'
response += bold('Команды для администраторов') + '\n' response += bold('Команды для администраторов') + '\n'
response += '!старт - начать работу в чате\n' response += '!старт - начать работу в чате\n'
@ -157,3 +159,35 @@ async def no_birthday_handler(message: Message):
await message.answer('Хорошо, я буду поздравлять тебя с днем рождения, если его дата не скрыта.') await message.answer('Хорошо, я буду поздравлять тебя с днем рождения, если его дата не скрыта.')
else: else:
await message.answer('Хорошо, я не буду поздравлять тебя с днем рождения.') await message.answer('Хорошо, я не буду поздравлять тебя с днем рождения.')
@labeler.chat_message(text="!проверка")
async def check_rules_violation_handler(message: Message):
chat_id = message.peer_id
chat = database.DB.create_chat_if_not_exists(chat_id)
if chat['active'] == 0:
await message.answer(MESSAGE_CHAT_NOT_ACTIVE)
return
chat_rules = chat['rules']
if chat_rules is None:
await message.answer(MESSAGE_DEFAULT_CHECK_RULES)
return
prompt = 'В чате действуют следующие правила:\n'
prompt += chat_rules + '\n\n'
prompt += 'Проверь, нарушает ли правила следующее сообщение (используй ссылки на конкретные пункты правил):'
fwd_messages: list[tuple[int, str]] = []
if message.reply_message is not None and len(message.reply_message.text) > 0:
fwd_messages.append((message.reply_message.from_id, message.reply_message.text))
else:
for fwd_message in message.fwd_messages:
if len(fwd_message.text) > 0:
fwd_messages.append((fwd_message.from_id, fwd_message.text))
if len(fwd_messages) == 0:
await message.answer(MESSAGE_NEED_REPLY_OR_FORWARD)
return
await message.answer(await get_ai_reply(message.ctx_api, chat_id, (message.from_id, prompt), fwd_messages))