Merge pull request 'develop-chatReply' (#5) from develop-chatReply into master

Reviewed-on: #5
Reviewed-by: Elias Müller <elias@elias-mueller.com>
This commit is contained in:
Lars Neuhaus 2024-10-06 13:52:58 +00:00
commit 77fbc12873
9 changed files with 250 additions and 13 deletions

View File

@ -1,6 +1,8 @@
package eu.mhsl.craftattack.spawn.appliances.chatMention; package eu.mhsl.craftattack.spawn.appliances.chatMention;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
import eu.mhsl.craftattack.spawn.appliances.settings.Settings; import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil; import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
import io.papermc.paper.event.player.AsyncChatDecorateEvent; import io.papermc.paper.event.player.AsyncChatDecorateEvent;
@ -8,6 +10,7 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -22,6 +25,7 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
ChatMentionSetting.ChatMentionConfig config = Settings.instance() ChatMentionSetting.ChatMentionConfig config = Settings.instance()
.getSetting(event.player(), Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class); .getSetting(event.player(), Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class);
ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
Component result = words.stream() Component result = words.stream()
.map(word -> { .map(word -> {
@ -29,10 +33,11 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
boolean isPlayer = getAppliance().getPlayerNames().contains(wordWithoutAnnotation); boolean isPlayer = getAppliance().getPlayerNames().contains(wordWithoutAnnotation);
if(isPlayer && config.applyMentions()) { if(isPlayer && config.applyMentions()) {
mentioned.add(wordWithoutAnnotation); mentioned.add(wordWithoutAnnotation);
return Component.text( Component mention = Component.text(
getAppliance().formatPlayer(wordWithoutAnnotation), getAppliance().formatPlayer(wordWithoutAnnotation),
NamedTextColor.GOLD NamedTextColor.GOLD
); );
return chatMessages.addReportActions(mention, wordWithoutAnnotation);
} else { } else {
return Component.text(word); return Component.text(word);
} }
@ -43,4 +48,9 @@ public class ChatMentionListener extends ApplianceListener<ChatMention> {
getAppliance().notifyPlayers(mentioned); getAppliance().notifyPlayers(mentioned);
event.result(result); event.result(result);
} }
@EventHandler
public void onJoin(PlayerJoinEvent event) {
getAppliance().refreshPlayers();
}
} }

View File

@ -19,11 +19,13 @@ public class ChatMessages extends Appliance {
} }
public Component getReportablePlayerName(Player player) { public Component getReportablePlayerName(Player player) {
return Component return addReportActions(player.displayName(), player.getName());
.text("") }
.append(player.displayName())
public Component addReportActions(Component message, String username) {
return message
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesen Spieler zu reporten").color(NamedTextColor.GOLD))) .hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesen Spieler zu reporten").color(NamedTextColor.GOLD)))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/report " + player.getName() + " ")); .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, String.format("/report %s ", username)));
} }
@Override @Override

View File

@ -0,0 +1,187 @@
package eu.mhsl.craftattack.spawn.appliances.privateMessage;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateMessageCommand;
import eu.mhsl.craftattack.spawn.appliances.privateMessage.commands.PrivateReplyCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
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.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collectors;
public class PrivateMessage extends Appliance {
public final int targetChangeTimeoutSeconds = 30;
public final int conversationTimeoutMinutes = 30;
private record Conversation(UUID target, Long lastSet) {}
private final Map<Player, List<Conversation>> replyMapping = new WeakHashMap<>();
public void reply(Player sender, String message) {
this.replyMapping.computeIfAbsent(sender, player -> new ArrayList<>());
List<Conversation> replyList = this.replyMapping.get(sender);
List<Conversation> tooOldConversations = replyList.stream()
.filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (conversationTimeoutMinutes*60*1000))
.toList();
replyList.removeAll(tooOldConversations);
if(replyList.isEmpty()) throw new ApplianceCommand.Error("Du führst aktuell keine Konversation.");
Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
Conversation youngestEntry = replyList.stream()
.max(Comparator.comparingLong(o -> o.lastSet))
.orElse(replyList.getLast());
if(message.isBlank()) {
Player currentTargetPlayer = Bukkit.getPlayer(youngestEntry.target());
Component currentTargetComponent = currentTargetPlayer != null
? Main.instance().getAppliance(ChatMessages.class).getReportablePlayerName(currentTargetPlayer)
: Component.text("niemandem.");
sender.sendMessage(
privatePrefix
.append(Component.text("Du schreibst aktuell mit ", NamedTextColor.GRAY))
.append(currentTargetComponent)
);
return;
}
List<Conversation> oldConversations = replyList.stream()
.filter(conversation -> conversation.lastSet < System.currentTimeMillis() - (targetChangeTimeoutSeconds*1000))
.toList();
if(oldConversations.contains(youngestEntry) || replyList.size() == 1) {
Player target = Bukkit.getPlayer(youngestEntry.target());
if(target == null) throw new ApplianceCommand.Error("Der Spieler " + Bukkit.getOfflinePlayer(youngestEntry.target()).getName() + " ist nicht mehr verfügbar.");
replyList.clear();
this.sendWhisper(sender, new ResolvedPmUserArguments(target, message));
return;
}
ComponentBuilder<TextComponent, TextComponent.Builder> component = Component.text();
component.append(
Component.newline()
.append(privatePrefix)
.append(Component.text("Das Ziel für /r hat sich bei dir in den letzten ", NamedTextColor.RED))
.append(Component.text(String.valueOf(this.targetChangeTimeoutSeconds), NamedTextColor.RED))
.append(Component.text(" Sekunden geändert. Wer soll deine Nachricht erhalten? ", NamedTextColor.RED))
.appendNewline()
.appendNewline()
);
if(!oldConversations.isEmpty()) {
Conversation youngestOldConversation = oldConversations.stream()
.max(Comparator.comparingLong(o -> o.lastSet))
.orElse(oldConversations.getLast());
replyList.removeAll(oldConversations);
replyList.add(youngestOldConversation);
}
List<String> playerNames = replyList.stream()
.map(conversation -> Bukkit.getOfflinePlayer(conversation.target()).getName())
.distinct()
.toList();
playerNames.forEach(playerName -> component.append(
Component.text("[")
.append(Component.text(playerName, NamedTextColor.GOLD))
.append(Component.text("]"))
.clickEvent(ClickEvent.runCommand(String.format("/msg %s %s", playerName, message)))
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesem Spieler zu schreiben.").color(NamedTextColor.GOLD))))
.append(Component.text(" "))
);
component.appendNewline();
sender.sendMessage(component.build());
}
public void sendWhisper(Player sender, ResolvedPmUserArguments userArguments) {
Conversation newReceiverConversation = new Conversation(
sender.getUniqueId(),
System.currentTimeMillis()
);
if(this.replyMapping.get(userArguments.receiver) != null) {
List<Conversation> oldEntries = this.replyMapping.get(userArguments.receiver).stream()
.filter(conversation -> conversation.target() == sender.getUniqueId())
.toList();
this.replyMapping.get(userArguments.receiver).removeAll(oldEntries);
} else {
this.replyMapping.put(
userArguments.receiver,
new ArrayList<>()
);
}
this.replyMapping.get(userArguments.receiver).add(newReceiverConversation);
List<Conversation> senderConversationList = new ArrayList<>();
senderConversationList.add(
new Conversation(
userArguments.receiver.getUniqueId(),
System.currentTimeMillis()
)
);
this.replyMapping.put(
sender,
senderConversationList
);
ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
Component privatePrefix = Component.text("[Privat] ", NamedTextColor.LIGHT_PURPLE);
sender.sendMessage(
Component.text()
.append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", userArguments.receiver.getName()))))
.append(sender.displayName())
.append(Component.text(" zu ", NamedTextColor.GRAY))
.append(chatMessages.getReportablePlayerName(userArguments.receiver))
.append(Component.text(" > ", NamedTextColor.GRAY))
.append(Component.text(userArguments.message))
);
userArguments.receiver.sendMessage(
Component.text()
.append(privatePrefix.clickEvent(ClickEvent.suggestCommand(String.format("/msg %s ", sender.getName()))))
.append(chatMessages.getReportablePlayerName(sender))
.append(Component.text(" zu ", NamedTextColor.GRAY))
.append(userArguments.receiver.displayName())
.append(Component.text(" > ", NamedTextColor.GRAY))
.append(Component.text(userArguments.message))
);
}
public record ResolvedPmUserArguments(Player receiver, String message) {}
public ResolvedPmUserArguments resolveImplicit(String[] args) {
if(args.length < 2) throw new ApplianceCommand.Error("Es muss ein Spieler sowie eine Nachricht angegeben werden.");
List<String> arguments = List.of(args);
Player targetPlayer = Bukkit.getPlayer(arguments.getFirst());
if(targetPlayer == null) throw new ApplianceCommand.Error(String.format("Der Spieler %s konnte nicht gefunden werden.", arguments.getFirst()));
String message = arguments.stream().skip(1).collect(Collectors.joining(" "));
return new ResolvedPmUserArguments(targetPlayer, message);
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new PrivateMessageCommand(),
new PrivateReplyCommand()
);
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.appliances.privateMessage.commands;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.privateMessage.PrivateMessage;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class PrivateMessageCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
public PrivateMessageCommand() {
super("msg");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
getAppliance().sendWhisper(getPlayer(), getAppliance().resolveImplicit(args));
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.appliances.privateMessage.commands;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.privateMessage.PrivateMessage;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class PrivateReplyCommand extends ApplianceCommand.PlayerChecked<PrivateMessage> {
public PrivateReplyCommand() {
super("r");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
getAppliance().reply(getPlayer(), String.join(" ", args));
}
}

View File

@ -66,12 +66,9 @@ public class Whitelist extends Appliance {
queryAppliance(Outlawed.class).updateForcedStatus(player, timestampRelevant(user.outlawed_until)); queryAppliance(Outlawed.class).updateForcedStatus(player, timestampRelevant(user.outlawed_until));
String purePlayerName; String purePlayerName = Floodgate.isBedrock(player)
if(Floodgate.isBedrock(player)) { ? Floodgate.getBedrockPlayer(player).getUsername()
purePlayerName = Floodgate.getBedrockPlayer(player).getUsername(); : player.getName();
} else {
purePlayerName = player.getName();
}
if(!user.username.trim().equalsIgnoreCase(purePlayerName)) if(!user.username.trim().equalsIgnoreCase(purePlayerName))
throw new DisconnectInfo.Throwable( throw new DisconnectInfo.Throwable(

View File

@ -9,6 +9,7 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.Duration; import java.time.Duration;
import java.util.logging.Level;
public class NetworkMonitor { public class NetworkMonitor {
private long previousRxBytes = 0; private long previousRxBytes = 0;
@ -78,8 +79,9 @@ public class NetworkMonitor {
String content = new String(Files.readAllBytes(Paths.get(path))); String content = new String(Files.readAllBytes(Paths.get(path)));
return Long.parseLong(content.trim()); return Long.parseLong(content.trim());
} catch(IOException e) { } catch(IOException e) {
Main.logger().log(Level.SEVERE, "Statistics are only supported on Linux! Is tablist.interface config set correctly?");
this.stop(); this.stop();
throw new RuntimeException("Failed receiving Network statistic, disabling statistics!", e); throw new RuntimeException("Failed reading network statistic", e);
} }
} }
} }

View File

@ -49,6 +49,7 @@ playerLimit:
maxPlayers: 10 maxPlayers: 10
whitelist: whitelist:
overrideIntegrityCheck: false
api: https://mhsl.eu/craftattack/api/user api: https://mhsl.eu/craftattack/api/user
tablist: tablist:

View File

@ -40,3 +40,5 @@ commands:
texturepack: texturepack:
maintanance: maintanance:
yearRank: yearRank:
msg:
r: