-
Notifications
You must be signed in to change notification settings - Fork 12
Implementation Specification
此文档是为准备编写 JKook 的实现的开发者准备的。
开发一个 JKook 的实现,需要您对以下内容有深入了解:
- ClassLoader 的使用、基本原理
- Java 网络编程 (侧重于 WebSocket 与 Webhook 的实现)
见下图:
graph TD;
start(JVM 启动)-->parse(解析参数);
parse-->arg(获得插件文件夹路径和 Bot Token);
fail(报错)-->stopService(停止服务);
stopService-->vmStop(JVM 结束);
arg-->startService(启动服务, 如 Scheduler);
startService-->loadPlugins(加载插件文件夹下所有插件);
loadPlugins-->getGateway(获取 Gateway);
getGateway-->invalidToken(无效 Token) --> fail;
getGateway-->startWs(尝试连接 Kook Websocket 服务器);
startWs-->invalidToken;
startWs-->successWs(连接成功);
successWs -- 启动 --> wsThreadLoop;
startWs-->failWs(连接出错);
failWs--> tooManyTimes{超过 2 次?};
tooManyTimes -- 否 --> startWs;
tooManyTimes -- 是 --> getGateway;
wsThreadLoop-->waitHello(等待 HELLO 包);
waitHello-->parseHello(解析 HELLO 包);
parseHello-->istokenerror{是 TOKEN 错误?};
istokenerror -- 是 --> startWs;
istokenerror -- 否 --> receiveEvent;
successWs-->tickLoop(REPL 循环);
wsThreadLoop -- 定时任务 --> ping(发出 PING 请求);
ping-->pong(收到 PONG);
pong-->istimeout{是否在超时状态?}
istimeout -- 是 --> timeoutfalse(取消超时状态);
ping-->nopong(未收到 PONG);
nopong-->timeout(进入超时状态);
timeout-- 尝试 2 次 -->ping;
timeout -- 2 次均失败 --> startWs;
tickLoop-->crticial(严重错误);
crticial-->stopService;
receiveEvent-->connectionLost(发现 RECONNECT 包)-->cleanup(清除已有数据)-->startWs;
tickLoop-->commandRequest(收到命令);
commandRequest-->parseCommand(解析命令);
parseCommand-->executeCommand(执行命令);
executeCommand-->ex;
tickLoop-->userRequestStop(用户请求关闭);
userRequestStop-->stopService;
wsThreadLoop(WebSocket 线程);
receiveEvent(循环接收消息);
receiveEvent-->parseEvent(解析消息);
parseEvent-->commandFound(发现为命令);
commandFound-->commandRequest;
parseEvent-->unknownEvent(未知类型);
unknownEvent -- 抛弃事件 -->warn(警告);
parseEvent-->callEvent(调用相关监听器);
callEvent-->ex(遇到 Exception);
ex-->warn;
看看 snw.jkook.plugin
包。
有两个类很重要,BasePlugin
和 PluginClassLoader
。
这两个类,前者表示一个 Plugin ,后者用于加载一个 Plugin (是 snw.jkook.plugin.PluginLoader
的标准实现) 。
后者使用了 SnakeYAML 解析目标 JAR 内的 plugin.yml
。符合 JKook 插件格式规范 。
- 若想定义全新的 JKook Plugin 格式,请自行实现
Plugin
和PluginLoader
类。但我们不推荐这样做!
很容易看出 PluginClassLoader
是一个 abstract
类。
有一个方法是 abstract
的,且等待你实现的,它的签名如下:
protected abstract <T extends Plugin> T construct(
Class<T> cls,
BotDescription description
) throws Exception;
然后看 BasePlugin
的一个重要方法 ---- init
,其签名如下:
public void init(
File configFile,
File dataFolder,
PluginDescription description,
File file,
Logger logger
)
这个方法负责真正让这个插件实例拥有其数据。
为了让开发者在开发过程中不需要额外覆写一个构造方法 (之前是这样的) ,我们把原本构造器的参数移到了 init 方法。
但是请不要把 Plugin 直接转为 BasePlugin 然后调用。请使用类似下面的语句调用此方法:
cls.getMethod("init", File.class, File.class, PluginDescription.class, File.class, Logger.class)
.invoke(plugin, configFile, dataFolder, description, file, logger);
为了兼容性考虑。(在一些特殊需求下,开发者可能会需要自行实现 Plugin 接口)
实现了 PluginClassLoader
,你的 JKook 实现也就完成了第一步。
请着重看 snw.jkook.entity 包,这个包下面有大量的描述 Kook 内事物的元素。
实现它们吧。除了 snw.jkook.entity.abilities 包。
但是有一些方法不是纯提供数据的方法,而且做出操作的方法。
对于这些,如果你还没开始写 Http 处理部分,就先写 TODO 注释吧。
尽可能减少 Http 请求,能缓存的就缓存。
另外,尽量别给调用方返回 null
,如果请求出错,就抛出异常吧。
一个好的 Http 基础模块是整个实现的灵魂,因此请认真实现!
这部分并没有什么特别的内容,你只是需要在 TextChannel#sendComponent()
方法做好特判,然后序列化,最后发送。