Compare commits

..

No commits in common. "fc9933cbb870a1387c21ff13be6474895ee1ffd0" and "3632e77b0b4d6b7bfb9c643a7ff9e37b9286d34b" have entirely different histories.

21 changed files with 220 additions and 377 deletions

View File

@ -1,22 +0,0 @@
name: "Build and test program"
on: push
jobs:
build:
name: "Build and test"
runs-on: java-bookworm
steps:
- name: Install gradle
run: |
wget https://services.gradle.org/distributions/gradle-6.6.1-bin.zip
mkdir /opt/gradle
unzip -d /opt/gradle gradle-6.6.1-bin.zip
echo PATH=$PATH:/opt/gradle/gradle-6.6.1/bin >> $GITHUB_ENV
- name: Check out repository code
uses: actions/checkout@v4
- name: Compile
run: gradle --build-cache compileJava
- name: Test
run: gradle --build-cache check
- name: Package
run: gradle --build-cache assemble

88
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,88 @@
image: docker:latest
services:
- docker:dind
stages:
- build
- test
- package
- release
- deploy
# Disable the Gradle daemon for Continuous Integration servers as correctness
# is usually a priority over speed in CI environments. Using a fresh
# runtime for each build is more reliable since the runtime is completely
# isolated from any previous builds.
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
# Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
CONTAINER_BRANCH_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- export GRADLE_USER_HOME=`pwd`/.gradle
build:
image: gradle:jdk15
stage: build
script: gradle --build-cache compileJava
cache:
key: "$CI_COMMIT_REF_NAME"
policy: push
paths:
- build
- .gradle
test:
image: gradle:jdk15
stage: test
script: gradle --build-cache check
cache:
key: "$CI_COMMIT_REF_NAME"
policy: pull
paths:
- build
- .gradle
package:
image: gradle:jdk15
stage: package
script: gradle --build-cache assemble
cache:
key: "$CI_COMMIT_REF_NAME"
policy: push
paths:
- build
- .gradle
artifacts:
paths:
- build/libs/moviesquotebot.jar
release:
stage: release
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build . -t $CONTAINER_BRANCH_IMAGE
- docker push $CONTAINER_BRANCH_IMAGE
release-master:
stage: release
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build . -t $CONTAINER_RELEASE_IMAGE
- docker push $CONTAINER_RELEASE_IMAGE
only:
- master
deploy:
stage: deploy
before_script:
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
script:
- ssh -o StrictHostKeyChecking=no -p $SSH_PORT $SSH_DESTINATION "cd $PATH_TO_APPLICATION; $UPGRADE_COMMAND;"

View File

@ -1,21 +0,0 @@
# How to contribute to MovieQuoteBot
When contributing, you have to:
- own the source code you're including in your contribution
- be polite and kind
- be patient
## Types of contribution
You can contribute to this project by:
- fixing bugs
- opening descriptive and constructive issues
- improving its documentation
- adding unit tests
## Feature requests
In order to get new features added to this project, you can open an issue describing the feature you would like to get added, so we can discuss over it, if it's a good idea to get implemented into this repo or if it justifies making a fork for it.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,4 +1,4 @@
DROP TABLE IF EXISTS properties, films, languages, subtitle_lines, subtitles, user_config; DROP TABLE IF EXISTS properties, films, languages, subtitle_lines, subtitles;
/** /**
Store some information on the application. Store some information on the application.
@ -76,18 +76,3 @@ CREATE TABLE IF NOT EXISTS subtitle_lines
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

@ -3,7 +3,6 @@
## DISCLAIMER ## DISCLAIMER
> THIS BOT IS A **PROOF OF CONCEPT** THAT NEEDED TO BE CODED WAY TOO QUICKLY TO IMPLEMENT **ANY KIND OF UNIT TESTS**. IT HASN'T BEEN TESTED CORRECTLY AND THEREFORE SHOULD ***NEVER BE USED IN PRODUCTION***. > THIS BOT IS A **PROOF OF CONCEPT** THAT NEEDED TO BE CODED WAY TOO QUICKLY TO IMPLEMENT **ANY KIND OF UNIT TESTS**. IT HASN'T BEEN TESTED CORRECTLY AND THEREFORE SHOULD ***NEVER BE USED IN PRODUCTION***.
> MOREOVER, THIS CODE IS **VERY CURSED**. ***EWW***. DON'T LOOK AT IT. JUST DON'T.
## Invite link ## Invite link

View File

@ -34,11 +34,11 @@ repositories {
dependencies { dependencies {
implementation 'com.discord4j:discord4j-core:3.1.5' implementation 'com.discord4j:discord4j-core:3.1.5'
implementation 'com.github.wtekiela:opensub4j:0.3.0' implementation 'com.github.wtekiela:opensub4j:0.3.0'
implementation 'org.apache.commons:commons-lang3:3.12.0' implementation 'org.apache.commons:commons-lang3:3.11'
implementation 'commons-cli:commons-cli:1.5.0' implementation 'commons-cli:commons-cli:1.4'
implementation 'org.jsoup:jsoup:1.14.3' implementation 'org.jsoup:jsoup:1.13.1'
implementation 'org.apache.commons:commons-csv:1.9.0' implementation 'org.apache.commons:commons-csv:1.8'
implementation 'ch.qos.logback:logback-classic:1.2.10' implementation 'ch.qos.logback:logback-classic:1.2.3'
implementation 'org.postgresql:postgresql:42.3.1' implementation 'org.postgresql:postgresql:42.2.18.jre7'
implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.google.code.gson:gson:2.8.6'
} }

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.7"; public static final String VERSION = "0.5";
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

@ -1,36 +1,45 @@
package xyz.vallat.louis.commands; package xyz.vallat.louis.commands;
import discord4j.common.util.Snowflake; import com.github.wtekiela.opensub4j.response.SubtitleInfo;
import discord4j.core.event.domain.message.MessageCreateEvent; import discord4j.core.event.domain.message.MessageCreateEvent;
import discord4j.core.object.reaction.ReactionEmoji; import discord4j.core.object.reaction.ReactionEmoji;
import discord4j.core.spec.EmbedCreateSpec;
import discord4j.rest.util.Color; import discord4j.rest.util.Color;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import xyz.vallat.louis.env.EnvironmentVariables; import xyz.vallat.louis.omdb.OMDBClient;
import xyz.vallat.louis.omdb.objects.Movie;
import xyz.vallat.louis.subtitles.OpenSubtitles;
import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class Command { public abstract class Command {
protected static final ReactionEmoji WAITING = ReactionEmoji.unicode(""); protected static final ReactionEmoji WAITING = ReactionEmoji.unicode("");
protected static final String DEFAULT_LANG = System.getenv(EnvironmentVariables.DEFAULT_LANGUAGE.getValue());
private static final Logger logger = LoggerFactory.getLogger(Command.class.getCanonicalName()); private static final Logger logger = LoggerFactory.getLogger(Command.class.getCanonicalName());
protected final String name; protected final String name;
protected final String description; protected final String description;
protected final String usage; protected final String usage;
protected final int minArgs;
protected final int maxArgs;
protected final Options options; protected final Options options;
protected Command(String name, String description, String usage) { protected Command(String name, String description, String usage, int minArgs, int maxArgs) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.usage = usage; this.usage = usage;
this.minArgs = minArgs;
this.maxArgs = maxArgs;
this.options = new Options(); this.options = new Options();
} }
@ -49,18 +58,18 @@ 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;
} }
protected Snowflake getUser(MessageCreateEvent event) {
return event.getMessage().getAuthor().isPresent() ? event.getMessage().getAuthor().get().getId() : null;
}
protected Snowflake getGuild(MessageCreateEvent event) {
return event.getMessage().getGuildId().isPresent() ? event.getMessage().getGuildId().get() : null;
}
protected Mono<Void> unknownLanguage(MessageCreateEvent event, String lang) { protected Mono<Void> unknownLanguage(MessageCreateEvent event, String lang) {
return event.getMessage().getChannel() return event.getMessage().getChannel()
.flatMap(channel -> .flatMap(channel ->
@ -88,6 +97,39 @@ public abstract class Command {
})).then(); })).then();
} }
protected void createEmbedListLang(String arg, EmbedCreateSpec embed, boolean isId) {
try {
Movie movie = OMDBClient.getMovie(arg, isId);
if (movie != null) {
Stream<SubtitleInfo> subtitles = OpenSubtitles.getSubtitleStreamFromMovie(movie);
if (subtitles != null) {
embed.setColor(Color.MEDIUM_SEA_GREEN);
String formattedSubtitles = subtitles.limit(20).map(SubtitleInfo::getLanguage)
.collect(Collectors.joining("\n"));
embed.setDescription("You requested a list of the languages available for a movie with the " +
(isId ? "IMDB Identifier" : "title") + " '" + arg + "'. ");
embed.addField("Movie", movie.toString(), true);
embed.addField("IMDB Link", "https://www.imdb.com/title/" + movie.getImdbID(), true);
if (movie.getPoster() != null) embed.setThumbnail(movie.getPoster());
embed.addField("Top 20 distinct languages (based on downloads)",
formattedSubtitles.isBlank() ? "None." : formattedSubtitles, false);
} else {
embed.setDescription("We couldn't find any subtitle right now. Sorry. Try again later or contact my" +
" administrator, please!");
}
} else {
embed.setDescription("I didn't find any correspondence, sorry! If you know this is an error, please " +
"contact my administrator!");
}
} catch (IOException e) {
logger.error("A network error happened: {}", e.getMessage());
logger.warn("It may be due to a website being down. If it's not" +
"the case, please fix this issue quickly.");
embed.setDescription("An error occurred. " +
"Try again later or contact my administrator.");
}
}
protected String[] getArgArray(MessageCreateEvent event) { protected String[] getArgArray(MessageCreateEvent event) {
return event.getMessage().getContent().substring(name.length()).split(" "); return event.getMessage().getContent().substring(name.length()).split(" ");
} }

View File

@ -1,59 +0,0 @@
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]");
options.addOption(Option.builder("g").longOpt("global").desc("Specify that this configuration should be global.").build());
options.addOptionGroup(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()));
}
@Override
public Mono<Void> execute(MessageCreateEvent event) {
try {
CommandLine cmd = new DefaultParser().parse(options,
event.getMessage().getContent().substring(name.length()).split(" "));
Snowflake user = getUser(event);
Snowflake guild = cmd.hasOption("g") ? null : getGuild(event);
Lang language = cmd.hasOption("l") ? LanguageManager.getLangFromAny(cmd.getOptionValue("l")) : null;
if (language == null && cmd.hasOption("l")) return unknownLanguage(event, cmd.getOptionValue("l"));
return event.getMessage().getChannel().flatMap(messageChannel -> messageChannel.createEmbed(embed -> {
embed.setTitle("Configuration").setColor(Color.RED).setTimestamp(Instant.now());
if (user == null)
embed.setDescription("I cannot set a null user's preference. Sorry.").setColor(Color.ORANGE);
else {
embed.setDescription("A configuration change has been noticed.");
if (cmd.hasOption("l")) UserConfigManager.setDefaultLanguage(user, guild, language);
else if (cmd.hasOption("rl")) UserConfigManager.setDefaultLanguage(user, guild, null);
else embed.setDescription("<@" + user.asString() + ">'s configuration.");
Lang l = UserConfigManager.getDefaultLanguage(user, guild);
embed.addField(LANG_FIELD_NAME, l == null ? NO_LANGUAGE : l.getEnglish(), true)
.setColor(Color.MEDIUM_SEA_GREEN);
}
})).then().onErrorResume(throwable -> fatalError(event, throwable));
} catch (
ParseException e) {
logger.debug("Parsing error: {}", e.getMessage());
return parsingError(event);
}
}
}

View File

@ -4,7 +4,6 @@ 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;
@ -16,7 +15,6 @@ 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;
@ -35,12 +33,12 @@ public class Download extends Command {
private static final Logger logger = LoggerFactory.getLogger(Download.class.getCanonicalName()); private static final Logger logger = LoggerFactory.getLogger(Download.class.getCanonicalName());
public Download(String name) { public Download(String name) {
super(name, "Download a subtitle.", name + " [-l|--lang <lang>] -i|--imdb <imdb> | -t|--title <title>"); super(name, "Download a subtitle.", name + " [-l|--lang <lang>] -i|--imdb <imdb> | -t|--title <title>", 3, 3);
OptionGroup iOrT = new OptionGroup() OptionGroup iOrT = new OptionGroup()
.addOption(Option.builder("i").longOpt("imdb").hasArg().desc("imdb identifier for the film").build()) .addOption(Option.builder("i").longOpt("imdb").hasArg().desc("imdb identifier for the film").build())
.addOption(Option.builder("t").longOpt("title").hasArgs().numberOfArgs(Option.UNLIMITED_VALUES).desc("movie title for the film").build()); .addOption(Option.builder("t").longOpt("title").hasArgs().numberOfArgs(Option.UNLIMITED_VALUES).desc("movie title for the film").build());
iOrT.setRequired(true); iOrT.setRequired(true);
options.addOption(Option.builder("l").longOpt("lang").hasArg().desc("specify a language (by default, " + DEFAULT_LANG + ")").build()); options.addOption(Option.builder("l").longOpt("lang").hasArg().desc("specify a language (by default, english)").build());
options.addOptionGroup(iOrT); options.addOptionGroup(iOrT);
} }
@ -48,16 +46,9 @@ 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));
Snowflake user = getUser(event); String langArg = cmd.hasOption("l") ? cmd.getOptionValue("l") : "english";
Snowflake guild = getGuild(event); Lang language = LanguageManager.getLangFromAny(langArg);
if (user == null) return Mono.empty(); if (language == null) return unknownLanguage(event, langArg);
Lang l;
if (!cmd.hasOption("l")) {
l = UserConfigManager.getDefaultLanguage(user, guild);
if (l == null) l = LanguageManager.getLangFromAny(DEFAULT_LANG);
} 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);
@ -68,7 +59,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 has already this language imported.") embed.setDescription("This movie already has already this language imported.")
.setColor(Color.ORANGE); .setColor(Color.ORANGE);
else else
computeImportation(event, language, embed, movie); computeImportation(event, language, embed, movie);
@ -114,7 +105,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."); "Try again please! And if you still have this issue, try contacting my administrator, please.");
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,
@ -123,7 +114,6 @@ 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

@ -10,7 +10,7 @@ import java.time.Instant;
public class Help extends Command { public class Help extends Command {
public Help(String name) { public Help(String name) {
super(name, "I need help. You need help. We all need help at some point.", name); super(name, "I need help. You need help. We all need help at some point.", name, 0, 0);
} }
@Override @Override

View File

@ -1,21 +1,15 @@
package xyz.vallat.louis.commands; package xyz.vallat.louis.commands;
import com.github.wtekiela.opensub4j.response.SubtitleInfo;
import discord4j.core.event.domain.message.MessageCreateEvent; import discord4j.core.event.domain.message.MessageCreateEvent;
import discord4j.core.spec.EmbedCreateSpec;
import discord4j.rest.util.Color; import discord4j.rest.util.Color;
import org.apache.commons.cli.*; import org.apache.commons.cli.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import xyz.vallat.louis.omdb.OMDBClient;
import xyz.vallat.louis.omdb.objects.Movie;
import xyz.vallat.louis.subtitles.OpenSubtitles; import xyz.vallat.louis.subtitles.OpenSubtitles;
import xyz.vallat.louis.subtitles.exceptions.UnauthorizedException;
import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ListLang extends Command { public class ListLang extends Command {
@ -23,7 +17,7 @@ public class ListLang extends Command {
public ListLang(String name) { public ListLang(String name) {
super(name, "List all languages attached to a movie title or an IMDB Identifier.", super(name, "List all languages attached to a movie title or an IMDB Identifier.",
name + " -i|--imdb <imdb identifier> | -t|--title <title>"); name + " -i|--imdb <imdb identifier> | -t|--title <title>", 2, 2);
OptionGroup iOrT = new OptionGroup() OptionGroup iOrT = new OptionGroup()
.addOption(Option.builder("i").longOpt("imdb").desc("specify the imdb identifier").hasArg().build()) .addOption(Option.builder("i").longOpt("imdb").desc("specify the imdb identifier").hasArg().build())
.addOption(Option.builder("t").longOpt("title").desc("specify the movie's title").hasArgs().numberOfArgs(Option.UNLIMITED_VALUES).build()); .addOption(Option.builder("t").longOpt("title").desc("specify the movie's title").hasArgs().numberOfArgs(Option.UNLIMITED_VALUES).build());
@ -41,10 +35,12 @@ public class ListLang extends Command {
.flatMap(channel -> event.getMessage().addReaction(WAITING).then(event.getMessage().getChannel() .flatMap(channel -> event.getMessage().addReaction(WAITING).then(event.getMessage().getChannel()
.flatMap(messageChannel -> messageChannel.createEmbed(embed -> { .flatMap(messageChannel -> messageChannel.createEmbed(embed -> {
embed.setTitle("Subtitle languages").setColor(Color.RED); embed.setTitle("Subtitle languages").setColor(Color.RED);
if (OpenSubtitles.isLoggedIn()) try {
createEmbedListLang(imdbOrTitle, embed, cmd.hasOption("i")); if (OpenSubtitles.isLoggedIn())
else { createEmbedListLang(imdbOrTitle, embed, cmd.hasOption("i"));
logger.error("Not logged in on OpenSubtitles! isLoggedIn returned false!"); else throw new UnauthorizedException("isLoggedIn returned false.");
} catch (UnauthorizedException e) {
logger.error("Not logged in on OpenSubtitles! {}", e.getMessage());
logger.warn("It may be due to a website being down. If it's not " + logger.warn("It may be due to a website being down. If it's not " +
"the case, please fix this issue quickly."); "the case, please fix this issue quickly.");
embed.setDescription("I cannot search for subtitle languages right now. Sorry."); embed.setDescription("I cannot search for subtitle languages right now. Sorry.");
@ -57,37 +53,4 @@ public class ListLang extends Command {
return parsingError(event); return parsingError(event);
} }
} }
private void createEmbedListLang(String arg, EmbedCreateSpec embed, boolean isId) {
try {
Movie movie = OMDBClient.getMovie(arg, isId);
if (movie != null) {
Stream<SubtitleInfo> subtitles = OpenSubtitles.getSubtitleStreamFromMovie(movie);
if (subtitles != null) {
embed.setColor(Color.MEDIUM_SEA_GREEN);
String formattedSubtitles = subtitles.limit(20).map(SubtitleInfo::getLanguage)
.collect(Collectors.joining("\n"));
embed.setDescription("You requested a list of the languages available for a movie with the " +
(isId ? "IMDB Identifier" : "title") + " '" + arg + "'. ");
embed.addField("Movie", movie.toString(), true);
embed.addField("IMDB Link", "https://www.imdb.com/title/" + movie.getImdbID(), true);
if (movie.getPoster() != null) embed.setThumbnail(movie.getPoster());
embed.addField("Top 20 distinct languages (based on downloads)",
formattedSubtitles.isBlank() ? "None." : formattedSubtitles, false);
} else {
embed.setDescription("We couldn't find any subtitle right now. Sorry. Try again later or contact my" +
" administrator, please!");
}
} else {
embed.setDescription("I didn't find any correspondence, sorry! If you know this is an error, please " +
"contact my administrator!");
}
} catch (IOException e) {
logger.error("A network error happened: {}", e.getMessage());
logger.warn("It may be due to a website being down. If it's not" +
"the case, please fix this issue quickly.");
embed.setDescription("An error occurred. " +
"Try again later or contact my administrator.");
}
}
} }

View File

@ -6,11 +6,13 @@ import reactor.core.publisher.Mono;
public class Ping extends Command { public class Ping extends Command {
public Ping(String name) { public Ping(String name) {
super(name, "Replies as soon as possible to check the bot's health.", name); super(name, "Replies as soon as possible to check the bot's health.", name, 0, 0);
} }
@Override @Override
public Mono<Void> execute(MessageCreateEvent event) { public Mono<Void> execute(MessageCreateEvent event) {
return event.getMessage().getChannel().flatMap(c -> c.createMessage("Pong!")).then().onErrorResume(t -> fatalError(event, t)); return event.getMessage().getChannel()
.flatMap(channel -> channel.createMessage("Pong!"))
.then().onErrorResume(throwable -> fatalError(event, throwable));
} }
} }

View File

@ -1,6 +1,5 @@
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.*;
@ -11,7 +10,6 @@ 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;
@ -23,7 +21,7 @@ public class Quote extends Command {
public Quote(String name) { public Quote(String name) {
super(name, "Get a random quote from any movie.", name + " [-l|--lang <lang>] " + super(name, "Get a random quote from any movie.", name + " [-l|--lang <lang>] " +
"[-s|--search <quote extract>] [-i|--imdb imdb_identifier]|[-t|--title title]"); "[-s|--search <quote extract>] [-i|--imdb imdb_identifier]|[-t|--title title]", 0, 1);
OptionGroup iOrT = new OptionGroup() OptionGroup iOrT = new OptionGroup()
.addOption(Option.builder("i").longOpt("imdb").desc("specify an IMDB identifier").hasArg().build()) .addOption(Option.builder("i").longOpt("imdb").desc("specify an IMDB identifier").hasArg().build())
.addOption(Option.builder("t").longOpt("title").desc("specify a title").hasArgs() .addOption(Option.builder("t").longOpt("title").desc("specify a title").hasArgs()
@ -40,31 +38,27 @@ 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; Lang language = LanguageManager.getLangFromAny(cmd.hasOption("l") ? cmd.getOptionValue("l") : "english");
Snowflake user = getUser(event);
Snowflake guild = getGuild(event);
if (cmd.hasOption("l")) language = LanguageManager.getLangFromAny(cmd.getOptionValue("l"));
else language = user == null ? LanguageManager.getLangFromAny(DEFAULT_LANG) :
UserConfigManager.getDefaultLanguage(user, guild);
if (language == null) language = LanguageManager.getLangFromAny(DEFAULT_LANG);
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);
FilmQuote quote = SubtitleLineManager.getRandomLine(finalLanguage, imdb, title, search); if (language == null)
if (quote == null) embed.setDescription("This language is unknown. Try again with another one. Or don't try at all.");
embed.setDescription("We don't have any quote in that language right now! Sorry!").setColor(Color.ORANGE);
else { else {
embed.setDescription(Jsoup.clean(quote.getSubtitleBlock().getDialogue() FilmQuote quote = SubtitleLineManager.getRandomLine(language, imdb, title, search);
.replaceAll("<i>|</i>", "*") if (quote == null)
.replaceAll("<b>|</b>", "**") embed.setDescription("We don't have any quote in that language right now! Sorry!").setColor(Color.ORANGE);
.replaceAll("<u>|</u>", "__"), else {
Whitelist.none())); embed.setDescription(Jsoup.clean(quote.getSubtitleBlock().getDialogue()
embed.setFooter(quote.getMovie().toString(), quote.getMovie().getPoster()); .replaceAll("<i>|</i>", "*")
embed.setColor(Color.MEDIUM_SEA_GREEN); .replaceAll("<b>|</b>", "**")
.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

@ -1,7 +1,7 @@
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.core.object.entity.Guild;
import discord4j.rest.util.Color; import discord4j.rest.util.Color;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import xyz.vallat.louis.MoviesQuoteBot; import xyz.vallat.louis.MoviesQuoteBot;
@ -14,27 +14,38 @@ import java.time.Instant;
public class Version extends Command { public class Version extends Command {
public Version(String name) { public Version(String name) {
super(name, "Give some information about the bot.", name); super(name, "Give some information about the bot.", name, 0, 0);
} }
@Override @Override
public Mono<Void> execute(MessageCreateEvent event) { public Mono<Void> execute(MessageCreateEvent event) {
return event.getMessage().getChannel().flatMap(channel -> channel.createEmbed(embed -> { return event.getMessage().getChannel()
embed .flatMap(channel -> channel
.setColor(Color.MEDIUM_SEA_GREEN) .createEmbed(embedCreateSpec -> {
.setTitle(MoviesQuoteBot.NAME) embedCreateSpec
.setDescription(MoviesQuoteBot.DESCRIPTION) .setColor(Color.MEDIUM_SEA_GREEN)
.addField("Guilds", String.valueOf(DiscordManager.getGuilds().block()), true) .setTitle(MoviesQuoteBot.NAME)
.addField("Subtitles imported", String.valueOf(SubtitleManager.getNumberOfSubtitles()), true) .setDescription(MoviesQuoteBot.DESCRIPTION)
.addField("Lines imported", String.valueOf(SubtitleLineManager.getNumberOfSubtitleLines()), true); .addField("Guilds", String.valueOf(DiscordManager.getGuilds().block()), true)
Snowflake guild = getGuild(event); .addField("Subtitles imported", String.valueOf(SubtitleManager.getNumberOfSubtitles()), true)
if (guild != null) .addField("Lines imported", String.valueOf(SubtitleLineManager.getNumberOfSubtitleLines()), true);
embed.addField("This guild imported", if (event.getGuildId().isPresent()) {
SubtitleLineManager.getNumberOfSubtitleLinesByGuild(guild.asLong()) + Guild guild = event.getGuild().block();
" subtitles lines, from " + if (guild != null)
SubtitleManager.getNumberOfSubtitlesByGuild(guild.asLong()) + " subtitles.", true); embedCreateSpec.addField("This guild imported",
embed.addField("Version", MoviesQuoteBot.VERSION, true).setTimestamp(Instant.now()); SubtitleLineManager
})).then().onErrorResume(throwable -> fatalError(event, throwable)); .getNumberOfSubtitleLinesByGuild(guild.getId().asLong()) +
" subtitles lines, from " +
SubtitleManager
.getNumberOfSubtitlesByGuild(guild.getId().asLong()) + " subtitles.",
true);
}
embedCreateSpec.addField("Version", MoviesQuoteBot.VERSION, true)
.setTimestamp(Instant.now());
}
)
)
.then().onErrorResume(throwable -> fatalError(event, throwable));
} }
} }

View File

@ -16,7 +16,6 @@ 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 {
@ -58,7 +57,6 @@ 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,38 +104,13 @@ 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

@ -1,100 +0,0 @@
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,7 +28,6 @@ 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"));

View File

@ -12,8 +12,7 @@ public enum EnvironmentVariables {
OS_USER_AGENT("OPEN_SUBTITLES_USER_AGENT"), OS_USER_AGENT("OPEN_SUBTITLES_USER_AGENT"),
SOCKET_PORT("SOCKET_PORT"), SOCKET_PORT("SOCKET_PORT"),
SOCKET_HOST("SOCKET_HOST"), SOCKET_HOST("SOCKET_HOST"),
OMDB_API_KEY("OMDB_API_KEY"), OMDB_API_KEY("OMDB_API_KEY");
DEFAULT_LANGUAGE("DEFAULT_LANG");
private final String value; private final String value;