Added a configuration system, including a default language

Signed-off-by: Louis Vallat <louis@louis-vallat.xyz>
This commit is contained in:
Louis Vallat 2021-05-13 23:50:11 +02:00
parent 3632e77b0b
commit cea9eeb6b7
10 changed files with 254 additions and 31 deletions

View File

@ -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. Store some information on the application.
@ -75,4 +75,19 @@ CREATE TABLE IF NOT EXISTS subtitle_lines
PRIMARY KEY (id), PRIMARY KEY (id),
FOREIGN KEY (subtitle_id) FOREIGN KEY (subtitle_id)
REFERENCES subtitles (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)
); );

View File

@ -23,7 +23,7 @@ public class MoviesQuoteBot {
public static final String PREFIX = "!"; public static final String PREFIX = "!";
public static final String NAME = "Movies Quote Bot"; public static final String NAME = "Movies Quote Bot";
public static final String DESCRIPTION = "I may know quotes from movies."; 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 KILL_SWITCH_FILE = "system_locked";
public static final String MAINTENANCE_MODE_FILE = "maintenance_mode_locked"; public static final String MAINTENANCE_MODE_FILE = "maintenance_mode_locked";
private static final Logger logger = LoggerFactory.getLogger(MoviesQuoteBot.class.getCanonicalName()); private static final Logger logger = LoggerFactory.getLogger(MoviesQuoteBot.class.getCanonicalName());

View File

@ -58,14 +58,6 @@ public abstract class Command {
return this.description; return this.description;
} }
public int getMinArgs() {
return minArgs;
}
public int getMaxArgs() {
return maxArgs;
}
public String getName() { public String getName() {
return name; return name;
} }

View File

@ -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<Void> 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);
}
}
}

View File

@ -4,6 +4,7 @@ import com.github.wtekiela.opensub4j.response.ListResponse;
import com.github.wtekiela.opensub4j.response.ResponseStatus; import com.github.wtekiela.opensub4j.response.ResponseStatus;
import com.github.wtekiela.opensub4j.response.SubtitleFile; import com.github.wtekiela.opensub4j.response.SubtitleFile;
import com.github.wtekiela.opensub4j.response.SubtitleInfo; import com.github.wtekiela.opensub4j.response.SubtitleInfo;
import discord4j.common.util.Snowflake;
import discord4j.core.event.domain.message.MessageCreateEvent; import discord4j.core.event.domain.message.MessageCreateEvent;
import discord4j.core.spec.EmbedCreateSpec; import discord4j.core.spec.EmbedCreateSpec;
import discord4j.rest.util.Color; 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.LanguageManager;
import xyz.vallat.louis.database.SubtitleLineManager; import xyz.vallat.louis.database.SubtitleLineManager;
import xyz.vallat.louis.database.SubtitleManager; import xyz.vallat.louis.database.SubtitleManager;
import xyz.vallat.louis.database.UserConfigManager;
import xyz.vallat.louis.omdb.OMDBClient; import xyz.vallat.louis.omdb.OMDBClient;
import xyz.vallat.louis.omdb.objects.Movie; import xyz.vallat.louis.omdb.objects.Movie;
import xyz.vallat.louis.subtitles.OpenSubtitles; import xyz.vallat.louis.subtitles.OpenSubtitles;
@ -46,9 +48,16 @@ public class Download extends Command {
public Mono<Void> execute(MessageCreateEvent event) { public Mono<Void> execute(MessageCreateEvent event) {
try { try {
CommandLine cmd = new DefaultParser().parse(options, getArgArray(event)); CommandLine cmd = new DefaultParser().parse(options, getArgArray(event));
String langArg = cmd.hasOption("l") ? cmd.getOptionValue("l") : "english"; Snowflake user = event.getMessage().getAuthor().isPresent() ? event.getMessage().getAuthor().get().getId() : null;
Lang language = LanguageManager.getLangFromAny(langArg); Snowflake guild = event.getMessage().getGuildId().isPresent() ? event.getMessage().getGuildId().get() : null;
if (language == null) return unknownLanguage(event, langArg); 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) return event.getMessage().getChannel().flatMap(channel -> event.getMessage().addReaction(WAITING)
.then(channel.createEmbed(embed -> { .then(channel.createEmbed(embed -> {
embed.setTitle("Importation").setColor(Color.RED); embed.setTitle("Importation").setColor(Color.RED);
@ -59,7 +68,7 @@ public class Download extends Command {
if (movie == null || movie.getId() < 0) if (movie == null || movie.getId() < 0)
embed.setDescription("We couldn't find any movie with these information. Sorry!"); embed.setDescription("We couldn't find any movie with these information. Sorry!");
else if (SubtitleManager.getSubtitlesId(movie, language.getId()) > 0) 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); .setColor(Color.ORANGE);
else else
computeImportation(event, language, embed, movie); computeImportation(event, language, embed, movie);
@ -105,7 +114,7 @@ public class Download extends Command {
subs.get(0).getContentAsString(subtitleInfo.getEncoding())); subs.get(0).getContentAsString(subtitleInfo.getEncoding()));
if (blocks.isEmpty()) if (blocks.isEmpty())
embed.setDescription("The file we downloaded was empty... \uD83E\uDD28 It should be a temporary error! " + 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 { else {
SubtitleLineManager.importSubtitleLines(blocks, language, movie, SubtitleLineManager.importSubtitleLines(blocks, language, movie,
event.getMember().isPresent() ? event.getMember().get().getId() : null, event.getMember().isPresent() ? event.getMember().get().getId() : null,
@ -114,6 +123,7 @@ public class Download extends Command {
"Congratulations and thank you" + "Congratulations and thank you" +
(event.getMember().isPresent() ? " <@!" + event.getMember().get().getId().asString() + "> " : " ") (event.getMember().isPresent() ? " <@!" + event.getMember().get().getId().asString() + "> " : " ")
+ "for your contribution!"); + "for your contribution!");
embed.setImage(movie.getPoster());
embed.addField("You imported", blocks.size() + " lines into my database", true); embed.addField("You imported", blocks.size() + " lines into my database", true);
embed.addField("From", movie.toString(), true); embed.addField("From", movie.toString(), true);
embed.addField("In", language.getEnglish(), true); embed.addField("In", language.getEnglish(), true);

View File

@ -1,5 +1,6 @@
package xyz.vallat.louis.commands; package xyz.vallat.louis.commands;
import discord4j.common.util.Snowflake;
import discord4j.core.event.domain.message.MessageCreateEvent; import discord4j.core.event.domain.message.MessageCreateEvent;
import discord4j.rest.util.Color; import discord4j.rest.util.Color;
import org.apache.commons.cli.*; import org.apache.commons.cli.*;
@ -10,6 +11,7 @@ import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import xyz.vallat.louis.database.LanguageManager; import xyz.vallat.louis.database.LanguageManager;
import xyz.vallat.louis.database.SubtitleLineManager; 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.FilmQuote;
import xyz.vallat.louis.subtitles.dao.Lang; import xyz.vallat.louis.subtitles.dao.Lang;
@ -38,27 +40,31 @@ public class Quote extends Command {
try { try {
CommandLine cmd = new DefaultParser().parse(options, CommandLine cmd = new DefaultParser().parse(options,
event.getMessage().getContent().substring(name.length()).split(" ")); 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 imdb = cmd.hasOption("i") ? cmd.getOptionValue("i") : null;
String title = cmd.hasOption("t") ? String.join(" ", cmd.getOptionValues("t")) : null; String title = cmd.hasOption("t") ? String.join(" ", cmd.getOptionValues("t")) : null;
String search = cmd.hasOption("s") ? String.join(" ", cmd.getOptionValues("s")) : 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 -> { return event.getMessage().getChannel().flatMap(messageChannel -> messageChannel.createEmbed(embed -> {
embed.setTitle("Quote").setColor(Color.RED); embed.setTitle("Quote").setColor(Color.RED);
if (language == null) FilmQuote quote = SubtitleLineManager.getRandomLine(finalLanguage, imdb, title, search);
embed.setDescription("This language is unknown. Try again with another one. Or don't try at all."); if (quote == null)
embed.setDescription("We don't have any quote in that language right now! Sorry!").setColor(Color.ORANGE);
else { else {
FilmQuote quote = SubtitleLineManager.getRandomLine(language, imdb, title, search); embed.setDescription(Jsoup.clean(quote.getSubtitleBlock().getDialogue()
if (quote == null) .replaceAll("<i>|</i>", "*")
embed.setDescription("We don't have any quote in that language right now! Sorry!").setColor(Color.ORANGE); .replaceAll("<b>|</b>", "**")
else { .replaceAll("<u>|</u>", "__"),
embed.setDescription(Jsoup.clean(quote.getSubtitleBlock().getDialogue() Whitelist.none()));
.replaceAll("<i>|</i>", "*") embed.setFooter(quote.getMovie().toString(), quote.getMovie().getPoster());
.replaceAll("<b>|</b>", "**") embed.setColor(Color.MEDIUM_SEA_GREEN);
.replaceAll("<u>|</u>", "__"),
Whitelist.none()));
embed.setFooter(quote.getMovie().toString(), quote.getMovie().getPoster());
embed.setColor(Color.MEDIUM_SEA_GREEN);
}
} }
embed.setTimestamp(Instant.now()); embed.setTimestamp(Instant.now());
})).then().onErrorResume(throwable -> fatalError(event, throwable)); })).then().onErrorResume(throwable -> fatalError(event, throwable));

View File

@ -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.PropertyManager.initializeProperties;
import static xyz.vallat.louis.database.SubtitleLineManager.initializeSubtitleLine; import static xyz.vallat.louis.database.SubtitleLineManager.initializeSubtitleLine;
import static xyz.vallat.louis.database.SubtitleManager.initializeSubtitle; import static xyz.vallat.louis.database.SubtitleManager.initializeSubtitle;
import static xyz.vallat.louis.database.UserConfigManager.initializeUserConfig;
public final class DBManager { public final class DBManager {
@ -57,6 +58,7 @@ public final class DBManager {
initializeFilm(connection); initializeFilm(connection);
initializeSubtitle(connection); initializeSubtitle(connection);
initializeSubtitleLine(connection); initializeSubtitleLine(connection);
initializeUserConfig(connection);
importLanguageIfNeeded(connection); importLanguageIfNeeded(connection);
updateDatabaseIfNeeded(); updateDatabaseIfNeeded();
} catch (SQLException e) { } catch (SQLException e) {

View File

@ -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) { public static Lang getLangFromAny(@NonNull String name) {
Lang lang = null; Lang lang = null;
name = name.toLowerCase(); name = name.toLowerCase();
try (Connection connection = DBManager.getConnection()) { try (Connection connection = DBManager.getConnection()) {
String query = "SELECT * FROM languages WHERE alpha3_b = ? " + String query = "SELECT * FROM languages WHERE alpha3_b = ? " +
"OR alpha3_t = ? OR alpha2 = ? OR english = ? OR french = ?;"; "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); for (int i = 1; i < 6; i++) stmt.setString(i, name);
stmt.execute(); stmt.execute();
if (stmt.getResultSet().next()) { if (stmt.getResultSet().next()) {

View File

@ -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);
}
}
}

View File

@ -28,6 +28,7 @@ public final class DiscordManager {
commands.add(new ListLang(PREFIX + "listLang")); commands.add(new ListLang(PREFIX + "listLang"));
commands.add(new Download(PREFIX + "download")); commands.add(new Download(PREFIX + "download"));
commands.add(new Quote(PREFIX + "quote")); commands.add(new Quote(PREFIX + "quote"));
commands.add(new Config(PREFIX + "config"));
commands.add(new Ping(PREFIX + "ping")); commands.add(new Ping(PREFIX + "ping"));
commands.add(new Help(PREFIX + "help")); commands.add(new Help(PREFIX + "help"));
commands.add(new Version(PREFIX + "version")); commands.add(new Version(PREFIX + "version"));