diff --git a/src/main/java/top/jie65535/minionebot/Main.java b/src/main/java/top/jie65535/minionebot/Main.java index e4bb3e3..707d81a 100644 --- a/src/main/java/top/jie65535/minionebot/Main.java +++ b/src/main/java/top/jie65535/minionebot/Main.java @@ -8,25 +8,6 @@ import java.util.Scanner; public class Main { public static void main(String[] args) { -// System.out.println("Hello world!"); -// -// // {"group_id":685816675,"raw_message":"Go","sender":{"area":"","role":"member","level":"108","user_id":840465812,"sex":"unknown","nickname":"筱傑","title":"加油啊5号马","age":22,"card":""},"sub_type":"normal","user_id":840465812,"self_id":1379892815,"message_id":-518394285,"message_type":"group","post_type":"message","time":1677306537,"message":"Go","font":0} -// var gson = new Gson(); -// var json = "{\"type\":\"message\",\"data\":{\"type\":\"text\",\"data\":\"Hello world!\"}}"; -// var obj = gson.fromJson(json, JsonObject.class); -// System.out.println(obj.get("type").getAsString()); -// var data = obj.get("data").getAsJsonObject(); -// System.out.println(data.get("type").getAsString()); -// System.out.println(data.get("data").getAsString()); -// -// var map = gson.fromJson(json, Map.class); -// System.out.println(map); -// System.out.println(Objects.equals(map.get("type"), "message")); -// var dataMap = (Map) map.get("data"); -// System.out.println(Objects.equals(dataMap.get("type"), "text")); -// System.out.println((String)dataMap.get("data")); - - Javalin app = Javalin.create().start(7070); var bot = new MiniOneBot(app, "HelloOneBot"); bot.startWsServer("/openchat"); @@ -35,17 +16,21 @@ public class Main { } catch (URISyntaxException ignored) { System.out.println("Uri Syntax error!"); } - bot.subscribeGroupMessageEvent(event -> System.out.println(event.message)); - bot.subscribeGuildChannelMessageEvent(event -> System.out.println(event.message)); + bot.subscribeGroupMessageEvent(event -> System.out.printf("[QQ]<%s> %s%n", event.senderCardOrNickname(), event.message())); + bot.subscribeGuildChannelMessageEvent(event -> System.out.printf("[Cl]<%s> %s%n", event.senderName(), event.message())); var scanner = new Scanner(System.in); while (true) { var input = scanner.nextLine(); - if (input.isEmpty() || input.equals("exit") || input.equals("quit")) { + if (input.isEmpty() || input.equals("stop") || input.equals("exit") || input.equals("quit")) { break; } - bot.sendMessage("Console", input); + bot.sendGroupMessage(0, "[MOB] Console: " + input); } + System.out.println("Ending..."); + bot.stop(); + app.stop(); + System.out.println("Ended..."); } } \ No newline at end of file diff --git a/src/main/java/top/jie65535/minionebot/MiniOneBot.java b/src/main/java/top/jie65535/minionebot/MiniOneBot.java index 5941e6a..9dd30f6 100644 --- a/src/main/java/top/jie65535/minionebot/MiniOneBot.java +++ b/src/main/java/top/jie65535/minionebot/MiniOneBot.java @@ -1,12 +1,19 @@ package top.jie65535.minionebot; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import io.javalin.Javalin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import top.jie65535.minionebot.events.GroupMessage; import top.jie65535.minionebot.events.GroupMessageHandler; +import top.jie65535.minionebot.events.GuildChannelMessage; import top.jie65535.minionebot.events.GuildChannelMessageHandler; +import java.io.IOException; import java.net.URI; +import java.util.Objects; +import java.util.regex.Pattern; public class MiniOneBot implements WsStream.WsMessageHandler { private final Logger logger = LoggerFactory.getLogger("Console"); @@ -14,11 +21,11 @@ public class MiniOneBot implements WsStream.WsMessageHandler { private final String token; private MiniOneBotWsServer server; private MiniOneBotWsClient client; + private final Gson gson = new Gson(); public MiniOneBot(Javalin javalin, String token) { this.javalin = javalin; this.token = token; - logger.info("Test"); } // region WebSocket @@ -39,30 +46,178 @@ public class MiniOneBot implements WsStream.WsMessageHandler { } } + public void stop() { + if (client != null) { + client.close(); + } + if (server != null) { + try { + server.close(); + } catch (IOException e) { + logger.error("Stop MiniOneBot WebSocket Server Failed!", e); + } + } + } + private void sendMessageToAll(String message) { + logger.info("Sending... message=\"{}\"", message); server.send(message); client.send(message); } + // endregion + + // region OneBot message + @Override public void onMessage(String message) { - // TODO + var map = gson.fromJson(message, JsonObject.class); + if (!map.has("post_type")) return; + var postType = map.get("post_type").getAsString(); + // 消息事件上报 + if (Objects.equals(postType, "message")) { + var messageType = map.get("message_type").getAsString(); + var subType = map.get("sub_type").getAsString(); + + // 发送者信息 https://docs.go-cqhttp.org/reference/data_struct.html#post-message-messagesender + var sender = map.get("sender").getAsJsonObject(); + var senderId = sender.get("user_id").getAsLong(); + var senderNickname = sender.get("nickname").getAsString(); + + // 群消息上报 https://docs.go-cqhttp.org/event/#%E7%BE%A4%E6%B6%88%E6%81%AF + if (Objects.equals(messageType, "group") + && Objects.equals(subType, "normal")) { + var groupId = map.get("group_id").getAsLong(); +// var message = (List>)map.get("message"); + var rawMessage = map.get("raw_message").getAsString(); + + var senderCard = sender.get("card").getAsString(); + var senderLevel = sender.get("level").getAsString(); + var senderRole = sender.get("role").getAsString(); + var senderCardOrNickname = senderCard == null || senderCard.isEmpty() ? senderNickname : senderCard; + var senderTitle = sender.get("title").getAsString(); + onGroupMessage(groupId, handleRawMessage(rawMessage), senderId, senderCardOrNickname, senderLevel, senderRole, senderTitle); + } + // 频道消息上报 https://docs.go-cqhttp.org/event/guild.html#%E6%94%B6%E5%88%B0%E9%A2%91%E9%81%93%E6%B6%88%E6%81%AF + else if (Objects.equals(messageType, "guild") + && Objects.equals(subType, "channel")) { + var guildId = map.get("guild_id").getAsString(); + var channelId = map.get("channel_id").getAsString(); + var rawMessage = map.get("message").getAsString(); // 需要 Message 消息链类型处理 + var tinyId = map.get("user_id").getAsString(); + onGuildMessage(guildId, channelId, handleRawMessage(rawMessage), tinyId, senderNickname); + } + } + } + + /** + * 当收到群消息时触发 + * @param groupId 群号 + * @param message 消息 + * @param senderId 发送者ID + * @param senderCardOrNickname 发送者群名片或昵称,名片为空时是昵称 + * @param senderLevel 发送者群等级 + * @param senderRole 发送者群身份 + * @param senderTitle 发送者群专属头衔 + */ + private void onGroupMessage(long groupId, + String message, + long senderId, + String senderCardOrNickname, + String senderLevel, + String senderRole, + String senderTitle) { + logger.info("groupId={}, message={}, senderId={}, senderCardOrNickname={}, senderLevel={}, senderRole={}, senderTitle={}", + groupId, message, senderId, senderCardOrNickname, senderLevel, senderRole, senderTitle); + groupMessageHandler.handleGroupMessage(new GroupMessage(groupId, message, senderId, senderCardOrNickname, senderLevel, senderRole, senderTitle)); + } + + /** + * 当收到频道消息时触发 + * @param guildId 频道Id + * @param channelId 子频道Id + * @param message 消息 + * @param senderId 发送者Id + * @param senderName 发送者昵称 + */ + private void onGuildMessage(String guildId, String channelId, String message, String senderId, String senderName) { + logger.info("guildId={}, channelId={}, message={}, senderId={}, senderNickname={}", + guildId, channelId, message, senderId, senderName); + guildChannelMessageHandler.handleGuildChannelMessage(new GuildChannelMessage(guildId, channelId, message, senderId, senderName)); } // endregion // region Message API - public void sendMessage(String sender, String message) { - // TODO + // region Models + private static class Action { + public String action; + public Object params; + public Action(String action, Object params) { + this.action = action; + this.params = params; + } + } + + private static class SendGroupMsgArgs { + public long group_id; + public String message; + public boolean auto_escape = true; + public SendGroupMsgArgs(long groupId, String message) { + this.group_id = groupId; + this.message = message; + } + } + + private static class SendGuildChannelMsgArgs { + public String guild_id; + public String channel_id; + public String message; + public boolean auto_escape = true; + public SendGuildChannelMsgArgs(String guildId, String channelId, String message) { + this.guild_id = guildId; + this.channel_id = channelId; + this.message = message; + } + } + // endregion + + /** + * 发送消息到群 + * @param groupId 群号 + * @param message 消息 + */ + public void sendGroupMessage(long groupId, String message) { + sendMessageToAll(gson.toJson(new Action("send_group_msg", new SendGroupMsgArgs(groupId, message)))); + } + + /** + * 发送消息到子频道 + * @param guildId 频道ID + * @param channelId 子频道ID + * @param message 消息 + */ + public void sendGuildChannelMessage(String guildId, String channelId, String message) { + sendMessageToAll(gson.toJson(new Action("send_guild_channel_msg", new SendGuildChannelMsgArgs(guildId, channelId, message)))); } GroupMessageHandler groupMessageHandler; + + GuildChannelMessageHandler guildChannelMessageHandler; + + /** + * 订阅群消息事件 + * @param handler 群消息处理器 + */ public void subscribeGroupMessageEvent(GroupMessageHandler handler) { groupMessageHandler = handler; } - GuildChannelMessageHandler guildChannelMessageHandler; + /** + * 订阅频道消息事件 + * @param handler 频道消息处理器 + */ public void subscribeGuildChannelMessageEvent(GuildChannelMessageHandler handler) { guildChannelMessageHandler = handler; } @@ -71,6 +226,33 @@ public class MiniOneBot implements WsStream.WsMessageHandler { // region Utils + private static final Pattern cqCodePattern = Pattern.compile("\\[CQ:(\\w+).*?]"); + + private static String handleRawMessage(String rawMessage) { + if (rawMessage.indexOf('[') == -1) + return unescape(rawMessage); + var message = new StringBuilder(); + var matcher = cqCodePattern.matcher(rawMessage); + while (matcher.find()) { + var type = matcher.group(1); + var replacement = switch (type) { + case "image" -> "[图片]"; + case "reply" -> "[回复]"; + case "at" -> "[@]"; + case "record" -> "[语音]"; + case "forward" -> "[合并转发]"; + case "video" -> "[视频]"; + case "music" -> "[音乐]"; + case "redbag" -> "[红包]"; + case "poke" -> "[戳一戳]"; + default -> ""; + }; + matcher.appendReplacement(message, replacement); + } + matcher.appendTail(message); + return unescape(message.toString()); + } + private static String escape(String msg) { return msg.replace("&", "&") .replace("[", "[") diff --git a/src/main/java/top/jie65535/minionebot/MiniOneBotWsServer.java b/src/main/java/top/jie65535/minionebot/MiniOneBotWsServer.java index 4187220..866792a 100644 --- a/src/main/java/top/jie65535/minionebot/MiniOneBotWsServer.java +++ b/src/main/java/top/jie65535/minionebot/MiniOneBotWsServer.java @@ -5,10 +5,12 @@ import io.javalin.websocket.*; import org.eclipse.jetty.websocket.api.CloseStatus; import org.slf4j.Logger; +import java.io.Closeable; +import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -public class MiniOneBotWsServer implements WsStream { +public class MiniOneBotWsServer implements WsStream, Closeable { private final String token; private final Logger logger; @@ -70,31 +72,6 @@ public class MiniOneBotWsServer implements WsStream { logger.debug("onMessage: {}", ctx.message()); callback.onMessage(ctx.message()); -// var map = JsonUtils.decode(ctx.message(), Map.class); -// // 消息事件上报 -// if (Objects.equals(map.get("post_type"), "message")) { -// // 群消息上报 https://docs.go-cqhttp.org/event/#%E7%BE%A4%E6%B6%88%E6%81%AF -// if (Objects.equals(map.get("message_type"), "group") -// && Objects.equals(map.get("sub_type"), "normal")) { -// // 检查群号 -// var groupId = (Long)map.get("group_id"); -// if (Objects.equals(config.groupId, groupId)) { -//// var message = (List>)map.get("message"); -// var rawMessage = map.get("raw_message").toString(); -// -// // 发送者信息 https://docs.go-cqhttp.org/reference/data_struct.html#post-message-messagesender -// var sender = (Map) map.get("sender"); -// var senderId = sender.get("user_id").toString(); -// var senderNickname = sender.get("nickname").toString(); -// var senderCard = sender.get("card").toString(); -// chatSystem.broadcastChatMessage(config.groupToGameFormat -// .replace("{card}", senderCard) -// .replace("{id}", senderId) -// .replace("{nickname}", senderNickname) -// .replace("{message}", rawMessage)); -// } -// } -// } } private WsMessageHandler callback; @@ -113,4 +90,15 @@ public class MiniOneBotWsServer implements WsStream { } } } + + @Override + public void close() throws IOException { + if (connections.isEmpty()) return; + for (var ctx : connections.keySet()) { + if (ctx.session.isOpen()) { + ctx.session.close(1001, "Service stopped"); + } + } + connections.clear(); + } } diff --git a/src/main/java/top/jie65535/minionebot/events/GroupMessage.java b/src/main/java/top/jie65535/minionebot/events/GroupMessage.java index 4e37f35..a1f8969 100644 --- a/src/main/java/top/jie65535/minionebot/events/GroupMessage.java +++ b/src/main/java/top/jie65535/minionebot/events/GroupMessage.java @@ -1,5 +1,12 @@ package top.jie65535.minionebot.events; -public class GroupMessage { - public String message; +public record GroupMessage( + long groupId, + String message, + long senderId, + String senderCardOrNickname, + String senderLevel, + String senderRole, + String senderTitle +) { } diff --git a/src/main/java/top/jie65535/minionebot/events/GuildChannelMessage.java b/src/main/java/top/jie65535/minionebot/events/GuildChannelMessage.java index 02d4800..861a558 100644 --- a/src/main/java/top/jie65535/minionebot/events/GuildChannelMessage.java +++ b/src/main/java/top/jie65535/minionebot/events/GuildChannelMessage.java @@ -1,5 +1,10 @@ package top.jie65535.minionebot.events; -public class GuildChannelMessage { - public String message; +public record GuildChannelMessage( + String guildId, + String channelId, + String message, + String senderId, + String senderName +) { }