diff --git a/src/ZM/Adapters/AdapterInterface.php b/src/ZM/Adapters/AdapterInterface.php new file mode 100644 index 00000000..b485c674 --- /dev/null +++ b/src/ZM/Adapters/AdapterInterface.php @@ -0,0 +1,29 @@ +data, true); + + // 将数据存入协程参数中 + set_coroutine_params(compact('data')); + + try { + // 事件类型不存在,代表为非法请求或 API 响应 + if (!isset($data['post_type'])) { + if (isset($data['echo']) && CoMessage::resumeByWS()) { + EventDispatcher::interrupt(); + } + $this->handleAPIResponse($data, $context); + return; + } + + if ($data['post_type'] !== 'meta_event') { + $before_result = $this->handleBeforeEvent($data, 'pre'); + if ($before_result->store === 'block') { + EventDispatcher::interrupt(); + } + } + + if (CoMessage::resumeByWS()) { + EventDispatcher::interrupt(); + } + + if ($data['post_type'] !== 'meta_event') { + $before_result = $this->handleBeforeEvent($data, 'post'); + if ($before_result->store === 'block') { + EventDispatcher::interrupt(); + } + } + + switch ($data['post_type']) { + case 'message': + $this->handleMessageEvent($data, $context); + break; + case 'meta_event': + $this->handleMetaEvent($data, $context); + break; + case 'notice': + $this->handleNoticeEvent($data, $context); + break; + case 'request': + $this->handleRequestEvent($data, $context); + break; + } + + if ($data['post_type'] !== 'meta_event') { + $before_result = $this->handleAfterEvent($data); + if ($before_result->store === 'block') { + EventDispatcher::interrupt(); + } + } + } catch (WaitTimeoutException $e) { + $e->module->finalReply($e->getMessage()); + } finally { + if (isset($data['post_type']) && $data['post_type'] !== 'meta_event') { + $before_result = $this->handleAfterEvent($data); + if ($before_result->store === 'block') { + EventDispatcher::interrupt(); + } + } + } + } + + /** + * 处理 API 响应 + * + * @param array $data 数据 + * @param ContextInterface $context 上下文 + */ + private function handleAPIResponse(array $data, ContextInterface $context): void + { + set_coroutine_params(['cq_response' => $data]); + $dispatcher = new EventDispatcher(CQAPIResponse::class); + $dispatcher->setRuleFunction(function (CQAPIResponse $event) use ($context) { + return $event->retcode === $context->getCQResponse()['retcode']; + }); + $dispatcher->dispatchEvents($data); + } + + /** + * 处理消息事件 + * + * @param array $data 消息数据 + * @param ContextInterface $context 上下文 + */ + private function handleMessageEvent(array $data, ContextInterface $context): void + { + // 分发 CQCommand 事件 + $dispatcher = new EventDispatcher(CQCommand::class); + // 设定返回值处理函数 + $dispatcher->setReturnFunction(function ($result) use ($context) { + if (is_string($result)) { + $context->reply($result); + } + if ($context->getCache('has_reply') === true) { + EventDispatcher::interrupt(); + } + }); + + $message = $data['message']; + + // 将消息段数组转换为消息字符串 + if (is_array($message)) { + $message = MessageUtil::arrayToStr($message); + } + + // 匹配命令 + $match_result = MessageUtil::matchCommand($message, $context->getData()); + + if ($match_result->status) { + $matches = $match_result->match; + + $input_arguments = MessageUtil::checkArguments($match_result->object->class, $match_result->object->method, $matches); + if (!empty($matches)) { + $context->setCache('match', $matches); + } + + $dispatcher->dispatchEvent($match_result->object, null, ...$input_arguments); + + // 处理命令返回结果 + if (is_string($dispatcher->store)) { + $context->reply($dispatcher->store); + } + + if ($context->getCache('has_reply') === true) { + $policy = ZMConfig::get('global', 'onebot')['message_command_policy'] ?? 'interrupt'; + switch ($policy) { + case 'interrupt': + EventDispatcher::interrupt(); + // no break + case 'continue': + break; + default: + throw new \Exception('未知的消息命令策略:' . $policy); + } + } + } + + // 分发 CQMessage 事件 + $dispatcher = new EventDispatcher(CQMessage::class); + // 设定匹配规则函数 + $dispatcher->setRuleFunction(function (CQMessage $event) use ($context) { + return ($event->message === '' || ($event->message === $context->getMessage())) + && ($event->user_id === 0 || ($event->user_id === $context->getUserId())) + && ($event->group_id === 0 || ($event->group_id === ($context->getGroupId() ?? 0))) + && ($event->message_type === '' || ($event->message_type === $context->getMessageType())) + && ($event->raw_message === '' || ($event->raw_message === $context->getData()['raw_message'])); + }); + // 设定返回值处理函数 + $dispatcher->setReturnFunction(function ($result) use ($context) { + if (is_string($result)) { + $context->reply($result); + } + }); + + $dispatcher->dispatchEvents($context->getMessage()); + } + + /** + * 处理元事件 + * + * @param array $data 消息数据 + * @param ContextInterface $context 上下文 + */ + private function handleMetaEvent(array $data, ContextInterface $context): void + { + $dispatcher = new EventDispatcher(CQMetaEvent::class); + // 设定匹配规则函数 + $dispatcher->setRuleFunction(function (CQMetaEvent $event) use ($context) { + return compare_object_and_array_by_keys($event, $context->getData(), ['meta_event_type']); + }); + + $dispatcher->dispatchEvents($context->getData()); + } + + /** + * 处理通知事件 + * + * @param array $data 消息数据 + * @param ContextInterface $context 上下文 + */ + private function handleNoticeEvent(array $data, ContextInterface $context): void + { + $dispatcher = new EventDispatcher(CQNotice::class); + // 设定匹配规则函数 + $dispatcher->setRuleFunction(function (CQNotice $event) use ($context) { + return compare_object_and_array_by_keys($event, $context->getData(), ['notice_type', 'sub_type', 'group_id', 'operator_id']); + }); + + $dispatcher->dispatchEvents($context->getData()); + } + + /** + * 处理请求事件 + * + * @param array $data 消息数据 + * @param ContextInterface $context 上下文 + */ + private function handleRequestEvent(array $data, ContextInterface $context): void + { + $dispatcher = new EventDispatcher(CQRequest::class); + // 设定匹配规则函数 + $dispatcher->setRuleFunction(function (CQRequest $event) use ($context) { + return compare_object_and_array_by_keys($event, $context->getData(), ['request_type', 'sub_type', 'user_id', 'comment']); + }); + + $dispatcher->dispatchEvents($context->getData()); + } + + /** + * 处理前置事件 + * + * @param array $data 消息数据 + * @param string $time 执行时机 + */ + private function handleBeforeEvent(array $data, string $time): EventDispatcher + { + $dispatcher = new EventDispatcher(CQBefore::class); + // 设定匹配规则函数 + $dispatcher->setRuleFunction(function (CQBefore $event) use ($data, $time) { + if ($time === 'pre') { + $level = $event->level >= 200; + } else { + $level = $event->level < 200; + } + return $level && ($event->cq_event === $data['post_type']); + }); + // 设定返回值处理函数 + $dispatcher->setReturnFunction(function ($result) { + if (!$result) { + EventDispatcher::interrupt('block'); + } + }); + + $dispatcher->dispatchEvents($data); + return $dispatcher; + } + + /** + * 处理后置事件 + * + * @param array $data 消息数据 + */ + private function handleAfterEvent(array $data): EventDispatcher + { + $dispatcher = new EventDispatcher(CQAfter::class); + // 设定匹配规则函数 + $dispatcher->setRuleFunction(function (CQAfter $event) use ($data) { + return $event->cq_event === $data['post_type']; + }); + + $dispatcher->dispatchEvents($data); + return $dispatcher; + } +} diff --git a/src/ZM/Event/EventDispatcher.php b/src/ZM/Event/EventDispatcher.php index 30392351..6e00f4bd 100644 --- a/src/ZM/Event/EventDispatcher.php +++ b/src/ZM/Event/EventDispatcher.php @@ -10,9 +10,11 @@ use Doctrine\Common\Annotations\AnnotationException; use Error; use Exception; use Throwable; +use ZM\Adapters\OneBot11Adapter; use ZM\Config\ZMConfig; use ZM\Console\Console; use ZM\Exception\InterruptException; +use ZM\Module\QQBot; use ZM\Store\LightCacheInside; use ZM\Store\Lock\SpinLock; use ZM\Store\ZMAtomic; @@ -117,6 +119,11 @@ class EventDispatcher { try { foreach ((EventManager::$events[$this->class] ?? []) as $v) { +// if ($v->class === QQBot::class && $v->method === 'handleByEvent') { +// zm_dump(EventManager::$events[$this->class]); +// $v->class = OneBot11Adapter::class; +// $v->method = 'handleIncomingRequest'; +// } $this->dispatchEvent($v, $this->rule, ...$params); if ($this->log) { Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . '::' . (is_string($v->method) ? $v->method : '{closure}') . ' 分发结束。'); diff --git a/src/ZM/Event/SwooleEvent/OnWorkerStart.php b/src/ZM/Event/SwooleEvent/OnWorkerStart.php index 51d36aa3..a5762eaf 100644 --- a/src/ZM/Event/SwooleEvent/OnWorkerStart.php +++ b/src/ZM/Event/SwooleEvent/OnWorkerStart.php @@ -14,6 +14,8 @@ use Swoole\Coroutine; use Swoole\Database\PDOConfig; use Swoole\Process; use Swoole\WebSocket\Server; +use ZM\Adapters\AdapterInterface; +use ZM\Adapters\OneBot11Adapter; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Swoole\OnMessageEvent; use ZM\Annotation\Swoole\OnStart; @@ -30,7 +32,6 @@ use ZM\Event\SwooleEvent; use ZM\Exception\DbException; use ZM\Exception\ZMKnownException; use ZM\Framework; -use ZM\Module\QQBot; use ZM\MySQL\MySQLPool; use ZM\Store\LightCacheInside; use ZM\Store\MySQL\SqlPoolStorage; @@ -215,8 +216,8 @@ class OnWorkerStart implements SwooleEvent Console::debug('OneBot support enabled, listening OneBot event(3).'); $obj = new OnMessageEvent(); $obj->connect_type = 'qq'; - $obj->class = QQBot::class; - $obj->method = 'handleByEvent'; + $obj->class = AdapterInterface::class; + $obj->method = 'handleIncomingRequest'; $obj->level = $obb_onebot['message_level'] ?? 99; EventManager::addEvent(OnMessageEvent::class, $obj); if ($obb_onebot['single_bot_mode']) { @@ -310,5 +311,7 @@ class OnWorkerStart implements SwooleEvent // 基础 $container->instance('server', $server); $container->instance('worker_id', $server->worker_id); + + $container->singleton(AdapterInterface::class, OneBot11Adapter::class); } } diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index 47df4fc9..510366c7 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -10,7 +10,6 @@ use ZM\Console\Console; use ZM\Framework; use ZM\Store\Lock\SpinLock; use ZM\Store\ZMAtomic; -use ZM\Store\ZMBuf; use function file_get_contents; use function get_included_files; use function is_callable; @@ -51,11 +50,7 @@ class ZMUtil public static function getModInstance($class) { - if (!isset(ZMBuf::$instance[$class])) { - // Console::debug('Class instance $class not exist, so I created it.'); - return ZMBuf::$instance[$class] = new $class(); - } - return ZMBuf::$instance[$class]; + return resolve($class); } /** diff --git a/src/ZM/global_functions.php b/src/ZM/global_functions.php index ac223c53..e559550b 100644 --- a/src/ZM/global_functions.php +++ b/src/ZM/global_functions.php @@ -759,6 +759,23 @@ function app(string $abstract = null, array $parameters = []) return resolve($abstract, $parameters); } +/** + * 根据键名比较对象和数组 + * + * @param object $object 对象 + * @param array $array 数组 + * @param array $keys 键名 + */ +function compare_object_and_array_by_keys(object $object, array $array, array $keys): bool +{ + foreach ($keys as $key) { + if (!isset($object->{$key}, $array[$key]) || $object->{$key} !== $array[$key]) { + return false; + } + } + return true; +} + /** * 以下为废弃的函数,将于未来移除 */