@@ -7,9 +7,13 @@ class ContextType(Enum): | |||||
TEXT = 1 # 文本消息 | TEXT = 1 # 文本消息 | ||||
VOICE = 2 # 音频消息 | VOICE = 2 # 音频消息 | ||||
IMAGE = 3 # 图片消息 | IMAGE = 3 # 图片消息 | ||||
FILE = 4 # 文件信息 | |||||
VIDEO = 5 # 视频信息 | |||||
IMAGE_CREATE = 10 # 创建图片命令 | IMAGE_CREATE = 10 # 创建图片命令 | ||||
JOIN_GROUP = 20 # 加入群聊 | JOIN_GROUP = 20 # 加入群聊 | ||||
PATPAT = 21 # 拍了拍 | PATPAT = 21 # 拍了拍 | ||||
FUNCTION = 22 # 函数调用 | |||||
def __str__(self): | def __str__(self): | ||||
return self.name | return self.name | ||||
@@ -33,4 +33,8 @@ def create_channel(channel_type): | |||||
from channel.wechatcom.wechatcomapp_channel import WechatComAppChannel | from channel.wechatcom.wechatcomapp_channel import WechatComAppChannel | ||||
return WechatComAppChannel() | return WechatComAppChannel() | ||||
elif channel_type == "wework": | |||||
from channel.wework.wework_channel import WeworkChannel | |||||
return WeworkChannel() | |||||
raise RuntimeError | raise RuntimeError |
@@ -109,6 +109,9 @@ class ChatChannel(Channel): | |||||
flag = True | flag = True | ||||
pattern = f"@{re.escape(self.name)}(\u2005|\u0020)" | pattern = f"@{re.escape(self.name)}(\u2005|\u0020)" | ||||
subtract_res = re.sub(pattern, r"", content) | subtract_res = re.sub(pattern, r"", content) | ||||
for at in context["msg"].at_list: | |||||
pattern = f"@{re.escape(at)}(\u2005|\u0020)" | |||||
subtract_res = re.sub(pattern, r"", subtract_res) | |||||
if subtract_res == content and context["msg"].self_display_name: | if subtract_res == content and context["msg"].self_display_name: | ||||
# 前缀移除后没有变化,使用群昵称再次移除 | # 前缀移除后没有变化,使用群昵称再次移除 | ||||
pattern = f"@{re.escape(context['msg'].self_display_name)}(\u2005|\u0020)" | pattern = f"@{re.escape(context['msg'].self_display_name)}(\u2005|\u0020)" | ||||
@@ -197,7 +200,8 @@ class ChatChannel(Channel): | |||||
reply = self._generate_reply(new_context) | reply = self._generate_reply(new_context) | ||||
else: | else: | ||||
return | return | ||||
elif context.type == ContextType.IMAGE: # 图片消息,当前无默认逻辑 | |||||
elif context.type == ContextType.IMAGE or context.type == ContextType.FUNCTION \ | |||||
or context.type == ContextType.FILE: # 图片/文件消息及函数调用等,当前无默认逻辑 | |||||
pass | pass | ||||
else: | else: | ||||
logger.error("[WX] unknown context type: {}".format(context.type)) | logger.error("[WX] unknown context type: {}".format(context.type)) | ||||
@@ -53,6 +53,7 @@ class ChatMessage(object): | |||||
is_at = False | is_at = False | ||||
actual_user_id = None | actual_user_id = None | ||||
actual_user_nickname = None | actual_user_nickname = None | ||||
at_list = None | |||||
_prepare_fn = None | _prepare_fn = None | ||||
_prepared = False | _prepared = False | ||||
@@ -67,7 +68,7 @@ class ChatMessage(object): | |||||
self._prepare_fn() | self._prepare_fn() | ||||
def __str__(self): | def __str__(self): | ||||
return "ChatMessage: id={}, create_time={}, ctype={}, content={}, from_user_id={}, from_user_nickname={}, to_user_id={}, to_user_nickname={}, other_user_id={}, other_user_nickname={}, is_group={}, is_at={}, actual_user_id={}, actual_user_nickname={}".format( | |||||
return "ChatMessage: id={}, create_time={}, ctype={}, content={}, from_user_id={}, from_user_nickname={}, to_user_id={}, to_user_nickname={}, other_user_id={}, other_user_nickname={}, is_group={}, is_at={}, actual_user_id={}, actual_user_nickname={}, at_list={}".format( | |||||
self.msg_id, | self.msg_id, | ||||
self.create_time, | self.create_time, | ||||
self.ctype, | self.ctype, | ||||
@@ -82,4 +83,5 @@ class ChatMessage(object): | |||||
self.is_at, | self.is_at, | ||||
self.actual_user_id, | self.actual_user_id, | ||||
self.actual_user_nickname, | self.actual_user_nickname, | ||||
self.at_list | |||||
) | ) |
@@ -0,0 +1,17 @@ | |||||
import os | |||||
import time | |||||
os.environ['ntwork_LOG'] = "ERROR" | |||||
import ntwork | |||||
wework = ntwork.WeWork() | |||||
def forever(): | |||||
try: | |||||
while True: | |||||
time.sleep(0.1) | |||||
except KeyboardInterrupt: | |||||
ntwork.exit_() | |||||
os._exit(0) | |||||
@@ -0,0 +1,306 @@ | |||||
import io | |||||
import os | |||||
import random | |||||
import tempfile | |||||
import threading | |||||
os.environ['ntwork_LOG'] = "ERROR" | |||||
import ntwork | |||||
import requests | |||||
import uuid | |||||
from bridge.context import * | |||||
from bridge.reply import * | |||||
from channel.chat_channel import ChatChannel | |||||
from channel.wework.wework_message import * | |||||
from channel.wework.wework_message import WeworkMessage | |||||
from common.singleton import singleton | |||||
from common.log import logger | |||||
from common.time_check import time_checker | |||||
from config import conf | |||||
from channel.wework.run import wework | |||||
from channel.wework import run | |||||
from PIL import Image | |||||
def get_wxid_by_name(room_members, group_wxid, name): | |||||
if group_wxid in room_members: | |||||
for member in room_members[group_wxid]['member_list']: | |||||
if member['room_nickname'] == name or member['username'] == name: | |||||
return member['user_id'] | |||||
return None # 如果没有找到对应的group_wxid或name,则返回None | |||||
def download_and_compress_image(url, filename, quality=30): | |||||
# 确定保存图片的目录 | |||||
directory = os.path.join(os.getcwd(), "tmp") | |||||
# 如果目录不存在,则创建目录 | |||||
if not os.path.exists(directory): | |||||
os.makedirs(directory) | |||||
# 下载图片 | |||||
response = requests.get(url) | |||||
image = Image.open(io.BytesIO(response.content)) | |||||
# 压缩图片 | |||||
image_path = os.path.join(directory, f"{filename}.jpg") | |||||
image.save(image_path, "JPEG", quality=quality) | |||||
return image_path | |||||
def download_video(url, filename): | |||||
# 确定保存视频的目录 | |||||
directory = os.path.join(os.getcwd(), "tmp") | |||||
# 如果目录不存在,则创建目录 | |||||
if not os.path.exists(directory): | |||||
os.makedirs(directory) | |||||
# 下载视频 | |||||
response = requests.get(url, stream=True) | |||||
total_size = 0 | |||||
video_path = os.path.join(directory, f"{filename}.mp4") | |||||
with open(video_path, 'wb') as f: | |||||
for block in response.iter_content(1024): | |||||
total_size += len(block) | |||||
# 如果视频的总大小超过30MB (30 * 1024 * 1024 bytes),则停止下载并返回 | |||||
if total_size > 30 * 1024 * 1024: | |||||
logger.info("[WX] Video is larger than 30MB, skipping...") | |||||
return None | |||||
f.write(block) | |||||
return video_path | |||||
def create_message(wework_instance, message, is_group): | |||||
logger.debug(f"正在为{'群聊' if is_group else '单聊'}创建 WeworkMessage") | |||||
cmsg = WeworkMessage(message, wework=wework_instance, is_group=is_group) | |||||
logger.debug(f"cmsg:{cmsg}") | |||||
return cmsg | |||||
def handle_message(cmsg, is_group): | |||||
logger.debug(f"准备用 WeworkChannel 处理{'群聊' if is_group else '单聊'}消息") | |||||
if is_group: | |||||
WeworkChannel().handle_group(cmsg) | |||||
else: | |||||
WeworkChannel().handle_single(cmsg) | |||||
logger.debug(f"已用 WeworkChannel 处理完{'群聊' if is_group else '单聊'}消息") | |||||
def _check(func): | |||||
def wrapper(self, cmsg: ChatMessage): | |||||
msgId = cmsg.msg_id | |||||
create_time = cmsg.create_time # 消息时间戳 | |||||
if create_time is None: | |||||
return func(self, cmsg) | |||||
if int(create_time) < int(time.time()) - 60: # 跳过1分钟前的历史消息 | |||||
logger.debug("[WX]history message {} skipped".format(msgId)) | |||||
return | |||||
return func(self, cmsg) | |||||
return wrapper | |||||
@wework.msg_register( | |||||
[ntwork.MT_RECV_TEXT_MSG, ntwork.MT_RECV_IMAGE_MSG, 11072, ntwork.MT_RECV_VOICE_MSG]) | |||||
def all_msg_handler(wework_instance: ntwork.WeWork, message): | |||||
logger.debug(f"收到消息: {message}") | |||||
if 'data' in message: | |||||
# 首先查找conversation_id,如果没有找到,则查找room_conversation_id | |||||
conversation_id = message['data'].get('conversation_id', message['data'].get('room_conversation_id')) | |||||
if conversation_id is not None: | |||||
is_group = "R:" in conversation_id | |||||
try: | |||||
cmsg = create_message(wework_instance=wework_instance, message=message, is_group=is_group) | |||||
except NotImplementedError as e: | |||||
logger.error(f"[WX]{message.get('MsgId', 'unknown')} 跳过: {e}") | |||||
return None | |||||
delay = random.randint(1, 2) | |||||
timer = threading.Timer(delay, handle_message, args=(cmsg, is_group)) | |||||
timer.start() | |||||
else: | |||||
logger.debug("消息数据中无 conversation_id") | |||||
return None | |||||
return None | |||||
def accept_friend_with_retries(wework_instance, user_id, corp_id): | |||||
result = wework_instance.accept_friend(user_id, corp_id) | |||||
logger.debug(f'result:{result}') | |||||
# @wework.msg_register(ntwork.MT_RECV_FRIEND_MSG) | |||||
# def friend(wework_instance: ntwork.WeWork, message): | |||||
# data = message["data"] | |||||
# user_id = data["user_id"] | |||||
# corp_id = data["corp_id"] | |||||
# logger.info(f"接收到好友请求,消息内容:{data}") | |||||
# delay = random.randint(1, 180) | |||||
# threading.Timer(delay, accept_friend_with_retries, args=(wework_instance, user_id, corp_id)).start() | |||||
# | |||||
# return None | |||||
def get_with_retry(get_func, max_retries=5, delay=5): | |||||
retries = 0 | |||||
result = None | |||||
while retries < max_retries: | |||||
result = get_func() | |||||
if result: | |||||
break | |||||
logger.warning(f"获取数据失败,重试第{retries + 1}次······") | |||||
retries += 1 | |||||
time.sleep(delay) # 等待一段时间后重试 | |||||
return result | |||||
@singleton | |||||
class WeworkChannel(ChatChannel): | |||||
NOT_SUPPORT_REPLYTYPE = [] | |||||
def __init__(self): | |||||
super().__init__() | |||||
def startup(self): | |||||
smart = conf().get("wework_smart", True) | |||||
wework.open(smart) | |||||
logger.info("等待登录······") | |||||
wework.wait_login() | |||||
login_info = wework.get_login_info() | |||||
self.user_id = login_info['user_id'] | |||||
self.name = login_info['nickname'] | |||||
logger.info(f"登录信息:>>>user_id:{self.user_id}>>>>>>>>name:{self.name}") | |||||
logger.info("静默延迟60s,等待客户端刷新数据,请勿进行任何操作······") | |||||
time.sleep(60) | |||||
contacts = get_with_retry(wework.get_external_contacts) | |||||
rooms = get_with_retry(wework.get_rooms) | |||||
directory = os.path.join(os.getcwd(), "tmp") | |||||
if not contacts or not rooms: | |||||
logger.error("获取contacts或rooms失败,程序退出") | |||||
ntwork.exit_() | |||||
os.exit(0) | |||||
if not os.path.exists(directory): | |||||
os.makedirs(directory) | |||||
# 将contacts保存到json文件中 | |||||
with open(os.path.join(directory, 'wework_contacts.json'), 'w', encoding='utf-8') as f: | |||||
json.dump(contacts, f, ensure_ascii=False, indent=4) | |||||
with open(os.path.join(directory, 'wework_rooms.json'), 'w', encoding='utf-8') as f: | |||||
json.dump(rooms, f, ensure_ascii=False, indent=4) | |||||
# 创建一个空字典来保存结果 | |||||
result = {} | |||||
# 遍历列表中的每个字典 | |||||
for room in rooms['room_list']: | |||||
# 获取聊天室ID | |||||
room_wxid = room['conversation_id'] | |||||
# 获取聊天室成员 | |||||
room_members = wework.get_room_members(room_wxid) | |||||
# 将聊天室成员保存到结果字典中 | |||||
result[room_wxid] = room_members | |||||
# 将结果保存到json文件中 | |||||
with open(os.path.join(directory, 'wework_room_members.json'), 'w', encoding='utf-8') as f: | |||||
json.dump(result, f, ensure_ascii=False, indent=4) | |||||
logger.info("wework程序初始化完成········") | |||||
run.forever() | |||||
@time_checker | |||||
@_check | |||||
def handle_single(self, cmsg: ChatMessage): | |||||
if cmsg.ctype == ContextType.VOICE: | |||||
if not conf().get("speech_recognition"): | |||||
return | |||||
logger.debug("[WX]receive voice msg: {}".format(cmsg.content)) | |||||
elif cmsg.ctype == ContextType.IMAGE: | |||||
logger.debug("[WX]receive image msg: {}".format(cmsg.content)) | |||||
elif cmsg.ctype == ContextType.PATPAT: | |||||
logger.debug("[WX]receive patpat msg: {}".format(cmsg.content)) | |||||
elif cmsg.ctype == ContextType.TEXT: | |||||
logger.debug("[WX]receive text msg: {}, cmsg={}".format(json.dumps(cmsg._rawmsg, ensure_ascii=False), cmsg)) | |||||
else: | |||||
logger.debug("[WX]receive msg: {}, cmsg={}".format(cmsg.content, cmsg)) | |||||
context = self._compose_context(cmsg.ctype, cmsg.content, isgroup=False, msg=cmsg) | |||||
if context: | |||||
self.produce(context) | |||||
@time_checker | |||||
@_check | |||||
def handle_group(self, cmsg: ChatMessage): | |||||
if cmsg.ctype == ContextType.VOICE: | |||||
if not conf().get("speech_recognition"): | |||||
return | |||||
logger.debug("[WX]receive voice for group msg: {}".format(cmsg.content)) | |||||
elif cmsg.ctype == ContextType.IMAGE: | |||||
logger.debug("[WX]receive image for group msg: {}".format(cmsg.content)) | |||||
elif cmsg.ctype in [ContextType.JOIN_GROUP, ContextType.PATPAT]: | |||||
logger.debug("[WX]receive note msg: {}".format(cmsg.content)) | |||||
elif cmsg.ctype == ContextType.TEXT: | |||||
pass | |||||
else: | |||||
logger.debug("[WX]receive group msg: {}".format(cmsg.content)) | |||||
context = self._compose_context(cmsg.ctype, cmsg.content, isgroup=True, msg=cmsg) | |||||
if context: | |||||
self.produce(context) | |||||
# 统一的发送函数,每个Channel自行实现,根据reply的type字段发送不同类型的消息 | |||||
def send(self, reply: Reply, context: Context): | |||||
logger.debug(f"context: {context}") | |||||
receiver = context["receiver"] | |||||
actual_user_id = context["msg"].actual_user_id | |||||
if reply.type == ReplyType.TEXT or reply.type == ReplyType.TEXT_: | |||||
match = re.search(r"^@(.*?)\n", reply.content) | |||||
logger.debug(f"match: {match}") | |||||
if match: | |||||
new_content = re.sub(r"^@(.*?)\n", "\n", reply.content) | |||||
at_list = [actual_user_id] | |||||
logger.debug(f"new_content: {new_content}") | |||||
wework.send_room_at_msg(receiver, new_content, at_list) | |||||
else: | |||||
wework.send_text(receiver, reply.content) | |||||
logger.info("[WX] sendMsg={}, receiver={}".format(reply, receiver)) | |||||
elif reply.type == ReplyType.ERROR or reply.type == ReplyType.INFO: | |||||
wework.send_text(receiver, reply.content) | |||||
logger.info("[WX] sendMsg={}, receiver={}".format(reply, receiver)) | |||||
elif reply.type == ReplyType.IMAGE: # 从文件读取图片 | |||||
image_storage = reply.content | |||||
image_storage.seek(0) | |||||
# Read data from image_storage | |||||
data = image_storage.read() | |||||
# Create a temporary file | |||||
with tempfile.NamedTemporaryFile(delete=False) as temp: | |||||
temp_path = temp.name | |||||
temp.write(data) | |||||
# Send the image | |||||
wework.send_image(receiver, temp_path) | |||||
logger.info("[WX] sendImage, receiver={}".format(receiver)) | |||||
# Remove the temporary file | |||||
os.remove(temp_path) | |||||
elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片 | |||||
img_url = reply.content | |||||
filename = str(uuid.uuid4()) | |||||
# 调用你的函数,下载图片并保存为本地文件 | |||||
image_path = download_and_compress_image(img_url, filename) | |||||
wework.send_image(receiver, file_path=image_path) | |||||
logger.info("[WX] sendImage url={}, receiver={}".format(img_url, receiver)) | |||||
elif reply.type == ReplyType.VIDEO_URL: | |||||
video_url = reply.content | |||||
filename = str(uuid.uuid4()) | |||||
video_path = download_video(video_url, filename) | |||||
if video_path is None: | |||||
# 如果视频太大,下载可能会被跳过,此时 video_path 将为 None | |||||
wework.send_text(receiver, "抱歉,视频太大了!!!") | |||||
else: | |||||
wework.send_video(receiver, video_path) | |||||
logger.info("[WX] sendVideo, receiver={}".format(receiver)) | |||||
elif reply.type == ReplyType.VOICE: | |||||
wework.send_file(receiver, reply.content) | |||||
logger.info("[WX] sendFile={}, receiver={}".format(reply.content, receiver)) |
@@ -0,0 +1,180 @@ | |||||
import datetime | |||||
import json | |||||
import os | |||||
import re | |||||
import time | |||||
import pilk | |||||
from bridge.context import ContextType | |||||
from channel.chat_message import ChatMessage | |||||
from common.log import logger | |||||
def get_with_retry(get_func, max_retries=5, delay=5): | |||||
retries = 0 | |||||
result = None | |||||
while retries < max_retries: | |||||
result = get_func() | |||||
if result: | |||||
break | |||||
logger.warning(f"获取数据失败,重试第{retries + 1}次······") | |||||
retries += 1 | |||||
time.sleep(delay) # 等待一段时间后重试 | |||||
return result | |||||
def get_room_info(wework, conversation_id): | |||||
logger.debug(f"传入的 conversation_id: {conversation_id}") | |||||
rooms = wework.get_rooms() | |||||
if not rooms or 'room_list' not in rooms: | |||||
logger.error(f"获取群聊信息失败: {rooms}") | |||||
return None | |||||
time.sleep(1) | |||||
logger.debug(f"获取到的群聊信息: {rooms}") | |||||
for room in rooms['room_list']: | |||||
if room['conversation_id'] == conversation_id: | |||||
return room | |||||
return None | |||||
def cdn_download(wework, message, file_name): | |||||
data = message["data"] | |||||
url = data["cdn"]["url"] | |||||
auth_key = data["cdn"]["auth_key"] | |||||
aes_key = data["cdn"]["aes_key"] | |||||
file_size = data["cdn"]["size"] | |||||
# 获取当前工作目录,然后与文件名拼接得到保存路径 | |||||
current_dir = os.getcwd() | |||||
save_path = os.path.join(current_dir, "tmp", file_name) | |||||
result = wework.wx_cdn_download(url, auth_key, aes_key, file_size, save_path) | |||||
logger.debug(result) | |||||
def c2c_download_and_convert(wework, message, file_name): | |||||
data = message["data"] | |||||
aes_key = data["cdn"]["aes_key"] | |||||
file_size = data["cdn"]["size"] | |||||
file_type = 5 | |||||
file_id = data["cdn"]["file_id"] | |||||
current_dir = os.getcwd() | |||||
save_path = os.path.join(current_dir, "tmp", file_name) | |||||
result = wework.c2c_cdn_download(file_id, aes_key, file_size, file_type, save_path) | |||||
logger.debug(result) | |||||
# 在下载完SILK文件之后,立即将其转换为WAV文件 | |||||
base_name, _ = os.path.splitext(save_path) | |||||
wav_file = base_name + ".wav" | |||||
pilk.silk_to_wav(save_path, wav_file, rate=24000) | |||||
class WeworkMessage(ChatMessage): | |||||
def __init__(self, wework_msg, wework, is_group=False): | |||||
try: | |||||
super().__init__(wework_msg) | |||||
self.msg_id = wework_msg['data'].get('conversation_id', wework_msg['data'].get('room_conversation_id')) | |||||
# 使用.get()防止 'send_time' 键不存在时抛出错误 | |||||
self.create_time = wework_msg['data'].get("send_time") | |||||
self.is_group = is_group | |||||
self.wework = wework | |||||
if wework_msg["type"] == 11041: # 文本消息类型 | |||||
if any(substring in wework_msg['data']['content'] for substring in ("该消息类型暂不能展示", "不支持的消息类型")): | |||||
return | |||||
self.ctype = ContextType.TEXT | |||||
self.content = wework_msg['data']['content'] | |||||
elif wework_msg["type"] == 11044: # 语音消息类型,需要缓存文件 | |||||
file_name = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".silk" | |||||
base_name, _ = os.path.splitext(file_name) | |||||
file_name_2 = base_name + ".wav" | |||||
current_dir = os.getcwd() | |||||
self.ctype = ContextType.VOICE | |||||
self.content = os.path.join(current_dir, "tmp", file_name_2) | |||||
self._prepare_fn = lambda: c2c_download_and_convert(wework, wework_msg, file_name) | |||||
elif wework_msg["type"] == 11042: # 图片消息类型,需要下载文件 | |||||
file_name = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ".jpg" | |||||
current_dir = os.getcwd() | |||||
self.ctype = ContextType.IMAGE | |||||
self.content = os.path.join(current_dir, "tmp", file_name) | |||||
self._prepare_fn = lambda: cdn_download(wework, wework_msg, file_name) | |||||
elif wework_msg["type"] == 11072: # 新成员入群通知 | |||||
self.ctype = ContextType.JOIN_GROUP | |||||
member_list = wework_msg['data']['member_list'] | |||||
self.actual_user_nickname = member_list[0]['name'] | |||||
self.actual_user_id = member_list[0]['user_id'] | |||||
self.content = f"{self.actual_user_nickname}加入了群聊!" | |||||
directory = os.path.join(os.getcwd(), "tmp") | |||||
rooms = get_with_retry(wework.get_rooms) | |||||
if not rooms: | |||||
logger.error("更新群信息失败···") | |||||
else: | |||||
result = {} | |||||
for room in rooms['room_list']: | |||||
# 获取聊天室ID | |||||
room_wxid = room['conversation_id'] | |||||
# 获取聊天室成员 | |||||
room_members = wework.get_room_members(room_wxid) | |||||
# 将聊天室成员保存到结果字典中 | |||||
result[room_wxid] = room_members | |||||
with open(os.path.join(directory, 'wework_room_members.json'), 'w', encoding='utf-8') as f: | |||||
json.dump(result, f, ensure_ascii=False, indent=4) | |||||
logger.info("有新成员加入,已自动更新群成员列表缓存!") | |||||
else: | |||||
raise NotImplementedError( | |||||
"Unsupported message type: Type:{} MsgType:{}".format(wework_msg["type"], wework_msg["MsgType"])) | |||||
data = wework_msg['data'] | |||||
login_info = self.wework.get_login_info() | |||||
logger.debug(f"login_info: {login_info}") | |||||
nickname = f"{login_info['username']}({login_info['nickname']})" if login_info['nickname'] else login_info['username'] | |||||
user_id = login_info['user_id'] | |||||
sender_id = data.get('sender') | |||||
conversation_id = data.get('conversation_id') | |||||
sender_name = data.get("sender_name") | |||||
self.from_user_id = user_id if sender_id == user_id else conversation_id | |||||
self.from_user_nickname = nickname if sender_id == user_id else sender_name | |||||
self.to_user_id = user_id | |||||
self.to_user_nickname = nickname | |||||
self.other_user_nickname = sender_name | |||||
self.other_user_id = conversation_id | |||||
if self.is_group: | |||||
conversation_id = data.get('conversation_id') or data.get('room_conversation_id') | |||||
self.other_user_id = conversation_id | |||||
if conversation_id: | |||||
room_info = get_room_info(wework=wework, conversation_id=conversation_id) | |||||
self.other_user_nickname = room_info.get('nickname', None) if room_info else None | |||||
at_list = data.get('at_list', []) | |||||
tmp_list = [] | |||||
for at in at_list: | |||||
tmp_list.append(at['nickname']) | |||||
at_list = tmp_list | |||||
logger.debug(f"at_list: {at_list}") | |||||
logger.debug(f"nickname: {nickname}") | |||||
self.is_at = nickname in at_list | |||||
self.at_list = at_list | |||||
# 检查消息内容是否包含@用户名。处理复制粘贴的消息,这类消息可能不会触发@通知,但内容中可能包含 "@用户名"。 | |||||
content = data.get('content', '') | |||||
name = nickname | |||||
pattern = f"@{re.escape(name)}(\u2005|\u0020)" | |||||
if re.search(pattern, content): | |||||
logger.debug(f"Wechaty message {self.msg_id} includes at") | |||||
self.is_at = True | |||||
if not self.actual_user_id: | |||||
self.actual_user_id = data.get("sender") | |||||
self.actual_user_nickname = sender_name if self.ctype != ContextType.JOIN_GROUP else self.actual_user_nickname | |||||
else: | |||||
logger.error("群聊消息中没有找到 conversation_id 或 room_conversation_id") | |||||
logger.debug(f"WeworkMessage has been successfully instantiated with message id: {self.msg_id}") | |||||
except Exception as e: | |||||
logger.error(f"在 WeworkMessage 的初始化过程中出现错误:{e}") | |||||
raise e |
@@ -23,8 +23,8 @@ available_setting = { | |||||
# Bot触发配置 | # Bot触发配置 | ||||
"single_chat_prefix": ["bot", "@bot"], # 私聊时文本需要包含该前缀才能触发机器人回复 | "single_chat_prefix": ["bot", "@bot"], # 私聊时文本需要包含该前缀才能触发机器人回复 | ||||
"single_chat_reply_prefix": "[bot] ", # 私聊时自动回复的前缀,用于区分真人 | "single_chat_reply_prefix": "[bot] ", # 私聊时自动回复的前缀,用于区分真人 | ||||
"single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行 | |||||
"group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复 | |||||
"single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行 | |||||
"group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复 | |||||
"group_chat_reply_prefix": "", # 群聊时自动回复的前缀 | "group_chat_reply_prefix": "", # 群聊时自动回复的前缀 | ||||
"group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行 | "group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行 | ||||
"group_chat_keyword": [], # 群聊时包含该关键词则会触发机器人回复 | "group_chat_keyword": [], # 群聊时包含该关键词则会触发机器人回复 | ||||
@@ -120,7 +120,9 @@ available_setting = { | |||||
"use_linkai": False, | "use_linkai": False, | ||||
"linkai_api_key": "", | "linkai_api_key": "", | ||||
"linkai_app_code": "", | "linkai_app_code": "", | ||||
"linkai_api_base": "https://api.link-ai.chat" # linkAI服务地址,若国内无法访问或延迟较高可改为 https://api.link-ai.tech | |||||
"linkai_api_base": "https://api.link-ai.chat", # linkAI服务地址,若国内无法访问或延迟较高可改为 https://api.link-ai.tech | |||||
# wework的通用配置 | |||||
"wework_smart": True # 配置wework是否使用已登录的企业微信,False为多开 | |||||
} | } | ||||