Add ability to fetch from watchlists and other private lists

pull/54/head
Mitchell Klijs 7 years ago
parent 5d3b74f120
commit ccf9fdf361

@ -8,6 +8,8 @@ Trakt lists currently supported:
- popular
- trending
Furthermore, watchlist and other types of list from multiple users are supported.
# Requirements
1. Python 3.5 or higher (`sudo apt install python3 python3-pip`).
@ -28,7 +30,38 @@ Install Traktarr to be run with `traktarr` command.
7. `traktarr` - run once to generate a default a config.json file.
8. `nano config.json` - edit preferences.
## 2. Setup Schedule
## 2. Authenticate Trakt (optional)
If you want to acces private lists (watchlists or other user lists),
you'll need to authenticate Traktarr to access your personal lists.
1. Create an Trakt application by going [here](https://trakt.tv/oauth/applications/new)
2. Enter a name for your application; for example `Traktarr`
3. Enter `urn:ietf:wg:oauth:2.0:oob` in the Redirecht uri field.
4. Click "Save app"
5. Open the Traktarr configuration file `config.json` and insert the
Client ID in the api_key and the Client Secret in the api_secret. Like this:
```
{
"trakt": {
"api_key": "my_client_id",
"api_secret": "my_client_secret_key"
}
}
```
Repeat the following steps for every user you want to authenticate:
1. Run `traktarr trakt_authenticate` (from the installation path)
2. Go to https://trakt.tv/activate.
3. Enter the code you see in your terminal
4. Click continue
5. If you're not loggedin to Trakt, login noe
6. Accept
You've now authenticated the user.
You can repeat this process for as many users as you want.
## 3. Setup Schedule
To have Traktarr get Movies and Shows for you automatically, on set interval.
@ -52,13 +85,25 @@ To have Traktarr get Movies and Shows for you automatically, on set interval.
"boxoffice": 10,
"interval": 24,
"popular": 3,
"trending": 2
"trending": 2,
"watchlist": {
"username": 10
},
"my-custom-list" {
"username": 10
}
},
"shows": {
"anticipated": 10,
"interval": 48,
"popular": 1,
"trending": 2
"trending": 2,
"watchlist": {
"username": 10
},
"my-custom-list" {
"username": 10
}
}
},
"filters": {
@ -149,7 +194,8 @@ To have Traktarr get Movies and Shows for you automatically, on set interval.
"url": "http://localhost:8989/"
},
"trakt": {
"api_key": ""
"api_key": "",
"api_secret": ""
}
}
```
@ -179,13 +225,33 @@ Movies can be run on a separate schedule from Shows.
"boxoffice": 10,
"interval": 24,
"popular": 3,
"trending": 2
"trending": 2,
"watchlist": {
"user1": 10
"user2": 10
},
"my-custom-list": {
"user1": 10
},
"another-custom-list": {
"user2": 10
}
},
"shows": {
"anticipated": 10,
"interval": 48,
"popular": 1,
"trending": 2
"trending": 2,
"watchlist": {
"user1": 10
"user2": 10
},
"my-custom-list": {
"user1": 10
},
"another-custom-list": {
"user2": 10
}
}
},
```
@ -194,6 +260,10 @@ Movies can be run on a separate schedule from Shows.
`anticipated`, `popular`, `trending`, `boxoffice` (movies only) - specify how many items from each Trakt list to find.
`watchlist` - add every user you want to fetch items from and specify how many items to fetch
You can add every (private) list you want by adding the list key.
## Filters
Use filters to specify the movie/shows's country of origin or blacklist (i.e. filter-out) certain keywords, genres, years, runtime, or specific movies/shows.
@ -437,14 +507,7 @@ Finally, we will edit the Traktarr config and assign the `AMZN` tag to certain n
`api_key` - Fill in your Trakt API key (_Client ID_).
How to get a Trakt API Key:
- Go to https://trakt.tv/oauth/applications/new
- Fill in:
- Name: `Traktarr`
- Redirect uri: `https://google.com`
- Click `Save App`
- Retrieve the _Client ID_.
`api_secret` - Fill in your Trakt Secret key (_Client Scret_)
# Usage
@ -467,9 +530,10 @@ Options:
--help Show this message and exit.
Commands:
movies Add new movies to Radarr.
run Run in automatic mode.
shows Add new shows to Sonarr.
movies Add new movies to Radarr.
run Run in automatic mode.
shows Add new shows to Sonarr.
trakt_authentication Authenticate Traktrarr to index your personal...
```
@ -486,8 +550,9 @@ Usage: traktarr movies [OPTIONS]
Add new movies to Radarr.
Options:
-t, --list-type [anticipated|trending|popular|boxoffice]
Trakt list to process. [required]
-t, --list-type TEXT Trakt list to process. For example, anticipated,
trending, popular, boxoffice, watchlist or any
other user list [required]
-l, --add-limit INTEGER Limit number of movies added to Radarr.
[default: 0]
-d, --add-delay FLOAT Seconds between each add request to Radarr.
@ -496,6 +561,8 @@ Options:
-f, --folder TEXT Add movies with this root folder to Radarr.
--no-search Disable search when adding movies to Radarr.
--notifications Send notifications.
--user TEXT Specify which user to use for the personal Trakt
lists. Default: first user in the config
--help Show this message and exit.
```
@ -508,8 +575,9 @@ Usage: traktarr shows [OPTIONS]
Add new shows to Sonarr.
Options:
-t, --list-type [anticipated|trending|popular]
Trakt list to process. [required]
-t, --list-type TEXT Trakt list to process. For example, anticipated,
trending, popular, watchlist or any other user
list [required]
-l, --add-limit INTEGER Limit number of shows added to Sonarr.
[default: 0]
-d, --add-delay FLOAT Seconds between each add request to Sonarr.
@ -518,6 +586,8 @@ Options:
-f, --folder TEXT Add shows with this root folder to Sonarr.
--no-search Disable search when adding shows to Sonarr.
--notifications Send notifications.
--user TEXT Specify which user to use for the personal Trakt
lists. Default: first user in the config
--help Show this message and exit.
```

@ -15,8 +15,12 @@ def backoff_handler(details):
class Trakt:
def __init__(self, api_key):
self.api_key = api_key
non_user_lists = ['anticipated', 'trending', 'popular', 'boxoffice']
def __init__(self, cfg):
self.cfg = cfg
self.api_key = cfg.trakt.api_key
self.api_secret = cfg.trakt.api_secret
self.headers = {
'Content-Type': 'application/json',
'trakt-api-version': '2',
@ -44,6 +48,142 @@ class Trakt:
log.exception("Exception validating api_key: ")
return False
############################################################
# OAuth Authentication Initialisation
############################################################
def __oauth_request_device_code(self):
log.info("We're talking to Trakt to get your verification code. Please wait a moment...")
payload = {'client_id': self.api_key}
# Request device code
req = requests.post('https://api.trakt.tv/oauth/device/code', params=payload, headers=self.headers)
device_code_response = req.json()
# Display needed information to the user
log.info('Go to: %s on any device and enter %s. We\'ll be polling Trakt every %s seconds for a reply',
device_code_response['verification_url'], device_code_response['user_code'],
device_code_response['interval'])
return device_code_response
def __oauth_process_token_request(self, req):
success = False
if req.status_code == 200:
# Success; saving the access token
access_token_response = req.json()
access_token = access_token_response['access_token']
# But first we need to find out what user this token belongs to
temp_headers = self.headers
temp_headers['Authorization'] = 'Bearer ' + access_token
req = requests.get('https://api.trakt.tv/users/me', headers=temp_headers)
from misc.config import Config
new_config = Config()
new_config.merge_settings({
"trakt": {
req.json()['username']: access_token_response
}
})
success = True
elif req.status_code == 404:
log.debug('The device code was wrong')
log.error('Whoops, something went wrong; aborting the authentication process')
elif req.status_code == 409:
log.error('You\'ve already authenticated this application; aborting the authentication process')
elif req.status_code == 410:
log.error('The authentication process has expired; please start again')
elif req.status_code == 418:
log.error('You\'ve denied the authentication; are you sure? Please try again')
elif req.status_code == 429:
log.debug('We\'re polling too quickly.')
return success, req.status_code
def __oauth_poll_for_access_token(self, device_code, polling_interval=5, polling_expire=600):
polling_start = time.time()
time.sleep(polling_interval)
tries = 0
while time.time() - polling_start < polling_expire:
tries += 1
log.debug('Polling Trakt for the %sth time; %s seconds left', tries,
polling_expire - round(time.time() - polling_start))
payload = {'code': device_code, 'client_id': self.api_key, 'client_secret': self.api_secret,
'grant_type': 'authorization_code'}
# Poll Trakt for access token
req = requests.post('https://api.trakt.tv/oauth/device/token', params=payload, headers=self.headers)
success, status_code = self.__oauth_process_token_request(req)
if success:
break
elif status_code == 426:
log.debug('Increasing the interval by one second')
polling_interval += 1
time.sleep(polling_interval)
return False
def __oauth_refresh_access_token(self, refresh_token):
# TODO Doesn't work
payload = {'refresh_token': refresh_token, 'client_id': self.api_key, 'client_secret': self.api_secret,
'grant_type': 'refresh_token'}
req = requests.post('https://api.trakt.tv/oauth/token', params=payload, headers=self.headers)
success, status_code = self.__oauth_process_token_request(req)
return success
def oauth_authentication(self):
try:
device_code_response = self.__oauth_request_device_code()
if self.__oauth_poll_for_access_token(device_code_response['device_code'],
device_code_response['interval'],
device_code_response['expires_in']):
return True
except Exception:
log.exception("Exception occurred when authenticating user")
return False
def oauth_headers(self, user):
headers = self.headers
if user is None:
users = self.cfg['trakt']
users.pop('api_key')
users.pop('api_secret')
user = list(users.keys())[0]
token_information = self.cfg['trakt'][user]
# Check if the acces_token for the user is expired
expires_at = token_information['created_at'] + token_information['expires_in']
if expires_at < round(time.time()):
log.info("The access token for the user %s has expired. We're requesting a new one; please wait a moment.",
user)
if self.__oauth_refresh_access_token(token_information["refresh_token"]):
log.info("The access token for the user %s has been refreshed. Please restart the application.",
user)
headers['Authorization'] = 'Bearer ' + token_information['access_token']
return headers
############################################################
# Shows
############################################################
@ -108,6 +248,133 @@ class Trakt:
log.exception("Exception retrieving anticipated shows: ")
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def get_watchlist_shows(self, user=None, limit=1000, languages=None):
try:
processed_shows = []
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/sync/watchlist/shows', params=payload,
headers=self.oauth_headers(user),
timeout=30)
log.debug("Request User: %s", user)
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 show in resp_json:
if show not in processed_shows:
processed_shows.append(show)
# 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)
elif req.status_code == 401:
log.error("The authentication to Trakt is revoked. Please re-authenticate.")
exit()
else:
log.error("Failed to retrieve shows on watchlist from %s, request response: %d", user,
req.status_code)
break
if len(processed_shows):
log.debug("Found %d shows on watchlist from %s", len(processed_shows), user)
return processed_shows
return None
except Exception:
log.exception("Exception retrieving shows on watchlist")
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def get_user_list_shows(self, list_id, user=None, limit=1000, languages=None):
try:
processed_shows = []
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/users/' + user + '/lists/' + list_id + '/items/shows',
params=payload,
headers=self.oauth_headers(user),
timeout=30)
log.debug("Request User: %s", user)
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 show in resp_json:
if show not in processed_shows:
processed_shows.append(show)
# 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)
elif req.status_code == 401:
log.error("The authentication to Trakt is revoked. Please re-authenticate.")
exit()
else:
log.error("Failed to retrieve shows on watchlist from %s, request response: %d", user,
req.status_code)
break
if len(processed_shows):
log.debug("Found %d shows on watchlist from %s", len(processed_shows), user)
return processed_shows
return None
except Exception:
log.exception("Exception retrieving shows on watchlist")
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def get_trending_shows(self, limit=1000, languages=None):
try:
@ -473,3 +740,130 @@ class Trakt:
except Exception:
log.exception("Exception retrieving boxoffice movies: ")
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def get_watchlist_movies(self, user=None, 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/sync/watchlist/movies', params=payload,
headers=self.oauth_headers(user),
timeout=30)
log.debug("Request User: %s", user)
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 show in resp_json:
if show not in processed_movies:
processed_movies.append(show)
# 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)
elif req.status_code == 401:
log.error("The authentication to Trakt is revoked. Please re-authenticate.")
exit()
else:
log.error("Failed to retrieve movies on watchlist from %s, request response: %d", user,
req.status_code)
break
if len(processed_movies):
log.debug("Found %d movies on watchlist from %s", len(processed_movies), user)
return processed_movies
return None
except Exception:
log.exception("Exception retrieving movies on watchlist")
return None
@backoff.on_predicate(backoff.expo, lambda x: x is None, max_tries=4, on_backoff=backoff_handler)
def get_user_list_movies(self, list_id, user=None, 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/users/' + user + '/lists/' + list_id + '/items/movies',
params=payload,
headers=self.oauth_headers(user),
timeout=30)
log.debug("Request User: %s", user)
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 show in resp_json:
if show not in processed_movies:
processed_movies.append(show)
# 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)
elif req.status_code == 401:
log.error("The authentication to Trakt is revoked. Please re-authenticate.")
exit()
else:
log.error("Failed to retrieve movies on watchlist from %s, request response: %d", user,
req.status_code)
break
if len(processed_movies):
log.debug("Found %d movies on watchlist from %s", len(processed_movies), user)
return processed_movies
return None
except Exception:
log.exception("Exception retrieving movies on watchlist")
return None

@ -33,7 +33,6 @@ class AttrConfig(AttrDict):
class Config(object, metaclass=Singleton):
base_config = {
'core': {
'debug': False
@ -150,36 +149,55 @@ class Config(object, metaclass=Singleton):
with open(self.config_path, 'r') as fp:
return AttrConfig(json.load(fp))
def __inner_upgrade(self, settings1, settings2, key=None, overwrite=False):
sub_upgraded = False
merged = settings2.copy()
# print(settings1)
# print(settings2)
# print(overwrite)
# print("_______________")
if isinstance(settings1, dict):
for k, v in settings1.items():
# missing k
if k not in settings2:
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 = self.__inner_upgrade(settings1[k], settings2[k], key=k,
overwrite=overwrite)
sub_upgraded = did_upgrade if did_upgrade else sub_upgraded
elif settings1[k] != settings2[k] and overwrite:
merged = settings1
sub_upgraded = True
elif isinstance(settings1, list) and key:
for v in settings1:
if v not in settings2:
merged.append(v)
sub_upgraded = True
print("Added to config option %r: %s" % (str(key), str(v)))
continue
return merged, sub_upgraded
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)
upgraded_settings, upgraded = self.__inner_upgrade(self.base_config, currents)
return AttrConfig(upgraded_settings), upgraded
def merge_settings(self, settings_to_merge):
upgraded_settings, upgraded = self.__inner_upgrade(settings_to_merge, self.conf, overwrite=True)
self.conf = upgraded_settings
if upgraded:
self.dump_config()
return AttrConfig(upgraded_settings), upgraded

@ -53,20 +53,40 @@ def app(config, logfile):
init_notifications()
############################################################
# Trakt OAuth
############################################################
@app.command(help='Authenticate Traktrarr to index your personal lists')
def trakt_authentication():
from media.trakt import Trakt
trakt = Trakt(cfg)
response = trakt.oauth_authentication()
if response:
log.info("Authentication information saved; please restart the application")
exit()
############################################################
# SHOWS
############################################################
@app.command(help='Add new shows to Sonarr.')
@click.option('--list-type', '-t', type=click.Choice(['anticipated', 'trending', 'popular']),
help='Trakt list to process.', required=True)
@click.option('--list-type', '-t',
help='Trakt list to process. For example, anticipated, trending, popular, watchlist or any other user list',
required=True)
@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('--genre', '-g', default=None, help='Only add shows from this genre to Sonarr.')
@click.option('--folder', '-f', default=None, help='Add shows with this root folder 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.')
def shows(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_search=False, notifications=False):
@click.option('--user',
help='Specify which user to use for the personal Trakt lists. Default: first user in the config')
def shows(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_search=False, notifications=False,
user=None):
from media.sonarr import Sonarr
from media.trakt import Trakt
from misc import helpers
@ -82,7 +102,7 @@ def shows(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_sea
cfg['sonarr']['root_folder'] = folder
# validate trakt api_key
trakt = Trakt(cfg.trakt.api_key)
trakt = Trakt(cfg)
if not trakt.validate_api_key():
log.error("Aborting due to failure to validate Trakt API Key")
if notifications:
@ -144,12 +164,11 @@ def shows(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_sea
trakt_series_list = trakt.get_trending_shows()
elif list_type.lower() == 'popular':
trakt_series_list = trakt.get_popular_shows()
elif list_type.lower() == 'watchlist':
trakt_series_list = trakt.get_watchlist_shows(user)
else:
log.error("Aborting due to unknown Trakt list type")
if notifications:
callback_notify({'event': 'abort', 'type': 'shows', 'list_type': list_type,
'reason': 'Failure to determine Trakt list type'})
return None
trakt_series_list = trakt.get_user_list_shows(list_type, user)
if not trakt_series_list:
log.error("Aborting due to failure to retrieve Trakt %s shows list", list_type)
if notifications:
@ -232,15 +251,19 @@ def shows(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_sea
############################################################
@app.command(help='Add new movies to Radarr.')
@click.option('--list-type', '-t', type=click.Choice(['anticipated', 'trending', 'popular', 'boxoffice']),
help='Trakt list to process.', required=True)
@click.option('--list-type', '-t',
help='Trakt list to process. For example, anticipated, trending, popular, boxoffice, watchlist or any other user list',
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('--genre', '-g', default=None, help='Only add movies from this genre to Radarr.')
@click.option('--folder', '-f', default=None, help='Add movies with this root folder 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.')
def movies(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_search=False, notifications=False):
@click.option('--user',
help='Specify which user to use for the personal Trakt lists. Default: first user in the config')
def movies(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_search=False, notifications=False,
user=None):
from media.radarr import Radarr
from media.trakt import Trakt
from misc import helpers
@ -256,7 +279,7 @@ def movies(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_se
cfg['radarr']['root_folder'] = folder
# validate trakt api_key
trakt = Trakt(cfg.trakt.api_key)
trakt = Trakt(cfg)
if not trakt.validate_api_key():
log.error("Aborting due to failure to validate Trakt API Key")
if notifications:
@ -310,12 +333,11 @@ def movies(list_type, add_limit=0, add_delay=2.5, genre=None, folder=None, no_se
trakt_movies_list = trakt.get_popular_movies()
elif list_type.lower() == 'boxoffice':
trakt_movies_list = trakt.get_boxoffice_movies()
elif list_type.lower() == 'watchlist':
trakt_movies_list = trakt.get_watchlist_shows(user)
else:
log.error("Aborting due to unknown Trakt list type")
if notifications:
callback_notify({'event': 'abort', 'type': 'movies', 'list_type': list_type,
'reason': 'Failure to determine Trakt list type'})
return None
trakt_movies_list = trakt.get_user_list_shows(list_type, user)
if not trakt_movies_list:
log.error("Aborting due to failure to retrieve Trakt %s movies list", list_type)
if notifications:
@ -412,23 +434,42 @@ def callback_notify(data):
def automatic_shows(add_delay=2.5, no_search=False, notifications=False):
from media.trakt import Trakt
total_shows_added = 0
try:
log.info("Started")
for list_type, type_amount in cfg.automatic.shows.items():
for list_type, value 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
if list_type.lower() not in Trakt.non_user_lists:
type_amount = value
if 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)
else:
log.info("Adding %d shows from Trakt's %s list", type_amount, list_type)
for user, type_amount in value:
if type_amount <= 0:
log.info("Skipped Trakt's %s for &s", list_type, user)
continue
else:
log.info("Adding %d shows from the %s from &s", type_amount, list_type, user)
# run shows
added_shows = shows.callback(list_type=list_type, add_limit=type_amount,
add_delay=add_delay, no_search=no_search,
notifications=notifications, user=user)
# 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)
@ -449,23 +490,42 @@ def automatic_shows(add_delay=2.5, no_search=False, notifications=False):
def automatic_movies(add_delay=2.5, no_search=False, notifications=False):
from media.trakt import Trakt
total_movies_added = 0
try:
log.info("Started")
for list_type, type_amount in cfg.automatic.movies.items():
for list_type, value 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
if list_type.lower() not in Trakt.non_user_lists:
type_amount = value
if 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)
else:
log.info("Adding %d movies from Trakt's %s list", type_amount, list_type)
for user, type_amount in value:
if type_amount <= 0:
log.info("Skipped Trakt's %s for &s", list_type, user)
continue
else:
log.info("Adding %d movies from the %s from &s", type_amount, list_type, user)
# run movies
added_movies = movies.callback(list_type=list_type, add_limit=type_amount,
add_delay=add_delay, no_search=no_search,
notifications=notifications, user=user)
# 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)

Loading…
Cancel
Save