mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-18 05:04:51 +08:00
add adapter feature (#121)
This commit is contained in:
parent
2481124ada
commit
a6401fa9ef
29
src/ZM/Adapters/AdapterInterface.php
Normal file
29
src/ZM/Adapters/AdapterInterface.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Adapters;
|
||||
|
||||
use Swoole\WebSocket\Frame;
|
||||
use ZM\Context\ContextInterface;
|
||||
|
||||
interface AdapterInterface
|
||||
{
|
||||
/**
|
||||
* 获取适配器名称
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* 获取适配器版本
|
||||
*/
|
||||
public function getVersion(): string;
|
||||
|
||||
/**
|
||||
* 处理传入请求
|
||||
*
|
||||
* @param Frame $frame WebSocket消息帧
|
||||
* @param ContextInterface $context 上下文
|
||||
*/
|
||||
public function handleIncomingRequest(Frame $frame, ContextInterface $context): void;
|
||||
}
|
||||
303
src/ZM/Adapters/OneBot11Adapter.php
Normal file
303
src/ZM/Adapters/OneBot11Adapter.php
Normal file
@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Adapters;
|
||||
|
||||
use Swoole\WebSocket\Frame;
|
||||
use ZM\Annotation\CQ\CQAfter;
|
||||
use ZM\Annotation\CQ\CQAPIResponse;
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
use ZM\Annotation\CQ\CQMetaEvent;
|
||||
use ZM\Annotation\CQ\CQNotice;
|
||||
use ZM\Annotation\CQ\CQRequest;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Context\ContextInterface;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Utils\CoMessage;
|
||||
use ZM\Utils\MessageUtil;
|
||||
|
||||
class OneBot11Adapter implements AdapterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'onebot';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getVersion(): string
|
||||
{
|
||||
return '11';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function handleIncomingRequest(Frame $frame, ContextInterface $context): void
|
||||
{
|
||||
$data = json_decode($frame->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;
|
||||
}
|
||||
}
|
||||
@ -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}') . ' 分发结束。');
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以下为废弃的函数,将于未来移除
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user