feat: support for accepting friends and inviting to join group chats automatically through keywords (#28)
parent
e3b9928eca
commit
042f2df3bf
|
|
@ -11,12 +11,13 @@ class ReplyType(Enum):
|
|||
VIDEO_URL = 5 # 视频URL
|
||||
FILE = 6 # 文件
|
||||
CARD = 7 # 微信名片,仅支持ntchat
|
||||
InviteRoom = 8 # 邀请好友进群
|
||||
INVITE_ROOM = 8 # 邀请好友进群
|
||||
INFO = 9
|
||||
ERROR = 10
|
||||
TEXT_ = 11 # 强制文本
|
||||
VIDEO = 12
|
||||
MINIAPP = 13 # 小程序
|
||||
ACCEPT_FRIEND = 19 # 接受好友申请
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ class ChatChannel(Channel):
|
|||
def _compose_context(self, ctype: ContextType, content, **kwargs):
|
||||
context = Context(ctype, content)
|
||||
context.kwargs = kwargs
|
||||
if ctype == ContextType.ACCEPT_FRIEND:
|
||||
return context
|
||||
# context首次传入时,origin_ctype是None,
|
||||
# 引入的起因是:当输入语音时,会嵌套生成两个context,第一步语音转文本,第二步通过文本生成文字回复。
|
||||
# origin_ctype用于第二步文本回复时,判断是否需要匹配前缀,如果是私聊的语音,就不需要匹配前缀
|
||||
|
|
@ -221,6 +223,8 @@ class ChatChannel(Channel):
|
|||
"path": context.content,
|
||||
"msg": context.get("msg")
|
||||
}
|
||||
elif context.type == ContextType.ACCEPT_FRIEND: # 好友申请,匹配字符串
|
||||
reply = self._build_friend_request_reply(context)
|
||||
elif context.type == ContextType.SHARING: # 分享信息,当前无默认逻辑
|
||||
pass
|
||||
elif context.type == ContextType.FUNCTION or context.type == ContextType.FILE: # 文件消息及函数调用等,当前无默认逻辑
|
||||
|
|
@ -262,6 +266,8 @@ class ChatChannel(Channel):
|
|||
reply.content = "[" + str(reply.type) + "]\n" + reply.content
|
||||
elif reply.type == ReplyType.IMAGE_URL or reply.type == ReplyType.VOICE or reply.type == ReplyType.IMAGE or reply.type == ReplyType.FILE or reply.type == ReplyType.VIDEO or reply.type == ReplyType.VIDEO_URL:
|
||||
pass
|
||||
elif reply.type == ReplyType.ACCEPT_FRIEND:
|
||||
pass
|
||||
else:
|
||||
logger.error("[WX] unknown reply type: {}".format(reply.type))
|
||||
return
|
||||
|
|
@ -293,6 +299,14 @@ class ChatChannel(Channel):
|
|||
if retry_cnt < 2:
|
||||
time.sleep(3 + 3 * retry_cnt)
|
||||
self._send(reply, context, retry_cnt + 1)
|
||||
# 处理好友申请
|
||||
def _build_friend_request_reply(self, context):
|
||||
logger.info("friend request content: {}".format(context.content["Content"]))
|
||||
logger.info("accept_friend_commands list: {}".format(conf().get("accept_friend_commands", [])))
|
||||
if context.content["Content"] in conf().get("accept_friend_commands", []):
|
||||
return Reply(type=ReplyType.ACCEPT_FRIEND, content=True)
|
||||
else:
|
||||
return Reply(type=ReplyType.ACCEPT_FRIEND, content=False)
|
||||
|
||||
def _success_callback(self, session_id, **kwargs): # 线程正常结束时的回调函数
|
||||
logger.debug("Worker return success, session_id = {}".format(session_id))
|
||||
|
|
@ -318,7 +332,7 @@ class ChatChannel(Channel):
|
|||
return func
|
||||
|
||||
def produce(self, context: Context):
|
||||
session_id = context["session_id"]
|
||||
session_id = context.get("session_id", 0)
|
||||
with self.lock:
|
||||
if session_id not in self.sessions:
|
||||
self.sessions[session_id] = [
|
||||
|
|
|
|||
|
|
@ -47,6 +47,16 @@ def handler_group_msg(msg):
|
|||
WechatChannel().handle_group(cmsg)
|
||||
return None
|
||||
|
||||
# 自动接受加好友
|
||||
@itchat.msg_register(FRIENDS)
|
||||
def deal_with_friend(msg):
|
||||
try:
|
||||
cmsg = WechatMessage(msg, False)
|
||||
except NotImplementedError as e:
|
||||
logger.debug("[WX]friend request {} skipped: {}".format(msg["MsgId"], e))
|
||||
return None
|
||||
WechatChannel().handle_friend_request(cmsg)
|
||||
return None
|
||||
|
||||
def _check(func):
|
||||
def wrapper(self, cmsg: ChatMessage):
|
||||
|
|
@ -206,9 +216,21 @@ class WechatChannel(ChatChannel):
|
|||
if context:
|
||||
self.produce(context)
|
||||
|
||||
@time_checker
|
||||
@_check
|
||||
def handle_friend_request(self, cmsg: ChatMessage):
|
||||
if cmsg.ctype == ContextType.ACCEPT_FRIEND:
|
||||
logger.debug("[WX]receive friend request: {}".format(cmsg.content["NickName"]))
|
||||
else:
|
||||
logger.debug("[WX]receive friend request: {}, cmsg={}".format(cmsg.content["NickName"], cmsg))
|
||||
context = self._compose_context(cmsg.ctype, cmsg.content, msg=cmsg)
|
||||
if context:
|
||||
self.produce(context)
|
||||
|
||||
|
||||
# 统一的发送函数,每个Channel自行实现,根据reply的type字段发送不同类型的消息
|
||||
def send(self, reply: Reply, context: Context):
|
||||
receiver = context["receiver"]
|
||||
receiver = context.get("receiver")
|
||||
if reply.type == ReplyType.TEXT:
|
||||
itchat.send(reply.content, toUserName=receiver)
|
||||
logger.info("[WX] sendMsg={}, receiver={}".format(reply, receiver))
|
||||
|
|
@ -258,6 +280,51 @@ class WechatChannel(ChatChannel):
|
|||
itchat.send_video(video_storage, toUserName=receiver)
|
||||
logger.info("[WX] sendVideo url={}, receiver={}".format(video_url, receiver))
|
||||
|
||||
elif reply.type == ReplyType.ACCEPT_FRIEND: # 新增接受好友申请回复类型
|
||||
# 假设 reply.content 包含了新好友的用户名
|
||||
is_accept = reply.content
|
||||
if is_accept:
|
||||
try:
|
||||
# 自动接受好友申请
|
||||
debug_msg = itchat.accept_friend(userName=context.content["UserName"], v4=context.content["Ticket"])
|
||||
logger.debug("[WX] accept_friend return: {}".format(debug_msg))
|
||||
logger.info("[WX] Accepted new friend, UserName={}, NickName={}".format(context.content["UserName"], context.content["NickName"]))
|
||||
except Exception as e:
|
||||
logger.error("[WX] Failed to add friend. Error: {}".format(e))
|
||||
else:
|
||||
logger.info("[WX] Ignored new friend, username={}".format(context.content["NickName"]))
|
||||
elif reply.type == ReplyType.INVITE_ROOM: # 新增邀请好友进群回复类型
|
||||
# 假设 reply.content 包含了群聊的名字
|
||||
|
||||
def get_group_id(group_name):
|
||||
"""
|
||||
根据群聊名称获取群聊ID。
|
||||
:param group_name: 群聊的名称。
|
||||
:return: 群聊的ID (UserName)。
|
||||
"""
|
||||
group_list = itchat.search_chatrooms(name=group_name)
|
||||
if group_list:
|
||||
return group_list[0]["UserName"]
|
||||
else:
|
||||
return None
|
||||
|
||||
try:
|
||||
chatroomUserName = reply.content
|
||||
group_id = get_group_id(chatroomUserName)
|
||||
logger.debug("[WX] find group_id={}, where chatroom={}".format(group_id, chatroomUserName))
|
||||
if group_id is None:
|
||||
raise ValueError("The specified group chat was not found: {}".format(chatroomUserName))
|
||||
# 调用 itchat 的 add_member_into_chatroom 方法来添加成员
|
||||
debug_msg = itchat.add_member_into_chatroom(group_id, receiver)
|
||||
logger.debug("[WX] add_member_into_chatroom return: {}".format(debug_msg))
|
||||
logger.info("[WX] invite members={}, to chatroom={}".format(receiver, chatroomUserName))
|
||||
except ValueError as ve:
|
||||
# 记录查找群聊失败的错误信息
|
||||
logger.error("[WX] {}".format(ve))
|
||||
except Exception as e:
|
||||
# 记录添加成员失败的错误信息
|
||||
logger.error("[WX] Failed to invite members to chatroom. Error: {}".format(e))
|
||||
|
||||
def _send_login_success():
|
||||
try:
|
||||
from common.linkai_client import chat_client
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ class WechatMessage(ChatMessage):
|
|||
elif itchat_msg["Type"] == SHARING:
|
||||
self.ctype = ContextType.SHARING
|
||||
self.content = itchat_msg.get("Url")
|
||||
elif itchat_msg["Type"] == FRIENDS:
|
||||
self.ctype = ContextType.ACCEPT_FRIEND
|
||||
self.content = itchat_msg.get("RecommendInfo")
|
||||
|
||||
else:
|
||||
raise NotImplementedError("Unsupported message type: Type:{} MsgType:{}".format(itchat_msg["Type"], itchat_msg["MsgType"]))
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@
|
|||
"single_chat_prefix": [""],
|
||||
"single_chat_reply_prefix": "",
|
||||
"group_chat_prefix": ["@bot"],
|
||||
"group_name_white_list": ["ALL_GROUP"]
|
||||
"group_name_white_list": ["ALL_GROUP"],
|
||||
"accept_friend_commands": ["加好友"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ available_setting = {
|
|||
"single_chat_prefix": ["bot", "@bot"], # 私聊时文本需要包含该前缀才能触发机器人回复
|
||||
"single_chat_reply_prefix": "[bot] ", # 私聊时自动回复的前缀,用于区分真人
|
||||
"single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行
|
||||
"accept_friend_commands": ["加好友"], # 自动接受好友请求的申请信息
|
||||
"group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复
|
||||
"group_chat_reply_prefix": "", # 群聊时自动回复的前缀
|
||||
"group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@
|
|||
"Apilot": {
|
||||
"url": "https://github.com/6vision/Apilot.git",
|
||||
"desc": "通过api直接查询早报、热榜、快递、天气等实用信息的插件"
|
||||
},
|
||||
"GroupInvitation": {
|
||||
"url": "https://github.com/dfldylan/GroupInvitation.git",
|
||||
"desc": "根据特定关键词自动邀请用户加入指定的群聊"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue