Appearance
事件系统概述
插件通过 @SubscribeEvent 注解即可监听机器人的各类事件。
事件按触发时机分为以下几类:
- 接收事件(
event/receive/)— 网关 DISPATCH 推送(群消息、私聊、互动、成员变动、入群申请等)与两个生命周期就绪事件(ReadyEvent仅 WS 模式、WebhookReadyEvent仅 Webhook 模式),共 18 种 - 发送事件(
event/send/)— 机器人主动操作(发消息、上传、撤回、禁言、踢人、审批、菜单、面板、素材等)前触发,共 28 种,全部可取消 - 后置事件(
event/send/,XxxNotifyEvent)— 上述操作成功后触发,共 28 种,不可取消,与发送事件一一配对 - 框架事件(
event/)—HeartbeatEvent(连接心跳)、PluginEnableEvent/PluginDisableEvent(插件启用 / 禁用)
「机器人上线」的推荐做法
插件做上线初始化(如启动定时任务)时,推荐实现 Plugin.onActive() 而非监听 ReadyEvent:onActive() 在两种接入模式统一触发一次(WS 全部分片就绪 / Webhook 回调服务就绪),无需判断接入模式与分片;ReadyEvent 是 WebSocket 网关专属事件(每分片各触发一次),Webhook 模式不会触发,对应的是 WebhookReadyEvent。
@SubscribeEvent
java
import cn.org.bukkit.bngeuit.Bngeuit;
import cn.org.bukkit.bngeuit.dto.message.User;
import cn.org.bukkit.bngeuit.dto.message.interaction.InteractionResponseCode;
import cn.org.bukkit.bngeuit.event.SubscribeEvent;
import cn.org.bukkit.bngeuit.event.receive.GroupAtMessageEvent;
import cn.org.bukkit.bngeuit.event.receive.C2CMessageEvent;
import cn.org.bukkit.bngeuit.event.receive.ReadyEvent;
import cn.org.bukkit.bngeuit.event.receive.InteractionEvent;
public class MyListener {
@SubscribeEvent
public void onGroupAt(GroupAtMessageEvent e) {
Bngeuit.replyGroupText(
e.getGroupOpenid(), e.getId(), 1,
"收到: " + e.getContent()
);
}
@SubscribeEvent
public void onC2CMessage(C2CMessageEvent e) {
User author = e.getAuthor();
if (author != null && author.userOpenid() != null) {
Bngeuit.replyC2CText(
author.userOpenid(), e.getId(), 1,
"收到私聊"
);
}
}
@SubscribeEvent
public void onReady(ReadyEvent e) {
// 机器人启动完成(仅 WebSocket 模式;多分片时每片触发一次,可用 e.getShardId() 区分)
if (e.getShardId() == 0) {
// 只对 shard 0 做一次全局初始化
}
}
@SubscribeEvent
public void onInteraction(InteractionEvent e) {
// 必须响应,否则客户端一直 loading
Bngeuit.respondInteraction(e.getId(),
InteractionResponseCode.SUCCESS);
}
}包位置
SubscribeEvent、Event、Cancellable、EventPriority 在 cn.org.bukkit.bngeuit.event; 具体事件类分属 event.receive 与 event.send 两个子包。
规则
- 标注在 public 方法上
- 有且仅有 一个参数,类型继承自
Event - 方法名、参数名无限制
- 插件 JAR 内的类会被自动扫描注册
发送事件与取消
发送事件(event/send/)在机器人调用 HTTP API 之前触发,实现 Cancellable 接口。取消后对应操作被跳过,API 返回 null:
java
import cn.org.bukkit.bngeuit.event.SubscribeEvent;
import cn.org.bukkit.bngeuit.event.EventPriority;
import cn.org.bukkit.bngeuit.event.send.GroupMessageSendEvent;
@SubscribeEvent(priority = EventPriority.P1) // 最先执行
public void onGroupSend(GroupMessageSendEvent e) {
if (e.getContent() != null && e.getContent().contains("敏感词")) {
e.setCancelled(true); // 阻止这条群消息发送
}
}后置事件(NotifyEvent)
每个发送事件都有一个配对的后置事件,命名规则固定为前置事件名 + Notify:
GroupMessageSendEvent → GroupMessageSendNotifyEvent
MenuUpdateEvent → MenuUpdateNotifyEvent
MediaImagePrepareEvent → MediaImagePrepareNotifyEvent两者语义严格区分:
前置(SendEvent) | 后置(NotifyEvent) | |
|---|---|---|
| 触发时机 | HTTP 调用前 | HTTP 调用成功后 |
| 可否取消 | 可取消(Cancellable) | 不可取消,只读通知 |
| 失败时 | — | 前置被取消或 HTTP 报错时不触发 |
| 携带内容 | 本次操作的入参 | getOriginal()(前置事件)+ getResponse()(响应对象) |
后置事件的典型用途是拿到只存在于响应里的数据:机器人消息的消息 ID(后续撤回要用)与自引用索引 refIdx(后续引用自己的消息要用)。
java
import cn.org.bukkit.bngeuit.event.send.GroupMessageSendNotifyEvent;
import cn.org.bukkit.bngeuit.dto.message.MessageResponse;
@SubscribeEvent
public void onSent(GroupMessageSendNotifyEvent e) {
MessageResponse resp = e.getResponse();
if (resp != null) {
// 记录 bot 自己发出的消息 ID,供后续撤回/引用
saveSentMessageId(e.getOriginal().getGroupOpenid(), resp.id());
}
}每个后置事件是独立类型,需要哪种就写哪个监听方法即可。
响应可能为 null
getResponse() 在 HTTP 成功但响应体为空时返回 null;返回 {} 的端点(撤回、禁言、删除等)对应的后置事件已在构造中归一为 EmptyResponse 实例,不会是 null。判断成功与否请看「前置有没有触发后置」,不要用 getResponse() == null 反推失败。
框架事件
HeartbeatEvent
WebSocket 模式发出心跳包(op=1)或收到心跳确认(op=11)时触发。无载荷、不可取消,每个心跳周期触发两次;Webhook 模式永不触发。
适合做连接活跃度指示、看门狗喂狗这类轻量信号:
java
@SubscribeEvent
public void onHeartbeat(HeartbeatEvent e) {
lastActiveAt = System.currentTimeMillis();
}不属于收发事件体系
HeartbeatEvent 不继承 ReceiveEvent / SendEvent / NotifyEvent,按它自己的类型监听即可。
PluginEnableEvent / PluginDisableEvent
插件启用完成(onEnable 执行完并登记到 PluginManager)后、插件禁用完成(onDisable 与资源清理执行完)后触发,覆盖启动期批量加载、plugin disable/reload、停服 disableAll 等路径。不可取消,仅作通知;可通过 getPlugin() 反查插件实例。
事件优先级
@SubscribeEvent(priority = EventPriority.XXX),数字越小越先执行:
| 优先级 | 说明 |
|---|---|
P1 | 最高,最先执行(适合过滤、权限检查) |
P2 | 较高 |
P3 | 普通(默认) |
P4 | 较低 |
P5 | 最低 |
P6 | 监看,无论事件是否取消都会执行 |
@SubscribeEvent(ignoreCancelled = true) 可在事件已被其他处理器取消时仍执行本方法。
触发自定义事件
Bngeuit.callEvent(event) 可同步触发任意 Event 子类:
java
public class MyEvent extends Event { ... }
@SubscribeEvent
public void onMyEvent(MyEvent e) { ... }
// 触发(按优先级分发;若事件实现 Cancellable 且被取消,返回 false)
boolean ok = Bngeuit.callEvent(new MyEvent());在 JavaPlugin 中使用
java
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
getLogger().info("插件已启用");
}
@SubscribeEvent
public void onGroupAt(GroupAtMessageEvent e) {
Bngeuit.replyGroupText(
e.getGroupOpenid(), e.getId(), 1,
"你好," + e.getAuthor().username()
);
}
}自动扫描
插件 JAR 内的所有 @SubscribeEvent 方法会被自动注册,无需手动操作。
事件 Metadata 系统
所有事件都支持 Metadata 系统,允许在不同优先级的处理器之间传递数据。
基本用法
java
import cn.org.bukkit.bngeuit.event.SubscribeEvent;
import cn.org.bukkit.bngeuit.event.EventPriority;
import cn.org.bukkit.bngeuit.metadata.MetadataValue;
import cn.org.bukkit.bngeuit.event.receive.GroupAtMessageEvent;
public class MetadataExample {
// P1 处理器:设置标记
@SubscribeEvent(priority = EventPriority.P1)
public void onCheck(GroupAtMessageEvent e) {
if (isSpam(e.getContent())) {
e.setMetadata("spam", new MetadataValue(true, this));
}
}
// P3 处理器:读取标记
@SubscribeEvent(priority = EventPriority.P3)
public void onHandle(GroupAtMessageEvent e) {
MetadataValue spam = e.getMetadata("spam");
if (spam != null && Boolean.TRUE.equals(spam.value())) {
// 是垃圾消息,跳过处理
return;
}
// 正常处理...
}
}MetadataValue
| 方法 | 返回类型 | 说明 |
|---|---|---|
value() | Object | 原始值(可为 null) |
source() | Object | 来源对象(通常是设置 metadata 的插件实例) |
as(type) | T | 类型转换(类型不匹配抛 ClassCastException) |
asSafe(type) | T | 安全类型转换(类型不匹配返回 null) |
Event Metadata 方法
| 方法 | 说明 |
|---|---|
setMetadata(key, value) | 设置 metadata |
getMetadata(key) | 获取 metadata(不存在返回 null) |
hasMetadata(key) | 是否存在指定 key |
removeMetadata(key) | 移除指定 key |
clearMetadata() | 移除所有 metadata |
生命周期
Metadata 生命周期与事件对象绑定,事件分发完成后自动回收。
