diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 53eb9c3..ef4060e 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -10,6 +10,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/fileTemplates/includes/Version.txt b/.idea/fileTemplates/includes/Version.txt
index 37e50c3..3d287b9 100644
--- a/.idea/fileTemplates/includes/Version.txt
+++ b/.idea/fileTemplates/includes/Version.txt
@@ -1 +1 @@
-0.0.3-dev
\ No newline at end of file
+0.0.4-dev
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 6f5f4dd..f55fa3e 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,7 +2,6 @@
-
diff --git a/build.gradle.kts b/build.gradle.kts
index d993c9f..13019be 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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")
diff --git a/src/main/java/cn/wzpmc/Main.java b/src/main/java/cn/wzpmc/Main.java
index 2146953..651e645 100644
--- a/src/main/java/cn/wzpmc/Main.java
+++ b/src/main/java/cn/wzpmc/Main.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());
+ 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);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/cn/wzpmc/api/plugins/BasePlugin.java b/src/main/java/cn/wzpmc/api/plugins/BasePlugin.java
index 7d572ce..6ab5f64 100644
--- a/src/main/java/cn/wzpmc/api/plugins/BasePlugin.java
+++ b/src/main/java/cn/wzpmc/api/plugins/BasePlugin.java
@@ -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();
}
diff --git a/src/main/java/cn/wzpmc/api/plugins/IPluginClassLoader.java b/src/main/java/cn/wzpmc/api/plugins/IPluginClassLoader.java
index 1a72633..8c78879 100644
--- a/src/main/java/cn/wzpmc/api/plugins/IPluginClassLoader.java
+++ b/src/main/java/cn/wzpmc/api/plugins/IPluginClassLoader.java
@@ -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();
}
diff --git a/src/main/java/cn/wzpmc/api/plugins/IPluginManager.java b/src/main/java/cn/wzpmc/api/plugins/IPluginManager.java
new file mode 100644
index 0000000..655a491
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/plugins/IPluginManager.java
@@ -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 插件主类类型
+ * @param baseClass 插件主类
+ * @param name 插件名称
+ * @param version 插件版本
+ * @return 这个插件的实例
+ * @author wzp
+ * @since 2024/8/5 上午12:58 v0.0.4-dev
+ */
+ T initPlugin(Class baseClass, String name, String version) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException;
+ List getPlugins();
+}
diff --git a/src/main/java/cn/wzpmc/api/plugins/JavaPlugin.java b/src/main/java/cn/wzpmc/api/plugins/JavaPlugin.java
new file mode 100644
index 0000000..92d0580
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/plugins/JavaPlugin.java
@@ -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;
+ }
+}
diff --git a/src/main/java/cn/wzpmc/api/plugins/event/EventHandler.java b/src/main/java/cn/wzpmc/api/plugins/event/EventHandler.java
new file mode 100644
index 0000000..f4ecd40
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/plugins/event/EventHandler.java
@@ -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 {
+}
diff --git a/src/main/java/cn/wzpmc/api/user/IBot.java b/src/main/java/cn/wzpmc/api/user/IBot.java
index a94ab31..ff1f7f2 100644
--- a/src/main/java/cn/wzpmc/api/user/IBot.java
+++ b/src/main/java/cn/wzpmc/api/user/IBot.java
@@ -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;
}
diff --git a/src/main/java/cn/wzpmc/api/utils/IncreasbleHashMap.java b/src/main/java/cn/wzpmc/api/utils/IncreasbleHashMap.java
new file mode 100644
index 0000000..3bc30bc
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/utils/IncreasbleHashMap.java
@@ -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 extends HashMap> implements IncreasbleMap{
+ @Override
+ public void add(K key, V value) {
+ List 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 vs = super.get(key);
+ return vs.remove(value);
+ }
+
+ @Override
+ public boolean delete(V value) {
+ boolean has = false;
+ for (List vs : super.values()) {
+ if (vs.contains(value)){
+ has = true;
+ vs.remove(value);
+ }
+ }
+ return has;
+ }
+
+ @Override
+ public void addAll(IncreasbleMap increasbleMap) {
+ for (Entry> entry : increasbleMap.entrySet()) {
+ this.addAll(entry.getKey(), entry.getValue());
+ }
+ }
+ @Override
+ public void addAll(K key, Collection values) {
+ List list = this.getOrDefault(key, new ArrayList<>());
+ list.addAll(values);
+ this.put(key, list);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ for (List vs : super.values()) {
+ if (vs.contains(value)){
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/cn/wzpmc/api/utils/IncreasbleMap.java b/src/main/java/cn/wzpmc/api/utils/IncreasbleMap.java
new file mode 100644
index 0000000..ef13bf2
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/utils/IncreasbleMap.java
@@ -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 extends Map> {
+ /**
+ * 向一个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 increasbleMap);
+
+ /**
+ * 将所有value添加到此key中
+ * @author wzp
+ * @since 2024/8/16 00:43 v0.0.4-dev
+ * @param key 键
+ * @param values 所有要添加的值的集合
+ */
+ void addAll(K key, Collection values);
+}
diff --git a/src/main/java/cn/wzpmc/console/logger/PluginMessageFactory.java b/src/main/java/cn/wzpmc/console/logger/PluginMessageFactory.java
new file mode 100644
index 0000000..88f1ead
--- /dev/null
+++ b/src/main/java/cn/wzpmc/console/logger/PluginMessageFactory.java
@@ -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);
+ }
+}
diff --git a/src/main/java/cn/wzpmc/entities/event/EventHandlerMethod.java b/src/main/java/cn/wzpmc/entities/event/EventHandlerMethod.java
new file mode 100644
index 0000000..db59cee
--- /dev/null
+++ b/src/main/java/cn/wzpmc/entities/event/EventHandlerMethod.java
@@ -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;
+}
diff --git a/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java b/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java
index 93a8681..0449394 100644
--- a/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java
+++ b/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java
@@ -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, 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 eventHandlerMethods = this.events.get(event.getClass());
+ for (EventHandlerMethod eventHandlerMethod : eventHandlerMethods) {
+ eventHandlerMethod.getMethod().invoke(eventHandlerMethod.getObject(), event);
+ }
+ }
}
diff --git a/src/main/java/cn/wzpmc/network/PacketHandler.java b/src/main/java/cn/wzpmc/network/PacketHandler.java
index cd4b525..e6c49a4 100644
--- a/src/main/java/cn/wzpmc/network/PacketHandler.java
+++ b/src/main/java/cn/wzpmc/network/PacketHandler.java
@@ -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 {
+ private final IBot bot;
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame webSocketFrame) {
@@ -23,8 +30,22 @@ public class PacketHandler extends SimpleChannelInboundHandler 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!!!"));
- }
-}
diff --git a/src/main/java/cn/wzpmc/plugins/PluginClassLoader.java b/src/main/java/cn/wzpmc/plugins/PluginClassLoader.java
index 528faec..e7f0544 100644
--- a/src/main/java/cn/wzpmc/plugins/PluginClassLoader.java
+++ b/src/main/java/cn/wzpmc/plugins/PluginClassLoader.java
@@ -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!!!");
+ }
}
diff --git a/src/main/java/cn/wzpmc/plugins/PluginManager.java b/src/main/java/cn/wzpmc/plugins/PluginManager.java
new file mode 100644
index 0000000..cbac7ac
--- /dev/null
+++ b/src/main/java/cn/wzpmc/plugins/PluginManager.java
@@ -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 plugins = new ArrayList<>();
+ public T initPlugin(Class 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 constructor = baseClass.getConstructor();
+ T t = constructor.newInstance();
+ pluginClassLoader.setPlugin(t, name, version);
+ plugins.add(t);
+ return t;
+ }
+}
diff --git a/src/main/java/cn/wzpmc/utils/ReflectionUtils.java b/src/main/java/cn/wzpmc/utils/ReflectionUtils.java
new file mode 100644
index 0000000..0f382a5
--- /dev/null
+++ b/src/main/java/cn/wzpmc/utils/ReflectionUtils.java
@@ -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 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, EventHandlerMethod> loadEvents(Object eventHandlerObject){
+ Class> eventHandlerClass = eventHandlerObject.getClass();
+ IncreasbleMap, 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;
+ }
+}
diff --git a/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java b/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java
index 75cd5dd..c7ce6a3 100644
--- a/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java
+++ b/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java
@@ -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()));
+ }
}
diff --git a/src/test/java/DemoEventHandler.java b/src/test/java/DemoEventHandler.java
new file mode 100644
index 0000000..e664b84
--- /dev/null
+++ b/src/test/java/DemoEventHandler.java
@@ -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!");
+ }
+}
diff --git a/src/test/java/TestEventHandle.java b/src/test/java/TestEventHandle.java
new file mode 100644
index 0000000..678dac2
--- /dev/null
+++ b/src/test/java/TestEventHandle.java
@@ -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);
+ }
+}
diff --git a/src/test/java/TestEventHandler.java b/src/test/java/TestEventHandler.java
new file mode 100644
index 0000000..6967b58
--- /dev/null
+++ b/src/test/java/TestEventHandler.java
@@ -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()));
+ }
+}