diff --git a/pom.xml b/pom.xml index 3aed105..870c215 100644 --- a/pom.xml +++ b/pom.xml @@ -6,14 +6,16 @@ wtf.beatrice.hidekobot HidekoBot - 0.6.3-SNAPSHOT + 0.9.0-SNAPSHOT 21 21 UTF-8 - ./target/dependency-check-report.html - ./target/dependency-check-report.json + ./target/dependency-check-report.html + + ./target/dependency-check-report.json + true @@ -30,11 +32,6 @@ slf4j-api 2.0.17 - - org.slf4j - slf4j-simple - 2.0.17 - @@ -95,27 +92,10 @@ test - - - org.springframework - spring-context - 6.2.7 - - - org.springframework - spring-tx - 6.2.7 - - - org.springframework - spring-orm - 6.2.7 - - - org.springframework.data - spring-data-jpa - 3.3.4 + org.springframework.boot + spring-boot-starter-data-jpa + 3.4.5 @@ -159,29 +139,6 @@ - - org.apache.maven.plugins - maven-assembly-plugin - - - package - - single - - - - - wtf.beatrice.hidekobot.HidekoBot - - - - jar-with-dependencies - - - - - - org.apache.maven.plugins maven-javadoc-plugin @@ -202,7 +159,9 @@ 8 ${nvdApiKey} - https://raw.githubusercontent.com/EugenMayer/cisa-known-exploited-mirror/main/known_exploited_vulnerabilities.json + + https://raw.githubusercontent.com/EugenMayer/cisa-known-exploited-mirror/main/known_exploited_vulnerabilities.json + html json @@ -213,6 +172,19 @@ + + org.springframework.boot + spring-boot-maven-plugin + 3.4.5 + + + + repackage + + + + + diff --git a/src/main/java/wtf/beatrice/hidekobot/Cache.java b/src/main/java/wtf/beatrice/hidekobot/Cache.java index 7a1385e..35398fb 100644 --- a/src/main/java/wtf/beatrice/hidekobot/Cache.java +++ b/src/main/java/wtf/beatrice/hidekobot/Cache.java @@ -5,12 +5,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import wtf.beatrice.hidekobot.datasources.ConfigurationEntry; import wtf.beatrice.hidekobot.datasources.ConfigurationSource; -import wtf.beatrice.hidekobot.datasources.DatabaseSource; import wtf.beatrice.hidekobot.datasources.PropertiesSource; import wtf.beatrice.hidekobot.listeners.MessageCommandListener; import wtf.beatrice.hidekobot.listeners.MessageLogger; import wtf.beatrice.hidekobot.listeners.SlashCommandCompletionListener; import wtf.beatrice.hidekobot.listeners.SlashCommandListener; +import wtf.beatrice.hidekobot.util.Services; import java.awt.*; import java.lang.reflect.Field; @@ -27,11 +27,22 @@ public class Cache throw new IllegalStateException("Utility class"); } - // todo: make this compatible with the message listener's regex private static final String BOT_PREFIX = "hideko"; private static final Logger LOGGER = LoggerFactory.getLogger(Cache.class); + private static Services SERVICES; + + public static void setServices(Services services) + { + SERVICES = services; + } + + public static Services getServices() + { + return SERVICES; + } + // map to store results of "love calculator", to avoid people re-running the same command until // they get what they wanted. // i didn't think this was worthy of a whole database table with a runnable checking for expiration, @@ -40,7 +51,6 @@ public class Cache private static PropertiesSource propertiesSource = null; private static ConfigurationSource configurationSource = null; - private static DatabaseSource databaseSource = null; private static boolean verbose = false; private static MessageLogger verbosityLogger = null; private static final long BOT_MAINTAINER_ID = 979809420714332260L; @@ -181,26 +191,6 @@ public class Cache return DEFAULT_INVITE_LINK.replace("%userid%", botApplicationId); } - /** - * Set the already fully-initialized DatabaseSource instance, ready to be accessed and used. - * - * @param databaseSourceInstance the fully-initialized DatabaseSource instance. - */ - public static void setDatabaseSourceInstance(DatabaseSource databaseSourceInstance) - { - databaseSource = databaseSourceInstance; - } - - /** - * Get the fully-initialized DatabaseSource instance, ready to be used. - * - * @return the DatabaseSource instance. - */ - public static @Nullable DatabaseSource getDatabaseSource() - { - return databaseSource; - } - /** * Set the properties source instance loaded from the JAR archive. * diff --git a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java index cffb67e..80f9693 100644 --- a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java +++ b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java @@ -6,20 +6,24 @@ import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.requests.GatewayIntent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; import wtf.beatrice.hidekobot.commands.completer.ProfileImageCommandCompleter; import wtf.beatrice.hidekobot.commands.message.HelloCommand; import wtf.beatrice.hidekobot.commands.slash.*; import wtf.beatrice.hidekobot.datasources.ConfigurationSource; -import wtf.beatrice.hidekobot.datasources.DatabaseSource; import wtf.beatrice.hidekobot.datasources.PropertiesSource; import wtf.beatrice.hidekobot.listeners.*; import wtf.beatrice.hidekobot.runnables.ExpiredMessageTask; import wtf.beatrice.hidekobot.runnables.HeartBeatTask; import wtf.beatrice.hidekobot.runnables.RandomOrgSeedTask; import wtf.beatrice.hidekobot.runnables.StatusUpdateTask; +import wtf.beatrice.hidekobot.services.DatabaseService; import wtf.beatrice.hidekobot.util.CommandUtil; import wtf.beatrice.hidekobot.util.FormatUtil; import wtf.beatrice.hidekobot.util.RandomUtil; +import wtf.beatrice.hidekobot.util.Services; import java.io.File; import java.time.LocalDateTime; @@ -30,6 +34,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +@SpringBootApplication public class HidekoBot { private static JDA jda; @@ -63,6 +68,16 @@ public class HidekoBot return; } + ConfigurableApplicationContext context = SpringApplication.run(HidekoBot.class, args); + + CommandUtil commandUtil = context.getBean(CommandUtil.class); + DatabaseService databaseService = context.getBean(DatabaseService.class); + Services services = new wtf.beatrice.hidekobot.util.Services( + commandUtil, + databaseService + ); + Cache.setServices(services); + try { // try to create the bot object and authenticate it with discord. @@ -114,7 +129,6 @@ public class HidekoBot } - boolean enableRandomSeedUpdaterTask = false; // initialize random.org object if API key is provided { @@ -128,8 +142,11 @@ public class HidekoBot } // register slash commands and completers - SlashCommandListener slashCommandListener = new SlashCommandListener(); - SlashCommandCompletionListener slashCommandCompletionListener = new SlashCommandCompletionListener(); + SlashCommandListener slashCommandListener = context.getBean(SlashCommandListener.class); + SlashCommandCompletionListener slashCommandCompletionListener = context.getBean(SlashCommandCompletionListener.class); + MessageCommandListener messageCommandListener = context.getBean(MessageCommandListener.class); + ButtonInteractionListener buttonInteractionListener = context.getBean(ButtonInteractionListener.class); + SelectMenuInteractionListener selectMenuInteractionListener = context.getBean(SelectMenuInteractionListener.class); AvatarCommand avatarCommand = new AvatarCommand(); ProfileImageCommandCompleter avatarCommandCompleter = new ProfileImageCommandCompleter(avatarCommand); slashCommandListener.registerCommand(avatarCommand); @@ -156,7 +173,6 @@ public class HidekoBot slashCommandListener.registerCommand(new UrbanDictionaryCommand()); // register message commands - MessageCommandListener messageCommandListener = new MessageCommandListener(); messageCommandListener.registerCommand(new HelloCommand()); messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.AliasCommand()); messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.AvatarCommand()); @@ -183,43 +199,28 @@ public class HidekoBot jda.addEventListener(messageCommandListener); jda.addEventListener(slashCommandListener); jda.addEventListener(slashCommandCompletionListener); - jda.addEventListener(new ButtonInteractionListener()); - jda.addEventListener(new SelectMenuInteractionListener()); + jda.addEventListener(buttonInteractionListener); + jda.addEventListener(selectMenuInteractionListener); // update slash commands (delayed) final boolean finalForceUpdateCommands = forceUpdateCommands; try (ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor()) { - executor.schedule(() -> CommandUtil.updateSlashCommands(finalForceUpdateCommands), + executor.schedule(() -> commandUtil.updateSlashCommands(finalForceUpdateCommands), 1, TimeUnit.SECONDS); } // set the bot's status jda.getPresence().setStatus(OnlineStatus.ONLINE); - // connect to database - LOGGER.info("Connecting to database..."); - String dbFilePath = Cache.getExecPath() + File.separator + "db.sqlite"; // in current directory - DatabaseSource databaseSource = new DatabaseSource(dbFilePath); - if (databaseSource.connect() && databaseSource.initDb()) - { - LOGGER.info("Database connection initialized!"); - Cache.setDatabaseSourceInstance(databaseSource); - - // load data here... - - LOGGER.info("Database data loaded into memory!"); - } else - { - LOGGER.error("Error initializing database connection!"); - } - // start scheduled runnables ScheduledExecutorService scheduler = Cache.getTaskScheduler(); - ExpiredMessageTask expiredMessageTask = new ExpiredMessageTask(); + ExpiredMessageTask expiredMessageTask = new ExpiredMessageTask(services.databaseService(), services.commandUtil()); scheduler.scheduleAtFixedRate(expiredMessageTask, 5L, 5L, TimeUnit.SECONDS); //every 5 seconds + HeartBeatTask heartBeatTask = new HeartBeatTask(); scheduler.scheduleAtFixedRate(heartBeatTask, 10L, 30L, TimeUnit.SECONDS); //every 30 seconds + StatusUpdateTask statusUpdateTask = new StatusUpdateTask(); scheduler.scheduleAtFixedRate(statusUpdateTask, 0L, 60L * 5L, TimeUnit.SECONDS); // every 5 minutes if (enableRandomSeedUpdaterTask) diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/base/CoinFlip.java b/src/main/java/wtf/beatrice/hidekobot/commands/base/CoinFlip.java index 2ce7cbd..945c9c1 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/base/CoinFlip.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/base/CoinFlip.java @@ -44,7 +44,7 @@ public class CoinFlip public static void buttonReFlip(ButtonInteractionEvent event) { // check if the user interacting is the same one who ran the command - if (!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) + if (!(Cache.getServices().databaseService().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) { event.reply("❌ You did not run this command!").setEphemeral(true).queue(); return; @@ -68,7 +68,7 @@ public class CoinFlip public static void trackAndRestrict(Message replyMessage, User user) { - Cache.getDatabaseSource().queueDisabling(replyMessage); - Cache.getDatabaseSource().trackRanCommandReply(replyMessage, user); + Cache.getServices().databaseService().queueDisabling(replyMessage); + Cache.getServices().databaseService().trackRanCommandReply(replyMessage, user); } } diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/base/Trivia.java b/src/main/java/wtf/beatrice/hidekobot/commands/base/Trivia.java index f0b305a..bbd8fd4 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/base/Trivia.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/base/Trivia.java @@ -19,7 +19,6 @@ import wtf.beatrice.hidekobot.objects.fun.TriviaCategory; import wtf.beatrice.hidekobot.objects.fun.TriviaQuestion; import wtf.beatrice.hidekobot.objects.fun.TriviaScore; import wtf.beatrice.hidekobot.runnables.TriviaTask; -import wtf.beatrice.hidekobot.util.CommandUtil; import java.io.BufferedReader; import java.io.IOException; @@ -253,14 +252,14 @@ public class Trivia public static void handleMenuSelection(StringSelectInteractionEvent event) { // check if the user interacting is the same one who ran the command - if (!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) + if (!(Cache.getServices().databaseService().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) { event.reply("❌ You did not run this command!").setEphemeral(true).queue(); return; } // todo: we shouldn't use this method, since it messes with the database... look at coin reflip - CommandUtil.disableExpired(event.getMessageId()); + Cache.getServices().commandUtil().disableExpired(event.getMessageId()); SelectOption pickedOption = event.getInteraction().getSelectedOptions().get(0); String categoryName = pickedOption.getLabel(); @@ -293,7 +292,8 @@ public class Trivia } - TriviaTask triviaTask = new TriviaTask(author, channel, category); + TriviaTask triviaTask = new TriviaTask(author, channel, category, + Cache.getServices().databaseService(), Cache.getServices().commandUtil()); ScheduledFuture future = Cache.getTaskScheduler().scheduleAtFixedRate(triviaTask, 0, diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/base/UrbanDictionary.java b/src/main/java/wtf/beatrice/hidekobot/commands/base/UrbanDictionary.java index 0903f87..887f236 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/base/UrbanDictionary.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/base/UrbanDictionary.java @@ -14,7 +14,7 @@ import org.apache.commons.text.WordUtils; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import wtf.beatrice.hidekobot.Cache; -import wtf.beatrice.hidekobot.datasources.DatabaseSource; +import wtf.beatrice.hidekobot.services.DatabaseService; import wtf.beatrice.hidekobot.util.SerializationUtil; import java.util.ArrayList; @@ -107,9 +107,9 @@ public class UrbanDictionary public static void track(Message message, User user, UrbanSearch search, String sanitizedTerm) { - Cache.getDatabaseSource().queueDisabling(message); - Cache.getDatabaseSource().trackRanCommandReply(message, user); - Cache.getDatabaseSource().trackUrban(search.getSerializedMeanings(), + Cache.getServices().databaseService().queueDisabling(message); + Cache.getServices().databaseService().trackRanCommandReply(message, user); + Cache.getServices().databaseService().trackUrban(search.getSerializedMeanings(), search.getSerializedExamples(), search.getSerializedContributors(), search.getSerializedDates(), @@ -120,7 +120,7 @@ public class UrbanDictionary public static void changePage(ButtonInteractionEvent event, ChangeType changeType) { String messageId = event.getMessageId(); - DatabaseSource database = Cache.getDatabaseSource(); + DatabaseService database = Cache.getServices().databaseService(); // check if the user interacting is the same one who ran the command if (!(database.isUserTrackedFor(event.getUser().getId(), messageId))) @@ -130,7 +130,7 @@ public class UrbanDictionary } // get current page and calculate how many pages there are - int page = Cache.getDatabaseSource().getUrbanPage(messageId); + int page = database.getUrbanPage(messageId); String term = database.getUrbanTerm(messageId); String url = generateUrl(term); diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/message/ClearCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/message/ClearCommand.java index a5c5349..2c8318a 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/message/ClearCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/message/ClearCommand.java @@ -108,8 +108,8 @@ public class ClearCommand implements MessageCommand Message finalMessage = event.getChannel().sendMessage(content).setActionRow(dismiss).complete(); // add the message to database. - Cache.getDatabaseSource().queueDisabling(finalMessage); - Cache.getDatabaseSource().trackRanCommandReply(finalMessage, event.getAuthor()); + Cache.getServices().databaseService().queueDisabling(finalMessage); + Cache.getServices().databaseService().trackRanCommandReply(finalMessage, event.getAuthor()); // delete the sender's message. event.getMessage().delete().queue(); diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/message/TriviaCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/message/TriviaCommand.java index 50fff25..7b93a93 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/message/TriviaCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/message/TriviaCommand.java @@ -94,8 +94,8 @@ public class TriviaCommand implements MessageCommand if (response.components() != null) responseAction = responseAction.addActionRow(response.components()); responseAction.queue(message -> { - Cache.getDatabaseSource().trackRanCommandReply(message, event.getAuthor()); - Cache.getDatabaseSource().queueDisabling(message); + Cache.getServices().databaseService().trackRanCommandReply(message, event.getAuthor()); + Cache.getServices().databaseService().queueDisabling(message); }); } diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearCommand.java index 528f1ae..420db6a 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/slash/ClearCommand.java @@ -72,8 +72,8 @@ public class ClearCommand extends SlashCommandImpl botMessage = botMessage.editMessage(content).setActionRow(dismiss).complete(); // add the message to database. - Cache.getDatabaseSource().queueDisabling(botMessage); - Cache.getDatabaseSource().trackRanCommandReply(botMessage, event.getUser()); + Cache.getServices().databaseService().queueDisabling(botMessage); + Cache.getServices().databaseService().trackRanCommandReply(botMessage, event.getUser()); } } diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/slash/TriviaCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/slash/TriviaCommand.java index 8730881..0c39104 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/slash/TriviaCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/slash/TriviaCommand.java @@ -44,8 +44,8 @@ public class TriviaCommand extends SlashCommandImpl event.getHook().editOriginalEmbeds(response.embed()).setActionRow(response.components()).queue(message -> { - Cache.getDatabaseSource().trackRanCommandReply(message, event.getUser()); - Cache.getDatabaseSource().queueDisabling(message); + Cache.getServices().databaseService().trackRanCommandReply(message, event.getUser()); + Cache.getServices().databaseService().queueDisabling(message); }); } } diff --git a/src/main/java/wtf/beatrice/hidekobot/datasources/DatabaseSource.java b/src/main/java/wtf/beatrice/hidekobot/datasources/DatabaseSource.java deleted file mode 100644 index fc52343..0000000 --- a/src/main/java/wtf/beatrice/hidekobot/datasources/DatabaseSource.java +++ /dev/null @@ -1,682 +0,0 @@ -package wtf.beatrice.hidekobot.datasources; - -import net.dv8tion.jda.api.entities.Message; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.entities.channel.ChannelType; -import org.slf4j.LoggerFactory; -import wtf.beatrice.hidekobot.Cache; - -import java.sql.*; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; - -public class DatabaseSource -{ - - private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DatabaseSource.class); - private static final String JDBC_URL = "jdbc:sqlite:%path%"; - private Connection dbConnection = null; - private final String dbPath; - - public DatabaseSource(String dbPath) - { - this.dbPath = dbPath; - } - - private void logException(SQLException e) - { - LOGGER.error("Database Exception", e); - } - - public boolean connect() - { - String url = JDBC_URL.replace("%path%", dbPath); - - if (!close()) return false; - - try - { - dbConnection = DriverManager.getConnection(url); - LOGGER.info("Database connection established!"); - return true; - } catch (SQLException e) - { - logException(e); - return false; - } - - } - - - public boolean close() - { - if (dbConnection != null) - { - try - { - if (!dbConnection.isClosed()) - { - dbConnection.close(); - } - } catch (SQLException e) - { - logException(e); - return false; - } - - dbConnection = null; - } - - return true; - } - - /* - * 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 | channel_type | - * -------------------------------------------------------------------------------------------- - * | 39402849302 | 39402849302 | 39402849302 | 39402849302 | PRIVATE | - * -------------------------------------------------------------------------------------------- - * - * TABLE 3: urban_dictionary - * ----------------------------------------------------------------------------------------------------- - * | message_id | page | meanings | examples | contributors | dates | term | - * ----------------------------------------------------------------------------------------------------- - * | 39402849302 | 0 | base64 | base64 | base64 | base64 | miku | - * ----------------------------------------------------------------------------------------------------- - */ - - //todo: javadocs - 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, - message_id TEXT NOT NULL, - user_id TEXT NOT NULL, - channel_type TEXT NOT NULL); - """); - - newTables.add(""" - CREATE TABLE IF NOT EXISTS urban_dictionary ( - message_id TEXT NOT NULL, - page INTEGER NOT NULL, - meanings TEXT NOT NULL, - examples TEXT NOT NULL, - contributors TEXT NOT NULL, - dates TEXT NOT NULL, - term TEXT NOT NULL - ); - """); - - for (String sql : newTables) - { - try (Statement stmt = dbConnection.createStatement()) - { - // execute the statement - stmt.execute(sql); - } catch (SQLException e) - { - logException(e); - return false; - } - } - - return true; - } - - public boolean trackRanCommandReply(Message message, User user) - { - String userId = user.getId(); - String guildId; - - ChannelType channelType = message.getChannelType(); - if (!(channelType.isGuild())) - { - guildId = userId; - } else - { - guildId = message.getGuild().getId(); - } - - String channelId = message.getChannel().getId(); - String messageId = message.getId(); - - - String query = """ - INSERT INTO command_runners - (guild_id, channel_id, message_id, user_id, channel_type) VALUES - (?, ?, ?, ?, ?); - """; - - try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) - { - preparedStatement.setString(1, guildId); - preparedStatement.setString(2, channelId); - preparedStatement.setString(3, messageId); - preparedStatement.setString(4, userId); - preparedStatement.setString(5, channelType.name()); - - preparedStatement.executeUpdate(); - - return true; - } catch (SQLException e) - { - logException(e); - } - - return false; - } - - public boolean isUserTrackedFor(String userId, String messageId) - { - String trackedUserId = getTrackedReplyUserId(messageId); - if (trackedUserId == null) return false; - return userId.equals(trackedUserId); - } - - public ChannelType getTrackedMessageChannelType(String messageId) - { - String query = """ - SELECT channel_type - FROM command_runners - 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()) - { - String channelTypeName = resultSet.getString("channel_type"); - return ChannelType.valueOf(channelTypeName); - } - - } catch (SQLException e) - { - logException(e); - } - - return null; - - } - - public String getTrackedReplyUserId(String messageId) - { - String query = """ - SELECT user_id - FROM command_runners - 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("user_id"); - } - - } catch (SQLException e) - { - logException(e); - } - - return null; - } - - public boolean queueDisabling(Message message) - { - String messageId = message.getId(); - String channelId = message.getChannel().getId(); - String guildId; - - ChannelType channelType = message.getChannelType(); - if (!(channelType.isGuild())) - { - guildId = "PRIVATE"; - } else - { - guildId = message.getGuild().getId(); - } - - LocalDateTime expiryTime = LocalDateTime.now().plusSeconds(Cache.getExpiryTimeSeconds()); - - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Cache.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) - { - logException(e); - } - - 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) - { - logException(e); - } - - 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) - { - logException(e); - 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) - { - logException(e); - return false; - } - - query = "DELETE FROM urban_dictionary WHERE message_id = ?;"; - try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) - { - preparedStatement.setString(1, messageId); - preparedStatement.execute(); - } catch (SQLException e) - { - logException(e); - 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) - { - logException(e); - } - - 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) - { - logException(e); - } - - 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) - { - logException(e); - } - - return null; - } - - public boolean trackUrban(String meanings, String examples, - String contributors, String dates, - Message message, String term) - { - - String query = """ - INSERT INTO urban_dictionary - (message_id, page, meanings, examples, contributors, dates, term) VALUES - (?, ?, ?, ?, ?, ?, ?); - """; - - try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) - { - preparedStatement.setString(1, message.getId()); - preparedStatement.setInt(2, 0); - preparedStatement.setString(3, meanings); - preparedStatement.setString(4, examples); - preparedStatement.setString(5, contributors); - preparedStatement.setString(6, dates); - preparedStatement.setString(7, term); - - preparedStatement.executeUpdate(); - - return true; - } catch (SQLException e) - { - logException(e); - } - - return false; - } - - public int getUrbanPage(String messageId) - { - String query = """ - SELECT page - FROM urban_dictionary - WHERE message_id = ?; - """; - - try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) - { - preparedStatement.setString(1, messageId); - ResultSet resultSet = preparedStatement.executeQuery(); - if (resultSet.isClosed()) return 0; - while (resultSet.next()) - { - return resultSet.getInt("page"); - } - - } catch (SQLException e) - { - logException(e); - } - - return 0; - } - - public String getUrbanMeanings(String messageId) - { - String query = """ - SELECT meanings - FROM urban_dictionary - 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("meanings"); - } - - } catch (SQLException e) - { - logException(e); - } - - return null; - } - - public String getUrbanExamples(String messageId) - { - String query = """ - SELECT examples - FROM urban_dictionary - 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("examples"); - } - - } catch (SQLException e) - { - logException(e); - } - - return null; - } - - public String getUrbanContributors(String messageId) - { - String query = """ - SELECT contributors - FROM urban_dictionary - 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("contributors"); - } - - } catch (SQLException e) - { - logException(e); - } - - return null; - } - - public String getUrbanDates(String messageId) - { - String query = """ - SELECT dates - FROM urban_dictionary - 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("dates"); - } - - } catch (SQLException e) - { - logException(e); - } - - return null; - } - - public String getUrbanTerm(String messageId) - { - String query = """ - SELECT term - FROM urban_dictionary - 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("term"); - } - - } catch (SQLException e) - { - logException(e); - } - - return null; - } - - public boolean setUrbanPage(String messageId, int page) - { - String query = """ - UPDATE urban_dictionary - SET page = ? - WHERE message_id = ?; - """; - - try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) - { - preparedStatement.setInt(1, page); - preparedStatement.setString(2, messageId); - preparedStatement.executeUpdate(); - - return true; - - } catch (SQLException e) - { - logException(e); - } - - return false; - } - - public boolean resetExpiryTimestamp(String messageId) - { - LocalDateTime expiryTime = LocalDateTime.now().plusSeconds(Cache.getExpiryTimeSeconds()); - - DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Cache.getExpiryTimestampFormat()); - String expiryTimeFormatted = dateTimeFormatter.format(expiryTime); - - String query = """ - UPDATE pending_disabled_messages - SET expiry_timestamp = ? - WHERE message_id = ?; - """; - - try (PreparedStatement preparedStatement = dbConnection.prepareStatement(query)) - { - preparedStatement.setString(1, expiryTimeFormatted); - preparedStatement.setString(2, messageId); - preparedStatement.executeUpdate(); - - return true; - - } catch (SQLException e) - { - logException(e); - } - - return false; - } - - -} diff --git a/src/main/java/wtf/beatrice/hidekobot/entities/UrbanDictionaryEntry.java b/src/main/java/wtf/beatrice/hidekobot/entities/UrbanDictionaryEntry.java index b747053..16c2794 100644 --- a/src/main/java/wtf/beatrice/hidekobot/entities/UrbanDictionaryEntry.java +++ b/src/main/java/wtf/beatrice/hidekobot/entities/UrbanDictionaryEntry.java @@ -30,4 +30,74 @@ public class UrbanDictionaryEntry @Column(name = "term", nullable = false) private String term; + + public String getMessageId() + { + return messageId; + } + + public void setMessageId(String messageId) + { + this.messageId = messageId; + } + + public Integer getPage() + { + return page; + } + + public void setPage(Integer page) + { + this.page = page; + } + + public String getMeanings() + { + return meanings; + } + + public void setMeanings(String meanings) + { + this.meanings = meanings; + } + + public String getExamples() + { + return examples; + } + + public void setExamples(String examples) + { + this.examples = examples; + } + + public String getContributors() + { + return contributors; + } + + public void setContributors(String contributors) + { + this.contributors = contributors; + } + + public String getDates() + { + return dates; + } + + public void setDates(String dates) + { + this.dates = dates; + } + + public String getTerm() + { + return term; + } + + public void setTerm(String term) + { + this.term = term; + } } diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java index 22c057b..37343ec 100644 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java @@ -4,16 +4,24 @@ import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; import wtf.beatrice.hidekobot.commands.base.CoinFlip; import wtf.beatrice.hidekobot.commands.base.Trivia; import wtf.beatrice.hidekobot.commands.base.UrbanDictionary; import wtf.beatrice.hidekobot.util.CommandUtil; +@Component public class ButtonInteractionListener extends ListenerAdapter { - private static final Logger LOGGER = LoggerFactory.getLogger(ButtonInteractionListener.class); + private final CommandUtil commandUtil; + + public ButtonInteractionListener(CommandUtil commandUtil) + { + this.commandUtil = commandUtil; + } + @Override public void onButtonInteraction(ButtonInteractionEvent event) { @@ -25,7 +33,7 @@ public class ButtonInteractionListener extends ListenerAdapter case "coinflip_reflip" -> CoinFlip.buttonReFlip(event); // generic dismiss button - case "generic_dismiss" -> CommandUtil.delete(event); + case "generic_dismiss" -> commandUtil.delete(event); // urban dictionary navigation case "urban_nextpage" -> UrbanDictionary.changePage(event, UrbanDictionary.ChangeType.NEXT); diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/MessageCommandListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/MessageCommandListener.java index ea105c4..b7d0b58 100644 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/MessageCommandListener.java +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/MessageCommandListener.java @@ -7,6 +7,7 @@ import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.objects.commands.CommandCategory; import wtf.beatrice.hidekobot.objects.commands.MessageCommand; @@ -14,6 +15,7 @@ import wtf.beatrice.hidekobot.objects.comparators.MessageCommandAliasesComparato import java.util.*; +@Component public class MessageCommandListener extends ListenerAdapter { diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/MessageLogger.java b/src/main/java/wtf/beatrice/hidekobot/listeners/MessageLogger.java index e747150..9ccbdb7 100644 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/MessageLogger.java +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/MessageLogger.java @@ -8,7 +8,9 @@ import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +@Component public class MessageLogger extends ListenerAdapter { // this class only gets loaded as a listener if verbosity is set to true on startup. diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/SelectMenuInteractionListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/SelectMenuInteractionListener.java index 2afc5c8..bf31dca 100644 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/SelectMenuInteractionListener.java +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/SelectMenuInteractionListener.java @@ -4,8 +4,10 @@ import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionE import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; import wtf.beatrice.hidekobot.commands.base.Trivia; +@Component public class SelectMenuInteractionListener extends ListenerAdapter { diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandCompletionListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandCompletionListener.java index ee5f97e..16e0418 100644 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandCompletionListener.java +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandCompletionListener.java @@ -2,11 +2,13 @@ package wtf.beatrice.hidekobot.listeners; import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.springframework.stereotype.Component; import wtf.beatrice.hidekobot.objects.commands.SlashArgumentsCompleter; import java.util.LinkedList; import java.util.TreeMap; +@Component public class SlashCommandCompletionListener extends ListenerAdapter { diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandListener.java index bae8d78..d00bf1d 100644 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandListener.java +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/SlashCommandListener.java @@ -3,11 +3,13 @@ package wtf.beatrice.hidekobot.listeners; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; import wtf.beatrice.hidekobot.objects.commands.SlashCommand; import java.util.LinkedList; import java.util.TreeMap; +@Component public class SlashCommandListener extends ListenerAdapter { diff --git a/src/main/java/wtf/beatrice/hidekobot/repositories/CommandRunnerRepository.java b/src/main/java/wtf/beatrice/hidekobot/repositories/CommandRunnerRepository.java new file mode 100644 index 0000000..9a28fcd --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/repositories/CommandRunnerRepository.java @@ -0,0 +1,8 @@ +package wtf.beatrice.hidekobot.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import wtf.beatrice.hidekobot.entities.CommandRunner; + +public interface CommandRunnerRepository extends JpaRepository +{ +} diff --git a/src/main/java/wtf/beatrice/hidekobot/repositories/PendingDisabledMessageRepository.java b/src/main/java/wtf/beatrice/hidekobot/repositories/PendingDisabledMessageRepository.java new file mode 100644 index 0000000..c410404 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/repositories/PendingDisabledMessageRepository.java @@ -0,0 +1,8 @@ +package wtf.beatrice.hidekobot.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import wtf.beatrice.hidekobot.entities.PendingDisabledMessage; + +public interface PendingDisabledMessageRepository extends JpaRepository +{ +} diff --git a/src/main/java/wtf/beatrice/hidekobot/repositories/UrbanDictionaryRepository.java b/src/main/java/wtf/beatrice/hidekobot/repositories/UrbanDictionaryRepository.java new file mode 100644 index 0000000..5ab072c --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/repositories/UrbanDictionaryRepository.java @@ -0,0 +1,8 @@ +package wtf.beatrice.hidekobot.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import wtf.beatrice.hidekobot.entities.UrbanDictionaryEntry; + +public interface UrbanDictionaryRepository extends JpaRepository +{ +} diff --git a/src/main/java/wtf/beatrice/hidekobot/runnables/ExpiredMessageTask.java b/src/main/java/wtf/beatrice/hidekobot/runnables/ExpiredMessageTask.java index 2882332..0fd8275 100644 --- a/src/main/java/wtf/beatrice/hidekobot/runnables/ExpiredMessageTask.java +++ b/src/main/java/wtf/beatrice/hidekobot/runnables/ExpiredMessageTask.java @@ -3,7 +3,7 @@ package wtf.beatrice.hidekobot.runnables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import wtf.beatrice.hidekobot.Cache; -import wtf.beatrice.hidekobot.datasources.DatabaseSource; +import wtf.beatrice.hidekobot.services.DatabaseService; import wtf.beatrice.hidekobot.util.CommandUtil; import java.time.LocalDateTime; @@ -13,16 +13,20 @@ import java.util.List; public class ExpiredMessageTask implements Runnable { + private final DatabaseService databaseService; + private final CommandUtil commandUtil; + private final DateTimeFormatter formatter; private static final Logger LOGGER = LoggerFactory.getLogger(ExpiredMessageTask.class); - private DatabaseSource databaseSource; - public ExpiredMessageTask() + public ExpiredMessageTask(DatabaseService databaseService, + CommandUtil commandUtil) { + this.databaseService = databaseService; + this.commandUtil = commandUtil; String format = Cache.getExpiryTimestampFormat(); formatter = DateTimeFormatter.ofPattern(format); - databaseSource = Cache.getDatabaseSource(); } @@ -30,10 +34,7 @@ public class ExpiredMessageTask implements Runnable public void run() { - databaseSource = Cache.getDatabaseSource(); - if (databaseSource == null) return; - - List expiringMessages = Cache.getDatabaseSource().getQueuedExpiringMessages(); + List expiringMessages = databaseService.getQueuedExpiringMessages(); if (expiringMessages == null || expiringMessages.isEmpty()) return; LocalDateTime now = LocalDateTime.now(); @@ -43,11 +44,11 @@ public class ExpiredMessageTask implements Runnable if (Cache.isVerbose()) LOGGER.info("expired check: {}", messageId); - String expiryTimestamp = databaseSource.getQueuedExpiringMessageExpiryDate(messageId); + String expiryTimestamp = databaseService.getQueuedExpiringMessageExpiryDate(messageId); if (expiryTimestamp == null || expiryTimestamp.isEmpty()) // if missing timestamp { // count it as already expired - databaseSource.untrackExpiredMessage(messageId); + databaseService.untrackExpiredMessage(messageId); // move on to next message continue; } @@ -57,7 +58,7 @@ public class ExpiredMessageTask implements Runnable if (now.isAfter(expiryDate)) { if (Cache.isVerbose()) LOGGER.info("expired: {}", messageId); - CommandUtil.disableExpired(messageId); + commandUtil.disableExpired(messageId); } } diff --git a/src/main/java/wtf/beatrice/hidekobot/runnables/TriviaTask.java b/src/main/java/wtf/beatrice/hidekobot/runnables/TriviaTask.java index 2a0be8a..00d67f6 100644 --- a/src/main/java/wtf/beatrice/hidekobot/runnables/TriviaTask.java +++ b/src/main/java/wtf/beatrice/hidekobot/runnables/TriviaTask.java @@ -13,6 +13,7 @@ import wtf.beatrice.hidekobot.objects.comparators.TriviaScoreComparator; import wtf.beatrice.hidekobot.objects.fun.TriviaCategory; import wtf.beatrice.hidekobot.objects.fun.TriviaQuestion; import wtf.beatrice.hidekobot.objects.fun.TriviaScore; +import wtf.beatrice.hidekobot.services.DatabaseService; import wtf.beatrice.hidekobot.util.CommandUtil; import java.util.*; @@ -20,6 +21,10 @@ import java.util.concurrent.ScheduledFuture; public class TriviaTask implements Runnable { + + private final DatabaseService databaseService; + private final CommandUtil commandUtil; + private final User author; private final MessageChannel channel; @@ -33,11 +38,17 @@ public class TriviaTask implements Runnable private int iteration = 0; - public TriviaTask(User author, MessageChannel channel, TriviaCategory category) + public TriviaTask(User author, + MessageChannel channel, + TriviaCategory category, + DatabaseService databaseService, + CommandUtil commandUtil) { this.author = author; this.channel = channel; this.category = category; + this.databaseService = databaseService; + this.commandUtil = commandUtil; triviaJson = Trivia.fetchJson(Trivia.getTriviaLink(category.categoryId())); questions = Trivia.parseQuestions(triviaJson); //todo: null check, rate limiting... @@ -55,7 +66,7 @@ public class TriviaTask implements Runnable if (previousMessage != null) { // todo: we shouldn't use this method, since it messes with the database... look at coin reflip - CommandUtil.disableExpired(previousMessage.getId()); + commandUtil.disableExpired(previousMessage.getId()); String previousCorrectAnswer = questions.get(iteration - 1).correctAnswer(); @@ -193,12 +204,12 @@ public class TriviaTask implements Runnable .complete(); - Cache.getDatabaseSource().trackRanCommandReply(previousMessage, author); + databaseService.trackRanCommandReply(previousMessage, author); // todo: ^ we should get rid of this tracking, since we don't need to know who started the trivia. // todo: however, for now, that's the only way to avoid a thread-locking scenario as some data is // todo: only stored in that table. this should be solved when we merge / fix the two main tables. // todo: then, we can remove this instruction. - Cache.getDatabaseSource().queueDisabling(previousMessage); + databaseService.queueDisabling(previousMessage); iteration++; } diff --git a/src/main/java/wtf/beatrice/hidekobot/services/DatabaseService.java b/src/main/java/wtf/beatrice/hidekobot/services/DatabaseService.java new file mode 100644 index 0000000..8517ad6 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/services/DatabaseService.java @@ -0,0 +1,183 @@ +package wtf.beatrice.hidekobot.services; + +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.channel.ChannelType; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import wtf.beatrice.hidekobot.Cache; +import wtf.beatrice.hidekobot.entities.CommandRunner; +import wtf.beatrice.hidekobot.entities.PendingDisabledMessage; +import wtf.beatrice.hidekobot.entities.UrbanDictionaryEntry; +import wtf.beatrice.hidekobot.repositories.CommandRunnerRepository; +import wtf.beatrice.hidekobot.repositories.PendingDisabledMessageRepository; +import wtf.beatrice.hidekobot.repositories.UrbanDictionaryRepository; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +@Service +@Transactional +public class DatabaseService +{ + + private final PendingDisabledMessageRepository pendingRepo; + private final CommandRunnerRepository runnerRepo; + private final UrbanDictionaryRepository urbanRepo; + + public DatabaseService(PendingDisabledMessageRepository p, CommandRunnerRepository c, UrbanDictionaryRepository u) + { + this.pendingRepo = p; + this.runnerRepo = c; + this.urbanRepo = u; + } + + // trackRanCommandReply + public void trackRanCommandReply(Message message, User user) + { + String userId = user.getId(); + String guildId = message.getChannelType().isGuild() ? message.getGuild().getId() : userId; + + CommandRunner row = new CommandRunner(); + row.setMessageId(message.getId()); + row.setGuildId(guildId); + row.setChannelId(message.getChannel().getId()); + row.setUserId(userId); + row.setChannelType(message.getChannelType().name()); + + runnerRepo.save(row); + } + + public boolean isUserTrackedFor(String userId, String messageId) + { + return runnerRepo.findById(messageId) + .map(r -> userId.equals(r.getUserId())) + .orElse(false); + } + + + public ChannelType getTrackedMessageChannelType(String messageId) + { + return runnerRepo.findById(messageId) + .map(r -> ChannelType.valueOf(r.getChannelType())) + .orElse(null); + } + + public String getTrackedReplyUserId(String messageId) + { + return runnerRepo.findById(messageId) + .map(CommandRunner::getUserId) + .orElse(null); + } + + public void queueDisabling(Message message) + { + String guildId = message.getChannelType().isGuild() ? message.getGuild().getId() : "PRIVATE"; + + LocalDateTime expiry = LocalDateTime.now().plusSeconds(Cache.getExpiryTimeSeconds()); + String formatted = DateTimeFormatter.ofPattern(Cache.getExpiryTimestampFormat()).format(expiry); + + PendingDisabledMessage row = new PendingDisabledMessage(); + row.setMessageId(message.getId()); + row.setChannelId(message.getChannel().getId()); + row.setGuildId(guildId); + row.setExpiryTimestamp(formatted); + + pendingRepo.save(row); + } + + public List getQueuedExpiringMessages() + { + return pendingRepo.findAll() + .stream() + .map(PendingDisabledMessage::getMessageId) + .toList(); + } + + public void untrackExpiredMessage(String messageId) + { + pendingRepo.deleteById(messageId); + runnerRepo.deleteById(messageId); + urbanRepo.deleteById(messageId); + } + + public String getQueuedExpiringMessageExpiryDate(String messageId) + { + return pendingRepo.findById(messageId).map(PendingDisabledMessage::getExpiryTimestamp).orElse(null); + } + + public String getQueuedExpiringMessageChannel(String messageId) + { + return pendingRepo.findById(messageId).map(PendingDisabledMessage::getChannelId).orElse(null); + } + + public String getQueuedExpiringMessageGuild(String messageId) + { + return pendingRepo.findById(messageId).map(PendingDisabledMessage::getGuildId).orElse(null); + } + + public void trackUrban(String meanings, String examples, String contributors, String dates, Message message, String term) + { + UrbanDictionaryEntry e = new UrbanDictionaryEntry(); + e.setMessageId(message.getId()); + e.setPage(0); + e.setMeanings(meanings); + e.setExamples(examples); + e.setContributors(contributors); + e.setDates(dates); + e.setTerm(term); + urbanRepo.save(e); + } + + public int getUrbanPage(String messageId) + { + return urbanRepo.findById(messageId) + .map(UrbanDictionaryEntry::getPage) + .orElse(0); + } + + public String getUrbanMeanings(String messageId) + { + return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getMeanings).orElse(null); + } + + public String getUrbanExamples(String messageId) + { + return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getExamples).orElse(null); + } + + public String getUrbanContributors(String messageId) + { + return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getContributors).orElse(null); + } + + public String getUrbanDates(String messageId) + { + return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getDates).orElse(null); + } + + public String getUrbanTerm(String messageId) + { + return urbanRepo.findById(messageId).map(UrbanDictionaryEntry::getTerm).orElse(null); + } + + public void setUrbanPage(String messageId, int page) + { + urbanRepo.findById(messageId).ifPresent(e -> { + e.setPage(page); + urbanRepo.save(e); + }); + } + + public void resetExpiryTimestamp(String messageId) + { + pendingRepo.findById(messageId).ifPresent(row -> { + String formatted = DateTimeFormatter + .ofPattern(Cache.getExpiryTimestampFormat()) + .format(LocalDateTime.now().plusSeconds(Cache.getExpiryTimeSeconds())); + row.setExpiryTimestamp(formatted); + pendingRepo.save(row); + }); + } +} diff --git a/src/main/java/wtf/beatrice/hidekobot/util/CommandUtil.java b/src/main/java/wtf/beatrice/hidekobot/util/CommandUtil.java index bb321f2..9cd66f3 100644 --- a/src/main/java/wtf/beatrice/hidekobot/util/CommandUtil.java +++ b/src/main/java/wtf/beatrice/hidekobot/util/CommandUtil.java @@ -14,22 +14,27 @@ import net.dv8tion.jda.api.interactions.components.LayoutComponent; import net.dv8tion.jda.api.requests.RestAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.HidekoBot; -import wtf.beatrice.hidekobot.datasources.DatabaseSource; import wtf.beatrice.hidekobot.objects.commands.SlashCommand; +import wtf.beatrice.hidekobot.services.DatabaseService; import java.util.ArrayList; import java.util.List; +@Component public class CommandUtil { private static final Logger LOGGER = LoggerFactory.getLogger(CommandUtil.class); - private CommandUtil() + private final DatabaseService databaseService; + + public CommandUtil(@Autowired DatabaseService databaseService) { - throw new IllegalStateException("Utility class"); + this.databaseService = databaseService; } /** @@ -38,10 +43,10 @@ public class CommandUtil * * @param event the button interaction event. */ - public static void delete(ButtonInteractionEvent event) + public void delete(ButtonInteractionEvent event) { // check if the user interacting is the same one who ran the command - if (!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) + if (!databaseService.isUserTrackedFor(event.getUser().getId(), event.getMessageId())) { event.reply("❌ You did not run this command!").setEphemeral(true).queue(); return; @@ -60,7 +65,7 @@ public class CommandUtil * * @param force a boolean specifying if the update should be forced even if no differences were found. */ - public static void updateSlashCommands(boolean force) + public void updateSlashCommands(boolean force) { // populate commands list from registered commands @@ -160,48 +165,46 @@ public class CommandUtil * * @param messageId the message id to disable. */ - public static void disableExpired(String messageId) + public void disableExpired(String messageId) { - DatabaseSource databaseSource = Cache.getDatabaseSource(); - - String channelId = databaseSource.getQueuedExpiringMessageChannel(messageId); + String channelId = databaseService.getQueuedExpiringMessageChannel(messageId); // todo: warning, the following method + related if check are thread-locking. // todo: we should probably merge the two tables somehow, since they have redundant information. - ChannelType msgChannelType = databaseSource.getTrackedMessageChannelType(messageId); + ChannelType msgChannelType = databaseService.getTrackedMessageChannelType(messageId); MessageChannel textChannel = null; // this should never happen, but only message channels are supported. if (!msgChannelType.isMessage()) { - databaseSource.untrackExpiredMessage(messageId); + databaseService.untrackExpiredMessage(messageId); return; } // if this is a DM if (!(msgChannelType.isGuild())) { - String userId = databaseSource.getTrackedReplyUserId(messageId); + String userId = databaseService.getTrackedReplyUserId(messageId); User user = userId == null ? null : HidekoBot.getAPI().retrieveUserById(userId).complete(); if (user == null) { // if user is not found, consider it expired // (deleted profile, or blocked the bot) - databaseSource.untrackExpiredMessage(messageId); + databaseService.untrackExpiredMessage(messageId); return; } textChannel = user.openPrivateChannel().complete(); } else { - String guildId = databaseSource.getQueuedExpiringMessageGuild(messageId); + String guildId = databaseService.getQueuedExpiringMessageGuild(messageId); Guild guild = guildId == null ? null : HidekoBot.getAPI().getGuildById(guildId); if (guild == null) { // if guild is not found, consider it expired // (server was deleted or bot was kicked) - databaseSource.untrackExpiredMessage(messageId); + databaseService.untrackExpiredMessage(messageId); return; } textChannel = guild.getTextChannelById(channelId); @@ -211,7 +214,7 @@ public class CommandUtil { // if channel is not found, count it as expired // (channel was deleted or bot permissions restricted) - databaseSource.untrackExpiredMessage(messageId); + databaseService.untrackExpiredMessage(messageId); return; } @@ -224,7 +227,7 @@ public class CommandUtil message -> { if (message == null) { - databaseSource.untrackExpiredMessage(messageId); + databaseService.untrackExpiredMessage(messageId); return; } @@ -237,9 +240,9 @@ public class CommandUtil } message.editMessageComponents(newComponents).queue(); - databaseSource.untrackExpiredMessage(messageId); + databaseService.untrackExpiredMessage(messageId); }, - error -> databaseSource.untrackExpiredMessage(messageId)); + error -> databaseService.untrackExpiredMessage(messageId)); } } diff --git a/src/main/java/wtf/beatrice/hidekobot/util/Services.java b/src/main/java/wtf/beatrice/hidekobot/util/Services.java new file mode 100644 index 0000000..d5cd0f7 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/util/Services.java @@ -0,0 +1,7 @@ +package wtf.beatrice.hidekobot.util; + +import wtf.beatrice.hidekobot.services.DatabaseService; + +public record Services(CommandUtil commandUtil, DatabaseService databaseService) +{ +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..d8ca95b --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,8 @@ +spring.datasource.url=jdbc:sqlite:/absolute/path/to/hidekobot.db +spring.datasource.driver-class-name=org.sqlite.JDBC +# let Hibernate create/update tables for you during the migration +spring.jpa.hibernate.ddl-auto=update +spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect +# optional logging while migrating +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true