diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py index 262483622..95000a6b6 100644 --- a/cores/qqbot/core.py +++ b/cores/qqbot/core.py @@ -207,6 +207,61 @@ def initBot(cfg, prov): if 'reply_prefix' in cfg: _global_object.reply_prefix = cfg['reply_prefix'] + + gu.log("--------加载机器人平台--------", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) + thread_inst = None + admin_qq = cc.get('admin_qq', None) + admin_qqchan = cc.get('admin_qqchan', None) + if admin_qq == None: + gu.log("未设置管理者QQ号(管理者才能使用update/plugin等指令)", gu.LEVEL_WARNING) + admin_qq = input("请输入管理者QQ号(必须设置): ") + gu.log("管理者QQ号设置为: " + admin_qq, gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) + cc.put('admin_qq', admin_qq) + if admin_qqchan == None: + gu.log("未设置管理者QQ频道用户号(管理者才能使用update/plugin等指令)", gu.LEVEL_WARNING) + admin_qqchan = input("请输入管理者频道用户号(不是QQ号, 可以先回车跳过然后在频道发送指令!myid获取): ") + if admin_qqchan == "": + gu.log("跳过设置管理者频道用户号", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) + else: + gu.log("管理者频道用户号设置为: " + admin_qqchan, gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) + cc.put('admin_qqchan', admin_qqchan) + + gu.log("管理者QQ: " + admin_qq, gu.LEVEL_INFO) + gu.log("管理者频道用户号: " + admin_qqchan, gu.LEVEL_INFO) + _global_object.admin_qq = admin_qq + _global_object.admin_qqchan = admin_qqchan + + # GOCQ + global gocq_bot + + if 'gocqbot' in cfg and cfg['gocqbot']['enable']: + gu.log("- 启用QQ机器人 -", gu.LEVEL_INFO) + + global gocq_app, gocq_loop + gocq_loop = asyncio.new_event_loop() + gocq_bot = QQ(True, cc, gocq_loop) + thread_inst = threading.Thread(target=run_gocq_bot, args=(gocq_loop, gocq_bot, gocq_app), daemon=False) + thread_inst.start() + else: + gocq_bot = QQ(False) + + _global_object.platform_qq = gocq_bot + + gu.log("机器人部署教程: https://github.com/Soulter/QQChannelChatGPT/wiki/", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) + gu.log("如果有任何问题, 请在 https://github.com/Soulter/QQChannelChatGPT 上提交issue或加群322154837", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) + gu.log("请给 https://github.com/Soulter/QQChannelChatGPT 点个star!", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) + + # QQ频道 + if 'qqbot' in cfg and cfg['qqbot']['enable']: + gu.log("- 启用QQ频道机器人 -", gu.LEVEL_INFO) + global qqchannel_bot, qqchan_loop + qqchannel_bot = QQChan() + qqchan_loop = asyncio.new_event_loop() + _global_object.platform_qqchan = qqchannel_bot + thread_inst = threading.Thread(target=run_qqchan_bot, args=(cfg, qqchan_loop, qqchannel_bot), daemon=False) + thread_inst.start() + # thread.join() + # 语言模型提供商 gu.log("--------加载语言模型--------", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) @@ -312,8 +367,6 @@ def initBot(cfg, prov): nick_qq = tuple(nick_qq) _global_object.nick = nick_qq - thread_inst = None - gu.log("--------加载插件--------", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) # 加载插件 _command = Command(None, _global_object) @@ -327,59 +380,6 @@ def initBot(cfg, prov): llm_command_instance[NONE_LLM] = _command chosen_provider = NONE_LLM - gu.log("--------加载机器人平台--------", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) - - admin_qq = cc.get('admin_qq', None) - admin_qqchan = cc.get('admin_qqchan', None) - if admin_qq == None: - gu.log("未设置管理者QQ号(管理者才能使用update/plugin等指令)", gu.LEVEL_WARNING) - admin_qq = input("请输入管理者QQ号(必须设置): ") - gu.log("管理者QQ号设置为: " + admin_qq, gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) - cc.put('admin_qq', admin_qq) - if admin_qqchan == None: - gu.log("未设置管理者QQ频道用户号(管理者才能使用update/plugin等指令)", gu.LEVEL_WARNING) - admin_qqchan = input("请输入管理者频道用户号(不是QQ号, 可以先回车跳过然后在频道发送指令!myid获取): ") - if admin_qqchan == "": - gu.log("跳过设置管理者频道用户号", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) - else: - gu.log("管理者频道用户号设置为: " + admin_qqchan, gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) - cc.put('admin_qqchan', admin_qqchan) - - gu.log("管理者QQ: " + admin_qq, gu.LEVEL_INFO) - gu.log("管理者频道用户号: " + admin_qqchan, gu.LEVEL_INFO) - _global_object.admin_qq = admin_qq - _global_object.admin_qqchan = admin_qqchan - - # GOCQ - global gocq_bot - - if 'gocqbot' in cfg and cfg['gocqbot']['enable']: - gu.log("- 启用QQ机器人 -", gu.LEVEL_INFO) - - global gocq_app, gocq_loop - gocq_loop = asyncio.new_event_loop() - gocq_bot = QQ(True, cc, gocq_loop) - thread_inst = threading.Thread(target=run_gocq_bot, args=(gocq_loop, gocq_bot, gocq_app), daemon=False) - thread_inst.start() - else: - gocq_bot = QQ(False) - - _global_object.platform_qq = gocq_bot - - gu.log("机器人部署教程: https://github.com/Soulter/QQChannelChatGPT/wiki/", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) - gu.log("如果有任何问题, 请在 https://github.com/Soulter/QQChannelChatGPT 上提交issue或加群322154837", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) - gu.log("请给 https://github.com/Soulter/QQChannelChatGPT 点个star!", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) - - # QQ频道 - if 'qqbot' in cfg and cfg['qqbot']['enable']: - gu.log("- 启用QQ频道机器人 -", gu.LEVEL_INFO) - global qqchannel_bot, qqchan_loop - qqchannel_bot = QQChan() - qqchan_loop = asyncio.new_event_loop() - _global_object.platform_qqchan = qqchannel_bot - thread_inst = threading.Thread(target=run_qqchan_bot, args=(cfg, qqchan_loop, qqchannel_bot), daemon=False) - thread_inst.start() - # thread.join() if thread_inst == None: input("[System-Error] 没有启用/成功启用任何机器人,程序退出") @@ -606,7 +606,7 @@ async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, Nak if session_id in gocq_bot.waiting and gocq_bot.waiting[session_id] == '': gocq_bot.waiting[session_id] = qq_msg return - hit, command_result = llm_command_instance[chosen_provider].check_command( + hit, command_result = await llm_command_instance[chosen_provider].check_command( qq_msg, session_id, role, @@ -752,7 +752,6 @@ class botClient(botpy.Client): # 收到频道消息 async def on_at_message_create(self, message: Message): gu.log(str(message), gu.LEVEL_DEBUG, max_len=9999) - # 转换层 nakuru_guild_message = qqchannel_bot.gocq_compatible_receive(message) gu.log(f"转换后: {str(nakuru_guild_message)}", gu.LEVEL_DEBUG, max_len=9999) diff --git a/model/command/adapter/nonebot/command_arg.py b/model/command/adapter/nonebot/command_arg.py new file mode 100644 index 000000000..3f6e8a305 --- /dev/null +++ b/model/command/adapter/nonebot/command_arg.py @@ -0,0 +1,2 @@ +class CommandArg: + pass \ No newline at end of file diff --git a/model/command/adapter/nonebot/common.py b/model/command/adapter/nonebot/common.py new file mode 100644 index 000000000..ac7532768 --- /dev/null +++ b/model/command/adapter/nonebot/common.py @@ -0,0 +1,32 @@ +import sys +from types import ModuleType +import asyncio +from pyppeteer import launch + + +async def template_to_pic(template_path, template_name, templates, pages, wait, type, quality, device_scale_factor): + browser = await launch() + page = await browser.newPage() + await page.setViewport(pages["viewport"]) + await page.goto(pages["base_url"]) + await asyncio.sleep(wait) + await page.evaluate('''(templates) => { + // 在页面中执行 JavaScript 代码,将数据注入到模板中 + // 这里的示例代码仅供参考,具体需要根据实际情况修改 + document.getElementById('css').innerText = templates.css; + document.getElementById('data').innerText = JSON.stringify(templates.data); + document.getElementById('detail').innerText = templates.detail; + }''', templates) + screenshot = await page.screenshot({ + 'type': type, + 'quality': quality, + 'deviceScaleFactor': device_scale_factor + }) + await browser.close() + return screenshot + +def require(module_str: str): + module = ModuleType(module_str) + sys.modules[module_str] = module + if module_str == 'nonebot_plugin_htmlrender': + module.template_to_pic = template_to_pic diff --git a/model/command/adapter/nonebot/driver.py b/model/command/adapter/nonebot/driver.py new file mode 100644 index 000000000..a184fbcaf --- /dev/null +++ b/model/command/adapter/nonebot/driver.py @@ -0,0 +1,11 @@ +class Driver: + def __init__(self) -> None: + self.config = {} + + def on_startup(self, func): + pass + def on_bot_connect(self, func): + pass + +def get_driver(): + return Driver() \ No newline at end of file diff --git a/model/command/adapter/onebot/bot.py b/model/command/adapter/onebot/bot.py new file mode 100644 index 000000000..19be4bcb3 --- /dev/null +++ b/model/command/adapter/onebot/bot.py @@ -0,0 +1,2 @@ +class Bot: + pass \ No newline at end of file diff --git a/model/command/adapter/onebot/message.py b/model/command/adapter/onebot/message.py new file mode 100644 index 000000000..574b0ce16 --- /dev/null +++ b/model/command/adapter/onebot/message.py @@ -0,0 +1,2 @@ +class Message: + pass \ No newline at end of file diff --git a/model/command/adapter/onebot/message_event.py b/model/command/adapter/onebot/message_event.py new file mode 100644 index 000000000..11712e075 --- /dev/null +++ b/model/command/adapter/onebot/message_event.py @@ -0,0 +1,2 @@ +class MessageEvent: + pass \ No newline at end of file diff --git a/model/command/adapter/onebot/message_segment.py b/model/command/adapter/onebot/message_segment.py new file mode 100644 index 000000000..2905a1757 --- /dev/null +++ b/model/command/adapter/onebot/message_segment.py @@ -0,0 +1,2 @@ +class MessageSegment: + pass \ No newline at end of file diff --git a/model/command/adapter/protocol_adapter.py b/model/command/adapter/protocol_adapter.py new file mode 100644 index 000000000..1f0f6030a --- /dev/null +++ b/model/command/adapter/protocol_adapter.py @@ -0,0 +1,137 @@ +import sys +from types import ModuleType +import asyncio +from pyppeteer import launch + +from model.platform.qqchan import QQChan + +from .nonebot.driver import Driver, get_driver +from .onebot.message import Message +from .onebot.message_event import MessageEvent +from .onebot.message_segment import MessageSegment +from .nonebot.command_arg import CommandArg +from .onebot.bot import Bot + +from nakuru import ( + GuildMessage, + GroupMessage, + FriendMessage +) + +from typing import Union + +NONEBOT = "nonebot" + +class UnifiedBotCompatibleLayer(): + def __init__(self, platform_qq_sdk: QQChan) -> None: + # 初始化兼容层 + self.plugins: dict[str, CommandOper] = {} + self.platform_qq_sdk = platform_qq_sdk + self._nonebot() + self.load_plugins() + + async def check_commands(self, message: str, message_obj: Union[GroupMessage, FriendMessage, GuildMessage]): + for k in self.plugins: + if message.startswith(k): + if self.plugins[k].framework_name == NONEBOT: + await self._nonebot_plugins_oper(message, message_obj, k) + + async def _nonebot_plugins_oper(self, message: str, message_obj: Union[GroupMessage, FriendMessage, GuildMessage], plugin_name: str = None): + # bad implementation + # 高并发场景下,下面的代码是不安全的 + while self.plugins[plugin_name].message_obj is not None: + await asyncio.sleep(1) + self.plugins[plugin_name].message_obj = message_obj + bot, event, arg = self._nonebot_adapter(message_obj) + await self.plugins[plugin_name].exec(bot, event, arg) # wrapper + + def load_plugins(self): + import nonebot_plugin_gspanel.nonebot_plugin_gspanel + + def _nonebot(self): + # 模拟 nonebot 模块 + nonebot_module = ModuleType('nonebot') + sys.modules['nonebot'] = nonebot_module + + nonebot_log_module = ModuleType('nonebot.log') + sys.modules['nonebot.log'] = nonebot_log_module + + nonebot_adapter_module = ModuleType('nonebot.adapters') + sys.modules['nonebot.adapters'] = nonebot_adapter_module + + nonebot_params_module = ModuleType('nonebot.params') + sys.modules['nonebot.params'] = nonebot_params_module + + nonebot_drivers_module = ModuleType('nonebot.drivers') + sys.modules['nonebot.drivers'] = nonebot_drivers_module + + nonebot_plugin_module = ModuleType('nonebot.plugin') + sys.modules['nonebot.plugin'] = nonebot_plugin_module + + nonebot_adapter_onebot_v11_module = ModuleType('nonebot.adapters.onebot.v11') + sys.modules['nonebot.adapters.onebot.v11'] = nonebot_adapter_onebot_v11_module + + nonebot_adapter_onebot_v11_event_module = ModuleType('nonebot.adapters.onebot.v11.event') + sys.modules['nonebot.adapters.onebot.v11.event'] = nonebot_adapter_onebot_v11_event_module + + nonebot_adapter_onebot_v11_message_module = ModuleType('nonebot.adapters.onebot.v11.message') + sys.modules['nonebot.adapters.onebot.v11.message'] = nonebot_adapter_onebot_v11_message_module + + nonebot_log_module.logger = lambda: None + nonebot_adapter_module.Message = Message + nonebot_params_module.CommandArg = CommandArg + on_command = wrap_on_command(self) + nonebot_plugin_module.on_command = on_command + nonebot_adapter_onebot_v11_module.Bot = Bot + nonebot_adapter_onebot_v11_event_module.MessageEvent = MessageEvent + nonebot_adapter_onebot_v11_message_module.MessageSegment = MessageSegment + nonebot_module.get_driver = get_driver + nonebot_module.require = require + nonebot_drivers_module.Driver = Driver + + def _nonebot_adapter(self, message_obj): + bot = Bot() + event = MessageEvent() + arg = CommandArg() + # tododssss + return bot, event, arg + + +class BaseBot(): + def __init__(self, framework_name) -> None: + self.framework_name = framework_name + +class CommandOper(BaseBot): + ''' + CommandOper for NoneBot + ''' + def __init__(self, name, aliases=None, priority=1, block=False, _ubcl: UnifiedBotCompatibleLayer = None) -> None: + super().__init__("nonebot") + self.name = name + self.aliases = aliases + self.priority = priority + self.block = block + self.exec = None + self._ubcl = _ubcl + self.message_obj: Union[GroupMessage, FriendMessage, GuildMessage] = None + _ubcl.plugins[name] = self + + def handle(self): + def decorator(func): + async def wrapper(bot: Bot, event: MessageEvent, arg: Message = CommandArg(), *args, **kwargs): + # 你可以在这里添加自定义的处理逻辑 + print(f"Command {self.name} is executed.") + await func(bot, event, arg, *args, **kwargs) + self.exec = wrapper + return wrapper + return decorator + + async def finish(self, msg, at_sender = True): + if self.message_obj is not None: + self._ubcl.platform_qq_sdk.send(self.message_obj, msg) + self.message_obj = None + +def wrap_on_command(_ubcl: UnifiedBotCompatibleLayer): + def on_command(name, aliases=None, priority=1, block=False): + return CommandOper(name, aliases, priority, block, _ubcl = _ubcl) + return on_command diff --git a/model/command/command.py b/model/command/command.py index b23c4ec05..b25f774b1 100644 --- a/model/command/command.py +++ b/model/command/command.py @@ -26,21 +26,29 @@ from PIL import Image as PILImage from cores.qqbot.global_object import GlobalObject, AstrMessageEvent from pip._internal import main as pipmain +from .adapter.protocol_adapter import UnifiedBotCompatibleLayer +import asyncio + PLATFORM_QQCHAN = 'qqchan' PLATFORM_GOCQ = 'gocq' # 指令功能的基类,通用的(不区分语言模型)的指令就在这实现 class Command: - def __init__(self, provider: Provider, global_object: GlobalObject = None): + def __init__(self, provider: Provider, global_object: GlobalObject = None, unified_bot_compatible_layer: UnifiedBotCompatibleLayer = None): self.provider = provider self.global_object = global_object + self.unified_bot_compatible_layer = unified_bot_compatible_layer - def check_command(self, + + async def check_command(self, message, session_id: str, role, platform, message_obj): + # UBCL + await self.unified_bot_compatible_layer.check_commands(message, message_obj) + # 插件 cached_plugins = self.global_object.cached_plugins ame = AstrMessageEvent( @@ -70,10 +78,8 @@ class Command: if self.command_start_with(message, "nick"): return True, self.set_nick(message, platform, role) - if self.command_start_with(message, "plugin"): return True, self.plugin_oper(message, role, cached_plugins, platform) - if self.command_start_with(message, "myid") or self.command_start_with(message, "!myid"): return True, self.get_my_id(message_obj) if self.command_start_with(message, "nconf") or self.command_start_with(message, "newconf"): diff --git a/model/command/openai_official.py b/model/command/openai_official.py index aa5247e6f..5974f12e1 100644 --- a/model/command/openai_official.py +++ b/model/command/openai_official.py @@ -5,6 +5,7 @@ from cores.qqbot.personality import personalities from model.platform.qq import QQ from util import general_utils as gu from cores.qqbot.global_object import GlobalObject +from .adapter.protocol_adapter import UnifiedBotCompatibleLayer class CommandOpenAIOfficial(Command): def __init__(self, provider: ProviderOpenAIOfficial, global_object: GlobalObject): @@ -12,16 +13,17 @@ class CommandOpenAIOfficial(Command): self.cached_plugins = {} self.global_object = global_object self.personality_str = "" - super().__init__(provider, global_object) + self.unified_bot_compatible_layer = UnifiedBotCompatibleLayer(self.global_object.platform_qqchan) + super().__init__(provider, global_object, self.unified_bot_compatible_layer) - def check_command(self, + async def check_command(self, message: str, session_id: str, role: str, platform: str, message_obj): self.platform = platform - hit, res = super().check_command( + hit, res = await super().check_command( message, session_id, role, diff --git a/model/command/rev_chatgpt.py b/model/command/rev_chatgpt.py index 6780930c2..3f8399e8b 100644 --- a/model/command/rev_chatgpt.py +++ b/model/command/rev_chatgpt.py @@ -12,14 +12,14 @@ class CommandRevChatGPT(Command): self.personality_str = "" super().__init__(provider, global_object) - def check_command(self, + async def check_command(self, message: str, session_id: str, role: str, platform: str, message_obj): self.platform = platform - hit, res = super().check_command( + hit, res = await super().check_command( message, session_id, role, diff --git a/model/platform/qqchan.py b/model/platform/qqchan.py index 217598ae4..ad3974f82 100644 --- a/model/platform/qqchan.py +++ b/model/platform/qqchan.py @@ -188,4 +188,9 @@ class QQChan(): _n = NakuruGuildMessage() _n.channel_id = channel_id self.send_qq_msg(_n, message_chain) - \ No newline at end of file + + def send(self, message: NakuruGuildMessage, res: list): + ''' + 同 send_qq_msg。回复频道消息 + ''' + self.send_qq_msg(message, res) \ No newline at end of file