Appearance
服务器扩展开发教程
通过子服 API,扩展插件可以调用机器人的消息发送、名单管理、变量解析等能力。单服和代理集群使用同一套接口,无需自行建立 WebSocket 连接或处理代理转发。
接入流程
1. 区分三个 JAR
| 文件 | 用途 | 放置位置 |
|---|---|---|
StellarAnchorBot.jar | 子服主插件,提供 API 类、实现和机器人连接 | Bukkit / Paper / Folia 子服的 plugins 目录 |
StellarAnchorBot-API.jar | 开发时提供接口类型与编译检查 | 扩展项目的 libs 目录,仅作编译依赖 |
| 你编写的扩展插件 JAR | 调用 API、处理事件与自己的业务 | 实际运行业务的子服 plugins 目录 |
运行时 API 由主插件提供,扩展不再复制一份。API JAR 不放进服务器 plugins,其中的 API 类不打包进扩展,也不要重定位 cn.stellaranchor.bot.api 包名。
集群的 BungeeCord / Velocity 主插件负责中转,第三方 Bukkit 扩展仍安装在子服,不安装到代理端。
2. 添加编译依赖并声明加载顺序
将匹配版本的 API JAR 放入项目 libs,在现有 Gradle 项目的 dependencies 中添加:
kotlin
// 仅参与编译,不把 API 类复制进扩展 JAR。
compileOnly(files("libs/StellarAnchorBot-API.jar"))在扩展的 plugin.yml 中声明硬依赖:
yaml
depend: [StellarAnchorBot]这样主插件会先于扩展加载。使用 Maven 时将 API 依赖设为 provided;打包时排除 API 类,不使用 Shade / Shadow 将它们合并进扩展。
3. 在启用时获取 API
在扩展主类的 onEnable() 中获取服务,检查成功后再让业务逻辑调用 API。下面是主类内的接入片段:
java
import cn.stellaranchor.bot.api.StellarAnchorApi;
import org.bukkit.Bukkit;
// 字段与方法放在你的 JavaPlugin 主类中。
private StellarAnchorApi api;
@Override
public void onEnable() {
api = Bukkit.getServicesManager().load(StellarAnchorApi.class);
if (api == null) {
getLogger().severe("无法取得 StellarAnchorBot API,请检查主插件版本及加载状态");
getServer().getPluginManager().disablePlugin(this);
return;
}
}以下调用片段放在已持有 api 的业务方法中,参数由自己的业务传入,不会自动注册游戏命令或群命令。接口对应参考文档中的 1.2.8 插件 API,机器人、子服和代理需使用相互匹配的版本。
4. 确认连接后再调用
API 服务获取成功只表示主插件提供了接口,不代表机器人已连接。先确认后台扩展核心已启用、子服到机器人或代理的连接正常;群相关操作还需要目标群已启用。
需要接收消息事件时,再开启「全量消息转发」并注册监听器。首次验证建议先查询名单,再发送一条测试消息,检查返回结果。
调用方式与返回结果
远程调用返回 CompletableFuture<ApiResult>,通过回调读取结果:
java
api.sendGroupMessage(groupOpenId, "服务器运行正常")
.whenComplete((result, throwable) -> {
if (throwable != null) {
getLogger().warning("请求异常:" + throwable.getMessage());
return;
}
if (result.isSuccess()) {
getLogger().info(result.getDataJson());
} else {
getLogger().warning(result.getErrorCode() + ": "
+ result.getErrorMessage());
}
});| 返回方法 | 含义 |
|---|---|
isSuccess() | 业务是否处理成功 |
getDataJson() | 业务结果 JSON,仅包含响应的 data |
getErrorCode() | 失败错误码 |
getErrorMessage() | 失败原因 |
不要在游戏主线程或 Folia 区域线程中使用 get()、join() 等待结果。回调中操作玩家、世界或实体时,应切换到对应调度器。参数错误可能直接抛出异常;超时后业务可能已执行,不要盲目重试修改、发送或处罚操作。
群消息发送
groupOpenId、userOpenId 是当前机器人的群和用户 OpenID,不是普通 QQ 群号或 QQ 号。目标群需在机器人后台启用。
| 方法 | 用途 |
|---|---|
sendGroupMessage(group, text) | 发送文本 |
sendGroupMessage(group, text, messageId) | 使用指定消息 ID 被动回复 |
sendGroupMarkdown(group, markdown, keyboard, messageId) | 发送原生 Markdown,可附带按钮 |
sendGroupImage(group, bytes, caption, messageId) | 上传并发送图片,可附说明文字 |
java
api.sendGroupMessage(groupOpenId, "服务器即将重启");
api.sendGroupMessage(groupOpenId, "收到", messageId);
// 无按钮传 null;空 messageId 使用消息池 / 主动发送策略
api.sendGroupMarkdown(groupOpenId, "# 状态\n运行正常", null, "");
// 文件读取应在异步任务中进行
byte[] image = java.nio.file.Files.readAllBytes(imagePath);
api.sendGroupImage(groupOpenId, image, "今日统计", messageId);传入 messageId 时直接进行被动回复,ID 必须来自目标群且未过期,失败不会自动切换为主动发送。不传或传空字符串时,机器人先按同群消息池策略尝试借用消息 ID,再按现有规则回退主动消息。
图片支持 PNG、JPEG、GIF、WebP,原始大小为 1–524288 字节。Markdown 与按钮需 QQ 平台开通对应能力,按钮使用原生 keyboard 对象。普通文本不解释本地图片标记,图片请使用图片接口发送。
HTML 渲染并发送
renderHtml(group, template, parameters, messageId) 将参数注入机器人端模板,渲染图片后发送:
java
java.util.Map<String, Object> values = new java.util.HashMap<>();
values.put("player", "Steve");
values.put("balance", 100);
api.renderHtml(groupOpenId, "player-info.html", values, messageId);模板放在机器人后台系统设置显示的 HTML 模板目录,名称填相对路径,渲染器需已配置并运行。模板可通过 window.__renderParams 或 window.__renderPage(params) 获取数据。自定义宽高等参数使用下方的通用调用接口。
黑白名单管理
这些方法操作机器人名单服务,不是 Minecraft 原生白名单。
| 方法 | 用途 |
|---|---|
addWhitelist(user, name, game) | 为用户添加游戏 ID |
queryWhitelist(game) | 查询游戏 ID 是否在白名单中 |
queryUserWhitelist(user) | 查询用户的全部白名单记录 |
removeWhitelist(game) | 移除游戏 ID 的白名单 |
addBlacklist(user) | 将用户加入黑名单 |
queryBlacklist(user) | 查询用户黑名单记录 |
removeBlacklist(user) | 移除用户黑名单 |
java
api.addWhitelist(userOpenId, "玩家昵称", "Steve");
api.queryWhitelist("Steve");
api.queryUserWhitelist(userOpenId);
api.removeWhitelist("Steve");
api.addBlacklist(userOpenId);
api.queryBlacklist(userOpenId);
api.removeBlacklist(userOpenId);查询结果同样通过 future 回调读取。添加白名单仍会检查 QQ 绑定、黑名单、游戏名及重复 ID 等规则。管理接口不会重复进行群命令操作者权限判断,扩展需自行控制调用入口;删除黑名单不保证恢复此前删除的白名单。
撤回与禁言
java
// 撤回目标群内指定消息
api.recallGroupMessage(groupOpenId, targetMessageId);
// 禁言指定用户 10 分钟
api.muteGroupMember(groupOpenId, userOpenId, 10);禁言时长为 1–43200 分钟,0 不表示解除禁言。实际执行受 QQ 官方能力与群权限限制。
变量组解析
resolveVariables(fileName, playerName, serverId) 读取子服变量组:
java
// 指定子服,变量组名称建议不带 .yml
api.resolveVariables("player-info", "Steve", "sub_server_01");
// 不指定子服,由代理请求全部已连接子服
api.resolveVariables("player-info", "Steve", "")
.thenAccept(result -> {
if (result.isSuccess()) {
getLogger().info(result.getDataJson());
}
});玩家名仅提供占位符解析上下文,不决定路由。一个子服成功返回时保留原 JSON;多个子服返回时以各自 server_id 为键包住完整结果。数据直接位于 getDataJson(),无需额外读取 results。
事件监听
监听类实现 org.bukkit.event.Listener,并在扩展启用时注册。若监听方法就在主类中,主类也需实现 Listener,在 onEnable() 中加入:
java
getServer().getPluginManager().registerEvents(this, this);监听器应快速返回,不要在其中等待远程请求或读取图片文件。全量消息事件不会自动把内容显示到游戏聊天栏。
群消息事件
监听 RobotMessageEvent,通过 getParamsJson() 获取消息参数:
java
@org.bukkit.event.EventHandler
public void onMessage(cn.stellaranchor.bot.api.event.RobotMessageEvent event) {
String paramsJson = event.getParamsJson();
// 解析参数后交给自己的业务处理。
}常用字段有 group_openid、sender_openid、sender_name、content、message_id、reference_id、attachments。从 getParamsJson() 中解析 group_openid 和 message_id 后,可用于同群被动回复:
java
// 以下两个变量来自消息参数解析,不是 QQ 群号或协议 request_id。
api.sendGroupMessage(groupOpenId, "收到", messageId);event.getMessageId() 是消息关联标识,不能一概视为 QQ 被动回复凭据;应优先使用参数中的 message_id,并确认来源群和有效性。reference_id 也不能代替它。
需在后台开启全量消息转发;开启「忽略已处理消息」后,机器人已处理的消息不会到达该事件。
名单与验证码事件
监听 RobotLifecycleEvent,用 getType() 区分类型,用 getDataJson() 读取内容:
java
@org.bukkit.event.EventHandler
public void onLifecycle(cn.stellaranchor.bot.api.event.RobotLifecycleEvent event) {
if ("whitelist.added".equals(event.getType())) {
String dataJson = event.getDataJson();
// 读取新增记录,处理自己的业务通知。
}
}| 事件类型 | 含义 |
|---|---|
whitelist.added | 白名单记录成功新增 |
whitelist.removed | 白名单记录成功移除 |
captcha.created | 本子服创建验证码任务 |
captcha.expired | 本子服验证码到期清理 |
captcha.verified | 本子服接受验证码成功确认 |
captcha.released | 本子服验证码提前释放或未通过确认 |
captcha.verified 是成功确认通知,不要在监听到它时再次提交白名单添加。事件不可取消,仅作为通知,不回放断线历史。需要可靠最终状态时调用查询接口。
通用调用接口
需要传入便捷方法以外的参数时,使用 api.call(operation, parameters)。参数使用标准 Java Map、List、字符串、数字或布尔值:
java
java.util.Map<String, Object> parameters = new java.util.HashMap<>();
parameters.put("group_openid", groupOpenId);
parameters.put("content", "你好");
parameters.put("message_id", "");
api.call("group.send", parameters).thenAccept(result -> {
if (!result.isSuccess()) {
getLogger().warning(result.getErrorMessage());
}
});| operation | 主要参数 |
|---|---|
group.send | group_openid;文本用 content,Markdown 用 markdown / buttons,图片用 image_base64;可选 message_id、reference_id |
html.send | group_openid、template;可选 parameters、message_id、reference_id、width、height、full_page |
whitelist.add | user_openid、game_id;可选 user_name |
whitelist.query | game_id 或 user_openid,同时提供时优先按游戏 ID 查询 |
whitelist.remove | game_id |
blacklist.add | user_openid;可选 user_name |
blacklist.query / blacklist.remove | user_openid |
group.recall | group_openid、message_id |
group.mute | group_openid、user_openid、minutes |
variables.resolve | file_name;可选 player_name、server_name(子服 server_id) |
group.send 的 Markdown 不能与文本或图片混用,图片可附带 content;image_base64 不带 data:image 前缀。html.send 的宽高为 0 时使用默认值,非零宽度范围为 320–4096,高度为 200–4096。
打包与验证
按自己的项目构建方式生成扩展 JAR,检查包内有扩展的类与 plugin.yml,没有 cn/stellaranchor/bot/api/ 下的 API 类。将扩展与主插件安装到测试子服后,启动并确认依赖加载、API 获取与机器人连接均正常;替换已加载的插件时先停止测试服,避免旧类残留。
| 现象 | 检查方向 |
|---|---|
| API 为 null | 主插件版本与启用状态、depend 声明、扩展是否误装到代理端 |
| 收不到消息事件 | 监听器注册、全量消息转发开关、消息路由到的子服,以及忽略已处理消息的设置 |
NOT_CONNECTED | 子服、代理和机器人之间的扩展核心连接 |
BUSINESS_ERROR | 读取 getErrorMessage(),检查目标群、名单规则或 QQ 官方权限 |
TIMEOUT | 先确认最终状态,不立即重复执行有副作用的操作 |