From 4ef42ffa9e890055824743284ae0e9f682f1915b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beatrice=20Dellac=C3=A0?= Date: Tue, 20 Dec 2022 00:34:49 +0100 Subject: [PATCH] Make urban command support multiple entries --- .../hidekobot/commands/base/CoinFlip.java | 2 - .../message/UrbanDictionaryCommand.java | 195 ++++++++++++-- .../hidekobot/datasources/DatabaseSource.java | 242 ++++++++++++++++++ .../listeners/ButtonInteractionListener.java | 5 + 4 files changed, 423 insertions(+), 21 deletions(-) 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 b39f1df..f0c7cdd 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/base/CoinFlip.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/base/CoinFlip.java @@ -61,8 +61,6 @@ public class CoinFlip public static void trackAndRestrict(Message replyMessage, User user) { - String replyMessageId = replyMessage.getId(); - Cache.getDatabaseSource().queueDisabling(replyMessage); Cache.getDatabaseSource().trackRanCommandReply(replyMessage, user); } diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/message/UrbanDictionaryCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/message/UrbanDictionaryCommand.java index 98ecff0..8f26c01 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/message/UrbanDictionaryCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/message/UrbanDictionaryCommand.java @@ -2,7 +2,16 @@ package wtf.beatrice.hidekobot.commands.message; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.entities.emoji.Emoji; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.ItemComponent; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import org.apache.commons.lang3.SerializationException; +import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.text.WordUtils; import org.jetbrains.annotations.Nullable; import org.jsoup.Jsoup; @@ -10,13 +19,11 @@ import org.jsoup.nodes.Document; 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.objects.commands.MessageCommand; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; +import java.io.*; +import java.util.*; public class UrbanDictionaryCommand implements MessageCommand { @@ -37,6 +44,34 @@ public class UrbanDictionaryCommand implements MessageCommand return false; } + static final String baseUrl = "https://www.urbandictionary.com/define.php?term="; + static final Button previousPageButton = Button.primary("urban_previouspage", "Back") + .withEmoji(Emoji.fromFormatted("⬅️")); + static final Button nextPageButton = Button.primary("urban_nextpage", "Next") + .withEmoji(Emoji.fromFormatted("➡️")); + + private static MessageEmbed buildEmbed(String term, + String url, + User author, + String meaning, + String example, + String contributor, + String date, + int page) + { + EmbedBuilder embedBuilder = new EmbedBuilder(); + embedBuilder.setColor(Cache.getBotColor()); + embedBuilder.setTitle(term + ", on Urban Dictionary", url); + embedBuilder.setAuthor(author.getAsTag(), null, author.getAvatarUrl()); + embedBuilder.addField("Definition", meaning, false); + embedBuilder.addField("Example", example, false); + embedBuilder.addField("Submission", + "*Entry " + (page+1) + " | Sent by " + contributor + " on " + date + "*", + false); + + return embedBuilder.build(); + } + @Override public void runCommand(MessageReceivedEvent event, String label, String[] args) { @@ -65,9 +100,9 @@ public class UrbanDictionaryCommand implements MessageCommand // cut it to length to avoid abuse if (term.length() > 64) term = term.substring(0, 64); - String url = "https://www.urbandictionary.com/define.php?term=" + term; + String url = baseUrl + term; - Document doc = null; + Document doc; try { doc = Jsoup.connect(url).get(); @@ -76,10 +111,10 @@ public class UrbanDictionaryCommand implements MessageCommand return; } - List contributorsNames = new ArrayList<>(); - List submissionDates = new ArrayList<>(); List plaintextMeanings = new ArrayList<>(); List plaintextExamples = new ArrayList<>(); + List contributorsNames = new ArrayList<>(); + List submissionDates = new ArrayList<>(); Elements definitions = doc.getElementsByClass("definition"); for(Element definition : definitions) @@ -94,6 +129,8 @@ public class UrbanDictionaryCommand implements MessageCommand String text = meaning.html() .replaceAll("", "\n") // keep newlines .replaceAll("<.*?>", ""); // remove all other html tags + // this is used to fix eg. & being shown literally instead of being parsed + text = StringEscapeUtils.unescapeHtml4(text); // discord only allows 1024 characters for embed fields if(text.length() > 1024) text = text.substring(0, 1020) + "..."; plaintextMeanings.add(text); @@ -110,6 +147,8 @@ public class UrbanDictionaryCommand implements MessageCommand String text = example.html() .replaceAll("", "\n") // keep newlines .replaceAll("<.*?>", ""); // remove all other html tags + // this is used to fix eg. & being shown literally instead of being parsed + text = StringEscapeUtils.unescapeHtml4(text); // discord only allows 1024 characters for embed fields if(text.length() > 1024) text = text.substring(0, 1020) + "..."; plaintextExamples.add(text); @@ -140,16 +179,134 @@ public class UrbanDictionaryCommand implements MessageCommand term = term.replaceAll("\\+", " "); term = WordUtils.capitalizeFully(term); - EmbedBuilder embedBuilder = new EmbedBuilder(); - embedBuilder.setColor(Cache.getBotColor()); - embedBuilder.setTitle(term + ", on Urban Dictionary", url); - embedBuilder.setAuthor(event.getAuthor().getAsTag(), null, event.getAuthor().getAvatarUrl()); - embedBuilder.addField("Definition", plaintextMeanings.get(0), false); - embedBuilder.addField("Example", plaintextExamples.get(0), false); - embedBuilder.addField("Submission", - "*sent by " + contributorsNames.get(0) + " on " + submissionDates.get(0) + "*", - false); - event.getChannel().sendMessageEmbeds(embedBuilder.build()).queue(); + + String serializedMeanings = serialize(plaintextMeanings); + String serializedExamples = serialize(plaintextExamples); + String serializedContributors = serialize(contributorsNames); + String serializedDates = serialize(submissionDates); + + // disable next page if we only have one result + Button nextPageBtnLocal = nextPageButton; + if(submissionDates.size() == 1) nextPageBtnLocal = nextPageBtnLocal.asDisabled(); + + MessageEmbed embed = buildEmbed(term, url, event.getAuthor(), plaintextMeanings.get(0), + plaintextExamples.get(0), contributorsNames.get(0), submissionDates.get(0), 0); + + // copy term for async thing + final String finalTerm = term; + event.getChannel() + .sendMessageEmbeds(embed) + .addActionRow(previousPageButton.asDisabled(), //disabled by default because we're on page 0 + nextPageBtnLocal) + .queue(message -> + { + + Cache.getDatabaseSource().queueDisabling(message); + Cache.getDatabaseSource().trackRanCommandReply(message, event.getAuthor()); + Cache.getDatabaseSource().trackUrban(serializedMeanings, + serializedExamples, + serializedContributors, + serializedDates, + message, + finalTerm); + }); + + } + + private String serialize(List dataList) { + + try (ByteArrayOutputStream bo = new ByteArrayOutputStream(); + ObjectOutputStream so = new ObjectOutputStream(bo)) { + so.writeObject(dataList); + so.flush(); + return Base64.getEncoder().encodeToString(bo.toByteArray()); + } + catch (IOException ignored) {} + return null; + } + + private static ArrayList deserialize(String dataStr) { + + byte[] b = Base64.getDecoder().decode(dataStr); + ByteArrayInputStream bi = new ByteArrayInputStream(b); + ObjectInputStream si; + try { + si = new ObjectInputStream(bi); + return ArrayList.class.cast(si.readObject()); + } + catch (IOException | ClassNotFoundException e) { + throw new SerializationException("Error during deserialization", e); + } + } + + + public static void changePage(ButtonInteractionEvent event, boolean increase) + { + + + String messageId = event.getMessageId(); + DatabaseSource database = Cache.getDatabaseSource(); + + // check if the user interacting is the same one who ran the command + if (!(database.isUserTrackedFor(event.getUser().getId(), messageId))) { + event.reply("❌ You did not run this command!").setEphemeral(true).queue(); + return; + } + + // get current page and calculate how many pages there are + int page = Cache.getDatabaseSource().getUrbanPage(messageId); + int pages; + + + String serializedMeanings = database.getUrbanMeanings(messageId); + List meanings = deserialize(serializedMeanings); + String serializedExamples = database.getUrbanExamples(messageId); + List examples = deserialize(serializedExamples); + String serializedContributors = database.getUrbanContributors(messageId); + List contributors = deserialize(serializedContributors); + String serializedDates = database.getUrbanDates(messageId); + List dates = deserialize(serializedDates); + String term = database.getUrbanTerm(messageId); + String url = baseUrl + term; + + // count how many pages there are + pages = meanings.size(); + + // move to new page + if(increase) + page++; + else page--; + + MessageEmbed updatedEmbed = buildEmbed(term, url, event.getUser(), + meanings.get(page), examples.get(page), contributors.get(page), + dates.get(page), page); + + + + List components = new ArrayList<>(); + + if(page > 0) + { + components.add(previousPageButton.asEnabled()); + } else { + components.add(previousPageButton.asDisabled()); + } + + if(page + 1 == pages) + { + components.add(nextPageButton.asDisabled()); + } else { + components.add(nextPageButton.asEnabled()); + } + + ActionRow currentRow = ActionRow.of(components); + List actionRows = new ArrayList<>(Collections.singletonList(currentRow)); + + event.getMessage().editMessageEmbeds(updatedEmbed).complete(); + event.editComponents(actionRows).complete(); + database.setUrbanPage(messageId, page); + database.resetExpiryTimestamp(messageId); + } } diff --git a/src/main/java/wtf/beatrice/hidekobot/datasources/DatabaseSource.java b/src/main/java/wtf/beatrice/hidekobot/datasources/DatabaseSource.java index c6fe2ed..912fda6 100644 --- a/src/main/java/wtf/beatrice/hidekobot/datasources/DatabaseSource.java +++ b/src/main/java/wtf/beatrice/hidekobot/datasources/DatabaseSource.java @@ -81,6 +81,12 @@ public class DatabaseSource * | 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 @@ -104,6 +110,16 @@ public class DatabaseSource "channel_type TEXT NOT NULL" + // channel type (PRIVATE, FORUM, ...) ");"); + newTables.add("CREATE TABLE IF NOT EXISTS urban_dictionary (" + + "message_id TEXT NOT NULL, " + // message id of the bot's response + "page INTEGER NOT NULL," + // page that we are currently on + "meanings TEXT NOT NULL," + // list of all meanings, serialized and encoded + "examples TEXT NOT NULL, " + // list of all examples, serialized and encoded + "contributors TEXT NOT NULL, " + // list of all contributors, serialized and encoded + "dates TEXT NOT NULL, " + // list of all submission dates, serialized and encoded + "term TEXT NOT NULL" + // the term that was searched + ");"); + for(String sql : newTables) { try (Statement stmt = dbConnection.createStatement()) { @@ -301,6 +317,17 @@ public class DatabaseSource 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) + { + e.printStackTrace(); + return false; + } + return true; } @@ -373,5 +400,220 @@ public class DatabaseSource 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) + { + e.printStackTrace(); + } + + 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) { + e.printStackTrace(); + } + + 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) { + e.printStackTrace(); + } + + 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) { + e.printStackTrace(); + } + + 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) { + e.printStackTrace(); + } + + 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) { + e.printStackTrace(); + } + + 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) { + e.printStackTrace(); + } + + 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) { + e.printStackTrace(); + } + + 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) { + e.printStackTrace(); + } + + return false; + } + } diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java index bdf5269..8fb267c 100644 --- a/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/ButtonInteractionListener.java @@ -4,6 +4,7 @@ import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import wtf.beatrice.hidekobot.commands.base.ClearChat; import wtf.beatrice.hidekobot.commands.base.CoinFlip; +import wtf.beatrice.hidekobot.commands.message.UrbanDictionaryCommand; public class ButtonInteractionListener extends ListenerAdapter { @@ -20,6 +21,10 @@ public class ButtonInteractionListener extends ListenerAdapter // clearchat command case "clear_dismiss" -> ClearChat.dismissMessage(event); + // urban dictionary navigation + case "urban_nextpage" -> UrbanDictionaryCommand.changePage(event, true); + case "urban_previouspage" -> UrbanDictionaryCommand.changePage(event, false); + } }