package net.minecraft.server.jsonrpc.methods; import com.google.common.net.InetAddresses; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.time.Instant; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import net.minecraft.network.chat.Component; import net.minecraft.server.jsonrpc.api.PlayerDto; import net.minecraft.server.jsonrpc.internalapi.MinecraftApi; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.IpBanListEntry; import net.minecraft.util.ExtraCodecs; import org.jspecify.annotations.Nullable; public class IpBanlistService { private static final String BAN_SOURCE = "Management server"; public static List get(final MinecraftApi minecraftApi) { return minecraftApi.banListService().getIpBanEntries().stream().map(IpBanlistService.IpBan::from).map(IpBanlistService.IpBanDto::from).toList(); } public static List add( final MinecraftApi minecraftApi, final List bans, final ClientInfo clientInfo ) { bans.stream() .map(ban -> banIp(minecraftApi, ban, clientInfo)) .flatMap(Collection::stream) .forEach(player -> player.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned"))); return get(minecraftApi); } private static List banIp(final MinecraftApi minecraftApi, final IpBanlistService.IncomingIpBanDto ban, final ClientInfo clientInfo) { IpBanlistService.IpBan ipBan = ban.toIpBan(); if (ipBan != null) { return banIp(minecraftApi, ipBan, clientInfo); } else { if (ban.player().isPresent()) { Optional player = minecraftApi.playerListService().getPlayer(((PlayerDto)ban.player().get()).id(), ((PlayerDto)ban.player().get()).name()); if (player.isPresent()) { return banIp(minecraftApi, ban.toIpBan((ServerPlayer)player.get()), clientInfo); } } return List.of(); } } private static List banIp(final MinecraftApi minecraftApi, final IpBanlistService.IpBan ban, final ClientInfo clientInfo) { minecraftApi.banListService().addIpBan(ban.toIpBanEntry(), clientInfo); return minecraftApi.playerListService().getPlayersWithAddress(ban.ip()); } public static List clear(final MinecraftApi minecraftApi, final ClientInfo clientInfo) { minecraftApi.banListService().clearIpBans(clientInfo); return get(minecraftApi); } public static List remove(final MinecraftApi minecraftApi, final List ban, final ClientInfo clientInfo) { ban.forEach(ip -> minecraftApi.banListService().removeIpBan(ip, clientInfo)); return get(minecraftApi); } public static List set(final MinecraftApi minecraftApi, final List ips, final ClientInfo clientInfo) { Set finalBanlist = (Set)ips.stream() .filter(ban -> InetAddresses.isInetAddress(ban.ip())) .map(IpBanlistService.IpBanDto::toIpBan) .collect(Collectors.toSet()); Set currentBans = (Set)minecraftApi.banListService() .getIpBanEntries() .stream() .map(IpBanlistService.IpBan::from) .collect(Collectors.toSet()); currentBans.stream().filter(ban -> !finalBanlist.contains(ban)).forEach(ban -> minecraftApi.banListService().removeIpBan(ban.ip(), clientInfo)); finalBanlist.stream().filter(ban -> !currentBans.contains(ban)).forEach(ban -> minecraftApi.banListService().addIpBan(ban.toIpBanEntry(), clientInfo)); finalBanlist.stream() .filter(ban -> !currentBans.contains(ban)) .flatMap(ban -> minecraftApi.playerListService().getPlayersWithAddress(ban.ip()).stream()) .forEach(player -> player.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned"))); return get(minecraftApi); } public record IncomingIpBanDto(Optional player, Optional ip, Optional reason, Optional source, Optional expires) { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( i -> i.group( PlayerDto.CODEC.codec().optionalFieldOf("player").forGetter(IpBanlistService.IncomingIpBanDto::player), Codec.STRING.optionalFieldOf("ip").forGetter(IpBanlistService.IncomingIpBanDto::ip), Codec.STRING.optionalFieldOf("reason").forGetter(IpBanlistService.IncomingIpBanDto::reason), Codec.STRING.optionalFieldOf("source").forGetter(IpBanlistService.IncomingIpBanDto::source), ExtraCodecs.INSTANT_ISO8601.optionalFieldOf("expires").forGetter(IpBanlistService.IncomingIpBanDto::expires) ) .apply(i, IpBanlistService.IncomingIpBanDto::new) ); private IpBanlistService.IpBan toIpBan(final ServerPlayer player) { return new IpBanlistService.IpBan( player.getIpAddress(), (String)this.reason().orElse(null), (String)this.source().orElse("Management server"), this.expires() ); } @Nullable private IpBanlistService.IpBan toIpBan() { return !this.ip().isEmpty() && InetAddresses.isInetAddress((String)this.ip().get()) ? new IpBanlistService.IpBan((String)this.ip().get(), (String)this.reason().orElse(null), (String)this.source().orElse("Management server"), this.expires()) : null; } } private record IpBan(String ip, @Nullable String reason, String source, Optional expires) { private static IpBanlistService.IpBan from(final IpBanListEntry entry) { return new IpBanlistService.IpBan( (String)Objects.requireNonNull(entry.getUser()), entry.getReason(), entry.getSource(), Optional.ofNullable(entry.getExpires()).map(Date::toInstant) ); } private IpBanListEntry toIpBanEntry() { return new IpBanListEntry(this.ip(), null, this.source(), (Date)this.expires().map(Date::from).orElse(null), this.reason()); } } public record IpBanDto(String ip, Optional reason, Optional source, Optional expires) { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( i -> i.group( Codec.STRING.fieldOf("ip").forGetter(IpBanlistService.IpBanDto::ip), Codec.STRING.optionalFieldOf("reason").forGetter(IpBanlistService.IpBanDto::reason), Codec.STRING.optionalFieldOf("source").forGetter(IpBanlistService.IpBanDto::source), ExtraCodecs.INSTANT_ISO8601.optionalFieldOf("expires").forGetter(IpBanlistService.IpBanDto::expires) ) .apply(i, IpBanlistService.IpBanDto::new) ); private static IpBanlistService.IpBanDto from(final IpBanlistService.IpBan ban) { return new IpBanlistService.IpBanDto(ban.ip(), Optional.ofNullable(ban.reason()), Optional.of(ban.source()), ban.expires()); } public static IpBanlistService.IpBanDto from(final IpBanListEntry ban) { return from(IpBanlistService.IpBan.from(ban)); } private IpBanlistService.IpBan toIpBan() { return new IpBanlistService.IpBan(this.ip(), (String)this.reason().orElse(null), (String)this.source().orElse("Management server"), this.expires()); } } }