@@ -43,7 +43,14 @@ def run(): | |||||
# os.environ['WECHATY_PUPPET_SERVICE_ENDPOINT'] = '127.0.0.1:9001' | # os.environ['WECHATY_PUPPET_SERVICE_ENDPOINT'] = '127.0.0.1:9001' | ||||
channel = channel_factory.create_channel(channel_name) | channel = channel_factory.create_channel(channel_name) | ||||
if channel_name in ["wx", "wxy", "terminal", "wechatmp", "wechatmp_service"]: | |||||
if channel_name in [ | |||||
"wx", | |||||
"wxy", | |||||
"terminal", | |||||
"wechatmp", | |||||
"wechatmp_service", | |||||
"wechatcom", | |||||
]: | |||||
PluginManager().load_plugins() | PluginManager().load_plugins() | ||||
# startup channel | # startup channel | ||||
@@ -29,4 +29,8 @@ def create_channel(channel_type): | |||||
from channel.wechatmp.wechatmp_channel import WechatMPChannel | from channel.wechatmp.wechatmp_channel import WechatMPChannel | ||||
return WechatMPChannel(passive_reply=False) | return WechatMPChannel(passive_reply=False) | ||||
elif channel_type == "wechatcom": | |||||
from channel.wechatcom.wechatcom_channel import WechatComChannel | |||||
return WechatComChannel() | |||||
raise RuntimeError | raise RuntimeError |
@@ -29,7 +29,7 @@ from plugins import * | |||||
@itchat.msg_register([TEXT, VOICE, PICTURE, NOTE]) | @itchat.msg_register([TEXT, VOICE, PICTURE, NOTE]) | ||||
def handler_single_msg(msg): | def handler_single_msg(msg): | ||||
try: | try: | ||||
cmsg = WeChatMessage(msg, False) | |||||
cmsg = WechatMessage(msg, False) | |||||
except NotImplementedError as e: | except NotImplementedError as e: | ||||
logger.debug("[WX]single message {} skipped: {}".format(msg["MsgId"], e)) | logger.debug("[WX]single message {} skipped: {}".format(msg["MsgId"], e)) | ||||
return None | return None | ||||
@@ -40,7 +40,7 @@ def handler_single_msg(msg): | |||||
@itchat.msg_register([TEXT, VOICE, PICTURE, NOTE], isGroupChat=True) | @itchat.msg_register([TEXT, VOICE, PICTURE, NOTE], isGroupChat=True) | ||||
def handler_group_msg(msg): | def handler_group_msg(msg): | ||||
try: | try: | ||||
cmsg = WeChatMessage(msg, True) | |||||
cmsg = WechatMessage(msg, True) | |||||
except NotImplementedError as e: | except NotImplementedError as e: | ||||
logger.debug("[WX]group message {} skipped: {}".format(msg["MsgId"], e)) | logger.debug("[WX]group message {} skipped: {}".format(msg["MsgId"], e)) | ||||
return None | return None | ||||
@@ -8,7 +8,7 @@ from lib import itchat | |||||
from lib.itchat.content import * | from lib.itchat.content import * | ||||
class WeChatMessage(ChatMessage): | |||||
class WechatMessage(ChatMessage): | |||||
def __init__(self, itchat_msg, is_group=False): | def __init__(self, itchat_msg, is_group=False): | ||||
super().__init__(itchat_msg) | super().__init__(itchat_msg) | ||||
self.msg_id = itchat_msg["MsgId"] | self.msg_id = itchat_msg["MsgId"] | ||||
@@ -0,0 +1,105 @@ | |||||
#!/usr/bin/env python | |||||
# -*- coding=utf-8 -*- | |||||
import web | |||||
from wechatpy.enterprise import WeChatClient, create_reply, parse_message | |||||
from wechatpy.enterprise.crypto import WeChatCrypto | |||||
from wechatpy.enterprise.exceptions import InvalidCorpIdException | |||||
from wechatpy.exceptions import InvalidSignatureException | |||||
from bridge.context import Context | |||||
from bridge.reply import Reply, ReplyType | |||||
from channel.chat_channel import ChatChannel | |||||
from channel.wechatcom.wechatcom_message import WechatComMessage | |||||
from common.log import logger | |||||
from common.singleton import singleton | |||||
from config import conf | |||||
@singleton | |||||
class WechatComChannel(ChatChannel): | |||||
NOT_SUPPORT_REPLYTYPE = [ReplyType.IMAGE, ReplyType.VOICE] | |||||
def __init__(self): | |||||
super().__init__() | |||||
self.corp_id = conf().get("wechatcom_corp_id") | |||||
self.secret = conf().get("wechatcom_secret") | |||||
self.agent_id = conf().get("wechatcom_agent_id") | |||||
self.token = conf().get("wechatcom_token") | |||||
self.aes_key = conf().get("wechatcom_aes_key") | |||||
print(self.corp_id, self.secret, self.agent_id, self.token, self.aes_key) | |||||
logger.info( | |||||
"[wechatcom] init: corp_id: {}, secret: {}, agent_id: {}, token: {}, aes_key: {}".format( | |||||
self.corp_id, self.secret, self.agent_id, self.token, self.aes_key | |||||
) | |||||
) | |||||
self.crypto = WeChatCrypto(self.token, self.aes_key, self.corp_id) | |||||
self.client = WeChatClient(self.corp_id, self.secret) # todo: 这里可能有线程安全问题 | |||||
def startup(self): | |||||
# start message listener | |||||
urls = ("/wxcom", "channel.wechatcom.wechatcom_channel.Query") | |||||
app = web.application(urls, globals(), autoreload=False) | |||||
port = conf().get("wechatcom_port", 8080) | |||||
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port)) | |||||
def send(self, reply: Reply, context: Context): | |||||
print("send reply: ", reply.content, context["receiver"]) | |||||
receiver = context["receiver"] | |||||
reply_text = reply.content | |||||
self.client.message.send_text(self.agent_id, receiver, reply_text) | |||||
logger.info("[send] Do send to {}: {}".format(receiver, reply_text)) | |||||
class Query: | |||||
def GET(self): | |||||
channel = WechatComChannel() | |||||
params = web.input() | |||||
signature = params.msg_signature | |||||
timestamp = params.timestamp | |||||
nonce = params.nonce | |||||
echostr = params.echostr | |||||
print(params) | |||||
try: | |||||
echostr = channel.crypto.check_signature( | |||||
signature, timestamp, nonce, echostr | |||||
) | |||||
except InvalidSignatureException: | |||||
raise web.Forbidden() | |||||
return echostr | |||||
def POST(self): | |||||
channel = WechatComChannel() | |||||
params = web.input() | |||||
signature = params.msg_signature | |||||
timestamp = params.timestamp | |||||
nonce = params.nonce | |||||
try: | |||||
message = channel.crypto.decrypt_message( | |||||
web.data(), signature, timestamp, nonce | |||||
) | |||||
except (InvalidSignatureException, InvalidCorpIdException): | |||||
raise web.Forbidden() | |||||
print(message) | |||||
msg = parse_message(message) | |||||
print(msg) | |||||
if msg.type == "event": | |||||
if msg.event == "subscribe": | |||||
reply = create_reply("感谢关注", msg).render() | |||||
res = channel.crypto.encrypt_message(reply, nonce, timestamp) | |||||
return res | |||||
else: | |||||
try: | |||||
wechatcom_msg = WechatComMessage(msg, client=channel.client) | |||||
except NotImplementedError as e: | |||||
logger.debug("[wechatcom] " + str(e)) | |||||
return "success" | |||||
context = channel._compose_context( | |||||
wechatcom_msg.ctype, | |||||
wechatcom_msg.content, | |||||
isgroup=False, | |||||
msg=wechatcom_msg, | |||||
) | |||||
if context: | |||||
channel.produce(context) | |||||
return "success" |
@@ -0,0 +1,54 @@ | |||||
import re | |||||
import requests | |||||
from wechatpy.enterprise import WeChatClient | |||||
from bridge.context import ContextType | |||||
from channel.chat_message import ChatMessage | |||||
from common.log import logger | |||||
from common.tmp_dir import TmpDir | |||||
from lib import itchat | |||||
from lib.itchat.content import * | |||||
class WechatComMessage(ChatMessage): | |||||
def __init__(self, msg, client: WeChatClient, is_group=False): | |||||
super().__init__(msg) | |||||
self.msg_id = msg.id | |||||
self.create_time = msg.time | |||||
self.is_group = is_group | |||||
if msg.type == "text": | |||||
self.ctype = ContextType.TEXT | |||||
self.content = msg.content | |||||
elif msg.type == "voice": | |||||
self.ctype = ContextType.VOICE | |||||
self.content = ( | |||||
TmpDir().path() + msg.media_id + "." + msg.format | |||||
) # content直接存临时目录路径 | |||||
def download_voice(): | |||||
# 如果响应状态码是200,则将响应内容写入本地文件 | |||||
response = client.media.download(msg.media_id) | |||||
if response.status_code == 200: | |||||
with open(self.content, "wb") as f: | |||||
f.write(response.content) | |||||
else: | |||||
logger.info( | |||||
f"[wechatcom] Failed to download voice file, {response.content}" | |||||
) | |||||
self._prepare_fn = download_voice | |||||
elif msg.type == "image": | |||||
self.ctype = ContextType.IMAGE | |||||
self.content = msg.image # content直接存临时目录路径 | |||||
print(self.content) | |||||
# self._prepare_fn = lambda: itchat_msg.download(self.content) # TODO: download image | |||||
else: | |||||
raise NotImplementedError( | |||||
"Unsupported message type: Type:{} ".format(msg.type) | |||||
) | |||||
self.from_user_id = msg.source | |||||
self.to_user_id = msg.target | |||||
self.other_user_id = msg.source |
@@ -98,7 +98,9 @@ class WechatMPChannel(ChatChannel): | |||||
if self.passive_reply: | if self.passive_reply: | ||||
receiver = context["receiver"] | receiver = context["receiver"] | ||||
self.cache_dict[receiver] = reply.content | self.cache_dict[receiver] = reply.content | ||||
logger.info("[send] reply to {} saved to cache: {}".format(receiver, reply)) | |||||
logger.info( | |||||
"[wechatmp] reply to {} saved to cache: {}".format(receiver, reply) | |||||
) | |||||
else: | else: | ||||
receiver = context["receiver"] | receiver = context["receiver"] | ||||
reply_text = reply.content | reply_text = reply.content | ||||
@@ -115,7 +117,7 @@ class WechatMPChannel(ChatChannel): | |||||
params=params, | params=params, | ||||
data=json.dumps(json_data, ensure_ascii=False).encode("utf8"), | data=json.dumps(json_data, ensure_ascii=False).encode("utf8"), | ||||
) | ) | ||||
logger.info("[send] Do send to {}: {}".format(receiver, reply_text)) | |||||
logger.info("[wechatmp] Do send to {}: {}".format(receiver, reply_text)) | |||||
return | return | ||||
def _success_callback(self, session_id, context, **kwargs): # 线程异常结束时的回调函数 | def _success_callback(self, session_id, context, **kwargs): # 线程异常结束时的回调函数 | ||||
@@ -75,6 +75,13 @@ available_setting = { | |||||
"wechatmp_port": 8080, # 微信公众平台的端口,需要端口转发到80或443 | "wechatmp_port": 8080, # 微信公众平台的端口,需要端口转发到80或443 | ||||
"wechatmp_app_id": "", # 微信公众平台的appID,仅服务号需要 | "wechatmp_app_id": "", # 微信公众平台的appID,仅服务号需要 | ||||
"wechatmp_app_secret": "", # 微信公众平台的appsecret,仅服务号需要 | "wechatmp_app_secret": "", # 微信公众平台的appsecret,仅服务号需要 | ||||
# wechatcom的配置 | |||||
"wechatcom_token": "", # 企业微信的token | |||||
"wechatcom_port": 9898, # 企业微信的服务端口,不需要端口转发 | |||||
"wechatcom_corp_id": "", # 企业微信的corpID | |||||
"wechatcom_secret": "", # 企业微信的secret | |||||
"wechatcom_agent_id": "", # 企业微信的appID | |||||
"wechatcom_aes_key": "", # 企业微信的aes_key | |||||
# chatgpt指令自定义触发词 | # chatgpt指令自定义触发词 | ||||
"clear_memory_commands": ["#清除记忆"], # 重置会话指令,必须以#开头 | "clear_memory_commands": ["#清除记忆"], # 重置会话指令,必须以#开头 | ||||
# channel配置 | # channel配置 | ||||
@@ -16,8 +16,9 @@ wechaty>=0.10.7 | |||||
wechaty_puppet>=0.4.23 | wechaty_puppet>=0.4.23 | ||||
pysilk_mod>=1.6.0 # needed by send voice | pysilk_mod>=1.6.0 # needed by send voice | ||||
# wechatmp | |||||
# wechatmp wechatcom | |||||
web.py | web.py | ||||
wechatpy | |||||
# chatgpt-tool-hub plugin | # chatgpt-tool-hub plugin | ||||