chore: sync current WechatHookBot workspace
This commit is contained in:
22
plugins/GroupWelcome/config.toml
Normal file
22
plugins/GroupWelcome/config.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
# 入群欢迎插件配置
|
||||
|
||||
[plugin]
|
||||
enabled = true
|
||||
name = "GroupWelcome"
|
||||
description = "新成员入群时发送欢迎卡片"
|
||||
|
||||
[welcome]
|
||||
# 欢迎卡片配置
|
||||
# 支持变量:{nickname} - 新成员昵称
|
||||
title = "欢迎 {nickname} 加入本群!"
|
||||
desc = "很高兴认识你 {nickname},请遵守群规,文明交流~"
|
||||
url = "https://www.functen.cn"
|
||||
# image_url 将使用新成员的头像,无需配置
|
||||
|
||||
[behavior]
|
||||
# 是否启用入群欢迎
|
||||
enabled = true
|
||||
# 启用欢迎的群聊列表(为空则对所有群生效)
|
||||
enabled_groups = []
|
||||
# 禁用欢迎的群聊列表
|
||||
disabled_groups = []
|
||||
@@ -11,35 +11,12 @@ import tomllib
|
||||
from pathlib import Path
|
||||
from loguru import logger
|
||||
from utils.plugin_base import PluginBase
|
||||
from utils.decorators import on_text_message
|
||||
|
||||
|
||||
# 定义事件装饰器
|
||||
def on_chatroom_member_add(priority=50):
|
||||
"""群成员新增装饰器"""
|
||||
def decorator(func):
|
||||
setattr(func, '_event_type', 'chatroom_member_add')
|
||||
setattr(func, '_priority', min(max(priority, 0), 99))
|
||||
return func
|
||||
return decorator
|
||||
|
||||
|
||||
def on_system_message(priority=50):
|
||||
"""系统消息装饰器"""
|
||||
def decorator(func):
|
||||
setattr(func, '_event_type', 'system_message')
|
||||
setattr(func, '_priority', min(max(priority, 0), 99))
|
||||
return func
|
||||
return decorator
|
||||
|
||||
|
||||
def on_chatroom_info_change(priority=50):
|
||||
"""群信息变化装饰器"""
|
||||
def decorator(func):
|
||||
setattr(func, '_event_type', 'chatroom_info_change')
|
||||
setattr(func, '_priority', min(max(priority, 0), 99))
|
||||
return func
|
||||
return decorator
|
||||
from utils.decorators import (
|
||||
on_text_message,
|
||||
on_system_message,
|
||||
on_chatroom_member_add,
|
||||
on_chatroom_info_change
|
||||
)
|
||||
|
||||
|
||||
class GroupWelcome(PluginBase):
|
||||
@@ -55,6 +32,10 @@ class GroupWelcome(PluginBase):
|
||||
self.config = None
|
||||
# 群成员缓存:{room_wxid: set(member_wxids)}
|
||||
self.member_cache = {}
|
||||
# 已欢迎成员缓存,防止重复欢迎:{(room_wxid, wxid_or_nickname): timestamp}
|
||||
self.welcomed_cache = {}
|
||||
# 机器人自己的 wxid
|
||||
self.bot_wxid = None
|
||||
|
||||
async def async_init(self):
|
||||
"""插件异步初始化"""
|
||||
@@ -65,6 +46,38 @@ class GroupWelcome(PluginBase):
|
||||
|
||||
logger.info("入群欢迎插件已加载")
|
||||
|
||||
def _is_already_welcomed(self, room_wxid: str, member_key: str, alias_keys: list | None = None) -> bool:
|
||||
"""检查成员是否已经被欢迎过(30秒内去重)"""
|
||||
import time
|
||||
cache_key = (room_wxid, member_key)
|
||||
now = time.time()
|
||||
|
||||
# 清理过期缓存(超过30秒的)
|
||||
expired_keys = [k for k, t in self.welcomed_cache.items() if now - t > 30]
|
||||
for k in expired_keys:
|
||||
del self.welcomed_cache[k]
|
||||
|
||||
if cache_key in self.welcomed_cache:
|
||||
return True
|
||||
|
||||
if alias_keys:
|
||||
for alias_key in alias_keys:
|
||||
if alias_key and (room_wxid, alias_key) in self.welcomed_cache:
|
||||
return True
|
||||
|
||||
self.welcomed_cache[cache_key] = now
|
||||
if alias_keys:
|
||||
for alias_key in alias_keys:
|
||||
if alias_key:
|
||||
self.welcomed_cache[(room_wxid, alias_key)] = now
|
||||
return False
|
||||
|
||||
def _normalize_nickname(self, nickname: str) -> str:
|
||||
"""规范化昵称,提升去重和匹配稳定性"""
|
||||
if not nickname:
|
||||
return ""
|
||||
return nickname.strip().strip('"').strip("“").strip("”")
|
||||
|
||||
@on_chatroom_member_add(priority=50)
|
||||
async def on_chatroom_member_add(self, bot, message: dict):
|
||||
"""处理群成员新增事件(type=11098)"""
|
||||
@@ -152,15 +165,11 @@ class GroupWelcome(PluginBase):
|
||||
if not self._should_welcome(room_wxid):
|
||||
return
|
||||
|
||||
# 如果有 member_list,直接处理(群创建或某些情况)
|
||||
if member_list:
|
||||
logger.info(f"[GroupWelcome] 群信息变化事件包含成员列表,直接处理")
|
||||
await self._process_new_members(bot, room_wxid, member_list)
|
||||
return
|
||||
# 注意:member_list 可能是全部成员列表,不能直接处理
|
||||
# 必须通过缓存对比找出真正的新成员
|
||||
|
||||
# 没有 member_list,需要通过对比缓存找出新成员
|
||||
# 获取当前群成员列表
|
||||
try:
|
||||
# 获取当前群成员列表
|
||||
current_members = await bot.get_chatroom_members(room_wxid)
|
||||
if not current_members:
|
||||
logger.warning(f"[GroupWelcome] 无法获取群成员列表: {room_wxid}")
|
||||
@@ -202,86 +211,123 @@ class GroupWelcome(PluginBase):
|
||||
|
||||
async def _process_new_members(self, bot, room_wxid: str, member_list: list):
|
||||
"""处理新成员列表"""
|
||||
# 获取机器人自己的 wxid(用于过滤)
|
||||
if not self.bot_wxid:
|
||||
try:
|
||||
login_info = await bot.get_login_info()
|
||||
if login_info:
|
||||
self.bot_wxid = login_info.get("wxid", "")
|
||||
except Exception as e:
|
||||
logger.warning(f"[GroupWelcome] 获取登录信息失败: {e}")
|
||||
|
||||
# 为每个新成员发送欢迎卡片
|
||||
for member in member_list:
|
||||
wxid = member.get("wxid", "")
|
||||
nickname = member.get("nickname", "新成员")
|
||||
nickname_raw = member.get("nickname", "新成员")
|
||||
nickname = self._normalize_nickname(nickname_raw) or "新成员"
|
||||
|
||||
# 过滤机器人自己
|
||||
if wxid and self.bot_wxid and wxid == self.bot_wxid:
|
||||
logger.info(f"[GroupWelcome] 跳过机器人自己: {wxid}")
|
||||
continue
|
||||
|
||||
# 去重检查(使用 wxid 或 nickname 作为 key)
|
||||
member_key = wxid if wxid else nickname
|
||||
alias_keys = [nickname] if wxid else None
|
||||
if self._is_already_welcomed(room_wxid, member_key, alias_keys):
|
||||
logger.info(f"[GroupWelcome] 成员已欢迎过,跳过: {member_key}")
|
||||
continue
|
||||
|
||||
try:
|
||||
# 如果有 wxid,尝试获取详细信息(包括头像)
|
||||
if wxid:
|
||||
user_info = await bot.get_user_info_in_chatroom(room_wxid, wxid)
|
||||
|
||||
if user_info:
|
||||
# 提取头像 URL
|
||||
big_head_img_url = user_info.get("bigHeadImgUrl", "")
|
||||
actual_nickname = user_info.get("nickName", {}).get("string", nickname)
|
||||
|
||||
logger.info(f"获取到新成员信息: {actual_nickname} ({wxid}), 头像: {big_head_img_url}")
|
||||
|
||||
# 发送欢迎卡片
|
||||
await self._send_welcome_card(
|
||||
bot, room_wxid, actual_nickname, big_head_img_url
|
||||
)
|
||||
else:
|
||||
logger.warning(f"无法获取新成员 {wxid} 的详细信息,使用默认配置")
|
||||
# 使用默认配置发送
|
||||
await self._send_welcome_card(bot, room_wxid, nickname, "")
|
||||
else:
|
||||
# 没有 wxid(从系统消息解析),尝试通过昵称匹配获取 wxid 和头像
|
||||
logger.info(f"[GroupWelcome] 尝试通过昵称匹配获取用户信息: {nickname}")
|
||||
|
||||
# 获取群成员列表(可能需要等待成员真正加入)
|
||||
members = await bot.get_chatroom_members(room_wxid)
|
||||
|
||||
# 如果没找到,等待1秒后重试一次
|
||||
if not members or not any(nickname.strip('"') == m.get("nickname", "") for m in members):
|
||||
import asyncio
|
||||
await asyncio.sleep(1)
|
||||
members = await bot.get_chatroom_members(room_wxid)
|
||||
|
||||
if members:
|
||||
# 通过昵称或群内昵称匹配
|
||||
matched_member = None
|
||||
for member in members:
|
||||
member_nickname = member.get("nickname", "")
|
||||
member_display_name = member.get("display_name", "")
|
||||
|
||||
# 匹配昵称(去除引号)
|
||||
if nickname.strip('"') == member_nickname or nickname.strip('"') == member_display_name:
|
||||
matched_member = member
|
||||
break
|
||||
|
||||
if matched_member:
|
||||
member_wxid = matched_member.get("wxid", "")
|
||||
member_nickname = matched_member.get("nickname", nickname)
|
||||
|
||||
logger.success(f"[GroupWelcome] 匹配成功: {member_nickname} ({member_wxid})")
|
||||
|
||||
# 获取成员详细信息(包括头像)
|
||||
try:
|
||||
user_info = await bot.get_user_info_in_chatroom(room_wxid, member_wxid)
|
||||
if user_info:
|
||||
member_avatar = user_info.get("bigHeadImgUrl", "")
|
||||
logger.info(f"[GroupWelcome] 获取到成员头像: {member_avatar}")
|
||||
await self._send_welcome_card(bot, room_wxid, member_nickname, member_avatar)
|
||||
else:
|
||||
logger.warning(f"[GroupWelcome] 无法获取成员详细信息,使用默认头像")
|
||||
await self._send_welcome_card(bot, room_wxid, member_nickname, "")
|
||||
except Exception as e:
|
||||
logger.error(f"[GroupWelcome] 获取成员详细信息失败: {e}")
|
||||
await self._send_welcome_card(bot, room_wxid, member_nickname, "")
|
||||
else:
|
||||
logger.warning(f"[GroupWelcome] 未找到匹配的成员: {nickname}")
|
||||
await self._send_welcome_card(bot, room_wxid, nickname, "")
|
||||
else:
|
||||
logger.warning(f"[GroupWelcome] 无法获取群成员列表")
|
||||
await self._send_welcome_card(bot, room_wxid, nickname, "")
|
||||
|
||||
await self._welcome_single_member(bot, room_wxid, wxid, nickname)
|
||||
except Exception as e:
|
||||
logger.error(f"处理新成员 {nickname} 欢迎失败: {e}")
|
||||
|
||||
async def _welcome_single_member(self, bot, room_wxid: str, wxid: str, nickname: str):
|
||||
"""欢迎单个新成员"""
|
||||
# 如果有 wxid,尝试获取详细信息(包括头像)
|
||||
if wxid:
|
||||
# 使用接口获取最新成员信息(新成员可能尚未同步进数据库)
|
||||
user_info = await bot.get_group_member_contact(room_wxid, wxid)
|
||||
|
||||
if user_info:
|
||||
# 提取头像 URL(处理可能是字典或字符串的情况)
|
||||
big_head = user_info.get("bigHeadImgUrl", "")
|
||||
if isinstance(big_head, dict):
|
||||
big_head_img_url = big_head.get("string", "")
|
||||
else:
|
||||
big_head_img_url = big_head if isinstance(big_head, str) else ""
|
||||
|
||||
# 提取昵称
|
||||
nick_name = user_info.get("nickName", {})
|
||||
if isinstance(nick_name, dict):
|
||||
actual_nickname = nick_name.get("string", nickname)
|
||||
else:
|
||||
actual_nickname = nick_name if isinstance(nick_name, str) else nickname
|
||||
|
||||
logger.info(f"获取到新成员信息: {actual_nickname} ({wxid}), 头像: {big_head_img_url}")
|
||||
|
||||
# 发送欢迎卡片
|
||||
await self._send_welcome_card(bot, room_wxid, actual_nickname, big_head_img_url)
|
||||
else:
|
||||
logger.warning(f"无法获取新成员 {wxid} 的详细信息,使用默认配置")
|
||||
await self._send_welcome_card(bot, room_wxid, nickname, "")
|
||||
else:
|
||||
# 没有 wxid(从系统消息解析),尝试通过昵称匹配
|
||||
await self._welcome_by_nickname(bot, room_wxid, nickname)
|
||||
|
||||
async def _welcome_by_nickname(self, bot, room_wxid: str, nickname: str):
|
||||
"""通过昵称匹配欢迎新成员"""
|
||||
import asyncio
|
||||
logger.info(f"[GroupWelcome] 尝试通过昵称匹配获取用户信息: {nickname}")
|
||||
|
||||
# 获取群成员列表(走接口,确保最新)
|
||||
members = await bot.get_chatroom_members(room_wxid)
|
||||
|
||||
# 如果没找到,等待1秒后重试
|
||||
if not members or not any(nickname.strip('"') == m.get("nickname", "") for m in members):
|
||||
await asyncio.sleep(1)
|
||||
members = await bot.get_chatroom_members(room_wxid)
|
||||
|
||||
if not members:
|
||||
logger.warning(f"[GroupWelcome] 无法获取群成员列表")
|
||||
await self._send_welcome_card(bot, room_wxid, nickname, "")
|
||||
return
|
||||
|
||||
# 通过昵称匹配
|
||||
matched_member = None
|
||||
for member in members:
|
||||
member_nickname = member.get("nickname", "")
|
||||
member_display_name = member.get("display_name", "")
|
||||
if nickname.strip('"') in (member_nickname, member_display_name):
|
||||
matched_member = member
|
||||
break
|
||||
|
||||
if not matched_member:
|
||||
logger.warning(f"[GroupWelcome] 未找到匹配的成员: {nickname}")
|
||||
await self._send_welcome_card(bot, room_wxid, nickname, "")
|
||||
return
|
||||
|
||||
member_wxid = matched_member.get("wxid", "")
|
||||
member_nickname = matched_member.get("nickname", nickname)
|
||||
logger.success(f"[GroupWelcome] 匹配成功: {member_nickname} ({member_wxid})")
|
||||
|
||||
# 获取头像
|
||||
try:
|
||||
user_info = await bot.get_group_member_contact(room_wxid, member_wxid)
|
||||
if user_info:
|
||||
big_head = user_info.get("bigHeadImgUrl", "")
|
||||
if isinstance(big_head, dict):
|
||||
member_avatar = big_head.get("string", "")
|
||||
else:
|
||||
member_avatar = big_head if isinstance(big_head, str) else ""
|
||||
await self._send_welcome_card(bot, room_wxid, member_nickname, member_avatar)
|
||||
else:
|
||||
await self._send_welcome_card(bot, room_wxid, member_nickname, "")
|
||||
except Exception as e:
|
||||
logger.error(f"[GroupWelcome] 获取成员详细信息失败: {e}")
|
||||
await self._send_welcome_card(bot, room_wxid, member_nickname, "")
|
||||
|
||||
async def _send_welcome_card(
|
||||
self, bot, room_wxid: str, nickname: str, image_url: str
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user