added webhooks

pull/431/head
meisnate12 3 years ago
parent cc8f925afc
commit 9312c5ec35

@ -34,9 +34,12 @@ settings: # Can be individually specified
missing_only_released: false missing_only_released: false
collection_minimum: 1 collection_minimum: 1
delete_below_minimum: true delete_below_minimum: true
notifiarr_collection_creation: false error_webhooks:
notifiarr_collection_addition: false run_start_webhooks:
notifiarr_collection_removing: false run_end_webhooks:
collection_creation_webhooks:
collection_addition_webhooks:
collection_removing_webhooks:
tvdb_language: eng tvdb_language: eng
plex: # Can be individually specified per library as well plex: # Can be individually specified per library as well
url: http://192.168.1.12:32400 url: http://192.168.1.12:32400
@ -78,7 +81,6 @@ omdb:
apikey: ######## apikey: ########
notifiarr: notifiarr:
apikey: #################################### apikey: ####################################
error_notification: true
trakt: trakt:
client_id: ################################################################ client_id: ################################################################
client_secret: ################################################################ client_secret: ################################################################

@ -78,15 +78,16 @@ summary_details = [
poster_details = ["url_poster", "tmdb_poster", "tmdb_profile", "tvdb_poster", "file_poster"] poster_details = ["url_poster", "tmdb_poster", "tmdb_profile", "tvdb_poster", "file_poster"]
background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"] background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"]
boolean_details = [ boolean_details = [
"visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", "missing_only_released", "visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing",
"delete_below_minimum", "notifiarr_collection_creation", "notifiarr_collection_addition", "notifiarr_collection_removing" "missing_only_released", "delete_below_minimum"
] ]
string_details = ["sort_title", "content_rating", "name_mapping"] string_details = ["sort_title", "content_rating", "name_mapping"]
ignored_details = [ ignored_details = [
"smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test",
"tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name" "tmdb_person", "build_collection", "collection_order", "collection_level", "validate_builders", "collection_name"
] ]
details = ["collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details notification_details = ["collection_creation_webhooks", "collection_addition_webhooks", "collection_removing_webhooks"]
details = ["collection_mode", "collection_order", "collection_level", "collection_minimum", "label"] + boolean_details + string_details + notification_details
collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \ collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \
poster_details + background_details + summary_details + string_details poster_details + background_details + summary_details + string_details
item_details = ["item_label", "item_radarr_tag", "item_sonarr_tag", "item_overlay", "item_assets", "revert_overlay", "item_refresh"] + list(plex.item_advance_keys.keys()) item_details = ["item_label", "item_radarr_tag", "item_sonarr_tag", "item_overlay", "item_assets", "revert_overlay", "item_refresh"] + list(plex.item_advance_keys.keys())
@ -176,9 +177,9 @@ class CollectionBuilder:
"missing_only_released": self.library.missing_only_released, "missing_only_released": self.library.missing_only_released,
"create_asset_folders": self.library.create_asset_folders, "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, "collection_creation_webhooks": self.library.collection_creation_webhooks,
"notifiarr_collection_addition": self.library.notifiarr_collection_addition, "collection_addition_webhooks": self.library.collection_addition_webhooks,
"notifiarr_collection_removing": self.library.notifiarr_collection_removing, "collection_removing_webhooks": self.library.collection_removing_webhooks,
} }
self.item_details = {} self.item_details = {}
self.radarr_details = {} self.radarr_details = {}
@ -193,8 +194,8 @@ class CollectionBuilder:
self.filtered_keys = {} self.filtered_keys = {}
self.run_again_movies = [] self.run_again_movies = []
self.run_again_shows = [] self.run_again_shows = []
self.notifiarr_additions = [] self.notification_additions = []
self.notifiarr_removals = [] self.notification_removals = []
self.items = [] self.items = []
self.posters = {} self.posters = {}
self.backgrounds = {} self.backgrounds = {}
@ -551,7 +552,6 @@ 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.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.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.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_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 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") 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")
@ -709,6 +709,8 @@ class CollectionBuilder:
self.details["label.sync"] = util.get_list(method_data) self.details["label.sync"] = util.get_list(method_data)
else: else:
self.details[method_final] = util.get_list(method_data) self.details[method_final] = util.get_list(method_data)
elif method_name in notification_details:
self.details[method_name] = util.parse(method_name, method_data, datatype="list")
elif method_name in boolean_details: elif method_name in boolean_details:
default = self.details[method_name] if method_name in self.details else None default = self.details[method_name] if method_name in self.details else None
self.details[method_name] = util.parse(method_name, method_data, datatype="bool", default=default) self.details[method_name] = util.parse(method_name, method_data, datatype="bool", default=default)
@ -1485,6 +1487,7 @@ class CollectionBuilder:
def add_to_collection(self): def add_to_collection(self):
name, collection_items = self.library.get_collection_name_and_items(self.obj if self.obj else self.name, self.smart_label_collection) name, collection_items = self.library.get_collection_name_and_items(self.obj if self.obj else self.name, self.smart_label_collection)
total = len(self.rating_keys) total = len(self.rating_keys)
amount_added = 0
for i, item in enumerate(self.rating_keys, 1): for i, item in enumerate(self.rating_keys, 1):
try: try:
current = self.fetch_item(item) current = self.fetch_item(item)
@ -1497,41 +1500,44 @@ class CollectionBuilder:
self.plex_map[current.ratingKey] = None self.plex_map[current.ratingKey] = None
else: else:
self.library.alter_collection(current, name, smart_label_collection=self.smart_label_collection) self.library.alter_collection(current, name, smart_label_collection=self.smart_label_collection)
if self.details["notifiarr_collection_addition"]: amount_added += 1
if self.details["collection_addition_webhooks"]:
if self.library.is_movie and current.ratingKey in self.library.movie_rating_key_map: 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] 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: 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] add_id = self.library.show_rating_key_map[current.ratingKey]
else: else:
add_id = None add_id = None
self.notifiarr_additions.append({"title": current.title, "id": add_id}) self.notification_additions.append({"title": current.title, "id": add_id})
util.print_end() util.print_end()
logger.info("") logger.info("")
logger.info(f"{total} {self.collection_level.capitalize()}{'s' if total > 1 else ''} Processed") logger.info(f"{total} {self.collection_level.capitalize()}{'s' if total > 1 else ''} Processed")
return amount_added
def sync_collection(self): def sync_collection(self):
count_removed = 0 amount_removed = 0
for ratingKey, item in self.plex_map.items(): for ratingKey, item in self.plex_map.items():
if item is not None: if item is not None:
if count_removed == 0: if amount_removed == 0:
logger.info("") logger.info("")
util.separator(f"Removed from {self.name} Collection", space=False, border=False) util.separator(f"Removed from {self.name} Collection", space=False, border=False)
logger.info("") logger.info("")
self.library.reload(item) self.library.reload(item)
logger.info(f"{self.name} Collection | - | {self.item_title(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) self.library.alter_collection(item, self.name, smart_label_collection=self.smart_label_collection, add=False)
if self.details["notifiarr_collection_removing"]: if self.details["collection_removing_webhooks"]:
if self.library.is_movie and item.ratingKey in self.library.movie_rating_key_map: 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] 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: 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] remove_id = self.library.show_rating_key_map[item.ratingKey]
else: else:
remove_id = None remove_id = None
self.notifiarr_removals.append({"title": item.title, "id": remove_id}) self.notification_removals.append({"title": item.title, "id": remove_id})
count_removed += 1 amount_removed += 1
if count_removed > 0: if amount_removed > 0:
logger.info("") logger.info("")
logger.info(f"{count_removed} {self.collection_level.capitalize()}{'s' if count_removed == 1 else ''} Removed") logger.info(f"{amount_removed} {self.collection_level.capitalize()}{'s' if amount_removed == 1 else ''} Removed")
return amount_removed
def check_tmdb_filter(self, item_id, is_movie, item=None, check_released=False): def check_tmdb_filter(self, item_id, is_movie, item=None, check_released=False):
if self.tmdb_filters or check_released: if self.tmdb_filters or check_released:
@ -1653,6 +1659,8 @@ class CollectionBuilder:
return True return True
def run_missing(self): def run_missing(self):
added_to_radarr = 0
added_to_sonarr = 0
if len(self.missing_movies) > 0: if len(self.missing_movies) > 0:
missing_movies_with_names = [] missing_movies_with_names = []
for missing_id in self.missing_movies: for missing_id in self.missing_movies:
@ -1679,7 +1687,7 @@ class CollectionBuilder:
if self.library.Radarr: if self.library.Radarr:
if self.radarr_details["add"]: if self.radarr_details["add"]:
try: try:
self.library.Radarr.add_tmdb(missing_tmdb_ids, **self.radarr_details) added_to_radarr += self.library.Radarr.add_tmdb(missing_tmdb_ids, **self.radarr_details)
except Failed as e: except Failed as e:
logger.error(e) logger.error(e)
if "item_radarr_tag" in self.item_details: if "item_radarr_tag" in self.item_details:
@ -1714,7 +1722,7 @@ class CollectionBuilder:
if self.library.Sonarr: if self.library.Sonarr:
if self.sonarr_details["add"]: if self.sonarr_details["add"]:
try: try:
self.library.Sonarr.add_tvdb(missing_tvdb_ids, **self.sonarr_details) added_to_sonarr += self.library.Sonarr.add_tvdb(missing_tvdb_ids, **self.sonarr_details)
except Failed as e: except Failed as e:
logger.error(e) logger.error(e)
if "item_sonarr_tag" in self.item_details: if "item_sonarr_tag" in self.item_details:
@ -1727,6 +1735,7 @@ class CollectionBuilder:
if len(self.missing_parts) > 0 and self.library.is_show and self.details["save_missing"] is True: if len(self.missing_parts) > 0 and self.library.is_show and self.details["save_missing"] is True:
for missing in self.missing_parts: for missing in self.missing_parts:
logger.info(f"{self.name} Collection | X | {missing}") logger.info(f"{self.name} Collection | X | {missing}")
return added_to_radarr, added_to_sonarr
def item_title(self, item): def item_title(self, item):
if self.collection_level == "season": if self.collection_level == "season":
@ -2034,16 +2043,17 @@ class CollectionBuilder:
def send_notifications(self): def send_notifications(self):
if self.obj and ( if self.obj and (
(self.details["notifiarr_collection_creation"] and self.created) or (self.details["collection_creation_webhooks"] and self.created) or
(self.details["notifiarr_collection_addition"] and len(self.notifiarr_additions) > 0) or (self.details["collection_addition_webhooks"] and len(self.notification_additions) > 0) or
(self.details["notifiarr_collection_removing"] and len(self.notifiarr_removals) > 0) (self.details["collection_removing_webhooks"] and len(self.notification_removals) > 0)
): ):
self.obj.reload() self.obj.reload()
self.library.Notifiarr.plex_collection( self.library.Webhooks.collection_hooks(
self.details["collection_creation_webhooks"] + self.details["collection_addition_webhooks"] + self.details["collection_removing_webhooks"],
self.obj, self.obj,
created=self.created, created=self.created,
additions=self.notifiarr_additions, additions=self.notification_additions,
removals=self.notifiarr_removals removals=self.notification_removals
) )
def run_collections_again(self): def run_collections_again(self):
@ -2051,7 +2061,7 @@ class CollectionBuilder:
name, collection_items = self.library.get_collection_name_and_items(self.obj, self.smart_label_collection) name, collection_items = self.library.get_collection_name_and_items(self.obj, self.smart_label_collection)
self.created = False self.created = False
rating_keys = [] rating_keys = []
self.notifiarr_additions = [] self.notification_additions = []
for mm in self.run_again_movies: for mm in self.run_again_movies:
if mm in self.library.movie_map: if mm in self.library.movie_map:
rating_keys.extend(self.library.movie_map[mm]) rating_keys.extend(self.library.movie_map[mm])
@ -2077,7 +2087,7 @@ class CollectionBuilder:
add_id = self.library.show_rating_key_map[current.ratingKey] add_id = self.library.show_rating_key_map[current.ratingKey]
else: else:
add_id = None add_id = None
self.notifiarr_additions.append({"title": current.title, "id": add_id}) self.notification_additions.append({"title": current.title, "id": add_id})
self.send_notifications() self.send_notifications()
logger.info(f"{len(rating_keys)} {self.collection_level.capitalize()}{'s' if len(rating_keys) > 1 else ''} Processed") logger.info(f"{len(rating_keys)} {self.collection_level.capitalize()}{'s' if len(rating_keys) > 1 else ''} Processed")

@ -10,7 +10,7 @@ from modules.icheckmovies import ICheckMovies
from modules.imdb import IMDb from modules.imdb import IMDb
from modules.letterboxd import Letterboxd from modules.letterboxd import Letterboxd
from modules.mal import MyAnimeList from modules.mal import MyAnimeList
from modules.notifiarr import NotifiarrFactory from modules.notifiarr import Notifiarr
from modules.omdb import OMDb from modules.omdb import OMDb
from modules.plex import Plex from modules.plex import Plex
from modules.radarr import Radarr from modules.radarr import Radarr
@ -21,6 +21,7 @@ from modules.tmdb import TMDb
from modules.trakt import Trakt from modules.trakt import Trakt
from modules.tvdb import TVDb from modules.tvdb import TVDb
from modules.util import Failed from modules.util import Failed
from modules.webhooks import Webhooks
from retrying import retry from retrying import retry
from ruamel import yaml from ruamel import yaml
@ -135,9 +136,9 @@ class Config:
elif var_type == "path": elif var_type == "path":
if os.path.exists(os.path.abspath(data[attribute])): return data[attribute] if os.path.exists(os.path.abspath(data[attribute])): return data[attribute]
else: message = f"Path {os.path.abspath(data[attribute])} does not exist" else: message = f"Path {os.path.abspath(data[attribute])} does not exist"
elif var_type == "list": return util.get_list(data[attribute]) elif var_type == "list": return util.get_list(data[attribute], split=False)
elif var_type == "list_path": elif var_type == "list_path":
temp_list = [p for p in util.get_list(data[attribute], split=True) if os.path.exists(os.path.abspath(p))] temp_list = [p for p in util.get_list(data[attribute], split=False) if os.path.exists(os.path.abspath(p))]
if len(temp_list) > 0: return temp_list if len(temp_list) > 0: return temp_list
else: message = "No Paths exist" else: message = "No Paths exist"
elif var_type == "lower_list": return util.get_list(data[attribute], lower=True) elif var_type == "lower_list": return util.get_list(data[attribute], lower=True)
@ -190,9 +191,12 @@ class Config:
"create_asset_folders": check_for_attribute(self.data, "create_asset_folders", 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), "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), "error_webhooks": check_for_attribute(self.data, "error_webhooks", parent="settings", var_type="list", default_is_none=True),
"notifiarr_collection_addition": check_for_attribute(self.data, "notifiarr_collection_addition", parent="settings", var_type="bool", default=False), "run_start_webhooks": check_for_attribute(self.data, "run_start_webhooks", parent="settings", var_type="list", default_is_none=True),
"notifiarr_collection_removing": check_for_attribute(self.data, "notifiarr_collection_removing", parent="settings", var_type="bool", default=False), "run_end_webhooks": check_for_attribute(self.data, "run_end_webhooks", parent="settings", var_type="list", default_is_none=True),
"collection_creation_webhooks": check_for_attribute(self.data, "collection_creation_webhooks", parent="settings", var_type="list", default_is_none=True),
"collection_addition_webhooks": check_for_attribute(self.data, "collection_addition_webhooks", parent="settings", var_type="list", default_is_none=True),
"collection_removing_webhooks": check_for_attribute(self.data, "collection_removing_webhooks", parent="settings", var_type="list", default_is_none=True),
"tvdb_language": check_for_attribute(self.data, "tvdb_language", parent="settings", default="default") "tvdb_language": check_for_attribute(self.data, "tvdb_language", parent="settings", default="default")
} }
if self.general["cache"]: if self.general["cache"]:
@ -207,9 +211,8 @@ class Config:
if "notifiarr" in self.data: if "notifiarr" in self.data:
logger.info("Connecting to Notifiarr...") logger.info("Connecting to Notifiarr...")
try: try:
self.NotifiarrFactory = NotifiarrFactory(self, { self.NotifiarrFactory = Notifiarr(self, {
"apikey": check_for_attribute(self.data, "apikey", parent="notifiarr", throw=True), "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), "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) "test": check_for_attribute(self.data, "test", parent="notifiarr", var_type="bool", default=False, do_print=False, save=False)
}) })
@ -219,6 +222,9 @@ class Config:
else: else:
logger.warning("notifiarr attribute not found") logger.warning("notifiarr attribute not found")
self.Webhooks = Webhooks(self, self.general, notifiarr=self.NotifiarrFactory)
self.Webhooks.start_time_hooks(self.run_start_time)
self.errors = [] self.errors = []
util.separator() util.separator()
@ -389,9 +395,10 @@ 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["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["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["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["error_webhooks"] = check_for_attribute(lib, "error_webhooks", parent="settings", var_type="list", default=self.general["error_webhooks"], 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["collection_creation_webhooks"] = check_for_attribute(lib, "collection_creation_webhooks", parent="settings", var_type="list", default=self.general["collection_creation_webhooks"], 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["collection_addition_webhooks"] = check_for_attribute(lib, "collection_addition_webhooks", parent="settings", var_type="list", default=self.general["collection_addition_webhooks"], do_print=False, save=False)
params["collection_removing_webhooks"] = check_for_attribute(lib, "collection_removing_webhooks", parent="settings", var_type="list", default=self.general["collection_removing_webhooks"], do_print=False, save=False)
params["assets_for_all"] = check_for_attribute(lib, "assets_for_all", var_type="bool", default=assets_for_all, save=False, do_print=lib and "assets_for_all" in lib) params["assets_for_all"] = check_for_attribute(lib, "assets_for_all", var_type="bool", default=assets_for_all, save=False, do_print=lib and "assets_for_all" in lib)
params["delete_unmanaged_collections"] = check_for_attribute(lib, "delete_unmanaged_collections", var_type="bool", default=False, save=False, do_print=lib and "delete_unmanaged_collections" in lib) params["delete_unmanaged_collections"] = check_for_attribute(lib, "delete_unmanaged_collections", var_type="bool", default=False, save=False, do_print=lib and "delete_unmanaged_collections" in lib)
@ -545,7 +552,7 @@ class Config:
logger.info("") logger.info("")
logger.info(f"{display_name} library's Tautulli Connection {'Failed' if library.Tautulli is None else 'Successful'}") 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 library.Webhooks = Webhooks(self, {"error_webhooks": library.error_webhooks}, library=library, notifiarr=self.NotifiarrFactory)
logger.info("") logger.info("")
self.libraries.append(library) self.libraries.append(library)
@ -566,11 +573,8 @@ class Config:
raise raise
def notify(self, text, library=None, collection=None, critical=True): def notify(self, text, library=None, collection=None, critical=True):
if self.NotifiarrFactory: for error in util.get_list(text, split=False):
if not isinstance(text, list): self.Webhooks.error_hooks(error, library=library, collection=collection, critical=critical)
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): def get_html(self, url, headers=None, params=None):
return html.fromstring(self.get(url, headers=headers, params=params).content) return html.fromstring(self.get(url, headers=headers, params=params).content)

@ -13,6 +13,7 @@ class Library(ABC):
self.Radarr = None self.Radarr = None
self.Sonarr = None self.Sonarr = None
self.Tautulli = None self.Tautulli = None
self.Webhooks = None
self.Notifiarr = None self.Notifiarr = None
self.collections = [] self.collections = []
self.metadatas = [] self.metadatas = []
@ -57,9 +58,10 @@ class Library(ABC):
self.sonarr_add_all = params["sonarr_add_all"] self.sonarr_add_all = params["sonarr_add_all"]
self.collection_minimum = params["collection_minimum"] self.collection_minimum = params["collection_minimum"]
self.delete_below_minimum = params["delete_below_minimum"] self.delete_below_minimum = params["delete_below_minimum"]
self.notifiarr_collection_creation = params["notifiarr_collection_creation"] self.error_webhooks = params["error_webhooks"]
self.notifiarr_collection_addition = params["notifiarr_collection_addition"] self.collection_creation_webhooks = params["collection_creation_webhooks"]
self.notifiarr_collection_removing = params["notifiarr_collection_removing"] self.collection_addition_webhooks = params["collection_addition_webhooks"]
self.collection_removing_webhooks = params["collection_removing_webhooks"]
self.split_duplicates = params["split_duplicates"] # TODO: Here or just in Plex? 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.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? self.empty_trash = params["plex"]["empty_trash"] # TODO: Here or just in Plex?
@ -182,6 +184,9 @@ class Library(ABC):
self.config.Cache.update_image_map(item.ratingKey, f"{self.image_table_name}_backgrounds", item.art, background.compare) 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): def notify(self, text, collection=None, critical=True):
for error in util.get_list(text, split=False):
self.Webhooks.error_hooks(error, library=self, collection=collection, critical=critical)
self.config.notify(text, library=self, collection=collection, critical=critical) self.config.notify(text, library=self, collection=collection, critical=critical)
@abstractmethod @abstractmethod

@ -7,70 +7,25 @@ logger = logging.getLogger("Plex Meta Manager")
base_url = "https://notifiarr.com/api/v1/" base_url = "https://notifiarr.com/api/v1/"
dev_url = "https://dev.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): class Notifiarr:
url = f"{dev_url if self.develop else base_url}" + \ def __init__(self, config, params):
("notification/test" if self.test else f"{path}{self.apikey}") self.config = config
logger.debug(url.replace(self.apikey, "APIKEY")) self.apikey = params["apikey"]
response = self.config.get(url, json=json, params={"event": "pmm"} if self.test else params) self.develop = params["develop"]
self.test = params["test"]
url, _ = self.get_url("user/validate/")
response = self.config.get(url)
response_json = response.json() 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 ("result" in response_json and response_json["result"] == "error"): if response.status_code >= 400 or ("result" in response_json and response_json["result"] == "error"):
logger.debug(f"Response: {response_json}")
raise Failed(f"({response.status_code} [{response.reason}]) {response_json}") raise Failed(f"({response.status_code} [{response.reason}]) {response_json}")
return response_json if not params["test"] and not response_json["details"]["response"]:
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/")["details"]["response"]:
raise Failed("Notifiarr Error: Invalid apikey") raise Failed("Notifiarr Error: Invalid apikey")
def getNotifiarr(self, library): def get_url(self, path):
return Notifiarr(self.config, library, self.apikey, self.develop, self.test, self.error_notification) url = f"{dev_url if self.develop else base_url}{'notification/test' if self.test else f'{path}{self.apikey}'}"
logger.debug(url.replace(self.apikey, "APIKEY"))
class Notifiarr(NotifiarrBase): params = {"event": "pmm" if self.test else "collections"}
def __init__(self, config, library, apikey, develop, test, error_notification): return url, params
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"})

@ -61,6 +61,8 @@ class Radarr:
for tmdb_id in invalid: for tmdb_id in invalid:
logger.info(f"Invalid TMDb ID | {tmdb_id}") logger.info(f"Invalid TMDb ID | {tmdb_id}")
return len(added)
def edit_tags(self, tmdb_ids, tags, apply_tags): def edit_tags(self, tmdb_ids, tags, apply_tags):
logger.info("") logger.info("")
logger.info(f"{apply_tags_translation[apply_tags].capitalize()} Radarr Tags: {tags}") logger.info(f"{apply_tags_translation[apply_tags].capitalize()} Radarr Tags: {tags}")

@ -87,6 +87,8 @@ class Sonarr:
logger.info("") logger.info("")
logger.info(f"Invalid TVDb ID | {tvdb_id}") logger.info(f"Invalid TVDb ID | {tvdb_id}")
return len(added)
def edit_tags(self, tvdb_ids, tags, apply_tags): def edit_tags(self, tvdb_ids, tags, apply_tags):
logger.info("") logger.info("")
logger.info(f"{apply_tags_translation[apply_tags].capitalize()} Sonarr Tags: {tags}") logger.info(f"{apply_tags_translation[apply_tags].capitalize()} Sonarr Tags: {tags}")

@ -304,7 +304,7 @@ def parse(attribute, data, datatype=None, methods=None, parent=None, default=Non
value = data[methods[attribute]] if methods and attribute in methods else data value = data[methods[attribute]] if methods and attribute in methods else data
if datatype == "list": if datatype == "list":
if methods and attribute in methods and data[methods[attribute]]: if value:
return [v for v in value if v] if isinstance(value, list) else [str(value)] return [v for v in value if v] if isinstance(value, list) else [str(value)]
return [] return []
elif datatype == "dictlist": elif datatype == "dictlist":

@ -0,0 +1,83 @@
import logging
from modules.util import Failed
logger = logging.getLogger("Plex Meta Manager")
class Webhooks:
def __init__(self, config, system_webhooks, library=None, notifiarr=None):
self.config = config
self.error_webhooks = system_webhooks["error_webhooks"] if "error_webhooks" in system_webhooks else []
self.run_start_webhooks = system_webhooks["run_start_webhooks"] if "run_start_webhooks" in system_webhooks else []
self.run_end_webhooks = system_webhooks["run_end_webhooks"] if "run_end_webhooks" in system_webhooks else []
self.library = library
self.notifiarr = notifiarr
def _request(self, webhooks, json):
if self.config.trace_mode:
logger.debug("")
logger.debug(f"JSON: {json}")
for webhook in webhooks:
if self.config.trace_mode:
logger.debug(f"Webhook: {webhook}")
if webhook == "notifiarr":
url, params = self.notifiarr.get_url("notification/plex/")
response = self.config.get(url, json=json, params=params)
else:
response = self.config.post(webhook, json=json)
response_json = response.json()
if self.config.trace_mode:
logger.debug(f"Response: {response_json}")
if response.status_code >= 400 or ("result" in response_json and response_json["result"] == "error"):
raise Failed(f"({response.status_code} [{response.reason}]) {response_json}")
def start_time_hooks(self, start_time):
if self.run_start_webhooks:
self._request(self.run_start_webhooks, {"start_time": start_time})
def end_time_hooks(self, start_time, run_time, stats):
if self.run_end_webhooks:
self._request(self.run_end_webhooks, {
"start_time": start_time,
"run_time": run_time,
"collections_created": stats["created"],
"collections_modified": stats["modified"],
"collections_deleted": stats["deleted"],
"items_added": stats["added"],
"items_removed": stats["removed"],
"added_to_radarr": stats["radarr"],
"added_to_sonarr": stats["sonarr"],
})
def error_hooks(self, text, library=None, collection=None, critical=True):
if self.error_webhooks:
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(self.error_webhooks, json)
def collection_hooks(self, webhooks, collection, created=False, additions=None, removals=None):
if self.library:
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(webhooks, json)

@ -63,6 +63,7 @@ times = get_arg("PMM_TIME", args.times)
divider = get_arg("PMM_DIVIDER", args.divider) divider = get_arg("PMM_DIVIDER", args.divider)
screen_width = get_arg("PMM_WIDTH", args.width) screen_width = get_arg("PMM_WIDTH", args.width)
config_file = get_arg("PMM_CONFIG", args.config) config_file = get_arg("PMM_CONFIG", args.config)
stats = {}
util.separating_character = divider[0] util.separating_character = divider[0]
@ -133,6 +134,9 @@ def start(attrs):
if "time" not in attrs: if "time" not in attrs:
attrs["time"] = start_time.strftime("%H:%M") attrs["time"] = start_time.strftime("%H:%M")
util.separator(f"Starting {start_type}Run") util.separator(f"Starting {start_type}Run")
config = None
global stats
stats = {"created": 0, "modified": 0, "deleted": 0, "added": 0, "removed": 0, "radarr": 0, "sonarr": 0}
try: try:
config = Config(default_dir, attrs) config = Config(default_dir, attrs)
except Exception as e: except Exception as e:
@ -146,10 +150,14 @@ def start(attrs):
util.print_stacktrace() util.print_stacktrace()
util.print_multiline(e, critical=True) util.print_multiline(e, critical=True)
logger.info("") logger.info("")
util.separator(f"Finished {start_type}Run\nRun Time: {str(datetime.now() - start_time).split('.')[0]}") run_time = str(datetime.now() - start_time).split('.')[0]
if config:
config.Webhooks.end_time_hooks(start_time, run_time, stats)
util.separator(f"Finished {start_type}Run\nRun Time: {run_time}")
logger.removeHandler(file_handler) logger.removeHandler(file_handler)
def update_libraries(config): def update_libraries(config):
global stats
for library in config.libraries: for library in config.libraries:
try: try:
os.makedirs(os.path.join(default_dir, "logs", library.mapping_name, "collections"), exist_ok=True) os.makedirs(os.path.join(default_dir, "logs", library.mapping_name, "collections"), exist_ok=True)
@ -460,6 +468,7 @@ def mass_metadata(config, library, items=None):
logger.error(e) logger.error(e)
def run_collection(config, library, metadata, requested_collections): def run_collection(config, library, metadata, requested_collections):
global stats
logger.info("") logger.info("")
for mapping_name, collection_attrs in requested_collections.items(): for mapping_name, collection_attrs in requested_collections.items():
collection_start = datetime.now() collection_start = datetime.now()
@ -520,6 +529,8 @@ def run_collection(config, library, metadata, requested_collections):
logger.info("") logger.info("")
util.print_multiline(builder.smart_filter_details, info=True) util.print_multiline(builder.smart_filter_details, info=True)
items_added = 0
items_removed = 0
if not builder.smart_url: if not builder.smart_url:
logger.info("") logger.info("")
logger.info(f"Sync Mode: {'sync' if builder.sync else 'append'}") logger.info(f"Sync Mode: {'sync' if builder.sync else 'append'}")
@ -535,14 +546,18 @@ def run_collection(config, library, metadata, requested_collections):
logger.info("") logger.info("")
util.separator(f"Adding to {mapping_name} Collection", space=False, border=False) util.separator(f"Adding to {mapping_name} Collection", space=False, border=False)
logger.info("") logger.info("")
builder.add_to_collection() items_added = builder.add_to_collection()
stats["added"] += items_added
items_removed = 0
if builder.sync: if builder.sync:
builder.sync_collection() items_removed = builder.sync_collection()
stats["removed"] += items_removed
elif len(builder.rating_keys) < builder.minimum and builder.build_collection: elif len(builder.rating_keys) < builder.minimum and builder.build_collection:
logger.info("") logger.info("")
logger.info(f"Collection Minimum: {builder.minimum} not met for {mapping_name} Collection") logger.info(f"Collection Minimum: {builder.minimum} not met for {mapping_name} Collection")
if builder.details["delete_below_minimum"] and builder.obj: if builder.details["delete_below_minimum"] and builder.obj:
builder.delete_collection() builder.delete_collection()
stats["deleted"] += 1
logger.info("") logger.info("")
logger.info(f"Collection {builder.obj.title} deleted") logger.info(f"Collection {builder.obj.title} deleted")
@ -551,12 +566,18 @@ def run_collection(config, library, metadata, requested_collections):
logger.info("") logger.info("")
util.separator(f"Missing from Library", space=False, border=False) util.separator(f"Missing from Library", space=False, border=False)
logger.info("") logger.info("")
builder.run_missing() radarr_add, sonarr_add = builder.run_missing()
stats["radarr"] += radarr_add
stats["sonarr"] += sonarr_add
run_item_details = True run_item_details = True
if builder.build_collection: if builder.build_collection:
try: try:
builder.load_collection() builder.load_collection()
if builder.created:
stats["created"] += 1
elif items_added > 0 or items_removed > 0:
stats["modified"] += 1
except Failed: except Failed:
util.print_stacktrace() util.print_stacktrace()
run_item_details = False run_item_details = False

Loading…
Cancel
Save