-
Notifications
You must be signed in to change notification settings - Fork 0
API 文档
windy664 edited this page Jul 20, 2026
·
2 revisions
XingtuBot 对外暴露 XingtuBotService 接口,供第三方 Velocity/Bukkit 插件扩展功能。
Velocity:
XingtuBotService service = proxy.getPluginManager()
.getPlugin("xingtubotvelocity")
.flatMap(PluginContainer::getInstance)
.map(p -> ((XingtuBotVelocity) p).getService())
.orElse(null);Bukkit:
XingtuBotHost host = getServer().getServicesManager()
.load(XingtuBotHost.class);
// host 提供 registry / registerService / getService / permission 等能力继承自 CommandRegistrar、MessageSender、CommandHookBus、BotRuntimeInfo。
public interface XingtuBotService
extends CommandRegistrar, MessageSender, CommandHookBus, BotRuntimeInfo {
int API_VERSION = 4;
// API 版本
int apiVersion();
// 注册消息处理器
void registerMessageHandler(BotMessageHandler handler);
// 注册命令
void registerCommand(BotCommand command);
// 发送 Markdown 消息到群(可附带键盘模板)
void sendToGroupMarkdown(String groupOpenId, String markdownContent,
String keyboardTemplateId);
// 命令执行前钩子(返回 false 可拦截)
void beforeCommand(BiPredicate<String, ? super BotMessageEvent> hook);
// 命令执行后钩子
void afterCommand(BiConsumer<String, ? super BotMessageEvent> hook);
// 获取机器人 AppID
String getBotAppId();
}通过 BotMessageContext.reply() 系列方法回复当前消息:
event.reply("纯文本回复");
event.replyImage("https://...", "图片描述");
event.replyMarkdown("**加粗**内容", "keyboard_template_id");
event.replyKeyboard("Markdown 内容", keyboardJson);
event.replyVoice("https://...");
event.replyArk(arkJson);
event.replyEmbed(embedJson);通过 ProactiveSender 服务主动向群发消息:
ProactiveSender sender = ctx.getService(ProactiveSender.class);
if (sender != null && sender.isReady()) {
sender.sendGroupMessage(groupOpenId, "主动消息");
sender.sendGroupMarkdown(groupOpenId, "**Markdown** 主动消息");
sender.sendGroupMarkdownKeyboard(groupOpenId, "内容", keyboardJson);
}未就绪或失败时应回退到 PendingMessageQueue:
PendingMessageQueue.getInstance().offer(groupOpenId, message);使用 Md 工具类构建 Markdown 卡片:
String md = Md.card("📦", "标题")
.subtitle("**副标题**")
.quote("引用内容")
.field("🔑", "字段名", "字段值")
.link("点击查看", "https://...")
.build();| 方法 | 说明 |
|---|---|
getConversationId() |
会话 ID(群 openid 或用户 openid) |
getSenderId() |
发送者 openid |
getUsername() |
发送者 QQ 昵称 |
getMessage() |
消息文本 |
getImageUrls() |
附带图片 URL 列表 |
getEventType() |
原始 QQ 事件类型 |
getMessageType() |
标准化消息类型枚举 |
isGroupMessage() |
是否群消息 |
isGroupAtMessage() |
是否 @ 机器人的消息 |
| 值 | 说明 |
|---|---|
GROUP_AT |
群 @ 消息 |
GROUP |
群消息(非 @) |
DIRECT |
私聊消息 |
UNKNOWN |
未知类型 |
// 方式一:通过 XingtuBotService
service.registerCommand(new MyCommand());
// 方式二:通过 ModuleContext(扩展插件推荐)
ctx.registry().register(new MyCommand());public interface BotCommand {
boolean matches(String message);
void handle(String message, BotMessageContext event);
String name();
boolean adminOnly(); // 默认 false
boolean adminFor(String msg); // 默认同 adminOnly()
List<String> triggers(); // 触发词
String usage(); // 出现在 /help
String description(); // 出现在 /help
String category(); // 菜单分组
}public interface BotMessageHandler {
boolean matches(String message, BotMessageContext event);
void handle(String message, BotMessageContext event);
String name();
int priority(); // 默认 100,越小越先
boolean adminOnly(); // 默认 false
boolean acceptsWithoutMention(); // 默认 false
List<String> triggers();
List<MenuEntry> menuEntries();
String usage();
String description();
String category();
void init(HandlerContext ctx);
void shutdown();
}- 按
priority()升序排列 - 依次调用
matches(),第一个命中的处理消息 -
listen-mode=mention时,非 @ 消息只触发acceptsWithoutMention()=true的 handler -
adminFor(msg)=true时,非管理员被拦截
// 命令执行前拦截
service.beforeCommand((cmdName, event) -> {
// 返回 false 拦截命令
return true;
});
// 命令执行后回调
service.afterCommand((cmdName, event) -> {
// 记录日志、统计等
});PermissionChecker permission = ctx.permission();
if (permission != null && permission.isAdmin(senderOpenid)) {
// 是管理员
}PermissionChecker 由 xt-auth 模块提供,未安装 xt-auth 时返回 null。
// 获取机器人 AppID
String appId = service.getBotAppId();
// 机器人昵称
String name = BotRuntimeState.getBotName();
// 是否大脑(主控端)
boolean isBrain = host.isBrain();核心与扩展、扩展与扩展之间通过服务总线共享能力:
// 注册
ctx.registerService(MyService.class, new MyServiceImpl());
// 也可以用字符串 key(跨 classloader)
ctx.registerService("my.service", new MyServiceImpl());
// 消费
MyService svc = ctx.getService(MyService.class);
Object svc = ctx.getServiceObject("my.service");
// 跨 classloader 反射获取
Class<?> cls = Class.forName("com.example.MyService");
Object svc = ctx.getServiceObject(cls);| 服务类型 | 提供方 | 获取方式 |
|---|---|---|
ProactiveSender |
核心 | ctx.getService(ProactiveSender.class) |
PermissionChecker |
xt-auth | ctx.permission() |
Translator |
xt-modquery | ctx.getService(Translator.class) |
AiService |
xt-ai | ctx.getService(AiService.class) |
GroupChatLink |
xt-chatlink | ctx.getServiceObject(Class.forName(...)) |
当主动推送失败时,回退到被动队列(下次有人 @ 机器人时一并发出):
PendingMessageQueue.getInstance().offer(groupOpenId, markdownContent);SensitiveFilter filter = SensitiveFilter.fromConfig(config, logger);
String filtered = filter.filter(rawText);xt-myext/
├── build.gradle
└── src/main/
├── java/org/windy/xingtubot/
│ ├── ext/xtmyext/
│ │ ├── MyExtBukkitPlugin.java # Bukkit 入口
│ │ ├── MyExtBungeeCordPlugin.java # BungeeCord 入口
│ │ └── MyExtVelocityPlugin.java # Velocity 入口
│ └── module/
│ ├── MyExtModule.java # BotModule 实现
│ └── myext/
│ ├── MyCommand.java # 命令
│ └── MyService.java # 业务逻辑
└── resources/
├── plugin.yml # Bukkit
├── bungee.yml # BungeeCord
└── config.yml # 扩展配置
每个入口类只需十几行样板代码(定位宿主 → 调 ExtensionBootstrap.enable),业务逻辑全在 BotModule 实现中。