diff --git a/.gitignore b/.gitignore index 34bb80f..b03c9e2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,6 @@ log/ .DS_Store # others -*.jar *.war *.zip *.tar diff --git a/lib/Residence5.1.0.1.jar b/LinearBot-bukkit/lib/Residence5.1.0.1.jar similarity index 100% rename from lib/Residence5.1.0.1.jar rename to LinearBot-bukkit/lib/Residence5.1.0.1.jar diff --git a/LinearBot-bukkit/pom.xml b/LinearBot-bukkit/pom.xml new file mode 100644 index 0000000..a960c3d --- /dev/null +++ b/LinearBot-bukkit/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + org.linear + LinearBot + 1.2.1 + + + LinearBot-bukkit + + + 11 + 11 + UTF-8 + + + + + jitpack.io + https://jitpack.io + + + placeholderapi + https://repo.extendedclip.com/content/repositories/placeholderapi/ + + + purpur + https://repo.purpurmc.org/snapshots + + + codemc-repo + https://repo.codemc.io/repository/maven-public/ + + + quickshop-repo + https://repo.codemc.io/repository/maven-public/ + + + griefdefender + https://repo.glaremasters.me/repository/bloodshot + + + + + + + org.purpurmc.purpur + purpur-api + 1.16.5-R0.1-SNAPSHOT + provided + + + me.clip + placeholderapi + 2.10.3 + provided + + + fr.xephi + authme + 5.6.0-SNAPSHOT + provided + + + org.maxgamer + QuickShop + 5.1.1.2 + provided + + + com.ghostchu + quickshop-bukkit + 3.6.1.5 + provided + shaded + + + com.griefdefender + api + 2.1.0-SNAPSHOT + provided + + + com.bekvon.bukkit.residence + Residence + 5.1.0.1 + system + ${project.basedir}/lib/Residence5.1.0.1.jar + + + + + + + diff --git a/src/main/java/org/linear/linearbot/LinearBot.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/LinearBot.java similarity index 95% rename from src/main/java/org/linear/linearbot/LinearBot.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/LinearBot.java index 18278e2..513c7f9 100644 --- a/src/main/java/org/linear/linearbot/LinearBot.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/LinearBot.java @@ -12,6 +12,7 @@ import org.linear.linearbot.event.server.QsHikariChatEvent; import org.linear.linearbot.event.server.ServerEvent; import org.linear.linearbot.hook.AuthMeHook; +import org.linear.linearbot.hook.GriefDefenderHook; import org.linear.linearbot.hook.QuickShopHook; import org.linear.linearbot.hook.ResidenceHook; import org.linear.linearbot.metrics.Metrics; @@ -34,6 +35,7 @@ public void onEnable() { AuthMeHook.hookAuthme(); ResidenceHook.hookRes(); QuickShopHook.hookQuickShop(); + GriefDefenderHook.hookGriefDefender(); getLogger().info("关联插件连接完毕"); Bukkit.getPluginManager().registerEvents(new ServerEvent(), this); if (QuickShopHook.hasQs) Bukkit.getPluginManager().registerEvents(new QsChatEvent(),this); @@ -62,7 +64,7 @@ public void onDisable() { getLogger().info("LinearBot已关闭"); List groups = Config.getGroupQQs(); for (long groupID : groups) { - Bot.sendMsg("插件已关闭", groupID); + Bot.sendMsg("LinearBot已关闭", groupID); } } diff --git a/src/main/java/org/linear/linearbot/bot/Bot.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/bot/Bot.java similarity index 96% rename from src/main/java/org/linear/linearbot/bot/Bot.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/bot/Bot.java index 2e08215..5a6f512 100644 --- a/src/main/java/org/linear/linearbot/bot/Bot.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/bot/Bot.java @@ -1,10 +1,10 @@ -package org.linear.linearbot.bot; - -import org.linear.linearbot.config.Config; -import me.dreamvoid.miraimc.api.MiraiBot; - -public class Bot { - public static void sendMsg(String msg,long groupID){ - MiraiBot.getBot(Config.getBotQQ()).getGroup(groupID).sendMessageMirai(msg); - } -} +package org.linear.linearbot.bot; + +import org.linear.linearbot.config.Config; +import me.dreamvoid.miraimc.api.MiraiBot; + +public class Bot { + public static void sendMsg(String msg,long groupID){ + MiraiBot.getBot(Config.getBotQQ()).getGroup(groupID).sendMessageMirai(msg); + } +} diff --git a/src/main/java/org/linear/linearbot/command/Commands.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/command/Commands.java similarity index 97% rename from src/main/java/org/linear/linearbot/command/Commands.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/command/Commands.java index 46fd54d..c59d839 100644 --- a/src/main/java/org/linear/linearbot/command/Commands.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/command/Commands.java @@ -1,70 +1,70 @@ -package org.linear.linearbot.command; - -import org.linear.linearbot.config.Config; - -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; - -public class Commands implements CommandExecutor{ - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - - if (args.length == 0) { - sender.sendMessage("请使用/lb help查看命令使用方法"); - return true; - } - - if(args.length != 0) { - switch (args[0]) { - /* - case "bind": - if (args.length != 2) return true; - if (!(sender instanceof Player)) { - sender.sendMessage(ConfigManager.getMessage_Server("ArgsError")); - return true; - } - Player player = (Player) sender; - try { - long qq = Long.parseLong(args[1]); - if (ConfigManager.getBindUser().containsKey(qq)) { - player.sendMessage(ConfigManager.getMessage_Server("QQReapt")); - return true; - } - player.sendMessage(ConfigManager.getMessage_Server("ChangeBind") - .replaceAll("%qq%", String.valueOf(qq))); - for (long group : ConfigManager.getGroups()) { - Bot.getMiraiBot() - .getGroup(group) - .sendMessage(ConfigManager.getMessage_QQ("ChangeBind") - .replaceAll("%qq%", String.valueOf(qq)) - .replaceAll("%player%", player.getName())); - } - PlayerData.changeBind(player.getName(), qq); - return true; - } catch (Exception ex) { - ex.printStackTrace(); - player.sendMessage(ConfigManager.getMessage_Server("ArgsError")); - } - break;*/ - case "reload": - if (args.length != 1) return true; - Config.loadConfig(); - sender.sendMessage("LinearBot配置文件已重载"); - break; - case "help": - if (args.length != 1) return true; - sender.sendMessage("§6LinearBot 机器人帮助菜单"); - sender.sendMessage("§6/lb reload :§f重载插件"); - sender.sendMessage("§6/lb help :§f获取插件帮助"); - break; - default: - if (args.length != 1) return true; - sender.sendMessage("错误的指令用法,请使用/lb help查看命令使用方法"); - break; - } - } - return true; - } -} +package org.linear.linearbot.command; + +import org.linear.linearbot.config.Config; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +public class Commands implements CommandExecutor{ + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + + if (args.length == 0) { + sender.sendMessage("请使用/lb help查看命令使用方法"); + return true; + } + + if(args.length != 0) { + switch (args[0]) { + /* + case "bind": + if (args.length != 2) return true; + if (!(sender instanceof Player)) { + sender.sendMessage(ConfigManager.getMessage_Server("ArgsError")); + return true; + } + Player player = (Player) sender; + try { + long qq = Long.parseLong(args[1]); + if (ConfigManager.getBindUser().containsKey(qq)) { + player.sendMessage(ConfigManager.getMessage_Server("QQReapt")); + return true; + } + player.sendMessage(ConfigManager.getMessage_Server("ChangeBind") + .replaceAll("%qq%", String.valueOf(qq))); + for (long group : ConfigManager.getGroups()) { + Bot.getMiraiBot() + .getGroup(group) + .sendMessage(ConfigManager.getMessage_QQ("ChangeBind") + .replaceAll("%qq%", String.valueOf(qq)) + .replaceAll("%player%", player.getName())); + } + PlayerData.changeBind(player.getName(), qq); + return true; + } catch (Exception ex) { + ex.printStackTrace(); + player.sendMessage(ConfigManager.getMessage_Server("ArgsError")); + } + break;*/ + case "reload": + if (args.length != 1) return true; + Config.loadConfig(); + sender.sendMessage("LinearBot配置文件已重载"); + break; + case "help": + if (args.length != 1) return true; + sender.sendMessage("§6LinearBot 机器人帮助菜单"); + sender.sendMessage("§6/lb reload :§f重载插件"); + sender.sendMessage("§6/lb help :§f获取插件帮助"); + break; + default: + if (args.length != 1) return true; + sender.sendMessage("错误的指令用法,请使用/lb help查看命令使用方法"); + break; + } + } + return true; + } +} diff --git a/src/main/java/org/linear/linearbot/config/Config.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/config/Config.java similarity index 96% rename from src/main/java/org/linear/linearbot/config/Config.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/config/Config.java index 8772c32..26284cc 100644 --- a/src/main/java/org/linear/linearbot/config/Config.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/config/Config.java @@ -1,119 +1,119 @@ -package org.linear.linearbot.config; - -import org.linear.linearbot.LinearBot; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; -import java.util.List; - -public class Config { - private static final LinearBot INSTANCE = LinearBot.INSTANCE; - private static final File botFile = new File(INSTANCE.getDataFolder(), "bot.yml"); - private static final File configFile = new File(INSTANCE.getDataFolder(), "config.yml"); - private static final File returnsFile = new File(INSTANCE.getDataFolder(), "returns.yml"); - private static final File commandsFile = new File(INSTANCE.getDataFolder(), "commands.yml"); - private static YamlConfiguration bot; - private static YamlConfiguration config; - private static YamlConfiguration returns; - private static YamlConfiguration commands; - - public static void createConfig(){ - File[] allFile = {botFile,configFile,returnsFile,commandsFile}; - for (File file : allFile) { - if (!file.exists()) { - INSTANCE.saveResource(file.getName(), true); - } - } - loadConfig(); - if (!Config.getBotYamlVersion().equals("1.1")){ - INSTANCE.saveResource(botFile.getName(), true); - } - if (!Config.getConfigYamlVersion().equals("1.2")){ - INSTANCE.saveResource(configFile.getName(), true); - } - if (!Config.getCommandsYamlVersion().equals("1.2")){ - INSTANCE.saveResource(commandsFile.getName(), true); - } - if (!Config.getReturnsYamlVersion().equals("1.2")){ - INSTANCE.saveResource(configFile.getName(), true); - } - } - - public static String getBotYamlVersion(){ - return getBotYaml().getString("Ver"); - } - - public static String getConfigYamlVersion(){ - return getConfigYaml().getString("Ver"); - } - - public static String getCommandsYamlVersion(){ - return getCommandsYaml().getString("Ver"); - } - - public static String getReturnsYamlVersion(){ - return getReturnsYaml().getString("Ver"); - } - - public static void loadConfig(){ - bot = YamlConfiguration.loadConfiguration(botFile); - config = YamlConfiguration.loadConfiguration(configFile); - returns = YamlConfiguration.loadConfiguration(returnsFile); - commands = YamlConfiguration.loadConfiguration(commandsFile); - } - - public static YamlConfiguration getBotYaml(){ - return bot; - } - - public static YamlConfiguration getConfigYaml() { - return config; - } - - public static YamlConfiguration getReturnsYaml() {return returns;} - - public static YamlConfiguration getCommandsYaml() {return commands;} - - public static long getBotQQ() { - return getBotYaml().getLong("Bot.QQ"); - } - - public static List getGroupQQs(){ - return getBotYaml().getLongList("Groups"); - } - - public static List getAdmins() { - return Config.getBotYaml().getLongList("Admins"); - } - - public static boolean DieReport(){ - return getConfigYaml().getBoolean("DieReport"); - } - - public static boolean Forwarding(){ - return getConfigYaml().getBoolean("Forwarding"); - } - - public static boolean WhiteList(){ - return getConfigYaml().getBoolean("WhiteList"); - } - - public static boolean JoinAndLeave(){ - return getConfigYaml().getBoolean("JoinAndLeave"); - } - - public static boolean CMD(){ - return getConfigYaml().getBoolean("CMD"); - } - - public static boolean Online(){ - return getConfigYaml().getBoolean("Online"); - } - - public static boolean TPS() {return getConfigYaml().getBoolean("TPS");} - - public static boolean SDC() {return getConfigYaml().getBoolean("SDC");} - - public static boolean SDR() {return getConfigYaml().getBoolean("SDR");} - -} +package org.linear.linearbot.config; + +import org.linear.linearbot.LinearBot; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.util.List; + +public class Config { + private static final LinearBot INSTANCE = LinearBot.INSTANCE; + private static final File botFile = new File(INSTANCE.getDataFolder(), "bot.yml"); + private static final File configFile = new File(INSTANCE.getDataFolder(), "config.yml"); + private static final File returnsFile = new File(INSTANCE.getDataFolder(), "returns.yml"); + private static final File commandsFile = new File(INSTANCE.getDataFolder(), "commands.yml"); + private static YamlConfiguration bot; + private static YamlConfiguration config; + private static YamlConfiguration returns; + private static YamlConfiguration commands; + + public static void createConfig(){ + File[] allFile = {botFile,configFile,returnsFile,commandsFile}; + for (File file : allFile) { + if (!file.exists()) { + INSTANCE.saveResource(file.getName(), true); + } + } + loadConfig(); + if (!Config.getBotYamlVersion().equals("1.1")){ + INSTANCE.saveResource(botFile.getName(), true); + } + if (!Config.getConfigYamlVersion().equals("1.2")){ + INSTANCE.saveResource(configFile.getName(), true); + } + if (!Config.getCommandsYamlVersion().equals("1.2")){ + INSTANCE.saveResource(commandsFile.getName(), true); + } + if (!Config.getReturnsYamlVersion().equals("1.2")){ + INSTANCE.saveResource(configFile.getName(), true); + } + } + + public static String getBotYamlVersion(){ + return getBotYaml().getString("Ver"); + } + + public static String getConfigYamlVersion(){ + return getConfigYaml().getString("Ver"); + } + + public static String getCommandsYamlVersion(){ + return getCommandsYaml().getString("Ver"); + } + + public static String getReturnsYamlVersion(){ + return getReturnsYaml().getString("Ver"); + } + + public static void loadConfig(){ + bot = YamlConfiguration.loadConfiguration(botFile); + config = YamlConfiguration.loadConfiguration(configFile); + returns = YamlConfiguration.loadConfiguration(returnsFile); + commands = YamlConfiguration.loadConfiguration(commandsFile); + } + + public static YamlConfiguration getBotYaml(){ + return bot; + } + + public static YamlConfiguration getConfigYaml() { + return config; + } + + public static YamlConfiguration getReturnsYaml() {return returns;} + + public static YamlConfiguration getCommandsYaml() {return commands;} + + public static long getBotQQ() { + return getBotYaml().getLong("Bot.QQ"); + } + + public static List getGroupQQs(){ + return getBotYaml().getLongList("Groups"); + } + + public static List getAdmins() { + return Config.getBotYaml().getLongList("Admins"); + } + + public static boolean DieReport(){ + return getConfigYaml().getBoolean("DieReport"); + } + + public static boolean Forwarding(){ + return getConfigYaml().getBoolean("Forwarding"); + } + + public static boolean WhiteList(){ + return getConfigYaml().getBoolean("WhiteList"); + } + + public static boolean JoinAndLeave(){ + return getConfigYaml().getBoolean("JoinAndLeave"); + } + + public static boolean CMD(){ + return getConfigYaml().getBoolean("CMD"); + } + + public static boolean Online(){ + return getConfigYaml().getBoolean("Online"); + } + + public static boolean TPS() {return getConfigYaml().getBoolean("TPS");} + + public static boolean SDC() {return getConfigYaml().getBoolean("SDC");} + + public static boolean SDR() {return getConfigYaml().getBoolean("SDR");} + +} diff --git a/src/main/java/org/linear/linearbot/event/qq/QQEvent.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/event/qq/QQEvent.java similarity index 97% rename from src/main/java/org/linear/linearbot/event/qq/QQEvent.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/event/qq/QQEvent.java index f41a96f..00ef7d3 100644 --- a/src/main/java/org/linear/linearbot/event/qq/QQEvent.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/event/qq/QQEvent.java @@ -1,196 +1,196 @@ -package org.linear.linearbot.event.qq; - -import me.dreamvoid.miraimc.api.MiraiBot; -import me.dreamvoid.miraimc.api.MiraiMC; -import me.dreamvoid.miraimc.bukkit.event.group.member.MiraiMemberLeaveEvent; -import me.dreamvoid.miraimc.bukkit.event.message.passive.MiraiFriendMessageEvent; -import me.dreamvoid.miraimc.bukkit.event.message.passive.MiraiGroupMessageEvent; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.linear.linearbot.bot.Bot; -import org.linear.linearbot.config.Config; -import org.linear.linearbot.event.server.ServerManager; -import org.linear.linearbot.event.server.ServerTps; -import org.linear.linearbot.tool.StringTool; - -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.bukkit.Bukkit.getPlayer; -import static org.bukkit.Bukkit.reloadWhitelist; - -public class QQEvent implements Listener { - @EventHandler - public void onFriendMessageReceive(MiraiFriendMessageEvent e){ - - if(e.getMessage().equals("/在线人数")) { - if(!Config.Online()){ - return; - } - MiraiBot.getBot(e.getBotID()).getFriend(e.getSenderID()).sendMessage("当前在线:" + Bukkit.getServer().getOnlinePlayers()+"("+Bukkit.getServer().getOnlinePlayers().size()+"人)"); - return; - } - - if(Config.getAdmins().contains(e.getSenderID())) { - - Pattern pattern; - Matcher matcher; - - pattern = Pattern.compile("/cmd .*"); - matcher = pattern.matcher(e.getMessage()); - if (matcher.find()) { - if(!Config.CMD()){ - return; - } - String cmd = Pattern.compile("/cmd .*").matcher(e.getMessage()).group().replace("/cmd ", ""); - ServerManager.sendCmd(cmd); - Bot.sendMsg("已发送指令至服务器",e.getSenderID()); - } - - return; - } - - Bukkit.broadcastMessage("§6"+"[私聊消息]"+"§a"+e.getSenderName()+"§f"+":"+e.getMessage()); - } - - @EventHandler - public void onGroupMessageReceive(MiraiGroupMessageEvent e){ - - Pattern pattern; - Matcher matcher; - - String msg = e.getMessage(); - long groupID= e.getGroupID(); - long senderID = e.getSenderID(); - - pattern = Pattern.compile(" groups = Config.getGroupQQs(); - for (long groupID : groups){ - Bot.sendMsg("[服务器]"+name+":"+message,groupID); - } - } - - @EventHandler - public void onJoin(PlayerJoinEvent event){ - - String name = StringTool.filterColor(event.getPlayer().getDisplayName()); - - if (!Config.JoinAndLeave()){ - return; - } - List groups = Config.getGroupQQs(); - for (long groupID : groups){ - Bot.sendMsg("玩家"+name+"加入游戏",groupID); - } - - } - - @EventHandler - public void onQuit(PlayerQuitEvent event){ - - String name = StringTool.filterColor(event.getPlayer().getDisplayName()); - - if (!Config.JoinAndLeave()){ - return; - } - List groups = Config.getGroupQQs(); - for (long groupID : groups){ - Bot.sendMsg("玩家"+name+"退出游戏",groupID); - } - } - - @EventHandler - public void onPlayerDeath(PlayerDeathEvent event){ - if(!Config.DieReport()){ - return; - } - Player player=event.getEntity(); - String name= player.getName(); - Location location=player.getLocation(); - int x= (int) location.getX(); - int y= (int) location.getY(); - int z= (int) location.getZ(); - String msg = "死在了"+location.getWorld().getName()+"世界"+"("+x+","+y+","+z+")"; - ServerManager.sendCmd("msg "+name+" "+msg); - List groups = Config.getGroupQQs(); - for (long groupID : groups){ - Bot.sendMsg("玩家"+name+msg,groupID); - } - } -} +package org.linear.linearbot.event.server; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.linear.linearbot.bot.Bot; +import org.linear.linearbot.config.Config; +import org.linear.linearbot.hook.AuthMeHook; +import org.linear.linearbot.hook.GriefDefenderHook; +import org.linear.linearbot.hook.QuickShopHook; +import org.linear.linearbot.hook.ResidenceHook; +import org.linear.linearbot.tool.StringTool; + +import java.util.List; + +import static org.linear.linearbot.hook.ResidenceHook.resChatApi; + +public class ServerEvent implements Listener{ + + @EventHandler + public void onChat(AsyncPlayerChatEvent event) { + if (!Config.Forwarding()){ + return; + } + String name = StringTool.filterColor(event.getPlayer().getDisplayName()); + String message = StringTool.filterColor(event.getMessage()); + if (AuthMeHook.hasAuthMe) {if (!AuthMeHook.authMeApi.isAuthenticated(event.getPlayer())) {return;} } + if (ResidenceHook.hasRes) {if (resChatApi.getPlayerChannel(event.getPlayer().getName()) != null) {return;}} + if (QuickShopHook.hasQs) {if (event.getPlayer() == QsChatEvent.getQsSender() && event.getMessage() == QsChatEvent.getQsMessage()) {return;}} + if (QuickShopHook.hasQsHikari) {if (event.getPlayer() == QsHikariChatEvent.getQsSender() && event.getMessage() == QsHikariChatEvent.getQsMessage()) {return;}} + if (GriefDefenderHook.hasGriefDefender) {if (GDClaimEvent.getGDMessage() == event.getMessage()){return;}} + List groups = Config.getGroupQQs(); + for (long groupID : groups){ + Bot.sendMsg("[服务器]"+name+":"+message,groupID); + } + } + + @EventHandler + public void onJoin(PlayerJoinEvent event){ + + String name = StringTool.filterColor(event.getPlayer().getDisplayName()); + + if (!Config.JoinAndLeave()){ + return; + } + List groups = Config.getGroupQQs(); + for (long groupID : groups){ + Bot.sendMsg("玩家"+name+"加入游戏",groupID); + } + + } + + @EventHandler + public void onQuit(PlayerQuitEvent event){ + + String name = StringTool.filterColor(event.getPlayer().getDisplayName()); + + if (!Config.JoinAndLeave()){ + return; + } + List groups = Config.getGroupQQs(); + for (long groupID : groups){ + Bot.sendMsg("玩家"+name+"退出游戏",groupID); + } + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event){ + if(!Config.DieReport()){ + return; + } + Player player=event.getEntity(); + String name= player.getName(); + Location location=player.getLocation(); + int x= (int) location.getX(); + int y= (int) location.getY(); + int z= (int) location.getZ(); + String msg = "死在了"+location.getWorld().getName()+"世界"+"("+x+","+y+","+z+")"; + ServerManager.sendCmd("msg "+name+" "+msg); + List groups = Config.getGroupQQs(); + for (long groupID : groups){ + Bot.sendMsg("玩家"+name+msg,groupID); + } + } +} diff --git a/src/main/java/org/linear/linearbot/event/server/ServerManager.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ServerManager.java similarity index 96% rename from src/main/java/org/linear/linearbot/event/server/ServerManager.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ServerManager.java index 5ba5815..9bdbaea 100644 --- a/src/main/java/org/linear/linearbot/event/server/ServerManager.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ServerManager.java @@ -1,35 +1,35 @@ -package org.linear.linearbot.event.server; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; -import org.linear.linearbot.LinearBot; -import org.linear.linearbot.config.Config; - -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -public class ServerManager { - - public static String listOnlinePlayer() { - List onlinePlayer = new LinkedList<>(); - for (Player p : Bukkit.getOnlinePlayers()) { - onlinePlayer.add(p.getName()); - } - return Arrays.toString(onlinePlayer.toArray()).replace("\\[|\\]", ""); - } - - public static void sendCmd(String cmd) { - if(!Config.CMD()){ - return; - } - new BukkitRunnable(){ - @Override - public void run(){ - Bukkit.dispatchCommand(Bukkit.getConsoleSender(),cmd); - } - }.runTask(LinearBot.INSTANCE); - } - -} +package org.linear.linearbot.event.server; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.linear.linearbot.LinearBot; +import org.linear.linearbot.config.Config; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +public class ServerManager { + + public static String listOnlinePlayer() { + List onlinePlayer = new LinkedList<>(); + for (Player p : Bukkit.getOnlinePlayers()) { + onlinePlayer.add(p.getName()); + } + return Arrays.toString(onlinePlayer.toArray()).replace("\\[|\\]", ""); + } + + public static void sendCmd(String cmd) { + if(!Config.CMD()){ + return; + } + new BukkitRunnable(){ + @Override + public void run(){ + Bukkit.dispatchCommand(Bukkit.getConsoleSender(),cmd); + } + }.runTask(LinearBot.INSTANCE); + } + +} diff --git a/src/main/java/org/linear/linearbot/event/server/ServerTps.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ServerTps.java similarity index 96% rename from src/main/java/org/linear/linearbot/event/server/ServerTps.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ServerTps.java index ab59733..9ed48b3 100644 --- a/src/main/java/org/linear/linearbot/event/server/ServerTps.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/event/server/ServerTps.java @@ -1,67 +1,67 @@ -package org.linear.linearbot.event.server; - -import org.bukkit.Bukkit; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.text.NumberFormat; -import java.util.Locale; -import java.util.function.Supplier; - -public class ServerTps { - - /** Number formatter for 2 decimal places */ - private final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.CHINA); - - /** NMS server to get TPS from on spigot */ - private Object server; - - /** TPS field*/ - private Field recentTps; - - /** Detection for presence of Paper's TPS getter */ - private Method paperTps; - - /** Detection for presence of Paper's MSPT getter */ - private Method paperMspt; - - private Supplier TPS; - - private String MSPT = "none"; - - public ServerTps() { - try { - server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer()); - recentTps = server.getClass().getField("recentTps"); - } catch (ReflectiveOperationException e) { - //not spigot - } - try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {} - try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {} - TPS = () -> { - if (paperTps != null) { - return formatTPS(Bukkit.getTPS()[0]); - } else if (recentTps != null) { - try { - return formatTPS(((double[]) recentTps.get(server))[0]); - } catch (IllegalAccessException e) { - return String.valueOf(-1); - } - } else { - return String.valueOf(-1); - } - }; - if (paperMspt != null) { - MSPT = numberFormat.format(Bukkit.getAverageTickTime()); - } - } - - private String formatTPS(double tps) { - return numberFormat.format(Math.min(20, tps)); - } - - public Object getTps() {return TPS.get();} - - public String getMSPT() {return MSPT;} - -} +package org.linear.linearbot.event.server; + +import org.bukkit.Bukkit; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.text.NumberFormat; +import java.util.Locale; +import java.util.function.Supplier; + +public class ServerTps { + + /** Number formatter for 2 decimal places */ + private final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.CHINA); + + /** NMS server to get TPS from on spigot */ + private Object server; + + /** TPS field*/ + private Field recentTps; + + /** Detection for presence of Paper's TPS getter */ + private Method paperTps; + + /** Detection for presence of Paper's MSPT getter */ + private Method paperMspt; + + private Supplier TPS; + + private String MSPT = "none"; + + public ServerTps() { + try { + server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer()); + recentTps = server.getClass().getField("recentTps"); + } catch (ReflectiveOperationException e) { + //not spigot + } + try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {} + try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {} + TPS = () -> { + if (paperTps != null) { + return formatTPS(Bukkit.getTPS()[0]); + } else if (recentTps != null) { + try { + return formatTPS(((double[]) recentTps.get(server))[0]); + } catch (IllegalAccessException e) { + return String.valueOf(-1); + } + } else { + return String.valueOf(-1); + } + }; + if (paperMspt != null) { + MSPT = numberFormat.format(Bukkit.getAverageTickTime()); + } + } + + private String formatTPS(double tps) { + return numberFormat.format(Math.min(20, tps)); + } + + public Object getTps() {return TPS.get();} + + public String getMSPT() {return MSPT;} + +} diff --git a/src/main/java/org/linear/linearbot/hook/AuthMeHook.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/AuthMeHook.java similarity index 95% rename from src/main/java/org/linear/linearbot/hook/AuthMeHook.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/AuthMeHook.java index 5530805..0d6e1a8 100644 --- a/src/main/java/org/linear/linearbot/hook/AuthMeHook.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/AuthMeHook.java @@ -1,28 +1,29 @@ -package org.linear.linearbot.hook; - -import fr.xephi.authme.api.v3.AuthMeApi; -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; -import org.linear.linearbot.LinearBot; - -public class AuthMeHook { - - public static Boolean hasAuthMe; - public static AuthMeApi authMeApi; - - public static void hookAuthme() { - - Plugin authMe = Bukkit.getPluginManager().getPlugin("AuthMe"); - try { - if (authMe != null) { - hasAuthMe = true; - authMeApi = AuthMeApi.getInstance(); - LinearBot.INSTANCE.getLogger().info("AuthMe 关联成功"); - }else{ - hasAuthMe = false; - LinearBot.INSTANCE.getLogger().info("AuthMe 关联失败"); - } - } catch (Throwable e) { - e.printStackTrace(); - }} -} +package org.linear.linearbot.hook; + +import fr.xephi.authme.api.v3.AuthMeApi; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.linear.linearbot.LinearBot; + +public class AuthMeHook { + + public static Boolean hasAuthMe; + public static AuthMeApi authMeApi; + + public static void hookAuthme() { + + Plugin authMe = Bukkit.getPluginManager().getPlugin("AuthMe"); + try { + if (authMe != null) { + hasAuthMe = true; + authMeApi = AuthMeApi.getInstance(); + LinearBot.INSTANCE.getLogger().info("AuthMe 关联成功"); + }else{ + hasAuthMe = false; + LinearBot.INSTANCE.getLogger().info("AuthMe 关联失败"); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } +} diff --git a/LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/GriefDefenderHook.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/GriefDefenderHook.java new file mode 100644 index 0000000..1557ee3 --- /dev/null +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/GriefDefenderHook.java @@ -0,0 +1,29 @@ +package org.linear.linearbot.hook; + +import com.griefdefender.api.Core; +import com.griefdefender.api.GriefDefender; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.linear.linearbot.LinearBot; + +public class GriefDefenderHook { + + + public static Boolean hasGriefDefender; + + public static void hookGriefDefender() { + + Plugin authMe = Bukkit.getPluginManager().getPlugin("GriefDefender"); + try { + if (authMe != null) { + hasGriefDefender = true; + LinearBot.INSTANCE.getLogger().info("GriefDefender 关联成功"); + }else{ + hasGriefDefender = false; + LinearBot.INSTANCE.getLogger().info("GriefDefender 关联失败"); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/linear/linearbot/hook/QuickShopHook.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/QuickShopHook.java similarity index 96% rename from src/main/java/org/linear/linearbot/hook/QuickShopHook.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/QuickShopHook.java index a6e0db3..434d292 100644 --- a/src/main/java/org/linear/linearbot/hook/QuickShopHook.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/QuickShopHook.java @@ -1,39 +1,40 @@ -package org.linear.linearbot.hook; - -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; -import org.linear.linearbot.LinearBot; - -public class QuickShopHook { - - public static Boolean hasQs; - - public static Boolean hasQsHikari; - - public static void hookQuickShop() { - - Plugin quickShop = Bukkit.getPluginManager().getPlugin("QuickShop"); - try { - if (quickShop != null) { - hasQs = true; - hasQsHikari = false; - LinearBot.INSTANCE.getLogger().info("QuickShop-Reremake 关联成功"); - }else{ - hasQs = false; - Plugin quickShopHikari = Bukkit.getPluginManager().getPlugin("QuickShop-Hikari"); - try { - if (quickShopHikari != null) { - hasQsHikari = true; - LinearBot.INSTANCE.getLogger().info("QuickShop-Hikari 关联成功"); - }else{ - hasQsHikari = false; - LinearBot.INSTANCE.getLogger().info("QuickShop 关联失败"); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } catch (Throwable e) { - e.printStackTrace(); - }} -} +package org.linear.linearbot.hook; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.linear.linearbot.LinearBot; + +public class QuickShopHook { + + public static Boolean hasQs; + + public static Boolean hasQsHikari; + + public static void hookQuickShop() { + + Plugin quickShop = Bukkit.getPluginManager().getPlugin("QuickShop"); + try { + if (quickShop != null) { + hasQs = true; + hasQsHikari = false; + LinearBot.INSTANCE.getLogger().info("QuickShop-Reremake 关联成功"); + }else{ + hasQs = false; + Plugin quickShopHikari = Bukkit.getPluginManager().getPlugin("QuickShop-Hikari"); + try { + if (quickShopHikari != null) { + hasQsHikari = true; + LinearBot.INSTANCE.getLogger().info("QuickShop-Hikari 关联成功"); + }else{ + hasQsHikari = false; + LinearBot.INSTANCE.getLogger().info("QuickShop 关联失败"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/linear/linearbot/hook/ResidenceHook.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/ResidenceHook.java similarity index 95% rename from src/main/java/org/linear/linearbot/hook/ResidenceHook.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/ResidenceHook.java index 4995df2..b4a46ec 100644 --- a/src/main/java/org/linear/linearbot/hook/ResidenceHook.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/hook/ResidenceHook.java @@ -1,31 +1,32 @@ -package org.linear.linearbot.hook; - -import com.bekvon.bukkit.residence.Residence; -import com.bekvon.bukkit.residence.chat.ChatManager; -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; -import org.linear.linearbot.LinearBot; - -public class ResidenceHook { - - public static Boolean hasRes; - - public static ChatManager resChatApi; - - public static void hookRes() { - - Plugin residence = Bukkit.getPluginManager().getPlugin("Residence"); - try { - if (residence != null) { - hasRes = true; - resChatApi = Residence.getInstance().getChatManager(); - LinearBot.INSTANCE.getLogger().info("Residence 关联成功"); - }else{ - hasRes = false; - LinearBot.INSTANCE.getLogger().info("Residence 关联失败"); - } - } catch (Throwable e) { - e.printStackTrace(); - }} -} - +package org.linear.linearbot.hook; + +import com.bekvon.bukkit.residence.Residence; +import com.bekvon.bukkit.residence.chat.ChatManager; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.linear.linearbot.LinearBot; + +public class ResidenceHook { + + public static Boolean hasRes; + + public static ChatManager resChatApi; + + public static void hookRes() { + + Plugin residence = Bukkit.getPluginManager().getPlugin("Residence"); + try { + if (residence != null) { + hasRes = true; + resChatApi = Residence.getInstance().getChatManager(); + LinearBot.INSTANCE.getLogger().info("Residence 关联成功"); + }else{ + hasRes = false; + LinearBot.INSTANCE.getLogger().info("Residence 关联失败"); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } +} + diff --git a/src/main/java/org/linear/linearbot/metrics/Metrics.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/metrics/Metrics.java similarity index 97% rename from src/main/java/org/linear/linearbot/metrics/Metrics.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/metrics/Metrics.java index 1c30196..fa7d567 100644 --- a/src/main/java/org/linear/linearbot/metrics/Metrics.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/metrics/Metrics.java @@ -1,853 +1,853 @@ -/* - * This Metrics class was auto-generated and can be copied into your project if you are - * not using a build tool like Gradle or Maven for dependency management. - * - * IMPORTANT: You are not allowed to modify this class, except changing the package. - * - * Unallowed modifications include but are not limited to: - * - Remove the option for users to opt-out - * - Change the frequency for data submission - * - Obfuscate the code (every obfucator should allow you to make an exception for specific files) - * - Reformat the code (if you use a linter, add an exception) - * - * Violations will result in a ban of your plugin and account from bStats. - */ -package org.linear.linearbot.metrics; - -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; - -import javax.net.ssl.HttpsURLConnection; -import java.io.*; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.zip.GZIPOutputStream; - -public class Metrics { - - private final Plugin plugin; - - private final MetricsBase metricsBase; - - /** - * Creates a new Metrics instance. - * - * @param plugin Your plugin instance. - * @param serviceId The id of the service. It can be found at What is my plugin id? - */ - public Metrics(JavaPlugin plugin, int serviceId) { - this.plugin = plugin; - // Get the config file - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - if (!config.isSet("serverUuid")) { - config.addDefault("enabled", true); - config.addDefault("serverUuid", UUID.randomUUID().toString()); - config.addDefault("logFailedRequests", false); - config.addDefault("logSentData", false); - config.addDefault("logResponseStatusText", false); - // Inform the server owners about bStats - config - .options() - .header( - "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" - + "many people use their plugin and their total player count. It's recommended to keep bStats\n" - + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" - + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" - + "anonymous.") - .copyDefaults(true); - try { - config.save(configFile); - } catch (IOException ignored) { - } - } - // Load the data - boolean enabled = config.getBoolean("enabled", true); - String serverUUID = config.getString("serverUuid"); - boolean logErrors = config.getBoolean("logFailedRequests", false); - boolean logSentData = config.getBoolean("logSentData", false); - boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); - metricsBase = - new MetricsBase( - "bukkit", - serverUUID, - serviceId, - enabled, - this::appendPlatformData, - this::appendServiceData, - submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), - plugin::isEnabled, - (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), - (message) -> this.plugin.getLogger().log(Level.INFO, message), - logErrors, - logSentData, - logResponseStatusText); - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - metricsBase.addCustomChart(chart); - } - - private void appendPlatformData(JsonObjectBuilder builder) { - builder.appendField("playerAmount", getPlayerAmount()); - builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); - builder.appendField("bukkitVersion", Bukkit.getVersion()); - builder.appendField("bukkitName", Bukkit.getName()); - builder.appendField("javaVersion", System.getProperty("java.version")); - builder.appendField("osName", System.getProperty("os.name")); - builder.appendField("osArch", System.getProperty("os.arch")); - builder.appendField("osVersion", System.getProperty("os.version")); - builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); - } - - private void appendServiceData(JsonObjectBuilder builder) { - builder.appendField("pluginVersion", plugin.getDescription().getVersion()); - } - - private int getPlayerAmount() { - try { - // Around MC 1.8 the return type was changed from an array to a collection, - // This fixes java.lang.NoSuchMethodError: - // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; - Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - return onlinePlayersMethod.getReturnType().equals(Collection.class) - ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() - : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; - } catch (Exception e) { - // Just use the new method if the reflection failed - return Bukkit.getOnlinePlayers().size(); - } - } - - public static class MetricsBase { - - /** The version of the Metrics class. */ - public static final String METRICS_VERSION = "3.0.0"; - - private static final ScheduledExecutorService scheduler = - Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); - - private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; - - private final String platform; - - private final String serverUuid; - - private final int serviceId; - - private final Consumer appendPlatformDataConsumer; - - private final Consumer appendServiceDataConsumer; - - private final Consumer submitTaskConsumer; - - private final Supplier checkServiceEnabledSupplier; - - private final BiConsumer errorLogger; - - private final Consumer infoLogger; - - private final boolean logErrors; - - private final boolean logSentData; - - private final boolean logResponseStatusText; - - private final Set customCharts = new HashSet<>(); - - private final boolean enabled; - - /** - * Creates a new MetricsBase class instance. - * - * @param platform The platform of the service. - * @param serviceId The id of the service. - * @param serverUuid The server uuid. - * @param enabled Whether or not data sending is enabled. - * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all platform-specific data. - * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all service-specific data. - * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be - * used to delegate the data collection to a another thread to prevent errors caused by - * concurrency. Can be {@code null}. - * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. - * @param errorLogger A consumer that accepts log message and an error. - * @param infoLogger A consumer that accepts info log messages. - * @param logErrors Whether or not errors should be logged. - * @param logSentData Whether or not the sent data should be logged. - * @param logResponseStatusText Whether or not the response status text should be logged. - */ - public MetricsBase( - String platform, - String serverUuid, - int serviceId, - boolean enabled, - Consumer appendPlatformDataConsumer, - Consumer appendServiceDataConsumer, - Consumer submitTaskConsumer, - Supplier checkServiceEnabledSupplier, - BiConsumer errorLogger, - Consumer infoLogger, - boolean logErrors, - boolean logSentData, - boolean logResponseStatusText) { - this.platform = platform; - this.serverUuid = serverUuid; - this.serviceId = serviceId; - this.enabled = enabled; - this.appendPlatformDataConsumer = appendPlatformDataConsumer; - this.appendServiceDataConsumer = appendServiceDataConsumer; - this.submitTaskConsumer = submitTaskConsumer; - this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; - this.errorLogger = errorLogger; - this.infoLogger = infoLogger; - this.logErrors = logErrors; - this.logSentData = logSentData; - this.logResponseStatusText = logResponseStatusText; - checkRelocation(); - if (enabled) { - // WARNING: Removing the option to opt-out will get your plugin banned from bStats - startSubmitting(); - } - } - - public void addCustomChart(CustomChart chart) { - this.customCharts.add(chart); - } - - private void startSubmitting() { - final Runnable submitTask = - () -> { - if (!enabled || !checkServiceEnabledSupplier.get()) { - // Submitting data or service is disabled - scheduler.shutdown(); - return; - } - if (submitTaskConsumer != null) { - submitTaskConsumer.accept(this::submitData); - } else { - this.submitData(); - } - }; - // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution - // of requests on the - // bStats backend. To circumvent this problem, we introduce some randomness into the initial - // and second delay. - // WARNING: You must not modify and part of this Metrics class, including the submit delay or - // frequency! - // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! - long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); - long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); - scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); - scheduler.scheduleAtFixedRate( - submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); - } - - private void submitData() { - final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); - appendPlatformDataConsumer.accept(baseJsonBuilder); - final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); - appendServiceDataConsumer.accept(serviceJsonBuilder); - JsonObjectBuilder.JsonObject[] chartData = - customCharts.stream() - .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) - .filter(Objects::nonNull) - .toArray(JsonObjectBuilder.JsonObject[]::new); - serviceJsonBuilder.appendField("id", serviceId); - serviceJsonBuilder.appendField("customCharts", chartData); - baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); - baseJsonBuilder.appendField("serverUUID", serverUuid); - baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); - JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); - scheduler.execute( - () -> { - try { - // Send the data - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logErrors) { - errorLogger.accept("Could not submit bStats metrics data", e); - } - } - }); - } - - private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { - if (logSentData) { - infoLogger.accept("Sent bStats metrics data: " + data.toString()); - } - String url = String.format(REPORT_URL, platform); - HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("User-Agent", "Metrics-Service/1"); - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = - new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - if (logResponseStatusText) { - infoLogger.accept("Sent data to bStats and received response: " + builder); - } - } - - /** Checks that the class was properly relocated. */ - private void checkRelocation() { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null - || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little - // "trick" ... :D - final String defaultPackage = - new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); - final String examplePackage = - new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure no one just copy & pastes the example and uses the wrong package - // names - if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) - || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - /** - * Gzips the given string. - * - * @param str The string to gzip. - * @return The gzipped string. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - } - - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @Override - public JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class MultiLineChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public MultiLineChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class SimpleBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimpleBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public abstract static class CustomChart { - - private final String chartId; - - protected CustomChart(String chartId) { - if (chartId == null) { - throw new IllegalArgumentException("chartId must not be null"); - } - this.chartId = chartId; - } - - public JsonObjectBuilder.JsonObject getRequestJsonObject( - BiConsumer errorLogger, boolean logErrors) { - JsonObjectBuilder builder = new JsonObjectBuilder(); - builder.appendField("chartId", chartId); - try { - JsonObjectBuilder.JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - builder.appendField("data", data); - } catch (Throwable t) { - if (logErrors) { - errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return builder.build(); - } - - protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; - } - - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class AdvancedBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - /** - * An extremely simple JSON builder. - * - *

While this class is neither feature-rich nor the most performant one, it's sufficient enough - * for its use-case. - */ - public static class JsonObjectBuilder { - - private StringBuilder builder = new StringBuilder(); - - private boolean hasAtLeastOneField = false; - - public JsonObjectBuilder() { - builder.append("{"); - } - - /** - * Appends a null field to the JSON. - * - * @param key The key of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendNull(String key) { - appendFieldUnescaped(key, "null"); - return this; - } - - /** - * Appends a string field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String value) { - if (value == null) { - throw new IllegalArgumentException("JSON value must not be null"); - } - appendFieldUnescaped(key, "\"" + escape(value) + "\""); - return this; - } - - /** - * Appends an integer field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int value) { - appendFieldUnescaped(key, String.valueOf(value)); - return this; - } - - /** - * Appends an object to the JSON. - * - * @param key The key of the field. - * @param object The object. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject object) { - if (object == null) { - throw new IllegalArgumentException("JSON object must not be null"); - } - appendFieldUnescaped(key, object.toString()); - return this; - } - - /** - * Appends a string array to the JSON. - * - * @param key The key of the field. - * @param values The string array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values) - .map(value -> "\"" + escape(value) + "\"") - .collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an integer array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an object array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends a field to the object. - * - * @param key The key of the field. - * @param escapedValue The escaped value of the field. - */ - private void appendFieldUnescaped(String key, String escapedValue) { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - if (key == null) { - throw new IllegalArgumentException("JSON key must not be null"); - } - if (hasAtLeastOneField) { - builder.append(","); - } - builder.append("\"").append(escape(key)).append("\":").append(escapedValue); - hasAtLeastOneField = true; - } - - /** - * Builds the JSON string and invalidates this builder. - * - * @return The built JSON string. - */ - public JsonObject build() { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - JsonObject object = new JsonObject(builder.append("}").toString()); - builder = null; - return object; - } - - /** - * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. - * - *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. - * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). - * - * @param value The value to escape. - * @return The escaped value. - */ - private static String escape(String value) { - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '"') { - builder.append("\\\""); - } else if (c == '\\') { - builder.append("\\\\"); - } else if (c <= '\u000F') { - builder.append("\\u000").append(Integer.toHexString(c)); - } else if (c <= '\u001F') { - builder.append("\\u00").append(Integer.toHexString(c)); - } else { - builder.append(c); - } - } - return builder.toString(); - } - - /** - * A super simple representation of a JSON object. - * - *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not - * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, - * JsonObject)}. - */ - public static class JsonObject { - - private final String value; - - private JsonObject(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } - } -} +/* + * This Metrics class was auto-generated and can be copied into your project if you are + * not using a build tool like Gradle or Maven for dependency management. + * + * IMPORTANT: You are not allowed to modify this class, except changing the package. + * + * Unallowed modifications include but are not limited to: + * - Remove the option for users to opt-out + * - Change the frequency for data submission + * - Obfuscate the code (every obfucator should allow you to make an exception for specific files) + * - Reformat the code (if you use a linter, add an exception) + * + * Violations will result in a ban of your plugin and account from bStats. + */ +package org.linear.linearbot.metrics; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; + +public class Metrics { + + private final Plugin plugin; + + private final MetricsBase metricsBase; + + /** + * Creates a new Metrics instance. + * + * @param plugin Your plugin instance. + * @param serviceId The id of the service. It can be found at What is my plugin id? + */ + public Metrics(JavaPlugin plugin, int serviceId) { + this.plugin = plugin; + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + if (!config.isSet("serverUuid")) { + config.addDefault("enabled", true); + config.addDefault("serverUuid", UUID.randomUUID().toString()); + config.addDefault("logFailedRequests", false); + config.addDefault("logSentData", false); + config.addDefault("logResponseStatusText", false); + // Inform the server owners about bStats + config + .options() + .header( + "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" + + "many people use their plugin and their total player count. It's recommended to keep bStats\n" + + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" + + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" + + "anonymous.") + .copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + // Load the data + boolean enabled = config.getBoolean("enabled", true); + String serverUUID = config.getString("serverUuid"); + boolean logErrors = config.getBoolean("logFailedRequests", false); + boolean logSentData = config.getBoolean("logSentData", false); + boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); + metricsBase = + new MetricsBase( + "bukkit", + serverUUID, + serviceId, + enabled, + this::appendPlatformData, + this::appendServiceData, + submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), + plugin::isEnabled, + (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), + (message) -> this.plugin.getLogger().log(Level.INFO, message), + logErrors, + logSentData, + logResponseStatusText); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + metricsBase.addCustomChart(chart); + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", getPlayerAmount()); + builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); + builder.appendField("bukkitVersion", Bukkit.getVersion()); + builder.appendField("bukkitName", Bukkit.getName()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } + + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField("pluginVersion", plugin.getDescription().getVersion()); + } + + private int getPlayerAmount() { + try { + // Around MC 1.8 the return type was changed from an array to a collection, + // This fixes java.lang.NoSuchMethodError: + // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + return onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + // Just use the new method if the reflection failed + return Bukkit.getOnlinePlayers().size(); + } + } + + public static class MetricsBase { + + /** The version of the Metrics class. */ + public static final String METRICS_VERSION = "3.0.0"; + + private static final ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is enabled. + * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be + * used to delegate the data collection to a another thread to prevent errors caused by + * concurrency. Can be {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. + * @param errorLogger A consumer that accepts log message and an error. + * @param infoLogger A consumer that accepts info log messages. + * @param logErrors Whether or not errors should be logged. + * @param logSentData Whether or not the sent data should be logged. + * @param logResponseStatusText Whether or not the response status text should be logged. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText) { + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + checkRelocation(); + if (enabled) { + // WARNING: Removing the option to opt-out will get your plugin banned from bStats + startSubmitting(); + } + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + private void startSubmitting() { + final Runnable submitTask = + () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution + // of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial + // and second delay. + // WARNING: You must not modify and part of this Metrics class, including the submit delay or + // frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = + customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); + } + } + }); + } + + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); + } + } + + /** Checks that the class was properly relocated. */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little + // "trick" ... :D + final String defaultPackage = + new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); + final String examplePackage = + new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure no one just copy & pastes the example and uses the wrong package + // names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + /** + * Gzips the given string. + * + * @param str The string to gzip. + * @return The gzipped string. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + } + + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

While this class is neither feature-rich nor the most performant one, it's sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * + *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * A super simple representation of a JSON object. + * + *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not + * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, + * JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } +} diff --git a/src/main/java/org/linear/linearbot/tool/StringTool.java b/LinearBot-bukkit/src/main/java/org/linear/linearbot/tool/StringTool.java similarity index 95% rename from src/main/java/org/linear/linearbot/tool/StringTool.java rename to LinearBot-bukkit/src/main/java/org/linear/linearbot/tool/StringTool.java index 65ac2b6..a43379c 100644 --- a/src/main/java/org/linear/linearbot/tool/StringTool.java +++ b/LinearBot-bukkit/src/main/java/org/linear/linearbot/tool/StringTool.java @@ -1,16 +1,16 @@ -package org.linear.linearbot.tool; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class StringTool { - - public static String filterColor(String text){ - - String regEx = "§[0-9a-zA-Z]"; - Pattern p = Pattern.compile(regEx); - Matcher matcher = p.matcher(text); - return matcher.replaceAll("").trim(); - - } -} +package org.linear.linearbot.tool; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class StringTool { + + public static String filterColor(String text){ + + String regEx = "§[0-9a-zA-Z]"; + Pattern p = Pattern.compile(regEx); + Matcher matcher = p.matcher(text); + return matcher.replaceAll("").trim(); + + } +} diff --git a/src/main/resources/bot.yml b/LinearBot-bukkit/src/main/resources/bot.yml similarity index 100% rename from src/main/resources/bot.yml rename to LinearBot-bukkit/src/main/resources/bot.yml diff --git a/src/main/resources/commands.yml b/LinearBot-bukkit/src/main/resources/commands.yml similarity index 96% rename from src/main/resources/commands.yml rename to LinearBot-bukkit/src/main/resources/commands.yml index 152f36b..0690609 100644 --- a/src/main/resources/commands.yml +++ b/LinearBot-bukkit/src/main/resources/commands.yml @@ -1,4 +1,4 @@ -#自定义回复配置 -Ver: '1.2' - +#自定义回复配置 +Ver: '1.2' + 自定义群聊命令: 要执行的游戏命令 \ No newline at end of file diff --git a/src/main/resources/config.yml b/LinearBot-bukkit/src/main/resources/config.yml similarity index 91% rename from src/main/resources/config.yml rename to LinearBot-bukkit/src/main/resources/config.yml index 446eb99..f7f01df 100644 --- a/src/main/resources/config.yml +++ b/LinearBot-bukkit/src/main/resources/config.yml @@ -1,29 +1,29 @@ -#机器人功能设置 -Ver: '1.2' - -#消息转发 -Forwarding: true - -#死亡报告 -DieReport: false - -#白名单 -WhiteList: false - -#执行命令功能 -CMD: true - -#进出提示 -JoinAndLeave: true - -#在线玩家查询 -Online: true - -#tps查询 -TPS: true - -#自定义命令 -SDC: true - -#自定义回复 +#机器人功能设置 +Ver: '1.2' + +#消息转发 +Forwarding: true + +#死亡报告 +DieReport: false + +#白名单 +WhiteList: false + +#执行命令功能 +CMD: true + +#进出提示 +JoinAndLeave: true + +#在线玩家查询 +Online: true + +#tps查询 +TPS: true + +#自定义命令 +SDC: true + +#自定义回复 SDR: true \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/LinearBot-bukkit/src/main/resources/plugin.yml similarity index 92% rename from src/main/resources/plugin.yml rename to LinearBot-bukkit/src/main/resources/plugin.yml index 6e19009..d526012 100644 --- a/src/main/resources/plugin.yml +++ b/LinearBot-bukkit/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ version: '${project.version}' main: org.linear.linearbot.LinearBot api-version: 1.16 prefix: LinearBot -authors: [ Linear ] +authors: [ Linear,RegadPole ] depend: [ MiraiMC ] softdepend: - AuthMe diff --git a/src/main/resources/returns.yml b/LinearBot-bukkit/src/main/resources/returns.yml similarity index 95% rename from src/main/resources/returns.yml rename to LinearBot-bukkit/src/main/resources/returns.yml index 3b71c01..10ae1ee 100644 --- a/src/main/resources/returns.yml +++ b/LinearBot-bukkit/src/main/resources/returns.yml @@ -1,4 +1,4 @@ -#自定义回复配置 -Ver: '1.2' - -自定义回复的关键词: 自定义回复的内容 +#自定义回复配置 +Ver: '1.2' + +自定义回复的关键词: 自定义回复的内容 diff --git a/LinearBot-velocity/pom.xml b/LinearBot-velocity/pom.xml new file mode 100644 index 0000000..ca0c832 --- /dev/null +++ b/LinearBot-velocity/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + org.linear + LinearBot + 1.2.1 + + + LinearBot-velocity + + + 18 + 18 + UTF-8 + + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + + + + com.velocitypowered + velocity-api + 3.1.1 + provided + + + org.yaml + snakeyaml + 1.27 + compile + + + com.alibaba + fastjson + 1.2.47 + compile + + + + \ No newline at end of file diff --git a/LinearBot-velocity/src/main/java/org/linear/linearbot/LinearBot.java b/LinearBot-velocity/src/main/java/org/linear/linearbot/LinearBot.java new file mode 100644 index 0000000..3a8611a --- /dev/null +++ b/LinearBot-velocity/src/main/java/org/linear/linearbot/LinearBot.java @@ -0,0 +1,117 @@ +package org.linear.linearbot; + +import com.google.inject.Inject; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandMeta; +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.plugin.Dependency; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import org.linear.linearbot.bot.Bot; +import org.linear.linearbot.command.Commands; +import org.linear.linearbot.config.VelocityConfig; +import org.linear.linearbot.event.qq.QQEvent; +import org.linear.linearbot.event.server.ServerEvent; +import org.linear.linearbot.internal.Config; +import org.linear.linearbot.metrics.Metrics; +import org.slf4j.Logger; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Plugin(id = "linearbot", name = "LinearBot", version = "1.2.1", + url = "https://github.com/LinearBit/LinearBot", description = "A plugin for MiraiMC!", authors = {"Linear,RegadPole"}, dependencies = {@Dependency(id = "miraimc")}) +public class LinearBot { + + private final ProxyServer server; + private final Logger logger; + private final Path dataDirectory; + private PluginContainer pluginContainer; + private final Metrics.Factory metricsFactory; + public VelocityConfig vconf; + + @Inject + public LinearBot(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) { + this.server = server; + this.logger = logger; + this.dataDirectory = dataDirectory; + this.metricsFactory = metricsFactory; + + logger.info("It's a plugin for MiraiMC!"); + } + + @Subscribe + public void onProxyInitialization(ProxyInitializeEvent event) { + // Do some operation demanding access to the Velocity API here. + // For instance, we could register an event: + try { + vconf = new VelocityConfig(this); + vconf.loadConfig(); + logger.info("配置文件获取成功"); + }catch (Exception e) { + getLogger().warn("An error occurred while loading plugin."); + e.printStackTrace(); + } + + server.getEventManager().register(this, new ServerEvent()); + logger.info("服务器事件监听器注册成功"); + server.getEventManager().register(this, new QQEvent(this)); + logger.info("QQ事件监听器注册成功"); + CommandManager manager = server.getCommandManager(); + CommandMeta linearbot = manager.metaBuilder("linearbot").aliases("lb", "LinearBot").build(); + manager.register(linearbot, new Commands(this)); + logger.info("插件命令监听器注册成功"); + + // All you have to do is adding the following two lines in your onProxyInitialization method. + // You can find the plugin ids of your plugins on the page https://bstats.org/what-is-my-plugin-id + int pluginId = 17478; + Metrics metrics = metricsFactory.make(this, pluginId); + metrics.addCustomChart(new Metrics.SimplePie("chart_id", () -> "value")); + logger.info("LinearBot 已启动"); + + runAfterDone(); + } + + @Subscribe(order = PostOrder.FIRST) + public void onProxyShutdown(ProxyShutdownEvent event) { + List groups = Config.bot.Groups; + for (long groupID : groups) { + Bot.sendMsg("LinearBot已关闭", groupID); + } + + getLogger().info("LinearBot已关闭"); + } + + public Logger getLogger() { + return logger; + } + + public File getDataFolder() { + return dataDirectory.toFile(); + } + public ProxyServer getServer() { + return server; + } + + public PluginContainer getPluginContainer(){ + return pluginContainer; + } + + public void runAfterDone() { + this.getServer().getScheduler().buildTask(this, () -> { + for (long groupID : Config.bot.Groups) { + Bot.sendMsg("LinearBot已启动", groupID); + } + }).delay(10L, TimeUnit.SECONDS).schedule(); + + } +} \ No newline at end of file diff --git a/LinearBot-velocity/src/main/java/org/linear/linearbot/bot/Bot.java b/LinearBot-velocity/src/main/java/org/linear/linearbot/bot/Bot.java new file mode 100644 index 0000000..5ece6f0 --- /dev/null +++ b/LinearBot-velocity/src/main/java/org/linear/linearbot/bot/Bot.java @@ -0,0 +1,10 @@ +package org.linear.linearbot.bot; + +import me.dreamvoid.miraimc.api.MiraiBot; +import org.linear.linearbot.internal.Config; + +public class Bot { + public static void sendMsg(String msg,long groupID){ + MiraiBot.getBot(Config.bot.Bot.QQ).getGroup(groupID).sendMessageMirai(msg); + } +} diff --git a/LinearBot-velocity/src/main/java/org/linear/linearbot/command/Commands.java b/LinearBot-velocity/src/main/java/org/linear/linearbot/command/Commands.java new file mode 100644 index 0000000..9f103af --- /dev/null +++ b/LinearBot-velocity/src/main/java/org/linear/linearbot/command/Commands.java @@ -0,0 +1,62 @@ +package org.linear.linearbot.command; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.linear.linearbot.LinearBot; +import org.linear.linearbot.config.VelocityConfig; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class Commands implements SimpleCommand { + + private final LinearBot plugin; + + public Commands(LinearBot plugin){ + this.plugin = plugin; + } + + @Override + public void execute(Invocation invocation) { + CommandSource source = invocation.source(); + // Get the arguments after the command alias + String[] args = invocation.arguments(); + + if (args.length == 0) { + source.sendMessage(Component.text("请使用/lb help查看帮助")); + return; + } else if (args.length == 1) { + switch (args[0].toLowerCase()) { + case "reload": { + if (source.hasPermission("linearbot.command")) { + try { + plugin.vconf = new VelocityConfig(plugin); + plugin.vconf.loadConfig(); + source.sendMessage(Component.text("配置文件已重新加载")); + } catch (IOException e) { + e.printStackTrace(); + source.sendMessage(Component.text("配置文件加载时出错").color(NamedTextColor.RED)); + } + }else source.sendMessage(Component.text("你没有权限执行此命令")); + break; + } + case "help": { + source.sendMessage(Component.text("§6LinearBot 机器人帮助菜单")); + source.sendMessage(Component.text("§6/lb reload :§f重载插件")); + source.sendMessage(Component.text("§6/lb help :§f获取插件帮助")); + break; + } + default: { + source.sendMessage(Component.text("请使用/lb help查看帮助")); + } + } + }else { + source.sendMessage(Component.text("请使用/lb help查看帮助")); + } + + } +} diff --git a/LinearBot-velocity/src/main/java/org/linear/linearbot/config/VelocityConfig.java b/LinearBot-velocity/src/main/java/org/linear/linearbot/config/VelocityConfig.java new file mode 100644 index 0000000..486380f --- /dev/null +++ b/LinearBot-velocity/src/main/java/org/linear/linearbot/config/VelocityConfig.java @@ -0,0 +1,115 @@ +package org.linear.linearbot.config; + +import com.alibaba.fastjson.JSONArray; +import org.linear.linearbot.LinearBot; +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.*; + +import static org.linear.linearbot.internal.Config.*; + +public class VelocityConfig { + private static VelocityConfig Instance; + private final LinearBot plugin; + private Map returnsObj; + + public VelocityConfig(LinearBot plugin){ + Instance = this; + this.plugin = plugin; + PluginDir = plugin.getDataFolder(); + } + + public void loadConfig() throws IOException { + File botFile = new File(plugin.getDataFolder(), "bot.yml"); + File configFile = new File(plugin.getDataFolder(), "config.yml"); + File returnsFile = new File(plugin.getDataFolder(), "returns.yml"); + + if(!PluginDir.exists() && !PluginDir.mkdirs()) throw new RuntimeException("Failed to create data folder!"); + File[] allFile = {botFile,configFile,returnsFile}; + for (File file : allFile) { + if (!file.exists()) { + try (InputStream is = plugin.getClass().getResourceAsStream("/" + file.getName())) { + assert is != null; + Files.copy(is, file.toPath()); + } + } + } + + InputStream botIs = new FileInputStream(botFile); + InputStream configIs = new FileInputStream(configFile); + InputStream returnsIs = new FileInputStream(returnsFile); + + Yaml yaml = new Yaml(); + + Map botObj = yaml.load(botIs); + Map configObj = yaml.load(configIs); + Map returnsObj = yaml.load(returnsIs); + this.returnsObj = returnsObj; + + bot.Ver = !Objects.isNull(botObj.get("Ver")) ? String.valueOf(botObj.get("Ver")) : "1.0"; + Map botMap = !Objects.isNull(botObj.get("Bot")) ? (Map) botObj.get("Bot") : new HashMap<>(); + bot.Bot.QQ = !Objects.isNull(botMap.get("QQ")) ? Long.parseLong(String.valueOf(botMap.get("QQ"))) : 111; + bot.Groups = !Objects.isNull(botObj.get("Groups")) ? JSONArray.parseArray(botObj.get("Groups").toString(), Long.class) : new ArrayList<>(); + bot.Admins = !Objects.isNull(botObj.get("Admins")) ? JSONArray.parseArray(botObj.get("Admins").toString(), Long.class) : new ArrayList<>(); + + config.Ver = !Objects.isNull(configObj.get("Ver")) ? String.valueOf(configObj.get("Ver")) : "1.0"; + config.Forwarding = !Objects.isNull(configObj.get("Forwarding")) ? Boolean.parseBoolean(String.valueOf(configObj.get("Forwarding"))) : false; + config.JoinAndLeave = !Objects.isNull(configObj.get("JoinAndLeave")) ? Boolean.parseBoolean(String.valueOf(configObj.get("JoinAndLeave"))) : false; + config.Online = !Objects.isNull(configObj.get("Online")) ? Boolean.parseBoolean(String.valueOf(configObj.get("Online"))) : false; + config.SDR = !Objects.isNull(configObj.get("SDR")) ? Boolean.parseBoolean(String.valueOf(configObj.get("SDR"))) : false; + + returns.Ver = !Objects.isNull(returnsObj.get("Ver")) ? String.valueOf(returnsObj.get("Ver")) : "1.0"; + + if (!"1.1".equals(bot.Ver)){ + try (InputStream is = plugin.getClass().getResourceAsStream("/" + botFile.getName())) { + botIs.close(); + assert is != null; + Files.copy(is, botFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + Instance.loadConfig(); + } + } + if (!config.Ver.equals("1.2")){ + try (InputStream is = plugin.getClass().getResourceAsStream("/" + configFile.getName())) { + configIs.close(); + assert is != null; + Files.copy(is, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + Instance.loadConfig(); + } + } + if (!returns.Ver.equals("1.2")){ + try (InputStream is = plugin.getClass().getResourceAsStream("/" + returnsFile.getName())) { + returnsIs.close(); + assert is != null; + Files.copy(is, returnsFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + Instance.loadConfig(); + } + } + } + + public Map getReturnsObj() { + return returnsObj; + } + + public static void reloadConfig() throws IOException { + Instance.loadConfig(); + } + +/* public static YamlConfiguration getBotYaml(){ + return bot; + } + + public static YamlConfiguration getConfigYaml() { + return config; + } + + public static YamlConfiguration getReturnsYaml() {return returns;} + + public static YamlConfiguration getCommandsYaml() {return commands;}*/ + +} diff --git a/LinearBot-velocity/src/main/java/org/linear/linearbot/event/qq/QQEvent.java b/LinearBot-velocity/src/main/java/org/linear/linearbot/event/qq/QQEvent.java new file mode 100644 index 0000000..00c296c --- /dev/null +++ b/LinearBot-velocity/src/main/java/org/linear/linearbot/event/qq/QQEvent.java @@ -0,0 +1,102 @@ +package org.linear.linearbot.event.qq; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.proxy.Player; +import me.dreamvoid.miraimc.api.MiraiBot; +import me.dreamvoid.miraimc.velocity.event.message.passive.MiraiFriendMessageEvent; +import me.dreamvoid.miraimc.velocity.event.message.passive.MiraiGroupMessageEvent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.linear.linearbot.LinearBot; +import org.linear.linearbot.bot.Bot; +import org.linear.linearbot.config.VelocityConfig; +import org.linear.linearbot.internal.Config; +import org.linear.linearbot.tool.StringTool; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class QQEvent { + + public static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder().build(); + + private final LinearBot plugin; + private VelocityConfig velocityConfig; + + public QQEvent(LinearBot plugin) { + this.plugin = plugin; + } + @Subscribe + public void onFriendMessageReceive(MiraiFriendMessageEvent e){ + + if(e.getMessage().equals("/在线人数")) { + if(!Config.config.Online){ + return; + } + List pname = new ArrayList<>(); + for (Player player : plugin.getServer().getAllPlayers()) { + pname.add(player.getUsername()); + } + MiraiBot.getBot(e.getBotID()).getFriend(e.getSenderID()).sendMessage("当前在线:" + "("+plugin.getServer().getAllPlayers().size()+"人)"+pname); + return; + } + + plugin.getServer().sendMessage(Component.text("§6"+"[私聊消息]"+"§a"+e.getSenderName()+"§f"+":"+e.getMessage())); + } + + @Subscribe + public void onGroupMessageReceive(MiraiGroupMessageEvent e){ + + Pattern pattern; + Matcher matcher; + + String msg = e.getMessage(); + long groupID= e.getGroupID(); + long senderID = e.getSenderID(); + + pattern = Pattern.compile(" pname = new ArrayList<>(); + for (Player player : plugin.getServer().getAllPlayers()) { + pname.add(player.getUsername()); + } + Bot.sendMsg("当前在线:" + "("+plugin.getServer().getAllPlayers().size()+"人)"+pname,groupID); + return; + } + + if(Config.bot.Groups.contains(groupID)) { + if (!Config.config.Forwarding){ + return; + } + String name = StringTool.filterColor(e.getSenderName()); + String smsg = StringTool.filterColor(msg); + String message = "§6" + "[" + e.getGroupName() + "]" + "§a" + name + "§f" + ":" + smsg; + plugin.getServer().getAllServers().forEach(server -> {server.sendMessage(SERIALIZER.deserialize(message));}); + } + + if (Config.config.SDR){ + if (plugin.vconf.getReturnsObj().get(msg) == null) return; + String back = String.valueOf(plugin.vconf.getReturnsObj().get(msg)); + if(back!=null){ + Bot.sendMsg(back,groupID); + } + } + + } + +} diff --git a/LinearBot-velocity/src/main/java/org/linear/linearbot/event/server/ServerEvent.java b/LinearBot-velocity/src/main/java/org/linear/linearbot/event/server/ServerEvent.java new file mode 100644 index 0000000..79d6e56 --- /dev/null +++ b/LinearBot-velocity/src/main/java/org/linear/linearbot/event/server/ServerEvent.java @@ -0,0 +1,56 @@ +package org.linear.linearbot.event.server; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.player.KickedFromServerEvent; +import com.velocitypowered.api.event.player.PlayerChatEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import org.linear.linearbot.bot.Bot; +import org.linear.linearbot.internal.Config; +import org.linear.linearbot.tool.StringTool; + +import java.util.List; + +public class ServerEvent { + @Subscribe + public void onPlayerChat(PlayerChatEvent event) { + if (!Config.config.Forwarding){ + return; + } + String name = StringTool.filterColor(event.getPlayer().getUsername()); + String message = StringTool.filterColor(event.getMessage()); + List groups = Config.bot.Groups; + for (long groupID : groups){ + Bot.sendMsg("[" + event.getPlayer().getCurrentServer().get().getServer().getServerInfo().getName() + "]" + name+":"+message,groupID); + } + } + + @Subscribe + public void onJoin(ServerConnectedEvent event){ + + String name = StringTool.filterColor(event.getPlayer().getUsername()); + + if (!Config.config.JoinAndLeave){ + return; + } + List groups = Config.bot.Groups; + for (long groupID : groups){ + Bot.sendMsg("玩家"+name+"加入服务器",groupID); + } + + } + + @Subscribe + public void onQuit(KickedFromServerEvent event){ + + String name = StringTool.filterColor(event.getPlayer().getUsername()); + + if (!Config.config.JoinAndLeave){ + return; + } + List groups = Config.bot.Groups; + for (long groupID : groups){ + Bot.sendMsg("玩家"+name+"退出服务器",groupID); + } + } + +} diff --git a/LinearBot-velocity/src/main/java/org/linear/linearbot/internal/Config.java b/LinearBot-velocity/src/main/java/org/linear/linearbot/internal/Config.java new file mode 100644 index 0000000..5b032f6 --- /dev/null +++ b/LinearBot-velocity/src/main/java/org/linear/linearbot/internal/Config.java @@ -0,0 +1,32 @@ +package org.linear.linearbot.internal; + +import java.io.File; +import java.util.List; + +public class Config { + public static File PluginDir; + + public static class bot{ // bot + public static String Ver; // version + + public static class Bot{ + public static long QQ; // bot's QQ + } + public static List Groups; // group's number + public static List Admins; // admin's QQ + } + + public static class config{ // bot + public static String Ver; // version + public static boolean Forwarding; // 消息转发 + public static boolean JoinAndLeave; // 进出游戏 + public static boolean Online; // 在线人数 + public static boolean SDR; // 自定义回复 + + } + + public static class returns{ // commands + public static String Ver; // version + + } +} diff --git a/LinearBot-velocity/src/main/java/org/linear/linearbot/metrics/Metrics.java b/LinearBot-velocity/src/main/java/org/linear/linearbot/metrics/Metrics.java new file mode 100644 index 0000000..2f8319c --- /dev/null +++ b/LinearBot-velocity/src/main/java/org/linear/linearbot/metrics/Metrics.java @@ -0,0 +1,1041 @@ +/* + * This Metrics class was auto-generated and can be copied into your project if you are + * not using a build tool like Gradle or Maven for dependency management. + * + * IMPORTANT: You are not allowed to modify this class, except changing the package. + * + * Unallowed modifications include but are not limited to: + * - Remove the option for users to opt-out + * - Change the frequency for data submission + * - Obfuscate the code (every obfucator should allow you to make an exception for specific files) + * - Reformat the code (if you use a linter, add an exception) + * + * Violations will result in a ban of your plugin and account from bStats. + */ +package org.linear.linearbot.metrics; + +import com.google.inject.Inject; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; +import javax.net.ssl.HttpsURLConnection; +import org.slf4j.Logger; + +public class Metrics { + + /** A factory to create new Metrics classes. */ + public static class Factory { + + private final ProxyServer server; + + private final Logger logger; + + private final Path dataDirectory; + + // The constructor is not meant to be called by the user. + // The instance is created using Dependency Injection + @Inject + private Factory(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { + this.server = server; + this.logger = logger; + this.dataDirectory = dataDirectory; + } + + /** + * Creates a new Metrics class. + * + * @param plugin The plugin instance. + * @param serviceId The id of the service. It can be found at What is my plugin id? + *

Not to be confused with Velocity's {@link PluginDescription#getId()} method! + * @return A Metrics instance that can be used to register custom charts. + *

The return value can be ignored, when you do not want to register custom charts. + */ + public Metrics make(Object plugin, int serviceId) { + return new Metrics(plugin, server, logger, dataDirectory, serviceId); + } + } + + private final PluginContainer pluginContainer; + + private final ProxyServer server; + + private MetricsBase metricsBase; + + private Metrics( + Object plugin, ProxyServer server, Logger logger, Path dataDirectory, int serviceId) { + pluginContainer = + server + .getPluginManager() + .fromInstance(plugin) + .orElseThrow( + () -> new IllegalArgumentException("The provided instance is not a plugin")); + this.server = server; + File configFile = dataDirectory.getParent().resolve("bStats").resolve("config.txt").toFile(); + MetricsConfig config; + try { + config = new MetricsConfig(configFile, true); + } catch (IOException e) { + logger.error("Failed to create bStats config", e); + return; + } + metricsBase = + new MetricsBase( + "velocity", + config.getServerUUID(), + serviceId, + config.isEnabled(), + this::appendPlatformData, + this::appendServiceData, + task -> server.getScheduler().buildTask(plugin, task).schedule(), + () -> true, + logger::warn, + logger::info, + config.isLogErrorsEnabled(), + config.isLogSentDataEnabled(), + config.isLogResponseStatusTextEnabled()); + if (!config.didExistBefore()) { + // Send an info message when the bStats config file gets created for the first time + logger.info( + "Velocity and some of its plugins collect metrics and send them to bStats (https://bStats.org)."); + logger.info( + "bStats collects some basic information for plugin authors, like how many people use"); + logger.info( + "their plugin and their total player count. It's recommend to keep bStats enabled, but"); + logger.info( + "if you're not comfortable with this, you can opt-out by editing the config.txt file in"); + logger.info("the '/plugins/bStats/' folder and setting enabled to false."); + } + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (metricsBase != null) { + metricsBase.addCustomChart(chart); + } + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", server.getPlayerCount()); + builder.appendField("managedServers", server.getAllServers().size()); + builder.appendField("onlineMode", server.getConfiguration().isOnlineMode() ? 1 : 0); + builder.appendField("velocityVersionVersion", server.getVersion().getVersion()); + builder.appendField("velocityVersionName", server.getVersion().getName()); + builder.appendField("velocityVersionVendor", server.getVersion().getVendor()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } + + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField( + "pluginVersion", pluginContainer.getDescription().getVersion().orElse("unknown")); + } + + public static class MetricsBase { + + /** The version of the Metrics class. */ + public static final String METRICS_VERSION = "3.0.0"; + + private static final ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is enabled. + * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be + * used to delegate the data collection to a another thread to prevent errors caused by + * concurrency. Can be {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. + * @param errorLogger A consumer that accepts log message and an error. + * @param infoLogger A consumer that accepts info log messages. + * @param logErrors Whether or not errors should be logged. + * @param logSentData Whether or not the sent data should be logged. + * @param logResponseStatusText Whether or not the response status text should be logged. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText) { + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + checkRelocation(); + if (enabled) { + // WARNING: Removing the option to opt-out will get your plugin banned from bStats + startSubmitting(); + } + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + private void startSubmitting() { + final Runnable submitTask = + () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution + // of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial + // and second delay. + // WARNING: You must not modify and part of this Metrics class, including the submit delay or + // frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = + customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); + } + } + }); + } + + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); + } + } + + /** Checks that the class was properly relocated. */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little + // "trick" ... :D + final String defaultPackage = + new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); + final String examplePackage = + new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure no one just copy & pastes the example and uses the wrong package + // names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + /** + * Gzips the given string. + * + * @param str The string to gzip. + * @return The gzipped string. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + } + + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

While this class is neither feature-rich nor the most performant one, it's sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * + *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * A super simple representation of a JSON object. + * + *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not + * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, + * JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } + + /** + * A simple config for bStats. + * + *

This class is not used by every platform. + */ + public static class MetricsConfig { + + private final File file; + + private final boolean defaultEnabled; + + private String serverUUID; + + private boolean enabled; + + private boolean logErrors; + + private boolean logSentData; + + private boolean logResponseStatusText; + + private boolean didExistBefore = true; + + public MetricsConfig(File file, boolean defaultEnabled) throws IOException { + this.file = file; + this.defaultEnabled = defaultEnabled; + setupConfig(); + } + + public String getServerUUID() { + return serverUUID; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isLogErrorsEnabled() { + return logErrors; + } + + public boolean isLogSentDataEnabled() { + return logSentData; + } + + public boolean isLogResponseStatusTextEnabled() { + return logResponseStatusText; + } + + /** + * Checks whether the config file did exist before or not. + * + * @return If the config did exist before. + */ + public boolean didExistBefore() { + return didExistBefore; + } + + /** Creates the config file if it does not exist and read its content. */ + private void setupConfig() throws IOException { + if (!file.exists()) { + // Looks like it's the first time we create it (or someone deleted it). + didExistBefore = false; + writeConfig(); + } + readConfig(); + if (serverUUID == null) { + // Found a malformed config file with no UUID. Let's recreate it. + writeConfig(); + readConfig(); + } + } + + /** Creates a config file with teh default content. */ + private void writeConfig() throws IOException { + List configContent = new ArrayList<>(); + configContent.add( + "# bStats (https://bStats.org) collects some basic information for plugin authors, like"); + configContent.add( + "# how many people use their plugin and their total player count. It's recommended to keep"); + configContent.add( + "# bStats enabled, but if you're not comfortable with this, you can turn this setting off."); + configContent.add( + "# There is no performance penalty associated with having metrics enabled, and data sent to"); + configContent.add("# bStats is fully anonymous."); + configContent.add("enabled=" + defaultEnabled); + configContent.add("server-uuid=" + UUID.randomUUID().toString()); + configContent.add("log-errors=false"); + configContent.add("log-sent-data=false"); + configContent.add("log-response-status-text=false"); + writeFile(file, configContent); + } + + /** Reads the content of the config file. */ + private void readConfig() throws IOException { + List lines = readFile(file); + if (lines == null) { + throw new AssertionError("Content of newly created file is null"); + } + enabled = getConfigValue("enabled", lines).map("true"::equals).orElse(true); + serverUUID = getConfigValue("server-uuid", lines).orElse(null); + logErrors = getConfigValue("log-errors", lines).map("true"::equals).orElse(false); + logSentData = getConfigValue("log-sent-data", lines).map("true"::equals).orElse(false); + logResponseStatusText = + getConfigValue("log-response-status-text", lines).map("true"::equals).orElse(false); + } + + /** + * Gets a config setting from the given list of lines of the file. + * + * @param key The key for the setting. + * @param lines The lines of the file. + * @return The value of the setting. + */ + private Optional getConfigValue(String key, List lines) { + return lines.stream() + .filter(line -> line.startsWith(key + "=")) + .map(line -> line.replaceFirst(Pattern.quote(key + "="), "")) + .findFirst(); + } + + /** + * Reads the text content of the given file. + * + * @param file The file to read. + * @return The lines of the given file. + */ + private List readFile(File file) throws IOException { + if (!file.exists()) { + return null; + } + try (FileReader fileReader = new FileReader(file); + BufferedReader bufferedReader = new BufferedReader(fileReader)) { + return bufferedReader.lines().collect(Collectors.toList()); + } + } + + /** + * Writes the given lines to the given file. + * + * @param file The file to write to. + * @param lines The lines to write. + */ + private void writeFile(File file, List lines) throws IOException { + if (!file.exists()) { + file.getParentFile().mkdirs(); + file.createNewFile(); + } + try (FileWriter fileWriter = new FileWriter(file); + BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { + for (String line : lines) { + bufferedWriter.write(line); + bufferedWriter.newLine(); + } + } + } + } +} \ No newline at end of file diff --git a/LinearBot-velocity/src/main/java/org/linear/linearbot/tool/StringTool.java b/LinearBot-velocity/src/main/java/org/linear/linearbot/tool/StringTool.java new file mode 100644 index 0000000..a43379c --- /dev/null +++ b/LinearBot-velocity/src/main/java/org/linear/linearbot/tool/StringTool.java @@ -0,0 +1,16 @@ +package org.linear.linearbot.tool; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class StringTool { + + public static String filterColor(String text){ + + String regEx = "§[0-9a-zA-Z]"; + Pattern p = Pattern.compile(regEx); + Matcher matcher = p.matcher(text); + return matcher.replaceAll("").trim(); + + } +} diff --git a/LinearBot-velocity/src/main/resources/bot.yml b/LinearBot-velocity/src/main/resources/bot.yml new file mode 100644 index 0000000..95b2790 --- /dev/null +++ b/LinearBot-velocity/src/main/resources/bot.yml @@ -0,0 +1,14 @@ +#Bot配置 +Ver: '1.1' + +Bot: + QQ: 111 + #机器人的qq号,一个长整形 + +Groups: + - 111 + #启用消息转发功能的群聊qq,也是一个长整形 + +Admins: + - 111 + #管理员的qq,还是一个长整形 diff --git a/LinearBot-velocity/src/main/resources/config.yml b/LinearBot-velocity/src/main/resources/config.yml new file mode 100644 index 0000000..4c1ffa8 --- /dev/null +++ b/LinearBot-velocity/src/main/resources/config.yml @@ -0,0 +1,14 @@ +#机器人功能设置 +Ver: '1.2' + +#消息转发 +Forwarding: true + +#进出提示 +JoinAndLeave: true + +#在线玩家查询 +Online: true + +#自定义回复 +SDR: true \ No newline at end of file diff --git a/LinearBot-velocity/src/main/resources/returns.yml b/LinearBot-velocity/src/main/resources/returns.yml new file mode 100644 index 0000000..10ae1ee --- /dev/null +++ b/LinearBot-velocity/src/main/resources/returns.yml @@ -0,0 +1,4 @@ +#自定义回复配置 +Ver: '1.2' + +自定义回复的关键词: 自定义回复的内容 diff --git a/LinearBot.iml b/LinearBot.iml index d96c31e..495426b 100644 --- a/LinearBot.iml +++ b/LinearBot.iml @@ -1,5 +1,8 @@ + + @@ -9,4 +12,9 @@ + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 73e1b2b..87b2d90 100644 --- a/pom.xml +++ b/pom.xml @@ -1,145 +1,91 @@ - - - 4.0.0 - - org.linear - LinearBot - 1.2.1 - jar - - LinearBot - - - 1.8 - UTF-8 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${java.version} - ${java.version} - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.4 - - - package - - shade - - - false - - - - - - - - src/main/resources - true - - - - - - - jitpack.io - https://jitpack.io - - - placeholderapi - https://repo.extendedclip.com/content/repositories/placeholderapi/ - - - purpur - https://repo.purpurmc.org/snapshots - - - codemc-repo - https://repo.codemc.io/repository/maven-public/ - - - quickshop-repo - https://repo.codemc.io/repository/maven-public/ - - - - - - - org.purpurmc.purpur - purpur-api - 1.16.5-R0.1-SNAPSHOT - provided - - - io.github.dreamvoid - MiraiMC-Integration - - 1.7.1 - provided - - - me.clip - placeholderapi - 2.10.3 - provided - - - fr.xephi - authme - 5.6.0-SNAPSHOT - provided - - - org.maxgamer - QuickShop - 5.1.1.2 - provided - - - com.ghostchu - quickshop-bukkit - 3.6.1.5 - provided - shaded - - - com.bekvon.bukkit.residence - Residence - 5.1.0.1 - system - ${project.basedir}/lib/Residence5.1.0.1.jar - - - - - - - + + + 4.0.0 + + org.linear + LinearBot + 1.2.1 + pom + + LinearBot + + LinearBot-velocity + LinearBot-bukkit + + + + 1.8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + jitpack.io + https://jitpack.io + + + + + + + io.github.dreamvoid + MiraiMC-Integration + + 1.7.1 + provided + + + + + + +