feat: cleanup join notice with welcome message

This commit is contained in:
ngfchl
2026-07-07 21:56:52 +08:00
parent d8dd77e56b
commit 2f6739685f
2 changed files with 87 additions and 6 deletions
+85 -4
View File
@@ -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
View File
@@ -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()