You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
syncarr/config.py

525 lines
21 KiB

#!/usr/bin/env python
import logging
import os
import sys
import time
import configparser
DEV = os.environ.get('DEV')
VER = '1.9.1'
DEBUG_LINE = '-' * 20
V1_API_PATH = 'v1/'
V2_API_PATH = ''
V3_API_PATH = 'v3/'
# https://github.com/lidarr/Lidarr/wiki/Artist
# https://github.com/Radarr/Radarr/wiki/API:Movie
# https://github.com/Sonarr/Sonarr/wiki/Series
########################################################################################################################
# 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)
########################################################################################################################
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:
value = os.environ.get(env_key)
if value is not None: # only return if given value else try config file
return value
try:
_config = ConfigSectionMap(config_section)
return _config.get(config_key)
except configparser.NoSectionError:
return None
########################################################################################################################
# load config file
BASE_CONFIG = 'config.conf'
if DEV:
settingsFilename = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'dev-{}'.format(BASE_CONFIG))
else:
settingsFilename = os.path.join(os.path.abspath(os.path.dirname(__file__)), BASE_CONFIG)
sys.tracebacklimit = 0 # dont show stack traces in prod mode
config = configparser.ConfigParser()
config.read(settingsFilename)
########################################################################################################################
# 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_path = get_config_value('RADARR_A_PATH', 'path', '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_tag_filter = get_config_value('RADARR_A_TAG_FILTER', 'tag_filter', 'radarrA')
radarrA_tag_filter_id = get_config_value('RADARR_A_TAG_FILTER_ID', 'tag_filter_id', 'radarrA')
radarrA_language = get_config_value('RADARR_A_LANGUAGE', 'language', 'radarrA')
radarrA_language_id = get_config_value('RADARR_A_LANGUAGE_ID', 'language_id', 'radarrA')
radarrA_quality_match = get_config_value('RADARR_A_QUALITY_MATCH', 'quality_match', 'radarrA')
radarrA_blacklist = get_config_value('RADARR_A_BLACKLIST', 'blacklist', 'radarrA')
radarrB_url = get_config_value('RADARR_B_URL', 'url', 'radarrB')
radarrB_key = get_config_value('RADARR_B_KEY', 'key', 'radarrB')
radarrB_path = get_config_value('RADARR_B_PATH', 'path', '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_tag_filter = get_config_value('RADARR_B_TAG_FILTER', 'tag_filter', 'radarrB')
radarrB_tag_filter_id = get_config_value('RADARR_B_TAG_FILTER_ID', 'tag_filter_id', 'radarrB')
radarrB_language = get_config_value('RADARR_B_LANGUAGE', 'language', 'radarrB')
radarrB_language_id = get_config_value('RADARR_B_LANGUAGE_ID', 'language_id', 'radarrB')
radarrB_quality_match = get_config_value('RADARR_B_QUALITY_MATCH', 'quality_match', 'radarrB')
radarrB_blacklist = get_config_value('RADARR_B_BLACKLIST', 'blacklist', '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_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')
sonarrA_blacklist = get_config_value('SONARR_A_BLACKLIST', 'blacklist', '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_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')
sonarrB_blacklist = get_config_value('SONARR_B_BLACKLIST', 'blacklist', '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_path = get_config_value('LIDARR_A_PATH', 'path', '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_language = get_config_value('LIDARR_A_LANGUAGE', 'language', 'lidarrA')
lidarrA_language_id = get_config_value('LIDARR_A_LANGUAGE_ID', 'language_id', 'lidarrA')
lidarrA_quality_match = get_config_value('LIDARR_A_QUALITY_MATCH', 'quality_match', 'lidarrA')
lidarrA_blacklist = get_config_value('LIDARR_A_BLACKLIST', 'blacklist', 'lidarrA')
lidarrB_url = get_config_value('LIDARR_B_URL', 'url', 'lidarrB')
lidarrB_key = get_config_value('LIDARR_B_KEY', 'key', 'lidarrB')
lidarrB_path = get_config_value('LIDARR_B_PATH', 'path', '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_B_PROFILE_FILTER', 'profile_filter', 'lidarrB')
lidarrB_profile_filter_id = get_config_value('LIDARR_B_PROFILE_FILTER_ID', 'profile_filter_id', 'lidarrB')
lidarrB_language = get_config_value('LIDARR_B_LANGUAGE', 'language', 'lidarrB')
lidarrB_language_id = get_config_value('LIDARR_B_LANGUAGE_ID', 'language_id', 'lidarrB')
lidarrB_quality_match = get_config_value('LIDARR_B_QUALITY_MATCH', 'quality_match', 'lidarrB')
lidarrB_blacklist = get_config_value('LIDARR_B_BLACKLIST', 'blacklist', 'lidarrB')
def set_general_option(env_name, config_name, default_value):
"""gets a general config value"""
config_value = get_config_value(env_name, config_name, 'general')
if config_value is not None:
try:
config_value = int(config_value)
except ValueError:
config_value = default_value
else:
config_value = default_value
return config_value
# get general options
sync_bidirectionally = set_general_option('SYNCARR_BIDIRECTIONAL_SYNC', 'bidirectional', default_value=0)
auto_search = set_general_option('SYNCARR_AUTO_SEARCH', 'auto_search', default_value=1)
skip_missing = set_general_option('SYNCARR_SKIP_MISSING', 'skip_missing', default_value=1)
monitor_new_content = set_general_option('SYNCARR_MONITOR_NEW_CONTENT', 'monitor_new_content', default_value=1)
sync_monitor = set_general_option('SYNCARR_SYNC_MONITOR', 'sync_monitor', default_value=0)
is_test_run = set_general_option('SYNCARR_TEST_RUN', 'test_run', default_value=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:
try:
log_level = int(log_level)
except ValueError:
log_level = 20
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_profile_filter_id = ''
instanceA_language = ''
instanceA_language_id = ''
instanceA_quality_match = ''
instanceA_tag_filter = ''
instanceA_tag_filter_id = ''
instanceA_blacklist = ''
instanceB_url = ''
instanceB_key = ''
instanceB_path = ''
instanceB_profile = ''
instanceB_profile_id = ''
instanceB_profile_filter = ''
instanceB_profile_filter_id = ''
instanceB_language = ''
instanceB_language_id = ''
instanceB_quality_match = ''
instanceB_tag_filter = ''
instanceB_tag_filter_id = ''
instanceB_blacklist = ''
api_version = ''
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
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_path = radarrA_path
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_language = radarrA_language
instanceA_language_id = radarrA_language_id
instanceA_tag_filter = radarrA_tag_filter and radarrA_tag_filter.split(',')
instanceA_tag_filter_id = radarrA_tag_filter_id and radarrA_tag_filter_id.split(',')
instanceA_quality_match = radarrA_quality_match
instanceA_blacklist = radarrA_blacklist
instanceB_url = radarrB_url
instanceB_key = radarrB_key
instanceB_path = radarrB_path
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_language = radarrB_language
instanceB_language_id = radarrB_language_id
instanceB_tag_filter = radarrB_tag_filter and radarrB_tag_filter.split(',')
instanceB_tag_filter_id = radarrB_tag_filter_id and radarrB_tag_filter_id.split(',')
instanceB_quality_match = radarrB_quality_match
instanceB_blacklist = radarrB_blacklist
api_version = V3_API_PATH
api_content_path = 'movie'
api_profile_path = 'qualityprofile'
api_status_path = 'system/status'
content_id_key = 'tmdbId'
is_radarr = 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
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
instanceA_blacklist = sonarrA_blacklist
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
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
instanceB_blacklist = sonarrB_blacklist
api_version = V3_API_PATH
api_content_path = 'series'
api_profile_path = 'qualityprofile'
api_status_path = 'system/status'
api_language_path = 'languageprofile'
api_tag_path = 'tag'
content_id_key = 'tvdbId'
is_sonarr = True
elif lidarrA_url and lidarrB_url:
instanceA_url = lidarrA_url
instanceA_key = lidarrA_key
instanceA_path = lidarrA_path
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_language = lidarrA_language
instanceA_language_id = lidarrA_language_id
instanceA_quality_match = lidarrA_quality_match
instanceA_blacklist = lidarrA_blacklist
instanceB_url = lidarrB_url
instanceB_key = lidarrB_key
instanceB_path = lidarrB_path
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_language = lidarrB_language
instanceB_language_id = lidarrB_language_id
instanceB_quality_match = lidarrB_quality_match
instanceB_blacklist = lidarrB_blacklist
api_version = V1_API_PATH
api_content_path = 'artist'
api_profile_path = 'qualityprofile'
api_status_path = 'system/status'
content_id_key = 'foreignArtistId'
is_lidarr = True
# format blacklists
if instanceA_blacklist:
instanceA_blacklist = instanceA_blacklist.split(',')
if instanceB_blacklist:
instanceB_blacklist = instanceB_blacklist.split(',')
########################################################################################################################
# path generators
def get_path(instance_url, api_path, key, changed_api_version=False):
global api_version, api_profile_path
logger.debug(DEBUG_LINE)
logger.debug({
'instance_url': instance_url,
'api_path': api_path,
'api_version': api_version,
'is_sonarr': is_sonarr,
'api_profile_path': api_profile_path,
'changed_api_version': changed_api_version,
})
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_content_put_path(instance_url, key, content_id):
url = get_path(instance_url, f'{api_content_path}/{content_id}', key)
logger.debug('get_content_put_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,
'instanceA_path': instanceA_path,
'instanceA_profile': instanceA_profile,
'instanceA_profile_id': instanceA_profile_id,
'instanceA_profile_filter': instanceA_profile_filter,
'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,
'instanceA_blacklist': instanceA_blacklist,
'instanceB_url': instanceB_url,
'instanceB_key': instanceB_key,
'instanceB_path': instanceB_path,
'instanceB_profile': instanceB_profile,
'instanceB_profile_id': instanceB_profile_id,
'instanceB_profile_filter': instanceB_profile_filter,
'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,
'instanceB_blacklist': instanceB_blacklist,
'api_content_path': api_content_path,
'api_profile_path': api_profile_path,
'api_language_path': api_language_path,
'is_sonarr': is_sonarr,
'is_lidarr': is_lidarr,
'is_radarr': is_radarr,
'monitor_new_content': monitor_new_content,
'sync_bidirectionally': sync_bidirectionally,
'auto_search': auto_search,
'skip_missing': skip_missing,
'api_version': api_version,
'sync_monitor': sync_monitor,
})
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 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:
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)