Skip to content

Commit

Permalink
feat: support for accepting friends and inviting to join group chats …
Browse files Browse the repository at this point in the history
…automatically through keywords (zhayujie#28)
  • Loading branch information
cype62 authored May 7, 2024
1 parent e3b9928 commit 042f2df
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 5 deletions.
3 changes: 2 additions & 1 deletion bridge/reply.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 15 additions & 1 deletion channel/chat_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ def __init__(self):
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用于第二步文本回复时,判断是否需要匹配前缀,如果是私聊的语音,就不需要匹配前缀
Expand Down Expand Up @@ -221,6 +223,8 @@ def _generate_reply(self, context: Context, reply: Reply = Reply()) -> Reply:
"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: # 文件消息及函数调用等,当前无默认逻辑
Expand Down Expand Up @@ -262,6 +266,8 @@ def _decorate_reply(self, context: Context, reply: Reply) -> Reply:
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
Expand Down Expand Up @@ -293,6 +299,14 @@ def _send(self, reply: Reply, context: Context, retry_cnt=0):
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))
Expand All @@ -318,7 +332,7 @@ def func(worker: Future):
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] = [
Expand Down
69 changes: 68 additions & 1 deletion channel/wechat/wechat_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -206,9 +216,21 @@ def handle_group(self, cmsg: ChatMessage):
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))
Expand Down Expand Up @@ -258,6 +280,51 @@ def send(self, reply: Reply, context: Context):
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
Expand Down
5 changes: 4 additions & 1 deletion channel/wechat/wechat_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ def __init__(self, itchat_msg, is_group=False):
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"]))

Expand Down
3 changes: 2 additions & 1 deletion config-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": ["加好友"]
}
1 change: 1 addition & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"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 可以换行
Expand Down
4 changes: 4 additions & 0 deletions plugins/source.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
"Apilot": {
"url": "https://github.com/6vision/Apilot.git",
"desc": "通过api直接查询早报、热榜、快递、天气等实用信息的插件"
},
"GroupInvitation": {
"url": "https://github.com/dfldylan/GroupInvitation.git",
"desc": "根据特定关键词自动邀请用户加入指定的群聊"
}
}
}

0 comments on commit 042f2df

Please sign in to comment.