mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-03-19 21:38:18 +08:00
formatting code
This commit is contained in:
@@ -24,17 +24,17 @@
|
||||
在本仓库中预置了一些插件,如果要安装其他仓库的插件,有两种方法。
|
||||
|
||||
- 第一种方法是在将下载的插件文件都解压到"plugins"文件夹的一个单独的文件夹,最终插件的代码都位于"plugins/PLUGIN_NAME/*"中。启动程序后,如果插件的目录结构正确,插件会自动被扫描加载。除此以外,注意你还需要安装文件夹中`requirements.txt`中的依赖。
|
||||
|
||||
|
||||
- 第二种方法是`Godcmd`插件,它是预置的管理员插件,能够让程序在运行时就能安装插件,它能够自动安装依赖。
|
||||
|
||||
|
||||
安装插件的命令是"#installp [仓库源](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/plugins/source.json)记录的插件名/仓库地址"。这是管理员命令,认证方法在[这里](https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/godcmd)。
|
||||
|
||||
|
||||
- 安装[仓库源](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/plugins/source.json)记录的插件:#installp sdwebui
|
||||
|
||||
- 安装指定仓库的插件:#installp https://github.com/lanvent/plugin_sdwebui.git
|
||||
|
||||
|
||||
在安装之后,需要执行"#scanp"命令来扫描加载新安装的插件(或者重新启动程序)。
|
||||
|
||||
|
||||
安装插件后需要注意有些插件有自己的配置模板,一般要去掉".template"新建一个配置文件。
|
||||
|
||||
## 插件化实现
|
||||
@@ -107,14 +107,14 @@
|
||||
```
|
||||
|
||||
回复`Reply`的定义如下所示,它允许Bot可以回复多类不同的消息。同时也加入了`INFO`和`ERROR`消息类型区分系统提示和系统错误。
|
||||
|
||||
|
||||
```python
|
||||
class ReplyType(Enum):
|
||||
TEXT = 1 # 文本
|
||||
VOICE = 2 # 音频文件
|
||||
IMAGE = 3 # 图片文件
|
||||
IMAGE_URL = 4 # 图片URL
|
||||
|
||||
|
||||
INFO = 9
|
||||
ERROR = 10
|
||||
class Reply:
|
||||
@@ -159,12 +159,12 @@
|
||||
|
||||
目前支持三类触发事件:
|
||||
```
|
||||
1.收到消息
|
||||
---> `ON_HANDLE_CONTEXT`
|
||||
2.产生回复
|
||||
---> `ON_DECORATE_REPLY`
|
||||
3.装饰回复
|
||||
---> `ON_SEND_REPLY`
|
||||
1.收到消息
|
||||
---> `ON_HANDLE_CONTEXT`
|
||||
2.产生回复
|
||||
---> `ON_DECORATE_REPLY`
|
||||
3.装饰回复
|
||||
---> `ON_SEND_REPLY`
|
||||
4.发送回复
|
||||
```
|
||||
|
||||
@@ -268,6 +268,6 @@ class Hello(Plugin):
|
||||
- 一个插件目录建议只注册一个插件类。建议使用单独的仓库维护插件,便于更新。
|
||||
|
||||
在测试调试好后提交`PR`,把自己的仓库加入到[仓库源](https://github.com/zhayujie/chatgpt-on-wechat/blob/master/plugins/source.json)中。
|
||||
|
||||
|
||||
- 插件的config文件、使用说明`README.md`、`requirement.txt`等放置在插件目录中。
|
||||
- 默认优先级不要超过管理员插件`Godcmd`的优先级(999),`Godcmd`插件提供了配置管理、插件管理等功能。
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from .plugin_manager import PluginManager
|
||||
from .event import *
|
||||
from .plugin import *
|
||||
from .plugin_manager import PluginManager
|
||||
|
||||
instance = PluginManager()
|
||||
|
||||
register = instance.register
|
||||
register = instance.register
|
||||
# load_plugins = instance.load_plugins
|
||||
# emit_event = instance.emit_event
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .banwords import *
|
||||
from .banwords import *
|
||||
|
||||
@@ -2,56 +2,67 @@
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import plugins
|
||||
from bridge.context import ContextType
|
||||
from bridge.reply import Reply, ReplyType
|
||||
import plugins
|
||||
from plugins import *
|
||||
from common.log import logger
|
||||
from plugins import *
|
||||
|
||||
from .lib.WordsSearch import WordsSearch
|
||||
|
||||
|
||||
@plugins.register(name="Banwords", desire_priority=100, hidden=True, desc="判断消息中是否有敏感词、决定是否回复。", version="1.0", author="lanvent")
|
||||
@plugins.register(
|
||||
name="Banwords",
|
||||
desire_priority=100,
|
||||
hidden=True,
|
||||
desc="判断消息中是否有敏感词、决定是否回复。",
|
||||
version="1.0",
|
||||
author="lanvent",
|
||||
)
|
||||
class Banwords(Plugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
try:
|
||||
curdir=os.path.dirname(__file__)
|
||||
config_path=os.path.join(curdir,"config.json")
|
||||
conf=None
|
||||
curdir = os.path.dirname(__file__)
|
||||
config_path = os.path.join(curdir, "config.json")
|
||||
conf = None
|
||||
if not os.path.exists(config_path):
|
||||
conf={"action":"ignore"}
|
||||
with open(config_path,"w") as f:
|
||||
json.dump(conf,f,indent=4)
|
||||
conf = {"action": "ignore"}
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(conf, f, indent=4)
|
||||
else:
|
||||
with open(config_path,"r") as f:
|
||||
conf=json.load(f)
|
||||
with open(config_path, "r") as f:
|
||||
conf = json.load(f)
|
||||
self.searchr = WordsSearch()
|
||||
self.action = conf["action"]
|
||||
banwords_path = os.path.join(curdir,"banwords.txt")
|
||||
with open(banwords_path, 'r', encoding='utf-8') as f:
|
||||
words=[]
|
||||
banwords_path = os.path.join(curdir, "banwords.txt")
|
||||
with open(banwords_path, "r", encoding="utf-8") as f:
|
||||
words = []
|
||||
for line in f:
|
||||
word = line.strip()
|
||||
if word:
|
||||
words.append(word)
|
||||
self.searchr.SetKeywords(words)
|
||||
self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
|
||||
if conf.get("reply_filter",True):
|
||||
if conf.get("reply_filter", True):
|
||||
self.handlers[Event.ON_DECORATE_REPLY] = self.on_decorate_reply
|
||||
self.reply_action = conf.get("reply_action","ignore")
|
||||
self.reply_action = conf.get("reply_action", "ignore")
|
||||
logger.info("[Banwords] inited")
|
||||
except Exception as e:
|
||||
logger.warn("[Banwords] init failed, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/banwords .")
|
||||
logger.warn(
|
||||
"[Banwords] init failed, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/banwords ."
|
||||
)
|
||||
raise e
|
||||
|
||||
|
||||
|
||||
def on_handle_context(self, e_context: EventContext):
|
||||
|
||||
if e_context['context'].type not in [ContextType.TEXT,ContextType.IMAGE_CREATE]:
|
||||
if e_context["context"].type not in [
|
||||
ContextType.TEXT,
|
||||
ContextType.IMAGE_CREATE,
|
||||
]:
|
||||
return
|
||||
|
||||
content = e_context['context'].content
|
||||
|
||||
content = e_context["context"].content
|
||||
logger.debug("[Banwords] on_handle_context. content: %s" % content)
|
||||
if self.action == "ignore":
|
||||
f = self.searchr.FindFirst(content)
|
||||
@@ -61,31 +72,34 @@ class Banwords(Plugin):
|
||||
return
|
||||
elif self.action == "replace":
|
||||
if self.searchr.ContainsAny(content):
|
||||
reply = Reply(ReplyType.INFO, "发言中包含敏感词,请重试: \n"+self.searchr.Replace(content))
|
||||
e_context['reply'] = reply
|
||||
reply = Reply(
|
||||
ReplyType.INFO, "发言中包含敏感词,请重试: \n" + self.searchr.Replace(content)
|
||||
)
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
return
|
||||
|
||||
def on_decorate_reply(self, e_context: EventContext):
|
||||
|
||||
if e_context['reply'].type not in [ReplyType.TEXT]:
|
||||
def on_decorate_reply(self, e_context: EventContext):
|
||||
if e_context["reply"].type not in [ReplyType.TEXT]:
|
||||
return
|
||||
|
||||
reply = e_context['reply']
|
||||
|
||||
reply = e_context["reply"]
|
||||
content = reply.content
|
||||
if self.reply_action == "ignore":
|
||||
f = self.searchr.FindFirst(content)
|
||||
if f:
|
||||
logger.info("[Banwords] %s in reply" % f["Keyword"])
|
||||
e_context['reply'] = None
|
||||
e_context["reply"] = None
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
return
|
||||
elif self.reply_action == "replace":
|
||||
if self.searchr.ContainsAny(content):
|
||||
reply = Reply(ReplyType.INFO, "已替换回复中的敏感词: \n"+self.searchr.Replace(content))
|
||||
e_context['reply'] = reply
|
||||
reply = Reply(
|
||||
ReplyType.INFO, "已替换回复中的敏感词: \n" + self.searchr.Replace(content)
|
||||
)
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.CONTINUE
|
||||
return
|
||||
|
||||
|
||||
def get_help_text(self, **kwargs):
|
||||
return Banwords.desc
|
||||
return Banwords.desc
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"action": "replace",
|
||||
"reply_filter": true,
|
||||
"reply_action": "ignore"
|
||||
}
|
||||
"action": "replace",
|
||||
"reply_filter": true,
|
||||
"reply_action": "ignore"
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ see https://ai.baidu.com/unit/home#/home?track=61fe1b0d3407ce3face1d92cb5c291087
|
||||
``` json
|
||||
{
|
||||
"service_id": "s...", #"机器人ID"
|
||||
"api_key": "",
|
||||
"api_key": "",
|
||||
"secret_key": ""
|
||||
}
|
||||
```
|
||||
@@ -1 +1 @@
|
||||
from .bdunit import *
|
||||
from .bdunit import *
|
||||
|
||||
@@ -2,21 +2,29 @@
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from uuid import getnode as get_mac
|
||||
|
||||
import requests
|
||||
|
||||
import plugins
|
||||
from bridge.context import ContextType
|
||||
from bridge.reply import Reply, ReplyType
|
||||
from common.log import logger
|
||||
import plugins
|
||||
from plugins import *
|
||||
from uuid import getnode as get_mac
|
||||
|
||||
|
||||
"""利用百度UNIT实现智能对话
|
||||
如果命中意图,返回意图对应的回复,否则返回继续交付给下个插件处理
|
||||
"""
|
||||
|
||||
|
||||
@plugins.register(name="BDunit", desire_priority=0, hidden=True, desc="Baidu unit bot system", version="0.1", author="jackson")
|
||||
@plugins.register(
|
||||
name="BDunit",
|
||||
desire_priority=0,
|
||||
hidden=True,
|
||||
desc="Baidu unit bot system",
|
||||
version="0.1",
|
||||
author="jackson",
|
||||
)
|
||||
class BDunit(Plugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -40,11 +48,10 @@ class BDunit(Plugin):
|
||||
raise e
|
||||
|
||||
def on_handle_context(self, e_context: EventContext):
|
||||
|
||||
if e_context['context'].type != ContextType.TEXT:
|
||||
if e_context["context"].type != ContextType.TEXT:
|
||||
return
|
||||
|
||||
content = e_context['context'].content
|
||||
content = e_context["context"].content
|
||||
logger.debug("[BDunit] on_handle_context. content: %s" % content)
|
||||
parsed = self.getUnit2(content)
|
||||
intent = self.getIntent(parsed)
|
||||
@@ -53,7 +60,7 @@ class BDunit(Plugin):
|
||||
reply = Reply()
|
||||
reply.type = ReplyType.TEXT
|
||||
reply.content = self.getSay(parsed)
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
|
||||
else:
|
||||
e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑
|
||||
@@ -70,17 +77,15 @@ class BDunit(Plugin):
|
||||
string: access_token
|
||||
"""
|
||||
url = "https://aip.baidubce.com/oauth/2.0/token?client_id={}&client_secret={}&grant_type=client_credentials".format(
|
||||
self.api_key, self.secret_key)
|
||||
self.api_key, self.secret_key
|
||||
)
|
||||
payload = ""
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
|
||||
response = requests.request("POST", url, headers=headers, data=payload)
|
||||
|
||||
# print(response.text)
|
||||
return response.json()['access_token']
|
||||
return response.json()["access_token"]
|
||||
|
||||
def getUnit(self, query):
|
||||
"""
|
||||
@@ -90,11 +95,14 @@ class BDunit(Plugin):
|
||||
"""
|
||||
|
||||
url = (
|
||||
'https://aip.baidubce.com/rpc/2.0/unit/service/v3/chat?access_token='
|
||||
"https://aip.baidubce.com/rpc/2.0/unit/service/v3/chat?access_token="
|
||||
+ self.access_token
|
||||
)
|
||||
request = {"query": query, "user_id": str(
|
||||
get_mac())[:32], "terminal_id": "88888"}
|
||||
request = {
|
||||
"query": query,
|
||||
"user_id": str(get_mac())[:32],
|
||||
"terminal_id": "88888",
|
||||
}
|
||||
body = {
|
||||
"log_id": str(uuid.uuid1()),
|
||||
"version": "3.0",
|
||||
@@ -142,11 +150,7 @@ class BDunit(Plugin):
|
||||
:param parsed: UNIT 解析结果
|
||||
:returns: 意图数组
|
||||
"""
|
||||
if (
|
||||
parsed
|
||||
and "result" in parsed
|
||||
and "response_list" in parsed["result"]
|
||||
):
|
||||
if parsed and "result" in parsed and "response_list" in parsed["result"]:
|
||||
try:
|
||||
return parsed["result"]["response_list"][0]["schema"]["intent"]
|
||||
except Exception as e:
|
||||
@@ -163,11 +167,7 @@ class BDunit(Plugin):
|
||||
:param intent: 意图的名称
|
||||
:returns: True: 包含; False: 不包含
|
||||
"""
|
||||
if (
|
||||
parsed
|
||||
and "result" in parsed
|
||||
and "response_list" in parsed["result"]
|
||||
):
|
||||
if parsed and "result" in parsed and "response_list" in parsed["result"]:
|
||||
response_list = parsed["result"]["response_list"]
|
||||
for response in response_list:
|
||||
if (
|
||||
@@ -189,11 +189,7 @@ class BDunit(Plugin):
|
||||
:returns: 词槽列表。你可以通过 name 属性筛选词槽,
|
||||
再通过 normalized_word 属性取出相应的值
|
||||
"""
|
||||
if (
|
||||
parsed
|
||||
and "result" in parsed
|
||||
and "response_list" in parsed["result"]
|
||||
):
|
||||
if parsed and "result" in parsed and "response_list" in parsed["result"]:
|
||||
response_list = parsed["result"]["response_list"]
|
||||
if intent == "":
|
||||
try:
|
||||
@@ -236,11 +232,7 @@ class BDunit(Plugin):
|
||||
:param parsed: UNIT 解析结果
|
||||
:returns: UNIT 的回复文本
|
||||
"""
|
||||
if (
|
||||
parsed
|
||||
and "result" in parsed
|
||||
and "response_list" in parsed["result"]
|
||||
):
|
||||
if parsed and "result" in parsed and "response_list" in parsed["result"]:
|
||||
response_list = parsed["result"]["response_list"]
|
||||
answer = {}
|
||||
for response in response_list:
|
||||
@@ -266,11 +258,7 @@ class BDunit(Plugin):
|
||||
:param intent: 意图的名称
|
||||
:returns: UNIT 的回复文本
|
||||
"""
|
||||
if (
|
||||
parsed
|
||||
and "result" in parsed
|
||||
and "response_list" in parsed["result"]
|
||||
):
|
||||
if parsed and "result" in parsed and "response_list" in parsed["result"]:
|
||||
response_list = parsed["result"]["response_list"]
|
||||
if intent == "":
|
||||
try:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"service_id": "s...",
|
||||
"api_key": "",
|
||||
"secret_key": ""
|
||||
}
|
||||
"service_id": "s...",
|
||||
"api_key": "",
|
||||
"secret_key": ""
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .dungeon import *
|
||||
from .dungeon import *
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
# encoding:utf-8
|
||||
|
||||
import plugins
|
||||
from bridge.bridge import Bridge
|
||||
from bridge.context import ContextType
|
||||
from bridge.reply import Reply, ReplyType
|
||||
from common.expired_dict import ExpiredDict
|
||||
from config import conf
|
||||
import plugins
|
||||
from plugins import *
|
||||
from common.log import logger
|
||||
from common import const
|
||||
from common.expired_dict import ExpiredDict
|
||||
from common.log import logger
|
||||
from config import conf
|
||||
from plugins import *
|
||||
|
||||
|
||||
# https://github.com/bupticybee/ChineseAiDungeonChatGPT
|
||||
class StoryTeller():
|
||||
class StoryTeller:
|
||||
def __init__(self, bot, sessionid, story):
|
||||
self.bot = bot
|
||||
self.sessionid = sessionid
|
||||
@@ -27,67 +28,85 @@ class StoryTeller():
|
||||
if user_action[-1] != "。":
|
||||
user_action = user_action + "。"
|
||||
if self.first_interact:
|
||||
prompt = """现在来充当一个文字冒险游戏,描述时候注意节奏,不要太快,仔细描述各个人物的心情和周边环境。一次只需写四到六句话。
|
||||
开头是,""" + self.story + " " + user_action
|
||||
prompt = (
|
||||
"""现在来充当一个文字冒险游戏,描述时候注意节奏,不要太快,仔细描述各个人物的心情和周边环境。一次只需写四到六句话。
|
||||
开头是,"""
|
||||
+ self.story
|
||||
+ " "
|
||||
+ user_action
|
||||
)
|
||||
self.first_interact = False
|
||||
else:
|
||||
prompt = """继续,一次只需要续写四到六句话,总共就只讲5分钟内发生的事情。""" + user_action
|
||||
return prompt
|
||||
|
||||
|
||||
@plugins.register(name="Dungeon", desire_priority=0, namecn="文字冒险", desc="A plugin to play dungeon game", version="1.0", author="lanvent")
|
||||
@plugins.register(
|
||||
name="Dungeon",
|
||||
desire_priority=0,
|
||||
namecn="文字冒险",
|
||||
desc="A plugin to play dungeon game",
|
||||
version="1.0",
|
||||
author="lanvent",
|
||||
)
|
||||
class Dungeon(Plugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
|
||||
logger.info("[Dungeon] inited")
|
||||
# 目前没有设计session过期事件,这里先暂时使用过期字典
|
||||
if conf().get('expires_in_seconds'):
|
||||
self.games = ExpiredDict(conf().get('expires_in_seconds'))
|
||||
if conf().get("expires_in_seconds"):
|
||||
self.games = ExpiredDict(conf().get("expires_in_seconds"))
|
||||
else:
|
||||
self.games = dict()
|
||||
|
||||
def on_handle_context(self, e_context: EventContext):
|
||||
|
||||
if e_context['context'].type != ContextType.TEXT:
|
||||
if e_context["context"].type != ContextType.TEXT:
|
||||
return
|
||||
bottype = Bridge().get_bot_type("chat")
|
||||
if bottype not in (const.CHATGPT, const.OPEN_AI):
|
||||
return
|
||||
bot = Bridge().get_bot("chat")
|
||||
content = e_context['context'].content[:]
|
||||
clist = e_context['context'].content.split(maxsplit=1)
|
||||
sessionid = e_context['context']['session_id']
|
||||
content = e_context["context"].content[:]
|
||||
clist = e_context["context"].content.split(maxsplit=1)
|
||||
sessionid = e_context["context"]["session_id"]
|
||||
logger.debug("[Dungeon] on_handle_context. content: %s" % clist)
|
||||
trigger_prefix = conf().get('plugin_trigger_prefix', "$")
|
||||
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
|
||||
if clist[0] == f"{trigger_prefix}停止冒险":
|
||||
if sessionid in self.games:
|
||||
self.games[sessionid].reset()
|
||||
del self.games[sessionid]
|
||||
reply = Reply(ReplyType.INFO, "冒险结束!")
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
elif clist[0] == f"{trigger_prefix}开始冒险" or sessionid in self.games:
|
||||
if sessionid not in self.games or clist[0] == f"{trigger_prefix}开始冒险":
|
||||
if len(clist)>1 :
|
||||
if len(clist) > 1:
|
||||
story = clist[1]
|
||||
else:
|
||||
story = "你在树林里冒险,指不定会从哪里蹦出来一些奇怪的东西,你握紧手上的手枪,希望这次冒险能够找到一些值钱的东西,你往树林深处走去。"
|
||||
story = (
|
||||
"你在树林里冒险,指不定会从哪里蹦出来一些奇怪的东西,你握紧手上的手枪,希望这次冒险能够找到一些值钱的东西,你往树林深处走去。"
|
||||
)
|
||||
self.games[sessionid] = StoryTeller(bot, sessionid, story)
|
||||
reply = Reply(ReplyType.INFO, "冒险开始,你可以输入任意内容,让故事继续下去。故事背景是:" + story)
|
||||
e_context['reply'] = reply
|
||||
e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
|
||||
else:
|
||||
prompt = self.games[sessionid].action(content)
|
||||
e_context['context'].type = ContextType.TEXT
|
||||
e_context['context'].content = prompt
|
||||
e_context.action = EventAction.BREAK # 事件结束,不跳过处理context的默认逻辑
|
||||
e_context["context"].type = ContextType.TEXT
|
||||
e_context["context"].content = prompt
|
||||
e_context.action = EventAction.BREAK # 事件结束,不跳过处理context的默认逻辑
|
||||
|
||||
def get_help_text(self, **kwargs):
|
||||
help_text = "可以和机器人一起玩文字冒险游戏。\n"
|
||||
if kwargs.get('verbose') != True:
|
||||
if kwargs.get("verbose") != True:
|
||||
return help_text
|
||||
trigger_prefix = conf().get('plugin_trigger_prefix', "$")
|
||||
help_text = f"{trigger_prefix}开始冒险 "+"背景故事: 开始一个基于{背景故事}的文字冒险,之后你的所有消息会协助完善这个故事。\n"+f"{trigger_prefix}停止冒险: 结束游戏。\n"
|
||||
if kwargs.get('verbose') == True:
|
||||
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
|
||||
help_text = (
|
||||
f"{trigger_prefix}开始冒险 "
|
||||
+ "背景故事: 开始一个基于{背景故事}的文字冒险,之后你的所有消息会协助完善这个故事。\n"
|
||||
+ f"{trigger_prefix}停止冒险: 结束游戏。\n"
|
||||
)
|
||||
if kwargs.get("verbose") == True:
|
||||
help_text += f"\n命令例子: '{trigger_prefix}开始冒险 你在树林里冒险,指不定会从哪里蹦出来一些奇怪的东西,你握紧手上的手枪,希望这次冒险能够找到一些值钱的东西,你往树林深处走去。'"
|
||||
return help_text
|
||||
return help_text
|
||||
|
||||
@@ -9,17 +9,17 @@ class Event(Enum):
|
||||
e_context = { "channel": 消息channel, "context" : 本次消息的context}
|
||||
"""
|
||||
|
||||
ON_HANDLE_CONTEXT = 2 # 处理消息前
|
||||
ON_HANDLE_CONTEXT = 2 # 处理消息前
|
||||
"""
|
||||
e_context = { "channel": 消息channel, "context" : 本次消息的context, "reply" : 目前的回复,初始为空 }
|
||||
"""
|
||||
|
||||
ON_DECORATE_REPLY = 3 # 得到回复后准备装饰
|
||||
ON_DECORATE_REPLY = 3 # 得到回复后准备装饰
|
||||
"""
|
||||
e_context = { "channel": 消息channel, "context" : 本次消息的context, "reply" : 目前的回复 }
|
||||
"""
|
||||
|
||||
ON_SEND_REPLY = 4 # 发送回复前
|
||||
ON_SEND_REPLY = 4 # 发送回复前
|
||||
"""
|
||||
e_context = { "channel": 消息channel, "context" : 本次消息的context, "reply" : 目前的回复 }
|
||||
"""
|
||||
@@ -28,9 +28,9 @@ class Event(Enum):
|
||||
|
||||
|
||||
class EventAction(Enum):
|
||||
CONTINUE = 1 # 事件未结束,继续交给下个插件处理,如果没有下个插件,则交付给默认的事件处理逻辑
|
||||
BREAK = 2 # 事件结束,不再给下个插件处理,交付给默认的事件处理逻辑
|
||||
BREAK_PASS = 3 # 事件结束,不再给下个插件处理,不交付给默认的事件处理逻辑
|
||||
CONTINUE = 1 # 事件未结束,继续交给下个插件处理,如果没有下个插件,则交付给默认的事件处理逻辑
|
||||
BREAK = 2 # 事件结束,不再给下个插件处理,交付给默认的事件处理逻辑
|
||||
BREAK_PASS = 3 # 事件结束,不再给下个插件处理,不交付给默认的事件处理逻辑
|
||||
|
||||
|
||||
class EventContext:
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .finish import *
|
||||
from .finish import *
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
# encoding:utf-8
|
||||
|
||||
import plugins
|
||||
from bridge.context import ContextType
|
||||
from bridge.reply import Reply, ReplyType
|
||||
from config import conf
|
||||
import plugins
|
||||
from plugins import *
|
||||
from common.log import logger
|
||||
from config import conf
|
||||
from plugins import *
|
||||
|
||||
|
||||
@plugins.register(name="Finish", desire_priority=-999, hidden=True, desc="A plugin that check unknown command", version="1.0", author="js00000")
|
||||
@plugins.register(
|
||||
name="Finish",
|
||||
desire_priority=-999,
|
||||
hidden=True,
|
||||
desc="A plugin that check unknown command",
|
||||
version="1.0",
|
||||
author="js00000",
|
||||
)
|
||||
class Finish(Plugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -16,19 +23,18 @@ class Finish(Plugin):
|
||||
logger.info("[Finish] inited")
|
||||
|
||||
def on_handle_context(self, e_context: EventContext):
|
||||
|
||||
if e_context['context'].type != ContextType.TEXT:
|
||||
if e_context["context"].type != ContextType.TEXT:
|
||||
return
|
||||
|
||||
content = e_context['context'].content
|
||||
content = e_context["context"].content
|
||||
logger.debug("[Finish] on_handle_context. content: %s" % content)
|
||||
trigger_prefix = conf().get('plugin_trigger_prefix',"$")
|
||||
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
|
||||
if content.startswith(trigger_prefix):
|
||||
reply = Reply()
|
||||
reply.type = ReplyType.ERROR
|
||||
reply.content = "未知插件命令\n查看插件命令列表请输入#help 插件名\n"
|
||||
e_context['reply'] = reply
|
||||
e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
|
||||
|
||||
def get_help_text(self, **kwargs):
|
||||
return ""
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .godcmd import *
|
||||
from .godcmd import *
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"password": "",
|
||||
"admin_users": []
|
||||
}
|
||||
"password": "",
|
||||
"admin_users": []
|
||||
}
|
||||
|
||||
@@ -6,14 +6,16 @@ import random
|
||||
import string
|
||||
import traceback
|
||||
from typing import Tuple
|
||||
|
||||
import plugins
|
||||
from bridge.bridge import Bridge
|
||||
from bridge.context import ContextType
|
||||
from bridge.reply import Reply, ReplyType
|
||||
from config import conf, load_config
|
||||
import plugins
|
||||
from plugins import *
|
||||
from common import const
|
||||
from common.log import logger
|
||||
from config import conf, load_config
|
||||
from plugins import *
|
||||
|
||||
# 定义指令集
|
||||
COMMANDS = {
|
||||
"help": {
|
||||
@@ -41,7 +43,7 @@ COMMANDS = {
|
||||
},
|
||||
"id": {
|
||||
"alias": ["id", "用户"],
|
||||
"desc": "获取用户id", # wechaty和wechatmp的用户id不会变化,可用于绑定管理员
|
||||
"desc": "获取用户id", # wechaty和wechatmp的用户id不会变化,可用于绑定管理员
|
||||
},
|
||||
"reset": {
|
||||
"alias": ["reset", "重置会话"],
|
||||
@@ -114,18 +116,20 @@ ADMIN_COMMANDS = {
|
||||
"desc": "开启机器调试日志",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# 定义帮助函数
|
||||
def get_help_text(isadmin, isgroup):
|
||||
help_text = "通用指令:\n"
|
||||
for cmd, info in COMMANDS.items():
|
||||
if cmd=="auth": #不提示认证指令
|
||||
if cmd == "auth": # 不提示认证指令
|
||||
continue
|
||||
if cmd=="id" and conf().get("channel_type","wx") not in ["wxy","wechatmp"]:
|
||||
if cmd == "id" and conf().get("channel_type", "wx") not in ["wxy", "wechatmp"]:
|
||||
continue
|
||||
alias=["#"+a for a in info['alias'][:1]]
|
||||
alias = ["#" + a for a in info["alias"][:1]]
|
||||
help_text += f"{','.join(alias)} "
|
||||
if 'args' in info:
|
||||
args=[a for a in info['args']]
|
||||
if "args" in info:
|
||||
args = [a for a in info["args"]]
|
||||
help_text += f"{' '.join(args)}"
|
||||
help_text += f": {info['desc']}\n"
|
||||
|
||||
@@ -135,39 +139,48 @@ def get_help_text(isadmin, isgroup):
|
||||
for plugin in plugins:
|
||||
if plugins[plugin].enabled and not plugins[plugin].hidden:
|
||||
namecn = plugins[plugin].namecn
|
||||
help_text += "\n%s:"%namecn
|
||||
help_text += PluginManager().instances[plugin].get_help_text(verbose=False).strip()
|
||||
help_text += "\n%s:" % namecn
|
||||
help_text += (
|
||||
PluginManager().instances[plugin].get_help_text(verbose=False).strip()
|
||||
)
|
||||
|
||||
if ADMIN_COMMANDS and isadmin:
|
||||
help_text += "\n\n管理员指令:\n"
|
||||
for cmd, info in ADMIN_COMMANDS.items():
|
||||
alias=["#"+a for a in info['alias'][:1]]
|
||||
alias = ["#" + a for a in info["alias"][:1]]
|
||||
help_text += f"{','.join(alias)} "
|
||||
if 'args' in info:
|
||||
args=[a for a in info['args']]
|
||||
if "args" in info:
|
||||
args = [a for a in info["args"]]
|
||||
help_text += f"{' '.join(args)}"
|
||||
help_text += f": {info['desc']}\n"
|
||||
return help_text
|
||||
|
||||
@plugins.register(name="Godcmd", desire_priority=999, hidden=True, desc="为你的机器人添加指令集,有用户和管理员两种角色,加载顺序请放在首位,初次运行后插件目录会生成配置文件, 填充管理员密码后即可认证", version="1.0", author="lanvent")
|
||||
class Godcmd(Plugin):
|
||||
|
||||
@plugins.register(
|
||||
name="Godcmd",
|
||||
desire_priority=999,
|
||||
hidden=True,
|
||||
desc="为你的机器人添加指令集,有用户和管理员两种角色,加载顺序请放在首位,初次运行后插件目录会生成配置文件, 填充管理员密码后即可认证",
|
||||
version="1.0",
|
||||
author="lanvent",
|
||||
)
|
||||
class Godcmd(Plugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
curdir=os.path.dirname(__file__)
|
||||
config_path=os.path.join(curdir,"config.json")
|
||||
gconf=None
|
||||
curdir = os.path.dirname(__file__)
|
||||
config_path = os.path.join(curdir, "config.json")
|
||||
gconf = None
|
||||
if not os.path.exists(config_path):
|
||||
gconf={"password":"","admin_users":[]}
|
||||
with open(config_path,"w") as f:
|
||||
json.dump(gconf,f,indent=4)
|
||||
gconf = {"password": "", "admin_users": []}
|
||||
with open(config_path, "w") as f:
|
||||
json.dump(gconf, f, indent=4)
|
||||
else:
|
||||
with open(config_path,"r") as f:
|
||||
gconf=json.load(f)
|
||||
with open(config_path, "r") as f:
|
||||
gconf = json.load(f)
|
||||
if gconf["password"] == "":
|
||||
self.temp_password = "".join(random.sample(string.digits, 4))
|
||||
logger.info("[Godcmd] 因未设置口令,本次的临时口令为%s。"%self.temp_password)
|
||||
logger.info("[Godcmd] 因未设置口令,本次的临时口令为%s。" % self.temp_password)
|
||||
else:
|
||||
self.temp_password = None
|
||||
custom_commands = conf().get("clear_memory_commands", [])
|
||||
@@ -178,41 +191,42 @@ class Godcmd(Plugin):
|
||||
COMMANDS["reset"]["alias"].append(custom_command)
|
||||
|
||||
self.password = gconf["password"]
|
||||
self.admin_users = gconf["admin_users"] # 预存的管理员账号,这些账号不需要认证。itchat的用户名每次都会变,不可用
|
||||
self.isrunning = True # 机器人是否运行中
|
||||
self.admin_users = gconf[
|
||||
"admin_users"
|
||||
] # 预存的管理员账号,这些账号不需要认证。itchat的用户名每次都会变,不可用
|
||||
self.isrunning = True # 机器人是否运行中
|
||||
|
||||
self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
|
||||
logger.info("[Godcmd] inited")
|
||||
|
||||
|
||||
def on_handle_context(self, e_context: EventContext):
|
||||
context_type = e_context['context'].type
|
||||
context_type = e_context["context"].type
|
||||
if context_type != ContextType.TEXT:
|
||||
if not self.isrunning:
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
return
|
||||
|
||||
content = e_context['context'].content
|
||||
content = e_context["context"].content
|
||||
logger.debug("[Godcmd] on_handle_context. content: %s" % content)
|
||||
if content.startswith("#"):
|
||||
# msg = e_context['context']['msg']
|
||||
channel = e_context['channel']
|
||||
user = e_context['context']['receiver']
|
||||
session_id = e_context['context']['session_id']
|
||||
isgroup = e_context['context'].get("isgroup", False)
|
||||
channel = e_context["channel"]
|
||||
user = e_context["context"]["receiver"]
|
||||
session_id = e_context["context"]["session_id"]
|
||||
isgroup = e_context["context"].get("isgroup", False)
|
||||
bottype = Bridge().get_bot_type("chat")
|
||||
bot = Bridge().get_bot("chat")
|
||||
# 将命令和参数分割
|
||||
command_parts = content[1:].strip().split()
|
||||
cmd = command_parts[0]
|
||||
args = command_parts[1:]
|
||||
isadmin=False
|
||||
isadmin = False
|
||||
if user in self.admin_users:
|
||||
isadmin=True
|
||||
ok=False
|
||||
result="string"
|
||||
if any(cmd in info['alias'] for info in COMMANDS.values()):
|
||||
cmd = next(c for c, info in COMMANDS.items() if cmd in info['alias'])
|
||||
isadmin = True
|
||||
ok = False
|
||||
result = "string"
|
||||
if any(cmd in info["alias"] for info in COMMANDS.values()):
|
||||
cmd = next(c for c, info in COMMANDS.items() if cmd in info["alias"])
|
||||
if cmd == "auth":
|
||||
ok, result = self.authenticate(user, args, isadmin, isgroup)
|
||||
elif cmd == "help" or cmd == "helpp":
|
||||
@@ -224,10 +238,14 @@ class Godcmd(Plugin):
|
||||
query_name = args[0].upper()
|
||||
# search name and namecn
|
||||
for name, plugincls in plugins.items():
|
||||
if not plugincls.enabled :
|
||||
if not plugincls.enabled:
|
||||
continue
|
||||
if query_name == name or query_name == plugincls.namecn:
|
||||
ok, result = True, PluginManager().instances[name].get_help_text(isgroup=isgroup, isadmin=isadmin, verbose=True)
|
||||
ok, result = True, PluginManager().instances[
|
||||
name
|
||||
].get_help_text(
|
||||
isgroup=isgroup, isadmin=isadmin, verbose=True
|
||||
)
|
||||
break
|
||||
if not ok:
|
||||
result = "插件不存在或未启用"
|
||||
@@ -236,14 +254,14 @@ class Godcmd(Plugin):
|
||||
elif cmd == "set_openai_api_key":
|
||||
if len(args) == 1:
|
||||
user_data = conf().get_user_data(user)
|
||||
user_data['openai_api_key'] = args[0]
|
||||
user_data["openai_api_key"] = args[0]
|
||||
ok, result = True, "你的OpenAI私有api_key已设置为" + args[0]
|
||||
else:
|
||||
ok, result = False, "请提供一个api_key"
|
||||
elif cmd == "reset_openai_api_key":
|
||||
try:
|
||||
user_data = conf().get_user_data(user)
|
||||
user_data.pop('openai_api_key')
|
||||
user_data.pop("openai_api_key")
|
||||
ok, result = True, "你的OpenAI私有api_key已清除"
|
||||
except Exception as e:
|
||||
ok, result = False, "你没有设置私有api_key"
|
||||
@@ -255,12 +273,16 @@ class Godcmd(Plugin):
|
||||
else:
|
||||
ok, result = False, "当前对话机器人不支持重置会话"
|
||||
logger.debug("[Godcmd] command: %s by %s" % (cmd, user))
|
||||
elif any(cmd in info['alias'] for info in ADMIN_COMMANDS.values()):
|
||||
elif any(cmd in info["alias"] for info in ADMIN_COMMANDS.values()):
|
||||
if isadmin:
|
||||
if isgroup:
|
||||
ok, result = False, "群聊不可执行管理员指令"
|
||||
else:
|
||||
cmd = next(c for c, info in ADMIN_COMMANDS.items() if cmd in info['alias'])
|
||||
cmd = next(
|
||||
c
|
||||
for c, info in ADMIN_COMMANDS.items()
|
||||
if cmd in info["alias"]
|
||||
)
|
||||
if cmd == "stop":
|
||||
self.isrunning = False
|
||||
ok, result = True, "服务已暂停"
|
||||
@@ -278,13 +300,13 @@ class Godcmd(Plugin):
|
||||
else:
|
||||
ok, result = False, "当前对话机器人不支持重置会话"
|
||||
elif cmd == "debug":
|
||||
logger.setLevel('DEBUG')
|
||||
logger.setLevel("DEBUG")
|
||||
ok, result = True, "DEBUG模式已开启"
|
||||
elif cmd == "plist":
|
||||
plugins = PluginManager().list_plugins()
|
||||
ok = True
|
||||
result = "插件列表:\n"
|
||||
for name,plugincls in plugins.items():
|
||||
for name, plugincls in plugins.items():
|
||||
result += f"{plugincls.name}_v{plugincls.version} {plugincls.priority} - "
|
||||
if plugincls.enabled:
|
||||
result += "已启用\n"
|
||||
@@ -294,16 +316,20 @@ class Godcmd(Plugin):
|
||||
new_plugins = PluginManager().scan_plugins()
|
||||
ok, result = True, "插件扫描完成"
|
||||
PluginManager().activate_plugins()
|
||||
if len(new_plugins) >0 :
|
||||
if len(new_plugins) > 0:
|
||||
result += "\n发现新插件:\n"
|
||||
result += "\n".join([f"{p.name}_v{p.version}" for p in new_plugins])
|
||||
else :
|
||||
result +=", 未发现新插件"
|
||||
result += "\n".join(
|
||||
[f"{p.name}_v{p.version}" for p in new_plugins]
|
||||
)
|
||||
else:
|
||||
result += ", 未发现新插件"
|
||||
elif cmd == "setpri":
|
||||
if len(args) != 2:
|
||||
ok, result = False, "请提供插件名和优先级"
|
||||
else:
|
||||
ok = PluginManager().set_plugin_priority(args[0], int(args[1]))
|
||||
ok = PluginManager().set_plugin_priority(
|
||||
args[0], int(args[1])
|
||||
)
|
||||
if ok:
|
||||
result = "插件" + args[0] + "优先级已设置为" + args[1]
|
||||
else:
|
||||
@@ -350,42 +376,42 @@ class Godcmd(Plugin):
|
||||
else:
|
||||
ok, result = False, "需要管理员权限才能执行该指令"
|
||||
else:
|
||||
trigger_prefix = conf().get('plugin_trigger_prefix',"$")
|
||||
if trigger_prefix == "#": # 跟插件聊天指令前缀相同,继续递交
|
||||
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
|
||||
if trigger_prefix == "#": # 跟插件聊天指令前缀相同,继续递交
|
||||
return
|
||||
ok, result = False, f"未知指令:{cmd}\n查看指令列表请输入#help \n"
|
||||
|
||||
|
||||
reply = Reply()
|
||||
if ok:
|
||||
reply.type = ReplyType.INFO
|
||||
else:
|
||||
reply.type = ReplyType.ERROR
|
||||
reply.content = result
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
|
||||
e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
|
||||
e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
|
||||
elif not self.isrunning:
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
|
||||
def authenticate(self, userid, args, isadmin, isgroup) -> Tuple[bool,str] :
|
||||
def authenticate(self, userid, args, isadmin, isgroup) -> Tuple[bool, str]:
|
||||
if isgroup:
|
||||
return False,"请勿在群聊中认证"
|
||||
|
||||
return False, "请勿在群聊中认证"
|
||||
|
||||
if isadmin:
|
||||
return False,"管理员账号无需认证"
|
||||
|
||||
return False, "管理员账号无需认证"
|
||||
|
||||
if len(args) != 1:
|
||||
return False,"请提供口令"
|
||||
|
||||
return False, "请提供口令"
|
||||
|
||||
password = args[0]
|
||||
if password == self.password:
|
||||
self.admin_users.append(userid)
|
||||
return True,"认证成功"
|
||||
return True, "认证成功"
|
||||
elif password == self.temp_password:
|
||||
self.admin_users.append(userid)
|
||||
return True,"认证成功,请尽快设置口令"
|
||||
return True, "认证成功,请尽快设置口令"
|
||||
else:
|
||||
return False,"认证失败"
|
||||
return False, "认证失败"
|
||||
|
||||
def get_help_text(self, isadmin = False, isgroup = False, **kwargs):
|
||||
return get_help_text(isadmin, isgroup)
|
||||
def get_help_text(self, isadmin=False, isgroup=False, **kwargs):
|
||||
return get_help_text(isadmin, isgroup)
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .hello import *
|
||||
from .hello import *
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
# encoding:utf-8
|
||||
|
||||
import plugins
|
||||
from bridge.context import ContextType
|
||||
from bridge.reply import Reply, ReplyType
|
||||
from channel.chat_message import ChatMessage
|
||||
import plugins
|
||||
from plugins import *
|
||||
from common.log import logger
|
||||
from plugins import *
|
||||
|
||||
|
||||
@plugins.register(name="Hello", desire_priority=-1, hidden=True, desc="A simple plugin that says hello", version="0.1", author="lanvent")
|
||||
@plugins.register(
|
||||
name="Hello",
|
||||
desire_priority=-1,
|
||||
hidden=True,
|
||||
desc="A simple plugin that says hello",
|
||||
version="0.1",
|
||||
author="lanvent",
|
||||
)
|
||||
class Hello(Plugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -16,33 +23,34 @@ class Hello(Plugin):
|
||||
logger.info("[Hello] inited")
|
||||
|
||||
def on_handle_context(self, e_context: EventContext):
|
||||
|
||||
if e_context['context'].type != ContextType.TEXT:
|
||||
if e_context["context"].type != ContextType.TEXT:
|
||||
return
|
||||
|
||||
content = e_context['context'].content
|
||||
|
||||
content = e_context["context"].content
|
||||
logger.debug("[Hello] on_handle_context. content: %s" % content)
|
||||
if content == "Hello":
|
||||
reply = Reply()
|
||||
reply.type = ReplyType.TEXT
|
||||
msg:ChatMessage = e_context['context']['msg']
|
||||
if e_context['context']['isgroup']:
|
||||
reply.content = f"Hello, {msg.actual_user_nickname} from {msg.from_user_nickname}"
|
||||
msg: ChatMessage = e_context["context"]["msg"]
|
||||
if e_context["context"]["isgroup"]:
|
||||
reply.content = (
|
||||
f"Hello, {msg.actual_user_nickname} from {msg.from_user_nickname}"
|
||||
)
|
||||
else:
|
||||
reply.content = f"Hello, {msg.from_user_nickname}"
|
||||
e_context['reply'] = reply
|
||||
e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS # 事件结束,并跳过处理context的默认逻辑
|
||||
|
||||
if content == "Hi":
|
||||
reply = Reply()
|
||||
reply.type = ReplyType.TEXT
|
||||
reply.content = "Hi"
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK # 事件结束,进入默认处理逻辑,一般会覆写reply
|
||||
|
||||
if content == "End":
|
||||
# 如果是文本消息"End",将请求转换成"IMAGE_CREATE",并将content设置为"The World"
|
||||
e_context['context'].type = ContextType.IMAGE_CREATE
|
||||
e_context["context"].type = ContextType.IMAGE_CREATE
|
||||
content = "The World"
|
||||
e_context.action = EventAction.CONTINUE # 事件继续,交付给下个插件或默认逻辑
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ class Plugin:
|
||||
self.handlers = {}
|
||||
|
||||
def get_help_text(self, **kwargs):
|
||||
return "暂无帮助信息"
|
||||
return "暂无帮助信息"
|
||||
|
||||
@@ -5,17 +5,19 @@ import importlib.util
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from common.log import logger
|
||||
from common.singleton import singleton
|
||||
from common.sorted_dict import SortedDict
|
||||
from .event import *
|
||||
from common.log import logger
|
||||
from config import conf
|
||||
|
||||
from .event import *
|
||||
|
||||
|
||||
@singleton
|
||||
class PluginManager:
|
||||
def __init__(self):
|
||||
self.plugins = SortedDict(lambda k,v: v.priority,reverse=True)
|
||||
self.plugins = SortedDict(lambda k, v: v.priority, reverse=True)
|
||||
self.listening_plugins = {}
|
||||
self.instances = {}
|
||||
self.pconf = {}
|
||||
@@ -26,17 +28,27 @@ class PluginManager:
|
||||
def wrapper(plugincls):
|
||||
plugincls.name = name
|
||||
plugincls.priority = desire_priority
|
||||
plugincls.desc = kwargs.get('desc')
|
||||
plugincls.author = kwargs.get('author')
|
||||
plugincls.desc = kwargs.get("desc")
|
||||
plugincls.author = kwargs.get("author")
|
||||
plugincls.path = self.current_plugin_path
|
||||
plugincls.version = kwargs.get('version') if kwargs.get('version') != None else "1.0"
|
||||
plugincls.namecn = kwargs.get('namecn') if kwargs.get('namecn') != None else name
|
||||
plugincls.hidden = kwargs.get('hidden') if kwargs.get('hidden') != None else False
|
||||
plugincls.version = (
|
||||
kwargs.get("version") if kwargs.get("version") != None else "1.0"
|
||||
)
|
||||
plugincls.namecn = (
|
||||
kwargs.get("namecn") if kwargs.get("namecn") != None else name
|
||||
)
|
||||
plugincls.hidden = (
|
||||
kwargs.get("hidden") if kwargs.get("hidden") != None else False
|
||||
)
|
||||
plugincls.enabled = True
|
||||
if self.current_plugin_path == None:
|
||||
raise Exception("Plugin path not set")
|
||||
self.plugins[name.upper()] = plugincls
|
||||
logger.info("Plugin %s_v%s registered, path=%s" % (name, plugincls.version, plugincls.path))
|
||||
logger.info(
|
||||
"Plugin %s_v%s registered, path=%s"
|
||||
% (name, plugincls.version, plugincls.path)
|
||||
)
|
||||
|
||||
return wrapper
|
||||
|
||||
def save_config(self):
|
||||
@@ -50,10 +62,12 @@ class PluginManager:
|
||||
if os.path.exists("./plugins/plugins.json"):
|
||||
with open("./plugins/plugins.json", "r", encoding="utf-8") as f:
|
||||
pconf = json.load(f)
|
||||
pconf['plugins'] = SortedDict(lambda k,v: v["priority"],pconf['plugins'],reverse=True)
|
||||
pconf["plugins"] = SortedDict(
|
||||
lambda k, v: v["priority"], pconf["plugins"], reverse=True
|
||||
)
|
||||
else:
|
||||
modified = True
|
||||
pconf = {"plugins": SortedDict(lambda k,v: v["priority"],reverse=True)}
|
||||
pconf = {"plugins": SortedDict(lambda k, v: v["priority"], reverse=True)}
|
||||
self.pconf = pconf
|
||||
if modified:
|
||||
self.save_config()
|
||||
@@ -67,7 +81,7 @@ class PluginManager:
|
||||
plugin_path = os.path.join(plugins_dir, plugin_name)
|
||||
if os.path.isdir(plugin_path):
|
||||
# 判断插件是否包含同名__init__.py文件
|
||||
main_module_path = os.path.join(plugin_path,"__init__.py")
|
||||
main_module_path = os.path.join(plugin_path, "__init__.py")
|
||||
if os.path.isfile(main_module_path):
|
||||
# 导入插件
|
||||
import_path = "plugins.{}".format(plugin_name)
|
||||
@@ -76,16 +90,26 @@ class PluginManager:
|
||||
if plugin_path in self.loaded:
|
||||
if self.loaded[plugin_path] == None:
|
||||
logger.info("reload module %s" % plugin_name)
|
||||
self.loaded[plugin_path] = importlib.reload(sys.modules[import_path])
|
||||
dependent_module_names = [name for name in sys.modules.keys() if name.startswith( import_path+ '.')]
|
||||
self.loaded[plugin_path] = importlib.reload(
|
||||
sys.modules[import_path]
|
||||
)
|
||||
dependent_module_names = [
|
||||
name
|
||||
for name in sys.modules.keys()
|
||||
if name.startswith(import_path + ".")
|
||||
]
|
||||
for name in dependent_module_names:
|
||||
logger.info("reload module %s" % name)
|
||||
importlib.reload(sys.modules[name])
|
||||
else:
|
||||
self.loaded[plugin_path] = importlib.import_module(import_path)
|
||||
self.loaded[plugin_path] = importlib.import_module(
|
||||
import_path
|
||||
)
|
||||
self.current_plugin_path = None
|
||||
except Exception as e:
|
||||
logger.exception("Failed to import plugin %s: %s" % (plugin_name, e))
|
||||
logger.exception(
|
||||
"Failed to import plugin %s: %s" % (plugin_name, e)
|
||||
)
|
||||
continue
|
||||
pconf = self.pconf
|
||||
news = [self.plugins[name] for name in self.plugins]
|
||||
@@ -95,21 +119,28 @@ class PluginManager:
|
||||
rawname = plugincls.name
|
||||
if rawname not in pconf["plugins"]:
|
||||
modified = True
|
||||
logger.info("Plugin %s not found in pconfig, adding to pconfig..." % name)
|
||||
pconf["plugins"][rawname] = {"enabled": plugincls.enabled, "priority": plugincls.priority}
|
||||
logger.info(
|
||||
"Plugin %s not found in pconfig, adding to pconfig..." % name
|
||||
)
|
||||
pconf["plugins"][rawname] = {
|
||||
"enabled": plugincls.enabled,
|
||||
"priority": plugincls.priority,
|
||||
}
|
||||
else:
|
||||
self.plugins[name].enabled = pconf["plugins"][rawname]["enabled"]
|
||||
self.plugins[name].priority = pconf["plugins"][rawname]["priority"]
|
||||
self.plugins._update_heap(name) # 更新下plugins中的顺序
|
||||
self.plugins._update_heap(name) # 更新下plugins中的顺序
|
||||
if modified:
|
||||
self.save_config()
|
||||
return new_plugins
|
||||
|
||||
def refresh_order(self):
|
||||
for event in self.listening_plugins.keys():
|
||||
self.listening_plugins[event].sort(key=lambda name: self.plugins[name].priority, reverse=True)
|
||||
self.listening_plugins[event].sort(
|
||||
key=lambda name: self.plugins[name].priority, reverse=True
|
||||
)
|
||||
|
||||
def activate_plugins(self): # 生成新开启的插件实例
|
||||
def activate_plugins(self): # 生成新开启的插件实例
|
||||
failed_plugins = []
|
||||
for name, plugincls in self.plugins.items():
|
||||
if plugincls.enabled:
|
||||
@@ -129,7 +160,7 @@ class PluginManager:
|
||||
self.refresh_order()
|
||||
return failed_plugins
|
||||
|
||||
def reload_plugin(self, name:str):
|
||||
def reload_plugin(self, name: str):
|
||||
name = name.upper()
|
||||
if name in self.instances:
|
||||
for event in self.listening_plugins:
|
||||
@@ -139,13 +170,13 @@ class PluginManager:
|
||||
self.activate_plugins()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def load_plugins(self):
|
||||
self.load_config()
|
||||
self.scan_plugins()
|
||||
pconf = self.pconf
|
||||
logger.debug("plugins.json config={}".format(pconf))
|
||||
for name,plugin in pconf["plugins"].items():
|
||||
for name, plugin in pconf["plugins"].items():
|
||||
if name.upper() not in self.plugins:
|
||||
logger.error("Plugin %s not found, but found in plugins.json" % name)
|
||||
self.activate_plugins()
|
||||
@@ -153,13 +184,18 @@ class PluginManager:
|
||||
def emit_event(self, e_context: EventContext, *args, **kwargs):
|
||||
if e_context.event in self.listening_plugins:
|
||||
for name in self.listening_plugins[e_context.event]:
|
||||
if self.plugins[name].enabled and e_context.action == EventAction.CONTINUE:
|
||||
logger.debug("Plugin %s triggered by event %s" % (name,e_context.event))
|
||||
if (
|
||||
self.plugins[name].enabled
|
||||
and e_context.action == EventAction.CONTINUE
|
||||
):
|
||||
logger.debug(
|
||||
"Plugin %s triggered by event %s" % (name, e_context.event)
|
||||
)
|
||||
instance = self.instances[name]
|
||||
instance.handlers[e_context.event](e_context, *args, **kwargs)
|
||||
return e_context
|
||||
|
||||
def set_plugin_priority(self, name:str, priority:int):
|
||||
def set_plugin_priority(self, name: str, priority: int):
|
||||
name = name.upper()
|
||||
if name not in self.plugins:
|
||||
return False
|
||||
@@ -174,11 +210,11 @@ class PluginManager:
|
||||
self.refresh_order()
|
||||
return True
|
||||
|
||||
def enable_plugin(self, name:str):
|
||||
def enable_plugin(self, name: str):
|
||||
name = name.upper()
|
||||
if name not in self.plugins:
|
||||
return False, "插件不存在"
|
||||
if not self.plugins[name].enabled :
|
||||
if not self.plugins[name].enabled:
|
||||
self.plugins[name].enabled = True
|
||||
rawname = self.plugins[name].name
|
||||
self.pconf["plugins"][rawname]["enabled"] = True
|
||||
@@ -188,43 +224,47 @@ class PluginManager:
|
||||
return False, "插件开启失败"
|
||||
return True, "插件已开启"
|
||||
return True, "插件已开启"
|
||||
|
||||
def disable_plugin(self, name:str):
|
||||
|
||||
def disable_plugin(self, name: str):
|
||||
name = name.upper()
|
||||
if name not in self.plugins:
|
||||
return False
|
||||
if self.plugins[name].enabled :
|
||||
if self.plugins[name].enabled:
|
||||
self.plugins[name].enabled = False
|
||||
rawname = self.plugins[name].name
|
||||
self.pconf["plugins"][rawname]["enabled"] = False
|
||||
self.save_config()
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
def list_plugins(self):
|
||||
return self.plugins
|
||||
|
||||
def install_plugin(self, repo:str):
|
||||
|
||||
def install_plugin(self, repo: str):
|
||||
try:
|
||||
import common.package_manager as pkgmgr
|
||||
|
||||
pkgmgr.check_dulwich()
|
||||
except Exception as e:
|
||||
logger.error("Failed to install plugin, {}".format(e))
|
||||
return False, "无法导入dulwich,安装插件失败"
|
||||
import re
|
||||
|
||||
from dulwich import porcelain
|
||||
|
||||
logger.info("clone git repo: {}".format(repo))
|
||||
|
||||
|
||||
match = re.match(r"^(https?:\/\/|git@)([^\/:]+)[\/:]([^\/:]+)\/(.+).git$", repo)
|
||||
|
||||
|
||||
if not match:
|
||||
try:
|
||||
with open("./plugins/source.json","r", encoding="utf-8") as f:
|
||||
with open("./plugins/source.json", "r", encoding="utf-8") as f:
|
||||
source = json.load(f)
|
||||
if repo in source["repo"]:
|
||||
repo = source["repo"][repo]["url"]
|
||||
match = re.match(r"^(https?:\/\/|git@)([^\/:]+)[\/:]([^\/:]+)\/(.+).git$", repo)
|
||||
match = re.match(
|
||||
r"^(https?:\/\/|git@)([^\/:]+)[\/:]([^\/:]+)\/(.+).git$", repo
|
||||
)
|
||||
if not match:
|
||||
return False, "安装插件失败,source中的仓库地址不合法"
|
||||
else:
|
||||
@@ -232,42 +272,53 @@ class PluginManager:
|
||||
except Exception as e:
|
||||
logger.error("Failed to install plugin, {}".format(e))
|
||||
return False, "安装插件失败,请检查仓库地址是否正确"
|
||||
dirname = os.path.join("./plugins",match.group(4))
|
||||
dirname = os.path.join("./plugins", match.group(4))
|
||||
try:
|
||||
repo = porcelain.clone(repo, dirname, checkout=True)
|
||||
if os.path.exists(os.path.join(dirname,"requirements.txt")):
|
||||
if os.path.exists(os.path.join(dirname, "requirements.txt")):
|
||||
logger.info("detect requirements.txt,installing...")
|
||||
pkgmgr.install_requirements(os.path.join(dirname,"requirements.txt"))
|
||||
pkgmgr.install_requirements(os.path.join(dirname, "requirements.txt"))
|
||||
return True, "安装插件成功,请使用 #scanp 命令扫描插件或重启程序,开启前请检查插件是否需要配置"
|
||||
except Exception as e:
|
||||
logger.error("Failed to install plugin, {}".format(e))
|
||||
return False, "安装插件失败,"+str(e)
|
||||
|
||||
def update_plugin(self, name:str):
|
||||
return False, "安装插件失败," + str(e)
|
||||
|
||||
def update_plugin(self, name: str):
|
||||
try:
|
||||
import common.package_manager as pkgmgr
|
||||
|
||||
pkgmgr.check_dulwich()
|
||||
except Exception as e:
|
||||
logger.error("Failed to install plugin, {}".format(e))
|
||||
return False, "无法导入dulwich,更新插件失败"
|
||||
from dulwich import porcelain
|
||||
|
||||
name = name.upper()
|
||||
if name not in self.plugins:
|
||||
return False, "插件不存在"
|
||||
if name in ["HELLO","GODCMD","ROLE","TOOL","BDUNIT","BANWORDS","FINISH","DUNGEON"]:
|
||||
if name in [
|
||||
"HELLO",
|
||||
"GODCMD",
|
||||
"ROLE",
|
||||
"TOOL",
|
||||
"BDUNIT",
|
||||
"BANWORDS",
|
||||
"FINISH",
|
||||
"DUNGEON",
|
||||
]:
|
||||
return False, "预置插件无法更新,请更新主程序仓库"
|
||||
dirname = self.plugins[name].path
|
||||
try:
|
||||
porcelain.pull(dirname, "origin")
|
||||
if os.path.exists(os.path.join(dirname,"requirements.txt")):
|
||||
if os.path.exists(os.path.join(dirname, "requirements.txt")):
|
||||
logger.info("detect requirements.txt,installing...")
|
||||
pkgmgr.install_requirements(os.path.join(dirname,"requirements.txt"))
|
||||
pkgmgr.install_requirements(os.path.join(dirname, "requirements.txt"))
|
||||
return True, "更新插件成功,请重新运行程序"
|
||||
except Exception as e:
|
||||
logger.error("Failed to update plugin, {}".format(e))
|
||||
return False, "更新插件失败,"+str(e)
|
||||
|
||||
def uninstall_plugin(self, name:str):
|
||||
return False, "更新插件失败," + str(e)
|
||||
|
||||
def uninstall_plugin(self, name: str):
|
||||
name = name.upper()
|
||||
if name not in self.plugins:
|
||||
return False, "插件不存在"
|
||||
@@ -276,6 +327,7 @@ class PluginManager:
|
||||
dirname = self.plugins[name].path
|
||||
try:
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(dirname)
|
||||
rawname = self.plugins[name].name
|
||||
for event in self.listening_plugins:
|
||||
@@ -288,4 +340,4 @@ class PluginManager:
|
||||
return True, "卸载插件成功"
|
||||
except Exception as e:
|
||||
logger.error("Failed to uninstall plugin, {}".format(e))
|
||||
return False, "卸载插件失败,请手动删除文件夹完成卸载,"+str(e)
|
||||
return False, "卸载插件失败,请手动删除文件夹完成卸载," + str(e)
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .role import *
|
||||
from .role import *
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import plugins
|
||||
from bridge.bridge import Bridge
|
||||
from bridge.context import ContextType
|
||||
from bridge.reply import Reply, ReplyType
|
||||
from common import const
|
||||
from config import conf
|
||||
import plugins
|
||||
from plugins import *
|
||||
from common.log import logger
|
||||
from config import conf
|
||||
from plugins import *
|
||||
|
||||
|
||||
class RolePlay():
|
||||
class RolePlay:
|
||||
def __init__(self, bot, sessionid, desc, wrapper=None):
|
||||
self.bot = bot
|
||||
self.sessionid = sessionid
|
||||
@@ -25,12 +26,20 @@ class RolePlay():
|
||||
|
||||
def action(self, user_action):
|
||||
session = self.bot.sessions.build_session(self.sessionid)
|
||||
if session.system_prompt != self.desc: # 目前没有触发session过期事件,这里先简单判断,然后重置
|
||||
if session.system_prompt != self.desc: # 目前没有触发session过期事件,这里先简单判断,然后重置
|
||||
session.set_system_prompt(self.desc)
|
||||
prompt = self.wrapper % user_action
|
||||
return prompt
|
||||
|
||||
@plugins.register(name="Role", desire_priority=0, namecn="角色扮演", desc="为你的Bot设置预设角色", version="1.0", author="lanvent")
|
||||
|
||||
@plugins.register(
|
||||
name="Role",
|
||||
desire_priority=0,
|
||||
namecn="角色扮演",
|
||||
desc="为你的Bot设置预设角色",
|
||||
version="1.0",
|
||||
author="lanvent",
|
||||
)
|
||||
class Role(Plugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -39,7 +48,7 @@ class Role(Plugin):
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
self.tags = { tag:(desc,[]) for tag,desc in config["tags"].items()}
|
||||
self.tags = {tag: (desc, []) for tag, desc in config["tags"].items()}
|
||||
self.roles = {}
|
||||
for role in config["roles"]:
|
||||
self.roles[role["title"].lower()] = role
|
||||
@@ -60,12 +69,16 @@ class Role(Plugin):
|
||||
logger.info("[Role] inited")
|
||||
except Exception as e:
|
||||
if isinstance(e, FileNotFoundError):
|
||||
logger.warn(f"[Role] init failed, {config_path} not found, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/role .")
|
||||
logger.warn(
|
||||
f"[Role] init failed, {config_path} not found, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/role ."
|
||||
)
|
||||
else:
|
||||
logger.warn("[Role] init failed, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/role .")
|
||||
logger.warn(
|
||||
"[Role] init failed, ignore or see https://github.com/zhayujie/chatgpt-on-wechat/tree/master/plugins/role ."
|
||||
)
|
||||
raise e
|
||||
|
||||
def get_role(self, name, find_closest=True, min_sim = 0.35):
|
||||
def get_role(self, name, find_closest=True, min_sim=0.35):
|
||||
name = name.lower()
|
||||
found_role = None
|
||||
if name in self.roles:
|
||||
@@ -75,6 +88,7 @@ class Role(Plugin):
|
||||
|
||||
def str_simularity(a, b):
|
||||
return difflib.SequenceMatcher(None, a, b).ratio()
|
||||
|
||||
max_sim = min_sim
|
||||
max_role = None
|
||||
for role in self.roles:
|
||||
@@ -86,25 +100,24 @@ class Role(Plugin):
|
||||
return found_role
|
||||
|
||||
def on_handle_context(self, e_context: EventContext):
|
||||
|
||||
if e_context['context'].type != ContextType.TEXT:
|
||||
if e_context["context"].type != ContextType.TEXT:
|
||||
return
|
||||
bottype = Bridge().get_bot_type("chat")
|
||||
if bottype not in (const.CHATGPT, const.OPEN_AI):
|
||||
return
|
||||
bot = Bridge().get_bot("chat")
|
||||
content = e_context['context'].content[:]
|
||||
clist = e_context['context'].content.split(maxsplit=1)
|
||||
content = e_context["context"].content[:]
|
||||
clist = e_context["context"].content.split(maxsplit=1)
|
||||
desckey = None
|
||||
customize = False
|
||||
sessionid = e_context['context']['session_id']
|
||||
trigger_prefix = conf().get('plugin_trigger_prefix', "$")
|
||||
sessionid = e_context["context"]["session_id"]
|
||||
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
|
||||
if clist[0] == f"{trigger_prefix}停止扮演":
|
||||
if sessionid in self.roleplays:
|
||||
self.roleplays[sessionid].reset()
|
||||
del self.roleplays[sessionid]
|
||||
reply = Reply(ReplyType.INFO, "角色扮演结束!")
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
return
|
||||
elif clist[0] == f"{trigger_prefix}角色":
|
||||
@@ -114,10 +127,10 @@ class Role(Plugin):
|
||||
elif clist[0] == f"{trigger_prefix}设定扮演":
|
||||
customize = True
|
||||
elif clist[0] == f"{trigger_prefix}角色类型":
|
||||
if len(clist) >1:
|
||||
if len(clist) > 1:
|
||||
tag = clist[1].strip()
|
||||
help_text = "角色列表:\n"
|
||||
for key,value in self.tags.items():
|
||||
for key, value in self.tags.items():
|
||||
if value[0] == tag:
|
||||
tag = key
|
||||
break
|
||||
@@ -130,57 +143,75 @@ class Role(Plugin):
|
||||
else:
|
||||
help_text = f"未知角色类型。\n"
|
||||
help_text += "目前的角色类型有: \n"
|
||||
help_text += ",".join([self.tags[tag][0] for tag in self.tags])+"\n"
|
||||
help_text += (
|
||||
",".join([self.tags[tag][0] for tag in self.tags]) + "\n"
|
||||
)
|
||||
else:
|
||||
help_text = f"请输入角色类型。\n"
|
||||
help_text += "目前的角色类型有: \n"
|
||||
help_text += ",".join([self.tags[tag][0] for tag in self.tags])+"\n"
|
||||
help_text += ",".join([self.tags[tag][0] for tag in self.tags]) + "\n"
|
||||
reply = Reply(ReplyType.INFO, help_text)
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
return
|
||||
elif sessionid not in self.roleplays:
|
||||
return
|
||||
logger.debug("[Role] on_handle_context. content: %s" % content)
|
||||
if desckey is not None:
|
||||
if len(clist) == 1 or (len(clist) > 1 and clist[1].lower() in ["help", "帮助"]):
|
||||
if len(clist) == 1 or (
|
||||
len(clist) > 1 and clist[1].lower() in ["help", "帮助"]
|
||||
):
|
||||
reply = Reply(ReplyType.INFO, self.get_help_text(verbose=True))
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
return
|
||||
role = self.get_role(clist[1])
|
||||
if role is None:
|
||||
reply = Reply(ReplyType.ERROR, "角色不存在")
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
return
|
||||
else:
|
||||
self.roleplays[sessionid] = RolePlay(bot, sessionid, self.roles[role][desckey], self.roles[role].get("wrapper","%s"))
|
||||
reply = Reply(ReplyType.INFO, f"预设角色为 {role}:\n"+self.roles[role][desckey])
|
||||
e_context['reply'] = reply
|
||||
self.roleplays[sessionid] = RolePlay(
|
||||
bot,
|
||||
sessionid,
|
||||
self.roles[role][desckey],
|
||||
self.roles[role].get("wrapper", "%s"),
|
||||
)
|
||||
reply = Reply(
|
||||
ReplyType.INFO, f"预设角色为 {role}:\n" + self.roles[role][desckey]
|
||||
)
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
elif customize == True:
|
||||
self.roleplays[sessionid] = RolePlay(bot, sessionid, clist[1], "%s")
|
||||
reply = Reply(ReplyType.INFO, f"角色设定为:\n{clist[1]}")
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
else:
|
||||
prompt = self.roleplays[sessionid].action(content)
|
||||
e_context['context'].type = ContextType.TEXT
|
||||
e_context['context'].content = prompt
|
||||
e_context["context"].type = ContextType.TEXT
|
||||
e_context["context"].content = prompt
|
||||
e_context.action = EventAction.BREAK
|
||||
|
||||
def get_help_text(self, verbose=False, **kwargs):
|
||||
help_text = "让机器人扮演不同的角色。\n"
|
||||
if not verbose:
|
||||
return help_text
|
||||
trigger_prefix = conf().get('plugin_trigger_prefix', "$")
|
||||
help_text = f"使用方法:\n{trigger_prefix}角色"+" 预设角色名: 设定角色为{预设角色名}。\n"+f"{trigger_prefix}role"+" 预设角色名: 同上,但使用英文设定。\n"
|
||||
help_text += f"{trigger_prefix}设定扮演"+" 角色设定: 设定自定义角色人设为{角色设定}。\n"
|
||||
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
|
||||
help_text = (
|
||||
f"使用方法:\n{trigger_prefix}角色"
|
||||
+ " 预设角色名: 设定角色为{预设角色名}。\n"
|
||||
+ f"{trigger_prefix}role"
|
||||
+ " 预设角色名: 同上,但使用英文设定。\n"
|
||||
)
|
||||
help_text += f"{trigger_prefix}设定扮演" + " 角色设定: 设定自定义角色人设为{角色设定}。\n"
|
||||
help_text += f"{trigger_prefix}停止扮演: 清除设定的角色。\n"
|
||||
help_text += f"{trigger_prefix}角色类型"+" 角色类型: 查看某类{角色类型}的所有预设角色,为所有时输出所有预设角色。\n"
|
||||
help_text += (
|
||||
f"{trigger_prefix}角色类型" + " 角色类型: 查看某类{角色类型}的所有预设角色,为所有时输出所有预设角色。\n"
|
||||
)
|
||||
help_text += "\n目前的角色类型有: \n"
|
||||
help_text += ",".join([self.tags[tag][0] for tag in self.tags])+"。\n"
|
||||
help_text += ",".join([self.tags[tag][0] for tag in self.tags]) + "。\n"
|
||||
help_text += f"\n命令例子: \n{trigger_prefix}角色 写作助理\n"
|
||||
help_text += f"{trigger_prefix}角色类型 所有\n"
|
||||
help_text += f"{trigger_prefix}停止扮演\n"
|
||||
|
||||
@@ -428,4 +428,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"repo": {
|
||||
"sdwebui": {
|
||||
"url": "https://github.com/lanvent/plugin_sdwebui.git",
|
||||
"desc": "利用stable-diffusion画图的插件"
|
||||
},
|
||||
"replicate": {
|
||||
"url": "https://github.com/lanvent/plugin_replicate.git",
|
||||
"desc": "利用replicate api画图的插件"
|
||||
},
|
||||
"summary": {
|
||||
"url": "https://github.com/lanvent/plugin_summary.git",
|
||||
"desc": "总结聊天记录的插件"
|
||||
}
|
||||
"repo": {
|
||||
"sdwebui": {
|
||||
"url": "https://github.com/lanvent/plugin_sdwebui.git",
|
||||
"desc": "利用stable-diffusion画图的插件"
|
||||
},
|
||||
"replicate": {
|
||||
"url": "https://github.com/lanvent/plugin_replicate.git",
|
||||
"desc": "利用replicate api画图的插件"
|
||||
},
|
||||
"summary": {
|
||||
"url": "https://github.com/lanvent/plugin_summary.git",
|
||||
"desc": "总结聊天记录的插件"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
## 插件描述
|
||||
一个能让chatgpt联网,搜索,数字运算的插件,将赋予强大且丰富的扩展能力
|
||||
一个能让chatgpt联网,搜索,数字运算的插件,将赋予强大且丰富的扩展能力
|
||||
使用该插件需在机器人回复你的前提下,在对话内容前加$tool;仅输入$tool将返回tool插件帮助信息,用于测试插件是否加载成功
|
||||
### 本插件所有工具同步存放至专用仓库:[chatgpt-tool-hub](https://github.com/goldfishh/chatgpt-tool-hub)
|
||||
|
||||
|
||||
|
||||
|
||||
## 使用说明
|
||||
使用该插件后将默认使用4个工具, 无需额外配置长期生效:
|
||||
### 1. python
|
||||
使用该插件后将默认使用4个工具, 无需额外配置长期生效:
|
||||
### 1. python
|
||||
###### python解释器,使用它来解释执行python指令,可以配合你想要chatgpt生成的代码输出结果或执行事务
|
||||
|
||||
|
||||
### 2. url-get
|
||||
###### 往往用来获取某个网站具体内容,结果可能会被反爬策略影响
|
||||
|
||||
@@ -23,16 +23,16 @@
|
||||
|
||||
> meteo调优记录:https://github.com/zhayujie/chatgpt-on-wechat/issues/776#issuecomment-1500771334
|
||||
|
||||
## 使用本插件对话(prompt)技巧
|
||||
### 1. 有指引的询问
|
||||
## 使用本插件对话(prompt)技巧
|
||||
### 1. 有指引的询问
|
||||
#### 例如:
|
||||
- 总结这个链接的内容 https://github.com/goldfishh/chatgpt-tool-hub
|
||||
- 总结这个链接的内容 https://github.com/goldfishh/chatgpt-tool-hub
|
||||
- 使用Terminal执行curl cip.cc
|
||||
- 使用python查询今天日期
|
||||
|
||||
|
||||
### 2. 使用搜索引擎工具
|
||||
- 如果有搜索工具就能让chatgpt获取到你的未传达清楚的上下文信息,比如chatgpt不知道你的地理位置,现在时间等,所以无法查询到天气
|
||||
|
||||
|
||||
## 其他工具
|
||||
|
||||
### 5. wikipedia
|
||||
@@ -55,9 +55,9 @@
|
||||
### 10. google-search *
|
||||
###### google搜索引擎,申请流程较bing-search繁琐
|
||||
|
||||
###### 注1:带*工具需要获取api-key才能使用,部分工具需要外网支持
|
||||
###### 注1:带*工具需要获取api-key才能使用,部分工具需要外网支持
|
||||
#### [申请方法](https://github.com/goldfishh/chatgpt-tool-hub/blob/master/docs/apply_optional_tool.md)
|
||||
|
||||
|
||||
## config.json 配置说明
|
||||
###### 默认工具无需配置,其它工具需手动配置,一个例子:
|
||||
```json
|
||||
@@ -71,15 +71,15 @@
|
||||
}
|
||||
|
||||
```
|
||||
注:config.json文件非必须,未创建仍可使用本tool;带*工具需在kwargs填入对应api-key键值对
|
||||
注:config.json文件非必须,未创建仍可使用本tool;带*工具需在kwargs填入对应api-key键值对
|
||||
- `tools`:本插件初始化时加载的工具, 目前可选集:["wikipedia", "wolfram-alpha", "bing-search", "google-search", "news", "morning-news"] & 默认工具,除wikipedia工具之外均需要申请api-key
|
||||
- `kwargs`:工具执行时的配置,一般在这里存放**api-key**,或环境配置
|
||||
- `request_timeout`: 访问openai接口的超时时间,默认与wechat-on-chatgpt配置一致,可单独配置
|
||||
- `no_default`: 用于配置默认加载4个工具的行为,如果为true则仅使用tools列表工具,不加载默认工具
|
||||
- `top_k_results`: 控制所有有关搜索的工具返回条目数,数字越高则参考信息越多,但无用信息可能干扰判断,该值一般为2
|
||||
- `model_name`: 用于控制tool插件底层使用的llm模型,目前暂未测试3.5以外的模型,一般保持默认
|
||||
|
||||
|
||||
|
||||
|
||||
## 备注
|
||||
- 强烈建议申请搜索工具搭配使用,推荐bing-search
|
||||
- 虽然我会有意加入一些限制,但请不要使用本插件做危害他人的事情,请提前了解清楚某些内容是否会违反相关规定,建议提前做好过滤
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .tool import *
|
||||
from .tool import *
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
{
|
||||
"tools": ["python", "url-get", "terminal", "meteo-weather"],
|
||||
"tools": [
|
||||
"python",
|
||||
"url-get",
|
||||
"terminal",
|
||||
"meteo-weather"
|
||||
],
|
||||
"kwargs": {
|
||||
"top_k_results": 2,
|
||||
"no_default": false,
|
||||
"model_name": "gpt-3.5-turbo"
|
||||
"top_k_results": 2,
|
||||
"no_default": false,
|
||||
"model_name": "gpt-3.5-turbo"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
from chatgpt_tool_hub.apps import load_app
|
||||
from chatgpt_tool_hub.apps.app import App
|
||||
from chatgpt_tool_hub.tools.all_tool_list import get_all_tool_names
|
||||
|
||||
import plugins
|
||||
from bridge.bridge import Bridge
|
||||
from bridge.context import ContextType
|
||||
@@ -14,7 +15,13 @@ from config import conf
|
||||
from plugins import *
|
||||
|
||||
|
||||
@plugins.register(name="tool", desc="Arming your ChatGPT bot with various tools", version="0.3", author="goldfishh", desire_priority=0)
|
||||
@plugins.register(
|
||||
name="tool",
|
||||
desc="Arming your ChatGPT bot with various tools",
|
||||
version="0.3",
|
||||
author="goldfishh",
|
||||
desire_priority=0,
|
||||
)
|
||||
class Tool(Plugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -28,22 +35,26 @@ class Tool(Plugin):
|
||||
help_text = "这是一个能让chatgpt联网,搜索,数字运算的插件,将赋予强大且丰富的扩展能力。"
|
||||
if not verbose:
|
||||
return help_text
|
||||
trigger_prefix = conf().get('plugin_trigger_prefix', "$")
|
||||
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
|
||||
help_text += "使用说明:\n"
|
||||
help_text += f"{trigger_prefix}tool "+"命令: 根据给出的{命令}使用一些可用工具尽力为你得到结果。\n"
|
||||
help_text += f"{trigger_prefix}tool " + "命令: 根据给出的{命令}使用一些可用工具尽力为你得到结果。\n"
|
||||
help_text += f"{trigger_prefix}tool reset: 重置工具。\n"
|
||||
return help_text
|
||||
|
||||
def on_handle_context(self, e_context: EventContext):
|
||||
if e_context['context'].type != ContextType.TEXT:
|
||||
if e_context["context"].type != ContextType.TEXT:
|
||||
return
|
||||
|
||||
# 暂时不支持未来扩展的bot
|
||||
if Bridge().get_bot_type("chat") not in (const.CHATGPT, const.OPEN_AI, const.CHATGPTONAZURE):
|
||||
if Bridge().get_bot_type("chat") not in (
|
||||
const.CHATGPT,
|
||||
const.OPEN_AI,
|
||||
const.CHATGPTONAZURE,
|
||||
):
|
||||
return
|
||||
|
||||
content = e_context['context'].content
|
||||
content_list = e_context['context'].content.split(maxsplit=1)
|
||||
content = e_context["context"].content
|
||||
content_list = e_context["context"].content.split(maxsplit=1)
|
||||
|
||||
if not content or len(content_list) < 1:
|
||||
e_context.action = EventAction.CONTINUE
|
||||
@@ -52,13 +63,13 @@ class Tool(Plugin):
|
||||
logger.debug("[tool] on_handle_context. content: %s" % content)
|
||||
reply = Reply()
|
||||
reply.type = ReplyType.TEXT
|
||||
trigger_prefix = conf().get('plugin_trigger_prefix', "$")
|
||||
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
|
||||
# todo: 有些工具必须要api-key,需要修改config文件,所以这里没有实现query增删tool的功能
|
||||
if content.startswith(f"{trigger_prefix}tool"):
|
||||
if len(content_list) == 1:
|
||||
logger.debug("[tool]: get help")
|
||||
reply.content = self.get_help_text()
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
return
|
||||
elif len(content_list) > 1:
|
||||
@@ -66,12 +77,14 @@ class Tool(Plugin):
|
||||
logger.debug("[tool]: reset config")
|
||||
self.app = self._reset_app()
|
||||
reply.content = "重置工具成功"
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
return
|
||||
elif content_list[1].startswith("reset"):
|
||||
logger.debug("[tool]: remind")
|
||||
e_context['context'].content = "请你随机用一种聊天风格,提醒用户:如果想重置tool插件,reset之后不要加任何字符"
|
||||
e_context[
|
||||
"context"
|
||||
].content = "请你随机用一种聊天风格,提醒用户:如果想重置tool插件,reset之后不要加任何字符"
|
||||
|
||||
e_context.action = EventAction.BREAK
|
||||
return
|
||||
@@ -80,34 +93,35 @@ class Tool(Plugin):
|
||||
|
||||
# Don't modify bot name
|
||||
all_sessions = Bridge().get_bot("chat").sessions
|
||||
user_session = all_sessions.session_query(query, e_context['context']['session_id']).messages
|
||||
user_session = all_sessions.session_query(
|
||||
query, e_context["context"]["session_id"]
|
||||
).messages
|
||||
|
||||
# chatgpt-tool-hub will reply you with many tools
|
||||
logger.debug("[tool]: just-go")
|
||||
try:
|
||||
_reply = self.app.ask(query, user_session)
|
||||
e_context.action = EventAction.BREAK_PASS
|
||||
all_sessions.session_reply(_reply, e_context['context']['session_id'])
|
||||
all_sessions.session_reply(
|
||||
_reply, e_context["context"]["session_id"]
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
logger.error(str(e))
|
||||
|
||||
e_context['context'].content = "请你随机用一种聊天风格,提醒用户:这个问题tool插件暂时无法处理"
|
||||
e_context["context"].content = "请你随机用一种聊天风格,提醒用户:这个问题tool插件暂时无法处理"
|
||||
reply.type = ReplyType.ERROR
|
||||
e_context.action = EventAction.BREAK
|
||||
return
|
||||
|
||||
reply.content = _reply
|
||||
e_context['reply'] = reply
|
||||
e_context["reply"] = reply
|
||||
return
|
||||
|
||||
def _read_json(self) -> dict:
|
||||
curdir = os.path.dirname(__file__)
|
||||
config_path = os.path.join(curdir, "config.json")
|
||||
tool_config = {
|
||||
"tools": [],
|
||||
"kwargs": {}
|
||||
}
|
||||
tool_config = {"tools": [], "kwargs": {}}
|
||||
if not os.path.exists(config_path):
|
||||
return tool_config
|
||||
else:
|
||||
@@ -123,7 +137,9 @@ class Tool(Plugin):
|
||||
"proxy": conf().get("proxy", ""),
|
||||
"request_timeout": conf().get("request_timeout", 60),
|
||||
# note: 目前tool暂未对其他模型测试,但这里仍对配置来源做了优先级区分,一般插件配置可覆盖全局配置
|
||||
"model_name": tool_model_name if tool_model_name else conf().get("model", "gpt-3.5-turbo"),
|
||||
"model_name": tool_model_name
|
||||
if tool_model_name
|
||||
else conf().get("model", "gpt-3.5-turbo"),
|
||||
"no_default": kwargs.get("no_default", False),
|
||||
"top_k_results": kwargs.get("top_k_results", 2),
|
||||
# for news tool
|
||||
@@ -160,4 +176,7 @@ class Tool(Plugin):
|
||||
# filter not support tool
|
||||
tool_list = self._filter_tool_list(tool_config.get("tools", []))
|
||||
|
||||
return load_app(tools_list=tool_list, **self._build_tool_kwargs(tool_config.get("kwargs", {})))
|
||||
return load_app(
|
||||
tools_list=tool_list,
|
||||
**self._build_tool_kwargs(tool_config.get("kwargs", {})),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user