Merge pull request #5 from l3uddz/develop

Develop
pull/8/head
James 7 years ago committed by GitHub
commit 557bf0b2a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -98,8 +98,9 @@ class Radarr:
if req.status_code == 201 and req.json()['tmdbId'] == movie_tmdbid:
log.debug("Successfully added %s (%d)", movie_title, movie_tmdbid)
return True
elif req.status_code == 409 and 'message' in req.text:
log.error("Failed to add %s (%d), reason: %s", movie_title, movie_tmdbid, req.json()['message'])
elif 'json' in req.headers['Content-Type'].lower() and 'message' in req.text:
log.error("Failed to add %s (%d) - status_code: %d, reason: %s", movie_title, movie_tmdbid,
req.status_code, req.json()['message'])
return False
else:
log.error("Failed to add %s (%d), unexpected response:\n%s", movie_title, movie_tmdbid, req.text)

@ -144,8 +144,9 @@ class Sonarr:
if req.status_code == 201 and req.json()['tvdbId'] == series_tvdbid:
log.debug("Successfully added %s (%d)", series_title, series_tvdbid)
return True
elif req.status_code == 401 and 'errorMessage' in req.text:
log.error("Failed to add %s (%d), reason: %s", series_title, series_tvdbid, req.json()['errorMessage'])
elif 'json' in req.headers['Content-Type'].lower() and 'errorMessage' in req.text:
log.error("Failed to add %s (%d) - status_code: %d, reason: %s", series_title, series_tvdbid,
req.status_code, req.json()['errorMessage'])
return False
else:
log.error("Failed to add %s (%d), unexpected response:\n%s", series_title, series_tvdbid, req.text)

@ -1,3 +1,5 @@
import time
import backoff
import requests
@ -84,6 +86,7 @@ class Trakt:
log.info("There are %d pages left to retrieve results from",
int(req.headers['X-Pagination-Page-Count']) - payload['page'])
payload['page'] += 1
time.sleep(5)
else:
log.error("Failed to retrieve anticipated shows, request response: %d", req.status_code)
@ -139,6 +142,7 @@ class Trakt:
log.info("There are %d pages left to retrieve results from",
int(req.headers['X-Pagination-Page-Count']) - payload['page'])
payload['page'] += 1
time.sleep(5)
else:
log.error("Failed to retrieve trending shows, request response: %d", req.status_code)
@ -195,6 +199,7 @@ class Trakt:
log.info("There are %d pages left to retrieve results from",
int(req.headers['X-Pagination-Page-Count']) - payload['page'])
payload['page'] += 1
time.sleep(5)
else:
log.error("Failed to retrieve popular shows, request response: %d", req.status_code)
@ -254,6 +259,7 @@ class Trakt:
log.info("There are %d pages left to retrieve results from",
int(req.headers['X-Pagination-Page-Count']) - payload['page'])
payload['page'] += 1
time.sleep(5)
else:
log.error("Failed to retrieve anticipated movies, request response: %d", req.status_code)
@ -309,6 +315,7 @@ class Trakt:
log.info("There are %d pages left to retrieve results from",
int(req.headers['X-Pagination-Page-Count']) - payload['page'])
payload['page'] += 1
time.sleep(5)
else:
log.error("Failed to retrieve trending movies, request response: %d", req.status_code)
@ -349,7 +356,7 @@ class Trakt:
if req.status_code == 200:
resp_json = req.json()
# process list so it conforms to standard we expect ( e.g. {"show": {.....}} )
# process list so it conforms to standard we expect ( e.g. {"movie": {.....}} )
for movie in resp_json:
if movie not in processed_movies:
processed_movies.append({'movie': movie})
@ -365,6 +372,7 @@ class Trakt:
log.info("There are %d pages left to retrieve results from",
int(req.headers['X-Pagination-Page-Count']) - payload['page'])
payload['page'] += 1
time.sleep(5)
else:
log.error("Failed to retrieve popular movies, request response: %d", req.status_code)
@ -377,3 +385,59 @@ class Trakt:
except Exception:
log.exception("Exception retrieving popular movies: ")
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def get_boxoffice_movies(self, limit=1000, languages=None):
try:
processed_movies = []
if languages is None:
languages = ['en']
# generate payload
payload = {'extended': 'full', 'limit': limit, 'page': 1}
if languages:
payload['languages'] = ','.join(languages)
# make request
while True:
req = requests.get('https://api.trakt.tv/movies/boxoffice', params=payload, headers=self.headers,
timeout=30)
log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload)
log.debug("Response Code: %d", req.status_code)
log.debug("Response Page: %d of %d", payload['page'],
0 if 'X-Pagination-Page-Count' not in req.headers else int(
req.headers['X-Pagination-Page-Count']))
if req.status_code == 200:
resp_json = req.json()
for movie in resp_json:
if movie not in processed_movies:
processed_movies.append(movie)
# check if we have fetched the last page, break if so
if 'X-Pagination-Page-Count' not in req.headers or not int(req.headers['X-Pagination-Page-Count']):
log.debug("There was no more pages to retrieve")
break
elif payload['page'] >= int(req.headers['X-Pagination-Page-Count']):
log.debug("There are no more pages to retrieve results from")
break
else:
log.info("There are %d pages left to retrieve results from",
int(req.headers['X-Pagination-Page-Count']) - payload['page'])
payload['page'] += 1
time.sleep(5)
else:
log.error("Failed to retrieve boxoffice movies, request response: %d", req.status_code)
break
if len(processed_movies):
log.debug("Found %d boxoffice movies", len(processed_movies))
return processed_movies
return None
except Exception:
log.exception("Exception retrieving boxoffice movies: ")
return None

@ -19,7 +19,7 @@ base_config = {
'root_folder': '/tv/',
'tags': {
'amzn': ['hbo', 'amc', 'usa network', 'tnt', 'starz', 'the cw', 'fx', 'fox', 'abc', 'nbc', 'cbs', 'tbs',
'amazon', 'syfy', 'cinemax']
'amazon', 'syfy', 'cinemax', 'bravo', 'showtime']
}
},
'radarr': {
@ -38,15 +38,44 @@ base_config = {
'fox sports'],
'allowed_countries': ['us', 'gb', 'ca'],
'blacklisted_min_runtime': 15,
'blacklisted_min_year': 2000
'blacklisted_min_year': 2000,
'blacklisted_max_year': 2019
},
'movies': {
'blacklisted_genres': ['documentary', 'music'],
'blacklisted_min_runtime': 60,
'blacklisted_min_year': 2000,
'blacklisted_max_year': 2019,
'blacklist_title_keywords': ['untitled', 'barbie'],
'allowed_countries': ['us', 'gb', 'ca']
}
},
'automatic': {
'movies': {
'interval': 24,
'anticipated': 10,
'trending': 2,
'popular': 3,
'boxoffice': 10
},
'shows': {
'interval': 72,
'anticipated': 100,
'trending': 2,
'popular': 1
}
},
'notifications': {
'verbose': True,
'my slack': {
'service': 'slack',
'webhook_url': ''
},
'my pushover': {
'service': 'pushover',
'app_token': '',
'user_token': ''
}
}
}
cfg = None

@ -85,7 +85,7 @@ def trakt_blacklisted_show_genre(show, genres):
return blacklisted
def trakt_blacklisted_show_year(show, earliest_year):
def trakt_blacklisted_show_year(show, earliest_year, latest_year):
blacklisted = False
try:
year = misc_str.get_year_from_timestamp(show['show']['first_aired'])
@ -93,11 +93,11 @@ def trakt_blacklisted_show_year(show, earliest_year):
log.debug("%s was blacklisted due to having an unknown first_aired date", show['show']['title'])
blacklisted = True
else:
if year < earliest_year:
if year < earliest_year or year > latest_year:
log.debug("%s was blacklisted because it first aired in: %d", show['show']['title'], year)
blacklisted = True
except Exception:
log.exception("Exception determining if show is before earliest_year %s:", show)
log.exception("Exception determining if show is within min_year and max_year range %s:", show)
return blacklisted
@ -156,7 +156,8 @@ def trakt_blacklisted_show_runtime(show, lowest_runtime):
def trakt_is_show_blacklisted(show, blacklist_settings):
blacklisted = False
try:
if trakt_blacklisted_show_year(show, blacklist_settings.blacklisted_min_year):
if trakt_blacklisted_show_year(show, blacklist_settings.blacklisted_min_year,
blacklist_settings.blacklisted_max_year):
blacklisted = True
if trakt_blacklisted_show_country(show, blacklist_settings.allowed_countries):
blacklisted = True
@ -240,7 +241,7 @@ def trakt_blacklisted_movie_genre(movie, genres):
return blacklisted
def trakt_blacklisted_movie_year(movie, earliest_year):
def trakt_blacklisted_movie_year(movie, earliest_year, latest_year):
blacklisted = False
try:
year = movie['movie']['year']
@ -248,11 +249,11 @@ def trakt_blacklisted_movie_year(movie, earliest_year):
log.debug("%s was blacklisted due to having an unknown year", movie['movie']['title'])
blacklisted = True
else:
if int(year) < earliest_year:
if int(year) < earliest_year or int(year) > latest_year:
log.debug("%s was blacklisted because it's year is: %d", movie['movie']['title'], int(year))
blacklisted = True
except Exception:
log.exception("Exception determining if movie is before earliest_year %s:", movie)
log.exception("Exception determining if movie is within min_year and max_year ranger %s:", movie)
return blacklisted
@ -312,7 +313,8 @@ def trakt_is_movie_blacklisted(movie, blacklist_settings):
try:
if trakt_blacklisted_movie_title(movie, blacklist_settings.blacklist_title_keywords):
blacklisted = True
if trakt_blacklisted_movie_year(movie, blacklist_settings.blacklisted_min_year):
if trakt_blacklisted_movie_year(movie, blacklist_settings.blacklisted_min_year,
blacklist_settings.blacklisted_max_year):
blacklisted = True
if trakt_blacklisted_movie_country(movie, blacklist_settings.allowed_countries):
blacklisted = True

@ -17,7 +17,9 @@ class Logger:
self.root_logger.setLevel(log_level)
# disable bloat loggers
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.ERROR)
logging.getLogger('schedule').setLevel(logging.ERROR)
# init console_logger
self.console_handler = logging.StreamHandler(sys.stdout)

@ -13,3 +13,16 @@ def get_year_from_timestamp(timestamp):
except Exception:
log.exception("Exception parsing year from %s: ", timestamp)
return int(year) if str(year).isdigit() else 0
def is_ascii(string):
try:
string.encode('ascii')
except UnicodeEncodeError:
return False
except UnicodeDecodeError:
return False
except Exception:
log.exception(u"Exception checking if %r was ascii: ", string)
return False
return True

@ -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.debug("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

@ -1,4 +1,5 @@
backoff==1.4.3
schedule==0.4.3
attrdict==2.0.0
click==6.7
backoff==1.4.3
requests==2.18.4

@ -0,0 +1,17 @@
# /etc/systemd/system/traktarr.service
[Unit]
Description=traktarr
After=network-online.target unionfs.service
[Service]
User=seed
Group=seed
Type=simple
WorkingDirectory=/opt/traktarr/
ExecStart=/usr/bin/python3 /opt/traktarr/traktarr.py run
Restart=always
RestartSec=10
[Install]
WantedBy=default.target

@ -2,6 +2,7 @@
import time
import click
import schedule
from media.radarr import Radarr
from media.sonarr import Sonarr
@ -9,6 +10,7 @@ from media.trakt import Trakt
from misc import helpers
from misc.config import cfg
from misc.log import logger
from notifications import Notifications
############################################################
# INIT
@ -17,9 +19,13 @@ from misc.log import logger
# Logging
log = logger.get_logger('traktarr')
# Notifications
notify = Notifications()
# Click
@click.group(help='Add new shows & movies to Sonarr/Radarr from Trakt lists.')
@click.version_option('1.1.0', prog_name='traktarr')
def app():
pass
@ -34,14 +40,18 @@ def app():
@click.option('--add-limit', '-l', default=0, help='Limit number of shows added to Sonarr.', show_default=True)
@click.option('--add-delay', '-d', default=2.5, help='Seconds between each add request to Sonarr.', show_default=True)
@click.option('--no-search', is_flag=True, help='Disable search when adding shows to Sonarr.')
def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
@click.option('--notifications', is_flag=True, help='Send notifications.')
def shows(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=False):
added_shows = 0
# validate trakt api_key
trakt = Trakt(cfg.trakt.api_key)
if not trakt.validate_api_key():
log.error("Aborting due to failure to validate Trakt API Key")
return
if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to validate Trakt API Key'})
return None
else:
log.info("Validated Trakt API Key")
@ -49,7 +59,10 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
sonarr = Sonarr(cfg.sonarr.url, cfg.sonarr.api_key)
if not sonarr.validate_api_key():
log.error("Aborting due to failure to validate Sonarr URL / API Key")
return
if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to validate Sonarr URL / API Key'})
return None
else:
log.info("Validated Sonarr URL & API Key")
@ -57,7 +70,10 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
profile_id = sonarr.get_profile_id(cfg.sonarr.profile)
if not profile_id or not profile_id > 0:
log.error("Aborting due to failure to retrieve Profile ID for: %s", cfg.sonarr.profile)
return
if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to retrieve Sonarr Profile ID of %s' % cfg.sonarr.profile})
return None
else:
log.info("Retrieved Profile ID for %s: %d", cfg.sonarr.profile, profile_id)
@ -65,7 +81,10 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
profile_tags = sonarr.get_tags()
if profile_tags is None:
log.error("Aborting due to failure to retrieve Tag ID's")
return
if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': "Failure to retrieve Sonarr Tag ID's"})
return None
else:
log.info("Retrieved %d Tag ID's", len(profile_tags))
@ -73,7 +92,10 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
sonarr_series_list = sonarr.get_series()
if not sonarr_series_list:
log.error("Aborting due to failure to retrieve Sonarr shows list")
return
if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to retrieve Sonarr shows list'})
return None
else:
log.info("Retrieved Sonarr shows list, shows found: %d", len(sonarr_series_list))
@ -87,18 +109,29 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
trakt_series_list = trakt.get_popular_shows()
else:
log.error("Aborting due to unknown Trakt list type")
return
if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to determine Trakt list type'})
return None
if not trakt_series_list:
log.error("Aborting due to failure to retrieve Trakt %s shows list", list_type)
return
if notifications:
callback_notify(
{'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to retrieve Trakt %s shows list' % list_type})
return None
else:
log.info("Retrieved Trakt %s shows list, shows found: %d", list_type, len(trakt_series_list))
# build filtered series list without series that exist in sonarr
processed_series_list = helpers.sonarr_remove_existing_series(sonarr_series_list, trakt_series_list)
if not processed_series_list:
if processed_series_list is None:
log.error("Aborting due to failure to remove existing Sonarr shows from retrieved Trakt shows list")
return
if notifications:
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
})
return None
else:
log.info("Removed existing Sonarr shows from Trakt shows list, shows left to process: %d",
len(processed_series_list))
@ -125,6 +158,8 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
series['show']['ids']['slug'], profile_id, cfg.sonarr.root_folder, use_tags,
not no_search):
log.info("ADDED %s (%d) with tags: %s", series['show']['title'], series['show']['year'], use_tags)
if notifications:
callback_notify({'event': 'add_show', 'list_type': list_type, 'show': series['show']})
added_shows += 1
else:
log.error("FAILED adding %s (%d) with tags: %s", series['show']['title'], series['show']['year'],
@ -142,21 +177,35 @@ def shows(list_type, add_limit=0, add_delay=2.5, no_search=False):
log.info("Added %d new show(s) to Sonarr", added_shows)
# send notification
if notifications:
notify.send(message="Added %d shows from Trakt's %s list" % (added_shows, list_type))
return added_shows
############################################################
# MOVIES
############################################################
@app.command(help='Add new movies to Radarr.')
@click.option('--list-type', '-t', type=click.Choice(['anticipated', 'trending', 'popular']),
@click.option('--list-type', '-t', type=click.Choice(['anticipated', 'trending', 'popular', 'boxoffice']),
help='Trakt list to process.', required=True)
@click.option('--add-limit', '-l', default=0, help='Limit number of movies added to Radarr.', show_default=True)
@click.option('--add-delay', '-d', default=2.5, help='Seconds between each add request to Radarr.', show_default=True)
@click.option('--no-search', is_flag=True, help='Disable search when adding movies to Radarr.')
def movies(list_type, add_limit=0, add_delay=2.5, no_search=False):
@click.option('--notifications', is_flag=True, help='Send notifications.')
def movies(list_type, add_limit=0, add_delay=2.5, no_search=False, notifications=False):
added_movies = 0
# validate trakt api_key
trakt = Trakt(cfg.trakt.api_key)
if not trakt.validate_api_key():
log.error("Aborting due to failure to validate Trakt API Key")
return
if notifications:
callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to validate Trakt API Key'})
return None
else:
log.info("Validated Trakt API Key")
@ -164,7 +213,11 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False):
radarr = Radarr(cfg.radarr.url, cfg.radarr.api_key)
if not radarr.validate_api_key():
log.error("Aborting due to failure to validate Radarr URL / API Key")
return
if notifications:
callback_notify(
{'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to validate Radarr URL / API Key'})
return None
else:
log.info("Validated Radarr URL & API Key")
@ -172,7 +225,10 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False):
profile_id = radarr.get_profile_id(cfg.radarr.profile)
if not profile_id or not profile_id > 0:
log.error("Aborting due to failure to retrieve Profile ID for: %s", cfg.radarr.profile)
return
if notifications:
callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to retrieve Radarr Profile ID of %s' % cfg.radarr.profile})
return None
else:
log.info("Retrieved Profile ID for %s: %d", cfg.radarr.profile, profile_id)
@ -180,7 +236,10 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False):
radarr_movie_list = radarr.get_movies()
if not radarr_movie_list:
log.error("Aborting due to failure to retrieve Radarr movies list")
return
if notifications:
callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to retrieve Radarr movies list'})
return None
else:
log.info("Retrieved Radarr movies list, movies found: %d", len(radarr_movie_list))
@ -192,20 +251,33 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False):
trakt_movies_list = trakt.get_trending_movies()
elif list_type.lower() == 'popular':
trakt_movies_list = trakt.get_popular_movies()
elif list_type.lower() == 'boxoffice':
trakt_movies_list = trakt.get_boxoffice_movies()
else:
log.error("Aborting due to unknown Trakt list type")
return
if notifications:
callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to determine Trakt list type'})
return None
if not trakt_movies_list:
log.error("Aborting due to failure to retrieve Trakt %s movies list", list_type)
return
if notifications:
callback_notify(
{'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to retrieve Trakt %s movies list' % list_type})
return None
else:
log.info("Retrieved Trakt %s movies list, movies found: %d", list_type, len(trakt_movies_list))
# build filtered movie list without movies that exist in radarr
processed_movies_list = helpers.radarr_remove_existing_movies(radarr_movie_list, trakt_movies_list)
if not processed_movies_list:
if processed_movies_list is None:
log.error("Aborting due to failure to remove existing Radarr movies from retrieved Trakt movies list")
return
if notifications:
callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to remove existing Radarr movies from retrieved '
'Trakt %s movies list' % list_type})
return None
else:
log.info("Removed existing Radarr movies from Trakt movies list, movies left to process: %d",
len(processed_movies_list))
@ -226,6 +298,8 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False):
if radarr.add_movie(movie['movie']['ids']['tmdb'], movie['movie']['title'], movie['movie']['year'],
movie['movie']['ids']['slug'], profile_id, cfg.radarr.root_folder, not no_search):
log.info("ADDED %s (%d)", movie['movie']['title'], movie['movie']['year'])
if notifications:
callback_notify({'event': 'add_movie', 'list_type': list_type, 'movie': movie['movie']})
added_movies += 1
else:
log.error("FAILED adding %s (%d)", movie['movie']['title'], movie['movie']['year'])
@ -242,10 +316,155 @@ def movies(list_type, add_limit=0, add_delay=2.5, no_search=False):
log.info("Added %d new movie(s) to Radarr", added_movies)
# send notification
if notifications:
notify.send(message="Added %d movies from Trakt's %s list" % (added_movies, list_type))
return added_movies
############################################################
# AUTOMATIC
############################################################
def callback_notify(data):
log.debug("Received callback data: %s", data)
# handle event
if data['event'] == 'add_movie':
if cfg.notifications.verbose:
notify.send(
message="Added %s movie: %s (%d)" % (data['list_type'], data['movie']['title'], data['movie']['year']))
return
elif data['event'] == 'add_show':
if cfg.notifications.verbose:
notify.send(
message="Added %s show: %s (%d)" % (data['list_type'], data['show']['title'], data['show']['year']))
return
elif data['event'] == 'abort':
notify.send(message="Aborted adding Trakt %s %s due to: %s" % (data['list_type'], data['type'], data['reason']))
return
else:
log.error("Unexpected callback: %s", data)
return
def automatic_shows(add_delay=2.5, no_search=False, notifications=False):
total_shows_added = 0
try:
log.info("Started")
for list_type, type_amount in cfg.automatic.shows.items():
if list_type.lower() == 'interval':
continue
elif type_amount <= 0:
log.info("Skipped Trakt's %s shows list", list_type)
continue
else:
log.info("Adding %d shows from Trakt's %s list", type_amount, list_type)
# run shows
added_shows = shows.callback(list_type=list_type, add_limit=type_amount,
add_delay=add_delay, no_search=no_search,
notifications=notifications)
if added_shows is None:
log.error("Failed adding shows from Trakt's %s list", list_type)
time.sleep(10)
continue
total_shows_added += added_shows
# sleep
time.sleep(10)
log.info("Finished, 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:
log.exception("Exception while automatically adding shows: ")
return
def automatic_movies(add_delay=2.5, no_search=False, notifications=False):
total_movies_added = 0
try:
log.info("Started")
for list_type, type_amount in cfg.automatic.movies.items():
if list_type.lower() == 'interval':
continue
elif type_amount <= 0:
log.info("Skipped Trakt's %s movies list", list_type)
continue
else:
log.info("Adding %d movies from Trakt's %s list", type_amount, list_type)
# run movies
added_movies = movies.callback(list_type=list_type, add_limit=type_amount,
add_delay=add_delay, no_search=no_search,
notifications=notifications)
if added_movies is None:
log.error("Failed adding movies from Trakt's %s list", list_type)
time.sleep(10)
continue
total_movies_added += added_movies
# sleep
time.sleep(10)
log.info("Finished, 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:
log.exception("Exception while automatically adding movies: ")
return
@app.command(help='Run in automatic mode.')
@click.option('--add-delay', '-d', default=2.5, help='Seconds between each add request to Sonarr / Radarr.',
show_default=True)
@click.option('--no-search', is_flag=True, help='Disable search when adding to Sonarr / Radarr.')
@click.option('--no-notifications', is_flag=True, help="Disable notifications.")
def run(add_delay=2.5, no_search=False, no_notifications=False):
# add tasks to repeat
schedule.every(cfg.automatic.movies.interval).hours.do(automatic_movies, add_delay, no_search,
not no_notifications)
schedule.every(cfg.automatic.shows.interval).hours.do(automatic_shows, add_delay, no_search, not no_notifications)
# run schedule
log.info("Automatic mode is now running...")
while True:
try:
schedule.run_pending()
except Exception:
log.exception("Unhandled exception occurred while processing scheduled tasks: ")
else:
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
############################################################
if __name__ == "__main__":
init_notifications()
app()

Loading…
Cancel
Save