diff --git a/src/main/java/com/alttd/buttonManager/ButtonManager.java b/src/main/java/com/alttd/buttonManager/ButtonManager.java index 607113f..2516ac4 100644 --- a/src/main/java/com/alttd/buttonManager/ButtonManager.java +++ b/src/main/java/com/alttd/buttonManager/ButtonManager.java @@ -1,5 +1,7 @@ package com.alttd.buttonManager; +import com.alttd.buttonManager.buttons.remindMeConfirm.ButtonRemindMeCancel; +import com.alttd.buttonManager.buttons.remindMeConfirm.ButtonRemindMeConfirm; import com.alttd.buttonManager.buttons.suggestionReview.ButtonSuggestionReviewAccept; import com.alttd.buttonManager.buttons.suggestionReview.ButtonSuggestionReviewDeny; import com.alttd.util.Util; @@ -22,7 +24,9 @@ public class ButtonManager extends ListenerAdapter { public ButtonManager() { buttons = List.of( new ButtonSuggestionReviewAccept(), - new ButtonSuggestionReviewDeny()); + new ButtonSuggestionReviewDeny(), + new ButtonRemindMeCancel(), + new ButtonRemindMeConfirm()); } @Override diff --git a/src/main/java/com/alttd/buttonManager/buttons/remindMeConfirm/ButtonRemindMeCancel.java b/src/main/java/com/alttd/buttonManager/buttons/remindMeConfirm/ButtonRemindMeCancel.java new file mode 100644 index 0000000..cce9259 --- /dev/null +++ b/src/main/java/com/alttd/buttonManager/buttons/remindMeConfirm/ButtonRemindMeCancel.java @@ -0,0 +1,25 @@ +package com.alttd.buttonManager.buttons.remindMeConfirm; + +import com.alttd.buttonManager.DiscordButton; +import com.alttd.util.Util; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.components.buttons.Button; + +public class ButtonRemindMeCancel extends DiscordButton { + @Override + public String getButtonId() { + return "remind_me_cancel"; + } + + @Override + public void execute(ButtonInteractionEvent event) { + ButtonRemindMeConfirm.removeReminder(event.getUser().getIdLong()); + event.replyEmbeds(Util.genericSuccessEmbed("Success", "Cancelled your reminder!")) + .setEphemeral(true).queue(); + } + + @Override + public Button getButton() { + return Button.danger(getButtonId(), "Cancel"); + } +} diff --git a/src/main/java/com/alttd/buttonManager/buttons/remindMeConfirm/ButtonRemindMeConfirm.java b/src/main/java/com/alttd/buttonManager/buttons/remindMeConfirm/ButtonRemindMeConfirm.java new file mode 100644 index 0000000..0b0ea5b --- /dev/null +++ b/src/main/java/com/alttd/buttonManager/buttons/remindMeConfirm/ButtonRemindMeConfirm.java @@ -0,0 +1,81 @@ +package com.alttd.buttonManager.buttons.remindMeConfirm; + +import com.alttd.buttonManager.DiscordButton; +import com.alttd.database.queries.QueriesReminders.QueriesReminders; +import com.alttd.database.queries.QueriesReminders.Reminder; +import com.alttd.reminders.ReminderScheduler; +import com.alttd.util.Util; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.components.buttons.Button; + +import java.util.HashMap; + +public class ButtonRemindMeConfirm extends DiscordButton { + + private static final HashMap unconfirmedReminders = new HashMap<>(); + + public static synchronized void putReminder(long id, Reminder reminder) { + unconfirmedReminders.put(id, reminder); + } + + public static synchronized Reminder removeReminder(long id) { + return unconfirmedReminders.get(id); + } + + @Override + public String getButtonId() { + return "remind_me_confirm"; + } + + @Override + public void execute(ButtonInteractionEvent event) { + Reminder reminder = removeReminder(event.getUser().getIdLong()); + + if (storeReminder(reminder, event)) { + event.replyEmbeds(Util.genericSuccessEmbed("Success", "Your reminder was successfully created!")) + .setEphemeral(true).queue(); + } + } + + private boolean storeReminder(Reminder reminder, ButtonInteractionEvent event) { + if (reminder == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to retrieve reminder data for this button")) + .setEphemeral(true).queue(); + return false; + } + int id = QueriesReminders.storeReminder(reminder); + if (id == 0) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to store reminder in the database")) + .setEphemeral(true).queue(); + return false; + } + + reminder = new Reminder( + id, + reminder.title(), + reminder.description(), + reminder.userId(), + reminder.guildId(), + reminder.channelId(), + reminder.messageId(), + reminder.shouldRepeat(), + reminder.creationDate(), + reminder.remindDate()); + + ReminderScheduler instance = ReminderScheduler.getInstance(event.getJDA()); + if (instance == null) { + QueriesReminders.removeReminder(reminder.id()); + event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to start reminder, removing it from the database...")) + .setEphemeral(true).queue(); + return false; + } + + instance.addReminder(reminder); + return true; + } + + @Override + public Button getButton() { + return Button.success(getButtonId(), "Confirm"); + } +} diff --git a/src/main/java/com/alttd/commandManager/CommandManager.java b/src/main/java/com/alttd/commandManager/CommandManager.java index a03bc7e..4d152f2 100644 --- a/src/main/java/com/alttd/commandManager/CommandManager.java +++ b/src/main/java/com/alttd/commandManager/CommandManager.java @@ -46,7 +46,8 @@ public class CommandManager extends ListenerAdapter { new CommandFlag(jda, this), new CommandHistory(jda, this), commandSetToggleableRoles, - new CommandToggleRole(commandSetToggleableRoles, jda, this)); + new CommandToggleRole(commandSetToggleableRoles, jda, this), + new CommandRemindMe(jda, this, modalManager)); } @Override diff --git a/src/main/java/com/alttd/commandManager/commands/CommandRemindMe.java b/src/main/java/com/alttd/commandManager/commands/CommandRemindMe.java index 11c95cc..3ffafad 100644 --- a/src/main/java/com/alttd/commandManager/commands/CommandRemindMe.java +++ b/src/main/java/com/alttd/commandManager/commands/CommandRemindMe.java @@ -1,4 +1,163 @@ package com.alttd.commandManager.commands; -public class CommandRemindMe { +import com.alttd.commandManager.CommandManager; +import com.alttd.commandManager.DiscordCommand; +import com.alttd.modalManager.ModalManager; +import com.alttd.modalManager.modals.ModalRemindMe; +import com.alttd.util.Util; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.unions.GuildChannelUnion; +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.components.Modal; + +import java.util.Calendar; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class CommandRemindMe extends DiscordCommand { + + private final CommandData commandData; + private final ModalManager modalManager; + + public CommandRemindMe(JDA jda, CommandManager commandManager, ModalManager modalManager) { + this.modalManager = modalManager; + commandData = Commands.slash(getName(), "Create a reminder") + .addOption(OptionType.CHANNEL, "channel", "The channel to send the reminder in", true) + .addOption(OptionType.STRING, "fromnow", "How long from now the reminder should send", true, true) + .setDefaultPermissions(DefaultMemberPermissions.ENABLED) + .setGuildOnly(true); + + Util.registerCommand(commandManager, jda, commandData, getName()); + } + + @Override + public String getName() { + return "remindme"; + } + + @Override + public void execute(SlashCommandInteractionEvent event) { + TextChannel channel = getValidChannel( + event.getInteraction().getOption("channel", OptionMapping::getAsChannel), event); + if (channel == null) + return; + + Long fromNow = getFromNow(event.getInteraction().getOption("fromnow", OptionMapping::getAsString), event); + if (fromNow == null) + return; + + Modal modal = modalManager.getModalFor("remindme"); + if (modal == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to retrieve remind me modal")) + .setEphemeral(true).queue(); + return; + } + + ModalRemindMe.putData(event.getIdLong(), channel, fromNow); + event.replyModal(modal).queue(); + } + + private TextChannel getValidChannel(GuildChannelUnion channel, SlashCommandInteractionEvent event) { + if (channel == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find channel")) + .setEphemeral(true).queue(); + return null; + } + + if (!(channel instanceof TextChannel textChannel)) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Not a valid TextChannel")) + .setEphemeral(true).queue(); + return null; + } + + if (!textChannel.canTalk()) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "I can't talk in this channel")) + .setEphemeral(true).queue(); + return null; + } + return textChannel; + } + + private Long getFromNow(String fromNow, SlashCommandInteractionEvent event) { + if (fromNow == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find from now option")) + .setEphemeral(true).queue(); + return null; + } + + if (!fromNow.matches("[1-9][0-9]*[dmy]")) { + return fromNowTimestamp(fromNow, event); + } + + int i; + try { + i = Integer.parseInt(fromNow.substring(0, fromNow.length() - 1)); + } catch (NumberFormatException e) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Invalid number")) + .setEphemeral(true).queue(); + return null; + } + + switch (fromNow.substring(fromNow.length() - 1)) { + case "d" -> { + return TimeUnit.DAYS.toMillis(i) + new Date().getTime(); + } + case "m" -> { + Calendar instance = Calendar.getInstance(); + instance.setTime(new Date()); + instance.add(Calendar.MONTH, i); + return instance.getTimeInMillis(); + } + case "y" -> { + Calendar instance = Calendar.getInstance(); + instance.setTime(new Date()); + instance.add(Calendar.YEAR, i); + return instance.getTimeInMillis(); + } + default -> { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Invalid format? This shouldn't be possible...")) + .setEphemeral(true).queue(); + return null; + } + } + } + + private Long fromNowTimestamp(String fromNow, SlashCommandInteractionEvent event) { + if (!fromNow.matches("t:[1-9][0-9]*")) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Invalid from now format ex: `1d`")) + .setEphemeral(true).queue(); + return null; + } + long l; + try { + l = Long.parseLong(fromNow.substring(2)); + } catch (NumberFormatException e) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Invalid number")) + .setEphemeral(true).queue(); + return null; + } + return l; + } + + @Override + public void suggest(CommandAutoCompleteInteractionEvent event) { + //TODO implement suggest + } + + @Override + public String getHelpMessage() { + return null; + } + + @Override + public CommandData getCommandData() { + return commandData; + } } diff --git a/src/main/java/com/alttd/database/DatabaseTables.java b/src/main/java/com/alttd/database/DatabaseTables.java index d1c0b3f..4150d5f 100644 --- a/src/main/java/com/alttd/database/DatabaseTables.java +++ b/src/main/java/com/alttd/database/DatabaseTables.java @@ -99,6 +99,28 @@ public class DatabaseTables { } } + private void createReminderTable() { + String sql = "CREATE TABLE IF NOT EXISTS new_reminders(" + + "id INT NOT NULL AUTO_INCREMENT, " + + "title VARCHAR(256) NOT NULL, " + + "description VARCHAR(4096) NOT NULL, " + + "user_id LONG NOT NULL, " + + "guild_id LONG NOT NULL, " + + "channel_id LONG NOT NULL, " + + "message_id LONG NOT NULL, " + + "should_repeat TINYINT(1) NOT NULL, " + + "creation_date LONG NOT NULL, " + + "remind_date LONG NOT NULL, " + + "PRIMARY KEY (id)" + + ")"; + try { + connection.prepareStatement(sql).executeUpdate(); + } catch (SQLException e) { + Logger.sql(e); + Logger.severe("Unable to create reminders table, shutting down..."); + } + } + public static void createTables(Connection connection) { if (instance == null) instance = new DatabaseTables(connection); diff --git a/src/main/java/com/alttd/database/queries/QueriesReminders/QueriesReminders.java b/src/main/java/com/alttd/database/queries/QueriesReminders/QueriesReminders.java new file mode 100644 index 0000000..fba207c --- /dev/null +++ b/src/main/java/com/alttd/database/queries/QueriesReminders/QueriesReminders.java @@ -0,0 +1,90 @@ +package com.alttd.database.queries.QueriesReminders; + +import com.alttd.database.Database; +import com.alttd.util.Logger; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; + +public class QueriesReminders { + + public static int storeReminder(Reminder reminder) { + String sql = "INSERT INTO new_reminders " + + "(title, description, user_id, guild_id, channel_id, message_id, should_repeat, creation_date, remind_date) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + try { + PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + + preparedStatement.setString(1, reminder.title()); + preparedStatement.setString(2, reminder.description()); + preparedStatement.setLong(3, reminder.userId()); + preparedStatement.setLong(4, reminder.guildId()); + preparedStatement.setLong(5, reminder.channelId()); + preparedStatement.setLong(6, 0); + preparedStatement.setInt(7, reminder.shouldRepeat() ? 1 : 0); + preparedStatement.setLong(8, reminder.creationDate()); + preparedStatement.setLong(9, reminder.remindDate()); + + if (preparedStatement.executeUpdate() == 1) { + ResultSet generatedKeys = preparedStatement.getGeneratedKeys(); + if (generatedKeys.next()) { + return generatedKeys.getInt(1); + } + } + + return -1; + } catch (SQLException e) { + Logger.exception(e); + } + return -1; + } + + public static boolean removeReminder(int id) { + String sql = "DELETE FROM new_reminders WHERE id = ?"; + try { + PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql); + + preparedStatement.setInt(1, id); + + return preparedStatement.executeUpdate() == 1; + } catch (SQLException e) { + Logger.exception(e); + } + return false; + } + + public static ArrayList getReminders() { + String sql = "SELECT * FROM new_reminders"; + try { + ArrayList reminders = new ArrayList<>(); + PreparedStatement preparedStatement = Database.getDatabase().getConnection().prepareStatement(sql); + + ResultSet resultSet = preparedStatement.executeQuery(); + while (resultSet.next()) { + reminders.add(getReminder(resultSet)); + } + return reminders; + } catch (SQLException e) { + Logger.exception(e); + } + return null; + } + + private static Reminder getReminder(ResultSet resultSet) throws SQLException { + int id = resultSet.getInt("id"); + String title = resultSet.getString("title"); + String desc = resultSet.getString("description"); + long userId = resultSet.getLong("user_id"); + long guildId = resultSet.getLong("guild_id"); + long channelId = resultSet.getLong("channel_id"); + long messageId = resultSet.getLong("message_id"); + boolean shouldRepeat = resultSet.getInt("should_repeat") == 1; + long creationDate = resultSet.getLong("creation_date"); + long remindDate = resultSet.getLong("remind_date"); + return new Reminder(id, title, desc, userId, guildId, channelId, messageId, shouldRepeat, creationDate, remindDate); + } + +} diff --git a/src/main/java/com/alttd/database/queries/QueriesReminders/Reminder.java b/src/main/java/com/alttd/database/queries/QueriesReminders/Reminder.java new file mode 100644 index 0000000..6cb9934 --- /dev/null +++ b/src/main/java/com/alttd/database/queries/QueriesReminders/Reminder.java @@ -0,0 +1,33 @@ +package com.alttd.database.queries.QueriesReminders; + +import com.alttd.util.Logger; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; + +public record Reminder (int id, String title, String description, long userId, long guildId, long channelId, + long messageId, boolean shouldRepeat, long creationDate, long remindDate) { + public TextChannel getChannel(JDA jda) { + Guild guildById = getGuild(jda); + if (guildById == null) + return null; + + TextChannel textChannelById = guildById.getTextChannelById(this.channelId); + if (textChannelById == null) { + Logger.warning("Unable to find text channel for reminder, text channel id: [" + channelId + "]"); + return null; + } + + return textChannelById; + } + + public Guild getGuild(JDA jda) { + Guild guildById = jda.getGuildById(guildId); + if (guildById == null) { + Logger.warning("Unable to find guild for reminder, guild id: [" + guildId + "]"); + return null; + } + + return guildById; + } +} diff --git a/src/main/java/com/alttd/listeners/JDAListener.java b/src/main/java/com/alttd/listeners/JDAListener.java index 6efd4bb..d117d58 100644 --- a/src/main/java/com/alttd/listeners/JDAListener.java +++ b/src/main/java/com/alttd/listeners/JDAListener.java @@ -4,6 +4,7 @@ import com.alttd.buttonManager.ButtonManager; import com.alttd.commandManager.CommandManager; import com.alttd.contextMenuManager.ContextMenuManager; import com.alttd.modalManager.ModalManager; +import com.alttd.reminders.ReminderScheduler; import com.alttd.request.RequestManager; import com.alttd.util.Logger; import net.dv8tion.jda.api.JDA; @@ -30,6 +31,10 @@ public class JDAListener extends ListenerAdapter { ContextMenuManager contextMenuManager = new ContextMenuManager(modalManager); CommandManager commandManager = new CommandManager(jda, modalManager, contextMenuManager); jda.addEventListener(buttonManager, modalManager, commandManager, contextMenuManager); + ReminderScheduler reminderScheduler = ReminderScheduler.getInstance(jda); + if (reminderScheduler == null) { + Logger.severe("Unable to start reminder scheduler!"); + } // RequestManager.init(); } diff --git a/src/main/java/com/alttd/modalManager/ModalManager.java b/src/main/java/com/alttd/modalManager/ModalManager.java index 1a3a608..ac2d6b7 100644 --- a/src/main/java/com/alttd/modalManager/ModalManager.java +++ b/src/main/java/com/alttd/modalManager/ModalManager.java @@ -2,6 +2,7 @@ package com.alttd.modalManager; import com.alttd.buttonManager.ButtonManager; import com.alttd.modalManager.modals.ModalEvidence; +import com.alttd.modalManager.modals.ModalRemindMe; import com.alttd.modalManager.modals.ModalReplySuggestion; import com.alttd.modalManager.modals.ModalSuggestion; import com.alttd.util.Util; @@ -25,7 +26,8 @@ public class ModalManager extends ListenerAdapter { modals = List.of( new ModalSuggestion(buttonManager), new ModalEvidence(), - new ModalReplySuggestion()); + new ModalReplySuggestion(), + new ModalRemindMe(buttonManager)); } @Override diff --git a/src/main/java/com/alttd/modalManager/modals/ModalRemindMe.java b/src/main/java/com/alttd/modalManager/modals/ModalRemindMe.java new file mode 100644 index 0000000..4286cae --- /dev/null +++ b/src/main/java/com/alttd/modalManager/modals/ModalRemindMe.java @@ -0,0 +1,134 @@ +package com.alttd.modalManager.modals; + +import com.alttd.buttonManager.ButtonManager; +import com.alttd.buttonManager.buttons.remindMeConfirm.ButtonRemindMeConfirm; +import com.alttd.database.queries.QueriesReminders.Reminder; +import com.alttd.modalManager.DiscordModal; +import com.alttd.util.Util; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.Modal; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.interactions.components.text.TextInput; +import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; +import net.dv8tion.jda.api.interactions.modals.ModalMapping; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.utils.TimeUtil; + +import java.util.Date; +import java.util.HashMap; + +public class ModalRemindMe extends DiscordModal { + + private static final HashMap userToRemindMeMap = new HashMap<>(); + + public static synchronized void putData(long userId, TextChannel channel, long timestamp) { + userToRemindMeMap.put(userId, new RemindMeData(channel, timestamp)); + } + + private static synchronized RemindMeData pullData(long userId) { + return userToRemindMeMap.remove(userId); + } + + private final ButtonManager buttonManager; + + public ModalRemindMe(ButtonManager buttonManager) { + this.buttonManager = buttonManager; + } + + @Override + public String getModalId() { + return "remindme"; + } + + @Override + public void execute(ModalInteractionEvent event) { + String title = getValidString(event.getValue("title"), event); + if (title == null) + return; + + String desc = getValidString(event.getValue("description"), event); + if (desc == null) + return; + + long userId = event.getUser().getIdLong(); + RemindMeData remindMeData = pullData(userId); + if (remindMeData == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find the data from the command that triggered this modal")) + .setEphemeral(true).queue(); + return; + } + + Reminder reminder = new Reminder( + -1, + title, + desc, + userId, + remindMeData.textChannel.getGuild().getIdLong(), + remindMeData.textChannel.getIdLong(), + 0, + false, + new Date().getTime(), + remindMeData.timestamp); + + Button remindMeConfirm = buttonManager.getButtonFor("remind_me_confirm"); + Button remindMeCancel = buttonManager.getButtonFor("remind_me_cancel"); + if (remindMeConfirm == null || remindMeCancel == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Unable to retrieve continue/cancel buttons")) + .setEphemeral(true).queue(); + return; + } + + long discordTimestamp = TimeUtil.getDiscordTimestamp(reminder.creationDate()); + MessageEmbed messageEmbed = new EmbedBuilder() + .setTitle(reminder.title()) + .setDescription(reminder.description()) + .setFooter("Requested ") + .build(); + ButtonRemindMeConfirm.putReminder(userId, reminder); + event.replyEmbeds(messageEmbed).queue(message -> + message.editOriginalComponents().setActionRow(remindMeConfirm, remindMeCancel) + .queue(RestAction.getDefaultSuccess(), Util::handleFailure)); + } + + public String getValidString(ModalMapping modalMapping, ModalInteractionEvent event) { + if (modalMapping == null) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find modal")) + .setEphemeral(true).queue(); + return null; + } + + String string = modalMapping.getAsString(); + if (string.isEmpty()) { + event.replyEmbeds(Util.genericErrorEmbed("Error", "Couldn't find contents of modal")) + .setEphemeral(true).queue(); + return null; + } + + return string; + } + + @Override + public Modal getModal() { + TextInput title = TextInput.create("title", "Title", TextInputStyle.SHORT) + .setPlaceholder("reminder title:") + .setRequiredRange(1, 256) + .setRequired(true) + .build(); + + TextInput desc = TextInput.create("description", "Description", TextInputStyle.PARAGRAPH) + .setPlaceholder("optional reminder description:") + .setRequiredRange(1, 4096) + .setRequired(false) + .build(); + + return Modal.create(getModalId(), "Remind Me") + .addActionRows(ActionRow.of(title), ActionRow.of(desc)) + .build(); + } + + private record RemindMeData(TextChannel textChannel, long timestamp) {} +} diff --git a/src/main/java/com/alttd/reminders/ReminderScheduler.java b/src/main/java/com/alttd/reminders/ReminderScheduler.java new file mode 100644 index 0000000..79cb195 --- /dev/null +++ b/src/main/java/com/alttd/reminders/ReminderScheduler.java @@ -0,0 +1,114 @@ +package com.alttd.reminders; + +import com.alttd.database.queries.QueriesReminders.QueriesReminders; +import com.alttd.database.queries.QueriesReminders.Reminder; +import com.alttd.util.Logger; +import com.alttd.util.Util; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.api.utils.TimeUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class ReminderScheduler { + + private static ReminderScheduler instance = null; + private final ArrayList reminders; + private Reminder nextReminder; + private final JDA jda; + + private ReminderScheduler(JDA jda) { + instance = this; + this.jda = jda; + reminders = QueriesReminders.getReminders(); + if (reminders == null) { + Logger.severe("Unable to retrieve reminders"); + instance = null; + return; + } + reminders.sort(Comparator.comparingLong(Reminder::remindDate)); + if (reminders.size() == 0) + nextReminder = null; + else + nextReminder = reminders.get(0); + ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + scheduledExecutorService.schedule(new ReminderRun(), 1, TimeUnit.MINUTES); + + } + + public static ReminderScheduler getInstance(JDA jda) { + if (instance == null) + instance = new ReminderScheduler(jda); + return instance; + } + + public synchronized void addReminder(Reminder reminder) { + reminders.add(reminder); + reminders.sort(Comparator.comparingLong(Reminder::remindDate)); + nextReminder = reminders.get(0); + } + + public synchronized void removeReminder(Reminder reminder) { + reminders.remove(reminder); + if (reminders.size() == 0) + nextReminder = null; + else + nextReminder = reminders.get(0); + QueriesReminders.removeReminder(reminder.id()); + } + + private class ReminderRun implements Runnable { + + @Override + public void run() { + long time = new Date().getTime(); + while (nextReminder != null && time > nextReminder.remindDate()) { + //TODO run reminder + TextChannel channel = nextReminder.getChannel(jda); + if (channel == null || !channel.canTalk()) { + Logger.warning("Unable to run reminder: " + nextReminder.id() + + "\ntitle: [" + nextReminder.title() + + "]\ndescription: [" + nextReminder.description() + "]"); + return; + } + sendEmbed(nextReminder, channel); + removeReminder(nextReminder); + } + } + + private void sendEmbed(Reminder reminder, TextChannel channel) { + long discordTimestamp = TimeUtil.getDiscordTimestamp(reminder.creationDate()); + EmbedBuilder embedBuilder = new EmbedBuilder() + .setTitle(reminder.title()) + .setDescription(reminder.description()) + .setFooter("Requested "); + Guild guild = reminder.getGuild(jda); + if (guild == null) { + sendEmbed(reminder, channel, embedBuilder); + return; + } + guild.retrieveMemberById(reminder.userId()).queue( + member -> sendEmbed(reminder, channel, embedBuilder, member), + failed -> sendEmbed(reminder, channel, embedBuilder)); + } + + private void sendEmbed(Reminder reminder, TextChannel channel, EmbedBuilder embedBuilder, Member member) { + embedBuilder.setAuthor(member.getEffectiveName(), null, member.getEffectiveAvatarUrl()); + channel.sendMessageEmbeds(embedBuilder.build()).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + } + + private void sendEmbed(Reminder reminder, TextChannel channel, EmbedBuilder embedBuilder) { + embedBuilder.setAuthor(reminder.userId() + ""); + channel.sendMessageEmbeds(embedBuilder.build()).queue(RestAction.getDefaultSuccess(), Util::handleFailure); + } + } +}