Merge branch 'master' into 57-feature-allow-subnets-to-be-whitelisted

# Conflicts:
#	Bukkit/pom.xml
#	Bungee/pom.xml
#	Bungee/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java
#	Common/pom.xml
#	Common/src/main/java/dev/brighten/antivpn/AntiVPN.java
#	Common/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java
#	Common/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java
#	Common/src/main/java/dev/brighten/antivpn/database/mongo/MongoVPN.java
#	Common/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java
#	Common/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java
#	Common/src/main/java/dev/brighten/antivpn/database/sql/utils/Query.java
#	Common/src/main/java/dev/brighten/antivpn/utils/StringUtil.java
#	Sponge/pom.xml
#	Universal/pom.xml
#	Velocity/pom.xml
#	Velocity/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java
#	pom.xml
This commit is contained in:
2025-05-28 21:19:55 -04:00
48 changed files with 2648 additions and 658 deletions
+22 -6
View File
@@ -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
+8 -5
View File
@@ -26,7 +26,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
@@ -45,10 +45,6 @@
<pattern>org.yaml.snakeyaml</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.yaml.snakeyaml</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.google.common</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
@@ -68,6 +64,13 @@
<maven.compiler.target>17</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
@@ -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<UUID, VPNResponse> 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());
@@ -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<org.bukkit.command.Command> 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<String, org.bukkit.command.Command> knownCommands =
(Map<String, org.bukkit.command.Command>) field.get(commandMap);
knownCommands.values().removeAll(registeredCommands);
registeredCommands.clear();
if(field.get(commandMap) instanceof Map<?, ?> knownCommands) {
Map<String, org.bukkit.command.Command> casted = (Map<String, org.bukkit.command.Command>) 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";
}
}
}
+5 -8
View File
@@ -26,7 +26,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<version>3.1.0</version>
<configuration>
<relocations>
<relocation>
@@ -38,10 +38,6 @@
<pattern>org.yaml.snakeyaml</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.yaml.snakeyaml</shadedPattern>
</relocation>
<relocation>
<pattern>com.google</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.google</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
@@ -75,9 +71,10 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.github.bungee</groupId>
<artifactId>BungeeCord-1.8</artifactId>
<version>1.8</version>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.21-R0.2</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
@@ -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<UUID, VPNResponse> 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
@@ -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<APIPlayer> 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<APIPlayer> 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<APIPlayer> 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());
}
@@ -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";
}
}
}
+72 -6
View File
@@ -38,7 +38,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
@@ -51,14 +51,60 @@
<relocation>
<pattern>org.yaml.snakeyaml</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.yaml.snakeyaml</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.google.common</shadedPattern>
<pattern>com.github.benmanes.caffeine</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.github.benmanes.caffeine</shadedPattern>
</relocation>
<relocation>
<pattern>org.h2</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.h2</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>org.bson</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.bson</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>com.mongodb</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.mongodb</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>com.mysql.cj</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.mysql.cj</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>com.mysql.jdbc</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.mysql.jdbc</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
</relocations>
</configuration>
@@ -87,6 +133,24 @@
<artifactId>sqlite-jdbc</artifactId>
<version>3.48.0.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.220</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>9.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
@@ -94,10 +158,12 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
@@ -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);
}
}
@@ -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<String, CheckResult> 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<CheckResult> 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);
}
}
@@ -0,0 +1,6 @@
package dev.brighten.antivpn.api;
import dev.brighten.antivpn.web.objects.VPNResponse;
public record CheckResult(VPNResponse response, ResultType resultType) {
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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<String> whitelistedIps = Collections.synchronizedSet(new HashSet<>());
private final Cache<String, VPNResponse> responseCache = CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.MINUTES)
.maximumSize(4000)
.build();
@Getter
private final List<Tuple<CheckResult, UUID>> 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<Tuple<CheckResult, UUID>> i = toKick.iterator();
while(i.hasNext()) {
var toCheck = i.next();
Optional<APIPlayer> 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<VPNResponse> result) {
threadExecutor.execute(() -> {
if(cachedResults) {
public CompletableFuture<VPNResponse> checkIp(String ip) {
return CompletableFuture.supplyAsync(() -> {
Optional<VPNResponse> 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<VPNResponse> 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();
}
@@ -55,28 +55,31 @@ public class LookupCommand extends Command {
Optional<APIPlayer> 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() + "...";
@@ -0,0 +1,371 @@
/*
* This file is part of helper, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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<URLClassLoaderAccess> 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<String, String> 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<String, String> relocations) {
load(new Dependency(groupId, artifactId, version, repoUrl), relocations);
}
public static void load(Dependency d, Map<String, String> 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<String, String> relocations)
throws IOException {
// Track service files to avoid duplicates
Map<String, StringBuilder> serviceFiles = new HashMap<>();
try (JarFile jar = new JarFile(sourceJar);
JarOutputStream jos = new JarOutputStream(Files.newOutputStream(targetJar.toPath()))) {
Enumeration<JarEntry> 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<String, StringBuilder> 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<String, StringBuilder> serviceFiles,
Map<String, String> 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<String, String> 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<String, String> 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<String, String> relocations) {
Map<String, String> slashMappings = new HashMap<>();
for (Map.Entry<String, String> 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<String, String> 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<String, String> relocations) {
// Convert path to package format (replacing / with .)
String packagePath = path.substring(0, path.length() - 6).replace('/', '.');
// Apply relocations
for (Map.Entry<String, String> 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
}
}
@@ -0,0 +1,40 @@
/*
* This file is part of helper, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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 {};
}
@@ -0,0 +1,69 @@
/*
* This file is part of helper, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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
}
@@ -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();
}
@@ -0,0 +1,45 @@
/*
* This file is part of helper, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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();
}
@@ -0,0 +1,175 @@
/*
* This file is part of helper, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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 (<a href="https://github.com/slimjar/slimjar">...</a>)
*/
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<URL> unopenedURLs;
private final Collection<URL> pathURLs;
@SuppressWarnings("unchecked")
Unsafe(URLClassLoader classLoader) {
super(classLoader);
Collection<URL> unopenedURLs;
Collection<URL> pathURLs;
try {
Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp");
unopenedURLs = (Collection<URL>) fetchField(ucp.getClass(), ucp, "unopenedUrls");
pathURLs = (Collection<URL>) 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();
}
}
}
@@ -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 {}
@@ -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();
@@ -0,0 +1,36 @@
/*
* This file is part of helper, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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 {
}
@@ -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.
*
* <p>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.
*
* <p>An example use case for this method is in implementing an {@code Iterator<T>} 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.
*
* <p>Why <i>not</i> 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> T uncheckedCastNullableTToT(T t) {
return t;
}
private NullnessCasts() {}
}
@@ -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> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
} else {
return reference;
}
}
public static <T> T checkNotNull(T reference, Object errorMessage) {
if (reference == null) {
throw new NullPointerException(String.valueOf(errorMessage));
} else {
return reference;
}
}
public static <T> T checkNotNull(T reference, String errorMessageTemplate, Object... errorMessageArgs) {
if (reference == null) {
throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
} else {
return reference;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, char p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, int p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, long p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> T checkNotNull(T obj, String errorMessageTemplate, Object p1) {
if (obj == null) {
throw new NullPointerException(format(errorMessageTemplate, p1));
} else {
return obj;
}
}
public static <T> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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();
}
}
@@ -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);
}
}
@@ -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<T> extends java.util.function.Supplier<T> {
T get();
}
@@ -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.
*
* <p>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: <a
* href="http://en.wikipedia.org/wiki/Memoization">memoization</a>
*
* <p>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.
*
* <p>When the underlying delegate throws an exception then this memoizing supplier will keep
* delegating calls until it returns valid data.
*
* <p>If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is
* returned directly.
*/
public static <T> Supplier<T> memoize(Supplier<T> delegate) {
if (delegate instanceof NonSerializableMemoizingSupplier
|| delegate instanceof MemoizingSupplier) {
return delegate;
}
return delegate instanceof Serializable
? new MemoizingSupplier<>(delegate)
: new NonSerializableMemoizingSupplier<>(delegate);
}
static class MemoizingSupplier<T> implements Supplier<T>, Serializable {
final Supplier<T> 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<T> 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 ? "<supplier that returned " + value + ">" : delegate)
+ ")";
}
private static final long serialVersionUID = 0;
}
static class NonSerializableMemoizingSupplier<T> implements Supplier<T> {
volatile Supplier<T> delegate;
volatile boolean initialized;
// "value" does not need to be volatile; visibility piggy-backs
// on volatile read of "initialized".
T value;
NonSerializableMemoizingSupplier(Supplier<T> 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<T> delegate = this.delegate;
return "Suppliers.memoize("
+ (delegate == null ? "<supplier that returned " + value + ">" : delegate)
+ ")";
}
}
}
@@ -0,0 +1,5 @@
package dev.brighten.antivpn.utils;
public record Tuple<F, S>(F first, S second) {
}
@@ -12,7 +12,7 @@ public class FunkemunkyAPI {
/**
*
* Queries https://funkemunky.cc/vpn API and returns information on the IP
* Queries <a href="https://funkemunky.cc/vpn">...</a> 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 <a href="https://funkemunky.cc/vpn/queryCheck">...</a> and returns information based on the
* provided licence input.
*
* @param license String
@@ -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 <a href="https://funkemunky.cc/vpn/queryCheck">...</a> 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 <a href="https://funkemunky.cc/vpn/queryCheck">...</a> into {@link QueryResponse} for project use.
*
* @param object JSONObject
* @return QueryResponse
+2 -1
View File
@@ -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
+143 -5
View File
@@ -13,8 +13,12 @@
<repositories>
<repository>
<id>sponge</id>
<url>https://repo.spongepowered.org/repository/maven-public/</url>
<id>spongepowered-repo</id>
<url>https://repo.spongepowered.org/maven/</url>
</repository>
<repository>
<id>funkemunky-releases</id>
<url>https://nexus.funkemunky.cc/content/repositories/releases/</url>
</repository>
</repositories>
@@ -22,20 +26,154 @@
<dependency>
<groupId>org.spongepowered</groupId>
<artifactId>spongeapi</artifactId>
<version>8.1.0</version>
<version>11.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>Common</artifactId>
<version>2.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.12.14</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.1.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.220</version>
<scope>compile</scope>
<version>2.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>21</source>
<target>21</target>
<compilerArgument>-XDignore.symbol.file</compilerArgument>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>com/google/**</exclude>
<exclude>org/objectweb/**</exclude>
<exclude>org/checkerframework/**</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>org.yaml.snakeyaml</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.yaml.snakeyaml</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>com.github.benmanes.caffeine</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.github.benmanes.caffeine</shadedPattern>
</relocation>
<relocation>
<pattern>org.h2</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.h2</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>org.bson</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.bson</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>com.mongodb</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.mongodb</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>com.mysql.cj</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.mysql.cj</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
<relocation>
<pattern>com.mysql.jdbc</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.mysql.jdbc</shadedPattern>
<excludes>
<!-- Exclude annotation values from relocation -->
<exclude>dev.brighten.antivpn.depends.Relocate</exclude>
<exclude>dev.brighten.antivpn.depends.MavenLibraries</exclude>
</excludes>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
@@ -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<APIPlayer> 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...");
}
}
@@ -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
@@ -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<UUID, SpongePlayer> playerCache = Caffeine.newBuilder().maximumSize(10000)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build();
@Override
public Optional<APIPlayer> getPlayer(String name) {
Optional<ServerPlayer> serverPlayer = Sponge.server().player(name);
return Optional.empty();
return serverPlayer.map(SpongePlayer::new);
}
@Override
public Optional<APIPlayer> getPlayer(UUID uuid) {
return Optional.empty();
SpongePlayer cachedPlayer = playerCache.getIfPresent(uuid);
if(cachedPlayer != null) {
return Optional.of(cachedPlayer);
}
Optional<ServerPlayer> serverPlayer = Sponge.server().player(uuid);
Optional<APIPlayer> 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<APIPlayer> 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();
}
}
@@ -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<Server> 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<Server> event) {
AntiVPN.getInstance().getExecutor().disablePlugin();
}
@Listener
public void onRegisterRawCommands(final RegisterCommandEvent<Command.Raw> 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());
}
}
}
@@ -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<CommandCompletion> 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<Component> shortDescription(CommandCause cause) {
return command.description() != null ? Optional.of(Component.text(command.description())) : Optional.empty();
}
@Override
public Optional<Component> extendedDescription(CommandCause cause) {
return Optional.empty();
}
@Override
public Optional<Component> 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();
}
}
@@ -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<APIPlayer> 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;
}
}
@@ -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
}
]
}
]
}
+43 -33
View File
@@ -2,26 +2,53 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>AntiVPN</artifactId>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>AntiVPN</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Assembly</artifactId>
<artifactId>Universal</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>Common</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>Bukkit</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>Bungee</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>Velocity</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
@@ -29,15 +56,25 @@
<goal>shade</goal>
</goals>
<configuration>
<finalName>AntiVPN-${project.version}-universal</finalName>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<relocations>
<relocation>
<pattern>org.yaml.snakeyaml</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.yaml.snakeyaml</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.com.google.common</shadedPattern>
<pattern>org.bstats</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.bstats</shadedPattern>
</relocation>
<relocation>
<pattern>org.objectweb</pattern>
<shadedPattern>dev.brighten.antivpn.shaded.org.objectweb</shadedPattern>
</relocation>
<!-- Add other relocations from Common/pom.xml -->
</relocations>
</configuration>
</execution>
@@ -46,31 +83,4 @@
</plugins>
</build>
<dependencies>
<dependency>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>Bungee</artifactId>
<version>${version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>Velocity</artifactId>
<version>${version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>Common</artifactId>
<version>${version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>dev.brighten.antivpn</groupId>
<artifactId>Bukkit</artifactId>
<version>${version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
+1 -1
View File
@@ -59,7 +59,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<version>3.6.0</version>
<configuration>
<relocations>
<relocation>
@@ -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<UUID, VPNResponse> 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");
}
}
@@ -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";
}
}
}
@@ -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);
}
}
+1 -5
View File
@@ -13,9 +13,9 @@
<module>Common</module>
<module>Bungee</module>
<module>Bukkit</module>
<module>Assembly</module>
<module>Velocity</module>
<module>Sponge</module>
<module>Universal</module>
</modules>
<properties>
@@ -53,10 +53,6 @@
</build>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>funkemunky-releases</id>
<url>https://nexus.funkemunky.cc/content/repositories/releases/</url>