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 334722c..f934be5 100644 Binary files a/images/oauth2-permissions.png and b/images/oauth2-permissions.png differ 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;