Initial commit: Batch Bot - Telegram Comment Bot

0.0.1
Features:
- Multi-account support via session files
- AI comments generation via Ollama (local LLM)
- Telegram bot for moderation (approve/reject/regenerate)
- Docker support (controller + worker)
- Auto-join public groups
- Comment regeneration on group re-add
- Statistics tracking

Tech stack:
- Python 3.11
- Telethon 1.34 (Telegram user client)
- Aiogram 3.4 (Telegram bot framework)
- SQLite (Database)
- Docker & Docker Compose
- Ollama (Local LLM)
This commit is contained in:
2026-02-24 04:40:07 +03:00
commit a18ad30961
20 changed files with 3431 additions and 0 deletions

909
bot/controller.py Normal file
View File

@@ -0,0 +1,909 @@
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
)
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(callback)
elif data == "pending":
await self._callback_pending(callback)
elif data == "groups":
await self._callback_groups(callback)
elif data == "group_add":
await self._callback_group_add(callback)
elif data.startswith("group_"):
await self._callback_group_action(callback)
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_"):
await self._callback_session_action(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_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']}"
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"🔗 Пост: {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())