Compare commits

...

7 Commits

16 changed files with 211 additions and 29 deletions

View File

@ -1 +1 @@
1.0.5 1.0.7

View File

@ -14,7 +14,7 @@ allprojects {
apply(plugin = "java-library") apply(plugin = "java-library")
val groupName by extra("cn.wzpmc") val groupName by extra("cn.wzpmc")
val projectArtifactId by extra("my-bot") val projectArtifactId by extra("my-bot")
val projectVersion by extra("1.0.6") val projectVersion by extra("1.0.7-SNAPSHOT")
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://libraries.minecraft.net") maven("https://libraries.minecraft.net")

View File

@ -8,12 +8,20 @@ package cn.wzpmc.plugins.configuration;
* @since 2024/7/31 上午3:42 * @since 2024/7/31 上午3:42
*/ */
public interface IConfiguration { public interface IConfiguration {
/** /**
* @return WebSocket连接URL
* @author wzp * @author wzp
* @since 2024/7/31 上午3:48 v0.0.1-dev * @since 2025/3/26 17:29 v1.0.7
* @return 网络相关配置
*/ */
String getWebsocket(); INetworkConfiguration getNetwork();
/**
*
* @author wzp
* @since 2025/2/4 13:44 v1.0.5
* @return 命令前缀
*/
String getCommandPrefix();
/** /**
* @return 通信验证 * @return 通信验证

View File

@ -0,0 +1,38 @@
package cn.wzpmc.plugins.configuration;
/**
* @author wzp
* @since 2025/3/26 17:26
* @version 1.0.7
*/
public interface INetworkConfiguration {
/**
* @return WebSocket连接URL
* @author wzp
* @since 2025/3/26 17:26 v1.0.7
*/
String getWebsocket();
/**
* @author wzp
* @since 2025/3/26 17:27 v1.0.7
* @return 是否启用连接重试
*/
Boolean isRetry();
/**
* @author wzp
* @since 2025/3/26 17:27 v1.0.7
* @return 重试最大次数-1为无限
*/
Integer getMaxRetryCount();
/**
* @author wzp
* @since 2025/3/26 17:28 v1.0.7
* @return 获取重试间隔单位毫秒
*/
Long getRetryInterval();
}

View File

@ -18,6 +18,7 @@ import cn.wzpmc.utils.JsonUtils;
import cn.wzpmc.utils.ReflectionUtils; import cn.wzpmc.utils.ReflectionUtils;
import cn.wzpmc.utils.TemplateFileUtils; import cn.wzpmc.utils.TemplateFileUtils;
import cn.wzpmc.utils.YamlUtils; import cn.wzpmc.utils.YamlUtils;
import com.alibaba.fastjson2.JSONObject;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
@ -52,6 +53,7 @@ public class Main {
} }
log.debug("读取配置文件 {}", configurationFile.getAbsolutePath()); log.debug("读取配置文件 {}", configurationFile.getAbsolutePath());
Configuration configuration = YamlUtils.readYamlFile(configurationFile, Configuration.class); Configuration configuration = YamlUtils.readYamlFile(configurationFile, Configuration.class);
JSONObject fullConfiguration = YamlUtils.readYamlFile(configurationFile, JSONObject.class);
Configuration defaultConfiguration = TemplateFileUtils.readDefaultConfig(classLoader, DEFAULT_CONFIGURATION_FILE_PATH, Configuration.class); Configuration defaultConfiguration = TemplateFileUtils.readDefaultConfig(classLoader, DEFAULT_CONFIGURATION_FILE_PATH, Configuration.class);
// 配置文件自动更新 start // 配置文件自动更新 start
boolean isChanged = false; boolean isChanged = false;
@ -73,11 +75,24 @@ public class Main {
isChanged = true; isChanged = true;
} }
// end // end
// 1.0.7 配置文件自动更新
if (configuration.getCommandPrefix() == null) {
configuration.setCommandPrefix(defaultConfiguration.getCommandPrefix());
isChanged = true;
}
if (configuration.getNetwork() == null) {
configuration.setNetwork(defaultConfiguration.getNetwork());
configuration.getNetwork().setWebsocket(fullConfiguration.getString("websocket"));
isChanged = true;
}
// end
if (isChanged) { if (isChanged) {
log.warn("已自动升级配置文件请检查config.yml是否有错误的地方有则请修改"); log.warn("已自动升级配置文件请检查config.yml是否有错误的地方有则请修改");
YamlUtils.writeYamlFile(configurationFile, configuration); YamlUtils.writeYamlFile(configurationFile, configuration);
} }
// 配置文件自动更新end // 配置文件自动更新end
return configuration; return configuration;
} }
@ -88,7 +103,7 @@ public class Main {
public static URI getUriFromConfiguration(Configuration configuration) { public static URI getUriFromConfiguration(Configuration configuration) {
URI uri; URI uri;
try { try {
uri = new URI(configuration.getWebsocket()); uri = new URI(configuration.getNetwork().getWebsocket());
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
return null; return null;
} }
@ -127,8 +142,8 @@ public class Main {
} }
public static WebSocketConnectionHandler createConnection(MyBot myBot, URI uri) { public static WebSocketConnectionHandler createConnection(MyBot myBot, URI uri) {
WebSocketConnectionHandler webSocketConnectionHandler = new WebSocketConnectionHandler(myBot); WebSocketConnectionHandler webSocketConnectionHandler = new WebSocketConnectionHandler(myBot, uri);
webSocketConnectionHandler.connect(uri); webSocketConnectionHandler.connect();
return webSocketConnectionHandler; return webSocketConnectionHandler;
} }

View File

@ -25,10 +25,12 @@ import java.util.concurrent.ConcurrentHashMap;
*/ */
public class HelpCommand implements BrigadierCommand { public class HelpCommand implements BrigadierCommand {
private final CommandManager commandManager; private final CommandManager commandManager;
private final String commandPrefix;
public HelpCommand() { public HelpCommand() {
IBot instance = MyBot.getInstance(); IBot instance = MyBot.getInstance();
this.commandManager = (CommandManager) instance.getCommandManager(); this.commandManager = (CommandManager) instance.getCommandManager();
this.commandPrefix = instance.getConfiguration().getCommandPrefix();
} }
private static void handlerNode(Collection<CommandNode<CommandSender>> node, int tabCount, StringBuilder builder) { private static void handlerNode(Collection<CommandNode<CommandSender>> node, int tabCount, StringBuilder builder) {
@ -54,7 +56,7 @@ public class HelpCommand implements BrigadierCommand {
CommandSender source = e.getSource(); CommandSender source = e.getSource();
for (CommandNode<CommandSender> child : children) { for (CommandNode<CommandSender> child : children) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append('/'); builder.append(commandPrefix);
builder.append(child.getUsageText()); builder.append(child.getUsageText());
builder.append('\n'); builder.append('\n');
handlerNode(child.getChildren(), 1, builder); handlerNode(child.getChildren(), 1, builder);
@ -63,7 +65,7 @@ public class HelpCommand implements BrigadierCommand {
} }
ConcurrentHashMap<String, RawCommand> rawCommands = this.commandManager.getRawCommands(); ConcurrentHashMap<String, RawCommand> rawCommands = this.commandManager.getRawCommands();
for (Map.Entry<String, RawCommand> stringRawCommandEntry : rawCommands.entrySet()) { for (Map.Entry<String, RawCommand> stringRawCommandEntry : rawCommands.entrySet()) {
source.sendMessage(StringMessage.text("/" + stringRawCommandEntry.getKey())); source.sendMessage(StringMessage.text(commandPrefix + stringRawCommandEntry.getKey()));
} }
return 0; return 0;
}). }).
@ -78,7 +80,7 @@ public class HelpCommand implements BrigadierCommand {
continue; continue;
} }
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append('/'); builder.append(commandPrefix);
builder.append(child.getUsageText()); builder.append(child.getUsageText());
builder.append('\n'); builder.append('\n');
handlerNode(child.getChildren(), 1, builder); handlerNode(child.getChildren(), 1, builder);
@ -87,7 +89,7 @@ public class HelpCommand implements BrigadierCommand {
} }
ConcurrentHashMap<String, RawCommand> rawCommands = this.commandManager.getRawCommands(); ConcurrentHashMap<String, RawCommand> rawCommands = this.commandManager.getRawCommands();
for (Map.Entry<String, RawCommand> stringRawCommandEntry : rawCommands.entrySet()) { for (Map.Entry<String, RawCommand> stringRawCommandEntry : rawCommands.entrySet()) {
source.sendMessage(StringMessage.text("/" + stringRawCommandEntry.getKey())); source.sendMessage(StringMessage.text(commandPrefix + stringRawCommandEntry.getKey()));
} }
return 0; return 0;
}) })

View File

@ -26,12 +26,14 @@ public class CommandEventHandler {
public void onGroupMessage(GroupMessageEvent event) { public void onGroupMessage(GroupMessageEvent event) {
GroupCommandSender groupCommandSender = GroupCommandSender.of(event); GroupCommandSender groupCommandSender = GroupCommandSender.of(event);
IBot instance = MyBot.getInstance(); IBot instance = MyBot.getInstance();
String commandPrefix = instance.getConfiguration().getCommandPrefix();
Long id = instance.getId(); Long id = instance.getId();
String message = event.getRawMessage().getMessage(); String message = event.getRawMessage().getMessage();
Pattern compile = Pattern.compile("\\[CQ:at,qq=" + id + ".*?]\\s*?/.*"); String quotedPrefix = Pattern.quote(commandPrefix);
Pattern compile = Pattern.compile("\\[CQ:at,qq=" + id + ".*?]\\s*?" + quotedPrefix + ".*");
if (compile.asMatchPredicate().test(message)) { if (compile.asMatchPredicate().test(message)) {
CommandManager commandManager = (CommandManager) instance.getCommandManager(); CommandManager commandManager = (CommandManager) instance.getCommandManager();
String commandRaw = message.replaceFirst("\\[CQ:at,qq=[0-9]{10}.*?]\\s*?/", ""); String commandRaw = message.replaceFirst("\\[CQ:at,qq=[0-9]{10}.*?]\\s*?" + quotedPrefix, "");
log.info("群{}中的用户{}使用了指令{}", groupCommandSender.getGroupId(), groupCommandSender.getId(), commandRaw); log.info("群{}中的用户{}使用了指令{}", groupCommandSender.getGroupId(), groupCommandSender.getId(), commandRaw);
commandManager.execute(groupCommandSender, commandRaw); commandManager.execute(groupCommandSender, commandRaw);
} }
@ -43,9 +45,10 @@ public class CommandEventHandler {
IBot instance = MyBot.getInstance(); IBot instance = MyBot.getInstance();
StringMessage rawMessage = event.getRawMessage(); StringMessage rawMessage = event.getRawMessage();
String message = rawMessage.getMessage(); String message = rawMessage.getMessage();
if (message.startsWith("/")) { String commandPrefix = instance.getConfiguration().getCommandPrefix();
if (message.startsWith(commandPrefix)) {
CommandManager commandManager = (CommandManager) instance.getCommandManager(); CommandManager commandManager = (CommandManager) instance.getCommandManager();
String commandRaw = message.replaceFirst("/", ""); String commandRaw = message.replaceFirst(commandPrefix, "");
log.info("用户{}使用了指令{}", sender.getId(), commandRaw); log.info("用户{}使用了指令{}", sender.getId(), commandRaw);
commandManager.execute(sender, commandRaw); commandManager.execute(sender, commandRaw);
} }

View File

@ -12,12 +12,19 @@ import lombok.Data;
*/ */
@Data @Data
public class Configuration implements IConfiguration { public class Configuration implements IConfiguration {
/** /**
* WebSocket连接URL * 网络相关配置
* * @since 2025/3/26 17:32 v1.0.7
* @since 2024/7/30 下午11:48 v0.0.1-dev
*/ */
private String websocket; private NetworkConfiguration network;
/**
* 命令前缀
* @since 2025/2/4 13:45 v1.0.5
*/
private String commandPrefix;
/** /**
* 通信验证 * 通信验证
* *

View File

@ -0,0 +1,38 @@
package cn.wzpmc.configuration;
import cn.wzpmc.plugins.configuration.INetworkConfiguration;
import lombok.Data;
/**
* @author wzp
* @since 2025/3/26 17:29
* @version 1.0.7
*/
@Data
public class NetworkConfiguration implements INetworkConfiguration {
/**
* WebSocket连接URL
* @since 2025/3/26 17:31 v1.0.7
*/
private String websocket;
/**
* 是否启用连接重试
* @since 2025/3/26 17:31 v1.0.7
*/
private Boolean retry;
/**
* 重试最大次数-1为无限
* @since 2025/3/26 17:31 v1.0.7
*/
private Integer maxRetryCount;
/**
* 获取重试间隔单位毫秒
* @since 2025/3/26 17:31 v1.0.7
*/
private Long retryInterval;
@Override
public Boolean isRetry() {
return this.retry;
}
}

View File

@ -1,8 +1,10 @@
package cn.wzpmc.console; package cn.wzpmc.console;
import cn.wzpmc.entities.api.ApiResponseRequired;
import cn.wzpmc.entities.user.bot.MyBot; import cn.wzpmc.entities.user.bot.MyBot;
import cn.wzpmc.network.WebSocketConnectionHandler; import cn.wzpmc.network.WebSocketConnectionHandler;
import cn.wzpmc.plugins.CommandManager; import cn.wzpmc.plugins.CommandManager;
import cn.wzpmc.utils.json.action.ActionReader;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
@ -47,12 +49,16 @@ public class MyBotConsole extends SimpleTerminalConsole {
@Override @Override
public void shutdown() { public void shutdown() {
this.webSocketConnectionHandler.kill(); this.webSocketConnectionHandler.kill();
for (ApiResponseRequired<?, ?> value : ActionReader.tasks.values()) {
value.getFuture().complete(null);
}
running = false; running = false;
} }
@Override @Override
public void start() { public void start() {
this.bot.setConsole(this); this.bot.setConsole(this);
if (this.bot.isShutdown()) return;
super.start(); super.start();
} }
} }

View File

@ -50,6 +50,8 @@ public class MyBot extends IBot {
private final Configuration configuration; private final Configuration configuration;
private final CommandManager commandManager = new CommandManager(this); private final CommandManager commandManager = new CommandManager(this);
private final PluginManager pluginManager = new PluginManager(); private final PluginManager pluginManager = new PluginManager();
@Setter
private boolean shutdown = false;
private final IncreasbleHashMap<Class<? extends Event>, EventHandlerMethod> events = new IncreasbleHashMap<>(); private final IncreasbleHashMap<Class<? extends Event>, EventHandlerMethod> events = new IncreasbleHashMap<>();
@Getter @Getter
private final Ops ops; private final Ops ops;

View File

@ -37,7 +37,6 @@ public class HandshakePacketHandler extends SimpleChannelInboundHandler<FullHttp
handshaker.finishHandshake(channelHandlerContext.channel(), fullHttpResponse); handshaker.finishHandshake(channelHandlerContext.channel(), fullHttpResponse);
this.handshakeFuture.complete(true); this.handshakeFuture.complete(true);
log.debug("握手成功"); log.debug("握手成功");
log.info("连接服务器成功!");
} }
} }
} }

View File

@ -32,6 +32,8 @@ import java.util.concurrent.Executors;
public class PacketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { public class PacketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private final IBot bot; private final IBot bot;
private final ExecutorService threadPool = Executors.newFixedThreadPool(4); private final ExecutorService threadPool = Executors.newFixedThreadPool(4);
private final Runnable retryFunction;
@Override @Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame webSocketFrame) { protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame webSocketFrame) {
@ -105,4 +107,10 @@ public class PacketHandler extends SimpleChannelInboundHandler<TextWebSocketFram
public <REQUEST, RESPONSE> void registerResponse(UUID echo, CompletableFuture<ActionResponse<RESPONSE>> responsePromise, Action<REQUEST, RESPONSE> request) { public <REQUEST, RESPONSE> void registerResponse(UUID echo, CompletableFuture<ActionResponse<RESPONSE>> responsePromise, Action<REQUEST, RESPONSE> request) {
ActionReader.tasks.put(echo, new ApiResponseRequired<>(responsePromise, request)); ActionReader.tasks.put(echo, new ApiResponseRequired<>(responsePromise, request));
} }
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.info("与服务器断开连接!");
retryFunction.run();
}
} }

View File

@ -2,7 +2,11 @@ package cn.wzpmc.network;
import cn.wzpmc.api.Action; import cn.wzpmc.api.Action;
import cn.wzpmc.api.ActionResponse; import cn.wzpmc.api.ActionResponse;
import cn.wzpmc.user.IBot; import cn.wzpmc.console.MyBotConsole;
import cn.wzpmc.entities.api.ApiResponseRequired;
import cn.wzpmc.entities.user.bot.MyBot;
import cn.wzpmc.plugins.configuration.INetworkConfiguration;
import cn.wzpmc.utils.json.action.ActionReader;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
@ -32,26 +36,70 @@ import java.util.concurrent.ExecutionException;
@RequiredArgsConstructor @RequiredArgsConstructor
public class WebSocketConnectionHandler { public class WebSocketConnectionHandler {
private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
private final IBot bot; private final MyBot bot;
/**
* websocket连接地址
* @since 2025/3/26 17:56 v1.0.7
*/
private final URI websocket;
private ChannelFuture channelFuture; private ChannelFuture channelFuture;
private PacketHandler packetHandler; private PacketHandler packetHandler;
private HandshakePacketHandler handshakePacketHandler; private HandshakePacketHandler handshakePacketHandler;
private int currentRetryCount = 0;
private void tryReconnect() {
INetworkConfiguration network = bot.getConfiguration().getNetwork();
if (!network.isRetry()) {
this.quit();
return;
}
Integer maxRetryCount = network.getMaxRetryCount();
if (maxRetryCount != 0 && currentRetryCount >= maxRetryCount) {
this.quit();
return;
}
this.currentRetryCount++;
log.info("尝试重连第{}次", currentRetryCount);
this.connect();
}
private void quit() {
for (ApiResponseRequired<?, ?> value : ActionReader.tasks.values()) {
value.getFuture().obtrudeException(new InterruptedException());
}
this.handshakePacketHandler.getHandshakeFuture().obtrudeException(new InterruptedException());
MyBotConsole console = bot.getConsole();
bot.setShutdown(true);
if (console == null) {
this.eventLoopGroup.shutdownGracefully();
return;
}
console.shutdown();
}
/** /**
* 建立连接 * 建立连接
*
* @param websocket websocket连接地址
* @author wzp * @author wzp
* @since 2024/7/30 下午11:55 v0.0.1-dev * @since 2024/7/30 下午11:55 v0.0.1-dev
*/ */
public void connect(URI websocket) { public void connect() {
log.info("正在连接websocket"); log.info("正在连接websocket");
Bootstrap bootstrap = new Bootstrap(); Bootstrap bootstrap = new Bootstrap();
WebSocketClientHandshaker clientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(websocket, WebSocketVersion.V13, null, false, new DefaultHttpHeaders(), 65536 * 100); WebSocketClientHandshaker clientHandshaker = WebSocketClientHandshakerFactory.newHandshaker(websocket, WebSocketVersion.V13, null, false, new DefaultHttpHeaders(), 65536 * 100);
this.handshakePacketHandler = new HandshakePacketHandler(clientHandshaker); this.handshakePacketHandler = new HandshakePacketHandler(clientHandshaker);
this.packetHandler = new PacketHandler(this.bot); this.packetHandler = new PacketHandler(this.bot, this::tryReconnect);
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new WebSocketChannelInitializer(this.packetHandler, this.handshakePacketHandler)); bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new WebSocketChannelInitializer(this.packetHandler, this.handshakePacketHandler));
this.channelFuture = bootstrap.connect(websocket.getHost(), websocket.getPort()); this.channelFuture = bootstrap.connect(websocket.getHost(), websocket.getPort());
this.channelFuture.addListener(future -> {
if (!future.isSuccess()) {
log.info("连接失败!");
this.tryReconnect();
} else {
log.info("连接成功!");
this.currentRetryCount = 0;
}
});
} }
/** /**
@ -81,6 +129,7 @@ public class WebSocketConnectionHandler {
this.handshakePacketHandler.getHandshakeFuture().get(); this.handshakePacketHandler.getHandshakeFuture().get();
} catch (ExecutionException e) { } catch (ExecutionException e) {
log.error(e); log.error(e);
return null;
} }
CompletableFuture<ActionResponse<RESPONSE>> responsePromise = new CompletableFuture<>(); CompletableFuture<ActionResponse<RESPONSE>> responsePromise = new CompletableFuture<>();
packetHandler.registerResponse(request.getEcho(), responsePromise, request); packetHandler.registerResponse(request.getEcho(), responsePromise, request);

View File

@ -1,6 +1,7 @@
package cn.wzpmc.utils; package cn.wzpmc.utils;
import cn.wzpmc.configuration.Configuration; import cn.wzpmc.configuration.Configuration;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
@ -30,7 +31,8 @@ public class YamlUtils {
*/ */
public static <T> T readYamlStream(InputStream is, Class<T> clazz) { public static <T> T readYamlStream(InputStream is, Class<T> clazz) {
Yaml yaml = new Yaml(); Yaml yaml = new Yaml();
return yaml.loadAs(is, clazz); JSONObject json = yaml.loadAs(is, JSONObject.class);
return json.to(clazz);
} }
/** /**

View File

@ -1,4 +1,9 @@
websocket: "<Your WebSocket connection link, e.g: ws://127.0.0.1:3001/>" network:
websocket: "<Your WebSocket connection link, e.g: ws://127.0.0.1:3001/>"
retry: true
maxRetryCount: 3
retryInterval: 10000
commandPrefix: "/"
authorization: authorization:
enable: false enable: false
token: "<If you enable authorization, you should fill in this>" token: "<If you enable authorization, you should fill in this>"