import base64 , os , re , requests
from datetime import datetime
from lxml import html
from modules import util , radarr , sonarr , operations
from modules . anidb import AniDB
from modules . anilist import AniList
from modules . cache import Cache
from modules . convert import Convert
from modules . ergast import Ergast
from modules . icheckmovies import ICheckMovies
from modules . imdb import IMDb
from modules . github import GitHub
from modules . letterboxd import Letterboxd
from modules . mal import MyAnimeList
from modules . meta import PlaylistFile
from modules . mojo import BoxOfficeMojo
from modules . notifiarr import Notifiarr
from modules . gotify import Gotify
from modules . omdb import OMDb
from modules . overlays import Overlays
from modules . plex import Plex
from modules . radarr import Radarr
from modules . sonarr import Sonarr
from modules . reciperr import Reciperr
from modules . mdblist import MDBList
from modules . tautulli import Tautulli
from modules . tmdb import TMDb
from modules . trakt import Trakt
from modules . tvdb import TVDb
from modules . util import Failed , NotScheduled , NotScheduledRange , YAML
from modules . webhooks import Webhooks
from retrying import retry
logger = util . logger
mediastingers_url = " https://raw.githubusercontent.com/Kometa-Team/Mediastingers/master/stingers.yml "
run_order_options = {
" collections " : " Represents Collection Updates " ,
" metadata " : " Represents Metadata Updates " ,
" overlays " : " Represents Overlay Updates " ,
" operations " : " Represents Operations Updates "
}
sync_modes = { " append " : " Only Add Items to the Collection or Playlist " , " sync " : " Add & Remove Items from the Collection or Playlist " }
filetype_list = {
" jpg " : " Use JPG files for saving Overlays " ,
" png " : " Use PNG files for saving Overlays " ,
" webp_lossy " : " Use Lossy WEBP files for saving Overlays " ,
" webp_lossless " : " Use Lossless WEBP files for saving Overlays "
}
imdb_label_options = {
" remove " : " Remove All IMDb Parental Labels " ,
" none " : " Add IMDb Parental Labels for None, Mild, Moderate, or Severe " ,
" mild " : " Add IMDb Parental Labels for Mild, Moderate, or Severe " ,
" moderate " : " Add IMDb Parental Labels for Moderate or Severe " ,
" severe " : " Add IMDb Parental Labels for Severe "
}
mass_genre_options = {
" lock " : " Lock Genre " , " unlock " : " Unlock Genre " , " remove " : " Remove and Lock Genre " , " reset " : " Remove and Unlock Genre " ,
" tmdb " : " Use TMDb Genres " , " imdb " : " Use IMDb Genres " , " omdb " : " Use IMDb Genres through OMDb " , " tvdb " : " Use TVDb Genres " ,
" mal " : " Use MyAnimeList Genres " , " anidb " : " Use AniDB Main Tags " ,
" anidb_3_0 " : " Use AniDB Main Tags and All 3 Star Tags and above " , " anidb_2_5 " : " Use AniDB Main Tags and All 2.5 Star Tags and above " ,
" anidb_2_0 " : " Use AniDB Main Tags and All 2 Star Tags and above " , " anidb_1_5 " : " Use AniDB Main Tags and All 1.5 Star Tags and above " ,
" anidb_1_0 " : " Use AniDB Main Tags and All 1 Star Tags and above " , " anidb_0_5 " : " Use AniDB Main Tags and All 0.5 Star Tags and above "
}
mass_content_options = {
" lock " : " Lock Rating " , " unlock " : " Unlock Rating " , " remove " : " Remove and Lock Rating " , " reset " : " Remove and Unlock Rating " ,
" omdb " : " Use IMDb Rating through OMDb " , " mdb " : " Use MDBList Rating " ,
" mdb_commonsense " : " Use Commonsense Rating through MDBList " , " mdb_commonsense0 " : " Use Commonsense Rating with Zero Padding through MDBList " ,
" mdb_age_rating " : " Use MDBList Age Rating " , " mdb_age_rating0 " : " Use MDBList Age Rating with Zero Padding " ,
" mal " : " Use MyAnimeList Rating "
}
mass_collection_content_options = {
" lock " : " Lock Rating " , " unlock " : " Unlock Rating " , " remove " : " Remove and Lock Rating " , " reset " : " Remove and Unlock Rating " ,
" highest " : " Highest Rating in the collection " , " lowest " : " Lowest Rating in the collection " ,
" average " : " Highest Rating in the collection "
}
content_rating_default = {
1 : [
]
}
mass_studio_options = {
" lock " : " Lock Rating " , " unlock " : " Unlock Rating " , " remove " : " Remove and Lock Rating " , " reset " : " Remove and Unlock Rating " ,
" tmdb " : " Use TMDb Studio " , " anidb " : " Use AniDB Animation Work " , " mal " : " Use MyAnimeList Studio "
}
mass_original_title_options = {
" lock " : " Lock Original Title " , " unlock " : " Unlock Original Title " , " remove " : " Remove and Lock Original Title " , " reset " : " Remove and Unlock Original Title " ,
" anidb " : " Use AniDB Main Title " , " anidb_official " : " Use AniDB Official Title based on the language attribute in the config file " ,
" mal " : " Use MyAnimeList Main Title " , " mal_english " : " Use MyAnimeList English Title " , " mal_japanese " : " Use MyAnimeList Japanese Title " ,
}
mass_available_options = {
" lock " : " Lock Originally Available " , " unlock " : " Unlock Originally Available " , " remove " : " Remove and Lock Originally Available " , " reset " : " Remove and Unlock Originally Available " ,
" tmdb " : " Use TMDb Release " , " omdb " : " Use IMDb Release through OMDb " , " mdb " : " Use MDBList Release " , " mdb_digital " : " Use MDBList Digital Release " , " tvdb " : " Use TVDb Release " ,
" anidb " : " Use AniDB Release " , " mal " : " Use MyAnimeList Release "
}
mass_image_options = {
" lock " : " Lock Image " , " unlock " : " Unlock Image " , " plex " : " Use Plex Images " , " tmdb " : " Use TMDb Images "
}
mass_episode_rating_options = {
" lock " : " Lock Rating " , " unlock " : " Unlock Rating " , " remove " : " Remove and Lock Rating " , " reset " : " Remove and Unlock Rating " ,
" tmdb " : " Use TMDb Rating " , " imdb " : " Use IMDb Rating "
}
mass_rating_options = {
" lock " : " Lock Rating " ,
" unlock " : " Unlock Rating " ,
" remove " : " Remove and Lock Rating " ,
" reset " : " Remove and Unlock Rating " ,
" tmdb " : " Use TMDb Rating " ,
" imdb " : " Use IMDb Rating " ,
" trakt_user " : " Use Trakt User Rating " ,
" omdb " : " Use IMDb Rating through OMDb " ,
" mdb " : " Use MDBList Score " ,
" mdb_average " : " Use MDBList Average Score " ,
" mdb_imdb " : " Use IMDb Rating through MDBList " ,
" mdb_metacritic " : " Use Metacritic Rating through MDBList " ,
" mdb_metacriticuser " : " Use Metacritic User Rating through MDBList " ,
" mdb_trakt " : " Use Trakt Rating through MDBList " ,
" mdb_tomatoes " : " Use Rotten Tomatoes Rating through MDBList " ,
" mdb_tomatoesaudience " : " Use Rotten Tomatoes Audience Rating through MDBList " ,
" mdb_tmdb " : " Use TMDb Rating through MDBList " ,
" mdb_letterboxd " : " Use Letterboxd Rating through MDBList " ,
" mdb_myanimelist " : " Use MyAnimeList Rating through MDBList " ,
" anidb_rating " : " Use AniDB Rating " ,
" anidb_average " : " Use AniDB Average " ,
" anidb_score " : " Use AniDB Review Dcore " ,
" mal " : " Use MyAnimeList Rating "
}
reset_overlay_options = { " tmdb " : " Reset to TMDb poster " , " plex " : " Reset to Plex Poster " }
library_operations = {
" assets_for_all " : " bool " , " split_duplicates " : " bool " , " update_blank_track_titles " : " bool " , " remove_title_parentheses " : " bool " ,
" radarr_add_all_existing " : " bool " , " radarr_remove_by_tag " : " str " , " sonarr_add_all_existing " : " bool " , " sonarr_remove_by_tag " : " str " ,
" mass_content_rating_update " : mass_content_options , " mass_collection_content_rating_update " : " dict " ,
" mass_genre_update " : mass_genre_options , " mass_studio_update " : mass_studio_options ,
" mass_audience_rating_update " : mass_rating_options , " mass_episode_audience_rating_update " : mass_episode_rating_options ,
" mass_critic_rating_update " : mass_rating_options , " mass_episode_critic_rating_update " : mass_episode_rating_options ,
" mass_user_rating_update " : mass_rating_options , " mass_episode_user_rating_update " : mass_episode_rating_options ,
" mass_original_title_update " : mass_original_title_options , " mass_originally_available_update " : mass_available_options ,
" mass_imdb_parental_labels " : imdb_label_options ,
" mass_collection_mode " : " mass_collection_mode " , " mass_poster_update " : " dict " , " mass_background_update " : " dict " ,
" metadata_backup " : " dict " , " delete_collections " : " dict " , " genre_mapper " : " dict " , " content_rating_mapper " : " dict " ,
}
class ConfigFile :
def __init__ ( self , default_dir , attrs , secrets ) :
logger . info ( " Locating config... " )
config_file = attrs [ " config_file " ]
if config_file and os . path . exists ( config_file ) : self . config_path = os . path . abspath ( config_file )
elif config_file and not os . path . exists ( config_file ) : raise Failed ( f " Config Error: config not found at { os . path . abspath ( config_file ) } " )
elif os . path . exists ( os . path . join ( default_dir , " config.yml " ) ) : self . config_path = os . path . abspath ( os . path . join ( default_dir , " config.yml " ) )
else : raise Failed ( f " Config Error: config not found at { os . path . abspath ( default_dir ) } " )
logger . info ( f " Using { self . config_path } as config " )
logger . clear_errors ( )
self . _mediastingers = None
self . default_dir = default_dir
self . secrets = secrets
self . version = attrs [ " version " ] if " version " in attrs else None
self . branch = attrs [ " branch " ] if " branch " in attrs else None
self . read_only = attrs [ " read_only " ] if " read_only " in attrs else False
self . no_missing = attrs [ " no_missing " ] if " no_missing " in attrs else None
self . no_report = attrs [ " no_report " ] if " no_report " in attrs else None
self . ignore_schedules = attrs [ " ignore_schedules " ] if " ignore_schedules " in attrs else False
self . start_time = attrs [ " time_obj " ]
self . run_hour = datetime . strptime ( attrs [ " time " ] , " % H: % M " ) . hour
self . requested_collections = None
if " collections " in attrs and attrs [ " collections " ] :
self . requested_collections = [ s . strip ( ) for s in attrs [ " collections " ] . split ( " | " ) ]
self . requested_libraries = None
if " libraries " in attrs and attrs [ " libraries " ] :
self . requested_libraries = [ s . strip ( ) for s in attrs [ " libraries " ] . split ( " | " ) ]
self . requested_files = None
if " files " in attrs and attrs [ " files " ] :
self . requested_files = [ ]
for s in attrs [ " files " ] . split ( " | " ) :
s = s . strip ( )
if s :
if s . endswith ( " .yml " ) :
self . requested_files . append ( s [ : - 4 ] )
elif s . endswith ( " .yaml " ) :
self . requested_files . append ( s [ : - 5 ] )
else :
self . requested_files . append ( s )
self . collection_only = attrs [ " collection_only " ] if " collection_only " in attrs else False
self . metadata_only = attrs [ " metadata_only " ] if " metadata_only " in attrs else False
self . operations_only = attrs [ " operations_only " ] if " operations_only " in attrs else False
self . overlays_only = attrs [ " overlays_only " ] if " overlays_only " in attrs else False
self . env_plex_url = attrs [ " plex_url " ] if " plex_url " in attrs else " "
self . env_plex_token = attrs [ " plex_token " ] if " plex_token " in attrs else " "
self . tpdb_timer = None
current_time = datetime . now ( )
with open ( self . config_path , encoding = " utf-8 " ) as fp :
logger . separator ( " Redacted Config " , space = False , border = False , debug = True )
for line in fp . readlines ( ) :
logger . debug ( re . sub ( r " (token|client.*|url|api_*key|secret|error|delete|run_start|run_end|version|changes|username|password): .+ " , r " \ 1: (redacted) " , line . strip ( " \r \n " ) ) )
logger . debug ( " " )
self . data = YAML ( self . config_path ) . data
def replace_attr ( all_data , in_attr , par ) :
if " settings " not in all_data :
all_data [ " settings " ] = { }
if par in all_data and all_data [ par ] and in_attr in all_data [ par ] and in_attr not in all_data [ " settings " ] :
all_data [ " settings " ] [ in_attr ] = all_data [ par ] [ in_attr ]
del all_data [ par ] [ in_attr ]
if " libraries " not in self . data :
self . data [ " libraries " ] = { }
if " settings " not in self . data :
self . data [ " settings " ] = { }
if " tmdb " not in self . data :
self . data [ " tmdb " ] = { }
replace_attr ( self . data , " cache " , " cache " )
replace_attr ( self . data , " cache_expiration " , " cache " )
if " config " in self . data :
del self . data [ " cache " ]
replace_attr ( self . data , " asset_directory " , " plex " )
replace_attr ( self . data , " sync_mode " , " plex " )
replace_attr ( self . data , " show_unmanaged " , " plex " )
replace_attr ( self . data , " show_filtered " , " plex " )
replace_attr ( self . data , " show_missing " , " plex " )
replace_attr ( self . data , " save_missing " , " plex " )
if self . data [ " libraries " ] :
for library in self . data [ " libraries " ] :
if not self . data [ " libraries " ] [ library ] :
continue
if " metadata_path " in self . data [ " libraries " ] [ library ] :
logger . warning ( " Config Warning: metadata_path has been deprecated and split into collection_files and metadata_files, Please visit the wiki to learn more about this transition. " )
path_dict = self . data [ " libraries " ] [ library ] . pop ( " metadata_path " )
if " collection_files " not in self . data [ " libraries " ] [ library ] :
self . data [ " libraries " ] [ library ] [ " collection_files " ] = path_dict
if " metadata_files " not in self . data [ " libraries " ] [ library ] :
self . data [ " libraries " ] [ library ] [ " metadata_files " ] = path_dict
if " overlay_path " in self . data [ " libraries " ] [ library ] :
logger . warning ( " Config Warning: overlay_path has been deprecated in favor of overlay_files, Please visit the wiki to learn more about this transition. " )
self . data [ " libraries " ] [ library ] [ " overlay_files " ] = self . data [ " libraries " ] [ library ] . pop ( " overlay_path " )
if " radarr_add_all " in self . data [ " libraries " ] [ library ] :
self . data [ " libraries " ] [ library ] [ " radarr_add_all_existing " ] = self . data [ " libraries " ] [ library ] . pop ( " radarr_add_all " )
if " sonarr_add_all " in self . data [ " libraries " ] [ library ] :
self . data [ " libraries " ] [ library ] [ " sonarr_add_all_existing " ] = self . data [ " libraries " ] [ library ] . pop ( " sonarr_add_all " )
if " plex " in self . data [ " libraries " ] [ library ] and self . data [ " libraries " ] [ library ] [ " plex " ] :
replace_attr ( self . data [ " libraries " ] [ library ] , " asset_directory " , " plex " )
replace_attr ( self . data [ " libraries " ] [ library ] , " sync_mode " , " plex " )
replace_attr ( self . data [ " libraries " ] [ library ] , " show_unmanaged " , " plex " )
replace_attr ( self . data [ " libraries " ] [ library ] , " show_filtered " , " plex " )
replace_attr ( self . data [ " libraries " ] [ library ] , " show_missing " , " plex " )
replace_attr ( self . data [ " libraries " ] [ library ] , " save_missing " , " plex " )
if " settings " in self . data [ " libraries " ] [ library ] and self . data [ " libraries " ] [ library ] [ " settings " ] :
if " collection_minimum " in self . data [ " libraries " ] [ library ] [ " settings " ] :
self . data [ " libraries " ] [ library ] [ " settings " ] [ " minimum_items " ] = self . data [ " libraries " ] [ library ] [ " settings " ] . pop ( " collection_minimum " )
if " save_missing " in self . data [ " libraries " ] [ library ] [ " settings " ] :
self . data [ " libraries " ] [ library ] [ " settings " ] [ " save_report " ] = self . data [ " libraries " ] [ library ] [ " settings " ] . pop ( " save_missing " )
if " radarr " in self . data [ " libraries " ] [ library ] and self . data [ " libraries " ] [ library ] [ " radarr " ] :
if " monitor " in self . data [ " libraries " ] [ library ] [ " radarr " ] and isinstance ( self . data [ " libraries " ] [ library ] [ " radarr " ] [ " monitor " ] , bool ) :
self . data [ " libraries " ] [ library ] [ " radarr " ] [ " monitor " ] = True if self . data [ " libraries " ] [ library ] [ " radarr " ] [ " monitor " ] else False
if " add " in self . data [ " libraries " ] [ library ] [ " radarr " ] :
self . data [ " libraries " ] [ library ] [ " radarr " ] [ " add_missing " ] = self . data [ " libraries " ] [ library ] [ " radarr " ] . pop ( " add " )
if " sonarr " in self . data [ " libraries " ] [ library ] and self . data [ " libraries " ] [ library ] [ " sonarr " ] :
if " add " in self . data [ " libraries " ] [ library ] [ " sonarr " ] :
self . data [ " libraries " ] [ library ] [ " sonarr " ] [ " add_missing " ] = self . data [ " libraries " ] [ library ] [ " sonarr " ] . pop ( " add " )
if " operations " in self . data [ " libraries " ] [ library ] and self . data [ " libraries " ] [ library ] [ " operations " ] :
if " radarr_add_all " in self . data [ " libraries " ] [ library ] [ " operations " ] :
self . data [ " libraries " ] [ library ] [ " operations " ] [ " radarr_add_all_existing " ] = self . data [ " libraries " ] [ library ] [ " operations " ] . pop ( " radarr_add_all " )
if " sonarr_add_all " in self . data [ " libraries " ] [ library ] [ " operations " ] :
self . data [ " libraries " ] [ library ] [ " operations " ] [ " sonarr_add_all_existing " ] = self . data [ " libraries " ] [ library ] [ " operations " ] . pop ( " sonarr_add_all " )
if " mass_imdb_parental_labels " in self . data [ " libraries " ] [ library ] [ " operations " ] and self . data [ " libraries " ] [ library ] [ " operations " ] [ " mass_imdb_parental_labels " ] :
if self . data [ " libraries " ] [ library ] [ " operations " ] [ " mass_imdb_parental_labels " ] == " with_none " :
self . data [ " libraries " ] [ library ] [ " operations " ] [ " mass_imdb_parental_labels " ] = " none "
elif self . data [ " libraries " ] [ library ] [ " operations " ] [ " mass_imdb_parental_labels " ] == " without_none " :
self . data [ " libraries " ] [ library ] [ " operations " ] [ " mass_imdb_parental_labels " ] = " mild "
if " webhooks " in self . data [ " libraries " ] [ library ] and self . data [ " libraries " ] [ library ] [ " webhooks " ] and " collection_changes " not in self . data [ " libraries " ] [ library ] [ " webhooks " ] :
changes = [ ]
def hooks ( hook_attr ) :
if hook_attr in self . data [ " libraries " ] [ library ] [ " webhooks " ] :
changes . extend ( [ w for w in util . get_list ( self . data [ " libraries " ] [ library ] [ " webhooks " ] . pop ( hook_attr ) , split = False ) if w not in changes ] )
hooks ( " collection_creation " )
hooks ( " collection_addition " )
hooks ( " collection_removal " )
hooks ( " collection_changes " )
self . data [ " libraries " ] [ library ] [ " webhooks " ] [ " changes " ] = None if not changes else changes if len ( changes ) > 1 else changes [ 0 ]
if " libraries " in self . data : self . data [ " libraries " ] = self . data . pop ( " libraries " )
if " playlist_files " in self . data : self . data [ " playlist_files " ] = self . data . pop ( " playlist_files " )
if " settings " in self . data :
temp = self . data . pop ( " settings " )
if " collection_minimum " in temp :
temp [ " minimum_items " ] = temp . pop ( " collection_minimum " )
if " playlist_sync_to_user " in temp :
temp [ " playlist_sync_to_users " ] = temp . pop ( " playlist_sync_to_user " )
if " save_missing " in temp :
temp [ " save_report " ] = temp . pop ( " save_missing " )
self . data [ " settings " ] = temp
if " webhooks " in self . data :
temp = self . data . pop ( " webhooks " )
if " changes " not in temp :
changes = [ ]
def hooks ( hook_attr ) :
if hook_attr in temp :
items = util . get_list ( temp . pop ( hook_attr ) , split = False )
if items :
changes . extend ( [ w for w in items if w not in changes ] )
hooks ( " collection_creation " )
hooks ( " collection_addition " )
hooks ( " collection_removal " )
hooks ( " collection_changes " )
temp [ " changes " ] = None if not changes else changes if len ( changes ) > 1 else changes [ 0 ]
self . data [ " webhooks " ] = temp
if " github " in self . data : self . data [ " github " ] = self . data . pop ( " github " )
if " plex " in self . data : self . data [ " plex " ] = self . data . pop ( " plex " )
if " tmdb " in self . data : self . data [ " tmdb " ] = self . data . pop ( " tmdb " )
if " tautulli " in self . data : self . data [ " tautulli " ] = self . data . pop ( " tautulli " )
if " omdb " in self . data : self . data [ " omdb " ] = self . data . pop ( " omdb " )
if " mdblist " in self . data : self . data [ " mdblist " ] = self . data . pop ( " mdblist " )
if " notifiarr " in self . data : self . data [ " notifiarr " ] = self . data . pop ( " notifiarr " )
if " gotify " in self . data : self . data [ " gotify " ] = self . data . pop ( " gotify " )
if " anidb " in self . data : self . data [ " anidb " ] = self . data . pop ( " anidb " )
if " radarr " in self . data :
if " monitor " in self . data [ " radarr " ] and isinstance ( self . data [ " radarr " ] [ " monitor " ] , bool ) :
self . data [ " radarr " ] [ " monitor " ] = True if self . data [ " radarr " ] [ " monitor " ] else False
temp = self . data . pop ( " radarr " )
if temp and " add " in temp :
temp [ " add_missing " ] = temp . pop ( " add " )
self . data [ " radarr " ] = temp
if " sonarr " in self . data :
temp = self . data . pop ( " sonarr " )
if temp and " add " in temp :
temp [ " add_missing " ] = temp . pop ( " add " )
self . data [ " sonarr " ] = temp
if " trakt " in self . data : self . data [ " trakt " ] = self . data . pop ( " trakt " )
if " mal " in self . data : self . data [ " mal " ] = self . data . pop ( " mal " )
def check_next ( next_data ) :
if isinstance ( next_data , dict ) :
for d in next_data :
out = check_next ( next_data [ d ] )
if out :
next_data [ d ] = out
elif isinstance ( next_data , list ) :
for d in next_data :
check_next ( d )
else :
for secret , secret_value in self . secrets . items ( ) :
for test in [ secret , secret . upper ( ) . replace ( " - " , " _ " ) ] :
if f " << { test } >> " in str ( next_data ) :
return str ( next_data ) . replace ( f " << { test } >> " , secret_value )
return next_data
if self . secrets :
check_next ( self . data )
def check_for_attribute ( data , attribute , parent = None , test_list = None , translations = None , default = None , do_print = True , default_is_none = False , req_default = False , var_type = " str " , throw = False , save = True , int_min = 0 , int_max = None ) :
endline = " "
if parent is not None :
if data and parent in data :
data = data [ parent ]
else :
data = None
do_print = False
save = False
final_value = data [ attribute ] if data and attribute in data else None
if translations and final_value in translations :
final_value = translations [ final_value ]
if self . read_only :
save = False
text = f " { attribute } attribute " if parent is None else f " { parent } sub-attribute { attribute } "
if data is None or attribute not in data :
message = f " { text } not found "
if parent and save is True :
yaml = YAML ( self . config_path )
endline = f " \n { parent } sub-attribute { attribute } added to config "
if parent not in yaml . data or not yaml . data [ parent ] : yaml . data [ parent ] = { attribute : default }
elif attribute not in yaml . data [ parent ] : yaml . data [ parent ] [ attribute ] = default
else : endline = " "
yaml . save ( )
if default_is_none and var_type in [ " list " , " int_list " , " lower_list " , " list_path " ] : return default if default else [ ]
elif final_value is None :
if default_is_none and var_type in [ " list " , " int_list " , " lower_list " , " list_path " ] : return default if default else [ ]
elif default_is_none : return None
else : message = f " { text } is blank "
elif var_type == " url " :
if final_value . endswith ( ( " \\ " , " / " ) ) : return final_value [ : - 1 ]
else : return final_value
elif var_type == " bool " :
if isinstance ( final_value , bool ) : return final_value
else : message = f " { text } must be either true or false "
elif var_type == " int " :
if isinstance ( final_value , int ) and final_value > = int_min and ( not int_max or final_value < = int_max ) :
return final_value
else :
message = f " { text } must an integer greater than or equal to { int_min } { f ' and less than or equal to { int_max } ' } "
elif var_type == " path " :
if os . path . exists ( os . path . abspath ( final_value ) ) : return final_value
else : message = f " Path { os . path . abspath ( final_value ) } does not exist "
elif var_type in [ " list " , " lower_list " , " int_list " ] :
output_list = [ ]
for output_item in util . get_list ( final_value , lower = var_type == " lower_list " , split = var_type != " list " , int_list = var_type == " int_list " ) :
if output_item not in output_list :
output_list . append ( output_item )
failed_items = [ o for o in output_list if o not in test_list ] if test_list else [ ]
if failed_items :
message = f " { text } : { ' , ' . join ( failed_items ) } is an invalid input "
else :
return output_list
elif var_type == " list_path " :
temp_list = [ ]
warning_message = " "
for p in util . get_list ( final_value , split = False ) :
if os . path . exists ( os . path . abspath ( p ) ) :
temp_list . append ( p )
else :
if len ( warning_message ) > 0 :
warning_message + = " \n "
warning_message + = f " Config Warning: Path does not exist: { os . path . abspath ( p ) } "
if do_print and warning_message :
logger . warning ( warning_message )
if len ( temp_list ) > 0 : return temp_list
else : message = " No Paths exist "
elif test_list is None or final_value in test_list : return final_value
else : message = f " { text } : { final_value } is an invalid input "
if var_type == " path " and default and os . path . exists ( os . path . abspath ( default ) ) :
return default
elif var_type == " path " and default :
if final_value :
message = f " neither { final_value } or the default path { default } could be found "
else :
message = f " no { text } found and the default path { default } could not be found "
default = None
if default is not None or default_is_none :
message = message + f " using { default } as default "
message = message + endline
if req_default and default is None :
raise Failed ( f " Config Error: { attribute } attribute must be set under { parent } globally or under this specific Library " )
options = " "
if test_list :
for test_option , test_description in test_list . items ( ) :
if len ( options ) > 0 :
options = f " { options } \n "
options = f " { options } { test_option } ( { test_description } ) "
if ( default is None and not default_is_none ) or throw :
if len ( options ) > 0 :
message = message + " \n " + options
raise Failed ( f " Config Error: { message } " )
if do_print :
logger . warning ( f " Config Warning: { message } " )
if final_value and test_list is not None and final_value not in test_list :
logger . warning ( options )
return default
self . general = {
" run_order " : check_for_attribute ( self . data , " run_order " , parent = " settings " , var_type = " lower_list " , test_list = run_order_options , default = [ " operations " , " metadata " , " collections " , " overlays " ] ) ,
" cache " : check_for_attribute ( self . data , " cache " , parent = " settings " , var_type = " bool " , default = True ) ,
" cache_expiration " : check_for_attribute ( self . data , " cache_expiration " , parent = " settings " , var_type = " int " , default = 60 , int_min = 1 ) ,
" asset_directory " : check_for_attribute ( self . data , " asset_directory " , parent = " settings " , var_type = " list_path " , default_is_none = True ) ,
" asset_folders " : check_for_attribute ( self . data , " asset_folders " , parent = " settings " , var_type = " bool " , default = True ) ,
" asset_depth " : check_for_attribute ( self . data , " asset_depth " , parent = " settings " , var_type = " int " , default = 0 ) ,
" create_asset_folders " : check_for_attribute ( self . data , " create_asset_folders " , parent = " settings " , var_type = " bool " , default = False ) ,
" prioritize_assets " : check_for_attribute ( self . data , " prioritize_assets " , parent = " settings " , var_type = " bool " , default = False ) ,
" dimensional_asset_rename " : check_for_attribute ( self . data , " dimensional_asset_rename " , parent = " settings " , var_type = " bool " , default = False ) ,
" download_url_assets " : check_for_attribute ( self . data , " download_url_assets " , parent = " settings " , var_type = " bool " , default = False ) ,
" show_missing_assets " : check_for_attribute ( self . data , " show_missing_assets " , parent = " settings " , var_type = " bool " , default = True ) ,
" show_missing_season_assets " : check_for_attribute ( self . data , " show_missing_season_assets " , parent = " settings " , var_type = " bool " , default = False ) ,
" show_missing_episode_assets " : check_for_attribute ( self . data , " show_missing_episode_assets " , parent = " settings " , var_type = " bool " , default = False ) ,
" show_asset_not_needed " : check_for_attribute ( self . data , " show_asset_not_needed " , parent = " settings " , var_type = " bool " , default = True ) ,
" sync_mode " : check_for_attribute ( self . data , " sync_mode " , parent = " settings " , default = " append " , test_list = sync_modes ) ,
" default_collection_order " : check_for_attribute ( self . data , " default_collection_order " , parent = " settings " , default_is_none = True ) ,
" minimum_items " : check_for_attribute ( self . data , " minimum_items " , parent = " settings " , var_type = " int " , default = 1 ) ,
" item_refresh_delay " : check_for_attribute ( self . data , " item_refresh_delay " , parent = " settings " , var_type = " int " , default = 0 ) ,
" delete_below_minimum " : check_for_attribute ( self . data , " delete_below_minimum " , parent = " settings " , var_type = " bool " , default = False ) ,
" delete_not_scheduled " : check_for_attribute ( self . data , " delete_not_scheduled " , parent = " settings " , var_type = " bool " , default = False ) ,
" run_again_delay " : check_for_attribute ( self . data , " run_again_delay " , parent = " settings " , var_type = " int " , default = 0 ) ,
" missing_only_released " : check_for_attribute ( self . data , " missing_only_released " , parent = " settings " , var_type = " bool " , default = False ) ,
" only_filter_missing " : check_for_attribute ( self . data , " only_filter_missing " , parent = " settings " , var_type = " bool " , default = False ) ,
" show_unmanaged " : check_for_attribute ( self . data , " show_unmanaged " , parent = " settings " , var_type = " bool " , default = True ) ,
" show_unconfigured " : check_for_attribute ( self . data , " show_unconfigured " , parent = " settings " , var_type = " bool " , default = True ) ,
" show_filtered " : check_for_attribute ( self . data , " show_filtered " , parent = " settings " , var_type = " bool " , default = False ) ,
" show_options " : check_for_attribute ( self . data , " show_options " , parent = " settings " , var_type = " bool " , default = False ) ,
" show_missing " : check_for_attribute ( self . data , " show_missing " , parent = " settings " , var_type = " bool " , default = True ) ,
" save_report " : check_for_attribute ( self . data , " save_report " , parent = " settings " , var_type = " bool " , default = False ) ,
" tvdb_language " : check_for_attribute ( self . data , " tvdb_language " , parent = " settings " , default = " default " ) ,
" ignore_ids " : check_for_attribute ( self . data , " ignore_ids " , parent = " settings " , var_type = " int_list " , default_is_none = True ) ,
" ignore_imdb_ids " : check_for_attribute ( self . data , " ignore_imdb_ids " , parent = " settings " , var_type = " lower_list " , default_is_none = True ) ,
" playlist_sync_to_users " : check_for_attribute ( self . data , " playlist_sync_to_users " , parent = " settings " , default = " all " , default_is_none = True ) ,
" playlist_exclude_users " : check_for_attribute ( self . data , " playlist_exclude_users " , parent = " settings " , default_is_none = True ) ,
" playlist_report " : check_for_attribute ( self . data , " playlist_report " , parent = " settings " , var_type = " bool " , default = True ) ,
" verify_ssl " : check_for_attribute ( self . data , " verify_ssl " , parent = " settings " , var_type = " bool " , default = True ) ,
" custom_repo " : check_for_attribute ( self . data , " custom_repo " , parent = " settings " , default_is_none = True ) ,
" overlay_artwork_filetype " : check_for_attribute ( self . data , " overlay_artwork_filetype " , parent = " settings " , test_list = filetype_list , translations = { " webp " : " webp_lossy " } , default = " jpg " ) ,
" overlay_artwork_quality " : check_for_attribute ( self . data , " overlay_artwork_quality " , parent = " settings " , var_type = " int " , default_is_none = True , int_min = 1 , int_max = 100 ) ,
" assets_for_all " : check_for_attribute ( self . data , " assets_for_all " , parent = " settings " , var_type = " bool " , default = False , save = False , do_print = False )
}
self . custom_repo = None
if self . general [ " custom_repo " ] :
repo = self . general [ " custom_repo " ]
if " https://github.com/ " in repo :
repo = repo . replace ( " https://github.com/ " , " https://raw.githubusercontent.com/ " ) . replace ( " /tree/ " , " / " )
self . custom_repo = repo
self . latest_version = util . current_version ( self . version , branch = self . branch )
add_operations = True if " operations " not in self . general [ " run_order " ] else False
add_metadata = True if " metadata " not in self . general [ " run_order " ] else False
add_collection = True if " collections " not in self . general [ " run_order " ] else False
add_overlays = True if " overlays " not in self . general [ " run_order " ] else False
if add_operations or add_metadata or add_collection or add_overlays :
new_run_order = [ ]
for run_order in self . general [ " run_order " ] :
if add_operations and not new_run_order :
new_run_order . append ( " operations " )
if add_metadata :
new_run_order . append ( " metadata " )
if add_collection :
new_run_order . append ( " collections " )
new_run_order . append ( run_order )
if add_metadata and run_order == " operations " :
new_run_order . append ( " metadata " )
if add_collection and ( run_order == " metadata " or ( run_order == " operations " and add_metadata ) ) :
new_run_order . append ( " collections " )
if add_overlays :
new_run_order . append ( " overlays " )
self . general [ " run_order " ] = new_run_order
yaml = YAML ( self . config_path )
if " settings " not in yaml . data or not yaml . data [ " settings " ] :
yaml . data [ " settings " ] = { }
yaml . data [ " settings " ] [ " run_order " ] = new_run_order
yaml . save ( )
self . session = requests . Session ( )
if not self . general [ " verify_ssl " ] :
self . session . verify = False
if self . session . verify is False :
import urllib3
urllib3 . disable_warnings ( urllib3 . exceptions . InsecureRequestWarning )
if self . general [ " cache " ] :
logger . separator ( )
self . Cache = Cache ( self . config_path , self . general [ " cache_expiration " ] )
else :
self . Cache = None
self . GitHub = GitHub ( self , { " token " : check_for_attribute ( self . data , " token " , parent = " github " , default_is_none = True ) } )
logger . separator ( )
self . NotifiarrFactory = None
if " notifiarr " in self . data :
logger . info ( " Connecting to Notifiarr... " )
try :
self . NotifiarrFactory = Notifiarr ( self , { " apikey " : check_for_attribute ( self . data , " apikey " , parent = " notifiarr " , throw = True ) } )
except Failed as e :
if str ( e ) . endswith ( " is blank " ) :
logger . warning ( e )
else :
logger . stacktrace ( )
logger . error ( e )
logger . info ( f " Notifiarr Connection { ' Failed ' if self . NotifiarrFactory is None else ' Successful ' } " )
else :
logger . info ( " notifiarr attribute not found " )
self . GotifyFactory = None
if " gotify " in self . data :
logger . info ( " Connecting to Gotify... " )
try :
self . GotifyFactory = Gotify ( self , {
" url " : check_for_attribute ( self . data , " url " , parent = " gotify " , throw = True ) ,
" token " : check_for_attribute ( self . data , " token " , parent = " gotify " , throw = True )
} )
except Failed as e :
if str ( e ) . endswith ( " is blank " ) :
logger . warning ( e )
else :
logger . stacktrace ( )
logger . error ( e )
logger . info ( f " Gotify Connection { ' Failed ' if self . GotifyFactory is None else ' Successful ' } " )
else :
logger . info ( " gotify attribute not found " )
self . webhooks = {
" error " : check_for_attribute ( self . data , " error " , parent = " webhooks " , var_type = " list " , default_is_none = True ) ,
" version " : check_for_attribute ( self . data , " version " , parent = " webhooks " , var_type = " list " , default_is_none = True ) ,
" run_start " : check_for_attribute ( self . data , " run_start " , parent = " webhooks " , var_type = " list " , default_is_none = True ) ,
" run_end " : check_for_attribute ( self . data , " run_end " , parent = " webhooks " , var_type = " list " , default_is_none = True ) ,
" changes " : check_for_attribute ( self . data , " changes " , parent = " webhooks " , var_type = " list " , default_is_none = True ) ,
" delete " : check_for_attribute ( self . data , " delete " , parent = " webhooks " , var_type = " list " , default_is_none = True )
}
self . Webhooks = Webhooks ( self , self . webhooks , notifiarr = self . NotifiarrFactory , gotify = self . GotifyFactory )
try :
self . Webhooks . start_time_hooks ( self . start_time )
if self . version [ 0 ] != " Unknown " and self . latest_version [ 0 ] != " Unknown " and self . version [ 1 ] != self . latest_version [ 1 ] or ( self . version [ 2 ] and self . version [ 2 ] < self . latest_version [ 2 ] ) :
self . Webhooks . version_hooks ( self . version , self . latest_version )
except Failed as e :
logger . stacktrace ( )
logger . error ( f " Webhooks Error: { e } " )
logger . save_errors = True
logger . separator ( )
try :
self . TMDb = None
if " tmdb " in self . data :
logger . info ( " Connecting to TMDb... " )
self . TMDb = TMDb ( self , {
" apikey " : check_for_attribute ( self . data , " apikey " , parent = " tmdb " , throw = True ) ,
" language " : check_for_attribute ( self . data , " language " , parent = " tmdb " , default = " en " ) ,
" expiration " : check_for_attribute ( self . data , " cache_expiration " , parent = " tmdb " , var_type = " int " , default = 60 , int_min = 1 )
} )
regions = { k . upper ( ) : v for k , v in self . TMDb . iso_3166_1 . items ( ) }
region = check_for_attribute ( self . data , " region " , parent = " tmdb " , test_list = regions , default_is_none = True )
self . TMDb . region = str ( region ) . upper ( ) if region else region
logger . info ( f " TMDb Connection { ' Failed ' if self . TMDb is None else ' Successful ' } " )
else :
raise Failed ( " Config Error: tmdb attribute not found " )
logger . separator ( )
self . OMDb = None
if " omdb " in self . data :
logger . info ( " Connecting to OMDb... " )
try :
self . OMDb = OMDb ( self , {
" apikey " : check_for_attribute ( self . data , " apikey " , parent = " omdb " , throw = True ) ,
" expiration " : check_for_attribute ( self . data , " cache_expiration " , parent = " omdb " , var_type = " int " , default = 60 , int_min = 1 )
} )
except Failed as e :
if str ( e ) . endswith ( " is blank " ) :
logger . warning ( e )
else :
logger . error ( e )
logger . info ( f " OMDb Connection { ' Failed ' if self . OMDb is None else ' Successful ' } " )
else :
logger . info ( " omdb attribute not found " )
logger . separator ( )
self . MDBList = MDBList ( self )
if " mdblist " in self . data :
logger . info ( " Connecting to MDBList... " )
try :
self . MDBList . add_key (
check_for_attribute ( self . data , " apikey " , parent = " mdblist " , throw = True ) ,
check_for_attribute ( self . data , " cache_expiration " , parent = " mdblist " , var_type = " int " , default = 60 , int_min = 1 )
)
logger . info ( " MDBList Connection Successful " )
except Failed as e :
if str ( e ) . endswith ( " is blank " ) :
logger . warning ( e )
else :
logger . error ( e )
logger . info ( " MDBList Connection Failed " )
else :
logger . info ( " mdblist attribute not found " )
logger . separator ( )
self . Trakt = None
if " trakt " in self . data :
logger . info ( " Connecting to Trakt... " )
try :
self . Trakt = Trakt ( self , {
" client_id " : check_for_attribute ( self . data , " client_id " , parent = " trakt " , throw = True ) ,
" client_secret " : check_for_attribute ( self . data , " client_secret " , parent = " trakt " , throw = True ) ,
" pin " : check_for_attribute ( self . data , " pin " , parent = " trakt " , default_is_none = True ) ,
" config_path " : self . config_path ,
" authorization " : self . data [ " trakt " ] [ " authorization " ] if " authorization " in self . data [ " trakt " ] else None
} )
except Failed as e :
if str ( e ) . endswith ( " is blank " ) :
logger . warning ( e )
else :
logger . error ( e )
logger . info ( f " Trakt Connection { ' Failed ' if self . Trakt is None else ' Successful ' } " )
else :
logger . info ( " trakt attribute not found " )
logger . separator ( )
self . MyAnimeList = None
if " mal " in self . data :
logger . info ( " Connecting to My Anime List... " )
try :
self . MyAnimeList = MyAnimeList ( self , {
" client_id " : check_for_attribute ( self . data , " client_id " , parent = " mal " , throw = True ) ,
" client_secret " : check_for_attribute ( self . data , " client_secret " , parent = " mal " , throw = True ) ,
" localhost_url " : check_for_attribute ( self . data , " localhost_url " , parent = " mal " , default_is_none = True ) ,
" cache_expiration " : check_for_attribute ( self . data , " cache_expiration " , parent = " mal " , var_type = " int " , default = 60 , int_min = 1 ) ,
" config_path " : self . config_path ,
" authorization " : self . data [ " mal " ] [ " authorization " ] if " authorization " in self . data [ " mal " ] else None
} )
except Failed as e :
if str ( e ) . endswith ( " is blank " ) :
logger . warning ( e )
else :
logger . error ( e )
logger . info ( f " My Anime List Connection { ' Failed ' if self . MyAnimeList is None else ' Successful ' } " )
else :
logger . info ( " mal attribute not found " )
self . AniDB = AniDB ( self , { " language " : check_for_attribute ( self . data , " language " , parent = " anidb " , default = " en " ) } )
if " anidb " in self . data :
logger . separator ( )
logger . info ( " Connecting to AniDB... " )
try :
self . AniDB . authorize (
check_for_attribute ( self . data , " client " , parent = " anidb " , throw = True ) ,
check_for_attribute ( self . data , " version " , parent = " anidb " , var_type = " int " , throw = True ) ,
check_for_attribute ( self . data , " cache_expiration " , parent = " anidb " , var_type = " int " , default = 60 , int_min = 1 )
)
except Failed as e :
if str ( e ) . endswith ( " is blank " ) :
logger . warning ( e )
else :
logger . error ( e )
logger . info ( f " AniDB API Connection { ' Successful ' if self . AniDB . is_authorized else ' Failed ' } " )
try :
self . AniDB . login (
check_for_attribute ( self . data , " username " , parent = " anidb " , throw = True ) ,
check_for_attribute ( self . data , " password " , parent = " anidb " , throw = True )
)
except Failed as e :
if str ( e ) . endswith ( " is blank " ) :
logger . warning ( e )
else :
logger . error ( e )
logger . info ( f " AniDB Login { ' Successful ' if self . AniDB . username else ' Failed Continuing as Guest ' } " )
logger . separator ( )
self . playlist_names = [ ]
self . playlist_files = [ ]
if " playlist_files " not in self . data :
logger . info ( " playlist_files attribute not found " )
elif not self . data [ " playlist_files " ] :
logger . info ( " playlist_files attribute is blank " )
else :
logger . info ( " " )
logger . info ( " Reading in Playlist Files " )
files , had_scheduled = util . load_files ( self . data [ " playlist_files " ] , " playlist_files " , schedule = ( current_time , self . run_hour , self . ignore_schedules ) )
if not files and not had_scheduled :
raise Failed ( " Config Error: No Paths Found for playlist_files " )
for file_type , playlist_file , temp_vars , asset_directory in files :
try :
playlist_obj = PlaylistFile ( self , file_type , playlist_file , temp_vars , asset_directory )
self . playlist_names . extend ( [ p for p in playlist_obj . playlists ] )
self . playlist_files . append ( playlist_obj )
except Failed as e :
logger . info ( " Playlist File Failed To Load " )
logger . error ( e )
except NotScheduled as e :
logger . info ( " " )
logger . separator ( f " Skipping { e } Playlist File " )
self . TVDb = TVDb ( self , self . general [ " tvdb_language " ] , self . general [ " cache_expiration " ] )
self . IMDb = IMDb ( self )
self . Convert = Convert ( self )
self . AniList = AniList ( self )
self . ICheckMovies = ICheckMovies ( self )
self . Letterboxd = Letterboxd ( self )
self . BoxOfficeMojo = BoxOfficeMojo ( self )
self . Reciperr = Reciperr ( self )
self . Ergast = Ergast ( self )
logger . separator ( )
logger . info ( " Connecting to Plex Libraries... " )
self . general [ " plex " ] = {
" url " : check_for_attribute ( self . data , " url " , parent = " plex " , var_type = " url " , default_is_none = True ) ,
" token " : check_for_attribute ( self . data , " token " , parent = " plex " , default_is_none = True ) ,
" timeout " : check_for_attribute ( self . data , " timeout " , parent = " plex " , var_type = " int " , default = 60 ) ,
" verify_ssl " : check_for_attribute ( self . data , " verify_ssl " , parent = " plex " , var_type = " bool " , default_is_none = True ) ,
" db_cache " : check_for_attribute ( self . data , " db_cache " , parent = " plex " , var_type = " int " , default_is_none = True )
}
for attr in [ " clean_bundles " , " empty_trash " , " optimize " ] :
try :
self . general [ " plex " ] [ attr ] = check_for_attribute ( self . data , attr , parent = " plex " , var_type = " bool " , default = False , throw = True )
except Failed as e :
if " plex " in self . data and attr in self . data [ " plex " ] and self . data [ " plex " ] [ attr ] :
self . general [ " plex " ] [ attr ] = self . data [ " plex " ] [ attr ]
else :
self . general [ " plex " ] [ attr ] = False
logger . warning ( str ( e ) . replace ( " Error " , " Warning " ) )
self . general [ " radarr " ] = {
" url " : check_for_attribute ( self . data , " url " , parent = " radarr " , var_type = " url " , default_is_none = True ) ,
" token " : check_for_attribute ( self . data , " token " , parent = " radarr " , default_is_none = True ) ,
" add_missing " : check_for_attribute ( self . data , " add_missing " , parent = " radarr " , var_type = " bool " , default = False ) ,
" add_existing " : check_for_attribute ( self . data , " add_existing " , parent = " radarr " , var_type = " bool " , default = False ) ,
" upgrade_existing " : check_for_attribute ( self . data , " upgrade_existing " , parent = " radarr " , var_type = " bool " , default = False ) ,
" monitor_existing " : check_for_attribute ( self . data , " monitor_existing " , parent = " radarr " , var_type = " bool " , default = False ) ,
" ignore_cache " : check_for_attribute ( self . data , " ignore_cache " , parent = " radarr " , var_type = " bool " , default = False ) ,
" root_folder_path " : check_for_attribute ( self . data , " root_folder_path " , parent = " radarr " , default_is_none = True ) ,
" monitor " : check_for_attribute ( self . data , " monitor " , parent = " radarr " , var_type = " bool " , default = True ) ,
" availability " : check_for_attribute ( self . data , " availability " , parent = " radarr " , test_list = radarr . availability_descriptions , default = " announced " ) ,
" quality_profile " : check_for_attribute ( self . data , " quality_profile " , parent = " radarr " , default_is_none = True ) ,
" tag " : check_for_attribute ( self . data , " tag " , parent = " radarr " , var_type = " lower_list " , default_is_none = True ) ,
" search " : check_for_attribute ( self . data , " search " , parent = " radarr " , var_type = " bool " , default = False ) ,
" radarr_path " : check_for_attribute ( self . data , " radarr_path " , parent = " radarr " , default_is_none = True ) ,
" plex_path " : check_for_attribute ( self . data , " plex_path " , parent = " radarr " , default_is_none = True )
}
self . general [ " sonarr " ] = {
" url " : check_for_attribute ( self . data , " url " , parent = " sonarr " , var_type = " url " , default_is_none = True ) ,
" token " : check_for_attribute ( self . data , " token " , parent = " sonarr " , default_is_none = True ) ,
" add_missing " : check_for_attribute ( self . data , " add_missing " , parent = " sonarr " , var_type = " bool " , default = False ) ,
" add_existing " : check_for_attribute ( self . data , " add_existing " , parent = " sonarr " , var_type = " bool " , default = False ) ,
" upgrade_existing " : check_for_attribute ( self . data , " upgrade_existing " , parent = " sonarr " , var_type = " bool " , default = False ) ,
" monitor_existing " : check_for_attribute ( self . data , " monitor_existing " , parent = " sonarr " , var_type = " bool " , default = False ) ,
" ignore_cache " : check_for_attribute ( self . data , " ignore_cache " , parent = " sonarr " , var_type = " bool " , default = False ) ,
" root_folder_path " : check_for_attribute ( self . data , " root_folder_path " , parent = " sonarr " , default_is_none = True ) ,
" monitor " : check_for_attribute ( self . data , " monitor " , parent = " sonarr " , test_list = sonarr . monitor_descriptions , default = " all " ) ,
" quality_profile " : check_for_attribute ( self . data , " quality_profile " , parent = " sonarr " , default_is_none = True ) ,
" language_profile " : check_for_attribute ( self . data , " language_profile " , parent = " sonarr " , default_is_none = True ) ,
" series_type " : check_for_attribute ( self . data , " series_type " , parent = " sonarr " , test_list = sonarr . series_type_descriptions , default = " standard " ) ,
" season_folder " : check_for_attribute ( self . data , " season_folder " , parent = " sonarr " , var_type = " bool " , default = True ) ,
" tag " : check_for_attribute ( self . data , " tag " , parent = " sonarr " , var_type = " lower_list " , default_is_none = True ) ,
" search " : check_for_attribute ( self . data , " search " , parent = " sonarr " , var_type = " bool " , default = False ) ,
" cutoff_search " : check_for_attribute ( self . data , " cutoff_search " , parent = " sonarr " , var_type = " bool " , default = False ) ,
" sonarr_path " : check_for_attribute ( self . data , " sonarr_path " , parent = " sonarr " , default_is_none = True ) ,
" plex_path " : check_for_attribute ( self . data , " plex_path " , parent = " sonarr " , default_is_none = True )
}
self . general [ " tautulli " ] = {
" url " : check_for_attribute ( self . data , " url " , parent = " tautulli " , var_type = " url " , default_is_none = True ) ,
" apikey " : check_for_attribute ( self . data , " apikey " , parent = " tautulli " , default_is_none = True )
}
self . libraries = [ ]
libs = check_for_attribute ( self . data , " libraries " , throw = True )
for library_name , lib in libs . items ( ) :
if self . requested_libraries and library_name not in self . requested_libraries :
continue
params = { o : None for o in library_operations }
params [ " mapping_name " ] = str ( library_name )
params [ " name " ] = str ( lib [ " library_name " ] ) if lib and " library_name " in lib and lib [ " library_name " ] else str ( library_name )
display_name = f " { params [ ' name ' ] } ( { params [ ' mapping_name ' ] } ) " if lib and " library_name " in lib and lib [ " library_name " ] else params [ " mapping_name " ]
logger . separator ( f " { display_name } Configuration " )
logger . info ( " " )
logger . info ( f " Connecting to { display_name } Library... " )
params [ " run_order " ] = check_for_attribute ( lib , " run_order " , parent = " settings " , var_type = " lower_list " , default = self . general [ " run_order " ] , do_print = False , save = False )
params [ " asset_directory " ] = check_for_attribute ( lib , " asset_directory " , parent = " settings " , var_type = " list_path " , default = self . general [ " asset_directory " ] , default_is_none = True , do_print = False , save = False )
params [ " asset_folders " ] = check_for_attribute ( lib , " asset_folders " , parent = " settings " , var_type = " bool " , default = self . general [ " asset_folders " ] , do_print = False , save = False )
params [ " asset_depth " ] = check_for_attribute ( lib , " asset_depth " , parent = " settings " , var_type = " int " , default = self . general [ " asset_depth " ] , do_print = False , save = False )
params [ " sync_mode " ] = check_for_attribute ( lib , " sync_mode " , parent = " settings " , test_list = sync_modes , default = self . general [ " sync_mode " ] , do_print = False , save = False )
params [ " default_collection_order " ] = check_for_attribute ( lib , " default_collection_order " , parent = " settings " , default = self . general [ " default_collection_order " ] , default_is_none = True , do_print = False , save = False )
params [ " show_unmanaged " ] = check_for_attribute ( lib , " show_unmanaged " , parent = " settings " , var_type = " bool " , default = self . general [ " show_unmanaged " ] , do_print = False , save = False )
params [ " show_unconfigured " ] = check_for_attribute ( lib , " show_unconfigured " , parent = " settings " , var_type = " bool " , default = self . general [ " show_unconfigured " ] , do_print = False , save = False )
params [ " show_filtered " ] = check_for_attribute ( lib , " show_filtered " , parent = " settings " , var_type = " bool " , default = self . general [ " show_filtered " ] , do_print = False , save = False )
params [ " show_options " ] = check_for_attribute ( lib , " show_options " , parent = " settings " , var_type = " bool " , default = self . general [ " show_options " ] , do_print = False , save = False )
params [ " show_missing " ] = check_for_attribute ( lib , " show_missing " , parent = " settings " , var_type = " bool " , default = self . general [ " show_missing " ] , do_print = False , save = False )
params [ " show_missing_assets " ] = check_for_attribute ( lib , " show_missing_assets " , parent = " settings " , var_type = " bool " , default = self . general [ " show_missing_assets " ] , do_print = False , save = False )
params [ " save_report " ] = check_for_attribute ( lib , " save_report " , parent = " settings " , var_type = " bool " , default = self . general [ " save_report " ] , do_print = False , save = False )
params [ " missing_only_released " ] = check_for_attribute ( lib , " missing_only_released " , parent = " settings " , var_type = " bool " , default = self . general [ " missing_only_released " ] , do_print = False , save = False )
params [ " only_filter_missing " ] = check_for_attribute ( lib , " only_filter_missing " , parent = " settings " , var_type = " bool " , default = self . general [ " only_filter_missing " ] , do_print = False , save = False )
params [ " create_asset_folders " ] = check_for_attribute ( lib , " create_asset_folders " , parent = " settings " , var_type = " bool " , default = self . general [ " create_asset_folders " ] , do_print = False , save = False )
params [ " dimensional_asset_rename " ] = check_for_attribute ( lib , " dimensional_asset_rename " , parent = " settings " , var_type = " bool " , default = self . general [ " dimensional_asset_rename " ] , do_print = False , save = False )
params [ " prioritize_assets " ] = check_for_attribute ( lib , " prioritize_assets " , parent = " settings " , var_type = " bool " , default = self . general [ " prioritize_assets " ] , do_print = False , save = False )
params [ " download_url_assets " ] = check_for_attribute ( lib , " download_url_assets " , parent = " settings " , var_type = " bool " , default = self . general [ " download_url_assets " ] , do_print = False , save = False )
params [ " show_missing_season_assets " ] = check_for_attribute ( lib , " show_missing_season_assets " , parent = " settings " , var_type = " bool " , default = self . general [ " show_missing_season_assets " ] , do_print = False , save = False )
params [ " show_missing_episode_assets " ] = check_for_attribute ( lib , " show_missing_episode_assets " , parent = " settings " , var_type = " bool " , default = self . general [ " show_missing_episode_assets " ] , do_print = False , save = False )
params [ " show_asset_not_needed " ] = check_for_attribute ( lib , " show_asset_not_needed " , parent = " settings " , var_type = " bool " , default = self . general [ " show_asset_not_needed " ] , do_print = False , save = False )
params [ " minimum_items " ] = check_for_attribute ( lib , " minimum_items " , parent = " settings " , var_type = " int " , default = self . general [ " minimum_items " ] , do_print = False , save = False )
params [ " item_refresh_delay " ] = check_for_attribute ( lib , " item_refresh_delay " , parent = " settings " , var_type = " int " , default = self . general [ " item_refresh_delay " ] , do_print = False , save = False )
params [ " delete_below_minimum " ] = check_for_attribute ( lib , " delete_below_minimum " , parent = " settings " , var_type = " bool " , default = self . general [ " delete_below_minimum " ] , do_print = False , save = False )
params [ " delete_not_scheduled " ] = check_for_attribute ( lib , " delete_not_scheduled " , parent = " settings " , var_type = " bool " , default = self . general [ " delete_not_scheduled " ] , do_print = False , save = False )
params [ " ignore_ids " ] = check_for_attribute ( lib , " ignore_ids " , parent = " settings " , var_type = " int_list " , default_is_none = True , do_print = False , save = False )
params [ " ignore_ids " ] . extend ( [ i for i in self . general [ " ignore_ids " ] if i not in params [ " ignore_ids " ] ] )
params [ " ignore_imdb_ids " ] = check_for_attribute ( lib , " ignore_imdb_ids " , parent = " settings " , var_type = " lower_list " , default_is_none = True , do_print = False , save = False )
params [ " ignore_imdb_ids " ] . extend ( [ i for i in self . general [ " ignore_imdb_ids " ] if i not in params [ " ignore_imdb_ids " ] ] )
params [ " overlay_artwork_filetype " ] = check_for_attribute ( lib , " overlay_artwork_filetype " , parent = " settings " , test_list = filetype_list , translations = { " webp " : " webp_lossy " } , default = self . general [ " overlay_artwork_filetype " ] , do_print = False , save = False )
params [ " overlay_artwork_quality " ] = check_for_attribute ( lib , " overlay_artwork_quality " , parent = " settings " , var_type = " int " , default = self . general [ " overlay_artwork_quality " ] , default_is_none = True , int_min = 1 , int_max = 100 , do_print = False , save = False )
params [ " changes_webhooks " ] = check_for_attribute ( lib , " changes " , parent = " webhooks " , var_type = " list " , default = self . webhooks [ " changes " ] , do_print = False , save = False , default_is_none = True )
params [ " report_path " ] = None
if lib and " report_path " in lib and lib [ " report_path " ] :
if os . path . exists ( os . path . dirname ( os . path . abspath ( lib [ " report_path " ] ) ) ) :
params [ " report_path " ] = lib [ " report_path " ]
else :
logger . error ( f " Config Error: Folder { os . path . dirname ( os . path . abspath ( lib [ ' report_path ' ] ) ) } does not exist " )
if lib and " operations " in lib and lib [ " operations " ] :
final_operations = { }
logger . separator ( " Operation Configuration " , space = False , border = False )
config_ops = util . parse ( " Config " , " operations " , lib [ " operations " ] , datatype = " listdict " )
op_size = len ( config_ops )
for i , config_op in enumerate ( config_ops , 1 ) :
logger . info ( " " )
logger . info ( f " Operation { i } / { op_size } " )
for k , v in config_op . items ( ) :
logger . info ( f " { k } : { v } " )
if " schedule " in config_op and not self . ignore_schedules :
if not config_op [ " schedule " ] :
logger . error ( " Config Error: schedule attribute is blank " )
else :
try :
util . schedule_check ( " schedule " , config_op [ " schedule " ] , current_time , self . run_hour )
except NotScheduled :
logger . info ( f " Skipping Operation Not Scheduled for { config_op [ ' schedule ' ] } " )
continue
if " delete_collections " not in config_op and ( " delete_unmanaged_collections " in config_op or " delete_collections_with_less " in config_op ) :
config_op [ " delete_collections " ] = { }
if " delete_unmanaged_collections " in config_op :
config_op [ " delete_collections " ] [ " unmanaged " ] = check_for_attribute ( config_op , " delete_unmanaged_collections " , var_type = " bool " , default = False , save = False )
if " delete_collections_with_less " in config_op :
config_op [ " delete_collections " ] [ " less " ] = check_for_attribute ( config_op , " delete_collections_with_less " , var_type = " int " , default_is_none = True , save = False )
section_final = { }
for op , data_type in library_operations . items ( ) :
if op not in config_op :
continue
if op == " mass_imdb_parental_labels " :
section_final [ op ] = check_for_attribute ( config_op , op , test_list = data_type , default_is_none = True , save = False )
elif isinstance ( data_type , dict ) :
try :
if not config_op [ op ] :
raise Failed ( " is blank " )
input_list = config_op [ op ] if isinstance ( config_op [ op ] , list ) else [ config_op [ op ] ]
final_list = [ ]
for list_attr in input_list :
if not list_attr :
raise Failed ( f " has a blank value " )
if str ( list_attr ) . lower ( ) in data_type :
final_list . append ( str ( list_attr ) . lower ( ) )
elif op in [ " mass_content_rating_update " , " mass_studio_update " , " mass_original_title_update " ] :
final_list . append ( str ( list_attr ) )
elif op == " mass_genre_update " :
final_list . append ( list_attr if isinstance ( list_attr , list ) else [ list_attr ] )
elif op == " mass_originally_available_update " :
final_list . append ( util . validate_date ( list_attr ) )
elif op . endswith ( " rating_update " ) :
final_list . append ( util . check_int ( list_attr , datatype = " float " , minimum = 0 , maximum = 10 , throw = True ) )
else :
raise Failed ( f " has an invalid value: { list_attr } " )
section_final [ op ] = final_list
except Failed as e :
logger . error ( f " Config Error: { op } { e } " )
elif op == " mass_collection_mode " :
section_final [ op ] = util . check_collection_mode ( config_op [ op ] )
elif data_type == " dict " :
input_dict = config_op [ op ]
if op in [ " mass_poster_update " , " mass_background_update " , " mass_collection_content_rating_update " ] and input_dict and not isinstance ( input_dict , dict ) :
input_dict = { " source " : input_dict }
if not input_dict or not isinstance ( input_dict , dict ) :
raise Failed ( f " Config Error: { op } must be a dictionary " )
elif op in [ " mass_poster_update " , " mass_background_update " ] :
section_final [ op ] = {
" source " : check_for_attribute ( input_dict , " source " , test_list = mass_image_options , default_is_none = True , save = False ) ,
" seasons " : check_for_attribute ( input_dict , " seasons " , var_type = " bool " , default = True , save = False ) ,
" episodes " : check_for_attribute ( input_dict , " episodes " , var_type = " bool " , default = True , save = False ) ,
}
elif op == " metadata_backup " :
default_path = os . path . join ( default_dir , f " { str ( library_name ) } _Metadata_Backup.yml " )
if " path " not in input_dict :
logger . warning ( f " Config Warning: path attribute not found using default: { default_path } " )
elif " path " in input_dict and not input_dict [ " path " ] :
logger . warning ( f " Config Warning: path attribute blank using default: { default_path } " )
else :
default_path = input_dict [ " path " ]
section_final [ op ] = {
" path " : default_path ,
" exclude " : check_for_attribute ( input_dict , " exclude " , var_type = " lower_list " , default_is_none = True , save = False ) ,
" sync_tags " : check_for_attribute ( input_dict , " sync_tags " , var_type = " bool " , default = False , save = False ) ,
" add_blank_entries " : check_for_attribute ( input_dict , " add_blank_entries " , var_type = " bool " , default = True , save = False )
}
elif " mapper " in op :
section_final [ op ] = { }
for old_value , new_value in input_dict . items ( ) :
if not old_value :
logger . warning ( " Config Warning: The key cannot be empty " )
elif new_value and str ( old_value ) == str ( new_value ) :
logger . warning ( f " Config Warning: { op } value ' { new_value } ' ignored as it cannot be mapped to itself " )
else :
section_final [ op ] [ str ( old_value ) ] = str ( new_value ) if new_value else None # noqa
elif op == " delete_collections " :
section_final [ op ] = {
" managed " : check_for_attribute ( input_dict , " managed " , var_type = " bool " , default_is_none = True , save = False ) ,
" configured " : check_for_attribute ( input_dict , " configured " , var_type = " bool " , default_is_none = True , save = False ) ,
" less " : check_for_attribute ( input_dict , " less " , var_type = " int " , default_is_none = True , save = False , int_min = 1 ) ,
}
elif op == " mass_collection_content_rating_update " :
section_final [ op ] = {
" source " : check_for_attribute ( input_dict , " source " , test_list = mass_collection_content_options , default_is_none = True , save = False ) ,
" ranking " : check_for_attribute ( input_dict , " ranking " , var_type = " list " , default = content_rating_default , save = False ) ,
}
else :
section_final [ op ] = check_for_attribute ( config_op , op , var_type = data_type , default = False , save = False )
for k , v in section_final . items ( ) :
if k not in final_operations :
final_operations [ k ] = v
else :
logger . warning ( f " Config Warning: Operation { k } already scheduled " )
for k , v in final_operations . items ( ) :
params [ k ] = v
for mass_key in operations . meta_operations :
if not params [ mass_key ] :
continue
sources = params [ mass_key ] [ " source " ] if isinstance ( params [ mass_key ] , dict ) else params [ mass_key ]
if not isinstance ( sources , list ) :
sources = [ sources ]
try :
for source in sources :
if source and source == " omdb " and self . OMDb is None :
raise Failed ( f " { source } without a successful OMDb Connection " )
if source and str ( source ) . startswith ( " mdb " ) and not self . MDBList . has_key :
raise Failed ( f " { source } without a successful MDBList Connection " )
if source and str ( source ) . startswith ( " anidb " ) and not self . AniDB . is_authorized :
raise Failed ( f " { source } without a successful AniDB Connection " )
if source and str ( source ) . startswith ( " mal " ) and self . MyAnimeList is None :
raise Failed ( f " { source } without a successful MyAnimeList Connection " )
if source and str ( source ) . startswith ( " trakt " ) and self . Trakt is None :
raise Failed ( f " { source } without a successful Trakt Connection " )
except Failed as e :
logger . error ( f " Config Error: { mass_key } cannot use { e } " )
params [ mass_key ] = None
lib_vars = { }
if lib and " template_variables " in lib and lib [ " template_variables " ] and isinstance ( lib [ " template_variables " ] , dict ) :
lib_vars = lib [ " template_variables " ]
params [ " collection_files " ] = [ ]
try :
if lib and " collection_files " in lib :
logger . info ( " " )
logger . info ( " Reading in Collection Files " )
if not lib [ " collection_files " ] :
raise Failed ( " Config Error: collection_files attribute is blank " )
files , had_scheduled = util . load_files ( lib [ " collection_files " ] , " collection_files " , schedule = ( current_time , self . run_hour , self . ignore_schedules ) , lib_vars = lib_vars )
if files :
params [ " collection_files " ] = files
elif not had_scheduled :
raise Failed ( " Config Error: No Paths Found for collection_files " )
except Failed as e :
logger . error ( e )
params [ " metadata_files " ] = [ ]
try :
if lib and " metadata_files " in lib :
logger . info ( " " )
logger . info ( " Reading in Metadata Files " )
if not lib [ " metadata_files " ] :
raise Failed ( " Config Error: metadata_files attribute is blank " )
files , had_scheduled = util . load_files ( lib [ " metadata_files " ] , " metadata_files " , schedule = ( current_time , self . run_hour , self . ignore_schedules ) , lib_vars = lib_vars )
if files :
params [ " metadata_files " ] = files
elif not had_scheduled :
raise Failed ( " Config Error: No Paths Found for metadata_files " )
except Failed as e :
logger . error ( e )
params [ " default_dir " ] = default_dir
params [ " skip_library " ] = False
if lib and " schedule " in lib and not self . requested_libraries and not self . ignore_schedules :
if not lib [ " schedule " ] :
logger . error ( f " Config Error: schedule attribute is blank " )
else :
logger . debug ( f " Value: { lib [ ' schedule ' ] } " )
try :
util . schedule_check ( " schedule " , lib [ " schedule " ] , current_time , self . run_hour )
except NotScheduled :
params [ " skip_library " ] = True
old_reset = None
old_schedule = None
params [ " overlay_files " ] = [ ]
params [ " remove_overlays " ] = False
params [ " reapply_overlays " ] = False
params [ " reset_overlays " ] = None
if lib and " overlay_files " in lib :
try :
logger . info ( " " )
logger . info ( " Reading in Overlay Files " )
if not lib [ " overlay_files " ] :
raise Failed ( " Config Error: overlay_files attribute is blank " )
files , _ = util . load_files ( lib [ " overlay_files " ] , " overlay_files " , lib_vars = lib_vars )
for file in util . get_list ( lib [ " overlay_files " ] , split = False ) :
if isinstance ( file , dict ) :
if ( " remove_overlays " in file and file [ " remove_overlays " ] is True ) \
or ( " remove_overlay " in file and file [ " remove_overlay " ] is True ) \
or ( " revert_overlays " in file and file [ " revert_overlays " ] is True ) :
logger . warning ( " Config Warning: remove_overlays under overlay_files is deprecated it now goes directly under the library attribute. " )
params [ " remove_overlays " ] = True
if ( " reapply_overlays " in file and file [ " reapply_overlays " ] is True ) \
or ( " reapply_overlay " in file and file [ " reapply_overlay " ] is True ) :
logger . warning ( " Config Warning: reapply_overlays under overlay_files is deprecated it now goes directly under the library attribute. " )
params [ " reapply_overlays " ] = True
if " reset_overlays " in file or " reset_overlay " in file :
attr = f " reset_overlay { ' s ' if ' reset_overlays ' in file else ' ' } "
logger . warning ( " Config Warning: reset_overlays under overlay_files is deprecated it now goes directly under the library attribute. " )
old_reset = file [ attr ]
if " schedule " in file and file [ " schedule " ] :
logger . warning ( " Config Warning: schedule under overlay_files is deprecated it now goes directly under the library attribute as schedule_overlays. " )
old_schedule = file [ " schedule " ]
params [ " overlay_files " ] = files
except Failed as e :
logger . error ( e )
if lib :
if ( " remove_overlays " in lib and lib [ " remove_overlays " ] is True ) \
or ( " remove_overlay " in lib and lib [ " remove_overlay " ] is True ) \
or ( " revert_overlays " in lib and lib [ " revert_overlays " ] is True ) :
params [ " remove_overlays " ] = True
if ( " reapply_overlays " in lib and lib [ " reapply_overlays " ] is True ) \
or ( " reapply_overlay " in lib and lib [ " reapply_overlay " ] is True ) :
params [ " reapply_overlays " ] = True
if " reset_overlays " in lib or " reset_overlay " in lib :
attr = f " reset_overlay { ' s ' if ' reset_overlays ' in lib else ' ' } "
old_reset = lib [ attr ]
if old_reset is not None :
reset_options = old_reset if isinstance ( old_reset , list ) else [ old_reset ]
final_list = [ ]
for reset_option in reset_options :
if reset_option and reset_option in reset_overlay_options :
final_list . append ( reset_option )
else :
final_text = f " Config Error: reset_overlays attribute { reset_option } invalid. Options: "
for option , description in reset_overlay_options . items ( ) :
final_text = f " { final_text } \n { option } ( { description } ) "
logger . error ( final_text )
if final_list :
params [ " reset_overlays " ] = final_list
else :
final_text = f " Config Error: No proper reset_overlays option found. { old_reset } . Options: "
for option , description in reset_overlay_options . items ( ) :
final_text = f " { final_text } \n { option } ( { description } ) "
logger . error ( final_text )
if " schedule_overlays " in lib or " schedule_overlay " in lib :
attr = f " schedule_overlay { ' s ' if ' schedule_overlays ' in lib else ' ' } "
old_schedule = lib [ attr ]
if old_schedule is not None :
logger . debug ( f " Value: { old_schedule } " )
err = None
try :
util . schedule_check ( " schedule_overlays " , old_schedule , current_time , self . run_hour )
except NotScheduledRange as e :
err = e
except NotScheduled as e :
if not self . ignore_schedules :
err = e
if err :
logger . info ( " " )
logger . info ( f " Overlay Schedule: { err } \n \n Overlays not scheduled to run " )
params [ " overlay_files " ] = [ ]
params [ " remove_overlays " ] = False
if lib and " overlay_files " in lib and not params [ " overlay_files " ] and params [ " remove_overlays " ] is False and params [ " reset_overlays " ] is False :
logger . error ( " Config Error: No Paths Found for overlay_files " )
params [ " image_files " ] = [ ]
try :
if lib and " image_files " in lib :
if not lib [ " image_files " ] :
raise Failed ( " Config Error: image_files attribute is blank " )
files , _ = util . load_files ( lib [ " image_files " ] , " image_files " )
if not files :
raise Failed ( " Config Error: No Paths Found for image_files " )
params [ " image_files " ] = files
except Failed as e :
logger . error ( e )
try :
logger . info ( " " )
logger . separator ( " Plex Configuration " , space = False , border = False )
params [ " plex " ] = {
" url " : check_for_attribute ( lib , " url " , parent = " plex " , var_type = " url " , default = self . general [ " plex " ] [ " url " ] , req_default = True , save = False ) ,
" token " : check_for_attribute ( lib , " token " , parent = " plex " , default = self . general [ " plex " ] [ " token " ] , req_default = True , save = False ) ,
" timeout " : check_for_attribute ( lib , " timeout " , parent = " plex " , var_type = " int " , default = self . general [ " plex " ] [ " timeout " ] , save = False ) ,
" verify_ssl " : check_for_attribute ( lib , " verify_ssl " , parent = " plex " , var_type = " bool " , default = self . general [ " plex " ] [ " verify_ssl " ] , default_is_none = True , save = False ) ,
" db_cache " : check_for_attribute ( lib , " db_cache " , parent = " plex " , var_type = " int " , default = self . general [ " plex " ] [ " db_cache " ] , default_is_none = True , save = False )
}
for attr in [ " clean_bundles " , " empty_trash " , " optimize " ] :
try :
params [ " plex " ] [ attr ] = check_for_attribute ( lib , attr , parent = " plex " , var_type = " bool " , save = False , throw = True )
except Failed as er :
test = lib [ " plex " ] [ attr ] if " plex " in lib and attr in lib [ " plex " ] and lib [ " plex " ] [ attr ] else self . general [ " plex " ] [ attr ]
params [ " plex " ] [ attr ] = False
if test is not True and test is not False :
try :
util . schedule_check ( attr , test , current_time , self . run_hour )
params [ " plex " ] [ attr ] = True
except NotScheduled :
logger . info ( f " Skipping Operation Not Scheduled for { test } " )
if params [ " plex " ] [ " url " ] . lower ( ) == " env " :
params [ " plex " ] [ " url " ] = self . env_plex_url
if params [ " plex " ] [ " token " ] . lower ( ) == " env " :
params [ " plex " ] [ " token " ] = self . env_plex_token
library = Plex ( self , params )
logger . info ( " " )
logger . info ( f " { display_name } Library Connection Successful " )
logger . info ( " " )
logger . separator ( " Scanning Files " , space = False , border = False )
library . scan_files ( self . operations_only , self . overlays_only , self . collection_only , self . metadata_only )
if not library . collection_files and not library . metadata_files and not library . overlay_files and not library . library_operation and not library . images_files and not self . playlist_files :
raise Failed ( " Config Error: No valid collection file, metadata file, overlay file, image file, playlist file, or library operations found " )
except Failed as e :
logger . stacktrace ( )
logger . error ( e )
logger . info ( " " )
logger . info ( f " { display_name } Library Connection Failed " )
continue
if self . general [ " radarr " ] [ " url " ] or ( lib and " radarr " in lib ) :
logger . info ( " " )
logger . separator ( " Radarr Configuration " , space = False , border = False )
logger . info ( " " )
logger . info ( f " Connecting to { display_name } library ' s Radarr... " )
logger . info ( " " )
try :
library . Radarr = Radarr ( self , library , {
" url " : check_for_attribute ( lib , " url " , parent = " radarr " , var_type = " url " , default = self . general [ " radarr " ] [ " url " ] , req_default = True , save = False ) ,
" token " : check_for_attribute ( lib , " token " , parent = " radarr " , default = self . general [ " radarr " ] [ " token " ] , req_default = True , save = False ) ,
" add_missing " : check_for_attribute ( lib , " add_missing " , parent = " radarr " , var_type = " bool " , default = self . general [ " radarr " ] [ " add_missing " ] , save = False ) ,
" add_existing " : check_for_attribute ( lib , " add_existing " , parent = " radarr " , var_type = " bool " , default = self . general [ " radarr " ] [ " add_existing " ] , save = False ) ,
" upgrade_existing " : check_for_attribute ( lib , " upgrade_existing " , parent = " radarr " , var_type = " bool " , default = self . general [ " radarr " ] [ " upgrade_existing " ] , save = False ) ,
" monitor_existing " : check_for_attribute ( lib , " monitor_existing " , parent = " radarr " , var_type = " bool " , default = self . general [ " radarr " ] [ " monitor_existing " ] , save = False ) ,
" ignore_cache " : check_for_attribute ( lib , " ignore_cache " , parent = " radarr " , var_type = " bool " , default = self . general [ " radarr " ] [ " ignore_cache " ] , save = False ) ,
" root_folder_path " : check_for_attribute ( lib , " root_folder_path " , parent = " radarr " , default = self . general [ " radarr " ] [ " root_folder_path " ] , req_default = True , save = False ) ,
" monitor " : check_for_attribute ( lib , " monitor " , parent = " radarr " , var_type = " bool " , default = self . general [ " radarr " ] [ " monitor " ] , save = False ) ,
" availability " : check_for_attribute ( lib , " availability " , parent = " radarr " , test_list = radarr . availability_descriptions , default = self . general [ " radarr " ] [ " availability " ] , save = False ) ,
" quality_profile " : check_for_attribute ( lib , " quality_profile " , parent = " radarr " , default = self . general [ " radarr " ] [ " quality_profile " ] , req_default = True , save = False ) ,
" tag " : check_for_attribute ( lib , " tag " , parent = " radarr " , var_type = " lower_list " , default = self . general [ " radarr " ] [ " tag " ] , default_is_none = True , save = False ) ,
" search " : check_for_attribute ( lib , " search " , parent = " radarr " , var_type = " bool " , default = self . general [ " radarr " ] [ " search " ] , save = False ) ,
" radarr_path " : check_for_attribute ( lib , " radarr_path " , parent = " radarr " , default = self . general [ " radarr " ] [ " radarr_path " ] , default_is_none = True , save = False ) ,
" plex_path " : check_for_attribute ( lib , " plex_path " , parent = " radarr " , default = self . general [ " radarr " ] [ " plex_path " ] , default_is_none = True , save = False )
} )
except Failed as e :
logger . stacktrace ( )
logger . error ( e )
logger . info ( " " )
logger . info ( f " { display_name } library ' s Radarr Connection { ' Failed ' if library . Radarr is None else ' Successful ' } " )
if self . general [ " sonarr " ] [ " url " ] or ( lib and " sonarr " in lib ) :
logger . info ( " " )
logger . separator ( " Sonarr Configuration " , space = False , border = False )
logger . info ( " " )
logger . info ( f " Connecting to { display_name } library ' s Sonarr... " )
logger . info ( " " )
try :
library . Sonarr = Sonarr ( self , library , {
" url " : check_for_attribute ( lib , " url " , parent = " sonarr " , var_type = " url " , default = self . general [ " sonarr " ] [ " url " ] , req_default = True , save = False ) ,
" token " : check_for_attribute ( lib , " token " , parent = " sonarr " , default = self . general [ " sonarr " ] [ " token " ] , req_default = True , save = False ) ,
" add_missing " : check_for_attribute ( lib , " add_missing " , parent = " sonarr " , var_type = " bool " , default = self . general [ " sonarr " ] [ " add_missing " ] , save = False ) ,
" add_existing " : check_for_attribute ( lib , " add_existing " , parent = " sonarr " , var_type = " bool " , default = self . general [ " sonarr " ] [ " add_existing " ] , save = False ) ,
" upgrade_existing " : check_for_attribute ( lib , " upgrade_existing " , parent = " sonarr " , var_type = " bool " , default = self . general [ " sonarr " ] [ " upgrade_existing " ] , save = False ) ,
" monitor_existing " : check_for_attribute ( lib , " monitor_existing " , parent = " sonarr " , var_type = " bool " , default = self . general [ " sonarr " ] [ " monitor_existing " ] , save = False ) ,
" ignore_cache " : check_for_attribute ( lib , " ignore_cache " , parent = " sonarr " , var_type = " bool " , default = self . general [ " sonarr " ] [ " ignore_cache " ] , save = False ) ,
" root_folder_path " : check_for_attribute ( lib , " root_folder_path " , parent = " sonarr " , default = self . general [ " sonarr " ] [ " root_folder_path " ] , req_default = True , save = False ) ,
" monitor " : check_for_attribute ( lib , " monitor " , parent = " sonarr " , test_list = sonarr . monitor_descriptions , default = self . general [ " sonarr " ] [ " monitor " ] , save = False ) ,
" quality_profile " : check_for_attribute ( lib , " quality_profile " , parent = " sonarr " , default = self . general [ " sonarr " ] [ " quality_profile " ] , req_default = True , save = False ) ,
" language_profile " : check_for_attribute ( lib , " language_profile " , parent = " sonarr " , default = self . general [ " sonarr " ] [ " language_profile " ] , save = False ) if self . general [ " sonarr " ] [ " language_profile " ] else check_for_attribute ( lib , " language_profile " , parent = " sonarr " , default_is_none = True , save = False ) ,
" series_type " : check_for_attribute ( lib , " series_type " , parent = " sonarr " , test_list = sonarr . series_type_descriptions , default = self . general [ " sonarr " ] [ " series_type " ] , save = False ) ,
" season_folder " : check_for_attribute ( lib , " season_folder " , parent = " sonarr " , var_type = " bool " , default = self . general [ " sonarr " ] [ " season_folder " ] , save = False ) ,
" tag " : check_for_attribute ( lib , " tag " , parent = " sonarr " , var_type = " lower_list " , default = self . general [ " sonarr " ] [ " tag " ] , default_is_none = True , save = False ) ,
" search " : check_for_attribute ( lib , " search " , parent = " sonarr " , var_type = " bool " , default = self . general [ " sonarr " ] [ " search " ] , save = False ) ,
" cutoff_search " : check_for_attribute ( lib , " cutoff_search " , parent = " sonarr " , var_type = " bool " , default = self . general [ " sonarr " ] [ " cutoff_search " ] , save = False ) ,
" sonarr_path " : check_for_attribute ( lib , " sonarr_path " , parent = " sonarr " , default = self . general [ " sonarr " ] [ " sonarr_path " ] , default_is_none = True , save = False ) ,
" plex_path " : check_for_attribute ( lib , " plex_path " , parent = " sonarr " , default = self . general [ " sonarr " ] [ " plex_path " ] , default_is_none = True , save = False )
} )
except Failed as e :
logger . stacktrace ( )
logger . error ( e )
logger . info ( " " )
logger . info ( f " { display_name } library ' s Sonarr Connection { ' Failed ' if library . Sonarr is None else ' Successful ' } " )
if self . general [ " tautulli " ] [ " url " ] or ( lib and " tautulli " in lib ) :
logger . info ( " " )
logger . separator ( " Tautulli Configuration " , space = False , border = False )
logger . info ( " " )
logger . info ( f " Connecting to { display_name } library ' s Tautulli... " )
logger . info ( " " )
try :
library . Tautulli = Tautulli ( self , library , {
" url " : check_for_attribute ( lib , " url " , parent = " tautulli " , var_type = " url " , default = self . general [ " tautulli " ] [ " url " ] , req_default = True , save = False ) ,
" apikey " : check_for_attribute ( lib , " apikey " , parent = " tautulli " , default = self . general [ " tautulli " ] [ " apikey " ] , req_default = True , save = False )
} )
except Failed as e :
logger . stacktrace ( )
logger . error ( e )
logger . info ( " " )
logger . info ( f " { display_name } library ' s Tautulli Connection { ' Failed ' if library . Tautulli is None else ' Successful ' } " )
library . Webhooks = Webhooks ( self , { } , library = library , notifiarr = self . NotifiarrFactory , gotify = self . GotifyFactory )
library . Overlays = Overlays ( self , library )
logger . info ( " " )
self . libraries . append ( library )
logger . separator ( )
self . library_map = { _l . original_mapping_name : _l for _l in self . libraries }
if len ( self . libraries ) > 0 :
logger . info ( f " { len ( self . libraries ) } Plex Library Connection { ' s ' if len ( self . libraries ) > 1 else ' ' } Successful " )
else :
raise Failed ( " Config Error: No libraries were found in config " )
logger . separator ( )
if logger . saved_errors :
self . notify ( logger . saved_errors )
except Exception as e :
logger . stacktrace ( )
self . notify ( logger . saved_errors + [ e ] )
logger . save_errors = False
logger . clear_errors ( )
raise
def notify ( self , text , server = None , library = None , collection = None , playlist = None , critical = True ) :
for error in util . get_list ( text , split = False ) :
try :
self . Webhooks . error_hooks ( error , server = server , library = library , collection = collection , playlist = playlist , critical = critical )
except Failed as e :
logger . stacktrace ( )
logger . error ( f " Webhooks Error: { e } " )
def notify_delete ( self , message , server = None , library = None ) :
try :
self . Webhooks . delete_hooks ( message , server = server , library = library )
except Failed as e :
logger . stacktrace ( )
logger . error ( f " Webhooks Error: { e } " )
def get_html ( self , url , headers = None , params = None ) :
return html . fromstring ( self . get ( url , headers = headers , params = params ) . content )
def get_json ( self , url , json = None , headers = None , params = None ) :
response = self . get ( url , json = json , headers = headers , params = params )
try :
return response . json ( )
except ValueError :
logger . error ( str ( response . content ) )
raise
@retry ( stop_max_attempt_number = 6 , wait_fixed = 10000 )
def get ( self , url , json = None , headers = None , params = None ) :
return self . session . get ( url , json = json , headers = headers , params = params )
def get_image_encoded ( self , url ) :
return base64 . b64encode ( self . get ( url ) . content ) . decode ( ' utf-8 ' )
def post_html ( self , url , data = None , json = None , headers = None ) :
return html . fromstring ( self . post ( url , data = data , json = json , headers = headers ) . content )
def post_json ( self , url , data = None , json = None , headers = None ) :
response = self . post ( url , data = data , json = json , headers = headers )
try :
return response . json ( )
except ValueError :
logger . error ( str ( response . content ) )
raise
@retry ( stop_max_attempt_number = 6 , wait_fixed = 10000 )
def post ( self , url , data = None , json = None , headers = None ) :
return self . session . post ( url , data = data , json = json , headers = headers )
def load_yaml ( self , url ) :
return YAML ( input_data = self . get ( url ) . content ) . data
@property
def mediastingers ( self ) :
if self . _mediastingers is None :
self . _mediastingers = self . load_yaml ( mediastingers_url )
return self . _mediastingers