push notifiarr update

pull/402/head
meisnate12 3 years ago
parent cef150bec0
commit 8516ac10db

@ -76,7 +76,10 @@ summary_details = [
]
poster_details = ["url_poster", "tmdb_poster", "tmdb_profile", "tvdb_poster", "file_poster"]
background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"]
boolean_details = ["visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", "missing_only_released", "delete_below_minimum"]
boolean_details = [
"visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", "missing_only_released",
"delete_below_minimum", "notifiarr_collection_creation", "notifiarr_collection_addition", "notifiarr_collection_removing"
]
string_details = ["sort_title", "content_rating", "name_mapping"]
ignored_details = [
"smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test",
@ -168,7 +171,10 @@ class CollectionBuilder:
"save_missing": self.library.save_missing,
"missing_only_released": self.library.missing_only_released,
"create_asset_folders": self.library.create_asset_folders,
"delete_below_minimum": self.library.delete_below_minimum
"delete_below_minimum": self.library.delete_below_minimum,
"notifiarr_collection_creation": self.library.notifiarr_collection_creation,
"notifiarr_collection_addition": self.library.notifiarr_collection_addition,
"notifiarr_collection_removing": self.library.notifiarr_collection_removing,
}
self.item_details = {}
self.radarr_details = {}
@ -183,6 +189,8 @@ class CollectionBuilder:
self.filtered_keys = {}
self.run_again_movies = []
self.run_again_shows = []
self.notifiarr_additions = []
self.notifiarr_removals = []
self.items = []
self.posters = {}
self.backgrounds = {}
@ -191,6 +199,8 @@ class CollectionBuilder:
self.minimum = self.library.collection_minimum
self.current_time = datetime.now()
self.current_year = self.current_time.year
self.exists = False
self.created = False
methods = {m.lower(): m for m in self.data}
@ -537,6 +547,7 @@ class CollectionBuilder:
elif not self.library.Sonarr and "sonarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Sonarr to be configured")
elif not self.library.Tautulli and "tautulli" in method_name: raise Failed(f"Collection Error: {method_final} requires Tautulli to be configured")
elif not self.config.MyAnimeList and "mal" in method_name: raise Failed(f"Collection Error: {method_final} requires MyAnimeList to be configured")
elif not self.library.Notifiarr and "notifiarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Notifiarr to be configured")
elif self.library.is_movie and method_name in show_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for show libraries")
elif self.library.is_show and method_name in movie_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for movie libraries")
elif self.library.is_show and method_name in plex.movie_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for movie libraries")
@ -617,6 +628,8 @@ class CollectionBuilder:
if self.sync and self.obj:
for item in self.library.get_collection_items(self.obj, self.smart_label_collection):
self.plex_map[item.ratingKey] = item
if self.obj:
self.exists = True
else:
self.obj = None
self.sync = False
@ -1122,7 +1135,7 @@ class CollectionBuilder:
rating_keys.append(input_id)
elif id_type == "tmdb" and not self.parts_collection:
if input_id in self.library.movie_map:
rating_keys.append(self.library.movie_map[input_id][0])
rating_keys.extend(self.library.movie_map[input_id])
elif input_id not in self.missing_movies:
self.missing_movies.append(input_id)
elif id_type in ["tvdb", "tmdb_show"] and not self.parts_collection:
@ -1133,12 +1146,12 @@ class CollectionBuilder:
logger.error(e)
continue
if input_id in self.library.show_map:
rating_keys.append(self.library.show_map[input_id][0])
rating_keys.extend(self.library.show_map[input_id])
elif input_id not in self.missing_shows:
self.missing_shows.append(input_id)
elif id_type == "imdb" and not self.parts_collection:
if input_id in self.library.imdb_map:
rating_keys.append(self.library.imdb_map[input_id][0])
rating_keys.extend(self.library.imdb_map[input_id])
else:
if self.do_missing:
try:
@ -1486,6 +1499,14 @@ class CollectionBuilder:
self.plex_map[current.ratingKey] = None
else:
self.library.alter_collection(current, name, smart_label_collection=self.smart_label_collection)
if self.details["notifiarr_collection_addition"]:
if self.library.is_movie and current.ratingKey in self.library.movie_rating_key_map:
add_id = self.library.movie_rating_key_map[current.ratingKey]
elif self.library.is_show and current.ratingKey in self.library.show_rating_key_map:
add_id = self.library.show_rating_key_map[current.ratingKey]
else:
add_id = None
self.notifiarr_additions.append({"title": current.title, "id": add_id})
util.print_end()
logger.info("")
logger.info(f"{total} {self.collection_level.capitalize()}{'s' if total > 1 else ''} Processed")
@ -1714,6 +1735,14 @@ class CollectionBuilder:
self.library.reload(item)
logger.info(f"{self.name} Collection | - | {self.item_title(item)}")
self.library.alter_collection(item, self.name, smart_label_collection=self.smart_label_collection, add=False)
if self.details["notifiarr_collection_removing"]:
if self.library.is_movie and item.ratingKey in self.library.movie_rating_key_map:
remove_id = self.library.movie_rating_key_map[item.ratingKey]
elif self.library.is_show and item.ratingKey in self.library.show_rating_key_map:
remove_id = self.library.show_rating_key_map[item.ratingKey]
else:
remove_id = None
self.notifiarr_removals.append({"title": item.title, "id": remove_id})
count_removed += 1
if count_removed > 0:
logger.info("")
@ -1835,6 +1864,8 @@ class CollectionBuilder:
except Failed:
raise Failed(f"Collection Error: Label: {self.name} was not added to any items in the Library")
self.obj = self.library.get_collection(self.name)
if not self.exists:
self.created = True
def update_details(self):
logger.info("")
@ -2002,10 +2033,26 @@ class CollectionBuilder:
self.library.move_item(self.obj, key, after=previous)
previous = key
def send_notifications(self):
if self.obj and (
(self.details["notifiarr_collection_creation"] and self.created) or
(self.details["notifiarr_collection_addition"] and len(self.notifiarr_additions) > 0) or
(self.details["notifiarr_collection_removing"] and len(self.notifiarr_removals) > 0)
):
self.obj.reload()
self.library.Notifiarr.plex_collection(
self.obj,
created=self.created,
additions=self.notifiarr_additions,
removals=self.notifiarr_removals
)
def run_collections_again(self):
self.obj = self.library.get_collection(self.name)
name, collection_items = self.library.get_collection_name_and_items(self.obj, self.smart_label_collection)
self.created = False
rating_keys = []
self.notifiarr_additions = []
for mm in self.run_again_movies:
if mm in self.library.movie_map:
rating_keys.extend(self.library.movie_map[mm])
@ -2025,6 +2072,14 @@ class CollectionBuilder:
else:
self.library.alter_collection(current, name, smart_label_collection=self.smart_label_collection)
logger.info(f"{name} Collection | + | {self.item_title(current)}")
if self.library.is_movie and current.ratingKey in self.library.movie_rating_key_map:
add_id = self.library.movie_rating_key_map[current.ratingKey]
elif self.library.is_show and current.ratingKey in self.library.show_rating_key_map:
add_id = self.library.show_rating_key_map[current.ratingKey]
else:
add_id = None
self.notifiarr_additions.append({"title": current.title, "id": add_id})
self.send_notifications()
logger.info(f"{len(rating_keys)} {self.collection_level.capitalize()}{'s' if len(rating_keys) > 1 else ''} Processed")
if len(self.run_again_movies) > 0:

@ -1,4 +1,4 @@
import logging, os, requests
import base64, logging, os, requests
from datetime import datetime
from lxml import html
from modules import util, radarr, sonarr
@ -10,6 +10,7 @@ from modules.icheckmovies import ICheckMovies
from modules.imdb import IMDb
from modules.letterboxd import Letterboxd
from modules.mal import MyAnimeList
from modules.notifiarr import NotifiarrFactory
from modules.omdb import OMDb
from modules.plex import Plex
from modules.radarr import Radarr
@ -29,21 +30,22 @@ sync_modes = {"append": "Only Add Items to the Collection", "sync": "Add & Remov
mass_update_options = {"tmdb": "Use TMDb Metadata", "omdb": "Use IMDb Metadata through OMDb"}
class Config:
def __init__(self, default_dir, config_path=None, is_test=False, time_scheduled=None, requested_collections=None, requested_libraries=None, resume_from=None):
def __init__(self, default_dir, attrs):
logger.info("Locating config...")
if config_path and os.path.exists(config_path): self.config_path = os.path.abspath(config_path)
elif config_path and not os.path.exists(config_path): raise Failed(f"Config Error: config not found at {os.path.abspath(config_path)}")
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")
self.default_dir = default_dir
self.test_mode = is_test
self.run_start_time = time_scheduled
self.run_hour = datetime.strptime(time_scheduled, "%H:%M").hour
self.requested_collections = util.get_list(requested_collections)
self.requested_libraries = util.get_list(requested_libraries)
self.resume_from = resume_from
self.test_mode = attrs["test"]
self.run_start_time = attrs["time"]
self.run_hour = datetime.strptime(attrs["time"], "%H:%M").hour
self.requested_collections = util.get_list(attrs["collections"])
self.requested_libraries = util.get_list(attrs["libraries"])
self.resume_from = attrs["resume"]
yaml.YAML().allow_duplicate_keys = True
try:
@ -87,6 +89,7 @@ class Config:
if "radarr" in new_config: new_config["radarr"] = new_config.pop("radarr")
if "sonarr" in new_config: new_config["sonarr"] = new_config.pop("sonarr")
if "omdb" in new_config: new_config["omdb"] = new_config.pop("omdb")
if "notifiarr" in new_config: new_config["notifiarr"] = new_config.pop("notifiarr")
if "trakt" in new_config: new_config["trakt"] = new_config.pop("trakt")
if "mal" in new_config: new_config["mal"] = new_config.pop("mal")
if "anidb" in new_config: new_config["anidb"] = new_config.pop("anidb")
@ -186,7 +189,10 @@ class Config:
"missing_only_released": check_for_attribute(self.data, "missing_only_released", parent="settings", var_type="bool", default=False),
"create_asset_folders": check_for_attribute(self.data, "create_asset_folders", parent="settings", var_type="bool", default=False),
"collection_minimum": check_for_attribute(self.data, "collection_minimum", parent="settings", var_type="int", default=1),
"delete_below_minimum": check_for_attribute(self.data, "delete_below_minimum", parent="settings", var_type="bool", default=False)
"delete_below_minimum": check_for_attribute(self.data, "delete_below_minimum", parent="settings", var_type="bool", default=False),
"notifiarr_collection_creation": check_for_attribute(self.data, "notifiarr_collection_creation", parent="settings", var_type="bool", default=False),
"notifiarr_collection_addition": check_for_attribute(self.data, "notifiarr_collection_addition", parent="settings", var_type="bool", default=False),
"notifiarr_collection_removing": check_for_attribute(self.data, "notifiarr_collection_removing", parent="settings", var_type="bool", default=False)
}
if self.general["cache"]:
util.separator()
@ -196,6 +202,27 @@ class Config:
util.separator()
self.NotifiarrFactory = None
if "notifiarr" in self.data:
logger.info("Connecting to Notifiarr...")
try:
self.NotifiarrFactory = NotifiarrFactory(self, {
"apikey": check_for_attribute(self.data, "apikey", parent="notifiarr", throw=True),
"error_notification": check_for_attribute(self.data, "error_notification", parent="notifiarr", var_type="bool", default=True),
"develop": check_for_attribute(self.data, "develop", parent="notifiarr", var_type="bool", default=False, do_print=False, save=False),
"test": check_for_attribute(self.data, "test", parent="notifiarr", var_type="bool", default=False, do_print=False, save=False)
})
except Failed as e:
logger.error(e)
logger.info(f"Notifiarr Connection {'Failed' if self.NotifiarrFactory is None else 'Successful'}")
else:
logger.warning("notifiarr attribute not found")
self.errors = []
util.separator()
try:
self.TMDb = None
if "tmdb" in self.data:
logger.info("Connecting to TMDb...")
@ -215,6 +242,7 @@ class Config:
try:
self.OMDb = OMDb(self, {"apikey": check_for_attribute(self.data, "apikey", parent="omdb", throw=True)})
except Failed as e:
self.errors.append(e)
logger.error(e)
logger.info(f"OMDb Connection {'Failed' if self.OMDb is None else 'Successful'}")
else:
@ -233,6 +261,7 @@ class Config:
"authorization": self.data["trakt"]["authorization"] if "authorization" in self.data["trakt"] else None
})
except Failed as e:
self.errors.append(e)
logger.error(e)
logger.info(f"Trakt Connection {'Failed' if self.Trakt is None else 'Successful'}")
else:
@ -251,6 +280,7 @@ class Config:
"authorization": self.data["mal"]["authorization"] if "authorization" in self.data["mal"] else None
})
except Failed as e:
self.errors.append(e)
logger.error(e)
logger.info(f"My Anime List Connection {'Failed' if self.MyAnimeList is None else 'Successful'}")
else:
@ -268,6 +298,7 @@ class Config:
"password": check_for_attribute(self.data, "password", parent="anidb", throw=True)
})
except Failed as e:
self.errors.append(e)
logger.error(e)
logger.info(f"My Anime List Connection {'Failed Continuing as Guest ' if self.MyAnimeList is None else 'Successful'}")
if self.AniDB is None:
@ -357,26 +388,37 @@ class Config:
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["collection_minimum"] = check_for_attribute(lib, "collection_minimum", parent="settings", var_type="int", default=self.general["collection_minimum"], 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["notifiarr_collection_creation"] = check_for_attribute(lib, "notifiarr_collection_creation", parent="settings", var_type="bool", default=self.general["notifiarr_collection_creation"], do_print=False, save=False)
params["notifiarr_collection_addition"] = check_for_attribute(lib, "notifiarr_collection_addition", parent="settings", var_type="bool", default=self.general["notifiarr_collection_addition"], do_print=False, save=False)
params["notifiarr_collection_removing"] = check_for_attribute(lib, "notifiarr_collection_removing", parent="settings", var_type="bool", default=self.general["notifiarr_collection_removing"], do_print=False, save=False)
params["mass_genre_update"] = check_for_attribute(lib, "mass_genre_update", test_list=mass_update_options, default_is_none=True, save=False, do_print=lib and "mass_genre_update" in lib)
if self.OMDb is None and params["mass_genre_update"] == "omdb":
params["mass_genre_update"] = None
logger.error("Config Error: mass_genre_update cannot be omdb without a successful OMDb Connection")
e = "Config Error: mass_genre_update cannot be omdb without a successful OMDb Connection"
self.errors.append(e)
logger.error(e)
params["mass_audience_rating_update"] = check_for_attribute(lib, "mass_audience_rating_update", test_list=mass_update_options, default_is_none=True, save=False, do_print=lib and "mass_audience_rating_update" in lib)
if self.OMDb is None and params["mass_audience_rating_update"] == "omdb":
params["mass_audience_rating_update"] = None
logger.error("Config Error: mass_audience_rating_update cannot be omdb without a successful OMDb Connection")
e = "Config Error: mass_audience_rating_update cannot be omdb without a successful OMDb Connection"
self.errors.append(e)
logger.error(e)
params["mass_critic_rating_update"] = check_for_attribute(lib, "mass_critic_rating_update", test_list=mass_update_options, default_is_none=True, save=False, do_print=lib and "mass_audience_rating_update" in lib)
if self.OMDb is None and params["mass_critic_rating_update"] == "omdb":
params["mass_critic_rating_update"] = None
logger.error("Config Error: mass_critic_rating_update cannot be omdb without a successful OMDb Connection")
e = "Config Error: mass_critic_rating_update cannot be omdb without a successful OMDb Connection"
self.errors.append(e)
logger.error(e)
params["mass_trakt_rating_update"] = check_for_attribute(lib, "mass_trakt_rating_update", var_type="bool", default=False, save=False, do_print=lib and "mass_trakt_rating_update" in lib)
if self.Trakt is None and params["mass_trakt_rating_update"]:
params["mass_trakt_rating_update"] = None
logger.error("Config Error: mass_trakt_rating_update cannot run without a successful Trakt Connection")
e = "Config Error: mass_trakt_rating_update cannot run without a successful Trakt Connection"
self.errors.append(e)
logger.error(e)
params["split_duplicates"] = check_for_attribute(lib, "split_duplicates", var_type="bool", default=False, save=False, do_print=lib and "split_duplicates" in lib)
params["radarr_add_all"] = check_for_attribute(lib, "radarr_add_all", var_type="bool", default=False, save=False, do_print=lib and "radarr_add_all" in lib)
@ -393,7 +435,9 @@ class Config:
def check_dict(attr, name):
if attr in path:
if path[attr] is None:
logger.error(f"Config Error: metadata_path {attr} is blank")
e = f"Config Error: metadata_path {attr} is blank"
self.errors.append(e)
logger.error(e)
else:
params["metadata_path"].append((name, path[attr]))
check_dict("url", "URL")
@ -417,6 +461,7 @@ class Config:
logger.info("")
logger.info(f"{display_name} Library Connection Successful")
except Failed as e:
self.errors.append(e)
util.print_stacktrace()
util.print_multiline(e, error=True)
logger.info(f"{display_name} Library Connection Failed")
@ -442,6 +487,7 @@ class Config:
"search": check_for_attribute(lib, "search", parent="radarr", var_type="bool", default=self.general["radarr"]["search"], save=False)
})
except Failed as e:
self.errors.append(e)
util.print_stacktrace()
util.print_multiline(e, error=True)
logger.info("")
@ -470,6 +516,7 @@ class Config:
"cutoff_search": check_for_attribute(lib, "cutoff_search", parent="sonarr", var_type="bool", default=self.general["sonarr"]["cutoff_search"], save=False)
})
except Failed as e:
self.errors.append(e)
util.print_stacktrace()
util.print_multiline(e, error=True)
logger.info("")
@ -487,11 +534,14 @@ class Config:
"apikey": check_for_attribute(lib, "apikey", parent="tautulli", default=self.general["tautulli"]["apikey"], req_default=True, save=False)
})
except Failed as e:
self.errors.append(e)
util.print_stacktrace()
util.print_multiline(e, error=True)
logger.info("")
logger.info(f"{display_name} library's Tautulli Connection {'Failed' if library.Tautulli is None else 'Successful'}")
library.Notifiarr = self.NotifiarrFactory.getNotifiarr(library) if self.NotifiarrFactory else None
logger.info("")
self.libraries.append(library)
@ -504,15 +554,31 @@ class Config:
util.separator()
if self.errors:
self.notify(self.errors)
except Exception as e:
self.notify(e)
raise
def notify(self, text, library=None, collection=None, critical=True):
if self.NotifiarrFactory:
if not isinstance(text, list):
text = [text]
for t in text:
self.NotifiarrFactory.error(t, library=library, collection=collection, critical=critical)
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, headers=None):
return self.get(url, headers=headers).json()
def get_json(self, url, json=None, headers=None, params=None):
return self.get(url, json=json, headers=headers, params=params).json()
@retry(stop_max_attempt_number=6, wait_fixed=10000)
def get(self, url, headers=None, params=None):
return self.session.get(url, headers=headers, params=params)
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)

@ -57,7 +57,7 @@ class IMDb:
pass
if total > 0:
return total, item_counts[page_type]
raise ValueError(f"IMDb Error: Failed to parse URL: {imdb_url}")
raise Failed(f"IMDb Error: Failed to parse URL: {imdb_url}")
def _ids_from_url(self, imdb_url, language, limit):
total, item_count = self._total(imdb_url, language)
@ -93,7 +93,7 @@ class IMDb:
if len(imdb_ids) > 0:
logger.debug(f"{len(imdb_ids)} IMDb IDs Found: {imdb_ids}")
return imdb_ids
raise ValueError(f"IMDb Error: No IMDb IDs Found at {imdb_url}")
raise Failed(f"IMDb Error: No IMDb IDs Found at {imdb_url}")
def get_imdb_ids(self, method, data, language):
if method == "imdb_id":

@ -13,6 +13,7 @@ class Library(ABC):
self.Radarr = None
self.Sonarr = None
self.Tautulli = None
self.Notifiarr = None
self.collections = []
self.metadatas = []
self.metadata_files = []
@ -54,6 +55,9 @@ class Library(ABC):
self.sonarr_add_all = params["sonarr_add_all"]
self.collection_minimum = params["collection_minimum"]
self.delete_below_minimum = params["delete_below_minimum"]
self.notifiarr_collection_creation = params["notifiarr_collection_creation"]
self.notifiarr_collection_addition = params["notifiarr_collection_addition"]
self.notifiarr_collection_removing = params["notifiarr_collection_removing"]
self.split_duplicates = params["split_duplicates"] # TODO: Here or just in Plex?
self.clean_bundles = params["plex"]["clean_bundles"] # TODO: Here or just in Plex?
self.empty_trash = params["plex"]["empty_trash"] # TODO: Here or just in Plex?
@ -175,6 +179,9 @@ class Library(ABC):
if background_uploaded:
self.config.Cache.update_image_map(item.ratingKey, f"{self.image_table_name}_backgrounds", item.art, background.compare)
def notify(self, text, collection=None, critical=True):
self.config.notify(text, library=self, collection=collection, critical=critical)
@abstractmethod
def _upload_image(self, item, image):
pass

@ -249,6 +249,7 @@ class Metadata:
add_edit("originally_available", item, meta, methods, key="originallyAvailableAt", value=originally_available, var_type="date")
add_edit("critic_rating", item, meta, methods, value=rating, key="rating", var_type="float")
add_edit("audience_rating", item, meta, methods, key="audienceRating", var_type="float")
add_edit("user_rating", item, meta, methods, key="userRating", var_type="float")
add_edit("content_rating", item, meta, methods, key="contentRating")
add_edit("original_title", item, meta, methods, key="originalTitle", value=original_title)
add_edit("studio", item, meta, methods, value=studio)

@ -0,0 +1,76 @@
import logging
from modules.util import Failed
logger = logging.getLogger("Plex Meta Manager")
base_url = "https://notifiarr.com/api/v1/"
dev_url = "https://dev.notifiarr.com/api/v1/"
class NotifiarrBase:
def __init__(self, config, apikey, develop, test, error_notification):
self.config = config
self.apikey = apikey
self.develop = develop
self.test = test
self.error_notification = error_notification
def _request(self, path, json=None, params=None):
url = f"{dev_url if self.develop else base_url}" + \
("notification/test" if self.test else f"{path}{self.apikey}")
logger.debug(url)
response = self.config.get(url, json=json, params={"event": "pmm"} if self.test else params)
response_json = response.json()
if self.develop or self.test:
logger.debug(json)
logger.debug("")
logger.debug(response_json)
if response.status_code >= 400 or ("response" in response_json and response_json["response"] == "error"):
raise Failed(f"({response.status_code} [{response.reason}]) {response_json}")
return response_json
def error(self, text, library=None, collection=None, critical=True):
if self.error_notification:
json = {"error": str(text), "critical": critical}
if library:
json["server_name"] = library.PlexServer.friendlyName
json["library_name"] = library.name
if collection:
json["collection"] = str(collection)
self._request("notification/plex/", json=json, params={"event": "collections"})
class NotifiarrFactory(NotifiarrBase):
def __init__(self, config, params):
super().__init__(config, params["apikey"], params["develop"], params["test"], params["error_notification"])
if not params["test"] and not self._request("user/validate/")["message"]["response"]:
raise Failed("Notifiarr Error: Invalid apikey")
def getNotifiarr(self, library):
return Notifiarr(self.config, library, self.apikey, self.develop, self.test, self.error_notification)
class Notifiarr(NotifiarrBase):
def __init__(self, config, library, apikey, develop, test, error_notification):
super().__init__(config, apikey, develop, test, error_notification)
self.library = library
def plex_collection(self, collection, created=False, additions=None, removals=None):
thumb = None
if collection.thumb and next((f for f in collection.fields if f.name == "thumb"), None):
thumb = self.config.get_image_encoded(f"{self.library.url}{collection.thumb}?X-Plex-Token={self.library.token}")
art = None
if collection.art and next((f for f in collection.fields if f.name == "art"), None):
art = self.config.get_image_encoded(f"{self.library.url}{collection.art}?X-Plex-Token={self.library.token}")
json = {
"server_name": self.library.PlexServer.friendlyName,
"library_name": self.library.name,
"type": "movie" if self.library.is_movie else "show",
"collection": collection.title,
"created": created,
"poster": thumb,
"background": art
}
if additions:
json["additions"] = additions
if removals:
json["removals"] = removals
self._request("notification/plex/", json=json, params={"event": "collections"})

@ -56,9 +56,12 @@ class TMDb:
self.TMDb = tmdbv3api.TMDb(session=self.config.session)
self.TMDb.api_key = params["apikey"]
self.TMDb.language = params["language"]
try:
response = tmdbv3api.Configuration().info()
if hasattr(response, "status_message"):
raise Failed(f"TMDb Error: {response.status_message}")
except TMDbException as e:
raise Failed(f"TMDb Error: {e}")
self.apikey = params["apikey"]
self.language = params["language"]
self.Movie = tmdbv3api.Movie()

@ -29,6 +29,9 @@ class ImageData:
self.compare = location if is_url else os.stat(location).st_size
self.message = f"{prefix}{'poster' if is_poster else 'background'} to [{'URL' if is_url else 'File'}] {location}"
def __str__(self):
return str(self.__dict__)
def retry_if_not_failed(exception):
return not isinstance(exception, Failed)

@ -98,7 +98,7 @@ logger.addHandler(cmd_handler)
sys.excepthook = util.my_except_hook
def start(config_path, is_test=False, time_scheduled=None, requested_collections=None, requested_libraries=None, resume_from=None):
def start(attrs):
file_logger = os.path.join(default_dir, "logs", "meta.log")
should_roll_over = os.path.isfile(file_logger)
file_handler = RotatingFileHandler(file_logger, delay=True, mode="w", backupCount=10, encoding="utf-8")
@ -115,22 +115,26 @@ def start(config_path, is_test=False, time_scheduled=None, requested_collections
logger.info(util.centered("| __/| | __/> < | | | | __/ || (_| | | | | | (_| | | | | (_| | (_| | __/ | "))
logger.info(util.centered("|_| |_|\\___/_/\\_\\ |_| |_|\\___|\\__\\__,_| |_| |_|\\__,_|_| |_|\\__,_|\\__, |\\___|_| "))
logger.info(util.centered(" |___/ "))
logger.info(util.centered(" Version: 1.12.2-develop0930 "))
if time_scheduled: start_type = f"{time_scheduled} "
elif is_test: start_type = "Test "
elif requested_collections: start_type = "Collections "
elif requested_libraries: start_type = "Libraries "
logger.info(util.centered(" Version: 1.12.2-develop1004 "))
if "time" in attrs: start_type = f"{attrs['time']} "
elif "test" in attrs: start_type = "Test "
elif "collections" in attrs: start_type = "Collections "
elif "libraries" in attrs: start_type = "Libraries "
else: start_type = ""
start_time = datetime.now()
if time_scheduled is None:
time_scheduled = start_time.strftime("%H:%M")
if "time" not in attrs:
attrs["time"] = start_time.strftime("%H:%M")
util.separator(f"Starting {start_type}Run")
try:
config = Config(default_dir, config_path=config_path, is_test=is_test,
time_scheduled=time_scheduled, requested_collections=requested_collections,
requested_libraries=requested_libraries, resume_from=resume_from)
config = Config(default_dir, attrs)
except Exception as e:
util.print_stacktrace()
util.print_multiline(e, critical=True)
else:
try:
update_libraries(config)
except Exception as e:
config.notify(e)
util.print_stacktrace()
util.print_multiline(e, critical=True)
logger.info("")
@ -139,6 +143,7 @@ def start(config_path, is_test=False, time_scheduled=None, requested_collections
def update_libraries(config):
for library in config.libraries:
try:
os.makedirs(os.path.join(default_dir, "logs", library.mapping_name, "collections"), exist_ok=True)
col_file_logger = os.path.join(default_dir, "logs", library.mapping_name, "library.log")
should_roll_over = os.path.isfile(col_file_logger)
@ -166,6 +171,7 @@ def update_libraries(config):
try:
metadata.update_metadata()
except Failed as e:
library.notify(e)
logger.error(e)
collections_to_run = metadata.get_collections(config.requested_collections)
if config.resume_from and config.resume_from not in collections_to_run:
@ -216,6 +222,10 @@ def update_libraries(config):
library.update_item_from_assets(item, create=library.create_asset_folders)
logger.removeHandler(library_handler)
except Exception as e:
library.notify(e)
util.print_stacktrace()
util.print_multiline(e, critical=True)
has_run_again = False
for library in config.libraries:
@ -234,6 +244,7 @@ def update_libraries(config):
util.print_end()
for library in config.libraries:
if library.run_again:
try:
col_file_logger = os.path.join(default_dir, "logs", library.mapping_name, f"library.log")
library_handler = RotatingFileHandler(col_file_logger, mode="w", backupCount=3, encoding="utf-8")
util.apply_formatter(library_handler)
@ -251,9 +262,14 @@ def update_libraries(config):
try:
builder.run_collections_again()
except Failed as e:
library.notify(e, collection=builder.name, critical=False)
util.print_stacktrace()
util.print_multiline(e, error=True)
logger.removeHandler(library_handler)
except Exception as e:
library.notify(e)
util.print_stacktrace()
util.print_multiline(e, critical=True)
used_url = []
for library in config.libraries:
@ -457,7 +473,7 @@ def run_collection(config, library, metadata, requested_collections):
collection_log_name, output_str = util.validate_filename(mapping_name)
collection_log_folder = os.path.join(default_dir, "logs", library.mapping_name, "collections", collection_log_name)
os.makedirs(collection_log_folder, exist_ok=True)
col_file_logger = os.path.join(collection_log_folder, f"collection.log")
col_file_logger = os.path.join(collection_log_folder, "collection.log")
should_roll_over = os.path.isfile(col_file_logger)
collection_handler = RotatingFileHandler(col_file_logger, delay=True, mode="w", backupCount=3, encoding="utf-8")
util.apply_formatter(collection_handler)
@ -533,6 +549,8 @@ def run_collection(config, library, metadata, requested_collections):
library.run_sort.append(builder)
# builder.sort_collection()
builder.send_notifications()
if builder.item_details and run_item_details:
try:
builder.load_collection_items()
@ -546,9 +564,11 @@ def run_collection(config, library, metadata, requested_collections):
library.run_again.append(builder)
except Failed as e:
library.notify(e, collection=mapping_name)
util.print_stacktrace()
util.print_multiline(e, error=True)
except Exception as e:
library.notify(f"Unknown Error: {e}", collection=mapping_name)
util.print_stacktrace()
logger.error(f"Unknown Error: {e}")
logger.info("")
@ -557,7 +577,13 @@ def run_collection(config, library, metadata, requested_collections):
try:
if run or test or collections or libraries or resume:
start(config_file, is_test=test, requested_collections=collections, requested_libraries=libraries, resume_from=resume)
start({
"config_file": config_file,
"test": test,
"collections": collections,
"libraries": libraries,
"resume": resume
})
else:
times_to_run = util.get_list(times)
valid_times = []
@ -570,7 +596,7 @@ try:
else:
raise Failed(f"Argument Error: blank time argument")
for time_to_run in valid_times:
schedule.every().day.at(time_to_run).do(start, config_file, time_scheduled=time_to_run)
schedule.every().day.at(time_to_run).do(start, {"config_file": config_file, "time": time_to_run})
while True:
schedule.run_pending()
if not no_countdown:

Loading…
Cancel
Save