diff --git a/common/package_manager.py b/common/package_manager.py new file mode 100644 index 0000000..6430934 --- /dev/null +++ b/common/package_manager.py @@ -0,0 +1,15 @@ +import pip + +def install(package): + pip.main(['install', package]) + +def install_requirements(file): + pip.main(['install', '-r', file, "--upgrade"]) + +def check_dulwich(): + try: + import dulwich + return + except ImportError: + install('dulwich') + raise ImportError("Unable to import dulwich") \ No newline at end of file diff --git a/plugins/banwords/__init__.py b/plugins/banwords/__init__.py index e69de29..97b59e1 100644 --- a/plugins/banwords/__init__.py +++ b/plugins/banwords/__init__.py @@ -0,0 +1 @@ +from .banwords import * \ No newline at end of file diff --git a/plugins/bdunit/__init__.py b/plugins/bdunit/__init__.py index e69de29..bb327a2 100644 --- a/plugins/bdunit/__init__.py +++ b/plugins/bdunit/__init__.py @@ -0,0 +1 @@ +from .bdunit import * \ No newline at end of file diff --git a/plugins/dungeon/__init__.py b/plugins/dungeon/__init__.py index e69de29..4c35098 100644 --- a/plugins/dungeon/__init__.py +++ b/plugins/dungeon/__init__.py @@ -0,0 +1 @@ +from .dungeon import * \ No newline at end of file diff --git a/plugins/finish/__init__.py b/plugins/finish/__init__.py new file mode 100644 index 0000000..42e0ec7 --- /dev/null +++ b/plugins/finish/__init__.py @@ -0,0 +1 @@ +from .finish import * \ No newline at end of file diff --git a/plugins/godcmd/__init__.py b/plugins/godcmd/__init__.py index e69de29..a0a68bd 100644 --- a/plugins/godcmd/__init__.py +++ b/plugins/godcmd/__init__.py @@ -0,0 +1 @@ +from .godcmd import * \ No newline at end of file diff --git a/plugins/godcmd/godcmd.py b/plugins/godcmd/godcmd.py index cb1da1a..25a26d6 100644 --- a/plugins/godcmd/godcmd.py +++ b/plugins/godcmd/godcmd.py @@ -37,10 +37,10 @@ COMMANDS = { "alias": ["reset_openai_api_key"], "desc": "重置为默认的api_key", }, - # "id": { - # "alias": ["id", "用户"], - # "desc": "获取用户id", #目前无实际意义 - # }, + "id": { + "alias": ["id", "用户"], + "desc": "获取用户id", # wechaty和wechatmp的用户id不会变化,可用于绑定管理员 + }, "reset": { "alias": ["reset", "重置会话"], "desc": "重置会话", @@ -92,6 +92,16 @@ ADMIN_COMMANDS = { "args": ["插件名"], "desc": "禁用指定插件", }, + "installp": { + "alias": ["installp", "安装插件"], + "args": ["仓库地址或插件名"], + "desc": "安装指定插件", + }, + "uninstallp": { + "alias": ["uninstallp", "卸载插件"], + "args": ["插件名"], + "desc": "卸载指定插件", + }, "debug": { "alias": ["debug", "调试模式", "DEBUG"], "desc": "开启机器调试日志", @@ -103,7 +113,9 @@ def get_help_text(isadmin, isgroup): for cmd, info in COMMANDS.items(): if cmd=="auth": #不提示认证指令 continue - alias=["#"+a for a in info['alias']] + if cmd=="id" and conf().get("channel_type","wx") not in ["wxy","wechatmp"]: + continue + alias=["#"+a for a in info['alias'][:1]] help_text += f"{','.join(alias)} " if 'args' in info: args=[a for a in info['args']] @@ -122,7 +134,7 @@ def get_help_text(isadmin, isgroup): if ADMIN_COMMANDS and isadmin: help_text += "\n\n管理员指令:\n" for cmd, info in ADMIN_COMMANDS.items(): - alias=["#"+a for a in info['alias']] + alias=["#"+a for a in info['alias'][:1]] help_text += f"{','.join(alias)} " if 'args' in info: args=[a for a in info['args']] @@ -208,6 +220,8 @@ class Godcmd(Plugin): break if not ok: result = "插件不存在或未启用" + elif cmd == "id": + ok, result = True, user elif cmd == "set_openai_api_key": if len(args) == 1: user_data = conf().get_user_data(user) @@ -310,7 +324,16 @@ class Godcmd(Plugin): result = "插件已禁用" else: result = "插件不存在" - + elif cmd == "installp": + if len(args) != 1: + ok, result = False, "请提供插件名或.git结尾的仓库地址" + else: + ok, result = PluginManager().install_plugin(args[0]) + elif cmd == "uninstallp": + if len(args) != 1: + ok, result = False, "请提供插件名" + else: + ok, result = PluginManager().uninstall_plugin(args[0]) logger.debug("[Godcmd] admin command: %s by %s" % (cmd, user)) else: ok, result = False, "需要管理员权限才能执行该指令" diff --git a/plugins/hello/__init__.py b/plugins/hello/__init__.py index e69de29..3b3590a 100644 --- a/plugins/hello/__init__.py +++ b/plugins/hello/__init__.py @@ -0,0 +1 @@ +from .hello import * \ No newline at end of file diff --git a/plugins/plugin_manager.py b/plugins/plugin_manager.py index c6a6631..d30a02e 100644 --- a/plugins/plugin_manager.py +++ b/plugins/plugin_manager.py @@ -17,6 +17,7 @@ class PluginManager: self.listening_plugins = {} self.instances = {} self.pconf = {} + self.current_plugin_path = None def register(self, name: str, desire_priority: int = 0, **kwargs): def wrapper(plugincls): @@ -24,12 +25,13 @@ class PluginManager: plugincls.priority = desire_priority 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.enabled = True self.plugins[name.upper()] = plugincls - logger.info("Plugin %s_v%s registered" % (name, plugincls.version)) + logger.info("Plugin %s_v%s registered, path=%s" % (name, plugincls.version, plugincls.path)) return plugincls return wrapper @@ -59,12 +61,13 @@ class PluginManager: for plugin_name in os.listdir(plugins_dir): plugin_path = os.path.join(plugins_dir, plugin_name) if os.path.isdir(plugin_path): - # 判断插件是否包含同名.py文件 - main_module_path = os.path.join(plugin_path, plugin_name+".py") + # 判断插件是否包含同名__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, plugin_name) + import_path = "plugins.{}".format(plugin_name) try: + self.current_plugin_path = plugin_path main_module = importlib.import_module(import_path) except Exception as e: logger.warn("Failed to import plugin %s: %s" % (plugin_name, e)) @@ -179,4 +182,59 @@ class PluginManager: return True def list_plugins(self): - return self.plugins \ No newline at end of file + return self.plugins + + 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") as f: + source = json.load(f) + if repo in source["repo"]: + repo = source["repo"][repo]["url"] + match = re.match(r"^(https?:\/\/|git@)([^\/:]+)[\/:]([^\/:]+)\/(.+).git$", repo) + if not match: + return False, "source中的仓库地址不合法" + else: + return False, "仓库地址不合法" + except Exception as e: + logger.error("Failed to install plugin, {}".format(e)) + return False, "安装插件失败" + 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")): + logger.info("detect requirements.txt,installing...") + pkgmgr.install_requirements(os.path.join(dirname,"requirements.txt")) + return True, "安装插件成功,请扫描插件或重启程序" + except Exception as e: + logger.error("Failed to install plugin, {}".format(e)) + return False, "安装插件失败" + + def uninstall_plugin(self, name:str): + name = name.upper() + if name not in self.plugins: + return False, "插件不存在" + if name in self.instances: + self.disable_plugin(name) + dirname = self.plugins[name].path + try: + import shutil + shutil.rmtree(dirname) + del self.plugins[name] + return True, "卸载插件成功" + except Exception as e: + logger.error("Failed to uninstall plugin, {}".format(e)) + return False, "卸载插件失败" \ No newline at end of file diff --git a/plugins/role/__init__.py b/plugins/role/__init__.py index e69de29..9e5b2b1 100644 --- a/plugins/role/__init__.py +++ b/plugins/role/__init__.py @@ -0,0 +1 @@ +from .role import * \ No newline at end of file diff --git a/plugins/source.json b/plugins/source.json new file mode 100644 index 0000000..6ff7e1e --- /dev/null +++ b/plugins/source.json @@ -0,0 +1,7 @@ +{ + "repo": { + "sdwebui": { + "url": "https://github.com/lanvent/plugin_sdwebui.git" + } + } +} \ No newline at end of file diff --git a/plugins/tool/__init__.py b/plugins/tool/__init__.py index e69de29..d3bf330 100644 --- a/plugins/tool/__init__.py +++ b/plugins/tool/__init__.py @@ -0,0 +1 @@ +from .tool import * \ No newline at end of file diff --git a/requirements-optional.txt b/requirements-optional.txt index ff9234e..55e3f7a 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -8,6 +8,9 @@ pyttsx3>=2.90 # pytsx text to speech baidu_aip>=4.16.10 # baidu voice # azure-cognitiveservices-speech # azure voice +#install plugin +dulwich + # wechaty wechaty>=0.10.7 wechaty_puppet>=0.4.23