diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java index de8c6d88..bf83a4a7 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankAccounts.java @@ -4,11 +4,13 @@ import com.google.gson.JsonParser; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; +import net.kyori.adventure.text.Component; import org.bukkit.NamespacedKey; import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitTask; @@ -162,6 +164,12 @@ public static void reload() { getInstance().getLogger().warning("Update details: https://modrinth.com/plugin/bankaccounts/version/" + latestVersion); })); getInstance().startInterestTimer(); + if (getInstance().invoiceNotificationTask != null) { + final int taskId = getInstance().invoiceNotificationTask.getTaskId(); + getInstance().getServer().getScheduler().cancelTask(taskId); + getInstance().invoiceNotificationTask = null; + } + getInstance().setupInvoiceNotificationTimer(); } /** @@ -247,6 +255,20 @@ private void startInterestTimer() { }, 0L, 20L*60); } + private @Nullable BukkitTask invoiceNotificationTask = null; + + private void setupInvoiceNotificationTimer() { + if (config().invoiceNotifyInterval() <= 0) return; + this.invoiceNotificationTask = getServer().getScheduler().runTaskTimerAsynchronously(this, () -> { + for (final @NotNull Player player : getServer().getOnlinePlayers()) { + final @NotNull Optional<@NotNull Component> message = BankAccounts.getInstance().config().messagesInvoiceNotify(Invoice.countUnpaid(player)); + if (message.isEmpty()) continue; + if (player.hasPermission(Permissions.INVOICE_NOTIFY) && Invoice.countUnpaid(player) > 0) + player.sendMessage(message.get()); + } + }, config().invoiceNotifyInterval() * 20L, config().invoiceNotifyInterval() * 20L); + } + private void interestPayment(final @NotNull Account account, final @NotNull BigDecimal amount, final double rate, final @NotNull Account serverAccount) { if (account.balance == null) return; if (account.id.equals(serverAccount.id)) return; diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java index b2521390..90b20c7b 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/BankConfig.java @@ -437,6 +437,16 @@ public int invoicePerPage() { public @NotNull Pattern disallowedRegex() { return Pattern.compile(Objects.requireNonNull(config.getString("disallowed-regex"))); } + + // invoice.notify.join + public boolean invoiceNotifyJoin() { + return config.getBoolean("invoice.notify.join"); + } + + // invoice.notify.interval + public int invoiceNotifyInterval() { + return config.getInt("invoice.notify.interval"); + } // messages.command-usage public @NotNull Component messagesCommandUsage(final @NotNull String command, final @NotNull String arguments) { @@ -1379,6 +1389,17 @@ public int invoicePerPage() { ); } + // messages.invoice.notify + public @NotNull Optional<@NotNull Component> messagesInvoiceNotify(final int unpaid) { + final @NotNull String message = Objects.requireNonNull(config.getString("messages.invoice.notify")); + if (message.isBlank()) return Optional.empty(); + return Optional.of(MiniMessage.miniMessage().deserialize( + message + .replace("", String.valueOf(unpaid)), + Formatter.choice("unpaid-choice", unpaid) + )); + } + // messages.update-available public @NotNull Component messagesUpdateAvailable(final @NotNull String version) { return MiniMessage.miniMessage().deserialize( diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Invoice.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Invoice.java index e984f7da..18dc158d 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Invoice.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Invoice.java @@ -234,4 +234,19 @@ public void update() { return new @NotNull Invoice[0]; } } + + public static int countUnpaid(final @NotNull OfflinePlayer player) { + try (final @NotNull Connection conn = BankAccounts.getInstance().getDb().getConnection(); + final @NotNull PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(`id`) as `count` FROM `bank_invoices` WHERE `buyer` = ? AND `transaction` IS NULL")) { + stmt.setString(1, player.getUniqueId().toString()); + + final @NotNull ResultSet rs = stmt.executeQuery(); + if (rs.next()) return rs.getInt("count"); + return 0; + } + catch (final @NotNull SQLException e) { + BankAccounts.getInstance().getLogger().log(Level.SEVERE, "Could not count unpaid invoices for player: " + player.getUniqueId(), e); + return 0; + } + } } diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java b/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java index 968319e3..a6dab299 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/Permissions.java @@ -21,6 +21,7 @@ public final class Permissions { public static final @NotNull String INVOICE_CREATE_OTHER = "bank.invoice.create.other"; public static final @NotNull String INVOICE_VIEW = "bank.invoice.view"; public static final @NotNull String INVOICE_VIEW_OTHER = "bank.invoice.view.other"; + public static final @NotNull String INVOICE_NOTIFY = "bank.invoice.notify"; public static final @NotNull String INVOICE_PAY_OTHER = "bank.invoice.pay.other"; public static final @NotNull String INVOICE_PAY_ACCOUNT_OTHER = "bank.invoice.pay.account-other"; public static final @NotNull String INVOICE_SEND = "bank.invoice.send"; diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/InvoiceCommand.java b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/InvoiceCommand.java index ef68f82a..c6b220fd 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/commands/InvoiceCommand.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/commands/InvoiceCommand.java @@ -199,7 +199,7 @@ public static boolean create(final @NotNull CommandSender sender, @NotNull Strin invoice.insert(); final @NotNull Optional<@NotNull Player> onlineRecipient = invoice.buyer().isPresent() ? Optional.ofNullable(invoice.buyer().get().getPlayer()) : Optional.empty(); - onlineRecipient.ifPresent(player -> sendMessage(player, BankAccounts.getInstance().config().messagesInvoiceCreated(invoice))); + onlineRecipient.ifPresent(player -> sendMessage(player, BankAccounts.getInstance().config().messagesInvoiceReceived(invoice))); return sendMessage(sender, BankAccounts.getInstance().config().messagesInvoiceCreated(invoice)); } diff --git a/src/main/java/pro/cloudnode/smp/bankaccounts/events/Join.java b/src/main/java/pro/cloudnode/smp/bankaccounts/events/Join.java index 51297656..453d3c55 100644 --- a/src/main/java/pro/cloudnode/smp/bankaccounts/events/Join.java +++ b/src/main/java/pro/cloudnode/smp/bankaccounts/events/Join.java @@ -1,5 +1,6 @@ package pro.cloudnode.smp.bankaccounts.events; +import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -7,6 +8,7 @@ import org.jetbrains.annotations.NotNull; import pro.cloudnode.smp.bankaccounts.Account; import pro.cloudnode.smp.bankaccounts.BankAccounts; +import pro.cloudnode.smp.bankaccounts.Invoice; import pro.cloudnode.smp.bankaccounts.Permissions; import java.math.BigDecimal; @@ -35,5 +37,11 @@ else if (BankAccounts.getInstance().config().integrationsVaultEnabled()) { player.sendMessage(BankAccounts.getInstance().config().messagesUpdateAvailable(latestVersion)) ), 20L); } + if (player.hasPermission(Permissions.INVOICE_NOTIFY) && BankAccounts.getInstance().config().invoiceNotifyJoin() && Invoice.countUnpaid(player) > 0) { + BankAccounts.getInstance().getServer().getScheduler().runTaskLater(BankAccounts.getInstance(), () -> { + final @NotNull Optional<@NotNull Component> message = BankAccounts.getInstance().config().messagesInvoiceNotify(Invoice.countUnpaid(player)); + message.ifPresent(player::sendMessage); + }, 20L); + } } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index ad39e520..b68118af 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -266,6 +266,15 @@ invoice: # Number of invoices to return per page per-page: 10 + # Notifications for unpaid invoices (see messages.invoice.notify) + notify: + # Send message when a player joins + join: true + # Periodically send message while player is online + # Interval between messages in seconds + # Set to 0 or negative to disable + interval: 300 + # Advanced: do not edit unless you have good understanding of RegEx # Regular expression for disallowed characters user-provided text inputs # e.g. account name, transaction description, POS description, invoice description @@ -602,6 +611,14 @@ messages: # Same placeholders as details footer: ">← Previous Page >Next →" + # You have unpaid invoices + # Sent periodically and on login (as configured in invoice.notify) + # Set to empty string to disable + # Placeholders: + # - number of unpaid invoices>." + # - choice placeholder for unpaid invoices; see https://docs.advntr.dev/minimessage/dynamic-replacements.html#insert-a-choice + notify: "(!) You have unpaid invoice. Click to view." + # New version available # Placeholders: # - New version