add filter by sonarr tags

pull/42/head
Junkbite 4 years ago
parent 0dd50a71eb
commit f3d8ffe436

@ -1,5 +1,5 @@
# Syncarr
Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a 4k radarr/sonarr instance to a 1080p radarr/sonarr instance.
Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a 4k Radarr/Sonarr instance to a 1080p Radarr/Sonarr instance.
* Supports Radarr/Sonarr version 2 and 3.
* Can sync by `profile` name or `profile_id`
@ -8,11 +8,12 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a
* Can set interval for syncing
* Support two way sync (one way by default)
* Set language profiles (Sonarr v3 only)
* Filter syncing by content file quality (Radarr only)
## Configuration
1. Edit the config.conf file and enter your servers URLs and API keys for each server.
2. Add the profile name (case insensitive) and movie path for the radarr instance the movies will be synced to:
2. Add the profile name (case insensitive) and movie path for the Radarr instance the movies will be synced to:
```ini
[radarrA]
url = https://4k.example.com:443
@ -24,7 +25,7 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a
profile = 1080p
path = /data/Movies
```
3. Or if you want to sync two sonarr instances:
3. Or if you want to sync two Sonarr instances:
```ini
[sonarrA]
url = https://4k.example.com:443
@ -36,7 +37,7 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a
profile = 1080p
path = /data/Shows
4. Or if you want to sync two lidarr instances:
4. Or if you want to sync two Lidarr instances:
```ini
[lidarrA]
url = https://lossless.example.com:443
@ -49,17 +50,19 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a
path = /data/Music
```
**Note** you cannot have a mix of radarr, lidarr, or sonarr config setups at the same time.
**Note** you cannot have a mix of Radarr, Lidarr, or Sonarr config setups at the same time.
5. Optional Configuration
```ini
[sonarrA]
[*arrA]
url = http://127.0.0.1:8080
key = XXXXX
profile_filter = 1080p # add a filter to only sync contents belonging to this profile (can set by profile_filter_id as well)
quality_match = HD- # (radarr only) regex match to only sync content that matches the set quality (ie if set to 1080p then only movies with matching downloaded quality of 1080p will be synced)
quality_match = HD- # (Radarr only) regex match to only sync content that matches the set quality (ie if set to 1080p then only movies with matching downloaded quality of 1080p will be synced)
tag_filter = Horror # (Sonarr only) sync movies by tag name (seperate multiple tags by comma (no spaces) ie horror,comedy,action)
tag_filter_id = 2 # (Sonarr only) sync movies by tag id (seperate multiple tags by comma (no spaces) ie 2,3,4)
[sonarrB]
[*arrB]
url = http://127.0.0.1:8080
key = XXXXX
profile_id = 1 # Syncarr will try to find id from name but you can specify the id directly if you want
@ -78,7 +81,7 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a
## Requirements
* Python 3.6 or greater
* 2x Radarr, Sonarr, or Lidarr servers
* 2 Radarr, Sonarr, or Lidarr servers
---

@ -100,6 +100,8 @@ sonarrA_profile = get_config_value('SONARR_A_PROFILE', 'profile', 'sonarrA')
sonarrA_profile_id = get_config_value('SONARR_A_PROFILE_ID', 'profile_id', 'sonarrA')
sonarrA_profile_filter = get_config_value('SONARR_A_PROFILE_FILTER', 'profile_filter', 'sonarrA')
sonarrA_profile_filter_id = get_config_value('SONARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'sonarrA')
sonarrA_tag_filter = get_config_value('SONARR_A_TAG_FILTER', 'tag_filter', 'sonarrA')
sonarrA_tag_filter_id = get_config_value('SONARR_A_TAG_FILTER_ID', 'tag_filter_id', 'sonarrA')
sonarrA_language = get_config_value('SONARR_A_LANGUAGE', 'language', 'sonarrA')
sonarrA_language_id = get_config_value('SONARR_A_LANGUAGE_ID', 'language_id', 'sonarrA')
sonarrA_quality_match = get_config_value('SONARR_A_QUALITY_MATCH', 'quality_match', 'sonarrA')
@ -111,6 +113,8 @@ sonarrB_profile = get_config_value('SONARR_B_PROFILE', 'profile', 'sonarrB')
sonarrB_profile_id = get_config_value('SONARR_B_PROFILE_ID', 'profile_id', 'sonarrB')
sonarrB_profile_filter = get_config_value('SONARR_B_PROFILE_FILTER', 'profile_filter', 'sonarrB')
sonarrB_profile_filter_id = get_config_value('SONARR_B_PROFILE_FILTER_ID', 'profile_filter_id', 'sonarrB')
sonarrB_tag_filter = get_config_value('SONARR_B_TAG_FILTER', 'tag_filter', 'sonarrB')
sonarrB_tag_filter_id = get_config_value('SONARR_B_TAG_FILTER_ID', 'tag_filter_id', 'sonarrB')
sonarrB_language = get_config_value('SONARR_B_LANGUAGE', 'language', 'sonarrB')
sonarrB_language_id = get_config_value('SONARR_B_LANGUAGE_ID', 'language_id', 'sonarrB')
sonarrB_quality_match = get_config_value('SONARR_B_QUALITY_MATCH', 'quality_match', 'sonarrB')
@ -228,6 +232,8 @@ instanceA_profile_filter_id = ''
instanceA_language = ''
instanceA_language_id = ''
instanceA_quality_match = ''
instanceA_tag_filter = ''
instanceA_tag_filter_id = ''
instanceB_url = ''
instanceB_key = ''
@ -239,16 +245,18 @@ instanceB_profile_filter_id = ''
instanceB_language = ''
instanceB_language_id = ''
instanceB_quality_match = ''
instanceB_tag_filter = ''
instanceB_tag_filter_id = ''
api_version = '' # we are going to detect what API version we are on
tested_api_version = False # only get api version once
api_content_path = '' # url path to add content
api_profile_path = '' # url path to get quality profiles
api_status_path = '' # url path to check on server status
api_language_path = '' # url to get lanaguge profiles
api_tag_path = '' # url to get tag profiles
is_radarr = False
is_sonarr = False
@ -328,6 +336,8 @@ elif sonarrA_url and sonarrB_url:
instanceA_profile_filter_id = sonarrA_profile_filter_id
instanceA_language = sonarrA_language
instanceA_language_id = sonarrA_language_id
instanceA_tag_filter = sonarrA_tag_filter and sonarrA_tag_filter.split(',')
instanceA_tag_filter_id = sonarrA_tag_filter_id and sonarrA_tag_filter_id.split(',')
instanceA_quality_match = sonarrA_quality_match
instanceB_url = sonarrB_url
@ -339,6 +349,8 @@ elif sonarrA_url and sonarrB_url:
instanceB_profile_filter_id = sonarrB_profile_filter_id
instanceB_language = sonarrB_language
instanceB_language_id = sonarrB_language_id
instanceB_tag_filter = sonarrB_tag_filter and sonarrB_tag_filter.split(',')
instanceB_tag_filter_id = sonarrB_tag_filter_id and sonarrB_tag_filter_id.split(',')
instanceB_quality_match = sonarrB_quality_match
api_version = V3_API_PATH # for sonarr try v3 first
@ -346,6 +358,7 @@ elif sonarrA_url and sonarrB_url:
api_profile_path = 'qualityprofile'
api_status_path = 'system/status'
api_language_path = 'languageprofile'
api_tag_path = 'tag'
content_id_key = 'tvdbId'
is_sonarr = True
@ -353,7 +366,6 @@ elif sonarrA_url and sonarrB_url:
########################################################################################################################
# path generators
def get_path(instance_url, api_path, key, changed_api_version=False):
global api_version, api_profile_path
@ -378,34 +390,34 @@ def get_path(instance_url, api_path, key, changed_api_version=False):
url = f"{instance_url}/api/{api_version}{api_path}?apikey={key}"
return url
def get_status_path(instance_url, key, changed_api_version):
url = get_path(instance_url, api_status_path, key, changed_api_version)
logger.debug('get_status_path: {}'.format(url))
return url
def get_content_path(instance_url, key):
url = get_path(instance_url, api_content_path, key)
logger.debug('get_content_path: {}'.format(url))
return url
def get_language_path(instance_url, key):
url = get_path(instance_url, api_language_path, key)
logger.debug('get_language_path: {}'.format(url))
return url
def get_profile_path(instance_url, key):
url = get_path(instance_url, api_profile_path, key)
logger.debug('get_profile_path: {}'.format(url))
return url
def get_tag_path(instance_url, key):
url = get_path(instance_url, api_tag_path, key)
logger.debug('get_tag_path: {}'.format(url))
return url
########################################################################################################################
# check for required fields
logger.debug({
'instanceA_url': instanceA_url,
'instanceA_key': instanceA_key,
@ -416,6 +428,8 @@ logger.debug({
'instanceA_profile_filter_id': instanceA_profile_filter_id,
'instanceA_language': instanceA_language,
'instanceA_language_id': instanceA_language_id,
'instanceA_tag_filter': instanceA_tag_filter,
'instanceA_tag_filter_id': instanceA_tag_filter_id,
'instanceA_quality_match': instanceA_quality_match,
'instanceB_url': instanceB_url,
@ -427,6 +441,8 @@ logger.debug({
'instanceB_profile_filter_id': instanceB_profile_filter_id,
'instanceB_language': instanceB_language,
'instanceB_language_id': instanceB_language_id,
'instanceB_tag_filter': instanceB_tag_filter,
'instanceB_tag_filter_id': instanceB_tag_filter_id,
'instanceB_quality_match': instanceB_quality_match,
'api_content_path': api_content_path,

@ -13,13 +13,15 @@ from config import (
instanceA_url, instanceA_key, instanceA_path, instanceA_profile,
instanceA_profile_id, instanceA_profile_filter, instanceA_profile_filter_id,
instanceA_language_id, instanceA_language, instanceA_quality_match,
instanceA_tag_filter_id, instanceA_tag_filter,
instanceB_url, instanceB_key, instanceB_path, instanceB_profile,
instanceB_profile_id, instanceB_profile_filter, instanceB_profile_filter_id,
instanceB_language_id, instanceB_language, instanceB_quality_match,
instanceB_tag_filter_id, instanceB_tag_filter,
content_id_key, logger, is_sonarr, is_radarr, is_lidarr,
get_status_path, get_content_path, get_profile_path, get_language_path,
get_status_path, get_content_path, get_profile_path, get_language_path, get_tag_path,
is_in_docker, instance_sync_interval_seconds,
sync_bidirectionally, auto_search, monitor_new_content,
@ -106,6 +108,7 @@ def get_quality_profiles(instance_session, instance_url, instance_key):
logger.error(f'Could not decode profile id from {instance_profile_url}')
sys.exit(0)
def get_profile_from_id(instance_session, instance_url, instance_key, instance_profile, instance_name=''):
instance_profiles = get_quality_profiles(instance_session=instance_session, instance_url=instance_url, instance_key=instance_key)
@ -120,27 +123,57 @@ def get_profile_from_id(instance_session, instance_url, instance_key, instance_p
return instance_profile_id
def get_tag_from_id(instance_session, instance_url, instance_key, instance_tag, instance_name=''):
instance_tag_url = get_tag_path(instance_url, instance_key)
tag_response = instance_session.get(instance_tag_url)
if tag_response.status_code != 200:
logger.error(f'Could not get tag id from (instance{instance_name}) {instance_tag_url} - only works on Sonarr')
sys.exit(0)
instance_tags = None
try:
instance_tags = tag_response.json()
except:
logger.error(f'Could not decode tag id from {instance_tag_url}')
sys.exit(0)
tag_ids = []
for item in instance_tags:
for instance_item in instance_tag:
if item.get('label').lower() == instance_item.lower():
tag_ids.append(item)
if not tag_ids:
logger.error(f'Could not find tag_id for instance {instance_name} and tag {instance_tags}')
sys.exit(0)
instance_tag_ids = [tag.get('id') for tag in tag_ids]
logger.debug(f'found id "{instance_tag_ids}" from tag "{instance_tag}" for instance {instance_name}')
if instance_tag_ids is None:
logger.error(f'tag_id is None for instance {instance_name} and tag {instance_tag}')
sys.exit(0)
return instance_tag_ids
def get_language_from_id(instance_session, instance_url, instance_key, instance_language, instance_name=''):
instance_language_url = get_language_path(instance_url, instance_key)
language_response = instance_session.get(instance_language_url)
if language_response.status_code != 200:
logger.error(
f'Could not get language id from (instance{instance_name}) {instance_language_url} - only works on sonarr v3')
logger.error(f'Could not get language id from (instance{instance_name}) {instance_language_url} - only works on sonarr v3')
sys.exit(0)
instance_languages = None
try:
instance_languages = language_response.json()
except:
logger.error(
f'Could not decode language id from {instance_language_url}')
logger.error(f'Could not decode language id from {instance_language_url}')
sys.exit(0)
instance_languages = instance_languages[0]['languages']
language = next((item for item in instance_languages
if item.get('language', {}).get('name').lower() == instance_language.lower()), False)
language = next((item for item in instance_languages if item.get('language', {}).get('name').lower() == instance_language.lower()), False)
logger.error(language)
if not language:
logger.error(f'Could not find language_id for instance {instance_name} and language {instance_language}')
sys.exit(0)
@ -157,8 +190,9 @@ def get_language_from_id(instance_session, instance_url, instance_key, instance_
def sync_servers(instanceA_contents, instanceB_language_id, instanceB_contentIds,
instanceB_path, instanceB_profile_id, instanceA_profile_filter_id,
instanceB_session, instanceB_url, instanceB_key, instanceA_quality_match):
global is_radarr
instanceB_session, instanceB_url, instanceB_key, instanceA_quality_match,
instanceA_tag_filter_id):
global is_radarr, is_sonarr
search_ids = []
# if given instance A profile id then we want to filter out content without that id
@ -184,6 +218,13 @@ def sync_servers(instanceA_contents, instanceB_language_id, instanceB_contentIds
logging.debug(f'Skipping content {title} - mismatched content_quality {content_quality} with instanceA_quality_match {instanceA_quality_match}')
continue
# if given tag filter then filter by tag - (Sonarr only)
if is_sonarr and instanceA_tag_filter_id:
content_tag_ids = content.get('tags')
if not (set(content_tag_ids) & set(instanceA_tag_filter_id)):
logging.debug(f'Skipping content {title} - mismatched content_tag_ids {content_tag_ids} with instanceA_tag_filter_id {instanceA_tag_filter_id}')
continue
logging.info(f'syncing content title "{title}"')
# get the POST payload and sync content to instance B
@ -280,7 +321,7 @@ def check_status(instance_session, instance_url, instance_key, instance_name='',
def sync_content():
global instanceA_profile_id, instanceA_profile, instanceB_profile_id, instanceB_profile, instanceA_profile_filter, instanceA_profile_filter_id, instanceB_profile_filter, instanceB_profile_filter_id, tested_api_version, instanceA_language_id, instanceA_language, instanceB_language_id, instanceB_language, instanceA_quality_match, instanceB_quality_match
global instanceA_profile_id, instanceA_profile, instanceB_profile_id, instanceB_profile, instanceA_profile_filter, instanceA_profile_filter_id, instanceB_profile_filter, instanceB_profile_filter_id, tested_api_version, instanceA_language_id, instanceA_language, instanceB_language_id, instanceB_language, instanceA_quality_match, instanceB_quality_match, is_sonarr, instanceA_tag_filter_id, instanceA_tag_filter, instanceB_tag_filter_id, instanceB_tag_filter
# get sessions
instanceA_session = requests.Session()
@ -295,11 +336,9 @@ def sync_content():
# if given a profile instead of a profile id then try to find the profile id
if not instanceA_profile_id and instanceA_profile:
instanceA_profile_id = get_profile_from_id(
instanceA_session, instanceA_url, instanceA_key, instanceA_profile, 'A')
instanceA_profile_id = get_profile_from_id(instanceA_session, instanceA_url, instanceA_key, instanceA_profile, 'A')
if not instanceB_profile_id and instanceB_profile:
instanceB_profile_id = get_profile_from_id(
instanceB_session, instanceB_url, instanceB_key, instanceB_profile, 'B')
instanceB_profile_id = get_profile_from_id(instanceB_session, instanceB_url, instanceB_key, instanceB_profile, 'B')
logger.debug({
'instanceA_profile_id': instanceA_profile_id,
'instanceA_profile': instanceA_profile,
@ -309,11 +348,9 @@ def sync_content():
# do the same for profile id filters if they exist
if not instanceA_profile_filter_id and instanceA_profile_filter:
instanceA_profile_filter_id = get_profile_from_id(
instanceA_session, instanceA_url, instanceA_key, instanceA_profile_filter, 'A')
instanceA_profile_filter_id = get_profile_from_id(instanceA_session, instanceA_url, instanceA_key, instanceA_profile_filter, 'A')
if not instanceB_profile_filter_id and instanceB_profile_filter:
instanceB_profile_filter_id = get_profile_from_id(
instanceB_session, instanceB_url, instanceB_key, instanceB_profile_filter, 'B')
instanceB_profile_filter_id = get_profile_from_id(instanceB_session, instanceB_url, instanceB_key, instanceB_profile_filter, 'B')
logger.debug({
'instanceAprofile_filter_id': instanceA_profile_filter_id,
'instanceAprofile_filter': instanceA_profile_filter,
@ -321,8 +358,20 @@ def sync_content():
'instanceBprofile_filter': instanceB_profile_filter,
})
# if given language instead of language id then try to find the lanaguage id
# only for sonarr v3
# do the same for tag id filters if they exist - (only Sonarr)
if is_sonarr:
if not instanceA_tag_filter_id and instanceA_tag_filter:
instanceA_tag_filter_id = get_tag_from_id(instanceA_session, instanceA_url, instanceA_key, instanceA_tag_filter, 'A')
if not instanceB_tag_filter_id and instanceB_tag_filter:
instanceB_tag_filter_id = get_tag_from_id(instanceB_session, instanceB_url, instanceB_key, instanceA_tag_filter, 'B')
logger.debug({
'instanceA_tag_filter': instanceA_tag_filter,
'instanceA_profile_filter': instanceA_profile_filter,
'instanceB_tag_filter_id': instanceB_tag_filter_id,
'instanceB_tag_filter': instanceB_tag_filter,
})
# if given language instead of language id then try to find the lanaguage id - (only Sonarr v3)
if is_sonarr:
if not instanceA_language_id and instanceA_language:
instanceA_language_id = get_language_from_id(
@ -341,15 +390,14 @@ def sync_content():
instance_language=instanceB_language,
instance_name='B'
)
logger.debug({
'instanceA_language_id': instanceA_language_id,
'instanceA_language': instanceA_language,
'instanceB_language_id': instanceB_language_id,
'instanceB_language': instanceB_language,
'is_sonarr': is_sonarr,
'api_version': api_version,
})
logger.debug({
'instanceA_language_id': instanceA_language_id,
'instanceA_language': instanceA_language,
'instanceB_language_id': instanceB_language_id,
'instanceB_language': instanceB_language,
'is_sonarr': is_sonarr,
'api_version': api_version,
})
# get contents to compare
instanceA_contents, instanceA_contentIds = get_instance_contents(instanceA_url, instanceA_key, instanceA_session, instance_name='A')
@ -367,6 +415,7 @@ def sync_content():
instanceA_profile_filter_id=instanceA_profile_filter_id,
instanceB_key=instanceB_key,
instanceA_quality_match=instanceA_quality_match,
instanceA_tag_filter_id=instanceA_tag_filter_id,
)
# if given bidirectional flag then sync from instance B to instance A
@ -384,6 +433,7 @@ def sync_content():
instanceA_profile_filter_id=instanceB_profile_filter_id,
instanceB_key=instanceA_key,
instanceA_quality_match=instanceB_quality_match,
instanceA_tag_filter_id=instanceB_tag_filter_id,
)
########################################################################################################################

Loading…
Cancel
Save