diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 640dd04..f4dd1cb 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -7,16 +7,21 @@ on: jobs: build: + name: Build and Test runs-on: ubuntu-latest - + steps: - uses: actions/checkout@v4 - - name: Set up JDK 17.0.2 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: 17.0 + java-version: '17' distribution: 'zulu' - cache: 'maven' + cache: maven + - name: Set up Maven + uses: stCarolas/setup-maven@v5 + with: + maven-version: 3.9.6 - name: Compile run: mvn -B package --file pom.xml env: @@ -25,4 +30,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: AntiVPN - path: Assembly/target/Assembly-*.jar + path: Universal/target/AntiVPN-*.jar diff --git a/Bukkit/pom.xml b/Bukkit/pom.xml index 0cc59d1..cda1424 100644 --- a/Bukkit/pom.xml +++ b/Bukkit/pom.xml @@ -16,17 +16,17 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.13.0 - 8 - 8 + 17 + 17 -XDignore.symbol.file org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 package @@ -45,10 +45,6 @@ org.yaml.snakeyaml dev.brighten.antivpn.shaded.org.yaml.snakeyaml - - com.google.common - dev.brighten.antivpn.shaded.com.google.common - @@ -64,10 +60,17 @@ - 8 - 8 + 17 + 17 + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + org.spigotmc diff --git a/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java b/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java index 8736537..1ded919 100644 --- a/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java +++ b/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java @@ -1,7 +1,7 @@ package dev.brighten.antivpn.bukkit; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.VPNExecutor; @@ -24,7 +24,7 @@ import java.util.logging.Level; @SuppressWarnings("unchecked") public class BukkitListener extends VPNExecutor implements Listener { - private final Cache responseCache = CacheBuilder.newBuilder() + private final Cache responseCache = Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) .maximumSize(2000) .build(); @@ -35,11 +35,6 @@ public class BukkitListener extends VPNExecutor implements Listener { .registerEvents(this, BukkitPlugin.pluginInstance); } - @Override - public void onShutdown() { - - } - @Override public void log(Level level, String log, Object... objects) { Bukkit.getLogger().log(level, String.format(log, objects)); @@ -51,7 +46,7 @@ public class BukkitListener extends VPNExecutor implements Listener { } @Override - public void logException(String message, Exception ex) { + public void logException(String message, Throwable ex) { Bukkit.getLogger().log(Level.SEVERE, message, ex); } @@ -99,43 +94,42 @@ public class BukkitListener extends VPNExecutor implements Listener { } final Player player = event.getPlayer(); - checkIp(address, - AntiVPN.getInstance().getVpnConfig().cachedResults(), result -> { - if(result.isSuccess()) { - //We need to run on main thread or kicking and running commands will cause errors - //If the player is whitelisted, we don't want to kick them - if(AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer().getUniqueId())) { - log("UUID is whitelisted: %s", event.getPlayer().getUniqueId().toString()); - return; - } + checkIp(address).thenAccept(result -> { + if(result.isSuccess()) { + //We need to run on main thread or kicking and running commands will cause errors + //If the player is whitelisted, we don't want to kick them + if(AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer().getUniqueId())) { + log("UUID is whitelisted: %s", event.getPlayer().getUniqueId().toString()); + return; + } - //If the IP is whitelisted, we don't want to kick them - if (AntiVPN.getInstance().getExecutor().isWhitelisted(address)) { - log("IP is whitelisted: %s", - address); - return; - } + //If the IP is whitelisted, we don't want to kick them + if (AntiVPN.getInstance().getExecutor().isWhitelisted(address)) { + log("IP is whitelisted: %s", + address); + return; + } - // If the countryList() size is zero, no need to check. - // Running country check first - if(!AntiVPN.getInstance().getVpnConfig().countryList().isEmpty() - // This bit of code will decide whether to kick the player - // If contains the code, and set to whitelist, it will not kick as they are equal - // and vise versa. However, if the contains does not match the state, it will kick. - && AntiVPN.getInstance().getVpnConfig().countryList() - .contains(result.getCountryCode()) - != AntiVPN.getInstance().getVpnConfig().whitelistCountries()) { - countryKick(player, result); - } else if(result.isProxy()) { - proxyKick(player, result); - } - } else { - log(Level.WARNING, - "The API query was not a success! " + - "You may need to upgrade your license on https://funkemunky.cc/shop"); - } - AntiVPN.getInstance().checked++; - }); + // If the countryList() size is zero, no need to check. + // Running country check first + if(!AntiVPN.getInstance().getVpnConfig().countryList().isEmpty() + // This bit of code will decide whether to kick the player + // If contains the code, and set to whitelist, it will not kick as they are equal + // and vise versa. However, if the contains does not match the state, it will kick. + && AntiVPN.getInstance().getVpnConfig().countryList() + .contains(result.getCountryCode()) + != AntiVPN.getInstance().getVpnConfig().whitelistCountries()) { + countryKick(player, result); + } else if(result.isProxy()) { + proxyKick(player, result); + } + } else { + log(Level.WARNING, + "The API query was not a success! " + + "You may need to upgrade your license on https://funkemunky.cc/shop"); + } + AntiVPN.getInstance().checked++; + }); } private void countryKick(Player player, VPNResponse result) { diff --git a/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java b/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java index 2b67350..fcd48a0 100644 --- a/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java +++ b/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java @@ -3,9 +3,13 @@ package dev.brighten.antivpn.bukkit; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.bukkit.command.BukkitCommand; import dev.brighten.antivpn.command.Command; +import dev.brighten.antivpn.database.VPNDatabase; +import dev.brighten.antivpn.database.local.H2VPN; +import dev.brighten.antivpn.database.mongo.MongoVPN; +import dev.brighten.antivpn.database.sql.MySqlVPN; import lombok.Getter; import org.bstats.bukkit.Metrics; -import org.bstats.charts.SingleLineChart; +import org.bstats.charts.SimplePie; import org.bukkit.Bukkit; import org.bukkit.command.SimpleCommandMap; import org.bukkit.event.HandlerList; @@ -24,8 +28,6 @@ public class BukkitPlugin extends JavaPlugin { private SimpleCommandMap commandMap; private final List registeredCommands = new ArrayList<>(); - @Getter - private SingleLineChart vpnDetections, ipsChecked; @Getter private PlayerCommandRunner playerCommandRunner; @@ -42,10 +44,7 @@ public class BukkitPlugin extends JavaPlugin { if(AntiVPN.getInstance().getVpnConfig().metrics()) { Bukkit.getLogger().info("Starting bStats metrics..."); Metrics metrics = new Metrics(this, 12615); - metrics.addCustomChart(vpnDetections = new SingleLineChart("vpn_detections", - () -> AntiVPN.getInstance().detections)); - metrics.addCustomChart(ipsChecked = new SingleLineChart("ips_checked", - () -> AntiVPN.getInstance().checked)); + metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); new BukkitRunnable() { public void run() { AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0; @@ -55,14 +54,13 @@ public class BukkitPlugin extends JavaPlugin { Bukkit.getLogger().info("Setting up and registering commands..."); // We need access to the commandMap to register our commands without using the "proper" method - if (pluginInstance.getServer().getPluginManager() instanceof SimplePluginManager) { - SimplePluginManager manager = (SimplePluginManager) pluginInstance.getServer().getPluginManager(); + if (pluginInstance.getServer().getPluginManager() instanceof SimplePluginManager manager) { try { Field field = SimplePluginManager.class.getDeclaredField("commandMap"); field.setAccessible(true); commandMap = (SimpleCommandMap) field.get(manager); } catch (IllegalArgumentException | SecurityException | NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); + AntiVPN.getInstance().getExecutor().logException(e); } } @@ -89,6 +87,7 @@ public class BukkitPlugin extends JavaPlugin { } @Override + @SuppressWarnings("unchecked") public void onDisable() { Bukkit.getLogger().info("Stopping plugin services..."); AntiVPN.getInstance().stop(); @@ -98,13 +97,15 @@ public class BukkitPlugin extends JavaPlugin { try { Field field = SimpleCommandMap.class.getDeclaredField("knownCommands"); field.setAccessible(true); - Map knownCommands = - (Map) field.get(commandMap); - knownCommands.values().removeAll(registeredCommands); - registeredCommands.clear(); + if(field.get(commandMap) instanceof Map knownCommands) { + Map casted = (Map) knownCommands; + casted.values().removeAll(registeredCommands); + registeredCommands.clear(); + } + } catch (IllegalAccessException | NoSuchFieldException e) { - e.printStackTrace(); + AntiVPN.getInstance().getExecutor().logException(e); } Bukkit.getLogger().info("Unregistering listeners..."); @@ -113,4 +114,18 @@ public class BukkitPlugin extends JavaPlugin { Bukkit.getLogger().info("Cancelling any running tasks..."); Bukkit.getScheduler().cancelTasks(this); } + + private String getDatabaseType() { + VPNDatabase database = AntiVPN.getInstance().getDatabase(); + + if(database instanceof H2VPN) { + return "H2"; + } else if(database instanceof MySqlVPN) { + return "MySQL"; + } else if(database instanceof MongoVPN) { + return "MongoDB"; + } else { + return "No-Database"; + } + } } diff --git a/Bungee/pom.xml b/Bungee/pom.xml index 06e4cff..9135c8d 100644 --- a/Bungee/pom.xml +++ b/Bungee/pom.xml @@ -16,17 +16,17 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.13.0 - 8 - 8 + 17 + 17 -XDignore.symbol.file org.apache.maven.plugins maven-shade-plugin - 3.1.0 + 3.6.0 @@ -38,10 +38,6 @@ org.yaml.snakeyaml dev.brighten.antivpn.shaded.org.yaml.snakeyaml - - com.google - dev.brighten.antivpn.shaded.com.google - @@ -63,8 +59,8 @@ - 8 - 8 + 17 + 17 @@ -75,9 +71,10 @@ provided - org.github.bungee - BungeeCord-1.8 - 1.8 + net.md-5 + bungeecord-api + 1.21-R0.2 + jar provided diff --git a/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java b/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java index 30d6250..ea8344b 100644 --- a/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java +++ b/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java @@ -1,12 +1,11 @@ package dev.brighten.antivpn.bungee; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.web.objects.VPNResponse; -import net.md_5.bungee.BungeeCord; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.event.PlayerDisconnectEvent; @@ -25,7 +24,7 @@ public class BungeeListener extends VPNExecutor implements Listener { private ScheduledTask cacheResetTask; - private final Cache responseCache = CacheBuilder.newBuilder() + private final Cache responseCache = Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) .maximumSize(2000) .build(); @@ -36,18 +35,9 @@ public class BungeeListener extends VPNExecutor implements Listener { .registerListener(BungeePlugin.pluginInstance, this); } - @Override - public void onShutdown() { - if(cacheResetTask != null) { - cacheResetTask.cancel(); - cacheResetTask = null; - } - BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterListener(this); - } - @Override public void log(Level level, String log, Object... objects) { - BungeeCord.getInstance().getLogger().log(Level.INFO, String.format(log, objects)); + BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.INFO, String.format(log, objects)); } @Override @@ -56,18 +46,18 @@ public class BungeeListener extends VPNExecutor implements Listener { } @Override - public void logException(String message, Exception ex) { - BungeeCord.getInstance().getLogger().log(Level.SEVERE, message, ex); + public void logException(String message, Throwable ex) { + BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.SEVERE, message, ex); } @Override public void disablePlugin() { - BungeeCord.getInstance().getPluginManager().unregisterListeners(BungeePlugin.pluginInstance); + BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterListeners(BungeePlugin.pluginInstance); if(cacheResetTask != null) { cacheResetTask.cancel(); cacheResetTask = null; } - BungeeCord.getInstance().getPluginManager().unregisterCommands(BungeePlugin.pluginInstance); + BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterCommands(BungeePlugin.pluginInstance); BungeePlugin.pluginInstance.onDisable(); } @@ -79,7 +69,7 @@ public class BungeeListener extends VPNExecutor implements Listener { if(cached != null && cached.isProxy()) { event.setCancelled(true); - event.setCancelReason(TextComponent.fromLegacyText(ChatColor + event.setReason(TextComponent.fromLegacy(ChatColor .translateAlternateColorCodes('&', AntiVPN.getInstance().getVpnConfig().getKickString()))); AntiVPN.getInstance().getExecutor().log(Level.INFO, @@ -94,92 +84,92 @@ public class BungeeListener extends VPNExecutor implements Listener { || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() .anyMatch(prefix -> event.getPlayer().getName().startsWith(prefix))) return; - checkIp(event.getPlayer().getAddress().getAddress().getHostAddress(), - AntiVPN.getInstance().getVpnConfig().cachedResults(), result -> { - if(result.isSuccess()) { - //If the player is whitelisted, we don't want to kick them - if(AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer().getUniqueId())) { - AntiVPN.getInstance().getExecutor().log("UUID is whitelisted: %s", - event.getPlayer().getUniqueId().toString()); - return; - } + String address = event.getPlayer().getSocketAddress().toString(); - //If the IP is whitelisted, we don't want to kick them - if(AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer().getAddress().getAddress() - .getHostAddress())) { - AntiVPN.getInstance().getExecutor().log("IP is whitelisted: %s", - event.getPlayer().getAddress().getAddress().getHostAddress()); - return; - } + if(AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer().getUniqueId())) { + AntiVPN.getInstance().getExecutor().log("UUID is whitelisted: %s", + event.getPlayer().getUniqueId().toString()); + return; + } - responseCache.put(event.getPlayer().getUniqueId(), result); + //If the IP is whitelisted, we don't want to kick them + if(AntiVPN.getInstance().getExecutor().isWhitelisted(address)) { + AntiVPN.getInstance().getExecutor().log("IP is whitelisted: %s", address); + return; + } - if(!AntiVPN.getInstance().getVpnConfig().countryList().isEmpty() - // This bit of code will decide whether or not to kick the player - // If it contains the code and it is set to whitelist, it will not kick as they are equal - // and vise versa. However, if the contains does not match the state, it will kick. - && AntiVPN.getInstance().getVpnConfig().countryList() - .contains(result.getCountryCode()) != AntiVPN.getInstance().getVpnConfig().whitelistCountries()) { - //Using our built in kicking system if no commands are configured - if(AntiVPN.getInstance().getVpnConfig().countryKickCommands().isEmpty()) { - final String kickReason = AntiVPN.getInstance().getVpnConfig() - .countryVanillaKickReason(); - // Kicking our player - event.getPlayer().disconnect(TextComponent.fromLegacyText(ChatColor - .translateAlternateColorCodes('&', - kickReason + checkIp(address) + .thenAccept(result -> { + if(result.isSuccess()) { + //If the player is whitelisted, we don't want to kick them + responseCache.put(event.getPlayer().getUniqueId(), result); + if(!AntiVPN.getInstance().getVpnConfig().countryList().isEmpty() + // This bit of code will decide whether or not to kick the player + // If it contains the code and it is set to whitelist, it will not kick as they are equal + // and vise versa. However, if the contains does not match the state, it will kick. + && AntiVPN.getInstance().getVpnConfig().countryList() + .contains(result.getCountryCode()) != AntiVPN.getInstance().getVpnConfig().whitelistCountries()) { + //Using our built in kicking system if no commands are configured + if(AntiVPN.getInstance().getVpnConfig().countryKickCommands().isEmpty()) { + final String kickReason = AntiVPN.getInstance().getVpnConfig() + .countryVanillaKickReason(); + // Kicking our player + event.getPlayer().disconnect(TextComponent.fromLegacy(ChatColor + .translateAlternateColorCodes('&', + kickReason + .replace("%player%", event.getPlayer().getName()) + .replace("%country%", result.getCountryName()) + .replace("%code%", result.getCountryCode())))); + } else { + for (String cmd : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { + final String formattedCommand = ChatColor.translateAlternateColorCodes('&', + cmd.replace("%player%", event.getPlayer().getName()) + .replace("%country%", result.getCountryName()) + .replace("%code%", result.getCountryCode())); + + // Runs our command from console + BungeePlugin.pluginInstance.getProxy().getPluginManager().dispatchCommand( + BungeePlugin.pluginInstance.getProxy().getConsole(), formattedCommand); + } + } + } else if(result.isProxy()) { + if(AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) + event.getPlayer().disconnect(TextComponent.fromLegacy(ChatColor + .translateAlternateColorCodes('&', + AntiVPN.getInstance().getVpnConfig().getKickString()))); + BungeePlugin.pluginInstance.getProxy().getLogger().info(event.getPlayer().getName() + + " joined on a VPN/Proxy (" + result.getMethod() + ")"); + + if(AntiVPN.getInstance().getVpnConfig().alertToStaff()) //Ensuring the user wishes to alert to staff + AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() + .filter(APIPlayer::isAlertsEnabled) + .forEach(pl -> pl.sendMessage(AntiVPN.getInstance().getVpnConfig() + .alertMessage() .replace("%player%", event.getPlayer().getName()) + .replace("%reason%", result.getMethod()) .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode())))); + .replace("%city%", result.getCity()))); + + //In case the user wants to run their own commands instead of using the built in kicking + if(AntiVPN.getInstance().getVpnConfig().runCommands()) { + for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { + BungeePlugin.pluginInstance.getProxy().getPluginManager() + .dispatchCommand(BungeePlugin.pluginInstance.getProxy().getConsole(), + ChatColor.translateAlternateColorCodes('&', + command.replace("%player%", event.getPlayer().getName()))); + } + } + AntiVPN.getInstance().detections++; + } + } else { - for (String cmd : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { - final String formattedCommand = ChatColor.translateAlternateColorCodes('&', - cmd.replace("%player%", event.getPlayer().getName()) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode())); - - // Runs our command from console - BungeeCord.getInstance().getPluginManager().dispatchCommand( - BungeeCord.getInstance().getConsole(), formattedCommand); - } + BungeePlugin.pluginInstance.getProxy().getLogger() + .log(Level.WARNING, + "The API query was not a success! " + + "You may need to upgrade your license on https://funkemunky.cc/shop"); } - } else if(result.isProxy()) { - if(AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) - event.getPlayer().disconnect(TextComponent.fromLegacyText(ChatColor - .translateAlternateColorCodes('&', - AntiVPN.getInstance().getVpnConfig().getKickString()))); - BungeeCord.getInstance().getLogger().info(event.getPlayer().getName() - + " joined on a VPN/Proxy (" + result.getMethod() + ")"); - - if(AntiVPN.getInstance().getVpnConfig().alertToStaff()) //Ensuring the user wishes to alert to staff - AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() - .filter(APIPlayer::isAlertsEnabled) - .forEach(pl -> pl.sendMessage(AntiVPN.getInstance().getVpnConfig().alertMessage() - .replace("%player%", event.getPlayer().getName()) - .replace("%reason%", result.getMethod()) - .replace("%country%", result.getCountryName()) - .replace("%city%", result.getCity()))); - - //In case the user wants to run their own commands instead of using the built in kicking - if(AntiVPN.getInstance().getVpnConfig().runCommands()) { - for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { - BungeeCord.getInstance().getPluginManager() - .dispatchCommand(BungeeCord.getInstance().getConsole(), - ChatColor.translateAlternateColorCodes('&', - command.replace("%player%", event.getPlayer().getName()))); - } - } - AntiVPN.getInstance().detections++; - } - - } else { - BungeeCord.getInstance().getLogger() - .log(Level.WARNING, - "The API query was not a success! " + - "You may need to upgrade your license on https://funkemunky.cc/shop"); - } - AntiVPN.getInstance().checked++; - }); + AntiVPN.getInstance().checked++; + }); } @EventHandler diff --git a/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeePlayerExecutor.java b/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeePlayerExecutor.java index b5e41fb..ab77f95 100644 --- a/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeePlayerExecutor.java +++ b/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeePlayerExecutor.java @@ -2,7 +2,6 @@ package dev.brighten.antivpn.bungee; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.PlayerExecutor; -import net.md_5.bungee.BungeeCord; import net.md_5.bungee.api.connection.ProxiedPlayer; import java.util.*; @@ -14,7 +13,7 @@ public class BungeePlayerExecutor implements PlayerExecutor { @Override public Optional getPlayer(String name) { - ProxiedPlayer player = BungeeCord.getInstance().getPlayer(name); + ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(name); if(player == null) return Optional.empty(); @@ -23,7 +22,7 @@ public class BungeePlayerExecutor implements PlayerExecutor { @Override public Optional getPlayer(UUID uuid) { - ProxiedPlayer player = BungeeCord.getInstance().getPlayer(uuid); + ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(uuid); if(player == null) return Optional.empty(); @@ -37,7 +36,7 @@ public class BungeePlayerExecutor implements PlayerExecutor { @Override public List getOnlinePlayers() { - return BungeeCord.getInstance().getPlayers().stream() + return BungeePlugin.pluginInstance.getProxy().getPlayers().stream() .map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), key -> new BungeePlayer(pl))) .collect(Collectors.toList()); } diff --git a/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java b/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java index bb54d83..15e29bc 100644 --- a/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java +++ b/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java @@ -3,10 +3,13 @@ package dev.brighten.antivpn.bungee; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.bungee.command.BungeeCommand; import dev.brighten.antivpn.command.Command; -import net.md_5.bungee.BungeeCord; +import dev.brighten.antivpn.database.VPNDatabase; +import dev.brighten.antivpn.database.local.H2VPN; +import dev.brighten.antivpn.database.mongo.MongoVPN; +import dev.brighten.antivpn.database.sql.MySqlVPN; import net.md_5.bungee.api.plugin.Plugin; import org.bstats.bungeecord.Metrics; -import org.bstats.charts.SingleLineChart; +import org.bstats.charts.SimplePie; import java.util.concurrent.TimeUnit; @@ -14,34 +17,29 @@ public class BungeePlugin extends Plugin { public static BungeePlugin pluginInstance; - private SingleLineChart vpnDetections, ipsChecked; - @Override public void onEnable() { pluginInstance = this; //Setting up config - BungeeCord.getInstance().getLogger().info("Loading config..."); + getProxy().getLogger().info("Loading config..."); //Loading plugin - BungeeCord.getInstance().getLogger().info("Starting AntiVPN services..."); + getProxy().getLogger().info("Starting AntiVPN services..."); AntiVPN.start(new BungeeListener(), new BungeePlayerExecutor(), getDataFolder()); if(AntiVPN.getInstance().getVpnConfig().metrics()) { - BungeeCord.getInstance().getLogger().info("Starting bStats metrics..."); + getProxy().getLogger().info("Starting bStats metrics..."); Metrics metrics = new Metrics(this, 12616); - metrics.addCustomChart(vpnDetections = new SingleLineChart("vpn_detections", - () -> AntiVPN.getInstance().detections)); - metrics.addCustomChart(ipsChecked = new SingleLineChart("ips_checked", - () -> AntiVPN.getInstance().checked)); - BungeeCord.getInstance().getScheduler().schedule(this, + metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); + getProxy().getScheduler().schedule(this, () -> AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0, 10, 10, TimeUnit.MINUTES); } for (Command command : AntiVPN.getInstance().getCommands()) { - BungeeCord.getInstance().getPluginManager().registerCommand(pluginInstance, new BungeeCommand(command)); + getProxy().getPluginManager().registerCommand(pluginInstance, new BungeeCommand(command)); } } @@ -49,4 +47,18 @@ public class BungeePlugin extends Plugin { public void onDisable() { AntiVPN.getInstance().stop(); } + + private String getDatabaseType() { + VPNDatabase database = AntiVPN.getInstance().getDatabase(); + + if(database instanceof H2VPN) { + return "H2"; + } else if(database instanceof MySqlVPN) { + return "MySQL"; + } else if(database instanceof MongoVPN) { + return "MongoDB"; + } else { + return "No-Database"; + } + } } diff --git a/Common/pom.xml b/Common/pom.xml index 82f313f..230cf7d 100644 --- a/Common/pom.xml +++ b/Common/pom.xml @@ -12,8 +12,8 @@ Common - 8 - 8 + 17 + 17 @@ -21,10 +21,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 + 3.13.0 - 8 - 8 + 17 + 17 -XDignore.symbol.file @@ -38,7 +38,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.4.1 + 3.6.0 package @@ -51,14 +51,65 @@ org.yaml.snakeyaml dev.brighten.antivpn.shaded.org.yaml.snakeyaml + + + dev.brighten.antivpn.depends.Relocate + dev.brighten.antivpn.depends.MavenLibraries + - com.google.common - dev.brighten.antivpn.shaded.com.google.common + com.google + dev.brighten.antivpn.shaded.com.google + + + dev.brighten.antivpn.depends.Relocate + dev.brighten.antivpn.depends.MavenLibraries + org.h2 dev.brighten.antivpn.shaded.org.h2 + + + dev.brighten.antivpn.depends.Relocate + dev.brighten.antivpn.depends.MavenLibraries + + + + org.bson + dev.brighten.antivpn.shaded.org.bson + + + dev.brighten.antivpn.depends.Relocate + dev.brighten.antivpn.depends.MavenLibraries + + + + com.mongodb + dev.brighten.antivpn.shaded.com.mongodb + + + dev.brighten.antivpn.depends.Relocate + dev.brighten.antivpn.depends.MavenLibraries + + + + com.mysql.cj + dev.brighten.antivpn.shaded.com.mysql.cj + + + dev.brighten.antivpn.depends.Relocate + dev.brighten.antivpn.depends.MavenLibraries + + + + com.mysql.jdbc + dev.brighten.antivpn.shaded.com.mysql.jdbc + + + dev.brighten.antivpn.depends.Relocate + dev.brighten.antivpn.depends.MavenLibraries + @@ -87,12 +138,24 @@ mysql-connector-j 9.1.0 jar - compile + provided com.h2database h2 2.2.220 + provided + + + org.ow2.asm + asm + 9.8 + compile + + + org.ow2.asm + asm-commons + 9.8 compile @@ -102,15 +165,17 @@ compile - com.google.guava - guava - 32.1.3-jre + com.github.ben-manes.caffeine + caffeine + 3.1.8 + compile + org.mongodb mongo-java-driver 3.12.14 - compile + provided diff --git a/Common/src/main/java/dev/brighten/antivpn/AntiVPN.java b/Common/src/main/java/dev/brighten/antivpn/AntiVPN.java index 9446bf8..3a24d96 100644 --- a/Common/src/main/java/dev/brighten/antivpn/AntiVPN.java +++ b/Common/src/main/java/dev/brighten/antivpn/AntiVPN.java @@ -9,6 +9,9 @@ import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.sql.MySqlVPN; +import dev.brighten.antivpn.depends.LibraryLoader; +import dev.brighten.antivpn.depends.MavenLibrary; +import dev.brighten.antivpn.depends.Relocate; import dev.brighten.antivpn.message.MessageHandler; import dev.brighten.antivpn.utils.ConfigDefault; import dev.brighten.antivpn.utils.MiscUtils; @@ -29,6 +32,21 @@ import java.util.List; @Getter @Setter(AccessLevel.PRIVATE) +@MavenLibrary(groupId = "com.h2database", artifactId ="h2", version = "2.2.220", relocations = { + @Relocate(from ="org" + ".\\h2", to ="dev.brighten.antivpn.shaded.org.h2")}) +@MavenLibrary(groupId = "org.mongodb", artifactId = "mongo-java-driver", version = "3.12.14", relocations = { + @Relocate(from = "com." + "\\mongodb", to = "dev.brighten.antivpn.shaded.com.mongodb"), + @Relocate(from = "org" + "\\.bson", to = "dev.brighten.antivpn.shaded.org.bson") +}) +@MavenLibrary( + groupId = "com.mysql", + artifactId = "mysql-connector-j", + version = "9.1.0", + relocations = { + @Relocate(from = "com.my\\" + "sql.cj", to = "dev.brighten.antivpn.shaded.com.mysql.cj"), + @Relocate(from = "com.my\\" + "sql.jdbc", to = "dev.brighten.antivpn.shaded.com.mysql.jdbc") + } +) public class AntiVPN { private static AntiVPN INSTANCE; @@ -51,6 +69,8 @@ public class AntiVPN { INSTANCE.executor = executor; INSTANCE.playerExecutor = playerExecutor; + LibraryLoader.loadAll(INSTANCE); + try { File configFile = new File(pluginFolder, "config.yml"); if(!configFile.exists()){ @@ -143,7 +163,20 @@ public class AntiVPN { } public void stop() { - executor.onShutdown(); + if (database instanceof H2VPN) { + database.shutdown(); + + // Try to deregister driver + try { + java.sql.Driver driver = java.sql.DriverManager.getDriver("jdbc:h2:"); + if (driver != null) { + java.sql.DriverManager.deregisterDriver(driver); + } + } catch (Exception e) { + // Log but don't throw + executor.log("Failed to deregister H2 driver: " + e.getMessage()); + } + } VPNExecutor.threadExecutor.shutdown(); if(database != null) database.shutdown(); } diff --git a/Common/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java b/Common/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java index 1ae8cd7..838e6d8 100644 --- a/Common/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java +++ b/Common/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java @@ -1,7 +1,5 @@ package dev.brighten.antivpn.api; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.utils.json.JSONException; import dev.brighten.antivpn.web.FunkemunkyAPI; @@ -10,11 +8,9 @@ import lombok.Getter; import java.io.IOException; import java.util.*; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import java.util.logging.Level; public abstract class VPNExecutor { @@ -24,23 +20,15 @@ public abstract class VPNExecutor { private final Set whitelisted = Collections.synchronizedSet(new HashSet<>()); @Getter private final Set whitelistedIps = Collections.synchronizedSet(new HashSet<>()); - - private final Cache responseCache = CacheBuilder.newBuilder() - .expireAfterWrite(20, TimeUnit.MINUTES) - .maximumSize(4000) - .build(); - public abstract void registerListeners(); - public abstract void onShutdown(); - public abstract void log(Level level, String log, Object... objects); public abstract void log(String log, Object... objects); - public abstract void logException(String message, Exception ex); + public abstract void logException(String message, Throwable ex); - public void logException(Exception ex) { + public void logException(Throwable ex) { logException("An exception occurred: " + ex.getMessage(), ex); } @@ -58,44 +46,31 @@ public abstract class VPNExecutor { return whitelistedIps.contains(ip); } - public void checkIp(String ip, boolean cachedResults, Consumer result) { - threadExecutor.execute(() -> { - if(cachedResults) { + public CompletableFuture checkIp(String ip) { + return CompletableFuture.supplyAsync(() -> { + Optional cachedRes = AntiVPN.getInstance().getDatabase().getStoredResponse(ip); + + if(cachedRes.isPresent()) { + return cachedRes.get(); + } + else { try { - result.accept(responseCache.get(ip, () -> checkIp(ip))); - } catch (ExecutionException e) { - log("Failed to process checkIp() method! Reason: " + e.getMessage()); - result.accept(VPNResponse.FAILED_RESPONSE); + VPNResponse response = FunkemunkyAPI + .getVPNResponse(ip, AntiVPN.getInstance().getVpnConfig().getLicense(), true); + + if (response.isSuccess()) { + AntiVPN.getInstance().getDatabase().cacheResponse(response); + } else { + log("Query to VPN API failed! Reason: " + response.getFailureReason()); + } + + return response; + } catch (JSONException | IOException e) { + log("Query to VPN API failed! Reason: " + e.getMessage()); + return VPNResponse.FAILED_RESPONSE; } - } else { - result.accept(checkIp(ip)); } - }); - } - - public VPNResponse checkIp(String ip) { - Optional cachedRes = AntiVPN.getInstance().getDatabase().getStoredResponse(ip); - - if(cachedRes.isPresent()) { - return cachedRes.get(); - } - else { - try { - VPNResponse response = FunkemunkyAPI - .getVPNResponse(ip, AntiVPN.getInstance().getVpnConfig().getLicense(), true); - - if (response.isSuccess()) { - AntiVPN.getInstance().getDatabase().cacheResponse(response); - } else { - log("Query to VPN API failed! Reason: " + response.getFailureReason()); - } - - return response; - } catch (JSONException | IOException e) { - log("Query to VPN API failed! Reason: " + e.getMessage()); - return VPNResponse.FAILED_RESPONSE; - } - } + }, threadExecutor); } public abstract void disablePlugin(); diff --git a/Common/src/main/java/dev/brighten/antivpn/command/impl/LookupCommand.java b/Common/src/main/java/dev/brighten/antivpn/command/impl/LookupCommand.java index cd2ba12..9949d40 100644 --- a/Common/src/main/java/dev/brighten/antivpn/command/impl/LookupCommand.java +++ b/Common/src/main/java/dev/brighten/antivpn/command/impl/LookupCommand.java @@ -55,28 +55,31 @@ public class LookupCommand extends Command { Optional player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[0]); - if(!player.isPresent()) { + if(player.isEmpty()) { return String.format("&cNo player found with the name \"%s\"", args[0]); } - AntiVPN.getInstance().getExecutor().checkIp(player.get().getIp().getHostAddress(), - false, result -> { - if(!result.isSuccess()) { - executor.sendMessage("&cThere was an error trying to find the information of this player."); - } else { - executor.sendMessage(StringUtil.line("&8")); - executor.sendMessage("&6&l" + player.get().getName() + "&7&l's Connection Information"); - executor.sendMessage(""); - executor.sendMessage("&e%s&8: &f%s", "Proxy", result.isProxy() - ? "&a" + result.getMethod() : "&cNo"); - executor.sendMessage("&e%s&8: &f%s", "ISP", result.getIsp()); - executor.sendMessage("&e%s&8: &f%s", "Country", result.getCountryName()); - executor.sendMessage("&e%s&8: &f%s", "City", result.getCity()); - executor.sendMessage("&e%s&8: &f%s", "Coordinates", result.getLatitude() - + "&7/&f" + result.getLongitude()); - executor.sendMessage(StringUtil.line("&8")); - } - }); + AntiVPN.getInstance().getExecutor() + .checkIp(player.get().getIp().getHostAddress()) + .thenAccept(result -> { + if(!result.isSuccess()) { + executor.sendMessage("&cThere was an error trying to find the " + + "information of this player."); + return; + } + + executor.sendMessage(StringUtil.line("&8")); + executor.sendMessage("&6&l" + player.get().getName() + "&7&l's Connection Information"); + executor.sendMessage(""); + executor.sendMessage("&e%s&8: &f%s", "Proxy", result.isProxy() + ? "&a" + result.getMethod() : "&cNo"); + executor.sendMessage("&e%s&8: &f%s", "ISP", result.getIsp()); + executor.sendMessage("&e%s&8: &f%s", "Country", result.getCountryName()); + executor.sendMessage("&e%s&8: &f%s", "City", result.getCity()); + executor.sendMessage("&e%s&8: &f%s", "Coordinates", result.getLatitude() + + "&7/&f" + result.getLongitude()); + executor.sendMessage(StringUtil.line("&8")); + }); return "&7Looking up the IP information for player " + player.get().getName() + "..."; diff --git a/Common/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java b/Common/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java index c2cd904..4dfcdf9 100644 --- a/Common/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java +++ b/Common/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java @@ -1,5 +1,7 @@ package dev.brighten.antivpn.database.local; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.database.VPNDatabase; @@ -20,6 +22,12 @@ import java.util.function.Consumer; public class H2VPN implements VPNDatabase { + private final Cache cachedResponses = Caffeine.newBuilder() + .expireAfterWrite(20, TimeUnit.MINUTES) + .maximumSize(4000) + .build(); + + public H2VPN() { VPNExecutor.threadExecutor.scheduleAtFixedRate(() -> { if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; @@ -41,29 +49,26 @@ public class H2VPN implements VPNDatabase { if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()|| MySQL.isClosed()) return Optional.empty(); - ResultSet rs = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip).executeQuery(); - - try { - if (rs != null && rs.next()) { - VPNResponse response = new VPNResponse(rs.getString("asn"), rs.getString("ip"), - rs.getString("countryName"), rs.getString("countryCode"), - rs.getString("city"), rs.getString("timeZone"), - rs.getString("method"), rs.getString("isp"), "N/A", - rs.getBoolean("proxy"), rs.getBoolean("cached"), true, - rs.getDouble("latitude"), rs.getDouble("longitude"), - rs.getTimestamp("inserted").getTime(), -1); - - if(System.currentTimeMillis() - response.getLastAccess() > TimeUnit.HOURS.toMillis(1)) { - VPNExecutor.threadExecutor.execute(() -> deleteResponse(ip)); - return Optional.empty(); + VPNResponse response = cachedResponses.get(ip, ip2 -> { + try(ResultSet rs = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip). + executeQuery()) { + if (rs != null && rs.next()) { + return new VPNResponse(rs.getString("asn"), rs.getString("ip"), + rs.getString("countryName"), rs.getString("countryCode"), + rs.getString("city"), rs.getString("timeZone"), + rs.getString("method"), rs.getString("isp"), "N/A", + rs.getBoolean("proxy"), rs.getBoolean("cached"), true, + rs.getDouble("latitude"), rs.getDouble("longitude"), + rs.getTimestamp("inserted").getTime(), -1); } - return Optional.of(response); + } catch (SQLException e) { + AntiVPN.getInstance().getExecutor().logException("There was a problem getting a response for " + + ip, e); } - } catch (SQLException throwables) { - throwables.printStackTrace(); - } + return null; + }); - return Optional.empty(); + return Optional.ofNullable(response); } /* @@ -80,6 +85,8 @@ public class H2VPN implements VPNDatabase { if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; + cachedResponses.put(toCache.getIp(), toCache); + Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`," + "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)") .append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName()) @@ -198,13 +205,12 @@ public class H2VPN implements VPNDatabase { if(MySQL.isClosed()) return; VPNExecutor.threadExecutor.execute(() -> { - ResultSet set = Query.prepare("select * from `alerts` where `uuid` = ? limit 1") - .append(uuid.toString()).executeQuery(); - try { + try(ResultSet set = Query.prepare("select * from `alerts` where `uuid` = ? limit 1") + .append(uuid.toString()).executeQuery()) { result.accept(set != null && set.next() && set.getString("uuid") != null); } catch (SQLException e) { - e.printStackTrace(); + AntiVPN.getInstance().getExecutor().logException("There was a problem getting alerts state for " + uuid, e); result.accept(false); } }); @@ -271,6 +277,7 @@ public class H2VPN implements VPNDatabase { public void shutdown() { if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; + MySQL.shutdown(); } } diff --git a/Common/src/main/java/dev/brighten/antivpn/database/mongo/MongoVPN.java b/Common/src/main/java/dev/brighten/antivpn/database/mongo/MongoVPN.java index 900abd4..2e7c4eb 100644 --- a/Common/src/main/java/dev/brighten/antivpn/database/mongo/MongoVPN.java +++ b/Common/src/main/java/dev/brighten/antivpn/database/mongo/MongoVPN.java @@ -1,5 +1,7 @@ package dev.brighten.antivpn.database.mongo; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import com.mongodb.*; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -24,6 +26,11 @@ public class MongoVPN implements VPNDatabase { private MongoCollection settingsDocument, cacheDocument; private MongoClient client; + private final Cache cachedResponses = Caffeine.newBuilder() + .expireAfterWrite(20, TimeUnit.MINUTES) + .maximumSize(4000) + .build(); + public MongoVPN() { VPNExecutor.threadExecutor.scheduleAtFixedRate(() -> { if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; @@ -41,32 +48,37 @@ public class MongoVPN implements VPNDatabase { } @Override public Optional getStoredResponse(String ip) { - Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first(); + VPNResponse response = cachedResponses.get(ip, ip2 -> { + Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first(); - if(rdoc != null) { - long lastUpdate = rdoc.get("lastAccess", 0L); + if(rdoc != null) { + long lastUpdate = rdoc.get("lastAccess", 0L); - if(System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) { - VPNExecutor.threadExecutor.execute(() -> deleteResponse(ip)); - return Optional.empty(); + if(System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) { + VPNExecutor.threadExecutor.execute(() -> deleteResponse(ip)); + return null; + } + + return VPNResponse.builder().asn(rdoc.getString("asn")).ip(ip) + .countryName(rdoc.getString("countryName")) + .countryCode(rdoc.getString("countryCode")) + .city(rdoc.getString("city")) + .isp(rdoc.getString("isp")) + .method(rdoc.getString("method")) + .timeZone(rdoc.getString("timeZone")) + .proxy(rdoc.getBoolean("proxy")) + .cached(rdoc.getBoolean("cached")) + .success(true) + .latitude(rdoc.getDouble("latitude")) + .longitude(rdoc.getDouble("longitude")) + .lastAccess(rdoc.get("lastAccess", 0L)) + .build(); } + return null; + }); - return Optional.of(VPNResponse.builder().asn(rdoc.getString("asn")).ip(ip) - .countryName(rdoc.getString("countryName")) - .countryCode(rdoc.getString("countryCode")) - .city(rdoc.getString("city")) - .isp(rdoc.getString("isp")) - .method(rdoc.getString("method")) - .timeZone(rdoc.getString("timeZone")) - .proxy(rdoc.getBoolean("proxy")) - .cached(rdoc.getBoolean("cached")) - .success(true) - .latitude(rdoc.getDouble("latitude")) - .longitude(rdoc.getDouble("longitude")) - .lastAccess(rdoc.get("lastAccess", 0L)) - .build()); - } - return Optional.empty(); + + return Optional.ofNullable(response); } @Override @@ -87,6 +99,8 @@ public class MongoVPN implements VPNDatabase { rdoc.put("longitude", toCache.getLongitude()); rdoc.put("lastAccess", System.currentTimeMillis()); + cachedResponses.put(toCache.getIp(), toCache); + VPNExecutor.threadExecutor.execute(() -> { Bson update = new Document("$set", rdoc); cacheDocument.updateOne(Filters.eq("ip", toCache.getIp()), update, diff --git a/Common/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java b/Common/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java index 1b3babd..39a9cea 100644 --- a/Common/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java +++ b/Common/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java @@ -1,5 +1,7 @@ package dev.brighten.antivpn.database.sql; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.database.VPNDatabase; @@ -20,6 +22,12 @@ import java.util.function.Consumer; public class MySqlVPN implements VPNDatabase { + private final Cache cachedResponses = Caffeine.newBuilder() + .expireAfterWrite(20, TimeUnit.MINUTES) + .maximumSize(4000) + .build(); + + public MySqlVPN() { VPNExecutor.threadExecutor.scheduleAtFixedRate(() -> { if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; @@ -41,30 +49,34 @@ public class MySqlVPN implements VPNDatabase { if (isDisabled()) return Optional.empty(); - ResultSet rs = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip).executeQuery(); + VPNResponse response = cachedResponses.get(ip, ip2 -> { + try(ResultSet rs = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip) + .executeQuery()) { + if (rs != null && rs.next()) { + VPNResponse responseFromDoc = new VPNResponse(rs.getString("asn"), + rs.getString("ip"), + rs.getString("countryName"), rs.getString("countryCode"), + rs.getString("city"), rs.getString("timeZone"), + rs.getString("method"), rs.getString("isp"), "N/A", + rs.getBoolean("proxy"), rs.getBoolean("cached"), true, + rs.getDouble("latitude"), rs.getDouble("longitude"), + rs.getTimestamp("inserted").getTime(), -1); - try { - if (rs != null && rs.next()) { - VPNResponse response = new VPNResponse(rs.getString("asn"), rs.getString("ip"), - rs.getString("countryName"), rs.getString("countryCode"), - rs.getString("city"), rs.getString("timeZone"), - rs.getString("method"), rs.getString("isp"), "N/A", - rs.getBoolean("proxy"), rs.getBoolean("cached"), true, - rs.getDouble("latitude"), rs.getDouble("longitude"), - rs.getTimestamp("inserted").getTime(), -1); + if(System.currentTimeMillis() - responseFromDoc.getLastAccess() > TimeUnit.HOURS.toMillis(1)) { + VPNExecutor.threadExecutor.execute(() -> deleteResponse(ip)); + return null; + } - if(System.currentTimeMillis() - response.getLastAccess() > TimeUnit.HOURS.toMillis(1)) { - VPNExecutor.threadExecutor.execute(() -> deleteResponse(ip)); - return Optional.empty(); + return responseFromDoc; } - - return Optional.of(response); + } catch (SQLException e) { + AntiVPN.getInstance().getExecutor() + .logException("Failed to get response from cache due to SQL error for: " + ip, e); } - } catch (SQLException throwables) { - throwables.printStackTrace(); - } + return null; + }); - return Optional.empty(); + return Optional.ofNullable(response); } /* @@ -81,6 +93,8 @@ public class MySqlVPN implements VPNDatabase { if (isDisabled()) return; + cachedResponses.put(toCache.getIp(), toCache); + Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`," + "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)") .append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName()) @@ -103,10 +117,10 @@ public class MySqlVPN implements VPNDatabase { public boolean isWhitelisted(UUID uuid) { if (isDisabled()) return false; - ResultSet set = Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1") - .append(uuid.toString()).executeQuery(); - - return set != null && set.next() && set.getString("uuid") != null; + try(ResultSet set = Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1") + .append(uuid.toString()).executeQuery()) { + return set != null && set.next() && set.getString("uuid") != null; + } } @SneakyThrows @@ -114,11 +128,10 @@ public class MySqlVPN implements VPNDatabase { public boolean isWhitelisted(String ip) { if (isDisabled()) return false; - ResultSet set = Query.prepare("select `ip` from `whitelisted-ips` where `ip` = ? limit 1") - .append(ip).executeQuery(); - - - return set != null && set.next() && set.getString("ip") != null; + try(ResultSet set = Query.prepare("select `ip` from `whitelisted-ips` where `ip` = ? limit 1") + .append(ip).executeQuery()) { + return set != null && set.next() && set.getString("ip") != null; + } } @Override @@ -199,13 +212,13 @@ public class MySqlVPN implements VPNDatabase { if(MySQL.isClosed()) return; VPNExecutor.threadExecutor.execute(() -> { - ResultSet set = Query.prepare("select * from `alerts` where `uuid` = ? limit 1") - .append(uuid.toString()).executeQuery(); - try { + try(ResultSet set = Query.prepare("select * from `alerts` where `uuid` = ? limit 1") + .append(uuid.toString()).executeQuery()) { result.accept(set != null && set.next() && set.getString("uuid") != null); } catch (SQLException e) { - e.printStackTrace(); + AntiVPN.getInstance().getExecutor() + .logException("Failed to get alerts state from database for: " + uuid, e); result.accept(false); } }); @@ -247,18 +260,16 @@ public class MySqlVPN implements VPNDatabase { AntiVPN.getInstance().getExecutor().log("Creating tables..."); //Running check for old table types to update - oldTableCheck: { - Query.prepare("select `DATA_TYPE` from INFORMATION_SCHEMA.COLUMNS " + - "WHERE table_name = 'responses' AND COLUMN_NAME = 'isp';").execute(set -> { - if(set.getObject("DATA_TYPE").toString().contains("varchar")) { - AntiVPN.getInstance().getExecutor().log("Using old database format for storing responses! " + - "Dropping table and creating a new one..."); - if(Query.prepare("drop table `responses`").execute() > 0) { - AntiVPN.getInstance().getExecutor().log("Successfully dropped table!"); - } - } - }); - } + Query.prepare("select `DATA_TYPE` from INFORMATION_SCHEMA.COLUMNS " + + "WHERE table_name = 'responses' AND COLUMN_NAME = 'isp';").execute(set -> { + if(set.getObject("DATA_TYPE").toString().contains("varchar")) { + AntiVPN.getInstance().getExecutor().log("Using old database format for storing responses! " + + "Dropping table and creating a new one..."); + if(Query.prepare("drop table `responses`").execute() > 0) { + AntiVPN.getInstance().getExecutor().log("Successfully dropped table!"); + } + } + }); Query.prepare("create table if not exists `whitelisted` (`uuid` varchar(36) not null)").execute(); Query.prepare("create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)").execute(); @@ -277,15 +288,13 @@ public class MySqlVPN implements VPNDatabase { " AND table_name='whitelisted' AND index_name='uuid_1';"; ResultSet rs = Query.prepare(query).executeQuery(); int id = 0; - whitelistedIndex: { - while (rs.next()) { - id = rs.getInt("IndexExists"); - } - if (id == 0) { - Query.prepare("create index `uuid_1` on `whitelisted` (`uuid`)").execute(); - } - id = 0; + while (rs.next()) { + id = rs.getInt("IndexExists"); } + if (id == 0) { + Query.prepare("create index `uuid_1` on `whitelisted` (`uuid`)").execute(); + } + id = 0; responsesIndex: { query = "SELECT COUNT(1) IndexExists FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema=DATABASE() " + "AND table_name='responses' AND index_name='ip_1';"; diff --git a/Common/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java b/Common/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java new file mode 100644 index 0000000..2ef3bca --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java @@ -0,0 +1,365 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.brighten.antivpn.depends; + +import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.utils.NonnullByDefault; +import dev.brighten.antivpn.utils.Supplier; +import dev.brighten.antivpn.utils.Suppliers; +import lombok.Getter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.Remapper; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +/** + * Resolves {@link MavenLibrary} annotations for a class, and loads the dependency + * into the classloader. + */ +@SuppressWarnings("CallToPrintStackTrace") +@NonnullByDefault +public final class LibraryLoader { + + @SuppressWarnings("Guava") + private static final Supplier URL_INJECTOR = Suppliers.memoize(() -> + URLClassLoaderAccess.create((URLClassLoader) AntiVPN.getInstance().getClass().getClassLoader())); + + public static void loadAll(Object object) { + loadAll(object.getClass()); + } + + public static void loadAll(Class clazz) { + MavenLibrary[] libs = clazz.getDeclaredAnnotationsByType(MavenLibrary.class); + + for (MavenLibrary lib : libs) { + // Create relocations map if any are defined + Map relocations = new HashMap<>(); + for (Relocate relocate : lib.relocations()) { + relocations.put(relocate.from().replace("\\", ""), relocate.to()); + } + + load(lib.groupId().replace("\\", ""), lib.artifactId(), lib.version(), lib.repo().url(), relocations); + } + } + + public static void load(String groupId, String artifactId, String version, String repoUrl, + Map relocations) { + load(new Dependency(groupId, artifactId, version, repoUrl), relocations); + } + + public static void load(Dependency d, Map relocations) { + System.out.printf("Loading dependency %s:%s:%s from %s%n", + d.getGroupId(), d.getArtifactId(), d.getVersion(), d.getRepoUrl()); + String name = d.getArtifactId() + "-" + d.getVersion(); + + // If we have relocations, add a suffix to identify the relocated version + String fileName = name + ".jar"; + if (!relocations.isEmpty()) { + fileName = name + "-relocated.jar"; + } + + File saveLocation = new File(getLibFolder(), fileName); + File originalJar = new File(getLibFolder(), name + ".jar"); + + // Download the original jar if it doesn't exist + if (!originalJar.exists()) { + try { + System.out.println("Dependency '" + name + + "' is not already in the libraries folder. Attempting to download..."); + URL url = d.getUrl(); + + try (InputStream is = url.openStream()) { + Files.copy(is, originalJar.toPath()); + } + System.out.println("Dependency '" + name + "' successfully downloaded."); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Unable to download dependency: " + d, e); + } + } + + // If we have relocations, create a relocated jar + if (!relocations.isEmpty() && !saveLocation.exists()) { + try { + System.out.println("Relocating packages for " + name + "..."); + relocateJar(originalJar, saveLocation, relocations); + System.out.println("Successfully relocated packages for " + name); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to relocate packages for dependency: " + d, e); + } + } + + // Load the appropriate jar (original or relocated) + File jarToLoad = relocations.isEmpty() ? originalJar : saveLocation; + + if (!jarToLoad.exists()) { + throw new RuntimeException("Unable to find dependency jar: " + jarToLoad.getAbsolutePath()); + } + + try { + URL_INJECTOR.get().addURL(jarToLoad.toURI().toURL()); + } catch (Exception e) { + throw new RuntimeException("Unable to load dependency: " + jarToLoad, e); + } + + System.out.println("Loaded dependency '" + name + "' successfully."); + } + + private static void relocateJar(File sourceJar, File targetJar, Map relocations) + throws IOException { + // Track service files to avoid duplicates + Map serviceFiles = new HashMap<>(); + + try (JarFile jar = new JarFile(sourceJar); + JarOutputStream jos = new JarOutputStream(Files.newOutputStream(targetJar.toPath()))) { + + Enumeration entries = jar.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + + // Skip directories + if (entry.isDirectory()) { + continue; + } + + try (InputStream is = jar.getInputStream(entry)) { + if (name.startsWith("META-INF/services/")) { + // Process service files but don't write yet + processServiceFile(name, is, serviceFiles, relocations); + } else if (name.endsWith(".class")) { + // Relocate class file path as well as content + String relocatedPath = relocateClassPath(name, relocations); + + JarEntry newEntry = new JarEntry(relocatedPath); + jos.putNextEntry(newEntry); + + byte[] classBytes = readAllBytes(is); + byte[] relocatedBytes = relocateClass(classBytes, relocations); + jos.write(relocatedBytes); + jos.closeEntry(); + } else { + // Copy other files as-is + JarEntry newEntry = new JarEntry(name); + jos.putNextEntry(newEntry); + copyStream(is, jos); + jos.closeEntry(); + } + } + } + + // Now write all service files after processing + for (Map.Entry entry : serviceFiles.entrySet()) { + try { + JarEntry serviceEntry = new JarEntry(entry.getKey()); + jos.putNextEntry(serviceEntry); + jos.write(entry.getValue().toString().getBytes()); + jos.closeEntry(); + } catch (Exception e) { + // Log but continue with other service files + System.out.println("Warning: Could not write service file " + + entry.getKey() + ": " + e.getMessage()); + } + } + } + } + + private static void processServiceFile(String name, InputStream is, + Map serviceFiles, + Map relocations) throws IOException { + // Read service file content + String content = new String(readAllBytes(is)); + StringBuilder contentBuilder = serviceFiles.computeIfAbsent(name, k -> new StringBuilder()); + + // Process and relocate service implementations + for (String line : content.split("\n")) { + String trimmed = line.trim(); + if (!trimmed.isEmpty() && !trimmed.startsWith("#")) { + for (Map.Entry relocation : relocations.entrySet()) { + if (trimmed.startsWith(relocation.getKey())) { + trimmed = relocation.getValue() + + trimmed.substring(relocation.getKey().length()); + break; + } + } + } + contentBuilder.append(trimmed).append("\n"); + } + } + + private static byte[] relocateClass(byte[] classBytes, Map relocations) { + try { + // Convert to slash notation for ASM + Remapper prefixRemapper = getPrefixRemapper(relocations); + + // Create custom ClassWriter to handle missing classes + ClassReader reader = new ClassReader(classBytes); + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS) { + @Override + protected String getCommonSuperClass(String type1, String type2) { + try { + return super.getCommonSuperClass(type1, type2); + } catch (RuntimeException e) { + // Fall back to Object when classes can't be loaded + return "java/lang/Object"; + } + } + }; + + ClassVisitor visitor = new ClassRemapper(writer, prefixRemapper); + + // Process class with remapper + reader.accept(visitor, ClassReader.EXPAND_FRAMES); + + return writer.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + return classBytes; + } + } + + private static Remapper getPrefixRemapper(Map relocations) { + Map slashMappings = new HashMap<>(); + for (Map.Entry entry : relocations.entrySet()) { + String fromSlash = entry.getKey().replace('.', '/'); + String toSlash = entry.getValue().replace('.', '/'); + slashMappings.put(fromSlash, toSlash); + } + + // Create customized remapper for package prefixes + return new Remapper() { + @Override + public String map(String typeName) { + if (typeName == null) return null; + + for (Map.Entry entry : slashMappings.entrySet()) { + String from = entry.getKey(); + String to = entry.getValue(); + + if (typeName.startsWith(from)) { + return to + typeName.substring(from.length()); + } + } + return typeName; + } + }; + } + + private static String relocateClassPath(String path, Map relocations) { + // Convert path to package format (replacing / with .) + String packagePath = path.substring(0, path.length() - 6).replace('/', '.'); + + // Apply relocations + for (Map.Entry relocation : relocations.entrySet()) { + if (packagePath.startsWith(relocation.getKey())) { + packagePath = relocation.getValue() + packagePath.substring(relocation.getKey().length()); + break; + } + } + + // Convert back to path format + return packagePath.replace('.', '/') + ".class"; + } + + private static byte[] readAllBytes(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int bytesRead; + byte[] data = new byte[1024]; + while ((bytesRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, bytesRead); + } + return buffer.toByteArray(); + } + + private static void copyStream(InputStream is, OutputStream os) throws IOException { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + } + + private static File getLibFolder() { + File pluginDataFolder = AntiVPN.getInstance().getPluginFolder(); + File libs = new File(pluginDataFolder, "libraries"); + if(libs.mkdirs()) { + System.out.println("Created libraries folder!"); + } + return libs; + } + + @Getter + @NonnullByDefault +// Fix the Dependency class to preserve original groupId for downloading + public static final class Dependency { + private final String groupId; + private final String artifactId; + private final String version; + private final String repoUrl; + // Keep the original groupId/artifactId for Maven downloads + private final String originalGroupId; + private final String originalArtifactId; + + public Dependency(String groupId, String artifactId, String version, String repoUrl) { + this.originalGroupId = Objects.requireNonNull(groupId, "groupId"); + this.originalArtifactId = Objects.requireNonNull(artifactId, "artifactId"); + this.groupId = this.originalGroupId; + this.artifactId = this.originalArtifactId; + this.version = Objects.requireNonNull(version, "version"); + this.repoUrl = Objects.requireNonNull(repoUrl, "repoUrl"); + } + + public URL getUrl() throws MalformedURLException { + String repo = this.repoUrl; + if (!repo.endsWith("/")) { + repo += "/"; + } + repo += "%s/%s/%s/%s-%s.jar"; + + // Always use original groupId for Maven repository URL + String url = String.format(repo, this.originalGroupId.replace(".", "/"), + this.originalArtifactId, this.version, this.originalArtifactId, this.version); + return new URL(url); + } + + // Rest of the class unchanged + } + + +} diff --git a/Common/src/main/java/dev/brighten/antivpn/depends/MavenLibraries.java b/Common/src/main/java/dev/brighten/antivpn/depends/MavenLibraries.java new file mode 100644 index 0000000..189f737 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/depends/MavenLibraries.java @@ -0,0 +1,40 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.brighten.antivpn.depends; + +import java.lang.annotation.*; + +/** + * Annotation to indicate the required libraries for a class. + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MavenLibraries { + + MavenLibrary[] value() default {}; + +} diff --git a/Common/src/main/java/dev/brighten/antivpn/depends/MavenLibrary.java b/Common/src/main/java/dev/brighten/antivpn/depends/MavenLibrary.java new file mode 100644 index 0000000..eed8117 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/depends/MavenLibrary.java @@ -0,0 +1,69 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.brighten.antivpn.depends; + +import java.lang.annotation.*; + +/** + * Annotation to indicate a required library for a class. + */ +@Documented +@Repeatable(MavenLibraries.class) +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MavenLibrary { + + /** + * The group id of the library + * + * @return the group id of the library + */ + String groupId(); + + /** + * The artifact id of the library + * + * @return the artifact id of the library + */ + String artifactId(); + + /** + * The version of the library + * + * @return the version of the library + */ + String version(); + + /** + * The repo where the library can be obtained from + * + * @return the repo where the library can be obtained from + */ + Repository repo() default @Repository(url = "https://repo1.maven.org/maven2"); + + Relocate[] relocations() default {}; // Add this line + +} diff --git a/Common/src/main/java/dev/brighten/antivpn/depends/Relocate.java b/Common/src/main/java/dev/brighten/antivpn/depends/Relocate.java new file mode 100644 index 0000000..32b8af5 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/depends/Relocate.java @@ -0,0 +1,14 @@ +package dev.brighten.antivpn.depends; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({}) +public @interface Relocate { + String from(); + String to(); +} \ No newline at end of file diff --git a/Common/src/main/java/dev/brighten/antivpn/depends/Repository.java b/Common/src/main/java/dev/brighten/antivpn/depends/Repository.java new file mode 100644 index 0000000..4381225 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/depends/Repository.java @@ -0,0 +1,45 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.brighten.antivpn.depends; + +import java.lang.annotation.*; + +/** + * Represents a maven repository. + */ +@Documented +@Target(ElementType.LOCAL_VARIABLE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Repository { + + /** + * Gets the base url of the repository. + * + * @return the base url of the repository + */ + String url(); + +} diff --git a/Common/src/main/java/dev/brighten/antivpn/depends/URLClassLoaderAccess.java b/Common/src/main/java/dev/brighten/antivpn/depends/URLClassLoaderAccess.java new file mode 100644 index 0000000..a0159f0 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/depends/URLClassLoaderAccess.java @@ -0,0 +1,175 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.brighten.antivpn.depends; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collection; + +/** + * Provides access to {@link URLClassLoader}#addURL. + */ +public abstract class URLClassLoaderAccess { + + /** + * Creates a {@link URLClassLoaderAccess} for the given class loader. + * + * @param classLoader the class loader + * @return the access object + */ + static URLClassLoaderAccess create(URLClassLoader classLoader) { + if (Reflection.isSupported()) { + return new Reflection(classLoader); + } else if (Unsafe.isSupported()) { + return new Unsafe(classLoader); + } else { + return Noop.INSTANCE; + } + } + + private final URLClassLoader classLoader; + + protected URLClassLoaderAccess(URLClassLoader classLoader) { + this.classLoader = classLoader; + } + + + /** + * Adds the given URL to the class loader. + * + * @param url the URL to add + */ + public abstract void addURL(URL url); + + /** + * Accesses using reflection, not supported on Java 9+. + */ + private static class Reflection extends URLClassLoaderAccess { + private static final Method ADD_URL_METHOD; + + static { + Method addUrlMethod; + try { + addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + addUrlMethod.setAccessible(true); + } catch (Exception e) { + addUrlMethod = null; + } + ADD_URL_METHOD = addUrlMethod; + } + + private static boolean isSupported() { + return ADD_URL_METHOD != null; + } + + Reflection(URLClassLoader classLoader) { + super(classLoader); + } + + @Override + public void addURL(URL url) { + try { + ADD_URL_METHOD.invoke(super.classLoader, url); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Accesses using sun.misc.Unsafe, supported on Java 9+. + * + * @author Vaishnav Anil (...) + */ + private static class Unsafe extends URLClassLoaderAccess { + private static final sun.misc.Unsafe UNSAFE; + + static { + sun.misc.Unsafe unsafe; + try { + Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + unsafe = (sun.misc.Unsafe) unsafeField.get(null); + } catch (Throwable t) { + unsafe = null; + } + UNSAFE = unsafe; + } + + private static boolean isSupported() { + return UNSAFE != null; + } + + private final Collection unopenedURLs; + private final Collection pathURLs; + + @SuppressWarnings("unchecked") + Unsafe(URLClassLoader classLoader) { + super(classLoader); + + Collection unopenedURLs; + Collection pathURLs; + try { + Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp"); + unopenedURLs = (Collection) fetchField(ucp.getClass(), ucp, "unopenedUrls"); + pathURLs = (Collection) fetchField(ucp.getClass(), ucp, "path"); + } catch (Throwable e) { + unopenedURLs = null; + pathURLs = null; + } + this.unopenedURLs = unopenedURLs; + this.pathURLs = pathURLs; + } + + private static Object fetchField(final Class clazz, final Object object, final String name) throws NoSuchFieldException { + Field field = clazz.getDeclaredField(name); + long offset = UNSAFE.objectFieldOffset(field); + return UNSAFE.getObject(object, offset); + } + + @Override + public void addURL(URL url) { + this.unopenedURLs.add(url); + this.pathURLs.add(url); + } + } + + private static class Noop extends URLClassLoaderAccess { + private static final Noop INSTANCE = new Noop(); + + private Noop() { + super(null); + } + + @Override + public void addURL(URL url) { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/Common/src/main/java/dev/brighten/antivpn/utils/ExtraObjectsMethodsForWeb.java b/Common/src/main/java/dev/brighten/antivpn/utils/ExtraObjectsMethodsForWeb.java new file mode 100644 index 0000000..484f35e --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/utils/ExtraObjectsMethodsForWeb.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2016 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package dev.brighten.antivpn.utils; + +/** + * Holder for extra methods of {@code Objects} only in web. Intended to be empty for regular + * version. + */ +abstract class ExtraObjectsMethodsForWeb {} diff --git a/Common/src/main/java/dev/brighten/antivpn/utils/NonnullByDefault.java b/Common/src/main/java/dev/brighten/antivpn/utils/NonnullByDefault.java new file mode 100644 index 0000000..48a1f73 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/utils/NonnullByDefault.java @@ -0,0 +1,36 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.brighten.antivpn.utils; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface NonnullByDefault { + +} diff --git a/Common/src/main/java/dev/brighten/antivpn/utils/NullnessCasts.java b/Common/src/main/java/dev/brighten/antivpn/utils/NullnessCasts.java new file mode 100644 index 0000000..24d68c9 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/utils/NullnessCasts.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package dev.brighten.antivpn.utils; + +/** A utility method to perform unchecked casts to suppress errors produced by nullness analyses. */ +final class NullnessCasts { + /** + * Accepts a {@code @Nullable T} and returns a plain {@code T}, without performing any check that + * that conversion is safe. + * + *

This method is intended to help with usages of type parameters that have { + * ParametricNullness parametric nullness}. If a type parameter instead ranges over only non-null + * types (or if the type is a non-variable type, like {@code String}), then code should almost + * never use this method, preferring instead to call {@code requireNonNull} so as to benefit from + * its runtime check. + * + *

An example use case for this method is in implementing an {@code Iterator} whose {@code + * next} field is lazily initialized. The type of that field would be {@code @Nullable T}, and the + * code would be responsible for populating a "real" {@code T} (which might still be the value + * {@code null}!) before returning it to callers. Depending on how the code is structured, a + * nullness analysis might not understand that the field has been populated. To avoid that problem + * without having to add {@code @SuppressWarnings}, the code can call this method. + * + *

Why not just add {@code SuppressWarnings}? The problem is that this method is + * typically useful for {@code return} statements. That leaves the code with two options: Either + * add the suppression to the whole method (which turns off checking for a large section of code), + * or extract a variable, and put the suppression on that. However, a local variable typically + * doesn't work: Because nullness analyses typically infer the nullness of local variables, + * there's no way to assign a {@code @Nullable T} to a field {@code T foo;} and instruct the + * analysis that that means "plain {@code T}" rather than the inferred type {@code @Nullable T}. + * (Even if supported added {@code @NonNull}, that would not help, since the problem case + * addressed by this method is the case in which {@code T} has parametric nullness -- and thus its + * value may be legitimately {@code null}.) + */ + @SuppressWarnings("nullness") + static T uncheckedCastNullableTToT(T t) { + return t; + } + + private NullnessCasts() {} +} diff --git a/Common/src/main/java/dev/brighten/antivpn/utils/Preconditions.java b/Common/src/main/java/dev/brighten/antivpn/utils/Preconditions.java new file mode 100644 index 0000000..7d24918 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/utils/Preconditions.java @@ -0,0 +1,244 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by FernFlower decompiler) +// + +package dev.brighten.antivpn.utils; + +public final class Preconditions { + private Preconditions() { + } + + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } else { + return reference; + } + } + + public static T checkNotNull(T reference, Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } else { + return reference; + } + } + + public static T checkNotNull(T reference, String errorMessageTemplate, Object... errorMessageArgs) { + if (reference == null) { + throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs)); + } else { + return reference; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2, Object p3) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2, Object p3, Object p4) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3, p4)); + } else { + return obj; + } + } + + static String format(String template, Object... args) { + template = String.valueOf(template); + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + + int i; + int placeholderStart; + for(i = 0; i < args.length; templateStart = placeholderStart + 2) { + placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + + builder.append(template, templateStart, placeholderStart); + builder.append(args[i++]); + } + + builder.append(template, templateStart, template.length()); + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + + while(i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + + builder.append(']'); + } + + return builder.toString(); + } +} diff --git a/Common/src/main/java/dev/brighten/antivpn/utils/Supplier.java b/Common/src/main/java/dev/brighten/antivpn/utils/Supplier.java new file mode 100644 index 0000000..c1f7365 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/utils/Supplier.java @@ -0,0 +1,11 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by FernFlower decompiler) +// + +package dev.brighten.antivpn.utils; + +@FunctionalInterface +public interface Supplier extends java.util.function.Supplier { + T get(); +} diff --git a/Common/src/main/java/dev/brighten/antivpn/utils/Suppliers.java b/Common/src/main/java/dev/brighten/antivpn/utils/Suppliers.java new file mode 100644 index 0000000..d380307 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/utils/Suppliers.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package dev.brighten.antivpn.utils; + +import java.io.Serializable; + +import static dev.brighten.antivpn.utils.NullnessCasts.uncheckedCastNullableTToT; +import static dev.brighten.antivpn.utils.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; + +/** + * Useful suppliers. + * + *

All methods return serializable suppliers as long as they're given serializable parameters. + * + * @author Laurence Gonsalves + * @author Harry Heymann + * @since 2.0 + */ +public final class Suppliers { + private Suppliers() {} + + /** + * Returns a supplier which caches the instance retrieved during the first call to {@code get()} + * and returns that value on subsequent calls to {@code get()}. See: memoization + * + *

The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at + * most once unless the underlying {@code get()} throws an exception. The supplier's serialized + * form does not contain the cached value, which will be recalculated when {@code get()} is called + * on the reserialized instance. + * + *

When the underlying delegate throws an exception then this memoizing supplier will keep + * delegating calls until it returns valid data. + * + *

If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is + * returned directly. + */ + public static Supplier memoize(Supplier delegate) { + if (delegate instanceof NonSerializableMemoizingSupplier + || delegate instanceof MemoizingSupplier) { + return delegate; + } + return delegate instanceof Serializable + ? new MemoizingSupplier<>(delegate) + : new NonSerializableMemoizingSupplier<>(delegate); + } + + static class MemoizingSupplier implements Supplier, Serializable { + final Supplier delegate; + transient volatile boolean initialized; + // "value" does not need to be volatile; visibility piggy-backs + // on volatile read of "initialized". + transient T value; + + MemoizingSupplier(Supplier delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public T get() { + // A 2-field variant of Double Checked Locking. + if (!initialized) { + synchronized (this) { + if (!initialized) { + T t = delegate.get(); + value = t; + initialized = true; + return t; + } + } + } + // This is safe because we checked `initialized.` + return uncheckedCastNullableTToT(value); + } + + @Override + public String toString() { + return "Suppliers.memoize(" + + (initialized ? "" : delegate) + + ")"; + } + + private static final long serialVersionUID = 0; + } + + static class NonSerializableMemoizingSupplier implements Supplier { + volatile Supplier delegate; + volatile boolean initialized; + // "value" does not need to be volatile; visibility piggy-backs + // on volatile read of "initialized". + T value; + + NonSerializableMemoizingSupplier(Supplier delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public T get() { + // A 2-field variant of Double Checked Locking. + if (!initialized) { + synchronized (this) { + if (!initialized) { + /* + * requireNonNull is safe because we read and write `delegate` under synchronization. + * + * TODO(cpovirk): To avoid having to check for null, replace `delegate` with a singleton + * `Supplier` that always throws an exception. + */ + T t = requireNonNull(delegate).get(); + value = t; + initialized = true; + // Release the delegate to GC. + delegate = null; + return t; + } + } + } + // This is safe because we checked `initialized.` + return uncheckedCastNullableTToT(value); + } + + @Override + public String toString() { + Supplier delegate = this.delegate; + return "Suppliers.memoize(" + + (delegate == null ? "" : delegate) + + ")"; + } + } +} diff --git a/Assembly/pom.xml b/Universal/pom.xml similarity index 58% rename from Assembly/pom.xml rename to Universal/pom.xml index a118190..ebe5ca1 100644 --- a/Assembly/pom.xml +++ b/Universal/pom.xml @@ -2,26 +2,56 @@ + 4.0.0 - AntiVPN dev.brighten.antivpn + AntiVPN 1.9.4-DEV - 4.0.0 - Assembly + Universal - 8 - 8 + 17 + 17 + UTF-8 + + + dev.brighten.antivpn + Common + ${project.version} + + + dev.brighten.antivpn + Bukkit + ${project.version} + + + dev.brighten.antivpn + Bungee + ${project.version} + + + dev.brighten.antivpn + Velocity + ${project.version} + + + + + org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.6.0 package @@ -29,15 +59,25 @@ shade + AntiVPN-${project.version}-universal + false + + + org.yaml.snakeyaml dev.brighten.antivpn.shaded.org.yaml.snakeyaml - com.google.common - dev.brighten.antivpn.shaded.com.google.common + org.bstats + dev.brighten.antivpn.shaded.org.bstats + + com.google + dev.brighten.antivpn.shaded.com.google + + @@ -46,37 +86,4 @@ - - - dev.brighten.antivpn - Bungee - ${version} - compile - - - dev.brighten.antivpn - Velocity - ${version} - compile - - - dev.brighten.antivpn - Common - ${version} - compile - - - org.xerial - sqlite-jdbc - 3.41.2.2 - compile - - - dev.brighten.antivpn - Bukkit - ${version} - compile - - - \ No newline at end of file diff --git a/Velocity/pom.xml b/Velocity/pom.xml index 045296f..f7dc4b3 100644 --- a/Velocity/pom.xml +++ b/Velocity/pom.xml @@ -12,8 +12,8 @@ Velocity - 8 - 8 + 17 + 17 @@ -49,17 +49,17 @@ org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.13.0 - 8 - 8 + 17 + 17 -XDignore.symbol.file org.apache.maven.plugins maven-shade-plugin - 3.1.0 + 3.6.0 diff --git a/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java b/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java index 04ab467..8c6a437 100644 --- a/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java +++ b/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java @@ -1,192 +1,155 @@ package dev.brighten.antivpn.velocity; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.velocitypowered.api.event.EventHandler; import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.LoginEvent; -import com.velocitypowered.api.scheduler.ScheduledTask; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.velocity.util.StringUtils; -import dev.brighten.antivpn.web.objects.VPNResponse; -import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.logging.Level; public class VelocityListener extends VPNExecutor { - private ScheduledTask cacheResetTask; - private final Cache responseCache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.MINUTES) - .maximumSize(2000) - .build(); - - private final EventHandler loginEvent = event -> { - if (event.getResult().isAllowed()) { - if (event.getPlayer().hasPermission("antivpn.bypass") //Has bypass permission - //Is exempt - || AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer().getUniqueId()) - //Or has a name that starts with a certain prefix. This is for Bedrock exempting. - || AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer().getRemoteAddress() - .getAddress().getHostAddress()) - || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() - .anyMatch(prefix -> event.getPlayer().getUsername().startsWith(prefix))) return; - - if(responseCache.asMap().containsKey(event.getPlayer().getUniqueId())) { - VPNResponse cached = responseCache.getIfPresent(event.getPlayer().getUniqueId()); - - if (cached != null && cached.isProxy()) { - event.setResult(ResultedEvent.ComponentResult.denied(Component.text("No"))); - return; - } - } - - checkIp(event.getPlayer().getRemoteAddress().getAddress().getHostAddress(), - AntiVPN.getInstance().getVpnConfig().cachedResults(), result -> { - if (result.isSuccess()) { - // If the countryList() size is zero, no need to check. - // Running country check first - if (!AntiVPN.getInstance().getVpnConfig().countryList().isEmpty() - && !(AntiVPN.getInstance().getExecutor() - .isWhitelisted(event.getPlayer().getUniqueId()) //Is exempt - //Or has a name that starts with a certain prefix. This is for Bedrock exempting. - || AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer() - .getRemoteAddress().getAddress().getHostAddress())) - // This bit of code will decide whether or not to kick the player - // If it contains the code and it is set to whitelist, it will not kick - // as they are equal and vise versa. However, if the contains does not match - // the state, it will kick. - && AntiVPN.getInstance().getVpnConfig().countryList() - .contains(result.getCountryCode()) - != AntiVPN.getInstance().getVpnConfig().whitelistCountries()) { - //Using our built in kicking system if no commands are configured - if (AntiVPN.getInstance().getVpnConfig().countryKickCommands().isEmpty()) { - final String kickReason = AntiVPN.getInstance().getVpnConfig() - .countryVanillaKickReason(); - // Kicking our player - event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder() - .character('&') - .build().deserialize(kickReason - .replace("%player%", event.getPlayer().getUsername()) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode())))); - VelocityPlugin.INSTANCE.getServer().getScheduler() - .buildTask(VelocityPlugin.INSTANCE, () -> - event.getPlayer().disconnect(LegacyComponentSerializer.builder() - .character('&') - .build().deserialize(kickReason - .replace("%player%", event.getPlayer().getUsername()) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode())))); - } else { - for (String cmd : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { - final String formattedCommand = StringUtils - .translateAlternateColorCodes('&', - cmd.replace("%player%", - event.getPlayer().getUsername()) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode())); - // Running the command from console - VelocityPlugin.INSTANCE.getServer().getCommandManager() - .executeAsync(VelocityPlugin.INSTANCE.getServer() - .getConsoleCommandSource(), - StringUtils.translateAlternateColorCodes('&', - formattedCommand)); - } - } - } else if (result.isProxy()) { - if (AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) { - // Delay code execution - event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder() - .character('&') - .build().deserialize(AntiVPN.getInstance().getVpnConfig() - .getKickString() - .replace("%player%", event.getPlayer().getUsername()) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode())))); - - VelocityPlugin.INSTANCE.getServer().getScheduler() - .buildTask(VelocityPlugin.INSTANCE, () -> - event.getPlayer().disconnect(LegacyComponentSerializer.builder() - .character('&') - .build().deserialize(AntiVPN.getInstance().getVpnConfig() - .getKickString() - .replace("%player%", event.getPlayer().getUsername()) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode())))) - .delay(1, TimeUnit.SECONDS).schedule(); - } - VelocityPlugin.INSTANCE.getLogger().info(event.getPlayer().getUsername() - + " joined on a VPN/Proxy (" + result.getMethod() + ")"); - //Ensuring the user wishes to alert to staff - if (AntiVPN.getInstance().getVpnConfig().alertToStaff()) - AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() - .filter(APIPlayer::isAlertsEnabled) - .forEach(pl -> - pl.sendMessage(AntiVPN.getInstance().getVpnConfig() - .alertMessage() - .replace("%player%", - event.getPlayer().getUsername()) - .replace("%reason%", - result.getMethod()) - .replace("%country%", - result.getCountryName()) - .replace("%city%", - result.getCity()))); - - //In case the user wants to run their own commands instead of using the - // built in kicking - if (AntiVPN.getInstance().getVpnConfig().runCommands()) { - for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { - VelocityPlugin.INSTANCE.getServer().getCommandManager() - .executeAsync(VelocityPlugin.INSTANCE.getServer() - .getConsoleCommandSource(), - StringUtils.translateAlternateColorCodes('&', - command.replace("%player%", - event.getPlayer().getUsername()))); - } - } - AntiVPN.getInstance().detections++; - } - } else { - VelocityPlugin.INSTANCE.getLogger() - .log(Level.WARNING, - "The API query was not a success! " + - "You may need to upgrade your license on " + - "https://funkemunky.cc/shop"); - } - AntiVPN.getInstance().checked++; - }); - } - }; - - @Override public void registerListeners() { VelocityPlugin.INSTANCE.getServer().getEventManager() .register(VelocityPlugin.INSTANCE, this); VelocityPlugin.INSTANCE.getServer().getEventManager().register(VelocityPlugin.INSTANCE, DisconnectEvent.class, - event -> AntiVPN.getInstance().getPlayerExecutor() - .unloadPlayer(event.getPlayer().getUniqueId())); + event -> AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId())); VelocityPlugin.INSTANCE.getServer().getEventManager().register(VelocityPlugin.INSTANCE, LoginEvent.class, - loginEvent); - } + event -> { + if (event.getResult().isAllowed()) { + if (event.getPlayer().hasPermission("antivpn.bypass") //Has bypass permission + //Is exempt + || AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer().getUniqueId()) + //Or has a name that starts with a certain prefix. This is for Bedrock exempting. + || AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer().getRemoteAddress() + .getAddress().getHostAddress()) + || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() + .anyMatch(prefix -> event.getPlayer().getUsername().startsWith(prefix))) return; + checkIp(event.getPlayer().getRemoteAddress().getAddress().getHostAddress()) + .thenAccept(result -> { + if(!result.isSuccess()) { + VelocityPlugin.INSTANCE.getLogger() + .log(Level.WARNING, + "The API query was not a success! " + + "You may need to upgrade your license on " + + "https://funkemunky.cc/shop"); + } + // If the countryList() size is zero, no need to check. + // Running country check first + if (!AntiVPN.getInstance().getVpnConfig().countryList().isEmpty() + && !(AntiVPN.getInstance().getExecutor() + .isWhitelisted(event.getPlayer().getUniqueId()) //Is exempt + //Or has a name that starts with a certain prefix. This is for Bedrock exempting. + || AntiVPN.getInstance().getExecutor().isWhitelisted(event.getPlayer() + .getRemoteAddress().getAddress().getHostAddress())) + // This bit of code will decide whether or not to kick the player + // If it contains the code and it is set to whitelist, it will not kick + // as they are equal and vise versa. However, if the contains does not match + // the state, it will kick. + && AntiVPN.getInstance().getVpnConfig().countryList() + .contains(result.getCountryCode()) + != AntiVPN.getInstance().getVpnConfig().whitelistCountries()) { + //Using our built in kicking system if no commands are configured + if (AntiVPN.getInstance().getVpnConfig().countryKickCommands().isEmpty()) { + final String kickReason = AntiVPN.getInstance().getVpnConfig() + .countryVanillaKickReason(); + // Kicking our player + event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder() + .character('&') + .build().deserialize(kickReason + .replace("%player%", event.getPlayer().getUsername()) + .replace("%country%", result.getCountryName()) + .replace("%code%", result.getCountryCode())))); + VelocityPlugin.INSTANCE.getServer().getScheduler() + .buildTask(VelocityPlugin.INSTANCE, () -> + event.getPlayer().disconnect(LegacyComponentSerializer.builder() + .character('&') + .build().deserialize(kickReason + .replace("%player%", event.getPlayer().getUsername()) + .replace("%country%", result.getCountryName()) + .replace("%code%", result.getCountryCode())))); + } else { + for (String cmd : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { + final String formattedCommand = StringUtils + .translateAlternateColorCodes('&', + cmd.replace("%player%", + event.getPlayer().getUsername()) + .replace("%country%", result.getCountryName()) + .replace("%code%", result.getCountryCode())); + // Running the command from console + VelocityPlugin.INSTANCE.getServer().getCommandManager() + .executeAsync(VelocityPlugin.INSTANCE.getServer() + .getConsoleCommandSource(), + StringUtils.translateAlternateColorCodes('&', + formattedCommand)); + } + } + } else if (result.isProxy()) { + if (AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) { + // Delay code execution + event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder() + .character('&') + .build().deserialize(AntiVPN.getInstance().getVpnConfig() + .getKickString() + .replace("%player%", event.getPlayer().getUsername()) + .replace("%country%", result.getCountryName()) + .replace("%code%", result.getCountryCode())))); - @Override - public void onShutdown() { - if (cacheResetTask != null) { - cacheResetTask.cancel(); - cacheResetTask = null; - } - VelocityPlugin.INSTANCE.getServer().getEventManager().unregisterListener(VelocityPlugin.INSTANCE, this); + VelocityPlugin.INSTANCE.getServer().getScheduler() + .buildTask(VelocityPlugin.INSTANCE, () -> + event.getPlayer().disconnect(LegacyComponentSerializer.builder() + .character('&') + .build().deserialize(AntiVPN.getInstance().getVpnConfig() + .getKickString() + .replace("%player%", event.getPlayer().getUsername()) + .replace("%country%", result.getCountryName()) + .replace("%code%", result.getCountryCode())))) + .delay(1, TimeUnit.SECONDS).schedule(); + } + VelocityPlugin.INSTANCE.getLogger().info(event.getPlayer().getUsername() + + " joined on a VPN/Proxy (" + result.getMethod() + ")"); + //Ensuring the user wishes to alert to staff + if (AntiVPN.getInstance().getVpnConfig().alertToStaff()) + AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() + .filter(APIPlayer::isAlertsEnabled) + .forEach(pl -> + pl.sendMessage(AntiVPN.getInstance().getVpnConfig() + .alertMessage() + .replace("%player%", + event.getPlayer().getUsername()) + .replace("%reason%", + result.getMethod()) + .replace("%country%", + result.getCountryName()) + .replace("%city%", + result.getCity()))); + + //In case the user wants to run their own commands instead of using the + // built in kicking + if (AntiVPN.getInstance().getVpnConfig().runCommands()) { + for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { + VelocityPlugin.INSTANCE.getServer().getCommandManager() + .executeAsync(VelocityPlugin.INSTANCE.getServer() + .getConsoleCommandSource(), + StringUtils.translateAlternateColorCodes('&', + command.replace("%player%", + event.getPlayer().getUsername()))); + } + } + AntiVPN.getInstance().detections++; + } + AntiVPN.getInstance().checked++; + }); + } + }); } @Override @@ -200,7 +163,7 @@ public class VelocityListener extends VPNExecutor { } @Override - public void logException(String message, Exception ex) { + public void logException(String message, Throwable ex) { VelocityPlugin.INSTANCE.getLogger().log(Level.SEVERE, message, ex); } diff --git a/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java b/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java index 33e8e89..da0de33 100644 --- a/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java +++ b/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java @@ -9,8 +9,13 @@ import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.proxy.ProxyServer; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.command.Command; +import dev.brighten.antivpn.database.VPNDatabase; +import dev.brighten.antivpn.database.local.H2VPN; +import dev.brighten.antivpn.database.mongo.MongoVPN; +import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.velocity.command.VelocityCommand; import lombok.Getter; +import org.bstats.charts.SimplePie; import org.bstats.velocity.Metrics; import javax.annotation.Nullable; @@ -29,6 +34,7 @@ public class VelocityPlugin { @Nullable private Metrics metrics; + public static VelocityPlugin INSTANCE; @Inject @@ -51,6 +57,8 @@ public class VelocityPlugin { if(AntiVPN.getInstance().getVpnConfig().metrics()) { logger.info("Starting metrics..."); metrics = metricsFactory.make(this, 12791); + + metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); } logger.info("Registering commands..."); @@ -61,8 +69,33 @@ public class VelocityPlugin { } @Subscribe - public void onShutdown(ProxyShutdownEvent event) { - AntiVPN.getInstance().stop(); + public void onDisable(ProxyShutdownEvent event) { + logger.info("Disabling AntiVPN..."); + AntiVPN.getInstance().getExecutor().log("Disabling AntiVPN..."); + + if (AntiVPN.getInstance().getDatabase() != null) { + AntiVPN.getInstance().stop(); + } + + if (metrics != null) { + metrics = null; + } + INSTANCE = null; + logger.info("Disabled AntiVPN."); + } + + private String getDatabaseType() { + VPNDatabase database = AntiVPN.getInstance().getDatabase(); + + if(database instanceof H2VPN) { + return "H2"; + } else if(database instanceof MySqlVPN) { + return "MySQL"; + } else if(database instanceof MongoVPN) { + return "MongoDB"; + } else { + return "No-Database"; + } } } diff --git a/pom.xml b/pom.xml index 1bab415..05cf495 100644 --- a/pom.xml +++ b/pom.xml @@ -13,14 +13,14 @@ Common Bungee Bukkit - Assembly Velocity Sponge + Universal - 8 - 8 + 17 + 17 @@ -30,8 +30,8 @@ maven-compiler-plugin 3.7.0 - 8 - 8 + 17 + 17 -XDignore.symbol.file false @@ -53,10 +53,6 @@ - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - funkemunky-releases https://nexus.funkemunky.cc/content/repositories/releases/