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

This commit is contained in:
2025-12-30 06:27:43 -08:00
12 changed files with 354 additions and 220 deletions
@@ -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().isKickPlayers()) {
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()
.getKickMessage()
.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()
.getCountryVanillaKickReason()
.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...");
}
}
@@ -0,0 +1,31 @@
package dev.brighten.antivpn.sponge;
import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.sponge.util.StringUtil;
import net.kyori.adventure.text.Component;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
public class SpongePlayer extends APIPlayer {
private final ServerPlayer player;
public SpongePlayer(ServerPlayer player) {
super(player.uniqueId(), player.name(), player.connection().address().getAddress());
this.player = player;
}
@Override
public void sendMessage(String message) {
player.sendMessage(Component.text(StringUtil.translateColorCodes('&', message)));
}
@Override
public void kickPlayer(String reason) {
player.kick(Component.text(StringUtil.translateColorCodes('&', reason)));
}
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
}
@@ -0,0 +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 serverPlayer.map(SpongePlayer::new);
}
@Override
public Optional<APIPlayer> getPlayer(UUID uuid) {
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() {
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();
}
}
@@ -0,0 +1,64 @@
package dev.brighten.antivpn.sponge;
import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.loader.LoaderBootstrap;
import dev.brighten.antivpn.sponge.command.SpongeCommand;
import lombok.Getter;
import org.apache.logging.log4j.Logger;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.Command;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.lifecycle.*;
import org.spongepowered.plugin.PluginContainer;
import java.io.File;
import java.util.Map;
@Getter
public class SpongePlugin implements LoaderBootstrap {
//Plugin init
@Getter
private static SpongePlugin instance;
private Logger logger;
private PluginContainer container;
private final Map<Class<?>, Object> objects;
private File dataFolder;
public SpongePlugin(Map<Class<?>, Object> objects) {
this.objects = objects;
}
@Listener
public void onRegisterRawCommands(final RegisterCommandEvent<Command.Raw> event){
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());
}
}
@Override
public void onLoad(File dataFolder) {
this.dataFolder = dataFolder;
container = (PluginContainer) objects.get(PluginContainer.class);
logger = (Logger) objects.get(Logger.class);
Sponge.eventManager().registerListeners(this.container, this);
}
@Override
public void onEnable() {
instance = this;
SpongeListener spongeListener = new SpongeListener();
AntiVPN.start(spongeListener, new SpongePlayerExecutor(), dataFolder);
}
@Override
public void onDisable() {
AntiVPN.getInstance().getExecutor().disablePlugin();
}
}
@@ -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 (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 (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,17 @@
package dev.brighten.antivpn.sponge.util;
public class StringUtil {
public static String translateColorCodes(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);
}
}