diff --git a/pom.xml b/pom.xml
index 6a163a2..69ce66e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,5 +81,11 @@
1.20.2-R0.1-SNAPSHOT
provided
+
+ com.zaxxer
+ HikariCP
+ 5.1.0
+ compile
+
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/CloudnodeMSG.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/CloudnodeMSG.java
index 8fdfe44..f17909e 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/CloudnodeMSG.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/CloudnodeMSG.java
@@ -1,19 +1,31 @@
package pro.cloudnode.smp.cloudnodemsg;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import pro.cloudnode.smp.cloudnodemsg.command.IgnoreCommand;
+import pro.cloudnode.smp.cloudnodemsg.command.MailCommand;
import pro.cloudnode.smp.cloudnodemsg.command.MainCommand;
import pro.cloudnode.smp.cloudnodemsg.command.MessageCommand;
import pro.cloudnode.smp.cloudnodemsg.command.ReplyCommand;
import pro.cloudnode.smp.cloudnodemsg.command.TeamMessageCommand;
+import pro.cloudnode.smp.cloudnodemsg.command.ToggleMessageCommand;
import pro.cloudnode.smp.cloudnodemsg.command.UnIgnoreCommand;
import pro.cloudnode.smp.cloudnodemsg.listener.AsyncChatListener;
-import pro.cloudnode.smp.cloudnodemsg.command.ToggleMessageCommand;
+import pro.cloudnode.smp.cloudnodemsg.listener.PlayerJoinListener;
+import java.io.InputStream;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Map;
import java.util.Objects;
+import java.util.logging.Level;
public final class CloudnodeMSG extends JavaPlugin {
public static @NotNull CloudnodeMSG getInstance() {
@@ -23,6 +35,8 @@ public final class CloudnodeMSG extends JavaPlugin {
public void reload() {
getInstance().reloadConfig();
getInstance().config.config = getInstance().getConfig();
+ setupDbSource();
+ runDDL();
}
@Override
@@ -37,13 +51,18 @@ public void onEnable() {
Objects.requireNonNull(getCommand("unignore")).setExecutor(new UnIgnoreCommand());
Objects.requireNonNull(getCommand("togglemsg")).setExecutor(new ToggleMessageCommand());
Objects.requireNonNull(getCommand("teammsg")).setExecutor(new TeamMessageCommand());
+ Objects.requireNonNull(getCommand("mail")).setExecutor(new MailCommand());
getServer().getPluginManager().registerEvents(new AsyncChatListener(), this);
+ getServer().getPluginManager().registerEvents(new PlayerJoinListener(), this);
+
+ minuteLoop = minuteLoop();
}
@Override
public void onDisable() {
- // Plugin shutdown logic
+ if (dbSource != null) dbSource.close();
+ if (minuteLoop != null) minuteLoop.cancel();
}
public static boolean isVanished(final @NotNull Player player) {
@@ -57,4 +76,67 @@ public static boolean isVanished(final @NotNull Player player) {
public @NotNull PluginConfig config() {
return config;
}
+
+ public final @NotNull HikariConfig hikariConfig = new HikariConfig();
+ private @Nullable HikariDataSource dbSource;
+
+ public @NotNull HikariDataSource db() {
+ assert dbSource != null;
+ return dbSource;
+ }
+
+ private void setupDbSource() {
+ if (dbSource != null) dbSource.close();
+ hikariConfig.setDriverClassName("org.sqlite.JDBC");
+ hikariConfig.setJdbcUrl("jdbc:sqlite:" + getDataFolder().getAbsolutePath() + "/" + config().dbSqliteFile());
+
+ for (final @NotNull Map.Entry<@NotNull String, @NotNull String> entry : config().dbHikariProperties().entrySet())
+ hikariConfig.addDataSourceProperty(entry.getKey(), entry.getValue());
+
+ dbSource = new HikariDataSource(hikariConfig);
+ }
+
+ /**
+ * Run DDL script
+ */
+ public void runDDL() {
+ final @NotNull String file = "ddl/sqlite.sql";
+ final @NotNull String @NotNull [] queries;
+ try (final @Nullable InputStream inputStream = getClassLoader().getResourceAsStream(file)) {
+ queries = Arrays.stream(
+ new String(Objects.requireNonNull(inputStream).readAllBytes()).split(";")
+ ).map(q -> q.stripTrailing().stripIndent().replaceAll("^\\s+(?:--.+)*", "")).toArray(String[]::new);
+ }
+ catch (final @NotNull Exception exception) {
+ getLogger().log(Level.SEVERE, "Could not read DDL script: " + file, exception);
+ getServer().getPluginManager().disablePlugin(this);
+ return;
+ }
+ for (final @NotNull String query : queries) {
+ if (query.isBlank()) continue;
+ try (final @NotNull PreparedStatement stmt = db().getConnection().prepareStatement(query)) {
+ stmt.execute();
+ }
+ catch (final @NotNull SQLException exception) {
+ getLogger().log(Level.SEVERE, "Could not execute DDL query: " + query, exception);
+ getServer().getPluginManager().disablePlugin(this);
+ return;
+ }
+ }
+ getLogger().info("Database successfully initialised with DDL");
+ }
+
+ /**
+ * Run code asynchronously
+ */
+ public static void runAsync(final @NotNull Runnable runnable) {
+ getInstance().getServer().getScheduler().runTaskAsynchronously(getInstance(), runnable);
+ }
+
+ private @NotNull BukkitTask minuteLoop() {
+ return getServer().getScheduler().runTaskTimerAsynchronously(this, () -> {
+ if ((System.currentTimeMillis() / 1000) % config().mailNotifyInterval() == 0) Mail.notifyUnread();
+ }, 0, 20 * 60);
+ }
+ private @Nullable BukkitTask minuteLoop;
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/Mail.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/Mail.java
new file mode 100644
index 0000000..296306f
--- /dev/null
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/Mail.java
@@ -0,0 +1,119 @@
+package pro.cloudnode.smp.cloudnodemsg;
+
+import org.bukkit.OfflinePlayer;
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.logging.Level;
+
+public final class Mail {
+ public final @NotNull UUID id;
+ public final @NotNull OfflinePlayer sender;
+ public final @NotNull OfflinePlayer recipient;
+ public final @NotNull String message;
+ public boolean seen;
+
+ public boolean starred;
+
+ public Mail(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient, final @NotNull String message) {
+ this.id = UUID.randomUUID();
+ this.sender = sender;
+ this.recipient = recipient;
+ this.message = message;
+ this.seen = false;
+ this.starred = false;
+ }
+
+ public Mail(final @NotNull ResultSet rs) throws SQLException {
+ final @NotNull Server server = CloudnodeMSG.getInstance().getServer();
+
+ this.id = UUID.fromString(rs.getString("id"));
+ this.sender = server.getOfflinePlayer(UUID.fromString(rs.getString("sender")));
+ this.recipient = server.getOfflinePlayer(UUID.fromString(rs.getString("recipient")));
+ this.message = rs.getString("message");
+ this.seen = rs.getBoolean("seen");
+ this.starred = rs.getBoolean("starred");
+ }
+
+ public void update() {
+ try (final @NotNull PreparedStatement stmt = CloudnodeMSG.getInstance().db().getConnection().prepareStatement("UPDATE `cloudnodemsg_mail` SET `seen` = ?, `starred` = ? WHERE `id` = ?")) {
+ stmt.setBoolean(1, seen);
+ stmt.setBoolean(2, starred);
+ stmt.setString(3, id.toString());
+ stmt.executeUpdate();
+ }
+ catch (final @NotNull SQLException exception) {
+ CloudnodeMSG.getInstance().getLogger().log(Level.SEVERE, "Could not update mail: " + id, exception);
+ }
+ }
+
+ public void delete() {
+ try (final @NotNull PreparedStatement stmt = CloudnodeMSG.getInstance().db().getConnection().prepareStatement("DELETE FROM `cloudnodemsg_mail` WHERE `id` = ?")) {
+ stmt.setString(1, id.toString());
+ stmt.executeUpdate();
+ }
+ catch (final @NotNull SQLException exception) {
+ CloudnodeMSG.getInstance().getLogger().log(Level.SEVERE, "Could not delete mail: " + id, exception);
+ }
+ }
+
+ public void insert() {
+ try (final @NotNull PreparedStatement stmt = CloudnodeMSG.getInstance().db().getConnection().prepareStatement("INSERT INTO `cloudnodemsg_mail` (`id`, `sender`, `recipient`, `message`, `seen`, `starred`) VALUES (?, ?, ?, ?, ?, ?)")) {
+ stmt.setString(1, id.toString());
+ stmt.setString(2, sender.getUniqueId().toString());
+ stmt.setString(3, recipient.getUniqueId().toString());
+ stmt.setString(4, message);
+ stmt.setBoolean(5, seen);
+ stmt.setBoolean(6, starred);
+ stmt.executeUpdate();
+ }
+ catch (final @NotNull SQLException exception) {
+ CloudnodeMSG.getInstance().getLogger().log(Level.SEVERE, "Could not insert mail: " + id, exception);
+ }
+ }
+
+ public static @NotNull Optional<@NotNull Mail> get(final @NotNull UUID id) {
+ try (final @NotNull PreparedStatement stmt = CloudnodeMSG.getInstance().db().getConnection().prepareStatement("SELECT * FROM `cloudnodemsg_mail` WHERE `id` = ? LIMIT 1")) {
+ stmt.setString(1, id.toString());
+ final @NotNull ResultSet rs = stmt.executeQuery();
+ if (!rs.next()) return Optional.empty();
+ return Optional.of(new Mail(rs));
+ }
+ catch (final @NotNull SQLException exception) {
+ CloudnodeMSG.getInstance().getLogger().log(Level.SEVERE, "Could not get mail: " + id, exception);
+ return Optional.empty();
+ }
+ }
+
+ public static int unread(final @NotNull OfflinePlayer player) {
+ try (final @NotNull PreparedStatement stmt = CloudnodeMSG.getInstance().db().getConnection().prepareStatement("SELECT COUNT(`id`) as `n` FROM `cloudnodemsg_mail` WHERE `recipient` = ? AND `seen` = 0")) {
+ stmt.setString(1, player.getUniqueId().toString());
+ final @NotNull ResultSet rs = stmt.executeQuery();
+ if (!rs.next()) return 0;
+ return rs.getInt("n");
+ }
+ catch (final @NotNull SQLException exception) {
+ CloudnodeMSG.getInstance().getLogger().log(Level.SEVERE, "Could not get unread mails: " + player.getName(), exception);
+ return 0;
+ }
+ }
+
+ /**
+ * Notify online players for their unread mail
+ */
+ public static void notifyUnread() {
+ CloudnodeMSG.runAsync(() -> {
+ for (final @NotNull Player player : CloudnodeMSG.getInstance().getServer().getOnlinePlayers()) {
+ if (!player.hasPermission(Permission.MAIL)) continue;
+ final int unread = Mail.unread(player);
+ if (unread > 0) player.sendMessage(CloudnodeMSG.getInstance().config().mailNotify(unread));
+ }
+ });
+ }
+}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/Message.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/Message.java
index b206444..e41af5c 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/Message.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/Message.java
@@ -36,24 +36,11 @@ public Message(@NotNull OfflinePlayer sender, @NotNull OfflinePlayer recipient,
this(sender, recipient, Component.text(message));
}
- private @NotNull String playerOrServerUsername(final @NotNull OfflinePlayer player) throws InvalidPlayerError {
- if (player.getUniqueId().equals(console.getUniqueId()))
- return CloudnodeMSG.getInstance().config().consoleName();
- else {
- final @NotNull Optional<@NotNull String> name = Optional.ofNullable(player.getName());
- if (name.isEmpty()) throw new InvalidPlayerError();
- else return name.get();
- }
- }
-
public void send() throws InvalidPlayerError {
send(Context.REGULAR);
}
public void send(final @NotNull Context context) throws InvalidPlayerError {
- final @NotNull String senderUsername = playerOrServerUsername(this.sender);
- final @NotNull String recipientUsername = playerOrServerUsername(this.recipient);
-
final @NotNull Optional<@NotNull Player> senderPlayer = Optional.ofNullable(this.sender.getPlayer());
final @NotNull Optional<@NotNull Player> recipientPlayer = Optional.ofNullable(this.recipient.getPlayer());
@@ -61,29 +48,28 @@ public void send(final @NotNull Context context) throws InvalidPlayerError {
if (context == Context.CHANNEL) {
final @NotNull Player player = Objects.requireNonNull(sender.getPlayer());
Message.exitChannel(player);
- new ChannelOfflineError(player.getName(), Optional.ofNullable(recipient.getName())
- .orElse("Unknown Player")).send(player);
+ new ChannelOfflineError(sender, recipient).send(player);
}
else {
final @NotNull Audience senderAudience = senderPlayer.isPresent() ? senderPlayer.get() : CloudnodeMSG.getInstance().getServer().getConsoleSender();
- if (context == Context.REPLY) new ReplyOfflineError(recipientUsername).send(senderAudience);
- else new PlayerNotFoundError(recipientUsername).send(senderAudience);
+ if (context == Context.REPLY) new ReplyOfflineError(recipient).send(senderAudience);
+ else new PlayerNotFoundError(name(this.recipient)).send(senderAudience);
}
return;
}
if (recipientPlayer.isPresent() && senderPlayer.isPresent() && !Message.isIncomingEnabled(recipientPlayer.get()) && !senderPlayer
.get().hasPermission(Permission.TOGGLE_BYPASS)) {
- new PlayerHasIncomingDisabledError(recipientPlayer.get().getName()).send(senderPlayer.get());
+ new PlayerHasIncomingDisabledError(recipientPlayer.get()).send(senderPlayer.get());
return;
}
sendSpyMessage(sender, recipient, message);
- sendMessage(sender, CloudnodeMSG.getInstance().config().outgoing(senderUsername, recipientUsername, message));
+ sendMessage(sender, CloudnodeMSG.getInstance().config().outgoing(sender, recipient, message));
if ((recipientPlayer.isPresent() && Message.isIgnored(recipientPlayer.get(), sender)) && (senderPlayer.isPresent() && !senderPlayer
.get().hasPermission(Permission.IGNORE_BYPASS))) return;
sendMessage(recipient, CloudnodeMSG.getInstance().config()
- .incoming(senderUsername, recipientUsername, message));
+ .incoming(sender, recipient, message));
if (sender.getUniqueId().equals(console.getUniqueId()) || (senderPlayer.isPresent() && !Message.hasChannel(senderPlayer.get(), recipient)))
setReplyTo(sender, recipient);
@@ -98,6 +84,12 @@ public void send(final @NotNull Context context) throws InvalidPlayerError {
return executor instanceof final @NotNull Player player ? player : console;
}
+ public static @NotNull String name(final @NotNull OfflinePlayer player) {
+ if (player.getUniqueId().equals(console.getUniqueId())) return CloudnodeMSG.getInstance().config().consoleName();
+ final @NotNull Optional<@NotNull String> name = Optional.ofNullable(player.getName());
+ return name.orElse(CloudnodeMSG.getInstance().config().unknownName());
+ }
+
public static void sendMessage(final @NotNull OfflinePlayer recipient, final @NotNull Component message) {
if (recipient.getUniqueId() == console.getUniqueId())
CloudnodeMSG.getInstance().getServer().getConsoleSender().sendMessage(message);
@@ -108,19 +100,16 @@ public static void sendMessage(final @NotNull OfflinePlayer recipient, final @No
* Send social spy to online players with permission
*/
public static void sendSpyMessage(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient, final @NotNull Component message) {
- final @NotNull String senderName = sender.getUniqueId().equals(console.getUniqueId()) ? CloudnodeMSG
- .getInstance().config().consoleName() : Optional.ofNullable(sender.getName()).orElse("Unknown Player");
- final @NotNull String recipientName = recipient.getUniqueId().equals(console.getUniqueId()) ? CloudnodeMSG
- .getInstance().config().consoleName() : Optional.ofNullable(recipient.getName())
- .orElse("Unknown Player");
for (final @NotNull Player player : CloudnodeMSG.getInstance().getServer().getOnlinePlayers()) {
- if (!player.hasPermission(Permission.SPY) || player.getUniqueId().equals(sender.getUniqueId()) || player
- .getUniqueId().equals(recipient.getUniqueId())) continue;
- sendMessage(player, CloudnodeMSG.getInstance().config().spy(senderName, recipientName, message));
+ if (
+ !player.hasPermission(Permission.SPY)
+ || player.getUniqueId().equals(sender.getUniqueId())
+ || player.getUniqueId().equals(recipient.getUniqueId())
+ ) continue;
+ sendMessage(player, CloudnodeMSG.getInstance().config().spy(sender, recipient, message));
}
- if (!sender.getUniqueId().equals(console.getUniqueId()) && !recipient.getUniqueId()
- .equals(console.getUniqueId()))
- sendMessage(console, CloudnodeMSG.getInstance().config().spy(senderName, recipientName, message));
+ if (!sender.getUniqueId().equals(console.getUniqueId()) && !recipient.getUniqueId().equals(console.getUniqueId()))
+ sendMessage(console, CloudnodeMSG.getInstance().config().spy(sender, recipient, message));
}
private static @Nullable UUID consoleReply;
@@ -143,12 +132,6 @@ else if (sender.isOnline()) Objects.requireNonNull(sender.getPlayer()).getPersis
return Optional.empty();
}
- public static void removeReplyTo(final @NotNull OfflinePlayer player) {
- if (player.getUniqueId().equals(console.getUniqueId())) consoleReply = null;
- else if (player.isOnline())
- Objects.requireNonNull(player.getPlayer()).getPersistentDataContainer().remove(REPLY_TO);
- }
-
public static final @NotNull NamespacedKey IGNORED_PLAYERS = new NamespacedKey(CloudnodeMSG.getInstance(), "ignored");
public static final @NotNull NamespacedKey CHANNEL_RECIPIENT = new NamespacedKey(CloudnodeMSG.getInstance(), "channel-recipient");
public static final @NotNull NamespacedKey CHANNEL_TEAM = new NamespacedKey(CloudnodeMSG.getInstance(), "channel-team");
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/Permission.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/Permission.java
index 6b4bcb8..a43f29a 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/Permission.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/Permission.java
@@ -52,4 +52,9 @@ public final class Permission {
* Allows to see the private messages of other players
*/
public final static @NotNull String SPY = "cloudnodemsg.spy";
+
+ /**
+ * Allows using the /mail command
+ */
+ public final static @NotNull String MAIL = "cloudnodemsg.mail";
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/PluginConfig.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/PluginConfig.java
index 1341017..587aefc 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/PluginConfig.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/PluginConfig.java
@@ -2,11 +2,16 @@
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.kyori.adventure.text.minimessage.tag.resolver.Formatter;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
+import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.scoreboard.Team;
import org.jetbrains.annotations.NotNull;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
public final class PluginConfig {
@@ -16,6 +21,31 @@ public PluginConfig(final @NotNull FileConfiguration config) {
this.config = config;
}
+ /**
+ * Database file name (in the plugin's folder)
+ *
If the file does not exist, it will be created
+ */
+ public @NotNull String dbSqliteFile() {
+ return Objects.requireNonNull(config.getString("db.sqlite.file"));
+ }
+
+ /**
+ * Advanced DB configuration / HikariCP properties
+ * Only change if you know what you're doing; you can add or remove any property you want
+ */
+ public @NotNull HashMap<@NotNull String, @NotNull String> dbHikariProperties() {
+ final @NotNull List<@NotNull Map, ?>> mapList = config.getMapList("db.hikaricp");
+ final @NotNull HashMap<@NotNull String, @NotNull String> properties = new HashMap<>(mapList.size());
+
+ for (final @NotNull Map, ?> map : mapList)
+ if (
+ map.get("name") instanceof final @NotNull String name
+ && map.get("value") instanceof final @NotNull String value
+ ) properties.put(name, value);
+
+ return properties;
+ }
+
/**
* Incoming message format (recipient's point of view)
* Placeholders:
@@ -25,14 +55,14 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* {@code } - the message text
*
*
- * @param sender The username of the message sender
- * @param recipient The username of the message recipient
+ * @param sender The message sender
+ * @param recipient The message recipient
* @param message The message text
*/
- public @NotNull Component incoming(final @NotNull String sender, final @NotNull String recipient, final @NotNull Component message) {
+ public @NotNull Component incoming(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient, final @NotNull Component message) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("incoming"))
- .replace("", sender)
- .replace("", recipient),
+ .replace("", Message.name(sender))
+ .replace("", Message.name(recipient)),
Placeholder.component("message", message)
);
}
@@ -41,19 +71,19 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Outgoing message format (sender's point of view)
* Placeholders:
*
- * - {@code } - the username of the message sender
+ * - {@code } - the message sender
* - {@code } - the username of the message recipient
* - {@code } - the message text
*
*
- * @param sender The username of the message sender
- * @param recipient The username of the message recipient
+ * @param sender The message sender
+ * @param recipient The message recipient
* @param message The message text
*/
- public @NotNull Component outgoing(final @NotNull String sender, final @NotNull String recipient, final @NotNull Component message) {
+ public @NotNull Component outgoing(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient, final @NotNull Component message) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("outgoing"))
- .replace("", sender)
- .replace("", recipient),
+ .replace("", Message.name(sender))
+ .replace("", Message.name(recipient)),
Placeholder.component("message", message)
);
}
@@ -72,9 +102,9 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* @param team The team
* @param message The message text
*/
- public @NotNull Component team(final @NotNull String sender, final @NotNull Team team, final @NotNull Component message) {
+ public @NotNull Component team(final @NotNull OfflinePlayer sender, final @NotNull Team team, final @NotNull Component message) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("team"))
- .replace("", sender),
+ .replace("", Message.name(sender)),
Placeholder.component("team", team.displayName()),
Placeholder.component("message", message)
);
@@ -89,14 +119,14 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* {@code } - the message text
*
*
- * @param sender The username of the message sender
- * @param recipient The username of the message recipient
+ * @param sender The message sender
+ * @param recipient The message recipient
* @param message The message text
*/
- public @NotNull Component spy(final @NotNull String sender, final @NotNull String recipient, final @NotNull Component message) {
+ public @NotNull Component spy(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient, final @NotNull Component message) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("spy"))
- .replace("", sender)
- .replace("", recipient),
+ .replace("", Message.name(sender))
+ .replace("", Message.name(recipient)),
Placeholder.component("message", message)
);
}
@@ -114,9 +144,9 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* @param team The team
* @param message The message text
*/
- public @NotNull Component teamSpy(final @NotNull String sender, final @NotNull Team team, final @NotNull Component message) {
+ public @NotNull Component teamSpy(final @NotNull OfflinePlayer sender, final @NotNull Team team, final @NotNull Component message) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("team-spy"))
- .replace("", sender),
+ .replace("", Message.name(sender)),
Placeholder.component("team", team.displayName()),
Placeholder.component("message", message)
);
@@ -127,11 +157,11 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the username of the player
*
- * @param player The username of the player
+ * @param player The player
*/
- public @NotNull Component ignored(final @NotNull String player) {
+ public @NotNull Component ignored(final @NotNull OfflinePlayer player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("ignored")),
- Placeholder.unparsed("player", player)
+ Placeholder.unparsed("player", Message.name(player))
);
}
@@ -140,11 +170,11 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the username of the player
*
- * @param player The username of the player
+ * @param player The player
*/
- public @NotNull Component unignored(final @NotNull String player) {
+ public @NotNull Component unignored(final @NotNull OfflinePlayer player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("unignored")),
- Placeholder.unparsed("player", player)
+ Placeholder.unparsed("player", Message.name(player))
);
}
@@ -157,14 +187,14 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* {@code } - the command used, e.g. `msg`, `dm`, etc.
*
*
- * @param sender The username of the message sender
- * @param recipient The username of the message recipient
+ * @param sender The message sender
+ * @param recipient The message recipient
* @param command The command used, e.g. `msg`, `dm`, etc.
*/
- public @NotNull Component channelCreated(final @NotNull String sender, final @NotNull String recipient, final @NotNull String command) {
+ public @NotNull Component channelCreated(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient, final @NotNull String command) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("channel.created"))
- .replace("", sender)
- .replace("", recipient)
+ .replace("", Message.name(sender))
+ .replace("", Message.name(recipient))
.replace("", command));
}
@@ -177,14 +207,14 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* {@code } - the command used, e.g. `msg`, `dm`, etc.
*
*
- * @param sender The username of the message sender
- * @param recipient The username of the message recipient
+ * @param sender The message sender
+ * @param recipient The message recipient
* @param command The command used, e.g. `msg`, `dm`, etc.
*/
- public @NotNull Component channelClosed(final @NotNull String sender, final @NotNull String recipient, final @NotNull String command) {
+ public @NotNull Component channelClosed(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient, final @NotNull String command) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("channel.closed"))
- .replace("", sender)
- .replace("", recipient)
+ .replace("", Message.name(sender))
+ .replace("", Message.name(recipient))
.replace("", command));
}
@@ -196,13 +226,13 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* {@code } - the username of the message recipient
*
*
- * @param sender The username of the message sender
- * @param recipient The username of the message recipient
+ * @param sender The message sender
+ * @param recipient The message recipient
*/
- public @NotNull Component channelOffline(final @NotNull String sender, final @NotNull String recipient) {
+ public @NotNull Component channelOffline(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("channel.offline"))
- .replace("", sender)
- .replace("", recipient));
+ .replace("", Message.name(sender))
+ .replace("", Message.name(recipient)));
}
/**
@@ -216,9 +246,9 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* @param sender The username of the message sender
* @param team The team@
*/
- public @NotNull Component channelTeamCreated(final @NotNull String sender, final @NotNull Team team) {
+ public @NotNull Component channelTeamCreated(final @NotNull OfflinePlayer sender, final @NotNull Team team) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("channel.team-created"))
- .replace("", sender),
+ .replace("", Message.name(sender)),
Placeholder.component("team", team.displayName()));
}
@@ -233,9 +263,9 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* @param sender The username of the message sender
* @param team The team
*/
- public @NotNull Component channelTeamClosed(final @NotNull String sender, final @NotNull Team team) {
+ public @NotNull Component channelTeamClosed(final @NotNull OfflinePlayer sender, final @NotNull Team team) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("channel.team-closed"))
- .replace("", sender),
+ .replace("", Message.name(sender)),
Placeholder.component("team", team.displayName()));
}
@@ -271,6 +301,13 @@ public PluginConfig(final @NotNull FileConfiguration config) {
return Objects.requireNonNull(config.getString("console-name"));
}
+ /**
+ * Name for player when name not found (usually very unlikely to happen)
+ */
+ public @NotNull String unknownName() {
+ return Objects.requireNonNull(config.getString("unknown-name"));
+ }
+
public @NotNull Component toggleDisable() {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("toggle.disable.message")));
}
@@ -280,11 +317,11 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the player's username
*
- * @param player the player's username
+ * @param player the player
*/
- public @NotNull Component toggleDisableOther(final @NotNull String player) {
+ public @NotNull Component toggleDisableOther(final @NotNull OfflinePlayer player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("toggle.disable.other")),
- Placeholder.unparsed("player", player)
+ Placeholder.unparsed("player", Message.name(player))
);
}
@@ -297,14 +334,89 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the player's username
*
- * @param player the player's username
+ * @param player the player
*/
- public @NotNull Component toggleEnableOther(final @NotNull String player) {
+ public @NotNull Component toggleEnableOther(final @NotNull OfflinePlayer player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("toggle.enable.other")),
- Placeholder.unparsed("player", player)
+ Placeholder.unparsed("player", Message.name(player))
);
}
+ /**
+ * Should players be notified for unread mail on login?
+ */
+ public boolean mailNotifyOnLogin() {
+ return config.getBoolean("mail.notify-on-login");
+ }
+
+ /**
+ * Interval (in minutes) to notify players of unread mail. Set to 0 to disable.
+ */
+ public int mailNotifyInterval() {
+ return config.getInt("mail.notify-interval");
+ }
+
+ /**
+ * Unread mail notification message
+ * Placeholders:
+ * - {@code } - the number of unread mail messages
+ *
+ * @param unread the number of unread mail messages
+ */
+ public @NotNull Component mailNotify(final int unread) {
+ return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("mail.notify")),
+ Placeholder.unparsed("unread", String.valueOf(unread))
+ );
+ }
+
+ /**
+ * Successfully sent mail
+ * Placeholders:
+ *
+ * - {@code } - the username of the sender
+ * - {@code } - the username of the recipient
+ * - {@code } - the message
+ * - {@code } - whether the mail was read/opened by the recipient (see: Insert a choice)
+ * - {@code } - whether the recipient has starred the mail (see: Insert a choice)
+ * - {@code } - the ID of the mail
+ *
+ *
+ * @param mail the mail
+ */
+ public @NotNull Component mailSent(final @NotNull Mail mail) {
+ return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("mail.sent"))
+ .replace("", mail.id.toString())
+ .replace("", Message.name(mail.sender))
+ .replace("", Message.name(mail.recipient)),
+ Formatter.booleanChoice("seen", mail.seen),
+ Formatter.booleanChoice("starred", mail.starred)
+ ).replaceText(configurer -> configurer.matchLiteral("").replacement(Component.text(mail.message)));
+ }
+
+ /**
+ * Received mail while online
+ * Placeholders:
+ *
+ * - {@code } - the username of the sender
+ * - {@code } - the username of the recipient
+ * - {@code } - the message
+ * - {@code } - whether the mail was read/opened by the recipient (see: Insert a choice)
+ * - {@code } - whether the recipient has starred the mail (see: Insert a choice)
+ * - {@code } - the ID of the mail
+ *
+ *
+ * @param mail the mail
+ */
+ public @NotNull Component mailReceived(final @NotNull Mail mail) {
+ return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("mail.received"))
+ .replace("", mail.id.toString())
+ .replace("", Message.name(mail.sender))
+ .replace("", Message.name(mail.recipient)),
+ Formatter.booleanChoice("seen", mail.seen),
+ Formatter.booleanChoice("starred", mail.starred)
+ ).replaceText(configurer -> configurer.matchLiteral("").replacement(Component.text(mail.message)));
+ }
+
/**
* No permission
*/
@@ -324,7 +436,7 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the player's username
*
- * @param player The player's username
+ * @param player The player
*/
public @NotNull Component playerNotFound(final @NotNull String player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("errors.player-not-found")),
@@ -345,11 +457,11 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the player's username
*
- * @param player The player's username
+ * @param player The player
*/
- public @NotNull Component replyOffline(final @NotNull String player) {
+ public @NotNull Component replyOffline(final @NotNull OfflinePlayer player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("errors.reply-offline")),
- Placeholder.unparsed("player", player)
+ Placeholder.unparsed("player", Message.name(player))
);
}
@@ -365,11 +477,11 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the player's username
*
- * @param player The player's username
+ * @param player The player
*/
- public @NotNull Component notIgnored(final @NotNull String player) {
+ public @NotNull Component notIgnored(final @NotNull OfflinePlayer player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("errors.not-ignored")),
- Placeholder.unparsed("player", player)
+ Placeholder.unparsed("player", Message.name(player))
);
}
@@ -378,11 +490,11 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the player's username
*
- * @param player The player's username
+ * @param player The player
*/
- public @NotNull Component cannotIgnore(final @NotNull String player) {
+ public @NotNull Component cannotIgnore(final @NotNull OfflinePlayer player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("errors.cannot-ignore")),
- Placeholder.unparsed("player", player)
+ Placeholder.unparsed("player", Message.name(player))
);
}
@@ -398,11 +510,11 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the player's username
*
- * @param player The player's username
+ * @param player The player
*/
- public @NotNull Component neverJoined(final @NotNull String player) {
+ public @NotNull Component neverJoined(final @NotNull OfflinePlayer player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("errors.never-joined")),
- Placeholder.unparsed("player", player)
+ Placeholder.unparsed("player", Message.name(player))
);
}
@@ -411,11 +523,11 @@ public PluginConfig(final @NotNull FileConfiguration config) {
* Placeholders:
* - {@code } - the player's username
*
- * @param player The player's username
+ * @param player The player
*/
- public @NotNull Component incomingDisabled(final @NotNull String player) {
+ public @NotNull Component incomingDisabled(final @NotNull OfflinePlayer player) {
return MiniMessage.miniMessage().deserialize(Objects.requireNonNull(config.getString("errors.incoming-disabled")),
- Placeholder.unparsed("player", player)
+ Placeholder.unparsed("player", Message.name(player))
);
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/Command.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/Command.java
index c168fd9..d9c7ca4 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/Command.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/Command.java
@@ -6,8 +6,11 @@
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
+import java.util.List;
+
public abstract class Command implements TabCompleter, CommandExecutor {
public static boolean sendMessage(final @NotNull Audience recipient, final @NotNull Component message) {
recipient.sendMessage(message);
@@ -15,12 +18,18 @@ public static boolean sendMessage(final @NotNull Audience recipient, final @NotN
}
public abstract boolean run(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args);
+ public abstract @Nullable List<@NotNull String> tab(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args);
@Override
public final boolean onCommand(final @NotNull CommandSender sender, final @NotNull org.bukkit.command.Command command, final @NotNull String label, @NotNull String @NotNull [] args) {
- CloudnodeMSG.getInstance().getServer().getScheduler().runTaskAsynchronously(CloudnodeMSG.getInstance(), () -> {
+ CloudnodeMSG.runAsync(() -> {
final boolean ignored = run(sender, label, args);
});
return true;
}
+
+ @Override
+ public final @Nullable List<@NotNull String> onTabComplete(final @NotNull CommandSender sender, final @NotNull org.bukkit.command.Command command, final @NotNull String label, final @NotNull String @NotNull [] args) {
+ return tab(sender, label, args);
+ }
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/IgnoreCommand.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/IgnoreCommand.java
index c33b8a9..e7de186 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/IgnoreCommand.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/IgnoreCommand.java
@@ -6,17 +6,16 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
+import pro.cloudnode.smp.cloudnodemsg.Message;
import pro.cloudnode.smp.cloudnodemsg.Permission;
import pro.cloudnode.smp.cloudnodemsg.error.CannotIgnoreError;
import pro.cloudnode.smp.cloudnodemsg.error.NeverJoinedError;
import pro.cloudnode.smp.cloudnodemsg.error.NoPermissionError;
import pro.cloudnode.smp.cloudnodemsg.error.NotPlayerError;
-import pro.cloudnode.smp.cloudnodemsg.Message;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
public final class IgnoreCommand extends Command {
public static final @NotNull String usage = "";
@@ -33,23 +32,19 @@ public boolean run(final @NotNull CommandSender sender, final @NotNull String la
}
public static boolean ignore(final @NotNull Player player, final @NotNull OfflinePlayer target) {
- if (target.isOnline() && Objects.requireNonNull(target.getPlayer()).hasPermission(Permission.IGNORE_BYPASS))
- return new CannotIgnoreError(Optional.ofNullable(target.getName()).orElse("Unknown Player")).send(player);
- if (!target.isOnline() && !target.hasPlayedBefore())
- return new NeverJoinedError(Optional.ofNullable(target.getName()).orElse("Unknown Player")).send(player);
+ if (target.isOnline() && Objects.requireNonNull(target.getPlayer()).hasPermission(Permission.IGNORE_BYPASS)) return new CannotIgnoreError(target).send(player);
+ if (!target.isOnline() && !target.hasPlayedBefore()) return new NeverJoinedError(target).send(player);
Message.ignore(player, target);
- return sendMessage(player, CloudnodeMSG.getInstance().config()
- .ignored(Optional.ofNullable(target.getName()).orElse("Unknown Player")));
+ return sendMessage(player, CloudnodeMSG.getInstance().config().ignored(target));
}
public static boolean unignore(final @NotNull Player player, final @NotNull OfflinePlayer target) {
Message.unignore(player, target);
- return sendMessage(player, CloudnodeMSG.getInstance().config()
- .unignored(Optional.ofNullable(target.getName()).orElse("Unknown Player")));
+ return sendMessage(player, CloudnodeMSG.getInstance().config().unignored(target));
}
@Override
- public @Nullable List<@NotNull String> onTabComplete(final @NotNull CommandSender sender, final @NotNull org.bukkit.command.Command command, final @NotNull String label, final @NotNull String @NotNull [] args) {
+ public @Nullable List<@NotNull String> tab(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args) {
if (args.length == 1 && sender.hasPermission(Permission.IGNORE) && sender instanceof Player) return null;
return new ArrayList<>();
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MailCommand.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MailCommand.java
new file mode 100644
index 0000000..8bf283c
--- /dev/null
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MailCommand.java
@@ -0,0 +1,56 @@
+package pro.cloudnode.smp.cloudnodemsg.command;
+
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
+import pro.cloudnode.smp.cloudnodemsg.Mail;
+import pro.cloudnode.smp.cloudnodemsg.Message;
+import pro.cloudnode.smp.cloudnodemsg.Permission;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+public final class MailCommand extends Command {
+
+ @Override
+ public boolean run(@NotNull CommandSender sender, @NotNull String label, @NotNull String @NotNull [] args) {
+ if (!sender.hasPermission(Permission.MAIL)) return sendMessage(sender, CloudnodeMSG.getInstance().config().noPermission());
+ if (args.length == 0) return help(sender);
+ final @NotNull String @NotNull [] subCommandArgs = Arrays.copyOfRange(args, 1, args.length);
+ final @NotNull String subCommandLabel = label + " " + args[0];
+ return switch (args[0]) {
+ case "send", "new", "to" -> send(sender, subCommandLabel, subCommandArgs);
+ default -> help(sender);
+ };
+ }
+
+ public static boolean help(final @NotNull CommandSender sender) {
+ // TODO
+ return false;
+ }
+
+ public static boolean send(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String[] args) {
+ if (args.length == 0) return sendMessage(sender, CloudnodeMSG.getInstance().config().usage(label, " "));
+ if (args.length == 1) return sendMessage(sender, CloudnodeMSG.getInstance().config().usage(label, args[0] + " "));
+ final @NotNull String message = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
+ final @NotNull OfflinePlayer recipient = CloudnodeMSG.getInstance().getServer().getOfflinePlayer(args[0]);
+ if (!recipient.hasPlayedBefore()) return sendMessage(sender, CloudnodeMSG.getInstance().config().playerNotFound(Message.name(recipient)));
+ final @NotNull Mail mail = new Mail(Message.offlinePlayer(sender), recipient, message);
+ mail.insert();
+ final @NotNull OfflinePlayer senderOfflinePlayer = Message.offlinePlayer(sender);
+ final @NotNull Optional<@NotNull Player> senderPlayer = Optional.ofNullable(senderOfflinePlayer.getPlayer());
+ final @NotNull Optional<@NotNull Player> recipientPlayer = Optional.ofNullable(recipient.getPlayer());
+ if (recipientPlayer.isPresent() && (!Message.isIgnored(recipientPlayer.get(), senderOfflinePlayer) || senderPlayer.isEmpty() || senderPlayer.get().hasPermission(Permission.IGNORE_BYPASS)))
+ sendMessage(recipientPlayer.get(), CloudnodeMSG.getInstance().config().mailReceived(mail));
+ return sendMessage(sender, CloudnodeMSG.getInstance().config().mailSent(mail));
+ }
+
+ @Override
+ public @Nullable List<@NotNull String> tab(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args) {
+ return null;
+ }
+}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MainCommand.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MainCommand.java
index 186a09c..ce0d0c7 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MainCommand.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MainCommand.java
@@ -37,7 +37,7 @@ private boolean reload(final @NotNull CommandSender sender) {
}
@Override
- public @NotNull List<@NotNull String> onTabComplete(final @NotNull CommandSender sender, final @NotNull org.bukkit.command.Command command, final @NotNull String label, final @NotNull String @NotNull [] args) {
+ public @NotNull List<@NotNull String> tab(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args) {
final @NotNull List<@NotNull String> completions = new ArrayList<>();
if (args.length == 1) if (sender.hasPermission(Permission.RELOAD) && "reload".startsWith(args[0].toLowerCase()))
completions.add("reload");
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MessageCommand.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MessageCommand.java
index 2ce01de..5dc026b 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MessageCommand.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/MessageCommand.java
@@ -41,13 +41,13 @@ public boolean run(final @NotNull CommandSender sender, final @NotNull String la
final @NotNull OfflinePlayer recipientOffline = CloudnodeMSG.getInstance().getServer().getOfflinePlayer(args[0]);
if (Message.getChannel(player).map(r -> r.getUniqueId().equals(recipientOffline.getUniqueId())).orElse(false)) {
Message.exitChannel(player);
- return sendMessage(player, CloudnodeMSG.getInstance().config().channelClosed(player.getName(), Optional.ofNullable(recipientOffline.getName()).orElse("Unknown Player"), label));
+ return sendMessage(player, CloudnodeMSG.getInstance().config().channelClosed(player, recipientOffline, label));
}
}
if (recipient.isEmpty() || (CloudnodeMSG.isVanished(recipient.get()) && !player.hasPermission(Permission.SEND_VANISHED))) return new PlayerNotFoundError(args[0]).send(player);
- if (!Message.isIncomingEnabled(recipient.get())) return new PlayerHasIncomingDisabledError(recipient.get().getName()).send(player);
+ if (!Message.isIncomingEnabled(recipient.get())) return new PlayerHasIncomingDisabledError(recipient.get()).send(player);
Message.createChannel(player, recipient.get());
- return sendMessage(player, CloudnodeMSG.getInstance().config().channelCreated(player.getName(), recipient.get().getName(), label));
+ return sendMessage(player, CloudnodeMSG.getInstance().config().channelCreated(player, recipient.get(), label));
}
try {
@@ -61,7 +61,7 @@ public boolean run(final @NotNull CommandSender sender, final @NotNull String la
}
@Override
- public @Nullable List<@NotNull String> onTabComplete(final @NotNull CommandSender sender, final @NotNull org.bukkit.command.Command command, final @NotNull String label, final @NotNull String @NotNull [] args) {
+ public @Nullable List<@NotNull String> tab(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args) {
if (!sender.hasPermission(Permission.USE)) return new ArrayList<>();
// `null` works for list of players
if (args.length == 1) return null;
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/ReplyCommand.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/ReplyCommand.java
index a7757c5..2b7c353 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/ReplyCommand.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/ReplyCommand.java
@@ -35,7 +35,7 @@ public boolean run(final @NotNull CommandSender sender, final @NotNull String la
}
@Override
- public @NotNull List<@NotNull String> onTabComplete(final @NotNull CommandSender sender, final @NotNull org.bukkit.command.Command command, final @NotNull String label, final @NotNull String @NotNull [] args) {
+ public @NotNull List<@NotNull String> tab(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args) {
return new ArrayList<>();
}
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/TeamMessageCommand.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/TeamMessageCommand.java
index cea49bf..16aedfb 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/TeamMessageCommand.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/TeamMessageCommand.java
@@ -28,19 +28,19 @@ public boolean run(final @NotNull CommandSender sender, final @NotNull String la
if (Message.hasTeamChannel(player)) {
Message.exitTeamChannel(player);
return sendMessage(player, CloudnodeMSG.getInstance().config()
- .channelTeamClosed(player.getName(), team.get()));
+ .channelTeamClosed(player, team.get()));
}
else {
Message.createTeamChannel(player);
return sendMessage(player, CloudnodeMSG.getInstance().config()
- .channelTeamCreated(player.getName(), team.get()));
+ .channelTeamCreated(player, team.get()));
}
}
return sendTeamMessage(player, team.get(), Component.text(String.join(" ", args)));
}
@Override
- public @Nullable List<@NotNull String> onTabComplete(final @NotNull CommandSender sender, final org.bukkit.command.@NotNull Command command, final @NotNull String label, final @NotNull String @NotNull [] args) {
+ public @Nullable List<@NotNull String> tab(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args) {
return new ArrayList<>();
}
@@ -52,12 +52,12 @@ public static boolean sendTeamMessage(final @NotNull Player sender, final @NotNu
if (Message.isIgnored(player, sender)) continue;
if (Optional.ofNullable(player.getScoreboard().getPlayerTeam(player)).map(t -> t.equals(team))
.orElse(false))
- sendMessage(player, CloudnodeMSG.getInstance().config().team(sender.getName(), team, message));
+ sendMessage(player, CloudnodeMSG.getInstance().config().team(sender, team, message));
else if (player.hasPermission(Permission.SPY))
- sendMessage(player, CloudnodeMSG.getInstance().config().teamSpy(sender.getName(), team, message));
+ sendMessage(player, CloudnodeMSG.getInstance().config().teamSpy(sender, team, message));
}
sender.getServer().getConsoleSender()
- .sendMessage(CloudnodeMSG.getInstance().config().teamSpy(sender.getName(), team, message));
+ .sendMessage(CloudnodeMSG.getInstance().config().teamSpy(sender, team, message));
return true;
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/ToggleMessageCommand.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/ToggleMessageCommand.java
index 6dbbd63..2652bfb 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/ToggleMessageCommand.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/ToggleMessageCommand.java
@@ -6,15 +6,14 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
+import pro.cloudnode.smp.cloudnodemsg.Message;
import pro.cloudnode.smp.cloudnodemsg.Permission;
import pro.cloudnode.smp.cloudnodemsg.error.NeverJoinedError;
import pro.cloudnode.smp.cloudnodemsg.error.NoPermissionError;
import pro.cloudnode.smp.cloudnodemsg.error.NotPlayerError;
-import pro.cloudnode.smp.cloudnodemsg.Message;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
public final class ToggleMessageCommand extends Command {
@Override
@@ -24,18 +23,16 @@ public boolean run(final @NotNull CommandSender sender, final @NotNull String la
if (args.length == 1) {
final @NotNull OfflinePlayer recipient = CloudnodeMSG.getInstance().getServer().getOfflinePlayer(args[0]);
- if (recipient.getPlayer() == null) return new NeverJoinedError(Optional.ofNullable(recipient.getName())
- .orElse("Unknown Player")).send(sender);
+ if (recipient.getPlayer() == null)
+ return new NeverJoinedError(recipient).send(sender);
if (Message.isIncomingEnabled(recipient.getPlayer())) {
Message.incomingDisable(recipient.getPlayer());
- return sendMessage(sender, CloudnodeMSG.getInstance().config()
- .toggleDisableOther(Optional.of(recipient.getPlayer().getName()).orElse("Unknown Player")));
+ return sendMessage(sender, CloudnodeMSG.getInstance().config().toggleDisableOther(recipient));
}
Message.incomingEnable(recipient.getPlayer());
- return sendMessage(sender, CloudnodeMSG.getInstance().config()
- .toggleEnableOther(Optional.of(recipient.getPlayer().getName()).orElse("Unknown Player")));
+ return sendMessage(sender, CloudnodeMSG.getInstance().config().toggleEnableOther(recipient));
}
if (!(sender instanceof final @NotNull Player player)) return new NotPlayerError().send(sender);
@@ -49,8 +46,9 @@ public boolean run(final @NotNull CommandSender sender, final @NotNull String la
}
@Override
- public @Nullable List<@NotNull String> onTabComplete(final @NotNull CommandSender sender, final org.bukkit.command.@NotNull Command command, final @NotNull String s, final @NotNull String @NotNull [] strings) {
- if (sender.hasPermission(Permission.TOGGLE_OTHER)) return null;
+ public @Nullable List<@NotNull String> tab(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args) {
+ if (sender.hasPermission(Permission.TOGGLE_OTHER))
+ return null;
return new ArrayList<>();
}
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/UnIgnoreCommand.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/UnIgnoreCommand.java
index d165c38..6e5104b 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/UnIgnoreCommand.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/command/UnIgnoreCommand.java
@@ -29,11 +29,11 @@ public boolean run(final @NotNull CommandSender sender, final @NotNull String la
if (args.length == 0) return sendMessage(sender, CloudnodeMSG.getInstance().config().usage(label, usage));
final @NotNull OfflinePlayer target = CloudnodeMSG.getInstance().getServer().getOfflinePlayer(args[0]);
if (Message.isIgnored(player, target)) return IgnoreCommand.unignore(player, target);
- return new NotIgnoredError(args[0]).send(sender);
+ return new NotIgnoredError(target).send(sender);
}
@Override
- public @Nullable List<@NotNull String> onTabComplete(final @NotNull CommandSender sender, final @NotNull org.bukkit.command.Command command, final @NotNull String label, final @NotNull String @NotNull [] args) {
+ public @Nullable List<@NotNull String> tab(final @NotNull CommandSender sender, final @NotNull String label, final @NotNull String @NotNull [] args) {
if (args.length == 1 && sender.hasPermission(Permission.IGNORE) && sender instanceof final @NotNull Player player) {
final @NotNull HashSet<@NotNull UUID> ignored = Message.getIgnored(player);
final @NotNull Server server = CloudnodeMSG.getInstance().getServer();
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/CannotIgnoreError.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/CannotIgnoreError.java
index 7c8ee01..50ee0e0 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/CannotIgnoreError.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/CannotIgnoreError.java
@@ -1,5 +1,6 @@
package pro.cloudnode.smp.cloudnodemsg.error;
+import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
@@ -7,7 +8,7 @@
* Player cannot be ignored
*/
public final class CannotIgnoreError extends Error {
- public CannotIgnoreError(final @NotNull String player) {
+ public CannotIgnoreError(final @NotNull OfflinePlayer player) {
super(CloudnodeMSG.getInstance().config().cannotIgnore(player));
}
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/ChannelOfflineError.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/ChannelOfflineError.java
index db6f776..0de6ddc 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/ChannelOfflineError.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/ChannelOfflineError.java
@@ -1,5 +1,6 @@
package pro.cloudnode.smp.cloudnodemsg.error;
+import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
@@ -13,7 +14,7 @@ public final class ChannelOfflineError extends Error {
* @param sender The username of the message sender
* @param recipient The username of the message recipient
*/
- public ChannelOfflineError(final @NotNull String sender, final @NotNull String recipient) {
+ public ChannelOfflineError(final @NotNull OfflinePlayer sender, final @NotNull OfflinePlayer recipient) {
super(CloudnodeMSG.getInstance().config().channelOffline(sender, recipient));
}
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/NeverJoinedError.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/NeverJoinedError.java
index e1e1780..5cc9753 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/NeverJoinedError.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/NeverJoinedError.java
@@ -1,5 +1,6 @@
package pro.cloudnode.smp.cloudnodemsg.error;
+import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
@@ -12,7 +13,7 @@ public final class NeverJoinedError extends Error {
*
* @param player the player's username
*/
- public NeverJoinedError(final @NotNull String player) {
+ public NeverJoinedError(final @NotNull OfflinePlayer player) {
super(CloudnodeMSG.getInstance().config().neverJoined(player));
}
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/NotIgnoredError.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/NotIgnoredError.java
index 1295628..c92e181 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/NotIgnoredError.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/NotIgnoredError.java
@@ -1,5 +1,6 @@
package pro.cloudnode.smp.cloudnodemsg.error;
+import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
@@ -12,7 +13,7 @@ public final class NotIgnoredError extends Error {
*
* @param player the player's username
*/
- public NotIgnoredError(final @NotNull String player) {
+ public NotIgnoredError(final @NotNull OfflinePlayer player) {
super(CloudnodeMSG.getInstance().config().notIgnored(player));
}
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/PlayerHasIncomingDisabledError.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/PlayerHasIncomingDisabledError.java
index 980dd55..032ef3f 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/PlayerHasIncomingDisabledError.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/PlayerHasIncomingDisabledError.java
@@ -1,10 +1,11 @@
package pro.cloudnode.smp.cloudnodemsg.error;
+import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
public class PlayerHasIncomingDisabledError extends Error {
- public PlayerHasIncomingDisabledError(final @NotNull String player) {
+ public PlayerHasIncomingDisabledError(final @NotNull OfflinePlayer player) {
super(CloudnodeMSG.getInstance().config().incomingDisabled(player));
}
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/ReplyOfflineError.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/ReplyOfflineError.java
index 3122050..6d29c90 100644
--- a/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/ReplyOfflineError.java
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/error/ReplyOfflineError.java
@@ -1,5 +1,6 @@
package pro.cloudnode.smp.cloudnodemsg.error;
+import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
@@ -12,7 +13,7 @@ public final class ReplyOfflineError extends Error {
*
* @param player The player's username
*/
- public ReplyOfflineError(final @NotNull String player) {
+ public ReplyOfflineError(final @NotNull OfflinePlayer player) {
super(CloudnodeMSG.getInstance().config().replyOffline(player));
}
}
diff --git a/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/PlayerJoinListener.java b/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/PlayerJoinListener.java
new file mode 100644
index 0000000..8881e48
--- /dev/null
+++ b/src/main/java/pro/cloudnode/smp/cloudnodemsg/listener/PlayerJoinListener.java
@@ -0,0 +1,23 @@
+package pro.cloudnode.smp.cloudnodemsg.listener;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.jetbrains.annotations.NotNull;
+import pro.cloudnode.smp.cloudnodemsg.CloudnodeMSG;
+import pro.cloudnode.smp.cloudnodemsg.Mail;
+import pro.cloudnode.smp.cloudnodemsg.Permission;
+
+public final class PlayerJoinListener implements Listener {
+ @EventHandler
+ public void onPlayerJoin(final @NotNull PlayerJoinEvent event) {
+ CloudnodeMSG.runAsync(() -> {
+ if (!CloudnodeMSG.getInstance().config().mailNotifyOnLogin()) return;
+ final @NotNull Player player = event.getPlayer();
+ if (!player.hasPermission(Permission.MAIL)) return;
+ final int unread = Mail.unread(player);
+ if (unread > 0) player.sendMessage(CloudnodeMSG.getInstance().config().mailNotify(unread));
+ });
+ }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index ade838b..bb8ce0d 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -1,3 +1,35 @@
+# Database configuration
+db:
+ sqlite:
+ # Database file name (in the plugin's folder)
+ # If the file does not exist, it will be created
+ file: 'cloudnodemsg.db'
+
+ # Advanced DB configuration / HikariCP properties
+ # Only change if you know what you're doing; you can add or remove any property you want
+ hikaricp:
+ - name: "cachePrepStmts"
+ value: "true"
+ - name: "prepStmtCacheSize"
+ value: "250"
+ - name: "prepStmtCacheSqlLimit"
+ value: "2048"
+ - name: "useServerPrepStmts"
+ value: "true"
+ - name: "useLocalSessionState"
+ value: "true"
+ - name: "rewriteBatchedStatements"
+ value: "true"
+ - name: "cacheResultSetMetadata"
+ value: "true"
+ - name: "cacheServerConfiguration"
+ value: "true"
+ - name: "elideSetAutoCommits"
+ value: "true"
+ - name: "maintainTimeStats"
+ value: "false"
+
+
# Incoming message format (recipient's point of view)
# Placeholders:
# - the username of the message sender
@@ -65,6 +97,9 @@ channel:
# Name for console/server that should appear as or in messages
console-name: "Server"
+# Name for player when name not found (usually very unlikely to happen)
+unknown-name: "Unknown Player"
+
# Command usage format
# Placeholders:
# - the command name
@@ -88,6 +123,38 @@ toggle:
# - the player's username
other: "(!) Re-enabled receiving of private messages for ."
+mail:
+ # Should players be notified for unread mail on login?
+ notify-on-login: true
+
+ # Interval (in minutes) to notify players of unread mail. Set to 0 to disable.
+ notify-interval: 3
+
+ # Unread mail notification message
+ # Placeholders:
+ # - the number of unread mail messages
+ notify: "(!) You have unread messages. View mail"
+
+ # Successfully sent mail
+ # Placeholders:
+ # - the username of the sender
+ # - the username of the recipient
+ # - the message
+ # - whether the mail was read/opened by the recipient (see: https://docs.advntr.dev/minimessage/dynamic-replacements.html#insert-a-choice)
+ # - whether the recipient has starred the mail (see: https://docs.advntr.dev/minimessage/dynamic-replacements.html#insert-a-choice)
+ # - the ID of the mail
+ sent: "(!) Successfully sent mail to ."
+
+ # Received mail while online
+ # Placeholders:
+ # - the username of the sender
+ # - the username of the recipient
+ # - the message
+ # - whether the mail was read/opened by the recipient (see: https://docs.advntr.dev/minimessage/dynamic-replacements.html#insert-a-choice)
+ # - whether the recipient has starred the mail (see: https://docs.advntr.dev/minimessage/dynamic-replacements.html#insert-a-choice)
+ # - the ID of the mail
+ received: "(!) You have new mail from . >Click to view"
+
# Error messages
errors:
# No permission
diff --git a/src/main/resources/ddl/sqlite.sql b/src/main/resources/ddl/sqlite.sql
new file mode 100644
index 0000000..901a8aa
--- /dev/null
+++ b/src/main/resources/ddl/sqlite.sql
@@ -0,0 +1,9 @@
+CREATE TABLE IF NOT EXISTS `cloudnodemsg_mail`
+(
+ `id` CHAR(32) COLLATE NOCASE PRIMARY KEY,
+ `sender` CHAR(32) COLLATE NOCASE NOT NULL,
+ `recipient` CHAR(32) COLLATE NOCASE NOT NULL,
+ `message` TEXT NOT NULL,
+ `seen` BOOLEAN DEFAULT 0,
+ `starred` BOOLEAN DEFAULT 0
+);
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 2f88c00..edc366c 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -37,3 +37,7 @@ commands:
usage: / [message]
aliases: [ "tmsg", "teamchat" , "tm" ]
permission: cloudnodemsg.use.team
+ mail:
+ description: Send and receive offline messages
+ usage: /
+ aliases: [ "post" ]