From cea9eeb6b72ba16737a96add0f6459b2dc0a2871 Mon Sep 17 00:00:00 2001 From: Louis Vallat Date: Thu, 13 May 2021 23:50:11 +0200 Subject: [PATCH] Added a configuration system, including a default language Signed-off-by: Louis Vallat --- MoviesQuoteBot.sql | 17 ++- .../java/xyz/vallat/louis/MoviesQuoteBot.java | 2 +- .../xyz/vallat/louis/commands/Command.java | 8 -- .../xyz/vallat/louis/commands/Config.java | 72 +++++++++++++ .../xyz/vallat/louis/commands/Download.java | 20 +++- .../java/xyz/vallat/louis/commands/Quote.java | 36 ++++--- .../xyz/vallat/louis/database/DBManager.java | 2 + .../louis/database/LanguageManager.java | 27 ++++- .../louis/database/UserConfigManager.java | 100 ++++++++++++++++++ .../vallat/louis/discord/DiscordManager.java | 1 + 10 files changed, 254 insertions(+), 31 deletions(-) create mode 100644 src/main/java/xyz/vallat/louis/commands/Config.java create mode 100644 src/main/java/xyz/vallat/louis/database/UserConfigManager.java diff --git a/MoviesQuoteBot.sql b/MoviesQuoteBot.sql index 2ef7056..27c2306 100644 --- a/MoviesQuoteBot.sql +++ b/MoviesQuoteBot.sql @@ -1,4 +1,4 @@ -DROP TABLE IF EXISTS properties, films, languages, subtitle_lines, subtitles; +DROP TABLE IF EXISTS properties, films, languages, subtitle_lines, subtitles, user_config; /** Store some information on the application. @@ -75,4 +75,19 @@ CREATE TABLE IF NOT EXISTS subtitle_lines PRIMARY KEY (id), FOREIGN KEY (subtitle_id) REFERENCES subtitles (id) +); + +/** + User configurations + */ +CREATE TABLE IF NOT EXISTS user_config +( + id int GENERATED ALWAYS AS IDENTITY, + user_id bigint NOT NULL, + guild_id bigint DEFAULT NULL, + default_language_id int DEFAULT NULL, + PRIMARY KEY (id), + UNIQUE (user_id, guild_id), + FOREIGN KEY (default_language_id) + REFERENCES languages (id) ); \ No newline at end of file diff --git a/src/main/java/xyz/vallat/louis/MoviesQuoteBot.java b/src/main/java/xyz/vallat/louis/MoviesQuoteBot.java index dbc66b4..cac887a 100644 --- a/src/main/java/xyz/vallat/louis/MoviesQuoteBot.java +++ b/src/main/java/xyz/vallat/louis/MoviesQuoteBot.java @@ -23,7 +23,7 @@ public class MoviesQuoteBot { public static final String PREFIX = "!"; public static final String NAME = "Movies Quote Bot"; public static final String DESCRIPTION = "I may know quotes from movies."; - public static final String VERSION = "0.5"; + public static final String VERSION = "0.6"; public static final String KILL_SWITCH_FILE = "system_locked"; public static final String MAINTENANCE_MODE_FILE = "maintenance_mode_locked"; private static final Logger logger = LoggerFactory.getLogger(MoviesQuoteBot.class.getCanonicalName()); diff --git a/src/main/java/xyz/vallat/louis/commands/Command.java b/src/main/java/xyz/vallat/louis/commands/Command.java index 5b16353..b68801e 100644 --- a/src/main/java/xyz/vallat/louis/commands/Command.java +++ b/src/main/java/xyz/vallat/louis/commands/Command.java @@ -58,14 +58,6 @@ public abstract class Command { return this.description; } - public int getMinArgs() { - return minArgs; - } - - public int getMaxArgs() { - return maxArgs; - } - public String getName() { return name; } diff --git a/src/main/java/xyz/vallat/louis/commands/Config.java b/src/main/java/xyz/vallat/louis/commands/Config.java new file mode 100644 index 0000000..0a6aa76 --- /dev/null +++ b/src/main/java/xyz/vallat/louis/commands/Config.java @@ -0,0 +1,72 @@ +package xyz.vallat.louis.commands; + +import discord4j.common.util.Snowflake; +import discord4j.core.event.domain.message.MessageCreateEvent; +import discord4j.rest.util.Color; +import org.apache.commons.cli.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.core.publisher.Mono; +import xyz.vallat.louis.database.LanguageManager; +import xyz.vallat.louis.database.UserConfigManager; +import xyz.vallat.louis.subtitles.dao.Lang; + +import java.time.Instant; + +public class Config extends Command { + + private static final Logger logger = LoggerFactory.getLogger(Config.class.getCanonicalName()); + private static final String NO_LANGUAGE = "None."; + private static final String LANG_FIELD_NAME = "Language"; + + public Config(String name) { + super(name, "Configure your own experience.", name + " [-g|--global] [-l/--lang 'default language'|-rl/--reset-lang]", 0, 0); + OptionGroup langOrResetLang = new OptionGroup() + .addOption(Option.builder("l").longOpt("lang").desc("Specify your default language").hasArg().build()) + .addOption(Option.builder("rl").longOpt("reset-lang").desc("Reset your default language").build()); + options.addOption(Option.builder("g").longOpt("global").desc("Specify that this configuration should be global.").build()); + options.addOptionGroup(langOrResetLang); + } + + @Override + public Mono execute(MessageCreateEvent event) { + try { + CommandLine cmd = new DefaultParser().parse(options, + event.getMessage().getContent().substring(name.length()).split(" ")); + Lang language = cmd.hasOption("l") ? LanguageManager.getLangFromAny(cmd.getOptionValue("l")) : null; + return event.getMessage().getChannel().flatMap(messageChannel -> messageChannel.createEmbed(embed -> { + Snowflake user = event.getMessage().getAuthor().isPresent() ? event.getMessage().getAuthor().get().getId() : null; + Snowflake guild = event.getMessage().getGuildId().isPresent() ? event.getMessage().getGuildId().get() : null; + if (cmd.hasOption("g")) guild = null; + embed.setTitle("Configuration").setColor(Color.RED); + if (user == null) embed.setDescription("What? Are you a non-existent user? A bug in the matrix?"); + else { + embed.setDescription("A configuration change has been noticed."); + if (cmd.hasOption("l")) { + if (language == null) + embed.setColor(Color.ORANGE) + .setDescription("The provided language is unknown. Try again with another one. Or don't try at all."); + else { + UserConfigManager.setDefaultLanguage(user, guild, language); + embed.addField(LANG_FIELD_NAME, language.getEnglish(), true); + } + } else if (cmd.hasOption("rl")) { + UserConfigManager.setDefaultLanguage(user, guild, null); + Lang l = UserConfigManager.getDefaultLanguage(user, guild); + embed.addField(LANG_FIELD_NAME, l == null ? NO_LANGUAGE : l.getEnglish(), false); + } else { + embed.setDescription("<@" + user.asString() + ">'s configuration."); + Lang l = UserConfigManager.getDefaultLanguage(user, guild); + embed.addField(LANG_FIELD_NAME, l == null ? NO_LANGUAGE : l.getEnglish(), false); + } + embed.setColor(Color.MEDIUM_SEA_GREEN); + } + embed.setTimestamp(Instant.now()); + })).then().onErrorResume(throwable -> fatalError(event, throwable)); + } catch ( + ParseException e) { + logger.debug("Parsing error: {}", e.getMessage()); + return parsingError(event); + } + } +} diff --git a/src/main/java/xyz/vallat/louis/commands/Download.java b/src/main/java/xyz/vallat/louis/commands/Download.java index 0d42c17..ac312fd 100644 --- a/src/main/java/xyz/vallat/louis/commands/Download.java +++ b/src/main/java/xyz/vallat/louis/commands/Download.java @@ -4,6 +4,7 @@ import com.github.wtekiela.opensub4j.response.ListResponse; import com.github.wtekiela.opensub4j.response.ResponseStatus; import com.github.wtekiela.opensub4j.response.SubtitleFile; import com.github.wtekiela.opensub4j.response.SubtitleInfo; +import discord4j.common.util.Snowflake; import discord4j.core.event.domain.message.MessageCreateEvent; import discord4j.core.spec.EmbedCreateSpec; import discord4j.rest.util.Color; @@ -15,6 +16,7 @@ import reactor.core.publisher.Mono; import xyz.vallat.louis.database.LanguageManager; import xyz.vallat.louis.database.SubtitleLineManager; import xyz.vallat.louis.database.SubtitleManager; +import xyz.vallat.louis.database.UserConfigManager; import xyz.vallat.louis.omdb.OMDBClient; import xyz.vallat.louis.omdb.objects.Movie; import xyz.vallat.louis.subtitles.OpenSubtitles; @@ -46,9 +48,16 @@ public class Download extends Command { public Mono execute(MessageCreateEvent event) { try { CommandLine cmd = new DefaultParser().parse(options, getArgArray(event)); - String langArg = cmd.hasOption("l") ? cmd.getOptionValue("l") : "english"; - Lang language = LanguageManager.getLangFromAny(langArg); - if (language == null) return unknownLanguage(event, langArg); + Snowflake user = event.getMessage().getAuthor().isPresent() ? event.getMessage().getAuthor().get().getId() : null; + Snowflake guild = event.getMessage().getGuildId().isPresent() ? event.getMessage().getGuildId().get() : null; + if (user == null) return Mono.empty(); + Lang l; + if (!cmd.hasOption("l")) { + l = UserConfigManager.getDefaultLanguage(user, guild); + if (l == null) l = LanguageManager.getLangFromAny("english"); + } else l = LanguageManager.getLangFromAny(cmd.getOptionValue("l")); + if (l == null) return unknownLanguage(event, cmd.getOptionValue("l")); + Lang language = l; return event.getMessage().getChannel().flatMap(channel -> event.getMessage().addReaction(WAITING) .then(channel.createEmbed(embed -> { embed.setTitle("Importation").setColor(Color.RED); @@ -59,7 +68,7 @@ public class Download extends Command { if (movie == null || movie.getId() < 0) embed.setDescription("We couldn't find any movie with these information. Sorry!"); else if (SubtitleManager.getSubtitlesId(movie, language.getId()) > 0) - embed.setDescription("This movie already has already this language imported.") + embed.setDescription("This movie has already this language imported.") .setColor(Color.ORANGE); else computeImportation(event, language, embed, movie); @@ -105,7 +114,7 @@ public class Download extends Command { subs.get(0).getContentAsString(subtitleInfo.getEncoding())); if (blocks.isEmpty()) embed.setDescription("The file we downloaded was empty... \uD83E\uDD28 It should be a temporary error! " + - "Try again please! And if you still have this issue, try contacting my administrator, please."); + "Try again please! And if you still have this issue, try contacting my administrator."); else { SubtitleLineManager.importSubtitleLines(blocks, language, movie, event.getMember().isPresent() ? event.getMember().get().getId() : null, @@ -114,6 +123,7 @@ public class Download extends Command { "Congratulations and thank you" + (event.getMember().isPresent() ? " <@!" + event.getMember().get().getId().asString() + "> " : " ") + "for your contribution!"); + embed.setImage(movie.getPoster()); embed.addField("You imported", blocks.size() + " lines into my database", true); embed.addField("From", movie.toString(), true); embed.addField("In", language.getEnglish(), true); diff --git a/src/main/java/xyz/vallat/louis/commands/Quote.java b/src/main/java/xyz/vallat/louis/commands/Quote.java index f0c81e2..4f79f2a 100644 --- a/src/main/java/xyz/vallat/louis/commands/Quote.java +++ b/src/main/java/xyz/vallat/louis/commands/Quote.java @@ -1,5 +1,6 @@ package xyz.vallat.louis.commands; +import discord4j.common.util.Snowflake; import discord4j.core.event.domain.message.MessageCreateEvent; import discord4j.rest.util.Color; import org.apache.commons.cli.*; @@ -10,6 +11,7 @@ import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; import xyz.vallat.louis.database.LanguageManager; import xyz.vallat.louis.database.SubtitleLineManager; +import xyz.vallat.louis.database.UserConfigManager; import xyz.vallat.louis.subtitles.dao.FilmQuote; import xyz.vallat.louis.subtitles.dao.Lang; @@ -38,27 +40,31 @@ public class Quote extends Command { try { CommandLine cmd = new DefaultParser().parse(options, event.getMessage().getContent().substring(name.length()).split(" ")); - Lang language = LanguageManager.getLangFromAny(cmd.hasOption("l") ? cmd.getOptionValue("l") : "english"); + Lang language; + Snowflake user = event.getMessage().getAuthor().isPresent() ? event.getMessage().getAuthor().get().getId() : null; + Snowflake guild = event.getMessage().getGuildId().isPresent() ? event.getMessage().getGuildId().get() : null; + if (user == null) return Mono.empty(); + if (cmd.hasOption("l")) language = LanguageManager.getLangFromAny(cmd.getOptionValue("l")); + else language = UserConfigManager.getDefaultLanguage(user, guild); + if (language == null) language = LanguageManager.getLangFromAny("english"); String imdb = cmd.hasOption("i") ? cmd.getOptionValue("i") : null; String title = cmd.hasOption("t") ? String.join(" ", cmd.getOptionValues("t")) : null; String search = cmd.hasOption("s") ? String.join(" ", cmd.getOptionValues("s")) : null; + Lang finalLanguage = language; + logger.error(finalLanguage.getFrench()); return event.getMessage().getChannel().flatMap(messageChannel -> messageChannel.createEmbed(embed -> { embed.setTitle("Quote").setColor(Color.RED); - if (language == null) - embed.setDescription("This language is unknown. Try again with another one. Or don't try at all."); + FilmQuote quote = SubtitleLineManager.getRandomLine(finalLanguage, imdb, title, search); + if (quote == null) + embed.setDescription("We don't have any quote in that language right now! Sorry!").setColor(Color.ORANGE); else { - FilmQuote quote = SubtitleLineManager.getRandomLine(language, imdb, title, search); - if (quote == null) - embed.setDescription("We don't have any quote in that language right now! Sorry!").setColor(Color.ORANGE); - else { - embed.setDescription(Jsoup.clean(quote.getSubtitleBlock().getDialogue() - .replaceAll("|", "*") - .replaceAll("|", "**") - .replaceAll("|", "__"), - Whitelist.none())); - embed.setFooter(quote.getMovie().toString(), quote.getMovie().getPoster()); - embed.setColor(Color.MEDIUM_SEA_GREEN); - } + embed.setDescription(Jsoup.clean(quote.getSubtitleBlock().getDialogue() + .replaceAll("|", "*") + .replaceAll("|", "**") + .replaceAll("|", "__"), + Whitelist.none())); + embed.setFooter(quote.getMovie().toString(), quote.getMovie().getPoster()); + embed.setColor(Color.MEDIUM_SEA_GREEN); } embed.setTimestamp(Instant.now()); })).then().onErrorResume(throwable -> fatalError(event, throwable)); diff --git a/src/main/java/xyz/vallat/louis/database/DBManager.java b/src/main/java/xyz/vallat/louis/database/DBManager.java index b969e7f..812071e 100644 --- a/src/main/java/xyz/vallat/louis/database/DBManager.java +++ b/src/main/java/xyz/vallat/louis/database/DBManager.java @@ -16,6 +16,7 @@ import static xyz.vallat.louis.database.LanguageManager.initializeLanguages; import static xyz.vallat.louis.database.PropertyManager.initializeProperties; import static xyz.vallat.louis.database.SubtitleLineManager.initializeSubtitleLine; import static xyz.vallat.louis.database.SubtitleManager.initializeSubtitle; +import static xyz.vallat.louis.database.UserConfigManager.initializeUserConfig; public final class DBManager { @@ -57,6 +58,7 @@ public final class DBManager { initializeFilm(connection); initializeSubtitle(connection); initializeSubtitleLine(connection); + initializeUserConfig(connection); importLanguageIfNeeded(connection); updateDatabaseIfNeeded(); } catch (SQLException e) { diff --git a/src/main/java/xyz/vallat/louis/database/LanguageManager.java b/src/main/java/xyz/vallat/louis/database/LanguageManager.java index cb53f6b..fd15f4b 100644 --- a/src/main/java/xyz/vallat/louis/database/LanguageManager.java +++ b/src/main/java/xyz/vallat/louis/database/LanguageManager.java @@ -104,13 +104,38 @@ public final class LanguageManager { } } + public static Lang getLangFromId(int id) { + Lang lang = null; + try (Connection connection = DBManager.getConnection()) { + String query = "SELECT * FROM languages WHERE id = ?;"; + try (PreparedStatement stmt = connection.prepareStatement(query)) { + stmt.setInt(1, id); + stmt.execute(); + if (stmt.getResultSet().next()) { + lang = new Lang( + stmt.getResultSet().getInt("id"), + stmt.getResultSet().getString("alpha3_b"), + stmt.getResultSet().getString("alpha3_t"), + stmt.getResultSet().getString("alpha2"), + stmt.getResultSet().getString("english"), + stmt.getResultSet().getString("french") + ); + } + } + } catch (SQLException e) { + logger.error("Cannot connect to database: {}", e.getMessage()); + System.exit(ExitCodes.CANNOT_CONNECT_TO_DB.getValue()); + } + return lang; + } + public static Lang getLangFromAny(@NonNull String name) { Lang lang = null; name = name.toLowerCase(); try (Connection connection = DBManager.getConnection()) { String query = "SELECT * FROM languages WHERE alpha3_b = ? " + "OR alpha3_t = ? OR alpha2 = ? OR english = ? OR french = ?;"; - try (PreparedStatement stmt = connection.prepareStatement(query)){ + try (PreparedStatement stmt = connection.prepareStatement(query)) { for (int i = 1; i < 6; i++) stmt.setString(i, name); stmt.execute(); if (stmt.getResultSet().next()) { diff --git a/src/main/java/xyz/vallat/louis/database/UserConfigManager.java b/src/main/java/xyz/vallat/louis/database/UserConfigManager.java new file mode 100644 index 0000000..307a924 --- /dev/null +++ b/src/main/java/xyz/vallat/louis/database/UserConfigManager.java @@ -0,0 +1,100 @@ +package xyz.vallat.louis.database; + +import discord4j.common.util.Snowflake; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import xyz.vallat.louis.codes.ExitCodes; +import xyz.vallat.louis.subtitles.dao.Lang; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +public final class UserConfigManager { + + private static final Logger logger = LoggerFactory.getLogger(UserConfigManager.class.getCanonicalName()); + + private UserConfigManager() { + } + + public static Lang getDefaultLanguage(Snowflake user, Snowflake guild) { + Lang lang = null; + try (Connection connection = DBManager.getConnection()) { + String query = "SELECT default_language_id FROM user_config " + + "WHERE user_id = ? AND guild_id " + (guild == null ? "IS NULL" : "= ?") + ";"; + try (PreparedStatement stmt = connection.prepareStatement(query)) { + stmt.setLong(1, user.asLong()); + if (guild != null) stmt.setLong(2, guild.asLong()); + stmt.executeQuery(); + if (!stmt.getResultSet().next()) return guild != null ? getDefaultLanguage(user, null) : null; + int id = stmt.getResultSet().getInt("default_language_id"); + if (stmt.getResultSet().wasNull()) return null; + return LanguageManager.getLangFromId(id); + } + } catch (SQLException e) { + logger.error("Could not connect to the database right now. Reason: {}", e.getMessage()); + System.exit(ExitCodes.CANNOT_CONNECT_TO_DB.getValue()); + } + return lang; + } + + public static void setDefaultLanguage(Snowflake user, Snowflake guild, Lang lang) { + try (Connection connection = DBManager.getConnection()) { + String query = "SELECT id FROM user_config WHERE user_id = ? AND guild_id " + (guild == null ? "IS NULL" : "= ?") + ";"; + try (PreparedStatement stmt = connection.prepareStatement(query)) { + stmt.setLong(1, user.asLong()); + if (guild != null) stmt.setLong(2, guild.asLong()); + stmt.execute(); + if (stmt.getResultSet().next()) updateDefaultLanguage(user, guild, lang, connection); + else insertDefaultLanguage(user, guild, lang, connection); + } + } catch (SQLException e) { + logger.error("Could not connect to the database right now. Reason: {}", e.getMessage()); + System.exit(ExitCodes.CANNOT_CONNECT_TO_DB.getValue()); + } + } + + private static void insertDefaultLanguage(Snowflake user, Snowflake guild, Lang lang, Connection connection) throws SQLException { + String insert = "INSERT INTO user_config(user_id, guild_id, default_language_id) VALUES(?, ?, ?);"; + try (PreparedStatement stmt = connection.prepareStatement(insert)) { + stmt.setLong(1, user.asLong()); + if (guild == null) stmt.setObject(2, null); + else stmt.setLong(2, guild.asLong()); + if (lang == null) stmt.setObject(3, null); + else stmt.setInt(3, lang.getId()); + stmt.executeUpdate(); + } + } + + private static void updateDefaultLanguage(Snowflake user, Snowflake guild, Lang lang, Connection connection) throws SQLException { + String update = "UPDATE user_config SET default_language_id = ? WHERE user_id = ? AND guild_id " + + (guild == null ? "IS NULL" : "= ?") + ";"; + try (PreparedStatement stmt = connection.prepareStatement(update)) { + if (lang == null) stmt.setObject(1, null); + else stmt.setLong(1, lang.getId()); + stmt.setLong(2, user.asLong()); + if (guild != null) stmt.setLong(3, guild.asLong()); + stmt.executeUpdate(); + } + } + + static void initializeUserConfig(Connection connection) throws SQLException { + logger.debug("Creating userConfigTable table."); + try (Statement stmt = connection.createStatement()) { + String query = """ + CREATE TABLE IF NOT EXISTS user_config + ( + id int GENERATED ALWAYS AS IDENTITY, + user_id bigint NOT NULL, + guild_id bigint DEFAULT NULL, + default_language_id int DEFAULT NULL, + PRIMARY KEY (id), + UNIQUE (user_id, guild_id), + FOREIGN KEY (default_language_id) + REFERENCES languages (id) + );"""; + stmt.executeUpdate(query); + } + } +} diff --git a/src/main/java/xyz/vallat/louis/discord/DiscordManager.java b/src/main/java/xyz/vallat/louis/discord/DiscordManager.java index 90aa97a..e4e49ec 100644 --- a/src/main/java/xyz/vallat/louis/discord/DiscordManager.java +++ b/src/main/java/xyz/vallat/louis/discord/DiscordManager.java @@ -28,6 +28,7 @@ public final class DiscordManager { commands.add(new ListLang(PREFIX + "listLang")); commands.add(new Download(PREFIX + "download")); commands.add(new Quote(PREFIX + "quote")); + commands.add(new Config(PREFIX + "config")); commands.add(new Ping(PREFIX + "ping")); commands.add(new Help(PREFIX + "help")); commands.add(new Version(PREFIX + "version"));