Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mail #34

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft

mail #34

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,11 @@
<version>1.20.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
86 changes: 84 additions & 2 deletions src/main/java/pro/cloudnode/smp/cloudnodemsg/CloudnodeMSG.java
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -23,6 +35,8 @@ public final class CloudnodeMSG extends JavaPlugin {
public void reload() {
getInstance().reloadConfig();
getInstance().config.config = getInstance().getConfig();
setupDbSource();
runDDL();
}

@Override
Expand All @@ -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) {
Expand All @@ -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;
}
119 changes: 119 additions & 0 deletions src/main/java/pro/cloudnode/smp/cloudnodemsg/Mail.java
Original file line number Diff line number Diff line change
@@ -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));
}
});
}
}
57 changes: 20 additions & 37 deletions src/main/java/pro/cloudnode/smp/cloudnodemsg/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,54 +36,40 @@ 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());

if (!recipient.getUniqueId().equals(console.getUniqueId()) && recipientPlayer.isEmpty() || (recipientPlayer.isPresent() && senderPlayer.isPresent() && CloudnodeMSG.isVanished(recipientPlayer.get()) && !senderPlayer.get().hasPermission(Permission.SEND_VANISHED))) {
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);
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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");
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/pro/cloudnode/smp/cloudnodemsg/Permission.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Loading
Loading