diff --git a/README.md b/README.md index 4c153f9..c0ea155 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a * Support two way sync (one way by default) * Set language profiles (Sonarr v3 only) * Filter syncing by content file quality (Radarr only) -* Filter syncing by tags (Sonarr only) +* Filter syncing by tags (Sonarr/Radarr v3 only) ## Configuration @@ -24,7 +24,7 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a url = http://127.0.0.1:8080 key = XXXXX profile = 1080p - path = /data/Movies + path = /data/Movies # if not given will use RadarrA path for each movie - may not be what you want! ``` 3. Or if you want to sync two Sonarr instances: ```ini @@ -60,8 +60,8 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a 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) - 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) + tag_filter = Horror # (Sonarr/Radarr v3 only) sync movies by tag name (seperate multiple tags by comma (no spaces) ie horror,comedy,action) + tag_filter_id = 2 # (Sonarr/Radarr v3 only) sync movies by tag id (seperate multiple tags by comma (no spaces) ie 2,3,4) [*arrB] url = http://127.0.0.1:8080 @@ -74,6 +74,7 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a sync_bidirectionally = 1 # sync from instance A to B **AND** instance B to A auto_search = 0 # search is automatically started on new content - disable by setting to 0 (default 1) monitor_new_content = 0 # set to 0 to never monitor new content synced or to 1 to always monitor new content synced (default 1) + test_run = 1 # enable test mode - will run through sync program but will not actually sync content ``` **Note** If `sync_bidirectionally` is set to `1`, then instance A will require either `profile_id` or `profile` AND `path` as well @@ -166,6 +167,7 @@ To filter by profile in docker use `ARR_A_PROFILE_FILTER` or `ARR_A_PROFILE_FILT * Set if you want to NOT monitor new content with `SYNCARR_MONITOR_NEW_CONTENT=0` (default 1) * Match regex quality profiles with `*ARR_A_QUALITY_MATCH` or `*ARR_B_QUALITY_MATCH` * Filter by tag names or ids with `*ARR_A_TAG_FILTER` / `*ARR_B_TAG_FILTER` or `*ARR_A_TAG_FILTER_ID` / `*ARR_B_TAG_FILTER_ID` +* Enable test mode with `SYNCARR_TEST_RUN` --- ## Troubleshooting diff --git a/config.py b/config.py index fef46fa..4c130b7 100644 --- a/config.py +++ b/config.py @@ -173,6 +173,16 @@ if monitor_new_content is not None: else: monitor_new_content = 1 +# enable test mode +is_test_run = get_config_value('SYNCARR_TEST_RUN', 'test_run', 'general') +if is_test_run is not None: + try: + is_test_run = int(is_test_run) + except ValueError: + is_test_run = 0 +else: + is_test_run = 0 + ######################################################################################################################## # setup logger @@ -451,6 +461,7 @@ logger.debug({ 'is_sonarr': is_sonarr, 'is_lidarr': is_lidarr, + 'is_radarr': is_radarr, 'monitor_new_content': monitor_new_content, 'sync_bidirectionally': sync_bidirectionally, diff --git a/index.py b/index.py index ec6e913..69d5d43 100644 --- a/index.py +++ b/index.py @@ -8,6 +8,7 @@ import configparser import sys import time import re +from os.path import basename from config import ( instanceA_url, instanceA_key, instanceA_path, instanceA_profile, @@ -25,7 +26,7 @@ from config import ( is_in_docker, instance_sync_interval_seconds, sync_bidirectionally, auto_search, monitor_new_content, - tested_api_version, api_version, V3_API_PATH, + tested_api_version, api_version, V3_API_PATH, is_test_run, ) @@ -192,7 +193,7 @@ 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, instanceA_tag_filter_id): - global is_radarr, is_sonarr + global is_radarr, is_sonarr, is_test_run search_ids = [] # if given instance A profile id then we want to filter out content without that id @@ -203,6 +204,7 @@ def sync_servers(instanceA_contents, instanceB_language_id, instanceB_contentIds for content in instanceA_contents: if content[content_id_key] not in instanceB_contentIds: title = content.get('title') or content.get('artistName') + instance_path = instanceB_path or basename(content.get('path')) # if given this, we want to filter from instance by profile id if instanceA_profile_filter_id: @@ -218,8 +220,8 @@ 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: + # if given tag filter then filter by tag - (Sonarr/Radarr v3 only) + if (is_sonarr or is_radarr) 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}') @@ -229,24 +231,28 @@ def sync_servers(instanceA_contents, instanceB_language_id, instanceB_contentIds # get the POST payload and sync content to instance B payload = get_new_content_payload( - content=content, - instance_path=instanceB_path, - instance_profile_id=instanceB_profile_id, - instance_url=instanceB_url, + content=content, + instance_path=instance_path, + instance_profile_id=instanceB_profile_id, + instance_url=instanceB_url, instance_language_id=instanceB_language_id, ) instanceB_content_url = get_content_path(instanceB_url, instanceB_key) - sync_response = instanceB_session.post(instanceB_content_url, data=json.dumps(payload)) - # check response and save content id for searching later on if success - if sync_response.status_code != 201 and sync_response.status_code != 200: - logger.error(f'server sync error for {title} - response: {sync_response.text}') + if is_test_run: + logging.info('content title "{0}" synced successfully (test only)'.format(title)) else: - try: - search_ids.append(int(sync_response.json()['id'])) - except: - logger.error(f'Could not decode sync response from {instanceB_content_url}') - logging.info('content title "{0}" synced successfully'.format(title)) + sync_response = instanceB_session.post(instanceB_content_url, data=json.dumps(payload)) + + # check response and save content id for searching later on if success + if sync_response.status_code != 201 and sync_response.status_code != 200: + logger.error(f'server sync error for {title} - response: {sync_response.text}') + else: + try: + search_ids.append(int(sync_response.json()['id'])) + except: + logger.error(f'Could not decode sync response from {instanceB_content_url}') + logging.info('content title "{0}" synced successfully'.format(title)) logging.info(f'{len(search_ids)} contents synced successfully') @@ -321,7 +327,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, is_sonarr, instanceA_tag_filter_id, instanceA_tag_filter, instanceB_tag_filter_id, instanceB_tag_filter + 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, is_radarr # get sessions instanceA_session = requests.Session() @@ -329,6 +335,7 @@ def sync_content(): instanceB_session = requests.Session() instanceB_session.trust_env = False + # check if we tested if we are using v2 or v3 if not tested_api_version: check_status(instanceA_session, instanceA_url, instanceA_key, instance_name='A') check_status(instanceB_session, instanceB_url, instanceB_key, instance_name='B') @@ -359,7 +366,7 @@ def sync_content(): }) # do the same for tag id filters if they exist - (only Sonarr) - if is_sonarr: + if is_sonarr or is_radarr: 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: