Files
tg_post_min/app/bot/handlers/callbacks.py
Andrey K. Choi c16ec54891 Bot become a Community Guard & Post send manager
added: dictionary support for censore
message/user management with dict triggers
2025-08-22 21:44:14 +09:00

173 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("Черновик отменён.")