add lanague profile option

pull/21/head
Leonardo Merza 4 years ago
parent 3f1e9ccd3b
commit cfd90a0401

@ -56,7 +56,7 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a
If `sync_bidirectionally` is set to true, then instance A will require either `profile_id` or `profile` AND `path`
6. syncarr will try to find the `profile_id` given a `profile` name, if no match is found, syncarr will exit with error. You can also specify a `profile_id` directly instead of a `profile` name:
```ini
```ini
[radarrB]
url = http://127.0.0.1:8080
key = XXXXX
@ -66,11 +66,19 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a
7. You can filter content to be synced only from a certain profile/profile_id by adding the `profile_filter` or `profile_filter_id` to instance A. The same goes to instance B if syncing bidirectionally.
```ini
[radarrA]
[radarrA]
url = http://127.0.0.1:8080
key = XXXXX
profile_filter = 1080p
```
8. Sonarr v3 can specify a `language` or `langauge_id` (for Docker `SONARR_A/B_LANGUAGE` or `SONARR_A/B_LANGUAGE_ID`) to specify a show's language when copying over a show. When syncing SonarrA to SonarrB:
```ini
[sonarrB]
url = http://127.0.0.1:8080
key = XXXXX
language = Vietnamese # when using docker -> SONARR_B_LANGUAGE: Vietnamese
```
---
## Notes

@ -0,0 +1,407 @@
#!/usr/bin/env python
import logging
import os
import sys
import time
import configparser
DEV = os.environ.get('DEV')
VER = '1.4.1'
V3_API_PATH = 'v3/'
def ConfigSectionMap(section):
'''get all config options from config file'''
dict1 = {}
options = config.options(section)
for option in options:
try:
dict1[option] = config.get(section, option)
except:
print("exception on %s!" % option)
dict1[option] = None
return dict1
def get_config_value(env_key, config_key, config_section):
if is_in_docker:
return os.environ.get(env_key)
try:
_config = ConfigSectionMap(config_section)
return _config.get(config_key)
except configparser.NoSectionError:
return ''
########################################################################################################################
# load config file
BASE_CONFIG = 'config.conf'
if DEV:
settingsFilename = os.path.join(os.getcwd(), 'dev-{}'.format(BASE_CONFIG))
else:
settingsFilename = os.path.join(os.getcwd(), BASE_CONFIG)
config = configparser.ConfigParser()
config.read(settingsFilename)
########################################################################################################################
# get docker based ENV vars
is_in_docker = os.environ.get('IS_IN_DOCKER')
instance_sync_interval_seconds = os.environ.get('SYNC_INTERVAL_SECONDS')
if instance_sync_interval_seconds:
instance_sync_interval_seconds = int(instance_sync_interval_seconds)
########################################################################################################################
# get config settings from ENV or config files for Radarr
radarrA_url = get_config_value('RADARR_A_URL', 'url', 'radarrA')
radarrA_key = get_config_value('RADARR_A_KEY', 'key', 'radarrA')
radarrA_profile = get_config_value('RADARR_A_PROFILE', 'profile', 'radarrA')
radarrA_profile_id = get_config_value(
'RADARR_A_PROFILE_ID', 'profile_id', 'radarrA')
radarrA_profile_filter = get_config_value(
'RADARR_A_PROFILE_FILTER', 'profile_filter', 'radarrA')
radarrA_profile_filter_id = get_config_value(
'RADARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'radarrA')
radarrA_path = get_config_value('RADARR_A_PATH', 'path', 'radarrA')
radarrB_url = get_config_value('RADARR_B_URL', 'url', 'radarrB')
radarrB_key = get_config_value('RADARR_B_KEY', 'key', 'radarrB')
radarrB_profile = get_config_value('RADARR_B_PROFILE', 'profile', 'radarrB')
radarrB_profile_id = get_config_value(
'RADARR_B_PROFILE_ID', 'profile_id', 'radarrB')
radarrB_profile_filter = get_config_value(
'RADARR_B_PROFILE_FILTER', 'profile_filter', 'radarrB')
radarrB_profile_filter_id = get_config_value(
'RADARR_B_PROFILE_FILTER_ID', 'profile_filter_id', 'radarrB')
radarrB_path = get_config_value('RADARR_B_PATH', 'path', 'radarrB')
# get config settings from ENV or config files for Sonarr
sonarrA_url = get_config_value('SONARR_A_URL', 'url', 'sonarrA')
sonarrA_key = get_config_value('SONARR_A_KEY', 'key', 'sonarrA')
sonarrA_path = get_config_value('SONARR_A_PATH', 'path', 'sonarrA')
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_language = get_config_value('SONARR_A_LANGUAGE', 'language', 'sonarrA')
sonarrA_language_id = get_config_value('SONARR_A_LANGUAGE_ID', 'language_id', 'sonarrA')
sonarrB_url = get_config_value('SONARR_B_URL', 'url', 'sonarrB')
sonarrB_key = get_config_value('SONARR_B_KEY', 'key', 'sonarrB')
sonarrB_path = get_config_value('SONARR_B_PATH', 'path', 'sonarrB')
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_A_PROFILE_FILTER', 'profile_filter', 'sonarrB')
sonarrB_profile_filter_id = get_config_value(
'SONARR_A_PROFILE_FILTER_ID', 'profile_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')
# get config settings from ENV or config files for Lidarr
lidarrA_url = get_config_value('LIDARR_A_URL', 'url', 'lidarrA')
lidarrA_key = get_config_value('LIDARR_A_KEY', 'key', 'lidarrA')
lidarrA_profile = get_config_value('LIDARR_A_PROFILE', 'profile', 'lidarrA')
lidarrA_profile_id = get_config_value(
'LIDARR_A_PROFILE_ID', 'profile_id', 'lidarrA')
lidarrA_profile_filter = get_config_value(
'LIDARR_A_PROFILE_FILTER', 'profile_filter', 'lidarrA')
lidarrA_profile_filter_id = get_config_value(
'LIDARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'lidarrA')
lidarrA_path = get_config_value('LIDARR_A_PATH', 'path', 'lidarrA')
lidarrB_url = get_config_value('LIDARR_B_URL', 'url', 'lidarrB')
lidarrB_key = get_config_value('LIDARR_B_KEY', 'key', 'lidarrB')
lidarrB_profile = get_config_value('LIDARR_B_PROFILE', 'profile', 'lidarrB')
lidarrB_profile_id = get_config_value(
'LIDARR_B_PROFILE_ID', 'profile_id', 'lidarrB')
lidarrB_profile_filter = get_config_value(
'LIDARR_A_PROFILE_FILTER', 'profile_filter', 'lidarrB')
lidarrB_profile_filter_id = get_config_value(
'LIDARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'lidarrB')
lidarrB_path = get_config_value('LIDARR_B_PATH', 'path', 'lidarrB')
# get general conf options
sync_bidirectionally = get_config_value(
'SYNCARR_BIDIRECTIONAL_SYNC', 'bidirectional', 'general') or 0
if sync_bidirectionally:
sync_bidirectionally = int(sync_bidirectionally) or 0
########################################################################################################################
# setup logger
# CRITICAL 50, ERROR 40, WARNING 3, INFO 20, DEBUG 10, NOTSET 0
log_level = get_config_value('LOG_LEVEL', 'log_level', 'general') or 20
if log_level:
log_level = int(log_level)
logger = logging.getLogger()
logger.setLevel(log_level)
logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s")
# log to txt file
fileHandler = logging.FileHandler("./output.txt")
fileHandler.setFormatter(logFormatter)
logger.addHandler(fileHandler)
# log to std out
consoleHandler = logging.StreamHandler(sys.stdout)
consoleHandler.setFormatter(logFormatter)
logger.addHandler(consoleHandler)
logger.debug('Syncarr Version {}'.format(VER))
logger.info('log level {}'.format(log_level))
if DEV:
logger.info('-----------------DEVV-----------------')
########################################################################################################################
# make sure we have radarr, lidarr, OR sonarr
if (
(sonarrA_url and radarrA_url) or
(sonarrA_url and radarrB_url) or
(sonarrA_url and lidarrA_url) or
(sonarrA_url and lidarrB_url) or
(radarrA_url and lidarrA_url) or
(radarrA_url and lidarrB_url) or
(radarrB_url and lidarrA_url) or
(radarrB_url and lidarrB_url)
):
logger.error('cannot have more than one *arr type profile(s) setup at the same time')
sys.exit(0)
########################################################################################################################
# get generic instanceA/B variables
instanceA_url = ''
instanceA_key = ''
instanceA_path = ''
instanceA_profile = ''
instanceA_profile_id = ''
instanceA_profile_filter = ''
instanceA_language_id = ''
instanceA_language = ''
instanceB_url = ''
instanceB_key = ''
instanceB_path = ''
instanceB_profile = ''
instanceB_profile_id = ''
instanceB_profile_filter = ''
instanceB_language_id = ''
instanceB_language = ''
api_version = 'v1/' # 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_search_path = '' # url path to search for content on RSS feeds
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
is_radarr = False
is_sonarr = False
is_lidarr = False
content_id_key = '' # the unique id for a content item
if radarrA_url and radarrB_url:
instanceA_url = radarrA_url
instanceA_key = radarrA_key
instanceA_profile = radarrA_profile
instanceA_profile_id = radarrA_profile_id
instanceA_profile_filter = radarrA_profile_filter
instanceA_profile_filter_id = radarrA_profile_filter_id
instanceA_path = radarrA_path
instanceB_url = radarrB_url
instanceB_key = radarrB_key
instanceB_profile = radarrB_profile
instanceB_profile_id = radarrB_profile_id
instanceB_profile_filter = radarrB_profile_filter
instanceB_profile_filter_id = radarrB_profile_filter_id
instanceB_path = radarrB_path
api_version = '' # radarr v2 doesnt have version in api url
api_content_path = 'movie'
api_search_path = 'command'
api_profile_path = 'profile'
api_status_path = 'system/status'
content_id_key = 'tmdbId'
is_radarr = True
elif lidarrA_url and lidarrB_url:
instanceA_url = lidarrA_url
instanceA_key = lidarrA_key
instanceA_profile = lidarrA_profile
instanceA_profile_id = lidarrA_profile_id
instanceA_profile_filter = lidarrA_profile_filter
instanceA_profile_filter_id = lidarrA_profile_filter_id
instanceA_path = lidarrA_path
instanceB_url = lidarrB_url
instanceB_key = lidarrB_key
instanceB_profile = lidarrB_profile
instanceB_profile_id = lidarrB_profile_id
instanceB_profile_filter = lidarrB_profile_filter
instanceB_profile_filter_id = lidarrB_profile_filter_id
instanceB_path = lidarrB_path
api_version = 'v1/'
api_content_path = 'artist'
api_search_path = 'command'
api_profile_path = 'qualityprofile'
api_status_path = 'system/status'
content_id_key = 'foreignArtistId'
is_lidarr = True
elif sonarrA_url and sonarrB_url:
instanceA_url = sonarrA_url
instanceA_key = sonarrA_key
instanceA_path = sonarrA_path
instanceA_profile = sonarrA_profile
instanceA_profile_id = sonarrA_profile_id
instanceA_profile_filter = sonarrA_profile_filter
instanceA_profile_filter_id = sonarrA_profile_filter_id
instanceA_language = sonarrA_language
instanceA_language_id = sonarrA_language_id
instanceB_url = sonarrB_url
instanceB_key = sonarrB_key
instanceB_path = sonarrB_path
instanceB_profile = sonarrB_profile
instanceB_profile_id = sonarrB_profile_id
instanceB_profile_filter = sonarrB_profile_filter
instanceB_profile_filter_id = sonarrB_profile_filter_id
instanceB_language = sonarrB_language
instanceB_language_id = sonarrB_language_id
api_version = ''
api_content_path = 'series'
api_search_path = 'command'
api_profile_path = 'profile'
api_status_path = 'system/status'
api_language_path = 'languageprofile'
content_id_key = 'tvdbId'
is_sonarr = True
########################################################################################################################
# path generators
def get_path(instance_url, api_path, key, checkV3=False):
global api_version, api_profile_path
if not tested_api_version:
logger.debug(f'checkV3: "{checkV3}" for {instance_url}')
if checkV3:
api_version = V3_API_PATH
if checkV3 and is_sonarr:
api_profile_path = 'qualityprofile'
url = f"{instance_url}/api/{api_version}{api_path}?apikey={key}"
return url
def get_status_path(instance_url, key, checkV3):
url = get_path(instance_url, api_status_path, key, checkV3)
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_search_path(instance_url, key):
url = get_path(instance_url, api_search_path, key)
logger.debug('get_search_path: {}'.format(url))
return url
def get_language_path(instance_url, key):
print('-'*20)
print(instance_url)
print('-'*20)
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
########################################################################################################################
# check for required fields
logger.debug({
'instanceA_url': instanceA_url,
'instanceA_key': instanceA_key,
'instanceB_path': instanceB_path,
'instanceB_url': instanceB_url,
'instanceB_key': instanceB_key,
'instanceB_path': instanceB_path,
'api_content_path': api_content_path,
'api_search_path': api_search_path,
'api_language_path': api_language_path,
'is_sonarr': is_sonarr,
'is_lidarr': is_lidarr,
})
if not instanceA_url:
logger.error('missing URL for instance A')
sys.exit(0)
if not instanceA_key:
logger.error('missing API key for instance A')
sys.exit(0)
if not instanceA_url:
logger.error('missing URL for instance B')
sys.exit(0)
if not instanceB_key:
logger.error('missing API key for instance B')
sys.exit(0)
if not api_content_path:
logger.error('missing api_content_path')
sys.exit(0)
if not api_search_path:
logger.error('missing api_search_path')
sys.exit(0)
if not content_id_key:
logger.error('missing content_id_key')
sys.exit(0)
# if two way sync need instance A path and profile
if sync_bidirectionally:
assert instanceA_path
if not instanceB_profile_id and not instanceB_profile:
logger.error('profile_id or profile is required for *arr instance A if sync bidirectionally is enabled')
sys.exit(0)
if not instanceB_profile_id and not instanceB_profile:
logger.error('profile_id or profile is required for *arr instance B')
sys.exit(0)

@ -8,15 +8,28 @@ import configparser
import sys
import time
from syncarr.config import *
from syncarr.paths import *
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,
instanceB_url, instanceB_key, instanceB_path, instanceB_profile,
instanceB_profile_id, instanceB_profile_filter, instanceB_profile_filter_id,
instanceB_language_id, instanceB_language,
def get_new_content_payload(content, instance_path, instance_profile_id, instanceB_url):
content_id_key, logger, is_sonarr, is_radarr, is_lidarr,
get_status_path, get_content_path, get_search_path, get_profile_path, get_language_path,
is_in_docker, instance_sync_interval_seconds, sync_bidirectionally,
tested_api_version, api_version, V3_API_PATH,
)
def get_new_content_payload(content, instance_path, instance_profile_id, instance_url, instance_language_id=None):
images = content.get('images')
for image in images:
image['url'] = '{0}{1}'.format(instanceB_url, image.get('url'))
image['url'] = '{0}{1}'.format(instance_url, image.get('url'))
payload = {
content_id_key: content.get(content_id_key),
@ -32,7 +45,8 @@ def get_new_content_payload(content, instance_path, instance_profile_id, instanc
payload['seasons'] = content.get('seasons')
payload['tvRageId'] = content.get('tvRageId')
payload['seasonFolder'] = content.get('seasonFolder')
payload['languageProfileId'] = content.get('languageProfileId')
payload['languageProfileId'] = instance_language_id if instance_language_id is not None else content.get(
'languageProfileId')
payload['tags'] = content.get('tags')
payload['seriesType'] = content.get('seriesType')
payload['useSceneNumbering'] = content.get('useSceneNumbering')
@ -42,6 +56,7 @@ def get_new_content_payload(content, instance_path, instance_profile_id, instanc
payload['title'] = content.get('title')
payload['tmdbId'] = content.get('tmdbId')
payload['titleSlug'] = content.get('titleSlug')
elif is_lidarr:
payload['artistName'] = content.get('artistName')
payload['addOptions'] = content.get('addOptions', {})
@ -54,35 +69,76 @@ def get_new_content_payload(content, instance_path, instance_profile_id, instanc
def get_profile_from_id(instance_session, instance_url, instance_key, instance_profile, instance_name=''):
instance_profile_url = get_profile_path(instance_url, instance_key)
instance_profiles = instance_session.get(instance_profile_url)
profiles_response = instance_session.get(instance_profile_url)
if profiles_response.status_code != 200:
logger.error(
f'Could not get profile id from {instance_profile_url}')
sys.exit(0)
instance_profiles = None
try:
instance_profiles = instance_profiles.json()
instance_profiles = profiles_response.json()
except:
logger.error(f'Could not decode profile id from {instance_profile_url}')
logger.error(
f'Could not decode profile id from {instance_profile_url}')
sys.exit(0)
profile = next((item for item in instance_profiles if item["name"].lower() == instance_profile.lower()), False)
if not profile:
logger.error('Could not find profile_id for instance {} profile {}'.format(instance_name, instance_profile))
logger.error('Could not find profile_id for instance {} profile {}'.format(
instance_name, instance_profile))
sys.exit(0)
instance_profile_id = profile.get('id')
logger.debug('found profile_id "{}" from profile "{}" for instance {}'.format(instance_profile_id, instance_profile, instance_name))
logger.debug('found profile_id "{}" from profile "{}" for instance {}'.format(
instance_profile_id, instance_profile, instance_name))
return instance_profile_id
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_language_url} - only works on sonarr v3')
return None
instance_languages = None
try:
instance_languages = language_response.json()
except:
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["name"].lower() == instance_language.lower()), False)
if not language:
logger.error('Could not find language_id for instance {} language {}'.format(
instance_name, instance_language))
sys.exit(0)
instance_language_id = language.get('id')
logger.debug('found language_id "{}" from language "{}" for instance {}'.format(
instance_language_id, instance_language, instance_name))
return instance_language_id
def search_synced(search_ids, instance_search_url, instance_session):
# now that we've synced all contents search for the newly synced contents
instanceA_search_url = get_search_path(instanceA_url, instanceA_key)
instance_search_url = get_search_path(instanceA_url, instanceA_key)
if len(search_ids):
payload = { 'name': 'contentsSearch', 'contentIds': search_ids }
instance_session.post(instance_search_url, data=json.dumps(payload))
def sync_servers(instanceA_contents, instanceB_contentIds, instanceB_path, instanceB_profile_id,
instanceB_session, instanceB_url, profile_filter_id, instanceB_key):
def sync_servers(instanceA_contents, instanceB_language_id, instanceB_contentIds,
instanceB_path, instanceB_profile_id, instanceB_session,
instanceB_url, profile_filter_id, instanceB_key):
search_ids = []
@ -102,7 +158,13 @@ def sync_servers(instanceA_contents, instanceB_contentIds, instanceB_path, insta
logging.info('syncing content title "{0}"'.format(title))
# get the POST payload and sync content to instance B
payload = get_new_content_payload(content, instanceB_path, instanceB_profile_id, instanceB_url)
payload = get_new_content_payload(
content=content,
instance_path=instanceB_path,
instance_profile_id=instanceB_profile_id,
instance_url=instanceB_url,
instance_language_id=instanceA_language_id,
)
instanceB_content_url = get_content_path(instanceB_url, instanceB_key)
sync_response = instanceB_session.post(instanceB_content_url, data=json.dumps(payload))
@ -127,7 +189,7 @@ def get_instance_contents(instance_url, instance_key, instance_session, instance
instance_content_url = get_content_path(instance_url, instance_key)
instance_contents = instance_session.get(instance_content_url)
if instance_contents.status_code != requests.codes.ok:
if instance_contents.status_code != 200:
logger.error('instance{} server error - response {}'.format(instance_name, instance_contents.status_code))
sys.exit(0)
else:
@ -187,7 +249,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
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
# get sessions
instanceA_session = requests.Session()
@ -202,26 +264,46 @@ 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,
'instanceA_profile_id': instanceA_profile_id,
'instanceB_profile_id': instanceB_profile_id,
'instanceB_profile': instanceB_profile,
})
# if given profile filters then get ids
if not instanceA_profile_id and instanceA_profile_filter:
instanceA_profile_filter_id = get_profile_from_id(instanceA_session, instanceA_url, instanceA_key, instanceA_profile_filter, 'A')
if not instanceB_profile_id and instanceB_profile_filter:
instanceB_profile_filter_id = get_profile_from_id(instanceB_session, instanceB_url, instanceB_key, instanceB_profile_filter, 'B')
# if given language instead of language id then try to find the lanaguage id
# only for sonarr v3
if is_sonarr:
if not instanceA_language_id and instanceA_language:
instanceA_language_id = get_language_from_id(
instance_session=instanceA_session,
instance_url=instanceA_url,
instance_key=instanceA_key,
instance_language=instanceA_language,
instance_name='A'
)
if not instanceB_language_id and instanceB_language:
instanceB_language_id = get_language_from_id(
instance_session=instanceB_session,
instance_url=instanceB_url,
instance_key=instanceB_key,
instance_language=instanceB_language,
instance_name='B'
)
logger.debug({
'instanceA_profile_filter': instanceA_profile_filter,
'instanceA_profile_filter_id': instanceA_profile_filter_id,
'instanceB_profile_filter': instanceB_profile_filter,
'instanceB_profile_filter_id': instanceB_profile_filter_id,
'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
@ -229,9 +311,10 @@ def sync_content():
instanceB_contents, instanceB_contentIds = get_instance_contents(instanceB_url, instanceB_key, instanceB_session, instance_name='B')
logger.info('syncing content from instance A to instance B')
search_ids = sync_servers(
sync_servers(
instanceA_contents=instanceA_contents,
instanceB_contentIds=instanceB_contentIds,
instanceB_language_id=instanceB_language_id,
instanceB_path=instanceB_path,
instanceB_profile_id=instanceB_profile_id,
instanceB_session=instanceB_session,
@ -244,9 +327,10 @@ def sync_content():
if sync_bidirectionally:
logger.info('syncing content from instance B to instance A')
search_ids = sync_servers(
sync_servers(
instanceA_contents=instanceB_contents,
instanceB_contentIds=instanceA_contentIds,
instanceB_language_id=instanceA_language_id,
instanceB_path=instanceA_path,
instanceB_profile_id=instanceA_profile_id,
instanceB_session=instanceA_session,

@ -1,231 +0,0 @@
#!/usr/bin/env python
import logging
import sys
import time
def ConfigSectionMap(section):
'''get all config options from config file'''
dict1 = {}
options = config.options(section)
for option in options:
try:
dict1[option] = config.get(section, option)
except:
print("exception on %s!" % option)
dict1[option] = None
return dict1
def get_config_value(env_key, config_key, config_section):
if is_in_docker:
return os.environ.get(env_key)
try:
_config = ConfigSectionMap(config_section)
return _config.get(config_key)
except configparser.NoSectionError:
return ''
from syncarr.get_config import *
########################################################################################################################
# setup logger
# CRITICAL 50, ERROR 40, WARNING 3, INFO 20, DEBUG 10, NOTSET 0
log_level = get_config_value('LOG_LEVEL', 'log_level', 'general') or 20
if log_level:
log_level = int(log_level)
logger = logging.getLogger()
logger.setLevel(log_level)
logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s")
# log to txt file
fileHandler = logging.FileHandler("./output.txt")
fileHandler.setFormatter(logFormatter)
logger.addHandler(fileHandler)
# log to std out
consoleHandler = logging.StreamHandler(sys.stdout)
consoleHandler.setFormatter(logFormatter)
logger.addHandler(consoleHandler)
logger.debug('Syncarr Version {}'.format(VER))
logger.info('log level {}'.format(log_level))
if DEV:
logger.info('-----------------DEVV-----------------')
########################################################################################################################
# make sure we have radarr, lidarr, OR sonarr
if (
(sonarrA_url and radarrA_url) or
(sonarrA_url and radarrB_url) or
(sonarrA_url and lidarrA_url) or
(sonarrA_url and lidarrB_url) or
(radarrA_url and lidarrA_url) or
(radarrA_url and lidarrB_url) or
(radarrB_url and lidarrA_url) or
(radarrB_url and lidarrB_url)
):
logger.error('cannot have more than one *arr type profile(s) setup at the same time')
sys.exit(0)
########################################################################################################################
# get generic instanceA/B variables
instanceA_url = ''
instanceA_key = ''
instanceA_profile = ''
instanceA_profile_id = ''
instanceA_profile_filter = ''
instanceA_path = ''
instanceB_url = ''
instanceB_key = ''
instanceB_profile = ''
instanceB_profile_id = ''
instanceB_profile_filter = ''
instanceB_path = ''
api_version = 'v1/' # 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_search_path = '' # url path to search for content on RSS feeds
api_profile_path = '' # url path to get quality profiles
api_status_path = '' # url path to check on server status
is_radarr = False
is_sonarr = False
is_lidarr = False
content_id_key = '' # the unique id for a content item
if radarrA_url and radarrB_url:
instanceA_url = radarrA_url
instanceA_key = radarrA_key
instanceA_profile = radarrA_profile
instanceA_profile_id = radarrA_profile_id
instanceA_profile_filter = radarrA_profile_filter
instanceA_profile_filter_id = radarrA_profile_filter_id
instanceA_path = radarrA_path
instanceB_url = radarrB_url
instanceB_key = radarrB_key
instanceB_profile = radarrB_profile
instanceB_profile_id = radarrB_profile_id
instanceB_profile_filter = radarrB_profile_filter
instanceB_profile_filter_id = radarrB_profile_filter_id
instanceB_path = radarrB_path
api_version = '' # radarr v2 doesnt have version in api url
api_content_path = 'movie'
api_search_path = 'command'
api_profile_path = 'profile'
api_status_path = 'system/status'
content_id_key = 'tmdbId'
is_radarr = True
elif lidarrA_url and lidarrB_url:
instanceA_url = lidarrA_url
instanceA_key = lidarrA_key
instanceA_profile = lidarrA_profile
instanceA_profile_id = lidarrA_profile_id
instanceA_profile_filter = lidarrA_profile_filter
instanceA_profile_filter_id = lidarrA_profile_filter_id
instanceA_path = lidarrA_path
instanceB_url = lidarrB_url
instanceB_key = lidarrB_key
instanceB_profile = lidarrB_profile
instanceB_profile_id = lidarrB_profile_id
instanceB_profile_filter = lidarrB_profile_filter
instanceB_profile_filter_id = lidarrB_profile_filter_id
instanceB_path = lidarrB_path
api_version = 'v1/'
api_content_path = 'artist'
api_search_path = 'command'
api_profile_path = 'qualityprofile'
api_status_path = 'system/status'
content_id_key = 'foreignArtistId'
is_lidarr = True
elif sonarrA_url and sonarrB_url:
instanceA_url = sonarrA_url
instanceA_key = sonarrA_key
instanceA_profile = sonarrA_profile
instanceA_profile_id = sonarrA_profile_id
instanceA_profile_filter = sonarrA_profile_filter
instanceA_profile_filter_id = sonarrA_profile_filter_id
instanceA_path = sonarrA_path
instanceB_url = sonarrB_url
instanceB_key = sonarrB_key
instanceB_profile = sonarrB_profile
instanceB_profile_id = sonarrB_profile_id
instanceB_profile_filter = sonarrB_profile_filter
instanceB_profile_filter_id = sonarrB_profile_filter_id
instanceB_path = sonarrB_path
api_version = ''
api_content_path = 'series'
api_search_path = 'command'
api_profile_path = 'profile'
api_status_path = 'system/status'
content_id_key = 'tvdbId'
is_sonarr = True
########################################################################################################################
logger.debug({
'instanceA_url': instanceA_url,
'instanceA_key': instanceA_key,
'instanceB_path': instanceB_path,
'instanceB_url': instanceB_url,
'instanceB_key': instanceB_key,
'instanceB_path': instanceB_path,
'api_content_path': api_content_path,
'api_search_path': api_search_path,
'is_sonarr': is_sonarr,
'is_lidarr': is_lidarr,
})
if not instanceA_url:
logger.error('missing URL for instance A')
sys.exit(0)
if not instanceA_key:
logger.error('missing API key for instance A')
sys.exit(0)
if not instanceA_url:
logger.error('missing URL for instance B')
sys.exit(0)
if not instanceB_key:
logger.error('missing API key for instance B')
sys.exit(0)
assert api_content_path
assert api_search_path
assert content_id_key
# if two way sync need instance A path and profile
if sync_bidirectionally:
assert instanceA_path
if not instanceB_profile_id and not instanceB_profile:
logger.error('profile_id or profile is required for *arr instance A if sync bidirectionally is enabled')
sys.exit(0)
if not instanceB_profile_id and not instanceB_profile:
logger.error('profile_id or profile is required for *arr instance B')
sys.exit(0)

@ -1,106 +0,0 @@
import os
import configparser
DEV = os.environ.get('DEV')
VER = '1.4.1'
def ConfigSectionMap(section):
'''get all config options from config file'''
dict1 = {}
options = config.options(section)
for option in options:
try:
dict1[option] = config.get(section, option)
except:
print("exception on %s!" % option)
dict1[option] = None
return dict1
def get_config_value(env_key, config_key, config_section):
if is_in_docker:
return os.environ.get(env_key)
try:
_config = ConfigSectionMap(config_section)
return _config.get(config_key)
except configparser.NoSectionError:
return ''
########################################################################################################################
# load config file
BASE_CONFIG = 'config.conf'
if DEV:
settingsFilename = os.path.join(os.getcwd(), 'dev-{}'.format(BASE_CONFIG))
else:
settingsFilename = os.path.join(os.getcwd(), BASE_CONFIG)
config = configparser.ConfigParser()
config.read(settingsFilename)
########################################################################################################################
# get docker based ENV vars
is_in_docker = os.environ.get('IS_IN_DOCKER')
instance_sync_interval_seconds = os.environ.get('SYNC_INTERVAL_SECONDS')
if instance_sync_interval_seconds:
instance_sync_interval_seconds = int(instance_sync_interval_seconds)
########################################################################################################################
# get config settings from ENV or config files for Radarr
radarrA_url = get_config_value('RADARR_A_URL', 'url', 'radarrA')
radarrA_key = get_config_value('RADARR_A_KEY', 'key', 'radarrA')
radarrA_profile = get_config_value('RADARR_A_PROFILE', 'profile', 'radarrA')
radarrA_profile_id = get_config_value('RADARR_A_PROFILE_ID', 'profile_id', 'radarrA')
radarrA_profile_filter = get_config_value('RADARR_A_PROFILE_FILTER', 'profile_filter', 'radarrA')
radarrA_profile_filter_id = get_config_value('RADARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'radarrA')
radarrA_path = get_config_value('RADARR_A_PATH', 'path', 'radarrA')
radarrB_url = get_config_value('RADARR_B_URL', 'url', 'radarrB')
radarrB_key = get_config_value('RADARR_B_KEY', 'key', 'radarrB')
radarrB_profile = get_config_value('RADARR_B_PROFILE', 'profile', 'radarrB')
radarrB_profile_id = get_config_value('RADARR_B_PROFILE_ID', 'profile_id', 'radarrB')
radarrB_profile_filter = get_config_value('RADARR_B_PROFILE_FILTER', 'profile_filter', 'radarrB')
radarrB_profile_filter_id = get_config_value('RADARR_B_PROFILE_FILTER_ID', 'profile_filter_id', 'radarrB')
radarrB_path = get_config_value('RADARR_B_PATH', 'path', 'radarrB')
# get config settings from ENV or config files for Sonarr
sonarrA_url = get_config_value('SONARR_A_URL', 'url', 'sonarrA')
sonarrA_key = get_config_value('SONARR_A_KEY', 'key', 'sonarrA')
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_path = get_config_value('SONARR_A_PATH', 'path', 'sonarrA')
sonarrB_url = get_config_value('SONARR_B_URL', 'url', 'sonarrB')
sonarrB_key = get_config_value('SONARR_B_KEY', 'key', 'sonarrB')
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_A_PROFILE_FILTER', 'profile_filter', 'sonarrB')
sonarrB_profile_filter_id = get_config_value('SONARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'sonarrB')
sonarrB_path = get_config_value('SONARR_B_PATH', 'path', 'sonarrB')
# get config settings from ENV or config files for Lidarr
lidarrA_url = get_config_value('LIDARR_A_URL', 'url', 'lidarrA')
lidarrA_key = get_config_value('LIDARR_A_KEY', 'key', 'lidarrA')
lidarrA_profile = get_config_value('LIDARR_A_PROFILE', 'profile', 'lidarrA')
lidarrA_profile_id = get_config_value('LIDARR_A_PROFILE_ID', 'profile_id', 'lidarrA')
lidarrA_profile_filter = get_config_value('LIDARR_A_PROFILE_FILTER', 'profile_filter', 'lidarrA')
lidarrA_profile_filter_id = get_config_value('LIDARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'lidarrA')
lidarrA_path = get_config_value('LIDARR_A_PATH', 'path', 'lidarrA')
lidarrB_url = get_config_value('LIDARR_B_URL', 'url', 'lidarrB')
lidarrB_key = get_config_value('LIDARR_B_KEY', 'key', 'lidarrB')
lidarrB_profile = get_config_value('LIDARR_B_PROFILE', 'profile', 'lidarrB')
lidarrB_profile_id = get_config_value('LIDARR_B_PROFILE_ID', 'profile_id', 'lidarrB')
lidarrB_profile_filter = get_config_value('LIDARR_A_PROFILE_FILTER', 'profile_filter', 'lidarrB')
lidarrB_profile_filter_id = get_config_value('LIDARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'lidarrB')
lidarrB_path = get_config_value('LIDARR_B_PATH', 'path', 'lidarrB')
# get general conf options
sync_bidirectionally = get_config_value('SYNCARR_BIDIRECTIONAL_SYNC', 'bidirectional', 'general') or 0
if sync_bidirectionally:
sync_bidirectionally = int(sync_bidirectionally) or 0

@ -1,38 +0,0 @@
from syncarr.config import *
def get_path(instance_url, api_path, key, checkV3=False):
global api_version, api_profile_path
logger.debug(f'checkV3: "{checkV3}" for {instance_url}')
if checkV3:
api_version = 'v3/'
if checkV3 and is_sonarr:
api_profile_path = 'qualityprofile'
url = f"{instance_url}/api/{api_version}{api_path}?apikey={key}"
return url
def get_status_path(instance_url, key, checkV3):
url = get_path(instance_url, api_status_path, key, checkV3)
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_search_path(instance_url, key):
url = get_path(instance_url, api_search_path, key)
logger.debug('get_search_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
Loading…
Cancel
Save