Parcourir la source

Merge pull request #1357 from zhayujie/feat-1.3.5

feat: add plugin instructions and fix some issues
master
zhayujie GitHub il y a 1 an
Parent
révision
176941ea3b
Aucune clé connue n'a été trouvée dans la base pour cette signature ID de la clé GPG: 4AEE18F83AFDEB23
9 fichiers modifiés avec 76 ajouts et 24 suppressions
  1. +6
    -0
      bridge/bridge.py
  2. +6
    -2
      channel/chat_channel.py
  3. +2
    -3
      channel/chat_message.py
  4. +1
    -1
      channel/wechat/wechat_channel.py
  5. +5
    -1
      channel/wechat/wechat_message.py
  6. +1
    -1
      plugins/linkai/README.md
  7. +36
    -14
      plugins/linkai/linkai.py
  8. +18
    -1
      plugins/linkai/midjourney.py
  9. +1
    -1
      plugins/plugin.py

+ 6
- 0
bridge/bridge.py Voir le fichier

@@ -54,3 +54,9 @@ class Bridge(object):


def fetch_translate(self, text, from_lang="", to_lang="en") -> Reply: def fetch_translate(self, text, from_lang="", to_lang="en") -> Reply:
return self.get_bot("translate").translate(text, from_lang, to_lang) return self.get_bot("translate").translate(text, from_lang, to_lang)

def reset_bot(self):
"""
重置bot路由
"""
self.__init__()

+ 6
- 2
channel/chat_channel.py Voir le fichier

@@ -108,8 +108,12 @@ class ChatChannel(Channel):
if not conf().get("group_at_off", False): if not conf().get("group_at_off", False):
flag = True flag = True
pattern = f"@{re.escape(self.name)}(\u2005|\u0020)" pattern = f"@{re.escape(self.name)}(\u2005|\u0020)"
content = re.sub(pattern, r"", content)

subtract_res = re.sub(pattern, r"", content)
if subtract_res == content and context["msg"].self_display_name:
# 前缀移除后没有变化,使用群昵称再次移除
pattern = f"@{re.escape(context['msg'].self_display_name)}(\u2005|\u0020)"
subtract_res = re.sub(pattern, r"", content)
content = subtract_res
if not flag: if not flag:
if context["origin_ctype"] == ContextType.VOICE: if context["origin_ctype"] == ContextType.VOICE:
logger.info("[WX]receive group voice, but checkprefix didn't match") logger.info("[WX]receive group voice, but checkprefix didn't match")


+ 2
- 3
channel/chat_message.py Voir le fichier

@@ -24,9 +24,7 @@ is_at: 是否被at
- (群消息时,一般会存在实际发送者,是群内某个成员的id和昵称,下列项仅在群消息时存在) - (群消息时,一般会存在实际发送者,是群内某个成员的id和昵称,下列项仅在群消息时存在)
actual_user_id: 实际发送者id (群聊必填) actual_user_id: 实际发送者id (群聊必填)
actual_user_nickname:实际发送者昵称 actual_user_nickname:实际发送者昵称



self_display_name: 自身的展示名,设置群昵称时,该字段表示群昵称


_prepare_fn: 准备函数,用于准备消息的内容,比如下载图片等, _prepare_fn: 准备函数,用于准备消息的内容,比如下载图片等,
_prepared: 是否已经调用过准备函数 _prepared: 是否已经调用过准备函数
@@ -49,6 +47,7 @@ class ChatMessage(object):
other_user_id = None other_user_id = None
other_user_nickname = None other_user_nickname = None
my_msg = False my_msg = False
self_display_name = None


is_group = False is_group = False
is_at = False is_at = False


+ 1
- 1
channel/wechat/wechat_channel.py Voir le fichier

@@ -58,7 +58,7 @@ def _check(func):
if conf().get("hot_reload") == True and int(create_time) < int(time.time()) - 60: # 跳过1分钟前的历史消息 if conf().get("hot_reload") == True and int(create_time) < int(time.time()) - 60: # 跳过1分钟前的历史消息
logger.debug("[WX]history message {} skipped".format(msgId)) logger.debug("[WX]history message {} skipped".format(msgId))
return return
if cmsg.my_msg:
if cmsg.my_msg and not cmsg.is_group:
logger.debug("[WX]my message {} skipped".format(msgId)) logger.debug("[WX]my message {} skipped".format(msgId))
return return
return func(self, cmsg) return func(self, cmsg)


+ 5
- 1
channel/wechat/wechat_message.py Voir le fichier

@@ -57,7 +57,8 @@ class WechatMessage(ChatMessage):
self.from_user_nickname = nickname self.from_user_nickname = nickname
if self.to_user_id == user_id: if self.to_user_id == user_id:
self.to_user_nickname = nickname self.to_user_nickname = nickname
try: # 陌生人时候, 'User'字段可能不存在
try: # 陌生人时候, User字段可能不存在
# my_msg 为True是表示是自己发送的消息
self.my_msg = itchat_msg["ToUserName"] == itchat_msg["User"]["UserName"] and \ self.my_msg = itchat_msg["ToUserName"] == itchat_msg["User"]["UserName"] and \
itchat_msg["ToUserName"] != itchat_msg["FromUserName"] itchat_msg["ToUserName"] != itchat_msg["FromUserName"]
self.other_user_id = itchat_msg["User"]["UserName"] self.other_user_id = itchat_msg["User"]["UserName"]
@@ -66,6 +67,9 @@ class WechatMessage(ChatMessage):
self.from_user_nickname = self.other_user_nickname self.from_user_nickname = self.other_user_nickname
if self.other_user_id == self.to_user_id: if self.other_user_id == self.to_user_id:
self.to_user_nickname = self.other_user_nickname self.to_user_nickname = self.other_user_nickname
if itchat_msg["User"].get("Self"):
# 自身的展示名,当设置了群昵称时,该字段表示群昵称
self.self_display_name = itchat_msg["User"].get("Self").get("DisplayName")
except KeyError as e: # 处理偶尔没有对方信息的情况 except KeyError as e: # 处理偶尔没有对方信息的情况
logger.warn("[WX]get other_user_id failed: " + str(e)) logger.warn("[WX]get other_user_id failed: " + str(e))
if self.from_user_id == user_id: if self.from_user_id == user_id:


+ 1
- 1
plugins/linkai/README.md Voir le fichier

@@ -33,7 +33,7 @@


## 插件使用 ## 插件使用


> 使用插件中的知识库管理功能需要首先开启`linkai`对话,依赖全局 `config.json` 中的 `use_linkai` 和 `linkai_api_key` 配置;而midjourney绘画功能则只需填写 `linkai_api_key` 配置。具体可参考 [详细文档](https://link-ai.tech/platform/link-app/wechat)。
> 使用插件中的知识库管理功能需要首先开启`linkai`对话,依赖全局 `config.json` 中的 `use_linkai` 和 `linkai_api_key` 配置;而midjourney绘画功能则只需填写 `linkai_api_key` 配置,`use_linkai` 无论是否关闭均可使用。具体可参考 [详细文档](https://link-ai.tech/platform/link-app/wechat)。


完成配置后运行项目,会自动运行插件,输入 `#help linkai` 可查看插件功能。 完成配置后运行项目,会自动运行插件,输入 `#help linkai` 可查看插件功能。




+ 36
- 14
plugins/linkai/linkai.py Voir le fichier

@@ -1,19 +1,10 @@
import asyncio
import json
import threading
from concurrent.futures import ThreadPoolExecutor

import plugins import plugins
from bridge.context import ContextType from bridge.context import ContextType
from bridge.reply import Reply, ReplyType from bridge.reply import Reply, ReplyType
from channel.chat_message import ChatMessage
from common.log import logger
from config import conf, global_config
from config import global_config
from plugins import * from plugins import *
from .midjourney import MJBot, TaskType

# 任务线程池
task_thread_pool = ThreadPoolExecutor(max_workers=4)
from .midjourney import MJBot
from bridge import bridge




@plugins.register( @plugins.register(
@@ -66,11 +57,28 @@ class LinkAI(Plugin):
if len(cmd) == 1 or (len(cmd) == 2 and cmd[1] == "help"): if len(cmd) == 1 or (len(cmd) == 2 and cmd[1] == "help"):
_set_reply_text(self.get_help_text(verbose=True), e_context, level=ReplyType.INFO) _set_reply_text(self.get_help_text(verbose=True), e_context, level=ReplyType.INFO)
return return

if len(cmd) == 2 and (cmd[1] == "open" or cmd[1] == "close"):
# 知识库开关指令
if not _is_admin(e_context):
_set_reply_text("需要管理员权限执行", e_context, level=ReplyType.ERROR)
return
is_open = True
tips_text = "开启"
if cmd[1] == "close":
tips_text = "关闭"
is_open = False
conf()["use_linkai"] = is_open
bridge.Bridge().reset_bot()
_set_reply_text(f"知识库功能已{tips_text}", e_context, level=ReplyType.INFO)
return

if len(cmd) == 3 and cmd[1] == "app": if len(cmd) == 3 and cmd[1] == "app":
# 知识库应用切换指令
if not context.kwargs.get("isgroup"): if not context.kwargs.get("isgroup"):
_set_reply_text("该指令需在群聊中使用", e_context, level=ReplyType.ERROR) _set_reply_text("该指令需在群聊中使用", e_context, level=ReplyType.ERROR)
return return
if context.kwargs.get("msg").actual_user_id not in global_config["admin_users"]:
if not _is_admin(e_context):
_set_reply_text("需要管理员权限执行", e_context, level=ReplyType.ERROR) _set_reply_text("需要管理员权限执行", e_context, level=ReplyType.ERROR)
return return
app_code = cmd[2] app_code = cmd[2]
@@ -84,7 +92,8 @@ class LinkAI(Plugin):
super().save_config(self.config) super().save_config(self.config)
_set_reply_text(f"应用设置成功: {app_code}", e_context, level=ReplyType.INFO) _set_reply_text(f"应用设置成功: {app_code}", e_context, level=ReplyType.INFO)
else: else:
_set_reply_text(f"指令错误,请输入{_get_trigger_prefix()}linkai help 获取帮助", e_context, level=ReplyType.INFO)
_set_reply_text(f"指令错误,请输入{_get_trigger_prefix()}linkai help 获取帮助", e_context,
level=ReplyType.INFO)
return return


# LinkAI 对话任务处理 # LinkAI 对话任务处理
@@ -127,6 +136,19 @@ class LinkAI(Plugin):




# 静态方法 # 静态方法
def _is_admin(e_context: EventContext) -> bool:
"""
判断消息是否由管理员用户发送
:param e_context: 消息上下文
:return: True: 是, False: 否
"""
context = e_context["context"]
if context["isgroup"]:
return context.kwargs.get("msg").actual_user_id in global_config["admin_users"]
else:
return context["receiver"] in global_config["admin_users"]


def _set_reply_text(content: str, e_context: EventContext, level: ReplyType = ReplyType.ERROR): def _set_reply_text(content: str, e_context: EventContext, level: ReplyType = ReplyType.ERROR):
reply = Reply(level, content) reply = Reply(level, content)
e_context["reply"] = reply e_context["reply"] = reply


+ 18
- 1
plugins/linkai/midjourney.py Voir le fichier

@@ -69,7 +69,7 @@ class MJBot:
:param e_context: 上下文 :param e_context: 上下文
:return: 任务类型枚举 :return: 任务类型枚举
""" """
if not self.config or not self.config.get("enabled"):
if not self.config:
return None return None
trigger_prefix = conf().get("plugin_trigger_prefix", "$") trigger_prefix = conf().get("plugin_trigger_prefix", "$")
context = e_context['context'] context = e_context['context']
@@ -92,9 +92,26 @@ class MJBot:
session_id = context["session_id"] session_id = context["session_id"]
cmd = context.content.split(maxsplit=1) cmd = context.content.split(maxsplit=1)
if len(cmd) == 1 and context.type == ContextType.TEXT: if len(cmd) == 1 and context.type == ContextType.TEXT:
# midjourney 帮助指令
self._set_reply_text(self.get_help_text(verbose=True), e_context, level=ReplyType.INFO) self._set_reply_text(self.get_help_text(verbose=True), e_context, level=ReplyType.INFO)
return return


if len(cmd) == 2 and (cmd[1] == "open" or cmd[1] == "close"):
# midjourney 开关指令
is_open = True
tips_text = "开启"
if cmd[1] == "close":
tips_text = "关闭"
is_open = False
self.config["enabled"] = is_open
self._set_reply_text(f"Midjourney绘画已{tips_text}", e_context, level=ReplyType.INFO)
return

if not self.config.get("enabled"):
logger.warn("Midjourney绘画未开启,请查看 plugins/linkai/config.json 中的配置")
self._set_reply_text(f"Midjourney绘画未开启", e_context, level=ReplyType.INFO)
return

if not self._check_rate_limit(session_id, e_context): if not self._check_rate_limit(session_id, e_context):
logger.warn("[MJ] midjourney task exceed rate limit") logger.warn("[MJ] midjourney task exceed rate limit")
return return


+ 1
- 1
plugins/plugin.py Voir le fichier

@@ -19,7 +19,7 @@ class Plugin:
# 全局配置不存在 或者 未开启全局配置开关,则获取插件目录下的配置 # 全局配置不存在 或者 未开启全局配置开关,则获取插件目录下的配置
plugin_config_path = os.path.join(self.path, "config.json") plugin_config_path = os.path.join(self.path, "config.json")
if os.path.exists(plugin_config_path): if os.path.exists(plugin_config_path):
with open(plugin_config_path, "r") as f:
with open(plugin_config_path, "r", encoding="utf-8") as f:
plugin_conf = json.load(f) plugin_conf = json.load(f)
logger.debug(f"loading plugin config, plugin_name={self.name}, conf={plugin_conf}") logger.debug(f"loading plugin config, plugin_name={self.name}, conf={plugin_conf}")
return plugin_conf return plugin_conf


Chargement…
Annuler
Enregistrer