From 98a162a33b6e7ef12cce3906d6b9f4add5fba025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beatrice=20Dellac=C3=A0?= Date: Mon, 21 Nov 2022 00:14:13 +0100 Subject: [PATCH] Implement SQLite database solving #1 A new basic database has been laid out, with support for message expiry and disabling buttons for old messages. --- db.sqlite | Bin 0 -> 12288 bytes pom.xml | 6 + .../wtf/beatrice/hidekobot/Configuration.java | 37 +++ .../wtf/beatrice/hidekobot/HidekoBot.java | 28 ++ .../commands/slash/ClearChatCommand.java | 248 ++++++++-------- .../hidekobot/database/DatabaseManager.java | 266 ++++++++++++++++++ .../hidekobot/utils/ExpiredMessageRunner.java | 99 +++++++ 7 files changed, 569 insertions(+), 115 deletions(-) create mode 100644 db.sqlite create mode 100644 src/main/java/wtf/beatrice/hidekobot/database/DatabaseManager.java create mode 100644 src/main/java/wtf/beatrice/hidekobot/utils/ExpiredMessageRunner.java diff --git a/db.sqlite b/db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..4b93be5739244d656f74f724b751ec5c9a464b9e GIT binary patch literal 12288 zcmeI#O;5rw7zgkUOu&QUjf;mKC8)IBOSfGR4L2gbVB&2VY~m6&$u<%_!Uyp?_zf)v z5`l0q;b6kQN!NBy^X%y_yY%tC6XhX&c}rtIr#d+ynntcDC4`h!FRH$51vNQXF4U;~ zns1wy$)$ICq@so6DXCnj2O0z*009U<00Izz00bZa0SN4;z_eBrR`l1?kyv5{&{c3TUtW+@rT$)t@?@b2@nSRkqP) zBo4F8ABLNONfxFXZ)?*#tyFC`wOQk*)^V5wQ8M&`D4Ulac#Cw|)<9vU{;k_~d6Mvb z9Hk##9;xDTKOV2z)aG|{b?GOSuS$PEe?XiX0uX=z1Rwwb2tWV=5P$##Ah08WPp&r{ z)3%v3brC zv!uxk%W>3~ZF`Z9Q^s88xQ4mB|L^FeNCN=~KmY;|fB*y_009U<00Izzz&{lD25|wY AlK=n! literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml index d94b454..31684fc 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,12 @@ slf4j-simple 2.0.0 + + org.xerial + sqlite-jdbc + 3.39.4.1 + + diff --git a/src/main/java/wtf/beatrice/hidekobot/Configuration.java b/src/main/java/wtf/beatrice/hidekobot/Configuration.java index ea43355..db3b5d9 100644 --- a/src/main/java/wtf/beatrice/hidekobot/Configuration.java +++ b/src/main/java/wtf/beatrice/hidekobot/Configuration.java @@ -1,17 +1,23 @@ package wtf.beatrice.hidekobot; +import org.jetbrains.annotations.Nullable; +import wtf.beatrice.hidekobot.database.DatabaseManager; import wtf.beatrice.hidekobot.listeners.MessageLogger; public class Configuration { + private static DatabaseManager dbManager = null; private static boolean verbose = false; private static MessageLogger verbosityLogger; // todo: allow people to set their own user id private static final long botOwnerId = 979809420714332260L; + private final static String expiryTimestampFormat = "yy/MM/dd HH:mm:ss"; + private final static long expiryTimeSeconds = 60L; + private final static String defaultInviteLink = "https://discord.com/api/oauth2/authorize?client_id=%userid%&scope=bot+applications.commands&permissions=8"; @@ -95,4 +101,35 @@ public class Configuration return defaultInviteLink.replace("%userid%", botApplicationId); } + /** + * Set the already fully-initialized DatabaseManager instance, ready to be accessed and used. + * + * @param databaseManagerInstance the fully-initialized DatabaseManager instance. + */ + public static void setDatabaseManagerInstance(DatabaseManager databaseManagerInstance) + { + dbManager = databaseManagerInstance; + } + + /** + * Get the fully-initialized DatabaseManager instance, ready to be used. + * + * @return the DatabaseManager instance. + */ + public static @Nullable DatabaseManager getDatabaseManager() { return dbManager; } + + /** + * Get the DateTimeFormatter string for parsing the expired messages timestamp. + * + * @return the String of the DateTimeFormatter format. + */ + public static String getExpiryTimestampFormat(){ return expiryTimestampFormat; } + + /** + * Get the amount of seconds after which a message expires. + * + * @return long value of the expiry seconds. + */ + public static long getExpiryTimeSeconds() { return expiryTimeSeconds; } + } diff --git a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java index d422208..5d32221 100644 --- a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java +++ b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java @@ -6,17 +6,21 @@ import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.requests.GatewayIntent; import sun.misc.Signal; +import wtf.beatrice.hidekobot.database.DatabaseManager; import wtf.beatrice.hidekobot.listeners.ButtonInteractionListener; import wtf.beatrice.hidekobot.listeners.MessageListener; import wtf.beatrice.hidekobot.listeners.SlashCommandCompleter; import wtf.beatrice.hidekobot.listeners.SlashCommandListener; +import wtf.beatrice.hidekobot.utils.ExpiredMessageRunner; import wtf.beatrice.hidekobot.utils.Logger; import wtf.beatrice.hidekobot.utils.SlashCommandsUtil; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class HidekoBot @@ -93,6 +97,30 @@ public class HidekoBot jda.getPresence().setStatus(OnlineStatus.ONLINE); jda.getPresence().setActivity(Activity.playing("Hatsune Miku: Project DIVA")); + // connect to database + logger.log("Connecting to database..."); + String dbFilePath = System.getProperty("user.dir") + File.separator + "db.sqlite"; // in current directory + DatabaseManager dbManager = new DatabaseManager(dbFilePath); + if(dbManager.connect() && dbManager.initDb()) + { + logger.log("Database connection initialized!"); + Configuration.setDatabaseManagerInstance(dbManager); + + // load data here... + + logger.log("Database data loaded into memory!"); + } else { + logger.log("Error initializing database connection!"); + } + + // start scheduled runnables + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + ExpiredMessageRunner task = new ExpiredMessageRunner(); + int initDelay = 5; + int periodicDelay = 5; + scheduler.scheduleAtFixedRate(task, initDelay, periodicDelay, TimeUnit.SECONDS); + + // print the bot logo. logger.log("\n\n" + logger.getLogo() + "\nv" + version + " - bot is ready!\n", 2); diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearChatCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearChatCommand.java index 77e36bc..d16f80a 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearChatCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearChatCommand.java @@ -10,7 +10,9 @@ import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.requests.restaction.WebhookMessageEditAction; import org.jetbrains.annotations.NotNull; +import wtf.beatrice.hidekobot.Configuration; import java.util.ArrayList; import java.util.List; @@ -20,145 +22,161 @@ public class ClearChatCommand public void runSlashCommand(@NotNull SlashCommandInteractionEvent event) { - MessageChannel channel = event.getChannel(); - if(!(channel instanceof TextChannel)) + // run in a new thread, so we don't block the main one + new Thread(() -> { - event.reply("Sorry! I can't delete messages here.").queue(); - return; - } - /* get the amount from the command args. - NULL should not be possible because we specified them as mandatory, - but apparently the mobile app doesn't care and still sends the command if you omit the args. */ - OptionMapping amountMapping = event.getOption("amount"); - int toDeleteAmount = amountMapping == null ? 1 : amountMapping.getAsInt(); + MessageChannel channel = event.getChannel(); - if(toDeleteAmount <= 0) - { - event.reply("Sorry, I can't delete that amount of messages!").queue(); - } - else { - // answer by saying that the operation has begun. - InteractionHook replyInteraction = event.reply("\uD83D\uDEA7 Clearing...").complete(); - - // int to keep track of how many messages we actually deleted. - int deleted = 0; - - int limit = 95; //discord limits this method to range 2-100. we set it to 95 to be safe. - - // increase the count by 1, because we technically aren't clearing the first ID ever - // which is actually the slash command's ID and not a message. - toDeleteAmount++; - - // count how many times we have to iterate this to delete the full messages. - int iterations = toDeleteAmount / limit; - - //if there are some messages left, but less than , we need one more iterations. - int remainder = toDeleteAmount % limit; - if(remainder != 0) iterations++; - - // set the starting point. - long messageId = event.getInteraction().getIdLong(); - - // boolean to see if we're trying to delete more messages than possible. - boolean outOfBounds = false; - - // do iterate. - for(int iteration = 0; iteration < iterations; iteration++) + if(!(channel instanceof TextChannel)) { - if(outOfBounds) break; + event.reply("Sorry! I can't delete messages here.").queue(); + return; + } - // set how many messages to delete for this iteration (usually unless there's a remainder) - int iterationSize = limit; + /* get the amount from the command args. + NULL should not be possible because we specified them as mandatory, + but apparently the mobile app doesn't care and still sends the command if you omit the args. */ + OptionMapping amountMapping = event.getOption("amount"); + int toDeleteAmount = amountMapping == null ? 1 : amountMapping.getAsInt(); - // if we are at the last iteration... - if(iteration+1 == iterations) + if(toDeleteAmount <= 0) + { + event.reply("Sorry, I can't delete that amount of messages!").queue(); + } + else { + // answer by saying that the operation has begun. + InteractionHook replyInteraction = event.reply("\uD83D\uDEA7 Clearing...").complete(); + + // int to keep track of how many messages we actually deleted. + int deleted = 0; + + int limit = 95; //discord limits this method to range 2-100. we set it to 95 to be safe. + + // increase the count by 1, because we technically aren't clearing the first ID ever + // which is actually the slash command's ID and not a message. + toDeleteAmount++; + + // count how many times we have to iterate this to delete the full messages. + int iterations = toDeleteAmount / limit; + + //if there are some messages left, but less than , we need one more iterations. + int remainder = toDeleteAmount % limit; + if(remainder != 0) iterations++; + + // set the starting point. + long messageId = event.getInteraction().getIdLong(); + + // boolean to see if we're trying to delete more messages than possible. + boolean outOfBounds = false; + + // do iterate. + for(int iteration = 0; iteration < iterations; iteration++) { - // check if we have or fewer messages to delete - if(remainder != 0) iterationSize = remainder; - } + if(outOfBounds) break; - if(iterationSize == 1) - { - // grab the message - Message toDelete = ((TextChannel)channel).retrieveMessageById(messageId).complete(); - //only delete one message - if(toDelete != null) toDelete.delete().queue(); - else outOfBounds = true; - // increase deleted counter by 1 - deleted++; - } else { - // get the last messages. - MessageHistory.MessageRetrieveAction action = channel.getHistoryBefore(messageId, iterationSize - 1); - // note: first one is the most recent, last one is the oldest message. - List messages = new ArrayList<>(); - // (we are skipping first iteration since it would return an error, given that the id is the slash command and not a message) - if(iteration!=0) messages.add(((TextChannel)channel).retrieveMessageById(messageId).complete()); - messages.addAll(action.complete().getRetrievedHistory()); + // set how many messages to delete for this iteration (usually unless there's a remainder) + int iterationSize = limit; - // check if we only have one or zero messages left (trying to delete more than possible) - if(messages.size() <= 1) + // if we are at the last iteration... + if(iteration+1 == iterations) { - outOfBounds = true; - } else { - // before deleting, we need to grab the message's id for next iteration. - action = channel.getHistoryBefore(messages.get(messages.size() - 1).getIdLong(), 1); + // check if we have or fewer messages to delete + if(remainder != 0) iterationSize = remainder; + } - List previousMessage = action.complete().getRetrievedHistory(); - - // if that message exists (we are not out of bounds)... store it - if(!previousMessage.isEmpty()) messageId = previousMessage.get(0).getIdLong(); + if(iterationSize == 1) + { + // grab the message + Message toDelete = ((TextChannel)channel).retrieveMessageById(messageId).complete(); + //only delete one message + if(toDelete != null) toDelete.delete().queue(); else outOfBounds = true; - } + // increase deleted counter by 1 + deleted++; + } else { + // get the last messages. + MessageHistory.MessageRetrieveAction action = channel.getHistoryBefore(messageId, iterationSize - 1); + // note: first one is the most recent, last one is the oldest message. + List messages = new ArrayList<>(); + // (we are skipping first iteration since it would return an error, given that the id is the slash command and not a message) + if(iteration!=0) messages.add(((TextChannel)channel).retrieveMessageById(messageId).complete()); + messages.addAll(action.complete().getRetrievedHistory()); - // queue messages for deletion - if(messages.size() == 1) - { - messages.get(0).delete().queue(); - } - else if(!messages.isEmpty()) - { - try { - ((TextChannel) channel).deleteMessages(messages).complete(); - /* alternatively, we could use purgeMessages, which is smarter... - however, it also tries to delete messages older than 2 weeks - which are restricted by discord, and thus has to use - a less efficient way that triggers rate-limiting very quickly. */ - } catch (Exception e) + // check if we only have one or zero messages left (trying to delete more than possible) + if(messages.size() <= 1) { - replyInteraction.editOriginal("\uD83D\uDE22 Sorry, it seems like there was an issue! " + e.getMessage()).queue(); - return; // warning: this quits everything. + outOfBounds = true; + } else { + // before deleting, we need to grab the message's id for next iteration. + action = channel.getHistoryBefore(messages.get(messages.size() - 1).getIdLong(), 1); + + List previousMessage = action.complete().getRetrievedHistory(); + + // if that message exists (we are not out of bounds)... store it + if(!previousMessage.isEmpty()) messageId = previousMessage.get(0).getIdLong(); + else outOfBounds = true; } + + // queue messages for deletion + if(messages.size() == 1) + { + messages.get(0).delete().queue(); + } + else if(!messages.isEmpty()) + { + try { + ((TextChannel) channel).deleteMessages(messages).complete(); + /* alternatively, we could use purgeMessages, which is smarter... + however, it also tries to delete messages older than 2 weeks + which are restricted by discord, and thus has to use + a less efficient way that triggers rate-limiting very quickly. */ + } catch (Exception e) + { + replyInteraction.editOriginal("\uD83D\uDE22 Sorry, it seems like there was an issue! " + e.getMessage()).queue(); + return; // warning: this quits everything. + } + } + + // increase deleted counter by + deleted += messages.size(); } - - // increase deleted counter by - deleted += messages.size(); } + + + Button deleteButton = Button.danger("clear_delete", "Delete").withEmoji(Emoji.fromUnicode("❌")); + + WebhookMessageEditAction webhookMessageEditAction; + + // log having deleted the messages (or not). + if(deleted < 1) + { + webhookMessageEditAction = replyInteraction.editOriginal("\uD83D\uDE22 Couldn't clear any message!"); + } else if(deleted == 1) + { + webhookMessageEditAction = replyInteraction.editOriginal("✂ Cleared 1 message!"); + } else { + webhookMessageEditAction = replyInteraction.editOriginal("✂ Cleared " + deleted + " messages!"); + } + + Message message = webhookMessageEditAction + .setActionRow(deleteButton) + .complete(); + + String replyMessageId = message.getId(); + String replyChannelId = message.getChannel().getId(); + String replyGuildId = message.getGuild().getId(); + + Configuration.getDatabaseManager().queueDisabling(replyGuildId, replyChannelId, replyMessageId); + } + }).start(); - - Button deleteButton = Button.danger("clear_delete", "Delete").withEmoji(Emoji.fromUnicode("❌")); - - // log having deleted the messages (or not). - if(deleted < 1) - { - replyInteraction.editOriginal("\uD83D\uDE22 Couldn't clear any message!") - .setActionRow(deleteButton).queue(); - } else if(deleted == 1) - { - replyInteraction.editOriginal("✂ Cleared 1 message!") - .setActionRow(deleteButton).queue(); - } else { - replyInteraction.editOriginal("✂ Cleared " + deleted + " messages!") - .setActionRow(deleteButton).queue(); - } - } } public void deleteButton(ButtonInteractionEvent event) { - // todo: permissions check event.getInteraction().getMessage().delete().queue(); } } diff --git a/src/main/java/wtf/beatrice/hidekobot/database/DatabaseManager.java b/src/main/java/wtf/beatrice/hidekobot/database/DatabaseManager.java new file mode 100644 index 0000000..221104f --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/database/DatabaseManager.java @@ -0,0 +1,266 @@ +package wtf.beatrice.hidekobot.database; + +import wtf.beatrice.hidekobot.Configuration; +import wtf.beatrice.hidekobot.utils.Logger; + +import java.sql.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class DatabaseManager +{ + + private final static String sqliteURL = "jdbc:sqlite:%path%"; + private Connection dbConnection = null; + private final String dbPath; + private final Logger logger; + + public DatabaseManager(String dbPath) + { + this.dbPath = dbPath; + this.logger = new Logger(getClass()); + } + + public boolean connect() + { + String url = sqliteURL.replace("%path%", dbPath); + + if(!close()) return false; + + try { + dbConnection = DriverManager.getConnection(url); + logger.log("Database connection established!"); + return true; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + + } + + + public boolean close() + { + if (dbConnection != null) + { + try { + if(!dbConnection.isClosed()) + { + dbConnection.close(); + } + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + + dbConnection = null; + } + + return true; + } + + + + public boolean initDb() + { + List newTables = new ArrayList<>(); + + newTables.add("CREATE TABLE IF NOT EXISTS pending_disabled_messages (" + + "guild_id TEXT NOT NULL, " + + "channel_id TEXT NOT NULL," + + "message_id TEXT NOT NULL," + + "expiry_timestamp TEXT NOT NULL" + + ");"); + + newTables.add("CREATE TABLE IF NOT EXISTS command_runners (" + + "guild_id TEXT NOT NULL, " + + "channel_id TEXT NOT NULL," + // channel the command was run in + "message_id TEXT NOT NULL," + // message id of the bot's response + "user_id TEXT NOT NULL" + // user who ran the command + ");"); + + for(String sql : newTables) + { + try (Statement stmt = dbConnection.createStatement()) { + // execute the statement + stmt.execute(sql); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + return true; + } + + public boolean queueDisabling(String guildId, String channelId, String messageId) + { + LocalDateTime expiryTime = LocalDateTime.now().plusSeconds(Configuration.getExpiryTimeSeconds()); + + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Configuration.getExpiryTimestampFormat()); + String expiryTimeFormatted = dateTimeFormatter.format(expiryTime); + + String query = "INSERT INTO pending_disabled_messages " + + "(guild_id, channel_id, message_id, expiry_timestamp) VALUES " + + " (?, ?, ?, ?);"; + + try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) + { + preparedStatement.setString(1, guildId); + preparedStatement.setString(2, channelId); + preparedStatement.setString(3, messageId); + preparedStatement.setString(4, expiryTimeFormatted); + + preparedStatement.executeUpdate(); + + return true; + } catch (SQLException e) + { + e.printStackTrace(); + } + + return false; + } + + public List getQueuedExpiringMessages() + { + List messages = new ArrayList<>(); + + String query = "SELECT message_id " + + "FROM pending_disabled_messages "; + + try (Statement statement = dbConnection.createStatement()) + { + ResultSet resultSet = statement.executeQuery(query); + if(resultSet.isClosed()) return messages; + while(resultSet.next()) + { + messages.add(resultSet.getString("message_id")); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return messages; + } + + public boolean untrackExpiredMessage(String messageId) + { + String query = "DELETE FROM pending_disabled_messages WHERE message_id = ?;"; + + try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) + { + preparedStatement.setString(1, messageId); + preparedStatement.execute(); + } catch (SQLException e) + { + e.printStackTrace(); + return false; + } + + query = "DELETE FROM command_runners WHERE message_id = ?;"; + try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) + { + preparedStatement.setString(1, messageId); + preparedStatement.execute(); + } catch (SQLException e) + { + e.printStackTrace(); + return false; + } + + return true; + } + + public String getQueuedExpiringMessageExpiryDate(String messageId) + { + String query = "SELECT expiry_timestamp " + + "FROM pending_disabled_messages " + + "WHERE message_id = ?;"; + + try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) + { + preparedStatement.setString(1, messageId); + ResultSet resultSet = preparedStatement.executeQuery(); + if(resultSet.isClosed()) return null; + while(resultSet.next()) + { + return resultSet.getString("expiry_timestamp"); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return null; + } + + public String getQueuedExpiringMessageChannel(String messageId) + { + String query = "SELECT channel_id " + + "FROM pending_disabled_messages " + + "WHERE message_id = ?;"; + + try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) + { + preparedStatement.setString(1, messageId); + ResultSet resultSet = preparedStatement.executeQuery(); + if(resultSet.isClosed()) return null; + while(resultSet.next()) + { + return resultSet.getString("channel_id"); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return null; + } + + public String getQueuedExpiringMessageGuild(String messageId) + { + String query = "SELECT guild_id " + + "FROM pending_disabled_messages " + + "WHERE message_id = ?;"; + + try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) + { + preparedStatement.setString(1, messageId); + ResultSet resultSet = preparedStatement.executeQuery(); + if(resultSet.isClosed()) return null; + while(resultSet.next()) + { + return resultSet.getString("guild_id"); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + + return null; + } + + /** + * DB STRUCTURE + * TABLE 1: pending_disabled_messages + * ---------------------------------------------------------------------------------- + * | guild_id | channel_id | message_id | expiry_timestamp | + * ---------------------------------------------------------------------------------- + * |39402849302 | 39402849302 | 39402849302 | 2022-11-20 22:45:53:300 | + * ---------------------------------------------------------------------------------- + * + * + * TABLE 2: command_runners + * -------------------------------------------------------------------------- + * | guild_id | channel_id | message_id | user_id | + * -------------------------------------------------------------------------- + * | 39402849302 | 39402849302 | 39402849302 | 39402849302 | + * -------------------------------------------------------------------------- + * + */ + + +} diff --git a/src/main/java/wtf/beatrice/hidekobot/utils/ExpiredMessageRunner.java b/src/main/java/wtf/beatrice/hidekobot/utils/ExpiredMessageRunner.java new file mode 100644 index 0000000..ff3d151 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/utils/ExpiredMessageRunner.java @@ -0,0 +1,99 @@ +package wtf.beatrice.hidekobot.utils; + +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.interactions.components.LayoutComponent; +import net.dv8tion.jda.api.requests.RestAction; +import wtf.beatrice.hidekobot.Configuration; +import wtf.beatrice.hidekobot.HidekoBot; +import wtf.beatrice.hidekobot.database.DatabaseManager; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class ExpiredMessageRunner implements Runnable { + + private final DateTimeFormatter formatter; + private final Logger logger; + private DatabaseManager databaseManager; + + + public ExpiredMessageRunner() + { + String format = Configuration.getExpiryTimestampFormat(); + formatter = DateTimeFormatter.ofPattern(format); + databaseManager = Configuration.getDatabaseManager(); + logger = new Logger(getClass()); + } + + + @Override + public void run() { + + databaseManager = Configuration.getDatabaseManager(); + if(databaseManager == null) return; + + List expiringMessages = Configuration.getDatabaseManager().getQueuedExpiringMessages(); + if(expiringMessages == null || expiringMessages.isEmpty()) return; + + LocalDateTime now = LocalDateTime.now(); + + for(String messageId : expiringMessages) + { + logger.log("ID: " + messageId); + String expiryTimestamp = databaseManager.getQueuedExpiringMessageExpiryDate(messageId); + if(expiryTimestamp == null || expiryTimestamp.equals("")) continue; //todo: idk count it as expired already? + + + LocalDateTime expiryDate = LocalDateTime.parse(expiryTimestamp, formatter); + if(now.isAfter(expiryDate)) + { + disableExpired(messageId); + } + } + + + + } + + private void disableExpired(String messageId) + { + String guildId = databaseManager.getQueuedExpiringMessageGuild(messageId); + String channelId = databaseManager.getQueuedExpiringMessageChannel(messageId); + + + + Guild guild = HidekoBot.getAPI().getGuildById(guildId); + if(guild == null) return; //todo count it as done/solved/removed? we probably got kicked + TextChannel textChannel = guild.getTextChannelById(channelId); + if(textChannel == null) return; //todo count it as done/solved/removed? channel was probably deleted + + RestAction retrieveAction = textChannel.retrieveMessageById(messageId); + + + + retrieveAction.queue( + + message -> { + if(message == null) return; //todo count it as done/solved/removed? message was probably deleted + + List components = message.getComponents(); + List newComponents = new ArrayList<>(); + for (LayoutComponent component : components) + { + component = component.asDisabled(); + newComponents.add(component); + } + + message.editMessageComponents(newComponents).queue(); + databaseManager.untrackExpiredMessage(messageId); + }, + + (error) -> { + databaseManager.untrackExpiredMessage(messageId); + }); + } +}