diff --git a/Essentials/src/main/java/com/earth2me/essentials/Console.java b/Essentials/src/main/java/com/earth2me/essentials/Console.java index 549a8517f75..dff940f3c38 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Console.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Console.java @@ -6,6 +6,8 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.UUID; + import static com.earth2me.essentials.I18n.tl; public final class Console implements IMessageRecipient { @@ -46,6 +48,11 @@ public String getName() { return Console.NAME; } + @Override + public UUID getUUID() { + return null; + } + @Override public String getDisplayName() { return Console.DISPLAY_NAME; diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index d659584f8db..8897f65dde4 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -82,6 +82,7 @@ import net.ess3.provider.providers.PaperSerializationProvider; import net.ess3.provider.providers.PaperServerStateProvider; import net.essentialsx.api.v2.services.BalanceTop; +import net.essentialsx.api.v2.services.mail.MailService; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.World; @@ -147,6 +148,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { private transient UserMap userMap; private transient BalanceTopImpl balanceTop; private transient ExecuteTimer execTimer; + private transient MailService mail; private transient I18n i18n; private transient MetricsWrapper metrics; private transient EssentialsTimer timer; @@ -204,6 +206,7 @@ public void setupForTesting(final Server server) throws IOException, InvalidDesc LOGGER.log(Level.INFO, tl("usingTempFolderForTesting")); LOGGER.log(Level.INFO, dataFolder.toString()); settings = new Settings(this); + mail = new MailServiceImpl(this); userMap = new UserMap(this); balanceTop = new BalanceTopImpl(this); permissionsHandler = new PermissionsHandler(this, false); @@ -277,6 +280,9 @@ public void onEnable() { confList.add(settings); execTimer.mark("Settings"); + mail = new MailServiceImpl(this); + execTimer.mark("Init(Mail)"); + userMap = new UserMap(this); confList.add(userMap); execTimer.mark("Init(Usermap)"); @@ -1159,6 +1165,11 @@ public EssentialsTimer getTimer() { return timer; } + @Override + public MailService getMail() { + return mail; + } + @Override public List getVanishedPlayers() { return Collections.unmodifiableList(new ArrayList<>(vanishedPlayers)); diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java index 8499f4d0faf..dad07e1fc88 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java @@ -364,8 +364,7 @@ public void run() { } if (!ess.getSettings().isCommandDisabled("mail") && user.isAuthorized("essentials.mail")) { - final List mail = user.getMails(); - if (mail.isEmpty()) { + if (user.getUnreadMailAmount() == 0) { if (ess.getSettings().isNotifyNoNewMail()) { user.sendMessage(tl("noNewMail")); // Only notify if they want us to. } diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java index 16fe6e9f816..9d1dce8c9f8 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsUpgrade.java @@ -7,7 +7,9 @@ import com.earth2me.essentials.utils.StringUtil; import com.google.common.base.Charsets; import com.google.common.collect.Maps; +import com.google.gson.reflect.TypeToken; import net.ess3.api.IEssentials; +import net.essentialsx.api.v2.services.mail.MailMessage; import org.bukkit.BanList; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -146,6 +148,46 @@ public static void uuidFileConvert(final IEssentials ess, final Boolean ignoreUF ess.getLogger().info("To rerun the conversion type /essentials uuidconvert"); } + public void convertMailList() { + if (doneFile.getBoolean("updateUsersMailList", false)) { + return; + } + + final File userdataFolder = new File(ess.getDataFolder(), "userdata"); + if (!userdataFolder.exists() || !userdataFolder.isDirectory()) { + return; + } + final File[] userFiles = userdataFolder.listFiles(); + for (File file : userFiles) { + if (!file.isFile() || !file.getName().endsWith(".yml")) { + continue; + } + final EssentialsConfiguration config = new EssentialsConfiguration(file); + try { + config.load(); + if (config.hasProperty("mail") && config.isList("mail")) { + final ArrayList messages = new ArrayList<>(); + for (String mailStr : Collections.synchronizedList(config.getList("mail", String.class))) { + if (mailStr == null) { + continue; + } + messages.add(new MailMessage(false, true, null, null, 0L, 0L, mailStr)); + } + + config.removeProperty("mail"); + config.setExplicitList("mail", messages, new TypeToken>() {}.getType()); + config.blockingSave(); + } + } catch (RuntimeException ex) { + LOGGER.log(Level.INFO, "File: " + file); + throw ex; + } + } + doneFile.setProperty("updateUsersMailList", true); + doneFile.save(); + LOGGER.info("Done converting mail list."); + } + public void convertStupidCamelCaseUserdataKeys() { if (doneFile.getBoolean("updateUsersLegacyPathNames", false)) { return; @@ -820,5 +862,6 @@ public void afterSettings() { repairUserMap(); convertIgnoreList(); convertStupidCamelCaseUserdataKeys(); + convertMailList(); } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java index f4f532930cc..6f47fbbed57 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java @@ -18,6 +18,7 @@ import net.ess3.provider.SpawnerItemProvider; import net.ess3.provider.SyncCommandsProvider; import net.essentialsx.api.v2.services.BalanceTop; +import net.essentialsx.api.v2.services.mail.MailService; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.command.Command; @@ -120,6 +121,8 @@ public interface IEssentials extends Plugin { EssentialsTimer getTimer(); + MailService getMail(); + /** * Get a list of players who are vanished. * diff --git a/Essentials/src/main/java/com/earth2me/essentials/IUser.java b/Essentials/src/main/java/com/earth2me/essentials/IUser.java index 42040f93d36..c78e18407ab 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IUser.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IUser.java @@ -6,12 +6,15 @@ import net.ess3.api.ITeleport; import net.ess3.api.MaxMoneyException; import net.ess3.api.events.AfkStatusChangeEvent; +import net.essentialsx.api.v2.services.mail.MailMessage; +import net.essentialsx.api.v2.services.mail.MailSender; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; @@ -148,10 +151,22 @@ public interface IUser { String getFormattedJailTime(); + @Deprecated List getMails(); + @Deprecated void addMail(String mail); + void sendMail(MailSender sender, String message); + + void sendMail(MailSender sender, String message, long expireAt); + + ArrayList getMailMessages(); + + void setMailList(ArrayList messages); + + int getMailAmount(); + boolean isAfk(); @Deprecated diff --git a/Essentials/src/main/java/com/earth2me/essentials/MailServiceImpl.java b/Essentials/src/main/java/com/earth2me/essentials/MailServiceImpl.java new file mode 100644 index 00000000000..a1490e64d54 --- /dev/null +++ b/Essentials/src/main/java/com/earth2me/essentials/MailServiceImpl.java @@ -0,0 +1,53 @@ +package com.earth2me.essentials; + +import net.ess3.api.IUser; +import net.essentialsx.api.v2.services.mail.MailService; +import net.essentialsx.api.v2.services.mail.MailMessage; +import net.essentialsx.api.v2.services.mail.MailSender; +import org.bukkit.plugin.ServicePriority; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; + +import static com.earth2me.essentials.I18n.tl; + +public class MailServiceImpl implements MailService { + private final transient ThreadLocal df = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy/MM/dd HH:mm")); + + public MailServiceImpl(IEssentials ess) { + ess.getServer().getServicesManager().register(MailService.class, this, ess, ServicePriority.Normal); + } + + @Override + public void sendMail(IUser recipient, MailSender sender, String message) { + sendMail(recipient, sender, message, 0L); + } + + @Override + public void sendMail(IUser recipient, MailSender sender, String message, long expireAt) { + sendMail(recipient, new MailMessage(false, false, sender.getName(), sender.getUUID(), System.currentTimeMillis(), expireAt, message)); + } + + @Override + public void sendLegacyMail(IUser recipient, String message) { + sendMail(recipient, new MailMessage(false, true, null, null, 0L, 0L, message)); + } + + private void sendMail(IUser recipient, MailMessage message) { + final ArrayList messages = recipient.getMailMessages(); + messages.add(0, message); + recipient.setMailList(messages); + } + + @Override + public String getMailLine(MailMessage mail) { + final String message = mail.getMessage(); + if (mail.isLegacy()) { + return tl("mailMessage", message); + } + + final String expire = mail.getTimeExpire() != 0 ? "Timed" : ""; + return tl((mail.isRead() ? "mailFormatNewRead" : "mailFormatNew") + expire, df.get().format(new Date(mail.getTimeSent())), mail.getSenderUsername(), message); + } +} diff --git a/Essentials/src/main/java/com/earth2me/essentials/User.java b/Essentials/src/main/java/com/earth2me/essentials/User.java index 6242e7384ae..9d8140cddba 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/User.java +++ b/Essentials/src/main/java/com/earth2me/essentials/User.java @@ -5,11 +5,11 @@ import com.earth2me.essentials.economy.EconomyLayers; import com.earth2me.essentials.messaging.IMessageRecipient; import com.earth2me.essentials.messaging.SimpleMessageRecipient; -import com.earth2me.essentials.utils.TriState; import com.earth2me.essentials.utils.DateUtil; import com.earth2me.essentials.utils.EnumUtil; import com.earth2me.essentials.utils.FormatUtil; import com.earth2me.essentials.utils.NumberUtil; +import com.earth2me.essentials.utils.TriState; import com.earth2me.essentials.utils.VersionUtil; import com.google.common.collect.Lists; import net.ess3.api.IEssentials; @@ -19,6 +19,7 @@ import net.ess3.api.events.MuteStatusChangeEvent; import net.ess3.api.events.UserBalanceUpdateEvent; import net.essentialsx.api.v2.events.TransactionEvent; +import net.essentialsx.api.v2.services.mail.MailSender; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Statistic; @@ -987,6 +988,11 @@ public String getName() { return this.getBase().getName(); } + @Override + public UUID getUUID() { + return getBase().getUniqueId(); + } + @Override public boolean isReachable() { return getBase().isOnline(); @@ -1054,12 +1060,28 @@ public ItemStack getItemInHand() { } } + @Override + public void sendMail(MailSender sender, String message) { + sendMail(sender, message, 0); + } + + @Override + public void sendMail(MailSender sender, String message, long expireAt) { + ess.getMail().sendMail(this, sender, message, expireAt); + } + + @Override + @Deprecated + public void addMail(String mail) { + ess.getMail().sendLegacyMail(this, mail); + } + public void notifyOfMail() { - final List mails = getMails(); - if (mails != null && !mails.isEmpty()) { + final int unread = getUnreadMailAmount(); + if (unread != 0) { final int notifyPlayerOfMailCooldown = ess.getSettings().getNotifyPlayerOfMailCooldown() * 1000; if (System.currentTimeMillis() - lastNotifiedAboutMailsMs >= notifyPlayerOfMailCooldown) { - sendMessage(tl("youHaveNewMail", mails.size())); + sendMessage(tl("youHaveNewMail", unread)); lastNotifiedAboutMailsMs = System.currentTimeMillis(); } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/UserData.java b/Essentials/src/main/java/com/earth2me/essentials/UserData.java index 46834a18354..6773ce0cc36 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/UserData.java +++ b/Essentials/src/main/java/com/earth2me/essentials/UserData.java @@ -10,6 +10,8 @@ import com.google.common.base.Charsets; import net.ess3.api.IEssentials; import net.ess3.api.MaxMoneyException; +import net.essentialsx.api.v2.services.mail.MailMessage; +import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -313,17 +315,59 @@ public void setJail(final String jail) { config.save(); } + /** + * @deprecated Mails are no longer just strings, this method is therefore misleading. + */ + @Deprecated public List getMails() { - return holder.mail(); + final List list = new ArrayList<>(); + if (getMailAmount() != 0) { + for (MailMessage mail : getMailMessages()) { + // I hate this code btw + list.add(mail.isLegacy() ? mail.getMessage() : ChatColor.GOLD + "[" + ChatColor.RESET + mail.getSenderUsername() + ChatColor.GOLD + "] " + ChatColor.RESET + mail.getMessage()); + } + } + return list; } + /** + * @deprecated This method does not support the new mail system and will fail at runtime. + */ + @Deprecated public void setMails(List mails) { - holder.mail(mails); - config.save(); + throw new UnsupportedOperationException("UserData#setMails(List) is deprecated and can no longer be used. Please tell the plugin author to update this!"); + } + + public int getMailAmount() { + return holder.mail() == null ? 0 : holder.mail().size(); + } + + public int getUnreadMailAmount() { + if (holder.mail() == null || holder.mail().isEmpty()) { + return 0; + } + + int unread = 0; + for (MailMessage element : holder.mail()) { + if (!element.isRead()) { + unread++; + } + } + return unread; + } + + /** + * @deprecated This method does not support the new mail system and should not be used. + */ + @Deprecated + abstract void addMail(final String mail); + + public ArrayList getMailMessages() { + return new ArrayList<>(holder.mail()); } - public void addMail(final String mail) { - holder.mail().add(mail); + public void setMailList(ArrayList messages) { + holder.mail(messages); config.save(); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandmail.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandmail.java index d966d91dba8..1f2595dc520 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandmail.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandmail.java @@ -1,18 +1,23 @@ package com.earth2me.essentials.commands; import com.earth2me.essentials.CommandSource; +import com.earth2me.essentials.Console; import com.earth2me.essentials.User; -import com.earth2me.essentials.textreader.IText; +import com.earth2me.essentials.messaging.IMessageRecipient; import com.earth2me.essentials.textreader.SimpleTextInput; import com.earth2me.essentials.textreader.TextPager; import com.earth2me.essentials.utils.DateUtil; import com.earth2me.essentials.utils.FormatUtil; +import com.earth2me.essentials.utils.NumberUtil; import com.earth2me.essentials.utils.StringUtil; import com.google.common.collect.Lists; +import net.essentialsx.api.v2.services.mail.MailMessage; import org.bukkit.Server; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.ListIterator; import java.util.UUID; import static com.earth2me.essentials.I18n.tl; @@ -28,17 +33,35 @@ public Commandmail() { @Override public void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception { if (args.length >= 1 && "read".equalsIgnoreCase(args[0])) { - final List mail = user.getMails(); - if (mail.isEmpty()) { + final ArrayList mail = user.getMailMessages(); + if (mail == null || mail.size() == 0) { + user.sendMessage(tl("noMail")); + throw new NoChargeException(); + } + + final SimpleTextInput input = new SimpleTextInput(); + final ListIterator iterator = mail.listIterator(); + while (iterator.hasNext()) { + final MailMessage mailObj = iterator.next(); + if (mailObj.isExpired()) { + iterator.remove(); + continue; + } + input.addLine(ess.getMail().getMailLine(mailObj)); + iterator.set(new MailMessage(true, mailObj.isLegacy(), mailObj.getSenderUsername(), + mailObj.getSenderUUID(), mailObj.getTimeSent(), mailObj.getTimeExpire(), mailObj.getMessage())); + } + + if (input.getLines().isEmpty()) { user.sendMessage(tl("noMail")); throw new NoChargeException(); } - final IText input = new SimpleTextInput(mail); final TextPager pager = new TextPager(input); pager.showPage(args.length > 1 ? args[1] : null, null, commandLabel + " " + args[0], user.getSource()); user.sendMessage(tl("mailClear")); + user.setMailList(mail); return; } if (args.length >= 3 && "send".equalsIgnoreCase(args[0])) { @@ -61,8 +84,8 @@ public void run(final Server server, final User user, final String commandLabel, throw new Exception(tl("playerNeverOnServer", args[1])); } - final String mail = tl("mailFormat", user.getName(), FormatUtil.formatMessage(user, "essentials.mail", StringUtil.sanitizeString(FormatUtil.stripFormat(getFinalArg(args, 2))))); - if (mail.length() > 1000) { + final String msg = FormatUtil.formatMessage(user, "essentials.mail", StringUtil.sanitizeString(FormatUtil.stripFormat(getFinalArg(args, 2)))); + if (msg.length() > 1000) { throw new Exception(tl("mailTooLong")); } @@ -75,29 +98,87 @@ public void run(final Server server, final User user, final String commandLabel, if (mailsPerMinute > ess.getSettings().getMailsPerMinute()) { throw new Exception(tl("mailDelay", ess.getSettings().getMailsPerMinute())); } - u.addMail(tl("mailMessage", mail)); + u.sendMail(user, msg); } user.sendMessage(tl("mailSentTo", u.getDisplayName(), u.getName())); - user.sendMessage(mail); + user.sendMessage(msg); + return; + } + if (args.length >= 4 && "sendtemp".equalsIgnoreCase(args[0])) { + if (!user.isAuthorized("essentials.mail.sendtemp")) { + throw new Exception(tl("noPerm", "essentials.mail.sendtemp")); + } + + if (user.isMuted()) { + final String dateDiff = user.getMuteTimeout() > 0 ? DateUtil.formatDateDiff(user.getMuteTimeout()) : null; + if (dateDiff == null) { + throw new Exception(user.hasMuteReason() ? tl("voiceSilencedReason", user.getMuteReason()) : tl("voiceSilenced")); + } + throw new Exception(user.hasMuteReason() ? tl("voiceSilencedReasonTime", dateDiff, user.getMuteReason()) : tl("voiceSilencedTime", dateDiff)); + } + + final User u; + try { + u = getPlayer(server, args[1], true, true); + } catch (final PlayerNotFoundException e) { + throw new Exception(tl("playerNeverOnServer", args[1])); + } + + final long dateDiff = DateUtil.parseDateDiff(args[2], true); + + final String msg = FormatUtil.formatMessage(user, "essentials.mail", StringUtil.sanitizeString(FormatUtil.stripFormat(getFinalArg(args, 3)))); + if (msg.length() > 1000) { + throw new Exception(tl("mailTooLong")); + } + + if (!u.isIgnoredPlayer(user)) { + if (Math.abs(System.currentTimeMillis() - timestamp) > 60000) { + timestamp = System.currentTimeMillis(); + mailsPerMinute = 0; + } + mailsPerMinute++; + if (mailsPerMinute > ess.getSettings().getMailsPerMinute()) { + throw new Exception(tl("mailDelay", ess.getSettings().getMailsPerMinute())); + } + u.sendMail(user, msg, dateDiff); + } + + user.sendMessage(tl("mailSentToExpire", u.getDisplayName(), DateUtil.formatDateDiff(dateDiff), u.getName())); + user.sendMessage(msg); return; } if (args.length > 1 && "sendall".equalsIgnoreCase(args[0])) { if (!user.isAuthorized("essentials.mail.sendall")) { throw new Exception(tl("noPerm", "essentials.mail.sendall")); } - ess.runTaskAsynchronously(new SendAll(tl("mailFormat", user.getName(), - FormatUtil.formatMessage(user, "essentials.mail", StringUtil.sanitizeString(FormatUtil.stripFormat(getFinalArg(args, 1))))))); + ess.runTaskAsynchronously(new SendAll(user, FormatUtil.formatMessage(user, "essentials.mail", StringUtil.sanitizeString(FormatUtil.stripFormat(getFinalArg(args, 1)))))); user.sendMessage(tl("mailSent")); return; } if (args.length >= 1 && "clear".equalsIgnoreCase(args[0])) { - if (user.getMails() == null || user.getMails().isEmpty()) { + final ArrayList mails = user.getMailMessages(); + if (mails == null || mails.size() == 0) { user.sendMessage(tl("noMail")); throw new NoChargeException(); } - user.setMails(null); + if (args.length > 1) { + if (!NumberUtil.isPositiveInt(args[1])) { + throw new NotEnoughArgumentsException(); + } + + final int toRemove = Integer.parseInt(args[1]); + if (toRemove > mails.size()) { + user.sendMessage(tl("mailClearIndex", mails.size())); + return; + } + mails.remove(toRemove - 1); + user.setMailList(mails); + } else { + user.setMailList(null); + } + user.sendMessage(tl("mailCleared")); return; } @@ -117,11 +198,22 @@ protected void run(final Server server, final CommandSource sender, final String } catch (final PlayerNotFoundException e) { throw new Exception(tl("playerNeverOnServer", args[1])); } - u.addMail(tl("mailFormat", "Server", FormatUtil.replaceFormat(getFinalArg(args, 2)))); + u.sendMail(Console.getInstance(), FormatUtil.replaceFormat(getFinalArg(args, 2))); + sender.sendMessage(tl("mailSent")); + return; + } else if (args.length >= 4 && "sendtemp".equalsIgnoreCase(args[0])) { + final User u; + try { + u = getPlayer(server, args[1], true, true); + } catch (final PlayerNotFoundException e) { + throw new Exception(tl("playerNeverOnServer", args[1])); + } + final long dateDiff = DateUtil.parseDateDiff(args[2], true); + u.sendMail(Console.getInstance(), FormatUtil.replaceFormat(getFinalArg(args, 3)), dateDiff); sender.sendMessage(tl("mailSent")); return; } else if (args.length >= 2 && "sendall".equalsIgnoreCase(args[0])) { - ess.runTaskAsynchronously(new SendAll(tl("mailFormat", "Server", FormatUtil.replaceFormat(getFinalArg(args, 1))))); + ess.runTaskAsynchronously(new SendAll(Console.getInstance(), FormatUtil.replaceFormat(getFinalArg(args, 1)))); sender.sendMessage(tl("mailSent")); return; } else if (args.length >= 2) { @@ -132,13 +224,33 @@ protected void run(final Server server, final CommandSource sender, final String } catch (final PlayerNotFoundException e) { throw new Exception(tl("playerNeverOnServer", args[0])); } - u.addMail(tl("mailFormat", "Server", FormatUtil.replaceFormat(getFinalArg(args, 1)))); + u.sendMail(Console.getInstance(), FormatUtil.replaceFormat(getFinalArg(args, 1))); sender.sendMessage(tl("mailSent")); return; } throw new NotEnoughArgumentsException(); } + private class SendAll implements Runnable { + IMessageRecipient messageRecipient; + String message; + + SendAll(IMessageRecipient messageRecipient, String message) { + this.messageRecipient = messageRecipient; + this.message = message; + } + + @Override + public void run() { + for (UUID userid : ess.getUserMap().getAllUniqueUsers()) { + final User user = ess.getUserMap().getUser(userid); + if (user != null) { + user.sendMail(messageRecipient, message); + } + } + } + } + @Override protected List getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) { if (args.length == 1) { @@ -146,15 +258,18 @@ protected List getTabCompleteOptions(final Server server, final User use if (user.isAuthorized("essentials.mail.send")) { options.add("send"); } + if (user.isAuthorized("essentials.mail.sendtemp")) { + options.add("sendtemp"); + } if (user.isAuthorized("essentials.mail.sendall")) { options.add("sendall"); } return options; - } else if (args.length == 2 && args[0].equalsIgnoreCase("send") && user.isAuthorized("essentials.mail.send")) { + } else if (args.length == 2 && ((args[0].equalsIgnoreCase("send") && user.isAuthorized("essentials.mail.send")) || (args[0].equalsIgnoreCase("sendtemp") && user.isAuthorized("essentials.mail.sendtemp")))) { return getPlayers(server, user); } else if (args.length == 2 && args[0].equalsIgnoreCase("read")) { - final List mail = user.getMails(); - final int pages = mail.size() / 9 + (mail.size() % 9 > 0 ? 1 : 0); + final ArrayList mail = user.getMailMessages(); + final int pages = mail != null ? (mail.size() / 9 + (mail.size() % 9 > 0 ? 1 : 0)) : 0; if (pages == 0) { return Lists.newArrayList("0"); } else { @@ -164,8 +279,8 @@ protected List getTabCompleteOptions(final Server server, final User use } return options; } - } else if ((args.length > 2 && args[0].equalsIgnoreCase("send") && user.isAuthorized("essentials.mail.send")) || (args.length > 1 && args[0].equalsIgnoreCase("sendall") && user.isAuthorized("essentials.mail.sendall"))) { - return null; // Use vanilla handler + } else if (args.length == 3 && args[0].equalsIgnoreCase("sendtemp") && user.isAuthorized("essentials.mail.sendtemp")) { + return COMMON_DATE_DIFFS; } else { return Collections.emptyList(); } @@ -175,30 +290,14 @@ protected List getTabCompleteOptions(final Server server, final User use protected List getTabCompleteOptions(final Server server, final CommandSource sender, final String commandLabel, final String[] args) { if (args.length == 1) { return Lists.newArrayList("send", "sendall"); - } else if (args.length == 2 && args[0].equalsIgnoreCase("send")) { + } else if (args.length == 2 && (args[0].equalsIgnoreCase("send") || args[0].equalsIgnoreCase("sendtemp"))) { return getPlayers(server, sender); + } else if (args.length == 3 && args[0].equalsIgnoreCase("sentemp")) { + return COMMON_DATE_DIFFS; } else if ((args.length > 2 && args[0].equalsIgnoreCase("send")) || (args.length > 1 && args[0].equalsIgnoreCase("sendall"))) { return null; // Use vanilla handler } else { return Collections.emptyList(); } } - - private class SendAll implements Runnable { - final String message; - - SendAll(final String message) { - this.message = message; - } - - @Override - public void run() { - for (final UUID userid : ess.getUserMap().getAllUniqueUsers()) { - final User user = ess.getUserMap().getUser(userid); - if (user != null) { - user.addMail(message); - } - } - } - } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java index 95c05f7acbe..1e9988a5378 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java +++ b/Essentials/src/main/java/com/earth2me/essentials/config/EssentialsConfiguration.java @@ -9,8 +9,10 @@ import com.earth2me.essentials.config.serializers.BigDecimalTypeSerializer; import com.earth2me.essentials.config.serializers.CommandCooldownSerializer; import com.earth2me.essentials.config.serializers.LocationTypeSerializer; +import com.earth2me.essentials.config.serializers.MailMessageSerializer; import com.earth2me.essentials.config.serializers.MaterialTypeSerializer; import net.ess3.api.InvalidWorldException; +import net.essentialsx.api.v2.services.mail.MailMessage; import org.bukkit.Location; import org.bukkit.Material; import org.spongepowered.configurate.CommentedConfigurationNode; @@ -26,6 +28,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Type; import java.math.BigDecimal; import java.nio.file.Files; import java.util.ArrayList; @@ -58,6 +61,7 @@ public class EssentialsConfiguration { .register(LazyLocation.class, new LocationTypeSerializer()) .register(Material.class, new MaterialTypeSerializer()) .register(CommandCooldown.class, new CommandCooldownSerializer()) + .register(MailMessage.class, new MailMessageSerializer()) .build(); private final AtomicInteger pendingWrites = new AtomicInteger(0); @@ -139,6 +143,14 @@ public void setProperty(final String path, final List list) { setInternal(path, list); } + public void setExplicitList(final String path, final List list, final Type type) { + try { + toSplitRoot(path, configurationNode).set(type, list); + } catch (SerializationException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + } + public List getList(final String path, Class type) { final CommentedConfigurationNode node = getInternal(path); if (node == null) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java b/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java index e7b1c36bdce..5f1de20c26f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java +++ b/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java @@ -4,6 +4,7 @@ import com.earth2me.essentials.config.annotations.DeleteOnEmpty; import com.earth2me.essentials.config.entities.CommandCooldown; import com.earth2me.essentials.config.entities.LazyLocation; +import net.essentialsx.api.v2.services.mail.MailMessage; import org.bukkit.Location; import org.bukkit.Material; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -113,16 +114,16 @@ public void jail(final String value) { } @DeleteOnEmpty - private @MonotonicNonNull List mail; + private @MonotonicNonNull ArrayList mail; - public List mail() { + public ArrayList mail() { if (this.mail == null) { this.mail = new ArrayList<>(); } return this.mail; } - public void mail(final List value) { + public void mail(final ArrayList value) { this.mail = value; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/config/serializers/MailMessageSerializer.java b/Essentials/src/main/java/com/earth2me/essentials/config/serializers/MailMessageSerializer.java new file mode 100644 index 00000000000..45aec4235b5 --- /dev/null +++ b/Essentials/src/main/java/com/earth2me/essentials/config/serializers/MailMessageSerializer.java @@ -0,0 +1,44 @@ +package com.earth2me.essentials.config.serializers; + +import net.essentialsx.api.v2.services.mail.MailMessage; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.serialize.SerializationException; +import org.spongepowered.configurate.serialize.TypeSerializer; + +import java.lang.reflect.Type; +import java.util.Objects; +import java.util.UUID; + +public class MailMessageSerializer implements TypeSerializer { + @Override + public MailMessage deserialize(Type type, ConfigurationNode node) throws SerializationException { + final boolean legacy = !node.node("legacy").isNull() && node.node("legacy").getBoolean(false); + + return new MailMessage(node.node("read").getBoolean(false), + legacy, + !legacy ? node.node("sender-name").getString() : null, + !legacy ? UUID.fromString(Objects.requireNonNull(node.node("sender-uuid").getString())) : null, + !legacy ? node.node("timestamp").getLong() : 0L, + !legacy ? node.node("expire").getLong() : 0L, + node.node("message").getString()); + } + + @Override + public void serialize(Type type, @Nullable MailMessage obj, ConfigurationNode node) throws SerializationException { + if (obj == null) { + node.raw(null); + return; + } + + node.node("legacy").set(Boolean.class, obj.isLegacy()); + node.node("read").set(Boolean.class, obj.isRead()); + node.node("message").set(String.class, obj.getMessage()); + if (!obj.isLegacy()) { + node.node("sender-name").set(String.class, obj.getSenderUsername()); + node.node("sender-uuid").set(String.class, obj.getSenderUUID().toString()); + node.node("timestamp").set(Long.class, obj.getTimeSent()); + node.node("expire").set(Long.class, obj.getTimeExpire()); + } + } +} diff --git a/Essentials/src/main/java/com/earth2me/essentials/messaging/IMessageRecipient.java b/Essentials/src/main/java/com/earth2me/essentials/messaging/IMessageRecipient.java index 77a208c9734..c4f53c0c9e5 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/messaging/IMessageRecipient.java +++ b/Essentials/src/main/java/com/earth2me/essentials/messaging/IMessageRecipient.java @@ -1,11 +1,12 @@ package com.earth2me.essentials.messaging; +import net.essentialsx.api.v2.services.mail.MailSender; import org.bukkit.entity.Player; /** * Represents an interface for message recipients. */ -public interface IMessageRecipient { +public interface IMessageRecipient extends MailSender { /** * Sends (prints) a message to this recipient. diff --git a/Essentials/src/main/java/com/earth2me/essentials/messaging/SimpleMessageRecipient.java b/Essentials/src/main/java/com/earth2me/essentials/messaging/SimpleMessageRecipient.java index 4ebed9f13f3..0dcd4349498 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/messaging/SimpleMessageRecipient.java +++ b/Essentials/src/main/java/com/earth2me/essentials/messaging/SimpleMessageRecipient.java @@ -8,6 +8,7 @@ import org.bukkit.entity.Player; import java.lang.ref.WeakReference; +import java.util.UUID; import static com.earth2me.essentials.I18n.tl; @@ -60,6 +61,11 @@ public String getName() { return this.parent.getName(); } + @Override + public UUID getUUID() { + return this.parent.getUUID(); + } + @Override public String getDisplayName() { return this.parent.getDisplayName(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/signs/SignMail.java b/Essentials/src/main/java/com/earth2me/essentials/signs/SignMail.java index fe0a3456099..105b9492a91 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/signs/SignMail.java +++ b/Essentials/src/main/java/com/earth2me/essentials/signs/SignMail.java @@ -2,8 +2,10 @@ import com.earth2me.essentials.User; import net.ess3.api.IEssentials; +import net.essentialsx.api.v2.services.mail.MailMessage; -import java.util.List; +import java.util.ArrayList; +import java.util.ListIterator; import static com.earth2me.essentials.I18n.tl; @@ -14,14 +16,28 @@ public SignMail() { @Override protected boolean onSignInteract(final ISign sign, final User player, final String username, final IEssentials ess) throws SignException { - final List mail = player.getMails(); - if (mail.isEmpty()) { + final ArrayList mail = player.getMailMessages(); + + final ListIterator iterator = mail.listIterator(); + boolean hadMail = false; + while (iterator.hasNext()) { + final MailMessage mailObj = iterator.next(); + if (mailObj.isExpired()) { + iterator.remove(); + continue; + } + hadMail = true; + player.sendMessage(ess.getMail().getMailLine(mailObj)); + iterator.set(new MailMessage(true, mailObj.isLegacy(), mailObj.getSenderUsername(), + mailObj.getSenderUUID(), mailObj.getTimeSent(), mailObj.getTimeExpire(), mailObj.getMessage())); + } + + if (!hadMail) { player.sendMessage(tl("noNewMail")); return false; } - for (final String s : mail) { - player.sendMessage(s); - } + player.setMailList(mail); + player.sendMessage(tl("markMailAsRead")); return true; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java b/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java index 365b0b119c7..3bc099e0812 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java +++ b/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java @@ -227,7 +227,7 @@ private String replaceLine(String line, final String fullMatch, final String[] m break; case MAILS: if (user != null) { - replacer = Integer.toString(user.getMails().size()); + replacer = Integer.toString(user.getMailAmount()); } break; case PLAYTIME: diff --git a/Essentials/src/main/java/com/earth2me/essentials/textreader/SimpleTextInput.java b/Essentials/src/main/java/com/earth2me/essentials/textreader/SimpleTextInput.java index 1627fcbcc0d..9c74c24501c 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/textreader/SimpleTextInput.java +++ b/Essentials/src/main/java/com/earth2me/essentials/textreader/SimpleTextInput.java @@ -20,6 +20,10 @@ public SimpleTextInput(final List input) { public SimpleTextInput() { } + public void addLine(String line) { + lines.add(line); + } + @Override public List getLines() { return lines; diff --git a/Essentials/src/main/java/net/essentialsx/api/v2/services/mail/MailMessage.java b/Essentials/src/main/java/net/essentialsx/api/v2/services/mail/MailMessage.java new file mode 100644 index 00000000000..c2ad168bcf2 --- /dev/null +++ b/Essentials/src/main/java/net/essentialsx/api/v2/services/mail/MailMessage.java @@ -0,0 +1,98 @@ +package net.essentialsx.api.v2.services.mail; + +import java.util.UUID; + +/** + * An immutable representation of a message sent as mail. + */ +public class MailMessage { + private final boolean read; + private final boolean legacy; + private final String senderName; + private final UUID senderId; + private final long timestamp; + private final long expire; + private final String message; + + public MailMessage(boolean read, boolean legacy, String sender, UUID uuid, long timestamp, long expire, String message) { + this.read = read; + this.legacy = legacy; + this.senderName = sender; + this.senderId = uuid; + this.timestamp = timestamp; + this.expire = expire; + this.message = message; + } + + /** + * Checks if this message has been read by its recipient yet. + * @return true if this message has been read. + */ + public boolean isRead() { + return read; + } + + /** + * Checks if this message was created via legacy api or converted from legacy format. + * + * A legacy messages only contains data for the read state and message. + * @see #isRead() + * @see #getMessage() + * @return true if this message is a legacy message. + */ + public boolean isLegacy() { + return legacy; + } + + /** + * Gets the sender's username at the time of sending the message. + * @return The sender's username. + */ + public String getSenderUsername() { + return senderName; + } + + /** + * Gets the sender's {@link UUID} or null if the sender does not have a UUID. + * @return The sender's {@link UUID} or null. + */ + public UUID getSenderUUID() { + return senderId; + } + + /** + * Gets the millisecond epoch time when the message was sent. + * @return The epoch time when message was sent. + */ + public long getTimeSent() { + return timestamp; + } + + /** + * Gets the millisecond epoch at which this message will expire and no longer been shown to the user. + * @return The epoch time when the message will expire. + */ + public long getTimeExpire() { + return expire; + } + + /** + * Gets the message content for normal mail or the entire mail format for legacy mail. + * @see #isLegacy() + * @return The mail content or format. + */ + public String getMessage() { + return message; + } + + /** + * Helper method to check if this mail has expired and should not been shown to the recipient. + * @return true if this mail has expired. + */ + public boolean isExpired() { + if (getTimeExpire() == 0L) { + return false; + } + return System.currentTimeMillis() >= getTimeExpire(); + } +} diff --git a/Essentials/src/main/java/net/essentialsx/api/v2/services/mail/MailSender.java b/Essentials/src/main/java/net/essentialsx/api/v2/services/mail/MailSender.java new file mode 100644 index 00000000000..2c168bc4fb8 --- /dev/null +++ b/Essentials/src/main/java/net/essentialsx/api/v2/services/mail/MailSender.java @@ -0,0 +1,22 @@ +package net.essentialsx.api.v2.services.mail; + +import java.util.UUID; + +/** + * An entity which is allowed to send mail to an {@link net.ess3.api.IUser IUser}. + * + * In Essentials, IUser and Console are the entities that implement this interface. + */ +public interface MailSender { + /** + * Gets the username of this {@link MailSender}. + * @return The sender's username. + */ + String getName(); + + /** + * Gets the {@link UUID} of this {@link MailSender} or null if this sender doesn't have a UUID. + * @return The sender's {@link UUID} or null if N/A. + */ + UUID getUUID(); +} diff --git a/Essentials/src/main/java/net/essentialsx/api/v2/services/mail/MailService.java b/Essentials/src/main/java/net/essentialsx/api/v2/services/mail/MailService.java new file mode 100644 index 00000000000..26edc78bf74 --- /dev/null +++ b/Essentials/src/main/java/net/essentialsx/api/v2/services/mail/MailService.java @@ -0,0 +1,43 @@ +package net.essentialsx.api.v2.services.mail; + +import net.ess3.api.IUser; + +/** + * This interface provides access to core Essentials mailing functions, allowing API users to send messages to {@link IUser IUser's }. + */ +public interface MailService { + /** + * Sends a message from the specified {@link MailSender sender} to the specified {@link IUser recipient}. + * @param recipient The {@link IUser} which to send the message to. + * @param sender The {@link MailSender} which sent the message. + * @param message The message content. + */ + void sendMail(IUser recipient, MailSender sender, String message); + + /** + * Sends a message from the specified {@link MailSender sender} to the specified {@link IUser recipient}. + * @param recipient The {@link IUser} which to send the message to. + * @param sender The {@link MailSender} which sent the message. + * @param message The message content. + * @param expireAt The millisecond epoch at which this message expires, or 0 if the message doesn't expire. + */ + void sendMail(IUser recipient, MailSender sender, String message, long expireAt); + + /** + * Sends a legacy message to the user without any advanced features. + * @param recipient The {@link IUser} which to send the message to. + * @param message The message content. + * @see #sendMail(IUser, MailSender, String) + * @see #sendMail(IUser, MailSender, String, long) + * @deprecated This is only for maintaining backwards compatibility with old API, please use the newer {@link #sendMail(IUser, MailSender, String)} API. + */ + @Deprecated + void sendLegacyMail(IUser recipient, String message); + + /** + * Generates the message sent to the recipient of the given {@link MailMessage}. + * @param message The {@link MailMessage} to generate the message for. + * @return The formatted message to be sent to the recipient. + */ + String getMailLine(MailMessage message); +} diff --git a/Essentials/src/main/resources/messages.properties b/Essentials/src/main/resources/messages.properties index 86813f672bc..38928e65238 100644 --- a/Essentials/src/main/resources/messages.properties +++ b/Essentials/src/main/resources/messages.properties @@ -647,21 +647,29 @@ loomCommandDescription=Opens up a loom. loomCommandUsage=/ mailClear=\u00a76To clear your mail, type\u00a7c /mail clear\u00a76. mailCleared=\u00a76Mail cleared\! +mailClearIndex=\u00a74You must specify a number between 1-{0}. mailCommandDescription=Manages inter-player, intra-server mail. -mailCommandUsage=/ [read|clear|send [to] [message]|sendall [message]] +mailCommandUsage=/ [read|clear|clear [number]|send [to] [message]|sendtemp [to] [expire time] [message]|sendall [message]] mailCommandUsage1=/ read [page] mailCommandUsage1Description=Reads the first (or specified) page of your mail -mailCommandUsage2=/ clear -mailCommandUsage2Description=Clears your mail +mailCommandUsage2=/ clear [number] +mailCommandUsage2Description=Clears either all or the specified mail(s) mailCommandUsage3=/ send mailCommandUsage3Description=Sends the specified player the given message mailCommandUsage4=/ sendall mailCommandUsage4Description=Sends all players the given message +mailCommandUsage5=/ sendtemp +mailCommandUsage5Description=Sends the specified player the given message which will expire in the specified time mailDelay=Too many mails have been sent within the last minute. Maximum\: {0} +mailFormatNew=\u00a76[\u00a7r{0}\u00a76] \u00a76[\u00a7r{1}\u00a76] \u00a7r{2} +mailFormatNewTimed=\u00a76[\u00a7e\u26a0\u00a76] \u00a76[\u00a7r{0}\u00a76] \u00a76[\u00a7r{1}\u00a76] \u00a7r{2} +mailFormatNewRead=\u00a76[\u00a7r{0}\u00a76] \u00a76[\u00a7r{1}\u00a76] \u00a77\u00a7o{2} +mailFormatNewReadTimed=\u00a76[\u00a7e\u26a0\u00a76] \u00a76[\u00a7r{0}\u00a76] \u00a76[\u00a7r{1}\u00a76] \u00a77\u00a7o{2} mailFormat=\u00a76[\u00a7r{0}\u00a76] \u00a7r{1} mailMessage={0} mailSent=\u00a76Mail sent\! mailSentTo=\u00a7c{0}\u00a76 has been sent the following mail\: +mailSentToExpire=\u00a7c{0}\u00a76 has been sent the following mail which will expire in \u00a7c{1}\u00a76\: mailTooLong=\u00a74Mail message too long. Try to keep it below 1000 characters. markMailAsRead=\u00a76To mark your mail as read, type\u00a7c /mail clear\u00a76. matchingIPAddress=\u00a76The following players previously logged in from that IP address\: diff --git a/Essentials/src/main/resources/plugin.yml b/Essentials/src/main/resources/plugin.yml index e96ec4affa5..a80ab358fc9 100644 --- a/Essentials/src/main/resources/plugin.yml +++ b/Essentials/src/main/resources/plugin.yml @@ -286,7 +286,7 @@ commands: aliases: [eloom] mail: description: Manages inter-player, intra-server mail. - usage: / [read|clear|send [to] [message]|sendall [message]] + usage: / [read|clear|clear [number]|send [to] [message]|sendtemp [to] [expire time] [message]|sendall [message]] aliases: [email,eemail,memo,ememo] me: description: Describes an action in the context of the player. diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordMessageRecipient.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordMessageRecipient.java index c047917c637..87f71487819 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordMessageRecipient.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordMessageRecipient.java @@ -5,6 +5,7 @@ import net.essentialsx.api.v2.services.discord.InteractionMember; import org.bukkit.entity.Player; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import static com.earth2me.essentials.I18n.tl; @@ -48,6 +49,11 @@ public String getName() { return member.getTag(); } + @Override + public UUID getUUID() { + return null; + } + @Override public String getDisplayName() { return member.getTag();