Final release: Multi-session comment bot with filtering

Features:
- Multi-account support (session files)
- AI comments via Ollama
- Telegram bot moderation
- Filter by sessions and groups
- Docker support
- Auto-join groups
- Log notifications
- DB migration script

Bug fixes:
- Fixed comment_to for proper post targeting
- Fixed entity lookup with multiple ID formats
- Fixed callback handlers for filtering
- Added auto-join before entity lookup
This commit is contained in:
2026-02-28 01:44:40 +03:00
parent a18ad30961
commit bb27161524
8 changed files with 537 additions and 99 deletions

View File

@@ -12,7 +12,8 @@ from bot.db import (
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
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 (
@@ -381,15 +382,27 @@ class CommentBot:
if data == "stats":
await self._callback_stats(callback)
elif data == "sessions":
await self._callback_sessions(callback)
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(callback)
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_"):
await self._callback_group_action(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:"):
@@ -400,8 +413,16 @@ class CommentBot:
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)
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("Неизвестная команда")
@@ -462,6 +483,99 @@ class CommentBot:
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 = (
@@ -622,16 +736,38 @@ class CommentBot:
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"📝 Новый комментарий\n\n"
f"📢 Канал: **{group_name}**\n"
f"🔗 Пост: {post_url}\n"
f"{session_info}"
f"📄 Текст поста:\n"
@@ -640,13 +776,13 @@ class CommentBot:
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,