diff --git a/handlers/admin.py b/handlers/admin.py index bb79b7a..da6d345 100644 --- a/handlers/admin.py +++ b/handlers/admin.py @@ -14,6 +14,31 @@ from services.stats_service import get_daily_stats logger = logging.getLogger("spam_guard") +async def ban_user_with_record(chat, bot, user_id: int, target: str | None, operator_id: int | None, reason: str, method: str = "command"): + unban_at = datetime.now() + timedelta(minutes=config.BAN_DURATION_MINUTES) + await chat.ban_member(user_id=user_id, until_date=int(unban_at.timestamp())) + ban = await Ban.create( + user_id=user_id, + username=target, + reason=reason, + method=method, + duration_minutes=config.BAN_DURATION_MINUTES, + unban_at=unban_at, + ) + await Action.create(action="BAN", user_id=user_id, username=target, operator_id=operator_id, details={"method": method}) + return ban + + +async def mark_reply_message_deleted(reply_msg, reason: str = "command_ban_reply"): + rec = await Message.get_or_none(chat_id=reply_msg.chat_id, msg_id=reply_msg.message_id) + if rec: + rec.manually_deleted = True + rec.deleted_at = datetime.now() + rec.delete_reason = reason + await rec.save() + + + async def cmd_stats(update: Update, context: ContextTypes.DEFAULT_TYPE): """今日统计""" stats = await get_daily_stats() @@ -45,18 +70,26 @@ async def cmd_ban(update: Update, context: ContextTypes.DEFAULT_TYPE): return try: - unban_at = datetime.now() + timedelta(minutes=config.BAN_DURATION_MINUTES) - await update.effective_chat.ban_member(user_id=user_id, until_date=int(unban_at.timestamp())) - await Ban.create( - user_id=user_id, - username=target, - reason=f"手动封禁 by {update.effective_user.username or update.effective_user.id}", - method="command", - duration_minutes=config.BAN_DURATION_MINUTES, - unban_at=unban_at, + await ban_user_with_record( + update.effective_chat, + context.bot, + user_id, + target, + update.effective_user.id, + f"手动封禁 by {update.effective_user.username or update.effective_user.id}", + method="reply_command" if update.message.reply_to_message else "command", ) - await Action.create(action="BAN", user_id=user_id, username=target, operator_id=update.effective_user.id) - reply = await update.message.reply_text(f"🔨 已封禁 {target} ({user_id}) {config.BAN_DURATION_MINUTES} 分钟") + if update.message.reply_to_message: + try: + await update.message.reply_to_message.delete() + await mark_reply_message_deleted(update.message.reply_to_message) + except Exception: + pass + try: + await update.message.delete() + except Exception: + pass + reply = await update.effective_chat.send_message(f"🔨 已封禁 {target} ({user_id}) {config.BAN_DURATION_MINUTES} 分钟") except Exception as e: logger.error(f"手动封禁失败: {type(e).__name__}: {e}") reply = await update.message.reply_text(f"❌ 封禁失败: {e}") diff --git a/web/api.py b/web/api.py index 1eeab67..c4baed2 100644 --- a/web/api.py +++ b/web/api.py @@ -193,13 +193,16 @@ async def api_send(request: Request): @app.post("/api/messages/{message_id}/delete") -async def api_delete_message(message_id: int): +async def api_delete_message(message_id: int, request: Request): """手动删除聊天记录:先删 Telegram 消息,再把数据库记录标记为已删除。""" import config from telegram import Bot from telegram.error import BadRequest from telegram.request import HTTPXRequest + data = await request.json() if request.headers.get("content-type", "").startswith("application/json") else {} + ban_user = bool(data.get("ban_user")) + rec = await Message.get_or_none(id=message_id) if not rec: return JSONResponse({"ok": False, "error": "消息不存在"}, status_code=404) @@ -226,6 +229,24 @@ async def api_delete_message(message_id: int): rec.deleted_at = now_tz() rec.delete_reason = "web_manual_delete" if not tg_error else f"web_manual_delete_after_tg_error: {tg_error[:160]}" await rec.save() + + ban = None + if ban_user and rec.user_id: + try: + from datetime import timedelta + unban_at = now_tz() + timedelta(minutes=config.BAN_DURATION_MINUTES) + await bot.ban_chat_member(chat_id=rec.chat_id, user_id=rec.user_id, until_date=int(unban_at.timestamp())) + ban = await Ban.create( + user_id=rec.user_id, + username=rec.username, + first_name=rec.first_name, + reason="Web 删除消息时手动封禁", + method="web_delete_ban", + duration_minutes=config.BAN_DURATION_MINUTES, + unban_at=unban_at, + ) + except Exception as e: + return JSONResponse({"ok": False, "error": f"消息已删除,但封禁失败: {type(e).__name__}: {e}"}, status_code=502) await Action.create( action="MANUAL_DELETE_MESSAGE", user_id=rec.user_id, @@ -239,7 +260,21 @@ async def api_delete_message(message_id: int): "chat_id": rec.chat_id, "deleted_at": rec.deleted_at, })) - return JSONResponse(json_safe({"ok": True, "tg_deleted": tg_deleted, "tg_error": tg_error, "deleted_at": rec.deleted_at})) + if ban: + await Action.create(action="BAN", user_id=rec.user_id, username=rec.username, details={"method": "web_delete_ban", "message_id": rec.id}) + await broadcast_sse("ban", json_safe({ + "id": ban.id, + "user_id": rec.user_id, + "username": rec.username, + "first_name": rec.first_name, + "reason": ban.reason, + "method": ban.method, + "duration_minutes": config.BAN_DURATION_MINUTES, + "auto_unban": True, + "unban_at": unban_at, + "time": ban.created_at, + })) + return JSONResponse(json_safe({"ok": True, "tg_deleted": tg_deleted, "tg_error": tg_error, "deleted_at": rec.deleted_at, "banned": bool(ban)})) @app.get("/api/scores") diff --git a/web/static/index.html b/web/static/index.html index 08addef..51caeab 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -217,7 +217,8 @@