update to 1.1.0 version

This commit is contained in:
crazywhalecc 2023-03-11 19:37:56 +08:00
parent 934ebbec42
commit 33ae4dbf82
No known key found for this signature in database
GPG Key ID: 1F4BDD59391F2680
8 changed files with 299 additions and 223 deletions

View File

@ -138,7 +138,7 @@ composer require zhamao/go-cqhttp-adapter-plugin
- 如果请求动作是 `download_file`,响应中的 `file` 字段转换为 `file_id` - 如果请求动作是 `download_file`,响应中的 `file` 字段转换为 `file_id`
- 如果请求动作是 `set_group_name``set_group_leave`,参数保持不变。 - 如果请求动作是 `set_group_name``set_group_leave`,参数保持不变。
- 如果请求动作是 `get_status`,仅保留 `good` 字段OB12 的 `bots` 字段值为 `[['impl' => 'go-cqhttp', 'version' => $user_agent, 'onebot_version' => '12']]` - 如果请求动作是 `get_status`,仅保留 `good` 字段OB12 的 `bots` 字段值为 `[['impl' => 'go-cqhttp', 'version' => $user_agent, 'onebot_version' => '12']]`
- 如果请求动作是 `get_version_info``app_name` 转换为 `impl``app_version` 转换为 `version``onebot_version` 设置为 12其他值丢弃。 - 如果请求动作是 `get_version_info``app_name` 转换为 `impl`且 impl 值后附加 `(go-cqhttp-adapter converted)``app_version` 转换为 `version``onebot_version` 设置为 12其他值丢弃。
## 其他不兼容项 ## 其他不兼容项

View File

@ -1,16 +1,19 @@
{ {
"name": "zhamao/go-cqhttp-adapter-plugin", "name": "zhamao/go-cqhttp-adapter-plugin",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"GocqAdapter\\": "src/" "GocqAdapter\\": "src/"
}
},
"require": {
"php": "~8.0 || ~8.1 || ~8.2"
},
"require-dev": {
"zhamao/framework": "dev-main"
},
"minimum-stability": "dev",
"extra": {
"zm-plugin-version": "1.0.0"
} }
},
"require": {
"php": "~8.0 || ~8.1"
},
"require-dev": {
"zhamao/framework": "dev-main"
},
"minimum-stability": "dev"
} }

80
src/GoActionTrait.php Normal file
View File

@ -0,0 +1,80 @@
<?php
namespace GocqAdapter;
use OneBot\Driver\Coroutine\Adaptive;
use OneBot\V12\Object\Action;
use OneBot\V12\Object\ActionResponse;
use ZM\Annotation\AnnotationHandler;
use ZM\Annotation\OneBot\BotAction;
use ZM\Exception\OneBot12Exception;
use ZM\Plugin\OneBot\BotMap;
trait GoActionTrait
{
/**
* 发送机器人动作
*
* @throws \Throwable
*/
public function sendAction(string $action, array $params = [], ?array $self = null): bool|ActionResponse
{
if ($self === null && $this->self !== null) {
$self = $this->self;
}
// 声明 Action 对象
$a = new Action($action, $params, ob_uuidgen(), $self);
// 调用事件在回复之前的回调
$handler = new AnnotationHandler(BotAction::class);
container()->set(Action::class, $a);
$handler->setRuleCallback(fn (BotAction $act) => ($act->action === '' || $act->action === $action) && !$act->need_response);
$handler->handleAll();
// 被阻断时候,就不发送了
if ($handler->getStatus() === AnnotationHandler::STATUS_INTERRUPTED) {
return false;
}
// 从这里开始gocq 需要做一个 12 -> 11 的转换
$action_array = GocqActionConverter::getInstance()->convertAction12To11($a);
// 将这个 action 提取出来需要记忆的 echo
GocqAdapter::$action_hold_list[$a->echo] = $action_array;
// 获取机器人的 BotMap 对应连接(前提是当前上下文有 self
if ($self !== null) {
$fd_map = BotMap::getBotFd($self['user_id'], $self['platform']);
if ($fd_map === null) {
logger()->error("机器人 [{$self['platform']}:{$self['user_id']}] 没有连接或未就绪,无法发送数据");
return false;
}
$result = ws_socket($fd_map[0])->send(json_encode($action_array), $fd_map[1]);
} elseif ($this instanceof GoBotConnectContext) {
// self 为空,说明可能是发送的元动作,需要通过 fd 来查找对应的 connect 连接
$flag = $this->getFlag();
$fd = $this->getFd();
$result = ws_socket($flag)->send(json_encode($action_array), $fd);
} elseif (method_exists($this, 'emitSendAction')) {
$result = $this->emitSendAction($a);
} else {
logger()->error('未匹配到任何机器人连接');
return false;
}
// 如果开启了协程,并且成功发送,那就进入协程等待,挂起等待结果返回一个 ActionResponse 对象
if (($result ?? false) === true && ($co = Adaptive::getCoroutine()) !== null) {
BotMap::$bot_coroutines[$a->echo] = $co->getCid();
$response = $co->suspend();
if ($response instanceof ActionResponse) {
$handler = new AnnotationHandler(BotAction::class);
$handler->setRuleCallback(fn(BotAction $act) => ($act->action === '' || $act->action === $action) && $act->need_response);
container()->set(ActionResponse::class, $response);
$handler->handleAll();
return $response;
}
return false;
}
if (isset($result)) {
return $result;
}
// 到这里表明你调用时候不在 WS 或 HTTP 上下文
throw new OneBot12Exception('No bot connection found.');
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace GocqAdapter;
use ZM\Context\BotConnectContext;
class GoBotConnectContext extends BotConnectContext
{
use GoActionTrait;
}

View File

@ -8,51 +8,12 @@ use OneBot\V12\Object\Action;
use OneBot\V12\Object\ActionResponse; use OneBot\V12\Object\ActionResponse;
use ZM\Annotation\AnnotationHandler; use ZM\Annotation\AnnotationHandler;
use ZM\Annotation\OneBot\BotAction; use ZM\Annotation\OneBot\BotAction;
use ZM\Context\BotConnectContext;
use ZM\Context\BotContext; use ZM\Context\BotContext;
use ZM\Exception\OneBot12Exception; use ZM\Exception\OneBot12Exception;
use ZM\Plugin\OneBot\BotMap;
class GoBotContext extends BotContext class GoBotContext extends BotContext
{ {
public function sendAction(string $action, array $params = [], ?array $self = null): bool|ActionResponse use GoActionTrait;
{
// 前面这里和 OneBot 12 的 sendAction 完全一样
// 声明 Action 对象
$a = new Action($action, $params, ob_uuidgen(), $self);
// 调用事件在回复之前的回调
$handler = new AnnotationHandler(BotAction::class);
container()->set(Action::class, $a);
$handler->setRuleCallback(fn (BotAction $act) => $act->action === '' || $act->action === $action && !$act->need_response);
$handler->handleAll($a);
// 被阻断时候,就不发送了
if ($handler->getStatus() === AnnotationHandler::STATUS_INTERRUPTED) {
return false;
}
// 从这里开始gocq 需要做一个 12 -> 11 的转换
$action_array = GocqActionConverter::getInstance()->convertAction12To11($a);
// 将这个 action 提取出来需要记忆的 echo
GocqAdapter::$action_hold_list[$a->echo] = $action_array;
// 调用机器人连接发送 Action
if ($this->base_event instanceof WebSocketMessageEvent) {
$result = $this->base_event->send(json_encode($action_array));
}
if (!isset($result) && container()->has('ws.message.event')) {
$result = container()->get('ws.message.event')->send(json_encode($action_array));
}
// 如果开启了协程,并且成功发送,那就进入协程等待,挂起等待结果返回一个 ActionResponse 对象
if (($result ?? false) === true && ($co = Adaptive::getCoroutine()) !== null) {
static::$coroutine_list[$a->echo] = $co->getCid();
$response = $co->suspend();
if ($response instanceof ActionResponse) {
return $response;
}
return false;
}
if (isset($result)) {
return $result;
}
// 到这里表明你调用时候不在 WS 或 HTTP 上下文
throw new OneBot12Exception('No bot connection found.');
}
} }

View File

@ -82,13 +82,17 @@ class GocqActionConverter
'thread_count' => $action->params['thread_count'] ?? 1, 'thread_count' => $action->params['thread_count'] ?? 1,
]; ];
break; break;
case 'get_version':
$act = 'get_version_info';
$params = $action->params;
break;
default: default:
// qq. 开头的动作,一律当作 gocq 的其他事件,这时候 params 原封不动发出 // qq. 开头的动作,一律当作 gocq 的其他事件,这时候 params 原封不动发出
if (str_starts_with($action->action, 'qq.')) { if (str_starts_with($action->action, 'qq.')) {
$act = substr($action->action, 3); $act = substr($action->action, 3);
$params = $action->params; $params = $action->params;
} else { } else {
throw new OneBot12Exception('Current action cannot send to gocq'); throw new OneBot12Exception('Current action cannot send to gocq: ' . $action->action);
} }
break; break;
} }
@ -178,6 +182,13 @@ class GocqActionConverter
'file_id' => $response['data']['file'], 'file_id' => $response['data']['file'],
]; ];
break; break;
case 'get_version_info':
$response_obj->data = [
'impl' => $response['data']['app_name'] . ' (go-cqhttp-adapter converted)',
'version' => $response['data']['app_version'],
'onebot_version' => '12',
];
break;
case 'set_group_name': case 'set_group_name':
case 'set_group_leave': case 'set_group_leave':
default: default:
@ -195,6 +206,9 @@ class GocqActionConverter
{ {
$msgs = []; $msgs = [];
foreach ($message as $v) { foreach ($message as $v) {
if (is_array($v)) {
$v = segment($v['type'], $v['data'] ?? []);
}
$msgs[] = GocqSegmentConverter::getInstance()->parseSegment12To11($v); $msgs[] = GocqSegmentConverter::getInstance()->parseSegment12To11($v);
} }
return $msgs; return $msgs;

View File

@ -8,17 +8,22 @@ use OneBot\Driver\Event\WebSocket\WebSocketCloseEvent;
use OneBot\Driver\Event\WebSocket\WebSocketMessageEvent; use OneBot\Driver\Event\WebSocket\WebSocketMessageEvent;
use OneBot\Driver\Event\WebSocket\WebSocketOpenEvent; use OneBot\Driver\Event\WebSocket\WebSocketOpenEvent;
use OneBot\V12\Exception\OneBotException; use OneBot\V12\Exception\OneBotException;
use OneBot\V12\Object\ActionResponse;
use OneBot\V12\Object\MessageSegment; use OneBot\V12\Object\MessageSegment;
use OneBot\V12\Object\OneBotEvent; use OneBot\V12\Object\OneBotEvent;
use ZM\Annotation\AnnotationHandler; use ZM\Annotation\AnnotationHandler;
use ZM\Annotation\Framework\BindEvent; use ZM\Annotation\Framework\BindEvent;
use ZM\Annotation\Framework\Init; use ZM\Annotation\Framework\Init;
use ZM\Annotation\Middleware\Middleware;
use ZM\Annotation\OneBot\BotActionResponse; use ZM\Annotation\OneBot\BotActionResponse;
use ZM\Annotation\OneBot\BotEvent; use ZM\Annotation\OneBot\BotEvent;
use ZM\Annotation\OneBot\CommandArgument; use ZM\Annotation\OneBot\CommandArgument;
use ZM\Container\ContainerRegistrant; use ZM\Container\ContainerRegistrant;
use ZM\Context\BotContext; use ZM\Context\BotContext;
use ZM\Exception\OneBot12Exception;
use ZM\Exception\WaitTimeoutException; use ZM\Exception\WaitTimeoutException;
use ZM\Middleware\WebSocketFilter;
use ZM\Plugin\OneBot\BotMap;
use ZM\Utils\ConnectionUtil; use ZM\Utils\ConnectionUtil;
class GocqAdapter class GocqAdapter
@ -47,29 +52,26 @@ class GocqAdapter
{ {
logger()->info('连接到 ob11'); logger()->info('连接到 ob11');
$request = $event->getRequest(); $request = $event->getRequest();
ob_dump($request);
// 判断是不是 Gocq 或 OneBot 11 标准的连接。OB11 标准必须带有 X-Client-Role 和 X-Self-ID 两个头。 // 判断是不是 Gocq 或 OneBot 11 标准的连接。OB11 标准必须带有 X-Client-Role 和 X-Self-ID 两个头。
if ($request->getHeaderLine('X-Client-Role') === 'Universal' && $request->getHeaderLine('X-Self-ID') !== '') { if ($request->getHeaderLine('X-Client-Role') === 'Universal' && $request->getHeaderLine('X-Self-ID') !== '') {
logger()->info('检测到 OneBot 11 反向 WS 连接 ' . $request->getHeaderLine('User-Agent')); logger()->info('检测到 OneBot 11 反向 WS 连接 ' . $request->getHeaderLine('User-Agent'));
$info = ['gocq_impl' => 'go-cqhttp', 'self_id' => $request->getHeaderLine('X-Self-ID')]; $info = ['gocq_impl' => 'go-cqhttp', 'self_id' => $request->getHeaderLine('X-Self-ID'), 'onebot-version' => 11];
// TODO: 验证 Token // TODO: 验证 Token
ConnectionUtil::setConnection($event->getFd(), $info); ConnectionUtil::setConnection($event->getFd(), $info);
logger()->info('已接入 go-cqhttp 的反向 WS 连接,连接 ID 为 ' . $event->getFd()); logger()->info('已接入 go-cqhttp 的反向 WS 连接,连接 ID 为 ' . $event->getFd());
BotMap::setCustomConnectContext($event->getSocketFlag(), $event->getFd(), new GoBotConnectContext($event->getSocketFlag(), $event->getFd()));
} }
} }
/** /**
* @throws OneBotException * @param WebSocketMessageEvent $event
* @throws \Throwable
* @throws OneBot12Exception
*/ */
#[BindEvent(WebSocketMessageEvent::class)] #[BindEvent(WebSocketMessageEvent::class)]
#[Middleware(WebSocketFilter::class, ['gocq_impl' => 'go-cqhttp'])]
public function handleWSReverseMessage(WebSocketMessageEvent $event): void public function handleWSReverseMessage(WebSocketMessageEvent $event): void
{ {
// 忽略非 gocq 的消息
$impl = ConnectionUtil::getConnection($event->getFd())['gocq_impl'] ?? null;
if ($impl === null) {
return;
}
// 解析 Frame 到 UTF-8 JSON // 解析 Frame 到 UTF-8 JSON
$body = $event->getFrame()->getData(); $body = $event->getFrame()->getData();
$body = json_decode($body, true); $body = json_decode($body, true);
@ -79,6 +81,7 @@ class GocqAdapter
} }
if (isset($body['post_type'], $body['self_id'])) { if (isset($body['post_type'], $body['self_id'])) {
// 获取转换后的对象
$ob12 = self::getConverter($event->getFd(), strval($body['self_id']))->convertEvent($body); $ob12 = self::getConverter($event->getFd(), strval($body['self_id']))->convertEvent($body);
if ($ob12 === null) { if ($ob12 === null) {
logger()->debug('收到了不支持的 Event丢弃此事件'); logger()->debug('收到了不支持的 Event丢弃此事件');
@ -94,8 +97,10 @@ class GocqAdapter
} }
// 绑定容器 // 绑定容器
ContainerRegistrant::registerOBEventServices($obj, GoBotContext::class); ContainerRegistrant::registerOBEventServices($obj);
BotMap::registerBotWithFd($obj->self['user_id'], $obj->self['platform'], true, $event->getFd(), $event->getSocketFlag());
BotMap::setCustomContext($obj->self['user_id'], $obj->self['platform'], GoBotContext::class);
container()->set(BotContext::class, bot());
// 调用 BotEvent 事件 // 调用 BotEvent 事件
$handler = new AnnotationHandler(BotEvent::class); $handler = new AnnotationHandler(BotEvent::class);
$handler->setRuleCallback(function (BotEvent $event) use ($obj) { $handler->setRuleCallback(function (BotEvent $event) use ($obj) {
@ -104,7 +109,7 @@ class GocqAdapter
&& ($event->detail_type === null || $event->detail_type === $obj->detail_type); && ($event->detail_type === null || $event->detail_type === $obj->detail_type);
}); });
try { try {
$handler->handleAll($obj); $handler->handleAll();
} catch (WaitTimeoutException $e) { } catch (WaitTimeoutException $e) {
// 这里是处理 prompt() 下超时的情况的 // 这里是处理 prompt() 下超时的情况的
if ($e->getTimeoutPrompt() === null) { if ($e->getTimeoutPrompt() === null) {
@ -125,14 +130,17 @@ class GocqAdapter
$origin_action = self::$action_hold_list[$body['echo']]; $origin_action = self::$action_hold_list[$body['echo']];
unset(self::$action_hold_list[$body['echo']]); unset(self::$action_hold_list[$body['echo']]);
$resp = GocqActionConverter::getInstance()->convertActionResponse11To12($body, $origin_action); $resp = GocqActionConverter::getInstance()->convertActionResponse11To12($body, $origin_action);
ContainerRegistrant::registerOBActionResponseServices($resp); ContainerRegistrant::registerOBActionResponseServices($resp);
// 调用 BotActionResponse 事件 // 调用 BotActionResponse 事件
$handler = new AnnotationHandler(BotActionResponse::class); $handler = new AnnotationHandler(BotActionResponse::class);
$handler->setRuleCallback(function (BotActionResponse $event) use ($resp) { $handler->setRuleCallback(function (BotActionResponse $event) use ($resp) {
return $event->retcode === null || $event->retcode === $resp->retcode; return ($event->retcode === null || $event->retcode === $resp->retcode)
&& ($event->status === null || $event->status === $resp->status);
}); });
$handler->handleAll($resp); container()->set(ActionResponse::class, $resp);
$handler->handleAll();
// 如果有协程,并且该 echo 记录在案的话,就恢复协程 // 如果有协程,并且该 echo 记录在案的话,就恢复协程
BotContext::tryResume($resp); BotContext::tryResume($resp);

View File

@ -103,7 +103,7 @@ class GocqEventConverter
$message[$k] = ['type' => $type, 'data' => $data]; $message[$k] = ['type' => $type, 'data' => $data];
} }
} }
return $message; return json_decode(json_encode($message), true);
} }
/** /**