Simple Robot | QQ频道 v4.0.0-beta5 Help

QGBot

公告

站点迁移啦~!

为什么迁移?

作为由我们官方维护的组件库,分散在各自的文档站点中的确有好处:它们可以各自维护自己所需的东西、互不干扰。

但是缺点也很明显: 太过分散。

组件库与核心库之间的关系是比较紧密的, 我们希望你能够在一个站点内就可以查阅或搜索到所有你想要得知的信息。

      作为一个QQ频道的 Bot 库,有一个用于描述机器人的 Bot 想必肯定是很正常的。

      API?

      在 API 模块 (simbot-component-qq-guild-api) 中,你可能找不到太多有关 Bot 的身影。 毕竟 API 模块仅是针对QQ频道中的 API 的封装与实现,本身是不包括对 Bot 的描述的。

      标准库 Bot

      在标准库模块 (simbot-component-qq-guild-stdlib) 中, 你可以发现一个类型 love.forte.simbot.qguild.Bot ,它便是对一个QQ频道机器人的描述, 可以用来订阅并处理事件等。

      创建 Bot

      使用工厂类 BotFactory.create(..) 可创建一个尚未启动的 Bot 实例。

      使用 Ticket

      // 准备 bot 的必要信息 val botId = "xxxx" val botSecret = "" // secret 如果用不到可使用空字符串 val botToken = "xxxx" // 用于注册 bot 的 “票据” 信息。 val ticket = Bot.Ticket(botId, botSecret, botToken) // 构建一个 Bot,并可选地进行一些配置。 val bot = BotFactory.create(ticket) { // 各种配置... // 比如切换服务地址为沙箱频道的服务地址 useSandboxServerUrl() // 指定需要订阅的事件的 intents,默认会订阅: // - 频道相关事件 // - 频道成员相关事件 // - 公域消息相关事件 intents // = xxx // 自定义一个 shard,默认是 Shard.FULL shard = Shard.FULL // 其他各种配置... }

      直接作为参数

      也可以直接将这三个必要信息作为参数使用。

      // 准备 bot 的必要信息 val botId = "xxxx" val botSecret = "" // secret 如果用不到可使用空字符串 val botToken = "xxxx" // 构建一个 Bot,并可选地进行一些配置。 val bot = BotFactory.create(botId, botSecret, botToken) { ... }

      直接提供配置类

      作为 DSL 的配置类也可以独立构建后直接提供。

      // 准备 bot 的必要信息 val botId = "xxxx" val botSecret = "" // secret 如果用不到可使用空字符串 val botToken = "xxxx" // 构建配置类 val configuration = ConfigurableBotConfiguration() // config... // 构建一个 Bot,并提供配置。 val bot = BotFactory.create(botId, botSecret, botToken, configuration)
      // 准备 bot 的必要信息 var botId = "xxxx"; var botSecret = ""; // secret 如果用不到可使用空字符串 var botToken = "xxxx"; // 用于注册 bot 的 “票据” 信息。 var ticket = new Bot.Ticket(botId, botSecret, botToken); // 构建一个 Bot,并可选地进行一些配置。 var bot = BotFactory.create(ticket, config -> { // 各种配置... // 比如切换服务地址为沙箱频道的服务地址 config.useSandboxServerUrl(); // 指定需要订阅的事件的 intents,默认会订阅: // - 频道相关事件 // - 频道成员相关事件 // - 公域消息相关事件 // config.setIntentsValue(...); // 自定义一个 shard,默认是 Shard.FULL config.setShard(Shard.FULL); // 其他各种配置... });

      直接作为参数

      也可以直接将这三个必要信息作为参数使用。

      // 准备 bot 的必要信息 var botId = "xxxx"; var botSecret = ""; // secret 如果用不到可使用空字符串 var botToken = "xxxx"; // 构建一个 Bot,并可选地进行一些配置。 var bot = BotFactory.create(botId, botSecret, botToken, config -> { ... });

      直接提供配置类

      作为 DSL 的配置类也可以独立构建后直接提供。

      // 准备 bot 的必要信息 var botId = "xxxx"; var botSecret = ""; // secret 如果用不到可使用空字符串 var botToken = "xxxx"; var configuration = new ConfigurableBotConfiguration(); // 构建一个 Bot,并可选地进行一些配置。 var bot = BotFactory.create(botId, botSecret, botToken, configuration);

      订阅事件

      你可以通过 subscribe 来订阅全部或指定类型的事件。

      订阅全部事件

      使用 subscribe 注册一个普通的事件处理器,此处理器会接收并处理所有类型的事件。

      bot.subscribe { raw -> // raw 代表事件的原始JSON字符串 // this: Signal.Dispatch, 也就是解析出来的事件结构体 println("event: $this") println("event.data: $data") println("raw: $raw") }

      订阅指定类型的事件

      使用扩展函数 subscribe 注册一个针对具体 Signal.Dispatch 事件类型的事件处理器, 它只有在接收到的 Signal.Dispatch 与目标类型一致时才会处理。

      此示例展示处理 AtMessageCreate 也就公域是消息事件, 并在对方发送了包含 "stop" 的文本时终止 bot。

      // 注册一个普通的事件处理器,此处理器会接收并处理所有类型的事件 bot.subscribe<AtMessageCreate> { if ("stop" in data.content) { // 终止 bot bot.cancel() } }

      订阅全部事件

      使用 subscribe 注册一个普通的事件处理器,此处理器会接收并处理所有类型的事件。

      bot.subscribe(EventProcessors.async((event, raw) -> { // raw 代表事件的原始JSON字符串 // event: Signal.Dispatch, 也就是解析出来的事件结构体 System.out.println("event: " + event); System.out.println("event.data: " + event.getData()); System.out.println("raw: " + raw); // 异步处理器必须返回 CompletableFuture return CompletableFuture.completedFuture(null); }));
      bot.subscribe(EventProcessors.block((event, raw) -> { // raw 代表事件的原始JSON字符串 // event: Signal.Dispatch, 也就是解析出来的事件结构体 System.out.println("event: " + event); System.out.println("event.data: " + event.getData()); System.out.println("raw: " + raw); }));

      订阅指定类型的事件

      使用 subscribe 注册一个指定了具体类型的事件处理器, 它只有在接收到的 Signal.Dispatch 与目标类型一致时才会处理。

      此示例展示处理 AtMessageCreate 也就公域是消息事件, 并在对方发送了包含 "stop" 的文本时终止 bot。

      bot.subscribe(EventProcessors.async(AtMessageCreate.class, (event, raw) -> { if (event.getData().getContent().contains("stop")) { // 终止 bot bot.cancel(); } return CompletableFuture.completedFuture(null); }));
      bot.subscribe(EventProcessors.block(AtMessageCreate.class, (event, raw) -> { if (event.getData().getContent().contains("stop")) { // 终止 bot bot.cancel(); } }));

      启动 Bot

      Bot 被启动之前,它不会与服务器建立连接,也不会收到并处理事件。

      bot.start() // 启动bot bot.join() // 挂起并直到bot被关闭
      var future = bot.startAsync() // // 启动bot .thenCompose(r -> bot.asFuture()); // 转为直到bot被终止后结束的 future // 阻塞线程或者怎么样都行 future.join();
      // 启动bot bot.startBlocking(); // 阻塞当前线程,直到bot被终止。 bot.joinBlocking();

      关闭 Bot

      使用 cancel 即可关闭一个 Bot。被关闭的 Bot 不能再次启动。

      bot.cancel() bot.cancel(reason) // 或可以提供一个 reason: Throwable
      bot.cancel(); bot.cancel(reason); // 或可以提供一个 Throwable reason

      组件库 QGBot

      当你在配合 simbot 使用组件库(simbot-component-qq-guild-core )的时候, 你可能会更需要了解 love.forte.simbot.component.qguild.bot.QGBot。 它是作为一个 simbot 组件库的 Bot 的实现 (simbot标准API中的 Bot 接口,不是上面提到的 stdlib bot)。

      源 stdlib Bot

      组件库的 QGBot 是在上文提到过的 标准库 Bot 的基础上进行构建与扩展的。

      你可以在 QGBot 中通过属性 source 获取到其对应的源 Bot

      val sourceBot = bot.source
      var sourceBot = bot.getSource();

      构建 QGBot

      Application 中安装 QQGuildBotManager 后即可使用其注册、启动一个 QGBot 了。

      安装 QQGuildBotManager

      val app = launchSimpleApplication { install(QQGuildComponent) // 别忘了安装 Component 标识 install(QQGuildBotManager) { // 可选地。进行一些配置,比如一个所有 Bot 共享的父级 CoroutineContext } }

      Kotlin 中可以选择使用扩展函数 useQQGuild 来简化代码:

      val app = launchSimpleApplication { // 同时安装组件标识和BotManager useQQGuild() // 或.. // 可选地进行一些配置 useQQGuild { // 可选地对 BotManager 进行一些配置 botManager { // ... } } }
      var appAsync = Applications.launchApplicationAsync(Simple.INSTANCE, builder -> { builder.install(QQGuildComponent.Factory); builder.install(QQGuildBotManager.Factory); }); // 可以转化为 CompletableFuture 然后进行一些操作 var appFuture = appAsync.asFuture();
      var app = Applications.launchApplicationBlocking(Simple.INSTANCE, builder -> { builder.install(QQGuildComponent.Factory); builder.install(QQGuildBotManager.Factory); });

      你可以可选地进行一些配置。

      var appAsync = Applications.launchApplicationAsync(Simple.INSTANCE, builder -> { builder.install(QQGuildComponent.Factory); builder.install(QQGuildBotManager.Factory, config -> { // 比如让所有的 Bot 都默认的使用一个 4 守护线程的线程池 // 这里仅供示例,真实配置请按照项目实际情况来。 config.setCoroutineContext( ExecutorsKt.from(Executors.newFixedThreadPool(4, r -> { final var t = new Thread(r); t.setDaemon(true); return t; })) ); }); }); // 可以转化为 CompletableFuture 然后进行一些操作 var appFuture = appAsync.asFuture();
      var app = Applications.launchApplicationBlocking(Simple.INSTANCE, builder -> { builder.install(QQGuildComponent.Factory); builder.install(QQGuildBotManager.Factory, config -> { // 比如让所有的 Bot 都默认使用虚拟线程线程池, // 这样就可以在事件处理器中相对更安全的使用 **阻塞API** 了。 // 这里仅供示例,真实配置请按照项目实际情况来。 config.setCoroutineContext( ExecutorsKt.from(Executors.newVirtualThreadPerTaskExecutor()) ); }); });

      构建完 Application 后,我们寻找 QQGuildBotManager ,并注册、启动一个 bot。

      val app = launchSimpleApplication { useQQGuild() } val bot = app.botManagers.get<QQGuildBotManager>() .register("APP ID", "SECRET", "TOKEN") { // 可选地进行一些配置 } // 启动它 bot.start() // 挂起它 bot.join() // 或者直接挂起 app app.join()

      Kotlin 中也可以使用扩展函数 qgGuildBots 来简化操作。

      val app = launchSimpleApplication { useQQGuild() } app.qgGuildBots { val bot = register("APP ID", "SECRET", "TOKEN") { // 可选地进行一些配置 } // 启动 bot bot.start() } // 挂起 app app.join()
      var appAsync = Applications.launchApplicationAsync(Simple.INSTANCE, builder -> { builder.install(QQGuildComponent.Factory); builder.install(QQGuildBotManager.Factory); }); var future = appAsync.asFuture() .thenCompose(app -> { // 找到 QQGuildBotManager var botManager = app.getBotManagers().stream().filter(it -> it instanceof QQGuildBotManager) .map(it -> (QQGuildBotManager) it) .findFirst() .orElseThrow(); // 注册 bot var bot = botManager.register("APP ID", "SECRET", "TOKEN", config -> { // 可选地进行一些配置 }); // 启动 bot, // 启动完 bot 后,返回 app 作为 future,并在外部直接 join app. return bot.startAsync() .thenCompose(($) -> app.asFuture()); }); future.join();
      var app = Applications.launchApplicationBlocking(Simple.INSTANCE, builder -> { builder.install(QQGuildComponent.Factory); builder.install(QQGuildBotManager.Factory); }); // 找到 QQGuildBotManager var botManager = app.getBotManagers().stream().filter(it -> it instanceof QQGuildBotManager) .map(it -> (QQGuildBotManager) it) .findFirst() .orElseThrow(); // 注册 bot var bot = botManager.register("APP ID", "SECRET", "TOKEN", config -> { // 可选地进行一些配置 }); // 启动 bot bot.startBlocking(); // 阻塞bot bot.joinBlocking(); // 或直接阻塞 app app.joinBlocking();

      监听事件

      实际上,在组件库中监听事件与某个具体的 Bot 无关。 你可以前往参考 Simple Robot 应用手册: 事件监听与处理

      你可以在 组件模块的标准 Event 实现 或API文档中找到所有可用于 simbot 中的事件类型。它们大多与 API 模块中定义的事件类型有一些对应规则。

      此处使用 公域消息事件 QGAtMessageCreateEvent 作为例子:

      val app = launchSimpleApplication { useQQGuild() } // 注册一个事件处理器,处理 QGAtMessageCreateEvent 类型的事件 app.eventDispatcher.listen<QGAtMessageCreateEvent> { atMessageEvent -> // QGAtMessageCreateEvent // this: EventListenerContext println("Event: $atMessageEvent") println("Context: $this") // result. EventResult.empty() } // 注册 bot... app.qgGuildBots { ... } app.join()
      var appAsync = Applications.launchApplicationAsync(Simple.INSTANCE, builder -> { builder.install(QQGuildComponent.Factory); builder.install(QQGuildBotManager.Factory); }); var future = appAsync.asFuture() .thenCompose(app -> { app.getEventDispatcher().register( EventListeners.async( QGAtMessageCreateEvent.class, (context, event) -> { System.out.println("Event: " + event); System.out.println("Context: " + context); // 返回异步结果 return CompletableFuture.completedFuture(EventResult.empty()); } ) ); // 注册 bot var bot = ... // 启动 bot, 或者之类的各种操作 return ... }); future.join();
      var app = Applications.launchApplicationBlocking(Simple.INSTANCE, builder -> { builder.install(QQGuildComponent.Factory); builder.install(QQGuildBotManager.Factory, config -> { // 如果使用完全的阻塞API,建议配置调度器为虚拟线程调度器,来更好的避免线程匮乏的问题。 // 这里仅供示例,真实配置请按照项目实际情况来。 config.setCoroutineContext( ExecutorsKt.from(Executors.newVirtualThreadPerTaskExecutor()) ); }); }); // 注册一个事件处理器,处理 QGAtMessageCreateEvent 类型的事件 app.getEventDispatcher().register(EventListeners.block( QGAtMessageCreateEvent.class, (context, event) -> { System.out.println("Event: " + event); System.out.println("Context: " + context); return EventResult.empty(); } )); // 注册bot... app.getBotManagers()... var bot = ... bot.startBlocking(); // 阻塞 app app.joinBlocking();

      频道操作

      对频道 (QGGuild) 以及之下的子频道(QGChannel )、频道成员(QGMember) 的操作, 都是在 QGBotguildRelation 中开始的。

      val bot: QGBot = ... val guildRelation = bot.guildRelation
      QGBot bot = ... var guildRelation = bot.getGuildRelation()

      通过获取到的 QGGuildRelation ,可以用来获取 QGGuildQGChannel 等信息。

      val guildRelation = bot.guildRelation // 寻找指定ID的频道服务器 val guild = guildRelation.guild(1234L.ID) // 获取全部 QGGuild 的流 guildRelation.guilds .asFlow() // 可以转成 Flow .collect { ... } // 额外提供可以指定批次数量和 lastId 的API guildRelation.guilds(lastId = null, batch = 100) .asFlow() // 可以转成 Flow .collect { ... } // 额外提供了一些直接查询子频道的API // 寻找指定ID的子频道 val channel = guildRelation.channel(1234L.ID) // 寻找指定ID的聊天子频道,可用来发消息的那种 val chatChannel = guildRelation.chatChannel(1234L.ID) // 寻找指定ID的帖子子频道 val forumChannel = guildRelation.forumChannel(1234L.ID)
      final var guildRelation = bot.getGuildRelation(); // 寻找指定ID的频道服务器 guildRelation.getGuildAsync(Identifies.of(1234L)) .thenAccept(guild -> { ... }); // 获取全部 QGGuild 的流 guildRelation.getGuilds() // 可以转化成 Flux .transform(SuspendReserves.flux()) .subscribe(guild -> { ... }); // 额外提供可以指定批次数量和 lastId 的API guildRelation.guilds(null, 100) // 可以转化成 Flux .transform(SuspendReserves.flux()) .subscribe(guild -> { ... }); // 额外提供了一些直接查询子频道的API // 寻找指定ID的子频道 guildRelation.getChannelAsync(Identifies.of(1234L)) .thenAccept(channel -> { ... }); // 寻找指定ID的聊天子频道,可用来发消息的那种 guildRelation.getChatChannelAsync(Identifies.of(1234L)) .thenAccept(chatChannel -> { ... }); // 寻找指定ID的帖子子频道 guildRelation.getForumChannelAsync(Identifies.of(1234L)) .thenAccept(forumChannel -> { ... });
      var guildRelation = bot.getGuildRelation(); // 寻找指定ID的频道服务器 var guildValue = guildRelation.getGuild(Identifies.of(1234L)); // 获取全部 QGGuild 的流 var guilds = guildRelation.getGuilds(); // 可以转成 stream 或者 list Collectables.asStream(guilds) .forEach(guild -> { ... }); // 额外提供可以指定批次数量和 lastId 的API var guildList = guildRelation.guilds(null, 100) // 可以转化成 list .transform(SuspendReserves.list()); // 额外提供了一些直接查询子频道的API // 寻找指定ID的子频道 var channel = guildRelation.getChannel(Identifies.of(1234L)) // 寻找指定ID的聊天子频道,可用来发消息的那种 var chatChannel = guildRelation.getChatChannel(Identifies.of(1234L)) // 寻找指定ID的帖子子频道 var forumChannel = guildRelation.getForumChannel(Identifies.of(1234L))

      消息发送

      除了使用频道中获取到的子频道对象 QGChannelsend 直接发送、 使用消息事件中的 reply 发送消息等方式以外, QGBot 本身提供了一些可以跳过获取频道这一环节、直接根据 ID 发送消息的 API QGBot.sendTo

      val bot: QGBot = ... bot.sendTo("channel id".ID, "消息内容") bot.sendTo("channel id".ID, "消息内容".toText() + At("user id".ID))
      QGBot bot = ... var sendTask1 = bot.sendToAsync(Identifies.of("channel id"), "消息内容"); var sendTask2 = bot.sendToAsync(Identifies.of("channel id"), Messages.of( Text.of("文本消息"), At.of(Identifies.of("user id")) )); // 如果有需要,记得异常处理 sendTask1.exceptionally(err -> ...); sendTask2.exceptionally(err -> ...);

      或者有顺序地执行这两个任务:

      QGBot bot = ... var sendTask = bot.sendToAsync(Identifies.of("channel id"), "消息内容") .thenCompose(($) -> bot.sendToAsync(Identifies.of("channel id"), Messages.of( Text.of("文本消息"), At.of(Identifies.of("user id")) )));

      如果希望在事件处理器中,处理器需要等待所有异步任务完成后再进行下一个处理器,则返回这个 future, 否则会不会等待。

      QGBot bot = ... var sendTask1 = bot.sendToAsync(...); var sendTask2 = bot.sendToAsync(...); return CompletableFuture.allOf(sendTask1, sendTask2) .thenApply($ -> EventResult.empty()); // 任务全部完成后,返回事件结果
      QGBot bot = ... bot.sendToBlocking(Identifies.of("channel id"), "消息内容"); bot.sendToBlocking(Identifies.of("channel id"), Messages.of( Text.of("文本消息"), At.of(Identifies.of("user id")) ));

      Spring Boot

      如果你使用 simbot 配合 Spring Boot 来使用QQ频道组件, 那么你可以前往:

      Last modified: 15 July 2024