diff --git a/.idea/file.template.settings.xml b/.idea/file.template.settings.xml
new file mode 100644
index 0000000..46b58c2
--- /dev/null
+++ b/.idea/file.template.settings.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/fileTemplates/code/JavaDoc Class.java b/.idea/fileTemplates/code/JavaDoc Class.java
new file mode 100644
index 0000000..0889faf
--- /dev/null
+++ b/.idea/fileTemplates/code/JavaDoc Class.java
@@ -0,0 +1,10 @@
+ * @author wzp
+ * @since ${DATE} ${TIME}
+ * @version #parse("version.txt")
+
+#foreach($param in $RECORD_COMPONENTS)
+ * @param $param
+#end
+#foreach($param in $TYPE_PARAMS)
+ * @param <$param>
+#end
\ No newline at end of file
diff --git a/.idea/fileTemplates/code/JavaDoc Constructor.java b/.idea/fileTemplates/code/JavaDoc Constructor.java
new file mode 100644
index 0000000..6104883
--- /dev/null
+++ b/.idea/fileTemplates/code/JavaDoc Constructor.java
@@ -0,0 +1,12 @@
+ * @author wzp
+ * @since ${DATE} ${TIME} v#parse("version.txt")
+
+#foreach($param in $PARAMS)
+ * @param $param
+#end
+#foreach($param in $TYPE_PARAMS)
+ * @param <$param>
+#end
+#foreach($exception in $THROWS)
+ * @throws $exception
+#end
\ No newline at end of file
diff --git a/.idea/fileTemplates/code/JavaDoc Field.java b/.idea/fileTemplates/code/JavaDoc Field.java
new file mode 100644
index 0000000..0ec16c8
--- /dev/null
+++ b/.idea/fileTemplates/code/JavaDoc Field.java
@@ -0,0 +1,2 @@
+ * @since ${DATE} ${TIME} v#parse("version.txt")
+
diff --git a/.idea/fileTemplates/code/JavaDoc Method.java b/.idea/fileTemplates/code/JavaDoc Method.java
new file mode 100644
index 0000000..379fb5d
--- /dev/null
+++ b/.idea/fileTemplates/code/JavaDoc Method.java
@@ -0,0 +1,15 @@
+ * @author wzp
+ * @since ${DATE} ${TIME} v#parse("version.txt")
+
+#foreach($param in $PARAMS)
+ * @param $param
+#end
+#if($RETURN_TYPE != "void")
+ * @return
+#end
+#foreach($param in $TYPE_PARAMS)
+ * @param <$param>
+#end
+#foreach($exception in $THROWS)
+ * @throws $exception
+#end
\ No newline at end of file
diff --git a/.idea/fileTemplates/code/JavaDoc Overriding Method.java b/.idea/fileTemplates/code/JavaDoc Overriding Method.java
new file mode 100644
index 0000000..36a73b1
--- /dev/null
+++ b/.idea/fileTemplates/code/JavaDoc Overriding Method.java
@@ -0,0 +1,20 @@
+ * @author wzp
+ * @since ${DATE} ${TIME} v#parse("version.txt")
+
+#foreach ($param in $PARAMS_INHERITED)
+ * @param $param
+#end
+#if (!$PARAMS_INHERITED)
+ #foreach ($param in $PARAMS)
+ * @param $param
+ #end
+#end
+#if($RETURN_TYPE != "void")
+ * @return
+#end
+#foreach($param in $TYPE_PARAMS)
+ * @param <$param>
+#end
+#foreach($exception in $THROWS)
+ * @throws $exception
+#end
\ No newline at end of file
diff --git a/.idea/fileTemplates/includes/File Header.java b/.idea/fileTemplates/includes/File Header.java
new file mode 100644
index 0000000..475d4cf
--- /dev/null
+++ b/.idea/fileTemplates/includes/File Header.java
@@ -0,0 +1,5 @@
+/**
+ * @author wzp
+ * @since ${DATE} ${TIME}
+ * @version #parse("version.txt")
+ */
\ No newline at end of file
diff --git a/.idea/fileTemplates/includes/Version.txt b/.idea/fileTemplates/includes/Version.txt
new file mode 100644
index 0000000..772a55e
--- /dev/null
+++ b/.idea/fileTemplates/includes/Version.txt
@@ -0,0 +1 @@
+0.0.1-dev
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index fdc392f..789c2d0 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -16,5 +16,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 73e20a2..0d5d7ec 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,15 +13,21 @@ version = projectVersion
repositories {
mavenCentral()
+ maven("https://libraries.minecraft.net")
}
dependencies {
+ implementation("com.mojang:brigadier:1.0.18")
// https://mvnrepository.com/artifact/io.netty/netty-all
implementation("io.netty:netty-all:4.1.112.Final")
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
implementation("org.apache.logging.log4j:log4j-core:2.23.1")
// https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
implementation("com.alibaba.fastjson2:fastjson2:2.0.52")
+ // https://mvnrepository.com/artifact/org.yaml/snakeyaml
+ implementation("org.yaml:snakeyaml:2.2")
+ // https://mvnrepository.com/artifact/org.jline/jline
+ implementation("org.jline:jline:3.26.3")
// https://mvnrepository.com/artifact/org.projectlombok/lombok
compileOnly("org.projectlombok:lombok:1.18.34")
annotationProcessor("org.projectlombok:lombok:1.18.34")
diff --git a/src/main/java/cn/wzpmc/Main.java b/src/main/java/cn/wzpmc/Main.java
index 7b4ca38..4019b42 100644
--- a/src/main/java/cn/wzpmc/Main.java
+++ b/src/main/java/cn/wzpmc/Main.java
@@ -1,10 +1,42 @@
package cn.wzpmc;
+import cn.wzpmc.configuration.Configuration;
+import cn.wzpmc.network.WebSocketConnectionHandler;
+import cn.wzpmc.utils.TemplateFileUtils;
+import cn.wzpmc.utils.YamlUtils;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+
@Log4j2
public class Main {
+ private static final String DEFAULT_CONFIGURATION_FILE_PATH = "templates/config.yaml";
+ @SneakyThrows
public static void main(String[] args) {
-
+ log.info("启动MyBot...");
+ File configurationFile = new File("config.yaml");
+ if (TemplateFileUtils.saveDefaultConfig(Main.class.getClassLoader(), DEFAULT_CONFIGURATION_FILE_PATH, configurationFile)) {
+ log.debug("创建日志文件成功!");
+ log.info("首次启动,默认配置文件已创建,请填写后再次启动MyBot!");
+ return;
+ }
+ log.debug("读取配置文件 {}", configurationFile.getAbsolutePath());
+ Configuration configuration = YamlUtils.readYamlFile(configurationFile, Configuration.class);
+ URI uri;
+ try {
+ uri = new URI(configuration.getWebsocket());
+ } catch (URISyntaxException e) {
+ log.error("无法解析websocket地址");
+ return;
+ }
+ WebSocketConnectionHandler webSocketConnectionHandler = new WebSocketConnectionHandler();
+ ChannelFuture future = webSocketConnectionHandler.connect(uri);
+ Channel channel = future.sync().channel();
+ log.info("连接服务器成功!");
}
}
\ No newline at end of file
diff --git a/src/main/java/cn/wzpmc/api/commands/BaseCommand.java b/src/main/java/cn/wzpmc/api/commands/BaseCommand.java
new file mode 100644
index 0000000..b8f4396
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/commands/BaseCommand.java
@@ -0,0 +1,10 @@
+package cn.wzpmc.api.commands;
+
+/**
+ * 指令基类
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午3:15
+ */
+public interface BaseCommand {
+}
diff --git a/src/main/java/cn/wzpmc/api/commands/BrigadierCommand.java b/src/main/java/cn/wzpmc/api/commands/BrigadierCommand.java
new file mode 100644
index 0000000..72797a6
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/commands/BrigadierCommand.java
@@ -0,0 +1,20 @@
+package cn.wzpmc.api.commands;
+
+import cn.wzpmc.api.user.CommandSender;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+
+/**
+ * Brigadier指令
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午2:59
+ */
+public interface BrigadierCommand extends BaseCommand {
+ /**
+ * 获取指令节点
+ * @author wzp
+ * @since 2024/7/31 上午3:16 v0.0.1-dev
+ * @return 指令节点
+ */
+ LiteralArgumentBuilder getCommandNode();
+}
diff --git a/src/main/java/cn/wzpmc/api/commands/RawCommand.java b/src/main/java/cn/wzpmc/api/commands/RawCommand.java
new file mode 100644
index 0000000..110a526
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/commands/RawCommand.java
@@ -0,0 +1,35 @@
+package cn.wzpmc.api.commands;
+
+import cn.wzpmc.api.user.CommandSender;
+
+import java.util.List;
+
+/**
+ * 原始指令
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午2:27
+ */
+public interface RawCommand extends BaseCommand {
+ /**
+ * 当指令执行时
+ * @author wzp
+ * @since 2024/7/31 上午2:57 v0.0.1-dev
+ * @param commandSender 指令发送者
+ * @param arguments 指令参数,如指令foo若执行/foo a b c则arguments为{"a", "b", "c"}
+ * @return 指令是否执行成功
+ */
+ boolean onExecute(CommandSender commandSender, List arguments);
+
+ /**
+ * 指令补全
+ * @author wzp
+ * @since 2024/7/31 上午2:58 v0.0.1-dev
+ * @param commandSender 指令发送者
+ * @param arguments 指令参数,如指令foo若执行/foo a b c则arguments为{"a", "b", "c"}
+ * @return 当前需要提示的子命令
+ */
+ default List onTabComplete(CommandSender commandSender, List arguments) {
+ return List.of();
+ }
+}
diff --git a/src/main/java/cn/wzpmc/api/message/MessageComponent.java b/src/main/java/cn/wzpmc/api/message/MessageComponent.java
new file mode 100644
index 0000000..a086fb7
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/message/MessageComponent.java
@@ -0,0 +1,17 @@
+package cn.wzpmc.api.message;
+
+/**
+ * 消息对象接口
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午2:33
+ */
+public interface MessageComponent {
+ /**
+ * 将其转换为发送的文本
+ * @author wzp
+ * @since 2024/7/31 上午2:41 v0.0.1-dev
+ * @return 消息文本
+ */
+ String toMessageString();
+}
diff --git a/src/main/java/cn/wzpmc/api/message/StringMessage.java b/src/main/java/cn/wzpmc/api/message/StringMessage.java
new file mode 100644
index 0000000..9955642
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/message/StringMessage.java
@@ -0,0 +1,29 @@
+package cn.wzpmc.api.message;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 纯文本消息
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午2:34
+ */
+@RequiredArgsConstructor
+public class StringMessage implements MessageComponent{
+ private final String message;
+ @Override
+ public String toMessageString() {
+ return this.message;
+ }
+
+ /**
+ * 构建纯文本消息
+ * @author wzp
+ * @since 2024/7/31 上午2:41 v0.0.1-dev
+ * @param message 消息文本
+ * @return 文本消息对象
+ */
+ public static StringMessage text(String message){
+ return new StringMessage(message);
+ }
+}
diff --git a/src/main/java/cn/wzpmc/api/message/json/JsonMessage.java b/src/main/java/cn/wzpmc/api/message/json/JsonMessage.java
new file mode 100644
index 0000000..4169ee4
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/message/json/JsonMessage.java
@@ -0,0 +1,25 @@
+package cn.wzpmc.api.message.json;
+
+import cn.wzpmc.api.message.MessageComponent;
+import com.alibaba.fastjson2.JSON;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * JSON消息
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午2:34
+ */
+public class JsonMessage implements MessageComponent {
+ List messageParts = new ArrayList<>();
+ @Override
+ public String toMessageString() {
+ return JSON.toJSONString(messageParts);
+ }
+ public String toTextDisplay() {
+ return this.messageParts.stream().map(JsonMessagePart::getTextDisplay).collect(Collectors.joining(""));
+ }
+}
diff --git a/src/main/java/cn/wzpmc/api/message/json/JsonMessagePart.java b/src/main/java/cn/wzpmc/api/message/json/JsonMessagePart.java
new file mode 100644
index 0000000..8e545e6
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/message/json/JsonMessagePart.java
@@ -0,0 +1,47 @@
+package cn.wzpmc.api.message.json;
+
+import java.util.Map;
+
+/**
+ * JSON消息段
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午2:36
+ */
+public interface JsonMessagePart {
+ /**
+ * 获取消息的类型
+ * @author wzp
+ * @since 2024/7/31 上午2:40 v0.0.1-dev
+ * @return 消息类型字符串
+ */
+ String getType();
+
+ /**
+ * 获取消息附带的数据
+ * @author wzp
+ * @since 2024/7/31 上午2:40 v0.0.1-dev
+ * @return 数据
+ */
+ Map getData();
+
+ /**
+ * 获取当纯文本界面的代替文本
+ * @author wzp
+ * @since 2024/7/31 上午2:45 v0.0.1-dev
+ * @return 文本
+ */
+ default String getTextDisplay(){
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(this.getType())
+ .append('(')
+ .append('\n');
+ Map data = this.getData();
+ int i = 0;
+ data.forEach((key, value) -> stringBuilder.append('\t').append(key).append('=').append(value).append(',').append('\n'));
+ int length = stringBuilder.length();
+ stringBuilder.delete(length - 2, length - 1);
+ stringBuilder.append(')');
+ return stringBuilder.toString();
+ }
+}
diff --git a/src/main/java/cn/wzpmc/api/plugins/ICommandManager.java b/src/main/java/cn/wzpmc/api/plugins/ICommandManager.java
new file mode 100644
index 0000000..989e451
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/plugins/ICommandManager.java
@@ -0,0 +1,28 @@
+package cn.wzpmc.api.plugins;
+
+import cn.wzpmc.api.commands.BrigadierCommand;
+import cn.wzpmc.api.commands.RawCommand;
+
+/**
+ * 指令管理器
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午3:40
+ */
+public interface ICommandManager {
+ /**
+ * 注册原始指令
+ * @author wzp
+ * @since 2024/7/31 上午3:34 v0.0.1-dev
+ * @param rawCommand 原始指令
+ * @param name 指令名称
+ */
+ void registerCommand(RawCommand rawCommand, String name);
+ /**
+ * 注册Brigadier指令
+ * @author wzp
+ * @since 2024/7/31 上午3:35 v0.0.1-dev
+ * @param brigadierCommand 指令对象
+ */
+ void registerCommand(BrigadierCommand brigadierCommand);
+}
diff --git a/src/main/java/cn/wzpmc/api/plugins/configuration/IAuthorizationConfiguration.java b/src/main/java/cn/wzpmc/api/plugins/configuration/IAuthorizationConfiguration.java
new file mode 100644
index 0000000..5695cf8
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/plugins/configuration/IAuthorizationConfiguration.java
@@ -0,0 +1,23 @@
+package cn.wzpmc.api.plugins.configuration;
+
+/**
+ *
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午3:42
+ */
+public interface IAuthorizationConfiguration {
+ /**
+ * @author wzp
+ * @since 2024/7/31 上午3:45 v0.0.1-dev
+ * @return 是否启用
+ */
+ boolean isEnable();
+
+ /**
+ * @author wzp
+ * @since 2024/7/31 上午3:45 v0.0.1-dev
+ * @return token
+ */
+ String getToken();
+}
diff --git a/src/main/java/cn/wzpmc/api/plugins/configuration/IConfiguration.java b/src/main/java/cn/wzpmc/api/plugins/configuration/IConfiguration.java
new file mode 100644
index 0000000..58d2217
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/plugins/configuration/IConfiguration.java
@@ -0,0 +1,30 @@
+package cn.wzpmc.api.plugins.configuration;
+
+/**
+ * 配置
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午3:42
+ */
+public interface IConfiguration {
+ /**
+ * @author wzp
+ * @since 2024/7/31 上午3:48 v0.0.1-dev
+ * @return WebSocket连接URL
+ */
+ String getWebsocket();
+
+ /**
+ * @author wzp
+ * @since 2024/7/31 上午3:48 v0.0.1-dev
+ * @return 通信验证
+ */
+ IAuthorizationConfiguration getAuthorization();
+
+ /**
+ * @author wzp
+ * @since 2024/7/31 上午3:49 v0.0.1-dev
+ * @return 失败消息提示
+ */
+ IFallbackConfiguration getFallback();
+}
diff --git a/src/main/java/cn/wzpmc/api/plugins/configuration/IFallbackConfiguration.java b/src/main/java/cn/wzpmc/api/plugins/configuration/IFallbackConfiguration.java
new file mode 100644
index 0000000..c1d279d
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/plugins/configuration/IFallbackConfiguration.java
@@ -0,0 +1,23 @@
+package cn.wzpmc.api.plugins.configuration;
+
+/**
+ * 通信验证配置
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午3:43
+ */
+public interface IFallbackConfiguration {
+ /**
+ * @author wzp
+ * @since 2024/7/31 上午3:47 v0.0.1-dev
+ * @return 当指令执行失败时
+ */
+ String getCommand();
+
+ /**
+ * @author wzp
+ * @since 2024/7/31 上午3:47 v0.0.1-dev
+ * @return 当出现未捕获的异常时
+ */
+ String getErrorUncaught();
+}
diff --git a/src/main/java/cn/wzpmc/api/user/CommandSender.java b/src/main/java/cn/wzpmc/api/user/CommandSender.java
new file mode 100644
index 0000000..c8ea646
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/user/CommandSender.java
@@ -0,0 +1,34 @@
+package cn.wzpmc.api.user;
+
+import cn.wzpmc.api.message.MessageComponent;
+
+/**
+ * 消息发送者
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午2:32
+ */
+public interface CommandSender {
+ /**
+ * 获取用户ID
+ * @author wzp
+ * @since 2024/7/30 下午11:48 v0.0.1-dev
+ * @return 用户ID
+ */
+ Long getId();
+
+ /**
+ * 获取用户名
+ * @author wzp
+ * @since 2024/7/30 下午11:48 v0.0.1-dev
+ * @return 用户名
+ */
+ Long getName();
+ /**
+ * 发送消息
+ * @author wzp
+ * @since 2024/7/31 上午2:42 v0.0.1-dev
+ * @param messageComponent 消息组件
+ */
+ void sendMessage(MessageComponent messageComponent);
+}
diff --git a/src/main/java/cn/wzpmc/api/user/IBot.java b/src/main/java/cn/wzpmc/api/user/IBot.java
new file mode 100644
index 0000000..06a78ab
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/user/IBot.java
@@ -0,0 +1,28 @@
+package cn.wzpmc.api.user;
+
+import cn.wzpmc.api.plugins.ICommandManager;
+import cn.wzpmc.api.plugins.configuration.IConfiguration;
+
+/**
+ * 机器人接口
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午2:31
+ */
+public interface IBot extends CommandSender {
+ /**
+ * 获取配置文件
+ * @author wzp
+ * @since 2024/7/31 上午2:55 v0.0.1-dev
+ * @return 配置文件
+ */
+ IConfiguration getConfiguration();
+
+ /**
+ * 获取指令管理器
+ * @author wzp
+ * @since 2024/7/31 上午3:42 v0.0.1-dev
+ * @return 指令管理器
+ */
+ ICommandManager getCommandManager();
+}
diff --git a/src/main/java/cn/wzpmc/api/user/IUser.java b/src/main/java/cn/wzpmc/api/user/IUser.java
new file mode 100644
index 0000000..9ab65fb
--- /dev/null
+++ b/src/main/java/cn/wzpmc/api/user/IUser.java
@@ -0,0 +1,11 @@
+package cn.wzpmc.api.user;
+
+/**
+ * 用户接口
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/30 下午11:42
+ */
+public interface IUser extends CommandSender {
+
+}
diff --git a/src/main/java/cn/wzpmc/commands/StopCommand.java b/src/main/java/cn/wzpmc/commands/StopCommand.java
new file mode 100644
index 0000000..75460b9
--- /dev/null
+++ b/src/main/java/cn/wzpmc/commands/StopCommand.java
@@ -0,0 +1,9 @@
+package cn.wzpmc.commands;
+
+/**
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午2:26
+ */
+public class StopCommand {
+}
diff --git a/src/main/java/cn/wzpmc/configuration/AuthorizationConfiguration.java b/src/main/java/cn/wzpmc/configuration/AuthorizationConfiguration.java
new file mode 100644
index 0000000..2226fc7
--- /dev/null
+++ b/src/main/java/cn/wzpmc/configuration/AuthorizationConfiguration.java
@@ -0,0 +1,24 @@
+package cn.wzpmc.configuration;
+
+import cn.wzpmc.api.plugins.configuration.IAuthorizationConfiguration;
+import lombok.*;
+
+/**
+ * 通信验证配置类
+ * @author wzp
+ * @since 2024/7/30 下午11:50
+ * @version 0.0.1-dev
+ */
+@Data
+public class AuthorizationConfiguration implements IAuthorizationConfiguration {
+ /**
+ * 是否启用
+ * @since 2024/7/30 下午11:50 v0.0.1-dev
+ */
+ private boolean enable;
+ /**
+ * token
+ * @since 2024/7/30 下午11:50 v0.0.1-dev
+ */
+ private String token;
+}
diff --git a/src/main/java/cn/wzpmc/configuration/Configuration.java b/src/main/java/cn/wzpmc/configuration/Configuration.java
new file mode 100644
index 0000000..a945eeb
--- /dev/null
+++ b/src/main/java/cn/wzpmc/configuration/Configuration.java
@@ -0,0 +1,29 @@
+package cn.wzpmc.configuration;
+
+import cn.wzpmc.api.plugins.configuration.IConfiguration;
+import lombok.*;
+
+/**
+ * 配置类
+ * @author wzp
+ * @since 2024/7/30 下午11:48
+ * @version 0.0.1-dev
+ */
+@Data
+public class Configuration implements IConfiguration {
+ /**
+ * WebSocket连接URL
+ * @since 2024/7/30 下午11:48 v0.0.1-dev
+ */
+ private String websocket;
+ /**
+ * 通信验证
+ * @since 2024/7/30 下午11:49 v0.0.1-dev
+ */
+ private AuthorizationConfiguration authorization;
+ /**
+ * 失败消息提示
+ * @since 2024/7/30 下午11:49 v0.0.1-dev
+ */
+ private FallbackConfiguration fallback;
+}
diff --git a/src/main/java/cn/wzpmc/configuration/FallbackConfiguration.java b/src/main/java/cn/wzpmc/configuration/FallbackConfiguration.java
new file mode 100644
index 0000000..990487d
--- /dev/null
+++ b/src/main/java/cn/wzpmc/configuration/FallbackConfiguration.java
@@ -0,0 +1,24 @@
+package cn.wzpmc.configuration;
+
+import cn.wzpmc.api.plugins.configuration.IFallbackConfiguration;
+import lombok.*;
+
+/**
+ * 当失败时报错消息配置实现
+ * @author wzp
+ * @since 2024/7/31 上午3:44
+ * @version 0.0.1-dev
+ */
+@Data
+public class FallbackConfiguration implements IFallbackConfiguration {
+ /**
+ * 当指令执行失败时
+ * @since 2024/7/31 上午3:44 v0.0.1-dev
+ */
+ private String command;
+ /**
+ * 当出现未捕获的异常时
+ * @since 2024/7/31 上午3:44 v0.0.1-dev
+ */
+ private String errorUncaught;
+}
diff --git a/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java b/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java
new file mode 100644
index 0000000..be1ff85
--- /dev/null
+++ b/src/main/java/cn/wzpmc/entities/user/bot/MyBot.java
@@ -0,0 +1,37 @@
+package cn.wzpmc.entities.user.bot;
+
+import cn.wzpmc.api.message.MessageComponent;
+import cn.wzpmc.api.message.StringMessage;
+import cn.wzpmc.api.message.json.JsonMessage;
+import cn.wzpmc.api.user.IBot;
+import cn.wzpmc.configuration.Configuration;
+import cn.wzpmc.plugins.CommandManager;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * 机器人实现类
+ * @author wzp
+ * @since 2024/7/30 下午11:46
+ * @version 0.0.1-dev
+ */
+@Log4j2
+@RequiredArgsConstructor
+@Getter
+public class MyBot implements IBot {
+ private final Configuration configuration;
+ private final Long id;
+ private final Long name;
+ private final CommandManager commandManager = new CommandManager();
+
+ @Override
+ public void sendMessage(MessageComponent messageComponent) {
+ if (messageComponent instanceof StringMessage){
+ log.info(messageComponent.toMessageString());
+ }
+ if (messageComponent instanceof JsonMessage){
+ log.info(((JsonMessage) messageComponent).toTextDisplay());
+ }
+ }
+}
diff --git a/src/main/java/cn/wzpmc/network/PacketHandler.java b/src/main/java/cn/wzpmc/network/PacketHandler.java
new file mode 100644
index 0000000..3ebcad7
--- /dev/null
+++ b/src/main/java/cn/wzpmc/network/PacketHandler.java
@@ -0,0 +1,31 @@
+package cn.wzpmc.network;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
+import io.netty.handler.codec.http.websocketx.WebSocketFrame;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午12:14
+ */
+@Log4j2
+@RequiredArgsConstructor
+public class PacketHandler extends SimpleChannelInboundHandler {
+ private final WebSocketClientHandshaker handshaker;
+
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ log.debug("开始WebSocket握手");
+ this.handshaker.handshake(ctx.channel());
+ log.debug("握手完成");
+ }
+
+ @Override
+ protected void channelRead0(ChannelHandlerContext channelHandlerContext, WebSocketFrame webSocketFrame) throws Exception {
+ System.out.println(webSocketFrame);
+ }
+}
diff --git a/src/main/java/cn/wzpmc/network/WebSocketChannelInitializer.java b/src/main/java/cn/wzpmc/network/WebSocketChannelInitializer.java
new file mode 100644
index 0000000..8b33461
--- /dev/null
+++ b/src/main/java/cn/wzpmc/network/WebSocketChannelInitializer.java
@@ -0,0 +1,29 @@
+package cn.wzpmc.network;
+
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * WebSocket连接初始化
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午1:19
+ */
+@RequiredArgsConstructor
+public class WebSocketChannelInitializer extends ChannelInitializer {
+ private final ChannelHandler handler;
+ @Override
+ protected void initChannel(SocketChannel socketChannel) throws Exception {
+ ChannelPipeline pipeline = socketChannel.pipeline();
+ pipeline.addLast(new HttpClientCodec());
+ pipeline.addLast(new ChunkedWriteHandler());
+ pipeline.addLast(new HttpObjectAggregator(64 * 1024));
+ pipeline.addLast(handler);
+ }
+}
diff --git a/src/main/java/cn/wzpmc/network/WebSocketConnectionHandler.java b/src/main/java/cn/wzpmc/network/WebSocketConnectionHandler.java
new file mode 100644
index 0000000..d96b026
--- /dev/null
+++ b/src/main/java/cn/wzpmc/network/WebSocketConnectionHandler.java
@@ -0,0 +1,49 @@
+package cn.wzpmc.network;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+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.extern.log4j.Log4j2;
+
+import java.net.URI;
+import java.util.concurrent.Future;
+
+/**
+ * 此类用于建立WebSocket连接
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/30 下午11:54
+ */
+@Log4j2
+public class WebSocketConnectionHandler {
+ private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
+ /**
+ * 建立连接
+ * @author wzp
+ * @since 2024/7/30 下午11:55 v0.0.1-dev
+ * @param websocket websocket连接地址
+ */
+ public ChannelFuture connect(URI websocket){
+ log.info("正在连接websocket");
+ Bootstrap bootstrap = new Bootstrap();
+ WebSocketClientHandshaker clientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(websocket, WebSocketVersion.V13, null, false, new DefaultHttpHeaders());
+ PacketHandler handler = new PacketHandler(clientHandshaker);
+ bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new WebSocketChannelInitializer(handler));
+ return bootstrap.connect(websocket.getHost(), websocket.getPort());
+ }
+
+ /**
+ * 强制结束通信
+ * @author wzp
+ * @since 2024/7/31 上午2:04 v0.0.1-dev
+ */
+ public void kill(){
+ this.eventLoopGroup.shutdownGracefully();
+ }
+}
diff --git a/src/main/java/cn/wzpmc/plugins/CommandManager.java b/src/main/java/cn/wzpmc/plugins/CommandManager.java
new file mode 100644
index 0000000..c69c067
--- /dev/null
+++ b/src/main/java/cn/wzpmc/plugins/CommandManager.java
@@ -0,0 +1,96 @@
+package cn.wzpmc.plugins;
+
+import cn.wzpmc.api.commands.BrigadierCommand;
+import cn.wzpmc.api.commands.RawCommand;
+import cn.wzpmc.api.plugins.ICommandManager;
+import cn.wzpmc.api.user.CommandSender;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestion;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.tree.LiteralCommandNode;
+import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.log4j.Log4j2;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * 指令管理器实现类
+ * @author wzp
+ * @version 0.0.1-dev
+ * @since 2024/7/31 上午3:13
+ */
+@Log4j2
+@NoArgsConstructor
+public class CommandManager implements ICommandManager {
+ private final CommandDispatcher dispatcher = new CommandDispatcher<>();
+ private final ConcurrentHashMap rawCommands = new ConcurrentHashMap<>();
+ @Override
+ public void registerCommand(RawCommand rawCommand, String name) {
+ if (rawCommands.containsKey(name)){
+ log.error("指令{}已经被注册,注册失败!", name);
+ return;
+ }
+ this.rawCommands.put(name, rawCommand);
+ }
+ @Override
+ public void registerCommand(BrigadierCommand brigadierCommand){
+ dispatcher.register(brigadierCommand.getCommandNode());
+ }
+ private static final class CommandPart {
+ private final String name;
+ private final List args;
+ public CommandPart(String rawCommandLine) {
+ List list = Arrays.asList(rawCommandLine.split(" "));
+ this.name = list.get(0);
+ this.args = list.subList(1, list.size());
+ }
+ }
+
+ /**
+ * 执行指令
+ * @author wzp
+ * @since 2024/7/31 上午3:35 v0.0.1-dev
+ * @param sender 发送者
+ * @param rawCommandLine 完整命令行
+ * @return 是否执行成功
+ */
+ public boolean execute(CommandSender sender, String rawCommandLine){
+ CommandPart commandPart = new CommandPart(rawCommandLine);
+ if (rawCommands.containsKey(commandPart.name)) {
+ return rawCommands.get(commandPart.name).onExecute(sender, commandPart.args);
+ }else {
+ try {
+ dispatcher.execute(rawCommandLine, sender);
+ } catch (CommandSyntaxException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * tab补全的结果
+ * @author wzp
+ * @since 2024/7/31 上午3:36 v0.0.1-dev
+ * @param sender 消息发送者
+ * @param rawCommandLine 完整命令行
+ * @return 所有被补全的指令
+ */
+ @SneakyThrows
+ public List tabComplete(CommandSender sender, String rawCommandLine){
+ CommandPart commandPart = new CommandPart(rawCommandLine);
+ List result = new ArrayList<>();
+ if (rawCommands.containsKey(commandPart.name)) {
+ result.addAll(rawCommands.get(commandPart.name).onTabComplete(sender, commandPart.args));
+ }
+ Suggestions suggestions = dispatcher.getCompletionSuggestions(dispatcher.parse(rawCommandLine, sender)).get();
+ result.addAll(suggestions.getList().stream().map(Suggestion::getText).collect(Collectors.toList()));
+ return result;
+ }
+}
diff --git a/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java b/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java
new file mode 100644
index 0000000..75cd5dd
--- /dev/null
+++ b/src/main/java/cn/wzpmc/utils/TemplateFileUtils.java
@@ -0,0 +1,43 @@
+package cn.wzpmc.utils;
+
+import lombok.extern.log4j.Log4j2;
+
+import java.io.*;
+
+/**
+ * 模板文件工具类
+ * @author wzp
+ * @since 2024/07/30 23:28:25
+ * @version 0.0.1-dev
+ */
+@Log4j2
+public class TemplateFileUtils {
+ /**
+ * 写入默认配置文件
+ * @param loader 读取默认配置所使用的类加载器
+ * @param path 默认配置的路径
+ * @param saved 保存到的文件
+ * @return 是否写入
+ */
+ public static boolean saveDefaultConfig(ClassLoader loader, String path, File saved){
+ log.debug("创建默认配置文件从ClassLoader {} -> {} ==> {}", loader, path, saved);
+ try(InputStream sourceInputStream = loader.getResourceAsStream(path)){
+ if (sourceInputStream == null){
+ throw new RuntimeException(new FileNotFoundException("Didn't find " + path + " from class loader: " + loader.getName()));
+ }
+ if (saved.exists()){
+ return false;
+ }
+ if (!saved.createNewFile()) {
+ throw new IOException("Cannot create file " + saved.getAbsolutePath());
+ }
+ try(FileOutputStream targetFileStream = new FileOutputStream(saved)){
+ sourceInputStream.transferTo(targetFileStream);
+ }
+ return true;
+ } catch (IOException e) {
+ log.throwing(e);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/cn/wzpmc/utils/YamlUtils.java b/src/main/java/cn/wzpmc/utils/YamlUtils.java
new file mode 100644
index 0000000..193f6aa
--- /dev/null
+++ b/src/main/java/cn/wzpmc/utils/YamlUtils.java
@@ -0,0 +1,50 @@
+package cn.wzpmc.utils;
+
+import lombok.extern.log4j.Log4j2;
+import org.yaml.snakeyaml.Yaml;
+
+import java.io.*;
+
+/**
+ * Yaml工具类
+ * @author wzp
+ * @since 2024/7/30 下午11:46
+ * @version 0.0.1-dev
+ */
+@Log4j2
+public class YamlUtils {
+ /**
+ * 读取Yaml文件并将其序列化为一个类
+ * @param file yaml文件
+ * @param clazz 需要序列化的类
+ * @return 一个对象
+ * @param 序列化的类型(需要空参构造方法)
+ */
+ public static T readYamlFile(File file, Class clazz){
+ log.debug("读取Yaml文件 {},并写入到类 {}", file, clazz);
+ try(FileInputStream fis = new FileInputStream(file)){
+ Yaml yaml = new Yaml();
+ return yaml.loadAs(fis, clazz);
+ } catch (IOException e) {
+ log.throwing(e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 将一个类序列化为Yaml文件
+ * @param file yaml文件
+ * @param obj 数据类
+ * @param 序列化的对象
+ */
+ public static void writeYamlFile(File file, T obj){
+ log.debug("将对象:{} 写入Yaml文件:{}中", obj, file);
+ try(FileWriter writer = new FileWriter(file)){
+ Yaml yaml = new Yaml();
+ yaml.dump(obj, writer);
+ } catch (IOException e) {
+ log.throwing(e);
+ throw new RuntimeException(e);
+ }
+ }
+}