2023-01-13 15:55:52 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
|
|
namespace GocqAdapter;
|
|
|
|
|
|
|
|
|
|
|
|
use OneBot\Driver\Event\WebSocket\WebSocketCloseEvent;
|
|
|
|
|
|
use OneBot\Driver\Event\WebSocket\WebSocketMessageEvent;
|
|
|
|
|
|
use OneBot\Driver\Event\WebSocket\WebSocketOpenEvent;
|
|
|
|
|
|
use OneBot\V12\Exception\OneBotException;
|
2023-03-11 19:37:56 +08:00
|
|
|
|
use OneBot\V12\Object\ActionResponse;
|
2023-01-13 15:55:52 +08:00
|
|
|
|
use OneBot\V12\Object\MessageSegment;
|
|
|
|
|
|
use OneBot\V12\Object\OneBotEvent;
|
|
|
|
|
|
use ZM\Annotation\AnnotationHandler;
|
|
|
|
|
|
use ZM\Annotation\Framework\BindEvent;
|
|
|
|
|
|
use ZM\Annotation\Framework\Init;
|
2023-03-11 19:37:56 +08:00
|
|
|
|
use ZM\Annotation\Middleware\Middleware;
|
2023-01-13 15:55:52 +08:00
|
|
|
|
use ZM\Annotation\OneBot\BotActionResponse;
|
|
|
|
|
|
use ZM\Annotation\OneBot\BotEvent;
|
|
|
|
|
|
use ZM\Annotation\OneBot\CommandArgument;
|
|
|
|
|
|
use ZM\Container\ContainerRegistrant;
|
|
|
|
|
|
use ZM\Context\BotContext;
|
2023-03-11 19:37:56 +08:00
|
|
|
|
use ZM\Exception\OneBot12Exception;
|
2023-01-13 15:55:52 +08:00
|
|
|
|
use ZM\Exception\WaitTimeoutException;
|
2023-03-11 19:37:56 +08:00
|
|
|
|
use ZM\Middleware\WebSocketFilter;
|
|
|
|
|
|
use ZM\Plugin\OneBot\BotMap;
|
2023-01-13 15:55:52 +08:00
|
|
|
|
use ZM\Utils\ConnectionUtil;
|
|
|
|
|
|
|
|
|
|
|
|
class GocqAdapter
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @var array<string, array>
|
|
|
|
|
|
* @internal
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static array $action_hold_list = [];
|
|
|
|
|
|
|
|
|
|
|
|
/** @var GocqEventConverter[] */
|
|
|
|
|
|
private static array $converters = [];
|
|
|
|
|
|
|
|
|
|
|
|
#[Init]
|
|
|
|
|
|
public function init(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
logger()->info('go-cqhttp 转换器已加载!');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* [CALLBACK] 接入和认证 go-cqhttp 的反向 WebSocket 连接
|
|
|
|
|
|
* @throws \JsonException
|
|
|
|
|
|
*/
|
|
|
|
|
|
#[BindEvent(WebSocketOpenEvent::class)]
|
|
|
|
|
|
public function handleWSReverseOpen(WebSocketOpenEvent $event): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$request = $event->getRequest();
|
|
|
|
|
|
// 判断是不是 Gocq 或 OneBot 11 标准的连接。OB11 标准必须带有 X-Client-Role 和 X-Self-ID 两个头。
|
|
|
|
|
|
if ($request->getHeaderLine('X-Client-Role') === 'Universal' && $request->getHeaderLine('X-Self-ID') !== '') {
|
|
|
|
|
|
logger()->info('检测到 OneBot 11 反向 WS 连接 ' . $request->getHeaderLine('User-Agent'));
|
2023-03-11 19:37:56 +08:00
|
|
|
|
$info = ['gocq_impl' => 'go-cqhttp', 'self_id' => $request->getHeaderLine('X-Self-ID'), 'onebot-version' => 11];
|
2023-01-13 15:55:52 +08:00
|
|
|
|
// TODO: 验证 Token
|
|
|
|
|
|
ConnectionUtil::setConnection($event->getFd(), $info);
|
|
|
|
|
|
logger()->info('已接入 go-cqhttp 的反向 WS 连接,连接 ID 为 ' . $event->getFd());
|
2023-03-11 19:37:56 +08:00
|
|
|
|
BotMap::setCustomConnectContext($event->getSocketFlag(), $event->getFd(), new GoBotConnectContext($event->getSocketFlag(), $event->getFd()));
|
2023-01-13 15:55:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2023-03-11 19:37:56 +08:00
|
|
|
|
* @param WebSocketMessageEvent $event
|
|
|
|
|
|
* @throws \Throwable
|
|
|
|
|
|
* @throws OneBot12Exception
|
2023-01-13 15:55:52 +08:00
|
|
|
|
*/
|
|
|
|
|
|
#[BindEvent(WebSocketMessageEvent::class)]
|
2023-03-11 19:37:56 +08:00
|
|
|
|
#[Middleware(WebSocketFilter::class, ['gocq_impl' => 'go-cqhttp'])]
|
2023-01-13 15:55:52 +08:00
|
|
|
|
public function handleWSReverseMessage(WebSocketMessageEvent $event): void
|
|
|
|
|
|
{
|
|
|
|
|
|
// 解析 Frame 到 UTF-8 JSON
|
|
|
|
|
|
$body = $event->getFrame()->getData();
|
|
|
|
|
|
$body = json_decode($body, true);
|
|
|
|
|
|
if ($body === null) {
|
|
|
|
|
|
logger()->warning('收到非 JSON 格式的消息,已忽略');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isset($body['post_type'], $body['self_id'])) {
|
2023-03-11 19:37:56 +08:00
|
|
|
|
// 获取转换后的对象
|
2023-01-13 15:55:52 +08:00
|
|
|
|
$ob12 = self::getConverter($event->getFd(), strval($body['self_id']))->convertEvent($body);
|
|
|
|
|
|
if ($ob12 === null) {
|
|
|
|
|
|
logger()->debug('收到了不支持的 Event,丢弃此事件');
|
|
|
|
|
|
logger()->debug('事件详情对象:' . json_encode($ob12, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
$obj = new OneBotEvent($ob12);
|
|
|
|
|
|
} catch (OneBotException $e) {
|
|
|
|
|
|
logger()->debug('收到非 OneBot 12(由11转换而来)标准的消息,已忽略');
|
|
|
|
|
|
logger()->debug($e->getMessage());
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定容器
|
2023-03-11 19:37:56 +08:00
|
|
|
|
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());
|
2023-01-13 15:55:52 +08:00
|
|
|
|
// 调用 BotEvent 事件
|
|
|
|
|
|
$handler = new AnnotationHandler(BotEvent::class);
|
|
|
|
|
|
$handler->setRuleCallback(function (BotEvent $event) use ($obj) {
|
|
|
|
|
|
return ($event->type === null || $event->type === $obj->type)
|
|
|
|
|
|
&& ($event->sub_type === null || $event->sub_type === $obj->sub_type)
|
|
|
|
|
|
&& ($event->detail_type === null || $event->detail_type === $obj->detail_type);
|
|
|
|
|
|
});
|
|
|
|
|
|
try {
|
2023-03-11 19:37:56 +08:00
|
|
|
|
$handler->handleAll();
|
2023-01-13 15:55:52 +08:00
|
|
|
|
} catch (WaitTimeoutException $e) {
|
|
|
|
|
|
// 这里是处理 prompt() 下超时的情况的
|
|
|
|
|
|
if ($e->getTimeoutPrompt() === null) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (($e->getPromptOption() & ZM_PROMPT_TIMEOUT_MENTION_USER) === ZM_PROMPT_TIMEOUT_MENTION_USER && ($ev = $e->getUserEvent()) !== null) {
|
|
|
|
|
|
$prompt = [MessageSegment::mention($ev->getUserId()), ...$e->getTimeoutPrompt()];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (($e->getPromptOption() & ZM_PROMPT_TIMEOUT_QUOTE_SELF) === ZM_PROMPT_TIMEOUT_QUOTE_SELF && ($rsp = $e->getPromptResponse()) !== null && ($ev = $e->getUserEvent()) !== null) {
|
|
|
|
|
|
$prompt = [MessageSegment::reply($rsp->data['message_id'], $ev->self['user_id']), ...$e->getTimeoutPrompt()];
|
|
|
|
|
|
} elseif (($e->getPromptOption() & ZM_PROMPT_TIMEOUT_QUOTE_USER) === ZM_PROMPT_TIMEOUT_QUOTE_USER && ($ev = $e->getUserEvent()) !== null) {
|
|
|
|
|
|
$prompt = [MessageSegment::reply($ev->getMessageId(), $ev->getUserId()), ...$e->getTimeoutPrompt()];
|
|
|
|
|
|
}
|
|
|
|
|
|
bot()->reply($prompt ?? $e->getTimeoutPrompt());
|
|
|
|
|
|
}
|
|
|
|
|
|
} elseif (isset($body['status'], $body['retcode'], $body['echo'])) {
|
|
|
|
|
|
if (isset(self::$action_hold_list[$body['echo']])) {
|
|
|
|
|
|
$origin_action = self::$action_hold_list[$body['echo']];
|
|
|
|
|
|
unset(self::$action_hold_list[$body['echo']]);
|
|
|
|
|
|
$resp = GocqActionConverter::getInstance()->convertActionResponse11To12($body, $origin_action);
|
2023-03-11 19:37:56 +08:00
|
|
|
|
|
2023-01-13 15:55:52 +08:00
|
|
|
|
ContainerRegistrant::registerOBActionResponseServices($resp);
|
|
|
|
|
|
|
|
|
|
|
|
// 调用 BotActionResponse 事件
|
|
|
|
|
|
$handler = new AnnotationHandler(BotActionResponse::class);
|
|
|
|
|
|
$handler->setRuleCallback(function (BotActionResponse $event) use ($resp) {
|
2023-03-11 19:37:56 +08:00
|
|
|
|
return ($event->retcode === null || $event->retcode === $resp->retcode)
|
|
|
|
|
|
&& ($event->status === null || $event->status === $resp->status);
|
2023-01-13 15:55:52 +08:00
|
|
|
|
});
|
2023-03-11 19:37:56 +08:00
|
|
|
|
container()->set(ActionResponse::class, $resp);
|
|
|
|
|
|
$handler->handleAll();
|
2023-01-13 15:55:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果有协程,并且该 echo 记录在案的话,就恢复协程
|
|
|
|
|
|
BotContext::tryResume($resp);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[BindEvent(WebSocketCloseEvent::class)]
|
|
|
|
|
|
public function handleWSReverseClose(WebSocketCloseEvent $event): void
|
|
|
|
|
|
{
|
|
|
|
|
|
unset(self::$converters[$event->getFd()]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static function getConverter(int $fd, ?string $self_id = null): GocqEventConverter
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isset(self::$converters[$fd])) {
|
|
|
|
|
|
self::$converters[$fd] = new GocqEventConverter($self_id, 'unknown');
|
|
|
|
|
|
}
|
|
|
|
|
|
return self::$converters[$fd];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|