Rework Mail System (#3710)

* New `/mail sendtemp <time diff> <message>` command to send mail that will self-destruct after time diff.
* New `/mail clear <number>` command to clear a specific mail item.
* `/mail read` now tracks which mails you read and won't nag you about them.
* A bunch of other flexibility since we store actual data instead of strings
This commit is contained in:
Josh Roy 2021-07-01 11:23:32 -04:00 committed by GitHub
parent 55db6c2476
commit 9c451271e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 622 additions and 65 deletions

View File

@ -6,6 +6,8 @@ import org.bukkit.Server;
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 final class Console implements IMessageRecipient {
return Console.NAME;
}
@Override
public UUID getUUID() {
return null;
}
@Override
public String getDisplayName() {
return Console.DISPLAY_NAME;

View File

@ -82,6 +82,7 @@ import net.ess3.provider.providers.PaperRecipeBookListener;
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 class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
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 class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
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 class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return timer;
}
@Override
public MailService getMail() {
return mail;
}
@Override
public List<String> getVanishedPlayers() {
return Collections.unmodifiableList(new ArrayList<>(vanishedPlayers));

View File

@ -364,8 +364,7 @@ public class EssentialsPlayerListener implements Listener {
}
if (!ess.getSettings().isCommandDisabled("mail") && user.isAuthorized("essentials.mail")) {
final List<String> 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.
}

View File

@ -7,7 +7,9 @@ import com.earth2me.essentials.craftbukkit.BanLookup;
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 class EssentialsUpgrade {
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<MailMessage> 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<List<MailMessage>>() {}.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 class EssentialsUpgrade {
repairUserMap();
convertIgnoreList();
convertStupidCamelCaseUserdataKeys();
convertMailList();
}
}

View File

@ -18,6 +18,7 @@ import net.ess3.provider.SpawnerBlockProvider;
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.
*

View File

@ -6,12 +6,15 @@ import com.earth2me.essentials.config.entities.CommandCooldown;
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<String> getMails();
@Deprecated
void addMail(String mail);
void sendMail(MailSender sender, String message);
void sendMail(MailSender sender, String message, long expireAt);
ArrayList<MailMessage> getMailMessages();
void setMailList(ArrayList<MailMessage> messages);
int getMailAmount();
boolean isAfk();
@Deprecated

View File

@ -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<SimpleDateFormat> 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<MailMessage> 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);
}
}

View File

@ -5,11 +5,11 @@ import com.earth2me.essentials.economy.EconomyLayer;
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.JailStatusChangeEvent;
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 class User extends UserData implements Comparable<User>, IMessageRecipien
return this.getBase().getName();
}
@Override
public UUID getUUID() {
return getBase().getUniqueId();
}
@Override
public boolean isReachable() {
return getBase().isOnline();
@ -1054,12 +1060,28 @@ public class User extends UserData implements Comparable<User>, IMessageRecipien
}
}
@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<String> 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();
}
}

View File

@ -10,6 +10,8 @@ import com.earth2me.essentials.utils.StringUtil;
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 abstract class UserData extends PlayerExtension implements IConf {
config.save();
}
/**
* @deprecated Mails are no longer just strings, this method is therefore misleading.
*/
@Deprecated
public List<String> getMails() {
return holder.mail();
final List<String> 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<String> mails) {
holder.mail(mails);
config.save();
throw new UnsupportedOperationException("UserData#setMails(List<String>) is deprecated and can no longer be used. Please tell the plugin author to update this!");
}
public void addMail(final String mail) {
holder.mail().add(mail);
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<MailMessage> getMailMessages() {
return new ArrayList<>(holder.mail());
}
public void setMailList(ArrayList<MailMessage> messages) {
holder.mail(messages);
config.save();
}

View File

@ -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 class Commandmail extends EssentialsCommand {
@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<String> mail = user.getMails();
if (mail.isEmpty()) {
final ArrayList<MailMessage> mail = user.getMailMessages();
if (mail == null || mail.size() == 0) {
user.sendMessage(tl("noMail"));
throw new NoChargeException();
}
final SimpleTextInput input = new SimpleTextInput();
final ListIterator<MailMessage> 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 class Commandmail extends EssentialsCommand {
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 class Commandmail extends EssentialsCommand {
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<MailMessage> 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 @@ public class Commandmail extends EssentialsCommand {
} 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 @@ public class Commandmail extends EssentialsCommand {
} 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<String> getTabCompleteOptions(final Server server, final User user, final String commandLabel, final String[] args) {
if (args.length == 1) {
@ -146,15 +258,18 @@ public class Commandmail extends EssentialsCommand {
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<String> mail = user.getMails();
final int pages = mail.size() / 9 + (mail.size() % 9 > 0 ? 1 : 0);
final ArrayList<MailMessage> 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 @@ public class Commandmail extends EssentialsCommand {
}
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 @@ public class Commandmail extends EssentialsCommand {
protected List<String> 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);
}
}
}
}
}

View File

@ -9,8 +9,10 @@ import com.earth2me.essentials.config.processors.DeleteOnEmptyProcessor;
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 org.spongepowered.configurate.yaml.YamlConfigurationLoader;
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 class EssentialsConfiguration {
setInternal(path, list);
}
public <T> void setExplicitList(final String path, final List<T> list, final Type type) {
try {
toSplitRoot(path, configurationNode).set(type, list);
} catch (SerializationException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
}
}
public <T> List<T> getList(final String path, Class<T> type) {
final CommentedConfigurationNode node = getInternal(path);
if (node == null) {

View File

@ -4,6 +4,7 @@ import com.earth2me.essentials.config.annotations.DeleteIfIncomplete;
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 class UserConfigHolder {
}
@DeleteOnEmpty
private @MonotonicNonNull List<String> mail;
private @MonotonicNonNull ArrayList<MailMessage> mail;
public List<String> mail() {
public ArrayList<MailMessage> mail() {
if (this.mail == null) {
this.mail = new ArrayList<>();
}
return this.mail;
}
public void mail(final List<String> value) {
public void mail(final ArrayList<MailMessage> value) {
this.mail = value;
}

View File

@ -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<MailMessage> {
@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());
}
}
}

View File

@ -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.

View File

@ -8,6 +8,7 @@ import net.ess3.api.events.PrivateMessageSentEvent;
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 class SimpleMessageRecipient implements IMessageRecipient {
return this.parent.getName();
}
@Override
public UUID getUUID() {
return this.parent.getUUID();
}
@Override
public String getDisplayName() {
return this.parent.getDisplayName();

View File

@ -2,8 +2,10 @@ package com.earth2me.essentials.signs;
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 class SignMail extends EssentialsSign {
@Override
protected boolean onSignInteract(final ISign sign, final User player, final String username, final IEssentials ess) throws SignException {
final List<String> mail = player.getMails();
if (mail.isEmpty()) {
final ArrayList<MailMessage> mail = player.getMailMessages();
final ListIterator<MailMessage> 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;
}

View File

@ -227,7 +227,7 @@ public class KeywordReplacer implements IText {
break;
case MAILS:
if (user != null) {
replacer = Integer.toString(user.getMails().size());
replacer = Integer.toString(user.getMailAmount());
}
break;
case PLAYTIME:

View File

@ -20,6 +20,10 @@ public class SimpleTextInput implements IText {
public SimpleTextInput() {
}
public void addLine(String line) {
lines.add(line);
}
@Override
public List<String> getLines() {
return lines;

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -647,21 +647,29 @@ loomCommandDescription=Opens up a loom.
loomCommandUsage=/<command>
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=/<command> [read|clear|send [to] [message]|sendall [message]]
mailCommandUsage=/<command> [read|clear|clear [number]|send [to] [message]|sendtemp [to] [expire time] [message]|sendall [message]]
mailCommandUsage1=/<command> read [page]
mailCommandUsage1Description=Reads the first (or specified) page of your mail
mailCommandUsage2=/<command> clear
mailCommandUsage2Description=Clears your mail
mailCommandUsage2=/<command> clear [number]
mailCommandUsage2Description=Clears either all or the specified mail(s)
mailCommandUsage3=/<command> send <player> <message>
mailCommandUsage3Description=Sends the specified player the given message
mailCommandUsage4=/<command> sendall <message>
mailCommandUsage4Description=Sends all players the given message
mailCommandUsage5=/<command> sendtemp <player> <expire time> <message>
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\:

View File

@ -286,7 +286,7 @@ commands:
aliases: [eloom]
mail:
description: Manages inter-player, intra-server mail.
usage: /<command> [read|clear|send [to] [message]|sendall [message]]
usage: /<command> [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.

View File

@ -5,6 +5,7 @@ import com.earth2me.essentials.utils.FormatUtil;
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 class DiscordMessageRecipient implements IMessageRecipient {
return member.getTag();
}
@Override
public UUID getUUID() {
return null;
}
@Override
public String getDisplayName() {
return member.getTag();