Skip to content

Commit

Permalink
Merge pull request #33 from shayaantx/feature/status-commandd
Browse files Browse the repository at this point in the history
feature/status-commandd
  • Loading branch information
shayaantx authored Feb 21, 2021
2 parents 9bdc1d0 + 4819431 commit fd9cef8
Show file tree
Hide file tree
Showing 20 changed files with 274 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/development-branch.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: pull-request
name: development
on:
push:
branches:
Expand Down
File renamed without changes.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[![Build status](https://ci.appveyor.com/api/projects/status/twce208g6yb18vgl/branch/development?svg=true)](https://ci.appveyor.com/project/shayaantx/botdarr/branch/development)
![Build status](https://github.com/shayaantx/botdarr/actions/workflows/development-branch.yml/badge.svg)
![Docker Pulls](https://img.shields.io/docker/pulls/shayaantx/botdarr)
![Latest Version](https://img.shields.io/docker/v/shayaantx/botdarr)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

# Summary

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.
Made this simple multi chat-client bot to access radarr, sonarr, and lidarr without a UI/server.

<br/>

Expand Down Expand Up @@ -36,6 +38,7 @@ Made this simple slack/discord/telegram/matrix bot so I could access radarr, son
- [x] (discord/slack only) Thumbs up reaction will add search results
- [x] User requests audited to local database\
- [x] Blacklist content by paths from showing up in searches
- [x] Get status of radarr, lidarr, sonarr, and any additional configured endpoints
- [ ] Lookup torrents for movies and force download
- [ ] Cancel/blacklist existing downloads
- [ ] Episode/season search
Expand Down Expand Up @@ -170,6 +173,7 @@ botdarr:
| MAX_DOWNLOADS_TO_SHOW | The max number of downloads to show. If you set this to any value less than or equal to 0, no downloads will show | yes | 20 |
| MAX_RESULTS_TO_SHOW | The max number of results to show per search command. If you set this to any value less than 0, the bot won't startup | yes | 20 |
| COMMAND_PREFIX | The command prefix (default is !). Any prefix is allowed (but I haven't tested every single prefix in every client) | yes | ! |
| STATUS_ENDPOINTS | Endpoints that can be used to return statuses via !status command. The endpoints are separated by a comma and each endpoint is in the following format - name:hostname:port | no | |

<br/>

Expand Down
1 change: 1 addition & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ if [ ! -e "$propertiesFile" ]; then
[[ ! -z "${MAX_SHOW_REQUESTS_PER_USER}" ]] && addConfiguration "max-show-requests-per-user" "${MAX_SHOW_REQUESTS_PER_USER}" "${propertiesFile}"
[[ ! -z "${MAX_MOVIE_REQUESTS_PER_USER}" ]] && addConfiguration "max-movie-requests-per-user" "${MAX_MOVIE_REQUESTS_PER_USER}" "${propertiesFile}"
[[ ! -z "${EXISTING_ITEM_PATHS_BLACKLIST}" ]] && addConfiguration "existing-item-paths-blacklist" "${EXISTING_ITEM_PATHS_BLACKLIST}" "${propertiesFile}"
[[ ! -z "${STATUS_ENDPOINTS}" ]] && addConfiguration "status-endpoints" "${STATUS_ENDPOINTS}" "${propertiesFile}"

addConfiguration "max-downloads-to-show" "${MAX_DOWNLOADS_TO_SHOW:-20}" "${propertiesFile}"
addConfiguration "max-results-to-show" "${MAX_RESULTS_TO_SHOW:-20}" "${propertiesFile}"
Expand Down
6 changes: 5 additions & 1 deletion sample.properties
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,8 @@ 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=
existing-item-paths-blacklist=

# Comma delimited Status endpoints (i.e., endpoints you want checked when the !status command is called)
# i.e., some-name:hostname:port,some-other-name:hostname2:port
#status-endpoints=
90 changes: 77 additions & 13 deletions src/main/java/com/botdarr/Config.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package com.botdarr;

import com.botdarr.clients.ChatClientType;
import com.botdarr.commands.StatusCommand;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.util.Strings;

import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;

import static com.botdarr.commands.StatusCommand.*;

public class Config {
private static volatile Config instance;

Expand Down Expand Up @@ -79,14 +84,16 @@ private Config() {
}

String configuredPrefix = properties.getProperty(Config.Constants.COMMAND_PREFIX);
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");
if (!Strings.isEmpty(configuredPrefix)) {
if (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);
Expand Down Expand Up @@ -115,12 +122,64 @@ public static ChatClientType getChatClientType() {
}

public static List<String> getExistingItemBlacklistPaths() {
String paths = getProperty(Constants.EXISTING_ITEMS_PATHS_BLACKLIST);
if (paths != null) {
if (paths.contains(",")) {
return Arrays.asList(paths.split(","));
return getCommaDelimitedList(getProperty(Constants.EXISTING_ITEMS_PATHS_BLACKLIST));
}

public static List<StatusEndPoint> getStatusEndpoints() {
List<String> endpoints = getCommaDelimitedList(getProperty(Constants.STATUS_ENDPOINTS));
List<StatusEndPoint> statusEndPoints = new ArrayList<>();
if (!endpoints.isEmpty()) {
for (String endpoint : endpoints) {
String[] splitEndpoint = endpoint.split(":");
if (splitEndpoint.length != 3) {
LOGGER.warn("Status endpoint not formatted correctly, usage - name:hostname:port, endpoint=" + endpoint);
}
statusEndPoints.add(new StatusEndPoint(splitEndpoint[0], splitEndpoint[1], Integer.valueOf(splitEndpoint[2])));
}
}
StatusEndPoint endpoint;
if (isLidarrEnabled()) {
endpoint = getDomain(Constants.LIDARR_URL, "lidarr");
if (endpoint != null) {
statusEndPoints.add(endpoint);
}
}
if (isRadarrEnabled()) {
endpoint = getDomain(Constants.RADARR_URL, "radarr");
if (endpoint != null) {
statusEndPoints.add(endpoint);
}
}
if (isSonarrEnabled()) {
endpoint = getDomain(Constants.SONARR_URL, "sonarr");
if (endpoint != null) {
statusEndPoints.add(endpoint);
}
}
return statusEndPoints;
}

private static StatusEndPoint getDomain(String constant, String name) {
try {
URI uri = new URI(getProperty(constant));
int port = uri.getPort();
if (port == -1) {
port = uri.getHost().startsWith("https") ? 443 : 80;
}
return new StatusEndPoint(name, uri.getHost(), port);
} catch (URISyntaxException e) {
LOGGER.error("Error trying to get uri for " + constant, e);
}
return null;
}

private static List<String> getCommaDelimitedList(String list) {
if (!Strings.isEmpty(list) && list.contains(",")) {
if (list.contains(",")) {
return Arrays.asList(list.split(","));
} else {
return new ArrayList<String>() {{add(list);}};
}
return new ArrayList<String>() {{add(paths);}};
}
return new ArrayList<>();
}
Expand Down Expand Up @@ -307,6 +366,11 @@ public static final class Constants {
* The paths of items to blacklist from searches
*/
public static final String EXISTING_ITEMS_PATHS_BLACKLIST = "existing-item-paths-blacklist";

/**
* The additional status endpoints to check
*/
public static final String STATUS_ENDPOINTS = "status-endpoints";
}

private static String propertiesPath = "config/properties";
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/botdarr/api/radarr/RadarrApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.*;

public class RadarrApi implements Api {
Expand Down Expand Up @@ -282,8 +283,8 @@ private ChatClientResponse addMovie(RadarrMovie radarrMovie) {
}
try (CloseableHttpResponse response = client.execute(post)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Respone=" + response.toString());
LOGGER.debug("Response content=" + IOUtils.toString(response.getEntity().getContent()));
LOGGER.debug("Response=" + response.toString());
LOGGER.debug("Response content=" + IOUtils.toString(response.getEntity().getContent(), Charset.defaultCharset()));
LOGGER.debug("Reason=" + response.getStatusLine().toString());
}
int statusCode = response.getStatusLine().getStatusCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public interface ChatClientResponseBuilder<T extends ChatClientResponse> {
Expand All @@ -38,6 +39,7 @@ public interface ChatClientResponseBuilder<T extends ChatClientResponse> {
T getNewOrExistingMovie(RadarrMovie lookupMovie, RadarrMovie existingMovie, boolean findNew);
T getNewOrExistingArtist(LidarrArtist lookupArtist, LidarrArtist existingArtist, boolean findNew);
T getDiscoverableMovies(RadarrMovie radarrMovie);
T getStatusCommandResponse(Map<String, Boolean> endpointStatuses);

static String getVersion() throws IOException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/com/botdarr/clients/ChatClientType.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.botdarr.clients.telegram.TelegramResponse;
import com.botdarr.clients.telegram.TelegramResponseBuilder;
import com.github.seratch.jslack.Slack;
import com.github.seratch.jslack.api.model.User;
import com.github.seratch.jslack.api.model.block.LayoutBlock;
import com.github.seratch.jslack.api.model.block.SectionBlock;
import com.github.seratch.jslack.api.model.block.composition.MarkdownTextObject;
Expand Down Expand Up @@ -244,7 +245,12 @@ public void handle(String message) {
SlackMessage slackMessage = new Gson().fromJson(json, SlackMessage.class);
if (slackMessage.getType() != null) {
if (slackMessage.getType().equalsIgnoreCase("message")) {
handleCommand(slackMessage.getText(), slackChatClient.getUser(slackMessage.getUserId()).getName(), slackMessage.getChannel());
User user = slackChatClient.getUser(slackMessage.getUserId());
if (user == null) {
LOGGER.debug("Could not find user for slack message " + slackMessage);
return;
}
handleCommand(slackMessage.getText(), user.getName(), slackMessage.getChannel());
} else if (slackMessage.getType().equalsIgnoreCase("reaction_added") && slackMessage.getReaction().equalsIgnoreCase("+1")) {
//thumbsup = +1 in slack for some reason
try {
Expand Down Expand Up @@ -353,6 +359,9 @@ private static <T extends ChatClientResponse> ApisAndCommandConfig buildConfig(C
commands.addAll(lidarrCommands);
apis.add(lidarrApi);
}
if (!Config.getStatusEndpoints().isEmpty()) {
commands.add(new StatusCommand<>(responseChatClientResponseBuilder));
}
commands.addAll(HelpCommands.getCommands(responseChatClientResponseBuilder, radarrCommands, sonarrCommands, lidarrCommands));
return new ApisAndCommandConfig(apis, commands);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@
import com.botdarr.utilities.ListUtils;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
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.List;
import java.util.Map;

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;
import static com.botdarr.commands.StatusCommand.STATUS_COMMAND;
import static com.botdarr.commands.StatusCommand.STATUS_COMMAND_DESCRIPTION;
import static net.dv8tion.jda.api.entities.MessageEmbed.VALUE_MAX_LENGTH;

public class DiscordResponseBuilder implements ChatClientResponseBuilder<DiscordResponse> {
Expand Down Expand Up @@ -52,6 +53,9 @@ public DiscordResponse getHelpResponse() {
if (!radarrEnabled && !sonarrEnabled && !lidarrEnabled) {
embedBuilder.appendDescription("No radarr or sonarr or lidarr commands configured, check your properties file and logs");
}
if (!Config.getStatusEndpoints().isEmpty()) {
embedBuilder.addField(new CommandProcessor().getPrefix() + STATUS_COMMAND, STATUS_COMMAND_DESCRIPTION, false);
}
return new DiscordResponse(embedBuilder.build());
}

Expand Down Expand Up @@ -250,6 +254,16 @@ public DiscordResponse getDiscoverableMovies(RadarrMovie radarrMovie) {
return getMovieResponse(radarrMovie);
}

@Override
public DiscordResponse getStatusCommandResponse(Map<String, Boolean> endpointStatuses) {
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setTitle("Endpoint Statuses");
for (Map.Entry<String, Boolean> endpointStatusEntry : endpointStatuses.entrySet()) {
embedBuilder.addField(endpointStatusEntry.getKey(), endpointStatusEntry.getValue() ? "\uD83D\uDFE2" : "\uD83D\uDD34", false);
}
return new DiscordResponse(embedBuilder.build());
}

@Override
public DiscordResponse createErrorMessage(String message) {
return new DiscordResponse(createErrorMessageEmbed(message));
Expand Down Expand Up @@ -293,6 +307,4 @@ private DiscordResponse getListOfCommands(List<Command> commands) {
}
return new DiscordResponse(embedBuilder.build());
}

private static final Logger LOGGER = LogManager.getLogger("DiscordLog");
}
4 changes: 4 additions & 0 deletions src/main/java/com/botdarr/clients/matrix/MatrixResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ public void addContent(String content) {
this.content.append(content + " </br>");
}

public void addRawContent(String content) {
this.content.append(content);
}

public void addImage(String imageUrl) {
if (imageUrl == null || imageUrl.isEmpty()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
import org.apache.logging.log4j.util.Strings;

import java.util.List;
import java.util.Map;

import static com.botdarr.api.lidarr.LidarrApi.ADD_ARTIST_COMMAND_FIELD_PREFIX;
import static com.botdarr.api.radarr.RadarrApi.ADD_MOVIE_COMMAND_FIELD_PREFIX;
import static com.botdarr.api.sonarr.SonarrApi.ADD_SHOW_COMMAND_FIELD_PREFIX;
import static com.botdarr.commands.StatusCommand.STATUS_COMMAND;
import static com.botdarr.commands.StatusCommand.STATUS_COMMAND_DESCRIPTION;

public class MatrixResponseBuilder implements ChatClientResponseBuilder<MatrixResponse> {
@Override
Expand All @@ -41,6 +44,10 @@ public MatrixResponse getHelpResponse() {
if (!radarrEnabled && !sonarrEnabled && !lidarrEnabled) {
matrixResponse.addContent("<b>No radarr or sonarr or lidarr commands configured, check your properties file and logs</b>");
}

if (!Config.getStatusEndpoints().isEmpty()) {
matrixResponse.addContent("<b>" + new CommandProcessor().getPrefix() + STATUS_COMMAND + "</b> - " + STATUS_COMMAND_DESCRIPTION);
}
return matrixResponse;
} catch (Exception e) {
throw new RuntimeException("Error getting botdarr version", e);
Expand Down Expand Up @@ -261,6 +268,25 @@ public MatrixResponse getDiscoverableMovies(RadarrMovie radarrMovie) {
return matrixResponse;
}

@Override
public MatrixResponse getStatusCommandResponse(Map<String, Boolean> endpointStatuses) {
MatrixResponse matrixResponse = new MatrixResponse();
matrixResponse.addRawContent("<table>");
matrixResponse.addRawContent("<thead><tr><td>Endpoint</td><td>Statuses</td></tr></thead>");
for (Map.Entry<String, Boolean> endpointStatusEntry : endpointStatuses.entrySet()) {
matrixResponse.addRawContent("<tr>");
matrixResponse.addRawContent("<td>");
matrixResponse.addRawContent(endpointStatusEntry.getKey());
matrixResponse.addRawContent("</td>");
matrixResponse.addRawContent("<td>");
matrixResponse.addRawContent(endpointStatusEntry.getValue() ? "\uD83D\uDFE2" : "\uD83D\uDD34");
matrixResponse.addRawContent("</td>");
matrixResponse.addRawContent("</tr>");
}
matrixResponse.addRawContent("</table>");
return matrixResponse;
}

private MatrixResponse getListOfCommands(List<Command> commands) {
MatrixResponse matrixResponse = new MatrixResponse();
matrixResponse.addContent("<b><u>Commands</u></b>");
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/botdarr/clients/slack/SlackMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ public SlackMessageItem getItem() {
return item;
}

@Override
public String toString() {
return "SlackMessage{" +
"type='" + type + '\'' +
", text='" + text + '\'' +
", user='" + user + '\'' +
", channel='" + channel + '\'' +
", reaction='" + reaction + '\'' +
", item=" + item +
'}';
}

private String type;
private String text;
private String user; //slack user id
Expand Down
Loading

0 comments on commit fd9cef8

Please sign in to comment.