import asyncio import logging import sqlite3 from datetime import datetime from aiogram import Bot, Dispatcher, F from aiogram.filters import Command, CommandStart from aiogram.types import CallbackQuery, Message from bot.config import BOT_TOKEN, ADMIN_IDS, LOG_GROUP_ID, INITIAL_SCAN_LIMIT, DB_PATH from bot.db import ( init_db, get_pending_comments, update_comment_status, save_comment, get_comment, get_comments_for_post, increment_regeneration, get_all_sessions, get_active_sessions, toggle_session, delete_session, get_summary_stats, get_stats, update_stats, add_target_group, get_target_groups, get_all_target_groups, remove_target_group, toggle_target_group, is_target_group, get_pending_comments_by_session, get_pending_comments_by_group ) from bot.ollama import generate_comment from bot.keyboard import ( create_moderation_keyboard, create_main_menu, create_main_keyboard, create_session_keyboard, create_edit_cancel_keyboard, create_groups_list_keyboard, create_group_action_keyboard ) from bot.session_manager import session_manager logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger('controller') class CommentBot: """Контроллер бота для модерации комментариев""" def __init__(self): if not BOT_TOKEN: raise ValueError("BOT_TOKEN не задан в .env") self.bot = Bot(token=BOT_TOKEN) self.dp = Dispatcher() self.editing_comments: dict[int, dict] = {} # chat_id: {message_id, comment_data} self._register_handlers() def _register_handlers(self): """Регистрация обработчиков""" self.dp.message(CommandStart())(self.cmd_start) self.dp.message(Command("stats"))(self.cmd_stats) self.dp.message(Command("pending"))(self.cmd_pending) self.dp.message(Command("sessions"))(self.cmd_sessions) self.dp.message(Command("groups"))(self.cmd_groups) self.dp.message(Command("add_group"))(self.cmd_add_group) self.dp.message(Command("help"))(self.cmd_help) self.dp.message(F.text)(self.handle_text_message) self.dp.callback_query()(self.handle_callback) # Состояния для добавления группы self.add_group_state: dict = {} # user_id: {'waiting': True} async def cmd_start(self, message: Message): """Обработчик команды /start""" if not self._is_admin(message.from_user.id): await message.answer("❌ У вас нет доступа к управлению ботом") return # Получаем список групп groups = get_target_groups() groups_text = "\n".join([f"• `{g['group_id']}`" for g in groups]) if groups else "Нет добавленных групп" await message.answer( f"🤖 **Bot Controller**\n\n" f"📋 Группы:\n{groups_text}\n\n" f"Используйте меню для управления:", reply_markup=create_main_keyboard()) async def cmd_stats(self, message: Message): """Обработчик команды /stats""" if not self._is_admin(message.from_user.id): return summary = get_summary_stats() sessions = get_active_sessions() text = ( "📊 **Статистика**\n\n" f"📝 Всего комментариев: `{summary.get('total_comments', 0)}`\n" f"⏳ Ожидают: `{summary.get('pending', 0)}`\n" f"✅ Одобрено: `{summary.get('approved', 0)}`\n" f"❌ Отклонено: `{summary.get('rejected', 0)}`\n" f"📤 Отправлено: `{summary.get('sent', 0)}`\n\n" f"👥 Активных сессий: `{len(sessions)}`" ) await message.answer(text) async def cmd_pending(self, message: Message): """Обработчик команды /pending""" if not self._is_admin(message.from_user.id): return pending = get_pending_comments() if not pending: await message.answer("✅ Нет ожидающих комментариев") return await message.answer(f"📝 Ожидают модерации: {len(pending)}") # Показываем первые 5 for comment in pending[:5]: await self._send_moderation_message( chat_id=message.chat.id, comment=comment ) async def cmd_sessions(self, message: Message): """Обработчик команды /sessions""" if not self._is_admin(message.from_user.id): return sessions = get_all_sessions() if not sessions: await message.answer( "❌ Нет активных сессий\n\n" "Добавьте файлы сессий в папку `sessions/`" ) return text = "👥 **Сессии**\n\n" for session in sessions: status = "🟢" if session['is_active'] else "🔴" username = f"@{session['username']}" if session['username'] else "Без username" text += f"{status} `{session['session_file']}` — {username}\n" await message.answer(text) async def cmd_help(self, message: Message): """Обработчик команды /help""" if not self._is_admin(message.from_user.id): return text = ( "📖 Справка\n\n" "Кнопки меню:\n" "📊 Статистика\n" "👥 Сессии\n" "📝 Ожидающие\n" "📋 Группы\n" "➕ Добавить группу\n" "❓ Помощь\n\n" "Команды:\n" "/start - Главное меню\n" "/stats - Статистика\n" "/pending - Ожидающие\n" "/groups - Группы\n" "/add_group ID - Добавить группу\n" "/help - Справка" ) await message.answer(text, reply_markup=create_main_keyboard()) async def handle_text_message(self, message: Message): """Обработчик текстовых сообщений (кнопки меню)""" if not self._is_admin(message.from_user.id): return text = message.text.strip() if text == "📊 Статистика": await self.cmd_stats(message) elif text == "👥 Сессии": await self.cmd_sessions(message) elif text == "📝 Ожидающие": await self.cmd_pending(message) elif text == "📋 Группы": await self.cmd_groups(message) elif text == "➕ Добавить группу": await message.answer( "📝 Добавление группы\n\n" "Отправьте ID группы или username:\n\n" "Примеры:\n" "@telegram (публичный канал)\n" "1234567890 (ID)\n" "-1001234567890 (ID канала)" ) # Включаем состояние ожидания self.add_group_state[message.from_user.id] = {'waiting': True} elif text == "❓ Помощь": await self.cmd_help(message) else: # Проверяем, не ждём ли мы ввод if message.from_user.id in self.add_group_state: # Пользователь отправил ID или username await self._add_group_by_input(message, text) del self.add_group_state[message.from_user.id] else: await message.answer( "Неизвестная команда. Используйте меню:", reply_markup=create_main_keyboard() ) async def _add_group_by_username(self, message: Message, username: str): """Добавление группы по username""" # Пробуем вступить всеми сессиями join_results = await session_manager.join_all_sessions(username) # Проверяем результаты all_success = all('✅' in result for result in join_results.values()) if not all_success: results_text = "\n".join([f"{k}: {v}" for k, v in join_results.items()]) await message.answer( f"❌ Ошибка при вступлении в группу {username}\n\n" f"Возможно группа приватная или не существует.\n\n" f"Результаты:\n{results_text}" ) return # Получаем информацию group_name = None try: chat = await self.bot.get_chat(username) group_name = chat.title except: pass # Добавляем в БД (username без @) clean_username = username.lstrip('@') if add_target_group(clean_username, group_name, clean_username): await message.answer( f"✅ Группа {group_name or username} добавлена!\n\n" f"Username: {username}\n" f"Все сессии вступили.\n" f"Бот будет мониторить сообщения." ) else: await message.answer(f"❌ Ошибка при добавлении группы {username}") async def _add_group_by_id(self, message: Message, group_id: int): """Добавление группы по ID с авто-вступлением""" # Конвертируем ID в правильный формат для Telethon # Если ID начинается с 100, добавляем -100 префикс if str(group_id).startswith('100') and not str(group_id).startswith('-'): telethon_id = -int(f"100{group_id}") elif str(group_id).startswith('-100'): telethon_id = int(group_id) else: telethon_id = int(group_id) # Пробуем вступить всеми сессиями join_results = await session_manager.join_all_sessions(telethon_id) # Проверяем результаты all_success = all('✅' in result for result in join_results.values()) if not all_success: results_text = "\n".join([f"{k}: {v}" for k, v in join_results.items()]) await message.answer( f"❌ Ошибка при вступлении в группу {group_id}\n\n" f"Возможно группа приватная.\n\n" f"Результаты:\n{results_text}\n\n" f"Используйте публичные группы/каналы." ) return # Все сессии вступили — получаем информацию через Telethon (не через Bot API) group_name = None group_username = None try: # Используем сессию для получения информации clients = session_manager.clients if clients: first_client = list(clients.values())[0] entity = await first_client.get_entity(telethon_id) group_name = getattr(entity, 'title', None) group_username = getattr(entity, 'username', None) except Exception as e: logger.warning(f"Не удалось получить информацию о группе {group_id}: {e}") # Добавляем группу в БД if add_target_group(telethon_id, group_name, group_username): username_text = f"@{group_username}" if group_username else "Нет" await message.answer( f"✅ Группа {group_name or group_id} добавлена!\n\n" f"ID: {group_id}\n" f"Username: {username_text}\n" f"Все сессии вступили в группу.\n" f"Теперь бот будет мониторить сообщения." ) else: await message.answer(f"❌ Ошибка при добавлении группы {group_id}") async def cmd_groups(self, message: Message): """Обработчик команды /groups""" if not self._is_admin(message.from_user.id): return groups = get_all_target_groups() if not groups: await message.answer( "📋 **Нет добавленных групп**\n\n" "Добавьте группу командой:\n" "`/add_group ID_группы`\n\n" "Или перешлите сообщение из канала, который хотите добавить.") return text = "📋 **Целевые группы**\n\n" for group in groups: status = "🟢" if group['is_active'] else "🔴" name = group['group_name'] or f"Группа {group['group_id']}" text += f"{status} `{name}` (ID: `{group['group_id']}`)\n" await message.answer( text, reply_markup=create_groups_list_keyboard(groups) ) async def cmd_add_group(self, message: Message): """Обработчик команды /add_group""" if not self._is_admin(message.from_user.id): return # Проверяем, есть ли аргументы (ID группы или username) args = message.text.split() if len(args) > 1: # ID или username передан в команде group_input = args[1] await self._add_group_by_input(message, group_input) else: # Нет аргументов - просим отправить ID await message.answer( "📝 Добавление группы\n\n" "Отправьте ID группы или username:\n\n" "Примеры:\n" "@telegram (публичный канал)\n" "1234567890 (ID)\n" "-1001234567890 (ID канала)" ) self.add_group_state[message.from_user.id] = {'waiting': True} async def _add_group_by_input(self, message: Message, group_input: str): """Добавление группы по ID или username""" # Определяем формат ввода if group_input.startswith('@'): # Username - используем как есть await self._add_group_by_username(message, group_input) else: # Числовой ID try: group_id = int(group_input) await self._add_group_by_id(message, group_id) except ValueError: await message.answer( "❌ Неверный формат.\n\n" "Примеры:\n" "@telegram (username)\n" "1234567890 (ID)\n" "-1001234567890 (ID канала)" ) async def handle_new_post(self, message: Message): """Обработка новых постов (если бот добавлен в канал)""" # Эта функция для будущего расширения # Сейчас посты обрабатываются через scan_previous_messages pass async def handle_callback(self, callback: CallbackQuery): """Обработчик callback-запросов""" if not self._is_admin(callback.from_user.id): await callback.answer("❌ Нет доступа", show_alert=True) return data = callback.data logger.info(f"Callback: {data}") try: if data == "stats": await self._callback_stats(callback) elif data == "sessions": await self._callback_sessions_list(callback) elif data.startswith("session_select:"): session_file = data.split(":")[1] await self._callback_session_pending(callback, session_file) elif data == "pending": await self._callback_pending_groups(callback) elif data.startswith("group_pending:"): group_id = data.split(":")[1] await self._callback_group_pending(callback, group_id) elif data == "groups": await self._callback_groups(callback) elif data == "group_add": await self._callback_group_add(callback) elif data.startswith("group_info:"): await self._callback_group_info(callback, data) elif data.startswith("group_pause:"): await self._callback_group_action(callback, "pause", data) elif data.startswith("group_resume:"): await self._callback_group_action(callback, "resume", data) elif data.startswith("group_delete:"): await self._callback_group_action(callback, "delete", data) elif data.startswith("approve:"): await self._callback_approve(callback) elif data.startswith("reject:"): await self._callback_reject(callback) elif data.startswith("regenerate:"): await self._callback_regenerate(callback) elif data.startswith("edit:"): await self._callback_edit(callback) elif data.startswith("edit_cancel:"): await self._callback_edit_cancel(callback) elif data.startswith("session_status:"): await self._callback_session_action(callback, "status", data) elif data.startswith("session_pause:"): await self._callback_session_action(callback, "pause", data) elif data.startswith("session_resume:"): await self._callback_session_action(callback, "resume", data) elif data.startswith("session_delete:"): await self._callback_session_action(callback, "delete", data) elif data == "main_menu": await self._callback_main_menu(callback) else: await callback.answer("Неизвестная команда") except Exception as e: logger.error(f"Ошибка при обработке callback {data}: {e}") await callback.answer(f"❌ Ошибка: {e}", show_alert=True) async def _callback_stats(self, callback: CallbackQuery): """Callback для статистики""" summary = get_summary_stats() text = ( "📊 **Статистика**\n\n" f"📝 Всего: `{summary.get('total_comments', 0)}`\n" f"⏳ Ожидают: `{summary.get('pending', 0)}`\n" f"✅ Одобрено: `{summary.get('approved', 0)}`\n" f"❌ Отклонено: `{summary.get('rejected', 0)}`\n" f"📤 Отправлено: `{summary.get('sent', 0)}`" ) await callback.message.edit_text( text, reply_markup=create_main_menu() ) await callback.answer() async def _callback_sessions(self, callback: CallbackQuery): """Callback для сессий""" sessions = get_all_sessions() if not sessions: text = "❌ Нет сессий\n\nДобавьте файлы в `sessions/`" else: text = "👥 **Сессии**\n\n" for session in sessions: status = "🟢" if session['is_active'] else "🔴" username = f"@{session['username']}" if session['username'] else "Без username" text += f"{status} `{session['session_file']}` — {username}\n" await callback.message.edit_text(text) await callback.answer() async def _callback_pending(self, callback: CallbackQuery): """Callback для ожидающих комментариев""" pending = get_pending_comments() if not pending: await callback.answer("✅ Нет ожидающих", show_alert=True) return await callback.message.edit_text(f"📝 Ожидают: {len(pending)}") for comment in pending[:10]: await self._send_moderation_message( chat_id=callback.message.chat.id, comment=comment ) await callback.answer() async def _callback_sessions_list(self, callback: CallbackQuery): """Callback для списка сессий""" sessions = get_all_sessions() if not sessions: text = "❌ Нет сессий\n\nДобавьте файлы сессий в `sessions/`" await callback.message.edit_text(text, reply_markup=create_back_keyboard()) else: text = "👥 **Сессии**\n\nВыберите сессию для просмотра комментариев:\n\n" for session in sessions: status = "🟢" if session['is_active'] else "🔴" username = f"@{session['username']}" if session.get('username') else "" text += f"{status} `{session['session_file']}` {username}\n" await callback.message.edit_text( text, parse_mode="Markdown", reply_markup=create_sessions_list_keyboard(sessions) ) await callback.answer() async def _callback_session_pending(self, callback: CallbackQuery, session_file: str): """Callback для просмотра pending комментариев сессии""" pending = get_pending_comments_by_session(session_file) if not pending: await callback.message.edit_text( f"✅ Нет ожидающих комментариев для `{session_file}`", reply_markup=create_back_keyboard() ) else: await callback.message.edit_text( f"📝 Ожидают модерации ({len(pending)}):\n\nСессия: `{session_file}`", reply_markup=create_back_keyboard() ) for comment in pending[:10]: await self._send_moderation_message( chat_id=callback.message.chat.id, comment=comment ) await callback.answer() async def _callback_pending_groups(self, callback: CallbackQuery): """Callback для выбора группы из pending""" groups = get_all_target_groups() if not groups: text = "📋 **Нет групп**\n\nДобавьте группу через /add_group" await callback.message.edit_text(text, parse_mode="Markdown", reply_markup=create_back_keyboard()) else: text = "📋 **Группы**\n\nВыберите группу для просмотра комментариев:\n\n" for group in groups: name = group['group_name'] or f"Группа {group['group_id']}" text += f"📢 `{name}`\n" await callback.message.edit_text( text, parse_mode="Markdown", reply_markup=create_groups_list_for_pending_keyboard(groups) ) await callback.answer() async def _callback_group_pending(self, callback: CallbackQuery, group_id: str): """Callback для просмотра pending комментариев группы""" pending = get_pending_comments_by_group(group_id) if not pending: await callback.message.edit_text( f"✅ Нет ожидающих комментариев для этой группы", reply_markup=create_back_keyboard() ) else: await callback.message.edit_text( f"📝 Ожидают модерации ({len(pending)}):\n\nГруппа: `{group_id}`", reply_markup=create_back_keyboard() ) for comment in pending[:10]: await self._send_moderation_message( chat_id=callback.message.chat.id, comment=comment ) await callback.answer() async def _callback_main_menu(self, callback: CallbackQuery): """Callback для возврата в главное меню""" await self.cmd_start(callback.message) await callback.answer() async def _callback_settings(self, callback: CallbackQuery): """Callback для настроек""" text = ( "⚙️ **Настройки**\n\n" f"Log Group: `{LOG_GROUP_ID}`\n" f"Scan Limit: `{INITIAL_SCAN_LIMIT}`" ) await callback.message.edit_text(text) await callback.answer() async def _callback_groups(self, callback: CallbackQuery): """Callback для списка групп""" groups = get_all_target_groups() if not groups: text = "📋 **Нет добавленных групп**\n\n" text += "Добавьте группу командой `/add_group ID`" else: text = "📋 **Целевые группы**\n\n" for group in groups: status = "🟢" if group['is_active'] else "🔴" name = group['group_name'] or f"Группа {group['group_id']}" username = f"@{group['group_username']}" if group['group_username'] else "" text += f"{status} **{name}** {username}\n(ID: `{group['group_id']}`)\n" await callback.message.edit_text( text, reply_markup=create_groups_list_keyboard(groups) ) await callback.answer() async def _callback_group_add(self, callback: CallbackQuery): """Callback для добавления группы""" await callback.message.answer( "📝 **Добавление группы**\n\n" "Отправьте ID группы или перешлите сообщение из канала.\n\n" "Пример: `/add_group 123456789`") await callback.answer() async def _callback_group_action(self, callback: CallbackQuery): """Callback для действий с группой""" data = callback.data parts = data.split(":") if len(parts) < 2: await callback.answer("❌ Ошибка данных", show_alert=True) return action = parts[0] group_id = parts[1] # Может быть username или ID if action == "group_info": # Показываем информацию о группе groups = get_all_target_groups() group = next((g for g in groups if str(g['group_id']) == str(group_id)), None) if group: # Экранируем символы для Markdown name = (group['group_name'] or 'Нет').replace('_', ' ') username = group['group_username'] or 'Нет' status = '🟢 Активна' if group['is_active'] else '🔴 Неактивна' text = ( f"📋 Группа\n\n" f"ID: {group['group_id']}\n" f"Название: {name}\n" f"Username: @{username}\n" f"Статус: {status}" ) await callback.message.edit_text( text, reply_markup=create_group_action_keyboard(group['group_id']) ) else: await callback.answer("❌ Группа не найдена", show_alert=True) elif action == "group_pause": toggle_target_group(group_id, False) await callback.message.answer(f"⏸️ Группа {group_id} приостановлена") await self._callback_groups(callback) elif action == "group_resume": toggle_target_group(group_id, True) await callback.message.answer(f"▶️ Группа {group_id} активирована") await self._callback_groups(callback) elif action == "group_delete": # Получаем информацию о группе перед удалением groups = get_all_target_groups() group = next((g for g in groups if str(g['group_id']) == str(group_id)), None) if group: group_db_id = group['group_id'] # ID как записан в БД comments_group_id = group.get('comments_group_id') # Выходим из группы во всех сессиях try: leave_results = await session_manager.leave_all_sessions(group_db_id) logger.info(f"Выход из группы {group_db_id}: {leave_results}") except Exception as e: logger.warning(f"Не удалось выйти из группы {group_db_id}: {e}") # Удаляем все комментарии этой группы из БД conn = None try: conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() # Собираем все возможные chat_id для этой группы chat_ids_to_delete = set() # Добавляем group_id в разных форматах chat_ids_to_delete.add(str(group_db_id)) try: chat_ids_to_delete.add(str(abs(int(group_db_id)))) chat_ids_to_delete.add(str(-abs(int(group_db_id)))) except (ValueError, TypeError): pass # group_id не числовое (username) # Добавляем comments_group_id если есть if comments_group_id: chat_ids_to_delete.add(str(comments_group_id)) try: chat_ids_to_delete.add(str(abs(int(comments_group_id)))) chat_ids_to_delete.add(str(-abs(int(comments_group_id)))) except (ValueError, TypeError): pass # Удаляем комментарии по всем chat_id for chat_id in chat_ids_to_delete: try: cursor.execute('DELETE FROM comments WHERE chat_id = ?', (chat_id,)) deleted = cursor.rowcount if deleted > 0: logger.info(f"Удалено {deleted} комментариев с chat_id={chat_id}") except Exception as e: logger.debug(f"Ошибка при удалении chat_id={chat_id}: {e}") conn.commit() logger.info(f"Удалено комментариев для группы {group_db_id}") except Exception as e: logger.error(f"Ошибка при удалении комментариев: {e}") finally: if conn: conn.close() # Удаляем группу из БД remove_target_group(group['group_id']) await callback.message.answer(f"🗑️ Группа {group['group_id']} удалена") await self._callback_groups(callback) await callback.answer() async def _send_moderation_message(self, chat_id: int, comment: dict): """Отправка сообщения на модерацию""" # Используем channel_id из БД (сохранён при генерации) channel_id = comment.get('channel_id') or comment['chat_id'] post_url = f"https://t.me/c/{channel_id}/{comment['message_id']}" # Получаем название группы/канала из БД # Ищем по chat_id (группа комментариев) или по channel_id (канал) groups = get_all_target_groups() group = None # Сначала ищем по chat_id (группа комментариев) for g in groups: if str(g['group_id']) == str(comment['chat_id']): group = g break # Или по channel_id (канал) if str(g.get('group_id')) == str(channel_id): group = g break # Или по comments_group_id if str(g.get('comments_group_id')) == str(comment['chat_id']): group = g break group_name = group['group_name'] if group and group.get('group_name') else f"Канал {channel_id}" session_info = "" if comment.get('session_file'): session_info = f"👤 Сессия: `{comment['session_file']}`\n\n" post_text = comment.get('post_text', 'Текст поста не сохранён') if len(post_text) > 300: post_text = post_text[:300] + "..." text = ( f"📝 Новый комментарий\n\n" f"📢 Канал: **{group_name}**\n" f"🔗 Пост: {post_url}\n" f"{session_info}" f"📄 Текст поста:\n" f"_{post_text}_\n\n" f"💬 Комментарий:\n" f"_{comment['comment_text'][:500]}_{'...' if len(comment['comment_text']) > 500 else ''}\n\n" f"🔄 Регенераций: {comment.get('regenerations', 0)}" ) keyboard = create_moderation_keyboard( comment['id'], comment['message_id'], comment['chat_id'] ) await self.bot.send_message( chat_id=chat_id, text=text, reply_markup=keyboard ) async def _callback_approve(self, callback: CallbackQuery): """Одобрение комментария""" comment_id = self._parse_callback_data(callback.data) if not comment_id: await callback.answer("❌ Ошибка данных", show_alert=True) return # Обновляем статус update_comment_status(comment_id, 'approved') # Обновляем сообщение await callback.message.edit_text( callback.message.text + "\n\n✅ Одобрено") # Обновляем статистику update_stats(datetime.now().strftime('%Y-%m-%d'), '', 'approved') await callback.answer("✅ Одобрено") async def _callback_reject(self, callback: CallbackQuery): """Отклонение комментария""" comment_id = self._parse_callback_data(callback.data) if not comment_id: await callback.answer("❌ Ошибка данных", show_alert=True) return update_comment_status(comment_id, 'rejected') await callback.message.edit_text( callback.message.text + "\n\n❌ Отклонено") update_stats(datetime.now().strftime('%Y-%m-%d'), '', 'rejected') await callback.answer("❌ Отклонено") def _get_post_url(self, comment: dict) -> str: """Получение URL поста""" channel_id = comment.get('channel_id') or comment['chat_id'] return f"https://t.me/c/{channel_id}/{comment['message_id']}" async def _callback_regenerate(self, callback: CallbackQuery): """Регенерация комментария""" comment_id = self._parse_callback_data(callback.data) if not comment_id: await callback.answer("❌ Ошибка данных", show_alert=True) return # Получаем комментарий из БД conn = None try: conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute('SELECT * FROM comments WHERE id = ?', (comment_id,)) comment = dict(cursor.fetchone()) finally: if conn: conn.close() if not comment: await callback.answer("❌ Комментарий не найден", show_alert=True) return # Проверяем лимит регенераций if comment.get('regenerations', 0) >= 3: await callback.answer("⚠️ Достигнут лимит регенераций", show_alert=True) return # Генерируем новый комментарий post_text = comment.get('post_text', '') if not post_text: post_text = comment['comment_text'][:200] + "..." new_comment = await generate_comment(post_text) if not new_comment: await callback.answer("❌ Ошибка генерации", show_alert=True) return # Сохраняем новый комментарий save_comment( comment['message_id'], comment['chat_id'], new_comment, comment.get('session_file'), comment.get('post_text'), comment.get('channel_id') ) increment_regeneration(comment_id) update_comment_status(comment_id, 'pending') # Обновляем сообщение post_url = self._get_post_url(comment) post_text = comment.get('post_text', 'Текст поста не сохранён') if len(post_text) > 300: post_text = post_text[:300] + "..." text = ( f"📝 Новый комментарий\n\n" f"🔗 Пост: {post_url}\n" f"📄 Текст поста:\n" f"_{post_text}_\n\n" f"💬 Комментарий:\n" f"_{new_comment[:500]}_{'...' if len(new_comment) > 500 else ''}\n\n" f"🔄 Регенераций: {comment.get('regenerations', 0) + 1}" ) keyboard = create_moderation_keyboard( comment_id, comment['message_id'], comment['chat_id'] ) await callback.message.edit_text(text, reply_markup=keyboard) await callback.answer("🔄 Сгенерирован новый вариант") async def _callback_edit(self, callback: CallbackQuery): """Редактирование комментария""" comment_id = self._parse_callback_data(callback.data) if not comment_id: await callback.answer("❌ Ошибка данных", show_alert=True) return # Сохраняем состояние редактирования self.editing_comments[callback.message.chat.id] = { 'comment_id': comment_id, 'mod_message_id': callback.message.message_id } keyboard = create_edit_cancel_keyboard(comment_id) await callback.message.edit_text( callback.message.text + "\n\n✏️ **Отправьте новый текст комментария**", reply_markup=keyboard ) await callback.answer("✏️ Отправьте новый текст") async def _callback_edit_cancel(self, callback: CallbackQuery): """Отмена редактирования""" parts = callback.data.split(':') comment_id = int(parts[1]) # Получаем оригинальный комментарий conn = None try: conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute('SELECT * FROM comments WHERE id = ?', (comment_id,)) comment = dict(cursor.fetchone()) finally: if conn: conn.close() if comment: post_url = self._get_post_url(comment) text = ( f"📝 Новый комментарий\n\n" f"🔗 Пост: {post_url}\n" f"💬 Текст:\n" f"_{comment['comment_text'][:500]}_" ) keyboard = create_moderation_keyboard( comment_id, comment['message_id'], comment['chat_id'] ) await callback.message.edit_text(text, reply_markup=keyboard) # Удаляем из editing_comments if callback.message.chat.id in self.editing_comments: del self.editing_comments[callback.message.chat.id] await callback.answer("✏️ Редактирование отменено") async def _callback_session_action(self, callback: CallbackQuery): """Действия с сессией""" action, session_file = callback.data.split(':', 1) if action == "session_status": session = next((s for s in get_all_sessions() if s['session_file'] == session_file), None) if session: status = "🟢 Активна" if session['is_active'] else "🔴 Неактивна" await callback.answer(f"{status}", show_alert=True) elif action == "session_pause": toggle_session(session_file, False) await callback.answer("⏸️ Сессия приостановлена") elif action == "session_resume": toggle_session(session_file, True) await callback.answer("▶️ Сессия активирована") elif action == "session_delete": delete_session(session_file) await callback.message.edit_text("🗑️ Сессия удалена") await callback.answer() def _parse_callback_data(self, data: str) -> int: """Разбор callback данных (возвращает только comment_id)""" parts = data.split(':')[1:] # Пропускаем действие if len(parts) >= 1: try: return int(parts[0]) except (ValueError, OverflowError): return None return None def _is_admin(self, user_id: int) -> bool: """Проверка прав администратора""" return user_id in ADMIN_IDS or not ADMIN_IDS async def start(self): """Запуск бота""" logger.info("Запуск Bot Controller...") # Инициализация БД init_db() # Загрузка сессий await session_manager.create_all_clients() # Запуск polling await self.dp.start_polling(self.bot) async def stop(self): """Остановка бота""" logger.info("Остановка Bot Controller...") await session_manager.disconnect_all() await self.bot.close() async def main(): """Точка входа""" bot = CommentBot() try: await bot.start() except KeyboardInterrupt: await bot.stop() if __name__ == '__main__': asyncio.run(main())