mirror of
https://github.com/jonesdevelopment/sonar.git
synced 2026-11-05 17:22:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6c3dc9d2b | ||
|
|
a3e98e5e51 | ||
|
|
769d1f2ddb | ||
|
|
f977ae4fe5 | ||
|
|
9b11c4c1e0 | ||
|
|
44afb98b3c | ||
|
|
1e246472cc | ||
|
|
77ee422c64 | ||
|
|
756d7e1f89 | ||
|
|
2ac4b7aad3 | ||
|
|
5726e2d3d2 | ||
|
|
7aea5754a9 | ||
|
|
c10adc37be |
No files matched your search
+1
-1
@@ -48,8 +48,8 @@ allprojects {
|
||||
relocate("com.github.benmanes.caffeine", "xyz.jonesdev.sonar.libs.caffeine")
|
||||
relocate("com.mysql", "xyz.jonesdev.sonar.libs.mysql")
|
||||
relocate("org.mariadb", "xyz.jonesdev.sonar.libs.mariadb")
|
||||
relocate("xyz.jonesdev.capja", "xyz.jonesdev.sonar.libs.capja")
|
||||
relocate("org.h2", "xyz.jonesdev.sonar.libs.h2")
|
||||
relocate("com.jhlabs", "xyz.jonesdev.sonar.libs.jhlabs")
|
||||
|
||||
// Exclude unnecessary metadata information
|
||||
exclude("META-INF/*/**")
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
org.gradle.daemon=false
|
||||
group=xyz.jonesdev.sonar
|
||||
version=2.1.1
|
||||
version=2.1.2
|
||||
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
|
||||
@@ -2,12 +2,15 @@
|
||||
netty = "4.1.112.Final"
|
||||
adventure = "4.17.0"
|
||||
adventure-platform = "4.3.4"
|
||||
bstats = "3.0.2"
|
||||
bstats = "3.0.3"
|
||||
libby = "2.0.0-SNAPSHOT"
|
||||
pluginyml = "0.6.0"
|
||||
|
||||
[plugins]
|
||||
indra-git = "net.kyori.indra.git:3.1.3"
|
||||
shadow = "com.gradleup.shadow:8.3.0"
|
||||
pluginyml-bungee = { id = "net.minecrell.plugin-yml.bungee", version.ref = "pluginyml" }
|
||||
pluginyml-bukkit = { id = "net.minecrell.plugin-yml.bukkit", version.ref = "pluginyml" }
|
||||
|
||||
[libraries]
|
||||
netty = { module = "io.netty:netty-all", version.ref = "netty" }
|
||||
@@ -23,12 +26,12 @@ libby-velocity = { module = "com.alessiodp.libby:libby-velocity", version.ref =
|
||||
bstats-bungee = { module = "org.bstats:bstats-bungeecord", version.ref = "bstats" }
|
||||
bstats-bukkit = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" }
|
||||
bstats-velocity = { module = "org.bstats:bstats-velocity", version.ref = "bstats" }
|
||||
imagefilters = "com.jhlabs:filters:2.0.235-1"
|
||||
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.8"
|
||||
annotations = "org.jetbrains:annotations:24.1.0"
|
||||
simpleyaml = "com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4"
|
||||
ormlite = "com.j256.ormlite:ormlite-jdbc:6.1"
|
||||
lombok = "org.projectlombok:lombok:1.18.34"
|
||||
capja = "xyz.jonesdev:capja:1.0.3"
|
||||
bungeecord = "net.md_5:bungeecord-proxy:master-SNAPSHOT"
|
||||
velocity = "com.velocitypowered:velocity-proxy:3.3.0-SNAPSHOT"
|
||||
# We have to use 1.8.8 for backwards compatibility
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
rootProject.name = "Sonar"
|
||||
|
||||
sequenceOf("api", "bukkit", "bungee", "common", "velocity").forEach {
|
||||
sequenceOf("api", "captcha", "common", "bukkit", "bungee", "velocity").forEach {
|
||||
val path = "sonar-$it"
|
||||
val project = ":$it"
|
||||
|
||||
|
||||
@@ -194,31 +194,20 @@ public final class SonarConfiguration {
|
||||
verification.vehicle.minimumPackets = clamp(generalConfig.getInt("verification.checks.vehicle.minimum-packets"), 0, 20);
|
||||
|
||||
verification.map.timing = Verification.Timing.valueOf(generalConfig.getString("verification.checks.map-captcha.timing"));
|
||||
verification.map.scratches = generalConfig.getBoolean("verification.checks.map-captcha.effects.scratches");
|
||||
verification.map.ripple = generalConfig.getBoolean("verification.checks.map-captcha.effects.ripple");
|
||||
verification.map.bump = generalConfig.getBoolean("verification.checks.map-captcha.effects.bump");
|
||||
|
||||
verification.map.distortion.enabled = generalConfig.getBoolean("verification.checks.map-captcha.effects.distortion.enabled");
|
||||
verification.map.distortion.shape = generalConfig.getInt("verification.checks.map-captcha.effects.distortion.shape");
|
||||
verification.map.distortion.distance = generalConfig.getInt("verification.checks.map-captcha.effects.distortion.distance");
|
||||
verification.map.distortion.density = (float) generalConfig.getYaml().getDouble("verification.checks.map-captcha.effects.distortion.density");
|
||||
verification.map.distortion.mix = (float) generalConfig.getYaml().getDouble("verification.checks.map-captcha.effects.distortion.mix");
|
||||
|
||||
final String backgroundImagePath = generalConfig.getString("verification.checks.map-captcha.background");
|
||||
if (!backgroundImagePath.isEmpty()) {
|
||||
verification.map.backgroundImage = new File(pluginFolder, backgroundImagePath);
|
||||
if (!verification.map.backgroundImage.exists()) {
|
||||
Sonar.get().getLogger().error("The background image does not exist! Please check the configuration.");
|
||||
}
|
||||
} else {
|
||||
verification.map.backgroundImage = null;
|
||||
}
|
||||
|
||||
verification.map.autoColor = generalConfig.getBoolean("verification.checks.map-captcha.auto-color");
|
||||
verification.map.precomputeAmount = clamp(generalConfig.getInt("verification.checks.map-captcha.precompute"), 1, 1000);
|
||||
verification.map.precomputeAmount = clamp(generalConfig.getInt("verification.checks.map-captcha.precompute"), 10, 5000);
|
||||
verification.map.maxDuration = clamp(generalConfig.getInt("verification.checks.map-captcha.max-duration"), 5000, 360000);
|
||||
verification.map.maxTries = generalConfig.getInt("verification.checks.map-captcha.max-tries");
|
||||
verification.map.dictionary = generalConfig.getString("verification.checks.map-captcha.dictionary");
|
||||
verification.map.alphabet = generalConfig.getString("verification.checks.map-captcha.alphabet");
|
||||
verification.map.backgroundImage = null;
|
||||
|
||||
final String backgroundPath = generalConfig.getString("verification.checks.map-captcha.background");
|
||||
if (!backgroundPath.isEmpty()) {
|
||||
try (final InputStream inputStream = new FileInputStream(backgroundPath)) {
|
||||
verification.map.backgroundImage = inputStream;
|
||||
} catch (IOException exception) {
|
||||
Sonar.get().getLogger().error("Could not find background image", exception);
|
||||
}
|
||||
}
|
||||
|
||||
verification.brand.enabled = generalConfig.getBoolean("verification.checks.client-brand.enabled");
|
||||
verification.brand.validRegex = Pattern.compile(generalConfig.getString("verification.checks.client-brand.valid-regex"));
|
||||
@@ -393,26 +382,11 @@ public final class SonarConfiguration {
|
||||
@Getter
|
||||
public static final class Map {
|
||||
private Timing timing;
|
||||
private boolean scratches;
|
||||
private boolean ripple;
|
||||
private boolean bump;
|
||||
private File backgroundImage;
|
||||
private boolean autoColor;
|
||||
private int precomputeAmount;
|
||||
private int maxDuration;
|
||||
private int maxTries;
|
||||
private String dictionary;
|
||||
|
||||
private Smear distortion = new Smear();
|
||||
|
||||
@Getter
|
||||
public static final class Smear {
|
||||
private boolean enabled;
|
||||
private int shape;
|
||||
private int distance;
|
||||
private float density;
|
||||
private float mix;
|
||||
}
|
||||
private String alphabet;
|
||||
private InputStream backgroundImage;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@@ -491,21 +465,21 @@ public final class SonarConfiguration {
|
||||
Library.builder()
|
||||
.groupId("com{}mysql")
|
||||
.artifactId("mysql-connector-j")
|
||||
.version("8.4.0")
|
||||
.version("9.0.0")
|
||||
.relocate("com{}mysql", "xyz{}jonesdev{}sonar{}libs{}mysql")
|
||||
.build()),
|
||||
MARIADB("MariaDB", "jdbc:mariadb://%s:%d/%s", new MariaDbDatabaseTypeAdapter(),
|
||||
Library.builder()
|
||||
.groupId("org{}mariadb{}jdbc")
|
||||
.artifactId("mariadb-java-client")
|
||||
.version("3.4.0")
|
||||
.version("3.4.1")
|
||||
.relocate("org{}mariadb", "xyz{}jonesdev{}sonar{}libs{}mariadb")
|
||||
.build()),
|
||||
H2("H2", "jdbc:h2:file:%s", new H2DatabaseTypeAdapter(),
|
||||
Library.builder()
|
||||
.groupId("com{}h2database")
|
||||
.artifactId("h2")
|
||||
.version("2.1.214")
|
||||
.version("2.2.220")
|
||||
.relocate("org{}h2", "xyz{}jonesdev{}sonar{}libs{}h2")
|
||||
.build()),
|
||||
NONE("None", null, null, null);
|
||||
|
||||
+1
-1
@@ -249,7 +249,7 @@ public final class VerifiedPlayerController {
|
||||
}
|
||||
|
||||
try {
|
||||
dao.create(player);
|
||||
dao.createIfNotExists(player);
|
||||
} catch (SQLException exception) {
|
||||
LOGGER.error("Error trying to add entry: {}", exception);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
|
||||
import java.net.InetAddress;
|
||||
@@ -45,6 +46,8 @@ public final class Fallback {
|
||||
private Cache<InetAddress, Byte> blacklist;
|
||||
@Setter
|
||||
private long blacklistTime;
|
||||
@Setter
|
||||
private CaptchaGenerator captchaGenerator;
|
||||
|
||||
private final @NotNull FallbackQueue queue = new FallbackQueue();
|
||||
private final @NotNull FallbackRatelimiter ratelimiter = new FallbackRatelimiter();
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2024 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.api.fallback.captcha;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CaptchaGenerator {
|
||||
BufferedImage createImage(final char[] answer);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import net.minecrell.pluginyml.bukkit.BukkitPluginDescription
|
||||
|
||||
plugins {
|
||||
id("net.minecrell.plugin-yml.bukkit") version "0.6.0" apply true
|
||||
alias(libs.plugins.pluginyml.bukkit) apply true
|
||||
}
|
||||
|
||||
bukkit {
|
||||
|
||||
+28
-16
@@ -25,6 +25,7 @@ import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.bukkit.fallback.FallbackBukkitInjector;
|
||||
import xyz.jonesdev.sonar.common.util.FakeChannelUtil;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
@@ -36,11 +37,11 @@ import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWID
|
||||
// Taken from
|
||||
// https://github.com/ViaVersion/ViaVersion/blob/master/bukkit/src/main/java/com/viaversion/viaversion/bukkit/listeners/JoinListener.java
|
||||
public final class BukkitJoinListener implements Listener {
|
||||
private static MethodHandle handleMethod;
|
||||
private static MethodHandle _handleMethod;
|
||||
// ServerGamePacketListenerImpl aka. PlayerConnection
|
||||
private static MethodHandle packetListenerField;
|
||||
private static MethodHandle managerField;
|
||||
private static MethodHandle channelField;
|
||||
private static MethodHandle _listenerField;
|
||||
private static MethodHandle _networkManagerField;
|
||||
private static MethodHandle _channelField;
|
||||
|
||||
private static boolean initialized;
|
||||
|
||||
@@ -50,14 +51,14 @@ public final class BukkitJoinListener implements Listener {
|
||||
.getDeclaredMethod("getHandle");
|
||||
final Field listenerField = FallbackBukkitInjector.findField(false, handleMethod.getReturnType(),
|
||||
"PlayerConnection", "ServerGamePacketListenerImpl");
|
||||
final Field managerField = FallbackBukkitInjector.findField(true, listenerField.getType(),
|
||||
final Field networkManagerField = FallbackBukkitInjector.findField(true, listenerField.getType(),
|
||||
"NetworkManager", "Connection");
|
||||
final Field channelField = FallbackBukkitInjector.getFieldAt(managerField.getType(), Channel.class, 0);
|
||||
final Field channelField = FallbackBukkitInjector.getFieldAt(networkManagerField.getType(), Channel.class, 0);
|
||||
|
||||
BukkitJoinListener.handleMethod = MethodHandles.lookup().unreflect(handleMethod);
|
||||
packetListenerField = MethodHandles.lookup().unreflectGetter(listenerField);
|
||||
BukkitJoinListener.managerField = MethodHandles.lookup().unreflectGetter(managerField);
|
||||
BukkitJoinListener.channelField = MethodHandles.lookup().unreflectGetter(channelField);
|
||||
_handleMethod = MethodHandles.lookup().unreflect(handleMethod);
|
||||
_listenerField = MethodHandles.lookup().unreflectGetter(listenerField);
|
||||
_networkManagerField = MethodHandles.lookup().unreflectGetter(networkManagerField);
|
||||
_channelField = MethodHandles.lookup().unreflectGetter(channelField);
|
||||
initialized = true;
|
||||
} catch (Exception exception) {
|
||||
initialized = false;
|
||||
@@ -66,22 +67,33 @@ public final class BukkitJoinListener implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void handle(final @NotNull PlayerJoinEvent event) {
|
||||
// Make sure the player is not an NPC
|
||||
if (!initialized || event.getPlayer().hasMetadata("NPC")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final Object handle = handleMethod.invoke(event.getPlayer());
|
||||
final Object listener = packetListenerField.invoke(handle);
|
||||
final Object networkManager = managerField.invoke(listener);
|
||||
final Channel channel = (Channel) channelField.invoke(networkManager);
|
||||
final Object handle = _handleMethod.invoke(event.getPlayer());
|
||||
final Object listener = _listenerField.invoke(handle);
|
||||
final Object networkManager = _networkManagerField.invoke(listener);
|
||||
final Channel channel = (Channel) _channelField.invoke(networkManager);
|
||||
|
||||
// Don't continue if the channel isn't found or if the channel is inactive
|
||||
if (channel == null || !channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the player is not a fake player
|
||||
if (FakeChannelUtil.isFakePlayer(channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the channel if Sonar's handlers are not found when the PlayerJoinEvent is called
|
||||
if (channel != null && channel.isActive() && channel.pipeline().context(FALLBACK_BANDWIDTH) == null) {
|
||||
if (channel.pipeline().context(FALLBACK_BANDWIDTH) == null) {
|
||||
channel.close();
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
Sonar.get().getLogger().warn("Failed to the channel for {}: {}", event.getPlayer().getName(), throwable);
|
||||
Sonar.get().getLogger().warn("Couldn't find {}: {}", event.getPlayer().getName(), throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id("net.minecrell.plugin-yml.bungee") version "0.6.0" apply true
|
||||
alias(libs.plugins.pluginyml.bungee) apply true
|
||||
}
|
||||
|
||||
bungee {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
dependencies {
|
||||
compileOnly(project(":api"))
|
||||
compileOnly(rootProject.libs.imagefilters)
|
||||
|
||||
testCompileOnly(project(":api"))
|
||||
testCompileOnly(rootProject.libs.imagefilters)
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
java.targetCompatibility = JavaVersion.VERSION_11
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.captcha;
|
||||
|
||||
import com.jhlabs.image.FBMFilter;
|
||||
import com.jhlabs.image.SmearFilter;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.fallback.captcha.CaptchaGenerator;
|
||||
import xyz.jonesdev.sonar.captcha.filters.CurvesOverlayFilter;
|
||||
import xyz.jonesdev.sonar.captcha.filters.NoiseOverlayFilter;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import static java.awt.image.BufferedImage.TYPE_3BYTE_BGR;
|
||||
import static xyz.jonesdev.sonar.captcha.StandardTTFFontProvider.FONTS;
|
||||
import static xyz.jonesdev.sonar.captcha.StandardTTFFontProvider.STANDARD_FONT_SIZE;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class StandardCaptchaGenerator implements CaptchaGenerator {
|
||||
private static final CurvesOverlayFilter CURVES = new CurvesOverlayFilter(3);
|
||||
//private static final CircleInverseFilter CIRCLES = new CircleInverseFilter(1, 30, 10);
|
||||
private static final NoiseOverlayFilter NOISE = new NoiseOverlayFilter(1, 20);
|
||||
private static final SmearFilter SMEAR = new SmearFilter();
|
||||
private static final FBMFilter FBM = new FBMFilter();
|
||||
private static final Random RANDOM = new Random();
|
||||
private static final Color[] COLORS = new Color[4];
|
||||
private static final float[] COLOR_FRACTIONS = new float[COLORS.length];
|
||||
|
||||
static {
|
||||
FBM.setAmount(0.6f);
|
||||
FBM.setScale(15);
|
||||
|
||||
SMEAR.setShape(SmearFilter.SQUARES);
|
||||
SMEAR.setDensity(0.1f);
|
||||
SMEAR.setDistance(0);
|
||||
SMEAR.setMix(0.35f);
|
||||
|
||||
// Create fractions based on the number of colors
|
||||
for (int i = 0; i < COLOR_FRACTIONS.length; i++) {
|
||||
COLOR_FRACTIONS[i] = (float) i / (COLOR_FRACTIONS.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private final int width, height;
|
||||
private final @Nullable InputStream background;
|
||||
private BufferedImage backgroundImage;
|
||||
|
||||
@Override
|
||||
public @NotNull BufferedImage createImage(final char[] answer) {
|
||||
final BufferedImage image = createBackgroundImage();
|
||||
final Graphics2D graphics = image.createGraphics();
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
// Draw characters and other effects on the image
|
||||
applyRandomColorGradient(graphics);
|
||||
drawCharacters(graphics, answer);
|
||||
CURVES.transform(image, graphics);
|
||||
NOISE.transform(image);
|
||||
//SMEAR.filter(image, image);
|
||||
//CIRCLES.transform(image); // TODO: check if the text is still easy to read after this
|
||||
// Make sure to dispose the graphics instance
|
||||
graphics.dispose();
|
||||
return image;
|
||||
}
|
||||
|
||||
private @NotNull BufferedImage createBackgroundImage() {
|
||||
if (background == null) {
|
||||
final BufferedImage image = new BufferedImage(width, height, TYPE_3BYTE_BGR);
|
||||
// Fill the entire image with a noise texture
|
||||
return FBM.filter(image, image);
|
||||
}
|
||||
try {
|
||||
return backgroundImage == null ? backgroundImage = ImageIO.read(background) : backgroundImage;
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("Could not read background image", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyRandomColorGradient(final @NotNull Graphics2D graphics) {
|
||||
// Randomize the colors for the gradient effect
|
||||
for (int i = 0; i < COLORS.length; i++) {
|
||||
final float random = 0.9f + RANDOM.nextFloat() * 0.1f;
|
||||
COLORS[i] = Color.getHSBColor(RANDOM.nextFloat(), random, random);
|
||||
}
|
||||
|
||||
// Apply the random gradient effect
|
||||
graphics.setPaint(new LinearGradientPaint(0, 0, width, height,
|
||||
COLOR_FRACTIONS, COLORS, MultipleGradientPaint.CycleMethod.REFLECT));
|
||||
}
|
||||
|
||||
private void drawCharacters(final @NotNull Graphics2D graphics,
|
||||
final char @NotNull [] answer) {
|
||||
final FontRenderContext ctx = graphics.getFontRenderContext();
|
||||
final GlyphVector[] glyphs = new GlyphVector[answer.length];
|
||||
|
||||
for (int i = 0; i < answer.length; i++) {
|
||||
// Create a glyph vector for the character with a random font
|
||||
final Font font = FONTS[RANDOM.nextInt(FONTS.length)];
|
||||
glyphs[i] = font.createGlyphVector(ctx, String.valueOf(answer[i]));
|
||||
}
|
||||
|
||||
final double scalingXY = 5 - Math.min(answer.length, 5) * 0.65;
|
||||
|
||||
// Calculate first X and Y positions
|
||||
final double totalWidth = Arrays.stream(glyphs)
|
||||
.mapToDouble(glyph -> glyph.getLogicalBounds().getWidth() * scalingXY - 1)
|
||||
.sum();
|
||||
double beginX = Math.max(Math.min(width / 2D - totalWidth / 2D, totalWidth), 0);
|
||||
double beginY = (height + STANDARD_FONT_SIZE / 2D) / 2D + scalingXY;
|
||||
|
||||
// Draw each glyph one by one
|
||||
for (final GlyphVector glyph : glyphs) {
|
||||
final AffineTransform transformation = AffineTransform.getTranslateInstance(beginX, beginY);
|
||||
// Shear the glyph by a random amount
|
||||
final double shearXY = Math.sin(beginX + beginY) / 6;
|
||||
transformation.shear(shearXY, shearXY);
|
||||
// Scale the glyph to perfectly fit the image
|
||||
transformation.scale(scalingXY, scalingXY);
|
||||
// Draw the glyph to the buffered image
|
||||
final Shape transformedShape = transformation.createTransformedShape(glyph.getOutline());
|
||||
graphics.fill(transformedShape);
|
||||
// Draw a random outline around the glyph
|
||||
if (RANDOM.nextFloat() < 0.25f) {
|
||||
createGlyphOutline(graphics, transformedShape);
|
||||
}
|
||||
// Make sure the next glyph isn't drawn at the same position
|
||||
beginX += glyph.getVisualBounds().getWidth() * scalingXY + 2;
|
||||
beginY += -10 + RANDOM.nextFloat() * 20;
|
||||
}
|
||||
}
|
||||
|
||||
private void createGlyphOutline(final @NotNull Graphics2D graphics, final @NotNull Shape shape) {
|
||||
final float txy = 1.25f + RANDOM.nextFloat();
|
||||
final float width = 1 + RANDOM.nextFloat();
|
||||
|
||||
// Create a randomly translated and stroked shape based on the original glyph shape
|
||||
final AffineTransform translation = AffineTransform.getTranslateInstance(txy, txy);
|
||||
final Shape strokedShape = new BasicStroke(width).createStrokedShape(shape);
|
||||
|
||||
graphics.fill(translation.createTransformedShape(strokedShape));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.captcha;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
@UtilityClass
|
||||
class StandardTTFFontProvider {
|
||||
private static final String[] FONT_NAMES = {"Kingthings_Trypewriter_2"};
|
||||
static final Font[] FONTS = new Font[FONT_NAMES.length];
|
||||
static final int STANDARD_FONT_SIZE = 25;
|
||||
|
||||
static {
|
||||
for (int i = 0; i < FONT_NAMES.length; i++) {
|
||||
FONTS[i] = loadFont(String.format("/assets/fonts/%s.ttf", FONT_NAMES[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private static Font loadFont(final @NotNull String path) {
|
||||
try (final InputStream inputStream = StandardCaptchaGenerator.class.getResourceAsStream(path)) {
|
||||
// Load the font from the TTF file
|
||||
final Font customFont = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(inputStream));
|
||||
// Set the font size and style
|
||||
return customFont.deriveFont(Font.PLAIN, STANDARD_FONT_SIZE);
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2024 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.captcha.filters;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Random;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class CircleInverseFilter {
|
||||
private final int amount, minRadius, maxRadiusExpansion;
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
public void transform(final @NotNull BufferedImage image) {
|
||||
final int minX = image.getWidth() / 4;
|
||||
final int minY = image.getHeight() / 4;
|
||||
|
||||
for (int i = 0; i < amount; i++) {
|
||||
// Generate random position and radius for the circle
|
||||
final float centerX = minX + RANDOM.nextInt(image.getWidth() - minX * 2);
|
||||
final float centerY = minY + RANDOM.nextInt(image.getHeight() - minY * 2);
|
||||
final int radius = minRadius + RANDOM.nextInt(maxRadiusExpansion);
|
||||
|
||||
final Circle circle = new Circle(centerX, centerY, radius);
|
||||
// Draw inverted circle
|
||||
_addInvertedCircle(image, circle);
|
||||
}
|
||||
}
|
||||
|
||||
private static void _addInvertedCircle(final @NotNull BufferedImage bufferedImage,
|
||||
final @NotNull Circle circle) {
|
||||
for (int x = 0; x < bufferedImage.getWidth(); x++) {
|
||||
for (int y = 0; y < bufferedImage.getHeight(); y++) {
|
||||
if (isWithinCircle(x, y, circle, circle.radius)) {
|
||||
// Invert colors within the circle
|
||||
final int rgb = bufferedImage.getRGB(x, y);
|
||||
final int invertedRGB = invertColorAndFilter(rgb);
|
||||
bufferedImage.setRGB(x, y, invertedRGB);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isWithinCircle(final float x, final float y, final @NotNull Circle circle, int radius) {
|
||||
final float distanceX = x - circle.centerX;
|
||||
final float distanceY = y - circle.centerY;
|
||||
return distanceX * distanceX + distanceY * distanceY <= radius * radius;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
static final class Circle {
|
||||
private final float centerX;
|
||||
private final float centerY;
|
||||
private final int radius;
|
||||
}
|
||||
|
||||
private static int invertColorAndFilter(final int rgb) {
|
||||
final Color color = new Color(rgb);
|
||||
final int red = Math.max(Color.LIGHT_GRAY.getRed() - color.getRed(), 0);
|
||||
final int green = Math.max(Color.LIGHT_GRAY.getGreen() - color.getGreen(), 0);
|
||||
final int blue = Math.max(Color.LIGHT_GRAY.getBlue() - color.getBlue(), 0);
|
||||
return new Color(red, green, blue).getRGB();
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.captcha.filters;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.CubicCurve2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Random;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class CurvesOverlayFilter {
|
||||
private final int amount;
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
public void transform(final @NotNull BufferedImage image,
|
||||
final @NotNull Graphics2D graphics) {
|
||||
// Randomize the stroke width
|
||||
graphics.setStroke(new BasicStroke(1 + RANDOM.nextFloat()));
|
||||
|
||||
final int halfWidth = image.getWidth() / 2;
|
||||
|
||||
for (int i = 0; i < amount; ++i) {
|
||||
final float randomX = image.getWidth() * RANDOM.nextFloat();
|
||||
final float randomY = image.getHeight() * RANDOM.nextFloat();
|
||||
final float amplitude = 6.2831855f * (RANDOM.nextFloat() - 0.5f);
|
||||
final float sin = (float) Math.sin(amplitude) * halfWidth;
|
||||
final float cos = (float) Math.cos(amplitude) * halfWidth;
|
||||
final float x1 = randomX - cos;
|
||||
final float y1 = randomY - sin;
|
||||
final float x2 = randomX + cos;
|
||||
final float y2 = randomY + sin;
|
||||
// Control points for the cubic curve
|
||||
final float ctrlX1 = randomX + sin / 2;
|
||||
final float ctrlY1 = randomY - cos / 2;
|
||||
final float ctrlX2 = randomX - sin / 2;
|
||||
final float ctrlY2 = randomY + cos / 2;
|
||||
// Draw a cubic curve instead of a straight line
|
||||
graphics.draw(new CubicCurve2D.Float(x1, y1, ctrlX1, ctrlY1, ctrlX2, ctrlY2, x2, y2));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.captcha.filters;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Random;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class GridOverlayFilter {
|
||||
private final int amount, lineWidth, randomOffset;
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
public void transform(final @NotNull BufferedImage image,
|
||||
final @NotNull Graphics2D graphics) {
|
||||
final int spacingX = image.getWidth() / amount + lineWidth;
|
||||
final int spacingY = image.getHeight() / amount + lineWidth;
|
||||
int currentX = spacingX / 2;
|
||||
int currentY = spacingY / 2;
|
||||
|
||||
for (int i = 0; i < amount; i++) {
|
||||
graphics.setStroke(new BasicStroke(lineWidth * 0.5f + lineWidth * RANDOM.nextFloat()));
|
||||
graphics.drawLine(currentX, 0, currentX + RANDOM.nextInt(randomOffset), image.getHeight());
|
||||
graphics.drawLine(0, currentY, image.getWidth(), currentY + RANDOM.nextInt(randomOffset));
|
||||
|
||||
currentX += spacingX + lineWidth;
|
||||
currentY += spacingY + lineWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.captcha.filters;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Random;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class NoiseOverlayFilter {
|
||||
private final float density, amount;
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private int randomGaussian(int x) {
|
||||
x += (int) (RANDOM.nextGaussian() * amount);
|
||||
return Math.max(Math.min(x, 0xff), 0);
|
||||
}
|
||||
|
||||
public void transform(final @NotNull BufferedImage image) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
if (RANDOM.nextFloat() <= density) {
|
||||
final int rgb = image.getRGB(x, y);
|
||||
int r = (rgb >> 16) & 0xff;
|
||||
int g = (rgb >> 8) & 0xff;
|
||||
int b = rgb & 0xff;
|
||||
r = randomGaussian(r);
|
||||
g = randomGaussian(g);
|
||||
b = randomGaussian(b);
|
||||
image.setRGB(x, y, (r << 16) | (g << 8) | b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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/>.
|
||||
*/
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
|
||||
@UtilityClass
|
||||
public class Main {
|
||||
public void main(final String... args) throws IOException {
|
||||
final Random random = new Random();
|
||||
final StandardCaptchaGenerator standardCaptchaGenerator = new StandardCaptchaGenerator(128, 128, null);
|
||||
|
||||
final long start = System.currentTimeMillis();
|
||||
final char[] dictionary = {'a', 'b', 'c', 'd', 'e', 'f'/*, 'g'*/, 'h'/*, 'i'*/, 'j',
|
||||
'k', 'm', 'n', 'o', 'p'/*, 'q'*/, 'r', 's', 't', 'u'/*, 'v', 'w'*/, 'x', 'y', 'z'};
|
||||
|
||||
// Generate image
|
||||
final int amount = 1;
|
||||
for (int i = 0; i < amount; i++) {
|
||||
final char[] answer = new char[3 + random.nextInt(2)];
|
||||
for (int j = 0; j < answer.length; j++) {
|
||||
answer[j] = dictionary[random.nextInt(dictionary.length)];
|
||||
}
|
||||
final BufferedImage bufferedImage = standardCaptchaGenerator.createImage(answer);
|
||||
|
||||
// Save image
|
||||
ImageIO.write(bufferedImage, "png", new File(i + ".png"));
|
||||
}
|
||||
System.out.println("Took " + (System.currentTimeMillis() - start) + "ms to generate " + amount + " image(s)");
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
dependencies {
|
||||
compileOnly(project(":api"))
|
||||
|
||||
implementation(project(":captcha"))
|
||||
compileOnly(rootProject.libs.adventure.nbt)
|
||||
|
||||
implementation(rootProject.libs.capja)
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
@@ -56,6 +56,13 @@ class LibraryLoader {
|
||||
.artifactId("ormlite-jdbc")
|
||||
.version("6.1")
|
||||
.relocate("com{}j256{}ormlite", "xyz{}jonesdev{}sonar{}libs{}ormlite")
|
||||
.build(),
|
||||
// JHLabs image filters
|
||||
Library.builder()
|
||||
.groupId("com{}jhlabs")
|
||||
.artifactId("filters")
|
||||
.version("2.0.235-1")
|
||||
.relocate("com{}jhlabs", "xyz{}jonesdev{}sonar{}libs{}jhlabs")
|
||||
.build()
|
||||
);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import xyz.jonesdev.sonar.api.statistics.SonarStatistics;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.api.verbose.Notification;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
|
||||
import xyz.jonesdev.sonar.common.service.SonarServiceManager;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
@@ -69,7 +70,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
// Load all libraries before anything else
|
||||
LibraryLoader.loadLibraries(libraryManager, platform);
|
||||
this.libraryManager = libraryManager;
|
||||
// Check if Netty is up-to-date
|
||||
// Check if Netty is up to date
|
||||
ProtocolUtil.checkNettyVersion();
|
||||
// Set the Sonar API
|
||||
SonarSupplier.set(this);
|
||||
@@ -81,8 +82,8 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
this.verboseHandler = new Verbose();
|
||||
this.notificationHandler = new Notification();
|
||||
this.config = new SonarConfiguration(dataDirectory);
|
||||
this.subcommandRegistry = new SubcommandRegistry();
|
||||
// Register all subcommands
|
||||
this.subcommandRegistry = new SubcommandRegistry();
|
||||
this.subcommandRegistry.register(
|
||||
new BlacklistCommand(),
|
||||
new VerifiedCommand(),
|
||||
@@ -147,6 +148,17 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
getLogger().warn("Reloading after changing the database type is not recommended as it may cause data loss.");
|
||||
}
|
||||
|
||||
// Update the CAPTCHA generator if necessary
|
||||
if (getConfig().getVerification().getMap().getTiming() == SonarConfiguration.Verification.Timing.NEVER) {
|
||||
getFallback().setCaptchaGenerator(null);
|
||||
} else if (getFallback().getCaptchaGenerator() == null
|
||||
|| getFallback().getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
|
||||
getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(128, 128,
|
||||
Sonar.get().getConfig().getVerification().getMap().getBackgroundImage()));
|
||||
} else {
|
||||
getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
|
||||
}
|
||||
|
||||
// Prepare cached packets
|
||||
getLogger().info("Taking cached snapshots of all packets...");
|
||||
FallbackPreparer.prepare();
|
||||
|
||||
+5
-10
@@ -27,8 +27,6 @@ import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameEncoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
@@ -201,14 +199,11 @@ public abstract class FallbackInboundHandlerAdapter extends ChannelInboundHandle
|
||||
ctx.pipeline().remove(entry.getValue());
|
||||
removalListener.accept(ctx.pipeline(), entry.getKey(), entry.getValue());
|
||||
}
|
||||
// Add our custom pipelines
|
||||
ctx.pipeline()
|
||||
.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarInt21FrameEncoder.INSTANCE)
|
||||
.addFirst(FALLBACK_TIMEOUT, new FallbackTimeoutHandler(
|
||||
Sonar.get().getConfig().getVerification().getReadTimeout(),
|
||||
Sonar.get().getConfig().getVerification().getWriteTimeout(),
|
||||
TimeUnit.MILLISECONDS))
|
||||
.addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
|
||||
// Add our read/write timeout handler
|
||||
ctx.pipeline().addFirst(FALLBACK_TIMEOUT, new FallbackTimeoutHandler(
|
||||
Sonar.get().getConfig().getVerification().getReadTimeout(),
|
||||
Sonar.get().getConfig().getVerification().getWriteTimeout(),
|
||||
TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ public final class FallbackTimeoutHandler extends IdleStateHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelIdle(final ChannelHandlerContext ctx,
|
||||
protected void channelIdle(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull IdleStateEvent idleStateEvent) throws Exception {
|
||||
// The netty (default) ReadTimeoutHandler would normally just throw an Exception
|
||||
// The default ReadTimeoutHandler does only check for the boolean 'closed' and
|
||||
|
||||
@@ -34,6 +34,8 @@ import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackTailExceptionsHandler;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.netty.FallbackVarInt21FrameEncoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
|
||||
@@ -118,6 +120,7 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
|
||||
// Replace normal encoder to allow custom packets
|
||||
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
|
||||
pipeline.addFirst(FALLBACK_FRAME_ENCODER, FallbackVarInt21FrameEncoder.INSTANCE);
|
||||
pipeline.addLast(FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
|
||||
// Send LoginSuccess packet to make the client think they are joining the server
|
||||
@@ -128,6 +131,7 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
|
||||
// Replace normal decoder to allow custom packets
|
||||
final FallbackPacketDecoder fallbackPacketDecoder = new FallbackPacketDecoder(protocolVersion);
|
||||
pipeline.addFirst(FALLBACK_FRAME_DECODER, new FallbackVarInt21FrameDecoder());
|
||||
pipeline.addLast(FALLBACK_PACKET_DECODER, fallbackPacketDecoder);
|
||||
// Listen for all incoming packets by setting the packet listener
|
||||
fallbackPacketDecoder.setListener(new FallbackLoginSessionHandler(this, username));
|
||||
|
||||
+4
-2
@@ -27,6 +27,7 @@ import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackInboundHandler;
|
||||
import xyz.jonesdev.sonar.common.util.FakeChannelUtil;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
@@ -60,8 +61,9 @@ public final class FallbackInjectedChannelInitializer extends ChannelInitializer
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
}
|
||||
|
||||
// Inject Sonar's channel handler into the pipeline
|
||||
if (channel.isActive()) {
|
||||
// Inject Sonar's channel handler into the pipeline;
|
||||
// Also make sure the player is not a fake player to avoid compatibility issues
|
||||
if (channel.isActive() && !FakeChannelUtil.isFakePlayer(channel)) {
|
||||
final ChannelHandler inboundHandler = new FallbackInboundHandler(sonarPipelineInjector);
|
||||
// We need to be careful on Bukkit, as the encoder can be different
|
||||
if (Sonar.get().getPlatform() == SonarPlatform.BUKKIT) {
|
||||
|
||||
+3
-6
@@ -108,9 +108,6 @@ public class FallbackPreparer {
|
||||
public double[] preparedCachedYMotions;
|
||||
public double maxFallDistance;
|
||||
|
||||
// CAPTCHA
|
||||
public final CaptchaPreparer MAP_INFO_PREPARER = new CaptchaPreparer();
|
||||
|
||||
// XP packets
|
||||
public FallbackPacket[] xpCountdown;
|
||||
|
||||
@@ -198,7 +195,7 @@ public class FallbackPreparer {
|
||||
// Prepare CAPTCHA messages
|
||||
enterCodeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
|
||||
MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.enter-code"),
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.enter"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
|
||||
incorrectCaptcha = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
|
||||
MiniMessage.miniMessage().deserialize(
|
||||
@@ -210,11 +207,11 @@ public class FallbackPreparer {
|
||||
|
||||
for (int i = 0; i < xpCountdown.length; i++) {
|
||||
final float bar = (float) i / xpCountdown.length;
|
||||
xpCountdown[i] = new FallbackPacketSnapshot(new SetExperiencePacket(bar, i, i));
|
||||
xpCountdown[i] = new FallbackPacketSnapshot(new SetExperiencePacket(bar, i, 0));
|
||||
}
|
||||
|
||||
// Prepare CAPTCHA answers
|
||||
MAP_INFO_PREPARER.prepare();
|
||||
CaptchaPreparer.prepare();
|
||||
} else {
|
||||
// Throw away if not needed
|
||||
enterCodeMessage = null;
|
||||
|
||||
+19
-66
@@ -17,99 +17,52 @@
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.captcha;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.capja.SimpleCaptchaGenerator;
|
||||
import xyz.jonesdev.capja.filter.SimpleRippleFilter;
|
||||
import xyz.jonesdev.capja.filter.SimpleScratchFilter;
|
||||
import xyz.jonesdev.capja.libs.jhlabs.image.AbstractBufferedImageOp;
|
||||
import xyz.jonesdev.capja.libs.jhlabs.image.BumpFilter;
|
||||
import xyz.jonesdev.capja.libs.jhlabs.image.SmearFilter;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public final class CaptchaPreparer {
|
||||
private static final ExecutorService PREPARATION_SERVICE = Executors.newSingleThreadExecutor();
|
||||
private static final Random RANDOM = new Random();
|
||||
@UtilityClass
|
||||
public class CaptchaPreparer {
|
||||
private final ExecutorService PREPARATION_SERVICE = Executors.newSingleThreadExecutor();
|
||||
private final Random RANDOM = new Random();
|
||||
|
||||
private MapCaptchaInfo[] cached;
|
||||
private int preparedAmount;
|
||||
|
||||
public void prepare() {
|
||||
// Make sure we're not preparing when Sonar is already preparing answers
|
||||
if (cached != null && preparedAmount != cached.length) return;
|
||||
preparedAmount = 0;
|
||||
if (cached != null && preparedAmount != cached.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SystemTimer timer = new SystemTimer();
|
||||
Sonar.get().getLogger().info("Asynchronously preparing CAPTCHA answers...");
|
||||
Sonar.get().getLogger().info("Players will be able to join even if the preparation isn't finished");
|
||||
|
||||
// Prepare cache
|
||||
final SonarConfiguration.Verification.Map config = Sonar.get().getConfig().getVerification().getMap();
|
||||
cached = new MapCaptchaInfo[config.getPrecomputeAmount()];
|
||||
final int precomputeAmount = Sonar.get().getConfig().getVerification().getMap().getPrecomputeAmount();
|
||||
final char[] alphabet = Sonar.get().getConfig().getVerification().getMap().getAlphabet().toCharArray();
|
||||
|
||||
preparedAmount = 0;
|
||||
cached = new MapCaptchaInfo[precomputeAmount];
|
||||
|
||||
// Prepare everything asynchronously
|
||||
PREPARATION_SERVICE.execute(() -> {
|
||||
// Create the images using capja
|
||||
final @Nullable File backgroundImage = Sonar.get().getConfig().getVerification().getMap().getBackgroundImage();
|
||||
final SimpleCaptchaGenerator generator = new SimpleCaptchaGenerator(128, 128, backgroundImage);
|
||||
final char[] dictionary = config.getDictionary().toCharArray();
|
||||
|
||||
// Prepare the filters for the CAPTCHA
|
||||
final List<AbstractBufferedImageOp> filters = new ArrayList<>();
|
||||
if (Sonar.get().getConfig().getVerification().getMap().isScratches()) {
|
||||
filters.add(new SimpleScratchFilter(5));
|
||||
}
|
||||
if (Sonar.get().getConfig().getVerification().getMap().isRipple()) {
|
||||
final SimpleRippleFilter rippleFilter = new SimpleRippleFilter();
|
||||
rippleFilter.setXAmplitude(0);
|
||||
float yAmplitude = 10 - ThreadLocalRandom.current().nextInt(20);
|
||||
if (Math.abs(yAmplitude) < 3) {
|
||||
yAmplitude = yAmplitude >= 0 ? 3 : -3;
|
||||
}
|
||||
rippleFilter.setYAmplitude(yAmplitude);
|
||||
filters.add(rippleFilter);
|
||||
}
|
||||
if (Sonar.get().getConfig().getVerification().getMap().getDistortion().isEnabled()) {
|
||||
final SmearFilter smearFilter = new SmearFilter();
|
||||
smearFilter.setShape(Sonar.get().getConfig().getVerification().getMap().getDistortion().getShape());
|
||||
smearFilter.setMix(Sonar.get().getConfig().getVerification().getMap().getDistortion().getMix());
|
||||
smearFilter.setDensity(Sonar.get().getConfig().getVerification().getMap().getDistortion().getDensity());
|
||||
smearFilter.setDistance(Sonar.get().getConfig().getVerification().getMap().getDistortion().getDistance());
|
||||
filters.add(smearFilter);
|
||||
}
|
||||
if (Sonar.get().getConfig().getVerification().getMap().isBump()) {
|
||||
filters.add(new BumpFilter());
|
||||
}
|
||||
|
||||
for (preparedAmount = 0; preparedAmount < config.getPrecomputeAmount(); preparedAmount++) {
|
||||
if (!Sonar.get().getConfig().getVerification().getMap().isAutoColor()) {
|
||||
// Generate a random gradient color if automatic coloring is disabled
|
||||
final Color color0 = Color.getHSBColor((float) Math.random(), 1, 1);
|
||||
final Color color1 = Color.getHSBColor((float) Math.random(), 1, 0.5f);
|
||||
final GradientPaint gradient = new GradientPaint(0, 0, color0,
|
||||
generator.getWidth(), generator.getHeight(), color1);
|
||||
generator.setGradient(gradient);
|
||||
}
|
||||
for (preparedAmount = 0; preparedAmount < precomputeAmount;) {
|
||||
// Generate CAPTCHA answer
|
||||
final char[] answer = new char[5];
|
||||
final char[] answer = new char[3 + RANDOM.nextInt(2)];
|
||||
for (int j = 0; j < answer.length; j++) {
|
||||
answer[j] = dictionary[ThreadLocalRandom.current().nextInt(dictionary.length)];
|
||||
answer[j] = alphabet[RANDOM.nextInt(alphabet.length)];
|
||||
}
|
||||
final BufferedImage image = generator.createImage(answer, filters);
|
||||
final BufferedImage image = Sonar.get().getFallback().getCaptchaGenerator().createImage(answer);
|
||||
// Convert and cache converted Minecraft map bytes
|
||||
final int[] buffer = MapColorPalette.getBufferFromImage(image);
|
||||
cached[preparedAmount] = new MapCaptchaInfo(new String(answer), buffer);
|
||||
cached[preparedAmount++] = new MapCaptchaInfo(new String(answer), buffer);
|
||||
}
|
||||
|
||||
Sonar.get().getLogger().info("Finished preparing {} CAPTCHA answers ({}s)!", preparedAmount, timer);
|
||||
@@ -117,7 +70,7 @@ public final class CaptchaPreparer {
|
||||
}
|
||||
|
||||
public boolean isCaptchaAvailable() {
|
||||
return cached != null && cached[0] != null;
|
||||
return preparedAmount > 0 && cached != null && cached[0] != null;
|
||||
}
|
||||
|
||||
public MapCaptchaInfo getRandomCaptcha() {
|
||||
|
||||
+4
-4
@@ -26,10 +26,10 @@ import java.awt.image.BufferedImage;
|
||||
public class MapColorPalette {
|
||||
// https://minecraft.wiki/w/Map_item_format#Full_color_tables
|
||||
public static final int[] COLOR_MAP = {
|
||||
-16777216,
|
||||
-16777216,
|
||||
-16777216,
|
||||
-16777216,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-10912473,
|
||||
-9594576,
|
||||
-8408520,
|
||||
|
||||
+5
-4
@@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.CaptchaPreparer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.ItemType;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.MapCaptchaInfo;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
@@ -48,7 +49,7 @@ public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler
|
||||
super(user, username);
|
||||
|
||||
// Disconnect the player if there is no CAPTCHA available at the moment
|
||||
if (!MAP_INFO_PREPARER.isCaptchaAvailable()) {
|
||||
if (!CaptchaPreparer.isCaptchaAvailable()) {
|
||||
user.disconnect(Sonar.get().getConfig().getVerification().getCurrentlyPreparing());
|
||||
throw new CorruptedFrameException("No captcha available");
|
||||
}
|
||||
@@ -60,8 +61,8 @@ public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler
|
||||
user.delayedWrite(new SetContainerSlotPacket(user.isGeyser() ? 36 : 40, 1,
|
||||
ItemType.FILLED_MAP.getId(user.getProtocolVersion()), SetContainerSlotPacket.MAP_NBT));
|
||||
// Send random captcha to the player
|
||||
final MapCaptchaInfo captcha = MAP_INFO_PREPARER.getRandomCaptcha();
|
||||
this.answer = captcha.getAnswer();
|
||||
final MapCaptchaInfo captcha = CaptchaPreparer.getRandomCaptcha();
|
||||
this.answer = captcha.getAnswer().toLowerCase();
|
||||
captcha.delayedWrite(user);
|
||||
// Teleport the player to the position above the platform
|
||||
user.delayedWrite(CAPTCHA_POSITION);
|
||||
@@ -85,7 +86,7 @@ public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler
|
||||
if (packet instanceof SystemChatPacket) {
|
||||
final SystemChatPacket chat = (SystemChatPacket) packet;
|
||||
// Finish the verification if the player entered the correct code
|
||||
if (chat.getMessage().equals(answer)) {
|
||||
if (chat.getMessage().toLowerCase().equals(answer)) {
|
||||
finishVerification();
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
}
|
||||
|
||||
// Send the player to the protocol check
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
decoder.setListener(new FallbackProtocolSessionHandler(user, username, forceCAPTCHA));
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -94,7 +94,7 @@ public final class FallbackLoginSessionHandler extends FallbackSessionHandler {
|
||||
updateEncoderDecoderState(FallbackPacketRegistry.GAME);
|
||||
// Pass the player to the next verification handler
|
||||
final FallbackGravitySessionHandler gravitySessionHandler = new FallbackGravitySessionHandler(user, username);
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
decoder.setListener(gravitySessionHandler);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ public final class FallbackLoginSessionHandler extends FallbackSessionHandler {
|
||||
}
|
||||
|
||||
private void updateEncoderDecoderState(final @NotNull FallbackPacketRegistry registry) {
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var encoder = (FallbackPacketEncoder) user.getPipeline().get(FallbackPacketEncoder.class);
|
||||
// Update the packet registry state in the encoder and decoder pipelines
|
||||
decoder.updateRegistry(registry);
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ public final class FallbackProtocolSessionHandler extends FallbackSessionHandler
|
||||
private void markSuccess() {
|
||||
// Either send the player to the vehicle check,
|
||||
// send the player to the CAPTCHA, or finish the verification.
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
// Pass the player to the next best verification handler
|
||||
if (Sonar.get().getConfig().getVerification().getVehicle().isEnabled()) {
|
||||
decoder.setListener(new FallbackVehicleSessionHandler(user, username, forceCAPTCHA));
|
||||
|
||||
+6
-3
@@ -23,6 +23,8 @@ import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifySuccessEvent;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketListener;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.ClientInformationPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.PluginMessagePacket;
|
||||
@@ -48,13 +50,11 @@ public abstract class FallbackSessionHandler implements FallbackPacketListener {
|
||||
protected void checkState(final boolean state, final String failReason) {
|
||||
// Fails the verification if the condition is not met
|
||||
if (!state) {
|
||||
// Let the API know that the user has failed the verification
|
||||
user.fail(failReason);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void finishVerification() {
|
||||
// Increment number of total successful verifications
|
||||
GlobalSonarStatistics.totalSuccessfulVerifications++;
|
||||
|
||||
// Add verified player to the database
|
||||
@@ -69,9 +69,12 @@ public abstract class FallbackSessionHandler implements FallbackPacketListener {
|
||||
// This feature was introduced by Mojang in Minecraft version 1.20.5.
|
||||
if (transferToOrigin != null
|
||||
&& user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0) {
|
||||
// Send the transfer packet to the player and close the channel
|
||||
// Send the transfer packet to the player (and close the channel if on Java Edition)
|
||||
if (user.isGeyser()) {
|
||||
user.write(transferToOrigin);
|
||||
// Make sure we cannot receive any more packets from the player
|
||||
user.getPipeline().remove(FallbackPacketDecoder.class);
|
||||
user.getPipeline().remove(FallbackPacketEncoder.class);
|
||||
} else {
|
||||
closeWith(user.getChannel(), user.getProtocolVersion(), transferToOrigin);
|
||||
}
|
||||
|
||||
+33
-20
@@ -73,7 +73,7 @@ public final class FallbackVehicleSessionHandler extends FallbackSessionHandler
|
||||
// Pass the player to the next best verification handler
|
||||
if (forceCAPTCHA || Sonar.get().getFallback().shouldPerformCaptcha()) {
|
||||
// Either send the player to the CAPTCHA or finish the verification.
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
final var decoder = user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
// Send the player to the CAPTCHA handler
|
||||
decoder.setListener(new FallbackCAPTCHASessionHandler(user, username));
|
||||
} else {
|
||||
@@ -83,7 +83,7 @@ public final class FallbackVehicleSessionHandler extends FallbackSessionHandler
|
||||
}
|
||||
|
||||
// We can abuse the entity remove mechanic and check for position packets when the entity dies
|
||||
private void move(double y) {
|
||||
private void handleMovement(double y) {
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) < 0) {
|
||||
y -= 1.62f; // Account for 1.7 bounding box
|
||||
}
|
||||
@@ -95,33 +95,37 @@ public final class FallbackVehicleSessionHandler extends FallbackSessionHandler
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRotation() {
|
||||
// Once the player sent enough packets, go to the next stage
|
||||
final int minimumPackets = Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets();
|
||||
if (paddlePackets > minimumPackets
|
||||
&& inputPackets > minimumPackets
|
||||
&& rotationPackets > minimumPackets) {
|
||||
// Remove the entity
|
||||
// The next y coordinate the player will send is going
|
||||
// to be the vehicle spawn position (y 64 in this case).
|
||||
user.write(removeEntities);
|
||||
// Listen for next movement packet(s)
|
||||
expectMovement = true;
|
||||
}
|
||||
rotationPackets++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(final @NotNull FallbackPacket packet) {
|
||||
if (packet instanceof SetPlayerPositionRotationPacket) {
|
||||
if (expectMovement) {
|
||||
final SetPlayerPositionRotationPacket posRot = (SetPlayerPositionRotationPacket) packet;
|
||||
move(posRot.getY());
|
||||
handleMovement(posRot.getY());
|
||||
}
|
||||
} else if (packet instanceof SetPlayerPositionPacket) {
|
||||
if (expectMovement) {
|
||||
final SetPlayerPositionPacket position = (SetPlayerPositionPacket) packet;
|
||||
move(position.getY());
|
||||
handleMovement(position.getY());
|
||||
}
|
||||
} else if (packet instanceof SetPlayerRotationPacket) {
|
||||
if (!expectMovement) {
|
||||
// Once the player sent enough packets, go to the next stage
|
||||
final int minimumPackets = Sonar.get().getConfig().getVerification().getVehicle().getMinimumPackets();
|
||||
if (paddlePackets > minimumPackets
|
||||
&& inputPackets > minimumPackets
|
||||
&& rotationPackets > minimumPackets) {
|
||||
// Remove the entity
|
||||
// The next y coordinate the player will send is going
|
||||
// to be the vehicle spawn position (y 64 in this case).
|
||||
user.write(removeEntities);
|
||||
// Listen for next movement packet(s)
|
||||
expectMovement = true;
|
||||
}
|
||||
rotationPackets++;
|
||||
if (!expectMovement && !user.isGeyser()) {
|
||||
handleRotation();
|
||||
}
|
||||
} else if (packet instanceof PaddleBoatPacket) {
|
||||
paddlePackets++;
|
||||
@@ -129,14 +133,23 @@ public final class FallbackVehicleSessionHandler extends FallbackSessionHandler
|
||||
final PlayerInputPacket playerInput = (PlayerInputPacket) packet;
|
||||
|
||||
// Check if the player is sending invalid vehicle speed values
|
||||
checkState(Math.abs(playerInput.getForward()) <= 0.98f, "illegal vehicle speed (f)");
|
||||
checkState(Math.abs(playerInput.getSideways()) <= 0.98f, "illegal vehicle speed (s)");
|
||||
final float forward = Math.abs(playerInput.getForward());
|
||||
final float sideways = Math.abs(playerInput.getSideways());
|
||||
final float maxVehicleSpeed = user.isGeyser() ? 1 : 0.98f;
|
||||
checkState(forward <= maxVehicleSpeed, "illegal speed (f): " + forward);
|
||||
checkState(sideways <= maxVehicleSpeed, "illegal speed (s): " + sideways);
|
||||
|
||||
// Only mark this packet as correct if the player is not moving the vehicle
|
||||
if (playerInput.isJump() || playerInput.isUnmount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bedrock users do not send SetPlayerPositionRotation and SetPlayerRotation packets
|
||||
// Don't ask me why; Microsoft is doing some *fascinating* things with Bedrock...
|
||||
if (user.isGeyser()) {
|
||||
handleRotation();
|
||||
}
|
||||
|
||||
// 1.8 and below do not have PaddleBoat packets,
|
||||
// so we simply exempt them from the PaddleBoat check.
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_9) < 0) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.util;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
// https://github.com/retrooper/packetevents/blob/2.0/api/src/main/java/com/github/retrooper/packetevents/util/FakeChannelUtil.java
|
||||
@UtilityClass
|
||||
public class FakeChannelUtil {
|
||||
|
||||
/**
|
||||
* @param channel Channel of the player
|
||||
* @return Whether the channel is spoofed/faked
|
||||
*/
|
||||
public boolean isFakePlayer(final @NotNull Channel channel) {
|
||||
final String simpleClassName = channel.getClass().getSimpleName();
|
||||
// Player spoof plugins use fake channels (e.g., Spoof Engine uses "FakeChannel")
|
||||
return "FakeChannel".equals(simpleClassName) || "SpoofedChannel".equals(simpleClassName);
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* Simple utility to determine if someone joins using GeyserMC
|
||||
*/
|
||||
@UtilityClass
|
||||
public class GeyserUtil {
|
||||
// https://github.com/GeyserMC/Floodgate/blob/master/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java#L206
|
||||
|
||||
@@ -110,40 +110,10 @@ verification:
|
||||
# - DURING_ATTACK: Noví hráči dostanou map captchu jenom při útoku
|
||||
# - NEVER: Noví hráči nikdy nedostanou map CAPTCHU (Doporučeno)
|
||||
timing: NEVER
|
||||
# Možnosti pro efekty na zobrazeném obrázku
|
||||
# Toto byste měli upravovat, pouze pokud víte, co děláte
|
||||
effects:
|
||||
# Přidá do obrázku náhodné čáry
|
||||
scratches: true
|
||||
# Přidá filtr zvlnění (vlnitý efekt)
|
||||
ripple: true
|
||||
# Přidá nárazový filtr (zaostřený text)
|
||||
bump: true
|
||||
# Filtr zkreslení
|
||||
distortion:
|
||||
# Měl by Sonar použít filtr zkreslení?
|
||||
enabled: true
|
||||
# Jaký tvar by měl Sonar použít pro filtr zkreslení?
|
||||
# Typy: 0, 1, 2, 3
|
||||
# - 0: Kříže
|
||||
# - 1: Čáry
|
||||
# - 2: Kruhy
|
||||
# - 3: Čtverce
|
||||
shape: 2
|
||||
# Vzdálenost mezi zkreslenými pixely/objekty
|
||||
distance: 5
|
||||
# Hustota zkreslených pixelů/objektů
|
||||
density: 0.1
|
||||
# Míra prolnutí mezi barvami objektů
|
||||
mix: 0.15
|
||||
# Cesta k obrázku na pozadí, který Sonar používá pro CAPTCHA
|
||||
# Pokud není obrázek nalezen, bude jako výchozí pozadí použit šum
|
||||
# Zde můžete jednoduše zadat název souboru, pokud je soubor v adresáři pluginu
|
||||
background: ''
|
||||
# Měl by Sonar automaticky detekovat barvu pozadí?
|
||||
# To se provádí za účelem vytvoření kontrastu mezi obrázkem na pozadí a textem
|
||||
# Pokud je toto zakázáno, Sonar použije pro barvy náhodný přechod
|
||||
auto-color: false
|
||||
# Kolik odpovědí by měl Sonar předgenerovat?
|
||||
# Tento úkol běží souběžně v pozadí
|
||||
precompute: 500
|
||||
@@ -154,7 +124,7 @@ verification:
|
||||
max-tries: 3
|
||||
# Znaky (čísla a písmena), které jsou povoleny k zobrazování v CAPTCHE
|
||||
# Nedoporučuje se přidávat čísla nebo písmena, která by se mohla navzájem podobat
|
||||
dictionary: '1235689'
|
||||
alphabet: 'abcdefhjkmnoprstuxyz'
|
||||
|
||||
# Zkouší pokuď hráč posílá správný "client brand packet" serveru
|
||||
client-brand:
|
||||
|
||||
@@ -107,40 +107,10 @@ verification:
|
||||
# - DURING_ATTACK: Neue Spieler erhalten nur während eines Angriffs ein CAPTCHA
|
||||
# - NEVER: Neue Spieler erhalten nie ein CAPTCHA (Empfohlen)
|
||||
timing: NEVER
|
||||
# Optionen für die Effekte auf dem angezeigten Bild
|
||||
# Sie sollten dies nur bearbeiten, wenn Sie wissen, was Sie tun
|
||||
effects:
|
||||
# Fügt dem Bild zufällige Linien hinzu
|
||||
scratches: true
|
||||
# Wendet einen Ripple-Filter an (Welleneffekt)
|
||||
ripple: true
|
||||
# Wendet einen Bump-Filter an (geschärfter Text)
|
||||
bump: true
|
||||
# Verzerrungsfilter
|
||||
distortion:
|
||||
# Sollte Sonar einen Verzerrungsfilter anwenden?
|
||||
enabled: true
|
||||
# Welche Form sollte Sonar für den Verzerrungsfilter verwenden?
|
||||
# Mögliche Typen: 0, 1, 2, 3
|
||||
# - 0: Kreuze
|
||||
# - 1: Linien
|
||||
# - 2: Kreise
|
||||
# - 3: Quadrate
|
||||
shape: 2
|
||||
# Abstand zwischen verzerrten Pixeln/Objekten
|
||||
distance: 5
|
||||
# Dichte der verzerrten Pixel/Objekte
|
||||
density: 0.1
|
||||
# Ausmaß der Vermischung zwischen den Farben von Objekten
|
||||
mix: 0.15
|
||||
# Pfad zum Hintergrundbild, das Sonar für das CAPTCHA verwendet
|
||||
# Wenn das Bild nicht gefunden wird, wird ein Rauschen als Standardhintergrund verwendet
|
||||
# Sie können hier einfach den Namen der Datei eingeben, wenn sich die Datei im Plugin-Verzeichnis befindet
|
||||
background: ''
|
||||
# Sollte Sonar die Hintergrundfarbe automatisch erkennen?
|
||||
# Dies geschieht, um einen Kontrast zwischen dem Hintergrundbild und dem Text zu erzeugen
|
||||
# Wenn dies deaktiviert ist, verwendet Sonar einen zufälligen Farbverlauf für die Farben
|
||||
auto-color: false
|
||||
# Wie viele Antworten soll Sonar vorbereiten?
|
||||
# Dieser Vorgang erfolgt asynchron im Hintergrund
|
||||
precompute: 500
|
||||
@@ -151,7 +121,7 @@ verification:
|
||||
max-tries: 3
|
||||
# Zeichen (Buchstaben und Zahlen), die im Antwort-CAPTCHA erscheinen dürfen
|
||||
# Es wird nicht empfohlen, Zahlen oder Buchstaben hinzuzufügen, die einander ähneln könnten
|
||||
dictionary: '1235689'
|
||||
alphabet: 'abcdefhjkmnoprstuxyz'
|
||||
|
||||
# Überprüft, ob die Spieler eine gültige Client-Marke an den Server senden
|
||||
client-brand:
|
||||
|
||||
@@ -109,41 +109,11 @@ verification:
|
||||
# - DURING_ATTACK: New players will only receive a CAPTCHA during an attack
|
||||
# - NEVER: New players will never receive a CAPTCHA (Recommended)
|
||||
timing: NEVER
|
||||
# Options for the effects on the image shown to the player
|
||||
# You should only edit this if you know what you're doing
|
||||
effects:
|
||||
# Adds random lines to the image
|
||||
scratches: true
|
||||
# Applies a ripple filter (wavy effect)
|
||||
ripple: true
|
||||
# Applies a bump filter (sharpened text)
|
||||
bump: true
|
||||
# Distortion filter
|
||||
distortion:
|
||||
# Should Sonar apply a distortion filter?
|
||||
enabled: true
|
||||
# What shape should Sonar use for the distortion filter?
|
||||
# Possible types: 0, 1, 2, 3
|
||||
# - 0: Crosses
|
||||
# - 1: Lines
|
||||
# - 2: Circles
|
||||
# - 3: Squares
|
||||
shape: 2
|
||||
# Distance between distorted pixels/objects
|
||||
distance: 5
|
||||
# Density of the distorted pixels/objects
|
||||
density: 0.1
|
||||
# Amount of blending between the colors of objects
|
||||
mix: 0.15
|
||||
# Path to the background image Sonar uses for the CAPTCHA
|
||||
# If the image isn't found, a noise will be used as the default background
|
||||
# You can put the name of the file here if the file is in the plugin directory
|
||||
background: ''
|
||||
# Should Sonar automatically detect the background color?
|
||||
# This is done to create a contrast between the background image and the text
|
||||
# If this is disabled, Sonar will use a random gradient for the colors
|
||||
auto-color: false
|
||||
# How many answers should Sonar prepare?
|
||||
# How many CAPTCHA answers should Sonar prepare?
|
||||
# This task happens asynchronously in the background
|
||||
precompute: 500
|
||||
# How long should Sonar wait until the player fails the CAPTCHA?
|
||||
@@ -151,9 +121,10 @@ verification:
|
||||
max-duration: 30000
|
||||
# How many times must a player fail the CAPTCHA before failing the verification?
|
||||
max-tries: 3
|
||||
# Characters (letters and numbers) that are allowed to appear in the answer to the CAPTCHA
|
||||
# It is not recommended to add numbers or letters that might look like one another
|
||||
dictionary: '1235689'
|
||||
# Characters (letters/numbers) that are allowed to appear in the answer to the CAPTCHA
|
||||
# It is not recommended to add numbers or letters that might look like one another.
|
||||
# You should only edit this if you know what you're doing
|
||||
alphabet: 'abcdefhjkmnoprstuxyz'
|
||||
|
||||
# Checks if the players are sending a valid client brand to the server
|
||||
client-brand:
|
||||
|
||||
@@ -109,40 +109,10 @@ verification:
|
||||
# - DURING_ATTACK : Les nouveaux joueurs ne recevront un CAPTCHA que pendant une attaque
|
||||
# - NEVER : Les nouveaux joueurs ne recevront jamais de CAPTCHA (Recommandé)
|
||||
timing: NEVER
|
||||
# Options pour les effets sur l'image montrée au joueur
|
||||
# Vous ne devriez modifier cela que si vous savez ce que vous faites
|
||||
effects:
|
||||
# Ajoute des lignes aléatoires à l'image
|
||||
scratches: true
|
||||
# Applique un filtre d'ondulation (effet ondulé)
|
||||
ripple: true
|
||||
# Applique un filtre de bosse (texte accentué)
|
||||
bump: true
|
||||
# Filtre de distorsion
|
||||
distortion:
|
||||
# Sonar devrait-il appliquer un filtre de distorsion ?
|
||||
enabled: true
|
||||
# Quelle forme Sonar devrait-il utiliser pour le filtre de distorsion ?
|
||||
# Types possibles : 0, 1, 2, 3
|
||||
# - 0 : Croix
|
||||
# - 1 : Lignes
|
||||
# - 2 : Cercles
|
||||
# - 3 : Carrés
|
||||
shape: 2
|
||||
# Distance entre les pixels/objets déformés
|
||||
distance: 5
|
||||
# Densité des pixels/objets déformés
|
||||
density: 0.1
|
||||
# Quantité de mélange entre les couleurs des objets
|
||||
mix: 0.15
|
||||
# Chemin vers l'image d'arrière-plan que Sonar utilise pour le CAPTCHA
|
||||
# Si l'image n'est pas trouvée, un bruit sera utilisé comme arrière-plan par défaut
|
||||
# Vous pouvez simplement mettre le nom du fichier ici si le fichier est dans le répertoire du plugin
|
||||
background: ''
|
||||
# Sonar devrait-il détecter automatiquement la couleur de fond ?
|
||||
# Cela est fait pour créer un contraste entre l'image d'arrière-plan et le texte
|
||||
# Si cela est désactivé, Sonar utilisera un dégradé aléatoire pour les couleurs
|
||||
auto-color: false
|
||||
# Combien de réponses Sonar devrait-il préparer ?
|
||||
# Cette tâche se déroule de manière asynchrone en arrière-plan
|
||||
precompute: 500
|
||||
@@ -153,7 +123,7 @@ verification:
|
||||
max-tries: 3
|
||||
# Caractères (lettres et chiffres) autorisés à apparaître dans la réponse au CAPTCHA
|
||||
# Il n'est pas recommandé d'ajouter des chiffres ou des lettres qui pourraient se ressembler
|
||||
dictionary: '1235689'
|
||||
alphabet: 'abcdefhjkmnoprstuxyz'
|
||||
|
||||
# Vérifie si le joueur envoie une marque client valide au serveur
|
||||
client-brand:
|
||||
|
||||
@@ -109,40 +109,10 @@ verification:
|
||||
# - DURING_ATTACK (შეტევის დროს): ახალი მოთამაშებს მოუწევთ CAPTCHA-ს გაკეთება მხოლოდ შეტევის დროს.
|
||||
# - NEVER (არასდროს): ახალ მოთამაშეებს არასდროს მოუწევთ CAPTCHA-ს გაკეთება (Recommended).
|
||||
timing: NEVER
|
||||
# ეფექტების პარამეტრები CAPTCHA გამოსახულებაზე, რომელსაც ვანახებთ მოთამაშეს
|
||||
# თქვენ უნდა შეცვალოთ ეს მხოლოდ იმ შემთხვევაში, თუ იცით, რას აკეთებთ
|
||||
effects:
|
||||
# მატებს შემთხვევითი ხაზების ეფექტს გამოსახულებაზე
|
||||
scratches: true
|
||||
# აყენებს ტალღური ეფექტის ფილტრს (ტალღისებური ეფექტი)
|
||||
ripple: true
|
||||
# აყენებს ამობურცული ტექსტის ფილტრს (გამხრწნილი ტექსტი)
|
||||
bump: true
|
||||
# დამახინჯების ფილტრი
|
||||
distortion:
|
||||
# უნდა დაამატოს თუ არა Sonar-მა დამახინჯების ფილტრი?
|
||||
enabled: true
|
||||
# რა ფორმას უნდა გამოიყენოს Sonar-მა დამახინჯების ფილტრისთვის?
|
||||
# შესაძლო ტიპები: 0, 1, 2, 3
|
||||
# - 0: ჯვრები
|
||||
# - 1: ხაზები
|
||||
# - 2: წრეები
|
||||
# - 3: კვადრატები
|
||||
shape: 2
|
||||
# მანძილი არეულ პიქსელებს/ობიექტებს შორის
|
||||
distance: 5
|
||||
# არეული პიქსელების/ობიექტების სიხშირე
|
||||
density: 0.1
|
||||
# შერევის ხარისხი ობიექტების ფერებს შორის
|
||||
mix: 0.15
|
||||
# CAPTCHA-სთვის Sonar-ის მიერ გამოყენებული ფონური გამოსახულების ბილიკი
|
||||
# თუ გამოსახულება არ მოიძებნა, ფონად გამოყენებული იქნება noise ი
|
||||
# შეგიძლიათ უბრალოდ ჩაწეროთ ფაილის სახელი, თუ ის პლაგინის დირექტორიაშია
|
||||
background: ''
|
||||
# უნდა განსაზღვროს თუ არა Sonar-მა ავტომატურად ფონის ფერი?
|
||||
# ეს კეთდება იმისთვის, რომ შეიქმნას კონტრასტი ფონის გამოსახულებასა და ტექსტს შორის
|
||||
# თუ ეს გამორთულია, Sonar-ი გამოიყენებს შემთხვევით გრადიენტს ფერებისთვის
|
||||
auto-color: false
|
||||
# რამდენი პასუხი უნდა მოამზადოს Sonar-მა?
|
||||
# ეს ხდება ასინქრონულად (ანუ ამის გამოთვლას სერვერი არ დაელოდება) უკანა ფონზე
|
||||
precompute: 500
|
||||
@@ -153,7 +123,7 @@ verification:
|
||||
max-tries: 3
|
||||
# სიმბოლოები (ასოები და რიცხვები), რომლებიც დაშვებულია გამოჩნდეს CAPTCHA-ს პასუხში
|
||||
# არ არის რეკომენდებული რიცხვების ან ასოების დამატება, რომლებიც შეიძლება ერთმანეთს ჰგავდეს
|
||||
dictionary: '1235689'
|
||||
alphabet: 'abcdefhjkmnoprstuxyz'
|
||||
|
||||
# ამოწმებს, აგზავნიან თუ არა მოთამაშეები სერვერზე რეალურ მოთამაშის მეტსახელს
|
||||
client-brand:
|
||||
|
||||
@@ -109,40 +109,10 @@ verification:
|
||||
# - DURING_ATTACK: Nieuwe spelers ontvangen alleen een CAPTCHA tijdens een aanval
|
||||
# - NEVER: Nieuwe spelers zullen nooit een CAPTCHA ontvangen (Aanbevolen)
|
||||
timing: NEVER
|
||||
# Opties voor de effecten op de afbeelding die aan de speler wordt getoond
|
||||
# Je moet dit alleen bewerken als je weet wat je doet
|
||||
effects:
|
||||
# Voegt willekeurige lijnen toe aan de afbeelding
|
||||
scratches: true
|
||||
# Past een ripple-filter toe (golfeffect)
|
||||
ripple: true
|
||||
# Past een bump-filter toe (verscherpte tekst)
|
||||
bump: true
|
||||
# Vervormingsfilter
|
||||
distortion:
|
||||
# Moet Sonar een vervormingsfilter toepassen?
|
||||
enabled: true
|
||||
# Welke vorm moet Sonar gebruiken voor het vervormingsfilter?
|
||||
# Mogelijke typen: 0, 1, 2, 3
|
||||
# - 0: Kruisen
|
||||
# - 1: Lijnen
|
||||
# - 2: Cirkels
|
||||
# - 3: Vierkantjes
|
||||
shape: 2
|
||||
# Afstand tussen vervormde pixels/objecten
|
||||
distance: 5
|
||||
# Dichtheid van de vervormde pixels/objecten
|
||||
density: 0.1
|
||||
# Hoeveelheid overvloeiing tussen de kleuren van objecten
|
||||
mix: 0.15
|
||||
# Pad naar de achtergrondafbeelding die Sonar gebruikt voor de CAPTCHA
|
||||
# Als de afbeelding niet wordt gevonden, wordt er een ruis gebruikt als standaardachtergrond
|
||||
# U kunt eenvoudig de naam van het bestand hier plaatsen als het bestand zich in de plugin map bevindt
|
||||
background: ''
|
||||
# Moet Sonar automatisch de achtergrondkleur detecteren?
|
||||
# Dit wordt gedaan om een contrast te creëren tussen de achtergrondafbeelding en de tekst
|
||||
# Als dit is uitgeschakeld, gebruikt Sonar een willekeurig verloop voor de kleuren
|
||||
auto-color: false
|
||||
# Hoeveel antwoorden moet Sonar voorbereiden?
|
||||
# Deze taak gebeurt asynchroon op de achtergrond
|
||||
precompute: 500
|
||||
@@ -153,7 +123,7 @@ verification:
|
||||
max-tries: 3
|
||||
# Tekens (letters en cijfers) die mogen voorkomen in het antwoord op de CAPTCHA
|
||||
# Het wordt niet aanbevolen om cijfers of letters toe te voegen die op elkaar lijken
|
||||
dictionary: '1235689'
|
||||
alphabet: 'abcdefhjkmnoprstuxyz'
|
||||
|
||||
# Controleert of de speler een geldig Minecraft-klantmerk naar de server verzendt
|
||||
client-brand:
|
||||
|
||||
@@ -109,40 +109,10 @@ verification:
|
||||
# - DURING_ATTACK: Nowi gracze otrzymają weryfikacje CAPTCHA tylko podczas ataku.
|
||||
# - NEVER: Nowi gracze nigdy nie otrzymają weryfikacji CAPTCHA (zalecane)
|
||||
timing: NEVER
|
||||
# Opcje efektów na mapie wyświetlanej graczowi
|
||||
# Powinieneś to edytować tylko wtedy, gdy wiesz, co robisz
|
||||
effects:
|
||||
# Dodawanie losowych linii do obrazu
|
||||
scratches: true
|
||||
# Stosuje efekt falowania
|
||||
ripple: true
|
||||
# Stosuje filtr nierówności (wyostrzony tekst)
|
||||
bump: true
|
||||
# Filtr zniekształceń
|
||||
distortion:
|
||||
# Czy Sonar powinien stosować filtr zniekształceń?
|
||||
enabled: true
|
||||
# Jakiego kształtu filtra zniekształceń powinien używać Sonar?
|
||||
# Dostępne rodzaje: 0, 1, 2, 3
|
||||
# - 0: Krzyże
|
||||
# - 1: Linie
|
||||
# - 2: Kółka
|
||||
# - 3: Kwadraty
|
||||
shape: 2
|
||||
# Odległość między zniekształconymi pikselami/obiektami
|
||||
distance: 5
|
||||
# Gęstość zniekształconych pikseli/obiektów
|
||||
density: 0.1
|
||||
# Stopień mieszania się kolorów obiektów
|
||||
mix: 0.15
|
||||
# Ścieżka do obrazu tła używanego przez Sonar dla CAPTCHA
|
||||
# Jeśli obraz nie zostanie znaleziony, jako domyślne tło zostanie użyty szum
|
||||
# Możesz umieścić nazwę pliku tutaj, jeśli plik znajduje się w katalogu pluginu
|
||||
background: ''
|
||||
# Czy Sonar powinien automatycznie wykrywać kolor tła?
|
||||
# Ma to na celu stworzenie kontrastu między obrazem tła a tekstem
|
||||
# Jeśli ta opcja jest wyłączona, Sonar użyje losowego gradientu dla kolorów
|
||||
auto-color: false
|
||||
# Ile możliwych odpowiedzi powinien przygotować Sonar?
|
||||
# Zadanie to wykonywane jest asynchronicznie w tle
|
||||
precompute: 500
|
||||
@@ -153,7 +123,7 @@ verification:
|
||||
max-tries: 3
|
||||
# Znaki (litery i cyfry), które mogą pojawić się w CAPTCHA
|
||||
# Nie zaleca się dodawania cyfr lub liter, które mogą być do siebie podobne
|
||||
dictionary: '1235689'
|
||||
alphabet: 'abcdefhjkmnoprstuxyz'
|
||||
|
||||
# Sprawdza, czy gracz wysyła prawidłowy client brand do serwera.
|
||||
client-brand:
|
||||
|
||||
@@ -109,39 +109,9 @@ verification:
|
||||
# - DURING_ATTACK: Новые игроки будут получать капчу только во время атаки
|
||||
# - NEVER: Новые игроки никогда не получат капчу (Рекомендуется)
|
||||
timing: NEVER
|
||||
# Кастомизация эффектов на картинке, которые показываются игроку
|
||||
# Рекомендуется редактировать только если вы знаете, что делаете
|
||||
effects:
|
||||
# Добавляет случайные линии на изображение
|
||||
scratches: true
|
||||
# Применяет эффект волны (ripple)
|
||||
ripple: true
|
||||
# Применяет фильтр bump (увеличивает резкость текста)
|
||||
bump: true
|
||||
# Фильтр искажения
|
||||
distortion:
|
||||
# Следует ли применять фильтр искажения?
|
||||
enabled: true
|
||||
# Какую форму следует использовать Sonar для фильтра искажения?
|
||||
# Возможные типы: 0, 1, 2, 3
|
||||
# - 0: Кресты
|
||||
# - 1: Линии
|
||||
# - 2: Круги
|
||||
# - 3: Квадраты
|
||||
shape: 2
|
||||
# Расстояние между искаженными пикселями/объектами
|
||||
distance: 5
|
||||
# Плотность искаженных пикселей/объектов
|
||||
density: 0.1
|
||||
# Степень смешивания между цветами объектов
|
||||
mix: 0.15
|
||||
# Путь к фоновому изображению, которое использует Sonar для CAPTCHA
|
||||
# Если изображение не найдено, будет использован шум как фон по умолчанию
|
||||
background: ''
|
||||
# Следует ли Sonar автоматически определять цвет фона?
|
||||
# Это делается для создания контраста между фоновым изображением и текстом
|
||||
# Просто укажите имя файла здесь, если файл находится в каталоге плагина
|
||||
auto-color: false
|
||||
# Сколько капч должен подготовить Sonar?
|
||||
# Эта задача выполняется асинхронно в фоновом режиме
|
||||
precompute: 500
|
||||
@@ -152,7 +122,7 @@ verification:
|
||||
max-tries: 3
|
||||
# Символы (буквы и цифры), которые разрешено использовать в ответе на капчу
|
||||
# Не рекомендуется добавлять цифры или буквы, которые могут быть похожи друг на друга
|
||||
dictionary: '1235689'
|
||||
alphabet: 'abcdefhjkmnoprstuxyz'
|
||||
|
||||
# Проверяет, отправляет ли игрок на сервер действительный клиентский бренд.
|
||||
client-brand:
|
||||
|
||||
@@ -107,40 +107,10 @@ verification:
|
||||
# - DURING_ATTACK: 仅在疑似遭受攻击时应用该检查
|
||||
# - NEVER: 永远不应用该检查
|
||||
timing: NEVER
|
||||
# 图像效果选项 (用于混淆自动图像识别)
|
||||
# 只有当您知道您在做什么时, 才应该编辑它.
|
||||
effects:
|
||||
# 是否向图像添加随机线条
|
||||
scratches: true
|
||||
# 是否应用纹波滤镜
|
||||
ripple: true
|
||||
# 是否锐化文本
|
||||
bump: true
|
||||
# 失真滤镜
|
||||
distortion:
|
||||
# 是否在验证码上应用失真滤镜?
|
||||
enabled: true
|
||||
# Sonar 应该应用什么形状的失真滤镜?
|
||||
# 支持的选项: 0, 1, 2, 3
|
||||
# - 0: 交叉
|
||||
# - 1: 线条
|
||||
# - 2: 圆形
|
||||
# - 3: 方形
|
||||
shape: 2
|
||||
# 畸变的像素/对象之间的距离
|
||||
distance: 5
|
||||
# 畸变的像素/对象之间的密度
|
||||
density: 0.1
|
||||
# 对象与另一个对象之间的颜色的混合的量
|
||||
mix: 0.15
|
||||
# 用于当作验证码的背景的图片
|
||||
# 如果未找到指定的图片, 则自动通过噪音生成一个随机的背景图片.
|
||||
# 如果图片在该插件的配置文件夹下, 则可以简单地输入图片的文件名.
|
||||
background: ''
|
||||
# Sonar 应该检测背景图片的颜色吗?
|
||||
# 这样做是为了避免无法区分背景和验证码的颜色.
|
||||
# 如果禁用该功能, Sonar 将会随机应用渐变的颜色.
|
||||
auto-color: false
|
||||
# Sonar 应该一次准备多少个验证码?
|
||||
precompute: 500
|
||||
# Sonar 应该给玩家多少时间完成验证码?
|
||||
@@ -150,7 +120,7 @@ verification:
|
||||
max-tries: 3
|
||||
# 允许出现在验证码答案中的字符
|
||||
# 不建议添加可能看起来彼此相似的数字或字母
|
||||
dictionary: '1235689'
|
||||
alphabet: 'abcdefhjkmnoprstuxyz'
|
||||
|
||||
# 检查玩家是否正在向服务器发送有效的客户端标识符
|
||||
client-brand:
|
||||
|
||||
@@ -192,64 +192,64 @@ verification:
|
||||
# Zprávy, které se zobrazují hráči, když má zadat CAPTCHA
|
||||
captcha:
|
||||
# Zpráva zobrazená hráči, když má zadat odpověď na captchu
|
||||
enter-code: '<prefix><green>Prosím, zadejte do chatu kód, který je zobrazen na mapě.'
|
||||
enter: '<prefix><green>Prosím, zadejte text do chatu, který se zobrazí na mapě.'
|
||||
# Zpráva zobrazená hráči, když zadá špatnou odpověď do chatu
|
||||
incorrect: '<prefix><red>Zadali jste špatný kód. Zkuste to prosím znovu.'
|
||||
incorrect: '<prefix><red>Zadali jste špatnou odpověď! Zkuste to prosím znovu.'
|
||||
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, zatímco captcha ještě není připravena
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>Vaše anti-bot data ještě nejsou připravena.
|
||||
- <gray>Počkejte prosím několik sekund, než se pokusíte znovu ověřit.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo znovu připojí příliš rychle během ověření
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>Připojili jste se příliš rychle, zkuste to později.
|
||||
- <gray>Počkejte prosím několik sekund, než se pokusíte znovu ověřit.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, ale již probíhá jeho ověření
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Vaše IP adresa je právě ověřována.
|
||||
- <red>Počkejte prosím několik sekund, než se pokusíte znovu ověřit.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, ale je již ve frontě na ověření
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Vaše IP adresa je momentálně ve frontě na ověření.
|
||||
- <red>Počkejte prosím několik minut, než se pokusíte znovu ověřit.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí, ale je dočasně na černé listině
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Momentálně vám je odepřen vstup na server.
|
||||
- <red>Počkejte prosím několik minut, abyste se mohli znovu připojit k serveru.
|
||||
- <gold>Falešně pozitivní? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí s neplatným uživatelským jménem
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Vaše uživatelské jméno obsahuje neplatné znaky.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Zpráva o odpojení, která se zobrazí, když se někdo připojí s verzí na černé listině
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Používáte verzi, která není na našem serveru povolena.
|
||||
- <gold>Potřebujete pomoc s přihlášením? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Zpráva o odpojení, která se zobrazí, když někdo úspěšně projde ověřením
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>Úspěšně jste prošli ověřením.
|
||||
- <white>Nyní se můžete připojit k serveru po opětovném připojení.
|
||||
# Zpráva o odpojení, která se zobrazí, když někdo selže při ověření
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Neprošli jste ověřením bota.
|
||||
- <gray>Počkejte prosím několik sekund, než se pokusíte znovu ověřit.
|
||||
- <gold>Potřebujete pomoc s přihlášením? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# Překlady pro podrobné zprávy Sonaru v panelu akcí
|
||||
verbose:
|
||||
|
||||
@@ -193,64 +193,64 @@ verification:
|
||||
# Nachrichten, die dem Spieler angezeigt werden, wenn er den CAPTCHA eingeben soll
|
||||
captcha:
|
||||
# Nachricht, die dem Spieler angezeigt wird, wenn er den CAPTCHA-Code im Chat eingeben muss
|
||||
enter-code: '<prefix><green>Bitte gib den Code im Chat ein, der auf der Karte angezeigt wird.'
|
||||
enter: '<prefix><green>Bitte gib den Text im Chat ein, der auf der Karte angezeigt wird.'
|
||||
# Nachricht, die dem Spieler angezeigt wird, wenn er den falschen Code im Chat eingibt
|
||||
incorrect: '<prefix><red>Du hast den falschen Code eingegeben. Bitte versuche es erneut.'
|
||||
incorrect: '<prefix><red>Du hast eine falsche Antwort eingegeben! Bitte versuche es erneut.'
|
||||
|
||||
# Trennungs-Nachricht, die angezeigt wird, wenn jemand beitritt, während der CAPTCHA noch nicht vorbereitet wurde
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>Deine Anti-Bot-Daten wurden noch nicht vorbereitet.
|
||||
- <gray>Bitte warte einige Sekunden bevor du es erneut versuchst.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Trennungs-Nachricht, die angezeigt wird, wenn jemand zu schnell während der Verifizierung erneut beitritt
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>Du hast zu schnell erneut eine Verbindung hergestellt, versuche es später noch einmal.
|
||||
- <gray>Bitte warte einige Sekunden bevor du es erneut versuchst.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Trennungs-Nachricht, die angezeigt wird, wenn jemand beitritt, aber bereits verifiziert wird
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Deine IP-Adresse wird derzeit verifiziert.
|
||||
- <red>Bitte warte einige Sekunden bevor du es erneut versuchst.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Trennungs-Nachricht, die angezeigt wird, wenn jemand beitritt, aber bereits in der Warteschlange für die Verifizierung ist
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Deine IP-Adresse befindet sich derzeit in der Warteschlange für die Verifizierung.
|
||||
- <red>Bitte warte einige Minuten, bevor du es erneut versuchst.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Trennungs-Nachricht, die angezeigt wird, wenn jemand beitritt, aber vorübergehend auf der Blacklist steht
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Du bist derzeit vom Betreten des Servers ausgeschlossen.
|
||||
- <red>Bitte warte einige Minuten, um den Server wieder betreten zu können.
|
||||
- <gold>Falschmeldung? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Nachricht, die angezeigt wird, wenn jemand mit einem ungültigen Benutzernamen beitritt
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Dein Benutzername enthält ungültige Zeichen.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Nachricht, die angezeigt wird, wenn jemand mit einer auf der Blacklist stehenden Version beitritt
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Du verwendest eine Version, die auf unserem Server nicht erlaubt ist.
|
||||
- <gold>Brauchst du Hilfe beim Einloggen? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Nachricht, die angezeigt wird, wenn jemand die Verifizierung erfolgreich abschließt
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>Du hast die Verifizierung erfolgreich bestanden.
|
||||
- <white>Verbinde dich erneut um auf dem Server zu joinen.
|
||||
# Nachricht, die angezeigt wird, wenn jemand die Verifizierung nicht besteht
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Du hast die Bot-Verifizierung nicht bestanden.
|
||||
- <gray>Bitte warte einige Sekunden bevor du es erneut versuchst.
|
||||
- <gold>Brauchst du Hilfe beim Einloggen? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# Übersetzungen für Sonars ausführliche Aktionsleisten-Nachrichten
|
||||
verbose:
|
||||
|
||||
@@ -191,65 +191,65 @@ verification:
|
||||
|
||||
# Messages that are shown to the player when they are supposed to enter the CAPTCHA
|
||||
captcha:
|
||||
# Message shown to the player when they have to enter the answer to the captcha
|
||||
enter-code: '<prefix><green>Please enter the code in chat that is displayed on the map.'
|
||||
# Message shown to the player when they have to enter the answer to the CAPTCHA
|
||||
enter: '<prefix><green>Please enter the text in chat that is displayed on the map.'
|
||||
# Message shown to the player when they enter the wrong answer in chat
|
||||
incorrect: '<prefix><red>You have entered the wrong code. Please try again.'
|
||||
incorrect: '<prefix><red>You have entered a wrong answer! Please try again.'
|
||||
|
||||
# Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>Your anti-bot data has not been prepared yet.
|
||||
- <gray>Please wait a few seconds before trying to verify again.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone rejoins too fast during verification
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>You reconnected too fast, try again later.
|
||||
- <gray>Please wait a few seconds before trying to verify again.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins but is already verifying
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Your IP address is currently being verified.
|
||||
- <red>Please wait a few seconds before trying to verify again.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins but is already queued for verification
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Your IP address is currently queued for verification.
|
||||
- <red>Please wait a few minutes before trying to verify again.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins but is temporarily blacklisted
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>You are currently denied from entering the server.
|
||||
- <red>Please wait a few minutes to be able to join the server again.
|
||||
- <gold>False positive? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins with an invalid username
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Your username contains invalid characters.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins with a blacklisted version
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>You are using a version that is not allowed on our server.
|
||||
- <gold>Need help logging in? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone verifies successfully
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>You have successfully passed the verification.
|
||||
- <white>You are now able to play on the server when you reconnect.
|
||||
# Disconnect message that is shown when someone fails verification
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>You have failed the bot verification.
|
||||
- <gray>Please wait a few seconds before trying to verify again.
|
||||
- <gold>Need help logging in? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# Translations for Sonar's actionbar verbose messages
|
||||
verbose:
|
||||
|
||||
@@ -66,67 +66,67 @@ commands:
|
||||
# Descripción para este subcomando
|
||||
description: "Recargar todas las configuraciones"
|
||||
# Message shown when someone starts reloading Sonar
|
||||
start: <prefix>Recargando Sonar...
|
||||
start: '<prefix>Recargando Sonar...'
|
||||
# Message shown when Sonar has finished reloading
|
||||
finish: <prefix><green>Recargado con éxito <gray>(<time-taken>ms)
|
||||
finish: '<prefix><green>Recargado con éxito <gray>(<time-taken>ms)'
|
||||
|
||||
# Translations for '/sonar verbose'
|
||||
verbose:
|
||||
# Descripción para este subcomando
|
||||
description: "Activar o desactivar detallado (verbose)"
|
||||
# Message shown when a player subscribes to Sonar verbose
|
||||
subscribe: <prefix>Ahora estás viendo Sonar detallado (verbose).
|
||||
subscribe: '<prefix>Ahora estás viendo Sonar detallado (verbose).'
|
||||
# Message shown when a player unsubscribes from Sonar verbose
|
||||
unsubscribe: <prefix>Ya no estás viendo Sonar detallado (verbose).
|
||||
unsubscribe: '<prefix>Ya no estás viendo Sonar detallado (verbose).'
|
||||
|
||||
# Translations for '/sonar notify'
|
||||
notify:
|
||||
# Descripción para este subcomando
|
||||
description: "Activar o desactivar las notificaciones de ataques"
|
||||
# Message shown when a player subscribes to Sonar attack notifications
|
||||
subscribe: <prefix>Ahora estás viendo las notificaciones de ataques de Sonar.
|
||||
subscribe: '<prefix>Ahora estás viendo las notificaciones de ataques de Sonar.'
|
||||
# Message shown when a player unsubscribes from Sonar attack notifications
|
||||
unsubscribe: <prefix>Ya no ves las notificaciones de ataques de Sonar.
|
||||
unsubscribe: '<prefix>Ya no ves las notificaciones de ataques de Sonar.'
|
||||
|
||||
# Translations for '/sonar blacklist'
|
||||
blacklist:
|
||||
# Descripción para este subcomando
|
||||
description: "Gestionar las direcciones IP de la lista negra"
|
||||
# Message shown when someone tries clearing the blacklist but is empty
|
||||
empty: <prefix>La lista negra está actualmente vacía. Por lo tanto, no se eliminó ninguna dirección IP de la lista negra.
|
||||
empty: '<prefix>La lista negra está actualmente vacía. Por lo tanto, no se eliminó ninguna dirección IP de la lista negra.'
|
||||
# Message shown when someone clears the blacklist
|
||||
clear: <prefix>Eliminaste con éxito un total de <amount> direcciones IP de la lista negra.
|
||||
clear: '<prefix>Eliminaste con éxito un total de <amount> direcciones IP de la lista negra.'
|
||||
# Message shown when someone checks the size of the blacklist
|
||||
size: <prefix>La lista negra actualmente contiene <amount> direccion(es) IP.
|
||||
size: '<prefix>La lista negra actualmente contiene <amount> direccion(es) IP.'
|
||||
# Message shown when someone removes an IP address from the blacklist
|
||||
remove: <prefix>Se eliminó con éxito <ip> de la lista negra.
|
||||
remove: '<prefix>Se eliminó con éxito <ip> de la lista negra.'
|
||||
# Message shown when someone adds an IP address to the blacklist
|
||||
add: <prefix>Añadida con éxito <ip> a la lista negra.
|
||||
add: '<prefix>Añadida con éxito <ip> a la lista negra.'
|
||||
# Message shown when someone adds an IP address to the blacklist that is verified
|
||||
add-warning: '<prefix><red>Advertencia: <white><ip> Actualmente está en la lista blanca. Considere eliminar la dirección IP de la lista de jugadores verificados para evitar posibles problemas.'
|
||||
# Message shown when someone adds an IP address to the blacklist, but it is already blacklisted
|
||||
ip-duplicate: <prefix>La dirección IP que proporcionó ya está en la lista negra.
|
||||
ip-duplicate: '<prefix>La dirección IP que proporcionó ya está en la lista negra.'
|
||||
# Message shown when someone removes an IP address from the blacklist, but it is not blacklisted
|
||||
ip-not-found: <prefix>La dirección IP que proporcionó no está en la lista negra.
|
||||
ip-not-found: '<prefix>La dirección IP que proporcionó no está en la lista negra.'
|
||||
|
||||
# Translations for '/sonar verified'
|
||||
verified:
|
||||
# Descripción para este subcomando
|
||||
description: "Gestionar direcciones IP verificadas"
|
||||
# Message shown when someone tries clearing the list of verified players but is empty
|
||||
empty: <prefix>La lista de jugadores verificados está actualmente vacía. Por lo tanto, ningún jugador ha sido verificado.
|
||||
empty: '<prefix>La lista de jugadores verificados está actualmente vacía. Por lo tanto, ningún jugador ha sido verificado.'
|
||||
# Message shown when someone clears the list of verified players
|
||||
clear: <prefix>Eliminaste de la lista de verificados exitosamente un total de <amount> jugador(es) único(s).
|
||||
clear: '<prefix>Eliminaste de la lista de verificados exitosamente un total de <amount> jugador(es) único(s).'
|
||||
# Message shown when someone checks the size of the list of verified players
|
||||
size: <prefix>Actualmente hay <amount> jugador(es) único(s) verificado(s).
|
||||
size: '<prefix>Actualmente hay <amount> jugador(es) único(s) verificado(s).'
|
||||
# Message shown when someone un-verifies an IP address
|
||||
remove: <prefix>Eliminaste de la lista de verificados la IP <ip>.
|
||||
remove: '<prefix>Eliminaste de la lista de verificados la IP <ip>.'
|
||||
# Message shown when someone verifies an IP address
|
||||
add: <prefix>Verificaste con éxito la IP <ip>.
|
||||
add: '<prefix>Verificaste con éxito la IP <ip>.'
|
||||
# Message shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: <prefix>La dirección IP y el UUID que proporcionó ya están verificados.
|
||||
already: '<prefix>La dirección IP y el UUID que proporcionó ya están verificados.'
|
||||
# Message shown when someone un-verifies an IP address, but it is not verified
|
||||
ip-not-found: <prefix>La dirección IP que proporcionó no está verificada.
|
||||
ip-not-found: '<prefix>La dirección IP que proporcionó no está verificada.'
|
||||
# Message shown when someone looks up the verification history of an IP address
|
||||
history: '<prefix><yellow>UUIDs anteriores para <ip>:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
@@ -181,20 +181,20 @@ verification:
|
||||
# Translations for all debug messages during the verification
|
||||
logs:
|
||||
# Message logged to console whenever a new player joins the server
|
||||
connection: <username><ip> (<protocol>) se ha conectado.
|
||||
connection: '<username><ip> (<protocol>) se ha conectado.'
|
||||
# Message logged to console whenever a player fails verification
|
||||
failed: '<ip> (<protocol>) no ha pasado la verificación de bot por: <reason>'
|
||||
# Message logged to console whenever a player is blacklisted
|
||||
blacklisted: <ip> (<protocol>) fue incluido en la lista negra por demasiados intentos fallidos.
|
||||
blacklisted: '<ip> (<protocol>) fue incluido en la lista negra por demasiados intentos fallidos.'
|
||||
# Message logged to console whenever a player is verified
|
||||
successful: <username> ha sido verificado exitosamente. (<time-taken>s!).
|
||||
successful: '<username> ha sido verificado exitosamente. (<time-taken>s!).'
|
||||
|
||||
# Messages that are shown to the player when they are supposed to enter the CAPTCHA
|
||||
captcha:
|
||||
# Message shown to the player when they have to enter the answer to the captcha
|
||||
enter-code: <prefix><green>Por favor ingresa en el chat el código que se muestra en el mapa.
|
||||
enter: '<prefix><green>Por favor, introduce en el chat el texto que se muestra en el mapa.'
|
||||
# Message shown to the player when they enter the wrong answer in chat
|
||||
incorrect: <prefix><red>Ha ingresado el código incorrecto. Inténtalo de nuevo.
|
||||
incorrect: '<prefix><red>¡Has introducido una respuesta incorrecta! Por favor, inténtalo de nuevo.'
|
||||
|
||||
# Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet
|
||||
currently-preparing:
|
||||
|
||||
@@ -192,64 +192,64 @@ verification:
|
||||
# Messages affichés au joueur lorsqu'il doit entrer le CAPTCHA
|
||||
captcha:
|
||||
# Message affiché au joueur lorsqu'il doit entrer la réponse au CAPTCHA
|
||||
enter-code: '<prefix><green>Veuillez entrer le code affiché sur la carte.'
|
||||
enter: '<prefix><green>Veuillez saisir le texte en chat qui s''affiche sur la carte.'
|
||||
# Message affiché au joueur lorsqu'il entre la mauvaise réponse dans le chat
|
||||
incorrect: '<prefix><red>Vous avez entré le mauvais code. Veuillez réessayer.'
|
||||
incorrect: '<prefix><red>Vous avez saisi une mauvaise réponse ! Veuillez réessayer.'
|
||||
|
||||
# Message de déconnexion affiché lorsque quelqu'un rejoint alors que le CAPTCHA n'a pas été préparé
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>Vos données anti-bot n'ont pas encore été préparées.
|
||||
- <gray>Veuillez patienter quelques secondes avant d'essayer de vous vérifier à nouveau.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Message de déconnexion affiché lorsque quelqu'un se reconnecte trop rapidement pendant la vérification
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>Vous avez rejoint trop rapidement, réessayez plus tard.
|
||||
- <gray>Veuillez patienter quelques secondes avant d'essayer de vous vérifier à nouveau.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Message de déconnexion affiché lorsque quelqu'un rejoint mais est déjà en vérification
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Votre adresse IP est actuellement en cours de vérification.
|
||||
- <red>Veuillez patienter quelques secondes avant d'essayer de vous vérifier à nouveau.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Message de déconnexion affiché lorsque quelqu'un rejoint mais est déjà en attente de vérification
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Votre adresse IP est actuellement en attente de vérification.
|
||||
- <red>Veuillez patienter quelques minutes avant d'essayer de vous vérifier à nouveau.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Message de déconnexion affiché lorsque quelqu'un rejoint mais est temporairement mis sur liste noire
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Vous êtes actuellement interdit d'entrer sur le serveur.
|
||||
- <red>Veuillez patienter quelques minutes pour pouvoir rejoindre le serveur à nouveau.
|
||||
- <gold>Erreur ? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Message de déconnexion affiché lorsque quelqu'un rejoint avec un nom d'utilisateur non valide
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Votre nom d'utilisateur contient des caractères non valides.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Message de déconnexion affiché lorsque quelqu'un rejoint avec une version sur liste noire
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Vous utilisez une version non autorisée sur notre serveur.
|
||||
- <gold>Besoin d'aide pour vous connecter ? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Message de déconnexion affiché lorsque quelqu'un réussit la vérification
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>Vous avez réussi la vérification.
|
||||
- <white>Vous pouvez maintenant jouer sur le serveur lorsque vous vous reconnectez.
|
||||
# Message de déconnexion affiché lorsque quelqu'un échoue à la vérification
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Vous avez échoué à la vérification du bot.
|
||||
- <gray>Veuillez patienter quelques secondes avant d'essayer de vous vérifier à nouveau.
|
||||
- <gold>Besoin d'aide pour vous connecter ? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# Translations for Sonar's actionbar verbose messages
|
||||
verbose:
|
||||
|
||||
@@ -191,65 +191,65 @@ verification:
|
||||
|
||||
# Pesan yang ditampilkan kepada pemain saat mereka seharusnya memasukkan CAPTCHA
|
||||
captcha:
|
||||
# Pesan yang ditampilkan kepada pemain saat mereka harus memasukkan jawaban untuk captcha
|
||||
enter-code: '<prefix><green>Silakan masukkan kode di chat yang ditampilkan di peta.'
|
||||
# Pesan yang ditampilkan kepada pemain saat mereka harus memasukkan jawaban untuk CAPTCHA
|
||||
enter: '<prefix><green>Masukkan teks dalam obrolan yang ditampilkan di peta.'
|
||||
# Pesan yang ditampilkan kepada pemain saat mereka memasukkan jawaban yang salah di chat
|
||||
incorrect: '<prefix><red>Anda memasukkan kode yang salah. Silakan coba lagi.'
|
||||
incorrect: '<prefix><red>Anda telah memasukkan jawaban yang salah! Silakan coba lagi.'
|
||||
|
||||
# Pesan putus yang ditampilkan saat seseorang bergabung sementara captcha belum disiapkan
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>Data anti-bot Anda belum disiapkan.
|
||||
- <gray>Silakan tunggu beberapa detik sebelum mencoba verifikasi lagi.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Pesan putus yang ditampilkan saat seseorang bergabung kembali terlalu cepat selama verifikasi
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>Anda terhubung kembali terlalu cepat, coba lagi nanti.
|
||||
- <gray>Silakan tunggu beberapa detik sebelum mencoba verifikasi lagi.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Pesan putus yang ditampilkan saat seseorang bergabung tetapi sudah dalam proses verifikasi
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Alamat IP Anda saat ini sedang diverifikasi.
|
||||
- <red>Silakan tunggu beberapa detik sebelum mencoba verifikasi lagi.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Pesan putus yang ditampilkan saat seseorang bergabung tetapi sudah antri untuk verifikasi
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Alamat IP Anda saat ini sedang antri untuk verifikasi.
|
||||
- <red>Silakan tunggu beberapa menit sebelum mencoba verifikasi lagi.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Pesan putus yang ditampilkan saat seseorang bergabung tetapi sedang masuk daftar hitam sementara
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Anda saat ini ditolak untuk masuk ke server.
|
||||
- <red>Silakan tunggu beberapa menit agar bisa bergabung ke server lagi.
|
||||
- <gold>Positif palsu? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Pesan putus yang ditampilkan saat seseorang bergabung dengan username yang tidak valid
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Username Anda mengandung karakter yang tidak valid.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Pesan putus yang ditampilkan saat seseorang bergabung dengan versi yang masuk daftar hitam
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Anda menggunakan versi yang tidak diizinkan di server kami.
|
||||
- <gold>Butuh bantuan masuk? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Pesan putus yang ditampilkan saat seseorang berhasil verifikasi
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>Anda telah berhasil melewati verifikasi.
|
||||
- <white>Anda sekarang dapat bermain di server saat Anda terhubung kembali.
|
||||
# Pesan putus yang ditampilkan saat seseorang gagal verifikasi
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Anda telah gagal dalam verifikasi bot.
|
||||
- <gray>Silakan tunggu beberapa detik sebelum mencoba verifikasi lagi.
|
||||
- <gold>Butuh bantuan masuk? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# Terjemahan untuk pesan verbose actionbar Sonar
|
||||
verbose:
|
||||
|
||||
@@ -192,64 +192,64 @@ verification:
|
||||
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა მათ უნდა დაწერონ CAPTCHA
|
||||
captcha:
|
||||
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა მათ უნდა მიუთითონ CAPTCHA ს პასუხი
|
||||
enter-code: '<prefix><green>გთხოვთ დაწეროთ ჩატში ის კოდი რომელიც არის ნაჩვენები რუკაზე.'
|
||||
enter: '<prefix><green>გთხოვთ დაწეროთ ჩატში ის კოდი რომელიც არის ნაჩვენები რუკაზე.'
|
||||
# მესიჯი რომელიც გაეგზავნება მოთამაშეს როცა ისინი ჩაწერენ არასწორ პასუხს ჩატში
|
||||
incorrect: '<prefix><red>თქვენ მიუთითეთ არასწორი კოდი. გთხოვთ ხელახლა ცადოთ.'
|
||||
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა მაგრამ CAPTCHA არ არის მზად
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>ანტიბოტი ჯერ არ მომზადებულა.
|
||||
- <gray>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა შემოხვალთ.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე ხელახლა შემოდის ძალიან სწრაფად შემოწმების დროს.
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>თქვენ კავშირდებით ხელახლა ძალიან სწრაფად.
|
||||
- <gray>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე ცდის შემოსვლას თუმცა იგი უკვე მოწმდება
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>თქვენი IP მისამართი ამჟამად უკვე მოწმდება.
|
||||
- <red>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა თუმცა იგი უკვე არის შემოწმების რიგში
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>თქვენი IP მისამართი უკვე არის რიგში შემოწმებისთვის.
|
||||
- <red>გთხოვთ დაიცადოთ რამოდენიმე წუთი სანამ ხელახლა ცდით შემოსვლას.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა თუმცა დროებით დაბლოკილია
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>თქვენ ამჟამად დაბლოკილი ხართ სერვერიდან.
|
||||
- <red>გთხოვთ დაიცადოთ რამოდენიმე წუთი რომ ისევ შეგეძლოტ სერვერზე შემოსვლა.
|
||||
- <gold>შეცდომა? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა არასწორი სახელით
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>შენი სახელი შეიცავს არასწორ კომპონენტებს.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოვა დაბლოკილი ვერსიით
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>შენ იყენებ ვერსიას რომელიც არ არის დაშვებული სერვერზე.
|
||||
- <gold>გჭირდება დახმარება შესვლაში? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე შემოწმდა წარმატებით
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>თქვენ წარმატებით გაიარეთ შემოწმება.
|
||||
- <white>თქვენ უკვე შეგიძლიათ შეხვიდეთ სერვერზე.
|
||||
# გავარდნის მესიჯი რომელიც არის ნაჩვენები როცა ვინმე წარუმატებლად გაივლის შემოწმებას
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>თქვენ ვერ გაიარეთ შემოწმება.
|
||||
- <gray>გთხოვთ დაიცადოთ რამოდენიმე წამი სანამ ხელახლა ცდით შემოსვლას.
|
||||
- <gold>გჭირდებათ დახმარება შესვლაში? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# თარგმანი Sonar_ის actionbar დეტალური ინფორმაციის მესიჯებისთვის
|
||||
verbose:
|
||||
|
||||
@@ -191,65 +191,65 @@ verification:
|
||||
|
||||
# Berichten die aan de speler worden getoond wanneer hij of zij de CAPTCHA moet invoeren
|
||||
captcha:
|
||||
# Bericht dat aan de speler wordt getoond wanneer hij het antwoord op de captcha moet invoeren
|
||||
enter-code: '<prefix><green>Voer de code in de chat in die wordt weergegeven op de kaart.'
|
||||
# Bericht dat aan de speler wordt getoond wanneer hij het antwoord op de CAPTCHA moet invoeren
|
||||
enter: '<prefix><green>Voer in de chat de tekst in die op de kaart wordt weergegeven.'
|
||||
# Bericht dat aan de speler wordt getoond wanneer hij of zij het verkeerde antwoord invoert in de chat
|
||||
incorrect: '<prefix><red>U heeft de verkeerde code ingevoerd. Probeer het opnieuw.'
|
||||
incorrect: '<prefix><red>Je hebt een verkeerd antwoord ingevoerd! Probeer het opnieuw.'
|
||||
|
||||
# Verbindingsbericht dat wordt getoond als iemand zich aansluit terwijl de captcha nog niet is voorbereid
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>Uw anti-bot gegevens zijn nog niet voorbereid.
|
||||
- <gray>Wacht een paar seconden voordat u opnieuw probeert te verifiëren.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Verbindingsbericht dat wordt weergegeven wanneer iemand tijdens de verificatie te snel opnieuw deelneemt
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>Je hebt te snel opnieuw verbinding gemaakt. Probeer het later opnieuw.
|
||||
- <gray>Wacht een paar seconden voordat u opnieuw probeert te verifiëren.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Verbindingsbericht dat wordt weergegeven wanneer iemand zich aanmeldt, maar zich al aan het verifiëren is
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Uw IP-adres wordt momenteel geverifieerd.
|
||||
- <red>Wacht een paar seconden voordat u opnieuw probeert te verifiëren.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Verbindingsbericht dat wordt weergegeven wanneer iemand zich aanmeldt, maar al in de wachtrij staat voor verificatie
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Uw IP-adres staat momenteel in de wachtrij voor verificatie.
|
||||
- <red>Wacht een paar minuten voordat u opnieuw probeert te verifiëren.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Verbindingsbericht dat wordt weergegeven wanneer iemand lid wordt, maar tijdelijk op de zwarte lijst staat
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Momenteel wordt u de toegang tot de server ontzegd.
|
||||
- <red>Wacht een paar minuten voordat u weer verbinding kunt maken met de server.
|
||||
- <gold>Vals positief? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Verbindingsbericht dat wordt weergegeven wanneer iemand zich aanmeldt met een ongeldige gebruikersnaam
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Uw gebruikersnaam bevat ongeldige tekens.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Verbindingsbericht dat wordt weergegeven wanneer iemand zich aanmeldt met een versie op de zwarte lijst
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>U gebruikt een versie die niet is toegestaan op onze server.
|
||||
- <gold>Hulp nodig bij het inloggen? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Verbindingsbericht dat wordt weergegeven wanneer iemand succesvol verifieert
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>U heeft de verificatie succesvol doorlopen.
|
||||
- <white>Wanneer u opnieuw verbinding maakt, kunt u nu op de server spelen.
|
||||
# Verbindingsbericht dat wordt weergegeven wanneer iemand de verificatie mislukt
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>De bot-verificatie is mislukt.
|
||||
- <gray>Wacht een paar seconden voordat u opnieuw probeert te verifiëren.
|
||||
- <gold>Hulp nodig bij het inloggen? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# Vertalingen voor Sonar's actiebalk uitgebreide berichten
|
||||
verbose:
|
||||
|
||||
@@ -184,20 +184,20 @@ verification:
|
||||
# Translations for all debug messages during the verification
|
||||
logs:
|
||||
# Message logged to console whenever a new player joins the server
|
||||
connection: <username><ip> (<protocol>) połączył się.
|
||||
connection: '<username><ip> (<protocol>) połączył się.'
|
||||
# Message logged to console whenever a player fails verification
|
||||
failed: '<ip> (<protocol>) nie powiodło się sprawdzenie bota dla: <reason>'
|
||||
# Message logged to console whenever a player is blacklisted
|
||||
blacklisted: <ip> (<protocol>) został umieszczony na czarnej liście z powodu zbyt wielu nieudanych prób
|
||||
blacklisted: '<ip> (<protocol>) został umieszczony na czarnej liście z powodu zbyt wielu nieudanych prób'
|
||||
# Message logged to console whenever a player is verified
|
||||
successful: <username> został pomyślnie zweryfikowany (<time-taken>s!).
|
||||
successful: '<username> został pomyślnie zweryfikowany (<time-taken>s!).'
|
||||
|
||||
# Messages that are shown to the player when they are supposed to enter the CAPTCHA
|
||||
captcha:
|
||||
# Message shown to the player when they have to enter the answer to the captcha
|
||||
enter-code: <prefix><green>Wprowadź na czacie kod wyświetlany na mapie.
|
||||
enter: '<prefix><green>Wprowadź na czacie tekst wyświetlany na mapie.'
|
||||
# Message shown to the player when they enter the wrong answer in chat
|
||||
incorrect: <prefix><red>Wprowadzono nieprawidłowy kod. Spróbuj ponownie.
|
||||
incorrect: '<prefix><red>Wprowadzono nieprawidłowy kod. Spróbuj ponownie.'
|
||||
|
||||
# Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet
|
||||
currently-preparing:
|
||||
|
||||
@@ -189,72 +189,73 @@ verification:
|
||||
# Сообщение, которое выводится в консоль при успешной проверке игрока
|
||||
successful: '<username> прошел проверку (<time-taken>s!).'
|
||||
|
||||
# Сообщения, которые показываются игроку, когда он должен ввести капчу
|
||||
captcha:
|
||||
# Сообщение, которое показывается игроку, когда он должен ввести ответ на капчу
|
||||
enter-code: '<prefix><green>Введите код в чате, который отображается на карте.'
|
||||
enter: '<prefix><green>Пожалуйста, введите в чат текст, который будет отображаться на карте.'
|
||||
# Сообщение, которое показывается игроку, когда он вводит неправильную капчу в чат
|
||||
incorrect: '<prefix><red>Вы ввели неправильный код. Пожалуйста, попробуйте еще раз.'
|
||||
incorrect: '<prefix><red>Вы ввели неправильный ответ! Пожалуйста, попробуйте еще раз.'
|
||||
|
||||
# Сообщение, которое показывается когда капча еще не подготовлена
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>Капча еще не подготовлена.
|
||||
- <gray>Пожалуйста, подождите несколько секунд, прежде чем повторить попытку.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Сообщение, которое показывается когда слишком много людей проходят проверку
|
||||
too-many-players:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>Слишком много игроков пытаются войти на сервер.
|
||||
- <gray>Пожалуйста, подождите несколько секунд, прежде чем повторить попытку.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Сообщение, которое показывается когда кто-то слишком быстро перезаходит
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>Вы перезаходите слишком быстро, повторите попытку позже.
|
||||
- <gray>Пожалуйста, подождите несколько секунд, прежде чем повторить попытку.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Сообщение, которое показывается когда чей то IP-адрес уже проходит верефикацию
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Ваш IP-адрес в настоящее время проверяется.
|
||||
- <gray>Пожалуйста, подождите несколько секунд, прежде чем повторить попытку.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Сообщение, которое показывается когда кто-то находится в очереди на проверку
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Ваш IP-адрес в настоящее время находится в очереди на проверку.
|
||||
- <gray>Пожалуйста, подождите несколько секунд, прежде чем повторить попытку.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Сообщение, которое показывается когда кто-то временно заблокирован на сервере
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Вам запрещено входить на сервер.
|
||||
- <red>Пожалуйста, подождите несколько минут, чтобы снова присоединиться к серверу.
|
||||
- <gold>Вы считаете что это ошибка? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Сообщение, которое показывается когда кто-то входит на сервер с некорректным ником
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Ваш ник содержит недопустимые символы.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Сообщение, которое показывается когда кто-то входит на сервер с заблокированной версии
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Вы используете версию, которая запрещена на нашем сервере.
|
||||
- <gold>Нужна помощь? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Сообщение которое показывается при успешной верификации
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>Вы успешно прошли проверку.
|
||||
- <white>Пожалуйста, перезайдите.
|
||||
# Сообщение которое показывается, когда кто-то не прошел верификацию
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Вы не прошли проверку бота.
|
||||
- <gray>Пожалуйста, подождите несколько секунд, прежде чем повторить попытку.
|
||||
- <gold>Нужна помощь? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# Перевод для action bar (verbose)
|
||||
verbose:
|
||||
|
||||
@@ -192,64 +192,64 @@ verification:
|
||||
# CAPTCHA'yı girmeleri gerektiğinde oyuncuya gösterilen mesajlar
|
||||
captcha:
|
||||
# CAPTCHA'nın cevabını girmeleri gerektiğinde oyuncuya gösterilen mesaj
|
||||
enter-code: '<prefix><green>Haritada gösterilen kodu lütfen sohbete girin.'
|
||||
enter: '<prefix><green>Lütfen haritada görüntülenen sohbet metnini girin.'
|
||||
# Message that is shown to the player when they enter the wrong answer in chat
|
||||
incorrect: '<prefix><red>Yanlış kodu girdiniz. Lütfen tekrar deneyin.'
|
||||
incorrect: '<prefix><red>Yanlış bir cevap girdiniz! Lütfen tekrar deneyin.'
|
||||
|
||||
# CAPTCHA henüz hazırlanmamışken birisi katıldığında gösterilen bağlantıyı kesme mesajı
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>Anti-bot verileriniz henüz hazır değil.
|
||||
- <gray>Tekrar doğrulamayı denemeden önce lütfen birkaç saniye bekleyin.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Doğrulama sırasında birisi çok hızlı bir şekilde yeniden katıldığında gösterilen bağlantıyı kesme mesajı
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>Çok hızlı yeniden bağlandın.
|
||||
- <gray>Tekrar doğrulamayı denemeden önce lütfen birkaç saniye bekleyin.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Birisi katıldığında ancak zaten doğrulama yaparken gösterilen bağlantıyı kesme mesajı
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>IP adresiniz şu anda doğrulanıyorç
|
||||
- <red>Tekrar doğrulamayı denemeden önce lütfen birkaç saniye bekleyin.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Birisi katıldığında gösterilen ancak doğrulama için zaten sıraya alınmış olduğunda gösterilen bağlantıyı kesme mesajı
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>IP adresiniz şu an doğrulanma sırasında.
|
||||
- <red>Tekrar doğrulamayı denemeden önce lütfen birkaç dakika bekleyin.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Birisi katıldığında ancak geçici olarak kara listeye alındığında gösterilen bağlantıyı kesme mesajı
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Şu anda sunucuya girişiniz engellenmiştir.
|
||||
- <red>Sunucuya tekrar katılabilmek için lütfen birkaç dakika bekleyin.
|
||||
- <gold>Bunun bir hata olduğunu mu düşünüyorsunuz? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Birisi geçersiz bir kullanıcı adıyla katıldığında gösterilen bağlantıyı kesme mesajı
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Kullanıcı adınız geçersiz karakterler içeriyor.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Birisi kara listeye alınmış bir sürümle katıldığında gösterilen bağlantıyı kes mesajı
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Sunucumuzda izin verilmeyen bir sürüm ile giriş yapmaya çalışıyorsun.
|
||||
- <gold>Giriş yapma hakkında yardıma mı ihtiyacın var? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# Birisi başarıyla doğrulama yaptığında gösterilen bağlantıyı kesme mesajı
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>Doğrulamayı başarıyla geçtin.
|
||||
- <white>Yeniden bağlandığınızda artık sunucuda oynayabilirsiniz.
|
||||
# Birisi doğrulamayı başaramadığında gösterilen bağlantıyı kesme mesajı
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>Doğrulamayı geçemediniz.
|
||||
- <gray>Tekrar doğrulamayı denemeden önce lütfen birkaç saniye bekleyin.
|
||||
- <gold>Giriş yapma hakkında yardıma mı ihtiyacın var? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# Sonar'ın actionbar verbose mesajları için çeviriler
|
||||
verbose:
|
||||
|
||||
@@ -192,64 +192,64 @@ verification:
|
||||
# 当验证中的玩家需要解决验证码时显示的消息
|
||||
captcha:
|
||||
# 玩家需要输入验证码答案时显示的消息
|
||||
enter-code: '<prefix><green>请在聊天框中发送地图上的验证码.'
|
||||
enter: '<prefix><green>请输入地图上显示的聊天文本.'
|
||||
# 当玩家输入错误的验证码时显示的消息
|
||||
incorrect: '<prefix><red>您输入了错误的验证码. 请再试一次'
|
||||
incorrect: '<prefix><red>您输入了错误的答案! 请重试.'
|
||||
|
||||
# 当验证码还在准备的时候显示的踢出消息
|
||||
currently-preparing:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <yellow>反机器人还在准备所需的数据
|
||||
- <gray>请稍后重新加入服务器
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# 当玩家需要验证时重新连接速度太快时显示的踢出消息
|
||||
too-fast-reconnect:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <gold>您的重新连接速度过快
|
||||
- <gray>请稍后重新加入服务器
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# 当玩家尝试加入但它的 IP 已经有另一位玩家在验证时显示的踢出消息
|
||||
already-verifying:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>有另一位个连接正在使用您当前的 IP 地址进行验证
|
||||
- <red>请稍后重新加入服务器
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# 已经在排队等待验证的人加入时显示的踢出消息
|
||||
already-queued:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>您的 IP 地址当前正在排队等待验证
|
||||
- <red>请稍后重新加入服务器
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# 当玩家尝试加入但暂时被列入黑名单时显示的踢出消息
|
||||
blacklisted:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>您已被临时禁止加入服务器
|
||||
- <red>请等待几分钟后重新加入服务器
|
||||
- <gold>您认为您不应该被禁止? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# 当玩家尝试加入 但他们的用户名包含不允许的字符时显示的踢出消息
|
||||
invalid-username:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>您的用户名包含无效的字符. 请更改您的用户名 然后再试.
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# 当玩家使用列入黑名单的版本加入时显示的踢出消息
|
||||
blacklisted-protocol:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>您当前所使用的协议/客户端版本已被服务器禁止
|
||||
- <gold>需要帮助以便您能登录服务器? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
# 当玩家验证成功时显示的踢出消息
|
||||
success:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <green>您已成功通过验证
|
||||
- <white>请重新连接以加入服务器
|
||||
# 当玩家未能通过验证时显示的消息
|
||||
failed:
|
||||
- '<header>'
|
||||
- <header>
|
||||
- <red>无法验证您是否是机器人
|
||||
- <gray>请等待几秒后重新加入服务器以重新验证
|
||||
- <gold>需要帮助以便您能登录服务器? <gray><support-url>
|
||||
- '<footer>'
|
||||
- <footer>
|
||||
|
||||
# 状态栏详细信息
|
||||
verbose:
|
||||
|
||||
Reference in new issue
Block a user