chore: sync current WechatHookBot workspace

This commit is contained in:
2026-03-09 15:48:45 +08:00
parent 4016c1e6eb
commit 9119e2307d
195 changed files with 24438 additions and 17498 deletions

View 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 = []

View File

@@ -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
):