Compare commits

...
6 Commits
Author SHA1 Message Date
Jones 75abebde7c Release 2.0.12 (#136) 2023-12-25 14:24:08 +01:00
Jones 28b1615b78 Implement maximum age to verified players in database (#134) 2023-12-21 22:27:26 +01:00
Jones 600275669b Bump gradle from 8.4 to 8.5 (#133) 2023-12-20 14:41:21 +01:00
Jones 0c3761be11 Fix event priorities for some event listeners (#132)
* fix(bungee): use highest event priority for audience listener

* fix(bungee): use lowest event priority for fallback listener

* fix(bukkit): use highest event priority for audience listener
2023-12-19 16:36:13 +01:00
Jones b7d8ec74d0 Bump adventure from 4.14.0 to 4.15.0 (#131) 2023-12-19 16:26:24 +01:00
dependabot[bot] 96efa574e0 Bump netty-all from 4.1.101.Final to 4.1.104.Final (#130)
Bumps [io.netty:netty-all](https://github.com/netty/netty) from 4.1.101.Final to 4.1.104.Final.
- [Commits](https://github.com/netty/netty/compare/netty-4.1.101.Final...netty-4.1.104.Final)

---
updated-dependencies:
- dependency-name: io.netty:netty-all
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 15:49:58 +01:00
14 changed files with 195 additions and 148 deletions

No files matched your search

+3 -2
View File
@@ -35,7 +35,7 @@ allprojects {
testCompileOnly("org.projectlombok:lombok:1.18.30")
testAnnotationProcessor("org.projectlombok:lombok:1.18.30")
val adventureVersion = "4.14.0"
val adventureVersion = "4.15.0"
// adventure
implementation("net.kyori:adventure-text-minimessage:$adventureVersion")
@@ -46,7 +46,7 @@ allprojects {
implementation("com.j256.ormlite:ormlite-jdbc:6.1") // ORM
implementation("xyz.jonesdev:cappuccino:0.1.6-SNAPSHOT") // expiring cache
compileOnly("io.netty:netty-all:4.1.101.Final") // netty
compileOnly("io.netty:netty-all:4.1.104.Final") // netty
}
}
@@ -85,6 +85,7 @@ tasks {
relocate("com.j256.ormlite", "xyz.jonesdev.sonar.libs.ormlite")
relocate("xyz.jonesdev.cappuccino", "xyz.jonesdev.sonar.libs.cappuccino")
relocate("net.kyori.examination", "xyz.jonesdev.sonar.libs.examination")
relocate("net.kyori.option", "xyz.jonesdev.sonar.libs.option")
// We have to be careful here, so we don't accidentally break adventure
relocate("net.kyori.adventure", "xyz.jonesdev.sonar.libs.adventure") {
exclude("net.kyori.adventure.text.**")
+1 -1
View File
@@ -1,3 +1,3 @@
group=xyz.jonesdev.sonar
version=2.0.11
version=2.0.12
description=Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -243,6 +243,7 @@ public final class SonarConfiguration {
private String name;
private String username;
private String password;
private int maximumAge;
}
@Getter
@@ -378,6 +379,10 @@ public final class SonarConfiguration {
"Password for authenticating with the SQL database");
database.password = generalConfig.getString("database.password", "");
generalConfig.getYaml().setComment("database.maximum-age",
"How many days should Sonar keep verified players in the database?");
database.maximumAge = clamp(generalConfig.getInt("database.maximum-age", 5), 1, 365);
// Queue
generalConfig.getYaml().setComment("queue",
"Every new login request will be queued to avoid spam join attacks"
@@ -25,6 +25,7 @@ import com.j256.ormlite.table.TableUtils;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.dependencies.DependencyLoader;
@@ -32,6 +33,9 @@ import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
import java.net.InetAddress;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
@@ -72,6 +76,9 @@ public final class VerifiedPlayerController {
dao = DaoManager.createDao(connectionSource, VerifiedPlayer.class);
queryBuilder = dao.queryBuilder();
// Make sure to clear all outdated entries first
clearOld(Sonar.get().getConfig().getDatabase().getMaximumAge());
// Add all entries from the database to the cache
dao.queryForAll().forEach(this::_add);
} catch (Throwable throwable) {
Sonar.get().getLogger().error("Error setting up database connection: {}", throwable);
@@ -197,6 +204,44 @@ public final class VerifiedPlayerController {
}
}
/**
* Clear all old entries using the given timestamp.
*/
public void clearOld(final @Range(from = 1, to = 365) int maximumAge) {
// Don't try to update the column if the database type is NONE
if (cachedDatabaseType == SonarConfiguration.Database.Type.NONE) {
return;
}
DB_UPDATE_SERVICE.execute(() -> {
// We cannot throw a NullPointerException within the executor service
// because we want to handle the error instead of simply throwing an exception
if (connectionSource == null) {
return;
}
try {
final long timestamp = Instant.now()
.minus(maximumAge, ChronoUnit.DAYS)
.getEpochSecond() * 1000L; // convert to ms
final List<VerifiedPlayer> oldEntries = queryBuilder.where()
.lt("timestamp", new Timestamp(timestamp))
.query();
if (oldEntries != null) {
for (final VerifiedPlayer player : oldEntries) {
dao.delete(player);
}
Sonar.get().getLogger().info("Removed {} database entries older than {} days.",
oldEntries.size(), maximumAge);
}
} catch (SQLException exception) {
Sonar.get().getLogger().error("Error trying to clear old entries: {}", exception);
}
});
}
/**
* @param inetAddress IP address
* @param uuid UUID associated to the IP
@@ -29,13 +29,13 @@ import xyz.jonesdev.sonar.bukkit.SonarBukkit;
public final class AudienceListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler(priority = EventPriority.HIGHEST)
public void handle(final @NotNull PlayerJoinEvent event) {
final Audience audience = SonarBukkit.INSTANCE.getBukkitAudiences().player(event.getPlayer());
Sonar.get().getVerboseHandler().getAudiences().put(event.getPlayer().getName(), audience);
}
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler(priority = EventPriority.HIGHEST)
public void handle(final @NotNull PlayerQuitEvent event) {
Sonar.get().getVerboseHandler().getAudiences().remove(event.getPlayer().getName());
}
@@ -29,13 +29,13 @@ import xyz.jonesdev.sonar.bungee.SonarBungee;
public final class AudienceListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler(priority = EventPriority.HIGHEST)
public void handle(final @NotNull PostLoginEvent event) {
final Audience audience = SonarBungee.INSTANCE.getBungeeAudiences().player(event.getPlayer());
Sonar.get().getVerboseHandler().getAudiences().put(event.getPlayer().getName(), audience);
}
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler(priority = EventPriority.HIGHEST)
public void handle(final @NotNull PlayerDisconnectEvent event) {
Sonar.get().getVerboseHandler().getAudiences().remove(event.getPlayer().getName());
}
@@ -32,7 +32,6 @@ public final class BungeeSonarCommand extends Command implements TabExecutor, So
}
@Override
@SuppressWarnings({"redundantSuppression"})
public void execute(final @NotNull CommandSender sender, final String[] args) {
// Create our own invocation source wrapper to handle messages properly
final InvocationSource invocationSource = new BungeeInvocationSource(sender);
@@ -22,6 +22,7 @@ import net.kyori.adventure.text.Component;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import org.jetbrains.annotations.NotNull;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.bungee.SonarBungee;
@@ -32,8 +33,8 @@ import java.util.Objects;
@RequiredArgsConstructor
public final class FallbackListener implements Listener {
@EventHandler
@SuppressWarnings("deprecation")
@EventHandler(priority = EventPriority.LOWEST)
public void handle(final @NotNull LoginEvent event) {
final InetAddress inetAddress = event.getConnection().getAddress().getAddress();
@@ -34,7 +34,7 @@ import java.time.Instant;
import java.util.UUID;
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
import static xyz.jonesdev.sonar.common.utility.nbt.NBTMessageUtil.fromJson;
import static xyz.jonesdev.sonar.common.utility.component.ComponentSerializer.serialize;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.*;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.readVarInt;
import static xyz.jonesdev.sonar.common.utility.protocol.VarIntUtil.writeVarInt;
@@ -77,7 +77,7 @@ public final class Chat implements FallbackPacket {
public Chat(final Component component, final String message, final byte type) {
this(component, message, type,
false, false, null, null,
0L, false, null, fromJson(new JsonParser().parse(message)));
0L, false, null, serialize(new JsonParser().parse(message)));
}
@Override
@@ -31,7 +31,7 @@ import org.jetbrains.annotations.Nullable;
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
import static xyz.jonesdev.sonar.common.utility.nbt.NBTMessageUtil.fromJson;
import static xyz.jonesdev.sonar.common.utility.component.ComponentSerializer.serialize;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeNamelessCompoundTag;
import static xyz.jonesdev.sonar.common.utility.protocol.ProtocolUtil.writeString;
@@ -59,7 +59,7 @@ public final class Disconnect implements FallbackPacket {
public static @NotNull Disconnect create(final Component component) {
final String serialized = JSONComponentSerializer.json().serialize(component);
final BinaryTag binaryTag = fromJson(new JsonParser().parse(serialized));
final BinaryTag binaryTag = serialize(new JsonParser().parse(serialized));
return new Disconnect(serialized, binaryTag);
}
}
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2023 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.utility.component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.internal.LazilyParsedNumber;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.nbt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
// Taken from
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java
@UtilityClass
public class ComponentSerializer {
public static BinaryTag serialize(final JsonElement json) {
if (json instanceof JsonPrimitive) {
final JsonPrimitive jsonPrimitive = (JsonPrimitive) json;
if (jsonPrimitive.isNumber()) {
final Number number = json.getAsNumber();
if (number instanceof Byte) {
return ByteBinaryTag.byteBinaryTag((Byte) number);
} else if (number instanceof Short) {
return ShortBinaryTag.shortBinaryTag((Short) number);
} else if (number instanceof Integer) {
return IntBinaryTag.intBinaryTag((Integer) number);
} else if (number instanceof Long) {
return LongBinaryTag.longBinaryTag((Long) number);
} else if (number instanceof Float) {
return FloatBinaryTag.floatBinaryTag((Float) number);
} else if (number instanceof Double) {
return DoubleBinaryTag.doubleBinaryTag((Double) number);
} else if (number instanceof LazilyParsedNumber) {
return IntBinaryTag.intBinaryTag(number.intValue());
}
} else if (jsonPrimitive.isString()) {
return StringBinaryTag.stringBinaryTag(jsonPrimitive.getAsString());
} else if (jsonPrimitive.isBoolean()) {
return ByteBinaryTag.byteBinaryTag((byte) (jsonPrimitive.getAsBoolean() ? 1 : 0));
} else {
throw new IllegalArgumentException("Unknown JSON primitive: " + jsonPrimitive);
}
} else if (json instanceof JsonObject) {
final CompoundBinaryTag.Builder compound = CompoundBinaryTag.builder();
for (Map.Entry<String, JsonElement> property : ((JsonObject) json).entrySet()) {
compound.put(property.getKey(), serialize(property.getValue()));
}
return compound.build();
} else if (json instanceof JsonArray) {
final JsonArray jsonArray = (JsonArray) json;
if (jsonArray.size() == 0) {
return ListBinaryTag.empty();
}
final List<BinaryTag> tagItems = new ArrayList<>(jsonArray.size());
BinaryTagType<? extends BinaryTag> listType = null;
for (final JsonElement jsonEl : jsonArray) {
final BinaryTag tag = serialize(jsonEl);
tagItems.add(tag);
if (listType == null) {
listType = tag.type();
} else if (listType != tag.type()) {
listType = BinaryTagTypes.COMPOUND;
}
}
switch (listType.id()) {
case 1://BinaryTagTypes.BYTE:
final byte[] bytes = new byte[jsonArray.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (Byte) jsonArray.get(i).getAsNumber();
}
return ByteArrayBinaryTag.byteArrayBinaryTag(bytes);
case 3://BinaryTagTypes.INT:
final int[] ints = new int[jsonArray.size()];
for (int i = 0; i < ints.length; i++) {
ints[i] = (Integer) jsonArray.get(i).getAsNumber();
}
return IntArrayBinaryTag.intArrayBinaryTag(ints);
case 4://BinaryTagTypes.LONG:
final long[] longs = new long[jsonArray.size()];
for (int i = 0; i < longs.length; i++) {
longs[i] = (Long) jsonArray.get(i).getAsNumber();
}
return LongArrayBinaryTag.longArrayBinaryTag(longs);
case 10://BinaryTagTypes.COMPOUND:
tagItems.replaceAll(tag -> {
if (tag.type() == BinaryTagTypes.COMPOUND) {
return tag;
} else {
return CompoundBinaryTag.builder().put("", tag).build();
}
});
break;
}
return ListBinaryTag.listBinaryTag(listType, tagItems);
}
return EndBinaryTag.endBinaryTag();
}
}
@@ -1,115 +0,0 @@
/*
* Copyright (C) 2023 Sonar Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.jonesdev.sonar.common.utility.nbt;
import com.google.gson.*;
import lombok.experimental.UtilityClass;
import net.kyori.adventure.nbt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
// Taken from
// https://github.com/Nan1t/NanoLimbo/pull/79/files#diff-0e355ffd4eb0e15e2c2e628227debc9b6ccefeb167d7b9619311cdc7db2645ec
@UtilityClass
public class NBTMessageUtil {
public static BinaryTag fromJson(final JsonElement json) {
if (json instanceof JsonPrimitive) {
JsonPrimitive jsonPrimitive = (JsonPrimitive) json;
if (jsonPrimitive.isNumber()) {
Number number = json.getAsNumber();
if (number instanceof Byte) {
return ByteBinaryTag.byteBinaryTag((Byte) number);
} else if (number instanceof Short) {
return ShortBinaryTag.shortBinaryTag((Short) number);
} else if (number instanceof Integer) {
return IntBinaryTag.intBinaryTag((Integer) number);
} else if (number instanceof Long) {
return LongBinaryTag.longBinaryTag((Long) number);
} else if (number instanceof Float) {
return FloatBinaryTag.floatBinaryTag((Float) number);
} else if (number instanceof Double) {
return DoubleBinaryTag.doubleBinaryTag((Double) number);
}
} else if (jsonPrimitive.isString()) {
return StringBinaryTag.stringBinaryTag(jsonPrimitive.getAsString());
} else if (jsonPrimitive.isBoolean()) {
return ByteBinaryTag.byteBinaryTag(jsonPrimitive.getAsBoolean() ? (byte) 1 : (byte) 0);
} else {
throw new IllegalArgumentException("Unknown JSON primitive: " + jsonPrimitive);
}
} else if (json instanceof JsonObject) {
final CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
for (Map.Entry<String, JsonElement> property : ((JsonObject) json).entrySet()) {
builder.put(property.getKey(), fromJson(property.getValue()));
}
return builder.build();
} else if (json instanceof JsonArray) {
final JsonArray jsonArray = ((JsonArray) json);
if (jsonArray.size() == 0) {
return ListBinaryTag.listBinaryTag(EndBinaryTag.endBinaryTag().type(), Collections.emptyList());
}
final BinaryTagType<?> tagByteType = ByteBinaryTag.ZERO.type();
final BinaryTagType<?> tagIntType = IntBinaryTag.intBinaryTag(0).type();
final BinaryTagType<?> tagLongType = LongBinaryTag.longBinaryTag(0).type();
final BinaryTag listTag;
final BinaryTagType<?> listType = fromJson(jsonArray.get(0)).type();
if (listType.equals(tagByteType)) {
byte[] bytes = new byte[jsonArray.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (Byte) jsonArray.get(i).getAsNumber();
}
listTag = ByteArrayBinaryTag.byteArrayBinaryTag(bytes);
} else if (listType.equals(tagIntType)) {
int[] ints = new int[jsonArray.size()];
for (int i = 0; i < ints.length; i++) {
ints[i] = (Integer) jsonArray.get(i).getAsNumber();
}
listTag = IntArrayBinaryTag.intArrayBinaryTag(ints);
} else if (listType.equals(tagLongType)) {
long[] longs = new long[jsonArray.size()];
for (int i = 0; i < longs.length; i++) {
longs[i] = (Long) jsonArray.get(i).getAsNumber();
}
listTag = LongArrayBinaryTag.longArrayBinaryTag(longs);
} else {
final List<BinaryTag> tagItems = new ArrayList<>(jsonArray.size());
for (final JsonElement jsonEl : jsonArray) {
final BinaryTag subTag = fromJson(jsonEl);
if (subTag.type() != listType) {
throw new IllegalArgumentException("Cannot convert mixed JsonArray to Tag");
}
tagItems.add(subTag);
}
listTag = ListBinaryTag.listBinaryTag(listType, tagItems);
}
return listTag;
} else if (json instanceof JsonNull) {
return EndBinaryTag.endBinaryTag();
}
throw new IllegalArgumentException("Unknown JSON element: " + json);
}
}
@@ -24,25 +24,6 @@ import org.jetbrains.annotations.NotNull;
@UtilityClass
public class VarIntUtil {
private static final int[] EXACT_BYTE_LENGTHS = new int[33];
static {
for (int i = 0; i <= 32; ++i) {
EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
}
EXACT_BYTE_LENGTHS[32] = 1;
}
public static int varIntBytes(final int value) {
return EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)];
}
public static void write21BitVarInt(final @NotNull ByteBuf byteBuf, final int value) {
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
byteBuf.writeMedium(w);
}
public static int readVarInt(final ByteBuf byteBuf) {
int read = readVarIntSafely(byteBuf);
if (read == Integer.MIN_VALUE) {