Add config and file location support

pull/54/head
Filipe Santos 7 years ago
parent 601512ba09
commit 35cd1b836f

@ -138,14 +138,14 @@ To have Traktarr get Movies and Shows for you automatically, on set interval.
"api_key": "", "api_key": "",
"profile": "HD-1080p", "profile": "HD-1080p",
"root_folder": "/movies/", "root_folder": "/movies/",
"url": "http://localhost:7878" "url": "http://localhost:7878/"
}, },
"sonarr": { "sonarr": {
"api_key": "", "api_key": "",
"profile": "HD-1080p", "profile": "HD-1080p",
"root_folder": "/tv/", "root_folder": "/tv/",
"tags": {}, "tags": {},
"url": "http://localhost:8989" "url": "http://localhost:8989/"
}, },
"trakt": { "trakt": {
"api_key": "" "api_key": ""

@ -1 +0,0 @@
from media import trakt, sonarr, radarr

@ -27,7 +27,7 @@ class Radarr:
def validate_api_key(self): def validate_api_key(self):
try: try:
# request system status to validate api_key # request system status to validate api_key
req = requests.get(urljoin(self.server_url, 'api/system/status'), headers=self.headers, timeout=30) req = requests.get(urljoin(self.server_url, 'api/system/status'), headers=self.headers, timeout=60)
log.debug("Request Response: %d", req.status_code) log.debug("Request Response: %d", req.status_code)
if req.status_code == 200 and 'version' in req.json(): if req.status_code == 200 and 'version' in req.json():
@ -41,7 +41,7 @@ class Radarr:
def get_movies(self): def get_movies(self):
try: try:
# make request # make request
req = requests.get(urljoin(self.server_url, 'api/movie'), headers=self.headers, timeout=30) req = requests.get(urljoin(self.server_url, 'api/movie'), headers=self.headers, timeout=60)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Response: %d", req.status_code) log.debug("Request Response: %d", req.status_code)
@ -59,7 +59,7 @@ class Radarr:
def get_profile_id(self, profile_name): def get_profile_id(self, profile_name):
try: try:
# make request # make request
req = requests.get(urljoin(self.server_url, 'api/profile'), headers=self.headers, timeout=30) req = requests.get(urljoin(self.server_url, 'api/profile'), headers=self.headers, timeout=60)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Response: %d", req.status_code) log.debug("Request Response: %d", req.status_code)
@ -82,16 +82,29 @@ class Radarr:
try: try:
# generate payload # generate payload
payload = { payload = {
'tmdbId': movie_tmdbid, 'title': movie_title, 'year': movie_year, 'tmdbId': movie_tmdbid,
'qualityProfileId': profile_id, 'images': [], 'title': movie_title,
'monitored': True, 'rootFolderPath': root_folder, 'year': movie_year,
'minimumAvailability': 'released', 'titleSlug': movie_title_slug, 'qualityProfileId': profile_id,
'addOptions': {'ignoreEpisodesWithFiles': False, 'ignoreEpisodesWithoutFiles': False, 'images': [],
'searchForMovie': search_missing} 'monitored': True,
'rootFolderPath': root_folder,
'minimumAvailability': 'released',
'titleSlug': movie_title_slug,
'addOptions': {
'ignoreEpisodesWithFiles': False,
'ignoreEpisodesWithoutFiles': False,
'searchForMovie': search_missing
}
} }
# make request # make request
req = requests.post(urljoin(self.server_url, 'api/movie'), json=payload, headers=self.headers, timeout=30) req = requests.post(
urljoin(self.server_url, 'api/movie'),
headers=self.headers,
json=payload,
timeout=60
)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload) log.debug("Request Payload: %s", payload)
log.debug("Request Response Code: %d", req.status_code) log.debug("Request Response Code: %d", req.status_code)

@ -20,14 +20,13 @@ class Sonarr:
self.server_url = server_url self.server_url = server_url
self.api_key = api_key self.api_key = api_key
self.headers = { self.headers = {
'Content-Type': 'application/json',
'X-Api-Key': self.api_key, 'X-Api-Key': self.api_key,
} }
def validate_api_key(self): def validate_api_key(self):
try: try:
# request system status to validate api_key # request system status to validate api_key
req = requests.get(urljoin(self.server_url, 'api/system/status'), headers=self.headers, timeout=30) req = requests.get(urljoin(self.server_url, 'api/system/status'), headers=self.headers, timeout=60)
log.debug("Request Response: %d", req.status_code) log.debug("Request Response: %d", req.status_code)
if req.status_code == 200 and 'version' in req.json(): if req.status_code == 200 and 'version' in req.json():
@ -41,7 +40,7 @@ class Sonarr:
def get_series(self): def get_series(self):
try: try:
# make request # make request
req = requests.get(urljoin(self.server_url, 'api/series'), headers=self.headers, timeout=30) req = requests.get(urljoin(self.server_url, 'api/series'), headers=self.headers, timeout=60)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Response: %d", req.status_code) log.debug("Request Response: %d", req.status_code)
@ -59,7 +58,7 @@ class Sonarr:
def get_profile_id(self, profile_name): def get_profile_id(self, profile_name):
try: try:
# make request # make request
req = requests.get(urljoin(self.server_url, 'api/profile'), headers=self.headers, timeout=30) req = requests.get(urljoin(self.server_url, 'api/profile'), headers=self.headers, timeout=60)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Response: %d", req.status_code) log.debug("Request Response: %d", req.status_code)
@ -81,7 +80,7 @@ class Sonarr:
def get_tag_id(self, tag_name): def get_tag_id(self, tag_name):
try: try:
# make request # make request
req = requests.get(urljoin(self.server_url, 'api/tag'), headers=self.headers, timeout=30) req = requests.get(urljoin(self.server_url, 'api/tag'), headers=self.headers, timeout=60)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Response: %d", req.status_code) log.debug("Request Response: %d", req.status_code)
@ -104,7 +103,7 @@ class Sonarr:
tags = {} tags = {}
try: try:
# make request # make request
req = requests.get(urljoin(self.server_url, 'api/tag'), headers=self.headers, timeout=30) req = requests.get(urljoin(self.server_url, 'api/tag'), headers=self.headers, timeout=60)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Response: %d", req.status_code) log.debug("Request Response: %d", req.status_code)
@ -126,18 +125,30 @@ class Sonarr:
try: try:
# generate payload # generate payload
payload = { payload = {
'tvdbId': series_tvdbid, 'title': series_title, 'titleSlug': series_title_slug, 'tvdbId': series_tvdbid,
'qualityProfileId': profile_id, 'tags': [] if not tag_ids or not isinstance(tag_ids, list) else tag_ids, 'title': series_title,
'titleSlug': series_title_slug,
'qualityProfileId': profile_id,
'tags': [] if not tag_ids or not isinstance(tag_ids, list) else tag_ids,
'images': [], 'images': [],
'seasons': [], 'seasonFolder': True, 'seasons': [],
'monitored': True, 'rootFolderPath': root_folder, 'seasonFolder': True,
'addOptions': {'ignoreEpisodesWithFiles': False, 'monitored': True,
'ignoreEpisodesWithoutFiles': False, 'rootFolderPath': root_folder,
'searchForMissingEpisodes': search_missing} 'addOptions': {
'ignoreEpisodesWithFiles': False,
'ignoreEpisodesWithoutFiles': False,
'searchForMissingEpisodes': search_missing
}
} }
# make request # make request
req = requests.post(urljoin(self.server_url, 'api/series'), json=payload, headers=self.headers, timeout=30) req = requests.post(
urljoin(self.server_url, 'api/series'),
headers=self.headers,
json=payload,
timeout=60
)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload) log.debug("Request Payload: %s", payload)
log.debug("Request Response Code: %d", req.status_code) log.debug("Request Response Code: %d", req.status_code)

@ -29,8 +29,12 @@ class Trakt:
payload = {'extended': 'full', 'limit': 1000} payload = {'extended': 'full', 'limit': 1000}
# make request # make request
req = requests.get('https://api.trakt.tv/shows/anticipated', params=payload, headers=self.headers, req = requests.get(
timeout=30) 'https://api.trakt.tv/shows/anticipated',
headers=self.headers,
params=payload,
timeout=30
)
log.debug("Request Response: %d", req.status_code) log.debug("Request Response: %d", req.status_code)
if req.status_code == 200: if req.status_code == 200:
@ -59,8 +63,12 @@ class Trakt:
# make request # make request
while True: while True:
req = requests.get('https://api.trakt.tv/shows/anticipated', params=payload, headers=self.headers, req = requests.get(
timeout=30) 'https://api.trakt.tv/shows/anticipated',
headers=self.headers,
params=payload,
timeout=30
)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload) log.debug("Request Payload: %s", payload)
log.debug("Response Code: %d", req.status_code) log.debug("Response Code: %d", req.status_code)
@ -115,8 +123,12 @@ class Trakt:
# make request # make request
while True: while True:
req = requests.get('https://api.trakt.tv/shows/trending', params=payload, headers=self.headers, req = requests.get(
timeout=30) 'https://api.trakt.tv/shows/trending',
headers=self.headers,
params=payload,
timeout=30
)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload) log.debug("Request Payload: %s", payload)
log.debug("Response Code: %d", req.status_code) log.debug("Response Code: %d", req.status_code)
@ -171,8 +183,12 @@ class Trakt:
# make request # make request
while True: while True:
req = requests.get('https://api.trakt.tv/shows/popular', params=payload, headers=self.headers, req = requests.get(
timeout=30) 'https://api.trakt.tv/shows/popular',
headers=self.headers,
params=payload,
timeout=30
)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload) log.debug("Request Payload: %s", payload)
log.debug("Response Code: %d", req.status_code) log.debug("Response Code: %d", req.status_code)
@ -232,8 +248,12 @@ class Trakt:
# make request # make request
while True: while True:
req = requests.get('https://api.trakt.tv/movies/anticipated', params=payload, headers=self.headers, req = requests.get(
timeout=30) 'https://api.trakt.tv/movies/anticipated',
headers=self.headers,
params=payload,
timeout=30
)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload) log.debug("Request Payload: %s", payload)
log.debug("Response Code: %d", req.status_code) log.debug("Response Code: %d", req.status_code)
@ -288,8 +308,12 @@ class Trakt:
# make request # make request
while True: while True:
req = requests.get('https://api.trakt.tv/movies/trending', params=payload, headers=self.headers, req = requests.get(
timeout=30) 'https://api.trakt.tv/movies/trending',
headers=self.headers,
params=payload,
timeout=30
)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload) log.debug("Request Payload: %s", payload)
log.debug("Response Code: %d", req.status_code) log.debug("Response Code: %d", req.status_code)
@ -344,8 +368,12 @@ class Trakt:
# make request # make request
while True: while True:
req = requests.get('https://api.trakt.tv/movies/popular', params=payload, headers=self.headers, req = requests.get(
timeout=30) 'https://api.trakt.tv/movies/popular',
headers=self.headers,
params=payload,
timeout=30
)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload) log.debug("Request Payload: %s", payload)
log.debug("Response Code: %d", req.status_code) log.debug("Response Code: %d", req.status_code)
@ -401,8 +429,12 @@ class Trakt:
# make request # make request
while True: while True:
req = requests.get('https://api.trakt.tv/movies/boxoffice', params=payload, headers=self.headers, req = requests.get(
timeout=30) 'https://api.trakt.tv/movies/boxoffice',
headers=self.headers,
params=payload,
timeout=30
)
log.debug("Request URL: %s", req.url) log.debug("Request URL: %s", req.url)
log.debug("Request Payload: %s", payload) log.debug("Request Payload: %s", payload)
log.debug("Response Code: %d", req.status_code) log.debug("Response Code: %d", req.status_code)

@ -1,2 +0,0 @@
from misc import config, str, helpers
from misc.log import logger

@ -4,68 +4,15 @@ import sys
from attrdict import AttrDict from attrdict import AttrDict
config_path = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'config.json')
base_config = { class Singleton(type):
'core': { _instances = {}
'debug': False
}, def __call__(cls, *args, **kwargs):
'trakt': { if cls not in cls._instances:
'api_key': '' cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
},
'sonarr': { return cls._instances[cls]
'url': 'http://localhost:8989',
'api_key': '',
'profile': 'HD-1080p',
'root_folder': '/tv/',
'tags': {
}
},
'radarr': {
'url': 'http://localhost:7878',
'api_key': '',
'profile': 'HD-1080p',
'root_folder': '/movies/'
},
'filters': {
'shows': {
'blacklisted_genres': [],
'blacklisted_networks': [],
'allowed_countries': [],
'blacklisted_min_runtime': 15,
'blacklisted_min_year': 2000,
'blacklisted_max_year': 2019,
'blacklisted_tvdb_ids': [],
},
'movies': {
'blacklisted_genres': [],
'blacklisted_min_runtime': 60,
'blacklisted_min_year': 2000,
'blacklisted_max_year': 2019,
'blacklist_title_keywords': [],
'blacklisted_tmdb_ids': [],
'allowed_countries': []
}
},
'automatic': {
'movies': {
'interval': 20,
'anticipated': 3,
'trending': 3,
'popular': 3,
'boxoffice': 10
},
'shows': {
'interval': 48,
'anticipated': 10,
'trending': 1,
'popular': 1
}
},
'notifications': {
'verbose': True
}
}
cfg = None
class AttrConfig(AttrDict): class AttrConfig(AttrDict):
@ -85,77 +32,154 @@ class AttrConfig(AttrDict):
return None return None
def build_config(): class Config(object, metaclass=Singleton):
if not os.path.exists(config_path):
print("Dumping default config to: %s" % config_path) base_config = {
with open(config_path, 'w') as fp: 'core': {
json.dump(base_config, fp, sort_keys=True, indent=2) 'debug': False
return True },
else: 'trakt': {
return False 'api_key': ''
},
'sonarr': {
def dump_config(): 'url': 'http://localhost:8989/',
if os.path.exists(config_path): 'api_key': '',
with open(config_path, 'w') as fp: 'profile': 'HD-1080p',
json.dump(cfg, fp, sort_keys=True, indent=2) 'root_folder': '/tv/',
return True 'tags': {
else: }
return False },
'radarr': {
'url': 'http://localhost:7878/',
def load_config(): 'api_key': '',
with open(config_path, 'r') as fp: 'profile': 'HD-1080p',
return AttrConfig(json.load(fp)) 'root_folder': '/movies/'
},
'filters': {
def upgrade_settings(defaults, currents): 'shows': {
upgraded = False 'blacklisted_genres': [],
'blacklisted_networks': [],
def inner_upgrade(default, current, key=None): 'allowed_countries': [],
sub_upgraded = False 'blacklisted_min_runtime': 15,
merged = current.copy() 'blacklisted_min_year': 2000,
if isinstance(default, dict): 'blacklisted_max_year': 2019,
for k, v in default.items(): 'blacklisted_tvdb_ids': [],
# missing k },
if k not in current: 'movies': {
merged[k] = v 'blacklisted_genres': [],
sub_upgraded = True 'blacklisted_min_runtime': 60,
if not key: 'blacklisted_min_year': 2000,
print("Added %r config option: %s" % (str(k), str(v))) 'blacklisted_max_year': 2019,
else: 'blacklist_title_keywords': [],
print("Added %r to config option %r: %s" % (str(k), str(key), str(v))) 'blacklisted_tmdb_ids': [],
continue 'allowed_countries': []
# iterate children }
if isinstance(v, dict) or isinstance(v, list): },
did_upgrade, merged[k] = inner_upgrade(default[k], current[k], key=k) 'automatic': {
sub_upgraded = did_upgrade if did_upgrade else sub_upgraded 'movies': {
'interval': 20,
elif isinstance(default, list) and key: 'anticipated': 3,
for v in default: 'trending': 3,
if v not in current: 'popular': 3,
merged.append(v) 'boxoffice': 10
sub_upgraded = True },
print("Added to config option %r: %s" % (str(key), str(v))) 'shows': {
continue 'interval': 48,
return sub_upgraded, merged 'anticipated': 10,
'trending': 1,
upgraded, upgraded_settings = inner_upgrade(defaults, currents) 'popular': 1
return upgraded, AttrConfig(upgraded_settings) }
},
'notifications': {
############################################################ 'verbose': True
# LOAD CFG }
############################################################ }
# dump/load config def __init__(self, config_path, logfile):
if build_config(): """Initializes config"""
print("Please edit the default configuration before running again!") self.conf = None
sys.exit(0)
else: self.config_path = config_path
tmp = load_config() self.log_path = logfile
upgraded, cfg = upgrade_settings(base_config, tmp)
if upgraded: @property
dump_config() def cfg(self):
print("New config options were added, adjust and restart!") # Return existing loaded config
sys.exit(0) if self.conf:
return self.conf
# Built initial config if it doesn't exist
if self.build_config():
print("Please edit the default configuration before running again!")
sys.exit(0)
# Load config, upgrade if necessary
else:
tmp = self.load_config()
self.conf, upgraded = self.upgrade_settings(tmp)
# Save config if upgraded
if upgraded:
self.dump_config()
print("New config options were added, adjust and restart!")
sys.exit(0)
return self.conf
@property
def logfile(self):
return self.log_path
def build_config(self):
if not os.path.exists(self.config_path):
print("Dumping default config to: %s" % self.config_path)
with open(self.config_path, 'w') as fp:
json.dump(self.base_config, fp, sort_keys=True, indent=2)
return True
else:
return False
def dump_config(self):
if os.path.exists(self.config_path):
with open(self.config_path, 'w') as fp:
json.dump(self.conf, fp, sort_keys=True, indent=2)
return True
else:
return False
def load_config(self):
with open(self.config_path, 'r') as fp:
return AttrConfig(json.load(fp))
def upgrade_settings(self, currents):
upgraded = False
def inner_upgrade(default, current, key=None):
sub_upgraded = False
merged = current.copy()
if isinstance(default, dict):
for k, v in default.items():
# missing k
if k not in current:
merged[k] = v
sub_upgraded = True
if not key:
print("Added %r config option: %s" % (str(k), str(v)))
else:
print("Added %r to config option %r: %s" % (str(k), str(key), str(v)))
continue
# iterate children
if isinstance(v, dict) or isinstance(v, list):
merged[k], did_upgrade = inner_upgrade(default[k], current[k], key=k)
sub_upgraded = did_upgrade if did_upgrade else sub_upgraded
elif isinstance(default, list) and key:
for v in default:
if v not in current:
merged.append(v)
sub_upgraded = True
print("Added to config option %r: %s" % (str(key), str(v)))
continue
return merged, sub_upgraded
upgraded_settings, upgraded = inner_upgrade(self.base_config, currents)
return AttrConfig(upgraded_settings), upgraded

@ -3,7 +3,7 @@ import os
import sys import sys
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from misc.config import cfg from misc.config import Config
class Logger: class Logger:
@ -49,4 +49,5 @@ class Logger:
return self.root_logger.getChild(name) return self.root_logger.getChild(name)
logger = Logger('activity.log', logging.DEBUG if cfg.core.debug else logging.INFO) # Default logger
logger = Logger(Config().logfile, logging.DEBUG if Config().cfg.core.debug else logging.INFO)

@ -1,33 +1,55 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os.path
import time import time
import click import click
import schedule import schedule
from media.radarr import Radarr
from media.sonarr import Sonarr
from media.trakt import Trakt
from misc import helpers
from misc.config import cfg
from misc.log import logger
from notifications import Notifications
############################################################ ############################################################
# INIT # INIT
############################################################ ############################################################
cfg = None
# Logging log = None
log = logger.get_logger('traktarr') notify = None
# 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.')
@click.version_option('1.1.2', prog_name='traktarr') @click.version_option('1.1.2', prog_name='traktarr')
def app(): @click.option(
pass '--config',
envvar='TRAKTARR_CONFIG',
type=click.Path(file_okay=True, dir_okay=False),
help='Configuration file',
show_default=True,
default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
)
@click.option(
'--logfile',
envvar='TRAKTARR_LOGFILE',
type=click.Path(file_okay=True, dir_okay=False),
help='Log file',
show_default=True,
default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "activity.log")
)
def app(config, logfile):
# Setup global variables
global cfg, log, notify
# Load config
from misc.config import Config
cfg = Config(config_path=config, logfile=logfile).cfg
# Load logger
from misc.log import logger
log = logger.get_logger('traktarr')
# Load notifications
from notifications import Notifications
notify = Notifications()
# Notifications
init_notifications()
############################################################ ############################################################
@ -44,6 +66,10 @@ def app():
@click.option('--no-search', is_flag=True, help='Disable search when adding shows to Sonarr.') @click.option('--no-search', is_flag=True, help='Disable search when adding shows to Sonarr.')
@click.option('--notifications', is_flag=True, help='Send notifications.') @click.option('--notifications', is_flag=True, help='Send notifications.')
def shows(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_search=False, notifications=False): def shows(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_search=False, notifications=False):
from media.sonarr import Sonarr
from media.trakt import Trakt
from misc import helpers
added_shows = 0 added_shows = 0
# remove genre from shows blacklisted_genres if supplied # remove genre from shows blacklisted_genres if supplied
@ -214,6 +240,10 @@ def shows(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_sea
@click.option('--no-search', is_flag=True, help='Disable search when adding movies to Radarr.') @click.option('--no-search', is_flag=True, help='Disable search when adding movies to Radarr.')
@click.option('--notifications', is_flag=True, help='Send notifications.') @click.option('--notifications', is_flag=True, help='Send notifications.')
def movies(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_search=False, notifications=False): def movies(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_search=False, notifications=False):
from media.radarr import Radarr
from media.trakt import Trakt
from misc import helpers
added_movies = 0 added_movies = 0
# remove genre from movies blacklisted_genres if supplied # remove genre from movies blacklisted_genres if supplied
@ -500,5 +530,4 @@ def init_notifications():
############################################################ ############################################################
if __name__ == "__main__": if __name__ == "__main__":
init_notifications()
app() app()

Loading…
Cancel
Save