Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21eb949bae | ||
|
|
3a978b39ee | ||
|
|
efc435df61 | ||
|
|
8baa920279 | ||
|
|
647931c31e | ||
|
|
c8c3685355 | ||
|
|
b759082416 | ||
|
|
31a09d60ac | ||
|
|
45fb866c1c | ||
|
|
9f8d828622 | ||
|
|
6e78c875a2 | ||
|
|
18486b9286 | ||
|
|
644d1387ce | ||
|
|
e80311bf24 | ||
|
|
f42dd4d39f | ||
|
|
7717fb26a0 | ||
|
|
97ff082fb4 | ||
|
|
9bdb884302 | ||
|
|
19d2c141de | ||
|
|
5ea02039f2 | ||
|
|
5a5c91b52d | ||
|
|
128e4aa0ca | ||
|
|
504e5f86ae | ||
|
|
932849ba22 | ||
|
|
6ba8148604 | ||
|
|
3fd23cb853 | ||
|
|
bed374e0f8 | ||
|
|
bb3706a949 | ||
|
|
4fa3a0a313 | ||
|
|
62ce6ded18 | ||
|
|
7d45b2d0db | ||
|
|
b578f813dc | ||
|
|
2e0f1ad8ff | ||
|
|
dd21a7df58 | ||
|
|
56d5b47bfe | ||
|
|
931cba82ed | ||
|
|
a1fffe2e74 | ||
|
|
263060e517 | ||
|
|
cffe9c4dd7 | ||
|
|
d96fcca78e | ||
|
|
0027f5988b | ||
|
|
d4d3b91240 | ||
|
|
ebc8ade9e4 | ||
|
|
ef846b137b | ||
|
|
0107a7e569 | ||
|
|
1055f695c6 | ||
|
|
b8bb1839a2 | ||
|
|
1d852e4eb2 | ||
|
|
4d35634caa |
No files matched your search
+14
-32
@@ -1,12 +1,11 @@
|
||||
<div align="center">
|
||||
<!-- Introduction -->
|
||||
<p>
|
||||
<h1>💫 Sonar</h1>
|
||||
Lightweight and easy-to-use anti-bot plugin for your Minecraft server. Supporting every client and server version.
|
||||
<br>
|
||||
An effective and extensible solution for protecting your Minecraft server against all kinds of bot attacks
|
||||
</p>
|
||||
|
||||
<h1>💫 Sonar</h1>
|
||||
Lightweight and easy-to-use anti-bot plugin for your Minecraft server.
|
||||
<br>
|
||||
Sonar keeps your server safe from common and even sophisticated bot attacks.
|
||||
<br><br>
|
||||
|
||||
<!-- Badges & icons -->
|
||||
[](https://github.com/jonesdevelopment/sonar/actions/workflows/gradle.yml)
|
||||
[](https://www.codefactor.io/repository/github/jonesdevelopment/sonar/overview/main)
|
||||
@@ -15,7 +14,6 @@
|
||||
[](https://jonesdev.xyz/discord)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
<br>
|
||||
<br>
|
||||
<!-- Quick navigation -->
|
||||
[Releases](https://github.com/jonesdevelopment/sonar/releases)
|
||||
|
|
||||
@@ -29,7 +27,6 @@
|
||||
</div>
|
||||
|
||||
## Design and Goal
|
||||
|
||||
* Effective, lightweight, and easy-to-use
|
||||
* No unnecessary features and clean code
|
||||
* Protection against all kinds of bot attacks
|
||||
@@ -38,44 +35,29 @@
|
||||
* Multi-platform support (See [supported versions](https://docs.jonesdev.xyz/sonar/supported-versions))
|
||||
|
||||
## Checks
|
||||
Sonar analyzes a player's behavior before joining the actual server, therefore stopping malicious traffic from ever reaching the backend. It is supposed to be an instant, powerful, and simple method of verification that should prevent all typical and advanced types of bots.
|
||||
|
||||
- [Fallback](https://github.com/jonesdevelopment/sonar#fallback) is Sonar's main anti-bot component
|
||||
- Sonar queues new player logins to prevent spam login attacks
|
||||
- Sonar checks the handshake packets for legitimacy
|
||||
- Sonar makes sure some packets cannot be duplicated illegitimately
|
||||
1. Sonar sends the player to a lightweight fake server when they connect for the first time.
|
||||
2. Sonar verifies that players obey the laws of Minecraft's physics, including gravity and proper block collision.
|
||||
3. Sonar verifies that players send legitimate packets when interacting with vehicles (e.g. boats).
|
||||
4. Sonar makes sure that players send legitimate packets according to the [vanilla Minecraft protocol](<https://wiki.vg/Protocol>).
|
||||
|
||||
### Fallback
|
||||
|
||||
Fallback analyzes a player's behavior before joining the actual server, therefore stopping malicious traffic from ever reaching the backend.
|
||||
It is supposed to be an instant, powerful, and invisible method of verification that should prevent all typical and advanced types of bots.
|
||||
|
||||
* Sends the player to a lightweight fake server when they connect for the first time.
|
||||
* Analyzes if the player is sending the necessary packets.
|
||||
* Analyzes if the player is sending legitimate packets.
|
||||
* Checks if the player is obeying client gravity.
|
||||
* Checks if the player is colliding with blocks correctly.
|
||||
|
||||
Fallback also protects against huge spambot attacks since it queues the incoming connections, therefore making it technically impossible to have a ton of bots join the server at the same time.
|
||||
Sonar also protects against spambot attacks since it queues the incoming connections, therefore making it technically impossible to have a ton of bots join the server at the same time.
|
||||
|
||||
### False positives
|
||||
|
||||
Fallback is unlikely to ever falsely prevent a player from joining the server since Minecraft uses the TCP protocol, which means that packets are always sent in the correct order. Therefore, lag or ping should not affect the bot check. However, there are some edge cases where Fallback might not receive packets within the necessary time period. In this case, Sonar tries to account for these edge cases in order to prevent false positives. If you or one of your players experiences a false positive, make sure to report it by opening a [GitHub issue](https://github.com/jonesdevelopment/sonar/issues) or a ticket on [Discord](https://jonesdev.xyz/discord/).
|
||||
Sonar is unlikely to ever falsely prevent a player from joining the server since Minecraft uses the TCP protocol, which means that packets are always sent in the correct order. Therefore, lag or ping should not affect the bot check.
|
||||
If you or one of your players experiences a false positive, make sure to report it by opening a [GitHub issue](https://github.com/jonesdevelopment/sonar/issues/new/choose) or a ticket on the [Discord server](https://jonesdev.xyz/discord/).
|
||||
|
||||
## Building
|
||||
|
||||
If you want to build your own version of Sonar, please take a look at the [Sonar building documentation](https://docs.jonesdev.xyz/development/building).
|
||||
<br>
|
||||
You can also take a look at the [gradle documentation](https://docs.gradle.org/current/userguide/userguide.html) for a better understanding of Gradle.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you are interested in contributing, you can check out the [Contributing Guidelines](https://github.com/jonesdevelopment/sonar/blob/main/.github/CONTRIBUTING.md) for detailed instructions.
|
||||
|
||||
## License
|
||||
|
||||
Sonar is licensed under the [GNU General Public License 3.0](https://www.gnu.org/licenses/gpl-3.0.en.html).
|
||||
|
||||
## Credits
|
||||
|
||||
- Special thanks to the [contributors of Sonar](https://github.com/jonesdevelopment/sonar/graphs/contributors).
|
||||
- The Varint decoding was taken from [Velocity](https://github.com/PaperMC/Velocity).
|
||||
+11
-7
@@ -1,13 +1,9 @@
|
||||
import net.kyori.indra.git.IndraGitExtension
|
||||
|
||||
plugins {
|
||||
java
|
||||
alias(libs.plugins.shadow)
|
||||
alias(libs.plugins.versioner) apply true
|
||||
}
|
||||
|
||||
versioner {
|
||||
pattern {
|
||||
pattern = "$version-%h-%c-%b"
|
||||
}
|
||||
alias(libs.plugins.indra.git) apply true
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -69,10 +65,18 @@ allprojects {
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
val indra = rootProject.extensions.getByType(IndraGitExtension::class.java)
|
||||
val gitBranch = indra.branchName() ?: "unknown"
|
||||
val gitCommit = indra.commit()?.name?.substring(0, 8) ?: "unknown"
|
||||
|
||||
// Set the implementation version, so we can create exact version
|
||||
// information in-game and make it accessible to the user.
|
||||
attributes["Implementation-Title"] = rootProject.name
|
||||
attributes["Implementation-Version"] = rootProject.version
|
||||
attributes["Implementation-Vendor"] = "Jones Development, Sonar Contributors"
|
||||
// Include the Git branch and Git commit SHA
|
||||
attributes["Git-Branch"] = gitBranch
|
||||
attributes["Git-Commit"] = gitCommit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
group=xyz.jonesdev.sonar
|
||||
version=2.0.32
|
||||
description=Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)
|
||||
version=2.0.35
|
||||
description=A lightweight, effective, and easy-to-use anti-bot plugin for Velocity, BungeeCord, and Bukkit.
|
||||
@@ -1,12 +1,12 @@
|
||||
[versions]
|
||||
netty = "4.1.110.Final"
|
||||
netty = "4.1.111.Final"
|
||||
adventure = "4.17.0"
|
||||
adventure-platform = "4.3.3"
|
||||
bstats = "3.0.2"
|
||||
libby = "2.0.0-SNAPSHOT"
|
||||
|
||||
[plugins]
|
||||
versioner = "io.toolebox.git-versioner:1.6.7"
|
||||
indra-git = "net.kyori.indra.git:3.1.3"
|
||||
shadow = "io.github.goooler.shadow:8.1.7"
|
||||
|
||||
[libraries]
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -89,7 +89,7 @@ public interface Sonar {
|
||||
|
||||
@NotNull
|
||||
default SonarVersion getVersion() {
|
||||
return SonarVersion.GET;
|
||||
return SonarVersion.INSTANCE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -18,35 +18,49 @@
|
||||
package xyz.jonesdev.sonar.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
@Getter
|
||||
public final class SonarVersion {
|
||||
static final SonarVersion GET = new SonarVersion();
|
||||
private final String semanticVersion, full, formatted, commitSHA;
|
||||
private final int build;
|
||||
private final boolean isOnMainBranch;
|
||||
static final SonarVersion INSTANCE = new SonarVersion();
|
||||
|
||||
private final String version, formatted, gitBranch, gitCommit;
|
||||
|
||||
SonarVersion() {
|
||||
final Package manifest = Sonar.class.getPackage();
|
||||
final String versionString = manifest.getImplementationVersion();
|
||||
// No need to null-check since we'll always just throw an exception if
|
||||
// we can't find the version information in this file.
|
||||
final Manifest manifest = getManifest();
|
||||
|
||||
if (versionString == null) {
|
||||
this.full = "<could not retrieve version>";
|
||||
this.semanticVersion = "<unknown version>";
|
||||
this.commitSHA = "<unknown commit>";
|
||||
this.build = -1;
|
||||
this.isOnMainBranch = false;
|
||||
this.formatted = full;
|
||||
Sonar.get().getLogger().warn("Could not find version information. Is the manifest missing?");
|
||||
return;
|
||||
this.version = manifest.getMainAttributes().getValue("Implementation-Version");
|
||||
this.gitBranch = manifest.getMainAttributes().getValue("Git-Branch");
|
||||
this.gitCommit = manifest.getMainAttributes().getValue("Git-Commit");
|
||||
this.formatted = version + " (" + gitCommit + ")";
|
||||
}
|
||||
|
||||
// Taken from
|
||||
// https://github.com/PaperMC/Velocity/pull/1336/
|
||||
private static @NotNull Manifest getManifest() {
|
||||
final String classLocation = "/" + Sonar.class.getName().replace(".", "/") + ".class";
|
||||
final URL resource = Sonar.class.getResource(classLocation);
|
||||
|
||||
if (resource == null) {
|
||||
throw new IllegalStateException("Could not find version information. Is the manifest missing?");
|
||||
}
|
||||
|
||||
this.full = versionString;
|
||||
this.semanticVersion = versionString.split("-")[0];
|
||||
this.commitSHA = versionString.split("-")[1];
|
||||
this.build = Integer.parseInt(versionString.split("-")[2]);
|
||||
this.isOnMainBranch = versionString.split("-")[3].equals("main");
|
||||
this.formatted = semanticVersion + " build " + build + " (" + commitSHA + ")";
|
||||
final String classFilePath = resource.toString().replace("\\", "/");
|
||||
final String archivePath = classFilePath.substring(0, classFilePath.length() - classLocation.length());
|
||||
final String manifestPath = archivePath + "/META-INF/MANIFEST.MF";
|
||||
|
||||
try (final InputStream stream = new URL(manifestPath).openStream()) {
|
||||
return new Manifest(stream);
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -53,7 +53,7 @@ public final class AttackTracker {
|
||||
public void checkIfUnderAttack() {
|
||||
final long joinsPerSecond = Sonar.get().getStatistics().getLoginsPerSecond();
|
||||
final int verifyingPlayers = Sonar.get().getFallback().getConnected().size();
|
||||
final int queuedPlayers = Sonar.get().getFallback().getQueue().getQueuedPlayers().size();
|
||||
final int queuedPlayers = Sonar.get().getFallback().getQueue().getPlayers().size();
|
||||
final int minPlayers = Sonar.get().getConfig().getMinPlayersForAttack();
|
||||
|
||||
if (joinsPerSecond > minPlayers // Check the number of bots/joins per second.
|
||||
@@ -68,7 +68,7 @@ public final class AttackTracker {
|
||||
currentAttack.successfulVerifications = Sonar.get().getVerifiedPlayerController().estimatedSize();
|
||||
currentAttack.failedVerifications = Sonar.get().getStatistics().getTotalFailedVerifications();
|
||||
Sonar.get().getEventManager().publish(new AttackDetectedEvent());
|
||||
Sonar.get().getNotificationHandler().sendAttackNotification();
|
||||
Sonar.get().getNotificationHandler().observe();
|
||||
} else {
|
||||
// Reset attack timer if we're still under attack
|
||||
currentAttack.timer.reset();
|
||||
@@ -125,16 +125,16 @@ public final class AttackTracker {
|
||||
webhook.post(() -> {
|
||||
final SonarConfiguration.Webhook.Embed config = Sonar.get().getConfig().getWebhook().getEmbed();
|
||||
final String description = config.getDescription()
|
||||
.replace("%start-timestamp%", startTimestamp)
|
||||
.replace("%end-timestamp%", endTimestamp)
|
||||
.replace("%duration%", formattedDuration)
|
||||
.replace("%peak-cpu%", peakCPU)
|
||||
.replace("%peak-memory%", peakMem)
|
||||
.replace("%peak-bps%", peakBPS)
|
||||
.replace("%peak-cps%", peakCPS)
|
||||
.replace("%total-blacklisted%", Sonar.DECIMAL_FORMAT.format(blacklisted))
|
||||
.replace("%total-failed%", Sonar.DECIMAL_FORMAT.format(failed))
|
||||
.replace("%total-success%", Sonar.DECIMAL_FORMAT.format(verified));
|
||||
.replace("<start-timestamp>", startTimestamp)
|
||||
.replace("<end-timestamp>", endTimestamp)
|
||||
.replace("<attack-duration>", formattedDuration)
|
||||
.replace("<peak-cpu>", peakCPU)
|
||||
.replace("<peak-memory>", peakMem)
|
||||
.replace("<peak-bps>", peakBPS)
|
||||
.replace("<peak-cps>", peakCPS)
|
||||
.replace("<total-blacklisted>", Sonar.DECIMAL_FORMAT.format(blacklisted))
|
||||
.replace("<total-failed>", Sonar.DECIMAL_FORMAT.format(failed))
|
||||
.replace("<total-success>", Sonar.DECIMAL_FORMAT.format(verified));
|
||||
return new SonarConfiguration.Webhook.Embed(
|
||||
config.getTitle(), config.getTitleUrl(), description, config.getR(), config.getG(), config.getB());
|
||||
});
|
||||
|
||||
@@ -20,13 +20,11 @@ package xyz.jonesdev.sonar.api.command;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@ToString(of = "rawArguments")
|
||||
public final class CommandInvocation {
|
||||
private final InvocationSource sender;
|
||||
private final Subcommand subcommand;
|
||||
private final InvocationSource source;
|
||||
private final String[] rawArguments;
|
||||
}
|
||||
@@ -17,20 +17,17 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.command;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.Ticker;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.Tag;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
@@ -39,92 +36,65 @@ public interface SonarCommand {
|
||||
|
||||
Map<String, List<String>> ARG_TAB_SUGGESTIONS = new HashMap<>();
|
||||
|
||||
Cache<Audience, Long> COMMAND_DELAY = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMillis(500))
|
||||
.ticker(Ticker.systemTicker())
|
||||
.build();
|
||||
|
||||
List<Component> CACHED_HELP_MESSAGE = new ArrayList<>();
|
||||
|
||||
default void handle(final @NotNull InvocationSource source, final String[] args) {
|
||||
if (source.isPlayer()) {
|
||||
// Check if the player actually has the permission to run the command
|
||||
if (!source.getPermissionFunction().test("sonar.command")) {
|
||||
source.sendMessage(Sonar.get().getConfig().getNoPermission());
|
||||
return;
|
||||
}
|
||||
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final long mapTimestamp = COMMAND_DELAY.get(source.getAudience(), audience -> -1L);
|
||||
|
||||
// There were some exploits with spamming commands in the past.
|
||||
// Spamming should be prevented, especially if some heavy operations are done,
|
||||
// which is not the case here but let's still stay safe!
|
||||
if (mapTimestamp > 0L) {
|
||||
source.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDown());
|
||||
|
||||
// Format delay
|
||||
final double left = 0.5D - (timestamp - mapTimestamp) / 1000D;
|
||||
|
||||
source.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDownLeft()
|
||||
.replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left)));
|
||||
return;
|
||||
}
|
||||
|
||||
COMMAND_DELAY.put(source.getAudience(), timestamp);
|
||||
default void handle(final @NotNull InvocationSource source, final String @NotNull [] args) {
|
||||
// Check if the player actually has the permission to run the command
|
||||
if (source.isPlayer() && !source.getPermissionFunction().test("sonar.command")) {
|
||||
source.sendMessage(Sonar.get().getConfig().getNoPermission());
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Subcommand> subcommand = Optional.empty();
|
||||
|
||||
if (args.length > 0) {
|
||||
// Search subcommand if command arguments are present
|
||||
final Optional<Subcommand> subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().stream()
|
||||
subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().stream()
|
||||
.filter(sub -> sub.getInfo().name().equalsIgnoreCase(args[0])
|
||||
|| Arrays.stream(sub.getInfo().aliases())
|
||||
.anyMatch(alias -> alias.equalsIgnoreCase(args[0])))
|
||||
|| Arrays.stream(sub.getInfo().aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0])))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
subcommand.ifPresentOrElse(command -> {
|
||||
// Check permissions for subcommands
|
||||
if (subcommand.isPresent()) {
|
||||
if (!subcommand.get().getInfo().onlyConsole()
|
||||
&& !source.getPermissionFunction().test(subcommand.get().getPermission())) {
|
||||
source.sendMessage(Sonar.get().getConfig().getCommands().getSubCommandNoPerm()
|
||||
.replace("%permission%", subcommand.get().getPermission()));
|
||||
return;
|
||||
}
|
||||
subcommand.get().invoke(source, args);
|
||||
if (!command.getInfo().onlyConsole()
|
||||
&& !source.getPermissionFunction().test(command.getPermission())) {
|
||||
source.sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.subcommand-no-permission"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("permission", command.getPermission())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Invoke subcommand
|
||||
command.invoke(source, args);
|
||||
}, () -> {
|
||||
// Print standard help message
|
||||
source.sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
String.join("<newline>",
|
||||
Sonar.get().getConfig().getMessagesConfig().getStringList("commands.main.header")),
|
||||
Placeholder.unparsed("version", Sonar.get().getVersion().getFormatted()),
|
||||
Placeholder.unparsed("platform", Sonar.get().getPlatform().getDisplayName()),
|
||||
Placeholder.unparsed("copyright-year", String.valueOf(Calendar.getInstance().get(Calendar.YEAR)))));
|
||||
|
||||
// Re-use the old, cached help message since we don't want to scan
|
||||
// for each subcommand and it's arguments/attributes every time
|
||||
// someone runs /sonar since the subcommand don't change
|
||||
for (final Component component : CACHED_HELP_MESSAGE) {
|
||||
source.sendMessage(component);
|
||||
}
|
||||
final Component yes = MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.main.tick"));
|
||||
final Component no = MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.main.cross"));
|
||||
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(command -> {
|
||||
source.sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.main.subcommands"),
|
||||
TagResolver.resolver("suggest-subcommand",
|
||||
(queue, context) -> Tag.styling(ClickEvent.suggestCommand("/sonar " + command.getInfo().name()))),
|
||||
Placeholder.unparsed("subcommand", command.getInfo().name()),
|
||||
Placeholder.unparsed("description", command.getDescription()),
|
||||
Placeholder.unparsed("permission", command.getPermission()),
|
||||
Placeholder.unparsed("aliases", command.getAliases()),
|
||||
Placeholder.component("only-players", command.getInfo().onlyPlayers() ? yes : no),
|
||||
Placeholder.component("only-console", command.getInfo().onlyConsole() ? yes : no)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static void prepareCachedMessages() {
|
||||
// Cache help message
|
||||
CACHED_HELP_MESSAGE.clear();
|
||||
for (final String message : Sonar.get().getConfig().getCommands().getHelpHeader()) {
|
||||
CACHED_HELP_MESSAGE.add(MiniMessage.miniMessage().deserialize(message
|
||||
.replace("%version%", Sonar.get().getVersion().getFormatted())
|
||||
.replace("%platform%", Sonar.get().getPlatform().getDisplayName())
|
||||
.replace("%copyright_year%", String.valueOf(Calendar.getInstance().get(Calendar.YEAR)))));
|
||||
}
|
||||
|
||||
final String subcommandFormat = Sonar.get().getConfig().getCommands().getHelpSubcommands();
|
||||
Sonar.get().getSubcommandRegistry().getSubcommands().forEach(subcommand -> {
|
||||
final Component deserialized = MiniMessage.miniMessage().deserialize(subcommandFormat
|
||||
.replace("%subcommand%", subcommand.getInfo().name())
|
||||
.replace("%description%", subcommand.getInfo().description())
|
||||
.replace("%only_players%", subcommand.getInfo().onlyPlayers() ? "<green>✔</green>" : "<red>✗</red>")
|
||||
.replace("%require_console%", subcommand.getInfo().onlyConsole() ? "<green>✔</green>" : "<red>✗</red>")
|
||||
.replace("%permission%", subcommand.getPermission())
|
||||
.replace("%aliases%", subcommand.getAliases()));
|
||||
CACHED_HELP_MESSAGE.add(deserialized);
|
||||
});
|
||||
|
||||
// Don't re-cache tab suggestions
|
||||
if (!TAB_SUGGESTIONS.isEmpty()) return;
|
||||
// Cache tab suggestions
|
||||
@@ -133,12 +103,10 @@ public interface SonarCommand {
|
||||
if (subcommand.getInfo().aliases().length > 0) {
|
||||
TAB_SUGGESTIONS.addAll(Arrays.asList(subcommand.getInfo().aliases()));
|
||||
}
|
||||
final List<String> parsedArguments = Arrays.stream(subcommand.getInfo().arguments())
|
||||
.map(Argument::value)
|
||||
.collect(Collectors.toList());
|
||||
ARG_TAB_SUGGESTIONS.put(subcommand.getInfo().name(), parsedArguments);
|
||||
final List<String> arguments = Arrays.asList(subcommand.getInfo().arguments());
|
||||
ARG_TAB_SUGGESTIONS.put(subcommand.getInfo().name(), arguments);
|
||||
for (final String alias : subcommand.getInfo().aliases()) {
|
||||
ARG_TAB_SUGGESTIONS.put(alias, parsedArguments);
|
||||
ARG_TAB_SUGGESTIONS.put(alias, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,8 +115,8 @@ public interface SonarCommand {
|
||||
if (arguments.length <= 1) {
|
||||
return TAB_SUGGESTIONS;
|
||||
} else if (arguments.length == 2) {
|
||||
final String subCommandName = arguments[0].toLowerCase();
|
||||
return ARG_TAB_SUGGESTIONS.getOrDefault(subCommandName, emptyList());
|
||||
final String subcommandName = arguments[0].toLowerCase();
|
||||
return ARG_TAB_SUGGESTIONS.getOrDefault(subcommandName, emptyList());
|
||||
}
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
@@ -18,77 +18,85 @@
|
||||
package xyz.jonesdev.sonar.api.command.subcommand;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.InvocationSource;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Getter
|
||||
public abstract class Subcommand {
|
||||
private final @NotNull SubcommandInfo info;
|
||||
private final String permission, aliases, arguments;
|
||||
|
||||
protected static final Sonar SONAR = Sonar.get();
|
||||
|
||||
public Subcommand() {
|
||||
info = getClass().getAnnotation(SubcommandInfo.class);
|
||||
permission = "sonar." + info.name();
|
||||
aliases = info.aliases().length == 0 ? "No aliases."
|
||||
this.info = Objects.requireNonNull(getClass().getAnnotation(SubcommandInfo.class));
|
||||
this.permission = "sonar." + info.name();
|
||||
this.aliases = info.aliases().length == 0 ? "No aliases."
|
||||
: String.join(", ", info.aliases());
|
||||
|
||||
arguments = info.arguments().length == 0 ? ""
|
||||
: Arrays.stream(info.arguments())
|
||||
.map(Argument::value)
|
||||
.collect(Collectors.joining(", "));
|
||||
this.arguments = info.arguments().length == 0 ? ""
|
||||
: String.join(", ", info.arguments());
|
||||
}
|
||||
|
||||
protected static @Nullable InetAddress getInetAddressIfValid(final InvocationSource source, final String rawIP) {
|
||||
final InetAddress inetAddress;
|
||||
try {
|
||||
inetAddress = InetAddress.getByName(rawIP);
|
||||
} catch (UnknownHostException exception) {
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getInvalidIpAddress());
|
||||
private static final Pattern IPv4_REGEX = Pattern.compile("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$");
|
||||
private static final Pattern IPv6_REGEX = Pattern.compile("(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))");
|
||||
|
||||
protected static @Nullable String validateIP(final InvocationSource source, final String raw) {
|
||||
if (!IPv4_REGEX.matcher(raw).matches() && !IPv6_REGEX.matcher(raw).matches()) {
|
||||
source.sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.invalid-ip-address"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return null;
|
||||
}
|
||||
return inetAddress;
|
||||
return raw;
|
||||
}
|
||||
|
||||
protected final void incorrectUsage(final @NotNull InvocationSource sender) {
|
||||
sender.sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", info.name() + " (" + arguments + ")"));
|
||||
protected final void incorrectUsage(final @NotNull InvocationSource invocationSource) {
|
||||
incorrectUsage(invocationSource, info.name() + " (" + arguments + ")");
|
||||
}
|
||||
|
||||
protected final void incorrectUsage(final @NotNull InvocationSource invocationSource, final String usage) {
|
||||
invocationSource.sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.incorrect-usage"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("subcommand-usage", usage)));
|
||||
}
|
||||
|
||||
public final void invoke(final @NotNull InvocationSource invocationSource, final String @NotNull [] arguments) {
|
||||
// Check if the subcommand can only be executed by players
|
||||
if (getInfo().onlyPlayers() && !invocationSource.isPlayer()) {
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getPlayersOnly());
|
||||
if (info.onlyPlayers() && !invocationSource.isPlayer()) {
|
||||
invocationSource.sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.player-only"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the subcommand can only be executed though console
|
||||
if (getInfo().onlyConsole() && invocationSource.isPlayer()) {
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getConsoleOnly());
|
||||
if (info.onlyConsole() && invocationSource.isPlayer()) {
|
||||
invocationSource.sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.console-only"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
final CommandInvocation commandInvocation = new CommandInvocation(invocationSource, this, arguments);
|
||||
|
||||
// The subcommands has arguments which are not present in the executed command
|
||||
if (getInfo().argumentsRequired() && getInfo().arguments().length > 0 && arguments.length <= 1) {
|
||||
invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", getInfo().name() + " (" + getArguments() + ")"));
|
||||
if (info.argumentsRequired() && info.arguments().length > 0 && arguments.length <= 1) {
|
||||
incorrectUsage(invocationSource);
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the sub command
|
||||
execute(commandInvocation);
|
||||
// Execute the sub command from the invocation source with the given arguments
|
||||
execute(new CommandInvocation(invocationSource, arguments));
|
||||
}
|
||||
|
||||
public final @NotNull String getDescription() {
|
||||
final String path = String.format("commands.%s.description", info.name());
|
||||
return Objects.requireNonNull(Sonar.get().getConfig().getMessagesConfig().getString(path));
|
||||
}
|
||||
|
||||
protected abstract void execute(final @NotNull CommandInvocation invocation);
|
||||
|
||||
+1
-8
@@ -17,8 +17,6 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.command.subcommand;
|
||||
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -33,11 +31,6 @@ public @interface SubcommandInfo {
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* @return Description of the subcommand
|
||||
*/
|
||||
String description();
|
||||
|
||||
/**
|
||||
* @return Aliases of the subcommand
|
||||
*/
|
||||
@@ -46,7 +39,7 @@ public @interface SubcommandInfo {
|
||||
/**
|
||||
* @return Arguments of the subcommand
|
||||
*/
|
||||
Argument[] arguments() default {};
|
||||
String[] arguments() default {};
|
||||
|
||||
/**
|
||||
* @return Whether the subcommands requires arguments
|
||||
|
||||
+8
-14
@@ -17,30 +17,24 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.command.subcommand;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
public interface SubcommandRegistry {
|
||||
List<Subcommand> SUBCOMMANDS = new Vector<>();
|
||||
|
||||
@NotNull
|
||||
@Unmodifiable
|
||||
default List<Subcommand> getSubcommands() {
|
||||
return Collections.unmodifiableList(SUBCOMMANDS);
|
||||
}
|
||||
@Getter
|
||||
public final class SubcommandRegistry {
|
||||
private final List<Subcommand> subcommands = new Vector<>();
|
||||
|
||||
/**
|
||||
* Registers a single or multiple subcommand(s)
|
||||
*
|
||||
* @param subcommand Array of Subcommands to register
|
||||
*/
|
||||
default void register(final Subcommand @NotNull ... subcommand) {
|
||||
SUBCOMMANDS.addAll(Arrays.asList(subcommand));
|
||||
public void register(final Subcommand @NotNull ... subcommand) {
|
||||
subcommands.addAll(Arrays.asList(subcommand));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +43,7 @@ public interface SubcommandRegistry {
|
||||
* @param subcommand Array of Subcommands to unregister
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
default void unregister(final Subcommand @NotNull ... subcommand) {
|
||||
SUBCOMMANDS.removeAll(Arrays.asList(subcommand));
|
||||
public void unregister(final Subcommand @NotNull ... subcommand) {
|
||||
subcommands.removeAll(Arrays.asList(subcommand));
|
||||
}
|
||||
}
|
||||
@@ -88,11 +88,6 @@ public final class SimpleYamlConfig {
|
||||
yaml.set(path, v);
|
||||
}
|
||||
|
||||
public int getInt(final String path, final int def) {
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getInt(path, def);
|
||||
}
|
||||
|
||||
public int getInt(final String path) {
|
||||
if (!yaml.contains(path)) {
|
||||
LOGGER.warn("Could not find {} in {}.", path, file.getName());
|
||||
@@ -101,11 +96,6 @@ public final class SimpleYamlConfig {
|
||||
return yaml.getInt(path);
|
||||
}
|
||||
|
||||
public boolean getBoolean(final String path, final boolean def) {
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getBoolean(path, def);
|
||||
}
|
||||
|
||||
public boolean getBoolean(final String path) {
|
||||
if (!yaml.contains(path)) {
|
||||
LOGGER.warn("Could not find {} in {}.", path, file.getName());
|
||||
@@ -114,42 +104,18 @@ public final class SimpleYamlConfig {
|
||||
return yaml.getBoolean(path);
|
||||
}
|
||||
|
||||
public String getString(final String path, final String def) {
|
||||
final Object object = getObject(path, def);
|
||||
if (object instanceof String) {
|
||||
return (String) object;
|
||||
}
|
||||
LOGGER.info("[config] Migrated {} to {}", path, def);
|
||||
set(path, def);
|
||||
return def;
|
||||
}
|
||||
|
||||
public @NotNull String getString(final String path) {
|
||||
if (!yaml.contains(path)) {
|
||||
LOGGER.warn("Could not find {} in {}.", path, file.getName());
|
||||
return "";
|
||||
}
|
||||
final Object object = getObject(path);
|
||||
final Object object = yaml.get(path);
|
||||
if (object instanceof String) {
|
||||
return (String) object;
|
||||
}
|
||||
throw new IllegalStateException("Invalid entry " + path);
|
||||
}
|
||||
|
||||
public Object getObject(final String path, final Object def) {
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.get(path, def);
|
||||
}
|
||||
|
||||
public Object getObject(final String path) {
|
||||
return yaml.get(path);
|
||||
}
|
||||
|
||||
public List<String> getStringList(final String path, final List<String> def) {
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getStringList(path);
|
||||
}
|
||||
|
||||
public List<String> getStringList(final String path) {
|
||||
if (!yaml.contains(path)) {
|
||||
LOGGER.warn("Could not find {} in {}.", path, file.getName());
|
||||
@@ -158,11 +124,6 @@ public final class SimpleYamlConfig {
|
||||
return yaml.getStringList(path);
|
||||
}
|
||||
|
||||
public List<Integer> getIntList(final String path, final List<Integer> def) {
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getIntegerList(path);
|
||||
}
|
||||
|
||||
public List<Integer> getIntList(final String path) {
|
||||
if (!yaml.contains(path)) {
|
||||
LOGGER.warn("Could not find {} in {}.", path, file.getName());
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.j256.ormlite.db.DatabaseType;
|
||||
import lombok.*;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
@@ -102,8 +102,11 @@ public final class SonarConfiguration {
|
||||
return Language.fromCode(property);
|
||||
} catch (Throwable throwable) {
|
||||
LOGGER.error("Could not find requested language: {}", throwable);
|
||||
LOGGER.error("You can view a full list of valid language codes here:");
|
||||
LOGGER.error("https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes");
|
||||
LOGGER.error("If a translation does not exist yet, Sonar will use English (en).");
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
} catch (IOException exception) {
|
||||
LOGGER.error("Error reading language file: {}", exception);
|
||||
}
|
||||
return DEFAULT_FALLBACK_LANGUAGE;
|
||||
@@ -225,19 +228,12 @@ public final class SonarConfiguration {
|
||||
verification.validLocaleRegex = Pattern.compile(generalConfig.getString("verification.checks.valid-locale-regex"));
|
||||
verification.maxLoginPackets = clamp(generalConfig.getInt("verification.checks.max-login-packets"), 128, 8192);
|
||||
|
||||
verification.transfer.enabled = generalConfig.getBoolean("verification.transfer.enabled");
|
||||
verification.transfer.host = generalConfig.getString("verification.transfer.destination-host");
|
||||
verification.transfer.port = clamp(generalConfig.getInt("verification.transfer.destination-port"), 0, 0xffff);
|
||||
|
||||
if (verification.transfer.enabled) {
|
||||
Sonar.get().getLogger().info("Transferring 1.20.5+ clients is enabled. Please make sure to follow the instructions in order for this to work properly.");
|
||||
}
|
||||
|
||||
verification.checkGeyser = generalConfig.getBoolean("verification.check-geyser-players");
|
||||
verification.logConnections = generalConfig.getBoolean("verification.log-connections");
|
||||
verification.logDuringAttack = generalConfig.getBoolean("verification.log-during-attack");
|
||||
verification.debugXYZPositions = generalConfig.getBoolean("verification.debug-xyz-positions");
|
||||
verification.readTimeout = clamp(generalConfig.getInt("verification.read-timeout"), 500, 30000);
|
||||
verification.readTimeout = clamp(generalConfig.getInt("verification.read-timeout"), 1000, 30000);
|
||||
verification.writeTimeout = clamp(generalConfig.getInt("verification.write-timeout"), 1000, 30000);
|
||||
verification.reconnectDelay = clamp(generalConfig.getInt("verification.rejoin-delay"), 0, 100000);
|
||||
verification.rememberTime = clamp(generalConfig.getInt("verification.remember-time"), 0, 86400000);
|
||||
verification.blacklistTime = clamp(generalConfig.getInt("verification.blacklist-time"), 0, 86400000);
|
||||
@@ -260,8 +256,7 @@ public final class SonarConfiguration {
|
||||
|
||||
webhook.embed.title = webhookConfig.getString(embedPath + "title");
|
||||
webhook.embed.titleUrl = webhookConfig.getString(embedPath + "title-url");
|
||||
webhook.embed.description = formatString(String.join("\n",
|
||||
webhookConfig.getStringList(embedPath + "description")));
|
||||
webhook.embed.description = String.join("\n", webhookConfig.getStringList(embedPath + "description"));
|
||||
webhook.embed.r = webhookConfig.getInt(embedPath + "color.red");
|
||||
webhook.embed.g = webhookConfig.getInt(embedPath + "color.green");
|
||||
webhook.embed.b = webhookConfig.getInt(embedPath + "color.blue");
|
||||
@@ -277,87 +272,43 @@ public final class SonarConfiguration {
|
||||
}
|
||||
|
||||
// Messages
|
||||
prefix = formatString(messagesConfig.getString("prefix"));
|
||||
supportUrl = messagesConfig.getString("support-url");
|
||||
|
||||
header = fromList(messagesConfig.getStringList("header"));
|
||||
footer = fromList(messagesConfig.getStringList("footer"));
|
||||
tooManyOnlinePerIp = deserialize(fromList(messagesConfig.getStringList("too-many-online-per-ip")));
|
||||
|
||||
noPermission = formatString(messagesConfig.getString("commands.no-permission"));
|
||||
commands.incorrectCommandUsage = formatString(messagesConfig.getString("commands.incorrect-usage"));
|
||||
commands.invalidIpAddress = formatString(messagesConfig.getString("commands.invalid-ip-address"));
|
||||
commands.playersOnly = formatString(messagesConfig.getString("commands.player-only"));
|
||||
commands.consoleOnly = formatString(messagesConfig.getString("commands.console-only"));
|
||||
commands.commandCoolDown = formatString(messagesConfig.getString("commands.cool-down"));
|
||||
commands.commandCoolDownLeft = formatString(messagesConfig.getString("commands.cool-down-left"));
|
||||
commands.subCommandNoPerm = formatString(messagesConfig.getString("commands.subcommand-no-permission"));
|
||||
commands.helpHeader = messagesConfig.getStringList("commands.main.header");
|
||||
commands.helpSubcommands = formatString(messagesConfig.getString("commands.main.subcommands"));
|
||||
prefix = MiniMessage.miniMessage().deserialize(messagesConfig.getString("prefix"));
|
||||
noPermission = MiniMessage.miniMessage().deserialize(messagesConfig.getString("commands.no-permission"),
|
||||
Placeholder.component("prefix", prefix));
|
||||
|
||||
SonarCommand.prepareCachedMessages();
|
||||
|
||||
commands.reloading = formatString(messagesConfig.getString("commands.reload.start"));
|
||||
commands.reloaded = formatString(messagesConfig.getString("commands.reload.finish"));
|
||||
supportUrl = messagesConfig.getString("support-url");
|
||||
header = MiniMessage.miniMessage().deserialize(
|
||||
String.join("<newline>", messagesConfig.getStringList("header")),
|
||||
Placeholder.unparsed("support-url", supportUrl),
|
||||
Placeholder.component("prefix", prefix));
|
||||
footer = MiniMessage.miniMessage().deserialize(
|
||||
String.join("<newline>", messagesConfig.getStringList("footer")),
|
||||
Placeholder.unparsed("support-url", supportUrl),
|
||||
Placeholder.component("prefix", prefix));
|
||||
|
||||
commands.verboseSubscribed = formatString(messagesConfig.getString("commands.verbose.subscribed"));
|
||||
commands.verboseUnsubscribed = formatString(messagesConfig.getString("commands.verbose.unsubscribed"));
|
||||
tooManyOnlinePerIp = deserializeDisconnectMessage("too-many-online-per-ip");
|
||||
verification.currentlyPreparing = deserializeDisconnectMessage("verification.currently-preparing");
|
||||
verification.tooFastReconnect = deserializeDisconnectMessage("verification.too-fast-reconnect");
|
||||
verification.alreadyVerifying = deserializeDisconnectMessage("verification.already-verifying");
|
||||
verification.alreadyQueued = deserializeDisconnectMessage("verification.already-queued");
|
||||
verification.blacklisted = deserializeDisconnectMessage("verification.blacklisted");
|
||||
verification.invalidUsername = deserializeDisconnectMessage("verification.invalid-username");
|
||||
verification.protocolBlacklisted = deserializeDisconnectMessage("verification.blacklisted-protocol");
|
||||
verification.verificationSuccess = deserializeDisconnectMessage("verification.success");
|
||||
verification.verificationFailed = deserializeDisconnectMessage("verification.failed");
|
||||
|
||||
commands.notificationsSubscribed = formatString(messagesConfig.getString("commands.notify.subscribed"));
|
||||
commands.notificationsUnsubscribed = formatString(messagesConfig.getString("commands.notify.unsubscribed"));
|
||||
verboseAnimation = Collections.unmodifiableList(messagesConfig.getStringList("verbose.animation"));
|
||||
}
|
||||
|
||||
commands.blacklistEmpty = formatString(messagesConfig.getString("commands.blacklist.empty"));
|
||||
commands.blacklistCleared = formatString(messagesConfig.getString("commands.blacklist.cleared"));
|
||||
commands.blacklistSize = formatString(messagesConfig.getString("commands.blacklist.size"));
|
||||
commands.blacklistAdd = formatString(messagesConfig.getString("commands.blacklist.added"));
|
||||
commands.blacklistAddWarning = formatString(messagesConfig.getString("commands.blacklist.added-warning"));
|
||||
commands.blacklistRemove = formatString(messagesConfig.getString("commands.blacklist.removed"));
|
||||
commands.blacklistDuplicate = formatString(messagesConfig.getString("commands.blacklist.duplicate-ip"));
|
||||
commands.blacklistNotFound = formatString(messagesConfig.getString("commands.blacklist.ip-not-found"));
|
||||
|
||||
commands.verifiedEmpty = formatString(messagesConfig.getString("commands.verified.empty"));
|
||||
commands.verifiedCleared = formatString(messagesConfig.getString("commands.verified.cleared"));
|
||||
commands.verifiedSize = formatString(messagesConfig.getString("commands.verified.size"));
|
||||
commands.verifiedRemove = formatString(messagesConfig.getString("commands.verified.removed"));
|
||||
commands.verifiedAdd = formatString(messagesConfig.getString("commands.verified.added"));
|
||||
commands.verifiedAlready = formatString(messagesConfig.getString("commands.verified.already"));
|
||||
commands.verifiedNotFound = formatString(messagesConfig.getString("commands.verified.ip-not-found"));
|
||||
commands.verifiedBlocked = formatString(messagesConfig.getString("commands.verified.blocked"));
|
||||
|
||||
commands.statisticsHeader = formatString(messagesConfig.getString("commands.statistics.header"));
|
||||
commands.unknownStatisticType = formatString(messagesConfig.getString("commands.statistics.unknown-type"));
|
||||
commands.generalStatistics = formatString(fromList(messagesConfig.getStringList("commands.statistics.general")));
|
||||
commands.cpuStatistics = formatString(fromList(messagesConfig.getStringList("commands.statistics.cpu")));
|
||||
commands.memoryStatistics = formatString(fromList(messagesConfig.getStringList("commands.statistics.memory")));
|
||||
commands.networkStatistics = formatString(fromList(messagesConfig.getStringList("commands.statistics.network")));
|
||||
|
||||
verification.connectLog = formatString(messagesConfig.getString("verification.logs.connection"));
|
||||
verification.failedLog = formatString(messagesConfig.getString("verification.logs.failed"));
|
||||
verification.blacklistLog = formatString(messagesConfig.getString("verification.logs.blacklisted"));
|
||||
verification.successLog = formatString(messagesConfig.getString("verification.logs.successful"));
|
||||
|
||||
verification.map.enterCode = deserialize(formatString(messagesConfig.getString("verification.captcha.enter-code")));
|
||||
verification.map.failedCaptcha = deserialize(formatString(messagesConfig.getString("verification.captcha.incorrect")));
|
||||
verification.currentlyPreparing = deserialize(fromList(messagesConfig.getStringList("verification.currently-preparing")));
|
||||
verification.tooFastReconnect = deserialize(fromList(messagesConfig.getStringList("verification.too-fast-reconnect")));
|
||||
verification.alreadyVerifying = deserialize(fromList(messagesConfig.getStringList("verification.already-verifying")));
|
||||
verification.alreadyQueued = deserialize(fromList(messagesConfig.getStringList("verification.already-queued")));
|
||||
verification.blacklisted = deserialize(fromList(messagesConfig.getStringList("verification.blacklisted")));
|
||||
verification.invalidUsername = deserialize(fromList(messagesConfig.getStringList("verification.invalid-username")));
|
||||
verification.protocolBlacklisted = deserialize(fromList(messagesConfig.getStringList("verification.blacklisted-protocol")));
|
||||
verification.verificationSuccess = deserialize(fromList(messagesConfig.getStringList("verification.success")));
|
||||
verification.verificationFailed = deserialize(fromList(messagesConfig.getStringList("verification.failed")));
|
||||
|
||||
verbose.actionBarLayout = deserialize(formatString(messagesConfig.getString("verbose.layout.normal")));
|
||||
verbose.actionBarLayoutDuringAttack = deserialize(formatString(messagesConfig.getString("verbose.layout.attack")));
|
||||
verbose.animation = Collections.unmodifiableList(messagesConfig.getStringList("verbose.animation"));
|
||||
|
||||
notifications.notificationTitle = deserialize(formatString(messagesConfig.getString("notifications.title")));
|
||||
notifications.notificationSubtitle = deserialize(formatString(messagesConfig.getString("notifications.subtitle")));
|
||||
notifications.title = Title.title(
|
||||
Sonar.get().getConfig().getNotifications().getNotificationTitle(),
|
||||
Sonar.get().getConfig().getNotifications().getNotificationSubtitle());
|
||||
notifications.notificationChat = deserialize(formatString(fromList(messagesConfig.getStringList("notifications.chat"))));
|
||||
private @NotNull Component deserializeDisconnectMessage(final String path) {
|
||||
return MiniMessage.miniMessage().deserialize(
|
||||
String.join("<newline>", messagesConfig.getStringList(path)),
|
||||
Placeholder.component("prefix", prefix),
|
||||
Placeholder.component("header", header),
|
||||
Placeholder.component("footer", footer),
|
||||
Placeholder.unparsed("support-url", supportUrl));
|
||||
}
|
||||
|
||||
private @NotNull URL getAsset(final String url, final @NotNull Language language) {
|
||||
@@ -382,26 +333,6 @@ public final class SonarConfiguration {
|
||||
return logPlayerAddresses ? inetAddress.toString() : "/<ip address withheld>";
|
||||
}
|
||||
|
||||
private @NotNull String fromList(final @NotNull Collection<String> list) {
|
||||
return formatString(String.join("<newline>", list));
|
||||
}
|
||||
|
||||
private static @NotNull Component deserialize(final String legacy) {
|
||||
return MiniMessage.miniMessage().deserialize(legacy);
|
||||
}
|
||||
|
||||
private @NotNull String formatString(final @NotNull String str) {
|
||||
return str
|
||||
.replace("%prefix%", prefix == null ? "" : prefix)
|
||||
.replace("%support-url%", supportUrl == null ? "" : supportUrl)
|
||||
.replace("%header%", header == null ? "" : header)
|
||||
.replace("%footer%", footer == null ? "" : footer);
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final Verbose verbose = new Verbose();
|
||||
@Getter
|
||||
private final Commands commands = new Commands();
|
||||
@Getter
|
||||
private final Queue queue = new Queue();
|
||||
@Getter
|
||||
@@ -410,15 +341,14 @@ public final class SonarConfiguration {
|
||||
private final Database database = new Database();
|
||||
@Getter
|
||||
private final Webhook webhook = new Webhook();
|
||||
@Getter
|
||||
private final Notifications notifications = new Notifications();
|
||||
|
||||
@Getter
|
||||
private String prefix;
|
||||
private Component prefix;
|
||||
private String supportUrl;
|
||||
private Component header;
|
||||
private Component footer;
|
||||
@Getter
|
||||
private String noPermission;
|
||||
private String header, footer;
|
||||
private Component noPermission;
|
||||
private boolean logPlayerAddresses;
|
||||
@Getter
|
||||
private int maxOnlinePerIp;
|
||||
@@ -432,23 +362,8 @@ public final class SonarConfiguration {
|
||||
private int attackCooldownDelay;
|
||||
@Getter
|
||||
private Component tooManyOnlinePerIp;
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static final class Verbose {
|
||||
private Component actionBarLayout;
|
||||
private Component actionBarLayoutDuringAttack;
|
||||
private List<String> animation;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static final class Notifications {
|
||||
private Title title;
|
||||
private Component notificationTitle;
|
||||
private Component notificationSubtitle;
|
||||
private Component notificationChat;
|
||||
}
|
||||
private List<String> verboseAnimation;
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@@ -475,7 +390,6 @@ public final class SonarConfiguration {
|
||||
private final Gravity gravity = new Gravity();
|
||||
private final Vehicle vehicle = new Vehicle();
|
||||
private final Brand brand = new Brand();
|
||||
private final Transfer transfer = new Transfer();
|
||||
|
||||
@Getter
|
||||
public static final class Map {
|
||||
@@ -489,8 +403,6 @@ public final class SonarConfiguration {
|
||||
private int maxDuration;
|
||||
private int maxTries;
|
||||
private String dictionary;
|
||||
private Component enterCode;
|
||||
private Component failedCaptcha;
|
||||
|
||||
private Smear distortion = new Smear();
|
||||
|
||||
@@ -537,26 +449,16 @@ public final class SonarConfiguration {
|
||||
private Pattern validRegex;
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static final class Transfer {
|
||||
private boolean enabled;
|
||||
private String host;
|
||||
private int port;
|
||||
}
|
||||
|
||||
private boolean checkGeyser;
|
||||
private boolean logConnections;
|
||||
private boolean logDuringAttack;
|
||||
private boolean debugXYZPositions;
|
||||
private Pattern validNameRegex;
|
||||
private Pattern validLocaleRegex;
|
||||
private String connectLog;
|
||||
private String failedLog;
|
||||
private String successLog;
|
||||
private String blacklistLog;
|
||||
|
||||
private int maxLoginPackets;
|
||||
private int readTimeout;
|
||||
private int writeTimeout;
|
||||
private int reconnectDelay;
|
||||
private int rememberTime;
|
||||
private int blacklistTime;
|
||||
@@ -575,55 +477,6 @@ public final class SonarConfiguration {
|
||||
private Component currentlyPreparing;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static final class Commands {
|
||||
private String incorrectCommandUsage;
|
||||
private String invalidIpAddress;
|
||||
private String subCommandNoPerm;
|
||||
private String playersOnly;
|
||||
private String consoleOnly;
|
||||
private String commandCoolDown;
|
||||
private String commandCoolDownLeft;
|
||||
|
||||
private String blacklistEmpty;
|
||||
private String blacklistAdd;
|
||||
private String blacklistAddWarning;
|
||||
private String blacklistDuplicate;
|
||||
private String blacklistNotFound;
|
||||
private String blacklistRemove;
|
||||
private String blacklistCleared;
|
||||
private String blacklistSize;
|
||||
|
||||
private String verifiedRemove;
|
||||
private String verifiedAdd;
|
||||
private String verifiedAlready;
|
||||
private String verifiedNotFound;
|
||||
private String verifiedCleared;
|
||||
private String verifiedSize;
|
||||
private String verifiedEmpty;
|
||||
private String verifiedBlocked;
|
||||
|
||||
private String statisticsHeader;
|
||||
private String unknownStatisticType;
|
||||
private String generalStatistics;
|
||||
private String memoryStatistics;
|
||||
private String networkStatistics;
|
||||
private String cpuStatistics;
|
||||
|
||||
private List<String> helpHeader;
|
||||
private String helpSubcommands;
|
||||
|
||||
private String verboseSubscribed;
|
||||
private String verboseUnsubscribed;
|
||||
|
||||
private String notificationsSubscribed;
|
||||
private String notificationsUnsubscribed;
|
||||
|
||||
private String reloading;
|
||||
private String reloaded;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static final class Database {
|
||||
|
||||
-11
@@ -33,7 +33,6 @@ import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
@@ -293,16 +292,6 @@ public final class VerifiedPlayerController {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param inetAddress IP address
|
||||
* @param uuid UUID associated to the IP
|
||||
* @return Whether the local cache contains the IP and UUID
|
||||
* @see #has(String, UUID)
|
||||
*/
|
||||
public boolean has(final @NotNull InetAddress inetAddress, final @NotNull UUID uuid) {
|
||||
return has(inetAddress.toString(), uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param inetAddress IP address
|
||||
* @return Whether the local cache contains the IP
|
||||
|
||||
@@ -37,7 +37,7 @@ public final class FallbackQueue {
|
||||
private static final int THREAD_POOL_SIZE = Math.min(0x7fff, Runtime.getRuntime().availableProcessors() * 2);
|
||||
private static final ExecutorService QUEUE_EXECUTOR = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
|
||||
|
||||
private final ConcurrentMap<InetAddress, Runnable> queuedPlayers = new ConcurrentHashMap<>(
|
||||
private final ConcurrentMap<InetAddress, Runnable> players = new ConcurrentHashMap<>(
|
||||
128, 0.75f, Runtime.getRuntime().availableProcessors());
|
||||
|
||||
public void poll() {
|
||||
@@ -46,7 +46,7 @@ public final class FallbackQueue {
|
||||
|
||||
// Iterate through the map and process up to maxQueuePolls entries
|
||||
// We need to be cautious here since we don't want any concurrency issues
|
||||
final Iterator<Map.Entry<InetAddress, Runnable>> iterator = queuedPlayers.entrySet().iterator();
|
||||
final Iterator<Map.Entry<InetAddress, Runnable>> iterator = players.entrySet().iterator();
|
||||
while (iterator.hasNext() && index++ < maxQueuePolls) {
|
||||
// Run the cached runnable
|
||||
final Map.Entry<InetAddress, Runnable> entry = iterator.next();
|
||||
|
||||
@@ -21,7 +21,6 @@ import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
@@ -60,10 +59,8 @@ public interface FallbackUser {
|
||||
* using our custom Disconnect packet.
|
||||
*
|
||||
* @param reason Disconnect message component
|
||||
* @param duringLogin True, if the player has not joined yet
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
void disconnect(final @NotNull Component reason, final boolean duringLogin);
|
||||
void disconnect(final @NotNull Component reason);
|
||||
|
||||
/**
|
||||
* Takes over the channel and begins the verification process
|
||||
|
||||
+7
-21
@@ -20,10 +20,8 @@ package xyz.jonesdev.sonar.api.fallback.protocol;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@@ -69,7 +67,8 @@ public enum ProtocolVersion {
|
||||
MINECRAFT_1_20(763),
|
||||
MINECRAFT_1_20_2(764),
|
||||
MINECRAFT_1_20_3(765),
|
||||
MINECRAFT_1_20_5(766);
|
||||
MINECRAFT_1_20_5(766),
|
||||
MINECRAFT_1_21(767);
|
||||
|
||||
private final int protocol;
|
||||
|
||||
@@ -77,27 +76,14 @@ public enum ProtocolVersion {
|
||||
public static final Map<Integer, ProtocolVersion> ID_TO_PROTOCOL_CONSTANT;
|
||||
|
||||
static {
|
||||
{
|
||||
final Map<Integer, ProtocolVersion> versions = new HashMap<>();
|
||||
final Map<Integer, ProtocolVersion> versions = new HashMap<>();
|
||||
|
||||
for (final ProtocolVersion version : values()) {
|
||||
versions.putIfAbsent(version.protocol, version);
|
||||
}
|
||||
|
||||
ID_TO_PROTOCOL_CONSTANT = versions;
|
||||
for (final ProtocolVersion version : values()) {
|
||||
versions.putIfAbsent(version.protocol, version);
|
||||
}
|
||||
|
||||
{
|
||||
final Set<ProtocolVersion> versions = EnumSet.noneOf(ProtocolVersion.class);
|
||||
|
||||
for (final ProtocolVersion value : values()) {
|
||||
if (!value.isUnknown()) {
|
||||
versions.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
LATEST_VERSION = (ProtocolVersion) versions.toArray()[versions.size() - 1];
|
||||
}
|
||||
ID_TO_PROTOCOL_CONSTANT = versions;
|
||||
LATEST_VERSION = values()[values().length - 1];
|
||||
}
|
||||
|
||||
public static ProtocolVersion fromId(final int protocol) {
|
||||
|
||||
@@ -77,14 +77,6 @@ public class JVMProcessInformation {
|
||||
return getSystemCPUUsage() / getVirtualCores();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Current system load average in percent
|
||||
* @apiNote This returns the global process load, not the CPU usage
|
||||
*/
|
||||
public double getSystemLoadAverage() {
|
||||
return MX.getSystemLoadAverage() * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Memory (in bytes) the JVM will attempt to use
|
||||
*/
|
||||
|
||||
@@ -63,7 +63,13 @@ public final class VerifiedPlayer {
|
||||
public VerifiedPlayer(final @NotNull InetAddress inetAddress,
|
||||
final @NotNull UUID playerUuid,
|
||||
final long timestamp) {
|
||||
this.inetAddress = inetAddress.toString();
|
||||
this(inetAddress.toString(), playerUuid, timestamp);
|
||||
}
|
||||
|
||||
public VerifiedPlayer(final @NotNull String inetAddress,
|
||||
final @NotNull UUID playerUuid,
|
||||
final long timestamp) {
|
||||
this.inetAddress = inetAddress;
|
||||
this.playerUuid = playerUuid;
|
||||
this.timestamp = new Timestamp(timestamp);
|
||||
}
|
||||
|
||||
@@ -20,33 +20,70 @@ package xyz.jonesdev.sonar.api.verbose;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.title.Title;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
|
||||
@Getter
|
||||
public final class Notification implements Observable {
|
||||
private final @NotNull Collection<UUID> subscribers = new Vector<>(0);
|
||||
public final class Notification extends Observable {
|
||||
|
||||
public void sendAttackNotification() {
|
||||
@Override
|
||||
public void observe() {
|
||||
// Don't prepare component if there are no subscribers
|
||||
Component chat = null;
|
||||
if (subscribers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the chat notification
|
||||
final Component notificationTitle = MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("notifications.title"));
|
||||
final Component notificationSubtitle = MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("notifications.subtitle"));
|
||||
final Title title = Title.title(notificationTitle, notificationSubtitle);
|
||||
|
||||
final List<String> chatNotification = Sonar.get().getConfig().getMessagesConfig().getStringList("notifications.chat");
|
||||
final Component[] chatNotificationComponents = new Component[chatNotification.size()];
|
||||
|
||||
for (int i = 0; i < chatNotificationComponents.length; i++) {
|
||||
chatNotificationComponents[i] = MiniMessage.miniMessage().deserialize(chatNotification.get(i),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("queued", DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getPlayers().size())),
|
||||
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size())),
|
||||
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize())),
|
||||
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalPlayersJoined())),
|
||||
Placeholder.unparsed("logins-per-second", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getLoginsPerSecond())),
|
||||
Placeholder.unparsed("connections-per-second", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getConnectionsPerSecond())),
|
||||
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
|
||||
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalSuccessfulVerifications())),
|
||||
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications())),
|
||||
Placeholder.unparsed("incoming-traffic", Sonar.get().getStatistics().getCurrentIncomingBandwidthFormatted()),
|
||||
Placeholder.unparsed("outgoing-traffic", Sonar.get().getStatistics().getCurrentOutgoingBandwidthFormatted()),
|
||||
Placeholder.unparsed("incoming-traffic-ttl", Sonar.get().getStatistics().getTotalIncomingBandwidthFormatted()),
|
||||
Placeholder.unparsed("outgoing-traffic-ttl", Sonar.get().getStatistics().getTotalOutgoingBandwidthFormatted()),
|
||||
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
|
||||
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
|
||||
Placeholder.unparsed("total-memory", formatMemory(getTotalMemory())),
|
||||
Placeholder.unparsed("max-memory", formatMemory(getMaxMemory())));
|
||||
}
|
||||
|
||||
// Send the title and chat messages to all online players
|
||||
for (final UUID subscriber : Sonar.get().getNotificationHandler().getSubscribers()) {
|
||||
final Audience audience = Sonar.get().audience(subscriber);
|
||||
if (audience == null) continue;
|
||||
|
||||
// Only prepare component if there are subscribers
|
||||
if (chat == null) {
|
||||
chat = replaceStatistic(Sonar.get().getConfig().getNotifications().getNotificationChat());
|
||||
}
|
||||
// Send the message to all online subscribers
|
||||
audience.sendMessage(chat);
|
||||
for (Component component : chatNotificationComponents) {
|
||||
audience.sendMessage(component);
|
||||
}
|
||||
// Send the title to all online subscribers
|
||||
audience.showTitle(Sonar.get().getConfig().getNotifications().getTitle());
|
||||
audience.showTitle(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,97 +17,38 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.verbose;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.statistics.SonarStatistics;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
@Getter
|
||||
public abstract class Observable {
|
||||
protected final @NotNull Collection<UUID> subscribers = new Vector<>(0);
|
||||
|
||||
public interface Observable {
|
||||
Collection<UUID> getSubscribers();
|
||||
public abstract void observe();
|
||||
|
||||
/**
|
||||
* @param uuid UUID of the player
|
||||
* @return Whether the audience is subscribed or not
|
||||
*/
|
||||
default boolean isSubscribed(final @NotNull UUID uuid) {
|
||||
public final boolean isSubscribed(final @NotNull UUID uuid) {
|
||||
return getSubscribers().contains(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uuid UUID of the player
|
||||
*/
|
||||
default void subscribe(final UUID uuid) {
|
||||
public final void subscribe(final UUID uuid) {
|
||||
getSubscribers().add(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uuid UUID of the player
|
||||
*/
|
||||
default void unsubscribe(final UUID uuid) {
|
||||
public final void unsubscribe(final UUID uuid) {
|
||||
getSubscribers().remove(uuid);
|
||||
}
|
||||
|
||||
// TODO: Make this a bit prettier and better for performance
|
||||
default @NotNull Component replaceStatistic(final @NotNull Component component) {
|
||||
final SonarStatistics statistics = Sonar.get().getStatistics();
|
||||
return component
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%queued%")
|
||||
.replacement(DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getQueuedPlayers().size()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%verifying%")
|
||||
.replacement(DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%blacklisted%")
|
||||
.replacement(DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%total-joins%")
|
||||
.replacement(DECIMAL_FORMAT.format(statistics.getTotalPlayersJoined()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%logins-per-second%")
|
||||
.replacement(DECIMAL_FORMAT.format(statistics.getLoginsPerSecond()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%connections-per-second%")
|
||||
.replacement(DECIMAL_FORMAT.format(statistics.getConnectionsPerSecond()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%verify-total%")
|
||||
.replacement(DECIMAL_FORMAT.format(statistics.getTotalAttemptedVerifications()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%verify-success%")
|
||||
.replacement(DECIMAL_FORMAT.format(statistics.getTotalSuccessfulVerifications()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%verify-failed%")
|
||||
.replacement(DECIMAL_FORMAT.format(statistics.getTotalFailedVerifications()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%incoming-traffic%")
|
||||
.replacement(statistics.getCurrentIncomingBandwidthFormatted())
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%outgoing-traffic%")
|
||||
.replacement(statistics.getCurrentOutgoingBandwidthFormatted())
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%incoming-traffic-ttl%")
|
||||
.replacement(statistics.getTotalIncomingBandwidthFormatted())
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%outgoing-traffic-ttl%")
|
||||
.replacement(statistics.getTotalOutgoingBandwidthFormatted())
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%used-memory%")
|
||||
.replacement(formatMemory(getUsedMemory()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%free-memory%")
|
||||
.replacement(formatMemory(getFreeMemory()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%total-memory%")
|
||||
.replacement(formatMemory(getTotalMemory()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%max-memory%")
|
||||
.replacement(formatMemory(getMaxMemory()))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -20,52 +20,70 @@ package xyz.jonesdev.sonar.api.verbose;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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.attack.AttackTracker;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.Vector;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
import static xyz.jonesdev.sonar.api.timer.SystemTimer.DATE_FORMATTER;
|
||||
|
||||
@Getter
|
||||
public final class Verbose implements Observable {
|
||||
private final @NotNull Collection<UUID> subscribers = new Vector<>(0);
|
||||
public final class Verbose extends Observable {
|
||||
private int animationIndex;
|
||||
|
||||
// Run action bar verbose
|
||||
public void update() {
|
||||
@Override
|
||||
public void observe() {
|
||||
// Don't prepare component if there are no subscribers
|
||||
Component component = null;
|
||||
if (subscribers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the action bar verbose
|
||||
final AttackTracker.AttackStatistics attackStatistics = Sonar.get().getAttackTracker().getCurrentAttack();
|
||||
final SystemTimer attackTimer = attackStatistics == null ? null : attackStatistics.getDuration();
|
||||
final String attackDuration = attackTimer == null ? "00:00" : DATE_FORMATTER.format(attackTimer.delay());
|
||||
|
||||
final Component actionBarComponent = MiniMessage.miniMessage().deserialize(attackTimer == null
|
||||
? Sonar.get().getConfig().getMessagesConfig().getString("verbose.layout.normal")
|
||||
: Sonar.get().getConfig().getMessagesConfig().getString("verbose.layout.attack"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("attack-duration", attackDuration),
|
||||
Placeholder.unparsed("animation", nextAnimation()),
|
||||
Placeholder.unparsed("queued", DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getPlayers().size())),
|
||||
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size())),
|
||||
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize())),
|
||||
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalPlayersJoined())),
|
||||
Placeholder.unparsed("logins-per-second", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getLoginsPerSecond())),
|
||||
Placeholder.unparsed("connections-per-second", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getConnectionsPerSecond())),
|
||||
Placeholder.unparsed("verify-total", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
|
||||
Placeholder.unparsed("verify-success", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalSuccessfulVerifications())),
|
||||
Placeholder.unparsed("verify-failed", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications())),
|
||||
Placeholder.unparsed("incoming-traffic", Sonar.get().getStatistics().getCurrentIncomingBandwidthFormatted()),
|
||||
Placeholder.unparsed("outgoing-traffic", Sonar.get().getStatistics().getCurrentOutgoingBandwidthFormatted()),
|
||||
Placeholder.unparsed("incoming-traffic-ttl", Sonar.get().getStatistics().getTotalIncomingBandwidthFormatted()),
|
||||
Placeholder.unparsed("outgoing-traffic-ttl", Sonar.get().getStatistics().getTotalOutgoingBandwidthFormatted()),
|
||||
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
|
||||
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
|
||||
Placeholder.unparsed("total-memory", formatMemory(getTotalMemory())),
|
||||
Placeholder.unparsed("max-memory", formatMemory(getMaxMemory())));
|
||||
|
||||
// Send the action bar to all online players
|
||||
for (final UUID subscriber : subscribers) {
|
||||
final Audience audience = Sonar.get().audience(subscriber);
|
||||
if (audience == null) continue;
|
||||
|
||||
// Only prepare component if there are subscribers
|
||||
if (component == null) {
|
||||
final AttackTracker.AttackStatistics attackStatistics = Sonar.get().getAttackTracker().getCurrentAttack();
|
||||
final SystemTimer attackDuration = attackStatistics == null ? null : attackStatistics.getDuration();
|
||||
component = replaceStatistic(attackDuration != null
|
||||
? Sonar.get().getConfig().getVerbose().getActionBarLayoutDuringAttack()
|
||||
: Sonar.get().getConfig().getVerbose().getActionBarLayout())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%attack-duration%")
|
||||
.replacement(attackDuration == null ? "00:00" : DATE_FORMATTER.format(attackDuration.delay()))
|
||||
.build())
|
||||
.replaceText(TextReplacementConfig.builder().once().matchLiteral("%animation%")
|
||||
.replacement(nextAnimation())
|
||||
.build());
|
||||
}
|
||||
// Send the action bar to all online subscribers
|
||||
audience.sendActionBar(component);
|
||||
audience.sendActionBar(actionBarComponent);
|
||||
}
|
||||
}
|
||||
|
||||
public String nextAnimation() {
|
||||
final var animations = Sonar.get().getConfig().getVerbose().getAnimation();
|
||||
final var animations = Sonar.get().getConfig().getVerboseAnimation();
|
||||
final int nextIndex = ++animationIndex % animations.size();
|
||||
return animations.toArray(new String[0])[nextIndex];
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ plugins {
|
||||
bukkit {
|
||||
name = rootProject.name
|
||||
description = rootProject.description
|
||||
version = rootProject.version.toString().split("-")[0]
|
||||
version = rootProject.version.toString()
|
||||
main = "xyz.jonesdev.sonar.bukkit.SonarBukkitPlugin"
|
||||
authors = listOf("Jones Development", "Sonar Contributors")
|
||||
website = "https://jonesdev.xyz/discord/"
|
||||
|
||||
@@ -5,7 +5,7 @@ plugins {
|
||||
bungee {
|
||||
name = rootProject.name
|
||||
description = rootProject.description
|
||||
version = rootProject.version.toString().split("-")[0]
|
||||
version = rootProject.version.toString()
|
||||
main = "xyz.jonesdev.sonar.bungee.SonarBungeePlugin"
|
||||
author = "Jones Development, Sonar Contributors"
|
||||
softDepends = setOf("Geyser-BungeeCord", "floodgate", "Protocolize", "ViaVersion", "packetevents", "FastLogin")
|
||||
|
||||
+12
-5
@@ -29,7 +29,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.common.fallback.FallbackChannelHandlerAdapter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import static net.md_5.bungee.netty.PipelineUtils.*;
|
||||
@@ -40,12 +41,12 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
|
||||
super(channel);
|
||||
}
|
||||
|
||||
private static final Field CHANNEL_WRAPPER_FIELD;
|
||||
private static final MethodHandle CHANNEL_WRAPPER_GETTER;
|
||||
|
||||
static {
|
||||
try {
|
||||
CHANNEL_WRAPPER_FIELD = HandlerBoss.class.getDeclaredField("channel");
|
||||
CHANNEL_WRAPPER_FIELD.setAccessible(true);
|
||||
CHANNEL_WRAPPER_GETTER = MethodHandles.privateLookupIn(HandlerBoss.class, MethodHandles.lookup())
|
||||
.findGetter(HandlerBoss.class, "channel", ChannelWrapper.class);
|
||||
} catch (Exception exception) {
|
||||
throw new ReflectiveOperationException(exception);
|
||||
}
|
||||
@@ -69,10 +70,16 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
|
||||
// Make sure to use the potentially modified, real IP
|
||||
final LoginRequest loginRequest = (LoginRequest) wrappedPacket;
|
||||
final HandlerBoss handlerBoss = channel.pipeline().get(HandlerBoss.class);
|
||||
final ChannelWrapper channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_FIELD.get(handlerBoss);
|
||||
final ChannelWrapper channelWrapper;
|
||||
try {
|
||||
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_GETTER.invokeExact(handlerBoss);
|
||||
} catch (final Throwable throwable) {
|
||||
throw new ReflectiveOperationException(throwable);
|
||||
}
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
|
||||
handleLogin(ctx, msg, loginRequest.getData(), socketAddress,
|
||||
PACKET_ENCODER, PACKET_DECODER, TIMEOUT_HANDLER, BOSS_HANDLER);
|
||||
packetWrapper.trySingleRelease();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.api.verbose.Notification;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
|
||||
import xyz.jonesdev.sonar.common.service.SonarServiceThreadManager;
|
||||
import xyz.jonesdev.sonar.common.service.SonarServiceManager;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
import xyz.jonesdev.sonar.common.subcommand.SubcommandRegistryHolder;
|
||||
import xyz.jonesdev.sonar.common.subcommands.*;
|
||||
import xyz.jonesdev.sonar.common.update.UpdateChecker;
|
||||
|
||||
import java.io.File;
|
||||
@@ -76,14 +76,23 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
this.verboseHandler = new Verbose();
|
||||
this.notificationHandler = new Notification();
|
||||
this.config = new SonarConfiguration(dataDirectory);
|
||||
this.subcommandRegistry = new SubcommandRegistryHolder();
|
||||
this.subcommandRegistry = new SubcommandRegistry();
|
||||
// Register all subcommands
|
||||
this.subcommandRegistry.register(
|
||||
new BlacklistCommand(),
|
||||
new VerifiedCommand(),
|
||||
new StatisticsCommand(),
|
||||
new VerboseCommand(),
|
||||
new ReloadCommand(),
|
||||
new DumpCommand(),
|
||||
new NotifyCommand());
|
||||
// Hide unnecessary debug information
|
||||
Logger.setGlobalLogLevel(Level.WARNING);
|
||||
}
|
||||
|
||||
public final void initialize() {
|
||||
// Check if the branch is not the main branch to warn about unstable versions
|
||||
if (!getVersion().isOnMainBranch()) {
|
||||
if (!getVersion().getGitBranch().equals("main")) {
|
||||
getLogger().warn("You are currently using an unofficial experimental branch.");
|
||||
getLogger().warn("It is highly recommended to use the latest stable release of Sonar:");
|
||||
getLogger().warn("https://github.com/jonesdevelopment/sonar/releases");
|
||||
@@ -104,7 +113,7 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
|
||||
// Start threads
|
||||
getLogger().info("Starting all managed threads...");
|
||||
SonarServiceThreadManager.start();
|
||||
SonarServiceManager.start();
|
||||
|
||||
// Done
|
||||
getLogger().info("Done ({}s)!", launchTimer);
|
||||
@@ -115,8 +124,10 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
return; // Do not check for updates if the launch failed
|
||||
}
|
||||
|
||||
// Check if a new version has been released
|
||||
UpdateChecker.checkForUpdates();
|
||||
// Check if a new version has been released if enabled in the configuration
|
||||
if (Sonar.get().getConfig().getGeneralConfig().getBoolean("general.check-for-updates")) {
|
||||
UpdateChecker.checkForUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void enable();
|
||||
@@ -173,10 +184,10 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
}
|
||||
|
||||
public final void shutdown() {
|
||||
// Initialize shutdown process
|
||||
// Initialize the shutdown process
|
||||
getLogger().info("Starting shutdown process...");
|
||||
// Interrupt threads
|
||||
SonarServiceThreadManager.stop();
|
||||
SonarServiceManager.stop();
|
||||
// Close database connection if present
|
||||
if (verifiedPlayerController != null) {
|
||||
verifiedPlayerController.close();
|
||||
|
||||
+21
-28
@@ -54,8 +54,6 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
public final void channelActive(final @NotNull ChannelHandlerContext ctx) throws Exception {
|
||||
// Increase connections per second for the action bar verbose
|
||||
GlobalSonarStatistics.countConnection();
|
||||
// Hook the traffic listener
|
||||
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
|
||||
// Make sure to let the server handle the rest
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
@@ -66,18 +64,11 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
// Account for this by checking if the inetAddress has been set yet
|
||||
if (inetAddress != null) {
|
||||
// Remove the IP address from the connected players
|
||||
FALLBACK.getConnected().remove(inetAddress);
|
||||
FALLBACK.getConnected().compute(inetAddress, (key, value) -> null);
|
||||
// Remove the IP address from the queue
|
||||
FALLBACK.getQueue().getQueuedPlayers().remove(inetAddress);
|
||||
// Remove this account from the online players
|
||||
// or decrement the number of accounts with the same IP
|
||||
FALLBACK.getOnline().compute(inetAddress, (key, value) -> {
|
||||
if (value == null || value <= 1) {
|
||||
return null;
|
||||
} else {
|
||||
return value - 1;
|
||||
}
|
||||
});
|
||||
FALLBACK.getQueue().getPlayers().compute(inetAddress, (key, value) -> null);
|
||||
// Remove this account from the online players or decrement the number of accounts with the same IP
|
||||
FALLBACK.getOnline().compute(inetAddress, (key, value) -> value == null || value <= 1 ? null : value - 1);
|
||||
}
|
||||
// Make sure to let the server handle the rest
|
||||
ctx.fireChannelInactive();
|
||||
@@ -110,6 +101,8 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
}
|
||||
// Store the protocol version
|
||||
protocolVersion = ProtocolVersion.fromId(protocol);
|
||||
// Hook the traffic listener
|
||||
channel.pipeline().addFirst(FALLBACK_BANDWIDTH, FallbackBandwidthHandler.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +143,18 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the player is already queued since we don't want bots to flood the queue
|
||||
if (FALLBACK.getQueue().getPlayers().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, alreadyQueued, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Fallback is already verifying a player with the same IP address
|
||||
if (FALLBACK.getConnected().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, alreadyVerifying, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
|
||||
initialLogin(ctx, loginPacket, encoder, handler);
|
||||
@@ -163,18 +168,6 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the player is already queued since we don't want bots to flood the queue
|
||||
if (FALLBACK.getQueue().getQueuedPlayers().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, alreadyQueued, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Fallback is already verifying a player with the same IP address
|
||||
if (FALLBACK.getConnected().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, alreadyVerifying, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the protocol ID of the player is not allowed to enter the server
|
||||
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols()
|
||||
.contains(protocolVersion.getProtocol())) {
|
||||
@@ -185,7 +178,7 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
// Make sure we actually have to verify the player
|
||||
final String offlineUUIDString = "OfflinePlayer:" + username;
|
||||
final UUID offlineUUID = UUID.nameUUIDFromBytes(offlineUUIDString.getBytes(StandardCharsets.UTF_8));
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, offlineUUID)) {
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress.toString(), offlineUUID)) {
|
||||
initialLogin(ctx, loginPacket, encoder, handler);
|
||||
return;
|
||||
}
|
||||
@@ -204,7 +197,7 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
}
|
||||
|
||||
// Queue the connection for further processing
|
||||
FALLBACK.getQueue().getQueuedPlayers().put(inetAddress, () -> {
|
||||
FALLBACK.getQueue().getPlayers().compute(inetAddress, (key, value) -> () -> {
|
||||
// Check if the username matches the valid name regex to prevent
|
||||
// UTF-16 names or other types of exploits
|
||||
if (!Sonar.get().getConfig().getVerification().getValidNameRegex()
|
||||
@@ -239,7 +232,7 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
final int onlinePerIp = FALLBACK.getOnline().getOrDefault(inetAddress, 0);
|
||||
if (onlinePerIp > maxOnlinePerIp) {
|
||||
if (onlinePerIp >= maxOnlinePerIp) {
|
||||
customDisconnect(channel, protocolVersion, tooManyOnlinePerIP, encoder, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-8
@@ -18,7 +18,6 @@
|
||||
package xyz.jonesdev.sonar.common.fallback;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -26,18 +25,13 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class FallbackTimeoutHandler extends IdleStateHandler {
|
||||
public FallbackTimeoutHandler(final long timeout, final TimeUnit timeUnit) {
|
||||
super(timeout, 0L, 0L, timeUnit);
|
||||
public FallbackTimeoutHandler(final int readTimeout, final int writeTimeout, final TimeUnit timeUnit) {
|
||||
super(readTimeout, writeTimeout, 0L, timeUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelIdle(final ChannelHandlerContext ctx,
|
||||
final @NotNull IdleStateEvent idleStateEvent) throws Exception {
|
||||
// We are only checking the read timeout state - skip all other states
|
||||
if (idleStateEvent.state() != IdleState.READER_IDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The netty (default) ReadTimeoutHandler would normally just throw an Exception
|
||||
// The default ReadTimeoutHandler does only check for the boolean 'closed' and
|
||||
// still throws the Exception even if the channel is closed
|
||||
|
||||
+38
-37
@@ -53,7 +53,7 @@ import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry.GAME;
|
||||
|
||||
@Getter
|
||||
@ToString(of = {"protocolVersion", "inetAddress"})
|
||||
@ToString(of = {"protocolVersion", "inetAddress", "geyser"})
|
||||
public final class FallbackUserWrapper implements FallbackUser {
|
||||
private final Channel channel;
|
||||
private final ChannelPipeline pipeline;
|
||||
@@ -78,7 +78,9 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(final @NotNull Component reason, final boolean duringLogin) {
|
||||
public void disconnect(final @NotNull Component reason) {
|
||||
final FallbackPacketEncoder encoder = pipeline.get(FallbackPacketEncoder.class);
|
||||
final boolean duringLogin = encoder != null && encoder.getPacketRegistry() != GAME;
|
||||
closeWith(channel, protocolVersion, DisconnectPacket.create(reason, duringLogin));
|
||||
}
|
||||
|
||||
@@ -94,29 +96,32 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
// We let the user override this through the configuration.
|
||||
if (Sonar.get().getAttackTracker().getCurrentAttack() == null
|
||||
|| Sonar.get().getConfig().getVerification().isLogDuringAttack()) {
|
||||
Sonar.get().getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getConnectLog()
|
||||
.replace("%name%", username)
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(inetAddress))
|
||||
.replace("%protocol%", String.valueOf(protocolVersion.getProtocol())));
|
||||
Sonar.get().getFallback().getLogger().info(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.connection")
|
||||
.replace("<username>", username)
|
||||
.replace("<ip>", Sonar.get().getConfig().formatAddress(inetAddress))
|
||||
.replace("<protocol>", String.valueOf(protocolVersion.getProtocol())));
|
||||
}
|
||||
}
|
||||
|
||||
// Call the VerifyJoinEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifyJoinEvent(username, this));
|
||||
|
||||
// Mark the player as connected → verifying players
|
||||
Sonar.get().getFallback().getConnected().put(inetAddress, (byte) 0);
|
||||
|
||||
// Add better timeout handler to avoid known exploits or issues
|
||||
// We also want to timeout bots quickly to avoid flooding
|
||||
final int readTimeout = Sonar.get().getConfig().getVerification().getReadTimeout();
|
||||
pipeline.replace(timeout, timeout, new FallbackTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS));
|
||||
|
||||
// Replace normal encoder to allow custom packets
|
||||
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
|
||||
pipeline.replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
// Mark the player as connected by caching them in a map of verifying players
|
||||
Sonar.get().getFallback().getConnected().compute(inetAddress, (key, value) -> (byte) 0);
|
||||
|
||||
channel.eventLoop().execute(() -> {
|
||||
// Add better timeout handler to avoid known exploits or issues
|
||||
// We also want to timeout bots quickly to avoid flooding
|
||||
pipeline.replace(timeout, timeout, new FallbackTimeoutHandler(
|
||||
Sonar.get().getConfig().getVerification().getReadTimeout(),
|
||||
Sonar.get().getConfig().getVerification().getWriteTimeout(),
|
||||
TimeUnit.MILLISECONDS));
|
||||
|
||||
// Replace normal encoder to allow custom packets
|
||||
final FallbackPacketEncoder newEncoder = new FallbackPacketEncoder(protocolVersion);
|
||||
pipeline.replace(encoder, FALLBACK_PACKET_ENCODER, newEncoder);
|
||||
|
||||
// Remove the main pipeline to completely take over the channel
|
||||
if (pipeline.get(handler) != null) {
|
||||
pipeline.remove(handler);
|
||||
@@ -128,23 +133,17 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
// The LoginSuccess packet has been sent, now we can change the registry state
|
||||
newEncoder.updateRegistry(protocolVersion.compareTo(MINECRAFT_1_20_2) >= 0 ? CONFIG : GAME);
|
||||
|
||||
try {
|
||||
// Replace normal decoder to allow custom packets
|
||||
final FallbackPacketDecoder fallbackPacketDecoder = new FallbackPacketDecoder(protocolVersion);
|
||||
pipeline.replace(decoder, FALLBACK_PACKET_DECODER, fallbackPacketDecoder);
|
||||
// Listen for all incoming packets by setting the packet listener
|
||||
fallbackPacketDecoder.setListener(new FallbackLoginSessionHandler(this, username, uuid));
|
||||
} catch (Throwable throwable) {
|
||||
// This rarely happens when the channel hangs and the player is still connecting
|
||||
// I honestly have no idea how else I'm supposed to fix this
|
||||
channel.close();
|
||||
}
|
||||
// Replace normal decoder to allow custom packets
|
||||
final FallbackPacketDecoder fallbackPacketDecoder = new FallbackPacketDecoder(protocolVersion);
|
||||
pipeline.replace(decoder, FALLBACK_PACKET_DECODER, fallbackPacketDecoder);
|
||||
// Listen for all incoming packets by setting the packet listener
|
||||
fallbackPacketDecoder.setListener(new FallbackLoginSessionHandler(this, username, uuid));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(final @NotNull String reason) {
|
||||
disconnect(Sonar.get().getConfig().getVerification().getVerificationFailed(), false);
|
||||
disconnect(Sonar.get().getConfig().getVerification().getVerificationFailed());
|
||||
|
||||
// Only log the failed message if the server isn't currently under attack.
|
||||
// However, we let the user override this through the configuration.
|
||||
@@ -152,10 +151,11 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
|| Sonar.get().getConfig().getVerification().isLogDuringAttack();
|
||||
|
||||
if (shouldLog) {
|
||||
Sonar.get().getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getFailedLog()
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(getProtocolVersion().getProtocol()))
|
||||
.replace("%reason%", reason));
|
||||
Sonar.get().getFallback().getLogger().info(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.failed")
|
||||
.replace("<ip>", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("<protocol>", String.valueOf(getProtocolVersion().getProtocol()))
|
||||
.replace("<reason>", reason));
|
||||
}
|
||||
|
||||
// Increment number of total failed verifications
|
||||
@@ -189,9 +189,10 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
Sonar.get().getFallback().getBlacklist().put(getInetAddress(), (byte) 0);
|
||||
|
||||
if (shouldLog) {
|
||||
Sonar.get().getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getBlacklistLog()
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(getProtocolVersion().getProtocol())));
|
||||
Sonar.get().getFallback().getLogger().info(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.blacklisted")
|
||||
.replace("<ip>", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("<protocol>", String.valueOf(getProtocolVersion().getProtocol())));
|
||||
}
|
||||
|
||||
// Invalidate the cached entry to ensure memory safety
|
||||
@@ -253,7 +254,7 @@ public final class FallbackUserWrapper implements FallbackUser {
|
||||
final @NotNull String encoder,
|
||||
final @NotNull String boss) {
|
||||
// Remove the main pipeline to completely take over the channel
|
||||
if (channel.pipeline().get(boss) != null) {
|
||||
if (channel.pipeline().context(boss) != null) {
|
||||
channel.pipeline().remove(boss);
|
||||
}
|
||||
final ChannelHandler currentEncoder = channel.pipeline().get(encoder);
|
||||
|
||||
+16
-19
@@ -52,26 +52,23 @@ public final class FallbackPacketDecoder extends ChannelInboundHandlerAdapter {
|
||||
if (msg instanceof ByteBuf) {
|
||||
final ByteBuf byteBuf = (ByteBuf) msg;
|
||||
|
||||
// Release the ByteBuf if the connection is not active
|
||||
// or the ByteBuf doesn't contain any data to avoid
|
||||
// memory leaks or other potential exploits.
|
||||
if (!ctx.channel().isActive() || !byteBuf.isReadable()) {
|
||||
byteBuf.release();
|
||||
return;
|
||||
}
|
||||
|
||||
final int originalReaderIndex = byteBuf.readerIndex();
|
||||
// Read the packet ID and then create the packet from it
|
||||
final int packetId = readVarInt(byteBuf);
|
||||
final FallbackPacket packet = registry.createPacket(packetId);
|
||||
|
||||
// If the packet isn't found, skip it
|
||||
if (packet == null) {
|
||||
byteBuf.readerIndex(originalReaderIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Release the ByteBuf if the connection is not active
|
||||
// or the ByteBuf doesn't contain any data to avoid
|
||||
// memory leaks or other potential exploits.
|
||||
if (!ctx.channel().isActive() || !byteBuf.isReadable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the packet ID and then create the packet from it
|
||||
final int packetId = readVarInt(byteBuf);
|
||||
final FallbackPacket packet = registry.createPacket(packetId);
|
||||
|
||||
// Skip the packet if it's not registered within Sonar's packet registry
|
||||
if (packet == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the packet isn't too large or too small
|
||||
checkPacketSize(byteBuf, packet);
|
||||
|
||||
|
||||
+8
-4
@@ -20,6 +20,7 @@ package xyz.jonesdev.sonar.common.fallback.protocol;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
|
||||
@@ -29,15 +30,18 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
|
||||
|
||||
public final class FallbackPacketEncoder extends MessageToByteEncoder<FallbackPacket> {
|
||||
private final ProtocolVersion protocolVersion;
|
||||
private FallbackPacketRegistry.ProtocolRegistry registry;
|
||||
@Getter
|
||||
private FallbackPacketRegistry packetRegistry;
|
||||
private FallbackPacketRegistry.ProtocolRegistry protocolRegistry;
|
||||
|
||||
public FallbackPacketEncoder(final ProtocolVersion protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
updateRegistry(LOGIN);
|
||||
}
|
||||
|
||||
public void updateRegistry(final @NotNull FallbackPacketRegistry registry) {
|
||||
this.registry = registry.getProtocolRegistry(CLIENTBOUND, protocolVersion);
|
||||
public void updateRegistry(final @NotNull FallbackPacketRegistry packetRegistry) {
|
||||
this.packetRegistry = packetRegistry;
|
||||
this.protocolRegistry = packetRegistry.getProtocolRegistry(CLIENTBOUND, protocolVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,7 +50,7 @@ public final class FallbackPacketEncoder extends MessageToByteEncoder<FallbackPa
|
||||
final ByteBuf out) throws Exception {
|
||||
final FallbackPacket originalPacket = packet instanceof FallbackPacketSnapshot
|
||||
? ((FallbackPacketSnapshot) packet).getOriginalPacket() : packet;
|
||||
final int packetId = registry.getPacketId(originalPacket);
|
||||
final int packetId = protocolRegistry.getPacketId(originalPacket);
|
||||
writeVarInt(out, packetId);
|
||||
packet.encode(out, protocolVersion);
|
||||
}
|
||||
|
||||
+44
-13
@@ -64,7 +64,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x05, MINECRAFT_1_20_2, true),
|
||||
map(0x07, MINECRAFT_1_20_5, true));
|
||||
|
||||
serverbound.register(ClientSettingsPacket.class, ClientSettingsPacket::new,
|
||||
serverbound.register(ClientInformationPacket.class, ClientInformationPacket::new,
|
||||
map(0x00, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(PluginMessagePacket.class, PluginMessagePacket::new,
|
||||
map(0x01, MINECRAFT_1_20_2, false),
|
||||
@@ -124,7 +124,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x1A, MINECRAFT_1_19_4, true),
|
||||
map(0x1B, MINECRAFT_1_20_2, true),
|
||||
map(0x1D, MINECRAFT_1_20_5, true));
|
||||
clientbound.register(PlayerPositionLookPacket.class, PlayerPositionLookPacket::new,
|
||||
clientbound.register(SetPlayerPositionRotation.class, SetPlayerPositionRotation::new,
|
||||
map(0x08, MINECRAFT_1_7_2, true),
|
||||
map(0x2E, MINECRAFT_1_9, true),
|
||||
map(0x2F, MINECRAFT_1_12_1, true),
|
||||
@@ -140,7 +140,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x3C, MINECRAFT_1_19_4, true),
|
||||
map(0x3E, MINECRAFT_1_20_2, true),
|
||||
map(0x40, MINECRAFT_1_20_5, true));
|
||||
clientbound.register(ClientAbilitiesPacket.class, ClientAbilitiesPacket::new,
|
||||
clientbound.register(PlayerAbilitiesPacket.class, PlayerAbilitiesPacket::new,
|
||||
map(0x39, MINECRAFT_1_7_2, true),
|
||||
map(0x2B, MINECRAFT_1_9, true),
|
||||
map(0x2C, MINECRAFT_1_12_1, true),
|
||||
@@ -253,7 +253,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x32, MINECRAFT_1_19_4, true),
|
||||
map(0x33, MINECRAFT_1_20_2, true),
|
||||
map(0x35, MINECRAFT_1_20_5, true));
|
||||
clientbound.register(DefaultSpawnPositionPacket.class, DefaultSpawnPositionPacket::new,
|
||||
clientbound.register(SetDefaultSpawnPositionPacket.class, SetDefaultSpawnPositionPacket::new,
|
||||
map(0x05, MINECRAFT_1_7_2, true),
|
||||
map(0x43, MINECRAFT_1_9, true),
|
||||
map(0x45, MINECRAFT_1_12, true),
|
||||
@@ -284,7 +284,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x29, MINECRAFT_1_19_4, true),
|
||||
map(0x2A, MINECRAFT_1_20_2, true),
|
||||
map(0x2C, MINECRAFT_1_20_5, true));
|
||||
clientbound.register(SetSlotPacket.class, SetSlotPacket::new,
|
||||
clientbound.register(SetContainerSlotPacket.class, SetContainerSlotPacket::new,
|
||||
map(0x2F, MINECRAFT_1_7_2, true),
|
||||
map(0x16, MINECRAFT_1_9, true),
|
||||
map(0x17, MINECRAFT_1_13, true),
|
||||
@@ -312,7 +312,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x58, MINECRAFT_1_20_2, true),
|
||||
map(0x5A, MINECRAFT_1_20_3, true),
|
||||
map(0x5C, MINECRAFT_1_20_5, true));
|
||||
clientbound.register(UniversalChatPacket.class, UniversalChatPacket::new,
|
||||
clientbound.register(SystemChatPacket.class, SystemChatPacket::new,
|
||||
map(0x02, MINECRAFT_1_7_2, false),
|
||||
map(0x0F, MINECRAFT_1_9, false),
|
||||
map(0x0E, MINECRAFT_1_13, false),
|
||||
@@ -331,7 +331,37 @@ public enum FallbackPacketRegistry {
|
||||
map(0x22, MINECRAFT_1_20_5, true));
|
||||
clientbound.register(TransferPacket.class, TransferPacket::new,
|
||||
map(0x73, MINECRAFT_1_20_5, true));
|
||||
clientbound.register(SetHeldItemPacket.class, SetHeldItemPacket::new,
|
||||
map(0x09, MINECRAFT_1_7_2, true),
|
||||
map(0x37, MINECRAFT_1_9, true),
|
||||
map(0x39, MINECRAFT_1_12, true),
|
||||
map(0x3A, MINECRAFT_1_12_1, true),
|
||||
map(0x3D, MINECRAFT_1_13, true),
|
||||
map(0x3F, MINECRAFT_1_14, true),
|
||||
map(0x40, MINECRAFT_1_15, true),
|
||||
map(0x3F, MINECRAFT_1_16, true),
|
||||
map(0x48, MINECRAFT_1_17, true),
|
||||
map(0x47, MINECRAFT_1_19, true),
|
||||
map(0x4A, MINECRAFT_1_19_1, true),
|
||||
map(0x49, MINECRAFT_1_19_3, true),
|
||||
map(0x4D, MINECRAFT_1_19_4, true),
|
||||
map(0x4F, MINECRAFT_1_20_2, true),
|
||||
map(0x51, MINECRAFT_1_20_3, true),
|
||||
map(0x53, MINECRAFT_1_20_5, true));
|
||||
|
||||
serverbound.register(SetHeldItemPacket.class, SetHeldItemPacket::new,
|
||||
map(0x09, MINECRAFT_1_7_2, false),
|
||||
map(0x17, MINECRAFT_1_9, false),
|
||||
map(0x1A, MINECRAFT_1_12, false),
|
||||
map(0x21, MINECRAFT_1_13, false),
|
||||
map(0x23, MINECRAFT_1_14, false),
|
||||
map(0x24, MINECRAFT_1_16, false),
|
||||
map(0x25, MINECRAFT_1_16_2, false),
|
||||
map(0x27, MINECRAFT_1_19, false),
|
||||
map(0x28, MINECRAFT_1_19_1, false),
|
||||
map(0x2B, MINECRAFT_1_20_2, false),
|
||||
map(0x2C, MINECRAFT_1_20_3, false),
|
||||
map(0x2F, MINECRAFT_1_20_5, false));
|
||||
serverbound.register(PaddleBoatPacket.class, PaddleBoatPacket::new,
|
||||
map(0x11, MINECRAFT_1_9, false),
|
||||
map(0x12, MINECRAFT_1_12, false),
|
||||
@@ -362,7 +392,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x22, MINECRAFT_1_20_2, false),
|
||||
map(0x23, MINECRAFT_1_20_3, false),
|
||||
map(0x26, MINECRAFT_1_20_5, false));
|
||||
serverbound.register(UniversalChatPacket.class, UniversalChatPacket::new,
|
||||
serverbound.register(SystemChatPacket.class, SystemChatPacket::new,
|
||||
map(0x01, MINECRAFT_1_7_2, false),
|
||||
map(0x02, MINECRAFT_1_9, false),
|
||||
map(0x03, MINECRAFT_1_12, false),
|
||||
@@ -388,7 +418,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x14, MINECRAFT_1_20_2, false),
|
||||
map(0x15, MINECRAFT_1_20_3, false),
|
||||
map(0x18, MINECRAFT_1_20_5, false));
|
||||
serverbound.register(ClientSettingsPacket.class, ClientSettingsPacket::new,
|
||||
serverbound.register(ClientInformationPacket.class, ClientInformationPacket::new,
|
||||
map(0x15, MINECRAFT_1_7_2, false),
|
||||
map(0x04, MINECRAFT_1_9, false),
|
||||
map(0x05, MINECRAFT_1_12, false),
|
||||
@@ -412,8 +442,9 @@ public enum FallbackPacketRegistry {
|
||||
map(0x0D, MINECRAFT_1_19_1, false),
|
||||
map(0x0C, MINECRAFT_1_19_3, false),
|
||||
map(0x0D, MINECRAFT_1_19_4, false),
|
||||
map(0x0F, MINECRAFT_1_20_2, false));
|
||||
serverbound.register(PlayerPositionLookPacket.class, PlayerPositionLookPacket::new,
|
||||
map(0x0F, MINECRAFT_1_20_2, false),
|
||||
map(0x12, MINECRAFT_1_21, false));
|
||||
serverbound.register(SetPlayerPositionRotation.class, SetPlayerPositionRotation::new,
|
||||
map(0x06, MINECRAFT_1_7_2, false),
|
||||
map(0x0D, MINECRAFT_1_9, false),
|
||||
map(0x0F, MINECRAFT_1_12, false),
|
||||
@@ -429,9 +460,9 @@ public enum FallbackPacketRegistry {
|
||||
map(0x17, MINECRAFT_1_20_2, false),
|
||||
map(0x18, MINECRAFT_1_20_3, false),
|
||||
map(0x1B, MINECRAFT_1_20_5, false));
|
||||
serverbound.register(TeleportConfirmPacket.class, TeleportConfirmPacket::new,
|
||||
serverbound.register(ConfirmTeleportationPacket.class, ConfirmTeleportationPacket::new,
|
||||
map(0x00, MINECRAFT_1_9, false));
|
||||
serverbound.register(PlayerPositionPacket.class, PlayerPositionPacket::new,
|
||||
serverbound.register(SetPlayerPositionPacket.class, SetPlayerPositionPacket::new,
|
||||
map(0x04, MINECRAFT_1_7_2, false),
|
||||
map(0x0C, MINECRAFT_1_9, false),
|
||||
map(0x0E, MINECRAFT_1_12, false),
|
||||
@@ -447,7 +478,7 @@ public enum FallbackPacketRegistry {
|
||||
map(0x16, MINECRAFT_1_20_2, false),
|
||||
map(0x17, MINECRAFT_1_20_3, false),
|
||||
map(0x1A, MINECRAFT_1_20_5, false));
|
||||
serverbound.register(PlayerPositionIdlePacket.class, PlayerPositionIdlePacket::new,
|
||||
serverbound.register(SetPlayerOnGround.class, SetPlayerOnGround::new,
|
||||
map(0x03, MINECRAFT_1_7_2, false),
|
||||
map(0x0F, MINECRAFT_1_9, false),
|
||||
map(0x0D, MINECRAFT_1_12, false),
|
||||
|
||||
+37
-17
@@ -18,6 +18,8 @@
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
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.common.fallback.protocol.block.BlockType;
|
||||
@@ -28,6 +30,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.FinishC
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.RegistryDataPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginSuccessPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.*;
|
||||
import xyz.jonesdev.sonar.common.util.ComponentHolder;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
@@ -38,9 +41,9 @@ public class FallbackPreparer {
|
||||
// LoginSuccess
|
||||
public final FallbackPacket LOGIN_SUCCESS = new FallbackPacketSnapshot(new LoginSuccessPacket(new UUID(1L, 1L), "Sonar"));
|
||||
// Abilities
|
||||
public final FallbackPacket DEFAULT_ABILITIES = new FallbackPacketSnapshot(new ClientAbilitiesPacket(0x00, 0f, 0f));
|
||||
public final FallbackPacket CAPTCHA_ABILITIES = new FallbackPacketSnapshot(new ClientAbilitiesPacket(0x02, 0f, 0f));
|
||||
public final FallbackPacket CAPTCHA_ABILITIES_BEDROCK = new FallbackPacketSnapshot(new ClientAbilitiesPacket(0x06, 0f, 0f));
|
||||
public final FallbackPacket DEFAULT_ABILITIES = new FallbackPacketSnapshot(new PlayerAbilitiesPacket(0x00, 0f, 0f));
|
||||
public final FallbackPacket CAPTCHA_ABILITIES = new FallbackPacketSnapshot(new PlayerAbilitiesPacket(0x02, 0f, 0f));
|
||||
public final FallbackPacket CAPTCHA_ABILITIES_BEDROCK = new FallbackPacketSnapshot(new PlayerAbilitiesPacket(0x06, 0f, 0f));
|
||||
// Chunks
|
||||
public final FallbackPacket EMPTY_CHUNK_DATA = new FallbackPacketSnapshot(new ChunkDataPacket(0, 0));
|
||||
// Finish Configuration
|
||||
@@ -48,6 +51,7 @@ public class FallbackPreparer {
|
||||
// Synchronize Registry
|
||||
public final FallbackPacket REGISTRY_SYNC_LEGACY = new FallbackPacketSnapshot(new RegistryDataPacket(DimensionRegistry.CODEC_1_20, null, null));
|
||||
public final FallbackPacket[] REGISTRY_SYNC_1_20_5 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_20);
|
||||
public final FallbackPacket[] REGISTRY_SYNC_1_21 = RegistryDataPacket.of(DimensionRegistry.CODEC_1_21);
|
||||
// Keep Alive
|
||||
public final FallbackPacket CAPTCHA_KEEP_ALIVE = new FallbackPacketSnapshot(new KeepAlivePacket(-1337L));
|
||||
// Game Event (1.20.3+)
|
||||
@@ -92,10 +96,8 @@ public class FallbackPreparer {
|
||||
public final int DEFAULT_Y_COLLIDE_POSITION = 255; // 255 is the maximum Y position allowed
|
||||
|
||||
// Captcha position
|
||||
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new PlayerPositionLookPacket(
|
||||
public final FallbackPacket CAPTCHA_POSITION = new FallbackPacketSnapshot(new SetPlayerPositionRotation(
|
||||
SPAWN_X_POSITION, 1337, SPAWN_Z_POSITION, 0f, 90f, 0, false));
|
||||
public final FallbackPacket CAPTCHA_SPAWN_POSITION = new FallbackPacketSnapshot(new DefaultSpawnPositionPacket(
|
||||
SPAWN_X_POSITION, 1337, SPAWN_Z_POSITION));
|
||||
|
||||
// Platform
|
||||
public BlockType blockType = BlockType.BARRIER;
|
||||
@@ -109,13 +111,18 @@ public class FallbackPreparer {
|
||||
// XP packets
|
||||
public FallbackPacket[] xpCountdown;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public void prepare() {
|
||||
// Preload the packet registry to avoid CPU/RAM issues on 1st connection
|
||||
Sonar.get().getLogger().info("Preloading all registered packets...");
|
||||
FallbackPacketRegistry.values();
|
||||
|
||||
// Prepare JoinGame packet
|
||||
joinGame = new JoinGamePacket(PLAYER_ENTITY_ID,
|
||||
joinGame = new FallbackPacketSnapshot(new JoinGamePacket(PLAYER_ENTITY_ID,
|
||||
Sonar.get().getConfig().getVerification().getGravity().getGamemode().getId(),
|
||||
0, false, 0,
|
||||
true, false, false,
|
||||
new String[]{"minecraft:overworld"}, "minecraft:overworld");
|
||||
new String[]{"minecraft:overworld"}, "minecraft:overworld"));
|
||||
|
||||
// Prepare cached motions for the gravity check
|
||||
maxFallDistance = 0;
|
||||
@@ -130,9 +137,9 @@ public class FallbackPreparer {
|
||||
|
||||
// Set the dynamic block and collide Y position based on the maximum fall distance
|
||||
dynamicSpawnYPosition = DEFAULT_Y_COLLIDE_POSITION + (int) Math.ceil(maxFallDistance);
|
||||
defaultSpawnPosition = new FallbackPacketSnapshot(new DefaultSpawnPositionPacket(
|
||||
defaultSpawnPosition = new FallbackPacketSnapshot(new SetDefaultSpawnPositionPacket(
|
||||
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION));
|
||||
spawnPosition = new FallbackPacketSnapshot(new PlayerPositionLookPacket(
|
||||
spawnPosition = new FallbackPacketSnapshot(new SetPlayerPositionRotation(
|
||||
SPAWN_X_POSITION, dynamicSpawnYPosition, SPAWN_Z_POSITION,
|
||||
0, -90, TELEPORT_ID, false));
|
||||
|
||||
@@ -163,9 +170,13 @@ public class FallbackPreparer {
|
||||
tooManyOnlinePerIP = new FallbackPacketSnapshot(DisconnectPacket.create(Sonar.get().getConfig().getTooManyOnlinePerIp(), true));
|
||||
|
||||
// Prepare transfer packet
|
||||
transferToOrigin = new FallbackPacketSnapshot(new TransferPacket(
|
||||
Sonar.get().getConfig().getVerification().getTransfer().getHost(),
|
||||
Sonar.get().getConfig().getVerification().getTransfer().getPort()));
|
||||
if (Sonar.get().getConfig().getGeneralConfig().getBoolean("verification.transfer.enabled")) {
|
||||
transferToOrigin = new FallbackPacketSnapshot(new TransferPacket(
|
||||
Sonar.get().getConfig().getGeneralConfig().getString("verification.transfer.destination-host"),
|
||||
Sonar.get().getConfig().getGeneralConfig().getInt("verification.transfer.destination-port")));
|
||||
} else {
|
||||
transferToOrigin = null;
|
||||
}
|
||||
|
||||
// Prepare packets for the vehicle check
|
||||
removeEntities = new FallbackPacketSnapshot(new RemoveEntitiesPacket(VEHICLE_ENTITY_ID));
|
||||
@@ -174,10 +185,14 @@ public class FallbackPreparer {
|
||||
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|
||||
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
|
||||
// Prepare CAPTCHA messages
|
||||
enterCodeMessage = new FallbackPacketSnapshot(new UniversalChatPacket(
|
||||
Sonar.get().getConfig().getVerification().getMap().getEnterCode(), UniversalChatPacket.SYSTEM_TYPE));
|
||||
incorrectCaptcha = new FallbackPacketSnapshot(new UniversalChatPacket(
|
||||
Sonar.get().getConfig().getVerification().getMap().getFailedCaptcha(), UniversalChatPacket.SYSTEM_TYPE));
|
||||
enterCodeMessage = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
|
||||
MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.enter-code"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
|
||||
incorrectCaptcha = new FallbackPacketSnapshot(new SystemChatPacket(new ComponentHolder(
|
||||
MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.captcha.incorrect"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())))));
|
||||
|
||||
// Prepare countdown
|
||||
xpCountdown = new FallbackPacket[Sonar.get().getConfig().getVerification().getMap().getMaxDuration() / 1000];
|
||||
@@ -189,6 +204,11 @@ public class FallbackPreparer {
|
||||
|
||||
// Prepare CAPTCHA answers
|
||||
MAP_INFO_PREPARER.prepare();
|
||||
} else {
|
||||
// Throw away if not needed
|
||||
enterCodeMessage = null;
|
||||
incorrectCaptcha = null;
|
||||
xpCountdown = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -79,6 +79,7 @@ public enum BlockType {
|
||||
case MINECRAFT_1_20_2:
|
||||
case MINECRAFT_1_20_3:
|
||||
case MINECRAFT_1_20_5:
|
||||
case MINECRAFT_1_21:
|
||||
return 10366;
|
||||
}
|
||||
}, 1D);
|
||||
|
||||
+1
@@ -73,6 +73,7 @@ public enum ItemType {
|
||||
case MINECRAFT_1_20_3:
|
||||
return 979;
|
||||
case MINECRAFT_1_20_5:
|
||||
case MINECRAFT_1_21:
|
||||
return 982;
|
||||
}
|
||||
});
|
||||
|
||||
+2
@@ -39,6 +39,7 @@ public final class DimensionRegistry {
|
||||
public final @NotNull CompoundBinaryTag CODEC_1_19_1;
|
||||
public final @NotNull CompoundBinaryTag CODEC_1_19_4;
|
||||
public final @NotNull CompoundBinaryTag CODEC_1_20;
|
||||
public final @NotNull CompoundBinaryTag CODEC_1_21;
|
||||
public final @NotNull CompoundBinaryTag OLD_CODEC;
|
||||
|
||||
static {
|
||||
@@ -48,6 +49,7 @@ public final class DimensionRegistry {
|
||||
CODEC_1_19_1 = getCodec("codec_1_19_1.nbt");
|
||||
CODEC_1_19_4 = getCodec("codec_1_19_4.nbt");
|
||||
CODEC_1_20 = getCodec("codec_1_20.nbt");
|
||||
CODEC_1_21 = getCodec("codec_1_21.nbt");
|
||||
OLD_CODEC = getCodec("codec_old.nbt");
|
||||
|
||||
DEFAULT_DIMENSION_1_16 = getDimension(CODEC_1_16);
|
||||
|
||||
+1
@@ -78,6 +78,7 @@ public enum EntityType {
|
||||
case MINECRAFT_1_20_3:
|
||||
return 9;
|
||||
case MINECRAFT_1_20_5:
|
||||
case MINECRAFT_1_21:
|
||||
return 10;
|
||||
}
|
||||
});
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class ClientSettingsPacket implements FallbackPacket {
|
||||
public final class ClientInformationPacket implements FallbackPacket {
|
||||
private String locale;
|
||||
private byte viewDistance;
|
||||
private int chatVisibility;
|
||||
+1
-1
@@ -31,7 +31,7 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readVarInt;
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class TeleportConfirmPacket implements FallbackPacket {
|
||||
public final class ConfirmTeleportationPacket implements FallbackPacket {
|
||||
private int teleportId;
|
||||
|
||||
@Override
|
||||
+2
-2
@@ -34,11 +34,11 @@ import xyz.jonesdev.sonar.common.util.ComponentHolder;
|
||||
@AllArgsConstructor
|
||||
public final class DisconnectPacket implements FallbackPacket {
|
||||
private @NotNull ComponentHolder componentHolder;
|
||||
private boolean login;
|
||||
private boolean duringLogin;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
|
||||
componentHolder.write(byteBuf, login ? ProtocolVersion.MINECRAFT_1_20_2 : protocolVersion);
|
||||
componentHolder.write(byteBuf, duringLogin ? ProtocolVersion.MINECRAFT_1_20_2 : protocolVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class ClientAbilitiesPacket implements FallbackPacket {
|
||||
public final class PlayerAbilitiesPacket implements FallbackPacket {
|
||||
private int encodedFlags;
|
||||
private float flySpeed, walkSpeed;
|
||||
|
||||
@@ -42,7 +42,7 @@ public final class ClientAbilitiesPacket implements FallbackPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
public void decode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+18
-5
@@ -18,6 +18,7 @@
|
||||
package xyz.jonesdev.sonar.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -25,14 +26,16 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_8;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readRetainedByteBufSlice17;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readExtendedForgeShort;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.readString;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public final class PluginMessagePacket implements FallbackPacket {
|
||||
private String channel;
|
||||
private ByteBuf slicedBuffer;
|
||||
private byte[] data;
|
||||
|
||||
private static final int FORGE_MAX_ARRAY_LENGTH = Integer.MAX_VALUE & 0x1FFF9A;
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
@@ -41,13 +44,23 @@ public final class PluginMessagePacket implements FallbackPacket {
|
||||
|
||||
@Override
|
||||
public void decode(final ByteBuf byteBuf, final @NotNull ProtocolVersion protocolVersion) throws Exception {
|
||||
channel = readString(byteBuf, 64);
|
||||
channel = readString(byteBuf, 48);
|
||||
|
||||
final int length;
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
slicedBuffer = byteBuf.readRetainedSlice(byteBuf.readableBytes());
|
||||
length = byteBuf.readableBytes();
|
||||
if (length > Short.MAX_VALUE) {
|
||||
throw new CorruptedFrameException("Got too much data: " + length);
|
||||
}
|
||||
} else {
|
||||
slicedBuffer = readRetainedByteBufSlice17(byteBuf);
|
||||
length = readExtendedForgeShort(byteBuf);
|
||||
if (length > FORGE_MAX_ARRAY_LENGTH) {
|
||||
throw new CorruptedFrameException("Got too much data: " + length);
|
||||
}
|
||||
}
|
||||
|
||||
data = new byte[length];
|
||||
byteBuf.readBytes(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class SetSlotPacket implements FallbackPacket {
|
||||
public final class SetContainerSlotPacket implements FallbackPacket {
|
||||
private int slot, count, itemId;
|
||||
private CompoundBinaryTag compoundBinaryTag;
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class DefaultSpawnPositionPacket implements FallbackPacket {
|
||||
public final class SetDefaultSpawnPositionPacket implements FallbackPacket {
|
||||
private int x, y, z;
|
||||
|
||||
@Override
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.common.fallback.protocol.packets.play;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class SetHeldItemPacket implements FallbackPacket {
|
||||
private int slot;
|
||||
|
||||
@Override
|
||||
public void encode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) throws Exception {
|
||||
byteBuf.writeByte(slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(final @NotNull ByteBuf byteBuf, final ProtocolVersion protocolVersion) {
|
||||
slot = byteBuf.readShort();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -30,7 +30,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class PlayerPositionIdlePacket implements FallbackPacket {
|
||||
public final class SetPlayerOnGround implements FallbackPacket {
|
||||
private boolean onGround;
|
||||
|
||||
@Override
|
||||
+2
-2
@@ -32,7 +32,7 @@ import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class PlayerPositionPacket implements FallbackPacket {
|
||||
public final class SetPlayerPositionPacket implements FallbackPacket {
|
||||
private double x, y, z;
|
||||
private boolean onGround;
|
||||
|
||||
@@ -46,7 +46,7 @@ public final class PlayerPositionPacket implements FallbackPacket {
|
||||
x = byteBuf.readDouble();
|
||||
y = byteBuf.readDouble();
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
|
||||
y = byteBuf.readDouble(); // Account for weird y positions on 1.7
|
||||
y = byteBuf.readDouble(); // Account 1.7 bounding box
|
||||
}
|
||||
z = byteBuf.readDouble();
|
||||
onGround = byteBuf.readBoolean();
|
||||
+2
-2
@@ -33,7 +33,7 @@ import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
|
||||
@ToString
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class PlayerPositionLookPacket implements FallbackPacket {
|
||||
public final class SetPlayerPositionRotation implements FallbackPacket {
|
||||
private double x, y, z;
|
||||
private float yaw, pitch;
|
||||
private int teleportId;
|
||||
@@ -68,7 +68,7 @@ public final class PlayerPositionLookPacket implements FallbackPacket {
|
||||
x = byteBuf.readDouble();
|
||||
y = byteBuf.readDouble();
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_8) < 0) {
|
||||
y = byteBuf.readDouble(); // Account for weird y positions on 1.7
|
||||
y = byteBuf.readDouble(); // Account 1.7 bounding box
|
||||
}
|
||||
z = byteBuf.readDouble();
|
||||
yaw = byteBuf.readFloat();
|
||||
+5
-2
@@ -26,8 +26,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeUUID;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeVarInt;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
@@ -44,7 +47,7 @@ public final class SpawnEntityPacket implements FallbackPacket {
|
||||
final boolean v1_9orHigher = protocolVersion.compareTo(MINECRAFT_1_8) > 0;
|
||||
|
||||
if (v1_9orHigher) {
|
||||
writeUUID(byteBuf, EMPTY_UUID);
|
||||
writeUUID(byteBuf, UUID.randomUUID());
|
||||
}
|
||||
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_14) >= 0) {
|
||||
|
||||
+9
-22
@@ -22,37 +22,27 @@ import io.netty.handler.codec.CorruptedFrameException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacket;
|
||||
import xyz.jonesdev.sonar.common.util.ComponentHolder;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.*;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public final class UniversalChatPacket implements FallbackPacket {
|
||||
public final class SystemChatPacket implements FallbackPacket {
|
||||
private static final int DIV_FLOOR = -Math.floorDiv(-20, 8);
|
||||
|
||||
public static final byte CHAT_TYPE = (byte) 0;
|
||||
public static final byte SYSTEM_TYPE = (byte) 1;
|
||||
public static final byte GAME_INFO_TYPE = (byte) 2;
|
||||
|
||||
private ComponentHolder componentHolder;
|
||||
private String message;
|
||||
private byte type;
|
||||
private byte[] signature;
|
||||
|
||||
public UniversalChatPacket(final @NotNull Component component, final byte type) {
|
||||
this(new ComponentHolder(component), type);
|
||||
}
|
||||
|
||||
public UniversalChatPacket(final ComponentHolder componentHolder, final byte type) {
|
||||
public SystemChatPacket(final @NotNull ComponentHolder componentHolder) {
|
||||
this.componentHolder = componentHolder;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,17 +52,17 @@ public final class UniversalChatPacket implements FallbackPacket {
|
||||
|
||||
// Type
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0) {
|
||||
byteBuf.writeBoolean(type == GAME_INFO_TYPE);
|
||||
byteBuf.writeBoolean(false); // it's not the GAME_INFO type
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_19) >= 0) {
|
||||
writeVarInt(byteBuf, type);
|
||||
writeVarInt(byteBuf, 1); // system chat
|
||||
} else if (protocolVersion.compareTo(MINECRAFT_1_8) >= 0) {
|
||||
byteBuf.writeByte(type);
|
||||
byteBuf.writeByte(1); // system chat
|
||||
}
|
||||
|
||||
// Sender
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_16) >= 0
|
||||
&& protocolVersion.compareTo(MINECRAFT_1_19) < 0) {
|
||||
writeUUID(byteBuf, EMPTY_UUID);
|
||||
writeUUID(byteBuf, UUID.randomUUID());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +78,7 @@ public final class UniversalChatPacket implements FallbackPacket {
|
||||
boolean unsigned = false;
|
||||
|
||||
if (saltLong != 0L && signatureBytes.length > 0) {
|
||||
signature = signatureBytes;
|
||||
// No need to store the valid signature
|
||||
} else if ((protocolVersion.compareTo(MINECRAFT_1_19_1) >= 0
|
||||
|| saltLong == 0L) && signatureBytes.length == 0) {
|
||||
unsigned = true;
|
||||
@@ -125,9 +115,6 @@ public final class UniversalChatPacket implements FallbackPacket {
|
||||
if (signed) {
|
||||
final byte[] sign = new byte[256];
|
||||
byteBuf.readBytes(sign);
|
||||
signature = sign;
|
||||
} else {
|
||||
signature = new byte[0];
|
||||
}
|
||||
|
||||
readVarInt(byteBuf);
|
||||
-2
@@ -55,8 +55,6 @@ public final class TransactionPacket implements FallbackPacket {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_17) < 0) {
|
||||
byteBuf.writeByte(windowId);
|
||||
byteBuf.writeShort((short) transactionId);
|
||||
// The "accepted" field is actually really unnecessary since
|
||||
// it's never even used in the client.
|
||||
byteBuf.writeBoolean(accepted);
|
||||
} else {
|
||||
byteBuf.writeInt(transactionId);
|
||||
|
||||
+9
-8
@@ -33,7 +33,7 @@ import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
* Flow for this session handler
|
||||
*
|
||||
* <li>
|
||||
* {@link SetSlotPacket} and {@link MapDataPacket} packets are sent to the client,
|
||||
* {@link SetContainerSlotPacket} and {@link MapDataPacket} packets are sent to the client,
|
||||
* therefore, setting the player's item to a map with a code on it (CAPTCHA).
|
||||
* <br>
|
||||
* See more: {@link FallbackCAPTCHASessionHandler}, {@link MapCaptchaInfo}
|
||||
@@ -49,9 +49,10 @@ public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler
|
||||
|
||||
this.tries = Sonar.get().getConfig().getVerification().getMap().getMaxTries();
|
||||
|
||||
// Set slot to map
|
||||
user.delayedWrite(new SetSlotPacket(36, 1,
|
||||
ItemType.FILLED_MAP.getId(user.getProtocolVersion()), SetSlotPacket.MAP_NBT));
|
||||
// If the player is on Java, set the 5th slot (ID 4) in the player's hotbar to the map.
|
||||
// If the player is on Bedrock, set the 1st slot (ID 0) in the player's hotbar to the map.
|
||||
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();
|
||||
@@ -76,8 +77,8 @@ public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler
|
||||
checkState(!user.getLoginTimer().elapsed(maxDuration), "took too long to enter captcha");
|
||||
|
||||
// Handle incoming chat messages
|
||||
if (packet instanceof UniversalChatPacket) {
|
||||
final UniversalChatPacket chat = (UniversalChatPacket) packet;
|
||||
if (packet instanceof SystemChatPacket) {
|
||||
final SystemChatPacket chat = (SystemChatPacket) packet;
|
||||
|
||||
// Captcha is correct, finish verification
|
||||
if (chat.getMessage().equals(answer)) {
|
||||
@@ -88,8 +89,8 @@ public final class FallbackCAPTCHASessionHandler extends FallbackSessionHandler
|
||||
// Captcha is incorrect, remove one try
|
||||
checkState(tries-- > 0, "failed captcha too often");
|
||||
user.write(incorrectCaptcha);
|
||||
} else if (packet instanceof PlayerPositionPacket
|
||||
|| packet instanceof PlayerPositionLookPacket) {
|
||||
} else if (packet instanceof SetPlayerPositionPacket
|
||||
|| packet instanceof SetPlayerPositionRotation) {
|
||||
// A position packet is sent approximately every second
|
||||
final long difference = maxDuration - user.getLoginTimer().delay();
|
||||
final int index = (int) (difference / 1000D);
|
||||
|
||||
+32
-70
@@ -32,7 +32,9 @@ import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
public final class FallbackGravitySessionHandler extends FallbackSessionHandler {
|
||||
|
||||
public FallbackGravitySessionHandler(final FallbackUser user, final String username, final UUID uuid) {
|
||||
public FallbackGravitySessionHandler(final @NotNull FallbackUser user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid) {
|
||||
super(user, username, uuid);
|
||||
|
||||
// We don't want to check Geyser players for valid gravity, as this might cause issues because of the protocol
|
||||
@@ -79,8 +81,7 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
|
||||
private final boolean enableGravityCheck, enableCollisionsCheck;
|
||||
private boolean teleported, checkMovement;
|
||||
private short expectedTransactionId = 1;
|
||||
private double x, y, z, deltaY;
|
||||
private double y, deltaY;
|
||||
private int movementTick;
|
||||
|
||||
private void markTeleported() {
|
||||
@@ -88,7 +89,7 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
teleported = true;
|
||||
}
|
||||
|
||||
private void markNextStage() {
|
||||
private void markSuccess(final boolean forceCAPTCHA) {
|
||||
// Force-stop the movement checks
|
||||
teleported = false;
|
||||
|
||||
@@ -98,77 +99,28 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
if (!user.isGeyser()) {
|
||||
checkState(user.isReceivedPluginMessage(), "didn't send plugin message");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses ping packets to check for an immediate, legitimate response from the client
|
||||
* <br>
|
||||
* <a href="https://wiki.vg/Protocol#Ping_.28configuration.29">Wiki.vg - Ping (configuration)</a>
|
||||
* <a href="https://wiki.vg/Protocol#Ping_.28play.29">Wiki.vg - Ping (play)</a>
|
||||
*/
|
||||
private void sendTransaction() {
|
||||
// Interrupt any ongoing process/check
|
||||
markNextStage();
|
||||
|
||||
// Send a Transaction (Ping) packet with a random ID
|
||||
expectedTransactionId = (short) -(RANDOM.nextInt(Short.MAX_VALUE));
|
||||
user.write(new TransactionPacket(0, expectedTransactionId, false));
|
||||
}
|
||||
|
||||
private void forceCAPTCHA() {
|
||||
// Interrupt any ongoing process/check
|
||||
markNextStage();
|
||||
|
||||
// Forcefully make the player solve a CAPTCHA
|
||||
// Send the player to the protocol check
|
||||
final var decoder = (FallbackPacketDecoder) user.getPipeline().get(FallbackPacketDecoder.class);
|
||||
decoder.setListener(new FallbackCAPTCHASessionHandler(user, username, uuid));
|
||||
}
|
||||
|
||||
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);
|
||||
// Pass the player to the next best verification handler
|
||||
if (Sonar.get().getFallback().shouldPerformVehicleCheck()) {
|
||||
decoder.setListener(new FallbackVehicleSessionHandler(user, username, uuid, x, y, z));
|
||||
} else if (Sonar.get().getFallback().shouldPerformCaptcha()) {
|
||||
decoder.setListener(new FallbackCAPTCHASessionHandler(user, username, uuid));
|
||||
} else {
|
||||
// The player has passed all checks
|
||||
finishVerification();
|
||||
}
|
||||
decoder.setListener(new FallbackProtocolSessionHandler(user, username, uuid, forceCAPTCHA));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(final @NotNull FallbackPacket packet) {
|
||||
if (packet instanceof TransactionPacket) {
|
||||
final TransactionPacket transaction = (TransactionPacket) packet;
|
||||
|
||||
// Make sure random transactions aren't counted
|
||||
checkState(expectedTransactionId <= 0, "unexpected transaction");
|
||||
// Make sure the transaction was accepted
|
||||
// This must - by vanilla protocol - always be accepted
|
||||
checkState(transaction.isAccepted(), "didn't accept transaction");
|
||||
// Also check if the transaction ID matches the expected ID
|
||||
final long transactionId = transaction.getTransactionId();
|
||||
checkState(transactionId == expectedTransactionId,
|
||||
"expected T ID " + expectedTransactionId + ", but got " + transactionId);
|
||||
|
||||
markSuccess();
|
||||
} else if (packet instanceof PlayerPositionLookPacket) {
|
||||
if (packet instanceof SetPlayerPositionRotation) {
|
||||
// Make sure the player has teleported before checking for position packets
|
||||
if (teleported) {
|
||||
final PlayerPositionLookPacket positionLook = (PlayerPositionLookPacket) packet;
|
||||
final SetPlayerPositionRotation positionLook = (SetPlayerPositionRotation) packet;
|
||||
handleMovement(positionLook.getX(), positionLook.getY(), positionLook.getZ(), positionLook.isOnGround());
|
||||
}
|
||||
} else if (packet instanceof PlayerPositionPacket) {
|
||||
} else if (packet instanceof SetPlayerPositionPacket) {
|
||||
// Make sure the player has teleported before checking for position packets
|
||||
if (teleported) {
|
||||
final PlayerPositionPacket position = (PlayerPositionPacket) packet;
|
||||
final SetPlayerPositionPacket position = (SetPlayerPositionPacket) packet;
|
||||
handleMovement(position.getX(), position.getY(), position.getZ(), position.isOnGround());
|
||||
}
|
||||
} else if (packet instanceof TeleportConfirmPacket) {
|
||||
final TeleportConfirmPacket teleportConfirm = (TeleportConfirmPacket) packet;
|
||||
} else if (packet instanceof ConfirmTeleportationPacket) {
|
||||
final ConfirmTeleportationPacket teleportConfirm = (ConfirmTeleportationPacket) packet;
|
||||
|
||||
// Only expect this packet to be sent once
|
||||
checkState(!teleported, "duplicate teleport confirm");
|
||||
@@ -180,9 +132,9 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
markTeleported();
|
||||
}
|
||||
// Make sure to catch all ClientSettings and PluginMessage packets during the play phase.
|
||||
else if (packet instanceof ClientSettingsPacket) {
|
||||
else if (packet instanceof ClientInformationPacket) {
|
||||
// Let the session handler itself know about this packet
|
||||
checkClientSettings((ClientSettingsPacket) packet);
|
||||
checkClientInformation((ClientInformationPacket) packet);
|
||||
} else if (packet instanceof PluginMessagePacket) {
|
||||
// Let the session handler itself know about this packet
|
||||
checkPluginMessage((PluginMessagePacket) packet);
|
||||
@@ -195,7 +147,7 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
// No need to continue checking if the gravity and collision checks are disabled
|
||||
// Continue with the next stage of the verification
|
||||
if (!enableGravityCheck && !enableCollisionsCheck) {
|
||||
sendTransaction();
|
||||
markSuccess(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -213,9 +165,7 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
final double lastDeltaY = this.deltaY;
|
||||
final double lastY = this.y;
|
||||
this.deltaY = y - lastY;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
|
||||
// Log/debug position if enabled in the configuration
|
||||
if (Sonar.get().getConfig().getVerification().isDebugXYZPositions()) {
|
||||
@@ -237,6 +187,9 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
return;
|
||||
}
|
||||
|
||||
// The movement tick should not be ignored when gravity check is disabled.
|
||||
movementTick++;
|
||||
|
||||
if (enableGravityCheck) {
|
||||
// Predict the player's current motion based on the last motion
|
||||
// https://minecraft.wiki/w/Entity#Motion_of_entities
|
||||
@@ -248,18 +201,27 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
if (difference > 1e-7) {
|
||||
// Do not throw an exception if the user configured to display the CAPTCHA instead
|
||||
if (Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
|
||||
forceCAPTCHA();
|
||||
markSuccess(true);
|
||||
return;
|
||||
}
|
||||
user.fail("incorrect gravity; predicted: " + predicted + " deltaY: " + deltaY + " y: " + y);
|
||||
}
|
||||
|
||||
// The player is obeying gravity, go on to the next stage if the collision check is disabled.
|
||||
if (++movementTick == maxMovementTick && !enableCollisionsCheck) {
|
||||
sendTransaction();
|
||||
if (movementTick == maxMovementTick && !enableCollisionsCheck) {
|
||||
markSuccess(false);
|
||||
}
|
||||
}
|
||||
} else if (enableCollisionsCheck) {
|
||||
// Make sure the player has actually moved before reaching the platform
|
||||
if (movementTick < maxMovementTick) {
|
||||
// Do not throw an exception if the user configured to display the CAPTCHA instead
|
||||
if (Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
|
||||
markSuccess(true);
|
||||
return;
|
||||
}
|
||||
user.fail("illegal collision tick: " + movementTick);
|
||||
}
|
||||
// Calculate the difference between the player's Y coordinate and the expected Y coordinate
|
||||
double collisionOffsetY = (DEFAULT_Y_COLLIDE_POSITION + blockType.getBlockHeight()) - y;
|
||||
// 1.7 sends the head position instead of the AABB minY
|
||||
@@ -270,7 +232,7 @@ public final class FallbackGravitySessionHandler extends FallbackSessionHandler
|
||||
// Make sure the player is actually colliding with the blocks and not only spoofing ground
|
||||
checkState(collisionOffsetY > -0.03, "illegal collision: " + collisionOffsetY);
|
||||
// The player has collided with the blocks, go on to the next stage
|
||||
sendTransaction();
|
||||
markSuccess(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -25,7 +25,7 @@ import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketEncoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPacketRegistry;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.configuration.FinishConfigurationPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.login.LoginAcknowledgedPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.ClientSettingsPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.ClientInformationPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.KeepAlivePacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.PluginMessagePacket;
|
||||
|
||||
@@ -60,7 +60,9 @@ import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
*/
|
||||
public final class FallbackLoginSessionHandler extends FallbackSessionHandler {
|
||||
|
||||
public FallbackLoginSessionHandler(final FallbackUser user, final String username, final UUID uuid) {
|
||||
public FallbackLoginSessionHandler(final @NotNull FallbackUser user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid) {
|
||||
super(user, username, uuid);
|
||||
|
||||
// Start initializing the actual join process for pre-1.20.2 clients
|
||||
@@ -138,9 +140,9 @@ public final class FallbackLoginSessionHandler extends FallbackSessionHandler {
|
||||
markSuccess();
|
||||
}
|
||||
// Make sure to catch all ClientSettings and PluginMessage packets during the configuration phase.
|
||||
else if (packet instanceof ClientSettingsPacket) {
|
||||
else if (packet instanceof ClientInformationPacket) {
|
||||
// Let the session handler itself know about this packet
|
||||
checkClientSettings((ClientSettingsPacket) packet);
|
||||
checkClientInformation((ClientInformationPacket) packet);
|
||||
} else if (packet instanceof PluginMessagePacket) {
|
||||
// Let the session handler itself know about this packet
|
||||
checkPluginMessage((PluginMessagePacket) packet);
|
||||
@@ -159,7 +161,8 @@ public final class FallbackLoginSessionHandler extends FallbackSessionHandler {
|
||||
// 1.20.5+ adds new "game bundle features" which overcomplicate all of this...
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0) {
|
||||
// Write the new RegistrySync packets to the buffer
|
||||
for (final FallbackPacket syncPacket : REGISTRY_SYNC_1_20_5) {
|
||||
for (final FallbackPacket syncPacket : user.getProtocolVersion().compareTo(MINECRAFT_1_21) < 0
|
||||
? REGISTRY_SYNC_1_20_5 : REGISTRY_SYNC_1_21) {
|
||||
user.delayedWrite(syncPacket);
|
||||
}
|
||||
} else {
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.common.fallback.session;
|
||||
|
||||
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.FallbackPacketDecoder;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.SetHeldItemPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.TransactionPacket;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class FallbackProtocolSessionHandler extends FallbackSessionHandler {
|
||||
|
||||
public FallbackProtocolSessionHandler(final @NotNull FallbackUser user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid,
|
||||
final boolean forceCAPTCHA) {
|
||||
super(user, username, uuid);
|
||||
|
||||
this.forceCAPTCHA = forceCAPTCHA;
|
||||
|
||||
// Immediately send the player the transaction packet
|
||||
sendTransaction();
|
||||
}
|
||||
|
||||
private final boolean forceCAPTCHA;
|
||||
private short expectedTransactionId;
|
||||
private int currentClientSlotId, expectedSlotId = -1;
|
||||
|
||||
/**
|
||||
* Uses Transaction (ping) packets to check for an immediate, legitimate response from the client
|
||||
* <br>
|
||||
* <a href="https://wiki.vg/Protocol#Ping_.28configuration.29">Wiki.vg - Ping (configuration)</a>
|
||||
* <a href="https://wiki.vg/Protocol#Ping_.28play.29">Wiki.vg - Ping (play)</a>
|
||||
*/
|
||||
private void sendTransaction() {
|
||||
// Send a Transaction (Ping) packet with a random ID
|
||||
expectedTransactionId = (short) -(RANDOM.nextInt(Short.MAX_VALUE));
|
||||
user.write(new TransactionPacket(0, expectedTransactionId, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses SetHeldItem packets to check for a legitimate response from the client
|
||||
* <br>
|
||||
* <a href="https://wiki.vg/Protocol#Set_Held_Item_.28serverbound.29">Wiki.vg - SetHeldItem (play)</a>
|
||||
*/
|
||||
private void sendSetHeldItem() {
|
||||
// Move the player's slot by 4 (slot limit divided by 2),
|
||||
// and then modulo it by the slot limit (8) to ensure that we don't send invalid slot IDs.
|
||||
expectedSlotId = (currentClientSlotId + 4) % 8;
|
||||
// Send two SetHeldItem packets with the same slot to check if the player responds with the correct slot.
|
||||
// By vanilla protocol, the client does not respond to duplicate SetHeldItem packets.
|
||||
// We can take advantage of this by sending two packets with the same content to check for a valid response.
|
||||
final SetHeldItemPacket heldItemPacket = new SetHeldItemPacket(expectedSlotId);
|
||||
user.delayedWrite(heldItemPacket);
|
||||
user.delayedWrite(heldItemPacket);
|
||||
user.getChannel().flush();
|
||||
}
|
||||
|
||||
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);
|
||||
// Pass the player to the next best verification handler
|
||||
if (Sonar.get().getFallback().shouldPerformVehicleCheck()) {
|
||||
decoder.setListener(new FallbackVehicleSessionHandler(user, username, uuid, forceCAPTCHA));
|
||||
} else if (forceCAPTCHA || Sonar.get().getFallback().shouldPerformCaptcha()) {
|
||||
decoder.setListener(new FallbackCAPTCHASessionHandler(user, username, uuid));
|
||||
} else {
|
||||
// The player has passed all checks
|
||||
finishVerification();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(final @NotNull FallbackPacket packet) {
|
||||
if (packet instanceof TransactionPacket) {
|
||||
final TransactionPacket transaction = (TransactionPacket) packet;
|
||||
|
||||
// Make sure random transactions aren't counted
|
||||
checkState(expectedTransactionId <= 0, "unexpected transaction");
|
||||
// Make sure the transaction was accepted
|
||||
// This must - by vanilla protocol - always be accepted
|
||||
checkState(transaction.isAccepted(), "didn't accept transaction");
|
||||
// Also check if the transaction ID matches the expected ID
|
||||
final long transactionId = transaction.getTransactionId();
|
||||
checkState(transactionId == expectedTransactionId,
|
||||
"expected T ID " + expectedTransactionId + ", but got " + transactionId);
|
||||
|
||||
// Only perform the SetHeldItem check on Java players since the Bedrock protocol breaks this check.
|
||||
// I don't really know why this doesn't work on Bedrock,
|
||||
// but I think it has to do with how Geyser/floodgate translates this packet:
|
||||
// https://wiki.vg/Bedrock_Protocol#Player_Hotbar
|
||||
if (user.isGeyser()) {
|
||||
markSuccess();
|
||||
} else {
|
||||
sendSetHeldItem();
|
||||
}
|
||||
} else if (packet instanceof SetHeldItemPacket) {
|
||||
final SetHeldItemPacket heldItemPacket = (SetHeldItemPacket) packet;
|
||||
|
||||
final int slotId = heldItemPacket.getSlot();
|
||||
// Also check if the player sent an invalid slot which is impossible by vanilla protocol
|
||||
checkState(slotId >= 0 && slotId <= 8, "slot out of range: " + slotId);
|
||||
// Check if the player sent a duplicate slot packet which is impossible by vanilla protocol
|
||||
checkState(slotId != currentClientSlotId, "invalid slot: " + slotId);
|
||||
|
||||
// Only continue checking if we're actually expecting a SetHeldItem packet
|
||||
// The player can send a SetHeldItem packet by themselves -> exempt
|
||||
if (expectedSlotId != -1
|
||||
// Check if the slot ID matches the expected slot ID
|
||||
// This can false flag if a player spams these packets, which is why we don't fail for this
|
||||
&& slotId == expectedSlotId) {
|
||||
markSuccess();
|
||||
}
|
||||
|
||||
currentClientSlotId = slotId;
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-20
@@ -17,7 +17,6 @@
|
||||
|
||||
package xyz.jonesdev.sonar.common.fallback.session;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
@@ -25,17 +24,17 @@ 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.FallbackPacketListener;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.ClientSettingsPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.ClientInformationPacket;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.packets.play.PluginMessagePacket;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
import xyz.jonesdev.sonar.common.util.ProtocolUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_13;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_5;
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.*;
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.closeWith;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.transferToOrigin;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.BRAND_CHANNEL;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.BRAND_CHANNEL_LEGACY;
|
||||
@@ -61,30 +60,31 @@ public abstract class FallbackSessionHandler implements FallbackPacketListener {
|
||||
GlobalSonarStatistics.totalSuccessfulVerifications++;
|
||||
|
||||
// Add verified player to the database
|
||||
final long timeTaken = user.getLoginTimer().delay();
|
||||
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(user.getInetAddress(), uuid, timeTaken));
|
||||
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(
|
||||
user.getInetAddress(), uuid, user.getLoginTimer().getStart()));
|
||||
|
||||
// Call the VerifySuccessEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(username, uuid, user, timeTaken));
|
||||
Sonar.get().getEventManager().publish(new UserVerifySuccessEvent(
|
||||
username, uuid, user, user.getLoginTimer().delay()));
|
||||
|
||||
// If enabled, transfer the player back to the origin server.
|
||||
// This feature was introduced by Mojang in Minecraft version 1.20.5.
|
||||
if (Sonar.get().getConfig().getVerification().getTransfer().isEnabled()
|
||||
if (transferToOrigin != null
|
||||
&& user.getProtocolVersion().compareTo(MINECRAFT_1_20_5) >= 0) {
|
||||
user.write(transferToOrigin);
|
||||
// Close the channel to ensure that the connection is closed
|
||||
user.getChannel().close();
|
||||
// Send the transfer packet to the player and close the channel
|
||||
closeWith(user.getChannel(), user.getProtocolVersion(), transferToOrigin);
|
||||
} else {
|
||||
// Disconnect player with the verification success message
|
||||
user.disconnect(Sonar.get().getConfig().getVerification().getVerificationSuccess(), false);
|
||||
user.disconnect(Sonar.get().getConfig().getVerification().getVerificationSuccess());
|
||||
}
|
||||
|
||||
Sonar.get().getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getSuccessLog()
|
||||
.replace("%name%", username)
|
||||
.replace("%time%", user.getLoginTimer().toString()));
|
||||
Sonar.get().getFallback().getLogger().info(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("verification.logs.successful")
|
||||
.replace("<username>", username)
|
||||
.replace("<time-taken>", user.getLoginTimer().toString()));
|
||||
}
|
||||
|
||||
protected final void checkClientSettings(final @NotNull ClientSettingsPacket clientSettings) {
|
||||
protected final void checkClientInformation(final @NotNull ClientInformationPacket clientSettings) {
|
||||
// Ensure that the client locale is correct
|
||||
validateClientLocale(clientSettings.getLocale());
|
||||
// Check if the player sent an unused bit flag in the skin section
|
||||
@@ -113,17 +113,22 @@ public abstract class FallbackSessionHandler implements FallbackPacketListener {
|
||||
|
||||
// Validate the client branding using a regex to filter unwanted characters.
|
||||
if (Sonar.get().getConfig().getVerification().getBrand().isEnabled()) {
|
||||
validateClientBrand(pluginMessage.getSlicedBuffer());
|
||||
validateClientBrand(pluginMessage.getData());
|
||||
}
|
||||
|
||||
// Mark the PluginMessage packet as received
|
||||
user.setReceivedPluginMessage(true);
|
||||
}
|
||||
|
||||
protected final void validateClientBrand(final @NotNull ByteBuf content) {
|
||||
protected final void validateClientBrand(final byte @NotNull [] data) {
|
||||
// No need to check for empty brands since ProtocolUtil#readBrandMessage
|
||||
// already performs invalid string checks by default.
|
||||
final String read = ProtocolUtil.readBrandMessage(content);
|
||||
String read = new String(data, StandardCharsets.UTF_8);
|
||||
checkState(read.length() > 1, "client brand is too short");
|
||||
// Remove the invalid character at the beginning of the client brand
|
||||
if (user.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
||||
read = read.substring(1);
|
||||
}
|
||||
// Check if the decoded client brand string is too long
|
||||
checkState(read.length() < Sonar.get().getConfig().getVerification().getBrand().getMaxLength(),
|
||||
"client brand is too long: " + read.length());
|
||||
|
||||
+7
-3
@@ -51,21 +51,25 @@ public final class FallbackVehicleSessionHandler extends FallbackSessionHandler
|
||||
public FallbackVehicleSessionHandler(final @NotNull FallbackUser user,
|
||||
final @NotNull String username,
|
||||
final @NotNull UUID uuid,
|
||||
final double x, final double y, final double z) {
|
||||
final boolean forceCAPTCHA) {
|
||||
super(user, username, uuid);
|
||||
|
||||
this.forceCAPTCHA = forceCAPTCHA;
|
||||
|
||||
// Send the necessary packets to mount the player on the vehicle
|
||||
final int entityTypeId = EntityType.BOAT.getId(user.getProtocolVersion());
|
||||
user.delayedWrite(new SpawnEntityPacket(VEHICLE_ENTITY_ID, entityTypeId, x, y, z));
|
||||
user.delayedWrite(new SpawnEntityPacket(VEHICLE_ENTITY_ID, entityTypeId,
|
||||
SPAWN_X_POSITION, DEFAULT_Y_COLLIDE_POSITION, SPAWN_Z_POSITION));
|
||||
user.delayedWrite(setPassengers);
|
||||
user.getChannel().flush();
|
||||
}
|
||||
|
||||
private final boolean forceCAPTCHA;
|
||||
private boolean receivedPaddle, receivedInput;
|
||||
|
||||
private void markSuccess() {
|
||||
// Pass the player to the next best verification handler
|
||||
if (Sonar.get().getFallback().shouldPerformCaptcha()) {
|
||||
if (forceCAPTCHA || Sonar.get().getFallback().shouldPerformCaptcha()) {
|
||||
// Make sure the player exits the vehicle before sending the CAPTCHA
|
||||
user.delayedWrite(removeEntities);
|
||||
// Either send the player to the CAPTCHA, or finish the verification.
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.common.service;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@UtilityClass
|
||||
public final class SonarServiceManager {
|
||||
private final ScheduledExecutorService VERBOSE = createScheduledExecutor("sonar-verbose-thread");
|
||||
private final ScheduledExecutorService FALLBACK_QUEUE = createScheduledExecutor("sonar-queue-thread");
|
||||
private final ScheduledExecutorService STATISTICS = createScheduledExecutor("sonar-statistics-thread");
|
||||
|
||||
private @NotNull ScheduledExecutorService createScheduledExecutor(final String threadName) {
|
||||
return Executors.newSingleThreadScheduledExecutor(runnable -> {
|
||||
final Thread thread = Executors.defaultThreadFactory().newThread(runnable);
|
||||
thread.setName(threadName);
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
}
|
||||
|
||||
public void start() {
|
||||
VERBOSE.scheduleAtFixedRate(() -> {
|
||||
// Make sure to clean up the cached statistics
|
||||
// since we don't want to display wrong values.
|
||||
Sonar.get().getStatistics().cleanUpCache();
|
||||
Sonar.get().getFallback().getRatelimiter().getFailCountCache().cleanUp();
|
||||
Sonar.get().getFallback().getBlacklist().cleanUp();
|
||||
// Update the attack tracker
|
||||
Sonar.get().getAttackTracker().checkIfUnderAttack();
|
||||
// Update the action bar verbose
|
||||
Sonar.get().getVerboseHandler().observe();
|
||||
}, 0L, 250L, TimeUnit.MILLISECONDS);
|
||||
|
||||
FALLBACK_QUEUE.scheduleAtFixedRate(() -> Sonar.get().getFallback().getQueue().poll(),
|
||||
1L, 1L, TimeUnit.SECONDS);
|
||||
|
||||
STATISTICS.scheduleAtFixedRate(CachedBandwidthStatistics::reset,
|
||||
0L, 1L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
VERBOSE.shutdown();
|
||||
FALLBACK_QUEUE.shutdown();
|
||||
STATISTICS.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* 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.common.service;
|
||||
|
||||
public final class SonarServiceThread extends Thread {
|
||||
private final long delay;
|
||||
private final Runnable runnable;
|
||||
|
||||
public SonarServiceThread(final String name, final long delay, final Runnable runnable) {
|
||||
setName(name);
|
||||
this.delay = delay;
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (!isInterrupted()) {
|
||||
runnable.run();
|
||||
// Sleep thread by specified delay
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
} catch (InterruptedException interruptedException) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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.common.service;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
|
||||
|
||||
@UtilityClass
|
||||
public final class SonarServiceThreadManager {
|
||||
private static final SonarServiceThread VERBOSE = new SonarServiceThread("sonar-verbose-thread",
|
||||
250L, () -> {
|
||||
// Make sure to clean up the cached statistics
|
||||
// since we don't want to display wrong values.
|
||||
Sonar.get().getStatistics().cleanUpCache();
|
||||
Sonar.get().getFallback().getRatelimiter().getFailCountCache().cleanUp();
|
||||
Sonar.get().getFallback().getBlacklist().cleanUp();
|
||||
// Update the attack tracker
|
||||
Sonar.get().getAttackTracker().checkIfUnderAttack();
|
||||
// Update the action bar verbose
|
||||
Sonar.get().getVerboseHandler().update();
|
||||
});
|
||||
private static final SonarServiceThread FALLBACK_QUEUE = new SonarServiceThread("sonar-fallback-queue-thread",
|
||||
1000L, () -> Sonar.get().getFallback().getQueue().poll());
|
||||
private static final SonarServiceThread STATISTICS = new SonarServiceThread("sonar-statistics-thread",
|
||||
1000L, CachedBandwidthStatistics::reset);
|
||||
|
||||
public void start() {
|
||||
VERBOSE.start();
|
||||
FALLBACK_QUEUE.start();
|
||||
STATISTICS.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
VERBOSE.interrupt();
|
||||
FALLBACK_QUEUE.interrupt();
|
||||
STATISTICS.interrupt();
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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.common.subcommand;
|
||||
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandRegistry;
|
||||
import xyz.jonesdev.sonar.common.subcommand.impl.*;
|
||||
|
||||
public final class SubcommandRegistryHolder implements SubcommandRegistry {
|
||||
public SubcommandRegistryHolder() {
|
||||
register(
|
||||
new BlacklistCommand(),
|
||||
new VerifiedCommand(),
|
||||
new StatisticsCommand(),
|
||||
new VerboseCommand(),
|
||||
new ReloadCommand(),
|
||||
new DumpCommand(),
|
||||
new NotifyCommand()
|
||||
);
|
||||
}
|
||||
}
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* 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.common.subcommand.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "blacklist",
|
||||
description = "Manage blacklisted IP addresses",
|
||||
arguments = {
|
||||
@Argument("add"),
|
||||
@Argument("remove"),
|
||||
@Argument("clear"),
|
||||
@Argument("size")
|
||||
}
|
||||
)
|
||||
public final class BlacklistCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
switch (invocation.getRawArguments()[1].toLowerCase()) {
|
||||
case "add": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "blacklist add <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
final InetAddress inetAddress = getInetAddressIfValid(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
// Make sure the IP is not blacklisted already
|
||||
if (SONAR.getFallback().getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistDuplicate());
|
||||
return;
|
||||
}
|
||||
|
||||
// Display a warning if the IP is verified but being added to the blacklist
|
||||
if (SONAR.getVerifiedPlayerController().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistAddWarning()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
}
|
||||
|
||||
// Blacklist the given IP address
|
||||
SONAR.getFallback().getBlacklist().put(inetAddress, (byte) 0);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistAdd()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
break;
|
||||
}
|
||||
|
||||
case "remove": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "blacklist remove <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
final InetAddress inetAddress = getInetAddressIfValid(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
// Make sure the IP is blacklisted
|
||||
if (!SONAR.getFallback().getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate the cache entry of the blacklisted IP address
|
||||
SONAR.getFallback().getBlacklist().invalidate(inetAddress);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistRemove()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
break;
|
||||
}
|
||||
|
||||
case "clear": {
|
||||
final long blacklisted = SONAR.getFallback().getBlacklist().estimatedSize();
|
||||
|
||||
if (blacklisted == 0) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistEmpty());
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate all cache entries
|
||||
SONAR.getFallback().getBlacklist().invalidateAll();
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistCleared()
|
||||
.replace("%removed%", Sonar.DECIMAL_FORMAT.format(blacklisted)));
|
||||
break;
|
||||
}
|
||||
|
||||
case "size": {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistSize()
|
||||
.replace("%amount%", Sonar.DECIMAL_FORMAT.format(SONAR.getFallback().getBlacklist().estimatedSize())));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
incorrectUsage(invocation.getSender());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* 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.common.subcommand.impl;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "statistics",
|
||||
aliases = {"stats"},
|
||||
description = "Show session statistics of this server",
|
||||
arguments = {
|
||||
@Argument("network"),
|
||||
@Argument("memory"),
|
||||
@Argument("cpu")
|
||||
},
|
||||
argumentsRequired = false
|
||||
)
|
||||
public final class StatisticsCommand extends Subcommand {
|
||||
private enum StatisticType {
|
||||
GENERAL,
|
||||
NETWORK,
|
||||
MEMORY,
|
||||
CPU
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
StatisticType type = StatisticType.GENERAL;
|
||||
if (invocation.getRawArguments().length >= 2) {
|
||||
try {
|
||||
type = StatisticType.valueOf(invocation.getRawArguments()[1].toUpperCase());
|
||||
} catch (Exception exception) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getUnknownStatisticType()
|
||||
.replace("%statistics%", getArguments()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getStatisticsHeader()
|
||||
.replace("%type%", type.name().toLowerCase()));
|
||||
invocation.getSender().sendMessage(Component.empty());
|
||||
|
||||
switch (type) {
|
||||
case GENERAL: {
|
||||
final long seconds = Sonar.get().getLaunchTimer().delay() / 1000L;
|
||||
final long days = seconds / (24L * 60L * 60L);
|
||||
final long hours = (seconds % (24L * 60L * 60L)) / (60L * 60L);
|
||||
final long minutes = (seconds % (60L * 60L)) / 60L;
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getGeneralStatistics()
|
||||
.replace("%verified%", DECIMAL_FORMAT.format(SONAR.getVerifiedPlayerController().estimatedSize()))
|
||||
.replace("%verifying%", DECIMAL_FORMAT.format(SONAR.getFallback().getConnected().size()))
|
||||
.replace("%blacklisted%", DECIMAL_FORMAT.format(SONAR.getFallback().getBlacklist().estimatedSize()))
|
||||
.replace("%queued%", DECIMAL_FORMAT.format(SONAR.getFallback().getQueue().getQueuedPlayers().size()))
|
||||
.replace("%uptime%", String.format("%dd %dh %dm %ds", days, hours, minutes, seconds % 60L))
|
||||
.replace("%total_joins%", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalPlayersJoined()))
|
||||
.replace("%total_attempts%", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications()))
|
||||
.replace("%total_failed%", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications())));
|
||||
break;
|
||||
}
|
||||
|
||||
case CPU: {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getCpuStatistics()
|
||||
.replace("%process_cpu%", DECIMAL_FORMAT.format(getProcessCPUUsage()))
|
||||
.replace("%system_cpu%", DECIMAL_FORMAT.format(getSystemCPUUsage()))
|
||||
.replace("%average_process_cpu%", DECIMAL_FORMAT.format(getAverageProcessCPUUsage()))
|
||||
.replace("%average_system_cpu%", DECIMAL_FORMAT.format(getAverageSystemCPUUsage()))
|
||||
.replace("%load_average%", DECIMAL_FORMAT.format(getSystemLoadAverage()))
|
||||
.replace("%virtual_cores%", DECIMAL_FORMAT.format(getVirtualCores())));
|
||||
break;
|
||||
}
|
||||
|
||||
case MEMORY: {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getMemoryStatistics()
|
||||
.replace("%free_memory%", formatMemory(getFreeMemory()))
|
||||
.replace("%used_memory%", formatMemory(getUsedMemory()))
|
||||
.replace("%max_memory%", formatMemory(getMaxMemory()))
|
||||
.replace("%total_memory%", formatMemory(getTotalMemory())));
|
||||
break;
|
||||
}
|
||||
|
||||
case NETWORK: {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getNetworkStatistics()
|
||||
.replace("%incoming%", CachedBandwidthStatistics.INCOMING.getCachedSecond())
|
||||
.replace("%outgoing%", CachedBandwidthStatistics.OUTGOING.getCachedSecond())
|
||||
.replace("%ttl_incoming%", CachedBandwidthStatistics.INCOMING.getCachedTtl())
|
||||
.replace("%ttl_outgoing%", CachedBandwidthStatistics.OUTGOING.getCachedTtl()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-184
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* 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.common.subcommand.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "verified",
|
||||
description = "Manage verified IP addresses",
|
||||
arguments = {
|
||||
@Argument("history"),
|
||||
@Argument("remove"),
|
||||
@Argument("add"),
|
||||
@Argument("clear"),
|
||||
@Argument("size")
|
||||
}
|
||||
)
|
||||
public final class VerifiedCommand extends Subcommand {
|
||||
private static final Queue<String> LOCK = new LinkedBlockingQueue<>(1);
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
switch (invocation.getRawArguments()[1].toLowerCase()) {
|
||||
case "history": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "verified history <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
final InetAddress inetAddress = getInetAddressIfValid(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
// Make sure the IP is verified already
|
||||
if (!SONAR.getVerifiedPlayerController().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
invocation.getSender().sendMessage("<yellow>Previous UUIDs for " + rawInetAddress + ":");
|
||||
for (final UUID uuid : SONAR.getVerifiedPlayerController().getUUIDs(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(" <gray>▪ <white>" + uuid.toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "remove": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "verified remove <IP address>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
|
||||
// Make sure we aren't currently locking the IP address to avoid a double I/O operation
|
||||
if (LOCK.contains(rawInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedBlocked());
|
||||
return;
|
||||
}
|
||||
|
||||
final InetAddress inetAddress = getInetAddressIfValid(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
// Make sure the player is verified already
|
||||
if (!SONAR.getVerifiedPlayerController().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
// Lock the IP address
|
||||
// Make sure we don't accidentally run 2 operations at the same time
|
||||
LOCK.add(rawInetAddress);
|
||||
SONAR.getVerifiedPlayerController().remove(inetAddress.toString());
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedRemove()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
// Unlock the IP address
|
||||
LOCK.remove(rawInetAddress);
|
||||
break;
|
||||
}
|
||||
|
||||
case "add": {
|
||||
if (invocation.getRawArguments().length <= 3) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "verified add <IP address> <UUID/username>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
|
||||
// Make sure we aren't currently locking the IP address to avoid a double I/O operation
|
||||
if (LOCK.contains(rawInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedBlocked());
|
||||
return;
|
||||
}
|
||||
|
||||
final InetAddress inetAddress = getInetAddressIfValid(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
// Try to parse the UUID (from the username, if needed)
|
||||
final String rawUUID = invocation.getRawArguments()[3];
|
||||
final UUID uuid = rawUUID.length() == 36 ? UUID.fromString(rawUUID)
|
||||
: UUID.nameUUIDFromBytes(("OfflinePlayer:" + rawUUID).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Make sure the player is verified already
|
||||
if (SONAR.getVerifiedPlayerController().has(inetAddress.toString(), uuid)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedAlready());
|
||||
return;
|
||||
}
|
||||
|
||||
// Lock the IP address
|
||||
// Make sure we don't accidentally run 2 operations at the same time
|
||||
LOCK.add(rawInetAddress);
|
||||
// Add verified player to the database
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(inetAddress, uuid, timestamp));
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedAdd()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
// Unlock the IP address
|
||||
LOCK.remove(rawInetAddress);
|
||||
break;
|
||||
}
|
||||
|
||||
case "clear": {
|
||||
final int verified = SONAR.getVerifiedPlayerController().estimatedSize();
|
||||
|
||||
if (verified == 0) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedEmpty());
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate all cache entries
|
||||
SONAR.getVerifiedPlayerController().clearAll();
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedCleared()
|
||||
.replace("%removed%", Sonar.DECIMAL_FORMAT.format(verified)));
|
||||
break;
|
||||
}
|
||||
|
||||
case "size": {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedSize()
|
||||
.replace("%amount%", Sonar.DECIMAL_FORMAT.format(SONAR.getVerifiedPlayerController().estimatedSize())));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
incorrectUsage(invocation.getSender());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.common.subcommands;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "blacklist",
|
||||
arguments = {"add", "remove", "clear", "size"}
|
||||
)
|
||||
public final class BlacklistCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
switch (invocation.getRawArguments()[1].toLowerCase()) {
|
||||
case "add": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
incorrectUsage(invocation.getSource(), "blacklist add <IP address>");
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
|
||||
try {
|
||||
// Try to resolve the IP address, so we can actually do something with it
|
||||
final InetAddress inetAddress = InetAddress.getAllByName(rawAddress)[0];
|
||||
|
||||
// Make sure the IP is not blacklisted already
|
||||
if (Sonar.get().getFallback().getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-duplicate"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Display a warning if the IP is verified but being added to the blacklist
|
||||
if (Sonar.get().getVerifiedPlayerController().has(rawAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.add-warning"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
}
|
||||
|
||||
// Blacklist the given IP address
|
||||
Sonar.get().getFallback().getBlacklist().put(inetAddress, (byte) 0);
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.add"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
} catch (UnknownHostException exception) {
|
||||
invocation.getSource().sendMessage(Component.text(
|
||||
"Unexpected error, check console.", NamedTextColor.RED));
|
||||
exception.printStackTrace(System.err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "remove": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
incorrectUsage(invocation.getSource(), "blacklist remove <IP address>");
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
|
||||
try {
|
||||
// Try to resolve the IP address, so we can actually do something with it
|
||||
final InetAddress inetAddress = InetAddress.getAllByName(rawAddress)[0];
|
||||
|
||||
// Make sure the IP is blacklisted
|
||||
if (!Sonar.get().getFallback().getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.ip-not-found"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate the cache entry of the blacklisted IP address
|
||||
Sonar.get().getFallback().getBlacklist().invalidate(inetAddress);
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.remove"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
} catch (UnknownHostException exception) {
|
||||
invocation.getSource().sendMessage(Component.text(
|
||||
"Unexpected error, check console.", NamedTextColor.RED));
|
||||
exception.printStackTrace(System.err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "clear": {
|
||||
final long blacklistSize = Sonar.get().getFallback().getBlacklist().estimatedSize();
|
||||
|
||||
if (blacklistSize == 0) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.empty"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate all cache entries
|
||||
Sonar.get().getFallback().getBlacklist().invalidateAll();
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.clear"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(blacklistSize))));
|
||||
break;
|
||||
}
|
||||
|
||||
case "size": {
|
||||
final long blacklistSize = Sonar.get().getFallback().getBlacklist().estimatedSize();
|
||||
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.blacklist.size"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(blacklistSize))));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
incorrectUsage(invocation.getSource());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-15
@@ -15,13 +15,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommand.impl;
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
@@ -30,7 +30,6 @@ import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.RuntimeMXBean;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
@@ -39,7 +38,6 @@ import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
// the Sonar contributors to fix issues more quickly
|
||||
@SubcommandInfo(
|
||||
name = "dump",
|
||||
description = "Print developer information",
|
||||
onlyConsole = true
|
||||
)
|
||||
public final class DumpCommand extends Subcommand {
|
||||
@@ -49,17 +47,12 @@ public final class DumpCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
final String json = GSON.toJson(collectMappedInformation());
|
||||
SONAR.getLogger().info("Generated dump: {}", json);
|
||||
}
|
||||
|
||||
@Unmodifiable
|
||||
private @NotNull Map<String, Object> collectMappedInformation() {
|
||||
final var mappings = new WeakHashMap<String, Object>();
|
||||
mappings.put("sonar", new Dump.Sonar(
|
||||
SONAR.getVersion().getFull(),
|
||||
SONAR.getPlatform(),
|
||||
SONAR.getVersion().isOnMainBranch()
|
||||
Sonar.get().getVersion().getFormatted(),
|
||||
Sonar.get().getPlatform(),
|
||||
Sonar.get().getVersion().getGitBranch(),
|
||||
Sonar.get().getVersion().getGitCommit()
|
||||
));
|
||||
mappings.put("runtime", new Dump.Runtime(
|
||||
getVirtualCores(),
|
||||
@@ -79,7 +72,8 @@ public final class DumpCommand extends Subcommand {
|
||||
formatMemory(getFreeMemory()),
|
||||
formatMemory(getUsedMemory())
|
||||
));
|
||||
return mappings;
|
||||
Sonar.get().getLogger().info(Sonar.get().getConfig().getMessagesConfig().getString("commands.dump.log")
|
||||
.replace("<dumped-json-data>", GSON.toJson(mappings)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -89,7 +83,8 @@ public final class DumpCommand extends Subcommand {
|
||||
private static final class Sonar {
|
||||
private final String version;
|
||||
private final SonarPlatform platform;
|
||||
private final boolean isOnMainBranch;
|
||||
private final String gitBranch;
|
||||
private final String gitCommit;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
+13
-7
@@ -15,29 +15,35 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommand.impl;
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "notify",
|
||||
description = "Enable or disable attack notifications",
|
||||
onlyPlayers = true
|
||||
)
|
||||
public final class NotifyCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
if (SONAR.getNotificationHandler().isSubscribed(invocation.getSender().getUuid())) {
|
||||
SONAR.getNotificationHandler().unsubscribe(invocation.getSender().getUuid());
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getNotificationsUnsubscribed());
|
||||
if (Sonar.get().getNotificationHandler().isSubscribed(invocation.getSource().getUuid())) {
|
||||
Sonar.get().getNotificationHandler().unsubscribe(invocation.getSource().getUuid());
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.notify.unsubscribe"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getNotificationsSubscribed());
|
||||
SONAR.getNotificationHandler().subscribe(invocation.getSender().getUuid());
|
||||
Sonar.get().getNotificationHandler().subscribe(invocation.getSource().getUuid());
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.notify.subscribe"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
}
|
||||
}
|
||||
+14
-7
@@ -15,28 +15,35 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommand.impl;
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "reload",
|
||||
description = "Reload all configurations"
|
||||
name = "reload"
|
||||
)
|
||||
public final class ReloadCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.reload.start"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
|
||||
final SystemTimer timer = new SystemTimer();
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getReloading());
|
||||
SONAR.reload();
|
||||
Sonar.get().reload();
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getReloaded()
|
||||
.replace("%taken%", String.valueOf(timer.delay())));
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.reload.finish"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("time-taken", String.valueOf(timer.delay()))));
|
||||
}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.common.subcommands;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.Sonar.DECIMAL_FORMAT;
|
||||
import static xyz.jonesdev.sonar.api.jvm.JVMProcessInformation.*;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "statistics",
|
||||
aliases = {"stats"},
|
||||
arguments = {"network", "memory", "cpu"},
|
||||
argumentsRequired = false
|
||||
)
|
||||
public final class StatisticsCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
String type = "general";
|
||||
if (invocation.getRawArguments().length >= 2) {
|
||||
try {
|
||||
type = invocation.getRawArguments()[1].toLowerCase();
|
||||
} catch (Exception exception) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.statistics.unknown-type"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("statistics", getArguments())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.statistics.header"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("statistics-type", type)));
|
||||
invocation.getSource().sendMessage(Component.empty());
|
||||
|
||||
TagResolver.@NotNull Single[] placeholders = null;
|
||||
|
||||
switch (type) {
|
||||
case "general": {
|
||||
final long seconds = Sonar.get().getLaunchTimer().delay() / 1000L;
|
||||
final long days = seconds / (24L * 60L * 60L);
|
||||
final long hours = (seconds % (24L * 60L * 60L)) / (60L * 60L);
|
||||
final long minutes = (seconds % (60L * 60L)) / 60L;
|
||||
|
||||
placeholders = new TagResolver.Single[]{
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("verified", DECIMAL_FORMAT.format(Sonar.get().getVerifiedPlayerController().estimatedSize())),
|
||||
Placeholder.unparsed("verifying", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size())),
|
||||
Placeholder.unparsed("blacklisted", DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize())),
|
||||
Placeholder.unparsed("queued", DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getPlayers().size())),
|
||||
Placeholder.unparsed("server-uptime", String.format("%dd %dh %dm %ds", days, hours, minutes, seconds % 60L)),
|
||||
Placeholder.unparsed("total-joins", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalPlayersJoined())),
|
||||
Placeholder.unparsed("total-attempts", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalAttemptedVerifications())),
|
||||
Placeholder.unparsed("total-failed", DECIMAL_FORMAT.format(Sonar.get().getStatistics().getTotalFailedVerifications()))
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "cpu": {
|
||||
placeholders = new TagResolver.Single[]{
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("process-cpu", DECIMAL_FORMAT.format(getProcessCPUUsage())),
|
||||
Placeholder.unparsed("system-cpu", DECIMAL_FORMAT.format(getSystemCPUUsage())),
|
||||
Placeholder.unparsed("average-process-cpu", DECIMAL_FORMAT.format(getAverageProcessCPUUsage())),
|
||||
Placeholder.unparsed("average-system-cpu", DECIMAL_FORMAT.format(getAverageSystemCPUUsage())),
|
||||
Placeholder.unparsed("virtual-core-count", DECIMAL_FORMAT.format(getVirtualCores()))
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "memory": {
|
||||
placeholders = new TagResolver.Single[]{
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("free-memory", formatMemory(getFreeMemory())),
|
||||
Placeholder.unparsed("used-memory", formatMemory(getUsedMemory())),
|
||||
Placeholder.unparsed("max-memory", formatMemory(getMaxMemory())),
|
||||
Placeholder.unparsed("total-memory", formatMemory(getTotalMemory()))
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "network": {
|
||||
placeholders = new TagResolver.Single[]{
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("incoming-traffic", CachedBandwidthStatistics.INCOMING.getCachedSecond()),
|
||||
Placeholder.unparsed("outgoing-traffic", CachedBandwidthStatistics.OUTGOING.getCachedSecond()),
|
||||
Placeholder.unparsed("incoming-traffic-ttl", CachedBandwidthStatistics.INCOMING.getCachedTtl()),
|
||||
Placeholder.unparsed("outgoing-traffic-ttl", CachedBandwidthStatistics.OUTGOING.getCachedTtl())
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (final String msg : Sonar.get().getConfig().getMessagesConfig().getStringList("commands.statistics." + type)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(msg, placeholders));
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-8
@@ -15,32 +15,38 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.common.subcommand.impl;
|
||||
package xyz.jonesdev.sonar.common.subcommands;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "verbose",
|
||||
description = "Enable or disable Sonar verbose",
|
||||
onlyPlayers = true
|
||||
)
|
||||
public final class VerboseCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
if (SONAR.getVerboseHandler().isSubscribed(invocation.getSender().getUuid())) {
|
||||
SONAR.getVerboseHandler().unsubscribe(invocation.getSender().getUuid());
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerboseUnsubscribed());
|
||||
if (Sonar.get().getVerboseHandler().isSubscribed(invocation.getSource().getUuid())) {
|
||||
Sonar.get().getVerboseHandler().unsubscribe(invocation.getSource().getUuid());
|
||||
// Reset ActionBar component when unsubscribing
|
||||
invocation.getSender().getAudience().sendActionBar(Component.empty());
|
||||
invocation.getSource().getAudience().sendActionBar(Component.empty());
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verbose.unsubscribe"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerboseSubscribed());
|
||||
SONAR.getVerboseHandler().subscribe(invocation.getSender().getUuid());
|
||||
Sonar.get().getVerboseHandler().subscribe(invocation.getSource().getUuid());
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verbose.subscribe"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.common.subcommands;
|
||||
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
@SubcommandInfo(
|
||||
name = "verified",
|
||||
arguments = {"add", "remove", "clear", "size", "history"}
|
||||
)
|
||||
public final class VerifiedCommand extends Subcommand {
|
||||
|
||||
@Override
|
||||
protected void execute(final @NotNull CommandInvocation invocation) {
|
||||
switch (invocation.getRawArguments()[1].toLowerCase()) {
|
||||
case "history": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
incorrectUsage(invocation.getSource(), "verified history <IP address>");
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
|
||||
// Make sure the IP is verified already
|
||||
if (!Sonar.get().getVerifiedPlayerController().has(rawAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.ip-not-found"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.history"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
|
||||
for (final UUID uuid : Sonar.get().getVerifiedPlayerController().getUUIDs(rawAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.history-entry"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("uuid", uuid.toString())));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "remove": {
|
||||
if (invocation.getRawArguments().length <= 2) {
|
||||
incorrectUsage(invocation.getSource(), "verified remove <IP address>");
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
|
||||
// Make sure the player is verified already
|
||||
if (!Sonar.get().getVerifiedPlayerController().has(rawAddress)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.ip-not-found"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
Sonar.get().getVerifiedPlayerController().remove(rawAddress);
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.remove"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
break;
|
||||
}
|
||||
|
||||
case "add": {
|
||||
if (invocation.getRawArguments().length <= 3) {
|
||||
incorrectUsage(invocation.getSource(), "verified add <IP address> <UUID/username>");
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawAddress = validateIP(invocation.getSource(), invocation.getRawArguments()[2]);
|
||||
// Make sure the given IP address is valid
|
||||
if (rawAddress == null) return;
|
||||
|
||||
// Try to parse the UUID (from the username, if needed)
|
||||
final String rawUUID = invocation.getRawArguments()[3];
|
||||
final UUID uuid = rawUUID.length() == 36 ? UUID.fromString(rawUUID)
|
||||
: UUID.nameUUIDFromBytes(("OfflinePlayer:" + rawUUID).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Make sure the player is verified already
|
||||
if (Sonar.get().getVerifiedPlayerController().has(rawAddress, uuid)) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.already"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
// Add verified player to the database
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
Sonar.get().getVerifiedPlayerController().add(new VerifiedPlayer(rawAddress, uuid, timestamp));
|
||||
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.add"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("ip", rawAddress)));
|
||||
break;
|
||||
}
|
||||
|
||||
case "clear": {
|
||||
final int verifiedSize = Sonar.get().getVerifiedPlayerController().estimatedSize();
|
||||
|
||||
if (verifiedSize == 0) {
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.empty"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix())));
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate all cache entries
|
||||
Sonar.get().getVerifiedPlayerController().clearAll();
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.clear"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(verifiedSize))));
|
||||
break;
|
||||
}
|
||||
|
||||
case "size": {
|
||||
final int verifiedSize = Sonar.get().getVerifiedPlayerController().estimatedSize();
|
||||
|
||||
invocation.getSource().sendMessage(MiniMessage.miniMessage().deserialize(
|
||||
Sonar.get().getConfig().getMessagesConfig().getString("commands.verified.size"),
|
||||
Placeholder.component("prefix", Sonar.get().getConfig().getPrefix()),
|
||||
Placeholder.unparsed("amount", Sonar.DECIMAL_FORMAT.format(verifiedSize))));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
incorrectUsage(invocation.getSource());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,56 @@ public class UpdateChecker {
|
||||
}
|
||||
};
|
||||
|
||||
public void checkForUpdates() {
|
||||
ASYNC_EXECUTOR.execute(() -> {
|
||||
try {
|
||||
final URL url = new URL("https://api.github.com/repos/jonesdevelopment/sonar/releases/latest");
|
||||
final HttpsURLConnection urlConnection = prepareConnection(url);
|
||||
final JsonObject json = parseJson(urlConnection);
|
||||
final String latestStableRelease = json.get("tag_name").getAsString();
|
||||
final int convertedLatestVersion = convertVersion(latestStableRelease);
|
||||
final int convertedCurrentVersion = convertVersion(Sonar.get().getVersion().getVersion());
|
||||
|
||||
if (convertedCurrentVersion < convertedLatestVersion) {
|
||||
LOGGER.warn("A new version of Sonar is available: {}", latestStableRelease);
|
||||
LOGGER.warn("Please make sure to update to the latest version to ensure stability and security:");
|
||||
LOGGER.warn("https://github.com/jonesdevelopment/sonar/releases/tag/{}", latestStableRelease);
|
||||
} else if (convertedCurrentVersion > convertedLatestVersion || !Sonar.get().getVersion().getGitBranch().equals("main")) {
|
||||
LOGGER.warn("You are currently using an unreleased version of Sonar!");
|
||||
LOGGER.warn("The contributors of Sonar are not responsible for any damage done by using an unstable version");
|
||||
} else {
|
||||
LOGGER.info("You are currently using the latest stable release of Sonar!");
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
LOGGER.warn("Unable to retrieve version information: {}", throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @NotNull JsonObject parseJson(final @NotNull HttpsURLConnection urlConnection) throws IOException {
|
||||
try (final InputStream inputStream = urlConnection.getInputStream();
|
||||
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
|
||||
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
|
||||
// Parse site content as JSON using Gson
|
||||
final JsonParser jsonParser = new JsonParser();
|
||||
return jsonParser.parse(bufferedReader).getAsJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull HttpsURLConnection prepareConnection(final @NotNull URL url) throws Exception {
|
||||
final HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
|
||||
// Set connection timeout
|
||||
urlConnection.setConnectTimeout(15000);
|
||||
urlConnection.setReadTimeout(15000);
|
||||
// Set necessary properties
|
||||
urlConnection.setRequestProperty("Accept", "application/vnd.github.v3+json");
|
||||
// Only continue if the connection is a success
|
||||
if (urlConnection.getResponseCode() != 200) {
|
||||
throw new IllegalStateException("Unexpected response code " + urlConnection.getResponseCode());
|
||||
}
|
||||
return urlConnection;
|
||||
}
|
||||
|
||||
private int convertVersion(final @NotNull String version) throws NumberFormatException, IllegalStateException {
|
||||
final String[] convertedParts = version.split("\\.");
|
||||
// Validate the length of the converted parts
|
||||
@@ -68,66 +118,4 @@ public class UpdateChecker {
|
||||
final int patch = Integer.parseInt(convertedParts[2]);
|
||||
return convertedMajor + convertedMinor + patch; // sum the converted version parts
|
||||
}
|
||||
|
||||
public void checkForUpdates() {
|
||||
ASYNC_EXECUTOR.execute(() -> {
|
||||
try {
|
||||
final URL url = new URL("https://api.github.com/repos/jonesdevelopment/sonar/releases/latest");
|
||||
final HttpsURLConnection urlConnection = prepareConnection(url);
|
||||
final JsonObject json = parseJson(urlConnection);
|
||||
final String latestStableRelease = json.get("tag_name").getAsString();
|
||||
final int convertedLatestVersion = convertVersion(latestStableRelease);
|
||||
final int convertedCurrentVersion = convertVersion(Sonar.get().getVersion().getSemanticVersion());
|
||||
|
||||
if (convertedCurrentVersion < convertedLatestVersion) {
|
||||
LOGGER.warn("A new version of Sonar is available: {}", latestStableRelease);
|
||||
LOGGER.warn("Please make sure to update to the latest version to ensure stability and security:");
|
||||
LOGGER.warn("https://github.com/jonesdevelopment/sonar/releases/tag/{}", latestStableRelease);
|
||||
} else if (convertedCurrentVersion > convertedLatestVersion || !Sonar.get().getVersion().isOnMainBranch()) {
|
||||
LOGGER.warn("You are currently using an unreleased version of Sonar!");
|
||||
LOGGER.warn("The contributors of Sonar are not responsible for any damage done by using an unstable version");
|
||||
} else {
|
||||
LOGGER.info("You are currently using the latest stable release of Sonar!");
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
LOGGER.warn("Could not retrieve latest version information: {}", exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private @NotNull JsonObject parseJson(final @NotNull HttpsURLConnection urlConnection) throws IOException {
|
||||
try (final InputStream inputStream = urlConnection.getInputStream()) {
|
||||
try (final InputStreamReader inputStreamReader = new InputStreamReader(inputStream)) {
|
||||
final StringBuilder content = new StringBuilder();
|
||||
|
||||
// Read the entire page content
|
||||
try (final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
|
||||
String line;
|
||||
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
content.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse site content as JSON using Gson
|
||||
final JsonParser jsonParser = new JsonParser();
|
||||
return jsonParser.parse(content.toString()).getAsJsonObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull HttpsURLConnection prepareConnection(final @NotNull URL url) throws Exception {
|
||||
final HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
|
||||
// Set connection timeout
|
||||
urlConnection.setConnectTimeout(15000);
|
||||
urlConnection.setReadTimeout(15000);
|
||||
// Set necessary properties
|
||||
urlConnection.setRequestMethod("GET");
|
||||
urlConnection.setRequestProperty("Accept", "application/vnd.github.v3+json");
|
||||
// Only continue if the connection is a success
|
||||
if (urlConnection.getResponseCode() != 200) {
|
||||
throw new IllegalStateException("Unexpected response code " + urlConnection.getResponseCode());
|
||||
}
|
||||
return urlConnection;
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion.MINECRAFT_1_20_3;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeNamelessCompoundTag;
|
||||
import static xyz.jonesdev.sonar.common.util.ProtocolUtil.writeString;
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java
|
||||
public final class ComponentHolder {
|
||||
private final String serializedComponent;
|
||||
private BinaryTag cachedBinaryTag;
|
||||
@@ -44,6 +43,8 @@ public final class ComponentHolder {
|
||||
this.serializedComponent = GsonComponentSerializer.gson().serialize(component);
|
||||
}
|
||||
|
||||
// Mostly taken from
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ComponentHolder.java
|
||||
private BinaryTag serialize(final JsonElement json) {
|
||||
if (json instanceof JsonPrimitive) {
|
||||
final JsonPrimitive jsonPrimitive = (JsonPrimitive) json;
|
||||
@@ -143,7 +144,7 @@ public final class ComponentHolder {
|
||||
|
||||
public void write(final @NotNull ByteBuf byteBuf,
|
||||
final @NotNull ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_3) >= 0) {
|
||||
if (protocolVersion.compareTo(MINECRAFT_1_20_3) >= 0) {
|
||||
if (cachedBinaryTag == null) {
|
||||
cachedBinaryTag = serialize(new JsonParser().parse(serializedComponent));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import io.netty.handler.codec.EncoderException;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.kyori.adventure.nbt.*;
|
||||
@@ -39,10 +38,6 @@ import java.util.UUID;
|
||||
// https://github.com/PaperMC/Velocity/blob/dev/3.0.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java
|
||||
@UtilityClass
|
||||
public class ProtocolUtil {
|
||||
public static final UUID EMPTY_UUID = new UUID(0L, 0L);
|
||||
|
||||
private static final int FORGE_MAX_ARRAY_LENGTH = Integer.MAX_VALUE & 0x1FFF9A;
|
||||
|
||||
public static final String BRAND_CHANNEL_LEGACY = "MC|Brand";
|
||||
public static final String BRAND_CHANNEL = "minecraft:brand";
|
||||
|
||||
@@ -184,15 +179,6 @@ public class ProtocolUtil {
|
||||
return array;
|
||||
}
|
||||
|
||||
public static @NotNull String readBrandMessage(final @NotNull ByteBuf content) throws DecoderException {
|
||||
final ByteBuf slice = content.slice();
|
||||
try {
|
||||
return readString(slice);
|
||||
} catch (DecoderException exception) {
|
||||
return readString(slice, 65536, slice.readableBytes());
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull String readString(final ByteBuf byteBuf) throws CorruptedFrameException {
|
||||
return readString(byteBuf, Short.MAX_VALUE);
|
||||
}
|
||||
@@ -210,7 +196,7 @@ public class ProtocolUtil {
|
||||
checkFrame(length <= cap * 3, "Bad string size");
|
||||
checkFrame(byteBuf.isReadable(length), "Tried to read a too-long string");
|
||||
final String str = byteBuf.toString(byteBuf.readerIndex(), length, StandardCharsets.UTF_8);
|
||||
byteBuf.skipBytes(length);
|
||||
byteBuf.readerIndex(byteBuf.readerIndex() + length);
|
||||
checkFrame(str.length() <= cap, "Got a too-long string");
|
||||
return str;
|
||||
}
|
||||
@@ -315,7 +301,7 @@ public class ProtocolUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static int readExtendedForgeShort(final @NotNull ByteBuf byteBuf) {
|
||||
public static int readExtendedForgeShort(final @NotNull ByteBuf byteBuf) {
|
||||
int low = byteBuf.readUnsignedShort();
|
||||
int high = 0;
|
||||
if ((low & 0x8000) != 0) {
|
||||
@@ -325,12 +311,6 @@ public class ProtocolUtil {
|
||||
return ((high & 0xFF) << 15) | low;
|
||||
}
|
||||
|
||||
public static ByteBuf readRetainedByteBufSlice17(final ByteBuf byteBuf) {
|
||||
final int length = readExtendedForgeShort(byteBuf);
|
||||
checkFrame(length <= FORGE_MAX_ARRAY_LENGTH, "Too long");
|
||||
return byteBuf.readRetainedSlice(length);
|
||||
}
|
||||
|
||||
private void checkFrame(final boolean expression, final String message) {
|
||||
if (!expression) {
|
||||
throw new CorruptedFrameException(message);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,6 +15,9 @@
|
||||
|
||||
# Obecné nastavení pro Sonar
|
||||
general:
|
||||
# Měl by Sonar automaticky kontrolovat nové aktualizace?
|
||||
# Sonar používá GitHub API ke kontrole, zda je aktuální verze zastaralá.
|
||||
check-for-updates: true
|
||||
# Měl by Sonar vypisovat IP adresy hráču do konzole?
|
||||
log-player-addresses: true
|
||||
# Maximum hráču online se stejnou IP adresou
|
||||
@@ -199,9 +202,12 @@ verification:
|
||||
# Tato funkce je experimentální a může způsobit problémy
|
||||
# Pokud je toto zakázáno, hráči Bedrock budou přeskočeni
|
||||
check-geyser-players: true
|
||||
# Množství času který musí uběhnout aby hráč byl odpojen
|
||||
# Doba, za kterou hráč přestal odesílat pakety před odpojením
|
||||
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
|
||||
read-timeout: 8000
|
||||
# Doba od okamžiku, kdy server přestal posílat pakety před odpojením hráče
|
||||
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
|
||||
write-timeout: 10000
|
||||
# Jak dlouho by měl hráč čekat, než se při ověřování znovu připojí
|
||||
# (Hodnota udávaná v milisekundách: 1 sekunda = 1000 milisekund)
|
||||
rejoin-delay: 8000
|
||||
|
||||
@@ -15,28 +15,31 @@
|
||||
|
||||
# Generelle Einstellungen für Sonar
|
||||
general:
|
||||
# Should Sonar automatically check for new updates?
|
||||
# Sonar uses the GitHub API to check if the current version is outdated
|
||||
check-for-updates: true
|
||||
# Soll Sonar die IP Adressen von Spielern in der Konsole anzeigen?
|
||||
log-player-addresses: true
|
||||
# Maximale Anzahl an Spielern mit der selben IP Adresse
|
||||
# Maximale Anzahl an Spielern mit derselben IP Adresse
|
||||
max-online-per-ip: 3
|
||||
|
||||
# Sensitivitätseinstellungen für Angriffstracker
|
||||
# Bearbeite diese Einstellungen nur wenn du weißt was du tust!
|
||||
# Bearbeite diese Einstellungen, nur wenn du weißt, was du tust!
|
||||
attack-tracker:
|
||||
# Minimale Anzahl an neuen Spielern damit ein Angriff als solcher erkannt wird
|
||||
min-players-for-attack: 8
|
||||
# Anzahl an Millisekunden bis ein Angriff vorbei ist
|
||||
min-attack-duration: 30000
|
||||
# Anzahl an gemeldenten Vorfällen damit ein Angriff als solcher erkannt wird
|
||||
# Diese Zahl repräsentiert einen Puffer um falsche Angriffsbenachrichtigungen zu filtern
|
||||
# Diese Zahl repräsentiert einen Puffer, um falsche Angriffsbenachrichtigungen zu filtern
|
||||
min-attack-threshold: 2
|
||||
# Anzahl an Millisekunden die vergehen muss damit eine neue Attacke erkannt wird
|
||||
attack-cooldown-delay: 3000
|
||||
|
||||
# Du kannst Sonar mit einer Datenbank verbinden um verifizierte Spieler zu speichern
|
||||
# Du kannst Sonar mit einer Datenbank verbinden, um verifizierte Spieler zu speichern
|
||||
# Beachte: IP adressen werden im Klartext gespeichert. Du bist für die Sicherheit der Datenbank verantwortlich
|
||||
database:
|
||||
# Typ von Datenbank die Sonar verwenden soll um verifizierte Spielerdaten zu speichern
|
||||
# Typ von Datenbank die Sonar verwenden soll, um verifizierte Spielerdaten zu speichern
|
||||
# Mögliche Typen: NONE, MYSQL, MARIADB
|
||||
type: NONE
|
||||
# HOST für die Authentifizierung mit der SQL Datenbank
|
||||
@@ -196,9 +199,12 @@ verification:
|
||||
# Diese Funktion ist experimentell und kann Probleme verursachen
|
||||
# Wenn dies deaktiviert ist, werden Bedrock-Spieler übersprungen
|
||||
check-geyser-players: true
|
||||
# Zeit, die verstreichen muss, bevor ein Spieler ein Timeout erleidet
|
||||
# Die Zeit, seit der der Spieler keine Pakete mehr sendet, bevor er die Verbindung trennt
|
||||
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
|
||||
read-timeout: 8000
|
||||
# Die Zeit, seit der der Server keine Pakete mehr sendet, bevor er die Verbindung zum Spieler trennt
|
||||
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
|
||||
write-timeout: 10000
|
||||
# Wie lange sollte ein Spieler warten, bevor er während der Überprüfung erneut verbinden kann
|
||||
# (Dieser Wert stellt die Zeit in Millisekunden dar: 1 Sekunde = 1000 Millisekunden)
|
||||
rejoin-delay: 8000
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
|
||||
# General settings for Sonar
|
||||
general:
|
||||
# Should Sonar automatically check for new updates?
|
||||
# Sonar uses the GitHub API to check if the current version is outdated
|
||||
check-for-updates: true
|
||||
# Should Sonar log players' IP addresses in console?
|
||||
log-player-addresses: true
|
||||
# Maximum number of players online with the same IP address
|
||||
@@ -198,13 +201,16 @@ verification:
|
||||
# This feature is experimental and might cause issues
|
||||
# If this is disabled, Bedrock players will be skipped
|
||||
check-geyser-players: true
|
||||
# Amount of time that has to pass before a player times out
|
||||
# The time since the player stopped sending packets before disconnecting
|
||||
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
|
||||
read-timeout: 8000
|
||||
# The time since the server stopped sending packets before disconnecting the player
|
||||
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
|
||||
write-timeout: 10000
|
||||
# How long should a player wait before reconnecting during verification
|
||||
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
|
||||
rejoin-delay: 8000
|
||||
# How long should Sonar remember the amount of failed verifications for a player?
|
||||
# How long should Sonar remember the number of failed verifications for a player?
|
||||
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
|
||||
remember-time: 120000
|
||||
# How long should an IP address be denied from logging in when failing the verification too often?
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
|
||||
# Paramètre général de Sonar
|
||||
general:
|
||||
# Devrait Sonar vérifier automatiquement les nouvelles mises à jour ?
|
||||
# Sonar utilise l'API GitHub pour vérifier si la version actuelle est obsolète.
|
||||
check-for-updates: true
|
||||
# Devrait Sonar enregistrer les adresses IP des joueurs dans la console ?
|
||||
log-player-addresses: true
|
||||
# Nombre maximum de joueurs en ligne avec la même IP
|
||||
@@ -198,9 +201,12 @@ verification:
|
||||
# Cette fonctionnalité est expérimentale et peut causer des problèmes
|
||||
# Si cela est désactivé, les joueurs Bedrock seront ignorés
|
||||
check-geyser-players: true
|
||||
# Durée avant qu'un joueur ne soit déconnecté
|
||||
# Temps écoulé depuis que le joueur a cessé d'envoyer des paquets avant de se déconnecter
|
||||
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
|
||||
read-timeout: 8000
|
||||
# Temps écoulé depuis que le serveur a cessé d'envoyer des paquets avant de se déconnecter
|
||||
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
|
||||
write-timeout: 10000
|
||||
# Combien de temps un joueur doit-il attendre avant de se reconnecter pendant la vérification
|
||||
# (Cette valeur représente le temps en millisecondes : 1 seconde = 1000 millisecondes)
|
||||
rejoin-delay: 8000
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
|
||||
# ზოგადი პარამეტრები Sonar-ისთვის
|
||||
general:
|
||||
# Should Sonar automatically check for new updates?
|
||||
# Sonar uses the GitHub API to check if the current version is outdated
|
||||
check-for-updates: true
|
||||
# უნდა ჩაწეროს თუ არა Sonar-მა მოთამაშეების IP მისამართები კონსოლში?
|
||||
log-player-addresses: true
|
||||
# მაქსიმალური ერთი და იგივე IP ზე ონლაინ მოთამაშეთა რაოდენობა
|
||||
@@ -200,6 +203,9 @@ verification:
|
||||
# დროის ოდენობა, რომელიც უნდა გავიდეს, სანამ მოთამაშე ტაიმაუტდება სერვერიდან.
|
||||
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
|
||||
read-timeout: 8000
|
||||
# The time since the server stopped sending packets before disconnecting the player
|
||||
# (This value represents the time in milliseconds: 1 second = 1000 milliseconds)
|
||||
write-timeout: 10000
|
||||
# რამდენ ხანს უნდა დაელოდოს მოთამაშე ვერიფიკაციის დროს ხელახლა დაკავშირებამდე?
|
||||
# (ეს არის ნაჩვენები მილიწამებში: 1 წამი = 1000 მილიწამი)
|
||||
rejoin-delay: 8000
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
|
||||
# Algemene instellingen voor Sonar
|
||||
general:
|
||||
# Moet Sonar automatisch controleren op nieuwe updates?
|
||||
# Sonar gebruikt de GitHub API om te controleren of de huidige versie verouderd is
|
||||
check-for-updates: true
|
||||
# Moet Sonar de IP-adressen van spelers in de console registreren?
|
||||
log-player-addresses: true
|
||||
# Maximaal aantal spelers online met hetzelfde IP-adres
|
||||
@@ -201,9 +204,12 @@ verification:
|
||||
# De hoeveelheid tijd die moet verstrijken voordat een speler een time-out krijgt
|
||||
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
|
||||
read-timeout: 8000
|
||||
# Hoe lang moet een speler wachten voordat hij tijdens de verificatie opnieuw verbinding maakt?
|
||||
# De tijd sinds de speler stopte met het verzenden van pakketten voordat de verbinding werd verbroken
|
||||
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
|
||||
rejoin-delay: 8000
|
||||
# De tijd sinds de server stopte met het versturen van pakketten voordat de verbinding met de speler werd verbroken
|
||||
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
|
||||
write-timeout: 10000
|
||||
# Hoe lang moet Sonar het aantal mislukte verificaties voor een speler onthouden?
|
||||
# (Deze waarde vertegenwoordigt de tijd in milliseconden: 1 seconde = 1000 milliseconden)
|
||||
remember-time: 120000
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
|
||||
# Основные настройки
|
||||
general:
|
||||
# Должен ли Sonar автоматически проверять наличие новых обновлений?
|
||||
# Sonar использует API GitHub, чтобы проверить, не устарела ли текущая версия
|
||||
check-for-updates: true
|
||||
# Должен ли Sonar логировать IP-адреса игроков в консоли?
|
||||
log-player-addresses: true
|
||||
# Максимальное количество игроков онлайн с одного IP-адреса
|
||||
@@ -197,13 +200,16 @@ verification:
|
||||
# Эта функция экспериментальна и может вызывать проблемы
|
||||
# Если это отключено, игроки Bedrock будут пропущены
|
||||
check-geyser-players: true
|
||||
# Количество времени, которое должно пройти, прежде чем игрока кикнет.
|
||||
# Время, прошедшее с момента прекращения отправки пакетов игроком до отключения.
|
||||
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
|
||||
read-timeout: 8000
|
||||
# Время, прошедшее с момента прекращения отправки сервером пакетов до отключения игрока
|
||||
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
|
||||
write-timeout: 10000
|
||||
# Сколько времени игрок должен ждать перед повторным подключением во время проверки
|
||||
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
|
||||
rejoin-delay: 8000
|
||||
# How long should Sonar remember the amount of failed verifications for a player?
|
||||
# Как долго Sonar должен помнить количество неудачных проверок для игрока?
|
||||
# (Это значение представляет время в миллисекундах: 1 секунда = 1000 миллисекунд)
|
||||
remember-time: 120000
|
||||
# Как долго IP-адресу должно быть запрещено входить на сервер, если он слишком часто не проходит проверку?
|
||||
|
||||
@@ -8,13 +8,16 @@
|
||||
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
|
||||
# \______/ \______/ |__/ |__/ \_______/|__/
|
||||
#
|
||||
# 有问题或需要帮助? https://jonesdev.xyz/discord
|
||||
# 需要帮助吗? https://jonesdev.xyz/discord
|
||||
# 源代码: https://github.com/jonesdevelopment/sonar
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# 未归类的设置
|
||||
general:
|
||||
# Sonar 是否应该自动检查新的更新?
|
||||
# Sonar 使用 GitHub API 检查当前版本是否过时
|
||||
check-for-updates: true
|
||||
# Sonar 应该在控制台中打印玩家的 IP 地址吗?
|
||||
log-player-addresses: true
|
||||
# 具有相同 IP 地址的在线玩家的最大数量
|
||||
@@ -103,37 +106,38 @@ verification:
|
||||
# - NEVER: 永远不应用该检查
|
||||
timing: NEVER
|
||||
# 图像效果选项 (用于混淆自动图像识别)
|
||||
# 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
|
||||
# Sonar 应该应用什么形状的失真滤镜?
|
||||
# 支持的选项: 0, 1, 2, 3
|
||||
# - 0: 交叉
|
||||
# - 1: 线条
|
||||
# - 2: 圆形
|
||||
# - 3: 方形
|
||||
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
|
||||
# 用于当作验证码的背景的图片
|
||||
# 如果未找到指定的图片, 则自动通过噪音生成一个随机的背景图片.
|
||||
# 如果图片在该插件的配置文件夹下, 则可以简单地输入图片的文件名.
|
||||
background: ''
|
||||
# Should Sonar automatically detect the background color?
|
||||
# This is done to create a contrast between the background image and the text
|
||||
# You can simply put the name of the file here if file is in the plugin directory
|
||||
# Sonar 应该检测背景图片的颜色吗?
|
||||
# 这样做是为了避免无法区分背景和验证码的颜色.
|
||||
# 如果禁用该功能, Sonar 将会随机应用渐变的颜色.
|
||||
auto-color: false
|
||||
# Sonar 应该一次准备多少个验证码?
|
||||
precompute: 500
|
||||
@@ -190,9 +194,12 @@ verification:
|
||||
# 此功能是实验性的. 请报告任何因为误判而导致无法通过检查的问题
|
||||
# 如果关闭该选项 Sonar 将不检查这些玩家以便直接连接到服务器.
|
||||
check-geyser-players: true
|
||||
# 要在客户端未响应多少毫秒后因超时而被踢出服务器?
|
||||
# 播放器断开连接前停止发送数据包的时间
|
||||
# (该值为毫秒. 1秒等于1000毫秒.)
|
||||
read-timeout: 8000
|
||||
# 服务器在断开玩家连接前停止发送数据包的时间
|
||||
# (该值为毫秒. 1秒等于1000毫秒.)
|
||||
write-timeout: 10000
|
||||
# 当玩家在需要验证时(即还未通过检查的情况下)需要等待多少毫秒才可以重新连接?
|
||||
# (该值为毫秒. 1秒等于1000毫秒.)
|
||||
rejoin-delay: 8000
|
||||
|
||||
@@ -13,268 +13,252 @@
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# Platzhalter für jedes '%prefix%' in dieser Konfigurationsdatei
|
||||
# Platzhalter für jedes '<prefix>' in dieser Konfigurationsdatei
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Platzhalter für jede '%support-url%' in dieser Konfigurationsdatei
|
||||
# Platzhalter für jede '<support-url>' in dieser Konfigurationsdatei
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# Platzhalter für jedes '%header%' in dieser Konfigurationsdatei
|
||||
# Platzhalter für jedes '<header>' in dieser Konfigurationsdatei
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# Platzhalter für jedes '%footer%' in dieser Konfigurationsdatei
|
||||
# Platzhalter für jedes '<footer>' in dieser Konfigurationsdatei
|
||||
footer:
|
||||
- <gray>Falls du glaubst, dass dies ein Fehler ist, kontaktiere einen Administrator.
|
||||
|
||||
# Nachricht, die angezeigt wird, wenn jemand beitritt, aber zu viele Online-Spieler mit ihrer IP-Adresse vorhanden sind
|
||||
too-many-online-per-ip:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>Es sind zu viele Spieler mit deiner IP-Adresse online.
|
||||
- '%footer%'
|
||||
- '<footer>'
|
||||
|
||||
# Übersetzungen für Befehle
|
||||
commands:
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler versucht, /sonar ohne Berechtigung auszuführen
|
||||
no-permission: '%prefix%<red>Du hast keine Berechtigung, diesen Befehl auszuführen.'
|
||||
no-permission: '<prefix><red>Du hast keine Berechtigung, diesen Befehl auszuführen.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand einen Befehl falsch verwendet
|
||||
incorrect-usage: '%prefix%<red>Benutzung: /sonar %usage%'
|
||||
incorrect-usage: '<prefix><red>Benutzung: /sonar <subcommand-usage>'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine ungültige IP-Adresse angibt (Ungültiges Format)
|
||||
invalid-ip-address: '%prefix%Die angegebene IP-Adresse scheint ungültig zu sein.'
|
||||
invalid-ip-address: '<prefix>Die angegebene IP-Adresse scheint ungültig zu sein.'
|
||||
# Nachricht, die angezeigt wird, wenn die Konsole einen Befehl ausführt, der nur für Spieler ist
|
||||
player-only: '%prefix%<red>Diesen Befehl kannst du nur als Spieler ausführen.'
|
||||
player-only: '<prefix><red>Diesen Befehl kannst du nur als Spieler ausführen.'
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler einen Befehl ausführt, der nur für die Konsole ist
|
||||
console-only: '%prefix%<red>Aus Sicherheitsgründen kannst du diesen Befehl nur über die Konsole ausführen.'
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler Sonar-Befehle zu schnell ausführt
|
||||
cool-down: '%prefix%<red>Du kannst diesen Befehl nur alle 0,5 Sekunden ausführen.'
|
||||
cool-down-left: '%prefix%<red>Bitte warte noch <bold>%time-left%s<reset><red>.'
|
||||
console-only: '<prefix><red>Aus Sicherheitsgründen kannst du diesen Befehl nur über die Konsole ausführen.'
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler keine Berechtigung hat, einen bestimmten Unterbefehl auszuführen
|
||||
subcommand-no-permission: '%prefix%<red>Du hast keine Berechtigung, diesen Unterbefehl auszuführen. <gray>(%permission%)'
|
||||
subcommand-no-permission: '<prefix><red>Du hast keine Berechtigung, diesen Unterbefehl auszuführen. <gray>(<permission>)'
|
||||
|
||||
# Übersetzungen für '/sonar'
|
||||
main:
|
||||
# Informationsnachricht, die oben angezeigt wird, wenn der Hauptbefehl ausgeführt wird
|
||||
header:
|
||||
- <yellow>Ausführung von <b>Sonar</b> %version% auf %platform%.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
|
||||
Jones Development und Sonar Mitwirkende</click>
|
||||
- <yellow>Ausführung von <b>Sonar</b> <version> auf <platform>.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development und Sonar Mitwirkende</click>
|
||||
- ''
|
||||
- <gray>Brauchst du Hilfe oder hast du Fragen?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Klicken
|
||||
um Discord zu öffnen)'>Öffne ein Ticket auf Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Klicken
|
||||
um GitHub zu öffnen)'>oder erstelle ein neues Issue auf GitHub.
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Klicken um Discord zu öffnen)'>Öffne ein Ticket auf Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Klicken um GitHub zu öffnen)'>oder erstelle ein neues Issue auf GitHub.
|
||||
- ''
|
||||
# Formatierung der Liste der Unterbefehle, die angezeigt wird, wenn der Hauptbefehl ausgeführt wird
|
||||
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>Nur
|
||||
Spieler: </gray>%only_players%<br><gray>Erfordert Konsole: </gray>%require_console%<br><gray>Berechtigung:
|
||||
</gray><white>%permission%<br><gray>Aliase: </gray>%aliases%''><dark_aqua>
|
||||
▪ <gray>/sonar %subcommand% <white>%description%'
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Nur Spieler: </gray><only-players><br><gray>Erfordert Konsole: </gray><only-console><br><gray>Berechtigung: </gray><white><permission><br><gray>Aliase: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# Formatierung für Ja- (cross) und Nein- (tick) Werte in den Unterbefehlen hover Text
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# Übersetzungen für '/sonar reload'
|
||||
reload:
|
||||
# Beschreibung für diesen Unterbefehl
|
||||
description: "Alle Konfigurationen neu laden"
|
||||
# Nachricht, die angezeigt wird, wenn jemand mit dem Neuladen von Sonar beginnt
|
||||
start: '%prefix%Sonar wird neu geladen...'
|
||||
start: '<prefix>Sonar wird neu geladen...'
|
||||
# Nachricht, die angezeigt wird, wenn Sonar erfolgreich neu geladen wurde
|
||||
finish: '%prefix%<green>Erfolgreich neu geladen <gray>(%taken%ms)'
|
||||
finish: '<prefix><green>Erfolgreich neu geladen <gray>(<time-taken>ms)'
|
||||
|
||||
# Übersetzungen für '/sonar verbose'
|
||||
verbose:
|
||||
# Beschreibung für diesen Unterbefehl
|
||||
description: "Aktivieren oder deaktivieren Sie Verbose-Informationen"
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler Sonar Verbose-Informationen abonniert
|
||||
subscribed: '%prefix%Du siehst jetzt Sonar Verbose-Informationen.'
|
||||
subscribe: '<prefix>Du siehst jetzt Sonar Verbose-Informationen.'
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler Sonar Verbose-Informationen abbestellt
|
||||
unsubscribed: '%prefix%Du siehst keine Sonar Verbose-Informationen mehr.'
|
||||
unsubscribe: '<prefix>Du siehst keine Sonar Verbose-Informationen mehr.'
|
||||
|
||||
# Übersetzungen für '/sonar notify'
|
||||
notify:
|
||||
# Beschreibung für diesen Unterbefehl
|
||||
description: "Aktivieren oder deaktivieren Sie Angriffsbenachrichtigungen"
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler Sonar Angriffsbenachrichtigungen abonniert
|
||||
subscribed: '%prefix%Du siehst jetzt Sonar Angriffsbenachrichtigungen.'
|
||||
subscribe: '<prefix>Du siehst jetzt Sonar Angriffsbenachrichtigungen.'
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler Sonar Angriffsbenachrichtigungen abbestellt
|
||||
unsubscribed: '%prefix%Du siehst keine Sonar Angriffsbenachrichtigungen mehr.'
|
||||
unsubscribe: '<prefix>Du siehst keine Sonar Angriffsbenachrichtigungen mehr.'
|
||||
|
||||
# Übersetzungen für '/sonar blacklist'
|
||||
blacklist:
|
||||
# Beschreibung für diesen Unterbefehl
|
||||
description: "Verwalten von IP-Adressen auf der schwarzen Liste"
|
||||
# Nachricht, die angezeigt wird, wenn jemand versucht, die Blacklist zu leeren, aber sie leer ist
|
||||
empty: '%prefix%Die Blacklist ist derzeit leer. Daher wurden keine IP-Adressen von der Blacklist entfernt.'
|
||||
empty: '<prefix>Die Blacklist ist derzeit leer. Daher wurden keine IP-Adressen von der Blacklist entfernt.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand die Blacklist leert
|
||||
cleared: '%prefix%Du hast erfolgreich insgesamt %removed% IP-Adresse(n) von der Blacklist entfernt.'
|
||||
clear: '<prefix>Du hast erfolgreich insgesamt <amount> IP-Adresse(n) von der Blacklist entfernt.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand die Größe der Blacklist überprüft
|
||||
size: '%prefix%Die Blacklist enthält derzeit %amount% IP-Adresse(n).'
|
||||
size: '<prefix>Die Blacklist enthält derzeit <amount> IP-Adresse(n).'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse zur Blacklist hinzufügt
|
||||
added: '%prefix%Erfolgreich %ip% zur Blacklist hinzugefügt.'
|
||||
add: '<prefix>Erfolgreich <ip> zur Blacklist hinzugefügt.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse zur Blacklist hinzufügt, die verifiziert ist
|
||||
added-warning: '%prefix%<red>Warnung: <white>%ip% ist derzeit auf der Whitelist. Erwäge,
|
||||
die IP-Adresse aus der Liste der verifizierten Spieler zu entfernen, um potenzielle
|
||||
Probleme zu vermeiden.'
|
||||
add-warning: '<prefix><red>Warnung: <white><ip> ist derzeit auf der Whitelist. Erwäge, die IP-Adresse aus der Liste der verifizierten Spieler zu entfernen, um potenzielle Probleme zu vermeiden.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse von der Blacklist entfernt
|
||||
removed: '%prefix%Erfolgreich %ip% von der Blacklist entfernt.'
|
||||
remove: '<prefix>Erfolgreich <ip> von der Blacklist entfernt.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse zur Blacklist hinzufügt, die bereits auf der Blacklist steht
|
||||
duplicate-ip: '%prefix%Die angegebene IP-Adresse ist bereits auf der Blacklist.'
|
||||
ip-duplicate: '<prefix>Die angegebene IP-Adresse ist bereits auf der Blacklist.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse von der Blacklist entfernt, die nicht auf der Blacklist steht
|
||||
ip-not-found: '%prefix%Die angegebene IP-Adresse ist nicht auf der Blacklist.'
|
||||
ip-not-found: '<prefix>Die angegebene IP-Adresse ist nicht auf der Blacklist.'
|
||||
|
||||
# Übersetzungen für '/sonar verified'
|
||||
verified:
|
||||
# Beschreibung für diesen Unterbefehl
|
||||
description: "Verwalten Sie verifizierte IP-Adressen"
|
||||
# Nachricht, die angezeigt wird, wenn jemand versucht, die Liste der verifizierten Spieler zu leeren, aber sie leer ist
|
||||
empty: '%prefix%Die Liste der verifizierten Spieler ist derzeit leer. Daher wurden keine
|
||||
Spieler unverifiziert.'
|
||||
empty: '<prefix>Die Liste der verifizierten Spieler ist derzeit leer. Daher wurden keine Spieler unverifiziert.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand die Liste der verifizierten Spieler leert
|
||||
cleared: '%prefix%Du hast erfolgreich insgesamt %removed% einzigartige Spieler unverifiziert.'
|
||||
clear: '<prefix>Du hast erfolgreich insgesamt <amount> einzigartige Spieler unverifiziert.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand die Größe der Liste der verifizierten Spieler überprüft
|
||||
size: '%prefix%Es sind derzeit %amount% einzigartige Spieler verifiziert.'
|
||||
size: '<prefix>Es sind derzeit <amount> einzigartige Spieler verifiziert.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse unverifiziert macht
|
||||
removed: '%prefix%Erfolgreich %ip% unverifiziert.'
|
||||
remove: '<prefix>Erfolgreich <ip> unverifiziert.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse verifiziert macht
|
||||
added: '%prefix%Erfolgreich %ip% verifiziert.'
|
||||
add: '<prefix>Erfolgreich <ip> verifiziert.'
|
||||
# Message that is shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: '%prefix%The IP address and UUID you provided are already verified.'
|
||||
already: '<prefix>The IP address and UUID you provided are already verified.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse unverifiziert macht, die nicht verifiziert ist
|
||||
ip-not-found: '%prefix%Die angegebene IP-Adresse ist nicht verifiziert.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand versucht, die gleiche IP-Adresse zweimal zu unverifizieren (doppelte Operation)
|
||||
blocked: '%prefix%Bitte warte, bis der aktuelle Vorgang abgeschlossen ist.'
|
||||
ip-not-found: '<prefix>Die angegebene IP-Adresse ist nicht verifiziert.'
|
||||
# Message shown when someone looks up the verification history of an IP address
|
||||
history: '<prefix><yellow>Bisherige UUIDs für <ip>:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# Übersetzungen für '/sonar statistics'
|
||||
statistics:
|
||||
# Beschreibung für diesen Unterbefehl
|
||||
description: "Sitzungsstatistiken für diesen Server anzeigen"
|
||||
# Informationsnachricht, die oben angezeigt wird, wenn die Statistiken angesehen werden
|
||||
header: '%prefix%Anzeige der %type%-Statistiken für diese Sitzung:'
|
||||
header: '<prefix>Anzeige der <statistics-type>-Statistiken für diese Sitzung:'
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler versucht, eine unbekannte Statistik anzusehen
|
||||
unknown-type: '%prefix%<red>Unbekannter Statistiktyp! Verfügbare Statistiken: <gray>%statistics%'
|
||||
unknown-type: '<prefix><red>Unbekannter Statistiktyp! Verfügbare Statistiken: <gray><statistics>'
|
||||
# Format der allgemeinen Statistiknachricht
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>Verifizierte IP-Adressen: <white>%verified%'
|
||||
- ' <dark_aqua>▪ <gray>IP-Adressen in Überprüfung: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Auf der Blacklist stehende IP-Adressen: <white>%blacklisted%'
|
||||
- ' <dark_aqua>▪ <gray>Aktuell in der Warteschlange befindliche Anmeldungen: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte nicht-einzigartige Beitritte: <white>%total_joins%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte Überprüfungsversuche: <white>%total_attempts%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte fehlgeschlagene Überprüfungen: <white>%total_failed%'
|
||||
- ' <dark_aqua>▪ <gray>Server-Laufzeit: <white>%uptime%'
|
||||
- ' <dark_aqua>▪ <gray>Verifizierte IP-Adressen: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>IP-Adressen in Überprüfung: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Auf der Blacklist stehende IP-Adressen: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>Aktuell in der Warteschlange befindliche Anmeldungen: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte nicht-einzigartige Beitritte: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte Überprüfungsversuche: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte fehlgeschlagene Überprüfungen: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>Server-Laufzeit: <white><server-uptime>'
|
||||
# Format der CPU-Statistiknachricht
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>Aktuelle CPU-Nutzung des Prozesses: <white>%process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Aktuelle CPU-Nutzung des Systems: <white>%system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Durchschnittliche CPU-Nutzung des Prozesses pro Kern: <white>%average_process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Durchschnittliche CPU-Nutzung des Systems pro Kern: <white>%average_system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Allgemeine Systemlast im Durchschnitt: <white>%load_average%%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamtanzahl virtueller CPUs: <white>%virtual_cores%'
|
||||
- ' <dark_aqua>▪ <gray>Aktuelle CPU-Nutzung des Prozesses: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Aktuelle CPU-Nutzung des Systems: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Durchschnittliche CPU-Nutzung des Prozesses pro Kern: <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Durchschnittliche CPU-Nutzung des Systems pro Kern: <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamtanzahl virtueller CPUs: <white><virtual-core-count>'
|
||||
# Format der Speicherstatistiknachricht
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>Gesamter freier Speicher: <white>%free_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamter genutzter Speicher: <white>%used_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamter maximaler Speicher: <white>%max_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamter zugewiesener Speicher: <white>%total_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamter freier Speicher: <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Gesamter genutzter Speicher: <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Gesamter maximaler Speicher: <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Gesamter zugewiesener Speicher: <white><total-memory>'
|
||||
# Format der Netzwerkstatistiknachricht
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>Aktuell genutzte eingehende Bandbreite: <white>%incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Aktuell genutzte ausgehende Bandbreite: <white>%outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte genutzte eingehende Bandbreite: <white>%ttl_incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte genutzte ausgehende Bandbreite: <white>%ttl_outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Aktuell genutzte eingehende Bandbreite: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Aktuell genutzte ausgehende Bandbreite: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte genutzte eingehende Bandbreite: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>Gesamte genutzte ausgehende Bandbreite: <white><outgoing-traffic-ttl>'
|
||||
|
||||
# Übersetzungen für '/sonar dump'
|
||||
dump:
|
||||
# Beschreibung für diesen Unterbefehl
|
||||
description: "Entwicklerinformationen ausgeben"
|
||||
# Format der an die Konsole gesendeten Informationen
|
||||
log: "Erstellter Dump: <dumped-json-data>"
|
||||
|
||||
# Übersetzungen für alle Nachrichten während des Übergabeprozesses
|
||||
verification:
|
||||
# Übersetzungen für alle Debug-Nachrichten während der Überprüfung
|
||||
logs:
|
||||
# Nachricht, die im Protokoll angezeigt wird, wenn ein neuer Spieler den Server betretet
|
||||
connection: '%name%%ip% (%protocol%) hat sich verbunden.'
|
||||
connection: '<username><ip> (<protocol>) hat sich verbunden.'
|
||||
# Nachricht, die im Protokoll angezeigt wird, wenn ein Spieler die Verifizierung nicht besteht
|
||||
failed: '%ip% (%protocol%) hat den Bot-Check nicht bestanden wegen: %reason%'
|
||||
failed: '<ip> (<protocol>) hat den Bot-Check nicht bestanden wegen: <reason>'
|
||||
# Nachricht, die im Protokoll angezeigt wird, wenn ein Spieler aufgrund zu vieler fehlgeschlagener Versuche auf die Blacklist gesetzt wird
|
||||
blacklisted: '%ip% (%protocol%) wurde aufgrund zu vieler fehlgeschlagener Versuche auf die Blacklist gesetzt'
|
||||
blacklisted: '<ip> (<protocol>) wurde aufgrund zu vieler fehlgeschlagener Versuche auf die Blacklist gesetzt'
|
||||
# Nachricht, die im Protokoll angezeigt wird, wenn ein Spieler erfolgreich verifiziert wurde
|
||||
successful: '%name% wurde erfolgreich verifiziert (%time%s!).'
|
||||
successful: '<username> wurde erfolgreich verifiziert (<time-taken>s!).'
|
||||
|
||||
# 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-code: '<prefix><green>Bitte gib den Code 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 den falschen Code 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%'
|
||||
- <gold>Falschmeldung? <gray><support-url>
|
||||
- '<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%'
|
||||
- <gold>Brauchst du Hilfe beim Einloggen? <gray><support-url>
|
||||
- '<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? <gray>%support-url%
|
||||
- '%footer%'
|
||||
- <gold>Brauchst du Hilfe beim Einloggen? <gray><support-url>
|
||||
- '<footer>'
|
||||
|
||||
# Übersetzungen für Sonars ausführliche Aktionsleisten-Nachrichten
|
||||
verbose:
|
||||
layout:
|
||||
# Layout für Sonars ausführliche Aktionsleiste
|
||||
# Platzhalter:
|
||||
# - '%queued%' Anzahl der in der Warteschlange befindlichen Verbindungen
|
||||
# - '%verifying%' Anzahl der Verifizierungen
|
||||
# - '%blacklisted%' Anzahl der IP-Adressen auf der Blacklist
|
||||
# - '%total-joins%' Gesamtzahl der Versuche beizutreten
|
||||
# - '%verify-total%' Gesamtzahl der Verifizierungsversuche
|
||||
# - '%verify-success%' Anzahl der verifizierten IP-Adressen
|
||||
# - '%verify-failed%' Anzahl der gescheiterten Verifizierungen
|
||||
# - '%logins-per-second%' Anzahl der Anmeldungen pro Sekunde
|
||||
# - '%connections-per-second%' Anzahl der Verbindungen pro Sekunde
|
||||
# - '%attack-duration%' Dauer des aktuellen Angriffs
|
||||
# - '%incoming-traffic%' Eingehende Bandbreitennutzung pro Sekunde
|
||||
# - '%outgoing-traffic%' Ausgehende Bandbreitennutzung pro Sekunde
|
||||
# - '%incoming-traffic-ttl%' Gesamte eingehende Bandbreitennutzung
|
||||
# - '%outgoing-traffic-ttl%' Gesamte ausgehende Bandbreitennutzung
|
||||
# - '%used-memory%' Menge des genutzten Arbeitsspeichers des Prozesses
|
||||
# - '%total-memory%' Menge des gesamten Arbeitsspeichers des Prozesses
|
||||
# - '%max-memory%' Menge des maximalen Arbeitsspeichers des Prozesses
|
||||
# - '%free-memory%' Menge des freien Arbeitsspeichers des Prozesses
|
||||
# - '%animation%' Anpassbares animiertes Symbol
|
||||
# Übersetzungen für Sonars normale ausführliche Ausgabe
|
||||
normal: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Anmeldungen/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Verifizierung <white>%verifying% <dark_aqua>|
|
||||
<green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
normal: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Anmeldungen/s <white><logins-per-second> <dark_aqua>╺ <gray>Verifizierung <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Layout für Sonars ausführliche Aktionsleiste während eines Angriffs
|
||||
attack: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Anmeldungen/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Warteschlange <white>%queued% <dark_aqua>╺
|
||||
<gray>Verifizierung <white>%verifying% <dark_aqua>╺ <gray>Dauer <white>%attack-duration%
|
||||
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
attack: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Anmeldungen/s <white><logins-per-second> <dark_aqua>╺ <gray>Warteschlange <white><queued> <dark_aqua>╺ <gray>Verifizierung <white><verifying> <dark_aqua>╺ <gray>Dauer <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Animation für die Aktionsleiste
|
||||
# Alternativen:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
@@ -297,11 +281,9 @@ notifications:
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>Ein Bot-Angriff wurde erkannt.
|
||||
- ' <dark_aqua>▪ <gray>Anmeldungen pro Sekunde: <white>%logins-per-second%'
|
||||
- ' <dark_aqua>▪ <gray>Verifizierende Spieler: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Spieler in der Warteschlange: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Anmeldungen pro Sekunde: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>Verifizierende Spieler: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Spieler in der Warteschlange: <white><queued>'
|
||||
- ''
|
||||
- <gray>Zeige mehr Informationen mit <white><click:run_command:'/sonar verbose'><hover:show_text:'(Click
|
||||
to run)'>/sonar verbose</hover></click><gray> oder <white><click:run_command:'/sonar
|
||||
stats'><hover:show_text:'(Click to run)'>/sonar stats</hover></click><gray>.
|
||||
- <gray>Zeige mehr Informationen mit <white><click:run_command:'/sonar verbose'><hover:show_text:'(Click to run)'>/sonar verbose</hover></click><gray> oder <white><click:run_command:'/sonar stats'><hover:show_text:'(Click to run)'>/sonar stats</hover></click><gray>.
|
||||
- ''
|
||||
@@ -13,272 +13,251 @@
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# Placeholder for every '%prefix%' in this configuration file
|
||||
# Placeholder for every '<prefix>' in this configuration file
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Placeholder for every '%support-url%' in this configuration file
|
||||
# Placeholder for every '<support-url>' in this configuration file
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# Placeholder for every '%header%' in this configuration file
|
||||
# Placeholder for every '<header>' in this configuration file
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# Placeholder for every '%footer%' in this configuration file
|
||||
# Placeholder for every '<footer>' in this configuration file
|
||||
footer:
|
||||
- <gray>If you believe that this is an error, contact an administrator.
|
||||
# Disconnect message that is shown when someone joins, but there are too many online players with their IP address
|
||||
too-many-online-per-ip:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>There are too many players online with your IP address.
|
||||
- '%footer%'
|
||||
- '<footer>'
|
||||
|
||||
# Translations for commands
|
||||
commands:
|
||||
# Message shown when a player tries running /sonar without permission
|
||||
no-permission: '%prefix%<red>You do not have permission to execute this command.'
|
||||
no-permission: '<prefix><red>You do not have permission to execute this command.'
|
||||
# Message shown when someone uses a command incorrectly
|
||||
incorrect-usage: '%prefix%<red>Usage: /sonar %usage%'
|
||||
incorrect-usage: '<prefix><red>Usage: /sonar <subcommand-usage>'
|
||||
# Message shown when someone provides an invalid IP address (Invalid format)
|
||||
invalid-ip-address: '%prefix%The IP address you provided seems to be invalid.'
|
||||
invalid-ip-address: '<prefix>The IP address you provided seems to be invalid.'
|
||||
# Message shown when the console runs a command that is player-only
|
||||
player-only: '%prefix%<red>You can only execute this command as a player.'
|
||||
player-only: '<prefix><red>You can only execute this command as a player.'
|
||||
# Message shown when a player runs a command that is console-only
|
||||
console-only: '%prefix%<red>For security reasons, you can only execute this command
|
||||
through console.'
|
||||
# Message shown when a player executes Sonar commands too quickly
|
||||
cool-down: '%prefix%<red>You can only execute this command every 0.5 seconds.'
|
||||
cool-down-left: '%prefix%<red>Please wait another <bold>%time-left%s<reset><red>.'
|
||||
console-only: '<prefix><red>For security reasons, you can only execute this command through console.'
|
||||
# Message shown when a player does not have permission to execute a certain subcommand
|
||||
subcommand-no-permission: '%prefix%<red>You do not have permission to execute this
|
||||
subcommand. <gray>(%permission%)'
|
||||
subcommand-no-permission: '<prefix><red>You do not have permission to execute this subcommand. <gray>(<permission>)'
|
||||
|
||||
# Translations for '/sonar'
|
||||
main:
|
||||
# Informational message that is shown above everything when running the main command
|
||||
header:
|
||||
- <yellow>Running <b>Sonar</b> %version% on %platform%.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
|
||||
Jones Development and Sonar Contributors</click>
|
||||
- <yellow>Running <b>Sonar</b> <version> on <platform>.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development and Sonar Contributors</click>
|
||||
- ''
|
||||
- <gray>Need help or have any questions?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Click
|
||||
to open Discord)'>Open a ticket on the Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Click
|
||||
to open GitHub)'>or open a new issue on GitHub.
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Click to open Discord)'>Open a ticket on the Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Click to open GitHub)'>or open a new issue on GitHub.
|
||||
- ''
|
||||
# Formatting of the list of subcommands shown when running the main command
|
||||
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>Only
|
||||
players: </gray>%only_players%<br><gray>Require console: </gray>%require_console%<br><gray>Permission:
|
||||
</gray><white>%permission%<br><gray>Aliases: </gray>%aliases%''><dark_aqua>
|
||||
▪ <gray>/sonar %subcommand% <white>%description%'
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Only players: </gray><only-players><br><gray>Require console: </gray><only-console><br><gray>Permission: </gray><white><permission><br><gray>Aliases: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# Formatting for yes (tick) and no (cross) values in the subcommands hover text
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# Translations for '/sonar reload'
|
||||
reload:
|
||||
# Description for this subcommand
|
||||
description: "Reload all configurations"
|
||||
# Message shown when someone starts reloading Sonar
|
||||
start: '%prefix%Reloading Sonar...'
|
||||
start: '<prefix>Reloading Sonar...'
|
||||
# Message shown when Sonar has finished reloading
|
||||
finish: '%prefix%<green>Successfully reloaded <gray>(%taken%ms)'
|
||||
finish: '<prefix><green>Successfully reloaded <gray>(<time-taken>ms)'
|
||||
|
||||
# Translations for '/sonar verbose'
|
||||
verbose:
|
||||
# Description for this subcommand
|
||||
description: "Enable or disable Sonar verbose"
|
||||
# Message shown when a player subscribes to Sonar verbose
|
||||
subscribed: '%prefix%You are now viewing Sonar verbose.'
|
||||
subscribe: '<prefix>You are now viewing Sonar verbose.'
|
||||
# Message shown when a player unsubscribes from Sonar verbose
|
||||
unsubscribed: '%prefix%You are no longer viewing Sonar verbose.'
|
||||
unsubscribe: '<prefix>You are no longer viewing Sonar verbose.'
|
||||
|
||||
# Translations for '/sonar notify'
|
||||
notify:
|
||||
# Description for this subcommand
|
||||
description: "Enable or disable attack notifications"
|
||||
# Message shown when a player subscribes to Sonar attack notifications
|
||||
subscribed: '%prefix%You are now viewing Sonar attack notifications.'
|
||||
subscribe: '<prefix>You are now viewing Sonar attack notifications.'
|
||||
# Message shown when a player unsubscribes from Sonar attack notifications
|
||||
unsubscribed: '%prefix%You are no longer viewing Sonar attack notifications.'
|
||||
unsubscribe: '<prefix>You are no longer viewing Sonar attack notifications.'
|
||||
|
||||
# Translations for '/sonar blacklist'
|
||||
blacklist:
|
||||
# Description for this subcommand
|
||||
description: "Manage blacklisted IP addresses"
|
||||
# Message shown when someone tries clearing the blacklist but is empty
|
||||
empty: '%prefix%The blacklist is currently empty. Therefore, no IP addresses were
|
||||
removed from the blacklist.'
|
||||
empty: '<prefix>The blacklist is currently empty. Therefore, no IP addresses were removed from the blacklist.'
|
||||
# Message shown when someone clears the blacklist
|
||||
cleared: '%prefix%You successfully removed a total of %removed% IP address(es)
|
||||
from the blacklist.'
|
||||
clear: '<prefix>You successfully removed a total of <amount> IP address(es) from the blacklist.'
|
||||
# Message shown when someone checks the size of the blacklist
|
||||
size: '%prefix%The blacklist currently contains %amount% IP address(es).'
|
||||
# Message shown when someone adds an IP address to the blacklist
|
||||
added: '%prefix%Successfully added %ip% to the blacklist.'
|
||||
# Message shown when someone adds an IP address to the blacklist that is verified
|
||||
added-warning: '%prefix%<red>Warning: <white>%ip% is currently whitelisted. Consider
|
||||
removing the IP address from the list of verified players to avoid potential
|
||||
issues.'
|
||||
size: '<prefix>The blacklist currently contains <amount> IP address(es).'
|
||||
# Message shown when someone removes an IP address from the blacklist
|
||||
removed: '%prefix%Successfully removed %ip% from the blacklist.'
|
||||
remove: '<prefix>Successfully removed <ip> from the blacklist.'
|
||||
# Message shown when someone adds an IP address to the blacklist
|
||||
add: '<prefix>Successfully added <ip> to the blacklist.'
|
||||
# Message shown when someone adds an IP address to the blacklist that is verified
|
||||
add-warning: '<prefix><red>Warning: <white><ip> is currently whitelisted. Consider removing the IP address from the list of verified players to avoid potential issues.'
|
||||
# Message shown when someone adds an IP address to the blacklist, but it is already blacklisted
|
||||
duplicate-ip: '%prefix%The IP address you provided is already blacklisted.'
|
||||
ip-duplicate: '<prefix>The IP address you provided is already blacklisted.'
|
||||
# Message shown when someone removes an IP address from the blacklist, but it is not blacklisted
|
||||
ip-not-found: '%prefix%The IP address you provided is not blacklisted.'
|
||||
ip-not-found: '<prefix>The IP address you provided is not blacklisted.'
|
||||
|
||||
# Translations for '/sonar verified'
|
||||
verified:
|
||||
# Description for this subcommand
|
||||
description: "Manage verified IP addresses"
|
||||
# Message shown when someone tries clearing the list of verified players but is empty
|
||||
empty: '%prefix%The list of verified players is currently empty. Therefore, no
|
||||
players were unverified.'
|
||||
empty: '<prefix>The list of verified players is currently empty. Therefore, no players were unverified.'
|
||||
# Message shown when someone clears the list of verified players
|
||||
cleared: '%prefix%You successfully unverified a total of %removed% unique player(s).'
|
||||
clear: '<prefix>You successfully unverified a total of <amount> unique player(s).'
|
||||
# Message shown when someone checks the size of the list of verified players
|
||||
size: '%prefix%There are currently %amount% unique player(s) verified.'
|
||||
size: '<prefix>There are currently <amount> unique player(s) verified.'
|
||||
# Message shown when someone un-verifies an IP address
|
||||
removed: '%prefix%Successfully unverified %ip%.'
|
||||
remove: '<prefix>Successfully unverified <ip>.'
|
||||
# Message shown when someone verifies an IP address
|
||||
added: '%prefix%Successfully verified %ip%.'
|
||||
add: '<prefix>Successfully verified <ip>.'
|
||||
# Message shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: '%prefix%The IP address and UUID you provided are already verified.'
|
||||
already: '<prefix>The IP address and UUID you provided are already verified.'
|
||||
# Message shown when someone un-verifies an IP address, but it is not verified
|
||||
ip-not-found: '%prefix%The IP address you provided is not verified.'
|
||||
# Message shown when someone tries un-verifying the same IP address twice (double operation)
|
||||
blocked: '%prefix%Please wait for the current operation to finish.'
|
||||
ip-not-found: '<prefix>The IP address you provided is not verified.'
|
||||
# Message shown when someone looks up the verification history of an IP address
|
||||
history: '<prefix><yellow>Previous UUIDs for <ip>:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# Translations for '/sonar statistics'
|
||||
statistics:
|
||||
# Description for this subcommand
|
||||
description: "Show session statistics of this server"
|
||||
# Informational message that is shown above everything when viewing the statistics
|
||||
header: '%prefix%Showing %type% statistics for this session:'
|
||||
header: '<prefix>Showing <statistics-type> statistics for this session:'
|
||||
# Message shown when a player tries viewing an unknown statistic
|
||||
unknown-type: '%prefix%<red>Unknown statistics type! Available statistics: <gray>%statistics%'
|
||||
unknown-type: '<prefix><red>Unknown statistics type! Available statistics: <gray><statistics>'
|
||||
# Format of the general statistics message
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>Verified IP addresses: <white>%verified%'
|
||||
- ' <dark_aqua>▪ <gray>Verifying IP addresses: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Blacklisted IP addresses: <white>%blacklisted%'
|
||||
- ' <dark_aqua>▪ <gray>Currently queued logins: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Total non-unique joins: <white>%total_joins%'
|
||||
- ' <dark_aqua>▪ <gray>Total verification attempts: <white>%total_attempts%'
|
||||
- ' <dark_aqua>▪ <gray>Total failed verifications: <white>%total_failed%'
|
||||
- ' <dark_aqua>▪ <gray>Server uptime: <white>%uptime%'
|
||||
- ' <dark_aqua>▪ <gray>Verified IP addresses: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>Verifying IP addresses: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Blacklisted IP addresses: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>Currently queued logins: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>Total non-unique joins: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>Total verification attempts: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>Total failed verifications: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>Server uptime: <white><server-uptime>'
|
||||
# Format of the CPU statistics message
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>Process CPU usage right now: <white>%process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>System CPU usage right now: <white>%system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Per-core process CPU usage: <white>%average_process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Per-core system CPU usage: <white>%average_system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>General system load average: <white>%load_average%%'
|
||||
- ' <dark_aqua>▪ <gray>Total amount of virtual cpus: <white>%virtual_cores%'
|
||||
- ' <dark_aqua>▪ <gray>Process CPU usage right now: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>System CPU usage right now: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Per-core process CPU usage: <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Per-core system CPU usage: <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Total amount of virtual cpus: <white><virtual-core-count>'
|
||||
# Format of the memory statistics message
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>Total free memory: <white>%free_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Total used memory: <white>%used_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Total maximum memory: <white>%max_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Total allocated memory: <white>%total_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Total free memory: <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Total used memory: <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Total maximum memory: <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Total allocated memory: <white><total-memory>'
|
||||
# Format of the network statistics message
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>Current incoming used bandwidth: <white>%incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Current outgoing used bandwidth: <white>%outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Total incoming used bandwidth: <white>%ttl_incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Total outgoing used bandwidth: <white>%ttl_outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Current incoming used bandwidth: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Current outgoing used bandwidth: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Total incoming used bandwidth: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>Total outgoing used bandwidth: <white><incoming-traffic-ttl>'
|
||||
|
||||
# Translations for '/sonar dump'
|
||||
dump:
|
||||
# Description for this subcommand
|
||||
description: "Print developer information"
|
||||
# Format of the information sent to console
|
||||
log: "Generated dump: <dumped-json-data>"
|
||||
|
||||
# Translations for all messages during the verification process
|
||||
verification:
|
||||
# Translations for all debug messages during the verification
|
||||
logs:
|
||||
# Message logged to console whenever a new player joins the server
|
||||
connection: '%name%%ip% (%protocol%) has connected.'
|
||||
connection: '<username><ip> (<protocol>) has connected.'
|
||||
# Message logged to console whenever a player fails verification
|
||||
failed: '%ip% (%protocol%) has failed the bot check for: %reason%'
|
||||
failed: '<ip> (<protocol>) has failed the bot check for: <reason>'
|
||||
# Message logged to console whenever a player is blacklisted
|
||||
blacklisted: '%ip% (%protocol%) was blacklisted for too many failed attempts'
|
||||
blacklisted: '<ip> (<protocol>) was blacklisted for too many failed attempts'
|
||||
# Message logged to console whenever a player is verified
|
||||
successful: '%name% has been verified successfully (%time%s!).'
|
||||
successful: '<username> has been verified successfully (<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>Please enter the code in chat that is displayed on
|
||||
the map.'
|
||||
enter-code: '<prefix><green>Please enter the code 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 the wrong code. 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%'
|
||||
- <gold>False positive? <gray><support-url>
|
||||
- '<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%'
|
||||
- <gold>Need help logging in? <gray><support-url>
|
||||
- '<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? <gray>%support-url%
|
||||
- '%footer%'
|
||||
- <gold>Need help logging in? <gray><support-url>
|
||||
- '<footer>'
|
||||
|
||||
# Translations for Sonar's actionbar verbose messages
|
||||
verbose:
|
||||
layout:
|
||||
# Layout for Sonar's actionbar verbose
|
||||
# Placeholders:
|
||||
# - '%queued%' Number of queued connections
|
||||
# - '%verifying%' Number of verifying connections
|
||||
# - '%blacklisted%' Number of blacklisted IP addresses
|
||||
# - '%total-joins%' Number of total attempted joins
|
||||
# - '%verify-total%' Number of total verification attempts
|
||||
# - '%verify-success%' Number of verified IP addresses
|
||||
# - '%verify-failed%' Number of failed verifications
|
||||
# - '%logins-per-second%' Number of logins per second
|
||||
# - '%connections-per-second%' Number of connections per second
|
||||
# - '%attack-duration%' Duration of the current attack
|
||||
# - '%incoming-traffic%' Incoming bandwidth usage per second
|
||||
# - '%outgoing-traffic%' Outgoing bandwidth usage per second
|
||||
# - '%incoming-traffic-ttl%' Total incoming bandwidth usage
|
||||
# - '%outgoing-traffic-ttl%' Total outgoing bandwidth usage
|
||||
# - '%used-memory%' Amount of used memory of the process
|
||||
# - '%total-memory%' Amount of total memory of the process
|
||||
# - '%max-memory%' Amount of max memory of the process
|
||||
# - '%free-memory%' Amount of free memory of the process
|
||||
# - '%animation%' Customizable animated symbol
|
||||
# Translations for Sonar's normal verbose output
|
||||
normal: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Logins/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Verifying <white>%verifying% <dark_aqua>|
|
||||
<green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
normal: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s <white><logins-per-second> <dark_aqua>╺ <gray>Verifying <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Layout for Sonar's actionbar verbose during an attack
|
||||
attack: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Logins/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Queue <white>%queued% <dark_aqua>╺
|
||||
<gray>Verifying <white>%verifying% <dark_aqua>╺ <gray>Duration <white>%attack-duration%
|
||||
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
attack: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s <white><logins-per-second> <dark_aqua>╺ <gray>Queue <white><queued> <dark_aqua>╺ <gray>Verifying <white><verifying> <dark_aqua>╺ <gray>Duration <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Animation for the action bar
|
||||
# Alternatives:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
@@ -301,11 +280,9 @@ notifications:
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>A bot attack has been detected.
|
||||
- ' <dark_aqua>▪ <gray>Logins per second: <white>%logins-per-second%'
|
||||
- ' <dark_aqua>▪ <gray>Verifying players: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Queued players: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Logins per second: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>Verifying players: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Queued players: <white><queued>'
|
||||
- ''
|
||||
- <gray>View more information using <white><click:run_command:'/sonar verbose'><hover:show_text:'(Click
|
||||
to run)'>/sonar verbose</hover></click><gray> or <white><click:run_command:'/sonar
|
||||
stats'><hover:show_text:'(Click to run)'>/sonar stats</hover></click><gray>.
|
||||
- <gray>View more information using <white><click:run_command:'/sonar verbose'><hover:show_text:'(Click to run)'>/sonar verbose</hover></click><gray> or <white><click:run_command:'/sonar stats'><hover:show_text:'(Click to run)'>/sonar stats</hover></click><gray>.
|
||||
- ''
|
||||
@@ -0,0 +1,288 @@
|
||||
#
|
||||
# /$$$$$$
|
||||
# /$$__ $$
|
||||
# | $$ \__/ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
|
||||
# | $$$$$$ /$$__ $$| $$__ $$ |____ $$ /$$__ $$
|
||||
# \____ $$| $$ \ $$| $$ \ $$ /$$$$$$$| $$ \__/
|
||||
# /$$ \ $$| $$ | $$| $$ | $$ /$$__ $$| $$
|
||||
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
|
||||
# \______/ \______/ |__/ |__/ \_______/|__/
|
||||
#
|
||||
# ¿Necesita ayuda o tiene alguna pregunta? https://jonesdev.xyz/discord
|
||||
# Código fuente: https://github.com/jonesdevelopment/sonar
|
||||
# Copyright Jones Development and Sonar Contributors, bajo licencia GPL-3.0
|
||||
#
|
||||
|
||||
# Placeholder for every '<prefix>' in this configuration file
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Placeholder for every '<support-url>' in this configuration file
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# Placeholder for every '<header>' in this configuration file
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# Placeholder for every '<footer>' in this configuration file
|
||||
footer:
|
||||
- <gray>Si cree que se trata de un error, por favor comuníquese con un administrador.
|
||||
# Disconnect message that is shown when someone joins, but there are too many online players with their IP address
|
||||
too-many-online-per-ip:
|
||||
- <header>
|
||||
- <red>Hay demasiados jugadores en línea con tu dirección IP.
|
||||
- <footer>
|
||||
|
||||
# Translations for commands
|
||||
commands:
|
||||
# Message shown when a player tries running /sonar without permission
|
||||
no-permission: <prefix><red>No tienes permiso para ejecutar ese comando.
|
||||
# Message shown when someone uses a command incorrectly
|
||||
incorrect-usage: '<prefix><red>Uso correcto: <subcommand-usage>'
|
||||
# Message shown when someone provides an invalid IP address (Invalid format)
|
||||
invalid-ip-address: <prefix>La dirección IP que proporcionó parece no ser válida.
|
||||
# Message shown when the console runs a command that is player-only
|
||||
player-only: <prefix><red>Solo puedes ejecutar este comando como jugador.
|
||||
# Message shown when a player runs a command that is console-only
|
||||
console-only: <prefix><red>Por razones de seguridad, solo puedes ejecutar este comando a través de consola.
|
||||
# Message shown when a player does not have permission to execute a certain subcommand
|
||||
subcommand-no-permission: <prefix><red>No tienes permiso para ejecutar ese subcomando. <gray>(<permission>)
|
||||
|
||||
# Translations for '/sonar'
|
||||
main:
|
||||
# Informational message that is shown above everything when running the main command
|
||||
header:
|
||||
- <yellow>Ejecutando <b>Sonar</b> <version> en <platform>.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year>Jones Development and Sonar Contributors</click>
|
||||
- ''
|
||||
- <gray>¿Necesita ayuda o tiene alguna pregunta?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Haga clic para abrir Discord)'>Abrir un ticket en Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Haga click para abrir GitHub)'>o abra una nueva Issue en GitHub.
|
||||
- ''
|
||||
# Formatting of the list of subcommands shown when running the main command
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Solo jugadores: </gray><only-players><br><gray>Requiere consola: </gray><only-console><br><gray>Permiso: </gray><white><permission><br><gray>Alias: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# Formato para los valores sí (tick) y no (cross) en el texto hover de los subcomandos
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# Translations for '/sonar reload'
|
||||
reload:
|
||||
# Descripción para este subcomando
|
||||
description: "Recargar todas las configuraciones"
|
||||
# Message shown when someone starts reloading Sonar
|
||||
start: <prefix>Recargando Sonar...
|
||||
# Message shown when Sonar has finished reloading
|
||||
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).
|
||||
# Message shown when a player unsubscribes from Sonar 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.
|
||||
# Message shown when a player unsubscribes from Sonar attack notifications
|
||||
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.
|
||||
# Message shown when someone clears the blacklist
|
||||
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.
|
||||
# Message shown when someone removes an IP address from the blacklist
|
||||
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.
|
||||
# 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.
|
||||
# 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.
|
||||
|
||||
# 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.
|
||||
# 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).
|
||||
# Message shown when someone checks the size of the list of verified players
|
||||
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>.
|
||||
# Message shown when someone verifies an IP address
|
||||
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.
|
||||
# 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.
|
||||
# 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>'
|
||||
|
||||
# Translations for '/sonar statistics'
|
||||
statistics:
|
||||
# Descripción para este subcomando
|
||||
description: "Mostrar estadísticas de sesión de este servidor"
|
||||
# Informational message that is shown above everything when viewing the statistics
|
||||
header: '<prefix>Mostrando estadísticas de <statistics-type> para esta sesión:'
|
||||
# Message shown when a player tries viewing an unknown statistic
|
||||
unknown-type: '<prefix><red>¡Tipo de estadísticas desconocido! Estadísticas disponibles: <gray><statistics>'
|
||||
# Format of the general statistics message
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>Direcciones IP verificadas: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>Verificación de direcciones IP: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Direcciones IP en lista negra: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>Inicios de sesión actualmente en cola: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>Total de uniones no únicas: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>Total de intentos de verificación: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>Total de verificaciones fallidas: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>Tiempo de actividad del servidor: <white><server-uptime>'
|
||||
# Format of the CPU statistics message
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>Uso de la CPU del proceso ahora mismo: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Uso de la CPU del sistema en este momento: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Uso de CPU de proceso por núcleo: <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Uso de CPU del sistema por núcleo: <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Cantidad total de CPU virtuales: <white><virtual-core-count>'
|
||||
# Format of the memory statistics message
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>Memoria libre total: <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Memoria total utilizada: <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Memoria máxima total: <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Memoria total asignada: <white><total-memory>'
|
||||
# Format of the network statistics message
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>Ancho de banda entrante utilizado actualmente: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Ancho de banda utilizado saliente actual: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Ancho de banda entrante total utilizado: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>Ancho de banda total utilizado saliente: <white><incoming-traffic-ttl>'
|
||||
|
||||
# Translations for '/sonar dump'
|
||||
dump:
|
||||
# Descripción para este subcomando
|
||||
description: "Imprimir información del promotor"
|
||||
# Formato de la información enviada a la consola
|
||||
log: "Log generado: <dumped-json-data>"
|
||||
|
||||
# Translations for all messages during the verification process
|
||||
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.
|
||||
# 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.
|
||||
# Message logged to console whenever a player is verified
|
||||
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.
|
||||
# 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.
|
||||
|
||||
# Disconnect message that is shown when someone joins while the captcha hasn't been prepared yet
|
||||
currently-preparing:
|
||||
- <header>
|
||||
- <yellow>Tus datos anti-bot aún no se han preparado.
|
||||
- <gray>Espere unos segundos antes de intentar realizar la verificación nuevamente.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone rejoins too fast during verification
|
||||
too-fast-reconnect:
|
||||
- <header>
|
||||
- <gold>Te reconectaste demasiado rápido, inténtalo de nuevo en un momento.
|
||||
- <gray>Espere unos segundos antes de intentar realizar la verificación nuevamente.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins but is already verifying
|
||||
already-verifying:
|
||||
- <header>
|
||||
- <red>Su dirección IP se está verificando.
|
||||
- <red>Espere unos segundos antes de intentar realizar la verificación nuevamente.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins but is already queued for verification
|
||||
already-queued:
|
||||
- <header>
|
||||
- <red>Su dirección IP está actualmente en cola para verificación.
|
||||
- <red>Espere unos minutos antes de intentar realizar la verificación nuevamente.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins but is temporarily blacklisted
|
||||
blacklisted:
|
||||
- <header>
|
||||
- <red>Actualmente no puedes ingresar al servidor.
|
||||
- <red>Espere unos minutos para poder unirse nuevamente al servidor.
|
||||
- <gold>¿Falso positivo? <gray><support-url>
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins with an invalid username
|
||||
invalid-username:
|
||||
- <header>
|
||||
- <red>Tu nombre de usuario contiene caracteres inválidos.
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone joins with a blacklisted version
|
||||
blacklisted-protocol:
|
||||
- <header>
|
||||
- <red>Estás utilizando una versión que no está permitida en nuestro servidor.
|
||||
- <gold>¿Necesitas ayuda para iniciar sesión? <gray><support-url>
|
||||
- <footer>
|
||||
# Disconnect message that is shown when someone verifies successfully
|
||||
success:
|
||||
- <header>
|
||||
- <green>Ha pasado con éxito la verificación.
|
||||
- <white>Ahora podrás jugar en el servidor cuando te vuelvas a conectar.
|
||||
# Disconnect message that is shown when someone fails verification
|
||||
failed:
|
||||
- <header>
|
||||
- <red>No pasaste la verificación del bot.
|
||||
- <gray>Espere unos segundos antes de intentar realizar la verificación nuevamente.
|
||||
- <gold>¿Necesitas ayuda para iniciar sesión? <gray><support-url>
|
||||
- <footer>
|
||||
|
||||
# Translations for Sonar's actionbar verbose messages
|
||||
verbose:
|
||||
layout:
|
||||
# Layout for Sonar's actionbar verbose
|
||||
normal: <prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Inicios de sesión/s <white><logins-per-second> <dark_aqua>╺ <gray>Verificando <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>
|
||||
# Layout for Sonar's actionbar verbose during an attack
|
||||
attack: <prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s <white><logins-per-second> <dark_aqua>╺ <gray>Cola <white><queued> <dark_aqua>╺ <gray>Verificando <white><verifying> <dark_aqua>╺ <gray>Duración <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>
|
||||
# Animation for the action bar
|
||||
# Alternatives:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
# - ⬈, ⬊, ⬋, ⬉
|
||||
animation:
|
||||
- ◜
|
||||
- ◝
|
||||
- ◞
|
||||
- ◟
|
||||
|
||||
# Translations for Sonar's attack notification messages
|
||||
notifications:
|
||||
# Layout for Sonar's attack notification title
|
||||
title: <yellow><b>Sonar<reset>
|
||||
# Layout for Sonar's attack notification subtitle
|
||||
subtitle: <gray>Se ha detectado un ataque de bots
|
||||
# Layout for Sonar's attack notification in chat
|
||||
chat:
|
||||
- ''
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>Se ha detectado un ataque de bots.
|
||||
- ' <dark_aqua>▪ <gray>Inicios de sesión por segundo: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>Verificando jugadores: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Jugadores en cola: <white><queued>'
|
||||
- ''
|
||||
- <gray>Ver más información usando <white><click:run_command:'/sonar verbose'><hover:show_text:'(Haga clic para ejecutar)'>/sonar verbose</hover></click><gray> or <white><click:run_command:'/sonar stats'><hover:show_text:'(Haga clic para ejecutar)'>/sonar stats</hover></click><gray>.
|
||||
- ''
|
||||
@@ -13,261 +13,251 @@
|
||||
# Droit d'auteur Jones Development, un contributeur Sonar, licence sous GPL-3.0
|
||||
#
|
||||
|
||||
# Espace réservé pour tout '%prefix%' dans ce fichier de configuration
|
||||
# Espace réservé pour tout '<prefix>' dans ce fichier de configuration
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Espace réservé pour tout '%support-url%' dans ce fichier de configuration
|
||||
# Espace réservé pour tout '<support-url>' dans ce fichier de configuration
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# Espace réservé pour tout '%header%' dans ce fichier de configuration
|
||||
# Espace réservé pour tout '<header>' dans ce fichier de configuration
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# Espace réservé pour tout '%footer%' dans ce fichier de configuration
|
||||
# Espace réservé pour tout '<footer>' dans ce fichier de configuration
|
||||
footer:
|
||||
- <gray>Si vous pensez que c'est une erreur, veuillez contacter un administrateur.
|
||||
# Message de déconnexion affiché lorsqu'un joueur se connecte mais qu'il y a trop de joueurs en ligne avec leur adresse IP
|
||||
too-many-online-per-ip:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>Il y a trop de joueurs en ligne avec votre adresse IP.
|
||||
- '%footer%'
|
||||
- '<footer>'
|
||||
|
||||
# Traductions pour les commandes
|
||||
commands:
|
||||
# Message affiché lorsqu'un joueur tente d'exécuter /sonar sans permission
|
||||
no-permission: "%prefix%<red>Vous n'avez pas la permission d'exécuter cette commande."
|
||||
no-permission: "<prefix><red>Vous n'avez pas la permission d'exécuter cette commande."
|
||||
# Message affiché lorsqu'une commande est utilisée de manière incorrecte
|
||||
incorrect-usage: '%prefix%<red>Utilisation : /sonar %usage%'
|
||||
incorrect-usage: '<prefix><red>Utilisation : /sonar <subcommand-usage>'
|
||||
# Message affiché lorsqu'une adresse IP invalide est fournie (format invalide)
|
||||
invalid-ip-address: "%prefix%L'adresse IP que vous avez fournie semble invalide."
|
||||
invalid-ip-address: "<prefix>L'adresse IP que vous avez fournie semble invalide."
|
||||
# Message affiché lorsque la console exécute une commande qui est uniquement pour les joueurs
|
||||
player-only: "%prefix%<red>Vous ne pouvez exécuter cette commande qu'en tant que joueur."
|
||||
player-only: "<prefix><red>Vous ne pouvez exécuter cette commande qu'en tant que joueur."
|
||||
# Message affiché lorsqu'un joueur exécute une commande qui est uniquement pour la console
|
||||
console-only: '%prefix%<red>Pour des raisons de sécurité, vous ne pouvez exécuter cette commande que via la console.'
|
||||
# Message affiché lorsqu'un joueur exécute trop rapidement les commandes Sonar
|
||||
cool-down: "%prefix%<red>Vous ne pouvez exécuter cette commande qu'une fois toutes les 0,5 secondes."
|
||||
cool-down-left: '%prefix%<red>Veuillez attendre encore <bold>%time-left%<reset><red>.'
|
||||
console-only: '<prefix><red>Pour des raisons de sécurité, vous ne pouvez exécuter cette commande que via la console.'
|
||||
# Message affiché lorsqu'un joueur n'a pas la permission d'exécuter une certaine sous-commande
|
||||
subcommand-no-permission: "%prefix%<red>Vous n'avez pas la permission d'exécuter cette sous-commande. <gray>(%permission%)"
|
||||
subcommand-no-permission: "<prefix><red>Vous n'avez pas la permission d'exécuter cette sous-commande. <gray>(<permission>)"
|
||||
|
||||
# Traductions pour '/sonar'
|
||||
main:
|
||||
# Message informatif affiché au-dessus de tout lors de l'exécution de la commande principale
|
||||
header:
|
||||
- <yellow>Exécution de <b>Sonar</b> %version% sur %platform%.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
|
||||
Jones Development et les contributeurs de Sonar</click>
|
||||
- <yellow>Exécution de <b>Sonar</b> <version> sur <platform>.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development et les contributeurs de Sonar</click>
|
||||
- ''
|
||||
- <gray>Besoin d'aide ou avez-vous des questions ?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Cliquez
|
||||
pour ouvrir Discord)'>Ouvrez un ticket sur Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Cliquez
|
||||
pour ouvrir GitHub)'>ou ouvrez un nouveau problème sur GitHub.
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Cliquez pour ouvrir Discord)'>Ouvrez un ticket sur Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Cliquez pour ouvrir GitHub)'>ou ouvrez un nouveau problème sur GitHub.
|
||||
- ''
|
||||
# Format de la liste des sous-commandes affichée lors de l'exécution de la commande principale
|
||||
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>Seulement pour les joueurs : </gray>%only_players%<br><gray>Nécessite la console : </gray>%require_console%<br><gray>Permission : </gray><white>%permission%<br><gray>Alias : </gray>%aliases%''><dark_aqua> ▪ <gray>/sonar %subcommand% <white>%description%'
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Seulement pour les joueurs : </gray><only-players><br><gray>Nécessite la console : </gray><only-console><br><gray>Permission : </gray><white><permission><br><gray>Alias : </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# Formatage des valeurs oui (tick) et non (cross) dans le texte de survol des sous-commandes
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# Traductions pour '/sonar reload'
|
||||
reload:
|
||||
# Description de cette sous-commande
|
||||
description: "Recharger toutes les configurations"
|
||||
# Message affiché lorsque quelqu'un commence à recharger Sonar
|
||||
start: '%prefix%Rechargement de Sonar...'
|
||||
start: '<prefix>Rechargement de Sonar...'
|
||||
# Message affiché lorsque Sonar a fini de recharger
|
||||
finish: '%prefix%<green>Rechargement réussi <gray>(%taken%ms)'
|
||||
finish: '<prefix><green>Rechargement réussi <gray>(<time-taken>ms)'
|
||||
|
||||
# Traductions pour '/sonar verbose'
|
||||
verbose:
|
||||
# Description de cette sous-commande
|
||||
description: "Activer ou désactiver la détails verbeux"
|
||||
# Message affiché lorsqu'un joueur s'abonne aux détails verbeux de Sonar
|
||||
subscribed: '%prefix%Vous visualisez maintenant les détails verbeux de Sonar.'
|
||||
subscribe: '<prefix>Vous visualisez maintenant les détails verbeux de Sonar.'
|
||||
# Message affiché lorsqu'un joueur se désabonne des détails verbeux de Sonar
|
||||
unsubscribed: '%prefix%Vous ne visualisez plus les détails verbeux de Sonar.'
|
||||
unsubscribe: '<prefix>Vous ne visualisez plus les détails verbeux de Sonar.'
|
||||
|
||||
# Traductions pour '/sonar notify'
|
||||
notify:
|
||||
# Description de cette sous-commande
|
||||
description: "Activer ou désactiver les notifications d'attaque"
|
||||
# Message affiché lorsqu'un joueur s'abonne aux notifications d'attaque de Sonar
|
||||
subscribed: "%prefix%Vous visualisez maintenant les notifications d'attaque de Sonar."
|
||||
subscribed: "<prefix>Vous visualisez maintenant les notifications d'attaque de Sonar."
|
||||
# Message affiché lorsqu'un joueur se désabonne des notifications d'attaque de Sonar
|
||||
unsubscribed: "%prefix%Vous ne visualisez plus les notifications d'attaque de Sonar."
|
||||
unsubscribed: "<prefix>Vous ne visualisez plus les notifications d'attaque de Sonar."
|
||||
|
||||
# Traductions pour '/sonar blacklist'
|
||||
blacklist:
|
||||
# Description de cette sous-commande
|
||||
description: "Gérer les adresses IP sur liste noire"
|
||||
# Message affiché lorsque quelqu'un tente de vider la liste noire mais qu'elle est vide
|
||||
empty: "%prefix%La liste noire est actuellement vide. Par conséquent, aucune adresse IP n'a été supprimée de la liste noire."
|
||||
empty: "<prefix>La liste noire est actuellement vide. Par conséquent, aucune adresse IP n'a été supprimée de la liste noire."
|
||||
# Message affiché lorsque quelqu'un vide la liste noire
|
||||
cleared: '%prefix%Vous avez réussi à supprimer un total de %removed% adresse(s) IP de la liste noire.'
|
||||
clear: '<prefix>Vous avez réussi à supprimer un total de <amount> adresse(s) IP de la liste noire.'
|
||||
# Message affiché lorsque quelqu'un vérifie la taille de la liste noire
|
||||
size: '%prefix%La liste noire contient actuellement %amount% adresse(s) IP.'
|
||||
size: '<prefix>La liste noire contient actuellement <amount> adresse(s) IP.'
|
||||
# Message affiché lorsque quelqu'un ajoute une adresse IP à la liste noire
|
||||
added: '%prefix%Adresse IP %ip% ajoutée avec succès à la liste noire.'
|
||||
add: '<prefix>Adresse IP <ip> ajoutée avec succès à la liste noire.'
|
||||
# Message affiché lorsque quelqu'un ajoute une adresse IP à la liste noire qui est vérifiée
|
||||
added-warning: "%prefix%<red>Avertissement : <white>%ip% est actuellement sur liste blanche. Considérez la suppression de l'adresse IP de la liste des joueurs vérifiés pour éviter des problèmes potentiels."
|
||||
add-warning: "<prefix><red>Avertissement : <white><ip> est actuellement sur liste blanche. Considérez la suppression de l'adresse IP de la liste des joueurs vérifiés pour éviter des problèmes potentiels."
|
||||
# Message affiché lorsque quelqu'un supprime une adresse IP de la liste noire
|
||||
removed: '%prefix%Adresse IP %ip% supprimée avec succès de la liste noire.'
|
||||
remove: '<prefix>Adresse IP <ip> supprimée avec succès de la liste noire.'
|
||||
# Message affiché lorsque quelqu'un ajoute une adresse IP à la liste noire, mais qu'elle est déjà en liste noire
|
||||
duplicate-ip: "%prefix%L'adresse IP que vous avez fournie est déjà sur la liste noire."
|
||||
duplicate-ip: "<prefix>L'adresse IP que vous avez fournie est déjà sur la liste noire."
|
||||
# Message affiché lorsque quelqu'un supprime une adresse IP de la liste noire, mais qu'elle n'est pas en liste noire
|
||||
ip-not-found: "%prefix%L'adresse IP que vous avez fournie ne figure pas sur la liste noire."
|
||||
ip-not-found: "<prefix>L'adresse IP que vous avez fournie ne figure pas sur la liste noire."
|
||||
|
||||
# Traductions pour '/sonar verified'
|
||||
verified:
|
||||
# Description de cette sous-commande
|
||||
description: "Gérer les adresses IP vérifiées"
|
||||
# Message affiché lorsque quelqu'un tente de vider la liste des joueurs vérifiés mais qu'elle est vide
|
||||
empty: "%prefix%La liste des joueurs vérifiés est actuellement vide. Par conséquent, aucun joueur n'a été désapprouvé."
|
||||
empty: "<prefix>La liste des joueurs vérifiés est actuellement vide. Par conséquent, aucun joueur n'a été désapprouvé."
|
||||
# Message affiché lorsque quelqu'un vide la liste des joueurs vérifiés
|
||||
cleared: '%prefix%Vous avez réussi à désapprouver un total de %removed% joueur(s) unique(s).'
|
||||
clear: '<prefix>Vous avez réussi à désapprouver un total de <amount> joueur(s) unique(s).'
|
||||
# Message affiché lorsque quelqu'un vérifie la taille de la liste des joueurs vérifiés
|
||||
size: '%prefix%Il y a actuellement %amount% joueur(s) unique(s) vérifié(s).'
|
||||
size: '<prefix>Il y a actuellement <amount> joueur(s) unique(s) vérifié(s).'
|
||||
# Message affiché lorsque quelqu'un désapprouve une adresse IP
|
||||
removed: '%prefix%Adresse IP %ip% désapprouvée avec succès.'
|
||||
remove: '<prefix>Adresse IP <ip> désapprouvée avec succès.'
|
||||
# Message affiché lorsque quelqu'un vérifie une adresse IP
|
||||
added: '%prefix%Adresse IP %ip% vérifiée avec succès.'
|
||||
add: '<prefix>Adresse IP <ip> vérifiée avec succès.'
|
||||
# Message affiché lorsque quelqu'un tente de vérifier une adresse IP/UUID qui est déjà vérifiée
|
||||
already: "%prefix%L'adresse IP et l'UUID que vous avez fournis sont déjà vérifiés."
|
||||
already: "<prefix>L'adresse IP et l'UUID que vous avez fournis sont déjà vérifiés."
|
||||
# Message affiché lorsque quelqu'un désapprouve une adresse IP, mais qu'elle n'est pas vérifiée
|
||||
ip-not-found: "%prefix%L'adresse IP que vous avez fournie n'est pas vérifiée."
|
||||
# Message affiché lorsque quelqu'un tente de désapprouver la même adresse IP deux fois (double opération)
|
||||
blocked: "%prefix%Veuillez attendre la fin de l'opération en cours."
|
||||
ip-not-found: "<prefix>L'adresse IP que vous avez fournie n'est pas vérifiée."
|
||||
# Message shown when someone looks up the verification history of an IP address
|
||||
history: '<prefix><yellow>UUID précédents pour <ip>:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# Traductions pour '/sonar statistics'
|
||||
statistics:
|
||||
# Description de cette sous-commande
|
||||
description: "Afficher les statistiques de session de ce serveur"
|
||||
# Message informatif affiché au-dessus de tout lors de la visualisation des statistiques
|
||||
header: '%prefix%Affichage des statistiques %type% pour cette session :'
|
||||
header: '<prefix>Affichage des statistiques <statistics-type> pour cette session :'
|
||||
# Message affiché lorsqu'un joueur tente de visualiser une statistique inconnue
|
||||
unknown-type: '%prefix%<red>Type de statistiques inconnu ! Statistiques disponibles : <gray>%statistics%'
|
||||
unknown-type: '<prefix><red>Type de statistiques inconnu ! Statistiques disponibles : <gray><statistics>'
|
||||
# Format du message de statistiques général
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>Adresses IP vérifiées : <white>%verified%'
|
||||
- ' <dark_aqua>▪ <gray>En train de vérifier les adresses IP : <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Adresses IP en liste noire : <white>%blacklisted%'
|
||||
- ' <dark_aqua>▪ <gray>Connexions en attente actuelles : <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Total des connexions non uniques : <white>%total_joins%'
|
||||
- ' <dark_aqua>▪ <gray>Total des tentatives de vérification : <white>%total_attempts%'
|
||||
- ' <dark_aqua>▪ <gray>Total des vérifications échouées : <white>%total_failed%'
|
||||
- ' <dark_aqua>▪ <gray>Temps de fonctionnement du serveur : <white>%uptime%'
|
||||
- ' <dark_aqua>▪ <gray>Adresses IP vérifiées : <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>En train de vérifier les adresses IP : <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Adresses IP en liste noire : <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>Connexions en attente actuelles : <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>Total des connexions non uniques : <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>Total des tentatives de vérification : <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>Total des vérifications échouées : <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>Temps de fonctionnement du serveur : <white><server-uptime>'
|
||||
# Format du message de statistiques CPU
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>Utilisation actuelle du CPU du processus : <white>%process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Utilisation actuelle du CPU du système : <white>%system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Utilisation du CPU par cœur du processus : <white>%average_process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Utilisation du CPU par cœur du système : <white>%average_system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Charge système générale : <white>%load_average%%'
|
||||
- ' <dark_aqua>▪ <gray>Nombre total de processeurs virtuels : <white>%virtual_cores%'
|
||||
- ' <dark_aqua>▪ <gray>Utilisation actuelle du CPU du processus : <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Utilisation actuelle du CPU du système : <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Utilisation du CPU par cœur du processus : <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Utilisation du CPU par cœur du système : <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Nombre total de processeurs virtuels : <white><virtual-core-count>'
|
||||
# Format du message de statistiques de mémoire
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>Mémoire libre totale : <white>%free_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Mémoire utilisée totale : <white>%used_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Mémoire maximale totale : <white>%max_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Mémoire allouée totale : <white>%total_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Mémoire libre totale : <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Mémoire utilisée totale : <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Mémoire maximale totale : <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Mémoire allouée totale : <white><total-memory>'
|
||||
# Format du message de statistiques réseau
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>Bande passante entrante actuelle utilisée : <white>%incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Bande passante sortante actuelle utilisée : <white>%outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Bande passante entrante totale utilisée : <white>%ttl_incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Bande passante sortante totale utilisée : <white>%ttl_outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Bande passante entrante actuelle utilisée : <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Bande passante sortante actuelle utilisée : <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Bande passante entrante totale utilisée : <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>Bande passante sortante totale utilisée : <white><outgoing-traffic-ttl>'
|
||||
|
||||
# Traductions pour '/sonar dump'
|
||||
dump:
|
||||
# Description de cette sous-commande
|
||||
description: "Imprimer les informations sur le développeur"
|
||||
# Format des informations envoyées à la console
|
||||
log: "Informations générées : <dumped-json-data>"
|
||||
|
||||
# Traductions pour tous les messages pendant le processus de vérification
|
||||
verification:
|
||||
# Traductions pour tous les messages de débogage pendant la vérification
|
||||
logs:
|
||||
# Message enregistré dans la console chaque fois qu'un nouveau joueur rejoint le serveur
|
||||
connection: "%name%%ip% (%protocol%) s'est connecté."
|
||||
connection: "<username><ip> (<protocol>) s'est connecté."
|
||||
# Message enregistré dans la console chaque fois qu'un joueur échoue à la vérification
|
||||
failed: '%ip% (%protocol%) a échoué au test du bot pour : %reason%'
|
||||
failed: '<ip> (<protocol>) a échoué au test du bot pour : <reason>'
|
||||
# Message enregistré dans la console chaque fois qu'un joueur est sur liste noire
|
||||
blacklisted: '%ip% (%protocol%) a été mis sur liste noire pour trop de tentatives échouées'
|
||||
blacklisted: '<ip> (<protocol>) a été mis sur liste noire pour trop de tentatives échouées'
|
||||
# Message enregistré dans la console chaque fois qu'un joueur est vérifié
|
||||
successful: '%name% a été vérifié avec succès (%time%s !).'
|
||||
successful: '<username> a été vérifié avec succès (<time-taken>s !).'
|
||||
|
||||
# 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-code: '<prefix><green>Veuillez entrer le code affiché 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 entré le mauvais code. 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%'
|
||||
- <gold>Erreur ? <gray><support-url>
|
||||
- '<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%'
|
||||
- <gold>Besoin d'aide pour vous connecter ? <gray><support-url>
|
||||
- '<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 ? <gray>%support-url%
|
||||
- '%footer%'
|
||||
- <gold>Besoin d'aide pour vous connecter ? <gray><support-url>
|
||||
- '<footer>'
|
||||
|
||||
# Translations for Sonar's actionbar verbose messages
|
||||
verbose:
|
||||
layout:
|
||||
# Mise en page pour le verbeux de la barre d'action de Sonar
|
||||
# Espacements réservés :
|
||||
# - '%queued%' Nombre de connexions en attente
|
||||
# - '%verifying%' Nombre de connexions en cours de vérification
|
||||
# - '%blacklisted%' Nombre d'adresses IP sur liste noire
|
||||
# - '%total-joins%' Nombre total de connexions tentées
|
||||
# - '%verify-total%' Nombre total de tentatives de vérification
|
||||
# - '%verify-success%' Nombre d'adresses IP vérifiées
|
||||
# - '%verify-failed%' Nombre de vérifications échouées
|
||||
# - '%logins-per-second%' Nombre de connexions par seconde
|
||||
# - '%connections-per-second%' Nombre de connexions par seconde
|
||||
# - '%attack-duration%' Durée de l'attaque actuelle
|
||||
# - '%incoming-traffic%' Utilisation de la bande passante entrante par seconde
|
||||
# - '%outgoing-traffic%' Utilisation de la bande passante sortante par seconde
|
||||
# - '%incoming-traffic-ttl%' Utilisation totale de la bande passante entrante
|
||||
# - '%outgoing-traffic-ttl%' Utilisation totale de la bande passante sortante
|
||||
# - '%used-memory%' Quantité de mémoire utilisée par le processus
|
||||
# - '%total-memory%' Quantité de mémoire totale du processus
|
||||
# - '%max-memory%' Quantité de mémoire maximale du processus
|
||||
# - '%free-memory%' Quantité de mémoire libre du processus
|
||||
# - '%animation%' Symbole animé personnalisable
|
||||
# Traductions pour la sortie verbeuse normale de Sonar
|
||||
normal: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Logins/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Vérification <white>%verifying% <dark_aqua>|
|
||||
<green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
normal: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s <white><logins-per-second> <dark_aqua>╺ <gray>Vérification <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Traductions pour la sortie verbeuse lors d'une attaque
|
||||
attack: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Logins/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Queue <white>%queued% <dark_aqua>╺
|
||||
<gray>Vérification <white>%verifying% <dark_aqua>╺ <gray>Durée <white>%attack-duration%
|
||||
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
attack: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s <white><logins-per-second> <dark_aqua>╺ <gray>Queue <white><queued> <dark_aqua>╺ <gray>Vérification <white><verifying> <dark_aqua>╺ <gray>Durée <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Animation pour la sortie verbeuse
|
||||
# Alternatives:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
@@ -290,11 +280,9 @@ notifications:
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>Une attaque de bot a été détectée.
|
||||
- ' <dark_aqua>▪ <gray>Connexions par seconde: <white>%logins-per-second%'
|
||||
- ' <dark_aqua>▪ <gray>Vérification des joueurs: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Joueurs en file d''attente: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Connexions par seconde: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>Vérification des joueurs: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Joueurs en file d''attente: <white><queued>'
|
||||
- ''
|
||||
- <gray>View more information using <white><click:run_command:'/sonar verbose'><hover:show_text:'(Click
|
||||
to run)'>/sonar verbose</hover></click><gray> or <white><click:run_command:'/sonar
|
||||
stats'><hover:show_text:'(Click to run)'>/sonar stats</hover></click><gray>.
|
||||
- <gray>View more information using <white><click:run_command:'/sonar verbose'><hover:show_text:'(Click to run)'>/sonar verbose</hover></click><gray> or <white><click:run_command:'/sonar stats'><hover:show_text:'(Click to run)'>/sonar stats</hover></click><gray>.
|
||||
- ''
|
||||
@@ -13,272 +13,251 @@
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# Tijdelijke aanduiding voor elke '%prefix%' in dit configuratiebestand
|
||||
# Tijdelijke aanduiding voor elke '<prefix>' in dit configuratiebestand
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Tijdelijke aanduiding voor elke '%support-url%' in dit configuratiebestand
|
||||
# Tijdelijke aanduiding voor elke '<support-url>' in dit configuratiebestand
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# Tijdelijke aanduiding voor elke '%header%' in dit configuratiebestand
|
||||
# Tijdelijke aanduiding voor elke '<header>' in dit configuratiebestand
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# Tijdelijke aanduiding voor elke '%footer%' in dit configuratiebestand
|
||||
# Tijdelijke aanduiding voor elke '<footer>' in dit configuratiebestand
|
||||
footer:
|
||||
- <gray>Als u denkt dat dit een fout is, neem dan contact op met een beheerder.
|
||||
# Verbindingsbericht dat wordt weergegeven als iemand zich aansluit, maar er zijn te veel online spelers met hun IP-adres
|
||||
too-many-online-per-ip:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>Er zijn te veel spelers online met uw IP-adres.
|
||||
- '%footer%'
|
||||
- '<footer>'
|
||||
|
||||
# Vertalingen voor commandos
|
||||
commands:
|
||||
# Bericht dat wordt weergegeven wanneer een speler zonder toestemming /sonar probeert uit te voeren
|
||||
no-permission: '%prefix%<red>U heeft geen toestemming om deze opdracht uit te voeren.'
|
||||
no-permission: '<prefix><red>U heeft geen toestemming om deze opdracht uit te voeren.'
|
||||
# Bericht dat verschijnt wanneer iemand een commando verkeerd gebruikt
|
||||
incorrect-usage: '%prefix%<red>Gebruik: /sonar %usage%'
|
||||
incorrect-usage: '<prefix><red>Gebruik: /sonar <subcommand-usage>'
|
||||
# Bericht dat wordt weergegeven wanneer iemand een ongeldig IP-adres opgeeft (ongeldig formaat)
|
||||
invalid-ip-address: '%prefix%Het door u opgegeven IP-adres lijkt ongeldig te zijn.'
|
||||
invalid-ip-address: '<prefix>Het door u opgegeven IP-adres lijkt ongeldig te zijn.'
|
||||
# Bericht dat wordt weergegeven wanneer de console een opdracht uitvoert die alleen voor spelers is
|
||||
player-only: '%prefix%<red>Je kunt dit commando alleen als speler uitvoeren.'
|
||||
player-only: '<prefix><red>Je kunt dit commando alleen als speler uitvoeren.'
|
||||
# Bericht dat wordt weergegeven wanneer een speler een opdracht uitvoert die alleen voor de console geldt
|
||||
console-only: '%prefix%<red>Om veiligheidsredenen kunt u deze opdracht alleen uitvoeren
|
||||
via console.'
|
||||
# Bericht dat wordt weergegeven wanneer een speler Sonar-opdrachten te snel uitvoert
|
||||
cool-down: '%prefix%<red>U kunt dit commando slechts elke 0,5 seconde uitvoeren.'
|
||||
cool-down-left: '%prefix%<red>Wacht nog <bold>%time-left%s<reset><red>.'
|
||||
console-only: '<prefix><red>Om veiligheidsredenen kunt u deze opdracht alleen uitvoeren via console.'
|
||||
# Bericht dat wordt getoond wanneer een speler geen toestemming heeft om een bepaald subcommand uit te voeren
|
||||
subcommand-no-permission: '%prefix%<red>U heeft geen toestemming om dit uit te voeren
|
||||
subcommand. <gray>(%permission%)'
|
||||
subcommand-no-permission: '<prefix><red>U heeft geen toestemming om dit uit te voeren subcommand. <gray>(<permission>)'
|
||||
|
||||
# Vertalingen voor '/sonar'
|
||||
main:
|
||||
# Informatief bericht dat boven alles wordt weergegeven bij het uitvoeren van de hoofdopdracht
|
||||
header:
|
||||
- <yellow><b>Sonar</b> %version% draaien op %platform%.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
|
||||
Jones Development and Sonar Contributors</click>
|
||||
- <yellow><b>Sonar</b> <version> draaien op <platform>.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development and Sonar Contributors</click>
|
||||
- ''
|
||||
- <gray>Hulp nodig of vragen?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Klik
|
||||
om Discord te openen)'>Open een ticket op de Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Klik
|
||||
om GitHub te openen)'>of open een nieuw probleem op GitHub.
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Klik om Discord te openen)'>Open een ticket op de Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Klik om GitHub te openen)'>of open een nieuw probleem op GitHub.
|
||||
- ''
|
||||
# Opmaak van de lijst met subcommand die wordt weergegeven bij het uitvoeren van de hoofdopdracht
|
||||
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>Alleen
|
||||
spelers: </gray>%only_players%<br><gray>Vereist console: </gray>%require_console%<br><gray>Toestemming:
|
||||
</gray><white>%permission%<br><gray>Aliassen: </gray>%aliases%''><dark_aqua>
|
||||
▪ <gray>/sonar %subcommand% <white>%description%'
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Alleen spelers: </gray><only-players><br><gray>Vereist console: </gray><only-console><br><gray>Toestemming: </gray><white><permission><br><gray>Aliassen: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# Opmaak voor ja- (tick) en nee- (cross) waarden in de hover-tekst van de subcommands
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# Vertalingen voor '/sonar reload'
|
||||
reload:
|
||||
# Beschrijving van dit subcommand
|
||||
description: "Alle configuraties opnieuw laden"
|
||||
# Bericht dat wordt weergegeven wanneer iemand Sonar begint te herladen
|
||||
start: '%prefix%Sonar opnieuw laden...'
|
||||
start: '<prefix>Sonar opnieuw laden...'
|
||||
# Bericht dat wordt weergegeven wanneer Sonar klaar is met herladen
|
||||
finish: '%prefix%<green>Opnieuw geladen <gray>(%taken%ms)'
|
||||
finish: '<prefix><green>Opnieuw geladen <gray>(<time-taken>ms)'
|
||||
|
||||
# Vertalingen voor '/sonar verbose'
|
||||
verbose:
|
||||
# Beschrijving van dit subcommand
|
||||
description: "Sonar verbose in- of uitschakelen"
|
||||
# Bericht dat wordt weergegeven wanneer een speler zich abonneert op Sonar uitgebreid
|
||||
subscribed: '%prefix%U bekijkt nu Sonar uitgebreid.'
|
||||
subscribe: '<prefix>U bekijkt nu Sonar uitgebreid.'
|
||||
# Bericht dat wordt weergegeven wanneer een speler zich afmeldt voor Sonar uitgebreid
|
||||
unsubscribed: '%prefix%Je bekijkt Sonar niet langer uitgebreid.'
|
||||
unsubscribe: '<prefix>Je bekijkt Sonar niet langer uitgebreid.'
|
||||
|
||||
# Vertalingen voor '/sonar notify'
|
||||
notify:
|
||||
# Beschrijving van dit subcommand
|
||||
description: "Aanval meldingen in- of uitschakelen"
|
||||
# Bericht dat wordt weergegeven wanneer een speler zich abonneert op Sonar aanval meldingen
|
||||
subscribed: '%prefix%U bekijkt nu Sonar aanval meldingen.'
|
||||
subscribe: '<prefix>U bekijkt nu Sonar aanval meldingen.'
|
||||
# Bericht dat wordt weergegeven wanneer een speler zich afmeldt voor Sonar aanval meldingen
|
||||
unsubscribed: '%prefix%Je bekijkt Sonar niet langer Sonar aanval meldingen.'
|
||||
unsubscribe: '<prefix>Je bekijkt Sonar niet langer Sonar aanval meldingen.'
|
||||
|
||||
# Vertalingen voor '/sonar blacklist'
|
||||
blacklist:
|
||||
# Beschrijving van dit subcommand
|
||||
description: "Beheer IP-adressen op de zwarte lijst"
|
||||
# Bericht dat wordt weergegeven wanneer iemand de zwarte lijst probeert te wissen, maar deze leeg is
|
||||
empty: '%prefix%De zwarte lijst is momenteel leeg. Daarom waren er geen IP-adressen
|
||||
verwijderd van de zwarte lijst.'
|
||||
empty: '<prefix>De zwarte lijst is momenteel leeg. Daarom waren er geen IP-adressen verwijderd van de zwarte lijst.'
|
||||
# Bericht dat wordt weergegeven wanneer iemand de zwarte lijst wist
|
||||
cleared: '%prefix%Je hebt in totaal %removed% IP-adres(sen) met succes verwijderd
|
||||
van de zwarte lijst.'
|
||||
clear: '<prefix>Je hebt in totaal <amount> IP-adres(sen) met succes verwijderd van de zwarte lijst.'
|
||||
# Bericht dat wordt getoond wanneer iemand de omvang van de zwarte lijst controleert
|
||||
size: '%prefix%De zwarte lijst bevat momenteel %amount% IP-adres(sen).'
|
||||
size: '<prefix>De zwarte lijst bevat momenteel <amount> IP-adres(sen).'
|
||||
# Bericht dat wordt getoond wanneer iemand een IP-adres aan de zwarte lijst toevoegt
|
||||
added: '%prefix%%ip% is succesvol toegevoegd aan de zwarte lijst.'
|
||||
add: '<prefix><ip> is succesvol toegevoegd aan de zwarte lijst.'
|
||||
# Bericht dat wordt getoond wanneer iemand een IP-adres toevoegt aan de zwarte lijst die is geverifieerd
|
||||
added-warning: '%prefix%<red>Attentie: <white>%ip% staat momenteel op de witte lijst. Overwegen
|
||||
het verwijderen van het IP-adres uit de lijst met geverifieerde spelers om potentieel te voorkomen
|
||||
problemen.'
|
||||
add-warning: '<prefix><red>Attentie: <white><ip> staat momenteel op de witte lijst. Overwegen het verwijderen van het IP-adres uit de lijst met geverifieerde spelers om potentieel te voorkomen problemen.'
|
||||
# Bericht dat verschijnt wanneer iemand een IP-adres van de zwarte lijst verwijdert
|
||||
removed: '%prefix%%ip% is met succes verwijderd van de zwarte lijst.'
|
||||
remove: '<prefix><ip> is met succes verwijderd van de zwarte lijst.'
|
||||
# Bericht dat wordt weergegeven wanneer iemand een IP-adres aan de zwarte lijst toevoegt, maar dit al op de zwarte lijst staat
|
||||
duplicate-ip: '%prefix%Het door u opgegeven IP-adres staat al op de zwarte lijst.'
|
||||
ip-duplicate: '<prefix>Het door u opgegeven IP-adres staat al op de zwarte lijst.'
|
||||
# Bericht dat verschijnt wanneer iemand een IP-adres van de zwarte lijst verwijdert, maar dit niet op de zwarte lijst staat
|
||||
ip-not-found: '%prefix%Het door u opgegeven IP-adres staat niet op de zwarte lijst.'
|
||||
ip-not-found: '<prefix>Het door u opgegeven IP-adres staat niet op de zwarte lijst.'
|
||||
|
||||
# Vertalingen voor '/sonar verified'
|
||||
verified:
|
||||
# Beschrijving van dit subcommand
|
||||
description: "Geverifieerde IP-adressen beheren"
|
||||
# Bericht dat wordt weergegeven wanneer iemand de lijst met geverifieerde spelers probeert te wissen, maar leeg is
|
||||
empty: '%prefix%De lijst met geverifieerde spelers is momenteel leeg. Daarom nee
|
||||
spelers waren niet geverifieerd.'
|
||||
empty: '<prefix>De lijst met geverifieerde spelers is momenteel leeg. Daarom nee spelers waren niet geverifieerd.'
|
||||
# Bericht dat wordt weergegeven wanneer iemand de lijst met geverifieerde spelers wist
|
||||
cleared: '%prefix%Je hebt met succes de verificatie van in totaal %removed% unieke speler(s) ongedaan gemaakt.'
|
||||
clear: '<prefix>Je hebt met succes de verificatie van in totaal <amount> unieke speler(s) ongedaan gemaakt.'
|
||||
# Bericht dat wordt weergegeven wanneer iemand de grootte van de lijst met geverifieerde spelers controleert
|
||||
size: '%prefix%Er zijn momenteel %amount% unieke speler(s) geverifieerd.'
|
||||
size: '<prefix>Er zijn momenteel <amount> unieke speler(s) geverifieerd.'
|
||||
# Bericht dat wordt weergegeven wanneer iemand de verificatie van een IP-adres ongedaan maakt
|
||||
removed: '%prefix%De verificatie van %ip% is succesvol ingetrokken.'
|
||||
remove: '<prefix>De verificatie van <ip> is succesvol ingetrokken.'
|
||||
# Bericht dat wordt weergegeven wanneer iemand een IP-adres verifieert
|
||||
added: '%prefix%%ip% succesvol geverifieerd.'
|
||||
add: '<prefix><ip> succesvol geverifieerd.'
|
||||
# Bericht dat wordt weergegeven wanneer iemand een IP-adres/UUID probeert te verifiëren die al is geverifieerd
|
||||
already: '%prefix%Het IP-adres en de UUID die u heeft opgegeven, zijn al geverifieerd.'
|
||||
already: '<prefix>Het IP-adres en de UUID die u heeft opgegeven, zijn al geverifieerd.'
|
||||
# Bericht dat wordt weergegeven wanneer iemand de verificatie van een IP-adres ongedaan maakt, maar het is niet geverifieerd
|
||||
ip-not-found: '%prefix%Het door u opgegeven IP-adres is niet geverifieerd.'
|
||||
# Bericht dat wordt weergegeven wanneer iemand tweemaal hetzelfde IP-adres probeert te de-verifiëren (dubbele handeling)
|
||||
blocked: '%prefix%Wacht tot de huidige bewerking is voltooid.'
|
||||
ip-not-found: '<prefix>Het door u opgegeven IP-adres is niet geverifieerd.'
|
||||
# Message shown when someone looks up the verification history of an IP address
|
||||
history: '<prefix><yellow>Eerdere UUID''s voor <ip>:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# Vertalingen voor '/sonar statistics'
|
||||
statistics:
|
||||
# Beschrijving van dit subcommand
|
||||
description: "Toon sessie statistieken van deze server"
|
||||
# Informatieve boodschap die boven alles wordt getoond bij het bekijken van de statistieken
|
||||
header: '%prefix%Er worden %type% statistieken voor deze sessie weergegeven:'
|
||||
header: '<prefix>Er worden <statistics-type> statistieken voor deze sessie weergegeven:'
|
||||
# Bericht dat wordt weergegeven wanneer een speler een onbekende statistiek probeert te bekijken
|
||||
unknown-type: '%prefix%<red>Onbekend statistieke! Beschikbare statistieken: <gray>%statistics%'
|
||||
unknown-type: '<prefix><red>Onbekend statistieke! Beschikbare statistieken: <gray><statistics>'
|
||||
# Formaat van het algemene statistische bericht
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>Geverifieerde IP-adressen: <white>%verified%'
|
||||
- ' <dark_aqua>▪ <gray>IP-adressen verifiëren: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>IP-adressen op de zwarte lijst: <white>%blacklisted%'
|
||||
- ' <dark_aqua>▪ <gray>Wachtrij staande aanmeldingen: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Totaal aantal niet-unieke joins: <white>%total_joins%'
|
||||
- ' <dark_aqua>▪ <gray>Totaal aantal verificatiepogingen: <white>%total_attempts%'
|
||||
- ' <dark_aqua>▪ <gray>Totaal aantal mislukte verificaties: <white>%total_failed%'
|
||||
- ' <dark_aqua>▪ <gray>Server-uptime: <white>%uptime%'
|
||||
- ' <dark_aqua>▪ <gray>Geverifieerde IP-adressen: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>IP-adressen verifiëren: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>IP-adressen op de zwarte lijst: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>Wachtrij staande aanmeldingen: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>Totaal aantal niet-unieke joins: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>Totaal aantal verificatiepogingen: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>Totaal aantal mislukte verificaties: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>Server-uptime: <white><server-uptime>'
|
||||
# Formaat van het bericht over CPU-statistieken
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>Verwerk het CPU-gebruik nu: <white>%process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Systeem-CPU-gebruik nu: <white>%system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>CPU-gebruik per kernproces: <white>%average_process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>CPU-gebruik per core systeem: <white>%average_system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Algemene systeembelasting: <white>%load_average%%'
|
||||
- ' <dark_aqua>▪ <gray>Totaal aantal virtuele CPUs: <white>%virtual_cores%'
|
||||
- ' <dark_aqua>▪ <gray>Verwerk het CPU-gebruik nu: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Systeem-CPU-gebruik nu: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>CPU-gebruik per kernproces: <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>CPU-gebruik per core systeem: <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Totaal aantal virtuele CPUs: <white><virtual-core-count>'
|
||||
# Formaat van het bericht met geheugenstatistieken
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>Totaal vrij geheugen: <white>%free_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Totaal gebruikt geheugen: <white>%used_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Totaal maximaal geheugen: <white>%max_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Totaal toegewezen geheugen: <white>%total_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Totaal vrij geheugen: <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Totaal gebruikt geheugen: <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Totaal maximaal geheugen: <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Totaal toegewezen geheugen: <white><total-memory>'
|
||||
# Formaat van het bericht met netwerkstatistieken
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>Huidige inkomende gebruikte bandbreedte: <white>%incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Huidige uitgaande gebruikte bandbreedte: <white>%outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Totale inkomende gebruikte bandbreedte: <white>%ttl_incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Totale uitgaande gebruikte bandbreedte: <white>%ttl_outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Huidige inkomende gebruikte bandbreedte: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Huidige uitgaande gebruikte bandbreedte: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Totale inkomende gebruikte bandbreedte: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>Totale uitgaande gebruikte bandbreedte: <white><outgoing-traffic-ttl>'
|
||||
|
||||
# Vertalingen voor '/sonar dump
|
||||
dump:
|
||||
# Beschrijving van dit subcommand
|
||||
description: "Informatie ontwikkelaar afdrukken"
|
||||
# Formaat van de informatie die naar de console wordt gestuurd
|
||||
log: "Informatie gegenereerd: <dumped-json-data>"
|
||||
|
||||
# Vertalingen voor alle berichten tijdens het verificatieproces
|
||||
verification:
|
||||
# Vertalingen voor alle debug-berichten tijdens de verificatie
|
||||
logs:
|
||||
# Bericht dat in de console wordt geregistreerd wanneer een nieuwe speler zich bij de server voegt
|
||||
connection: '%name%%ip% (%protocol%) heeft verbinding gemaakt.'
|
||||
connection: '<username><ip> (<protocol>) heeft verbinding gemaakt.'
|
||||
# Bericht dat in de console wordt geregistreerd wanneer de verificatie van een speler mislukt
|
||||
failed: '%ip% (%protocol%) heeft de bot-controle niet doorstaan vanwege: %reason%'
|
||||
failed: '<ip> (<protocol>) heeft de bot-controle niet doorstaan vanwege: <reason>'
|
||||
# Bericht dat op de console wordt geregistreerd wanneer een speler op de zwarte lijst staat
|
||||
blacklisted: '%ip% (%protocol%) stond op de zwarte lijst vanwege te veel mislukte pogingen'
|
||||
blacklisted: '<ip> (<protocol>) stond op de zwarte lijst vanwege te veel mislukte pogingen'
|
||||
# Bericht dat op de console wordt geregistreerd wanneer een speler is geverifieerd
|
||||
successful: '%name% is succesvol geverifieerd (%time%s!).'
|
||||
successful: '<username> is succesvol geverifieerd (<time-taken>s!).'
|
||||
|
||||
# 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.'
|
||||
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 of zij het verkeerde antwoord invoert in de chat
|
||||
incorrect: '%prefix%<red>U heeft de verkeerde code ingevoerd. Probeer het opnieuw.'
|
||||
incorrect: '<prefix><red>U heeft de verkeerde code 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%'
|
||||
- <gold>Vals positief? <gray><support-url>
|
||||
- '<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%'
|
||||
- <gold>Hulp nodig bij het inloggen? <gray><support-url>
|
||||
- '<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? <gray>%support-url%
|
||||
- '%footer%'
|
||||
- <gold>Hulp nodig bij het inloggen? <gray><support-url>
|
||||
- '<footer>'
|
||||
|
||||
# Vertalingen voor Sonar's actiebalk uitgebreide berichten
|
||||
verbose:
|
||||
layout:
|
||||
# Lay-out voor de actiebalk van Sonar, uitgebreid
|
||||
# Tijdelijke aanduidingen:
|
||||
# - '%queued%' Aantal verbindingen in de wachtrij
|
||||
# - '%verifying%' Aantal verifiërende verbindingen
|
||||
# - '%blacklisted%' Aantal op de zwarte lijst geplaatste IP-adressen
|
||||
# - '%total-joins%' Aantal totale pogingen tot deelname
|
||||
# - '%verify-total%' Aantal totale verificatiepogingen
|
||||
# - '%verify-success%' Aantal geverifieerde IP-adressen
|
||||
# - '%verify-failed%' Aantal mislukte verificaties
|
||||
# - '%logins-per-second%' Aantal logins per seconde
|
||||
# - '%connections-per-second%' Aantal verbindingen per seconde
|
||||
# - '%attack-duration%' Duur van de huidige aanval
|
||||
# - '%incoming-traffic%' Inkomend bandbreedtegebruik per seconde
|
||||
# - '%outgoing-traffic%' Uitgaand bandbreedtegebruik per seconde
|
||||
# - '%incoming-traffic-ttl%' Totaal inkomend bandbreedtegebruik
|
||||
# - '%outgoing-traffic-ttl%' Totaal uitgaand bandbreedtegebruik
|
||||
# - '%used-memory%' Hoeveelheid gebruikt geheugen van het proces
|
||||
# - '%total-memory%' Hoeveelheid totale geheugen van het proces
|
||||
# - '%max-memory%' Hoeveelheid maximaal geheugen van het proces
|
||||
# - '%free-memory%' Hoeveelheid vrij geheugen van het proces
|
||||
# - '%animation%' Aanpasbaar geanimeerd symbool
|
||||
# Vertalingen voor de normale uitgebreide uitvoer van Sonar
|
||||
normal: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Inloggen/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Verifiëren <white>%verifying% <dark_aqua>|
|
||||
<green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
# Layout for Sonar's actionbar verbose during an attack
|
||||
attack: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Inloggen/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Wachtrij <white>%queued% <dark_aqua>╺
|
||||
<gray>Verifiëren <white>%verifying% <dark_aqua>╺ <gray>Duur <white>%attack-duration%
|
||||
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
# Lay-out voor de actiebalk van Sonar
|
||||
normal: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Inloggen/s <white><logins-per-second> <dark_aqua>╺ <gray>Verifiëren <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Lay-out voor Sonars actiebalk verbose tijdens een aanval
|
||||
attack: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Inloggen/s <white><logins-per-second> <dark_aqua>╺ <gray>Wachtrij <white><queued> <dark_aqua>╺ <gray>Verifiëren <white><verifying> <dark_aqua>╺ <gray>Duur <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Animatie voor de actiebalk
|
||||
# Alternatieven:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
@@ -301,11 +280,9 @@ notifications:
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>Er is een bot-aanval gedetecteerd.
|
||||
- ' <dark_aqua>▪ <gray>Inloggen per seconde: <white>%logins-per-second%'
|
||||
- ' <dark_aqua>▪ <gray>Spelers verifiëren: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Spelers in de wachtrij: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Inloggen per seconde: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>Spelers verifiëren: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Spelers in de wachtrij: <white><queued>'
|
||||
- ''
|
||||
- <gray>Bekijk meer informatie via <white><click:run_command:'/sonar verbose'><hover:show_text:'(Klik
|
||||
om uit te voeren)'>/sonar verbose</hover></click><gray> of <white><click:run_command:'/sonar
|
||||
stats'><hover:show_text:'(Klik om uit te voeren)'>/sonar stats</hover></click><gray>.
|
||||
- <gray>Bekijk meer informatie via <white><click:run_command:'/sonar verbose'><hover:show_text:'(Klik om uit te voeren)'>/sonar verbose</hover></click><gray> of <white><click:run_command:'/sonar stats'><hover:show_text:'(Klik om uit te voeren)'>/sonar stats</hover></click><gray>.
|
||||
- ''
|
||||
@@ -13,275 +13,261 @@
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# Плейсхолдер для '%prefix%' в конфиге
|
||||
# Плейсхолдер для '<prefix>' в конфиге
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Плейсхолдер для '%support-url%' в конфиге
|
||||
# Плейсхолдер для '<support-url>' в конфиге
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# Плейсхолдер для '%header%' в конфиге
|
||||
# Плейсхолдер для '<header>' в конфиге
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# Плейсхолдер для '%footer%' в конфиге
|
||||
# Плейсхолдер для '<footer>' в конфиге
|
||||
footer:
|
||||
- <gray>Если вы считаете что это ошибка, обратитесь к администратору.
|
||||
# Сообщение, которое показывается когда кто-то заходит на сервер, но в сети слишком много игроков с его IP-адресом
|
||||
too-many-online-per-ip:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>С вашего IP-адреса в сети слишком много игроков.
|
||||
- '%footer%'
|
||||
- '<footer>'
|
||||
|
||||
# Перевод для команд
|
||||
commands:
|
||||
# Сообщение, которое будет показано, когда игрок попытается выполнить команду /sonar без разрешения
|
||||
no-permission: '%prefix%<red>У вас нет прав на использование этой команды.'
|
||||
no-permission: '<prefix><red>У вас нет прав на использование этой команды.'
|
||||
# Сообщение, которое отображается при неправильном использовании команды
|
||||
incorrect-usage: '%prefix%<red>Применение: /sonar %usage%'
|
||||
incorrect-usage: '<prefix><red>Применение: /sonar <subcommand-usage>'
|
||||
# Сообщение, которое отображается, когда кто-то предоставляет неверный формат IP-адреса
|
||||
invalid-ip-address: '%prefix%Указанный вами IP-адрес недействителен'
|
||||
invalid-ip-address: '<prefix>Указанный вами IP-адрес недействителен'
|
||||
# Сообщение, которое отображается, когда консоль выполняет команду, предназначенную только для игроков
|
||||
player-only: '%prefix%<red>Эта команда может быть выполнена только игроком.'
|
||||
player-only: '<prefix><red>Эта команда может быть выполнена только игроком.'
|
||||
# Сообщение, которое отображается, когда игрок выполняет команду, доступную только для консоли
|
||||
console-only: '%prefix%<red>Эту команду можно выполнить только только через консоль.'
|
||||
# Сообщение, которое появляется, когда игрок выполняет команды плагина слишком быстро
|
||||
cool-down: '%prefix%<red>Эту команду можно использовать только каждые 0,5 секунды.'
|
||||
cool-down-left: '%prefix%<red>Подождите еще <bold>%time-left%s<reset><red>.'
|
||||
console-only: '<prefix><red>Эту команду можно выполнить только только через консоль.'
|
||||
# Сообщение, которое отображается, когда у игрока нет разрешения на выполнение подкоманды
|
||||
subcommand-no-permission: '%prefix%<red>У вас нет разрешения на выполнение этой
|
||||
подкоманды. <gray>(%permission%)'
|
||||
subcommand-no-permission: '<prefix><red>У вас нет разрешения на выполнение этой подкоманды. <gray>(<permission>)'
|
||||
|
||||
# Перевод для '/sonar'
|
||||
main:
|
||||
# Информационное сообщение, которое показывается поверх всего при выполнении главной команды
|
||||
header:
|
||||
- <yellow>Запущен <b>Sonar</b> %version% на %platform%.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
|
||||
Jones Development and Sonar Contributors</click>
|
||||
- <yellow>Запущен <b>Sonar</b> <version> на <platform>.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development and Sonar Contributors</click>
|
||||
- ''
|
||||
- <gray>Нужна помощь или есть вопросы?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Нажмите
|
||||
чтобы открыть Discord)'>Откройте тикет в Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Нажмите
|
||||
чтобы открыть GitHub)'>или откройте новый вопрос на GitHub.
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Нажмите чтобы открыть Discord)'>Откройте тикет в Discord </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Нажмите чтобы открыть GitHub)'>или откройте новый вопрос на GitHub.
|
||||
- ''
|
||||
# Форматирование списка подкоманд
|
||||
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>Только
|
||||
игроки: </gray>%only_players%<br><gray>Требуется консоль: </gray>%require_console%<br><gray>Permission:
|
||||
</gray><white>%permission%<br><gray>Псевдонимы: </gray>%aliases%''><dark_aqua>
|
||||
▪ <gray>/sonar %subcommand% <white>%description%'
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Только игроки: </gray><only-players><br><gray>Требуется консоль: </gray><only-console><br><gray>Permission: </gray><white><permission><br><gray>Псевдонимы: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# Форматирование значений "да" (tick) и "нет" (cross) в наведенном тексте подкоманд
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# Перевод для '/sonar reload'
|
||||
reload:
|
||||
# Описание для этой подкоманды
|
||||
description: "Перезагрузить все конфигурации"
|
||||
# Сообщение, которое отображается, когда кто-то начинает перезагружать Sonar
|
||||
start: '%prefix%Перезагрузка...'
|
||||
start: '<prefix>Перезагрузка...'
|
||||
# Сообщение, которое отображается, когда Sonar завершает перезагрузку
|
||||
finish: '%prefix%<green>Успешная перезагрузка <gray>(%taken%ms)'
|
||||
finish: '<prefix><green>Успешная перезагрузка <gray>(<time-taken>ms)'
|
||||
|
||||
# Перевод для '/sonar verbose'
|
||||
verbose:
|
||||
# Описание для этой подкоманды
|
||||
description: "Включить или отключить подробную информацию"
|
||||
# Сообщение, которое отображается, когда игрок прослушивает Sonar verbose
|
||||
subscribed: '%prefix%Вы просматриваете подробную информацию о Sonar.'
|
||||
subscribe: '<prefix>Вы просматриваете подробную информацию о Sonar.'
|
||||
# Сообщение, которое отображается, когда игрок перестает прослушивать Sonar verbose
|
||||
unsubscribed: '%prefix%Вы больше не просматриваете подробную информацию о Sonar.'
|
||||
unsubscribe: '<prefix>Вы больше не просматриваете подробную информацию о Sonar.'
|
||||
|
||||
# Перевод для '/sonar notify'
|
||||
notify:
|
||||
# Описание для этой подкоманды
|
||||
description: "Включить или отключить уведомления об атаках"
|
||||
# Сообщение, которое отображается, когда игрок подписывается на уведомления об атаках
|
||||
subscribed: '%prefix%Вы получаете уведомления об атаках.'
|
||||
subscribe: '<prefix>Вы получаете уведомления об атаках.'
|
||||
# Сообщение, которое отображается, когда игрок отписывается на уведомления об атаках
|
||||
unsubscribed: '%prefix%Вы больше не получаете уведомления об атаках.'
|
||||
unsubscribe: '<prefix>Вы больше не получаете уведомления об атаках.'
|
||||
|
||||
# Перевод для '/sonar blacklist'
|
||||
blacklist:
|
||||
# Описание для этой подкоманды
|
||||
description: "Управление IP-адресами из черного списка"
|
||||
# Сообщение, которое отображается, когда кто-то пытается очистить черный список, но он пустой
|
||||
empty: '%prefix%В настоящее время черный список пуст. Поэтому ни один IP-адрес
|
||||
не был удален из черного списка.'
|
||||
empty: '<prefix>В настоящее время черный список пуст. Поэтому ни один IP-адрес не был удален из черного списка.'
|
||||
# Сообщение, которое показывается, когда кто-то очищает черный список
|
||||
cleared: '%prefix%Вы успешно удалили %удалено% IP-адресов из черного списка.'
|
||||
clear: '<prefix>Вы успешно удалили <amount> IP-адресов из черного списка.'
|
||||
# Сообщение, которое показывается, когда кто-то проверяет размер черного списка
|
||||
size: '%prefix%В настоящее время черный список содержит %amount% IP-адрес(ов).'
|
||||
size: '<prefix>В настоящее время черный список содержит <amount> IP-адрес(ов).'
|
||||
# Сообщение, которое показывается, когда кто-то добавляет IP-адрес в черный список
|
||||
added: '%prefix%Успешно добавлен %ip% в черный список.'
|
||||
add: '<prefix>Успешно добавлен <ip> в черный список.'
|
||||
# Сообщение, которое отображается, когда кто-то добавляет IP-адрес в черный список, который находится в белом списке
|
||||
added-warning: '%prefix%<red>Предупреждение: <white>%ip% находится в белом списке.
|
||||
Рассмотрите возможность удалить IP-адрес из белого списка игроков, чтобы избежать
|
||||
потенциальных ошибок.'
|
||||
add-warning: '<prefix><red>Предупреждение: <white><ip> находится в белом списке. Рассмотрите возможность удалить IP-адрес из белого списка игроков, чтобы избежать потенциальных ошибок.'
|
||||
# Сообщение, которое показывается, когда кто-то удаляет IP-адрес из черного списка
|
||||
removed: '%prefix%Успешно удален %ip% из черного списка.'
|
||||
remove: '<prefix>Успешно удален <ip> из черного списка.'
|
||||
# Сообщение, которое отображается, когда кто-то добавляет IP-адрес в черный список, но он уже находится в черном списке
|
||||
duplicate-ip: '%prefix%Указанный вами IP-адрес уже внесен в черный список.'
|
||||
ip-duplicate: '<prefix>Указанный вами IP-адрес уже внесен в черный список.'
|
||||
# Сообщение, которое отображается, когда кто-то удаляет IP-адрес из черного списка, но он не занесен в черный список
|
||||
ip-not-found: '%prefix%Указанный вами IP-адрес не внесен в черный список.'
|
||||
ip-not-found: '<prefix>Указанный вами IP-адрес не внесен в черный список.'
|
||||
|
||||
# Перевод для '/sonar verified'
|
||||
verified:
|
||||
# Описание для этой подкоманды
|
||||
description: "Управление проверенными IP-адресами"
|
||||
# Сообщение, которое отображается, когда кто-то пытается очистить список проверенных игроков, но он пустой
|
||||
empty: '%prefix%Список проверенных игроков в настоящее время пуст. Поэтому, ни
|
||||
один игрок не был удален.'
|
||||
empty: '<prefix>Список проверенных игроков в настоящее время пуст. Поэтому, ни один игрок не был удален.'
|
||||
# Сообщение, которое отображается когда кто-то отчистил список уникальных игроков
|
||||
cleared: '%prefix%Вы успешно очистили %removed% уникального игрока(ов).'
|
||||
clear: '<prefix>Вы успешно очистили <amount> уникального игрока(ов).'
|
||||
# Сообщение, которое отображается когда кто-то проверяет размер списка проврененных игроков
|
||||
size: '%prefix%В настоящее время проверено %amount% уникальных игроков.'
|
||||
size: '<prefix>В настоящее время проверено <amount> уникальных игроков.'
|
||||
# Сообщение, которое отображается при удалении IP-адреса из списка проверенных игроков
|
||||
removed: '%prefix%Успешно удален %ip%.'
|
||||
# Message that is shown when someone verifies an IP address
|
||||
added: '%prefix%Successfully verified %ip%.'
|
||||
# Message that is shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: '%prefix%The IP address and UUID you provided are already verified.'
|
||||
remove: '<prefix>Успешно удален <ip>.'
|
||||
# Сообщение, которое отображается при проверке IP-адреса
|
||||
add: '<prefix>Успешно проверен <ip>.'
|
||||
# Сообщение, которое отображается, когда кто-то пытается проверить IP-адрес/UUID, которые уже проверены
|
||||
already: '<prefix>Указанные вами IP-адрес и UUID уже проверены.'
|
||||
# Сообщение, которое показывается когда кто-то удаляет IP из списка но его небыло в списке
|
||||
ip-not-found: '%prefix%Указанный вами IP-адрес не в списке.'
|
||||
# Сообщение, которое показывается когда выполняю сразу две операции (двойная операция)
|
||||
blocked: '%prefix%Пожалуйста, дождитесь завершения текущей операции.'
|
||||
ip-not-found: '<prefix>Указанный вами IP-адрес не в списке.'
|
||||
# Сообщение, показываемое, когда кто-то просматривает историю проверки IP-адреса
|
||||
history: '<prefix><yellow>Предыдущие UUID для <ip>:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# Перевод для '/sonar statistics'
|
||||
statistics:
|
||||
# Описание для этой подкоманды
|
||||
description: "Показать статистику сеансов этого сервера"
|
||||
# Информационное сообщение, которое отображается поверх всего при просмотре статистики
|
||||
header: '%prefix%Показываем статистику %type% для этой сессии:'
|
||||
header: '<prefix>Показываем статистику <statistics-type> для этой сессии:'
|
||||
# Сообщение, которое отображается, когда игрок пытается просмотреть неизвестную статистику
|
||||
unknown-type: '%prefix%<redНеизвестный тип статистики! Доступные статистики: <gray>%statistics%'
|
||||
unknown-type: '<prefix><redНеизвестный тип статистики! Доступные статистики: <gray><statistics>'
|
||||
# Формат сообщения общей статистики
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>Проверенные IP-адреса: <white>%verified%'
|
||||
- ' <dark_aqua>▪ <gray>Проверяющиеся IP-адреса: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>IP-адреса в черном списке: <white>%blacklisted%'
|
||||
- ' <dark_aqua>▪ <gray>Текущая очередь на проверку: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Всего неуникальных соединений: <white>%total_joins%'
|
||||
- ' <dark_aqua>▪ <gray>Общее количество попыток проверки: <white>%total_attempts%'
|
||||
- ' <dark_aqua>▪ <gray>Всего неудачных попыток провери: <white>%total_failed%'
|
||||
- ' <dark_aqua>▪ <gray>Время работы сервера: <white>%uptime%'
|
||||
- ' <dark_aqua>▪ <gray>Проверенные IP-адреса: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>Проверяющиеся IP-адреса: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>IP-адреса в черном списке: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>Текущая очередь на проверку: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>Всего неуникальных соединений: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>Общее количество попыток проверки: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>Всего неудачных попыток провери: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>Время работы сервера: <white><server-uptime>'
|
||||
# Формат сообщения статистики процессора
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>Использование процессора сервером в данный момент: <white>%process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Использование процессора системой прямо сейчас: <white>%system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Использование процессора каждым ядром (сервером): <white>%average_process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Использование процессора каждым ядром (системой): <white>%average_system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Средняя нагрузка на систему: <white>%load_average%%'
|
||||
- ' <dark_aqua>▪ <gray>Общее количество виртуальных ядер: <white>%virtual_cores%'
|
||||
- ' <dark_aqua>▪ <gray>Использование процессора сервером в данный момент: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Использование процессора системой прямо сейчас: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Использование процессора каждым ядром (сервером): <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Использование процессора каждым ядром (системой): <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Общее количество виртуальных ядер: <white><virtual-core-count>'
|
||||
# Формат сообщения статистики памяти
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>Общее количество свободной памяти: <white>%free_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Общее количество используемой памяти: <white>%used_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Общий объем максимальной памяти: <white>%max_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Общее количество выделенной памяти: <white>%total_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Общее количество свободной памяти: <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Общее количество используемой памяти: <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Общий объем максимальной памяти: <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Общее количество выделенной памяти: <white><total-memory>'
|
||||
# Формат сообщения сетевой статистики
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>Текущая входящая используемая полоса пропускания: <white>%incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Текущая исходящая используемая полоса пропускания: <white>%outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Общая входящая использованная полоса пропускания: <white>%ttl_incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Общая исходящая использованная полоса пропускания: <white>%ttl_outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Текущая входящая используемая полоса пропускания: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Текущая исходящая используемая полоса пропускания: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Общая входящая использованная полоса пропускания: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>Общая исходящая использованная полоса пропускания: <white><outgoing-traffic-ttl>'
|
||||
|
||||
# Перевод для '/sonar dump'
|
||||
dump:
|
||||
# Описание для этой подкоманды
|
||||
description: "Вывести информацию о разработчике"
|
||||
# Формат информации, отправляемой в консоль
|
||||
log: "Сгенерированный дамп: <dumped-json-data>"
|
||||
|
||||
# Переводы всех сообщений во время проверки
|
||||
verification:
|
||||
# Переводы всех консольных сообщений во время проверки
|
||||
logs:
|
||||
# Сообщение, которое показыватся когда на сервер заходит новый игрок
|
||||
connection: '%name%%ip% (%protocol%) подключился.'
|
||||
connection: '<username><ip> (<protocol>) подключился.'
|
||||
# Сообщение, которое показывается когда кто-то не прошел проверку на бота
|
||||
failed: '%ip% (%protocol%) не прошел проверку бота на: %reason%'
|
||||
failed: '<ip> (<protocol>) не прошел проверку бота на: <reason>'
|
||||
# Сообщение, которое показывается когда кто-то было занесен в черный список
|
||||
blacklisted: '%ip% (%protocol%) был занесен в черный список за слишком большое
|
||||
количество неудачных попыток'
|
||||
blacklisted: '<ip> (<protocol>) был занесен в черный список за слишком большое количество неудачных попыток'
|
||||
# Сообщение, которое выводится в консоль при успешной проверке игрока
|
||||
successful: '%name% прошел проверку (%time%s!).'
|
||||
successful: '<username> прошел проверку (<time-taken>s!).'
|
||||
|
||||
captcha:
|
||||
# Сообщение, которое показывается игроку, когда он должен ввести ответ на капчу
|
||||
enter-code: '%prefix%<green>Введите код в чате, который отображается на карте.'
|
||||
enter-code: '<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%'
|
||||
- <gold>Вы считаете что это ошибка? <gray><support-url>
|
||||
- '<footer>'
|
||||
# Сообщение, которое показывается когда кто-то входит на сервер с некорректным ником
|
||||
invalid-username:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>Ваш ник содержит недопустимые символы.
|
||||
- '%footer%'
|
||||
- '<footer>'
|
||||
# Сообщение, которое показывается когда кто-то входит на сервер с заблокированной версии
|
||||
blacklisted-protocol:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>Вы используете версию, которая запрещена на нашем сервере.
|
||||
- <gold>Нужна помощь? <gray>%support-url%
|
||||
- '%footer%'
|
||||
- <gold>Нужна помощь? <gray><support-url>
|
||||
- '<footer>'
|
||||
# Сообщение которое показывается при успешной верификации
|
||||
success:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <green>Вы успешно прошли проверку.
|
||||
- <white>Пожалуйста, перезайдите.
|
||||
# Сообщение которое показывается, когда кто-то не прошел верификацию
|
||||
failed:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>Вы не прошли проверку бота.
|
||||
- <gray>Пожалуйста, подождите несколько секунд, прежде чем повторить попытку.
|
||||
- <gold>Нужна помощь? <gray>%support-url%
|
||||
- '%footer%'
|
||||
- <gold>Нужна помощь? <gray><support-url>
|
||||
- '<footer>'
|
||||
|
||||
# Перевод для action bar (verbose)
|
||||
verbose:
|
||||
layout:
|
||||
# Панель действий Sonar verbose
|
||||
# Placeholders:
|
||||
# - '%queued%' Количество игроков в очереди
|
||||
# - '%verifying%' Количество проверяемых игроков
|
||||
# - '%blacklisted%' Количество IP-адресов в черном списке
|
||||
# - '%total-joins%' Количество всех попыток соединения
|
||||
# - '%verify-total%' Количество попыток верификации
|
||||
# - '%verify-success%' Количество проверенных IP-адресов
|
||||
# - '%verify-failed%' Количество неудачных проверок
|
||||
# - '%logins-per-second%' Количество входов на сервер в секунду
|
||||
# - '%connections-per-second%' Количество соединений в секунду
|
||||
# - '%attack-duration%' Продолжительность текущей атаки
|
||||
# - '%incoming-traffic%' Использование входящего трафика в секунду
|
||||
# - '%outgoing-traffic%' Использование исходящего трафика в секунду
|
||||
# - '%incoming-traffic-ttl%' Общее использование входящего трафика пропускания
|
||||
# - '%outgoing-traffic-ttl%' Общее использование исходящего трафика пропускания
|
||||
# - '%used-memory%' Количество использованной памяти
|
||||
# - '%total-memory%' Количество общей памяти
|
||||
# - '%max-memory%' Объем максимальной памяти
|
||||
# - '%free-memory%' Количество свободной памяти
|
||||
# - '%animation%' Настраиваемый анимированный символ
|
||||
# Статистика в режиме реального времени (verbose)
|
||||
normal: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Logins/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>На проверке <white>%verifying%
|
||||
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
# Action bar отображающей информацию
|
||||
normal: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s
|
||||
<white><logins-per-second> <dark_aqua>╺ <gray>На проверке <white><verifying>
|
||||
<dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Action bar отображающей информацию во время атаки
|
||||
attack: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Logins/s
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>В очереди <white>%queued% <dark_aqua>╺
|
||||
<gray>На проверке <white>%verifying% <dark_aqua>╺ <gray>Продолжительность <white>%attack-duration%
|
||||
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
attack: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Logins/s
|
||||
<white><logins-per-second> <dark_aqua>╺ <gray>В очереди <white><queued> <dark_aqua>╺
|
||||
<gray>На проверке <white><verifying> <dark_aqua>╺ <gray>Продолжительность <white><attack-duration>
|
||||
<dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Анимация для загрузки
|
||||
# Альтернативы:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
@@ -304,12 +290,9 @@ notifications:
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>Бот-атака была обнаружена.
|
||||
- ' <dark_aqua>▪ <gray>Количество входов в секунду: <white>%logins-per-second%'
|
||||
- ' <dark_aqua>▪ <gray>Игроков проверяется: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Игроков в очереди: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Количество входов в секунду: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>Игроков проверяется: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Игроков в очереди: <white><queued>'
|
||||
- ''
|
||||
- <gray>Просмотреть дополнительную информацию, используя <white><click:run_command:'/sonar
|
||||
verbose'><hover:show_text:'(Нажмите чтобы выполнить команду)'>/sonar verbose</hover></click><gray>
|
||||
или <white><click:run_command:'/sonar stats'><hover:show_text:'(Нажмите чтобы
|
||||
выполнить команду)'>/sonar stats</hover></click><gray>.
|
||||
- <gray>Просмотреть дополнительную информацию, используя <white><click:run_command:'/sonar verbose'><hover:show_text:'(Нажмите чтобы выполнить команду)'>/sonar verbose</hover></click><gray> или <white><click:run_command:'/sonar stats'><hover:show_text:'(Нажмите чтобы выполнить команду)'>/sonar stats</hover></click><gray>.
|
||||
- ''
|
||||
@@ -13,262 +13,253 @@
|
||||
# Jones Development ve Sonar Katkıda Bulunanlar tarafından GPL-3.0 lisansı altında lisanslanmıştır
|
||||
#
|
||||
|
||||
# Bu yapılandırma dosyasındaki her '%prefix%' için yer tutucu
|
||||
# Bu yapılandırma dosyasındaki her '<prefix>' için yer tutucu
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# Bu yapılandırma dosyasındaki her '%support-url%' için yer tutucu
|
||||
# Bu yapılandırma dosyasındaki her '<support-url>' için yer tutucu
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# Bu yapılandırma dosyasındaki her '%header%' için yer tutucu
|
||||
# Bu yapılandırma dosyasındaki her '<header>' için yer tutucu
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# Bu yapılandırma dosyasındaki her '%footer%' için yer tutucu
|
||||
# Bu yapılandırma dosyasındaki her '<footer>' için yer tutucu
|
||||
footer:
|
||||
- <gray>Bunun bir hata olduğunu düşünüyorsanız lütfen bir yönetici ile iletişime geçin.
|
||||
# Birisi bağlandığında ancak IP adresleriyle çevrimiçi olan oyuncu sayısı fazla olduğunda gösterilen bağlantı kesme mesajı
|
||||
too-many-online-per-ip:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>IP adresinizle çevrimiçi olan çok fazla oyuncu var.
|
||||
- '%footer%'
|
||||
- '<footer>'
|
||||
|
||||
# Komutlar için çeviriler
|
||||
commands:
|
||||
# Bir oyuncu /sonar komutunu izinsiz çalıştırdığında gösterilen mesaj
|
||||
no-permission: '%prefix%<red>Bu komutu çalıştırmak için izniniz yok.'
|
||||
no-permission: '<prefix><red>Bu komutu çalıştırmak için izniniz yok.'
|
||||
# Birisi bir komutu yanlış kullandığında gösterilen mesaj
|
||||
incorrect-usage: '%prefix%<red>Kullanım: /sonar %usage%'
|
||||
incorrect-usage: '<prefix><red>Kullanım: /sonar <subcommand-usage>'
|
||||
# Birisi geçersiz bir IP adresi sağladığında gösterilen mesaj (Geçersiz format)
|
||||
invalid-ip-address: '%prefix%Sağladığınız IP adresi geçersiz görünüyor.'
|
||||
invalid-ip-address: '<prefix>Sağladığınız IP adresi geçersiz görünüyor.'
|
||||
# Konsol, sadece oyuncunun çalıştırabileceği bir komut çalıştırdığında gösterilen mesaj
|
||||
player-only: '%prefix%<red>Bu komutu sadece bir oyuncu olarak çalıştırabilirsiniz.'
|
||||
player-only: '<prefix><red>Bu komutu sadece bir oyuncu olarak çalıştırabilirsiniz.'
|
||||
# Bir oyuncu, sadece konsolun çalıştırabileceği bir komutu çalıştırdığında gösterilen mesaj
|
||||
console-only: '%prefix%<red>Güvenlik nedeniyle, bu komutu sadece konsoldan çalıştırabilirsiniz.'
|
||||
# Bir oyuncu Sonar komutlarını çok hızlı çalıştırdığında gösterilen mesaj
|
||||
cool-down: '%prefix%<red>Bu komutu her 0,5 saniyede bir çalıştırabilirsiniz.'
|
||||
cool-down-left: '%prefix%<red>Lütfen bir <bold>%time-left%sn<reset><red> daha bekleyin.'
|
||||
console-only: '<prefix><red>Güvenlik nedeniyle, bu komutu sadece konsoldan çalıştırabilirsiniz.'
|
||||
# Bir oyuncunun belirli bir alt komutu çalıştırma izni olmadığında gösterilen mesaj
|
||||
subcommand-no-permission: '%prefix%<red>Bu alt komutu çalıştırmak için izniniz yok. <gray>(%permission%)'
|
||||
subcommand-no-permission: '<prefix><red>Bu alt komutu çalıştırmak için izniniz yok. <gray>(<permission>)'
|
||||
|
||||
# '/sonar' için çeviriler
|
||||
main:
|
||||
# Ana komutu çalıştırırken her şeyin üstünde gösterilen bilgilendirici mesaj
|
||||
header:
|
||||
- <yellow><b>Sonar</b> %version% sürümü %platform% üzerinde çalışıyor.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
|
||||
Jones Development ve Sonar Katkıda Bulunanlar</click>
|
||||
- <yellow><b>Sonar</b> <version> sürümü <platform> üzerinde çalışıyor.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development ve Sonar Katkıda Bulunanlar</click>
|
||||
- ''
|
||||
- <gray>Yardıma mı ihtiyacınız var veya sorularınız mı var?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Discord\'u açmak için tıklayın)'>Discord üzerinden bir bilet açın </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(GitHub\'ı açmak için tıklayın)'>veya GitHub üzerinde yeni bir sorun açın.
|
||||
- ''
|
||||
# Ana komutu çalıştırırken gösterilen alt komutların listesi için biçimlendirme
|
||||
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>Sadece oyuncular: </gray>%only_players%<br><gray>Konsol gerekli: </gray>%require_console%<br><gray>İzin: </gray><white>%permission%<br><gray>Diğer isimler: </gray>%aliases%''><dark_aqua>
|
||||
▪ <gray>/sonar %subcommand% <white>%description%'
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>Sadece oyuncular: </gray><only-players><br><gray>Konsol gerekli: </gray><only-console><br><gray>İzin: </gray><white><permission><br><gray>Diğer isimler: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <suggest-subcommand><subcommand></suggest-subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# Alt komutların üzerine gelinen metindeki evet (tick) ve hayır (cross) değerleri için biçimlendirme
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# '/sonar reload' için çeviriler
|
||||
reload:
|
||||
# Bu alt komut için açıklama
|
||||
description: "Tüm konfigürasyonları yeniden yükle"
|
||||
# Birisi Sonar'ı yeniden yüklemeye başladığında gösterilen mesaj
|
||||
start: '%prefix%Sonar yeniden yükleniyor...'
|
||||
start: '<prefix>Sonar yeniden yükleniyor...'
|
||||
# Sonar yeniden yüklendiğinde gösterilen mesaj
|
||||
finish: '%prefix%<green>Sonar başarıyla yeniden yüklendi <gray>(%taken%ms)'
|
||||
finish: '<prefix><green>Sonar başarıyla yeniden yüklendi <gray>(<time-taken>ms)'
|
||||
|
||||
# '/sonar verbose' için çeviriler
|
||||
verbose:
|
||||
# Bu alt komut için açıklama
|
||||
description: "Sonar verbose özelliğini etkinleştirme veya devre dışı bırakma"
|
||||
# Bir oyuncu Sonar detaylı bilgilerini görmeye başladığında gösterilen mesaj
|
||||
subscribed: '%prefix%Artık Sonar detaylı bilgilerini görüntülüyorsunuz.'
|
||||
subscribe: '<prefix>Artık Sonar detaylı bilgilerini görüntülüyorsunuz.'
|
||||
# Bir oyuncu Sonar detaylı bilgilerinden çıktığında gösterilen mesaj
|
||||
unsubscribed: '%prefix%Artık Sonar detaylı bilgilerini görüntülemiyorsunuz.'
|
||||
unsubscribe: '<prefix>Artık Sonar detaylı bilgilerini görüntülemiyorsunuz.'
|
||||
|
||||
# '/sonar notify' için çeviriler
|
||||
notify:
|
||||
# Bu alt komut için açıklama
|
||||
description: "Saldırı bildirimlerini etkinleştirme veya devre dışı bırakma"
|
||||
# Bir oyuncu Sonar saldırı bildirimlerini görmeye başladığında gösterilen mesaj
|
||||
subscribed: '%prefix%Artık Sonar saldırı bildirimlerini görüntülüyorsunuz.'
|
||||
subscribe: '<prefix>Artık Sonar saldırı bildirimlerini görüntülüyorsunuz.'
|
||||
# Bir oyuncu Sonar saldırı bildirimlerinden çıktığında gösterilen mesaj
|
||||
unsubscribed: '%prefix%Artık Sonar saldırı bildirimlerini görüntülemiyorsunuz.'
|
||||
unsubscribe: '<prefix>Artık Sonar saldırı bildirimlerini görüntülemiyorsunuz.'
|
||||
|
||||
# '/sonar blacklist' için çeviriler
|
||||
blacklist:
|
||||
# Bu alt komut için açıklama
|
||||
description: "Kara listeye alınmış IP adreslerini yönetme"
|
||||
# Birisi blacklist'i temizlemeye çalıştığında ama boş olduğunda gösterilen mesaj
|
||||
empty: '%prefix%Kara liste şu anda boş. Bu nedenle, kara listeden hiçbir IP adresi kaldırılmadı.'
|
||||
empty: '<prefix>Kara liste şu anda boş. Bu nedenle, kara listeden hiçbir IP adresi kaldırılmadı.'
|
||||
# Birisi kara listeyi temizlediğinde gösterilen mesaj
|
||||
cleared: '%prefix%Toplamda %removed% IP adres(ler)i başarıyla kara listeden kaldırıldı.'
|
||||
clear: '<prefix>Toplamda <amount> IP adres(ler)i başarıyla kara listeden kaldırıldı.'
|
||||
# Birisi kara listenin boyutunu kontrol ettiğinde gösterilen mesaj
|
||||
size: '%prefix%Kara liste şu anda %amount% IP adres(ler)i içeriyor.'
|
||||
size: '<prefix>Kara liste şu anda <amount> IP adres(ler)i içeriyor.'
|
||||
# Birisi bir IP adresini kara listeye eklediğinde gösterilen mesaj
|
||||
added: '%prefix%Başarıyla %ip% kara listeye eklendi.'
|
||||
add: '<prefix>Başarıyla <ip> kara listeye eklendi.'
|
||||
# Birisi doğrulanmış olarak işaretlenmiş bir IP adresini kara listeye eklediğinde gösterilen mesaj
|
||||
added-warning: '%prefix%<red>Uyarı: <white>%ip% şu anda beyaz listede. Potansiyel sorunları önlemek için IP adresini doğrulanmış oyuncular listesinden kaldırmayı düşünün.'
|
||||
add-warning: '<prefix><red>Uyarı: <white><ip> şu anda beyaz listede. Potansiyel sorunları önlemek için IP adresini doğrulanmış oyuncular listesinden kaldırmayı düşünün.'
|
||||
# Birisi bir IP adresini kara listeden çıkardığında gösterilen mesaj
|
||||
removed: '%prefix%Başarıyla %ip% kara listeden çıkarıldı.'
|
||||
remove: '<prefix>Başarıyla <ip> kara listeden çıkarıldı.'
|
||||
# Birisi bir IP adresini kara listeye eklemeye çalıştığında ama zaten kara listede olduğunda gösterilen mesaj
|
||||
duplicate-ip: '%prefix%Sağladığınız IP adresi zaten kara listede.'
|
||||
ip-duplicate: '<prefix>Sağladığınız IP adresi zaten kara listede.'
|
||||
# Birisi bir IP adresini kara listeden çıkarmaya çalıştığında ama kara listede olmadığında gösterilen mesaj
|
||||
ip-not-found: '%prefix%Sağladığınız IP adresi kara listede değil.'
|
||||
ip-not-found: '<prefix>Sağladığınız IP adresi kara listede değil.'
|
||||
|
||||
# '/sonar verified' için çeviriler
|
||||
verified:
|
||||
# Bu alt komut için açıklama
|
||||
description: "Doğrulanmış IP adreslerini yönetme"
|
||||
# Birisi doğrulanmış oyuncular listesini temizlemeye çalıştığında ama boş olduğunda gösterilen mesaj
|
||||
empty: '%prefix%Doğrulanmış oyuncular listesi şu anda boş. Bu nedenle, hiçbir oyuncu doğrulanmamış olarak işaretlenmedi.'
|
||||
empty: '<prefix>Doğrulanmış oyuncular listesi şu anda boş. Bu nedenle, hiçbir oyuncu doğrulanmamış olarak işaretlenmedi.'
|
||||
# Birisi doğrulanmış oyuncular listesini temizlediğinde gösterilen mesaj
|
||||
cleared: '%prefix%Toplamda %removed% benzersiz oyuncu başarıyla doğrulanmamış olarak işaretlendi.'
|
||||
clear: '<prefix>Toplamda <amount> benzersiz oyuncu başarıyla doğrulanmamış olarak işaretlendi.'
|
||||
# Birisi doğrulanmış oyuncular listesinin boyutunu kontrol ettiğinde gösterilen mesaj
|
||||
size: '%prefix%Şu anda %amount% benzersiz oyuncu doğrulanmış.'
|
||||
size: '<prefix>Şu anda <amount> benzersiz oyuncu doğrulanmış.'
|
||||
# Birisi bir IP adresini doğrulanmamış olarak işaretlediğinde gösterilen mesaj
|
||||
removed: '%prefix%Başarıyla %ip% doğrulanmamış olarak işaretlendi.'
|
||||
remove: '<prefix>Başarıyla <ip> doğrulanmamış olarak işaretlendi.'
|
||||
# Message that is shown when someone verifies an IP address
|
||||
added: '%prefix%Successfully verified %ip%.'
|
||||
add: '<prefix><ip> başarıyla doğrulandı.'
|
||||
# Message that is shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: '%prefix%The IP address and UUID you provided are already verified.'
|
||||
already: '<prefix>Sağladığınız IP adresi ve UUID zaten doğrulanmıştır.'
|
||||
# Birisi bir IP adresini doğrulanmamış olarak işaretlemeye çalıştığında ama doğrulanmamış olduğunda gösterilen mesaj
|
||||
ip-not-found: '%prefix%Sağladığınız IP adresi doğrulanmış olarak işaretlenmemiş.'
|
||||
# Birisi aynı IP adresini iki kez (çift işlem) doğrulanmamış olarak işaretlemeye çalıştığında gösterilen mesaj
|
||||
blocked: '%prefix%Mevcut işlemin bitmesini bekleyin.'
|
||||
ip-not-found: '<prefix>Sağladığınız IP adresi doğrulanmış olarak işaretlenmemiş.'
|
||||
# Birisi bir IP adresini doğrulama geçmişine baktığında gösterilen mesaj
|
||||
history: '<prefix><yellow><ip> için önceki UUID''ler:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# '/sonar statistics' için çeviriler
|
||||
statistics:
|
||||
# Bu alt komut için açıklama
|
||||
description: "Bu sunucunun oturum istatistiklerini göster"
|
||||
# İstatistikleri görüntülerken her şeyin üzerinde gösterilen bilgilendirme mesajı
|
||||
header: 'Bu oturum için %type% istatistikleri gösteriliyor:'
|
||||
header: 'Bu oturum için <statistics-type> istatistikleri gösteriliyor:'
|
||||
# Message that is shown when a player tries viewing an unknown statistic
|
||||
unknown-type: '%prefix%<red>Bilinmeyen istatistik türü! Mevcut istatistikler: <gray>%statistics%'
|
||||
unknown-type: '<prefix><red>Bilinmeyen istatistik türü! Mevcut istatistikler: <gray><statistics>'
|
||||
# Genel istatistik mesajının biçimi
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>Doğrulanmış IP adresleri: <white>%verified%'
|
||||
- ' <dark_aqua>▪ <gray>Doğrulanan IP adresleri: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Kara listedeki IP adresleri: <white>%blacklisted%'
|
||||
- ' <dark_aqua>▪ <gray>Şu anda sıraya alınmış girişler: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam benzersiz olmayan girişler: <white>%total_joins%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam doğrulama denemeleri: <white>%total_attempts%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam başarısız doğrulama denemesi: <white>%total_failed%'
|
||||
- ' <dark_aqua>▪ <gray>Sunucu çalışma süresi: <white>%uptime%'
|
||||
- ' <dark_aqua>▪ <gray>Doğrulanmış IP adresleri: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>Doğrulanan IP adresleri: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Kara listedeki IP adresleri: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>Şu anda sıraya alınmış girişler: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>Toplam benzersiz olmayan girişler: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>Toplam doğrulama denemeleri: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>Toplam başarısız doğrulama denemesi: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>Sunucu çalışma süresi: <white><server-uptime>'
|
||||
# İşlemci istatistik mesajının biçimi
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>Şu anda işlemci CPU kullanımı: <white>%process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Şu anda sistem CPU kullanımı: <white>%system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Çekirdek başına işlem CPU kullanımı: <white>%average_process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Çekirdek başına sistem CPU kullanımı: <white>%average_system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>Genel sistem yük ortalaması: <white>%load_average%%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam sanal CPU miktarı: <white>%virtual_cores%'
|
||||
- ' <dark_aqua>▪ <gray>Şu anda işlemci CPU kullanımı: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Şu anda sistem CPU kullanımı: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Çekirdek başına işlem CPU kullanımı: <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Çekirdek başına sistem CPU kullanımı: <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam sanal CPU miktarı: <white><virtual-core-count>'
|
||||
# Hafıza istatistik mesajının biçimi
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>Toplam boş hafıza: <white>%free_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam kullanılan hafıza: <white>%used_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam maksimum bellek: <white>%max_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam ayrılmış bellek: <white>%total_memory%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam boş hafıza: <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Toplam kullanılan hafıza: <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Toplam maksimum bellek: <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>Toplam ayrılmış bellek: <white><total-memory>'
|
||||
# İnternet istatistik mesajının biçimi
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>Mevcut gelen kullanılan bant genişliği: <white>%incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Mevcut giden kullanılan bant genişliği: <white>%outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam gelen kullanılan bant genişliği: <white>%ttl_incoming%'
|
||||
- ' <dark_aqua>▪ <gray>Toplam giden kullanılan bant genişliği: <white>%ttl_outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>Mevcut gelen kullanılan bant genişliği: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Mevcut giden kullanılan bant genişliği: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>Toplam gelen kullanılan bant genişliği: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>Toplam giden kullanılan bant genişliği: <white><outgoing-traffic-ttl>'
|
||||
|
||||
# '/sonar dump' için çeviriler
|
||||
dump:
|
||||
# Bu alt komut için açıklama
|
||||
description: "Geliştirici bilgilerini yazdır"
|
||||
# Konsola gönderilen bilginin biçimi
|
||||
log: "Oluşturulan döküm: <dumped-json-data>"
|
||||
|
||||
# Doğrulama işlemi sırasında tüm mesajlar için çeviriler
|
||||
verification:
|
||||
# Doğrulama sırasında tüm hata ayıklama mesajları için çeviriler
|
||||
logs:
|
||||
# Sunucuya yeni bir oyuncu katıldığında konsola kaydedilen mesaj
|
||||
connection: '%name%%ip% (%protocol%) sunucuya bağlandı.'
|
||||
connection: '<username><ip> (<protocol>) sunucuya bağlandı.'
|
||||
# Bir oyuncu doğrulamada başarısız olduğunda konsola kaydedilen mesaj
|
||||
failed: '%ip% (%protocol%) bot korumasında başarısız oldu: %reason%'
|
||||
failed: '<ip> (<protocol>) bot korumasında başarısız oldu: <reason>'
|
||||
# Bir oyuncu kara listeye alındığında konsola kaydedilen mesaj
|
||||
blacklisted: '%ip% (%protocol%) çok fazla doğrulama denemesinden dolayı kara listeye alındı'
|
||||
blacklisted: '<ip> (<protocol>) çok fazla doğrulama denemesinden dolayı kara listeye alındı'
|
||||
# Bir oyuncu doğrulandığında konsola kaydedilen mesaj
|
||||
successful: '%name% başarıyla doğrulandı! (%time%s).'
|
||||
successful: '<username> başarıyla doğrulandı! (<time-taken>s).'
|
||||
|
||||
# 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-code: '<prefix><green>Haritada gösterilen kodu lütfen sohbete 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ış kodu 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%'
|
||||
- <gold>Bunun bir hata olduğunu mu düşünüyorsunuz? <gray><support-url>
|
||||
- '<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%'
|
||||
- <gold>Giriş yapma hakkında yardıma mı ihtiyacın var? <gray><support-url>
|
||||
- '<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%'
|
||||
- <gold>Giriş yapma hakkında yardıma mı ihtiyacın var? <gray><support-url>
|
||||
- '<footer>'
|
||||
|
||||
# Sonar'ın actionbar verbose mesajları için çeviriler
|
||||
verbose:
|
||||
layout:
|
||||
# Sonar verbose için yer tutucular
|
||||
# Placeholders:
|
||||
# - '%queued%' Sırada bekleyen bağlantı sayısı
|
||||
# - '%verifying%' Doğrulanıyor olan bağlantı sayısı
|
||||
# - '%blacklisted%' Kara listeye alınmış IP adresi sayısı
|
||||
# - '%total-joins%' Toplam denenen katılım sayısı
|
||||
# - '%verify-total%' Toplam doğrulama denemesi sayısı
|
||||
# - '%verify-success%' Doğrulanmış IP adresi sayısı
|
||||
# - '%verify-failed%' Başarısız doğrulama sayısı
|
||||
# - '%logins-per-second%' Saniye başına giriş sayısı
|
||||
# - '%connections-per-second%' Saniye başına bağlantı sayısı
|
||||
# - '%attack-duration%' Şu anki bağlantının süresi
|
||||
# - '%incoming-traffic%': Saniyedeki gelen bant genişliği kullanımı
|
||||
# - '%outgoing-traffic%': Saniyedeki giden bant genişliği kullanımı
|
||||
# - '%incoming-traffic-ttl%': Toplam gelen bant genişliği kullanımı
|
||||
# - '%outgoing-traffic-ttl%': Toplam giden bant genişliği kullanımı
|
||||
# - '%used-memory%' İşlemin kullanılan bellek miktarı
|
||||
# - '%total-memory%' İşlemin toplam bellek miktarı
|
||||
# - '%max-memory%' İşlemin maksimum bellek miktarı
|
||||
# - '%free-memory%' İşlemin boş bellek miktarı
|
||||
# - '%animation%' Özelleştirilebilir animasyonlu simge
|
||||
# Translations for Sonar's normal verbose output
|
||||
normal: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Girişler/sn
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Doğrulama <white>%verifying% <dark_aqua>|
|
||||
<green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
# Layout for Sonar's actionbar verbose during an attack
|
||||
attack: '%prefix%<gray>CPS <white>%connections-per-second% <dark_aqua>╺ <gray>Girişler/sn
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>Sıra <white>%queued% <dark_aqua>╺
|
||||
<gray>Doğrulama <white>%verifying% <dark_aqua>╺ <gray>Saldırı süresi <white>%attack-duration%
|
||||
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
# Animation for the action bar
|
||||
# Alternatives:
|
||||
# Sonar'ın normal verbose çıktısı için çeviriler
|
||||
normal: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Girişler/sn <white><logins-per-second> <dark_aqua>╺ <gray>Doğrulama <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Sonar'ın normal verbose çıktısı için çeviriler bir saldırı sırasında
|
||||
attack: '<prefix><gray>CPS <white><connections-per-second> <dark_aqua>╺ <gray>Girişler/sn <white><logins-per-second> <dark_aqua>╺ <gray>Sıra <white><queued> <dark_aqua>╺ <gray>Doğrulama <white><verifying> <dark_aqua>╺ <gray>Saldırı süresi <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# Actionbar için animasyon
|
||||
# Alternatifler:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
# - ⬈, ⬊, ⬋, ⬉
|
||||
animation:
|
||||
@@ -277,22 +268,21 @@ verbose:
|
||||
- ◞
|
||||
- ◟
|
||||
|
||||
# Translations for Sonar's attack notification messages
|
||||
# Sonar'ın saldırı bildirim mesajları için çeviriler
|
||||
notifications:
|
||||
# Layout for Sonar's attack notification title
|
||||
# Sonar'ın saldırı bildirimi başlığı için düzen
|
||||
title: <yellow><b>Sonar<reset>
|
||||
# Layout for Sonar's attack notification subtitle
|
||||
# Sonar'ın saldırı bildirimi altyazısı için düzen
|
||||
subtitle: <gray>Bir bot saldırısı tespit edildi
|
||||
# Layout for Sonar's attack notification in chat
|
||||
# Sohbette Sonar'ın saldırı bildirimi için düzen
|
||||
chat:
|
||||
- ''
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>Bir bot saldırısı tespit edildi.
|
||||
- ' <dark_aqua>▪ <gray>Saniye başına giriş sayısı: <white>%logins-per-second%'
|
||||
- ' <dark_aqua>▪ <gray>Doğrulanıyor olan oyuncular: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>Sırada bekleyen oyuncular: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>Saniye başına giriş sayısı: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>Doğrulanıyor olan oyuncular: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>Sırada bekleyen oyuncular: <white><queued>'
|
||||
- ''
|
||||
- <gray>Daha fazla bilgi görmek için <white><click:run_command:'/sonar verbose'><hover:show_text:'(Çalıştırmak için tıkla)'>/sonar verbose</hover></click><gray> veya <white><click:run_command:'/sonar
|
||||
stats'><hover:show_text:'(Çalıştırmak için tıkla)'>/sonar stats</hover></click><gray> kullanın.
|
||||
- <gray>Daha fazla bilgi görmek için <white><click:run_command:'/sonar verbose'><hover:show_text:'(Çalıştırmak için tıkla)'>/sonar verbose</hover></click><gray> veya <white><click:run_command:'/sonar stats'><hover:show_text:'(Çalıştırmak için tıkla)'>/sonar stats</hover></click><gray> kullanın.
|
||||
- ''
|
||||
@@ -8,269 +8,256 @@
|
||||
# | $$$$$$/| $$$$$$/| $$ | $$| $$$$$$$| $$
|
||||
# \______/ \______/ |__/ |__/ \_______/|__/
|
||||
#
|
||||
# 有问题或需要帮助? https://jonesdev.xyz/discord
|
||||
# 需要帮助吗? https://jonesdev.xyz/discord
|
||||
# 源代码: https://github.com/jonesdevelopment/sonar
|
||||
# Copyright Jones Development and Sonar Contributors, licensed under GPL-3.0
|
||||
#
|
||||
|
||||
# %prefix% 占位符
|
||||
# <prefix> 占位符
|
||||
prefix: <b><yellow>Sonar</b> <gray>» <white>
|
||||
# %support-url% 占位符
|
||||
# <support-url> 占位符
|
||||
support-url: https://jonesdev.xyz/discord/
|
||||
# %header% 占位符. (支持多行)
|
||||
# <header> 占位符. (支持多行)
|
||||
header:
|
||||
- <b><yellow>Sonar</b><reset>
|
||||
- ''
|
||||
# %footer% 占位符. (支持多行)
|
||||
# <footer> 占位符. (支持多行)
|
||||
footer:
|
||||
- <gray>如果您认为这是一个错误, 请联系服务器管理员.
|
||||
# 当玩家尝试加入但有太多在线玩家同时使用者玩家的 IP 地址时显示的踢出消息.
|
||||
too-many-online-per-ip:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>您所使用的 IP 地址已经无法再登录更多账户了.
|
||||
- '%footer%'
|
||||
- '<footer>'
|
||||
|
||||
# 有关命令的语言配置.
|
||||
commands:
|
||||
# 当玩家尝试在没有权限的情况下运行 /sonar 时显示的消息
|
||||
no-permission: '%prefix%<red>您没有足够的权限使用该命令.'
|
||||
no-permission: '<prefix><red>您没有足够的权限使用该命令.'
|
||||
# 当命令用法不正确时显示的消息
|
||||
incorrect-usage: '%prefix%<red>用法: /sonar %usage%'
|
||||
incorrect-usage: '<prefix><red>用法: /sonar <subcommand-usage>'
|
||||
# 当提供无效的 IP 地址时显示的消息
|
||||
invalid-ip-address: '%prefix%无法正确识别您提供的IP地址 请检查输入是否有误.'
|
||||
invalid-ip-address: '<prefix>无法正确识别您提供的IP地址 请检查输入是否有误.'
|
||||
# 当控制台尝试执行只有玩家能执行的命令时显示的消息
|
||||
player-only: '%prefix%<red>只有玩家才可以执行该命令.'
|
||||
player-only: '<prefix><red>只有玩家才可以执行该命令.'
|
||||
# 当玩家尝试执行只有控制台能执行的命令时显示的消息
|
||||
console-only: '%prefix%<red>出于安全考虑 只允许通过控制台执行该命令.'
|
||||
# 玩家执行Sonar命令的速度过快时显示的消息
|
||||
cool-down: '%prefix%<red>您只能每 0.5 秒执行一次此命令.'
|
||||
cool-down-left: '%prefix%<red>请等待 <bold>%time-left% 秒<reset><red> 后执行该命令.'
|
||||
console-only: '<prefix><red>出于安全考虑 只允许通过控制台执行该命令.'
|
||||
# 当玩家没有权限执行某个子命令时显示的消息
|
||||
subcommand-no-permission: '%prefix%<red>您没有足够的权限使用该子命令. <gray>(%permission%)'
|
||||
subcommand-no-permission: '<prefix><red>您没有足够的权限使用该子命令. <gray>(<permission>)'
|
||||
|
||||
# 根命令
|
||||
main:
|
||||
# 执行根命令时显示在所有输出内容(由该命令输出的消息)上方的消息
|
||||
header:
|
||||
- <yellow>正在在 %platform% 平台上运行 <b>Sonar</b> %version%.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) %copyright_year%
|
||||
Jones Development and Sonar Contributors</click>
|
||||
- <yellow>正在在 <platform> 平台上运行 <b>Sonar</b> <version>.
|
||||
- <gray><click:open_url:'https://github.com/jonesdevelopment/sonar'>(C) <copyright-year> Jones Development and Sonar Contributors</click>
|
||||
- ''
|
||||
- <gray>有问题或需要帮助?
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Click
|
||||
to open Discord)'>在我们的Discord上打开一张ticket </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Click
|
||||
to open GitHub)'>或在Github打开新的问题.
|
||||
- <white><click:open_url:'https://jonesdev.xyz/discord/'><hover:show_text:'(Click to open Discord)'>在我们的Discord上打开一张ticket </hover></click><click:open_url:'https://github.com/jonesdevelopment/sonar/issues'><hover:show_text:'(Click to open GitHub)'>或在Github打开新的问题.
|
||||
- ''
|
||||
# 执行根命令时 对于子命令的描述.
|
||||
subcommands: '<click:suggest_command:''/sonar %subcommand% ''><hover:show_text:''<gray>只有玩家可以执行该命令:
|
||||
</gray>%only_players%<br><gray>只有控制台可以执行该命令: </gray>%require_console%<br><gray>需要的权限:
|
||||
</gray><white>%permission%<br><gray>别名: </gray>%aliases%''><dark_aqua>
|
||||
▪ <gray>/sonar %subcommand% <white>%description%'
|
||||
subcommands: '<suggest-subcommand><hover:show_text:''<gray>只有玩家可以执行该命令: </gray><only-players><br><gray>只有控制台可以执行该命令: </gray><only-console><br><gray>需要的权限: </gray><white><permission><br><gray>别名: </gray><aliases>''><dark_aqua> ▪ <gray>/sonar <subcommand> <white><description></hover></suggest-subcommand>'
|
||||
# 子命令悬停文本中 "是"(tick)和 "否"(cross)值的格式设置
|
||||
tick: '<green>✔</green>'
|
||||
cross: '<red>✗</red>'
|
||||
|
||||
# 重新加载命令
|
||||
reload:
|
||||
# 该子命令的描述
|
||||
description: "重新加载所有配置"
|
||||
# 当有人开始重载插件时显示的消息
|
||||
start: '%prefix%正在重新加载 Sonar...'
|
||||
start: '<prefix>正在重新加载 Sonar...'
|
||||
# 插件完成重载时显示的消息
|
||||
finish: '%prefix%<green>成功重新加载配置文件. <gray>(花费 %taken% 毫秒)'
|
||||
finish: '<prefix><green>成功重新加载配置文件. <gray>(花费 <time-taken> 毫秒)'
|
||||
|
||||
# '/sonar verbose' 命令的消息
|
||||
verbose:
|
||||
# 该子命令的描述
|
||||
description: "启用或禁用 Sonar 冗余"
|
||||
# 玩家订阅详细信息时显示的消息
|
||||
subscribed: '%prefix%已为您显示 Sonar 的详细信息.'
|
||||
subscribe: '<prefix>已为您显示 Sonar 的详细信息.'
|
||||
# 玩家取消订阅详细信息时显示的消息
|
||||
unsubscribed: '%prefix%不再为您显示 Sonar 的详细信息.'
|
||||
unsubscribe: '<prefix>不再为您显示 Sonar 的详细信息.'
|
||||
|
||||
# '/sonar notify' 命令的消息
|
||||
notify:
|
||||
# 该子命令的描述
|
||||
description: "启用或禁用攻击通知"
|
||||
# 玩家订阅攻击通知时显示的消息
|
||||
subscribed: '%prefix%已订阅攻击通知. 当服务器受到攻击时将会提醒您.'
|
||||
subscribe: '<prefix>已订阅攻击通知. 当服务器受到攻击时将会提醒您.'
|
||||
# 玩家取消订阅攻击通知时显示的消息
|
||||
unsubscribed: '%prefix%已退订攻击通知'
|
||||
unsubscribe: '<prefix>已退订攻击通知'
|
||||
|
||||
# '/sonar blacklist' 命令的消息
|
||||
blacklist:
|
||||
# 该子命令的描述
|
||||
description: "管理黑名单 IP 地址"
|
||||
# 当尝试清理黑名单但黑名单中没有任何 IP 地址显示的消息
|
||||
empty: '%prefix%黑名单中没有任何 IP 地址.'
|
||||
empty: '<prefix>黑名单中没有任何 IP 地址.'
|
||||
# 当成功清理黑名单时显示的消息
|
||||
cleared: '%prefix%您已成功从黑名单中删除 %removed% 个 IP 地址.'
|
||||
clear: '<prefix>您已成功从黑名单中删除 <amount> 个 IP 地址.'
|
||||
# 当查看黑名单的大小时显示的消息
|
||||
size: '%prefix%黑名单中目前有 %amount% 个 IP 地址.'
|
||||
size: '<prefix>黑名单中目前有 <amount> 个 IP 地址.'
|
||||
# 当手动将一个 IP 地址添加到黑名单时显示的消息
|
||||
added: '%prefix%成功将 IP 地址 %ip% 列入黑名单.'
|
||||
add: '<prefix>成功将 IP 地址 <ip> 列入黑名单.'
|
||||
# 当尝试将白名单中的 IP 地址添加到黑名单中时显示的警告消息
|
||||
added-warning: '%prefix%<red>警告: <white>IP 地址 %ip% 在白名单中. 添加到黑名单则意味着它将被同时从白名单中移除.'
|
||||
add-warning: '<prefix><red>警告: <white>IP 地址 <ip> 在白名单中. 添加到黑名单则意味着它将被同时从白名单中移除.'
|
||||
# 当尝试从黑名单中移除一个 IP 地址时显示的消息
|
||||
removed: '%prefix%成功将 IP 地址 %ip% 移出黑名单.'
|
||||
remove: '<prefix>成功将 IP 地址 <ip> 移出黑名单.'
|
||||
# 当尝试向黑名单添加一个已经在黑名单中的 IP 地址时显示的消息
|
||||
duplicate-ip: '%prefix%该 IP 地址已经在黑名单里了.'
|
||||
ip-duplicate: '<prefix>该 IP 地址已经在黑名单里了.'
|
||||
# 当尝试移除一个不在黑名单中的 IP 地址时显示的消息
|
||||
ip-not-found: '%prefix%您提供的 IP 地址并不在黑名单中.'
|
||||
ip-not-found: '<prefix>您提供的 IP 地址并不在黑名单中.'
|
||||
|
||||
# '/sonar verified' 命令的消息
|
||||
verified:
|
||||
# 该子命令的描述
|
||||
description: "管理已验证的 IP 地址"
|
||||
# 当尝试清除白名单但为空时显示的消息
|
||||
empty: '%prefix%白名单中没有任何玩家.'
|
||||
empty: '<prefix>白名单中没有任何玩家.'
|
||||
# 当成功清理白名单时显示的消息
|
||||
cleared: '%prefix%您已成功从白名单中删除 %removed% 个玩家.'
|
||||
clear: '<prefix>您已成功从白名单中删除 <amount> 个玩家.'
|
||||
# 当查看白名单的大小时显示的消息
|
||||
size: '%prefix%白名单中目前有 %amount% 个已验证的玩家.'
|
||||
size: '<prefix>白名单中目前有 <amount> 个已验证的玩家.'
|
||||
# 当成功将一个 IP 地址内所有已验证的玩家移出白名单时显示的消息
|
||||
removed: '%prefix%成功将所有使用 IP 地址 (%ip%) 的玩家移出白名单.'
|
||||
remove: '<prefix>成功将所有使用 IP 地址 (<ip>) 的玩家移出白名单.'
|
||||
# 当手动将一个 IP 地址添加到白名单中显示的消息
|
||||
added: '%prefix%成功将 IP 地址 %ip% 列入白名单.'
|
||||
add: '<prefix>成功将 IP 地址 <ip> 列入白名单.'
|
||||
# 当尝试添加一个已经在白名单中的 IP 地址和玩家的UUID 时显示的消息
|
||||
already: '%prefix%您提供的 IP 地址和玩家的UUID已经在白名单中了.'
|
||||
already: '<prefix>您提供的 IP 地址和玩家的UUID已经在白名单中了.'
|
||||
# 当尝试移除一个不在白名单中的 IP 地址时显示的消息
|
||||
ip-not-found: '%prefix%您提供的 IP 地址不在白名单中.'
|
||||
# 当操作冲突时(例如同时将一个 IP 地址移除白名单)显示的消息
|
||||
blocked: '%prefix%请等待当前操作完成.'
|
||||
ip-not-found: '<prefix>您提供的 IP 地址不在白名单中.'
|
||||
# 当尝试移除一个查询 IP 地址的验证历史时显示的信息
|
||||
history: '<prefix><yellow>以前的 UUID 用于 <ip>:'
|
||||
history-entry: ' <gray>▪ <white><uuid>'
|
||||
|
||||
# '/sonar statistics' 命令的消息
|
||||
statistics:
|
||||
# 该子命令的描述
|
||||
description: "显示该服务器的会话统计信息"
|
||||
# 执行该命令时显示在所有输出内容(由该命令输出的消息)上方的消息
|
||||
header: '%prefix%当前会话中的 %type% 类型的统计信息:'
|
||||
header: '<prefix>当前会话中的 <statistics-type> 类型的统计信息:'
|
||||
# 当查看一个未知的统计信息时显示的消息
|
||||
unknown-type: '%prefix%<red>未知的统计信息! 可用的统计信息: <gray>%statistics%'
|
||||
unknown-type: '<prefix><red>未知的统计信息! 可用的统计信息: <gray><statistics>'
|
||||
# 默认统计信息的消息
|
||||
general:
|
||||
- ' <dark_aqua>▪ <gray>白名单中的 IP 地址数量: <white>%verified%'
|
||||
- ' <dark_aqua>▪ <gray>正在进行验证的 IP 地址数量: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>黑名单中的 IP 地址数量: <white>%blacklisted%'
|
||||
- ' <dark_aqua>▪ <gray>缓冲队列中的等待登录的数量: <white>%queued%'
|
||||
- ' <dark_aqua>▪ <gray>玩家加入次数: <white>%total_joins%'
|
||||
- ' <dark_aqua>▪ <gray>总验证次数: <white>%total_attempts%'
|
||||
- ' <dark_aqua>▪ <gray>验证失败的次数: <white>%total_failed%'
|
||||
- ' <dark_aqua>▪ <gray>服务器当前会话的运行时间: <white>%uptime%'
|
||||
- ' <dark_aqua>▪ <gray>白名单中的 IP 地址数量: <white><verified>'
|
||||
- ' <dark_aqua>▪ <gray>正在进行验证的 IP 地址数量: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>黑名单中的 IP 地址数量: <white><blacklisted>'
|
||||
- ' <dark_aqua>▪ <gray>缓冲队列中的等待登录的数量: <white><queued>'
|
||||
- ' <dark_aqua>▪ <gray>玩家加入次数: <white><total-joins>'
|
||||
- ' <dark_aqua>▪ <gray>总验证次数: <white><total-attempts>'
|
||||
- ' <dark_aqua>▪ <gray>验证失败的次数: <white><total-failed>'
|
||||
- ' <dark_aqua>▪ <gray>服务器当前会话的运行时间: <white><server-uptime>'
|
||||
# CPU 统计信息
|
||||
cpu:
|
||||
- ' <dark_aqua>▪ <gray>当前进程的 CPU 使用率: <white>%process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>当前系统的 CPU 使用率: <white>%system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>当前进程的多核平均使用率: <white>%average_process_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>当前系统的多核平均使用率: <white>%average_system_cpu%%'
|
||||
- ' <dark_aqua>▪ <gray>系统平均负载: <white>%load_average%%'
|
||||
- ' <dark_aqua>▪ <gray>虚拟核心数量: <white>%virtual_cores%'
|
||||
- ' <dark_aqua>▪ <gray>当前进程的 CPU 使用率: <white><process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>当前系统的 CPU 使用率: <white><system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>当前进程的多核平均使用率: <white><average-process-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>当前系统的多核平均使用率: <white><average-system-cpu>%'
|
||||
- ' <dark_aqua>▪ <gray>可使用的处理器核心数量: <white><virtual-core-count>'
|
||||
# 内存统计信息
|
||||
memory:
|
||||
- ' <dark_aqua>▪ <gray>可用内存: <white>%free_memory%'
|
||||
- ' <dark_aqua>▪ <gray>已使用的内存: <white>%used_memory%'
|
||||
- ' <dark_aqua>▪ <gray>最大可用内存: <white>%max_memory%'
|
||||
- ' <dark_aqua>▪ <gray>已分配的内存: <white>%total_memory%'
|
||||
- ' <dark_aqua>▪ <gray>可用内存: <white><free-memory>'
|
||||
- ' <dark_aqua>▪ <gray>已使用的内存: <white><used-memory>'
|
||||
- ' <dark_aqua>▪ <gray>最大可用内存: <white><max-memory>'
|
||||
- ' <dark_aqua>▪ <gray>已分配的内存: <white><total-memory>'
|
||||
# 网络统计信息
|
||||
network:
|
||||
- ' <dark_aqua>▪ <gray>当前下行: <white>%incoming%'
|
||||
- ' <dark_aqua>▪ <gray>当前上行: <white>%outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>下行总量: <white>%ttl_incoming%'
|
||||
- ' <dark_aqua>▪ <gray>上行总量: <white>%ttl_outgoing%'
|
||||
- ' <dark_aqua>▪ <gray>当前下行: <white><incoming-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>当前上行: <white><outgoing-traffic>'
|
||||
- ' <dark_aqua>▪ <gray>下行总量: <white><incoming-traffic-ttl>'
|
||||
- ' <dark_aqua>▪ <gray>上行总量: <white><outgoing-traffic-ttl>'
|
||||
|
||||
# '/sonar dump' 命令的消息
|
||||
dump:
|
||||
# 该子命令的描述
|
||||
description: "打印开发人员信息"
|
||||
# 发送到控制台的信息格式
|
||||
log: "生成信息: <dumped-json-data>"
|
||||
|
||||
# 验证过程中所有消息的翻译
|
||||
verification:
|
||||
# 验证过程中向控制台打印的调试信息
|
||||
logs:
|
||||
# 当玩家进入服务器并验证时显示的消息
|
||||
connection: '%name%%ip% (%protocol%) 连接到了服务器.'
|
||||
connection: '<username><ip> (<protocol>) 连接到了服务器.'
|
||||
# 当玩家未通过检查时打印的消息
|
||||
failed: '%ip% (%protocol%) 由于原因 %reason% 无法通过检查.'
|
||||
failed: '<ip> (<protocol>) 由于原因 <reason> 无法通过检查.'
|
||||
# 当玩家被自动列入黑名单时打印的消息
|
||||
blacklisted: '%ip% (%protocol%) 因太多次无法完成验证而被列入黑名单.'
|
||||
blacklisted: '<ip> (<protocol>) 因太多次无法完成验证而被列入黑名单.'
|
||||
# 当玩家完成验证时打印的消息
|
||||
successful: '%name% 成功通过了验证 (耗时 %time% 秒!).'
|
||||
successful: '<username> 成功通过了验证 (耗时 <time-taken> 秒!).'
|
||||
|
||||
# 当验证中的玩家需要解决验证码时显示的消息
|
||||
captcha:
|
||||
# 玩家需要输入验证码答案时显示的消息
|
||||
enter-code: '%prefix%<green>请在聊天框中发送地图上的验证码.'
|
||||
enter-code: '<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%'
|
||||
- <red>有另一位玩家正在使用您当前的 IP 地址进行验证
|
||||
- '<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%'
|
||||
- <gold>您认为您不应该被禁止? <gray><support-url>
|
||||
- '<footer>'
|
||||
# 当玩家尝试加入 但他们的用户名包含不允许的字符时显示的踢出消息
|
||||
invalid-username:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>您的用户名包含无效的字符. 请更改您的用户名 然后再试.
|
||||
- '%footer%'
|
||||
- '<footer>'
|
||||
# 当玩家使用列入黑名单的版本加入时显示的踢出消息
|
||||
blacklisted-protocol:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>您当前所使用的协议/客户端版本已被服务器禁止
|
||||
- <gold>需要帮助以便您能登录服务器? <gray>%support-url%
|
||||
- '%footer%'
|
||||
- <gold>需要帮助以便您能登录服务器? <gray><support-url>
|
||||
- '<footer>'
|
||||
# 当玩家验证成功时显示的踢出消息
|
||||
success:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <green>您已成功通过验证
|
||||
- <white>请重新连接以加入服务器
|
||||
# 当玩家未能通过验证时显示的消息
|
||||
failed:
|
||||
- '%header%'
|
||||
- '<header>'
|
||||
- <red>无法验证您是否是机器人
|
||||
- <gray>请等待几秒后重新加入服务器以重新验证
|
||||
- <gold>需要帮助? <gray>%support-url%
|
||||
- '%footer%'
|
||||
- <gold>需要帮助以便您能登录服务器? <gray><support-url>
|
||||
- '<footer>'
|
||||
|
||||
# 状态栏详细信息
|
||||
verbose:
|
||||
layout:
|
||||
# 详细信息状态栏样式
|
||||
# 占位符:
|
||||
# - '%queued%' 正在排队的连接数数量
|
||||
# - '%verifying%' 正在进行验证的连接数数量
|
||||
# - '%blacklisted%' 黑名单大小
|
||||
# - '%total-joins%' 玩家加入次数
|
||||
# - '%verify-total%' 已执行的验证次数
|
||||
# - '%verify-success%' 成功验证的 IP 的数量
|
||||
# - '%verify-failed%' 验证失败的次数
|
||||
# - '%logins-per-second%' 每秒登录服务器次数
|
||||
# - '%connections-per-second%' 每秒接受连接次数
|
||||
# - '%attack-duration%' 攻击持续时间
|
||||
# - '%incoming-traffic%' 每秒下行带宽
|
||||
# - '%outgoing-traffic%' 每秒上行带宽
|
||||
# - '%incoming-traffic-ttl%' 已使用的下行带宽
|
||||
# - '%outgoing-traffic-ttl%' 已使用的上行带宽
|
||||
# - '%used-memory%' 已使用的内存
|
||||
# - '%total-memory%' 已分配的内存
|
||||
# - '%max-memory%' 最大可用内存
|
||||
# - '%free-memory%' 可用内存
|
||||
# - '%animation%' 可自定义的动画符号
|
||||
# 默认的状态栏
|
||||
normal: '%prefix%<gray>每秒连接数 <white>%connections-per-second% <dark_aqua>╺ <gray>每秒登录次数
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>正在验证的连接数 <white>%verifying% <dark_aqua>|
|
||||
<green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
normal: '<prefix><gray>每秒连接数 <white><connections-per-second> <dark_aqua>╺ <gray>每秒登录次数 <white><logins-per-second> <dark_aqua>╺ <gray>正在验证的连接数 <white><verifying> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# 遭受攻击时的状态栏
|
||||
attack: '%prefix%<gray>每秒连接数 <white>%connections-per-second% <dark_aqua>╺ <gray>每秒登录次数
|
||||
<white>%logins-per-second% <dark_aqua>╺ <gray>排队连接数 <white>%queued% <dark_aqua>╺
|
||||
<gray>正在验证的连接数 <white>%verifying% <dark_aqua>╺ <gray>攻击持续时间 <white>%attack-duration%
|
||||
<dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s <green><bold>%animation%<reset>'
|
||||
attack: '<prefix><gray>每秒连接数 <white><connections-per-second> <dark_aqua>╺ <gray>每秒登录次数 <white><logins-per-second> <dark_aqua>╺ <gray>排队连接数 <white><queued> <dark_aqua>╺ <gray>正在验证的连接数 <white><verifying> <dark_aqua>╺ <gray>攻击持续时间 <white><attack-duration> <dark_aqua>| <green>⬆ <white><outgoing-traffic>/s <red>⬇ <white><incoming-traffic>/s <green><bold><animation>'
|
||||
# 动画占位符
|
||||
# 其它预设:
|
||||
# - ▙, ▛, ▜, ▟
|
||||
@@ -286,18 +273,16 @@ notifications:
|
||||
# 标题
|
||||
title: <yellow><b>Sonar<reset>
|
||||
# 子标题
|
||||
subtitle: <gray>检测到活动的机器人攻击..
|
||||
subtitle: <gray>检测到机器人攻击..
|
||||
# 聊天消息
|
||||
chat:
|
||||
- ''
|
||||
- <yellow><b>Sonar<reset>
|
||||
- ''
|
||||
- <gray>检测到活动的机器人攻击
|
||||
- ' <dark_aqua>▪ <gray>每秒登录次数: <white>%logins-per-second%'
|
||||
- ' <dark_aqua>▪ <gray>正在验证的连接数: <white>%verifying%'
|
||||
- ' <dark_aqua>▪ <gray>排队连接数: <white>%queued%'
|
||||
- <gray>检测到机器人攻击
|
||||
- ' <dark_aqua>▪ <gray>每秒登录次数: <white><logins-per-second>'
|
||||
- ' <dark_aqua>▪ <gray>正在验证的连接数: <white><verifying>'
|
||||
- ' <dark_aqua>▪ <gray>排队连接数: <white><queued>'
|
||||
- ''
|
||||
- <gray>使用 <white><click:run_command:'/sonar verbose'><hover:show_text:'(
|
||||
- 点击执行)'>/sonar verbose</hover></click><gray> 或 <white><click:run_command:'/sonar
|
||||
stats'><hover:show_text:'(点击执行)'>/sonar stats</hover></click><gray> 来查看更多信息.
|
||||
- <gray>使用 <white><click:run_command:'/sonar verbose'><hover:show_text:'(点击执行)'>/sonar verbose</hover></click><gray> 或 <white><click:run_command:'/sonar stats'><hover:show_text:'(点击执行)'>/sonar stats</hover></click><gray> 来查看更多信息.
|
||||
- ''
|
||||
Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user