From 342040c6be3b3d019377f4dc6620f2dd58ced0c8 Mon Sep 17 00:00:00 2001 From: shayaantx <5449086+shayaantx@users.noreply.github.com> Date: Sun, 29 May 2022 22:47:25 -0400 Subject: [PATCH] Add ability for discord slash commands (#73) * Add ability for slash commands + add button to slash command results + update test cases * Fix issue where existing content doesn't show images of content Radarr/sonarr cache endpoints don't return remote poster like the search endpoints do, those were easy to fix. Lidarr doesn't seem to return a remote poster like radarr, sonarr, so I just use the last album cover if we can't find a remote image. --- README.md | 12 ++ images/oauth2-permissions.png | Bin 54005 -> 67863 bytes pom.xml | 7 +- src/main/java/com/botdarr/Config.java | 11 ++ .../com/botdarr/api/lidarr/LidarrAlbum.java | 16 ++ .../com/botdarr/api/lidarr/LidarrApi.java | 1 + .../com/botdarr/api/lidarr/LidarrArtist.java | 19 +++ .../botdarr/api/lidarr/LidarrCommands.java | 36 ++-- .../com/botdarr/api/lidarr/LidarrImage.java | 9 + .../com/botdarr/api/radarr/RadarrApi.java | 1 + .../botdarr/api/radarr/RadarrCommands.java | 32 ++-- .../com/botdarr/api/radarr/RadarrMovie.java | 14 +- .../com/botdarr/api/sonarr/SonarrApi.java | 1 + .../botdarr/api/sonarr/SonarrCommands.java | 34 ++-- .../com/botdarr/api/sonarr/SonarrImage.java | 9 + .../com/botdarr/api/sonarr/SonarrShow.java | 13 ++ .../botdarr/clients/ChatClientBootstrap.java | 42 +++-- .../clients/discord/DiscordBootstrap.java | 102 +++++++++++- .../clients/discord/DiscordResponse.java | 14 ++ .../discord/DiscordResponseBuilder.java | 73 +++++--- .../clients/matrix/MatrixBootstrap.java | 3 +- .../clients/matrix/MatrixResponseBuilder.java | 22 +-- .../botdarr/clients/slack/SlackBootstrap.java | 3 +- .../clients/slack/SlackResponseBuilder.java | 28 ++-- .../clients/telegram/TelegramBootstrap.java | 3 +- .../telegram/TelegramResponseBuilder.java | 22 +-- .../com/botdarr/commands/BaseCommand.java | 19 ++- .../java/com/botdarr/commands/Command.java | 1 + .../com/botdarr/commands/CommandContext.java | 24 +++ .../botdarr/commands/CommandProcessor.java | 48 ++---- .../com/botdarr/commands/HelpCommands.java | 8 +- src/main/resources/version.txt | 2 +- src/test/java/com/botdarr/ConfigTests.java | 19 +++ .../discord/DiscordBootstrapTests.java | 156 ++++++++++++++++++ .../commands/CommandProcessorTests.java | 42 ++++- 35 files changed, 678 insertions(+), 168 deletions(-) create mode 100644 src/main/java/com/botdarr/api/lidarr/LidarrAlbum.java create mode 100644 src/test/java/com/botdarr/clients/discord/DiscordBootstrapTests.java diff --git a/README.md b/README.md index 452ef87..ea45eae 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Made this simple multi chat-client bot to access radarr, sonarr, and lidarr with - [x] User requests audited to local database\ - [x] Blacklist content by paths from showing up in searches - [x] Get status of radarr, lidarr, sonarr, and any additional configured endpoints +- [x] Discord slash commands - [ ] Lookup torrents for movies and force download - [ ] Cancel/blacklist existing downloads - [ ] Episode/season search @@ -51,6 +52,17 @@ Made this simple multi chat-client bot to access radarr, sonarr, and lidarr with See https://github.com/shayaantx/botdarr/wiki/Install-Discord-Bot +Slash commands installation/updates: +- By default, slash commands are automatically available now +- You can no longer use / with discord as that is reserved for slash commands +- If you are trying to use slash commands, you will need to update your bots permission to include slash commands, then re-invite your bot your discord server +- It can take up to 1 hour for discord slash commands to appear +- If you don't see them after an hour, try disabling one of the commands, and re-enabling it. This workarounds the following bug I've noticed in desktop discord client + https://stackoverflow.com/questions/72111433/discord-bot-slash-command-not-appear-on-win10 + https://github.com/discord/discord-api-docs/issues/4859 + https://github.com/discord/discord-api-docs/issues/4856 +- Slash commands from botdarr operate the same except they are not the same case (i.e., all the commands have dashes in them) + ## Slack Bot Installation See https://github.com/shayaantx/botdarr/wiki/Install-Slack-Bot diff --git a/images/oauth2-permissions.png b/images/oauth2-permissions.png index 334722c57218cc4819ac5639d6c6adeee27562e0..f934be5ed6e020fea47bc9a95c9e2f851933dabb 100644 GIT binary patch literal 67863 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 diff --git a/pom.xml b/pom.xml index e82ae19..2656dde 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,11 @@ jcenter-bintray https://jcenter.bintray.com + + dv8tion + m2-dv8tion + https://m2.dv8tion.net/releases + @@ -29,7 +34,7 @@ net.dv8tion JDA - 4.2.0_168 + 4.4.0_350 jar compile diff --git a/src/main/java/com/botdarr/Config.java b/src/main/java/com/botdarr/Config.java index 3f900e7..a62e778 100644 --- a/src/main/java/com/botdarr/Config.java +++ b/src/main/java/com/botdarr/Config.java @@ -178,6 +178,17 @@ public static int getTimeout() { return 5000; } + /** + * @return The command prefix + */ + public static String getPrefix() { + String configuredPrefix = Config.getProperty(Config.Constants.COMMAND_PREFIX); + if (!Strings.isEmpty(configuredPrefix)) { + return configuredPrefix; + } + return "!"; + } + private static StatusEndPoint getDomain(String constant, String name) { try { URI uri = new URI(getProperty(constant)); diff --git a/src/main/java/com/botdarr/api/lidarr/LidarrAlbum.java b/src/main/java/com/botdarr/api/lidarr/LidarrAlbum.java new file mode 100644 index 0000000..4d7aabc --- /dev/null +++ b/src/main/java/com/botdarr/api/lidarr/LidarrAlbum.java @@ -0,0 +1,16 @@ +package com.botdarr.api.lidarr; + +import java.util.ArrayList; +import java.util.List; + +public class LidarrAlbum { + public List getImages() { + return images; + } + + public void setImages(List images) { + this.images = images; + } + + private List images = new ArrayList<>(); +} diff --git a/src/main/java/com/botdarr/api/lidarr/LidarrApi.java b/src/main/java/com/botdarr/api/lidarr/LidarrApi.java index 6ac2008..3dcb4ba 100644 --- a/src/main/java/com/botdarr/api/lidarr/LidarrApi.java +++ b/src/main/java/com/botdarr/api/lidarr/LidarrApi.java @@ -305,4 +305,5 @@ private CommandResponse addArtist(LidarrArtist lidarrArtist) { private static final LidarrCache LIDARR_CACHE = new LidarrCache(); public static final String ADD_ARTIST_COMMAND_FIELD_PREFIX = "Add artist command"; + public static final String ARTIST_LOOKUP_KEY_FIELD = "ForeignArtistId"; } diff --git a/src/main/java/com/botdarr/api/lidarr/LidarrArtist.java b/src/main/java/com/botdarr/api/lidarr/LidarrArtist.java index c87a004..9a017de 100644 --- a/src/main/java/com/botdarr/api/lidarr/LidarrArtist.java +++ b/src/main/java/com/botdarr/api/lidarr/LidarrArtist.java @@ -1,6 +1,8 @@ package com.botdarr.api.lidarr; import com.botdarr.api.KeyBased; +import com.botdarr.api.sonarr.SonarrImage; +import org.apache.logging.log4j.util.Strings; import java.util.List; @@ -102,6 +104,22 @@ public String getRemotePoster() { return remotePoster; } + public String getRemoteImage() { + if (Strings.isEmpty(remotePoster)) { + for (LidarrImage lidarrImage : images) { + if (lidarrImage.getCoverType().equals("poster") && !Strings.isEmpty(lidarrImage.getRemoteUrl())) { + return lidarrImage.getRemoteUrl(); + } + } + for (LidarrImage lidarrImage : this.lastAlbum.getImages()) { + if (lidarrImage.getCoverType().equals("cover") && !Strings.isEmpty(lidarrImage.getUrl())) { + return lidarrImage.getUrl(); + } + } + } + return remotePoster; + } + public void setRemotePoster(String remotePoster) { this.remotePoster = remotePoster; } @@ -227,4 +245,5 @@ public void setPath(String path) { private LidarrAddOptions addOptions = new LidarrAddOptions(); private String rootFolderPath; private String path; + private LidarrAlbum lastAlbum = new LidarrAlbum(); } diff --git a/src/main/java/com/botdarr/api/lidarr/LidarrCommands.java b/src/main/java/com/botdarr/api/lidarr/LidarrCommands.java index 2183dff..39e0b7f 100644 --- a/src/main/java/com/botdarr/api/lidarr/LidarrCommands.java +++ b/src/main/java/com/botdarr/api/lidarr/LidarrCommands.java @@ -1,31 +1,33 @@ package com.botdarr.api.lidarr; +import com.botdarr.Config; import com.botdarr.api.ContentType; import com.botdarr.api.lidarr.LidarrApi; -import com.botdarr.commands.BaseCommand; -import com.botdarr.commands.Command; -import com.botdarr.commands.CommandProcessor; -import com.botdarr.commands.CommandResponseUtil; +import com.botdarr.commands.*; import com.botdarr.commands.responses.CommandResponse; import com.botdarr.commands.responses.InfoResponse; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; public class LidarrCommands { public static List getCommands(LidarrApi lidarrApi) { return new ArrayList() {{ - add(new BaseCommand("music artist add", "music artist add ", - "Adds an artist using search text (i.e., music add Dre Fudgington)") { + add(new BaseCommand( + "music artist add", + "Adds an artist using search text (i.e., music add Dre Fudgington)", + Collections.singletonList("artist-name")) { @Override public List execute(String artistToSearch) { return lidarrApi.addArtist(artistToSearch); } }); - add(new BaseCommand("music artist id add", "music artist id add ", - "Adds an artist using lidarr artist id and artist name (i.e., music artist id add F894MN4-F84J4 Beastie Girls). The easiest" + - " way to use this command is using the find commands to find new artists, which have the add commands or you can use thumbs reaction (in slack/discord)") { + add(new BaseCommand( + "music artist id add", + "Adds an artist using lidarr artist id and artist name (i.e., music artist id add F894MN4-F84J4 Beastie Girls).", + Arrays.asList("lidar-artist-id", "artist-name")) { @Override public List execute(String command) { int lastSpace = command.lastIndexOf(" "); @@ -37,15 +39,19 @@ public List execute(String command) { return Collections.singletonList(lidarrApi.addArtistWithId(id, searchText)); } }); - add(new BaseCommand("music artist find existing", "music artist find existing ", - "Finds an existing artist using lidarr (i.e., music artist find existing ArtistA)") { + add(new BaseCommand( + "music artist find existing", + "Finds an existing artist using lidarr (i.e., music artist find existing ArtistA)", + Collections.singletonList("artist-name")) { @Override public List execute(String command) { return lidarrApi.lookupArtists(command, false); } }); - add(new BaseCommand("music artist find new", "music artist find new ", - "Finds a new artist using lidarr (i.e., music find new artist ArtistB)") { + add(new BaseCommand( + "music artist find new", + "Finds a new artist using lidarr (i.e., music find new artist ArtistB)", + Collections.singletonList("artist-name")) { @Override public List execute(String command) { return lidarrApi.lookupArtists(command, true); @@ -66,10 +72,10 @@ public List execute(String command) { } public static String getAddArtistCommandStr(String artistName, String foreignArtistId) { - return new CommandProcessor().getPrefix() + "music artist id add " + artistName + " " + foreignArtistId; + return CommandContext.getConfig().getPrefix() + "music artist id add " + artistName + " " + foreignArtistId; } public static String getHelpCommandStr() { - return new CommandProcessor().getPrefix() + "music help"; + return CommandContext.getConfig().getPrefix() + "music help"; } } diff --git a/src/main/java/com/botdarr/api/lidarr/LidarrImage.java b/src/main/java/com/botdarr/api/lidarr/LidarrImage.java index c9b1a0d..e56634c 100644 --- a/src/main/java/com/botdarr/api/lidarr/LidarrImage.java +++ b/src/main/java/com/botdarr/api/lidarr/LidarrImage.java @@ -17,6 +17,15 @@ public void setUrl(String url) { this.url = url; } + public String getRemoteUrl() { + return remoteUrl; + } + + public void setRemoteUrl(String remoteUrl) { + this.remoteUrl = remoteUrl; + } + private String coverType; private String url; + private String remoteUrl; } diff --git a/src/main/java/com/botdarr/api/radarr/RadarrApi.java b/src/main/java/com/botdarr/api/radarr/RadarrApi.java index 7d42529..ea56ef8 100644 --- a/src/main/java/com/botdarr/api/radarr/RadarrApi.java +++ b/src/main/java/com/botdarr/api/radarr/RadarrApi.java @@ -332,4 +332,5 @@ private boolean isPathBlacklisted(RadarrMovie item) { private static RadarrCache RADARR_CACHE = new RadarrCache(); public static final String ADD_MOVIE_COMMAND_FIELD_PREFIX = "Add movie command"; + public static final String MOVIE_LOOKUP_FIELD = "TmdbId"; } diff --git a/src/main/java/com/botdarr/api/radarr/RadarrCommands.java b/src/main/java/com/botdarr/api/radarr/RadarrCommands.java index cac927f..191f30d 100644 --- a/src/main/java/com/botdarr/api/radarr/RadarrCommands.java +++ b/src/main/java/com/botdarr/api/radarr/RadarrCommands.java @@ -1,14 +1,12 @@ package com.botdarr.api.radarr; import com.botdarr.api.ContentType; -import com.botdarr.commands.BaseCommand; -import com.botdarr.commands.Command; -import com.botdarr.commands.CommandProcessor; -import com.botdarr.commands.CommandResponseUtil; +import com.botdarr.commands.*; import com.botdarr.commands.responses.CommandResponse; import org.apache.logging.log4j.util.Strings; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -26,8 +24,10 @@ public List execute(String command) { return radarrApi.discover(); } }); - add(new BaseCommand("movie id add", "movie id add ", "Adds a movie using search text and tmdb id (i.e., movie id add John Wick 484737). The easiest" + - " way to use this command is to use \"movie find new TITLE\", then the results will contain the movie add command for you") { + add(new BaseCommand( + "movie id add", + "Adds a movie using search text and tmdb id (i.e., movie id add John Wick 484737).", + Arrays.asList("movie-title", "movie-tmdbid")) { @Override public List execute(String command) { int lastSpace = command.lastIndexOf(" "); @@ -41,8 +41,10 @@ public List execute(String command) { return Collections.singletonList(radarrApi.addWithId(searchText, id)); } }); - add(new BaseCommand("movie title add", "movie title add ", "Adds a movie with just a title. Since many movies can have same title or very similar titles, the trakt" + - " search can return multiple movies, if we detect multiple new films, we will return those films, otherwise we will add the single film.") { + add(new BaseCommand( + "movie title add", + "Adds a movie with just a title. Since many movies can have same title or very similar titles", + Collections.singletonList("movie-title")) { @Override public List execute(String searchText) { validateMovieTitle(searchText); @@ -60,14 +62,20 @@ public List execute(String command) { return radarrApi.getProfiles(); } }); - add(new BaseCommand("movie find new", "movie find new ", "Finds a new movie using radarr (i.e., movie find new John Wick)") { + add(new BaseCommand( + "movie find new", + "Finds a new movie using radarr (i.e., movie find new John Wick)", + Collections.singletonList("movie-title")) { @Override public List execute(String searchText) { validateMovieTitle(searchText); return radarrApi.lookup(searchText, true); } }); - add(new BaseCommand("movie find existing", "movie find existing ", "Finds an existing movie using radarr (i.e., movie find existing Princess Fudgecake)") { + add(new BaseCommand( + "movie find existing", + "Finds an existing movie using radarr (i.e., movie find existing Princess Fudgecake)", + Collections.singletonList("movie-title")) { @Override public List execute(String searchText) { validateMovieTitle(searchText); @@ -89,11 +97,11 @@ public List execute(String command) { } public static String getAddMovieCommandStr(String title, long tmdbId) { - return new CommandProcessor().getPrefix() + "movie id add " + title + " " + tmdbId; + return CommandContext.getConfig().getPrefix() + "movie id add " + title + " " + tmdbId; } public static String getHelpMovieCommandStr() { - return new CommandProcessor().getPrefix() + "movies help"; + return CommandContext.getConfig().getPrefix() + "movies help"; } private static void validateMovieTitle(String movieTitle) { diff --git a/src/main/java/com/botdarr/api/radarr/RadarrMovie.java b/src/main/java/com/botdarr/api/radarr/RadarrMovie.java index 4dbd1a2..009d95f 100644 --- a/src/main/java/com/botdarr/api/radarr/RadarrMovie.java +++ b/src/main/java/com/botdarr/api/radarr/RadarrMovie.java @@ -1,6 +1,7 @@ package com.botdarr.api.radarr; import com.botdarr.api.KeyBased; +import org.apache.logging.log4j.util.Strings; import java.util.Date; import java.util.List; @@ -90,7 +91,18 @@ public void setWebsite(String website) { this.website = website; } - public String getRemotePoster() { + public String getRemoteImage() { + if (Strings.isEmpty(remotePoster)) { + for(RadarrImage radarrImage : images) { + if (radarrImage.getCoverType().equals("poster") && !Strings.isEmpty(radarrImage.getRemoteUrl())) { + return radarrImage.getRemoteUrl(); + } + } + } + return remotePoster; + } + + public String getRemotePost() { return remotePoster; } diff --git a/src/main/java/com/botdarr/api/sonarr/SonarrApi.java b/src/main/java/com/botdarr/api/sonarr/SonarrApi.java index be2823b..6405db7 100644 --- a/src/main/java/com/botdarr/api/sonarr/SonarrApi.java +++ b/src/main/java/com/botdarr/api/sonarr/SonarrApi.java @@ -282,4 +282,5 @@ private boolean isPathBlacklisted(SonarrShow item) { private static final SonarrCache SONARR_CACHE = new SonarrCache(); public static final String ADD_SHOW_COMMAND_FIELD_PREFIX = "Add show command"; + public static final String SHOW_LOOKUP_FIELD = "TvdbId"; } diff --git a/src/main/java/com/botdarr/api/sonarr/SonarrCommands.java b/src/main/java/com/botdarr/api/sonarr/SonarrCommands.java index e589e0e..9701aac 100644 --- a/src/main/java/com/botdarr/api/sonarr/SonarrCommands.java +++ b/src/main/java/com/botdarr/api/sonarr/SonarrCommands.java @@ -1,23 +1,22 @@ package com.botdarr.api.sonarr; import com.botdarr.api.ContentType; -import com.botdarr.commands.BaseCommand; -import com.botdarr.commands.Command; -import com.botdarr.commands.CommandProcessor; -import com.botdarr.commands.CommandResponseUtil; +import com.botdarr.commands.*; import com.botdarr.commands.responses.CommandResponse; import org.apache.logging.log4j.util.Strings; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; public class SonarrCommands { public static List getCommands(SonarrApi sonarrApi) { return new ArrayList() {{ - add(new BaseCommand("show id add", "show id add ", - "Adds a show using search text and tmdb id (i.e., show id add 30 rock 484737). The easiest" + - " way to use this command is to use \"show find new \", then the results will contain the show add command for you") { + add(new BaseCommand( + "show id add", + "Adds a show using search text and tmdb id (i.e., show id add 30 clock 484767)", + Arrays.asList("show-title", "show-tvdbid")) { @Override public List execute(String command) { int lastSpace = command.lastIndexOf(" "); @@ -31,9 +30,10 @@ public List execute(String command) { return Collections.singletonList(sonarrApi.addWithId(searchText, id)); } }); - add(new BaseCommand("show title add", "show title add ", - "Adds a show with just a title. Since there can be multiple shows that match search criteria" + - " we will either add the show or return all the shows that match your search.") { + add(new BaseCommand( + "show title add", + "Adds a show with just a title.", + Collections.singletonList("show-title")) { @Override public List execute(String command) { validateShowTitle(command); @@ -62,14 +62,20 @@ public List execute(String command) { return sonarrApi.getProfiles(); } }); - add(new BaseCommand("show find existing", "show find existing ", "Finds a existing show using sonarr (i.e., show find existing Ahh! Real fudgecakes)") { + add(new BaseCommand( + "show find existing", + "Finds a existing show using sonarr (i.e., show find existing Ahh! Real fudgecakes)", + Collections.singletonList("show-title")) { @Override public List execute(String command) { validateShowTitle(command); return sonarrApi.lookup(command, false); } }); - add(new BaseCommand("show find new", "show find new ", "Finds a new show using sonarr (i.e., show find new Fresh Prince of Fresh air)") { + add(new BaseCommand( + "show find new", + "Finds a new show using sonarr (i.e., show find new Fresh Prince of Fresh air)", + Collections.singletonList("show-title")) { @Override public List execute(String command) { validateShowTitle(command); @@ -80,11 +86,11 @@ public List execute(String command) { } public static String getAddShowCommandStr(String title, long tvdbId) { - return new CommandProcessor().getPrefix() + "show id add " + title + " " + tvdbId; + return CommandContext.getConfig().getPrefix() + "show id add " + title + " " + tvdbId; } public static String getHelpShowCommandStr() { - return new CommandProcessor().getPrefix() + "shows help"; + return CommandContext.getConfig().getPrefix() + "shows help"; } private static void validateShowTitle(String movieTitle) { diff --git a/src/main/java/com/botdarr/api/sonarr/SonarrImage.java b/src/main/java/com/botdarr/api/sonarr/SonarrImage.java index c54bae7..4391354 100644 --- a/src/main/java/com/botdarr/api/sonarr/SonarrImage.java +++ b/src/main/java/com/botdarr/api/sonarr/SonarrImage.java @@ -17,6 +17,15 @@ public void setUrl(String url) { this.url = url; } + public String getRemoteUrl() { + return remoteUrl; + } + + public void setRemoteUrl(String remoteUrl) { + this.remoteUrl = remoteUrl; + } + private String coverType; private String url; + private String remoteUrl; } diff --git a/src/main/java/com/botdarr/api/sonarr/SonarrShow.java b/src/main/java/com/botdarr/api/sonarr/SonarrShow.java index dfe85cd..1402f9c 100644 --- a/src/main/java/com/botdarr/api/sonarr/SonarrShow.java +++ b/src/main/java/com/botdarr/api/sonarr/SonarrShow.java @@ -1,6 +1,8 @@ package com.botdarr.api.sonarr; import com.botdarr.api.KeyBased; +import com.botdarr.api.radarr.RadarrImage; +import org.apache.logging.log4j.util.Strings; import java.util.List; @@ -74,6 +76,17 @@ public String getRemotePoster() { return remotePoster; } + public String getRemoteImage() { + if (Strings.isEmpty(remotePoster)) { + for(SonarrImage sonarrImage : images) { + if (sonarrImage.getCoverType().equals("poster") && !Strings.isEmpty(sonarrImage.getRemoteUrl())) { + return sonarrImage.getRemoteUrl(); + } + } + } + return remotePoster; + } + public void setRemotePoster(String remotePoster) { this.remotePoster = remotePoster; } diff --git a/src/main/java/com/botdarr/clients/ChatClientBootstrap.java b/src/main/java/com/botdarr/clients/ChatClientBootstrap.java index 0a70628..6128217 100644 --- a/src/main/java/com/botdarr/clients/ChatClientBootstrap.java +++ b/src/main/java/com/botdarr/clients/ChatClientBootstrap.java @@ -8,7 +8,6 @@ import com.botdarr.api.radarr.RadarrCommands; import com.botdarr.api.sonarr.SonarrApi; import com.botdarr.api.sonarr.SonarrCommands; -import com.botdarr.clients.telegram.TelegramResponse; import com.botdarr.commands.*; import com.botdarr.commands.responses.CommandResponse; import com.botdarr.scheduling.Scheduler; @@ -26,7 +25,7 @@ public void validatePrefix(String configuredPrefix) { } } - protected static ApisAndCommandConfig buildConfig() { + protected ApisAndCommandConfig buildConfig() { RadarrApi radarrApi = new RadarrApi(); SonarrApi sonarrApi = new SonarrApi(); LidarrApi lidarrApi = new LidarrApi(); @@ -63,20 +62,31 @@ protected void initScheduling(ChatClient chatC scheduler.initApiNotifications(apis, chatClient, responseBuilder); } - protected static void runAndProcessCommands(String message, - String username, - ChatClientResponseBuilder responseBuilder, - ChatSender chatSender) { - List commandResponses = - commandProcessor.processRequestMessage(buildConfig().getCommands(), message, username); - if (commandResponses != null) { - //if there is a response, format it for given response builder - for (CommandResponse commandResponse : commandResponses) { - //convert command response into chat client specific response - T telegramResponse = commandResponse.convertToChatClientResponse(responseBuilder); - //then send the response - chatSender.send(telegramResponse); + protected void runAndProcessCommands(String prefix, + String message, + String username, + ChatClientResponseBuilder responseBuilder, + ChatSender chatSender) { + try { + CommandContext + .start() + .setPrefix(prefix) + .setUsername(username); + LOGGER.debug("Processing command " + message + " for username " + username + " with prefix " + prefix); + List commandResponses = + commandProcessor.processCommand(prefix, buildConfig().getCommands(), message, username); + if (commandResponses != null) { + //if there is a response, format it for given response builder + for (CommandResponse commandResponse : commandResponses) { + LOGGER.debug("Processing command response " + commandResponse.toString()); + //convert command response into chat client specific response + T clientResponse = commandResponse.convertToChatClientResponse(responseBuilder); + //then send the response + chatSender.send(clientResponse); + } } + } finally { + CommandContext.end(); } } @@ -98,6 +108,6 @@ public List getCommands() { private final List commands; } - protected static CommandProcessor commandProcessor = new CommandProcessor(); + protected CommandProcessor commandProcessor = new CommandProcessor(); protected static final Logger LOGGER = LogManager.getLogger(ChatClientBootstrap.class); } diff --git a/src/main/java/com/botdarr/clients/discord/DiscordBootstrap.java b/src/main/java/com/botdarr/clients/discord/DiscordBootstrap.java index 5a06447..4e37af0 100644 --- a/src/main/java/com/botdarr/clients/discord/DiscordBootstrap.java +++ b/src/main/java/com/botdarr/clients/discord/DiscordBootstrap.java @@ -1,30 +1,47 @@ package com.botdarr.clients.discord; import com.botdarr.Config; +import com.botdarr.api.lidarr.LidarrCommands; +import com.botdarr.api.radarr.RadarrCommands; +import com.botdarr.api.sonarr.SonarrCommands; import com.botdarr.clients.ChatClient; import com.botdarr.clients.ChatClientBootstrap; import com.botdarr.clients.ChatClientResponseBuilder; +import com.botdarr.commands.Command; +import com.botdarr.commands.CommandContext; import com.botdarr.scheduling.Scheduler; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.MessageHistory; +import net.dv8tion.jda.api.entities.MessageType; import net.dv8tion.jda.api.events.GenericEvent; import net.dv8tion.jda.api.events.ReadyEvent; +import net.dv8tion.jda.api.events.interaction.ButtonClickEvent; +import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.requests.restaction.WebhookMessageAction; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.util.Strings; +import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; +import java.util.Collections; import java.util.List; import java.util.Properties; import static com.botdarr.api.lidarr.LidarrApi.ADD_ARTIST_COMMAND_FIELD_PREFIX; +import static com.botdarr.api.lidarr.LidarrApi.ARTIST_LOOKUP_KEY_FIELD; import static com.botdarr.api.radarr.RadarrApi.ADD_MOVIE_COMMAND_FIELD_PREFIX; +import static com.botdarr.api.radarr.RadarrApi.MOVIE_LOOKUP_FIELD; import static com.botdarr.api.sonarr.SonarrApi.ADD_SHOW_COMMAND_FIELD_PREFIX; +import static com.botdarr.api.sonarr.SonarrApi.SHOW_LOOKUP_FIELD; public class DiscordBootstrap extends ChatClientBootstrap { @Override @@ -48,6 +65,43 @@ public void onReady(@Nonnull ReadyEvent event) { super.onReady(event); } + @Override + public void onSlashCommand(@NotNull SlashCommandEvent event) { + // let discord know we received the message as quick as we can + event.deferReply().queue(); + + String discordSlashCommandPrefix = "/"; + StringBuilder eventCommand = new StringBuilder(discordSlashCommandPrefix + event.getName().replace('-', ' ')); + for (OptionMapping option : event.getOptions()) { + eventCommand.append(" ").append(option.getAsString()); + } + ChatClientResponseBuilder slashCommandResponseBuilder = new DiscordResponseBuilder().usesSlashCommands(); + //capture/process command + Scheduler.getScheduler().executeCommand(() -> { + runAndProcessCommands(discordSlashCommandPrefix, eventCommand.toString(), event.getUser().getName(), slashCommandResponseBuilder, chatClientResponse -> { + // then send the rest of the messages + WebhookMessageAction action = event.getHook().sendMessageEmbeds(Collections.singletonList(chatClientResponse.getMessage())); + if (!chatClientResponse.getActionComponents().isEmpty()) { + action = action.addActionRow(chatClientResponse.getActionComponents()); + } + action.queue(); + }); + return null; + }); + LogManager.getLogger("com.botdarr.clients.discord").debug(eventCommand); + } + + @Override + public void onButtonClick(@NotNull ButtonClickEvent event) { + Message message = event.getInteraction().getMessage(); + message.getEmbeds().forEach(embed -> { + String command = getCommandFromEmbed(embed); + if (!Strings.isEmpty(command)) { + handleCommand(event.getJDA(), command, event.getUser().getName(), event.getChannel().getName()); + } + }); + } + @Override public void onGuildMessageReactionAdd(@Nonnull GuildMessageReactionAddEvent event) { if (event.getReactionEmote().getName().equalsIgnoreCase(THUMBS_UP_EMOTE)) { @@ -78,8 +132,10 @@ public void onGuildMessageReactionAdd(@Nonnull GuildMessageReactionAddEvent even @Override public void onMessageReceived(@Nonnull MessageReceivedEvent event) { - handleCommand(event.getJDA(), event.getMessage().getContentStripped(), event.getAuthor().getName(), event.getChannel().getName()); - LogManager.getLogger("com.botdarr.clients.discord").debug(event.getMessage().getContentRaw()); + if (event.getMessage().getType() != MessageType.APPLICATION_COMMAND && event.getMessage().getEmbeds().isEmpty()) { + handleCommand(event.getJDA(), event.getMessage().getContentStripped(), event.getAuthor().getName(), event.getChannel().getName()); + LogManager.getLogger("com.botdarr.clients.discord").debug(event.getMessage().getContentRaw()); + } super.onMessageReceived(event); } @@ -89,7 +145,7 @@ private void handleCommand(JDA jda, String message, String author, String channe //capture/process command Scheduler.getScheduler().executeCommand(() -> { - runAndProcessCommands(message, author, responseBuilder, chatClientResponse -> { + DiscordBootstrap.this.runAndProcessCommands(CommandContext.getConfig().getPrefix(), message, author, responseBuilder, chatClientResponse -> { discordChatClient.sendMessage(chatClientResponse, channelName); }); return null; @@ -98,6 +154,7 @@ private void handleCommand(JDA jda, String message, String author, String channe private static final String THUMBS_UP_EMOTE = "\uD83D\uDC4D"; }).build(); + config.getCommands().forEach(command -> jda.upsertCommand(convertCommandToCommandData(command)).queue()); jda.awaitReady(); } catch (Throwable e) { LogManager.getLogger("com.botdarr.clients.discord").error("Error caught during main", e); @@ -110,4 +167,43 @@ public boolean isConfigured(Properties properties) { return !Strings.isBlank(properties.getProperty(Config.Constants.DISCORD_TOKEN)) && !Strings.isBlank(properties.getProperty(Config.Constants.DISCORD_CHANNELS)); } + + @Override + public void validatePrefix(String configuredPrefix) { + super.validatePrefix(configuredPrefix); + if (configuredPrefix.equals("/")) { + throw new RuntimeException("Cannot use / command prefix in discord since / command is used by discord slash commands"); + } + } + + public String getCommandFromEmbed(MessageEmbed embed) { + for(MessageEmbed.Field field : embed.getFields()) { + if (field.getName() != null) { + if (field.getName().equals(MOVIE_LOOKUP_FIELD)) { + return RadarrCommands.getAddMovieCommandStr(embed.getTitle(), Long.parseLong(field.getValue())); + } + if (field.getName().equals(SHOW_LOOKUP_FIELD)) { + return SonarrCommands.getAddShowCommandStr(embed.getTitle(), Long.parseLong(field.getValue())); + } + if (field.getName().equals(ARTIST_LOOKUP_KEY_FIELD)) { + return LidarrCommands.getAddArtistCommandStr(embed.getTitle(), field.getValue()); + } + } + } + return null; + } + + public CommandData convertCommandToCommandData(Command command) { + String description = command.getDescription(); + if (description.length() > 100) { + description = description.substring(0, 97); + description += "..."; + } + CommandData commandData = new CommandData(command.getCommandText().replace(' ', '-'), description); + command.getInput().forEach(input -> { + // all input is required by default + commandData.addOption(OptionType.STRING, input, input, true); + }); + return commandData; + } } diff --git a/src/main/java/com/botdarr/clients/discord/DiscordResponse.java b/src/main/java/com/botdarr/clients/discord/DiscordResponse.java index cc3e473..ab31615 100644 --- a/src/main/java/com/botdarr/clients/discord/DiscordResponse.java +++ b/src/main/java/com/botdarr/clients/discord/DiscordResponse.java @@ -2,15 +2,29 @@ import com.botdarr.clients.ChatClientResponse; import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.interactions.components.Component; + +import java.util.ArrayList; +import java.util.List; public class DiscordResponse implements ChatClientResponse { public DiscordResponse(MessageEmbed message) { this.message = message; } + public DiscordResponse(MessageEmbed message, List actionComponents) { + this(message); + this.actionComponents = actionComponents; + } + public MessageEmbed getMessage() { return message; } + public List getActionComponents() { + return actionComponents; + } + + private List actionComponents = new ArrayList<>(); private final MessageEmbed message; } diff --git a/src/main/java/com/botdarr/clients/discord/DiscordResponseBuilder.java b/src/main/java/com/botdarr/clients/discord/DiscordResponseBuilder.java index f4de1a0..b73b596 100644 --- a/src/main/java/com/botdarr/clients/discord/DiscordResponseBuilder.java +++ b/src/main/java/com/botdarr/clients/discord/DiscordResponseBuilder.java @@ -13,16 +13,22 @@ import com.botdarr.utilities.ListUtils; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.interactions.components.Button; +import net.dv8tion.jda.api.interactions.components.Component; import org.apache.logging.log4j.util.Strings; import java.awt.*; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import static com.botdarr.api.lidarr.LidarrApi.ADD_ARTIST_COMMAND_FIELD_PREFIX; +import static com.botdarr.api.lidarr.LidarrApi.ARTIST_LOOKUP_KEY_FIELD; import static com.botdarr.api.radarr.RadarrApi.ADD_MOVIE_COMMAND_FIELD_PREFIX; +import static com.botdarr.api.radarr.RadarrApi.MOVIE_LOOKUP_FIELD; import static com.botdarr.api.sonarr.SonarrApi.ADD_SHOW_COMMAND_FIELD_PREFIX; +import static com.botdarr.api.sonarr.SonarrApi.SHOW_LOOKUP_FIELD; import static com.botdarr.commands.StatusCommand.STATUS_COMMAND; import static com.botdarr.commands.StatusCommand.STATUS_COMMAND_DESCRIPTION; import static net.dv8tion.jda.api.entities.MessageEmbed.VALUE_MAX_LENGTH; @@ -41,22 +47,22 @@ public DiscordResponse build(HelpResponse helpResponse) { boolean sonarrEnabled = Config.isSonarrEnabled(); boolean lidarrEnabled = Config.isLidarrEnabled(); if (radarrEnabled) { - embedBuilder.addField(RadarrCommands.getHelpMovieCommandStr(), "Shows all the commands for movies", false); + embedBuilder.addField(RadarrCommands.getHelpMovieCommandStr().replace(" ", "-"), "Shows all the commands for movies", false); } if (sonarrEnabled) { - embedBuilder.addField(SonarrCommands.getHelpShowCommandStr(), "Shows all the commands for shows", false); + embedBuilder.addField(SonarrCommands.getHelpShowCommandStr().replace(" ", "-"), "Shows all the commands for shows", false); } if (lidarrEnabled) { - embedBuilder.addField(LidarrCommands.getHelpCommandStr(), "Shows all the commands for music", false); + embedBuilder.addField(LidarrCommands.getHelpCommandStr().replace(" ", "-"), "Shows all the commands for music", false); } if (!radarrEnabled && !sonarrEnabled && !lidarrEnabled) { embedBuilder.appendDescription("No radarr or sonarr or lidarr commands configured, check your properties file and logs"); } if (!Config.getStatusEndpoints().isEmpty()) { - embedBuilder.addField(new CommandProcessor().getPrefix() + STATUS_COMMAND, STATUS_COMMAND_DESCRIPTION, false); + embedBuilder.addField(CommandContext.getConfig().getPrefix() + STATUS_COMMAND, STATUS_COMMAND_DESCRIPTION, false); } return new DiscordResponse(embedBuilder.build()); } @@ -81,9 +87,9 @@ public DiscordResponse build(ShowResponse showResponse) { EmbedBuilder embedBuilder = new EmbedBuilder(); SonarrShow show = showResponse.getShow(); embedBuilder.setTitle(show.getTitle()); - embedBuilder.addField("TvdbId", String.valueOf(show.getTvdbId()), false); + embedBuilder.addField(SHOW_LOOKUP_FIELD, String.valueOf(show.getTvdbId()), false); embedBuilder.addField(ADD_SHOW_COMMAND_FIELD_PREFIX, SonarrCommands.getAddShowCommandStr(show.getTitle(), show.getTvdbId()), false); - embedBuilder.setImage(show.getRemotePoster()); + embedBuilder.setImage(show.getRemoteImage()); return new DiscordResponse(embedBuilder.build()); } @@ -94,7 +100,7 @@ public DiscordResponse build(MusicArtistResponse musicArtistResponse) { embedBuilder.setTitle(lidarrArtist.getArtistName()); embedBuilder.addField("Id", String.valueOf(lidarrArtist.getForeignArtistId()), false); embedBuilder.addField(ADD_ARTIST_COMMAND_FIELD_PREFIX, LidarrCommands.getAddArtistCommandStr(lidarrArtist.getArtistName(), lidarrArtist.getForeignArtistId()), false); - embedBuilder.setImage(lidarrArtist.getRemotePoster()); + embedBuilder.setImage(lidarrArtist.getRemoteImage()); return new DiscordResponse(embedBuilder.build()); } @@ -204,9 +210,14 @@ public DiscordResponse build(NewShowResponse newShowResponse) { SonarrShow sonarrShow = newShowResponse.getNewShow(); embedBuilder.setTitle(sonarrShow.getTitle()); embedBuilder.addField("TvdbId", "" + sonarrShow.getTvdbId(), true); - embedBuilder.addField(ADD_SHOW_COMMAND_FIELD_PREFIX, SonarrCommands.getAddShowCommandStr(sonarrShow.getTitle(), sonarrShow.getTvdbId()), false); - embedBuilder.setImage(sonarrShow.getRemotePoster()); - return new DiscordResponse(embedBuilder.build()); + embedBuilder.setImage(sonarrShow.getRemoteImage()); + if (!usingSlashCommand) { + embedBuilder.addField(ADD_SHOW_COMMAND_FIELD_PREFIX, SonarrCommands.getAddShowCommandStr(sonarrShow.getTitle(), sonarrShow.getTvdbId()), false); + return new DiscordResponse(embedBuilder.build()); + } + List actionComponents = new ArrayList<>(); + actionComponents.add(Button.primary("add", "Add")); + return new DiscordResponse(embedBuilder.build(), actionComponents); } @Override @@ -224,7 +235,7 @@ public DiscordResponse build(ExistingShowResponse existingShowResponse) { ",Available Epsiodes=" + sonarrSeason.getStatistics().getEpisodeCount() + ",Total Epsiodes=" + sonarrSeason.getStatistics().getTotalEpisodeCount(), false); } } - embedBuilder.setImage(existingShow.getRemotePoster()); + embedBuilder.setImage(existingShow.getRemoteImage()); return new DiscordResponse(embedBuilder.build()); } @@ -233,10 +244,15 @@ public DiscordResponse build(NewMovieResponse newMovieResponse) { EmbedBuilder embedBuilder = new EmbedBuilder(); RadarrMovie radarrMovie = newMovieResponse.getRadarrMovie(); embedBuilder.setTitle(radarrMovie.getTitle()); - embedBuilder.addField("TmdbId", String.valueOf(radarrMovie.getTmdbId()), false); - embedBuilder.addField(ADD_MOVIE_COMMAND_FIELD_PREFIX, RadarrCommands.getAddMovieCommandStr(radarrMovie.getTitle(), radarrMovie.getTmdbId()), false); - embedBuilder.setImage(radarrMovie.getRemotePoster()); - return new DiscordResponse(embedBuilder.build()); + embedBuilder.addField(MOVIE_LOOKUP_FIELD, String.valueOf(radarrMovie.getTmdbId()), false); + embedBuilder.setImage(radarrMovie.getRemoteImage()); + if (!usingSlashCommand) { + embedBuilder.addField(ADD_MOVIE_COMMAND_FIELD_PREFIX, RadarrCommands.getAddMovieCommandStr(radarrMovie.getTitle(), radarrMovie.getTmdbId()), false); + return new DiscordResponse(embedBuilder.build()); + } + List actionComponents = new ArrayList<>(); + actionComponents.add(Button.primary("add", "Add")); + return new DiscordResponse(embedBuilder.build(), actionComponents); } @Override @@ -248,7 +264,7 @@ public DiscordResponse build(ExistingMovieResponse existingMovieResponse) { embedBuilder.addField("Id", String.valueOf(radarrMovie.getId()), false); embedBuilder.addField("Downloaded", String.valueOf((radarrMovie.getSizeOnDisk() > 0)), false); embedBuilder.addField("Has File", String.valueOf(radarrMovie.isHasFile()), false); - embedBuilder.setImage(radarrMovie.getRemotePoster()); + embedBuilder.setImage(radarrMovie.getRemoteImage()); return new DiscordResponse(embedBuilder.build()); } @@ -258,9 +274,15 @@ public DiscordResponse build(NewMusicArtistResponse newMusicArtistResponse) { LidarrArtist lookupArtist = newMusicArtistResponse.getLidarrArtist(); String artistDetail = " (" + lookupArtist.getDisambiguation() + ")"; embedBuilder.setTitle(lookupArtist.getArtistName() + (Strings.isEmpty(lookupArtist.getDisambiguation()) ? "" : artistDetail)); - embedBuilder.addField(ADD_ARTIST_COMMAND_FIELD_PREFIX, LidarrCommands.getAddArtistCommandStr(lookupArtist.getArtistName(), lookupArtist.getForeignArtistId()), false); - embedBuilder.setImage(lookupArtist.getRemotePoster()); - return new DiscordResponse(embedBuilder.build()); + embedBuilder.addField(ARTIST_LOOKUP_KEY_FIELD, lookupArtist.getForeignArtistId(), false); + embedBuilder.setImage(lookupArtist.getRemoteImage()); + if (!usingSlashCommand) { + embedBuilder.addField(ADD_ARTIST_COMMAND_FIELD_PREFIX, LidarrCommands.getAddArtistCommandStr(lookupArtist.getArtistName(), lookupArtist.getForeignArtistId()), false); + return new DiscordResponse(embedBuilder.build()); + } + List actionComponents = new ArrayList<>(); + actionComponents.add(Button.primary("add", "Add")); + return new DiscordResponse(embedBuilder.build(), actionComponents); } @Override @@ -269,7 +291,7 @@ public DiscordResponse build(ExistingMusicArtistResponse existingMusicArtistResp LidarrArtist lookupArtist = existingMusicArtistResponse.getLidarrArtist(); String artistDetail = " (" + lookupArtist.getDisambiguation() + ")"; embedBuilder.setTitle(lookupArtist.getArtistName() + (Strings.isEmpty(lookupArtist.getDisambiguation()) ? "" : artistDetail)); - embedBuilder.setImage(lookupArtist.getRemotePoster()); + embedBuilder.setImage(lookupArtist.getRemoteImage()); return new DiscordResponse(embedBuilder.build()); } @@ -313,7 +335,7 @@ private DiscordResponse getMovieResponse(RadarrMovie radarrMovie) { embedBuilder.setTitle(radarrMovie.getTitle()); embedBuilder.addField("TmdbId", "" + radarrMovie.getTmdbId(), false); embedBuilder.addField(ADD_MOVIE_COMMAND_FIELD_PREFIX, RadarrCommands.getAddMovieCommandStr(radarrMovie.getTitle(), radarrMovie.getTmdbId()), false); - embedBuilder.setImage(radarrMovie.getRemotePoster()); + embedBuilder.setImage(radarrMovie.getRemoteImage()); return new DiscordResponse(embedBuilder.build()); } @@ -341,8 +363,15 @@ private DiscordResponse getListOfCommands(List commands) { EmbedBuilder embedBuilder = new EmbedBuilder(); embedBuilder.setTitle("Commands"); for (Command com : commands) { - embedBuilder.addField(new CommandProcessor().getPrefix() + com.getCommandUsage(), com.getDescription(), false); + embedBuilder.addField(CommandContext.getConfig().getPrefix().replace(" ", "-") + com.getCommandUsage(), com.getDescription(), false); } return new DiscordResponse(embedBuilder.build()); } + + public DiscordResponseBuilder usesSlashCommands() { + this.usingSlashCommand = true; + return this; + } + + private boolean usingSlashCommand = false; } diff --git a/src/main/java/com/botdarr/clients/matrix/MatrixBootstrap.java b/src/main/java/com/botdarr/clients/matrix/MatrixBootstrap.java index 8625f76..4438c2b 100644 --- a/src/main/java/com/botdarr/clients/matrix/MatrixBootstrap.java +++ b/src/main/java/com/botdarr/clients/matrix/MatrixBootstrap.java @@ -3,6 +3,7 @@ import com.botdarr.Config; import com.botdarr.clients.ChatClientResponseBuilder; import com.botdarr.clients.ChatClientBootstrap; +import com.botdarr.commands.CommandContext; import com.botdarr.scheduling.Scheduler; import org.apache.logging.log4j.util.Strings; @@ -18,7 +19,7 @@ public void init() throws Exception { initScheduling(chatClient, responseChatClientResponseBuilder, config.getApis()); chatClient.addListener((roomId, sender, content) -> { Scheduler.getScheduler().executeCommand(() -> { - runAndProcessCommands(content, sender, responseChatClientResponseBuilder, chatClientResponse -> { + MatrixBootstrap.this.runAndProcessCommands(CommandContext.getConfig().getPrefix(), content, sender, responseChatClientResponseBuilder, chatClientResponse -> { chatClient.sendMessage(chatClientResponse, roomId); }); return null; diff --git a/src/main/java/com/botdarr/clients/matrix/MatrixResponseBuilder.java b/src/main/java/com/botdarr/clients/matrix/MatrixResponseBuilder.java index 3efa08f..83c003a 100644 --- a/src/main/java/com/botdarr/clients/matrix/MatrixResponseBuilder.java +++ b/src/main/java/com/botdarr/clients/matrix/MatrixResponseBuilder.java @@ -48,7 +48,7 @@ public MatrixResponse build(HelpResponse helpResponse) { } if (!Config.getStatusEndpoints().isEmpty()) { - matrixResponse.addContent("" + new CommandProcessor().getPrefix() + STATUS_COMMAND + " - " + STATUS_COMMAND_DESCRIPTION); + matrixResponse.addContent("" + CommandContext.getConfig().getPrefix() + STATUS_COMMAND + " - " + STATUS_COMMAND_DESCRIPTION); } return matrixResponse; } catch (Exception e) { @@ -78,7 +78,7 @@ public MatrixResponse build(ShowResponse showResponse) { matrixResponse.addContent("Title - " + show.getTitle()); matrixResponse.addContent("TvdbId - " + show.getTvdbId()); matrixResponse.addContent("" + ADD_SHOW_COMMAND_FIELD_PREFIX + " - " + SonarrCommands.getAddShowCommandStr(show.getTitle(), show.getTvdbId())); - matrixResponse.addImage(show.getRemotePoster()); + matrixResponse.addImage(show.getRemoteImage()); return matrixResponse; } @@ -222,7 +222,7 @@ public MatrixResponse build(NewShowResponse newShowResponse) { matrixResponse.addContent("Title - " + sonarrShow.getTitle()); matrixResponse.addContent("TvdbId - " + sonarrShow.getTvdbId()); matrixResponse.addContent("" + ADD_SHOW_COMMAND_FIELD_PREFIX + " - " + SonarrCommands.getAddShowCommandStr(sonarrShow.getTitle(), sonarrShow.getTvdbId())); - matrixResponse.addImage(sonarrShow.getRemotePoster()); + matrixResponse.addImage(sonarrShow.getRemoteImage()); return matrixResponse; } @@ -242,7 +242,7 @@ public MatrixResponse build(ExistingShowResponse existingShowResponse) { ",Total Epsiodes=" + sonarrSeason.getStatistics().getTotalEpisodeCount()); } } - matrixResponse.addImage(existingShow.getRemotePoster()); + matrixResponse.addImage(existingShow.getRemoteImage()); return matrixResponse; } @@ -253,7 +253,7 @@ public MatrixResponse build(NewMovieResponse newMovieResponse) { matrixResponse.addContent("Title - " + radarrMovie.getTitle()); matrixResponse.addContent("TmdbId - " + radarrMovie.getTmdbId()); matrixResponse.addContent("" + ADD_MOVIE_COMMAND_FIELD_PREFIX + " - " + RadarrCommands.getAddMovieCommandStr(radarrMovie.getTitle(), radarrMovie.getTmdbId())); - matrixResponse.addImage(radarrMovie.getRemotePoster()); + matrixResponse.addImage(radarrMovie.getRemoteImage()); return matrixResponse; } @@ -266,7 +266,7 @@ public MatrixResponse build(ExistingMovieResponse existingMovieResponse) { matrixResponse.addContent("Id - " + radarrMovie.getId()); matrixResponse.addContent("Downloaded - " + (radarrMovie.getSizeOnDisk() > 0)); matrixResponse.addContent("Has File - " + radarrMovie.isHasFile()); - matrixResponse.addImage(radarrMovie.getRemotePoster()); + matrixResponse.addImage(radarrMovie.getRemoteImage()); return matrixResponse; } @@ -277,7 +277,7 @@ public MatrixResponse build(NewMusicArtistResponse newMusicArtistResponse) { String artistDetail = " (" + lidarrArtist.getDisambiguation() + ")"; matrixResponse.addContent("Title - " + (lidarrArtist.getArtistName() + (Strings.isEmpty(lidarrArtist.getDisambiguation()) ? "" : artistDetail))); matrixResponse.addContent("" + ADD_ARTIST_COMMAND_FIELD_PREFIX + " - " + LidarrCommands.getAddArtistCommandStr(lidarrArtist.getArtistName(), lidarrArtist.getForeignArtistId())); - matrixResponse.addImage(lidarrArtist.getRemotePoster()); + matrixResponse.addImage(lidarrArtist.getRemoteImage()); return matrixResponse; } @@ -287,7 +287,7 @@ public MatrixResponse build(ExistingMusicArtistResponse existingMusicArtistRespo MatrixResponse matrixResponse = new MatrixResponse(); String artistDetail = " (" + lidarrArtist.getDisambiguation() + ")"; matrixResponse.addContent("Title - " + (lidarrArtist.getArtistName() + (Strings.isEmpty(lidarrArtist.getDisambiguation()) ? "" : artistDetail))); - matrixResponse.addImage(lidarrArtist.getRemotePoster()); + matrixResponse.addImage(lidarrArtist.getRemoteImage()); return matrixResponse; } @@ -298,8 +298,8 @@ public MatrixResponse build(DiscoverMovieResponse discoverMovieResponse) { matrixResponse.addContent("Title - " + radarrMovie.getTitle()); matrixResponse.addContent("TmdbId - " + radarrMovie.getTmdbId()); matrixResponse.addContent("" + ADD_MOVIE_COMMAND_FIELD_PREFIX + " - " + RadarrCommands.getAddMovieCommandStr(radarrMovie.getTitle(), radarrMovie.getTmdbId())); - if (radarrMovie.getRemotePoster() != null && !radarrMovie.getRemotePoster().isEmpty()) { - matrixResponse.addImage(radarrMovie.getRemotePoster()); + if (radarrMovie.getRemoteImage() != null && !radarrMovie.getRemoteImage().isEmpty()) { + matrixResponse.addImage(radarrMovie.getRemoteImage()); } return matrixResponse; } @@ -327,7 +327,7 @@ private MatrixResponse getListOfCommands(List commands) { MatrixResponse matrixResponse = new MatrixResponse(); matrixResponse.addContent("Commands"); for (Command command : commands) { - matrixResponse.addContent("" + new CommandProcessor().getPrefix() + command.getCommandUsage() + "" + " - " + command.getDescription()); + matrixResponse.addContent("" + CommandContext.getConfig().getPrefix() + command.getCommandUsage() + "" + " - " + command.getDescription()); } return matrixResponse; } diff --git a/src/main/java/com/botdarr/clients/slack/SlackBootstrap.java b/src/main/java/com/botdarr/clients/slack/SlackBootstrap.java index 5a21958..b536b8d 100644 --- a/src/main/java/com/botdarr/clients/slack/SlackBootstrap.java +++ b/src/main/java/com/botdarr/clients/slack/SlackBootstrap.java @@ -3,6 +3,7 @@ import com.botdarr.Config; import com.botdarr.clients.ChatClientBootstrap; import com.botdarr.clients.ChatClientResponseBuilder; +import com.botdarr.commands.CommandContext; import com.botdarr.scheduling.Scheduler; import com.github.seratch.jslack.Slack; import com.github.seratch.jslack.api.model.Message; @@ -86,7 +87,7 @@ public void handle(String message) { private void handleCommand(String text, String userId, String channel) { Scheduler.getScheduler().executeCommand(() -> { - runAndProcessCommands(text, userId, responseChatClientResponseBuilder, chatClientResponse -> { + SlackBootstrap.this.runAndProcessCommands(CommandContext.getConfig().getPrefix(), text, userId, responseChatClientResponseBuilder, chatClientResponse -> { slackChatClient.sendMessage(chatClientResponse, channel); }); return null; diff --git a/src/main/java/com/botdarr/clients/slack/SlackResponseBuilder.java b/src/main/java/com/botdarr/clients/slack/SlackResponseBuilder.java index a715bf4..d1b3776 100644 --- a/src/main/java/com/botdarr/clients/slack/SlackResponseBuilder.java +++ b/src/main/java/com/botdarr/clients/slack/SlackResponseBuilder.java @@ -72,7 +72,7 @@ public SlackResponse build(HelpResponse helpResponse) { } if (!Config.getStatusEndpoints().isEmpty()) { slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text(new CommandProcessor().getPrefix() + STATUS_COMMAND + " - " + STATUS_COMMAND_DESCRIPTION).build()) + .text(MarkdownTextObject.builder().text(CommandContext.getConfig().getPrefix() + STATUS_COMMAND + " - " + STATUS_COMMAND_DESCRIPTION).build()) .build()); } } catch (IOException e) { @@ -109,11 +109,11 @@ public SlackResponse build(ShowResponse showResponse) { slackResponse.addBlock(SectionBlock.builder() .text(PlainTextObject.builder().text(ADD_SHOW_COMMAND_FIELD_PREFIX + " - " + SonarrCommands.getAddShowCommandStr(show.getTitle(), show.getTvdbId())).build()) .build()); - if (!Strings.isBlank(show.getRemotePoster())) { + if (!Strings.isBlank(show.getRemoteImage())) { //if there is no poster to display, slack will fail to render all the blocks //so make sure there is one before trying to render slackResponse.addBlock(ImageBlock.builder() - .imageUrl(show.getRemotePoster()) + .imageUrl(show.getRemoteImage()) .altText(show.getTitle() + " poster") .build()); } @@ -133,11 +133,11 @@ public SlackResponse build(MusicArtistResponse musicArtistResponse) { slackResponse.addBlock(SectionBlock.builder() .text(PlainTextObject.builder().text(ADD_ARTIST_COMMAND_FIELD_PREFIX + " - " + LidarrCommands.getAddArtistCommandStr(lidarrArtist.getArtistName(), lidarrArtist.getForeignArtistId())).build()) .build()); - if (!Strings.isBlank(lidarrArtist.getRemotePoster())) { + if (!Strings.isBlank(lidarrArtist.getRemoteImage())) { //if there is no poster to display, slack will fail to render all the blocks //so make sure there is one before trying to render slackResponse.addBlock(ImageBlock.builder() - .imageUrl(lidarrArtist.getRemotePoster()) + .imageUrl(lidarrArtist.getRemoteImage()) .altText(lidarrArtist.getArtistName() + " poster") .build()); } @@ -347,7 +347,7 @@ public SlackResponse build(NewShowResponse newShowResponse) { .text(MarkdownTextObject.builder().text(ADD_SHOW_COMMAND_FIELD_PREFIX + " - " + SonarrCommands.getAddShowCommandStr(sonarrShow.getTitle(), sonarrShow.getTvdbId())).build()) .build()); slackResponse.addBlock(ImageBlock.builder() - .imageUrl(sonarrShow.getRemotePoster()) + .imageUrl(sonarrShow.getRemoteImage()) .altText(sonarrShow.getTitle() + " poster") .build()); return slackResponse; @@ -382,7 +382,7 @@ public SlackResponse build(ExistingShowResponse existingShowResponse) { .build()); } slackResponse.addBlock(ImageBlock.builder() - .imageUrl(sonarrShow.getRemotePoster()) + .imageUrl(sonarrShow.getRemoteImage()) .altText(sonarrShow.getTitle() + " poster") .build()); return slackResponse; @@ -403,7 +403,7 @@ public SlackResponse build(NewMovieResponse newMovieResponse) { .text(MarkdownTextObject.builder().text(ADD_MOVIE_COMMAND_FIELD_PREFIX + " - " + RadarrCommands.getAddMovieCommandStr(lookupMovie.getTitle(), lookupMovie.getTmdbId())).build()) .build()); slackResponse.addBlock(ImageBlock.builder() - .imageUrl(lookupMovie.getRemotePoster()) + .imageUrl(lookupMovie.getRemoteImage()) .altText(lookupMovie.getTitle() + " poster") .build()); return slackResponse; @@ -430,7 +430,7 @@ public SlackResponse build(ExistingMovieResponse existingMovieResponse) { .text(MarkdownTextObject.builder().text("Has File - " + lookupMovie.isHasFile()).build()) .build()); slackResponse.addBlock(ImageBlock.builder() - .imageUrl(lookupMovie.getRemotePoster()) + .imageUrl(lookupMovie.getRemoteImage()) .altText(lookupMovie.getTitle() + " poster") .build()); return slackResponse; @@ -449,7 +449,7 @@ public SlackResponse build(NewMusicArtistResponse newMusicArtistResponse) { LidarrCommands.getAddArtistCommandStr(lookupArtist.getArtistName(), lookupArtist.getForeignArtistId())).build()) .build()); slackResponse.addBlock(ImageBlock.builder() - .imageUrl(lookupArtist.getRemotePoster()) + .imageUrl(lookupArtist.getRemoteImage()) .altText(lookupArtist.getArtistName() + " poster") .build()); return slackResponse; @@ -464,7 +464,7 @@ public SlackResponse build(ExistingMusicArtistResponse existingMusicArtistRespon .text(MarkdownTextObject.builder().text("*Artist Name* - " + lookupArtist.getArtistName() + (Strings.isEmpty(lookupArtist.getDisambiguation()) ? "" : artistDetail)).build()) .build()); slackResponse.addBlock(ImageBlock.builder() - .imageUrl(lookupArtist.getRemotePoster()) + .imageUrl(lookupArtist.getRemoteImage()) .altText(lookupArtist.getArtistName() + " poster") .build()); return slackResponse; @@ -505,11 +505,11 @@ private SlackResponse getMovieResponse(RadarrMovie radarrMovie) { slackResponse.addBlock(SectionBlock.builder() .text(MarkdownTextObject.builder().text(ADD_MOVIE_COMMAND_FIELD_PREFIX + " - " + RadarrCommands.getAddMovieCommandStr(radarrMovie.getTitle(), radarrMovie.getTmdbId())).build()) .build()); - if (!Strings.isBlank(radarrMovie.getRemotePoster())) { + if (!Strings.isBlank(radarrMovie.getRemoteImage())) { //if there is no poster to display, slack will fail to render all the blocks //so make sure there is one before trying to render slackResponse.addBlock(ImageBlock.builder() - .imageUrl(radarrMovie.getRemotePoster()) + .imageUrl(radarrMovie.getRemoteImage()) .altText(radarrMovie.getTitle() + " poster") .build()); } @@ -523,7 +523,7 @@ private SlackResponse getListOfCommands(List commands) { .build()); for (Command command : commands) { slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text(new CommandProcessor().getPrefix() + command.getCommandUsage() + " - " + command.getDescription()).build()) + .text(MarkdownTextObject.builder().text(CommandContext.getConfig().getPrefix() + command.getCommandUsage() + " - " + command.getDescription()).build()) .build()); } return slackResponse; diff --git a/src/main/java/com/botdarr/clients/telegram/TelegramBootstrap.java b/src/main/java/com/botdarr/clients/telegram/TelegramBootstrap.java index 3945510..6a85674 100644 --- a/src/main/java/com/botdarr/clients/telegram/TelegramBootstrap.java +++ b/src/main/java/com/botdarr/clients/telegram/TelegramBootstrap.java @@ -3,6 +3,7 @@ import com.botdarr.Config; import com.botdarr.clients.ChatClientBootstrap; import com.botdarr.clients.ChatClientResponseBuilder; +import com.botdarr.commands.CommandContext; import com.botdarr.scheduling.Scheduler; import com.google.common.base.Splitter; import com.google.common.collect.Sets; @@ -37,7 +38,7 @@ public void init() throws Exception { //for now we leave the author as "telegram" till a better solution arises String author = "telegram"; Scheduler.getScheduler().executeCommand(() -> { - runAndProcessCommands(text, author, responseChatClientResponseBuilder, chatClientResponse -> { + TelegramBootstrap.this.runAndProcessCommands(CommandContext.getConfig().getPrefix(), text, author, responseChatClientResponseBuilder, chatClientResponse -> { telegramChatClient.sendMessage(chatClientResponse, message.chat()); }); return null; diff --git a/src/main/java/com/botdarr/clients/telegram/TelegramResponseBuilder.java b/src/main/java/com/botdarr/clients/telegram/TelegramResponseBuilder.java index 92fc8ca..c238f2f 100644 --- a/src/main/java/com/botdarr/clients/telegram/TelegramResponseBuilder.java +++ b/src/main/java/com/botdarr/clients/telegram/TelegramResponseBuilder.java @@ -50,7 +50,7 @@ public TelegramResponse build(HelpResponse helpResponse) { domContents.add(b("*No radarr or sonarr or lidarr commands configured, check your properties file and logs*")); } if (!Config.getStatusEndpoints().isEmpty()) { - domContents.add(text(new CommandProcessor().getPrefix() + STATUS_COMMAND + " - " + STATUS_COMMAND_DESCRIPTION)); + domContents.add(text(CommandContext.getConfig().getPrefix() + STATUS_COMMAND + " - " + STATUS_COMMAND_DESCRIPTION)); } return new TelegramResponse(domContents); } catch (IOException e) { @@ -80,7 +80,7 @@ public TelegramResponse build(ShowResponse showResponse) { domContents.add(b("*Title* - " + show.getTitle())); domContents.add(code("TvdbId - " + show.getTvdbId())); domContents.add(u(ADD_SHOW_COMMAND_FIELD_PREFIX + " - " + SonarrCommands.getAddShowCommandStr(show.getTitle(), show.getTvdbId()))); - domContents.add(a(show.getRemotePoster())); + domContents.add(a(show.getRemoteImage())); return new TelegramResponse(domContents); } @@ -91,7 +91,7 @@ public TelegramResponse build(MusicArtistResponse musicArtistResponse) { domContents.add(b("*Artist Name* - " + lidarrArtist.getArtistName())); domContents.add(code("Id - " + lidarrArtist.getForeignArtistId())); domContents.add(u(ADD_ARTIST_COMMAND_FIELD_PREFIX + " - " + LidarrCommands.getAddArtistCommandStr(lidarrArtist.getArtistName(), lidarrArtist.getForeignArtistId()))); - domContents.add(a(lidarrArtist.getRemotePoster())); + domContents.add(a(lidarrArtist.getRemoteImage())); return new TelegramResponse(domContents); } @@ -233,7 +233,7 @@ public TelegramResponse build(NewShowResponse newShowResponse) { domContents.add(b("*Title* - " + sonarrShow.getTitle())); domContents.add(code("TvdbId - " + sonarrShow.getTvdbId())); domContents.add(u(ADD_SHOW_COMMAND_FIELD_PREFIX + " - " + SonarrCommands.getAddShowCommandStr(sonarrShow.getTitle(), sonarrShow.getTvdbId()))); - domContents.add(a(sonarrShow.getRemotePoster())); + domContents.add(a(sonarrShow.getRemoteImage())); return new TelegramResponse(domContents); } @@ -256,7 +256,7 @@ public TelegramResponse build(ExistingShowResponse existingShowResponse) { .append("\n"); } domContents.add(code(existingShowDetails.toString())); - domContents.add(a(sonarrShow.getRemotePoster())); + domContents.add(a(sonarrShow.getRemoteImage())); return new TelegramResponse(domContents); } @@ -266,7 +266,7 @@ public TelegramResponse build(NewMovieResponse newMovieResponse) { List domContents = new ArrayList<>(); domContents.add(b(lookupMovie.getTitle())); domContents.add(u(ADD_MOVIE_COMMAND_FIELD_PREFIX + " - " + RadarrCommands.getAddMovieCommandStr(lookupMovie.getTitle(), lookupMovie.getTmdbId()))); - domContents.add(a(lookupMovie.getRemotePoster())); + domContents.add(a(lookupMovie.getRemoteImage())); return new TelegramResponse(domContents); } @@ -279,7 +279,7 @@ public TelegramResponse build(ExistingMovieResponse existingMovieResponse) { "Downloaded - " + (lookupMovie.getSizeOnDisk() > 0) + "\n" + "Has File - " + lookupMovie.isHasFile() + "\n"; domContents.add(code(existingDetails)); - domContents.add(a(lookupMovie.getRemotePoster())); + domContents.add(a(lookupMovie.getRemoteImage())); return new TelegramResponse(domContents); } @@ -290,7 +290,7 @@ public TelegramResponse build(NewMusicArtistResponse newMusicArtistResponse) { String artistDetail = " (" + lookupArtist.getDisambiguation() + ")"; domContents.add(b(lookupArtist.getArtistName() + (Strings.isEmpty(lookupArtist.getDisambiguation()) ? "" : artistDetail))); domContents.add(u(ADD_ARTIST_COMMAND_FIELD_PREFIX + " - " + LidarrCommands.getAddArtistCommandStr(lookupArtist.getArtistName(), lookupArtist.getForeignArtistId()))); - domContents.add(a(lookupArtist.getRemotePoster())); + domContents.add(a(lookupArtist.getRemoteImage())); return new TelegramResponse(domContents); } @@ -300,7 +300,7 @@ public TelegramResponse build(ExistingMusicArtistResponse existingMusicArtistRes List domContents = new ArrayList<>(); String artistDetail = " (" + lookupArtist.getDisambiguation() + ")"; domContents.add(b(lookupArtist.getArtistName() + (Strings.isEmpty(lookupArtist.getDisambiguation()) ? "" : artistDetail))); - domContents.add(a(lookupArtist.getRemotePoster())); + domContents.add(a(lookupArtist.getRemoteImage())); return new TelegramResponse(domContents); } @@ -329,7 +329,7 @@ private TelegramResponse getMovieResponse(RadarrMovie radarrMovie) { domContents.add(b(radarrMovie.getTitle())); domContents.add(text("TmdbId - " + radarrMovie.getTmdbId())); domContents.add(u(b(ADD_MOVIE_COMMAND_FIELD_PREFIX + " - " + RadarrCommands.getAddMovieCommandStr(radarrMovie.getTitle(), radarrMovie.getTmdbId())))); - domContents.add(a(radarrMovie.getRemotePoster())); + domContents.add(a(radarrMovie.getRemoteImage())); return new TelegramResponse(domContents); } @@ -337,7 +337,7 @@ private List getListOfCommands(List commands) { List domContents = new ArrayList<>(); domContents.add(u(b("*Commands*"))); for (Command command : commands) { - domContents.add(b(text(new CommandProcessor().getPrefix() + command.getCommandUsage()))); + domContents.add(b(text(CommandContext.getConfig().getPrefix() + command.getCommandUsage()))); domContents.add(text(command.getDescription())); domContents.add(text(" ")); } diff --git a/src/main/java/com/botdarr/commands/BaseCommand.java b/src/main/java/com/botdarr/commands/BaseCommand.java index b0e9dd0..a748ae7 100644 --- a/src/main/java/com/botdarr/commands/BaseCommand.java +++ b/src/main/java/com/botdarr/commands/BaseCommand.java @@ -1,16 +1,19 @@ package com.botdarr.commands; -import com.google.common.base.Strings; + +import java.util.Collections; +import java.util.List; public abstract class BaseCommand implements Command { public BaseCommand(String commandText, String description) { - this(commandText, "", description); + this(commandText, description, Collections.emptyList()); } - public BaseCommand(String commandText, String usageText, String description) { + public BaseCommand(String commandText, String description, List input) { this.commandText = commandText; this.description = description; - this.usageText = usageText; + this.usageText = commandText + (input != null && !input.isEmpty() ? " " + String.join(" ", input) : ""); + this.input = input; } @Override @@ -30,10 +33,16 @@ public String getCommandText() { @Override public String getCommandUsage() { - return Strings.isNullOrEmpty(usageText) ? getCommandText() : usageText; + return this.usageText; + } + + @Override + public List getInput() { + return input; } private final String description; private final String commandText; private final String usageText; + private final List input; } diff --git a/src/main/java/com/botdarr/commands/Command.java b/src/main/java/com/botdarr/commands/Command.java index eaa4ace..1cbbe6f 100644 --- a/src/main/java/com/botdarr/commands/Command.java +++ b/src/main/java/com/botdarr/commands/Command.java @@ -8,6 +8,7 @@ public interface Command { String getCommandText(); String getDescription(); String getIdentifier(); + List getInput(); default String getCommandUsage() { return ""; } diff --git a/src/main/java/com/botdarr/commands/CommandContext.java b/src/main/java/com/botdarr/commands/CommandContext.java index 159d91a..128cc5e 100644 --- a/src/main/java/com/botdarr/commands/CommandContext.java +++ b/src/main/java/com/botdarr/commands/CommandContext.java @@ -1,5 +1,8 @@ package com.botdarr.commands; +import com.botdarr.Config; +import org.apache.logging.log4j.util.Strings; + public class CommandContext { public static CommandContextConfig getConfig() { if (contextConfigThreadLocal == null) { @@ -25,11 +28,32 @@ public String getUsername() { return this.username; } + /** + * @return The command prefix. The prefix can change under various scenarios: + * 1. If the entry point has a hardcoded prefix (i.e., slash commands) + * 2. If there is no hardcoded prefix, we just use whatever is configured + */ + public String getPrefix() { + if (!Strings.isEmpty(this.prefix)) { + return this.prefix; + } + String configuredPrefix = Config.getProperty(Config.Constants.COMMAND_PREFIX); + if (!Strings.isEmpty(configuredPrefix)) { + return configuredPrefix; + } + return "!"; + } + public CommandContextConfig setUsername(String username) { this.username = username; return this; } + public CommandContextConfig setPrefix(String prefix) { + this.prefix = prefix; + return this; + } private String username; + private String prefix; } private static ThreadLocal contextConfigThreadLocal; } diff --git a/src/main/java/com/botdarr/commands/CommandProcessor.java b/src/main/java/com/botdarr/commands/CommandProcessor.java index ba2031d..6738e91 100644 --- a/src/main/java/com/botdarr/commands/CommandProcessor.java +++ b/src/main/java/com/botdarr/commands/CommandProcessor.java @@ -1,58 +1,42 @@ package com.botdarr.commands; -import com.botdarr.Config; import com.botdarr.clients.ChatClientResponse; import com.botdarr.commands.responses.CommandResponse; import com.botdarr.commands.responses.ErrorResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.util.Strings; import java.util.Collections; import java.util.List; public class CommandProcessor { - public List processRequestMessage(List apiCommands, - String strippedMessage, - String username) { + public List processCommand(String commandPrefix, + List apiCommands, + String strippedMessage, + String username) { try { String rawMessage = strippedMessage.toLowerCase(); - String commandPrefix = getPrefix(); + final String processedCommand; if (rawMessage.startsWith(commandPrefix)) { //remove the prefix character from the message - String rawMessageWithoutPrefix = rawMessage.trim().substring(1); - for (Command apiCommand : apiCommands) { - String command = apiCommand.getIdentifier().toLowerCase(); - boolean foundCommand = apiCommand.hasArguments() ? rawMessageWithoutPrefix.startsWith(command) : rawMessageWithoutPrefix.equalsIgnoreCase(command); - if (foundCommand) { - String commandOperation = rawMessageWithoutPrefix.replaceAll(command, ""); - try { - CommandContext - .start() - .setUsername(username); - return apiCommand.execute(commandOperation.trim()); - } finally { - CommandContext.end(); - } - } + processedCommand = rawMessage.trim().substring(1); + } else { + processedCommand = rawMessage; + } + for (Command apiCommand : apiCommands) { + String command = apiCommand.getIdentifier().toLowerCase(); + boolean foundCommand = apiCommand.hasArguments() ? processedCommand.startsWith(command) : processedCommand.equalsIgnoreCase(command); + if (foundCommand) { + String commandOperation = processedCommand.replaceAll(command, ""); + return apiCommand.execute(commandOperation.trim()); } - return Collections.singletonList(new ErrorResponse("Invalid command - type " + commandPrefix + "help for command usage")); } - + return Collections.singletonList(new ErrorResponse("Invalid command - type " + commandPrefix + "help for command usage")); } catch (Throwable e) { LOGGER.error("Error trying to execute command " + strippedMessage, e); return Collections.singletonList(new ErrorResponse("Error trying to parse command " + strippedMessage + ", error=" + e.getMessage())); } - return null; - } - - public String getPrefix() { - String configuredPrefix = Config.getProperty(Config.Constants.COMMAND_PREFIX); - if (!Strings.isEmpty(configuredPrefix)) { - return configuredPrefix; - } - return "!"; } private static final Logger LOGGER = LogManager.getLogger(CommandProcessor.class); diff --git a/src/main/java/com/botdarr/commands/HelpCommands.java b/src/main/java/com/botdarr/commands/HelpCommands.java index e8a45bc..168a89f 100644 --- a/src/main/java/com/botdarr/commands/HelpCommands.java +++ b/src/main/java/com/botdarr/commands/HelpCommands.java @@ -11,25 +11,25 @@ public static List getCommands(List radarrCommands, List sonarrCommands, List lidarrCommands) { return new ArrayList() {{ - add(new BaseHelpCommand("help", "") { + add(new BaseHelpCommand("help", "Shows all the help commands") { @Override public List execute(String command) { return Collections.singletonList(new HelpResponse()); } }); - add(new BaseHelpCommand("movies help", "") { + add(new BaseHelpCommand("movies help", "Shows all the movie commands") { @Override public List execute(String command) { return Collections.singletonList(new MoviesHelpResponse(radarrCommands)); } }); - add(new BaseHelpCommand("shows help", "") { + add(new BaseHelpCommand("shows help", "Shows all the show commands") { @Override public List execute(String command) { return Collections.singletonList(new ShowsHelpResponse(sonarrCommands)); } }); - add(new BaseHelpCommand("music help", "") { + add(new BaseHelpCommand("music help", "Shows all the music commands") { @Override public List execute(String command) { return Collections.singletonList(new MusicHelpResponse(lidarrCommands)); diff --git a/src/main/resources/version.txt b/src/main/resources/version.txt index 229793a..1e20ec3 100644 --- a/src/main/resources/version.txt +++ b/src/main/resources/version.txt @@ -1 +1 @@ -5.3.5 \ No newline at end of file +5.4.0 \ No newline at end of file diff --git a/src/test/java/com/botdarr/ConfigTests.java b/src/test/java/com/botdarr/ConfigTests.java index 280ff80..ce1bca3 100644 --- a/src/test/java/com/botdarr/ConfigTests.java +++ b/src/test/java/com/botdarr/ConfigTests.java @@ -112,6 +112,25 @@ public void getConfig_telegramGroupIdsContainNegativeOneHundred() throws Excepti Config.getProperty(""); } + @Test + public void getPrefix_returnsDefaultPrefix() throws Exception { + Properties properties = new Properties(); + properties.put("telegram-token", "%H$$54j45i"); + properties.put("telegram-private-groups", "group1:100459349"); + writeFakePropertiesFile(properties); + Assert.assertEquals("!", Config.getPrefix()); + } + + @Test + public void getPrefix_returnsConfiguredPrefix() throws Exception { + Properties properties = new Properties(); + properties.put("telegram-token", "%H$$54j45i"); + properties.put("telegram-private-groups", "group1:100459349"); + properties.put("command-prefix", "$"); + writeFakePropertiesFile(properties); + Assert.assertEquals("$", Config.getPrefix()); + } + private void writeFakePropertiesFile(Properties properties) throws Exception { File propertiesFile = new File(temporaryFolder.getRoot(), "properties"); Deencapsulation.setField(Config.class, "propertiesPath", propertiesFile.getPath()); diff --git a/src/test/java/com/botdarr/clients/discord/DiscordBootstrapTests.java b/src/test/java/com/botdarr/clients/discord/DiscordBootstrapTests.java new file mode 100644 index 0000000..ae2ccf9 --- /dev/null +++ b/src/test/java/com/botdarr/clients/discord/DiscordBootstrapTests.java @@ -0,0 +1,156 @@ +package com.botdarr.clients.discord; + +import com.botdarr.Config; +import com.botdarr.commands.Command; +import mockit.Deencapsulation; +import mockit.Expectations; +import mockit.Mocked; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Properties; + +public class DiscordBootstrapTests { + @Before + public void beforeEachTest() { + writeFakePropertiesFile(getDefaultProperties()); + } + + @Test + public void convertCommandToCommandData_commandDescriptionGreaterThan100Characters() { + String longDescription = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffxd"; + new Expectations() {{ + mockedCommand.getDescription(); result = longDescription; + mockedCommand.getCommandText(); result = "command1"; + mockedCommand.getInput(); result = Collections.emptyList(); + }}; + CommandData commandData = new DiscordBootstrap().convertCommandToCommandData(mockedCommand); + Assert.assertEquals(longDescription.substring(0, 97) + "...", commandData.getDescription()); + } + + @Test + public void convertCommandToCommandData_commandDescriptionLessThan100Characters() { + String longDescription = "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + new Expectations() {{ + mockedCommand.getDescription(); result = longDescription; + mockedCommand.getCommandText(); result = "command1"; + mockedCommand.getInput(); result = Collections.emptyList(); + }}; + CommandData commandData = new DiscordBootstrap().convertCommandToCommandData(mockedCommand); + Assert.assertEquals(longDescription, commandData.getDescription()); + } + + @Test + public void convertCommandToCommandData_commandInputConvertedToSlashCommandFormat() { + String description = "description"; + new Expectations() {{ + mockedCommand.getDescription(); result = description; + mockedCommand.getCommandText(); result = "command input1 input2"; + mockedCommand.getInput(); result = Collections.emptyList(); + }}; + CommandData commandData = new DiscordBootstrap().convertCommandToCommandData(mockedCommand); + Assert.assertEquals(description, commandData.getDescription()); + Assert.assertEquals("command-input1-input2", commandData.getName()); + } + + @Test + public void getCommandFromEmbed_returnsNullDueMissingFieldsInEmbed() { + new Expectations() {{ + mockedEmbed.getFields(); result = Collections.emptyList(); + }}; + String command = new DiscordBootstrap().getCommandFromEmbed(mockedEmbed); + Assert.assertNull(command); + } + + @Test + public void getCommandFromEmbed_returnsNullDueFieldNotMatchingExpectedFieldNames() { + new Expectations() {{ + mockedEmbed.getFields(); result = new ArrayList(){{ + add(new MessageEmbed.Field("unknown-field1", "", false)); + }}; + }}; + String command = new DiscordBootstrap().getCommandFromEmbed(mockedEmbed); + Assert.assertNull(command); + } + + @Test + public void getCommandFromEmbed_returnsMovieCommand() { + new Expectations() {{ + mockedEmbed.getFields(); result = new ArrayList(){{ + add(new MessageEmbed.Field("TmdbId", "43234", false)); + }}; + mockedEmbed.getTitle(); result = "MovieTitle1"; + }}; + String command = new DiscordBootstrap().getCommandFromEmbed(mockedEmbed); + Assert.assertEquals("!movie id add MovieTitle1 43234", command); + } + + @Test + public void getCommandFromEmbed_returnsShowCommand() { + new Expectations() {{ + mockedEmbed.getFields(); result = new ArrayList(){{ + add(new MessageEmbed.Field("TvdbId", "43234", false)); + }}; + mockedEmbed.getTitle(); result = "ShowTitle1"; + }}; + String command = new DiscordBootstrap().getCommandFromEmbed(mockedEmbed); + Assert.assertEquals("!show id add ShowTitle1 43234", command); + } + + @Test + public void getCommandFromEmbed_returnsArtistCommand() { + new Expectations() {{ + mockedEmbed.getFields(); result = new ArrayList(){{ + add(new MessageEmbed.Field("ForeignArtistId", "43234", false)); + }}; + mockedEmbed.getTitle(); result = "ArtistTitle1"; + }}; + String command = new DiscordBootstrap().getCommandFromEmbed(mockedEmbed); + Assert.assertEquals("!music artist id add ArtistTitle1 43234", command); + } + + private void writeFakePropertiesFile(Properties properties) { + File propertiesFile = null; + try { + propertiesFile = temporaryFolder.newFile("properties"); + } catch (IOException e) { + throw new RuntimeException(e); + } + Deencapsulation.setField(Config.class, "propertiesPath", propertiesFile.getPath()); + try (FileOutputStream fos = new FileOutputStream(propertiesFile)) { + properties.store(fos, ""); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Properties getDefaultProperties() { + Properties properties = new Properties(); + properties.setProperty("discord-token", "G$K$GK"); + properties.setProperty("discord-channels", "plex-testing2"); + properties.setProperty("radarr-url", "http://localhost:444"); + properties.setProperty("radarr-token", "FSJDkjmf#$Kf3"); + properties.setProperty("radarr-path", "/movies"); + properties.setProperty("radarr-default-profile", "any"); + return properties; + } + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Mocked + private MessageEmbed mockedEmbed; + + @Mocked + private Command mockedCommand; +} diff --git a/src/test/java/com/botdarr/commands/CommandProcessorTests.java b/src/test/java/com/botdarr/commands/CommandProcessorTests.java index 9d6abc8..edbbacb 100644 --- a/src/test/java/com/botdarr/commands/CommandProcessorTests.java +++ b/src/test/java/com/botdarr/commands/CommandProcessorTests.java @@ -1,5 +1,6 @@ package com.botdarr.commands; +import com.botdarr.Config; import com.botdarr.TestCommandResponse; import com.botdarr.api.lidarr.LidarrApi; import com.botdarr.api.lidarr.LidarrCommands; @@ -14,11 +15,16 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Properties; /** * These tests specifically test commands that take no arguments and commands that take arguments @@ -27,6 +33,7 @@ public class CommandProcessorTests { @Before public void beforeEachTest() { mockCommandPrefix("!"); + writeFakePropertiesFile(getDefaultProperties()); } @Test @@ -334,7 +341,7 @@ private void validateInvalidCommandIdentifier(String invalidCommand) { private List validateValidCommand(String validCommand) { CommandProcessor commandProcessor = new CommandProcessor(); List commandResponses = - commandProcessor.processRequestMessage(getCommandsToTest(), validCommand, "user1"); + commandProcessor.processCommand(CommandContext.getConfig().getPrefix(), getCommandsToTest(), validCommand, "user1"); //we are just making sure no error responses are returned since they signify a failure with the command if (commandResponses != null) { for (CommandResponse response: commandResponses) { @@ -348,7 +355,7 @@ private List validateValidCommand(String validCommand) { private void validateInvalidCommand(String invalidCommand, String expectedErrorResponseString) { CommandProcessor commandProcessor = new CommandProcessor(); List commandResponses = - commandProcessor.processRequestMessage(getCommandsToTest(), invalidCommand, "user1"); + commandProcessor.processCommand(CommandContext.getConfig().getPrefix(), getCommandsToTest(), invalidCommand, "user1"); if (commandResponses != null) { Assert.assertEquals(1, commandResponses.size()); CommandResponse commandResponse = commandResponses.get(0); @@ -380,6 +387,35 @@ private List getCommandsToTest() { return commands; } + private void writeFakePropertiesFile(Properties properties) { + File propertiesFile = null; + try { + propertiesFile = temporaryFolder.newFile("properties"); + } catch (IOException e) { + throw new RuntimeException(e); + } + Deencapsulation.setField(Config.class, "propertiesPath", propertiesFile.getPath()); + try (FileOutputStream fos = new FileOutputStream(propertiesFile)) { + properties.store(fos, ""); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Properties getDefaultProperties() { + Properties properties = new Properties(); + properties.setProperty("discord-token", "G$K$GK"); + properties.setProperty("discord-channels", "plex-testing2"); + properties.setProperty("radarr-url", "http://localhost:444"); + properties.setProperty("radarr-token", "FSJDkjmf#$Kf3"); + properties.setProperty("radarr-path", "/movies"); + properties.setProperty("radarr-default-profile", "any"); + return properties; + } + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Injectable private RadarrApi radarrApi;