feat: cleanup join notice with welcome message
This commit is contained in:
+85
-4
@@ -1,19 +1,46 @@
|
||||
"""入群欢迎 / 退群告别"""
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from telegram import ChatMember, InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.ext import ChatMemberHandler, ContextTypes
|
||||
from telegram.ext import ChatMemberHandler, ContextTypes, MessageHandler, filters
|
||||
|
||||
import config
|
||||
from services.message_utils import record_bot_message
|
||||
from db.models import Action, Ban
|
||||
from services.message_utils import schedule_delete
|
||||
from db.models import Action, Ban, Message
|
||||
from services.spam_detector import classify_username_spam
|
||||
from services.verify_service import create_verify
|
||||
from web.api import broadcast_sse, json_safe
|
||||
|
||||
logger = logging.getLogger("spam_guard")
|
||||
JOIN_SERVICE_TEXT = "__service__:new_chat_member"
|
||||
|
||||
|
||||
async def handle_new_chat_members_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""记录 Telegram 入群系统消息,供后台删除用户消息时联动清理。"""
|
||||
msg = update.effective_message
|
||||
if not msg or not msg.new_chat_members:
|
||||
return
|
||||
chat_id = update.effective_chat.id if update.effective_chat else msg.chat_id
|
||||
for user in msg.new_chat_members:
|
||||
if user.is_bot:
|
||||
continue
|
||||
username = user.username or ""
|
||||
first_name = " ".join(x for x in [user.first_name, user.last_name] if x) or user.first_name or ""
|
||||
exists = await Message.get_or_none(chat_id=chat_id, msg_id=msg.message_id, user_id=user.id, text=JOIN_SERVICE_TEXT)
|
||||
if exists:
|
||||
continue
|
||||
await Message.create(
|
||||
msg_id=msg.message_id,
|
||||
chat_id=chat_id,
|
||||
user_id=user.id,
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
text=JOIN_SERVICE_TEXT,
|
||||
)
|
||||
logger.info(f"🧾 已记录入群系统消息: user={user.id}, msg_id={msg.message_id}")
|
||||
|
||||
|
||||
WELCOME_TEXT = (
|
||||
"👋 欢迎新成员 **{username}** 加入收割机群组!\n"
|
||||
@@ -34,6 +61,59 @@ def verify_keyboard() -> InlineKeyboardMarkup:
|
||||
])
|
||||
|
||||
|
||||
async def delete_join_service_message(bot, chat_id: int, user_id: int) -> dict:
|
||||
"""删除指定用户最近的 Telegram 入群系统提示。"""
|
||||
rows = await Message.filter(
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
text=JOIN_SERVICE_TEXT,
|
||||
manually_deleted=False,
|
||||
).order_by("-created_at").limit(3)
|
||||
result = {"attempted": 0, "deleted": 0, "errors": []}
|
||||
seen_msg_ids: set[int] = set()
|
||||
for row in rows:
|
||||
if not row.msg_id or int(row.msg_id) in seen_msg_ids:
|
||||
row.manually_deleted = True
|
||||
row.deleted_at = datetime.now()
|
||||
row.delete_reason = "welcome_cleanup_join_duplicate"
|
||||
await row.save()
|
||||
continue
|
||||
seen_msg_ids.add(int(row.msg_id))
|
||||
result["attempted"] += 1
|
||||
try:
|
||||
await bot.delete_message(chat_id=chat_id, message_id=row.msg_id)
|
||||
result["deleted"] += 1
|
||||
row.delete_reason = "welcome_cleanup_join_service"
|
||||
logger.info(f"🧹 已删除入群系统消息: user={user_id}, msg_id={row.msg_id}")
|
||||
except Exception as e:
|
||||
msg = str(e)
|
||||
if "message to delete not found" in msg.lower():
|
||||
row.delete_reason = f"welcome_cleanup_join_not_found: {msg[:120]}"
|
||||
else:
|
||||
result["errors"].append(f"{type(e).__name__}: {msg}")
|
||||
continue
|
||||
row.manually_deleted = True
|
||||
row.deleted_at = datetime.now()
|
||||
await row.save()
|
||||
if result["attempted"]:
|
||||
await Action.create(action="WELCOME_CLEAN_JOIN_MESSAGE", chat_id=chat_id, user_id=user_id, details=result)
|
||||
return result
|
||||
|
||||
|
||||
async def auto_delete_welcome_and_join(bot, welcome_msg, chat_id: int, user_id: int, seconds: int) -> None:
|
||||
"""欢迎消息到期销毁时,同步删除该用户的入群系统记录。"""
|
||||
await asyncio.sleep(seconds)
|
||||
try:
|
||||
await welcome_msg.delete()
|
||||
logger.info(f"🧹 已删除入群欢迎消息: user={user_id}, msg_id={welcome_msg.message_id}")
|
||||
except Exception as e:
|
||||
logger.debug(f"自动删除入群欢迎消息失败: {e}")
|
||||
try:
|
||||
await delete_join_service_message(bot, chat_id, user_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"删除入群系统消息失败: user={user_id}, {type(e).__name__}: {e}")
|
||||
|
||||
|
||||
async def handle_chat_member(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""入群欢迎 / 退群告别"""
|
||||
cmu = update.chat_member
|
||||
@@ -144,7 +224,7 @@ async def handle_chat_member(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
||||
parse_mode="Markdown",
|
||||
reply_markup=verify_keyboard(),
|
||||
)
|
||||
schedule_delete(msg, seconds=config.VERIFY_TIMEOUT_SECONDS)
|
||||
asyncio.create_task(auto_delete_welcome_and_join(context.bot, msg, update.effective_chat.id, user.id, config.VERIFY_TIMEOUT_SECONDS))
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 欢迎消息发送失败: {e}")
|
||||
|
||||
@@ -157,4 +237,5 @@ async def handle_chat_member(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
||||
|
||||
def register_welcome(app):
|
||||
"""注册入群欢迎/退群告别"""
|
||||
app.add_handler(MessageHandler(filters.StatusUpdate.NEW_CHAT_MEMBERS, handle_new_chat_members_message), group=-1)
|
||||
app.add_handler(ChatMemberHandler(handle_chat_member, ChatMemberHandler.CHAT_MEMBER))
|
||||
|
||||
+2
-2
@@ -401,7 +401,7 @@ def normalize_ban(row: dict) -> dict:
|
||||
|
||||
@app.get("/api/messages")
|
||||
async def api_messages(limit: int = Query(default=50, ge=1, le=500), chat_id: int | None = None):
|
||||
qs = Message.all()
|
||||
qs = Message.exclude(text="__service__:new_chat_member")
|
||||
if chat_id is not None:
|
||||
qs = qs.filter(chat_id=chat_id)
|
||||
msgs = await qs.order_by("-created_at").limit(limit).values()
|
||||
@@ -411,7 +411,7 @@ async def api_messages(limit: int = Query(default=50, ge=1, le=500), chat_id: in
|
||||
@app.get("/api/chat")
|
||||
async def api_chat(limit: int = Query(default=100, ge=1, le=500), chat_id: int | None = None):
|
||||
"""兼容旧前端:返回聊天记录。"""
|
||||
qs = Message.all()
|
||||
qs = Message.exclude(text="__service__:new_chat_member")
|
||||
if chat_id is not None:
|
||||
qs = qs.filter(chat_id=chat_id)
|
||||
msgs = await qs.order_by("-created_at").limit(limit).values()
|
||||
|
||||
Reference in New Issue
Block a user