Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)
65 changes: 32 additions & 33 deletions description.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,49 @@

### Config description:
---

##### Links:

#### <span style="color:blue">Sonarr LAN IP Address</span>
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)

---

#### <span style="color:blue">Sonarr API Key</span>
Sonarr application API key
##### Plugin Settings:

###### <span style="color:blue">Sonarr LAN IP Address</span>
The protocol and IP address of the Sonarr application

#### <span style="color:blue">Mode</span>
###### <span style="color:blue">Sonarr API Key</span>
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.
###### <span style="color:blue">Mode</span>
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.

<div style="background-color:pink;border-radius:4px;border-left:solid 5px red;padding:10px;">
<b>Warning:</b>
<br>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.
</div>
- **Import episode on task complete**

#### <span style="color:blue">Limit file import size</span>
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:
- ###### <span style="color:blue">Limit file import size</span>

#### <span style="color:blue">Minimum file size</span>
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:
- ###### <span style="color:blue">Minimum file size</span>

- Bytes (Eg. '<span style="color:blue">50</span>' or '<span style="color:blue">800 B</span>')
- Kilobytes (Eg. '<span style="color:blue">100KB</span>' or '<span style="color:blue">23 K</span>')
- Megabytes (Eg. '<span style="color:blue">9M</span>' or '<span style="color:blue">34 MB</span>')
- Gigabytes (Eg. '<span style="color:blue">4GB</span>')
- etc...
Sizes can be written as:
- Bytes (Eg. '<span style="color:blue">50</span>' or '<span style="color:blue">800 B</span>')
- Kilobytes (Eg. '<span style="color:blue">100KB</span>' or '<span style="color:blue">23 K</span>')
- Megabytes (Eg. '<span style="color:blue">9M</span>' or '<span style="color:blue">34 MB</span>')
- Gigabytes (Eg. '<span style="color:blue">4GB</span>')
- etc...
3 changes: 3 additions & 0 deletions info.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
110 changes: 91 additions & 19 deletions plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@
If not, see <https://www.gnu.org/licenses/>.

"""
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
Expand Down Expand Up @@ -71,23 +74,91 @@ 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'
return values

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


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):
Expand Down Expand Up @@ -119,7 +190,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
Expand All @@ -135,16 +205,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
Expand All @@ -159,7 +230,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')

Expand All @@ -168,11 +239,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)


Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
pyarr==3.0.1
humanfriendly>=9.1