diff --git a/README.md b/README.md index 43f50cd..a0276c2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Summary -Made this simple slack/discord/telegram bot so I could access radarr, sonarr, and lidarr all from a multiple slack/discord/telegram channels without a UI/server. +Made this simple slack/discord/telegram/matrix bot so I could access radarr, sonarr, and lidarr all from a multiple slack/discord/telegram/matrix channels without a UI/server.
@@ -20,6 +20,7 @@ Made this simple slack/discord/telegram bot so I could access radarr, sonarr, an - [x] Discord - [x] Slack - [x] Telegram +- [x] Matrix ## Currently Supported Feature's @@ -34,11 +35,12 @@ Made this simple slack/discord/telegram bot so I could access radarr, sonarr, an - [x] Configurable value for url base for radarr, sonarr, and lidarr - [x] Lookup torrents for movies and force download - [x] (discord/slack only) Thumbs up reaction will add search results -- [x] User requests audited to local database +- [x] User requests audited to local database\ +- [x] Blacklist content by paths from showing up in searches - [ ] Cancel/blacklist existing downloads - [ ] Episode/season search - [ ] Album/song search -- [ ] Run bot in mode where all 3 chat clients work in same process (right now you would need 3 separate processes/containers) +- [ ] Run bot in mode where all 4 chat clients work in same process (right now you would need 4 separate processes/containers) ## Discord Bot Installation @@ -54,6 +56,10 @@ See https://github.com/shayaantx/botdarr/wiki/Install-Slack-Bot See https://github.com/shayaantx/botdarr/wiki/Install-Telegram-Bot +## Matrix bot installation + +See https://github.com/shayaantx/botdarr/wiki/Install-Matrix-Bot + ## Jar installation/Configuration 1. Get latest copy of botdarr botdarr-release.jar @@ -69,26 +75,35 @@ Radarr seems to return a 200 http code, not actually add the movie, and return j the api url with your radarr url base. So MAKE SURE you account for this in your config/setup. ``` # your discord bot token -discord-token= +#discord-token= # the discord channel(s) you want the bot installed on -discord-channels= +#discord-channels= # Your slack bot oauth authentication token -slack-bot-token= +#slack-bot-token= # Your slack user oauth authentication token -slack-user-token= +#slack-user-token= # the slack channel(s) you want the bot installed on -slack-channels= +#slack-channels= # Your telegram bot token -telegram-token= +#telegram-token= # Your actual telegram channels your bot can respond in # this should be a list containing the name and id of the channel, i.e., CHANNEL_NAME:CHANNEL_ID # to get the channel id, right click any post in private channel and copy post link # you should see something like this, https://t.me/c/1408146664/63 # the id is between c// -# example: plex-channel1:id1,plex-channel2:id2 -telegram-private-channels= +# example: channel1:id1,channel2:id2 +#telegram-private-channels= + +# Your matrix bot user +#matrix-username= +# Your matrix bot password +#matrix-password= +# The room your matrix bot will send messages in +#matrix-room= +# The url of your homeserver +#matrix-home-server-url= # your radarr url (i.e., http://SOME-IP:SOME-PORT) radarr-url= @@ -145,9 +160,13 @@ lidarr-url-base= # If you set this to any value less than 0, the bot won't startup #max-results-to-show=20 -# The command prefix (default is /) +# The command prefix (default is !) # Any prefix is allowed (but I haven't tested every single prefix in every client) -command-prefix=/ +command-prefix=! + +# If you want content to NOT appear in searches against your library, you can list blacklisted paths here +# in comma delimited form, and they will be ignored when building up responses +#existing-item-paths-blacklist= ``` 1. Run the jar using java @@ -187,20 +206,18 @@ botdarr: ## Usage -* Type /help in your configured chat client to get information about commands and what is supported +* Type !help in your configured chat client to get information about commands and what is supported * Notifications will appear indicating the current downloads (based on your configuration for max downloads), their status, and their time remaining. -* When you search for content (i.e., /movie title add History of Dumb People) if too many results are returned you will be presented with multiple results. You can either use the thumbs up reaction (in discord or slack) or copy the add command (which will be embedded in the result) into the chat client. - -![](https://raw.githubusercontent.com/shayaantx/botdarr/development/images/search-results.png) -* The success of the bot depends a lot on how diverse your trackers you use in radarr, sonarr, lidarr and your quality profiles. If you have a trackers with little content or very restrictive quality profiles, a lot of content will never actually get added. The bot can't do anything about this. +* When you search for content (i.e., !movie title add History of Dumb People) if too many results are returned you will be presented with multiple results. You can either use the thumbs up reaction (in discord or slack) or copy the add command (which will be embedded in the result) into the chat client. * Example commands: - * /movie title add Lion Fling - * /show title add One Fliece - * /movie find new zombies - * /artist find new Linkin Flarp - * /movie downloads - * /show downloads - * /help - * /shows help - * /movies help + * !movie title add Lion Fling + * !show title add One Fliece + * !movie find new zombies + * !artist find new Linkin Flarp + * !movie downloads + * !show downloads + * !help + * !shows help + * !movies help +* The default command prefix is !. I chose ! because / (original command prefix) is commonly used by many chat clients and has existing functionality with it that leads to some commands not working nicely.
diff --git a/images/search-results.png b/images/search-results.png deleted file mode 100644 index 97e1d63..0000000 Binary files a/images/search-results.png and /dev/null differ diff --git a/sample.properties b/sample.properties index 04fb2fc..232ccff 100644 --- a/sample.properties +++ b/sample.properties @@ -1,24 +1,33 @@ # Your bot token goes here (don't share) -discord-token= +#discord-token= # The actual discord channel(s) the bot lives in -discord-channels= +#discord-channels= # Your slack bot oauth authentication token -slack-bot-token= +#slack-bot-token= # Your slack user oauth authentication token -slack-user-token= +#slack-user-token= # The actual slack channel(s) you want to post slack messages to -slack-channels= +#slack-channels= # Your telegram bot token -telegram-token= +#telegram-token= # Your actual telegram channels your bot can respond in # this should be a list containing the name and id of the channel, i.e., CHANNEL_NAME:CHANNEL_ID # to get the channel id, right click any post in private channel and copy post link # you should see something like this, https://t.me/c/1408146664/63 # the id is between c// # example: plex-channel1:id1,plex-channel2:id2 -telegram-private-channels= +#telegram-private-channels= + +# Your matrix bot user +#matrix-username= +# Your matrix bot password +#matrix-password= +# The room your matrix bot will send messages in +#matrix-room= +# The url of your homeserver +#matrix-home-server-url= # Your various media tool urls/keys go here radarr-url= @@ -67,6 +76,10 @@ max-downloads-to-show=20 # If you set this to any value less than 0, the bot won't startup max-results-to-show=20 -# The command prefix (default is /) +# The command prefix (default is !) # Any prefix is allowed (but I haven't tested every single prefix in every client) -command-prefix=/ \ No newline at end of file +command-prefix=! + +# If you want content to NOT appear in searches against your library, you can list blacklisted paths here +# in comma delimited form, and they will be ignored when building up responses +existing-item-paths-blacklist= \ No newline at end of file diff --git a/src/main/java/com/botdarr/Config.java b/src/main/java/com/botdarr/Config.java index 15cc798..3f3246a 100644 --- a/src/main/java/com/botdarr/Config.java +++ b/src/main/java/com/botdarr/Config.java @@ -7,9 +7,7 @@ import java.io.FileInputStream; import java.io.InputStream; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Properties; +import java.util.*; import java.util.stream.Collectors; public class Config { @@ -84,6 +82,12 @@ private Config() { if (!Strings.isEmpty(configuredPrefix) && configuredPrefix.length() > 1) { throw new RuntimeException("Command prefix must be a single character"); } + if (chatClientType == ChatClientType.SLACK && configuredPrefix.equals("/")) { + throw new RuntimeException("Cannot use / command prefix in slack since /help command was deprecated by slack"); + } + if (chatClientType == ChatClientType.MATRIX && configuredPrefix.equals("/")) { + throw new RuntimeException("Cannot use / command prefix in matrix since /help command is used by element by default"); + } } catch (Exception ex) { LOGGER.error("Error loading properties file", ex); throw new RuntimeException(ex); @@ -110,7 +114,35 @@ public static ChatClientType getChatClientType() { return getConfig().chatClientType; } + public static List getExistingItemBlacklistPaths() { + String paths = getProperty(Constants.EXISTING_ITEMS_PATHS_BLACKLIST); + if (paths != null && paths.contains(",")) { + return Arrays.asList(paths.split(",")); + } + return new ArrayList() {{add(paths);}}; + } + public static final class Constants { + /** + * The matrix bot username + */ + public static final String MATRIX_USERNAME = "matrix-username"; + + /** + * The matrix bot password + */ + public static final String MATRIX_PASSWORD = "matrix-password"; + + /** + * The matrix room for the bot + */ + public static final String MATRIX_ROOM = "matrix-room"; + + /** + * The matrix home server for the bot + */ + public static final String MATRIX_HOME_SERVER = "matrix-home-server-url"; + /** * The telegram auth token */ @@ -267,6 +299,11 @@ public static final class Constants { * The prefix for all commands */ public static final String COMMAND_PREFIX = "command-prefix"; + + /** + * The paths of items to blacklist from searches + */ + public static final String EXISTING_ITEMS_PATHS_BLACKLIST = "existing-item-paths-blacklist"; } private static String propertiesPath = "config/properties"; diff --git a/src/main/java/com/botdarr/api/CacheContentStrategy.java b/src/main/java/com/botdarr/api/CacheContentStrategy.java index 3e49ec3..e60eaaa 100644 --- a/src/main/java/com/botdarr/api/CacheContentStrategy.java +++ b/src/main/java/com/botdarr/api/CacheContentStrategy.java @@ -21,7 +21,7 @@ public CacheContentStrategy(Api api, String url) { public void cacheData() { List itemsAddedUpdated = new ArrayList<>(); - ConnectionHelper.makeGetRequest(this.api, this.url, new ConnectionHelper.SimpleEntityResponseHandler() { + ConnectionHelper.makeGetRequest(this.api, this.url, new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) throws Exception { JsonParser parser = new JsonParser(); diff --git a/src/main/java/com/botdarr/api/LookupStrategy.java b/src/main/java/com/botdarr/api/LookupStrategy.java index 8f28f29..eb69eab 100644 --- a/src/main/java/com/botdarr/api/LookupStrategy.java +++ b/src/main/java/com/botdarr/api/LookupStrategy.java @@ -19,14 +19,22 @@ public LookupStrategy(ChatClientResponseBuilder ch public abstract T lookupExistingItem(T lookupItem); public abstract List lookup(String searchTerm) throws Exception; public abstract ChatClientResponse getNewOrExistingItem(T lookupItem, T existingItem, boolean findNew); + public abstract boolean isPathBlacklisted(T item); public List lookup(String search, boolean findNew) { try { List responses = new ArrayList<>(); List lookupItems = lookup(search); + if (lookupItems == null) { + return Arrays.asList(chatClientResponseBuilder.createErrorMessage("Something failed during lookup for search term=" + search)); + } for (T lookupItem : lookupItems) { T existingItem = lookupExistingItem(lookupItem); boolean isExistingItem = existingItem != null; + if (isExistingItem && isPathBlacklisted(existingItem)) { + //skip any items that have blacklisted paths + continue; + } boolean skip = findNew ? isExistingItem : !isExistingItem; if (skip) { continue; diff --git a/src/main/java/com/botdarr/api/lidarr/LidarrApi.java b/src/main/java/com/botdarr/api/lidarr/LidarrApi.java index cd51252..9d5eb96 100644 --- a/src/main/java/com/botdarr/api/lidarr/LidarrApi.java +++ b/src/main/java/com/botdarr/api/lidarr/LidarrApi.java @@ -2,6 +2,7 @@ import com.botdarr.Config; import com.botdarr.api.*; +import com.botdarr.api.sonarr.SonarrShow; import com.botdarr.api.sonarr.SonarrUrls; import com.botdarr.clients.ChatClient; import com.botdarr.clients.ChatClientResponse; @@ -65,7 +66,7 @@ public void deleteFromCache(List profilesAddUpdated) { @Override public List getProfiles() { - return ConnectionHelper.makeGetRequest(LidarrApi.this, LidarrUrls.PROFILE, new ConnectionHelper.SimpleEntityResponseHandler() { + return ConnectionHelper.makeGetRequest(LidarrApi.this, LidarrUrls.PROFILE, new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) { List lidarrQualityProfiles = new ArrayList<>(); @@ -95,7 +96,7 @@ public void deleteFromCache(List profilesAddUpdated) { @Override public List getProfiles() { - return ConnectionHelper.makeGetRequest(LidarrApi.this, LidarrUrls.METADATA_PROFILE, new ConnectionHelper.SimpleEntityResponseHandler() { + return ConnectionHelper.makeGetRequest(LidarrApi.this, LidarrUrls.METADATA_PROFILE, new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) { List lidarrMetadataProfiles = new ArrayList<>(); @@ -164,6 +165,16 @@ public List lookup(String searchTerm) throws Exception { public ChatClientResponse getNewOrExistingItem(LidarrArtist lookupItem, LidarrArtist existingItem, boolean findNew) { return chatClientResponseBuilder.getNewOrExistingArtist(lookupItem, existingItem, findNew); } + + @Override + public boolean isPathBlacklisted(LidarrArtist item) { + for (String path : Config.getExistingItemBlacklistPaths()) { + if (item.getPath() != null && item.getPath().startsWith(path)) { + return true; + } + } + return false; + } }.lookup(search, findNew); } @@ -226,7 +237,7 @@ public List onSuccess(String response) { private List lookupArtists(String search) throws Exception { return ConnectionHelper.makeGetRequest(this, LidarrUrls.LOOKUP_ARTISTS, "&term=" + URLEncoder.encode(search, "UTF-8"), - new ConnectionHelper.SimpleEntityResponseHandler() { + new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) { List artists = new ArrayList<>(); diff --git a/src/main/java/com/botdarr/api/radarr/RadarrApi.java b/src/main/java/com/botdarr/api/radarr/RadarrApi.java index 68df357..b90feb7 100644 --- a/src/main/java/com/botdarr/api/radarr/RadarrApi.java +++ b/src/main/java/com/botdarr/api/radarr/RadarrApi.java @@ -53,6 +53,16 @@ public List lookup(String searchTerm) throws Exception { public ChatClientResponse getNewOrExistingItem(RadarrMovie lookupItem, RadarrMovie existingItem, boolean findNew) { return chatClientResponseBuilder.getNewOrExistingMovie(lookupItem, existingItem, findNew); } + + @Override + public boolean isPathBlacklisted(RadarrMovie item) { + for (String path : Config.getExistingItemBlacklistPaths()) { + if (item.getPath() != null && item.getPath().startsWith(path)) { + return true; + } + } + return false; + } }.lookup(search, findNew); } @@ -82,74 +92,6 @@ public List getProfiles() { return profileMessages; } - public List forceDownload(String command) { - String decodedKey = new String(Base64.getDecoder().decode(command.getBytes())); - //the hash format is guid:title - //title couldn't contain : so we find the first occurrence - int titleIndex = decodedKey.indexOf("title="); - String[] decodedKeyArray = {decodedKey.substring(0, titleIndex - 1), decodedKey.substring(titleIndex + 6)}; - if (decodedKeyArray.length != 2) { - return Arrays.asList(chatClientResponseBuilder.createErrorMessage("Invalid key=" + decodedKey)); - } - - String guid = decodedKeyArray[0]; - String title = decodedKeyArray[1]; - List radarrTorrents = lookupTorrents(title); - - if (radarrTorrents.isEmpty()) { - return Arrays.asList(chatClientResponseBuilder.createErrorMessage("Found no movies to force download, title=" + title)); - } - - for (RadarrTorrent radarrTorrent : radarrTorrents) { - if (radarrTorrent.getGuid().equalsIgnoreCase(guid)) { - return ConnectionHelper.makePostRequest(this, RadarrUrls.RELEASE_BASE, radarrTorrent, new ConnectionHelper.SimpleMessageEmbedResponseHandler(chatClientResponseBuilder) { - @Override - public List onSuccess(String response) throws Exception { - return Arrays.asList(chatClientResponseBuilder.createSuccessMessage("Forced the download for " + title)); - } - }); - } - } - return Arrays.asList(chatClientResponseBuilder.createErrorMessage("Could not force download movie for title=" + title + ", guid=" + guid)); - } - - public List lookupTorrents(String movieTitle, boolean showRejected) { - List radarrTorrents = lookupTorrents(movieTitle); - if (radarrTorrents.isEmpty()) { - return Arrays.asList(chatClientResponseBuilder.createErrorMessage("No downloads available for " + movieTitle + ", make sure you have exact film name.")); - } - - List responses = new ArrayList<>(); - for (RadarrTorrent radarrTorrent : radarrTorrents) { - if (!showRejected && radarrTorrent.isRejected()) { - //dont show rejected torrents - continue; - } - responses.add(chatClientResponseBuilder.getTorrentResponses(radarrTorrent, movieTitle)); - } - - if (responses.isEmpty()) { - responses.add(chatClientResponseBuilder.createErrorMessage("Torrents were found but all of them were rejected based on your profiles/indexer settings for movie " + movieTitle)); - } - - return responses; - } - - public List cancelDownload(String command) { - try { - Long id = Long.valueOf(command); - return ConnectionHelper.makeDeleteRequest(this, RadarrUrls.DOWNLOAD_BASE + "/" + id, "&blacklist=true", new ConnectionHelper.SimpleMessageEmbedResponseHandler(chatClientResponseBuilder) { - @Override - public List onSuccess(String response) throws Exception { - //TODO: implement - return Arrays.asList(chatClientResponseBuilder.createErrorMessage("Not implemented yet")); - } - }); - } catch (NumberFormatException e) { - return Arrays.asList(chatClientResponseBuilder.createErrorMessage("Require an id value to cancel a download, e=" + e.getMessage())); - } - } - @Override public void sendPeriodicNotifications(ChatClient chatClient) { new PeriodicNotificationStrategy(ContentType.MOVIE, getDownloadsStrategy()) { @@ -170,7 +112,7 @@ public void deleteFromCache(List profilesAddUpdated) { @Override public List getProfiles() { - return ConnectionHelper.makeGetRequest(RadarrApi.this, RadarrUrls.PROFILE_BASE, new ConnectionHelper.SimpleEntityResponseHandler() { + return ConnectionHelper.makeGetRequest(RadarrApi.this, RadarrUrls.PROFILE_BASE, new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) { List radarrProfiles = new ArrayList<>(); @@ -212,7 +154,7 @@ public String getApiToken() { } public List discover() { - return ConnectionHelper.makeGetRequest(this, RadarrUrls.DISCOVER_MOVIES, new ConnectionHelper.SimpleEntityResponseHandler() { + return ConnectionHelper.makeGetRequest(this, RadarrUrls.DISCOVER_MOVIES, new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) throws Exception { List recommendedMovies = new ArrayList<>(); @@ -334,34 +276,9 @@ private ChatClientResponse addMovie(RadarrMovie radarrMovie) { } } - private List lookupTorrents(String title) { - Long id = RADARR_CACHE.getMovieSonarrId(title); - if (id == null) { - LOGGER.warn("Could not find title id for title " + title); - return Collections.emptyList(); - } - return ConnectionHelper.makeGetRequest(this, RadarrUrls.RELEASE_BASE, "&movieId=" + id + "&sort_by=releaseWeight&order=asc", new ConnectionHelper.SimpleEntityResponseHandler() { - @Override - public List onSuccess(String response) throws Exception { - List radarrTorrents = new ArrayList<>(); - if (response == null || response.isEmpty() || response.equalsIgnoreCase("[]")) { - LOGGER.warn("Found no response when looking for radarr torrents"); - return Collections.emptyList(); - } - JsonParser parser = new JsonParser(); - JsonArray json = parser.parse(response).getAsJsonArray(); - - for (int i = 0; i < json.size(); i++) { - radarrTorrents.add(new Gson().fromJson(json.get(i), RadarrTorrent.class)); - } - return radarrTorrents; - } - }); - } - private List lookupMovies(String search) throws Exception { return ConnectionHelper.makeGetRequest(this, RadarrUrls.MOVIE_LOOKUP, "&term=" + URLEncoder.encode(search, "UTF-8"), - new ConnectionHelper.SimpleEntityResponseHandler() { + new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) { List movies = new ArrayList<>(); @@ -377,7 +294,7 @@ public List onSuccess(String response) { private List lookupMoviesById(String tmdbid) throws Exception { return ConnectionHelper.makeGetRequest(this, RadarrUrls.MOVIE_LOOKUP_TMDB, "&tmdbId=" + URLEncoder.encode(tmdbid, "UTF-8"), - new ConnectionHelper.SimpleEntityResponseHandler() { + new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) { List movies = new ArrayList<>(); diff --git a/src/main/java/com/botdarr/api/radarr/RadarrTorrent.java b/src/main/java/com/botdarr/api/radarr/RadarrTorrent.java deleted file mode 100644 index 2e9592b..0000000 --- a/src/main/java/com/botdarr/api/radarr/RadarrTorrent.java +++ /dev/null @@ -1,364 +0,0 @@ -package com.botdarr.api.radarr; - -public class RadarrTorrent { - public String getGuid() { - return guid; - } - - public void setGuid(String guid) { - this.guid = guid; - } - - public RadarrProfileQualityItem getQuality() { - return quality; - } - - public void setQuality(RadarrProfileQualityItem quality) { - this.quality = quality; - } - - public String getIndexer() { - return indexer; - } - - public void setIndexer(String indexer) { - this.indexer = indexer; - } - - public boolean isRejected() { - return rejected; - } - - public void setRejected(boolean rejected) { - this.rejected = rejected; - } - - public String getDownloadUrl() { - return downloadUrl; - } - - public void setDownloadUrl(String downloadUrl) { - this.downloadUrl = downloadUrl; - } - - public int getSeeders() { - return seeders; - } - - public void setSeeders(int seeders) { - this.seeders = seeders; - } - - public int getLeechers() { - return leechers; - } - - public void setLeechers(int leechers) { - this.leechers = leechers; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String[] getRejections() { - return rejections; - } - - public void setRejections(String[] rejections) { - this.rejections = rejections; - } - - public long getAge() { - return age; - } - - public void setAge(long age) { - this.age = age; - } - - public double getAgeHours() { - return ageHours; - } - - public void setAgeHours(double ageHours) { - this.ageHours = ageHours; - } - - public double getAgeMinutes() { - return ageMinutes; - } - - public void setAgeMinutes(double ageMinutes) { - this.ageMinutes = ageMinutes; - } - - public boolean isApproved() { - return approved; - } - - public void setApproved(boolean approved) { - this.approved = approved; - } - - public String getCommentUrl() { - return commentUrl; - } - - public void setCommentUrl(String commentUrl) { - this.commentUrl = commentUrl; - } - - public String getEdition() { - return edition; - } - - public void setEdition(String edition) { - this.edition = edition; - } - - public boolean isFullSeason() { - return fullSeason; - } - - public void setFullSeason(boolean fullSeason) { - this.fullSeason = fullSeason; - } - - public String[] getIndexerFlags() { - return indexerFlags; - } - - public void setIndexerFlags(String[] indexerFlags) { - this.indexerFlags = indexerFlags; - } - - public int getIndexerId() { - return indexerId; - } - - public void setIndexerId(int indexerId) { - this.indexerId = indexerId; - } - - public String getInfoHash() { - return infoHash; - } - - public void setInfoHash(String infoHash) { - this.infoHash = infoHash; - } - - public String getInfoUrl() { - return infoUrl; - } - - public void setInfoUrl(String infoUrl) { - this.infoUrl = infoUrl; - } - - public boolean isAbsoluteNumbering() { - return isAbsoluteNumbering; - } - - public void setAbsoluteNumbering(boolean absoluteNumbering) { - isAbsoluteNumbering = absoluteNumbering; - } - - public boolean isDaily() { - return isDaily; - } - - public void setDaily(boolean daily) { - isDaily = daily; - } - - public boolean isPossibleSpecialEpisode() { - return isPossibleSpecialEpisode; - } - - public void setPossibleSpecialEpisode(boolean possibleSpecialEpisode) { - isPossibleSpecialEpisode = possibleSpecialEpisode; - } - - public String[] getLanguages() { - return languages; - } - - public void setLanguages(String[] languages) { - this.languages = languages; - } - - public String getMagnetUrl() { - return magnetUrl; - } - - public void setMagnetUrl(String magnetUrl) { - this.magnetUrl = magnetUrl; - } - - public String getMappingResult() { - return mappingResult; - } - - public void setMappingResult(String mappingResult) { - this.mappingResult = mappingResult; - } - - public String getMovieTitle() { - return movieTitle; - } - - public void setMovieTitle(String movieTitle) { - this.movieTitle = movieTitle; - } - - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } - - public String getPublishDate() { - return publishDate; - } - - public void setPublishDate(String publishDate) { - this.publishDate = publishDate; - } - - public int getQualityWeight() { - return qualityWeight; - } - - public void setQualityWeight(int qualityWeight) { - this.qualityWeight = qualityWeight; - } - - public String getReleaseGroup() { - return releaseGroup; - } - - public void setReleaseGroup(String releaseGroup) { - this.releaseGroup = releaseGroup; - } - - public int getReleaseWeight() { - return releaseWeight; - } - - public void setReleaseWeight(int releaseWeight) { - this.releaseWeight = releaseWeight; - } - - public int getSeasonNumber() { - return seasonNumber; - } - - public void setSeasonNumber(int seasonNumber) { - this.seasonNumber = seasonNumber; - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - public boolean isSpecial() { - return special; - } - - public void setSpecial(boolean special) { - this.special = special; - } - - public long getSuspectedMovieId() { - return suspectedMovieId; - } - - public void setSuspectedMovieId(long suspectedMovieId) { - this.suspectedMovieId = suspectedMovieId; - } - - public boolean isTemporarilyRejected() { - return temporarilyRejected; - } - - public void setTemporarilyRejected(boolean temporarilyRejected) { - this.temporarilyRejected = temporarilyRejected; - } - - public int getTvRageId() { - return tvRageId; - } - - public void setTvRageId(int tvRageId) { - this.tvRageId = tvRageId; - } - - public long getTvdbId() { - return tvdbId; - } - - public void setTvdbId(long tvdbId) { - this.tvdbId = tvdbId; - } - - public int getYear() { - return year; - } - - public void setYear(int year) { - this.year = year; - } - - private String[] rejections; - private String guid; - private RadarrProfileQualityItem quality; - private String indexer; - private boolean rejected; - private String downloadUrl; - private int seeders; - private int leechers; - private String title; - private long age; - private double ageHours; - private double ageMinutes; - private boolean approved; - private String commentUrl; - private String edition; - private boolean fullSeason; - private String[] indexerFlags; - private int indexerId; - private String infoHash; - private String infoUrl; - private boolean isAbsoluteNumbering; - private boolean isDaily; - private boolean isPossibleSpecialEpisode; - private String[] languages; - private String magnetUrl; - private String mappingResult; - private String movieTitle; - private String protocol; - private String publishDate; - private int qualityWeight; - private String releaseGroup; - private int releaseWeight; - private int seasonNumber; - private long size; - private boolean special; - private long suspectedMovieId; - private boolean temporarilyRejected; - private int tvRageId; - private long tvdbId; - private int year; -} diff --git a/src/main/java/com/botdarr/api/sonarr/SonarrApi.java b/src/main/java/com/botdarr/api/sonarr/SonarrApi.java index 25daec6..7555756 100644 --- a/src/main/java/com/botdarr/api/sonarr/SonarrApi.java +++ b/src/main/java/com/botdarr/api/sonarr/SonarrApi.java @@ -2,6 +2,7 @@ import com.botdarr.Config; import com.botdarr.api.*; +import com.botdarr.api.radarr.RadarrMovie; import com.botdarr.clients.ChatClient; import com.botdarr.clients.ChatClientResponse; import com.botdarr.clients.ChatClientResponseBuilder; @@ -64,17 +65,17 @@ public List lookup(String searchTerm) throws Exception { public ChatClientResponse getNewOrExistingItem(SonarrShow lookupItem, SonarrShow existingItem, boolean findNew) { return chatClientResponseBuilder.getNewOrExistingShow(lookupItem, existingItem, findNew); } - }.lookup(search, findNew); - } - - public List lookupTorrents(String command, boolean showRejected) { - //TODO: implement - return null; - } - public List cancelDownload(long id) { - //TODO: implement - return null; + @Override + public boolean isPathBlacklisted(SonarrShow item) { + for (String path : Config.getExistingItemBlacklistPaths()) { + if (item.getPath() != null && item.getPath().startsWith(path)) { + return true; + } + } + return false; + } + }.lookup(search, findNew); } public List getProfiles() { @@ -90,11 +91,6 @@ public List getProfiles() { return profileMessages; } - public List forceDownload(String command) { - //TODO: implement - return null; - } - @Override public void sendPeriodicNotifications(ChatClient chatClient) { new PeriodicNotificationStrategy(ContentType.SHOW, getDownloadsStrategy()) { @@ -115,7 +111,7 @@ public void deleteFromCache(List profilesAddUpdated) { @Override public List getProfiles() { - return ConnectionHelper.makeGetRequest(SonarrApi.this, SonarrUrls.PROFILE, new ConnectionHelper.SimpleEntityResponseHandler() { + return ConnectionHelper.makeGetRequest(SonarrApi.this, SonarrUrls.PROFILE, new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) { List sonarrProfiles = new ArrayList<>(); @@ -253,7 +249,7 @@ private ChatClientResponse addShow(SonarrShow sonarrShow) { } private List lookupShows(String search) throws Exception { - return ConnectionHelper.makeGetRequest(this, SonarrUrls.LOOKUP_SERIES, "&term=" + URLEncoder.encode(search, "UTF-8"), new ConnectionHelper.SimpleEntityResponseHandler() { + return ConnectionHelper.makeGetRequest(this, SonarrUrls.LOOKUP_SERIES, "&term=" + URLEncoder.encode(search, "UTF-8"), new ConnectionHelper.SimpleEntityResponseHandler>() { @Override public List onSuccess(String response) { List movies = new ArrayList<>(); diff --git a/src/main/java/com/botdarr/clients/ChatClientResponseBuilder.java b/src/main/java/com/botdarr/clients/ChatClientResponseBuilder.java index e74f19b..46400ea 100644 --- a/src/main/java/com/botdarr/clients/ChatClientResponseBuilder.java +++ b/src/main/java/com/botdarr/clients/ChatClientResponseBuilder.java @@ -1,12 +1,10 @@ package com.botdarr.clients; import com.botdarr.api.lidarr.LidarrArtist; -import com.botdarr.api.lidarr.LidarrQueue; import com.botdarr.api.lidarr.LidarrQueueRecord; import com.botdarr.api.radarr.RadarrMovie; import com.botdarr.api.radarr.RadarrProfile; import com.botdarr.api.radarr.RadarrQueue; -import com.botdarr.api.radarr.RadarrTorrent; import com.botdarr.api.sonarr.SonarrProfile; import com.botdarr.api.sonarr.SonarrQueue; import com.botdarr.api.sonarr.SonarrShow; @@ -34,7 +32,6 @@ public interface ChatClientResponseBuilder { T createErrorMessage(String message); T createInfoMessage(String message); T createSuccessMessage(String message); - T getTorrentResponses(RadarrTorrent radarrTorrent, String movieTitle); T getShowProfile(SonarrProfile sonarrProfile); T getMovieProfile(RadarrProfile radarrProfile); T getNewOrExistingShow(SonarrShow sonarrShow, SonarrShow existingShow, boolean findNew); diff --git a/src/main/java/com/botdarr/clients/ChatClientType.java b/src/main/java/com/botdarr/clients/ChatClientType.java index f508172..1bb66d4 100644 --- a/src/main/java/com/botdarr/clients/ChatClientType.java +++ b/src/main/java/com/botdarr/clients/ChatClientType.java @@ -5,6 +5,9 @@ import com.botdarr.api.lidarr.LidarrApi; import com.botdarr.api.radarr.RadarrApi; import com.botdarr.api.sonarr.SonarrApi; +import com.botdarr.clients.matrix.MatrixChatClient; +import com.botdarr.clients.matrix.MatrixResponse; +import com.botdarr.clients.matrix.MatrixResponseBuilder; import com.botdarr.commands.*; import com.botdarr.clients.discord.DiscordChatClient; import com.botdarr.clients.discord.DiscordResponse; @@ -43,12 +46,47 @@ import javax.annotation.Nonnull; import java.util.*; +import java.util.concurrent.Callable; import static com.botdarr.api.lidarr.LidarrApi.ADD_ARTIST_COMMAND_FIELD_PREFIX; import static com.botdarr.api.radarr.RadarrApi.ADD_MOVIE_COMMAND_FIELD_PREFIX; import static com.botdarr.api.sonarr.SonarrApi.ADD_SHOW_COMMAND_FIELD_PREFIX; public enum ChatClientType { + MATRIX() { + @Override + public void init() throws Exception { + MatrixChatClient chatClient = new MatrixChatClient(); + ChatClientResponseBuilder responseChatClientResponseBuilder = new MatrixResponseBuilder(); + ApisAndCommandConfig config = buildConfig(responseChatClientResponseBuilder); + initScheduling(chatClient, config.apis); + chatClient.addListener((roomId, sender, content) -> { + Scheduler.getScheduler().executeCommand(() -> { + CommandResponse commandResponse = + commandProcessor.processMessage(config.commands, content, sender, responseChatClientResponseBuilder); + if (commandResponse != null) { + chatClient.sendMessage(commandResponse, roomId); + } + return null; + }); + }); + chatClient.listen(); + } + + @Override + public boolean isConfigured(Properties properties) { + return + !Strings.isBlank(properties.getProperty(Config.Constants.MATRIX_USERNAME)) && + !Strings.isBlank(properties.getProperty(Config.Constants.MATRIX_PASSWORD)) && + !Strings.isBlank(properties.getProperty(Config.Constants.MATRIX_ROOM)) && + !Strings.isBlank(properties.getProperty(Config.Constants.MATRIX_HOME_SERVER)); + } + + @Override + public String getReadableName() { + return "Matrix"; + } + }, TELEGRAM() { @Override public void init() throws Exception { @@ -66,11 +104,14 @@ public void init() throws Exception { //TODO: the telegram api doesn't seem return "from" field in channel posts for some reason //for now we leave the author as "telegram" till a better solution arises String author = "telegram"; - CommandResponse commandResponse = - commandProcessor.processMessage(config.commands, text, author, responseChatClientResponseBuilder); - if (commandResponse != null) { - telegramChatClient.sendMessage(commandResponse, message.chat()); - } + Scheduler.getScheduler().executeCommand(() -> { + CommandResponse commandResponse = + commandProcessor.processMessage(config.commands, text, author, responseChatClientResponseBuilder); + if (commandResponse != null) { + telegramChatClient.sendMessage(commandResponse, message.chat()); + } + return null; + }); } } } catch (Throwable t) { @@ -108,7 +149,7 @@ public void onGenericEvent(@Nonnull GenericEvent event) { @Override public void onReady(@Nonnull ReadyEvent event) { - LogManager.getLogger("DiscordLog").info("Connected to discord"); + LogManager.getLogger("com.botdarr.clients.discord").info("Connected to discord"); ChatClient chatClient = new DiscordChatClient(event.getJDA()); //start the scheduler threads that send notifications and cache data periodically initScheduling(chatClient, config.apis); @@ -143,7 +184,7 @@ 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("DiscordLog").debug(event.getMessage().getContentRaw()); + LogManager.getLogger("com.botdarr.clients.discord").debug(event.getMessage().getContentRaw()); super.onMessageReceived(event); } @@ -152,22 +193,25 @@ private void handleCommand(JDA jda, String message, String author, String channe DiscordChatClient discordChatClient = new DiscordChatClient(jda); //capture/process command - CommandResponse commandResponse = commandProcessor.processMessage( - config.commands, - message, - author, - responseChatClientResponseBuilder); - if (commandResponse != null) { - //then send the response - discordChatClient.sendMessage(commandResponse, channelName); - } + Scheduler.getScheduler().executeCommand(() -> { + CommandResponse commandResponse = commandProcessor.processMessage( + config.commands, + message, + author, + responseChatClientResponseBuilder); + if (commandResponse != null) { + //then send the response + discordChatClient.sendMessage(commandResponse, channelName); + } + return null; + }); } private static final String THUMBS_UP_EMOTE = "\uD83D\uDC4D"; }).build(); jda.awaitReady(); } catch (Throwable e) { - LogManager.getLogger("DiscordLog").error("Error caught during main", e); + LogManager.getLogger("com.botdarr.clients.discord").error("Error caught during main", e); throw e; } } @@ -232,24 +276,27 @@ public void handle(String message) { } } } catch (Exception e) { - LogManager.getLogger("SlackLog").error("Error fetching conversation history", e); + LogManager.getLogger("com.botdarr.clients.slack").error("Error fetching conversation history", e); } } } - LogManager.getLogger("SlackLog").debug(json); + LogManager.getLogger("com.botdarr.clients.slack").debug(json); } private void handleCommand(String text, String userId, String channel) { - //capture/process the command - CommandResponse commandResponse = commandProcessor.processMessage( - config.commands, - text, - userId, - responseChatClientResponseBuilder); - if (commandResponse != null) { - //then send the response - slackChatClient.sendMessage(commandResponse, channel); - } + Scheduler.getScheduler().executeCommand(() -> { + //capture/process the command + CommandResponse commandResponse = commandProcessor.processMessage( + config.commands, + text, + userId, + responseChatClientResponseBuilder); + if (commandResponse != null) { + //then send the response + slackChatClient.sendMessage(commandResponse, channel); + } + return null; + }); } }); diff --git a/src/main/java/com/botdarr/clients/discord/DiscordResponseBuilder.java b/src/main/java/com/botdarr/clients/discord/DiscordResponseBuilder.java index 262dd82..2c7a75f 100644 --- a/src/main/java/com/botdarr/clients/discord/DiscordResponseBuilder.java +++ b/src/main/java/com/botdarr/clients/discord/DiscordResponseBuilder.java @@ -11,14 +11,12 @@ import com.botdarr.utilities.ListUtils; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed; -import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.util.Strings; import java.awt.*; import java.io.IOException; -import java.util.Base64; import java.util.List; import static com.botdarr.api.lidarr.LidarrApi.ADD_ARTIST_COMMAND_FIELD_PREFIX; @@ -113,7 +111,6 @@ public DiscordResponse getShowDownloadResponses(SonarrQueue showQueue) { } } } - embedBuilder.addField("Cancel download command", "show cancel download " + showQueue.getId(), true); return new DiscordResponse(embedBuilder.build()); } @@ -131,8 +128,6 @@ public DiscordResponse getMovieDownloadResponses(RadarrQueue radarrQueue) { } } } - //TODO: implement - //embedBuilder.addField("Cancel download command", "movie cancel download " + radarrQueue.getId(), true); return new DiscordResponse(embedBuilder.build()); } @@ -152,29 +147,6 @@ public DiscordResponse getArtistDownloadResponses(LidarrQueueRecord lidarrQueueR return new DiscordResponse(embedBuilder.build()); } - @Override - public DiscordResponse getTorrentResponses(RadarrTorrent radarrTorrent, String movieTitle) { - EmbedBuilder embedBuilder = new EmbedBuilder(); - embedBuilder.addField("Title", radarrTorrent.getTitle(), false); - embedBuilder.addField("Torrent", radarrTorrent.getGuid(), false); - embedBuilder.addField("Quality", radarrTorrent.getQuality().getQuality().getName(), true); - embedBuilder.addField("Indexer", radarrTorrent.getIndexer(), true); - embedBuilder.addField("Seeders", "" + radarrTorrent.getSeeders(), true); - embedBuilder.addField("Leechers", "" + radarrTorrent.getLeechers(), true); - embedBuilder.addField("Size", "" + FileUtils.byteCountToDisplaySize(radarrTorrent.getSize()), true); - String[] rejections = radarrTorrent.getRejections(); - if (rejections != null) { - embedBuilder.addBlankField(false); - for (String rejection : rejections) { - embedBuilder.addField("Rejection Reason", rejection, false); - } - } - String key = radarrTorrent.getGuid() + ":title=" + movieTitle; - byte[] encodedBytes = Base64.getEncoder().encode(key.getBytes()); - embedBuilder.addField("Download hash command", "movie hash download " + new String(encodedBytes), true); - return new DiscordResponse(embedBuilder.build()); - } - @Override public DiscordResponse getShowProfile(SonarrProfile sonarrProfile) { EmbedBuilder embedBuilder = new EmbedBuilder(); diff --git a/src/main/java/com/botdarr/clients/matrix/MatrixChatClient.java b/src/main/java/com/botdarr/clients/matrix/MatrixChatClient.java new file mode 100644 index 0000000..124b312 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/MatrixChatClient.java @@ -0,0 +1,318 @@ +package com.botdarr.clients.matrix; + +import com.botdarr.Config; +import com.botdarr.clients.ChatClient; +import com.botdarr.clients.matrix.transactions.*; +import com.botdarr.clients.matrix.transactions.filter.*; +import com.botdarr.clients.matrix.transactions.sync.MatrixSyncEvent; +import com.botdarr.clients.matrix.transactions.sync.MatrixSyncJoinRoom; +import com.botdarr.clients.matrix.transactions.sync.MatrixSyncResponse; +import com.botdarr.clients.matrix.transactions.sync.MatrixSyncRooms; +import com.botdarr.commands.CommandResponse; +import com.botdarr.connections.ConnectionHelper; +import com.google.gson.Gson; +import org.apache.http.client.methods.*; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.net.URLEncoder; +import java.util.*; + +public class MatrixChatClient implements ChatClient { + public MatrixChatClient() { + if (!isPasswordLoginSupported()) { + throw new RuntimeException("Password login not supported on homeserver"); + } + MatrixLoginResponse loginResponse = getAccessToken(); + if (loginResponse == null) { + throw new RuntimeException("Could not get login response"); + } + this.accessToken = loginResponse.getAccessToken(); + this.userId = loginResponse.getUserId(); + if (this.accessToken == null || this.accessToken.isEmpty()) { + throw new RuntimeException("Could not get access token"); + } + if (this.userId == null || this.userId.isEmpty()) { + throw new RuntimeException("Could not get user id"); + } + for (String room : getRooms()) { + if (!this.joinRoom(room)) { + throw new RuntimeException("Could not join room " + room); + } + } + this.messageFilterId = this.addMessageFilter(); + if (this.messageFilterId == null || this.messageFilterId.isEmpty()) { + throw new RuntimeException("Could not add/get matrix filter"); + } + } + + @Override + public void sendToConfiguredChannels(List chatClientResponses) { + for (MatrixResponse response : chatClientResponses) { + for (String room : getRooms()) { + sendMatrixResponse(response, room); + } + } + } + + public void sendMessage(CommandResponse commandResponse, String roomId) { + if (commandResponse.getSingleChatClientResponse() != null) { + sendMatrixResponse(commandResponse.getSingleChatClientResponse(), roomId); + + } else if (commandResponse.getMultipleChatClientResponses() != null) { + for (MatrixResponse response : commandResponse.getMultipleChatClientResponses()) { + sendMatrixResponse(response, roomId); + } + } else { + //TODO: err + } + } + + public void addListener(MatrixMessageListener listener) { + this.listeners.add(listener); + } + + public void listen() { + String nextBatch = null; + while (true) { + try { + //TODO: handle invalid access token + MatrixSyncResponse syncResponse = sync(nextBatch); + nextBatch = syncResponse.getNextBatch(); + MatrixSyncRooms syncRooms = syncResponse.getRooms(); + for (Map.Entry entry : syncRooms.getJoin().entrySet()) { + String roomId = entry.getKey(); + MatrixSyncJoinRoom joinRoom = entry.getValue(); + for (MatrixSyncEvent event : joinRoom.getTimeline().getEvents()) { + if (event.getContent().getMsgtype() != null && event.getContent().getMsgtype().equals("m.text")) { + for (MatrixMessageListener matrixMessageListener : this.listeners) { + matrixMessageListener.process(roomId, event.getSender(), event.getContent().getBody()); + } + } + } + } + } catch (Throwable t) { + LOGGER.error("Error during event listener", t); + } + } + } + + private List getRooms() { + String rawRoomsStr = Config.getProperty(Config.Constants.MATRIX_ROOM); + if (rawRoomsStr.contains(",")) { + return Arrays.asList(rawRoomsStr.split(",")); + } + return new ArrayList() {{add(rawRoomsStr);}}; + } + + private MatrixPreviewUrlResponse getPreviewUrl(String url) { + return ConnectionHelper.makeRequest(() -> new HttpGet( + Config.getProperty(Config.Constants.MATRIX_HOME_SERVER) + + Constants.API_PREVIEW_URL + + "?access_token=" + MatrixChatClient.this.accessToken + + "&url=" + URLEncoder.encode(url, "UTF-8")), new MatrixResponseHandler() { + @Override + public MatrixPreviewUrlResponse onSuccess(String response) throws Exception { + return new Gson().fromJson(response, MatrixPreviewUrlResponse.class); + } + }); + } + + private void sendMatrixResponse(MatrixResponse response, String roomId) { + List mxcUrls = new ArrayList<>(); + //image previews urls seem to have sporadic behavior across clients in matrix ecosystem + //so I just use the preview url api to get images in a format that works fine + if (!response.getImageUrls().isEmpty()) { + for (String imageUrl : response.getImageUrls()) { + MatrixPreviewUrlResponse previewUrlResponse = getPreviewUrl(imageUrl); + if (previewUrlResponse == null) { + LOGGER.warn("Could not find image " + imageUrl); + continue; + } + mxcUrls.add(previewUrlResponse.getMxcUri()); + } + } + MatrixSendMessageRequest sendMessageRequest = new MatrixSendMessageRequest(); + sendMessageRequest.setBody(response.getContent()); + sendMessage(roomId, sendMessageRequest); + for (String mxcUrl : mxcUrls) { + MatrixSendImageRequest sendImageRequest = new MatrixSendImageRequest(); + sendImageRequest.setUrl(mxcUrl); + sendMessage(roomId, sendImageRequest); + } + } + + private void sendMessage(String roomId, T message) { + MatrixSendMessageResponse response = ConnectionHelper.makeRequest(() -> { + HttpPost post = new HttpPost(Config.getProperty(Config.Constants.MATRIX_HOME_SERVER) + + String.format(Constants.API_SEND_MESSAGE, roomId) + + "?access_token=" + MatrixChatClient.this.accessToken); + post.setEntity(new StringEntity(new Gson().toJson(message), ContentType.APPLICATION_JSON)); + return post; + }, + new MatrixResponseHandler() { + @Override + public MatrixSendMessageResponse onSuccess(String response) throws Exception { + return new Gson().fromJson(response, MatrixSendMessageResponse.class); + } + }); + if (response == null || response.getEventId() == null || response.getEventId().isEmpty()) { + LOGGER.error("Missing event id for room " + roomId + " and message " + message); + } + } + + private String addMessageFilter() { + return ConnectionHelper.makeRequest(() -> { + HttpPost post = new HttpPost(Config.getProperty(Config.Constants.MATRIX_HOME_SERVER) + + String.format(Constants.API_ADD_FILTER, this.userId) + "?access_token=" + this.accessToken); + MatrixFilterRequest matrixFilterRequest = new MatrixFilterRequest(); + + String[] excludeAll = new String[]{"*"}; + String[] rooms = getRooms().toArray(new String[0]); + MatrixEventFilter accountData = new MatrixEventFilter(); + //don't need account data + accountData.setNotTypes(excludeAll); + MatrixEventFilter presenceData = new MatrixEventFilter(); + // no presence data either + presenceData.setNotTypes(excludeAll); + MatrixRoomFilter roomFilter = new MatrixRoomFilter(); + MatrixRoomEventFilter accountDataRoom = new MatrixRoomEventFilter(); + //no account data for the room events + accountDataRoom.setNotTypes(excludeAll); + MatrixStateFilter stateFilter = new MatrixStateFilter(); + stateFilter.setRooms(rooms); + //only message events for the room + stateFilter.setTypes(new String[] {"m.room.message"}); + //ignore events from the bot user itself + stateFilter.setNotSenders(new String[] {this.userId}); + roomFilter.setAccountData(accountDataRoom); + roomFilter.setRooms(rooms); + roomFilter.setState(stateFilter); + roomFilter.setTimeline(new MatrixRoomEventFilter()); + + matrixFilterRequest.setRoom(roomFilter); + matrixFilterRequest.setPresence(presenceData); + matrixFilterRequest.setAccountData(accountData); + matrixFilterRequest.setEventFields(new String[]{"content", "sender", "room_id", "event_id"}); + + post.setEntity(new StringEntity(new Gson().toJson(matrixFilterRequest), ContentType.APPLICATION_JSON)); + return post; + }, new MatrixResponseHandler() { + @Override + public String onSuccess(String response) throws Exception { + MatrixFilterResponse filterResponse = + new Gson().fromJson(response, MatrixFilterResponse.class); + return filterResponse.getFilterId(); + } + }); + } + + private MatrixSyncResponse sync(String nextBatch) { + return ConnectionHelper.makeRequest(() -> new HttpGet( + Config.getProperty(Config.Constants.MATRIX_HOME_SERVER) + + Constants.API_SYNC + + "?access_token=" + MatrixChatClient.this.accessToken + + "&timeout=" + POLL_TIMEOUT + + (nextBatch != null && !nextBatch.isEmpty() ? "&since=" + nextBatch : "") + + "&filter=" + MatrixChatClient.this.messageFilterId + + "&set_presence=online"), new MatrixResponseHandler() { + @Override + public MatrixSyncResponse onSuccess(String response) throws Exception { + return new Gson().fromJson(response, MatrixSyncResponse.class); + } + }); + } + + private boolean joinRoom(String room) { + Boolean joinedRoom = ConnectionHelper.makeRequest(() -> new HttpPost( + Config.getProperty(Config.Constants.MATRIX_HOME_SERVER) + + Constants.API_JOIN_ROOM + + URLEncoder.encode(room, "UTF-8") + + "/join?access_token=" + MatrixChatClient.this.accessToken), new MatrixResponseHandler() { + @Override + public Boolean onSuccess(String response) throws Exception { + MatrixJoinRoomResponse joinRoomResponse = + new Gson().fromJson(response, MatrixJoinRoomResponse.class); + return !joinRoomResponse.getRoomId().isEmpty(); + } + }); + return joinedRoom != null && joinedRoom; + } + + private MatrixLoginResponse getAccessToken() { + return ConnectionHelper.makeRequest(() -> { + HttpPost post = new HttpPost(Config.getProperty(Config.Constants.MATRIX_HOME_SERVER) + Constants.API_LOGIN); + MatrixLoginRequest matrixLoginRequest = new MatrixLoginRequest(); + matrixLoginRequest.setUser(Config.getProperty(Config.Constants.MATRIX_USERNAME)); + matrixLoginRequest.setPassword(Config.getProperty(Config.Constants.MATRIX_PASSWORD)); + post.setEntity(new StringEntity(new Gson().toJson(matrixLoginRequest), ContentType.APPLICATION_JSON)); + return post; + }, new MatrixResponseHandler() { + @Override + public MatrixLoginResponse onSuccess(String response) throws Exception { + return new Gson().fromJson(response, MatrixLoginResponse.class); + } + }); + } + + private boolean isPasswordLoginSupported() { + Boolean isPasswordLoginSupported = ConnectionHelper.makeRequest(() -> + new HttpGet(Config.getProperty(Config.Constants.MATRIX_HOME_SERVER) + Constants.API_LOGIN), new MatrixResponseHandler() { + @Override + public Boolean onSuccess(String response) throws Exception { + MatrixLoginInformation matrixLoginType = + new Gson().fromJson(response, MatrixLoginInformation.class); + for (MatrixLoginFlow flow : matrixLoginType.getFlows()) { + if (flow.getType().equals("m.login.password")) { + return true; + } + } + return false; + } + }); + return isPasswordLoginSupported != null && isPasswordLoginSupported; + } + + private static abstract class MatrixResponseHandler implements ConnectionHelper.ResponseHandler { + + @Override + public T onFailure(int statusCode, String reason) { + LOGGER.error("Error from matrix response, code=" + statusCode + ", reason=" + reason); + if (statusCode == 429) { + LOGGER.error("Rate limited from matrix"); + } + return null; + } + + @Override + public T onException(Exception e) { + LOGGER.error("Error trying to make matrix request", e); + return null; + } + } + + protected static class Constants { + private static final String API_BASE = "/_matrix/client/r0"; + private static final String API_LOGIN = API_BASE + "/login"; + private static final String API_JOIN_ROOM = API_BASE + "/rooms/"; + private static final String API_SYNC = API_BASE + "/sync"; + /** 1 == user_id */ + private static final String API_ADD_FILTER = API_BASE + "/user/%s/filter"; + /** 1 == room id*/ + private static final String API_SEND_MESSAGE = API_BASE + "/rooms/%s/send/m.room.message"; + private static final String API_PREVIEW_URL = "/_matrix/media/r0/preview_url"; + } + + public interface MatrixMessageListener { + void process(String roomId, String sender, String content); + } + + private final List listeners = new ArrayList<>(); + private final String messageFilterId; + private final String accessToken; + private final String userId; + private static final int POLL_TIMEOUT = 60000; + private static final Logger LOGGER = LogManager.getLogger(); +} diff --git a/src/main/java/com/botdarr/clients/matrix/MatrixResponse.java b/src/main/java/com/botdarr/clients/matrix/MatrixResponse.java new file mode 100644 index 0000000..42d1d78 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/MatrixResponse.java @@ -0,0 +1,30 @@ +package com.botdarr.clients.matrix; + +import com.botdarr.clients.ChatClientResponse; + +import java.util.ArrayList; +import java.util.List; + +public class MatrixResponse implements ChatClientResponse { + public void addContent(String content) { + this.content.append(content + "
"); + } + + public void addImage(String imageUrl) { + if (imageUrl == null || imageUrl.isEmpty()) { + return; + } + this.imageUrls.add(imageUrl); + } + + public String getContent() { + return this.content.toString(); + } + + public List getImageUrls() { + return imageUrls; + } + + private List imageUrls = new ArrayList<>(); + private StringBuilder content = new StringBuilder(); +} diff --git a/src/main/java/com/botdarr/clients/matrix/MatrixResponseBuilder.java b/src/main/java/com/botdarr/clients/matrix/MatrixResponseBuilder.java new file mode 100644 index 0000000..0f27f35 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/MatrixResponseBuilder.java @@ -0,0 +1,271 @@ +package com.botdarr.clients.matrix; + +import com.botdarr.Config; +import com.botdarr.api.lidarr.LidarrArtist; +import com.botdarr.api.lidarr.LidarrQueueRecord; +import com.botdarr.api.lidarr.LidarrQueueStatusMessage; +import com.botdarr.api.radarr.*; +import com.botdarr.api.sonarr.*; +import com.botdarr.clients.ChatClientResponseBuilder; +import com.botdarr.commands.*; +import com.botdarr.utilities.ListUtils; +import org.apache.logging.log4j.util.Strings; + +import java.util.List; + +import static com.botdarr.api.lidarr.LidarrApi.ADD_ARTIST_COMMAND_FIELD_PREFIX; +import static com.botdarr.api.radarr.RadarrApi.ADD_MOVIE_COMMAND_FIELD_PREFIX; +import static com.botdarr.api.sonarr.SonarrApi.ADD_SHOW_COMMAND_FIELD_PREFIX; + +public class MatrixResponseBuilder implements ChatClientResponseBuilder { + @Override + public MatrixResponse getHelpResponse() { + try { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Version: " + ChatClientResponseBuilder.getVersion() + ""); + boolean radarrEnabled = Config.isRadarrEnabled(); + boolean sonarrEnabled = Config.isSonarrEnabled(); + boolean lidarrEnabled = Config.isLidarrEnabled(); + if (radarrEnabled) { + matrixResponse.addContent("" + RadarrCommands.getHelpMovieCommandStr() + " - Shows all the commands for movies"); + } + + if (sonarrEnabled) { + matrixResponse.addContent("" + SonarrCommands.getHelpShowCommandStr() + " - Shows all the commands for shows"); + } + + if (lidarrEnabled) { + matrixResponse.addContent("" + LidarrCommands.getHelpCommandStr() + " - Shows all the commands for music"); + } + + if (!radarrEnabled && !sonarrEnabled && !lidarrEnabled) { + matrixResponse.addContent("No radarr or sonarr or lidarr commands configured, check your properties file and logs"); + } + return matrixResponse; + } catch (Exception e) { + throw new RuntimeException("Error getting botdarr version", e); + } + } + + @Override + public MatrixResponse getMusicHelpResponse(List lidarCommands) { + return getListOfCommands(lidarCommands); + } + + @Override + public MatrixResponse getMoviesHelpResponse(List radarrCommands) { + return getListOfCommands(radarrCommands); + } + + @Override + public MatrixResponse getShowsHelpResponse(List sonarrCommands) { + return getListOfCommands(sonarrCommands); + } + + @Override + public MatrixResponse getShowResponse(SonarrShow show) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Title - " + show.getTitle()); + matrixResponse.addContent("TvdbId - " + String.valueOf(show.getTvdbId())); + matrixResponse.addContent("" + ADD_SHOW_COMMAND_FIELD_PREFIX + " - " + SonarrCommands.getAddShowCommandStr(show.getTitle(), show.getTvdbId())); + matrixResponse.addImage(show.getRemotePoster()); + return matrixResponse; + } + + @Override + public MatrixResponse getArtistResponse(LidarrArtist lidarrArtist) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Artist Name - " + lidarrArtist.getArtistName()); + matrixResponse.addContent("Id - " + String.valueOf(lidarrArtist.getForeignArtistId())); + matrixResponse.addContent("" + ADD_ARTIST_COMMAND_FIELD_PREFIX + " - " + LidarrCommands.getAddArtistCommandStr(lidarrArtist.getArtistName(), lidarrArtist.getForeignArtistId())); + return matrixResponse; + } + + @Override + public MatrixResponse getMovieResponse(RadarrMovie radarrMovie) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Movie Title - " + radarrMovie.getTitle()); + matrixResponse.addContent("TmdbId - " + radarrMovie.getTmdbId()); + matrixResponse.addContent("" + ADD_MOVIE_COMMAND_FIELD_PREFIX + " - " + RadarrCommands.getAddMovieCommandStr(radarrMovie.getTitle(), radarrMovie.getTmdbId())); + return matrixResponse; + } + + @Override + public MatrixResponse getShowDownloadResponses(SonarrQueue sonarrShow) { + MatrixResponse matrixResponse = new MatrixResponse(); + SonarQueueEpisode episode = sonarrShow.getEpisode(); + matrixResponse.addContent("Season/Episode - " + "S" + episode.getSeasonNumber() + "E" + episode.getEpisodeNumber()); + matrixResponse.addContent("Quality - " + sonarrShow.getQuality().getQuality().getName()); + matrixResponse.addContent("Status - " + sonarrShow.getStatus()); + matrixResponse.addContent("Time Left - " + (sonarrShow.getTimeleft() == null ? "unknown" : sonarrShow.getTimeleft()) + ""); + String overview = episode.getTitle() + ": " + episode.getOverview(); + if (overview.length() > 1024) { + overview = overview.substring(0, 1024); + } + matrixResponse.addContent("Overview - " + overview); + if (sonarrShow.getStatusMessages() != null) { + for (SonarrQueueStatusMessages statusMessage : sonarrShow.getStatusMessages()) { + for (String message : statusMessage.getMessages()) { + matrixResponse.addContent("Download message - " + message); + } + } + } + return matrixResponse; + } + + @Override + public MatrixResponse getMovieDownloadResponses(RadarrQueue radarrQueue) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Title - " + radarrQueue.getRadarrQueueMovie().getTitle()); + matrixResponse.addContent("Quality - " + radarrQueue.getQuality().getQuality().getName()); + matrixResponse.addContent("Status - " + radarrQueue.getStatus()); + matrixResponse.addContent("Time Left - " + (radarrQueue.getTimeleft() == null ? "unknown" : radarrQueue.getTimeleft()) + ""); + if (radarrQueue.getStatusMessages() != null) { + for (RadarrQueueStatusMessages statusMessage : radarrQueue.getStatusMessages()) { + for (String message : statusMessage.getMessages()) { + matrixResponse.addContent("Download message - " + message); + } + } + } + return matrixResponse; + } + + @Override + public MatrixResponse getArtistDownloadResponses(LidarrQueueRecord lidarrQueue) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Title - " + lidarrQueue.getTitle()); + matrixResponse.addContent("Time Left - " + (lidarrQueue.getTimeleft() == null ? "unknown" : lidarrQueue.getTimeleft()) + ""); + matrixResponse.addContent("Status - " + lidarrQueue.getStatus()); + if (lidarrQueue.getStatusMessages() != null) { + for (LidarrQueueStatusMessage statusMessage : ListUtils.subList(lidarrQueue.getStatusMessages(), 5)) { + for (String message : statusMessage.getMessages()) { + matrixResponse.addContent("Download message - " + message); + } + } + } + return matrixResponse; + } + + @Override + public MatrixResponse createErrorMessage(String message) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("" + message + ""); + return matrixResponse; + } + + @Override + public MatrixResponse createInfoMessage(String message) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("" + message + ""); + return matrixResponse; + } + + @Override + public MatrixResponse createSuccessMessage(String message) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("" + message + ""); + return matrixResponse; + } + + @Override + public MatrixResponse getShowProfile(SonarrProfile sonarrProfile) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Profile"); + matrixResponse.addContent("Name - " + sonarrProfile.getName()); + matrixResponse.addContent("Cutoff - " + sonarrProfile.getCutoff().getName()); + for (int k = 0; k < sonarrProfile.getItems().size(); k++) { + SonarrProfileQualityItem sonarrProfileQualityItem = sonarrProfile.getItems().get(k); + if (sonarrProfileQualityItem.isAllowed()) { + matrixResponse.addContent("Quality - Name: " + sonarrProfileQualityItem.getQuality().getName() + ", Resolution: " + sonarrProfileQualityItem.getQuality().getResolution()); + } + } + return matrixResponse; + } + + @Override + public MatrixResponse getMovieProfile(RadarrProfile radarrProfile) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Profile"); + matrixResponse.addContent("Name - " + radarrProfile.getName()); + matrixResponse.addContent("Cutoff - " + radarrProfile.getCutoff().getName()); + for (int k = 0; k < radarrProfile.getItems().size(); k++) { + RadarrProfileQualityItem sonarrProfileQualityItem = radarrProfile.getItems().get(k); + if (sonarrProfileQualityItem.isAllowed()) { + matrixResponse.addContent("Quality - Name: " + sonarrProfileQualityItem.getQuality().getName() + ", Resolution: " + sonarrProfileQualityItem.getQuality().getResolution()); + } + } + return matrixResponse; + } + + @Override + public MatrixResponse getNewOrExistingShow(SonarrShow sonarrShow, SonarrShow existingShow, boolean findNew) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Title - " + sonarrShow.getTitle()); + matrixResponse.addContent("TvdbId - " + sonarrShow.getTvdbId()); + if (findNew) { + matrixResponse.addContent("" + ADD_SHOW_COMMAND_FIELD_PREFIX + " - " + SonarrCommands.getAddShowCommandStr(sonarrShow.getTitle(), sonarrShow.getTvdbId())); + } else { + matrixResponse.addContent("Id - " + existingShow.getId()); + if (existingShow.getSeasons() != null) { + matrixResponse.addContent("Number of seasons - " + existingShow.getSeasons().size()); + for (SonarrSeason sonarrSeason : existingShow.getSeasons()) { + matrixResponse.addContent( + "Season#" + sonarrSeason.getSeasonNumber() + + ",Available Epsiodes=" + sonarrSeason.getStatistics().getEpisodeCount() + + ",Total Epsiodes=" + sonarrSeason.getStatistics().getTotalEpisodeCount()); + } + } + } + matrixResponse.addImage(sonarrShow.getRemotePoster()); + return matrixResponse; + } + + @Override + public MatrixResponse getNewOrExistingMovie(RadarrMovie lookupMovie, RadarrMovie existingMovie, boolean findNew) { + MatrixResponse matrixResponse = new MatrixResponse(); + matrixResponse.addContent("Title - " + lookupMovie.getTitle()); + matrixResponse.addContent("TmdbId - " + String.valueOf(lookupMovie.getTmdbId())); + if (findNew) { + matrixResponse.addContent("" + ADD_MOVIE_COMMAND_FIELD_PREFIX + " - " + RadarrCommands.getAddMovieCommandStr(lookupMovie.getTitle(), lookupMovie.getTmdbId())); + } else { + matrixResponse.addContent("Id - " + existingMovie.getId()); + matrixResponse.addContent("Downloaded - " + String.valueOf(existingMovie.isDownloaded())); + matrixResponse.addContent("Has File - " + String.valueOf(existingMovie.isHasFile())); + } + matrixResponse.addImage(lookupMovie.getRemotePoster()); + return matrixResponse; + } + + @Override + public MatrixResponse getNewOrExistingArtist(LidarrArtist lookupArtist, LidarrArtist existingArtist, boolean findNew) { + MatrixResponse matrixResponse = new MatrixResponse(); + String artistDetail = " (" + lookupArtist.getDisambiguation() + ")"; + matrixResponse.addContent("Title - " + (lookupArtist.getArtistName() + (Strings.isEmpty(lookupArtist.getDisambiguation()) ? "" : artistDetail))); + if (findNew) { + matrixResponse.addContent("" + ADD_ARTIST_COMMAND_FIELD_PREFIX + " - " + LidarrCommands.getAddArtistCommandStr(lookupArtist.getArtistName(), lookupArtist.getForeignArtistId())); + } + matrixResponse.addImage(lookupArtist.getRemotePoster()); + return matrixResponse; + } + + @Override + public MatrixResponse getDiscoverableMovies(RadarrMovie radarrMovie) { + MatrixResponse matrixResponse = new MatrixResponse(); + 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()); + } + return matrixResponse; + } + + 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()); + } + return matrixResponse; + } +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/MatrixJoinRoomResponse.java b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixJoinRoomResponse.java new file mode 100644 index 0000000..33478b4 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixJoinRoomResponse.java @@ -0,0 +1,9 @@ +package com.botdarr.clients.matrix.transactions; + +public class MatrixJoinRoomResponse { + public String getRoomId() { + return room_id; + } + + private String room_id = ""; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginFlow.java b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginFlow.java new file mode 100644 index 0000000..4a8b301 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginFlow.java @@ -0,0 +1,9 @@ +package com.botdarr.clients.matrix.transactions; + +public class MatrixLoginFlow { + public String getType() { + return type; + } + + private String type; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginInformation.java b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginInformation.java new file mode 100644 index 0000000..344db8b --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginInformation.java @@ -0,0 +1,9 @@ +package com.botdarr.clients.matrix.transactions; + +public class MatrixLoginInformation { + public MatrixLoginFlow[] getFlows() { + return flows; + } + + private MatrixLoginFlow[] flows = new MatrixLoginFlow[]{}; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginRequest.java b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginRequest.java new file mode 100644 index 0000000..2ddc713 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginRequest.java @@ -0,0 +1,15 @@ +package com.botdarr.clients.matrix.transactions; + +public class MatrixLoginRequest { + public void setUser(String user) { + this.user = user; + } + + public void setPassword(String password) { + this.password = password; + } + + private String user; + private String password; + private String type = "m.login.password"; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginResponse.java b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginResponse.java new file mode 100644 index 0000000..45b82ef --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixLoginResponse.java @@ -0,0 +1,19 @@ +package com.botdarr.clients.matrix.transactions; + +public class MatrixLoginResponse { + public String getUserId() { + return user_id; + } + + public String getHomeServer() { + return home_server; + } + + public String getAccessToken() { + return access_token; + } + + private String user_id; + private String home_server; + private String access_token; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/MatrixPreviewUrlResponse.java b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixPreviewUrlResponse.java new file mode 100644 index 0000000..e599510 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixPreviewUrlResponse.java @@ -0,0 +1,18 @@ +package com.botdarr.clients.matrix.transactions; + +import com.google.gson.annotations.SerializedName; + +public class MatrixPreviewUrlResponse { + public Integer getSize() { + return size; + } + + public String getMxcUri() { + return mxcUri; + } + + @SerializedName("matrix:image:size") + private Integer size; + @SerializedName("og:image") + private String mxcUri; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/MatrixSendImageRequest.java b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixSendImageRequest.java new file mode 100644 index 0000000..6d7b086 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixSendImageRequest.java @@ -0,0 +1,11 @@ +package com.botdarr.clients.matrix.transactions; + +public class MatrixSendImageRequest { + public void setUrl(String url) { + this.url = url; + } + + private String msgtype = "m.image"; + private String url; + private String body = "image-text"; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/MatrixSendMessageRequest.java b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixSendMessageRequest.java new file mode 100644 index 0000000..7053f2c --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixSendMessageRequest.java @@ -0,0 +1,13 @@ +package com.botdarr.clients.matrix.transactions; + +public class MatrixSendMessageRequest { + public void setBody(String body) { + this.body = body; + this.formatted_body = body; + } + + private String msgtype = "m.text"; + private String format = "org.matrix.custom.html"; + private String formatted_body; + private String body; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/MatrixSendMessageResponse.java b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixSendMessageResponse.java new file mode 100644 index 0000000..0287747 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/MatrixSendMessageResponse.java @@ -0,0 +1,9 @@ +package com.botdarr.clients.matrix.transactions; + +public class MatrixSendMessageResponse { + public String getEventId() { + return event_id; + } + + private String event_id; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixEventFilter.java b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixEventFilter.java new file mode 100644 index 0000000..fc58d58 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixEventFilter.java @@ -0,0 +1,14 @@ +package com.botdarr.clients.matrix.transactions.filter; + +public class MatrixEventFilter { + public void setTypes(String[] types) { + this.types = types; + } + + public void setNotTypes(String[] not_types) { + this.not_types = not_types; + } + + private String[] types; + private String[] not_types; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixFilterRequest.java b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixFilterRequest.java new file mode 100644 index 0000000..621130c --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixFilterRequest.java @@ -0,0 +1,24 @@ +package com.botdarr.clients.matrix.transactions.filter; + +public class MatrixFilterRequest { + public void setEventFields(String[] event_fields) { + this.event_fields = event_fields; + } + + public void setAccountData(MatrixEventFilter account_data) { + this.account_data = account_data; + } + + public void setPresence(MatrixEventFilter presence) { + this.presence = presence; + } + + public void setRoom(MatrixRoomFilter room) { + this.room = room; + } + + private String[] event_fields; + private MatrixEventFilter presence; + private MatrixEventFilter account_data; + private MatrixRoomFilter room; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixFilterResponse.java b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixFilterResponse.java new file mode 100644 index 0000000..ec78578 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixFilterResponse.java @@ -0,0 +1,9 @@ +package com.botdarr.clients.matrix.transactions.filter; + +public class MatrixFilterResponse { + public String getFilterId() { + return filter_id; + } + + private String filter_id; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixRoomEventFilter.java b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixRoomEventFilter.java new file mode 100644 index 0000000..067b625 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixRoomEventFilter.java @@ -0,0 +1,12 @@ +package com.botdarr.clients.matrix.transactions.filter; + +public class MatrixRoomEventFilter { + public void setNotTypes(String[] not_types) { + this.not_types = not_types; + } + + private String[] not_types; + //this setting makes it so we never receive historical events from the server + //be careful reusing this type outside of MatrixRoomFilter + private int limit = 0; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixRoomFilter.java b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixRoomFilter.java new file mode 100644 index 0000000..77c7c14 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixRoomFilter.java @@ -0,0 +1,24 @@ +package com.botdarr.clients.matrix.transactions.filter; + +public class MatrixRoomFilter { + public void setRooms(String[] rooms) { + this.rooms = rooms; + } + + public void setState(MatrixStateFilter state) { + this.state = state; + } + + public void setAccountData(MatrixRoomEventFilter account_data) { + this.account_data = account_data; + } + + public void setTimeline(MatrixRoomEventFilter timeline) { + this.timeline = timeline; + } + + private String[] rooms; + private MatrixStateFilter state; + private MatrixRoomEventFilter account_data; + private MatrixRoomEventFilter timeline; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixStateFilter.java b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixStateFilter.java new file mode 100644 index 0000000..caaa566 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/filter/MatrixStateFilter.java @@ -0,0 +1,19 @@ +package com.botdarr.clients.matrix.transactions.filter; + +public class MatrixStateFilter { + public void setTypes(String[] types) { + this.types = types; + } + + public void setRooms(String[] rooms) { + this.rooms = rooms; + } + + public void setNotSenders(String[] not_senders) { + this.not_senders = not_senders; + } + + private String[] types; + private String[] rooms; + private String[] not_senders; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncEvent.java b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncEvent.java new file mode 100644 index 0000000..ef221d1 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncEvent.java @@ -0,0 +1,18 @@ +package com.botdarr.clients.matrix.transactions.sync; + +public class MatrixSyncEvent { + public String getSender() { + return sender; + } + + public String getEvent_id() { + return event_id; + } + + public MatrixSyncEventContent getContent() { + return content; + } + private String sender; + private String event_id; + private MatrixSyncEventContent content; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncEventContent.java b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncEventContent.java new file mode 100644 index 0000000..592ff6f --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncEventContent.java @@ -0,0 +1,14 @@ +package com.botdarr.clients.matrix.transactions.sync; + +public class MatrixSyncEventContent { + public String getMsgtype() { + return msgtype; + } + + public String getBody() { + return body; + } + + private String msgtype; + private String body; +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncJoinRoom.java b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncJoinRoom.java new file mode 100644 index 0000000..4878727 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncJoinRoom.java @@ -0,0 +1,9 @@ +package com.botdarr.clients.matrix.transactions.sync; + +public class MatrixSyncJoinRoom { + public MatrixSyncTimeline getTimeline() { + return timeline; + } + + private MatrixSyncTimeline timeline = new MatrixSyncTimeline(); +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncResponse.java b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncResponse.java new file mode 100644 index 0000000..c0f6d3d --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncResponse.java @@ -0,0 +1,14 @@ +package com.botdarr.clients.matrix.transactions.sync; + +public class MatrixSyncResponse { + public String getNextBatch() { + return next_batch; + } + + public MatrixSyncRooms getRooms() { + return rooms; + } + + private String next_batch; + private MatrixSyncRooms rooms = new MatrixSyncRooms(); +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncRooms.java b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncRooms.java new file mode 100644 index 0000000..ee5e9f6 --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncRooms.java @@ -0,0 +1,12 @@ +package com.botdarr.clients.matrix.transactions.sync; + +import java.util.HashMap; +import java.util.Map; + +public class MatrixSyncRooms { + public Map getJoin() { + return join; + } + + private Map join = new HashMap<>(); +} diff --git a/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncTimeline.java b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncTimeline.java new file mode 100644 index 0000000..c895dcb --- /dev/null +++ b/src/main/java/com/botdarr/clients/matrix/transactions/sync/MatrixSyncTimeline.java @@ -0,0 +1,9 @@ +package com.botdarr.clients.matrix.transactions.sync; + +public class MatrixSyncTimeline { + public MatrixSyncEvent[] getEvents() { + return events; + } + + private MatrixSyncEvent[] events = new MatrixSyncEvent[]{}; +} diff --git a/src/main/java/com/botdarr/clients/slack/SlackResponseBuilder.java b/src/main/java/com/botdarr/clients/slack/SlackResponseBuilder.java index 4b65fad..d49de46 100644 --- a/src/main/java/com/botdarr/clients/slack/SlackResponseBuilder.java +++ b/src/main/java/com/botdarr/clients/slack/SlackResponseBuilder.java @@ -15,13 +15,11 @@ import com.github.seratch.jslack.api.model.block.SectionBlock; import com.github.seratch.jslack.api.model.block.composition.MarkdownTextObject; import com.github.seratch.jslack.api.model.block.composition.PlainTextObject; -import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.util.Strings; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Base64; import java.util.List; import static com.botdarr.api.lidarr.LidarrApi.ADD_ARTIST_COMMAND_FIELD_PREFIX; @@ -171,9 +169,6 @@ public SlackResponse getShowDownloadResponses(SonarrQueue showQueue) { .build()); } } - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("Cancel download command - " + "show cancel download " + showQueue.getId()).build()) - .build()); return slackResponse; } @@ -205,9 +200,6 @@ public SlackResponse getMovieDownloadResponses(RadarrQueue radarrQueue) { .build()); } } - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("Cancel download command - " + "movie cancel download " + radarrQueue.getId()).build()) - .build()); return slackResponse; } @@ -266,49 +258,6 @@ public SlackResponse createSuccessMessage(String message) { return slackResponse; } - @Override - public SlackResponse getTorrentResponses(RadarrTorrent radarrTorrent, String movieTitle) { - SlackResponse slackResponse = new SlackResponse(); - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("*Title* - " + radarrTorrent.getTitle()).build()) - .build()); - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("Torrent - " + radarrTorrent.getGuid()).build()) - .build()); - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("Quality - " + radarrTorrent.getQuality().getQuality().getName()).build()) - .build()); - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("Indexer - " + radarrTorrent.getIndexer()).build()) - .build()); - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("Seeders - " + radarrTorrent.getSeeders()).build()) - .build()); - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("Leechers - " + radarrTorrent.getLeechers()).build()) - .build()); - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("Size - " + FileUtils.byteCountToDisplaySize(radarrTorrent.getSize())).build()) - .build()); - - String[] rejections = radarrTorrent.getRejections(); - if (rejections != null) { - List contextBlockElements = new ArrayList<>(); - for (String rejection : rejections) { - contextBlockElements.add(PlainTextObject.builder().text("Rejection Reason - " + rejection).build()); - } - slackResponse.addBlock(ContextBlock.builder() - .elements(contextBlockElements) - .build()); - } - String key = radarrTorrent.getGuid() + ":title=" + movieTitle; - byte[] encodedBytes = Base64.getEncoder().encode(key.getBytes()); - slackResponse.addBlock(SectionBlock.builder() - .text(MarkdownTextObject.builder().text("Download hash command - " + "movie hash download " + new String(encodedBytes)).build()) - .build()); - return slackResponse; - } - @Override public SlackResponse getShowProfile(SonarrProfile sonarrProfile) { SlackResponse slackResponse = new SlackResponse(); diff --git a/src/main/java/com/botdarr/clients/telegram/TelegramResponseBuilder.java b/src/main/java/com/botdarr/clients/telegram/TelegramResponseBuilder.java index ff7d86f..710ce00 100644 --- a/src/main/java/com/botdarr/clients/telegram/TelegramResponseBuilder.java +++ b/src/main/java/com/botdarr/clients/telegram/TelegramResponseBuilder.java @@ -10,7 +10,6 @@ import com.botdarr.commands.*; import com.botdarr.utilities.ListUtils; import j2html.tags.DomContent; -import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.util.Strings; import static com.botdarr.api.lidarr.LidarrApi.ADD_ARTIST_COMMAND_FIELD_PREFIX; @@ -21,7 +20,6 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Base64; import java.util.List; public class TelegramResponseBuilder implements ChatClientResponseBuilder { @@ -114,7 +112,6 @@ public TelegramResponse getShowDownloadResponses(SonarrQueue showQueue) { } domContents.add(code(statusMessageBuilder.toString())); } - domContents.add(b("Cancel download command - " + "show cancel download " + showQueue.getId())); return new TelegramResponse(domContents); } @@ -177,30 +174,6 @@ public TelegramResponse createSuccessMessage(String message) { return new TelegramResponse(domContents); } - @Override - public TelegramResponse getTorrentResponses(RadarrTorrent radarrTorrent, String movieTitle) { - List domContents = new ArrayList<>(); - domContents.add(b("Title - " + radarrTorrent.getTitle())); - domContents.add(text("Torrent - " + radarrTorrent.getGuid())); - domContents.add(text("Quality - " + radarrTorrent.getQuality().getQuality().getName())); - domContents.add(text("Indexer - " + radarrTorrent.getIndexer())); - domContents.add(text("Seeders - " + radarrTorrent.getSeeders())); - domContents.add(text("Leechers - " + radarrTorrent.getLeechers())); - domContents.add(text("Size - " + FileUtils.byteCountToDisplaySize(radarrTorrent.getSize()))); - String[] rejections = radarrTorrent.getRejections(); - if (rejections != null) { - StringBuilder rejectionReasons = new StringBuilder(); - for (String rejection : rejections) { - rejectionReasons.append("Rejection Reason - " + rejection + "\n"); - } - domContents.add(code(rejectionReasons.toString())); - } - String key = radarrTorrent.getGuid() + ":title=" + movieTitle; - byte[] encodedBytes = Base64.getEncoder().encode(key.getBytes()); - domContents.add(u(b("Download hash command - " + "movie hash download " + new String(encodedBytes)))); - return new TelegramResponse(domContents); - } - @Override public TelegramResponse getShowProfile(SonarrProfile sonarrProfile) { List domContents = new ArrayList<>(); diff --git a/src/main/java/com/botdarr/commands/CommandProcessor.java b/src/main/java/com/botdarr/commands/CommandProcessor.java index ecc695c..75cd3c2 100644 --- a/src/main/java/com/botdarr/commands/CommandProcessor.java +++ b/src/main/java/com/botdarr/commands/CommandProcessor.java @@ -52,7 +52,7 @@ public String getPrefix() { if (!Strings.isEmpty(configuredPrefix)) { return configuredPrefix; } - return "/"; + return "!"; } private static final Logger LOGGER = LogManager.getLogger(CommandProcessor.class); diff --git a/src/main/java/com/botdarr/commands/RadarrCommands.java b/src/main/java/com/botdarr/commands/RadarrCommands.java index 622563b..13c5f25 100644 --- a/src/main/java/com/botdarr/commands/RadarrCommands.java +++ b/src/main/java/com/botdarr/commands/RadarrCommands.java @@ -70,29 +70,6 @@ public CommandResponse execute(String searchText) return new CommandResponse(radarrApi.lookup(searchText, false)); } }); - add(new BaseCommand("movie find downloads", "movie find downloads ", "Lists all the available (not rejected) torrents for a movie (i.e., movie find downloads TITLE OF MOVIE). " + - "You can get the title by using \"movie find existing\". This can be a SLOW operation depending on the number of indexers configured" + - " in your Radarr settings and particularly how fast each indexer is. Also these are torrents that have not been marked as rejected based" + - " on whatever quality/profile settings are configured in Radarr") { - @Override - public CommandResponse execute(String searchText) { - validateMovieTitle(searchText); - return new CommandResponse(radarrApi.lookupTorrents(searchText, false)); - } - }); - add(new BaseCommand("movie find all downloads", "movie find all downloads ","List all the available torrents for a movie whether they are rejected by radarr or not") { - @Override - public CommandResponse execute(String searchText) { - validateMovieTitle(searchText); - return new CommandResponse(radarrApi.lookupTorrents(searchText, true)); - } - }); - add(new BaseCommand("movie hash download", "movie hash download ", "Force downloads a movie using a hash string, you can only get from the command 'movie find all downloads'") { - @Override - public CommandResponse execute(String command) { - return new CommandResponse(radarrApi.forceDownload(command)); - } - }); add(new BaseCommand("movie downloads", "Shows all the active movies downloading in radarr") { @Override public boolean hasArguments() { diff --git a/src/main/java/com/botdarr/connections/ConnectionHelper.java b/src/main/java/com/botdarr/connections/ConnectionHelper.java index d7cea05..38bdd8d 100644 --- a/src/main/java/com/botdarr/connections/ConnectionHelper.java +++ b/src/main/java/com/botdarr/connections/ConnectionHelper.java @@ -5,12 +5,8 @@ import com.botdarr.clients.ChatClientResponse; import com.botdarr.clients.ChatClientResponseBuilder; import com.google.gson.Gson; -import com.sun.jndi.toolkit.url.Uri; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URIBuilder; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.*; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; @@ -21,85 +17,62 @@ import java.io.IOException; import java.util.Arrays; -import java.util.Collections; import java.util.List; public class ConnectionHelper { - public static List makeGetRequest(Api api, String path, ResponseHandler responseHandler) { + public static T makeGetRequest(Api api, String path, ResponseHandler responseHandler) { return makeGetRequest(api, path, "", responseHandler); } - public static List makePostRequest(Api api, String path, K params, ResponseHandler responseHandler) { - try (CloseableHttpClient client = HttpClientBuilder.create().build()) { - HttpPost post = new HttpPost(api.getApiUrl(path) + params); - post.setHeader("X-Api-Key", Config.getProperty(api.getApiToken())); - post.setEntity(new StringEntity(new Gson().toJson(params), ContentType.APPLICATION_JSON)); - try (CloseableHttpResponse response = client.execute(post)) { - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode == 200) { - try { - return responseHandler.onSuccess(EntityUtils.toString(response.getEntity())); - } catch (Exception e) { - LOGGER.error("Error trying to make post request", e); - return responseHandler.onException(e); - } - } else { - return responseHandler.onFailure(statusCode, response.getStatusLine().getReasonPhrase()); - } + public static T makeGetRequest(Api api, String path, String params, ResponseHandler responseHandler) { + return makeRequest(new RequestHandler() { + @Override + public HttpRequestBase buildRequest() throws Exception { + HttpGet get = new HttpGet(api.getApiUrl(path) + params); + get.setHeader("X-Api-Key", Config.getProperty(api.getApiToken())); + return get; } - } catch (IOException e) { - LOGGER.error("Error trying to make connection during post request", e); - return responseHandler.onException(e); - } - } - public static List makeGetRequest(Api api, String path, String params, ResponseHandler responseHandler) { - try (CloseableHttpClient client = HttpClientBuilder.create().build()) { - HttpGet get = new HttpGet(api.getApiUrl(path) + params); - get.setHeader("X-Api-Key", Config.getProperty(api.getApiToken())); - try (CloseableHttpResponse response = client.execute(get)) { - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode == 200) { - try { - return responseHandler.onSuccess(EntityUtils.toString(response.getEntity())); - } catch (Exception e) { - LOGGER.error("Error trying to make get request", e); - return responseHandler.onException(e); - } - } else { - return responseHandler.onFailure(statusCode, response.getStatusLine().getReasonPhrase()); - } + @Override + public boolean turnOnTimeouts() { + // all api requests should use timeouts + return true; } - } catch (IOException e) { - LOGGER.error("Error trying to make connection during get request", e); - return responseHandler.onException(e); - } + }, responseHandler); } - public static List makeDeleteRequest(Api api, String path, String params, ResponseHandler responseHandler) { - try (CloseableHttpClient client = HttpClientBuilder.create().build()) { - HttpDelete delete = new HttpDelete(api.getApiUrl(path) + params); - delete.setHeader("X-Api-Key", Config.getProperty(api.getApiToken())); - try (CloseableHttpResponse response = client.execute(delete)) { + public static T makeRequest(RequestHandler requestHandler, ResponseHandler responseHandler) { + RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); + if (requestHandler.turnOnTimeouts()) { + int timeout = 5000; + requestConfigBuilder.setConnectTimeout(timeout); + requestConfigBuilder.setSocketTimeout(timeout); + requestConfigBuilder.setConnectionRequestTimeout(timeout); + } + try (CloseableHttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(requestConfigBuilder.build()).build()) { + try (CloseableHttpResponse response = client.execute(requestHandler.buildRequest())) { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { try { return responseHandler.onSuccess(EntityUtils.toString(response.getEntity())); } catch (Exception e) { - LOGGER.error("Error trying to make delete request", e); + LOGGER.error("Error trying to process response", e); return responseHandler.onException(e); } } else { return responseHandler.onFailure(statusCode, response.getStatusLine().getReasonPhrase()); } + } catch (Exception e) { + LOGGER.error("Error trying to execute connection during post request", e); + return responseHandler.onException(e); } } catch (IOException e) { - LOGGER.error("Error trying to make connection during delete request", e); + LOGGER.error("Error trying to make connection during post request", e); return responseHandler.onException(e); } } - public static abstract class SimpleMessageEmbedResponseHandler implements ResponseHandler { + public static abstract class SimpleMessageEmbedResponseHandler implements ResponseHandler> { public SimpleMessageEmbedResponseHandler(ChatClientResponseBuilder chatClientResponseBuilder) { this.chatClientResponseBuilder = chatClientResponseBuilder; } @@ -116,26 +89,32 @@ public List onException(Exception e) { private ChatClientResponseBuilder chatClientResponseBuilder; } - //TODO: add ability to return any object (instead of just a list) public static abstract class SimpleEntityResponseHandler implements ResponseHandler { @Override - public List onFailure(int statusCode, String reason) { - return Collections.emptyList(); + public T onFailure(int statusCode, String reason) { + return null; } @Override - public List onException(Exception e) { - return Collections.emptyList(); + public T onException(Exception e) { + return null; + } + } + + public interface RequestHandler { + HttpRequestBase buildRequest() throws Exception; + default boolean turnOnTimeouts() { + return false; } } - public static interface ResponseHandler { - List onSuccess(String response) throws Exception; + public interface ResponseHandler { + T onSuccess(String response) throws Exception; - List onFailure(int statusCode, String reason); + T onFailure(int statusCode, String reason); - List onException(Exception e); + T onException(Exception e); } private static final Logger LOGGER = LogManager.getLogger(); diff --git a/src/main/java/com/botdarr/scheduling/Scheduler.java b/src/main/java/com/botdarr/scheduling/Scheduler.java index 6ea445a..236710c 100644 --- a/src/main/java/com/botdarr/scheduling/Scheduler.java +++ b/src/main/java/com/botdarr/scheduling/Scheduler.java @@ -6,9 +6,7 @@ import org.apache.logging.log4j.Logger; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; public class Scheduler { public static Scheduler getScheduler() { @@ -33,7 +31,7 @@ public void initApiNotifications(List apis, ChatClient chatClient) { } catch (Throwable e) { LOGGER.error("Error during api notification", e); } - }, 0, 1, TimeUnit.HOURS); + }, 0, 5, TimeUnit.MINUTES); } } @@ -57,8 +55,16 @@ public void initApiCaching(List apis) { } } + public void executeCommand(Callable callable) { + if (commandThreadPool == null) { + commandThreadPool = Executors.newFixedThreadPool(10); + } + commandThreadPool.submit(callable); + } + private ScheduledFuture notificationFuture; private ScheduledFuture cacheFuture; + private ExecutorService commandThreadPool; private static volatile Scheduler instance; private static final Logger LOGGER = LogManager.getLogger(); } diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 417d7ca..8691a33 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -89,6 +89,18 @@ + + logs/matrix.log + logs/%d{yyyy-MM-dd-hh-mm}.log.zip + + %d{yyyy-MMM-dd HH:mm:ss a} [%t] %-5level %logger{36} - %msg%n + + + + + + + logs/telegram.log logs/%d{yyyy-MM-dd-hh-mm}.log.zip @@ -125,18 +137,22 @@ - + - + - + + + + + @@ -146,6 +162,7 @@ + \ No newline at end of file diff --git a/src/main/resources/version.txt b/src/main/resources/version.txt index c20c645..831446c 100644 --- a/src/main/resources/version.txt +++ b/src/main/resources/version.txt @@ -1 +1 @@ -5.0.6 +5.1.0 diff --git a/src/test/java/com/botdarr/ConfigTests.java b/src/test/java/com/botdarr/ConfigTests.java index f7c234e..25a4503 100644 --- a/src/test/java/com/botdarr/ConfigTests.java +++ b/src/test/java/com/botdarr/ConfigTests.java @@ -33,7 +33,7 @@ public void getConfig_invalidCommandPrefixConfigured() throws Exception { public void getConfig_noChatClientsConfigured() throws Exception { writeFakePropertiesFile(new Properties()); expectedException.expect(RuntimeException.class); - expectedException.expectMessage("You don't have Discord, Slack, Telegram configured, please configure one"); + expectedException.expectMessage("You don't have Discord, Matrix, Slack, Telegram configured, please configure one"); Config.getProperty(""); } diff --git a/src/test/java/com/botdarr/TestResponseBuilder.java b/src/test/java/com/botdarr/TestResponseBuilder.java index 5a234dd..95dab48 100644 --- a/src/test/java/com/botdarr/TestResponseBuilder.java +++ b/src/test/java/com/botdarr/TestResponseBuilder.java @@ -5,7 +5,6 @@ import com.botdarr.api.radarr.RadarrMovie; import com.botdarr.api.radarr.RadarrProfile; import com.botdarr.api.radarr.RadarrQueue; -import com.botdarr.api.radarr.RadarrTorrent; import com.botdarr.api.sonarr.SonarrProfile; import com.botdarr.api.sonarr.SonarrQueue; import com.botdarr.api.sonarr.SonarrShow; @@ -76,11 +75,6 @@ public TestResponse createSuccessMessage(String message) { return new TestResponse(message); } - @Override - public TestResponse getTorrentResponses(RadarrTorrent radarrTorrent, String movieTitle) { - return new TestResponse(); - } - @Override public TestResponse getShowProfile(SonarrProfile sonarrProfile) { return new TestResponse(); diff --git a/src/test/java/com/botdarr/api/LookupStrategyTests.java b/src/test/java/com/botdarr/api/LookupStrategyTests.java index 44b1f9a..f7b33cb 100644 --- a/src/test/java/com/botdarr/api/LookupStrategyTests.java +++ b/src/test/java/com/botdarr/api/LookupStrategyTests.java @@ -129,5 +129,10 @@ public List lookup(String searchTerm) throws Exception { public ChatClientResponse getNewOrExistingItem(Object lookupItem, Object existingItem, boolean findNew) { return null; } + + @Override + public boolean isPathBlacklisted(Object item) { + return false; + } } } diff --git a/src/test/java/com/botdarr/commands/CommandProcessorTests.java b/src/test/java/com/botdarr/commands/CommandProcessorTests.java index 153f914..ddb15a1 100644 --- a/src/test/java/com/botdarr/commands/CommandProcessorTests.java +++ b/src/test/java/com/botdarr/commands/CommandProcessorTests.java @@ -175,52 +175,6 @@ public void processMessage_validMovieTitleWithSpacesForFindExistingMovieCommand( validateValidCommand("!movie find existing Princess 5"); } - @Test - public void processMessage_invalidMovieTitleForFindDownloadsCommand() { - validateInvalidCommand("!movie find downloads", - "Error trying to parse command !movie find downloads, " + - "error=Movie title is missing"); - } - - @Test - public void processMessage_validMovieTitleForFindDownloadsCommand() { - new Expectations() {{ - radarrApi.lookupTorrents("princess5", false); times = 1; result = new TestResponse(); - }}; - validateValidCommand("!movie find downloads Princess5"); - } - - @Test - public void processMessage_validMovieTitleWithSpacesForFindDownloadsCommand() { - new Expectations() {{ - radarrApi.lookupTorrents("princess 5", false); times = 1; result = new TestResponse(); - }}; - validateValidCommand("!movie find downloads Princess 5"); - } - - @Test - public void processMessage_invalidMovieTitleForFindAllDownloadsCommand() { - validateInvalidCommand("!movie find all downloads", - "Error trying to parse command !movie find all downloads, " + - "error=Movie title is missing"); - } - - @Test - public void processMessage_validMovieTitleForFindAllDownloadsCommand() { - new Expectations() {{ - radarrApi.lookupTorrents("princess5", true); times = 1; result = new TestResponse(); - }}; - validateValidCommand("!movie find all downloads Princess5"); - } - - @Test - public void processMessage_validMovieTitleWithSpacesForFindAllDownloadsCommand() { - new Expectations() {{ - radarrApi.lookupTorrents("princess 5", true); times = 1; result = new TestResponse(); - }}; - validateValidCommand("!movie find all downloads Princess 5"); - } - @Test public void processMessage_missingShowTitleAndIdForAddCommand() { validateInvalidCommand("!show id add",