Merge pull request #9 from l3uddz/develop

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

@ -0,0 +1,23 @@
# Git
.git
# Systemd files
systemd
# Logs
*.log*
# Configs
*.json
# Byte-compiled / optimized / DLL files
**/__pycache__
*.py[cod]
*$py.class
*.pyc
# Pyenv
**/.python-version
# User-specific stuff:
.idea

@ -0,0 +1,31 @@
FROM python:3.6-alpine3.7
ENV \
# App directory
APP_DIR=traktarr \
# Branch to clone
BRANCH=master \
# Config file
TRAKTARR_CONFIG=/config/config.json \
# Log file
TRAKTARR_LOGFILE=/config/traktarr.log
RUN \
echo "** Upgrade all packages **" && \
apk --no-cache -U upgrade && \
echo "** Install OS dependencies **" && \
apk --no-cache -U add git && \
echo "** Get Traktarr **" && \
git clone --depth 1 --branch ${BRANCH} https://github.com/l3uddz/traktarr.git /${APP_DIR} && \
echo "** Install PIP dependencies **" && \
pip install --no-cache-dir --upgrade pip setuptools && \
pip install --no-cache-dir --upgrade -r /${APP_DIR}/requirements.txt
# Change directory
WORKDIR /${APP_DIR}
# Config volume
VOLUME /config
# Entrypoint
ENTRYPOINT ["python", "traktarr.py"]

@ -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

@ -1,8 +1,10 @@
from urllib.parse import urljoin import os.path
import backoff import backoff
import requests import requests
from misc import helpers
from misc import str as misc_str
from misc.log import logger from misc.log import logger
log = logger.get_logger(__name__) log = logger.get_logger(__name__)
@ -26,7 +28,11 @@ 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(
os.path.join(misc_str.ensure_endswith(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():
@ -40,7 +46,11 @@ 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(
os.path.join(misc_str.ensure_endswith(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)
@ -58,7 +68,11 @@ 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(
os.path.join(misc_str.ensure_endswith(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,26 +95,45 @@ 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(
os.path.join(misc_str.ensure_endswith(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: %d", req.status_code) log.debug("Request Response Code: %d", req.status_code)
log.debug("Request Response Text:\n%s", req.text)
response_json = None
if 'json' in req.headers['Content-Type'].lower():
response_json = helpers.get_response_dict(req.json(), 'tmdbId', movie_tmdbid)
if (req.status_code == 201 or req.status_code == 200) and req.json()['tmdbId'] == movie_tmdbid: if (req.status_code == 201 or req.status_code == 200) and (response_json and 'tmdbId' in response_json) \
and response_json['tmdbId'] == movie_tmdbid:
log.debug("Successfully added %s (%d)", movie_title, movie_tmdbid) log.debug("Successfully added %s (%d)", movie_title, movie_tmdbid)
return True return True
elif 'json' in req.headers['Content-Type'].lower() and 'message' in req.text: elif response_json and 'message' in response_json:
log.error("Failed to add %s (%d) - status_code: %d, reason: %s", movie_title, movie_tmdbid, log.error("Failed to add %s (%d) - status_code: %d, reason: %s", movie_title, movie_tmdbid,
req.status_code, req.json()['message']) req.status_code, response_json['message'])
return False return False
else: else:
log.error("Failed to add %s (%d), unexpected response:\n%s", movie_title, movie_tmdbid, req.text) log.error("Failed to add %s (%d), unexpected response:\n%s", movie_title, movie_tmdbid, req.text)

@ -1,8 +1,10 @@
from urllib.parse import urljoin import os.path
import backoff import backoff
import requests import requests
from misc import helpers
from misc import str as misc_str
from misc.log import logger from misc.log import logger
log = logger.get_logger(__name__) log = logger.get_logger(__name__)
@ -19,14 +21,14 @@ 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(os.path.join(misc_str.ensure_endswith(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():
@ -40,7 +42,11 @@ 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(
os.path.join(misc_str.ensure_endswith(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)
@ -58,7 +64,11 @@ 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(
os.path.join(misc_str.ensure_endswith(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)
@ -80,7 +90,11 @@ 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(
os.path.join(misc_str.ensure_endswith(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)
@ -103,7 +117,11 @@ 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(
os.path.join(misc_str.ensure_endswith(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)
@ -125,28 +143,46 @@ 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,
'rootFolderPath': root_folder,
'addOptions': {
'ignoreEpisodesWithFiles': False,
'ignoreEpisodesWithoutFiles': False, 'ignoreEpisodesWithoutFiles': False,
'searchForMissingEpisodes': search_missing} '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(
os.path.join(misc_str.ensure_endswith(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: %d", req.status_code) log.debug("Request Response Code: %d", req.status_code)
log.debug("Request Response Text:\n%s", req.text)
response_json = None
if 'json' in req.headers['Content-Type'].lower():
response_json = helpers.get_response_dict(req.json(), 'tvdbId', series_tvdbid)
if (req.status_code == 201 or req.status_code == 200) and req.json()['tvdbId'] == series_tvdbid: if (req.status_code == 201 or req.status_code == 200) and (response_json and 'tvdbId' in response_json) \
and response_json['tvdbId'] == series_tvdbid:
log.debug("Successfully added %s (%d)", series_title, series_tvdbid) log.debug("Successfully added %s (%d)", series_title, series_tvdbid)
return True return True
elif 'json' in req.headers['Content-Type'].lower() and 'errorMessage' in req.text: elif response_json and 'errorMessage' in response_json:
log.error("Failed to add %s (%d) - status_code: %d, reason: %s", series_title, series_tvdbid, log.error("Failed to add %s (%d) - status_code: %d, reason: %s", series_title, series_tvdbid,
req.status_code, req.json()['errorMessage']) req.status_code, response_json['errorMessage'])
return False return False
else: else:
log.error("Failed to add %s (%d), unexpected response:\n%s", series_title, series_tvdbid, req.text) log.error("Failed to add %s (%d), unexpected response:\n%s", series_title, series_tvdbid, req.text)

@ -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,7 +4,36 @@ 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')
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class AttrConfig(AttrDict):
"""
Simple AttrDict subclass to return None when requested attribute does not exist
"""
def __init__(self, config):
super().__init__(config)
def __getattr__(self, item):
try:
return super().__getattr__(item)
except AttributeError:
pass
# Default behaviour
return None
class Config(object, metaclass=Singleton):
base_config = { base_config = {
'core': { 'core': {
'debug': False 'debug': False
@ -13,7 +42,7 @@ base_config = {
'api_key': '' 'api_key': ''
}, },
'sonarr': { 'sonarr': {
'url': 'http://localhost:8989', 'url': 'http://localhost:8989/',
'api_key': '', 'api_key': '',
'profile': 'HD-1080p', 'profile': 'HD-1080p',
'root_folder': '/tv/', 'root_folder': '/tv/',
@ -21,7 +50,7 @@ base_config = {
} }
}, },
'radarr': { 'radarr': {
'url': 'http://localhost:7878', 'url': 'http://localhost:7878/',
'api_key': '', 'api_key': '',
'profile': 'HD-1080p', 'profile': 'HD-1080p',
'root_folder': '/movies/' 'root_folder': '/movies/'
@ -65,51 +94,63 @@ base_config = {
'verbose': True 'verbose': True
} }
} }
cfg = None
def __init__(self, config_path, logfile):
"""Initializes config"""
self.conf = None
class AttrConfig(AttrDict): self.config_path = config_path
""" self.log_path = logfile
Simple AttrDict subclass to return None when requested attribute does not exist
"""
def __init__(self, config): @property
super().__init__(config) def cfg(self):
# Return existing loaded config
if self.conf:
return self.conf
def __getattr__(self, item): # Built initial config if it doesn't exist
try: if self.build_config():
return super().__getattr__(item) print("Please edit the default configuration before running again!")
except AttributeError: sys.exit(0)
pass # Load config, upgrade if necessary
# Default behaviour else:
return None 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(): def build_config(self):
if not os.path.exists(config_path): if not os.path.exists(self.config_path):
print("Dumping default config to: %s" % config_path) print("Dumping default config to: %s" % self.config_path)
with open(config_path, 'w') as fp: with open(self.config_path, 'w') as fp:
json.dump(base_config, fp, sort_keys=True, indent=2) json.dump(self.base_config, fp, sort_keys=True, indent=2)
return True return True
else: else:
return False return False
def dump_config(self):
def dump_config(): if os.path.exists(self.config_path):
if os.path.exists(config_path): with open(self.config_path, 'w') as fp:
with open(config_path, 'w') as fp: json.dump(self.conf, fp, sort_keys=True, indent=2)
json.dump(cfg, fp, sort_keys=True, indent=2)
return True return True
else: else:
return False return False
def load_config(self):
def load_config(): with open(self.config_path, 'r') as fp:
with open(config_path, 'r') as fp:
return AttrConfig(json.load(fp)) return AttrConfig(json.load(fp))
def upgrade_settings(self, currents):
def upgrade_settings(defaults, currents):
upgraded = False upgraded = False
def inner_upgrade(default, current, key=None): def inner_upgrade(default, current, key=None):
@ -128,7 +169,7 @@ def upgrade_settings(defaults, currents):
continue continue
# iterate children # iterate children
if isinstance(v, dict) or isinstance(v, list): if isinstance(v, dict) or isinstance(v, list):
did_upgrade, merged[k] = inner_upgrade(default[k], current[k], key=k) merged[k], did_upgrade = inner_upgrade(default[k], current[k], key=k)
sub_upgraded = did_upgrade if did_upgrade else sub_upgraded sub_upgraded = did_upgrade if did_upgrade else sub_upgraded
elif isinstance(default, list) and key: elif isinstance(default, list) and key:
@ -138,24 +179,7 @@ def upgrade_settings(defaults, currents):
sub_upgraded = True sub_upgraded = True
print("Added to config option %r: %s" % (str(key), str(v))) print("Added to config option %r: %s" % (str(key), str(v)))
continue continue
return sub_upgraded, merged return merged, sub_upgraded
upgraded, upgraded_settings = inner_upgrade(defaults, currents)
return upgraded, AttrConfig(upgraded_settings)
############################################################ upgraded_settings, upgraded = inner_upgrade(self.base_config, currents)
# LOAD CFG return AttrConfig(upgraded_settings), upgraded
############################################################
# dump/load config
if build_config():
print("Please edit the default configuration before running again!")
sys.exit(0)
else:
tmp = load_config()
upgraded, cfg = upgrade_settings(base_config, tmp)
if upgraded:
dump_config()
print("New config options were added, adjust and restart!")
sys.exit(0)

@ -380,3 +380,33 @@ def trakt_is_movie_blacklisted(movie, blacklist_settings):
except Exception: except Exception:
log.exception("Exception determining if movie was blacklisted %s: ", movie) log.exception("Exception determining if movie was blacklisted %s: ", movie)
return blacklisted return blacklisted
############################################################
# MISC
############################################################
def get_response_dict(response, key_field=None, key_value=None):
found_response = None
try:
if isinstance(response, list):
if not key_field or not key_value:
found_response = response[0]
else:
for result in response:
if isinstance(result, dict) and key_field in result and result[key_field] == key_value:
found_response = result
break
if not found_response:
log.error("Unable to find a result with key %s where the value is %s", key_field, key_value)
elif isinstance(response, dict):
found_response = response
else:
log.error("Unexpected response instance type of %s for %s", type(response).__name__, response)
except Exception:
log.exception("Exception determining response for %s: ", response)
return found_response

@ -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)

@ -26,3 +26,10 @@ def is_ascii(string):
log.exception(u"Exception checking if %r was ascii: ", string) log.exception(u"Exception checking if %r was ascii: ", string)
return False return False
return True return True
def ensure_endswith(data, endswith_key):
if not data.strip().endswith(endswith_key):
return "%s%s" % (data.strip(), endswith_key)
else:
return data

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

@ -1,33 +1,56 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os.path
import sys
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
log = None
notify = None
# Logging
# Click
@click.group(help='Add new shows & movies to Sonarr/Radarr from Trakt lists.')
@click.version_option('1.1.3', prog_name='traktarr')
@click.option(
'--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.realpath(sys.argv[0])), "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.realpath(sys.argv[0])), "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') log = logger.get_logger('traktarr')
# Notifications # Load notifications
from notifications import Notifications
notify = Notifications() notify = Notifications()
# Notifications
# Click init_notifications()
@click.group(help='Add new shows & movies to Sonarr/Radarr from Trakt lists.')
@click.version_option('1.1.2', prog_name='traktarr')
def app():
pass
############################################################ ############################################################
@ -44,6 +67,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 +241,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 +531,4 @@ def init_notifications():
############################################################ ############################################################
if __name__ == "__main__": if __name__ == "__main__":
init_notifications()
app() app()

Loading…
Cancel
Save