fix(bot): приоритет роутера legal_advisor, кнопка в меню и всеядные команды

This commit is contained in:
Admin 2026-10-13 10:45:48 +04:00
parent d4f572cbb4
commit 0aac30187b
4 changed files with 63 additions and 110 deletions

View file

@ -58,104 +58,8 @@ async def process_swarm_task(message: Message, state: FSMContext):
# ... (код остается прежним) # ... (код остается прежним)
pass pass
# ============================================================================= # (Устаревший блок NotebookLM перенесен в специализированный модуль handlers/legal_advisor.py)
# ЮРИДИЧЕСКИЙ ПОМОЩНИК (NotebookLM)
# =============================================================================
@router.message(Command('jurist'))
@router.message(F.text == '⚖️ Юрист (ЖК РФ)')
@admin_only
async def cmd_jurist(message: Message, state: FSMContext):
"""Активация юридического помощника"""
await message.answer(
"⚖️ <b>ЮРИДИЧЕСКИЙ ПОМОЩНИК (ЖК РФ)</b>\n\n"
"Напишите ваш вопрос для анализа на основе судебных дел и ЖК РФ.\n\n"
"<i>Я проанализирую ваш запрос через NotebookLM.</i>",
parse_mode='HTML'
)
await state.set_state(JuristState.waiting_for_question)
@router.callback_query(F.data == 'admin_jurist')
@admin_callback_only
async def cb_admin_jurist(callback: CallbackQuery, state: FSMContext):
"""Callback-активация юридического помощника"""
await callback.answer()
await callback.message.answer(
"⚖️ <b>ЮРИДИЧЕСКИЙ ПОМОЩНИК (ЖК РФ)</b>\n\n"
"Напишите ваш вопрос для анализа на основе судебных дел и ЖК РФ.\n\n"
"<i>Я проанализирую ваш запрос через NotebookLM.</i>",
parse_mode='HTML'
)
await state.set_state(JuristState.waiting_for_question)
@router.message(JuristState.waiting_for_question)
@admin_only
async def process_jurist_question(message: Message, state: FSMContext):
"""Обработка вопроса к юристу через NotebookLM"""
status_msg = await message.answer(
"⏳ <i>Отправляю запрос в NotebookLM, анализирую судебные материалы и законы (это займет около 10-15 секунд)...</i>",
parse_mode='HTML'
)
import asyncio
import os
# Динамический поиск бинарника nlm
nlm_bin = "nlm"
for p in ["/app/bin/nlm", "/usr/local/bin/nlm", os.path.expanduser("~/.local/bin/nlm"), "/home/matrixhasyou/.local/bin/nlm"]:
if os.path.exists(p):
nlm_bin = p
break
cmd = [
nlm_bin, "query", "notebook",
"00ed7d92-1ab2-4159-ac72-1d0c4be21377",
message.text
]
env = os.environ.copy()
try:
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
env=env
)
stdout, stderr = await proc.communicate()
output = stdout.decode('utf-8').strip()
error = stderr.decode('utf-8').strip()
# Убираем предупреждения о новой версии из вывода nlm
if "Update available:" in output:
output = output.split("Update available:")[0].strip()
if proc.returncode == 0:
import html
import re
# Заменяем <br>, <br/>, <br /> на переносы строк
clean_output = output.replace("<br>", "\n").replace("<br/>", "\n").replace("<br />", "\n")
# Экранируем спецсимволы для parse_mode='HTML'
safe_output = html.escape(clean_output)
# Форматируем **жирный** текст -> <b>жирный</b>
safe_output = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', safe_output, flags=re.DOTALL)
# Форматируем [ссылка](url) -> <a href="\2">\1</a>
safe_output = re.sub(r'\[(.*?)\]\((.*?)\)', r'<a href="\2">\1</a>', safe_output, flags=re.DOTALL)
if len(safe_output) > 4000:
parts = [safe_output[i:i+4000] for i in range(0, len(safe_output), 4000)]
await status_msg.edit_text(f"⚖️ <b>Анализ NotebookLM (часть 1):</b>\n\n{parts[0]}", parse_mode='HTML')
for part in parts[1:]:
await message.answer(part, parse_mode='HTML')
else:
await status_msg.edit_text(f"⚖️ <b>Анализ NotebookLM:</b>\n\n{safe_output}", parse_mode='HTML')
else:
await status_msg.edit_text(f"❌ <b>Ошибка запроса к NotebookLM:</b>\n{error or output}")
except Exception as e:
await status_msg.edit_text(f"❌ <b>Внутренняя ошибка бота:</b> {str(e)}")
finally:
await state.clear()
@router.message(F.text & ~F.text.startswith('/') & F.chat.type == 'private') @router.message(F.text & ~F.text.startswith('/') & F.chat.type == 'private')

View file

@ -77,11 +77,46 @@ async def determine_user_role(user_id: int) -> str:
# === КОМАНДЫ АКТИВАЦИИ === # === КОМАНДЫ АКТИВАЦИИ ===
@router.message(Command("юрист", "jurist", "вопрос", "help_law")) @router.message(Command("jurist", "юрист", "вопрос", "help_law"))
@router.message(F.text == "⚖️ Юрист (ЖК РФ)") @router.message(F.text.casefold().in_(["/юрист", "/jurist", "/вопрос", "юрист", "⚖️ юрист (жк рф)"]))
async def cmd_legal_advisor(message: Message, state: FSMContext): @router.message(F.text.casefold().startswith(("/юрист", "/jurist")))
async def cmd_legal_advisor(message: Message, state: FSMContext, bot: Bot):
role = await determine_user_role(message.from_user.id) role = await determine_user_role(message.from_user.id)
# Проверяем, не задал ли пользователь вопрос прямо в команде: /юрист Какой срок ответа?
parts = (message.text or "").split(maxsplit=1)
if len(parts) > 1 and parts[0].casefold() in ("/юрист", "/jurist", "/вопрос"):
inline_question = parts[1].strip()
if len(inline_question) > 3:
status_msg = await message.answer("⏳ <i>Анализирую нормативную базу дома...</i>", parse_mode='HTML')
result = await legal_engine.query(prompt=inline_question, role=role)
await status_msg.delete()
if result.get("success"):
header = f"⚖️ <b>Юридический ответ ({result.get('latency_sec')} сек):</b>"
await send_split_message(message, header, result["text"])
else:
await message.answer(f"❌ <b>Ошибка:</b> {result.get('text')}", parse_mode='HTML')
return
role_desc = {
"admin": "👑 <b>Режим Стратегического Аналитика (Администратор)</b>",
"lawyer": "⚖️ <b>Режим Судебного Юриста (Совет дома / Активист)</b>",
"resident": "🏡 <b>Соседский Юридический Помощник</b>"
}.get(role, "🏡 <b>Юридический Помощник</b>")
text = (
f"{role_desc}\n\n"
"Задайте ваш вопрос по ЖКХ, протечкам, тарифам, собраниям или спорам с управляющей компанией.\n\n"
"🎙 <b>Вы можете написать текст или отправить голосовое сообщение!</b>\n"
"<i>Я проанализирую обращение по базе законов РФ и материалам дома.</i>"
)
await message.answer(text, parse_mode='HTML')
await state.set_state(LegalQuestionState.waiting_for_question)
@router.callback_query(F.data.in_(["admin_jurist", "jurist_ai", "main_jurist"]))
async def cb_legal_advisor(callback: CallbackQuery, state: FSMContext):
await callback.answer()
role = await determine_user_role(callback.from_user.id)
role_desc = { role_desc = {
"admin": "👑 <b>Режим Стратегического Аналитика (Администратор)</b>", "admin": "👑 <b>Режим Стратегического Аналитика (Администратор)</b>",
"lawyer": "⚖️ <b>Режим Судебного Юриста (Совет дома / Активист)</b>", "lawyer": "⚖️ <b>Режим Судебного Юриста (Совет дома / Активист)</b>",
@ -94,7 +129,7 @@ async def cmd_legal_advisor(message: Message, state: FSMContext):
"🎙 <b>Вы можете написать текст или отправить голосовое сообщение!</b>\n" "🎙 <b>Вы можете написать текст или отправить голосовое сообщение!</b>\n"
"<i>Я проанализирую обращение по базе законов РФ и судебным материалам дома.</i>" "<i>Я проанализирую обращение по базе законов РФ и судебным материалам дома.</i>"
) )
await message.answer(text, parse_mode='HTML') await callback.message.answer(text, parse_mode='HTML')
await state.set_state(LegalQuestionState.waiting_for_question) await state.set_state(LegalQuestionState.waiting_for_question)
# === ОБРАБОТКА ТЕКСТОВЫХ ВОПРОСОВ === # === ОБРАБОТКА ТЕКСТОВЫХ ВОПРОСОВ ===

View file

@ -15,6 +15,9 @@ def get_main_menu(verified: bool = False, is_ig: bool = False, is_admin: bool =
# СТРОКА 1: БАЗА (ВСЕГДА ТУТ) # СТРОКА 1: БАЗА (ВСЕГДА ТУТ)
builder.row( builder.row(
InlineKeyboardButton(text='📞 Телефоны', callback_data='phones'), InlineKeyboardButton(text='📞 Телефоны', callback_data='phones'),
InlineKeyboardButton(text='⚖️ Юрист (ЖК РФ)', callback_data='main_jurist')
)
builder.row(
InlineKeyboardButton(text='🏢 О доме', callback_data='about_house') InlineKeyboardButton(text='🏢 О доме', callback_data='about_house')
) )

29
main.py
View file

@ -49,6 +49,18 @@ async def on_startup(bot: Bot):
await init_db() await init_db()
scheduler = Scheduler(bot) scheduler = Scheduler(bot)
scheduler.start() scheduler.start()
try:
from aiogram.types import BotCommand
await bot.set_my_commands([
BotCommand(command="start", description="Главное меню дома"),
BotCommand(command="jurist", description="⚖️ Юрист & Разбор ЖК РФ"),
BotCommand(command="admin", description="🛡️ Админ-панель"),
BotCommand(command="phones", description="📞 Экстренные телефоны"),
BotCommand(command="help", description="❓ Помощь и навигация")
])
except Exception as e:
logger.warning(f"Failed to set bot commands: {e}")
try: try:
await bot.send_message(ADMIN_USER_ID, '✅ <b>LKM37 CORE v4.3 АКТИВИРОВАН</b>') await bot.send_message(ADMIN_USER_ID, '✅ <b>LKM37 CORE v4.3 АКТИВИРОВАН</b>')
await send_domovoy_alert("Сервис запущен", "Домовой МКД успешно запущен в автономном коммунальном режиме.") await send_domovoy_alert("Сервис запущен", "Домовой МКД успешно запущен в автономном коммунальном режиме.")
@ -59,7 +71,12 @@ async def main():
bot = get_bot() bot = get_bot()
dp = Dispatcher(storage=MemoryStorage()) dp = Dispatcher(storage=MemoryStorage())
# Регистрация роутеров МКД # Регистрация роутеров МКД (Юрист в наивысшем приоритете)
from handlers.legal_advisor import router as legal_advisor_router
from handlers.smart_broadcast import router as smart_broadcast_router
from handlers.digest import router as digest_router
dp.include_router(legal_advisor_router)
dp.include_router(admin_router) dp.include_router(admin_router)
dp.include_router(users_router) dp.include_router(users_router)
@ -69,18 +86,12 @@ async def main():
antispam_router, ratelimit_router, schedule_router, ads_router, antispam_router, ratelimit_router, schedule_router, ads_router,
payments_router, multicount_router, topics_router, toxicity_router, payments_router, multicount_router, topics_router, toxicity_router,
profile_history_router, achievements_router, thanks_router, profile_history_router, achievements_router, thanks_router,
events_router, initiative_group_router, mutual_aid_router events_router, initiative_group_router, mutual_aid_router,
smart_broadcast_router, digest_router
] ]
for r in other_routers: for r in other_routers:
dp.include_router(r) dp.include_router(r)
from handlers.smart_broadcast import router as smart_broadcast_router
from handlers.digest import router as digest_router
from handlers.legal_advisor import router as legal_advisor_router
dp.include_router(legal_advisor_router)
dp.include_router(smart_broadcast_router)
dp.include_router(digest_router)
dp.startup.register(on_startup) dp.startup.register(on_startup)