From 301b2a17c70aff5432c1eb5b0b7f7e094e9a740c Mon Sep 17 00:00:00 2001 From: "Josh.5" Date: Sat, 27 Aug 2022 16:08:53 +1200 Subject: [PATCH 1/4] Specify platform --- info.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/info.json b/info.json index 7f07670..b0ac24f 100644 --- a/info.json +++ b/info.json @@ -7,6 +7,9 @@ "icon": "https://raw.githubusercontent.com/Josh5/unmanic.plugin.notify_sonarr/master/icon.png", "id": "notify_sonarr", "name": "Notify Sonarr", + "platform": [ + "all" + ], "priorities": { "on_postprocessor_task_results": 0 }, From 158e99f5c286fb726c2d3bd5047f0e32021b87b6 Mon Sep 17 00:00:00 2001 From: "Josh.5" Date: Sat, 27 Aug 2022 16:10:17 +1200 Subject: [PATCH 2/4] Roll my own session class for connecting to the Sonarr API --- plugin.py | 98 ++++++++++++++++++++++++++++++++++++++++-------- requirements.txt | 1 - 2 files changed, 83 insertions(+), 16 deletions(-) diff --git a/plugin.py b/plugin.py index 134188b..6495513 100644 --- a/plugin.py +++ b/plugin.py @@ -21,12 +21,15 @@ If not, see . """ +import json import logging import os import pprint +import time + +import requests import humanfriendly -from pyarr import SonarrAPI from unmanic.libs.unplugins.settings import PluginSettings # Configure plugin logger @@ -88,6 +91,70 @@ def __set_minimum_file_size(self): return values +class Session(object): + """ + Rolling my own class for connecting to the Arr API. + Some Docker containers were giving issues with the session token when using pyarr. + We can just add apiKey=? to the URL for what we are doing here... + """ + + def __init__(self, host_url, api_key): + self.api_path = '/sonarr/api/v3' + self.host_url = host_url + self.api_key = api_key + + @staticmethod + def __get(url, params=None): + headers = { + 'Content-Type': 'application/json', + } + r = requests.get(url, headers=headers, params=params) + if r.status_code == 200: + return r.json() + return {} + + @staticmethod + def __post(url, data=None): + headers = { + 'Content-Type': 'application/json', + } + r = requests.post(url, headers=headers, json=data, timeout=5) + if r.status_code == 200: + return r.json() + return {} + + def get_parsed_title(self, title): + # http://{host_url}/{api_path}}/parse?apiKey=1234567890&title=SomeTitle + path = "/parse" + params = { + "apiKey": self.api_key, + "title": title, + } + url = "{host}{api}{path}".format(host=self.host_url.rstrip('/'), api=self.api_path, path=path) + return self.__get(url, params=params) + + def get_queue(self): + # http://{host_url}/{api_path}}/queue?apiKey=1234567890&page=1&pageSize=9999 + path = "/queue" + params = { + "apiKey": self.api_key, + "page": '1', + "pageSize": '9999', + } + url = "{host}{api}{path}".format(host=self.host_url.rstrip('/'), api=self.api_path, path=path) + return self.__get(url, params=params) + + def post_command(self, name, **kwargs): + # http://{host_url}/{api_path}}/command?apiKey=1234567890 + path = "/command?apiKey={api_key}".format(api_key=self.api_key) + data = { + "name": name, + **kwargs, + } + url = "{host}{api}{path}".format(host=self.host_url.rstrip('/'), api=self.api_path, path=path) + return self.__post(url, data=data) + + def check_file_size_under_max_file_size(path, minimum_file_size): file_stats = os.stat(os.path.join(path)) if int(humanfriendly.parse_size(minimum_file_size)) < int(file_stats.st_size): @@ -119,7 +186,6 @@ def update_mode(api, dest_path): def import_mode(api, source_path, dest_path): source_basename = os.path.basename(source_path) - abspath_string = dest_path.replace('\\', '') download_id = None episode_title = None @@ -135,16 +201,17 @@ def import_mode(api, source_path, dest_path): break # Run import + logger.warning("Sending path '{}'".format(dest_path)) if download_id: # Run API command for DownloadedEpisodesScan - # - DownloadedEpisodesScan with a path and downloadClientId - logger.info("Queued import episode '{}' using downloadClientId: '{}'".format(episode_title, download_id)) - result = api.post_command('DownloadedEpisodesScan', path=abspath_string, downloadClientId=download_id) + # - DownloadedEpisodesScan with a path and download_client_id + logger.info("Queued import episode '{}' using download_client_id: '{}'".format(episode_title, download_id)) + result = api.post_command('DownloadedEpisodesScan', path=dest_path, download_client_id=download_id) else: - # Run API command for DownloadedEpisodesScan without passing a downloadClientId - # - DownloadedEpisodesScan with a path and downloadClientId - logger.info("Queued import using just the file path '{}'".format(abspath_string)) - result = api.post_command('DownloadedEpisodesScan', path=abspath_string) + # Run API command for DownloadedEpisodesScan without passing a download_client_id + # - DownloadedEpisodesScan with a path and download_client_id + logger.info("Queued import using just the file path '{}'".format(dest_path)) + result = api.post_command('DownloadedEpisodesScan', path=dest_path) # Log results message = result @@ -159,7 +226,7 @@ def import_mode(api, source_path, dest_path): def process_files(settings, source_file, destination_files, host_url, api_key): - api = SonarrAPI(host_url, api_key) + api = Session(host_url, api_key) mode = settings.get_setting('mode') @@ -168,11 +235,12 @@ def process_files(settings, source_file, destination_files, host_url, api_key): if mode == 'update_mode': update_mode(api, dest_file) elif mode == 'import_mode': - minimum_file_size = settings.get_setting('minimum_file_size') - if check_file_size_under_max_file_size(dest_file, minimum_file_size): - # Ignore this file - logger.info("Ignoring file as it is under configured minimum size file: '{}'".format(dest_file)) - continue + if settings.get_setting('limit_import_on_file_size'): + minimum_file_size = settings.get_setting('minimum_file_size') + if check_file_size_under_max_file_size(dest_file, minimum_file_size): + # Ignore this file + logger.info("Ignoring file as it is under configured minimum size file: '{}'".format(dest_file)) + continue import_mode(api, source_file, dest_file) diff --git a/requirements.txt b/requirements.txt index e1b3672..caaffad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -pyarr==3.0.1 humanfriendly>=9.1 \ No newline at end of file From 1ccd2831c2046bfacc419e62cd2eaa2a40272a52 Mon Sep 17 00:00:00 2001 From: "Josh.5" Date: Sat, 27 Aug 2022 16:15:42 +1200 Subject: [PATCH 3/4] Update documentation --- README.md | 8 ++++++- description.md | 65 +++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 59304c5..eb18a08 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # Notify Sonarr +Plugin for [Unmanic](https://github.com/Unmanic) -plugin for [Unmanic](https://github.com/Unmanic) +--- + +### Information: + +- [Description](description.md) +- [Changelog](changelog.md) diff --git a/description.md b/description.md index d2ac1af..6d275b5 100644 --- a/description.md +++ b/description.md @@ -1,50 +1,49 @@ -### Config description: +--- +##### Links: -#### Sonarr LAN IP Address -The protocol and IP address of the Sonarr application +- [Support](https://unmanic.app/discord) +- [Issues/Feature Requests](https://github.com/Unmanic/plugin.notify_sonarr/issues) +- [Pull Requests](https://github.com/Unmanic/plugin.notify_sonarr/pulls) +--- -#### Sonarr API Key -Sonarr application API key +##### Plugin Settings: +###### Sonarr LAN IP Address +The protocol and IP address of the Sonarr application -#### Mode +###### Sonarr API Key +Sonarr application API key -##### Trigger series refresh on task complete -Use this mode when you wish to simply trigger a refresh on a series to re-read a modified file after Unmanic has -processed it. +###### Mode +There are x2 modes available. Each mode has a different set of configuration options available to it. -##### Import episode on task complete -Use this mode when you are running Unmanic prior to importing a file into Sonarr. -This will trigger a download import. +- **Trigger series refresh on task complete** -If possible, this will associate with a matching queued download and import the file that way. However, it is possibly this will fail. -If it does fail, it will fallback to providing the file path to Sonarr and allowing Sonarr to carry out a normal -automated import by parsing the file name. + Use this mode when you wish to simply trigger a refresh on a series to re-read a modified file after Unmanic has processed it. -
-Warning: -
When configuring a library that will be using this plugin in this "import" mode, it is advised to not include -the temporary download location within the library path. This may cause Unmanic to collect the incomplete -download especially with file monitor enabled. -
+- **Import episode on task complete** -#### Limit file import size -Only available if the *Import episode on task complete* mode is selected. + Use this mode when you are running Unmanic **prior** to importing a file into Sonarr. This will trigger a download import. -Enable limiting the Sonarr notification on items over the value specified in the *Minimum file size* option. + If possible, this will associate with a matching queued download and import the file that way. However, it is possible this will fail. If it does fail, it will fallback to providing the file path to Sonarr and allowing Sonarr to carry out a normal automated import by parsing the file name. + + :::warning + When configuring a library that will be using this plugin in this "import" mode, it is advised to not include the temporary download location within the library path. This may cause Unmanic to collect the incomplete download especially with file monitor enabled. + ::: + Configuration options: + - ###### Limit file import size -#### Minimum file size -Only available if the *Import episode on task complete* mode, and the *Limit file import size* -box is selected. + Enable limiting the Sonarr notification on items over the value specified in the *Minimum file size* option. -Sizes can be written as: + - ###### Minimum file size -- Bytes (Eg. '50' or '800 B') -- Kilobytes (Eg. '100KB' or '23 K') -- Megabytes (Eg. '9M' or '34 MB') -- Gigabytes (Eg. '4GB') -- etc... + Sizes can be written as: + - Bytes (Eg. '50' or '800 B') + - Kilobytes (Eg. '100KB' or '23 K') + - Megabytes (Eg. '9M' or '34 MB') + - Gigabytes (Eg. '4GB') + - etc... From ccd51721ed6d56f8b8e9db1be58ff2bbfb8a6fe0 Mon Sep 17 00:00:00 2001 From: "Josh.5" Date: Sat, 27 Aug 2022 16:18:11 +1200 Subject: [PATCH 4/4] Add tooltips and improvements to layout of plugin settings in UI --- plugin.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugin.py b/plugin.py index 6495513..d43f295 100644 --- a/plugin.py +++ b/plugin.py @@ -74,7 +74,9 @@ def __init__(self, *args, **kwargs): def __set_limit_import_on_file_size(self): values = { - "label": "Limit file import size", + "label": "Limit file import size", + "tooltip": "Enable limiting the Sonarr notification on items over a set file size", + "sub_setting": True, } if self.get_setting('mode') != 'import_mode': values["display"] = 'hidden' @@ -82,12 +84,14 @@ def __set_limit_import_on_file_size(self): def __set_minimum_file_size(self): values = { - "label": "Minimum file size", + "label": "Minimum file size", + "description": "Specify the minimum file size of a file that would trigger a notification", + "sub_setting": True, } if self.get_setting('mode') != 'import_mode': values["display"] = 'hidden' - if not self.get_setting('limit_import_on_file_size'): - values["display"] = 'hidden' + elif not self.get_setting('limit_import_on_file_size'): + values["display"] = 'disabled' return values