from telegram import Update, InputMediaPhoto, InputMediaVideo, InputMediaAnimation from telegram.constants import ParseMode from telegram.ext import ContextTypes from app.db.session import get_session from app.db.models import Draft, Chat, Delivery, User from app.bot.keyboards.common import kb_multiselect # ← только мультивыбор from app.bot.messages import NEED_MEDIA_BEFORE_NEXT, NO_SELECTION, SENT_SUMMARY from app.moderation.engine import check_message_allowed async def on_callback(update: Update, ctx: ContextTypes.DEFAULT_TYPE): q = update.callback_query await q.answer() data = q.data # --- Переход с медиа на текст --- if data.startswith("draft_next_text:"): draft_id = int(data.split(":")[1]) with get_session() as s: d = s.get(Draft, draft_id) if not d: await q.edit_message_text("Черновик не найден.") return if len(d.media) == 0: await q.edit_message_text(NEED_MEDIA_BEFORE_NEXT) return ctx.user_data["draft_id"] = draft_id ctx.user_data["draft_state"] = "await_text" await q.edit_message_text("Шаг 2/3 — текст.\nОтправьте текст поста.") # --- Подтверждение -> мультивыбор чатов --- elif data.startswith("draft_confirm_send:"): draft_id = int(data.split(":")[1]) with get_session() as s: d = s.get(Draft, draft_id) if not d: await q.edit_message_text("Черновик не найден.") return d.status = "ready" s.commit() chats = s.query(Chat).filter_by(owner_user_id=d.user_id, can_post=True).all() chat_rows = [(c.title or str(c.chat_id), c.chat_id) for c in chats] sel_key = f"sel:{draft_id}" ctx.user_data[sel_key] = set() await q.edit_message_text("Выберите чаты:", reply_markup=kb_multiselect(draft_id, chat_rows, ctx.user_data[sel_key])) # --- Тоггл чекбокса --- elif data.startswith("tgl:"): _, draft_id, chat_id = data.split(":") draft_id = int(draft_id) chat_id = int(chat_id) sel_key = f"sel:{draft_id}" with get_session() as s: d = s.get(Draft, draft_id) chats = s.query(Chat).filter_by(owner_user_id=d.user_id, can_post=True).all() rows = [(c.title or str(c.chat_id), c.chat_id) for c in chats] sel: set[int] = set(ctx.user_data.get(sel_key, set())) if chat_id in sel: sel.remove(chat_id) else: sel.add(chat_id) ctx.user_data[sel_key] = sel await q.edit_message_reply_markup(reply_markup=kb_multiselect(draft_id, rows, sel)) # --- Выбрать все --- elif data.startswith("selall:"): draft_id = int(data.split(":")[1]) sel_key = f"sel:{draft_id}" with get_session() as s: d = s.get(Draft, draft_id) chats = s.query(Chat).filter_by(owner_user_id=d.user_id, can_post=True).all() rows = [(c.title or str(c.chat_id), c.chat_id) for c in chats] ctx.user_data[sel_key] = {cid for _, cid in rows} await q.edit_message_reply_markup(reply_markup=kb_multiselect(draft_id, rows, ctx.user_data[sel_key])) # --- Сброс --- elif data.startswith("clear:"): draft_id = int(data.split(":")[1]) sel_key = f"sel:{draft_id}" with get_session() as s: d = s.get(Draft, draft_id) chats = s.query(Chat).filter_by(owner_user_id=d.user_id, can_post=True).all() rows = [(c.title or str(c.chat_id), c.chat_id) for c in chats] ctx.user_data[sel_key] = set() await q.edit_message_reply_markup(reply_markup=kb_multiselect(draft_id, rows, set())) # --- Отправка выбранных --- elif data.startswith("sendmulti:"): draft_id = int(data.split(":")[1]) sel_key = f"sel:{draft_id}" with get_session() as s: d = s.get(Draft, draft_id) if not d: await q.edit_message_text("Черновик не найден.") return chats = s.query(Chat).filter_by(owner_user_id=d.user_id, can_post=True).all() rows = [(c.title or str(c.chat_id), c.chat_id) for c in chats] selected: set[int] = ctx.user_data.get(sel_key, set()) if not selected: await q.edit_message_text(NO_SELECTION, reply_markup=kb_multiselect(draft_id, rows, selected)) return ok = 0 fail = 0 media = list(sorted(d.media, key=lambda m: m.order)) media_ids = [m.file_id for m in media] text_val = d.text or "" owner = s.get(User, d.user_id) for cid in list(selected): allowed, reasons, content_hash = check_message_allowed( s, cid, owner_user_id=owner.id, text=text_val, media_ids=media_ids ) if not allowed: s.add(Delivery(draft_id=d.id, chat_id=cid, status="failed", error="; ".join(reasons), content_hash=content_hash)) s.commit() fail += 1 continue try: if media: if len(media) > 1: im = [] for i, m in enumerate(media): cap = text_val if i == 0 else None if m.kind == "photo": im.append(InputMediaPhoto(media=m.file_id, caption=cap, parse_mode=ParseMode.HTML)) elif m.kind == "video": im.append(InputMediaVideo(media=m.file_id, caption=cap, parse_mode=ParseMode.HTML)) else: im.append(InputMediaAnimation(media=m.file_id, caption=cap, parse_mode=ParseMode.HTML)) await ctx.bot.send_media_group(chat_id=cid, media=im) else: m = media[0] if m.kind == "photo": await ctx.bot.send_photo(chat_id=cid, photo=m.file_id, caption=text_val, parse_mode=ParseMode.HTML) elif m.kind == "video": await ctx.bot.send_video(chat_id=cid, video=m.file_id, caption=text_val, parse_mode=ParseMode.HTML) else: await ctx.bot.send_animation(chat_id=cid, animation=m.file_id, caption=text_val, parse_mode=ParseMode.HTML) else: await ctx.bot.send_message(chat_id=cid, text=text_val or "(пусто)", parse_mode=ParseMode.HTML) s.add(Delivery(draft_id=d.id, chat_id=cid, status="sent", content_hash=content_hash)) s.commit() ok += 1 except Exception as e: s.add(Delivery(draft_id=d.id, chat_id=cid, status="failed", error=str(e), content_hash=content_hash)) s.commit() fail += 1 ctx.user_data.pop(sel_key, None) await q.edit_message_text(SENT_SUMMARY.format(ok=ok, fail=fail)) # --- Отмена --- elif data.startswith("draft_cancel:"): draft_id = int(data.split(":")[1]) with get_session() as s: d = s.get(Draft, draft_id) if d: d.status = "cancelled" s.commit() ctx.user_data.pop(f"sel:{draft_id}", None) ctx.user_data.pop("draft_id", None) ctx.user_data.pop("draft_state", None) await q.edit_message_text("Черновик отменён.")