feat: adding event handler system
This commit is contained in:
parent
c68305aa63
commit
db13460ae4
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -10,6 +10,6 @@
|
||||
<module name="MyBot.main" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel target="17" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
2
.idea/fileTemplates/includes/Version.txt
generated
2
.idea/fileTemplates/includes/Version.txt
generated
@ -1 +1 @@
|
||||
0.0.3-dev
|
||||
0.0.4-dev
|
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@ -2,7 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/MyBot.iml" filepath="$PROJECT_DIR$/MyBot.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/MyBot.main.iml" filepath="$PROJECT_DIR$/.idea/modules/MyBot.main.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
|
@ -4,7 +4,7 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCach
|
||||
val projectName = rootProject.name
|
||||
val groupName by extra("cn.wzpmc")
|
||||
val projectArtifactId by extra("my-bot")
|
||||
val projectVersion by extra("0.0.3-dev")
|
||||
val projectVersion by extra("0.0.4-dev-SNAPSHOT")
|
||||
|
||||
plugins {
|
||||
id("java")
|
||||
|
@ -1,12 +1,16 @@
|
||||
package cn.wzpmc;
|
||||
|
||||
import cn.wzpmc.api.plugins.BasePlugin;
|
||||
import cn.wzpmc.commands.StopCommand;
|
||||
import cn.wzpmc.configuration.Configuration;
|
||||
import cn.wzpmc.console.MyBotConsole;
|
||||
import cn.wzpmc.entities.user.bot.MyBot;
|
||||
import cn.wzpmc.network.WebSocketConnectionHandler;
|
||||
import cn.wzpmc.plugins.CommandManager;
|
||||
import cn.wzpmc.plugins.PluginClassLoader;
|
||||
import cn.wzpmc.plugins.PluginManager;
|
||||
import cn.wzpmc.utils.JsonUtils;
|
||||
import cn.wzpmc.utils.ReflectionUtils;
|
||||
import cn.wzpmc.utils.TemplateFileUtils;
|
||||
import cn.wzpmc.utils.YamlUtils;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
@ -14,40 +18,102 @@ import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
|
||||
@Log4j2
|
||||
public class Main {
|
||||
private static final String DEFAULT_CONFIGURATION_FILE_PATH = "templates/config.yaml";
|
||||
@SneakyThrows
|
||||
public static void main(String[] args) {
|
||||
private static File pluginsDir;
|
||||
public static void initializeJVM(){
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
System.setProperty("terminal.jline", "true");
|
||||
}
|
||||
public static void initializeJsonUtils(){
|
||||
JsonUtils.initReader();
|
||||
JsonUtils.initWriter();
|
||||
log.info("启动MyBot...");
|
||||
}
|
||||
public static Configuration getConfiguration() {
|
||||
File configurationFile = new File("config.yaml");
|
||||
if (TemplateFileUtils.saveDefaultConfig(Main.class.getClassLoader(), DEFAULT_CONFIGURATION_FILE_PATH, configurationFile)) {
|
||||
log.debug("创建日志文件成功!");
|
||||
log.info("首次启动,默认配置文件已创建,请填写后再次启动MyBot!");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
pluginsDir = new File("plugins");
|
||||
if (TemplateFileUtils.createDefaultDirectory(pluginsDir)) {
|
||||
log.debug("plugin文件夹创建");
|
||||
}
|
||||
log.debug("读取配置文件 {}", configurationFile.getAbsolutePath());
|
||||
Configuration configuration = YamlUtils.readYamlFile(configurationFile, Configuration.class);
|
||||
return YamlUtils.readYamlFile(configurationFile, Configuration.class);
|
||||
}
|
||||
public static MyBot createBot(Configuration configuration){
|
||||
return new MyBot(configuration);
|
||||
}
|
||||
public static URI getUriFromConfiguration(Configuration configuration){
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(configuration.getWebsocket());
|
||||
} catch (URISyntaxException e) {
|
||||
log.error("无法解析websocket地址");
|
||||
return null;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
public static void loadPlugins(MyBot myBot) throws MalformedURLException {
|
||||
File[] files = pluginsDir.listFiles();
|
||||
if (files == null) {
|
||||
log.error("没有权限读取插件目录!");
|
||||
return;
|
||||
}
|
||||
WebSocketConnectionHandler webSocketConnectionHandler = new WebSocketConnectionHandler();
|
||||
ChannelFuture future = webSocketConnectionHandler.connect(uri);
|
||||
MyBot myBot = new MyBot(configuration);
|
||||
PluginManager pluginManager = myBot.getPluginManager();
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
String name = file.getName();
|
||||
if (!name.endsWith(".jar")) {
|
||||
continue;
|
||||
}
|
||||
URI fileURI = file.toURI();
|
||||
PluginClassLoader pluginClassLoader = new PluginClassLoader(new URL[]{fileURI.toURL()}, myBot);
|
||||
BasePlugin load = ReflectionUtils.load(pluginClassLoader, file, pluginManager);
|
||||
if (load == null){
|
||||
log.info("插件{}加载失败!", name);
|
||||
continue;
|
||||
}
|
||||
load.onLoad();
|
||||
}
|
||||
CommandManager commandManager = myBot.getCommandManager();
|
||||
commandManager.registerCommand(new StopCommand(myBot));
|
||||
}
|
||||
public static WebSocketConnectionHandler createConnection(MyBot myBot, URI uri){
|
||||
WebSocketConnectionHandler webSocketConnectionHandler = new WebSocketConnectionHandler(myBot);
|
||||
ChannelFuture future = webSocketConnectionHandler.connect(uri);
|
||||
return webSocketConnectionHandler;
|
||||
}
|
||||
public static void startConsole(MyBot myBot, WebSocketConnectionHandler webSocketConnectionHandler){
|
||||
MyBotConsole myBotConsole = new MyBotConsole(myBot, webSocketConnectionHandler);
|
||||
myBotConsole.start();
|
||||
}
|
||||
@SneakyThrows
|
||||
public static void main(String[] args) {
|
||||
initializeJVM();
|
||||
initializeJsonUtils();
|
||||
log.info("启动MyBot...");
|
||||
Configuration configuration = getConfiguration();
|
||||
if (configuration == null){
|
||||
return;
|
||||
}
|
||||
MyBot myBot = createBot(configuration);
|
||||
URI uri = getUriFromConfiguration(configuration);
|
||||
if (uri == null){
|
||||
log.error("无法解析websocket地址");
|
||||
return;
|
||||
}
|
||||
loadPlugins(myBot);
|
||||
WebSocketConnectionHandler webSocketConnectionHandler = createConnection(myBot, uri);
|
||||
startConsole(myBot, webSocketConnectionHandler);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package cn.wzpmc.api.plugins;
|
||||
|
||||
import cn.wzpmc.api.user.IBot;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* 插件基类
|
||||
@ -45,4 +46,8 @@ public interface BasePlugin {
|
||||
* @return 类加载器
|
||||
*/
|
||||
IPluginClassLoader getClassLoader();
|
||||
|
||||
void onLoad();
|
||||
void onUnload();
|
||||
Logger getLogger();
|
||||
}
|
||||
|
@ -32,4 +32,20 @@ public abstract class IPluginClassLoader extends URLClassLoader {
|
||||
* @return Bot对象
|
||||
*/
|
||||
abstract public IBot getBot();
|
||||
|
||||
/**
|
||||
* 获取插件名称
|
||||
* @author wzp
|
||||
* @since 2024/8/8 23:16 v0.0.4-dev
|
||||
* @return 插件名称
|
||||
*/
|
||||
abstract public String getName();
|
||||
|
||||
/**
|
||||
* 获取插件版本
|
||||
* @author wzp
|
||||
* @since 2024/8/8 23:16 v0.0.4-dev
|
||||
* @return 版本
|
||||
*/
|
||||
abstract public String getVersion();
|
||||
}
|
||||
|
25
src/main/java/cn/wzpmc/api/plugins/IPluginManager.java
Normal file
25
src/main/java/cn/wzpmc/api/plugins/IPluginManager.java
Normal file
@ -0,0 +1,25 @@
|
||||
package cn.wzpmc.api.plugins;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author wzp
|
||||
* @version 0.0.4-dev
|
||||
* @since 2024/8/6 下午3:19
|
||||
*/
|
||||
public interface IPluginManager {
|
||||
/**
|
||||
* 初始化插件主类
|
||||
*
|
||||
* @param <T> 插件主类类型
|
||||
* @param baseClass 插件主类
|
||||
* @param name 插件名称
|
||||
* @param version 插件版本
|
||||
* @return 这个插件的实例
|
||||
* @author wzp
|
||||
* @since 2024/8/5 上午12:58 v0.0.4-dev
|
||||
*/
|
||||
<T extends BasePlugin> T initPlugin(Class<T> baseClass, String name, String version) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException;
|
||||
List<BasePlugin> getPlugins();
|
||||
}
|
38
src/main/java/cn/wzpmc/api/plugins/JavaPlugin.java
Normal file
38
src/main/java/cn/wzpmc/api/plugins/JavaPlugin.java
Normal file
@ -0,0 +1,38 @@
|
||||
package cn.wzpmc.api.plugins;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Java插件基类
|
||||
* @author wzp
|
||||
* @version 0.0.2-dev
|
||||
* @since 2024/7/31 下午7:01
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
public abstract class JavaPlugin implements BasePlugin {
|
||||
private Logger logger;
|
||||
@Override
|
||||
public IPluginClassLoader getClassLoader() {
|
||||
Class<? extends JavaPlugin> aClass = this.getClass();
|
||||
ClassLoader loader = aClass.getClassLoader();
|
||||
if (loader instanceof IPluginClassLoader){
|
||||
return (IPluginClassLoader) loader;
|
||||
}
|
||||
throw new RuntimeException(new IllegalAccessException("You shouldn't load plugin class without PluginClassLoader!!!"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
IPluginClassLoader classLoader = this.getClassLoader();
|
||||
String name = classLoader.getName();
|
||||
String version = classLoader.getVersion();
|
||||
if (this.logger == null){
|
||||
this.logger = LogManager.getLogger(name + "-" + version);
|
||||
}
|
||||
return this.logger;
|
||||
}
|
||||
}
|
17
src/main/java/cn/wzpmc/api/plugins/event/EventHandler.java
Normal file
17
src/main/java/cn/wzpmc/api/plugins/event/EventHandler.java
Normal file
@ -0,0 +1,17 @@
|
||||
package cn.wzpmc.api.plugins.event;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 使用此注解以指定一个方法为事件执行器
|
||||
* @author wzp
|
||||
* @version 0.0.4-dev
|
||||
* @since 2024/8/15 23:47
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface EventHandler {
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
package cn.wzpmc.api.user;
|
||||
|
||||
import cn.wzpmc.api.events.Event;
|
||||
import cn.wzpmc.api.plugins.ICommandManager;
|
||||
import cn.wzpmc.api.plugins.configuration.IConfiguration;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/**
|
||||
* 机器人接口
|
||||
* @author wzp
|
||||
@ -32,4 +35,20 @@ public abstract class IBot extends CommandSender {
|
||||
* @since 2024/8/1 下午4:57 v0.0.2-dev
|
||||
*/
|
||||
public abstract void stop();
|
||||
|
||||
/**
|
||||
* 注册事件执行器
|
||||
* @author wzp
|
||||
* @since 2024/8/15 23:46 v0.0.4-dev
|
||||
* @param handler 事件执行器
|
||||
*/
|
||||
public abstract void registerEventHandler(Object handler);
|
||||
|
||||
/**
|
||||
* 触发一个事件
|
||||
* @author wzp
|
||||
* @since 2024/8/16 00:49 v0.0.4-dev
|
||||
* @param event 事件
|
||||
*/
|
||||
public abstract void triggerEvent(Event event) throws InvocationTargetException, IllegalAccessException;
|
||||
}
|
||||
|
65
src/main/java/cn/wzpmc/api/utils/IncreasbleHashMap.java
Normal file
65
src/main/java/cn/wzpmc/api/utils/IncreasbleHashMap.java
Normal file
@ -0,0 +1,65 @@
|
||||
package cn.wzpmc.api.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 一个单Key对应多Value的HashMap
|
||||
* @author wzp
|
||||
* @version 0.0.4-dev
|
||||
* @since 2024/8/16 00:02
|
||||
*/
|
||||
public class IncreasbleHashMap<K, V> extends HashMap<K, List<V>> implements IncreasbleMap<K, V>{
|
||||
@Override
|
||||
public void add(K key, V value) {
|
||||
List<V> newArrayList = super.getOrDefault(key, new ArrayList<>());
|
||||
newArrayList.add(value);
|
||||
super.put(key, newArrayList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(K key, V value) {
|
||||
if (!super.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
List<V> vs = super.get(key);
|
||||
return vs.remove(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(V value) {
|
||||
boolean has = false;
|
||||
for (List<V> vs : super.values()) {
|
||||
if (vs.contains(value)){
|
||||
has = true;
|
||||
vs.remove(value);
|
||||
}
|
||||
}
|
||||
return has;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAll(IncreasbleMap<K, V> increasbleMap) {
|
||||
for (Entry<K, List<V>> entry : increasbleMap.entrySet()) {
|
||||
this.addAll(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void addAll(K key, Collection<V> values) {
|
||||
List<V> list = this.getOrDefault(key, new ArrayList<>());
|
||||
list.addAll(values);
|
||||
this.put(key, list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
for (List<V> vs : super.values()) {
|
||||
if (vs.contains(value)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
58
src/main/java/cn/wzpmc/api/utils/IncreasbleMap.java
Normal file
58
src/main/java/cn/wzpmc/api/utils/IncreasbleMap.java
Normal file
@ -0,0 +1,58 @@
|
||||
package cn.wzpmc.api.utils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 一个单Key对应多Value的Map
|
||||
* @author wzp
|
||||
* @version 0.0.4-dev
|
||||
* @since 2024/8/15 23:57
|
||||
*/
|
||||
public interface IncreasbleMap<K, V> extends Map<K, List<V>> {
|
||||
/**
|
||||
* 向一个Key中添加元素
|
||||
* @author wzp
|
||||
* @since 2024/8/16 00:04 v0.0.4-dev
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
*/
|
||||
void add(K key, V value);
|
||||
|
||||
/**
|
||||
* 删除某个key中的对应元素
|
||||
* @author wzp
|
||||
* @since 2024/8/16 00:05 v0.0.4-dev
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
boolean delete(K key, V value);
|
||||
|
||||
/**
|
||||
* 删除所有的对应value的值
|
||||
* @author wzp
|
||||
* @since 2024/8/16 00:05 v0.0.4-dev
|
||||
* @param value 值
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
boolean delete(V value);
|
||||
|
||||
/**
|
||||
* 将两个表融合
|
||||
* @author wzp
|
||||
* @since 2024/8/16 00:35 v0.0.4-dev
|
||||
* @param increasbleMap 另一个表
|
||||
*/
|
||||
void addAll(IncreasbleMap<K, V> increasbleMap);
|
||||
|
||||
/**
|
||||
* 将所有value添加到此key中
|
||||
* @author wzp
|
||||
* @since 2024/8/16 00:43 v0.0.4-dev
|
||||
* @param key 键
|
||||
* @param values 所有要添加的值的集合
|
||||
*/
|
||||
void addAll(K key, Collection<V> values);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package cn.wzpmc.console.logger;
|
||||
|
||||
import cn.wzpmc.api.plugins.BasePlugin;
|
||||
import cn.wzpmc.api.plugins.IPluginClassLoader;
|
||||
import org.apache.logging.log4j.message.*;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import static org.apache.logging.log4j.spi.AbstractLogger.DEFAULT_FLOW_MESSAGE_FACTORY_CLASS;
|
||||
|
||||
/**
|
||||
* @author wzp
|
||||
* @version 0.0.4-dev
|
||||
* @since 2024/8/9 00:35
|
||||
*/
|
||||
public class PluginMessageFactory implements MessageFactory {
|
||||
private final String tag;
|
||||
private static final MessageFactory baseFactory;
|
||||
|
||||
static {
|
||||
try {
|
||||
baseFactory = (MessageFactory) DEFAULT_FLOW_MESSAGE_FACTORY_CLASS.getConstructor().newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PluginMessageFactory(BasePlugin plugin) {
|
||||
IPluginClassLoader classLoader = plugin.getClassLoader();
|
||||
String name = classLoader.getName();
|
||||
String version = classLoader.getVersion();
|
||||
this.tag = '[' + name + '-' + version + "] ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message newMessage(Object message) {
|
||||
return baseFactory.newMessage(tag + "{}", message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message newMessage(String message) {
|
||||
return baseFactory.newMessage(this.tag + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message newMessage(String message, Object... params) {
|
||||
return baseFactory.newMessage(this.tag + message, params);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package cn.wzpmc.entities.event;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author wzp
|
||||
* @version 0.0.4-dev
|
||||
* @since 2024/8/15 23:53
|
||||
*/
|
||||
@Data
|
||||
public class EventHandlerMethod {
|
||||
private final Object object;
|
||||
private final Method method;
|
||||
}
|
@ -1,17 +1,26 @@
|
||||
package cn.wzpmc.entities.user.bot;
|
||||
|
||||
import cn.wzpmc.api.events.Event;
|
||||
import cn.wzpmc.api.message.MessageComponent;
|
||||
import cn.wzpmc.api.message.StringMessage;
|
||||
import cn.wzpmc.api.message.json.JsonMessage;
|
||||
import cn.wzpmc.api.plugins.BasePlugin;
|
||||
import cn.wzpmc.api.user.IBot;
|
||||
import cn.wzpmc.api.user.permission.Permissions;
|
||||
import cn.wzpmc.api.utils.IncreasbleHashMap;
|
||||
import cn.wzpmc.configuration.Configuration;
|
||||
import cn.wzpmc.console.MyBotConsole;
|
||||
import cn.wzpmc.entities.event.EventHandlerMethod;
|
||||
import cn.wzpmc.plugins.CommandManager;
|
||||
import cn.wzpmc.plugins.PluginManager;
|
||||
import cn.wzpmc.utils.ReflectionUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 机器人实现类
|
||||
* @author wzp
|
||||
@ -27,6 +36,8 @@ public class MyBot extends IBot {
|
||||
@Setter
|
||||
private String name;
|
||||
private final CommandManager commandManager = new CommandManager(this);
|
||||
private final PluginManager pluginManager = new PluginManager();
|
||||
private final IncreasbleHashMap<Class<? extends Event>, EventHandlerMethod> events = new IncreasbleHashMap<>();
|
||||
@Setter
|
||||
private MyBotConsole console = null;
|
||||
public MyBot(Configuration configuration){
|
||||
@ -46,8 +57,24 @@ public class MyBot extends IBot {
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
for (BasePlugin plugin : this.pluginManager.getPlugins()) {
|
||||
plugin.onUnload();
|
||||
}
|
||||
if (this.console != null) {
|
||||
this.console.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerEventHandler(Object handler) {
|
||||
this.events.addAll(ReflectionUtils.loadEvents(handler));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerEvent(Event event) throws InvocationTargetException, IllegalAccessException {
|
||||
List<EventHandlerMethod> eventHandlerMethods = this.events.get(event.getClass());
|
||||
for (EventHandlerMethod eventHandlerMethod : eventHandlerMethods) {
|
||||
eventHandlerMethod.getMethod().invoke(eventHandlerMethod.getObject(), event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
package cn.wzpmc.network;
|
||||
|
||||
import cn.wzpmc.api.events.Event;
|
||||
import cn.wzpmc.api.user.IBot;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/**
|
||||
* websocket包处理器
|
||||
* @author wzp
|
||||
@ -14,7 +19,9 @@ import lombok.extern.log4j.Log4j2;
|
||||
* @since 2024/7/31 上午12:14
|
||||
*/
|
||||
@Log4j2
|
||||
@RequiredArgsConstructor
|
||||
public class PacketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||
private final IBot bot;
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame webSocketFrame) {
|
||||
@ -23,8 +30,22 @@ public class PacketHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
log.warn("收到了无法处理的WebSocket数据包:{}", text);
|
||||
return;
|
||||
}
|
||||
System.out.println(text);
|
||||
JSONObject jsonObject = JSON.parseObject(text);
|
||||
if (jsonObject.containsKey("echo")) {
|
||||
handleApiEcho(jsonObject);
|
||||
return;
|
||||
}
|
||||
handleEvent(text);
|
||||
}
|
||||
private void handleEvent(String text){
|
||||
Event event = JSON.parseObject(text, Event.class);
|
||||
System.out.println(event);
|
||||
try {
|
||||
this.bot.triggerEvent(event);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
log.error(new RuntimeException(e));
|
||||
}
|
||||
}
|
||||
private void handleApiEcho(JSONObject data){
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.wzpmc.network;
|
||||
|
||||
import cn.wzpmc.api.user.IBot;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
@ -9,6 +10,7 @@ import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.net.URI;
|
||||
@ -20,8 +22,10 @@ import java.net.URI;
|
||||
* @since 2024/7/30 下午11:54
|
||||
*/
|
||||
@Log4j2
|
||||
@RequiredArgsConstructor
|
||||
public class WebSocketConnectionHandler {
|
||||
private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
|
||||
private final IBot bot;
|
||||
/**
|
||||
* 建立连接
|
||||
* @author wzp
|
||||
@ -34,7 +38,7 @@ public class WebSocketConnectionHandler {
|
||||
Bootstrap bootstrap = new Bootstrap();
|
||||
WebSocketClientHandshaker clientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(websocket, WebSocketVersion.V13, null, false, new DefaultHttpHeaders());
|
||||
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(clientHandshaker);
|
||||
PacketHandler handler = new PacketHandler();
|
||||
PacketHandler handler = new PacketHandler(this.bot);
|
||||
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new WebSocketChannelInitializer(handler, handshakePacketHandler));
|
||||
return bootstrap.connect(websocket.getHost(), websocket.getPort());
|
||||
}
|
||||
|
@ -171,12 +171,8 @@ public class CommandManager implements ICommandManager, Completer, Highlighter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setErrorPattern(Pattern pattern) {
|
||||
|
||||
}
|
||||
public void setErrorPattern(Pattern pattern) {}
|
||||
|
||||
@Override
|
||||
public void setErrorIndex(int i) {
|
||||
|
||||
}
|
||||
public void setErrorIndex(int i) {}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
package cn.wzpmc.plugins;
|
||||
|
||||
import cn.wzpmc.api.plugins.BasePlugin;
|
||||
import cn.wzpmc.api.plugins.IPluginClassLoader;
|
||||
|
||||
/**
|
||||
* Java插件基类
|
||||
* @author wzp
|
||||
* @version 0.0.2-dev
|
||||
* @since 2024/7/31 下午7:01
|
||||
*/
|
||||
public class JavaPlugin implements BasePlugin {
|
||||
|
||||
@Override
|
||||
public IPluginClassLoader getClassLoader() {
|
||||
Class<? extends JavaPlugin> aClass = this.getClass();
|
||||
ClassLoader loader = aClass.getClassLoader();
|
||||
if (loader instanceof IPluginClassLoader){
|
||||
return (IPluginClassLoader) loader;
|
||||
}
|
||||
throw new RuntimeException(new IllegalAccessException("You shouldn't load plugin class without PluginClassLoader!!!"));
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 插件类加载器实现
|
||||
@ -21,10 +22,20 @@ import java.net.URL;
|
||||
@ToString
|
||||
public class PluginClassLoader extends IPluginClassLoader {
|
||||
private final IBot bot;
|
||||
@Setter
|
||||
private BasePlugin plugin;
|
||||
private String name;
|
||||
private String version;
|
||||
public PluginClassLoader(URL[] urls, IBot bot) {
|
||||
super(urls);
|
||||
this.bot = bot;
|
||||
}
|
||||
public void setPlugin(BasePlugin plugin, String name, String version){
|
||||
if (Objects.isNull(this.plugin)){
|
||||
this.plugin = plugin;
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException("Cannot set plugin with a initialized class loader!!!");
|
||||
}
|
||||
}
|
||||
|
37
src/main/java/cn/wzpmc/plugins/PluginManager.java
Normal file
37
src/main/java/cn/wzpmc/plugins/PluginManager.java
Normal file
@ -0,0 +1,37 @@
|
||||
package cn.wzpmc.plugins;
|
||||
|
||||
import cn.wzpmc.api.plugins.BasePlugin;
|
||||
import cn.wzpmc.api.plugins.IPluginManager;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 插件管理器
|
||||
* @author wzp
|
||||
* @since 2024/8/4 下午2:38
|
||||
* @version 0.0.4-dev
|
||||
*/
|
||||
@Getter
|
||||
public class PluginManager implements IPluginManager {
|
||||
private final List<BasePlugin> plugins = new ArrayList<>();
|
||||
public <T extends BasePlugin> T initPlugin(Class<T> baseClass, String name, String version) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
|
||||
ClassLoader loader = baseClass.getClassLoader();
|
||||
if (!(loader instanceof PluginClassLoader)){
|
||||
throw new IllegalArgumentException("baseClass " + baseClass.getName() + " must be loaded with plugin class loader!!!");
|
||||
}
|
||||
PluginClassLoader pluginClassLoader = (PluginClassLoader) loader;
|
||||
if (Objects.nonNull(pluginClassLoader.getPlugin())){
|
||||
throw new IllegalStateException("baseClass " + baseClass.getName() + " has already been initialization!!!");
|
||||
}
|
||||
Constructor<T> constructor = baseClass.getConstructor();
|
||||
T t = constructor.newInstance();
|
||||
pluginClassLoader.setPlugin(t, name, version);
|
||||
plugins.add(t);
|
||||
return t;
|
||||
}
|
||||
}
|
92
src/main/java/cn/wzpmc/utils/ReflectionUtils.java
Normal file
92
src/main/java/cn/wzpmc/utils/ReflectionUtils.java
Normal file
@ -0,0 +1,92 @@
|
||||
package cn.wzpmc.utils;
|
||||
|
||||
import cn.wzpmc.api.events.Event;
|
||||
import cn.wzpmc.api.plugins.BasePlugin;
|
||||
import cn.wzpmc.api.plugins.IPluginManager;
|
||||
import cn.wzpmc.api.plugins.event.EventHandler;
|
||||
import cn.wzpmc.api.utils.IncreasbleHashMap;
|
||||
import cn.wzpmc.api.utils.IncreasbleMap;
|
||||
import cn.wzpmc.entities.event.EventHandlerMethod;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* 反射工具类
|
||||
* @author wzp
|
||||
* @since 2024/8/5 上午1:42
|
||||
* @version 0.0.4-dev
|
||||
*/
|
||||
@Log4j2
|
||||
public class ReflectionUtils {
|
||||
public static BasePlugin load(URLClassLoader loader, File file, IPluginManager pluginManager){
|
||||
String absolutePath = file.getAbsolutePath();
|
||||
try(JarFile jarFile = new JarFile(file)){
|
||||
Optional<JarEntry> first = jarFile.stream().filter((e) -> "plugin.yml".equals(e.getName())).findFirst();
|
||||
if (first.isEmpty()){
|
||||
log.error("cannot find plugin.yml in plugin {}", absolutePath);
|
||||
return null;
|
||||
}
|
||||
InputStream stream = loader.getResourceAsStream("plugin.yml");
|
||||
Yaml yaml = new Yaml();
|
||||
JSONObject jsonObject = yaml.loadAs(stream, JSONObject.class);
|
||||
String main = jsonObject.getString("main");
|
||||
String version = jsonObject.getString("version");
|
||||
String name = jsonObject.getString("name");
|
||||
try{
|
||||
Class<?> aClass = loader.loadClass(main);
|
||||
if (!BasePlugin.class.isAssignableFrom(aClass)) {
|
||||
log.error("插件{}-{}的主类{}未继承cn.wzpmc.api.plugins.JavaPlugin", name, version, main);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
//noinspection unchecked
|
||||
BasePlugin basePlugin = pluginManager.initPlugin((Class<? extends BasePlugin>) aClass, name, version);
|
||||
log.info("插件{}-{}已成功加载", name, version);
|
||||
return basePlugin;
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
|
||||
IllegalAccessException e) {
|
||||
log.error(e);
|
||||
return null;
|
||||
}
|
||||
}catch (ClassNotFoundException e) {
|
||||
log.error("无法为插件{}-{}加载主类{}!", name, version, main);
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static IncreasbleMap<Class<? extends Event>, EventHandlerMethod> loadEvents(Object eventHandlerObject){
|
||||
Class<?> eventHandlerClass = eventHandlerObject.getClass();
|
||||
IncreasbleMap<Class<? extends Event>, EventHandlerMethod> result = new IncreasbleHashMap<>();
|
||||
for (Method declaredMethod : eventHandlerClass.getDeclaredMethods()) {
|
||||
declaredMethod.setAccessible(true);
|
||||
if (!declaredMethod.isAnnotationPresent(EventHandler.class)){
|
||||
continue;
|
||||
}
|
||||
if (declaredMethod.getParameterCount() == 1) {
|
||||
Class<?> eventType = declaredMethod.getParameterTypes()[0];
|
||||
if (Event.class.isAssignableFrom(eventType)){
|
||||
//noinspection unchecked
|
||||
result.add((Class<? extends Event>) eventType, new EventHandlerMethod(eventHandlerObject, declaredMethod));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
log.warn("Not event method {}::{} shouldn't been annotations with @EventHandler", eventHandlerClass, declaredMethod.getName());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -40,4 +40,22 @@ public class TemplateFileUtils {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认文件夹,当其不存在时会被创建
|
||||
* @author wzp
|
||||
* @since 2024/8/4 下午2:36 v0.0.4-dev
|
||||
* @param path 文件夹路径
|
||||
* @return 是否创建
|
||||
*/
|
||||
public static boolean createDefaultDirectory(File path){
|
||||
log.debug("创建文件夹:{}", path.getAbsolutePath());
|
||||
if (path.isDirectory()){
|
||||
return false;
|
||||
}
|
||||
if (path.mkdir()) {
|
||||
return true;
|
||||
}
|
||||
throw new RuntimeException(new IOException("Cannot create directory " + path.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
|
42
src/test/java/DemoEventHandler.java
Normal file
42
src/test/java/DemoEventHandler.java
Normal file
@ -0,0 +1,42 @@
|
||||
import cn.wzpmc.api.events.Event;
|
||||
import cn.wzpmc.api.events.message.priv.PrivateMessageEvent;
|
||||
import cn.wzpmc.api.events.notice.notify.PokeNotifyEvent;
|
||||
import cn.wzpmc.api.plugins.event.EventHandler;
|
||||
|
||||
/**
|
||||
* @author wzp
|
||||
* @version 0.0.4-dev
|
||||
* @since 2024/8/16 01:02
|
||||
*/
|
||||
public final class DemoEventHandler {
|
||||
@EventHandler
|
||||
public void onMessage(PrivateMessageEvent event) {
|
||||
System.out.println(event);
|
||||
System.out.println("Called 1");
|
||||
}
|
||||
@EventHandler
|
||||
public void onMessage2(PrivateMessageEvent event) {
|
||||
System.out.println(event);
|
||||
System.out.println("Called 2");
|
||||
}
|
||||
@EventHandler
|
||||
public void onPoke(PokeNotifyEvent event) {
|
||||
System.out.println(event);
|
||||
System.out.println("Called poke");
|
||||
}
|
||||
public void otherMethod(Event event){
|
||||
System.err.println(event);
|
||||
System.err.println("otherMethod shouldn't called!");
|
||||
}
|
||||
@EventHandler
|
||||
public void wrongMethod(Event event, String wrongArgs){
|
||||
System.err.println(event);
|
||||
System.err.println(wrongArgs);
|
||||
System.err.println("wrongMethod shouldn't called!");
|
||||
}
|
||||
@EventHandler
|
||||
public void wrongMethod(String wrongArgs){
|
||||
System.err.println(wrongArgs);
|
||||
System.err.println("wrongMethod2 shouldn't called!");
|
||||
}
|
||||
}
|
32
src/test/java/TestEventHandle.java
Normal file
32
src/test/java/TestEventHandle.java
Normal file
@ -0,0 +1,32 @@
|
||||
import cn.wzpmc.Main;
|
||||
import cn.wzpmc.api.events.message.priv.PrivateMessageEvent;
|
||||
import cn.wzpmc.api.events.notice.notify.PokeNotifyEvent;
|
||||
import cn.wzpmc.api.user.Friend;
|
||||
import cn.wzpmc.configuration.Configuration;
|
||||
import cn.wzpmc.entities.user.bot.MyBot;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/**
|
||||
* @author wzp
|
||||
* @version 0.0.4-dev
|
||||
* @since 2024/8/16 01:01
|
||||
*/
|
||||
public class TestEventHandle {
|
||||
@Test
|
||||
public void testEventHandle() throws InvocationTargetException, IllegalAccessException {
|
||||
Configuration configuration = Main.getConfiguration();
|
||||
Friend targetUser = new Friend();
|
||||
targetUser.setNickname("test");
|
||||
targetUser.setId(Long.valueOf(0));
|
||||
MyBot bot = Main.createBot(configuration);
|
||||
bot.registerEventHandler(new DemoEventHandler());
|
||||
PokeNotifyEvent pokeNotifyEvent = new PokeNotifyEvent();
|
||||
pokeNotifyEvent.setTargetId(Long.valueOf(0));
|
||||
bot.triggerEvent(pokeNotifyEvent);
|
||||
PrivateMessageEvent privateMessageEvent = new PrivateMessageEvent();
|
||||
privateMessageEvent.setSender(targetUser);
|
||||
bot.triggerEvent(privateMessageEvent);
|
||||
}
|
||||
}
|
18
src/test/java/TestEventHandler.java
Normal file
18
src/test/java/TestEventHandler.java
Normal file
@ -0,0 +1,18 @@
|
||||
import cn.wzpmc.api.events.Event;
|
||||
import cn.wzpmc.api.events.message.priv.PrivateMessageEvent;
|
||||
import cn.wzpmc.api.events.notice.notify.PokeNotifyEvent;
|
||||
import cn.wzpmc.api.plugins.event.EventHandler;
|
||||
import cn.wzpmc.utils.ReflectionUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* @author wzp
|
||||
* @version 0.0.4-dev
|
||||
* @since 2024/8/16 00:12
|
||||
*/
|
||||
public class TestEventHandler {
|
||||
@Test
|
||||
public void testEventHandler() {
|
||||
System.out.println(ReflectionUtils.loadEvents(new DemoEventHandler()));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user