diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 640dd04..073bbfa 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -7,16 +7,27 @@ on: jobs: build: + name: Build and Test runs-on: ubuntu-latest - + steps: + - name: Cache local Maven repository + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- - uses: actions/checkout@v4 - - name: Set up JDK 17.0.2 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: 17.0 + java-version: '21' distribution: 'zulu' - 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: @@ -24,5 +35,10 @@ jobs: - name: Upload AntiVPN uses: actions/upload-artifact@v4 with: - name: AntiVPN - path: Assembly/target/Assembly-*.jar + name: AntiVPN-Universal + path: Universal/target/AntiVPN-*.jar + - name: Upload Sponge plugin + uses: actions/upload-artifact@v4 + with: + name: AntiVPN-Sponge + path: Sponge/target/Sponge-*.jar diff --git a/Bukkit/pom.xml b/Bukkit/pom.xml index aae2a1e..3cd4cb3 100644 --- a/Bukkit/pom.xml +++ b/Bukkit/pom.xml @@ -26,7 +26,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.0 + 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 - @@ -68,6 +64,13 @@ 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 5dbb552..7f036c3 100644 --- a/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java +++ b/Bukkit/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java @@ -1,34 +1,27 @@ package dev.brighten.antivpn.bukkit; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.APIPlayer; +import dev.brighten.antivpn.api.CheckResult; +import dev.brighten.antivpn.api.OfflinePlayer; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.message.VpnString; -import dev.brighten.antivpn.web.objects.VPNResponse; +import dev.brighten.antivpn.utils.StringUtil; +import dev.brighten.antivpn.utils.Tuple; import net.md_5.bungee.api.ChatColor; import org.bukkit.Bukkit; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.scheduler.BukkitRunnable; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.util.UUID; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; @SuppressWarnings("unchecked") public class BukkitListener extends VPNExecutor implements Listener { - private final Cache responseCache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.MINUTES) - .maximumSize(2000) - .build(); @Override public void registerListeners() { @@ -47,11 +40,80 @@ 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); } - @EventHandler + @Override + public void runCommand(String command) { + Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), + ChatColor.translateAlternateColorCodes('&', command)); + } + + @Override + public void disablePlugin() { + HandlerList.unregisterAll(this); + BukkitPlugin.pluginInstance.getServer().getPluginManager().disablePlugin(BukkitPlugin.pluginInstance); + } + + @EventHandler(priority = EventPriority.HIGH) + public void onLogin(final PlayerLoginEvent event) { + APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId()) + .orElse(new OfflinePlayer( + event.getPlayer().getUniqueId(), + event.getPlayer().getName(), + event.getAddress() + )); + + CheckResult instantResult = player.checkPlayer(result -> { + if(!result.resultType().isShouldBlock()) return; + + AntiVPN.getInstance().getExecutor().log(Level.INFO, "Adding %s to kick", event.getPlayer().getName()); + AntiVPN.getInstance().getExecutor().getToKick().add(new Tuple<>(result, event.getPlayer().getUniqueId())); + }); + + if(!instantResult.resultType().isShouldBlock()) return; + + AntiVPN.getInstance().getExecutor().getToKick() + .add(new Tuple<>(instantResult, event.getPlayer().getUniqueId())); + + if(!AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) { + return; + } + + AntiVPN.getInstance().getExecutor().log(Level.INFO, "%s was kicked from pre-login cache with IP %s", event.getPlayer().getName(), instantResult.response().getIp()); + + event.setResult(PlayerLoginEvent.Result.KICK_BANNED); + switch (instantResult.resultType()) { + case DENIED_COUNTRY -> event.setKickMessage(StringUtil.translateAlternateColorCodes('&', + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().countryVanillaKickReason(), + player, + instantResult.response() + ))); + case DENIED_PROXY -> { + if(AntiVPN.getInstance().getVpnConfig().alertToStaff()) { + AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() + .filter(APIPlayer::isAlertsEnabled) + .forEach(pl -> + pl.sendMessage(StringUtil.varReplace( + ChatColor.translateAlternateColorCodes( + '&', + AntiVPN.getInstance().getVpnConfig().alertMessage()), + player, + instantResult.response()))); + } + event.setKickMessage(StringUtil.translateAlternateColorCodes('&', + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getKickString(), + player, + instantResult.response() + ))); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) public void onJoin(final PlayerJoinEvent event) { AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId()) .ifPresent(player -> { @@ -66,161 +128,6 @@ public class BukkitListener extends VPNExecutor implements Listener { }); } - @EventHandler - public void onListener(final PlayerLoginEvent event) { - //If they're exempt, don't check. - if(event.getPlayer().hasPermission("antivpn.bypass") //Has bypass permission - || 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.getAddress().getHostAddress()) - || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() - .anyMatch(prefix -> event.getPlayer().getName().startsWith(prefix))) return; - - if(responseCache.asMap().containsKey(event.getPlayer().getUniqueId())) { - VPNResponse cached = responseCache.getIfPresent(event.getPlayer().getUniqueId()); - - if (cached != null && cached.isProxy()) { - event.setResult(PlayerLoginEvent.Result.KICK_BANNED); - event.setKickMessage(org.bukkit.ChatColor.translateAlternateColorCodes('&', - AntiVPN.getInstance().getVpnConfig().getKickString())); - return; - } - } - - final Player player = event.getPlayer(); - checkIp(event.getAddress().getHostAddress(), - 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; - } - - //If the IP is whitelisted, we don't want to kick them - InetSocketAddress address = event.getPlayer().getAddress(); - if (address != null){ - InetAddress address1 = address.getAddress(); - if (address1 != null && AntiVPN.getInstance().getExecutor() - .isWhitelisted(address1.getHostAddress())) { - log("IP is whitelisted: %s", - address1.getHostAddress()); - 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 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()) { - final String kickReason = AntiVPN.getInstance().getVpnConfig() - .countryVanillaKickReason(); - - // Start "online" fix - // In case the response was so fast from API the player wouldn't be "online". - event.setResult(PlayerLoginEvent.Result.KICK_BANNED); - event.setKickMessage(ChatColor - .translateAlternateColorCodes('&', - kickReason - .replace("%player%", event.getPlayer().getName()) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode()))); - // End "online" fix - - //Using our built in kicking system if no commands are configured - if(AntiVPN.getInstance().getVpnConfig().countryKickCommands().isEmpty()) { - // Kicking our player - new BukkitRunnable() { - public void run() { - event.getPlayer().kickPlayer(ChatColor - .translateAlternateColorCodes('&', - kickReason - .replace("%player%", event.getPlayer().getName()) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode()))); - } - }.runTask(BukkitPlugin.pluginInstance); - } else { - final String playerName = event.getPlayer().getName(); - - BukkitPlugin.pluginInstance.getPlayerCommandRunner() - .addAction(event.getPlayer().getUniqueId(), () -> { - for (String cmd : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { - final String formattedCommand = ChatColor.translateAlternateColorCodes('&', - cmd.replace("%player%", playerName) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode())); - - // Runs our command from console - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), formattedCommand); - } - }); - } - } else if(result.isProxy()) { - - // Start "online" fix - // In case the response was so fast from API the player wouldn't be "online". - event.setResult(PlayerLoginEvent.Result.KICK_BANNED); - event.setKickMessage(ChatColor - .translateAlternateColorCodes('&', - AntiVPN.getInstance().getVpnConfig().getKickString() - .replace("%player%", event.getPlayer().getName()) - .replace("%country%", result.getCountryName()) - .replace("%code%", result.getCountryCode()))); - // End "online" fix - - if(AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) { - new BukkitRunnable() { - public void run() { - player.kickPlayer(org.bukkit.ChatColor.translateAlternateColorCodes('&', - AntiVPN.getInstance().getVpnConfig().getKickString())); - } - }.runTask(BukkitPlugin.pluginInstance); - } - log(Level.INFO, event.getPlayer().getName() - + " 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().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()) { - String playerName = event.getPlayer().getName(); - BukkitPlugin.pluginInstance.getPlayerCommandRunner() - .addAction(event.getPlayer().getUniqueId(), () -> { - for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), - ChatColor.translateAlternateColorCodes('&', - command.replace("%player%", - playerName))); - } - }); - } - AntiVPN.getInstance().detections++; - } - } 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++; - }); - } - @EventHandler public void onQuit(PlayerQuitEvent event) { AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId()); 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 5fedaa4..dc90784 100644 --- a/Bungee/pom.xml +++ b/Bungee/pom.xml @@ -26,7 +26,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.0 + 3.1.0 @@ -38,10 +38,6 @@ org.yaml.snakeyaml dev.brighten.antivpn.shaded.org.yaml.snakeyaml - - com.google - dev.brighten.antivpn.shaded.com.google - @@ -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 a602781..2ecf55d 100644 --- a/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java +++ b/Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java @@ -1,35 +1,27 @@ package dev.brighten.antivpn.bungee; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; 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 dev.brighten.antivpn.api.*; +import dev.brighten.antivpn.utils.MiscUtils; +import dev.brighten.antivpn.utils.StringUtil; +import dev.brighten.antivpn.utils.Tuple; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.event.PlayerDisconnectEvent; -import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.event.PreLoginEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.scheduler.ScheduledTask; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; +import java.net.InetSocketAddress; import java.util.UUID; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; public class BungeeListener extends VPNExecutor implements Listener { private ScheduledTask cacheResetTask; - private final Cache responseCache = CacheBuilder.newBuilder() - .expireAfterWrite(5, TimeUnit.MINUTES) - .maximumSize(2000) - .build(); - @Override public void registerListeners() { BungeePlugin.pluginInstance.getProxy().getPluginManager() @@ -48,7 +40,7 @@ public class BungeeListener extends VPNExecutor implements Listener { @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 @@ -57,119 +49,77 @@ 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); } - @EventHandler(priority = EventPriority.LOWEST) - public void onListener(final PreLoginEvent event) { - if(!responseCache.asMap().containsKey(event.getConnection().getUniqueId())) return; + @Override + public void runCommand(String command) { + BungeePlugin.pluginInstance.getProxy().getPluginManager() + .dispatchCommand(BungeePlugin.pluginInstance.getProxy().getConsole(), command); + } - VPNResponse cached = responseCache.getIfPresent(event.getConnection().getUniqueId()); - - if(cached != null && cached.isProxy()) { - event.setCancelled(true); - event.setCancelReason(TextComponent.fromLegacyText(ChatColor - .translateAlternateColorCodes('&', - AntiVPN.getInstance().getVpnConfig().getKickString()))); - AntiVPN.getInstance().getExecutor().log(Level.INFO, - "%s was kicked from pre-login proxy cache.", - event.getConnection().getName()); + @Override + public void disablePlugin() { + BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterListeners(BungeePlugin.pluginInstance); + if (cacheResetTask != null) { + cacheResetTask.cancel(); + cacheResetTask = null; } + BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterCommands(BungeePlugin.pluginInstance); + BungeePlugin.pluginInstance.onDisable(); } - @EventHandler(priority = EventPriority.LOWEST) - public void onListener(final PostLoginEvent event) { - if(event.getPlayer().hasPermission("antivpn.bypass") //Has bypass permission - || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() - .anyMatch(prefix -> event.getPlayer().getName().startsWith(prefix))) return; + @EventHandler(priority = EventPriority.HIGH) + public void onListener(final PreLoginEvent event) { - 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; - } + APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId()) + .orElseGet(() -> { + UUID uuid = MiscUtils.lookupUUID(event.getConnection().getName()); + AntiVPN.getInstance().getExecutor().log(Level.INFO, "Getting offline player for %s with name %s", + event.getConnection().getUniqueId(), uuid); - //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; - } + return new OfflinePlayer(uuid, event.getConnection().getName(), + ((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress()); + }); - 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.fromLegacyText(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 - BungeeCord.getInstance().getPluginManager().dispatchCommand( - BungeeCord.getInstance().getConsole(), formattedCommand); - } - } - } 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++; + CheckResult instantResult = player.checkPlayer(result -> { + if (!result.resultType().isShouldBlock()) return; + AntiVPN.getInstance().getExecutor().getToKick() + .add(new Tuple<>(result, player.getUuid())); }); + + if (!instantResult.resultType().isShouldBlock()) { + return; + } + + AntiVPN.getInstance().getExecutor().getToKick() + .add(new Tuple<>(instantResult, player.getUuid())); + + if (!AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) { + return; + } + + event.setCancelled(true); + AntiVPN.getInstance().getExecutor().log(Level.INFO, + "%s was kicked from pre-login proxy cache.", + event.getConnection().getName()); + + + switch (instantResult.resultType()) { + case DENIED_PROXY -> event.setReason(TextComponent.fromLegacy(ChatColor + .translateAlternateColorCodes('&', + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getKickString(), + player, + instantResult.response())))); + case DENIED_COUNTRY -> event.setReason(TextComponent.fromLegacy(ChatColor + .translateAlternateColorCodes('&', + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().countryVanillaKickReason(), + player, + instantResult.response())))); + } } @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 5d8e70a..eb861d9 100644 --- a/Common/pom.xml +++ b/Common/pom.xml @@ -38,7 +38,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.0 + 3.6.0 package @@ -51,14 +51,60 @@ 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.github.benmanes.caffeine + dev.brighten.antivpn.shaded.com.github.benmanes.caffeine 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,6 +133,24 @@ sqlite-jdbc 3.48.0.0 + + com.h2database + h2 + 2.2.220 + provided + + + org.ow2.asm + asm + 9.8 + compile + + + org.ow2.asm + asm-commons + 9.8 + compile + org.yaml snakeyaml @@ -94,10 +158,12 @@ compile - com.google.guava - guava - 32.1.3-jre + com.github.ben-manes.caffeine + caffeine + 3.1.8 + provided + org.mongodb mongo-java-driver diff --git a/Common/src/main/java/dev/brighten/antivpn/AntiVPN.java b/Common/src/main/java/dev/brighten/antivpn/AntiVPN.java index 1d6aaa6..05d32ce 100644 --- a/Common/src/main/java/dev/brighten/antivpn/AntiVPN.java +++ b/Common/src/main/java/dev/brighten/antivpn/AntiVPN.java @@ -7,6 +7,9 @@ import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.impl.AntiVPNCommand; import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.sqllite.LiteDatabase; +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; @@ -27,6 +30,25 @@ 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") + } +) +@MavenLibrary(groupId = "com.\\github\\.ben-manes\\.caffeine", artifactId = "caffeine", version = "3.1.8", + relocations = { + @Relocate(from = "com\\.github\\.benmanes\\.caffeine", to = "dev.brighten.antivpn.shaded.com.github.benmanes.caffeine"), + }) public class AntiVPN { private static AntiVPN INSTANCE; @@ -49,16 +71,22 @@ public class AntiVPN { INSTANCE.executor = executor; INSTANCE.playerExecutor = playerExecutor; + LibraryLoader.loadAll(INSTANCE); + try { File configFile = new File(pluginFolder, "config.yml"); if(!configFile.exists()){ - configFile.getParentFile().mkdirs(); + if(configFile.getParentFile().mkdirs()) { + AntiVPN.getInstance().getExecutor().log("Created plugin folder!"); + } MiscUtils.copy(INSTANCE.getResource( "config.yml"), configFile); } INSTANCE.config = ConfigurationProvider.getProvider(YamlConfiguration.class) .load(configFile); } catch (IOException e) { - e.printStackTrace(); + AntiVPN.getInstance().getExecutor().logException("Could not load config.yml, plugin disabling...", e); + executor.disablePlugin(); + return; } INSTANCE.vpnConfig = new VPNConfig(); @@ -89,6 +117,9 @@ public class AntiVPN { (vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), AntiVPN.getInstance()) .get()); AntiVPN.getInstance().getMessageHandler().reloadStrings(); + + // Starting kick checks + AntiVPN.getInstance().getExecutor().startKickChecks(); } public InputStream getResource(String filename) { @@ -111,7 +142,21 @@ public class AntiVPN { } public void stop() { - executor.shutdown(); + 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(); } @@ -132,7 +177,7 @@ public class AntiVPN { ConfigurationProvider.getProvider(YamlConfiguration.class) .save(getConfig(), new File(pluginFolder.getPath() + File.separator + "config.yml")); } catch (IOException e) { - e.printStackTrace(); + AntiVPN.getInstance().getExecutor().logException(e); } } diff --git a/Common/src/main/java/dev/brighten/antivpn/api/APIPlayer.java b/Common/src/main/java/dev/brighten/antivpn/api/APIPlayer.java index 72fa742..1c0f2bc 100644 --- a/Common/src/main/java/dev/brighten/antivpn/api/APIPlayer.java +++ b/Common/src/main/java/dev/brighten/antivpn/api/APIPlayer.java @@ -1,18 +1,30 @@ package dev.brighten.antivpn.api; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import dev.brighten.antivpn.AntiVPN; import lombok.Getter; +import lombok.Setter; import java.net.InetAddress; import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.logging.Level; @Getter public abstract class APIPlayer { private final UUID uuid; private final String name; private final InetAddress ip; + @Setter private boolean alertsEnabled; + private static final Cache checkResultCache = Caffeine.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .maximumSize(2000) + .build(); + public APIPlayer(UUID uuid, String name, InetAddress ip) { this.uuid = uuid; this.name = name; @@ -25,12 +37,65 @@ public abstract class APIPlayer { public abstract boolean hasPermission(String permission); - public void setAlertsEnabled(boolean alertsEnabled) { - this.alertsEnabled = alertsEnabled; - } - public void updateAlertsState() { //Updating into database so its synced across servers and saved on logout. AntiVPN.getInstance().getDatabase().updateAlertsState(uuid, alertsEnabled); } + + public CheckResult checkPlayer(Consumer onKick) { + if (hasPermission("antivpn.bypass") //Has bypass permission + //Is exempt + || (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid)) + //Or has a name that starts with a certain prefix. This is for Bedrock exempting. + || AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress()) + || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() + .anyMatch(name::startsWith)) return new CheckResult(null, ResultType.WHITELISTED); + + CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress()); + + if(cachedResult != null) { + if(cachedResult.response().getIp().equals(ip.getHostAddress())) { + AntiVPN.getInstance().getExecutor().log(Level.FINE, "Cached result for " + ip.getHostAddress() + " is " + cachedResult.resultType()); + return cachedResult; + } + } + + AntiVPN.getInstance().getExecutor().checkIp(ip.getHostAddress()) + .thenAccept(result -> { + if(!result.isSuccess()) { + AntiVPN.getInstance().getExecutor().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 + CheckResult checkResult; + if (!AntiVPN.getInstance().getVpnConfig().countryList().isEmpty() + && !((uuid != null && AntiVPN.getInstance().getExecutor() + .isWhitelisted(uuid)) + //Or has a name that starts with a certain prefix. This is for Bedrock exempting. + || AntiVPN.getInstance().getExecutor().isWhitelisted(ip.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 + checkResult = new CheckResult(result, ResultType.DENIED_COUNTRY); + } else if (result.isProxy()) { + checkResult = new CheckResult(result, ResultType.DENIED_PROXY); + } else { + checkResult = new CheckResult(result, ResultType.ALLOWED); + } + + AntiVPN.getInstance().getExecutor().log(Level.FINE, "Result for " + ip.getHostAddress() + " is " + checkResult.resultType()); + + checkResultCache.put(ip.getHostAddress(), checkResult); + onKick.accept(checkResult); + AntiVPN.getInstance().checked++; + }); + return new CheckResult(null, ResultType.UNKNOWN); + } } diff --git a/Common/src/main/java/dev/brighten/antivpn/api/CheckResult.java b/Common/src/main/java/dev/brighten/antivpn/api/CheckResult.java new file mode 100644 index 0000000..c331961 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/api/CheckResult.java @@ -0,0 +1,6 @@ +package dev.brighten.antivpn.api; + +import dev.brighten.antivpn.web.objects.VPNResponse; + +public record CheckResult(VPNResponse response, ResultType resultType) { +} diff --git a/Common/src/main/java/dev/brighten/antivpn/api/OfflinePlayer.java b/Common/src/main/java/dev/brighten/antivpn/api/OfflinePlayer.java new file mode 100644 index 0000000..27ce5f7 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/api/OfflinePlayer.java @@ -0,0 +1,26 @@ +package dev.brighten.antivpn.api; + +import java.net.InetAddress; +import java.util.UUID; + +public class OfflinePlayer extends APIPlayer { + + public OfflinePlayer(UUID uuid, String name, InetAddress ip) { + super(uuid, name, ip); + } + + @Override + public void sendMessage(String message) { + + } + + @Override + public void kickPlayer(String reason) { + + } + + @Override + public boolean hasPermission(String permission) { + return false; + } +} diff --git a/Common/src/main/java/dev/brighten/antivpn/api/ResultType.java b/Common/src/main/java/dev/brighten/antivpn/api/ResultType.java new file mode 100644 index 0000000..e8dfea4 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/api/ResultType.java @@ -0,0 +1,18 @@ +package dev.brighten.antivpn.api; + +import lombok.Getter; + +public enum ResultType { + ALLOWED(false), + WHITELISTED(false), + DENIED_COUNTRY(true), + DENIED_PROXY(true), + UNKNOWN(false); + + @Getter + private final boolean shouldBlock; + + ResultType(boolean shouldBlock) { + this.shouldBlock = shouldBlock; + } +} 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 5117524..e979581 100644 --- a/Common/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java +++ b/Common/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java @@ -1,8 +1,8 @@ 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.StringUtil; +import dev.brighten.antivpn.utils.Tuple; import dev.brighten.antivpn.utils.json.JSONException; import dev.brighten.antivpn.web.FunkemunkyAPI; import dev.brighten.antivpn.web.objects.VPNResponse; @@ -10,11 +10,10 @@ 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 { @@ -28,10 +27,8 @@ public abstract class VPNExecutor { @Getter private final Set whitelistedIps = Collections.synchronizedSet(new HashSet<>()); - private final Cache responseCache = CacheBuilder.newBuilder() - .expireAfterWrite(20, TimeUnit.MINUTES) - .maximumSize(4000) - .build(); + @Getter + private final List> toKick = Collections.synchronizedList(new LinkedList<>()); public abstract void registerListeners(); @@ -43,12 +40,75 @@ public abstract class VPNExecutor { 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 abstract void runCommand(String command); + + public void logException(Throwable ex) { logException("An exception occurred: " + ex.getMessage(), ex); } + public void startKickChecks() { + threadExecutor.scheduleAtFixedRate(() -> { + synchronized (toKick) { + if(toKick.isEmpty()) return; + + Iterator> i = toKick.iterator(); + + while(i.hasNext()) { + var toCheck = i.next(); + + Optional player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(toCheck.second()); + + if(player.isEmpty()) { + continue; + } + + handleKickingOfPlayer(toCheck.first(), player.get()); + + i.remove(); + } + } + }, 8, 2, TimeUnit.SECONDS); + } + + public void handleKickingOfPlayer(CheckResult result, APIPlayer player) { + if (AntiVPN.getInstance().getVpnConfig().alertToStaff()) AntiVPN.getInstance().getPlayerExecutor() + .getOnlinePlayers() + .stream() + .filter(APIPlayer::isAlertsEnabled) + .forEach(pl -> + pl.sendMessage(StringUtil.translateAlternateColorCodes('&', + StringUtil.varReplace(dev.brighten.antivpn.AntiVPN.getInstance().getVpnConfig() + .alertMessage(), player, result.response())))); + + if(AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) { + switch (result.resultType()) { + case DENIED_PROXY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() + .getKickString(), player, result.response())); + case DENIED_COUNTRY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() + .countryVanillaKickReason(), player, result.response())); + } + } + + if(!AntiVPN.getInstance().getVpnConfig().runCommands()) return; + + switch (result.resultType()) { + case DENIED_PROXY -> { + for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { + runCommand(StringUtil.translateAlternateColorCodes('&', + StringUtil.varReplace(command, player, result.response()))); + } + } + case DENIED_COUNTRY -> { + for (String command : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { + runCommand(StringUtil.translateAlternateColorCodes('&', + StringUtil.varReplace(command, player, result.response()))); + } + } + } + } + public boolean isWhitelisted(UUID uuid) { if(AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) { return AntiVPN.getInstance().getDatabase().isWhitelisted(uuid); @@ -63,43 +123,32 @@ 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)); } - }); + }, threadExecutor); } - 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; - } - } - } + 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/depends/LibraryLoader.java b/Common/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java new file mode 100644 index 0000000..8f16b26 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java @@ -0,0 +1,371 @@ +/* + * 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 = AntiVPN.getInstance().getClass().getClassLoader() instanceof URLClassLoader ? + Suppliers.memoize(() -> + URLClassLoaderAccess.create((URLClassLoader) AntiVPN.getInstance().getClass().getClassLoader())) + : null; + + public static void loadAll(Object object) { + if(URL_INJECTOR == null) + return; + loadAll(object.getClass()); + } + + public static void loadAll(Class clazz) { + if(URL_INJECTOR == null) + return; + 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/MiscUtils.java b/Common/src/main/java/dev/brighten/antivpn/utils/MiscUtils.java index 41c1856..77cde0b 100644 --- a/Common/src/main/java/dev/brighten/antivpn/utils/MiscUtils.java +++ b/Common/src/main/java/dev/brighten/antivpn/utils/MiscUtils.java @@ -1,6 +1,12 @@ package dev.brighten.antivpn.utils; +import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.utils.json.JSONException; +import dev.brighten.antivpn.utils.json.JSONObject; +import dev.brighten.antivpn.utils.json.JsonReader; + import java.io.*; +import java.util.UUID; import java.util.concurrent.ThreadFactory; import java.util.regex.Pattern; @@ -12,7 +18,7 @@ public class MiscUtils { try { for (Closeable closeable : closeables) if (closeable != null) closeable.close(); } catch (Exception e) { - e.printStackTrace(); + AntiVPN.getInstance().getExecutor().logException(e); } } @@ -20,7 +26,7 @@ public class MiscUtils { try { for (AutoCloseable closeable : closeables) if (closeable != null) closeable.close(); } catch (Exception e) { - e.printStackTrace(); + AntiVPN.getInstance().getExecutor().logException(e); } } @@ -38,7 +44,7 @@ public class MiscUtils { out.close(); in.close(); } catch (Exception e) { - e.printStackTrace(); + AntiVPN.getInstance().getExecutor().logException(e); } } @@ -50,6 +56,33 @@ public class MiscUtils { }; } + public static UUID formatFromMojangUUID(String mojangUUID) { + StringBuilder uuid = new StringBuilder(); + for(int i = 0; i <= 31; i++) { + uuid.append(mojangUUID.charAt(i)); + if(i == 7 || i == 11 || i == 15 || i == 19) { + uuid.append("-"); + } + } + + return UUID.fromString(uuid.toString()); + } + + public static UUID lookupUUID(String playername) { + try { + JSONObject object = JsonReader + .readJsonFromUrl("https://funkemunky.cc/mojang/uuid?name=" + playername); + + if(object.has("uuid")) { + return UUID.fromString(object.getString("uuid")); + } + } catch (IOException | JSONException e) { + AntiVPN.getInstance().getExecutor().logException("Error while looking up UUID for " + playername, e); + } + + return null; + } + public static boolean isIpv4(String ip) { return ipv4.matcher(ip).matches(); 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/StringUtil.java b/Common/src/main/java/dev/brighten/antivpn/utils/StringUtil.java index b47e5b4..7ef61cb 100644 --- a/Common/src/main/java/dev/brighten/antivpn/utils/StringUtil.java +++ b/Common/src/main/java/dev/brighten/antivpn/utils/StringUtil.java @@ -6,6 +6,9 @@ import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import dev.brighten.antivpn.api.APIPlayer; +import dev.brighten.antivpn.web.objects.VPNResponse; + public class StringUtil { public static String line(String color) { return color + "&m-----------------------------------------------------"; @@ -33,4 +36,24 @@ public class StringUtil { } return null; } + + public static String varReplace(String input, APIPlayer player, VPNResponse result) { + return input.replace("%player%", player.getName()) + .replace("%reason%", result.getMethod()) + .replace("%country%", result.getCountryName()) + .replace("%city%", result.getCity()); + } + + public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { + char[] b = textToTranslate.toCharArray(); + + for(int i = 0; i < b.length - 1; ++i) { + if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) { + b[i] = 167; + b[i + 1] = Character.toLowerCase(b[i + 1]); + } + } + + return new String(b); + } } 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/Common/src/main/java/dev/brighten/antivpn/utils/Tuple.java b/Common/src/main/java/dev/brighten/antivpn/utils/Tuple.java new file mode 100644 index 0000000..0e54854 --- /dev/null +++ b/Common/src/main/java/dev/brighten/antivpn/utils/Tuple.java @@ -0,0 +1,5 @@ +package dev.brighten.antivpn.utils; + +public record Tuple(F first, S second) { + +} diff --git a/Common/src/main/java/dev/brighten/antivpn/web/FunkemunkyAPI.java b/Common/src/main/java/dev/brighten/antivpn/web/FunkemunkyAPI.java index fc35735..7710325 100644 --- a/Common/src/main/java/dev/brighten/antivpn/web/FunkemunkyAPI.java +++ b/Common/src/main/java/dev/brighten/antivpn/web/FunkemunkyAPI.java @@ -12,7 +12,7 @@ public class FunkemunkyAPI { /** * - * Queries https://funkemunky.cc/vpn API and returns information on the IP + * Queries ... API and returns information on the IP * * @param ip String * @param license String @@ -25,7 +25,7 @@ public class FunkemunkyAPI { throws JSONException, IOException { JSONObject result = JsonReader.readJsonFromUrl(String .format("https://funkemunky.cc/vpn?ip=%s&license=%s&cache=%s", - ip, license.length() == 0 ? "none" : license, cachedResults)); + ip, license.isEmpty() ? "none" : license, cachedResults)); return VPNResponse.fromJson(result); } @@ -43,7 +43,7 @@ public class FunkemunkyAPI { } /** - * Queries https://funkemunky.cc/vpn/queryCheck and returns information based on the + * Queries ... and returns information based on the * provided licence input. * * @param license String diff --git a/Common/src/main/java/dev/brighten/antivpn/web/objects/QueryResponse.java b/Common/src/main/java/dev/brighten/antivpn/web/objects/QueryResponse.java index 2b2d6dd..3dc96bb 100644 --- a/Common/src/main/java/dev/brighten/antivpn/web/objects/QueryResponse.java +++ b/Common/src/main/java/dev/brighten/antivpn/web/objects/QueryResponse.java @@ -7,7 +7,7 @@ import lombok.Data; /** - * Used to format the JSON response from https://funkemunky.cc/vpn/queryCheck into an object for project use. + * Used to format the JSON response from ... into an object for project use. */ @Data @Builder(toBuilder = true) @@ -19,18 +19,7 @@ public class QueryResponse { private long queriesMax; /** - * Takes a JSON String and feeds it into {@link QueryResponse#fromJson(JSONObject)} - * - * @param jsonString String (formatted in JSON) - * @return QueryResponse - * @throws JSONException Throws when JSON is not formatted properly. - */ - public static QueryResponse fromJson(String jsonString) throws JSONException { - return fromJson(new JSONObject(jsonString)); - } - - /** - * Formats response from https://funkemunky.cc/vpn/queryCheck into {@link QueryResponse} for project use. + * Formats response from ... into {@link QueryResponse} for project use. * * @param object JSONObject * @return QueryResponse diff --git a/Common/src/main/resources/config.yml b/Common/src/main/resources/config.yml index a28421a..4827f1e 100644 --- a/Common/src/main/resources/config.yml +++ b/Common/src/main/resources/config.yml @@ -11,7 +11,8 @@ cachedResults: true # All players with any of the following characters in the beginning of their name will be whitelisted # This is a useful feature for servers that allow players through Geyser and their IPs are not forwarded, causing # players to be removed falsely for use of proxy. -prefixWhitelists: [] +prefixWhitelists: + - "*" # Configure your database here. database: # Enable to cache queries and save alerts state beyond restarts diff --git a/Sponge/pom.xml b/Sponge/pom.xml index 62be928..27e5520 100644 --- a/Sponge/pom.xml +++ b/Sponge/pom.xml @@ -13,8 +13,12 @@ - sponge - https://repo.spongepowered.org/repository/maven-public/ + spongepowered-repo + https://repo.spongepowered.org/maven/ + + + funkemunky-releases + https://nexus.funkemunky.cc/content/repositories/releases/ @@ -22,20 +26,154 @@ org.spongepowered spongeapi - 8.1.0 + 11.0.0 provided dev.brighten.antivpn Common 2.0.0-SNAPSHOT + compile + + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + compile + + + org.mongodb + mongo-java-driver + 3.12.14 + compile + + + com.mysql + mysql-connector-j + 9.1.0 + jar + compile + + + com.h2database + h2 + 2.2.220 + compile + 2.0.0-SNAPSHOT provided - 17 - 17 + 21 + 21 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 21 + 21 + -XDignore.symbol.file + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + package + + shade + + + + + *:* + + com/google/** + org/objectweb/** + org/checkerframework/** + + + + + + + org.yaml.snakeyaml + dev.brighten.antivpn.shaded.org.yaml.snakeyaml + + + dev.brighten.antivpn.depends.Relocate + dev.brighten.antivpn.depends.MavenLibraries + + + + com.github.benmanes.caffeine + dev.brighten.antivpn.shaded.com.github.benmanes.caffeine + + + 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 + + + + + + + + + + + src/main/resources + true + + + + \ No newline at end of file diff --git a/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongeListener.java b/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongeListener.java new file mode 100644 index 0000000..47b6664 --- /dev/null +++ b/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongeListener.java @@ -0,0 +1,114 @@ +package dev.brighten.antivpn.sponge; + +import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.api.*; +import dev.brighten.antivpn.sponge.util.StringUtil; +import dev.brighten.antivpn.utils.Tuple; +import net.kyori.adventure.text.Component; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.exception.CommandException; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.network.ServerSideConnectionEvent; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; + +public class SpongeListener extends VPNExecutor { + + @Listener(order = Order.EARLY) + public void onJoin(ServerSideConnectionEvent.Login event) { + AtomicReference player = new AtomicReference<>(AntiVPN.getInstance().getPlayerExecutor() + .getPlayer(event.profile().uuid()) + .orElse(new OfflinePlayer( + event.profile().uuid(), + event.profile().name().orElse("Unknown"), + event.connection().address().getAddress() + ))); + + CheckResult instantResult = player.get().checkPlayer(result -> { + if(result.resultType().isShouldBlock()) { + AntiVPN.getInstance().getExecutor().getToKick().add(new Tuple<>(result, player.get().getUuid())); + } + }); + + if(!instantResult.resultType().isShouldBlock()) { + return; + } + + AntiVPN.getInstance().getExecutor().getToKick().add(new Tuple<>(instantResult, player.get().getUuid())); + + if(!AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) { + return; + } + + AntiVPN.getInstance().getExecutor().log(Level.INFO, "%s was kicked from cache with IP %s", player.get().getName(), instantResult.response().getIp()); + + event.setCancelled(true); + switch (instantResult.resultType()) { + case DENIED_PROXY -> { + AntiVPN.getInstance().getExecutor().log(Level.INFO, player.get().getName() + + " joined on a VPN/Proxy (" + instantResult.response().getMethod() + ")"); + event.setMessage(Component.text(StringUtil + .translateColorCodes('&', AntiVPN.getInstance().getVpnConfig() + .getKickString() + .replace("%player%", player.get().getName()) + .replace("%country%", instantResult.response().getCountryName()) + .replace("%code%", instantResult.response().getCountryCode())))); + } + case DENIED_COUNTRY -> + event.setMessage(Component.text(StringUtil + .translateColorCodes('&', AntiVPN.getInstance().getVpnConfig() + .countryVanillaKickReason() + .replace("%player%", player.get().getName()) + .replace("%country%", instantResult.response().getCountryName()) + .replace("%code%", instantResult.response().getCountryCode())))); + } + } + + @Listener + public void onPlayerDisconnect(ServerSideConnectionEvent.Disconnect event) { + event.profile().ifPresent(profile -> + AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(profile.uuid())); + } + + @Override + public void registerListeners() { + Sponge.eventManager().registerListeners(SpongePlugin.getInstance().getContainer(), this); + } + + @Override + public void log(Level level, String log, Object... objects) { + if (level.equals(Level.SEVERE)) { + SpongePlugin.getInstance().getLogger().error(String.format(log, objects)); + } else if (level.equals(Level.WARNING)) { + SpongePlugin.getInstance().getLogger().warn(String.format(log, objects)); + } else { + SpongePlugin.getInstance().getLogger().info(String.format(log, objects)); + } + } + + @Override + public void log(String log, Object... objects) { + log(Level.INFO, String.format(log, objects)); + } + + @Override + public void logException(String message, Throwable ex) { + SpongePlugin.getInstance().getLogger().error(message, ex); + } + + @Override + public void runCommand(String command) { + try { + Sponge.server().commandManager().process(Sponge.systemSubject(), command); + } catch (CommandException e) { + logException(e); + } + } + + @Override + public void disablePlugin() { + AntiVPN.getInstance().getExecutor().log(Level.INFO, "Disabling listeners for plugin..."); + } +} diff --git a/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlayer.java b/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlayer.java index 9ce17f9..2052eea 100644 --- a/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlayer.java +++ b/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlayer.java @@ -16,7 +16,7 @@ public class SpongePlayer extends APIPlayer { @Override public void sendMessage(String message) { - //player.sendMessage(StringUtil.translateColorCodes('&', message)); + player.sendMessage(Component.text(StringUtil.translateColorCodes('&', message))); } @Override diff --git a/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlayerExecutor.java b/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlayerExecutor.java index 414d7b3..3904633 100644 --- a/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlayerExecutor.java +++ b/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlayerExecutor.java @@ -1,31 +1,70 @@ package dev.brighten.antivpn.sponge; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.PlayerExecutor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.TimeUnit; public class SpongePlayerExecutor implements PlayerExecutor { + + private final Cache playerCache = Caffeine.newBuilder().maximumSize(10000) + .expireAfterAccess(30, TimeUnit.MINUTES) + .build(); + @Override public Optional getPlayer(String name) { + Optional serverPlayer = Sponge.server().player(name); - return Optional.empty(); + return serverPlayer.map(SpongePlayer::new); } @Override public Optional getPlayer(UUID uuid) { - return Optional.empty(); + SpongePlayer cachedPlayer = playerCache.getIfPresent(uuid); + + if(cachedPlayer != null) { + return Optional.of(cachedPlayer); + } + + Optional serverPlayer = Sponge.server().player(uuid); + + Optional player = serverPlayer.map(SpongePlayer::new); + + player.ifPresent(value -> playerCache.put(uuid, (SpongePlayer) value)); + + return player; } @Override public void unloadPlayer(UUID uuid) { - + playerCache.invalidate(uuid); } @Override public List getOnlinePlayers() { - return null; + if(!Sponge.game().isServerAvailable()) return Collections.emptyList(); + return Sponge.server().onlinePlayers() + .stream() + .map(pl -> { + SpongePlayer cachedPlayer = playerCache.getIfPresent(pl.uniqueId()); + + if(cachedPlayer != null) { + return cachedPlayer; + } + + SpongePlayer player = new SpongePlayer(pl); + playerCache.put(pl.uniqueId(), player); + + return (APIPlayer) player; + }) + .toList(); } } diff --git a/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlugin.java b/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlugin.java index 39d9c50..1aa11f2 100644 --- a/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlugin.java +++ b/Sponge/src/main/java/dev/brighten/antivpn/sponge/SpongePlugin.java @@ -1,28 +1,60 @@ package dev.brighten.antivpn.sponge; import com.google.inject.Inject; +import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.sponge.command.SpongeCommand; +import lombok.Getter; import org.spongepowered.api.Server; +import org.apache.logging.log4j.Logger; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.Command; +import org.spongepowered.api.config.ConfigManager; import org.spongepowered.api.event.Listener; -import org.spongepowered.api.event.lifecycle.StartedEngineEvent; +import org.spongepowered.api.event.lifecycle.*; +import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.builtin.jvm.Plugin; -import java.util.logging.Logger; - @Plugin("kaurivpn") +@Getter public class SpongePlugin { - public static SpongePlugin INSTANCE; //Plugin init - + @Inject + private PluginContainer container; @Inject private Logger logger; + @Getter + private static SpongePlugin instance; @Listener - public void onServerStart(final StartedEngineEvent event) { - INSTANCE = this; + public void onConstruct(final ConstructPluginEvent event) { + instance = this; - logger.info("Starting AntiVPN services..."); - //Start AntiVPN + ConfigManager configManager = Sponge.configManager(); + SpongeListener spongeListener = new SpongeListener(); + + var path = configManager.sharedConfig(container).directory(); + + logger.info("Fucking path: " + path); + + AntiVPN.start(spongeListener, new SpongePlayerExecutor(), path.toFile()); } + @Listener + public void onServer(final StoppingEngineEvent event) { + AntiVPN.getInstance().getExecutor().disablePlugin(); + } + + @Listener + public void onRegisterRawCommands(final RegisterCommandEvent event){ + if(AntiVPN.getInstance() == null) { + for(int i = 0 ; i < 5 ; i++) System.out.println("FUCKING NULL"); + return; + } + AntiVPN.getInstance().getExecutor().log("Registering commands..."); + for (dev.brighten.antivpn.command.Command command : AntiVPN.getInstance().getCommands()) { + AntiVPN.getInstance().getExecutor().log("Registering command %s...", command.name()); + event.register(this.container, new SpongeCommand(command), command.name(), command.aliases()); + } + } } diff --git a/Sponge/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommand.java b/Sponge/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommand.java new file mode 100644 index 0000000..3bccdc2 --- /dev/null +++ b/Sponge/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommand.java @@ -0,0 +1,111 @@ +package dev.brighten.antivpn.sponge.command; + +import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.command.Command; +import dev.brighten.antivpn.command.CommandExecutor; +import dev.brighten.antivpn.utils.StringUtil; +import lombok.val; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.command.CommandCompletion; +import org.spongepowered.api.command.CommandResult; +import org.spongepowered.api.command.parameter.ArgumentReader; + +import java.util.*; +import java.util.stream.IntStream; + +public class SpongeCommand implements org.spongepowered.api.command.Command.Raw { + + private final Command command; + + public SpongeCommand(Command command) { + this.command = command; + } + + @Override + public CommandResult process(CommandCause sender, ArgumentReader.Mutable arguments) { + + String[] args = arguments.input().split(" "); + + CommandExecutor commandExecutor = new SpongeCommandExecutor(sender); + + val children = command.children(); + + if(children.length > 0 && args.length > 0) { + for (dev.brighten.antivpn.command.Command child : children) { + if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) + .anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { + if(!sender.hasPermission("antivpn.command.*") + && !sender.hasPermission(child.permission())) { + return CommandResult.error(Component.text(StringUtil.translateAlternateColorCodes('&', + AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()))); + } + + commandExecutor.sendMessage(StringUtil + .translateAlternateColorCodes('&', + child.execute(commandExecutor, IntStream + .range(0, args.length - 1) + .mapToObj(i -> args[i + 1]).toArray(String[]::new)))); + return CommandResult.success(); + } + } + } + + commandExecutor.sendMessage(StringUtil + .translateAlternateColorCodes('&', + command.execute(new SpongeCommandExecutor(sender), args))); + + command.execute(new SpongeCommandExecutor(sender), args); + return CommandResult.success(); + } + + @Override + public List complete(CommandCause sender, ArgumentReader.Mutable arguments) { + val children = command.children(); + String[] args = arguments.input().split(" "); + if(children.length > 0 && args.length > 0) { + for (dev.brighten.antivpn.command.Command child : children) { + if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) + .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { + return child.tabComplete(new SpongeCommandExecutor(sender), "alias", IntStream + .range(0, args.length - 1) + .mapToObj(i -> args[i + 1]).toArray(String[]::new)) + .stream() + .map(CommandCompletion::of) + .toList(); + } + } + } + return command.tabComplete(new SpongeCommandExecutor(sender), "alias", args) + .stream() + .map(CommandCompletion::of) + .toList(); + } + + @Override + public boolean canExecute(CommandCause cause) { + return cause.hasPermission(command.permission()); + } + + @Override + public Optional shortDescription(CommandCause cause) { + return command.description() != null ? Optional.of(Component.text(command.description())) : Optional.empty(); + } + + @Override + public Optional extendedDescription(CommandCause cause) { + return Optional.empty(); + } + + @Override + public Optional help(@NonNull CommandCause cause) { + return Optional.of(Component.text(StringUtil.translateAlternateColorCodes('&', + command.execute(new SpongeCommandExecutor(cause), new String[0])))); + } + + @Override + public Component usage(CommandCause cause) { + return command.usage() != null ? Component.text(command.usage()) : Component.empty(); + } +} diff --git a/Sponge/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommandExecutor.java b/Sponge/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommandExecutor.java new file mode 100644 index 0000000..de96dd5 --- /dev/null +++ b/Sponge/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommandExecutor.java @@ -0,0 +1,42 @@ +package dev.brighten.antivpn.sponge.command; + +import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.api.APIPlayer; +import dev.brighten.antivpn.command.CommandExecutor; +import dev.brighten.antivpn.sponge.util.StringUtil; +import lombok.RequiredArgsConstructor; +import net.kyori.adventure.text.Component; +import org.spongepowered.api.command.CommandCause; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; + +import java.util.Optional; + +@RequiredArgsConstructor +public class SpongeCommandExecutor implements CommandExecutor { + + private final CommandCause cause; + + @Override + public void sendMessage(String message, Object... objects) { + cause.sendMessage(Component.text(StringUtil.translateColorCodes('&', + String.format(message, objects)))); + } + + @Override + public boolean hasPermission(String permission) { + return cause.hasPermission(permission); + } + + @Override + public Optional getPlayer() { + if(cause.subject() instanceof ServerPlayer serverPlayer) { + return AntiVPN.getInstance().getPlayerExecutor().getPlayer(serverPlayer.uniqueId()); + } + return Optional.empty(); + } + + @Override + public boolean isPlayer() { + return cause.subject() instanceof ServerPlayer; + } +} diff --git a/Sponge/src/main/resources/META-INF/sponge_plugins.json b/Sponge/src/main/resources/META-INF/sponge_plugins.json new file mode 100644 index 0000000..be21d3b --- /dev/null +++ b/Sponge/src/main/resources/META-INF/sponge_plugins.json @@ -0,0 +1,29 @@ +{ + "loader": { + "name": "java_plain", + "version": "1.0" + }, + "license": "All-Rights-Reserved", + "plugins": [ + { + "id": "kaurivpn", + "name": "Kauri VPN", + "version": "${version}", + "entrypoint": "dev.brighten.antivpn.sponge.SpongePlugin", + "description": "A simple and fast antivpn plugin.", + "branding": {}, + "links": { + }, + "contributors": [ + ], + "dependencies": [ + { + "id": "spongeapi", + "version": "11.0.0", + "load-order": "after", + "optional": false + } + ] + } + ] +} diff --git a/Assembly/pom.xml b/Universal/pom.xml similarity index 68% rename from Assembly/pom.xml rename to Universal/pom.xml index 22efa46..66e21c3 100644 --- a/Assembly/pom.xml +++ b/Universal/pom.xml @@ -2,26 +2,53 @@ + 4.0.0 - AntiVPN dev.brighten.antivpn + AntiVPN 2.0.0-SNAPSHOT - 4.0.0 - Assembly + Universal 17 17 + + + dev.brighten.antivpn + Common + ${project.version} + compile + + + dev.brighten.antivpn + Bukkit + ${project.version} + compile + + + dev.brighten.antivpn + Bungee + ${project.version} + compile + + + dev.brighten.antivpn + Velocity + ${project.version} + compile + + + org.apache.maven.plugins maven-shade-plugin - 3.5.0 + 3.6.0 package @@ -29,15 +56,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 + + org.objectweb + dev.brighten.antivpn.shaded.org.objectweb + + @@ -46,31 +83,4 @@ - - - dev.brighten.antivpn - Bungee - ${version} - compile - - - dev.brighten.antivpn - Velocity - ${version} - compile - - - dev.brighten.antivpn - Common - ${version} - compile - - - dev.brighten.antivpn - Bukkit - ${version} - compile - - - \ No newline at end of file diff --git a/Velocity/pom.xml b/Velocity/pom.xml index bd501ef..98026cc 100644 --- a/Velocity/pom.xml +++ b/Velocity/pom.xml @@ -59,7 +59,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.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 ff22744..98d2d6b 100644 --- a/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java +++ b/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java @@ -1,181 +1,168 @@ package dev.brighten.antivpn.velocity; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; 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.CheckResult; +import dev.brighten.antivpn.api.OfflinePlayer; import dev.brighten.antivpn.api.VPNExecutor; -import dev.brighten.antivpn.velocity.util.StringUtils; +import dev.brighten.antivpn.utils.StringUtil; 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(); - - @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, 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; + APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId()) + .orElse(new OfflinePlayer( + event.getPlayer().getUniqueId(), + event.getPlayer().getUsername(), + event.getPlayer().getRemoteAddress().getAddress() + )); - if(responseCache.asMap().containsKey(event.getPlayer().getUniqueId())) { - VPNResponse cached = responseCache.getIfPresent(event.getPlayer().getUniqueId()); + CheckResult instantResult = player.checkPlayer(result -> { + if(!result.resultType().isShouldBlock()) return; - if (cached != null && cached.isProxy()) { - event.setResult(ResultedEvent.ComponentResult.denied(Component.text("No"))); - return; - } - } + handleDeniedTasks(event, result); + }); - 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())))); + if(!instantResult.resultType().isShouldBlock()) return; - 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()))); + switch (instantResult.resultType()) { + case DENIED_COUNTRY -> event.setResult(ResultedEvent.ComponentResult.denied( + LegacyComponentSerializer.builder() + .character('&') + .build().deserialize(AntiVPN.getInstance().getVpnConfig() + .countryVanillaKickReason() + .replace("%player%", event.getPlayer().getUsername()) + .replace("%country%", instantResult.response().getCountryName()) + .replace("%code%", instantResult.response().getCountryCode())))); + case DENIED_PROXY -> { + VelocityPlugin.INSTANCE.getLogger().info(event.getPlayer().getUsername() + + " joined on a VPN/Proxy (" + instantResult.response().getMethod() + ")"); + event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder() + .character('&') + .build().deserialize(AntiVPN.getInstance().getVpnConfig() + .getKickString() + .replace("%player%", event.getPlayer().getUsername()) + .replace("%country%", instantResult.response().getCountryName()) + .replace("%code%", instantResult.response().getCountryCode())))); + } + } - //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++; - }); - } - }); + handleDeniedTasks(event, instantResult, true); + }); } + private void handleDeniedTasks(LoginEvent event, CheckResult result) { + handleDeniedTasks(event, result, false); + } + + private void handleDeniedTasks(LoginEvent event, CheckResult checkResult, boolean deniedOnLogin) { + VPNResponse result = checkResult.response(); + //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(dev.brighten.antivpn.AntiVPN.getInstance().getVpnConfig() + .alertMessage() + .replace("%player%", + event.getPlayer().getUsername()) + .replace("%reason%", + result.getMethod()) + .replace("%country%", + result.getCountryName()) + .replace("%city%", + result.getCity()))); + + if(deniedOnLogin) return; + + //In case the user wants to run their own commands instead of using the + // built in kicking + + if(AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) { + switch (checkResult.resultType()) { + case DENIED_PROXY -> 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(); + case DENIED_COUNTRY -> VelocityPlugin.INSTANCE.getServer().getScheduler() + .buildTask(VelocityPlugin.INSTANCE, () -> + event.getPlayer().disconnect(LegacyComponentSerializer.builder() + .character('&') + .build().deserialize(AntiVPN.getInstance().getVpnConfig() + .countryVanillaKickReason() + .replace("%player%", event.getPlayer().getUsername()) + .replace("%country%", result.getCountryName()) + .replace("%code%", result.getCountryCode())))) + .delay(1, TimeUnit.SECONDS).schedule(); + } + } + + if(!AntiVPN.getInstance().getVpnConfig().runCommands()) return; + + switch (checkResult.resultType()) { + case DENIED_PROXY -> { + for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { + VelocityPlugin.INSTANCE.getServer().getCommandManager() + .executeAsync(VelocityPlugin.INSTANCE.getServer() + .getConsoleCommandSource(), + StringUtil.translateAlternateColorCodes('&', + StringUtil.varReplace( + command, + AntiVPN.getInstance().getPlayerExecutor() + .getPlayer(event.getPlayer().getUniqueId()) + .orElse(new OfflinePlayer( + event.getPlayer().getUniqueId(), + event.getPlayer().getUsername(), + event.getPlayer().getRemoteAddress().getAddress()) + ), + result))); + } + } + case DENIED_COUNTRY -> { + for (String cmd : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { + final String formattedCommand = StringUtil + .translateAlternateColorCodes('&', + StringUtil.varReplace( + cmd, + AntiVPN.getInstance().getPlayerExecutor() + .getPlayer(event.getPlayer().getUniqueId()) + .orElse(new OfflinePlayer( + event.getPlayer().getUniqueId(), + event.getPlayer().getUsername(), + event.getPlayer().getRemoteAddress().getAddress()) + ), + result)); + // Running the command from console + runCommand(formattedCommand); + } + } + } + @Override public void shutdown() { if (cacheResetTask != null) { @@ -197,7 +184,22 @@ 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); } + + @Override + public void runCommand(String command) { + VelocityPlugin.INSTANCE.getServer().getCommandManager() + .executeAsync(VelocityPlugin.INSTANCE.getServer() + .getConsoleCommandSource(), + StringUtil.translateAlternateColorCodes('&', + command)); + } + + @Override + public void disablePlugin() { + VelocityPlugin.INSTANCE.getServer().getEventManager().unregisterListener(VelocityPlugin.INSTANCE, this); + VelocityPlugin.INSTANCE.getServer().getCommandManager().unregister("antivpn"); + } } 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 afecfa9..da0de33 100644 --- a/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java +++ b/Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java @@ -3,15 +3,22 @@ package dev.brighten.antivpn.velocity; import com.google.inject.Inject; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; 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; import java.nio.file.Path; import java.util.logging.Logger; @@ -24,6 +31,10 @@ public class VelocityPlugin { private final Metrics.Factory metricsFactory; private final Path configDir; + @Nullable + private Metrics metrics; + + public static VelocityPlugin INSTANCE; @Inject @@ -45,7 +56,9 @@ public class VelocityPlugin { if(AntiVPN.getInstance().getVpnConfig().metrics()) { logger.info("Starting metrics..."); - Metrics metrics = metricsFactory.make(this, 12791); + metrics = metricsFactory.make(this, 12791); + + metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); } logger.info("Registering commands..."); @@ -54,4 +67,35 @@ public class VelocityPlugin { .aliases(command.aliases()).build(), new VelocityCommand(command)); } } + + @Subscribe + 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/Velocity/src/main/java/dev/brighten/antivpn/velocity/util/StringUtils.java b/Velocity/src/main/java/dev/brighten/antivpn/velocity/util/StringUtils.java deleted file mode 100644 index a4f02fe..0000000 --- a/Velocity/src/main/java/dev/brighten/antivpn/velocity/util/StringUtils.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.brighten.antivpn.velocity.util; - -public class StringUtils { - - public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { - char[] b = textToTranslate.toCharArray(); - - for(int i = 0; i < b.length - 1; ++i) { - if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) { - b[i] = 167; - b[i + 1] = Character.toLowerCase(b[i + 1]); - } - } - - return new String(b); - } -} diff --git a/pom.xml b/pom.xml index 67ea6c4..267fed9 100644 --- a/pom.xml +++ b/pom.xml @@ -13,9 +13,9 @@ Common Bungee Bukkit - Assembly Velocity Sponge + Universal @@ -53,10 +53,6 @@ - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - funkemunky-releases https://nexus.funkemunky.cc/content/repositories/releases/