198 lines
8.9 KiB
Python
198 lines
8.9 KiB
Python
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
|
||
import logging
|
||
|
||
# ИМПОРТИРУЕМ КОНСТАНТЫ ИЗ drafts.py, чтобы не промахнуться с ключами
|
||
from app.bot.handlers.drafts import (
|
||
STATE_DRAFT, STATE_AWAIT_TEXT, STATE_CONFIRM,
|
||
KEY_DRAFT_ID,
|
||
)
|
||
|
||
log = logging.getLogger(__name__)
|
||
async def on_callback(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||
q = update.callback_query
|
||
await q.answer()
|
||
data = q.data
|
||
log.debug("callbacks:on_callback data=%s", 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
|
||
|
||
# Сброс «залипших» режимов (если были)
|
||
for k in ("await_dict_file", "dict_params", "await_chat_id"):
|
||
ctx.user_data.pop(k, None)
|
||
|
||
# ЯВНО ПРОСТАВЛЯЕМ draft_id и состояние await_text через общие константы
|
||
ctx.user_data[KEY_DRAFT_ID] = draft_id
|
||
ctx.user_data[STATE_DRAFT] = STATE_AWAIT_TEXT
|
||
|
||
log.info("callbacks:draft_next_text user=%s chat=%s -> draft_id=%s state=%s",
|
||
getattr(update.effective_user, "id", None),
|
||
getattr(update.effective_chat, "id", None),
|
||
draft_id, ctx.user_data.get(STATE_DRAFT))
|
||
|
||
await q.edit_message_text("Шаг 2/3 — текст.\nОтправьте текст поста.")
|
||
return
|
||
|
||
|
||
# --- Подтверждение -> мультивыбор чатов ---
|
||
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("Черновик отменён.")
|
||
else:
|
||
await q.edit_message_text("Неизвестная команда.")
|
||
log.warning("callbacks:unhandled data=%s", data) |