Compare commits

...
15 Commits
Author SHA1 Message Date
Jones def40be5c0 Release 2.1.3 (#369) 2024-08-28 15:33:53 +02:00
Jones d4bdf86fcb Fix NullPointerException 2024-08-28 15:28:10 +02:00
Jones e6c3dc9d2b Release 2.1.2 (#368) 2024-08-28 13:06:04 +02:00
Jones a3e98e5e51 Recode CAPTCHA generation (#367)
* feat: fully recode CAPTCHA generation

* style: remove unused explicit cast

* fix(captcha): increase color brightness of second gradient

* style: fix some comments

* chore: fix copyright headers

* refactor: move ttf font loading into separate class

* style: fix some comments

* refactor(captcha): move to array for glyphs

* feat(captcha): recode color gradient algorithm

* fix(captcha): only randomize stroke width once

* feat(captcha): re-add custom background support

* feat(fallback): optimize pre-queue process

* chore: minor code improvements

* feat: update messages for new CAPTCHA

* fix: use 0 for total experience in SetExp packet

* feat(captcha): [wip] circle inverse filter

* fix(captcha): make expected CAPTCHA answer lowercase

* feat: use (actually legal-to-use) typewriter font

* feat: add NoiseOverlayFilter

* feat: automatically adapt the font scale

* feat(captcha): improve text positioning and coloring

* feat(captcha): add optional SmearOverlayFilter

* feat(captcha): add random glyph outlines

* fix: set debug image generation amount to 1

* refactor: use JHLab's smear filter

* style: correct some spelling mistakes in comments

* fix: let the system decide the random seed
2024-08-28 12:01:41 +02:00
Jones 769d1f2ddb Bump h2databases from 2.1.214 to 2.2.220 2024-08-27 17:36:53 +02:00
Jones f977ae4fe5 Bump mysql-connector-j from 8.4.0 to 9.0.0 2024-08-27 17:33:44 +02:00
Jones 9b11c4c1e0 Bump mariadb-java-client from 3.4.0 to 3.4.1 2024-08-27 17:30:16 +02:00
Jones 44afb98b3c Fix Bedrock false positive with rotation packets 2024-08-27 17:06:28 +02:00
Jones 1e246472cc Bump Gradle from 8.9 to 8.10 2024-08-27 14:57:48 +02:00
Jones 77ee422c64 Fix Bedrock false flagging the vehicle speed check 2024-08-27 14:04:27 +02:00
dependabot[bot] 756d7e1f89 Bump bstats from 3.0.2 to 3.0.3 (#364)
Bumps `bstats` from 3.0.2 to 3.0.3.

Updates `org.bstats:bstats-bungeecord` from 3.0.2 to 3.0.3
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.2...v3.0.3)

Updates `org.bstats:bstats-bukkit` from 3.0.2 to 3.0.3
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.2...v3.0.3)

Updates `org.bstats:bstats-velocity` from 3.0.2 to 3.0.3
- [Release notes](https://github.com/Bastian/bStats-Metrics/releases)
- [Commits](https://github.com/Bastian/bStats-Metrics/compare/v3.0.2...v3.0.3)

---
updated-dependencies:
- dependency-name: org.bstats:bstats-bungeecord
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.bstats:bstats-bukkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.bstats:bstats-velocity
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-26 14:13:16 +02:00
Jones 2ac4b7aad3 Don't create duplicate entries in database 2024-08-24 14:49:30 +02:00
Jones 5726e2d3d2 Exempt fake (spoofed) players from being checked 2024-08-20 17:04:00 +02:00
Jones 7aea5754a9 Enable org.gradle.daemon to speed up build times 2024-08-18 16:40:45 +02:00
Jones c10adc37be Move net.minecrell.plugin-yml to version catalog 2024-08-18 16:35:50 +02:00
60 changed files with 1002 additions and 703 deletions

No files matched your search

+1 -1
View File
@@ -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
View File
@@ -1,4 +1,3 @@
org.gradle.daemon=false
group=xyz.jonesdev.sonar
version=2.1.1
version=2.1.3
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
+5 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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);
@@ -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 -1
View File
@@ -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 {
@@ -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 -1
View File
@@ -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 {
+10
View File
@@ -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);
}
}
}
@@ -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();
}
}
@@ -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;
}
}
}
@@ -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);
}
}
}
}
}
+51
View File
@@ -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 -3
View File
@@ -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()
);
@@ -69,7 +69,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 +81,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(),
@@ -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));
}
/**
@@ -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));
@@ -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) {
@@ -22,6 +22,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.captcha.StandardCaptchaGenerator;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockType;
import xyz.jonesdev.sonar.common.fallback.protocol.block.BlockUpdate;
import xyz.jonesdev.sonar.common.fallback.protocol.captcha.CaptchaPreparer;
@@ -108,9 +109,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 +196,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,16 +208,26 @@ 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));
}
// Update the CAPTCHA generator if necessary
if (Sonar.get().getFallback().getCaptchaGenerator() == null
|| Sonar.get().getFallback().getCaptchaGenerator() instanceof StandardCaptchaGenerator) {
Sonar.get().getFallback().setCaptchaGenerator(new StandardCaptchaGenerator(128, 128,
Sonar.get().getConfig().getVerification().getMap().getBackgroundImage()));
} else {
Sonar.get().getLogger().info("Custom CAPTCHA generator detected, skipping reinitialization.");
}
// Prepare CAPTCHA answers
MAP_INFO_PREPARER.prepare();
CaptchaPreparer.prepare();
} else {
// Throw away if not needed
enterCodeMessage = null;
incorrectCaptcha = null;
xpCountdown = null;
Sonar.get().getFallback().setCaptchaGenerator(null);
}
}
}
@@ -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() {
@@ -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,
@@ -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;
}
@@ -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));
}
@@ -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);
@@ -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));
@@ -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);
}
@@ -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: