Skip to content

Commit

Permalink
Implemented /mute, /ban, /unmute und /unban commands
Browse files Browse the repository at this point in the history
API: Change return type of punishing methods to CompletableFuture<...>, thus exceptions are no longer swallowed and be easily checked by a handler.
MiniMessage is now being used to serialize reasons when sending over messaging channels
Fixed some smaller issues
  • Loading branch information
JvstvsHD committed Jul 26, 2024
1 parent 5aadb59 commit 0c3e803
Show file tree
Hide file tree
Showing 22 changed files with 482 additions and 55 deletions.
7 changes: 5 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ sqlite = { group = "org.xerial", name = "sqlite-jdbc", version.ref = "sqlite" }
mysql = { group = "com.mysql", name = "mysql-connector-j", version.ref = "mysql" }
adventure-api = { group = "net.kyori", name = "adventure-api", version.ref = "adventure" }
adventure-text-minimessage = { group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure" }
adventure-text-serializer-plain = { group = "net.kyori", name = "adventure-text-serializer-plain", version.ref = "adventure" }
cloud-core = { group = "org.incendo", name = "cloud-core", version.ref = "cloud" }
cloud-annotations = { group = "org.incendo", name = "cloud-annotations", version.ref = "cloud" }
cloud-velocity = { group = "org.incendo", name = "cloud-velocity", version.ref = "cloud" }
cloud-minecraft-extras = { group = "org.incendo", name = "cloud-minecraft-extras", version.ref = "cloud" }
cloud-brigadier = { group = "org.incendo", name = "cloud-brigadier", version.ref = "cloud" }
jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" }
eventbus = { group = "org.greenrobot", name = "eventbus-java", version.ref = "eventbus" }
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
Expand All @@ -52,5 +55,5 @@ slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }

database = ["postgresql", "hikari", "sadu", "mariadb", "sqlite", "mysql", "sadu-queries"]
jackson = ["jackson-databind", "jackson-yaml", "jackson-datatype-jsr310"]
adventure = ["adventure-api", "adventure-text-minimessage"]
cloud = ["cloud-core", "cloud-annotations"]
adventure = ["adventure-api", "adventure-text-minimessage", "adventure-text-serializer-plain"]
cloud = ["cloud-core", "cloud-annotations", "cloud-minecraft-extras"]
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
public abstract class NecrifyEvent {

private final String name;
private EventOrigin origin = null;
private EventOrigin origin = EventOrigin.nullOrigin();
private EventDispatcher executingDispatcher = null;

public NecrifyEvent(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,16 @@ static EventOrigin ofClass(Class<?> clazz) {
}

boolean originatesFrom(Object object);

static EventOrigin nullOrigin() {
return new NullEventOrigin();
}

class NullEventOrigin implements EventOrigin {

@Override
public boolean originatesFrom(Object object) {
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public interface TemporalPunishment extends Punishment {
CompletableFuture<Punishment> change(@NotNull PunishmentDuration newDuration, @Nullable Component newReason) throws PunishmentException;

@Override
default CompletableFuture<Punishment> change(Component newReason) throws PunishmentException {
return change(getDuration(), newReason);
default CompletableFuture<Punishment> change(@Nullable Component newReason) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public interface NecrifyUser extends CommandSender {
* @return the ban object representing the ban. The ban may not be active yet, as the execution takes some time to complete.
*/
@NotNull
Ban ban(@Nullable Component reason, @NotNull PunishmentDuration duration);
CompletableFuture<Ban> ban(@Nullable Component reason, @NotNull PunishmentDuration duration);

/**
* Bans the user permanently with the given reason. A permanently banned user is not able to join the server this system
Expand All @@ -94,7 +94,7 @@ public interface NecrifyUser extends CommandSender {
* @return the ban object representing the ban. The ban may not be active yet, as the execution takes some time to complete.
*/
@NotNull
Ban banPermanent(@Nullable Component reason);
CompletableFuture<Ban> banPermanent(@Nullable Component reason);

/**
* Mutes the user with the given reason and duration. A muted user is not able to send messages in the chat of the server
Expand All @@ -108,7 +108,7 @@ public interface NecrifyUser extends CommandSender {
* @return the mute object representing the mute. The mute may not be active yet, as the execution takes some time to complete.
*/
@NotNull
Mute mute(@Nullable Component reason, @NotNull PunishmentDuration duration);
CompletableFuture<Mute> mute(@Nullable Component reason, @NotNull PunishmentDuration duration);

/**
* Mutes the user permanently with the given reason. A permanently muted user is not able to send messages in the chat of
Expand All @@ -120,7 +120,7 @@ public interface NecrifyUser extends CommandSender {
* @return the mute object representing the mute. The mute may not be active yet, as the execution takes some time to complete.
*/
@NotNull
Mute mutePermanent(@Nullable Component reason);
CompletableFuture<Mute> mutePermanent(@Nullable Component reason);

/**
* Kicks the user with the given reason. A kicked user is removed from the server this system belongs to. They are able to
Expand All @@ -131,7 +131,7 @@ public interface NecrifyUser extends CommandSender {
* @return the kick object representing the kick. The kick may not be active yet, as the execution takes some time to complete.
*/
@NotNull
Kick kick(@Nullable Component reason);
CompletableFuture<Kick> kick(@Nullable Component reason);

/**
* This method queries all punishments with the given {@link UUID} of a player and returns them in a list.
Expand Down
3 changes: 3 additions & 0 deletions necrify-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ dependencies {
exclude(group = "org.slf4j", module = "slf4j-api")
}
api(libs.bundles.cloud)
compileOnly(libs.cloud.brigadier)
compileOnly(libs.brigadier)
annotationProcessor(libs.cloud.annotations)
compileOnly(libs.slf4j.api)
compileOnly("com.google.code.gson:gson:2.10.1")
compileOnly(libs.bundles.adventure)
testImplementation(libs.junit.jupiter.api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public final void registerCommands(CommandManager<NecrifyUser> manager, boolean
});
var parserRegistry = manager.parserRegistry();
parserRegistry.registerParser(ParserDescriptor.of(new NecrifyUserParser(this.getUserManager()), NecrifyUser.class));
parserRegistry.registerParser(ComponentParser.componentParser(MiniMessage.miniMessage(), StringParser.StringMode.GREEDY_FLAG_YIELDING));
parserRegistry.registerParser(ComponentParser.componentParser(MiniMessage.miniMessage(), StringParser.StringMode.GREEDY));
var commands = new NecrifyCommand(this);
parser.parse(commands);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,210 @@

package de.jvstvshd.necrify.common.commands;

import com.mojang.brigadier.LiteralMessage;
import com.mojang.brigadier.Message;
import de.jvstvshd.necrify.api.PunishmentException;
import de.jvstvshd.necrify.api.event.punishment.PunishmentCancelledEvent;
import de.jvstvshd.necrify.api.message.MessageProvider;
import de.jvstvshd.necrify.api.punishment.Punishment;
import de.jvstvshd.necrify.api.punishment.StandardPunishmentType;
import de.jvstvshd.necrify.api.user.NecrifyUser;
import de.jvstvshd.necrify.api.user.UserManager;
import de.jvstvshd.necrify.common.AbstractNecrifyPlugin;
import de.jvstvshd.necrify.common.plugin.Util;
import de.jvstvshd.necrify.common.util.PunishmentHelper;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEventSource;
import net.kyori.adventure.text.format.NamedTextColor;
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.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.incendo.cloud.annotation.specifier.Greedy;
import org.incendo.cloud.annotations.Argument;
import org.incendo.cloud.annotations.Command;
import org.incendo.cloud.annotations.Default;
import org.incendo.cloud.annotations.Permission;
import org.incendo.cloud.annotations.parser.Parser;
import org.incendo.cloud.annotations.processing.CommandContainer;
import org.incendo.cloud.annotations.suggestion.Suggestions;
import org.incendo.cloud.brigadier.suggestion.TooltipSuggestion;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.injection.ParameterInjector;
import org.incendo.cloud.minecraft.extras.suggestion.ComponentTooltipSuggestion;
import org.incendo.cloud.parser.ArgumentParseResult;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.type.Either;
import org.incendo.cloud.util.annotation.AnnotationAccessor;
import org.slf4j.Logger;

import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

public class NecrifyCommand {

private final AbstractNecrifyPlugin plugin;
private final MiniMessage miniMessage;
private final UserManager userManager;
private final Logger logger;
private final ExecutorService executor;

public NecrifyCommand(AbstractNecrifyPlugin plugin) {
this.plugin = plugin;
this.userManager = plugin.getUserManager();
this.miniMessage = MiniMessage.miniMessage();
this.logger = plugin.getLogger();
this.executor = plugin.getExecutor();
}

@Command("necrify ban <target> [reason]")
@Command("ban <target> [reason]")
@Permission(value = {"necrify.command.ban", "necrify.admin"}, mode = Permission.Mode.ANY_OF)
public void banCommand(
NecrifyUser sender,
@Argument(value = "target", description = "Player to ban", suggestions = "suggestOnlinePlayers") NecrifyUser target,
@Argument(value = "reason", description = "Reason the user should be banned for") @Greedy String reason
) {
var finalReason = reasonOrDefaultTo(reason, StandardPunishmentType.PERMANENT_BAN);
target.banPermanent(finalReason).whenComplete((ban, throwable) -> {
if (throwable != null) {
logException(sender, throwable);
return;
}
sender.sendMessage("command.ban.success",
miniMessage(target.getUsername()).color(NamedTextColor.YELLOW),
copyComponent(target.getUuid().toString()).color(NamedTextColor.YELLOW),
finalReason);
});
}

@Command("necrify ban")
@Command("ban")
public void banCommand() {
@Command("necrify mute <target> [reason]")
@Command("mute <target> [reason]")
@Permission(value = {"necrify.command.mute", "necrify.admin"}, mode = Permission.Mode.ANY_OF)
public void muteCommand(
NecrifyUser sender,
@Argument(value = "target", description = "Player to mute", suggestions = "suggestOnlinePlayers") NecrifyUser target,
@Argument(value = "reason", description = "Reason the user should be muted for") @Greedy String reason
) {
var finalReason = reasonOrDefaultTo(reason, StandardPunishmentType.PERMANENT_MUTE);
target.mutePermanent(finalReason).whenComplete((mute, throwable) -> {
if (throwable != null) {
logException(sender, throwable);
return;
}
sender.sendMessage("command.mute.success",
miniMessage(target.getUsername()).color(NamedTextColor.YELLOW),
copyComponent(target.getUuid().toString()).color(NamedTextColor.YELLOW),
finalReason);
});
}

@Command("necrify unban <target>")
@Command("unban <target>")
@Permission(value = {"necrify.command.unban", "necrify.admin"}, mode = Permission.Mode.ANY_OF)
public void unbanCommand(
NecrifyUser sender,
@Argument(value = "target", description = "Player to unban") NecrifyUser target
) {
var punishments = target.getPunishments(StandardPunishmentType.BAN, StandardPunishmentType.PERMANENT_BAN);
try {
removePunishments(sender, "unban", punishments);
} catch (Exception e) {
logException(e);
}
}

@Command("necrify unmute <target>")
@Command("unmute <target>")
@Permission(value = {"necrify.command.unmute", "necrify.admin"}, mode = Permission.Mode.ANY_OF)
public void unmuteCommand(
NecrifyUser sender,
@Argument(value = "target", description = "Player to unmute", suggestions = "suggestOnlinePlayers") NecrifyUser target
) {
var punishments = target.getPunishments(StandardPunishmentType.MUTE, StandardPunishmentType.PERMANENT_MUTE);
removePunishments(sender, "unmute", punishments);
}

@Command("necrify kick")
public void kickCommand() {
private void removePunishments(NecrifyUser source, String commandName, List<Punishment> punishments) {
if (punishments.isEmpty()) {
source.sendMessage(plugin.getMessageProvider().provide("command.punishment.not-banned").color(NamedTextColor.RED));
return;
}
if (punishments.size() > 1) {
source.sendMessage(plugin.getMessageProvider().provide("command." + commandName + ".multiple-bans").color(NamedTextColor.YELLOW));
for (Punishment punishment : punishments) {
source.sendMessage(buildComponent(PunishmentHelper.buildPunishmentData(punishment, plugin.getMessageProvider()), punishment));
}
} else {
Punishment punishment = punishments.getFirst();
try {
punishment.cancel().whenCompleteAsync((unused, th) -> {
if (th != null) {
logException(source, th);
plugin.getLogger().error("An error occurred while removing punishment {} for player {}", punishment.getPunishmentUuid(), punishment.getUser().getUsername(), th);
source.sendMessage(plugin.getMessageProvider().internalError());
return;
}
source.sendMessage(plugin.getMessageProvider().provide("command." + commandName + ".success").color(NamedTextColor.GREEN));
}, plugin.getService());
} catch (Exception e) {
logException(source, e);
}
}
}

@Suggestions("suggestOnlinePlayers")
public List<? extends Suggestion> suggestNames(CommandContext<NecrifyUser> context, CommandInput input) {
return plugin
.getOnlinePlayers()
.stream()
.filter(pair -> pair.first().toLowerCase(Locale.ROOT).startsWith(input.peekString().toLowerCase(Locale.ROOT)))
.map(pair -> ComponentTooltipSuggestion.suggestion(pair.first(),
miniMessage("<red>The player <yellow>(<name>/<uuid>)</yellow> you want to select.</red><yellow>",
Placeholder.parsed("name", pair.first()),
Placeholder.parsed("uuid", pair.second().toString()))
)).toList();
}

private Component buildComponent(Component dataComponent, Punishment punishment) {
return dataComponent.clickEvent(ClickEvent.runCommand("/punishment " + punishment.getPunishmentUuid()
.toString().toLowerCase(Locale.ROOT) + " remove"))
.hoverEvent((HoverEventSource<Component>) op -> HoverEvent.showText(Component
.text("Click to remove punishment").color(NamedTextColor.GREEN)));
}

private Component reasonOrDefaultTo(String reason, StandardPunishmentType type) {
return miniMessage(reason == null ? plugin.getDefaultReason(type) : reason);
}

private Component miniMessage(String message, TagResolver... resolvers) {
return miniMessage.deserialize(message, resolvers);
}

public TextComponent copyComponent(String text) {
return Component.text(text).clickEvent(ClickEvent.suggestCommand(text))
.hoverEvent((HoverEventSource<Component>) op -> HoverEvent.showText(plugin
.getMessageProvider()
.provide("commands.general.copy")
.color(NamedTextColor.GREEN)));
}



private void logException(Throwable throwable) {
logger.error("An error occurred while executing a command", throwable);
}

private void logException(NecrifyUser sender, Throwable throwable) {
logger.error("An error occurred while executing a command for player {} ({})", sender.getUsername(), sender.getUuid(), throwable);
sender.sendErrorMessage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.jvstvshd.necrify.common.commands;

import de.jvstvshd.necrify.api.user.NecrifyUser;
import de.jvstvshd.necrify.api.user.UserManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.parser.ArgumentParseResult;
import org.incendo.cloud.parser.ArgumentParser;

import java.util.concurrent.CompletableFuture;

public class NecrifyUserParser implements ArgumentParser.FutureArgumentParser<NecrifyUser, NecrifyUser> {

private final UserManager userManager;

public NecrifyUserParser(UserManager userManager) {
this.userManager = userManager;
}

@Override
public @NonNull CompletableFuture<@NonNull ArgumentParseResult<NecrifyUser>> parseFuture(@NonNull CommandContext<NecrifyUser> commandContext, @NonNull CommandInput commandInput) {
var target = commandInput.peekString();
return userManager.loadOrCreateUser(target).handle((necrifyUser, throwable) -> {
if (throwable != null) {
return ArgumentParseResult.failure(throwable);
}
if (necrifyUser.isPresent()) {
commandInput.readString();
return ArgumentParseResult.success(necrifyUser.get());
}
return ArgumentParseResult.failure(new UserNotFoundParseException(NecrifyUser.class, commandContext, target));
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.jvstvshd.necrify.common.commands;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.caption.Caption;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.exception.parsing.ParserException;

public class UserNotFoundParseException extends ParserException {

private final String playerName;

public UserNotFoundParseException(@NonNull Class<?> argumentParser, @NonNull CommandContext<?> context, String playerName) {
super(argumentParser, context, Caption.of(""));
this.playerName = playerName;
}

public String playerName() {
return playerName;
}
}
Loading

0 comments on commit 0c3e803

Please sign in to comment.