added notifications class and more work on notifications.

pull/5/head
l3uddz 7 years ago
parent 68c960e579
commit 934c2f2374

@ -64,13 +64,15 @@ base_config = {
} }
}, },
'notifications': { 'notifications': {
'plex slack': { 'verbose': False,
'type': 'slack', 'my slack': {
'webhook': 'http://' 'service': 'slack',
'webhook_url': ''
}, },
'my pushover': { 'my pushover': {
'client_id': '....', 'service': 'pushover',
'client_secret': '....' 'app_token': '',
'user_token': ''
} }
} }
} }

@ -17,6 +17,7 @@ class Logger:
self.root_logger.setLevel(log_level) self.root_logger.setLevel(log_level)
# disable bloat loggers # disable bloat loggers
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.ERROR) logging.getLogger('urllib3').setLevel(logging.ERROR)
logging.getLogger('schedule').setLevel(logging.ERROR) logging.getLogger('schedule').setLevel(logging.ERROR)

@ -0,0 +1,54 @@
from misc.log import logger
from .pushover import Pushover
from .slack import Slack
log = logger.get_logger(__name__)
SERVICES = {
'pushover': Pushover,
'slack': Slack
}
class Notifications:
def __init__(self):
self.services = []
def load(self, **kwargs):
if 'service' not in kwargs:
log.error("You must specify a service to load with the service parameter")
return False
elif kwargs['service'] not in SERVICES:
log.error("You specified an invalid service to load: %s", kwargs['service'])
return False
try:
chosen_service = SERVICES[kwargs['service']]
del kwargs['service']
# load service
service = chosen_service(**kwargs)
self.services.append(service)
except Exception:
log.exception("Exception while loading service, kwargs=%r: ", kwargs)
def send(self, **kwargs):
try:
# remove service keyword if supplied
if 'service' in kwargs:
# send notification to specified service
chosen_service = kwargs['service'].lower()
del kwargs['service']
else:
chosen_service = None
# send notification(s)
for service in self.services:
if chosen_service and service.NAME.lower() != chosen_service:
continue
elif service.send(**kwargs):
log.info("Sent notification with %s", service.NAME)
except Exception:
log.exception("Exception sending notification, kwargs=%r: ", kwargs)

@ -0,0 +1,33 @@
import requests
from misc.log import logger
log = logger.get_logger(__name__)
class Pushover:
NAME = "Pushover"
def __init__(self, app_token, user_token):
self.app_token = app_token
self.user_token = user_token
log.debug("Initialized Pushover notification agent")
def send(self, **kwargs):
if not self.app_token or not self.user_token:
log.error("You must specify an app_token and user_token when initializing this class")
return False
# send notification
try:
payload = {
'token': self.app_token,
'user': self.user_token,
'message': kwargs['message']
}
resp = requests.post('https://api.pushover.net/1/messages.json', data=payload, timeout=30)
return True if resp.status_code == 200 else False
except Exception:
log.exception("Error sending notification to %r", self.user_token)
return False

@ -0,0 +1,38 @@
import requests
from misc.log import logger
log = logger.get_logger(__name__)
class Slack:
NAME = "Slack"
def __init__(self, webhook_url, sender_name='traktarr', sender_icon=':movie_camera:', channel=None):
self.webhook_url = webhook_url
self.sender_name = sender_name
self.sender_icon = sender_icon
self.channel = channel
log.debug("Initialized Slack notification agent")
def send(self, **kwargs):
if not self.webhook_url or not self.sender_name or not self.sender_icon:
log.error("You must specify an webhook_url, sender_name and sender_icon when initializing this class")
return False
# send notification
try:
payload = {
'text': kwargs['message'],
'username': self.sender_name,
'icon_emoji': self.sender_icon,
}
if self.channel:
payload['channel'] = self.channel
resp = requests.post(self.webhook_url, json=payload, timeout=30)
return True if resp.status_code == 200 else False
except Exception:
log.exception("Error sending notification to %r", self.webhook_url)
return False

@ -10,6 +10,7 @@ from media.trakt import Trakt
from misc import helpers from misc import helpers
from misc.config import cfg from misc.config import cfg
from misc.log import logger from misc.log import logger
from notifications import Notifications
############################################################ ############################################################
# INIT # INIT
@ -18,6 +19,9 @@ from misc.log import logger
# Logging # Logging
log = logger.get_logger('traktarr') log = logger.get_logger('traktarr')
# Notifications
notify = Notifications()
# Click # Click
@click.group(help='Add new shows & movies to Sonarr/Radarr from Trakt lists.') @click.group(help='Add new shows & movies to Sonarr/Radarr from Trakt lists.')
@ -45,7 +49,8 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=
if not trakt.validate_api_key(): if not trakt.validate_api_key():
log.error("Aborting due to failure to validate Trakt API Key") log.error("Aborting due to failure to validate Trakt API Key")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'reason': 'Failure to validate Trakt API Key'}) callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to validate Trakt API Key'})
return None return None
else: else:
log.info("Validated Trakt API Key") log.info("Validated Trakt API Key")
@ -55,7 +60,8 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=
if not sonarr.validate_api_key(): if not sonarr.validate_api_key():
log.error("Aborting due to failure to validate Sonarr URL / API Key") log.error("Aborting due to failure to validate Sonarr URL / API Key")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'reason': 'Failure to validate Sonarr URL / API Key'}) callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to validate Sonarr URL / API Key'})
return None return None
else: else:
log.info("Validated Sonarr URL & API Key") log.info("Validated Sonarr URL & API Key")
@ -65,7 +71,7 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=
if not profile_id or not profile_id > 0: if not profile_id or not profile_id > 0:
log.error("Aborting due to failure to retrieve Profile ID for: %s", cfg.sonarr.profile) log.error("Aborting due to failure to retrieve Profile ID for: %s", cfg.sonarr.profile)
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'shows', callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to retrieve Sonarr Profile ID of %s' % cfg.sonarr.profile}) 'reason': 'Failure to retrieve Sonarr Profile ID of %s' % cfg.sonarr.profile})
return None return None
else: else:
@ -76,7 +82,8 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=
if profile_tags is None: if profile_tags is None:
log.error("Aborting due to failure to retrieve Tag ID's") log.error("Aborting due to failure to retrieve Tag ID's")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'reason': "Failure to retrieve Sonarr Tag ID's"}) callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': "Failure to retrieve Sonarr Tag ID's"})
return None return None
else: else:
log.info("Retrieved %d Tag ID's", len(profile_tags)) log.info("Retrieved %d Tag ID's", len(profile_tags))
@ -86,7 +93,8 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=
if not sonarr_series_list: if not sonarr_series_list:
log.error("Aborting due to failure to retrieve Sonarr shows list") log.error("Aborting due to failure to retrieve Sonarr shows list")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'reason': 'Failure to retrieve Sonarr shows list'}) callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to retrieve Sonarr shows list'})
return None return None
else: else:
log.info("Retrieved Sonarr shows list, shows found: %d", len(sonarr_series_list)) log.info("Retrieved Sonarr shows list, shows found: %d", len(sonarr_series_list))
@ -102,13 +110,15 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=
else: else:
log.error("Aborting due to unknown Trakt list type") log.error("Aborting due to unknown Trakt list type")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'reason': 'Failure to determine Trakt list type'}) callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to determine Trakt list type'})
return None return None
if not trakt_series_list: if not trakt_series_list:
log.error("Aborting due to failure to retrieve Trakt %s shows list", list_type) log.error("Aborting due to failure to retrieve Trakt %s shows list", list_type)
if notifications: if notifications:
callback_notify( callback_notify(
{'event': 'abort', 'type': 'shows', 'reason': 'Failure to retrieve Trakt %s shows list' % list_type}) {'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to retrieve Trakt %s shows list' % list_type})
return None return None
else: else:
log.info("Retrieved Trakt %s shows list, shows found: %d", list_type, len(trakt_series_list)) log.info("Retrieved Trakt %s shows list, shows found: %d", list_type, len(trakt_series_list))
@ -118,7 +128,7 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=
if processed_series_list is None: if processed_series_list is None:
log.error("Aborting due to failure to remove existing Sonarr shows from retrieved Trakt shows list") log.error("Aborting due to failure to remove existing Sonarr shows from retrieved Trakt shows list")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'shows', callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to remove existing Sonarr shows from retrieved Trakt %s shows list' % list_type 'reason': 'Failure to remove existing Sonarr shows from retrieved Trakt %s shows list' % list_type
}) })
return None return None
@ -149,7 +159,7 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=
not no_search): not no_search):
log.info("ADDED %s (%d) with tags: %s", series['show']['title'], series['show']['year'], use_tags) log.info("ADDED %s (%d) with tags: %s", series['show']['title'], series['show']['year'], use_tags)
if notifications: if notifications:
callback_notify({'event': 'add_show', 'show': series['show']}) callback_notify({'event': 'add_show', 'list_type': list_type, 'show': series['show']})
added_shows += 1 added_shows += 1
else: else:
log.error("FAILED adding %s (%d) with tags: %s", series['show']['title'], series['show']['year'], log.error("FAILED adding %s (%d) with tags: %s", series['show']['title'], series['show']['year'],
@ -188,7 +198,8 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications
if not trakt.validate_api_key(): if not trakt.validate_api_key():
log.error("Aborting due to failure to validate Trakt API Key") log.error("Aborting due to failure to validate Trakt API Key")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'movies', 'reason': 'Failure to validate Trakt API Key'}) callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to validate Trakt API Key'})
return None return None
else: else:
log.info("Validated Trakt API Key") log.info("Validated Trakt API Key")
@ -199,7 +210,8 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications
log.error("Aborting due to failure to validate Radarr URL / API Key") log.error("Aborting due to failure to validate Radarr URL / API Key")
if notifications: if notifications:
callback_notify( callback_notify(
{'event': 'abort', 'type': 'movies', 'reason': 'Failure to validate Radarr URL / API Key'}) {'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to validate Radarr URL / API Key'})
return None return None
else: else:
log.info("Validated Radarr URL & API Key") log.info("Validated Radarr URL & API Key")
@ -209,7 +221,7 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications
if not profile_id or not profile_id > 0: if not profile_id or not profile_id > 0:
log.error("Aborting due to failure to retrieve Profile ID for: %s", cfg.radarr.profile) log.error("Aborting due to failure to retrieve Profile ID for: %s", cfg.radarr.profile)
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'movies', callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to retrieve Radarr Profile ID of %s' % cfg.radarr.profile}) 'reason': 'Failure to retrieve Radarr Profile ID of %s' % cfg.radarr.profile})
return None return None
else: else:
@ -220,7 +232,8 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications
if not radarr_movie_list: if not radarr_movie_list:
log.error("Aborting due to failure to retrieve Radarr movies list") log.error("Aborting due to failure to retrieve Radarr movies list")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'movies', 'reason': 'Failure to retrieve Radarr movies list'}) callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to retrieve Radarr movies list'})
return None return None
else: else:
log.info("Retrieved Radarr movies list, movies found: %d", len(radarr_movie_list)) log.info("Retrieved Radarr movies list, movies found: %d", len(radarr_movie_list))
@ -238,13 +251,15 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications
else: else:
log.error("Aborting due to unknown Trakt list type") log.error("Aborting due to unknown Trakt list type")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'movies', 'reason': 'Failure to determine Trakt list type'}) callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to determine Trakt list type'})
return None return None
if not trakt_movies_list: if not trakt_movies_list:
log.error("Aborting due to failure to retrieve Trakt %s movies list", list_type) log.error("Aborting due to failure to retrieve Trakt %s movies list", list_type)
if notifications: if notifications:
callback_notify( callback_notify(
{'event': 'abort', 'type': 'movies', 'reason': 'Failure to retrieve Trakt %s movies list' % list_type}) {'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to retrieve Trakt %s movies list' % list_type})
return None return None
else: else:
log.info("Retrieved Trakt %s movies list, movies found: %d", list_type, len(trakt_movies_list)) log.info("Retrieved Trakt %s movies list, movies found: %d", list_type, len(trakt_movies_list))
@ -254,7 +269,7 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications
if processed_movies_list is None: if processed_movies_list is None:
log.error("Aborting due to failure to remove existing Radarr movies from retrieved Trakt movies list") log.error("Aborting due to failure to remove existing Radarr movies from retrieved Trakt movies list")
if notifications: if notifications:
callback_notify({'event': 'abort', 'type': 'movies', callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to remove existing Radarr movies from retrieved ' 'reason': 'Failure to remove existing Radarr movies from retrieved '
'Trakt %s movies list' % list_type}) 'Trakt %s movies list' % list_type})
return None return None
@ -279,7 +294,7 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications
movie['movie']['ids']['slug'], profile_id, cfg.radarr.root_folder, not no_search): movie['movie']['ids']['slug'], profile_id, cfg.radarr.root_folder, not no_search):
log.info("ADDED %s (%d)", movie['movie']['title'], movie['movie']['year']) log.info("ADDED %s (%d)", movie['movie']['title'], movie['movie']['year'])
if notifications: if notifications:
callback_notify({'event': 'add_movie', 'movie': movie['movie']}) callback_notify({'event': 'add_movie', 'list_type': list_type, 'movie': movie['movie']})
added_movies += 1 added_movies += 1
else: else:
log.error("FAILED adding %s (%d)", movie['movie']['title'], movie['movie']['year']) log.error("FAILED adding %s (%d)", movie['movie']['title'], movie['movie']['year'])
@ -307,13 +322,17 @@ def callback_notify(data):
# handle event # handle event
if data['event'] == 'add_movie': if data['event'] == 'add_movie':
log.info("Added movie: %s (%d)", data['movie']['title'], data['movie']['year']) if cfg.notifications.verbose:
notify.send(
message="Added %s movie: %s (%d)" % (data['list_type'], data['movie']['title'], data['movie']['year']))
return return
elif data['event'] == 'add_show': elif data['event'] == 'add_show':
log.info("Added show: %s (%d)", data['show']['title'], data['show']['year']) if cfg.notifications.verbose:
notify.send(
message="Added %s show: %s (%d)" % (data['list_type'], data['show']['title'], data['show']['year']))
return return
elif data['event'] == 'abort': elif data['event'] == 'abort':
log.error("Error while adding %s due to: %s", data['type'], data['reason']) notify.send(message="Aborted adding Trakt %s %s due to: %s" % (data['list_type'], data['type'], data['reason']))
return return
else: else:
log.error("Unexpected callback: %s", data) log.error("Unexpected callback: %s", data)
@ -345,12 +364,16 @@ def automatic_shows(add_delay=2.5, no_search=False, notifications=False):
total_shows_added += added_shows total_shows_added += added_shows
# send notification # send notification
log.info("Added %d shows from Trakt's %s list", added_shows, list_type) if notifications and not cfg.notifications.verbose:
notify.send(message="Added %d shows from Trakt's %s list" % (added_shows, list_type))
# sleep # sleep
time.sleep(10) time.sleep(10)
log.info("Finished, added %d shows in total to Sonarr", total_shows_added) log.info("Added %d shows total to Sonarr", total_shows_added)
# send notification
if notifications:
notify.send(message="Added %d shows total to Sonarr" % total_shows_added)
except Exception: except Exception:
log.exception("Exception while automatically adding shows: ") log.exception("Exception while automatically adding shows: ")
@ -382,12 +405,16 @@ def automatic_movies(add_delay=2.5, no_search=False, notifications=False):
total_movies_added += added_movies total_movies_added += added_movies
# send notification # send notification
log.info("Added %d movies from Trakt's %s list", added_movies, list_type) if notifications and not cfg.notifications.verbose:
notify.send(message="Added %d movies from Trakt's %s list" % (added_movies, list_type))
# sleep # sleep
time.sleep(10) time.sleep(10)
log.info("Finished, added %d movies in total to Radarr", total_movies_added) log.info("Added %d movies total to Radarr", total_movies_added)
# send notification
if notifications:
notify.send(message="Added %d movies total to Radarr" % total_movies_added)
except Exception: except Exception:
log.exception("Exception while automatically adding movies: ") log.exception("Exception while automatically adding movies: ")
@ -416,9 +443,26 @@ def run(add_delay=2.5, no_search=False, no_notifications=False):
time.sleep(1) time.sleep(1)
############################################################
# MISC
############################################################
def init_notifications():
try:
for notification_name, notification_config in cfg.notifications.items():
if notification_name.lower() == 'verbose':
continue
notify.load(**notification_config)
except Exception:
log.exception("Exception initializing notification agents: ")
return
############################################################ ############################################################
# MAIN # MAIN
############################################################ ############################################################
if __name__ == "__main__": if __name__ == "__main__":
init_notifications()
app() app()

Loading…
Cancel
Save