Browse Source

Merge pull request #1675 from zhayujie/feat-client

feat: channel client
master
zhayujie GitHub 10 months ago
parent
commit
8fcdda625d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 27 deletions
  1. +2
    -1
      .gitignore
  2. +9
    -0
      app.py
  3. +19
    -3
      bot/linkai/link_ai_bot.py
  4. +1
    -0
      channel/channel.py
  5. +17
    -19
      channel/channel_factory.py
  6. +8
    -4
      channel/feishu/feishu_channel.py
  7. +11
    -0
      channel/wechat/wechat_channel.py
  8. +28
    -0
      common/linkai_client.py

+ 2
- 1
.gitignore View File

@@ -29,4 +29,5 @@ plugins/banwords/lib/__pycache__
!plugins/hello !plugins/hello
!plugins/role !plugins/role
!plugins/keyword !plugins/keyword
!plugins/linkai
!plugins/linkai
client_config.json

+ 9
- 0
app.py View File

@@ -8,6 +8,7 @@ from channel import channel_factory
from common import const from common import const
from config import load_config from config import load_config
from plugins import * from plugins import *
import threading




def sigterm_handler_wrap(_signo): def sigterm_handler_wrap(_signo):
@@ -46,8 +47,16 @@ def run():
if channel_name in ["wx", "wxy", "terminal", "wechatmp", "wechatmp_service", "wechatcom_app", "wework", const.FEISHU,const.DINGTALK]: if channel_name in ["wx", "wxy", "terminal", "wechatmp", "wechatmp_service", "wechatcom_app", "wework", const.FEISHU,const.DINGTALK]:
PluginManager().load_plugins() PluginManager().load_plugins()


if conf().get("use_linkai"):
try:
from common import linkai_client
threading.Thread(target=linkai_client.start, args=(channel, )).start()
except Exception as e:
pass

# startup channel # startup channel
channel.startup() channel.startup()

except Exception as e: except Exception as e:
logger.error("App startup failed!") logger.error("App startup failed!")
logger.exception(e) logger.exception(e)


+ 19
- 3
bot/linkai/link_ai_bot.py View File

@@ -16,7 +16,6 @@ import threading
from common import memory, utils from common import memory, utils
import base64 import base64



class LinkAIBot(Bot): class LinkAIBot(Bot):
# authentication failed # authentication failed
AUTH_FAILED_CODE = 401 AUTH_FAILED_CODE = 401
@@ -83,7 +82,6 @@ class LinkAIBot(Bot):
if session_message[0].get("role") == "system": if session_message[0].get("role") == "system":
if app_code or model == "wenxin": if app_code or model == "wenxin":
session_message.pop(0) session_message.pop(0)

body = { body = {
"app_code": app_code, "app_code": app_code,
"messages": session_message, "messages": session_message,
@@ -92,7 +90,25 @@ class LinkAIBot(Bot):
"top_p": conf().get("top_p", 1), "top_p": conf().get("top_p", 1),
"frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 "frequency_penalty": conf().get("frequency_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容
"presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容 "presence_penalty": conf().get("presence_penalty", 0.0), # [-2,2]之间,该值越大则更倾向于产生不同的内容
"session_id": session_id,
"channel_type": conf().get("channel_type")
} }
try:
from linkai import LinkAIClient
client_id = LinkAIClient.fetch_client_id()
if client_id:
body["client_id"] = client_id
# start: client info deliver
if context.kwargs.get("msg"):
body["session_id"] = context.kwargs.get("msg").from_user_id
if context.kwargs.get("msg").is_group:
body["is_group"] = True
body["group_name"] = context.kwargs.get("msg").from_user_nickname
body["sender_name"] = context.kwargs.get("msg").actual_user_nickname
else:
body["sender_name"] = context.kwargs.get("msg").from_user_nickname
except Exception as e:
pass
file_id = context.kwargs.get("file_id") file_id = context.kwargs.get("file_id")
if file_id: if file_id:
body["file_id"] = file_id body["file_id"] = file_id
@@ -230,7 +246,7 @@ class LinkAIBot(Bot):
} }
if self.args.get("max_tokens"): if self.args.get("max_tokens"):
body["max_tokens"] = self.args.get("max_tokens") body["max_tokens"] = self.args.get("max_tokens")
headers = {"Authorization": "Bearer " + conf().get("linkai_api_key")}
headers = {"Authorization": "Bearer " + conf().get("linkai_api_key")}


# do http request # do http request
base_url = conf().get("linkai_api_base", "https://api.link-ai.chat") base_url = conf().get("linkai_api_base", "https://api.link-ai.chat")


+ 1
- 0
channel/channel.py View File

@@ -8,6 +8,7 @@ from bridge.reply import *




class Channel(object): class Channel(object):
channel_type = ""
NOT_SUPPORT_REPLYTYPE = [ReplyType.VOICE, ReplyType.IMAGE] NOT_SUPPORT_REPLYTYPE = [ReplyType.VOICE, ReplyType.IMAGE]


def startup(self): def startup(self):


+ 17
- 19
channel/channel_factory.py View File

@@ -2,46 +2,44 @@
channel factory channel factory
""" """
from common import const from common import const
from .channel import Channel


def create_channel(channel_type):

def create_channel(channel_type) -> Channel:
""" """
create a channel instance create a channel instance
:param channel_type: channel type code :param channel_type: channel type code
:return: channel instance :return: channel instance
""" """
ch = Channel()
if channel_type == "wx": if channel_type == "wx":
from channel.wechat.wechat_channel import WechatChannel from channel.wechat.wechat_channel import WechatChannel

return WechatChannel()
ch = WechatChannel()
elif channel_type == "wxy": elif channel_type == "wxy":
from channel.wechat.wechaty_channel import WechatyChannel from channel.wechat.wechaty_channel import WechatyChannel

return WechatyChannel()
ch = WechatyChannel()
elif channel_type == "terminal": elif channel_type == "terminal":
from channel.terminal.terminal_channel import TerminalChannel from channel.terminal.terminal_channel import TerminalChannel

return TerminalChannel()
ch = TerminalChannel()
elif channel_type == "wechatmp": elif channel_type == "wechatmp":
from channel.wechatmp.wechatmp_channel import WechatMPChannel from channel.wechatmp.wechatmp_channel import WechatMPChannel

return WechatMPChannel(passive_reply=True)
ch = WechatMPChannel(passive_reply=True)
elif channel_type == "wechatmp_service": elif channel_type == "wechatmp_service":
from channel.wechatmp.wechatmp_channel import WechatMPChannel from channel.wechatmp.wechatmp_channel import WechatMPChannel

return WechatMPChannel(passive_reply=False)
ch = WechatMPChannel(passive_reply=False)
elif channel_type == "wechatcom_app": elif channel_type == "wechatcom_app":
from channel.wechatcom.wechatcomapp_channel import WechatComAppChannel from channel.wechatcom.wechatcomapp_channel import WechatComAppChannel

return WechatComAppChannel()
ch = WechatComAppChannel()
elif channel_type == "wework": elif channel_type == "wework":
from channel.wework.wework_channel import WeworkChannel from channel.wework.wework_channel import WeworkChannel
return WeworkChannel()

ch = WeworkChannel()
elif channel_type == const.FEISHU: elif channel_type == const.FEISHU:
from channel.feishu.feishu_channel import FeiShuChanel from channel.feishu.feishu_channel import FeiShuChanel
return FeiShuChanel()
ch = FeiShuChanel()
elif channel_type == const.DINGTALK: elif channel_type == const.DINGTALK:
from channel.dingtalk.dingtalk_channel import DingTalkChanel from channel.dingtalk.dingtalk_channel import DingTalkChanel
return DingTalkChanel()

raise RuntimeError
ch = DingTalkChanel()
else:
raise RuntimeError
ch.channel_type = channel_type
return ch

+ 8
- 4
channel/feishu/feishu_channel.py View File

@@ -51,10 +51,14 @@ class FeiShuChanel(ChatChannel):
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port)) web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))


def send(self, reply: Reply, context: Context): def send(self, reply: Reply, context: Context):
msg = context["msg"]
msg = context.get("msg")
is_group = context["isgroup"] is_group = context["isgroup"]
if msg:
access_token = msg.access_token
else:
access_token = self.fetch_access_token()
headers = { headers = {
"Authorization": "Bearer " + msg.access_token,
"Authorization": "Bearer " + access_token,
"Content-Type": "application/json", "Content-Type": "application/json",
} }
msg_type = "text" msg_type = "text"
@@ -63,7 +67,7 @@ class FeiShuChanel(ChatChannel):
content_key = "text" content_key = "text"
if reply.type == ReplyType.IMAGE_URL: if reply.type == ReplyType.IMAGE_URL:
# 图片上传 # 图片上传
reply_content = self._upload_image_url(reply.content, msg.access_token)
reply_content = self._upload_image_url(reply.content, access_token)
if not reply_content: if not reply_content:
logger.warning("[FeiShu] upload file failed") logger.warning("[FeiShu] upload file failed")
return return
@@ -79,7 +83,7 @@ class FeiShuChanel(ChatChannel):
res = requests.post(url=url, headers=headers, json=data, timeout=(5, 10)) res = requests.post(url=url, headers=headers, json=data, timeout=(5, 10))
else: else:
url = "https://open.feishu.cn/open-apis/im/v1/messages" url = "https://open.feishu.cn/open-apis/im/v1/messages"
params = {"receive_id_type": context.get("receive_id_type")}
params = {"receive_id_type": context.get("receive_id_type") or "open_id"}
data = { data = {
"receive_id": context.get("receiver"), "receive_id": context.get("receiver"),
"msg_type": msg_type, "msg_type": msg_type,


+ 11
- 0
channel/wechat/wechat_channel.py View File

@@ -109,6 +109,7 @@ class WechatChannel(ChatChannel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.receivedMsgs = ExpiredDict(60 * 60) self.receivedMsgs = ExpiredDict(60 * 60)
self.auto_login_times = 0


def startup(self): def startup(self):
itchat.instance.receivingRetryCount = 600 # 修改断线超时时间 itchat.instance.receivingRetryCount = 600 # 修改断线超时时间
@@ -120,6 +121,8 @@ class WechatChannel(ChatChannel):
hotReload=hotReload, hotReload=hotReload,
statusStorageDir=status_path, statusStorageDir=status_path,
qrCallback=qrCallback, qrCallback=qrCallback,
exitCallback=self.exitCallback,
loginCallback=self.loginCallback
) )
self.user_id = itchat.instance.storageClass.userName self.user_id = itchat.instance.storageClass.userName
self.name = itchat.instance.storageClass.nickName self.name = itchat.instance.storageClass.nickName
@@ -127,6 +130,14 @@ class WechatChannel(ChatChannel):
# start message listener # start message listener
itchat.run() itchat.run()


def exitCallback(self):
self.auto_login_times += 1
if self.auto_login_times < 100:
self.startup()

def loginCallback(self):
pass

# handle_* 系列函数处理收到的消息后构造Context,然后传入produce函数中处理Context和发送回复 # handle_* 系列函数处理收到的消息后构造Context,然后传入produce函数中处理Context和发送回复
# Context包含了消息的所有信息,包括以下属性 # Context包含了消息的所有信息,包括以下属性
# type 消息类型, 包括TEXT、VOICE、IMAGE_CREATE # type 消息类型, 包括TEXT、VOICE、IMAGE_CREATE


+ 28
- 0
common/linkai_client.py View File

@@ -0,0 +1,28 @@
from bridge.context import Context, ContextType
from bridge.reply import Reply, ReplyType
from common.log import logger
from linkai import LinkAIClient, PushMsg
from config import conf


class ChatClient(LinkAIClient):
def __init__(self, api_key, host, channel):
super().__init__(api_key, host)
self.channel = channel
self.client_type = channel.channel_type

def on_message(self, push_msg: PushMsg):
session_id = push_msg.session_id
msg_content = push_msg.msg_content
logger.info(f"receive msg push, session_id={session_id}, msg_content={msg_content}")
context = Context()
context.type = ContextType.TEXT
context["receiver"] = session_id
context["isgroup"] = push_msg.is_group
self.channel.send(Reply(ReplyType.TEXT, content=msg_content), context)


def start(channel):
client = ChatClient(api_key=conf().get("linkai_api_key"),
host="link-ai.chat", channel=channel)
client.start()

Loading…
Cancel
Save