""" Автоматическая отправка комментариев под постами канала (через discussion group) + меню настройки (FSM) + полная диагностика + ДИНАМИЧЕСКИЕ КАНАЛЫ ИЗ БД (без .env!) ВАЖНО: - Комментарии в Telegram — это reply в привязанной группе обсуждений. - Поэтому ловим auto-forward сообщения в группе: Message.is_automatic_forward == True. """ from __future__ import annotations import time from typing import Optional, Tuple, Dict, Any, List from aiogram import Router, F, Bot from aiogram.types import Message, CallbackQuery from aiogram.filters import Command from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.utils.markdown import hide_link from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from configs import settings, COMMANDS from database import get_manager, AutoComment from middleware.loggers import logger from bot.filters.admin import IsAdmin from bot.utils import log_action, tg_emoji __all__ = ("router",) router: Router = Router(name="channel_comments_router") # ====================================================================== # FSM STATES # ====================================================================== class CommentEditStates(StatesGroup): """Состояния для редактирования комментариев""" selecting_channel = State() waiting_text = State() waiting_button_text = State() waiting_button_url = State() waiting_photo_url = State() waiting_add_channel = State() # ✅ ДОБАВИЛИ # ====================================================================== # HELPERS # ====================================================================== def _defaults() -> dict: return { "text": settings.AUTO_COMMENT_TEXT, "button_text": settings.AUTO_COMMENT_BUTTON_TEXT, "button_url": settings.AUTO_COMMENT_BUTTON_URL, "photo_url": settings.AUTO_COMMENT_PHOTO_URL, "is_enabled": False, } def _render_menu_text(channel_id: int, config: dict) -> str: status_emoji = "✅ Включено" if config.get("is_enabled") else "❌ Выключено" text = config.get("text") or "" photo_url = config.get("photo_url") or "" text_preview = (text[:100] + "...") if len(text) > 100 else text photo_preview = (photo_url[:60] + "...") if len(photo_url) > 60 else photo_url return ( f"⚙️ НАСТРОЙКА АВТОКОММЕНТАРИЕВ\n\n" f"📢 Канал: {channel_id}\n" f"🔘 Статус: {status_emoji}\n\n" f"📝 Текст:\n{text_preview or '(пусто)'}\n\n" f"🔘 Кнопка: {config.get('button_text') or '(нет)'}\n" f"🔗 URL: {config.get('button_url') or ''}\n\n" f"🖼 Фото:\n{photo_preview}\n\n" f"💡 Выберите действие:" ) def create_main_menu(channel_id: int) -> InlineKeyboardBuilder: """Создаёт главное меню управления автокомментариями""" ikb = InlineKeyboardBuilder() ikb.button(text="📝 Текст комментария", callback_data=f"edit:{channel_id}:text") ikb.button(text="🔘 Кнопка", callback_data=f"edit:{channel_id}:button") ikb.button(text="🖼 Фото (скрытое)", callback_data=f"edit:{channel_id}:photo") ikb.button(text="👁 Предпросмотр", callback_data=f"edit:{channel_id}:preview") ikb.button(text="🔄 Переключить", callback_data=f"edit:{channel_id}:toggle") ikb.button(text="🔍 Диагностика", callback_data=f"edit:{channel_id}:diagnostic") ikb.button(text="➕ Добавить канал", callback_data="add_channel") # ✅ ДОБАВИЛИ ikb.button(text="🗑 Удалить", callback_data=f"edit:{channel_id}:delete") ikb.button(text="❌ Закрыть", callback_data="menu:close") ikb.adjust(2, 2, 2, 2, 1) return ikb def create_channels_menu(channels: List[int]) -> InlineKeyboardBuilder(): # ✅ List[int] """Создаёт меню выбора канала""" ikb = InlineKeyboardBuilder() for channel_id in channels: ikb.button(text=f"Канал {channel_id}", callback_data=f"select_channel:{channel_id}") ikb.button(text="➕ Добавить канал", callback_data="add_channel") # ✅ ДОБАВИЛИ ikb.button(text="❌ Закрыть", callback_data="menu:close") ikb.adjust(1) return ikb async def get_all_channels() -> List[int]: # ✅ ✅ ✅ ИСПРАВЛЕНО: async! """Получает ВСЕ каналы из БД""" manager = get_manager() return await manager.get_auto_comment_channels() def _build_comment_payload(config: dict) -> Tuple[str, InlineKeyboardBuilder]: photo_url = (config.get("photo_url") or "").strip() text = config.get("text") or "" full_text = (hide_link(photo_url) if photo_url else "") + text keyboard = InlineKeyboardBuilder() if config.get("button_text") and config.get("button_url"): keyboard.button(text=config["button_text"], url=config["button_url"]) return full_text, keyboard def _extract_origin_channel_id(message: Message) -> Optional[int]: if not message.is_automatic_forward: return None if message.forward_from_chat and message.forward_from_chat.type == "channel": return message.forward_from_chat.id return None # Дедуп: чтобы не комментировать каждый элемент альбома (media_group_id) _MEDIA_GROUP_SEEN: Dict[tuple[int, str], float] = {} _MEDIA_GROUP_TTL_SEC = 45.0 def _media_group_should_skip(message: Message) -> bool: if not message.media_group_id: return False now = time.time() key = (message.chat.id, str(message.media_group_id)) last = _MEDIA_GROUP_SEEN.get(key) if len(_MEDIA_GROUP_SEEN) > 500: cutoff = now - _MEDIA_GROUP_TTL_SEC for k, t in list(_MEDIA_GROUP_SEEN.items()): if t < cutoff: _MEDIA_GROUP_SEEN.pop(k, None) if last and (now - last) < _MEDIA_GROUP_TTL_SEC: return True _MEDIA_GROUP_SEEN[key] = now return False async def get_channel_config(channel_id: int) -> dict: """ Получает настройки автокомментариев для канала из БД. Ничего "не затирает": если поля отсутствуют — подставляет дефолты. """ manager = get_manager() config = await manager.get_auto_comment_settings(channel_id) or {} merged = _defaults() merged.update({k: v for k, v in config.items() if v is not None}) if "is_enabled" not in config: merged["is_enabled"] = False return merged async def _persist_settings_preserve_enabled( channel_id: int, patch: dict, updated_by: int, ) -> bool: """ Надёжное сохранение настроек: - всегда делает "первичное сохранение" через save_auto_comment_settings (чтобы запись точно появилась) - сохраняет старый is_enabled (если было выключено — выключаем обратно после сохранения) """ manager = get_manager() raw = await manager.get_auto_comment_settings(channel_id) or {} was_enabled = bool(raw.get("is_enabled", False)) merged = _defaults() merged.update({k: v for k, v in raw.items() if v is not None}) merged.update({k: v for k, v in patch.items() if v is not None}) # save_auto_comment_settings у тебя уже используется при включении (значит умеет создавать запись) success = await manager.save_auto_comment_settings( channel_id=channel_id, text=merged.get("text") or "", button_text=merged.get("button_text") or "", button_url=merged.get("button_url") or "", photo_url=merged.get("photo_url") or "", updated_by=updated_by, ) if not success: return False # Если было выключено — сохраняем выключенным (на случай если save_* включает фичу) if not was_enabled: try: await manager.repo.toggle_auto_comment( channel_id=channel_id, is_enabled=False, updated_by=updated_by, ) except Exception as e: logger.warning(f"toggle_auto_comment failed (preserve disabled): {e}", log_type="CHANNEL") return True # ====================================================================== # CORE: AUTO COMMENTS (discussion group) ✅ ФИКС #3 # ====================================================================== @router.message(F.is_automatic_forward) async def auto_comment_from_discussion_forward(message: Message) -> None: if _media_group_should_skip(message): logger.debug( f"⏭ Skip media_group duplicate: chat={message.chat.id} media_group_id={message.media_group_id}", log_type="CHANNEL" ) return logger.info( f"📥 Discussion forward received: chat={message.chat.id}, msg_id={message.message_id}, " f"is_auto={message.is_automatic_forward}, forward_from_chat={getattr(message.forward_from_chat, 'id', None)}", log_type="CHANNEL" ) channel_id = _extract_origin_channel_id(message) if not channel_id: logger.warning( f"❌ Cannot extract origin channel id for msg={message.message_id} in chat={message.chat.id}", log_type="CHANNEL" ) return channels = await get_all_channels() # ✅ ✅ ✅ ФИКС #3: await! if not channels: return if channel_id not in channels: return is_test = False txt = message.text or message.caption or "" if "/test_comment" in txt: is_test = True try: config = await get_channel_config(channel_id) except Exception as e: logger.error(f"❌ Config load failed for channel={channel_id}: {e}", log_type="CHANNEL") return if not config.get("is_enabled") and not is_test: logger.debug(f"⏭ Auto-comments disabled for channel={channel_id}", log_type="CHANNEL") return try: full_text, keyboard = _build_comment_payload(config) sent = await message.reply( text=full_text, reply_markup=keyboard.as_markup(), parse_mode="HTML", ) logger.success( f"✅ Comment sent (discussion reply)\n" f" ├─ Origin channel: {channel_id}\n" f" ├─ Discussion chat: {message.chat.id}\n" f" ├─ Forward msg id: {message.message_id}\n" f" ├─ Comment msg id: {sent.message_id}\n" f" └─ Test mode: {is_test}", log_type="CHANNEL" ) except TelegramBadRequest as e: logger.error( f"❌ TelegramBadRequest while sending comment: {e}\n" f" channel={channel_id} discussion_chat={message.chat.id} msg={message.message_id}", log_type="CHANNEL" ) except TelegramForbiddenError as e: logger.error( f"❌ TelegramForbiddenError while sending comment: {e}\n" f" Bot likely has no rights to write in discussion group.\n" f" channel={channel_id} discussion_chat={message.chat.id}", log_type="CHANNEL" ) except Exception as e: logger.error(f"❌ Unexpected error while sending comment: {e}", log_type="CHANNEL") # ====================================================================== # ✅ НОВЫЕ ХЕНДЛЕРЫ ДЛЯ ДОБАВЛЕНИЯ КАНАЛА # ====================================================================== @router.callback_query(F.data == "add_channel", IsAdmin()) async def add_channel_callback(callback: CallbackQuery, state: FSMContext) -> None: await state.update_data(action="add_channel") await state.set_state(CommentEditStates.waiting_add_channel) await callback.message.edit_text( text=( "➕ ДОБАВИТЬ КАНАЛ\n\n" "Отправьте ID канала (число с минусом):\n" "Пример: -1003876862007\n\n" "💡 @userinfobot для получения ID\n\n" "Для отмены: /cancel" ), parse_mode="HTML" ) await callback.answer() @router.message(CommentEditStates.waiting_add_channel, IsAdmin()) async def process_add_channel(message: Message, state: FSMContext) -> None: if message.text == "/cancel": await state.clear() await message.answer("❌ Отменено") return try: channel_id = int(message.text.strip()) if not str(channel_id).startswith('-'): raise ValueError() except ValueError: await message.answer("❌ Неверный ID. Пример: -1003876862007", parse_mode="HTML") return manager = get_manager() success = await manager.add_auto_comment_channel(channel_id, message.from_user.id) await state.clear() if success: await message.answer(f"✅ Канал добавлен!\n{channel_id}\n/redactcomment", parse_mode="HTML") else: await message.answer(f"❌ Канал {channel_id} уже существует!", parse_mode="HTML") # ====================================================================== # DIAGNOSTICS ✅ ФИКС #2 # ====================================================================== @router.callback_query(F.data.regexp(r"edit:(-?\d+):diagnostic")) async def diagnostic_channel_callback(callback: CallbackQuery) -> None: channel_id = int(callback.data.split(":")[1]) bot: Bot = callback.bot await callback.answer("🔍 Запуск диагностики...", show_alert=False) diagnostic_text = "🔍 ДИАГНОСТИКА АВТОКОММЕНТАРИЕВ\n\n" channels = await get_all_channels() # ✅ ✅ ✅ ФИКС #2: await! diagnostic_text += "1️⃣ Настройки:\n" diagnostic_text += f" ├─ Каналы из БД: {channels}\n" diagnostic_text += f" └─ Канал в списке: {'✅' if channel_id in channels else '❌'}\n\n" diagnostic_text += "2️⃣ База данных:\n" try: config = await get_channel_config(channel_id) diagnostic_text += " ├─ Настройки читаются: ✅\n" diagnostic_text += f" ├─ Статус: {'✅ Включено' if config.get('is_enabled') else '❌ Выключено'}\n" diagnostic_text += f" ├─ Текст: {len(config.get('text') or '')} символов\n" diagnostic_text += f" ├─ Кнопка: {('✅' if (config.get('button_text') and config.get('button_url')) else '❌')}\n" diagnostic_text += f" └─ Фото URL: {('✅' if bool(config.get('photo_url')) else '❌')}\n\n" except Exception as e: diagnostic_text += f" └─ ❌ Ошибка: {e}\n\n" config = None diagnostic_text += "3️⃣ Бот в канале:\n" try: member = await bot.get_chat_member(channel_id, bot.id) diagnostic_text += f" ├─ Статус: {member.status}\n" if member.status == "administrator": diagnostic_text += " ├─ Админ: ✅\n" if hasattr(member, "can_post_messages"): diagnostic_text += f" └─ can_post_messages: {'✅' if member.can_post_messages else '❌'}\n" else: diagnostic_text += " └─ can_post_messages: (нет поля у этого типа)\n" elif member.status == "creator": diagnostic_text += " └─ Создатель: ✅\n" else: diagnostic_text += " └─ НЕ админ: ❌\n" diagnostic_text += "\n" except TelegramForbiddenError: diagnostic_text += " └─ ❌ Бот не в канале / нет доступа\n\n" except Exception as e: diagnostic_text += f" └─ ❌ Ошибка: {e}\n\n" diagnostic_text += "4️⃣ Привязанная группа обсуждений:\n" linked_chat_id = None try: chat = await bot.get_chat(channel_id) linked_chat_id = getattr(chat, "linked_chat_id", None) if linked_chat_id: diagnostic_text += " ├─ linked_chat_id: ✅\n" diagnostic_text += f" └─ ID: {linked_chat_id}\n\n" else: diagnostic_text += " └─ ❌ Не подключена (linked_chat_id отсутствует)\n\n" except Exception as e: diagnostic_text += f" └─ ❌ Ошибка: {e}\n\n" diagnostic_text += "5️⃣ Бот в группе обсуждений:\n" if not linked_chat_id: diagnostic_text += " └─ ⏭ Пропущено (группа не найдена)\n\n" else: try: gmember = await bot.get_chat_member(linked_chat_id, bot.id) diagnostic_text += f" ├─ Статус: {gmember.status}\n" if gmember.status in ("administrator", "creator", "member"): diagnostic_text += " ├─ Присутствует: ✅\n" else: diagnostic_text += " ├─ Присутствует: ❌\n" if hasattr(gmember, "can_send_messages"): diagnostic_text += f" └─ can_send_messages: {'✅' if gmember.can_send_messages else '❌'}\n\n" else: diagnostic_text += " └─ can_send_messages: (нет поля у этого типа)\n\n" except TelegramForbiddenError: diagnostic_text += " └─ ❌ Бот не в группе / нет доступа\n\n" except Exception as e: diagnostic_text += f" └─ ❌ Ошибка: {e}\n\n" diagnostic_text += "💡 Что должно быть для работы:\n" if channel_id not in channels: diagnostic_text += " • Добавьте канал ➕\n" diagnostic_text += ( " • Включите автокомментарии (🔄 Переключить)\n" " • Подключите discussion group к каналу\n" " • Дайте боту право писать в группе обсуждений\n" " • Для теста: отправьте пост в канал или пост с /test_comment\n" ) if callback.message: await callback.message.answer(text=diagnostic_text, parse_mode="HTML") # ====================================================================== # ADMIN UI: COMMAND + MENUS ✅ ФИКС #1 # ====================================================================== @router.callback_query(F.data.casefold() == "redactcomment", IsAdmin()) @router.message(Command(*COMMANDS["redactcomment"], prefix=settings.PREFIX, ignore_case=True), IsAdmin()) @log_action(action_name="START_COMMAND", log_args=True) async def redact_comment_cmd(message: Message, state: FSMContext) -> None: channels = await get_all_channels() # ✅ ✅ ✅ ФИКС #1: await! await state.clear() if not channels: await message.answer( "📢 УПРАВЛЕНИЕ АВТОКОММЕНТАРИЯМИ\n\n" "🚫 Каналы не настроены\n\n" "👆 ➕ Добавить канал", reply_markup=create_channels_menu([]).as_markup(), # ✅ Пустое + кнопка parse_mode="HTML" ) return if len(channels) == 1: await show_channel_menu(message, channels[0]) else: await message.answer( "📢 Выберите канал:", reply_markup=create_channels_menu(channels).as_markup(), parse_mode="HTML" ) async def show_channel_menu(message: Message, channel_id: int) -> None: config = await get_channel_config(channel_id) output = _render_menu_text(channel_id, config) await message.answer( text=output, reply_markup=create_main_menu(channel_id).as_markup(), parse_mode="HTML" ) @router.callback_query(F.data.startswith("select_channel:")) async def select_channel_callback(callback: CallbackQuery, state: FSMContext) -> None: channel_id = int(callback.data.split(":")[1]) await state.clear() config = await get_channel_config(channel_id) output = _render_menu_text(channel_id, config) if callback.message: await callback.message.edit_text( text=output, reply_markup=create_main_menu(channel_id).as_markup(), parse_mode="HTML" ) await callback.answer() # ====================================================================== # EDIT TEXT # ====================================================================== @router.callback_query(F.data.regexp(r"edit:(-?\d+):text"), IsAdmin()) async def edit_text_callback(callback: CallbackQuery, state: FSMContext) -> None: channel_id = int(callback.data.split(":")[1]) await state.update_data(channel_id=channel_id) await state.set_state(CommentEditStates.waiting_text) if callback.message: await callback.message.edit_text( text=( "📝 РЕДАКТИРОВАНИЕ ТЕКСТА\n\n" "Отправьте новый текст комментария.\n\n" "💡 Поддерживается HTML\n\n" "Для отмены: /cancel" ), parse_mode="HTML" ) await callback.answer() @router.message(CommentEditStates.waiting_text, IsAdmin()) async def process_text_input(message: Message, state: FSMContext) -> None: if (message.text or "").strip() == "/cancel": await state.clear() await message.answer("❌ Отменено") return data = await state.get_data() channel_id = data.get("channel_id") if not channel_id: await state.clear() await message.answer("❌ Не выбран канал. Откройте меню заново: /redactcomment") return new_text = message.text or "" try: success = await _persist_settings_preserve_enabled( channel_id=int(channel_id), patch={"text": new_text}, updated_by=message.from_user.id ) except Exception as e: logger.error(f"update text failed: {e}", log_type="CHANNEL") success = False await state.clear() if not success: await message.answer( "❌ Ошибка сохранения\n\nПопробуйте ещё раз через /redactcomment", parse_mode="HTML" ) return await message.answer("✅ Текст обновлён!", parse_mode="HTML") await show_channel_menu(message, int(channel_id)) # ====================================================================== # EDIT BUTTON # ====================================================================== @router.callback_query(F.data.regexp(r"edit:(-?\d+):button"), IsAdmin()) async def edit_button_callback(callback: CallbackQuery, state: FSMContext) -> None: channel_id = int(callback.data.split(":")[1]) await state.update_data(channel_id=channel_id) await state.set_state(CommentEditStates.waiting_button_text) if callback.message: await callback.message.edit_text( text=( "🔘 РЕДАКТИРОВАНИЕ КНОПКИ\n\n" "Шаг 1 из 2: Отправьте текст кнопки\n\n" "Для отмены: /cancel" ), parse_mode="HTML" ) await callback.answer() @router.message(CommentEditStates.waiting_button_text, IsAdmin()) async def process_button_text(message: Message, state: FSMContext) -> None: if (message.text or "").strip() == "/cancel": await state.clear() await message.answer("❌ Отменено") return await state.update_data(button_text=message.text or "") await state.set_state(CommentEditStates.waiting_button_url) await message.answer( text=( f"✅ Текст кнопки: {(message.text or '').strip()}\n\n" f"Шаг 2 из 2: Отправьте URL кнопки\n\n" f"Для отмены: /cancel" ), parse_mode="HTML" ) @router.message(CommentEditStates.waiting_button_url, IsAdmin()) async def process_button_url(message: Message, state: FSMContext) -> None: if (message.text or "").strip() == "/cancel": await state.clear() await message.answer("❌ Отменено") return url = (message.text or "").strip() if not url.startswith(("http://", "https://")): await message.answer( "❌ Неверный формат URL\n\nURL должен начинаться с http:// или https://", parse_mode="HTML" ) return data = await state.get_data() channel_id = data.get("channel_id") button_text = (data.get("button_text") or "").strip() if not channel_id: await state.clear() await message.answer("❌ Не выбран канал. Откройте меню заново: /redactcomment") return try: success = await _persist_settings_preserve_enabled( channel_id=int(channel_id), patch={"button_text": button_text, "button_url": url}, updated_by=message.from_user.id ) except Exception as e: logger.error(f"update button failed: {e}", log_type="CHANNEL") success = False await state.clear() if not success: await message.answer("❌ Ошибка сохранения\n\nПопробуйте ещё раз через /redactcomment", parse_mode="HTML") return await message.answer("✅ Кнопка обновлена!", parse_mode="HTML") await show_channel_menu(message, int(channel_id)) # ====================================================================== # EDIT PHOTO URL # ====================================================================== @router.callback_query(F.data.regexp(r"edit:(-?\d+):photo"), IsAdmin()) async def edit_photo_callback(callback: CallbackQuery, state: FSMContext) -> None: channel_id = int(callback.data.split(":")[1]) await state.update_data(channel_id=channel_id) await state.set_state(CommentEditStates.waiting_photo_url) if callback.message: await callback.message.edit_text( text=( "🖼 РЕДАКТИРОВАНИЕ ФОТО\n\n" "Отправьте прямую ссылку на изображение (http/https).\n\n" "Для отмены: /cancel" ), parse_mode="HTML" ) await callback.answer() @router.message(CommentEditStates.waiting_photo_url, IsAdmin()) async def process_photo_url(message: Message, state: FSMContext) -> None: if (message.text or "").strip() == "/cancel": await state.clear() await message.answer("❌ Отменено") return url = (message.text or "").strip() if not url.startswith(("http://", "https://")): await message.answer( "❌ Неверный формат URL\n\nURL должен начинаться с http:// или https://", parse_mode="HTML" ) return data = await state.get_data() channel_id = data.get("channel_id") if not channel_id: await state.clear() await message.answer("❌ Не выбран канал. Откройте меню заново: /redactcomment") return try: success = await _persist_settings_preserve_enabled( channel_id=int(channel_id), patch={"photo_url": url}, updated_by=message.from_user.id ) except Exception as e: logger.error(f"update photo failed: {e}", log_type="CHANNEL") success = False await state.clear() if not success: await message.answer("❌ Ошибка сохранения\n\nПопробуйте ещё раз через /redactcomment", parse_mode="HTML") return await message.answer(hide_link(url) + "✅ Фото обновлено!", parse_mode="HTML") await show_channel_menu(message, int(channel_id)) # ====================================================================== # PREVIEW # ====================================================================== @router.callback_query(F.data.regexp(r"edit:(-?\d+):preview"), IsAdmin()) async def preview_comment_callback(callback: CallbackQuery) -> None: channel_id = int(callback.data.split(":")[1]) config = await get_channel_config(channel_id) full_text, keyboard = _build_comment_payload(config) if callback.message: await callback.message.answer( text=f"👁 ПРЕВЬЮ КОММЕНТАРИЯ\n\n{full_text}", reply_markup=keyboard.as_markup(), parse_mode="HTML" ) await callback.answer("✅ Превью отправлено") # ====================================================================== # TOGGLE # ====================================================================== @router.callback_query(F.data.regexp(r"edit:(-?\d+):toggle"), IsAdmin()) async def toggle_comment_callback(callback: CallbackQuery) -> None: channel_id = int(callback.data.split(":")[1]) config = await get_channel_config(channel_id) current_status = bool(config.get("is_enabled")) new_status = not current_status manager = get_manager() if new_status: success = await manager.save_auto_comment_settings( channel_id=channel_id, text=config.get("text") or "", button_text=config.get("button_text") or "", button_url=config.get("button_url") or "", photo_url=config.get("photo_url") or "", updated_by=callback.from_user.id ) else: success = await manager.repo.toggle_auto_comment( channel_id=channel_id, is_enabled=False, updated_by=callback.from_user.id ) if not success: await callback.answer("❌ Ошибка переключения", show_alert=True) return await callback.answer(f"Автокомментарии {'✅ включены' if new_status else '❌ выключены'}", show_alert=True) config = await get_channel_config(channel_id) output = _render_menu_text(channel_id, config) if callback.message: await callback.message.edit_text( text=output, reply_markup=create_main_menu(channel_id).as_markup(), parse_mode="HTML" ) # ====================================================================== # DELETE SETTINGS # ====================================================================== @router.callback_query(F.data.regexp(r"edit:(-?\d+):delete"), IsAdmin()) async def delete_comment_callback(callback: CallbackQuery) -> None: channel_id = int(callback.data.split(":")[1]) manager = get_manager() success = await manager.repo.delete_auto_comment(channel_id) if not success: await callback.answer("❌ Ошибка удаления", show_alert=True) return await callback.answer("🗑 Настройки удалены", show_alert=True) if callback.message: await callback.message.edit_text( text=( "🗑 НАСТРОЙКИ УДАЛЕНЫ\n\n" f"Автокомментарии для канала {channel_id} удалены.\n\n" "Будут использоваться настройки по умолчанию из .env\n\n" "Для настройки: /redactcomment" ), parse_mode="HTML" )