# -*- coding: utf-8 -*- """ Обработка файлов: отправка пользователю через HTTP Upload и хирургическая замена в DOCX. Функции: send_file_to_user, handle_ai_file_request. АДАПТАЦИЯ: эти функции уже работают через HTTP (загружают файлы в ejabberd). Они не требуют изменений, так как работают с локальными файлами. Но я добавлю комментарии для ясности. """ import asyncio import os import re import shutil import time import random import string import logging logger = logging.getLogger(__name__) async def send_file_to_user(bot, msg, file_path, prefix="", suffix=None): """ Отправляет файл пользователю через HTTP Upload (XEP-0363). Аргументы: bot: экземпляр бота msg: объект XMPP-сообщения file_path: путь к файлу на диске prefix: текст перед ссылкой suffix: суффикс для имени файла Возвращает: bool: True при успехе, False при ошибке """ if not os.path.exists(file_path): logger.error(f"Файл не найден: {file_path}") return False original_name = os.path.basename(file_path) name, ext = os.path.splitext(original_name) # Санитизация суффикса (только буквы, цифры, подчёркивание, дефис) if suffix: suffix = re.sub(r'[^a-zA-Zа-яА-ЯёЁ0-9_-]', '', suffix) suffix = suffix.strip('.') if not suffix or suffix == '_': suffix = '_sanitized' if not suffix.startswith('_'): suffix = '_' + suffix new_name = f"{name}{suffix}{ext}" new_path = os.path.join(os.path.dirname(file_path), new_name) os.rename(file_path, new_path) file_path = new_path try: # Загружаем файл через XEP-0363 (HTTP Upload) result = await bot.plugin['xep_0363'].upload_file(file_path) if isinstance(result, dict): file_url = result.get('get_url', result.get('url', str(result))) else: file_url = str(result) except Exception as e: logger.error(f"Ошибка загрузки файла через XEP-0363: {e}", exc_info=True) error_reply = msg.reply("❌ Не удалось загрузить файл. Проверьте настройки HTTP Upload.") if error_reply: await error_reply.send() return False reply = msg.reply(f"{prefix}\n📄 {file_url}") if reply: reply.send() else: logger.error("Не удалось создать reply для ссылки на файл") return True async def handle_ai_file_request(bot, msg, ai_response, user_jid, room_jid=None, body=None, suffix=None): """ Обрабатывает теги [SURGICAL_REPLACE], [REWRITE], [FILE] из ответа AI. Поддерживает три режима: - SURGICAL: замена текста в существующем DOCX - REWRITE: создание нового DOCX из текста - FILE: создание нового DOCX (общий случай) """ key = (user_jid, room_jid) if room_jid is not None else user_jid surgical_match = re.search(r'\[SURGICAL_REPLACE\](.*?)\[/SURGICAL_REPLACE\]', ai_response, re.DOTALL) rewrite_match = re.search(r'\[REWRITE\](.*?)\[/REWRITE\]', ai_response, re.DOTALL) file_match = re.search(r'\[FILE\](.*?)\[/FILE\]', ai_response, re.DOTALL) # ---- SURGICAL_REPLACE ---- if surgical_match: content = surgical_match.group(1).strip() replacements = {} for line in content.split('\n'): line = line.strip() if '|||' in line: old, new = line.split('|||', 1) replacements[old.strip()] = new.strip() if not replacements: reply = msg.reply("⚠️ Не найдено пар 'старое ||| новое' в теге SURGICAL_REPLACE.") if reply: reply.send() return original_path = bot._last_file_path.get(key) if not original_path or not os.path.exists(original_path): reply = msg.reply("❌ Нет загруженного документа для замены. Сначала отправьте файл (без !learn).") if reply: reply.send() return new_path = await asyncio.to_thread(bot.files.surgical_replace, original_path, replacements) if new_path: await send_file_to_user(bot, msg, new_path, prefix="✅ Замена выполнена:", suffix="surgical") try: os.unlink(new_path) except Exception as e: logger.warning(f"Не удалось удалить временный файл {new_path}: {e}") else: reply = msg.reply("❌ Ошибка при хирургической замене. Проверьте формат документа.") if reply: reply.send() return # ---- REWRITE ---- if rewrite_match: content = rewrite_match.group(1).strip() title = "Сгенерированный документ" title_match = re.search(r'\[TITLE\](.*?)\[/TITLE\]', ai_response, re.DOTALL) if title_match: title = title_match.group(1).strip() new_path = await asyncio.to_thread(bot.files.create_docx, content, title, user_jid, mode="REWRITE") if new_path: await send_file_to_user(bot, msg, new_path, prefix="📄 Документ создан:", suffix="rewrite") try: os.unlink(new_path) except Exception as e: logger.warning(f"Не удалось удалить временный файл {new_path}: {e}") else: reply = msg.reply("❌ Не удалось создать документ.") if reply: reply.send() return # ---- FILE ---- if file_match: content = file_match.group(1).strip() title = "Файл" title_match = re.search(r'\[TITLE\](.*?)\[/TITLE\]', ai_response, re.DOTALL) if title_match: title = title_match.group(1).strip() new_path = await asyncio.to_thread(bot.files.create_docx, content, title, user_jid, mode="FILE") if new_path: await send_file_to_user(bot, msg, new_path, prefix="📄 Файл создан:", suffix="file") try: os.unlink(new_path) except Exception as e: logger.warning(f"Не удалось удалить временный файл {new_path}: {e}") else: reply = msg.reply("❌ Не удалось создать файл.") if reply: reply.send() return logger.debug(f"Ответ AI не содержит тегов файлов: {ai_response[:200]}")