package tc.oc.commons.bukkit.whisper; import java.time.Duration; import java.time.Instant; import javax.inject.Inject; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.TranslatableComponent; import org.bukkit.Sound; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import tc.oc.api.docs.Server; import tc.oc.api.docs.Whisper; import tc.oc.api.servers.ServerStore; import tc.oc.commons.bukkit.chat.Audiences; import tc.oc.commons.bukkit.chat.BukkitSound; import tc.oc.commons.bukkit.chat.ConsoleAudience; import tc.oc.commons.bukkit.chat.NameStyle; import tc.oc.commons.bukkit.chat.PlayerComponent; import tc.oc.commons.bukkit.chat.UserTextComponent; import tc.oc.commons.bukkit.chat.WarningComponent; import tc.oc.commons.bukkit.format.MiscFormatter; import tc.oc.commons.bukkit.format.ServerFormatter; import tc.oc.commons.bukkit.nick.Identity; import tc.oc.commons.bukkit.nick.IdentityProvider; import tc.oc.commons.bukkit.settings.SettingManagerProvider; import tc.oc.commons.core.chat.Audience; import tc.oc.commons.core.chat.Component; import tc.oc.commons.core.formatting.PeriodFormats; import tc.oc.commons.core.util.Comparables; public class WhisperFormatter { private static final Duration OLD_MESSAGE = Duration.ofSeconds(10); private static final BukkitSound MESSAGE_SOUND = new BukkitSound(Sound.ENTITY_PLAYER_LEVELUP, 1, 2); private final IdentityProvider identities; private final MiscFormatter miscFormatter; private final ServerFormatter serverFormatter; private final ServerStore serverStore; private final Server localServer; private final SettingManagerProvider playerSettings; private final Audiences audiences; private final ConsoleAudience consoleAudience; @Inject WhisperFormatter(IdentityProvider identities, MiscFormatter miscFormatter, ServerStore serverStore, Server localServer, SettingManagerProvider playerSettings, Audiences audiences, ConsoleAudience consoleAudience) { this.identities = identities; this.miscFormatter = miscFormatter; this.serverStore = serverStore; this.localServer = localServer; this.playerSettings = playerSettings; this.audiences = audiences; this.consoleAudience = consoleAudience; this.serverFormatter = ServerFormatter.dark; } public Identity senderIdentity(Whisper whisper) { return identities.createIdentity(whisper.sender_uid(), whisper.sender_nickname()); } public Identity recipientIdentity(Whisper whisper) { return identities.createIdentity(whisper.recipient_uid(), whisper.recipient_specified()); } private Component prefix() { return new Component(miscFormatter.typePrefix("PM"), ChatColor.GRAY); } public void send(CommandSender viewer, Whisper whisper) { final Identity sender = senderIdentity(whisper); final Identity recipient = recipientIdentity(whisper); final Audience audience = audiences.get(viewer); final Component display = prefix() .extra(new TranslatableComponent(sender.getNickname() == null ? "privateMessage.to" : "privateMessage.from.to", new PlayerComponent(sender, NameStyle.VERBOSE), new PlayerComponent(recipient, NameStyle.VERBOSE))) .extra(": ") .extra(new Component(new UserTextComponent(sender, whisper.content()), ChatColor.WHITE)); audience.sendMessage(display); consoleAudience.sendMessage(display); } public void receive(Player viewer, Whisper whisper) { final Identity sender = senderIdentity(whisper); final Identity recipient = recipientIdentity(whisper); final Audience audience = audiences.get(viewer); final boolean local = whisper.server_id().equals(localServer._id()); final Component from = new Component(); if(!local) { // Show sender server if not local. There is an edge case where this reveals nicked players: // if a player uses /reply to send a message from their real identity while they are nicked, // the recipient will know they are nicked because they are not reported as online, and they // will also know what server they are on. The former is unavoidable. The latter could be // avoided by saving a flag in the message indicating that the sender was disguised, but // that probably isn't worth the trouble. We might do it when we move PMs to the API. from.extra(serverFormatter.nameWithDatacenter(serverStore.byId(whisper.server_id()))).extra(" "); } from.extra(new PlayerComponent(sender, NameStyle.VERBOSE)); // Show recipient identity if it is nicked OR if it is not the recipient's current identity String key = "privateMessage.from"; if(recipient.getNickname() != null || !recipient.isCurrent()) { key += ".to"; } final Duration age = Duration.between(whisper.sent(), Instant.now()); if(Comparables.greaterThan(age, OLD_MESSAGE)) { // If message is old, show the time key += ".time"; } else { // If message is new, play a sound if(playerSettings.getManager(viewer) .getValue(WhisperSettings.sound(), WhisperSettings.Options.class) .isAllowed(sender.familiarity(viewer))) { audience.playSound(MESSAGE_SOUND); } } final Component display = prefix() .extra(new TranslatableComponent(key, from, new PlayerComponent(recipient, NameStyle.VERBOSE), new Component(new TranslatableComponent("time.ago", PeriodFormats.briefNaturalApproximate(age)), ChatColor.GOLD))) .extra(": ") .extra(new Component(new UserTextComponent(whisper.content()), ChatColor.WHITE)); audience.sendMessage(display); if(!local) { consoleAudience.sendMessage(display); } } public void blocked(CommandSender viewer, Identity recipient) { audiences.get(viewer).sendMessage(new WarningComponent("command.message.blockedNoPermissions", new PlayerComponent(recipient, NameStyle.VERBOSE))); } public void noReply(CommandSender viewer) { audiences.get(viewer).sendMessage(new WarningComponent("command.reply.noMessages")); } }